diff options
Diffstat (limited to '')
-rw-r--r-- | dom/media/mediasession/MediaSession.cpp | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/dom/media/mediasession/MediaSession.cpp b/dom/media/mediasession/MediaSession.cpp new file mode 100644 index 0000000000..e55fa28d96 --- /dev/null +++ b/dom/media/mediasession/MediaSession.cpp @@ -0,0 +1,335 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/ContentMediaController.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/MediaSession.h" +#include "mozilla/dom/MediaControlUtils.h" +#include "mozilla/dom/WindowContext.h" +#include "mozilla/EnumeratedArrayCycleCollection.h" + +// avoid redefined macro in unified build +#undef LOG +#define LOG(msg, ...) \ + MOZ_LOG(gMediaControlLog, LogLevel::Debug, \ + ("MediaSession=%p, " msg, this, ##__VA_ARGS__)) + +namespace mozilla::dom { + +// We don't use NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE because we need to +// unregister MediaSession from document's activity listeners. +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(MediaSession) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaSession) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaMetadata) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActionHandlers) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaSession) + tmp->Shutdown(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaMetadata) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mActionHandlers) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaSession) +NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaSession) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaSession) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity) +NS_INTERFACE_MAP_END + +MediaSession::MediaSession(nsPIDOMWindowInner* aParent) + : mParent(aParent), mDoc(mParent->GetExtantDoc()) { + MOZ_ASSERT(mParent); + MOZ_ASSERT(mDoc); + mDoc->RegisterActivityObserver(this); + if (mDoc->IsCurrentActiveDocument()) { + SetMediaSessionDocStatus(SessionDocStatus::eActive); + } +} + +void MediaSession::Shutdown() { + if (mDoc) { + mDoc->UnregisterActivityObserver(this); + } + if (mParent) { + SetMediaSessionDocStatus(SessionDocStatus::eInactive); + } +} + +void MediaSession::NotifyOwnerDocumentActivityChanged() { + const bool isDocActive = mDoc->IsCurrentActiveDocument(); + LOG("Document activity changed, isActive=%d", isDocActive); + if (isDocActive) { + SetMediaSessionDocStatus(SessionDocStatus::eActive); + } else { + SetMediaSessionDocStatus(SessionDocStatus::eInactive); + } +} + +void MediaSession::SetMediaSessionDocStatus(SessionDocStatus aState) { + if (mSessionDocState == aState) { + return; + } + mSessionDocState = aState; + NotifyMediaSessionDocStatus(mSessionDocState); +} + +nsPIDOMWindowInner* MediaSession::GetParentObject() const { return mParent; } + +JSObject* MediaSession::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return MediaSession_Binding::Wrap(aCx, this, aGivenProto); +} + +MediaMetadata* MediaSession::GetMetadata() const { return mMediaMetadata; } + +void MediaSession::SetMetadata(MediaMetadata* aMetadata) { + mMediaMetadata = aMetadata; + NotifyMetadataUpdated(); +} + +void MediaSession::SetPlaybackState( + const MediaSessionPlaybackState& aPlaybackState) { + if (mDeclaredPlaybackState == aPlaybackState) { + return; + } + mDeclaredPlaybackState = aPlaybackState; + NotifyPlaybackStateUpdated(); +} + +MediaSessionPlaybackState MediaSession::PlaybackState() const { + return mDeclaredPlaybackState; +} + +void MediaSession::SetActionHandler(MediaSessionAction aAction, + MediaSessionActionHandler* aHandler) { + MOZ_ASSERT(size_t(aAction) < ArrayLength(mActionHandlers)); + // If the media session changes its supported action, then we would propagate + // this information to the chrome process in order to run the media session + // actions update algorithm. + // https://w3c.github.io/mediasession/#supported-media-session-actions + RefPtr<MediaSessionActionHandler>& hanlder = mActionHandlers[aAction]; + if (!hanlder && aHandler) { + NotifyEnableSupportedAction(aAction); + } else if (hanlder && !aHandler) { + NotifyDisableSupportedAction(aAction); + } + mActionHandlers[aAction] = aHandler; +} + +MediaSessionActionHandler* MediaSession::GetActionHandler( + MediaSessionAction aAction) const { + MOZ_ASSERT(size_t(aAction) < ArrayLength(mActionHandlers)); + return mActionHandlers[aAction]; +} + +void MediaSession::SetPositionState(const MediaPositionState& aState, + ErrorResult& aRv) { + // https://w3c.github.io/mediasession/#dom-mediasession-setpositionstate + // If the state is an empty dictionary then clear the position state. + if (!aState.IsAnyMemberPresent()) { + mPositionState.reset(); + return; + } + + // If the duration is not present, throw a TypeError. + if (!aState.mDuration.WasPassed()) { + return aRv.ThrowTypeError("Duration is not present"); + } + + // If the duration is negative, throw a TypeError. + if (aState.mDuration.WasPassed() && aState.mDuration.Value() < 0.0) { + return aRv.ThrowTypeError(nsPrintfCString( + "Invalid duration %f, it can't be negative", aState.mDuration.Value())); + } + + // If the position is negative or greater than duration, throw a TypeError. + if (aState.mPosition.WasPassed() && + (aState.mPosition.Value() < 0.0 || + aState.mPosition.Value() > aState.mDuration.Value())) { + return aRv.ThrowTypeError(nsPrintfCString( + "Invalid position %f, it can't be negative or greater than duration", + aState.mPosition.Value())); + } + + // If the playbackRate is zero, throw a TypeError. + if (aState.mPlaybackRate.WasPassed() && aState.mPlaybackRate.Value() == 0.0) { + return aRv.ThrowTypeError("The playbackRate is zero"); + } + + // If the position is not present, set it to zero. + double position = aState.mPosition.WasPassed() ? aState.mPosition.Value() : 0; + + // If the playbackRate is not present, set it to 1.0. + double playbackRate = + aState.mPlaybackRate.WasPassed() ? aState.mPlaybackRate.Value() : 1.0; + + // Update the position state and last position updated time. + MOZ_ASSERT(aState.mDuration.WasPassed()); + mPositionState = + Some(PositionState(aState.mDuration.Value(), playbackRate, position)); + NotifyPositionStateChanged(); +} + +void MediaSession::NotifyHandler(const MediaSessionActionDetails& aDetails) { + DispatchNotifyHandler(aDetails); +} + +void MediaSession::DispatchNotifyHandler( + const MediaSessionActionDetails& aDetails) { + class Runnable final : public mozilla::Runnable { + public: + Runnable(const MediaSession* aSession, + const MediaSessionActionDetails& aDetails) + : mozilla::Runnable("MediaSession::DispatchNotifyHandler"), + mSession(aSession), + mDetails(aDetails) {} + + MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override { + if (RefPtr<MediaSessionActionHandler> handler = + mSession->GetActionHandler(mDetails.mAction)) { + handler->Call(mDetails); + } + return NS_OK; + } + + private: + RefPtr<const MediaSession> mSession; + MediaSessionActionDetails mDetails; + }; + + RefPtr<nsIRunnable> runnable = new Runnable(this, aDetails); + NS_DispatchToMainThread(runnable); +} + +bool MediaSession::IsSupportedAction(MediaSessionAction aAction) const { + MOZ_ASSERT(size_t(aAction) < ArrayLength(mActionHandlers)); + return mActionHandlers[aAction] != nullptr; +} + +bool MediaSession::IsActive() const { + RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext(); + MOZ_ASSERT(currentBC); + RefPtr<WindowContext> wc = currentBC->GetTopWindowContext(); + if (!wc) { + return false; + } + Maybe<uint64_t> activeSessionContextId = wc->GetActiveMediaSessionContextId(); + if (!activeSessionContextId) { + return false; + } + LOG("session context Id=%" PRIu64 ", active session context Id=%" PRIu64, + currentBC->Id(), *activeSessionContextId); + return *activeSessionContextId == currentBC->Id(); +} + +void MediaSession::NotifyMediaSessionDocStatus(SessionDocStatus aState) { + RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext(); + MOZ_ASSERT(currentBC, "Update session status after context destroyed!"); + + RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC); + if (!updater) { + return; + } + if (aState == SessionDocStatus::eActive) { + updater->NotifySessionCreated(currentBC->Id()); + // If media session set its attributes before its document becomes active, + // then we would notify those attributes which hasn't been notified as well + // because attributes update would only happen if its document is already + // active. + NotifyMediaSessionAttributes(); + } else { + updater->NotifySessionDestroyed(currentBC->Id()); + } +} + +void MediaSession::NotifyMediaSessionAttributes() { + MOZ_ASSERT(mSessionDocState == SessionDocStatus::eActive); + if (mDeclaredPlaybackState != MediaSessionPlaybackState::None) { + NotifyPlaybackStateUpdated(); + } + if (mMediaMetadata) { + NotifyMetadataUpdated(); + } + for (size_t idx = 0; idx < ArrayLength(mActionHandlers); idx++) { + MediaSessionAction action = static_cast<MediaSessionAction>(idx); + if (mActionHandlers[action]) { + NotifyEnableSupportedAction(action); + } + } + if (mPositionState) { + NotifyPositionStateChanged(); + } +} + +void MediaSession::NotifyPlaybackStateUpdated() { + if (mSessionDocState != SessionDocStatus::eActive) { + return; + } + RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext(); + MOZ_ASSERT(currentBC, + "Update session playback state after context destroyed!"); + if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC)) { + updater->SetDeclaredPlaybackState(currentBC->Id(), mDeclaredPlaybackState); + } +} + +void MediaSession::NotifyMetadataUpdated() { + if (mSessionDocState != SessionDocStatus::eActive) { + return; + } + RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext(); + MOZ_ASSERT(currentBC, "Update session metadata after context destroyed!"); + + Maybe<MediaMetadataBase> metadata; + if (GetMetadata()) { + metadata.emplace(*(GetMetadata()->AsMetadataBase())); + } + if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC)) { + updater->UpdateMetadata(currentBC->Id(), metadata); + } +} + +void MediaSession::NotifyEnableSupportedAction(MediaSessionAction aAction) { + if (mSessionDocState != SessionDocStatus::eActive) { + return; + } + RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext(); + MOZ_ASSERT(currentBC, "Update action after context destroyed!"); + if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC)) { + updater->EnableAction(currentBC->Id(), aAction); + } +} + +void MediaSession::NotifyDisableSupportedAction(MediaSessionAction aAction) { + if (mSessionDocState != SessionDocStatus::eActive) { + return; + } + RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext(); + MOZ_ASSERT(currentBC, "Update action after context destroyed!"); + if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC)) { + updater->DisableAction(currentBC->Id(), aAction); + } +} + +void MediaSession::NotifyPositionStateChanged() { + if (mSessionDocState != SessionDocStatus::eActive) { + return; + } + RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext(); + MOZ_ASSERT(currentBC, "Update action after context destroyed!"); + if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC)) { + updater->UpdatePositionState(currentBC->Id(), *mPositionState); + } +} + +} // namespace mozilla::dom |