Merge pull request #26126 from stephan49/fix-pipewire-unlock-error
[xbmc.git] / xbmc / video / guilib / VideoGUIUtils.cpp
blob18619fb271f4fd55798fcf28d4d99eeacd953b64
1 /*
2 * Copyright (C) 2022 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "VideoGUIUtils.h"
11 #include "FileItem.h"
12 #include "FileItemList.h"
13 #include "GUIPassword.h"
14 #include "PartyModeManager.h"
15 #include "PlayListPlayer.h"
16 #include "ServiceBroker.h"
17 #include "Util.h"
18 #include "application/ApplicationComponents.h"
19 #include "application/ApplicationPlayer.h"
20 #include "dialogs/GUIDialogBusy.h"
21 #include "filesystem/Directory.h"
22 #include "filesystem/VideoDatabaseDirectory.h"
23 #include "filesystem/VideoDatabaseDirectory/DirectoryNode.h"
24 #include "guilib/GUIComponent.h"
25 #include "guilib/GUIWindowManager.h"
26 #include "guilib/LocalizeStrings.h"
27 #include "music/MusicFileItemClassify.h"
28 #include "network/NetworkFileItemClassify.h"
29 #include "playlists/PlayList.h"
30 #include "playlists/PlayListFactory.h"
31 #include "playlists/PlayListFileItemClassify.h"
32 #include "profiles/ProfileManager.h"
33 #include "settings/MediaSettings.h"
34 #include "settings/SettingUtils.h"
35 #include "settings/Settings.h"
36 #include "settings/SettingsComponent.h"
37 #include "threads/IRunnable.h"
38 #include "utils/FileUtils.h"
39 #include "utils/StringUtils.h"
40 #include "utils/URIUtils.h"
41 #include "utils/log.h"
42 #include "video/VideoDatabase.h"
43 #include "video/VideoFileItemClassify.h"
44 #include "video/VideoInfoTag.h"
45 #include "video/VideoUtils.h"
46 #include "view/GUIViewState.h"
48 namespace KODI
51 namespace
53 class CAsyncGetItemsForPlaylist : public IRunnable
55 public:
56 CAsyncGetItemsForPlaylist(const std::shared_ptr<CFileItem>& item, CFileItemList& queuedItems)
57 : m_item(item),
58 m_resume((item->GetStartOffset() == STARTOFFSET_RESUME) &&
59 VIDEO::UTILS::GetItemResumeInformation(*item).isResumable),
60 m_queuedItems(queuedItems)
64 ~CAsyncGetItemsForPlaylist() override = default;
66 void Run() override
68 // fast lookup is needed here
69 m_queuedItems.SetFastLookup(true);
71 GetItemsForPlaylist(m_item);
74 private:
75 void GetItemsForPlaylist(const std::shared_ptr<CFileItem>& item);
77 const std::shared_ptr<CFileItem> m_item;
78 const bool m_resume{false};
79 CFileItemList& m_queuedItems;
82 SortDescription GetSortDescription(const CGUIViewState& state, const CFileItemList& items)
84 SortDescription sortDescDate;
86 auto sortDescriptions = state.GetSortDescriptions();
87 for (auto& sortDescription : sortDescriptions)
89 if (sortDescription.sortBy == SortByEpisodeNumber)
91 // check whether at least one item has actually an episode number set
92 for (const auto& item : items)
94 if (item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_iEpisode > 0)
96 // first choice for folders containig episodes
97 sortDescription.sortOrder = SortOrderAscending;
98 return sortDescription;
101 continue;
103 else if (sortDescription.sortBy == SortByYear)
105 // check whether at least one item has actually a year set
106 for (const auto& item : items)
108 if (item->HasVideoInfoTag() && item->GetVideoInfoTag()->HasYear())
110 // first choice for folders containing movies
111 sortDescription.sortOrder = SortOrderAscending;
112 return sortDescription;
116 else if (sortDescription.sortBy == SortByDate)
118 // check whether at least one item has actually a valid date set
119 for (const auto& item : items)
121 if (item->m_dateTime.IsValid())
123 // fallback, if neither ByEpisode nor ByYear is available
124 sortDescDate = sortDescription;
125 sortDescDate.sortOrder = SortOrderAscending;
126 break; // leave items loop. we can still find ByEpisode or ByYear. so, no return here.
132 if (sortDescDate.sortBy != SortByNone)
133 return sortDescDate;
134 else
135 return state.GetSortMethod(); // last resort
138 void CAsyncGetItemsForPlaylist::GetItemsForPlaylist(const std::shared_ptr<CFileItem>& item)
140 if (item->IsParentFolder() || !item->CanQueue() || item->IsRAR() || item->IsZIP())
141 return;
143 if (item->m_bIsFolder)
145 if (!item->IsPlugin())
147 // check if it's a folder with dvd or bluray files, then just add the relevant file
148 const std::string mediapath = VIDEO::UTILS::GetOpticalMediaPath(*item);
149 if (!mediapath.empty())
151 m_queuedItems.Add(std::make_shared<CFileItem>(mediapath, false));
152 return;
156 // Check if we add a locked share
157 if (!item->IsPVR() && item->m_bIsShareOrDrive)
159 if (!g_passwordManager.IsItemUnlocked(item.get(), "video"))
160 return;
163 CFileItemList items;
164 XFILE::CDirectory::GetDirectory(item->GetPath(), items, "", XFILE::DIR_FLAG_DEFAULTS);
166 int viewStateWindowId = WINDOW_VIDEO_NAV;
167 if (URIUtils::IsPVRRadioRecordingFileOrFolder(item->GetPath()))
168 viewStateWindowId = WINDOW_RADIO_RECORDINGS;
169 else if (URIUtils::IsPVRTVRecordingFileOrFolder(item->GetPath()))
170 viewStateWindowId = WINDOW_TV_RECORDINGS;
172 const std::unique_ptr<CGUIViewState> state(
173 CGUIViewState::GetViewState(viewStateWindowId, items));
174 if (state)
176 LABEL_MASKS labelMasks;
177 state->GetSortMethodLabelMasks(labelMasks);
179 const CLabelFormatter fileFormatter(labelMasks.m_strLabelFile, labelMasks.m_strLabel2File);
180 const CLabelFormatter folderFormatter(labelMasks.m_strLabelFolder,
181 labelMasks.m_strLabel2Folder);
182 for (const auto& i : items)
184 if (i->IsLabelPreformatted())
185 continue;
187 if (i->m_bIsFolder)
188 folderFormatter.FormatLabels(i.get());
189 else
190 fileFormatter.FormatLabels(i.get());
193 SortDescription sortDesc;
194 if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == viewStateWindowId)
196 sortDesc = state->GetSortMethod();
198 // It makes no sense to play from younger to older.
199 if (sortDesc.sortBy == SortByDate || sortDesc.sortBy == SortByYear ||
200 sortDesc.sortBy == SortByEpisodeNumber)
201 sortDesc.sortOrder = SortOrderAscending;
203 else
204 sortDesc = GetSortDescription(*state, items);
206 if (sortDesc.sortBy == SortByLabel)
207 items.ClearSortState();
209 items.Sort(sortDesc);
212 if (items.GetContent().empty() && !VIDEO::IsVideoDb(items) && !items.IsVirtualDirectoryRoot() &&
213 !items.IsSourcesPath() && !items.IsLibraryFolder())
215 CVideoDatabase db;
216 if (db.Open())
218 std::string content = db.GetContentForPath(items.GetPath());
219 if (content.empty() && !items.IsPlugin())
220 content = "files";
222 items.SetContent(content);
224 // Get play counts and resume bookmarks for the items.
225 db.GetPlayCounts(items.GetPath(), items);
229 if (m_resume)
231 // put last played item at the begin of the playlist; add start offsets for videos
232 std::shared_ptr<CFileItem> lastPlayedItem;
233 CDateTime lastPlayed;
234 for (const auto& i : items)
236 if (!i->HasVideoInfoTag())
237 continue;
239 const auto videoTag = i->GetVideoInfoTag();
241 const CBookmark& bookmark = videoTag->GetResumePoint();
242 if (bookmark.IsSet())
244 i->SetStartOffset(CUtil::ConvertSecsToMilliSecs(bookmark.timeInSeconds));
246 const CDateTime& currLastPlayed = videoTag->m_lastPlayed;
247 if (currLastPlayed.IsValid() && (!lastPlayed.IsValid() || (lastPlayed < currLastPlayed)))
249 lastPlayedItem = i;
250 lastPlayed = currLastPlayed;
255 if (lastPlayedItem)
257 items.Remove(lastPlayedItem.get());
258 items.AddFront(lastPlayedItem, 0);
262 int watchedMode;
263 if (m_resume)
264 watchedMode = WatchedModeUnwatched;
265 else
266 watchedMode = CMediaSettings::GetInstance().GetWatchedMode(items.GetContent());
268 const bool unwatchedOnly = watchedMode == WatchedModeUnwatched;
269 const bool watchedOnly = watchedMode == WatchedModeWatched;
270 bool fetchedPlayCounts = false;
271 for (const auto& i : items)
273 if (i->m_bIsFolder)
275 std::string path = i->GetPath();
276 URIUtils::RemoveSlashAtEnd(path);
277 if (StringUtils::EndsWithNoCase(path, "sample")) // skip sample folders
278 continue;
280 else
282 if (!fetchedPlayCounts &&
283 (!i->HasVideoInfoTag() || !i->GetVideoInfoTag()->IsPlayCountSet()))
285 CVideoDatabase db;
286 if (db.Open())
288 fetchedPlayCounts = true;
289 db.GetPlayCounts(items.GetPath(), items);
292 if (i->HasVideoInfoTag() && i->GetVideoInfoTag()->IsPlayCountSet())
294 const int playCount = i->GetVideoInfoTag()->GetPlayCount();
295 if ((unwatchedOnly && playCount > 0) || (watchedOnly && playCount <= 0))
296 continue;
299 GetItemsForPlaylist(i);
302 else if (PLAYLIST::IsPlayList(*item))
304 // just queue the playlist, it will be expanded on play
305 m_queuedItems.Add(item);
307 else if (NETWORK::IsInternetStream(*item))
309 // just queue the internet stream, it will be expanded on play
310 m_queuedItems.Add(item);
312 else if (item->IsPlugin() && item->GetProperty("isplayable").asBoolean())
314 // a playable python files
315 m_queuedItems.Add(item);
317 else if (VIDEO::IsVideoDb(*item))
319 // this case is needed unless we allow IsVideo() to return true for videodb items,
320 // but then we have issues with playlists of videodb items
321 const auto itemCopy = std::make_shared<CFileItem>(*item->GetVideoInfoTag());
322 itemCopy->SetStartOffset(item->GetStartOffset());
323 m_queuedItems.Add(itemCopy);
325 else if (!item->IsNFO() && VIDEO::IsVideo(*item))
327 m_queuedItems.Add(item);
331 std::string GetVideoDbItemPath(const CFileItem& item)
333 std::string path = item.GetPath();
334 if (!URIUtils::IsVideoDb(path))
335 path = item.GetProperty("original_listitem_url").asString();
337 if (URIUtils::IsVideoDb(path))
338 return path;
340 return {};
343 void AddItemToPlayListAndPlay(const std::shared_ptr<CFileItem>& itemToQueue,
344 const std::shared_ptr<CFileItem>& itemToPlay,
345 const std::string& player)
347 // recursively add items to list
348 CFileItemList queuedItems;
349 VIDEO::UTILS::GetItemsForPlayList(itemToQueue, queuedItems);
351 auto& playlistPlayer = CServiceBroker::GetPlaylistPlayer();
352 playlistPlayer.ClearPlaylist(PLAYLIST::Id::TYPE_VIDEO);
353 playlistPlayer.Reset();
354 playlistPlayer.Add(PLAYLIST::Id::TYPE_VIDEO, queuedItems);
356 // figure out where to start playback
357 PLAYLIST::CPlayList& playList = playlistPlayer.GetPlaylist(PLAYLIST::Id::TYPE_VIDEO);
358 int pos = 0;
359 if (itemToPlay)
361 for (const std::shared_ptr<CFileItem>& queuedItem : queuedItems)
363 if (queuedItem->IsSamePath(itemToPlay.get()))
365 queuedItem->m_lStartPartNumber = itemToPlay->m_lStartPartNumber;
366 break;
368 pos++;
372 if (playlistPlayer.IsShuffled(PLAYLIST::Id::TYPE_VIDEO))
374 playList.Swap(0, playList.FindOrder(pos));
375 pos = 0;
378 playlistPlayer.SetCurrentPlaylist(PLAYLIST::Id::TYPE_VIDEO);
379 playlistPlayer.Play(pos, player);
382 } // unnamed namespace
384 } // namespace KODI
386 namespace KODI::VIDEO::UTILS
388 void PlayItem(
389 const std::shared_ptr<CFileItem>& itemIn,
390 const std::string& player,
391 ContentUtils::PlayMode mode /* = ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_VIDEO */)
393 auto item = itemIn;
395 // Allow queuing of unqueueable items
396 // when we try to queue them directly
397 if (!itemIn->CanQueue())
399 // make a copy to not alter the original item
400 item = std::make_shared<CFileItem>(*itemIn);
401 item->SetCanQueue(true);
404 if (item->m_bIsFolder && !item->IsPlugin())
406 AddItemToPlayListAndPlay(item, nullptr, player);
408 else if (item->HasVideoInfoTag())
410 if (mode == ContentUtils::PlayMode::PLAY_FROM_HERE ||
411 (mode == ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM && IsAutoPlayNextItem(*item)))
413 // Add item and all its siblings to the playlist and play. Prefer videodb path if available,
414 // because it provides more information than just a plain file system path for example.
415 std::string parentPath = item->GetProperty("ParentPath").asString();
416 if (parentPath.empty())
418 std::string path = GetVideoDbItemPath(*item);
419 if (path.empty())
420 path = item->GetPath();
422 URIUtils::GetParentPath(path, parentPath);
424 if (parentPath.empty())
426 CLog::LogF(LOGERROR, "Unable to obtain parent path for '{}'", item->GetPath());
427 return;
431 const auto parentItem = std::make_shared<CFileItem>(parentPath, true);
432 parentItem->SetProperty("IsVideoFolder", true);
433 parentItem->LoadDetails();
434 if (item->GetStartOffset() == STARTOFFSET_RESUME)
435 parentItem->SetStartOffset(STARTOFFSET_RESUME);
437 AddItemToPlayListAndPlay(parentItem, item, player);
439 else // mode == PlayMode::PLAY_ONLY_THIS
441 // single item, play it
442 auto& playlistPlayer = CServiceBroker::GetPlaylistPlayer();
443 playlistPlayer.Reset();
444 playlistPlayer.SetCurrentPlaylist(PLAYLIST::Id::TYPE_NONE);
445 playlistPlayer.Play(item, player);
450 void QueueItem(const std::shared_ptr<CFileItem>& itemIn, QueuePosition pos)
452 auto item = itemIn;
454 // Allow queuing of unqueueable items
455 // when we try to queue them directly
456 if (!itemIn->CanQueue())
458 // make a copy to not alter the original item
459 item = std::make_shared<CFileItem>(*itemIn);
460 item->SetCanQueue(true);
463 auto& player = CServiceBroker::GetPlaylistPlayer();
464 const auto& components = CServiceBroker::GetAppComponents();
466 // Determine the proper list to queue this element
467 PLAYLIST::Id playlistId = player.GetCurrentPlaylist();
468 if (playlistId == PLAYLIST::Id::TYPE_NONE)
469 playlistId = components.GetComponent<CApplicationPlayer>()->GetPreferredPlaylist();
471 if (playlistId == PLAYLIST::Id::TYPE_NONE)
472 playlistId = PLAYLIST::Id::TYPE_VIDEO;
474 CFileItemList queuedItems;
475 GetItemsForPlayList(item, queuedItems);
477 // if party mode, add items but DONT start playing
478 if (g_partyModeManager.IsEnabled(PartyModeContext::VIDEO))
480 g_partyModeManager.AddUserSongs(queuedItems, false);
481 return;
484 if (pos == QueuePosition::POSITION_BEGIN &&
485 components.GetComponent<CApplicationPlayer>()->IsPlaying())
486 player.Insert(playlistId, queuedItems, player.GetCurrentItemIdx() + 1);
487 else
488 player.Add(playlistId, queuedItems);
490 player.SetCurrentPlaylist(playlistId);
492 // Note: video does not auto play on queue like music
495 bool GetItemsForPlayList(const std::shared_ptr<CFileItem>& item, CFileItemList& queuedItems)
497 CAsyncGetItemsForPlaylist getItems(item, queuedItems);
498 return CGUIDialogBusy::Wait(&getItems,
499 500, // 500ms before busy dialog appears
500 true); // can be cancelled
503 namespace
505 bool IsNonExistingUserPartyModePlaylist(const CFileItem& item)
507 if (!PLAYLIST::IsSmartPlayList(item))
508 return false;
510 const std::string& path{item.GetPath()};
511 const auto profileManager{CServiceBroker::GetSettingsComponent()->GetProfileManager()};
512 return ((profileManager->GetUserDataItem("PartyMode-Video.xsp") == path) &&
513 !CFileUtils::Exists(path));
516 bool IsEmptyVideoItem(const CFileItem& item)
518 return item.HasVideoInfoTag() && item.GetVideoInfoTag()->IsEmpty();
520 } // unnamed namespace
522 bool IsItemPlayable(const CFileItem& item)
524 if (item.IsParentFolder())
525 return false;
527 if (item.IsDeleted())
528 return false;
530 // Include all PVR recordings and recordings folders
531 if (URIUtils::IsPVRRecordingFileOrFolder(item.GetPath()))
532 return true;
534 // Include Live TV
535 if (!item.m_bIsFolder && (item.IsLiveTV() || item.IsEPG()))
536 return true;
538 // Exclude all music library items
539 if (MUSIC::IsMusicDb(item) || StringUtils::StartsWithNoCase(item.GetPath(), "library://music/"))
540 return false;
542 // Exclude add-ons
543 if (item.IsAddonsPath())
544 return false;
546 // Exclude special items
547 if (StringUtils::StartsWithNoCase(item.GetPath(), "newsmartplaylist://") ||
548 StringUtils::StartsWithNoCase(item.GetPath(), "newplaylist://") ||
549 StringUtils::StartsWithNoCase(item.GetPath(), "newtag://"))
550 return false;
552 // Include playlists located at one of the possible video/mixed playlist locations
553 if (PLAYLIST::IsPlayList(item))
555 if (StringUtils::StartsWithNoCase(item.GetMimeType(), "video/"))
556 return true;
558 if (StringUtils::StartsWithNoCase(item.GetPath(), "special://videoplaylists/") ||
559 StringUtils::StartsWithNoCase(item.GetPath(), "special://profile/playlists/video/") ||
560 StringUtils::StartsWithNoCase(item.GetPath(), "special://profile/playlists/mixed/"))
561 return true;
563 // Has user changed default playlists location and the list is located there?
564 const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
565 std::string path = settings->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH);
566 StringUtils::TrimRight(path, "/");
567 if (StringUtils::StartsWith(item.GetPath(), StringUtils::Format("{}/video/", path)) ||
568 StringUtils::StartsWith(item.GetPath(), StringUtils::Format("{}/mixed/", path)))
569 return true;
571 if (!item.m_bIsFolder && !item.HasVideoInfoTag())
573 // Unknown location. Type cannot be determined for non-folder items.
574 return false;
578 if (IsNonExistingUserPartyModePlaylist(item))
579 return false;
581 if (item.m_bIsFolder &&
582 (IsVideoDb(item) || StringUtils::StartsWithNoCase(item.GetPath(), "library://video/")))
584 // Exclude top level nodes - eg can't play 'genres' just a specific genre etc
585 const auto node = XFILE::CVideoDatabaseDirectory::GetDirectoryParentType(item.GetPath());
586 if (node == XFILE::VIDEODATABASEDIRECTORY::NodeType::OVERVIEW ||
587 node == XFILE::VIDEODATABASEDIRECTORY::NodeType::MOVIES_OVERVIEW ||
588 node == XFILE::VIDEODATABASEDIRECTORY::NodeType::TVSHOWS_OVERVIEW ||
589 node == XFILE::VIDEODATABASEDIRECTORY::NodeType::MUSICVIDEOS_OVERVIEW)
590 return false;
592 return true;
595 if (item.IsPlugin() && IsVideo(item) && !IsEmptyVideoItem(item) &&
596 item.GetProperty("isplayable").asBoolean(false))
598 return true;
600 else if (item.HasVideoInfoTag() && item.CanQueue() && !item.IsPlugin() && !item.IsScript())
602 return true;
604 else if ((!item.m_bIsFolder && IsVideo(item) && !IsEmptyVideoItem(item)) || item.IsDVD() ||
605 MUSIC::IsCDDA(item))
607 return true;
609 else if (item.m_bIsFolder && !item.IsPlugin() && !item.IsScript())
611 // Not a video-specific folder (like file:// or nfs://). Allow play if context is Video window.
612 if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_NAV &&
613 item.GetPath() != "add") // Exclude "Add video source" item
614 return true;
617 return false;
620 bool HasItemVideoDbInformation(const CFileItem& item)
622 CVideoDatabase db;
623 if (!db.Open())
625 CLog::LogF(LOGERROR, "Cannot open VideoDatabase");
626 return false;
629 return db.HasMovieInfo(item.GetDynPath()) ||
630 db.HasTvShowInfo(URIUtils::GetDirectory(item.GetPath())) ||
631 db.HasEpisodeInfo(item.GetDynPath()) || db.HasMusicVideoInfo(item.GetDynPath());
634 std::string GetResumeString(const CFileItem& item)
636 const ResumeInformation resumeInfo = GetItemResumeInformation(item);
637 if (resumeInfo.isResumable)
639 return GetResumeString(resumeInfo.startOffset, resumeInfo.partNumber);
641 else
643 return {};
647 std::string GetResumeString(int64_t startOffset, unsigned int partNumber)
649 if (startOffset > 0)
651 std::string resumeString{
652 StringUtils::Format(g_localizeStrings.Get(12022),
653 StringUtils::SecondsToTimeString(
654 static_cast<long>(CUtil::ConvertMilliSecsToSecsInt(startOffset)),
655 TIME_FORMAT_HH_MM_SS))};
656 if (partNumber > 0)
658 const std::string partString{StringUtils::Format(g_localizeStrings.Get(23051), partNumber)};
659 resumeString += " (" + partString + ")";
661 return resumeString;
663 else
665 return g_localizeStrings.Get(13362); // Continue watching
669 } // namespace KODI::VIDEO::UTILS