Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/audiochannel/AudioChannelService.cpp
Line
Count
Source (jump to first uncovered line)
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 "nsContentUtils.h"
16
#include "nsIScriptSecurityManager.h"
17
#include "nsISupportsPrimitives.h"
18
#include "nsThreadUtils.h"
19
#include "nsHashPropertyBag.h"
20
#include "nsComponentManagerUtils.h"
21
#include "nsGlobalWindow.h"
22
#include "nsPIDOMWindow.h"
23
#include "nsServiceManagerUtils.h"
24
25
#include "mozilla/Preferences.h"
26
27
using namespace mozilla;
28
using namespace mozilla::dom;
29
30
static mozilla::LazyLogModule gAudioChannelLog("AudioChannel");
31
32
namespace {
33
34
bool sAudioChannelCompeting = false;
35
bool sAudioChannelCompetingAllAgents = false;
36
bool sXPCOMShuttingDown = false;
37
38
class NotifyChannelActiveRunnable final : public Runnable
39
{
40
public:
41
  NotifyChannelActiveRunnable(uint64_t aWindowID, bool aActive)
42
    : Runnable("NotifyChannelActiveRunnable")
43
    , mWindowID(aWindowID)
44
    , mActive(aActive)
45
0
  {}
46
47
  NS_IMETHOD Run() override
48
0
  {
49
0
    nsCOMPtr<nsIObserverService> observerService =
50
0
      services::GetObserverService();
51
0
    if (NS_WARN_IF(!observerService)) {
52
0
      return NS_ERROR_FAILURE;
53
0
    }
54
0
55
0
    nsCOMPtr<nsISupportsPRUint64> wrapper =
56
0
      do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
57
0
    if (NS_WARN_IF(!wrapper)) {
58
0
       return NS_ERROR_FAILURE;
59
0
    }
60
0
61
0
    wrapper->SetData(mWindowID);
62
0
63
0
    observerService->NotifyObservers(wrapper,
64
0
                                     "media-playback",
65
0
                                     mActive
66
0
                                       ? u"active"
67
0
                                       : u"inactive");
68
0
69
0
    MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
70
0
           ("NotifyChannelActiveRunnable, active = %s\n",
71
0
            mActive ? "true" : "false"));
72
0
73
0
    return NS_OK;
74
0
  }
75
76
private:
77
  const uint64_t mWindowID;
78
  const bool mActive;
79
};
80
81
class AudioPlaybackRunnable final : public Runnable
82
{
83
public:
84
  AudioPlaybackRunnable(nsPIDOMWindowOuter* aWindow,
85
                        bool aActive,
86
                        AudioChannelService::AudibleChangedReasons aReason)
87
    : mozilla::Runnable("AudioPlaybackRunnable")
88
    , mWindow(aWindow)
89
    , mActive(aActive)
90
    , mReason(aReason)
91
0
  {}
92
93
 NS_IMETHOD Run() override
94
0
 {
95
0
    nsCOMPtr<nsIObserverService> observerService =
96
0
      services::GetObserverService();
97
0
    if (NS_WARN_IF(!observerService)) {
98
0
      return NS_ERROR_FAILURE;
99
0
    }
100
0
101
0
    nsAutoString state;
102
0
    GetActiveState(state);
103
0
104
0
    observerService->NotifyObservers(ToSupports(mWindow),
105
0
                                     "audio-playback",
106
0
                                     state.get());
107
0
108
0
    MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
109
0
           ("AudioPlaybackRunnable, active = %s, reason = %s\n",
110
0
            mActive ? "true" : "false", AudibleChangedReasonToStr(mReason)));
111
0
112
0
    return NS_OK;
113
0
  }
114
115
private:
116
  void GetActiveState(nsAString& aState)
117
0
  {
118
0
    if (mActive) {
119
0
      aState.AssignLiteral("active");
120
0
    } else {
121
0
      if(mReason == AudioChannelService::AudibleChangedReasons::ePauseStateChanged) {
122
0
        aState.AssignLiteral("inactive-pause");
123
0
      } else {
124
0
        aState.AssignLiteral("inactive-nonaudible");
125
0
      }
126
0
    }
127
0
  }
128
129
  nsCOMPtr<nsPIDOMWindowOuter> mWindow;
130
  bool mActive;
131
  AudioChannelService::AudibleChangedReasons mReason;
132
};
133
134
bool
135
IsEnableAudioCompetingForAllAgents()
136
0
{
137
0
  // In general, the audio competing should only be for audible media and it
138
0
  // helps user can focus on one media at the same time. However, we hope to
139
0
  // treat all media as the same in the mobile device. First reason is we have
140
0
  // media control on fennec and we just want to control one media at once time.
141
0
  // Second reason is to reduce the bandwidth, avoiding to play any non-audible
142
0
  // media in background which user doesn't notice about.
143
#ifdef MOZ_WIDGET_ANDROID
144
  return true;
145
#else
146
  return sAudioChannelCompetingAllAgents;
147
0
#endif
148
0
}
149
150
} // anonymous namespace
151
152
namespace mozilla {
153
namespace dom {
154
155
const char*
156
SuspendTypeToStr(const nsSuspendedTypes& aSuspend)
157
0
{
158
0
  MOZ_ASSERT(aSuspend == nsISuspendedTypes::NONE_SUSPENDED ||
159
0
             aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE ||
160
0
             aSuspend == nsISuspendedTypes::SUSPENDED_BLOCK ||
161
0
             aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE ||
162
0
             aSuspend == nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE);
163
0
164
0
  switch (aSuspend) {
165
0
    case nsISuspendedTypes::NONE_SUSPENDED:
166
0
      return "none";
167
0
    case nsISuspendedTypes::SUSPENDED_PAUSE:
168
0
      return "pause";
169
0
    case nsISuspendedTypes::SUSPENDED_BLOCK:
170
0
      return "block";
171
0
    case nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE:
172
0
      return "disposable-pause";
173
0
    case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE:
174
0
      return "disposable-stop";
175
0
    default:
176
0
      return "unknown";
177
0
  }
178
0
}
179
180
const char*
181
AudibleStateToStr(const AudioChannelService::AudibleState& aAudible)
182
0
{
183
0
  MOZ_ASSERT(aAudible == AudioChannelService::AudibleState::eNotAudible ||
184
0
             aAudible == AudioChannelService::AudibleState::eMaybeAudible ||
185
0
             aAudible == AudioChannelService::AudibleState::eAudible);
186
0
187
0
  switch (aAudible) {
188
0
    case AudioChannelService::AudibleState::eNotAudible :
189
0
      return "not-audible";
190
0
    case AudioChannelService::AudibleState::eMaybeAudible :
191
0
      return "maybe-audible";
192
0
    case AudioChannelService::AudibleState::eAudible :
193
0
      return "audible";
194
0
    default:
195
0
      return "unknown";
196
0
  }
197
0
}
198
199
const char*
200
AudibleChangedReasonToStr(const AudioChannelService::AudibleChangedReasons& aReason)
201
0
{
202
0
  MOZ_ASSERT(aReason == AudioChannelService::AudibleChangedReasons::eVolumeChanged ||
203
0
             aReason == AudioChannelService::AudibleChangedReasons::eDataAudibleChanged ||
204
0
             aReason == AudioChannelService::AudibleChangedReasons::ePauseStateChanged);
205
0
206
0
  switch (aReason) {
207
0
    case AudioChannelService::AudibleChangedReasons::eVolumeChanged :
208
0
      return "volume";
209
0
    case AudioChannelService::AudibleChangedReasons::eDataAudibleChanged :
210
0
      return "data-audible";
211
0
    case AudioChannelService::AudibleChangedReasons::ePauseStateChanged :
212
0
      return "pause-state";
213
0
    default:
214
0
      return "unknown";
215
0
  }
216
0
}
217
218
StaticRefPtr<AudioChannelService> gAudioChannelService;
219
220
/* static */ void
221
AudioChannelService::CreateServiceIfNeeded()
222
0
{
223
0
  MOZ_ASSERT(NS_IsMainThread());
224
0
225
0
  if (!gAudioChannelService) {
226
0
    gAudioChannelService = new AudioChannelService();
227
0
  }
228
0
}
229
230
/* static */ already_AddRefed<AudioChannelService>
231
AudioChannelService::GetOrCreate()
232
0
{
233
0
  if (sXPCOMShuttingDown) {
234
0
    return nullptr;
235
0
  }
236
0
237
0
  CreateServiceIfNeeded();
238
0
  RefPtr<AudioChannelService> service = gAudioChannelService.get();
239
0
  return service.forget();
240
0
}
241
242
/* static */ already_AddRefed<AudioChannelService>
243
AudioChannelService::Get()
244
0
{
245
0
  if (sXPCOMShuttingDown) {
246
0
    return nullptr;
247
0
  }
248
0
249
0
  RefPtr<AudioChannelService> service = gAudioChannelService.get();
250
0
  return service.forget();
251
0
}
252
253
/* static */ LogModule*
254
AudioChannelService::GetAudioChannelLog()
255
0
{
256
0
  return gAudioChannelLog;
257
0
}
258
259
/* static */ void
260
AudioChannelService::Shutdown()
261
0
{
262
0
  if (gAudioChannelService) {
263
0
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
264
0
    if (obs) {
265
0
      obs->RemoveObserver(gAudioChannelService, "xpcom-shutdown");
266
0
      obs->RemoveObserver(gAudioChannelService, "outer-window-destroyed");
267
0
    }
268
0
269
0
    gAudioChannelService->mWindows.Clear();
270
0
271
0
    gAudioChannelService = nullptr;
272
0
  }
273
0
}
274
275
/* static */ bool
276
AudioChannelService::IsEnableAudioCompeting()
277
0
{
278
0
  CreateServiceIfNeeded();
279
0
  return sAudioChannelCompeting;
280
0
}
281
282
0
NS_INTERFACE_MAP_BEGIN(AudioChannelService)
283
0
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
284
0
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
285
0
NS_INTERFACE_MAP_END
286
287
NS_IMPL_ADDREF(AudioChannelService)
288
NS_IMPL_RELEASE(AudioChannelService)
289
290
AudioChannelService::AudioChannelService()
291
0
{
292
0
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
293
0
  if (obs) {
294
0
    obs->AddObserver(this, "xpcom-shutdown", false);
295
0
    obs->AddObserver(this, "outer-window-destroyed", false);
296
0
  }
297
0
298
0
  Preferences::AddBoolVarCache(&sAudioChannelCompeting,
299
0
                               "dom.audiochannel.audioCompeting");
300
0
  Preferences::AddBoolVarCache(&sAudioChannelCompetingAllAgents,
301
0
                               "dom.audiochannel.audioCompeting.allAgents");
302
0
}
303
304
AudioChannelService::~AudioChannelService()
305
0
{
306
0
}
307
308
void
309
AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
310
                                               AudibleState aAudible)
311
0
{
312
0
  MOZ_ASSERT(aAgent);
313
0
314
0
  uint64_t windowID = aAgent->WindowID();
315
0
  AudioChannelWindow* winData = GetWindowData(windowID);
316
0
  if (!winData) {
317
0
    winData = new AudioChannelWindow(windowID);
318
0
    mWindows.AppendElement(winData);
319
0
  }
320
0
321
0
  // To make sure agent would be alive because AppendAgent() would trigger the
322
0
  // callback function of AudioChannelAgentOwner that means the agent might be
323
0
  // released in their callback.
324
0
  RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
325
0
  winData->AppendAgent(aAgent, aAudible);
326
0
}
327
328
void
329
AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
330
0
{
331
0
  MOZ_ASSERT(aAgent);
332
0
333
0
  AudioChannelWindow* winData = GetWindowData(aAgent->WindowID());
334
0
  if (!winData) {
335
0
    return;
336
0
  }
337
0
338
0
  // To make sure agent would be alive because AppendAgent() would trigger the
339
0
  // callback function of AudioChannelAgentOwner that means the agent might be
340
0
  // released in their callback.
341
0
  RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
342
0
  winData->RemoveAgent(aAgent);
343
0
}
344
345
AudioPlaybackConfig
346
AudioChannelService::GetMediaConfig(nsPIDOMWindowOuter* aWindow) const
347
0
{
348
0
  AudioPlaybackConfig config(1.0, false,
349
0
                             nsISuspendedTypes::NONE_SUSPENDED);
350
0
351
0
  if (!aWindow) {
352
0
    config.SetConfig(0.0, true,
353
0
                     nsISuspendedTypes::SUSPENDED_BLOCK);
354
0
    return config;
355
0
  }
356
0
357
0
  AudioChannelWindow* winData = nullptr;
358
0
  nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
359
0
360
0
  // The volume must be calculated based on the window hierarchy. Here we go up
361
0
  // to the top window and we calculate the volume and the muted flag.
362
0
  do {
363
0
    winData = GetWindowData(window->WindowID());
364
0
    if (winData) {
365
0
      config.mVolume *= winData->mConfig.mVolume;
366
0
      config.mMuted = config.mMuted || winData->mConfig.mMuted;
367
0
      config.mSuspend = winData->mOwningAudioFocus ?
368
0
        config.mSuspend : nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE;
369
0
    }
370
0
371
0
    config.mVolume *= window->GetAudioVolume();
372
0
    config.mMuted = config.mMuted || window->GetAudioMuted();
373
0
    if (window->GetMediaSuspend() != nsISuspendedTypes::NONE_SUSPENDED) {
374
0
      config.mSuspend = window->GetMediaSuspend();
375
0
    }
376
0
377
0
    nsCOMPtr<nsPIDOMWindowOuter> win = window->GetScriptableParentOrNull();
378
0
    if (!win) {
379
0
      break;
380
0
    }
381
0
382
0
    window = win;
383
0
384
0
    // If there is no parent, or we are the toplevel we don't continue.
385
0
  } while (window && window != aWindow);
386
0
387
0
  return config;
388
0
}
389
390
void
391
AudioChannelService::AudioAudibleChanged(AudioChannelAgent* aAgent,
392
                                         AudibleState aAudible,
393
                                         AudibleChangedReasons aReason)
394
0
{
395
0
  MOZ_ASSERT(aAgent);
396
0
397
0
  uint64_t windowID = aAgent->WindowID();
398
0
  AudioChannelWindow* winData = GetWindowData(windowID);
399
0
  if (winData) {
400
0
    winData->AudioAudibleChanged(aAgent, aAudible, aReason);
401
0
  }
402
0
}
403
404
NS_IMETHODIMP
405
AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic,
406
                             const char16_t* aData)
407
0
{
408
0
  if (!strcmp(aTopic, "xpcom-shutdown")) {
409
0
    sXPCOMShuttingDown = true;
410
0
    Shutdown();
411
0
  } else if (!strcmp(aTopic, "outer-window-destroyed")) {
412
0
    nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
413
0
    NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
414
0
415
0
    uint64_t outerID;
416
0
    nsresult rv = wrapper->GetData(&outerID);
417
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
418
0
      return rv;
419
0
    }
420
0
421
0
    nsAutoPtr<AudioChannelWindow> winData;
422
0
    {
423
0
      nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator
424
0
        iter(mWindows);
425
0
      while (iter.HasMore()) {
426
0
        nsAutoPtr<AudioChannelWindow>& next = iter.GetNext();
427
0
        if (next->mWindowID == outerID) {
428
0
          uint32_t pos = mWindows.IndexOf(next);
429
0
          winData = next.forget();
430
0
          mWindows.RemoveElementAt(pos);
431
0
          break;
432
0
        }
433
0
      }
434
0
    }
435
0
436
0
    if (winData) {
437
0
      nsTObserverArray<AudioChannelAgent*>::ForwardIterator
438
0
        iter(winData->mAgents);
439
0
      while (iter.HasMore()) {
440
0
        iter.GetNext()->WindowVolumeChanged();
441
0
      }
442
0
    }
443
0
  }
444
0
445
0
  return NS_OK;
446
0
}
447
448
void
449
AudioChannelService::RefreshAgents(nsPIDOMWindowOuter* aWindow,
450
                                   const std::function<void(AudioChannelAgent*)>& aFunc)
451
0
{
452
0
  MOZ_ASSERT(aWindow);
453
0
454
0
  nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
455
0
  if (!topWindow) {
456
0
    return;
457
0
  }
458
0
459
0
  AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
460
0
  if (!winData) {
461
0
    return;
462
0
  }
463
0
464
0
  nsTObserverArray<AudioChannelAgent*>::ForwardIterator
465
0
    iter(winData->mAgents);
466
0
  while (iter.HasMore()) {
467
0
    aFunc(iter.GetNext());
468
0
  }
469
0
}
470
471
void
472
AudioChannelService::RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow)
473
0
{
474
0
  RefreshAgents(aWindow, [] (AudioChannelAgent* agent) {
475
0
    agent->WindowVolumeChanged();
476
0
  });
477
0
}
478
479
void
480
AudioChannelService::RefreshAgentsSuspend(nsPIDOMWindowOuter* aWindow,
481
                                          nsSuspendedTypes aSuspend)
482
0
{
483
0
  RefreshAgents(aWindow, [aSuspend] (AudioChannelAgent* agent) {
484
0
    agent->WindowSuspendChanged(aSuspend);
485
0
  });
486
0
}
487
488
void
489
AudioChannelService::SetWindowAudioCaptured(nsPIDOMWindowOuter* aWindow,
490
                                            uint64_t aInnerWindowID,
491
                                            bool aCapture)
492
0
{
493
0
  MOZ_ASSERT(NS_IsMainThread());
494
0
  MOZ_ASSERT(aWindow);
495
0
496
0
  MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
497
0
         ("AudioChannelService, SetWindowAudioCaptured, window = %p, "
498
0
          "aCapture = %d\n", aWindow, aCapture));
499
0
500
0
  nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
501
0
  if (!topWindow) {
502
0
    return;
503
0
  }
504
0
505
0
  AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
506
0
507
0
  // This can happen, but only during shutdown, because the the outer window
508
0
  // changes ScriptableTop, so that its ID is different.
509
0
  // In this case either we are capturing, and it's too late because the window
510
0
  // has been closed anyways, or we are un-capturing, and everything has already
511
0
  // been cleaned up by the HTMLMediaElements or the AudioContexts.
512
0
  if (!winData) {
513
0
    return;
514
0
  }
515
0
516
0
  if (aCapture != winData->mIsAudioCaptured) {
517
0
    winData->mIsAudioCaptured = aCapture;
518
0
    nsTObserverArray<AudioChannelAgent*>::ForwardIterator
519
0
      iter(winData->mAgents);
520
0
    while (iter.HasMore()) {
521
0
      iter.GetNext()->WindowAudioCaptureChanged(aInnerWindowID, aCapture);
522
0
    }
523
0
  }
524
0
}
525
526
AudioChannelService::AudioChannelWindow*
527
AudioChannelService::GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow)
528
0
{
529
0
  MOZ_ASSERT(NS_IsMainThread());
530
0
  MOZ_ASSERT(aWindow);
531
0
532
0
  AudioChannelWindow* winData = GetWindowData(aWindow->WindowID());
533
0
  if (!winData) {
534
0
    winData = new AudioChannelWindow(aWindow->WindowID());
535
0
    mWindows.AppendElement(winData);
536
0
  }
537
0
538
0
  return winData;
539
0
}
540
541
AudioChannelService::AudioChannelWindow*
542
AudioChannelService::GetWindowData(uint64_t aWindowID) const
543
0
{
544
0
  nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator
545
0
    iter(mWindows);
546
0
  while (iter.HasMore()) {
547
0
    AudioChannelWindow* next = iter.GetNext();
548
0
    if (next->mWindowID == aWindowID) {
549
0
      return next;
550
0
    }
551
0
  }
552
0
553
0
  return nullptr;
554
0
}
555
556
bool
557
AudioChannelService::IsWindowActive(nsPIDOMWindowOuter* aWindow)
558
0
{
559
0
  MOZ_ASSERT(NS_IsMainThread());
560
0
561
0
  auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
562
0
  if (!window) {
563
0
    return false;
564
0
  }
565
0
566
0
  AudioChannelWindow* winData = GetWindowData(window->WindowID());
567
0
  if (!winData) {
568
0
    return false;
569
0
  }
570
0
571
0
  return !winData->mAudibleAgents.IsEmpty();
572
0
}
573
574
void
575
AudioChannelService::RefreshAgentsAudioFocusChanged(AudioChannelAgent* aAgent)
576
0
{
577
0
  MOZ_ASSERT(aAgent);
578
0
579
0
  nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator
580
0
    iter(mWindows);
581
0
  while (iter.HasMore()) {
582
0
    AudioChannelWindow* winData = iter.GetNext();
583
0
    if (winData->mOwningAudioFocus) {
584
0
      winData->AudioFocusChanged(aAgent);
585
0
    }
586
0
  }
587
0
}
588
589
void
590
AudioChannelService::NotifyMediaResumedFromBlock(nsPIDOMWindowOuter* aWindow)
591
0
{
592
0
  MOZ_ASSERT(aWindow);
593
0
594
0
  nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
595
0
  if (!topWindow) {
596
0
    return;
597
0
  }
598
0
599
0
  AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
600
0
  if (!winData) {
601
0
    return;
602
0
  }
603
0
604
0
  winData->NotifyMediaBlockStop(aWindow);
605
0
}
606
607
void
608
AudioChannelService::AudioChannelWindow::RequestAudioFocus(AudioChannelAgent* aAgent)
609
0
{
610
0
  MOZ_ASSERT(aAgent);
611
0
612
0
  // Don't need to check audio focus for window-less agent.
613
0
  if (!aAgent->Window()) {
614
0
    return;
615
0
  }
616
0
617
0
  // We already have the audio focus. No operation is needed.
618
0
  if (mOwningAudioFocus) {
619
0
    return;
620
0
  }
621
0
622
0
  // Only foreground window can request audio focus, but it would still own the
623
0
  // audio focus even it goes to background. Audio focus would be abandoned
624
0
  // only when other foreground window starts audio competing.
625
0
  // One exception is if the pref "media.block-autoplay-until-in-foreground"
626
0
  // is on and the background page is the non-visited before. Because the media
627
0
  // in that page would be blocked until the page is going to foreground.
628
0
  mOwningAudioFocus = (!(aAgent->Window()->IsBackground()) ||
629
0
                       aAgent->Window()->GetMediaSuspend() == nsISuspendedTypes::SUSPENDED_BLOCK) ;
630
0
631
0
  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
632
0
         ("AudioChannelWindow, RequestAudioFocus, this = %p, "
633
0
          "agent = %p, owning audio focus = %s\n",
634
0
          this, aAgent, mOwningAudioFocus ? "true" : "false"));
635
0
}
636
637
void
638
AudioChannelService::AudioChannelWindow::NotifyAudioCompetingChanged(AudioChannelAgent* aAgent)
639
0
{
640
0
  // This function may be called after RemoveAgentAndReduceAgentsNum(), so the
641
0
  // agent may be not contained in mAgent. In addition, the agent would still
642
0
  // be alive because we have kungFuDeathGrip in UnregisterAudioChannelAgent().
643
0
  MOZ_ASSERT(aAgent);
644
0
645
0
  RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
646
0
  MOZ_ASSERT(service);
647
0
648
0
  if (!service->IsEnableAudioCompeting()) {
649
0
    return;
650
0
  }
651
0
652
0
  if (!IsAgentInvolvingInAudioCompeting(aAgent)) {
653
0
    return;
654
0
  }
655
0
656
0
  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
657
0
         ("AudioChannelWindow, NotifyAudioCompetingChanged, this = %p, "
658
0
          "agent = %p\n",
659
0
          this, aAgent));
660
0
661
0
  service->RefreshAgentsAudioFocusChanged(aAgent);
662
0
}
663
664
bool
665
AudioChannelService::AudioChannelWindow::IsAgentInvolvingInAudioCompeting(AudioChannelAgent* aAgent) const
666
0
{
667
0
  MOZ_ASSERT(aAgent);
668
0
669
0
  if(!mOwningAudioFocus) {
670
0
    return false;
671
0
  }
672
0
673
0
  if (IsAudioCompetingInSameTab()) {
674
0
    return false;
675
0
  }
676
0
677
0
  // TODO : add MediaSession::ambient kind, because it doens't interact with
678
0
  // other kinds.
679
0
  return true;
680
0
}
681
682
bool
683
AudioChannelService::AudioChannelWindow::IsAudioCompetingInSameTab() const
684
0
{
685
0
  bool hasMultipleActiveAgents = IsEnableAudioCompetingForAllAgents() ?
686
0
    mAgents.Length() > 1 : mAudibleAgents.Length() > 1;
687
0
  return mOwningAudioFocus && hasMultipleActiveAgents;
688
0
}
689
690
void
691
AudioChannelService::AudioChannelWindow::AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent)
692
0
{
693
0
  // This agent isn't always known for the current window, because it can comes
694
0
  // from other window.
695
0
  MOZ_ASSERT(aNewPlayingAgent);
696
0
697
0
  if (IsInactiveWindow()) {
698
0
    // These would happen in two situations,
699
0
    // (1) Audio in page A was ended, and another page B want to play audio.
700
0
    //     Page A should abandon its focus.
701
0
    // (2) Audio was paused by remote-control, page should still own the focus.
702
0
    mOwningAudioFocus = IsContainingPlayingAgent(aNewPlayingAgent);
703
0
  } else {
704
0
    nsTObserverArray<AudioChannelAgent*>::ForwardIterator
705
0
      iter(IsEnableAudioCompetingForAllAgents() ? mAgents : mAudibleAgents);
706
0
    while (iter.HasMore()) {
707
0
      AudioChannelAgent* agent = iter.GetNext();
708
0
      MOZ_ASSERT(agent);
709
0
710
0
      // Don't need to update the playing state of new playing agent.
711
0
      if (agent == aNewPlayingAgent) {
712
0
        continue;
713
0
      }
714
0
715
0
      uint32_t type = GetCompetingBehavior(agent);
716
0
717
0
      // If window will be suspended, it needs to abandon the audio focus
718
0
      // because only one window can own audio focus at a time. However, we
719
0
      // would support multiple audio focus at the same time in the future.
720
0
      mOwningAudioFocus = (type == nsISuspendedTypes::NONE_SUSPENDED);
721
0
722
0
      // TODO : support other behaviors which are definded in MediaSession API.
723
0
      switch (type) {
724
0
        case nsISuspendedTypes::NONE_SUSPENDED:
725
0
        case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE:
726
0
          agent->WindowSuspendChanged(type);
727
0
          break;
728
0
      }
729
0
    }
730
0
  }
731
0
732
0
  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
733
0
         ("AudioChannelWindow, AudioFocusChanged, this = %p, "
734
0
          "OwningAudioFocus = %s\n", this, mOwningAudioFocus ? "true" : "false"));
735
0
}
736
737
bool
738
AudioChannelService::AudioChannelWindow::IsContainingPlayingAgent(AudioChannelAgent* aAgent) const
739
0
{
740
0
  return (aAgent->WindowID() == mWindowID);
741
0
}
742
743
uint32_t
744
AudioChannelService::AudioChannelWindow::GetCompetingBehavior(AudioChannelAgent* aAgent) const
745
0
{
746
0
  MOZ_ASSERT(aAgent);
747
0
  MOZ_ASSERT(IsEnableAudioCompetingForAllAgents() ?
748
0
    mAgents.Contains(aAgent) : mAudibleAgents.Contains(aAgent));
749
0
750
0
  uint32_t competingBehavior = nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE;
751
0
752
0
  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
753
0
         ("AudioChannelWindow, GetCompetingBehavior, this = %p, "
754
0
          "behavior = %s\n",
755
0
          this, SuspendTypeToStr(competingBehavior)));
756
0
757
0
  return competingBehavior;
758
0
}
759
760
void
761
AudioChannelService::AudioChannelWindow::AppendAgent(AudioChannelAgent* aAgent,
762
                                                     AudibleState aAudible)
763
0
{
764
0
  MOZ_ASSERT(aAgent);
765
0
766
0
  RequestAudioFocus(aAgent);
767
0
  AppendAgentAndIncreaseAgentsNum(aAgent);
768
0
  AudioCapturedChanged(aAgent, AudioCaptureState::eCapturing);
769
0
  if (aAudible == AudibleState::eAudible) {
770
0
    AudioAudibleChanged(aAgent,
771
0
                        AudibleState::eAudible,
772
0
                        AudibleChangedReasons::eDataAudibleChanged);
773
0
  } else if (IsEnableAudioCompetingForAllAgents() &&
774
0
             aAudible != AudibleState::eAudible) {
775
0
    NotifyAudioCompetingChanged(aAgent);
776
0
  }
777
0
}
778
779
void
780
AudioChannelService::AudioChannelWindow::RemoveAgent(AudioChannelAgent* aAgent)
781
0
{
782
0
  MOZ_ASSERT(aAgent);
783
0
784
0
  RemoveAgentAndReduceAgentsNum(aAgent);
785
0
  AudioCapturedChanged(aAgent, AudioCaptureState::eNotCapturing);
786
0
  AudioAudibleChanged(aAgent,
787
0
                      AudibleState::eNotAudible,
788
0
                      AudibleChangedReasons::ePauseStateChanged);
789
0
}
790
791
void
792
AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop(nsPIDOMWindowOuter* aWindow)
793
0
{
794
0
  if (mShouldSendActiveMediaBlockStopEvent) {
795
0
    mShouldSendActiveMediaBlockStopEvent = false;
796
0
    nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
797
0
    NS_DispatchToCurrentThread(NS_NewRunnableFunction(
798
0
      "dom::AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop",
799
0
      [window]() -> void {
800
0
        nsCOMPtr<nsIObserverService> observerService =
801
0
          services::GetObserverService();
802
0
        if (NS_WARN_IF(!observerService)) {
803
0
          return;
804
0
        }
805
0
806
0
        observerService->NotifyObservers(ToSupports(window),
807
0
                                         "audio-playback",
808
0
                                         u"activeMediaBlockStop");
809
0
      })
810
0
    );
811
0
  }
812
0
}
813
814
void
815
AudioChannelService::AudioChannelWindow::AppendAgentAndIncreaseAgentsNum(AudioChannelAgent* aAgent)
816
0
{
817
0
  MOZ_ASSERT(aAgent);
818
0
  MOZ_ASSERT(!mAgents.Contains(aAgent));
819
0
820
0
  mAgents.AppendElement(aAgent);
821
0
822
0
  ++mConfig.mNumberOfAgents;
823
0
824
0
  // TODO: Make NotifyChannelActiveRunnable irrelevant to BrowserElementAudioChannel
825
0
  if (mConfig.mNumberOfAgents == 1) {
826
0
    NotifyChannelActive(aAgent->WindowID(), true);
827
0
  }
828
0
}
829
830
void
831
AudioChannelService::AudioChannelWindow::RemoveAgentAndReduceAgentsNum(AudioChannelAgent* aAgent)
832
0
{
833
0
  MOZ_ASSERT(aAgent);
834
0
  MOZ_ASSERT(mAgents.Contains(aAgent));
835
0
836
0
  mAgents.RemoveElement(aAgent);
837
0
838
0
  MOZ_ASSERT(mConfig.mNumberOfAgents > 0);
839
0
  --mConfig.mNumberOfAgents;
840
0
841
0
  if (mConfig.mNumberOfAgents == 0) {
842
0
    NotifyChannelActive(aAgent->WindowID(), false);
843
0
  }
844
0
}
845
846
void
847
AudioChannelService::AudioChannelWindow::AudioCapturedChanged(AudioChannelAgent* aAgent,
848
                                                              AudioCaptureState aCapture)
849
0
{
850
0
  MOZ_ASSERT(aAgent);
851
0
852
0
  if (mIsAudioCaptured) {
853
0
    aAgent->WindowAudioCaptureChanged(aAgent->InnerWindowID(), aCapture);
854
0
  }
855
0
}
856
857
void
858
AudioChannelService::AudioChannelWindow::AudioAudibleChanged(AudioChannelAgent* aAgent,
859
                                                             AudibleState aAudible,
860
                                                             AudibleChangedReasons aReason)
861
0
{
862
0
  MOZ_ASSERT(aAgent);
863
0
864
0
  if (aAudible == AudibleState::eAudible) {
865
0
    AppendAudibleAgentIfNotContained(aAgent, aReason);
866
0
    NotifyAudioCompetingChanged(aAgent);
867
0
  } else {
868
0
    RemoveAudibleAgentIfContained(aAgent, aReason);
869
0
  }
870
0
871
0
  if (aAudible != AudibleState::eNotAudible) {
872
0
    MaybeNotifyMediaBlockStart(aAgent);
873
0
  }
874
0
}
875
876
void
877
AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent,
878
                                                                          AudibleChangedReasons aReason)
879
0
{
880
0
  MOZ_ASSERT(aAgent);
881
0
  MOZ_ASSERT(mAgents.Contains(aAgent));
882
0
883
0
  if (!mAudibleAgents.Contains(aAgent)) {
884
0
    mAudibleAgents.AppendElement(aAgent);
885
0
    if (IsFirstAudibleAgent()) {
886
0
      NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eAudible, aReason);
887
0
    }
888
0
  }
889
0
}
890
891
void
892
AudioChannelService::AudioChannelWindow::RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent,
893
                                                                       AudibleChangedReasons aReason)
894
0
{
895
0
  MOZ_ASSERT(aAgent);
896
0
897
0
  if (mAudibleAgents.Contains(aAgent)) {
898
0
    mAudibleAgents.RemoveElement(aAgent);
899
0
    if (IsLastAudibleAgent()) {
900
0
      NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eNotAudible, aReason);
901
0
    }
902
0
  }
903
0
}
904
905
bool
906
AudioChannelService::AudioChannelWindow::IsFirstAudibleAgent() const
907
0
{
908
0
  return (mAudibleAgents.Length() == 1);
909
0
}
910
911
bool
912
AudioChannelService::AudioChannelWindow::IsLastAudibleAgent() const
913
0
{
914
0
  return mAudibleAgents.IsEmpty();
915
0
}
916
917
bool
918
AudioChannelService::AudioChannelWindow::IsInactiveWindow() const
919
0
{
920
0
  return IsEnableAudioCompetingForAllAgents() ?
921
0
    mAudibleAgents.IsEmpty() && mAgents.IsEmpty() : mAudibleAgents.IsEmpty();
922
0
}
923
924
void
925
AudioChannelService::AudioChannelWindow::NotifyAudioAudibleChanged(nsPIDOMWindowOuter* aWindow,
926
                                                                   AudibleState aAudible,
927
                                                                   AudibleChangedReasons aReason)
928
0
{
929
0
  RefPtr<AudioPlaybackRunnable> runnable =
930
0
    new AudioPlaybackRunnable(aWindow,
931
0
                              aAudible == AudibleState::eAudible,
932
0
                              aReason);
933
0
  DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
934
0
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
935
0
}
936
937
void
938
AudioChannelService::AudioChannelWindow::NotifyChannelActive(uint64_t aWindowID,
939
                                                             bool aActive)
940
0
{
941
0
  RefPtr<NotifyChannelActiveRunnable> runnable =
942
0
    new NotifyChannelActiveRunnable(aWindowID, aActive);
943
0
  DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
944
0
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
945
0
}
946
947
void
948
AudioChannelService::AudioChannelWindow::MaybeNotifyMediaBlockStart(AudioChannelAgent* aAgent)
949
0
{
950
0
  nsCOMPtr<nsPIDOMWindowOuter> window = aAgent->Window();
951
0
  if (!window) {
952
0
    return;
953
0
  }
954
0
955
0
  nsCOMPtr<nsPIDOMWindowInner> inner = window->GetCurrentInnerWindow();
956
0
  if (!inner) {
957
0
    return;
958
0
  }
959
0
960
0
  nsCOMPtr<nsIDocument> doc = inner->GetExtantDoc();
961
0
  if (!doc) {
962
0
    return;
963
0
  }
964
0
965
0
  if (window->GetMediaSuspend() != nsISuspendedTypes::SUSPENDED_BLOCK ||
966
0
      !doc->Hidden()) {
967
0
    return;
968
0
  }
969
0
970
0
  if (!mShouldSendActiveMediaBlockStopEvent) {
971
0
      mShouldSendActiveMediaBlockStopEvent = true;
972
0
      NS_DispatchToCurrentThread(NS_NewRunnableFunction(
973
0
        "dom::AudioChannelService::AudioChannelWindow::"
974
0
        "MaybeNotifyMediaBlockStart",
975
0
        [window]() -> void {
976
0
          nsCOMPtr<nsIObserverService> observerService =
977
0
            services::GetObserverService();
978
0
          if (NS_WARN_IF(!observerService)) {
979
0
            return;
980
0
          }
981
0
982
0
          observerService->NotifyObservers(
983
0
            ToSupports(window), "audio-playback", u"activeMediaBlockStart");
984
0
        }));
985
0
  }
986
0
}
987
988
} // namespace dom
989
} // namespace mozilla
990