diff options
Diffstat (limited to 'xbmc/application')
30 files changed, 8505 insertions, 0 deletions
diff --git a/xbmc/application/AppEnvironment.cpp b/xbmc/application/AppEnvironment.cpp new file mode 100644 index 0000000..32d4c4f --- /dev/null +++ b/xbmc/application/AppEnvironment.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2005-2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "AppEnvironment.h" + +#include "ServiceBroker.h" +#include "settings/SettingsComponent.h" +#include "utils/log.h" + +void CAppEnvironment::SetUp(const std::shared_ptr<CAppParams>& appParams) +{ + CServiceBroker::RegisterAppParams(appParams); + CServiceBroker::CreateLogging(); + const auto settingsComponent = std::make_shared<CSettingsComponent>(); + settingsComponent->Initialize(); + CServiceBroker::RegisterSettingsComponent(settingsComponent); +} + +void CAppEnvironment::TearDown() +{ + CServiceBroker::GetLogging().UnregisterFromSettings(); + CServiceBroker::GetSettingsComponent()->Deinitialize(); + CServiceBroker::UnregisterSettingsComponent(); + CServiceBroker::GetLogging().Deinitialize(); + CServiceBroker::DestroyLogging(); + CServiceBroker::UnregisterAppParams(); +} diff --git a/xbmc/application/AppEnvironment.h b/xbmc/application/AppEnvironment.h new file mode 100644 index 0000000..d301866 --- /dev/null +++ b/xbmc/application/AppEnvironment.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2005-2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> + +class CAppParams; + +class CAppEnvironment +{ +public: + static void SetUp(const std::shared_ptr<CAppParams>& appParams); + static void TearDown(); +}; diff --git a/xbmc/application/AppInboundProtocol.cpp b/xbmc/application/AppInboundProtocol.cpp new file mode 100644 index 0000000..7a1c80d --- /dev/null +++ b/xbmc/application/AppInboundProtocol.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "AppInboundProtocol.h" + +#include "ServiceBroker.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPowerHandling.h" + +CAppInboundProtocol::CAppInboundProtocol(CApplication &app) : m_pApp(app) +{ + +} + +bool CAppInboundProtocol::OnEvent(XBMC_Event &event) +{ + return m_pApp.OnEvent(event); +} + +void CAppInboundProtocol::SetRenderGUI(bool renderGUI) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->SetRenderGUI(renderGUI); +} diff --git a/xbmc/application/AppInboundProtocol.h b/xbmc/application/AppInboundProtocol.h new file mode 100644 index 0000000..b0489e5 --- /dev/null +++ b/xbmc/application/AppInboundProtocol.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "windowing/XBMC_events.h" + +class CApplication; + +class CAppInboundProtocol +{ +public: + CAppInboundProtocol(CApplication &app); + bool OnEvent(XBMC_Event &event); + void SetRenderGUI(bool renderGUI); + +protected: + CApplication &m_pApp; +}; diff --git a/xbmc/application/AppParamParser.cpp b/xbmc/application/AppParamParser.cpp new file mode 100644 index 0000000..ca9cd2a --- /dev/null +++ b/xbmc/application/AppParamParser.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "AppParamParser.h" + +#include "CompileInfo.h" +#include "FileItem.h" +#include "ServiceBroker.h" +#include "application/AppParams.h" +#include "utils/StringUtils.h" +#include "utils/SystemInfo.h" +#include "utils/log.h" + +#include <iostream> +#include <stdlib.h> +#include <string> + +namespace +{ + +constexpr const char* versionText = + R"""({0} Media Center {1} +Copyright (C) {2} Team {0} - http://kodi.tv +)"""; + +constexpr const char* helpText = + R"""(Usage: {0} [OPTION]... [FILE]... + +Arguments: + -fs Runs {1} in full screen + --standalone {1} runs in a stand alone environment without a window + manager and supporting applications. For example, that + enables network settings. + -p or --portable {1} will look for configurations in install folder instead of ~/.{0} + --debug Enable debug logging + --version Print version information + --test Enable test mode. [FILE] required. + --settings=<filename> Loads specified file after advancedsettings.xml replacing any settings specified + specified file must exist in special://xbmc/system/ +)"""; + +} // namespace + +CAppParamParser::CAppParamParser() : m_params(std::make_shared<CAppParams>()) +{ +} + +void CAppParamParser::Parse(const char* const* argv, int nArgs) +{ + std::vector<std::string> args; + args.reserve(nArgs); + + for (int i = 0; i < nArgs; i++) + { + args.emplace_back(argv[i]); + if (i > 0) + ParseArg(argv[i]); + } + + if (nArgs > 1) + { + // testmode is only valid if at least one item to play was given + if (m_params->GetPlaylist().IsEmpty()) + m_params->SetTestMode(false); + } + + // Record raw paramerters + m_params->SetRawArgs(std::move(args)); +} + +void CAppParamParser::DisplayVersion() +{ + std::cout << StringUtils::Format(versionText, CSysInfo::GetAppName(), CSysInfo::GetVersion(), + CCompileInfo::GetCopyrightYears()); + exit(0); +} + +void CAppParamParser::DisplayHelp() +{ + std::string lcAppName = CSysInfo::GetAppName(); + StringUtils::ToLower(lcAppName); + + std::cout << StringUtils::Format(helpText, lcAppName, CSysInfo::GetAppName()); +} + +void CAppParamParser::ParseArg(const std::string &arg) +{ + if (arg == "-fs" || arg == "--fullscreen") + m_params->SetStartFullScreen(true); + else if (arg == "-h" || arg == "--help") + { + DisplayHelp(); + exit(0); + } + else if (arg == "-v" || arg == "--version") + DisplayVersion(); + else if (arg == "--standalone") + m_params->SetStandAlone(true); + else if (arg == "-p" || arg == "--portable") + m_params->SetPlatformDirectories(false); + else if (arg == "--debug") + m_params->SetLogLevel(LOG_LEVEL_DEBUG); + else if (arg == "--test") + m_params->SetTestMode(true); + else if (arg.substr(0, 11) == "--settings=") + m_params->SetSettingsFile(arg.substr(11)); + else if (arg.length() != 0 && arg[0] != '-') + { + const CFileItemPtr item = std::make_shared<CFileItem>(arg); + item->SetPath(arg); + m_params->GetPlaylist().Add(item); + } +} diff --git a/xbmc/application/AppParamParser.h b/xbmc/application/AppParamParser.h new file mode 100644 index 0000000..44907f8 --- /dev/null +++ b/xbmc/application/AppParamParser.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> +#include <string> + +class CAppParams; + +class CAppParamParser +{ +public: + CAppParamParser(); + virtual ~CAppParamParser() = default; + + void Parse(const char* const* argv, int nArgs); + + std::shared_ptr<CAppParams> GetAppParams() const { return m_params; } + +protected: + virtual void ParseArg(const std::string& arg); + virtual void DisplayHelp(); + +private: + void DisplayVersion(); + + std::shared_ptr<CAppParams> m_params; +}; diff --git a/xbmc/application/AppParams.cpp b/xbmc/application/AppParams.cpp new file mode 100644 index 0000000..97c09e3 --- /dev/null +++ b/xbmc/application/AppParams.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2005-2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "AppParams.h" + +#include "FileItem.h" + +CAppParams::CAppParams() : m_playlist(std::make_unique<CFileItemList>()) +{ +} + +void CAppParams::SetRawArgs(std::vector<std::string> args) +{ + m_rawArgs = std::move(args); +} diff --git a/xbmc/application/AppParams.h b/xbmc/application/AppParams.h new file mode 100644 index 0000000..4452cd7 --- /dev/null +++ b/xbmc/application/AppParams.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2005-202 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "commons/ilog.h" + +#include <memory> +#include <string> +#include <vector> + +class CFileItemList; + +class CAppParams +{ +public: + CAppParams(); + virtual ~CAppParams() = default; + + int GetLogLevel() const { return m_logLevel; } + void SetLogLevel(int logLevel) { m_logLevel = logLevel; } + + bool IsStartFullScreen() const { return m_startFullScreen; } + void SetStartFullScreen(bool startFullScreen) { m_startFullScreen = startFullScreen; } + + bool IsStandAlone() const { return m_standAlone; } + void SetStandAlone(bool standAlone) { m_standAlone = standAlone; } + + bool HasPlatformDirectories() const { return m_platformDirectories; } + void SetPlatformDirectories(bool platformDirectories) + { + m_platformDirectories = platformDirectories; + } + + bool IsTestMode() const { return m_testmode; } + void SetTestMode(bool testMode) { m_testmode = testMode; } + + const std::string& GetSettingsFile() const { return m_settingsFile; } + void SetSettingsFile(const std::string& settingsFile) { m_settingsFile = settingsFile; } + + const std::string& GetWindowing() const { return m_windowing; } + void SetWindowing(const std::string& windowing) { m_windowing = windowing; } + + const std::string& GetLogTarget() const { return m_logTarget; } + void SetLogTarget(const std::string& logTarget) { m_logTarget = logTarget; } + + CFileItemList& GetPlaylist() const { return *m_playlist; } + + /*! + * \brief Get the raw command-line arguments + * + * Note: Raw arguments are currently not used by Kodi, but they will be + * useful if/when ROS 2 support is ever merged. + * + * \return The arguments. Note that the leading argument is the executable + * path name. + */ + const std::vector<std::string>& GetRawArgs() const { return m_rawArgs; } + + /*! + * \brief Set the raw command-line arguments + * + * \args The arguments. Note that the leading argument is the executable path + * name. + */ + void SetRawArgs(std::vector<std::string> args); + +private: + int m_logLevel{LOG_LEVEL_NORMAL}; + + bool m_startFullScreen{false}; + bool m_standAlone{false}; + bool m_platformDirectories{true}; + bool m_testmode{false}; + + std::string m_settingsFile; + std::string m_windowing; + std::string m_logTarget; + + std::unique_ptr<CFileItemList> m_playlist; + + // The raw command-line arguments + std::vector<std::string> m_rawArgs; +}; diff --git a/xbmc/application/Application.cpp b/xbmc/application/Application.cpp new file mode 100644 index 0000000..0765cf3 --- /dev/null +++ b/xbmc/application/Application.cpp @@ -0,0 +1,3693 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Application.h" + +#include "Autorun.h" +#include "CompileInfo.h" +#include "GUIInfoManager.h" +#include "HDRStatus.h" +#include "LangInfo.h" +#include "PlayListPlayer.h" +#include "ServiceManager.h" +#include "URL.h" +#include "Util.h" +#include "addons/AddonManager.h" +#include "addons/RepositoryUpdater.h" +#include "addons/Service.h" +#include "addons/Skin.h" +#include "addons/VFSEntry.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "application/AppInboundProtocol.h" +#include "application/AppParams.h" +#include "application/ApplicationActionListeners.h" +#include "application/ApplicationPlayer.h" +#include "application/ApplicationPowerHandling.h" +#include "application/ApplicationSkinHandling.h" +#include "application/ApplicationStackHelper.h" +#include "application/ApplicationVolumeHandling.h" +#include "cores/AudioEngine/Engines/ActiveAE/ActiveAE.h" +#include "cores/IPlayer.h" +#include "cores/playercorefactory/PlayerCoreFactory.h" +#include "dialogs/GUIDialogBusy.h" +#include "dialogs/GUIDialogCache.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "events/EventLog.h" +#include "events/NotificationEvent.h" +#include "filesystem/File.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIControlProfiler.h" +#include "guilib/GUIFontManager.h" +#include "guilib/StereoscopicsManager.h" +#include "guilib/TextureManager.h" +#include "interfaces/builtins/Builtins.h" +#include "interfaces/generic/ScriptInvocationManager.h" +#include "music/MusicLibraryQueue.h" +#include "music/tags/MusicInfoTag.h" +#include "network/EventServer.h" +#include "network/Network.h" +#include "platform/Environment.h" +#include "playlists/PlayListFactory.h" +#include "threads/SystemClock.h" +#include "utils/ContentUtils.h" +#include "utils/JobManager.h" +#include "utils/LangCodeExpander.h" +#include "utils/Screenshot.h" +#include "utils/Variant.h" +#include "video/Bookmark.h" +#include "video/VideoLibraryQueue.h" + +#ifdef HAS_PYTHON +#include "interfaces/python/XBPython.h" +#endif +#include "GUILargeTextureManager.h" +#include "GUIPassword.h" +#include "GUIUserMessages.h" +#include "SectionLoader.h" +#include "SeekHandler.h" +#include "ServiceBroker.h" +#include "TextureCache.h" +#include "cores/DllLoader/DllLoaderContainer.h" +#include "filesystem/Directory.h" +#include "filesystem/DirectoryCache.h" +#include "filesystem/DllLibCurl.h" +#include "filesystem/PluginDirectory.h" +#include "filesystem/SpecialProtocol.h" +#include "guilib/GUIAudioManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/InertialScrollingHandler.h" +#include "input/KeyboardLayoutManager.h" +#include "input/actions/ActionTranslator.h" +#include "messaging/ApplicationMessenger.h" +#include "messaging/ThreadMessage.h" +#include "messaging/helpers/DialogHelper.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "playlists/PlayList.h" +#include "playlists/SmartPlayList.h" +#include "powermanagement/PowerManager.h" +#include "profiles/ProfileManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/DisplaySettings.h" +#include "settings/MediaSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "speech/ISpeechRecognition.h" +#include "threads/SingleLock.h" +#include "utils/CPUInfo.h" +#include "utils/FileExtensionProvider.h" +#include "utils/RegExp.h" +#include "utils/SystemInfo.h" +#include "utils/TimeUtils.h" +#include "utils/XTimeUtils.h" +#include "utils/log.h" +#include "windowing/WinSystem.h" +#include "windowing/WindowSystemFactory.h" + +#include <cmath> + +#ifdef HAS_UPNP +#include "network/upnp/UPnP.h" +#include "filesystem/UPnPDirectory.h" +#endif +#if defined(TARGET_POSIX) && defined(HAS_FILESYSTEM_SMB) +#include "platform/posix/filesystem/SMBFile.h" +#endif +#ifdef HAS_FILESYSTEM_NFS +#include "filesystem/NFSFile.h" +#endif +#include "PartyModeManager.h" +#include "network/ZeroconfBrowser.h" +#ifndef TARGET_POSIX +#include "platform/win32/threads/Win32Exception.h" +#endif +#include "interfaces/json-rpc/JSONRPC.h" +#include "interfaces/AnnouncementManager.h" +#include "peripherals/Peripherals.h" +#include "music/infoscanner/MusicInfoScanner.h" +#include "music/MusicUtils.h" +#include "music/MusicThumbLoader.h" + +// Windows includes +#include "guilib/GUIWindowManager.h" +#include "video/PlayerController.h" + +// Dialog includes +#include "addons/gui/GUIDialogAddonSettings.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "dialogs/GUIDialogSimpleMenu.h" +#include "video/dialogs/GUIDialogVideoBookmarks.h" + +// PVR related include Files +#include "pvr/PVRManager.h" +#include "pvr/guilib/PVRGUIActionsPlayback.h" +#include "pvr/guilib/PVRGUIActionsPowerManagement.h" + +#ifdef TARGET_WINDOWS +#include "win32util.h" +#endif + +#ifdef TARGET_DARWIN_OSX +#include "platform/darwin/osx/CocoaInterface.h" +#include "platform/darwin/osx/XBMCHelper.h" +#endif +#ifdef TARGET_DARWIN +#include "platform/darwin/DarwinUtils.h" +#endif + +#ifdef HAS_DVD_DRIVE +#include <cdio/logging.h> +#endif + +#include "DatabaseManager.h" +#include "input/InputManager.h" +#include "storage/MediaManager.h" +#include "utils/AlarmClock.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" + +#ifdef TARGET_POSIX +#include "platform/posix/XHandle.h" +#include "platform/posix/PlatformPosix.h" +#endif + +#if defined(TARGET_ANDROID) +#include "platform/android/activity/XBMCApp.h" +#endif + +#ifdef TARGET_WINDOWS +#include "platform/Environment.h" +#endif + +//TODO: XInitThreads +#ifdef HAVE_X11 +#include <X11/Xlib.h> +#endif + +#include "FileItem.h" +#include "addons/AddonSystemSettings.h" +#include "cores/FFmpeg.h" +#include "pictures/GUIWindowSlideShow.h" +#include "utils/CharsetConverter.h" + +#include <mutex> + +using namespace ADDON; +using namespace XFILE; +#ifdef HAS_DVD_DRIVE +using namespace MEDIA_DETECT; +#endif +using namespace VIDEO; +using namespace MUSIC_INFO; +using namespace EVENTSERVER; +using namespace JSONRPC; +using namespace PVR; +using namespace PERIPHERALS; +using namespace KODI; +using namespace KODI::MESSAGING; +using namespace ActiveAE; + +using namespace XbmcThreads; +using namespace std::chrono_literals; + +using KODI::MESSAGING::HELPERS::DialogResponse; + +using namespace std::chrono_literals; + +#define MAX_FFWD_SPEED 5 + +CApplication::CApplication(void) + : +#ifdef HAS_DVD_DRIVE + m_Autorun(new CAutorun()), +#endif + m_pInertialScrollingHandler(new CInertialScrollingHandler()), + m_WaitingExternalCalls(0) +{ + TiXmlBase::SetCondenseWhiteSpace(false); + +#ifdef HAVE_X11 + XInitThreads(); +#endif + + // register application components + RegisterComponent(std::make_shared<CApplicationActionListeners>(m_critSection)); + RegisterComponent(std::make_shared<CApplicationPlayer>()); + RegisterComponent(std::make_shared<CApplicationPowerHandling>()); + RegisterComponent(std::make_shared<CApplicationSkinHandling>(this, this, m_bInitializing)); + RegisterComponent(std::make_shared<CApplicationVolumeHandling>()); + RegisterComponent(std::make_shared<CApplicationStackHelper>()); +} + +CApplication::~CApplication(void) +{ + DeregisterComponent(typeid(CApplicationStackHelper)); + DeregisterComponent(typeid(CApplicationVolumeHandling)); + DeregisterComponent(typeid(CApplicationSkinHandling)); + DeregisterComponent(typeid(CApplicationPowerHandling)); + DeregisterComponent(typeid(CApplicationPlayer)); + DeregisterComponent(typeid(CApplicationActionListeners)); +} + +bool CApplication::OnEvent(XBMC_Event& newEvent) +{ + std::unique_lock<CCriticalSection> lock(m_portSection); + m_portEvents.push_back(newEvent); + return true; +} + +void CApplication::HandlePortEvents() +{ + std::unique_lock<CCriticalSection> lock(m_portSection); + while (!m_portEvents.empty()) + { + auto newEvent = m_portEvents.front(); + m_portEvents.pop_front(); + CSingleExit lock(m_portSection); + switch(newEvent.type) + { + case XBMC_QUIT: + if (!m_bStop) + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT); + break; + case XBMC_VIDEORESIZE: + if (CServiceBroker::GetGUI()->GetWindowManager().Initialized()) + { + if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen) + { + CServiceBroker::GetWinSystem()->GetGfxContext().ApplyWindowResize(newEvent.resize.w, newEvent.resize.h); + + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + settings->SetInt(CSettings::SETTING_WINDOW_WIDTH, newEvent.resize.w); + settings->SetInt(CSettings::SETTING_WINDOW_HEIGHT, newEvent.resize.h); + settings->Save(); + } +#ifdef TARGET_WINDOWS + else + { + // this may occurs when OS tries to resize application window + //CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP, true); + //auto& gfxContext = CServiceBroker::GetWinSystem()->GetGfxContext(); + //gfxContext.SetVideoResolution(gfxContext.GetVideoResolution(), true); + // try to resize window back to it's full screen size + //! TODO: DX windowing should emit XBMC_FULLSCREEN_UPDATE instead with the proper dimensions + //! and position to avoid the ifdef in common code + auto& res_info = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP); + CServiceBroker::GetWinSystem()->ResizeWindow(res_info.iScreenWidth, res_info.iScreenHeight, 0, 0); + } +#endif + } + break; + case XBMC_FULLSCREEN_UPDATE: + { + if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen) + { + CServiceBroker::GetWinSystem()->ResizeWindow(newEvent.resize.w, newEvent.resize.h, + newEvent.move.x, newEvent.move.y); + } + break; + } + case XBMC_VIDEOMOVE: + { + CServiceBroker::GetWinSystem()->OnMove(newEvent.move.x, newEvent.move.y); + } + break; + case XBMC_MODECHANGE: + CServiceBroker::GetWinSystem()->GetGfxContext().ApplyModeChange(newEvent.mode.res); + break; + case XBMC_USEREVENT: + CServiceBroker::GetAppMessenger()->PostMsg(static_cast<uint32_t>(newEvent.user.code)); + break; + case XBMC_SETFOCUS: + { + // Reset the screensaver + const auto appPower = GetComponent<CApplicationPowerHandling>(); + appPower->ResetScreenSaver(); + appPower->WakeUpScreenSaverAndDPMS(); + // Send a mouse motion event with no dx,dy for getting the current guiitem selected + OnAction(CAction(ACTION_MOUSE_MOVE, 0, static_cast<float>(newEvent.focus.x), static_cast<float>(newEvent.focus.y), 0, 0)); + break; + } + default: + CServiceBroker::GetInputManager().OnEvent(newEvent); + } + } +} + +extern "C" void __stdcall init_emu_environ(); +extern "C" void __stdcall update_emu_environ(); +extern "C" void __stdcall cleanup_emu_environ(); + +bool CApplication::Create() +{ + m_bStop = false; + + RegisterSettings(); + + CServiceBroker::RegisterCPUInfo(CCPUInfo::GetCPUInfo()); + + // Register JobManager service + CServiceBroker::RegisterJobManager(std::make_shared<CJobManager>()); + + // Announcement service + m_pAnnouncementManager = std::make_shared<ANNOUNCEMENT::CAnnouncementManager>(); + m_pAnnouncementManager->Start(); + CServiceBroker::RegisterAnnouncementManager(m_pAnnouncementManager); + + const auto appMessenger = std::make_shared<CApplicationMessenger>(); + CServiceBroker::RegisterAppMessenger(appMessenger); + + const auto keyboardLayoutManager = std::make_shared<CKeyboardLayoutManager>(); + CServiceBroker::RegisterKeyboardLayoutManager(keyboardLayoutManager); + + m_ServiceManager.reset(new CServiceManager()); + + if (!m_ServiceManager->InitStageOne()) + { + return false; + } + + // here we register all global classes for the CApplicationMessenger, + // after that we can send messages to the corresponding modules + appMessenger->RegisterReceiver(this); + appMessenger->RegisterReceiver(&CServiceBroker::GetPlaylistPlayer()); + appMessenger->SetGUIThread(CThread::GetCurrentThreadId()); + appMessenger->SetProcessThread(CThread::GetCurrentThreadId()); + + // copy required files + CUtil::CopyUserDataIfNeeded("special://masterprofile/", "RssFeeds.xml"); + CUtil::CopyUserDataIfNeeded("special://masterprofile/", "favourites.xml"); + CUtil::CopyUserDataIfNeeded("special://masterprofile/", "Lircmap.xml"); + + CServiceBroker::GetLogging().Initialize(CSpecialProtocol::TranslatePath("special://logpath")); + +#ifdef TARGET_POSIX //! @todo Win32 has no special://home/ mapping by default, so we + //! must create these here. Ideally this should be using special://home/ and + //! be platform agnostic (i.e. unify the InitDirectories*() functions) + if (!CServiceBroker::GetAppParams()->HasPlatformDirectories()) +#endif + { + CDirectory::Create("special://xbmc/addons"); + } + + // Init our DllLoaders emu env + init_emu_environ(); + + PrintStartupLog(); + + // initialize network protocols + avformat_network_init(); + // set avutil callback + av_log_set_callback(ff_avutil_log); + + CLog::Log(LOGINFO, "loading settings"); + const auto settingsComponent = CServiceBroker::GetSettingsComponent(); + if (!settingsComponent->Load()) + return false; + + CLog::Log(LOGINFO, "creating subdirectories"); + const std::shared_ptr<CProfileManager> profileManager = settingsComponent->GetProfileManager(); + const std::shared_ptr<CSettings> settings = settingsComponent->GetSettings(); + CLog::Log(LOGINFO, "userdata folder: {}", + CURL::GetRedacted(profileManager->GetProfileUserDataFolder())); + CLog::Log(LOGINFO, "recording folder: {}", + CURL::GetRedacted(settings->GetString(CSettings::SETTING_AUDIOCDS_RECORDINGPATH))); + CLog::Log(LOGINFO, "screenshots folder: {}", + CURL::GetRedacted(settings->GetString(CSettings::SETTING_DEBUG_SCREENSHOTPATH))); + CDirectory::Create(profileManager->GetUserDataFolder()); + CDirectory::Create(profileManager->GetProfileUserDataFolder()); + profileManager->CreateProfileFolders(); + + update_emu_environ();//apply the GUI settings + + // application inbound service + m_pAppPort = std::make_shared<CAppInboundProtocol>(*this); + CServiceBroker::RegisterAppPort(m_pAppPort); + + if (!m_ServiceManager->InitStageTwo( + settingsComponent->GetProfileManager()->GetProfileUserDataFolder())) + { + return false; + } + + m_pActiveAE.reset(new ActiveAE::CActiveAE()); + CServiceBroker::RegisterAE(m_pActiveAE.get()); + + // initialize m_replayGainSettings + GetComponent<CApplicationVolumeHandling>()->CacheReplayGainSettings(*settings); + + // load the keyboard layouts + if (!keyboardLayoutManager->Load()) + { + CLog::Log(LOGFATAL, "CApplication::Create: Unable to load keyboard layouts"); + return false; + } + + // set user defined CA trust bundle + std::string caCert = + CSpecialProtocol::TranslatePath(settingsComponent->GetAdvancedSettings()->m_caTrustFile); + if (!caCert.empty()) + { + if (XFILE::CFile::Exists(caCert)) + { + CEnvironment::setenv("SSL_CERT_FILE", caCert, 1); + CLog::Log(LOGDEBUG, "CApplication::Create - SSL_CERT_FILE: {}", caCert); + } + else + { + CLog::Log(LOGDEBUG, "CApplication::Create - Error reading SSL_CERT_FILE: {} -> ignored", + caCert); + } + } + + CUtil::InitRandomSeed(); + + m_lastRenderTime = std::chrono::steady_clock::now(); + return true; +} + +bool CApplication::CreateGUI() +{ + m_frameMoveGuard.lock(); + + const auto appPower = GetComponent<CApplicationPowerHandling>(); + appPower->SetRenderGUI(true); + + auto windowSystems = KODI::WINDOWING::CWindowSystemFactory::GetWindowSystems(); + + const std::string& windowing = CServiceBroker::GetAppParams()->GetWindowing(); + + if (!windowing.empty()) + windowSystems = {windowing}; + + for (auto& windowSystem : windowSystems) + { + CLog::Log(LOGDEBUG, "CApplication::{} - trying to init {} windowing system", __FUNCTION__, + windowSystem); + m_pWinSystem = KODI::WINDOWING::CWindowSystemFactory::CreateWindowSystem(windowSystem); + + if (!m_pWinSystem) + continue; + + if (!windowing.empty() && windowing != windowSystem) + continue; + + CServiceBroker::RegisterWinSystem(m_pWinSystem.get()); + + if (!m_pWinSystem->InitWindowSystem()) + { + CLog::Log(LOGDEBUG, "CApplication::{} - unable to init {} windowing system", __FUNCTION__, + windowSystem); + m_pWinSystem->DestroyWindowSystem(); + m_pWinSystem.reset(); + CServiceBroker::UnregisterWinSystem(); + continue; + } + else + { + CLog::Log(LOGINFO, "CApplication::{} - using the {} windowing system", __FUNCTION__, + windowSystem); + break; + } + } + + if (!m_pWinSystem) + { + CLog::Log(LOGFATAL, "CApplication::{} - unable to init windowing system", __FUNCTION__); + CServiceBroker::UnregisterWinSystem(); + return false; + } + + // Retrieve the matching resolution based on GUI settings + bool sav_res = false; + CDisplaySettings::GetInstance().SetCurrentResolution(CDisplaySettings::GetInstance().GetDisplayResolution()); + CLog::Log(LOGINFO, "Checking resolution {}", + CDisplaySettings::GetInstance().GetCurrentResolution()); + if (!CServiceBroker::GetWinSystem()->GetGfxContext().IsValidResolution(CDisplaySettings::GetInstance().GetCurrentResolution())) + { + CLog::Log(LOGINFO, "Setting safe mode {}", RES_DESKTOP); + // defer saving resolution after window was created + CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP); + sav_res = true; + } + + // update the window resolution + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + CServiceBroker::GetWinSystem()->SetWindowResolution(settings->GetInt(CSettings::SETTING_WINDOW_WIDTH), settings->GetInt(CSettings::SETTING_WINDOW_HEIGHT)); + + if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_startFullScreen && CDisplaySettings::GetInstance().GetCurrentResolution() == RES_WINDOW) + { + // defer saving resolution after window was created + CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP); + sav_res = true; + } + + if (!CServiceBroker::GetWinSystem()->GetGfxContext().IsValidResolution(CDisplaySettings::GetInstance().GetCurrentResolution())) + { + // Oh uh - doesn't look good for starting in their wanted screenmode + CLog::Log(LOGERROR, "The screen resolution requested is not valid, resetting to a valid mode"); + CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP); + sav_res = true; + } + if (!InitWindow()) + { + return false; + } + + // Set default screen saver mode + auto screensaverModeSetting = std::static_pointer_cast<CSettingString>(settings->GetSetting(CSettings::SETTING_SCREENSAVER_MODE)); + // Can only set this after windowing has been initialized since it depends on it + if (CServiceBroker::GetWinSystem()->GetOSScreenSaver()) + { + // If OS has a screen saver, use it by default + screensaverModeSetting->SetDefault(""); + } + else + { + // If OS has no screen saver, use Kodi one by default + screensaverModeSetting->SetDefault("screensaver.xbmc.builtin.dim"); + } + + if (sav_res) + CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP, true); + + m_pGUI.reset(new CGUIComponent()); + m_pGUI->Init(); + + // Splash requires gui component!! + CServiceBroker::GetRenderSystem()->ShowSplash(""); + + // The key mappings may already have been loaded by a peripheral + CLog::Log(LOGINFO, "load keymapping"); + if (!CServiceBroker::GetInputManager().LoadKeymaps()) + return false; + + RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(); + CLog::Log(LOGINFO, "GUI format {}x{}, Display {}", info.iWidth, info.iHeight, info.strMode); + + return true; +} + +bool CApplication::InitWindow(RESOLUTION res) +{ + if (res == RES_INVALID) + res = CDisplaySettings::GetInstance().GetCurrentResolution(); + + bool bFullScreen = res != RES_WINDOW; + if (!CServiceBroker::GetWinSystem()->CreateNewWindow(CSysInfo::GetAppName(), + bFullScreen, CDisplaySettings::GetInstance().GetResolutionInfo(res))) + { + CLog::Log(LOGFATAL, "CApplication::Create: Unable to create window"); + return false; + } + + if (!CServiceBroker::GetRenderSystem()->InitRenderSystem()) + { + CLog::Log(LOGFATAL, "CApplication::Create: Unable to init rendering system"); + return false; + } + // set GUI res and force the clear of the screen + CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(res, false); + return true; +} + +bool CApplication::Initialize() +{ + m_pActiveAE->Start(); + // restore AE's previous volume state + + const auto appVolume = GetComponent<CApplicationVolumeHandling>(); + const auto level = appVolume->GetVolumeRatio(); + const auto muted = appVolume->IsMuted(); + appVolume->SetHardwareVolume(level); + CServiceBroker::GetActiveAE()->SetMute(muted); + +#if defined(HAS_DVD_DRIVE) && !defined(TARGET_WINDOWS) // somehow this throws an "unresolved external symbol" on win32 + // turn off cdio logging + cdio_loglevel_default = CDIO_LOG_ERROR; +#endif + + // load the language and its translated strings + if (!LoadLanguage(false)) + return false; + + // load media manager sources (e.g. root addon type sources depend on language strings to be available) + CServiceBroker::GetMediaManager().LoadSources(); + + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + profileManager->GetEventLog().Add(EventPtr(new CNotificationEvent( + StringUtils::Format(g_localizeStrings.Get(177), g_sysinfo.GetAppName()), + StringUtils::Format(g_localizeStrings.Get(178), g_sysinfo.GetAppName()), + "special://xbmc/media/icon256x256.png", EventLevel::Basic))); + + m_ServiceManager->GetNetwork().WaitForNet(); + + // initialize (and update as needed) our databases + CDatabaseManager &databaseManager = m_ServiceManager->GetDatabaseManager(); + + CEvent event(true); + CServiceBroker::GetJobManager()->Submit([&databaseManager, &event]() { + databaseManager.Initialize(); + event.Set(); + }); + + std::string localizedStr = g_localizeStrings.Get(24150); + int iDots = 1; + while (!event.Wait(1000ms)) + { + if (databaseManager.IsUpgrading()) + CServiceBroker::GetRenderSystem()->ShowSplash(std::string(iDots, ' ') + localizedStr + std::string(iDots, '.')); + + if (iDots == 3) + iDots = 1; + else + ++iDots; + } + CServiceBroker::GetRenderSystem()->ShowSplash(""); + + // Initialize GUI font manager to build/update fonts cache + //! @todo Move GUIFontManager into service broker and drop the global reference + event.Reset(); + GUIFontManager& guiFontManager = g_fontManager; + CServiceBroker::GetJobManager()->Submit([&guiFontManager, &event]() { + guiFontManager.Initialize(); + event.Set(); + }); + localizedStr = g_localizeStrings.Get(39175); + iDots = 1; + while (!event.Wait(1000ms)) + { + if (g_fontManager.IsUpdating()) + CServiceBroker::GetRenderSystem()->ShowSplash(std::string(iDots, ' ') + localizedStr + + std::string(iDots, '.')); + + if (iDots == 3) + iDots = 1; + else + ++iDots; + } + CServiceBroker::GetRenderSystem()->ShowSplash(""); + + // GUI depends on seek handler + GetComponent<CApplicationPlayer>()->GetSeekHandler().Configure(); + + const auto skinHandling = GetComponent<CApplicationSkinHandling>(); + + bool uiInitializationFinished = false; + + if (CServiceBroker::GetGUI()->GetWindowManager().Initialized()) + { + const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + + CServiceBroker::GetGUI()->GetWindowManager().CreateWindows(); + + skinHandling->m_confirmSkinChange = false; + + std::vector<AddonInfoPtr> incompatibleAddons; + event.Reset(); + + // Addon migration + if (CServiceBroker::GetAddonMgr().GetIncompatibleEnabledAddonInfos(incompatibleAddons)) + { + if (CAddonSystemSettings::GetInstance().GetAddonAutoUpdateMode() == AUTO_UPDATES_ON) + { + CServiceBroker::GetJobManager()->Submit( + [&event, &incompatibleAddons]() { + if (CServiceBroker::GetRepositoryUpdater().CheckForUpdates()) + CServiceBroker::GetRepositoryUpdater().Await(); + + incompatibleAddons = CServiceBroker::GetAddonMgr().MigrateAddons(); + event.Set(); + }, + CJob::PRIORITY_DEDICATED); + localizedStr = g_localizeStrings.Get(24151); + iDots = 1; + while (!event.Wait(1000ms)) + { + CServiceBroker::GetRenderSystem()->ShowSplash(std::string(iDots, ' ') + localizedStr + + std::string(iDots, '.')); + if (iDots == 3) + iDots = 1; + else + ++iDots; + } + m_incompatibleAddons = incompatibleAddons; + } + else + { + // If no update is active disable all incompatible addons during start + m_incompatibleAddons = + CServiceBroker::GetAddonMgr().DisableIncompatibleAddons(incompatibleAddons); + } + } + + // Start splashscreen and load skin + CServiceBroker::GetRenderSystem()->ShowSplash(""); + skinHandling->m_confirmSkinChange = true; + + auto setting = settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKIN); + if (!setting) + { + CLog::Log(LOGFATAL, "Failed to load setting for: {}", CSettings::SETTING_LOOKANDFEEL_SKIN); + return false; + } + + CServiceBroker::RegisterTextureCache(std::make_shared<CTextureCache>()); + + std::string skinId = settings->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN); + if (!skinHandling->LoadSkin(skinId)) + { + CLog::Log(LOGERROR, "Failed to load skin '{}'", skinId); + std::string defaultSkin = + std::static_pointer_cast<const CSettingString>(setting)->GetDefault(); + if (!skinHandling->LoadSkin(defaultSkin)) + { + CLog::Log(LOGFATAL, "Default skin '{}' could not be loaded! Terminating..", defaultSkin); + return false; + } + } + + // initialize splash window after splash screen disappears + // because we need a real window in the background which gets + // rendered while we load the main window or enter the master lock key + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SPLASH); + + if (settings->GetBool(CSettings::SETTING_MASTERLOCK_STARTUPLOCK) && + profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && + !profileManager->GetMasterProfile().getLockCode().empty()) + { + g_passwordManager.CheckStartUpLock(); + } + + // check if we should use the login screen + if (profileManager->UsingLoginScreen()) + { + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_LOGIN_SCREEN); + } + else + { + // activate the configured start window + int firstWindow = g_SkinInfo->GetFirstWindow(); + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(firstWindow); + + if (CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_STARTUP_ANIM)) + { + CLog::Log(LOGWARNING, "CApplication::Initialize - startup.xml taints init process"); + } + + // the startup window is considered part of the initialization as it most likely switches to the final window + uiInitializationFinished = firstWindow != WINDOW_STARTUP_ANIM; + } + } + else //No GUI Created + { + uiInitializationFinished = true; + } + + CJSONRPC::Initialize(); + + CServiceBroker::RegisterSpeechRecognition(speech::ISpeechRecognition::CreateInstance()); + + if (!m_ServiceManager->InitStageThree(profileManager)) + { + CLog::Log(LOGERROR, "Application - Init3 failed"); + } + + g_sysinfo.Refresh(); + + CLog::Log(LOGINFO, "removing tempfiles"); + CUtil::RemoveTempFiles(); + + if (!profileManager->UsingLoginScreen()) + { + UpdateLibraries(); + SetLoggingIn(false); + } + + m_slowTimer.StartZero(); + + // register action listeners + const auto appListener = GetComponent<CApplicationActionListeners>(); + const auto appPlayer = GetComponent<CApplicationPlayer>(); + appListener->RegisterActionListener(&appPlayer->GetSeekHandler()); + appListener->RegisterActionListener(&CPlayerController::GetInstance()); + + CServiceBroker::GetRepositoryUpdater().Start(); + if (!profileManager->UsingLoginScreen()) + CServiceBroker::GetServiceAddons().Start(); + + CLog::Log(LOGINFO, "initialize done"); + + const auto appPower = GetComponent<CApplicationPowerHandling>(); + appPower->CheckOSScreenSaverInhibitionSetting(); + // reset our screensaver (starts timers etc.) + appPower->ResetScreenSaver(); + + // if the user interfaces has been fully initialized let everyone know + if (uiInitializationFinished) + { + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UI_READY); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + } + + return true; +} + +bool CApplication::OnSettingsSaving() const +{ + // don't save settings when we're busy stopping the application + // a lot of screens try to save settings on deinit and deinit is + // called for every screen when the application is stopping + return !m_bStop; +} + +void CApplication::Render() +{ + // do not render if we are stopped or in background + if (m_bStop) + return; + + const auto appPlayer = GetComponent<CApplicationPlayer>(); + const auto appPower = GetComponent<CApplicationPowerHandling>(); + + bool hasRendered = false; + + // Whether externalplayer is playing and we're unfocused + bool extPlayerActive = appPlayer->IsExternalPlaying() && !m_AppFocused; + + if (!extPlayerActive && CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenVideo() && + !appPlayer->IsPausedPlayback()) + { + appPower->ResetScreenSaver(); + } + + if (!CServiceBroker::GetRenderSystem()->BeginRender()) + return; + + // render gui layer + if (appPower->GetRenderGUI() && !m_skipGuiRender) + { + if (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode()) + { + CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RENDER_STEREO_VIEW_LEFT); + hasRendered |= CServiceBroker::GetGUI()->GetWindowManager().Render(); + + if (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() != RENDER_STEREO_MODE_MONO) + { + CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RENDER_STEREO_VIEW_RIGHT); + hasRendered |= CServiceBroker::GetGUI()->GetWindowManager().Render(); + } + CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RENDER_STEREO_VIEW_OFF); + } + else + { + hasRendered |= CServiceBroker::GetGUI()->GetWindowManager().Render(); + } + // execute post rendering actions (finalize window closing) + CServiceBroker::GetGUI()->GetWindowManager().AfterRender(); + + m_lastRenderTime = std::chrono::steady_clock::now(); + } + + // render video layer + CServiceBroker::GetGUI()->GetWindowManager().RenderEx(); + + CServiceBroker::GetRenderSystem()->EndRender(); + + // reset our info cache - we do this at the end of Render so that it is + // fresh for the next process(), or after a windowclose animation (where process() + // isn't called) + CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager(); + infoMgr.ResetCache(); + infoMgr.GetInfoProviders().GetGUIControlsInfoProvider().ResetContainerMovingCache(); + + if (hasRendered) + { + infoMgr.GetInfoProviders().GetSystemInfoProvider().UpdateFPS(); + } + + CServiceBroker::GetWinSystem()->GetGfxContext().Flip(hasRendered, + appPlayer->IsRenderingVideoLayer()); + + CTimeUtils::UpdateFrameTime(hasRendered); +} + +bool CApplication::OnAction(const CAction &action) +{ + // special case for switching between GUI & fullscreen mode. + if (action.GetID() == ACTION_SHOW_GUI) + { // Switch to fullscreen mode if we can + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (gui) + { + if (gui->GetWindowManager().SwitchToFullScreen()) + { + GetComponent<CApplicationPowerHandling>()->m_navigationTimer.StartZero(); + return true; + } + } + } + + const auto appPlayer = GetComponent<CApplicationPlayer>(); + + if (action.GetID() == ACTION_TOGGLE_FULLSCREEN) + { + CServiceBroker::GetWinSystem()->GetGfxContext().ToggleFullScreen(); + appPlayer->TriggerUpdateResolution(); + return true; + } + + if (action.IsMouse()) + CServiceBroker::GetInputManager().SetMouseActive(true); + + if (action.GetID() == ACTION_CREATE_EPISODE_BOOKMARK) + { + CGUIDialogVideoBookmarks::OnAddEpisodeBookmark(); + } + if (action.GetID() == ACTION_CREATE_BOOKMARK) + { + CGUIDialogVideoBookmarks::OnAddBookmark(); + } + + // The action PLAYPAUSE behaves as ACTION_PAUSE if we are currently + // playing or ACTION_PLAYER_PLAY if we are seeking (FF/RW) or not playing. + if (action.GetID() == ACTION_PLAYER_PLAYPAUSE) + { + CGUIWindowSlideShow* pSlideShow = CServiceBroker::GetGUI()-> + GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if ((appPlayer->IsPlaying() && appPlayer->GetPlaySpeed() == 1) || + (pSlideShow && pSlideShow->InSlideShow() && !pSlideShow->IsPaused())) + return OnAction(CAction(ACTION_PAUSE)); + else + return OnAction(CAction(ACTION_PLAYER_PLAY)); + } + + //if the action would start or stop inertial scrolling + //by gesture - bypass the normal OnAction handler of current window + if( !m_pInertialScrollingHandler->CheckForInertialScrolling(&action) ) + { + // in normal case + // just pass the action to the current window and let it handle it + if (CServiceBroker::GetGUI()->GetWindowManager().OnAction(action)) + { + GetComponent<CApplicationPowerHandling>()->ResetNavigationTimer(); + return true; + } + } + + // handle extra global presses + + // notify action listeners + if (GetComponent<CApplicationActionListeners>()->NotifyActionListeners(action)) + return true; + + // screenshot : take a screenshot :) + if (action.GetID() == ACTION_TAKE_SCREENSHOT) + { + CScreenShot::TakeScreenshot(); + return true; + } + // Display HDR : toggle HDR on/off + if (action.GetID() == ACTION_HDR_TOGGLE) + { + // Only enables manual HDR toggle if no video is playing or auto HDR switch is disabled + if (appPlayer->IsPlayingVideo() && + CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CServiceBroker::GetWinSystem()->SETTING_WINSYSTEM_IS_HDR_DISPLAY)) + return true; + + HDR_STATUS hdrStatus = CServiceBroker::GetWinSystem()->ToggleHDR(); + + if (hdrStatus == HDR_STATUS::HDR_OFF) + { + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(34220), + g_localizeStrings.Get(34221)); + } + else if (hdrStatus == HDR_STATUS::HDR_ON) + { + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(34220), + g_localizeStrings.Get(34222)); + } + return true; + } + // Tone Mapping : switch to next tone map method + if (action.GetID() == ACTION_CYCLE_TONEMAP_METHOD) + { + // Only enables tone mapping switch if display is not HDR capable or HDR is not enabled + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CServiceBroker::GetWinSystem()->SETTING_WINSYSTEM_IS_HDR_DISPLAY) && + CServiceBroker::GetWinSystem()->IsHDRDisplay()) + return true; + + if (appPlayer->IsPlayingVideo()) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_ToneMapMethod = static_cast<ETONEMAPMETHOD>(static_cast<int>(vs.m_ToneMapMethod) + 1); + if (vs.m_ToneMapMethod >= VS_TONEMAPMETHOD_MAX) + vs.m_ToneMapMethod = + static_cast<ETONEMAPMETHOD>(static_cast<int>(VS_TONEMAPMETHOD_OFF) + 1); + + appPlayer->SetVideoSettings(vs); + + int code = 0; + switch (vs.m_ToneMapMethod) + { + case VS_TONEMAPMETHOD_REINHARD: + code = 36555; + break; + case VS_TONEMAPMETHOD_ACES: + code = 36557; + break; + case VS_TONEMAPMETHOD_HABLE: + code = 36558; + break; + default: + throw std::logic_error("Tonemapping method not found. Did you forget to add a mapping?"); + } + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(34224), + g_localizeStrings.Get(code), 1000, false, 500); + } + return true; + } + // built in functions : execute the built-in + if (action.GetID() == ACTION_BUILT_IN_FUNCTION) + { + if (!CBuiltins::GetInstance().IsSystemPowerdownCommand(action.GetName()) || + CServiceBroker::GetPVRManager().Get<PVR::GUI::PowerManagement>().CanSystemPowerdown()) + { + CBuiltins::GetInstance().Execute(action.GetName()); + GetComponent<CApplicationPowerHandling>()->ResetNavigationTimer(); + } + return true; + } + + // reload keymaps + if (action.GetID() == ACTION_RELOAD_KEYMAPS) + CServiceBroker::GetInputManager().ReloadKeymaps(); + + // show info : Shows the current video or song information + if (action.GetID() == ACTION_SHOW_INFO) + { + CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPlayerInfoProvider().ToggleShowInfo(); + return true; + } + + if (action.GetID() == ACTION_SET_RATING && appPlayer->IsPlayingAudio()) + { + int userrating = MUSIC_UTILS::ShowSelectRatingDialog(m_itemCurrentFile->GetMusicInfoTag()->GetUserrating()); + if (userrating < 0) // Nothing selected, so user rating unchanged + return true; + userrating = std::min(userrating, 10); + if (userrating != m_itemCurrentFile->GetMusicInfoTag()->GetUserrating()) + { + m_itemCurrentFile->GetMusicInfoTag()->SetUserrating(userrating); + // Mirror changes to GUI item + CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile); + + // Asynchronously update song userrating in music library + MUSIC_UTILS::UpdateSongRatingJob(m_itemCurrentFile, userrating); + + // Tell all windows (e.g. playlistplayer, media windows) to update the fileitem + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_itemCurrentFile); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + } + return true; + } + + else if ((action.GetID() == ACTION_INCREASE_RATING || action.GetID() == ACTION_DECREASE_RATING) && + appPlayer->IsPlayingAudio()) + { + int userrating = m_itemCurrentFile->GetMusicInfoTag()->GetUserrating(); + bool needsUpdate(false); + if (userrating > 0 && action.GetID() == ACTION_DECREASE_RATING) + { + m_itemCurrentFile->GetMusicInfoTag()->SetUserrating(userrating - 1); + needsUpdate = true; + } + else if (userrating < 10 && action.GetID() == ACTION_INCREASE_RATING) + { + m_itemCurrentFile->GetMusicInfoTag()->SetUserrating(userrating + 1); + needsUpdate = true; + } + if (needsUpdate) + { + // Mirror changes to current GUI item + CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile); + + // Asynchronously update song userrating in music library + MUSIC_UTILS::UpdateSongRatingJob(m_itemCurrentFile, m_itemCurrentFile->GetMusicInfoTag()->GetUserrating()); + + // send a message to all windows to tell them to update the fileitem (eg playlistplayer, media windows) + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_itemCurrentFile); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + } + + return true; + } + else if ((action.GetID() == ACTION_INCREASE_RATING || action.GetID() == ACTION_DECREASE_RATING) && + appPlayer->IsPlayingVideo()) + { + int rating = m_itemCurrentFile->GetVideoInfoTag()->m_iUserRating; + bool needsUpdate(false); + if (rating > 1 && action.GetID() == ACTION_DECREASE_RATING) + { + m_itemCurrentFile->GetVideoInfoTag()->m_iUserRating = rating - 1; + needsUpdate = true; + } + else if (rating < 10 && action.GetID() == ACTION_INCREASE_RATING) + { + m_itemCurrentFile->GetVideoInfoTag()->m_iUserRating = rating + 1; + needsUpdate = true; + } + if (needsUpdate) + { + // Mirror changes to GUI item + CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile); + + CVideoDatabase db; + if (db.Open()) + { + db.SetVideoUserRating(m_itemCurrentFile->GetVideoInfoTag()->m_iDbId, + m_itemCurrentFile->GetVideoInfoTag()->m_iUserRating, + m_itemCurrentFile->GetVideoInfoTag()->m_type); + db.Close(); + } + // send a message to all windows to tell them to update the fileitem (eg playlistplayer, media windows) + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_itemCurrentFile); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + } + return true; + } + + // Now check with the playlist player if action can be handled. + // In case of ACTION_PREV_ITEM, we only allow the playlist player to take it if we're less than ACTION_PREV_ITEM_THRESHOLD seconds into playback. + if (!(action.GetID() == ACTION_PREV_ITEM && appPlayer->CanSeek() && + GetTime() > ACTION_PREV_ITEM_THRESHOLD)) + { + if (CServiceBroker::GetPlaylistPlayer().OnAction(action)) + return true; + } + + // Now check with the player if action can be handled. + bool bIsPlayingPVRChannel = (CServiceBroker::GetPVRManager().IsStarted() && + CurrentFileItem().IsPVRChannel()); + + bool bNotifyPlayer = false; + if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO) + bNotifyPlayer = true; + else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_GAME) + bNotifyPlayer = true; + else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION && bIsPlayingPVRChannel) + bNotifyPlayer = true; + else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_DIALOG_VIDEO_OSD || + (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_DIALOG_MUSIC_OSD && bIsPlayingPVRChannel)) + { + switch (action.GetID()) + { + case ACTION_NEXT_ITEM: + case ACTION_PREV_ITEM: + case ACTION_CHANNEL_UP: + case ACTION_CHANNEL_DOWN: + bNotifyPlayer = true; + break; + default: + break; + } + } + else if (action.GetID() == ACTION_STOP) + bNotifyPlayer = true; + + if (bNotifyPlayer) + { + if (appPlayer->OnAction(action)) + return true; + } + + // stop : stops playing current audio song + if (action.GetID() == ACTION_STOP) + { + StopPlaying(); + return true; + } + + // In case the playlist player nor the player didn't handle PREV_ITEM, because we are past the ACTION_PREV_ITEM_THRESHOLD secs limit. + // If so, we just jump to the start of the track. + if (action.GetID() == ACTION_PREV_ITEM && appPlayer->CanSeek()) + { + SeekTime(0); + appPlayer->SetPlaySpeed(1); + return true; + } + + // forward action to graphic context and see if it can handle it + if (CServiceBroker::GetGUI()->GetStereoscopicsManager().OnAction(action)) + return true; + + if (appPlayer->IsPlaying()) + { + // forward channel switches to the player - he knows what to do + if (action.GetID() == ACTION_CHANNEL_UP || action.GetID() == ACTION_CHANNEL_DOWN) + { + appPlayer->OnAction(action); + return true; + } + + // pause : toggle pause action + if (action.GetID() == ACTION_PAUSE) + { + appPlayer->Pause(); + // go back to normal play speed on unpause + if (!appPlayer->IsPaused() && appPlayer->GetPlaySpeed() != 1) + appPlayer->SetPlaySpeed(1); + + CGUIComponent *gui = CServiceBroker::GetGUI(); + if (gui) + gui->GetAudioManager().Enable(appPlayer->IsPaused()); + return true; + } + // play: unpause or set playspeed back to normal + if (action.GetID() == ACTION_PLAYER_PLAY) + { + // if currently paused - unpause + if (appPlayer->IsPaused()) + return OnAction(CAction(ACTION_PAUSE)); + // if we do a FF/RW then go back to normal speed + if (appPlayer->GetPlaySpeed() != 1) + appPlayer->SetPlaySpeed(1); + return true; + } + if (!appPlayer->IsPaused()) + { + if (action.GetID() == ACTION_PLAYER_FORWARD || action.GetID() == ACTION_PLAYER_REWIND) + { + float playSpeed = appPlayer->GetPlaySpeed(); + + if (action.GetID() == ACTION_PLAYER_REWIND && (playSpeed == 1)) // Enables Rewinding + playSpeed *= -2; + else if (action.GetID() == ACTION_PLAYER_REWIND && playSpeed > 1) //goes down a notch if you're FFing + playSpeed /= 2; + else if (action.GetID() == ACTION_PLAYER_FORWARD && playSpeed < 1) //goes up a notch if you're RWing + playSpeed /= 2; + else + playSpeed *= 2; + + if (action.GetID() == ACTION_PLAYER_FORWARD && playSpeed == -1) //sets iSpeed back to 1 if -1 (didn't plan for a -1) + playSpeed = 1; + if (playSpeed > 32 || playSpeed < -32) + playSpeed = 1; + + appPlayer->SetPlaySpeed(playSpeed); + return true; + } + else if ((action.GetAmount() || appPlayer->GetPlaySpeed() != 1) && + (action.GetID() == ACTION_ANALOG_REWIND || action.GetID() == ACTION_ANALOG_FORWARD)) + { + // calculate the speed based on the amount the button is held down + int iPower = (int)(action.GetAmount() * MAX_FFWD_SPEED + 0.5f); + // amount can be negative, for example rewind and forward share the same axis + iPower = std::abs(iPower); + // returns 0 -> MAX_FFWD_SPEED + int iSpeed = 1 << iPower; + if (iSpeed != 1 && action.GetID() == ACTION_ANALOG_REWIND) + iSpeed = -iSpeed; + appPlayer->SetPlaySpeed(static_cast<float>(iSpeed)); + if (iSpeed == 1) + CLog::Log(LOGDEBUG,"Resetting playspeed"); + return true; + } + } + // allow play to unpause + else + { + if (action.GetID() == ACTION_PLAYER_PLAY) + { + // unpause, and set the playspeed back to normal + appPlayer->Pause(); + + CGUIComponent *gui = CServiceBroker::GetGUI(); + if (gui) + gui->GetAudioManager().Enable(appPlayer->IsPaused()); + + appPlayer->SetPlaySpeed(1); + return true; + } + } + } + + + if (action.GetID() == ACTION_SWITCH_PLAYER) + { + const CPlayerCoreFactory &playerCoreFactory = m_ServiceManager->GetPlayerCoreFactory(); + + if (appPlayer->IsPlaying()) + { + std::vector<std::string> players; + CFileItem item(*m_itemCurrentFile.get()); + playerCoreFactory.GetPlayers(item, players); + std::string player = playerCoreFactory.SelectPlayerDialog(players); + if (!player.empty()) + { + item.SetStartOffset(CUtil::ConvertSecsToMilliSecs(GetTime())); + PlayFile(item, player, true); + } + } + else + { + std::vector<std::string> players; + playerCoreFactory.GetRemotePlayers(players); + std::string player = playerCoreFactory.SelectPlayerDialog(players); + if (!player.empty()) + { + PlayFile(CFileItem(), player, false); + } + } + } + + if (CServiceBroker::GetPeripherals().OnAction(action)) + return true; + + if (action.GetID() == ACTION_MUTE) + { + const auto appVolume = GetComponent<CApplicationVolumeHandling>(); + appVolume->ToggleMute(); + appVolume->ShowVolumeBar(&action); + return true; + } + + if (action.GetID() == ACTION_TOGGLE_DIGITAL_ANALOG) + { + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + bool passthrough = settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH); + settings->SetBool(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH, !passthrough); + + if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SETTINGS_SYSTEM) + { + CGUIMessage msg(GUI_MSG_WINDOW_INIT, 0,0,WINDOW_INVALID,CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow()); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + } + return true; + } + + // Check for global volume control + if ((action.GetAmount() && (action.GetID() == ACTION_VOLUME_UP || action.GetID() == ACTION_VOLUME_DOWN)) || action.GetID() == ACTION_VOLUME_SET) + { + const auto appVolume = GetComponent<CApplicationVolumeHandling>(); + if (!appPlayer->IsPassthrough()) + { + if (appVolume->IsMuted()) + appVolume->UnMute(); + float volume = appVolume->GetVolumeRatio(); + int volumesteps = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_AUDIOOUTPUT_VOLUMESTEPS); + // sanity check + if (volumesteps == 0) + volumesteps = 90; + +// Android has steps based on the max available volume level +#if defined(TARGET_ANDROID) + float step = (CApplicationVolumeHandling::VOLUME_MAXIMUM - + CApplicationVolumeHandling::VOLUME_MINIMUM) / + CXBMCApp::GetMaxSystemVolume(); +#else + float step = (CApplicationVolumeHandling::VOLUME_MAXIMUM - + CApplicationVolumeHandling::VOLUME_MINIMUM) / + volumesteps; + + if (action.GetRepeat()) + step *= action.GetRepeat() * 50; // 50 fps +#endif + if (action.GetID() == ACTION_VOLUME_UP) + volume += action.GetAmount() * action.GetAmount() * step; + else if (action.GetID() == ACTION_VOLUME_DOWN) + volume -= action.GetAmount() * action.GetAmount() * step; + else + volume = action.GetAmount() * step; + if (volume != appVolume->GetVolumeRatio()) + appVolume->SetVolume(volume, false); + } + // show visual feedback of volume or passthrough indicator + appVolume->ShowVolumeBar(&action); + return true; + } + + if (action.GetID() == ACTION_GUIPROFILE_BEGIN) + { + CGUIControlProfiler::Instance().SetOutputFile(CSpecialProtocol::TranslatePath("special://home/guiprofiler.xml")); + CGUIControlProfiler::Instance().Start(); + return true; + } + if (action.GetID() == ACTION_SHOW_PLAYLIST) + { + const PLAYLIST::Id playlistId = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(); + if (playlistId == PLAYLIST::TYPE_VIDEO && + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_VIDEO_PLAYLIST) + { + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_PLAYLIST); + } + else if (playlistId == PLAYLIST::TYPE_MUSIC && + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != + WINDOW_MUSIC_PLAYLIST) + { + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST); + } + return true; + } + return false; +} + +int CApplication::GetMessageMask() +{ + return TMSG_MASK_APPLICATION; +} + +void CApplication::OnApplicationMessage(ThreadMessage* pMsg) +{ + uint32_t msg = pMsg->dwMessage; + if (msg == TMSG_SYSTEM_POWERDOWN) + { + if (CServiceBroker::GetPVRManager().Get<PVR::GUI::PowerManagement>().CanSystemPowerdown()) + msg = pMsg->param1; // perform requested shutdown action + else + return; // no shutdown + } + + const auto appPlayer = GetComponent<CApplicationPlayer>(); + + switch (msg) + { + case TMSG_POWERDOWN: + if (Stop(EXITCODE_POWERDOWN)) + CServiceBroker::GetPowerManager().Powerdown(); + break; + + case TMSG_QUIT: + Stop(EXITCODE_QUIT); + break; + + case TMSG_SHUTDOWN: + GetComponent<CApplicationPowerHandling>()->HandleShutdownMessage(); + break; + + case TMSG_RENDERER_FLUSH: + appPlayer->FlushRenderer(); + break; + + case TMSG_HIBERNATE: + CServiceBroker::GetPowerManager().Hibernate(); + break; + + case TMSG_SUSPEND: + CServiceBroker::GetPowerManager().Suspend(); + break; + + case TMSG_RESTART: + case TMSG_RESET: + if (Stop(EXITCODE_REBOOT)) + CServiceBroker::GetPowerManager().Reboot(); + break; + + case TMSG_RESTARTAPP: +#if defined(TARGET_WINDOWS) || defined(TARGET_LINUX) + Stop(EXITCODE_RESTARTAPP); +#endif + break; + + case TMSG_INHIBITIDLESHUTDOWN: + GetComponent<CApplicationPowerHandling>()->InhibitIdleShutdown(pMsg->param1 != 0); + break; + + case TMSG_INHIBITSCREENSAVER: + GetComponent<CApplicationPowerHandling>()->InhibitScreenSaver(pMsg->param1 != 0); + break; + + case TMSG_ACTIVATESCREENSAVER: + GetComponent<CApplicationPowerHandling>()->ActivateScreenSaver(); + break; + + case TMSG_RESETSCREENSAVER: + GetComponent<CApplicationPowerHandling>()->m_bResetScreenSaver = true; + break; + + case TMSG_VOLUME_SHOW: + { + CAction action(pMsg->param1); + GetComponent<CApplicationVolumeHandling>()->ShowVolumeBar(&action); + } + break; + +#ifdef TARGET_ANDROID + case TMSG_DISPLAY_SETUP: + // We might come from a refresh rate switch destroying the native window; use the context resolution + *static_cast<bool*>(pMsg->lpVoid) = InitWindow(CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution()); + GetComponent<CApplicationPowerHandling>()->SetRenderGUI(true); + break; + + case TMSG_DISPLAY_DESTROY: + *static_cast<bool*>(pMsg->lpVoid) = CServiceBroker::GetWinSystem()->DestroyWindow(); + GetComponent<CApplicationPowerHandling>()->SetRenderGUI(false); + break; +#endif + + case TMSG_START_ANDROID_ACTIVITY: + { +#if defined(TARGET_ANDROID) + if (pMsg->params.size()) + { + CXBMCApp::StartActivity(pMsg->params[0], pMsg->params.size() > 1 ? pMsg->params[1] : "", + pMsg->params.size() > 2 ? pMsg->params[2] : "", + pMsg->params.size() > 3 ? pMsg->params[3] : "", + pMsg->params.size() > 4 ? pMsg->params[4] : "", + pMsg->params.size() > 5 ? pMsg->params[5] : "", + pMsg->params.size() > 6 ? pMsg->params[6] : "", + pMsg->params.size() > 7 ? pMsg->params[7] : "", + pMsg->params.size() > 8 ? pMsg->params[8] : ""); + } +#endif + } + break; + + case TMSG_NETWORKMESSAGE: + m_ServiceManager->GetNetwork().NetworkMessage(static_cast<CNetworkBase::EMESSAGE>(pMsg->param1), + pMsg->param2); + break; + + case TMSG_SETLANGUAGE: + SetLanguage(pMsg->strParam); + break; + + + case TMSG_SWITCHTOFULLSCREEN: + { + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (gui) + gui->GetWindowManager().SwitchToFullScreen(true); + break; + } + case TMSG_VIDEORESIZE: + { + XBMC_Event newEvent = {}; + newEvent.type = XBMC_VIDEORESIZE; + newEvent.resize.w = pMsg->param1; + newEvent.resize.h = pMsg->param2; + OnEvent(newEvent); + CServiceBroker::GetGUI()->GetWindowManager().MarkDirty(); + } + break; + + case TMSG_SETVIDEORESOLUTION: + CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(static_cast<RESOLUTION>(pMsg->param1), pMsg->param2 == 1); + break; + + case TMSG_TOGGLEFULLSCREEN: + CServiceBroker::GetWinSystem()->GetGfxContext().ToggleFullScreen(); + appPlayer->TriggerUpdateResolution(); + break; + + case TMSG_MINIMIZE: + CServiceBroker::GetWinSystem()->Minimize(); + break; + + case TMSG_EXECUTE_OS: + // Suspend AE temporarily so exclusive or hog-mode sinks + // don't block external player's access to audio device + IAE *audioengine; + audioengine = CServiceBroker::GetActiveAE(); + if (audioengine) + { + if (!audioengine->Suspend()) + { + CLog::Log(LOGINFO, "{}: Failed to suspend AudioEngine before launching external program", + __FUNCTION__); + } + } +#if defined(TARGET_DARWIN) + CLog::Log(LOGINFO, "ExecWait is not implemented on this platform"); +#elif defined(TARGET_POSIX) + CUtil::RunCommandLine(pMsg->strParam, (pMsg->param1 == 1)); +#elif defined(TARGET_WINDOWS) + CWIN32Util::XBMCShellExecute(pMsg->strParam.c_str(), (pMsg->param1 == 1)); +#endif + // Resume AE processing of XBMC native audio + if (audioengine) + { + if (!audioengine->Resume()) + { + CLog::Log(LOGFATAL, "{}: Failed to restart AudioEngine after return from external player", + __FUNCTION__); + } + } + break; + + case TMSG_EXECUTE_SCRIPT: + CScriptInvocationManager::GetInstance().ExecuteAsync(pMsg->strParam); + break; + + case TMSG_EXECUTE_BUILT_IN: + CBuiltins::GetInstance().Execute(pMsg->strParam); + break; + + case TMSG_PICTURE_SHOW: + { + CGUIWindowSlideShow *pSlideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (!pSlideShow) return; + + // stop playing file + if (appPlayer->IsPlayingVideo()) + StopPlaying(); + + if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO) + CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow(); + + const auto appPower = GetComponent<CApplicationPowerHandling>(); + appPower->ResetScreenSaver(); + appPower->WakeUpScreenSaverAndDPMS(); + + if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_SLIDESHOW) + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SLIDESHOW); + if (URIUtils::IsZIP(pMsg->strParam) || URIUtils::IsRAR(pMsg->strParam)) // actually a cbz/cbr + { + CFileItemList items; + CURL pathToUrl; + if (URIUtils::IsZIP(pMsg->strParam)) + pathToUrl = URIUtils::CreateArchivePath("zip", CURL(pMsg->strParam), ""); + else + pathToUrl = URIUtils::CreateArchivePath("rar", CURL(pMsg->strParam), ""); + + CUtil::GetRecursiveListing(pathToUrl.Get(), items, CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), XFILE::DIR_FLAG_NO_FILE_DIRS); + if (items.Size() > 0) + { + pSlideShow->Reset(); + for (int i = 0; i<items.Size(); ++i) + { + pSlideShow->Add(items[i].get()); + } + pSlideShow->Select(items[0]->GetPath()); + } + } + else + { + CFileItem item(pMsg->strParam, false); + pSlideShow->Reset(); + pSlideShow->Add(&item); + pSlideShow->Select(pMsg->strParam); + } + } + break; + + case TMSG_PICTURE_SLIDESHOW: + { + CGUIWindowSlideShow *pSlideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (!pSlideShow) return; + + if (appPlayer->IsPlayingVideo()) + StopPlaying(); + + pSlideShow->Reset(); + + CFileItemList items; + std::string strPath = pMsg->strParam; + std::string extensions = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(); + if (pMsg->param1) + extensions += "|.tbn"; + CUtil::GetRecursiveListing(strPath, items, extensions); + + if (items.Size() > 0) + { + for (int i = 0; i<items.Size(); ++i) + pSlideShow->Add(items[i].get()); + pSlideShow->StartSlideShow(); //Start the slideshow! + } + + if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_SLIDESHOW) + { + if (items.Size() == 0) + { + CServiceBroker::GetSettingsComponent()->GetSettings()->SetString(CSettings::SETTING_SCREENSAVER_MODE, "screensaver.xbmc.builtin.dim"); + GetComponent<CApplicationPowerHandling>()->ActivateScreenSaver(); + } + else + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SLIDESHOW); + } + + } + break; + + case TMSG_LOADPROFILE: + { + const int profile = pMsg->param1; + if (profile >= 0) + CServiceBroker::GetSettingsComponent()->GetProfileManager()->LoadProfile(static_cast<unsigned int>(profile)); + } + + break; + + case TMSG_EVENT: + { + if (pMsg->lpVoid) + { + XBMC_Event* event = static_cast<XBMC_Event*>(pMsg->lpVoid); + OnEvent(*event); + delete event; + } + } + break; + + case TMSG_UPDATE_PLAYER_ITEM: + { + std::unique_ptr<CFileItem> item{static_cast<CFileItem*>(pMsg->lpVoid)}; + if (item) + { + m_itemCurrentFile->UpdateInfo(*item); + CServiceBroker::GetGUI()->GetInfoManager().UpdateCurrentItem(*m_itemCurrentFile); + } + } + break; + + default: + CLog::Log(LOGERROR, "{}: Unhandled threadmessage sent, {}", __FUNCTION__, msg); + break; + } +} + +void CApplication::LockFrameMoveGuard() +{ + ++m_WaitingExternalCalls; + m_frameMoveGuard.lock(); + ++m_ProcessedExternalCalls; + CServiceBroker::GetWinSystem()->GetGfxContext().lock(); +}; + +void CApplication::UnlockFrameMoveGuard() +{ + --m_WaitingExternalCalls; + CServiceBroker::GetWinSystem()->GetGfxContext().unlock(); + m_frameMoveGuard.unlock(); +}; + +void CApplication::FrameMove(bool processEvents, bool processGUI) +{ + const auto appPlayer = GetComponent<CApplicationPlayer>(); + bool renderGUI = GetComponent<CApplicationPowerHandling>()->GetRenderGUI(); + if (processEvents) + { + // currently we calculate the repeat time (ie time from last similar keypress) just global as fps + float frameTime = m_frameTime.GetElapsedSeconds(); + m_frameTime.StartZero(); + // never set a frametime less than 2 fps to avoid problems when debugging and on breaks + if (frameTime > 0.5f) + frameTime = 0.5f; + + if (processGUI && renderGUI) + { + std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext()); + // check if there are notifications to display + CGUIDialogKaiToast *toast = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogKaiToast>(WINDOW_DIALOG_KAI_TOAST); + if (toast && toast->DoWork()) + { + if (!toast->IsDialogRunning()) + { + toast->Open(); + } + } + } + + HandlePortEvents(); + CServiceBroker::GetInputManager().Process(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(), frameTime); + + if (processGUI && renderGUI) + { + m_pInertialScrollingHandler->ProcessInertialScroll(frameTime); + appPlayer->GetSeekHandler().FrameMove(); + } + + // Open the door for external calls e.g python exactly here. + // Window size can be between 2 and 10ms and depends on number of continuous requests + if (m_WaitingExternalCalls) + { + CSingleExit ex(CServiceBroker::GetWinSystem()->GetGfxContext()); + m_frameMoveGuard.unlock(); + + // Calculate a window size between 2 and 10ms, 4 continuous requests let the window grow by 1ms + // When not playing video we allow it to increase to 80ms + unsigned int max_sleep = 10; + if (!appPlayer->IsPlayingVideo() || appPlayer->IsPausedPlayback()) + max_sleep = 80; + unsigned int sleepTime = std::max(static_cast<unsigned int>(2), std::min(m_ProcessedExternalCalls >> 2, max_sleep)); + KODI::TIME::Sleep(std::chrono::milliseconds(sleepTime)); + m_frameMoveGuard.lock(); + m_ProcessedExternalDecay = 5; + } + if (m_ProcessedExternalDecay && --m_ProcessedExternalDecay == 0) + m_ProcessedExternalCalls = 0; + } + + if (processGUI && renderGUI) + { + m_skipGuiRender = false; + + /*! @todo look into the possibility to use this for GBM + int fps = 0; + + // This code reduces rendering fps of the GUI layer when playing videos in fullscreen mode + // it makes only sense on architectures with multiple layers + if (CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenVideo() && !m_appPlayer.IsPausedPlayback() && m_appPlayer.IsRenderingVideoLayer()) + fps = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_LIMITGUIUPDATE); + + auto now = std::chrono::steady_clock::now(); + + auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastRenderTime).count(); + if (fps > 0 && frameTime * fps < 1000) + m_skipGuiRender = true; + */ + + if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiSmartRedraw && m_guiRefreshTimer.IsTimePast()) + { + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_REFRESH_TIMER, 0, 0); + m_guiRefreshTimer.Set(500ms); + } + + if (!m_bStop) + { + if (!m_skipGuiRender) + CServiceBroker::GetGUI()->GetWindowManager().Process(CTimeUtils::GetFrameTime()); + } + CServiceBroker::GetGUI()->GetWindowManager().FrameMove(); + } + + appPlayer->FrameMove(); + + // this will go away when render systems gets its own thread + CServiceBroker::GetWinSystem()->DriveRenderLoop(); +} + + +void CApplication::ResetCurrentItem() +{ + m_itemCurrentFile->Reset(); + if (m_pGUI) + m_pGUI->GetInfoManager().ResetCurrentItem(); +} + +int CApplication::Run() +{ + CLog::Log(LOGINFO, "Running the application..."); + + std::chrono::time_point<std::chrono::steady_clock> lastFrameTime; + std::chrono::milliseconds frameTime; + const unsigned int noRenderFrameTime = 15; // Simulates ~66fps + + CFileItemList& playlist = CServiceBroker::GetAppParams()->GetPlaylist(); + if (playlist.Size() > 0) + { + CServiceBroker::GetPlaylistPlayer().Add(PLAYLIST::TYPE_MUSIC, playlist); + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_MUSIC); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_PLAYLISTPLAYER_PLAY, -1); + } + + // Run the app + while (!m_bStop) + { + // Animate and render a frame + + lastFrameTime = std::chrono::steady_clock::now(); + Process(); + + bool renderGUI = GetComponent<CApplicationPowerHandling>()->GetRenderGUI(); + if (!m_bStop) + { + FrameMove(true, renderGUI); + } + + if (renderGUI && !m_bStop) + { + Render(); + } + else if (!renderGUI) + { + auto now = std::chrono::steady_clock::now(); + frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastFrameTime); + if (frameTime.count() < noRenderFrameTime) + KODI::TIME::Sleep(std::chrono::milliseconds(noRenderFrameTime - frameTime.count())); + } + } + + Cleanup(); + + CLog::Log(LOGINFO, "Exiting the application..."); + return m_ExitCode; +} + +bool CApplication::Cleanup() +{ + try + { + ResetCurrentItem(); + StopPlaying(); + + if (m_ServiceManager) + m_ServiceManager->DeinitStageThree(); + + CServiceBroker::UnregisterSpeechRecognition(); + + CLog::Log(LOGINFO, "unload skin"); + GetComponent<CApplicationSkinHandling>()->UnloadSkin(); + + CServiceBroker::UnregisterTextureCache(); + + // stop all remaining scripts; must be done after skin has been unloaded, + // not before some windows still need it when deinitializing during skin + // unloading + CScriptInvocationManager::GetInstance().Uninitialize(); + + const auto appPower = GetComponent<CApplicationPowerHandling>(); + appPower->m_globalScreensaverInhibitor.Release(); + appPower->m_screensaverInhibitor.Release(); + + CRenderSystemBase *renderSystem = CServiceBroker::GetRenderSystem(); + if (renderSystem) + renderSystem->DestroyRenderSystem(); + + CWinSystemBase *winSystem = CServiceBroker::GetWinSystem(); + if (winSystem) + winSystem->DestroyWindow(); + + if (m_pGUI) + m_pGUI->GetWindowManager().DestroyWindows(); + + CLog::Log(LOGINFO, "unload sections"); + + // Shutdown as much as possible of the + // application, to reduce the leaks dumped + // to the vc output window before calling + // _CrtDumpMemoryLeaks(). Most of the leaks + // shown are no real leaks, as parts of the app + // are still allocated. + + g_localizeStrings.Clear(); + g_LangCodeExpander.Clear(); + g_charsetConverter.clear(); + g_directoryCache.Clear(); + //CServiceBroker::GetInputManager().ClearKeymaps(); //! @todo + CEventServer::RemoveInstance(); + DllLoaderContainer::Clear(); + CServiceBroker::GetPlaylistPlayer().Clear(); + + if (m_ServiceManager) + m_ServiceManager->DeinitStageTwo(); + +#ifdef TARGET_POSIX + CXHandle::DumpObjectTracker(); + +#ifdef HAS_DVD_DRIVE + CLibcdio::ReleaseInstance(); +#endif +#endif +#ifdef _CRTDBG_MAP_ALLOC + _CrtDumpMemoryLeaks(); + while(1); // execution ends +#endif + + if (m_pGUI) + { + m_pGUI->Deinit(); + m_pGUI.reset(); + } + + if (winSystem) + { + winSystem->DestroyWindowSystem(); + CServiceBroker::UnregisterWinSystem(); + winSystem = nullptr; + m_pWinSystem.reset(); + } + + // Cleanup was called more than once on exit during my tests + if (m_ServiceManager) + { + m_ServiceManager->DeinitStageOne(); + m_ServiceManager.reset(); + } + + CServiceBroker::UnregisterKeyboardLayoutManager(); + + CServiceBroker::UnregisterAppMessenger(); + + CServiceBroker::UnregisterAnnouncementManager(); + m_pAnnouncementManager->Deinitialize(); + m_pAnnouncementManager.reset(); + + CServiceBroker::UnregisterJobManager(); + CServiceBroker::UnregisterCPUInfo(); + + UnregisterSettings(); + + m_bInitializing = true; + + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "Exception in CApplication::Cleanup()"); + return false; + } +} + +bool CApplication::Stop(int exitCode) +{ +#if defined(TARGET_ANDROID) + // Note: On Android, the app must be stopped asynchronously, once Android has + // signalled that the app shall be destroyed. See android_main() implementation. + if (!CXBMCApp::Get().Stop(exitCode)) + return false; +#endif + + CLog::Log(LOGINFO, "Stopping the application..."); + + bool success = true; + + CLog::Log(LOGINFO, "Stopping player"); + const auto appPlayer = GetComponent<CApplicationPlayer>(); + appPlayer->ClosePlayer(); + + { + // close inbound port + CServiceBroker::UnregisterAppPort(); + XbmcThreads::EndTime<> timer(1000ms); + while (m_pAppPort.use_count() > 1) + { + KODI::TIME::Sleep(100ms); + if (timer.IsTimePast()) + { + CLog::Log(LOGERROR, "CApplication::Stop - CAppPort still in use, app may crash"); + break; + } + } + m_pAppPort.reset(); + } + + try + { + m_frameMoveGuard.unlock(); + + CVariant vExitCode(CVariant::VariantTypeObject); + vExitCode["exitcode"] = exitCode; + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::System, "OnQuit", vExitCode); + + // Abort any active screensaver + GetComponent<CApplicationPowerHandling>()->WakeUpScreenSaverAndDPMS(); + + g_alarmClock.StopThread(); + + CLog::Log(LOGINFO, "Storing total System Uptime"); + g_sysinfo.SetTotalUptime(g_sysinfo.GetTotalUptime() + (int)(CTimeUtils::GetFrameTime() / 60000)); + + // Update the settings information (volume, uptime etc. need saving) + if (CFile::Exists(CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetSettingsFile())) + { + CLog::Log(LOGINFO, "Saving settings"); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + } + else + CLog::Log(LOGINFO, "Not saving settings (settings.xml is not present)"); + + // kodi may crash or deadlock during exit (shutdown / reboot) due to + // either a bug in core or misbehaving addons. so try saving + // skin settings early + CLog::Log(LOGINFO, "Saving skin settings"); + if (g_SkinInfo != nullptr) + g_SkinInfo->SaveSettings(); + + m_bStop = true; + // Add this here to keep the same ordering behaviour for now + // Needs cleaning up + CServiceBroker::GetAppMessenger()->Stop(); + m_AppFocused = false; + m_ExitCode = exitCode; + CLog::Log(LOGINFO, "Stopping all"); + + // cancel any jobs from the jobmanager + CServiceBroker::GetJobManager()->CancelJobs(); + + // stop scanning before we kill the network and so on + if (CMusicLibraryQueue::GetInstance().IsRunning()) + CMusicLibraryQueue::GetInstance().CancelAllJobs(); + + if (CVideoLibraryQueue::GetInstance().IsRunning()) + CVideoLibraryQueue::GetInstance().CancelAllJobs(); + + CServiceBroker::GetAppMessenger()->Cleanup(); + + m_ServiceManager->GetNetwork().NetworkMessage(CNetworkBase::SERVICES_DOWN, 0); + +#ifdef HAS_ZEROCONF + if(CZeroconfBrowser::IsInstantiated()) + { + CLog::Log(LOGINFO, "Stopping zeroconf browser"); + CZeroconfBrowser::GetInstance()->Stop(); + CZeroconfBrowser::ReleaseInstance(); + } +#endif + + for (const auto& vfsAddon : CServiceBroker::GetVFSAddonCache().GetAddonInstances()) + vfsAddon->DisconnectAll(); + +#if defined(TARGET_POSIX) && defined(HAS_FILESYSTEM_SMB) + smb.Deinit(); +#endif + +#if defined(TARGET_DARWIN_OSX) + if (XBMCHelper::GetInstance().IsAlwaysOn() == false) + XBMCHelper::GetInstance().Stop(); +#endif + + // Stop services before unloading Python + CServiceBroker::GetServiceAddons().Stop(); + + // Stop any other python scripts that may be looping waiting for monitor.abortRequested() + CScriptInvocationManager::GetInstance().StopRunningScripts(); + + // unregister action listeners + const auto appListener = GetComponent<CApplicationActionListeners>(); + appListener->UnregisterActionListener(&GetComponent<CApplicationPlayer>()->GetSeekHandler()); + appListener->UnregisterActionListener(&CPlayerController::GetInstance()); + + CGUIComponent *gui = CServiceBroker::GetGUI(); + if (gui) + gui->GetAudioManager().DeInitialize(); + + // shutdown the AudioEngine + CServiceBroker::UnregisterAE(); + m_pActiveAE->Shutdown(); + m_pActiveAE.reset(); + + CLog::Log(LOGINFO, "Application stopped"); + } + catch (...) + { + CLog::Log(LOGERROR, "Exception in CApplication::Stop()"); + success = false; + } + + cleanup_emu_environ(); + + KODI::TIME::Sleep(200ms); + + return success; +} + +namespace +{ +class CCreateAndLoadPlayList : public IRunnable +{ +public: + CCreateAndLoadPlayList(CFileItem& item, std::unique_ptr<PLAYLIST::CPlayList>& playlist) + : m_item(item), m_playlist(playlist) + { + } + + void Run() override + { + const std::unique_ptr<PLAYLIST::CPlayList> playlist(PLAYLIST::CPlayListFactory::Create(m_item)); + if (playlist) + { + if (playlist->Load(m_item.GetPath())) + *m_playlist = *playlist; + } + } + +private: + CFileItem& m_item; + std::unique_ptr<PLAYLIST::CPlayList>& m_playlist; +}; +} // namespace + +bool CApplication::PlayMedia(CFileItem& item, const std::string& player, PLAYLIST::Id playlistId) +{ + // if the item is a plugin we need to resolve the plugin paths + if (URIUtils::HasPluginPath(item) && !XFILE::CPluginDirectory::GetResolvedPluginResult(item)) + return false; + + if (item.IsSmartPlayList()) + { + CFileItemList items; + CUtil::GetRecursiveListing(item.GetPath(), items, "", DIR_FLAG_NO_FILE_DIRS); + if (items.Size()) + { + CSmartPlaylist smartpl; + //get name and type of smartplaylist, this will always succeed as GetDirectory also did this. + smartpl.OpenAndReadName(item.GetURL()); + PLAYLIST::CPlayList playlist; + playlist.Add(items); + PLAYLIST::Id smartplPlaylistId = PLAYLIST::TYPE_VIDEO; + + if (smartpl.GetType() == "songs" || smartpl.GetType() == "albums" || + smartpl.GetType() == "artists") + smartplPlaylistId = PLAYLIST::TYPE_MUSIC; + + return ProcessAndStartPlaylist(smartpl.GetName(), playlist, smartplPlaylistId); + } + } + else if (item.IsPlayList() || item.IsInternetStream()) + { + // Not owner. Dialog auto-deletes itself. + CGUIDialogCache* dlgCache = + new CGUIDialogCache(5s, g_localizeStrings.Get(10214), item.GetLabel()); + + //is or could be a playlist + std::unique_ptr<PLAYLIST::CPlayList> playlist; + CCreateAndLoadPlayList getPlaylist(item, playlist); + bool cancelled = !CGUIDialogBusy::Wait(&getPlaylist, 100, true); + + if (dlgCache) + { + dlgCache->Close(); + if (dlgCache->IsCanceled()) + cancelled = true; + } + + if (cancelled) + return true; + + if (playlist) + { + + if (playlistId != PLAYLIST::TYPE_NONE) + { + int track=0; + if (item.HasProperty("playlist_starting_track")) + track = (int)item.GetProperty("playlist_starting_track").asInteger(); + return ProcessAndStartPlaylist(item.GetPath(), *playlist, playlistId, track); + } + else + { + CLog::Log(LOGWARNING, + "CApplication::PlayMedia called to play a playlist {} but no idea which playlist " + "to use, playing first item", + item.GetPath()); + if (playlist->size()) + return PlayFile(*(*playlist)[0], "", false); + } + } + } + else if (item.IsPVR()) + { + return CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayMedia(item); + } + + CURL path(item.GetPath()); + if (path.GetProtocol() == "game") + { + AddonPtr addon; + if (CServiceBroker::GetAddonMgr().GetAddon(path.GetHostName(), addon, AddonType::GAMEDLL, + OnlyEnabled::CHOICE_YES)) + { + CFileItem addonItem(addon); + return PlayFile(addonItem, player, false); + } + } + + //nothing special just play + return PlayFile(item, player, false); +} + +// PlayStack() +// For playing a multi-file video. Particularly inefficient +// on startup, as we are required to calculate the length +// of each video, so we open + close each one in turn. +// A faster calculation of video time would improve this +// substantially. +// return value: same with PlayFile() +bool CApplication::PlayStack(CFileItem& item, bool bRestart) +{ + const auto stackHelper = GetComponent<CApplicationStackHelper>(); + if (!stackHelper->InitializeStack(item)) + return false; + + int startoffset = stackHelper->InitializeStackStartPartAndOffset(item); + + CFileItem selectedStackPart = stackHelper->GetCurrentStackPartFileItem(); + selectedStackPart.SetStartOffset(startoffset); + + if (item.HasProperty("savedplayerstate")) + { + selectedStackPart.SetProperty("savedplayerstate", item.GetProperty("savedplayerstate")); // pass on to part + item.ClearProperty("savedplayerstate"); + } + + return PlayFile(selectedStackPart, "", true); +} + +bool CApplication::PlayFile(CFileItem item, const std::string& player, bool bRestart) +{ + // Ensure the MIME type has been retrieved for http:// and shout:// streams + if (item.GetMimeType().empty()) + item.FillInMimeType(); + + const auto appPlayer = GetComponent<CApplicationPlayer>(); + const auto stackHelper = GetComponent<CApplicationStackHelper>(); + + if (!bRestart) + { + // bRestart will be true when called from PlayStack(), skipping this block + appPlayer->SetPlaySpeed(1); + + m_nextPlaylistItem = -1; + stackHelper->Clear(); + + if (item.IsVideo()) + CUtil::ClearSubtitles(); + } + + if (item.IsDiscStub()) + { + return CServiceBroker::GetMediaManager().playStubFile(item); + } + + if (item.IsPlayList()) + return false; + + // if the item is a plugin we need to resolve the plugin paths + if (URIUtils::HasPluginPath(item) && !XFILE::CPluginDirectory::GetResolvedPluginResult(item)) + return false; + +#ifdef HAS_UPNP + if (URIUtils::IsUPnP(item.GetPath())) + { + if (!XFILE::CUPnPDirectory::GetResource(item.GetURL(), item)) + return false; + } +#endif + + // if we have a stacked set of files, we need to setup our stack routines for + // "seamless" seeking and total time of the movie etc. + // will recall with restart set to true + if (item.IsStack()) + return PlayStack(item, bRestart); + + CPlayerOptions options; + + if (item.HasProperty("StartPercent")) + { + options.startpercent = item.GetProperty("StartPercent").asDouble(); + item.SetStartOffset(0); + } + + options.starttime = CUtil::ConvertMilliSecsToSecs(item.GetStartOffset()); + + if (bRestart) + { + // have to be set here due to playstack using this for starting the file + if (item.HasVideoInfoTag()) + options.state = item.GetVideoInfoTag()->GetResumePoint().playerState; + } + if (!bRestart || stackHelper->IsPlayingISOStack()) + { + // the following code block is only applicable when bRestart is false OR to ISO stacks + + if (item.IsVideo()) + { + // open the d/b and retrieve the bookmarks for the current movie + CVideoDatabase dbs; + dbs.Open(); + + std::string path = item.GetPath(); + std::string videoInfoTagPath(item.GetVideoInfoTag()->m_strFileNameAndPath); + if (videoInfoTagPath.find("removable://") == 0 || item.IsVideoDb()) + path = videoInfoTagPath; + dbs.LoadVideoInfo(path, *item.GetVideoInfoTag()); + + if (item.HasProperty("savedplayerstate")) + { + options.starttime = CUtil::ConvertMilliSecsToSecs(item.GetStartOffset()); + options.state = item.GetProperty("savedplayerstate").asString(); + item.ClearProperty("savedplayerstate"); + } + else if (item.GetStartOffset() == STARTOFFSET_RESUME) + { + options.starttime = 0.0; + if (item.IsResumePointSet()) + { + options.starttime = item.GetCurrentResumeTime(); + if (item.HasVideoInfoTag()) + options.state = item.GetVideoInfoTag()->GetResumePoint().playerState; + } + else + { + CBookmark bookmark; + std::string path = item.GetPath(); + if (item.HasVideoInfoTag() && StringUtils::StartsWith(item.GetVideoInfoTag()->m_strFileNameAndPath, "removable://")) + path = item.GetVideoInfoTag()->m_strFileNameAndPath; + else if (item.HasProperty("original_listitem_url") && URIUtils::IsPlugin(item.GetProperty("original_listitem_url").asString())) + path = item.GetProperty("original_listitem_url").asString(); + if (dbs.GetResumeBookMark(path, bookmark)) + { + options.starttime = bookmark.timeInSeconds; + options.state = bookmark.playerState; + } + } + + if (options.starttime == 0.0 && item.HasVideoInfoTag()) + { + // No resume point is set, but check if this item is part of a multi-episode file + const CVideoInfoTag *tag = item.GetVideoInfoTag(); + + if (tag->m_iBookmarkId > 0) + { + CBookmark bookmark; + dbs.GetBookMarkForEpisode(*tag, bookmark); + options.starttime = bookmark.timeInSeconds; + options.state = bookmark.playerState; + } + } + } + else if (item.HasVideoInfoTag()) + { + const CVideoInfoTag *tag = item.GetVideoInfoTag(); + + if (tag->m_iBookmarkId > 0) + { + CBookmark bookmark; + dbs.GetBookMarkForEpisode(*tag, bookmark); + options.starttime = bookmark.timeInSeconds; + options.state = bookmark.playerState; + } + } + + dbs.Close(); + } + } + + // a disc image might be Blu-Ray disc + if (!(options.startpercent > 0.0 || options.starttime > 0.0) && + (item.IsBDFile() || item.IsDiscImage())) + { + //check if we must show the simplified bd menu + if (!CGUIDialogSimpleMenu::ShowPlaySelection(item)) + return true; + } + + // this really aught to be inside !bRestart, but since PlayStack + // uses that to init playback, we have to keep it outside + const PLAYLIST::Id playlistId = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(); + if (item.IsAudio() && playlistId == PLAYLIST::TYPE_MUSIC) + { // playing from a playlist by the looks + // don't switch to fullscreen if we are not playing the first item... + options.fullscreen = !CServiceBroker::GetPlaylistPlayer().HasPlayedFirstFile() && + CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_MUSICFILES_SELECTACTION) && + !CMediaSettings::GetInstance().DoesMediaStartWindowed(); + } + else if (item.IsVideo() && playlistId == PLAYLIST::TYPE_VIDEO && + CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).size() > 1) + { // playing from a playlist by the looks + // don't switch to fullscreen if we are not playing the first item... + options.fullscreen = !CServiceBroker::GetPlaylistPlayer().HasPlayedFirstFile() && + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreenOnMovieStart && + !CMediaSettings::GetInstance().DoesMediaStartWindowed(); + } + else if (stackHelper->IsPlayingRegularStack()) + { + //! @todo - this will fail if user seeks back to first file in stack + if (stackHelper->GetCurrentPartNumber() == 0 || + stackHelper->GetRegisteredStack(item)->GetStartOffset() != 0) + options.fullscreen = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()-> + m_fullScreenOnMovieStart && !CMediaSettings::GetInstance().DoesMediaStartWindowed(); + else + options.fullscreen = false; + } + else + options.fullscreen = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()-> + m_fullScreenOnMovieStart && !CMediaSettings::GetInstance().DoesMediaStartWindowed(); + + // stereo streams may have lower quality, i.e. 32bit vs 16 bit + options.preferStereo = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoPreferStereoStream && + CServiceBroker::GetActiveAE()->HasStereoAudioChannelCount(); + + // reset VideoStartWindowed as it's a temp setting + CMediaSettings::GetInstance().SetMediaStartWindowed(false); + + { + // for playing a new item, previous playing item's callback may already + // pushed some delay message into the threadmessage list, they are not + // expected be processed after or during the new item playback starting. + // so we clean up previous playing item's playback callback delay messages here. + int previousMsgsIgnoredByNewPlaying[] = { + GUI_MSG_PLAYBACK_STARTED, + GUI_MSG_PLAYBACK_ENDED, + GUI_MSG_PLAYBACK_STOPPED, + GUI_MSG_PLAYLIST_CHANGED, + GUI_MSG_PLAYLISTPLAYER_STOPPED, + GUI_MSG_PLAYLISTPLAYER_STARTED, + GUI_MSG_PLAYLISTPLAYER_CHANGED, + GUI_MSG_QUEUE_NEXT_ITEM, + 0 + }; + int dMsgCount = CServiceBroker::GetGUI()->GetWindowManager().RemoveThreadMessageByMessageIds(&previousMsgsIgnoredByNewPlaying[0]); + if (dMsgCount > 0) + CLog::LogF(LOGDEBUG, "Ignored {} playback thread messages", dMsgCount); + } + + const auto appVolume = GetComponent<CApplicationVolumeHandling>(); + appPlayer->OpenFile(item, options, m_ServiceManager->GetPlayerCoreFactory(), player, *this); + appPlayer->SetVolume(appVolume->GetVolumeRatio()); + appPlayer->SetMute(appVolume->IsMuted()); + +#if !defined(TARGET_POSIX) + CGUIComponent *gui = CServiceBroker::GetGUI(); + if (gui) + gui->GetAudioManager().Enable(false); +#endif + + if (item.HasPVRChannelInfoTag()) + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_NONE); + + return true; +} + +void CApplication::PlaybackCleanup() +{ + const auto appPlayer = GetComponent<CApplicationPlayer>(); + const auto stackHelper = GetComponent<CApplicationStackHelper>(); + + if (!appPlayer->IsPlaying()) + { + CGUIComponent *gui = CServiceBroker::GetGUI(); + if (gui) + CServiceBroker::GetGUI()->GetAudioManager().Enable(true); + appPlayer->OpenNext(m_ServiceManager->GetPlayerCoreFactory()); + } + + if (!appPlayer->IsPlayingVideo()) + { + if(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO || + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_GAME) + { + CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow(); + } + else + { + // resets to res_desktop or look&feel resolution (including refreshrate) + CServiceBroker::GetWinSystem()->GetGfxContext().SetFullScreenVideo(false); + } +#ifdef TARGET_DARWIN_EMBEDDED + CDarwinUtils::SetScheduling(false); +#endif + } + + const auto appPower = GetComponent<CApplicationPowerHandling>(); + + if (!appPlayer->IsPlayingAudio() && + CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_NONE && + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION) + { + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); // save vis settings + appPower->WakeUpScreenSaverAndDPMS(); + CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow(); + } + + // DVD ejected while playing in vis ? + if (!appPlayer->IsPlayingAudio() && + (m_itemCurrentFile->IsCDDA() || m_itemCurrentFile->IsOnDVD()) && + !CServiceBroker::GetMediaManager().IsDiscInDrive() && + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION) + { + // yes, disable vis + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); // save vis settings + appPower->WakeUpScreenSaverAndDPMS(); + CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow(); + } + + if (!appPlayer->IsPlaying()) + { + stackHelper->Clear(); + appPlayer->ResetPlayer(); + } + + if (CServiceBroker::GetAppParams()->IsTestMode()) + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT); +} + +bool CApplication::IsPlayingFullScreenVideo() const +{ + const auto appPlayer = GetComponent<CApplicationPlayer>(); + return appPlayer->IsPlayingVideo() && + CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenVideo(); +} + +bool CApplication::IsFullScreen() +{ + return IsPlayingFullScreenVideo() || + (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION) || + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SLIDESHOW; +} + +void CApplication::StopPlaying() +{ + CGUIComponent *gui = CServiceBroker::GetGUI(); + + if (gui) + { + int iWin = gui->GetWindowManager().GetActiveWindow(); + const auto appPlayer = GetComponent<CApplicationPlayer>(); + if (appPlayer->IsPlaying()) + { + appPlayer->ClosePlayer(); + + // turn off visualisation window when stopping + if ((iWin == WINDOW_VISUALISATION || + iWin == WINDOW_FULLSCREEN_VIDEO || + iWin == WINDOW_FULLSCREEN_GAME) && + !m_bStop) + gui->GetWindowManager().PreviousWindow(); + + g_partyModeManager.Disable(); + } + } +} + +bool CApplication::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_NOTIFY_ALL: + { + if (message.GetParam1()==GUI_MSG_REMOVED_MEDIA) + { + // Update general playlist: Remove DVD playlist items + int nRemoved = CServiceBroker::GetPlaylistPlayer().RemoveDVDItems(); + if ( nRemoved > 0 ) + { + CGUIMessage msg( GUI_MSG_PLAYLIST_CHANGED, 0, 0 ); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage( msg ); + } + // stop the file if it's on dvd (will set the resume point etc) + if (m_itemCurrentFile->IsOnDVD()) + StopPlaying(); + } + else if (message.GetParam1() == GUI_MSG_UI_READY) + { + // remove splash window + CServiceBroker::GetGUI()->GetWindowManager().Delete(WINDOW_SPLASH); + + // show the volumebar if the volume is muted + const auto appVolume = GetComponent<CApplicationVolumeHandling>(); + if (appVolume->IsMuted() || + appVolume->GetVolumeRatio() <= CApplicationVolumeHandling::VOLUME_MINIMUM) + appVolume->ShowVolumeBar(); + + if (!m_incompatibleAddons.empty()) + { + // filter addons that are not dependencies + std::vector<std::string> disabledAddonNames; + for (const auto& addoninfo : m_incompatibleAddons) + { + if (!CAddonType::IsDependencyType(addoninfo->MainType())) + disabledAddonNames.emplace_back(addoninfo->Name()); + } + + // migration (incompatible addons) dialog + auto addonList = StringUtils::Join(disabledAddonNames, ", "); + auto msg = StringUtils::Format(g_localizeStrings.Get(24149), addonList); + HELPERS::ShowOKDialogText(CVariant{24148}, CVariant{std::move(msg)}); + m_incompatibleAddons.clear(); + } + + // show info dialog about moved configuration files if needed + ShowAppMigrationMessage(); + + // offer enabling addons at kodi startup that are disabled due to + // e.g. os package manager installation on linux + ConfigureAndEnableAddons(); + + m_bInitializing = false; + + if (message.GetSenderId() == WINDOW_SETTINGS_PROFILES) + GetComponent<CApplicationSkinHandling>()->ReloadSkin(false); + } + else if (message.GetParam1() == GUI_MSG_UPDATE_ITEM && message.GetItem()) + { + CFileItemPtr item = std::static_pointer_cast<CFileItem>(message.GetItem()); + if (m_itemCurrentFile->IsSamePath(item.get())) + { + m_itemCurrentFile->UpdateInfo(*item); + CServiceBroker::GetGUI()->GetInfoManager().UpdateCurrentItem(*item); + } + } + } + break; + + case GUI_MSG_PLAYBACK_STARTED: + { +#ifdef TARGET_DARWIN_EMBEDDED + // @TODO move this away to platform code + CDarwinUtils::SetScheduling(GetComponent<CApplicationPlayer>()->IsPlayingVideo()); +#endif + PLAYLIST::CPlayList playList = CServiceBroker::GetPlaylistPlayer().GetPlaylist( + CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist()); + + // Update our infoManager with the new details etc. + if (m_nextPlaylistItem >= 0) + { + // playing an item which is not in the list - player might be stopped already + // so do nothing + if (playList.size() <= m_nextPlaylistItem) + return true; + + // we've started a previously queued item + CFileItemPtr item = playList[m_nextPlaylistItem]; + // update the playlist manager + int currentSong = CServiceBroker::GetPlaylistPlayer().GetCurrentSong(); + int param = ((currentSong & 0xffff) << 16) | (m_nextPlaylistItem & 0xffff); + CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_CHANGED, 0, 0, CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(), param, item); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + CServiceBroker::GetPlaylistPlayer().SetCurrentSong(m_nextPlaylistItem); + m_itemCurrentFile.reset(new CFileItem(*item)); + } + CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile); + g_partyModeManager.OnSongChange(true); + +#ifdef HAS_PYTHON + // informs python script currently running playback has started + // (does nothing if python is not loaded) + CServiceBroker::GetXBPython().OnPlayBackStarted(*m_itemCurrentFile); +#endif + + CVariant param; + param["player"]["speed"] = 1; + param["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(); + + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPlay", + m_itemCurrentFile, param); + + // we don't want a busy dialog when switching channels + const auto appPlayer = GetComponent<CApplicationPlayer>(); + if (!m_itemCurrentFile->IsLiveTV() || + (!appPlayer->IsPlayingVideo() && !appPlayer->IsPlayingAudio())) + { + CGUIDialogBusy* dialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogBusy>( + WINDOW_DIALOG_BUSY); + if (dialog && !dialog->IsDialogRunning()) + dialog->WaitOnEvent(m_playerEvent); + } + + return true; + } + break; + + case GUI_MSG_QUEUE_NEXT_ITEM: + { + // Check to see if our playlist player has a new item for us, + // and if so, we check whether our current player wants the file + int iNext = CServiceBroker::GetPlaylistPlayer().GetNextSong(); + PLAYLIST::CPlayList& playlist = CServiceBroker::GetPlaylistPlayer().GetPlaylist( + CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist()); + if (iNext < 0 || iNext >= playlist.size()) + { + GetComponent<CApplicationPlayer>()->OnNothingToQueueNotify(); + return true; // nothing to do + } + + // ok, grab the next song + CFileItem file(*playlist[iNext]); + // handle plugin:// + CURL url(file.GetDynPath()); + if (url.IsProtocol("plugin")) + XFILE::CPluginDirectory::GetPluginResult(url.Get(), file, false); + + // Don't queue if next media type is different from current one + bool bNothingToQueue = false; + + const auto appPlayer = GetComponent<CApplicationPlayer>(); + if (!file.IsVideo() && appPlayer->IsPlayingVideo()) + bNothingToQueue = true; + else if ((!file.IsAudio() || file.IsVideo()) && appPlayer->IsPlayingAudio()) + bNothingToQueue = true; + + if (bNothingToQueue) + { + appPlayer->OnNothingToQueueNotify(); + return true; + } + +#ifdef HAS_UPNP + if (URIUtils::IsUPnP(file.GetDynPath())) + { + if (!XFILE::CUPnPDirectory::GetResource(file.GetDynURL(), file)) + return true; + } +#endif + + // ok - send the file to the player, if it accepts it + if (appPlayer->QueueNextFile(file)) + { + // player accepted the next file + m_nextPlaylistItem = iNext; + } + else + { + /* Player didn't accept next file: *ALWAYS* advance playlist in this case so the player can + queue the next (if it wants to) and it doesn't keep looping on this song */ + CServiceBroker::GetPlaylistPlayer().SetCurrentSong(iNext); + } + + return true; + } + break; + + case GUI_MSG_PLAY_TRAILER: + { + const CFileItem* item = dynamic_cast<CFileItem*>(message.GetItem().get()); + if (item == nullptr) + { + CLog::LogF(LOGERROR, "Supplied item is not a CFileItem! Trailer cannot be played."); + return false; + } + + std::unique_ptr<CFileItem> trailerItem = + ContentUtils::GeneratePlayableTrailerItem(*item, g_localizeStrings.Get(20410)); + + if (item->IsPlayList()) + { + std::unique_ptr<CFileItemList> fileitemList = std::make_unique<CFileItemList>(); + fileitemList->Add(std::move(trailerItem)); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, -1, -1, + static_cast<void*>(fileitemList.release())); + } + else + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, 1, 0, + static_cast<void*>(trailerItem.release())); + } + break; + } + + case GUI_MSG_PLAYBACK_STOPPED: + m_playerEvent.Set(); + ResetCurrentItem(); + PlaybackCleanup(); +#ifdef HAS_PYTHON + CServiceBroker::GetXBPython().OnPlayBackStopped(); +#endif + return true; + + case GUI_MSG_PLAYBACK_ENDED: + { + m_playerEvent.Set(); + const auto stackHelper = GetComponent<CApplicationStackHelper>(); + if (stackHelper->IsPlayingRegularStack() && stackHelper->HasNextStackPartFileItem()) + { // just play the next item in the stack + PlayFile(stackHelper->SetNextStackPartCurrentFileItem(), "", true); + return true; + } + ResetCurrentItem(); + if (!CServiceBroker::GetPlaylistPlayer().PlayNext(1, true)) + GetComponent<CApplicationPlayer>()->ClosePlayer(); + + PlaybackCleanup(); + +#ifdef HAS_PYTHON + CServiceBroker::GetXBPython().OnPlayBackEnded(); +#endif + return true; + } + + case GUI_MSG_PLAYLISTPLAYER_STOPPED: + ResetCurrentItem(); + if (GetComponent<CApplicationPlayer>()->IsPlaying()) + StopPlaying(); + PlaybackCleanup(); + return true; + + case GUI_MSG_PLAYBACK_AVSTARTED: + m_playerEvent.Set(); +#ifdef HAS_PYTHON + // informs python script currently running playback has started + // (does nothing if python is not loaded) + CServiceBroker::GetXBPython().OnAVStarted(*m_itemCurrentFile); +#endif + return true; + + case GUI_MSG_PLAYBACK_AVCHANGE: +#ifdef HAS_PYTHON + // informs python script currently running playback has started + // (does nothing if python is not loaded) + CServiceBroker::GetXBPython().OnAVChange(); +#endif + return true; + + case GUI_MSG_PLAYBACK_ERROR: + HELPERS::ShowOKDialogText(CVariant{16026}, CVariant{16027}); + return true; + + case GUI_MSG_PLAYLISTPLAYER_STARTED: + case GUI_MSG_PLAYLISTPLAYER_CHANGED: + { + return true; + } + break; + case GUI_MSG_FULLSCREEN: + { // Switch to fullscreen, if we can + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (gui) + gui->GetWindowManager().SwitchToFullScreen(); + + return true; + } + break; + case GUI_MSG_EXECUTE: + if (message.GetNumStringParams()) + return ExecuteXBMCAction(message.GetStringParam(), message.GetItem()); + break; + } + return false; +} + +bool CApplication::ExecuteXBMCAction(std::string actionStr, const CGUIListItemPtr &item /* = NULL */) +{ + // see if it is a user set string + + //We don't know if there is unsecure information in this yet, so we + //postpone any logging + const std::string in_actionStr(actionStr); + if (item) + actionStr = GUILIB::GUIINFO::CGUIInfoLabel::GetItemLabel(actionStr, item.get()); + else + actionStr = GUILIB::GUIINFO::CGUIInfoLabel::GetLabel(actionStr, INFO::DEFAULT_CONTEXT); + + // user has asked for something to be executed + if (CBuiltins::GetInstance().HasCommand(actionStr)) + { + if (!CBuiltins::GetInstance().IsSystemPowerdownCommand(actionStr) || + CServiceBroker::GetPVRManager().Get<PVR::GUI::PowerManagement>().CanSystemPowerdown()) + CBuiltins::GetInstance().Execute(actionStr); + } + else + { + // try translating the action from our ButtonTranslator + unsigned int actionID; + if (CActionTranslator::TranslateString(actionStr, actionID)) + { + OnAction(CAction(actionID)); + return true; + } + CFileItem item(actionStr, false); +#ifdef HAS_PYTHON + if (item.IsPythonScript()) + { // a python script + CScriptInvocationManager::GetInstance().ExecuteAsync(item.GetPath()); + } + else +#endif + if (item.IsAudio() || item.IsVideo() || item.IsGame()) + { // an audio or video file + PlayFile(item, ""); + } + else + { + //At this point we have given up to translate, so even though + //there may be insecure information, we log it. + CLog::LogF(LOGDEBUG, "Tried translating, but failed to understand {}", in_actionStr); + return false; + } + } + return true; +} + +// inform the user that the configuration data has moved from old XBMC location +// to new Kodi location - if applicable +void CApplication::ShowAppMigrationMessage() +{ + // .kodi_migration_complete will be created from the installer/packaging + // once an old XBMC configuration was moved to the new Kodi location + // if this is the case show the migration info to the user once which + // tells him to have a look into the wiki where the move of configuration + // is further explained. + if (CFile::Exists("special://home/.kodi_data_was_migrated") && + !CFile::Exists("special://home/.kodi_migration_info_shown")) + { + HELPERS::ShowOKDialogText(CVariant{24128}, CVariant{24129}); + CFile tmpFile; + // create the file which will prevent this dialog from appearing in the future + tmpFile.OpenForWrite("special://home/.kodi_migration_info_shown"); + tmpFile.Close(); + } +} + +void CApplication::ConfigureAndEnableAddons() +{ + std::vector<std::shared_ptr<IAddon>> + disabledAddons; /*!< Installed addons, but not auto-enabled via manifest */ + + auto& addonMgr = CServiceBroker::GetAddonMgr(); + + if (addonMgr.GetDisabledAddons(disabledAddons) && !disabledAddons.empty()) + { + // this applies to certain platforms only: + // look at disabled addons with disabledReason == NONE, usually those are installed via package managers or manually. + // also try to enable add-ons with disabledReason == INCOMPATIBLE at startup for all platforms. + + bool isConfigureAddonsAtStartupEnabled = + m_ServiceManager->GetPlatform().IsConfigureAddonsAtStartupEnabled(); + + for (const auto& addon : disabledAddons) + { + if (addonMgr.IsAddonDisabledWithReason(addon->ID(), ADDON::AddonDisabledReason::INCOMPATIBLE)) + { + auto addonInfo = addonMgr.GetAddonInfo(addon->ID(), AddonType::UNKNOWN); + if (addonInfo && addonMgr.IsCompatible(addonInfo)) + { + CLog::Log(LOGDEBUG, "CApplication::{}: enabling the compatible version of [{}].", + __FUNCTION__, addon->ID()); + addonMgr.EnableAddon(addon->ID()); + } + continue; + } + + if (addonMgr.IsAddonDisabledExcept(addon->ID(), ADDON::AddonDisabledReason::NONE) || + CAddonType::IsDependencyType(addon->MainType())) + { + continue; + } + + if (isConfigureAddonsAtStartupEnabled) + { + if (HELPERS::ShowYesNoDialogLines(CVariant{24039}, // Disabled add-ons + CVariant{24059}, // Would you like to enable this add-on? + CVariant{addon->Name()}) == DialogResponse::CHOICE_YES) + { + if (addon->CanHaveAddonOrInstanceSettings()) + { + if (CGUIDialogAddonSettings::ShowForAddon(addon)) + { + // only enable if settings dialog hasn't been cancelled + addonMgr.EnableAddon(addon->ID()); + } + } + else + { + addonMgr.EnableAddon(addon->ID()); + } + } + else + { + // user chose not to configure/enable so we're not asking anymore + addonMgr.UpdateDisabledReason(addon->ID(), ADDON::AddonDisabledReason::USER); + } + } + } + } +} + +void CApplication::Process() +{ + // dispatch the messages generated by python or other threads to the current window + CServiceBroker::GetGUI()->GetWindowManager().DispatchThreadMessages(); + + // process messages which have to be send to the gui + // (this can only be done after CServiceBroker::GetGUI()->GetWindowManager().Render()) + CServiceBroker::GetAppMessenger()->ProcessWindowMessages(); + + // handle any active scripts + + { + // Allow processing of script threads to let them shut down properly. + CSingleExit ex(CServiceBroker::GetWinSystem()->GetGfxContext()); + m_frameMoveGuard.unlock(); + CScriptInvocationManager::GetInstance().Process(); + m_frameMoveGuard.lock(); + } + + // process messages, even if a movie is playing + CServiceBroker::GetAppMessenger()->ProcessMessages(); + if (m_bStop) return; //we're done, everything has been unloaded + + // update sound + GetComponent<CApplicationPlayer>()->DoAudioWork(); + + // do any processing that isn't needed on each run + if( m_slowTimer.GetElapsedMilliseconds() > 500 ) + { + m_slowTimer.Reset(); + ProcessSlow(); + } +} + +// We get called every 500ms +void CApplication::ProcessSlow() +{ + // process skin resources (skin timers) + GetComponent<CApplicationSkinHandling>()->ProcessSkin(); + + CServiceBroker::GetPowerManager().ProcessEvents(); + +#if defined(TARGET_DARWIN_OSX) && defined(SDL_FOUND) + // There is an issue on OS X that several system services ask the cursor to become visible + // during their startup routines. Given that we can't control this, we hack it in by + // forcing the + if (CServiceBroker::GetWinSystem()->IsFullScreen()) + { // SDL thinks it's hidden + Cocoa_HideMouse(); + } +#endif + + // Temporarily pause pausable jobs when viewing video/picture + int currentWindow = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); + if (CurrentFileItem().IsVideo() || + CurrentFileItem().IsPicture() || + currentWindow == WINDOW_FULLSCREEN_VIDEO || + currentWindow == WINDOW_FULLSCREEN_GAME || + currentWindow == WINDOW_SLIDESHOW) + { + CServiceBroker::GetJobManager()->PauseJobs(); + } + else + { + CServiceBroker::GetJobManager()->UnPauseJobs(); + } + + // Check if we need to activate the screensaver / DPMS. + const auto appPower = GetComponent<CApplicationPowerHandling>(); + appPower->CheckScreenSaverAndDPMS(); + + // Check if we need to shutdown (if enabled). +#if defined(TARGET_DARWIN) + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNTIME) && + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen) +#else + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNTIME)) +#endif + { + appPower->CheckShutdown(); + } + +#if defined(TARGET_POSIX) + if (CPlatformPosix::TestQuitFlag()) + { + CLog::Log(LOGINFO, "Quitting due to POSIX signal"); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT); + } +#endif + + // check if we should restart the player + CheckDelayedPlayerRestart(); + + // check if we can unload any unreferenced dlls or sections + const auto appPlayer = GetComponent<CApplicationPlayer>(); + if (!appPlayer->IsPlayingVideo()) + CSectionLoader::UnloadDelayed(); + +#ifdef TARGET_ANDROID + // Pass the slow loop to droid + CXBMCApp::Get().ProcessSlow(); +#endif + + // check for any idle curl connections + g_curlInterface.CheckIdle(); + + CServiceBroker::GetGUI()->GetLargeTextureManager().CleanupUnusedImages(); + + CServiceBroker::GetGUI()->GetTextureManager().FreeUnusedTextures(5000); + +#ifdef HAS_DVD_DRIVE + // checks whats in the DVD drive and tries to autostart the content (xbox games, dvd, cdda, avi files...) + if (!appPlayer->IsPlayingVideo()) + m_Autorun->HandleAutorun(); +#endif + + // update upnp server/renderer states +#ifdef HAS_UPNP + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SERVICES_UPNP) && UPNP::CUPnP::IsInstantiated()) + UPNP::CUPnP::GetInstance()->UpdateState(); +#endif + +#if defined(TARGET_POSIX) && defined(HAS_FILESYSTEM_SMB) + smb.CheckIfIdle(); +#endif + +#ifdef HAS_FILESYSTEM_NFS + gNfsConnection.CheckIfIdle(); +#endif + + for (const auto& vfsAddon : CServiceBroker::GetVFSAddonCache().GetAddonInstances()) + vfsAddon->ClearOutIdle(); + + CServiceBroker::GetMediaManager().ProcessEvents(); + + // if we don't render the gui there's no reason to start the screensaver. + // that way the screensaver won't kick in if we maximize the XBMC window + // after the screensaver start time. + if (!appPower->GetRenderGUI()) + appPower->ResetScreenSaverTimer(); +} + +void CApplication::DelayedPlayerRestart() +{ + m_restartPlayerTimer.StartZero(); +} + +void CApplication::CheckDelayedPlayerRestart() +{ + if (m_restartPlayerTimer.GetElapsedSeconds() > 3) + { + m_restartPlayerTimer.Stop(); + m_restartPlayerTimer.Reset(); + Restart(true); + } +} + +void CApplication::Restart(bool bSamePosition) +{ + // this function gets called when the user changes a setting (like noninterleaved) + // and which means we gotta close & reopen the current playing file + + // first check if we're playing a file + const auto appPlayer = GetComponent<CApplicationPlayer>(); + if (!appPlayer->IsPlayingVideo() && !appPlayer->IsPlayingAudio()) + return ; + + if (!appPlayer->HasPlayer()) + return ; + + // do we want to return to the current position in the file + if (!bSamePosition) + { + // no, then just reopen the file and start at the beginning + PlayFile(*m_itemCurrentFile, "", true); + return ; + } + + // else get current position + double time = GetTime(); + + // get player state, needed for dvd's + std::string state = appPlayer->GetPlayerState(); + + // set the requested starttime + m_itemCurrentFile->SetStartOffset(CUtil::ConvertSecsToMilliSecs(time)); + + // reopen the file + if (PlayFile(*m_itemCurrentFile, "", true)) + appPlayer->SetPlayerState(state); +} + +const std::string& CApplication::CurrentFile() +{ + return m_itemCurrentFile->GetPath(); +} + +std::shared_ptr<CFileItem> CApplication::CurrentFileItemPtr() +{ + return m_itemCurrentFile; +} + +CFileItem& CApplication::CurrentFileItem() +{ + return *m_itemCurrentFile; +} + +const CFileItem& CApplication::CurrentUnstackedItem() +{ + const auto stackHelper = GetComponent<CApplicationStackHelper>(); + + if (stackHelper->IsPlayingISOStack() || stackHelper->IsPlayingRegularStack()) + return stackHelper->GetCurrentStackPartFileItem(); + else + return *m_itemCurrentFile; +} + +// Returns the total time in seconds of the current media. Fractional +// portions of a second are possible - but not necessarily supported by the +// player class. This returns a double to be consistent with GetTime() and +// SeekTime(). +double CApplication::GetTotalTime() const +{ + double rc = 0.0; + + const auto appPlayer = GetComponent<CApplicationPlayer>(); + const auto stackHelper = GetComponent<CApplicationStackHelper>(); + + if (appPlayer->IsPlaying()) + { + if (stackHelper->IsPlayingRegularStack()) + rc = stackHelper->GetStackTotalTimeMs() * 0.001; + else + rc = appPlayer->GetTotalTime() * 0.001; + } + + return rc; +} + +// Returns the current time in seconds of the currently playing media. +// Fractional portions of a second are possible. This returns a double to +// be consistent with GetTotalTime() and SeekTime(). +double CApplication::GetTime() const +{ + double rc = 0.0; + + const auto appPlayer = GetComponent<CApplicationPlayer>(); + const auto stackHelper = GetComponent<CApplicationStackHelper>(); + + if (appPlayer->IsPlaying()) + { + if (stackHelper->IsPlayingRegularStack()) + { + uint64_t startOfCurrentFile = stackHelper->GetCurrentStackPartStartTimeMs(); + rc = (startOfCurrentFile + appPlayer->GetTime()) * 0.001; + } + else + rc = appPlayer->GetTime() * 0.001; + } + + return rc; +} + +// Sets the current position of the currently playing media to the specified +// time in seconds. Fractional portions of a second are valid. The passed +// time is the time offset from the beginning of the file as opposed to a +// delta from the current position. This method accepts a double to be +// consistent with GetTime() and GetTotalTime(). +void CApplication::SeekTime( double dTime ) +{ + const auto appPlayer = GetComponent<CApplicationPlayer>(); + const auto stackHelper = GetComponent<CApplicationStackHelper>(); + + if (appPlayer->IsPlaying() && (dTime >= 0.0)) + { + if (!appPlayer->CanSeek()) + return; + + if (stackHelper->IsPlayingRegularStack()) + { + // find the item in the stack we are seeking to, and load the new + // file if necessary, and calculate the correct seek within the new + // file. Otherwise, just fall through to the usual routine if the + // time is higher than our total time. + int partNumberToPlay = + stackHelper->GetStackPartNumberAtTimeMs(static_cast<uint64_t>(dTime * 1000.0)); + uint64_t startOfNewFile = stackHelper->GetStackPartStartTimeMs(partNumberToPlay); + if (partNumberToPlay == stackHelper->GetCurrentPartNumber()) + appPlayer->SeekTime(static_cast<uint64_t>(dTime * 1000.0) - startOfNewFile); + else + { // seeking to a new file + stackHelper->SetStackPartCurrentFileItem(partNumberToPlay); + CFileItem* item = new CFileItem(stackHelper->GetCurrentStackPartFileItem()); + item->SetStartOffset(static_cast<uint64_t>(dTime * 1000.0) - startOfNewFile); + // don't just call "PlayFile" here, as we are quite likely called from the + // player thread, so we won't be able to delete ourselves. + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, 1, 0, static_cast<void*>(item)); + } + return; + } + // convert to milliseconds and perform seek + appPlayer->SeekTime(static_cast<int64_t>(dTime * 1000.0)); + } +} + +float CApplication::GetPercentage() const +{ + const auto appPlayer = GetComponent<CApplicationPlayer>(); + const auto stackHelper = GetComponent<CApplicationStackHelper>(); + + if (appPlayer->IsPlaying()) + { + if (appPlayer->GetTotalTime() == 0 && appPlayer->IsPlayingAudio() && + m_itemCurrentFile->HasMusicInfoTag()) + { + const CMusicInfoTag& tag = *m_itemCurrentFile->GetMusicInfoTag(); + if (tag.GetDuration() > 0) + return (float)(GetTime() / tag.GetDuration() * 100); + } + + if (stackHelper->IsPlayingRegularStack()) + { + double totalTime = GetTotalTime(); + if (totalTime > 0.0) + return (float)(GetTime() / totalTime * 100); + } + else + return appPlayer->GetPercentage(); + } + return 0.0f; +} + +float CApplication::GetCachePercentage() const +{ + const auto appPlayer = GetComponent<CApplicationPlayer>(); + const auto stackHelper = GetComponent<CApplicationStackHelper>(); + + if (appPlayer->IsPlaying()) + { + // Note that the player returns a relative cache percentage and we want an absolute percentage + if (stackHelper->IsPlayingRegularStack()) + { + float stackedTotalTime = (float) GetTotalTime(); + // We need to take into account the stack's total time vs. currently playing file's total time + if (stackedTotalTime > 0.0f) + return std::min(100.0f, + GetPercentage() + (appPlayer->GetCachePercentage() * + appPlayer->GetTotalTime() * 0.001f / stackedTotalTime)); + } + else + return std::min(100.0f, appPlayer->GetPercentage() + appPlayer->GetCachePercentage()); + } + return 0.0f; +} + +void CApplication::SeekPercentage(float percent) +{ + const auto appPlayer = GetComponent<CApplicationPlayer>(); + const auto stackHelper = GetComponent<CApplicationStackHelper>(); + + if (appPlayer->IsPlaying() && (percent >= 0.0f)) + { + if (!appPlayer->CanSeek()) + return; + if (stackHelper->IsPlayingRegularStack()) + SeekTime(static_cast<double>(percent) * 0.01 * GetTotalTime()); + else + appPlayer->SeekPercentage(percent); + } +} + +std::string CApplication::GetCurrentPlayer() +{ + const auto appPlayer = GetComponent<CApplicationPlayer>(); + return appPlayer->GetCurrentPlayer(); +} + +void CApplication::UpdateLibraries() +{ + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + if (settings->GetBool(CSettings::SETTING_VIDEOLIBRARY_UPDATEONSTARTUP)) + { + CLog::LogF(LOGINFO, "Starting video library startup scan"); + CVideoLibraryQueue::GetInstance().ScanLibrary( + "", false, !settings->GetBool(CSettings::SETTING_VIDEOLIBRARY_BACKGROUNDUPDATE)); + } + + if (settings->GetBool(CSettings::SETTING_MUSICLIBRARY_UPDATEONSTARTUP)) + { + CLog::LogF(LOGINFO, "Starting music library startup scan"); + CMusicLibraryQueue::GetInstance().ScanLibrary( + "", MUSIC_INFO::CMusicInfoScanner::SCAN_NORMAL, + !settings->GetBool(CSettings::SETTING_MUSICLIBRARY_BACKGROUNDUPDATE)); + } +} + +void CApplication::UpdateCurrentPlayArt() +{ + const auto appPlayer = GetComponent<CApplicationPlayer>(); + if (!appPlayer->IsPlayingAudio()) + return; + //Clear and reload the art for the currently playing item to show updated art on OSD + m_itemCurrentFile->ClearArt(); + CMusicThumbLoader loader; + loader.LoadItem(m_itemCurrentFile.get()); + // Mirror changes to GUI item + CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile); +} + +bool CApplication::ProcessAndStartPlaylist(const std::string& strPlayList, + PLAYLIST::CPlayList& playlist, + PLAYLIST::Id playlistId, + int track) +{ + CLog::Log(LOGDEBUG, "CApplication::ProcessAndStartPlaylist({}, {})", strPlayList, playlistId); + + // initial exit conditions + // no songs in playlist just return + if (playlist.size() == 0) + return false; + + // illegal playlist + if (playlistId == PLAYLIST::TYPE_NONE || playlistId == PLAYLIST::TYPE_PICTURE) + return false; + + // setup correct playlist + CServiceBroker::GetPlaylistPlayer().ClearPlaylist(playlistId); + + // if the playlist contains an internet stream, this file will be used + // to generate a thumbnail for musicplayer.cover + m_strPlayListFile = strPlayList; + + // add the items to the playlist player + CServiceBroker::GetPlaylistPlayer().Add(playlistId, playlist); + + // if we have a playlist + if (CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).size()) + { + // start playing it + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(playlistId); + CServiceBroker::GetPlaylistPlayer().Reset(); + CServiceBroker::GetPlaylistPlayer().Play(track, ""); + return true; + } + return false; +} + +bool CApplication::GetRenderGUI() const +{ + return GetComponent<CApplicationPowerHandling>()->GetRenderGUI(); +} + +bool CApplication::SetLanguage(const std::string &strLanguage) +{ + // nothing to be done if the language hasn't changed + if (strLanguage == CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOCALE_LANGUAGE)) + return true; + + return CServiceBroker::GetSettingsComponent()->GetSettings()->SetString(CSettings::SETTING_LOCALE_LANGUAGE, strLanguage); +} + +bool CApplication::LoadLanguage(bool reload) +{ + // load the configured language + if (!g_langInfo.SetLanguage("", reload)) + return false; + + // set the proper audio and subtitle languages + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + g_langInfo.SetAudioLanguage(settings->GetString(CSettings::SETTING_LOCALE_AUDIOLANGUAGE)); + g_langInfo.SetSubtitleLanguage(settings->GetString(CSettings::SETTING_LOCALE_SUBTITLELANGUAGE)); + + return true; +} + +void CApplication::SetLoggingIn(bool switchingProfiles) +{ + // don't save skin settings on unloading when logging into another profile + // because in that case we have already loaded the new profile and + // would therefore write the previous skin's settings into the new profile + // instead of into the previous one + GetComponent<CApplicationSkinHandling>()->m_saveSkinOnUnloading = !switchingProfiles; +} + +void CApplication::PrintStartupLog() +{ + CLog::Log(LOGINFO, "-----------------------------------------------------------------------"); + CLog::Log(LOGINFO, "Starting {} ({}). Platform: {} {} {}-bit", CSysInfo::GetAppName(), + CSysInfo::GetVersion(), g_sysinfo.GetBuildTargetPlatformName(), + g_sysinfo.GetBuildTargetCpuFamily(), g_sysinfo.GetXbmcBitness()); + + std::string buildType; +#if defined(_DEBUG) + buildType = "Debug"; +#elif defined(NDEBUG) + buildType = "Release"; +#else + buildType = "Unknown"; +#endif + + CLog::Log(LOGINFO, "Using {} {} x{}", buildType, CSysInfo::GetAppName(), + g_sysinfo.GetXbmcBitness()); + CLog::Log(LOGINFO, "{} compiled {} by {} for {} {} {}-bit {} ({})", CSysInfo::GetAppName(), + CSysInfo::GetBuildDate(), g_sysinfo.GetUsedCompilerNameAndVer(), + g_sysinfo.GetBuildTargetPlatformName(), g_sysinfo.GetBuildTargetCpuFamily(), + g_sysinfo.GetXbmcBitness(), g_sysinfo.GetBuildTargetPlatformVersionDecoded(), + g_sysinfo.GetBuildTargetPlatformVersion()); + + std::string deviceModel(g_sysinfo.GetModelName()); + if (!g_sysinfo.GetManufacturerName().empty()) + deviceModel = g_sysinfo.GetManufacturerName() + " " + + (deviceModel.empty() ? std::string("device") : deviceModel); + if (!deviceModel.empty()) + CLog::Log(LOGINFO, "Running on {} with {}, kernel: {} {} {}-bit version {}", deviceModel, + g_sysinfo.GetOsPrettyNameWithVersion(), g_sysinfo.GetKernelName(), + g_sysinfo.GetKernelCpuFamily(), g_sysinfo.GetKernelBitness(), + g_sysinfo.GetKernelVersionFull()); + else + CLog::Log(LOGINFO, "Running on {}, kernel: {} {} {}-bit version {}", + g_sysinfo.GetOsPrettyNameWithVersion(), g_sysinfo.GetKernelName(), + g_sysinfo.GetKernelCpuFamily(), g_sysinfo.GetKernelBitness(), + g_sysinfo.GetKernelVersionFull()); + + CLog::Log(LOGINFO, "FFmpeg version/source: {}", av_version_info()); + + std::string cpuModel(CServiceBroker::GetCPUInfo()->GetCPUModel()); + if (!cpuModel.empty()) + { + CLog::Log(LOGINFO, "Host CPU: {}, {} core{} available", cpuModel, + CServiceBroker::GetCPUInfo()->GetCPUCount(), + (CServiceBroker::GetCPUInfo()->GetCPUCount() == 1) ? "" : "s"); + } + else + CLog::Log(LOGINFO, "{} CPU core{} available", CServiceBroker::GetCPUInfo()->GetCPUCount(), + (CServiceBroker::GetCPUInfo()->GetCPUCount() == 1) ? "" : "s"); + + // Any system info logging that is unique to a platform + m_ServiceManager->GetPlatform().PlatformSyslog(); + +#if defined(__arm__) || defined(__aarch64__) + CLog::Log(LOGINFO, "ARM Features: Neon {}", + (CServiceBroker::GetCPUInfo()->GetCPUFeatures() & CPU_FEATURE_NEON) ? "enabled" + : "disabled"); +#endif + CSpecialProtocol::LogPaths(); + +#ifdef HAS_WEB_SERVER + CLog::Log(LOGINFO, "Webserver extra whitelist paths: {}", + StringUtils::Join(CCompileInfo::GetWebserverExtraWhitelist(), ", ")); +#endif + + // Check, whether libkodi.so was reused (happens on Android, where the system does not unload + // the lib on activity end, but keeps it loaded (as long as there is enough memory) and reuses + // it on next activity start. + static bool firstRun = true; + + CLog::Log(LOGINFO, "The executable running is: {}{}", CUtil::ResolveExecutablePath(), + firstRun ? "" : " [reused]"); + + firstRun = false; + + std::string hostname("[unknown]"); + m_ServiceManager->GetNetwork().GetHostName(hostname); + CLog::Log(LOGINFO, "Local hostname: {}", hostname); + std::string lowerAppName = CCompileInfo::GetAppName(); + StringUtils::ToLower(lowerAppName); + CLog::Log(LOGINFO, "Log File is located: {}.log", + CSpecialProtocol::TranslatePath("special://logpath/" + lowerAppName)); + CRegExp::LogCheckUtf8Support(); + CLog::Log(LOGINFO, "-----------------------------------------------------------------------"); +} + +void CApplication::CloseNetworkShares() +{ + CLog::Log(LOGDEBUG,"CApplication::CloseNetworkShares: Closing all network shares"); + +#if defined(HAS_FILESYSTEM_SMB) && !defined(TARGET_WINDOWS) + smb.Deinit(); +#endif + +#ifdef HAS_FILESYSTEM_NFS + gNfsConnection.Deinit(); +#endif + + for (const auto& vfsAddon : CServiceBroker::GetVFSAddonCache().GetAddonInstances()) + vfsAddon->DisconnectAll(); +} diff --git a/xbmc/application/Application.h b/xbmc/application/Application.h new file mode 100644 index 0000000..a37dcf7 --- /dev/null +++ b/xbmc/application/Application.h @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "application/ApplicationComponents.h" +#include "application/ApplicationEnums.h" +#include "application/ApplicationPlayerCallback.h" +#include "application/ApplicationSettingsHandling.h" +#include "guilib/IMsgTargetCallback.h" +#include "guilib/IWindowManagerCallback.h" +#include "messaging/IMessageTarget.h" +#include "playlists/PlayListTypes.h" +#include "threads/SystemClock.h" +#include "utils/GlobalsHandling.h" +#include "utils/Stopwatch.h" +#include "windowing/Resolution.h" +#include "windowing/XBMC_events.h" + +#include <atomic> +#include <chrono> +#include <deque> +#include <memory> +#include <string> +#include <vector> + +class CAction; +class CAppInboundProtocol; +class CBookmark; +class CFileItem; +class CFileItemList; +class CGUIComponent; +class CInertialScrollingHandler; +class CKey; +class CSeekHandler; +class CServiceManager; +class CSettingsComponent; +class CSplash; +class CWinSystemBase; + +namespace ADDON +{ + class CSkinInfo; + class IAddon; + typedef std::shared_ptr<IAddon> AddonPtr; + class CAddonInfo; +} + +namespace ANNOUNCEMENT +{ + class CAnnouncementManager; +} + +namespace MEDIA_DETECT +{ + class CAutorun; +} + +namespace PLAYLIST +{ + class CPlayList; +} + +namespace ActiveAE +{ + class CActiveAE; +} + +namespace VIDEO +{ + class CVideoInfoScanner; +} + +namespace MUSIC_INFO +{ + class CMusicInfoScanner; +} + +class CApplication : public IWindowManagerCallback, + public IMsgTargetCallback, + public KODI::MESSAGING::IMessageTarget, + public CApplicationComponents, + public CApplicationPlayerCallback, + public CApplicationSettingsHandling +{ +friend class CAppInboundProtocol; + +public: + + // If playback time of current item is greater than this value, ACTION_PREV_ITEM will seek to start + // of currently playing item, otherwise it will seek to start of the previous item in playlist + static const unsigned int ACTION_PREV_ITEM_THRESHOLD = 3; // seconds; + + CApplication(void); + ~CApplication(void) override; + + bool Create(); + bool Initialize(); + int Run(); + bool Cleanup(); + + void FrameMove(bool processEvents, bool processGUI = true) override; + void Render() override; + + bool IsInitialized() const { return !m_bInitializing; } + bool IsStopping() const { return m_bStop; } + + bool CreateGUI(); + bool InitWindow(RESOLUTION res = RES_INVALID); + + bool Stop(int exitCode); + const std::string& CurrentFile(); + CFileItem& CurrentFileItem(); + std::shared_ptr<CFileItem> CurrentFileItemPtr(); + const CFileItem& CurrentUnstackedItem(); + bool OnMessage(CGUIMessage& message) override; + std::string GetCurrentPlayer(); + + int GetMessageMask() override; + void OnApplicationMessage(KODI::MESSAGING::ThreadMessage* pMsg) override; + + bool PlayMedia(CFileItem& item, const std::string& player, PLAYLIST::Id playlistId); + bool ProcessAndStartPlaylist(const std::string& strPlayList, + PLAYLIST::CPlayList& playlist, + PLAYLIST::Id playlistId, + int track = 0); + bool PlayFile(CFileItem item, const std::string& player, bool bRestart = false); + void StopPlaying(); + void Restart(bool bSamePosition = true); + void DelayedPlayerRestart(); + void CheckDelayedPlayerRestart(); + bool IsPlayingFullScreenVideo() const; + bool IsFullScreen(); + bool OnAction(const CAction &action); + void CloseNetworkShares(); + + void ConfigureAndEnableAddons(); + void ShowAppMigrationMessage(); + void Process() override; + void ProcessSlow(); + /*! + \brief Returns the total time in fractional seconds of the currently playing media + + Beware that this method returns fractional seconds whereas IPlayer::GetTotalTime() returns milliseconds. + */ + double GetTotalTime() const; + /*! + \brief Returns the current time in fractional seconds of the currently playing media + + Beware that this method returns fractional seconds whereas IPlayer::GetTime() returns milliseconds. + */ + double GetTime() const; + float GetPercentage() const; + + // Get the percentage of data currently cached/buffered (aq/vq + FileCache) from the input stream if applicable. + float GetCachePercentage() const; + + void SeekPercentage(float percent); + void SeekTime( double dTime = 0.0 ); + + void UpdateLibraries(); + + void UpdateCurrentPlayArt(); + + bool ExecuteXBMCAction(std::string action, const CGUIListItemPtr &item = NULL); + +#ifdef HAS_DVD_DRIVE + std::unique_ptr<MEDIA_DETECT::CAutorun> m_Autorun; +#endif + + std::string m_strPlayListFile; + + bool IsAppFocused() const { return m_AppFocused; } + + bool GetRenderGUI() const override; + + bool SetLanguage(const std::string &strLanguage); + bool LoadLanguage(bool reload); + + void SetLoggingIn(bool switchingProfiles); + + std::unique_ptr<CServiceManager> m_ServiceManager; + + /*! + \brief Locks calls from outside kodi (e.g. python) until framemove is processed. + */ + void LockFrameMoveGuard(); + + /*! + \brief Unlocks calls from outside kodi (e.g. python). + */ + void UnlockFrameMoveGuard(); + +protected: + bool OnSettingsSaving() const override; + void PlaybackCleanup(); + + // inbound protocol + bool OnEvent(XBMC_Event& newEvent); + + std::shared_ptr<ANNOUNCEMENT::CAnnouncementManager> m_pAnnouncementManager; + std::unique_ptr<CGUIComponent> m_pGUI; + std::unique_ptr<CWinSystemBase> m_pWinSystem; + std::unique_ptr<ActiveAE::CActiveAE> m_pActiveAE; + std::shared_ptr<CAppInboundProtocol> m_pAppPort; + std::deque<XBMC_Event> m_portEvents; + CCriticalSection m_portSection; + + // timer information + CStopWatch m_restartPlayerTimer; + CStopWatch m_frameTime; + CStopWatch m_slowTimer; + XbmcThreads::EndTime<> m_guiRefreshTimer; + + std::string m_prevMedia; + bool m_bInitializing = true; + + int m_nextPlaylistItem = -1; + + std::chrono::time_point<std::chrono::steady_clock> m_lastRenderTime; + bool m_skipGuiRender = false; + + std::unique_ptr<MUSIC_INFO::CMusicInfoScanner> m_musicInfoScanner; + + bool PlayStack(CFileItem& item, bool bRestart); + + void HandlePortEvents(); + + std::unique_ptr<CInertialScrollingHandler> m_pInertialScrollingHandler; + + std::vector<std::shared_ptr<ADDON::CAddonInfo>> + m_incompatibleAddons; /*!< Result of addon migration (incompatible addon infos) */ + +public: + bool m_bStop{false}; + bool m_AppFocused{true}; + +private: + void PrintStartupLog(); + void ResetCurrentItem(); + + mutable CCriticalSection m_critSection; /*!< critical section for all changes to this class, except for changes to triggers */ + + CCriticalSection m_frameMoveGuard; /*!< critical section for synchronizing GUI actions from inside and outside (python) */ + std::atomic_uint m_WaitingExternalCalls; /*!< counts threads which are waiting to be processed in FrameMove */ + unsigned int m_ProcessedExternalCalls = 0; /*!< counts calls which are processed during one "door open" cycle in FrameMove */ + unsigned int m_ProcessedExternalDecay = 0; /*!< counts to close door after a few frames of no python activity */ + int m_ExitCode{EXITCODE_QUIT}; +}; + +XBMC_GLOBAL_REF(CApplication,g_application); +#define g_application XBMC_GLOBAL_USE(CApplication) diff --git a/xbmc/application/ApplicationActionListeners.cpp b/xbmc/application/ApplicationActionListeners.cpp new file mode 100644 index 0000000..83c798e --- /dev/null +++ b/xbmc/application/ApplicationActionListeners.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ApplicationActionListeners.h" + +#include "interfaces/IActionListener.h" +#include "threads/CriticalSection.h" + +#include <algorithm> +#include <mutex> + +CApplicationActionListeners::CApplicationActionListeners(CCriticalSection& section) + : m_critSection(section) +{ +} + +void CApplicationActionListeners::RegisterActionListener(IActionListener* listener) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + const auto it = std::find(m_actionListeners.begin(), m_actionListeners.end(), listener); + if (it == m_actionListeners.end()) + m_actionListeners.push_back(listener); +} + +void CApplicationActionListeners::UnregisterActionListener(IActionListener* listener) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + auto it = std::find(m_actionListeners.begin(), m_actionListeners.end(), listener); + if (it != m_actionListeners.end()) + m_actionListeners.erase(it); +} + +bool CApplicationActionListeners::NotifyActionListeners(const CAction& action) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + for (const auto& listener : m_actionListeners) + { + if (listener->OnAction(action)) + return true; + } + + return false; +} diff --git a/xbmc/application/ApplicationActionListeners.h b/xbmc/application/ApplicationActionListeners.h new file mode 100644 index 0000000..8f72dd4 --- /dev/null +++ b/xbmc/application/ApplicationActionListeners.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "application/IApplicationComponent.h" + +#include <vector> + +class CAction; +class CApplication; +class CCriticalSection; +class IActionListener; + +/*! + * \brief Class handling application support for action listeners. + */ + +class CApplicationActionListeners : public IApplicationComponent +{ + friend class CApplication; + +public: + CApplicationActionListeners(CCriticalSection& sect); + + /*! + \brief Register an action listener. + \param listener The listener to register + */ + void RegisterActionListener(IActionListener* listener); + /*! + \brief Unregister an action listener. + \param listener The listener to unregister + */ + void UnregisterActionListener(IActionListener* listener); + +protected: + /*! + \brief Delegates the action to all registered action handlers. + \param action The action + \return true, if the action was taken by one of the action listener. + */ + bool NotifyActionListeners(const CAction& action) const; + + std::vector<IActionListener*> m_actionListeners; + + CCriticalSection& m_critSection; +}; diff --git a/xbmc/application/ApplicationComponents.h b/xbmc/application/ApplicationComponents.h new file mode 100644 index 0000000..33ae0d3 --- /dev/null +++ b/xbmc/application/ApplicationComponents.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "application/IApplicationComponent.h" +#include "utils/ComponentContainer.h" + +//! \brief Convenience alias for application components. +using CApplicationComponents = CComponentContainer<IApplicationComponent>; diff --git a/xbmc/application/ApplicationEnums.h b/xbmc/application/ApplicationEnums.h new file mode 100644 index 0000000..3afa41e --- /dev/null +++ b/xbmc/application/ApplicationEnums.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +enum StartupAction +{ + STARTUP_ACTION_NONE = 0, + STARTUP_ACTION_PLAY_TV, + STARTUP_ACTION_PLAY_RADIO +}; + +// Do not change the numbers; external scripts depend on them +enum +{ + EXITCODE_QUIT = 0, + EXITCODE_POWERDOWN = 64, + EXITCODE_RESTARTAPP = 65, + EXITCODE_REBOOT = 66, +}; diff --git a/xbmc/application/ApplicationPlayer.cpp b/xbmc/application/ApplicationPlayer.cpp new file mode 100644 index 0000000..ef571a6 --- /dev/null +++ b/xbmc/application/ApplicationPlayer.cpp @@ -0,0 +1,1061 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ApplicationPlayer.h" + +#include "ServiceBroker.h" +#include "cores/DataCacheCore.h" +#include "cores/IPlayer.h" +#include "cores/VideoPlayer/VideoPlayer.h" +#include "cores/playercorefactory/PlayerCoreFactory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" + +#include <mutex> + +using namespace std::chrono_literals; + +std::shared_ptr<const IPlayer> CApplicationPlayer::GetInternal() const +{ + std::unique_lock<CCriticalSection> lock(m_playerLock); + return m_pPlayer; +} + +std::shared_ptr<IPlayer> CApplicationPlayer::GetInternal() +{ + std::unique_lock<CCriticalSection> lock(m_playerLock); + return m_pPlayer; +} + +void CApplicationPlayer::ClosePlayer() +{ + m_nextItem.pItem.reset(); + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + { + CloseFile(); + ResetPlayer(); + } +} + +void CApplicationPlayer::ResetPlayer() +{ + // we need to do this directly on the member + std::unique_lock<CCriticalSection> lock(m_playerLock); + m_pPlayer.reset(); +} + +void CApplicationPlayer::CloseFile(bool reopen) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + { + player->CloseFile(reopen); + } +} + +void CApplicationPlayer::CreatePlayer(const CPlayerCoreFactory &factory, const std::string &player, IPlayerCallback& callback) +{ + std::unique_lock<CCriticalSection> lock(m_playerLock); + if (!m_pPlayer) + { + CDataCacheCore::GetInstance().Reset(); + m_pPlayer = factory.CreatePlayer(player, callback); + } +} + +std::string CApplicationPlayer::GetCurrentPlayer() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + { + return player->m_name; + } + return ""; +} + +bool CApplicationPlayer::OpenFile(const CFileItem& item, const CPlayerOptions& options, + const CPlayerCoreFactory &factory, + const std::string &playerName, IPlayerCallback& callback) +{ + // get player type + std::string newPlayer; + if (!playerName.empty()) + newPlayer = playerName; + else + newPlayer = factory.GetDefaultPlayer(item); + + // check if we need to close current player + // VideoPlayer can open a new file while playing + std::shared_ptr<IPlayer> player = GetInternal(); + if (player && player->IsPlaying()) + { + bool needToClose = false; + + if (item.IsDiscImage() || item.IsDVDFile()) + needToClose = true; + + if (player->m_name != newPlayer) + needToClose = true; + + if (player->m_type != "video") + needToClose = true; + + if (needToClose) + { + m_nextItem.pItem = std::make_shared<CFileItem>(item); + m_nextItem.options = options; + m_nextItem.playerName = newPlayer; + m_nextItem.callback = &callback; + + CloseFile(); + if (player->m_name != newPlayer) + { + std::unique_lock<CCriticalSection> lock(m_playerLock); + m_pPlayer.reset(); + } + return true; + } + } + else if (player && player->m_name != newPlayer) + { + CloseFile(); + { + std::unique_lock<CCriticalSection> lock(m_playerLock); + m_pPlayer.reset(); + player.reset(); + } + } + + if (!player) + { + CreatePlayer(factory, newPlayer, callback); + player = GetInternal(); + if (!player) + return false; + } + + bool ret = player->OpenFile(item, options); + + m_nextItem.pItem.reset(); + + // reset caching timers + m_audioStreamUpdate.SetExpired(); + m_videoStreamUpdate.SetExpired(); + m_subtitleStreamUpdate.SetExpired(); + + return ret; +} + +void CApplicationPlayer::OpenNext(const CPlayerCoreFactory &factory) +{ + if (m_nextItem.pItem) + { + OpenFile(*m_nextItem.pItem, m_nextItem.options, + factory, + m_nextItem.playerName, *m_nextItem.callback); + m_nextItem.pItem.reset(); + } +} + +bool CApplicationPlayer::HasPlayer() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + return player != nullptr; +} + +int CApplicationPlayer::GetChapter() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->GetChapter(); + else + return -1; +} + +int CApplicationPlayer::GetChapterCount() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->GetChapterCount(); + else + return 0; +} + +void CApplicationPlayer::GetChapterName(std::string& strChapterName, int chapterIdx) const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + player->GetChapterName(strChapterName, chapterIdx); +} + +int64_t CApplicationPlayer::GetChapterPos(int chapterIdx) const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->GetChapterPos(chapterIdx); + + return -1; +} + +bool CApplicationPlayer::HasAudio() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + return (player && player->HasAudio()); +} + +bool CApplicationPlayer::HasVideo() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + return (player && player->HasVideo()); +} + +bool CApplicationPlayer::HasGame() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + return (player && player->HasGame()); +} + +PLAYLIST::Id CApplicationPlayer::GetPreferredPlaylist() const +{ + if (IsPlayingVideo()) + return PLAYLIST::TYPE_VIDEO; + + if (IsPlayingAudio()) + return PLAYLIST::TYPE_MUSIC; + + return PLAYLIST::TYPE_NONE; +} + +bool CApplicationPlayer::HasRDS() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + return (player && player->HasRDS()); +} + +bool CApplicationPlayer::IsPaused() const +{ + return (GetPlaySpeed() == 0); +} + +bool CApplicationPlayer::IsPlaying() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + return (player && player->IsPlaying()); +} + +bool CApplicationPlayer::IsPausedPlayback() const +{ + return (IsPlaying() && (GetPlaySpeed() == 0)); +} + +bool CApplicationPlayer::IsPlayingAudio() const +{ + return (IsPlaying() && !HasVideo() && HasAudio()); +} + +bool CApplicationPlayer::IsPlayingVideo() const +{ + return (IsPlaying() && HasVideo()); +} + +bool CApplicationPlayer::IsPlayingGame() const +{ + return (IsPlaying() && HasGame()); +} + +bool CApplicationPlayer::IsPlayingRDS() const +{ + return (IsPlaying() && HasRDS()); +} + +void CApplicationPlayer::Pause() +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + { + player->Pause(); + } +} + +void CApplicationPlayer::SetMute(bool bOnOff) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->SetMute(bOnOff); +} + +void CApplicationPlayer::SetVolume(float volume) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->SetVolume(volume); +} + +void CApplicationPlayer::Seek(bool bPlus, bool bLargeStep, bool bChapterOverride) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->Seek(bPlus, bLargeStep, bChapterOverride); +} + +void CApplicationPlayer::SeekPercentage(float fPercent) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->SeekPercentage(fPercent); +} + +bool CApplicationPlayer::IsPassthrough() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + return (player && player->IsPassthrough()); +} + +bool CApplicationPlayer::CanSeek() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + return (player && player->CanSeek()); +} + +bool CApplicationPlayer::SeekScene(bool bPlus) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + return (player && player->SeekScene(bPlus)); +} + +void CApplicationPlayer::SeekTime(int64_t iTime) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->SeekTime(iTime); +} + +void CApplicationPlayer::SeekTimeRelative(int64_t iTime) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + { + // use relative seeking if implemented by player + if (!player->SeekTimeRelative(iTime)) + { + int64_t abstime = GetTime() + iTime; + player->SeekTime(abstime); + } + } +} + +int64_t CApplicationPlayer::GetTime() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return CDataCacheCore::GetInstance().GetPlayTime(); + else + return 0; +} + +int64_t CApplicationPlayer::GetMinTime() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return CDataCacheCore::GetInstance().GetMinTime(); + else + return 0; +} + +int64_t CApplicationPlayer::GetMaxTime() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return CDataCacheCore::GetInstance().GetMaxTime(); + else + return 0; +} + +time_t CApplicationPlayer::GetStartTime() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return CDataCacheCore::GetInstance().GetStartTime(); + else + return 0; +} + +int64_t CApplicationPlayer::GetTotalTime() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + { + int64_t total = CDataCacheCore::GetInstance().GetMaxTime() - CDataCacheCore::GetInstance().GetMinTime(); + return total; + } + else + return 0; +} + +bool CApplicationPlayer::IsCaching() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + return (player && player->IsCaching()); +} + +bool CApplicationPlayer::IsInMenu() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + return (player && player->IsInMenu()); +} + +MenuType CApplicationPlayer::GetSupportedMenuType() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + if (!player) + { + return MenuType::NONE; + } + return player->GetSupportedMenuType(); +} + +int CApplicationPlayer::GetCacheLevel() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->GetCacheLevel(); + else + return 0; +} + +int CApplicationPlayer::GetSubtitleCount() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->GetSubtitleCount(); + else + return 0; +} + +int CApplicationPlayer::GetAudioStream() +{ + if (!m_audioStreamUpdate.IsTimePast()) + return m_iAudioStream; + + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + { + m_iAudioStream = player->GetAudioStream(); + m_audioStreamUpdate.Set(1000ms); + return m_iAudioStream; + } + else + return 0; +} + +int CApplicationPlayer::GetSubtitle() +{ + if (!m_subtitleStreamUpdate.IsTimePast()) + return m_iSubtitleStream; + + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + { + m_iSubtitleStream = player->GetSubtitle(); + m_subtitleStreamUpdate.Set(1000ms); + return m_iSubtitleStream; + } + else + return 0; +} + +bool CApplicationPlayer::GetSubtitleVisible() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + return player && player->GetSubtitleVisible(); +} + +bool CApplicationPlayer::CanPause() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + return (player && player->CanPause()); +} + +bool CApplicationPlayer::HasTeletextCache() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->HasTeletextCache(); + else + return false; +} + +std::shared_ptr<TextCacheStruct_t> CApplicationPlayer::GetTeletextCache() +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + return player->GetTeletextCache(); + else + return {}; +} + +float CApplicationPlayer::GetPercentage() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + { + float fPercent = CDataCacheCore::GetInstance().GetPlayPercentage(); + return std::max(0.0f, std::min(fPercent, 100.0f)); + } + else + return 0.0; +} + +float CApplicationPlayer::GetCachePercentage() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->GetCachePercentage(); + else + return 0.0; +} + +void CApplicationPlayer::SetSpeed(float speed) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->SetSpeed(speed); +} + +void CApplicationPlayer::SetTempo(float tempo) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->SetTempo(tempo); +} + +void CApplicationPlayer::FrameAdvance(int frames) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->FrameAdvance(frames); +} + +void CApplicationPlayer::DoAudioWork() +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->DoAudioWork(); +} + +std::string CApplicationPlayer::GetPlayerState() +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + return player->GetPlayerState(); + else + return ""; +} + +bool CApplicationPlayer::QueueNextFile(const CFileItem &file) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + return (player && player->QueueNextFile(file)); +} + +bool CApplicationPlayer::SetPlayerState(const std::string& state) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + return (player && player->SetPlayerState(state)); +} + +void CApplicationPlayer::OnNothingToQueueNotify() +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->OnNothingToQueueNotify(); +} + +void CApplicationPlayer::GetVideoStreamInfo(int streamId, VideoStreamInfo& info) const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + player->GetVideoStreamInfo(streamId, info); +} + +void CApplicationPlayer::GetAudioStreamInfo(int index, AudioStreamInfo& info) const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + player->GetAudioStreamInfo(index, info); +} + +int CApplicationPlayer::GetPrograms(std::vector<ProgramInfo> &programs) +{ + int ret = 0; + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + ret = player->GetPrograms(programs); + return ret; +} + +void CApplicationPlayer::SetProgram(int progId) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->SetProgram(progId); +} + +int CApplicationPlayer::GetProgramsCount() const +{ + int ret = 0; + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + ret = player->GetProgramsCount(); + return ret; +} + +bool CApplicationPlayer::OnAction(const CAction &action) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + return (player && player->OnAction(action)); +} + +int CApplicationPlayer::GetAudioStreamCount() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->GetAudioStreamCount(); + else + return 0; +} + +int CApplicationPlayer::GetVideoStream() +{ + if (!m_videoStreamUpdate.IsTimePast()) + return m_iVideoStream; + + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + { + m_iVideoStream = player->GetVideoStream(); + m_videoStreamUpdate.Set(1000ms); + return m_iVideoStream; + } + else + return 0; +} + +int CApplicationPlayer::GetVideoStreamCount() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->GetVideoStreamCount(); + else + return 0; +} + +void CApplicationPlayer::SetAudioStream(int iStream) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + { + player->SetAudioStream(iStream); + m_iAudioStream = iStream; + m_audioStreamUpdate.Set(1000ms); + } +} + +void CApplicationPlayer::GetSubtitleStreamInfo(int index, SubtitleStreamInfo& info) const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + player->GetSubtitleStreamInfo(index, info); +} + +void CApplicationPlayer::SetSubtitle(int iStream) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + { + player->SetSubtitle(iStream); + m_iSubtitleStream = iStream; + m_subtitleStreamUpdate.Set(1000ms); + } +} + +void CApplicationPlayer::SetSubtitleVisible(bool bVisible) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + { + player->SetSubtitleVisible(bVisible); + } +} + +void CApplicationPlayer::SetSubtitleVerticalPosition(int value, bool save) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + { + player->SetSubtitleVerticalPosition(value, save); + } +} + +void CApplicationPlayer::SetTime(int64_t time) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + return player->SetTime(time); +} + +void CApplicationPlayer::SetTotalTime(int64_t time) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->SetTotalTime(time); +} + +void CApplicationPlayer::SetVideoStream(int iStream) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + { + player->SetVideoStream(iStream); + m_iVideoStream = iStream; + m_videoStreamUpdate.Set(1000ms); + } +} + +void CApplicationPlayer::AddSubtitle(const std::string& strSubPath) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->AddSubtitle(strSubPath); +} + +void CApplicationPlayer::SetSubTitleDelay(float fValue) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->SetSubTitleDelay(fValue); +} + +void CApplicationPlayer::SetAVDelay(float fValue) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->SetAVDelay(fValue); +} + +void CApplicationPlayer::SetDynamicRangeCompression(long drc) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->SetDynamicRangeCompression(drc); +} + +void CApplicationPlayer::LoadPage(int p, int sp, unsigned char* buffer) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->LoadPage(p, sp, buffer); +} + +void CApplicationPlayer::GetAudioCapabilities(std::vector<int>& audioCaps) const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + player->GetAudioCapabilities(audioCaps); +} + +void CApplicationPlayer::GetSubtitleCapabilities(std::vector<int>& subCaps) const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + player->GetSubtitleCapabilities(subCaps); +} + +int CApplicationPlayer::SeekChapter(int iChapter) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + return player->SeekChapter(iChapter); + else + return 0; +} + +void CApplicationPlayer::SetPlaySpeed(float speed) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (!player) + return; + + if (!IsPlayingAudio() && !IsPlayingVideo()) + return ; + + SetSpeed(speed); +} + +float CApplicationPlayer::GetPlaySpeed() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + { + return CDataCacheCore::GetInstance().GetSpeed(); + } + else + return 0; +} + +float CApplicationPlayer::GetPlayTempo() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + { + return CDataCacheCore::GetInstance().GetTempo(); + } + else + return 0; +} + +bool CApplicationPlayer::SupportsTempo() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->SupportsTempo(); + else + return false; +} + +void CApplicationPlayer::FrameMove() +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + { + if (CDataCacheCore::GetInstance().IsPlayerStateChanged()) + // CApplicationMessenger would be overhead because we are already in gui thread + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_STATE_CHANGED); + } +} + +void CApplicationPlayer::Render(bool clear, uint32_t alpha, bool gui) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->Render(clear, alpha, gui); +} + +void CApplicationPlayer::FlushRenderer() +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->FlushRenderer(); +} + +void CApplicationPlayer::SetRenderViewMode(int mode, float zoom, float par, float shift, bool stretch) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->SetRenderViewMode(mode, zoom, par, shift, stretch); +} + +float CApplicationPlayer::GetRenderAspectRatio() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->GetRenderAspectRatio(); + else + return 1.0; +} + +void CApplicationPlayer::TriggerUpdateResolution() +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->TriggerUpdateResolution(); +} + +bool CApplicationPlayer::IsRenderingVideo() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->IsRenderingVideo(); + else + return false; +} + +bool CApplicationPlayer::IsRenderingGuiLayer() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return CServiceBroker::GetDataCacheCore().GetGuiRender(); + else + return false; +} + +bool CApplicationPlayer::IsRenderingVideoLayer() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return CServiceBroker::GetDataCacheCore().GetVideoRender(); + else + return false; +} + +bool CApplicationPlayer::Supports(EINTERLACEMETHOD method) const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->Supports(method); + else + return false; +} + +EINTERLACEMETHOD CApplicationPlayer::GetDeinterlacingMethodDefault() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->GetDeinterlacingMethodDefault(); + else + return EINTERLACEMETHOD::VS_INTERLACEMETHOD_NONE; +} + +bool CApplicationPlayer::Supports(ESCALINGMETHOD method) const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->Supports(method); + else + return false; +} + +bool CApplicationPlayer::Supports(ERENDERFEATURE feature) const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->Supports(feature); + else + return false; +} + +unsigned int CApplicationPlayer::RenderCaptureAlloc() +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + return player->RenderCaptureAlloc(); + else + return 0; +} + +void CApplicationPlayer::RenderCapture(unsigned int captureId, unsigned int width, unsigned int height, int flags) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->RenderCapture(captureId, width, height, flags); +} + +void CApplicationPlayer::RenderCaptureRelease(unsigned int captureId) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + player->RenderCaptureRelease(captureId); +} + +bool CApplicationPlayer::RenderCaptureGetPixels(unsigned int captureId, unsigned int millis, uint8_t *buffer, unsigned int size) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + return player->RenderCaptureGetPixels(captureId, millis, buffer, size); + else + return false; +} + +bool CApplicationPlayer::IsExternalPlaying() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + { + if (player->IsPlaying() && player->m_type == "external") + return true; + } + return false; +} + +bool CApplicationPlayer::IsRemotePlaying() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + { + if (player->IsPlaying() && player->m_type == "remote") + return true; + } + return false; +} + +CVideoSettings CApplicationPlayer::GetVideoSettings() const +{ + std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + { + return player->GetVideoSettings(); + } + return CVideoSettings(); +} + +void CApplicationPlayer::SetVideoSettings(CVideoSettings& settings) +{ + std::shared_ptr<IPlayer> player = GetInternal(); + if (player) + { + return player->SetVideoSettings(settings); + } +} + +CSeekHandler& CApplicationPlayer::GetSeekHandler() +{ + return m_seekHandler; +} + +const CSeekHandler& CApplicationPlayer::GetSeekHandler() const +{ + return m_seekHandler; +} + +void CApplicationPlayer::SetUpdateStreamDetails() +{ + std::shared_ptr<IPlayer> player = GetInternal(); + CVideoPlayer* vp = dynamic_cast<CVideoPlayer*>(player.get()); + if (vp) + vp->SetUpdateStreamDetails(); +} + +bool CApplicationPlayer::HasGameAgent() const +{ + const std::shared_ptr<const IPlayer> player = GetInternal(); + if (player) + return player->HasGameAgent(); + + return false; +} + +int CApplicationPlayer::GetSubtitleDelay() const +{ + // converts subtitle delay to a percentage + const auto& advSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); + const auto delay = this->GetVideoSettings().m_SubtitleDelay; + const auto range = advSettings->m_videoSubsDelayRange; + return static_cast<int>(0.5f + (delay + range) / (2.f * range) * 100.0f); +} + +int CApplicationPlayer::GetAudioDelay() const +{ + // converts audio delay to a percentage + const auto& advSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); + const auto delay = this->GetVideoSettings().m_AudioDelay; + const auto range = advSettings->m_videoAudioDelayRange; + return static_cast<int>(0.5f + (delay + range) / (2.f * range) * 100.0f); +} diff --git a/xbmc/application/ApplicationPlayer.h b/xbmc/application/ApplicationPlayer.h new file mode 100644 index 0000000..f760936 --- /dev/null +++ b/xbmc/application/ApplicationPlayer.h @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "SeekHandler.h" +#include "application/IApplicationComponent.h" +#include "cores/IPlayer.h" +#include "cores/MenuType.h" +#include "playlists/PlayListTypes.h" +#include "threads/CriticalSection.h" +#include "threads/SystemClock.h" + +#include <memory> +#include <string> +#include <vector> + +class CAction; +class CPlayerCoreFactory; +class CPlayerOptions; +class CStreamDetails; + +struct AudioStreamInfo; +struct VideoStreamInfo; +struct SubtitleStreamInfo; +struct TextCacheStruct_t; + +class CApplicationPlayer : public IApplicationComponent +{ +public: + CApplicationPlayer() = default; + + // player management + void ClosePlayer(); + void ResetPlayer(); + std::string GetCurrentPlayer() const; + float GetPlaySpeed() const; + float GetPlayTempo() const; + bool HasPlayer() const; + bool OpenFile(const CFileItem& item, const CPlayerOptions& options, + const CPlayerCoreFactory &factory, + const std::string &playerName, IPlayerCallback& callback); + void OpenNext(const CPlayerCoreFactory &factory); + void SetPlaySpeed(float speed); + void SetTempo(float tempo); + void FrameAdvance(int frames); + + void FrameMove(); + void Render(bool clear, uint32_t alpha = 255, bool gui = true); + void FlushRenderer(); + void SetRenderViewMode(int mode, float zoom, float par, float shift, bool stretch); + float GetRenderAspectRatio() const; + void TriggerUpdateResolution(); + bool IsRenderingVideo() const; + bool IsRenderingGuiLayer() const; + bool IsRenderingVideoLayer() const; + bool Supports(EINTERLACEMETHOD method) const; + EINTERLACEMETHOD GetDeinterlacingMethodDefault() const; + bool Supports(ESCALINGMETHOD method) const; + bool Supports(ERENDERFEATURE feature) const; + unsigned int RenderCaptureAlloc(); + void RenderCapture(unsigned int captureId, unsigned int width, unsigned int height, int flags = 0); + void RenderCaptureRelease(unsigned int captureId); + bool RenderCaptureGetPixels(unsigned int captureId, unsigned int millis, uint8_t *buffer, unsigned int size); + bool IsExternalPlaying() const; + bool IsRemotePlaying() const; + + // proxy calls + void AddSubtitle(const std::string& strSubPath); + bool CanPause() const; + bool CanSeek() const; + void DoAudioWork(); + int GetAudioDelay() const; + void GetAudioCapabilities(std::vector<int>& audioCaps) const; + int GetAudioStream(); + int GetAudioStreamCount() const; + void GetAudioStreamInfo(int index, AudioStreamInfo& info) const; + int GetCacheLevel() const; + float GetCachePercentage() const; + int GetChapterCount() const; + int GetChapter() const; + void GetChapterName(std::string& strChapterName, int chapterIdx = -1) const; + int64_t GetChapterPos(int chapterIdx = -1) const; + float GetPercentage() const; + std::string GetPlayerState(); + PLAYLIST::Id GetPreferredPlaylist() const; + int GetSubtitleDelay() const; + int GetSubtitle(); + void GetSubtitleCapabilities(std::vector<int>& subCaps) const; + int GetSubtitleCount() const; + void GetSubtitleStreamInfo(int index, SubtitleStreamInfo& info) const; + bool GetSubtitleVisible() const; + bool HasTeletextCache() const; + std::shared_ptr<TextCacheStruct_t> GetTeletextCache(); + int64_t GetTime() const; + int64_t GetMinTime() const; + int64_t GetMaxTime() const; + time_t GetStartTime() const; + int64_t GetTotalTime() const; + int GetVideoStream(); + int GetVideoStreamCount() const; + void GetVideoStreamInfo(int streamId, VideoStreamInfo& info) const; + int GetPrograms(std::vector<ProgramInfo>& programs); + void SetProgram(int progId); + int GetProgramsCount() const; + bool HasAudio() const; + + /*! + * \brief Get the supported menu type + * \return The supported menu type + */ + MenuType GetSupportedMenuType() const; + + bool HasVideo() const; + bool HasGame() const; + bool HasRDS() const; + bool IsCaching() const; + bool IsInMenu() const; + bool IsPaused() const; + bool IsPausedPlayback() const; + bool IsPassthrough() const; + bool IsPlaying() const; + bool IsPlayingAudio() const; + bool IsPlayingVideo() const; + bool IsPlayingGame() const; + bool IsPlayingRDS() const; + void LoadPage(int p, int sp, unsigned char* buffer); + bool OnAction(const CAction &action); + void OnNothingToQueueNotify(); + void Pause(); + bool QueueNextFile(const CFileItem &file); + void Seek(bool bPlus = true, bool bLargeStep = false, bool bChapterOverride = false); + int SeekChapter(int iChapter); + void SeekPercentage(float fPercent = 0); + bool SeekScene(bool bPlus = true); + void SeekTime(int64_t iTime = 0); + void SeekTimeRelative(int64_t iTime = 0); + void SetAudioStream(int iStream); + void SetAVDelay(float fValue = 0.0f); + void SetDynamicRangeCompression(long drc); + void SetMute(bool bOnOff); + bool SetPlayerState(const std::string& state); + void SetSubtitle(int iStream); + void SetSubTitleDelay(float fValue = 0.0f); + void SetSubtitleVisible(bool bVisible); + + /*! + * \brief Set the subtitle vertical position, + * it depends on current screen resolution + * \param value The subtitle position in pixels + * \param save If true, the value will be saved to resolution info + */ + void SetSubtitleVerticalPosition(const int value, bool save); + + void SetTime(int64_t time); + void SetTotalTime(int64_t time); + void SetVideoStream(int iStream); + void SetVolume(float volume); + void SetSpeed(float speed); + bool SupportsTempo() const; + + CVideoSettings GetVideoSettings() const; + void SetVideoSettings(CVideoSettings& settings); + + CSeekHandler& GetSeekHandler(); + const CSeekHandler& GetSeekHandler() const; + + void SetUpdateStreamDetails(); + + /*! + * \copydoc IPlayer::HasGameAgent + */ + bool HasGameAgent() const; + +private: + std::shared_ptr<const IPlayer> GetInternal() const; + std::shared_ptr<IPlayer> GetInternal(); + void CreatePlayer(const CPlayerCoreFactory &factory, const std::string &player, IPlayerCallback& callback); + void CloseFile(bool reopen = false); + + std::shared_ptr<IPlayer> m_pPlayer; + mutable CCriticalSection m_playerLock; + CSeekHandler m_seekHandler; + + // cache player state + XbmcThreads::EndTime<> m_audioStreamUpdate; + int m_iAudioStream; + XbmcThreads::EndTime<> m_videoStreamUpdate; + int m_iVideoStream; + XbmcThreads::EndTime<> m_subtitleStreamUpdate; + int m_iSubtitleStream; + + struct SNextItem + { + std::shared_ptr<CFileItem> pItem; + CPlayerOptions options = {}; + std::string playerName; + IPlayerCallback *callback = nullptr; + } m_nextItem; +}; diff --git a/xbmc/application/ApplicationPlayerCallback.cpp b/xbmc/application/ApplicationPlayerCallback.cpp new file mode 100644 index 0000000..0dbb4b9 --- /dev/null +++ b/xbmc/application/ApplicationPlayerCallback.cpp @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ApplicationPlayerCallback.h" + +#include "FileItem.h" +#include "GUIUserMessages.h" +#include "PlayListPlayer.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "application/ApplicationStackHelper.h" +#include "cores/DataCacheCore.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/StereoscopicsManager.h" +#include "interfaces/AnnouncementManager.h" +#include "interfaces/json-rpc/JSONUtils.h" +#include "interfaces/python/XBPython.h" +#include "profiles/ProfileManager.h" +#include "pvr/PVRManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/MediaSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/SaveFileStateJob.h" +#include "utils/URIUtils.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" +#include "video/VideoInfoTag.h" + +CApplicationPlayerCallback::CApplicationPlayerCallback() + : m_itemCurrentFile(new CFileItem), m_playerEvent(true, true) +{ +} + +void CApplicationPlayerCallback::OnPlayBackEnded() +{ + CLog::LogF(LOGDEBUG, "CApplicationPlayerCallback::OnPlayBackEnded"); + + CServiceBroker::GetPVRManager().OnPlaybackEnded(*m_itemCurrentFile); + + CVariant data(CVariant::VariantTypeObject); + data["end"] = true; + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnStop", + m_itemCurrentFile, data); + + CGUIMessage msg(GUI_MSG_PLAYBACK_ENDED, 0, 0); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); +} + +void CApplicationPlayerCallback::OnPlayBackStarted(const CFileItem& file) +{ + CLog::LogF(LOGDEBUG, "CApplication::OnPlayBackStarted"); + + // check if VideoPlayer should set file item stream details from its current streams + const bool isBlu_dvd_image_or_stream = (URIUtils::IsBluray(file.GetPath()) || file.IsDVDFile() || + file.IsDiscImage() || file.IsInternetStream()); + + const bool hasNoStreamDetails = + (!file.HasVideoInfoTag() || !file.GetVideoInfoTag()->HasStreamDetails()); + + if (file.GetProperty("get_stream_details_from_player").asBoolean() || + (hasNoStreamDetails && isBlu_dvd_image_or_stream)) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + appPlayer->SetUpdateStreamDetails(); + } + + auto& components = CServiceBroker::GetAppComponents(); + const auto stackHelper = components.GetComponent<CApplicationStackHelper>(); + if (stackHelper->IsPlayingISOStack() || stackHelper->IsPlayingRegularStack()) + m_itemCurrentFile.reset(new CFileItem(*stackHelper->GetRegisteredStack(file))); + else + m_itemCurrentFile.reset(new CFileItem(file)); + + /* When playing video pause any low priority jobs, they will be unpaused when playback stops. + * This should speed up player startup for files on internet filesystems (eg. webdav) and + * increase performance on low powered systems (Atom/ARM). + */ + if (file.IsVideo() || file.IsGame()) + { + CServiceBroker::GetJobManager()->PauseJobs(); + } + + CServiceBroker::GetPVRManager().OnPlaybackStarted(*m_itemCurrentFile); + stackHelper->OnPlayBackStarted(file); + + m_playerEvent.Reset(); + + CGUIMessage msg(GUI_MSG_PLAYBACK_STARTED, 0, 0); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); +} + +void CApplicationPlayerCallback::OnPlayerCloseFile(const CFileItem& file, + const CBookmark& bookmarkParam) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto stackHelper = components.GetComponent<CApplicationStackHelper>(); + + std::unique_lock<CCriticalSection> lock(stackHelper->m_critSection); + + CFileItem fileItem(file); + CBookmark bookmark = bookmarkParam; + CBookmark resumeBookmark; + bool playCountUpdate = false; + float percent = 0.0f; + + // Make sure we don't reset existing bookmark etc. on eg. player start failure + if (bookmark.timeInSeconds == 0.0) + return; + + if (stackHelper->GetRegisteredStack(fileItem) != nullptr && + stackHelper->GetRegisteredStackTotalTimeMs(fileItem) > 0) + { + // regular stack case: we have to save the bookmark on the stack + fileItem = *stackHelper->GetRegisteredStack(file); + // the bookmark coming from the player is only relative to the current part, thus needs to be corrected with these attributes (start time will be 0 for non-stackparts) + bookmark.timeInSeconds += stackHelper->GetRegisteredStackPartStartTimeMs(file) / 1000.0; + if (stackHelper->GetRegisteredStackTotalTimeMs(file) > 0) + bookmark.totalTimeInSeconds = stackHelper->GetRegisteredStackTotalTimeMs(file) / 1000.0; + bookmark.partNumber = stackHelper->GetRegisteredStackPartNumber(file); + } + + percent = bookmark.timeInSeconds / bookmark.totalTimeInSeconds * 100; + + const std::shared_ptr<CAdvancedSettings> advancedSettings = + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); + + if ((fileItem.IsAudio() && advancedSettings->m_audioPlayCountMinimumPercent > 0 && + percent >= advancedSettings->m_audioPlayCountMinimumPercent) || + (fileItem.IsVideo() && advancedSettings->m_videoPlayCountMinimumPercent > 0 && + percent >= advancedSettings->m_videoPlayCountMinimumPercent)) + { + playCountUpdate = true; + } + + if (advancedSettings->m_videoIgnorePercentAtEnd > 0 && + bookmark.totalTimeInSeconds - bookmark.timeInSeconds < + 0.01 * static_cast<double>(advancedSettings->m_videoIgnorePercentAtEnd) * + bookmark.totalTimeInSeconds) + { + resumeBookmark.timeInSeconds = -1.0; + } + else if (bookmark.timeInSeconds > advancedSettings->m_videoIgnoreSecondsAtStart) + { + resumeBookmark = bookmark; + if (stackHelper->GetRegisteredStack(file) != nullptr) + { + // also update video info tag with total time + fileItem.GetVideoInfoTag()->m_streamDetails.SetVideoDuration( + 0, resumeBookmark.totalTimeInSeconds); + } + } + else + { + resumeBookmark.timeInSeconds = 0.0; + } + + if (CServiceBroker::GetSettingsComponent() + ->GetProfileManager() + ->GetCurrentProfile() + .canWriteDatabases()) + { + CSaveFileState::DoWork(fileItem, resumeBookmark, playCountUpdate); + } +} + +void CApplicationPlayerCallback::OnPlayBackPaused() +{ +#ifdef HAS_PYTHON + CServiceBroker::GetXBPython().OnPlayBackPaused(); +#endif + + CVariant param; + param["player"]["speed"] = 0; + param["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPause", + m_itemCurrentFile, param); +} + +void CApplicationPlayerCallback::OnPlayBackResumed() +{ +#ifdef HAS_PYTHON + CServiceBroker::GetXBPython().OnPlayBackResumed(); +#endif + + CVariant param; + param["player"]["speed"] = 1; + param["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnResume", + m_itemCurrentFile, param); +} + +void CApplicationPlayerCallback::OnPlayBackStopped() +{ + CLog::LogF(LOGDEBUG, "CApplication::OnPlayBackStopped"); + + CServiceBroker::GetPVRManager().OnPlaybackStopped(*m_itemCurrentFile); + + CVariant data(CVariant::VariantTypeObject); + data["end"] = false; + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnStop", + m_itemCurrentFile, data); + + CGUIMessage msg(GUI_MSG_PLAYBACK_STOPPED, 0, 0); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); +} + +void CApplicationPlayerCallback::OnPlayBackError() +{ + //@todo Playlists can be continued by calling OnPlaybackEnded instead + // open error dialog + CGUIMessage msg(GUI_MSG_PLAYBACK_ERROR, 0, 0); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + OnPlayBackStopped(); +} + +void CApplicationPlayerCallback::OnQueueNextItem() +{ + CLog::LogF(LOGDEBUG, "CApplication::OnQueueNextItem"); + + // informs python script currently running that we are requesting the next track + // (does nothing if python is not loaded) +#ifdef HAS_PYTHON + CServiceBroker::GetXBPython().OnQueueNextItem(); // currently unimplemented +#endif + + CGUIMessage msg(GUI_MSG_QUEUE_NEXT_ITEM, 0, 0); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); +} + +void CApplicationPlayerCallback::OnPlayBackSeek(int64_t iTime, int64_t seekOffset) +{ +#ifdef HAS_PYTHON + CServiceBroker::GetXBPython().OnPlayBackSeek(static_cast<int>(iTime), + static_cast<int>(seekOffset)); +#endif + + CVariant param; + JSONRPC::CJSONUtils::MillisecondsToTimeObject(iTime, param["player"]["time"]); + JSONRPC::CJSONUtils::MillisecondsToTimeObject(seekOffset, param["player"]["seekoffset"]); + param["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(); + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + param["player"]["speed"] = static_cast<int>(appPlayer->GetPlaySpeed()); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnSeek", + m_itemCurrentFile, param); + + CDataCacheCore::GetInstance().SeekFinished(static_cast<int>(seekOffset)); +} + +void CApplicationPlayerCallback::OnPlayBackSeekChapter(int iChapter) +{ +#ifdef HAS_PYTHON + CServiceBroker::GetXBPython().OnPlayBackSeekChapter(iChapter); +#endif +} + +void CApplicationPlayerCallback::OnPlayBackSpeedChanged(int iSpeed) +{ +#ifdef HAS_PYTHON + CServiceBroker::GetXBPython().OnPlayBackSpeedChanged(iSpeed); +#endif + + CVariant param; + param["player"]["speed"] = iSpeed; + param["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnSpeedChanged", + m_itemCurrentFile, param); +} + +void CApplicationPlayerCallback::OnAVChange() +{ + CLog::LogF(LOGDEBUG, "CApplication::OnAVChange"); + + CServiceBroker::GetGUI()->GetStereoscopicsManager().OnStreamChange(); + + CGUIMessage msg(GUI_MSG_PLAYBACK_AVCHANGE, 0, 0); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + + CVariant param; + param["player"]["speed"] = 1; + param["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnAVChange", + m_itemCurrentFile, param); +} + +void CApplicationPlayerCallback::OnAVStarted(const CFileItem& file) +{ + CLog::LogF(LOGDEBUG, "CApplication::OnAVStarted"); + + CGUIMessage msg(GUI_MSG_PLAYBACK_AVSTARTED, 0, 0); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + + CVariant param; + param["player"]["speed"] = 1; + param["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnAVStart", + m_itemCurrentFile, param); +} + +void CApplicationPlayerCallback::RequestVideoSettings(const CFileItem& fileItem) +{ + CVideoDatabase dbs; + if (dbs.Open()) + { + CLog::Log(LOGDEBUG, "Loading settings for {}", CURL::GetRedacted(fileItem.GetPath())); + + // Load stored settings if they exist, otherwise use default + CVideoSettings vs; + if (!dbs.GetVideoSettings(fileItem, vs)) + vs = CMediaSettings::GetInstance().GetDefaultVideoSettings(); + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + appPlayer->SetVideoSettings(vs); + + dbs.Close(); + } +} + +void CApplicationPlayerCallback::StoreVideoSettings(const CFileItem& fileItem, + const CVideoSettings& vs) +{ + CVideoDatabase dbs; + if (dbs.Open()) + { + if (vs != CMediaSettings::GetInstance().GetDefaultVideoSettings()) + { + dbs.SetVideoSettings(fileItem, vs); + } + else + { + dbs.EraseVideoSettings(fileItem); + } + dbs.Close(); + } +} diff --git a/xbmc/application/ApplicationPlayerCallback.h b/xbmc/application/ApplicationPlayerCallback.h new file mode 100644 index 0000000..8cfd40b --- /dev/null +++ b/xbmc/application/ApplicationPlayerCallback.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "cores/IPlayerCallback.h" +#include "threads/Event.h" + +#include <memory> + +class CApplicationStackHelper; +class CFileItem; + +class CApplicationPlayerCallback : public IPlayerCallback +{ +public: + CApplicationPlayerCallback(); + + void OnPlayBackEnded() override; + void OnPlayBackStarted(const CFileItem& file) override; + void OnPlayerCloseFile(const CFileItem& file, const CBookmark& bookmark) override; + void OnPlayBackPaused() override; + void OnPlayBackResumed() override; + void OnPlayBackStopped() override; + void OnPlayBackError() override; + void OnQueueNextItem() override; + void OnPlayBackSeek(int64_t iTime, int64_t seekOffset) override; + void OnPlayBackSeekChapter(int iChapter) override; + void OnPlayBackSpeedChanged(int iSpeed) override; + void OnAVChange() override; + void OnAVStarted(const CFileItem& file) override; + void RequestVideoSettings(const CFileItem& fileItem) override; + void StoreVideoSettings(const CFileItem& fileItem, const CVideoSettings& vs) override; + +protected: + std::shared_ptr<CFileItem> m_itemCurrentFile; //!< Currently playing file + CEvent m_playerEvent; +}; diff --git a/xbmc/application/ApplicationPowerHandling.cpp b/xbmc/application/ApplicationPowerHandling.cpp new file mode 100644 index 0000000..ea182d5 --- /dev/null +++ b/xbmc/application/ApplicationPowerHandling.cpp @@ -0,0 +1,598 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ApplicationPowerHandling.h" + +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "addons/Addon.h" +#include "addons/AddonManager.h" +#include "addons/addoninfo/AddonType.h" +#include "addons/gui/GUIDialogAddonSettings.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindowManager.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "interfaces/AnnouncementManager.h" +#include "interfaces/generic/ScriptInvocationManager.h" +#include "messaging/ApplicationMessenger.h" +#include "music/MusicLibraryQueue.h" +#include "powermanagement/DPMSSupport.h" +#include "powermanagement/PowerTypes.h" +#include "profiles/ProfileManager.h" +#include "pvr/PVRManager.h" +#include "pvr/guilib/PVRGUIActionsChannels.h" +#include "pvr/guilib/PVRGUIActionsPowerManagement.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/AlarmClock.h" +#include "utils/log.h" +#include "video/VideoLibraryQueue.h" +#include "windowing/WinSystem.h" + +void CApplicationPowerHandling::ResetScreenSaver() +{ + // reset our timers + m_shutdownTimer.StartZero(); + + // screen saver timer is reset only if we're not already in screensaver or + // DPMS mode + if ((!m_screensaverActive && m_iScreenSaveLock == 0) && !m_dpmsIsActive) + ResetScreenSaverTimer(); +} + +void CApplicationPowerHandling::ResetScreenSaverTimer() +{ + m_screenSaverTimer.StartZero(); +} + +void CApplicationPowerHandling::ResetSystemIdleTimer() +{ + // reset system idle timer + m_idleTimer.StartZero(); +} + +void CApplicationPowerHandling::ResetNavigationTimer() +{ + m_navigationTimer.StartZero(); +} + +void CApplicationPowerHandling::SetRenderGUI(bool renderGUI) +{ + if (renderGUI && !m_renderGUI) + { + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (gui) + CServiceBroker::GetGUI()->GetWindowManager().MarkDirty(); + } + m_renderGUI = renderGUI; +} + +void CApplicationPowerHandling::StopScreenSaverTimer() +{ + m_screenSaverTimer.Stop(); +} + +bool CApplicationPowerHandling::ToggleDPMS(bool manual) +{ + auto winSystem = CServiceBroker::GetWinSystem(); + if (!winSystem) + return false; + + std::shared_ptr<CDPMSSupport> dpms = winSystem->GetDPMSManager(); + if (!dpms) + return false; + + if (manual || (m_dpmsIsManual == manual)) + { + if (m_dpmsIsActive) + { + m_dpmsIsActive = false; + m_dpmsIsManual = false; + SetRenderGUI(true); + CheckOSScreenSaverInhibitionSetting(); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::GUI, "OnDPMSDeactivated"); + return dpms->DisablePowerSaving(); + } + else + { + if (dpms->EnablePowerSaving(dpms->GetSupportedModes()[0])) + { + m_dpmsIsActive = true; + m_dpmsIsManual = manual; + SetRenderGUI(false); + CheckOSScreenSaverInhibitionSetting(); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::GUI, "OnDPMSActivated"); + return true; + } + } + } + return false; +} + +bool CApplicationPowerHandling::WakeUpScreenSaverAndDPMS(bool bPowerOffKeyPressed /* = false */) +{ + bool result = false; + + // First reset DPMS, if active + if (m_dpmsIsActive) + { + if (m_dpmsIsManual) + return false; + //! @todo if screensaver lock is specified but screensaver is not active + //! (DPMS came first), activate screensaver now. + ToggleDPMS(false); + ResetScreenSaverTimer(); + result = !m_screensaverActive || WakeUpScreenSaver(bPowerOffKeyPressed); + } + else if (m_screensaverActive) + result = WakeUpScreenSaver(bPowerOffKeyPressed); + + if (result) + { + // allow listeners to ignore the deactivation if it precedes a powerdown/suspend etc + CVariant data(CVariant::VariantTypeObject); + data["shuttingdown"] = bPowerOffKeyPressed; + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::GUI, + "OnScreensaverDeactivated", data); + } + + return result; +} + +bool CApplicationPowerHandling::WakeUpScreenSaver(bool bPowerOffKeyPressed /* = false */) +{ + if (m_iScreenSaveLock == 2) + return false; + + // if Screen saver is active + if (m_screensaverActive && !m_screensaverIdInUse.empty()) + { + if (m_iScreenSaveLock == 0) + { + const std::shared_ptr<CProfileManager> profileManager = + CServiceBroker::GetSettingsComponent()->GetProfileManager(); + if (profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && + (profileManager->UsingLoginScreen() || + CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_MASTERLOCK_STARTUPLOCK)) && + profileManager->GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE && + m_screensaverIdInUse != "screensaver.xbmc.builtin.dim" && + m_screensaverIdInUse != "screensaver.xbmc.builtin.black" && + m_screensaverIdInUse != "visualization") + { + m_iScreenSaveLock = 2; + CGUIMessage msg(GUI_MSG_CHECK_LOCK, 0, 0); + + CGUIWindow* pWindow = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_SCREENSAVER); + if (pWindow) + pWindow->OnMessage(msg); + } + } + if (m_iScreenSaveLock == -1) + { + m_iScreenSaveLock = 0; + return true; + } + + // disable screensaver + m_screensaverActive = false; + m_iScreenSaveLock = 0; + ResetScreenSaverTimer(); + + if (m_screensaverIdInUse == "visualization") + { + // we can just continue as usual from vis mode + return false; + } + else if (m_screensaverIdInUse == "screensaver.xbmc.builtin.dim" || + m_screensaverIdInUse == "screensaver.xbmc.builtin.black" || + m_screensaverIdInUse.empty()) + { + return true; + } + else + { // we're in screensaver window + if (m_pythonScreenSaver) + { +// What sound does a python screensaver make? +#define SCRIPT_ALARM "sssssscreensaver" +#define SCRIPT_TIMEOUT 15 // seconds + + /* FIXME: This is a hack but a proper fix is non-trivial. Basically this code + * makes sure the addon gets terminated after we've moved out of the screensaver window. + * If we don't do this, we may simply lockup. + */ + g_alarmClock.Start(SCRIPT_ALARM, SCRIPT_TIMEOUT, + "StopScript(" + m_pythonScreenSaver->LibPath() + ")", true, false); + m_pythonScreenSaver.reset(); + } + if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SCREENSAVER) + CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow(); // show the previous window + else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SLIDESHOW) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_SLIDESHOW, -1, + static_cast<void*>(new CAction(ACTION_STOP))); + } + return true; + } + else + return false; +} + +void CApplicationPowerHandling::CheckOSScreenSaverInhibitionSetting() +{ + // Kodi screen saver overrides OS one: always inhibit OS screen saver then + // except when DPMS is active (inhibiting the screen saver then might also + // disable DPMS again) + if (!m_dpmsIsActive && + !CServiceBroker::GetSettingsComponent() + ->GetSettings() + ->GetString(CSettings::SETTING_SCREENSAVER_MODE) + .empty() && + CServiceBroker::GetWinSystem()->GetOSScreenSaver()) + { + if (!m_globalScreensaverInhibitor) + { + m_globalScreensaverInhibitor = + CServiceBroker::GetWinSystem()->GetOSScreenSaver()->CreateInhibitor(); + } + } + else if (m_globalScreensaverInhibitor) + { + m_globalScreensaverInhibitor.Release(); + } +} + +void CApplicationPowerHandling::CheckScreenSaverAndDPMS() +{ + bool maybeScreensaver = true; + if (m_dpmsIsActive) + maybeScreensaver = false; + else if (m_screensaverActive) + maybeScreensaver = false; + else if (CServiceBroker::GetSettingsComponent() + ->GetSettings() + ->GetString(CSettings::SETTING_SCREENSAVER_MODE) + .empty()) + maybeScreensaver = false; + + auto winSystem = CServiceBroker::GetWinSystem(); + if (!winSystem) + return; + + std::shared_ptr<CDPMSSupport> dpms = winSystem->GetDPMSManager(); + + bool maybeDPMS = true; + if (m_dpmsIsActive) + maybeDPMS = false; + else if (!dpms || !dpms->IsSupported()) + maybeDPMS = false; + else if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_POWERMANAGEMENT_DISPLAYSOFF) <= 0) + maybeDPMS = false; + + // whether the current state of the application should be regarded as active even when there is no + // explicit user activity such as input + bool haveIdleActivity = false; + + if (m_bResetScreenSaver) + { + m_bResetScreenSaver = false; + haveIdleActivity = true; + } + + // When inhibit screensaver is enabled prevent screensaver from kicking in + if (m_bInhibitScreenSaver) + haveIdleActivity = true; + + // Are we playing a video and it is not paused? + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer && appPlayer->IsPlayingVideo() && !appPlayer->IsPaused()) + haveIdleActivity = true; + + // Are we playing some music in fullscreen vis? + else if (appPlayer && appPlayer->IsPlayingAudio() && + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION && + !CServiceBroker::GetSettingsComponent() + ->GetSettings() + ->GetString(CSettings::SETTING_MUSICPLAYER_VISUALISATION) + .empty()) + { + haveIdleActivity = true; + } + + // Handle OS screen saver state + if (haveIdleActivity && CServiceBroker::GetWinSystem()->GetOSScreenSaver()) + { + // Always inhibit OS screen saver during these kinds of activities + if (!m_screensaverInhibitor) + { + m_screensaverInhibitor = + CServiceBroker::GetWinSystem()->GetOSScreenSaver()->CreateInhibitor(); + } + } + else if (m_screensaverInhibitor) + { + m_screensaverInhibitor.Release(); + } + + // Has the screen saver window become active? + if (maybeScreensaver && + CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_SCREENSAVER)) + { + m_screensaverActive = true; + maybeScreensaver = false; + } + + if (m_screensaverActive && haveIdleActivity) + { + WakeUpScreenSaverAndDPMS(); + return; + } + + if (!maybeScreensaver && !maybeDPMS) + return; // Nothing to do. + + // See if we need to reset timer. + if (haveIdleActivity) + { + ResetScreenSaverTimer(); + return; + } + + float elapsed = m_screenSaverTimer.IsRunning() ? m_screenSaverTimer.GetElapsedSeconds() : 0.f; + + // DPMS has priority (it makes the screensaver not needed) + if (maybeDPMS && elapsed > CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_POWERMANAGEMENT_DISPLAYSOFF) * + 60) + { + ToggleDPMS(false); + WakeUpScreenSaver(); + } + else if (maybeScreensaver && + elapsed > CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_SCREENSAVER_TIME) * + 60) + { + ActivateScreenSaver(); + } +} + +// activate the screensaver. +// if forceType is true, we ignore the various conditions that can alter +// the type of screensaver displayed +void CApplicationPowerHandling::ActivateScreenSaver(bool forceType /*= false */) +{ + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer && appPlayer->IsPlayingAudio() && + settings->GetBool(CSettings::SETTING_SCREENSAVER_USEMUSICVISINSTEAD) && + !settings->GetString(CSettings::SETTING_MUSICPLAYER_VISUALISATION).empty()) + { // just activate the visualisation if user toggled the usemusicvisinstead option + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VISUALISATION); + return; + } + + m_screensaverActive = true; + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::GUI, "OnScreensaverActivated"); + + // disable screensaver lock from the login screen + m_iScreenSaveLock = + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_LOGIN_SCREEN ? 1 : 0; + + m_screensaverIdInUse = settings->GetString(CSettings::SETTING_SCREENSAVER_MODE); + + if (!forceType) + { + if (m_screensaverIdInUse == "screensaver.xbmc.builtin.dim" || + m_screensaverIdInUse == "screensaver.xbmc.builtin.black" || m_screensaverIdInUse.empty()) + { + return; + } + + // Enforce Dim for special cases. + bool bUseDim = false; + if (CServiceBroker::GetGUI()->GetWindowManager().HasModalDialog(true)) + bUseDim = true; + else if (appPlayer && appPlayer->IsPlayingVideo() && + settings->GetBool(CSettings::SETTING_SCREENSAVER_USEDIMONPAUSE)) + bUseDim = true; + else if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().IsRunningChannelScan()) + bUseDim = true; + + if (bUseDim) + m_screensaverIdInUse = "screensaver.xbmc.builtin.dim"; + } + + if (m_screensaverIdInUse == "screensaver.xbmc.builtin.dim" || + m_screensaverIdInUse == "screensaver.xbmc.builtin.black" || m_screensaverIdInUse.empty()) + { + return; + } + else if (CServiceBroker::GetAddonMgr().GetAddon(m_screensaverIdInUse, m_pythonScreenSaver, + ADDON::AddonType::SCREENSAVER, + ADDON::OnlyEnabled::CHOICE_YES)) + { + std::string libPath = m_pythonScreenSaver->LibPath(); + if (CScriptInvocationManager::GetInstance().HasLanguageInvoker(libPath)) + { + CLog::Log(LOGDEBUG, "using python screensaver add-on {}", m_screensaverIdInUse); + + // Don't allow a previously-scheduled alarm to kill our new screensaver + g_alarmClock.Stop(SCRIPT_ALARM, true); + + if (!CScriptInvocationManager::GetInstance().Stop(libPath)) + CScriptInvocationManager::GetInstance().ExecuteAsync( + libPath, + ADDON::AddonPtr(new ADDON::CAddon(dynamic_cast<ADDON::CAddon&>(*m_pythonScreenSaver)))); + return; + } + m_pythonScreenSaver.reset(); + } + + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SCREENSAVER); +} + +void CApplicationPowerHandling::InhibitScreenSaver(bool inhibit) +{ + m_bInhibitScreenSaver = inhibit; +} + +bool CApplicationPowerHandling::IsScreenSaverInhibited() const +{ + return m_bInhibitScreenSaver; +} + +// Global Idle Time in Seconds +// idle time will be reset if on any OnKey() +// int return: system Idle time in seconds! 0 is no idle! +int CApplicationPowerHandling::GlobalIdleTime() +{ + if (!m_idleTimer.IsRunning()) + m_idleTimer.StartZero(); + return (int)m_idleTimer.GetElapsedSeconds(); +} + +float CApplicationPowerHandling::NavigationIdleTime() +{ + if (!m_navigationTimer.IsRunning()) + m_navigationTimer.StartZero(); + return m_navigationTimer.GetElapsedSeconds(); +} + +void CApplicationPowerHandling::StopShutdownTimer() +{ + m_shutdownTimer.Stop(); +} + +void CApplicationPowerHandling::ResetShutdownTimers() +{ + // reset system shutdown timer + m_shutdownTimer.StartZero(); + + // delete custom shutdown timer + if (g_alarmClock.HasAlarm("shutdowntimer")) + g_alarmClock.Stop("shutdowntimer", true); +} + +void CApplicationPowerHandling::HandleShutdownMessage() +{ + switch (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNSTATE)) + { + case POWERSTATE_SHUTDOWN: + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_POWERDOWN); + break; + + case POWERSTATE_SUSPEND: + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SUSPEND); + break; + + case POWERSTATE_HIBERNATE: + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_HIBERNATE); + break; + + case POWERSTATE_QUIT: + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT); + break; + + case POWERSTATE_MINIMIZE: + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MINIMIZE); + break; + + default: + CLog::Log(LOGERROR, "{}: No valid shutdownstate matched", __FUNCTION__); + break; + } +} + +void CApplicationPowerHandling::CheckShutdown() +{ + // first check if we should reset the timer + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (!appPlayer) + return; + + if (m_bInhibitIdleShutdown || appPlayer->IsPlaying() || + appPlayer->IsPausedPlayback() // is something playing? + || CMusicLibraryQueue::GetInstance().IsRunning() || + CVideoLibraryQueue::GetInstance().IsRunning() || + CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive( + WINDOW_DIALOG_PROGRESS) // progress dialog is onscreen + || + !CServiceBroker::GetPVRManager().Get<PVR::GUI::PowerManagement>().CanSystemPowerdown(false)) + { + m_shutdownTimer.StartZero(); + return; + } + + float elapsed = m_shutdownTimer.IsRunning() ? m_shutdownTimer.GetElapsedSeconds() : 0.f; + if (elapsed > CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNTIME) * + 60) + { + // Since it is a sleep instead of a shutdown, let's set everything to reset when we wake up. + m_shutdownTimer.Stop(); + + // Sleep the box + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SHUTDOWN); + } +} + +void CApplicationPowerHandling::InhibitIdleShutdown(bool inhibit) +{ + m_bInhibitIdleShutdown = inhibit; +} + +bool CApplicationPowerHandling::IsIdleShutdownInhibited() const +{ + return m_bInhibitIdleShutdown; +} + +bool CApplicationPowerHandling::OnSettingChanged(const CSetting& setting) +{ + const std::string& settingId = setting.GetId(); + + if (settingId == CSettings::SETTING_SCREENSAVER_MODE) + { + CheckOSScreenSaverInhibitionSetting(); + } + else + return false; + + return true; +} + +bool CApplicationPowerHandling::OnSettingAction(const CSetting& setting) +{ + const std::string& settingId = setting.GetId(); + + if (settingId == CSettings::SETTING_SCREENSAVER_PREVIEW) + ActivateScreenSaver(true); + else if (settingId == CSettings::SETTING_SCREENSAVER_SETTINGS) + { + ADDON::AddonPtr addon; + if (CServiceBroker::GetAddonMgr().GetAddon( + CServiceBroker::GetSettingsComponent()->GetSettings()->GetString( + CSettings::SETTING_SCREENSAVER_MODE), + addon, ADDON::AddonType::SCREENSAVER, ADDON::OnlyEnabled::CHOICE_YES)) + CGUIDialogAddonSettings::ShowForAddon(addon); + } + else + return false; + + return true; +} diff --git a/xbmc/application/ApplicationPowerHandling.h b/xbmc/application/ApplicationPowerHandling.h new file mode 100644 index 0000000..9d78c6e --- /dev/null +++ b/xbmc/application/ApplicationPowerHandling.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "application/IApplicationComponent.h" + +#ifdef TARGET_WINDOWS +#include "powermanagement/WinIdleTimer.h" +#endif +#include "utils/Stopwatch.h" +#include "windowing/OSScreenSaver.h" + +#include <string> + +namespace ADDON +{ +class IAddon; +using AddonPtr = std::shared_ptr<IAddon>; +} // namespace ADDON + +class CApplication; +class CSetting; + +/*! + * \brief Class handling application support for screensavers, dpms and shutdown timers. + */ + +class CApplicationPowerHandling : public IApplicationComponent +{ + friend class CApplication; + +public: + bool IsInScreenSaver() const { return m_screensaverActive; } + bool IsScreenSaverInhibited() const; + void ResetScreenSaver(); + void SetScreenSaverLockFailed() { m_iScreenSaveLock = -1; } + void SetScreenSaverUnlocked() { m_iScreenSaveLock = 1; } + void StopScreenSaverTimer(); + std::string ScreensaverIdInUse() const { return m_screensaverIdInUse; } + + bool GetRenderGUI() const { return m_renderGUI; } + void SetRenderGUI(bool renderGUI); + + int GlobalIdleTime(); + void ResetSystemIdleTimer(); + bool IsIdleShutdownInhibited() const; + + void ResetShutdownTimers(); + void StopShutdownTimer(); + + void ResetNavigationTimer(); + + bool IsDPMSActive() const { return m_dpmsIsActive; } + bool ToggleDPMS(bool manual); + + // Wakes up from the screensaver and / or DPMS. Returns true if woken up. + bool WakeUpScreenSaverAndDPMS(bool bPowerOffKeyPressed = false); + + bool OnSettingChanged(const CSetting& setting); + bool OnSettingAction(const CSetting& setting); + +protected: + void ActivateScreenSaver(bool forceType = false); + void CheckOSScreenSaverInhibitionSetting(); + // Checks whether the screensaver and / or DPMS should become active. + void CheckScreenSaverAndDPMS(); + void InhibitScreenSaver(bool inhibit); + void ResetScreenSaverTimer(); + bool WakeUpScreenSaver(bool bPowerOffKeyPressed = false); + + void InhibitIdleShutdown(bool inhibit); + + /*! \brief Helper method to determine how to handle TMSG_SHUTDOWN + */ + void HandleShutdownMessage(); + void CheckShutdown(); + + float NavigationIdleTime(); + + bool m_renderGUI{false}; + + bool m_bInhibitScreenSaver = false; + bool m_bResetScreenSaver = false; + ADDON::AddonPtr + m_pythonScreenSaver; // @warning: Fallback for Python interface, for binaries not needed! + bool m_screensaverActive = false; + // -1 = failed, 0 = locked, 1 = unlocked, 2 = check in progress + int m_iScreenSaveLock = 0; + std::string m_screensaverIdInUse; + + bool m_dpmsIsActive = false; + bool m_dpmsIsManual = false; + + bool m_bInhibitIdleShutdown = false; + CStopWatch m_navigationTimer; + CStopWatch m_shutdownTimer; + +#ifdef TARGET_WINDOWS + CWinIdleTimer m_idleTimer; + CWinIdleTimer m_screenSaverTimer; +#else + CStopWatch m_idleTimer; + CStopWatch m_screenSaverTimer; +#endif + + // OS screen saver inhibitor that is always active if user selected a Kodi screen saver + KODI::WINDOWING::COSScreenSaverInhibitor m_globalScreensaverInhibitor; + // Inhibitor that is active e.g. during video playback + KODI::WINDOWING::COSScreenSaverInhibitor m_screensaverInhibitor; +}; diff --git a/xbmc/application/ApplicationSettingsHandling.cpp b/xbmc/application/ApplicationSettingsHandling.cpp new file mode 100644 index 0000000..0a2c1b1 --- /dev/null +++ b/xbmc/application/ApplicationSettingsHandling.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ApplicationSettingsHandling.h" + +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/addoninfo/AddonType.h" +#include "addons/gui/GUIDialogAddonSettings.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "application/ApplicationPowerHandling.h" +#include "application/ApplicationSkinHandling.h" +#include "application/ApplicationVolumeHandling.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "messaging/ApplicationMessenger.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingsManager.h" +#if defined(TARGET_DARWIN_OSX) +#include "utils/StringUtils.h" +#endif + +namespace +{ +bool IsPlaying(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return data ? static_cast<CApplicationPlayer*>(data)->IsPlaying() : false; +} +} // namespace + +void CApplicationSettingsHandling::RegisterSettings() +{ + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + CSettingsManager* settingsMgr = settings->GetSettingsManager(); + + settingsMgr->RegisterSettingsHandler(this); + + settingsMgr->RegisterCallback(this, {CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH, + CSettings::SETTING_LOOKANDFEEL_SKIN, + CSettings::SETTING_LOOKANDFEEL_SKINSETTINGS, + CSettings::SETTING_LOOKANDFEEL_FONT, + CSettings::SETTING_LOOKANDFEEL_SKINTHEME, + CSettings::SETTING_LOOKANDFEEL_SKINCOLORS, + CSettings::SETTING_LOOKANDFEEL_SKINZOOM, + CSettings::SETTING_MUSICPLAYER_REPLAYGAINPREAMP, + CSettings::SETTING_MUSICPLAYER_REPLAYGAINNOGAINPREAMP, + CSettings::SETTING_MUSICPLAYER_REPLAYGAINTYPE, + CSettings::SETTING_MUSICPLAYER_REPLAYGAINAVOIDCLIPPING, + CSettings::SETTING_SCRAPERS_MUSICVIDEOSDEFAULT, + CSettings::SETTING_SCREENSAVER_MODE, + CSettings::SETTING_SCREENSAVER_PREVIEW, + CSettings::SETTING_SCREENSAVER_SETTINGS, + CSettings::SETTING_AUDIOCDS_SETTINGS, + CSettings::SETTING_VIDEOSCREEN_GUICALIBRATION, + CSettings::SETTING_VIDEOSCREEN_TESTPATTERN, + CSettings::SETTING_VIDEOPLAYER_USEMEDIACODEC, + CSettings::SETTING_VIDEOPLAYER_USEMEDIACODECSURFACE, + CSettings::SETTING_AUDIOOUTPUT_VOLUMESTEPS, + CSettings::SETTING_SOURCE_VIDEOS, + CSettings::SETTING_SOURCE_MUSIC, + CSettings::SETTING_SOURCE_PICTURES, + CSettings::SETTING_VIDEOSCREEN_FAKEFULLSCREEN}); + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (!appPlayer) + return; + + settingsMgr->RegisterCallback( + &appPlayer->GetSeekHandler(), + {CSettings::SETTING_VIDEOPLAYER_SEEKDELAY, CSettings::SETTING_VIDEOPLAYER_SEEKSTEPS, + CSettings::SETTING_MUSICPLAYER_SEEKDELAY, CSettings::SETTING_MUSICPLAYER_SEEKSTEPS}); + + settingsMgr->AddDynamicCondition("isplaying", IsPlaying, appPlayer.get()); + + settings->RegisterSubSettings(this); +} + +void CApplicationSettingsHandling::UnregisterSettings() +{ + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + CSettingsManager* settingsMgr = settings->GetSettingsManager(); + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (!appPlayer) + return; + + settings->UnregisterSubSettings(this); + settingsMgr->RemoveDynamicCondition("isplaying"); + settingsMgr->UnregisterCallback(&appPlayer->GetSeekHandler()); + settingsMgr->UnregisterCallback(this); + settingsMgr->UnregisterSettingsHandler(this); +} + +void CApplicationSettingsHandling::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (!setting) + return; + + auto& components = CServiceBroker::GetAppComponents(); + const auto appSkin = components.GetComponent<CApplicationSkinHandling>(); + if (appSkin->OnSettingChanged(*setting)) + return; + + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + if (appVolume->OnSettingChanged(*setting)) + return; + + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + if (appPower->OnSettingChanged(*setting)) + return; + + const std::string& settingId = setting->GetId(); + + if (settingId == CSettings::SETTING_VIDEOSCREEN_FAKEFULLSCREEN) + { + if (CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenRoot()) + CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution( + CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution(), true); + } + else if (settingId == CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH) + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_RESTART); + } +} + +void CApplicationSettingsHandling::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + if (!setting) + return; + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + if (appPower->OnSettingAction(*setting)) + return; + + const std::string& settingId = setting->GetId(); + if (settingId == CSettings::SETTING_LOOKANDFEEL_SKINSETTINGS) + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SKIN_SETTINGS); + else if (settingId == CSettings::SETTING_AUDIOCDS_SETTINGS) + { + ADDON::AddonPtr addon; + if (CServiceBroker::GetAddonMgr().GetAddon( + CServiceBroker::GetSettingsComponent()->GetSettings()->GetString( + CSettings::SETTING_AUDIOCDS_ENCODER), + addon, ADDON::AddonType::AUDIOENCODER, ADDON::OnlyEnabled::CHOICE_YES)) + CGUIDialogAddonSettings::ShowForAddon(addon); + } + else if (settingId == CSettings::SETTING_VIDEOSCREEN_GUICALIBRATION) + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SCREEN_CALIBRATION); + else if (settingId == CSettings::SETTING_SOURCE_VIDEOS) + { + std::vector<std::string> params{"library://video/files.xml", "return"}; + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_NAV, params); + } + else if (settingId == CSettings::SETTING_SOURCE_MUSIC) + { + std::vector<std::string> params{"library://music/files.xml", "return"}; + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_NAV, params); + } + else if (settingId == CSettings::SETTING_SOURCE_PICTURES) + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_PICTURES); +} + +bool CApplicationSettingsHandling::OnSettingUpdate(const std::shared_ptr<CSetting>& setting, + const char* oldSettingId, + const TiXmlNode* oldSettingNode) +{ + if (!setting) + return false; + +#if defined(TARGET_DARWIN_OSX) + if (setting->GetId() == CSettings::SETTING_AUDIOOUTPUT_AUDIODEVICE) + { + std::shared_ptr<CSettingString> audioDevice = std::static_pointer_cast<CSettingString>(setting); + // Gotham and older didn't enumerate audio devices per stream on osx + // add stream0 per default which should be ok for all old settings. + if (!StringUtils::EqualsNoCase(audioDevice->GetValue(), "DARWINOSX:default") && + StringUtils::FindWords(audioDevice->GetValue().c_str(), ":stream") == std::string::npos) + { + std::string newSetting = audioDevice->GetValue(); + newSetting += ":stream0"; + return audioDevice->SetValue(newSetting); + } + } +#endif + + return false; +} + +bool CApplicationSettingsHandling::Load(const TiXmlNode* settings) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + return appVolume->Load(settings); +} + +bool CApplicationSettingsHandling::Save(TiXmlNode* settings) const +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + return appVolume->Save(settings); +} diff --git a/xbmc/application/ApplicationSettingsHandling.h b/xbmc/application/ApplicationSettingsHandling.h new file mode 100644 index 0000000..5dedd8f --- /dev/null +++ b/xbmc/application/ApplicationSettingsHandling.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/ISubSettings.h" +#include "settings/lib/ISettingCallback.h" +#include "settings/lib/ISettingsHandler.h" + +/*! + * \brief Class handling application support for settings. + */ + +class CApplicationSettingsHandling : public ISettingCallback, + public ISettingsHandler, + public ISubSettings +{ +protected: + void RegisterSettings(); + void UnregisterSettings(); + + bool Load(const TiXmlNode* settings) override; + bool Save(TiXmlNode* settings) const override; + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + bool OnSettingUpdate(const std::shared_ptr<CSetting>& setting, + const char* oldSettingId, + const TiXmlNode* oldSettingNode) override; +}; diff --git a/xbmc/application/ApplicationSkinHandling.cpp b/xbmc/application/ApplicationSkinHandling.cpp new file mode 100644 index 0000000..edca76b --- /dev/null +++ b/xbmc/application/ApplicationSkinHandling.cpp @@ -0,0 +1,514 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ApplicationSkinHandling.h" + +#include "FileItem.h" +#include "GUIInfoManager.h" +#include "GUILargeTextureManager.h" +#include "GUIUserMessages.h" +#include "PlayListPlayer.h" +#include "ServiceBroker.h" +#include "TextureCache.h" +#include "addons/AddonManager.h" +#include "addons/AddonVersion.h" +#include "addons/Skin.h" +#include "addons/addoninfo/AddonType.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "dialogs/GUIDialogButtonMenu.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "dialogs/GUIDialogSubMenu.h" +#include "filesystem/Directory.h" +#include "filesystem/DirectoryCache.h" +#include "guilib/GUIAudioManager.h" +#include "guilib/GUIColorManager.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIFontManager.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/StereoscopicsManager.h" +#include "messaging/ApplicationMessenger.h" +#include "messaging/helpers/DialogHelper.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/SkinSettings.h" +#include "settings/lib/Setting.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" +#include "video/dialogs/GUIDialogFullScreenInfo.h" + +using namespace KODI::MESSAGING; + +CApplicationSkinHandling::CApplicationSkinHandling(IMsgTargetCallback* msgCb, + IWindowManagerCallback* wCb, + bool& bInitializing) + : m_msgCb(msgCb), m_wCb(wCb), m_bInitializing(bInitializing) +{ +} + +bool CApplicationSkinHandling::LoadSkin(const std::string& skinID) +{ + std::shared_ptr<ADDON::CSkinInfo> skin; + { + ADDON::AddonPtr addon; + if (!CServiceBroker::GetAddonMgr().GetAddon(skinID, addon, ADDON::AddonType::SKIN, + ADDON::OnlyEnabled::CHOICE_YES)) + return false; + skin = std::static_pointer_cast<ADDON::CSkinInfo>(addon); + } + + // store player and rendering state + bool bPreviousPlayingState = false; + + enum class RENDERING_STATE + { + NONE, + VIDEO, + GAME, + } previousRenderingState = RENDERING_STATE::NONE; + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer && appPlayer->IsPlayingVideo()) + { + bPreviousPlayingState = !appPlayer->IsPausedPlayback(); + if (bPreviousPlayingState) + appPlayer->Pause(); + appPlayer->FlushRenderer(); + if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO) + { + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME); + previousRenderingState = RENDERING_STATE::VIDEO; + } + else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == + WINDOW_FULLSCREEN_GAME) + { + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME); + previousRenderingState = RENDERING_STATE::GAME; + } + } + + std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext()); + + // store current active window with its focused control + int currentWindowID = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); + int currentFocusedControlID = -1; + if (currentWindowID != WINDOW_INVALID) + { + CGUIWindow* pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(currentWindowID); + if (pWindow) + currentFocusedControlID = pWindow->GetFocusedControlID(); + } + + UnloadSkin(); + + skin->Start(); + + // migrate any skin-specific settings that are still stored in guisettings.xml + CSkinSettings::GetInstance().MigrateSettings(skin); + + // check if the skin has been properly loaded and if it has a Home.xml + if (!skin->HasSkinFile("Home.xml")) + { + CLog::Log(LOGERROR, "failed to load requested skin '{}'", skin->ID()); + return false; + } + + CLog::Log(LOGINFO, " load skin from: {} (version: {})", skin->Path(), + skin->Version().asString()); + g_SkinInfo = skin; + + CLog::Log(LOGINFO, " load fonts for skin..."); + CServiceBroker::GetWinSystem()->GetGfxContext().SetMediaDir(skin->Path()); + g_directoryCache.ClearSubPaths(skin->Path()); + + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + CServiceBroker::GetGUI()->GetColorManager().Load( + settings->GetString(CSettings::SETTING_LOOKANDFEEL_SKINCOLORS)); + + g_SkinInfo->LoadIncludes(); + + g_fontManager.LoadFonts(settings->GetString(CSettings::SETTING_LOOKANDFEEL_FONT)); + + // load in the skin strings + std::string langPath = URIUtils::AddFileToFolder(skin->Path(), "language"); + URIUtils::AddSlashAtEnd(langPath); + + g_localizeStrings.LoadSkinStrings(langPath, + settings->GetString(CSettings::SETTING_LOCALE_LANGUAGE)); + g_SkinInfo->LoadTimers(); + + const auto start = std::chrono::steady_clock::now(); + + CLog::Log(LOGINFO, " load new skin..."); + + // Load custom windows + LoadCustomWindows(); + + const auto end = std::chrono::steady_clock::now(); + std::chrono::duration<double, std::milli> duration = end - start; + + CLog::Log(LOGDEBUG, "Load Skin XML: {:.2f} ms", duration.count()); + + CLog::Log(LOGINFO, " initialize new skin..."); + CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(m_msgCb); + CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(&CServiceBroker::GetPlaylistPlayer()); + CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(&g_fontManager); + CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget( + &CServiceBroker::GetGUI()->GetStereoscopicsManager()); + CServiceBroker::GetGUI()->GetWindowManager().SetCallback(*m_wCb); + + //@todo should be done by GUIComponents + CServiceBroker::GetGUI()->GetWindowManager().Initialize(); + CServiceBroker::GetGUI()->GetAudioManager().Enable(true); + CServiceBroker::GetGUI()->GetAudioManager().Load(); + CServiceBroker::GetTextureCache()->Initialize(); + + if (g_SkinInfo->HasSkinFile("DialogFullScreenInfo.xml")) + CServiceBroker::GetGUI()->GetWindowManager().Add(new CGUIDialogFullScreenInfo); + + CLog::Log(LOGINFO, " skin loaded..."); + + // leave the graphics lock + lock.unlock(); + + // restore active window + if (currentWindowID != WINDOW_INVALID) + { + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(currentWindowID); + if (currentFocusedControlID != -1) + { + CGUIWindow* pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(currentWindowID); + if (pWindow && pWindow->HasSaveLastControl()) + { + CGUIMessage msg(GUI_MSG_SETFOCUS, currentWindowID, currentFocusedControlID, 0); + pWindow->OnMessage(msg); + } + } + } + + // restore player and rendering state + if (appPlayer && appPlayer->IsPlayingVideo()) + { + if (bPreviousPlayingState) + appPlayer->Pause(); + + switch (previousRenderingState) + { + case RENDERING_STATE::VIDEO: + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_FULLSCREEN_VIDEO); + break; + case RENDERING_STATE::GAME: + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_FULLSCREEN_GAME); + break; + default: + break; + } + } + + return true; +} + +void CApplicationSkinHandling::UnloadSkin() +{ + if (g_SkinInfo != nullptr && m_saveSkinOnUnloading) + g_SkinInfo->SaveSettings(); + else if (!m_saveSkinOnUnloading) + m_saveSkinOnUnloading = true; + + if (g_SkinInfo) + g_SkinInfo->Unload(); + + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (gui) + { + gui->GetAudioManager().Enable(false); + + gui->GetWindowManager().DeInitialize(); + CServiceBroker::GetTextureCache()->Deinitialize(); + + // remove the skin-dependent window + gui->GetWindowManager().Delete(WINDOW_DIALOG_FULLSCREEN_INFO); + + gui->GetTextureManager().Cleanup(); + gui->GetLargeTextureManager().CleanupUnusedImages(true); + + g_fontManager.Clear(); + + gui->GetColorManager().Clear(); + + gui->GetInfoManager().Clear(); + } + + // The g_SkinInfo shared_ptr ought to be reset here + // but there are too many places it's used without checking for nullptr + // and as a result a race condition on exit can cause a crash. + CLog::Log(LOGINFO, "Unloaded skin"); +} + +bool CApplicationSkinHandling::LoadCustomWindows() +{ + // Start from wherever home.xml is + std::vector<std::string> vecSkinPath; + g_SkinInfo->GetSkinPaths(vecSkinPath); + + for (const auto& skinPath : vecSkinPath) + { + CLog::Log(LOGINFO, "Loading custom window XMLs from skin path {}", skinPath); + + CFileItemList items; + if (XFILE::CDirectory::GetDirectory(skinPath, items, ".xml", XFILE::DIR_FLAG_NO_FILE_DIRS)) + { + for (const auto& item : items) + { + if (item->m_bIsFolder) + continue; + + std::string skinFile = URIUtils::GetFileName(item->GetPath()); + if (StringUtils::StartsWithNoCase(skinFile, "custom")) + { + CXBMCTinyXML xmlDoc; + if (!xmlDoc.LoadFile(item->GetPath())) + { + CLog::Log(LOGERROR, "Unable to load custom window XML {}. Line {}\n{}", item->GetPath(), + xmlDoc.ErrorRow(), xmlDoc.ErrorDesc()); + continue; + } + + // Root element should be <window> + TiXmlElement* pRootElement = xmlDoc.RootElement(); + std::string strValue = pRootElement->Value(); + if (!StringUtils::EqualsNoCase(strValue, "window")) + { + CLog::Log(LOGERROR, "No <window> root element found for custom window in {}", skinFile); + continue; + } + + int id = WINDOW_INVALID; + + // Read the type attribute or element to get the window type to create + // If no type is specified, create a CGUIWindow as default + std::string strType; + if (pRootElement->Attribute("type")) + strType = pRootElement->Attribute("type"); + else + { + const TiXmlNode* pType = pRootElement->FirstChild("type"); + if (pType && pType->FirstChild()) + strType = pType->FirstChild()->Value(); + } + + // Read the id attribute or element to get the window id + if (!pRootElement->Attribute("id", &id)) + { + const TiXmlNode* pType = pRootElement->FirstChild("id"); + if (pType && pType->FirstChild()) + id = atol(pType->FirstChild()->Value()); + } + + int windowId = id + WINDOW_HOME; + if (id == WINDOW_INVALID || + CServiceBroker::GetGUI()->GetWindowManager().GetWindow(windowId)) + { + // No id specified or id already in use + CLog::Log(LOGERROR, "No id specified or id already in use for custom window in {}", + skinFile); + continue; + } + + CGUIWindow* pWindow = nullptr; + bool hasVisibleCondition = false; + + if (StringUtils::EqualsNoCase(strType, "dialog")) + { + DialogModalityType modality = DialogModalityType::MODAL; + hasVisibleCondition = pRootElement->FirstChildElement("visible") != nullptr; + // By default dialogs that have visible conditions are considered modeless unless explicitly + // set to "modal" by the skinner using the "modality" attribute in the root XML element of the window + if (hasVisibleCondition && + (!pRootElement->Attribute("modality") || + !StringUtils::EqualsNoCase(pRootElement->Attribute("modality"), "modal"))) + modality = DialogModalityType::MODELESS; + + pWindow = new CGUIDialog(windowId, skinFile, modality); + } + else if (StringUtils::EqualsNoCase(strType, "submenu")) + { + pWindow = new CGUIDialogSubMenu(windowId, skinFile); + } + else if (StringUtils::EqualsNoCase(strType, "buttonmenu")) + { + pWindow = new CGUIDialogButtonMenu(windowId, skinFile); + } + else + { + pWindow = new CGUIWindow(windowId, skinFile); + } + + if (!pWindow) + { + CLog::Log(LOGERROR, "Failed to create custom window from {}", skinFile); + continue; + } + + pWindow->SetCustom(true); + + // Determining whether our custom dialog is modeless (visible condition is present) + // will be done on load. Therefore we need to initialize the custom dialog on gui init. + pWindow->SetLoadType(hasVisibleCondition ? CGUIWindow::LOAD_ON_GUI_INIT + : CGUIWindow::KEEP_IN_MEMORY); + + CServiceBroker::GetGUI()->GetWindowManager().AddCustomWindow(pWindow); + } + } + } + } + return true; +} + +void CApplicationSkinHandling::ReloadSkin(bool confirm) +{ + if (!g_SkinInfo || m_bInitializing) + return; // Don't allow reload before skin is loaded by system + + std::string oldSkin = g_SkinInfo->ID(); + + CGUIMessage msg(GUI_MSG_LOAD_SKIN, -1, + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow()); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + std::string newSkin = settings->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN); + if (LoadSkin(newSkin)) + { + /* The Reset() or SetString() below will cause recursion, so the m_confirmSkinChange boolean is set so as to not prompt the + user as to whether they want to keep the current skin. */ + if (confirm && m_confirmSkinChange) + { + if (HELPERS::ShowYesNoDialogText(CVariant{13123}, CVariant{13111}, CVariant{""}, CVariant{""}, + 10000) != HELPERS::DialogResponse::CHOICE_YES) + { + m_confirmSkinChange = false; + settings->SetString(CSettings::SETTING_LOOKANDFEEL_SKIN, oldSkin); + } + else + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_STARTUP_ANIM); + } + } + else + { + // skin failed to load - we revert to the default only if we didn't fail loading the default + auto setting = settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKIN); + if (!setting) + { + CLog::Log(LOGFATAL, "Failed to load setting for: {}", CSettings::SETTING_LOOKANDFEEL_SKIN); + return; + } + + std::string defaultSkin = std::static_pointer_cast<CSettingString>(setting)->GetDefault(); + if (newSkin != defaultSkin) + { + m_confirmSkinChange = false; + setting->Reset(); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(24102), + g_localizeStrings.Get(24103)); + } + } + m_confirmSkinChange = true; +} + +bool CApplicationSkinHandling::OnSettingChanged(const CSetting& setting) +{ + const std::string& settingId = setting.GetId(); + + if (settingId == CSettings::SETTING_LOOKANDFEEL_SKIN || + settingId == CSettings::SETTING_LOOKANDFEEL_FONT || + settingId == CSettings::SETTING_LOOKANDFEEL_SKINTHEME || + settingId == CSettings::SETTING_LOOKANDFEEL_SKINCOLORS) + { + // check if we should ignore this change event due to changing skins in which case we have to + // change several settings and each one of them could lead to a complete skin reload which would + // result in multiple skin reloads. Therefore we manually specify to ignore specific settings + // which are going to be changed. + if (m_ignoreSkinSettingChanges) + return true; + + // if the skin changes and the current color/theme/font is not the default one, reset + // the it to the default value + if (settingId == CSettings::SETTING_LOOKANDFEEL_SKIN) + { + const std::shared_ptr<CSettings> settings = + CServiceBroker::GetSettingsComponent()->GetSettings(); + SettingPtr skinRelatedSetting = + settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKINCOLORS); + if (!skinRelatedSetting->IsDefault()) + { + m_ignoreSkinSettingChanges = true; + skinRelatedSetting->Reset(); + } + + skinRelatedSetting = settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKINTHEME); + if (!skinRelatedSetting->IsDefault()) + { + m_ignoreSkinSettingChanges = true; + skinRelatedSetting->Reset(); + } + + skinRelatedSetting = settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_FONT); + if (!skinRelatedSetting->IsDefault()) + { + m_ignoreSkinSettingChanges = true; + skinRelatedSetting->Reset(); + } + } + else if (settingId == CSettings::SETTING_LOOKANDFEEL_SKINTHEME) + { + std::shared_ptr<CSettingString> skinColorsSetting = std::static_pointer_cast<CSettingString>( + CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting( + CSettings::SETTING_LOOKANDFEEL_SKINCOLORS)); + m_ignoreSkinSettingChanges = true; + + // we also need to adjust the skin color setting + std::string colorTheme = static_cast<const CSettingString&>(setting).GetValue(); + URIUtils::RemoveExtension(colorTheme); + if (setting.IsDefault() || StringUtils::EqualsNoCase(colorTheme, "Textures")) + skinColorsSetting->Reset(); + else + skinColorsSetting->SetValue(colorTheme); + } + + m_ignoreSkinSettingChanges = false; + + if (g_SkinInfo) + { + // now we can finally reload skins + std::string builtin("ReloadSkin"); + if (settingId == CSettings::SETTING_LOOKANDFEEL_SKIN && m_confirmSkinChange) + builtin += "(confirm)"; + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, builtin); + } + } + else if (settingId == CSettings::SETTING_LOOKANDFEEL_SKINZOOM) + { + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_WINDOW_RESIZE); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + } + else + return false; + + return true; +} + +void CApplicationSkinHandling::ProcessSkin() const +{ + if (g_SkinInfo != nullptr) + g_SkinInfo->ProcessTimers(); +} diff --git a/xbmc/application/ApplicationSkinHandling.h b/xbmc/application/ApplicationSkinHandling.h new file mode 100644 index 0000000..fc5c3d0 --- /dev/null +++ b/xbmc/application/ApplicationSkinHandling.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "application/IApplicationComponent.h" + +#include <string> + +class CApplication; +class CSetting; +class IMsgTargetCallback; +class IWindowManagerCallback; + +/*! + * \brief Class handling application support for skin management. + */ +class CApplicationSkinHandling : public IApplicationComponent +{ + friend class CApplication; + +public: + CApplicationSkinHandling(IMsgTargetCallback* msgCb, + IWindowManagerCallback* wCb, + bool& bInitializing); + + void UnloadSkin(); + + bool OnSettingChanged(const CSetting& setting); + void ReloadSkin(bool confirm = false); + +protected: + bool LoadSkin(const std::string& skinID); + bool LoadCustomWindows(); + + /*! + * \brief Called by the application main/render thread for processing + * operations belonging to the skin. + */ + void ProcessSkin() const; + + bool m_saveSkinOnUnloading = true; + bool m_confirmSkinChange = true; + bool m_ignoreSkinSettingChanges = false; + IMsgTargetCallback* m_msgCb; + IWindowManagerCallback* m_wCb; + bool& m_bInitializing; +}; diff --git a/xbmc/application/ApplicationStackHelper.cpp b/xbmc/application/ApplicationStackHelper.cpp new file mode 100644 index 0000000..a1e8a0e --- /dev/null +++ b/xbmc/application/ApplicationStackHelper.cpp @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ApplicationStackHelper.h" + +#include "FileItem.h" +#include "URL.h" +#include "Util.h" +#include "cores/VideoPlayer/DVDFileInfo.h" +#include "filesystem/StackDirectory.h" +#include "utils/URIUtils.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" + +#include <utility> + +using namespace XFILE; + +CApplicationStackHelper::CApplicationStackHelper(void) + : m_currentStack(new CFileItemList) +{ +} + +void CApplicationStackHelper::Clear() +{ + m_currentStackPosition = 0; + m_currentStack->Clear(); +} + +void CApplicationStackHelper::OnPlayBackStarted(const CFileItem& item) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + // time to clean up stack map + if (!HasRegisteredStack(item)) + m_stackmap.clear(); + else + { + auto stack = GetRegisteredStack(item); + Stackmap::iterator itr = m_stackmap.begin(); + while (itr != m_stackmap.end()) + { + if (itr->second->m_pStack != stack) + { + itr = m_stackmap.erase(itr); + } + else + { + ++itr; + } + } + } +} + +bool CApplicationStackHelper::InitializeStack(const CFileItem & item) +{ + if (!item.IsStack()) + return false; + + auto stack = std::make_shared<CFileItem>(item); + + Clear(); + // read and determine kind of stack + CStackDirectory dir; + if (!dir.GetDirectory(item.GetURL(), *m_currentStack) || m_currentStack->IsEmpty()) + return false; + for (int i = 0; i < m_currentStack->Size(); i++) + { + // keep cross-references between stack parts and the stack + SetRegisteredStack(GetStackPartFileItem(i), stack); + SetRegisteredStackPartNumber(GetStackPartFileItem(i), i); + } + m_currentStackIsDiscImageStack = CFileItem(CStackDirectory::GetFirstStackedFile(item.GetPath()), false).IsDiscImage(); + + return true; +} + +int CApplicationStackHelper::InitializeStackStartPartAndOffset(const CFileItem& item) +{ + CVideoDatabase dbs; + int64_t startoffset = 0; + + // case 1: stacked ISOs + if (m_currentStackIsDiscImageStack) + { + // first assume values passed to the stack + int selectedFile = item.m_lStartPartNumber; + startoffset = item.GetStartOffset(); + + // check if we instructed the stack to resume from default + if (startoffset == STARTOFFSET_RESUME) // selected file is not specified, pick the 'last' resume point + { + if (dbs.Open()) + { + CBookmark bookmark; + std::string path = item.GetPath(); + if (item.HasProperty("original_listitem_url") && URIUtils::IsPlugin(item.GetProperty("original_listitem_url").asString())) + path = item.GetProperty("original_listitem_url").asString(); + if (dbs.GetResumeBookMark(path, bookmark)) + { + startoffset = CUtil::ConvertSecsToMilliSecs(bookmark.timeInSeconds); + selectedFile = bookmark.partNumber; + } + dbs.Close(); + } + else + CLog::LogF(LOGERROR, "Cannot open VideoDatabase"); + } + + // make sure that the selected part is within the boundaries + if (selectedFile <= 0) + { + CLog::LogF(LOGWARNING, "Selected part {} out of range, playing part 1", selectedFile); + selectedFile = 1; + } + else if (selectedFile > m_currentStack->Size()) + { + CLog::LogF(LOGWARNING, "Selected part {} out of range, playing part {}", selectedFile, + m_currentStack->Size()); + selectedFile = m_currentStack->Size(); + } + + // set startoffset in selected item, track stack item for updating purposes, and finally play disc part + m_currentStackPosition = selectedFile - 1; + startoffset = startoffset > 0 ? STARTOFFSET_RESUME : 0; + } + // case 2: all other stacks + else + { + // see if we have the info in the database + //! @todo If user changes the time speed (FPS via framerate conversion stuff) + //! then these times will be wrong. + //! Also, this is really just a hack for the slow load up times we have + //! A much better solution is a fast reader of FPS and fileLength + //! that we can use on a file to get it's time. + std::vector<uint64_t> times; + bool haveTimes(false); + + if (dbs.Open()) + { + haveTimes = dbs.GetStackTimes(item.GetPath(), times); + dbs.Close(); + } + + // calculate the total time of the stack + uint64_t totalTimeMs = 0; + for (int i = 0; i < m_currentStack->Size(); i++) + { + if (haveTimes) + { + // set end time in every part + GetStackPartFileItem(i).SetEndOffset(times[i]); + } + else + { + int duration; + if (!CDVDFileInfo::GetFileDuration(GetStackPartFileItem(i).GetPath(), duration)) + { + m_currentStack->Clear(); + return false; + } + totalTimeMs += duration; + // set end time in every part + GetStackPartFileItem(i).SetEndOffset(totalTimeMs); + times.push_back(totalTimeMs); + } + // set start time in every part + SetRegisteredStackPartStartTimeMs(GetStackPartFileItem(i), GetStackPartStartTimeMs(i)); + } + // set total time in every part + totalTimeMs = GetStackTotalTimeMs(); + for (int i = 0; i < m_currentStack->Size(); i++) + SetRegisteredStackTotalTimeMs(GetStackPartFileItem(i), totalTimeMs); + + uint64_t msecs = item.GetStartOffset(); + + if (!haveTimes || item.GetStartOffset() == STARTOFFSET_RESUME) + { + if (dbs.Open()) + { + // have our times now, so update the dB + if (!haveTimes && !times.empty()) + dbs.SetStackTimes(item.GetPath(), times); + + if (item.GetStartOffset() == STARTOFFSET_RESUME) + { + // can only resume seek here, not dvdstate + CBookmark bookmark; + std::string path = item.GetPath(); + if (item.HasProperty("original_listitem_url") && URIUtils::IsPlugin(item.GetProperty("original_listitem_url").asString())) + path = item.GetProperty("original_listitem_url").asString(); + if (dbs.GetResumeBookMark(path, bookmark)) + msecs = static_cast<uint64_t>(bookmark.timeInSeconds * 1000); + else + msecs = 0; + } + dbs.Close(); + } + } + + m_currentStackPosition = GetStackPartNumberAtTimeMs(msecs); + startoffset = msecs - GetStackPartStartTimeMs(m_currentStackPosition); + } + return startoffset; +} + +bool CApplicationStackHelper::IsPlayingISOStack() const +{ + return m_currentStack->Size() > 0 && m_currentStackIsDiscImageStack; +} + +bool CApplicationStackHelper::IsPlayingRegularStack() const +{ + return m_currentStack->Size() > 0 && !m_currentStackIsDiscImageStack; +} + +bool CApplicationStackHelper::HasNextStackPartFileItem() const +{ + return m_currentStackPosition < m_currentStack->Size() - 1; +} + +uint64_t CApplicationStackHelper::GetStackPartEndTimeMs(int partNumber) const +{ + return GetStackPartFileItem(partNumber).GetEndOffset(); +} + +uint64_t CApplicationStackHelper::GetStackTotalTimeMs() const +{ + return GetStackPartEndTimeMs(m_currentStack->Size() - 1); +} + +int CApplicationStackHelper::GetStackPartNumberAtTimeMs(uint64_t msecs) +{ + if (msecs > 0) + { + // work out where to seek to + for (int partNumber = 0; partNumber < m_currentStack->Size(); partNumber++) + { + if (msecs < GetStackPartEndTimeMs(partNumber)) + return partNumber; + } + } + return 0; +} + +void CApplicationStackHelper::ClearAllRegisteredStackInformation() +{ + m_stackmap.clear(); +} + +std::shared_ptr<const CFileItem> CApplicationStackHelper::GetRegisteredStack( + const CFileItem& item) const +{ + return GetStackPartInformation(item.GetPath())->m_pStack; +} + +bool CApplicationStackHelper::HasRegisteredStack(const CFileItem& item) const +{ + const auto it = m_stackmap.find(item.GetPath()); + return it != m_stackmap.end() && it->second != nullptr; +} + +void CApplicationStackHelper::SetRegisteredStack(const CFileItem& item, + std::shared_ptr<CFileItem> stackItem) +{ + GetStackPartInformation(item.GetPath())->m_pStack = std::move(stackItem); +} + +CFileItem& CApplicationStackHelper::GetStackPartFileItem(int partNumber) +{ + return *(*m_currentStack)[partNumber]; +} + +const CFileItem& CApplicationStackHelper::GetStackPartFileItem(int partNumber) const +{ + return *(*m_currentStack)[partNumber]; +} + +int CApplicationStackHelper::GetRegisteredStackPartNumber(const CFileItem& item) +{ + return GetStackPartInformation(item.GetPath())->m_lStackPartNumber; +} + +void CApplicationStackHelper::SetRegisteredStackPartNumber(const CFileItem& item, int partNumber) +{ + GetStackPartInformation(item.GetPath())->m_lStackPartNumber = partNumber; +} + +uint64_t CApplicationStackHelper::GetRegisteredStackPartStartTimeMs(const CFileItem& item) const +{ + return GetStackPartInformation(item.GetPath())->m_lStackPartStartTimeMs; +} + +void CApplicationStackHelper::SetRegisteredStackPartStartTimeMs(const CFileItem& item, uint64_t startTime) +{ + GetStackPartInformation(item.GetPath())->m_lStackPartStartTimeMs = startTime; +} + +uint64_t CApplicationStackHelper::GetRegisteredStackTotalTimeMs(const CFileItem& item) const +{ + return GetStackPartInformation(item.GetPath())->m_lStackTotalTimeMs; +} + +void CApplicationStackHelper::SetRegisteredStackTotalTimeMs(const CFileItem& item, uint64_t totalTime) +{ + GetStackPartInformation(item.GetPath())->m_lStackTotalTimeMs = totalTime; +} + +CApplicationStackHelper::StackPartInformationPtr CApplicationStackHelper::GetStackPartInformation( + const std::string& key) +{ + if (m_stackmap.count(key) == 0) + { + StackPartInformationPtr value(new StackPartInformation()); + m_stackmap[key] = value; + } + return m_stackmap[key]; +} + +CApplicationStackHelper::StackPartInformationPtr CApplicationStackHelper::GetStackPartInformation( + const std::string& key) const +{ + const auto it = m_stackmap.find(key); + if (it == m_stackmap.end()) + return std::make_shared<StackPartInformation>(); + return it->second; +} diff --git a/xbmc/application/ApplicationStackHelper.h b/xbmc/application/ApplicationStackHelper.h new file mode 100644 index 0000000..6af0044 --- /dev/null +++ b/xbmc/application/ApplicationStackHelper.h @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "application/IApplicationComponent.h" +#include "threads/CriticalSection.h" + +#include <map> +#include <memory> +#include <string> + +class CFileItem; +class CFileItemList; + +class CApplicationStackHelper : public IApplicationComponent +{ +public: + CApplicationStackHelper(void); + ~CApplicationStackHelper() = default; + + void Clear(); + void OnPlayBackStarted(const CFileItem& item); + + /*! + \brief Initialize stack + \param item the FileItem object that is the stack + */ + bool InitializeStack(const CFileItem& item); + + /*! + \brief Initialize stack times for each part, start & end, total time, and current part number if resume offset is specified. + \param item the FileItem object that is the stack + */ + int InitializeStackStartPartAndOffset(const CFileItem& item); + + /*! + \brief returns the current part number + */ + int GetCurrentPartNumber() const { return m_currentStackPosition; } + + /*! + \brief Returns true if Application is currently playing an ISO stack + */ + bool IsPlayingISOStack() const; + + /*! + \brief Returns true if Application is currently playing a Regular (non-ISO) stack + */ + bool IsPlayingRegularStack() const; + + /*! + \brief returns true if there is a next part available + */ + bool HasNextStackPartFileItem() const; + + /*! + \brief sets the next stack part as the current and returns a reference to it + */ + const CFileItem& SetNextStackPartCurrentFileItem() + { + return GetStackPartFileItem(++m_currentStackPosition); + } + + /*! + \brief sets a given stack part as the current and returns a reference to it + \param partNumber the number of the part that needs to become the current one + */ + const CFileItem& SetStackPartCurrentFileItem(int partNumber) + { + return GetStackPartFileItem(m_currentStackPosition = partNumber); + } + + /*! + \brief Returns the FileItem currently playing back as part of a (non-ISO) stack playback + */ + const CFileItem& GetCurrentStackPartFileItem() const + { + return GetStackPartFileItem(m_currentStackPosition); + } + + /*! + \brief Returns the end time of a FileItem part of a (non-ISO) stack playback + \param partNumber the requested part number in the stack + */ + uint64_t GetStackPartEndTimeMs(int partNumber) const; + + /*! + \brief Returns the start time of a FileItem part of a (non-ISO) stack playback + \param partNumber the requested part number in the stack + */ + uint64_t GetStackPartStartTimeMs(int partNumber) const { return (partNumber > 0) ? GetStackPartEndTimeMs(partNumber - 1) : 0; } + + /*! + \brief Returns the start time of the current FileItem part of a (non-ISO) stack playback + */ + uint64_t GetCurrentStackPartStartTimeMs() const { return GetStackPartStartTimeMs(m_currentStackPosition); } + + /*! + \brief Returns the total time of a (non-ISO) stack playback + */ + uint64_t GetStackTotalTimeMs() const; + + /*! + \brief Returns the stack part number corresponding to the given timestamp in a (non-ISO) stack playback + \param msecs the requested timestamp in the stack (in milliseconds) + */ + int GetStackPartNumberAtTimeMs(uint64_t msecs); + + // Stack information registration methods + + /*! + \brief Clear all entries in the item-stack map. To be called upon playback stopped. + */ + void ClearAllRegisteredStackInformation(); + + /*! + \brief Returns a smart pointer to the stack CFileItem. + */ + std::shared_ptr<const CFileItem> GetRegisteredStack(const CFileItem& item) const; + + /*! + \brief Returns true if there is a registered stack for the given CFileItem part. + \param item the reference to the item that is part of a stack + */ + bool HasRegisteredStack(const CFileItem& item) const; + + /*! + \brief Stores a smart pointer to the stack CFileItem in the item-stack map. + \param item the reference to the item that is part of a stack + \param stackItem the smart pointer to the stack CFileItem + */ + void SetRegisteredStack(const CFileItem& item, std::shared_ptr<CFileItem> stackItem); + + /*! + \brief Returns the part number of the part in the parameter + \param item the reference to the item that is part of a stack + */ + int GetRegisteredStackPartNumber(const CFileItem& item); + + /*! + \brief Stores the part number in the item-stack map. + \param item the reference to the item that is part of a stack + \param partNumber the part number of the part in other parameter + */ + void SetRegisteredStackPartNumber(const CFileItem& item, int partNumber); + + /*! + \brief Returns the start time of the part in the parameter + \param item the reference to the item that is part of a stack + */ + uint64_t GetRegisteredStackPartStartTimeMs(const CFileItem& item) const; + + /*! + \brief Stores the part start time in the item-stack map. + \param item the reference to the item that is part of a stack + \param startTime the start time of the part in other parameter + */ + void SetRegisteredStackPartStartTimeMs(const CFileItem& item, uint64_t startTimeMs); + + /*! + \brief Returns the total time of the stack associated to the part in the parameter + \param item the reference to the item that is part of a stack + */ + uint64_t GetRegisteredStackTotalTimeMs(const CFileItem& item) const; + + /*! + \brief Stores the stack's total time associated to the part in the item-stack map. + \param item the reference to the item that is part of a stack + \param totalTime the total time of the stack + */ + void SetRegisteredStackTotalTimeMs(const CFileItem& item, uint64_t totalTimeMs); + + CCriticalSection m_critSection; + +protected: + /*! + \brief Returns a FileItem part of a (non-ISO) stack playback + \param partNumber the requested part number in the stack + */ + CFileItem& GetStackPartFileItem(int partNumber); + const CFileItem& GetStackPartFileItem(int partNumber) const; + + class StackPartInformation + { + public: + StackPartInformation() + { + m_lStackPartNumber = 0; + m_lStackPartStartTimeMs = 0; + m_lStackTotalTimeMs = 0; + }; + uint64_t m_lStackPartStartTimeMs; + uint64_t m_lStackTotalTimeMs; + int m_lStackPartNumber; + std::shared_ptr<CFileItem> m_pStack; + }; + + typedef std::shared_ptr<StackPartInformation> StackPartInformationPtr; + typedef std::map<std::string, StackPartInformationPtr> Stackmap; + Stackmap m_stackmap; + StackPartInformationPtr GetStackPartInformation(const std::string& key); + StackPartInformationPtr GetStackPartInformation(const std::string& key) const; + + std::unique_ptr<CFileItemList> m_currentStack; + int m_currentStackPosition = 0; + bool m_currentStackIsDiscImageStack = false; +}; diff --git a/xbmc/application/ApplicationVolumeHandling.cpp b/xbmc/application/ApplicationVolumeHandling.cpp new file mode 100644 index 0000000..25229be --- /dev/null +++ b/xbmc/application/ApplicationVolumeHandling.cpp @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ApplicationVolumeHandling.h" + +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "cores/AudioEngine/Interfaces/AE.h" +#include "dialogs/GUIDialogVolumeBar.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "interfaces/AnnouncementManager.h" +#include "peripherals/Peripherals.h" +#include "settings/Settings.h" +#include "settings/lib/Setting.h" +#include "utils/Variant.h" +#include "utils/XMLUtils.h" + +#include <tinyxml.h> + +float CApplicationVolumeHandling::GetVolumePercent() const +{ + // converts the hardware volume to a percentage + return m_volumeLevel * 100.0f; +} + +float CApplicationVolumeHandling::GetVolumeRatio() const +{ + return m_volumeLevel; +} + +void CApplicationVolumeHandling::SetHardwareVolume(float hardwareVolume) +{ + m_volumeLevel = std::clamp(hardwareVolume, VOLUME_MINIMUM, VOLUME_MAXIMUM); + + IAE* ae = CServiceBroker::GetActiveAE(); + if (ae) + ae->SetVolume(m_volumeLevel); +} + +void CApplicationVolumeHandling::VolumeChanged() +{ + CVariant data(CVariant::VariantTypeObject); + data["volume"] = static_cast<int>(std::lroundf(GetVolumePercent())); + data["muted"] = m_muted; + const auto announcementMgr = CServiceBroker::GetAnnouncementManager(); + announcementMgr->Announce(ANNOUNCEMENT::Application, "OnVolumeChanged", data); + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + // if player has volume control, set it. + if (appPlayer) + { + appPlayer->SetVolume(m_volumeLevel); + appPlayer->SetMute(m_muted); + } +} + +void CApplicationVolumeHandling::ShowVolumeBar(const CAction* action) +{ + const auto& wm = CServiceBroker::GetGUI()->GetWindowManager(); + auto* volumeBar = wm.GetWindow<CGUIDialogVolumeBar>(WINDOW_DIALOG_VOLUME_BAR); + if (volumeBar != nullptr && volumeBar->IsVolumeBarEnabled()) + { + volumeBar->Open(); + if (action) + volumeBar->OnAction(*action); + } +} + +bool CApplicationVolumeHandling::IsMuted() const +{ + if (CServiceBroker::GetPeripherals().IsMuted()) + return true; + IAE* ae = CServiceBroker::GetActiveAE(); + if (ae) + return ae->IsMuted(); + return true; +} + +void CApplicationVolumeHandling::ToggleMute(void) +{ + if (m_muted) + UnMute(); + else + Mute(); +} + +void CApplicationVolumeHandling::SetMute(bool mute) +{ + if (m_muted != mute) + { + ToggleMute(); + m_muted = mute; + } +} + +void CApplicationVolumeHandling::Mute() +{ + if (CServiceBroker::GetPeripherals().Mute()) + return; + + IAE* ae = CServiceBroker::GetActiveAE(); + if (ae) + ae->SetMute(true); + m_muted = true; + VolumeChanged(); +} + +void CApplicationVolumeHandling::UnMute() +{ + if (CServiceBroker::GetPeripherals().UnMute()) + return; + + IAE* ae = CServiceBroker::GetActiveAE(); + if (ae) + ae->SetMute(false); + m_muted = false; + VolumeChanged(); +} + +void CApplicationVolumeHandling::SetVolume(float iValue, bool isPercentage) +{ + float hardwareVolume = iValue; + + if (isPercentage) + hardwareVolume /= 100.0f; + + SetHardwareVolume(hardwareVolume); + VolumeChanged(); +} + +void CApplicationVolumeHandling::CacheReplayGainSettings(const CSettings& settings) +{ + // initialize m_replayGainSettings + m_replayGainSettings.iType = settings.GetInt(CSettings::SETTING_MUSICPLAYER_REPLAYGAINTYPE); + m_replayGainSettings.iPreAmp = settings.GetInt(CSettings::SETTING_MUSICPLAYER_REPLAYGAINPREAMP); + m_replayGainSettings.iNoGainPreAmp = + settings.GetInt(CSettings::SETTING_MUSICPLAYER_REPLAYGAINNOGAINPREAMP); + m_replayGainSettings.bAvoidClipping = + settings.GetBool(CSettings::SETTING_MUSICPLAYER_REPLAYGAINAVOIDCLIPPING); +} + +bool CApplicationVolumeHandling::Load(const TiXmlNode* settings) +{ + if (!settings) + return false; + + const TiXmlElement* audioElement = settings->FirstChildElement("audio"); + if (audioElement) + { + XMLUtils::GetBoolean(audioElement, "mute", m_muted); + if (!XMLUtils::GetFloat(audioElement, "fvolumelevel", m_volumeLevel, VOLUME_MINIMUM, + VOLUME_MAXIMUM)) + m_volumeLevel = VOLUME_MAXIMUM; + } + + return true; +} + +bool CApplicationVolumeHandling::Save(TiXmlNode* settings) const +{ + if (!settings) + return false; + + TiXmlElement volumeNode("audio"); + TiXmlNode* audioNode = settings->InsertEndChild(volumeNode); + if (!audioNode) + return false; + + XMLUtils::SetBoolean(audioNode, "mute", m_muted); + XMLUtils::SetFloat(audioNode, "fvolumelevel", m_volumeLevel); + + return true; +} + +bool CApplicationVolumeHandling::OnSettingChanged(const CSetting& setting) +{ + const std::string& settingId = setting.GetId(); + + if (StringUtils::EqualsNoCase(settingId, CSettings::SETTING_MUSICPLAYER_REPLAYGAINTYPE)) + m_replayGainSettings.iType = static_cast<const CSettingInt&>(setting).GetValue(); + else if (StringUtils::EqualsNoCase(settingId, CSettings::SETTING_MUSICPLAYER_REPLAYGAINPREAMP)) + m_replayGainSettings.iPreAmp = static_cast<const CSettingInt&>(setting).GetValue(); + else if (StringUtils::EqualsNoCase(settingId, + CSettings::SETTING_MUSICPLAYER_REPLAYGAINNOGAINPREAMP)) + m_replayGainSettings.iNoGainPreAmp = static_cast<const CSettingInt&>(setting).GetValue(); + else if (StringUtils::EqualsNoCase(settingId, + CSettings::SETTING_MUSICPLAYER_REPLAYGAINAVOIDCLIPPING)) + m_replayGainSettings.bAvoidClipping = static_cast<const CSettingBool&>(setting).GetValue(); + else + return false; + + return true; +} diff --git a/xbmc/application/ApplicationVolumeHandling.h b/xbmc/application/ApplicationVolumeHandling.h new file mode 100644 index 0000000..d417227 --- /dev/null +++ b/xbmc/application/ApplicationVolumeHandling.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "application/IApplicationComponent.h" + +class CAction; +class CApplication; +class CSetting; +class CSettings; +class TiXmlNode; + +/*! + * \brief Class handling application support for audio volume management. + */ +class CApplicationVolumeHandling : public IApplicationComponent +{ + friend class CApplication; + +public: + // replay gain settings struct for quick access by the player multiple + // times per second (saves doing settings lookup) + struct ReplayGainSettings + { + int iPreAmp; + int iNoGainPreAmp; + int iType; + bool bAvoidClipping; + }; + + float GetVolumePercent() const; + float GetVolumeRatio() const; + bool IsMuted() const; + + void SetVolume(float iValue, bool isPercentage = true); + void SetMute(bool mute); + void ToggleMute(void); + + const ReplayGainSettings& GetReplayGainSettings() const { return m_replayGainSettings; } + + static constexpr float VOLUME_MINIMUM = 0.0f; // -60dB + static constexpr float VOLUME_MAXIMUM = 1.0f; // 0dB + static constexpr float VOLUME_DYNAMIC_RANGE = 90.0f; // 60dB + + bool Load(const TiXmlNode* settings); + bool Save(TiXmlNode* settings) const; + bool OnSettingChanged(const CSetting& setting); + +protected: + bool IsMutedInternal() const { return m_muted; } + void ShowVolumeBar(const CAction* action = nullptr); + + void CacheReplayGainSettings(const CSettings& settings); + + void Mute(); + void UnMute(); + + void SetHardwareVolume(float hardwareVolume); + + void VolumeChanged(); + + bool m_muted = false; + float m_volumeLevel = VOLUME_MAXIMUM; + ReplayGainSettings m_replayGainSettings; +}; diff --git a/xbmc/application/CMakeLists.txt b/xbmc/application/CMakeLists.txt new file mode 100644 index 0000000..2fd3dbe --- /dev/null +++ b/xbmc/application/CMakeLists.txt @@ -0,0 +1,29 @@ +set(SOURCES AppEnvironment.cpp + AppInboundProtocol.cpp + Application.cpp + ApplicationActionListeners.cpp + ApplicationPlayer.cpp + ApplicationPlayerCallback.cpp + ApplicationPowerHandling.cpp + ApplicationSettingsHandling.cpp + ApplicationSkinHandling.cpp + ApplicationStackHelper.cpp + ApplicationVolumeHandling.cpp + AppParamParser.cpp + AppParams.cpp) + +set(HEADERS AppEnvironment.h + AppInboundProtocol.h + Application.h + ApplicationActionListeners.h + ApplicationPlayer.h + ApplicationPlayerCallback.h + ApplicationPowerHandling.h + ApplicationSettingsHandling.h + ApplicationSkinHandling.h + ApplicationStackHelper.h + ApplicationVolumeHandling.h + AppParamParser.h + AppParams.h) + +core_add_library(application) diff --git a/xbmc/application/IApplicationComponent.h b/xbmc/application/IApplicationComponent.h new file mode 100644 index 0000000..da23d3e --- /dev/null +++ b/xbmc/application/IApplicationComponent.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +//! \brief Base class for application components. +class IApplicationComponent +{ +public: + virtual ~IApplicationComponent() = default; +}; |