Bug 1620113 - part4 : update main controller when controller changes its playback state to playing. r=chunmin
authoralwu <alwu@mozilla.com>
Wed, 25 Mar 2020 14:42:53 +0000
changeset 520628 5241e4aab772ac8c55999c144731313360d85e5e
parent 520627 1185f5a8daf848069785f9ac35216a2f2ef7174c
child 520629 d17830bcc17773200a4deba3b3aac87fec0e792a
push id37254
push usernerli@mozilla.com
push dateFri, 27 Mar 2020 04:48:07 +0000
treeherdermozilla-central@2d758b42bd73 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerschunmin
bugs1620113
milestone76.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1620113 - part4 : update main controller when controller changes its playback state to playing. r=chunmin The main controller is the only controller that can receive media control keys among all other controllers registered to the service, and it should always be the last controller which is playing. Therefore, we would maintain a list which sorts the controllers based on the order of playing controller, and the last one (last playing controller) would be the main controller. For example, if the controller A starts first, then B starts, B should be the main controller. But if A restarts later then A should become the main controler again. Differential Revision: https://phabricator.services.mozilla.com/D66857
dom/media/mediacontrol/MediaControlService.cpp
dom/media/mediacontrol/MediaControlService.h
dom/media/mediacontrol/MediaController.cpp
dom/media/mediacontrol/MediaController.h
--- a/dom/media/mediacontrol/MediaControlService.cpp
+++ b/dom/media/mediacontrol/MediaControlService.cpp
@@ -119,16 +119,46 @@ bool MediaControlService::UnregisterActi
     return false;
   }
   LOG("Unregister media controller %" PRId64 ", currentNum=%" PRId64,
       aController->Id(), GetActiveControllersNum());
   mMediaControllerAmountChangedEvent.Notify(GetActiveControllersNum());
   return true;
 }
 
+void MediaControlService::NotifyControllerPlaybackStateChanged(
+    MediaController* aController) {
+  MOZ_DIAGNOSTIC_ASSERT(
+      mControllerManager,
+      "controller state change happens before initializing service");
+  MOZ_DIAGNOSTIC_ASSERT(aController);
+  // The controller is not an active controller.
+  if (!mControllerManager->Contains(aController)) {
+    return;
+  }
+
+  // The controller is the main controller, propagate its playback state.
+  if (GetMainController() == aController) {
+    mControllerManager->MainControllerPlaybackStateChanged(
+        aController->GetState());
+    return;
+  }
+
+  // The controller is not the main controller, but will become a new main
+  // controller. As the service can contains multiple controllers and only one
+  // controller can be controlled by media control keys. Therefore, when
+  // controller's state becomes `playing`, then we would like to let that
+  // controller being controlled, rather than other controller which might not
+  // be playing at the time.
+  if (GetMainController() != aController &&
+      aController->GetState() == MediaSessionPlaybackState::Playing) {
+    mControllerManager->UpdateMainController(aController);
+  }
+}
+
 uint64_t MediaControlService::GetActiveControllersNum() const {
   MOZ_DIAGNOSTIC_ASSERT(mControllerManager);
   return mControllerManager->GetControllersNum();
 }
 
 MediaController* MediaControlService::GetMainController() const {
   MOZ_DIAGNOSTIC_ASSERT(mControllerManager);
   return mControllerManager->GetMainController();
@@ -169,34 +199,54 @@ MediaControlService::ControllerManager::
 
 bool MediaControlService::ControllerManager::AddController(
     MediaController* aController) {
   MOZ_DIAGNOSTIC_ASSERT(aController);
   if (mControllers.contains(aController)) {
     return false;
   }
   mControllers.insertBack(aController);
-  UpdateMainController(aController);
+  UpdateMainControllerInternal(aController);
   return true;
 }
 
 bool MediaControlService::ControllerManager::RemoveController(
     MediaController* aController) {
   MOZ_DIAGNOSTIC_ASSERT(aController);
   if (!mControllers.contains(aController)) {
     return false;
   }
   // This is LinkedListElement's method which will remove controller from
   // `mController`.
   aController->remove();
-  UpdateMainController(mControllers.isEmpty() ? nullptr
-                                              : mControllers.getLast());
+  UpdateMainControllerInternal(mControllers.isEmpty() ? nullptr
+                                                      : mControllers.getLast());
   return true;
 }
 
+void MediaControlService::ControllerManager::UpdateMainController(
+    MediaController* aController) {
+  MOZ_DIAGNOSTIC_ASSERT(aController);
+  MOZ_DIAGNOSTIC_ASSERT(mControllers.contains(aController));
+
+  // Make the main controller as the last element in the list to maintain the
+  // order of controllers because we always use the last controller in the list
+  // as the next main controller when removing current main controller from the
+  // list.
+  // Eg. If the list contains [A, B, C], and now the last element C is the main
+  // controller. When B becomes main controller later, the list would become
+  // [A, C, B]. And if A becomes main controller, list would become [C, B, A].
+  // Then, if we remove A from the list, the next main controller would be B.
+  // But if we don't maintain the controller order when main controller changes,
+  // we would pick C as the main controller because the list is still [A, B, C].
+  aController->remove();
+  mControllers.insertBack(aController);
+  UpdateMainControllerInternal(aController);
+}
+
 void MediaControlService::ControllerManager::Shutdown() {
   mControllers.clear();
   DisconnectMainControllerEvents();
 }
 
 void MediaControlService::ControllerManager::MainControllerPlaybackStateChanged(
     MediaSessionPlaybackState aState) {
   MOZ_ASSERT(NS_IsMainThread());
@@ -210,17 +260,17 @@ void MediaControlService::ControllerMana
 }
 
 void MediaControlService::ControllerManager::MainControllerMetadataChanged(
     const MediaMetadataBase& aMetadata) {
   MOZ_ASSERT(NS_IsMainThread());
   mSource->SetMediaMetadata(aMetadata);
 }
 
-void MediaControlService::ControllerManager::UpdateMainController(
+void MediaControlService::ControllerManager::UpdateMainControllerInternal(
     MediaController* aController) {
   MOZ_ASSERT(NS_IsMainThread());
   mMainController = aController;
 
   // As main controller has been changed, we should disconnect the listener from
   // the previous controller and reconnect it to the new controller.
   DisconnectMainControllerEvents();
 
@@ -236,39 +286,38 @@ void MediaControlService::ControllerMana
     if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
       obs->NotifyObservers(nullptr, "main-media-controller-changed", nullptr);
     }
   }
 }
 
 void MediaControlService::ControllerManager::ConnectToMainControllerEvents() {
   MOZ_ASSERT(mMainController);
-  // Listen to main controller's event in order to get its playback state and
-  // metadata update.
-  mPlayStateChangedListener =
-      mMainController->PlaybackStateChangedEvent().Connect(
-          AbstractThread::MainThread(), this,
-          &ControllerManager::MainControllerPlaybackStateChanged);
+  // Listen to main controller's event in order to get its metadata update.
   mMetadataChangedListener = mMainController->MetadataChangedEvent().Connect(
       AbstractThread::MainThread(), this,
       &ControllerManager::MainControllerMetadataChanged);
 
   // Update controller's current status to the event source.
   mSource->SetPlaybackState(mMainController->GetState());
   mSource->SetMediaMetadata(mMainController->GetCurrentMediaMetadata());
 }
 
 void MediaControlService::ControllerManager::DisconnectMainControllerEvents() {
-  mPlayStateChangedListener.DisconnectIfExists();
   mMetadataChangedListener.DisconnectIfExists();
 }
 
 MediaController* MediaControlService::ControllerManager::GetMainController()
     const {
   return mMainController.get();
 }
 
 uint64_t MediaControlService::ControllerManager::GetControllersNum() const {
   return mControllers.length();
 }
 
+bool MediaControlService::ControllerManager::Contains(
+    MediaController* aController) const {
+  return mControllers.contains(aController);
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/media/mediacontrol/MediaControlService.h
+++ b/dom/media/mediacontrol/MediaControlService.h
@@ -43,16 +43,19 @@ class MediaControlService final : public
 
   // Use these functions to register/unresgister controller to/from the active
   // controller list in the service. Return true if the controller is registered
   // or unregistered sucessfully.
   bool RegisterActiveMediaController(MediaController* aController);
   bool UnregisterActiveMediaController(MediaController* aController);
   uint64_t GetActiveControllersNum() const;
 
+  // This method would be called when the controller changes its playback state.
+  void NotifyControllerPlaybackStateChanged(MediaController* aController);
+
   // The main controller is the controller which can receive the media control
   // key events and would show its metadata to virtual controller interface.
   MediaController* GetMainController() const;
 
   // This event is used to generate a media event indicating media controller
   // amount changed.
   MediaEventSource<uint64_t>& MediaControllerAmountChangedEvent() {
     return mMediaControllerAmountChangedEvent;
@@ -89,29 +92,31 @@ class MediaControlService final : public
    */
   class ControllerManager final {
    public:
     explicit ControllerManager(MediaControlService* aService);
     ~ControllerManager() = default;
 
     bool AddController(MediaController* aController);
     bool RemoveController(MediaController* aController);
+    void UpdateMainController(MediaController* aController);
 
     void Shutdown();
 
     MediaController* GetMainController() const;
     MediaController* GetControllerById(uint64_t aId) const;
+    bool Contains(MediaController* aController) const;
     uint64_t GetControllersNum() const;
 
     // These functions are used for monitoring main controller's status change.
     void MainControllerPlaybackStateChanged(MediaSessionPlaybackState aState);
     void MainControllerMetadataChanged(const MediaMetadataBase& aMetadata);
 
    private:
-    void UpdateMainController(MediaController* aController);
+    void UpdateMainControllerInternal(MediaController* aController);
     void ConnectToMainControllerEvents();
     void DisconnectMainControllerEvents();
 
     LinkedList<RefPtr<MediaController>> mControllers;
     RefPtr<MediaController> mMainController;
 
     // These member are use to listen main controller's play state changes and
     // update the playback state to the event source.
--- a/dom/media/mediacontrol/MediaController.cpp
+++ b/dom/media/mediacontrol/MediaController.cpp
@@ -236,17 +236,19 @@ void MediaController::UpdateActualPlayba
           ? MediaSessionPlaybackState::Playing
           : mGuessedPlaybackState;
   if (mActualPlaybackState == newState) {
     return;
   }
   mActualPlaybackState = newState;
   LOG("UpdateActualPlaybackState : '%s'",
       ToMediaSessionPlaybackStateStr(mActualPlaybackState));
-  mPlaybackStateChangedEvent.Notify(mActualPlaybackState);
+  if (RefPtr<MediaControlService> service = MediaControlService::GetService()) {
+    service->NotifyControllerPlaybackStateChanged(this);
+  }
 }
 
 MediaSessionPlaybackState MediaController::GetState() const {
   return mActualPlaybackState;
 }
 
 bool MediaController::IsAudible() const {
   return mGuessedPlaybackState == MediaSessionPlaybackState::Playing &&
--- a/dom/media/mediacontrol/MediaController.h
+++ b/dom/media/mediacontrol/MediaController.h
@@ -63,20 +63,16 @@ class MediaController final
   // Calling this method explicitly would mark this controller as deprecated,
   // then calling any its method won't take any effect.
   void Shutdown();
 
   bool IsAudible() const;
   uint64_t ControlledMediaNum() const;
   MediaSessionPlaybackState GetState() const;
 
-  MediaEventSource<MediaSessionPlaybackState>& PlaybackStateChangedEvent() {
-    return mPlaybackStateChangedEvent;
-  }
-
   void SetDeclaredPlaybackState(uint64_t aSessionContextId,
                                 MediaSessionPlaybackState aState) override;
 
   // These methods are only being used to notify the state changes of controlled
   // media in ContentParent or MediaControlUtils.
   void NotifyMediaStateChanged(ControlledMediaState aState);
   void NotifyMediaAudibleChanged(bool aAudible);
 
@@ -116,16 +112,14 @@ class MediaController final
   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;
-
-  MediaEventProducer<MediaSessionPlaybackState> mPlaybackStateChangedEvent;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif