2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "threads/SystemClock.h"
22 #include "PartyModeManager.h"
23 #include "PlayListPlayer.h"
24 #include "music/MusicDatabase.h"
25 #include "music/windows/GUIWindowMusicPlaylist.h"
26 #include "video/VideoDatabase.h"
27 #include "playlists/SmartPlayList.h"
28 #include "profiles/ProfilesManager.h"
29 #include "dialogs/GUIDialogProgress.h"
30 #include "GUIUserMessages.h"
31 #include "guilib/GUIWindowManager.h"
32 #include "dialogs/GUIDialogOK.h"
33 #include "playlists/PlayList.h"
34 #include "utils/TimeUtils.h"
35 #include "utils/log.h"
36 #include "utils/StringUtils.h"
37 #include "Application.h"
38 #include "interfaces/AnnouncementManager.h"
41 using namespace PLAYLIST;
43 #define QUEUE_DEPTH 10
45 CPartyModeManager::CPartyModeManager(void)
49 m_strCurrentFilterMusic.clear();
50 m_strCurrentFilterVideo.clear();
54 CPartyModeManager::~CPartyModeManager(void)
58 bool CPartyModeManager::Enable(PartyModeContext context /*= PARTYMODECONTEXT_MUSIC*/, const CStdString& strXspPath /*= ""*/)
60 // Filter using our PartyMode xml file
61 CSmartPlaylist playlist;
62 CStdString partyModePath;
65 m_bIsVideo = context == PARTYMODECONTEXT_VIDEO;
66 if (!strXspPath.empty()) //if a path to a smartplaylist is supplied use it
67 partyModePath = strXspPath;
69 partyModePath = CProfilesManager::Get().GetUserDataItem("PartyMode-Video.xsp");
71 partyModePath = CProfilesManager::Get().GetUserDataItem("PartyMode.xsp");
73 playlistLoaded=playlist.Load(partyModePath);
77 m_type = playlist.GetType();
78 if (context == PARTYMODECONTEXT_UNKNOWN)
80 //get it from the xsp file
81 m_bIsVideo = (m_type.Equals("video") || m_type.Equals("musicvideos") || m_type.Equals("mixed"));
84 if (m_type.Equals("mixed"))
85 playlist.SetType("songs");
87 if (m_type.Equals("mixed"))
88 playlist.SetType("video");
90 playlist.SetType(m_type);
94 m_strCurrentFilterMusic.clear();
95 m_strCurrentFilterVideo.clear();
96 m_type = m_bIsVideo ? "musicvideos" : "songs";
99 CGUIDialogProgress* pDialog = (CGUIDialogProgress*)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
100 int iHeading = (m_bIsVideo ? 20250 : 20121);
101 int iLine0 = (m_bIsVideo ? 20251 : 20123);
102 pDialog->SetHeading(iHeading);
103 pDialog->SetLine(0, iLine0);
104 pDialog->SetLine(1, "");
105 pDialog->SetLine(2, "");
106 pDialog->StartModal();
109 unsigned int time = XbmcThreads::SystemClockMillis();
110 vector< pair<int,int> > songIDs;
111 if (m_type.Equals("songs") || m_type.Equals("mixed"))
116 set<CStdString> playlists;
117 if ( playlistLoaded )
118 m_strCurrentFilterMusic = playlist.GetWhereClause(db, playlists);
120 CLog::Log(LOGINFO, "PARTY MODE MANAGER: Registering filter:[%s]", m_strCurrentFilterMusic.c_str());
121 m_iMatchingSongs = (int)db.GetSongIDs(m_strCurrentFilterMusic, songIDs);
122 if (m_iMatchingSongs < 1 && m_type.Equals("songs"))
126 OnError(16031, (CStdString)"Party mode found no matching songs. Aborting.");
133 OnError(16033, (CStdString)"Party mode could not open database. Aborting.");
139 if (m_type.Equals("musicvideos") || m_type.Equals("mixed"))
141 vector< pair<int,int> > songIDs2;
145 set<CStdString> playlists;
146 if ( playlistLoaded )
147 m_strCurrentFilterVideo = playlist.GetWhereClause(db, playlists);
149 CLog::Log(LOGINFO, "PARTY MODE MANAGER: Registering filter:[%s]", m_strCurrentFilterVideo.c_str());
150 m_iMatchingSongs += (int)db.GetMusicVideoIDs(m_strCurrentFilterVideo, songIDs2);
151 if (m_iMatchingSongs < 1)
155 OnError(16031, (CStdString)"Party mode found no matching songs. Aborting.");
162 OnError(16033, (CStdString)"Party mode could not open database. Aborting.");
166 songIDs.insert(songIDs.end(),songIDs2.begin(),songIDs2.end());
169 // calculate history size
170 if (m_iMatchingSongs < 50)
171 m_songsInHistory = 0;
173 m_songsInHistory = (int)(m_iMatchingSongs/2);
174 if (m_songsInHistory > 200)
175 m_songsInHistory = 200;
177 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Matching songs = %i, History size = %i", m_iMatchingSongs, m_songsInHistory);
178 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Party mode enabled!");
180 int iPlaylist = m_bIsVideo ? PLAYLIST_VIDEO : PLAYLIST_MUSIC;
182 g_playlistPlayer.ClearPlaylist(iPlaylist);
183 g_playlistPlayer.SetShuffle(iPlaylist, false);
184 g_playlistPlayer.SetRepeat(iPlaylist, PLAYLIST::REPEAT_NONE);
186 pDialog->SetLine(0, (m_bIsVideo ? 20252 : 20124));
189 if (!AddInitialSongs(songIDs))
194 CLog::Log(LOGDEBUG, "%s time for song fetch: %u",
195 __FUNCTION__, XbmcThreads::SystemClockMillis() - time);
198 g_playlistPlayer.SetCurrentPlaylist(iPlaylist);
202 // open now playing window
203 if (m_type.Equals("songs"))
205 if (g_windowManager.GetActiveWindow() != WINDOW_MUSIC_PLAYLIST)
206 g_windowManager.ActivateWindow(WINDOW_MUSIC_PLAYLIST);
215 void CPartyModeManager::Disable()
221 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Party mode disabled.");
224 void CPartyModeManager::OnSongChange(bool bUpdatePlayed /* = false */)
233 void CPartyModeManager::AddUserSongs(CPlayList& tempList, bool bPlay /* = false */)
240 if (m_iLastUserSong < 0 || bPlay)
241 iAddAt = 1; // under the currently playing song
243 iAddAt = m_iLastUserSong + 1; // under the last user added song
245 int iNewUserSongs = tempList.size();
246 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Adding %i user selected songs at %i", iNewUserSongs, iAddAt);
248 int iPlaylist = PLAYLIST_MUSIC;
250 iPlaylist = PLAYLIST_VIDEO;
251 g_playlistPlayer.GetPlaylist(iPlaylist).Insert(tempList, iAddAt);
253 // update last user added song location
254 if (m_iLastUserSong < 0)
256 m_iLastUserSong += iNewUserSongs;
262 void CPartyModeManager::AddUserSongs(CFileItemList& tempList, bool bPlay /* = false */)
269 if (m_iLastUserSong < 0 || bPlay)
270 iAddAt = 1; // under the currently playing song
272 iAddAt = m_iLastUserSong + 1; // under the last user added song
274 int iNewUserSongs = tempList.Size();
275 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Adding %i user selected songs at %i", iNewUserSongs, iAddAt);
277 int iPlaylist = PLAYLIST_MUSIC;
279 iPlaylist = PLAYLIST_VIDEO;
281 g_playlistPlayer.GetPlaylist(iPlaylist).Insert(tempList, iAddAt);
283 // update last user added song location
284 if (m_iLastUserSong < 0)
286 m_iLastUserSong += iNewUserSongs;
292 void CPartyModeManager::Process()
301 bool CPartyModeManager::AddRandomSongs(int iSongs /* = 0 */)
303 int iPlaylist = PLAYLIST_MUSIC;
305 iPlaylist = PLAYLIST_VIDEO;
307 CPlayList& playlist = g_playlistPlayer.GetPlaylist(iPlaylist);
308 int iMissingSongs = QUEUE_DEPTH - playlist.size();
310 iSongs = iMissingSongs;
311 // distribute between types if mixed
312 int iSongsToAdd=iSongs;
313 int iVidsToAdd=iSongs;
314 if (m_type.Equals("mixed"))
318 if (rand() % 10 < 7) // 70 % chance of grabbing a song
323 if (iSongs > 1) // grab 70 % songs, 30 % mvids
325 iSongsToAdd = (int).7f*iSongs;
326 iVidsToAdd = (int).3f*iSongs;
327 while (iSongsToAdd+iVidsToAdd < iSongs) // correct any rounding by adding songs
332 // add songs to fill queue
333 if (m_type.Equals("songs") || m_type.Equals("mixed"))
335 CMusicDatabase database;
339 // 1. Grab a random entry from the database using a where clause
340 // 2. Iterate on iSongs.
342 // Note: At present, this method is faster than the alternative, which is to grab
343 // all valid songids, then select a random number of them (as done in AddInitialSongs()).
344 // The reason for this is simply the number of songs we are requesting - we generally
345 // only want one here. Any more than about 3 songs and it is more efficient
346 // to use the technique in AddInitialSongs. As it's unlikely that we'll require
347 // more than 1 song at a time here, this method is faster.
349 for (int i = 0; i < iSongsToAdd; i++)
351 pair<CStdString,CStdString> whereClause = GetWhereClauseWithHistory();
352 CFileItemPtr item(new CFileItem);
354 if (database.GetRandomSong(item.get(), songID, whereClause.first))
357 AddToHistory(1,songID);
369 OnError(16034, (CStdString)"Cannot get songs from database. Aborting.");
375 OnError(16033, (CStdString)"Party mode could not open database. Aborting.");
380 if (m_type.Equals("musicvideos") || m_type.Equals("mixed"))
382 CVideoDatabase database;
386 // 1. Grab a random entry from the database using a where clause
387 // 2. Iterate on iSongs.
389 // Note: At present, this method is faster than the alternative, which is to grab
390 // all valid songids, then select a random number of them (as done in AddInitialSongs()).
391 // The reason for this is simply the number of songs we are requesting - we generally
392 // only want one here. Any more than about 3 songs and it is more efficient
393 // to use the technique in AddInitialSongs. As it's unlikely that we'll require
394 // more than 1 song at a time here, this method is faster.
396 for (int i = 0; i < iVidsToAdd; i++)
398 pair<CStdString,CStdString> whereClause = GetWhereClauseWithHistory();
399 CFileItemPtr item(new CFileItem);
401 if (database.GetRandomMusicVideo(item.get(), songID, whereClause.second))
404 AddToHistory(2,songID);
416 OnError(16034, (CStdString)"Cannot get songs from database. Aborting.");
422 OnError(16033, (CStdString)"Party mode could not open database. Aborting.");
430 void CPartyModeManager::Add(CFileItemPtr &pItem)
432 int iPlaylist = m_bIsVideo ? PLAYLIST_VIDEO : PLAYLIST_MUSIC;
433 if (pItem->HasMusicInfoTag())
435 CMusicDatabase database;
437 database.SetPropertiesForFileItem(*pItem);
440 CPlayList& playlist = g_playlistPlayer.GetPlaylist(iPlaylist);
442 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Adding randomly selected song at %i:[%s]", playlist.size() - 1, pItem->GetPath().c_str());
443 m_iMatchingSongsPicked++;
446 bool CPartyModeManager::ReapSongs()
448 int iPlaylist = m_bIsVideo ? PLAYLIST_VIDEO : PLAYLIST_MUSIC;
450 // reap any played songs
451 int iCurrentSong = g_playlistPlayer.GetCurrentSong();
453 while (i < g_playlistPlayer.GetPlaylist(iPlaylist).size())
455 if (i < iCurrentSong)
457 g_playlistPlayer.GetPlaylist(iPlaylist).Remove(i);
459 if (i <= m_iLastUserSong)
466 g_playlistPlayer.SetCurrentSong(iCurrentSong);
470 bool CPartyModeManager::MovePlaying()
472 // move current song to the top if its not there
473 int iCurrentSong = g_playlistPlayer.GetCurrentSong();
474 int iPlaylist = m_bIsVideo ? PLAYLIST_MUSIC : PLAYLIST_VIDEO;
476 if (iCurrentSong > 0)
478 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Moving currently playing song from %i to 0", iCurrentSong);
479 CPlayList &playlist = g_playlistPlayer.GetPlaylist(iPlaylist);
480 CPlayList playlistTemp;
481 playlistTemp.Add(playlist[iCurrentSong]);
482 playlist.Remove(iCurrentSong);
483 for (int i=0; i<playlist.size(); i++)
484 playlistTemp.Add(playlist[i]);
486 for (int i=0; i<playlistTemp.size(); i++)
487 playlist.Add(playlistTemp[i]);
489 g_playlistPlayer.SetCurrentSong(0);
493 void CPartyModeManager::SendUpdateMessage()
495 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
496 g_windowManager.SendThreadMessage(msg);
499 void CPartyModeManager::Play(int iPos)
501 // move current song to the top if its not there
502 g_playlistPlayer.Play(iPos);
503 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Playing song at %i", iPos);
507 void CPartyModeManager::OnError(int iError, const CStdString& strLogMessage)
510 CGUIDialogOK::ShowAndGetInput(257, 16030, iError, 0);
511 CLog::Log(LOGERROR, "PARTY MODE MANAGER: %s", strLogMessage.c_str());
516 int CPartyModeManager::GetSongsPlayed()
520 return m_iSongsPlayed;
523 int CPartyModeManager::GetMatchingSongs()
527 return m_iMatchingSongs;
530 int CPartyModeManager::GetMatchingSongsPicked()
534 return m_iMatchingSongsPicked;
537 int CPartyModeManager::GetMatchingSongsLeft()
541 return m_iMatchingSongsLeft;
544 int CPartyModeManager::GetRelaxedSongs()
548 return m_iRelaxedSongs;
551 int CPartyModeManager::GetRandomSongs()
555 return m_iRandomSongs;
558 PartyModeContext CPartyModeManager::GetType() const
561 return PARTYMODECONTEXT_UNKNOWN;
564 return PARTYMODECONTEXT_VIDEO;
566 return PARTYMODECONTEXT_MUSIC;
569 void CPartyModeManager::ClearState()
571 m_iLastUserSong = -1;
573 m_iMatchingSongs = 0;
574 m_iMatchingSongsPicked = 0;
575 m_iMatchingSongsLeft = 0;
579 m_songsInHistory = 0;
583 void CPartyModeManager::UpdateStats()
585 m_iMatchingSongsLeft = m_iMatchingSongs - m_iMatchingSongsPicked;
586 m_iRandomSongs = m_iMatchingSongsPicked;
587 m_iRelaxedSongs = 0; // unsupported at this stage
590 bool CPartyModeManager::AddInitialSongs(vector<pair<int,int> > &songIDs)
592 int iPlaylist = m_bIsVideo ? PLAYLIST_VIDEO : PLAYLIST_MUSIC;
594 CPlayList& playlist = g_playlistPlayer.GetPlaylist(iPlaylist);
595 int iMissingSongs = QUEUE_DEPTH - playlist.size();
596 if (iMissingSongs > 0)
598 // generate iMissingSongs random ids from songIDs
599 if (iMissingSongs > (int)songIDs.size())
600 return false; // can't do it if we have less songs than we need
602 vector<pair<int,int> > chosenSongIDs;
603 GetRandomSelection(songIDs, iMissingSongs, chosenSongIDs);
604 CStdString sqlWhereMusic = "songview.idSong IN (";
605 CStdString sqlWhereVideo = "idMVideo IN (";
607 for (vector< pair<int,int> >::iterator it = chosenSongIDs.begin(); it != chosenSongIDs.end(); it++)
609 CStdString song = StringUtils::Format("%i,", it->second);
611 sqlWhereMusic += song;
613 sqlWhereVideo += song;
615 // add songs to fill queue
618 if (sqlWhereMusic.size() > 26)
620 sqlWhereMusic[sqlWhereMusic.size() - 1] = ')'; // replace the last comma with closing bracket
621 CMusicDatabase database;
623 database.GetSongsByWhere("musicdb://songs/", sqlWhereMusic, items);
625 if (sqlWhereVideo.size() > 19)
627 sqlWhereVideo[sqlWhereVideo.size() - 1] = ')'; // replace the last comma with closing bracket
628 CVideoDatabase database;
630 database.GetMusicVideosByWhere("videodb://musicvideos/titles/", sqlWhereVideo, items);
633 m_history = chosenSongIDs;
634 items.Randomize(); //randomizing the initial list or they will be in database order
635 for (int i = 0; i < items.Size(); i++)
637 CFileItemPtr item(items[i]);
639 // TODO: Allow "relaxed restrictions" later?
645 pair<CStdString,CStdString> CPartyModeManager::GetWhereClauseWithHistory() const
647 // now add this on to the normal where clause
648 std::vector<std::string> historyItemsMusic;
649 std::vector<std::string> historyItemsVideo;
650 for (unsigned int i = 0; i < m_history.size(); i++)
652 std::string number = StringUtils::Format("%i", m_history[i].second);
653 if (m_history[i].first == 1)
654 historyItemsMusic.push_back(number);
655 if (m_history[i].first == 2)
656 historyItemsVideo.push_back(number);
659 std::string historyWhereMusic;
660 if (!historyItemsMusic.empty())
662 if (!m_strCurrentFilterMusic.empty())
663 historyWhereMusic = m_strCurrentFilterMusic + " and ";
664 historyWhereMusic += "songview.idSong not in (" + StringUtils::Join(historyItemsMusic, ", ") + ")";
667 std::string historyWhereVideo;
668 if (!historyItemsVideo.empty())
670 if (!m_strCurrentFilterVideo.empty())
671 historyWhereVideo = m_strCurrentFilterVideo + " and ";
672 historyWhereVideo += "idMVideo not in (" + StringUtils::Join(historyItemsVideo, ", ") + ")";
675 return make_pair(historyWhereMusic, historyWhereVideo);
678 void CPartyModeManager::AddToHistory(int type, int songID)
680 while (m_history.size() >= m_songsInHistory && m_songsInHistory)
681 m_history.erase(m_history.begin());
682 m_history.push_back(make_pair(type,songID));
685 void CPartyModeManager::GetRandomSelection(vector< pair<int,int> >& in, unsigned int number, vector< pair<int,int> >& out)
687 number = min(number, (unsigned int)in.size());
688 random_shuffle(in.begin(), in.end());
689 out.assign(in.begin(), in.begin() + number);
692 bool CPartyModeManager::IsEnabled(PartyModeContext context /* = PARTYMODECONTEXT_UNKNOWN */) const
694 if (!m_bEnabled) return false;
695 if (context == PARTYMODECONTEXT_VIDEO)
697 if (context == PARTYMODECONTEXT_MUSIC)
699 return true; // unknown, but we're enabled
702 void CPartyModeManager::Announce()
704 if (g_application.m_pPlayer->IsPlaying())
708 data["player"]["playerid"] = g_playlistPlayer.GetCurrentPlaylist();
709 data["property"]["partymode"] = m_bEnabled;
710 ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::Player, "xbmc", "OnPropertyChanged", data);