diff options
Diffstat (limited to 'xbmc/application/Application.cpp')
-rw-r--r-- | xbmc/application/Application.cpp | 3693 |
1 files changed, 3693 insertions, 0 deletions
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(); +} |