[WASAPI] set stream audio category
[xbmc.git] / xbmc / cores / RetroPlayer / RetroPlayer.cpp
blobda217273877c86971cebc8e7a1618c6fbf450e2b
1 /*
2 * Copyright (C) 2012-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 "RetroPlayer.h"
11 #include "FileItem.h"
12 #include "GUIInfoManager.h"
13 #include "RetroPlayerAutoSave.h"
14 #include "RetroPlayerInput.h"
15 #include "ServiceBroker.h"
16 #include "URL.h"
17 #include "addons/AddonManager.h"
18 #include "addons/addoninfo/AddonType.h"
19 #include "cores/DataCacheCore.h"
20 #include "cores/IPlayerCallback.h"
21 #include "cores/RetroPlayer/cheevos/Cheevos.h"
22 #include "cores/RetroPlayer/guibridge/GUIGameMessenger.h"
23 #include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h"
24 #include "cores/RetroPlayer/guiplayback/GUIPlaybackControl.h"
25 #include "cores/RetroPlayer/playback/IPlayback.h"
26 #include "cores/RetroPlayer/playback/RealtimePlayback.h"
27 #include "cores/RetroPlayer/playback/ReversiblePlayback.h"
28 #include "cores/RetroPlayer/process/RPProcessInfo.h"
29 #include "cores/RetroPlayer/rendering/RPRenderManager.h"
30 #include "cores/RetroPlayer/savestates/ISavestate.h"
31 #include "cores/RetroPlayer/savestates/SavestateDatabase.h"
32 #include "cores/RetroPlayer/streams/RPStreamManager.h"
33 #include "dialogs/GUIDialogYesNo.h"
34 #include "games/GameServices.h"
35 #include "games/GameSettings.h"
36 #include "games/GameUtils.h"
37 #include "games/addons/GameClient.h"
38 #include "games/addons/input/GameClientInput.h"
39 #include "games/tags/GameInfoTag.h"
40 #include "guilib/GUIComponent.h"
41 #include "guilib/GUIWindowManager.h"
42 #include "guilib/LocalizeStrings.h"
43 #include "guilib/WindowIDs.h"
44 #include "input/actions/Action.h"
45 #include "input/actions/ActionIDs.h"
46 #include "interfaces/AnnouncementManager.h"
47 #include "messaging/ApplicationMessenger.h"
48 #include "utils/JobManager.h"
49 #include "utils/StringUtils.h"
50 #include "utils/log.h"
51 #include "windowing/WinSystem.h"
53 #include <memory>
54 #include <mutex>
56 using namespace KODI;
57 using namespace GAME;
58 using namespace RETRO;
60 CRetroPlayer::CRetroPlayer(IPlayerCallback& callback)
61 : IPlayer(callback), m_gameServices(CServiceBroker::GetGameServices())
63 ResetPlayback();
64 CServiceBroker::GetWinSystem()->RegisterRenderLoop(this);
67 CRetroPlayer::~CRetroPlayer()
69 CServiceBroker::GetWinSystem()->UnregisterRenderLoop(this);
70 CloseFile();
73 bool CRetroPlayer::OpenFile(const CFileItem& file, const CPlayerOptions& options)
75 CFileItem fileCopy(file);
77 std::string savestatePath;
79 // When playing a game, set the game client that we'll use to open the game.
80 // This will prompt the user to select a savestate if there are any.
81 // If there are no savestates, or the user wants to create a new savestate
82 // it will prompt the user to select a game client
83 if (!GAME::CGameUtils::FillInGameClient(fileCopy, savestatePath))
85 CLog::Log(LOGINFO,
86 "RetroPlayer[PLAYER]: No compatible game client selected, aborting playback");
87 return false;
90 // Check if we should open in standalone mode
91 const bool bStandalone = fileCopy.GetPath().empty();
93 m_processInfo = CRPProcessInfo::CreateInstance();
94 if (!m_processInfo)
96 CLog::Log(LOGERROR, "RetroPlayer[PLAYER]: Failed to create - no process info registered");
97 return false;
100 m_processInfo->SetDataCache(&CServiceBroker::GetDataCacheCore());
101 m_processInfo->ResetInfo();
103 m_guiMessenger = std::make_unique<CGUIGameMessenger>(*m_processInfo);
104 m_renderManager = std::make_unique<CRPRenderManager>(*m_processInfo);
106 std::unique_lock<CCriticalSection> lock(m_mutex);
108 if (IsPlaying())
109 CloseFile();
111 PrintGameInfo(fileCopy);
113 bool bSuccess = false;
115 std::string gameClientId = fileCopy.GetGameInfoTag()->GetGameClient();
117 ADDON::AddonPtr addon;
118 if (gameClientId.empty())
120 CLog::Log(LOGERROR, "RetroPlayer[PLAYER]: Can't play game, no game client was passed!");
122 else if (!CServiceBroker::GetAddonMgr().GetAddon(gameClientId, addon, ADDON::AddonType::GAMEDLL,
123 ADDON::OnlyEnabled::CHOICE_YES))
125 CLog::Log(LOGERROR, "RetroPlayer[PLAYER]: Can't find add-on {} for game file!", gameClientId);
127 else
129 m_gameClient = std::static_pointer_cast<CGameClient>(addon);
130 if (m_gameClient->Initialize())
132 m_streamManager = std::make_unique<CRPStreamManager>(*m_renderManager, *m_processInfo);
134 // Initialize input
135 m_input = std::make_unique<CRetroPlayerInput>(CServiceBroker::GetPeripherals(),
136 *m_processInfo, m_gameClient);
137 m_input->StartAgentManager();
139 if (!bStandalone)
141 std::string redactedPath = CURL::GetRedacted(fileCopy.GetPath());
142 CLog::Log(LOGINFO, "RetroPlayer[PLAYER]: Opening: {}", redactedPath);
143 bSuccess = m_gameClient->OpenFile(fileCopy, *m_streamManager, m_input.get());
145 else
147 CLog::Log(LOGINFO, "RetroPlayer[PLAYER]: Opening standalone");
148 bSuccess = m_gameClient->OpenStandalone(*m_streamManager, m_input.get());
151 if (bSuccess)
152 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Using game client {}", gameClientId);
153 else
154 CLog::Log(LOGERROR, "RetroPlayer[PLAYER]: Failed to open file using {}", gameClientId);
156 else
157 CLog::Log(LOGERROR, "RetroPlayer[PLAYER]: Failed to initialize {}", gameClientId);
160 if (bSuccess && !bStandalone)
162 CSavestateDatabase savestateDb;
164 std::unique_ptr<ISavestate> save = CSavestateDatabase::AllocateSavestate();
165 if (savestateDb.GetSavestate(savestatePath, *save))
167 // Check if game client is the same
168 if (save->GameClientID() != m_gameClient->ID())
170 ADDON::AddonPtr addon;
171 if (CServiceBroker::GetAddonMgr().GetAddon(save->GameClientID(), addon,
172 ADDON::OnlyEnabled::CHOICE_YES))
174 // Warn the user that continuing with a different game client will
175 // overwrite the save
176 bool dummy;
177 if (!CGUIDialogYesNo::ShowAndGetInput(
178 438, StringUtils::Format(g_localizeStrings.Get(35217), addon->Name()), dummy, 222,
179 35218, 0))
180 bSuccess = false;
186 if (bSuccess)
188 // Switch to fullscreen
189 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SWITCHTOFULLSCREEN);
191 m_cheevos = std::make_shared<CCheevos>(m_gameClient.get(),
192 m_gameServices.GameSettings().GetRAUsername(),
193 m_gameServices.GameSettings().GetRAToken());
195 m_cheevos->EnableRichPresence();
197 // Initialize gameplay
198 CreatePlayback(savestatePath);
199 RegisterWindowCallbacks();
200 m_playbackControl = std::make_unique<CGUIPlaybackControl>(*this);
201 m_callback.OnPlayBackStarted(fileCopy);
202 m_callback.OnAVStarted(fileCopy);
203 if (!bStandalone)
204 m_autoSave = std::make_unique<CRetroPlayerAutoSave>(*this, m_gameServices.GameSettings());
206 // Set video framerate
207 m_processInfo->SetVideoFps(static_cast<float>(m_gameClient->GetFrameRate()));
209 else
211 m_input.reset();
212 m_streamManager.reset();
213 if (m_gameClient)
214 m_gameClient->Unload();
215 m_gameClient.reset();
218 return bSuccess;
221 bool CRetroPlayer::CloseFile(bool reopen /* = false */)
223 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Closing file");
225 m_autoSave.reset();
227 UnregisterWindowCallbacks();
229 m_playbackControl.reset();
231 std::unique_lock<CCriticalSection> lock(m_mutex);
233 if (m_gameClient && m_gameServices.GameSettings().AutosaveEnabled())
235 std::string savePath = m_playback->CreateSavestate(true);
236 if (!savePath.empty())
237 CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Saved state to {}", CURL::GetRedacted(savePath));
238 else
239 CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Failed to save state at close");
242 m_playback.reset();
244 if (m_input)
245 m_input->StopAgentManager();
247 m_cheevos.reset();
249 if (m_gameClient)
250 m_gameClient->CloseFile();
252 m_input.reset();
254 m_streamManager.reset();
256 if (m_gameClient)
257 m_gameClient->Unload();
258 m_gameClient.reset();
260 m_renderManager.reset();
261 if (m_processInfo)
263 m_processInfo->ResetInfo();
265 m_processInfo.reset();
266 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Playback ended");
267 m_callback.OnPlayBackEnded();
269 return true;
272 bool CRetroPlayer::IsPlaying() const
274 if (m_gameClient)
275 return m_gameClient->IsPlaying();
276 return false;
279 bool CRetroPlayer::CanPause() const
281 return m_playback->CanPause();
284 void CRetroPlayer::Pause()
286 if (!CanPause())
287 return;
289 float speed;
291 if (m_playback->GetSpeed() == 0.0)
292 speed = 1.0f;
293 else
294 speed = 0.0f;
296 SetSpeed(speed);
299 bool CRetroPlayer::CanSeek() const
301 return m_playback->CanSeek();
304 void CRetroPlayer::Seek(bool bPlus /* = true */,
305 bool bLargeStep /* = false */,
306 bool bChapterOverride /* = false */)
308 if (!CanSeek())
309 return;
311 if (m_gameClient)
313 //! @todo
315 if (bPlus)
317 if (bLargeStep)
318 m_playback->BigSkipForward();
319 else
320 m_playback->SmallSkipForward();
322 else
324 if (bLargeStep)
325 m_playback->BigSkipBackward();
326 else
327 m_playback->SmallSkipBackward();
333 void CRetroPlayer::SeekPercentage(float fPercent /* = 0 */)
335 if (!CanSeek())
336 return;
338 if (fPercent < 0.0f)
339 fPercent = 0.0f;
340 else if (fPercent > 100.0f)
341 fPercent = 100.0f;
343 uint64_t totalTime = GetTotalTime();
344 if (totalTime != 0)
345 SeekTime(static_cast<int64_t>(totalTime * fPercent / 100.0f));
348 float CRetroPlayer::GetCachePercentage() const
350 const float cacheMs = static_cast<float>(m_playback->GetCacheTimeMs());
351 const float totalMs = static_cast<float>(m_playback->GetTotalTimeMs());
353 if (totalMs != 0.0f)
354 return cacheMs / totalMs * 100.0f;
356 return 0.0f;
359 void CRetroPlayer::SetMute(bool bOnOff)
361 if (m_streamManager)
362 m_streamManager->EnableAudio(!bOnOff);
365 void CRetroPlayer::SeekTime(int64_t iTime /* = 0 */)
367 if (!CanSeek())
368 return;
370 m_playback->SeekTimeMs(static_cast<unsigned int>(iTime));
373 bool CRetroPlayer::SeekTimeRelative(int64_t iTime)
375 if (!CanSeek())
376 return false;
378 SeekTime(GetTime() + iTime);
380 return true;
383 uint64_t CRetroPlayer::GetTime()
385 return m_playback->GetTimeMs();
388 uint64_t CRetroPlayer::GetTotalTime()
390 return m_playback->GetTotalTimeMs();
393 void CRetroPlayer::SetSpeed(float speed)
395 if (m_playback->GetSpeed() != static_cast<double>(speed))
397 if (speed == 1.0f)
398 m_callback.OnPlayBackResumed();
399 else if (speed == 0.0f)
400 m_callback.OnPlayBackPaused();
402 SetSpeedInternal(static_cast<double>(speed));
404 if (speed == 0.0f)
406 const int dialogId = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog();
407 if (dialogId == WINDOW_FULLSCREEN_GAME)
409 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Opening OSD via speed change ({:f})", speed);
410 OpenOSD();
413 else
415 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Closing OSD via speed change ({:f})", speed);
416 CloseOSD();
421 bool CRetroPlayer::OnAction(const CAction& action)
423 switch (action.GetID())
425 case ACTION_PLAYER_RESET:
427 if (m_gameClient)
429 float speed = static_cast<float>(m_playback->GetSpeed());
431 m_playback->SetSpeed(0.0);
433 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Sending reset command via ACTION_PLAYER_RESET");
434 m_cheevos->ResetRuntime();
435 m_gameClient->Input().HardwareReset();
437 // If rewinding or paused, begin playback
438 if (speed <= 0.0f)
439 speed = 1.0f;
441 SetSpeed(speed);
443 return true;
445 case ACTION_SHOW_OSD:
447 if (m_gameClient)
449 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Closing OSD via ACTION_SHOW_OSD");
450 CloseOSD();
451 return true;
453 break;
455 default:
456 break;
459 return false;
462 std::string CRetroPlayer::GetPlayerState()
464 std::string savestatePath;
466 if (m_autoSave)
468 savestatePath = m_playback->CreateSavestate(true);
469 if (savestatePath.empty())
471 CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Continuing without saving");
472 m_autoSave.reset();
475 return savestatePath;
478 bool CRetroPlayer::SetPlayerState(const std::string& state)
480 return m_playback->LoadSavestate(state);
483 void CRetroPlayer::FrameMove()
485 if (m_renderManager)
486 m_renderManager->FrameMove();
488 if (m_playbackControl)
489 m_playbackControl->FrameMove();
491 if (m_processInfo)
492 m_processInfo->SetPlayTimes(0, GetTime(), 0, GetTotalTime());
495 void CRetroPlayer::Render(bool clear, uint32_t alpha /* = 255 */, bool gui /* = true */)
497 // Performed by callbacks
500 bool CRetroPlayer::IsRenderingVideo() const
502 return true;
505 bool CRetroPlayer::HasGameAgent() const
507 if (m_gameClient)
508 return m_gameClient->Input().HasAgent();
510 return false;
513 std::string CRetroPlayer::GameClientID() const
515 if (m_gameClient)
516 return m_gameClient->ID();
518 return "";
521 std::string CRetroPlayer::GetPlayingGame() const
523 if (m_gameClient)
524 return m_gameClient->GetGamePath();
526 return "";
529 std::string CRetroPlayer::CreateSavestate(bool autosave)
531 if (m_playback)
532 return m_playback->CreateSavestate(autosave);
534 return "";
537 bool CRetroPlayer::UpdateSavestate(const std::string& savestatePath)
539 if (m_playback)
540 return !m_playback->CreateSavestate(false, savestatePath).empty();
542 return false;
545 bool CRetroPlayer::LoadSavestate(const std::string& savestatePath)
547 if (m_playback)
548 return m_playback->LoadSavestate(savestatePath);
550 return false;
553 void CRetroPlayer::FreeSavestateResources(const std::string& savestatePath)
555 if (m_renderManager)
556 m_renderManager->ClearVideoFrame(savestatePath);
559 void CRetroPlayer::CloseOSDCallback()
561 CloseOSD();
564 void CRetroPlayer::SetPlaybackSpeed(double speed)
566 if (m_playback)
568 if (m_playback->GetSpeed() != speed)
570 if (speed == 1.0)
572 IPlayerCallback* callback = &m_callback;
573 CServiceBroker::GetJobManager()->Submit([callback]() { callback->OnPlayBackResumed(); },
574 CJob::PRIORITY_NORMAL);
576 else if (speed == 0.0)
578 IPlayerCallback* callback = &m_callback;
579 CServiceBroker::GetJobManager()->Submit([callback]() { callback->OnPlayBackPaused(); },
580 CJob::PRIORITY_NORMAL);
585 SetSpeedInternal(speed);
588 void CRetroPlayer::EnableInput(bool bEnable)
590 if (m_input)
591 m_input->EnableInput(bEnable);
594 bool CRetroPlayer::IsAutoSaveEnabled() const
596 return m_playback->GetSpeed() > 0.0;
599 std::string CRetroPlayer::CreateAutosave()
601 return m_playback->CreateSavestate(true);
604 void CRetroPlayer::SetSpeedInternal(double speed)
606 OnSpeedChange(speed);
608 if (speed == 0.0)
609 m_playback->PauseAsync();
610 else
611 m_playback->SetSpeed(speed);
614 void CRetroPlayer::OnSpeedChange(double newSpeed)
616 m_streamManager->EnableAudio(newSpeed == 1.0);
617 m_input->SetSpeed(newSpeed);
618 m_renderManager->SetSpeed(newSpeed);
619 m_processInfo->SetSpeed(static_cast<float>(newSpeed));
622 void CRetroPlayer::CreatePlayback(const std::string& savestatePath)
624 if (m_gameClient->RequiresGameLoop())
626 m_playback->Deinitialize();
627 m_playback = std::make_unique<CReversiblePlayback>(
628 m_gameClient.get(), *m_renderManager, m_cheevos.get(), *m_guiMessenger,
629 m_gameClient->GetFrameRate(), m_gameClient->GetSerializeSize());
631 else
632 ResetPlayback();
634 if (!savestatePath.empty())
636 const bool bStandalone = m_gameClient->GetGamePath().empty();
637 if (!bStandalone)
639 CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Loading savestate");
641 if (!SetPlayerState(savestatePath))
642 CLog::Log(LOGERROR, "RetroPlayer[SAVE]: Failed to load savestate");
646 m_playback->Initialize();
649 void CRetroPlayer::ResetPlayback()
651 // Called from the constructor, m_playback might not be initialized
652 if (m_playback)
653 m_playback->Deinitialize();
655 m_playback = std::make_unique<CRealtimePlayback>();
658 void CRetroPlayer::OpenOSD()
660 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_DIALOG_GAME_OSD);
663 void CRetroPlayer::CloseOSD()
665 CServiceBroker::GetGUI()->GetWindowManager().CloseDialogs(true);
668 void CRetroPlayer::RegisterWindowCallbacks()
670 m_gameServices.GameRenderManager().RegisterPlayer(m_renderManager->GetGUIRenderTargetFactory(),
671 m_renderManager.get(), this);
674 void CRetroPlayer::UnregisterWindowCallbacks()
676 m_gameServices.GameRenderManager().UnregisterPlayer();
679 void CRetroPlayer::PrintGameInfo(const CFileItem& file) const
681 const CGameInfoTag* tag = file.GetGameInfoTag();
682 if (tag)
684 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: ---------------------------------------");
685 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Game tag loaded");
686 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: URL: {}", tag->GetURL());
687 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Title: {}", tag->GetTitle());
688 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Platform: {}", tag->GetPlatform());
689 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Genres: {}",
690 StringUtils::Join(tag->GetGenres(), ", "));
691 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Developer: {}", tag->GetDeveloper());
692 if (tag->GetYear() > 0)
693 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Year: {}", tag->GetYear());
694 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Game Code: {}", tag->GetID());
695 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Region: {}", tag->GetRegion());
696 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Publisher: {}", tag->GetPublisher());
697 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Format: {}", tag->GetFormat());
698 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Cartridge type: {}", tag->GetCartridgeType());
699 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Game client: {}", tag->GetGameClient());
700 CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: ---------------------------------------");