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