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