aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKai Sommerfeld <kai.sommerfeld@gmx.com>2022-10-27 08:28:42 +0200
committerGitHub <noreply@github.com>2022-10-27 08:28:42 +0200
commitff728b8f7246ee3911c2abf9cc352d0758ae0d2b (patch)
tree5d55f8e9dd56d70ec4032f6165c900c3f3889800
parente4ac5d9e7f5272c3d3292ae8005aac85fc44b491 (diff)
parente93ff2d1c2d91d057963e601aaa565ef60d8b8cf (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
-rw-r--r--addons/resource.language.en_gb/resources/strings.po32
-rw-r--r--addons/skin.estuary/language/resource.language.en_gb/strings.po8
-rw-r--r--addons/skin.estuary/xml/DialogMusicInfo.xml21
-rw-r--r--addons/skin.estuary/xml/Home.xml10
-rw-r--r--addons/skin.estuary/xml/Includes_Home.xml2
-rw-r--r--addons/skin.estuary/xml/SkinSettings.xml7
-rw-r--r--addons/skin.estuary/xml/Variables.xml14
-rw-r--r--xbmc/ContextMenuManager.cpp4
-rw-r--r--xbmc/interfaces/builtins/PlayerBuiltins.cpp151
-rw-r--r--xbmc/music/ContextMenus.cpp59
-rw-r--r--xbmc/music/ContextMenus.h32
-rw-r--r--xbmc/music/MusicUtils.cpp907
-rw-r--r--xbmc/music/MusicUtils.h77
-rw-r--r--xbmc/music/dialogs/GUIDialogMusicInfo.cpp32
-rw-r--r--xbmc/music/dialogs/GUIDialogMusicInfo.h1
-rw-r--r--xbmc/music/dialogs/GUIDialogSongInfo.cpp13
-rw-r--r--xbmc/music/dialogs/GUIDialogSongInfo.h3
-rw-r--r--xbmc/music/windows/GUIWindowMusicBase.cpp197
-rw-r--r--xbmc/music/windows/GUIWindowMusicBase.h3
-rw-r--r--xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp18
-rw-r--r--xbmc/music/windows/GUIWindowMusicPlaylistEditor.h3
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();