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