[PlayListPlayer] Fix hint on playlist file with multiple paths
[xbmc.git] / xbmc / music / windows / GUIWindowMusicBase.cpp
blobdf45141c9bfa12e6067d557bf246cb315956a817
1 /*
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.
7 */
9 #include "GUIWindowMusicBase.h"
11 #include "GUIUserMessages.h"
12 #include "PlayListPlayer.h"
13 #include "ServiceBroker.h"
14 #include "Util.h"
15 #include "application/Application.h"
16 #include "application/ApplicationComponents.h"
17 #include "application/ApplicationPlayer.h"
18 #include "dialogs/GUIDialogMediaSource.h"
19 #include "input/actions/Action.h"
20 #include "input/actions/ActionIDs.h"
21 #include "music/MusicDbUrl.h"
22 #include "music/MusicLibraryQueue.h"
23 #include "music/MusicUtils.h"
24 #include "music/dialogs/GUIDialogInfoProviderSettings.h"
25 #include "music/dialogs/GUIDialogMusicInfo.h"
26 #include "playlists/PlayList.h"
27 #include "playlists/PlayListFactory.h"
28 #ifdef HAS_CDDA_RIPPER
29 #include "cdrip/CDDARipper.h"
30 #endif
31 #include "Autorun.h"
32 #include "FileItem.h"
33 #include "GUIInfoManager.h"
34 #include "GUIPassword.h"
35 #include "PartyModeManager.h"
36 #include "URL.h"
37 #include "addons/gui/GUIDialogAddonInfo.h"
38 #include "cores/playercorefactory/PlayerCoreFactory.h"
39 #include "dialogs/GUIDialogProgress.h"
40 #include "dialogs/GUIDialogSmartPlaylistEditor.h"
41 #include "dialogs/GUIDialogYesNo.h"
42 #include "filesystem/Directory.h"
43 #include "filesystem/MusicDatabaseDirectory.h"
44 #include "guilib/GUIComponent.h"
45 #include "guilib/GUIWindowManager.h"
46 #include "guilib/LocalizeStrings.h"
47 #include "guilib/guiinfo/GUIInfoLabels.h"
48 #include "messaging/helpers/DialogHelper.h"
49 #include "messaging/helpers/DialogOKHelper.h"
50 #include "music/infoscanner/MusicInfoScanner.h"
51 #include "music/tags/MusicInfoTag.h"
52 #include "profiles/ProfileManager.h"
53 #include "settings/AdvancedSettings.h"
54 #include "settings/MediaSourceSettings.h"
55 #include "settings/Settings.h"
56 #include "settings/SettingsComponent.h"
57 #include "storage/MediaManager.h"
58 #include "utils/FileUtils.h"
59 #include "utils/StringUtils.h"
60 #include "utils/URIUtils.h"
61 #include "utils/Variant.h"
62 #include "utils/XTimeUtils.h"
63 #include "utils/log.h"
64 #include "video/VideoInfoTag.h"
65 #include "video/dialogs/GUIDialogVideoInfo.h"
66 #include "view/GUIViewState.h"
68 #include <algorithm>
70 using namespace XFILE;
71 using namespace MUSICDATABASEDIRECTORY;
72 using namespace MUSIC_GRABBER;
73 using namespace MUSIC_INFO;
74 using namespace KODI::MESSAGING;
75 using KODI::MESSAGING::HELPERS::DialogResponse;
77 using namespace std::chrono_literals;
79 #define CONTROL_BTNVIEWASICONS 2
80 #define CONTROL_BTNSORTBY 3
81 #define CONTROL_BTNSORTASC 4
82 #define CONTROL_BTNPLAYLISTS 7
83 #define CONTROL_BTNSCAN 9
84 #define CONTROL_BTNRIP 11
86 CGUIWindowMusicBase::CGUIWindowMusicBase(int id, const std::string &xmlFile)
87 : CGUIMediaWindow(id, xmlFile.c_str())
89 m_dlgProgress = NULL;
90 m_thumbLoader.SetObserver(this);
93 CGUIWindowMusicBase::~CGUIWindowMusicBase () = default;
95 bool CGUIWindowMusicBase::OnBack(int actionID)
97 if (!CMusicLibraryQueue::GetInstance().IsScanningLibrary())
99 CUtil::RemoveTempFiles();
101 return CGUIMediaWindow::OnBack(actionID);
105 \brief Handle messages on window.
106 \param message GUI Message that can be reacted on.
107 \return if a message can't be processed, return \e false
109 On these messages this class reacts.\n
110 When retrieving...
111 - #GUI_MSG_WINDOW_DEINIT\n
112 ...the last focused control is saved to m_iLastControl.
113 - #GUI_MSG_WINDOW_INIT\n
114 ...the musicdatabase is opend and the music extensions and shares are set.
115 The last focused control is set.
116 - #GUI_MSG_CLICKED\n
117 ... the base class reacts on the following controls:\n
118 Buttons:\n
119 - #CONTROL_BTNVIEWASICONS - switch between list, thumb and with large items
120 - #CONTROL_BTNSEARCH - Search for items\n
121 Other Controls:
122 - The container controls\n
123 Have the following actions in message them clicking on them.
124 - #ACTION_QUEUE_ITEM - add selected item to end of playlist
125 - #ACTION_QUEUE_ITEM_NEXT - add selected item to next pos in playlist
126 - #ACTION_SHOW_INFO - retrieve album info from the internet
127 - #ACTION_SELECT_ITEM - Item has been selected. Overwrite OnClick() to react on it
129 bool CGUIWindowMusicBase::OnMessage(CGUIMessage& message)
131 switch ( message.GetMessage() )
133 case GUI_MSG_WINDOW_DEINIT:
135 if (m_thumbLoader.IsLoading())
136 m_thumbLoader.StopThread();
137 m_musicdatabase.Close();
139 break;
141 case GUI_MSG_WINDOW_INIT:
143 m_dlgProgress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
145 m_musicdatabase.Open();
147 if (!CGUIMediaWindow::OnMessage(message))
148 return false;
150 return true;
152 break;
153 case GUI_MSG_DIRECTORY_SCANNED:
155 CFileItem directory(message.GetStringParam(), true);
157 // Only update thumb on a local drive
158 if (directory.IsHD())
160 std::string strParent;
161 URIUtils::GetParentPath(directory.GetPath(), strParent);
162 if (directory.GetPath() == m_vecItems->GetPath() || strParent == m_vecItems->GetPath())
163 Refresh();
166 break;
168 // update the display
169 case GUI_MSG_SCAN_FINISHED:
170 case GUI_MSG_REFRESH_THUMBS: // Never called as is secondary msg sent as GUI_MSG_NOTIFY_ALL
171 Refresh();
172 break;
174 case GUI_MSG_CLICKED:
176 int iControl = message.GetSenderId();
177 if (iControl == CONTROL_BTNRIP)
179 OnRipCD();
181 else if (iControl == CONTROL_BTNPLAYLISTS)
183 if (!m_vecItems->IsPath("special://musicplaylists/"))
184 Update("special://musicplaylists/");
186 else if (iControl == CONTROL_BTNSCAN)
188 OnScan(-1);
190 else if (m_viewControl.HasControl(iControl)) // list/thumb control
192 int iItem = m_viewControl.GetSelectedItem();
193 int iAction = message.GetParam1();
195 // iItem is checked for validity inside these routines
196 if (iAction == ACTION_QUEUE_ITEM || iAction == ACTION_MOUSE_MIDDLE_CLICK)
198 OnQueueItem(iItem);
200 else if (iAction == ACTION_QUEUE_ITEM_NEXT)
202 OnQueueItem(iItem, true);
204 else if (iAction == ACTION_SHOW_INFO)
206 OnItemInfo(iItem);
208 else if (iAction == ACTION_DELETE_ITEM)
210 // is delete allowed?
211 // must be at the playlists directory
212 if (m_vecItems->IsPath("special://musicplaylists/"))
213 OnDeleteItem(iItem);
215 else
216 return false;
218 // use play button to add folders of items to temp playlist
219 else if (iAction == ACTION_PLAYER_PLAY)
221 const auto& components = CServiceBroker::GetAppComponents();
222 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
223 // if playback is paused or playback speed != 1, return
224 if (appPlayer->IsPlayingAudio())
226 if (appPlayer->IsPausedPlayback())
227 return false;
228 if (appPlayer->GetPlaySpeed() != 1)
229 return false;
232 // not playing audio, or playback speed == 1
233 PlayItem(iItem);
235 return true;
239 break;
240 case GUI_MSG_NOTIFY_ALL:
242 if (message.GetParam1()==GUI_MSG_REMOVED_MEDIA)
243 CUtil::DeleteDirectoryCache("r-");
245 break;
247 return CGUIMediaWindow::OnMessage(message);
250 bool CGUIWindowMusicBase::OnAction(const CAction &action)
252 if (action.GetID() == ACTION_SHOW_PLAYLIST)
254 if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_MUSIC ||
255 CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::TYPE_MUSIC).size() > 0)
257 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST);
258 return true;
262 if (action.GetID() == ACTION_SCAN_ITEM)
264 int item = m_viewControl.GetSelectedItem();
265 if (item > -1 && m_vecItems->Get(item)->m_bIsFolder)
266 OnScan(item);
268 return true;
271 return CGUIMediaWindow::OnAction(action);
274 void CGUIWindowMusicBase::OnItemInfoAll(const std::string& strPath, bool refresh)
276 if (StringUtils::EqualsNoCase(m_vecItems->GetContent(), "albums"))
278 if (CMusicLibraryQueue::GetInstance().IsScanningLibrary())
279 return;
281 CMusicLibraryQueue::GetInstance().StartAlbumScan(strPath, refresh);
283 else if (StringUtils::EqualsNoCase(m_vecItems->GetContent(), "artists"))
285 if (CMusicLibraryQueue::GetInstance().IsScanningLibrary())
286 return;
288 CMusicLibraryQueue::GetInstance().StartArtistScan(strPath, refresh);
292 void CGUIWindowMusicBase::OnItemInfo(int iItem)
294 if ( iItem < 0 || iItem >= m_vecItems->Size() )
295 return;
297 CFileItemPtr item = m_vecItems->Get(iItem);
299 // Match visibility test of CMusicInfo::IsVisible
300 if (item->IsVideoDb() && item->HasVideoInfoTag() &&
301 (item->HasProperty("artist_musicid") || item->HasProperty("album_musicid")))
303 // Music video artist or album (navigation by music > music video > artist))
304 CGUIDialogMusicInfo::ShowFor(item.get());
305 return;
308 if (item->IsVideo() && item->HasVideoInfoTag() &&
309 item->GetVideoInfoTag()->m_type == MediaTypeMusicVideo)
310 { // Music video on a mixed current playlist or navigation by music > music video > artist > video
311 CGUIDialogVideoInfo::ShowFor(*item);
312 return;
315 if (!m_vecItems->IsPlugin() && (item->IsPlugin() || item->IsScript()))
317 CGUIDialogAddonInfo::ShowForItem(item);
318 return;
321 // Match visibility test of CMusicInfo::IsVisible
322 if (item->HasMusicInfoTag() && (item->GetMusicInfoTag()->GetType() == MediaTypeSong ||
323 item->GetMusicInfoTag()->GetType() == MediaTypeAlbum ||
324 item->GetMusicInfoTag()->GetType() == MediaTypeArtist))
325 CGUIDialogMusicInfo::ShowFor(item.get());
328 void CGUIWindowMusicBase::RefreshContent(const std::string& strContent)
330 if ( CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_MUSIC_NAV &&
331 m_vecItems->GetContent() == strContent &&
332 m_vecItems->GetSortMethod() == SortByUserRating)
333 // When music library window is active and showing songs or albums sorted
334 // by userrating refresh the list to resort items and show new userrating
335 Refresh(true);
338 /// \brief Retrieve tag information for \e m_vecItems
339 void CGUIWindowMusicBase::RetrieveMusicInfo()
341 auto start = std::chrono::steady_clock::now();
343 OnRetrieveMusicInfo(*m_vecItems);
345 //! @todo Scan for multitrack items here...
346 std::vector<std::string> itemsForRemove;
347 CFileItemList itemsForAdd;
348 for (int i = 0; i < m_vecItems->Size(); ++i)
350 CFileItemPtr pItem = (*m_vecItems)[i];
351 if (pItem->m_bIsFolder || pItem->IsPlayList() || pItem->IsPicture() || pItem->IsLyrics() || pItem->IsVideo())
352 continue;
354 CMusicInfoTag& tag = *pItem->GetMusicInfoTag();
355 if (tag.Loaded() && !tag.GetCueSheet().empty())
356 pItem->LoadEmbeddedCue();
358 if (pItem->HasCueDocument()
359 && pItem->LoadTracksFromCueDocument(itemsForAdd))
361 itemsForRemove.push_back(pItem->GetPath());
364 for (size_t i = 0; i < itemsForRemove.size(); ++i)
366 for (int j = 0; j < m_vecItems->Size(); ++j)
368 if ((*m_vecItems)[j]->GetPath() == itemsForRemove[i])
370 m_vecItems->Remove(j);
371 break;
375 m_vecItems->Append(itemsForAdd);
377 auto end = std::chrono::steady_clock::now();
378 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
380 CLog::Log(LOGDEBUG, "RetrieveMusicInfo() took {} ms", duration.count());
383 /// \brief Add selected list/thumb control item to playlist and start playing
384 /// \param iItem Selected Item in list/thumb control
385 void CGUIWindowMusicBase::OnQueueItem(int iItem, bool first)
387 // don't re-queue items from playlist window
388 if (iItem < 0 || iItem >= m_vecItems->Size() || GetID() == WINDOW_MUSIC_PLAYLIST)
389 return;
391 // add item 2 playlist
392 const auto item = m_vecItems->Get(iItem);
394 if (item->IsRAR() || item->IsZIP())
395 return;
397 MUSIC_UTILS::QueueItem(item, first ? MUSIC_UTILS::QueuePosition::POSITION_BEGIN
398 : MUSIC_UTILS::QueuePosition::POSITION_END);
400 // select next item
401 m_viewControl.SetSelectedItem(iItem + 1);
404 void CGUIWindowMusicBase::UpdateButtons()
406 CONTROL_ENABLE_ON_CONDITION(CONTROL_BTNRIP, CServiceBroker::GetMediaManager().IsAudio());
408 CONTROL_ENABLE_ON_CONDITION(CONTROL_BTNSCAN,
409 !(m_vecItems->IsVirtualDirectoryRoot() ||
410 m_vecItems->IsMusicDb()));
412 if (CMusicLibraryQueue::GetInstance().IsScanningLibrary())
413 SET_CONTROL_LABEL(CONTROL_BTNSCAN, 14056); // Stop Scan
414 else
415 SET_CONTROL_LABEL(CONTROL_BTNSCAN, 102); // Scan
417 CGUIMediaWindow::UpdateButtons();
420 void CGUIWindowMusicBase::GetContextButtons(int itemNumber, CContextButtons &buttons)
422 CFileItemPtr item;
423 if (itemNumber >= 0 && itemNumber < m_vecItems->Size())
424 item = m_vecItems->Get(itemNumber);
426 if (item)
428 const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
430 // Check for the partymode playlist item.
431 // When "PartyMode.xsp" not exist, only context menu button is edit
432 if (item->IsSmartPlayList() &&
433 (item->GetPath() == profileManager->GetUserDataItem("PartyMode.xsp")) &&
434 !CFileUtils::Exists(item->GetPath()))
436 buttons.Add(CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, 586);
437 return;
440 if (!item->IsParentFolder())
442 //! @todo get rid of IsAddonsPath and IsScript check. CanQueue should be enough!
443 if (item->CanQueue() && !item->IsAddonsPath() && !item->IsScript())
445 if (!item->m_bIsFolder &&
446 (!item->IsPlayList() ||
447 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders))
449 const CPlayerCoreFactory& playerCoreFactory = CServiceBroker::GetPlayerCoreFactory();
451 // check what players we have, if we have multiple display play with option
452 std::vector<std::string> players;
453 playerCoreFactory.GetPlayers(*item, players);
454 if (players.size() >= 1)
455 buttons.Add(CONTEXT_BUTTON_PLAY_WITH, 15213); // Play With...
458 if (item->IsSmartPlayList())
459 buttons.Add(CONTEXT_BUTTON_PLAY_PARTYMODE, 15216); // Play in Partymode
461 if (item->IsSmartPlayList() || m_vecItems->IsSmartPlayList())
462 buttons.Add(CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, 586);
463 else if (item->IsPlayList() || m_vecItems->IsPlayList())
464 buttons.Add(CONTEXT_BUTTON_EDIT, 586);
466 #ifdef HAS_DVD_DRIVE
467 // enable Rip CD Audio or Track button if we have an audio disc
468 if (CServiceBroker::GetMediaManager().IsDiscInDrive() && m_vecItems->IsCDDA())
470 // those cds can also include Audio Tracks: CDExtra and MixedMode!
471 MEDIA_DETECT::CCdInfo* pCdInfo = CServiceBroker::GetMediaManager().GetCdInfo();
472 if (pCdInfo->IsAudio(1) || pCdInfo->IsCDExtra(1) || pCdInfo->IsMixedMode(1))
473 buttons.Add(CONTEXT_BUTTON_RIP_TRACK, 610);
475 #endif
478 // enable CDDB lookup if the current dir is CDDA
479 if (CServiceBroker::GetMediaManager().IsDiscInDrive() && m_vecItems->IsCDDA() &&
480 (profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser))
482 buttons.Add(CONTEXT_BUTTON_CDDB, 16002);
485 CGUIMediaWindow::GetContextButtons(itemNumber, buttons);
488 void CGUIWindowMusicBase::GetNonContextButtons(CContextButtons &buttons)
492 bool CGUIWindowMusicBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
494 CFileItemPtr item;
495 if (itemNumber >= 0 && itemNumber < m_vecItems->Size())
496 item = m_vecItems->Get(itemNumber);
498 if (CGUIDialogContextMenu::OnContextButton("music", item, button))
500 if (button == CONTEXT_BUTTON_REMOVE_SOURCE)
501 OnRemoveSource(itemNumber);
503 Update(m_vecItems->GetPath());
504 return true;
507 switch (button)
509 case CONTEXT_BUTTON_INFO:
510 OnItemInfo(itemNumber);
511 return true;
513 case CONTEXT_BUTTON_EDIT:
515 std::string playlist = item->IsPlayList() ? item->GetPath() : m_vecItems->GetPath(); // save path as activatewindow will destroy our items
516 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST_EDITOR, playlist);
517 // need to update
518 m_vecItems->RemoveDiscCache(GetID());
519 return true;
522 case CONTEXT_BUTTON_EDIT_SMART_PLAYLIST:
524 std::string playlist = item->IsSmartPlayList() ? item->GetPath() : m_vecItems->GetPath(); // save path as activatewindow will destroy our items
525 if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist, "music"))
526 Refresh(true); // need to update
527 return true;
530 case CONTEXT_BUTTON_PLAY_WITH:
532 const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory();
534 std::vector<std::string> players;
535 playerCoreFactory.GetPlayers(*item, players);
536 std::string player = playerCoreFactory.SelectPlayerDialog(players);
537 if (!player.empty())
538 OnClick(itemNumber, player);
539 return true;
542 case CONTEXT_BUTTON_PLAY_PARTYMODE:
543 g_partyModeManager.Enable(PARTYMODECONTEXT_MUSIC, item->GetPath());
544 return true;
546 case CONTEXT_BUTTON_RIP_CD:
547 OnRipCD();
548 return true;
550 #ifdef HAS_CDDA_RIPPER
551 case CONTEXT_BUTTON_CANCEL_RIP_CD:
552 KODI::CDRIP::CCDDARipper::GetInstance().CancelJobs();
553 return true;
554 #endif
556 case CONTEXT_BUTTON_RIP_TRACK:
557 OnRipTrack(itemNumber);
558 return true;
560 case CONTEXT_BUTTON_SCAN:
561 // Check if scanning already and inform user
562 if (CMusicLibraryQueue::GetInstance().IsScanningLibrary())
563 HELPERS::ShowOKDialogText(CVariant{ 189 }, CVariant{ 14057 });
564 else
565 OnScan(itemNumber, true);
566 return true;
568 case CONTEXT_BUTTON_CDDB:
569 if (m_musicdatabase.LookupCDDBInfo(true))
570 Refresh();
571 return true;
573 default:
574 break;
577 return CGUIMediaWindow::OnContextButton(itemNumber, button);
580 bool CGUIWindowMusicBase::OnAddMediaSource()
582 return CGUIDialogMediaSource::ShowAndAddMediaSource("music");
585 void CGUIWindowMusicBase::OnRipCD()
587 if (CServiceBroker::GetMediaManager().IsAudio())
589 if (!g_application.CurrentFileItem().IsCDDA())
591 #ifdef HAS_CDDA_RIPPER
592 KODI::CDRIP::CCDDARipper::GetInstance().RipCD();
593 #endif
595 else
596 HELPERS::ShowOKDialogText(CVariant{257}, CVariant{20099});
600 void CGUIWindowMusicBase::OnRipTrack(int iItem)
602 if (CServiceBroker::GetMediaManager().IsAudio())
604 if (!g_application.CurrentFileItem().IsCDDA())
606 #ifdef HAS_CDDA_RIPPER
607 CFileItemPtr item = m_vecItems->Get(iItem);
608 KODI::CDRIP::CCDDARipper::GetInstance().RipTrack(item.get());
609 #endif
611 else
612 HELPERS::ShowOKDialogText(CVariant{257}, CVariant{20099});
616 void CGUIWindowMusicBase::PlayItem(int iItem)
618 // restrictions should be placed in the appropriate window code
619 // only call the base code if the item passes since this clears
620 // the current playlist
622 const CFileItemPtr pItem = m_vecItems->Get(iItem);
623 #ifdef HAS_DVD_DRIVE
624 if (pItem->IsDVD())
626 MEDIA_DETECT::CAutorun::PlayDiscAskResume(pItem->GetPath());
627 return;
629 #endif
631 // Check for the partymode playlist item, do nothing when "PartyMode.xsp" not exist
632 if (pItem->IsSmartPlayList())
634 const std::shared_ptr<CProfileManager> profileManager =
635 CServiceBroker::GetSettingsComponent()->GetProfileManager();
636 if ((pItem->GetPath() == profileManager->GetUserDataItem("PartyMode.xsp")) &&
637 !CFileUtils::Exists(pItem->GetPath()))
638 return;
641 // if its a folder, build a playlist
642 if (pItem->m_bIsFolder && !pItem->IsPlugin())
644 // make a copy so that we can alter the queue state
645 CFileItemPtr item(new CFileItem(*m_vecItems->Get(iItem)));
647 // Allow queuing of unqueueable items
648 // when we try to queue them directly
649 if (!item->CanQueue())
650 item->SetCanQueue(true);
652 // skip ".."
653 if (item->IsParentFolder())
654 return;
656 CFileItemList queuedItems;
657 MUSIC_UTILS::GetItemsForPlayList(item, queuedItems);
658 if (g_partyModeManager.IsEnabled())
660 g_partyModeManager.AddUserSongs(queuedItems, true);
661 return;
665 std::string strPlayListDirectory = m_vecItems->GetPath();
666 URIUtils::RemoveSlashAtEnd(strPlayListDirectory);
669 CServiceBroker::GetPlaylistPlayer().ClearPlaylist(PLAYLIST::TYPE_MUSIC);
670 CServiceBroker::GetPlaylistPlayer().Reset();
671 CServiceBroker::GetPlaylistPlayer().Add(PLAYLIST::TYPE_MUSIC, queuedItems);
672 CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_MUSIC);
674 // play!
675 CServiceBroker::GetPlaylistPlayer().Play();
677 else if (pItem->IsPlayList())
679 // load the playlist the old way
680 LoadPlayList(pItem->GetPath());
682 else
684 // just a single item, play it
685 //! @todo Add music-specific code for single playback of an item here (See OnClick in MediaWindow, and OnPlayMedia below)
686 OnClick(iItem);
690 void CGUIWindowMusicBase::LoadPlayList(const std::string& strPlayList)
692 // if partymode is active, we disable it
693 if (g_partyModeManager.IsEnabled())
694 g_partyModeManager.Disable();
696 // load a playlist like .m3u, .pls
697 // first get correct factory to load playlist
698 std::unique_ptr<PLAYLIST::CPlayList> pPlayList(PLAYLIST::CPlayListFactory::Create(strPlayList));
699 if (pPlayList)
701 // load it
702 if (!pPlayList->Load(strPlayList))
704 HELPERS::ShowOKDialogText(CVariant{6}, CVariant{477});
705 return; //hmmm unable to load playlist?
709 int iSize = pPlayList->size();
710 if (g_application.ProcessAndStartPlaylist(strPlayList, *pPlayList, PLAYLIST::TYPE_MUSIC))
712 if (m_guiState)
713 m_guiState->SetPlaylistDirectory("playlistmusic://");
714 // activate the playlist window if its not activated yet
715 if (GetID() == CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() && iSize > 1)
717 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST);
722 bool CGUIWindowMusicBase::OnPlayMedia(int iItem, const std::string &player)
724 CFileItemPtr pItem = m_vecItems->Get(iItem);
726 // party mode
727 if (g_partyModeManager.IsEnabled())
729 PLAYLIST::CPlayList playlistTemp;
730 playlistTemp.Add(pItem);
731 g_partyModeManager.AddUserSongs(playlistTemp, !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICPLAYER_QUEUEBYDEFAULT));
732 return true;
734 else if (!pItem->IsPlayList() && !pItem->IsInternetStream())
735 { // single music file - if we get here then we have autoplaynextitem turned off or queuebydefault
736 // turned on, but we still want to use the playlist player in order to handle more queued items
737 // following etc.
738 if ( (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICPLAYER_QUEUEBYDEFAULT) && CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_MUSIC_PLAYLIST_EDITOR) )
740 //! @todo Should the playlist be cleared if nothing is already playing?
741 OnQueueItem(iItem);
742 return true;
744 pItem->SetProperty("playlist_type_hint", m_guiState->GetPlaylist());
745 CServiceBroker::GetPlaylistPlayer().Play(pItem, player);
746 return true;
748 return CGUIMediaWindow::OnPlayMedia(iItem, player);
751 /// \brief Can be overwritten to implement an own tag filling function.
752 /// \param items File items to fill
753 void CGUIWindowMusicBase::OnRetrieveMusicInfo(CFileItemList& items)
755 // No need to attempt to read music file tags for music videos
756 if (items.IsVideoDb())
757 return;
758 if (items.GetFolderCount()==items.Size() || items.IsMusicDb() ||
759 (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICFILES_USETAGS) && !items.IsCDDA()))
761 return;
763 // Start the music info loader thread
764 m_musicInfoLoader.SetProgressCallback(m_dlgProgress);
765 m_musicInfoLoader.Load(items);
767 bool bShowProgress = !CServiceBroker::GetGUI()->GetWindowManager().HasModalDialog(true);
768 bool bProgressVisible = false;
770 auto start = std::chrono::steady_clock::now();
772 while (m_musicInfoLoader.IsLoading())
774 if (bShowProgress)
775 { // Do we have to init a progress dialog?
776 auto end = std::chrono::steady_clock::now();
777 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
779 if (!bProgressVisible && duration.count() > 1500 && m_dlgProgress)
780 { // tag loading takes more then 1.5 secs, show a progress dialog
781 CURL url(items.GetPath());
782 m_dlgProgress->SetHeading(CVariant{189});
783 m_dlgProgress->SetLine(0, CVariant{505});
784 m_dlgProgress->SetLine(1, CVariant{""});
785 m_dlgProgress->SetLine(2, CVariant{url.GetWithoutUserDetails()});
786 m_dlgProgress->Open();
787 m_dlgProgress->ShowProgressBar(true);
788 bProgressVisible = true;
791 if (bProgressVisible && m_dlgProgress && !m_dlgProgress->IsCanceled())
792 { // keep GUI alive
793 m_dlgProgress->Progress();
795 } // if (bShowProgress)
796 KODI::TIME::Sleep(1ms);
797 } // while (m_musicInfoLoader.IsLoading())
799 if (bProgressVisible && m_dlgProgress)
800 m_dlgProgress->Close();
803 bool CGUIWindowMusicBase::GetDirectory(const std::string &strDirectory, CFileItemList &items)
805 items.ClearArt();
806 bool bResult = CGUIMediaWindow::GetDirectory(strDirectory, items);
807 if (bResult)
809 // We want to expand disc images when browsing in file view but not on library, smartplaylist
810 // or node menu music windows
811 if (!items.GetPath().empty() && !StringUtils::StartsWithNoCase(items.GetPath(), "musicdb://") &&
812 !StringUtils::StartsWithNoCase(items.GetPath(), "special://") &&
813 !StringUtils::StartsWithNoCase(items.GetPath(), "library://"))
814 CDirectory::FilterFileDirectories(items, ".iso", true);
816 CMusicThumbLoader loader;
817 loader.FillThumb(items);
819 CQueryParams params;
820 CDirectoryNode::GetDatabaseInfo(items.GetPath(), params);
822 // Get art for directory when album or artist
823 bool artfound = false;
824 std::vector<ArtForThumbLoader> art;
825 if (params.GetAlbumId() > 0)
826 { // Get album and related artist(s) art
827 artfound = m_musicdatabase.GetArtForItem(-1, params.GetAlbumId(), -1, false, art);
829 else if (params.GetArtistId() > 0)
830 { // get artist art
831 artfound = m_musicdatabase.GetArtForItem(-1, -1, params.GetArtistId(), true, art);
833 if (artfound)
835 std::string dirType = MediaTypeArtist;
836 if (params.GetAlbumId() > 0)
837 dirType = MediaTypeAlbum;
838 std::map<std::string, std::string> artmap;
839 for (auto artitem : art)
841 std::string artname;
842 if (dirType == artitem.mediaType)
843 artname = artitem.artType;
844 else if (artitem.prefix.empty())
845 artname = artitem.mediaType + "." + artitem.artType;
846 else
848 if (dirType == MediaTypeAlbum)
849 StringUtils::Replace(artitem.prefix, "albumartist", "artist");
850 artname = artitem.prefix + "." + artitem.artType;
852 artmap.insert(std::make_pair(artname, artitem.url));
854 items.SetArt(artmap);
857 int iWindow = GetID();
858 // Add "New Playlist" items when in the playlists folder, except on playlist editor screen
859 if ((iWindow != WINDOW_MUSIC_PLAYLIST_EDITOR) &&
860 (items.GetPath() == "special://musicplaylists/") && !items.Contains("newplaylist://"))
862 const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
864 CFileItemPtr newPlaylist(new CFileItem(profileManager->GetUserDataItem("PartyMode.xsp"),false));
865 newPlaylist->SetLabel(g_localizeStrings.Get(16035));
866 newPlaylist->SetLabelPreformatted(true);
867 newPlaylist->SetArt("icon", "DefaultPartyMode.png");
868 newPlaylist->m_bIsFolder = true;
869 items.Add(newPlaylist);
871 newPlaylist.reset(new CFileItem("newplaylist://", false));
872 newPlaylist->SetLabel(g_localizeStrings.Get(525));
873 newPlaylist->SetArt("icon", "DefaultAddSource.png");
874 newPlaylist->SetLabelPreformatted(true);
875 newPlaylist->SetSpecialSort(SortSpecialOnBottom);
876 newPlaylist->SetCanQueue(false);
877 items.Add(newPlaylist);
879 newPlaylist.reset(new CFileItem("newsmartplaylist://music", false));
880 newPlaylist->SetLabel(g_localizeStrings.Get(21437));
881 newPlaylist->SetArt("icon", "DefaultAddSource.png");
882 newPlaylist->SetLabelPreformatted(true);
883 newPlaylist->SetSpecialSort(SortSpecialOnBottom);
884 newPlaylist->SetCanQueue(false);
885 items.Add(newPlaylist);
888 // check for .CUE files here.
889 items.FilterCueItems();
891 std::string label;
892 if (items.GetLabel().empty() && m_rootDir.IsSource(items.GetPath(), CMediaSourceSettings::GetInstance().GetSources("music"), &label))
893 items.SetLabel(label);
896 return bResult;
899 bool CGUIWindowMusicBase::CheckFilterAdvanced(CFileItemList &items) const
901 const std::string& content = items.GetContent();
902 if ((items.IsMusicDb() || CanContainFilter(m_strFilterPath)) &&
903 (StringUtils::EqualsNoCase(content, "artists") ||
904 StringUtils::EqualsNoCase(content, "albums") ||
905 StringUtils::EqualsNoCase(content, "songs")))
906 return true;
908 return false;
911 bool CGUIWindowMusicBase::CanContainFilter(const std::string &strDirectory) const
913 return URIUtils::IsProtocol(strDirectory, "musicdb");
916 bool CGUIWindowMusicBase::OnSelect(int iItem)
918 auto item = m_vecItems->Get(iItem);
919 if (item->IsAudioBook())
921 int bookmark;
922 if (m_musicdatabase.GetResumeBookmarkForAudioBook(*item, bookmark) && bookmark > 0)
924 // find which chapter the bookmark belongs to
925 auto itemIt =
926 std::find_if(m_vecItems->cbegin(), m_vecItems->cend(),
927 [&](const CFileItemPtr& item) { return bookmark < item->GetEndOffset(); });
929 if (itemIt != m_vecItems->cend())
931 // ask the user if they want to play or resume
932 CContextButtons choices;
933 choices.Add(MUSIC_SELECT_ACTION_PLAY, 208); // 208 = Play
934 choices.Add(MUSIC_SELECT_ACTION_RESUME,
935 StringUtils::Format(g_localizeStrings.Get(12022), // 12022 = Resume from ...
936 (*itemIt)->GetMusicInfoTag()->GetTitle()));
938 auto choice = CGUIDialogContextMenu::Show(choices);
939 if (choice == MUSIC_SELECT_ACTION_RESUME)
941 (*itemIt)->SetProperty("audiobook_bookmark", bookmark);
942 return CGUIMediaWindow::OnSelect(static_cast<int>(itemIt - m_vecItems->cbegin()));
944 else if (choice < 0)
945 return true;
950 return CGUIMediaWindow::OnSelect(iItem);
953 void CGUIWindowMusicBase::OnInitWindow()
955 CGUIMediaWindow::OnInitWindow();
956 // Prompt for rescan of library to read music file tags that were not processed by previous versions
957 // and accommodate any changes to the way some tags are processed
958 if (m_musicdatabase.GetMusicNeedsTagScan() != 0)
960 if (CServiceBroker::GetGUI()
961 ->GetInfoManager()
962 .GetInfoProviders()
963 .GetLibraryInfoProvider()
964 .GetLibraryBool(LIBRARY_HAS_MUSIC) &&
965 !CMusicLibraryQueue::GetInstance().IsScanningLibrary())
967 // rescan of music library required
968 if (CGUIDialogYesNo::ShowAndGetInput(CVariant{799}, CVariant{38060}))
970 int flags = CMusicInfoScanner::SCAN_RESCAN;
971 // When set to fetch information on update enquire about scraping that as well
972 // It may take some time, so the user may want to do it later by "Query Info For All"
973 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO))
974 if (CGUIDialogYesNo::ShowAndGetInput(CVariant{799}, CVariant{38061}))
975 flags |= CMusicInfoScanner::SCAN_ONLINE;
977 CMusicLibraryQueue::GetInstance().ScanLibrary("", flags, true);
979 m_musicdatabase.SetMusicTagScanVersion(); // once is enough (user may interrupt, but that's up to them)
982 else
984 // no need to force a rescan if there's no music in the library or if a library scan is already active
985 m_musicdatabase.SetMusicTagScanVersion();
990 std::string CGUIWindowMusicBase::GetStartFolder(const std::string &dir)
992 std::string lower(dir); StringUtils::ToLower(lower);
993 if (lower == "plugins" || lower == "addons")
994 return "addons://sources/audio/";
995 else if (lower == "$playlists" || lower == "playlists")
996 return "special://musicplaylists/";
997 return CGUIMediaWindow::GetStartFolder(dir);
1000 void CGUIWindowMusicBase::OnScan(int iItem, bool bPromptRescan /*= false*/)
1002 std::string strPath;
1003 if (iItem < 0 || iItem >= m_vecItems->Size())
1004 strPath = m_vecItems->GetPath();
1005 else if (m_vecItems->Get(iItem)->m_bIsFolder)
1006 strPath = m_vecItems->Get(iItem)->GetPath();
1007 else
1008 { //! @todo MUSICDB - should we allow scanning a single item into the database?
1009 //! This will require changes to the info scanner, which assumes we're running on a folder
1010 strPath = m_vecItems->GetPath();
1012 // Ask for full rescan of music files when scan item from file view context menu
1013 bool doRescan = false;
1014 if (bPromptRescan)
1015 doRescan = CGUIDialogYesNo::ShowAndGetInput(CVariant{ 799 }, CVariant{ 38062 });
1017 DoScan(strPath, doRescan);
1020 void CGUIWindowMusicBase::DoScan(const std::string &strPath, bool bRescan /*= false*/)
1022 if (CMusicLibraryQueue::GetInstance().IsScanningLibrary())
1024 CMusicLibraryQueue::GetInstance().StopLibraryScanning();
1025 return;
1028 // Start background loader
1029 int iControl=GetFocusedControlID();
1030 int flags = 0;
1031 if (bRescan)
1032 flags = CMusicInfoScanner::SCAN_RESCAN;
1033 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO))
1034 flags |= CMusicInfoScanner::SCAN_ONLINE;
1036 CMusicLibraryQueue::GetInstance().ScanLibrary(strPath, flags, true);
1038 SET_CONTROL_FOCUS(iControl, 0);
1039 UpdateButtons();
1042 void CGUIWindowMusicBase::OnRemoveSource(int iItem)
1045 //Remove music source from library, even when leaving songs
1046 CMusicDatabase database;
1047 database.Open();
1048 database.RemoveSource(m_vecItems->Get(iItem)->GetLabel());
1050 bool bCanceled;
1051 if (CGUIDialogYesNo::ShowAndGetInput(CVariant{522}, CVariant{20340}, bCanceled, CVariant{""}, CVariant{""}, CGUIDialogYesNo::NO_TIMEOUT))
1053 MAPSONGS songs;
1054 database.RemoveSongsFromPath(m_vecItems->Get(iItem)->GetPath(), songs, false);
1055 database.CleanupOrphanedItems();
1056 database.CheckArtistLinksChanged();
1057 CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetLibraryInfoProvider().ResetLibraryBools();
1058 m_vecItems->RemoveDiscCache(GetID());
1060 database.Close();
1063 void CGUIWindowMusicBase::OnPrepareFileItems(CFileItemList &items)
1065 CGUIMediaWindow::OnPrepareFileItems(items);
1067 if (!items.IsMusicDb() && !items.IsSmartPlayList())
1068 RetrieveMusicInfo();
1071 void CGUIWindowMusicBase::OnAssignContent(const std::string& oldName, const CMediaSource& source)
1073 // Music scrapers are not source specific, so unlike video there is no content selection logic here.
1074 // Called on having added or edited a music source, this starts scanning items into library when required
1076 //! @todo: do async as updating sources for all albums could be slow??
1077 //Store music source in the music library, even those not scanned
1078 CMusicDatabase database;
1079 database.Open();
1080 database.UpdateSource(oldName, source.strName, source.strPath, source.vecPaths);
1081 database.Close();
1083 // "Add to library" yes/no dialog with additional "settings" custom button
1084 // "Do you want to add the media from this source to your library?"
1085 DialogResponse rep = DialogResponse::CHOICE_CUSTOM;
1086 while (rep == DialogResponse::CHOICE_CUSTOM)
1088 rep = HELPERS::ShowYesNoCustomDialog(CVariant{20444}, CVariant{20447}, CVariant{106}, CVariant{107}, CVariant{10004});
1089 if (rep == DialogResponse::CHOICE_CUSTOM)
1090 // Edit default info provider settings so can be applied during scan
1091 CGUIDialogInfoProviderSettings::Show();
1093 if (rep == DialogResponse::CHOICE_YES)
1094 CMusicLibraryQueue::GetInstance().ScanLibrary(source.strPath,
1095 MUSIC_INFO::CMusicInfoScanner::SCAN_NORMAL, true);