Bug 1658937 - [1.5] Move media controls to WebIDL. r=geckoview-reviewers,agi,alwu
authorEugen Sawin <esawin@mozilla.com>
Thu, 24 Sep 2020 21:21:02 +0000
changeset 550247 d15245dad149b882414e5657a15cfad1a1270fa4
parent 550246 70c423e6c9fa621dd5acf06029be437f05aaf783
child 550248 f1c6d2333d5a46881e9dd178a91eaafc72d81dc1
push id127197
push useresawin@mozilla.com
push dateThu, 24 Sep 2020 21:40:56 +0000
treeherderautoland@f4662be775db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgeckoview-reviewers, agi, alwu
bugs1658937
milestone83.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 1658937 - [1.5] Move media controls to WebIDL. r=geckoview-reviewers,agi,alwu Differential Revision: https://phabricator.services.mozilla.com/D89819
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/MediaSession.java
mobile/android/modules/geckoview/GeckoViewMediaControl.jsm
widget/android/moz.build
widget/android/nsWindow.cpp
widget/android/nsWindow.h
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -1120,24 +1120,16 @@ public class GeckoSession {
                                            GeckoBundle initData);
 
         @WrapForJNI(dispatchTo = "proxy")
         public native void attachEditable(IGeckoEditableParent parent);
 
         @WrapForJNI(dispatchTo = "proxy")
         public native void attachAccessibility(SessionAccessibility.NativeProvider sessionAccessibility);
 
-        @WrapForJNI(dispatchTo = "proxy")
-        public native void attachMediaSessionController(
-            final MediaSession.Controller controller, final long id);
-
-        @WrapForJNI(dispatchTo = "proxy")
-        public native void detachMediaSessionController(
-            final MediaSession.Controller controller);
-
         @WrapForJNI(calledFrom = "gecko")
         private synchronized void onReady(final @Nullable NativeQueue queue) {
             // onReady is called the first time the Gecko window is ready, with a null queue
             // argument. In this case, we simply set the current queue to ready state.
             //
             // After the initial call, onReady is called again every time Window.transfer()
             // is called, with a non-null queue argument. In this case, we only set the
             // current queue to ready state _if_ the current queue matches the given queue,
@@ -2600,84 +2592,28 @@ public class GeckoSession {
     /**
      * Set the media session delegate.
      * This will replace the current handler.
      * @param delegate An implementation of {@link MediaSession.Delegate}.
      */
     @AnyThread
     public void setMediaSessionDelegate(
             final @Nullable MediaSession.Delegate delegate) {
-        Log.d(LOGTAG, "setMediaSessionDelegate " + mWindow);
         mMediaSessionHandler.setDelegate(delegate, this);
     }
 
     /**
      * Get the media session delegate.
      * @return The current media session delegate.
      */
     @AnyThread
     public @Nullable MediaSession.Delegate getMediaSessionDelegate() {
         return mMediaSessionHandler.getDelegate();
     }
 
-    @UiThread
-    /* package */ void attachMediaSessionController(
-            final MediaSession.Controller controller) {
-        ThreadUtils.assertOnUiThread();
-
-        if (DEBUG) {
-            Log.d(LOGTAG,
-                    "attachMediaSessionController" +
-                    " isOpen=" + isOpen() +
-                    ", isEnabled=" + mMediaSessionHandler.isEnabled());
-        }
-
-        if (!isOpen() || !mMediaSessionHandler.isEnabled()) {
-            return;
-        }
-
-        if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
-            mWindow.attachMediaSessionController(controller, controller.getId());
-        } else {
-            GeckoThread.queueNativeCallUntil(
-                    GeckoThread.State.PROFILE_READY,
-                    mWindow, "attachMediaSessionController",
-                    MediaSession.Controller.class,
-                    controller,
-                    controller.getId());
-        }
-    }
-
-    @UiThread
-    /* package */ void detachMediaSessionController(
-            final MediaSession.Controller controller) {
-        ThreadUtils.assertOnUiThread();
-
-        if (DEBUG) {
-            Log.d(LOGTAG,
-                    "detachMediaSessionController" +
-                    " isOpen=" + isOpen() +
-                    ", isEnabled=" + mMediaSessionHandler.isEnabled());
-        }
-
-        if (!isOpen() || !mMediaSessionHandler.isEnabled()) {
-            return;
-        }
-
-        if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
-            mWindow.detachMediaSessionController(controller);
-        } else {
-            GeckoThread.queueNativeCallUntil(
-                    GeckoThread.State.PROFILE_READY,
-                    mWindow, "detachMediaSessionController",
-                    MediaSession.Controller.class,
-                    controller);
-        }
-    }
-
     /**
      * Get the current selection action delegate for this GeckoSession.
      *
      * @return SelectionActionDelegate instance or null if not set.
      */
     @AnyThread
     public @Nullable SelectionActionDelegate getSelectionActionDelegate() {
         return mSelectionActionDelegate.getDelegate();
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/MediaSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/MediaSession.java
@@ -14,298 +14,178 @@ import androidx.annotation.LongDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import android.util.Log;
 
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ImageResource;
-import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.mozglue.JNIObject;
 
 /**
  * The MediaSession API provides media controls and events for a GeckoSession.
  * This includes support for the DOM Media Session API and regular HTML media
  * content.
  *
  * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaSession">Media Session API</a>
  */
 @UiThread
 public class MediaSession {
     private static final String LOGTAG = "MediaSession";
     private static final boolean DEBUG = false;
 
     private final GeckoSession mSession;
-    private Controller mController;
-
-    private static final String ATTACHED_EVENT =
-        "GeckoView:MediaSession:Attached";
-    private boolean mControllerAttached;
+    private boolean mIsActive;
 
     protected MediaSession(final GeckoSession session) {
         mSession = session;
     }
 
-    /* package */ final class Controller extends JNIObject {
-        private final long mId;
-
-        /* package */ Controller(final long id) {
-            mId = id;
-        }
-
-        public long getId() {
-            return mId;
-        }
-
-        @Override // JNIObject
-        public void disposeNative() {
-            // Dispose in native code.
-            throw new UnsupportedOperationException();
-        }
-
-        @WrapForJNI(calledFrom = "ui")
-        /* package */ void onAttached() {
-            MediaSession.this.onControllerAttached();
-        }
-
-        @WrapForJNI(calledFrom = "ui")
-        /* package */ void onDetached() {
-            MediaSession.this.onControllerDetached();
-        }
-
-        @WrapForJNI(dispatchTo = "gecko")
-        public native void pause();
-
-        @WrapForJNI(dispatchTo = "gecko")
-        public native void stop();
-
-        @WrapForJNI(dispatchTo = "gecko")
-        public native void play();
-
-        @WrapForJNI(dispatchTo = "gecko")
-        public native void skipAd();
-
-        @WrapForJNI(dispatchTo = "gecko")
-        public native void focus();
-
-        @WrapForJNI(dispatchTo = "gecko")
-        public native void seekTo(double time, boolean fast);
-
-        @WrapForJNI(dispatchTo = "gecko")
-        public native void seekForward(double offset);
-
-        @WrapForJNI(dispatchTo = "gecko")
-        public native void seekBackward(double offset);
-
-        @WrapForJNI(dispatchTo = "gecko")
-        public native void nextTrack();
-
-        @WrapForJNI(dispatchTo = "gecko")
-        public native void previousTrack();
-
-        @WrapForJNI(dispatchTo = "gecko")
-        public native void muteAudio(boolean mute);
-    }
-
-    /* package */ Controller getController() {
-        return mController;
-    }
-
     /**
      * Get whether the media session is active.
      * Only active media sessions can be controlled.
      * Inactive media session may receive state events since some state events
      * may be dispatched before the media session becomes active.
      *
      * Changes in the active state are notified via {@link Delegate#onActivated}
      * and {@link Delegate#onDeactivated} respectively.
      *
      * @see MediaSession.Delegate#onActivated
      * @see MediaSession.Delegate#onDeactivated
      *
      * @return True if this media session is active, false otherwise.
      */
     public boolean isActive() {
-        return mControllerAttached;
-    }
-
-    /* package */ void attachController(final long id) {
-        mController = new Controller(id);
-        mSession.attachMediaSessionController(mController);
-    }
-
-    void onControllerAttached() {
-        mControllerAttached = true;
-        // TODO: Remove temp workaround once we move to webidl (bug 1658937).
-        mSession.getEventDispatcher().dispatch(ATTACHED_EVENT, null);
+        return mIsActive;
     }
 
-    void onControllerDetached() {
-        if (!mControllerAttached) {
-            return;
-        }
-        mControllerAttached = false;
-        // TODO: Remove temp workaround once we move to webidl (bug 1658937).
-        mSession.getEventDispatcher().dispatch(DEACTIVATED_EVENT, null);
-    }
-
-    /* package */ void detachController() {
-        if (mControllerAttached) {
-            return;
-        }
-        mSession.detachMediaSessionController(mController);
-        mControllerAttached = false;
-        mController = null;
+    /* package */ void setActive(final boolean active) {
+        mIsActive = active;
     }
 
     /**
      * Pause playback for the media session.
      */
     public void pause() {
-        if (!mControllerAttached) {
-            return;
-        }
         if (DEBUG) {
             Log.d(LOGTAG, "pause");
         }
-        mController.pause();
+        mSession.getEventDispatcher().dispatch(PAUSE_EVENT, null);
     }
 
     /**
      * Stop playback for the media session.
      */
     public void stop() {
-        if (!mControllerAttached) {
-            return;
-        }
         if (DEBUG) {
             Log.d(LOGTAG, "stop");
         }
-        mController.stop();
+        mSession.getEventDispatcher().dispatch(STOP_EVENT, null);
     }
 
     /**
      * Start playback for the media session.
      */
     public void play() {
-        if (!mControllerAttached) {
-            return;
-        }
         if (DEBUG) {
             Log.d(LOGTAG, "play");
         }
-        mController.play();
+        mSession.getEventDispatcher().dispatch(PLAY_EVENT, null);
     }
 
     /**
      * Seek to a specific time.
      * Prefer using fast seeking when calling this in a sequence.
      * Don't use fast seeking for the last or only call in a sequence.
      *
      * @param time The time in seconds to move the playback time to.
      * @param fast Whether fast seeking should be used.
      */
     public void seekTo(final double time, final boolean fast) {
-        if (!mControllerAttached) {
-            return;
-        }
         if (DEBUG) {
             Log.d(LOGTAG, "seekTo: time=" + time + ", fast=" + fast);
         }
-        mController.seekTo(time, fast);
+        final GeckoBundle bundle = new GeckoBundle(2);
+        bundle.putDouble("time", time);
+        bundle.putBoolean("fast", fast);
+        mSession.getEventDispatcher().dispatch(SEEK_TO_EVENT, bundle);
     }
 
     /**
      * Seek forward by a sensible number of seconds.
      */
     public void seekForward() {
-        if (!mControllerAttached) {
-            return;
-        }
         if (DEBUG) {
             Log.d(LOGTAG, "seekForward");
         }
-        mController.seekForward(0.0);
+        final GeckoBundle bundle = new GeckoBundle(1);
+        bundle.putDouble("offset", 0.0);
+        mSession.getEventDispatcher().dispatch(SEEK_FORWARD_EVENT, bundle);
     }
 
     /**
      * Seek backward by a sensible number of seconds.
      */
     public void seekBackward() {
-        if (!mControllerAttached) {
-            return;
-        }
         if (DEBUG) {
             Log.d(LOGTAG, "seekBackward");
         }
-        mController.seekBackward(0.0);
+        final GeckoBundle bundle = new GeckoBundle(1);
+        bundle.putDouble("offset", 0.0);
+        mSession.getEventDispatcher().dispatch(SEEK_BACKWARD_EVENT, bundle);
     }
 
     /**
      * Select and play the next track.
      * Move playback to the next item in the playlist when supported.
      */
     public void nextTrack() {
-        if (!mControllerAttached) {
-            return;
-        }
         if (DEBUG) {
             Log.d(LOGTAG, "nextTrack");
         }
-        mController.nextTrack();
+        mSession.getEventDispatcher().dispatch(NEXT_TRACK_EVENT, null);
     }
 
     /**
      * Select and play the previous track.
      * Move playback to the previous item in the playlist when supported.
      */
     public void previousTrack() {
-        if (!mControllerAttached) {
-            return;
-        }
         if (DEBUG) {
             Log.d(LOGTAG, "previousTrack");
         }
-        mController.previousTrack();
+        mSession.getEventDispatcher().dispatch(PREV_TRACK_EVENT, null);
     }
 
     /**
      * Skip the advertisement that is currently playing.
      */
     public void skipAd() {
-        if (!mControllerAttached) {
-            return;
-        }
         if (DEBUG) {
             Log.d(LOGTAG, "skipAd");
         }
-        mController.skipAd();
+        mSession.getEventDispatcher().dispatch(SKIP_AD_EVENT, null);
     }
 
     /**
      * Set whether audio should be muted.
      * Muting audio is supported by default and does not require the media
      * session to be active.
      *
      * @param mute True if audio for this media session should be muted.
      */
     public void muteAudio(final boolean mute) {
-        if (!mControllerAttached) {
-            return;
-        }
         if (DEBUG) {
             Log.d(LOGTAG, "muteAudio=" + mute);
         }
-        mController.muteAudio(mute);
+        final GeckoBundle bundle = new GeckoBundle(1);
+        bundle.putBoolean("mute", mute);
+        mSession.getEventDispatcher().dispatch(MUTE_AUDIO_EVENT, bundle);
     }
 
-    // TODO: Not sure if we want it.
-    // public void focus() {}
-
     /**
      * Implement this delegate to receive media session events.
      */
     @UiThread
     public interface Delegate {
         /**
          * Notify that the given media session has become active.
          *
@@ -406,29 +286,16 @@ public class MediaSession {
          * @param enabled True when this media session in in fullscreen mode.
          * @param meta An instance of {@link ElementMetadata}, if enabled.
          */
         default void onFullscreen(
                 @NonNull GeckoSession session,
                 @NonNull MediaSession mediaSession,
                 boolean enabled,
                 @Nullable ElementMetadata meta) {}
-
-        /**
-         * Notify on changed picture-in-picture mode state.
-         *
-         * @param session The associated GeckoSession.
-         * @param mediaSession The media session for the given GeckoSession.
-         * @param enabled True when this media session in in picture-in-picture
-         *                mode.
-         */
-        default void onPictureInPicture(
-                @NonNull GeckoSession session,
-                @NonNull MediaSession mediaSession,
-                boolean enabled) {}
     }
 
 
     /**
      * The representation of a media element's metadata.
      */
     public static class ElementMetadata {
         /**
@@ -672,18 +539,18 @@ public class MediaSession {
     }
 
     @Retention(RetentionPolicy.SOURCE)
     @LongDef(flag = true,
              value = {
                  Feature.NONE, Feature.PLAY, Feature.PAUSE, Feature.STOP,
                  Feature.SEEK_TO, Feature.SEEK_FORWARD, Feature.SEEK_BACKWARD,
                  Feature.SKIP_AD, Feature.NEXT_TRACK, Feature.PREVIOUS_TRACK,
-                 //Feature.SET_VIDEO_SURFACE,
-                 Feature.FOCUS })
+                 //Feature.SET_VIDEO_SURFACE
+             })
     /* package */ @interface MSFeature {}
 
     /**
      * Flags for supported media session features.
      */
     public static class Feature {
         public static final long NONE = 0;
 
@@ -767,42 +634,59 @@ public class MediaSession {
     private static final String METADATA_EVENT =
         "GeckoView:MediaSession:Metadata";
     private static final String POSITION_STATE_EVENT =
         "GeckoView:MediaSession:PositionState";
     private static final String FEATURES_EVENT =
         "GeckoView:MediaSession:Features";
     private static final String FULLSCREEN_EVENT =
         "GeckoView:MediaSession:Fullscreen";
-    private static final String PICTURE_IN_PICTURE_EVENT =
-        "GeckoView:MediaSession:PictureInPicture";
     private static final String PLAYBACK_NONE_EVENT =
         "GeckoView:MediaSession:Playback:None";
     private static final String PLAYBACK_PAUSED_EVENT =
         "GeckoView:MediaSession:Playback:Paused";
     private static final String PLAYBACK_PLAYING_EVENT =
         "GeckoView:MediaSession:Playback:Playing";
 
+    private static final String PLAY_EVENT =
+        "GeckoView:MediaSession:Play";
+    private static final String PAUSE_EVENT =
+        "GeckoView:MediaSession:Pause";
+    private static final String STOP_EVENT =
+        "GeckoView:MediaSession:Stop";
+    private static final String NEXT_TRACK_EVENT =
+        "GeckoView:MediaSession:NextTrack";
+    private static final String PREV_TRACK_EVENT =
+        "GeckoView:MediaSession:PrevTrack";
+    private static final String SEEK_FORWARD_EVENT =
+        "GeckoView:MediaSession:SeekForward";
+    private static final String SEEK_BACKWARD_EVENT =
+        "GeckoView:MediaSession:SeekBackward";
+    private static final String SKIP_AD_EVENT =
+        "GeckoView:MediaSession:SkipAd";
+    private static final String SEEK_TO_EVENT =
+        "GeckoView:MediaSession:SeekTo";
+    private static final String MUTE_AUDIO_EVENT =
+        "GeckoView:MediaSession:MuteAudio";
+
     /* package */ static class Handler
             extends GeckoSessionHandler<MediaSession.Delegate> {
 
         private final GeckoSession mSession;
         private final MediaSession mMediaSession;
 
         public Handler(final GeckoSession session) {
             super(
                 "GeckoViewMediaControl",
                 session,
                 new String[]{
-                    ATTACHED_EVENT,
                     ACTIVATED_EVENT,
                     DEACTIVATED_EVENT,
                     METADATA_EVENT,
                     FULLSCREEN_EVENT,
-                    PICTURE_IN_PICTURE_EVENT,
                     POSITION_STATE_EVENT,
                     PLAYBACK_NONE_EVENT,
                     PLAYBACK_PAUSED_EVENT,
                     PLAYBACK_PLAYING_EVENT,
                     FEATURES_EVENT,
                 });
             mSession = session;
             mMediaSession = new MediaSession(session);
@@ -813,27 +697,25 @@ public class MediaSession {
                 final Delegate delegate,
                 final String event,
                 final GeckoBundle message,
                 final EventCallback callback) {
             if (DEBUG) {
                 Log.d(LOGTAG, "handleMessage " + event);
             }
 
-            if (ATTACHED_EVENT.equals(event)) {
+            if (ACTIVATED_EVENT.equals(event)) {
+                mMediaSession.setActive(true);
                 delegate.onActivated(mSession, mMediaSession);
-            } else if (ACTIVATED_EVENT.equals(event)) {
-                mMediaSession.attachController(message.getLong("id"));
-                // TODO: We can call this direclty, once we move to webidl.
-                // delegate.onActivated(mSession, mMediaSession);
             } else if (DEACTIVATED_EVENT.equals(event)) {
-                mMediaSession.detachController();
+                mMediaSession.setActive(false);
                 delegate.onDeactivated(mSession, mMediaSession);
             } else if (METADATA_EVENT.equals(event)) {
-                final Metadata meta = Metadata.fromBundle(message);
+                final Metadata meta =
+                        Metadata.fromBundle(message.getBundle("metadata"));
                 delegate.onMetadata(mSession, mMediaSession, meta);
             } else if (POSITION_STATE_EVENT.equals(event)) {
                 final PositionState state =
                         PositionState.fromBundle(message.getBundle("state"));
                 delegate.onPositionState(mSession, mMediaSession, state);
             } else if (PLAYBACK_NONE_EVENT.equals(event)) {
                 delegate.onStop(mSession, mMediaSession);
             } else if (PLAYBACK_PAUSED_EVENT.equals(event)) {
@@ -845,15 +727,12 @@ public class MediaSession {
                         message.getBundle("features"));
                 delegate.onFeatures(mSession, mMediaSession, features);
             } else if (FULLSCREEN_EVENT.equals(event)) {
                 final boolean enabled = message.getBoolean("enabled");
                 final ElementMetadata meta =
                         ElementMetadata.fromBundle(
                                 message.getBundle("metadata"));
                 delegate.onFullscreen(mSession, mMediaSession, enabled, meta);
-            } else if (PICTURE_IN_PICTURE_EVENT.equals(event)) {
-                final boolean enabled = message.getBoolean("enabled");
-                delegate.onPictureInPicture(mSession, mMediaSession, enabled);
             }
         }
     }
 }
--- a/mobile/android/modules/geckoview/GeckoViewMediaControl.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewMediaControl.jsm
@@ -26,42 +26,101 @@ class GeckoViewMediaControl extends Geck
       mozSystemGroup: true,
       capture: false,
     };
 
     this.controller.addEventListener("activated", this, options);
     this.controller.addEventListener("deactivated", this, options);
     this.controller.addEventListener("supportedkeyschange", this, options);
     this.controller.addEventListener("positionstatechange", this, options);
-    // TODO: Move other events to webidl once supported.
+    this.controller.addEventListener("metadatachange", this, options);
+    this.controller.addEventListener("playbackstatechange", this, options);
 
     this.messageManager.addMessageListener(
       "GeckoView:MediaControl:Fullscreen",
       this
     );
+
+    this.registerListener([
+      "GeckoView:MediaSession:Play",
+      "GeckoView:MediaSession:Pause",
+      "GeckoView:MediaSession:Stop",
+      "GeckoView:MediaSession:NextTrack",
+      "GeckoView:MediaSession:PrevTrack",
+      "GeckoView:MediaSession:SeekForward",
+      "GeckoView:MediaSession:SeekBackward",
+      "GeckoView:MediaSession:SkipAd",
+      "GeckoView:MediaSession:SeekTo",
+      "GeckoView:MediaSession:MuteAudio",
+    ]);
   }
 
   onDisable() {
     debug`onDisable`;
 
     this.controller.removeEventListener("activated", this);
     this.controller.removeEventListener("deactivated", this);
     this.controller.removeEventListener("supportedkeyschange", this);
     this.controller.removeEventListener("positionstatechange", this);
+    this.controller.removeEventListener("metadatachange", this);
+    this.controller.removeEventListener("playbackstatechange", this);
 
     this.messageManager.removeMessageListener(
       "GeckoView:MediaControl:Fullscreen",
       this
     );
+
+    this.unregisterListener();
   }
 
   get controller() {
     return this.browser.browsingContext.mediaController;
   }
 
+  onEvent(aEvent, aData, aCallback) {
+    debug`onEvent: event=${aEvent}, data=${aData}`;
+
+    switch (aEvent) {
+      case "GeckoView:MediaSession:Play":
+        this.controller.play();
+        break;
+      case "GeckoView:MediaSession:Pause":
+        this.controller.pause();
+        break;
+      case "GeckoView:MediaSession:Stop":
+        this.controller.stop();
+        break;
+      case "GeckoView:MediaSession:NextTrack":
+        this.controller.nextTrack();
+        break;
+      case "GeckoView:MediaSession:PrevTrack":
+        this.controller.prevTrack();
+        break;
+      case "GeckoView:MediaSession:SeekForward":
+        this.controller.seekForward();
+        break;
+      case "GeckoView:MediaSession:SeekBackward":
+        this.controller.seekBackward();
+        break;
+      case "GeckoView:MediaSession:SkipAd":
+        this.controller.skipAd();
+        break;
+      case "GeckoView:MediaSession:SeekTo":
+        this.controller.seekTo(aData.time, aData.fast);
+        break;
+      case "GeckoView:MediaSession:MuteAudio":
+        if (aData.mute) {
+          this.browser.mute();
+        } else {
+          this.browser.unmute();
+        }
+        break;
+    }
+  }
+
   receiveMessage(aMsg) {
     debug`receiveMessage: name=${aMsg.name}, data=${aMsg.data}`;
 
     switch (aMsg.name) {
       case "GeckoView:MediaControl:Fullscreen":
         this.handleFullscreenChanged(aMsg.data);
         break;
       default:
@@ -82,57 +141,59 @@ class GeckoViewMediaControl extends Geck
         this.handleDeactivated();
         break;
       case "supportedkeyschange":
         this.handleSupportedKeysChanged();
         break;
       case "positionstatechange":
         this.handlePositionStateChanged(aEvent);
         break;
+      case "metadatachange":
+        this.handleMetadataChanged();
+        break;
+      case "playbackstatechange":
+        this.handlePlaybackStateChanged();
+        break;
       default:
         warn`Unknown event type ${aEvent.type}`;
         break;
     }
   }
 
   handleFullscreenChanged(aData) {
     debug`handleFullscreenChanged ${aData.enabled}`;
 
     this.eventDispatcher.sendRequest({
       type: "GeckoView:MediaSession:Fullscreen",
-      id: this.controller.id,
       enabled: aData.enabled,
       metadata: aData.metadata,
     });
   }
 
   handleActivated() {
     debug`handleActivated`;
 
     this.eventDispatcher.sendRequest({
       type: "GeckoView:MediaSession:Activated",
-      id: this.controller.id,
     });
   }
 
   handleDeactivated() {
     debug`handleDeactivated`;
 
     this.eventDispatcher.sendRequest({
       type: "GeckoView:MediaSession:Deactivated",
-      id: this.controller.id,
     });
   }
 
   handlePositionStateChanged(aEvent) {
     debug`handlePositionStateChanged`;
 
     this.eventDispatcher.sendRequest({
       type: "GeckoView:MediaSession:PositionState",
-      id: this.controller.id,
       state: {
         duration: aEvent.duration,
         playbackRate: aEvent.playbackRate,
         position: aEvent.position,
       },
     });
   }
 
@@ -145,17 +206,60 @@ class GeckoViewMediaControl extends Geck
     // implementation for now.
     const features = new Map();
     supported.forEach(key => {
       features[key] = true;
     });
 
     this.eventDispatcher.sendRequest({
       type: "GeckoView:MediaSession:Features",
-      id: this.controller.id,
       features,
     });
   }
+
+  handleMetadataChanged() {
+    let metadata = null;
+    try {
+      metadata = this.controller.getMetadata();
+    } catch (e) {
+      warn`Metadata not available`;
+    }
+    debug`handleMetadataChanged ${metadata}`;
+
+    if (metadata) {
+      this.eventDispatcher.sendRequest({
+        type: "GeckoView:MediaSession:Metadata",
+        metadata,
+      });
+    }
+  }
+
+  handlePlaybackStateChanged() {
+    const state = this.controller.playbackState;
+    let type = null;
+
+    debug`handlePlaybackStateChanged ${state}`;
+
+    switch (state) {
+      case "none":
+        type = "GeckoView:MediaSession:Playback:None";
+        break;
+      case "paused":
+        type = "GeckoView:MediaSession:Playback:Paused";
+        break;
+      case "playing":
+        type = "GeckoView:MediaSession:Playback:Playing";
+        break;
+    }
+
+    if (!type) {
+      return;
+    }
+
+    this.eventDispatcher.sendRequest({
+      type,
+    });
+  }
 }
 
 const { debug, warn } = GeckoViewMediaControl.initLogging(
   "GeckoViewMediaControl"
 );
--- a/widget/android/moz.build
+++ b/widget/android/moz.build
@@ -57,17 +57,16 @@ classes_with_WrapForJNI = [
     'GeckoSystemStateListener',
     'GeckoThread',
     'GeckoVRManager',
     'GeckoVideoInfo',
     'GeckoWebExecutor',
     'HardwareCodecCapabilityUtils',
     'ImageDecoder',
     'MediaDrmProxy',
-    'MediaSession',
     'PanZoomController',
     'PrefsHelper',
     'RuntimeTelemetry',
     'Sample',
     'SampleBuffer',
     'ScreenManagerHelper',
     'ServiceAllocator',
     'SessionAccessibility',
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -24,17 +24,16 @@
 
 #include "mozilla/Preferences.h"
 #include "mozilla/Unused.h"
 #include "mozilla/a11y/SessionAccessibility.h"
 #include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/CanonicalBrowsingContext.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
-#include "mozilla/dom/MediaControlService.h"
 #include "mozilla/dom/MouseEventBinding.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/DataSurfaceHelpers.h"
 #include "mozilla/gfx/Types.h"
 #include "mozilla/layers/RenderTrace.h"
 #include <algorithm>
 
 using mozilla::Unused;
@@ -93,17 +92,16 @@ using mozilla::gfx::SurfaceFormat;
 #include "KeyEvent.h"
 #include "MotionEvent.h"
 #include "mozilla/java/EventDispatcherWrappers.h"
 #include "mozilla/java/GeckoAppShellWrappers.h"
 #include "mozilla/java/GeckoEditableChildWrappers.h"
 #include "mozilla/java/GeckoResultWrappers.h"
 #include "mozilla/java/GeckoSessionNatives.h"
 #include "mozilla/java/GeckoSystemStateListenerWrappers.h"
-#include "mozilla/java/MediaSessionNatives.h"
 #include "mozilla/java/PanZoomControllerNatives.h"
 #include "mozilla/java/SessionAccessibilityWrappers.h"
 #include "ScreenHelperAndroid.h"
 
 #include "GeckoProfiler.h"  // For AUTO_PROFILER_LABEL
 #include "nsPrintfCString.h"
 #include "nsString.h"
 
@@ -170,365 +168,16 @@ bool DispatchToUiThread(const char* aNam
 }
 }  // namespace
 
 namespace mozilla {
 namespace widget {
 
 using WindowPtr = jni::NativeWeakPtr<GeckoViewSupport>;
 
-class MediaSessionSupport final
-    : public mozilla::java::MediaSession::Controller::Natives<
-          MediaSessionSupport> {
-  using MediaKeysArray = nsTArray<MediaControlKey>;
-
-  typedef RefPtr<mozilla::dom::MediaController> ControllerPtr;
-
-  WindowPtr mWindow;
-  mozilla::java::MediaSession::Controller::WeakRef mJavaController;
-  ControllerPtr mMediaController;
-  MediaEventListener mMetadataChangedListener;
-  MediaEventListener mPlaybackChangedListener;
-
- public:
-  typedef java::MediaSession::Controller::Natives<MediaSessionSupport> Base;
-
-  using Base::AttachNative;
-  using Base::DisposeNative;
-
-  MediaSessionSupport(
-      WindowPtr aWindow,
-      const java::MediaSession::Controller::LocalRef& aController)
-      : mWindow(aWindow),
-        mJavaController(aController),
-        mMediaController(nullptr) {
-#if defined(DEBUG)
-    auto win(mWindow.Access());
-    MOZ_ASSERT(!!win);
-#endif  // defined(DEBUG)
-  }
-
-  bool Dispatch(const char16_t aType[],
-                java::GeckoBundle::Param aBundle = nullptr) {
-    auto win = mWindow.Access();
-    if (!win) {
-      return false;
-    }
-
-    nsWindow* gkWindow = win->GetNsWindow();
-    if (!gkWindow) {
-      return false;
-    }
-
-    widget::EventDispatcher* dispatcher = gkWindow->GetEventDispatcher();
-    if (!dispatcher) {
-      return false;
-    }
-
-    dispatcher->Dispatch(aType, aBundle);
-
-    return true;
-  }
-
-  void PipChanged(const bool aEnabled) {
-    const size_t kBundleSize = 1;
-
-    AutoTArray<jni::String::LocalRef, kBundleSize> keys;
-    AutoTArray<jni::Object::LocalRef, kBundleSize> values;
-
-    keys.AppendElement(
-        jni::StringParam(NS_LITERAL_STRING_FROM_CSTRING("enabled")));
-    values.AppendElement(aEnabled ? java::sdk::Boolean::TRUE()
-                                  : java::sdk::Boolean::FALSE());
-
-    MOZ_ASSERT(kBundleSize == keys.Length());
-    MOZ_ASSERT(kBundleSize == values.Length());
-
-    auto bundleKeys = jni::ObjectArray::New<jni::String>(kBundleSize);
-    auto bundleValues = jni::ObjectArray::New<jni::Object>(kBundleSize);
-
-    for (size_t i = 0; i < kBundleSize; ++i) {
-      bundleKeys->SetElement(i, keys[i]);
-      bundleValues->SetElement(i, values[i]);
-    }
-    auto bundle = java::GeckoBundle::New(bundleKeys, bundleValues);
-
-    const char16_t kPictureInPicture[] =
-        u"GeckoView:MediaSession:PictureInPicture";
-    Dispatch(kPictureInPicture, bundle);
-  }
-
-  void MetadataChanged(const dom::MediaMetadataBase& aMetadata) {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    const size_t kBundleSize = 4;
-
-    AutoTArray<jni::String::LocalRef, kBundleSize> keys;
-    AutoTArray<jni::Object::LocalRef, kBundleSize> values;
-
-    keys.AppendElement(
-        jni::StringParam(NS_LITERAL_STRING_FROM_CSTRING("title")));
-    values.AppendElement(jni::StringParam(aMetadata.mTitle));
-
-    keys.AppendElement(
-        jni::StringParam(NS_LITERAL_STRING_FROM_CSTRING("artist")));
-    values.AppendElement(jni::StringParam(aMetadata.mArtist));
-
-    keys.AppendElement(
-        jni::StringParam(NS_LITERAL_STRING_FROM_CSTRING("album")));
-    values.AppendElement(jni::StringParam(aMetadata.mAlbum));
-
-    auto images =
-        jni::ObjectArray::New<java::GeckoBundle>(aMetadata.mArtwork.Length());
-
-    for (size_t i = 0; i < aMetadata.mArtwork.Length(); ++i) {
-      const auto& image = aMetadata.mArtwork[i];
-
-      const size_t kImageBundleSize = 3;
-      auto imageKeys = jni::ObjectArray::New<jni::String>(kImageBundleSize);
-      auto imageValues = jni::ObjectArray::New<jni::String>(kImageBundleSize);
-
-      imageKeys->SetElement(
-          0, jni::StringParam(NS_LITERAL_STRING_FROM_CSTRING("src")));
-      imageValues->SetElement(0, jni::StringParam(image.mSrc));
-
-      imageKeys->SetElement(
-          1, jni::StringParam(NS_LITERAL_STRING_FROM_CSTRING("type")));
-      imageValues->SetElement(1, jni::StringParam(image.mType));
-
-      imageKeys->SetElement(
-          2, jni::StringParam(NS_LITERAL_STRING_FROM_CSTRING("sizes")));
-      imageValues->SetElement(2, jni::StringParam(image.mSizes));
-
-      images->SetElement(i, java::GeckoBundle::New(imageKeys, imageValues));
-    }
-
-    keys.AppendElement(
-        jni::StringParam(NS_LITERAL_STRING_FROM_CSTRING("artwork")));
-    values.AppendElement(images);
-
-    MOZ_ASSERT(kBundleSize == keys.Length());
-    MOZ_ASSERT(kBundleSize == values.Length());
-
-    auto bundleKeys = jni::ObjectArray::New<jni::String>(kBundleSize);
-    auto bundleValues = jni::ObjectArray::New<jni::Object>(kBundleSize);
-
-    for (size_t i = 0; i < kBundleSize; ++i) {
-      bundleKeys->SetElement(i, keys[i]);
-      bundleValues->SetElement(i, values[i]);
-    }
-    auto bundle = java::GeckoBundle::New(bundleKeys, bundleValues);
-
-    const char16_t kMetadata[] = u"GeckoView:MediaSession:Metadata";
-    Dispatch(kMetadata, bundle);
-  }
-
-  void PlaybackChanged(const MediaSessionPlaybackState& aState) {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    const char16_t kPlaybackNone[] = u"GeckoView:MediaSession:Playback:None";
-    const char16_t kPlaybackPaused[] =
-        u"GeckoView:MediaSession:Playback:Paused";
-    const char16_t kPlaybackPlaying[] =
-        u"GeckoView:MediaSession:Playback:Playing";
-
-    switch (aState) {
-      case MediaSessionPlaybackState::None:
-        Dispatch(kPlaybackNone);
-        break;
-      case MediaSessionPlaybackState::Paused:
-        Dispatch(kPlaybackPaused);
-        break;
-      case MediaSessionPlaybackState::Playing:
-        Dispatch(kPlaybackPlaying);
-        break;
-      default:
-        MOZ_ASSERT_UNREACHABLE("Invalid MediaSessionPlaybackState");
-        break;
-    }
-  }
-
-  void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer) {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    RefPtr<Runnable> disposer = aDisposer;
-
-    SetNativeController(nullptr);
-
-    if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
-      auto controller =
-          java::MediaSession::Controller::GlobalRef(mJavaController);
-      if (!controller) {
-        return;
-      }
-
-      uiThread->Dispatch(
-          NS_NewRunnableFunction("MEdiaSessionSupport::OnDetach",
-                                 [controller, disposer = std::move(disposer)] {
-                                   controller->OnDetached();
-                                   disposer->Run();
-                                 }));
-    }
-  }
-
-  const java::MediaSession::Controller::Ref& GetJavaController() const {
-    return mJavaController;
-  }
-
-  void SetNativeController(mozilla::dom::MediaController* aController) {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mMediaController == aController) {
-      return;
-    }
-
-    MOZ_ASSERT(!mMediaController || !aController);
-
-    if (mMediaController) {
-      UnregisterControllerListeners();
-    }
-
-    mMediaController = aController;
-
-    if (mMediaController) {
-      MetadataChanged(mMediaController->GetCurrentMediaMetadata());
-      PlaybackChanged(mMediaController->PlaybackState());
-
-      RegisterControllerListeners();
-    }
-  }
-
-  void RegisterControllerListeners() {
-    mMetadataChangedListener = mMediaController->MetadataChangedEvent().Connect(
-        AbstractThread::MainThread(), this,
-        &MediaSessionSupport::MetadataChanged);
-
-    mPlaybackChangedListener = mMediaController->PlaybackChangedEvent().Connect(
-        AbstractThread::MainThread(), this,
-        &MediaSessionSupport::PlaybackChanged);
-  }
-
-  void UnregisterControllerListeners() {
-    mMetadataChangedListener.DisconnectIfExists();
-    mPlaybackChangedListener.DisconnectIfExists();
-  }
-
-  bool IsActive() const {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    return mMediaController && mMediaController->IsActive();
-  }
-
-  void Pause() {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (!IsActive()) {
-      return;
-    }
-    mMediaController->Pause();
-  }
-
-  void Stop() {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (!IsActive()) {
-      return;
-    }
-    mMediaController->Stop();
-  }
-
-  void Play() {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (!IsActive()) {
-      return;
-    }
-    mMediaController->Play();
-  }
-
-  void Focus() {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (!IsActive()) {
-      return;
-    }
-    mMediaController->Focus();
-  }
-
-  void NextTrack() {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (!IsActive()) {
-      return;
-    }
-    mMediaController->NextTrack();
-  }
-
-  void PreviousTrack() {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (!IsActive()) {
-      return;
-    }
-    mMediaController->PrevTrack();
-  }
-
-  void SeekTo(double aTime, bool aFast) {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (!IsActive()) {
-      return;
-    }
-    mMediaController->SeekTo(aTime, aFast);
-  }
-
-  void SeekForward(double aOffset) {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (!IsActive()) {
-      return;
-    }
-    mMediaController->SeekForward();
-  }
-
-  void SeekBackward(double aOffset) {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (!IsActive()) {
-      return;
-    }
-    mMediaController->SeekBackward();
-  }
-
-  void SkipAd() {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (!IsActive()) {
-      return;
-    }
-    mMediaController->SkipAd();
-  }
-
-  void MuteAudio(bool aMute) {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (!IsActive()) {
-      return;
-    }
-
-    RefPtr<dom::BrowsingContext> bc =
-        dom::BrowsingContext::Get(mMediaController->Id());
-    if (!bc) {
-      return;
-    }
-
-    Unused << bc->SetMuted(aMute);
-  }
-};
-
 /**
  * PanZoomController handles its native calls on the UI thread, so make
  * it separate from GeckoViewSupport.
  */
 class NPZCSupport final
     : public java::PanZoomController::NativeProvider::Natives<NPZCSupport> {
   WindowPtr mWindow;
   java::PanZoomController::NativeProvider::WeakRef mNPZC;
@@ -1768,67 +1417,35 @@ void GeckoViewSupport::PassExternalRespo
 
   auto response = java::WebResponse::GlobalRef(aResponse);
 
   DispatchToUiThread("GeckoViewSupport::PassExternalResponse",
                      [window = java::GeckoSession::Window::GlobalRef(window),
                       response] { window->PassExternalWebResponse(response); });
 }
 
-void GeckoViewSupport::AttachMediaSessionController(
-    const GeckoSession::Window::LocalRef& inst, jni::Object::Param aController,
-    const int64_t aId) {
-  auto controller = java::MediaSession::Controller::LocalRef(
-      jni::GetGeckoThreadEnv(),
-      java::MediaSession::Controller::Ref::From(aController));
-  mWindow->mMediaSessionSupport =
-      jni::NativeWeakPtrHolder<MediaSessionSupport>::Attach(
-          controller, mWindow->mGeckoViewSupport, controller);
-
-  RefPtr<BrowsingContext> bc = BrowsingContext::Get(aId);
-  RefPtr<dom::MediaController> nativeController =
-      bc->Canonical()->GetMediaController();
-  MOZ_ASSERT(nativeController);
-
-  if (auto acc = mWindow->mMediaSessionSupport.Access()) {
-    acc->SetNativeController(nativeController);
-  }
-
-  DispatchToUiThread("GeckoViewSupport::AttachMediaSessionController",
-                     [controller = java::MediaSession::Controller::GlobalRef(
-                          controller)] { controller->OnAttached(); });
-}
-
-void GeckoViewSupport::DetachMediaSessionController(
-    const GeckoSession::Window::LocalRef& inst,
-    jni::Object::Param aController) {
-  mWindow->mMediaSessionSupport.Detach();
-}
-
 }  // namespace widget
 }  // namespace mozilla
 
 void nsWindow::InitNatives() {
   jni::InitConversionStatics();
   mozilla::widget::GeckoViewSupport::Base::Init();
   mozilla::widget::LayerViewSupport::Init();
-  mozilla::widget::MediaSessionSupport::Init();
   mozilla::widget::NPZCSupport::Init();
 
   mozilla::widget::GeckoEditableSupport::Init();
   a11y::SessionAccessibility::Init();
 }
 
 void nsWindow::DetachNatives() {
   MOZ_ASSERT(NS_IsMainThread());
   mEditableSupport.Detach();
   mNPZCSupport.Detach();
   mLayerViewSupport.Detach();
   mSessionAccessibility.Detach();
-  mMediaSessionSupport.Detach();
 }
 
 /* static */
 already_AddRefed<nsWindow> nsWindow::From(nsPIDOMWindowOuter* aDOMWindow) {
   nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aDOMWindow);
   return From(widget);
 }
 
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -33,17 +33,16 @@ class APZCTreeManager;
 class UiCompositorControllerChild;
 }  // namespace layers
 
 namespace widget {
 class AndroidView;
 class GeckoEditableSupport;
 class GeckoViewSupport;
 class LayerViewSupport;
-class MediaSessionSupport;
 class NPZCSupport;
 }  // namespace widget
 
 namespace ipc {
 class Shmem;
 }  // namespace ipc
 
 namespace a11y {
@@ -91,19 +90,16 @@ class nsWindow final : public nsBaseWidg
       mEditableSupport;
   mozilla::jni::Object::GlobalRef mEditableParent;
 
   // Object that implements native SessionAccessibility calls.
   // Strong referenced by the Java instance.
   mozilla::jni::NativeWeakPtr<mozilla::a11y::SessionAccessibility>
       mSessionAccessibility;
 
-  mozilla::jni::NativeWeakPtr<mozilla::widget::MediaSessionSupport>
-      mMediaSessionSupport;
-
   // Object that implements native GeckoView calls and associated states.
   // nullptr for nsWindows that were not opened from GeckoView.
   mozilla::jni::NativeWeakPtr<mozilla::widget::GeckoViewSupport>
       mGeckoViewSupport;
 
   mozilla::Atomic<bool, mozilla::ReleaseAcquire> mContentDocumentDisplayed;
 
  public: