diff --git a/dom/media/mediacontrol/AudioFocusManager.cpp b/dom/media/mediacontrol/AudioFocusManager.cpp index 9eeed586cf0e..24ac7374d46c 100644 --- a/dom/media/mediacontrol/AudioFocusManager.cpp +++ b/dom/media/mediacontrol/AudioFocusManager.cpp @@ -10,6 +10,8 @@ #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, ...) \ @@ -23,9 +25,20 @@ void AudioFocusManager::RequestAudioFocus(IMediaController* aController) { if (mOwningFocusControllers.Contains(aController)) { return; } - ClearFocusControllersIfNeeded(); + 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) { @@ -37,23 +50,85 @@ void AudioFocusManager::RevokeAudioFocus(IMediaController* aController) { mOwningFocusControllers.RemoveElement(aController); } -void AudioFocusManager::ClearFocusControllersIfNeeded() { +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; + 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 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 task = NS_NewRunnableFunction( + "AudioFocusManager::RequestAudioFocus", + [focusNum, activeControllerNum]() { + if (RefPtr 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 diff --git a/dom/media/mediacontrol/AudioFocusManager.h b/dom/media/mediacontrol/AudioFocusManager.h index 5c9bacc4f6c4..65d09d51f492 100644 --- a/dom/media/mediacontrol/AudioFocusManager.h +++ b/dom/media/mediacontrol/AudioFocusManager.h @@ -7,6 +7,7 @@ #include "base/basictypes.h" #include "nsTArray.h" +#include "VideoUtils.h" namespace mozilla { namespace dom { @@ -29,15 +30,24 @@ class AudioFocusManager { void RevokeAudioFocus(IMediaController* aController); explicit AudioFocusManager() = default; - ~AudioFocusManager() = default; + ~AudioFocusManager(); uint32_t GetAudioFocusNums() const; private: friend class MediaControlService; - void ClearFocusControllersIfNeeded(); + // Return true if we manage audio focus by clearing other controllers owning + // audio focus before assigning audio focus to the new controller. + bool ClearFocusControllersIfNeeded(); + + void CreateTimerForUpdatingTelemetry(); + // This would check if user has managed audio focus by themselves and update + // the result to telemetry. This method should only be called from timer. + void UpdateTelemetryDataFromTimer(uint32_t aPrevFocusNum, + uint64_t aPrevActiveControllerNum); nsTArray> mOwningFocusControllers; + RefPtr mTelemetryTimer; }; } // namespace dom diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index a30083cf9037..107ceb664ca9 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -15499,6 +15499,19 @@ "description": "The number of times `play/pause/stop` actions are being handled on either default handler or media session action handler.", "releaseChannelCollection": "opt-out" }, + "TABS_AUDIO_COMPETITION": { + "record_in_processes": ["main"], + "products": ["firefox"], + "alert_emails": [ + "alwu@mozilla.com" + ], + "bug_numbers": [1683788], + "expires_in_version": "91", + "kind": "categorical", + "labels": ["None", "Ignored", "ManagedFocusByUser", "ManagedFocusByGecko"], + "description": "This probe would be reported whenever (1) media starts without encountering audio competition (2) encountering audio competition but users ignore it (keep all media continually playing) (3) encountering audio competition and users manually handle it (4) encountering audio competition and Gecko handled it. The value of each state would be the total happening count for specific situation during the whole session.", + "releaseChannelCollection": "opt-out" + }, "QM_QUOTA_INFO_LOAD_TIME_V0": { "record_in_processes": ["main"], "products": ["firefox", "fennec"],