diff options
author | Kai Sommerfeld <kai.sommerfeld@gmx.com> | 2022-11-07 09:51:03 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-07 09:51:03 +0100 |
commit | 880e7acac4fd626ac0fdd3524c0cce673d96c5bb (patch) | |
tree | 2be6e688f82688487ce83ba0330aa2604cfed9ea | |
parent | ee399f6e182c2b1ae10ccec40ea171588349290a (diff) | |
parent | 66a23261d70d4dbbfbd60d2e19e205e965f23d45 (diff) |
Merge pull request #22097 from ksooo/video-ctx-menus
[video] Migrate Play/Queue context menu items to 'new' context menu system
-rw-r--r-- | addons/resource.language.en_gb/resources/strings.po | 10 | ||||
-rw-r--r-- | xbmc/ContextMenuManager.cpp | 1 | ||||
-rw-r--r-- | xbmc/FileItem.cpp | 74 | ||||
-rw-r--r-- | xbmc/FileItem.h | 11 | ||||
-rw-r--r-- | xbmc/interfaces/builtins/PlayerBuiltins.cpp | 55 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRRecordings.cpp | 24 | ||||
-rw-r--r-- | xbmc/utils/URIUtils.cpp | 10 | ||||
-rw-r--r-- | xbmc/utils/URIUtils.h | 2 | ||||
-rw-r--r-- | xbmc/video/CMakeLists.txt | 2 | ||||
-rw-r--r-- | xbmc/video/ContextMenus.cpp | 150 | ||||
-rw-r--r-- | xbmc/video/ContextMenus.h | 7 | ||||
-rw-r--r-- | xbmc/video/VideoDatabase.cpp | 38 | ||||
-rw-r--r-- | xbmc/video/VideoDatabase.h | 4 | ||||
-rw-r--r-- | xbmc/video/VideoUtils.cpp | 402 | ||||
-rw-r--r-- | xbmc/video/VideoUtils.h | 55 | ||||
-rw-r--r-- | xbmc/video/windows/GUIWindowVideoBase.cpp | 373 | ||||
-rw-r--r-- | xbmc/video/windows/GUIWindowVideoBase.h | 2 |
17 files changed, 898 insertions, 322 deletions
diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index a33bc81d88..6ed4ecdcd8 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -4915,7 +4915,6 @@ msgstr "" #: addons/skin.estuary/xml/Variables.xml #: xbmc/music/ContextMenus.h #: xbmc/video/ContextMenus.cpp -#: xbmc/video/windows/GUIWindowVideoBase.cpp msgctxt "#10008" msgid "Play next" msgstr "" @@ -6929,7 +6928,6 @@ msgstr "" #: system/settings/settings.xml #: xbmc/music/ContextMenus.h #: xbmc/video/ContextMenus.cpp -#: xbmc/video/windows/GUIWindowVideoBase.cpp msgctxt "#13347" msgid "Queue item" msgstr "" @@ -6995,7 +6993,13 @@ msgctxt "#13361" msgid "Enable voice" msgstr "" -#empty strings from id 13362 to 13374 +#. label for resume context menu item for video folders (like a TV show or a single season of a TV show) +#: xbmc/video/windows/GUIWindowVideoBase.cpp +msgctxt "#13362" +msgid "Continue watching" +msgstr "" + +#empty strings from id 13363 to 13374 msgctxt "#13375" msgid "Enable device" diff --git a/xbmc/ContextMenuManager.cpp b/xbmc/ContextMenuManager.cpp index d56f25f701..0efff74732 100644 --- a/xbmc/ContextMenuManager.cpp +++ b/xbmc/ContextMenuManager.cpp @@ -60,6 +60,7 @@ void CContextMenuManager::Init() std::unique_lock<CCriticalSection> lock(m_criticalSection); m_items = { + std::make_shared<CONTEXTMENU::CVideoBrowse>(), std::make_shared<CONTEXTMENU::CVideoResume>(), std::make_shared<CONTEXTMENU::CVideoPlay>(), std::make_shared<CONTEXTMENU::CVideoPlayAndQueue>(), diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 27821aefc8..9d711d1d83 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -3684,6 +3684,80 @@ bool CFileItem::LoadGameTag() return false; } +bool CFileItem::LoadDetails() +{ + if (IsVideoDb()) + { + if (HasVideoInfoTag()) + return true; + + CVideoDatabase db; + if (!db.Open()) + return false; + + VIDEODATABASEDIRECTORY::CQueryParams params; + VIDEODATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo(GetPath(), params); + + if (params.GetMovieId() >= 0) + db.GetMovieInfo(GetPath(), *GetVideoInfoTag(), static_cast<int>(params.GetMovieId())); + else if (params.GetMVideoId() >= 0) + db.GetMusicVideoInfo(GetPath(), *GetVideoInfoTag(), static_cast<int>(params.GetMVideoId())); + else if (params.GetEpisodeId() >= 0) + db.GetEpisodeInfo(GetPath(), *GetVideoInfoTag(), static_cast<int>(params.GetEpisodeId())); + else if (params.GetSetId() >= 0) // movie set + db.GetSetInfo(static_cast<int>(params.GetSetId()), *GetVideoInfoTag(), this); + else if (params.GetTvShowId() >= 0) + { + if (params.GetSeason() >= 0) + { + const int idSeason = db.GetSeasonId(static_cast<int>(params.GetTvShowId()), + static_cast<int>(params.GetSeason())); + if (idSeason >= 0) + db.GetSeasonInfo(idSeason, *GetVideoInfoTag(), this); + } + else + db.GetTvShowInfo(GetPath(), *GetVideoInfoTag(), static_cast<int>(params.GetTvShowId()), + this); + } + else + { + db.Close(); + return false; + } + db.Close(); + return true; + } + + if (m_bIsFolder && URIUtils::IsPVRRecordingFileOrFolder(GetPath())) + { + if (HasProperty("watchedepisodes") || HasProperty("watched")) + return true; + + const std::string parentPath = URIUtils::GetParentPath(GetPath()); + + //! @todo optimize, find a way to set the details of the directory without loading its content. + CFileItemList items; + if (CDirectory::GetDirectory(parentPath, items, "", XFILE::DIR_FLAG_DEFAULTS)) + { + const std::string path = GetPath(); + const auto it = std::find_if(items.cbegin(), items.cend(), + [path](const auto& entry) { return entry->GetPath() == path; }); + if (it != items.cend()) + { + *this = *(*it); + return true; + } + } + + CLog::LogF(LOGERROR, "Error filling item details (path={})", GetPath()); + return false; + } + + //! @todo add support for other types on demand. + CLog::LogF(LOGDEBUG, "Unsupported item type (path={})", GetPath()); + return false; +} + void CFileItemList::Swap(unsigned int item1, unsigned int item2) { if (item1 != item2 && item1 < m_items.size() && item2 < m_items.size()) diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index 01d64428ff..e0adc28e4d 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -517,8 +517,15 @@ public: // finds a matching local trailer file std::string FindTrailer() const; - virtual bool LoadMusicTag(); - virtual bool LoadGameTag(); + bool LoadMusicTag(); + bool LoadGameTag(); + + /*! \brief Load detailed data for an item constructed with only a path and a folder flag + Fills item's video info tag, sets item properties. + + \return true on success, false otherwise. + */ + bool LoadDetails(); /* Returns the content type of this item if known */ const std::string& GetMimeType() const { return m_mimetype; } diff --git a/xbmc/interfaces/builtins/PlayerBuiltins.cpp b/xbmc/interfaces/builtins/PlayerBuiltins.cpp index 992b647957..ac02169e71 100644 --- a/xbmc/interfaces/builtins/PlayerBuiltins.cpp +++ b/xbmc/interfaces/builtins/PlayerBuiltins.cpp @@ -19,9 +19,9 @@ #include "application/ApplicationComponents.h" #include "application/ApplicationPlayer.h" #include "application/ApplicationPowerHandling.h" -#include "filesystem/Directory.h" #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" +#include "music/MusicUtils.h" #include "playlists/PlayList.h" #include "pvr/PVRManager.h" #include "pvr/channels/PVRChannel.h" @@ -31,13 +31,12 @@ #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "storage/MediaManager.h" -#include "utils/FileExtensionProvider.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/log.h" #include "video/PlayerController.h" +#include "video/VideoUtils.h" #include "video/windows/GUIWindowVideoBase.h" -#include "view/GUIViewState.h" #include <math.h> @@ -423,6 +422,16 @@ static int PlayDVD(const std::vector<std::string>& params) namespace { +void GetItemsForPlayList(const std::shared_ptr<CFileItem>& item, CFileItemList& queuedItems) +{ + if (VIDEO_UTILS::IsItemPlayable(*item)) + VIDEO_UTILS::GetItemsForPlayList(item, queuedItems); + else if (MUSIC_UTILS::IsItemPlayable(*item)) + MUSIC_UTILS::GetItemsForPlayList(item, queuedItems); + else + CLog::LogF(LOGERROR, "Unable to get playlist items for {}", item->GetPath()); +} + int PlayOrQueueMedia(const std::vector<std::string>& params, bool forcePlay) { // restore to previous window if needed @@ -484,25 +493,27 @@ int PlayOrQueueMedia(const std::vector<std::string>& params, bool forcePlay) if (!item.m_bIsFolder && item.IsPlugin()) item.SetProperty("IsPlayable", true); - if ( askToResume == true ) + // Here, the item instance has only the path and the folder flag. We need some + // extended item properties to process resume successfully. Load them. + item.LoadDetails(); + + if (askToResume) { - if ( CGUIWindowVideoBase::ShowResumeMenu(item) == false ) + if (!CGUIWindowVideoBase::ShowResumeMenu(item)) return false; } if (item.m_bIsFolder || item.IsPlayList()) { CFileItemList items; - auto& provider = CServiceBroker::GetFileExtensionProvider(); - const std::string exts = provider.GetVideoExtensions() + "|" + provider.GetMusicExtensions(); - CUtil::GetRecursiveListing(item.GetPath(), items, exts, XFILE::DIR_FLAG_DEFAULTS); - + GetItemsForPlayList(std::make_shared<CFileItem>(item), items); if (!items.IsEmpty()) // fall through on non expandable playlist { - bool containsMusic = false, containsVideo = false; - for (int i = 0; i < items.Size(); i++) + bool containsMusic = false; + bool containsVideo = false; + for (const auto& i : items) { - bool isVideo = items[i]->IsVideo(); + const bool isVideo = i->IsVideo(); containsMusic |= !isVideo; containsVideo |= isVideo; @@ -510,12 +521,6 @@ int PlayOrQueueMedia(const std::vector<std::string>& params, bool forcePlay) break; } - std::unique_ptr<CGUIViewState> state(CGUIViewState::GetViewState(containsVideo ? WINDOW_VIDEO_NAV : WINDOW_MUSIC_NAV, items)); - if (state) - items.Sort(state->GetSortMethod()); - else - items.Sort(SortByLabel, SortOrderAscending); - PLAYLIST::Id playlistId = containsVideo ? PLAYLIST::TYPE_VIDEO : PLAYLIST::TYPE_MUSIC; // Mixed playlist item played by music player, mixed content folder has music removed if (containsMusic && containsVideo) @@ -563,7 +568,12 @@ int PlayOrQueueMedia(const std::vector<std::string>& params, bool forcePlay) if (items.Size() && !appPlayer->IsPlaying()) { playlistPlayer.SetCurrentPlaylist(playlistId); - playlistPlayer.Play(hasPlayOffset ? playOffset : oldSize, ""); + + if (containsMusic) + { + // video does not auto play on queue like music + playlistPlayer.Play(hasPlayOffset ? playOffset : oldSize, ""); + } } } @@ -573,6 +583,13 @@ int PlayOrQueueMedia(const std::vector<std::string>& params, bool forcePlay) if (forcePlay) { + if (item.HasVideoInfoTag() && item.GetStartOffset() == STARTOFFSET_RESUME) + { + const CBookmark bookmark = item.GetVideoInfoTag()->GetResumePoint(); + if (bookmark.IsSet()) + item.SetStartOffset(CUtil::ConvertSecsToMilliSecs(bookmark.timeInSeconds)); + } + if ((item.IsAudio() || item.IsVideo()) && !item.IsSmartPlayList()) CServiceBroker::GetPlaylistPlayer().Play(std::make_shared<CFileItem>(item), ""); else diff --git a/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp index 38c0bc4165..9dc8b886a1 100644 --- a/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp @@ -28,6 +28,7 @@ #include "settings/SettingsComponent.h" #include "utils/URIUtils.h" #include "video/VideoLibraryQueue.h" +#include "video/VideoUtils.h" #include "video/windows/GUIWindowVideoNav.h" #include <memory> @@ -235,19 +236,24 @@ bool CGUIWindowPVRRecordingsBase::OnMessage(CGUIMessage& message) break; } - if (item->m_bIsFolder) + if (!item->IsParentFolder() && message.GetParam1() == ACTION_PLAYER_PLAY) { - // recording folders and ".." folders in subfolders are handled by base class. - bReturn = false; - break; - } + if (item->m_bIsFolder) + { + if (CGUIWindowVideoNav::ShowResumeMenu(*item)) + VIDEO_UTILS::PlayItem(item); + } + else + CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayRecording( + *item, true /* check resume */); - if (message.GetParam1() == ACTION_PLAYER_PLAY) - { - CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayRecording( - *item, true /* check resume */); bReturn = true; } + else if (item->m_bIsFolder) + { + // recording folders and ".." folders in subfolders are handled by base class. + bReturn = false; + } else { switch (m_settings.GetIntValue(CSettings::SETTING_MYVIDEOS_SELECTACTION)) diff --git a/xbmc/utils/URIUtils.cpp b/xbmc/utils/URIUtils.cpp index c5353b9f2d..b2b9b23bc0 100644 --- a/xbmc/utils/URIUtils.cpp +++ b/xbmc/utils/URIUtils.cpp @@ -1108,6 +1108,16 @@ bool URIUtils::IsPVRRecordingFileOrFolder(const std::string& strFile) return StringUtils::StartsWith(strFile, "pvr://recordings"); } +bool URIUtils::IsPVRTVRecordingFileOrFolder(const std::string& strFile) +{ + return StringUtils::StartsWith(strFile, "pvr://recordings/tv"); +} + +bool URIUtils::IsPVRRadioRecordingFileOrFolder(const std::string& strFile) +{ + return StringUtils::StartsWith(strFile, "pvr://recordings/radio"); +} + bool URIUtils::IsMusicDb(const std::string& strFile) { return IsProtocol(strFile, "musicdb"); diff --git a/xbmc/utils/URIUtils.h b/xbmc/utils/URIUtils.h index 0594ce0997..85ba81bd81 100644 --- a/xbmc/utils/URIUtils.h +++ b/xbmc/utils/URIUtils.h @@ -140,6 +140,8 @@ public: static bool IsLiveTV(const std::string& strFile); static bool IsPVRRecording(const std::string& strFile); static bool IsPVRRecordingFileOrFolder(const std::string& strFile); + static bool IsPVRTVRecordingFileOrFolder(const std::string& strFile); + static bool IsPVRRadioRecordingFileOrFolder(const std::string& strFile); static bool IsMultiPath(const std::string& strPath); static bool IsMusicDb(const std::string& strFile); static bool IsNfs(const std::string& strFile); diff --git a/xbmc/video/CMakeLists.txt b/xbmc/video/CMakeLists.txt index 4cb7944725..00f9bafb54 100644 --- a/xbmc/video/CMakeLists.txt +++ b/xbmc/video/CMakeLists.txt @@ -10,6 +10,7 @@ set(SOURCES Bookmark.cpp VideoInfoTag.cpp VideoLibraryQueue.cpp VideoThumbLoader.cpp + VideoUtils.cpp ViewModeSettings.cpp) set(HEADERS Bookmark.h @@ -26,6 +27,7 @@ set(HEADERS Bookmark.h VideoInfoTag.h VideoLibraryQueue.h VideoThumbLoader.h + VideoUtils.h ViewModeSettings.h) core_add_library(video) diff --git a/xbmc/video/ContextMenus.cpp b/xbmc/video/ContextMenus.cpp index 190eaa707b..2f3ad46cdd 100644 --- a/xbmc/video/ContextMenus.cpp +++ b/xbmc/video/ContextMenus.cpp @@ -9,6 +9,7 @@ #include "ContextMenus.h" #include "Autorun.h" +#include "GUIUserMessages.h" #include "PlayListPlayer.h" #include "ServiceBroker.h" #include "application/Application.h" @@ -20,7 +21,9 @@ #include "guilib/LocalizeStrings.h" #include "playlists/PlayList.h" #include "settings/MediaSettings.h" +#include "utils/StringUtils.h" #include "utils/URIUtils.h" +#include "video/VideoUtils.h" #include "video/dialogs/GUIDialogVideoInfo.h" #include "video/windows/GUIWindowVideoBase.h" #include "view/GUIViewState.h" @@ -119,6 +122,39 @@ bool CVideoMarkUnWatched::Execute(const std::shared_ptr<CFileItem>& item) const return true; } +bool CVideoBrowse::IsVisible(const CFileItem& item) const +{ + if (item.IsFileFolder(EFILEFOLDER_MASK_ONBROWSE)) + return false; // handled by CMediaWindow + + return item.m_bIsFolder && VIDEO_UTILS::IsItemPlayable(item); +} + +bool CVideoBrowse::Execute(const std::shared_ptr<CFileItem>& item) const +{ + int target = WINDOW_INVALID; + if (URIUtils::IsPVRRadioRecordingFileOrFolder(item->GetPath())) + target = WINDOW_RADIO_RECORDINGS; + else if (URIUtils::IsPVRTVRecordingFileOrFolder(item->GetPath())) + target = WINDOW_TV_RECORDINGS; + else + target = WINDOW_VIDEO_NAV; + + auto& windowMgr = CServiceBroker::GetGUI()->GetWindowManager(); + + if (target == windowMgr.GetActiveWindow()) + { + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, target, 0, GUI_MSG_UPDATE); + msg.SetStringParam(item->GetPath()); + windowMgr.SendMessage(msg); + } + else + { + windowMgr.ActivateWindow(target, {item->GetPath(), "return"}); + } + return true; +} + std::string CVideoResume::GetLabel(const CFileItem& item) const { return CGUIWindowVideoBase::GetResumeString(item.GetItemToPlay()); @@ -130,7 +166,7 @@ bool CVideoResume::IsVisible(const CFileItem& itemIn) const if (item.IsDeleted()) // e.g. trashed pvr recording return false; - return CGUIWindowVideoBase::HasResumeItemOffset(&item); + return !CGUIWindowVideoBase::GetResumeString(item).empty(); } namespace @@ -180,31 +216,6 @@ void AddRecordingsToPlayListAndSort(const std::shared_ptr<CFileItem>& item, } } -void QueueRecordings(const std::shared_ptr<CFileItem>& item, bool bPlayNext) -{ - CFileItemList queuedItems; - AddRecordingsToPlayListAndSort(item, queuedItems); - - PLAYLIST::CPlayListPlayer& player = CServiceBroker::GetPlaylistPlayer(); - - // Determine the proper list to queue this element - PLAYLIST::Id playlistId = player.GetCurrentPlaylist(); - const auto& components = CServiceBroker::GetAppComponents(); - const auto appPlayer = components.GetComponent<CApplicationPlayer>(); - - if (playlistId == PLAYLIST::TYPE_NONE) - playlistId = appPlayer->GetPreferredPlaylist(); - if (playlistId == PLAYLIST::TYPE_NONE) - playlistId = PLAYLIST::TYPE_VIDEO; - - if (bPlayNext && appPlayer && appPlayer->IsPlaying()) - player.Insert(playlistId, queuedItems, player.GetCurrentSong() + 1); - else - player.Add(playlistId, queuedItems); - - player.SetCurrentPlaylist(playlistId); -} - void PlayAndQueueRecordings(const std::shared_ptr<CFileItem>& item, int windowId) { const std::shared_ptr<CFileItem> parentFolderItem = @@ -243,23 +254,9 @@ void PlayAndQueueRecordings(const std::shared_ptr<CFileItem>& item, int windowId player.Play(itemToPlay, ""); } -bool IsActiveRecordingsFolder(const CFileItem& item) -{ - if (item.m_bIsFolder && !item.IsParentFolder() && - URIUtils::IsPVRRecordingFileOrFolder(item.GetPath())) - { - // Note: Recordings contained in the folder must be sorted properly, thus this - // item is only available if one of the recordings windows is active. - const int windowId = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); - return windowId == WINDOW_TV_RECORDINGS || windowId == WINDOW_RADIO_RECORDINGS; - } - - return false; -} - void SetPathAndPlay(CFileItem& item) { - if (item.IsVideoDb()) + if (!item.m_bIsFolder && item.IsVideoDb()) { item.SetProperty("original_listitem_url", item.GetPath()); item.SetPath(item.GetVideoInfoTag()->m_strFileNameAndPath); @@ -270,25 +267,10 @@ void SetPathAndPlay(CFileItem& item) { g_application.PlayMedia(item, "", PLAYLIST::TYPE_VIDEO); } - else if (IsActiveRecordingsFolder(item)) - { - // recursively add items to play list - CFileItemList queuedItems; - AddRecordingsToPlayListAndSort(std::make_shared<CFileItem>(item), queuedItems); - - PLAYLIST::CPlayListPlayer& player = CServiceBroker::GetPlaylistPlayer(); - - player.ClearPlaylist(PLAYLIST::TYPE_VIDEO); - player.Reset(); - player.Add(PLAYLIST::TYPE_VIDEO, queuedItems); - player.SetCurrentPlaylist(PLAYLIST::TYPE_VIDEO); - - player.Play(); - } else { item.SetProperty("playlist_type_hint", PLAYLIST::TYPE_VIDEO); - CServiceBroker::GetPlaylistPlayer().Play(std::make_shared<CFileItem>(item), ""); + VIDEO_UTILS::PlayItem(std::make_shared<CFileItem>(item)); } } @@ -312,30 +294,14 @@ std::string CVideoPlay::GetLabel(const CFileItem& itemIn) const CFileItem item(itemIn.GetItemToPlay()); if (item.IsLiveTV()) return g_localizeStrings.Get(19000); // Switch to channel - if (CGUIWindowVideoBase::HasResumeItemOffset(&item)) + if (!CGUIWindowVideoBase::GetResumeString(item).empty()) return g_localizeStrings.Get(12021); // Play from beginning return g_localizeStrings.Get(208); // Play } -bool CVideoPlay::IsVisible(const CFileItem& itemIn) const +bool CVideoPlay::IsVisible(const CFileItem& item) const { - CFileItem item(itemIn.GetItemToPlay()); - if (item.IsDeleted()) // e.g. trashed pvr recording - return false; - - if (IsActiveRecordingsFolder(item)) - return true; - - // Music nav window has own "Play" context menu button, do not show this one. Playlist files - // like .m3u and .strm return IsVideo() true but from music nav window play with paplayer. - const int currentWindow = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); - if (currentWindow == WINDOW_MUSIC_NAV) - return false; - - if (item.m_bIsFolder) - return false; //! @todo implement - - return item.IsVideo() || item.IsLiveTV() || item.IsDVD() || item.IsCDDA(); + return VIDEO_UTILS::IsItemPlayable(item); } bool CVideoPlay::Execute(const std::shared_ptr<CFileItem>& itemIn) const @@ -354,10 +320,10 @@ bool CVideoQueue::IsVisible(const CFileItem& item) const if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_PLAYLIST) return false; // Already queued - if (item.IsUsablePVRRecording() || IsActiveRecordingsFolder(item)) - return true; + if (!item.CanQueue()) + return false; - return false; //! @todo implement + return VIDEO_UTILS::IsItemPlayable(item); } bool CVideoQueue::Execute(const std::shared_ptr<CFileItem>& item) const @@ -365,14 +331,8 @@ bool CVideoQueue::Execute(const std::shared_ptr<CFileItem>& item) const if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_PLAYLIST) return false; // Already queued - if (item->IsUsablePVRRecording() || IsActiveRecordingsFolder(*item)) - { - // recursively add items to play list - QueueRecordings(item, false); - return true; - } - - return true; //! @todo implement + VIDEO_UTILS::QueueItem(item, VIDEO_UTILS::QueuePosition::POSITION_END); + return true; }; bool CVideoPlayNext::IsVisible(const CFileItem& item) const @@ -380,10 +340,10 @@ bool CVideoPlayNext::IsVisible(const CFileItem& item) const if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_PLAYLIST) return false; // Already queued - if (item.IsUsablePVRRecording() || IsActiveRecordingsFolder(item)) - return true; + if (!item.CanQueue()) + return false; - return false; //! @todo implement + return VIDEO_UTILS::IsItemPlayable(item); } bool CVideoPlayNext::Execute(const std::shared_ptr<CFileItem>& item) const @@ -391,14 +351,8 @@ bool CVideoPlayNext::Execute(const std::shared_ptr<CFileItem>& item) const if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_PLAYLIST) return false; // Already queued - if (item->IsUsablePVRRecording() || IsActiveRecordingsFolder(*item)) - { - // recursively add items to play list - QueueRecordings(item, true); - return true; - } - - return true; //! @todo implement + VIDEO_UTILS::QueueItem(item, VIDEO_UTILS::QueuePosition::POSITION_BEGIN); + return true; }; bool CVideoPlayAndQueue::IsVisible(const CFileItem& item) const diff --git a/xbmc/video/ContextMenus.h b/xbmc/video/ContextMenus.h index b3faaddf8e..1cbffe48f1 100644 --- a/xbmc/video/ContextMenus.h +++ b/xbmc/video/ContextMenus.h @@ -69,6 +69,13 @@ struct CVideoMarkUnWatched : CStaticContextMenuAction bool Execute(const std::shared_ptr<CFileItem>& item) const override; }; +struct CVideoBrowse : CStaticContextMenuAction +{ + CVideoBrowse() : CStaticContextMenuAction(37015) {} // Browse into + bool IsVisible(const CFileItem& item) const override; + bool Execute(const std::shared_ptr<CFileItem>& item) const override; +}; + struct CVideoResume : IContextMenuItem { std::string GetLabel(const CFileItem& item) const override; diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index d832c72e3f..119299c39a 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -2138,6 +2138,19 @@ bool CVideoDatabase::GetTvShowInfo(const std::string& strPath, CVideoInfoTag& de bool CVideoDatabase::GetSeasonInfo(int idSeason, CVideoInfoTag& details, bool allDetails /* = true */) { + if (allDetails) + { + CFileItem dummy; // only interested in the tag data... + return GetSeasonInfo(idSeason, details, &dummy); + } + else + { + return GetSeasonInfo(idSeason, details, nullptr); + } +} + +bool CVideoDatabase::GetSeasonInfo(int idSeason, CVideoInfoTag& details, CFileItem* item) +{ if (idSeason < 0) return false; @@ -2153,7 +2166,7 @@ bool CVideoDatabase::GetSeasonInfo(int idSeason, CVideoInfoTag& details, bool al if (m_pDS->num_rows() != 1) return false; - if (allDetails) + if (item) { int idShow = m_pDS->fv(1).get_asInt(); @@ -2175,6 +2188,7 @@ bool CVideoDatabase::GetSeasonInfo(int idSeason, CVideoInfoTag& details, bool al if (season->HasVideoInfoTag() && season->GetVideoInfoTag()->m_iDbId == idSeason && season->GetVideoInfoTag()->m_iIdShow == idShow) { details = *season->GetVideoInfoTag(); + *item = *season; return true; } } @@ -2283,7 +2297,7 @@ bool CVideoDatabase::GetMusicVideoInfo(const std::string& strFilenameAndPath, CV return false; } -bool CVideoDatabase::GetSetInfo(int idSet, CVideoInfoTag& details) +bool CVideoDatabase::GetSetInfo(int idSet, CVideoInfoTag& details, CFileItem* item /* = nullptr */) { try { @@ -2299,6 +2313,8 @@ bool CVideoDatabase::GetSetInfo(int idSet, CVideoInfoTag& details) return false; details = *(items[0]->GetVideoInfoTag()); + if (item) + *item = *items[0]; return !details.IsEmpty(); } catch (...) @@ -3822,7 +3838,7 @@ void CVideoDatabase::GetDetailsFromDB(const dbiplus::sql_record* const record, i } } -CVideoInfoTag CVideoDatabase::GetDetailsByTypeAndId(VideoDbContentType type, int id) +bool CVideoDatabase::GetDetailsByTypeAndId(CFileItem& item, VideoDbContentType type, int id) { CVideoInfoTag details; details.Reset(); @@ -3833,7 +3849,7 @@ CVideoInfoTag CVideoDatabase::GetDetailsByTypeAndId(VideoDbContentType type, int GetMovieInfo("", details, id); break; case VideoDbContentType::TVSHOWS: - GetTvShowInfo("", details, id); + GetTvShowInfo("", details, id, &item); break; case VideoDbContentType::EPISODES: GetEpisodeInfo("", details, id); @@ -3842,10 +3858,20 @@ CVideoInfoTag CVideoDatabase::GetDetailsByTypeAndId(VideoDbContentType type, int GetMusicVideoInfo("", details, id); break; default: - break; + return false; } - return details; + item.SetFromVideoInfoTag(details); + return true; +} + +CVideoInfoTag CVideoDatabase::GetDetailsByTypeAndId(VideoDbContentType type, int id) +{ + CFileItem item; + if (GetDetailsByTypeAndId(item, type, id)) + return CVideoInfoTag(*item.GetVideoInfoTag()); + + return {}; } bool CVideoDatabase::GetStreamDetails(CFileItem& item) diff --git a/xbmc/video/VideoDatabase.h b/xbmc/video/VideoDatabase.h index 690450a0c9..675ec44e89 100644 --- a/xbmc/video/VideoDatabase.h +++ b/xbmc/video/VideoDatabase.h @@ -513,11 +513,12 @@ public: bool LoadVideoInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int getDetails = VideoDbDetailsAll); bool GetMovieInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idMovie = -1, int getDetails = VideoDbDetailsAll); bool GetTvShowInfo(const std::string& strPath, CVideoInfoTag& details, int idTvShow = -1, CFileItem* item = NULL, int getDetails = VideoDbDetailsAll); + bool GetSeasonInfo(int idSeason, CVideoInfoTag& details, CFileItem* item); bool GetSeasonInfo(int idSeason, CVideoInfoTag& details, bool allDetails = true); bool GetEpisodeBasicInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idEpisode = -1); bool GetEpisodeInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idEpisode = -1, int getDetails = VideoDbDetailsAll); bool GetMusicVideoInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idMVideo = -1, int getDetails = VideoDbDetailsAll); - bool GetSetInfo(int idSet, CVideoInfoTag& details); + bool GetSetInfo(int idSet, CVideoInfoTag& details, CFileItem* item = nullptr); bool GetFileInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idFile = -1); int GetPathId(const std::string& strPath); @@ -653,6 +654,7 @@ public: bool GetResumePoint(CVideoInfoTag& tag); bool GetStreamDetails(CFileItem& item); bool GetStreamDetails(CVideoInfoTag& tag) const; + bool GetDetailsByTypeAndId(CFileItem& item, VideoDbContentType type, int id); CVideoInfoTag GetDetailsByTypeAndId(VideoDbContentType type, int id); // scraper settings diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp new file mode 100644 index 0000000000..f35c59d818 --- /dev/null +++ b/xbmc/video/VideoUtils.cpp @@ -0,0 +1,402 @@ +/* + * Copyright (C) 2022 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 "VideoUtils.h" + +#include "FileItem.h" +#include "GUIPassword.h" +#include "PartyModeManager.h" +#include "PlayListPlayer.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "Util.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "dialogs/GUIDialogBusy.h" +#include "filesystem/Directory.h" +#include "filesystem/VideoDatabaseDirectory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "playlists/PlayList.h" +#include "playlists/PlayListFactory.h" +#include "settings/MediaSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "threads/IRunnable.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/log.h" +#include "video/VideoInfoTag.h" +#include "view/GUIViewState.h" + +namespace +{ +class CAsyncGetItemsForPlaylist : public IRunnable +{ +public: + CAsyncGetItemsForPlaylist(const std::shared_ptr<CFileItem>& item, CFileItemList& queuedItems) + : m_item(item), + m_resume(item->GetStartOffset() == STARTOFFSET_RESUME), + m_queuedItems(queuedItems) + { + } + + ~CAsyncGetItemsForPlaylist() override = default; + + void Run() override + { + // fast lookup is needed here + m_queuedItems.SetFastLookup(true); + + GetItemsForPlaylist(m_item); + } + +private: + void GetItemsForPlaylist(const std::shared_ptr<CFileItem>& item); + + const std::shared_ptr<CFileItem> m_item; + const bool m_resume{false}; + CFileItemList& m_queuedItems; +}; + +void CAsyncGetItemsForPlaylist::GetItemsForPlaylist(const std::shared_ptr<CFileItem>& item) +{ + if (item->IsParentFolder() || !item->CanQueue() || item->IsRAR() || item->IsZIP()) + return; + + if (item->m_bIsFolder) + { + // check if it's a folder with dvd or bluray files, then just add the relevant file + const std::string mediapath = item->GetOpticalMediaPath(); + if (!mediapath.empty()) + { + m_queuedItems.Add(std::make_shared<CFileItem>(mediapath, false)); + return; + } + + // Check if we add a locked share + if (!item->IsPVR() && item->m_bIsShareOrDrive) + { + if (!g_passwordManager.IsItemUnlocked(item.get(), "video")) + return; + } + + CFileItemList items; + XFILE::CDirectory::GetDirectory(item->GetPath(), items, "", XFILE::DIR_FLAG_DEFAULTS); + + int viewStateWindowId = WINDOW_VIDEO_NAV; + if (URIUtils::IsPVRRadioRecordingFileOrFolder(item->GetPath())) + viewStateWindowId = WINDOW_RADIO_RECORDINGS; + else if (URIUtils::IsPVRTVRecordingFileOrFolder(item->GetPath())) + viewStateWindowId = WINDOW_TV_RECORDINGS; + + const std::unique_ptr<CGUIViewState> state( + CGUIViewState::GetViewState(viewStateWindowId, items)); + if (state) + { + LABEL_MASKS labelMasks; + state->GetSortMethodLabelMasks(labelMasks); + + const CLabelFormatter fileFormatter(labelMasks.m_strLabelFile, labelMasks.m_strLabel2File); + const CLabelFormatter folderFormatter(labelMasks.m_strLabelFolder, + labelMasks.m_strLabel2Folder); + for (const auto& i : items) + { + if (i->IsLabelPreformatted()) + continue; + + if (i->m_bIsFolder) + folderFormatter.FormatLabels(i.get()); + else + fileFormatter.FormatLabels(i.get()); + } + + if (items.GetSortMethod() == SortByLabel) + items.ClearSortState(); + + items.Sort(state->GetSortMethod()); + } + + if (m_resume) + { + // put last played item at the begin of the playlist; add start offsets for videos + std::shared_ptr<CFileItem> lastPlayedItem; + CDateTime lastPlayed; + for (const auto& i : items) + { + if (!i->HasVideoInfoTag()) + continue; + + const auto videoTag = i->GetVideoInfoTag(); + + const CBookmark& bookmark = videoTag->GetResumePoint(); + if (bookmark.IsSet()) + i->SetStartOffset(CUtil::ConvertSecsToMilliSecs(bookmark.timeInSeconds)); + + const CDateTime& currLastPlayed = videoTag->m_lastPlayed; + if (currLastPlayed.IsValid() && (!lastPlayed.IsValid() || (lastPlayed < currLastPlayed))) + { + lastPlayedItem = i; + lastPlayed = currLastPlayed; + } + } + + if (lastPlayedItem) + { + items.Remove(lastPlayedItem.get()); + items.AddFront(lastPlayedItem, 0); + } + } + + int watchedMode; + if (m_resume) + watchedMode = WatchedModeUnwatched; + else + watchedMode = CMediaSettings::GetInstance().GetWatchedMode(items.GetContent()); + + const bool unwatchedOnly = watchedMode == WatchedModeUnwatched; + const bool watchedOnly = watchedMode == WatchedModeWatched; + for (const auto& i : items) + { + if (i->m_bIsFolder) + { + std::string path = i->GetPath(); + URIUtils::RemoveSlashAtEnd(path); + if (StringUtils::EndsWithNoCase(path, "sample")) // skip sample folders + continue; + } + else if (i->HasVideoInfoTag() && + ((unwatchedOnly && i->GetVideoInfoTag()->GetPlayCount() > 0) || + (watchedOnly && i->GetVideoInfoTag()->GetPlayCount() <= 0))) + continue; + + GetItemsForPlaylist(i); + } + } + else if (item->IsPlayList()) + { + const std::unique_ptr<PLAYLIST::CPlayList> playList(PLAYLIST::CPlayListFactory::Create(*item)); + if (!playList) + { + CLog::Log(LOGERROR, "{} failed to create playlist {}", __FUNCTION__, item->GetPath()); + return; + } + + if (!playList->Load(item->GetPath())) + { + CLog::Log(LOGERROR, "{} failed to load playlist {}", __FUNCTION__, item->GetPath()); + return; + } + + for (int i = 0; i < playList->size(); ++i) + { + GetItemsForPlaylist((*playList)[i]); + } + } + else if (item->IsInternetStream()) + { + // just queue the internet stream, it will be expanded on play + m_queuedItems.Add(item); + } + else if (item->IsPlugin() && item->GetProperty("isplayable").asBoolean()) + { + // a playable python files + m_queuedItems.Add(item); + } + else if (item->IsVideoDb()) + { + // this case is needed unless we allow IsVideo() to return true for videodb items, + // but then we have issues with playlists of videodb items + const auto itemCopy = std::make_shared<CFileItem>(*item->GetVideoInfoTag()); + itemCopy->SetStartOffset(item->GetStartOffset()); + m_queuedItems.Add(itemCopy); + } + else if (!item->IsNFO() && item->IsVideo()) + { + m_queuedItems.Add(item); + } +} + +} // unnamed namespace + +namespace VIDEO_UTILS +{ +void PlayItem(const std::shared_ptr<CFileItem>& itemIn) +{ + auto item = itemIn; + + // Allow queuing of unqueueable items + // when we try to queue them directly + if (!itemIn->CanQueue()) + { + // make a copy to not alter the original item + item = std::make_shared<CFileItem>(*itemIn); + item->SetCanQueue(true); + } + + if (item->m_bIsFolder && !item->IsPlugin()) + { + // recursively add items to list + CFileItemList queuedItems; + GetItemsForPlayList(item, queuedItems); + + auto& player = CServiceBroker::GetPlaylistPlayer(); + player.ClearPlaylist(PLAYLIST::TYPE_VIDEO); + player.Reset(); + player.Add(PLAYLIST::TYPE_VIDEO, queuedItems); + player.SetCurrentPlaylist(PLAYLIST::TYPE_VIDEO); + player.Play(); + } + else if (item->HasVideoInfoTag()) + { + // single item, play it + CServiceBroker::GetPlaylistPlayer().Play(item, ""); + } +} + +void QueueItem(const std::shared_ptr<CFileItem>& itemIn, QueuePosition pos) +{ + auto item = itemIn; + + // Allow queuing of unqueueable items + // when we try to queue them directly + if (!itemIn->CanQueue()) + { + // make a copy to not alter the original item + item = std::make_shared<CFileItem>(*itemIn); + item->SetCanQueue(true); + } + + auto& player = CServiceBroker::GetPlaylistPlayer(); + const auto& components = CServiceBroker::GetAppComponents(); + + // Determine the proper list to queue this element + PLAYLIST::Id playlistId = player.GetCurrentPlaylist(); + if (playlistId == PLAYLIST::TYPE_NONE) + playlistId = components.GetComponent<CApplicationPlayer>()->GetPreferredPlaylist(); + + if (playlistId == PLAYLIST::TYPE_NONE) + playlistId = PLAYLIST::TYPE_VIDEO; + + CFileItemList queuedItems; + GetItemsForPlayList(item, queuedItems); + + // if party mode, add items but DONT start playing + if (g_partyModeManager.IsEnabled(PARTYMODECONTEXT_VIDEO)) + { + g_partyModeManager.AddUserSongs(queuedItems, false); + return; + } + + if (pos == QueuePosition::POSITION_BEGIN && + components.GetComponent<CApplicationPlayer>()->IsPlaying()) + player.Insert(playlistId, queuedItems, player.GetCurrentSong() + 1); + else + player.Add(playlistId, queuedItems); + + player.SetCurrentPlaylist(playlistId); + + // Note: video does not auto play on queue like music +} + +bool GetItemsForPlayList(const std::shared_ptr<CFileItem>& item, CFileItemList& queuedItems) +{ + CAsyncGetItemsForPlaylist getItems(item, queuedItems); + return CGUIDialogBusy::Wait(&getItems, + 500, // 500ms before busy dialog appears + true); // can be cancelled +} + +bool IsItemPlayable(const CFileItem& item) +{ + if (item.IsParentFolder()) + return false; + + if (item.IsDeleted()) + return false; + + // Include all PVR recordings and recordings folders + if (URIUtils::IsPVRRecordingFileOrFolder(item.GetPath())) + return true; + + // Include Live TV + if (!item.m_bIsFolder && (item.IsLiveTV() || item.IsEPG())) + return true; + + // Exclude all music library items + if (item.IsMusicDb() || StringUtils::StartsWithNoCase(item.GetPath(), "library://music/")) + return false; + + // Exclude other components + if (item.IsPlugin() || item.IsScript() || item.IsAddonsPath()) + return false; + + // Exclude unwanted windows + if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_PLAYLIST) + return false; + + // Exclude special items + if (StringUtils::StartsWithNoCase(item.GetPath(), "newsmartplaylist://") || + StringUtils::StartsWithNoCase(item.GetPath(), "newplaylist://") || + StringUtils::StartsWithNoCase(item.GetPath(), "newtag://")) + return false; + + // Include playlists located at one of the possible video/mixed playlist locations + if (item.IsPlayList()) + { + if (StringUtils::StartsWithNoCase(item.GetPath(), "special://videoplaylists/") || + StringUtils::StartsWithNoCase(item.GetPath(), "special://profile/playlists/video/") || + StringUtils::StartsWithNoCase(item.GetPath(), "special://profile/playlists/mixed/")) + return true; + + // Has user changed default playlists location and the list is located there? + const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + std::string path = settings->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH); + StringUtils::TrimRight(path, "/"); + if (StringUtils::StartsWith(item.GetPath(), StringUtils::Format("{}/video/", path)) || + StringUtils::StartsWith(item.GetPath(), StringUtils::Format("{}/mixed/", path))) + return true; + + // Unknown location. Type cannot be determined. + return false; + } + + if (item.m_bIsFolder && + (item.IsVideoDb() || StringUtils::StartsWithNoCase(item.GetPath(), "library://video/"))) + { + // Exclude top level nodes - eg can't play 'genres' just a specific genre etc + const XFILE::VIDEODATABASEDIRECTORY::NODE_TYPE node = + XFILE::CVideoDatabaseDirectory::GetDirectoryParentType(item.GetPath()); + if (node == XFILE::VIDEODATABASEDIRECTORY::NODE_TYPE_OVERVIEW || + node == XFILE::VIDEODATABASEDIRECTORY::NODE_TYPE_MOVIES_OVERVIEW || + node == XFILE::VIDEODATABASEDIRECTORY::NODE_TYPE_TVSHOWS_OVERVIEW || + node == XFILE::VIDEODATABASEDIRECTORY::NODE_TYPE_MUSICVIDEOS_OVERVIEW) + return false; + + return true; + } + + if (item.HasVideoInfoTag() && item.CanQueue()) + { + return true; + } + else if ((!item.m_bIsFolder && item.IsVideo()) || item.IsDVD() || item.IsCDDA()) + { + return true; + } + else if (!item.m_bIsShareOrDrive && item.m_bIsFolder) + { + // Not a video-specific folder (like file:// or nfs://). Allow play if context is Video window. + if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_NAV) + return true; + } + + return false; +} + +} // namespace VIDEO_UTILS diff --git a/xbmc/video/VideoUtils.h b/xbmc/video/VideoUtils.h new file mode 100644 index 0000000000..0694f7d6e5 --- /dev/null +++ b/xbmc/video/VideoUtils.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 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. + */ + +#pragma once + +#include <memory> + +class CFileItem; +class CFileItemList; + +namespace VIDEO_UTILS +{ +/*! \brief Start playback of the given item. If the item is a folder, build a playlist with + all items contained in the folder and start playback of the playlist. If item is a single video + item, start playback directly, without adding it to the video playlist first. + \param item [in] the item to play + */ +void PlayItem(const std::shared_ptr<CFileItem>& item); + +enum class QueuePosition +{ + POSITION_BEGIN, // place at begin of queue, before other items + POSITION_END, // place at end of queue, after other items +}; + +/*! \brief Queue the given item in the currently active playlist. If no playlist is active, + put the item into the video playlist. + \param item [in] the item to queue + \param pos [in] whether to place the item and the begin or the end of the queue + */ +void QueueItem(const std::shared_ptr<CFileItem>& item, QueuePosition pos); + +/*! \brief For a given item, get the items to put in a playlist. If the item is a folder, all ++ subitems will be added recursively to the returned item list. If the item is a playlist, the ++ playlist will be loaded and contained items will be added to the returned item list. Shows a ++ busy dialog if action takes certain amount of time to give the user visual feedback. ++ \param item [in] the item to add to the playlist ++ \param queuedItems [out] the items that can be put in a play list ++ \return true on success, false otherwise ++ */ +bool GetItemsForPlayList(const std::shared_ptr<CFileItem>& item, CFileItemList& queuedItems); + +/*! + \brief Check whether the given item can be played by the app playlist player as one or more videos. + \param item The item to check + \return True if playable, false otherwise. + */ +bool IsItemPlayable(const CFileItem& item); + +} // namespace VIDEO_UTILS diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index 55d38e0f22..086da0336f 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -40,11 +40,11 @@ #include "playlists/PlayListFactory.h" #include "profiles/ProfileManager.h" #include "settings/AdvancedSettings.h" -#include "settings/MediaSettings.h" #include "settings/SettingUtils.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "settings/dialogs/GUIDialogContentSettings.h" +#include "settings/lib/Setting.h" #include "storage/MediaManager.h" #include "utils/FileExtensionProvider.h" #include "utils/FileUtils.h" @@ -55,6 +55,7 @@ #include "utils/log.h" #include "video/VideoInfoScanner.h" #include "video/VideoLibraryQueue.h" +#include "video/VideoUtils.h" #include "video/dialogs/GUIDialogVideoInfo.h" #include "view/GUIViewState.h" @@ -441,143 +442,24 @@ bool CGUIWindowVideoBase::ShowIMDB(CFileItemPtr item, const ScraperPtr &info2, b void CGUIWindowVideoBase::OnQueueItem(int iItem, bool first) { - const auto& components = CServiceBroker::GetAppComponents(); - const auto appPlayer = components.GetComponent<CApplicationPlayer>(); - - // Determine the proper list to queue this element - PLAYLIST::Id playlistId = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(); - if (playlistId == PLAYLIST::TYPE_NONE) - playlistId = appPlayer->GetPreferredPlaylist(); - if (playlistId == PLAYLIST::TYPE_NONE) - playlistId = PLAYLIST::TYPE_VIDEO; - // don't re-queue items from playlist window - if ( iItem < 0 || iItem >= m_vecItems->Size() || GetID() == WINDOW_VIDEO_PLAYLIST ) return ; - - // we take a copy so that we can alter the queue state - CFileItemPtr item(new CFileItem(*m_vecItems->Get(iItem))); - if (item->IsRAR() || item->IsZIP()) + if (GetID() == WINDOW_VIDEO_PLAYLIST) return; - // Allow queuing of unqueueable items - // when we try to queue them directly - if (!item->CanQueue()) - item->SetCanQueue(true); - - CFileItemList queuedItems; - AddItemToPlayList(item, queuedItems); - // if party mode, add items but DONT start playing - if (g_partyModeManager.IsEnabled(PARTYMODECONTEXT_VIDEO)) - { - g_partyModeManager.AddUserSongs(queuedItems, false); + if (iItem < 0 || iItem >= m_vecItems->Size()) return; - } - if (first && appPlayer->IsPlaying()) - { - CServiceBroker::GetPlaylistPlayer().Insert( - playlistId, queuedItems, CServiceBroker::GetPlaylistPlayer().GetCurrentSong() + 1); - } - else - CServiceBroker::GetPlaylistPlayer().Add(playlistId, queuedItems); - CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(playlistId); - // video does not auto play on queue like music - m_viewControl.SetSelectedItem(iItem + 1); -} + // add item 2 playlist + const auto item = m_vecItems->Get(iItem); -void CGUIWindowVideoBase::AddItemToPlayList(const CFileItemPtr &pItem, CFileItemList &queuedItems) -{ - if (!pItem->CanQueue() || pItem->IsRAR() || pItem->IsZIP() || pItem->IsParentFolder()) // no zip/rar enqueues thank you! + if (item->IsRAR() || item->IsZIP()) return; - if (pItem->m_bIsFolder) - { - // check if it's a folder with dvd or bluray files, then just add the relevant file - std::string mediapath(pItem->GetOpticalMediaPath()); - if (!mediapath.empty()) - { - CFileItemPtr item(new CFileItem(mediapath, false)); - queuedItems.Add(item); - return; - } - - // Check if we add a locked share - if ( pItem->m_bIsShareOrDrive ) - { - CFileItem item = *pItem; - if ( !g_passwordManager.IsItemUnlocked( &item, "video" ) ) - return; - } - - // recursive - CFileItemList items; - GetDirectory(pItem->GetPath(), items); - FormatAndSort(items); - - int watchedMode = CMediaSettings::GetInstance().GetWatchedMode(items.GetContent()); - bool unwatchedOnly = watchedMode == WatchedModeUnwatched; - bool watchedOnly = watchedMode == WatchedModeWatched; - for (int i = 0; i < items.Size(); ++i) - { - if (items[i]->m_bIsFolder) - { - std::string strPath = items[i]->GetPath(); - URIUtils::RemoveSlashAtEnd(strPath); - if (StringUtils::EndsWithNoCase(strPath, "sample")) // skip sample folders - { - continue; - } - } - else if (items[i]->HasVideoInfoTag() && - ((unwatchedOnly && items[i]->GetVideoInfoTag()->GetPlayCount() > 0) || - (watchedOnly && items[i]->GetVideoInfoTag()->GetPlayCount() <= 0))) - continue; + VIDEO_UTILS::QueueItem(item, first ? VIDEO_UTILS::QueuePosition::POSITION_BEGIN + : VIDEO_UTILS::QueuePosition::POSITION_END); - AddItemToPlayList(items[i], queuedItems); - } - } - else - { - // just an item - if (pItem->IsPlayList()) - { - std::unique_ptr<PLAYLIST::CPlayList> pPlayList(PLAYLIST::CPlayListFactory::Create(*pItem)); - if (pPlayList) - { - // load it - if (!pPlayList->Load(pItem->GetPath())) - { - HELPERS::ShowOKDialogText(CVariant{6}, CVariant{477}); - return; //hmmm unable to load playlist? - } - - PLAYLIST::CPlayList playlist = *pPlayList; - for (int i = 0; i < playlist.size(); ++i) - { - AddItemToPlayList(playlist[i], queuedItems); - } - return; - } - } - else if(pItem->IsInternetStream()) - { // just queue the internet stream, it will be expanded on play - queuedItems.Add(pItem); - } - else if (pItem->IsPlugin() && pItem->GetProperty("isplayable").asBoolean()) - { // a playable python files - queuedItems.Add(pItem); - } - else if (pItem->IsVideoDb()) - { // this case is needed unless we allow IsVideo() to return true for videodb items, - // but then we have issues with playlists of videodb items - CFileItemPtr item(new CFileItem(*pItem->GetVideoInfoTag())); - queuedItems.Add(item); - } - else if (!pItem->IsNFO() && pItem->IsVideo()) - { - queuedItems.Add(pItem); - } - } + // select next item + m_viewControl.SetSelectedItem(iItem + 1); } void CGUIWindowVideoBase::GetResumeItemOffset(const CFileItem *item, int64_t& startoffset, int& partNumber) @@ -597,10 +479,7 @@ void CGUIWindowVideoBase::GetResumeItemOffset(const CFileItem *item, int64_t& st } else { - CBookmark bookmark; - std::string strPath = item->GetPath(); - if ((item->IsVideoDb() || item->IsDVD()) && item->HasVideoInfoTag()) - strPath = item->GetVideoInfoTag()->m_strFileNameAndPath; + // Obtain the resume bookmark from video db... CVideoDatabase db; if (!db.Open()) @@ -608,12 +487,55 @@ void CGUIWindowVideoBase::GetResumeItemOffset(const CFileItem *item, int64_t& st CLog::Log(LOGERROR, "{} - Cannot open VideoDatabase", __FUNCTION__); return; } - if (db.GetResumeBookMark(strPath, bookmark)) + + std::string path = item->GetPath(); + if (item->IsVideoDb() || item->IsDVD()) + { + if (item->HasVideoInfoTag()) + { + path = item->GetVideoInfoTag()->m_strFileNameAndPath; + } + else if (item->IsVideoDb()) + { + // Obtain fileNamAndPath from video db + VIDEODATABASEDIRECTORY::CQueryParams params; + VIDEODATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo(item->GetPath(), params); + + long id = -1; + VideoDbContentType content_type; + if ((id = params.GetMovieId()) >= 0) + content_type = VideoDbContentType::MOVIES; + else if ((id = params.GetEpisodeId()) >= 0) + content_type = VideoDbContentType::EPISODES; + else if ((id = params.GetMVideoId()) >= 0) + content_type = VideoDbContentType::MUSICVIDEOS; + else + { + CLog::Log(LOGERROR, "{} - Cannot obtain video content type", __FUNCTION__); + db.Close(); + return; + } + + db.GetFilePathById(static_cast<int>(id), path, content_type); + } + else + { + // DVD + CLog::Log(LOGERROR, "{} - Cannot obtain bookmark for DVD", __FUNCTION__); + db.Close(); + return; + } + } + + CBookmark bookmark; + db.GetResumeBookMark(path, bookmark); + db.Close(); + + if (bookmark.IsSet()) { startoffset = CUtil::ConvertSecsToMilliSecs(bookmark.timeInSeconds); partNumber = bookmark.partNumber; } - db.Close(); } } } @@ -704,6 +626,11 @@ bool CGUIWindowVideoBase::OnFileAction(int iItem, int action, const std::string& return true; case SELECT_ACTION_RESUME: item->SetStartOffset(STARTOFFSET_RESUME); + if (item->m_bIsFolder) + { + PlayItem(iItem, player); + return true; + } break; case SELECT_ACTION_PLAYPART: if (!OnPlayStackPart(iItem)) @@ -713,6 +640,12 @@ bool CGUIWindowVideoBase::OnFileAction(int iItem, int action, const std::string& OnQueueItem(iItem); return true; case SELECT_ACTION_PLAY: + if (item->m_bIsFolder) + { + PlayItem(iItem, player); + return true; + } + break; default: break; } @@ -788,23 +721,125 @@ void CGUIWindowVideoBase::OnRestartItem(int iItem, const std::string &player) CGUIMediaWindow::OnClick(iItem, player); } +namespace +{ +bool HasInProgressVideo(const std::string& path, CVideoDatabase& db) +{ + //! @todo this function is really very expensive and should be optimized (at db level). + + CFileItemList items; + CUtil::GetRecursiveListing(path, items, {}, XFILE::DIR_FLAG_DEFAULTS); + + if (items.IsEmpty()) + return false; + + for (const auto& item : items) + { + const auto videoTag = item->GetVideoInfoTag(); + if (!item->HasVideoInfoTag()) + continue; + + if (videoTag->GetPlayCount() > 0) + continue; + + // get resume point + CBookmark bookmark(videoTag->GetResumePoint()); + if (!bookmark.IsSet() && db.GetResumeBookMark(videoTag->m_strFileNameAndPath, bookmark)) + videoTag->SetResumePoint(bookmark); + + if (bookmark.IsSet()) + return true; + } + + return false; +} +} // unnamed namespace + std::string CGUIWindowVideoBase::GetResumeString(const CFileItem &item) { std::string resumeString; - int64_t startOffset = 0; - int startPart = 0; - GetResumeItemOffset(&item, startOffset, startPart); - if (startOffset > 0) + if (item.m_bIsFolder) { - resumeString = - StringUtils::Format(g_localizeStrings.Get(12022), - StringUtils::SecondsToTimeString( - static_cast<long>(CUtil::ConvertMilliSecsToSecsInt(startOffset)), - TIME_FORMAT_HH_MM_SS)); - if (startPart > 0) + bool hasInProgressVideo = false; + + CFileItem folderItem(item); + if ((!folderItem.HasProperty("watchedepisodes") || // season/show + (folderItem.GetProperty("watchedepisodes").asInteger() == 0)) && + (!folderItem.HasProperty("watched") || // movie set + (folderItem.GetProperty("watched").asInteger() == 0))) { - std::string partString = StringUtils::Format(g_localizeStrings.Get(23051), startPart); - resumeString += " (" + partString + ")"; + CVideoDatabase db; + if (db.Open()) + { + if (!folderItem.HasProperty("watchedepisodes") && !folderItem.HasProperty("watched")) + { + VIDEODATABASEDIRECTORY::CQueryParams params; + VIDEODATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo(item.GetPath(), params); + + if (params.GetTvShowId() >= 0) + { + if (params.GetSeason() >= 0) + { + const int idSeason = db.GetSeasonId(static_cast<int>(params.GetTvShowId()), + static_cast<int>(params.GetSeason())); + if (idSeason >= 0) + { + CVideoInfoTag details; + db.GetSeasonInfo(idSeason, details, &folderItem); + } + } + else + { + CVideoInfoTag details; + db.GetTvShowInfo(item.GetPath(), details, static_cast<int>(params.GetTvShowId()), + &folderItem); + } + } + else if (params.GetSetId() >= 0) + { + CVideoInfoTag details; + db.GetSetInfo(static_cast<int>(params.GetSetId()), details, &folderItem); + } + } + + // no episodes/movies watched completely, but there could be some or more we have + // started watching + if ((folderItem.HasProperty("watchedepisodes") && // season/show + folderItem.GetProperty("watchedepisodes").asInteger() == 0) || + (folderItem.HasProperty("watched") && // movie set + folderItem.GetProperty("watched").asInteger() == 0)) + hasInProgressVideo = HasInProgressVideo(item.GetPath(), db); + + db.Close(); + } + } + + if (hasInProgressVideo || + (folderItem.GetProperty("watchedepisodes").asInteger() > 0 && + folderItem.GetProperty("unwatchedepisodes").asInteger() > 0) || + (folderItem.GetProperty("watched").asInteger() > 0 && + folderItem.GetProperty("unwatched").asInteger() > 0)) + { + resumeString = g_localizeStrings.Get(13362); // Continue watching + } + } + else + { + int64_t startOffset = 0; + int startPart = 0; + GetResumeItemOffset(&item, startOffset, startPart); + if (startOffset > 0) + { + resumeString = + StringUtils::Format(g_localizeStrings.Get(12022), + StringUtils::SecondsToTimeString( + static_cast<long>(CUtil::ConvertMilliSecsToSecsInt(startOffset)), + TIME_FORMAT_HH_MM_SS)); + if (startPart > 0) + { + std::string partString = StringUtils::Format(g_localizeStrings.Get(23051), startPart); + resumeString += " (" + partString + ")"; + } } } return resumeString; @@ -812,7 +847,7 @@ std::string CGUIWindowVideoBase::GetResumeString(const CFileItem &item) bool CGUIWindowVideoBase::ShowResumeMenu(CFileItem &item) { - if (!item.m_bIsFolder && !item.IsPVR()) + if (!item.IsLiveTV()) { std::string resumeString = GetResumeString(item); if (!resumeString.empty()) @@ -835,13 +870,6 @@ bool CGUIWindowVideoBase::OnResumeItem(int iItem, const std::string &player) if (iItem < 0 || iItem >= m_vecItems->Size()) return true; CFileItemPtr item = m_vecItems->Get(iItem); - if (item->m_bIsFolder) - { - // resuming directories isn't supported yet. play. - PlayItem(iItem, player); - return true; - } - std::string resumeString = GetResumeString(*item); if (!resumeString.empty()) @@ -855,6 +883,13 @@ bool CGUIWindowVideoBase::OnResumeItem(int iItem, const std::string &player) return OnFileAction(iItem, value, player); } + if (item->m_bIsFolder) + { + // resuming directories isn't fully supported yet. play all of its content. + PlayItem(iItem, player); + return true; + } + return OnFileAction(iItem, SELECT_ACTION_PLAY, player); } @@ -882,20 +917,6 @@ void CGUIWindowVideoBase::GetContextButtons(int itemNumber, CContextButtons &but if (m_database.GetStackTimes(path,times) || CFileItem(CStackDirectory::GetFirstStackedFile(path),false).IsDiscImage()) buttons.Add(CONTEXT_BUTTON_PLAY_PART, 20324); } - - // allow a folder to be ad-hoc queued and played by the default player - if (item->m_bIsFolder || (item->IsPlayList() && - !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders)) - { - buttons.Add(CONTEXT_BUTTON_PLAY_ITEM, 208); - } - - if (!m_vecItems->GetPath().empty() && !StringUtils::StartsWithNoCase(item->GetPath(), "newsmartplaylist://") && !StringUtils::StartsWithNoCase(item->GetPath(), "newtag://") - && !m_vecItems->IsSourcesPath()) - { - buttons.Add(CONTEXT_BUTTON_QUEUE_ITEM, 13347); // Add to Playlist - buttons.Add(CONTEXT_BUTTON_PLAY_NEXT, 10008); // Play next - } } if (!item->m_bIsFolder && !(item->IsPlayList() && !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders)) @@ -1053,18 +1074,6 @@ bool CGUIWindowVideoBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button) else return false; } - case CONTEXT_BUTTON_QUEUE_ITEM: - OnQueueItem(itemNumber); - return true; - - case CONTEXT_BUTTON_PLAY_NEXT: - OnQueueItem(itemNumber, true); - return true; - - case CONTEXT_BUTTON_PLAY_ITEM: - PlayItem(itemNumber); - return true; - case CONTEXT_BUTTON_PLAY_WITH: { const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); @@ -1301,7 +1310,7 @@ void CGUIWindowVideoBase::PlayItem(int iItem, const std::string &player) // recursively add items to list CFileItemList queuedItems; - AddItemToPlayList(item, queuedItems); + VIDEO_UTILS::GetItemsForPlayList(item, queuedItems); CServiceBroker::GetPlaylistPlayer().ClearPlaylist(PLAYLIST::TYPE_VIDEO); CServiceBroker::GetPlaylistPlayer().Reset(); diff --git a/xbmc/video/windows/GUIWindowVideoBase.h b/xbmc/video/windows/GUIWindowVideoBase.h index 1108568aaa..99ac54187c 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.h +++ b/xbmc/video/windows/GUIWindowVideoBase.h @@ -115,8 +115,6 @@ protected: bool ShowIMDB(CFileItemPtr item, const ADDON::ScraperPtr& content, bool fromDB); - void AddItemToPlayList(const CFileItemPtr &pItem, CFileItemList &queuedItems); - void OnSearch(); void OnSearchItemFound(const CFileItem* pSelItem); int GetScraperForItem(CFileItem *item, ADDON::ScraperPtr &info, VIDEO::SScanSettings& settings); |