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