aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKai Sommerfeld <kai.sommerfeld@gmx.com>2022-11-07 09:51:03 +0100
committerGitHub <noreply@github.com>2022-11-07 09:51:03 +0100
commit880e7acac4fd626ac0fdd3524c0cce673d96c5bb (patch)
tree2be6e688f82688487ce83ba0330aa2604cfed9ea
parentee399f6e182c2b1ae10ccec40ea171588349290a (diff)
parent66a23261d70d4dbbfbd60d2e19e205e965f23d45 (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.po10
-rw-r--r--xbmc/ContextMenuManager.cpp1
-rw-r--r--xbmc/FileItem.cpp74
-rw-r--r--xbmc/FileItem.h11
-rw-r--r--xbmc/interfaces/builtins/PlayerBuiltins.cpp55
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRRecordings.cpp24
-rw-r--r--xbmc/utils/URIUtils.cpp10
-rw-r--r--xbmc/utils/URIUtils.h2
-rw-r--r--xbmc/video/CMakeLists.txt2
-rw-r--r--xbmc/video/ContextMenus.cpp150
-rw-r--r--xbmc/video/ContextMenus.h7
-rw-r--r--xbmc/video/VideoDatabase.cpp38
-rw-r--r--xbmc/video/VideoDatabase.h4
-rw-r--r--xbmc/video/VideoUtils.cpp402
-rw-r--r--xbmc/video/VideoUtils.h55
-rw-r--r--xbmc/video/windows/GUIWindowVideoBase.cpp373
-rw-r--r--xbmc/video/windows/GUIWindowVideoBase.h2
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);