xref: /haiku/src/apps/mediaplayer/playlist/Playlist.cpp (revision d2e1e872611179c9cfaa43ce11bd58b1e3554e4b)
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 Stephan Aßmus <superstippi@gmx.de>
6  * Copyright (C) 2008 Fredrik Modéen 	<fredrik@modeen.se> (I have no problem changing my things to MIT)
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 #include "Playlist.h"
23 
24 #include <debugger.h>
25 #include <new.h>
26 #include <stdio.h>
27 #include <String.h>
28 #include <NodeInfo.h>
29 #include <File.h>
30 #include <Mime.h>
31 
32 #include <Autolock.h>
33 #include <Directory.h>
34 #include <Message.h>
35 #include <Path.h>
36 
37 #include "FileReadWrite.h"
38 
39 using std::nothrow;
40 
41 // TODO: using BList for objects is bad, replace it with a template
42 
43 Playlist::Listener::Listener() {}
44 Playlist::Listener::~Listener() {}
45 void Playlist::Listener::RefAdded(const entry_ref& ref, int32 index) {}
46 void Playlist::Listener::RefRemoved(int32 index) {}
47 void Playlist::Listener::RefsSorted() {}
48 void Playlist::Listener::CurrentRefChanged(int32 newIndex) {}
49 
50 
51 // #pragma mark -
52 
53 
54 Playlist::Playlist()
55  :	BLocker("playlist lock")
56  ,	fRefs()
57  ,	fCurrentIndex(-1)
58 {
59 }
60 
61 
62 Playlist::~Playlist()
63 {
64 	MakeEmpty();
65 
66 	if (fListeners.CountItems() > 0)
67 		debugger("Playlist::~Playlist() - there are still listeners attached!");
68 }
69 
70 
71 void
72 Playlist::MakeEmpty()
73 {
74 	int32 count = fRefs.CountItems();
75 	for (int32 i = count - 1; i >= 0; i--) {
76 		entry_ref* ref = (entry_ref*)fRefs.RemoveItem(i);
77 		_NotifyRefRemoved(i);
78 		delete ref;
79 	}
80 	SetCurrentRefIndex(-1);
81 }
82 
83 
84 int32
85 Playlist::CountItems() const
86 {
87 	return fRefs.CountItems();
88 }
89 
90 
91 void
92 Playlist::Sort()
93 {
94 	fRefs.SortItems(playlist_cmp);
95 	_NotifyRefsSorted();
96 }
97 
98 
99 bool
100 Playlist::AddRef(const entry_ref &ref)
101 {
102 	return AddRef(ref, CountItems());
103 }
104 
105 
106 bool
107 Playlist::AddRef(const entry_ref &ref, int32 index)
108 {
109 	entry_ref* copy = new (nothrow) entry_ref(ref);
110 	if (!copy)
111 		return false;
112 	if (!fRefs.AddItem(copy, index)) {
113 		delete copy;
114 		return false;
115 	}
116 	_NotifyRefAdded(ref, index);
117 
118 	if (index <= fCurrentIndex)
119 		SetCurrentRefIndex(fCurrentIndex + 1);
120 
121 	return true;
122 }
123 
124 
125 bool
126 Playlist::AdoptPlaylist(Playlist& other)
127 {
128 	return AdoptPlaylist(other, CountItems());
129 }
130 
131 
132 bool
133 Playlist::AdoptPlaylist(Playlist& other, int32 index)
134 {
135 	if (&other == this)
136 		return false;
137 	// NOTE: this is not intended to merge two "equal" playlists
138 	// the given playlist is assumed to be a temporary "dummy"
139 	if (fRefs.AddList(&other.fRefs, index)) {
140 		// take care of the notifications
141 		int32 count = other.fRefs.CountItems();
142 		for (int32 i = index; i < index + count; i++) {
143 			entry_ref* ref = (entry_ref*)fRefs.ItemAtFast(i);
144 			_NotifyRefAdded(*ref, i);
145 		}
146 		if (index <= fCurrentIndex)
147 			SetCurrentRefIndex(fCurrentIndex + count);
148 		// empty the other list, so that the entry_refs are no ours
149 		other.fRefs.MakeEmpty();
150 		return true;
151 	}
152 	return false;
153 }
154 
155 
156 entry_ref
157 Playlist::RemoveRef(int32 index, bool careAboutCurrentIndex)
158 {
159 	entry_ref _ref;
160 	entry_ref* ref = (entry_ref*)fRefs.RemoveItem(index);
161 	if (!ref)
162 		return _ref;
163 	_NotifyRefRemoved(index);
164 	_ref = *ref;
165 	delete ref;
166 
167 	if (careAboutCurrentIndex) {
168 		if (index == fCurrentIndex)
169 			SetCurrentRefIndex(-1);
170 		else if (index < fCurrentIndex)
171 			SetCurrentRefIndex(fCurrentIndex - 1);
172 	}
173 
174 	return _ref;
175 }
176 
177 
178 int32
179 Playlist::IndexOf(const entry_ref& _ref) const
180 {
181 	int32 count = CountItems();
182 	for (int32 i = 0; i < count; i++) {
183 		entry_ref* ref = (entry_ref*)fRefs.ItemAtFast(i);
184 		if (*ref == _ref)
185 			return i;
186 	}
187 	return -1;
188 }
189 
190 
191 status_t
192 Playlist::GetRefAt(int32 index, entry_ref* _ref) const
193 {
194 	if (!_ref)
195 		return B_BAD_VALUE;
196 	entry_ref* ref = (entry_ref*)fRefs.ItemAt(index);
197 	if (!ref)
198 		return B_BAD_INDEX;
199 	*_ref = *ref;
200 
201 	return B_OK;
202 }
203 
204 
205 //bool
206 //Playlist::HasRef(const entry_ref& ref) const
207 //{
208 //	return IndexOf(ref) >= 0;
209 //}
210 
211 
212 
213 // #pragma mark -
214 
215 
216 void
217 Playlist::SetCurrentRefIndex(int32 index)
218 {
219 	if (index == fCurrentIndex)
220 		return;
221 
222 	fCurrentIndex = index;
223 	_NotifyCurrentRefChanged(fCurrentIndex);
224 }
225 
226 
227 int32
228 Playlist::CurrentRefIndex() const
229 {
230 	return fCurrentIndex;
231 }
232 
233 
234 void
235 Playlist::GetSkipInfo(bool* canSkipPrevious, bool* canSkipNext) const
236 {
237 	if (canSkipPrevious)
238 		*canSkipPrevious = fCurrentIndex > 0;
239 	if (canSkipNext)
240 		*canSkipNext = fCurrentIndex < CountItems() - 1;
241 }
242 
243 
244 // pragma mark -
245 
246 
247 bool
248 Playlist::AddListener(Listener* listener)
249 {
250 	BAutolock _(this);
251 	if (listener && !fListeners.HasItem(listener))
252 		return fListeners.AddItem(listener);
253 	return false;
254 }
255 
256 
257 void
258 Playlist::RemoveListener(Listener* listener)
259 {
260 	BAutolock _(this);
261 	fListeners.RemoveItem(listener);
262 }
263 
264 
265 // #pragma mark -
266 
267 
268 void
269 Playlist::AppendRefs(const BMessage* refsReceivedMessage, int32 appendIndex)
270 {
271 	// the playlist ist replaced by the refs in the message
272 	// or the refs are appended at the appendIndex
273 	// in the existing playlist
274 	bool add = appendIndex >= 0;
275 
276 	if (!add)
277 		MakeEmpty();
278 
279 	bool startPlaying = CountItems() == 0;
280 
281 	Playlist temporaryPlaylist;
282 	Playlist* playlist = add ? &temporaryPlaylist : this;
283 
284 	entry_ref ref;
285 	for (int i = 0; refsReceivedMessage->FindRef("refs", i, &ref) == B_OK; i++)
286 		AppendToPlaylistRecursive(ref, playlist);
287 	playlist->Sort();
288 
289 	if (add)
290 		AdoptPlaylist(temporaryPlaylist, appendIndex);
291 
292 	if (startPlaying) {
293 		// open first file
294 		SetCurrentRefIndex(0);
295 	}
296 }
297 
298 
299 /*static*/ void
300 Playlist::AppendToPlaylistRecursive(const entry_ref& ref, Playlist* playlist)
301 {
302 	// recursively append the ref (dive into folders)
303 	BEntry entry(&ref, true);
304 	if (entry.InitCheck() < B_OK) {
305 		printf("Not OK\n");
306 		return;
307 	}
308 
309 	if (!entry.Exists()) {
310 		BPath path = BPath(&ref);
311 		//printf("Don't exist - %s\n", path.Path());
312 		return;
313 	}
314 
315 	if (entry.IsDirectory()) {
316 		BDirectory dir(&entry);
317 		if (dir.InitCheck() < B_OK)
318 			return;
319 		entry.Unset();
320 		entry_ref subRef;
321 		while (dir.GetNextRef(&subRef) == B_OK)
322 			AppendToPlaylistRecursive(subRef, playlist);
323 	} else if (entry.IsFile()) {
324 		//printf("Is File\n");
325 		BString mimeString = _MIMEString(&ref);
326 		if (_IsMediaFile(mimeString)) {
327 			//printf("Adding\n");
328 			playlist->AddRef(ref);
329 		} else if (_IsPlaylist(mimeString)) {
330 			//printf("RunPlaylist thing\n");
331 			BFile file(&ref, B_READ_ONLY);
332 			FileReadWrite lineReader(&file);
333 
334 			BString str;
335 			entry_ref refPath;
336 			status_t err;
337 			BPath path;
338 			while (lineReader.Next(str)) {
339 				str = str.RemoveFirst("file://");
340 				str = str.RemoveLast("..");
341 				path = BPath(str.String());
342 				printf("Line %s\n", path.Path());
343 				if (path.Path() != NULL) {
344 					if ((err = get_ref_for_path(path.Path(), &refPath)) == B_OK) {
345 						playlist->AddRef(refPath);
346 					} else
347 						printf("Error - %s: [%lx]\n", strerror(err), (int32) err);
348 				} else
349 					printf("Error - No File Found in playlist\n");
350 			}
351 		} else
352 			printf("MIME Type = %s\n", mimeString.String());
353 	}
354 }
355 
356 
357 // #pragma mark -
358 
359 
360 int
361 Playlist::playlist_cmp(const void *p1, const void *p2)
362 {
363 	// compare complete path
364 	BEntry a(*(const entry_ref **)p1, false);
365 	BEntry b(*(const entry_ref **)p2, false);
366 
367 	BPath aPath(&a);
368 	BPath bPath(&b);
369 
370 	return strcmp(aPath.Path(), bPath.Path());
371 }
372 
373 
374 /*static*/ void
375 Playlist::_AddPlayListFileToPlayList(const entry_ref& ref, Playlist* playlist)
376 {
377 
378 	BFile file(&ref, B_READ_ONLY);
379 	FileReadWrite lineReader(&file);
380 
381 	BString str;
382 	while (lineReader.Next(str)) {
383 		BPath path = BPath(str.String());
384 		entry_ref refPath;
385 		status_t err;
386 		if ((err = get_ref_for_path(path.Path(), &refPath)) == B_OK) {
387 			AppendToPlaylistRecursive(refPath, playlist);
388 		} else
389 			printf("Error - %s: [%lx]\n", strerror(err), (int32) err);
390 	}
391 	/*
392 		Read line for x to the end of file (while?)
393 		make a enty_ref and call AppendToPlaylistRecursive(with new entry, playlist)
394 	*/
395 }
396 
397 
398 /*static*/ bool
399 Playlist::_IsMediaFile(const BString& mimeString)
400 {
401 	BMimeType superType;
402 	BMimeType fileType(mimeString.String());
403 
404 	if (fileType.GetSupertype(&superType) != B_OK)
405 		return false;
406 
407 	// TODO: some media files have other super types, I think
408 	// for example ASF has "application" super type... so it would
409 	// need special handling
410 	return (superType == "audio" || superType == "video");
411 }
412 
413 
414 /*static*/ bool
415 Playlist::_IsPlaylist(const BString& mimeString)
416 {
417 	return mimeString.Compare("text/x-playlist") == 0;
418 }
419 
420 
421 /*static*/ BString
422 Playlist::_MIMEString(const entry_ref* ref)
423 {
424 	BFile file(ref, B_READ_ONLY);
425 	BNodeInfo nodeInfo(&file);
426 	char mimeString[B_MIME_TYPE_LENGTH];
427 	if (nodeInfo.GetType(mimeString) != B_OK) {
428 		BMimeType type;
429 		if (BMimeType::GuessMimeType(ref, &type) != B_OK)
430 			return BString();
431 
432 		strlcpy(mimeString, type.Type(), B_MIME_TYPE_LENGTH);
433 		nodeInfo.SetType(type.Type());
434 	}
435 	return BString(mimeString);
436 }
437 
438 
439 void
440 Playlist::_NotifyRefAdded(const entry_ref& ref, int32 index) const
441 {
442 	BList listeners(fListeners);
443 	int32 count = listeners.CountItems();
444 	for (int32 i = 0; i < count; i++) {
445 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
446 		listener->RefAdded(ref, index);
447 	}
448 }
449 
450 
451 void
452 Playlist::_NotifyRefRemoved(int32 index) const
453 {
454 	BList listeners(fListeners);
455 	int32 count = listeners.CountItems();
456 	for (int32 i = 0; i < count; i++) {
457 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
458 		listener->RefRemoved(index);
459 	}
460 }
461 
462 
463 void
464 Playlist::_NotifyRefsSorted() const
465 {
466 	BList listeners(fListeners);
467 	int32 count = listeners.CountItems();
468 	for (int32 i = 0; i < count; i++) {
469 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
470 		listener->RefsSorted();
471 	}
472 }
473 
474 
475 void
476 Playlist::_NotifyCurrentRefChanged(int32 newIndex) const
477 {
478 	BList listeners(fListeners);
479 	int32 count = listeners.CountItems();
480 	for (int32 i = 0; i < count; i++) {
481 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
482 		listener->CurrentRefChanged(newIndex);
483 	}
484 }
485 
486