Fixed: Don't busywait for navsounds to finish.
[xbmc:jj72orguks-xbmc.git] / guilib / GUIAudioManager.cpp
1 /*
2  *      Copyright (C) 2005-2008 Team XBMC
3  *      http://www.xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #include "system.h"
23 #include "GUIAudioManager.h"
24 #include "Key.h"
25 #include "AudioContext.h"
26 #include "GUISound.h"
27 #include "GUISettings.h"
28 #include "ButtonTranslator.h"
29 #include "utils/SingleLock.h"
30 #include "../xbmc/Util.h"
31 #include "../xbmc/FileSystem/Directory.h"
32 #include "tinyXML/tinyxml.h"
33 #include "addons/Skin.h"
34 #ifdef HAS_SDL_AUDIO
35 #include <SDL/SDL_mixer.h>
36 #endif
37
38 using namespace std;
39 using namespace XFILE;
40
41 CGUIAudioManager g_audioManager;
42
43 CGUIAudioManager::CGUIAudioManager()
44 {
45   m_bInitialized = false;
46   m_bEnabled = false;
47   m_actionSound=NULL;
48 }
49
50 CGUIAudioManager::~CGUIAudioManager()
51 {
52
53 }
54
55 void CGUIAudioManager::Initialize(int iDevice)
56 {
57   if (g_guiSettings.GetString("lookandfeel.soundskin")=="OFF")
58     return;
59
60   if (iDevice==CAudioContext::DEFAULT_DEVICE)
61   {
62     CSingleLock lock(m_cs);
63     
64     if (m_bInitialized)
65       return;
66
67     CLog::Log(LOGDEBUG, "CGUIAudioManager::Initialize");
68 #ifdef _WIN32
69     bool bAudioOnAllSpeakers=false;
70     g_audioContext.SetupSpeakerConfig(2, bAudioOnAllSpeakers);
71     g_audioContext.SetActiveDevice(CAudioContext::DIRECTSOUND_DEVICE);
72     m_bInitialized = true;
73 #elif defined(HAS_SDL_AUDIO)
74     Mix_CloseAudio();
75     if (Mix_OpenAudio(44100, AUDIO_S16, 2, 4096))
76        CLog::Log(LOGERROR, "Unable to open audio mixer");
77     m_bInitialized = true;
78 #endif
79   }
80 }
81
82 void CGUIAudioManager::DeInitialize(int iDevice)
83 {
84   if (!(iDevice == CAudioContext::DIRECTSOUND_DEVICE || iDevice == CAudioContext::DEFAULT_DEVICE)) return;
85
86   CSingleLock lock(m_cs);
87
88   if (!m_bInitialized)
89     return;
90
91   CLog::Log(LOGDEBUG, "CGUIAudioManager::DeInitialize");
92
93   if (m_actionSound)
94     m_actionSound->Wait();
95
96   Stop();
97 #ifdef HAS_SDL_AUDIO
98   Mix_CloseAudio();
99 #endif
100   m_bInitialized = false;
101 }
102
103 void CGUIAudioManager::Stop()
104 {
105   CSingleLock lock(m_cs);
106   if (m_actionSound)
107   {
108     delete m_actionSound;
109     m_actionSound=NULL;
110   }
111
112   for (windowSoundsMap::iterator it=m_windowSounds.begin();it!=m_windowSounds.end();it++)
113   {
114     CGUISound* sound=it->second;
115     if (sound->IsPlaying())
116       sound->Stop();
117
118     delete sound;
119   }
120   m_windowSounds.clear();
121
122   for (pythonSoundsMap::iterator it1=m_pythonSounds.begin();it1!=m_pythonSounds.end();it1++)
123   {
124     CGUISound* sound=it1->second;
125     if (sound->IsPlaying())
126       sound->Stop();
127
128     delete sound;
129   }
130   m_pythonSounds.clear();
131 }
132
133 // \brief Clear any unused audio buffers
134 void CGUIAudioManager::FreeUnused()
135 {
136   CSingleLock lock(m_cs);
137
138   //  Free the sound from the last action
139   if (m_actionSound && !m_actionSound->IsPlaying())
140   {
141     delete m_actionSound;
142     m_actionSound=NULL;
143   }
144
145   //  Free sounds from windows
146   windowSoundsMap::iterator it=m_windowSounds.begin();
147   while (it!=m_windowSounds.end())
148   {
149     CGUISound* sound=it->second;
150     if (!sound->IsPlaying())
151     {
152       delete sound;
153       m_windowSounds.erase(it++);
154     }
155     else ++it;
156   }
157
158   // Free sounds from python
159   pythonSoundsMap::iterator it1=m_pythonSounds.begin();
160   while (it1!=m_pythonSounds.end())
161   {
162     CGUISound* sound=it1->second;
163     if (!sound->IsPlaying())
164     {
165       delete sound;
166       m_pythonSounds.erase(it1++);
167     }
168     else ++it1;
169   }
170 }
171
172 // \brief Play a sound associated with a CAction
173 void CGUIAudioManager::PlayActionSound(const CAction& action)
174 {
175   CSingleLock lock(m_cs);
176
177   // it's not possible to play gui sounds when passthrough is active
178   if (!m_bEnabled || !m_bInitialized || g_audioContext.IsPassthroughActive())
179     return;
180
181   actionSoundMap::iterator it=m_actionSoundMap.find(action.GetID());
182   if (it==m_actionSoundMap.end())
183     return;
184
185   if (m_actionSound)
186   {
187     delete m_actionSound;
188     m_actionSound=NULL;
189   }
190
191   m_actionSound=new CGUISound();
192   if (!m_actionSound->Load(CUtil::AddFileToFolder(m_strMediaDir, it->second)))
193   {
194     delete m_actionSound;
195     m_actionSound=NULL;
196     return;
197   }
198
199   m_actionSound->Play();
200 }
201
202 // \brief Play a sound associated with a window and its event
203 // Events: SOUND_INIT, SOUND_DEINIT
204 void CGUIAudioManager::PlayWindowSound(int id, WINDOW_SOUND event)
205 {
206   CSingleLock lock(m_cs);
207
208   // it's not possible to play gui sounds when passthrough is active
209   if (!m_bEnabled || !m_bInitialized || g_audioContext.IsPassthroughActive())
210     return;
211
212   windowSoundMap::iterator it=m_windowSoundMap.find(id);
213   if (it==m_windowSoundMap.end())
214     return;
215
216   CWindowSounds sounds=it->second;
217   CStdString strFile;
218   switch (event)
219   {
220   case SOUND_INIT:
221     strFile=sounds.strInitFile;
222     break;
223   case SOUND_DEINIT:
224     strFile=sounds.strDeInitFile;
225     break;
226   }
227
228   if (strFile.IsEmpty())
229     return;
230
231   //  One sound buffer for each window
232   windowSoundsMap::iterator itsb=m_windowSounds.find(id);
233   if (itsb!=m_windowSounds.end())
234   {
235     CGUISound* sound=itsb->second;
236     if (sound->IsPlaying())
237       sound->Stop();
238     delete sound;
239     m_windowSounds.erase(itsb++);
240   }
241
242   CGUISound* sound=new CGUISound();
243   if (!sound->Load(CUtil::AddFileToFolder(m_strMediaDir, strFile)))
244   {
245     delete sound;
246     return;
247   }
248
249   m_windowSounds.insert(pair<int, CGUISound*>(id, sound));
250   sound->Play();
251 }
252
253 // \brief Play a sound given by filename
254 void CGUIAudioManager::PlayPythonSound(const CStdString& strFileName)
255 {
256   CSingleLock lock(m_cs);
257
258   // it's not possible to play gui sounds when passthrough is active
259   if (!m_bEnabled || !m_bInitialized || g_audioContext.IsPassthroughActive())
260     return;
261
262   // If we already loaded the sound, just play it
263   pythonSoundsMap::iterator itsb=m_pythonSounds.find(strFileName);
264   if (itsb!=m_pythonSounds.end())
265   {
266     CGUISound* sound=itsb->second;
267     if (sound->IsPlaying())
268       sound->Stop();
269
270     sound->Play();
271
272     return;
273   }
274
275   CGUISound* sound=new CGUISound();
276   if (!sound->Load(strFileName))
277   {
278     delete sound;
279     return;
280   }
281
282   m_pythonSounds.insert(pair<CStdString, CGUISound*>(strFileName, sound));
283   sound->Play();
284 }
285
286 // \brief Load the config file (sounds.xml) for nav sounds
287 // Can be located in a folder "sounds" in the skin or from a
288 // subfolder of the folder "sounds" in the root directory of
289 // xbmc
290 bool CGUIAudioManager::Load()
291 {
292   CSingleLock lock(m_cs);
293
294   m_actionSoundMap.clear();
295   m_windowSoundMap.clear();
296
297   if (g_guiSettings.GetString("lookandfeel.soundskin")=="OFF")
298     return true;
299   else
300     Enable(true);
301
302   if (g_guiSettings.GetString("lookandfeel.soundskin")=="SKINDEFAULT")
303   {
304     m_strMediaDir = CUtil::AddFileToFolder(g_SkinInfo->Path(), "sounds");
305   }
306   else
307     m_strMediaDir = CUtil::AddFileToFolder("special://xbmc/sounds", g_guiSettings.GetString("lookandfeel.soundskin"));
308
309   CStdString strSoundsXml = CUtil::AddFileToFolder(m_strMediaDir, "sounds.xml");
310
311   //  Load our xml file
312   TiXmlDocument xmlDoc;
313
314   CLog::Log(LOGINFO, "Loading %s", strSoundsXml.c_str());
315
316   //  Load the config file
317   if (!xmlDoc.LoadFile(strSoundsXml))
318   {
319     CLog::Log(LOGNOTICE, "%s, Line %d\n%s", strSoundsXml.c_str(), xmlDoc.ErrorRow(), xmlDoc.ErrorDesc());
320     return false;
321   }
322
323   TiXmlElement* pRoot = xmlDoc.RootElement();
324   CStdString strValue = pRoot->Value();
325   if ( strValue != "sounds")
326   {
327     CLog::Log(LOGNOTICE, "%s Doesn't contain <sounds>", strSoundsXml.c_str());
328     return false;
329   }
330
331   //  Load sounds for actions
332   TiXmlElement* pActions = pRoot->FirstChildElement("actions");
333   if (pActions)
334   {
335     TiXmlNode* pAction = pActions->FirstChild("action");
336
337     while (pAction)
338     {
339       TiXmlNode* pIdNode = pAction->FirstChild("name");
340       int id = 0;    // action identity
341       if (pIdNode && pIdNode->FirstChild())
342       {
343         CButtonTranslator::TranslateActionString(pIdNode->FirstChild()->Value(), id);
344       }
345
346       TiXmlNode* pFileNode = pAction->FirstChild("file");
347       CStdString strFile;
348       if (pFileNode && pFileNode->FirstChild())
349         strFile+=pFileNode->FirstChild()->Value();
350
351       if (id > 0 && !strFile.IsEmpty())
352         m_actionSoundMap.insert(pair<int, CStdString>(id, strFile));
353
354       pAction = pAction->NextSibling();
355     }
356   }
357
358   //  Load window specific sounds
359   TiXmlElement* pWindows = pRoot->FirstChildElement("windows");
360   if (pWindows)
361   {
362     TiXmlNode* pWindow = pWindows->FirstChild("window");
363
364     while (pWindow)
365     {
366       int id = 0;
367
368       TiXmlNode* pIdNode = pWindow->FirstChild("name");
369       if (pIdNode)
370       {
371         if (pIdNode->FirstChild())
372           id = CButtonTranslator::TranslateWindow(pIdNode->FirstChild()->Value());
373       }
374
375       CWindowSounds sounds;
376       LoadWindowSound(pWindow, "activate", sounds.strInitFile);
377       LoadWindowSound(pWindow, "deactivate", sounds.strDeInitFile);
378
379       if (id > 0)
380         m_windowSoundMap.insert(pair<int, CWindowSounds>(id, sounds));
381
382       pWindow = pWindow->NextSibling();
383     }
384   }
385
386   return true;
387 }
388
389 // \brief Load a window node of the config file (sounds.xml)
390 bool CGUIAudioManager::LoadWindowSound(TiXmlNode* pWindowNode, const CStdString& strIdentifier, CStdString& strFile)
391 {
392   if (!pWindowNode)
393     return false;
394
395   TiXmlNode* pFileNode = pWindowNode->FirstChild(strIdentifier);
396   if (pFileNode && pFileNode->FirstChild())
397   {
398     strFile = pFileNode->FirstChild()->Value();
399     return true;
400   }
401
402   return false;
403 }
404
405 // \brief Enable/Disable nav sounds
406 void CGUIAudioManager::Enable(bool bEnable)
407 {
408   // always deinit audio when we don't want gui sounds
409   if (g_guiSettings.GetString("lookandfeel.soundskin")=="OFF")
410     bEnable = false;
411
412   CSingleLock lock(m_cs);
413
414   m_bEnabled = bEnable;
415
416   if (bEnable)
417     Initialize(CAudioContext::DEFAULT_DEVICE);
418   else
419     DeInitialize(CAudioContext::DEFAULT_DEVICE);
420 }
421
422 // \brief Sets the volume of all playing sounds
423 void CGUIAudioManager::SetVolume(int iLevel)
424 {
425   CSingleLock lock(m_cs);
426
427   if (m_actionSound)
428     m_actionSound->SetVolume(iLevel);
429
430   windowSoundsMap::iterator it=m_windowSounds.begin();
431   while (it!=m_windowSounds.end())
432   {
433     if (it->second)
434       it->second->SetVolume(iLevel);
435
436     ++it;
437   }
438
439   pythonSoundsMap::iterator it1=m_pythonSounds.begin();
440   while (it1!=m_pythonSounds.end())
441   {
442     if (it1->second)
443       it1->second->SetVolume(iLevel);
444
445     ++it1;
446   }
447 }