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.
9 #include "RetroPlayer.h"
12 #include "GUIInfoManager.h"
13 #include "RetroPlayerAutoSave.h"
14 #include "RetroPlayerInput.h"
15 #include "ServiceBroker.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"
58 using namespace RETRO
;
60 CRetroPlayer::CRetroPlayer(IPlayerCallback
& callback
)
61 : IPlayer(callback
), m_gameServices(CServiceBroker::GetGameServices())
64 CServiceBroker::GetWinSystem()->RegisterRenderLoop(this);
67 CRetroPlayer::~CRetroPlayer()
69 CServiceBroker::GetWinSystem()->UnregisterRenderLoop(this);
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
))
86 "RetroPlayer[PLAYER]: No compatible game client selected, aborting playback");
90 // Check if we should open in standalone mode
91 const bool bStandalone
= fileCopy
.GetPath().empty();
93 m_processInfo
= CRPProcessInfo::CreateInstance();
96 CLog::Log(LOGERROR
, "RetroPlayer[PLAYER]: Failed to create - no process info registered");
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
);
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
);
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
);
135 m_input
= std::make_unique
<CRetroPlayerInput
>(CServiceBroker::GetPeripherals(),
136 *m_processInfo
, m_gameClient
);
137 m_input
->StartAgentManager();
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());
147 CLog::Log(LOGINFO
, "RetroPlayer[PLAYER]: Opening standalone");
148 bSuccess
= m_gameClient
->OpenStandalone(*m_streamManager
, m_input
.get());
152 CLog::Log(LOGDEBUG
, "RetroPlayer[PLAYER]: Using game client {}", gameClientId
);
154 CLog::Log(LOGERROR
, "RetroPlayer[PLAYER]: Failed to open file using {}", gameClientId
);
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
177 if (!CGUIDialogYesNo::ShowAndGetInput(
178 438, StringUtils::Format(g_localizeStrings
.Get(35217), addon
->Name()), dummy
, 222,
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
);
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()));
212 m_streamManager
.reset();
214 m_gameClient
->Unload();
215 m_gameClient
.reset();
221 bool CRetroPlayer::CloseFile(bool reopen
/* = false */)
223 CLog::Log(LOGDEBUG
, "RetroPlayer[PLAYER]: Closing file");
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
));
239 CLog::Log(LOGDEBUG
, "RetroPlayer[SAVE]: Failed to save state at close");
245 m_input
->StopAgentManager();
250 m_gameClient
->CloseFile();
254 m_streamManager
.reset();
257 m_gameClient
->Unload();
258 m_gameClient
.reset();
260 m_renderManager
.reset();
263 m_processInfo
->ResetInfo();
265 m_processInfo
.reset();
266 CLog::Log(LOGDEBUG
, "RetroPlayer[PLAYER]: Playback ended");
267 m_callback
.OnPlayBackEnded();
272 bool CRetroPlayer::IsPlaying() const
275 return m_gameClient
->IsPlaying();
279 bool CRetroPlayer::CanPause() const
281 return m_playback
->CanPause();
284 void CRetroPlayer::Pause()
291 if (m_playback
->GetSpeed() == 0.0)
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 */)
318 m_playback->BigSkipForward();
320 m_playback->SmallSkipForward();
325 m_playback->BigSkipBackward();
327 m_playback->SmallSkipBackward();
333 void CRetroPlayer::SeekPercentage(float fPercent
/* = 0 */)
340 else if (fPercent
> 100.0f
)
343 uint64_t totalTime
= GetTotalTime();
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());
354 return cacheMs
/ totalMs
* 100.0f
;
359 void CRetroPlayer::SetMute(bool bOnOff
)
362 m_streamManager
->EnableAudio(!bOnOff
);
365 void CRetroPlayer::SeekTime(int64_t iTime
/* = 0 */)
370 m_playback
->SeekTimeMs(static_cast<unsigned int>(iTime
));
373 bool CRetroPlayer::SeekTimeRelative(int64_t iTime
)
378 SeekTime(GetTime() + iTime
);
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
))
398 m_callback
.OnPlayBackResumed();
399 else if (speed
== 0.0f
)
400 m_callback
.OnPlayBackPaused();
402 SetSpeedInternal(static_cast<double>(speed
));
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
);
415 CLog::Log(LOGDEBUG
, "RetroPlayer[PLAYER]: Closing OSD via speed change ({:f})", speed
);
421 bool CRetroPlayer::OnAction(const CAction
& action
)
423 switch (action
.GetID())
425 case ACTION_PLAYER_RESET
:
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
445 case ACTION_SHOW_OSD
:
449 CLog::Log(LOGDEBUG
, "RetroPlayer[PLAYER]: Closing OSD via ACTION_SHOW_OSD");
462 std::string
CRetroPlayer::GetPlayerState()
464 std::string savestatePath
;
468 savestatePath
= m_playback
->CreateSavestate(true);
469 if (savestatePath
.empty())
471 CLog::Log(LOGDEBUG
, "RetroPlayer[SAVE]: Continuing without saving");
475 return savestatePath
;
478 bool CRetroPlayer::SetPlayerState(const std::string
& state
)
480 return m_playback
->LoadSavestate(state
);
483 void CRetroPlayer::FrameMove()
486 m_renderManager
->FrameMove();
488 if (m_playbackControl
)
489 m_playbackControl
->FrameMove();
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
505 bool CRetroPlayer::HasGameAgent() const
508 return m_gameClient
->Input().HasAgent();
513 std::string
CRetroPlayer::GameClientID() const
516 return m_gameClient
->ID();
521 std::string
CRetroPlayer::GetPlayingGame() const
524 return m_gameClient
->GetGamePath();
529 std::string
CRetroPlayer::CreateSavestate(bool autosave
)
532 return m_playback
->CreateSavestate(autosave
);
537 bool CRetroPlayer::UpdateSavestate(const std::string
& savestatePath
)
540 return !m_playback
->CreateSavestate(false, savestatePath
).empty();
545 bool CRetroPlayer::LoadSavestate(const std::string
& savestatePath
)
548 return m_playback
->LoadSavestate(savestatePath
);
553 void CRetroPlayer::FreeSavestateResources(const std::string
& savestatePath
)
556 m_renderManager
->ClearVideoFrame(savestatePath
);
559 void CRetroPlayer::CloseOSDCallback()
564 void CRetroPlayer::SetPlaybackSpeed(double speed
)
568 if (m_playback
->GetSpeed() != speed
)
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
)
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
);
609 m_playback
->PauseAsync();
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());
634 if (!savestatePath
.empty())
636 const bool bStandalone
= m_gameClient
->GetGamePath().empty();
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
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();
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]: ---------------------------------------");