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