Backed out 7 changesets (bug 1623715) for Linting failure and android build bustage. CLOSED TREE

Backed out changeset c8ca1d1866e7 (bug 1623715)
Backed out changeset 1b7c3fb0da3b (bug 1623715)
Backed out changeset 4887dea37231 (bug 1623715)
Backed out changeset 47f3eb481909 (bug 1623715)
Backed out changeset 6e7ce9e5f89b (bug 1623715)
Backed out changeset ddff358f800e (bug 1623715)
Backed out changeset cd585490e79b (bug 1623715)
This commit is contained in:
Dorel Luca 2020-08-18 00:20:24 +03:00
parent 806d75bb33
commit 6e3ab7e5fa
27 changed files with 81 additions and 2527 deletions

View file

@ -450,7 +450,14 @@ MediaController* MediaControlService::ControllerManager::GetMainController()
}
uint64_t MediaControlService::ControllerManager::GetControllersNum() const {
return mControllers.length();
size_t length = 0;
const auto* element =
static_cast<ConstLinkedListControllerPtr>(mControllers.getFirst());
while (element) {
length++;
element = element->getNext();
}
return length;
}
bool MediaControlService::ControllerManager::Contains(

View file

@ -119,6 +119,7 @@ class MediaControlService final : public nsIObserver {
void Shutdown();
MediaController* GetMainController() const;
MediaController* GetControllerById(uint64_t aId) const;
bool Contains(MediaController* aController) const;
uint64_t GetControllersNum() const;

View file

@ -297,7 +297,6 @@ void MediaStatusManager::UpdateActualPlaybackState() {
LOG("UpdateActualPlaybackState : '%s'",
ToMediaSessionPlaybackStateStr(mActualPlaybackState));
HandleActualPlaybackStateChanged();
mPlaybackStateChangedEvent.Notify(mActualPlaybackState);
}
void MediaStatusManager::EnableAction(uint64_t aBrowsingContextId,

View file

@ -180,15 +180,10 @@ class MediaStatusManager : public IMediaInfoUpdater {
MediaEventSource<MediaMetadataBase>& MetadataChangedEvent() {
return mMetadataChangedEvent;
}
MediaEventSource<PositionState>& PositionChangedEvent() {
return mPositionStateChangedEvent;
}
MediaEventSource<MediaSessionPlaybackState>& PlaybackChangedEvent() {
return mPlaybackStateChangedEvent;
}
// Return the actual playback state.
MediaSessionPlaybackState GetState() const;
@ -262,7 +257,6 @@ class MediaStatusManager : public IMediaInfoUpdater {
MediaEventProducer<nsTArray<MediaSessionAction>>
mSupportedActionsChangedEvent;
MediaEventProducer<PositionState> mPositionStateChangedEvent;
MediaEventProducer<MediaSessionPlaybackState> mPlaybackStateChangedEvent;
MediaPlaybackStatus mPlaybackStatusDelegate;
};

View file

@ -515,7 +515,15 @@ class LinkedList {
/**
* Return the length of elements in the list.
*/
size_t length() const { return std::distance(begin(), end()); }
size_t length() const {
size_t length = 0;
ConstRawType element = getFirst();
while (element) {
length++;
element = element->getNext();
}
return length;
}
/*
* Allow range-based iteration:

View file

@ -7,15 +7,10 @@
const { GeckoViewChildModule } = ChromeUtils.import(
"resource://gre/modules/GeckoViewChildModule.jsm"
);
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
MediaUtils: "resource://gre/modules/MediaUtils.jsm",
});
class GeckoViewMediaChild extends GeckoViewChildModule {
onInit() {
this._videoIndex = 0;
@ -54,7 +49,6 @@ class GeckoViewMediaChild extends GeckoViewChildModule {
onEnable() {
debug`onEnable`;
addEventListener("UAWidgetSetupOrChange", this, false);
addEventListener("MozDOMFullscreen:Entered", this, false);
addEventListener("MozDOMFullscreen:Exited", this, false);
@ -141,11 +135,15 @@ class GeckoViewMediaChild extends GeckoViewChildModule {
break;
case "MozDOMFullscreen:Entered":
const element = content && content.document.fullscreenElement;
// document.fullscreenElement can be a div container instead of the
// HTMLMediaElement in some pages (e.g Vimeo).
const mediaElement = MediaUtils.findVideoElement(element);
if (mediaElement) {
this.handleFullscreenChange(mediaElement);
if (this.isMedia(element)) {
this.handleFullscreenChange(element);
} else {
// document.fullscreenElement can be a div container instead of the HTMLMediaElement
// in some pages (e.g Vimeo).
const childrenMedias = element.getElementsByTagName("video");
if (childrenMedias && childrenMedias.length > 0) {
this.handleFullscreenChange(childrenMedias[0]);
}
}
break;
case "MozDOMFullscreen:Exited":
@ -174,7 +172,7 @@ class GeckoViewMediaChild extends GeckoViewChildModule {
}
handleNewMedia(aElement) {
if (this.getState(aElement) || !MediaUtils.isMediaElement(aElement)) {
if (this.getState(aElement) || !this.isMedia(aElement)) {
return;
}
const state = {
@ -190,10 +188,12 @@ class GeckoViewMediaChild extends GeckoViewChildModule {
notifyNewMedia(aElement) {
this.getState(aElement).notified = true;
const message = MediaUtils.getMetadata(aElement) ?? {};
message.type = "GeckoView:MediaAdd";
message.id = this.getState(aElement).id;
const message = {
type: "GeckoView:MediaAdd",
id: this.getState(aElement).id,
};
this.fillMetadata(aElement, message);
this.eventDispatcher.sendRequest(message);
}
@ -236,6 +236,16 @@ class GeckoViewMediaChild extends GeckoViewChildModule {
}
}
isMedia(aElement) {
if (!aElement) {
return false;
}
const elementType = ChromeUtils.getClassName(aElement);
return (
elementType === "HTMLVideoElement" || elementType === "HTMLAudioElement"
);
}
isObserving(aElement) {
return (
aElement && this.getState(aElement) && this.getState(aElement).observing
@ -282,6 +292,30 @@ class GeckoViewMediaChild extends GeckoViewChildModule {
return false;
}
fillMetadata(aElement, aOut) {
aOut.src = aElement.currentSrc || aElement.src;
aOut.width = aElement.videoWidth || 0;
aOut.height = aElement.videoHeight || 0;
aOut.duration = aElement.duration;
aOut.seekable = !!aElement.seekable;
if (aElement.audioTracks) {
aOut.audioTrackCount = aElement.audioTracks.length;
} else {
aOut.audioTrackCount =
aElement.mozHasAudio ||
aElement.webkitAudioDecodedByteCount ||
ChromeUtils.getClassName(aElement) === "HTMLAudioElement"
? 1
: 0;
}
if (aElement.videoTracks) {
aOut.videoTrackCount = aElement.videoTracks.length;
} else {
aOut.videoTrackCount =
ChromeUtils.getClassName(aElement) === "HTMLVideoElement" ? 1 : 0;
}
}
handleMediaEvent(aEvent) {
const element = aEvent.target;
if (!this.isObserving(element)) {
@ -354,10 +388,11 @@ class GeckoViewMediaChild extends GeckoViewChildModule {
}
notifyMetadataChange(aElement) {
const message = MediaUtils.getMetadata(aElement) ?? {};
message.type = "GeckoView:MediaMetadataChanged";
message.id = this.getState(aElement).id;
const message = {
type: "GeckoView:MediaMetadataChanged",
id: this.getState(aElement).id,
};
this.fillMetadata(aElement, message);
this.eventDispatcher.sendRequest(message);
}

View file

@ -1,72 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { GeckoViewChildModule } = ChromeUtils.import(
"resource://gre/modules/GeckoViewChildModule.jsm"
);
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
MediaUtils: "resource://gre/modules/MediaUtils.jsm",
});
class GeckoViewMediaControl extends GeckoViewChildModule {
onInit() {
debug`onEnable`;
}
onEnable() {
debug`onEnable`;
addEventListener("MozDOMFullscreen:Entered", this, false);
addEventListener("MozDOMFullscreen:Exited", this, false);
}
onDisable() {
debug`onDisable`;
removeEventListener("MozDOMFullscreen:Entered", this);
removeEventListener("MozDOMFullscreen:Exited", this);
}
handleEvent(aEvent) {
debug`handleEvent: ${aEvent.type}`;
switch (aEvent.type) {
case "MozDOMFullscreen:Entered":
case "MozDOMFullscreen:Exited":
this.handleFullscreenChanged();
break;
}
}
handleFullscreenChanged() {
const element = content && content.document.fullscreenElement;
const mediaElement = MediaUtils.findMediaElement(element);
if (element && !mediaElement) {
// Non-media element fullscreen.
return;
}
const message = {
metadata: MediaUtils.getMetadata(mediaElement) ?? {},
enabled: !!element,
};
this.messageManager.sendAsyncMessage(
"GeckoView:MediaControl:Fullscreen",
message
);
}
}
const { debug } = GeckoViewMediaControl.initLogging("GeckoViewMediaControl");
const module = GeckoViewMediaControl.create(this);

View file

@ -649,13 +649,6 @@ function startup() {
frameScript: "chrome://geckoview/content/GeckoViewAutofillChild.js",
},
},
{
name: "GeckoViewMediaControl",
onEnable: {
resource: "resource://gre/modules/GeckoViewMediaControl.jsm",
frameScript: "chrome://geckoview/content/GeckoViewMediaControlChild.js",
},
},
]);
// TODO: Bug 1569360 Allows actors to temporarely access ModuleManager until

View file

@ -14,7 +14,6 @@ geckoview.jar:
content/geckoview.js
content/GeckoViewAutofillChild.js
content/GeckoViewMediaChild.js
content/GeckoViewMediaControlChild.js
content/GeckoViewProgressChild.js
content/GeckoViewPromptChild.js
content/GeckoViewScrollChild.js

View file

@ -74,7 +74,6 @@ import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoSessionSettings;
import org.mozilla.geckoview.GeckoView;
import org.mozilla.geckoview.MediaElement;
import org.mozilla.geckoview.MediaSession;
import org.mozilla.geckoview.OverscrollEdgeEffect;
import org.mozilla.geckoview.PanZoomController;
import org.mozilla.geckoview.ProfilerController;
@ -661,7 +660,6 @@ package org.mozilla.geckoview {
method @AnyThread @NonNull public SessionFinder getFinder();
method @AnyThread @Nullable public GeckoSession.HistoryDelegate getHistoryDelegate();
method @AnyThread @Nullable public GeckoSession.MediaDelegate getMediaDelegate();
method @AnyThread @Nullable public MediaSession.Delegate getMediaSessionDelegate();
method @UiThread @Nullable public GeckoSession.NavigationDelegate getNavigationDelegate();
method @UiThread @NonNull public OverscrollEdgeEffect getOverscrollEdgeEffect();
method @UiThread public void getPageToScreenMatrix(@NonNull Matrix);
@ -709,7 +707,6 @@ package org.mozilla.geckoview {
method @AnyThread public void setFocused(boolean);
method @AnyThread public void setHistoryDelegate(@Nullable GeckoSession.HistoryDelegate);
method @AnyThread public void setMediaDelegate(@Nullable GeckoSession.MediaDelegate);
method @AnyThread public void setMediaSessionDelegate(@Nullable MediaSession.Delegate);
method @UiThread public void setNavigationDelegate(@Nullable GeckoSession.NavigationDelegate);
method @UiThread public void setPermissionDelegate(@Nullable GeckoSession.PermissionDelegate);
method @UiThread public void setProgressDelegate(@Nullable GeckoSession.ProgressDelegate);
@ -1342,73 +1339,6 @@ package org.mozilla.geckoview {
field public final long width;
}
@UiThread public class MediaSession {
ctor protected MediaSession(GeckoSession);
method public boolean isActive();
method public void muteAudio(boolean);
method public void nextTrack();
method public void pause();
method public void play();
method public void previousTrack();
method public void seekBackward();
method public void seekForward();
method public void seekTo(double, boolean);
method public void skipAd();
method public void stop();
}
@UiThread public static interface MediaSession.Delegate {
method default public void onActivated(@NonNull GeckoSession, @NonNull MediaSession);
method default public void onDeactivated(@NonNull GeckoSession, @NonNull MediaSession);
method default public void onFeatures(@NonNull GeckoSession, @NonNull MediaSession, long);
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);
}
public static class MediaSession.ElementMetadata {
ctor public ElementMetadata(@Nullable String, double, long, long, int, int);
field public final int audioTrackCount;
field public final double duration;
field public final long height;
field @Nullable public final String source;
field public final int videoTrackCount;
field public final long width;
}
public static class MediaSession.Feature {
ctor public Feature();
field public static final long FOCUS = 512L;
field public static final long NEXT_TRACK = 128L;
field public static final long NONE = 0L;
field public static final long PAUSE = 2L;
field public static final long PLAY = 1L;
field public static final long PREVIOUS_TRACK = 256L;
field public static final long SEEK_BACKWARD = 32L;
field public static final long SEEK_FORWARD = 16L;
field public static final long SEEK_TO = 8L;
field public static final long SKIP_AD = 64L;
field public static final long STOP = 4L;
}
public static class MediaSession.Metadata {
ctor protected Metadata(@Nullable String, @Nullable String, @Nullable String);
field @Nullable public final String album;
field @Nullable public final String artist;
field @Nullable public final String title;
}
public static class MediaSession.PositionState {
ctor protected PositionState(double, double, double);
field public final double duration;
field public final double playbackRate;
field public final double position;
}
@UiThread public final class OverscrollEdgeEffect {
method public void draw(@NonNull Canvas);
method @Nullable public Runnable getInvalidationCallback();

View file

@ -1,13 +0,0 @@
<html>
<head><title>MediaSessionDefaultTest1</title></head>
<body>
<script>
const audio1 = document.createElement("audio");
audio1.src = "audio/owl.mp3";
window.onload = () => {
audio1.play();
};
</script>
</body>
</html>

View file

@ -1,87 +0,0 @@
<html>
<head><title>MediaSessionDOMTest1</title></head>
<body>
<script>
function updatePositionState(event) {
navigator.mediaSession.setPositionState({
duration: parseFloat(event.target.duration),
position: parseFloat(event.target.currentTime),
playbackRate: 1,
});
}
function updateMetadata(event) {
active.removeEventListener("timeupdate", updatePositionState);
active = event.target;
updatePositionState(event);
active.addEventListener("timeupdate", updatePositionState);
navigator.mediaSession.metadata = active.metadata;
}
function getTrack(offset) {
console.log("" + active.id + " " + offset);
const nextId = Math.min(tracks.length - 1,
Math.max(0, parseInt(active.id) + offset));
return tracks[nextId];
}
navigator.mediaSession.setActionHandler("play", () => {
active.play();
});
navigator.mediaSession.setActionHandler("pause", () => {
active.pause();
});
navigator.mediaSession.setActionHandler("previoustrack", () => {
active.pause();
getTrack(-1).play();
});
navigator.mediaSession.setActionHandler("nexttrack", () => {
active.pause();
getTrack(1).play();
});
const audio1 = document.createElement("audio");
audio1.src = "audio/owl.mp3";
audio1.addEventListener("play", updateMetadata);
audio1.metadata = new window.MediaMetadata({
title: "hoot",
artist: "owl",
album: "hoots",
artwork: [{src: "owl.png", type: "image/png", sizes: "16x16 32x32"}]
});
audio1.id = 0;
const audio2 = document.createElement("audio");
audio2.src = "audio/owl.mp3";
audio2.addEventListener("play", updateMetadata);
audio2.metadata = new window.MediaMetadata({
title: "hoot2",
artist: "stillowl",
album: "dahoots",
artwork: [{src: "owl.png", type: "image/png", sizes: "16x16 32x32"}]
});
audio2.id = 1;
const audio3 = document.createElement("audio");
audio3.src = "audio/owl.mp3";
audio3.addEventListener("play", updateMetadata);
audio3.metadata = new window.MediaMetadata({
title: "hoot3",
artist: "immaowl",
album: "mahoots",
artwork: [{src: "owl.png", type: "image/png", sizes: "16x16 32x32"}]
});
audio3.id = 2;
const tracks = [audio1, audio2, audio3];
let active = audio1;
window.onload = () => {
getTrack(0).play();
};
</script>
</body>
</html>

View file

@ -72,8 +72,7 @@ open class BaseSessionTest(noErrorCollector: Boolean = false) {
const val OPEN_WINDOW_TARGET_PATH = "/assets/www/worker/open_window_target.html"
const val DATA_URI_PATH = "/assets/www/data_uri.html"
const val IFRAME_UNKNOWN_PROTOCOL = "/assets/www/iframe_unknown_protocol.html"
const val MEDIA_SESSION_DOM1_PATH = "/assets/www/media_session_dom1.html"
const val MEDIA_SESSION_DEFAULT1_PATH = "/assets/www/media_session_default1.html"
const val TEST_ENDPOINT = GeckoSessionTestRule.TEST_ENDPOINT
}

View file

@ -1,701 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.geckoview.test
import androidx.test.filters.MediumTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import android.util.Log
import org.hamcrest.Matchers.*
import org.json.JSONObject
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assume.assumeThat
import org.junit.Assume.assumeTrue
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule
import org.mozilla.geckoview.test.util.Callbacks
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.MediaSession
class Metadata(
title: String?,
artist: String?,
album: String?)
: MediaSession.Metadata(title, artist, album) {}
@RunWith(AndroidJUnit4::class)
@MediumTest
class MediaSessionTest : BaseSessionTest() {
companion object {
// See MEDIA_SESSION_DOM1_PATH file for details.
const val DOM_TEST_TITLE1 = "hoot"
const val DOM_TEST_TITLE2 = "hoot2"
const val DOM_TEST_TITLE3 = "hoot3"
const val DOM_TEST_ARTIST1 = "owl"
const val DOM_TEST_ARTIST2 = "stillowl"
const val DOM_TEST_ARTIST3 = "immaowl"
const val DOM_TEST_ALBUM1 = "hoots"
const val DOM_TEST_ALBUM2 = "dahoots"
const val DOM_TEST_ALBUM3 = "mahoots"
const val DEFAULT_TEST_TITLE1 = "MediaSessionDefaultTest1"
const val TEST_DURATION1 = 3.37
val DOM_META = arrayOf(
Metadata(
DOM_TEST_TITLE1,
DOM_TEST_ARTIST1,
DOM_TEST_ALBUM1),
Metadata(
DOM_TEST_TITLE2,
DOM_TEST_ARTIST2,
DOM_TEST_ALBUM2),
Metadata(
DOM_TEST_TITLE3,
DOM_TEST_ARTIST3,
DOM_TEST_ALBUM3))
val DEFAULT_META = arrayOf(
Metadata(
DEFAULT_TEST_TITLE1,
// TODO: enforce null for empty strings?
"",
""))
}
@Before
fun setup() {
sessionRule.setPrefsUntilTestEnd(mapOf(
"media.mediacontrol.stopcontrol.aftermediaends" to false,
"dom.media.mediasession.enabled" to true))
}
@After
fun teardown() {
}
@Ignore // TODO: Enable with disabled controller deactivation settings.
@Test
fun domMetadataPlayback() {
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>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>())
val onPauseCalled = arrayOf(GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>())
// Test:
// 1. Load DOM Media Session page which contains 3 audio tracks.
// 2. Track 1 is played on page load.
// a. Ensure onActivated is called.
// b. Ensure onMetadata (1) is called.
// c. Ensure onPlay (1) is called.
val completedStep2 = GeckoResult.allOf(
onActivatedCalled[0],
onMetadataCalled[0],
onPlayCalled[0])
// 3. Pause playback of track 1.
// a. Ensure onPause (1) is called.
val completedStep3 = GeckoResult.allOf(
onPauseCalled[0])
// 4. Resume playback (1).
// a. Ensure onMetadata (1) is called.
// b. Ensure onPlay (1) is called.
val completedStep4 = GeckoResult.allOf(
onPlayCalled[1])
// 5. Wait for track 1 end.
// a. Ensure onPause (1) is called.
val completedStep5 = GeckoResult.allOf(
onPauseCalled[1])
// 6. Play next track (2).
// a. Ensure onMetadata (2) is called.
// b. Ensure onPlay (2) is called.
val completedStep6 = GeckoResult.allOf(
onMetadataCalled[1],
onPlayCalled[2])
// 7. Play next track (3).
// a. Ensure onPause (2) is called.
// b. Ensure onMetadata (3) is called.
// c. Ensure onPlay (3) is called.
val completedStep7 = GeckoResult.allOf(
onPauseCalled[2],
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 completedStep8 = GeckoResult.allOf(
onPauseCalled[3],
onMetadataCalled[3],
onPlayCalled[4])
// 9. Wait for track 2 end.
// a. Ensure onPause (2) is called.
val completedStep9 = GeckoResult.allOf(
onPauseCalled[4])
val path = MEDIA_SESSION_DOM1_PATH
val session1 = sessionRule.createOpenSession()
var mediaSession1 : MediaSession? = null
// 1.
session1.loadTestPath(path)
session1.delegateUntilTestEnd(object : Callbacks.MediaSessionDelegate {
@AssertCalled(count = 1)
override fun onActivated(
session: GeckoSession,
mediaSession: MediaSession) {
onActivatedCalled[0].complete(null)
mediaSession1 = mediaSession
}
@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
val next = (features and MediaSession.Feature.PAUSE) != 0L
val prev = (features and MediaSession.Feature.PAUSE) != 0L
assertThat(
"Playback constrols should be supported",
play && pause && stop && next && prev,
equalTo(true))
}
@AssertCalled(count = 5)
override fun onMetadata(
session: GeckoSession,
mediaSession: MediaSession,
meta: MediaSession.Metadata) {
assertThat(
"Title should match",
meta.title,
equalTo(forEachCall(
DOM_META[0].title,
DOM_META[0].title,
DOM_META[1].title,
DOM_META[2].title,
DOM_META[1].title)))
assertThat(
"Artist should match",
meta.artist,
equalTo(forEachCall(
DOM_META[0].artist,
DOM_META[0].artist,
DOM_META[1].artist,
DOM_META[2].artist,
DOM_META[1].artist)))
assertThat(
"Album should match",
meta.album,
equalTo(forEachCall(
DOM_META[0].album,
DOM_META[0].album,
DOM_META[1].album,
DOM_META[2].album,
DOM_META[1].album)))
onMetadataCalled[sessionRule.currentCall.counter - 1]
.complete(null)
}
@AssertCalled
override fun onPositionState(
session: GeckoSession,
mediaSession: MediaSession,
state: MediaSession.PositionState) {
assertThat(
"Duration should match",
state.duration,
closeTo(TEST_DURATION1, 0.01))
assertThat(
"Playback rate should match",
state.playbackRate,
closeTo(1.0, 0.01))
assertThat(
"Position should be >= 0",
state.position,
greaterThanOrEqualTo(0.0))
}
@AssertCalled(count = 5)
override fun onPlay(
session: GeckoSession,
mediaSession: MediaSession) {
onPlayCalled[sessionRule.currentCall.counter - 1]
.complete(null)
}
@AssertCalled(count = 5)
override fun onPause(
session: GeckoSession,
mediaSession: MediaSession) {
onPauseCalled[sessionRule.currentCall.counter - 1]
.complete(null)
}
})
sessionRule.waitForResult(completedStep2)
mediaSession1!!.pause()
sessionRule.waitForResult(completedStep3)
mediaSession1!!.play()
sessionRule.waitForResult(completedStep4)
sessionRule.waitForResult(completedStep5)
mediaSession1!!.nextTrack()
sessionRule.waitForResult(completedStep6)
mediaSession1!!.nextTrack()
sessionRule.waitForResult(completedStep7)
mediaSession1!!.previousTrack()
sessionRule.waitForResult(completedStep8)
sessionRule.waitForResult(completedStep9)
}
@Ignore // TODO: Enable with disabled controller deactivation settings.
@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>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>())
val onPauseCalled = arrayOf(GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>())
// Test:
// 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.
// a. Ensure onPause (1) is called.
val completedStep3 = GeckoResult.allOf(
onPauseCalled[0])
// 4. Resume playback (1).
// b. Ensure onPlay (1) is called.
val completedStep4 = GeckoResult.allOf(
onPlayCalled[1])
// 5. Wait for track 1 end.
// a. Ensure onPause (1) is called.
val completedStep5 = GeckoResult.allOf(
onPauseCalled[1])
val path = MEDIA_SESSION_DEFAULT1_PATH
val session1 = sessionRule.createOpenSession()
var mediaSession1 : MediaSession? = null
// 1.
session1.loadTestPath(path)
session1.delegateUntilTestEnd(object : Callbacks.MediaSessionDelegate {
@AssertCalled(count = 1)
override fun onActivated(
session: GeckoSession,
mediaSession: MediaSession) {
onActivatedCalled[0].complete(null)
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,
mediaSession: MediaSession) {
onPlayCalled[sessionRule.currentCall.counter - 1]
.complete(null)
}
@AssertCalled(count = 2)
override fun onPause(
session: GeckoSession,
mediaSession: MediaSession) {
onPauseCalled[sessionRule.currentCall.counter - 1]
.complete(null)
}
})
sessionRule.waitForResult(completedStep2)
mediaSession1!!.pause()
sessionRule.waitForResult(completedStep3)
mediaSession1!!.play()
sessionRule.waitForResult(completedStep4)
sessionRule.waitForResult(completedStep5)
}
@Ignore // We don't need to control active state anymore.
@Test
fun domMultiSessions() {
val onActivatedCalled = arrayOf(
arrayOf(GeckoResult<Void>()),
arrayOf(GeckoResult<Void>()))
val onMetadataCalled = arrayOf(
arrayOf(
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>()),
arrayOf(
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>()))
val onPlayCalled = arrayOf(
arrayOf(
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>()),
arrayOf(
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>()))
val onPauseCalled = arrayOf(
arrayOf(
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>()),
arrayOf(
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>(),
GeckoResult<Void>()))
// Test:
// 1. Session1: Load DOM Media Session page with 3 audio tracks.
// 2. Session1: Track 1 is played on page load.
// a. Session1: Ensure onActivated is called.
// b. Session1: Ensure onMetadata (1) is called.
// c. Session1: Ensure onPlay (1) is called.
// d. Session1: Verify isActive.
val completedStep2 = GeckoResult.allOf(
onActivatedCalled[0][0],
onMetadataCalled[0][0],
onPlayCalled[0][0])
// 3. Session1: Pause playback of track 1.
// a. Session1: Ensure onPause (1) is called.
val completedStep3 = GeckoResult.allOf(
onPauseCalled[0][0])
// 4. Session2: Load DOM Media Session page with 3 audio tracks.
// 5. Session2: Track 1 is played on page load.
// a. Session2: Ensure onActivated is called.
// b. Session2: Ensure onMetadata (1) is called.
// c. Session2: Ensure onPlay (1) is called.
// d. Session2: Verify isActive.
// e. Session1: Verify !isActive.
val completedStep5 = GeckoResult.allOf(
onActivatedCalled[1][0],
onMetadataCalled[1][0],
onPlayCalled[1][0])
// 6. Session2: Pause playback of track 1.
// a. Session2: Ensure onPause (1) is called.
val completedStep6 = GeckoResult.allOf(
onPauseCalled[1][0])
// 7. Session1: Play next track (2).
// a. Session1: Ensure onMetadata (2) is called.
// b. Session1: Ensure onPlay (2) is called.
val completedStep7 = GeckoResult.allOf(
onMetadataCalled[0][1],
onPlayCalled[0][1])
// 8. Session1: wait for track 1 end.
// a. Ensure onPause (1) is called.
val completedStep8 = GeckoResult.allOf(
onPauseCalled[0][1])
val path = MEDIA_SESSION_DOM1_PATH
val session1 = sessionRule.createOpenSession()
val session2 = sessionRule.createOpenSession()
var mediaSession1 : MediaSession? = null
var mediaSession2 : MediaSession? = null
session1.delegateUntilTestEnd(object : Callbacks.MediaSessionDelegate {
@AssertCalled(count = 1)
override fun onActivated(
session: GeckoSession,
mediaSession: MediaSession) {
onActivatedCalled[0][sessionRule.currentCall.counter - 1]
.complete(null)
mediaSession1 = mediaSession
assertThat(
"Should be active",
mediaSession1?.isActive,
equalTo(true))
}
@AssertCalled
override fun onPositionState(
session: GeckoSession,
mediaSession: MediaSession,
state: MediaSession.PositionState) {
assertThat(
"Duration should match",
state.duration,
closeTo(TEST_DURATION1, 0.01))
assertThat(
"Playback rate should match",
state.playbackRate,
closeTo(1.0, 0.01))
assertThat(
"Position should be >= 0",
state.position,
greaterThanOrEqualTo(0.0))
}
@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
val next = (features and MediaSession.Feature.PAUSE) != 0L
val prev = (features and MediaSession.Feature.PAUSE) != 0L
assertThat(
"Playback constrols should be supported",
play && pause && stop && next && prev,
equalTo(true))
}
@AssertCalled(count = 2)
override fun onMetadata(
session: GeckoSession,
mediaSession: MediaSession,
meta: MediaSession.Metadata) {
onMetadataCalled[0][sessionRule.currentCall.counter - 1]
.complete(null)
assertThat(
"Title should match",
meta.title,
equalTo(forEachCall(
DOM_META[0].title,
DOM_META[1].title)))
assertThat(
"Artist should match",
meta.artist,
equalTo(forEachCall(
DOM_META[0].artist,
DOM_META[1].artist)))
assertThat(
"Album should match",
meta.album,
equalTo(forEachCall(
DOM_META[0].album,
DOM_META[1].album)))
}
@AssertCalled(count = 2)
override fun onPlay(
session: GeckoSession,
mediaSession: MediaSession) {
onPlayCalled[0][sessionRule.currentCall.counter - 1]
.complete(null)
}
@AssertCalled(count = 2)
override fun onPause(
session: GeckoSession,
mediaSession: MediaSession) {
onPauseCalled[0][sessionRule.currentCall.counter - 1]
.complete(null)
}
})
session2.delegateUntilTestEnd(object : Callbacks.MediaSessionDelegate {
@AssertCalled(count = 1)
override fun onActivated(
session: GeckoSession,
mediaSession: MediaSession) {
onActivatedCalled[1][sessionRule.currentCall.counter - 1]
.complete(null)
mediaSession2 = mediaSession;
assertThat(
"Should be active",
mediaSession1!!.isActive,
equalTo(true))
assertThat(
"Should be active",
mediaSession2!!.isActive,
equalTo(true))
}
@AssertCalled(count = 1)
override fun onMetadata(
session: GeckoSession,
mediaSession: MediaSession,
meta: MediaSession.Metadata) {
onMetadataCalled[1][sessionRule.currentCall.counter - 1]
.complete(null)
assertThat(
"Title should match",
meta.title,
equalTo(forEachCall(
DOM_META[0].title)))
assertThat(
"Artist should match",
meta.artist,
equalTo(forEachCall(
DOM_META[0].artist)))
assertThat(
"Album should match",
meta.album,
equalTo(forEachCall(
DOM_META[0].album)))
}
@AssertCalled(count = 1)
override fun onPlay(
session: GeckoSession,
mediaSession: MediaSession) {
onPlayCalled[1][sessionRule.currentCall.counter - 1]
.complete(null)
}
@AssertCalled(count = 1)
override fun onPause(
session: GeckoSession,
mediaSession: MediaSession) {
onPauseCalled[1][sessionRule.currentCall.counter - 1]
.complete(null)
}
})
session1.loadTestPath(path)
sessionRule.waitForResult(completedStep2)
mediaSession1!!.pause()
sessionRule.waitForResult(completedStep3)
session2.loadTestPath(path)
sessionRule.waitForResult(completedStep5)
mediaSession2!!.pause()
sessionRule.waitForResult(completedStep6)
mediaSession1!!.nextTrack()
sessionRule.waitForResult(completedStep7)
sessionRule.waitForResult(completedStep8)
}
}

View file

@ -17,7 +17,6 @@ import org.mozilla.geckoview.GeckoResult;
import org.mozilla.geckoview.GeckoRuntime;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoSessionSettings;
import org.mozilla.geckoview.MediaSession;
import org.mozilla.geckoview.RuntimeTelemetry;
import org.mozilla.geckoview.SessionTextInput;
import org.mozilla.geckoview.WebExtension;
@ -880,10 +879,6 @@ public class GeckoSessionTestRule implements TestRule {
return GeckoSession.class.getMethod("setAutofillDelegate", cls)
.invoke(session, delegate);
}
if (cls == MediaSession.Delegate.class) {
return GeckoSession.class.getMethod("setMediaSessionDelegate", cls)
.invoke(session, delegate);
}
return GeckoSession.class.getMethod("set" + cls.getSimpleName(), cls)
.invoke(session, delegate);
}
@ -903,10 +898,6 @@ public class GeckoSessionTestRule implements TestRule {
return GeckoSession.class.getMethod("getAutofillDelegate")
.invoke(session);
}
if (cls == MediaSession.Delegate.class) {
return GeckoSession.class.getMethod("getMediaSessionDelegate")
.invoke(session);
}
return GeckoSession.class.getMethod("get" + cls.getSimpleName())
.invoke(session);
}

View file

@ -13,7 +13,6 @@ import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoSession.ContentDelegate.ContextElement
import org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest
import org.mozilla.geckoview.MediaElement
import org.mozilla.geckoview.MediaSession
import org.mozilla.geckoview.WebRequestError
import android.view.inputmethod.CursorAnchorInfo
@ -25,7 +24,7 @@ class Callbacks private constructor() {
object Default : All
interface All : AutofillDelegate, ContentBlockingDelegate, ContentDelegate,
HistoryDelegate, MediaDelegate, MediaSessionDelegate,
HistoryDelegate, MediaDelegate,
NavigationDelegate, PermissionDelegate, ProgressDelegate,
PromptDelegate, ScrollDelegate, SelectionActionDelegate,
TextInputDelegate
@ -41,7 +40,6 @@ class Callbacks private constructor() {
interface SelectionActionDelegate : GeckoSession.SelectionActionDelegate {}
interface MediaDelegate: GeckoSession.MediaDelegate {}
interface HistoryDelegate : GeckoSession.HistoryDelegate {}
interface MediaSessionDelegate: MediaSession.Delegate {}
interface TextInputDelegate : GeckoSession.TextInputDelegate {
override fun restartInput(session: GeckoSession, reason: Int) {

View file

@ -883,18 +883,14 @@ public class GeckoSession implements Parcelable {
}
};
private final MediaSession.Handler mMediaSessionHandler =
new MediaSession.Handler(this);
/* package */ int handlersCount;
private final GeckoSessionHandler<?>[] mSessionHandlers =
new GeckoSessionHandler<?>[] {
mContentHandler, mHistoryHandler, mMediaHandler,
mNavigationHandler, mPermissionHandler, mProcessHangHandler,
mProgressHandler, mScrollHandler, mSelectionActionDelegate,
mContentBlockingHandler, mMediaSessionHandler
};
private final GeckoSessionHandler<?>[] mSessionHandlers = new GeckoSessionHandler<?>[] {
mContentHandler, mHistoryHandler, mMediaHandler, mNavigationHandler,
mPermissionHandler, mProcessHangHandler, mProgressHandler, mScrollHandler,
mSelectionActionDelegate, mContentBlockingHandler
};
private static class PermissionCallback implements
PermissionDelegate.Callback, PermissionDelegate.MediaCallback {
@ -1123,14 +1119,6 @@ public class GeckoSession implements Parcelable {
@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
@ -2642,81 +2630,6 @@ public class GeckoSession implements Parcelable {
return mMediaHandler.getDelegate();
}
/**
* 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.

View file

@ -1,823 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* vim: ts=4 sw=4 expandtab:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.geckoview;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import android.support.annotation.AnyThread;
import android.support.annotation.LongDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.util.Log;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
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;
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(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);
}
/* 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");
}
mController.pause();
}
/**
* Stop playback for the media session.
*/
public void stop() {
if (!mControllerAttached) {
return;
}
if (DEBUG) {
Log.d(LOGTAG, "stop");
}
mController.stop();
}
/**
* Start playback for the media session.
*/
public void play() {
if (!mControllerAttached) {
return;
}
if (DEBUG) {
Log.d(LOGTAG, "play");
}
mController.play();
}
/**
* 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);
}
/**
* Seek forward by a sensible number of seconds.
*/
public void seekForward() {
if (!mControllerAttached) {
return;
}
if (DEBUG) {
Log.d(LOGTAG, "seekForward");
}
mController.seekForward(0.0);
}
/**
* Seek backward by a sensible number of seconds.
*/
public void seekBackward() {
if (!mControllerAttached) {
return;
}
if (DEBUG) {
Log.d(LOGTAG, "seekBackward");
}
mController.seekBackward(0.0);
}
/**
* 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();
}
/**
* 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();
}
/**
* Skip the advertisement that is currently playing.
*/
public void skipAd() {
if (!mControllerAttached) {
return;
}
if (DEBUG) {
Log.d(LOGTAG, "skipAd");
}
mController.skipAd();
}
/**
* 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);
}
// 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.
*
* @param session The associated GeckoSession.
* @param mediaSession The media session for the given GeckoSession.
*/
default void onActivated(
@NonNull GeckoSession session,
@NonNull MediaSession mediaSession) {}
/**
* Notify that the given media session has become inactive.
* Inactive media sessions can not be controlled.
*
* TODO: Add settings links to control behavior.
*
* @param session The associated GeckoSession.
* @param mediaSession The media session for the given GeckoSession.
*/
default void onDeactivated(
@NonNull GeckoSession session,
@NonNull MediaSession mediaSession) {}
/**
* Notify on updated metadata.
* Metadata may be provided by content via the DOM API or by GeckoView
* when not availble.
*
* @param session The associated GeckoSession.
* @param mediaSession The media session for the given GeckoSession.
* @param meta The updated metadata.
*/
default void onMetadata(
@NonNull GeckoSession session,
@NonNull MediaSession mediaSession,
@NonNull Metadata meta) {}
/**
* Notify on updated supported features.
* Unsupported actions will have no effect.
*
* @param session The associated GeckoSession.
* @param mediaSession The media session for the given GeckoSession.
* @param features A combination of {@link Feature}.
*/
default void onFeatures(
@NonNull GeckoSession session,
@NonNull MediaSession mediaSession,
@MSFeature long features) {}
/**
* Notify that playback has started for the given media session.
*
* @param session The associated GeckoSession.
* @param mediaSession The media session for the given GeckoSession.
*/
default void onPlay(
@NonNull GeckoSession session,
@NonNull MediaSession mediaSession) {}
/**
* Notify that playback has paused for the given media session.
*
* @param session The associated GeckoSession.
* @param mediaSession The media session for the given GeckoSession.
*/
default void onPause(
@NonNull GeckoSession session,
@NonNull MediaSession mediaSession) {}
/**
* Notify that playback has stopped for the given media session.
*
* @param session The associated GeckoSession.
* @param mediaSession The media session for the given GeckoSession.
*/
default void onStop(
@NonNull GeckoSession session,
@NonNull MediaSession mediaSession) {}
/**
* Notify on updated position state.
*
* @param session The associated GeckoSession.
* @param mediaSession The media session for the given GeckoSession.
* @param state An instance of {@link PositionState}.
*/
default void onPositionState(
@NonNull GeckoSession session,
@NonNull MediaSession mediaSession,
@NonNull PositionState state) {}
/**
* Notify on changed fullscreen state.
*
* @param session The associated GeckoSession.
* @param mediaSession The media session for the given GeckoSession.
* @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 {
/**
* The media source URI.
*/
public final @Nullable String source;
/**
* The duration of the media in seconds.
*/
public final double duration;
/**
* The width of the video in device pixels.
*/
public final long width;
/**
* The height of the video in device pixels.
*/
public final long height;
/**
* The number of audio tracks contained in this element.
*/
public final int audioTrackCount;
/**
* The number of video tracks contained in this element.
*/
public final int videoTrackCount;
/**
* ElementMetadata constructor.
*
* @param source The media URI.
* @param duration The media duration in seconds.
* @param width The video width in device pixels.
* @param height The video height in device pixels.
* @param audioTrackCount The audio track count.
* @param videoTrackCount The video track count.
*/
public ElementMetadata(
@Nullable final String source,
final double duration,
final long width,
final long height,
final int audioTrackCount,
final int videoTrackCount) {
this.source = source;
this.duration = duration;
this.width = width;
this.height = height;
this.audioTrackCount = audioTrackCount;
this.videoTrackCount = videoTrackCount;
}
/* package */ static @NonNull ElementMetadata fromBundle(
final GeckoBundle bundle) {
// Sync with MediaUtils.jsm.
return new ElementMetadata(
bundle.getString("src"),
bundle.getDouble("duration", 0.0),
bundle.getLong("width", 0),
bundle.getLong("height", 0),
bundle.getInt("audioTrackCount", 0),
bundle.getInt("videoTrackCount", 0));
}
}
/**
* The representation of a media session's metadata.
*/
public static class Metadata {
/**
* The media title.
* May be backfilled based on the document's title.
* May be null or empty.
*/
public final @Nullable String title;
/**
* The media artist name.
* May be null or empty.
*/
public final @Nullable String artist;
/**
* The media album title.
* May be null or empty.
*/
public final @Nullable String album;
/**
* Metadata constructor.
*
* @param title The media title string.
* @param artist The media artist string.
* @param album The media album string.
*/
protected Metadata(
final @Nullable String title,
final @Nullable String artist,
final @Nullable String album) {
this.title = title;
this.artist = artist;
this.album = album;
}
@AnyThread
/* package */ static final class Builder {
private final GeckoBundle mBundle;
public Builder(final GeckoBundle bundle) {
mBundle = new GeckoBundle(bundle);
}
public Builder(final Metadata meta) {
mBundle = meta.toBundle();
}
@NonNull Builder title(final @Nullable String title) {
mBundle.putString("title", title);
return this;
}
@NonNull Builder artist(final @Nullable String artist) {
mBundle.putString("artist", artist);
return this;
}
@NonNull Builder album(final @Nullable String album) {
mBundle.putString("album", album);
return this;
}
}
/* package */ static @NonNull Metadata fromBundle(
final GeckoBundle bundle) {
return new Metadata(
bundle.getString("title"),
bundle.getString("artist"),
bundle.getString("album"));
}
/* package */ @NonNull GeckoBundle toBundle() {
final GeckoBundle bundle = new GeckoBundle(3);
bundle.putString("title", title);
bundle.putString("artist", artist);
bundle.putString("album", album);
return bundle;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder("Metadata {");
builder
.append(", title=").append(title)
.append(", artist=").append(artist)
.append(", album=").append(album)
.append("}");
return builder.toString();
}
}
/**
* Holds the details of the media session's playback state.
*/
public static class PositionState {
/**
* The duration of the media in seconds.
*/
public final double duration;
/**
* The last reported media playback position in seconds.
*/
public final double position;
/**
* The media playback rate coefficient.
* The rate is positive for forward and negative for backward playback.
*/
public final double playbackRate;
/**
* PositionState constructor.
*
* @param duration The media duration in seconds.
* @param position The current media playback position in seconds.
* @param playbackRate The playback rate coefficient.
*/
protected PositionState(
final double duration,
final double position,
final double playbackRate) {
this.duration = duration;
this.position = position;
this.playbackRate = playbackRate;
}
/* package */ static @NonNull PositionState fromBundle(
final GeckoBundle bundle) {
return new PositionState(
bundle.getDouble("duration"),
bundle.getDouble("position"),
bundle.getDouble("playbackRate"));
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder("PositionState {");
builder
.append("duration=").append(duration)
.append(", position=").append(position)
.append(", playbackRate=").append(playbackRate)
.append("}");
return builder.toString();
}
}
@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 })
/* package */ @interface MSFeature {}
/**
* Flags for supported media session features.
*/
public static class Feature {
public static final long NONE = 0;
/**
* Playback supported.
*/
public static final long PLAY = 1 << 0;
/**
* Pausing supported.
*/
public static final long PAUSE = 1 << 1;
/**
* Stopping supported.
*/
public static final long STOP = 1 << 2;
/**
* Absolute seeking supported.
*/
public static final long SEEK_TO = 1 << 3;
/**
* Relative seeking supported (forward).
*/
public static final long SEEK_FORWARD = 1 << 4;
/**
* Relative seeking supported (backward).
*/
public static final long SEEK_BACKWARD = 1 << 5;
/**
* Skipping advertisements supported.
*/
public static final long SKIP_AD = 1 << 6;
/**
* Next track selection supported.
*/
public static final long NEXT_TRACK = 1 << 7;
/**
* Previous track selection supported.
*/
public static final long PREVIOUS_TRACK = 1 << 8;
/**
* Focusing supported.
*/
public static final long FOCUS = 1 << 9;
// /**
// * Custom video surface supported.
// */
// public static final long SET_VIDEO_SURFACE = 1 << 10;
/* package */ static long fromBundle(final GeckoBundle bundle) {
// Sync with MediaController.webidl.
final long features =
NONE |
(bundle.getBoolean("play") ? PLAY : NONE) |
(bundle.getBoolean("pause") ? PAUSE : NONE) |
(bundle.getBoolean("stop") ? STOP : NONE) |
(bundle.getBoolean("seekto") ? SEEK_TO : NONE) |
(bundle.getBoolean("seekforward") ? SEEK_FORWARD : NONE) |
(bundle.getBoolean("seekbackward") ? SEEK_BACKWARD : NONE) |
(bundle.getBoolean("nexttrack") ? NEXT_TRACK : NONE) |
(bundle.getBoolean("previoustrack") ? PREVIOUS_TRACK : NONE) |
(bundle.getBoolean("skipad") ? SKIP_AD : NONE) |
(bundle.getBoolean("focus") ? FOCUS : NONE);
return features;
}
}
private static final String ACTIVATED_EVENT =
"GeckoView:MediaSession:Activated";
private static final String DEACTIVATED_EVENT =
"GeckoView:MediaSession:Deactivated";
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";
/* 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);
}
@Override
public void handleMessage(
final Delegate delegate,
final String event,
final GeckoBundle message,
final EventCallback callback) {
if (DEBUG) {
Log.d(LOGTAG, "handleMessage " + event);
}
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.detachController();
delegate.onDeactivated(mSession, mMediaSession);
} else if (METADATA_EVENT.equals(event)) {
final Metadata meta = Metadata.fromBundle(message);
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)) {
delegate.onPause(mSession, mMediaSession);
} else if (PLAYBACK_PLAYING_EVENT.equals(event)) {
delegate.onPlay(mSession, mMediaSession);
} else if (FEATURES_EVENT.equals(event)) {
final long features = Feature.fromBundle(
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);
}
}
}
}

View file

@ -25,11 +25,9 @@ exclude: true
of the default implementations. ([bug 1647883]({{bugzilla}}1647883))
- Added `strictSocialTrackingProtection` to [`ContentBlocking.Settings.Builder`][80.1] and `getStrictSocialTrackingProtection`
to [`ContentBlocking.Settings`][80.2].
- Added [`MediaSession`][80.3] API for session-based media events and control.
[80.1]: {{javadoc_uri}}/ContentBlocking.Settings.Builder.html
[80.2]: {{javadoc_uri}}/ContentBlocking.Settings.html
[80.3]: {{javadoc_uri}}/MediaSession.html
## v79
- Added `runtime.openOptionsPage` support. For `options_ui.open_in_new_tab` ==
@ -761,4 +759,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]: a3245a37268efa5b1bbf787a3aca046670d36cdb
[api-version]: b7c6af9e586923fd7c9be735ea7244365a663c2e

View file

@ -1,161 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var EXPORTED_SYMBOLS = ["GeckoViewMediaControl"];
const { GeckoViewModule } = ChromeUtils.import(
"resource://gre/modules/GeckoViewModule.jsm"
);
class GeckoViewMediaControl extends GeckoViewModule {
onInit() {
debug`onInit`;
}
onEnable() {
debug`onEnable`;
if (this.controller.isActive) {
this.handleActivated();
}
const options = {
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.messageManager.addMessageListener(
"GeckoView:MediaControl:Fullscreen",
this
);
}
onDisable() {
debug`onDisable`;
this.controller.removeEventListener("activated", this);
this.controller.removeEventListener("deactivated", this);
this.controller.removeEventListener("supportedkeyschange", this);
this.controller.removeEventListener("positionstatechange", this);
this.messageManager.removeMessageListener(
"GeckoView:MediaControl:Fullscreen",
this
);
}
get controller() {
return this.browser.browsingContext.mediaController;
}
receiveMessage(aMsg) {
debug`receiveMessage: name=${aMsg.name}, data=${aMsg.data}`;
switch (aMsg.name) {
case "GeckoView:MediaControl:Fullscreen":
this.handleFullscreenChanged(aMsg.data);
break;
default:
warn`Unknown message name ${aMsg.name}`;
break;
}
}
// eslint-disable-next-line complexity
handleEvent(aEvent) {
debug`handleEvent: ${aEvent.type}`;
switch (aEvent.type) {
case "activated":
this.handleActivated();
break;
case "deactivated":
this.handleDeactivated();
break;
case "supportedkeyschange":
this.handleSupportedKeysChanged();
break;
case "positionstatechange":
this.handlePositionStateChanged(aEvent);
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,
},
});
}
handleSupportedKeysChanged() {
const supported = this.controller.supportedKeys;
debug`handleSupportedKeysChanged ${supported}`;
// Mapping it to a key-value store for compatibility with the JNI
// 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,
});
}
}
const { debug, warn } = GeckoViewMediaControl.initLogging(
"GeckoViewMediaControl"
);

View file

@ -1,76 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const EXPORTED_SYMBOLS = ["MediaUtils"];
const MediaUtils = {
getMetadata(aElement) {
if (!aElement) {
return null;
}
return {
src: aElement.currentSrc ?? aElement.src,
width: aElement.videoWidth ?? 0,
height: aElement.videoHeight ?? 0,
duration: aElement.duration,
seekable: !!aElement.seekable,
audioTrackCount:
aElement.audioTracks?.length ??
aElement.mozHasAudio ??
aElement.webkitAudioDecodedByteCount ??
MediaUtils.isAudioElement(aElement)
? 1
: 0,
videoTrackCount:
aElement.videoTracks?.length ?? MediaUtils.isVideoElement(aElement)
? 1
: 0,
};
},
isVideoElement(aElement) {
return (
aElement && ChromeUtils.getClassName(aElement) === "HTMLVideoElement"
);
},
isAudioElement(aElement) {
return (
aElement && ChromeUtils.getClassName(aElement) === "HTMLAudioElement"
);
},
isMediaElement(aElement) {
return (
MediaUtils.isVideoElement(aElement) || MediaUtils.isAudioElement(aElement)
);
},
findVideoElement(aElement) {
if (!aElement) {
return null;
}
if (MediaUtils.isVideoElement(aElement)) {
return aElement;
}
const childrenMedia = aElement.getElementsByTagName("video");
if (childrenMedia && childrenMedia.length > 0) {
return childrenMedia[0];
}
return null;
},
findAudioElement(aElement) {
if (!aElement || MediaUtils.isAudioElement(aElement)) {
return aElement;
}
const childrenMedia = aElement.getElementsByTagName("audio");
if (childrenMedia && childrenMedia.length > 0) {
return childrenMedia[0];
}
return null;
},
};

View file

@ -19,7 +19,6 @@ EXTRA_JS_MODULES += [
'GeckoViewContentBlocking.jsm',
'GeckoViewContentBlockingController.jsm',
'GeckoViewMedia.jsm',
'GeckoViewMediaControl.jsm',
'GeckoViewModule.jsm',
'GeckoViewNavigation.jsm',
'GeckoViewProcessHangMonitor.jsm',
@ -33,6 +32,5 @@ EXTRA_JS_MODULES += [
'GeckoViewUtils.jsm',
'GeckoViewWebExtension.jsm',
'LoadURIDelegate.jsm',
'MediaUtils.jsm',
'Messaging.jsm',
]

View file

@ -8,8 +8,7 @@ namespace mozilla {
namespace widget {
mozilla::dom::MediaControlKeySource* CreateMediaControlKeySource() {
// GeckoView uses MediaController.webidl for media session events and control,
// see bug 1623715.
// TODO : will implement this in bug 1601510.
return nullptr;
}

View file

@ -62,7 +62,6 @@ classes_with_WrapForJNI = [
'HardwareCodecCapabilityUtils',
'ImageDecoder',
'MediaDrmProxy',
'MediaSession',
'PanZoomController',
'PrefsHelper',
'RuntimeTelemetry',

View file

@ -26,11 +26,8 @@
#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"
@ -92,7 +89,6 @@ using mozilla::dom::ContentParent;
#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"
@ -367,340 +363,8 @@ class nsWindow::GeckoViewSupport final
int32_t aFlags, mozilla::jni::String::Param aTriggeringUri,
bool aHasUserGesture, bool aIsTopLevel) const
-> java::GeckoResult::LocalRef;
void AttachMediaSessionController(const GeckoSession::Window::LocalRef& inst,
jni::Object::Param aController,
const int64_t aId);
void DetachMediaSessionController(const GeckoSession::Window::LocalRef& inst,
jni::Object::Param aController);
};
class nsWindow::MediaSessionSupport final
: public mozilla::java::MediaSession::Controller::Natives<
MediaSessionSupport> {
using LockedWindowPtr = WindowPtr<MediaSessionSupport>::Locked;
using MediaKeysArray = nsTArray<MediaControlKey>;
typedef RefPtr<mozilla::dom::MediaController> ControllerPtr;
WindowPtr<MediaSessionSupport> 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(
NativePtr<MediaSessionSupport>* aPtr, nsWindow* aWindow,
const java::MediaSession::Controller::LocalRef& aController)
: mWindow(aPtr, aWindow),
mJavaController(aController),
mMediaController(nullptr) {
MOZ_ASSERT(mWindow);
}
bool Dispatch(const char16_t aType[],
java::GeckoBundle::Param aBundle = nullptr) {
widget::EventDispatcher* dispatcher = mWindow->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 OnDetach(already_AddRefed<Runnable> aDisposer) {
MOZ_ASSERT(NS_IsMainThread());
SetNativeController(nullptr);
}
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->GetState());
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);
}
};
template <>
const char nsWindow::NativePtr<nsWindow::MediaSessionSupport>::sName[] =
"MediaSessionSupport";
/**
* PanZoomController handles its native calls on the UI thread, so make
* it separate from GeckoViewSupport.
@ -1831,46 +1495,11 @@ void nsWindow::GeckoViewSupport::AttachAccessibility(
sessionAccessibility);
}
void nsWindow::GeckoViewSupport::AttachMediaSessionController(
const GeckoSession::Window::LocalRef& inst, jni::Object::Param aController,
const int64_t aId) {
if (window.mMediaSessionSupport) {
window.mMediaSessionSupport.Detach(
window.mMediaSessionSupport->GetJavaController());
}
auto controller = java::MediaSession::Controller::LocalRef(
jni::GetGeckoThreadEnv(),
java::MediaSession::Controller::Ref::From(aController));
window.mMediaSessionSupport.Attach(controller, &window, controller);
RefPtr<BrowsingContext> bc = BrowsingContext::Get(aId);
RefPtr<dom::MediaController> nativeController =
bc->Canonical()->GetMediaController();
MOZ_ASSERT(nativeController && nativeController->Id() == aId);
window.mMediaSessionSupport->SetNativeController(nativeController);
DispatchToUiThread("GeckoViewSupport::AttachMediaSessionController",
[controller = java::MediaSession::Controller::GlobalRef(
controller)] { controller->OnAttached(); });
}
void nsWindow::GeckoViewSupport::DetachMediaSessionController(
const GeckoSession::Window::LocalRef& inst,
jni::Object::Param aController) {
if (window.mMediaSessionSupport) {
window.mMediaSessionSupport.Detach(
window.mMediaSessionSupport->GetJavaController());
}
}
void nsWindow::InitNatives() {
jni::InitConversionStatics();
nsWindow::GeckoViewSupport::Base::Init();
nsWindow::LayerViewSupport::Init();
nsWindow::NPZCSupport::Init();
nsWindow::MediaSessionSupport::Init();
GeckoEditableSupport::Init();
a11y::SessionAccessibility::Init();

View file

@ -192,9 +192,6 @@ class nsWindow final : public nsBaseWidget {
// Strong referenced by the Java instance.
NativePtr<mozilla::a11y::SessionAccessibility> mSessionAccessibility;
class MediaSessionSupport;
NativePtr<MediaSessionSupport> mMediaSessionSupport;
class GeckoViewSupport;
// Object that implements native GeckoView calls and associated states.
// nullptr for nsWindows that were not opened from GeckoView.