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 * Released under the terms of the MIT license. 9 */ 10 11 12 #include "Playlist.h" 13 14 #include <debugger.h> 15 #include <new> 16 #include <stdio.h> 17 #include <strings.h> 18 19 #include <AppFileInfo.h> 20 #include <Application.h> 21 #include <Autolock.h> 22 #include <Directory.h> 23 #include <Entry.h> 24 #include <File.h> 25 #include <Message.h> 26 #include <Mime.h> 27 #include <NodeInfo.h> 28 #include <Path.h> 29 #include <Roster.h> 30 #include <String.h> 31 32 #include <QueryFile.h> 33 34 #include "FilePlaylistItem.h" 35 #include "FileReadWrite.h" 36 #include "MainApp.h" 37 38 using std::nothrow; 39 40 // TODO: using BList for objects is bad, replace it with a template 41 42 Playlist::Listener::Listener() {} 43 Playlist::Listener::~Listener() {} 44 void Playlist::Listener::ItemAdded(PlaylistItem* item, int32 index) {} 45 void Playlist::Listener::ItemRemoved(int32 index) {} 46 void Playlist::Listener::ItemsSorted() {} 47 void Playlist::Listener::CurrentItemChanged(int32 newIndex, bool play) {} 48 void Playlist::Listener::ImportFailed() {} 49 50 51 // #pragma mark - 52 53 54 static void 55 make_item_compare_string(const PlaylistItem* item, char* buffer, 56 size_t bufferSize) 57 { 58 // TODO: Maybe "location" would be useful here as well. 59 // snprintf(buffer, bufferSize, "%s - %s - %0*ld - %s", 60 // item->Author().String(), 61 // item->Album().String(), 62 // 3, item->TrackNumber(), 63 // item->Title().String()); 64 snprintf(buffer, bufferSize, "%s", item->LocationURI().String()); 65 } 66 67 68 static int 69 playlist_item_compare(const void* _item1, const void* _item2) 70 { 71 // compare complete path 72 const PlaylistItem* item1 = *(const PlaylistItem**)_item1; 73 const PlaylistItem* item2 = *(const PlaylistItem**)_item2; 74 75 static const size_t bufferSize = 1024; 76 char string1[bufferSize]; 77 make_item_compare_string(item1, string1, bufferSize); 78 char string2[bufferSize]; 79 make_item_compare_string(item2, string2, bufferSize); 80 81 return strcmp(string1, string2); 82 } 83 84 85 // #pragma mark - 86 87 88 Playlist::Playlist() 89 : 90 BLocker("playlist lock"), 91 fItems(), 92 fCurrentIndex(-1) 93 { 94 } 95 96 97 Playlist::~Playlist() 98 { 99 MakeEmpty(); 100 101 if (fListeners.CountItems() > 0) 102 debugger("Playlist::~Playlist() - there are still listeners attached!"); 103 } 104 105 106 // #pragma mark - archiving 107 108 109 static const char* kItemArchiveKey = "item"; 110 111 112 status_t 113 Playlist::Unarchive(const BMessage* archive) 114 { 115 if (archive == NULL) 116 return B_BAD_VALUE; 117 118 MakeEmpty(); 119 120 BMessage itemArchive; 121 for (int32 i = 0; 122 archive->FindMessage(kItemArchiveKey, i, &itemArchive) == B_OK; i++) { 123 124 BArchivable* archivable = instantiate_object(&itemArchive); 125 PlaylistItem* item = dynamic_cast<PlaylistItem*>(archivable); 126 if (!item) { 127 delete archivable; 128 continue; 129 } 130 131 if (!AddItem(item)) { 132 delete item; 133 return B_NO_MEMORY; 134 } 135 } 136 137 return B_OK; 138 } 139 140 141 status_t 142 Playlist::Archive(BMessage* into) const 143 { 144 if (into == NULL) 145 return B_BAD_VALUE; 146 147 int32 count = CountItems(); 148 for (int32 i = 0; i < count; i++) { 149 const PlaylistItem* item = ItemAtFast(i); 150 BMessage itemArchive; 151 status_t ret = item->Archive(&itemArchive); 152 if (ret != B_OK) 153 return ret; 154 ret = into->AddMessage(kItemArchiveKey, &itemArchive); 155 if (ret != B_OK) 156 return ret; 157 } 158 159 return B_OK; 160 } 161 162 163 const uint32 kPlaylistMagicBytes = 'MPPL'; 164 const char* kTextPlaylistMimeString = "text/x-playlist"; 165 const char* kBinaryPlaylistMimeString = "application/x-vnd.haiku-playlist"; 166 167 status_t 168 Playlist::Unflatten(BDataIO* stream) 169 { 170 if (stream == NULL) 171 return B_BAD_VALUE; 172 173 uint32 magicBytes; 174 ssize_t read = stream->Read(&magicBytes, 4); 175 if (read != 4) { 176 if (read < 0) 177 return (status_t)read; 178 return B_IO_ERROR; 179 } 180 181 if (B_LENDIAN_TO_HOST_INT32(magicBytes) != kPlaylistMagicBytes) 182 return B_BAD_VALUE; 183 184 BMessage archive; 185 status_t ret = archive.Unflatten(stream); 186 if (ret != B_OK) 187 return ret; 188 189 return Unarchive(&archive); 190 } 191 192 193 status_t 194 Playlist::Flatten(BDataIO* stream) const 195 { 196 if (stream == NULL) 197 return B_BAD_VALUE; 198 199 BMessage archive; 200 status_t ret = Archive(&archive); 201 if (ret != B_OK) 202 return ret; 203 204 uint32 magicBytes = B_HOST_TO_LENDIAN_INT32(kPlaylistMagicBytes); 205 ssize_t written = stream->Write(&magicBytes, 4); 206 if (written != 4) { 207 if (written < 0) 208 return (status_t)written; 209 return B_IO_ERROR; 210 } 211 212 return archive.Flatten(stream); 213 } 214 215 216 // #pragma mark - list access 217 218 219 void 220 Playlist::MakeEmpty(bool deleteItems) 221 { 222 int32 count = CountItems(); 223 for (int32 i = count - 1; i >= 0; i--) { 224 PlaylistItem* item = RemoveItem(i, false); 225 _NotifyItemRemoved(i); 226 if (deleteItems) 227 item->ReleaseReference(); 228 } 229 SetCurrentItemIndex(-1); 230 } 231 232 233 int32 234 Playlist::CountItems() const 235 { 236 return fItems.CountItems(); 237 } 238 239 240 bool 241 Playlist::IsEmpty() const 242 { 243 return fItems.IsEmpty(); 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, false); 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 // fCurrentIndex isn't in sync yet, so might be one too large (if the 318 // removed item was above the currently playing item). 319 if (index < fCurrentIndex) 320 SetCurrentItemIndex(fCurrentIndex - 1, false); 321 else if (index == fCurrentIndex) { 322 if (fCurrentIndex == CountItems()) 323 fCurrentIndex--; 324 SetCurrentItemIndex(fCurrentIndex, true); 325 } 326 } 327 328 return item; 329 } 330 331 332 int32 333 Playlist::IndexOf(PlaylistItem* item) const 334 { 335 return fItems.IndexOf(item); 336 } 337 338 339 PlaylistItem* 340 Playlist::ItemAt(int32 index) const 341 { 342 return (PlaylistItem*)fItems.ItemAt(index); 343 } 344 345 346 PlaylistItem* 347 Playlist::ItemAtFast(int32 index) const 348 { 349 return (PlaylistItem*)fItems.ItemAtFast(index); 350 } 351 352 353 // #pragma mark - navigation 354 355 356 bool 357 Playlist::SetCurrentItemIndex(int32 index, bool notify) 358 { 359 bool result = true; 360 if (index >= CountItems()) { 361 index = CountItems() - 1; 362 result = false; 363 notify = false; 364 } 365 if (index < 0) { 366 index = -1; 367 result = false; 368 } 369 if (index == fCurrentIndex && !notify) 370 return result; 371 372 fCurrentIndex = index; 373 _NotifyCurrentItemChanged(fCurrentIndex, notify); 374 return result; 375 } 376 377 378 int32 379 Playlist::CurrentItemIndex() const 380 { 381 return fCurrentIndex; 382 } 383 384 385 void 386 Playlist::GetSkipInfo(bool* canSkipPrevious, bool* canSkipNext) const 387 { 388 if (canSkipPrevious) 389 *canSkipPrevious = fCurrentIndex > 0; 390 if (canSkipNext) 391 *canSkipNext = fCurrentIndex < CountItems() - 1; 392 } 393 394 395 // pragma mark - 396 397 398 bool 399 Playlist::AddListener(Listener* listener) 400 { 401 BAutolock _(this); 402 if (listener && !fListeners.HasItem(listener)) 403 return fListeners.AddItem(listener); 404 return false; 405 } 406 407 408 void 409 Playlist::RemoveListener(Listener* listener) 410 { 411 BAutolock _(this); 412 fListeners.RemoveItem(listener); 413 } 414 415 416 // #pragma mark - support 417 418 419 void 420 Playlist::AppendItems(const BMessage* refsReceivedMessage, int32 appendIndex, 421 bool sortItems) 422 { 423 // the playlist is replaced by the refs in the message 424 // or the refs are appended at the appendIndex 425 // in the existing playlist 426 if (appendIndex == APPEND_INDEX_APPEND_LAST) 427 appendIndex = CountItems(); 428 429 bool add = appendIndex != APPEND_INDEX_REPLACE_PLAYLIST; 430 431 if (!add) 432 MakeEmpty(); 433 434 bool startPlaying = CountItems() == 0; 435 436 Playlist temporaryPlaylist; 437 Playlist* playlist = add ? &temporaryPlaylist : this; 438 bool hasSavedPlaylist = false; 439 440 // TODO: This is not very fair, we should abstract from 441 // entry ref representation and support more URLs. 442 BMessage archivedUrl; 443 if (refsReceivedMessage->FindMessage("mediaplayer:url", &archivedUrl) 444 == B_OK) { 445 BUrl url(&archivedUrl); 446 AddItem(new UrlPlaylistItem(url)); 447 } 448 449 entry_ref ref; 450 int32 subAppendIndex = CountItems(); 451 for (int i = 0; refsReceivedMessage->FindRef("refs", i, &ref) == B_OK; 452 i++) { 453 Playlist subPlaylist; 454 BString type = _MIMEString(&ref); 455 456 if (_IsPlaylist(type)) { 457 AppendPlaylistToPlaylist(ref, &subPlaylist); 458 // Do not sort the whole playlist anymore, as that 459 // will screw up the ordering in the saved playlist. 460 hasSavedPlaylist = true; 461 } else { 462 if (_IsQuery(type)) 463 AppendQueryToPlaylist(ref, &subPlaylist); 464 else if (_IsM3u(ref)) 465 AppendM3uToPlaylist(ref, &subPlaylist); 466 else { 467 if (!_ExtraMediaExists(this, ref)) { 468 AppendToPlaylistRecursive(ref, &subPlaylist); 469 } 470 } 471 472 // At least sort this subsection of the playlist 473 // if the whole playlist is not sorted anymore. 474 if (sortItems && hasSavedPlaylist) 475 subPlaylist.Sort(); 476 } 477 478 if (!subPlaylist.IsEmpty()) { 479 // Add to recent documents 480 be_roster->AddToRecentDocuments(&ref, kAppSig); 481 } 482 483 int32 subPlaylistCount = subPlaylist.CountItems(); 484 AdoptPlaylist(subPlaylist, subAppendIndex); 485 subAppendIndex += subPlaylistCount; 486 } 487 488 if (sortItems) 489 playlist->Sort(); 490 491 if (add) 492 AdoptPlaylist(temporaryPlaylist, appendIndex); 493 494 if (startPlaying) { 495 // open first file 496 SetCurrentItemIndex(0); 497 } 498 } 499 500 501 /*static*/ void 502 Playlist::AppendToPlaylistRecursive(const entry_ref& ref, Playlist* playlist) 503 { 504 // recursively append the ref (dive into folders) 505 BEntry entry(&ref, true); 506 if (entry.InitCheck() != B_OK || !entry.Exists()) 507 return; 508 509 if (entry.IsDirectory()) { 510 BDirectory dir(&entry); 511 if (dir.InitCheck() != B_OK) 512 return; 513 514 entry.Unset(); 515 516 entry_ref subRef; 517 while (dir.GetNextRef(&subRef) == B_OK) { 518 AppendToPlaylistRecursive(subRef, playlist); 519 } 520 } else if (entry.IsFile()) { 521 BString mimeString = _MIMEString(&ref); 522 if (_IsMediaFile(mimeString)) { 523 PlaylistItem* item = new (std::nothrow) FilePlaylistItem(ref); 524 if (!_ExtraMediaExists(playlist, ref)) { 525 _BindExtraMedia(item); 526 if (item != NULL && !playlist->AddItem(item)) 527 delete item; 528 } else 529 delete item; 530 } else 531 printf("MIME Type = %s\n", mimeString.String()); 532 } 533 } 534 535 536 /*static*/ void 537 Playlist::AppendPlaylistToPlaylist(const entry_ref& ref, Playlist* playlist) 538 { 539 BEntry entry(&ref, true); 540 if (entry.InitCheck() != B_OK || !entry.Exists()) 541 return; 542 543 BString mimeString = _MIMEString(&ref); 544 if (_IsTextPlaylist(mimeString)) { 545 //printf("RunPlaylist thing\n"); 546 BFile file(&ref, B_READ_ONLY); 547 FileReadWrite lineReader(&file); 548 549 BString str; 550 entry_ref refPath; 551 status_t err; 552 BPath path; 553 while (lineReader.Next(str)) { 554 str = str.RemoveFirst("file://"); 555 str = str.RemoveLast(".."); 556 path = BPath(str.String()); 557 printf("Line %s\n", path.Path()); 558 if (path.Path() != NULL) { 559 if ((err = get_ref_for_path(path.Path(), &refPath)) == B_OK) { 560 PlaylistItem* item 561 = new (std::nothrow) FilePlaylistItem(refPath); 562 if (item == NULL || !playlist->AddItem(item)) 563 delete item; 564 } else { 565 printf("Error - %s: [%" B_PRIx32 "]\n", strerror(err), 566 err); 567 } 568 } else 569 printf("Error - No File Found in playlist\n"); 570 } 571 } else if (_IsBinaryPlaylist(mimeString)) { 572 BFile file(&ref, B_READ_ONLY); 573 Playlist temp; 574 if (temp.Unflatten(&file) == B_OK) 575 playlist->AdoptPlaylist(temp, playlist->CountItems()); 576 } 577 } 578 579 580 /*static*/ void 581 Playlist::AppendM3uToPlaylist(const entry_ref& ref, Playlist* playlist) 582 { 583 BFile file(&ref, B_READ_ONLY); 584 FileReadWrite lineReader(&file); 585 586 BString line; 587 while (lineReader.Next(line)) { 588 if (line.FindFirst("#") != 0) { 589 BPath path(line.String()); 590 entry_ref refPath; 591 status_t err; 592 593 if ((err = get_ref_for_path(path.Path(), &refPath)) == B_OK) { 594 PlaylistItem* item 595 = new (std::nothrow) FilePlaylistItem(refPath); 596 if (item == NULL || !playlist->AddItem(item)) 597 delete item; 598 } else { 599 BUrl url(line.String()); 600 if (url.IsValid()) { 601 PlaylistItem* item 602 = new (std::nothrow) UrlPlaylistItem(url); 603 if (item == NULL || !playlist->AddItem(item)) 604 delete item; 605 } else { 606 printf("Error - %s: [%" B_PRIx32 "]\n", strerror(err), err); 607 } 608 } 609 } 610 611 line.Truncate(0); 612 } 613 } 614 615 616 /*static*/ void 617 Playlist::AppendQueryToPlaylist(const entry_ref& ref, Playlist* playlist) 618 { 619 BQueryFile query(&ref); 620 if (query.InitCheck() != B_OK) 621 return; 622 623 entry_ref foundRef; 624 while (query.GetNextRef(&foundRef) == B_OK) { 625 PlaylistItem* item = new (std::nothrow) FilePlaylistItem(foundRef); 626 if (item == NULL || !playlist->AddItem(item)) 627 delete item; 628 } 629 } 630 631 632 void 633 Playlist::NotifyImportFailed() 634 { 635 BAutolock _(this); 636 _NotifyImportFailed(); 637 } 638 639 640 /*static*/ bool 641 Playlist::ExtraMediaExists(Playlist* playlist, PlaylistItem* item) 642 { 643 FilePlaylistItem* fileItem = dynamic_cast<FilePlaylistItem*>(item); 644 if (fileItem != NULL) 645 return _ExtraMediaExists(playlist, fileItem->Ref()); 646 647 // If we are here let's see if it is an url 648 UrlPlaylistItem* urlItem = dynamic_cast<UrlPlaylistItem*>(item); 649 if (urlItem == NULL) 650 return true; 651 652 return _ExtraMediaExists(playlist, urlItem->Url()); 653 } 654 655 656 // #pragma mark - private 657 658 659 /*static*/ bool 660 Playlist::_ExtraMediaExists(Playlist* playlist, const entry_ref& ref) 661 { 662 BString exceptExtension = _GetExceptExtension(BPath(&ref).Path()); 663 664 for (int32 i = 0; i < playlist->CountItems(); i++) { 665 FilePlaylistItem* compare = dynamic_cast<FilePlaylistItem*>(playlist->ItemAt(i)); 666 if (compare == NULL) 667 continue; 668 if (compare->Ref() != ref 669 && _GetExceptExtension(BPath(&compare->Ref()).Path()) == exceptExtension ) 670 return true; 671 } 672 return false; 673 } 674 675 676 /*static*/ bool 677 Playlist::_ExtraMediaExists(Playlist* playlist, BUrl url) 678 { 679 for (int32 i = 0; i < playlist->CountItems(); i++) { 680 UrlPlaylistItem* compare 681 = dynamic_cast<UrlPlaylistItem*>(playlist->ItemAt(i)); 682 if (compare == NULL) 683 continue; 684 if (compare->Url() == url) 685 return true; 686 } 687 return false; 688 } 689 690 691 /*static*/ bool 692 Playlist::_IsImageFile(const BString& mimeString) 693 { 694 BMimeType superType; 695 BMimeType fileType(mimeString.String()); 696 697 if (fileType.GetSupertype(&superType) != B_OK) 698 return false; 699 700 if (superType == "image") 701 return true; 702 703 return false; 704 } 705 706 707 /*static*/ bool 708 Playlist::_IsMediaFile(const BString& mimeString) 709 { 710 BMimeType superType; 711 BMimeType fileType(mimeString.String()); 712 713 if (fileType.GetSupertype(&superType) != B_OK) 714 return false; 715 716 // try a shortcut first 717 if (superType == "audio" || superType == "video") 718 return true; 719 720 // Look through our supported types 721 app_info appInfo; 722 if (be_app->GetAppInfo(&appInfo) != B_OK) 723 return false; 724 BFile appFile(&appInfo.ref, B_READ_ONLY); 725 if (appFile.InitCheck() != B_OK) 726 return false; 727 BMessage types; 728 BAppFileInfo appFileInfo(&appFile); 729 if (appFileInfo.GetSupportedTypes(&types) != B_OK) 730 return false; 731 732 const char* type; 733 for (int32 i = 0; types.FindString("types", i, &type) == B_OK; i++) { 734 if (strcasecmp(mimeString.String(), type) == 0) 735 return true; 736 } 737 738 return false; 739 } 740 741 742 /*static*/ bool 743 Playlist::_IsTextPlaylist(const BString& mimeString) 744 { 745 return mimeString.Compare(kTextPlaylistMimeString) == 0; 746 } 747 748 749 /*static*/ bool 750 Playlist::_IsBinaryPlaylist(const BString& mimeString) 751 { 752 return mimeString.Compare(kBinaryPlaylistMimeString) == 0; 753 } 754 755 756 /*static*/ bool 757 Playlist::_IsPlaylist(const BString& mimeString) 758 { 759 return _IsTextPlaylist(mimeString) || _IsBinaryPlaylist(mimeString); 760 } 761 762 763 /*static*/ bool 764 Playlist::_IsM3u(const entry_ref& ref) 765 { 766 BString path(BPath(&ref).Path()); 767 return path.FindLast(".m3u") == path.CountChars() - 4 768 || path.FindLast(".m3u8") == path.CountChars() - 5; 769 } 770 771 772 /*static*/ bool 773 Playlist::_IsQuery(const BString& mimeString) 774 { 775 return mimeString.Compare(BQueryFile::MimeType()) == 0; 776 } 777 778 779 /*static*/ BString 780 Playlist::_MIMEString(const entry_ref* ref) 781 { 782 BFile file(ref, B_READ_ONLY); 783 BNodeInfo nodeInfo(&file); 784 char mimeString[B_MIME_TYPE_LENGTH]; 785 if (nodeInfo.GetType(mimeString) != B_OK) { 786 BMimeType type; 787 if (BMimeType::GuessMimeType(ref, &type) != B_OK) 788 return BString(); 789 790 strlcpy(mimeString, type.Type(), B_MIME_TYPE_LENGTH); 791 nodeInfo.SetType(type.Type()); 792 } 793 return BString(mimeString); 794 } 795 796 797 // _BindExtraMedia() searches additional videos and audios 798 // and addes them as extra medias. 799 /*static*/ void 800 Playlist::_BindExtraMedia(PlaylistItem* item) 801 { 802 FilePlaylistItem* fileItem = dynamic_cast<FilePlaylistItem*>(item); 803 if (!fileItem) 804 return; 805 806 // If the media file is foo.mp3, _BindExtraMedia() searches foo.avi. 807 BPath mediaFilePath(&fileItem->Ref()); 808 BString mediaFilePathString = mediaFilePath.Path(); 809 BPath dirPath; 810 mediaFilePath.GetParent(&dirPath); 811 BDirectory dir(dirPath.Path()); 812 if (dir.InitCheck() != B_OK) 813 return; 814 815 BEntry entry; 816 BString entryPathString; 817 while (dir.GetNextEntry(&entry, true) == B_OK) { 818 if (!entry.IsFile()) 819 continue; 820 entryPathString = BPath(&entry).Path(); 821 if (entryPathString != mediaFilePathString 822 && _GetExceptExtension(entryPathString) == _GetExceptExtension(mediaFilePathString)) { 823 _BindExtraMedia(fileItem, entry); 824 } 825 } 826 } 827 828 829 /*static*/ void 830 Playlist::_BindExtraMedia(FilePlaylistItem* fileItem, const BEntry& entry) 831 { 832 entry_ref ref; 833 entry.GetRef(&ref); 834 BString mimeString = _MIMEString(&ref); 835 if (_IsMediaFile(mimeString)) { 836 fileItem->AddRef(ref); 837 } else if (_IsImageFile(mimeString)) { 838 fileItem->AddImageRef(ref); 839 } 840 } 841 842 843 /*static*/ BString 844 Playlist::_GetExceptExtension(const BString& path) 845 { 846 int32 periodPos = path.FindLast('.'); 847 if (periodPos <= path.FindLast('/')) 848 return path; 849 return BString(path.String(), periodPos); 850 } 851 852 853 // #pragma mark - notifications 854 855 856 void 857 Playlist::_NotifyItemAdded(PlaylistItem* item, int32 index) const 858 { 859 BList listeners(fListeners); 860 int32 count = listeners.CountItems(); 861 for (int32 i = 0; i < count; i++) { 862 Listener* listener = (Listener*)listeners.ItemAtFast(i); 863 listener->ItemAdded(item, index); 864 } 865 } 866 867 868 void 869 Playlist::_NotifyItemRemoved(int32 index) const 870 { 871 BList listeners(fListeners); 872 int32 count = listeners.CountItems(); 873 for (int32 i = 0; i < count; i++) { 874 Listener* listener = (Listener*)listeners.ItemAtFast(i); 875 listener->ItemRemoved(index); 876 } 877 } 878 879 880 void 881 Playlist::_NotifyItemsSorted() const 882 { 883 BList listeners(fListeners); 884 int32 count = listeners.CountItems(); 885 for (int32 i = 0; i < count; i++) { 886 Listener* listener = (Listener*)listeners.ItemAtFast(i); 887 listener->ItemsSorted(); 888 } 889 } 890 891 892 void 893 Playlist::_NotifyCurrentItemChanged(int32 newIndex, bool play) const 894 { 895 BList listeners(fListeners); 896 int32 count = listeners.CountItems(); 897 for (int32 i = 0; i < count; i++) { 898 Listener* listener = (Listener*)listeners.ItemAtFast(i); 899 listener->CurrentItemChanged(newIndex, play); 900 } 901 } 902 903 904 void 905 Playlist::_NotifyImportFailed() const 906 { 907 BList listeners(fListeners); 908 int32 count = listeners.CountItems(); 909 for (int32 i = 0; i < count; i++) { 910 Listener* listener = (Listener*)listeners.ItemAtFast(i); 911 listener->ImportFailed(); 912 } 913 } 914