diff options
Diffstat (limited to 'dom/audiochannel/AudioChannelService.h')
-rw-r--r-- | dom/audiochannel/AudioChannelService.h | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/dom/audiochannel/AudioChannelService.h b/dom/audiochannel/AudioChannelService.h new file mode 100644 index 0000000000..bf7fa387e2 --- /dev/null +++ b/dom/audiochannel/AudioChannelService.h @@ -0,0 +1,245 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_audiochannelservice_h__ +#define mozilla_dom_audiochannelservice_h__ + +#include "nsIObserver.h" +#include "nsTObserverArray.h" +#include "nsTArray.h" + +#include "AudioChannelAgent.h" +#include "nsAttrValue.h" +#include "mozilla/Logging.h" +#include "mozilla/UniquePtr.h" + +#include <functional> + +class nsPIDOMWindowOuter; +struct PRLogModuleInfo; + +namespace mozilla::dom { + +class AudioPlaybackConfig { + public: + AudioPlaybackConfig() + : mVolume(1.0), + mMuted(false), + mSuspend(nsISuspendedTypes::NONE_SUSPENDED), + mNumberOfAgents(0) {} + + AudioPlaybackConfig(float aVolume, bool aMuted, uint32_t aSuspended) + : mVolume(aVolume), + mMuted(aMuted), + mSuspend(aSuspended), + mNumberOfAgents(0) {} + + float mVolume; + bool mMuted; + uint32_t mSuspend; + bool mCapturedAudio = false; + uint32_t mNumberOfAgents; +}; + +class AudioChannelService final : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + /** + * We use `AudibleState` to represent the audible state of an owner of audio + * channel agent. Those information in AudioChannelWindow could help us to + * determine if a tab is being audible or not, in order to tell Chrome JS to + * show the sound indicator or delayed autoplay icon on the tab bar. + * + * - Sound indicator + * When a tab is playing sound, we would show the sound indicator on tab bar + * to tell users that this tab is producing sound now. In addition, the sound + * indicator also give users an ablility to mute or unmute tab. + * + * When an AudioChannelWindow first contains an agent with state `eAudible`, + * or an AudioChannelWindow losts its last agent with state `eAudible`, we + * would notify Chrome JS about those changes, to tell them that a tab has + * been being audible or not, in order to display or remove the indicator for + * a corresponding tab. + * + * - Delayed autoplay icon (Play Tab icon) + * When we enable delaying autoplay, which is to postpone the autoplay media + * for unvisited tab until it first goes to foreground, or user click the + * play tab icon to resume the delayed media. + * + * When an AudioChannelWindow first contains an agent with state `eAudible` or + * `eMaybeAudible`, we would notify Chrome JS about this change, in order to + * show the delayed autoplay tab icon to user, which is used to notice user + * there is a media being delayed starting, and then user can click the play + * tab icon to resume the start of media, or visit that tab to resume delayed + * media automatically. + * + * According to our UX design, we don't show this icon for inaudible media. + * The reason of showing the icon for a tab, where the agent starts with state + * `eMaybeAudible`, is because some video might be silent in the beginning + * but would soon become audible later. + * + * --------------------------------------------------------------------------- + * + * eNotAudible : agent is not audible + * eMaybeAudible : agent is not audible now, but it might be audible later + * eAudible : agent is audible now + */ + enum AudibleState : uint8_t { + eNotAudible = 0, + eMaybeAudible = 1, + eAudible = 2 + }; + + enum AudioCaptureState : bool { eCapturing = true, eNotCapturing = false }; + + enum AudibleChangedReasons : uint32_t { + eVolumeChanged = 0, + eDataAudibleChanged = 1, + ePauseStateChanged = 2 + }; + + /** + * Returns the AudioChannelServce singleton. + * If AudioChannelService doesn't exist, create and return new one. + * Only to be called from main thread. + */ + static already_AddRefed<AudioChannelService> GetOrCreate(); + + /** + * Returns the AudioChannelService singleton if one exists. + * If AudioChannelService doesn't exist, returns null. + */ + static already_AddRefed<AudioChannelService> Get(); + + static LogModule* GetAudioChannelLog(); + + static bool IsEnableAudioCompeting(); + + /** + * Any audio channel agent that starts playing should register itself to + * this service, sharing the AudioChannel. + */ + void RegisterAudioChannelAgent(AudioChannelAgent* aAgent, + AudibleState aAudible); + + /** + * Any audio channel agent that stops playing should unregister itself to + * this service. + */ + void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent); + + /** + * Return the state to indicate this audioChannel for his window should keep + * playing/muted/suspended. + */ + AudioPlaybackConfig GetMediaConfig(nsPIDOMWindowOuter* aWindow) const; + + /** + * Called this method when the audible state of the audio playback changed, + * it would dispatch the playback event to observers which want to know the + * actual audible state of the window. + */ + void AudioAudibleChanged(AudioChannelAgent* aAgent, AudibleState aAudible, + AudibleChangedReasons aReason); + + bool IsWindowActive(nsPIDOMWindowOuter* aWindow); + + void RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow, float aVolume, + bool aMuted); + + // This method needs to know the inner window that wants to capture audio. We + // group agents per top outer window, but we can have multiple innerWindow per + // top outerWindow (subiframes, etc.) and we have to identify all the agents + // just for a particular innerWindow. + void SetWindowAudioCaptured(nsPIDOMWindowOuter* aWindow, + uint64_t aInnerWindowID, bool aCapture); + + void NotifyResumingDelayedMedia(nsPIDOMWindowOuter* aWindow); + + private: + AudioChannelService(); + ~AudioChannelService(); + + void RefreshAgents(nsPIDOMWindowOuter* aWindow, + const std::function<void(AudioChannelAgent*)>& aFunc); + + void RefreshAgentsSuspend(nsPIDOMWindowOuter* aWindow, + nsSuspendedTypes aSuspend); + + static void CreateServiceIfNeeded(); + + /** + * Shutdown the singleton. + */ + static void Shutdown(); + + void RefreshAgentsAudioFocusChanged(AudioChannelAgent* aAgent); + + class AudioChannelWindow final { + public: + explicit AudioChannelWindow(uint64_t aWindowID) + : mWindowID(aWindowID), + mIsAudioCaptured(false), + mShouldSendActiveMediaBlockStopEvent(false) {} + + void AudioAudibleChanged(AudioChannelAgent* aAgent, AudibleState aAudible, + AudibleChangedReasons aReason); + + void AppendAgent(AudioChannelAgent* aAgent, AudibleState aAudible); + void RemoveAgent(AudioChannelAgent* aAgent); + + void NotifyMediaBlockStop(nsPIDOMWindowOuter* aWindow); + + uint64_t mWindowID; + bool mIsAudioCaptured; + AudioPlaybackConfig mConfig; + + // Raw pointer because the AudioChannelAgent must unregister itself. + nsTObserverArray<AudioChannelAgent*> mAgents; + nsTObserverArray<AudioChannelAgent*> mAudibleAgents; + + // If we've dispatched "activeMediaBlockStart" event, we must dispatch + // another event "activeMediablockStop" when the window is resumed from + // suspend-block. + bool mShouldSendActiveMediaBlockStopEvent; + + private: + void AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent, + AudibleChangedReasons aReason); + void RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent, + AudibleChangedReasons aReason); + + void AppendAgentAndIncreaseAgentsNum(AudioChannelAgent* aAgent); + void RemoveAgentAndReduceAgentsNum(AudioChannelAgent* aAgent); + + bool IsFirstAudibleAgent() const; + bool IsLastAudibleAgent() const; + + void NotifyAudioAudibleChanged(nsPIDOMWindowOuter* aWindow, + AudibleState aAudible, + AudibleChangedReasons aReason); + + void MaybeNotifyMediaBlockStart(AudioChannelAgent* aAgent); + }; + + AudioChannelWindow* GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow); + + AudioChannelWindow* GetWindowData(uint64_t aWindowID) const; + + nsTObserverArray<UniquePtr<AudioChannelWindow>> mWindows; +}; + +const char* SuspendTypeToStr(const nsSuspendedTypes& aSuspend); +const char* AudibleStateToStr( + const AudioChannelService::AudibleState& aAudible); +const char* AudibleChangedReasonToStr( + const AudioChannelService::AudibleChangedReasons& aReason); + +} // namespace mozilla::dom + +#endif |