tor-browser

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

MediaController.cpp (20842B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      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 file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "MediaController.h"
      8 
      9 #include "MediaControlKeySource.h"
     10 #include "MediaControlService.h"
     11 #include "MediaControlUtils.h"
     12 #include "mozilla/AsyncEventDispatcher.h"
     13 #include "mozilla/StaticPrefs_media.h"
     14 #include "mozilla/dom/BrowsingContext.h"
     15 #include "mozilla/dom/CanonicalBrowsingContext.h"
     16 #include "mozilla/dom/MediaSession.h"
     17 #include "mozilla/dom/PositionStateEvent.h"
     18 
     19 // avoid redefined macro in unified build
     20 #undef LOG
     21 #define LOG(msg, ...)                                                    \
     22  MOZ_LOG(gMediaControlLog, LogLevel::Debug,                             \
     23          ("MediaController=%p, Id=%" PRId64 ", " msg, this, this->Id(), \
     24           ##__VA_ARGS__))
     25 
     26 namespace mozilla::dom {
     27 
     28 NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaController, DOMEventTargetHelper)
     29 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(MediaController,
     30                                             DOMEventTargetHelper,
     31                                             nsITimerCallback, nsINamed)
     32 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(MediaController,
     33                                               DOMEventTargetHelper)
     34 NS_IMPL_CYCLE_COLLECTION_TRACE_END
     35 
     36 nsISupports* MediaController::GetParentObject() const {
     37  RefPtr<BrowsingContext> bc = BrowsingContext::Get(Id());
     38  return bc;
     39 }
     40 
     41 JSObject* MediaController::WrapObject(JSContext* aCx,
     42                                      JS::Handle<JSObject*> aGivenProto) {
     43  return MediaController_Binding::Wrap(aCx, this, aGivenProto);
     44 }
     45 
     46 void MediaController::GetSupportedKeys(
     47    nsTArray<MediaControlKey>& aRetVal) const {
     48  aRetVal.Clear();
     49  for (const auto& key : mSupportedKeys) {
     50    aRetVal.AppendElement(key);
     51  }
     52 }
     53 
     54 void MediaController::GetMetadata(MediaMetadataInit& aMetadata,
     55                                  ErrorResult& aRv) {
     56  if (!IsActive() || mShutdown) {
     57    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     58    return;
     59  }
     60 
     61  const MediaMetadataBase metadata = GetCurrentMediaMetadata();
     62  aMetadata.mTitle = metadata.mTitle;
     63  aMetadata.mArtist = metadata.mArtist;
     64  aMetadata.mAlbum = metadata.mAlbum;
     65  for (const auto& artwork : metadata.mArtwork) {
     66    if (MediaImage* image = aMetadata.mArtwork.AppendElement(fallible)) {
     67      image->mSrc = artwork.mSrc;
     68      image->mSizes = artwork.mSizes;
     69      image->mType = artwork.mType;
     70    } else {
     71      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     72      return;
     73    }
     74  }
     75 }
     76 
     77 static const MediaControlKey sDefaultSupportedKeys[] = {
     78    MediaControlKey::Focus,       MediaControlKey::Play,
     79    MediaControlKey::Pause,       MediaControlKey::Playpause,
     80    MediaControlKey::Stop,        MediaControlKey::Seekto,
     81    MediaControlKey::Seekforward, MediaControlKey::Seekbackward};
     82 
     83 static void GetDefaultSupportedKeys(nsTArray<MediaControlKey>& aKeys) {
     84  for (const auto& key : sDefaultSupportedKeys) {
     85    aKeys.AppendElement(key);
     86  }
     87 }
     88 
     89 MediaController::MediaController(uint64_t aBrowsingContextId)
     90    : MediaStatusManager(aBrowsingContextId) {
     91  MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(),
     92                        "MediaController only runs on Chrome process!");
     93  LOG("Create controller %" PRId64, Id());
     94  GetDefaultSupportedKeys(mSupportedKeys);
     95  mSupportedActionsChangedListener = SupportedActionsChangedEvent().Connect(
     96      AbstractThread::MainThread(), this,
     97      &MediaController::HandleSupportedMediaSessionActionsChanged);
     98  mPlaybackChangedListener = PlaybackChangedEvent().Connect(
     99      AbstractThread::MainThread(), this,
    100      &MediaController::HandleActualPlaybackStateChanged);
    101  mPositionStateChangedListener = PositionChangedEvent().Connect(
    102      AbstractThread::MainThread(), this,
    103      &MediaController::HandlePositionStateChanged);
    104  mMetadataChangedListener =
    105      MetadataChangedEvent().Connect(AbstractThread::MainThread(), this,
    106                                     &MediaController::HandleMetadataChanged);
    107 }
    108 
    109 MediaController::~MediaController() {
    110  LOG("Destroy controller %" PRId64, Id());
    111  if (!mShutdown) {
    112    Shutdown();
    113  }
    114 };
    115 
    116 void MediaController::Focus() {
    117  LOG("Focus");
    118  UpdateMediaControlActionToContentMediaIfNeeded(
    119      MediaControlAction(MediaControlKey::Focus));
    120 }
    121 
    122 void MediaController::Play() {
    123  LOG("Play");
    124  UpdateMediaControlActionToContentMediaIfNeeded(
    125      MediaControlAction(MediaControlKey::Play));
    126 }
    127 
    128 void MediaController::Pause() {
    129  LOG("Pause");
    130  UpdateMediaControlActionToContentMediaIfNeeded(
    131      MediaControlAction(MediaControlKey::Pause));
    132 }
    133 
    134 void MediaController::PrevTrack() {
    135  LOG("Prev Track");
    136  UpdateMediaControlActionToContentMediaIfNeeded(
    137      MediaControlAction(MediaControlKey::Previoustrack));
    138 }
    139 
    140 void MediaController::NextTrack() {
    141  LOG("Next Track");
    142  UpdateMediaControlActionToContentMediaIfNeeded(
    143      MediaControlAction(MediaControlKey::Nexttrack));
    144 }
    145 
    146 void MediaController::SeekBackward(double aSeekOffset) {
    147  LOG("Seek Backward");
    148  UpdateMediaControlActionToContentMediaIfNeeded(MediaControlAction(
    149      MediaControlKey::Seekbackward, SeekDetails(aSeekOffset)));
    150 }
    151 
    152 void MediaController::SeekForward(double aSeekOffset) {
    153  LOG("Seek Forward");
    154  UpdateMediaControlActionToContentMediaIfNeeded(MediaControlAction(
    155      MediaControlKey::Seekforward, SeekDetails(aSeekOffset)));
    156 }
    157 
    158 void MediaController::SkipAd() {
    159  LOG("Skip Ad");
    160  UpdateMediaControlActionToContentMediaIfNeeded(
    161      MediaControlAction(MediaControlKey::Skipad));
    162 }
    163 
    164 void MediaController::SeekTo(double aSeekTime, bool aFastSeek) {
    165  LOG("Seek To");
    166  UpdateMediaControlActionToContentMediaIfNeeded(MediaControlAction(
    167      MediaControlKey::Seekto, SeekDetails(aSeekTime, aFastSeek)));
    168 }
    169 
    170 void MediaController::Stop() {
    171  LOG("Stop");
    172  UpdateMediaControlActionToContentMediaIfNeeded(
    173      MediaControlAction(MediaControlKey::Stop));
    174  MediaStatusManager::ClearActiveMediaSessionContextIdIfNeeded();
    175 }
    176 
    177 uint64_t MediaController::Id() const { return mTopLevelBrowsingContextId; }
    178 
    179 bool MediaController::IsAudible() const { return IsMediaAudible(); }
    180 
    181 bool MediaController::IsPlaying() const { return IsMediaPlaying(); }
    182 
    183 bool MediaController::IsActive() const { return mIsActive; };
    184 
    185 bool MediaController::ShouldPropagateActionToAllContexts(
    186    const MediaControlAction& aAction) const {
    187  // These actions have default action handler for each frame, so we
    188  // need to propagate to all contexts. We would handle default handlers in
    189  // `ContentMediaController::HandleMediaKey`.
    190  if (aAction.mKey.isSome()) {
    191    switch (aAction.mKey.value()) {
    192      case MediaControlKey::Play:
    193      case MediaControlKey::Pause:
    194      case MediaControlKey::Stop:
    195      case MediaControlKey::Seekto:
    196      case MediaControlKey::Seekforward:
    197      case MediaControlKey::Seekbackward:
    198        return true;
    199      default:
    200        return false;
    201    }
    202  }
    203  return false;
    204 }
    205 
    206 void MediaController::UpdateMediaControlActionToContentMediaIfNeeded(
    207    const MediaControlAction& aAction) {
    208  // If the controller isn't active or it has been shutdown, we don't need to
    209  // update media action to the content process.
    210  if (!mIsActive || mShutdown) {
    211    return;
    212  }
    213 
    214  // For some actions which have default action handler, we want to propagate
    215  // them on all contexts in order to trigger the default handler on each
    216  // context separately. Otherwise, other action should only be propagated to
    217  // the context where active media session exists.
    218  const bool propateToAll = ShouldPropagateActionToAllContexts(aAction);
    219  const uint64_t targetContextId = propateToAll || !mActiveMediaSessionContextId
    220                                       ? Id()
    221                                       : *mActiveMediaSessionContextId;
    222  RefPtr<BrowsingContext> context = BrowsingContext::Get(targetContextId);
    223  if (!context || context->IsDiscarded()) {
    224    return;
    225  }
    226 
    227  if (propateToAll) {
    228    context->PreOrderWalk([&](BrowsingContext* bc) {
    229      bc->Canonical()->UpdateMediaControlAction(aAction);
    230    });
    231  } else {
    232    context->Canonical()->UpdateMediaControlAction(aAction);
    233  }
    234 }
    235 
    236 void MediaController::Shutdown() {
    237  MOZ_ASSERT(!mShutdown, "Do not call shutdown twice!");
    238  // The media controller would be removed from the service when we receive a
    239  // notification from the content process about all controlled media has been
    240  // stoppped. However, if controlled media is stopped after detaching
    241  // browsing context, then sending the notification from the content process
    242  // would fail so that we are not able to notify the chrome process to remove
    243  // the corresponding controller. Therefore, we should manually remove the
    244  // controller from the service.
    245  Deactivate();
    246  mShutdown = true;
    247  mSupportedActionsChangedListener.DisconnectIfExists();
    248  mPlaybackChangedListener.DisconnectIfExists();
    249  mPositionStateChangedListener.DisconnectIfExists();
    250  mMetadataChangedListener.DisconnectIfExists();
    251 }
    252 
    253 void MediaController::NotifyMediaPlaybackChanged(uint64_t aBrowsingContextId,
    254                                                 MediaPlaybackState aState) {
    255  if (mShutdown) {
    256    return;
    257  }
    258  MediaStatusManager::NotifyMediaPlaybackChanged(aBrowsingContextId, aState);
    259  UpdateDeactivationTimerIfNeeded();
    260  UpdateActivatedStateIfNeeded();
    261 }
    262 
    263 void MediaController::UpdateDeactivationTimerIfNeeded() {
    264  if (!StaticPrefs::media_mediacontrol_stopcontrol_timer()) {
    265    return;
    266  }
    267 
    268  bool shouldBeAlwaysActive = IsPlaying() || IsBeingUsedInPIPModeOrFullscreen();
    269  if (shouldBeAlwaysActive && mDeactivationTimer) {
    270    LOG("Cancel deactivation timer");
    271    mDeactivationTimer->Cancel();
    272    mDeactivationTimer = nullptr;
    273  } else if (!shouldBeAlwaysActive && !mDeactivationTimer) {
    274    nsresult rv = NS_NewTimerWithCallback(
    275        getter_AddRefs(mDeactivationTimer), this,
    276        StaticPrefs::media_mediacontrol_stopcontrol_timer_ms(),
    277        nsITimer::TYPE_ONE_SHOT, AbstractThread::MainThread());
    278    if (NS_SUCCEEDED(rv)) {
    279      LOG("Create a deactivation timer");
    280    } else {
    281      LOG("Failed to create a deactivation timer");
    282    }
    283  }
    284 }
    285 
    286 bool MediaController::IsBeingUsedInPIPModeOrFullscreen() const {
    287  return mIsInPictureInPictureMode || mIsInFullScreenMode;
    288 }
    289 
    290 NS_IMETHODIMP MediaController::Notify(nsITimer* aTimer) {
    291  mDeactivationTimer = nullptr;
    292  if (!StaticPrefs::media_mediacontrol_stopcontrol_timer()) {
    293    return NS_OK;
    294  }
    295 
    296  if (mShutdown) {
    297    LOG("Cancel deactivation timer because controller has been shutdown");
    298    return NS_OK;
    299  }
    300 
    301  // As the media being used in the PIP mode or fullscreen would always display
    302  // on the screen, users would have high chance to interact with it again, so
    303  // we don't want to stop media control.
    304  if (IsBeingUsedInPIPModeOrFullscreen()) {
    305    LOG("Cancel deactivation timer because controller is in PIP mode");
    306    return NS_OK;
    307  }
    308 
    309  if (IsPlaying()) {
    310    LOG("Cancel deactivation timer because controller is still playing");
    311    return NS_OK;
    312  }
    313 
    314  if (!mIsActive) {
    315    LOG("Cancel deactivation timer because controller has been deactivated");
    316    return NS_OK;
    317  }
    318  Deactivate();
    319  return NS_OK;
    320 }
    321 
    322 NS_IMETHODIMP MediaController::GetName(nsACString& aName) {
    323  aName.AssignLiteral("MediaController");
    324  return NS_OK;
    325 }
    326 
    327 void MediaController::NotifyMediaAudibleChanged(uint64_t aBrowsingContextId,
    328                                                MediaAudibleState aState) {
    329  if (mShutdown) {
    330    return;
    331  }
    332 
    333  bool oldAudible = IsAudible();
    334  MediaStatusManager::NotifyMediaAudibleChanged(aBrowsingContextId, aState);
    335  if (IsAudible() == oldAudible) {
    336    return;
    337  }
    338  UpdateActivatedStateIfNeeded();
    339 
    340  // Request the audio focus amongs different controllers that could cause
    341  // pausing other audible controllers if we enable the audio focus management.
    342  RefPtr<MediaControlService> service = MediaControlService::GetService();
    343  MOZ_ASSERT(service);
    344  if (IsAudible()) {
    345    service->GetAudioFocusManager().RequestAudioFocus(this);
    346  } else {
    347    service->GetAudioFocusManager().RevokeAudioFocus(this);
    348  }
    349 }
    350 
    351 bool MediaController::ShouldActivateController() const {
    352  MOZ_ASSERT(!mShutdown);
    353  // After media is successfully loaded and match our critiera, such as its
    354  // duration is longer enough, which is used to exclude the notification-ish
    355  // sound, then it would be able to be controlled once the controll gets
    356  // activated.
    357  //
    358  // Activating a controller means that we would start to intercept the media
    359  // keys on the platform and show the virtual control interface (if needed).
    360  // The controller would be activated when (1) controllable media starts in the
    361  // browsing context that controller belongs to (2) controllable media enters
    362  // fullscreen or PIP mode.
    363  return IsAnyMediaBeingControlled() &&
    364         (IsPlaying() || IsBeingUsedInPIPModeOrFullscreen()) && !mIsActive;
    365 }
    366 
    367 bool MediaController::ShouldDeactivateController() const {
    368  MOZ_ASSERT(!mShutdown);
    369  // If we don't have an active media session and no controlled media exists,
    370  // then we don't need to keep controller active, because there is nothing to
    371  // control. However, if we still have an active media session, then we should
    372  // keep controller active in order to receive media keys even if we don't have
    373  // any controlled media existing, because a website might start other media
    374  // when media session receives media keys.
    375  return !IsAnyMediaBeingControlled() && mIsActive &&
    376         !mActiveMediaSessionContextId;
    377 }
    378 
    379 void MediaController::Activate() {
    380  MOZ_ASSERT(!mShutdown);
    381  RefPtr<MediaControlService> service = MediaControlService::GetService();
    382  if (service && !mIsActive) {
    383    LOG("Activate");
    384    mIsActive = service->RegisterActiveMediaController(this);
    385    MOZ_ASSERT(mIsActive, "Fail to register controller!");
    386    DispatchAsyncEvent(u"activated"_ns);
    387  }
    388 }
    389 
    390 void MediaController::Deactivate() {
    391  MOZ_ASSERT(!mShutdown);
    392  RefPtr<MediaControlService> service = MediaControlService::GetService();
    393  if (service) {
    394    service->GetAudioFocusManager().RevokeAudioFocus(this);
    395    if (mIsActive) {
    396      LOG("Deactivate");
    397      mIsActive = !service->UnregisterActiveMediaController(this);
    398      MOZ_ASSERT(!mIsActive, "Fail to unregister controller!");
    399      DispatchAsyncEvent(u"deactivated"_ns);
    400    }
    401  }
    402 }
    403 
    404 void MediaController::SetIsInPictureInPictureMode(
    405    uint64_t aBrowsingContextId, bool aIsInPictureInPictureMode) {
    406  if (mIsInPictureInPictureMode == aIsInPictureInPictureMode) {
    407    return;
    408  }
    409  LOG("Set IsInPictureInPictureMode to %s",
    410      aIsInPictureInPictureMode ? "true" : "false");
    411  mIsInPictureInPictureMode = aIsInPictureInPictureMode;
    412  ForceToBecomeMainControllerIfNeeded();
    413  UpdateDeactivationTimerIfNeeded();
    414  mPictureInPictureModeChangedEvent.Notify(mIsInPictureInPictureMode);
    415 }
    416 
    417 void MediaController::NotifyMediaFullScreenState(uint64_t aBrowsingContextId,
    418                                                 bool aIsInFullScreen) {
    419  if (mIsInFullScreenMode == aIsInFullScreen) {
    420    return;
    421  }
    422  LOG("%s fullscreen", aIsInFullScreen ? "Entered" : "Left");
    423  mIsInFullScreenMode = aIsInFullScreen;
    424  ForceToBecomeMainControllerIfNeeded();
    425  mFullScreenChangedEvent.Notify(mIsInFullScreenMode);
    426 }
    427 
    428 bool MediaController::IsMainController() const {
    429  RefPtr<MediaControlService> service = MediaControlService::GetService();
    430  return service ? service->GetMainController() == this : false;
    431 }
    432 
    433 bool MediaController::ShouldRequestForMainController() const {
    434  // This controller is already the main controller.
    435  if (IsMainController()) {
    436    return false;
    437  }
    438  // We would only force controller to become main controller if it's in the
    439  // PIP mode or fullscreen, otherwise it should follow the general rule.
    440  // In addition, do nothing if the controller has been shutdowned.
    441  return IsBeingUsedInPIPModeOrFullscreen() && !mShutdown;
    442 }
    443 
    444 void MediaController::ForceToBecomeMainControllerIfNeeded() {
    445  if (!ShouldRequestForMainController()) {
    446    return;
    447  }
    448  RefPtr<MediaControlService> service = MediaControlService::GetService();
    449  MOZ_ASSERT(service, "service was shutdown before shutting down controller?");
    450  // If the controller hasn't been activated and it's ready to be activated,
    451  // then activating it should also make it become a main controller. If it's
    452  // already activated but isn't a main controller yet, then explicitly request
    453  // it.
    454  if (!IsActive() && ShouldActivateController()) {
    455    Activate();
    456  } else if (IsActive()) {
    457    service->RequestUpdateMainController(this);
    458  }
    459 }
    460 
    461 void MediaController::HandleActualPlaybackStateChanged() {
    462  // Media control service would like to know all controllers' playback state
    463  // in order to decide which controller should be the main controller that is
    464  // usually the last tab which plays media.
    465  if (RefPtr<MediaControlService> service = MediaControlService::GetService()) {
    466    service->NotifyControllerPlaybackStateChanged(this);
    467  }
    468  DispatchAsyncEvent(u"playbackstatechange"_ns);
    469 }
    470 
    471 void MediaController::UpdateActivatedStateIfNeeded() {
    472  if (ShouldActivateController()) {
    473    Activate();
    474  } else if (ShouldDeactivateController()) {
    475    Deactivate();
    476  }
    477 }
    478 
    479 void MediaController::HandleSupportedMediaSessionActionsChanged(
    480    const nsTArray<MediaSessionAction>& aSupportedAction) {
    481  // Convert actions to keys, some of them have been included in the supported
    482  // keys, such as "play", "pause" and "stop".
    483  nsTArray<MediaControlKey> newSupportedKeys;
    484  GetDefaultSupportedKeys(newSupportedKeys);
    485  for (const auto& action : aSupportedAction) {
    486    MediaControlKey key = ConvertMediaSessionActionToControlKey(action);
    487    if (!newSupportedKeys.Contains(key)) {
    488      newSupportedKeys.AppendElement(key);
    489    }
    490  }
    491  // As the supported key event should only be notified when supported keys
    492  // change, so abort following steps if they don't change.
    493  if (newSupportedKeys == mSupportedKeys) {
    494    return;
    495  }
    496  LOG("Supported keys changes");
    497  mSupportedKeys = newSupportedKeys;
    498  mSupportedKeysChangedEvent.Notify(mSupportedKeys);
    499  RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
    500      this, u"supportedkeyschange"_ns, CanBubble::eYes);
    501  asyncDispatcher->PostDOMEvent();
    502  MediaController_Binding::ClearCachedSupportedKeysValue(this);
    503 }
    504 
    505 void MediaController::HandlePositionStateChanged(
    506    const Maybe<PositionState>& aState) {
    507  if (!aState) {
    508    return;
    509  }
    510 
    511  PositionStateEventInit init;
    512  init.mDuration = aState->mDuration;
    513  init.mPlaybackRate = aState->mPlaybackRate;
    514  init.mPosition = aState->mLastReportedPlaybackPosition;
    515  RefPtr<PositionStateEvent> event =
    516      PositionStateEvent::Constructor(this, u"positionstatechange"_ns, init);
    517  DispatchAsyncEvent(event.forget());
    518 }
    519 
    520 void MediaController::HandleMetadataChanged(
    521    const MediaMetadataBase& aMetadata) {
    522  // The reason we don't append metadata with `metadatachange` event is that
    523  // allocating artwork might fail if the memory is not enough, but for the
    524  // event we are not able to throw an error. Therefore, we want to the listener
    525  // to use `getMetadata()` to get metadata, because it would throw an error if
    526  // we fail to allocate artwork.
    527  DispatchAsyncEvent(u"metadatachange"_ns);
    528  // If metadata change is because of resetting active media session, then we
    529  // should check if controller needs to be deactivated.
    530  if (ShouldDeactivateController()) {
    531    Deactivate();
    532  }
    533 }
    534 
    535 void MediaController::DispatchAsyncEvent(const nsAString& aName) {
    536  RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
    537  event->InitEvent(aName, false, false);
    538  event->SetTrusted(true);
    539  DispatchAsyncEvent(event.forget());
    540 }
    541 
    542 void MediaController::DispatchAsyncEvent(already_AddRefed<Event> aEvent) {
    543  RefPtr<Event> event = aEvent;
    544  MOZ_ASSERT(event);
    545  nsAutoString eventType;
    546  event->GetType(eventType);
    547  if (!mIsActive && !eventType.EqualsLiteral("deactivated")) {
    548    LOG("Only 'deactivated' can be dispatched on a deactivated controller, not "
    549        "'%s'",
    550        NS_ConvertUTF16toUTF8(eventType).get());
    551    return;
    552  }
    553  LOG("Dispatch event %s", NS_ConvertUTF16toUTF8(eventType).get());
    554  RefPtr<AsyncEventDispatcher> asyncDispatcher =
    555      new AsyncEventDispatcher(this, event.forget());
    556  asyncDispatcher->PostDOMEvent();
    557 }
    558 
    559 CopyableTArray<MediaControlKey> MediaController::GetSupportedMediaKeys() const {
    560  return mSupportedKeys;
    561 }
    562 
    563 void MediaController::Select() const {
    564  if (RefPtr<BrowsingContext> bc = BrowsingContext::Get(Id())) {
    565    bc->Canonical()->AddPageAwakeRequest();
    566  }
    567 }
    568 
    569 void MediaController::Unselect() const {
    570  if (RefPtr<BrowsingContext> bc = BrowsingContext::Get(Id())) {
    571    bc->Canonical()->RemovePageAwakeRequest();
    572  }
    573 }
    574 
    575 }  // namespace mozilla::dom