summaryrefslogtreecommitdiffstats
path: root/dom/media/mediacontrol/MediaStatusManager.h
blob: a4216c8453eadb0d6500208c5a18e1dca29c2805 (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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
/* 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 DOM_MEDIA_MEDIACONTROL_MEDIASTATUSMANAGER_H_
#define DOM_MEDIA_MEDIACONTROL_MEDIASTATUSMANAGER_H_

#include "MediaControlKeySource.h"
#include "MediaEventSource.h"
#include "MediaPlaybackStatus.h"
#include "mozilla/dom/MediaMetadata.h"
#include "mozilla/dom/MediaSessionBinding.h"
#include "mozilla/Maybe.h"
#include "nsTHashMap.h"
#include "nsISupportsImpl.h"

namespace mozilla::dom {

class MediaSessionInfo {
 public:
  MediaSessionInfo() = default;

  explicit MediaSessionInfo(MediaMetadataBase& aMetadata) {
    mMetadata.emplace(aMetadata);
  }

  MediaSessionInfo(MediaMetadataBase& aMetadata,
                   MediaSessionPlaybackState& aState) {
    mMetadata.emplace(aMetadata);
    mDeclaredPlaybackState = aState;
  }

  static MediaSessionInfo EmptyInfo() { return MediaSessionInfo(); }

  static uint32_t GetActionBitMask(MediaSessionAction aAction) {
    return 1 << static_cast<uint8_t>(aAction);
  }

  void EnableAction(MediaSessionAction aAction) {
    mSupportedActions |= GetActionBitMask(aAction);
  }

  void DisableAction(MediaSessionAction aAction) {
    mSupportedActions &= ~GetActionBitMask(aAction);
  }

  bool IsActionSupported(MediaSessionAction aAction) const {
    return mSupportedActions & GetActionBitMask(aAction);
  }

  // These attributes are all propagated from the media session in the content
  // process.
  Maybe<MediaMetadataBase> mMetadata;
  MediaSessionPlaybackState mDeclaredPlaybackState =
      MediaSessionPlaybackState::None;
  Maybe<PositionState> mPositionState;
  // Use bitwise to store the supported actions.
  uint32_t mSupportedActions = 0;
};

/**
 * IMediaInfoUpdater is an interface which provides methods to update the media
 * related information that happens in the content process.
 */
class IMediaInfoUpdater {
  NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING

  // Use this method to update controlled media's playback state and the
  // browsing context where controlled media exists. When notifying the state
  // change, we MUST follow the following rules.
  // (1) `eStart` MUST be the first state and `eStop` MUST be the last state
  // (2) Do not notify same state again
  // (3) `ePaused` can only be notified after notifying `ePlayed`.
  virtual void NotifyMediaPlaybackChanged(uint64_t aBrowsingContextId,
                                          MediaPlaybackState aState) = 0;

  // Use this method to update the audible state of controlled media, and MUST
  // follow the following rules in which `audible` and `inaudible` should be a
  // pair. `inaudible` should always be notified after `audible`. When audible
  // media paused, `inaudible` should be notified
  // Eg. (O) `audible` -> `inaudible` -> `audible` -> `inaudible`
  //     (X) `inaudible` -> `audible`    [notify `inaudible` before `audible`]
  //     (X) `audible` -> `audible`      [notify `audible` twice]
  //     (X) `audible` -> (media pauses) [forgot to notify `inaudible`]
  virtual void NotifyMediaAudibleChanged(uint64_t aBrowsingContextId,
                                         MediaAudibleState aState) = 0;

  // Use this method to update media session's declared playback state for the
  // specific media session.
  virtual void SetDeclaredPlaybackState(uint64_t aBrowsingContextId,
                                        MediaSessionPlaybackState aState) = 0;

  // Use these methods to update controller's media session list. We'd use it
  // when media session is created/destroyed in the content process.
  virtual void NotifySessionCreated(uint64_t aBrowsingContextId) = 0;
  virtual void NotifySessionDestroyed(uint64_t aBrowsingContextId) = 0;

  // Use this method to update the metadata for the specific media session.
  virtual void UpdateMetadata(uint64_t aBrowsingContextId,
                              const Maybe<MediaMetadataBase>& aMetadata) = 0;

  // Use this method to update the picture in picture mode state of controlled
  // media, and it's safe to notify same state again.
  virtual void SetIsInPictureInPictureMode(uint64_t aBrowsingContextId,
                                           bool aIsInPictureInPictureMode) = 0;

  // Use these methods to update the supported media session action for the
  // specific media session. For a media session from a given browsing context,
  // do not re-enable the same action, or disable the action without enabling it
  // before.
  virtual void EnableAction(uint64_t aBrowsingContextId,
                            MediaSessionAction aAction) = 0;
  virtual void DisableAction(uint64_t aBrowsingContextId,
                             MediaSessionAction aAction) = 0;

  // Use this method when media enters or leaves the fullscreen.
  virtual void NotifyMediaFullScreenState(uint64_t aBrowsingContextId,
                                          bool aIsInFullScreen) = 0;

  // Use this method when media session update its position state.
  virtual void UpdatePositionState(uint64_t aBrowsingContextId,
                                   const Maybe<PositionState>& aState) = 0;
};

/**
 * MediaStatusManager would decide the media related status which can represents
 * the whole tab. The status includes the playback status, tab's metadata and
 * the active media session ID if it exists.
 *
 * We would use `IMediaInfoUpdater` methods to update the media playback related
 * information and then use `MediaPlaybackStatus` to determine the final
 * playback state.
 *
 * The metadata would be the one from the active media session, or the default
 * one. This class would determine which media session is an active media
 * session [1] whithin a tab. It tracks all alive media sessions within a tab
 * and store their metadata which could be used to show on the virtual media
 * control interface. In addition, we can use it to get the current media
 * metadata even if there is no media session existing. However, the meaning of
 * active media session here is not equal to the definition from the spec [1].
 * We just choose the session which is the active one inside the tab, the global
 * active media session among different tabs would be the one inside the main
 * controller which is determined by MediaControlService.
 *
 * [1] https://w3c.github.io/mediasession/#active-media-session
 */
class MediaStatusManager : public IMediaInfoUpdater {
 public:
  explicit MediaStatusManager(uint64_t aBrowsingContextId);

  // IMediaInfoUpdater's methods
  void NotifyMediaPlaybackChanged(uint64_t aBrowsingContextId,
                                  MediaPlaybackState aState) override;
  void NotifyMediaAudibleChanged(uint64_t aBrowsingContextId,
                                 MediaAudibleState aState) override;
  void SetDeclaredPlaybackState(uint64_t aSessionContextId,
                                MediaSessionPlaybackState aState) override;
  void NotifySessionCreated(uint64_t aSessionContextId) override;
  void NotifySessionDestroyed(uint64_t aSessionContextId) override;
  void UpdateMetadata(uint64_t aSessionContextId,
                      const Maybe<MediaMetadataBase>& aMetadata) override;
  void EnableAction(uint64_t aBrowsingContextId,
                    MediaSessionAction aAction) override;
  void DisableAction(uint64_t aBrowsingContextId,
                     MediaSessionAction aAction) override;
  void UpdatePositionState(uint64_t aBrowsingContextId,
                           const Maybe<PositionState>& aState) override;

  // Return active media session's metadata if active media session exists and
  // it has already set its metadata. Otherwise, return default media metadata
  // which is based on website's title and favicon.
  MediaMetadataBase GetCurrentMediaMetadata() const;

  bool IsMediaAudible() const;
  bool IsMediaPlaying() const;
  bool IsAnyMediaBeingControlled() const;

  // These events would be notified when the active media session's certain
  // property changes.
  MediaEventSource<MediaMetadataBase>& MetadataChangedEvent() {
    return mMetadataChangedEvent;
  }

  MediaEventSource<Maybe<PositionState>>& PositionChangedEvent() {
    return mPositionStateChangedEvent;
  }

  MediaEventSource<MediaSessionPlaybackState>& PlaybackChangedEvent() {
    return mPlaybackStateChangedEvent;
  }

  // Return the actual playback state.
  MediaSessionPlaybackState PlaybackState() const;

  // When page title changes, we might need to update it on the default
  // metadata as well.
  void NotifyPageTitleChanged();

 protected:
  ~MediaStatusManager() = default;

  // This event would be notified when the active media session changes its
  // supported actions.
  MediaEventSource<nsTArray<MediaSessionAction>>&
  SupportedActionsChangedEvent() {
    return mSupportedActionsChangedEvent;
  }

  uint64_t mTopLevelBrowsingContextId;

  // Within a tab, the Id of the browsing context which has already created a
  // media session and owns the audio focus within a tab.
  Maybe<uint64_t> mActiveMediaSessionContextId;

  void ClearActiveMediaSessionContextIdIfNeeded();

 private:
  nsString GetDefaultFaviconURL() const;
  nsString GetDefaultTitle() const;
  MediaMetadataBase CreateDefaultMetadata() const;
  bool IsInPrivateBrowsing() const;
  void FillMissingTitleAndArtworkIfNeeded(MediaMetadataBase& aMetadata) const;

  bool IsSessionOwningAudioFocus(uint64_t aBrowsingContextId) const;
  void SetActiveMediaSessionContextId(uint64_t aBrowsingContextId);
  void HandleAudioFocusOwnerChanged(Maybe<uint64_t>& aBrowsingContextId);

  void NotifySupportedKeysChangedIfNeeded(uint64_t aBrowsingContextId);

  // Return a copyable array filled with the supported media session actions.
  // Use copyable array so that we can use the result as a parameter for the
  // media event.
  CopyableTArray<MediaSessionAction> GetSupportedActions() const;

  void StoreMediaSessionContextIdOnWindowContext();

  // When the amount of playing media changes, we would use this function to
  // update the guessed playback state.
  void SetGuessedPlayState(MediaSessionPlaybackState aState);

  // Whenever the declared playback state or the guessed playback state changes,
  // we should recompute actual playback state to know if we need to update the
  // virtual control interface.
  void UpdateActualPlaybackState();

  // Return the active media session's declared playback state. If the active
  // media session doesn't exist, return  'None' instead.
  MediaSessionPlaybackState GetCurrentDeclaredPlaybackState() const;

  // Return the active media session's position state. If the active media
  // session doesn't exist or doesn't have any state, Nothing is returned.
  Maybe<PositionState> GetCurrentPositionState() const;

  // This state can match to the `guessed playback state` in the spec [1], it
  // indicates if we have any media element playing within the tab which this
  // controller belongs to. But currently we only take media elements into
  // account, which is different from the way the spec recommends. In addition,
  // We don't support web audio and plugin and not consider audible state of
  // media.
  // [1] https://w3c.github.io/mediasession/#guessed-playback-state
  MediaSessionPlaybackState mGuessedPlaybackState =
      MediaSessionPlaybackState::None;

  // This playback state would be the final playback which can be used to know
  // if the controller is playing or not.
  // https://w3c.github.io/mediasession/#actual-playback-state
  MediaSessionPlaybackState mActualPlaybackState =
      MediaSessionPlaybackState::None;

  nsTHashMap<nsUint64HashKey, MediaSessionInfo> mMediaSessionInfoMap;
  MediaEventProducer<MediaMetadataBase> mMetadataChangedEvent;
  MediaEventProducer<nsTArray<MediaSessionAction>>
      mSupportedActionsChangedEvent;
  MediaEventProducer<Maybe<PositionState>> mPositionStateChangedEvent;
  MediaEventProducer<MediaSessionPlaybackState> mPlaybackStateChangedEvent;
  MediaPlaybackStatus mPlaybackStatusDelegate;
};

}  // namespace mozilla::dom

#endif  // DOM_MEDIA_MEDIACONTROL_MEDIASTATUSMANAGER_H_