Source code

Revision control

Other Tools

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
#ifndef mozilla_dom_audiochannelservice_h__
8
#define mozilla_dom_audiochannelservice_h__
9
10
#include "nsAutoPtr.h"
11
#include "nsIObserver.h"
12
#include "nsTObserverArray.h"
13
#include "nsTArray.h"
14
15
#include "AudioChannelAgent.h"
16
#include "nsAttrValue.h"
17
#include "mozilla/Logging.h"
18
19
#include <functional>
20
21
class nsPIDOMWindowOuter;
22
struct PRLogModuleInfo;
23
24
namespace mozilla {
25
namespace dom {
26
27
class AudioPlaybackConfig {
28
public:
29
AudioPlaybackConfig()
30
: mVolume(1.0),
31
mMuted(false),
32
mSuspend(nsISuspendedTypes::NONE_SUSPENDED),
33
mNumberOfAgents(0) {}
34
35
AudioPlaybackConfig(float aVolume, bool aMuted, uint32_t aSuspended)
36
: mVolume(aVolume),
37
mMuted(aMuted),
38
mSuspend(aSuspended),
39
mNumberOfAgents(0) {}
40
41
float mVolume;
42
bool mMuted;
43
uint32_t mSuspend;
44
bool mCapturedAudio = false;
45
uint32_t mNumberOfAgents;
46
};
47
48
class AudioChannelService final : public nsIObserver {
49
public:
50
NS_DECL_ISUPPORTS
51
NS_DECL_NSIOBSERVER
52
53
/**
54
* We use `AudibleState` to represent the audible state of an owner of audio
55
* channel agent. Those information in AudioChannelWindow could help us to
56
* determine if a tab is being audible or not, in order to tell Chrome JS to
57
* show the sound indicator or delayed autoplay icon on the tab bar.
58
*
59
* - Sound indicator
60
* When a tab is playing sound, we would show the sound indicator on tab bar
61
* to tell users that this tab is producing sound now. In addition, the sound
62
* indicator also give users an ablility to mute or unmute tab.
63
*
64
* When an AudioChannelWindow first contains an agent with state `eAudible`,
65
* or an AudioChannelWindow losts its last agent with state `eAudible`, we
66
* would notify Chrome JS about those changes, to tell them that a tab has
67
* been being audible or not, in order to display or remove the indicator for
68
* a corresponding tab.
69
*
70
* - Delayed autoplay icon (Play Tab icon)
71
* When we enable delaying autoplay, which is to postpone the autoplay media
72
* for unvisited tab until it first goes to foreground, or user click the
73
* play tab icon to resume the delayed media.
74
*
75
* When an AudioChannelWindow first contains an agent with state `eAudible` or
76
* `eMaybeAudible`, we would notify Chrome JS about this change, in order to
77
* show the delayed autoplay tab icon to user, which is used to notice user
78
* there is a media being delayed starting, and then user can click the play
79
* tab icon to resume the start of media, or visit that tab to resume delayed
80
* media automatically.
81
*
82
* According to our UX design, we don't show this icon for inaudible media.
83
* The reason of showing the icon for a tab, where the agent starts with state
84
* `eMaybeAudible`, is because some video might be silent in the beginning
85
* but would soon become audible later.
86
*
87
* ---------------------------------------------------------------------------
88
*
89
* eNotAudible : agent is not audible
90
* eMaybeAudible : agent is not audible now, but it might be audible later
91
* eAudible : agent is audible now
92
*/
93
enum AudibleState : uint8_t {
94
eNotAudible = 0,
95
eMaybeAudible = 1,
96
eAudible = 2
97
};
98
99
enum AudioCaptureState : bool { eCapturing = true, eNotCapturing = false };
100
101
enum AudibleChangedReasons : uint32_t {
102
eVolumeChanged = 0,
103
eDataAudibleChanged = 1,
104
ePauseStateChanged = 2
105
};
106
107
/**
108
* Returns the AudioChannelServce singleton.
109
* If AudioChannelService doesn't exist, create and return new one.
110
* Only to be called from main thread.
111
*/
112
static already_AddRefed<AudioChannelService> GetOrCreate();
113
114
/**
115
* Returns the AudioChannelService singleton if one exists.
116
* If AudioChannelService doesn't exist, returns null.
117
*/
118
static already_AddRefed<AudioChannelService> Get();
119
120
static LogModule* GetAudioChannelLog();
121
122
static bool IsEnableAudioCompeting();
123
124
/**
125
* Any audio channel agent that starts playing should register itself to
126
* this service, sharing the AudioChannel.
127
*/
128
void RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
129
AudibleState aAudible);
130
131
/**
132
* Any audio channel agent that stops playing should unregister itself to
133
* this service.
134
*/
135
void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
136
137
/**
138
* Return the state to indicate this audioChannel for his window should keep
139
* playing/muted/suspended.
140
*/
141
AudioPlaybackConfig GetMediaConfig(nsPIDOMWindowOuter* aWindow) const;
142
143
/**
144
* Called this method when the audible state of the audio playback changed,
145
* it would dispatch the playback event to observers which want to know the
146
* actual audible state of the window.
147
*/
148
void AudioAudibleChanged(AudioChannelAgent* aAgent, AudibleState aAudible,
149
AudibleChangedReasons aReason);
150
151
bool IsWindowActive(nsPIDOMWindowOuter* aWindow);
152
153
void RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow, float aVolume,
154
bool aMuted);
155
void RefreshAgentsSuspend(nsPIDOMWindowOuter* aWindow,
156
nsSuspendedTypes aSuspend);
157
158
// This method needs to know the inner window that wants to capture audio. We
159
// group agents per top outer window, but we can have multiple innerWindow per
160
// top outerWindow (subiframes, etc.) and we have to identify all the agents
161
// just for a particular innerWindow.
162
void SetWindowAudioCaptured(nsPIDOMWindowOuter* aWindow,
163
uint64_t aInnerWindowID, bool aCapture);
164
165
void NotifyMediaResumedFromBlock(nsPIDOMWindowOuter* aWindow);
166
167
private:
168
AudioChannelService();
169
~AudioChannelService();
170
171
void RefreshAgents(nsPIDOMWindowOuter* aWindow,
172
const std::function<void(AudioChannelAgent*)>& aFunc);
173
174
static void CreateServiceIfNeeded();
175
176
/**
177
* Shutdown the singleton.
178
*/
179
static void Shutdown();
180
181
void RefreshAgentsAudioFocusChanged(AudioChannelAgent* aAgent);
182
183
class AudioChannelWindow final {
184
public:
185
explicit AudioChannelWindow(uint64_t aWindowID)
186
: mWindowID(aWindowID),
187
mIsAudioCaptured(false),
188
mOwningAudioFocus(!AudioChannelService::IsEnableAudioCompeting()),
189
mShouldSendActiveMediaBlockStopEvent(false) {}
190
191
void AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent);
192
void AudioAudibleChanged(AudioChannelAgent* aAgent, AudibleState aAudible,
193
AudibleChangedReasons aReason);
194
195
void AppendAgent(AudioChannelAgent* aAgent, AudibleState aAudible);
196
void RemoveAgent(AudioChannelAgent* aAgent);
197
198
void NotifyMediaBlockStop(nsPIDOMWindowOuter* aWindow);
199
200
uint64_t mWindowID;
201
bool mIsAudioCaptured;
202
AudioPlaybackConfig mConfig;
203
204
// Raw pointer because the AudioChannelAgent must unregister itself.
205
nsTObserverArray<AudioChannelAgent*> mAgents;
206
nsTObserverArray<AudioChannelAgent*> mAudibleAgents;
207
208
// Owning audio focus when the window starts playing audible sound, and
209
// lose audio focus when other windows starts playing.
210
bool mOwningAudioFocus;
211
212
// If we've dispatched "activeMediaBlockStart" event, we must dispatch
213
// another event "activeMediablockStop" when the window is resumed from
214
// suspend-block.
215
bool mShouldSendActiveMediaBlockStopEvent;
216
217
private:
218
void AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent,
219
AudibleChangedReasons aReason);
220
void RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent,
221
AudibleChangedReasons aReason);
222
223
void AppendAgentAndIncreaseAgentsNum(AudioChannelAgent* aAgent);
224
void RemoveAgentAndReduceAgentsNum(AudioChannelAgent* aAgent);
225
226
bool IsFirstAudibleAgent() const;
227
bool IsLastAudibleAgent() const;
228
229
void NotifyAudioAudibleChanged(nsPIDOMWindowOuter* aWindow,
230
AudibleState aAudible,
231
AudibleChangedReasons aReason);
232
233
void NotifyChannelActive(uint64_t aWindowID, bool aActive);
234
void MaybeNotifyMediaBlockStart(AudioChannelAgent* aAgent);
235
236
void RequestAudioFocus(AudioChannelAgent* aAgent);
237
238
// We need to do audio competing only when the new incoming agent started.
239
void NotifyAudioCompetingChanged(AudioChannelAgent* aAgent);
240
241
uint32_t GetCompetingBehavior(AudioChannelAgent* aAgent) const;
242
bool IsAgentInvolvingInAudioCompeting(AudioChannelAgent* aAgent) const;
243
bool IsAudioCompetingInSameTab() const;
244
bool IsContainingPlayingAgent(AudioChannelAgent* aAgent) const;
245
246
bool IsInactiveWindow() const;
247
};
248
249
AudioChannelWindow* GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow);
250
251
AudioChannelWindow* GetWindowData(uint64_t aWindowID) const;
252
253
nsTObserverArray<nsAutoPtr<AudioChannelWindow>> mWindows;
254
};
255
256
const char* SuspendTypeToStr(const nsSuspendedTypes& aSuspend);
257
const char* AudibleStateToStr(
258
const AudioChannelService::AudibleState& aAudible);
259
const char* AudibleChangedReasonToStr(
260
const AudioChannelService::AudibleChangedReasons& aReason);
261
262
} // namespace dom
263
} // namespace mozilla
264
265
#endif