void StPlayList::clear() { StMutexAuto anAutoLock(myMutex); if(myFirst != NULL) { myWasCleared = true; mySerial.increment(); } if(!myPlsFile.isNull() && myCurrent != NULL) { if(myPlsFile->File->isEmpty()) { myPlsFile->File->add(new StFileNode(myCurrent->getPath(), myPlsFile->File.access())); } else { myPlsFile->File->changeValue(0)->setSubPath(myCurrent->getPath()); } } myPlsFile.nullify(); // destroy double-linked list content for(StPlayItem *anItem(myFirst), *anItemToDel(NULL); anItem != NULL;) { anItemToDel = anItem; anItem = anItem->getNext(); delete anItemToDel; } myStackPrev.clear(); myStackNext.clear(); myFirst = myLast = myCurrent = NULL; myItemsCount = myPlayedCount = 0; anAutoLock.unlock(); signals.onPlaylistChange(); }
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::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()); } }
int32_t StPlayList::getSerial() { StMutexAuto anAutoLock(myMutex); if(myWasCleared && myFirst != NULL) { myWasCleared = false; mySerial.increment(); } return mySerial.getValue(); }
void StPlayList::currentToRecent() { StMutexAuto anAutoLock(myMutex); if( myCurrent == NULL || !myPlsFile.isNull()) { return; } StHandle<StPlayList::StRecentItem> aRecent = addRecentFile(*myCurrent->getFileNode()); aRecent->Params = myCurrent->getParams(); }
bool StPlayList::walkToFirst() { StMutexAuto anAutoLock(myMutex); bool wasntFirst = (myCurrent != myFirst); myCurrent = myFirst; if(wasntFirst) { myStackPrev.clear(); myStackNext.clear(); anAutoLock.unlock(); signals.onPositionChange(0); } return wasntFirst; }
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; }
bool StPlayList::walkToLast() { StMutexAuto anAutoLock(myMutex); bool wasntLast = (myCurrent != myLast); myCurrent = myLast; if(wasntLast) { myStackPrev.clear(); myStackNext.clear(); const size_t anItemId = myCurrent != NULL ? myCurrent->getPosition() : 0; anAutoLock.unlock(); signals.onPositionChange(anItemId); } return wasntLast; }
void StPlayList::addOneFile(const StString& theFilePath, const StMIME& theFileMIME) { StMutexAuto anAutoLock(myMutex); StFileNode* aFileNode = new StFileNode(theFilePath, &myFoldersRoot); aFileNode->setMIME(theFileMIME); myFoldersRoot.add(aFileNode); addRecentFile(*aFileNode); // append to recent files list addPlayItem(new StPlayItem(aFileNode, myDefStParams)); anAutoLock.unlock(); signals.onPlaylistChange(); }
StHandle<StFileNode> StPlayList::getCurrentFile() { StMutexAuto anAutoLock(myMutex); if(myCurrent == NULL) { // empty list return StHandle<StFileNode>(); } StFileNode* aFileNode = myCurrent->getFileNode(); if(aFileNode == NULL) { // invalid item return StHandle<StFileNode>(); } return new StFileNode(aFileNode->getPath()); }
StPlayList::CurrentPosition StPlayList::getCurrentPosition() const { StMutexAuto anAutoLock(myMutex); if(myCurrent == NULL) { return CurrentPosition_NONE; } else if(myCurrent == myFirst) { if(myCurrent == myLast) { return CurrentPosition_Single; } return CurrentPosition_First; } else if(myCurrent == myLast) { return CurrentPosition_Last; } return CurrentPosition_Middle; }
void StPlayList::getRecentList(StArrayList<StString>& theList) const { theList.clear(); StMutexAuto anAutoLock(myMutex); for(size_t anIter = 0; anIter < myRecent.size(); ++anIter) { const StHandle<StRecentItem>& aRecent = myRecent[anIter]; const StHandle<StFileNode>& aFile = aRecent->File; const StString aPath = aFile->size() == 2 ? aFile->getValue(0)->getPath() : aFile->getPath(); StString aTitleString; StString aFolder; StFileNode::getFolderAndFile(aPath, aFolder, aTitleString); theList.add(aTitleString); } }
void StPlayList::setTitle(const StHandle<StStereoParams>& theKey, const StString& theTitle) { StMutexAuto anAutoLock(myMutex); if(myCurrent == NULL) { return; } if(theKey != myCurrent->getParams()) { return; } const size_t anItemId = myCurrent->getPosition(); myCurrent->setTitle(theTitle); anAutoLock.unlock(); signals.onTitleChange(anItemId); }
void StPlayList::updateRecent(const StHandle<StFileNode>& theFile, const StHandle<StStereoParams>& theParams) { StMutexAuto anAutoLock(myMutex); if(!myPlsFile.isNull() && myPlsFile->File == theFile) { // remember properties of last played file myPlsFile->Params = theParams; return; } for(size_t anIter = 0; anIter < myRecent.size(); ++anIter) { StHandle<StRecentItem>& aRecent = myRecent[anIter]; if(stAreSameRecent(*theFile, *aRecent->File)) { aRecent->Params = theParams; return; } } }
size_t StPlayList::findRecent(const StString thePathL, const StString thePathR) const { StFileNode aNode; if(thePathR.isEmpty()) { aNode.setSubPath(thePathL); } else { aNode.add(new StFileNode(thePathL, &aNode)); aNode.add(new StFileNode(thePathR, &aNode)); } StMutexAuto anAutoLock(myMutex); for(size_t anIter = 0; anIter < myRecent.size(); ++anIter) { const StHandle<StRecentItem>& aRecent = myRecent[anIter]; if(stAreSameRecent(aNode, *aRecent->File)) { return anIter; } } return size_t(-1); }
StString StPlayList::dumpRecentList() const { StMutexAuto anAutoLock(myMutex); StArgumentsMap aMap; for(size_t anIter = 0; anIter < myRecent.size(); ++anIter) { const StHandle<StRecentItem>& aRecent = myRecent[anIter]; const StHandle<StFileNode>& aFile = aRecent->File; const StHandle<StStereoParams>& aParams = aRecent->Params; if(!myPlsFile.isNull() && aFile == myPlsFile->File && myCurrent != NULL) { StArgument anArgFile(StString("file") + anIter, aFile->getPath()); StArgument anArgPos (StString("pos") + anIter, myCurrent->getPath()); aMap.add(anArgFile); aMap.add(anArgPos); } else if(aFile->isEmpty()) { StArgument anArgFile(StString("file") + anIter, aFile->getPath()); aMap.add(anArgFile); } else if(aFile->size() == 1) { StArgument anArgFile(StString("file") + anIter, aFile->getPath()); StArgument anArgPos (StString("pos") + anIter, aFile->getValue(0)->getSubPath()); aMap.add(anArgFile); aMap.add(anArgPos); } else if(aFile->size() == 2) { StArgument anArgLeft (StString("left") + anIter, aFile->getValue(0)->getPath()); StArgument anArgRight(StString("right") + anIter, aFile->getValue(1)->getPath()); aMap.add(anArgLeft); aMap.add(anArgRight); } if(!aParams.isNull()) { if(aParams->Timestamp > 360.0) { std::stringstream aStream; aStream.imbue(std::locale("C")); aStream << aParams->Timestamp; const StString aStrValue = aStream.str().c_str(); StArgument anArgTime(StString("time") + anIter, aStrValue); aMap.add(anArgTime); } } } return aMap.toString(); }
void StPlayList::loadRecentList(const StString theString) { StMutexAuto anAutoLock(myMutex); StArgumentsMap aMap; aMap.parseString(theString); myRecent.clear(); for(size_t anIter = 0; anIter < myRecentLimit; ++anIter) { const StArgument anArgFile = aMap[StString("file") + anIter]; const StArgument anArgLeft = aMap[StString("left") + anIter]; const StArgument anArgRight = aMap[StString("right") + anIter]; const StArgument anArgTime = aMap[StString("time") + anIter]; StHandle<StRecentItem> aRecent; if(anArgLeft.isValid() && anArgRight.isValid()) { StHandle<StFileNode> aFileNode = new StFileNode(StString()); aFileNode->add(new StFileNode(anArgLeft.getValue(), aFileNode.access())); aFileNode->add(new StFileNode(anArgRight.getValue(), aFileNode.access())); aRecent = addRecentFile(*aFileNode, false); } else if(anArgFile.isValid()) { StHandle<StFileNode> aFileNode = new StFileNode(anArgFile.getValue()); const StArgument anArgPos = aMap[StString("pos") + anIter]; if(anArgPos.isValid()) { aFileNode->add(new StFileNode(anArgPos.getValue(), aFileNode.access())); } aRecent = addRecentFile(*aFileNode, false); } if(aRecent.isNull()) { continue; } if(anArgTime.isValid()) { if(aRecent->Params.isNull()) { aRecent->Params = new StStereoParams(); } std::stringstream aStream; aStream.imbue(std::locale("C")); aStream << anArgTime.getValue().toCString(); aStream >> aRecent->Params->Timestamp; } } }
StHandle<StStereoParams> StPlayList::openRecent(const size_t theItemId) { StMutexAuto anAutoLock(myMutex); if(theItemId >= myRecent.size()) { return StHandle<StStereoParams>(); } const StHandle<StRecentItem> aRecent = myRecent[theItemId]; const StHandle<StFileNode> aFile = aRecent->File; if(aFile->size() == 2) { // stereo pair from two files clear(); addOneFile(aFile->getValue(0)->getPath(), aFile->getValue(1)->getPath()); } else if(aFile->size() == 1) { // playlist open(aFile->getPath(), aFile->getValue(0)->getSubPath()); } else { // single file open(aFile->getPath()); } return aRecent->Params; }
bool StPlayList::walkToPrev() { StMutexAuto anAutoLock(myMutex); if(myCurrent == NULL) { return false; } else if(myIsShuffle && myItemsCount >= 3) { StPlayItem* aNext = myCurrent; if(!myStackPrev.empty()) { myCurrent = myStackPrev.back(); myStackPrev.pop_back(); } else if(myCurrent != myFirst) { myCurrent = myCurrent->getPrev(); } else { aNext = NULL; } if(aNext != myCurrent && aNext != NULL) { myStackNext.push_front(aNext); if(myStackNext.size() > THE_UNDO_LIMIT) { myStackNext.pop_back(); } const size_t anItemId = myCurrent->getPosition(); anAutoLock.unlock(); signals.onPositionChange(anItemId); return true; } return false; } else if(myCurrent != myFirst) { myCurrent = myCurrent->getPrev(); const size_t anItemId = myCurrent->getPosition(); anAutoLock.unlock(); signals.onPositionChange(anItemId); return true; } else if(myIsLoopFlag) { return walkToLast(); } return false; }
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)); }
bool StPlayList::getCurrentFile(StHandle<StFileNode>& theFileNode, StHandle<StStereoParams>& theParams, StHandle<StFileNode>& thePlsFile) { theFileNode.nullify(); theParams.nullify(); thePlsFile.nullify(); StMutexAuto anAutoLock(myMutex); if(myCurrent == NULL) { // empty list return false; } StFileNode* aFileNode = myCurrent->getFileNode(); if(aFileNode == NULL) { // invalid item return false; } theFileNode = aFileNode->detach(); theParams = myCurrent->getParams(); if(!myPlsFile.isNull()) { thePlsFile = myPlsFile->File; } return true; }
void StPlayList::clearRecent() { StMutexAuto anAutoLock(myMutex); myRecent.clear(); myIsNewRecent = true; }
bool StPlayList::isShuffle() const { StMutexAuto anAutoLock(myMutex); return myIsShuffle; }
void StPlayList::setShuffle(bool theShuffle) { StMutexAuto anAutoLock(myMutex); myIsShuffle = theShuffle; }
size_t StPlayList::getCurrentId() const { StMutexAuto anAutoLock(myMutex); return (myCurrent != NULL) ? myCurrent->getPosition() : 0; }
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; }
void StPlayList::setLoopSingle(const bool theValue) { StMutexAuto anAutoLock(myMutex); myToLoopSingle = theValue; }
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(); }
StString StPlayList::getCurrentTitle() const { StMutexAuto anAutoLock(myMutex); return (myCurrent != NULL) ? myCurrent->getTitle() : StString(); }