/* * Copyright (C) 2017-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #include "ApplicationStackHelper.h" #include "FileItem.h" #include "URL.h" #include "Util.h" #include "cores/VideoPlayer/DVDFileInfo.h" #include "filesystem/StackDirectory.h" #include "utils/URIUtils.h" #include "utils/log.h" #include "video/VideoDatabase.h" #include using namespace XFILE; CApplicationStackHelper::CApplicationStackHelper(void) : m_currentStack(new CFileItemList) { } void CApplicationStackHelper::Clear() { m_currentStackPosition = 0; m_currentStack->Clear(); } void CApplicationStackHelper::OnPlayBackStarted(const CFileItem& item) { std::unique_lock lock(m_critSection); // time to clean up stack map if (!HasRegisteredStack(item)) m_stackmap.clear(); else { auto stack = GetRegisteredStack(item); Stackmap::iterator itr = m_stackmap.begin(); while (itr != m_stackmap.end()) { if (itr->second->m_pStack != stack) { itr = m_stackmap.erase(itr); } else { ++itr; } } } } bool CApplicationStackHelper::InitializeStack(const CFileItem & item) { if (!item.IsStack()) return false; auto stack = std::make_shared(item); Clear(); // read and determine kind of stack CStackDirectory dir; if (!dir.GetDirectory(item.GetURL(), *m_currentStack) || m_currentStack->IsEmpty()) return false; for (int i = 0; i < m_currentStack->Size(); i++) { // keep cross-references between stack parts and the stack SetRegisteredStack(GetStackPartFileItem(i), stack); SetRegisteredStackPartNumber(GetStackPartFileItem(i), i); } m_currentStackIsDiscImageStack = CFileItem(CStackDirectory::GetFirstStackedFile(item.GetPath()), false).IsDiscImage(); return true; } int CApplicationStackHelper::InitializeStackStartPartAndOffset(const CFileItem& item) { CVideoDatabase dbs; int64_t startoffset = 0; // case 1: stacked ISOs if (m_currentStackIsDiscImageStack) { // first assume values passed to the stack int selectedFile = item.m_lStartPartNumber; startoffset = item.GetStartOffset(); // check if we instructed the stack to resume from default if (startoffset == STARTOFFSET_RESUME) // selected file is not specified, pick the 'last' resume point { if (dbs.Open()) { CBookmark bookmark; std::string path = item.GetPath(); if (item.HasProperty("original_listitem_url") && URIUtils::IsPlugin(item.GetProperty("original_listitem_url").asString())) path = item.GetProperty("original_listitem_url").asString(); if (dbs.GetResumeBookMark(path, bookmark)) { startoffset = CUtil::ConvertSecsToMilliSecs(bookmark.timeInSeconds); selectedFile = bookmark.partNumber; } dbs.Close(); } else CLog::LogF(LOGERROR, "Cannot open VideoDatabase"); } // make sure that the selected part is within the boundaries if (selectedFile <= 0) { CLog::LogF(LOGWARNING, "Selected part {} out of range, playing part 1", selectedFile); selectedFile = 1; } else if (selectedFile > m_currentStack->Size()) { CLog::LogF(LOGWARNING, "Selected part {} out of range, playing part {}", selectedFile, m_currentStack->Size()); selectedFile = m_currentStack->Size(); } // set startoffset in selected item, track stack item for updating purposes, and finally play disc part m_currentStackPosition = selectedFile - 1; startoffset = startoffset > 0 ? STARTOFFSET_RESUME : 0; } // case 2: all other stacks else { // see if we have the info in the database //! @todo If user changes the time speed (FPS via framerate conversion stuff) //! then these times will be wrong. //! Also, this is really just a hack for the slow load up times we have //! A much better solution is a fast reader of FPS and fileLength //! that we can use on a file to get it's time. std::vector times; bool haveTimes(false); if (dbs.Open()) { haveTimes = dbs.GetStackTimes(item.GetPath(), times); dbs.Close(); } // calculate the total time of the stack uint64_t totalTimeMs = 0; for (int i = 0; i < m_currentStack->Size(); i++) { if (haveTimes) { // set end time in every part GetStackPartFileItem(i).SetEndOffset(times[i]); } else { int duration; if (!CDVDFileInfo::GetFileDuration(GetStackPartFileItem(i).GetPath(), duration)) { m_currentStack->Clear(); return false; } totalTimeMs += duration; // set end time in every part GetStackPartFileItem(i).SetEndOffset(totalTimeMs); times.push_back(totalTimeMs); } // set start time in every part SetRegisteredStackPartStartTimeMs(GetStackPartFileItem(i), GetStackPartStartTimeMs(i)); } // set total time in every part totalTimeMs = GetStackTotalTimeMs(); for (int i = 0; i < m_currentStack->Size(); i++) SetRegisteredStackTotalTimeMs(GetStackPartFileItem(i), totalTimeMs); uint64_t msecs = item.GetStartOffset(); if (!haveTimes || item.GetStartOffset() == STARTOFFSET_RESUME) { if (dbs.Open()) { // have our times now, so update the dB if (!haveTimes && !times.empty()) dbs.SetStackTimes(item.GetPath(), times); if (item.GetStartOffset() == STARTOFFSET_RESUME) { // can only resume seek here, not dvdstate CBookmark bookmark; std::string path = item.GetPath(); if (item.HasProperty("original_listitem_url") && URIUtils::IsPlugin(item.GetProperty("original_listitem_url").asString())) path = item.GetProperty("original_listitem_url").asString(); if (dbs.GetResumeBookMark(path, bookmark)) msecs = static_cast(bookmark.timeInSeconds * 1000); else msecs = 0; } dbs.Close(); } } m_currentStackPosition = GetStackPartNumberAtTimeMs(msecs); startoffset = msecs - GetStackPartStartTimeMs(m_currentStackPosition); } return startoffset; } bool CApplicationStackHelper::IsPlayingISOStack() const { return m_currentStack->Size() > 0 && m_currentStackIsDiscImageStack; } bool CApplicationStackHelper::IsPlayingRegularStack() const { return m_currentStack->Size() > 0 && !m_currentStackIsDiscImageStack; } bool CApplicationStackHelper::HasNextStackPartFileItem() const { return m_currentStackPosition < m_currentStack->Size() - 1; } uint64_t CApplicationStackHelper::GetStackPartEndTimeMs(int partNumber) const { return GetStackPartFileItem(partNumber).GetEndOffset(); } uint64_t CApplicationStackHelper::GetStackTotalTimeMs() const { return GetStackPartEndTimeMs(m_currentStack->Size() - 1); } int CApplicationStackHelper::GetStackPartNumberAtTimeMs(uint64_t msecs) { if (msecs > 0) { // work out where to seek to for (int partNumber = 0; partNumber < m_currentStack->Size(); partNumber++) { if (msecs < GetStackPartEndTimeMs(partNumber)) return partNumber; } } return 0; } void CApplicationStackHelper::ClearAllRegisteredStackInformation() { m_stackmap.clear(); } std::shared_ptr CApplicationStackHelper::GetRegisteredStack( const CFileItem& item) const { return GetStackPartInformation(item.GetPath())->m_pStack; } bool CApplicationStackHelper::HasRegisteredStack(const CFileItem& item) const { const auto it = m_stackmap.find(item.GetPath()); return it != m_stackmap.end() && it->second != nullptr; } void CApplicationStackHelper::SetRegisteredStack(const CFileItem& item, std::shared_ptr stackItem) { GetStackPartInformation(item.GetPath())->m_pStack = std::move(stackItem); } CFileItem& CApplicationStackHelper::GetStackPartFileItem(int partNumber) { return *(*m_currentStack)[partNumber]; } const CFileItem& CApplicationStackHelper::GetStackPartFileItem(int partNumber) const { return *(*m_currentStack)[partNumber]; } int CApplicationStackHelper::GetRegisteredStackPartNumber(const CFileItem& item) { return GetStackPartInformation(item.GetPath())->m_lStackPartNumber; } void CApplicationStackHelper::SetRegisteredStackPartNumber(const CFileItem& item, int partNumber) { GetStackPartInformation(item.GetPath())->m_lStackPartNumber = partNumber; } uint64_t CApplicationStackHelper::GetRegisteredStackPartStartTimeMs(const CFileItem& item) const { return GetStackPartInformation(item.GetPath())->m_lStackPartStartTimeMs; } void CApplicationStackHelper::SetRegisteredStackPartStartTimeMs(const CFileItem& item, uint64_t startTime) { GetStackPartInformation(item.GetPath())->m_lStackPartStartTimeMs = startTime; } uint64_t CApplicationStackHelper::GetRegisteredStackTotalTimeMs(const CFileItem& item) const { return GetStackPartInformation(item.GetPath())->m_lStackTotalTimeMs; } void CApplicationStackHelper::SetRegisteredStackTotalTimeMs(const CFileItem& item, uint64_t totalTime) { GetStackPartInformation(item.GetPath())->m_lStackTotalTimeMs = totalTime; } CApplicationStackHelper::StackPartInformationPtr CApplicationStackHelper::GetStackPartInformation( const std::string& key) { if (m_stackmap.count(key) == 0) { StackPartInformationPtr value(new StackPartInformation()); m_stackmap[key] = value; } return m_stackmap[key]; } CApplicationStackHelper::StackPartInformationPtr CApplicationStackHelper::GetStackPartInformation( const std::string& key) const { const auto it = m_stackmap.find(key); if (it == m_stackmap.end()) return std::make_shared(); return it->second; }