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