/* 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(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 mMetadata; MediaSessionPlaybackState mDeclaredPlaybackState = MediaSessionPlaybackState::None; // 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& 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 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& aMetadata) override; void EnableAction(uint64_t aBrowsingContextId, MediaSessionAction aAction) override; void DisableAction(uint64_t aBrowsingContextId, MediaSessionAction aAction) override; void UpdatePositionState(uint64_t aBrowsingContextId, const 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& MetadataChangedEvent() { return mMetadataChangedEvent; } MediaEventSource& PositionChangedEvent() { return mPositionStateChangedEvent; } MediaEventSource& 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>& 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 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& 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 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; // 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 mMediaSessionInfoMap; MediaEventProducer mMetadataChangedEvent; MediaEventProducer> mSupportedActionsChangedEvent; MediaEventProducer mPositionStateChangedEvent; MediaEventProducer mPlaybackStateChangedEvent; MediaPlaybackStatus mPlaybackStatusDelegate; }; } // namespace mozilla::dom #endif // DOM_MEDIA_MEDIACONTROL_MEDIASTATUSMANAGER_H_