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 Stephan Aßmus <superstippi@gmx.de> 6 * Copyright (C) 2008 Fredrik Modéen <fredrik@modeen.se> (I have no problem changing my things to MIT) 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 #include "Playlist.h" 23 24 #include <debugger.h> 25 #include <new.h> 26 #include <stdio.h> 27 28 #include <Autolock.h> 29 #include <Directory.h> 30 #include <File.h> 31 #include <Message.h> 32 #include <Mime.h> 33 #include <NodeInfo.h> 34 #include <Path.h> 35 #include <String.h> 36 37 #include "FileReadWrite.h" 38 39 using std::nothrow; 40 41 // TODO: using BList for objects is bad, replace it with a template 42 43 Playlist::Listener::Listener() {} 44 Playlist::Listener::~Listener() {} 45 void Playlist::Listener::RefAdded(const entry_ref& ref, int32 index) {} 46 void Playlist::Listener::RefRemoved(int32 index) {} 47 void Playlist::Listener::RefsSorted() {} 48 void Playlist::Listener::CurrentRefChanged(int32 newIndex) {} 49 50 51 // #pragma mark - 52 53 54 Playlist::Playlist() 55 : BLocker("playlist lock") 56 , fRefs() 57 , fCurrentIndex(-1) 58 { 59 } 60 61 62 Playlist::~Playlist() 63 { 64 MakeEmpty(); 65 66 if (fListeners.CountItems() > 0) 67 debugger("Playlist::~Playlist() - there are still listeners attached!"); 68 } 69 70 71 // #pragma mark - archiving 72 73 74 static const char* kPathKey = "path"; 75 76 77 status_t 78 Playlist::UnArchive(const BMessage* archive) 79 { 80 if (archive == NULL) 81 return B_BAD_VALUE; 82 83 MakeEmpty(); 84 85 BString path; 86 for (int32 i = 0; archive->FindString(kPathKey, i, &path) == B_OK; i++) { 87 BEntry entry(path.String(), false); 88 // don't follow links, we want to do that when opening files only 89 entry_ref ref; 90 if (entry.GetRef(&ref) != B_OK) 91 continue; 92 if (!AddRef(ref)) 93 return B_NO_MEMORY; 94 } 95 96 return B_OK; 97 } 98 99 100 status_t 101 Playlist::Archive(BMessage* into) const 102 { 103 if (into == NULL) 104 return B_BAD_VALUE; 105 106 int32 count = fRefs.CountItems(); 107 for (int32 i = 0; i < count; i++) { 108 const entry_ref* ref = (entry_ref*)fRefs.ItemAtFast(i); 109 BPath path(ref); 110 if (path.InitCheck() != B_OK) 111 continue; 112 status_t ret = into->AddString(kPathKey, path.Path()); 113 if (ret != B_OK) 114 return ret; 115 } 116 117 return B_OK; 118 } 119 120 121 // #pragma mark - list access 122 123 124 void 125 Playlist::MakeEmpty() 126 { 127 int32 count = fRefs.CountItems(); 128 for (int32 i = count - 1; i >= 0; i--) { 129 entry_ref* ref = (entry_ref*)fRefs.RemoveItem(i); 130 _NotifyRefRemoved(i); 131 delete ref; 132 } 133 SetCurrentRefIndex(-1); 134 } 135 136 137 int32 138 Playlist::CountItems() const 139 { 140 return fRefs.CountItems(); 141 } 142 143 144 void 145 Playlist::Sort() 146 { 147 fRefs.SortItems(playlist_cmp); 148 _NotifyRefsSorted(); 149 } 150 151 152 bool 153 Playlist::AddRef(const entry_ref &ref) 154 { 155 return AddRef(ref, CountItems()); 156 } 157 158 159 bool 160 Playlist::AddRef(const entry_ref &ref, int32 index) 161 { 162 entry_ref* copy = new (nothrow) entry_ref(ref); 163 if (!copy) 164 return false; 165 if (!fRefs.AddItem(copy, index)) { 166 delete copy; 167 return false; 168 } 169 _NotifyRefAdded(ref, index); 170 171 if (index <= fCurrentIndex) 172 SetCurrentRefIndex(fCurrentIndex + 1); 173 174 return true; 175 } 176 177 178 bool 179 Playlist::AdoptPlaylist(Playlist& other) 180 { 181 return AdoptPlaylist(other, CountItems()); 182 } 183 184 185 bool 186 Playlist::AdoptPlaylist(Playlist& other, int32 index) 187 { 188 if (&other == this) 189 return false; 190 // NOTE: this is not intended to merge two "equal" playlists 191 // the given playlist is assumed to be a temporary "dummy" 192 if (fRefs.AddList(&other.fRefs, index)) { 193 // take care of the notifications 194 int32 count = other.fRefs.CountItems(); 195 for (int32 i = index; i < index + count; i++) { 196 entry_ref* ref = (entry_ref*)fRefs.ItemAtFast(i); 197 _NotifyRefAdded(*ref, i); 198 } 199 if (index <= fCurrentIndex) 200 SetCurrentRefIndex(fCurrentIndex + count); 201 // empty the other list, so that the entry_refs are no ours 202 other.fRefs.MakeEmpty(); 203 return true; 204 } 205 return false; 206 } 207 208 209 entry_ref 210 Playlist::RemoveRef(int32 index, bool careAboutCurrentIndex) 211 { 212 entry_ref _ref; 213 entry_ref* ref = (entry_ref*)fRefs.RemoveItem(index); 214 if (!ref) 215 return _ref; 216 _NotifyRefRemoved(index); 217 _ref = *ref; 218 delete ref; 219 220 if (careAboutCurrentIndex) { 221 if (index == fCurrentIndex) 222 SetCurrentRefIndex(-1); 223 else if (index < fCurrentIndex) 224 SetCurrentRefIndex(fCurrentIndex - 1); 225 } 226 227 return _ref; 228 } 229 230 231 int32 232 Playlist::IndexOf(const entry_ref& _ref) const 233 { 234 int32 count = CountItems(); 235 for (int32 i = 0; i < count; i++) { 236 entry_ref* ref = (entry_ref*)fRefs.ItemAtFast(i); 237 if (*ref == _ref) 238 return i; 239 } 240 return -1; 241 } 242 243 244 status_t 245 Playlist::GetRefAt(int32 index, entry_ref* _ref) const 246 { 247 if (!_ref) 248 return B_BAD_VALUE; 249 entry_ref* ref = (entry_ref*)fRefs.ItemAt(index); 250 if (!ref) 251 return B_BAD_INDEX; 252 *_ref = *ref; 253 254 return B_OK; 255 } 256 257 258 //bool 259 //Playlist::HasRef(const entry_ref& ref) const 260 //{ 261 // return IndexOf(ref) >= 0; 262 //} 263 264 265 266 // #pragma mark - navigation 267 268 269 bool 270 Playlist::SetCurrentRefIndex(int32 index) 271 { 272 bool result = true; 273 if (index >= CountItems() || index < 0) { 274 index = -1; 275 result = false; 276 } 277 278 if (index == fCurrentIndex) 279 return result; 280 281 fCurrentIndex = index; 282 _NotifyCurrentRefChanged(fCurrentIndex); 283 return result; 284 } 285 286 287 int32 288 Playlist::CurrentRefIndex() const 289 { 290 return fCurrentIndex; 291 } 292 293 294 void 295 Playlist::GetSkipInfo(bool* canSkipPrevious, bool* canSkipNext) const 296 { 297 if (canSkipPrevious) 298 *canSkipPrevious = fCurrentIndex > 0; 299 if (canSkipNext) 300 *canSkipNext = fCurrentIndex < CountItems() - 1; 301 } 302 303 304 // pragma mark - 305 306 307 bool 308 Playlist::AddListener(Listener* listener) 309 { 310 BAutolock _(this); 311 if (listener && !fListeners.HasItem(listener)) 312 return fListeners.AddItem(listener); 313 return false; 314 } 315 316 317 void 318 Playlist::RemoveListener(Listener* listener) 319 { 320 BAutolock _(this); 321 fListeners.RemoveItem(listener); 322 } 323 324 325 // #pragma mark - 326 327 328 void 329 Playlist::AppendRefs(const BMessage* refsReceivedMessage, int32 appendIndex) 330 { 331 // the playlist ist replaced by the refs in the message 332 // or the refs are appended at the appendIndex 333 // in the existing playlist 334 bool add = appendIndex >= 0; 335 336 if (!add) 337 MakeEmpty(); 338 339 bool startPlaying = CountItems() == 0; 340 341 Playlist temporaryPlaylist; 342 Playlist* playlist = add ? &temporaryPlaylist : this; 343 344 entry_ref ref; 345 for (int i = 0; refsReceivedMessage->FindRef("refs", i, &ref) == B_OK; i++) 346 AppendToPlaylistRecursive(ref, playlist); 347 playlist->Sort(); 348 349 if (add) 350 AdoptPlaylist(temporaryPlaylist, appendIndex); 351 352 if (startPlaying) { 353 // open first file 354 SetCurrentRefIndex(0); 355 } 356 } 357 358 359 /*static*/ void 360 Playlist::AppendToPlaylistRecursive(const entry_ref& ref, Playlist* playlist) 361 { 362 // recursively append the ref (dive into folders) 363 BEntry entry(&ref, true); 364 if (entry.InitCheck() < B_OK) { 365 printf("Not OK\n"); 366 return; 367 } 368 369 if (!entry.Exists()) { 370 BPath path = BPath(&ref); 371 //printf("Don't exist - %s\n", path.Path()); 372 return; 373 } 374 375 if (entry.IsDirectory()) { 376 BDirectory dir(&entry); 377 if (dir.InitCheck() < B_OK) 378 return; 379 entry.Unset(); 380 entry_ref subRef; 381 while (dir.GetNextRef(&subRef) == B_OK) 382 AppendToPlaylistRecursive(subRef, playlist); 383 } else if (entry.IsFile()) { 384 //printf("Is File\n"); 385 BString mimeString = _MIMEString(&ref); 386 if (_IsMediaFile(mimeString)) { 387 //printf("Adding\n"); 388 playlist->AddRef(ref); 389 } else if (_IsPlaylist(mimeString)) { 390 //printf("RunPlaylist thing\n"); 391 BFile file(&ref, B_READ_ONLY); 392 FileReadWrite lineReader(&file); 393 394 BString str; 395 entry_ref refPath; 396 status_t err; 397 BPath path; 398 while (lineReader.Next(str)) { 399 str = str.RemoveFirst("file://"); 400 str = str.RemoveLast(".."); 401 path = BPath(str.String()); 402 printf("Line %s\n", path.Path()); 403 if (path.Path() != NULL) { 404 if ((err = get_ref_for_path(path.Path(), &refPath)) == B_OK) { 405 playlist->AddRef(refPath); 406 } else 407 printf("Error - %s: [%lx]\n", strerror(err), (int32) err); 408 } else 409 printf("Error - No File Found in playlist\n"); 410 } 411 } else 412 printf("MIME Type = %s\n", mimeString.String()); 413 } 414 } 415 416 417 // #pragma mark - 418 419 420 int 421 Playlist::playlist_cmp(const void *p1, const void *p2) 422 { 423 // compare complete path 424 BEntry a(*(const entry_ref **)p1, false); 425 BEntry b(*(const entry_ref **)p2, false); 426 427 BPath aPath(&a); 428 BPath bPath(&b); 429 430 return strcmp(aPath.Path(), bPath.Path()); 431 } 432 433 434 /*static*/ void 435 Playlist::_AddPlayListFileToPlayList(const entry_ref& ref, Playlist* playlist) 436 { 437 438 BFile file(&ref, B_READ_ONLY); 439 FileReadWrite lineReader(&file); 440 441 BString str; 442 while (lineReader.Next(str)) { 443 BPath path = BPath(str.String()); 444 entry_ref refPath; 445 status_t err; 446 if ((err = get_ref_for_path(path.Path(), &refPath)) == B_OK) { 447 AppendToPlaylistRecursive(refPath, playlist); 448 } else 449 printf("Error - %s: [%lx]\n", strerror(err), (int32) err); 450 } 451 /* 452 Read line for x to the end of file (while?) 453 make a enty_ref and call AppendToPlaylistRecursive(with new entry, playlist) 454 */ 455 } 456 457 458 /*static*/ bool 459 Playlist::_IsMediaFile(const BString& mimeString) 460 { 461 BMimeType superType; 462 BMimeType fileType(mimeString.String()); 463 464 if (fileType.GetSupertype(&superType) != B_OK) 465 return false; 466 467 // TODO: some media files have other super types, I think 468 // for example ASF has "application" super type... so it would 469 // need special handling 470 return (superType == "audio" || superType == "video"); 471 } 472 473 474 /*static*/ bool 475 Playlist::_IsPlaylist(const BString& mimeString) 476 { 477 return mimeString.Compare("text/x-playlist") == 0; 478 } 479 480 481 /*static*/ BString 482 Playlist::_MIMEString(const entry_ref* ref) 483 { 484 BFile file(ref, B_READ_ONLY); 485 BNodeInfo nodeInfo(&file); 486 char mimeString[B_MIME_TYPE_LENGTH]; 487 if (nodeInfo.GetType(mimeString) != B_OK) { 488 BMimeType type; 489 if (BMimeType::GuessMimeType(ref, &type) != B_OK) 490 return BString(); 491 492 strlcpy(mimeString, type.Type(), B_MIME_TYPE_LENGTH); 493 nodeInfo.SetType(type.Type()); 494 } 495 return BString(mimeString); 496 } 497 498 499 void 500 Playlist::_NotifyRefAdded(const entry_ref& ref, int32 index) const 501 { 502 BList listeners(fListeners); 503 int32 count = listeners.CountItems(); 504 for (int32 i = 0; i < count; i++) { 505 Listener* listener = (Listener*)listeners.ItemAtFast(i); 506 listener->RefAdded(ref, index); 507 } 508 } 509 510 511 void 512 Playlist::_NotifyRefRemoved(int32 index) const 513 { 514 BList listeners(fListeners); 515 int32 count = listeners.CountItems(); 516 for (int32 i = 0; i < count; i++) { 517 Listener* listener = (Listener*)listeners.ItemAtFast(i); 518 listener->RefRemoved(index); 519 } 520 } 521 522 523 void 524 Playlist::_NotifyRefsSorted() const 525 { 526 BList listeners(fListeners); 527 int32 count = listeners.CountItems(); 528 for (int32 i = 0; i < count; i++) { 529 Listener* listener = (Listener*)listeners.ItemAtFast(i); 530 listener->RefsSorted(); 531 } 532 } 533 534 535 void 536 Playlist::_NotifyCurrentRefChanged(int32 newIndex) const 537 { 538 BList listeners(fListeners); 539 int32 count = listeners.CountItems(); 540 for (int32 i = 0; i < count; i++) { 541 Listener* listener = (Listener*)listeners.ItemAtFast(i); 542 listener->CurrentRefChanged(newIndex); 543 } 544 } 545 546