summaryrefslogtreecommitdiffstats
path: root/xbmc/music/MusicUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/music/MusicUtils.cpp')
-rw-r--r--xbmc/music/MusicUtils.cpp841
1 files changed, 841 insertions, 0 deletions
diff --git a/xbmc/music/MusicUtils.cpp b/xbmc/music/MusicUtils.cpp
new file mode 100644
index 0000000..ac66aa3
--- /dev/null
+++ b/xbmc/music/MusicUtils.cpp
@@ -0,0 +1,841 @@
+/*
+ * Copyright (C) 2018 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 "MusicUtils.h"
+
+#include "FileItem.h"
+#include "GUIPassword.h"
+#include "PartyModeManager.h"
+#include "PlayListPlayer.h"
+#include "ServiceBroker.h"
+#include "application/Application.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "dialogs/GUIDialogBusy.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "dialogs/GUIDialogSelect.h"
+#include "filesystem/Directory.h"
+#include "filesystem/MusicDatabaseDirectory.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIKeyboardFactory.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "media/MediaType.h"
+#include "music/MusicDatabase.h"
+#include "music/MusicDbUrl.h"
+#include "music/tags/MusicInfoTag.h"
+#include "playlists/PlayList.h"
+#include "playlists/PlayListFactory.h"
+#include "profiles/ProfileManager.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/IRunnable.h"
+#include "utils/FileUtils.h"
+#include "utils/JobManager.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+#include "view/GUIViewState.h"
+
+using namespace MUSIC_INFO;
+using namespace XFILE;
+using namespace std::chrono_literals;
+
+namespace MUSIC_UTILS
+{
+class CSetArtJob : public CJob
+{
+ CFileItemPtr pItem;
+ std::string m_artType;
+ std::string m_newArt;
+
+public:
+ CSetArtJob(const CFileItemPtr& item, const std::string& type, const std::string& newArt)
+ : pItem(item), m_artType(type), m_newArt(newArt)
+ {
+ }
+
+ ~CSetArtJob(void) override = default;
+
+ bool HasSongExtraArtChanged(const CFileItemPtr& pSongItem,
+ const std::string& type,
+ const int itemID,
+ CMusicDatabase& db)
+ {
+ if (!pSongItem->HasMusicInfoTag())
+ return false;
+ int idSong = pSongItem->GetMusicInfoTag()->GetDatabaseId();
+ if (idSong <= 0)
+ return false;
+ bool result = false;
+ if (type == MediaTypeAlbum)
+ // Update art when song is from album
+ result = (itemID == pSongItem->GetMusicInfoTag()->GetAlbumId());
+ else if (type == MediaTypeArtist)
+ {
+ // Update art when artist is song or album artist of the song
+ if (pSongItem->HasProperty("artistid"))
+ {
+ // Check artistid property when we have it
+ for (CVariant::const_iterator_array varid =
+ pSongItem->GetProperty("artistid").begin_array();
+ varid != pSongItem->GetProperty("artistid").end_array(); ++varid)
+ {
+ int idArtist = static_cast<int>(varid->asInteger());
+ result = (itemID == idArtist);
+ if (result)
+ break;
+ }
+ }
+ else
+ { // Check song artists in database
+ result = db.IsSongArtist(idSong, itemID);
+ }
+ if (!result)
+ {
+ // Check song album artists
+ result = db.IsSongAlbumArtist(idSong, itemID);
+ }
+ }
+ return result;
+ }
+
+ // Asynchronously update song, album or artist art in library
+ // and trigger update to album & artist art of the currently playing song
+ // and songs queued in the current playlist
+ bool DoWork(void) override
+ {
+ int itemID = pItem->GetMusicInfoTag()->GetDatabaseId();
+ if (itemID <= 0)
+ return false;
+ std::string type = pItem->GetMusicInfoTag()->GetType();
+ CMusicDatabase db;
+ if (!db.Open())
+ return false;
+ if (!m_newArt.empty())
+ db.SetArtForItem(itemID, type, m_artType, m_newArt);
+ else
+ db.RemoveArtForItem(itemID, type, m_artType);
+ // Artwork changed so set datemodified field for artist, album or song
+ db.SetItemUpdated(itemID, type);
+
+ /* Update the art of the songs of the current music playlist.
+ Song thumb is often a fallback from the album and fanart is from the artist(s).
+ Clear the art if it is a song from the album or by the artist
+ (as song or album artist) that has modified artwork. The new artwork gets
+ loaded when the playlist is shown.
+ */
+ bool clearcache(false);
+ const PLAYLIST::CPlayList& playlist =
+ CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::TYPE_MUSIC);
+
+ for (int i = 0; i < playlist.size(); ++i)
+ {
+ CFileItemPtr songitem = playlist[i];
+ if (HasSongExtraArtChanged(songitem, type, itemID, db))
+ {
+ songitem->ClearArt(); // Art gets reloaded when the current playist is shown
+ clearcache = true;
+ }
+ }
+ if (clearcache)
+ {
+ // Clear the music playlist from cache
+ CFileItemList items("playlistmusic://");
+ items.RemoveDiscCache(WINDOW_MUSIC_PLAYLIST);
+ }
+
+ // Similarly update the art of the currently playing song so it shows on OSD
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsPlayingAudio() && g_application.CurrentFileItem().HasMusicInfoTag())
+ {
+ CFileItemPtr songitem = CFileItemPtr(new CFileItem(g_application.CurrentFileItem()));
+ if (HasSongExtraArtChanged(songitem, type, itemID, db))
+ g_application.UpdateCurrentPlayArt();
+ }
+
+ db.Close();
+ return true;
+ }
+};
+
+class CSetSongRatingJob : public CJob
+{
+ std::string strPath;
+ int idSong;
+ int iUserrating;
+
+public:
+ CSetSongRatingJob(const std::string& filePath, int userrating)
+ : strPath(filePath), idSong(-1), iUserrating(userrating)
+ {
+ }
+
+ CSetSongRatingJob(int songId, int userrating) : strPath(), idSong(songId), iUserrating(userrating)
+ {
+ }
+
+ ~CSetSongRatingJob(void) override = default;
+
+ bool DoWork(void) override
+ {
+ // Asynchronously update song userrating in library
+ CMusicDatabase db;
+ if (db.Open())
+ {
+ if (idSong > 0)
+ db.SetSongUserrating(idSong, iUserrating);
+ else
+ db.SetSongUserrating(strPath, iUserrating);
+ db.Close();
+ }
+
+ return true;
+ }
+};
+
+void UpdateArtJob(const std::shared_ptr<CFileItem>& pItem,
+ const std::string& strType,
+ const std::string& strArt)
+{
+ // Asynchronously update that type of art in the database
+ CSetArtJob* job = new CSetArtJob(pItem, strType, strArt);
+ CServiceBroker::GetJobManager()->AddJob(job, nullptr);
+}
+
+// Add art types required in Kodi and configured by the user
+void AddHardCodedAndExtendedArtTypes(std::vector<std::string>& artTypes, const CMusicInfoTag& tag)
+{
+ for (const auto& artType : GetArtTypesToScan(tag.GetType()))
+ {
+ if (find(artTypes.begin(), artTypes.end(), artType) == artTypes.end())
+ artTypes.push_back(artType);
+ }
+}
+
+// Add art types currently assigned to the media item
+void AddCurrentArtTypes(std::vector<std::string>& artTypes,
+ const CMusicInfoTag& tag,
+ CMusicDatabase& db)
+{
+ std::map<std::string, std::string> currentArt;
+ db.GetArtForItem(tag.GetDatabaseId(), tag.GetType(), currentArt);
+ for (const auto& art : currentArt)
+ {
+ if (!art.second.empty() && find(artTypes.begin(), artTypes.end(), art.first) == artTypes.end())
+ artTypes.push_back(art.first);
+ }
+}
+
+// Add art types that exist for other media items of the same type
+void AddMediaTypeArtTypes(std::vector<std::string>& artTypes,
+ const CMusicInfoTag& tag,
+ CMusicDatabase& db)
+{
+ std::vector<std::string> dbArtTypes;
+ db.GetArtTypes(tag.GetType(), dbArtTypes);
+ for (const auto& artType : dbArtTypes)
+ {
+ if (find(artTypes.begin(), artTypes.end(), artType) == artTypes.end())
+ artTypes.push_back(artType);
+ }
+}
+
+// Add art types from available but unassigned artwork for this media item
+void AddAvailableArtTypes(std::vector<std::string>& artTypes,
+ const CMusicInfoTag& tag,
+ CMusicDatabase& db)
+{
+ for (const auto& artType : db.GetAvailableArtTypesForItem(tag.GetDatabaseId(), tag.GetType()))
+ {
+ if (find(artTypes.begin(), artTypes.end(), artType) == artTypes.end())
+ artTypes.push_back(artType);
+ }
+}
+
+bool FillArtTypesList(CFileItem& musicitem, CFileItemList& artlist)
+{
+ const CMusicInfoTag& tag = *musicitem.GetMusicInfoTag();
+ if (tag.GetDatabaseId() < 1 || tag.GetType().empty())
+ return false;
+ if (tag.GetType() != MediaTypeArtist && tag.GetType() != MediaTypeAlbum &&
+ tag.GetType() != MediaTypeSong)
+ return false;
+
+ artlist.Clear();
+
+ CMusicDatabase db;
+ db.Open();
+
+ std::vector<std::string> artTypes;
+
+ AddHardCodedAndExtendedArtTypes(artTypes, tag);
+ AddCurrentArtTypes(artTypes, tag, db);
+ AddMediaTypeArtTypes(artTypes, tag, db);
+ AddAvailableArtTypes(artTypes, tag, db);
+
+ db.Close();
+
+ for (const auto& type : artTypes)
+ {
+ CFileItemPtr artitem(new CFileItem(type, false));
+ // Localise the names of common types of art
+ if (type == "banner")
+ artitem->SetLabel(g_localizeStrings.Get(20020));
+ else if (type == "fanart")
+ artitem->SetLabel(g_localizeStrings.Get(20445));
+ else if (type == "poster")
+ artitem->SetLabel(g_localizeStrings.Get(20021));
+ else if (type == "thumb")
+ artitem->SetLabel(g_localizeStrings.Get(21371));
+ else
+ artitem->SetLabel(type);
+ // Set art type as art item property
+ artitem->SetProperty("arttype", type);
+ // Set current art as art item thumb
+ if (musicitem.HasArt(type))
+ artitem->SetArt("thumb", musicitem.GetArt(type));
+ artlist.Add(artitem);
+ }
+
+ return !artlist.IsEmpty();
+}
+
+std::string ShowSelectArtTypeDialog(CFileItemList& artitems)
+{
+ // Prompt for choice
+ CGUIDialogSelect* dialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(
+ WINDOW_DIALOG_SELECT);
+ if (!dialog)
+ return "";
+
+ dialog->SetHeading(CVariant{13521});
+ dialog->Reset();
+ dialog->SetUseDetails(true);
+ dialog->EnableButton(true, 13516);
+
+ dialog->SetItems(artitems);
+ dialog->Open();
+
+ if (dialog->IsButtonPressed())
+ {
+ // Get the new art type name
+ std::string strArtTypeName;
+ if (!CGUIKeyboardFactory::ShowAndGetInput(strArtTypeName,
+ CVariant{g_localizeStrings.Get(13516)}, false))
+ return "";
+ // Add new type to the list of art types
+ CFileItemPtr artitem(new CFileItem(strArtTypeName, false));
+ artitem->SetLabel(strArtTypeName);
+ artitem->SetProperty("arttype", strArtTypeName);
+ artitems.Add(artitem);
+
+ return strArtTypeName;
+ }
+
+ return dialog->GetSelectedFileItem()->GetProperty("arttype").asString();
+}
+
+int ShowSelectRatingDialog(int iSelected)
+{
+ CGUIDialogSelect* dialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(
+ WINDOW_DIALOG_SELECT);
+ if (dialog)
+ {
+ dialog->SetHeading(CVariant{38023});
+ dialog->Add(g_localizeStrings.Get(38022));
+ for (int i = 1; i <= 10; i++)
+ dialog->Add(StringUtils::Format("{}: {}", g_localizeStrings.Get(563), i));
+ dialog->SetSelected(iSelected);
+ dialog->Open();
+
+ int userrating = dialog->GetSelectedItem();
+ userrating = std::max(userrating, -1);
+ userrating = std::min(userrating, 10);
+ return userrating;
+ }
+ return -1;
+}
+
+void UpdateSongRatingJob(const std::shared_ptr<CFileItem>& pItem, int userrating)
+{
+ // Asynchronously update the song user rating in music library
+ const CMusicInfoTag* tag = pItem->GetMusicInfoTag();
+ CSetSongRatingJob* job;
+ if (tag && tag->GetType() == MediaTypeSong && tag->GetDatabaseId() > 0)
+ // Use song ID when known
+ job = new CSetSongRatingJob(tag->GetDatabaseId(), userrating);
+ else
+ job = new CSetSongRatingJob(pItem->GetPath(), userrating);
+ CServiceBroker::GetJobManager()->AddJob(job, nullptr);
+}
+
+std::vector<std::string> GetArtTypesToScan(const MediaType& mediaType)
+{
+ std::vector<std::string> arttypes;
+ // Get default types of art that are to be automatically fetched during scanning
+ if (mediaType == MediaTypeArtist)
+ {
+ arttypes = {"thumb", "fanart"};
+ for (auto& artType : CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(
+ CSettings::SETTING_MUSICLIBRARY_ARTISTART_WHITELIST))
+ {
+ if (find(arttypes.begin(), arttypes.end(), artType.asString()) == arttypes.end())
+ arttypes.emplace_back(artType.asString());
+ }
+ }
+ else if (mediaType == MediaTypeAlbum)
+ {
+ arttypes = {"thumb"};
+ for (auto& artType : CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(
+ CSettings::SETTING_MUSICLIBRARY_ALBUMART_WHITELIST))
+ {
+ if (find(arttypes.begin(), arttypes.end(), artType.asString()) == arttypes.end())
+ arttypes.emplace_back(artType.asString());
+ }
+ }
+ return arttypes;
+}
+
+bool IsValidArtType(const std::string& potentialArtType)
+{
+ // Check length and is ascii
+ return potentialArtType.length() <= 25 &&
+ std::find_if_not(potentialArtType.begin(), potentialArtType.end(),
+ StringUtils::isasciialphanum) == potentialArtType.end();
+}
+
+} // namespace MUSIC_UTILS
+
+namespace
+{
+class CAsyncGetItemsForPlaylist : public IRunnable
+{
+public:
+ CAsyncGetItemsForPlaylist(const std::shared_ptr<CFileItem>& item, CFileItemList& queuedItems)
+ : m_item(item), m_queuedItems(queuedItems)
+ {
+ }
+
+ ~CAsyncGetItemsForPlaylist() override = default;
+
+ void Run() override
+ {
+ // fast lookup is needed here
+ m_queuedItems.SetFastLookup(true);
+
+ m_musicDatabase.Open();
+ GetItemsForPlaylist(m_item);
+ m_musicDatabase.Close();
+ }
+
+private:
+ void GetItemsForPlaylist(const std::shared_ptr<CFileItem>& item);
+
+ const std::shared_ptr<CFileItem> m_item;
+ CFileItemList& m_queuedItems;
+ CMusicDatabase m_musicDatabase;
+};
+
+SortDescription GetSortDescription(const CGUIViewState& state, const CFileItemList& items)
+{
+ SortDescription sortDescTrackNumber;
+
+ auto sortDescriptions = state.GetSortDescriptions();
+ for (auto& sortDescription : sortDescriptions)
+ {
+ if (sortDescription.sortBy == SortByTrackNumber)
+ {
+ // check whether at least one item has actually a track number set
+ for (const auto& item : items)
+ {
+ if (item->HasMusicInfoTag() && item->GetMusicInfoTag()->GetTrackNumber() > 0)
+ {
+ // First choice for folders containing a single album
+ sortDescTrackNumber = sortDescription;
+ sortDescTrackNumber.sortOrder = SortOrderAscending;
+ break; // leave items loop. we can still find ByArtistThenYear. so, no return here.
+ }
+ }
+ }
+ else if (sortDescription.sortBy == SortByArtistThenYear)
+ {
+ // check whether songs from at least two different albums are in the list
+ int lastAlbumId = -1;
+ for (const auto& item : items)
+ {
+ if (item->HasMusicInfoTag())
+ {
+ const auto tag = item->GetMusicInfoTag();
+ if (lastAlbumId != -1 && tag->GetAlbumId() != lastAlbumId)
+ {
+ // First choice for folders containing multiple albums
+ sortDescription.sortOrder = SortOrderAscending;
+ return sortDescription;
+ }
+ lastAlbumId = tag->GetAlbumId();
+ }
+ }
+ }
+ }
+
+ if (sortDescTrackNumber.sortBy != SortByNone)
+ return sortDescTrackNumber;
+ else
+ return state.GetSortMethod(); // last resort
+}
+
+void CAsyncGetItemsForPlaylist::GetItemsForPlaylist(const std::shared_ptr<CFileItem>& item)
+{
+ if (item->IsParentFolder() || !item->CanQueue() || item->IsRAR() || item->IsZIP())
+ return;
+
+ if (item->IsMusicDb() && item->m_bIsFolder && !item->IsParentFolder())
+ {
+ // we have a music database folder, just grab the "all" item underneath it
+ XFILE::CMusicDatabaseDirectory dir;
+
+ if (!dir.ContainsSongs(item->GetPath()))
+ {
+ // grab the ALL item in this category
+ // Genres will still require 2 lookups, and queuing the entire Genre folder
+ // will require 3 lookups (genre, artist, album)
+ CMusicDbUrl musicUrl;
+ if (musicUrl.FromString(item->GetPath()))
+ {
+ musicUrl.AppendPath("-1/");
+
+ const auto allItem = std::make_shared<CFileItem>(musicUrl.ToString(), true);
+ allItem->SetCanQueue(true); // workaround for CanQueue() check above
+ GetItemsForPlaylist(allItem);
+ }
+ return;
+ }
+ }
+
+ if (item->m_bIsFolder)
+ {
+ // Check if we add a locked share
+ if (item->m_bIsShareOrDrive)
+ {
+ if (!g_passwordManager.IsItemUnlocked(item.get(), "music"))
+ return;
+ }
+
+ CFileItemList items;
+ XFILE::CDirectory::GetDirectory(item->GetPath(), items, "", XFILE::DIR_FLAG_DEFAULTS);
+
+ const std::unique_ptr<CGUIViewState> state(
+ CGUIViewState::GetViewState(WINDOW_MUSIC_NAV, 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());
+ }
+
+ SortDescription sortDesc;
+ if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_MUSIC_NAV)
+ sortDesc = state->GetSortMethod();
+ else
+ sortDesc = GetSortDescription(*state, items);
+
+ if (sortDesc.sortBy == SortByLabel)
+ items.ClearSortState();
+
+ items.Sort(sortDesc);
+ }
+
+ for (const auto& i : items)
+ {
+ 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() && !item->IsMusicDb())
+ {
+ // just queue the internet stream, it will be expanded on play
+ m_queuedItems.Add(item);
+ }
+ else if (item->IsPlugin() && item->GetProperty("isplayable").asBoolean())
+ {
+ // python files can be played
+ m_queuedItems.Add(item);
+ }
+ else if (!item->IsNFO() && (item->IsAudio() || item->IsVideo()))
+ {
+ const auto itemCheck = m_queuedItems.Get(item->GetPath());
+ if (!itemCheck || itemCheck->GetStartOffset() != item->GetStartOffset())
+ {
+ // add item
+ m_musicDatabase.SetPropertiesForFileItem(*item);
+ m_queuedItems.Add(item);
+ }
+ }
+ }
+}
+
+void ShowToastNotification(const CFileItem& item, int titleId)
+{
+ std::string localizedMediaType;
+ std::string title;
+
+ if (item.HasMusicInfoTag())
+ {
+ localizedMediaType = CMediaTypes::GetCapitalLocalization(item.GetMusicInfoTag()->GetType());
+ title = item.GetMusicInfoTag()->GetTitle();
+ }
+
+ if (title.empty())
+ title = item.GetLabel();
+ if (title.empty())
+ return; // no meaningful toast possible.
+
+ const std::string message =
+ localizedMediaType.empty() ? title : localizedMediaType + ": " + title;
+
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(titleId),
+ message);
+}
+} // unnamed namespace
+
+namespace MUSIC_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)
+ {
+ // build a playlist and play it
+ CFileItemList queuedItems;
+ GetItemsForPlayList(item, queuedItems);
+
+ auto& player = CServiceBroker::GetPlaylistPlayer();
+ player.ClearPlaylist(PLAYLIST::TYPE_MUSIC);
+ player.Reset();
+ player.Add(PLAYLIST::TYPE_MUSIC, queuedItems);
+ player.SetCurrentPlaylist(PLAYLIST::TYPE_MUSIC);
+ player.Play();
+ }
+ else if (item->HasMusicInfoTag())
+ {
+ // song, so just 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();
+
+ PLAYLIST::Id playlistId = player.GetCurrentPlaylist();
+ if (playlistId == PLAYLIST::TYPE_NONE)
+ {
+ const auto& components = CServiceBroker::GetAppComponents();
+ playlistId = components.GetComponent<CApplicationPlayer>()->GetPreferredPlaylist();
+ }
+
+ if (playlistId == PLAYLIST::TYPE_NONE)
+ playlistId = PLAYLIST::TYPE_MUSIC;
+
+ // Check for the partymode playlist item, do nothing when "PartyMode.xsp" not exists
+ if (item->IsSmartPlayList() && !CFileUtils::Exists(item->GetPath()))
+ {
+ const auto profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
+ if (item->GetPath() == profileManager->GetUserDataItem("PartyMode.xsp"))
+ return;
+ }
+
+ const int oldSize = player.GetPlaylist(playlistId).size();
+
+ CFileItemList queuedItems;
+ GetItemsForPlayList(item, queuedItems);
+
+ // if party mode, add items but DONT start playing
+ if (g_partyModeManager.IsEnabled())
+ {
+ g_partyModeManager.AddUserSongs(queuedItems, false);
+ return;
+ }
+
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+
+ if (pos == QueuePosition::POSITION_BEGIN && appPlayer->IsPlaying())
+ player.Insert(playlistId, queuedItems,
+ CServiceBroker::GetPlaylistPlayer().GetCurrentSong() + 1);
+ else
+ player.Add(playlistId, queuedItems);
+
+ bool playbackStarted = false;
+
+ if (!appPlayer->IsPlaying() && player.GetPlaylist(playlistId).size())
+ {
+ const int winID = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow();
+ if (winID == WINDOW_MUSIC_NAV)
+ {
+ CGUIViewState* viewState = CGUIViewState::GetViewState(winID, queuedItems);
+ if (viewState)
+ viewState->SetPlaylistDirectory("playlistmusic://");
+ }
+
+ player.Reset();
+ player.SetCurrentPlaylist(playlistId);
+ player.Play(oldSize, ""); // start playing at the first new item
+
+ playbackStarted = true;
+ }
+
+ if (!playbackStarted)
+ {
+ if (pos == QueuePosition::POSITION_END)
+ ShowToastNotification(*item, 38082); // Added to end of playlist
+ else
+ ShowToastNotification(*item, 38083); // Added to playlist to play next
+ }
+}
+
+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)
+{
+ // Exclude all parent folders
+ if (item.IsParentFolder())
+ return false;
+
+ // Exclude all video library items
+ if (item.IsVideoDb() || StringUtils::StartsWithNoCase(item.GetPath(), "library://video/"))
+ return false;
+
+ // Exclude other components
+ if (item.IsPVR() || item.IsPlugin() || item.IsScript() || item.IsAddonsPath())
+ return false;
+
+ // Exclude special items
+ if (StringUtils::StartsWithNoCase(item.GetPath(), "newsmartplaylist://") ||
+ StringUtils::StartsWithNoCase(item.GetPath(), "newplaylist://"))
+ return false;
+
+ // Exclude unwanted windows
+ if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_MUSIC_PLAYLIST)
+ return false;
+
+ // Include playlists located at one of the possible music playlist locations
+ if (item.IsPlayList())
+ {
+ if (StringUtils::StartsWithNoCase(item.GetMimeType(), "audio/"))
+ return true;
+
+ if (StringUtils::StartsWithNoCase(item.GetPath(), "special://musicplaylists/") ||
+ StringUtils::StartsWithNoCase(item.GetPath(), "special://profile/playlists/music/"))
+ 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("{}/music/", path)))
+ return true;
+
+ if (!item.m_bIsFolder)
+ {
+ // Unknown location. Type cannot be determined for non-folder items.
+ return false;
+ }
+ }
+
+ if (item.m_bIsFolder &&
+ (item.IsMusicDb() || StringUtils::StartsWithNoCase(item.GetPath(), "library://music/")))
+ {
+ // Exclude top level nodes - eg can't play 'genres' just a specific genre etc
+ const XFILE::MUSICDATABASEDIRECTORY::NODE_TYPE node =
+ XFILE::CMusicDatabaseDirectory::GetDirectoryParentType(item.GetPath());
+ if (node == XFILE::MUSICDATABASEDIRECTORY::NODE_TYPE_OVERVIEW)
+ return false;
+
+ return true;
+ }
+
+ if (item.HasMusicInfoTag() && item.CanQueue())
+ return true;
+ else if (!item.m_bIsFolder && item.IsAudio())
+ return true;
+ else if (item.m_bIsFolder)
+ {
+ // Not a music-specific folder (just file:// or nfs://). Allow play if context is Music window.
+ if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_MUSIC_NAV &&
+ item.GetPath() != "add") // Exclude "Add music source" item
+ return true;
+ }
+ return false;
+}
+
+} // namespace MUSIC_UTILS