[filesystem][SpecialProtocol] Removed assert from GetPath
[xbmc.git] / xbmc / PartyModeManager.cpp
blob2f02c23bf57f6d822994ebb5b78c56f772a2d93d
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 "PartyModeManager.h"
11 #include "FileItem.h"
12 #include "GUIUserMessages.h"
13 #include "PlayListPlayer.h"
14 #include "ServiceBroker.h"
15 #include "application/ApplicationComponents.h"
16 #include "application/ApplicationPlayer.h"
17 #include "dialogs/GUIDialogProgress.h"
18 #include "guilib/GUIComponent.h"
19 #include "guilib/GUIWindowManager.h"
20 #include "interfaces/AnnouncementManager.h"
21 #include "messaging/helpers/DialogOKHelper.h"
22 #include "music/MusicDatabase.h"
23 #include "music/tags/MusicInfoTag.h"
24 #include "playlists/PlayList.h"
25 #include "playlists/SmartPlayList.h"
26 #include "profiles/ProfileManager.h"
27 #include "settings/SettingsComponent.h"
28 #include "utils/Random.h"
29 #include "utils/StringUtils.h"
30 #include "utils/Variant.h"
31 #include "utils/log.h"
32 #include "video/VideoDatabase.h"
33 #include "video/VideoInfoTag.h"
35 #include <algorithm>
37 using namespace KODI::MESSAGING;
39 #define QUEUE_DEPTH 10
41 CPartyModeManager::CPartyModeManager(void)
43 m_bIsVideo = false;
44 m_bEnabled = false;
45 ClearState();
48 bool CPartyModeManager::Enable(PartyModeContext context /*= PARTYMODECONTEXT_MUSIC*/, const std::string& strXspPath /*= ""*/)
50 // Filter using our PartyMode xml file
51 CSmartPlaylist playlist;
52 std::string partyModePath;
53 bool playlistLoaded;
55 m_bIsVideo = context == PARTYMODECONTEXT_VIDEO;
57 const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
59 if (!strXspPath.empty()) //if a path to a smartplaylist is supplied use it
60 partyModePath = strXspPath;
61 else if (m_bIsVideo)
62 partyModePath = profileManager->GetUserDataItem("PartyMode-Video.xsp");
63 else
64 partyModePath = profileManager->GetUserDataItem("PartyMode.xsp");
66 playlistLoaded=playlist.Load(partyModePath);
68 if (playlistLoaded)
70 m_type = playlist.GetType();
71 if (context == PARTYMODECONTEXT_UNKNOWN)
73 //get it from the xsp file
74 m_bIsVideo = (StringUtils::EqualsNoCase(m_type, "video") ||
75 StringUtils::EqualsNoCase(m_type, "musicvideos") ||
76 StringUtils::EqualsNoCase(m_type, "mixed"));
79 else if (m_bIsVideo)
80 m_type = "musicvideos";
81 else
82 m_type = "songs";
84 CGUIDialogProgress* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
85 int iHeading = (m_bIsVideo ? 20250 : 20121);
86 int iLine0 = (m_bIsVideo ? 20251 : 20123);
87 pDialog->SetHeading(CVariant{iHeading});
88 pDialog->SetLine(0, CVariant{iLine0});
89 pDialog->SetLine(1, CVariant{""});
90 pDialog->SetLine(2, CVariant{""});
91 pDialog->Open();
93 ClearState();
94 std::string strCurrentFilterMusic;
95 std::string strCurrentFilterVideo;
96 unsigned int songcount = 0;
97 unsigned int videocount = 0;
98 auto start = std::chrono::steady_clock::now();
100 if (StringUtils::EqualsNoCase(m_type, "songs") ||
101 StringUtils::EqualsNoCase(m_type, "mixed"))
103 CMusicDatabase db;
104 if (db.Open())
106 std::set<std::string> playlists;
107 if (playlistLoaded)
109 playlist.SetType("songs");
110 strCurrentFilterMusic = playlist.GetWhereClause(db, playlists);
113 CLog::Log(LOGINFO, "PARTY MODE MANAGER: Registering filter:[{}]", strCurrentFilterMusic);
114 songcount = db.GetRandomSongIDs(CDatabase::Filter(strCurrentFilterMusic), m_songIDCache);
115 m_iMatchingSongs = static_cast<int>(songcount);
116 if (m_iMatchingSongs < 1 && StringUtils::EqualsNoCase(m_type, "songs"))
118 pDialog->Close();
119 db.Close();
120 OnError(16031, "Party mode found no matching songs. Aborting.");
121 return false;
124 else
126 pDialog->Close();
127 OnError(16033, "Party mode could not open database. Aborting.");
128 return false;
130 db.Close();
133 if (StringUtils::EqualsNoCase(m_type, "musicvideos") ||
134 StringUtils::EqualsNoCase(m_type, "mixed"))
136 std::vector< std::pair<int,int> > songIDs2;
137 CVideoDatabase db;
138 if (db.Open())
140 std::set<std::string> playlists;
141 if (playlistLoaded)
143 playlist.SetType("musicvideos");
144 strCurrentFilterVideo = playlist.GetWhereClause(db, playlists);
147 CLog::Log(LOGINFO, "PARTY MODE MANAGER: Registering filter:[{}]", strCurrentFilterVideo);
148 videocount = db.GetRandomMusicVideoIDs(strCurrentFilterVideo, songIDs2);
149 m_iMatchingSongs += static_cast<int>(videocount);
150 if (m_iMatchingSongs < 1)
152 pDialog->Close();
153 db.Close();
154 OnError(16031, "Party mode found no matching songs. Aborting.");
155 return false;
158 else
160 pDialog->Close();
161 OnError(16033, "Party mode could not open database. Aborting.");
162 return false;
164 db.Close();
165 m_songIDCache.insert(m_songIDCache.end(), songIDs2.begin(), songIDs2.end());
168 // Songs and music videos are random from query, but need mixing together when have both
169 if (songcount > 0 && videocount > 0 )
170 KODI::UTILS::RandomShuffle(m_songIDCache.begin(), m_songIDCache.end());
172 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Matching songs = {0}", m_iMatchingSongs);
173 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Party mode enabled!");
175 PLAYLIST::Id playlistId = GetPlaylistId();
177 CServiceBroker::GetPlaylistPlayer().ClearPlaylist(playlistId);
178 CServiceBroker::GetPlaylistPlayer().SetShuffle(playlistId, false);
179 CServiceBroker::GetPlaylistPlayer().SetRepeat(playlistId, PLAYLIST::RepeatState::NONE);
181 pDialog->SetLine(0, CVariant{m_bIsVideo ? 20252 : 20124});
182 pDialog->Progress();
183 // add initial songs
184 if (!AddRandomSongs())
186 pDialog->Close();
187 return false;
190 auto end = std::chrono::steady_clock::now();
191 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
192 CLog::Log(LOGDEBUG, "{} time for song fetch: {} ms", __FUNCTION__, duration.count());
194 // start playing
195 CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(playlistId);
196 Play(0);
198 pDialog->Close();
199 // open now playing window
200 if (StringUtils::EqualsNoCase(m_type, "songs"))
202 if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_MUSIC_PLAYLIST)
203 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST);
206 // done
207 m_bEnabled = true;
208 Announce();
209 return true;
212 void CPartyModeManager::Disable()
214 if (!IsEnabled())
215 return;
216 m_bEnabled = false;
217 Announce();
218 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Party mode disabled.");
221 void CPartyModeManager::OnSongChange(bool bUpdatePlayed /* = false */)
223 if (!IsEnabled())
224 return;
225 Process();
226 if (bUpdatePlayed)
227 m_iSongsPlayed++;
230 void CPartyModeManager::AddUserSongs(PLAYLIST::CPlayList& tempList, bool bPlay /* = false */)
232 if (!IsEnabled())
233 return;
235 // where do we add?
236 int iAddAt = -1;
237 if (m_iLastUserSong < 0 || bPlay)
238 iAddAt = 1; // under the currently playing song
239 else
240 iAddAt = m_iLastUserSong + 1; // under the last user added song
242 int iNewUserSongs = tempList.size();
243 CLog::Log(LOGINFO, "PARTY MODE MANAGER: Adding {} user selected songs at {}", iNewUserSongs,
244 iAddAt);
246 CServiceBroker::GetPlaylistPlayer().GetPlaylist(GetPlaylistId()).Insert(tempList, iAddAt);
248 // update last user added song location
249 if (m_iLastUserSong < 0)
250 m_iLastUserSong = 0;
251 m_iLastUserSong += iNewUserSongs;
253 if (bPlay)
254 Play(1);
257 void CPartyModeManager::AddUserSongs(CFileItemList& tempList, bool bPlay /* = false */)
259 if (!IsEnabled())
260 return;
262 // where do we add?
263 int iAddAt = -1;
264 if (m_iLastUserSong < 0 || bPlay)
265 iAddAt = 1; // under the currently playing song
266 else
267 iAddAt = m_iLastUserSong + 1; // under the last user added song
269 int iNewUserSongs = tempList.Size();
270 CLog::Log(LOGINFO, "PARTY MODE MANAGER: Adding {} user selected songs at {}", iNewUserSongs,
271 iAddAt);
273 CServiceBroker::GetPlaylistPlayer().GetPlaylist(GetPlaylistId()).Insert(tempList, iAddAt);
275 // update last user added song location
276 if (m_iLastUserSong < 0)
277 m_iLastUserSong = 0;
278 m_iLastUserSong += iNewUserSongs;
280 if (bPlay)
281 Play(1);
284 void CPartyModeManager::Process()
286 ReapSongs();
287 MovePlaying();
288 AddRandomSongs();
289 UpdateStats();
290 SendUpdateMessage();
293 bool CPartyModeManager::AddRandomSongs()
295 // All songs have been picked, no more to add
296 if (static_cast<int>(m_songIDCache.size()) == m_iMatchingSongsPicked)
297 return false;
299 PLAYLIST::CPlayList& playlist = CServiceBroker::GetPlaylistPlayer().GetPlaylist(GetPlaylistId());
300 int iMissingSongs = QUEUE_DEPTH - playlist.size();
302 if (iMissingSongs > 0)
304 // Limit songs fetched to remainder of songID cache
305 iMissingSongs = std::min(iMissingSongs, static_cast<int>(m_songIDCache.size()) - m_iMatchingSongsPicked);
307 // Pick iMissingSongs from remaining songID cache
308 std::string sqlWhereMusic = "songview.idSong IN (";
309 std::string sqlWhereVideo = "idMVideo IN (";
311 bool bSongs = false;
312 bool bMusicVideos = false;
313 for (int i = m_iMatchingSongsPicked; i < m_iMatchingSongsPicked + iMissingSongs; i++)
315 std::string song = StringUtils::Format("{},", m_songIDCache[i].second);
316 if (m_songIDCache[i].first == 1)
318 sqlWhereMusic += song;
319 bSongs = true;
321 else if (m_songIDCache[i].first == 2)
323 sqlWhereVideo += song;
324 bMusicVideos = true;
327 CFileItemList items;
329 if (bSongs)
331 sqlWhereMusic.back() = ')'; // replace the last comma with closing bracket
332 // Apply random sort (and limit) at db query for efficiency
333 SortDescription SortDescription;
334 SortDescription.sortBy = SortByRandom;
335 SortDescription.limitEnd = QUEUE_DEPTH;
336 CMusicDatabase database;
337 if (database.Open())
339 database.GetSongsFullByWhere("musicdb://songs/", CDatabase::Filter(sqlWhereMusic),
340 items, SortDescription, true);
342 // Get artist and album properties for songs
343 for (auto& item : items)
344 database.SetPropertiesForFileItem(*item);
345 database.Close();
347 else
349 OnError(16033, "Party mode could not open database. Aborting.");
350 return false;
353 if (bMusicVideos)
355 sqlWhereVideo.back() = ')'; // replace the last comma with closing bracket
356 CVideoDatabase database;
357 if (database.Open())
359 database.GetMusicVideosByWhere("videodb://musicvideos/titles/",
360 CDatabase::Filter(sqlWhereVideo), items);
361 database.Close();
363 else
365 OnError(16033, "Party mode could not open database. Aborting.");
366 return false;
370 // Randomize if the list has music videos or they will be in db order
371 // Songs only are already random.
372 if (bMusicVideos)
373 items.Randomize();
374 for (const auto& item : items)
376 // Update songID cache with order items in playlist
377 if (item->HasMusicInfoTag())
379 m_songIDCache[m_iMatchingSongsPicked].first = 1;
380 m_songIDCache[m_iMatchingSongsPicked].second = item->GetMusicInfoTag()->GetDatabaseId();
382 else if (item->HasVideoInfoTag())
384 m_songIDCache[m_iMatchingSongsPicked].first = 2;
385 m_songIDCache[m_iMatchingSongsPicked].second = item->GetVideoInfoTag()->m_iDbId;
387 CFileItemPtr pItem(item);
388 Add(pItem); // inc m_iMatchingSongsPicked
391 return true;
394 void CPartyModeManager::Add(CFileItemPtr &pItem)
396 PLAYLIST::CPlayList& playlist = CServiceBroker::GetPlaylistPlayer().GetPlaylist(GetPlaylistId());
397 playlist.Add(pItem);
398 CLog::Log(LOGINFO, "PARTY MODE MANAGER: Adding randomly selected song at {}:[{}]",
399 playlist.size() - 1, pItem->GetPath());
400 m_iMatchingSongsPicked++;
403 bool CPartyModeManager::ReapSongs()
405 const PLAYLIST::Id playlistId = GetPlaylistId();
407 // reap any played songs
408 int iCurrentSong = CServiceBroker::GetPlaylistPlayer().GetCurrentSong();
409 int i=0;
410 while (i < CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).size())
412 if (i < iCurrentSong)
414 CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).Remove(i);
415 iCurrentSong--;
416 if (i <= m_iLastUserSong)
417 m_iLastUserSong--;
419 else
420 i++;
423 CServiceBroker::GetPlaylistPlayer().SetCurrentSong(iCurrentSong);
424 return true;
427 bool CPartyModeManager::MovePlaying()
429 // move current song to the top if its not there
430 int iCurrentSong = CServiceBroker::GetPlaylistPlayer().GetCurrentSong();
432 if (iCurrentSong > 0)
434 CLog::Log(LOGINFO, "PARTY MODE MANAGER: Moving currently playing song from {} to 0",
435 iCurrentSong);
436 PLAYLIST::CPlayList& playlist =
437 CServiceBroker::GetPlaylistPlayer().GetPlaylist(GetPlaylistId());
438 PLAYLIST::CPlayList playlistTemp;
439 playlistTemp.Add(playlist[iCurrentSong]);
440 playlist.Remove(iCurrentSong);
441 for (int i=0; i<playlist.size(); i++)
442 playlistTemp.Add(playlist[i]);
443 playlist.Clear();
444 for (int i=0; i<playlistTemp.size(); i++)
445 playlist.Add(playlistTemp[i]);
447 CServiceBroker::GetPlaylistPlayer().SetCurrentSong(0);
448 return true;
451 void CPartyModeManager::SendUpdateMessage()
453 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
454 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
457 void CPartyModeManager::Play(int iPos)
459 // Move current song to the top if its not there. Playlist filled up below by
460 // OnSongChange call from application GUI_MSG_PLAYBACK_STARTED processing
461 CServiceBroker::GetPlaylistPlayer().Play(iPos, "");
462 CLog::Log(LOGINFO, "PARTY MODE MANAGER: Playing song at {}", iPos);
465 void CPartyModeManager::OnError(int iError, const std::string& strLogMessage)
467 // open error dialog
468 HELPERS::ShowOKDialogLines(CVariant{257}, CVariant{16030}, CVariant{iError}, CVariant{0});
469 CLog::Log(LOGERROR, "PARTY MODE MANAGER: {}", strLogMessage);
470 m_bEnabled = false;
471 SendUpdateMessage();
474 int CPartyModeManager::GetSongsPlayed()
476 if (!IsEnabled())
477 return -1;
478 return m_iSongsPlayed;
481 int CPartyModeManager::GetMatchingSongs()
483 if (!IsEnabled())
484 return -1;
485 return m_iMatchingSongs;
488 int CPartyModeManager::GetMatchingSongsPicked()
490 if (!IsEnabled())
491 return -1;
492 return m_iMatchingSongsPicked;
495 int CPartyModeManager::GetMatchingSongsLeft()
497 if (!IsEnabled())
498 return -1;
499 return m_iMatchingSongsLeft;
502 int CPartyModeManager::GetRelaxedSongs()
504 if (!IsEnabled())
505 return -1;
506 return m_iRelaxedSongs;
509 int CPartyModeManager::GetRandomSongs()
511 if (!IsEnabled())
512 return -1;
513 return m_iRandomSongs;
516 PartyModeContext CPartyModeManager::GetType() const
518 if (!IsEnabled())
519 return PARTYMODECONTEXT_UNKNOWN;
521 if (m_bIsVideo)
522 return PARTYMODECONTEXT_VIDEO;
524 return PARTYMODECONTEXT_MUSIC;
527 void CPartyModeManager::ClearState()
529 m_iLastUserSong = -1;
530 m_iSongsPlayed = 0;
531 m_iMatchingSongs = 0;
532 m_iMatchingSongsPicked = 0;
533 m_iMatchingSongsLeft = 0;
534 m_iRelaxedSongs = 0;
535 m_iRandomSongs = 0;
537 m_songIDCache.clear();
540 void CPartyModeManager::UpdateStats()
542 m_iMatchingSongsLeft = m_iMatchingSongs - m_iMatchingSongsPicked;
543 m_iRandomSongs = m_iMatchingSongsPicked;
544 m_iRelaxedSongs = 0; // unsupported at this stage
547 bool CPartyModeManager::IsEnabled(PartyModeContext context /* = PARTYMODECONTEXT_UNKNOWN */) const
549 if (!m_bEnabled) return false;
550 if (context == PARTYMODECONTEXT_VIDEO)
551 return m_bIsVideo;
552 if (context == PARTYMODECONTEXT_MUSIC)
553 return !m_bIsVideo;
554 return true; // unknown, but we're enabled
557 void CPartyModeManager::Announce()
559 const auto& components = CServiceBroker::GetAppComponents();
560 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
561 if (appPlayer->IsPlaying())
563 CVariant data;
565 data["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();
566 data["property"]["partymode"] = m_bEnabled;
567 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPropertyChanged",
568 data);
572 PLAYLIST::Id CPartyModeManager::GetPlaylistId() const
574 return m_bIsVideo ? PLAYLIST::TYPE_VIDEO : PLAYLIST::TYPE_MUSIC;