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