Backed out 3 changesets (bug 1658937) for bustage on missing MediaSessionNatives.h. CLOSED TREE

Backed out changeset 031b4f94e7f7 (bug 1658937)
Backed out changeset 8d6b4239dacb (bug 1658937)
Backed out changeset 9d9674f18f0c (bug 1658937)
This commit is contained in:
Csoregi Natalia 2020-09-23 06:14:19 +03:00
Родитель 722ad5b0f7
Коммит 9fefb5edac
9 изменённых файлов: 709 добавлений и 208 удалений

Просмотреть файл

@ -1373,6 +1373,7 @@ package org.mozilla.geckoview {
method default public void onFullscreen(@NonNull GeckoSession, @NonNull MediaSession, boolean, @Nullable MediaSession.ElementMetadata);
method default public void onMetadata(@NonNull GeckoSession, @NonNull MediaSession, @NonNull MediaSession.Metadata);
method default public void onPause(@NonNull GeckoSession, @NonNull MediaSession);
method default public void onPictureInPicture(@NonNull GeckoSession, @NonNull MediaSession, boolean);
method default public void onPlay(@NonNull GeckoSession, @NonNull MediaSession);
method default public void onPositionState(@NonNull GeckoSession, @NonNull MediaSession, @NonNull MediaSession.PositionState);
method default public void onStop(@NonNull GeckoSession, @NonNull MediaSession);

Просмотреть файл

@ -3,9 +3,6 @@
<body>
<script>
function updatePositionState(event) {
if (event.target != active) {
return;
}
navigator.mediaSession.setPositionState({
duration: parseFloat(event.target.duration),
position: parseFloat(event.target.currentTime),
@ -13,7 +10,11 @@
});
}
function updateMetadata() {
function updateMetadata(event) {
active.removeEventListener("timeupdate", updatePositionState);
active = event.target;
updatePositionState(event);
active.addEventListener("timeupdate", updatePositionState);
navigator.mediaSession.metadata = active.metadata;
}
@ -24,9 +25,8 @@
return tracks[nextId];
}
navigator.mediaSession.setActionHandler("play", async () => {
await active.play();
updateMetadata();
navigator.mediaSession.setActionHandler("play", () => {
active.play();
});
navigator.mediaSession.setActionHandler("pause", () => {
@ -34,16 +34,18 @@
});
navigator.mediaSession.setActionHandler("previoustrack", () => {
active = getTrack(-1);
active.pause();
getTrack(-1).play();
});
navigator.mediaSession.setActionHandler("nexttrack", () => {
active = getTrack(1);
active.pause();
getTrack(1).play();
});
const audio1 = document.createElement("audio");
audio1.src = "audio/owl.mp3";
audio1.addEventListener("timeupdate", updatePositionState);
audio1.addEventListener("play", updateMetadata);
audio1.metadata = new window.MediaMetadata({
title: "hoot",
artist: "owl",
@ -58,7 +60,7 @@
const audio2 = document.createElement("audio");
audio2.src = "audio/owl.mp3";
audio2.addEventListener("timeupdate", updatePositionState);
audio2.addEventListener("play", updateMetadata);
audio2.metadata = new window.MediaMetadata({
title: "hoot2",
artist: "stillowl",
@ -73,7 +75,7 @@
const audio3 = document.createElement("audio");
audio3.src = "audio/owl.mp3";
audio3.addEventListener("timeupdate", updatePositionState);
audio3.addEventListener("play", updateMetadata);
audio3.metadata = new window.MediaMetadata({
title: "hoot3",
artist: "immaowl",
@ -89,10 +91,8 @@
const tracks = [audio1, audio2, audio3];
let active = audio1;
window.onload = async () => {
active = getTrack(0);
await active.play();
updateMetadata();
window.onload = () => {
getTrack(0).play();
};
</script>
</body>

Просмотреть файл

@ -61,6 +61,13 @@ class MediaSessionTest : BaseSessionTest() {
DOM_TEST_TITLE3,
DOM_TEST_ARTIST3,
DOM_TEST_ALBUM3))
val DEFAULT_META = arrayOf(
Metadata(
DEFAULT_TEST_TITLE1,
// TODO: enforce null for empty strings?
"",
""))
}
@Before
@ -116,8 +123,7 @@ class MediaSessionTest : BaseSessionTest() {
// a. Ensure onMetadata (1) is called.
// b. Ensure onPlay (1) is called.
val completedStep4 = GeckoResult.allOf(
onPlayCalled[1],
onMetadataCalled[1])
onPlayCalled[1])
// 5. Wait for track 1 end.
// a. Ensure onPause (1) is called.
@ -128,7 +134,7 @@ class MediaSessionTest : BaseSessionTest() {
// a. Ensure onMetadata (2) is called.
// b. Ensure onPlay (2) is called.
val completedStep6 = GeckoResult.allOf(
onMetadataCalled[2],
onMetadataCalled[1],
onPlayCalled[2])
// 7. Play next track (3).
@ -137,18 +143,16 @@ class MediaSessionTest : BaseSessionTest() {
// c. Ensure onPlay (3) is called.
val completedStep7 = GeckoResult.allOf(
onPauseCalled[2],
onMetadataCalled[3],
onMetadataCalled[2],
onPlayCalled[3])
// 8. Play previous track (2).
// a. Ensure onPause (3) is called.
// b. Ensure onMetadata (2) is called.
// c. Ensure onPlay (2) is called.
val completedStep8a = GeckoResult.allOf(
onPauseCalled[3])
// Without the split, this seems to race and we don't get the pause event.
val completedStep8b = GeckoResult.allOf(
onMetadataCalled[4],
val completedStep8 = GeckoResult.allOf(
onPauseCalled[3],
onMetadataCalled[3],
onPlayCalled[4])
// 9. Wait for track 2 end.
@ -172,12 +176,6 @@ class MediaSessionTest : BaseSessionTest() {
mediaSession1 = mediaSession
}
@AssertCalled(false)
override fun onDeactivated(
session: GeckoSession,
mediaSession: MediaSession) {
}
@AssertCalled
override fun onFeatures(
session: GeckoSession,
@ -284,29 +282,27 @@ class MediaSessionTest : BaseSessionTest() {
sessionRule.waitForResult(completedStep4)
sessionRule.waitForResult(completedStep5)
mediaSession1!!.pause()
mediaSession1!!.nextTrack()
mediaSession1!!.play()
sessionRule.waitForResult(completedStep6)
mediaSession1!!.pause()
mediaSession1!!.nextTrack()
mediaSession1!!.play()
sessionRule.waitForResult(completedStep7)
mediaSession1!!.pause()
sessionRule.waitForResult(completedStep8a)
mediaSession1!!.previousTrack()
mediaSession1!!.play()
sessionRule.waitForResult(completedStep8b)
sessionRule.waitForResult(completedStep8)
sessionRule.waitForResult(completedStep9)
}
@Test
fun defaultMetadataPlayback() {
val onActivatedCalled = arrayOf(GeckoResult<Void>())
val onMetadataCalled = arrayOf(
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>())
val onPlayCalled = arrayOf(GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
@ -324,9 +320,11 @@ class MediaSessionTest : BaseSessionTest() {
// 1. Load Media Session page which contains 1 audio track.
// 2. Track 1 is played on page load.
// a. Ensure onActivated is called.
// a. Ensure onMetadata (1) is called.
// b. Ensure onPlay (1) is called.
val completedStep2 = GeckoResult.allOf(
onActivatedCalled[0],
onMetadataCalled[0],
onPlayCalled[0])
// 3. Pause playback of track 1.
@ -360,6 +358,47 @@ class MediaSessionTest : BaseSessionTest() {
mediaSession1 = mediaSession
}
/*
TODO: currently not called for non-media-session content.
@AssertCalled
override fun onFeatures(
session: GeckoSession,
mediaSession: MediaSession,
features: Long) {
val play = (features and MediaSession.Feature.PLAY) != 0L
val pause = (features and MediaSession.Feature.PAUSE) != 0L
val stop = (features and MediaSession.Feature.PAUSE) != 0L
assertThat(
"Core playback constrols should be supported",
play && pause && stop,
equalTo(true))
}
*/
@AssertCalled(count = 1)
override fun onMetadata(
session: GeckoSession,
mediaSession: MediaSession,
meta: MediaSession.Metadata) {
assertThat(
"Title should match",
meta.title,
equalTo(DEFAULT_META[0].title))
assertThat(
"Artist should match",
meta.artist,
equalTo(DEFAULT_META[0].artist))
assertThat(
"Album should match",
meta.album,
equalTo(DEFAULT_META[0].album))
onMetadataCalled[sessionRule.currentCall.counter - 1]
.complete(null)
}
@AssertCalled(count = 2)
override fun onPlay(
session: GeckoSession,
@ -660,9 +699,7 @@ class MediaSessionTest : BaseSessionTest() {
mediaSession2!!.pause()
sessionRule.waitForResult(completedStep6)
mediaSession1!!.pause()
mediaSession1!!.nextTrack()
mediaSession1!!.play()
sessionRule.waitForResult(completedStep7)
sessionRule.waitForResult(completedStep8)
}

Просмотреть файл

@ -1125,6 +1125,14 @@ public class GeckoSession {
@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
@ -2610,6 +2618,61 @@ public class GeckoSession {
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.
*

Просмотреть файл

@ -19,6 +19,8 @@ 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.
@ -33,12 +35,81 @@ public class MediaSession {
private static final boolean DEBUG = false;
private final GeckoSession mSession;
private boolean mIsActive;
private Controller mController;
private static final String ATTACHED_EVENT =
"GeckoView:MediaSession:Attached";
private boolean mControllerAttached;
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.
@ -54,41 +125,75 @@ public class MediaSession {
* @return True if this media session is active, false otherwise.
*/
public boolean isActive() {
return mIsActive;
return mControllerAttached;
}
/* package */ void setActive(final boolean active) {
mIsActive = active;
/* 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);
}
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;
}
/**
* Pause playback for the media session.
*/
public void pause() {
if (!mControllerAttached) {
return;
}
if (DEBUG) {
Log.d(LOGTAG, "pause");
}
mSession.getEventDispatcher().dispatch(PAUSE_EVENT, null);
mController.pause();
}
/**
* Stop playback for the media session.
*/
public void stop() {
if (!mControllerAttached) {
return;
}
if (DEBUG) {
Log.d(LOGTAG, "stop");
}
mSession.getEventDispatcher().dispatch(STOP_EVENT, null);
mController.stop();
}
/**
* Start playback for the media session.
*/
public void play() {
if (!mControllerAttached) {
return;
}
if (DEBUG) {
Log.d(LOGTAG, "play");
}
mSession.getEventDispatcher().dispatch(PLAY_EVENT, null);
mController.play();
}
/**
@ -100,37 +205,39 @@ public class MediaSession {
* @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);
}
final GeckoBundle bundle = new GeckoBundle(2);
bundle.putDouble("time", time);
bundle.putBoolean("fast", fast);
mSession.getEventDispatcher().dispatch(SEEK_TO_EVENT, bundle);
mController.seekTo(time, fast);
}
/**
* Seek forward by a sensible number of seconds.
*/
public void seekForward() {
if (!mControllerAttached) {
return;
}
if (DEBUG) {
Log.d(LOGTAG, "seekForward");
}
final GeckoBundle bundle = new GeckoBundle(1);
bundle.putDouble("offset", 0.0);
mSession.getEventDispatcher().dispatch(SEEK_FORWARD_EVENT, bundle);
mController.seekForward(0.0);
}
/**
* Seek backward by a sensible number of seconds.
*/
public void seekBackward() {
if (!mControllerAttached) {
return;
}
if (DEBUG) {
Log.d(LOGTAG, "seekBackward");
}
final GeckoBundle bundle = new GeckoBundle(1);
bundle.putDouble("offset", 0.0);
mSession.getEventDispatcher().dispatch(SEEK_BACKWARD_EVENT, bundle);
mController.seekBackward(0.0);
}
/**
@ -138,10 +245,13 @@ public class MediaSession {
* Move playback to the next item in the playlist when supported.
*/
public void nextTrack() {
if (!mControllerAttached) {
return;
}
if (DEBUG) {
Log.d(LOGTAG, "nextTrack");
}
mSession.getEventDispatcher().dispatch(NEXT_TRACK_EVENT, null);
mController.nextTrack();
}
/**
@ -149,20 +259,26 @@ public class MediaSession {
* Move playback to the previous item in the playlist when supported.
*/
public void previousTrack() {
if (!mControllerAttached) {
return;
}
if (DEBUG) {
Log.d(LOGTAG, "previousTrack");
}
mSession.getEventDispatcher().dispatch(PREV_TRACK_EVENT, null);
mController.previousTrack();
}
/**
* Skip the advertisement that is currently playing.
*/
public void skipAd() {
if (!mControllerAttached) {
return;
}
if (DEBUG) {
Log.d(LOGTAG, "skipAd");
}
mSession.getEventDispatcher().dispatch(SKIP_AD_EVENT, null);
mController.skipAd();
}
/**
@ -173,14 +289,18 @@ public class MediaSession {
* @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);
}
final GeckoBundle bundle = new GeckoBundle(1);
bundle.putBoolean("mute", mute);
mSession.getEventDispatcher().dispatch(MUTE_AUDIO_EVENT, bundle);
mController.muteAudio(mute);
}
// TODO: Not sure if we want it.
// public void focus() {}
/**
* Implement this delegate to receive media session events.
*/
@ -291,6 +411,19 @@ public class MediaSession {
@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) {}
}
@ -544,8 +677,8 @@ public class MediaSession {
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.SET_VIDEO_SURFACE,
Feature.FOCUS })
/* package */ @interface MSFeature {}
/**
@ -639,6 +772,8 @@ public class MediaSession {
"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 =
@ -646,27 +781,6 @@ public class MediaSession {
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> {
@ -678,10 +792,12 @@ public class MediaSession {
"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,
@ -702,15 +818,17 @@ public class MediaSession {
Log.d(LOGTAG, "handleMessage " + event);
}
if (ACTIVATED_EVENT.equals(event)) {
mMediaSession.setActive(true);
if (ATTACHED_EVENT.equals(event)) {
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.setActive(false);
mMediaSession.detachController();
delegate.onDeactivated(mSession, mMediaSession);
} else if (METADATA_EVENT.equals(event)) {
final Metadata meta =
Metadata.fromBundle(message.getBundle("metadata"));
final Metadata meta = Metadata.fromBundle(message);
delegate.onMetadata(mSession, mMediaSession, meta);
} else if (POSITION_STATE_EVENT.equals(event)) {
final PositionState state =
@ -732,6 +850,9 @@ public class MediaSession {
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);
}
}
}

Просмотреть файл

@ -14,12 +14,6 @@ exclude: true
⚠️ breaking change and deprecation notices
## v83
- ⚠️ Removing unsupported `MediaSession.Delegate.onPictureInPicture` for now.
Also, [`MediaSession.Delegate.onMetadata`][83.1] is no longer dispatched for
plain media elements.
([bug 1658937]({{bugzilla}}1658937))
[83.1]: {{javadoc_uri}}/MediaSession.Delegate.html#onMetadata-org.mozilla.geckoview.GeckoSession-org.mozilla.geckoview.MediaSession-org.mozilla.geckoview.MediaSession.Metadata-
## v82
- ⚠️ [`WebNotification.source`][79.2] is now `@Nullable` to account for
@ -809,4 +803,4 @@ to allow adding gecko profiler markers.
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
[65.25]: {{javadoc_uri}}/GeckoResult.html
[api-version]: 8cbbe3b03d78c33888562ea18a4554cff90f531d
[api-version]: e62341ace2541ae9fbd4f08f7c2d03ba28bf416a

Просмотреть файл

@ -31,26 +31,12 @@ class GeckoViewMediaControl extends GeckoViewModule {
this.controller.addEventListener("deactivated", this, options);
this.controller.addEventListener("supportedkeyschange", this, options);
this.controller.addEventListener("positionstatechange", this, options);
this.controller.addEventListener("metadatachange", this, options);
this.controller.addEventListener("playbackstatechange", this, options);
// TODO: Move other events to webidl once supported.
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() {
@ -60,8 +46,6 @@ class GeckoViewMediaControl extends GeckoViewModule {
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",
@ -73,47 +57,6 @@ class GeckoViewMediaControl extends GeckoViewModule {
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}`;
@ -144,12 +87,6 @@ class GeckoViewMediaControl extends GeckoViewModule {
case "positionstatechange":
this.handlePositionStateChanged(aEvent);
break;
case "metadatachange":
this.handleMetadataChanged();
break;
case "playbackstatechange":
this.handlePlaybackStateChanged();
break;
default:
warn`Unknown event type ${aEvent.type}`;
break;
@ -161,6 +98,7 @@ class GeckoViewMediaControl extends GeckoViewModule {
this.eventDispatcher.sendRequest({
type: "GeckoView:MediaSession:Fullscreen",
id: this.controller.id,
enabled: aData.enabled,
metadata: aData.metadata,
});
@ -171,6 +109,7 @@ class GeckoViewMediaControl extends GeckoViewModule {
this.eventDispatcher.sendRequest({
type: "GeckoView:MediaSession:Activated",
id: this.controller.id,
});
}
@ -179,6 +118,7 @@ class GeckoViewMediaControl extends GeckoViewModule {
this.eventDispatcher.sendRequest({
type: "GeckoView:MediaSession:Deactivated",
id: this.controller.id,
});
}
@ -187,6 +127,7 @@ class GeckoViewMediaControl extends GeckoViewModule {
this.eventDispatcher.sendRequest({
type: "GeckoView:MediaSession:PositionState",
id: this.controller.id,
state: {
duration: aEvent.duration,
playbackRate: aEvent.playbackRate,
@ -209,53 +150,10 @@ class GeckoViewMediaControl extends GeckoViewModule {
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(

Просмотреть файл

@ -29,6 +29,7 @@
#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"
@ -97,6 +98,7 @@ using mozilla::gfx::SurfaceFormat;
#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"
@ -173,6 +175,355 @@ 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.
@ -1422,6 +1773,36 @@ void GeckoViewSupport::PassExternalResponse(
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
@ -1429,6 +1810,7 @@ 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();
@ -1441,6 +1823,7 @@ void nsWindow::DetachNatives() {
mNPZCSupport.Detach();
mLayerViewSupport.Detach();
mSessionAccessibility.Detach();
mMediaSessionSupport.Detach();
}
/* static */

Просмотреть файл

@ -38,6 +38,7 @@ class AndroidView;
class GeckoEditableSupport;
class GeckoViewSupport;
class LayerViewSupport;
class MediaSessionSupport;
class NPZCSupport;
} // namespace widget
@ -95,6 +96,9 @@ class nsWindow final : public nsBaseWidget {
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>