tor-browser

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

MediaControlService.cpp (18702B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "MediaControlService.h"
      6 
      7 #include "MediaControlUtils.h"
      8 #include "MediaController.h"
      9 #include "mozilla/AppShutdown.h"
     10 #include "mozilla/Assertions.h"
     11 #include "mozilla/Logging.h"
     12 #include "mozilla/Services.h"
     13 #include "mozilla/StaticPrefs_media.h"
     14 #include "mozilla/StaticPtr.h"
     15 #include "mozilla/intl/Localization.h"
     16 #include "nsIObserverService.h"
     17 #include "nsXULAppAPI.h"
     18 
     19 using mozilla::intl::Localization;
     20 
     21 #undef LOG
     22 #define LOG(msg, ...)                        \
     23  MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
     24          ("MediaControlService=%p, " msg, this, ##__VA_ARGS__))
     25 
     26 #undef LOG_MAINCONTROLLER
     27 #define LOG_MAINCONTROLLER(msg, ...) \
     28  MOZ_LOG(gMediaControlLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
     29 
     30 #undef LOG_MAINCONTROLLER_INFO
     31 #define LOG_MAINCONTROLLER_INFO(msg, ...) \
     32  MOZ_LOG(gMediaControlLog, LogLevel::Info, (msg, ##__VA_ARGS__))
     33 
     34 namespace mozilla::dom {
     35 
     36 StaticRefPtr<MediaControlService> gMediaControlService;
     37 
     38 /* static */
     39 RefPtr<MediaControlService> MediaControlService::GetService() {
     40  MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(),
     41                        "MediaControlService only runs on Chrome process!");
     42  if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdown)) {
     43    return nullptr;
     44  }
     45  if (!gMediaControlService) {
     46    gMediaControlService = new MediaControlService();
     47    gMediaControlService->Init();
     48  }
     49  RefPtr<MediaControlService> service = gMediaControlService.get();
     50  return service;
     51 }
     52 
     53 /* static */
     54 void MediaControlService::GenerateMediaControlKey(const GlobalObject& global,
     55                                                  MediaControlKey aKey,
     56                                                  double aSeekTime) {
     57  RefPtr<MediaControlService> service = MediaControlService::GetService();
     58  if (service) {
     59    service->GenerateTestMediaControlKey(aKey, aSeekTime);
     60  }
     61 }
     62 
     63 /* static */
     64 void MediaControlService::GetCurrentActiveMediaMetadata(
     65    const GlobalObject& aGlobal, MediaMetadataInit& aMetadata) {
     66  if (RefPtr<MediaControlService> service = MediaControlService::GetService()) {
     67    MediaMetadataBase metadata = service->GetMainControllerMediaMetadata();
     68    aMetadata.mTitle = metadata.mTitle;
     69    aMetadata.mArtist = metadata.mArtist;
     70    aMetadata.mAlbum = metadata.mAlbum;
     71    for (const auto& artwork : metadata.mArtwork) {
     72      // If OOM happens resulting in not able to append the element, then we
     73      // would get incorrect result and fail on test, so we don't need to throw
     74      // an error explicitly.
     75      if (MediaImage* image = aMetadata.mArtwork.AppendElement(fallible)) {
     76        image->mSrc = artwork.mSrc;
     77        image->mSizes = artwork.mSizes;
     78        image->mType = artwork.mType;
     79      }
     80    }
     81  }
     82 }
     83 
     84 /* static */
     85 MediaSessionPlaybackState
     86 MediaControlService::GetCurrentMediaSessionPlaybackState(
     87    GlobalObject& aGlobal) {
     88  if (RefPtr<MediaControlService> service = MediaControlService::GetService()) {
     89    return service->GetMainControllerPlaybackState();
     90  }
     91  return MediaSessionPlaybackState::None;
     92 }
     93 
     94 NS_INTERFACE_MAP_BEGIN(MediaControlService)
     95  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
     96  NS_INTERFACE_MAP_ENTRY(nsIObserver)
     97 NS_INTERFACE_MAP_END
     98 
     99 NS_IMPL_ADDREF(MediaControlService)
    100 NS_IMPL_RELEASE(MediaControlService)
    101 
    102 MediaControlService::MediaControlService() {
    103  LOG("create media control service");
    104  RefPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    105  if (obs) {
    106    obs->AddObserver(this, "xpcom-shutdown", false);
    107  }
    108 }
    109 
    110 void MediaControlService::Init() {
    111  mMediaKeysHandler = new MediaControlKeyHandler();
    112  mMediaControlKeyManager = new MediaControlKeyManager();
    113  mMediaControlKeyManager->AddListener(mMediaKeysHandler.get());
    114  mControllerManager = MakeUnique<ControllerManager>(this);
    115 
    116  // Initialize the fallback title
    117  nsTArray<nsCString> resIds{
    118      "branding/brand.ftl"_ns,
    119      "dom/media.ftl"_ns,
    120  };
    121  RefPtr<Localization> l10n = Localization::Create(resIds, true);
    122  {
    123    nsAutoCString translation;
    124    IgnoredErrorResult rv;
    125    l10n->FormatValueSync("mediastatus-fallback-title"_ns, {}, translation, rv);
    126    if (!rv.Failed()) {
    127      mFallbackTitle = NS_ConvertUTF8toUTF16(translation);
    128    }
    129  }
    130 }
    131 
    132 MediaControlService::~MediaControlService() {
    133  LOG("destroy media control service");
    134  Shutdown();
    135 }
    136 
    137 NS_IMETHODIMP
    138 MediaControlService::Observe(nsISupports* aSubject, const char* aTopic,
    139                             const char16_t* aData) {
    140  if (!strcmp(aTopic, "xpcom-shutdown")) {
    141    LOG("XPCOM shutdown");
    142    MOZ_ASSERT(gMediaControlService);
    143    RefPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    144    if (obs) {
    145      obs->RemoveObserver(this, "xpcom-shutdown");
    146    }
    147    Shutdown();
    148    gMediaControlService = nullptr;
    149  }
    150  return NS_OK;
    151 }
    152 
    153 void MediaControlService::Shutdown() {
    154  mControllerManager->Shutdown();
    155  mMediaControlKeyManager->RemoveListener(mMediaKeysHandler.get());
    156 }
    157 
    158 bool MediaControlService::RegisterActiveMediaController(
    159    MediaController* aController) {
    160  MOZ_DIAGNOSTIC_ASSERT(mControllerManager,
    161                        "Register controller before initializing service");
    162  if (!mControllerManager->AddController(aController)) {
    163    LOG("Fail to register controller %" PRId64, aController->Id());
    164    return false;
    165  }
    166  LOG("Register media controller %" PRId64 ", currentNum=%" PRId64,
    167      aController->Id(), GetActiveControllersNum());
    168  if (StaticPrefs::media_mediacontrol_testingevents_enabled()) {
    169    if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
    170      obs->NotifyObservers(nullptr, "media-controller-amount-changed", nullptr);
    171    }
    172  }
    173  return true;
    174 }
    175 
    176 bool MediaControlService::UnregisterActiveMediaController(
    177    MediaController* aController) {
    178  MOZ_DIAGNOSTIC_ASSERT(mControllerManager,
    179                        "Unregister controller before initializing service");
    180  if (!mControllerManager->RemoveController(aController)) {
    181    LOG("Fail to unregister controller %" PRId64, aController->Id());
    182    return false;
    183  }
    184  LOG("Unregister media controller %" PRId64 ", currentNum=%" PRId64,
    185      aController->Id(), GetActiveControllersNum());
    186  if (StaticPrefs::media_mediacontrol_testingevents_enabled()) {
    187    if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
    188      obs->NotifyObservers(nullptr, "media-controller-amount-changed", nullptr);
    189    }
    190  }
    191  return true;
    192 }
    193 
    194 void MediaControlService::NotifyControllerPlaybackStateChanged(
    195    MediaController* aController) {
    196  MOZ_DIAGNOSTIC_ASSERT(
    197      mControllerManager,
    198      "controller state change happens before initializing service");
    199  MOZ_DIAGNOSTIC_ASSERT(aController);
    200  // The controller is not an active controller.
    201  if (!mControllerManager->Contains(aController)) {
    202    return;
    203  }
    204 
    205  // The controller is the main controller, propagate its playback state.
    206  if (GetMainController() == aController) {
    207    mControllerManager->MainControllerPlaybackStateChanged(
    208        aController->PlaybackState());
    209    return;
    210  }
    211 
    212  // The controller is not the main controller, but will become a new main
    213  // controller. As the service can contains multiple controllers and only one
    214  // controller can be controlled by media control keys. Therefore, when
    215  // controller's state becomes `playing`, then we would like to let that
    216  // controller being controlled, rather than other controller which might not
    217  // be playing at the time.
    218  if (GetMainController() != aController &&
    219      aController->PlaybackState() == MediaSessionPlaybackState::Playing) {
    220    mControllerManager->UpdateMainControllerIfNeeded(aController);
    221  }
    222 }
    223 
    224 void MediaControlService::RequestUpdateMainController(
    225    MediaController* aController) {
    226  MOZ_DIAGNOSTIC_ASSERT(aController);
    227  MOZ_DIAGNOSTIC_ASSERT(
    228      mControllerManager,
    229      "using controller in PIP mode before initializing service");
    230  // The controller is not an active controller.
    231  if (!mControllerManager->Contains(aController)) {
    232    return;
    233  }
    234  mControllerManager->UpdateMainControllerIfNeeded(aController);
    235 }
    236 
    237 uint64_t MediaControlService::GetActiveControllersNum() const {
    238  MOZ_DIAGNOSTIC_ASSERT(mControllerManager);
    239  return mControllerManager->GetControllersNum();
    240 }
    241 
    242 MediaController* MediaControlService::GetMainController() const {
    243  MOZ_DIAGNOSTIC_ASSERT(mControllerManager);
    244  return mControllerManager->GetMainController();
    245 }
    246 
    247 void MediaControlService::GenerateTestMediaControlKey(MediaControlKey aKey,
    248                                                      double aSeekValue) {
    249  if (!StaticPrefs::media_mediacontrol_testingevents_enabled()) {
    250    return;
    251  }
    252  // Generate seek details when necessary
    253  switch (aKey) {
    254    case MediaControlKey::Seekto:
    255      mMediaKeysHandler->OnActionPerformed(MediaControlAction(
    256          aKey, SeekDetails(aSeekValue, false /* fast seek */)));
    257      break;
    258    case MediaControlKey::Seekbackward:
    259    case MediaControlKey::Seekforward:
    260      mMediaKeysHandler->OnActionPerformed(
    261          MediaControlAction(aKey, SeekDetails(aSeekValue)));
    262      break;
    263    default:
    264      mMediaKeysHandler->OnActionPerformed(MediaControlAction(aKey));
    265  }
    266 }
    267 
    268 MediaMetadataBase MediaControlService::GetMainControllerMediaMetadata() const {
    269  MediaMetadataBase metadata;
    270  if (!StaticPrefs::media_mediacontrol_testingevents_enabled()) {
    271    return metadata;
    272  }
    273  return GetMainController() ? GetMainController()->GetCurrentMediaMetadata()
    274                             : metadata;
    275 }
    276 
    277 MediaSessionPlaybackState MediaControlService::GetMainControllerPlaybackState()
    278    const {
    279  if (!StaticPrefs::media_mediacontrol_testingevents_enabled()) {
    280    return MediaSessionPlaybackState::None;
    281  }
    282  return GetMainController() ? GetMainController()->PlaybackState()
    283                             : MediaSessionPlaybackState::None;
    284 }
    285 
    286 nsString MediaControlService::GetFallbackTitle() const {
    287  return mFallbackTitle;
    288 }
    289 
    290 // Following functions belong to ControllerManager
    291 MediaControlService::ControllerManager::ControllerManager(
    292    MediaControlService* aService)
    293    : mSource(aService->GetMediaControlKeySource()) {
    294  MOZ_ASSERT(mSource);
    295 }
    296 
    297 bool MediaControlService::ControllerManager::AddController(
    298    MediaController* aController) {
    299  MOZ_DIAGNOSTIC_ASSERT(aController);
    300  if (mControllers.contains(aController)) {
    301    return false;
    302  }
    303  mControllers.insertBack(aController);
    304  UpdateMainControllerIfNeeded(aController);
    305  return true;
    306 }
    307 
    308 bool MediaControlService::ControllerManager::RemoveController(
    309    MediaController* aController) {
    310  MOZ_DIAGNOSTIC_ASSERT(aController);
    311  if (!mControllers.contains(aController)) {
    312    return false;
    313  }
    314  // This is LinkedListElement's method which will remove controller from
    315  // `mController`.
    316  static_cast<LinkedListControllerPtr>(aController)->remove();
    317  // If main controller is removed from the list, the last controller in the
    318  // list would become the main controller. Or reset the main controller when
    319  // the list is already empty.
    320  if (GetMainController() == aController) {
    321    UpdateMainControllerInternal(
    322        mControllers.isEmpty() ? nullptr : mControllers.getLast());
    323  }
    324  return true;
    325 }
    326 
    327 void MediaControlService::ControllerManager::UpdateMainControllerIfNeeded(
    328    MediaController* aController) {
    329  MOZ_DIAGNOSTIC_ASSERT(aController);
    330 
    331  if (GetMainController() == aController) {
    332    LOG_MAINCONTROLLER("This controller is alreay the main controller");
    333    return;
    334  }
    335 
    336  if (GetMainController() &&
    337      GetMainController()->IsBeingUsedInPIPModeOrFullscreen() &&
    338      !aController->IsBeingUsedInPIPModeOrFullscreen()) {
    339    LOG_MAINCONTROLLER(
    340        "Normal media controller can't replace the controller being used in "
    341        "PIP mode or fullscreen");
    342    return ReorderGivenController(aController,
    343                                  InsertOptions::eInsertAsNormalController);
    344  }
    345  ReorderGivenController(aController, InsertOptions::eInsertAsMainController);
    346  UpdateMainControllerInternal(aController);
    347 }
    348 
    349 void MediaControlService::ControllerManager::ReorderGivenController(
    350    MediaController* aController, InsertOptions aOption) {
    351  MOZ_DIAGNOSTIC_ASSERT(aController);
    352  MOZ_DIAGNOSTIC_ASSERT(mControllers.contains(aController));
    353  // Reset the controller's position and make it not in any list.
    354  static_cast<LinkedListControllerPtr>(aController)->remove();
    355 
    356  if (aOption == InsertOptions::eInsertAsMainController) {
    357    // Make the main controller as the last element in the list to maintain the
    358    // order of controllers because we always use the last controller in the
    359    // list as the next main controller when removing current main controller
    360    // from the list. Eg. If the list contains [A, B, C], and now the last
    361    // element C is the main controller. When B becomes main controller later,
    362    // the list would become [A, C, B]. And if A becomes main controller, list
    363    // would become [C, B, A]. Then, if we remove A from the list, the next main
    364    // controller would be B. But if we don't maintain the controller order when
    365    // main controller changes, we would pick C as the main controller because
    366    // the list is still [A, B, C].
    367    return mControllers.insertBack(aController);
    368  }
    369 
    370  MOZ_ASSERT(aOption == InsertOptions::eInsertAsNormalController);
    371  MOZ_ASSERT(GetMainController() != aController);
    372  // We might have multiple controllers which have higher priority (being used
    373  // in PIP or fullscreen) from the head, the normal controller should be
    374  // inserted before them. Therefore, search a higher priority controller from
    375  // the head and insert new controller before it.
    376  // Eg. a list [A, B, C, D, E] and D and E have higher priority, if we want
    377  // to insert F, then the final result would be [A, B, C, F, D, E]
    378  auto* current = static_cast<LinkedListControllerPtr>(mControllers.getFirst());
    379  while (!static_cast<MediaController*>(current)
    380              ->IsBeingUsedInPIPModeOrFullscreen()) {
    381    current = current->getNext();
    382  }
    383  MOZ_ASSERT(current, "Should have at least one higher priority controller!");
    384  current->setPrevious(aController);
    385 }
    386 
    387 void MediaControlService::ControllerManager::Shutdown() {
    388  mControllers.clear();
    389  DisconnectMainControllerEvents();
    390 }
    391 
    392 void MediaControlService::ControllerManager::MainControllerPlaybackStateChanged(
    393    MediaSessionPlaybackState aState) {
    394  MOZ_ASSERT(NS_IsMainThread());
    395  mSource->SetPlaybackState(aState);
    396 }
    397 
    398 void MediaControlService::ControllerManager::MainControllerMetadataChanged(
    399    const MediaMetadataBase& aMetadata) {
    400  MOZ_ASSERT(NS_IsMainThread());
    401  mSource->SetMediaMetadata(aMetadata);
    402 }
    403 
    404 void MediaControlService::ControllerManager::UpdateMainControllerInternal(
    405    MediaController* aController) {
    406  MOZ_ASSERT(NS_IsMainThread());
    407  if (aController) {
    408    aController->Select();
    409  }
    410  if (mMainController) {
    411    mMainController->Unselect();
    412  }
    413  mMainController = aController;
    414 
    415  if (!mMainController) {
    416    LOG_MAINCONTROLLER_INFO("Clear main controller");
    417    mSource->Close();
    418    DisconnectMainControllerEvents();
    419  } else {
    420    LOG_MAINCONTROLLER_INFO("Set controller %" PRId64 " as main controller",
    421                            mMainController->Id());
    422    if (!mSource->Open()) {
    423      LOG("Failed to open source for monitoring media keys");
    424    }
    425    // We would still update those status to the event source even if it failed
    426    // to open, because it would save the result and set them to the real
    427    // source when it opens. In addition, another benefit to do that is to
    428    // prevent testing from affecting by platform specific issues, because our
    429    // testing events rely on those status changes and they are all platform
    430    // independent.
    431    mSource->SetPlaybackState(mMainController->PlaybackState());
    432    mSource->SetMediaMetadata(mMainController->GetCurrentMediaMetadata());
    433    mSource->SetSupportedMediaKeys(mMainController->GetSupportedMediaKeys());
    434    mSource->SetPositionState(mMainController->GetCurrentPositionState());
    435    ConnectMainControllerEvents();
    436  }
    437 
    438  if (StaticPrefs::media_mediacontrol_testingevents_enabled()) {
    439    if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
    440      obs->NotifyObservers(nullptr, "main-media-controller-changed", nullptr);
    441    }
    442  }
    443 }
    444 
    445 void MediaControlService::ControllerManager::ConnectMainControllerEvents() {
    446  // As main controller has been changed, we should disconnect listeners from
    447  // the previous controller and reconnect them to the new controller.
    448  DisconnectMainControllerEvents();
    449  // Listen to main controller's event in order to propagate the content that
    450  // might be displayed on the virtual control interface created by the source.
    451  mMetadataChangedListener = mMainController->MetadataChangedEvent().Connect(
    452      AbstractThread::MainThread(), this,
    453      &ControllerManager::MainControllerMetadataChanged);
    454  mSupportedKeysChangedListener =
    455      mMainController->SupportedKeysChangedEvent().Connect(
    456          AbstractThread::MainThread(),
    457          [this](const MediaKeysArray& aSupportedKeys) {
    458            mSource->SetSupportedMediaKeys(aSupportedKeys);
    459          });
    460  mFullScreenChangedListener =
    461      mMainController->FullScreenChangedEvent().Connect(
    462          AbstractThread::MainThread(), [this](bool aIsEnabled) {
    463            mSource->SetEnableFullScreen(aIsEnabled);
    464          });
    465  mPictureInPictureModeChangedListener =
    466      mMainController->PictureInPictureModeChangedEvent().Connect(
    467          AbstractThread::MainThread(), [this](bool aIsEnabled) {
    468            mSource->SetEnablePictureInPictureMode(aIsEnabled);
    469          });
    470  mPositionChangedListener = mMainController->PositionChangedEvent().Connect(
    471      AbstractThread::MainThread(), [this](const Maybe<PositionState>& aState) {
    472        mSource->SetPositionState(aState);
    473      });
    474 }
    475 
    476 void MediaControlService::ControllerManager::DisconnectMainControllerEvents() {
    477  mMetadataChangedListener.DisconnectIfExists();
    478  mSupportedKeysChangedListener.DisconnectIfExists();
    479  mFullScreenChangedListener.DisconnectIfExists();
    480  mPictureInPictureModeChangedListener.DisconnectIfExists();
    481  mPositionChangedListener.DisconnectIfExists();
    482 }
    483 
    484 MediaController* MediaControlService::ControllerManager::GetMainController()
    485    const {
    486  return mMainController.get();
    487 }
    488 
    489 uint64_t MediaControlService::ControllerManager::GetControllersNum() const {
    490  return mControllers.length();
    491 }
    492 
    493 bool MediaControlService::ControllerManager::Contains(
    494    MediaController* aController) const {
    495  return mControllers.contains(aController);
    496 }
    497 
    498 }  // namespace mozilla::dom