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 "FileItemList.h"
13 #include "GUIUserMessages.h"
14 #include "PlayListPlayer.h"
15 #include "ServiceBroker.h"
16 #include "application/ApplicationComponents.h"
17 #include "application/ApplicationPlayer.h"
18 #include "dialogs/GUIDialogProgress.h"
19 #include "guilib/GUIComponent.h"
20 #include "guilib/GUIWindowManager.h"
21 #include "interfaces/AnnouncementManager.h"
22 #include "messaging/helpers/DialogOKHelper.h"
23 #include "music/MusicDatabase.h"
24 #include "music/tags/MusicInfoTag.h"
25 #include "playlists/PlayList.h"
26 #include "playlists/SmartPlayList.h"
27 #include "profiles/ProfileManager.h"
28 #include "settings/SettingsComponent.h"
29 #include "utils/Random.h"
30 #include "utils/StringUtils.h"
31 #include "utils/Variant.h"
32 #include "utils/log.h"
33 #include "video/VideoDatabase.h"
34 #include "video/VideoInfoTag.h"
39 using namespace KODI::MESSAGING
;
41 #define QUEUE_DEPTH 10
43 CPartyModeManager::CPartyModeManager(void)
50 bool CPartyModeManager::Enable(PartyModeContext context
/*= PARTYMODECONTEXT_MUSIC*/, const std::string
& strXspPath
/*= ""*/)
52 // Filter using our PartyMode xml file
53 PLAYLIST::CSmartPlaylist playlist
;
54 std::string partyModePath
;
57 m_bIsVideo
= context
== PARTYMODECONTEXT_VIDEO
;
59 const std::shared_ptr
<CProfileManager
> profileManager
= CServiceBroker::GetSettingsComponent()->GetProfileManager();
61 if (!strXspPath
.empty()) //if a path to a smartplaylist is supplied use it
62 partyModePath
= strXspPath
;
64 partyModePath
= profileManager
->GetUserDataItem("PartyMode-Video.xsp");
66 partyModePath
= profileManager
->GetUserDataItem("PartyMode.xsp");
68 playlistLoaded
=playlist
.Load(partyModePath
);
72 m_type
= playlist
.GetType();
73 if (context
== PARTYMODECONTEXT_UNKNOWN
)
75 //get it from the xsp file
76 m_bIsVideo
= (StringUtils::EqualsNoCase(m_type
, "video") ||
77 StringUtils::EqualsNoCase(m_type
, "musicvideos") ||
78 StringUtils::EqualsNoCase(m_type
, "mixed"));
82 m_type
= "musicvideos";
86 CGUIDialogProgress
* pDialog
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogProgress
>(WINDOW_DIALOG_PROGRESS
);
87 int iHeading
= (m_bIsVideo
? 20250 : 20121);
88 int iLine0
= (m_bIsVideo
? 20251 : 20123);
89 pDialog
->SetHeading(CVariant
{iHeading
});
90 pDialog
->SetLine(0, CVariant
{iLine0
});
91 pDialog
->SetLine(1, CVariant
{""});
92 pDialog
->SetLine(2, CVariant
{""});
96 std::string strCurrentFilterMusic
;
97 std::string strCurrentFilterVideo
;
98 unsigned int songcount
= 0;
99 unsigned int videocount
= 0;
100 auto start
= std::chrono::steady_clock::now();
102 if (StringUtils::EqualsNoCase(m_type
, "songs") ||
103 StringUtils::EqualsNoCase(m_type
, "mixed"))
108 std::set
<std::string
> playlists
;
111 playlist
.SetType("songs");
112 strCurrentFilterMusic
= playlist
.GetWhereClause(db
, playlists
);
115 CLog::Log(LOGINFO
, "PARTY MODE MANAGER: Registering filter:[{}]", strCurrentFilterMusic
);
116 songcount
= db
.GetRandomSongIDs(CDatabase::Filter(strCurrentFilterMusic
), m_songIDCache
);
117 m_iMatchingSongs
= static_cast<int>(songcount
);
118 if (m_iMatchingSongs
< 1 && StringUtils::EqualsNoCase(m_type
, "songs"))
122 OnError(16031, "Party mode found no matching songs. Aborting.");
129 OnError(16033, "Party mode could not open database. Aborting.");
135 if (StringUtils::EqualsNoCase(m_type
, "musicvideos") ||
136 StringUtils::EqualsNoCase(m_type
, "mixed"))
138 std::vector
< std::pair
<int,int> > songIDs2
;
142 std::set
<std::string
> playlists
;
145 playlist
.SetType("musicvideos");
146 strCurrentFilterVideo
= playlist
.GetWhereClause(db
, playlists
);
149 CLog::Log(LOGINFO
, "PARTY MODE MANAGER: Registering filter:[{}]", strCurrentFilterVideo
);
150 videocount
= db
.GetRandomMusicVideoIDs(strCurrentFilterVideo
, songIDs2
);
151 m_iMatchingSongs
+= static_cast<int>(videocount
);
152 if (m_iMatchingSongs
< 1)
156 OnError(16031, "Party mode found no matching songs. Aborting.");
163 OnError(16033, "Party mode could not open database. Aborting.");
167 m_songIDCache
.insert(m_songIDCache
.end(), songIDs2
.begin(), songIDs2
.end());
170 // Songs and music videos are random from query, but need mixing together when have both
171 if (songcount
> 0 && videocount
> 0 )
172 KODI::UTILS::RandomShuffle(m_songIDCache
.begin(), m_songIDCache
.end());
174 CLog::Log(LOGINFO
,"PARTY MODE MANAGER: Matching songs = {0}", m_iMatchingSongs
);
175 CLog::Log(LOGINFO
,"PARTY MODE MANAGER: Party mode enabled!");
177 PLAYLIST::Id playlistId
= GetPlaylistId();
179 CServiceBroker::GetPlaylistPlayer().ClearPlaylist(playlistId
);
180 CServiceBroker::GetPlaylistPlayer().SetShuffle(playlistId
, false);
181 CServiceBroker::GetPlaylistPlayer().SetRepeat(playlistId
, PLAYLIST::RepeatState::NONE
);
183 pDialog
->SetLine(0, CVariant
{m_bIsVideo
? 20252 : 20124});
186 if (!AddRandomSongs())
192 auto end
= std::chrono::steady_clock::now();
193 auto duration
= std::chrono::duration_cast
<std::chrono::milliseconds
>(end
- start
);
194 CLog::Log(LOGDEBUG
, "{} time for song fetch: {} ms", __FUNCTION__
, duration
.count());
197 CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(playlistId
);
201 // open now playing window
202 if (StringUtils::EqualsNoCase(m_type
, "songs"))
204 if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_MUSIC_PLAYLIST
)
205 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST
);
214 void CPartyModeManager::Disable()
220 CLog::Log(LOGINFO
,"PARTY MODE MANAGER: Party mode disabled.");
223 void CPartyModeManager::OnSongChange(bool bUpdatePlayed
/* = false */)
232 void CPartyModeManager::AddUserSongs(PLAYLIST::CPlayList
& tempList
, bool bPlay
/* = false */)
239 if (m_iLastUserSong
< 0 || bPlay
)
240 iAddAt
= 1; // under the currently playing song
242 iAddAt
= m_iLastUserSong
+ 1; // under the last user added song
244 int iNewUserSongs
= tempList
.size();
245 CLog::Log(LOGINFO
, "PARTY MODE MANAGER: Adding {} user selected songs at {}", iNewUserSongs
,
248 CServiceBroker::GetPlaylistPlayer().GetPlaylist(GetPlaylistId()).Insert(tempList
, iAddAt
);
250 // update last user added song location
251 if (m_iLastUserSong
< 0)
253 m_iLastUserSong
+= iNewUserSongs
;
259 void CPartyModeManager::AddUserSongs(CFileItemList
& tempList
, bool bPlay
/* = false */)
266 if (m_iLastUserSong
< 0 || bPlay
)
267 iAddAt
= 1; // under the currently playing song
269 iAddAt
= m_iLastUserSong
+ 1; // under the last user added song
271 int iNewUserSongs
= tempList
.Size();
272 CLog::Log(LOGINFO
, "PARTY MODE MANAGER: Adding {} user selected songs at {}", iNewUserSongs
,
275 CServiceBroker::GetPlaylistPlayer().GetPlaylist(GetPlaylistId()).Insert(tempList
, iAddAt
);
277 // update last user added song location
278 if (m_iLastUserSong
< 0)
280 m_iLastUserSong
+= iNewUserSongs
;
286 void CPartyModeManager::Process()
295 bool CPartyModeManager::AddRandomSongs()
297 // All songs have been picked, no more to add
298 if (static_cast<int>(m_songIDCache
.size()) == m_iMatchingSongsPicked
)
301 PLAYLIST::CPlayList
& playlist
= CServiceBroker::GetPlaylistPlayer().GetPlaylist(GetPlaylistId());
302 int iMissingSongs
= QUEUE_DEPTH
- playlist
.size();
304 if (iMissingSongs
> 0)
306 // Limit songs fetched to remainder of songID cache
307 iMissingSongs
= std::min(iMissingSongs
, static_cast<int>(m_songIDCache
.size()) - m_iMatchingSongsPicked
);
309 // Pick iMissingSongs from remaining songID cache
310 std::string sqlWhereMusic
= "songview.idSong IN (";
311 std::string sqlWhereVideo
= "idMVideo IN (";
314 bool bMusicVideos
= false;
315 for (int i
= m_iMatchingSongsPicked
; i
< m_iMatchingSongsPicked
+ iMissingSongs
; i
++)
317 std::string song
= StringUtils::Format("{},", m_songIDCache
[i
].second
);
318 if (m_songIDCache
[i
].first
== 1)
320 sqlWhereMusic
+= song
;
323 else if (m_songIDCache
[i
].first
== 2)
325 sqlWhereVideo
+= song
;
333 sqlWhereMusic
.back() = ')'; // replace the last comma with closing bracket
334 // Apply random sort (and limit) at db query for efficiency
335 SortDescription SortDescription
;
336 SortDescription
.sortBy
= SortByRandom
;
337 SortDescription
.limitEnd
= QUEUE_DEPTH
;
338 CMusicDatabase database
;
341 database
.GetSongsFullByWhere("musicdb://songs/", CDatabase::Filter(sqlWhereMusic
),
342 items
, SortDescription
, true);
344 // Get artist and album properties for songs
345 for (auto& item
: items
)
346 database
.SetPropertiesForFileItem(*item
);
351 OnError(16033, "Party mode could not open database. Aborting.");
357 sqlWhereVideo
.back() = ')'; // replace the last comma with closing bracket
358 CVideoDatabase database
;
361 database
.GetMusicVideosByWhere("videodb://musicvideos/titles/",
362 CDatabase::Filter(sqlWhereVideo
), items
);
367 OnError(16033, "Party mode could not open database. Aborting.");
372 // Randomize if the list has music videos or they will be in db order
373 // Songs only are already random.
376 for (const auto& item
: items
)
378 // Update songID cache with order items in playlist
379 if (item
->HasMusicInfoTag())
381 m_songIDCache
[m_iMatchingSongsPicked
].first
= 1;
382 m_songIDCache
[m_iMatchingSongsPicked
].second
= item
->GetMusicInfoTag()->GetDatabaseId();
384 else if (item
->HasVideoInfoTag())
386 m_songIDCache
[m_iMatchingSongsPicked
].first
= 2;
387 m_songIDCache
[m_iMatchingSongsPicked
].second
= item
->GetVideoInfoTag()->m_iDbId
;
389 CFileItemPtr
pItem(item
);
390 Add(pItem
); // inc m_iMatchingSongsPicked
396 void CPartyModeManager::Add(CFileItemPtr
&pItem
)
398 PLAYLIST::CPlayList
& playlist
= CServiceBroker::GetPlaylistPlayer().GetPlaylist(GetPlaylistId());
400 CLog::Log(LOGINFO
, "PARTY MODE MANAGER: Adding randomly selected song at {}:[{}]",
401 playlist
.size() - 1, pItem
->GetPath());
402 m_iMatchingSongsPicked
++;
405 bool CPartyModeManager::ReapSongs()
407 const PLAYLIST::Id playlistId
= GetPlaylistId();
409 // reap any played songs
410 int iCurrentSong
= CServiceBroker::GetPlaylistPlayer().GetCurrentItemIdx();
412 while (i
< CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId
).size())
414 if (i
< iCurrentSong
)
416 CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId
).Remove(i
);
418 if (i
<= m_iLastUserSong
)
425 CServiceBroker::GetPlaylistPlayer().SetCurrentItemIdx(iCurrentSong
);
429 bool CPartyModeManager::MovePlaying()
431 // move current song to the top if its not there
432 int iCurrentSong
= CServiceBroker::GetPlaylistPlayer().GetCurrentItemIdx();
434 if (iCurrentSong
> 0)
436 CLog::Log(LOGINFO
, "PARTY MODE MANAGER: Moving currently playing song from {} to 0",
438 PLAYLIST::CPlayList
& playlist
=
439 CServiceBroker::GetPlaylistPlayer().GetPlaylist(GetPlaylistId());
440 PLAYLIST::CPlayList playlistTemp
;
441 playlistTemp
.Add(playlist
[iCurrentSong
]);
442 playlist
.Remove(iCurrentSong
);
443 for (int i
=0; i
<playlist
.size(); i
++)
444 playlistTemp
.Add(playlist
[i
]);
446 for (int i
=0; i
<playlistTemp
.size(); i
++)
447 playlist
.Add(playlistTemp
[i
]);
449 CServiceBroker::GetPlaylistPlayer().SetCurrentItemIdx(0);
453 void CPartyModeManager::SendUpdateMessage()
455 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
456 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg
);
459 void CPartyModeManager::Play(int iPos
)
461 // Move current song to the top if its not there. Playlist filled up below by
462 // OnSongChange call from application GUI_MSG_PLAYBACK_STARTED processing
463 CServiceBroker::GetPlaylistPlayer().Play(iPos
, "");
464 CLog::Log(LOGINFO
, "PARTY MODE MANAGER: Playing song at {}", iPos
);
467 void CPartyModeManager::OnError(int iError
, const std::string
& strLogMessage
)
470 HELPERS::ShowOKDialogLines(CVariant
{257}, CVariant
{16030}, CVariant
{iError
}, CVariant
{0});
471 CLog::Log(LOGERROR
, "PARTY MODE MANAGER: {}", strLogMessage
);
476 int CPartyModeManager::GetSongsPlayed()
480 return m_iSongsPlayed
;
483 int CPartyModeManager::GetMatchingSongs()
487 return m_iMatchingSongs
;
490 int CPartyModeManager::GetMatchingSongsPicked()
494 return m_iMatchingSongsPicked
;
497 int CPartyModeManager::GetMatchingSongsLeft()
501 return m_iMatchingSongsLeft
;
504 int CPartyModeManager::GetRelaxedSongs()
508 return m_iRelaxedSongs
;
511 int CPartyModeManager::GetRandomSongs()
515 return m_iRandomSongs
;
518 PartyModeContext
CPartyModeManager::GetType() const
521 return PARTYMODECONTEXT_UNKNOWN
;
524 return PARTYMODECONTEXT_VIDEO
;
526 return PARTYMODECONTEXT_MUSIC
;
529 void CPartyModeManager::ClearState()
531 m_iLastUserSong
= -1;
533 m_iMatchingSongs
= 0;
534 m_iMatchingSongsPicked
= 0;
535 m_iMatchingSongsLeft
= 0;
539 m_songIDCache
.clear();
542 void CPartyModeManager::UpdateStats()
544 m_iMatchingSongsLeft
= m_iMatchingSongs
- m_iMatchingSongsPicked
;
545 m_iRandomSongs
= m_iMatchingSongsPicked
;
546 m_iRelaxedSongs
= 0; // unsupported at this stage
549 bool CPartyModeManager::IsEnabled(PartyModeContext context
/* = PARTYMODECONTEXT_UNKNOWN */) const
551 if (!m_bEnabled
) return false;
552 if (context
== PARTYMODECONTEXT_VIDEO
)
554 if (context
== PARTYMODECONTEXT_MUSIC
)
556 return true; // unknown, but we're enabled
559 void CPartyModeManager::Announce()
561 const auto& components
= CServiceBroker::GetAppComponents();
562 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
563 if (appPlayer
->IsPlaying())
567 data
["player"]["playerid"] =
568 static_cast<int>(CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist());
569 data
["property"]["partymode"] = m_bEnabled
;
570 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player
, "OnPropertyChanged",
575 PLAYLIST::Id
CPartyModeManager::GetPlaylistId() const
577 return m_bIsVideo
? PLAYLIST::Id::TYPE_VIDEO
: PLAYLIST::Id::TYPE_MUSIC
;