Merge pull request #26350 from jjd-uk/estuary_media_align
[xbmc.git] / xbmc / guilib / GUIAudioManager.cpp
blob0be723b8c2aad86c6c389639b46f78178928d890
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "GUIAudioManager.h"
11 #include "ServiceBroker.h"
12 #include "addons/AddonManager.h"
13 #include "addons/Skin.h"
14 #include "addons/addoninfo/AddonType.h"
15 #include "cores/AudioEngine/Interfaces/AE.h"
16 #include "filesystem/Directory.h"
17 #include "input/WindowTranslator.h"
18 #include "input/actions/Action.h"
19 #include "input/actions/ActionIDs.h"
20 #include "input/actions/ActionTranslator.h"
21 #include "settings/Settings.h"
22 #include "settings/SettingsComponent.h"
23 #include "settings/lib/Setting.h"
24 #include "utils/URIUtils.h"
25 #include "utils/XBMCTinyXML.h"
26 #include "utils/log.h"
28 #include <mutex>
30 using namespace KODI;
32 CGUIAudioManager::CGUIAudioManager()
33 : m_settings(CServiceBroker::GetSettingsComponent()->GetSettings())
35 m_bEnabled = false;
37 m_settings->RegisterCallback(this, {CSettings::SETTING_LOOKANDFEEL_SOUNDSKIN,
38 CSettings::SETTING_AUDIOOUTPUT_GUISOUNDVOLUME});
41 CGUIAudioManager::~CGUIAudioManager()
43 m_settings->UnregisterCallback(this);
46 void CGUIAudioManager::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
48 if (setting == NULL)
49 return;
51 const std::string &settingId = setting->GetId();
52 if (settingId == CSettings::SETTING_LOOKANDFEEL_SOUNDSKIN)
54 Enable(true);
55 Load();
59 bool CGUIAudioManager::OnSettingUpdate(const std::shared_ptr<CSetting>& setting,
60 const char* oldSettingId,
61 const TiXmlNode* oldSettingNode)
63 if (setting == NULL)
64 return false;
66 if (setting->GetId() == CSettings::SETTING_LOOKANDFEEL_SOUNDSKIN)
68 //Migrate the old settings
69 if (std::static_pointer_cast<CSettingString>(setting)->GetValue() == "SKINDEFAULT")
70 std::static_pointer_cast<CSettingString>(setting)->Reset();
71 else if (std::static_pointer_cast<CSettingString>(setting)->GetValue() == "OFF")
72 std::static_pointer_cast<CSettingString>(setting)->SetValue("");
74 if (setting->GetId() == CSettings::SETTING_AUDIOOUTPUT_GUISOUNDVOLUME)
76 int vol = m_settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_GUISOUNDVOLUME);
77 SetVolume(0.01f * vol);
79 return true;
83 void CGUIAudioManager::Initialize()
87 void CGUIAudioManager::DeInitialize()
89 std::unique_lock<CCriticalSection> lock(m_cs);
90 UnLoad();
93 void CGUIAudioManager::Stop()
95 std::unique_lock<CCriticalSection> lock(m_cs);
96 for (const auto& windowSound : m_windowSoundMap)
98 if (windowSound.second.initSound)
99 windowSound.second.initSound->Stop();
100 if (windowSound.second.deInitSound)
101 windowSound.second.deInitSound->Stop();
104 for (const auto& pythonSound : m_pythonSounds)
106 pythonSound.second->Stop();
110 // \brief Play a sound associated with a CAction
111 void CGUIAudioManager::PlayActionSound(const CAction& action)
113 std::unique_lock<CCriticalSection> lock(m_cs);
115 // it's not possible to play gui sounds when passthrough is active
116 if (!m_bEnabled)
117 return;
119 const auto it = m_actionSoundMap.find(action.GetID());
120 if (it == m_actionSoundMap.end())
121 return;
123 if (it->second)
125 int vol = m_settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_GUISOUNDVOLUME);
126 it->second->SetVolume(0.01f * vol);
127 it->second->Play();
131 // \brief Play a sound associated with a window and its event
132 // Events: SOUND_INIT, SOUND_DEINIT
133 void CGUIAudioManager::PlayWindowSound(int id, WINDOW_SOUND event)
135 std::unique_lock<CCriticalSection> lock(m_cs);
137 // it's not possible to play gui sounds when passthrough is active
138 if (!m_bEnabled)
139 return;
141 const auto it = m_windowSoundMap.find(id);
142 if (it==m_windowSoundMap.end())
143 return;
145 std::shared_ptr<IAESound> sound;
146 switch (event)
148 case SOUND_INIT:
149 sound = it->second.initSound;
150 break;
151 case SOUND_DEINIT:
152 sound = it->second.deInitSound;
153 break;
156 if (!sound)
157 return;
159 int vol = m_settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_GUISOUNDVOLUME);
160 sound->SetVolume(0.01f * vol);
161 sound->Play();
164 // \brief Play a sound given by filename
165 void CGUIAudioManager::PlayPythonSound(const std::string& strFileName, bool useCached /*= true*/)
167 std::unique_lock<CCriticalSection> lock(m_cs);
169 // it's not possible to play gui sounds when passthrough is active
170 if (!m_bEnabled)
171 return;
173 // If we already loaded the sound, just play it
174 const auto itsb = m_pythonSounds.find(strFileName);
175 if (itsb != m_pythonSounds.end())
177 const auto& sound = itsb->second;
178 if (useCached)
180 sound->Play();
181 return;
183 else
185 m_pythonSounds.erase(itsb);
189 auto sound = LoadSound(strFileName);
190 if (!sound)
191 return;
193 int vol = m_settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_GUISOUNDVOLUME);
194 sound->SetVolume(0.01f * vol);
195 sound->Play();
196 m_pythonSounds.emplace(strFileName, std::move(sound));
199 void CGUIAudioManager::UnLoad()
201 m_windowSoundMap.clear();
202 m_pythonSounds.clear();
203 m_actionSoundMap.clear();
204 m_soundCache.clear();
208 std::string GetSoundSkinPath()
210 auto setting = std::static_pointer_cast<CSettingString>(CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(CSettings::SETTING_LOOKANDFEEL_SOUNDSKIN));
211 auto value = setting->GetValue();
212 if (value.empty())
213 return "";
215 ADDON::AddonPtr addon;
216 if (!CServiceBroker::GetAddonMgr().GetAddon(value, addon, ADDON::AddonType::RESOURCE_UISOUNDS,
217 ADDON::OnlyEnabled::CHOICE_YES))
219 CLog::Log(LOGINFO, "Unknown sounds addon '{}'. Setting default sounds.", value);
220 setting->Reset();
222 return URIUtils::AddFileToFolder("resource://", setting->GetValue());
226 // \brief Load the config file (sounds.xml) for nav sounds
227 bool CGUIAudioManager::Load()
229 std::unique_lock<CCriticalSection> lock(m_cs);
230 UnLoad();
232 m_strMediaDir = GetSoundSkinPath();
233 if (m_strMediaDir.empty())
234 return true;
236 Enable(true);
237 std::string strSoundsXml = URIUtils::AddFileToFolder(m_strMediaDir, "sounds.xml");
239 // Load our xml file
240 CXBMCTinyXML xmlDoc;
242 CLog::Log(LOGINFO, "Loading {}", strSoundsXml);
244 // Load the config file
245 if (!xmlDoc.LoadFile(strSoundsXml))
247 CLog::Log(LOGINFO, "{}, Line {}\n{}", strSoundsXml, xmlDoc.ErrorRow(), xmlDoc.ErrorDesc());
248 return false;
251 TiXmlElement* pRoot = xmlDoc.RootElement();
252 std::string strValue = pRoot->Value();
253 if ( strValue != "sounds")
255 CLog::Log(LOGINFO, "{} Doesn't contain <sounds>", strSoundsXml);
256 return false;
259 // Load sounds for actions
260 TiXmlElement* pActions = pRoot->FirstChildElement("actions");
261 if (pActions)
263 TiXmlNode* pAction = pActions->FirstChild("action");
265 while (pAction)
267 TiXmlNode* pIdNode = pAction->FirstChild("name");
268 unsigned int id = ACTION_NONE; // action identity
269 if (pIdNode && pIdNode->FirstChild())
271 ACTION::CActionTranslator::TranslateString(pIdNode->FirstChild()->Value(), id);
274 TiXmlNode* pFileNode = pAction->FirstChild("file");
275 std::string strFile;
276 if (pFileNode && pFileNode->FirstChild())
277 strFile += pFileNode->FirstChild()->Value();
279 if (id != ACTION_NONE && !strFile.empty())
281 std::string filename = URIUtils::AddFileToFolder(m_strMediaDir, strFile);
282 auto sound = LoadSound(filename);
283 if (sound)
284 m_actionSoundMap.emplace(id, std::move(sound));
287 pAction = pAction->NextSibling();
291 // Load window specific sounds
292 TiXmlElement* pWindows = pRoot->FirstChildElement("windows");
293 if (pWindows)
295 TiXmlNode* pWindow = pWindows->FirstChild("window");
297 while (pWindow)
299 int id = 0;
301 TiXmlNode* pIdNode = pWindow->FirstChild("name");
302 if (pIdNode)
304 if (pIdNode->FirstChild())
305 id = CWindowTranslator::TranslateWindow(pIdNode->FirstChild()->Value());
308 CWindowSounds sounds;
309 sounds.initSound = LoadWindowSound(pWindow, "activate" );
310 sounds.deInitSound = LoadWindowSound(pWindow, "deactivate");
312 if (id > 0)
313 m_windowSoundMap.insert(std::pair<int, CWindowSounds>(id, sounds));
315 pWindow = pWindow->NextSibling();
319 return true;
322 std::shared_ptr<IAESound> CGUIAudioManager::LoadSound(const std::string& filename)
324 std::unique_lock<CCriticalSection> lock(m_cs);
325 const auto it = m_soundCache.find(filename);
326 if (it != m_soundCache.end())
328 auto sound = it->second.lock();
329 if (sound)
330 return sound;
331 else
332 m_soundCache.erase(it); // cleanup orphaned cache entry
335 IAE *ae = CServiceBroker::GetActiveAE();
336 if (!ae)
337 return nullptr;
339 std::shared_ptr<IAESound> sound(ae->MakeSound(filename));
340 if (!sound)
341 return nullptr;
343 m_soundCache[filename] = sound;
345 return sound;
348 // \brief Load a window node of the config file (sounds.xml)
349 std::shared_ptr<IAESound> CGUIAudioManager::LoadWindowSound(TiXmlNode* pWindowNode,
350 const std::string& strIdentifier)
352 if (!pWindowNode)
353 return NULL;
355 TiXmlNode* pFileNode = pWindowNode->FirstChild(strIdentifier);
356 if (pFileNode && pFileNode->FirstChild())
357 return LoadSound(URIUtils::AddFileToFolder(m_strMediaDir, pFileNode->FirstChild()->Value()));
359 return NULL;
362 // \brief Enable/Disable nav sounds
363 void CGUIAudioManager::Enable(bool bEnable)
365 // always deinit audio when we don't want gui sounds
366 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_SOUNDSKIN).empty())
367 bEnable = false;
369 std::unique_lock<CCriticalSection> lock(m_cs);
370 m_bEnabled = bEnable;
373 // \brief Sets the volume of all playing sounds
374 void CGUIAudioManager::SetVolume(float level)
376 std::unique_lock<CCriticalSection> lock(m_cs);
379 for (const auto& actionSound : m_actionSoundMap)
381 if (actionSound.second)
382 actionSound.second->SetVolume(level);
386 for (const auto& windowSound : m_windowSoundMap)
388 if (windowSound.second.initSound)
389 windowSound.second.initSound->SetVolume(level);
390 if (windowSound.second.deInitSound)
391 windowSound.second.deInitSound->SetVolume(level);
395 for (const auto& pythonSound : m_pythonSounds)
397 if (pythonSound.second)
398 pythonSound.second->SetVolume(level);