From df9c134aa60ba99cef05acfb0f6733c84a374da6 Mon Sep 17 00:00:00 2001 From: alwu Date: Thu, 24 Dec 2020 02:42:56 +0000 Subject: [PATCH] Bug 1683788 - add telemetry probe for the audio competition. r=bryce If multiple audible tabs play at the same time, we call it as `Audio Competition`. When the audio competition happens, there are several possibilities (1) ignore that and keep multiple tabs playing at the same time (2) user manually handles that by pausing or muting audible playing tabs, remaining only an audible playing tab (3) user enables audio focus management, so gecko would pause them and only allow one audible tab playing at a time By collecting these situations, we can know the details of how user would react for the audio compeition. In addition, we also collect the situation where no audio competition happens, in order to know whether the audio competition is common to users. Differential Revision: https://phabricator.services.mozilla.com/D100292 --- dom/media/mediacontrol/AudioFocusManager.cpp | 81 +++++++++++++++++++- dom/media/mediacontrol/AudioFocusManager.h | 14 +++- toolkit/components/telemetry/Histograms.json | 13 ++++ 3 files changed, 103 insertions(+), 5 deletions(-) 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"],