2 * Copyright (C) 2005-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 "GUIWindowMusicBase.h"
13 #include "FileItemList.h"
14 #include "GUIInfoManager.h"
15 #include "GUIPassword.h"
16 #include "GUIUserMessages.h"
17 #include "PartyModeManager.h"
18 #include "PlayListPlayer.h"
19 #include "ServiceBroker.h"
22 #include "addons/gui/GUIDialogAddonInfo.h"
23 #include "application/Application.h"
24 #include "application/ApplicationComponents.h"
25 #include "application/ApplicationPlayer.h"
26 #include "music/MusicFileItemClassify.h"
27 #include "network/NetworkFileItemClassify.h"
28 #include "video/VideoFileItemClassify.h"
29 #ifdef HAS_CDDA_RIPPER
30 #include "cdrip/CDDARipper.h"
32 #include "dialogs/GUIDialogMediaSource.h"
33 #include "dialogs/GUIDialogProgress.h"
34 #include "dialogs/GUIDialogSmartPlaylistEditor.h"
35 #include "dialogs/GUIDialogYesNo.h"
36 #include "filesystem/Directory.h"
37 #include "filesystem/MusicDatabaseDirectory.h"
38 #include "guilib/GUIComponent.h"
39 #include "guilib/GUIWindowManager.h"
40 #include "guilib/LocalizeStrings.h"
41 #include "guilib/guiinfo/GUIInfoLabels.h"
42 #include "input/actions/Action.h"
43 #include "input/actions/ActionIDs.h"
44 #include "messaging/helpers/DialogHelper.h"
45 #include "messaging/helpers/DialogOKHelper.h"
46 #include "music/MusicDbUrl.h"
47 #include "music/MusicLibraryQueue.h"
48 #include "music/MusicUtils.h"
49 #include "music/dialogs/GUIDialogInfoProviderSettings.h"
50 #include "music/dialogs/GUIDialogMusicInfo.h"
51 #include "music/infoscanner/MusicInfoScanner.h"
52 #include "music/tags/MusicInfoTag.h"
53 #include "playlists/PlayList.h"
54 #include "playlists/PlayListFactory.h"
55 #include "profiles/ProfileManager.h"
56 #include "settings/AdvancedSettings.h"
57 #include "settings/MediaSourceSettings.h"
58 #include "settings/Settings.h"
59 #include "settings/SettingsComponent.h"
60 #include "storage/MediaManager.h"
61 #include "utils/FileUtils.h"
62 #include "utils/StringUtils.h"
63 #include "utils/URIUtils.h"
64 #include "utils/Variant.h"
65 #include "utils/XTimeUtils.h"
66 #include "utils/log.h"
67 #include "video/VideoInfoTag.h"
68 #include "video/dialogs/GUIDialogVideoInfo.h"
69 #include "view/GUIViewState.h"
74 using namespace XFILE
;
75 using namespace MUSICDATABASEDIRECTORY
;
76 using namespace MUSIC_GRABBER
;
77 using namespace MUSIC_INFO
;
79 using namespace KODI::MESSAGING
;
80 using KODI::MESSAGING::HELPERS::DialogResponse
;
81 using namespace KODI::VIDEO
;
83 using namespace std::chrono_literals
;
85 #define CONTROL_BTNVIEWASICONS 2
86 #define CONTROL_BTNSORTBY 3
87 #define CONTROL_BTNSORTASC 4
88 #define CONTROL_BTNPLAYLISTS 7
89 #define CONTROL_BTNSCAN 9
90 #define CONTROL_BTNRIP 11
92 CGUIWindowMusicBase::CGUIWindowMusicBase(int id
, const std::string
&xmlFile
)
93 : CGUIMediaWindow(id
, xmlFile
.c_str())
96 m_thumbLoader
.SetObserver(this);
99 CGUIWindowMusicBase::~CGUIWindowMusicBase () = default;
101 bool CGUIWindowMusicBase::OnBack(int actionID
)
103 if (!CMusicLibraryQueue::GetInstance().IsScanningLibrary())
105 CUtil::RemoveTempFiles();
107 return CGUIMediaWindow::OnBack(actionID
);
111 \brief Handle messages on window.
112 \param message GUI Message that can be reacted on.
113 \return if a message can't be processed, return \e false
115 On these messages this class reacts.\n
117 - #GUI_MSG_WINDOW_DEINIT\n
118 ...the last focused control is saved to m_iLastControl.
119 - #GUI_MSG_WINDOW_INIT\n
120 ...the musicdatabase is opend and the music extensions and shares are set.
121 The last focused control is set.
123 ... the base class reacts on the following controls:\n
125 - #CONTROL_BTNVIEWASICONS - switch between list, thumb and with large items
126 - #CONTROL_BTNSEARCH - Search for items\n
128 - The container controls\n
129 Have the following actions in message them clicking on them.
130 - #ACTION_QUEUE_ITEM - add selected item to end of playlist
131 - #ACTION_QUEUE_ITEM_NEXT - add selected item to next pos in playlist
132 - #ACTION_SHOW_INFO - retrieve album info from the internet
133 - #ACTION_SELECT_ITEM - Item has been selected. Overwrite OnClick() to react on it
135 bool CGUIWindowMusicBase::OnMessage(CGUIMessage
& message
)
137 switch ( message
.GetMessage() )
139 case GUI_MSG_WINDOW_DEINIT
:
141 if (m_thumbLoader
.IsLoading())
142 m_thumbLoader
.StopThread();
143 m_musicdatabase
.Close();
147 case GUI_MSG_WINDOW_INIT
:
149 m_dlgProgress
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogProgress
>(WINDOW_DIALOG_PROGRESS
);
151 m_musicdatabase
.Open();
153 if (!CGUIMediaWindow::OnMessage(message
))
159 case GUI_MSG_DIRECTORY_SCANNED
:
161 CFileItem
directory(message
.GetStringParam(), true);
163 // Only update thumb on a local drive
164 if (directory
.IsHD())
166 std::string strParent
;
167 URIUtils::GetParentPath(directory
.GetPath(), strParent
);
168 if (directory
.GetPath() == m_vecItems
->GetPath() || strParent
== m_vecItems
->GetPath())
174 // update the display
175 case GUI_MSG_SCAN_FINISHED
:
176 case GUI_MSG_REFRESH_THUMBS
: // Never called as is secondary msg sent as GUI_MSG_NOTIFY_ALL
180 case GUI_MSG_CLICKED
:
182 int iControl
= message
.GetSenderId();
183 if (iControl
== CONTROL_BTNRIP
)
187 else if (iControl
== CONTROL_BTNPLAYLISTS
)
189 if (!m_vecItems
->IsPath("special://musicplaylists/"))
190 Update("special://musicplaylists/");
192 else if (iControl
== CONTROL_BTNSCAN
)
196 else if (m_viewControl
.HasControl(iControl
)) // list/thumb control
198 int iItem
= m_viewControl
.GetSelectedItem();
199 int iAction
= message
.GetParam1();
201 // iItem is checked for validity inside these routines
202 if (iAction
== ACTION_QUEUE_ITEM
|| iAction
== ACTION_MOUSE_MIDDLE_CLICK
)
206 else if (iAction
== ACTION_QUEUE_ITEM_NEXT
)
208 OnQueueItem(iItem
, true);
210 else if (iAction
== ACTION_SHOW_INFO
)
214 else if (iAction
== ACTION_DELETE_ITEM
)
216 // is delete allowed?
217 // must be at the playlists directory
218 if (m_vecItems
->IsPath("special://musicplaylists/"))
224 // use play button to add folders of items to temp playlist
225 else if (iAction
== ACTION_PLAYER_PLAY
)
227 const auto& components
= CServiceBroker::GetAppComponents();
228 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
229 // if playback is paused or playback speed != 1, return
230 if (appPlayer
->IsPlayingAudio())
232 if (appPlayer
->IsPausedPlayback())
234 if (appPlayer
->GetPlaySpeed() != 1)
238 // not playing audio, or playback speed == 1
246 case GUI_MSG_NOTIFY_ALL
:
248 if (message
.GetParam1()==GUI_MSG_REMOVED_MEDIA
)
249 CUtil::DeleteDirectoryCache("r-");
253 return CGUIMediaWindow::OnMessage(message
);
256 bool CGUIWindowMusicBase::OnAction(const CAction
&action
)
258 if (action
.GetID() == ACTION_SHOW_PLAYLIST
)
260 if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::Id::TYPE_MUSIC
||
261 CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::Id::TYPE_MUSIC
).size() > 0)
263 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST
);
268 if (action
.GetID() == ACTION_SCAN_ITEM
)
270 int item
= m_viewControl
.GetSelectedItem();
271 if (item
> -1 && m_vecItems
->Get(item
)->m_bIsFolder
)
277 return CGUIMediaWindow::OnAction(action
);
280 void CGUIWindowMusicBase::OnItemInfoAll(const std::string
& strPath
, bool refresh
)
282 if (StringUtils::EqualsNoCase(m_vecItems
->GetContent(), "albums"))
284 if (CMusicLibraryQueue::GetInstance().IsScanningLibrary())
287 CMusicLibraryQueue::GetInstance().StartAlbumScan(strPath
, refresh
);
289 else if (StringUtils::EqualsNoCase(m_vecItems
->GetContent(), "artists"))
291 if (CMusicLibraryQueue::GetInstance().IsScanningLibrary())
294 CMusicLibraryQueue::GetInstance().StartArtistScan(strPath
, refresh
);
298 void CGUIWindowMusicBase::OnItemInfo(int iItem
)
300 if ( iItem
< 0 || iItem
>= m_vecItems
->Size() )
303 CFileItemPtr item
= m_vecItems
->Get(iItem
);
305 // Match visibility test of CMusicInfo::IsVisible
306 if (IsVideoDb(*item
) && item
->HasVideoInfoTag() &&
307 (item
->HasProperty("artist_musicid") || item
->HasProperty("album_musicid")))
309 // Music video artist or album (navigation by music > music video > artist))
310 CGUIDialogMusicInfo::ShowFor(item
.get());
314 if (IsVideo(*item
) && item
->HasVideoInfoTag() &&
315 item
->GetVideoInfoTag()->m_type
== MediaTypeMusicVideo
)
316 { // Music video on a mixed current playlist or navigation by music > music video > artist > video
317 CGUIDialogVideoInfo::ShowFor(*item
);
321 if (!m_vecItems
->IsPlugin() && (item
->IsPlugin() || item
->IsScript()))
323 CGUIDialogAddonInfo::ShowForItem(item
);
327 // Match visibility test of CMusicInfo::IsVisible
328 if (item
->HasMusicInfoTag() && (item
->GetMusicInfoTag()->GetType() == MediaTypeSong
||
329 item
->GetMusicInfoTag()->GetType() == MediaTypeAlbum
||
330 item
->GetMusicInfoTag()->GetType() == MediaTypeArtist
))
331 CGUIDialogMusicInfo::ShowFor(item
.get());
334 void CGUIWindowMusicBase::RefreshContent(const std::string
& strContent
)
336 if ( CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_MUSIC_NAV
&&
337 m_vecItems
->GetContent() == strContent
&&
338 m_vecItems
->GetSortMethod() == SortByUserRating
)
339 // When music library window is active and showing songs or albums sorted
340 // by userrating refresh the list to resort items and show new userrating
344 /// \brief Retrieve tag information for \e m_vecItems
345 void CGUIWindowMusicBase::RetrieveMusicInfo()
347 auto start
= std::chrono::steady_clock::now();
349 OnRetrieveMusicInfo(*m_vecItems
);
351 //! @todo Scan for multitrack items here...
352 std::vector
<std::string
> itemsForRemove
;
353 CFileItemList itemsForAdd
;
354 for (int i
= 0; i
< m_vecItems
->Size(); ++i
)
356 CFileItemPtr pItem
= (*m_vecItems
)[i
];
357 if (pItem
->m_bIsFolder
|| pItem
->IsPlayList() || pItem
->IsPicture() ||
358 MUSIC::IsLyrics(*pItem
) || IsVideo(*pItem
))
361 CMusicInfoTag
& tag
= *pItem
->GetMusicInfoTag();
362 if (tag
.Loaded() && !tag
.GetCueSheet().empty())
363 pItem
->LoadEmbeddedCue();
365 if (pItem
->HasCueDocument()
366 && pItem
->LoadTracksFromCueDocument(itemsForAdd
))
368 itemsForRemove
.push_back(pItem
->GetPath());
371 for (size_t i
= 0; i
< itemsForRemove
.size(); ++i
)
373 for (int j
= 0; j
< m_vecItems
->Size(); ++j
)
375 if ((*m_vecItems
)[j
]->GetPath() == itemsForRemove
[i
])
377 m_vecItems
->Remove(j
);
382 m_vecItems
->Append(itemsForAdd
);
384 auto end
= std::chrono::steady_clock::now();
385 auto duration
= std::chrono::duration_cast
<std::chrono::milliseconds
>(end
- start
);
387 CLog::Log(LOGDEBUG
, "RetrieveMusicInfo() took {} ms", duration
.count());
390 /// \brief Add selected list/thumb control item to playlist and start playing
391 /// \param iItem Selected Item in list/thumb control
392 void CGUIWindowMusicBase::OnQueueItem(int iItem
, bool first
)
394 // don't re-queue items from playlist window
395 if (iItem
< 0 || iItem
>= m_vecItems
->Size() || GetID() == WINDOW_MUSIC_PLAYLIST
)
398 // add item 2 playlist
399 const auto item
= m_vecItems
->Get(iItem
);
401 if (item
->IsRAR() || item
->IsZIP())
404 MUSIC_UTILS::QueueItem(item
, first
? MUSIC_UTILS::QueuePosition::POSITION_BEGIN
405 : MUSIC_UTILS::QueuePosition::POSITION_END
);
408 m_viewControl
.SetSelectedItem(iItem
+ 1);
411 void CGUIWindowMusicBase::UpdateButtons()
413 CONTROL_ENABLE_ON_CONDITION(CONTROL_BTNRIP
, CServiceBroker::GetMediaManager().IsAudio());
415 CONTROL_ENABLE_ON_CONDITION(
416 CONTROL_BTNSCAN
, !(m_vecItems
->IsVirtualDirectoryRoot() || MUSIC::IsMusicDb(*m_vecItems
)));
418 if (CMusicLibraryQueue::GetInstance().IsScanningLibrary())
419 SET_CONTROL_LABEL(CONTROL_BTNSCAN
, 14056); // Stop Scan
421 SET_CONTROL_LABEL(CONTROL_BTNSCAN
, 102); // Scan
423 CGUIMediaWindow::UpdateButtons();
426 void CGUIWindowMusicBase::GetContextButtons(int itemNumber
, CContextButtons
&buttons
)
429 if (itemNumber
>= 0 && itemNumber
< m_vecItems
->Size())
430 item
= m_vecItems
->Get(itemNumber
);
434 const std::shared_ptr
<CProfileManager
> profileManager
= CServiceBroker::GetSettingsComponent()->GetProfileManager();
436 // Check for the partymode playlist item.
437 // When "PartyMode.xsp" not exist, only context menu button is edit
438 if (item
->IsSmartPlayList() &&
439 (item
->GetPath() == profileManager
->GetUserDataItem("PartyMode.xsp")) &&
440 !CFileUtils::Exists(item
->GetPath()))
442 buttons
.Add(CONTEXT_BUTTON_EDIT_SMART_PLAYLIST
, 586);
446 if (!item
->IsParentFolder())
448 //! @todo get rid of IsAddonsPath and IsScript check. CanQueue should be enough!
449 if (item
->CanQueue() && !item
->IsAddonsPath() && !item
->IsScript())
451 if (item
->IsSmartPlayList())
452 buttons
.Add(CONTEXT_BUTTON_PLAY_PARTYMODE
, 15216); // Play in Partymode
454 if (item
->IsSmartPlayList() || m_vecItems
->IsSmartPlayList())
455 buttons
.Add(CONTEXT_BUTTON_EDIT_SMART_PLAYLIST
, 586);
456 else if (item
->IsPlayList() || m_vecItems
->IsPlayList())
457 buttons
.Add(CONTEXT_BUTTON_EDIT
, 586);
459 #ifdef HAS_OPTICAL_DRIVE
460 // enable Rip CD Audio or Track button if we have an audio disc
461 if (CServiceBroker::GetMediaManager().IsDiscInDrive() && MUSIC::IsCDDA(*m_vecItems
))
463 // those cds can also include Audio Tracks: CDExtra and MixedMode!
464 MEDIA_DETECT::CCdInfo
* pCdInfo
= CServiceBroker::GetMediaManager().GetCdInfo();
465 if (pCdInfo
->IsAudio(1) || pCdInfo
->IsCDExtra(1) || pCdInfo
->IsMixedMode(1))
466 buttons
.Add(CONTEXT_BUTTON_RIP_TRACK
, 610);
471 // enable CDDB lookup if the current dir is CDDA
472 if (CServiceBroker::GetMediaManager().IsDiscInDrive() && MUSIC::IsCDDA(*m_vecItems
) &&
473 (profileManager
->GetCurrentProfile().canWriteDatabases() || g_passwordManager
.bMasterUser
))
475 buttons
.Add(CONTEXT_BUTTON_CDDB
, 16002);
478 CGUIMediaWindow::GetContextButtons(itemNumber
, buttons
);
481 void CGUIWindowMusicBase::GetNonContextButtons(CContextButtons
&buttons
)
485 bool CGUIWindowMusicBase::OnContextButton(int itemNumber
, CONTEXT_BUTTON button
)
488 if (itemNumber
>= 0 && itemNumber
< m_vecItems
->Size())
489 item
= m_vecItems
->Get(itemNumber
);
491 if (CGUIDialogContextMenu::OnContextButton("music", item
, button
))
493 if (button
== CONTEXT_BUTTON_REMOVE_SOURCE
)
494 OnRemoveSource(itemNumber
);
496 Update(m_vecItems
->GetPath());
502 case CONTEXT_BUTTON_INFO
:
503 OnItemInfo(itemNumber
);
506 case CONTEXT_BUTTON_EDIT
:
508 std::string playlist
= item
->IsPlayList() ? item
->GetPath() : m_vecItems
->GetPath(); // save path as activatewindow will destroy our items
509 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST_EDITOR
, playlist
);
511 m_vecItems
->RemoveDiscCache(GetID());
515 case CONTEXT_BUTTON_EDIT_SMART_PLAYLIST
:
517 std::string playlist
= item
->IsSmartPlayList() ? item
->GetPath() : m_vecItems
->GetPath(); // save path as activatewindow will destroy our items
518 if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist
, "music"))
519 Refresh(true); // need to update
523 case CONTEXT_BUTTON_PLAY_PARTYMODE
:
524 g_partyModeManager
.Enable(PARTYMODECONTEXT_MUSIC
, item
->GetPath());
527 case CONTEXT_BUTTON_RIP_CD
:
531 #ifdef HAS_CDDA_RIPPER
532 case CONTEXT_BUTTON_CANCEL_RIP_CD
:
533 KODI::CDRIP::CCDDARipper::GetInstance().CancelJobs();
537 case CONTEXT_BUTTON_RIP_TRACK
:
538 OnRipTrack(itemNumber
);
541 case CONTEXT_BUTTON_SCAN
:
542 // Check if scanning already and inform user
543 if (CMusicLibraryQueue::GetInstance().IsScanningLibrary())
544 HELPERS::ShowOKDialogText(CVariant
{ 189 }, CVariant
{ 14057 });
546 OnScan(itemNumber
, true);
549 case CONTEXT_BUTTON_CDDB
:
550 if (m_musicdatabase
.LookupCDDBInfo(true))
558 return CGUIMediaWindow::OnContextButton(itemNumber
, button
);
561 bool CGUIWindowMusicBase::OnAddMediaSource()
563 return CGUIDialogMediaSource::ShowAndAddMediaSource("music");
566 void CGUIWindowMusicBase::OnRipCD()
568 if (CServiceBroker::GetMediaManager().IsAudio())
570 if (!MUSIC::IsCDDA(g_application
.CurrentFileItem()))
572 #ifdef HAS_CDDA_RIPPER
573 KODI::CDRIP::CCDDARipper::GetInstance().RipCD();
577 HELPERS::ShowOKDialogText(CVariant
{257}, CVariant
{20099});
581 void CGUIWindowMusicBase::OnRipTrack(int iItem
)
583 if (CServiceBroker::GetMediaManager().IsAudio())
585 if (!MUSIC::IsCDDA(g_application
.CurrentFileItem()))
587 #ifdef HAS_CDDA_RIPPER
588 CFileItemPtr item
= m_vecItems
->Get(iItem
);
589 KODI::CDRIP::CCDDARipper::GetInstance().RipTrack(item
.get());
593 HELPERS::ShowOKDialogText(CVariant
{257}, CVariant
{20099});
597 void CGUIWindowMusicBase::PlayItem(int iItem
)
599 // restrictions should be placed in the appropriate window code
600 // only call the base code if the item passes since this clears
601 // the current playlist
603 const CFileItemPtr pItem
= m_vecItems
->Get(iItem
);
604 #ifdef HAS_OPTICAL_DRIVE
607 MEDIA_DETECT::CAutorun::PlayDiscAskResume(pItem
->GetPath());
612 // Check for the partymode playlist item, do nothing when "PartyMode.xsp" not exist
613 if (pItem
->IsSmartPlayList())
615 const std::shared_ptr
<CProfileManager
> profileManager
=
616 CServiceBroker::GetSettingsComponent()->GetProfileManager();
617 if ((pItem
->GetPath() == profileManager
->GetUserDataItem("PartyMode.xsp")) &&
618 !CFileUtils::Exists(pItem
->GetPath()))
622 // if its a folder, build a playlist
623 if (pItem
->m_bIsFolder
&& !pItem
->IsPlugin())
625 // make a copy so that we can alter the queue state
626 CFileItemPtr
item(new CFileItem(*m_vecItems
->Get(iItem
)));
628 // Allow queuing of unqueueable items
629 // when we try to queue them directly
630 if (!item
->CanQueue())
631 item
->SetCanQueue(true);
634 if (item
->IsParentFolder())
637 CFileItemList queuedItems
;
638 MUSIC_UTILS::GetItemsForPlayList(item
, queuedItems
);
639 if (g_partyModeManager
.IsEnabled())
641 g_partyModeManager
.AddUserSongs(queuedItems
, true);
646 std::string strPlayListDirectory = m_vecItems->GetPath();
647 URIUtils::RemoveSlashAtEnd(strPlayListDirectory);
650 CServiceBroker::GetPlaylistPlayer().ClearPlaylist(PLAYLIST::Id::TYPE_MUSIC
);
651 CServiceBroker::GetPlaylistPlayer().Reset();
652 CServiceBroker::GetPlaylistPlayer().Add(PLAYLIST::Id::TYPE_MUSIC
, queuedItems
);
653 CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::Id::TYPE_MUSIC
);
656 CServiceBroker::GetPlaylistPlayer().Play();
658 else if (pItem
->IsPlayList())
660 // load the playlist the old way
661 LoadPlayList(pItem
->GetPath());
665 // just a single item, play it
666 //! @todo Add music-specific code for single playback of an item here (See OnClick in MediaWindow, and OnPlayMedia below)
671 void CGUIWindowMusicBase::LoadPlayList(const std::string
& strPlayList
)
673 // if partymode is active, we disable it
674 if (g_partyModeManager
.IsEnabled())
675 g_partyModeManager
.Disable();
677 // load a playlist like .m3u, .pls
678 // first get correct factory to load playlist
679 std::unique_ptr
<PLAYLIST::CPlayList
> pPlayList(PLAYLIST::CPlayListFactory::Create(strPlayList
));
683 if (!pPlayList
->Load(strPlayList
))
685 HELPERS::ShowOKDialogText(CVariant
{6}, CVariant
{477});
686 return; //hmmm unable to load playlist?
690 int iSize
= pPlayList
->size();
691 if (g_application
.ProcessAndStartPlaylist(strPlayList
, *pPlayList
, PLAYLIST::Id::TYPE_MUSIC
))
694 m_guiState
->SetPlaylistDirectory("playlistmusic://");
695 // activate the playlist window if its not activated yet
696 if (GetID() == CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() && iSize
> 1)
698 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST
);
703 bool CGUIWindowMusicBase::OnPlayMedia(int iItem
, const std::string
&player
)
705 CFileItemPtr pItem
= m_vecItems
->Get(iItem
);
708 if (g_partyModeManager
.IsEnabled())
710 PLAYLIST::CPlayList playlistTemp
;
711 playlistTemp
.Add(pItem
);
712 g_partyModeManager
.AddUserSongs(playlistTemp
, !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICPLAYER_QUEUEBYDEFAULT
));
715 else if (!pItem
->IsPlayList() && !NETWORK::IsInternetStream(*pItem
))
716 { // single music file - if we get here then we have autoplaynextitem turned off or queuebydefault
717 // turned on, but we still want to use the playlist player in order to handle more queued items
719 if ( (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICPLAYER_QUEUEBYDEFAULT
) && CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_MUSIC_PLAYLIST_EDITOR
) )
721 //! @todo Should the playlist be cleared if nothing is already playing?
725 pItem
->SetProperty("playlist_type_hint", static_cast<int>(m_guiState
->GetPlaylist()));
726 CServiceBroker::GetPlaylistPlayer().Play(pItem
, player
);
729 return CGUIMediaWindow::OnPlayMedia(iItem
, player
);
732 /// \brief Can be overwritten to implement an own tag filling function.
733 /// \param items File items to fill
734 void CGUIWindowMusicBase::OnRetrieveMusicInfo(CFileItemList
& items
)
736 // No need to attempt to read music file tags for music videos
737 if (IsVideoDb(items
))
739 if (items
.GetFolderCount() == items
.Size() || MUSIC::IsMusicDb(items
) ||
740 (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
741 CSettings::SETTING_MUSICFILES_USETAGS
) &&
742 !MUSIC::IsCDDA(items
)))
746 // Start the music info loader thread
747 m_musicInfoLoader
.SetProgressCallback(m_dlgProgress
);
748 m_musicInfoLoader
.Load(items
);
750 bool bShowProgress
= !CServiceBroker::GetGUI()->GetWindowManager().HasModalDialog(true);
751 bool bProgressVisible
= false;
753 auto start
= std::chrono::steady_clock::now();
755 while (m_musicInfoLoader
.IsLoading())
758 { // Do we have to init a progress dialog?
759 auto end
= std::chrono::steady_clock::now();
760 auto duration
= std::chrono::duration_cast
<std::chrono::milliseconds
>(end
- start
);
762 if (!bProgressVisible
&& duration
.count() > 1500 && m_dlgProgress
)
763 { // tag loading takes more then 1.5 secs, show a progress dialog
764 CURL
url(items
.GetPath());
765 m_dlgProgress
->SetHeading(CVariant
{189});
766 m_dlgProgress
->SetLine(0, CVariant
{505});
767 m_dlgProgress
->SetLine(1, CVariant
{""});
768 m_dlgProgress
->SetLine(2, CVariant
{url
.GetWithoutUserDetails()});
769 m_dlgProgress
->Open();
770 m_dlgProgress
->ShowProgressBar(true);
771 bProgressVisible
= true;
774 if (bProgressVisible
&& m_dlgProgress
&& !m_dlgProgress
->IsCanceled())
776 m_dlgProgress
->Progress();
778 } // if (bShowProgress)
779 KODI::TIME::Sleep(1ms
);
780 } // while (m_musicInfoLoader.IsLoading())
782 if (bProgressVisible
&& m_dlgProgress
)
783 m_dlgProgress
->Close();
786 bool CGUIWindowMusicBase::GetDirectory(const std::string
&strDirectory
, CFileItemList
&items
)
789 bool bResult
= CGUIMediaWindow::GetDirectory(strDirectory
, items
);
792 // We want to expand disc images when browsing in file view but not on library, smartplaylist
793 // or node menu music windows
794 if (!items
.GetPath().empty() && !StringUtils::StartsWithNoCase(items
.GetPath(), "musicdb://") &&
795 !StringUtils::StartsWithNoCase(items
.GetPath(), "special://") &&
796 !StringUtils::StartsWithNoCase(items
.GetPath(), "library://"))
797 CDirectory::FilterFileDirectories(items
, ".iso", true);
799 CMusicThumbLoader loader
;
800 loader
.FillThumb(items
);
803 CDirectoryNode::GetDatabaseInfo(items
.GetPath(), params
);
805 // Get art for directory when album or artist
806 bool artfound
= false;
807 std::vector
<ArtForThumbLoader
> art
;
808 if (params
.GetAlbumId() > 0)
809 { // Get album and related artist(s) art
810 artfound
= m_musicdatabase
.GetArtForItem(-1, params
.GetAlbumId(), -1, false, art
);
812 else if (params
.GetArtistId() > 0)
814 artfound
= m_musicdatabase
.GetArtForItem(-1, -1, params
.GetArtistId(), true, art
);
818 std::string dirType
= MediaTypeArtist
;
819 if (params
.GetAlbumId() > 0)
820 dirType
= MediaTypeAlbum
;
821 std::map
<std::string
, std::string
> artmap
;
822 for (auto artitem
: art
)
825 if (dirType
== artitem
.mediaType
)
826 artname
= artitem
.artType
;
827 else if (artitem
.prefix
.empty())
828 artname
= artitem
.mediaType
+ "." + artitem
.artType
;
831 if (dirType
== MediaTypeAlbum
)
832 StringUtils::Replace(artitem
.prefix
, "albumartist", "artist");
833 artname
= artitem
.prefix
+ "." + artitem
.artType
;
835 artmap
.insert(std::make_pair(artname
, artitem
.url
));
837 items
.SetArt(artmap
);
840 int iWindow
= GetID();
841 // Add "New Playlist" items when in the playlists folder, except on playlist editor screen
842 if ((iWindow
!= WINDOW_MUSIC_PLAYLIST_EDITOR
) &&
843 (items
.GetPath() == "special://musicplaylists/") && !items
.Contains("newplaylist://"))
845 const std::shared_ptr
<CProfileManager
> profileManager
= CServiceBroker::GetSettingsComponent()->GetProfileManager();
847 CFileItemPtr
newPlaylist(new CFileItem(profileManager
->GetUserDataItem("PartyMode.xsp"),false));
848 newPlaylist
->SetLabel(g_localizeStrings
.Get(16035));
849 newPlaylist
->SetLabelPreformatted(true);
850 newPlaylist
->SetArt("icon", "DefaultPartyMode.png");
851 newPlaylist
->m_bIsFolder
= true;
852 items
.Add(newPlaylist
);
854 newPlaylist
= std::make_shared
<CFileItem
>("newplaylist://", false);
855 newPlaylist
->SetLabel(g_localizeStrings
.Get(525));
856 newPlaylist
->SetArt("icon", "DefaultAddSource.png");
857 newPlaylist
->SetLabelPreformatted(true);
858 newPlaylist
->SetSpecialSort(SortSpecialOnBottom
);
859 newPlaylist
->SetCanQueue(false);
860 items
.Add(newPlaylist
);
862 newPlaylist
= std::make_shared
<CFileItem
>("newsmartplaylist://music", false);
863 newPlaylist
->SetLabel(g_localizeStrings
.Get(21437));
864 newPlaylist
->SetArt("icon", "DefaultAddSource.png");
865 newPlaylist
->SetLabelPreformatted(true);
866 newPlaylist
->SetSpecialSort(SortSpecialOnBottom
);
867 newPlaylist
->SetCanQueue(false);
868 items
.Add(newPlaylist
);
871 // check for .CUE files here.
872 items
.FilterCueItems();
875 if (items
.GetLabel().empty() && m_rootDir
.IsSource(items
.GetPath(), CMediaSourceSettings::GetInstance().GetSources("music"), &label
))
876 items
.SetLabel(label
);
882 bool CGUIWindowMusicBase::CheckFilterAdvanced(CFileItemList
&items
) const
884 const std::string
& content
= items
.GetContent();
885 if ((MUSIC::IsMusicDb(items
) || CanContainFilter(m_strFilterPath
)) &&
886 (StringUtils::EqualsNoCase(content
, "artists") ||
887 StringUtils::EqualsNoCase(content
, "albums") || StringUtils::EqualsNoCase(content
, "songs")))
893 bool CGUIWindowMusicBase::CanContainFilter(const std::string
&strDirectory
) const
895 return URIUtils::IsProtocol(strDirectory
, "musicdb");
898 bool CGUIWindowMusicBase::OnSelect(int iItem
)
900 auto item
= m_vecItems
->Get(iItem
);
901 if (MUSIC::IsAudioBook(*item
))
904 if (m_musicdatabase
.GetResumeBookmarkForAudioBook(*item
, bookmark
) && bookmark
> 0)
906 // find which chapter the bookmark belongs to
908 std::find_if(m_vecItems
->cbegin(), m_vecItems
->cend(),
909 [&](const CFileItemPtr
& item
) { return bookmark
< item
->GetEndOffset(); });
911 if (itemIt
!= m_vecItems
->cend())
913 // ask the user if they want to play or resume
914 CContextButtons choices
;
915 choices
.Add(MUSIC_SELECT_ACTION_PLAY
, 208); // 208 = Play
916 choices
.Add(MUSIC_SELECT_ACTION_RESUME
,
917 StringUtils::Format(g_localizeStrings
.Get(12022), // 12022 = Resume from ...
918 (*itemIt
)->GetMusicInfoTag()->GetTitle()));
920 auto choice
= CGUIDialogContextMenu::Show(choices
);
921 if (choice
== MUSIC_SELECT_ACTION_RESUME
)
923 (*itemIt
)->SetProperty("audiobook_bookmark", bookmark
);
924 return CGUIMediaWindow::OnSelect(static_cast<int>(itemIt
- m_vecItems
->cbegin()));
932 return CGUIMediaWindow::OnSelect(iItem
);
935 void CGUIWindowMusicBase::OnInitWindow()
937 CGUIMediaWindow::OnInitWindow();
938 // Prompt for rescan of library to read music file tags that were not processed by previous versions
939 // and accommodate any changes to the way some tags are processed
940 if (m_musicdatabase
.GetMusicNeedsTagScan() != 0)
942 if (CServiceBroker::GetGUI()
945 .GetLibraryInfoProvider()
946 .GetLibraryBool(LIBRARY_HAS_MUSIC
) &&
947 !CMusicLibraryQueue::GetInstance().IsScanningLibrary())
949 // rescan of music library required
950 if (CGUIDialogYesNo::ShowAndGetInput(CVariant
{799}, CVariant
{38060}))
952 int flags
= CMusicInfoScanner::SCAN_RESCAN
;
953 // When set to fetch information on update enquire about scraping that as well
954 // It may take some time, so the user may want to do it later by "Query Info For All"
955 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO
))
956 if (CGUIDialogYesNo::ShowAndGetInput(CVariant
{799}, CVariant
{38061}))
957 flags
|= CMusicInfoScanner::SCAN_ONLINE
;
959 CMusicLibraryQueue::GetInstance().ScanLibrary("", flags
, true);
961 m_musicdatabase
.SetMusicTagScanVersion(); // once is enough (user may interrupt, but that's up to them)
966 // no need to force a rescan if there's no music in the library or if a library scan is already active
967 m_musicdatabase
.SetMusicTagScanVersion();
972 std::string
CGUIWindowMusicBase::GetStartFolder(const std::string
&dir
)
974 std::string
lower(dir
); StringUtils::ToLower(lower
);
975 if (lower
== "plugins" || lower
== "addons")
976 return "addons://sources/audio/";
977 else if (lower
== "$playlists" || lower
== "playlists")
978 return "special://musicplaylists/";
979 return CGUIMediaWindow::GetStartFolder(dir
);
982 void CGUIWindowMusicBase::OnScan(int iItem
, bool bPromptRescan
/*= false*/)
985 if (iItem
< 0 || iItem
>= m_vecItems
->Size())
986 strPath
= m_vecItems
->GetPath();
987 else if (m_vecItems
->Get(iItem
)->m_bIsFolder
)
988 strPath
= m_vecItems
->Get(iItem
)->GetPath();
990 { //! @todo MUSICDB - should we allow scanning a single item into the database?
991 //! This will require changes to the info scanner, which assumes we're running on a folder
992 strPath
= m_vecItems
->GetPath();
994 // Ask for full rescan of music files when scan item from file view context menu
995 bool doRescan
= false;
997 doRescan
= CGUIDialogYesNo::ShowAndGetInput(CVariant
{ 799 }, CVariant
{ 38062 });
999 DoScan(strPath
, doRescan
);
1002 void CGUIWindowMusicBase::DoScan(const std::string
&strPath
, bool bRescan
/*= false*/)
1004 if (CMusicLibraryQueue::GetInstance().IsScanningLibrary())
1006 CMusicLibraryQueue::GetInstance().StopLibraryScanning();
1010 // Start background loader
1011 int iControl
=GetFocusedControlID();
1014 flags
= CMusicInfoScanner::SCAN_RESCAN
;
1015 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO
))
1016 flags
|= CMusicInfoScanner::SCAN_ONLINE
;
1018 CMusicLibraryQueue::GetInstance().ScanLibrary(strPath
, flags
, true);
1020 SET_CONTROL_FOCUS(iControl
, 0);
1024 void CGUIWindowMusicBase::OnRemoveSource(int iItem
)
1027 //Remove music source from library, even when leaving songs
1028 CMusicDatabase database
;
1030 database
.RemoveSource(m_vecItems
->Get(iItem
)->GetLabel());
1033 if (CGUIDialogYesNo::ShowAndGetInput(CVariant
{522}, CVariant
{20340}, bCanceled
, CVariant
{""}, CVariant
{""}, CGUIDialogYesNo::NO_TIMEOUT
))
1036 database
.RemoveSongsFromPath(m_vecItems
->Get(iItem
)->GetPath(), songs
, false);
1037 database
.CleanupOrphanedItems();
1038 database
.CheckArtistLinksChanged();
1039 CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetLibraryInfoProvider().ResetLibraryBools();
1040 m_vecItems
->RemoveDiscCache(GetID());
1045 void CGUIWindowMusicBase::OnPrepareFileItems(CFileItemList
&items
)
1047 CGUIMediaWindow::OnPrepareFileItems(items
);
1049 if (!MUSIC::IsMusicDb(items
) && !items
.IsSmartPlayList())
1050 RetrieveMusicInfo();
1053 void CGUIWindowMusicBase::OnAssignContent(const std::string
& oldName
, const CMediaSource
& source
)
1055 // Music scrapers are not source specific, so unlike video there is no content selection logic here.
1056 // Called on having added or edited a music source, this starts scanning items into library when required
1058 //! @todo: do async as updating sources for all albums could be slow??
1059 //Store music source in the music library, even those not scanned
1060 CMusicDatabase database
;
1062 database
.UpdateSource(oldName
, source
.strName
, source
.strPath
, source
.vecPaths
);
1065 // "Add to library" yes/no dialog with additional "settings" custom button
1066 // "Do you want to add the media from this source to your library?"
1067 DialogResponse rep
= DialogResponse::CHOICE_CUSTOM
;
1068 while (rep
== DialogResponse::CHOICE_CUSTOM
)
1070 rep
= HELPERS::ShowYesNoCustomDialog(CVariant
{20444}, CVariant
{20447}, CVariant
{106}, CVariant
{107}, CVariant
{10004});
1071 if (rep
== DialogResponse::CHOICE_CUSTOM
)
1072 // Edit default info provider settings so can be applied during scan
1073 CGUIDialogInfoProviderSettings::Show();
1075 if (rep
== DialogResponse::CHOICE_YES
)
1076 CMusicLibraryQueue::GetInstance().ScanLibrary(source
.strPath
,
1077 MUSIC_INFO::CMusicInfoScanner::SCAN_NORMAL
, true);