xref: /haiku/src/apps/mediaplayer/playlist/Playlist.cpp (revision 7a74a5df454197933bc6e80a542102362ee98703)
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, bool play) {}
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, false);
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 >= CountItems())
329 			SetCurrentItemIndex(CountItems() - 1);
330 		else if (index <= fCurrentIndex)
331 			SetCurrentItemIndex(index - 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, bool notify)
364 {
365 	bool result = true;
366 	if (index >= CountItems()) {
367 		index = CountItems() - 1;
368 		result = false;
369 		notify = false;
370 	}
371 	if (index < 0) {
372 		index = -1;
373 		result = false;
374 	}
375 	if (index == fCurrentIndex && !notify)
376 		return result;
377 
378 	fCurrentIndex = index;
379 	_NotifyCurrentItemChanged(fCurrentIndex, notify);
380 	return result;
381 }
382 
383 
384 int32
385 Playlist::CurrentItemIndex() const
386 {
387 	return fCurrentIndex;
388 }
389 
390 
391 void
392 Playlist::GetSkipInfo(bool* canSkipPrevious, bool* canSkipNext) const
393 {
394 	if (canSkipPrevious)
395 		*canSkipPrevious = fCurrentIndex > 0;
396 	if (canSkipNext)
397 		*canSkipNext = fCurrentIndex < CountItems() - 1;
398 }
399 
400 
401 // pragma mark -
402 
403 
404 bool
405 Playlist::AddListener(Listener* listener)
406 {
407 	BAutolock _(this);
408 	if (listener && !fListeners.HasItem(listener))
409 		return fListeners.AddItem(listener);
410 	return false;
411 }
412 
413 
414 void
415 Playlist::RemoveListener(Listener* listener)
416 {
417 	BAutolock _(this);
418 	fListeners.RemoveItem(listener);
419 }
420 
421 
422 // #pragma mark - support
423 
424 
425 void
426 Playlist::AppendRefs(const BMessage* refsReceivedMessage, int32 appendIndex)
427 {
428 	// the playlist is replaced by the refs in the message
429 	// or the refs are appended at the appendIndex
430 	// in the existing playlist
431 	if (appendIndex == APPEND_INDEX_APPEND_LAST)
432 		appendIndex = CountItems();
433 
434 	bool add = appendIndex != APPEND_INDEX_REPLACE_PLAYLIST;
435 
436 	if (!add)
437 		MakeEmpty();
438 
439 	bool startPlaying = CountItems() == 0;
440 
441 	Playlist temporaryPlaylist;
442 	Playlist* playlist = add ? &temporaryPlaylist : this;
443 	bool sortPlaylist = true;
444 
445 	entry_ref ref;
446 	int32 subAppendIndex = CountItems();
447 	for (int i = 0; refsReceivedMessage->FindRef("refs", i, &ref) == B_OK;
448 			i++) {
449 		Playlist subPlaylist;
450 		BString type = _MIMEString(&ref);
451 
452 		if (_IsPlaylist(type)) {
453 			AppendPlaylistToPlaylist(ref, &subPlaylist);
454 			// Do not sort the whole playlist anymore, as that
455 			// will screw up the ordering in the saved playlist.
456 			sortPlaylist = false;
457 		} else {
458 			if (_IsQuery(type))
459 				AppendQueryToPlaylist(ref, &subPlaylist);
460 			else {
461 				if (!ExtraMediaExists(this, ref)) {
462 					AppendToPlaylistRecursive(ref, &subPlaylist);
463 				}
464 			}
465 
466 			// At least sort this subsection of the playlist
467 			// if the whole playlist is not sorted anymore.
468 			if (!sortPlaylist)
469 				subPlaylist.Sort();
470 		}
471 
472 		if (!subPlaylist.IsEmpty()) {
473 			// Add to recent documents
474 			be_roster->AddToRecentDocuments(&ref, kAppSig);
475 		}
476 
477 		int32 subPlaylistCount = subPlaylist.CountItems();
478 		AdoptPlaylist(subPlaylist, subAppendIndex);
479 		subAppendIndex += subPlaylistCount;
480 	}
481 	if (sortPlaylist)
482 		playlist->Sort();
483 
484 	if (add)
485 		AdoptPlaylist(temporaryPlaylist, appendIndex);
486 
487 	if (startPlaying) {
488 		// open first file
489 		SetCurrentItemIndex(0);
490 	}
491 }
492 
493 
494 /*static*/ void
495 Playlist::AppendToPlaylistRecursive(const entry_ref& ref, Playlist* playlist)
496 {
497 	// recursively append the ref (dive into folders)
498 	BEntry entry(&ref, true);
499 	if (entry.InitCheck() != B_OK || !entry.Exists())
500 		return;
501 
502 	if (entry.IsDirectory()) {
503 		BDirectory dir(&entry);
504 		if (dir.InitCheck() != B_OK)
505 			return;
506 
507 		entry.Unset();
508 
509 		entry_ref subRef;
510 		while (dir.GetNextRef(&subRef) == B_OK) {
511 			AppendToPlaylistRecursive(subRef, playlist);
512 		}
513 	} else if (entry.IsFile()) {
514 		BString mimeString = _MIMEString(&ref);
515 		if (_IsMediaFile(mimeString)) {
516 			PlaylistItem* item = new (std::nothrow) FilePlaylistItem(ref);
517 			if (!ExtraMediaExists(playlist, ref)) {
518 				_BindExtraMedia(item);
519 				if (item != NULL && !playlist->AddItem(item))
520 					delete item;
521 			} else
522 				delete item;
523 		} else
524 			printf("MIME Type = %s\n", mimeString.String());
525 	}
526 }
527 
528 
529 /*static*/ void
530 Playlist::AppendPlaylistToPlaylist(const entry_ref& ref, Playlist* playlist)
531 {
532 	BEntry entry(&ref, true);
533 	if (entry.InitCheck() != B_OK || !entry.Exists())
534 		return;
535 
536 	BString mimeString = _MIMEString(&ref);
537 	if (_IsTextPlaylist(mimeString)) {
538 		//printf("RunPlaylist thing\n");
539 		BFile file(&ref, B_READ_ONLY);
540 		FileReadWrite lineReader(&file);
541 
542 		BString str;
543 		entry_ref refPath;
544 		status_t err;
545 		BPath path;
546 		while (lineReader.Next(str)) {
547 			str = str.RemoveFirst("file://");
548 			str = str.RemoveLast("..");
549 			path = BPath(str.String());
550 			printf("Line %s\n", path.Path());
551 			if (path.Path() != NULL) {
552 				if ((err = get_ref_for_path(path.Path(), &refPath)) == B_OK) {
553 					PlaylistItem* item
554 						= new (std::nothrow) FilePlaylistItem(refPath);
555 					if (item == NULL || !playlist->AddItem(item))
556 						delete item;
557 				} else
558 					printf("Error - %s: [%lx]\n", strerror(err), (int32) err);
559 			} else
560 				printf("Error - No File Found in playlist\n");
561 		}
562 	} else if (_IsBinaryPlaylist(mimeString)) {
563 		BFile file(&ref, B_READ_ONLY);
564 		Playlist temp;
565 		if (temp.Unflatten(&file) == B_OK)
566 			playlist->AdoptPlaylist(temp, playlist->CountItems());
567 	}
568 }
569 
570 
571 /*static*/ void
572 Playlist::AppendQueryToPlaylist(const entry_ref& ref, Playlist* playlist)
573 {
574 	BQueryFile query(&ref);
575 	if (query.InitCheck() != B_OK)
576 		return;
577 
578 	entry_ref foundRef;
579 	while (query.GetNextRef(&foundRef) == B_OK) {
580 		PlaylistItem* item = new (std::nothrow) FilePlaylistItem(foundRef);
581 		if (item == NULL || !playlist->AddItem(item))
582 			delete item;
583 	}
584 }
585 
586 
587 void
588 Playlist::NotifyImportFailed()
589 {
590 	BAutolock _(this);
591 	_NotifyImportFailed();
592 }
593 
594 
595 /*static*/ bool
596 Playlist::ExtraMediaExists(Playlist* playlist, const entry_ref& ref)
597 {
598 	BString exceptExtension = _GetExceptExtension(BPath(&ref).Path());
599 
600 	for (int32 i = 0; i < playlist->CountItems(); i++) {
601 		FilePlaylistItem* compare = dynamic_cast<FilePlaylistItem*>(playlist->ItemAt(i));
602 		if (compare == NULL)
603 			continue;
604 		if (compare->Ref() != ref
605 				&& _GetExceptExtension(BPath(&compare->Ref()).Path()) == exceptExtension )
606 			return true;
607 	}
608 	return false;
609 }
610 
611 
612 // #pragma mark - private
613 
614 
615 /*static*/ bool
616 Playlist::_IsImageFile(const BString& mimeString)
617 {
618 	BMimeType superType;
619 	BMimeType fileType(mimeString.String());
620 
621 	if (fileType.GetSupertype(&superType) != B_OK)
622 		return false;
623 
624 	if (superType == "image")
625 		return true;
626 
627 	return false;
628 }
629 
630 
631 /*static*/ bool
632 Playlist::_IsMediaFile(const BString& mimeString)
633 {
634 	BMimeType superType;
635 	BMimeType fileType(mimeString.String());
636 
637 	if (fileType.GetSupertype(&superType) != B_OK)
638 		return false;
639 
640 	// try a shortcut first
641 	if (superType == "audio" || superType == "video")
642 		return true;
643 
644 	// Look through our supported types
645 	app_info appInfo;
646 	if (be_app->GetAppInfo(&appInfo) != B_OK)
647 		return false;
648 	BFile appFile(&appInfo.ref, B_READ_ONLY);
649 	if (appFile.InitCheck() != B_OK)
650 		return false;
651 	BMessage types;
652 	BAppFileInfo appFileInfo(&appFile);
653 	if (appFileInfo.GetSupportedTypes(&types) != B_OK)
654 		return false;
655 
656 	const char* type;
657 	for (int32 i = 0; types.FindString("types", i, &type) == B_OK; i++) {
658 		if (strcasecmp(mimeString.String(), type) == 0)
659 			return true;
660 	}
661 
662 	return false;
663 }
664 
665 
666 /*static*/ bool
667 Playlist::_IsTextPlaylist(const BString& mimeString)
668 {
669 	return mimeString.Compare(kTextPlaylistMimeString) == 0;
670 }
671 
672 
673 /*static*/ bool
674 Playlist::_IsBinaryPlaylist(const BString& mimeString)
675 {
676 	return mimeString.Compare(kBinaryPlaylistMimeString) == 0;
677 }
678 
679 
680 /*static*/ bool
681 Playlist::_IsPlaylist(const BString& mimeString)
682 {
683 	return _IsTextPlaylist(mimeString) || _IsBinaryPlaylist(mimeString);
684 }
685 
686 
687 /*static*/ bool
688 Playlist::_IsQuery(const BString& mimeString)
689 {
690 	return mimeString.Compare(BQueryFile::MimeType()) == 0;
691 }
692 
693 
694 /*static*/ BString
695 Playlist::_MIMEString(const entry_ref* ref)
696 {
697 	BFile file(ref, B_READ_ONLY);
698 	BNodeInfo nodeInfo(&file);
699 	char mimeString[B_MIME_TYPE_LENGTH];
700 	if (nodeInfo.GetType(mimeString) != B_OK) {
701 		BMimeType type;
702 		if (BMimeType::GuessMimeType(ref, &type) != B_OK)
703 			return BString();
704 
705 		strlcpy(mimeString, type.Type(), B_MIME_TYPE_LENGTH);
706 		nodeInfo.SetType(type.Type());
707 	}
708 	return BString(mimeString);
709 }
710 
711 
712 // _BindExtraMedia() searches additional videos and audios
713 // and addes them as extra medias.
714 /*static*/ void
715 Playlist::_BindExtraMedia(PlaylistItem* item)
716 {
717 	FilePlaylistItem* fileItem = dynamic_cast<FilePlaylistItem*>(item);
718 	if (!fileItem)
719 		return;
720 
721 	// If the media file is foo.mp3, _BindExtraMedia() searches foo.avi.
722 	BPath mediaFilePath(&fileItem->Ref());
723 	BString mediaFilePathString = mediaFilePath.Path();
724 	BPath dirPath;
725 	mediaFilePath.GetParent(&dirPath);
726 	BDirectory dir(dirPath.Path());
727 	if (dir.InitCheck() != B_OK)
728 		return;
729 
730 	BEntry entry;
731 	BString entryPathString;
732 	while (dir.GetNextEntry(&entry, true) == B_OK) {
733 		if (!entry.IsFile())
734 			continue;
735 		entryPathString = BPath(&entry).Path();
736 		if (entryPathString != mediaFilePathString
737 				&& _GetExceptExtension(entryPathString) == _GetExceptExtension(mediaFilePathString)) {
738 			_BindExtraMedia(fileItem, entry);
739 		}
740 	}
741 }
742 
743 
744 /*static*/ void
745 Playlist::_BindExtraMedia(FilePlaylistItem* fileItem, const BEntry& entry)
746 {
747 	entry_ref ref;
748 	entry.GetRef(&ref);
749 	BString mimeString = _MIMEString(&ref);
750 	if (_IsMediaFile(mimeString)) {
751 		fileItem->AddRef(ref);
752 	} else if (_IsImageFile(mimeString)) {
753 		fileItem->AddImageRef(ref);
754 	}
755 }
756 
757 
758 /*static*/ BString
759 Playlist::_GetExceptExtension(const BString& path)
760 {
761 	int32 periodPos = path.FindLast('.');
762 	if (periodPos <= path.FindLast('/'))
763 		return path;
764 	return BString(path.String(), periodPos);
765 }
766 
767 
768 // #pragma mark - notifications
769 
770 
771 void
772 Playlist::_NotifyItemAdded(PlaylistItem* item, int32 index) const
773 {
774 	BList listeners(fListeners);
775 	int32 count = listeners.CountItems();
776 	for (int32 i = 0; i < count; i++) {
777 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
778 		listener->ItemAdded(item, index);
779 	}
780 }
781 
782 
783 void
784 Playlist::_NotifyItemRemoved(int32 index) const
785 {
786 	BList listeners(fListeners);
787 	int32 count = listeners.CountItems();
788 	for (int32 i = 0; i < count; i++) {
789 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
790 		listener->ItemRemoved(index);
791 	}
792 }
793 
794 
795 void
796 Playlist::_NotifyItemsSorted() const
797 {
798 	BList listeners(fListeners);
799 	int32 count = listeners.CountItems();
800 	for (int32 i = 0; i < count; i++) {
801 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
802 		listener->ItemsSorted();
803 	}
804 }
805 
806 
807 void
808 Playlist::_NotifyCurrentItemChanged(int32 newIndex, bool play) const
809 {
810 	BList listeners(fListeners);
811 	int32 count = listeners.CountItems();
812 	for (int32 i = 0; i < count; i++) {
813 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
814 		listener->CurrentItemChanged(newIndex, play);
815 	}
816 }
817 
818 
819 void
820 Playlist::_NotifyImportFailed() const
821 {
822 	BList listeners(fListeners);
823 	int32 count = listeners.CountItems();
824 	for (int32 i = 0; i < count; i++) {
825 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
826 		listener->ImportFailed();
827 	}
828 }
829