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 "PartyModeManager.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"
37 using namespace KODI::MESSAGING
;
39 #define QUEUE_DEPTH 10
41 CPartyModeManager::CPartyModeManager(void)
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
;
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
;
62 partyModePath
= profileManager
->GetUserDataItem("PartyMode-Video.xsp");
64 partyModePath
= profileManager
->GetUserDataItem("PartyMode.xsp");
66 playlistLoaded
=playlist
.Load(partyModePath
);
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"));
80 m_type
= "musicvideos";
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
{""});
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"))
106 std::set
<std::string
> playlists
;
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"))
120 OnError(16031, "Party mode found no matching songs. Aborting.");
127 OnError(16033, "Party mode could not open database. Aborting.");
133 if (StringUtils::EqualsNoCase(m_type
, "musicvideos") ||
134 StringUtils::EqualsNoCase(m_type
, "mixed"))
136 std::vector
< std::pair
<int,int> > songIDs2
;
140 std::set
<std::string
> playlists
;
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)
154 OnError(16031, "Party mode found no matching songs. Aborting.");
161 OnError(16033, "Party mode could not open database. Aborting.");
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});
184 if (!AddRandomSongs())
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());
195 CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(playlistId
);
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
);
212 void CPartyModeManager::Disable()
218 CLog::Log(LOGINFO
,"PARTY MODE MANAGER: Party mode disabled.");
221 void CPartyModeManager::OnSongChange(bool bUpdatePlayed
/* = false */)
230 void CPartyModeManager::AddUserSongs(PLAYLIST::CPlayList
& tempList
, bool bPlay
/* = false */)
237 if (m_iLastUserSong
< 0 || bPlay
)
238 iAddAt
= 1; // under the currently playing song
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
,
246 CServiceBroker::GetPlaylistPlayer().GetPlaylist(GetPlaylistId()).Insert(tempList
, iAddAt
);
248 // update last user added song location
249 if (m_iLastUserSong
< 0)
251 m_iLastUserSong
+= iNewUserSongs
;
257 void CPartyModeManager::AddUserSongs(CFileItemList
& tempList
, bool bPlay
/* = false */)
264 if (m_iLastUserSong
< 0 || bPlay
)
265 iAddAt
= 1; // under the currently playing song
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
,
273 CServiceBroker::GetPlaylistPlayer().GetPlaylist(GetPlaylistId()).Insert(tempList
, iAddAt
);
275 // update last user added song location
276 if (m_iLastUserSong
< 0)
278 m_iLastUserSong
+= iNewUserSongs
;
284 void CPartyModeManager::Process()
293 bool CPartyModeManager::AddRandomSongs()
295 // All songs have been picked, no more to add
296 if (static_cast<int>(m_songIDCache
.size()) == m_iMatchingSongsPicked
)
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 (";
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
;
321 else if (m_songIDCache
[i
].first
== 2)
323 sqlWhereVideo
+= song
;
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
;
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
);
349 OnError(16033, "Party mode could not open database. Aborting.");
355 sqlWhereVideo
.back() = ')'; // replace the last comma with closing bracket
356 CVideoDatabase database
;
359 database
.GetMusicVideosByWhere("videodb://musicvideos/titles/",
360 CDatabase::Filter(sqlWhereVideo
), items
);
365 OnError(16033, "Party mode could not open database. Aborting.");
370 // Randomize if the list has music videos or they will be in db order
371 // Songs only are already random.
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
394 void CPartyModeManager::Add(CFileItemPtr
&pItem
)
396 PLAYLIST::CPlayList
& playlist
= CServiceBroker::GetPlaylistPlayer().GetPlaylist(GetPlaylistId());
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();
410 while (i
< CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId
).size())
412 if (i
< iCurrentSong
)
414 CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId
).Remove(i
);
416 if (i
<= m_iLastUserSong
)
423 CServiceBroker::GetPlaylistPlayer().SetCurrentSong(iCurrentSong
);
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",
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
]);
444 for (int i
=0; i
<playlistTemp
.size(); i
++)
445 playlist
.Add(playlistTemp
[i
]);
447 CServiceBroker::GetPlaylistPlayer().SetCurrentSong(0);
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
)
468 HELPERS::ShowOKDialogLines(CVariant
{257}, CVariant
{16030}, CVariant
{iError
}, CVariant
{0});
469 CLog::Log(LOGERROR
, "PARTY MODE MANAGER: {}", strLogMessage
);
474 int CPartyModeManager::GetSongsPlayed()
478 return m_iSongsPlayed
;
481 int CPartyModeManager::GetMatchingSongs()
485 return m_iMatchingSongs
;
488 int CPartyModeManager::GetMatchingSongsPicked()
492 return m_iMatchingSongsPicked
;
495 int CPartyModeManager::GetMatchingSongsLeft()
499 return m_iMatchingSongsLeft
;
502 int CPartyModeManager::GetRelaxedSongs()
506 return m_iRelaxedSongs
;
509 int CPartyModeManager::GetRandomSongs()
513 return m_iRandomSongs
;
516 PartyModeContext
CPartyModeManager::GetType() const
519 return PARTYMODECONTEXT_UNKNOWN
;
522 return PARTYMODECONTEXT_VIDEO
;
524 return PARTYMODECONTEXT_MUSIC
;
527 void CPartyModeManager::ClearState()
529 m_iLastUserSong
= -1;
531 m_iMatchingSongs
= 0;
532 m_iMatchingSongsPicked
= 0;
533 m_iMatchingSongsLeft
= 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
)
552 if (context
== PARTYMODECONTEXT_MUSIC
)
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())
565 data
["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();
566 data
["property"]["partymode"] = m_bEnabled
;
567 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player
, "OnPropertyChanged",
572 PLAYLIST::Id
CPartyModeManager::GetPlaylistId() const
574 return m_bIsVideo
? PLAYLIST::TYPE_VIDEO
: PLAYLIST::TYPE_MUSIC
;