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 "profiles/ProfileManager.h"
38 #include "settings/Settings.h"
39 #include "settings/SettingsComponent.h"
40 #include "threads/IRunnable.h"
41 #include "utils/FileUtils.h"
42 #include "utils/JobManager.h"
43 #include "utils/StringUtils.h"
44 #include "utils/URIUtils.h"
45 #include "utils/log.h"
46 #include "video/VideoFileItemClassify.h"
47 #include "view/GUIViewState.h"
52 using namespace KODI::VIDEO
;
53 using namespace MUSIC_INFO
;
54 using namespace XFILE
;
55 using namespace std::chrono_literals
;
59 class CSetArtJob
: public CJob
62 std::string m_artType
;
66 CSetArtJob(const CFileItemPtr
& item
, const std::string
& type
, const std::string
& newArt
)
67 : pItem(item
), m_artType(type
), m_newArt(newArt
)
71 ~CSetArtJob(void) override
= default;
73 bool HasSongExtraArtChanged(const CFileItemPtr
& pSongItem
,
74 const std::string
& type
,
78 if (!pSongItem
->HasMusicInfoTag())
80 int idSong
= pSongItem
->GetMusicInfoTag()->GetDatabaseId();
84 if (type
== MediaTypeAlbum
)
85 // Update art when song is from album
86 result
= (itemID
== pSongItem
->GetMusicInfoTag()->GetAlbumId());
87 else if (type
== MediaTypeArtist
)
89 // Update art when artist is song or album artist of the song
90 if (pSongItem
->HasProperty("artistid"))
92 // Check artistid property when we have it
93 for (CVariant::const_iterator_array varid
=
94 pSongItem
->GetProperty("artistid").begin_array();
95 varid
!= pSongItem
->GetProperty("artistid").end_array(); ++varid
)
97 int idArtist
= static_cast<int>(varid
->asInteger());
98 result
= (itemID
== idArtist
);
104 { // Check song artists in database
105 result
= db
.IsSongArtist(idSong
, itemID
);
109 // Check song album artists
110 result
= db
.IsSongAlbumArtist(idSong
, itemID
);
116 // Asynchronously update song, album or artist art in library
117 // and trigger update to album & artist art of the currently playing song
118 // and songs queued in the current playlist
119 bool DoWork(void) override
121 int itemID
= pItem
->GetMusicInfoTag()->GetDatabaseId();
124 std::string type
= pItem
->GetMusicInfoTag()->GetType();
128 if (!m_newArt
.empty())
129 db
.SetArtForItem(itemID
, type
, m_artType
, m_newArt
);
131 db
.RemoveArtForItem(itemID
, type
, m_artType
);
132 // Artwork changed so set datemodified field for artist, album or song
133 db
.SetItemUpdated(itemID
, type
);
135 /* Update the art of the songs of the current music playlist.
136 Song thumb is often a fallback from the album and fanart is from the artist(s).
137 Clear the art if it is a song from the album or by the artist
138 (as song or album artist) that has modified artwork. The new artwork gets
139 loaded when the playlist is shown.
141 bool clearcache(false);
142 const PLAYLIST::CPlayList
& playlist
=
143 CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::Id::TYPE_MUSIC
);
145 for (int i
= 0; i
< playlist
.size(); ++i
)
147 CFileItemPtr songitem
= playlist
[i
];
148 if (HasSongExtraArtChanged(songitem
, type
, itemID
, db
))
150 songitem
->ClearArt(); // Art gets reloaded when the current playist is shown
156 // Clear the music playlist from cache
157 CFileItemList
items("playlistmusic://");
158 items
.RemoveDiscCache(WINDOW_MUSIC_PLAYLIST
);
161 // Similarly update the art of the currently playing song so it shows on OSD
162 const auto& components
= CServiceBroker::GetAppComponents();
163 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
164 if (appPlayer
->IsPlayingAudio() && g_application
.CurrentFileItem().HasMusicInfoTag())
166 CFileItemPtr songitem
= std::make_shared
<CFileItem
>(g_application
.CurrentFileItem());
167 if (HasSongExtraArtChanged(songitem
, type
, itemID
, db
))
168 g_application
.UpdateCurrentPlayArt();
176 class CSetSongRatingJob
: public CJob
183 CSetSongRatingJob(const std::string
& filePath
, int userrating
)
184 : strPath(filePath
), idSong(-1), iUserrating(userrating
)
188 CSetSongRatingJob(int songId
, int userrating
) : strPath(), idSong(songId
), iUserrating(userrating
)
192 ~CSetSongRatingJob(void) override
= default;
194 bool DoWork(void) override
196 // Asynchronously update song userrating in library
201 db
.SetSongUserrating(idSong
, iUserrating
);
203 db
.SetSongUserrating(strPath
, iUserrating
);
211 void UpdateArtJob(const std::shared_ptr
<CFileItem
>& pItem
,
212 const std::string
& strType
,
213 const std::string
& strArt
)
215 // Asynchronously update that type of art in the database
216 CSetArtJob
* job
= new CSetArtJob(pItem
, strType
, strArt
);
217 CServiceBroker::GetJobManager()->AddJob(job
, nullptr);
220 // Add art types required in Kodi and configured by the user
221 void AddHardCodedAndExtendedArtTypes(std::vector
<std::string
>& artTypes
, const CMusicInfoTag
& tag
)
223 for (const auto& artType
: GetArtTypesToScan(tag
.GetType()))
225 if (find(artTypes
.begin(), artTypes
.end(), artType
) == artTypes
.end())
226 artTypes
.push_back(artType
);
230 // Add art types currently assigned to the media item
231 void AddCurrentArtTypes(std::vector
<std::string
>& artTypes
,
232 const CMusicInfoTag
& tag
,
235 std::map
<std::string
, std::string
> currentArt
;
236 db
.GetArtForItem(tag
.GetDatabaseId(), tag
.GetType(), currentArt
);
237 for (const auto& art
: currentArt
)
239 if (!art
.second
.empty() && find(artTypes
.begin(), artTypes
.end(), art
.first
) == artTypes
.end())
240 artTypes
.push_back(art
.first
);
244 // Add art types that exist for other media items of the same type
245 void AddMediaTypeArtTypes(std::vector
<std::string
>& artTypes
,
246 const CMusicInfoTag
& tag
,
249 std::vector
<std::string
> dbArtTypes
;
250 db
.GetArtTypes(tag
.GetType(), dbArtTypes
);
251 for (const auto& artType
: dbArtTypes
)
253 if (find(artTypes
.begin(), artTypes
.end(), artType
) == artTypes
.end())
254 artTypes
.push_back(artType
);
258 // Add art types from available but unassigned artwork for this media item
259 void AddAvailableArtTypes(std::vector
<std::string
>& artTypes
,
260 const CMusicInfoTag
& tag
,
263 for (const auto& artType
: db
.GetAvailableArtTypesForItem(tag
.GetDatabaseId(), tag
.GetType()))
265 if (find(artTypes
.begin(), artTypes
.end(), artType
) == artTypes
.end())
266 artTypes
.push_back(artType
);
270 bool FillArtTypesList(CFileItem
& musicitem
, CFileItemList
& artlist
)
272 const CMusicInfoTag
& tag
= *musicitem
.GetMusicInfoTag();
273 if (tag
.GetDatabaseId() < 1 || tag
.GetType().empty())
275 if (tag
.GetType() != MediaTypeArtist
&& tag
.GetType() != MediaTypeAlbum
&&
276 tag
.GetType() != MediaTypeSong
)
284 std::vector
<std::string
> artTypes
;
286 AddHardCodedAndExtendedArtTypes(artTypes
, tag
);
287 AddCurrentArtTypes(artTypes
, tag
, db
);
288 AddMediaTypeArtTypes(artTypes
, tag
, db
);
289 AddAvailableArtTypes(artTypes
, tag
, db
);
293 for (const auto& type
: artTypes
)
295 CFileItemPtr
artitem(new CFileItem(type
, false));
296 // Localise the names of common types of art
297 if (type
== "banner")
298 artitem
->SetLabel(g_localizeStrings
.Get(20020));
299 else if (type
== "fanart")
300 artitem
->SetLabel(g_localizeStrings
.Get(20445));
301 else if (type
== "poster")
302 artitem
->SetLabel(g_localizeStrings
.Get(20021));
303 else if (type
== "thumb")
304 artitem
->SetLabel(g_localizeStrings
.Get(21371));
306 artitem
->SetLabel(type
);
307 // Set art type as art item property
308 artitem
->SetProperty("arttype", type
);
309 // Set current art as art item thumb
310 if (musicitem
.HasArt(type
))
311 artitem
->SetArt("thumb", musicitem
.GetArt(type
));
312 artlist
.Add(artitem
);
315 return !artlist
.IsEmpty();
318 std::string
ShowSelectArtTypeDialog(CFileItemList
& artitems
)
321 CGUIDialogSelect
* dialog
=
322 CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogSelect
>(
323 WINDOW_DIALOG_SELECT
);
327 dialog
->SetHeading(CVariant
{13521});
329 dialog
->SetUseDetails(true);
330 dialog
->EnableButton(true, 13516);
332 dialog
->SetItems(artitems
);
335 if (dialog
->IsButtonPressed())
337 // Get the new art type name
338 std::string strArtTypeName
;
339 if (!CGUIKeyboardFactory::ShowAndGetInput(strArtTypeName
,
340 CVariant
{g_localizeStrings
.Get(13516)}, false))
342 // Add new type to the list of art types
343 CFileItemPtr
artitem(new CFileItem(strArtTypeName
, false));
344 artitem
->SetLabel(strArtTypeName
);
345 artitem
->SetProperty("arttype", strArtTypeName
);
346 artitems
.Add(artitem
);
348 return strArtTypeName
;
351 return dialog
->GetSelectedFileItem()->GetProperty("arttype").asString();
354 int ShowSelectRatingDialog(int iSelected
)
356 CGUIDialogSelect
* dialog
=
357 CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogSelect
>(
358 WINDOW_DIALOG_SELECT
);
361 dialog
->SetHeading(CVariant
{38023});
362 dialog
->Add(g_localizeStrings
.Get(38022));
363 for (int i
= 1; i
<= 10; i
++)
364 dialog
->Add(StringUtils::Format("{}: {}", g_localizeStrings
.Get(563), i
));
365 dialog
->SetSelected(iSelected
);
368 int userrating
= dialog
->GetSelectedItem();
369 userrating
= std::max(userrating
, -1);
370 userrating
= std::min(userrating
, 10);
376 void UpdateSongRatingJob(const std::shared_ptr
<CFileItem
>& pItem
, int userrating
)
378 // Asynchronously update the song user rating in music library
379 const CMusicInfoTag
* tag
= pItem
->GetMusicInfoTag();
380 CSetSongRatingJob
* job
;
381 if (tag
&& tag
->GetType() == MediaTypeSong
&& tag
->GetDatabaseId() > 0)
382 // Use song ID when known
383 job
= new CSetSongRatingJob(tag
->GetDatabaseId(), userrating
);
385 job
= new CSetSongRatingJob(pItem
->GetPath(), userrating
);
386 CServiceBroker::GetJobManager()->AddJob(job
, nullptr);
389 std::vector
<std::string
> GetArtTypesToScan(const MediaType
& mediaType
)
391 std::vector
<std::string
> arttypes
;
392 // Get default types of art that are to be automatically fetched during scanning
393 if (mediaType
== MediaTypeArtist
)
395 arttypes
= {"thumb", "fanart"};
396 for (auto& artType
: CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(
397 CSettings::SETTING_MUSICLIBRARY_ARTISTART_WHITELIST
))
399 if (find(arttypes
.begin(), arttypes
.end(), artType
.asString()) == arttypes
.end())
400 arttypes
.emplace_back(artType
.asString());
403 else if (mediaType
== MediaTypeAlbum
)
405 arttypes
= {"thumb"};
406 for (auto& artType
: CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(
407 CSettings::SETTING_MUSICLIBRARY_ALBUMART_WHITELIST
))
409 if (find(arttypes
.begin(), arttypes
.end(), artType
.asString()) == arttypes
.end())
410 arttypes
.emplace_back(artType
.asString());
416 bool IsValidArtType(const std::string
& potentialArtType
)
418 // Check length and is ascii
419 return potentialArtType
.length() <= 25 &&
420 std::find_if_not(potentialArtType
.begin(), potentialArtType
.end(),
421 StringUtils::isasciialphanum
) == potentialArtType
.end();
424 } // namespace MUSIC_UTILS
428 class CAsyncGetItemsForPlaylist
: public IRunnable
431 CAsyncGetItemsForPlaylist(const std::shared_ptr
<CFileItem
>& item
, CFileItemList
& queuedItems
)
432 : m_item(item
), m_queuedItems(queuedItems
)
436 ~CAsyncGetItemsForPlaylist() override
= default;
440 // fast lookup is needed here
441 m_queuedItems
.SetFastLookup(true);
443 m_musicDatabase
.Open();
444 GetItemsForPlaylist(m_item
);
445 m_musicDatabase
.Close();
449 void GetItemsForPlaylist(const std::shared_ptr
<CFileItem
>& item
);
451 const std::shared_ptr
<CFileItem
> m_item
;
452 CFileItemList
& m_queuedItems
;
453 CMusicDatabase m_musicDatabase
;
456 SortDescription
GetSortDescription(const CGUIViewState
& state
, const CFileItemList
& items
)
458 SortDescription sortDescTrackNumber
;
460 auto sortDescriptions
= state
.GetSortDescriptions();
461 for (auto& sortDescription
: sortDescriptions
)
463 if (sortDescription
.sortBy
== SortByTrackNumber
)
465 // check whether at least one item has actually a track number set
466 for (const auto& item
: items
)
468 if (item
->HasMusicInfoTag() && item
->GetMusicInfoTag()->GetTrackNumber() > 0)
470 // First choice for folders containing a single album
471 sortDescTrackNumber
= sortDescription
;
472 sortDescTrackNumber
.sortOrder
= SortOrderAscending
;
473 break; // leave items loop. we can still find ByArtistThenYear. so, no return here.
477 else if (sortDescription
.sortBy
== SortByArtistThenYear
)
479 // check whether songs from at least two different albums are in the list
480 int lastAlbumId
= -1;
481 for (const auto& item
: items
)
483 if (item
->HasMusicInfoTag())
485 const auto tag
= item
->GetMusicInfoTag();
486 if (lastAlbumId
!= -1 && tag
->GetAlbumId() != lastAlbumId
)
488 // First choice for folders containing multiple albums
489 sortDescription
.sortOrder
= SortOrderAscending
;
490 return sortDescription
;
492 lastAlbumId
= tag
->GetAlbumId();
498 if (sortDescTrackNumber
.sortBy
!= SortByNone
)
499 return sortDescTrackNumber
;
501 return state
.GetSortMethod(); // last resort
504 void CAsyncGetItemsForPlaylist::GetItemsForPlaylist(const std::shared_ptr
<CFileItem
>& item
)
506 if (item
->IsParentFolder() || !item
->CanQueue() || item
->IsRAR() || item
->IsZIP())
509 if (MUSIC::IsMusicDb(*item
) && item
->m_bIsFolder
&& !item
->IsParentFolder())
511 // we have a music database folder, just grab the "all" item underneath it
512 XFILE::CMusicDatabaseDirectory dir
;
514 if (!dir
.ContainsSongs(item
->GetPath()))
516 // grab the ALL item in this category
517 // Genres will still require 2 lookups, and queuing the entire Genre folder
518 // will require 3 lookups (genre, artist, album)
519 CMusicDbUrl musicUrl
;
520 if (musicUrl
.FromString(item
->GetPath()))
522 musicUrl
.AppendPath("-1/");
524 const auto allItem
= std::make_shared
<CFileItem
>(musicUrl
.ToString(), true);
525 allItem
->SetCanQueue(true); // workaround for CanQueue() check above
526 GetItemsForPlaylist(allItem
);
532 if (item
->m_bIsFolder
)
534 // Check if we add a locked share
535 if (item
->m_bIsShareOrDrive
)
537 if (!g_passwordManager
.IsItemUnlocked(item
.get(), "music"))
542 XFILE::CDirectory::GetDirectory(item
->GetPath(), items
, "", XFILE::DIR_FLAG_DEFAULTS
);
544 const std::unique_ptr
<CGUIViewState
> state(
545 CGUIViewState::GetViewState(WINDOW_MUSIC_NAV
, items
));
548 LABEL_MASKS labelMasks
;
549 state
->GetSortMethodLabelMasks(labelMasks
);
551 const CLabelFormatter
fileFormatter(labelMasks
.m_strLabelFile
, labelMasks
.m_strLabel2File
);
552 const CLabelFormatter
folderFormatter(labelMasks
.m_strLabelFolder
,
553 labelMasks
.m_strLabel2Folder
);
554 for (const auto& i
: items
)
556 if (i
->IsLabelPreformatted())
560 folderFormatter
.FormatLabels(i
.get());
562 fileFormatter
.FormatLabels(i
.get());
565 SortDescription sortDesc
;
566 if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_MUSIC_NAV
)
567 sortDesc
= state
->GetSortMethod();
569 sortDesc
= GetSortDescription(*state
, items
);
571 if (sortDesc
.sortBy
== SortByLabel
)
572 items
.ClearSortState();
574 items
.Sort(sortDesc
);
577 for (const auto& i
: items
)
579 GetItemsForPlaylist(i
);
584 if (item
->IsPlayList())
586 const std::unique_ptr
<PLAYLIST::CPlayList
> playList(
587 PLAYLIST::CPlayListFactory::Create(*item
));
590 CLog::Log(LOGERROR
, "{} failed to create playlist {}", __FUNCTION__
, item
->GetPath());
594 if (!playList
->Load(item
->GetPath()))
596 CLog::Log(LOGERROR
, "{} failed to load playlist {}", __FUNCTION__
, item
->GetPath());
600 for (int i
= 0; i
< playList
->size(); ++i
)
602 GetItemsForPlaylist((*playList
)[i
]);
605 else if (NETWORK::IsInternetStream(*item
) && !MUSIC::IsMusicDb(*item
))
607 // just queue the internet stream, it will be expanded on play
608 m_queuedItems
.Add(item
);
610 else if (item
->IsPlugin() && item
->GetProperty("isplayable").asBoolean())
612 // python files can be played
613 m_queuedItems
.Add(item
);
615 else if (!item
->IsNFO() && (MUSIC::IsAudio(*item
) || IsVideo(*item
)))
617 const auto itemCheck
= m_queuedItems
.Get(item
->GetPath());
618 if (!itemCheck
|| itemCheck
->GetStartOffset() != item
->GetStartOffset())
621 m_musicDatabase
.SetPropertiesForFileItem(*item
);
622 m_queuedItems
.Add(item
);
628 void ShowToastNotification(const CFileItem
& item
, int titleId
)
630 std::string localizedMediaType
;
633 if (item
.HasMusicInfoTag())
635 localizedMediaType
= CMediaTypes::GetCapitalLocalization(item
.GetMusicInfoTag()->GetType());
636 title
= item
.GetMusicInfoTag()->GetTitle();
640 title
= item
.GetLabel();
642 return; // no meaningful toast possible.
644 const std::string message
=
645 localizedMediaType
.empty() ? title
: localizedMediaType
+ ": " + title
;
647 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info
, g_localizeStrings
.Get(titleId
),
651 std::string
GetMusicDbItemPath(const CFileItem
& item
)
653 std::string path
= item
.GetPath();
654 if (!URIUtils::IsMusicDb(path
))
655 path
= item
.GetProperty("original_listitem_url").asString();
657 if (URIUtils::IsMusicDb(path
))
663 void AddItemToPlayListAndPlay(const std::shared_ptr
<CFileItem
>& itemToQueue
,
664 const std::shared_ptr
<CFileItem
>& itemToPlay
,
665 const std::string
& player
)
667 // recursively add items to list
668 CFileItemList queuedItems
;
669 MUSIC_UTILS::GetItemsForPlayList(itemToQueue
, queuedItems
);
671 auto& playlistPlayer
= CServiceBroker::GetPlaylistPlayer();
672 playlistPlayer
.ClearPlaylist(PLAYLIST::Id::TYPE_MUSIC
);
673 playlistPlayer
.Reset();
674 playlistPlayer
.Add(PLAYLIST::Id::TYPE_MUSIC
, queuedItems
);
676 // figure out where to start playback
677 PLAYLIST::CPlayList
& playList
= playlistPlayer
.GetPlaylist(PLAYLIST::Id::TYPE_MUSIC
);
681 for (const std::shared_ptr
<CFileItem
>& queuedItem
: queuedItems
)
683 if (queuedItem
->IsSamePath(itemToPlay
.get()))
690 if (playlistPlayer
.IsShuffled(PLAYLIST::Id::TYPE_MUSIC
))
692 playList
.Swap(0, playList
.FindOrder(pos
));
696 playlistPlayer
.SetCurrentPlaylist(PLAYLIST::Id::TYPE_MUSIC
);
697 playlistPlayer
.Play(pos
, player
);
699 } // unnamed namespace
701 namespace MUSIC_UTILS
703 bool IsAutoPlayNextItem(const CFileItem
& item
)
705 if (!item
.HasMusicInfoTag())
708 const auto settings
= CServiceBroker::GetSettingsComponent()->GetSettings();
709 return settings
->GetBool(CSettings::SETTING_MUSICPLAYER_AUTOPLAYNEXTITEM
) &&
710 !settings
->GetBool(CSettings::SETTING_MUSICPLAYER_QUEUEBYDEFAULT
);
713 void PlayItem(const std::shared_ptr
<CFileItem
>& itemIn
,
714 const std::string
& player
,
715 ContentUtils::PlayMode mode
/* = ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM */)
719 // Allow queuing of unqueueable items
720 // when we try to queue them directly
721 if (!itemIn
->CanQueue())
723 // make a copy to not alter the original item
724 item
= std::make_shared
<CFileItem
>(*itemIn
);
725 item
->SetCanQueue(true);
728 if (item
->m_bIsFolder
)
730 AddItemToPlayListAndPlay(item
, nullptr, player
);
732 else if (item
->HasMusicInfoTag())
734 if (mode
== ContentUtils::PlayMode::PLAY_FROM_HERE
||
735 (mode
== ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM
&& IsAutoPlayNextItem(*item
)))
737 // Add item and all its siblings to the playlist and play. Prefer musicdb path if available,
738 // because it provides more information than just a plain file system path for example.
739 std::string parentPath
= item
->GetProperty("ParentPath").asString();
740 if (parentPath
.empty())
742 std::string path
= GetMusicDbItemPath(*item
);
744 path
= item
->GetPath();
746 URIUtils::GetParentPath(path
, parentPath
);
748 if (parentPath
.empty())
750 CLog::LogF(LOGERROR
, "Unable to obtain parent path for '{}'", item
->GetPath());
755 const auto parentItem
= std::make_shared
<CFileItem
>(parentPath
, true);
756 if (item
->GetStartOffset() == STARTOFFSET_RESUME
)
757 parentItem
->SetStartOffset(STARTOFFSET_RESUME
);
759 AddItemToPlayListAndPlay(parentItem
, item
, player
);
761 else // mode == PlayMode::PLAY_ONLY_THIS
763 // song, so just play it
764 auto& playlistPlayer
= CServiceBroker::GetPlaylistPlayer();
765 playlistPlayer
.Reset();
766 playlistPlayer
.SetCurrentPlaylist(PLAYLIST::Id::TYPE_NONE
);
767 playlistPlayer
.Play(item
, player
);
772 void QueueItem(const std::shared_ptr
<CFileItem
>& itemIn
, QueuePosition pos
)
776 // Allow queuing of unqueueable items
777 // when we try to queue them directly
778 if (!itemIn
->CanQueue())
780 // make a copy to not alter the original item
781 item
= std::make_shared
<CFileItem
>(*itemIn
);
782 item
->SetCanQueue(true);
785 auto& player
= CServiceBroker::GetPlaylistPlayer();
787 PLAYLIST::Id playlistId
= player
.GetCurrentPlaylist();
788 if (playlistId
== PLAYLIST::Id::TYPE_NONE
)
790 const auto& components
= CServiceBroker::GetAppComponents();
791 playlistId
= components
.GetComponent
<CApplicationPlayer
>()->GetPreferredPlaylist();
794 if (playlistId
== PLAYLIST::Id::TYPE_NONE
)
795 playlistId
= PLAYLIST::Id::TYPE_MUSIC
;
797 // Check for the partymode playlist item, do nothing when "PartyMode.xsp" not exists
798 if (item
->IsSmartPlayList() && !CFileUtils::Exists(item
->GetPath()))
800 const auto profileManager
= CServiceBroker::GetSettingsComponent()->GetProfileManager();
801 if (item
->GetPath() == profileManager
->GetUserDataItem("PartyMode.xsp"))
805 const int oldSize
= player
.GetPlaylist(playlistId
).size();
807 CFileItemList queuedItems
;
808 GetItemsForPlayList(item
, queuedItems
);
810 // if party mode, add items but DONT start playing
811 if (g_partyModeManager
.IsEnabled())
813 g_partyModeManager
.AddUserSongs(queuedItems
, false);
817 const auto& components
= CServiceBroker::GetAppComponents();
818 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
820 if (pos
== QueuePosition::POSITION_BEGIN
&& appPlayer
->IsPlaying())
821 player
.Insert(playlistId
, queuedItems
,
822 CServiceBroker::GetPlaylistPlayer().GetCurrentItemIdx() + 1);
824 player
.Add(playlistId
, queuedItems
);
826 bool playbackStarted
= false;
828 if (!appPlayer
->IsPlaying() && player
.GetPlaylist(playlistId
).size())
830 const int winID
= CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow();
831 if (winID
== WINDOW_MUSIC_NAV
)
833 CGUIViewState
* viewState
= CGUIViewState::GetViewState(winID
, queuedItems
);
835 viewState
->SetPlaylistDirectory("playlistmusic://");
839 player
.SetCurrentPlaylist(playlistId
);
840 player
.Play(oldSize
, ""); // start playing at the first new item
842 playbackStarted
= true;
845 if (!playbackStarted
)
847 if (pos
== QueuePosition::POSITION_END
)
848 ShowToastNotification(*item
, 38082); // Added to end of playlist
850 ShowToastNotification(*item
, 38083); // Added to playlist to play next
854 bool GetItemsForPlayList(const std::shared_ptr
<CFileItem
>& item
, CFileItemList
& queuedItems
)
856 CAsyncGetItemsForPlaylist
getItems(item
, queuedItems
);
857 return CGUIDialogBusy::Wait(&getItems
,
858 500, // 500ms before busy dialog appears
859 true); // can be cancelled
864 bool IsNonExistingUserPartyModePlaylist(const CFileItem
& item
)
866 if (!item
.IsSmartPlayList())
869 const std::string
& path
{item
.GetPath()};
870 const auto profileManager
{CServiceBroker::GetSettingsComponent()->GetProfileManager()};
871 return ((profileManager
->GetUserDataItem("PartyMode.xsp") == path
) && !CFileUtils::Exists(path
));
873 } // unnamed namespace
875 bool IsItemPlayable(const CFileItem
& item
)
877 // Exclude all parent folders
878 if (item
.IsParentFolder())
881 // Exclude all video library items
882 if (IsVideoDb(item
) || StringUtils::StartsWithNoCase(item
.GetPath(), "library://video/"))
885 // Exclude other components
886 if (item
.IsPVR() || item
.IsPlugin() || item
.IsScript() || item
.IsAddonsPath())
889 // Exclude special items
890 if (StringUtils::StartsWithNoCase(item
.GetPath(), "newsmartplaylist://") ||
891 StringUtils::StartsWithNoCase(item
.GetPath(), "newplaylist://"))
894 // Include playlists located at one of the possible music playlist locations
895 if (item
.IsPlayList())
897 if (StringUtils::StartsWithNoCase(item
.GetMimeType(), "audio/"))
900 if (StringUtils::StartsWithNoCase(item
.GetPath(), "special://musicplaylists/") ||
901 StringUtils::StartsWithNoCase(item
.GetPath(), "special://profile/playlists/music/"))
904 // Has user changed default playlists location and the list is located there?
905 const auto settings
= CServiceBroker::GetSettingsComponent()->GetSettings();
906 std::string path
= settings
->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH
);
907 StringUtils::TrimRight(path
, "/");
908 if (StringUtils::StartsWith(item
.GetPath(), StringUtils::Format("{}/music/", path
)))
911 if (!item
.m_bIsFolder
&& !item
.HasMusicInfoTag())
913 // Unknown location. Type cannot be determined for non-folder items.
918 if (IsNonExistingUserPartyModePlaylist(item
))
921 if (item
.m_bIsFolder
&&
922 (MUSIC::IsMusicDb(item
) || StringUtils::StartsWithNoCase(item
.GetPath(), "library://music/")))
924 // Exclude top level nodes - eg can't play 'genres' just a specific genre etc
925 const XFILE::MUSICDATABASEDIRECTORY::NODE_TYPE node
=
926 XFILE::CMusicDatabaseDirectory::GetDirectoryParentType(item
.GetPath());
927 if (node
== XFILE::MUSICDATABASEDIRECTORY::NODE_TYPE_OVERVIEW
)
933 if (item
.HasMusicInfoTag() && item
.CanQueue())
935 else if (!item
.m_bIsFolder
&& MUSIC::IsAudio(item
))
937 else if (item
.m_bIsFolder
)
939 // Not a music-specific folder (just file:// or nfs://). Allow play if context is Music window.
940 if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_MUSIC_NAV
&&
941 item
.GetPath() != "add") // Exclude "Add music source" item
947 } // namespace MUSIC_UTILS