summaryrefslogtreecommitdiffstats
path: root/dom/media/mediacontrol/AudioFocusManager.cpp
blob: 24ac7374d46cd75ac56a933be09e18ab7b7e85ee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "AudioFocusManager.h"

#include "MediaController.h"
#include "MediaControlUtils.h"
#include "MediaControlService.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/Logging.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/Telemetry.h"
#include "nsThreadUtils.h"

#undef LOG
#define LOG(msg, ...)                        \
  MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
          ("AudioFocusManager=%p, " msg, this, ##__VA_ARGS__))

namespace mozilla::dom {

void AudioFocusManager::RequestAudioFocus(IMediaController* aController) {
  MOZ_ASSERT(aController);
  if (mOwningFocusControllers.Contains(aController)) {
    return;
  }
  const bool hasManagedAudioFocus = ClearFocusControllersIfNeeded();
  LOG("Controller %" PRId64 " grants audio focus", aController->Id());
  mOwningFocusControllers.AppendElement(aController);
  if (hasManagedAudioFocus) {
    AccumulateCategorical(
        mozilla::Telemetry::LABELS_TABS_AUDIO_COMPETITION::ManagedFocusByGecko);
  } else if (GetAudioFocusNums() == 1) {
    // Only one audible tab is playing within gecko.
    AccumulateCategorical(
        mozilla::Telemetry::LABELS_TABS_AUDIO_COMPETITION::None);
  } else {
    // Multiple audible tabs are playing at the same time within gecko.
    CreateTimerForUpdatingTelemetry();
  }
}

void AudioFocusManager::RevokeAudioFocus(IMediaController* aController) {
  MOZ_ASSERT(aController);
  if (!mOwningFocusControllers.Contains(aController)) {
    return;
  }
  LOG("Controller %" PRId64 " loses audio focus", aController->Id());
  mOwningFocusControllers.RemoveElement(aController);
}

bool AudioFocusManager::ClearFocusControllersIfNeeded() {
  // Enable audio focus management will start the audio competition which is
  // only allowing one controller playing at a time.
  if (!StaticPrefs::media_audioFocus_management()) {
    return false;
  }

  bool hasStoppedAnyController = false;
  for (auto& controller : mOwningFocusControllers) {
    LOG("Controller %" PRId64 " loses audio focus in audio competitition",
        controller->Id());
    hasStoppedAnyController = true;
    controller->Stop();
  }
  mOwningFocusControllers.Clear();
  return hasStoppedAnyController;
}

uint32_t AudioFocusManager::GetAudioFocusNums() const {
  return mOwningFocusControllers.Length();
}

void AudioFocusManager::CreateTimerForUpdatingTelemetry() {
  MOZ_ASSERT(NS_IsMainThread());
  // Already create the timer.
  if (mTelemetryTimer) {
    return;
  }

  const uint32_t focusNum = GetAudioFocusNums();
  MOZ_ASSERT(focusNum > 1);

  RefPtr<MediaControlService> service = MediaControlService::GetService();
  MOZ_ASSERT(service);
  const uint32_t activeControllerNum = service->GetActiveControllersNum();

  // It takes time if users want to manually manage the audio competition by
  // pausing one of playing tabs. So we will check the status after a short
  // while to see if users handle the audio competition, or simply ignore it.
  nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
      "AudioFocusManager::RequestAudioFocus",
      [focusNum, activeControllerNum]() {
        if (RefPtr<MediaControlService> service =
                MediaControlService::GetService()) {
          service->GetAudioFocusManager().UpdateTelemetryDataFromTimer(
              focusNum, activeControllerNum);
        }
      });
  mTelemetryTimer =
      SimpleTimer::Create(task, 4000, GetMainThreadSerialEventTarget());
}

void AudioFocusManager::UpdateTelemetryDataFromTimer(
    uint32_t aPrevFocusNum, uint64_t aPrevActiveControllerNum) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(mTelemetryTimer);
  // Users pause or mute tabs which decreases the amount of audible playing
  // tabs, which should not affect the total controller amount.
  if (GetAudioFocusNums() < aPrevFocusNum) {
    // If active controller amount is not equal, that means controllers got
    // deactivated by other reasons, such as reaching to the end, which are not
    // the situation we would like to accumulate for telemetry.
    if (MediaControlService::GetService()->GetActiveControllersNum() ==
        aPrevActiveControllerNum) {
      AccumulateCategorical(mozilla::Telemetry::LABELS_TABS_AUDIO_COMPETITION::
                                ManagedFocusByUser);
    }
  } else {
    AccumulateCategorical(
        mozilla::Telemetry::LABELS_TABS_AUDIO_COMPETITION::Ignored);
  }
  mTelemetryTimer = nullptr;
}

AudioFocusManager::~AudioFocusManager() {
  if (mTelemetryTimer) {
    mTelemetryTimer->Cancel();
    mTelemetryTimer = nullptr;
  }
}

}  // namespace mozilla::dom