1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "AudioChannelService.h"
8 
9 #include "base/basictypes.h"
10 
11 #include "mozilla/Services.h"
12 #include "mozilla/StaticPtr.h"
13 #include "mozilla/Unused.h"
14 
15 #include "mozilla/dom/ContentChild.h"
16 #include "mozilla/dom/ContentParent.h"
17 #include "mozilla/dom/TabParent.h"
18 
19 #include "nsContentUtils.h"
20 #include "nsIScriptSecurityManager.h"
21 #include "nsISupportsPrimitives.h"
22 #include "nsThreadUtils.h"
23 #include "nsHashPropertyBag.h"
24 #include "nsComponentManagerUtils.h"
25 #include "nsGlobalWindow.h"
26 #include "nsPIDOMWindow.h"
27 #include "nsServiceManagerUtils.h"
28 #include "mozilla/dom/SettingChangeNotificationBinding.h"
29 
30 #ifdef MOZ_WIDGET_GONK
31 #include "nsJSUtils.h"
32 #include "SpeakerManagerService.h"
33 #endif
34 
35 #include "mozilla/Preferences.h"
36 
37 using namespace mozilla;
38 using namespace mozilla::dom;
39 using namespace mozilla::hal;
40 
41 namespace {
42 
43 // If true, any new AudioChannelAgent will be muted when created.
44 bool sAudioChannelMutedByDefault = false;
45 bool sAudioChannelCompeting = false;
46 bool sAudioChannelCompetingAllAgents = false;
47 bool sXPCOMShuttingDown = false;
48 
49 class NotifyChannelActiveRunnable final : public Runnable
50 {
51 public:
NotifyChannelActiveRunnable(uint64_t aWindowID,AudioChannel aAudioChannel,bool aActive)52   NotifyChannelActiveRunnable(uint64_t aWindowID, AudioChannel aAudioChannel,
53                               bool aActive)
54     : mWindowID(aWindowID)
55     , mAudioChannel(aAudioChannel)
56     , mActive(aActive)
57   {}
58 
Run()59   NS_IMETHOD Run() override
60   {
61     nsCOMPtr<nsIObserverService> observerService =
62       services::GetObserverService();
63     if (NS_WARN_IF(!observerService)) {
64       return NS_ERROR_FAILURE;
65     }
66 
67     nsCOMPtr<nsISupportsPRUint64> wrapper =
68       do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
69     if (NS_WARN_IF(!wrapper)) {
70        return NS_ERROR_FAILURE;
71     }
72 
73     wrapper->SetData(mWindowID);
74 
75     nsAutoString name;
76     AudioChannelService::GetAudioChannelString(mAudioChannel, name);
77 
78     nsAutoCString topic;
79     topic.Assign("audiochannel-activity-");
80     topic.Append(NS_ConvertUTF16toUTF8(name));
81 
82     observerService->NotifyObservers(wrapper, topic.get(),
83                                      mActive
84                                        ? u"active"
85                                        : u"inactive");
86 
87     // TODO : remove b2g related event in bug1299390.
88     observerService->NotifyObservers(wrapper,
89                                      "media-playback",
90                                      mActive
91                                        ? u"active"
92                                        : u"inactive");
93 
94     MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
95            ("NotifyChannelActiveRunnable, type = %d, active = %d\n",
96             mAudioChannel, mActive));
97 
98     return NS_OK;
99   }
100 
101 private:
102   const uint64_t mWindowID;
103   const AudioChannel mAudioChannel;
104   const bool mActive;
105 };
106 
107 bool
IsParentProcess()108 IsParentProcess()
109 {
110   return XRE_GetProcessType() == GeckoProcessType_Default;
111 }
112 
113 class AudioPlaybackRunnable final : public Runnable
114 {
115 public:
AudioPlaybackRunnable(nsPIDOMWindowOuter * aWindow,bool aActive,AudioChannelService::AudibleChangedReasons aReason)116   AudioPlaybackRunnable(nsPIDOMWindowOuter* aWindow, bool aActive,
117                         AudioChannelService::AudibleChangedReasons aReason)
118     : mWindow(aWindow)
119     , mActive(aActive)
120     , mReason(aReason)
121   {}
122 
Run()123  NS_IMETHOD Run() override
124  {
125     nsCOMPtr<nsIObserverService> observerService =
126       services::GetObserverService();
127     if (NS_WARN_IF(!observerService)) {
128       return NS_ERROR_FAILURE;
129     }
130 
131     nsAutoString state;
132     GetActiveState(state);
133 
134     observerService->NotifyObservers(ToSupports(mWindow),
135                                      "audio-playback",
136                                      state.get());
137 
138     MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
139            ("AudioPlaybackRunnable, active = %d, reason = %d\n",
140             mActive, mReason));
141 
142     return NS_OK;
143   }
144 
145 private:
GetActiveState(nsAString & astate)146   void GetActiveState(nsAString& astate)
147   {
148     if (mActive) {
149       CopyASCIItoUTF16("active", astate);
150     } else {
151       if(mReason == AudioChannelService::AudibleChangedReasons::ePauseStateChanged) {
152         CopyASCIItoUTF16("inactive-pause", astate);
153       } else {
154         CopyASCIItoUTF16("inactive-nonaudible", astate);
155       }
156     }
157   }
158 
159   nsCOMPtr<nsPIDOMWindowOuter> mWindow;
160   bool mActive;
161   AudioChannelService::AudibleChangedReasons mReason;
162 };
163 
164 bool
IsEnableAudioCompetingForAllAgents()165 IsEnableAudioCompetingForAllAgents()
166 {
167   // In general, the audio competing should only be for audible media and it
168   // helps user can focus on one media at the same time. However, we hope to
169   // treat all media as the same in the mobile device. First reason is we have
170   // media control on fennec and we just want to control one media at once time.
171   // Second reason is to reduce the bandwidth, avoiding to play any non-audible
172   // media in background which user doesn't notice about.
173 #ifdef MOZ_WIDGET_ANDROID
174   return true;
175 #else
176   return sAudioChannelCompetingAllAgents;
177 #endif
178 }
179 
180 } // anonymous namespace
181 
182 StaticRefPtr<AudioChannelService> gAudioChannelService;
183 
184 // Mappings from 'mozaudiochannel' attribute strings to an enumeration.
185 static const nsAttrValue::EnumTable kMozAudioChannelAttributeTable[] = {
186   { "normal",             (int16_t)AudioChannel::Normal },
187   { "content",            (int16_t)AudioChannel::Content },
188   { "notification",       (int16_t)AudioChannel::Notification },
189   { "alarm",              (int16_t)AudioChannel::Alarm },
190   { "telephony",          (int16_t)AudioChannel::Telephony },
191   { "ringer",             (int16_t)AudioChannel::Ringer },
192   { "publicnotification", (int16_t)AudioChannel::Publicnotification },
193   { "system",             (int16_t)AudioChannel::System },
194   { nullptr,              0 }
195 };
196 
197 /* static */ void
CreateServiceIfNeeded()198 AudioChannelService::CreateServiceIfNeeded()
199 {
200   MOZ_ASSERT(NS_IsMainThread());
201 
202   if (!gAudioChannelService) {
203     gAudioChannelService = new AudioChannelService();
204   }
205 }
206 
207 /* static */ already_AddRefed<AudioChannelService>
GetOrCreate()208 AudioChannelService::GetOrCreate()
209 {
210   if (sXPCOMShuttingDown) {
211     return nullptr;
212   }
213 
214   CreateServiceIfNeeded();
215   RefPtr<AudioChannelService> service = gAudioChannelService.get();
216   return service.forget();
217 }
218 
219 /* static */ PRLogModuleInfo*
GetAudioChannelLog()220 AudioChannelService::GetAudioChannelLog()
221 {
222   static PRLogModuleInfo *gAudioChannelLog;
223   if (!gAudioChannelLog) {
224     gAudioChannelLog = PR_NewLogModule("AudioChannel");
225   }
226   return gAudioChannelLog;
227 }
228 
229 /* static */ void
Shutdown()230 AudioChannelService::Shutdown()
231 {
232   if (gAudioChannelService) {
233     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
234     if (obs) {
235       obs->RemoveObserver(gAudioChannelService, "xpcom-shutdown");
236       obs->RemoveObserver(gAudioChannelService, "outer-window-destroyed");
237 
238       if (IsParentProcess()) {
239         obs->RemoveObserver(gAudioChannelService, "ipc:content-shutdown");
240 
241 #ifdef MOZ_WIDGET_GONK
242         // To monitor the volume settings based on audio channel.
243         obs->RemoveObserver(gAudioChannelService, "mozsettings-changed");
244 #endif
245       }
246     }
247 
248     gAudioChannelService->mWindows.Clear();
249     gAudioChannelService->mPlayingChildren.Clear();
250     gAudioChannelService->mTabParents.Clear();
251 #ifdef MOZ_WIDGET_GONK
252     gAudioChannelService->mSpeakerManager.Clear();
253 #endif
254 
255     gAudioChannelService = nullptr;
256   }
257 }
258 
259 /* static */ bool
IsEnableAudioCompeting()260 AudioChannelService::IsEnableAudioCompeting()
261 {
262   CreateServiceIfNeeded();
263   return sAudioChannelCompeting;
264 }
265 
266 NS_INTERFACE_MAP_BEGIN(AudioChannelService)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,nsIAudioChannelService)267   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAudioChannelService)
268   NS_INTERFACE_MAP_ENTRY(nsIAudioChannelService)
269   NS_INTERFACE_MAP_ENTRY(nsIObserver)
270 NS_INTERFACE_MAP_END
271 
272 NS_IMPL_ADDREF(AudioChannelService)
273 NS_IMPL_RELEASE(AudioChannelService)
274 
275 AudioChannelService::AudioChannelService()
276   : mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN)
277   , mTelephonyChannel(false)
278   , mContentOrNormalChannel(false)
279   , mAnyChannel(false)
280 {
281   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
282   if (obs) {
283     obs->AddObserver(this, "xpcom-shutdown", false);
284     obs->AddObserver(this, "outer-window-destroyed", false);
285     if (IsParentProcess()) {
286       obs->AddObserver(this, "ipc:content-shutdown", false);
287 
288 #ifdef MOZ_WIDGET_GONK
289       // To monitor the volume settings based on audio channel.
290       obs->AddObserver(this, "mozsettings-changed", false);
291 #endif
292     }
293   }
294 
295   Preferences::AddBoolVarCache(&sAudioChannelMutedByDefault,
296                                "dom.audiochannel.mutedByDefault");
297   Preferences::AddBoolVarCache(&sAudioChannelCompeting,
298                                "dom.audiochannel.audioCompeting");
299   Preferences::AddBoolVarCache(&sAudioChannelCompetingAllAgents,
300                                "dom.audiochannel.audioCompeting.allAgents");
301 }
302 
~AudioChannelService()303 AudioChannelService::~AudioChannelService()
304 {
305 }
306 
307 void
RegisterAudioChannelAgent(AudioChannelAgent * aAgent,AudibleState aAudible)308 AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
309                                                AudibleState aAudible)
310 {
311   MOZ_ASSERT(aAgent);
312 
313   uint64_t windowID = aAgent->WindowID();
314   AudioChannelWindow* winData = GetWindowData(windowID);
315   if (!winData) {
316     winData = new AudioChannelWindow(windowID);
317     mWindows.AppendElement(winData);
318   }
319 
320   // To make sure agent would be alive because AppendAgent() would trigger the
321   // callback function of AudioChannelAgentOwner that means the agent might be
322   // released in their callback.
323   RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
324   winData->AppendAgent(aAgent, aAudible);
325 
326   MaybeSendStatusUpdate();
327 }
328 
329 void
UnregisterAudioChannelAgent(AudioChannelAgent * aAgent)330 AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
331 {
332   MOZ_ASSERT(aAgent);
333 
334   AudioChannelWindow* winData = GetWindowData(aAgent->WindowID());
335   if (!winData) {
336     return;
337   }
338 
339   // To make sure agent would be alive because AppendAgent() would trigger the
340   // callback function of AudioChannelAgentOwner that means the agent might be
341   // released in their callback.
342   RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
343   winData->RemoveAgent(aAgent);
344 
345 #ifdef MOZ_WIDGET_GONK
346   bool active = AnyAudioChannelIsActive();
347   for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
348     mSpeakerManager[i]->SetAudioChannelActive(active);
349   }
350 #endif
351 
352   MaybeSendStatusUpdate();
353 }
354 
355 void
RegisterTabParent(TabParent * aTabParent)356 AudioChannelService::RegisterTabParent(TabParent* aTabParent)
357 {
358   MOZ_ASSERT(aTabParent);
359   MOZ_ASSERT(!mTabParents.Contains(aTabParent));
360   mTabParents.AppendElement(aTabParent);
361 }
362 
363 void
UnregisterTabParent(TabParent * aTabParent)364 AudioChannelService::UnregisterTabParent(TabParent* aTabParent)
365 {
366   MOZ_ASSERT(aTabParent);
367   mTabParents.RemoveElement(aTabParent);
368 }
369 
370 AudioPlaybackConfig
GetMediaConfig(nsPIDOMWindowOuter * aWindow,uint32_t aAudioChannel) const371 AudioChannelService::GetMediaConfig(nsPIDOMWindowOuter* aWindow,
372                                     uint32_t aAudioChannel) const
373 {
374   MOZ_ASSERT(!aWindow || aWindow->IsOuterWindow());
375   MOZ_ASSERT(aAudioChannel < NUMBER_OF_AUDIO_CHANNELS);
376 
377   AudioPlaybackConfig config(1.0, false,
378                              nsISuspendedTypes::NONE_SUSPENDED);
379 
380   if (!aWindow || !aWindow->IsOuterWindow()) {
381     config.SetConfig(0.0, true,
382                      nsISuspendedTypes::SUSPENDED_BLOCK);
383     return config;
384   }
385 
386   AudioChannelWindow* winData = nullptr;
387   nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
388 
389   // The volume must be calculated based on the window hierarchy. Here we go up
390   // to the top window and we calculate the volume and the muted flag.
391   do {
392     winData = GetWindowData(window->WindowID());
393     if (winData) {
394       config.mVolume *= winData->mChannels[aAudioChannel].mVolume;
395       config.mMuted = config.mMuted || winData->mChannels[aAudioChannel].mMuted;
396       config.mSuspend = winData->mOwningAudioFocus ?
397         config.mSuspend : nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE;
398     }
399 
400     config.mVolume *= window->GetAudioVolume();
401     config.mMuted = config.mMuted || window->GetAudioMuted();
402     if (window->GetMediaSuspend() != nsISuspendedTypes::NONE_SUSPENDED) {
403       config.mSuspend = window->GetMediaSuspend();
404     }
405 
406     nsCOMPtr<nsPIDOMWindowOuter> win = window->GetScriptableParentOrNull();
407     if (!win) {
408       break;
409     }
410 
411     window = do_QueryInterface(win);
412 
413     // If there is no parent, or we are the toplevel we don't continue.
414   } while (window && window != aWindow);
415 
416   return config;
417 }
418 
419 void
AudioAudibleChanged(AudioChannelAgent * aAgent,AudibleState aAudible,AudibleChangedReasons aReason)420 AudioChannelService::AudioAudibleChanged(AudioChannelAgent* aAgent,
421                                          AudibleState aAudible,
422                                          AudibleChangedReasons aReason)
423 {
424   MOZ_ASSERT(aAgent);
425 
426   uint64_t windowID = aAgent->WindowID();
427   AudioChannelWindow* winData = GetWindowData(windowID);
428   if (winData) {
429     winData->AudioAudibleChanged(aAgent, aAudible, aReason);
430   }
431 }
432 
433 bool
TelephonyChannelIsActive()434 AudioChannelService::TelephonyChannelIsActive()
435 {
436   nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator windowsIter(mWindows);
437   while (windowsIter.HasMore()) {
438     AudioChannelWindow* next = windowsIter.GetNext();
439     if (next->mChannels[(uint32_t)AudioChannel::Telephony].mNumberOfAgents != 0 &&
440         !next->mChannels[(uint32_t)AudioChannel::Telephony].mMuted) {
441       return true;
442     }
443   }
444 
445   if (IsParentProcess()) {
446     nsTObserverArray<nsAutoPtr<AudioChannelChildStatus>>::ForwardIterator
447       childrenIter(mPlayingChildren);
448     while (childrenIter.HasMore()) {
449       AudioChannelChildStatus* child = childrenIter.GetNext();
450       if (child->mActiveTelephonyChannel) {
451         return true;
452       }
453     }
454   }
455 
456   return false;
457 }
458 
459 bool
ContentOrNormalChannelIsActive()460 AudioChannelService::ContentOrNormalChannelIsActive()
461 {
462   // This method is meant to be used just by the child to send status update.
463   MOZ_ASSERT(!IsParentProcess());
464 
465   nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator iter(mWindows);
466   while (iter.HasMore()) {
467     AudioChannelWindow* next = iter.GetNext();
468     if (next->mChannels[(uint32_t)AudioChannel::Content].mNumberOfAgents > 0 ||
469         next->mChannels[(uint32_t)AudioChannel::Normal].mNumberOfAgents > 0) {
470       return true;
471     }
472   }
473   return false;
474 }
475 
476 AudioChannelService::AudioChannelChildStatus*
GetChildStatus(uint64_t aChildID) const477 AudioChannelService::GetChildStatus(uint64_t aChildID) const
478 {
479   nsTObserverArray<nsAutoPtr<AudioChannelChildStatus>>::ForwardIterator
480     iter(mPlayingChildren);
481   while (iter.HasMore()) {
482     AudioChannelChildStatus* child = iter.GetNext();
483     if (child->mChildID == aChildID) {
484       return child;
485     }
486   }
487 
488   return nullptr;
489 }
490 
491 void
RemoveChildStatus(uint64_t aChildID)492 AudioChannelService::RemoveChildStatus(uint64_t aChildID)
493 {
494   nsTObserverArray<nsAutoPtr<AudioChannelChildStatus>>::ForwardIterator
495     iter(mPlayingChildren);
496   while (iter.HasMore()) {
497     nsAutoPtr<AudioChannelChildStatus>& child = iter.GetNext();
498     if (child->mChildID == aChildID) {
499       mPlayingChildren.RemoveElement(child);
500       break;
501     }
502   }
503 }
504 
505 bool
ProcessContentOrNormalChannelIsActive(uint64_t aChildID)506 AudioChannelService::ProcessContentOrNormalChannelIsActive(uint64_t aChildID)
507 {
508   AudioChannelChildStatus* child = GetChildStatus(aChildID);
509   if (!child) {
510     return false;
511   }
512 
513   return child->mActiveContentOrNormalChannel;
514 }
515 
516 bool
AnyAudioChannelIsActive()517 AudioChannelService::AnyAudioChannelIsActive()
518 {
519   nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator iter(mWindows);
520   while (iter.HasMore()) {
521     AudioChannelWindow* next = iter.GetNext();
522     for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
523       if (next->mChannels[kMozAudioChannelAttributeTable[i].value].mNumberOfAgents
524           != 0) {
525         return true;
526       }
527     }
528   }
529 
530   if (IsParentProcess()) {
531     return !mPlayingChildren.IsEmpty();
532   }
533 
534   return false;
535 }
536 
537 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)538 AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic,
539                              const char16_t* aData)
540 {
541   if (!strcmp(aTopic, "xpcom-shutdown")) {
542     sXPCOMShuttingDown = true;
543     Shutdown();
544   } else if (!strcmp(aTopic, "outer-window-destroyed")) {
545     nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
546     NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
547 
548     uint64_t outerID;
549     nsresult rv = wrapper->GetData(&outerID);
550     if (NS_WARN_IF(NS_FAILED(rv))) {
551       return rv;
552     }
553 
554     nsAutoPtr<AudioChannelWindow> winData;
555     {
556       nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator
557         iter(mWindows);
558       while (iter.HasMore()) {
559         nsAutoPtr<AudioChannelWindow>& next = iter.GetNext();
560         if (next->mWindowID == outerID) {
561           uint32_t pos = mWindows.IndexOf(next);
562           winData = next.forget();
563           mWindows.RemoveElementAt(pos);
564           break;
565         }
566       }
567     }
568 
569     if (winData) {
570       nsTObserverArray<AudioChannelAgent*>::ForwardIterator
571         iter(winData->mAgents);
572       while (iter.HasMore()) {
573         iter.GetNext()->WindowVolumeChanged();
574       }
575     }
576 
577 #ifdef MOZ_WIDGET_GONK
578     bool active = AnyAudioChannelIsActive();
579     for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
580       mSpeakerManager[i]->SetAudioChannelActive(active);
581     }
582 #endif
583   } else if (!strcmp(aTopic, "ipc:content-shutdown")) {
584     nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
585     if (!props) {
586       NS_WARNING("ipc:content-shutdown message without property bag as subject");
587       return NS_OK;
588     }
589 
590     uint64_t childID = 0;
591     nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"),
592                                              &childID);
593     if (NS_WARN_IF(NS_FAILED(rv))) {
594       return rv;
595     }
596 
597     if (mDefChannelChildID == childID) {
598       SetDefaultVolumeControlChannelInternal(-1, false, childID);
599       mDefChannelChildID = CONTENT_PROCESS_ID_UNKNOWN;
600     }
601 
602     RemoveChildStatus(childID);
603   }
604 
605   return NS_OK;
606 }
607 
608 void
RefreshAgentsVolumeAndPropagate(AudioChannel aAudioChannel,nsPIDOMWindowOuter * aWindow)609 AudioChannelService::RefreshAgentsVolumeAndPropagate(AudioChannel aAudioChannel,
610                                                      nsPIDOMWindowOuter* aWindow)
611 {
612   MOZ_ASSERT(aWindow);
613   MOZ_ASSERT(aWindow->IsOuterWindow());
614 
615   nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
616   if (!topWindow) {
617     return;
618   }
619 
620   AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
621   if (!winData) {
622     return;
623   }
624 
625   for (uint32_t i = 0; i < mTabParents.Length(); ++i) {
626     mTabParents[i]->AudioChannelChangeNotification(aWindow, aAudioChannel,
627                                                    winData->mChannels[(uint32_t)aAudioChannel].mVolume,
628                                                    winData->mChannels[(uint32_t)aAudioChannel].mMuted);
629   }
630 
631   RefreshAgentsVolume(aWindow);
632 }
633 
634 void
RefreshAgents(nsPIDOMWindowOuter * aWindow,mozilla::function<void (AudioChannelAgent *)> aFunc)635 AudioChannelService::RefreshAgents(nsPIDOMWindowOuter* aWindow,
636                                    mozilla::function<void(AudioChannelAgent*)> aFunc)
637 {
638   MOZ_ASSERT(aWindow);
639   MOZ_ASSERT(aWindow->IsOuterWindow());
640 
641   nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
642   if (!topWindow) {
643     return;
644   }
645 
646   AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
647   if (!winData) {
648     return;
649   }
650 
651   nsTObserverArray<AudioChannelAgent*>::ForwardIterator
652     iter(winData->mAgents);
653   while (iter.HasMore()) {
654     aFunc(iter.GetNext());
655   }
656 }
657 
658 void
RefreshAgentsVolume(nsPIDOMWindowOuter * aWindow)659 AudioChannelService::RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow)
660 {
661   RefreshAgents(aWindow, [] (AudioChannelAgent* agent) {
662     agent->WindowVolumeChanged();
663   });
664 }
665 
666 void
RefreshAgentsSuspend(nsPIDOMWindowOuter * aWindow,nsSuspendedTypes aSuspend)667 AudioChannelService::RefreshAgentsSuspend(nsPIDOMWindowOuter* aWindow,
668                                           nsSuspendedTypes aSuspend)
669 {
670   RefreshAgents(aWindow, [aSuspend] (AudioChannelAgent* agent) {
671     agent->WindowSuspendChanged(aSuspend);
672   });
673 }
674 
675 void
SetWindowAudioCaptured(nsPIDOMWindowOuter * aWindow,uint64_t aInnerWindowID,bool aCapture)676 AudioChannelService::SetWindowAudioCaptured(nsPIDOMWindowOuter* aWindow,
677                                             uint64_t aInnerWindowID,
678                                             bool aCapture)
679 {
680   MOZ_ASSERT(NS_IsMainThread());
681   MOZ_ASSERT(aWindow);
682   MOZ_ASSERT(aWindow->IsOuterWindow());
683 
684   MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
685          ("AudioChannelService, SetWindowAudioCaptured, window = %p, "
686           "aCapture = %d\n", aWindow, aCapture));
687 
688   nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
689   if (!topWindow) {
690     return;
691   }
692 
693   AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
694 
695   // This can happen, but only during shutdown, because the the outer window
696   // changes ScriptableTop, so that its ID is different.
697   // In this case either we are capturing, and it's too late because the window
698   // has been closed anyways, or we are un-capturing, and everything has already
699   // been cleaned up by the HTMLMediaElements or the AudioContexts.
700   if (!winData) {
701     return;
702   }
703 
704   if (aCapture != winData->mIsAudioCaptured) {
705     winData->mIsAudioCaptured = aCapture;
706     nsTObserverArray<AudioChannelAgent*>::ForwardIterator
707       iter(winData->mAgents);
708     while (iter.HasMore()) {
709       iter.GetNext()->WindowAudioCaptureChanged(aInnerWindowID, aCapture);
710     }
711   }
712 }
713 
714 /* static */ const nsAttrValue::EnumTable*
GetAudioChannelTable()715 AudioChannelService::GetAudioChannelTable()
716 {
717   return kMozAudioChannelAttributeTable;
718 }
719 
720 /* static */ AudioChannel
GetAudioChannel(const nsAString & aChannel)721 AudioChannelService::GetAudioChannel(const nsAString& aChannel)
722 {
723   for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
724     if (aChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
725       return static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value);
726     }
727   }
728 
729   return AudioChannel::Normal;
730 }
731 
732 /* static */ AudioChannel
GetDefaultAudioChannel()733 AudioChannelService::GetDefaultAudioChannel()
734 {
735   nsAutoString audioChannel(Preferences::GetString("media.defaultAudioChannel"));
736   if (audioChannel.IsEmpty()) {
737     return AudioChannel::Normal;
738   }
739 
740   for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
741     if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
742       return static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value);
743     }
744   }
745 
746   return AudioChannel::Normal;
747 }
748 
749 /* static */ void
GetAudioChannelString(AudioChannel aChannel,nsAString & aString)750 AudioChannelService::GetAudioChannelString(AudioChannel aChannel,
751                                            nsAString& aString)
752 {
753   aString.AssignASCII("normal");
754 
755   for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
756     if (aChannel ==
757         static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value)) {
758       aString.AssignASCII(kMozAudioChannelAttributeTable[i].tag);
759       break;
760     }
761   }
762 }
763 
764 /* static */ void
GetDefaultAudioChannelString(nsAString & aString)765 AudioChannelService::GetDefaultAudioChannelString(nsAString& aString)
766 {
767   aString.AssignASCII("normal");
768 
769   nsAutoString audioChannel(Preferences::GetString("media.defaultAudioChannel"));
770   if (!audioChannel.IsEmpty()) {
771     for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
772       if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
773         aString = audioChannel;
774         break;
775       }
776     }
777   }
778 }
779 
780 AudioChannelService::AudioChannelWindow*
GetOrCreateWindowData(nsPIDOMWindowOuter * aWindow)781 AudioChannelService::GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow)
782 {
783   MOZ_ASSERT(NS_IsMainThread());
784   MOZ_ASSERT(aWindow);
785   MOZ_ASSERT(aWindow->IsOuterWindow());
786 
787   AudioChannelWindow* winData = GetWindowData(aWindow->WindowID());
788   if (!winData) {
789     winData = new AudioChannelWindow(aWindow->WindowID());
790     mWindows.AppendElement(winData);
791   }
792 
793   return winData;
794 }
795 
796 AudioChannelService::AudioChannelWindow*
GetWindowData(uint64_t aWindowID) const797 AudioChannelService::GetWindowData(uint64_t aWindowID) const
798 {
799   nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator
800     iter(mWindows);
801   while (iter.HasMore()) {
802     AudioChannelWindow* next = iter.GetNext();
803     if (next->mWindowID == aWindowID) {
804       return next;
805     }
806   }
807 
808   return nullptr;
809 }
810 
811 float
GetAudioChannelVolume(nsPIDOMWindowOuter * aWindow,AudioChannel aAudioChannel)812 AudioChannelService::GetAudioChannelVolume(nsPIDOMWindowOuter* aWindow,
813                                            AudioChannel aAudioChannel)
814 {
815   MOZ_ASSERT(NS_IsMainThread());
816   MOZ_ASSERT(aWindow);
817   MOZ_ASSERT(aWindow->IsOuterWindow());
818 
819   AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
820   return winData->mChannels[(uint32_t)aAudioChannel].mVolume;
821 }
822 
823 NS_IMETHODIMP
GetAudioChannelVolume(mozIDOMWindowProxy * aWindow,unsigned short aAudioChannel,float * aVolume)824 AudioChannelService::GetAudioChannelVolume(mozIDOMWindowProxy* aWindow,
825                                            unsigned short aAudioChannel,
826                                            float* aVolume)
827 {
828   MOZ_ASSERT(NS_IsMainThread());
829 
830   auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
831   *aVolume = GetAudioChannelVolume(window, (AudioChannel)aAudioChannel);
832   return NS_OK;
833 }
834 
835 void
SetAudioChannelVolume(nsPIDOMWindowOuter * aWindow,AudioChannel aAudioChannel,float aVolume)836 AudioChannelService::SetAudioChannelVolume(nsPIDOMWindowOuter* aWindow,
837                                            AudioChannel aAudioChannel,
838                                            float aVolume)
839 {
840   MOZ_ASSERT(NS_IsMainThread());
841   MOZ_ASSERT(aWindow);
842   MOZ_ASSERT(aWindow->IsOuterWindow());
843 
844   MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
845          ("AudioChannelService, SetAudioChannelVolume, window = %p, type = %d, "
846           "volume = %f\n", aWindow, aAudioChannel, aVolume));
847 
848   AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
849   winData->mChannels[(uint32_t)aAudioChannel].mVolume = aVolume;
850   RefreshAgentsVolumeAndPropagate(aAudioChannel, aWindow);
851 }
852 
853 NS_IMETHODIMP
SetAudioChannelVolume(mozIDOMWindowProxy * aWindow,unsigned short aAudioChannel,float aVolume)854 AudioChannelService::SetAudioChannelVolume(mozIDOMWindowProxy* aWindow,
855                                            unsigned short aAudioChannel,
856                                            float aVolume)
857 {
858   MOZ_ASSERT(NS_IsMainThread());
859 
860   auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
861   SetAudioChannelVolume(window, (AudioChannel)aAudioChannel, aVolume);
862   return NS_OK;
863 }
864 
865 bool
GetAudioChannelMuted(nsPIDOMWindowOuter * aWindow,AudioChannel aAudioChannel)866 AudioChannelService::GetAudioChannelMuted(nsPIDOMWindowOuter* aWindow,
867                                           AudioChannel aAudioChannel)
868 {
869   MOZ_ASSERT(NS_IsMainThread());
870   MOZ_ASSERT(aWindow);
871   MOZ_ASSERT(aWindow->IsOuterWindow());
872 
873   AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
874   return winData->mChannels[(uint32_t)aAudioChannel].mMuted;
875 }
876 
877 NS_IMETHODIMP
GetAudioChannelMuted(mozIDOMWindowProxy * aWindow,unsigned short aAudioChannel,bool * aMuted)878 AudioChannelService::GetAudioChannelMuted(mozIDOMWindowProxy* aWindow,
879                                           unsigned short aAudioChannel,
880                                           bool* aMuted)
881 {
882   MOZ_ASSERT(NS_IsMainThread());
883 
884   auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
885   *aMuted = GetAudioChannelMuted(window, (AudioChannel)aAudioChannel);
886   return NS_OK;
887 }
888 
889 void
SetAudioChannelMuted(nsPIDOMWindowOuter * aWindow,AudioChannel aAudioChannel,bool aMuted)890 AudioChannelService::SetAudioChannelMuted(nsPIDOMWindowOuter* aWindow,
891                                           AudioChannel aAudioChannel,
892                                           bool aMuted)
893 {
894   MOZ_ASSERT(NS_IsMainThread());
895   MOZ_ASSERT(aWindow);
896   MOZ_ASSERT(aWindow->IsOuterWindow());
897 
898   MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
899          ("AudioChannelService, SetAudioChannelMuted, window = %p, type = %d, "
900           "mute = %d\n", aWindow, aAudioChannel, aMuted));
901 
902   if (aAudioChannel == AudioChannel::System) {
903     // Workaround for bug1183033, system channel type can always playback.
904     return;
905   }
906 
907   AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
908   winData->mChannels[(uint32_t)aAudioChannel].mMuted = aMuted;
909   RefreshAgentsVolumeAndPropagate(aAudioChannel, aWindow);
910 }
911 
912 NS_IMETHODIMP
SetAudioChannelMuted(mozIDOMWindowProxy * aWindow,unsigned short aAudioChannel,bool aMuted)913 AudioChannelService::SetAudioChannelMuted(mozIDOMWindowProxy* aWindow,
914                                           unsigned short aAudioChannel,
915                                           bool aMuted)
916 {
917   MOZ_ASSERT(NS_IsMainThread());
918 
919   auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
920   SetAudioChannelMuted(window, (AudioChannel)aAudioChannel, aMuted);
921   return NS_OK;
922 }
923 
924 bool
IsAudioChannelActive(nsPIDOMWindowOuter * aWindow,AudioChannel aAudioChannel)925 AudioChannelService::IsAudioChannelActive(nsPIDOMWindowOuter* aWindow,
926                                           AudioChannel aAudioChannel)
927 {
928   MOZ_ASSERT(NS_IsMainThread());
929   MOZ_ASSERT(aWindow);
930   MOZ_ASSERT(aWindow->IsOuterWindow());
931 
932   AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
933   return !!winData->mChannels[(uint32_t)aAudioChannel].mNumberOfAgents;
934 }
935 
936 NS_IMETHODIMP
IsAudioChannelActive(mozIDOMWindowProxy * aWindow,unsigned short aAudioChannel,bool * aActive)937 AudioChannelService::IsAudioChannelActive(mozIDOMWindowProxy* aWindow,
938                                           unsigned short aAudioChannel,
939                                           bool* aActive)
940 {
941   MOZ_ASSERT(NS_IsMainThread());
942 
943   auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
944   *aActive = IsAudioChannelActive(window, (AudioChannel)aAudioChannel);
945   return NS_OK;
946 }
947 void
SetDefaultVolumeControlChannel(int32_t aChannel,bool aVisible)948 AudioChannelService::SetDefaultVolumeControlChannel(int32_t aChannel,
949                                                     bool aVisible)
950 {
951   SetDefaultVolumeControlChannelInternal(aChannel, aVisible,
952                                          CONTENT_PROCESS_ID_MAIN);
953 }
954 
955 void
SetDefaultVolumeControlChannelInternal(int32_t aChannel,bool aVisible,uint64_t aChildID)956 AudioChannelService::SetDefaultVolumeControlChannelInternal(int32_t aChannel,
957                                                             bool aVisible,
958                                                             uint64_t aChildID)
959 {
960   if (!IsParentProcess()) {
961     ContentChild* cc = ContentChild::GetSingleton();
962     if (cc) {
963       cc->SendAudioChannelChangeDefVolChannel(aChannel, aVisible);
964     }
965 
966     return;
967   }
968 
969   // If this child is in the background and mDefChannelChildID is set to
970   // others then it means other child in the foreground already set it's
971   // own default channel.
972   if (!aVisible && mDefChannelChildID != aChildID) {
973     return;
974   }
975 
976   // Workaround for the call screen app. The call screen app is running on the
977   // main process, that will results in wrong visible state. Because we use the
978   // docshell's active state as visible state, the main process is always
979   // active. Therefore, we will see the strange situation that the visible
980   // state of the call screen is always true. If the mDefChannelChildID is set
981   // to others then it means other child in the foreground already set it's
982   // own default channel already.
983   // Summary :
984   //   Child process : foreground app always can set type.
985   //   Parent process : check the mDefChannelChildID.
986   else if (aChildID == CONTENT_PROCESS_ID_MAIN &&
987            mDefChannelChildID != CONTENT_PROCESS_ID_UNKNOWN) {
988     return;
989   }
990 
991   mDefChannelChildID = aVisible ? aChildID : CONTENT_PROCESS_ID_UNKNOWN;
992   nsAutoString channelName;
993 
994   if (aChannel == -1) {
995     channelName.AssignASCII("unknown");
996   } else {
997     GetAudioChannelString(static_cast<AudioChannel>(aChannel), channelName);
998   }
999 
1000   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1001   if (obs) {
1002     obs->NotifyObservers(nullptr, "default-volume-channel-changed",
1003                          channelName.get());
1004   }
1005 }
1006 
1007 void
MaybeSendStatusUpdate()1008 AudioChannelService::MaybeSendStatusUpdate()
1009 {
1010   if (IsParentProcess()) {
1011     return;
1012   }
1013 
1014   bool telephonyChannel = TelephonyChannelIsActive();
1015   bool contentOrNormalChannel = ContentOrNormalChannelIsActive();
1016   bool anyChannel = AnyAudioChannelIsActive();
1017 
1018   if (telephonyChannel == mTelephonyChannel &&
1019       contentOrNormalChannel == mContentOrNormalChannel &&
1020       anyChannel == mAnyChannel) {
1021     return;
1022   }
1023 
1024   mTelephonyChannel = telephonyChannel;
1025   mContentOrNormalChannel = contentOrNormalChannel;
1026   mAnyChannel = anyChannel;
1027 
1028   ContentChild* cc = ContentChild::GetSingleton();
1029   if (cc) {
1030     cc->SendAudioChannelServiceStatus(telephonyChannel, contentOrNormalChannel,
1031                                       anyChannel);
1032   }
1033 }
1034 
1035 void
ChildStatusReceived(uint64_t aChildID,bool aTelephonyChannel,bool aContentOrNormalChannel,bool aAnyChannel)1036 AudioChannelService::ChildStatusReceived(uint64_t aChildID,
1037                                          bool aTelephonyChannel,
1038                                          bool aContentOrNormalChannel,
1039                                          bool aAnyChannel)
1040 {
1041   if (!aAnyChannel) {
1042     RemoveChildStatus(aChildID);
1043     return;
1044   }
1045 
1046   AudioChannelChildStatus* data = GetChildStatus(aChildID);
1047   if (!data) {
1048     data = new AudioChannelChildStatus(aChildID);
1049     mPlayingChildren.AppendElement(data);
1050   }
1051 
1052   data->mActiveTelephonyChannel = aTelephonyChannel;
1053   data->mActiveContentOrNormalChannel = aContentOrNormalChannel;
1054 }
1055 
1056 void
RefreshAgentsAudioFocusChanged(AudioChannelAgent * aAgent)1057 AudioChannelService::RefreshAgentsAudioFocusChanged(AudioChannelAgent* aAgent)
1058 {
1059   MOZ_ASSERT(aAgent);
1060 
1061   nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator
1062     iter(mWindows);
1063   while (iter.HasMore()) {
1064     AudioChannelWindow* winData = iter.GetNext();
1065     if (winData->mOwningAudioFocus) {
1066       winData->AudioFocusChanged(aAgent);
1067     }
1068   }
1069 }
1070 
1071 void
RequestAudioFocus(AudioChannelAgent * aAgent)1072 AudioChannelService::AudioChannelWindow::RequestAudioFocus(AudioChannelAgent* aAgent)
1073 {
1074   MOZ_ASSERT(aAgent);
1075 
1076   // Don't need to check audio focus for window-less agent.
1077   if (!aAgent->Window()) {
1078     return;
1079   }
1080 
1081   // We already have the audio focus. No operation is needed.
1082   if (mOwningAudioFocus) {
1083     return;
1084   }
1085 
1086   // Only foreground window can request audio focus, but it would still own the
1087   // audio focus even it goes to background. Audio focus would be abandoned
1088   // only when other foreground window starts audio competing.
1089   // One exception is if the pref "media.block-autoplay-until-in-foreground"
1090   // is on and the background page is the non-visited before. Because the media
1091   // in that page would be blocked until the page is going to foreground.
1092   mOwningAudioFocus = (!(aAgent->Window()->IsBackground()) ||
1093                        aAgent->Window()->GetMediaSuspend() == nsISuspendedTypes::SUSPENDED_BLOCK) ;
1094 
1095   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1096          ("AudioChannelWindow, RequestAudioFocus, this = %p, "
1097           "agent = %p, owning audio focus = %d\n",
1098           this, aAgent, mOwningAudioFocus));
1099 }
1100 
1101 void
NotifyAudioCompetingChanged(AudioChannelAgent * aAgent)1102 AudioChannelService::AudioChannelWindow::NotifyAudioCompetingChanged(AudioChannelAgent* aAgent)
1103 {
1104   // This function may be called after RemoveAgentAndReduceAgentsNum(), so the
1105   // agent may be not contained in mAgent. In addition, the agent would still
1106   // be alive because we have kungFuDeathGrip in UnregisterAudioChannelAgent().
1107   MOZ_ASSERT(aAgent);
1108 
1109   RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
1110   MOZ_ASSERT(service);
1111 
1112   if (!service->IsEnableAudioCompeting()) {
1113     return;
1114   }
1115 
1116   if (!IsAgentInvolvingInAudioCompeting(aAgent)) {
1117     return;
1118   }
1119 
1120   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1121          ("AudioChannelWindow, NotifyAudioCompetingChanged, this = %p, "
1122           "agent = %p\n",
1123           this, aAgent));
1124 
1125   service->RefreshAgentsAudioFocusChanged(aAgent);
1126 }
1127 
1128 bool
IsAgentInvolvingInAudioCompeting(AudioChannelAgent * aAgent) const1129 AudioChannelService::AudioChannelWindow::IsAgentInvolvingInAudioCompeting(AudioChannelAgent* aAgent) const
1130 {
1131   MOZ_ASSERT(aAgent);
1132 
1133   if(!mOwningAudioFocus) {
1134     return false;
1135   }
1136 
1137   if (IsAudioCompetingInSameTab()) {
1138     return false;
1139   }
1140 
1141   // TODO : add MediaSession::ambient kind, because it doens't interact with
1142   // other kinds.
1143   return true;
1144 }
1145 
1146 bool
IsAudioCompetingInSameTab() const1147 AudioChannelService::AudioChannelWindow::IsAudioCompetingInSameTab() const
1148 {
1149   bool hasMultipleActiveAgents = IsEnableAudioCompetingForAllAgents() ?
1150     mAgents.Length() > 1 : mAudibleAgents.Length() > 1;
1151   return mOwningAudioFocus && hasMultipleActiveAgents;
1152 }
1153 
1154 void
AudioFocusChanged(AudioChannelAgent * aNewPlayingAgent)1155 AudioChannelService::AudioChannelWindow::AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent)
1156 {
1157   // This agent isn't always known for the current window, because it can comes
1158   // from other window.
1159   MOZ_ASSERT(aNewPlayingAgent);
1160 
1161   if (IsInactiveWindow()) {
1162     // These would happen in two situations,
1163     // (1) Audio in page A was ended, and another page B want to play audio.
1164     //     Page A should abandon its focus.
1165     // (2) Audio was paused by remote-control, page should still own the focus.
1166     mOwningAudioFocus = IsContainingPlayingAgent(aNewPlayingAgent);
1167   } else {
1168     nsTObserverArray<AudioChannelAgent*>::ForwardIterator
1169       iter(IsEnableAudioCompetingForAllAgents() ? mAgents : mAudibleAgents);
1170     while (iter.HasMore()) {
1171       AudioChannelAgent* agent = iter.GetNext();
1172       MOZ_ASSERT(agent);
1173 
1174       // Don't need to update the playing state of new playing agent.
1175       if (agent == aNewPlayingAgent) {
1176         continue;
1177       }
1178 
1179       uint32_t type = GetCompetingBehavior(agent,
1180                                            aNewPlayingAgent->AudioChannelType());
1181 
1182       // If window will be suspended, it needs to abandon the audio focus
1183       // because only one window can own audio focus at a time. However, we
1184       // would support multiple audio focus at the same time in the future.
1185       mOwningAudioFocus = (type == nsISuspendedTypes::NONE_SUSPENDED);
1186 
1187       // TODO : support other behaviors which are definded in MediaSession API.
1188       switch (type) {
1189         case nsISuspendedTypes::NONE_SUSPENDED:
1190         case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE:
1191           agent->WindowSuspendChanged(type);
1192           break;
1193       }
1194     }
1195   }
1196 
1197   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1198          ("AudioChannelWindow, AudioFocusChanged, this = %p, "
1199           "OwningAudioFocus = %d\n", this, mOwningAudioFocus));
1200 }
1201 
1202 bool
IsContainingPlayingAgent(AudioChannelAgent * aAgent) const1203 AudioChannelService::AudioChannelWindow::IsContainingPlayingAgent(AudioChannelAgent* aAgent) const
1204 {
1205   return (aAgent->WindowID() == mWindowID);
1206 }
1207 
1208 uint32_t
GetCompetingBehavior(AudioChannelAgent * aAgent,int32_t aIncomingChannelType) const1209 AudioChannelService::AudioChannelWindow::GetCompetingBehavior(AudioChannelAgent* aAgent,
1210                                                               int32_t aIncomingChannelType) const
1211 {
1212   MOZ_ASSERT(aAgent);
1213   MOZ_ASSERT(IsEnableAudioCompetingForAllAgents() ?
1214     mAgents.Contains(aAgent) : mAudibleAgents.Contains(aAgent));
1215 
1216   uint32_t competingBehavior = nsISuspendedTypes::NONE_SUSPENDED;
1217   int32_t presentChannelType = aAgent->AudioChannelType();
1218 
1219   // TODO : add other competing cases for MediaSession API
1220   if (presentChannelType == int32_t(AudioChannel::Normal) &&
1221       aIncomingChannelType == int32_t(AudioChannel::Normal)) {
1222     competingBehavior = nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE;
1223   }
1224 
1225   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1226          ("AudioChannelWindow, GetCompetingBehavior, this = %p, "
1227           "present type = %d, incoming channel = %d, behavior = %d\n",
1228           this, presentChannelType, aIncomingChannelType, competingBehavior));
1229 
1230   return competingBehavior;
1231 }
1232 
1233 /* static */ bool
IsAudioChannelMutedByDefault()1234 AudioChannelService::IsAudioChannelMutedByDefault()
1235 {
1236   CreateServiceIfNeeded();
1237   return sAudioChannelMutedByDefault;
1238 }
1239 
1240 void
AppendAgent(AudioChannelAgent * aAgent,AudibleState aAudible)1241 AudioChannelService::AudioChannelWindow::AppendAgent(AudioChannelAgent* aAgent,
1242                                                      AudibleState aAudible)
1243 {
1244   MOZ_ASSERT(aAgent);
1245 
1246   RequestAudioFocus(aAgent);
1247   AppendAgentAndIncreaseAgentsNum(aAgent);
1248   AudioCapturedChanged(aAgent, AudioCaptureState::eCapturing);
1249   if (aAudible == AudibleState::eAudible) {
1250     AudioAudibleChanged(aAgent,
1251                         AudibleState::eAudible,
1252                         AudibleChangedReasons::eDataAudibleChanged);
1253   } else if (IsEnableAudioCompetingForAllAgents() &&
1254              aAudible != AudibleState::eAudible) {
1255     NotifyAudioCompetingChanged(aAgent);
1256   }
1257 }
1258 
1259 void
RemoveAgent(AudioChannelAgent * aAgent)1260 AudioChannelService::AudioChannelWindow::RemoveAgent(AudioChannelAgent* aAgent)
1261 {
1262   MOZ_ASSERT(aAgent);
1263 
1264   RemoveAgentAndReduceAgentsNum(aAgent);
1265   AudioCapturedChanged(aAgent, AudioCaptureState::eNotCapturing);
1266   AudioAudibleChanged(aAgent,
1267                       AudibleState::eNotAudible,
1268                       AudibleChangedReasons::ePauseStateChanged);
1269 }
1270 
1271 void
AppendAgentAndIncreaseAgentsNum(AudioChannelAgent * aAgent)1272 AudioChannelService::AudioChannelWindow::AppendAgentAndIncreaseAgentsNum(AudioChannelAgent* aAgent)
1273 {
1274   MOZ_ASSERT(aAgent);
1275   MOZ_ASSERT(!mAgents.Contains(aAgent));
1276 
1277   int32_t channel = aAgent->AudioChannelType();
1278   mAgents.AppendElement(aAgent);
1279 
1280   ++mChannels[channel].mNumberOfAgents;
1281 
1282   // The first one, we must inform the BrowserElementAudioChannel.
1283   if (mChannels[channel].mNumberOfAgents == 1) {
1284     NotifyChannelActive(aAgent->WindowID(),
1285                         static_cast<AudioChannel>(channel),
1286                         true);
1287   }
1288 }
1289 
1290 void
RemoveAgentAndReduceAgentsNum(AudioChannelAgent * aAgent)1291 AudioChannelService::AudioChannelWindow::RemoveAgentAndReduceAgentsNum(AudioChannelAgent* aAgent)
1292 {
1293   MOZ_ASSERT(aAgent);
1294   MOZ_ASSERT(mAgents.Contains(aAgent));
1295 
1296   int32_t channel = aAgent->AudioChannelType();
1297   mAgents.RemoveElement(aAgent);
1298 
1299   MOZ_ASSERT(mChannels[channel].mNumberOfAgents > 0);
1300   --mChannels[channel].mNumberOfAgents;
1301 
1302   if (mChannels[channel].mNumberOfAgents == 0) {
1303     NotifyChannelActive(aAgent->WindowID(),
1304                         static_cast<AudioChannel>(channel),
1305                         false);
1306   }
1307 }
1308 
1309 void
AudioCapturedChanged(AudioChannelAgent * aAgent,AudioCaptureState aCapture)1310 AudioChannelService::AudioChannelWindow::AudioCapturedChanged(AudioChannelAgent* aAgent,
1311                                                               AudioCaptureState aCapture)
1312 {
1313   MOZ_ASSERT(aAgent);
1314 
1315   if (mIsAudioCaptured) {
1316     aAgent->WindowAudioCaptureChanged(aAgent->InnerWindowID(), aCapture);
1317   }
1318 }
1319 
1320 void
AudioAudibleChanged(AudioChannelAgent * aAgent,AudibleState aAudible,AudibleChangedReasons aReason)1321 AudioChannelService::AudioChannelWindow::AudioAudibleChanged(AudioChannelAgent* aAgent,
1322                                                              AudibleState aAudible,
1323                                                              AudibleChangedReasons aReason)
1324 {
1325   MOZ_ASSERT(aAgent);
1326 
1327   if (aAudible == AudibleState::eAudible) {
1328     AppendAudibleAgentIfNotContained(aAgent, aReason);
1329   } else {
1330     RemoveAudibleAgentIfContained(aAgent, aReason);
1331   }
1332 
1333   if (aAudible == AudibleState::eAudible) {
1334     NotifyAudioCompetingChanged(aAgent);
1335   } else if (aAudible != AudibleState::eNotAudible) {
1336     MaybeNotifyMediaBlocked(aAgent);
1337   }
1338 }
1339 
1340 void
AppendAudibleAgentIfNotContained(AudioChannelAgent * aAgent,AudibleChangedReasons aReason)1341 AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent,
1342                                                                           AudibleChangedReasons aReason)
1343 {
1344   MOZ_ASSERT(aAgent);
1345   MOZ_ASSERT(mAgents.Contains(aAgent));
1346 
1347   if (!mAudibleAgents.Contains(aAgent)) {
1348     mAudibleAgents.AppendElement(aAgent);
1349     if (IsFirstAudibleAgent()) {
1350       NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eAudible, aReason);
1351     }
1352   }
1353 }
1354 
1355 void
RemoveAudibleAgentIfContained(AudioChannelAgent * aAgent,AudibleChangedReasons aReason)1356 AudioChannelService::AudioChannelWindow::RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent,
1357                                                                        AudibleChangedReasons aReason)
1358 {
1359   MOZ_ASSERT(aAgent);
1360 
1361   if (mAudibleAgents.Contains(aAgent)) {
1362     mAudibleAgents.RemoveElement(aAgent);
1363     if (IsLastAudibleAgent()) {
1364       NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eNotAudible, aReason);
1365     }
1366   }
1367 }
1368 
1369 bool
IsFirstAudibleAgent() const1370 AudioChannelService::AudioChannelWindow::IsFirstAudibleAgent() const
1371 {
1372   return (mAudibleAgents.Length() == 1);
1373 }
1374 
1375 bool
IsLastAudibleAgent() const1376 AudioChannelService::AudioChannelWindow::IsLastAudibleAgent() const
1377 {
1378   return mAudibleAgents.IsEmpty();
1379 }
1380 
1381 bool
IsInactiveWindow() const1382 AudioChannelService::AudioChannelWindow::IsInactiveWindow() const
1383 {
1384   return IsEnableAudioCompetingForAllAgents() ?
1385     mAudibleAgents.IsEmpty() && mAgents.IsEmpty() : mAudibleAgents.IsEmpty();
1386 }
1387 
1388 void
NotifyAudioAudibleChanged(nsPIDOMWindowOuter * aWindow,AudibleState aAudible,AudibleChangedReasons aReason)1389 AudioChannelService::AudioChannelWindow::NotifyAudioAudibleChanged(nsPIDOMWindowOuter* aWindow,
1390                                                                    AudibleState aAudible,
1391                                                                    AudibleChangedReasons aReason)
1392 {
1393   RefPtr<AudioPlaybackRunnable> runnable =
1394     new AudioPlaybackRunnable(aWindow,
1395                               aAudible == AudibleState::eAudible,
1396                               aReason);
1397   DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
1398   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
1399 }
1400 
1401 void
NotifyChannelActive(uint64_t aWindowID,AudioChannel aChannel,bool aActive)1402 AudioChannelService::AudioChannelWindow::NotifyChannelActive(uint64_t aWindowID,
1403                                                              AudioChannel aChannel,
1404                                                              bool aActive)
1405 {
1406   RefPtr<NotifyChannelActiveRunnable> runnable =
1407     new NotifyChannelActiveRunnable(aWindowID, aChannel, aActive);
1408   DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
1409   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
1410 }
1411 
1412 void
MaybeNotifyMediaBlocked(AudioChannelAgent * aAgent)1413 AudioChannelService::AudioChannelWindow::MaybeNotifyMediaBlocked(AudioChannelAgent* aAgent)
1414 {
1415   nsCOMPtr<nsPIDOMWindowOuter> window = aAgent->Window();
1416   if (!window) {
1417     return;
1418   }
1419 
1420   MOZ_ASSERT(window->IsOuterWindow());
1421   if (window->GetMediaSuspend() != nsISuspendedTypes::SUSPENDED_BLOCK) {
1422     return;
1423   }
1424 
1425   NS_DispatchToCurrentThread(NS_NewRunnableFunction([window] () -> void {
1426       nsCOMPtr<nsIObserverService> observerService =
1427         services::GetObserverService();
1428       if (NS_WARN_IF(!observerService)) {
1429         return;
1430       }
1431 
1432       observerService->NotifyObservers(ToSupports(window),
1433                                        "audio-playback",
1434                                        u"block");
1435     })
1436   );
1437 }
1438