diff options
author | Kai Sommerfeld <kai.sommerfeld@gmx.com> | 2022-10-27 08:28:42 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-27 08:28:42 +0200 |
commit | ff728b8f7246ee3911c2abf9cc352d0758ae0d2b (patch) | |
tree | 5d55f8e9dd56d70ec4032f6165c900c3f3889800 | |
parent | e4ac5d9e7f5272c3d3292ae8005aac85fc44b491 (diff) | |
parent | e93ff2d1c2d91d057963e601aaa565ef60d8b8cf (diff) |
Merge pull request #22048 from ksooo/music-enhancements
[music][Estuary] Add ability to play albums directly from Estuary home screen, extend context menu functionality
21 files changed, 1060 insertions, 534 deletions
diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 47195b45fa..a33bc81d88 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -949,8 +949,12 @@ msgstr "" #. generic "play" (some sort of media) label used in different places #: addons/skin.estuary/xml/DialogVideoInfo.xml +#: addons/skin.estuary/xml/SkinSettings.xml +#: addons/skin.estuary/xml/Variables.xml #: xbmc/dialogs/GUIDialogPlayEject.cpp #: xbmc/games/windows/GUIWindowGames.cpp +#: xbmc/music/ContextMenus.h +#: xbmc/music/MusicUtils.cpp #: xbmc/video/ContextMenus.cpp #: xbmc/video/windows/GUIWindowVideoBase.cpp #: system/settings/settings.xml @@ -4907,7 +4911,9 @@ msgctxt "#10007" msgid "System information" msgstr "" -#: xbmc/music/windows/GUIWindowMusicBase.cpp +#: addons/skin.estuary/xml/SkinSettings.xml +#: addons/skin.estuary/xml/Variables.xml +#: xbmc/music/ContextMenus.h #: xbmc/video/ContextMenus.cpp #: xbmc/video/windows/GUIWindowVideoBase.cpp msgctxt "#10008" @@ -6918,7 +6924,10 @@ msgctxt "#13346" msgid "Movie information" msgstr "" +#: addons/skin.estuary/xml/SkinSettings.xml +#: addons/skin.estuary/xml/Variables.xml #: system/settings/settings.xml +#: xbmc/music/ContextMenus.h #: xbmc/video/ContextMenus.cpp #: xbmc/video/windows/GUIWindowVideoBase.cpp msgctxt "#13347" @@ -21847,7 +21856,10 @@ msgctxt "#37014" msgid "Last used profile" msgstr "" -#: xbmc/Windows/GUIMediaWindow.cpp +#: addons/skin.estuary/xml/SkinSettings.xml +#: addons/skin.estuary/xml/Variables.xml +#: xbmc/music/ContextMenus.h +#: xbmc/windows/GUIMediaWindow.cpp msgctxt "#37015" msgid "Browse into" msgstr "" @@ -22360,8 +22372,20 @@ msgctxt "#38081" msgid "Release status" msgstr "" -#empty strings from id 38082 to 38099 -#strings 38082 to 38099 reserved for music library +#. Displayed in a toast notification when music items are added to a playlist +#: xbmc/music/MusicUtils.cpp +msgctxt "#38082" +msgid "Added to end of playlist" +msgstr "" + +#. Displayed in a toast notification when music items are added to a playlist to play next +#: xbmc/music/MusicUtils.cpp +msgctxt "#38083" +msgid "Added to playlist to play next" +msgstr "" + +#empty strings from id 38084 to 38099 +#strings 38084 to 38099 reserved for music library #. Description of section #14200 "Player"" #: system/settings/settings.xml diff --git a/addons/skin.estuary/language/resource.language.en_gb/strings.po b/addons/skin.estuary/language/resource.language.en_gb/strings.po index 3ae40652cd..1692a96166 100644 --- a/addons/skin.estuary/language/resource.language.en_gb/strings.po +++ b/addons/skin.estuary/language/resource.language.en_gb/strings.po @@ -838,7 +838,13 @@ msgctxt "#31173" msgid "Video OSD autoclose time (seconds)" msgstr "" -#empty strings from id 31174 to 31599 +#: /xml/SkinSettings.xml +#. Setting to control what happens when clicking a music album on the home screen +msgctxt "#31174" +msgid "Default select action for albums on the home screen" +msgstr "" + +#empty strings from id 31175 to 31599 #: /xml/DialogPlayerProcessInfo.xml #. Label to show the video codec name diff --git a/addons/skin.estuary/xml/DialogMusicInfo.xml b/addons/skin.estuary/xml/DialogMusicInfo.xml index 12daea9f39..b4dd30ff03 100644 --- a/addons/skin.estuary/xml/DialogMusicInfo.xml +++ b/addons/skin.estuary/xml/DialogMusicInfo.xml @@ -360,22 +360,6 @@ <align>center</align> <itemgap>-15</itemgap> <orientation>horizontal</orientation> - <control type="radiobutton" id="155"> - <include content="VideoInfoButtonsCommon"> - <param name="icon" value="icons/infodialogs/play.png" /> - </include> - <label>$LOCALIZE[208]</label> - <onup>130</onup> - <onleft>12</onleft> - <onright>120</onright> - <onclick condition="String.IsEmpty(ListItem.DBID)">PlayMedia($INFO[ListItem.FilenameAndPath])</onclick> - <onclick condition="System.AddonIsEnabled(script.playalbum) + String.IsEqual(ListItem.DBType,album)">RunScript(script.playalbum,albumid=$INFO[ListItem.DBID])</onclick> - <onclick condition="System.AddonIsEnabled(script.playalbum) + String.IsEqual(ListItem.DBType,song)">RunScript(script.playalbum,songid=$INFO[ListItem.DBID])</onclick> - <onclick condition="System.AddonIsEnabled(script.playalbum)">Action(close)</onclick> - <onclick condition="System.HasAddon(script.playalbum) + !System.AddonIsEnabled(script.playalbum) + !String.IsEmpty(ListItem.DBID)">EnableAddon(script.playalbum)</onclick> - <onclick condition="!System.HasAddon(script.playalbum) + !String.IsEmpty(ListItem.DBID)">InstallAddon(script.playalbum)</onclick> - <visible>!String.IsEqual(ListItem.DBType,artist)</visible> - </control> <control type="group" id="420"> <width>264</width> <visible>String.IsEqual(ListItem.DBType,album) | String.IsEqual(ListItem.DBType,song)</visible> @@ -419,6 +403,11 @@ <visible>String.IsEqual(ListItem.DBType,artist) | String.IsEqual(ListItem.DBType,album)</visible> </control> <include content="InfoDialogButton"> + <param name="id" value="8" /> + <param name="icon" value="icons/infodialogs/play.png" /> + <param name="label" value="$LOCALIZE[208]" /> + </include> + <include content="InfoDialogButton"> <param name="id" value="6" /> <param name="icon" value="icons/infodialogs/update.png" /> <param name="label" value="$LOCALIZE[184]" /> diff --git a/addons/skin.estuary/xml/Home.xml b/addons/skin.estuary/xml/Home.xml index 17e6567d01..0abdb8fbf3 100644 --- a/addons/skin.estuary/xml/Home.xml +++ b/addons/skin.estuary/xml/Home.xml @@ -192,6 +192,8 @@ <param name="widget_target" value="music"/> <param name="list_id" value="7100"/> <param name="fallback_icon" value="DefaultMusicAlbums.png"/> + <param name="onclick_condition" value="true"/> + <param name="onclick_action" value="$VAR[AlbumOnClickActionVar]"/> </include> <include content="WidgetListSquare" condition="Library.HasContent(music)"> <param name="content_path" value="musicdb://recentlyaddedalbums/"/> @@ -199,6 +201,8 @@ <param name="widget_target" value="music"/> <param name="list_id" value="7200"/> <param name="fallback_icon" value="DefaultMusicAlbums.png"/> + <param name="onclick_condition" value="true"/> + <param name="onclick_action" value="$VAR[AlbumOnClickActionVar]"/> </include> <include content="WidgetListSquare" condition="Library.HasContent(music)"> <param name="content_path" value="special://skin/playlists/random_albums.xsp"/> @@ -206,6 +210,8 @@ <param name="widget_target" value="music"/> <param name="list_id" value="7300"/> <param name="fallback_icon" value="DefaultMusicAlbums.png"/> + <param name="onclick_condition" value="true"/> + <param name="onclick_action" value="$VAR[AlbumOnClickActionVar]"/> </include> <include content="WidgetListSquare" condition="Library.HasContent(music)"> <param name="content_path" value="special://skin/playlists/random_artists.xsp"/> @@ -220,6 +226,8 @@ <param name="widget_target" value="music"/> <param name="list_id" value="7500"/> <param name="fallback_icon" value="DefaultMusicAlbums.png"/> + <param name="onclick_condition" value="true"/> + <param name="onclick_action" value="$VAR[AlbumOnClickActionVar]"/> </include> <include content="WidgetListSquare" condition="Library.HasContent(music)"> <param name="content_path" value="special://skin/playlists/mostplayed_albums.xsp"/> @@ -229,6 +237,8 @@ <param name="fallback_icon" value="DefaultMusicAlbums.png"/> <param name="sortby" value="playcount"/> <param name="sortorder" value="descending"/> + <param name="onclick_condition" value="true"/> + <param name="onclick_action" value="$VAR[AlbumOnClickActionVar]"/> </include> </control> <include content="ImageWidget" condition="!Library.HasContent(music)"> diff --git a/addons/skin.estuary/xml/Includes_Home.xml b/addons/skin.estuary/xml/Includes_Home.xml index 24c6ea5a0c..cde8a85e90 100644 --- a/addons/skin.estuary/xml/Includes_Home.xml +++ b/addons/skin.estuary/xml/Includes_Home.xml @@ -213,6 +213,7 @@ <param name="sortorder">ascending</param> <param name="widget_limit">15</param> <param name="fallback_icon">DefaultAudio.png</param> + <param name="onclick_condition">false</param> <definition> <include content="CategoryLabel"> <param name="label">$PARAM[widget_header]</param> @@ -229,6 +230,7 @@ <top>120</top> <right>0</right> <height>500</height> + <onclick condition="$PARAM[onclick_condition]">$PARAM[onclick_action]</onclick> <include content="WidgetListCommon"> <param name="list_id" value="$PARAM[list_id]"/> </include> diff --git a/addons/skin.estuary/xml/SkinSettings.xml b/addons/skin.estuary/xml/SkinSettings.xml index f1b620797d..800f240da2 100644 --- a/addons/skin.estuary/xml/SkinSettings.xml +++ b/addons/skin.estuary/xml/SkinSettings.xml @@ -195,6 +195,13 @@ <onclick condition="!System.HasAddon(plugin.library.node.editor)">InstallAddon(plugin.library.node.editor)</onclick> <enable>!Skin.HasSetting(HomeMenuNoMusicButton)</enable> </control> + <control type="button" id="625"> + <label>- $LOCALIZE[31174]</label> + <include>DefaultSettingButton</include> + <onclick>Skin.SelectBool(31174, 37015|album_onclick_browse, 208|album_onclick_play, 10008|album_onclick_playnext, 13347|album_onclick_queue)</onclick> + <label2>$VAR[AlbumOnClickActionLabel2Var]</label2> + <enable>!Skin.HasSetting(HomeMenuNoMusicButton)</enable> + </control> <control type="radiobutton" id="6131"> <label>$LOCALIZE[20389]</label> <include>DefaultSettingButton</include> diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 73a29b64c0..b28b170b34 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -180,6 +180,20 @@ <value condition="Skin.HasSetting(show_profileavatar)">$LOCALIZE[31166]</value> <value>$LOCALIZE[16018]</value> </variable> + <variable name="AlbumOnClickActionLabel2Var"> + <value condition="Skin.HasSetting(album_onclick_browse)">$LOCALIZE[37015]</value> + <value condition="Skin.HasSetting(album_onclick_play)">$LOCALIZE[208]</value> + <value condition="Skin.HasSetting(album_onclick_playnext)">$LOCALIZE[10008]</value> + <value condition="Skin.HasSetting(album_onclick_queue)">$LOCALIZE[13347]</value> + <value>$LOCALIZE[37015]</value> + </variable> + <variable name="AlbumOnClickActionVar"> + <value condition="Skin.HasSetting(album_onclick_browse)">ActivateWindow(music,musicdb://albums/$INFO[ListItem.DBID]/,return)</value> + <value condition="Skin.HasSetting(album_onclick_play)">PlayMedia(musicdb://albums/$INFO[ListItem.DBID]/)</value> + <value condition="Skin.HasSetting(album_onclick_playnext)">QueueMedia(musicdb://albums/$INFO[ListItem.DBID]/,playnext)</value> + <value condition="Skin.HasSetting(album_onclick_queue)">QueueMedia(musicdb://albums/$INFO[ListItem.DBID]/)</value> + <value>ActivateWindow(music,musicdb://albums/$INFO[ListItem.DBID]/,return)</value> + </variable> <variable name="AddonLifecycleType"> <value condition="String.IsEqual(ListItem.AddonLifecycleType,$LOCALIZE[24170])">[COLOR button_focus]$LOCALIZE[24170][/COLOR][CR]$INFO[ListItem.AddonLifecycleDesc]</value> <!-- Deprecated --> <value condition="String.IsEqual(ListItem.AddonLifecycleType,$LOCALIZE[24171])">[COLOR button_focus]$LOCALIZE[24171][/COLOR][CR]$INFO[ListItem.AddonLifecycleDesc]</value> <!-- Broken --> diff --git a/xbmc/ContextMenuManager.cpp b/xbmc/ContextMenuManager.cpp index 154f4c85c0..fedc700085 100644 --- a/xbmc/ContextMenuManager.cpp +++ b/xbmc/ContextMenuManager.cpp @@ -88,6 +88,10 @@ void CContextMenuManager::Init() std::make_shared<CONTEXTMENU::CRenameFavourite>(), std::make_shared<CONTEXTMENU::CRemoveFavourite>(), std::make_shared<CONTEXTMENU::CAddRemoveFavourite>(), + std::make_shared<CONTEXTMENU::CMusicBrowse>(), + std::make_shared<CONTEXTMENU::CMusicPlay>(), + std::make_shared<CONTEXTMENU::CMusicPlayNext>(), + std::make_shared<CONTEXTMENU::CMusicQueue>(), }; ReloadAddonItems(); diff --git a/xbmc/interfaces/builtins/PlayerBuiltins.cpp b/xbmc/interfaces/builtins/PlayerBuiltins.cpp index b3f60c3782..992b647957 100644 --- a/xbmc/interfaces/builtins/PlayerBuiltins.cpp +++ b/xbmc/interfaces/builtins/PlayerBuiltins.cpp @@ -22,6 +22,7 @@ #include "filesystem/Directory.h" #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" +#include "playlists/PlayList.h" #include "pvr/PVRManager.h" #include "pvr/channels/PVRChannel.h" #include "pvr/guilib/PVRGUIActionsChannels.h" @@ -420,23 +421,10 @@ static int PlayDVD(const std::vector<std::string>& params) return 0; } -/*! \brief Start playback of media. - * \param params The parameters. - * \details params[0] = URL to media to play (optional). - * params[1,...] = "isdir" if media is a directory (optional). - * params[1,...] = "1" to start playback in fullscreen (optional). - * params[1,...] = "resume" to force resuming (optional). - * params[1,...] = "noresume" to force not resuming (optional). - * params[1,...] = "playoffset=<offset>" to start playback from a given position in a playlist (optional). - * params[1,...] = "playlist_type_hint=<id>" to set the playlist type if a playlist file (e.g. STRM) is played (optional), - * for <id> value refer to PLAYLIST::TYPE_MUSIC / PLAYLIST::TYPE_VIDEO values, if not set will fallback to music playlist. - */ -static int PlayMedia(const std::vector<std::string>& params) +namespace +{ +int PlayOrQueueMedia(const std::vector<std::string>& params, bool forcePlay) { - CFileItem item(params[0], false); - if (URIUtils::HasSlashAtEnd(params[0])) - item.m_bIsFolder = true; - // restore to previous window if needed if( CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SLIDESHOW || CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO || @@ -450,9 +438,13 @@ static int PlayMedia(const std::vector<std::string>& params) appPower->ResetScreenSaver(); appPower->WakeUpScreenSaverAndDPMS(); + CFileItem item(params[0], URIUtils::HasSlashAtEnd(params[0], true)); + // ask if we need to check guisettings to resume bool askToResume = true; int playOffset = 0; + bool hasPlayOffset = false; + bool playNext = true; for (unsigned int i = 1 ; i < params.size() ; i++) { if (StringUtils::EqualsNoCase(params[i], "isdir")) @@ -470,9 +462,11 @@ static int PlayMedia(const std::vector<std::string>& params) // force the item to start at the beginning (m_lStartOffset is initialized to 0) askToResume = false; } - else if (StringUtils::StartsWithNoCase(params[i], "playoffset=")) { + else if (StringUtils::StartsWithNoCase(params[i], "playoffset=")) + { playOffset = atoi(params[i].substr(11).c_str()) - 1; item.SetProperty("playlist_starting_track", playOffset); + hasPlayOffset = true; } else if (StringUtils::StartsWithNoCase(params[i], "playlist_type_hint=")) { @@ -480,6 +474,11 @@ static int PlayMedia(const std::vector<std::string>& params) int playlistTypeHint = std::stoi(params[i].substr(19)); item.SetProperty("playlist_type_hint", playlistTypeHint); } + else if (StringUtils::EqualsNoCase(params[i], "playnext")) + { + // If app player is currently playing, the queued media shall be played next. + playNext = true; + } } if (!item.m_bIsFolder && item.IsPlugin()) @@ -490,14 +489,13 @@ static int PlayMedia(const std::vector<std::string>& params) if ( CGUIWindowVideoBase::ShowResumeMenu(item) == false ) return false; } + if (item.m_bIsFolder || item.IsPlayList()) { CFileItemList items; - std::string extensions = CServiceBroker::GetFileExtensionProvider().GetVideoExtensions() + "|" + CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(); - if (item.IsPlayList()) - CUtil::GetRecursiveListing(item.GetPath(), items, extensions, XFILE::DIR_FLAG_DEFAULTS); - else - XFILE::CDirectory::GetDirectory(item.GetPath(), items, extensions, XFILE::DIR_FLAG_DEFAULTS); + auto& provider = CServiceBroker::GetFileExtensionProvider(); + const std::string exts = provider.GetVideoExtensions() + "|" + provider.GetMusicExtensions(); + CUtil::GetRecursiveListing(item.GetPath(), items, exts, XFILE::DIR_FLAG_DEFAULTS); if (!items.IsEmpty()) // fall through on non expandable playlist { @@ -534,21 +532,93 @@ static int PlayMedia(const std::vector<std::string>& params) } } - CServiceBroker::GetPlaylistPlayer().ClearPlaylist(playlistId); - CServiceBroker::GetPlaylistPlayer().Add(playlistId, items); - CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(playlistId); - CServiceBroker::GetPlaylistPlayer().Play(playOffset, ""); + auto& playlistPlayer = CServiceBroker::GetPlaylistPlayer(); + + // Play vs. Queue (+Play) + if (forcePlay) + { + playlistPlayer.ClearPlaylist(playlistId); + playlistPlayer.Reset(); + playlistPlayer.Add(playlistId, items); + playlistPlayer.SetCurrentPlaylist(playlistId); + playlistPlayer.Play(playOffset, ""); + } + else + { + const int oldSize = playlistPlayer.GetPlaylist(playlistId).size(); + + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (playNext) + { + if (appPlayer->IsPlaying()) + playlistPlayer.Insert(playlistId, items, playlistPlayer.GetCurrentSong() + 1); + else + playlistPlayer.Add(playlistId, items); + } + else + { + playlistPlayer.Add(playlistId, items); + } + + if (items.Size() && !appPlayer->IsPlaying()) + { + playlistPlayer.SetCurrentPlaylist(playlistId); + playlistPlayer.Play(hasPlayOffset ? playOffset : oldSize, ""); + } + } + return 0; } } - if ((item.IsAudio() || item.IsVideo()) && !item.IsSmartPlayList()) - CServiceBroker::GetPlaylistPlayer().Play(std::make_shared<CFileItem>(item), ""); - else - g_application.PlayMedia(item, "", PLAYLIST::TYPE_NONE); + + if (forcePlay) + { + if ((item.IsAudio() || item.IsVideo()) && !item.IsSmartPlayList()) + CServiceBroker::GetPlaylistPlayer().Play(std::make_shared<CFileItem>(item), ""); + else + g_application.PlayMedia(item, "", PLAYLIST::TYPE_NONE); + } return 0; } +/*! \brief Start playback of media. + * \param params The parameters. + * \details params[0] = URL to media to play (optional). + * params[1,...] = "isdir" if media is a directory (optional). + * params[1,...] = "1" to start playback in fullscreen (optional). + * params[1,...] = "resume" to force resuming (optional). + * params[1,...] = "noresume" to force not resuming (optional). + * params[1,...] = "playoffset=<offset>" to start playback from a given position in a playlist (optional). + * params[1,...] = "playlist_type_hint=<id>" to set the playlist type if a playlist file (e.g. STRM) is played (optional), + * for <id> value refer to PLAYLIST::TYPE_MUSIC / PLAYLIST::TYPE_VIDEO values, if not set will fallback to music playlist. + */ +int PlayMedia(const std::vector<std::string>& params) +{ + return PlayOrQueueMedia(params, true); +} + +/*! \brief Queue media in the video or music playlist, according to type of media items. If both audio and video items are contained, queue to video + * playlist. Start playback at requested position if player is not playing. + * \param params The parameters. + * \details params[0] = URL of media to queue. + * params[1,...] = "isdir" if media is a directory (optional). + * params[1,...] = "1" to start playback in fullscreen (optional). + * params[1,...] = "resume" to force resuming (optional). + * params[1,...] = "noresume" to force not resuming (optional). + * params[1,...] = "playoffset=<offset>" to start playback from a given position in a playlist (optional). + * params[1,...] = "playlist_type_hint=<id>" to set the playlist type if a playlist file (e.g. STRM) is played (optional), + * for <id> value refer to PLAYLIST::TYPE_MUSIC / PLAYLIST::TYPE_VIDEO values, if not set will fallback to music playlist. + * params[1,...] = "playnext" if player is currently playing, to play the media right after the currently playing item. If player is not + * playing, append media to current playlist (optional). + */ +int QueueMedia(const std::vector<std::string>& params) +{ + return PlayOrQueueMedia(params, false); +} + +} // unnamed namespace + /*! \brief Start playback with a given playback core. * \param params The parameters. * \details params[0] = Name of playback core. @@ -699,6 +769,26 @@ static int SubtitleShiftDown(const std::vector<std::string>& params) /// playing media. A negative value will seek backward and a positive value forward. /// @param[in] seconds Number of seconds to seek. /// } +/// \table_row2_l{ +/// <b>`QueueMedia(media[\,isdir][\,1][\,playnext]\,[playoffset=xx])`</b> +/// \anchor Builtin_QueueMedia, +/// Queues the given media. This can be a playlist\, music\, or video file\, directory\, +/// plugin or an Url. The optional parameter "\,isdir" can be used for playing +/// a directory. "\,1" will start the media without switching to fullscreen. +/// If media is a playlist\, you can use playoffset=xx where xx is +/// the position to start playback from. +/// @param[in] media URL of media to queue. +/// @param[in] isdir Set "isdir" if media is a directory (optional). +/// @param[in] 1 Set "1" to start playback without switching to fullscreen (optional). +/// @param[in] resume Set "resume" to force resuming (optional). +/// @param[in] noresume Set "noresume" to force not resuming (optional). +/// @param[in] playeroffset Set "playoffset=<offset>" to start playback from a given position in a playlist (optional). +/// @param[in] playnext Set "playnext" to play the media right after the currently playing item, if player is currently +/// playing. If player is not playing, append media to current playlist (optional). +/// <p><hr> +/// @skinning_v20 **[New builtin]** \link Builtin_QueueMedia `QueueMedia(media[\,isdir][\,1][\,playnext]\,[playoffset=xx])`\endlink +/// <p> +/// } /// \table_end /// @@ -712,6 +802,7 @@ CBuiltins::CommandMap CPlayerBuiltins::GetOperations() const {"playlist.playoffset", {"Start playing from a particular offset in the playlist", 1, PlayOffset}}, {"playercontrol", {"Control the music or video player", 1, PlayerControl}}, {"playmedia", {"Play the specified media file (or playlist)", 1, PlayMedia}}, + {"queuemedia", {"Queue the specified media in video or music playlist", 1, QueueMedia}}, {"playwith", {"Play the selected item with the specified core", 1, PlayWith}}, {"seek", {"Performs a seek in seconds on the current playing media file", 1, Seek}}, {"subtitleshiftup", {"Shift up the subtitle position", 0, SubtitleShiftUp}}, diff --git a/xbmc/music/ContextMenus.cpp b/xbmc/music/ContextMenus.cpp index 00c85018cb..4d98d0121a 100644 --- a/xbmc/music/ContextMenus.cpp +++ b/xbmc/music/ContextMenus.cpp @@ -9,14 +9,17 @@ #include "ContextMenus.h" #include "FileItem.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" +#include "music/MusicUtils.h" #include "music/dialogs/GUIDialogMusicInfo.h" #include "tags/MusicInfoTag.h" #include <utility> -namespace CONTEXTMENU -{ +using namespace CONTEXTMENU; CMusicInfo::CMusicInfo(MediaType mediaType) : CStaticContextMenuAction(19033), m_mediaType(std::move(mediaType)) @@ -36,4 +39,56 @@ bool CMusicInfo::Execute(const std::shared_ptr<CFileItem>& item) const return true; } +bool CMusicBrowse::IsVisible(const CFileItem& item) const +{ + return item.m_bIsFolder && MUSIC_UTILS::IsItemPlayable(item); +} + +bool CMusicBrowse::Execute(const std::shared_ptr<CFileItem>& item) const +{ + auto& windowMgr = CServiceBroker::GetGUI()->GetWindowManager(); + if (windowMgr.GetActiveWindow() == WINDOW_MUSIC_NAV) + { + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, WINDOW_MUSIC_NAV, 0, GUI_MSG_UPDATE); + msg.SetStringParam(item->GetPath()); + windowMgr.SendMessage(msg); + } + else + { + windowMgr.ActivateWindow(WINDOW_MUSIC_NAV, {item->GetPath(), "return"}); + } + return true; +} + +bool CMusicPlay::IsVisible(const CFileItem& item) const +{ + return MUSIC_UTILS::IsItemPlayable(item); +} + +bool CMusicPlay::Execute(const std::shared_ptr<CFileItem>& item) const +{ + MUSIC_UTILS::PlayItem(item); + return true; +} + +bool CMusicPlayNext::IsVisible(const CFileItem& item) const +{ + return MUSIC_UTILS::IsItemPlayable(item); +} + +bool CMusicPlayNext::Execute(const std::shared_ptr<CFileItem>& item) const +{ + MUSIC_UTILS::QueueItem(item, MUSIC_UTILS::QueuePosition::POSITION_BEGIN); + return true; +} + +bool CMusicQueue::IsVisible(const CFileItem& item) const +{ + return MUSIC_UTILS::IsItemPlayable(item); +} + +bool CMusicQueue::Execute(const std::shared_ptr<CFileItem>& item) const +{ + MUSIC_UTILS::QueueItem(item, MUSIC_UTILS::QueuePosition::POSITION_END); + return true; } diff --git a/xbmc/music/ContextMenus.h b/xbmc/music/ContextMenus.h index 50ac00cf2b..73275923fc 100644 --- a/xbmc/music/ContextMenus.h +++ b/xbmc/music/ContextMenus.h @@ -13,6 +13,8 @@ #include <memory> +class CFileItem; + namespace CONTEXTMENU { @@ -41,4 +43,32 @@ struct CSongInfo : CMusicInfo CSongInfo() : CMusicInfo(MediaTypeSong) {} }; -} +struct CMusicBrowse : CStaticContextMenuAction +{ + CMusicBrowse() : CStaticContextMenuAction(37015) {} // Browse into + bool IsVisible(const CFileItem& item) const override; + bool Execute(const std::shared_ptr<CFileItem>& item) const override; +}; + +struct CMusicPlay : CStaticContextMenuAction +{ + CMusicPlay() : CStaticContextMenuAction(208) {} // Play + bool IsVisible(const CFileItem& item) const override; + bool Execute(const std::shared_ptr<CFileItem>& item) const override; +}; + +struct CMusicPlayNext : CStaticContextMenuAction +{ + CMusicPlayNext() : CStaticContextMenuAction(10008) {} // Play next + bool IsVisible(const CFileItem& item) const override; + bool Execute(const std::shared_ptr<CFileItem>& item) const override; +}; + +struct CMusicQueue : CStaticContextMenuAction +{ + CMusicQueue() : CStaticContextMenuAction(13347) {} // Queue item + bool IsVisible(const CFileItem& item) const override; + bool Execute(const std::shared_ptr<CFileItem>& item) const override; +}; + +} // namespace CONTEXTMENU diff --git a/xbmc/music/MusicUtils.cpp b/xbmc/music/MusicUtils.cpp index feabc19849..86f9f104dc 100644 --- a/xbmc/music/MusicUtils.cpp +++ b/xbmc/music/MusicUtils.cpp @@ -9,384 +9,737 @@ #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 +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) { - 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) + } + + ~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) { - 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")) { - // 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 artistid property when we have it + for (CVariant::const_iterator_array varid = + pSongItem->GetProperty("artistid").begin_array(); + varid != pSongItem->GetProperty("artistid").end_array(); ++varid) { - // Check song album artists - result = db.IsSongAlbumArtist(idSong, itemID); + int idArtist = static_cast<int>(varid->asInteger()); + result = (itemID == idArtist); + if (result) + break; } } - return result; + 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); + // 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. + /* 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); - } + bool clearcache(false); + const PLAYLIST::CPlayList& playlist = + CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::TYPE_MUSIC); - // 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()) + for (int i = 0; i < playlist.size(); ++i) + { + CFileItemPtr songitem = playlist[i]; + if (HasSongExtraArtChanged(songitem, type, itemID, db)) { - CFileItemPtr songitem = CFileItemPtr(new CFileItem(g_application.CurrentFileItem())); - if (HasSongExtraArtChanged(songitem, type, itemID, db)) - g_application.UpdateCurrentPlayArt(); + songitem->ClearArt(); // Art gets reloaded when the current playist is shown + clearcache = true; } - - 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 + if (clearcache) { - // 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(); - } + // Clear the music playlist from cache + CFileItemList items("playlistmusic://"); + items.RemoveDiscCache(WINDOW_MUSIC_PLAYLIST); + } - return true; + // 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(); } - }; - void UpdateArtJob(const std::shared_ptr<CFileItem>& pItem, - const std::string& strType, - const std::string& strArt) + 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) { - // 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) + CSetSongRatingJob(int songId, int userrating) : strPath(), idSong(songId), iUserrating(userrating) { - 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) + ~CSetSongRatingJob(void) override = default; + + bool DoWork(void) override { - std::map<std::string, std::string> currentArt; - db.GetArtForItem(tag.GetDatabaseId(), tag.GetType(), currentArt); - for (const auto& art : currentArt) + // Asynchronously update song userrating in library + CMusicDatabase db; + if (db.Open()) { - if (!art.second.empty() && find(artTypes.begin(), artTypes.end(), art.first) == artTypes.end()) - artTypes.push_back(art.first); + if (idSong > 0) + db.SetSongUserrating(idSong, iUserrating); + else + db.SetSongUserrating(strPath, iUserrating); + db.Close(); } + + return true; } +}; - // 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) +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())) { - 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); - } + 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) +// 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) { - for (const auto& artType : db.GetAvailableArtTypesForItem(tag.GetDatabaseId(), tag.GetType())) - { - if (find(artTypes.begin(), artTypes.end(), artType) == artTypes.end()) - artTypes.push_back(artType); - } + if (!art.second.empty() && find(artTypes.begin(), artTypes.end(), art.first) == artTypes.end()) + artTypes.push_back(art.first); } +} - bool FillArtTypesList(CFileItem& musicitem, CFileItemList& artlist) +// 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) { - 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; + if (find(artTypes.begin(), artTypes.end(), artType) == artTypes.end()) + artTypes.push_back(artType); + } +} - artlist.Clear(); +// 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); + } +} - CMusicDatabase db; - db.Open(); +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; - std::vector<std::string> artTypes; + artlist.Clear(); - AddHardCodedAndExtendedArtTypes(artTypes, tag); - AddCurrentArtTypes(artTypes, tag, db); - AddMediaTypeArtTypes(artTypes, tag, db); - AddAvailableArtTypes(artTypes, tag, db); + CMusicDatabase db; + db.Open(); - db.Close(); + std::vector<std::string> artTypes; - 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); - } + AddHardCodedAndExtendedArtTypes(artTypes, tag); + AddCurrentArtTypes(artTypes, tag, db); + AddMediaTypeArtTypes(artTypes, tag, db); + AddAvailableArtTypes(artTypes, tag, db); + + db.Close(); - return !artlist.IsEmpty(); + 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); } - std::string ShowSelectArtTypeDialog(CFileItemList& artitems) + 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()) { - // Prompt for choice - CGUIDialogSelect *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); - if (!dialog) + // 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); - dialog->SetHeading(CVariant{ 13521 }); - dialog->Reset(); - dialog->SetUseDetails(true); - dialog->EnableButton(true, 13516); + return strArtTypeName; + } + + return dialog->GetSelectedFileItem()->GetProperty("arttype").asString(); +} - dialog->SetItems(artitems); +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(); - if (dialog->IsButtonPressed()) + 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)) { - // 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; + 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 - return dialog->GetSelectedFileItem()->GetProperty("arttype").asString(); +namespace +{ +class CAsyncGetItemsForPlaylist : public IRunnable +{ +public: + CAsyncGetItemsForPlaylist(const std::shared_ptr<CFileItem>& item, CFileItemList& queuedItems) + : m_item(item), m_queuedItems(queuedItems) + { } - int ShowSelectRatingDialog(int iSelected) + ~CAsyncGetItemsForPlaylist() override = default; + + void Run() override { - CGUIDialogSelect *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); - if (dialog) + // 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; +}; + +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())) { - 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; + // 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; } - return -1; } - void UpdateSongRatingJob(const std::shared_ptr<CFileItem>& pItem, int userrating) + if (item->m_bIsFolder) { - // 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); - } + // 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()); + } + + if (items.GetSortMethod() == SortByLabel) + items.ClearSortState(); - std::vector<std::string> GetArtTypesToScan(const MediaType& mediaType) + items.Sort(state->GetSortMethod()); + } + + for (const auto& i : items) + { + GetItemsForPlaylist(i); + } + } + else { - std::vector<std::string> arttypes; - // Get default types of art that are to be automatically fetched during scanning - if (mediaType == MediaTypeArtist) + if (item->IsPlayList()) { - arttypes = { "thumb", "fanart" }; - for (auto& artType : CServiceBroker::GetSettingsComponent()->GetSettings()->GetList( - CSettings::SETTING_MUSICLIBRARY_ARTISTART_WHITELIST)) + const std::unique_ptr<PLAYLIST::CPlayList> playList( + PLAYLIST::CPlayListFactory::Create(*item)); + if (!playList) { - if (find(arttypes.begin(), arttypes.end(), artType.asString()) == arttypes.end()) - arttypes.emplace_back(artType.asString()); + 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 (mediaType == MediaTypeAlbum) + else if (item->IsPlugin() && item->GetProperty("isplayable").asBoolean()) { - arttypes = { "thumb" }; - for (auto& artType : - CServiceBroker::GetSettingsComponent()->GetSettings()->GetList( - CSettings::SETTING_MUSICLIBRARY_ALBUMART_WHITELIST)) + // 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()) { - if (find(arttypes.begin(), arttypes.end(), artType.asString()) == arttypes.end()) - arttypes.emplace_back(artType.asString()); + // add item + m_musicDatabase.SetPropertiesForFileItem(*item); + m_queuedItems.Add(item); } } - return arttypes; } +} + +void ShowToastNotification(const CFileItem& item, int titleId) +{ + const std::string localizedMediaType = + CMediaTypes::GetCapitalLocalization(item.GetMusicInfoTag()->GetType()); + + std::string title = item.GetMusicInfoTag()->GetTitle(); + if (title.empty()) + title = item.GetLabel(); + + const std::string message = + localizedMediaType.empty() ? title : localizedMediaType + ": " + title; - bool IsValidArtType(const std::string& potentialArtType) + 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()) { - // Check length and is ascii - return potentialArtType.length() <= 25 && - std::find_if_not(potentialArtType.begin(), potentialArtType.end(), - StringUtils::isasciialphanum) == potentialArtType.end(); + // make a copy to not alter the original item + item = std::make_shared<CFileItem>(*itemIn); + item->SetCanQueue(true); } - } // namespace MUSIC_UTILS + 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; + + // Check for right window + const int winID = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); + if (winID != WINDOW_MUSIC_NAV && winID != WINDOW_HOME) + return false; + + if (item.m_bIsFolder) + { + // 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; + } + + if (item.HasMusicInfoTag() && item.CanQueue() && !item.IsParentFolder()) + return true; + else if (item.IsPlayList() && item.IsAudio()) + return true; + else if (!item.m_bIsShareOrDrive && item.m_bIsFolder && !item.IsParentFolder()) + return true; + + return false; +} + +} // namespace MUSIC_UTILS diff --git a/xbmc/music/MusicUtils.h b/xbmc/music/MusicUtils.h index 7d99cda2f4..c9b6f94052 100644 --- a/xbmc/music/MusicUtils.h +++ b/xbmc/music/MusicUtils.h @@ -19,7 +19,7 @@ class CFileItemList; namespace MUSIC_UTILS { - /*! \brief Show a dialog to allow the selection of type of art from a list. +/*! \brief Show a dialog to allow the selection of type of art from a list. Input is a fileitem list, with each item having an "arttype" property e.g. "thumb", current art URL (if art exists), and label. One of these art types can be selected, or a new art type added. The new art type is added as a new item @@ -28,9 +28,9 @@ namespace MUSIC_UTILS \return the selected art type e.g. "fanart" or empty string when cancelled. \sa FillArtTypesList */ - std::string ShowSelectArtTypeDialog(CFileItemList& artitems); +std::string ShowSelectArtTypeDialog(CFileItemList& artitems); - /*! \brief Helper function to build a list of art types for a music library item. +/*! \brief Helper function to build a list of art types for a music library item. This fetches the possible types of art for a song, album or artist, and the current art URL (if the item has art of that type), for display on a dialog. \param musicitem a music CFileItem (song, album or artist) @@ -39,9 +39,9 @@ namespace MUSIC_UTILS \return true if art types are retrieved, false if none is found. \sa ShowSelectArtTypeDialog */ - bool FillArtTypesList(CFileItem& musicitem, CFileItemList& artlist); +bool FillArtTypesList(CFileItem& musicitem, CFileItemList& artlist); - /*! \brief Helper function to asynchronously update art in the music database +/*! \brief Helper function to asynchronously update art in the music database and then refresh the album & artist art of the currently playing song. For the song, album or artist this adds a job to the queue to update the art table modifying, adding or deleting that type of art. Changes to album or artist art are @@ -50,34 +50,71 @@ namespace MUSIC_UTILS \param strType the type of art e.g. "fanart" or "thumb" etc. \param strArt art URL, when empty the entry for that type of art is deleted. */ - void UpdateArtJob(const std::shared_ptr<CFileItem>& pItem, - const std::string& strType, - const std::string& strArt); +void UpdateArtJob(const std::shared_ptr<CFileItem>& pItem, + const std::string& strType, + const std::string& strArt); - /*! \brief Show a dialog to allow the selection of user rating. +/*! \brief Show a dialog to allow the selection of user rating. \param iSelected the rating to show initially \return the selected rating, 0 (no rating), 1 to 10 or -1 no rating selected */ - int ShowSelectRatingDialog(int iSelected); +int ShowSelectRatingDialog(int iSelected); /*! \brief Helper function to asynchronously update the user rating of a song -\param pItem pointer to song item being rated -\param userrating the userrating 0 = no rating, 1 to 10 -*/ - void UpdateSongRatingJob(const std::shared_ptr<CFileItem>& pItem, int userrating); + \param pItem pointer to song item being rated + \param userrating the userrating 0 = no rating, 1 to 10 + */ +void UpdateSongRatingJob(const std::shared_ptr<CFileItem>& pItem, int userrating); - /*! \brief Get the types of art for an artist or album that are to be +/*! \brief Get the types of art for an artist or album that are to be automatically fetched from local files during scanning \param mediaType [in] artist or album \return vector of art types that are to be fetched during scanning */ - std::vector<std::string> GetArtTypesToScan(const MediaType& mediaType); +std::vector<std::string> GetArtTypesToScan(const MediaType& mediaType); - /*! \brief Validate string is acceptable as the name of an additional art type +/*! \brief Validate string is acceptable as the name of an additional art type - limited length, and ascii alphanumberic characters only \param potentialArtType [in] potential art type name - \return true if the art type is valid + \return true if the art type is valid + */ +bool IsValidArtType(const std::string& potentialArtType); + +/*! \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 music + item, start playback directly, without adding it to the music 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 none is active, put the + item into the music playlist. Start playback of the playlist, if player is not already playing. + \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 songs. + \param item The item to check + \return True if playable, false otherwise. */ - bool IsValidArtType(const std::string& potentialArtType); +bool IsItemPlayable(const CFileItem& item); - } // namespace MUSIC_UTILS +} // namespace MUSIC_UTILS diff --git a/xbmc/music/dialogs/GUIDialogMusicInfo.cpp b/xbmc/music/dialogs/GUIDialogMusicInfo.cpp index 6800322605..a4b282a563 100644 --- a/xbmc/music/dialogs/GUIDialogMusicInfo.cpp +++ b/xbmc/music/dialogs/GUIDialogMusicInfo.cpp @@ -50,6 +50,7 @@ using namespace KODI::MESSAGING; #define CONTROL_BTN_REFRESH 6 #define CONTROL_USERRATING 7 +#define CONTROL_BTN_PLAY 8 #define CONTROL_BTN_GET_THUMB 10 #define CONTROL_ARTISTINFO 12 @@ -407,6 +408,29 @@ bool CGUIDialogMusicInfo::OnMessage(CGUIMessage& message) } } } + else if (iControl == CONTROL_BTN_PLAY) + { + if (m_album.idAlbum >= 0) + { + // Play album + const std::string path = StringUtils::Format("musicdb://albums/{}", m_album.idAlbum); + OnPlayItem(std::make_shared<CFileItem>(path, m_album)); + return true; + } + else + { + CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), iControl); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + const int iItem = msg.GetParam1(); + if (iItem >= 0 && iItem < m_albumSongs->Size()) + { + // Play selected song + OnPlayItem(m_albumSongs->Get(iItem)); + return true; + } + } + return false; + } } break; } @@ -552,11 +576,13 @@ void CGUIDialogMusicInfo::OnInitWindow() SET_CONTROL_LABEL(CONTROL_USERRATING, 38023); SET_CONTROL_LABEL(CONTROL_BTN_GET_THUMB, 13511); SET_CONTROL_LABEL(CONTROL_ARTISTINFO, 21891); + SET_CONTROL_LABEL(CONTROL_BTN_PLAY, 208); if (m_bArtistInfo) { SET_CONTROL_HIDDEN(CONTROL_ARTISTINFO); SET_CONTROL_HIDDEN(CONTROL_USERRATING); + SET_CONTROL_HIDDEN(CONTROL_BTN_PLAY); } CGUIDialog::OnInitWindow(); } @@ -1009,3 +1035,9 @@ void CGUIDialogMusicInfo::ShowFor(CFileItem* pItem) } } } + +void CGUIDialogMusicInfo::OnPlayItem(const std::shared_ptr<CFileItem>& item) +{ + Close(true); + MUSIC_UTILS::PlayItem(item); +} diff --git a/xbmc/music/dialogs/GUIDialogMusicInfo.h b/xbmc/music/dialogs/GUIDialogMusicInfo.h index 761b1d6316..781a7d1bcb 100644 --- a/xbmc/music/dialogs/GUIDialogMusicInfo.h +++ b/xbmc/music/dialogs/GUIDialogMusicInfo.h @@ -62,6 +62,7 @@ protected: void OnArtistInfo(int id); void OnSetUserrating() const; void SetUserrating(int userrating) const; + void OnPlayItem(const std::shared_ptr<CFileItem>& item); CAlbum m_album; CArtist m_artist; diff --git a/xbmc/music/dialogs/GUIDialogSongInfo.cpp b/xbmc/music/dialogs/GUIDialogSongInfo.cpp index 61a6a098be..8efca94ab4 100644 --- a/xbmc/music/dialogs/GUIDialogSongInfo.cpp +++ b/xbmc/music/dialogs/GUIDialogSongInfo.cpp @@ -32,6 +32,7 @@ #define CONTROL_BTN_REFRESH 6 #define CONTROL_USERRATING 7 +#define CONTROL_BTN_PLAY 8 #define CONTROL_BTN_GET_THUMB 10 #define CONTROL_ALBUMINFO 12 @@ -183,6 +184,12 @@ bool CGUIDialogSongInfo::OnMessage(CGUIMessage& message) return true; } } + else if (iControl == CONTROL_BTN_PLAY) + { + OnPlaySong(m_song); + return true; + } + return false; } break; } @@ -245,6 +252,7 @@ void CGUIDialogSongInfo::OnInitWindow() SET_CONTROL_LABEL(CONTROL_USERRATING, 38023); SET_CONTROL_LABEL(CONTROL_BTN_GET_THUMB, 13511); SET_CONTROL_LABEL(CONTROL_ALBUMINFO, 10523); + SET_CONTROL_LABEL(CONTROL_BTN_PLAY, 208); CGUIDialog::OnInitWindow(); } @@ -506,5 +514,10 @@ void CGUIDialogSongInfo::ShowFor(CFileItem* pItem) } } } +} +void CGUIDialogSongInfo::OnPlaySong(const std::shared_ptr<CFileItem>& item) +{ + Close(true); + MUSIC_UTILS::PlayItem(item); } diff --git a/xbmc/music/dialogs/GUIDialogSongInfo.h b/xbmc/music/dialogs/GUIDialogSongInfo.h index 6c6a552a8f..274f1acc0f 100644 --- a/xbmc/music/dialogs/GUIDialogSongInfo.h +++ b/xbmc/music/dialogs/GUIDialogSongInfo.h @@ -12,6 +12,8 @@ #include "guilib/GUIDialog.h" #include "threads/Event.h" +#include <memory> + class CGUIDialogSongInfo : public CGUIDialog { @@ -39,6 +41,7 @@ protected: void OnGetArt(); void SetUserrating(int userrating); void OnSetUserrating(); + void OnPlaySong(const std::shared_ptr<CFileItem>& item); CFileItemPtr m_song; CFileItemList m_artTypeList; diff --git a/xbmc/music/windows/GUIWindowMusicBase.cpp b/xbmc/music/windows/GUIWindowMusicBase.cpp index e6a4dc586f..49fa03a8c9 100644 --- a/xbmc/music/windows/GUIWindowMusicBase.cpp +++ b/xbmc/music/windows/GUIWindowMusicBase.cpp @@ -20,6 +20,7 @@ #include "input/actions/ActionIDs.h" #include "music/MusicDbUrl.h" #include "music/MusicLibraryQueue.h" +#include "music/MusicUtils.h" #include "music/dialogs/GUIDialogInfoProviderSettings.h" #include "music/dialogs/GUIDialogMusicInfo.h" #include "playlists/PlayList.h" @@ -383,163 +384,21 @@ void CGUIWindowMusicBase::RetrieveMusicInfo() /// \param iItem Selected Item in list/thumb control void CGUIWindowMusicBase::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_MUSIC; - // don't re-queue items from playlist window - if ( iItem < 0 || iItem >= m_vecItems->Size() || GetID() == WINDOW_MUSIC_PLAYLIST) return ; - - int iOldSize = CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).size(); + if (iItem < 0 || iItem >= m_vecItems->Size() || GetID() == WINDOW_MUSIC_PLAYLIST) + return; - // add item 2 playlist (make a copy as we alter the queuing state) - CFileItemPtr item(new CFileItem(*m_vecItems->Get(iItem))); + // add item 2 playlist + const auto item = m_vecItems->Get(iItem); if (item->IsRAR() || item->IsZIP()) return; - // Check for the partymode playlist item, do nothing when "PartyMode.xsp" not exist - if (item->IsSmartPlayList()) - { - const std::shared_ptr<CProfileManager> profileManager = - CServiceBroker::GetSettingsComponent()->GetProfileManager(); - if ((item->GetPath() == profileManager->GetUserDataItem("PartyMode.xsp")) && - !CFileUtils::Exists(item->GetPath())) - return; - } - - // Allow queuing of unqueueable items - // when we try to queue them directly - if (!item->CanQueue()) - item->SetCanQueue(true); - - CLog::Log(LOGDEBUG, "Adding file {}{} to music playlist", item->GetPath(), - item->m_bIsFolder ? " (folder) " : ""); - CFileItemList queuedItems; - AddItemToPlayList(item, queuedItems); + MUSIC_UTILS::QueueItem(item, first ? MUSIC_UTILS::QueuePosition::POSITION_BEGIN + : MUSIC_UTILS::QueuePosition::POSITION_END); // select next item m_viewControl.SetSelectedItem(iItem + 1); - - // if party mode, add items but DONT start playing - if (g_partyModeManager.IsEnabled()) - { - g_partyModeManager.AddUserSongs(queuedItems, false); - return; - } - - if (first && appPlayer->IsPlaying()) - CServiceBroker::GetPlaylistPlayer().Insert( - playlistId, queuedItems, CServiceBroker::GetPlaylistPlayer().GetCurrentSong() + 1); - else - CServiceBroker::GetPlaylistPlayer().Add(playlistId, queuedItems); - if (CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).size() && !appPlayer->IsPlaying()) - { - if (m_guiState) - m_guiState->SetPlaylistDirectory("playlistmusic://"); - - CServiceBroker::GetPlaylistPlayer().Reset(); - CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(playlistId); - CServiceBroker::GetPlaylistPlayer().Play(iOldSize, ""); // start playing at the first new item - } -} - -/// \brief Add unique file and folders and its subfolders to playlist -/// \param pItem The file item to add -void CGUIWindowMusicBase::AddItemToPlayList(const CFileItemPtr &pItem, CFileItemList &queuedItems) -{ - if (!pItem->CanQueue() || pItem->IsRAR() || pItem->IsZIP() || pItem->IsParentFolder()) // no zip/rar enqueues thank you! - return; - - // fast lookup is needed here - queuedItems.SetFastLookup(true); - - if (pItem->IsMusicDb() && pItem->m_bIsFolder && !pItem->IsParentFolder()) - { // we have a music database folder, just grab the "all" item underneath it - CMusicDatabaseDirectory dir; - if (!dir.ContainsSongs(pItem->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(pItem->GetPath())) - { - musicUrl.AppendPath("-1/"); - CFileItemPtr item(new CFileItem(musicUrl.ToString(), true)); - item->SetCanQueue(true); // workaround for CanQueue() check above - AddItemToPlayList(item, queuedItems); - } - return; - } - } - if (pItem->m_bIsFolder) - { - // Check if we add a locked share - if ( pItem->m_bIsShareOrDrive ) - { - CFileItem item = *pItem; - if ( !g_passwordManager.IsItemUnlocked( &item, "music" ) ) - return ; - } - - // recursive - CFileItemList items; - GetDirectory(pItem->GetPath(), items); - //OnRetrieveMusicInfo(items); - FormatAndSort(items); - for (int i = 0; i < items.Size(); ++i) - AddItemToPlayList(items[i], queuedItems); - } - else - { - 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() && !pItem->IsMusicDb()) - { // just queue the internet stream, it will be expanded on play - queuedItems.Add(pItem); - } - else if (pItem->IsPlugin() && pItem->GetProperty("isplayable").asBoolean()) - { - // python files can be played - queuedItems.Add(pItem); - } - else if (!pItem->IsNFO() && (pItem->IsAudio() || pItem->IsVideo())) - { - CFileItemPtr itemCheck = queuedItems.Get(pItem->GetPath()); - if (!itemCheck || itemCheck->GetStartOffset() != pItem->GetStartOffset()) - { // add item - CFileItemPtr item(new CFileItem(*pItem)); - m_musicdatabase.SetPropertiesForFileItem(*item); - queuedItems.Add(item); - } - } - } } void CGUIWindowMusicBase::UpdateButtons() @@ -583,18 +442,11 @@ void CGUIWindowMusicBase::GetContextButtons(int itemNumber, CContextButtons &but //! @todo get rid of IsAddonsPath and IsScript check. CanQueue should be enough! if (item->CanQueue() && !item->IsAddonsPath() && !item->IsScript()) { - buttons.Add(CONTEXT_BUTTON_QUEUE_ITEM, 13347); //queue - buttons.Add(CONTEXT_BUTTON_PLAY_NEXT, 10008); //play next - - // 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)) + if (!item->m_bIsFolder && + (!item->IsPlayList() || + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders)) { - buttons.Add(CONTEXT_BUTTON_PLAY_ITEM, 208); // Play - } - else - { - const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + const CPlayerCoreFactory& playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); // check what players we have, if we have multiple display play with option std::vector<std::string> players; @@ -602,10 +454,9 @@ void CGUIWindowMusicBase::GetContextButtons(int itemNumber, CContextButtons &but if (players.size() >= 1) buttons.Add(CONTEXT_BUTTON_PLAY_WITH, 15213); // Play With... } + if (item->IsSmartPlayList()) - { - buttons.Add(CONTEXT_BUTTON_PLAY_PARTYMODE, 15216); // Play in Partymode - } + buttons.Add(CONTEXT_BUTTON_PLAY_PARTYMODE, 15216); // Play in Partymode if (item->IsSmartPlayList() || m_vecItems->IsSmartPlayList()) buttons.Add(CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, 586); @@ -655,19 +506,11 @@ bool CGUIWindowMusicBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button) switch (button) { - case CONTEXT_BUTTON_QUEUE_ITEM: - OnQueueItem(itemNumber); - return true; - - case CONTEXT_BUTTON_PLAY_NEXT: - OnQueueItem(itemNumber, true); - return true; - - case CONTEXT_BUTTON_INFO: - OnItemInfo(itemNumber); - return true; + case CONTEXT_BUTTON_INFO: + OnItemInfo(itemNumber); + return true; - case CONTEXT_BUTTON_EDIT: + case CONTEXT_BUTTON_EDIT: { std::string playlist = item->IsPlayList() ? item->GetPath() : m_vecItems->GetPath(); // save path as activatewindow will destroy our items CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST_EDITOR, playlist); @@ -684,10 +527,6 @@ bool CGUIWindowMusicBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button) return true; } - case CONTEXT_BUTTON_PLAY_ITEM: - PlayItem(itemNumber); - return true; - case CONTEXT_BUTTON_PLAY_WITH: { const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); @@ -815,7 +654,7 @@ void CGUIWindowMusicBase::PlayItem(int iItem) return; CFileItemList queuedItems; - AddItemToPlayList(item, queuedItems); + MUSIC_UTILS::GetItemsForPlayList(item, queuedItems); if (g_partyModeManager.IsEnabled()) { g_partyModeManager.AddUserSongs(queuedItems, true); diff --git a/xbmc/music/windows/GUIWindowMusicBase.h b/xbmc/music/windows/GUIWindowMusicBase.h index f91ee4cd15..85f49312c6 100644 --- a/xbmc/music/windows/GUIWindowMusicBase.h +++ b/xbmc/music/windows/GUIWindowMusicBase.h @@ -70,8 +70,7 @@ protected: bool GetDirectory(const std::string &strDirectory, CFileItemList &items) override; virtual void OnRetrieveMusicInfo(CFileItemList& items); - void OnPrepareFileItems(CFileItemList &items) override; - void AddItemToPlayList(const CFileItemPtr &pItem, CFileItemList &queuedItems); + void OnPrepareFileItems(CFileItemList& items) override; void OnRipCD(); std::string GetStartFolder(const std::string &dir) override; void OnItemLoaded(CFileItem* pItem) override {} diff --git a/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp index 63fe95fc91..393923d326 100644 --- a/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp +++ b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp @@ -19,6 +19,7 @@ #include "guilib/GUIKeyboardFactory.h" #include "guilib/LocalizeStrings.h" #include "input/Key.h" +#include "music/MusicUtils.h" #include "playlists/PlayListM3U.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" @@ -256,7 +257,7 @@ void CGUIWindowMusicPlaylistEditor::OnQueueItem(int iItem, bool) // and thus want a different layout for each item CFileItemPtr item(new CFileItem(*m_vecItems->Get(iItem))); CFileItemList newItems; - AddItemToPlayList(item, newItems); + MUSIC_UTILS::GetItemsForPlayList(item, newItems); AppendToPlaylist(newItems); } @@ -432,3 +433,18 @@ void CGUIWindowMusicPlaylistEditor::OnPlaylistContext() else if (btnid == CONTEXT_BUTTON_DELETE) OnDeletePlaylistItem(item); } + +bool CGUIWindowMusicPlaylistEditor::OnContextButton(int itemNumber, CONTEXT_BUTTON button) +{ + switch (button) + { + case CONTEXT_BUTTON_QUEUE_ITEM: + OnQueueItem(itemNumber); + return true; + + default: + break; + } + + return CGUIWindowMusicBase::OnContextButton(itemNumber, button); +} diff --git a/xbmc/music/windows/GUIWindowMusicPlaylistEditor.h b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.h index 3db741e527..3859b556e8 100644 --- a/xbmc/music/windows/GUIWindowMusicPlaylistEditor.h +++ b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.h @@ -28,7 +28,8 @@ protected: void UpdateButtons() override; bool Update(const std::string &strDirectory, bool updateFilterPath = true) override; void OnPrepareFileItems(CFileItemList &items) override; - void OnQueueItem(int iItem, bool) override; + bool OnContextButton(int itemNumber, CONTEXT_BUTTON button) override; + void OnQueueItem(int iItem, bool first = false) override; std::string GetStartFolder(const std::string& dir) override { return ""; } void OnSourcesContext(); |