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