diff options
-rw-r--r-- | addons/resource.language.en_gb/resources/strings.po | 2 | ||||
-rw-r--r-- | xbmc/interfaces/builtins/PlayerBuiltins.cpp | 40 | ||||
-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 | 113 | ||||
-rw-r--r-- | xbmc/video/VideoUtils.cpp | 402 | ||||
-rw-r--r-- | xbmc/video/VideoUtils.h | 55 | ||||
-rw-r--r-- | xbmc/video/windows/GUIWindowVideoBase.cpp | 168 | ||||
-rw-r--r-- | xbmc/video/windows/GUIWindowVideoBase.h | 2 |
10 files changed, 523 insertions, 273 deletions
diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index bd31344ec8..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 "" diff --git a/xbmc/interfaces/builtins/PlayerBuiltins.cpp b/xbmc/interfaces/builtins/PlayerBuiltins.cpp index a7e1380582..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 @@ -497,16 +506,14 @@ int PlayOrQueueMedia(const std::vector<std::string>& params, bool forcePlay) 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; @@ -514,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) @@ -567,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, ""); + } } } 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 eaec3064ea..ec400a9541 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" @@ -180,31 +183,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 +221,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 +234,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)); } } @@ -317,25 +266,9 @@ std::string CVideoPlay::GetLabel(const CFileItem& itemIn) const 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 +287,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 +298,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 +307,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 +318,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/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 ac9ba1bffd..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; - - 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? - } + VIDEO_UTILS::QueueItem(item, first ? VIDEO_UTILS::QueuePosition::POSITION_BEGIN + : VIDEO_UTILS::QueuePosition::POSITION_END); - 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) @@ -1035,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)) @@ -1206,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(); @@ -1454,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); |