xref: /haiku/src/apps/mediaplayer/playlist/Playlist.cpp (revision 1345706a9ff6ad0dc041339a02d4259998b0765d)
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 "FilePlaylistItem.h"
44 #include "FileReadWrite.h"
45 
46 using std::nothrow;
47 
48 // TODO: using BList for objects is bad, replace it with a template
49 
50 Playlist::Listener::Listener() {}
51 Playlist::Listener::~Listener() {}
52 void Playlist::Listener::ItemAdded(PlaylistItem* item, int32 index) {}
53 void Playlist::Listener::ItemRemoved(int32 index) {}
54 void Playlist::Listener::ItemsSorted() {}
55 void Playlist::Listener::CurrentItemChanged(int32 newIndex) {}
56 
57 
58 // #pragma mark -
59 
60 
61 static void
62 make_item_compare_string(const PlaylistItem* item, char* buffer,
63 	size_t bufferSize)
64 {
65 	// TODO: Maybe "location" would be useful here as well.
66 //	snprintf(buffer, bufferSize, "%s - %s - %0*ld - %s",
67 //		item->Author().String(),
68 //		item->Album().String(),
69 //		3, item->TrackNumber(),
70 //		item->Title().String());
71 	snprintf(buffer, bufferSize, "%s", item->LocationURI().String());
72 }
73 
74 
75 static int
76 playlist_item_compare(const void* _item1, const void* _item2)
77 {
78 	// compare complete path
79 	const PlaylistItem* item1 = *(const PlaylistItem**)_item1;
80 	const PlaylistItem* item2 = *(const PlaylistItem**)_item2;
81 
82 	static const size_t bufferSize = 1024;
83 	char string1[bufferSize];
84 	make_item_compare_string(item1, string1, bufferSize);
85 	char string2[bufferSize];
86 	make_item_compare_string(item2, string2, bufferSize);
87 
88 	return strcmp(string1, string2);
89 }
90 
91 
92 // #pragma mark -
93 
94 
95 Playlist::Playlist()
96 	:
97 	BLocker("playlist lock"),
98 	fItems(),
99  	fCurrentIndex(-1)
100 {
101 }
102 
103 
104 Playlist::~Playlist()
105 {
106 	MakeEmpty();
107 
108 	if (fListeners.CountItems() > 0)
109 		debugger("Playlist::~Playlist() - there are still listeners attached!");
110 }
111 
112 
113 // #pragma mark - archiving
114 
115 
116 static const char* kItemArchiveKey = "item";
117 
118 
119 status_t
120 Playlist::Unarchive(const BMessage* archive)
121 {
122 	if (archive == NULL)
123 		return B_BAD_VALUE;
124 
125 	MakeEmpty();
126 
127 	BMessage itemArchive;
128 	for (int32 i = 0;
129 		archive->FindMessage(kItemArchiveKey, i, &itemArchive) == B_OK; i++) {
130 
131 		BArchivable* archivable = instantiate_object(&itemArchive);
132 		PlaylistItem* item = dynamic_cast<PlaylistItem*>(archivable);
133 		if (!item) {
134 			delete archivable;
135 			continue;
136 		}
137 
138 		if (!AddItem(item)) {
139 			delete item;
140 			return B_NO_MEMORY;
141 		}
142 	}
143 
144 	return B_OK;
145 }
146 
147 
148 status_t
149 Playlist::Archive(BMessage* into) const
150 {
151 	if (into == NULL)
152 		return B_BAD_VALUE;
153 
154 	int32 count = CountItems();
155 	for (int32 i = 0; i < count; i++) {
156 		const PlaylistItem* item = ItemAtFast(i);
157 		BMessage itemArchive;
158 		status_t ret = item->Archive(&itemArchive);
159 		if (ret != B_OK)
160 			return ret;
161 		ret = into->AddMessage(kItemArchiveKey, &itemArchive);
162 		if (ret != B_OK)
163 			return ret;
164 	}
165 
166 	return B_OK;
167 }
168 
169 
170 const uint32 kPlaylistMagicBytes = 'MPPL';
171 const char* kTextPlaylistMimeString = "text/x-playlist";
172 const char* kBinaryPlaylistMimeString = "application/x-vnd.haiku-playlist";
173 
174 status_t
175 Playlist::Unflatten(BDataIO* stream)
176 {
177 	if (stream == NULL)
178 		return B_BAD_VALUE;
179 
180 	uint32 magicBytes;
181 	ssize_t read = stream->Read(&magicBytes, 4);
182 	if (read != 4) {
183 		if (read < 0)
184 			return (status_t)read;
185 		return B_IO_ERROR;
186 	}
187 
188 	if (B_LENDIAN_TO_HOST_INT32(magicBytes) != kPlaylistMagicBytes)
189 		return B_BAD_VALUE;
190 
191 	BMessage archive;
192 	status_t ret = archive.Unflatten(stream);
193 	if (ret != B_OK)
194 		return ret;
195 
196 	return Unarchive(&archive);
197 }
198 
199 
200 status_t
201 Playlist::Flatten(BDataIO* stream) const
202 {
203 	if (stream == NULL)
204 		return B_BAD_VALUE;
205 
206 	BMessage archive;
207 	status_t ret = Archive(&archive);
208 	if (ret != B_OK)
209 		return ret;
210 
211 	uint32 magicBytes = B_HOST_TO_LENDIAN_INT32(kPlaylistMagicBytes);
212 	ssize_t written = stream->Write(&magicBytes, 4);
213 	if (written != 4) {
214 		if (written < 0)
215 			return (status_t)written;
216 		return B_IO_ERROR;
217 	}
218 
219 	return archive.Flatten(stream);
220 }
221 
222 
223 // #pragma mark - list access
224 
225 
226 void
227 Playlist::MakeEmpty(bool deleteItems)
228 {
229 	int32 count = CountItems();
230 	for (int32 i = count - 1; i >= 0; i--) {
231 		PlaylistItem* item = RemoveItem(i, false);
232 		_NotifyItemRemoved(i);
233 		if (deleteItems)
234 			item->RemoveReference();
235 	}
236 	SetCurrentItemIndex(-1);
237 }
238 
239 
240 int32
241 Playlist::CountItems() const
242 {
243 	return fItems.CountItems();
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);
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 		if (index == fCurrentIndex && index >= CountItems())
318 			SetCurrentItemIndex(CountItems() - 1);
319 		else if (index < fCurrentIndex)
320 			SetCurrentItemIndex(fCurrentIndex - 1);
321 	}
322 
323 	return item;
324 }
325 
326 
327 int32
328 Playlist::IndexOf(PlaylistItem* item) const
329 {
330 	return fItems.IndexOf(item);
331 }
332 
333 
334 PlaylistItem*
335 Playlist::ItemAt(int32 index) const
336 {
337 	return (PlaylistItem*)fItems.ItemAt(index);
338 }
339 
340 
341 PlaylistItem*
342 Playlist::ItemAtFast(int32 index) const
343 {
344 	return (PlaylistItem*)fItems.ItemAtFast(index);
345 }
346 
347 
348 // #pragma mark - navigation
349 
350 
351 bool
352 Playlist::SetCurrentItemIndex(int32 index)
353 {
354 	bool result = true;
355 	if (index >= CountItems()) {
356 		index = CountItems() - 1;
357 		result = false;
358 	}
359 	if (index < 0) {
360 		index = -1;
361 		result = false;
362 	}
363 	if (index == fCurrentIndex)
364 		return result;
365 
366 	fCurrentIndex = index;
367 	_NotifyCurrentItemChanged(fCurrentIndex);
368 	return result;
369 }
370 
371 
372 int32
373 Playlist::CurrentItemIndex() const
374 {
375 	return fCurrentIndex;
376 }
377 
378 
379 void
380 Playlist::GetSkipInfo(bool* canSkipPrevious, bool* canSkipNext) const
381 {
382 	if (canSkipPrevious)
383 		*canSkipPrevious = fCurrentIndex > 0;
384 	if (canSkipNext)
385 		*canSkipNext = fCurrentIndex < CountItems() - 1;
386 }
387 
388 
389 // pragma mark -
390 
391 
392 bool
393 Playlist::AddListener(Listener* listener)
394 {
395 	BAutolock _(this);
396 	if (listener && !fListeners.HasItem(listener))
397 		return fListeners.AddItem(listener);
398 	return false;
399 }
400 
401 
402 void
403 Playlist::RemoveListener(Listener* listener)
404 {
405 	BAutolock _(this);
406 	fListeners.RemoveItem(listener);
407 }
408 
409 
410 // #pragma mark - support
411 
412 
413 void
414 Playlist::AppendRefs(const BMessage* refsReceivedMessage, int32 appendIndex)
415 {
416 	// the playlist is replaced by the refs in the message
417 	// or the refs are appended at the appendIndex
418 	// in the existing playlist
419 	if (appendIndex == APPEND_INDEX_APPEND_LAST)
420 		appendIndex = CountItems();
421 
422 	bool add = appendIndex != APPEND_INDEX_REPLACE_PLAYLIST;
423 
424 	if (!add)
425 		MakeEmpty();
426 
427 	bool startPlaying = CountItems() == 0;
428 
429 	Playlist temporaryPlaylist;
430 	Playlist* playlist = add ? &temporaryPlaylist : this;
431 	bool sortPlaylist = true;
432 
433 	entry_ref ref;
434 	int32 subAppendIndex = CountItems();
435 	for (int i = 0; refsReceivedMessage->FindRef("refs", i, &ref) == B_OK;
436 			i++) {
437 		Playlist subPlaylist;
438 		if (_IsPlaylist(_MIMEString(&ref))) {
439 			AppendPlaylistToPlaylist(ref, &subPlaylist);
440 			// Do not sort the whole playlist anymore, as that
441 			// will screw up the ordering in the saved playlist.
442 			sortPlaylist = false;
443 		} else {
444 			AppendToPlaylistRecursive(ref, &subPlaylist);
445 			// At least sort this subsection of the playlist
446 			// if the whole playlist is not sorted anymore.
447 			if (!sortPlaylist)
448 				subPlaylist.Sort();
449 		}
450 		int32 subPlaylistCount = subPlaylist.CountItems();
451 		AdoptPlaylist(subPlaylist, subAppendIndex);
452 		subAppendIndex += subPlaylistCount;
453 	}
454 	if (sortPlaylist)
455 		playlist->Sort();
456 
457 	if (add)
458 		AdoptPlaylist(temporaryPlaylist, appendIndex);
459 
460 	if (startPlaying) {
461 		// open first file
462 		SetCurrentItemIndex(0);
463 	}
464 }
465 
466 
467 /*static*/ void
468 Playlist::AppendToPlaylistRecursive(const entry_ref& ref, Playlist* playlist)
469 {
470 	// recursively append the ref (dive into folders)
471 	BEntry entry(&ref, true);
472 	if (entry.InitCheck() != B_OK || !entry.Exists())
473 		return;
474 
475 	if (entry.IsDirectory()) {
476 		BDirectory dir(&entry);
477 		if (dir.InitCheck() != B_OK)
478 			return;
479 
480 		entry.Unset();
481 
482 		entry_ref subRef;
483 		while (dir.GetNextRef(&subRef) == B_OK) {
484 			AppendToPlaylistRecursive(subRef, playlist);
485 		}
486 	} else if (entry.IsFile()) {
487 		BString mimeString = _MIMEString(&ref);
488 		if (_IsMediaFile(mimeString)) {
489 			PlaylistItem* item = new (std::nothrow) FilePlaylistItem(ref);
490 			if (item != NULL && !playlist->AddItem(item))
491 				delete item;
492 		} else
493 			printf("MIME Type = %s\n", mimeString.String());
494 	}
495 }
496 
497 
498 /*static*/ void
499 Playlist::AppendPlaylistToPlaylist(const entry_ref& ref, Playlist* playlist)
500 {
501 	BEntry entry(&ref, true);
502 	if (entry.InitCheck() != B_OK || !entry.Exists())
503 		return;
504 
505 	BString mimeString = _MIMEString(&ref);
506 	if (_IsTextPlaylist(mimeString)) {
507 		//printf("RunPlaylist thing\n");
508 		BFile file(&ref, B_READ_ONLY);
509 		FileReadWrite lineReader(&file);
510 
511 		BString str;
512 		entry_ref refPath;
513 		status_t err;
514 		BPath path;
515 		while (lineReader.Next(str)) {
516 			str = str.RemoveFirst("file://");
517 			str = str.RemoveLast("..");
518 			path = BPath(str.String());
519 			printf("Line %s\n", path.Path());
520 			if (path.Path() != NULL) {
521 				if ((err = get_ref_for_path(path.Path(), &refPath)) == B_OK) {
522 					PlaylistItem* item
523 						= new (std::nothrow) FilePlaylistItem(refPath);
524 					if (item == NULL || !playlist->AddItem(item))
525 						delete item;
526 				} else
527 					printf("Error - %s: [%lx]\n", strerror(err), (int32) err);
528 			} else
529 				printf("Error - No File Found in playlist\n");
530 		}
531 	} else if (_IsBinaryPlaylist(mimeString)) {
532 		BFile file(&ref, B_READ_ONLY);
533 		Playlist temp;
534 		if (temp.Unflatten(&file) == B_OK)
535 			playlist->AdoptPlaylist(temp, playlist->CountItems());
536 	}
537 }
538 
539 
540 // #pragma mark - private
541 
542 
543 /*static*/ bool
544 Playlist::_IsMediaFile(const BString& mimeString)
545 {
546 	BMimeType superType;
547 	BMimeType fileType(mimeString.String());
548 
549 	if (fileType.GetSupertype(&superType) != B_OK)
550 		return false;
551 
552 	// try a shortcut first
553 	if (superType == "audio" || superType == "video")
554 		return true;
555 
556 	// Look through our supported types
557 	app_info appInfo;
558 	if (be_app->GetAppInfo(&appInfo) != B_OK)
559 		return false;
560 	BFile appFile(&appInfo.ref, B_READ_ONLY);
561 	if (appFile.InitCheck() != B_OK)
562 		return false;
563 	BMessage types;
564 	BAppFileInfo appFileInfo(&appFile);
565 	if (appFileInfo.GetSupportedTypes(&types) != B_OK)
566 		return false;
567 
568 	const char* type;
569 	for (int32 i = 0; types.FindString("types", i, &type) == B_OK; i++) {
570 		if (strcasecmp(mimeString.String(), type) == 0)
571 			return true;
572 	}
573 
574 	return false;
575 }
576 
577 
578 /*static*/ bool
579 Playlist::_IsTextPlaylist(const BString& mimeString)
580 {
581 	return mimeString.Compare(kTextPlaylistMimeString) == 0;
582 }
583 
584 
585 /*static*/ bool
586 Playlist::_IsBinaryPlaylist(const BString& mimeString)
587 {
588 	return mimeString.Compare(kBinaryPlaylistMimeString) == 0;
589 }
590 
591 
592 /*static*/ bool
593 Playlist::_IsPlaylist(const BString& mimeString)
594 {
595 	return _IsTextPlaylist(mimeString) || _IsBinaryPlaylist(mimeString);
596 }
597 
598 
599 /*static*/ BString
600 Playlist::_MIMEString(const entry_ref* ref)
601 {
602 	BFile file(ref, B_READ_ONLY);
603 	BNodeInfo nodeInfo(&file);
604 	char mimeString[B_MIME_TYPE_LENGTH];
605 	if (nodeInfo.GetType(mimeString) != B_OK) {
606 		BMimeType type;
607 		if (BMimeType::GuessMimeType(ref, &type) != B_OK)
608 			return BString();
609 
610 		strlcpy(mimeString, type.Type(), B_MIME_TYPE_LENGTH);
611 		nodeInfo.SetType(type.Type());
612 	}
613 	return BString(mimeString);
614 }
615 
616 
617 // #pragma mark - notifications
618 
619 
620 void
621 Playlist::_NotifyItemAdded(PlaylistItem* item, int32 index) const
622 {
623 	BList listeners(fListeners);
624 	int32 count = listeners.CountItems();
625 	for (int32 i = 0; i < count; i++) {
626 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
627 		listener->ItemAdded(item, index);
628 	}
629 }
630 
631 
632 void
633 Playlist::_NotifyItemRemoved(int32 index) const
634 {
635 	BList listeners(fListeners);
636 	int32 count = listeners.CountItems();
637 	for (int32 i = 0; i < count; i++) {
638 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
639 		listener->ItemRemoved(index);
640 	}
641 }
642 
643 
644 void
645 Playlist::_NotifyItemsSorted() const
646 {
647 	BList listeners(fListeners);
648 	int32 count = listeners.CountItems();
649 	for (int32 i = 0; i < count; i++) {
650 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
651 		listener->ItemsSorted();
652 	}
653 }
654 
655 
656 void
657 Playlist::_NotifyCurrentItemChanged(int32 newIndex) const
658 {
659 	BList listeners(fListeners);
660 	int32 count = listeners.CountItems();
661 	for (int32 i = 0; i < count; i++) {
662 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
663 		listener->CurrentItemChanged(newIndex);
664 	}
665 }
666 
667