xref: /haiku/src/apps/mediaplayer/playlist/Playlist.cpp (revision 7749d0bb0c358a3279b1b9cc76d8376e900130a5)
1 /*
2  * Playlist.cpp - Media Player for the Haiku Operating System
3  *
4  * Copyright (C) 2006 Marcus Overhagen <marcus@overhagen.de>
5  * Copyright (C) 2007-2009 Stephan Aßmus <superstippi@gmx.de> (MIT ok)
6  * Copyright (C) 2008-2009 Fredrik Modéen <[FirstName]@[LastName].se> (MIT ok)
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * version 2 as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20  *
21  */
22 
23 
24 #include "Playlist.h"
25 
26 #include <debugger.h>
27 #include <new>
28 #include <stdio.h>
29 
30 #include <AppFileInfo.h>
31 #include <Application.h>
32 #include <Autolock.h>
33 #include <Directory.h>
34 #include <Entry.h>
35 #include <File.h>
36 #include <Message.h>
37 #include <Mime.h>
38 #include <NodeInfo.h>
39 #include <Path.h>
40 #include <Roster.h>
41 #include <String.h>
42 
43 #include <QueryFile.h>
44 
45 #include "FilePlaylistItem.h"
46 #include "FileReadWrite.h"
47 #include "MainApp.h"
48 
49 using std::nothrow;
50 
51 // TODO: using BList for objects is bad, replace it with a template
52 
53 Playlist::Listener::Listener() {}
54 Playlist::Listener::~Listener() {}
55 void Playlist::Listener::ItemAdded(PlaylistItem* item, int32 index) {}
56 void Playlist::Listener::ItemRemoved(int32 index) {}
57 void Playlist::Listener::ItemsSorted() {}
58 void Playlist::Listener::CurrentItemChanged(int32 newIndex) {}
59 void Playlist::Listener::ImportFailed() {}
60 
61 
62 // #pragma mark -
63 
64 
65 static void
66 make_item_compare_string(const PlaylistItem* item, char* buffer,
67 	size_t bufferSize)
68 {
69 	// TODO: Maybe "location" would be useful here as well.
70 //	snprintf(buffer, bufferSize, "%s - %s - %0*ld - %s",
71 //		item->Author().String(),
72 //		item->Album().String(),
73 //		3, item->TrackNumber(),
74 //		item->Title().String());
75 	snprintf(buffer, bufferSize, "%s", item->LocationURI().String());
76 }
77 
78 
79 static int
80 playlist_item_compare(const void* _item1, const void* _item2)
81 {
82 	// compare complete path
83 	const PlaylistItem* item1 = *(const PlaylistItem**)_item1;
84 	const PlaylistItem* item2 = *(const PlaylistItem**)_item2;
85 
86 	static const size_t bufferSize = 1024;
87 	char string1[bufferSize];
88 	make_item_compare_string(item1, string1, bufferSize);
89 	char string2[bufferSize];
90 	make_item_compare_string(item2, string2, bufferSize);
91 
92 	return strcmp(string1, string2);
93 }
94 
95 
96 // #pragma mark -
97 
98 
99 Playlist::Playlist()
100 	:
101 	BLocker("playlist lock"),
102 	fItems(),
103  	fCurrentIndex(-1)
104 {
105 }
106 
107 
108 Playlist::~Playlist()
109 {
110 	MakeEmpty();
111 
112 	if (fListeners.CountItems() > 0)
113 		debugger("Playlist::~Playlist() - there are still listeners attached!");
114 }
115 
116 
117 // #pragma mark - archiving
118 
119 
120 static const char* kItemArchiveKey = "item";
121 
122 
123 status_t
124 Playlist::Unarchive(const BMessage* archive)
125 {
126 	if (archive == NULL)
127 		return B_BAD_VALUE;
128 
129 	MakeEmpty();
130 
131 	BMessage itemArchive;
132 	for (int32 i = 0;
133 		archive->FindMessage(kItemArchiveKey, i, &itemArchive) == B_OK; i++) {
134 
135 		BArchivable* archivable = instantiate_object(&itemArchive);
136 		PlaylistItem* item = dynamic_cast<PlaylistItem*>(archivable);
137 		if (!item) {
138 			delete archivable;
139 			continue;
140 		}
141 
142 		if (!AddItem(item)) {
143 			delete item;
144 			return B_NO_MEMORY;
145 		}
146 	}
147 
148 	return B_OK;
149 }
150 
151 
152 status_t
153 Playlist::Archive(BMessage* into) const
154 {
155 	if (into == NULL)
156 		return B_BAD_VALUE;
157 
158 	int32 count = CountItems();
159 	for (int32 i = 0; i < count; i++) {
160 		const PlaylistItem* item = ItemAtFast(i);
161 		BMessage itemArchive;
162 		status_t ret = item->Archive(&itemArchive);
163 		if (ret != B_OK)
164 			return ret;
165 		ret = into->AddMessage(kItemArchiveKey, &itemArchive);
166 		if (ret != B_OK)
167 			return ret;
168 	}
169 
170 	return B_OK;
171 }
172 
173 
174 const uint32 kPlaylistMagicBytes = 'MPPL';
175 const char* kTextPlaylistMimeString = "text/x-playlist";
176 const char* kBinaryPlaylistMimeString = "application/x-vnd.haiku-playlist";
177 
178 status_t
179 Playlist::Unflatten(BDataIO* stream)
180 {
181 	if (stream == NULL)
182 		return B_BAD_VALUE;
183 
184 	uint32 magicBytes;
185 	ssize_t read = stream->Read(&magicBytes, 4);
186 	if (read != 4) {
187 		if (read < 0)
188 			return (status_t)read;
189 		return B_IO_ERROR;
190 	}
191 
192 	if (B_LENDIAN_TO_HOST_INT32(magicBytes) != kPlaylistMagicBytes)
193 		return B_BAD_VALUE;
194 
195 	BMessage archive;
196 	status_t ret = archive.Unflatten(stream);
197 	if (ret != B_OK)
198 		return ret;
199 
200 	return Unarchive(&archive);
201 }
202 
203 
204 status_t
205 Playlist::Flatten(BDataIO* stream) const
206 {
207 	if (stream == NULL)
208 		return B_BAD_VALUE;
209 
210 	BMessage archive;
211 	status_t ret = Archive(&archive);
212 	if (ret != B_OK)
213 		return ret;
214 
215 	uint32 magicBytes = B_HOST_TO_LENDIAN_INT32(kPlaylistMagicBytes);
216 	ssize_t written = stream->Write(&magicBytes, 4);
217 	if (written != 4) {
218 		if (written < 0)
219 			return (status_t)written;
220 		return B_IO_ERROR;
221 	}
222 
223 	return archive.Flatten(stream);
224 }
225 
226 
227 // #pragma mark - list access
228 
229 
230 void
231 Playlist::MakeEmpty(bool deleteItems)
232 {
233 	int32 count = CountItems();
234 	for (int32 i = count - 1; i >= 0; i--) {
235 		PlaylistItem* item = RemoveItem(i, false);
236 		_NotifyItemRemoved(i);
237 		if (deleteItems)
238 			item->ReleaseReference();
239 	}
240 	SetCurrentItemIndex(-1);
241 }
242 
243 
244 int32
245 Playlist::CountItems() const
246 {
247 	return fItems.CountItems();
248 }
249 
250 
251 bool
252 Playlist::IsEmpty() const
253 {
254 	return fItems.IsEmpty();
255 }
256 
257 
258 void
259 Playlist::Sort()
260 {
261 	fItems.SortItems(playlist_item_compare);
262 	_NotifyItemsSorted();
263 }
264 
265 
266 bool
267 Playlist::AddItem(PlaylistItem* item)
268 {
269 	return AddItem(item, CountItems());
270 }
271 
272 
273 bool
274 Playlist::AddItem(PlaylistItem* item, int32 index)
275 {
276 	if (!fItems.AddItem(item, index))
277 		return false;
278 
279 	if (index <= fCurrentIndex)
280 		SetCurrentItemIndex(fCurrentIndex + 1);
281 
282 	_NotifyItemAdded(item, index);
283 
284 	return true;
285 }
286 
287 
288 bool
289 Playlist::AdoptPlaylist(Playlist& other)
290 {
291 	return AdoptPlaylist(other, CountItems());
292 }
293 
294 
295 bool
296 Playlist::AdoptPlaylist(Playlist& other, int32 index)
297 {
298 	if (&other == this)
299 		return false;
300 	// NOTE: this is not intended to merge two "equal" playlists
301 	// the given playlist is assumed to be a temporary "dummy"
302 	if (fItems.AddList(&other.fItems, index)) {
303 		// take care of the notifications
304 		int32 count = other.CountItems();
305 		for (int32 i = index; i < index + count; i++) {
306 			PlaylistItem* item = ItemAtFast(i);
307 			_NotifyItemAdded(item, i);
308 		}
309 		if (index <= fCurrentIndex)
310 			SetCurrentItemIndex(fCurrentIndex + count);
311 		// empty the other list, so that the PlaylistItems are now ours
312 		other.fItems.MakeEmpty();
313 		return true;
314 	}
315 	return false;
316 }
317 
318 
319 PlaylistItem*
320 Playlist::RemoveItem(int32 index, bool careAboutCurrentIndex)
321 {
322 	PlaylistItem* item = (PlaylistItem*)fItems.RemoveItem(index);
323 	if (!item)
324 		return NULL;
325 	_NotifyItemRemoved(index);
326 
327 	if (careAboutCurrentIndex) {
328 		if (index == fCurrentIndex && index >= CountItems())
329 			SetCurrentItemIndex(CountItems() - 1);
330 		else if (index < fCurrentIndex)
331 			SetCurrentItemIndex(fCurrentIndex - 1);
332 	}
333 
334 	return item;
335 }
336 
337 
338 int32
339 Playlist::IndexOf(PlaylistItem* item) const
340 {
341 	return fItems.IndexOf(item);
342 }
343 
344 
345 PlaylistItem*
346 Playlist::ItemAt(int32 index) const
347 {
348 	return (PlaylistItem*)fItems.ItemAt(index);
349 }
350 
351 
352 PlaylistItem*
353 Playlist::ItemAtFast(int32 index) const
354 {
355 	return (PlaylistItem*)fItems.ItemAtFast(index);
356 }
357 
358 
359 // #pragma mark - navigation
360 
361 
362 bool
363 Playlist::SetCurrentItemIndex(int32 index)
364 {
365 	bool result = true;
366 	if (index >= CountItems()) {
367 		index = CountItems() - 1;
368 		result = false;
369 	}
370 	if (index < 0) {
371 		index = -1;
372 		result = false;
373 	}
374 	if (index == fCurrentIndex)
375 		return result;
376 
377 	fCurrentIndex = index;
378 	_NotifyCurrentItemChanged(fCurrentIndex);
379 	return result;
380 }
381 
382 
383 int32
384 Playlist::CurrentItemIndex() const
385 {
386 	return fCurrentIndex;
387 }
388 
389 
390 void
391 Playlist::GetSkipInfo(bool* canSkipPrevious, bool* canSkipNext) const
392 {
393 	if (canSkipPrevious)
394 		*canSkipPrevious = fCurrentIndex > 0;
395 	if (canSkipNext)
396 		*canSkipNext = fCurrentIndex < CountItems() - 1;
397 }
398 
399 
400 // pragma mark -
401 
402 
403 bool
404 Playlist::AddListener(Listener* listener)
405 {
406 	BAutolock _(this);
407 	if (listener && !fListeners.HasItem(listener))
408 		return fListeners.AddItem(listener);
409 	return false;
410 }
411 
412 
413 void
414 Playlist::RemoveListener(Listener* listener)
415 {
416 	BAutolock _(this);
417 	fListeners.RemoveItem(listener);
418 }
419 
420 
421 // #pragma mark - support
422 
423 
424 void
425 Playlist::AppendRefs(const BMessage* refsReceivedMessage, int32 appendIndex)
426 {
427 	// the playlist is replaced by the refs in the message
428 	// or the refs are appended at the appendIndex
429 	// in the existing playlist
430 	if (appendIndex == APPEND_INDEX_APPEND_LAST)
431 		appendIndex = CountItems();
432 
433 	bool add = appendIndex != APPEND_INDEX_REPLACE_PLAYLIST;
434 
435 	if (!add)
436 		MakeEmpty();
437 
438 	bool startPlaying = CountItems() == 0;
439 
440 	Playlist temporaryPlaylist;
441 	Playlist* playlist = add ? &temporaryPlaylist : this;
442 	bool sortPlaylist = true;
443 
444 	entry_ref ref;
445 	int32 subAppendIndex = CountItems();
446 	for (int i = 0; refsReceivedMessage->FindRef("refs", i, &ref) == B_OK;
447 			i++) {
448 		Playlist subPlaylist;
449 		BString type = _MIMEString(&ref);
450 
451 		if (_IsPlaylist(type)) {
452 			AppendPlaylistToPlaylist(ref, &subPlaylist);
453 			// Do not sort the whole playlist anymore, as that
454 			// will screw up the ordering in the saved playlist.
455 			sortPlaylist = false;
456 		} else {
457 			if (_IsQuery(type))
458 				AppendQueryToPlaylist(ref, &subPlaylist);
459 			else {
460 				if ( !ExtraMediaExists(this, ref) ) {
461 					AppendToPlaylistRecursive(ref, &subPlaylist);
462 				}
463 			}
464 
465 			// At least sort this subsection of the playlist
466 			// if the whole playlist is not sorted anymore.
467 			if (!sortPlaylist)
468 				subPlaylist.Sort();
469 		}
470 
471 		if (!subPlaylist.IsEmpty()) {
472 			// Add to recent documents
473 			be_roster->AddToRecentDocuments(&ref, kAppSig);
474 		}
475 
476 		int32 subPlaylistCount = subPlaylist.CountItems();
477 		AdoptPlaylist(subPlaylist, subAppendIndex);
478 		subAppendIndex += subPlaylistCount;
479 	}
480 	if (sortPlaylist)
481 		playlist->Sort();
482 
483 	if (add)
484 		AdoptPlaylist(temporaryPlaylist, appendIndex);
485 
486 	if (startPlaying) {
487 		// open first file
488 		SetCurrentItemIndex(0);
489 	}
490 }
491 
492 
493 /*static*/ void
494 Playlist::AppendToPlaylistRecursive(const entry_ref& ref, Playlist* playlist)
495 {
496 	// recursively append the ref (dive into folders)
497 	BEntry entry(&ref, true);
498 	if (entry.InitCheck() != B_OK || !entry.Exists())
499 		return;
500 
501 	if (entry.IsDirectory()) {
502 		BDirectory dir(&entry);
503 		if (dir.InitCheck() != B_OK)
504 			return;
505 
506 		entry.Unset();
507 
508 		entry_ref subRef;
509 		while (dir.GetNextRef(&subRef) == B_OK) {
510 			AppendToPlaylistRecursive(subRef, playlist);
511 		}
512 	} else if (entry.IsFile()) {
513 		BString mimeString = _MIMEString(&ref);
514 		if (_IsMediaFile(mimeString)) {
515 			PlaylistItem* item = new (std::nothrow) FilePlaylistItem(ref);
516 			if (!ExtraMediaExists(playlist, ref)) {
517 				_BindExtraMedia(item);
518 				if (item != NULL && !playlist->AddItem(item))
519 					delete item;
520 			} else
521 				delete item;
522 		} else
523 			printf("MIME Type = %s\n", mimeString.String());
524 	}
525 }
526 
527 
528 /*static*/ void
529 Playlist::AppendPlaylistToPlaylist(const entry_ref& ref, Playlist* playlist)
530 {
531 	BEntry entry(&ref, true);
532 	if (entry.InitCheck() != B_OK || !entry.Exists())
533 		return;
534 
535 	BString mimeString = _MIMEString(&ref);
536 	if (_IsTextPlaylist(mimeString)) {
537 		//printf("RunPlaylist thing\n");
538 		BFile file(&ref, B_READ_ONLY);
539 		FileReadWrite lineReader(&file);
540 
541 		BString str;
542 		entry_ref refPath;
543 		status_t err;
544 		BPath path;
545 		while (lineReader.Next(str)) {
546 			str = str.RemoveFirst("file://");
547 			str = str.RemoveLast("..");
548 			path = BPath(str.String());
549 			printf("Line %s\n", path.Path());
550 			if (path.Path() != NULL) {
551 				if ((err = get_ref_for_path(path.Path(), &refPath)) == B_OK) {
552 					PlaylistItem* item
553 						= new (std::nothrow) FilePlaylistItem(refPath);
554 					if (item == NULL || !playlist->AddItem(item))
555 						delete item;
556 				} else
557 					printf("Error - %s: [%lx]\n", strerror(err), (int32) err);
558 			} else
559 				printf("Error - No File Found in playlist\n");
560 		}
561 	} else if (_IsBinaryPlaylist(mimeString)) {
562 		BFile file(&ref, B_READ_ONLY);
563 		Playlist temp;
564 		if (temp.Unflatten(&file) == B_OK)
565 			playlist->AdoptPlaylist(temp, playlist->CountItems());
566 	}
567 }
568 
569 
570 /*static*/ void
571 Playlist::AppendQueryToPlaylist(const entry_ref& ref, Playlist* playlist)
572 {
573 	BQueryFile query(&ref);
574 	if (query.InitCheck() != B_OK)
575 		return;
576 
577 	entry_ref foundRef;
578 	while (query.GetNextRef(&foundRef) == B_OK) {
579 		PlaylistItem* item = new (std::nothrow) FilePlaylistItem(foundRef);
580 		if (item == NULL || !playlist->AddItem(item))
581 			delete item;
582 	}
583 }
584 
585 
586 void
587 Playlist::NotifyImportFailed()
588 {
589 	BAutolock _(this);
590 	_NotifyImportFailed();
591 }
592 
593 
594 /*static*/ bool
595 Playlist::ExtraMediaExists(Playlist* playlist, const entry_ref& ref)
596 {
597 	BString exceptExtension = _GetExceptExtension(BPath(&ref).Path());
598 
599 	for (int32 i = 0; i < playlist->CountItems(); i++) {
600 		FilePlaylistItem* compare = dynamic_cast<FilePlaylistItem*>(playlist->ItemAt(i));
601 		if (compare == NULL)
602 			continue;
603 		if (compare->Ref() != ref
604 				&& _GetExceptExtension(BPath(&compare->Ref()).Path()) == exceptExtension )
605 			return true;
606 	}
607 	return false;
608 }
609 
610 
611 // #pragma mark - private
612 
613 
614 /*static*/ bool
615 Playlist::_IsImageFile(const BString& mimeString)
616 {
617 	BMimeType superType;
618 	BMimeType fileType(mimeString.String());
619 
620 	if (fileType.GetSupertype(&superType) != B_OK)
621 		return false;
622 
623 	if (superType == "image")
624 		return true;
625 
626 	return false;
627 }
628 
629 
630 /*static*/ bool
631 Playlist::_IsMediaFile(const BString& mimeString)
632 {
633 	BMimeType superType;
634 	BMimeType fileType(mimeString.String());
635 
636 	if (fileType.GetSupertype(&superType) != B_OK)
637 		return false;
638 
639 	// try a shortcut first
640 	if (superType == "audio" || superType == "video")
641 		return true;
642 
643 	// Look through our supported types
644 	app_info appInfo;
645 	if (be_app->GetAppInfo(&appInfo) != B_OK)
646 		return false;
647 	BFile appFile(&appInfo.ref, B_READ_ONLY);
648 	if (appFile.InitCheck() != B_OK)
649 		return false;
650 	BMessage types;
651 	BAppFileInfo appFileInfo(&appFile);
652 	if (appFileInfo.GetSupportedTypes(&types) != B_OK)
653 		return false;
654 
655 	const char* type;
656 	for (int32 i = 0; types.FindString("types", i, &type) == B_OK; i++) {
657 		if (strcasecmp(mimeString.String(), type) == 0)
658 			return true;
659 	}
660 
661 	return false;
662 }
663 
664 
665 /*static*/ bool
666 Playlist::_IsTextPlaylist(const BString& mimeString)
667 {
668 	return mimeString.Compare(kTextPlaylistMimeString) == 0;
669 }
670 
671 
672 /*static*/ bool
673 Playlist::_IsBinaryPlaylist(const BString& mimeString)
674 {
675 	return mimeString.Compare(kBinaryPlaylistMimeString) == 0;
676 }
677 
678 
679 /*static*/ bool
680 Playlist::_IsPlaylist(const BString& mimeString)
681 {
682 	return _IsTextPlaylist(mimeString) || _IsBinaryPlaylist(mimeString);
683 }
684 
685 
686 /*static*/ bool
687 Playlist::_IsQuery(const BString& mimeString)
688 {
689 	return mimeString.Compare(BQueryFile::MimeType()) == 0;
690 }
691 
692 
693 /*static*/ BString
694 Playlist::_MIMEString(const entry_ref* ref)
695 {
696 	BFile file(ref, B_READ_ONLY);
697 	BNodeInfo nodeInfo(&file);
698 	char mimeString[B_MIME_TYPE_LENGTH];
699 	if (nodeInfo.GetType(mimeString) != B_OK) {
700 		BMimeType type;
701 		if (BMimeType::GuessMimeType(ref, &type) != B_OK)
702 			return BString();
703 
704 		strlcpy(mimeString, type.Type(), B_MIME_TYPE_LENGTH);
705 		nodeInfo.SetType(type.Type());
706 	}
707 	return BString(mimeString);
708 }
709 
710 
711 // _BindExtraMedia() searches additional videos and audios
712 // and addes them as extra medias.
713 /*static*/ void
714 Playlist::_BindExtraMedia(PlaylistItem* item)
715 {
716 	FilePlaylistItem* fileItem = dynamic_cast<FilePlaylistItem*>(item);
717 	if (!item)
718 		return;
719 
720 	// If the media file is foo.mp3, _BindExtraMedia() searches foo.avi.
721 	BPath mediaFilePath(&fileItem->Ref());
722 	BString mediaFilePathString = mediaFilePath.Path();
723 	BPath dirPath;
724 	mediaFilePath.GetParent(&dirPath);
725 	BDirectory dir(dirPath.Path());
726 	if (dir.InitCheck() != B_OK)
727 		return;
728 
729 	BEntry entry;
730 	BString entryPathString;
731 	while (dir.GetNextEntry(&entry, true) == B_OK) {
732 		if (!entry.IsFile())
733 			continue;
734 		entryPathString = BPath(&entry).Path();
735 		if (entryPathString != mediaFilePathString
736 				&& _GetExceptExtension(entryPathString) == _GetExceptExtension(mediaFilePathString)) {
737 			_BindExtraMedia(fileItem, entry);
738 		}
739 	}
740 }
741 
742 
743 /*static*/ void
744 Playlist::_BindExtraMedia(FilePlaylistItem* fileItem, const BEntry& entry)
745 {
746 	entry_ref ref;
747 	entry.GetRef(&ref);
748 	BString mimeString = _MIMEString(&ref);
749 	if (_IsMediaFile(mimeString)) {
750 		fileItem->AddRef(ref);
751 	} else if (_IsImageFile(mimeString)) {
752 		fileItem->AddImageRef(ref);
753 	}
754 }
755 
756 
757 /*static*/ BString
758 Playlist::_GetExceptExtension(const BString& path)
759 {
760 	int32 periodPos = path.FindLast('.');
761 	if (periodPos <= path.FindLast('/'))
762 		return path;
763 	return BString(path.String(), periodPos);
764 }
765 
766 
767 // #pragma mark - notifications
768 
769 
770 void
771 Playlist::_NotifyItemAdded(PlaylistItem* item, int32 index) const
772 {
773 	BList listeners(fListeners);
774 	int32 count = listeners.CountItems();
775 	for (int32 i = 0; i < count; i++) {
776 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
777 		listener->ItemAdded(item, index);
778 	}
779 }
780 
781 
782 void
783 Playlist::_NotifyItemRemoved(int32 index) const
784 {
785 	BList listeners(fListeners);
786 	int32 count = listeners.CountItems();
787 	for (int32 i = 0; i < count; i++) {
788 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
789 		listener->ItemRemoved(index);
790 	}
791 }
792 
793 
794 void
795 Playlist::_NotifyItemsSorted() const
796 {
797 	BList listeners(fListeners);
798 	int32 count = listeners.CountItems();
799 	for (int32 i = 0; i < count; i++) {
800 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
801 		listener->ItemsSorted();
802 	}
803 }
804 
805 
806 void
807 Playlist::_NotifyCurrentItemChanged(int32 newIndex) const
808 {
809 	BList listeners(fListeners);
810 	int32 count = listeners.CountItems();
811 	for (int32 i = 0; i < count; i++) {
812 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
813 		listener->CurrentItemChanged(newIndex);
814 	}
815 }
816 
817 
818 void
819 Playlist::_NotifyImportFailed() const
820 {
821 	BList listeners(fListeners);
822 	int32 count = listeners.CountItems();
823 	for (int32 i = 0; i < count; i++) {
824 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
825 		listener->ImportFailed();
826 	}
827 }
828