summaryrefslogtreecommitdiffstats
path: root/dom/audiochannel/AudioChannelService.h
blob: bf7fa387e275ac6d79bbfe77dc814bb8b471ced3 (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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
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