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