Route the browser tab mute through the parent-process MediaController so the tab mute state lives in one place instead of a separate browser-level flag. Differential Revision: https://phabricator.services.mozilla.com/D291668
266 lines
7.1 KiB
JavaScript
266 lines
7.1 KiB
JavaScript
/* 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/. */
|
|
|
|
import { GeckoViewModule } from "resource://gre/modules/GeckoViewModule.sys.mjs";
|
|
|
|
export class GeckoViewMediaControl extends GeckoViewModule {
|
|
onInit() {
|
|
debug`onInit`;
|
|
}
|
|
|
|
onInitBrowser() {
|
|
debug`onInitBrowser`;
|
|
|
|
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);
|
|
this.controller.addEventListener("metadatachange", this, options);
|
|
this.controller.addEventListener("playbackstatechange", this, options);
|
|
this.controller.addEventListener(
|
|
"effectiveaudiosessiontypechange",
|
|
this,
|
|
options
|
|
);
|
|
}
|
|
|
|
onDestroyBrowser() {
|
|
debug`onDestroyBrowser`;
|
|
|
|
this.controller.removeEventListener("activated", this);
|
|
this.controller.removeEventListener("deactivated", this);
|
|
this.controller.removeEventListener("supportedkeyschange", this);
|
|
this.controller.removeEventListener("positionstatechange", this);
|
|
this.controller.removeEventListener("metadatachange", this);
|
|
this.controller.removeEventListener("playbackstatechange", this);
|
|
this.controller.removeEventListener(
|
|
"effectiveaudiosessiontypechange",
|
|
this
|
|
);
|
|
}
|
|
|
|
onEnable() {
|
|
debug`onEnable`;
|
|
|
|
if (this.controller.isActive) {
|
|
this.handleActivated();
|
|
}
|
|
|
|
this.registerListener([
|
|
"GeckoView:MediaSession:Play",
|
|
"GeckoView:MediaSession:Pause",
|
|
"GeckoView:MediaSession:Stop",
|
|
"GeckoView:MediaSession:NextTrack",
|
|
"GeckoView:MediaSession:PrevTrack",
|
|
"GeckoView:MediaSession:SeekForward",
|
|
"GeckoView:MediaSession:SeekBackward",
|
|
"GeckoView:MediaSession:SkipAd",
|
|
"GeckoView:MediaSession:SeekTo",
|
|
"GeckoView:MediaSession:MuteAudio",
|
|
]);
|
|
}
|
|
|
|
onDisable() {
|
|
debug`onDisable`;
|
|
|
|
this.unregisterListener();
|
|
}
|
|
|
|
get controller() {
|
|
return this.browser.browsingContext.mediaController;
|
|
}
|
|
|
|
onEvent(aEvent, aData) {
|
|
debug`onEvent: event=${aEvent}, data=${aData}`;
|
|
|
|
switch (aEvent) {
|
|
case "GeckoView:MediaSession:Play":
|
|
this.controller.play();
|
|
break;
|
|
case "GeckoView:MediaSession:Pause":
|
|
this.controller.pause("user");
|
|
break;
|
|
case "GeckoView:MediaSession:Stop":
|
|
this.controller.stop();
|
|
this.eventDispatcher.sendRequest(
|
|
"GeckoView:MediaSession:Playback:None"
|
|
);
|
|
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.controller.mute();
|
|
} else {
|
|
this.controller.unmute();
|
|
}
|
|
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;
|
|
case "metadatachange":
|
|
this.handleMetadataChanged();
|
|
break;
|
|
case "playbackstatechange":
|
|
this.handlePlaybackStateChanged();
|
|
break;
|
|
case "effectiveaudiosessiontypechange":
|
|
this.handleEffectiveAudioSessionTypeChanged();
|
|
break;
|
|
default:
|
|
warn`Unknown event type ${aEvent.type}`;
|
|
break;
|
|
}
|
|
}
|
|
|
|
handleEffectiveAudioSessionTypeChanged() {
|
|
const type = this.controller.effectiveAudioSessionType;
|
|
debug`handleEffectiveAudioSessionTypeChanged ${type}`;
|
|
|
|
// Only shape the embedder's audio focus after the audio-session type when
|
|
// the GeckoView-specific preference is on. When off, the type is never
|
|
// forwarded, so the embedder keeps its existing audio-focus behaviour
|
|
// regardless of whether the web Audio Session API is enabled.
|
|
if (
|
|
!Services.prefs.getBoolPref(
|
|
"media.geckoview.audio_session_type.enabled",
|
|
false
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
this.eventDispatcher.sendRequest(
|
|
"GeckoView:MediaSession:AudioSessionType",
|
|
{ type }
|
|
);
|
|
}
|
|
|
|
handleActivated() {
|
|
debug`handleActivated`;
|
|
|
|
this.eventDispatcher.sendRequest("GeckoView:MediaSession:Activated");
|
|
}
|
|
|
|
handleDeactivated() {
|
|
debug`handleDeactivated`;
|
|
|
|
this.eventDispatcher.sendRequest("GeckoView:MediaSession:Deactivated");
|
|
}
|
|
|
|
handlePositionStateChanged(aEvent) {
|
|
debug`handlePositionStateChanged`;
|
|
|
|
this.eventDispatcher.sendRequest("GeckoView:MediaSession:PositionState", {
|
|
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("GeckoView:MediaSession:Features", {
|
|
features,
|
|
});
|
|
}
|
|
|
|
handleMetadataChanged() {
|
|
let metadata = null;
|
|
try {
|
|
metadata = this.controller.getMetadata();
|
|
} catch (e) {
|
|
warn`Metadata not available`;
|
|
}
|
|
debug`handleMetadataChanged ${metadata}`;
|
|
|
|
if (metadata) {
|
|
this.eventDispatcher.sendRequest("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 = this.controller.isAnyMediaBeingControlled
|
|
? "GeckoView:MediaSession:Playback:Paused"
|
|
: "GeckoView:MediaSession:Playback:None";
|
|
break;
|
|
case "playing":
|
|
type = "GeckoView:MediaSession:Playback:Playing";
|
|
break;
|
|
}
|
|
|
|
if (!type) {
|
|
return;
|
|
}
|
|
|
|
this.eventDispatcher.sendRequest(type);
|
|
}
|
|
}
|
|
|
|
const { debug, warn } = GeckoViewMediaControl.initLogging(
|
|
"GeckoViewMediaControl"
|
|
);
|