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 "Application.h"
37 #include "interfaces/AnnouncementManager.h"
40 using namespace PLAYLIST;
42 #define QUEUE_DEPTH 10
44 CPartyModeManager::CPartyModeManager(void)
48 m_strCurrentFilterMusic.Empty();
49 m_strCurrentFilterVideo.Empty();
53 CPartyModeManager::~CPartyModeManager(void)
57 bool CPartyModeManager::Enable(PartyModeContext context /*= PARTYMODECONTEXT_MUSIC*/, const CStdString& strXspPath /*= ""*/)
59 // Filter using our PartyMode xml file
60 CSmartPlaylist playlist;
61 CStdString partyModePath;
64 m_bIsVideo = context == PARTYMODECONTEXT_VIDEO;
65 if (!strXspPath.IsEmpty()) //if a path to a smartplaylist is supplied use it
66 partyModePath = strXspPath;
68 partyModePath = CProfilesManager::Get().GetUserDataItem("PartyMode-Video.xsp");
70 partyModePath = CProfilesManager::Get().GetUserDataItem("PartyMode.xsp");
72 playlistLoaded=playlist.Load(partyModePath);
76 m_type = playlist.GetType();
77 if (context == PARTYMODECONTEXT_UNKNOWN)
79 //get it from the xsp file
80 m_bIsVideo = (m_type.Equals("video") || m_type.Equals("musicvideos") || m_type.Equals("mixed"));
83 if (m_type.Equals("mixed"))
84 playlist.SetType("songs");
86 if (m_type.Equals("mixed"))
87 playlist.SetType("video");
89 playlist.SetType(m_type);
93 m_strCurrentFilterMusic.Empty();
94 m_strCurrentFilterVideo.Empty();
95 m_type = m_bIsVideo ? "musicvideos" : "songs";
98 CGUIDialogProgress* pDialog = (CGUIDialogProgress*)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
99 int iHeading = (m_bIsVideo ? 20250 : 20121);
100 int iLine0 = (m_bIsVideo ? 20251 : 20123);
101 pDialog->SetHeading(iHeading);
102 pDialog->SetLine(0, iLine0);
103 pDialog->SetLine(1, "");
104 pDialog->SetLine(2, "");
105 pDialog->StartModal();
108 unsigned int time = XbmcThreads::SystemClockMillis();
109 vector< pair<int,int> > songIDs;
110 if (m_type.Equals("songs") || m_type.Equals("mixed"))
115 set<CStdString> playlists;
116 if ( playlistLoaded )
117 m_strCurrentFilterMusic = playlist.GetWhereClause(db, playlists);
119 CLog::Log(LOGINFO, "PARTY MODE MANAGER: Registering filter:[%s]", m_strCurrentFilterMusic.c_str());
120 m_iMatchingSongs = (int)db.GetSongIDs(m_strCurrentFilterMusic, songIDs);
121 if (m_iMatchingSongs < 1 && m_type.Equals("songs"))
125 OnError(16031, (CStdString)"Party mode found no matching songs. Aborting.");
132 OnError(16033, (CStdString)"Party mode could not open database. Aborting.");
138 if (m_type.Equals("musicvideos") || m_type.Equals("mixed"))
140 vector< pair<int,int> > songIDs2;
144 set<CStdString> playlists;
145 if ( playlistLoaded )
146 m_strCurrentFilterVideo = playlist.GetWhereClause(db, playlists);
148 CLog::Log(LOGINFO, "PARTY MODE MANAGER: Registering filter:[%s]", m_strCurrentFilterVideo.c_str());
149 m_iMatchingSongs += (int)db.GetMusicVideoIDs(m_strCurrentFilterVideo, songIDs2);
150 if (m_iMatchingSongs < 1)
154 OnError(16031, (CStdString)"Party mode found no matching songs. Aborting.");
161 OnError(16033, (CStdString)"Party mode could not open database. Aborting.");
165 songIDs.insert(songIDs.end(),songIDs2.begin(),songIDs2.end());
168 // calculate history size
169 if (m_iMatchingSongs < 50)
170 m_songsInHistory = 0;
172 m_songsInHistory = (int)(m_iMatchingSongs/2);
173 if (m_songsInHistory > 200)
174 m_songsInHistory = 200;
176 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Matching songs = %i, History size = %i", m_iMatchingSongs, m_songsInHistory);
177 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Party mode enabled!");
179 int iPlaylist = m_bIsVideo ? PLAYLIST_VIDEO : PLAYLIST_MUSIC;
181 g_playlistPlayer.ClearPlaylist(iPlaylist);
182 g_playlistPlayer.SetShuffle(iPlaylist, false);
183 g_playlistPlayer.SetRepeat(iPlaylist, PLAYLIST::REPEAT_NONE);
185 pDialog->SetLine(0, (m_bIsVideo ? 20252 : 20124));
188 if (!AddInitialSongs(songIDs))
193 CLog::Log(LOGDEBUG, "%s time for song fetch: %u",
194 __FUNCTION__, XbmcThreads::SystemClockMillis() - time);
197 g_playlistPlayer.SetCurrentPlaylist(iPlaylist);
201 // open now playing window
202 if (m_type.Equals("songs"))
204 if (g_windowManager.GetActiveWindow() != WINDOW_MUSIC_PLAYLIST)
205 g_windowManager.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(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 %i user selected songs at %i", iNewUserSongs, iAddAt);
247 int iPlaylist = PLAYLIST_MUSIC;
249 iPlaylist = PLAYLIST_VIDEO;
250 g_playlistPlayer.GetPlaylist(iPlaylist).Insert(tempList, iAddAt);
252 // update last user added song location
253 if (m_iLastUserSong < 0)
255 m_iLastUserSong += iNewUserSongs;
261 void CPartyModeManager::AddUserSongs(CFileItemList& tempList, bool bPlay /* = false */)
268 if (m_iLastUserSong < 0 || bPlay)
269 iAddAt = 1; // under the currently playing song
271 iAddAt = m_iLastUserSong + 1; // under the last user added song
273 int iNewUserSongs = tempList.Size();
274 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Adding %i user selected songs at %i", iNewUserSongs, iAddAt);
276 int iPlaylist = PLAYLIST_MUSIC;
278 iPlaylist = PLAYLIST_VIDEO;
280 g_playlistPlayer.GetPlaylist(iPlaylist).Insert(tempList, iAddAt);
282 // update last user added song location
283 if (m_iLastUserSong < 0)
285 m_iLastUserSong += iNewUserSongs;
291 void CPartyModeManager::Process()
300 bool CPartyModeManager::AddRandomSongs(int iSongs /* = 0 */)
302 int iPlaylist = PLAYLIST_MUSIC;
304 iPlaylist = PLAYLIST_VIDEO;
306 CPlayList& playlist = g_playlistPlayer.GetPlaylist(iPlaylist);
307 int iMissingSongs = QUEUE_DEPTH - playlist.size();
309 iSongs = iMissingSongs;
310 // distribute between types if mixed
311 int iSongsToAdd=iSongs;
312 int iVidsToAdd=iSongs;
313 if (m_type.Equals("mixed"))
317 if (rand() % 10 < 7) // 70 % chance of grabbing a song
322 if (iSongs > 1) // grab 70 % songs, 30 % mvids
324 iSongsToAdd = (int).7f*iSongs;
325 iVidsToAdd = (int).3f*iSongs;
326 while (iSongsToAdd+iVidsToAdd < iSongs) // correct any rounding by adding songs
331 // add songs to fill queue
332 if (m_type.Equals("songs") || m_type.Equals("mixed"))
334 CMusicDatabase database;
338 // 1. Grab a random entry from the database using a where clause
339 // 2. Iterate on iSongs.
341 // Note: At present, this method is faster than the alternative, which is to grab
342 // all valid songids, then select a random number of them (as done in AddInitialSongs()).
343 // The reason for this is simply the number of songs we are requesting - we generally
344 // only want one here. Any more than about 3 songs and it is more efficient
345 // to use the technique in AddInitialSongs. As it's unlikely that we'll require
346 // more than 1 song at a time here, this method is faster.
348 for (int i = 0; i < iSongsToAdd; i++)
350 pair<CStdString,CStdString> whereClause = GetWhereClauseWithHistory();
351 CFileItemPtr item(new CFileItem);
353 if (database.GetRandomSong(item.get(), songID, whereClause.first))
356 AddToHistory(1,songID);
368 OnError(16034, (CStdString)"Cannot get songs from database. Aborting.");
374 OnError(16033, (CStdString)"Party mode could not open database. Aborting.");
379 if (m_type.Equals("musicvideos") || m_type.Equals("mixed"))
381 CVideoDatabase database;
385 // 1. Grab a random entry from the database using a where clause
386 // 2. Iterate on iSongs.
388 // Note: At present, this method is faster than the alternative, which is to grab
389 // all valid songids, then select a random number of them (as done in AddInitialSongs()).
390 // The reason for this is simply the number of songs we are requesting - we generally
391 // only want one here. Any more than about 3 songs and it is more efficient
392 // to use the technique in AddInitialSongs. As it's unlikely that we'll require
393 // more than 1 song at a time here, this method is faster.
395 for (int i = 0; i < iVidsToAdd; i++)
397 pair<CStdString,CStdString> whereClause = GetWhereClauseWithHistory();
398 CFileItemPtr item(new CFileItem);
400 if (database.GetRandomMusicVideo(item.get(), songID, whereClause.second))
403 AddToHistory(2,songID);
415 OnError(16034, (CStdString)"Cannot get songs from database. Aborting.");
421 OnError(16033, (CStdString)"Party mode could not open database. Aborting.");
429 void CPartyModeManager::Add(CFileItemPtr &pItem)
431 int iPlaylist = m_bIsVideo ? PLAYLIST_VIDEO : PLAYLIST_MUSIC;
432 if (pItem->HasMusicInfoTag())
434 CMusicDatabase database;
436 database.SetPropertiesForFileItem(*pItem);
439 CPlayList& playlist = g_playlistPlayer.GetPlaylist(iPlaylist);
441 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Adding randomly selected song at %i:[%s]", playlist.size() - 1, pItem->GetPath().c_str());
442 m_iMatchingSongsPicked++;
445 bool CPartyModeManager::ReapSongs()
447 int iPlaylist = m_bIsVideo ? PLAYLIST_VIDEO : PLAYLIST_MUSIC;
449 // reap any played songs
450 int iCurrentSong = g_playlistPlayer.GetCurrentSong();
452 while (i < g_playlistPlayer.GetPlaylist(iPlaylist).size())
454 if (i < iCurrentSong)
456 g_playlistPlayer.GetPlaylist(iPlaylist).Remove(i);
458 if (i <= m_iLastUserSong)
465 g_playlistPlayer.SetCurrentSong(iCurrentSong);
469 bool CPartyModeManager::MovePlaying()
471 // move current song to the top if its not there
472 int iCurrentSong = g_playlistPlayer.GetCurrentSong();
473 int iPlaylist = m_bIsVideo ? PLAYLIST_MUSIC : PLAYLIST_VIDEO;
475 if (iCurrentSong > 0)
477 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Moving currently playing song from %i to 0", iCurrentSong);
478 CPlayList &playlist = g_playlistPlayer.GetPlaylist(iPlaylist);
479 CPlayList playlistTemp;
480 playlistTemp.Add(playlist[iCurrentSong]);
481 playlist.Remove(iCurrentSong);
482 for (int i=0; i<playlist.size(); i++)
483 playlistTemp.Add(playlist[i]);
485 for (int i=0; i<playlistTemp.size(); i++)
486 playlist.Add(playlistTemp[i]);
488 g_playlistPlayer.SetCurrentSong(0);
492 void CPartyModeManager::SendUpdateMessage()
494 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
495 g_windowManager.SendThreadMessage(msg);
498 void CPartyModeManager::Play(int iPos)
500 // move current song to the top if its not there
501 g_playlistPlayer.Play(iPos);
502 CLog::Log(LOGINFO,"PARTY MODE MANAGER: Playing song at %i", iPos);
506 void CPartyModeManager::OnError(int iError, const CStdString& strLogMessage)
509 CGUIDialogOK::ShowAndGetInput(257, 16030, iError, 0);
510 CLog::Log(LOGERROR, "PARTY MODE MANAGER: %s", strLogMessage.c_str());
515 int CPartyModeManager::GetSongsPlayed()
519 return m_iSongsPlayed;
522 int CPartyModeManager::GetMatchingSongs()
526 return m_iMatchingSongs;
529 int CPartyModeManager::GetMatchingSongsPicked()
533 return m_iMatchingSongsPicked;
536 int CPartyModeManager::GetMatchingSongsLeft()
540 return m_iMatchingSongsLeft;
543 int CPartyModeManager::GetRelaxedSongs()
547 return m_iRelaxedSongs;
550 int CPartyModeManager::GetRandomSongs()
554 return m_iRandomSongs;
557 PartyModeContext CPartyModeManager::GetType() const
560 return PARTYMODECONTEXT_UNKNOWN;
563 return PARTYMODECONTEXT_VIDEO;
565 return PARTYMODECONTEXT_MUSIC;
568 void CPartyModeManager::ClearState()
570 m_iLastUserSong = -1;
572 m_iMatchingSongs = 0;
573 m_iMatchingSongsPicked = 0;
574 m_iMatchingSongsLeft = 0;
578 m_songsInHistory = 0;
582 void CPartyModeManager::UpdateStats()
584 m_iMatchingSongsLeft = m_iMatchingSongs - m_iMatchingSongsPicked;
585 m_iRandomSongs = m_iMatchingSongsPicked;
586 m_iRelaxedSongs = 0; // unsupported at this stage
589 bool CPartyModeManager::AddInitialSongs(vector<pair<int,int> > &songIDs)
591 int iPlaylist = m_bIsVideo ? PLAYLIST_VIDEO : PLAYLIST_MUSIC;
593 CPlayList& playlist = g_playlistPlayer.GetPlaylist(iPlaylist);
594 int iMissingSongs = QUEUE_DEPTH - playlist.size();
595 if (iMissingSongs > 0)
597 // generate iMissingSongs random ids from songIDs
598 if (iMissingSongs > (int)songIDs.size())
599 return false; // can't do it if we have less songs than we need
601 vector<pair<int,int> > chosenSongIDs;
602 GetRandomSelection(songIDs, iMissingSongs, chosenSongIDs);
603 CStdString sqlWhereMusic = "songview.idSong IN (";
604 CStdString sqlWhereVideo = "idMVideo IN (";
606 for (vector< pair<int,int> >::iterator it = chosenSongIDs.begin(); it != chosenSongIDs.end(); it++)
609 song.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 CStdString historyWhereMusic;
648 CStdString historyWhereVideo;
649 // now add this on to the normal where clause
650 if (m_history.size())
652 if (m_strCurrentFilterMusic.IsEmpty())
653 historyWhereMusic = "songview.idSong not in (";
655 historyWhereMusic = m_strCurrentFilterMusic + " and songview.idSong not in (";
656 if (m_strCurrentFilterVideo.IsEmpty())
657 historyWhereVideo = "idMVideo not in (";
659 historyWhereVideo = m_strCurrentFilterVideo + " and idMVideo not in (";
661 for (unsigned int i = 0; i < m_history.size(); i++)
664 number.Format("%i,", m_history[i].second);
665 if (m_history[i].first == 1)
666 historyWhereMusic += number;
667 if (m_history[i].first == 2)
668 historyWhereVideo += number;
670 historyWhereMusic.TrimRight(",");
671 historyWhereMusic += ")";
672 historyWhereVideo.TrimRight(",");
673 historyWhereVideo += ")";
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);