[XAudio2] avoid leak + fix voice creation for closest match
[xbmc.git] / xbmc / music / MusicUtils.cpp
blob215dbee5f568addc5c714d0633aa63e8baf5c9a5
1 /*
2 * Copyright (C) 2018 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 "MusicUtils.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 "application/Application.h"
18 #include "application/ApplicationComponents.h"
19 #include "application/ApplicationPlayer.h"
20 #include "dialogs/GUIDialogBusy.h"
21 #include "dialogs/GUIDialogKaiToast.h"
22 #include "dialogs/GUIDialogSelect.h"
23 #include "filesystem/Directory.h"
24 #include "filesystem/MusicDatabaseDirectory.h"
25 #include "guilib/GUIComponent.h"
26 #include "guilib/GUIKeyboardFactory.h"
27 #include "guilib/GUIWindowManager.h"
28 #include "guilib/LocalizeStrings.h"
29 #include "media/MediaType.h"
30 #include "music/MusicDatabase.h"
31 #include "music/MusicDbUrl.h"
32 #include "music/MusicFileItemClassify.h"
33 #include "music/tags/MusicInfoTag.h"
34 #include "network/NetworkFileItemClassify.h"
35 #include "playlists/PlayList.h"
36 #include "playlists/PlayListFactory.h"
37 #include "playlists/PlayListFileItemClassify.h"
38 #include "profiles/ProfileManager.h"
39 #include "settings/Settings.h"
40 #include "settings/SettingsComponent.h"
41 #include "threads/IRunnable.h"
42 #include "utils/FileUtils.h"
43 #include "utils/JobManager.h"
44 #include "utils/StringUtils.h"
45 #include "utils/URIUtils.h"
46 #include "utils/log.h"
47 #include "video/VideoFileItemClassify.h"
48 #include "view/GUIViewState.h"
50 #include <memory>
52 using namespace KODI;
53 using namespace KODI::VIDEO;
54 using namespace MUSIC_INFO;
55 using namespace XFILE;
56 using namespace std::chrono_literals;
58 namespace MUSIC_UTILS
60 class CSetArtJob : public CJob
62 CFileItemPtr pItem;
63 std::string m_artType;
64 std::string m_newArt;
66 public:
67 CSetArtJob(const CFileItemPtr& item, const std::string& type, const std::string& newArt)
68 : pItem(item), m_artType(type), m_newArt(newArt)
72 ~CSetArtJob(void) override = default;
74 bool HasSongExtraArtChanged(const CFileItemPtr& pSongItem,
75 const std::string& type,
76 const int itemID,
77 CMusicDatabase& db)
79 if (!pSongItem->HasMusicInfoTag())
80 return false;
81 int idSong = pSongItem->GetMusicInfoTag()->GetDatabaseId();
82 if (idSong <= 0)
83 return false;
84 bool result = false;
85 if (type == MediaTypeAlbum)
86 // Update art when song is from album
87 result = (itemID == pSongItem->GetMusicInfoTag()->GetAlbumId());
88 else if (type == MediaTypeArtist)
90 // Update art when artist is song or album artist of the song
91 if (pSongItem->HasProperty("artistid"))
93 // Check artistid property when we have it
94 for (CVariant::const_iterator_array varid =
95 pSongItem->GetProperty("artistid").begin_array();
96 varid != pSongItem->GetProperty("artistid").end_array(); ++varid)
98 int idArtist = static_cast<int>(varid->asInteger());
99 result = (itemID == idArtist);
100 if (result)
101 break;
104 else
105 { // Check song artists in database
106 result = db.IsSongArtist(idSong, itemID);
108 if (!result)
110 // Check song album artists
111 result = db.IsSongAlbumArtist(idSong, itemID);
114 return result;
117 // Asynchronously update song, album or artist art in library
118 // and trigger update to album & artist art of the currently playing song
119 // and songs queued in the current playlist
120 bool DoWork(void) override
122 int itemID = pItem->GetMusicInfoTag()->GetDatabaseId();
123 if (itemID <= 0)
124 return false;
125 std::string type = pItem->GetMusicInfoTag()->GetType();
126 CMusicDatabase db;
127 if (!db.Open())
128 return false;
129 if (!m_newArt.empty())
130 db.SetArtForItem(itemID, type, m_artType, m_newArt);
131 else
132 db.RemoveArtForItem(itemID, type, m_artType);
133 // Artwork changed so set datemodified field for artist, album or song
134 db.SetItemUpdated(itemID, type);
136 /* Update the art of the songs of the current music playlist.
137 Song thumb is often a fallback from the album and fanart is from the artist(s).
138 Clear the art if it is a song from the album or by the artist
139 (as song or album artist) that has modified artwork. The new artwork gets
140 loaded when the playlist is shown.
142 bool clearcache(false);
143 const PLAYLIST::CPlayList& playlist =
144 CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::Id::TYPE_MUSIC);
146 for (int i = 0; i < playlist.size(); ++i)
148 CFileItemPtr songitem = playlist[i];
149 if (HasSongExtraArtChanged(songitem, type, itemID, db))
151 songitem->ClearArt(); // Art gets reloaded when the current playist is shown
152 clearcache = true;
155 if (clearcache)
157 // Clear the music playlist from cache
158 CFileItemList items("playlistmusic://");
159 items.RemoveDiscCache(WINDOW_MUSIC_PLAYLIST);
162 // Similarly update the art of the currently playing song so it shows on OSD
163 const auto& components = CServiceBroker::GetAppComponents();
164 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
165 if (appPlayer->IsPlayingAudio() && g_application.CurrentFileItem().HasMusicInfoTag())
167 CFileItemPtr songitem = std::make_shared<CFileItem>(g_application.CurrentFileItem());
168 if (HasSongExtraArtChanged(songitem, type, itemID, db))
169 g_application.UpdateCurrentPlayArt();
172 db.Close();
173 return true;
177 class CSetSongRatingJob : public CJob
179 std::string strPath;
180 int idSong;
181 int iUserrating;
183 public:
184 CSetSongRatingJob(const std::string& filePath, int userrating)
185 : strPath(filePath), idSong(-1), iUserrating(userrating)
189 CSetSongRatingJob(int songId, int userrating) : strPath(), idSong(songId), iUserrating(userrating)
193 ~CSetSongRatingJob(void) override = default;
195 bool DoWork(void) override
197 // Asynchronously update song userrating in library
198 CMusicDatabase db;
199 if (db.Open())
201 if (idSong > 0)
202 db.SetSongUserrating(idSong, iUserrating);
203 else
204 db.SetSongUserrating(strPath, iUserrating);
205 db.Close();
208 return true;
212 void UpdateArtJob(const std::shared_ptr<CFileItem>& pItem,
213 const std::string& strType,
214 const std::string& strArt)
216 // Asynchronously update that type of art in the database
217 CSetArtJob* job = new CSetArtJob(pItem, strType, strArt);
218 CServiceBroker::GetJobManager()->AddJob(job, nullptr);
221 // Add art types required in Kodi and configured by the user
222 void AddHardCodedAndExtendedArtTypes(std::vector<std::string>& artTypes, const CMusicInfoTag& tag)
224 for (const auto& artType : GetArtTypesToScan(tag.GetType()))
226 if (find(artTypes.begin(), artTypes.end(), artType) == artTypes.end())
227 artTypes.push_back(artType);
231 // Add art types currently assigned to the media item
232 void AddCurrentArtTypes(std::vector<std::string>& artTypes,
233 const CMusicInfoTag& tag,
234 CMusicDatabase& db)
236 std::map<std::string, std::string> currentArt;
237 db.GetArtForItem(tag.GetDatabaseId(), tag.GetType(), currentArt);
238 for (const auto& art : currentArt)
240 if (!art.second.empty() && find(artTypes.begin(), artTypes.end(), art.first) == artTypes.end())
241 artTypes.push_back(art.first);
245 // Add art types that exist for other media items of the same type
246 void AddMediaTypeArtTypes(std::vector<std::string>& artTypes,
247 const CMusicInfoTag& tag,
248 CMusicDatabase& db)
250 std::vector<std::string> dbArtTypes;
251 db.GetArtTypes(tag.GetType(), dbArtTypes);
252 for (const auto& artType : dbArtTypes)
254 if (find(artTypes.begin(), artTypes.end(), artType) == artTypes.end())
255 artTypes.push_back(artType);
259 // Add art types from available but unassigned artwork for this media item
260 void AddAvailableArtTypes(std::vector<std::string>& artTypes,
261 const CMusicInfoTag& tag,
262 CMusicDatabase& db)
264 for (const auto& artType : db.GetAvailableArtTypesForItem(tag.GetDatabaseId(), tag.GetType()))
266 if (find(artTypes.begin(), artTypes.end(), artType) == artTypes.end())
267 artTypes.push_back(artType);
271 bool FillArtTypesList(CFileItem& musicitem, CFileItemList& artlist)
273 const CMusicInfoTag& tag = *musicitem.GetMusicInfoTag();
274 if (tag.GetDatabaseId() < 1 || tag.GetType().empty())
275 return false;
276 if (tag.GetType() != MediaTypeArtist && tag.GetType() != MediaTypeAlbum &&
277 tag.GetType() != MediaTypeSong)
278 return false;
280 artlist.Clear();
282 CMusicDatabase db;
283 db.Open();
285 std::vector<std::string> artTypes;
287 AddHardCodedAndExtendedArtTypes(artTypes, tag);
288 AddCurrentArtTypes(artTypes, tag, db);
289 AddMediaTypeArtTypes(artTypes, tag, db);
290 AddAvailableArtTypes(artTypes, tag, db);
292 db.Close();
294 for (const auto& type : artTypes)
296 CFileItemPtr artitem(new CFileItem(type, false));
297 // Localise the names of common types of art
298 if (type == "banner")
299 artitem->SetLabel(g_localizeStrings.Get(20020));
300 else if (type == "fanart")
301 artitem->SetLabel(g_localizeStrings.Get(20445));
302 else if (type == "poster")
303 artitem->SetLabel(g_localizeStrings.Get(20021));
304 else if (type == "thumb")
305 artitem->SetLabel(g_localizeStrings.Get(21371));
306 else
307 artitem->SetLabel(type);
308 // Set art type as art item property
309 artitem->SetProperty("arttype", type);
310 // Set current art as art item thumb
311 if (musicitem.HasArt(type))
312 artitem->SetArt("thumb", musicitem.GetArt(type));
313 artlist.Add(artitem);
316 return !artlist.IsEmpty();
319 std::string ShowSelectArtTypeDialog(CFileItemList& artitems)
321 // Prompt for choice
322 CGUIDialogSelect* dialog =
323 CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(
324 WINDOW_DIALOG_SELECT);
325 if (!dialog)
326 return "";
328 dialog->SetHeading(CVariant{13521});
329 dialog->Reset();
330 dialog->SetUseDetails(true);
331 dialog->EnableButton(true, 13516);
333 dialog->SetItems(artitems);
334 dialog->Open();
336 if (dialog->IsButtonPressed())
338 // Get the new art type name
339 std::string strArtTypeName;
340 if (!CGUIKeyboardFactory::ShowAndGetInput(strArtTypeName,
341 CVariant{g_localizeStrings.Get(13516)}, false))
342 return "";
343 // Add new type to the list of art types
344 CFileItemPtr artitem(new CFileItem(strArtTypeName, false));
345 artitem->SetLabel(strArtTypeName);
346 artitem->SetProperty("arttype", strArtTypeName);
347 artitems.Add(artitem);
349 return strArtTypeName;
352 return dialog->GetSelectedFileItem()->GetProperty("arttype").asString();
355 int ShowSelectRatingDialog(int iSelected)
357 CGUIDialogSelect* dialog =
358 CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(
359 WINDOW_DIALOG_SELECT);
360 if (dialog)
362 dialog->SetHeading(CVariant{38023});
363 dialog->Add(g_localizeStrings.Get(38022));
364 for (int i = 1; i <= 10; i++)
365 dialog->Add(StringUtils::Format("{}: {}", g_localizeStrings.Get(563), i));
366 dialog->SetSelected(iSelected);
367 dialog->Open();
369 int userrating = dialog->GetSelectedItem();
370 userrating = std::max(userrating, -1);
371 userrating = std::min(userrating, 10);
372 return userrating;
374 return -1;
377 void UpdateSongRatingJob(const std::shared_ptr<CFileItem>& pItem, int userrating)
379 // Asynchronously update the song user rating in music library
380 const CMusicInfoTag* tag = pItem->GetMusicInfoTag();
381 CSetSongRatingJob* job;
382 if (tag && tag->GetType() == MediaTypeSong && tag->GetDatabaseId() > 0)
383 // Use song ID when known
384 job = new CSetSongRatingJob(tag->GetDatabaseId(), userrating);
385 else
386 job = new CSetSongRatingJob(pItem->GetPath(), userrating);
387 CServiceBroker::GetJobManager()->AddJob(job, nullptr);
390 std::vector<std::string> GetArtTypesToScan(const MediaType& mediaType)
392 std::vector<std::string> arttypes;
393 // Get default types of art that are to be automatically fetched during scanning
394 if (mediaType == MediaTypeArtist)
396 arttypes = {"thumb", "fanart"};
397 for (auto& artType : CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(
398 CSettings::SETTING_MUSICLIBRARY_ARTISTART_WHITELIST))
400 if (find(arttypes.begin(), arttypes.end(), artType.asString()) == arttypes.end())
401 arttypes.emplace_back(artType.asString());
404 else if (mediaType == MediaTypeAlbum)
406 arttypes = {"thumb"};
407 for (auto& artType : CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(
408 CSettings::SETTING_MUSICLIBRARY_ALBUMART_WHITELIST))
410 if (find(arttypes.begin(), arttypes.end(), artType.asString()) == arttypes.end())
411 arttypes.emplace_back(artType.asString());
414 return arttypes;
417 bool IsValidArtType(const std::string& potentialArtType)
419 // Check length and is ascii
420 return potentialArtType.length() <= 25 &&
421 std::find_if_not(potentialArtType.begin(), potentialArtType.end(),
422 StringUtils::isasciialphanum) == potentialArtType.end();
425 } // namespace MUSIC_UTILS
427 namespace
429 class CAsyncGetItemsForPlaylist : public IRunnable
431 public:
432 CAsyncGetItemsForPlaylist(const std::shared_ptr<CFileItem>& item, CFileItemList& queuedItems)
433 : m_item(item), m_queuedItems(queuedItems)
437 ~CAsyncGetItemsForPlaylist() override = default;
439 void Run() override
441 // fast lookup is needed here
442 m_queuedItems.SetFastLookup(true);
444 m_musicDatabase.Open();
445 GetItemsForPlaylist(m_item);
446 m_musicDatabase.Close();
449 private:
450 void GetItemsForPlaylist(const std::shared_ptr<CFileItem>& item);
452 const std::shared_ptr<CFileItem> m_item;
453 CFileItemList& m_queuedItems;
454 CMusicDatabase m_musicDatabase;
457 SortDescription GetSortDescription(const CGUIViewState& state, const CFileItemList& items)
459 SortDescription sortDescTrackNumber;
461 auto sortDescriptions = state.GetSortDescriptions();
462 for (auto& sortDescription : sortDescriptions)
464 if (sortDescription.sortBy == SortByTrackNumber)
466 // check whether at least one item has actually a track number set
467 for (const auto& item : items)
469 if (item->HasMusicInfoTag() && item->GetMusicInfoTag()->GetTrackNumber() > 0)
471 // First choice for folders containing a single album
472 sortDescTrackNumber = sortDescription;
473 sortDescTrackNumber.sortOrder = SortOrderAscending;
474 break; // leave items loop. we can still find ByArtistThenYear. so, no return here.
478 else if (sortDescription.sortBy == SortByArtistThenYear)
480 // check whether songs from at least two different albums are in the list
481 int lastAlbumId = -1;
482 for (const auto& item : items)
484 if (item->HasMusicInfoTag())
486 const auto tag = item->GetMusicInfoTag();
487 if (lastAlbumId != -1 && tag->GetAlbumId() != lastAlbumId)
489 // First choice for folders containing multiple albums
490 sortDescription.sortOrder = SortOrderAscending;
491 return sortDescription;
493 lastAlbumId = tag->GetAlbumId();
499 if (sortDescTrackNumber.sortBy != SortByNone)
500 return sortDescTrackNumber;
501 else
502 return state.GetSortMethod(); // last resort
505 void CAsyncGetItemsForPlaylist::GetItemsForPlaylist(const std::shared_ptr<CFileItem>& item)
507 if (item->IsParentFolder() || !item->CanQueue() || item->IsRAR() || item->IsZIP())
508 return;
510 if (MUSIC::IsMusicDb(*item) && item->m_bIsFolder && !item->IsParentFolder())
512 // we have a music database folder, just grab the "all" item underneath it
513 XFILE::CMusicDatabaseDirectory dir;
515 if (!dir.ContainsSongs(item->GetPath()))
517 // grab the ALL item in this category
518 // Genres will still require 2 lookups, and queuing the entire Genre folder
519 // will require 3 lookups (genre, artist, album)
520 CMusicDbUrl musicUrl;
521 if (musicUrl.FromString(item->GetPath()))
523 musicUrl.AppendPath("-1/");
525 const auto allItem = std::make_shared<CFileItem>(musicUrl.ToString(), true);
526 allItem->SetCanQueue(true); // workaround for CanQueue() check above
527 GetItemsForPlaylist(allItem);
529 return;
533 if (item->m_bIsFolder)
535 // Check if we add a locked share
536 if (item->m_bIsShareOrDrive)
538 if (!g_passwordManager.IsItemUnlocked(item.get(), "music"))
539 return;
542 CFileItemList items;
543 XFILE::CDirectory::GetDirectory(item->GetPath(), items, "", XFILE::DIR_FLAG_DEFAULTS);
545 const std::unique_ptr<CGUIViewState> state(
546 CGUIViewState::GetViewState(WINDOW_MUSIC_NAV, items));
547 if (state)
549 LABEL_MASKS labelMasks;
550 state->GetSortMethodLabelMasks(labelMasks);
552 const CLabelFormatter fileFormatter(labelMasks.m_strLabelFile, labelMasks.m_strLabel2File);
553 const CLabelFormatter folderFormatter(labelMasks.m_strLabelFolder,
554 labelMasks.m_strLabel2Folder);
555 for (const auto& i : items)
557 if (i->IsLabelPreformatted())
558 continue;
560 if (i->m_bIsFolder)
561 folderFormatter.FormatLabels(i.get());
562 else
563 fileFormatter.FormatLabels(i.get());
566 SortDescription sortDesc;
567 if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_MUSIC_NAV)
568 sortDesc = state->GetSortMethod();
569 else
570 sortDesc = GetSortDescription(*state, items);
572 if (sortDesc.sortBy == SortByLabel)
573 items.ClearSortState();
575 items.Sort(sortDesc);
578 for (const auto& i : items)
580 GetItemsForPlaylist(i);
583 else
585 if (PLAYLIST::IsPlayList(*item))
587 const std::unique_ptr<PLAYLIST::CPlayList> playList(
588 PLAYLIST::CPlayListFactory::Create(*item));
589 if (!playList)
591 CLog::Log(LOGERROR, "{} failed to create playlist {}", __FUNCTION__, item->GetPath());
592 return;
595 if (!playList->Load(item->GetPath()))
597 CLog::Log(LOGERROR, "{} failed to load playlist {}", __FUNCTION__, item->GetPath());
598 return;
601 for (int i = 0; i < playList->size(); ++i)
603 GetItemsForPlaylist((*playList)[i]);
606 else if (NETWORK::IsInternetStream(*item) && !MUSIC::IsMusicDb(*item))
608 // just queue the internet stream, it will be expanded on play
609 m_queuedItems.Add(item);
611 else if (item->IsPlugin() && item->GetProperty("isplayable").asBoolean())
613 // python files can be played
614 m_queuedItems.Add(item);
616 else if (!item->IsNFO() && (MUSIC::IsAudio(*item) || IsVideo(*item)))
618 const auto itemCheck = m_queuedItems.Get(item->GetPath());
619 if (!itemCheck || itemCheck->GetStartOffset() != item->GetStartOffset())
621 // add item
622 m_musicDatabase.SetPropertiesForFileItem(*item);
623 m_queuedItems.Add(item);
629 void ShowToastNotification(const CFileItem& item, int titleId)
631 std::string localizedMediaType;
632 std::string title;
634 if (item.HasMusicInfoTag())
636 localizedMediaType = CMediaTypes::GetCapitalLocalization(item.GetMusicInfoTag()->GetType());
637 title = item.GetMusicInfoTag()->GetTitle();
640 if (title.empty())
641 title = item.GetLabel();
642 if (title.empty())
643 return; // no meaningful toast possible.
645 const std::string message =
646 localizedMediaType.empty() ? title : localizedMediaType + ": " + title;
648 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(titleId),
649 message);
652 std::string GetMusicDbItemPath(const CFileItem& item)
654 std::string path = item.GetPath();
655 if (!URIUtils::IsMusicDb(path))
656 path = item.GetProperty("original_listitem_url").asString();
658 if (URIUtils::IsMusicDb(path))
659 return path;
661 return {};
664 void AddItemToPlayListAndPlay(const std::shared_ptr<CFileItem>& itemToQueue,
665 const std::shared_ptr<CFileItem>& itemToPlay,
666 const std::string& player)
668 // recursively add items to list
669 CFileItemList queuedItems;
670 MUSIC_UTILS::GetItemsForPlayList(itemToQueue, queuedItems);
672 auto& playlistPlayer = CServiceBroker::GetPlaylistPlayer();
673 playlistPlayer.ClearPlaylist(PLAYLIST::Id::TYPE_MUSIC);
674 playlistPlayer.Reset();
675 playlistPlayer.Add(PLAYLIST::Id::TYPE_MUSIC, queuedItems);
677 // figure out where to start playback
678 PLAYLIST::CPlayList& playList = playlistPlayer.GetPlaylist(PLAYLIST::Id::TYPE_MUSIC);
679 int pos = 0;
680 if (itemToPlay)
682 for (const std::shared_ptr<CFileItem>& queuedItem : queuedItems)
684 if (queuedItem->IsSamePath(itemToPlay.get()))
685 break;
687 pos++;
691 if (playlistPlayer.IsShuffled(PLAYLIST::Id::TYPE_MUSIC))
693 playList.Swap(0, playList.FindOrder(pos));
694 pos = 0;
697 playlistPlayer.SetCurrentPlaylist(PLAYLIST::Id::TYPE_MUSIC);
698 playlistPlayer.Play(pos, player);
700 } // unnamed namespace
702 namespace MUSIC_UTILS
704 bool IsAutoPlayNextItem(const CFileItem& item)
706 if (!item.HasMusicInfoTag())
707 return false;
709 const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
710 return settings->GetBool(CSettings::SETTING_MUSICPLAYER_AUTOPLAYNEXTITEM) &&
711 !settings->GetBool(CSettings::SETTING_MUSICPLAYER_QUEUEBYDEFAULT);
714 void PlayItem(const std::shared_ptr<CFileItem>& itemIn,
715 const std::string& player,
716 ContentUtils::PlayMode mode /* = ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM */)
718 auto item = itemIn;
720 // Allow queuing of unqueueable items
721 // when we try to queue them directly
722 if (!itemIn->CanQueue())
724 // make a copy to not alter the original item
725 item = std::make_shared<CFileItem>(*itemIn);
726 item->SetCanQueue(true);
729 if (item->m_bIsFolder)
731 AddItemToPlayListAndPlay(item, nullptr, player);
733 else if (item->HasMusicInfoTag())
735 if (mode == ContentUtils::PlayMode::PLAY_FROM_HERE ||
736 (mode == ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM && IsAutoPlayNextItem(*item)))
738 // Add item and all its siblings to the playlist and play. Prefer musicdb path if available,
739 // because it provides more information than just a plain file system path for example.
740 std::string parentPath = item->GetProperty("ParentPath").asString();
741 if (parentPath.empty())
743 std::string path = GetMusicDbItemPath(*item);
744 if (path.empty())
745 path = item->GetPath();
747 URIUtils::GetParentPath(path, parentPath);
749 if (parentPath.empty())
751 CLog::LogF(LOGERROR, "Unable to obtain parent path for '{}'", item->GetPath());
752 return;
756 const auto parentItem = std::make_shared<CFileItem>(parentPath, true);
757 if (item->GetStartOffset() == STARTOFFSET_RESUME)
758 parentItem->SetStartOffset(STARTOFFSET_RESUME);
760 AddItemToPlayListAndPlay(parentItem, item, player);
762 else // mode == PlayMode::PLAY_ONLY_THIS
764 // song, so just play it
765 auto& playlistPlayer = CServiceBroker::GetPlaylistPlayer();
766 playlistPlayer.Reset();
767 playlistPlayer.SetCurrentPlaylist(PLAYLIST::Id::TYPE_NONE);
768 playlistPlayer.Play(item, player);
773 void QueueItem(const std::shared_ptr<CFileItem>& itemIn, QueuePosition pos)
775 auto item = itemIn;
777 // Allow queuing of unqueueable items
778 // when we try to queue them directly
779 if (!itemIn->CanQueue())
781 // make a copy to not alter the original item
782 item = std::make_shared<CFileItem>(*itemIn);
783 item->SetCanQueue(true);
786 auto& player = CServiceBroker::GetPlaylistPlayer();
788 PLAYLIST::Id playlistId = player.GetCurrentPlaylist();
789 if (playlistId == PLAYLIST::Id::TYPE_NONE)
791 const auto& components = CServiceBroker::GetAppComponents();
792 playlistId = components.GetComponent<CApplicationPlayer>()->GetPreferredPlaylist();
795 if (playlistId == PLAYLIST::Id::TYPE_NONE)
796 playlistId = PLAYLIST::Id::TYPE_MUSIC;
798 // Check for the partymode playlist item, do nothing when "PartyMode.xsp" not exists
799 if (PLAYLIST::IsSmartPlayList(*item) && !CFileUtils::Exists(item->GetPath()))
801 const auto profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
802 if (item->GetPath() == profileManager->GetUserDataItem("PartyMode.xsp"))
803 return;
806 const int oldSize = player.GetPlaylist(playlistId).size();
808 CFileItemList queuedItems;
809 GetItemsForPlayList(item, queuedItems);
811 // if party mode, add items but DONT start playing
812 if (g_partyModeManager.IsEnabled())
814 g_partyModeManager.AddUserSongs(queuedItems, false);
815 return;
818 const auto& components = CServiceBroker::GetAppComponents();
819 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
821 if (pos == QueuePosition::POSITION_BEGIN && appPlayer->IsPlaying())
822 player.Insert(playlistId, queuedItems,
823 CServiceBroker::GetPlaylistPlayer().GetCurrentItemIdx() + 1);
824 else
825 player.Add(playlistId, queuedItems);
827 bool playbackStarted = false;
829 if (!appPlayer->IsPlaying() && player.GetPlaylist(playlistId).size())
831 const int winID = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow();
832 if (winID == WINDOW_MUSIC_NAV)
834 CGUIViewState* viewState = CGUIViewState::GetViewState(winID, queuedItems);
835 if (viewState)
836 viewState->SetPlaylistDirectory("playlistmusic://");
839 player.Reset();
840 player.SetCurrentPlaylist(playlistId);
841 player.Play(oldSize, ""); // start playing at the first new item
843 playbackStarted = true;
846 if (!playbackStarted)
848 if (pos == QueuePosition::POSITION_END)
849 ShowToastNotification(*item, 38082); // Added to end of playlist
850 else
851 ShowToastNotification(*item, 38083); // Added to playlist to play next
855 bool GetItemsForPlayList(const std::shared_ptr<CFileItem>& item, CFileItemList& queuedItems)
857 CAsyncGetItemsForPlaylist getItems(item, queuedItems);
858 return CGUIDialogBusy::Wait(&getItems,
859 500, // 500ms before busy dialog appears
860 true); // can be cancelled
863 namespace
865 bool IsNonExistingUserPartyModePlaylist(const CFileItem& item)
867 if (!PLAYLIST::IsSmartPlayList(item))
868 return false;
870 const std::string& path{item.GetPath()};
871 const auto profileManager{CServiceBroker::GetSettingsComponent()->GetProfileManager()};
872 return ((profileManager->GetUserDataItem("PartyMode.xsp") == path) && !CFileUtils::Exists(path));
874 } // unnamed namespace
876 bool IsItemPlayable(const CFileItem& item)
878 // Exclude all parent folders
879 if (item.IsParentFolder())
880 return false;
882 // Exclude all video library items
883 if (IsVideoDb(item) || StringUtils::StartsWithNoCase(item.GetPath(), "library://video/"))
884 return false;
886 // Exclude other components
887 if (item.IsPVR() || item.IsPlugin() || item.IsScript() || item.IsAddonsPath())
888 return false;
890 // Exclude special items
891 if (StringUtils::StartsWithNoCase(item.GetPath(), "newsmartplaylist://") ||
892 StringUtils::StartsWithNoCase(item.GetPath(), "newplaylist://"))
893 return false;
895 // Include playlists located at one of the possible music playlist locations
896 if (PLAYLIST::IsPlayList(item))
898 if (StringUtils::StartsWithNoCase(item.GetMimeType(), "audio/"))
899 return true;
901 if (StringUtils::StartsWithNoCase(item.GetPath(), "special://musicplaylists/") ||
902 StringUtils::StartsWithNoCase(item.GetPath(), "special://profile/playlists/music/"))
903 return true;
905 // Has user changed default playlists location and the list is located there?
906 const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
907 std::string path = settings->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH);
908 StringUtils::TrimRight(path, "/");
909 if (StringUtils::StartsWith(item.GetPath(), StringUtils::Format("{}/music/", path)))
910 return true;
912 if (!item.m_bIsFolder && !item.HasMusicInfoTag())
914 // Unknown location. Type cannot be determined for non-folder items.
915 return false;
919 if (IsNonExistingUserPartyModePlaylist(item))
920 return false;
922 if (item.m_bIsFolder &&
923 (MUSIC::IsMusicDb(item) || StringUtils::StartsWithNoCase(item.GetPath(), "library://music/")))
925 // Exclude top level nodes - eg can't play 'genres' just a specific genre etc
926 const XFILE::MUSICDATABASEDIRECTORY::NODE_TYPE node =
927 XFILE::CMusicDatabaseDirectory::GetDirectoryParentType(item.GetPath());
928 if (node == XFILE::MUSICDATABASEDIRECTORY::NODE_TYPE_OVERVIEW)
929 return false;
931 return true;
934 if (item.HasMusicInfoTag() && item.CanQueue())
935 return true;
936 else if (!item.m_bIsFolder && MUSIC::IsAudio(item))
937 return true;
938 else if (item.m_bIsFolder)
940 // Not a music-specific folder (just file:// or nfs://). Allow play if context is Music window.
941 if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_MUSIC_NAV &&
942 item.GetPath() != "add") // Exclude "Add music source" item
943 return true;
945 return false;
948 } // namespace MUSIC_UTILS