void StPlayList::getSubList(StArrayList<StString>& theList, const size_t theStart, const size_t theEnd) const { theList.clear(); StMutexAuto anAutoLock(myMutex); size_t anIter = 0; StPlayItem* anItem = myFirst; for(; anItem != NULL; anItem = anItem->getNext(), ++anIter) { if(anIter == theStart) { break; } } if(anIter != theStart) { return; } for(; anItem != NULL; anItem = anItem->getNext(), ++anIter) { if(anIter == theEnd) { break; } theList.add(anItem->getTitle()); } }
bool StPlayList::walkToPosition(const size_t theId) { StMutexAuto anAutoLock(myMutex); size_t anIter = 0; for(StPlayItem* anItem = myFirst; anItem != NULL; anItem = anItem->getNext(), ++anIter) { if(anIter == theId) { if(myCurrent == anItem) { return false; } StPlayItem* aPrev = myCurrent; if(aPrev != NULL) { myStackPrev.push_back(aPrev); if(myStackPrev.size() > THE_UNDO_LIMIT) { myStackPrev.pop_front(); } } myCurrent = anItem; anAutoLock.unlock(); signals.onPositionChange(theId); return true; } } return false; }
bool StPlayList::saveM3U(const StCString& thePath) { StRawFile aFile; if(thePath.isEmpty() || !aFile.openFile(StRawFile::WRITE, thePath)) { return false; } StMutexAuto anAutoLock(myMutex); aFile.write(stCString("#EXTM3U")); for(StPlayItem* anItem = myFirst; anItem != NULL; anItem = anItem->getNext()) { const StFileNode* aNode = anItem->getFileNode(); if(aNode == NULL) { continue; } else if(aNode->size() < 2) { aFile.write(stCString("\n#EXTINF:0,")); if(anItem->hasCustomTitle()) { aFile.write(anItem->getTitle()); } aFile.write(stCString("\n")); aFile.write(aNode->getPath()); } } aFile.write(stCString("\n")); return true; }
void StPlayList::delPlayItem(StPlayItem* theRemItem) { if(myFirst == NULL || theRemItem == NULL) { // item does not exists in the list return; } // update first/last items if(theRemItem == myLast) { myLast = myLast->hasPrev() ? myLast->getPrev() : NULL; } if(myFirst == theRemItem) { myFirst = myFirst->hasNext() ? myFirst->getNext() : NULL; } // reset enumeration size_t aPosId = theRemItem->getPosition(); for(StPlayItem* anIter = theRemItem->getNext(); anIter != NULL; ++aPosId, anIter = anIter->getNext()) { anIter->setPosition(aPosId); } if(theRemItem->hasPrev()) { // connect previous and next items theRemItem->getPrev()->setNext(theRemItem->getNext()); } else if(theRemItem->hasNext()) { theRemItem->getNext()->setPrev(NULL); } myStackPrev.clear(); myStackNext.clear(); --myItemsCount; }
char* StPlayList::parseM3UIter(char* theIter, StFolder* theFolder, StString& theTitle) { if(*theIter == '\0') { return NULL; } char* aNextLine = nextLine(theIter); if(aNextLine > theIter + 1) { // replace LF or CRLF with '\0' *(aNextLine - 1) = '\0'; char* aTail = aNextLine - 2; if(*(aNextLine - 2) == '\x0D') { *(aNextLine - 2) = '\0'; --aTail; } // skip trailing spaces for(; *aTail == ' ' && aTail >= theIter; --aTail) { *aTail = '\0'; } } if(*theIter == '\0') { return aNextLine; // skip empty lines } if(*theIter != '#') { StString anItemPath = theIter; StFolder* aFolder = theFolder != NULL && StFileNode::isRelativePath(anItemPath) ? theFolder : &myFoldersRoot; StFileNode* aFileNode = new StFileNode(anItemPath, aFolder); aFolder->add(aFileNode); StPlayItem* anItem = new StPlayItem(aFileNode, myDefStParams); anItem->setTitle(theTitle); addPlayItem(anItem); theTitle = ""; } else if(stAreEqual(theIter, "#EXTINF:", 8)) { theIter += 8; for(; *theIter != '\0'; ++theIter) { if(*theIter == ',') { for(; *theIter == ' '; ++theIter) { // skip spaces in the beginning } theTitle = ++theIter; break; } } } return aNextLine; }
bool StPlayList::remove(const StString& thePath, const bool theToRemovePhysically) { StString aPath = thePath; StPlayItem* aRemItem = NULL; StMutexAuto anAutoLock(myMutex); if(myCurrent == NULL) { // empty playlist return false; } else if(aPath != myCurrent->getPath()) { // search play item for(StPlayItem* anItem = myFirst; anItem != NULL; anItem = anItem->getNext()) { if(aPath == anItem->getPath()) { aRemItem = anItem; break; } } } else { // walk to another playlist position aRemItem = myCurrent; const bool aPlayedFlag = aRemItem->getPlayedFlag(); if(myCurrent->hasNext()) { myCurrent = myCurrent->getNext(); } else if(myCurrent->hasPrev()) { myCurrent = myCurrent->getPrev(); } else { myCurrent = NULL; myPlayedCount = 0; } if(myCurrent != NULL) { if(aRemItem->getPlayedFlag() != aPlayedFlag) { // the item has not been played yet - mark it as such aRemItem->setPlayedFlag(aPlayedFlag); } else { // one played item has been removed --myPlayedCount; } } } // remove item itself const bool isDeleted = aRemItem != NULL && (!theToRemovePhysically || StFileNode::removeFile(aPath)); if(isDeleted) { delPlayItem(aRemItem); delete aRemItem; } anAutoLock.unlock(); signals.onPlaylistChange(); return isDeleted; }
void StPlayList::addToNode(const StHandle<StFileNode>& theFileNode, const StString& thePathToAdd) { StString aPath = theFileNode->getPath(); StMutexAuto anAutoLock(myMutex); if(myCurrent == NULL) { return; } else if(aPath != myCurrent->getPath()) { for(StPlayItem* anItem = myFirst; anItem != NULL; anItem = anItem->getNext()) { if(aPath == anItem->getPath()) { myCurrent = anItem; break; } } } StFileNode* aFileNode = myCurrent->getFileNode(); if(aFileNode->getParent() != &myFoldersRoot) { // convert filenode to metafile with empty root aFileNode->reParent(&myFoldersRoot); aFileNode->setSubPath(StString()); aFileNode->add(new StFileNode(aPath, aFileNode)); } aFileNode->add(new StFileNode(thePathToAdd, aFileNode)); }
void StPlayList::open(const StCString& thePath, const StCString& theItem) { StMutexAuto anAutoLock(myMutex); // check if it is recently played playlist bool hasTarget = !theItem.isEmpty(); StString aTarget = hasTarget ? theItem : thePath; if(!hasTarget) { for(size_t anIter = 0; anIter < myRecent.size(); ++anIter) { const StHandle<StRecentItem>& aRecent = myRecent[anIter]; const StHandle<StFileNode>& aFile = aRecent->File; if(aFile->size() != 1) { continue; } if(thePath.isEquals(aFile->getPath())) { hasTarget = true; aTarget = aFile->getValue(0)->getSubPath(); break; } } } clear(); int aSearchDeep = myRecursionDeep; StString aFolderPath; StString aFileName; if(StFolder::isFolder(thePath)) { // add all files from the folder and subfolders aFolderPath = thePath; aSearchDeep = myRecursionDeep; myPlsFile = addRecentFile(StFileNode(thePath)); // append to recent files list } else if(StFileNode::isFileExists(thePath)) { // search only current folder StFileNode::getFolderAndFile(thePath, aFolderPath, aFileName); aSearchDeep = 1; bool hasSupportedExt = false; StString anExt = StFileNode::getExtension(aFileName); for(size_t anExtId = 0; anExtId < myExtensions.size() && !hasSupportedExt; ++anExtId) { hasSupportedExt = anExt.isEqualsIgnoreCase(myExtensions[anExtId]); } // parse m3u playlist if(anExt.isEqualsIgnoreCase(stCString("m3u"))) { StRawFile aRawFile(thePath); if(aRawFile.readFile()) { StString aTitle; char* anIter = (char* )aRawFile.getBuffer(); if(anIter[0] == '\xEF' && anIter[1] == '\xBB' && anIter[2] == '\xBF') { // skip BOM for UTF8 written by some stupid programs anIter += 3; } while(anIter != NULL) { anIter = parseM3UIter(anIter, aTitle); } myPlsFile = addRecentFile(StFileNode(thePath)); // append to recent files list if(hasTarget) { // set current item for(StPlayItem* anItem = myFirst; anItem != NULL; anItem = anItem->getNext()) { if(anItem->getPath() == aTarget) { myCurrent = anItem; break; } } } anAutoLock.unlock(); signals.onPlaylistChange(); return; } } if(!hasSupportedExt) { // file with unsupported extension? StFileNode* aFileNode = new StFileNode(thePath, &myFoldersRoot); myFoldersRoot.add(aFileNode); addPlayItem(new StPlayItem(aFileNode, myDefStParams)); } } else { // not a filesystem element - probably url or invalid path StFileNode* aFileNode = new StFileNode(thePath, &myFoldersRoot); myFoldersRoot.add(aFileNode); addRecentFile(*aFileNode); // append to recent files list addPlayItem(new StPlayItem(aFileNode, myDefStParams)); anAutoLock.unlock(); signals.onPlaylistChange(); return; } StFolder* aSubFolder = new StFolder(aFolderPath, &myFoldersRoot); aSubFolder->init(myExtensions, aSearchDeep); myFoldersRoot.add(aSubFolder); addToPlayList(aSubFolder); myCurrent = myFirst; if(hasTarget || !aFileName.isEmpty()) { // set current item for(StPlayItem* anItem = myFirst; anItem != NULL; anItem = anItem->getNext()) { if(anItem->getPath() == aTarget) { myCurrent = anItem; if(myPlsFile.isNull()) { addRecentFile(*anItem->getFileNode()); // append to recent files list } break; } } } anAutoLock.unlock(); signals.onPlaylistChange(); }
bool StPlayList::walkToNext(const bool theToForce) { StMutexAuto anAutoLock(myMutex); if(myCurrent == NULL || (myToLoopSingle && !theToForce)) { return false; } else if(myIsShuffle && myItemsCount >= 3) { StPlayItem* aPrev = myCurrent; if(!myStackNext.empty()) { myCurrent = myStackNext.front(); myStackNext.pop_front(); } else { if((myPlayedCount >= (myItemsCount - 1)) || (myPlayedCount == 0)) { // reset the playback counter #ifdef _WIN32 FILETIME aTime; GetSystemTimeAsFileTime(&aTime); myRandGen.setSeed(aTime.dwLowDateTime); #else timeval aTime; gettimeofday(&aTime, NULL); myRandGen.setSeed(aTime.tv_usec); #endif myPlayedCount = 0; myCurrent->setPlayedFlag(!myCurrent->getPlayedFlag()); ST_DEBUG_LOG("Restart the shuffle"); } // determine next random position const size_t aCurrPos = myCurrent->getPosition(); bool aCurrFlag = myCurrent->getPlayedFlag(); StPlayItem* aNextItem = myCurrent; const size_t aNextPos = stMin(size_t(myRandGen.next() * myItemsCount), myItemsCount - 1); if(aNextPos > aCurrPos) { // forward direction for(size_t aNextDiff = aNextPos - aCurrPos; aNextItem != NULL && aNextDiff != 0; --aNextDiff) { aNextItem = aNextItem->getNext(); } } else { // backward direction for(size_t aNextDiff = aCurrPos - aNextPos; aNextItem != NULL && aNextDiff != 0; --aNextDiff) { aNextItem = aNextItem->getPrev(); } } if(aCurrFlag == aNextItem->getPlayedFlag()) { // find nearest position not yet played - prefer item farther from current one StPlayItem* aNextItem1 = aNextPos > aCurrPos ? aNextItem->getNext() : aNextItem->getPrev(); StPlayItem* aNextItem2 = aNextPos > aCurrPos ? aNextItem->getPrev() : aNextItem->getNext(); for(; aNextItem1 != NULL || aNextItem2 != NULL;) { if(aNextItem1 != NULL) { if(aCurrFlag != aNextItem1->getPlayedFlag()) { aNextItem = aNextItem1; break; } aNextItem1 = aNextPos > aCurrPos ? aNextItem1->getNext() : aNextItem1->getPrev(); } if(aNextItem2 != NULL) { if(aCurrFlag != aNextItem2->getPlayedFlag()) { aNextItem = aNextItem2; break; } aNextItem2 = aNextPos > aCurrPos ? aNextItem2->getPrev() : aNextItem2->getNext(); } } if(aCurrFlag == aNextItem->getPlayedFlag()) { // something wrong! ST_DEBUG_LOG("Disaster - next shuffle position not found!"); aCurrFlag = !aCurrFlag; myPlayedCount = 0; } } ST_DEBUG_LOG(aCurrPos + " -> " + aNextItem->getPosition()); ++myPlayedCount; aNextItem->setPlayedFlag(aCurrFlag); myCurrent = aNextItem; } if(aPrev != myCurrent && aPrev != NULL) { myStackPrev.push_back(aPrev); if(myStackPrev.size() > THE_UNDO_LIMIT) { myStackPrev.pop_front(); } } const size_t anItemId = myCurrent->getPosition(); anAutoLock.unlock(); signals.onPositionChange(anItemId); return true; } else if(myCurrent != myLast) { myCurrent = myCurrent->getNext(); const size_t anItemId = myCurrent->getPosition(); anAutoLock.unlock(); signals.onPositionChange(anItemId); return true; } else if(myIsLoopFlag) { return walkToFirst(); } return false; }