tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

MediaSession.cpp (12839B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      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
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/MediaSession.h"
      8 
      9 #include "mozilla/EnumeratedArrayCycleCollection.h"
     10 #include "mozilla/dom/BrowsingContext.h"
     11 #include "mozilla/dom/ContentMediaController.h"
     12 #include "mozilla/dom/Document.h"
     13 #include "mozilla/dom/MediaControlUtils.h"
     14 #include "mozilla/dom/WindowContext.h"
     15 
     16 // avoid redefined macro in unified build
     17 #undef LOG
     18 #define LOG(msg, ...)                        \
     19  MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
     20          ("MediaSession=%p, " msg, this, ##__VA_ARGS__))
     21 
     22 namespace mozilla::dom {
     23 
     24 double PositionState::CurrentPlaybackPosition(TimeStamp aNow) const {
     25  // https://w3c.github.io/mediasession/#current-playback-position
     26 
     27  // Set time elapsed to the system time in seconds minus the last position
     28  // updated time.
     29  auto timeElapsed = aNow - mPositionUpdatedTime;
     30  // Mutliply time elapsed with actual playback rate.
     31  timeElapsed = timeElapsed.MultDouble(mPlaybackRate);
     32  // Set position to time elapsed added to last reported playback position.
     33  auto position = timeElapsed.ToSeconds() + mLastReportedPlaybackPosition;
     34 
     35  // If position is less than zero, return zero.
     36  if (position < 0.0) {
     37    return 0.0;
     38  }
     39  // If position is greater than duration, return duration.
     40  if (position > mDuration) {
     41    return mDuration;
     42  }
     43  // Return position.
     44  return position;
     45 }
     46 
     47 // We don't use NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE because we need to
     48 // unregister MediaSession from document's activity listeners.
     49 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(MediaSession)
     50 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaSession)
     51  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
     52  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaMetadata)
     53  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActionHandlers)
     54  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc)
     55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     56 
     57 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaSession)
     58  tmp->Shutdown();
     59  NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
     60  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaMetadata)
     61  NS_IMPL_CYCLE_COLLECTION_UNLINK(mActionHandlers)
     62  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc)
     63  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
     64 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     65 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaSession)
     66 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaSession)
     67 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaSession)
     68  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     69  NS_INTERFACE_MAP_ENTRY(nsISupports)
     70  NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity)
     71 NS_INTERFACE_MAP_END
     72 
     73 MediaSession::MediaSession(nsPIDOMWindowInner* aParent)
     74    : mParent(aParent), mDoc(mParent->GetExtantDoc()) {
     75  MOZ_ASSERT(mParent);
     76  MOZ_ASSERT(mDoc);
     77  mDoc->RegisterActivityObserver(this);
     78  if (mDoc->IsCurrentActiveDocument()) {
     79    SetMediaSessionDocStatus(SessionDocStatus::eActive);
     80  }
     81 }
     82 
     83 void MediaSession::Shutdown() {
     84  if (mDoc) {
     85    mDoc->UnregisterActivityObserver(this);
     86  }
     87  if (mParent) {
     88    SetMediaSessionDocStatus(SessionDocStatus::eInactive);
     89  }
     90 }
     91 
     92 void MediaSession::NotifyOwnerDocumentActivityChanged() {
     93  const bool isDocActive = mDoc->IsCurrentActiveDocument();
     94  LOG("Document activity changed, isActive=%d", isDocActive);
     95  if (isDocActive) {
     96    SetMediaSessionDocStatus(SessionDocStatus::eActive);
     97  } else {
     98    SetMediaSessionDocStatus(SessionDocStatus::eInactive);
     99  }
    100 }
    101 
    102 void MediaSession::SetMediaSessionDocStatus(SessionDocStatus aState) {
    103  if (mSessionDocState == aState) {
    104    return;
    105  }
    106  mSessionDocState = aState;
    107  NotifyMediaSessionDocStatus(mSessionDocState);
    108 }
    109 
    110 nsPIDOMWindowInner* MediaSession::GetParentObject() const { return mParent; }
    111 
    112 JSObject* MediaSession::WrapObject(JSContext* aCx,
    113                                   JS::Handle<JSObject*> aGivenProto) {
    114  return MediaSession_Binding::Wrap(aCx, this, aGivenProto);
    115 }
    116 
    117 MediaMetadata* MediaSession::GetMetadata() const { return mMediaMetadata; }
    118 
    119 void MediaSession::SetMetadata(MediaMetadata* aMetadata) {
    120  mMediaMetadata = aMetadata;
    121  NotifyMetadataUpdated();
    122 }
    123 
    124 void MediaSession::SetPlaybackState(
    125    const MediaSessionPlaybackState& aPlaybackState) {
    126  if (mDeclaredPlaybackState == aPlaybackState) {
    127    return;
    128  }
    129  mDeclaredPlaybackState = aPlaybackState;
    130  NotifyPlaybackStateUpdated();
    131 }
    132 
    133 MediaSessionPlaybackState MediaSession::PlaybackState() const {
    134  return mDeclaredPlaybackState;
    135 }
    136 
    137 void MediaSession::SetActionHandler(MediaSessionAction aAction,
    138                                    MediaSessionActionHandler* aHandler) {
    139  MOZ_ASSERT(size_t(aAction) < std::size(mActionHandlers));
    140  // If the media session changes its supported action, then we would propagate
    141  // this information to the chrome process in order to run the media session
    142  // actions update algorithm.
    143  // https://w3c.github.io/mediasession/#supported-media-session-actions
    144  RefPtr<MediaSessionActionHandler>& handler = mActionHandlers[aAction];
    145  if (!handler && aHandler) {
    146    NotifyEnableSupportedAction(aAction);
    147  } else if (handler && !aHandler) {
    148    NotifyDisableSupportedAction(aAction);
    149  }
    150  mActionHandlers[aAction] = aHandler;
    151 }
    152 
    153 MediaSessionActionHandler* MediaSession::GetActionHandler(
    154    MediaSessionAction aAction) const {
    155  MOZ_ASSERT(size_t(aAction) < std::size(mActionHandlers));
    156  return mActionHandlers[aAction];
    157 }
    158 
    159 void MediaSession::SetPositionState(const MediaPositionState& aState,
    160                                    ErrorResult& aRv) {
    161  // https://w3c.github.io/mediasession/#dom-mediasession-setpositionstate
    162  // If the state is an empty dictionary then clear the position state.
    163  if (!aState.IsAnyMemberPresent()) {
    164    mPositionState.reset();
    165    NotifyPositionStateChanged();
    166    return;
    167  }
    168 
    169  // If the duration is not present, throw a TypeError.
    170  if (!aState.mDuration.WasPassed()) {
    171    return aRv.ThrowTypeError("Duration is not present");
    172  }
    173 
    174  // If the duration is negative, throw a TypeError.
    175  if (aState.mDuration.WasPassed() && aState.mDuration.Value() < 0.0) {
    176    return aRv.ThrowTypeError(nsPrintfCString(
    177        "Invalid duration %f, it can't be negative", aState.mDuration.Value()));
    178  }
    179 
    180  // If the position is negative or greater than duration, throw a TypeError.
    181  if (aState.mPosition.WasPassed() &&
    182      (aState.mPosition.Value() < 0.0 ||
    183       aState.mPosition.Value() > aState.mDuration.Value())) {
    184    return aRv.ThrowTypeError(nsPrintfCString(
    185        "Invalid position %f, it can't be negative or greater than duration",
    186        aState.mPosition.Value()));
    187  }
    188 
    189  // If the playbackRate is zero, throw a TypeError.
    190  if (aState.mPlaybackRate.WasPassed() && aState.mPlaybackRate.Value() == 0.0) {
    191    return aRv.ThrowTypeError("The playbackRate is zero");
    192  }
    193 
    194  // If the position is not present, set it to zero.
    195  double position = aState.mPosition.WasPassed() ? aState.mPosition.Value() : 0;
    196 
    197  // If the playbackRate is not present, set it to 1.0.
    198  double playbackRate =
    199      aState.mPlaybackRate.WasPassed() ? aState.mPlaybackRate.Value() : 1.0;
    200 
    201  // Update the position state and last position updated time.
    202  MOZ_ASSERT(aState.mDuration.WasPassed());
    203  mPositionState = Some(PositionState(aState.mDuration.Value(), playbackRate,
    204                                      position, TimeStamp::Now()));
    205  NotifyPositionStateChanged();
    206 }
    207 
    208 void MediaSession::NotifyHandler(const MediaSessionActionDetails& aDetails) {
    209  DispatchNotifyHandler(aDetails);
    210 }
    211 
    212 void MediaSession::DispatchNotifyHandler(
    213    const MediaSessionActionDetails& aDetails) {
    214  class Runnable final : public mozilla::Runnable {
    215   public:
    216    Runnable(const MediaSession* aSession,
    217             const MediaSessionActionDetails& aDetails)
    218        : mozilla::Runnable("MediaSession::DispatchNotifyHandler"),
    219          mSession(aSession),
    220          mDetails(aDetails) {}
    221 
    222    MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
    223      if (RefPtr<MediaSessionActionHandler> handler =
    224              mSession->GetActionHandler(mDetails.mAction)) {
    225        handler->Call(mDetails);
    226      }
    227      return NS_OK;
    228    }
    229 
    230   private:
    231    RefPtr<const MediaSession> mSession;
    232    MediaSessionActionDetails mDetails;
    233  };
    234 
    235  RefPtr<nsIRunnable> runnable = new Runnable(this, aDetails);
    236  NS_DispatchToMainThread(runnable);
    237 }
    238 
    239 bool MediaSession::IsSupportedAction(MediaSessionAction aAction) const {
    240  MOZ_ASSERT(size_t(aAction) < std::size(mActionHandlers));
    241  return mActionHandlers[aAction] != nullptr;
    242 }
    243 
    244 bool MediaSession::IsActive() const {
    245  RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext();
    246  MOZ_ASSERT(currentBC);
    247  RefPtr<WindowContext> wc = currentBC->GetTopWindowContext();
    248  if (!wc) {
    249    return false;
    250  }
    251  Maybe<uint64_t> activeSessionContextId = wc->GetActiveMediaSessionContextId();
    252  if (!activeSessionContextId) {
    253    return false;
    254  }
    255  LOG("session context Id=%" PRIu64 ", active session context Id=%" PRIu64,
    256      currentBC->Id(), *activeSessionContextId);
    257  return *activeSessionContextId == currentBC->Id();
    258 }
    259 
    260 void MediaSession::NotifyMediaSessionDocStatus(SessionDocStatus aState) {
    261  RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext();
    262  MOZ_ASSERT(currentBC, "Update session status after context destroyed!");
    263 
    264  RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC);
    265  if (!updater) {
    266    return;
    267  }
    268  if (aState == SessionDocStatus::eActive) {
    269    updater->NotifySessionCreated(currentBC->Id());
    270    // If media session set its attributes before its document becomes active,
    271    // then we would notify those attributes which hasn't been notified as well
    272    // because attributes update would only happen if its document is already
    273    // active.
    274    NotifyMediaSessionAttributes();
    275  } else {
    276    updater->NotifySessionDestroyed(currentBC->Id());
    277  }
    278 }
    279 
    280 void MediaSession::NotifyMediaSessionAttributes() {
    281  MOZ_ASSERT(mSessionDocState == SessionDocStatus::eActive);
    282  if (mDeclaredPlaybackState != MediaSessionPlaybackState::None) {
    283    NotifyPlaybackStateUpdated();
    284  }
    285  if (mMediaMetadata) {
    286    NotifyMetadataUpdated();
    287  }
    288  for (size_t idx = 0; idx < std::size(mActionHandlers); idx++) {
    289    MediaSessionAction action = static_cast<MediaSessionAction>(idx);
    290    if (mActionHandlers[action]) {
    291      NotifyEnableSupportedAction(action);
    292    }
    293  }
    294  if (mPositionState) {
    295    NotifyPositionStateChanged();
    296  }
    297 }
    298 
    299 void MediaSession::NotifyPlaybackStateUpdated() {
    300  if (mSessionDocState != SessionDocStatus::eActive) {
    301    return;
    302  }
    303  RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext();
    304  MOZ_ASSERT(currentBC,
    305             "Update session playback state after context destroyed!");
    306  if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC)) {
    307    updater->SetDeclaredPlaybackState(currentBC->Id(), mDeclaredPlaybackState);
    308  }
    309 }
    310 
    311 void MediaSession::NotifyMetadataUpdated() {
    312  if (mSessionDocState != SessionDocStatus::eActive) {
    313    return;
    314  }
    315  RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext();
    316  MOZ_ASSERT(currentBC, "Update session metadata after context destroyed!");
    317 
    318  Maybe<MediaMetadataBase> metadata;
    319  if (GetMetadata()) {
    320    metadata.emplace(*(GetMetadata()->AsMetadataBase()));
    321  }
    322  if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC)) {
    323    updater->UpdateMetadata(currentBC->Id(), metadata);
    324  }
    325 }
    326 
    327 void MediaSession::NotifyEnableSupportedAction(MediaSessionAction aAction) {
    328  if (mSessionDocState != SessionDocStatus::eActive) {
    329    return;
    330  }
    331  RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext();
    332  MOZ_ASSERT(currentBC, "Update action after context destroyed!");
    333  if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC)) {
    334    updater->EnableAction(currentBC->Id(), aAction);
    335  }
    336 }
    337 
    338 void MediaSession::NotifyDisableSupportedAction(MediaSessionAction aAction) {
    339  if (mSessionDocState != SessionDocStatus::eActive) {
    340    return;
    341  }
    342  RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext();
    343  MOZ_ASSERT(currentBC, "Update action after context destroyed!");
    344  if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC)) {
    345    updater->DisableAction(currentBC->Id(), aAction);
    346  }
    347 }
    348 
    349 void MediaSession::NotifyPositionStateChanged() {
    350  if (mSessionDocState != SessionDocStatus::eActive) {
    351    return;
    352  }
    353  RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext();
    354  MOZ_ASSERT(currentBC, "Update action after context destroyed!");
    355  if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC)) {
    356    updater->UpdatePositionState(currentBC->Id(), mPositionState);
    357  }
    358 }
    359 
    360 }  // namespace mozilla::dom