mirror of https://github.com/xbmc/xbmc
2872 lines
96 KiB
C++
2872 lines
96 KiB
C++
/*
|
|
* 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 "DatabaseManager.h"
|
|
#include "FileItem.h"
|
|
#include "FileItemList.h"
|
|
#include "GUIInfoManager.h"
|
|
#include "GUILargeTextureManager.h"
|
|
#include "GUIPassword.h"
|
|
#include "GUIUserMessages.h"
|
|
#include "HDRStatus.h"
|
|
#include "LangInfo.h"
|
|
#include "PartyModeManager.h"
|
|
#include "PlayListPlayer.h"
|
|
#include "SectionLoader.h"
|
|
#include "SeekHandler.h"
|
|
#include "ServiceBroker.h"
|
|
#include "ServiceManager.h"
|
|
#include "TextureCache.h"
|
|
#include "URL.h"
|
|
#include "Util.h"
|
|
#include "addons/AddonManager.h"
|
|
#include "addons/AddonSystemSettings.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 "addons/gui/GUIDialogAddonSettings.h"
|
|
#include "application/AppInboundProtocol.h"
|
|
#include "application/AppParams.h"
|
|
#include "application/ApplicationActionListeners.h"
|
|
#include "application/ApplicationMessageHandling.h"
|
|
#include "application/ApplicationPlay.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/FFmpeg.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"
|
|
#ifdef HAVE_LIBBLURAY
|
|
#include "filesystem/BlurayDiscCache.h"
|
|
#endif
|
|
#include "filesystem/Directory.h"
|
|
#include "filesystem/DirectoryCache.h"
|
|
#include "filesystem/DllLibCurl.h"
|
|
#include "filesystem/File.h"
|
|
#include "music/MusicFileItemClassify.h"
|
|
#include "network/DNSNameCache.h"
|
|
#include "network/NetworkFileItemClassify.h"
|
|
#include "playlists/PlayListFileItemClassify.h"
|
|
#include "video/VideoFileItemClassify.h"
|
|
#ifdef HAS_FILESYSTEM_NFS
|
|
#include "filesystem/NFSFile.h"
|
|
#endif
|
|
#include "filesystem/PluginDirectory.h"
|
|
#include "filesystem/SpecialProtocol.h"
|
|
#include "guilib/GUIAudioManager.h"
|
|
#include "guilib/GUIComponent.h"
|
|
#include "guilib/GUIControlProfiler.h"
|
|
#include "guilib/GUIFontManager.h"
|
|
#include "guilib/GUIWindowManager.h"
|
|
#include "guilib/LocalizeStrings.h"
|
|
#include "guilib/StereoscopicsManager.h"
|
|
#include "guilib/TextureManager.h"
|
|
#include "input/InertialScrollingHandler.h"
|
|
#include "input/InputManager.h"
|
|
#include "input/actions/Action.h"
|
|
#include "input/actions/ActionIDs.h"
|
|
#include "input/actions/ActionTranslator.h"
|
|
#include "input/keyboard/KeyboardLayoutManager.h"
|
|
#include "interfaces/AnnouncementManager.h"
|
|
#include "interfaces/builtins/Builtins.h"
|
|
#include "interfaces/generic/ScriptInvocationManager.h"
|
|
#include "interfaces/json-rpc/JSONRPC.h"
|
|
#ifdef HAS_PYTHON
|
|
#include "interfaces/python/XBPython.h"
|
|
#endif
|
|
#include "messaging/ApplicationMessenger.h"
|
|
#include "messaging/ThreadMessage.h"
|
|
#include "messaging/helpers/DialogHelper.h"
|
|
#include "messaging/helpers/DialogOKHelper.h"
|
|
#include "music/MusicLibraryQueue.h"
|
|
#include "music/MusicThumbLoader.h"
|
|
#include "music/MusicUtils.h"
|
|
#include "music/infoscanner/MusicInfoScanner.h"
|
|
#include "music/tags/MusicInfoTag.h"
|
|
#include "network/EventServer.h"
|
|
#include "network/Network.h"
|
|
#include "network/ZeroconfBrowser.h"
|
|
#ifdef HAS_UPNP
|
|
#include "filesystem/UPnPDirectory.h"
|
|
#include "network/upnp/UPnP.h"
|
|
#endif
|
|
#include "jobs/JobManager.h"
|
|
#include "peripherals/Peripherals.h"
|
|
#include "pictures/SlideShowDelegator.h"
|
|
#include "platform/Environment.h"
|
|
#include "playlists/PlayList.h"
|
|
#include "playlists/PlayListFactory.h"
|
|
#include "playlists/SmartPlayList.h"
|
|
#include "powermanagement/PowerManager.h"
|
|
#include "profiles/ProfileManager.h"
|
|
#include "pvr/PVRManager.h"
|
|
#include "pvr/guilib/PVRGUIActionsPlayback.h"
|
|
#include "pvr/guilib/PVRGUIActionsPowerManagement.h"
|
|
#include "settings/AdvancedSettings.h"
|
|
#include "settings/DisplaySettings.h"
|
|
#include "settings/MediaSettings.h"
|
|
#include "settings/Settings.h"
|
|
#include "settings/SettingsComponent.h"
|
|
#include "settings/lib/Setting.h"
|
|
#include "speech/ISpeechRecognition.h"
|
|
#include "storage/MediaManager.h"
|
|
#include "utils/AlarmClock.h"
|
|
#include "utils/CPUInfo.h"
|
|
#include "utils/CharsetConverter.h"
|
|
#include "utils/ContentUtils.h"
|
|
#include "utils/FileExtensionProvider.h"
|
|
#include "utils/LangCodeExpander.h"
|
|
#include "utils/PlayerUtils.h"
|
|
#include "utils/RegExp.h"
|
|
#include "utils/Screenshot.h"
|
|
#include "utils/StringUtils.h"
|
|
#include "utils/SystemInfo.h"
|
|
#include "utils/TimeUtils.h"
|
|
#include "utils/URIUtils.h"
|
|
#include "utils/Variant.h"
|
|
#include "utils/log.h"
|
|
#include "video/PlayerController.h"
|
|
#include "video/VideoLibraryQueue.h"
|
|
#include "video/dialogs/GUIDialogVideoBookmarks.h"
|
|
#ifdef TARGET_WINDOWS
|
|
#include "win32util.h"
|
|
#endif
|
|
#include "windowing/GraphicContext.h"
|
|
#include "windowing/WinSystem.h"
|
|
#include "windowing/WindowSystemFactory.h"
|
|
#if defined(TARGET_ANDROID)
|
|
#include "platform/android/activity/XBMCApp.h"
|
|
#endif
|
|
#ifdef TARGET_DARWIN
|
|
#include "platform/darwin/DarwinUtils.h"
|
|
#endif
|
|
#ifdef TARGET_DARWIN_OSX
|
|
#ifdef HAS_XBMCHELPER
|
|
#include "platform/darwin/osx/XBMCHelper.h"
|
|
#endif
|
|
#endif
|
|
#ifdef TARGET_POSIX
|
|
#include "platform/posix/PlatformPosix.h"
|
|
#include "platform/posix/XHandle.h"
|
|
#endif
|
|
#if defined(TARGET_POSIX) && defined(HAS_FILESYSTEM_SMB)
|
|
#include "platform/posix/filesystem/SMBFile.h"
|
|
#endif
|
|
#ifndef TARGET_POSIX
|
|
#include "platform/win32/threads/Win32Exception.h"
|
|
#endif
|
|
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <mutex>
|
|
|
|
#include <tinyxml.h>
|
|
|
|
//TODO: XInitThreads
|
|
#ifdef HAVE_X11
|
|
#include <X11/Xlib.h>
|
|
#endif
|
|
#ifdef HAS_OPTICAL_DRIVE
|
|
#include <cdio/logging.h>
|
|
#endif
|
|
|
|
using namespace ADDON;
|
|
using namespace XFILE;
|
|
#ifdef HAS_OPTICAL_DRIVE
|
|
using namespace MEDIA_DETECT;
|
|
#endif
|
|
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_OPTICAL_DRIVE
|
|
m_Autorun(new CAutorun()),
|
|
#endif
|
|
m_pInertialScrollingHandler(new CInertialScrollingHandler()),
|
|
m_WaitingExternalCalls(0),
|
|
m_itemCurrentFile(std::make_shared<CFileItem>()),
|
|
m_playerEvent(true, true)
|
|
{
|
|
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));
|
|
}
|
|
|
|
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<KEYBOARD::CKeyboardLayoutManager>();
|
|
CServiceBroker::RegisterKeyboardLayoutManager(keyboardLayoutManager);
|
|
|
|
CServiceBroker::RegisterDNSNameCache(std::make_shared<CDNSNameCache>());
|
|
|
|
m_ServiceManager = std::make_unique<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;
|
|
|
|
// Log Cache GUI settings (replacement of cache in advancedsettings.xml)
|
|
const auto settings = settingsComponent->GetSettings();
|
|
const float readFactor = settings->GetInt(CSettings::SETTING_FILECACHE_READFACTOR) / 100.0f;
|
|
CLog::Log(LOGINFO,
|
|
"New Cache GUI Settings (replacement of cache in advancedsettings.xml) are:\n Buffer "
|
|
"Mode: {}\n Memory Size: {} MB\n Read "
|
|
"Factor: {:.2f} x {}\n Chunk Size : {} bytes",
|
|
settings->GetInt(CSettings::SETTING_FILECACHE_BUFFERMODE),
|
|
settings->GetInt(CSettings::SETTING_FILECACHE_MEMORYSIZE), readFactor,
|
|
(readFactor < 1.0f) ? "(adaptive)" : "",
|
|
settings->GetInt(CSettings::SETTING_FILECACHE_CHUNKSIZE));
|
|
|
|
CLog::Log(LOGINFO, "creating subdirectories");
|
|
const std::shared_ptr<CProfileManager> profileManager = settingsComponent->GetProfileManager();
|
|
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 message handling service
|
|
m_pMsgHandling = std::make_shared<CApplicationMessageHandling>(*this);
|
|
CServiceBroker::RegisterAppPort(std::static_pointer_cast<CAppInboundProtocol>(m_pMsgHandling));
|
|
|
|
#ifdef HAVE_LIBBLURAY
|
|
CServiceBroker::RegisterBlurayDiscCache(std::make_shared<CBlurayDiscCache>());
|
|
#endif
|
|
|
|
if (!m_ServiceManager->InitStageTwo(
|
|
settingsComponent->GetProfileManager()->GetProfileUserDataFolder()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_pActiveAE = std::make_unique<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 = std::make_unique<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_OPTICAL_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();
|
|
|
|
bool allDatabasesInitialized{false};
|
|
CEvent event(true);
|
|
CServiceBroker::GetJobManager()->Submit(
|
|
[&allDatabasesInitialized, &databaseManager, &event]()
|
|
{
|
|
allDatabasesInitialized = databaseManager.Initialize();
|
|
event.Set();
|
|
});
|
|
|
|
const std::string& connecting{g_localizeStrings.Get(24186)};
|
|
const std::string& updating{g_localizeStrings.Get(24150)};
|
|
int iDots = 1;
|
|
while (!event.Wait(1000ms))
|
|
{
|
|
if (databaseManager.IsConnecting() || databaseManager.IsUpgrading())
|
|
{
|
|
CServiceBroker::GetRenderSystem()->ShowSplash(
|
|
std::string(iDots, ' ') + (databaseManager.IsConnecting() ? connecting : updating) +
|
|
std::string(iDots, '.'));
|
|
}
|
|
|
|
if (iDots == 3)
|
|
iDots = 1;
|
|
else
|
|
++iDots;
|
|
}
|
|
CServiceBroker::GetRenderSystem()->ShowSplash("");
|
|
|
|
if (!allDatabasesInitialized)
|
|
{
|
|
// Bail out if any of the databases failed to initialize properly.
|
|
CLog::Log(LOGFATAL, "Failed to initialize databases");
|
|
|
|
const std::string& dbInitFailedExiting{g_localizeStrings.Get(24187)};
|
|
|
|
unsigned int secondsLeftUntilExit{10};
|
|
while (secondsLeftUntilExit)
|
|
{
|
|
CServiceBroker::GetRenderSystem()->ShowSplash(
|
|
StringUtils::Format(dbInitFailedExiting, secondsLeftUntilExit));
|
|
KODI::TIME::Sleep(1s);
|
|
secondsLeftUntilExit--;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 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();
|
|
});
|
|
|
|
std::string 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() != LockMode::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)
|
|
{
|
|
CSlideShowDelegator& slideShow = CServiceBroker::GetSlideShowDelegator();
|
|
if ((appPlayer->IsPlaying() && appPlayer->GetPlaySpeed() == 1) ||
|
|
(slideShow.InSlideShow() && !slideShow.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::GetWinSystem()->IsHDRDisplaySettingEnabled())
|
|
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::GetWinSystem()->IsHDRDisplaySettingEnabled())
|
|
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;
|
|
}
|
|
else if (action.GetID() == ACTION_PLAYER_INCREASE_TEMPO)
|
|
{
|
|
CPlayerUtils::AdvanceTempoStep(appPlayer, TempoStepChange::INCREASE);
|
|
return true;
|
|
}
|
|
else if (action.GetID() == ACTION_PLAYER_DECREASE_TEMPO)
|
|
{
|
|
CPlayerUtils::AdvanceTempoStep(appPlayer, TempoStepChange::DECREASE);
|
|
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::Id::TYPE_VIDEO &&
|
|
CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_VIDEO_PLAYLIST)
|
|
{
|
|
CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_PLAYLIST);
|
|
}
|
|
else if (playlistId == PLAYLIST::Id::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)
|
|
{
|
|
m_pMsgHandling->OnApplicationMessage(pMsg);
|
|
}
|
|
|
|
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 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();
|
|
}
|
|
}
|
|
}
|
|
|
|
m_pMsgHandling->HandleEvents();
|
|
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 = std::make_unique<CFileItem>();
|
|
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::Id::TYPE_MUSIC, playlist);
|
|
CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::Id::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();
|
|
|
|
#ifdef HAVE_LIBBLURAY
|
|
CServiceBroker::UnregisterBlurayDiscCache();
|
|
#endif
|
|
|
|
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();
|
|
CServiceBroker::GetPlaylistPlayer().Clear();
|
|
|
|
if (m_ServiceManager)
|
|
m_ServiceManager->DeinitStageTwo();
|
|
|
|
#ifdef TARGET_POSIX
|
|
CXHandle::DumpObjectTracker();
|
|
|
|
#ifdef HAS_OPTICAL_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::UnregisterDNSNameCache();
|
|
|
|
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_pMsgHandling.use_count() > 1)
|
|
{
|
|
KODI::TIME::Sleep(100ms);
|
|
if (timer.IsTimePast())
|
|
{
|
|
CLog::Log(LOGERROR, "CApplication::Stop - CAppPort still in use, app may crash");
|
|
break;
|
|
}
|
|
}
|
|
m_pMsgHandling->Close();
|
|
}
|
|
|
|
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) and defined(HAS_XBMCHELPER)
|
|
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 (PLAYLIST::IsSmartPlayList(item))
|
|
{
|
|
CFileItemList items;
|
|
CUtil::GetRecursiveListing(item.GetPath(), items, "", DIR_FLAG_NO_FILE_DIRS);
|
|
if (items.Size())
|
|
{
|
|
PLAYLIST::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::Id::TYPE_VIDEO;
|
|
|
|
if (smartpl.GetType() == "songs" || smartpl.GetType() == "albums" ||
|
|
smartpl.GetType() == "artists")
|
|
smartplPlaylistId = PLAYLIST::Id::TYPE_MUSIC;
|
|
|
|
return ProcessAndStartPlaylist(smartpl.GetName(), playlist, smartplPlaylistId);
|
|
}
|
|
}
|
|
else if (PLAYLIST::IsPlayList(item) || NETWORK::IsInternetStream(item))
|
|
{
|
|
// 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::Id::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);
|
|
}
|
|
|
|
bool CApplication::PlayFile(CFileItem item, const std::string& player, bool bRestart /* = false */)
|
|
{
|
|
const auto appPlayer{GetComponent<CApplicationPlayer>()};
|
|
const auto stackHelper{GetComponent<CApplicationStackHelper>()};
|
|
|
|
if (!bRestart)
|
|
{
|
|
appPlayer->SetPlaySpeed(1);
|
|
|
|
m_nextPlaylistItem = -1;
|
|
stackHelper->Clear();
|
|
|
|
if (VIDEO::IsVideo(item))
|
|
CUtil::ClearSubtitles();
|
|
}
|
|
|
|
using enum CApplicationPlay::GatherPlaybackDetailsResult;
|
|
|
|
CApplicationPlay appPlay{*stackHelper};
|
|
if (const auto result{appPlay.GatherPlaybackDetails(item, player, bRestart)};
|
|
result == RESULT_ERROR)
|
|
return false;
|
|
else if (result == RESULT_NO_PLAYLIST_SELECTED)
|
|
{
|
|
m_cancelPlayback = true;
|
|
return true; // Special case; not to be treated as error.
|
|
}
|
|
|
|
// Special handling for disc stubs.
|
|
//! @todo Shouldn't disc stubs also be handled via appPlayer->OpenFile()?
|
|
const CFileItem& resolvedItem{appPlay.GetResolvedItem()};
|
|
if (VIDEO::IsDiscStub(resolvedItem))
|
|
return CServiceBroker::GetMediaManager().playStubFile(resolvedItem);
|
|
|
|
// 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.
|
|
static constexpr std::array 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};
|
|
if (const int dMsgCount{
|
|
CServiceBroker::GetGUI()->GetWindowManager().RemoveThreadMessageByMessageIds(
|
|
&previousMsgsIgnoredByNewPlaying[0])};
|
|
dMsgCount > 0)
|
|
CLog::LogF(LOGDEBUG, "Ignored {} playback thread messages", dMsgCount);
|
|
|
|
appPlayer->OpenFile(resolvedItem, appPlay.GetPlayerOptions(),
|
|
m_ServiceManager->GetPlayerCoreFactory(), appPlay.GetResolvedPlayer(), *this);
|
|
|
|
const auto appVolume{GetComponent<CApplicationVolumeHandling>()};
|
|
appPlayer->SetVolume(appVolume->GetVolumeRatio());
|
|
appPlayer->SetMute(appVolume->IsMuted());
|
|
|
|
#if !defined(TARGET_POSIX)
|
|
if (CGUIComponent * gui{CServiceBroker::GetGUI()}; gui)
|
|
gui->GetAudioManager().Enable(false);
|
|
#endif
|
|
|
|
if (item.HasPVRChannelInfoTag())
|
|
CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::Id::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::Id::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() &&
|
|
(MUSIC::IsCDDA(*m_itemCurrentFile) || 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)
|
|
{
|
|
return m_pMsgHandling->OnMessage(message);
|
|
}
|
|
|
|
bool CApplication::ExecuteXBMCAction(std::string actionStr,
|
|
const std::shared_ptr<CGUIListItem>& 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, item);
|
|
}
|
|
else
|
|
{
|
|
// try translating the action from our ButtonTranslator
|
|
unsigned int actionID;
|
|
if (ACTION::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 (MUSIC::IsAudio(item) || VIDEO::IsVideo(item) || 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;
|
|
}
|
|
|
|
void CApplication::ConfigureAndEnableAddons()
|
|
{
|
|
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();
|
|
}
|
|
|
|
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
|
|
|
|
// 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();
|
|
|
|
// Temporarily pause pausable jobs when viewing video/picture
|
|
int currentWindow = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow();
|
|
if (VIDEO::IsVideo(CurrentFileItem()) || 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_OPTICAL_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->IsPlayingStack())
|
|
return stackHelper->GetCurrentStackPart();
|
|
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() || stackHelper->IsPlayingResolvedDiscStack())
|
|
rc = static_cast<double>(stackHelper->GetStackTotalTime().count()) / 1000.0;
|
|
else
|
|
rc = static_cast<double>(appPlayer->GetTotalTime()) / 1000.0;
|
|
}
|
|
|
|
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() || stackHelper->IsPlayingResolvedDiscStack())
|
|
{
|
|
uint64_t startOfCurrentFile =
|
|
static_cast<uint64_t>(stackHelper->GetCurrentStackPartStartTime().count());
|
|
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() || stackHelper->IsPlayingResolvedDiscStack())
|
|
{
|
|
// 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->GetStackPartNumberAtTime(
|
|
std::chrono::milliseconds(static_cast<int64_t>(dTime * 1000.0)));
|
|
uint64_t startOfNewFile = stackHelper->GetStackPartStartTime(partNumberToPlay).count();
|
|
if (partNumberToPlay == stackHelper->GetCurrentPartNumber())
|
|
appPlayer->SeekTime(static_cast<int64_t>(dTime * 1000.0) -
|
|
static_cast<int64_t>(startOfNewFile));
|
|
else
|
|
{
|
|
// seeking to a new file
|
|
stackHelper->SetStackPartAsCurrent(partNumberToPlay);
|
|
stackHelper->SetSeekingParts(true);
|
|
CFileItem* item = new CFileItem(stackHelper->GetCurrentStackPart());
|
|
item->SetStartOffset(static_cast<int64_t>(dTime * 1000.0) -
|
|
static_cast<int64_t>(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() || stackHelper->IsPlayingResolvedDiscStack())
|
|
{
|
|
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() || stackHelper->IsPlayingResolvedDiscStack())
|
|
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,
|
|
static_cast<int>(playlistId));
|
|
|
|
// initial exit conditions
|
|
// no songs in playlist just return
|
|
if (playlist.size() == 0)
|
|
return false;
|
|
|
|
// illegal playlist
|
|
if (playlistId == PLAYLIST::Id::TYPE_NONE || playlistId == PLAYLIST::Id::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();
|
|
}
|