mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-20 23:37:33 +09:00
PiP initial commit
This commit is contained in:
parent
5ec3ae4a4c
commit
9a1d4edaa9
14 changed files with 377 additions and 1 deletions
|
|
@ -159,6 +159,9 @@
|
||||||
label="&mediaHideControls.label;"
|
label="&mediaHideControls.label;"
|
||||||
accesskey="&mediaHideControls.accesskey;"
|
accesskey="&mediaHideControls.accesskey;"
|
||||||
oncommand="gContextMenu.mediaCommand('hidecontrols');"/>
|
oncommand="gContextMenu.mediaCommand('hidecontrols');"/>
|
||||||
|
<menuitem id="context-video-pictureinpicture"
|
||||||
|
label="&pictureInPicture.title;"
|
||||||
|
oncommand="gContextMenu.pictureInPicture();"/>
|
||||||
<menuitem id="context-video-fullscreen"
|
<menuitem id="context-video-fullscreen"
|
||||||
accesskey="&videoFullScreen.accesskey;"
|
accesskey="&videoFullScreen.accesskey;"
|
||||||
label="&videoFullScreen.label;"
|
label="&videoFullScreen.label;"
|
||||||
|
|
|
||||||
|
|
@ -681,6 +681,17 @@ addMessageListener("ContextMenu:MediaCommand", (message) => {
|
||||||
() => {
|
() => {
|
||||||
let media = message.objects.element;
|
let media = message.objects.element;
|
||||||
switch (message.data.command) {
|
switch (message.data.command) {
|
||||||
|
case "pictureinpicture": {
|
||||||
|
let {PictureInPicture} = Cu.import("resource:///modules/PictureInPicture.jsm", {});
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = PictureInPicture.register(media, message.data.data);
|
||||||
|
} catch (error) {
|
||||||
|
data = {id: message.data.data, error: error.message};
|
||||||
|
}
|
||||||
|
sendAsyncMessage("PictureInPicture:Prepared", data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "play":
|
case "play":
|
||||||
media.play();
|
media.play();
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -442,6 +442,11 @@ nsContextMenu.prototype = {
|
||||||
this.showItem("context-media-loop", onMedia);
|
this.showItem("context-media-loop", onMedia);
|
||||||
this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
|
this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
|
||||||
this.showItem("context-media-hidecontrols", this.target.controls && (this.onVideo || (this.onAudio && !this.inSyntheticDoc)));
|
this.showItem("context-media-hidecontrols", this.target.controls && (this.onVideo || (this.onAudio && !this.inSyntheticDoc)));
|
||||||
|
this.showItem("context-video-pictureinpicture", this.onVideo);
|
||||||
|
this.setItemAttr("context-video-pictureinpicture", "disabled",
|
||||||
|
!this.onVideo || this.target.error ||
|
||||||
|
!this.target.videoWidth || !this.target.videoHeight ||
|
||||||
|
!!this.target.mediaKeys);
|
||||||
this.showItem("context-video-fullscreen", this.onVideo && this.target.ownerDocument.fullscreenElement == null);
|
this.showItem("context-video-fullscreen", this.onVideo && this.target.ownerDocument.fullscreenElement == null);
|
||||||
this.showItem("context-media-eme-learnmore", this.onDRMMedia);
|
this.showItem("context-media-eme-learnmore", this.onDRMMedia);
|
||||||
this.showItem("context-media-eme-separator", this.onDRMMedia);
|
this.showItem("context-media-eme-separator", this.onDRMMedia);
|
||||||
|
|
@ -1682,6 +1687,11 @@ nsContextMenu.prototype = {
|
||||||
this.browser.messageManager.sendAsyncMessage("SwitchDocumentDirection");
|
this.browser.messageManager.sendAsyncMessage("SwitchDocumentDirection");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pictureInPicture: function() {
|
||||||
|
let {PictureInPicture} = Cu.import("resource:///modules/PictureInPicture.jsm", {});
|
||||||
|
PictureInPicture.open(this.browser, this.target);
|
||||||
|
},
|
||||||
|
|
||||||
mediaCommand : function(command, data) {
|
mediaCommand : function(command, data) {
|
||||||
let mm = this.browser.messageManager;
|
let mm = this.browser.messageManager;
|
||||||
let win = this.browser.ownerGlobal;
|
let win = this.browser.ownerGlobal;
|
||||||
|
|
|
||||||
87
browser/base/content/pictureInPicture.js
Normal file
87
browser/base/content/pictureInPicture.js
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
/* 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 Player = {
|
||||||
|
browser: null,
|
||||||
|
source: null,
|
||||||
|
sourceTab: null,
|
||||||
|
|
||||||
|
init() {
|
||||||
|
let args = window.arguments[0];
|
||||||
|
this.source = args.browser;
|
||||||
|
if (!this.source || !this.source.isConnected) {
|
||||||
|
window.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.sourceTab = this.source.ownerGlobal.gBrowser.getTabForBrowser(this.source);
|
||||||
|
this.close = () => window.close();
|
||||||
|
this.sourceTab.addEventListener("TabClose", this.close);
|
||||||
|
this.source.addEventListener("oop-browser-crashed", this.close);
|
||||||
|
|
||||||
|
this.playLabel = document.getElementById("play").label;
|
||||||
|
this.muteLabel = document.getElementById("mute").label;
|
||||||
|
let browser = this.browser = document.createElement("browser");
|
||||||
|
browser.setAttribute("type", "content");
|
||||||
|
browser.setAttribute("flex", "1");
|
||||||
|
browser.setAttribute("disablehistory", "true");
|
||||||
|
browser.setAttribute("remote", this.source.isRemoteBrowser ? "true" : "false");
|
||||||
|
if (this.source.isRemoteBrowser) {
|
||||||
|
browser.setAttribute("remoteType", this.source.getAttribute("remoteType"));
|
||||||
|
}
|
||||||
|
// The process-local source reference requires this process affinity.
|
||||||
|
browser.relatedBrowser = this.source;
|
||||||
|
document.getElementById("player-container").appendChild(browser);
|
||||||
|
let mm = browser.messageManager;
|
||||||
|
mm.addMessageListener("PictureInPicture:Ready", () => {
|
||||||
|
mm.sendAsyncMessage("PictureInPicture:Init", {id: args.id});
|
||||||
|
});
|
||||||
|
mm.addMessageListener("PictureInPicture:Close", this.close);
|
||||||
|
mm.addMessageListener("PictureInPicture:State", message => {
|
||||||
|
document.getElementById("play").label = message.data.paused ?
|
||||||
|
this.playLabel : document.getElementById("pause-label").value;
|
||||||
|
document.getElementById("mute").label = message.data.muted ?
|
||||||
|
document.getElementById("unmute-label").value : this.muteLabel;
|
||||||
|
});
|
||||||
|
browser.addEventListener("oop-browser-crashed", this.close);
|
||||||
|
mm.loadFrameScript("chrome://browser/content/pictureInPictureContent.js", false);
|
||||||
|
},
|
||||||
|
|
||||||
|
command(command) {
|
||||||
|
if (this.browser) {
|
||||||
|
this.browser.messageManager.sendAsyncMessage("PictureInPicture:Command", {command});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
returnToTab() {
|
||||||
|
if (this.source && this.source.isConnected) {
|
||||||
|
let win = this.source.ownerGlobal;
|
||||||
|
win.gBrowser.selectedTab = this.sourceTab;
|
||||||
|
win.focus();
|
||||||
|
}
|
||||||
|
window.close();
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this.sourceTab) {
|
||||||
|
this.sourceTab.removeEventListener("TabClose", this.close);
|
||||||
|
this.source.removeEventListener("oop-browser-crashed", this.close);
|
||||||
|
}
|
||||||
|
if (this.browser) {
|
||||||
|
this.command("close");
|
||||||
|
}
|
||||||
|
this.source = this.sourceTab = null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("load", () => Player.init(), {once: true});
|
||||||
|
window.addEventListener("unload", () => Player.destroy(), {once: true});
|
||||||
|
window.addEventListener("keydown", event => {
|
||||||
|
if (event.key == "Escape") {
|
||||||
|
window.close();
|
||||||
|
} else if (event.key == " " && event.target.localName != "button") {
|
||||||
|
event.preventDefault();
|
||||||
|
Player.command("playpause");
|
||||||
|
}
|
||||||
|
});
|
||||||
21
browser/base/content/pictureInPicture.xul
Normal file
21
browser/base/content/pictureInPicture.xul
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.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/. -->
|
||||||
|
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||||
|
<!DOCTYPE window SYSTEM "chrome://browser/locale/browser.dtd">
|
||||||
|
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||||
|
id="picture-in-picture" windowtype="Browser:PictureInPicture"
|
||||||
|
title="&pictureInPicture.title;" width="480" height="318"
|
||||||
|
persist="screenX screenY width height" style="min-width: 240px; min-height: 160px;">
|
||||||
|
<script type="application/javascript" src="chrome://browser/content/pictureInPicture.js"/>
|
||||||
|
<vbox id="player-container" flex="1" style="background: black;"/>
|
||||||
|
<hbox align="center" pack="center">
|
||||||
|
<button id="play" label="&mediaPlay.label;" oncommand="Player.command('playpause');"/>
|
||||||
|
<button id="mute" label="&mediaMute.label;" oncommand="Player.command('mute');"/>
|
||||||
|
<button id="return" label="&pictureInPicture.return;" oncommand="Player.returnToTab();"/>
|
||||||
|
<button id="close" label="&pictureInPicture.close;" oncommand="window.close();"/>
|
||||||
|
</hbox>
|
||||||
|
<label id="pause-label" value="&mediaPause.label;" hidden="true"/>
|
||||||
|
<label id="unmute-label" value="&mediaUnmute.label;" hidden="true"/>
|
||||||
|
</window>
|
||||||
129
browser/base/content/pictureInPictureContent.js
Normal file
129
browser/base/content/pictureInPictureContent.js
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
/* 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 {PictureInPicture} = Components.utils.import(
|
||||||
|
"resource:///modules/PictureInPicture.jsm", {});
|
||||||
|
var source = null;
|
||||||
|
var stream = null;
|
||||||
|
var output = null;
|
||||||
|
var still = null;
|
||||||
|
var observer = null;
|
||||||
|
var sourceWindow = null;
|
||||||
|
var events = ["play", "pause", "volumechange", "ended", "seeked", "resize"];
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
if (!source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Capture streams do not produce frames while their source is paused.
|
||||||
|
// Keep a snapshot for that case, without changing the source's play state.
|
||||||
|
still.hidden = !source.paused && !source.ended;
|
||||||
|
if (!still.hidden && source.readyState >= 2) {
|
||||||
|
still.width = source.videoWidth;
|
||||||
|
still.height = source.videoHeight;
|
||||||
|
still.getContext("2d").drawImage(source, 0, 0, still.width, still.height);
|
||||||
|
}
|
||||||
|
sendAsyncMessage("PictureInPicture:State",
|
||||||
|
{paused: source.paused || source.ended, muted: source.muted});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
if (observer) {
|
||||||
|
observer.disconnect();
|
||||||
|
observer = null;
|
||||||
|
}
|
||||||
|
if (source) {
|
||||||
|
for (let name of events) {
|
||||||
|
source.removeEventListener(name, update);
|
||||||
|
}
|
||||||
|
source.removeEventListener("emptied", closePlayer);
|
||||||
|
source.removeEventListener("error", closePlayer);
|
||||||
|
sourceWindow.removeEventListener("pagehide", closePlayer);
|
||||||
|
source.mozPictureInPicture = false;
|
||||||
|
source = sourceWindow = null;
|
||||||
|
}
|
||||||
|
if (output) {
|
||||||
|
output.srcObject = null;
|
||||||
|
output = null;
|
||||||
|
}
|
||||||
|
if (stream) {
|
||||||
|
for (let track of stream.getTracks()) {
|
||||||
|
track.stop();
|
||||||
|
}
|
||||||
|
stream = null;
|
||||||
|
}
|
||||||
|
still = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePlayer() {
|
||||||
|
cleanup();
|
||||||
|
sendAsyncMessage("PictureInPicture:Close");
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessageListener("PictureInPicture:Init", message => {
|
||||||
|
try {
|
||||||
|
source = PictureInPicture.takeSource(message.data.id);
|
||||||
|
sourceWindow = source.ownerGlobal;
|
||||||
|
let doc = content.document;
|
||||||
|
doc.documentElement.style.cssText = "height:100%;background:black";
|
||||||
|
doc.body.style.cssText = "margin:0;height:100%;overflow:hidden";
|
||||||
|
output = doc.createElement("video");
|
||||||
|
still = doc.createElement("canvas");
|
||||||
|
for (let element of [output, still]) {
|
||||||
|
element.style.cssText = "position:absolute;width:100%;height:100%;object-fit:contain";
|
||||||
|
doc.body.appendChild(element);
|
||||||
|
}
|
||||||
|
output.muted = true; // Audio continues to come from the originating video.
|
||||||
|
source.mozPictureInPicture = true;
|
||||||
|
stream = source.mozCaptureStream();
|
||||||
|
output.srcObject = stream;
|
||||||
|
output.play().catch(error => {
|
||||||
|
Components.utils.reportError(error);
|
||||||
|
closePlayer();
|
||||||
|
});
|
||||||
|
for (let name of events) {
|
||||||
|
source.addEventListener(name, update);
|
||||||
|
}
|
||||||
|
source.addEventListener("emptied", closePlayer);
|
||||||
|
source.addEventListener("error", closePlayer);
|
||||||
|
sourceWindow.addEventListener("pagehide", closePlayer);
|
||||||
|
observer = new sourceWindow.MutationObserver(() => {
|
||||||
|
if (source && !source.isConnected) {
|
||||||
|
closePlayer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observer.observe(source.ownerDocument, {childList: true, subtree: true});
|
||||||
|
update();
|
||||||
|
} catch (error) {
|
||||||
|
Components.utils.reportError(error);
|
||||||
|
closePlayer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addMessageListener("PictureInPicture:Command", message => {
|
||||||
|
if (!source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (message.data.command) {
|
||||||
|
case "playpause":
|
||||||
|
if (source.paused || source.ended) {
|
||||||
|
source.play().catch(Components.utils.reportError);
|
||||||
|
} else {
|
||||||
|
source.pause();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "mute":
|
||||||
|
source.muted = !source.muted;
|
||||||
|
break;
|
||||||
|
case "close":
|
||||||
|
cleanup();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Frame-script unload also covers a parent window closing before an async
|
||||||
|
// close command can be delivered.
|
||||||
|
addEventListener("unload", cleanup);
|
||||||
|
sendAsyncMessage("PictureInPicture:Ready");
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# 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/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
browser.jar:
|
browser.jar:
|
||||||
|
content/browser/pictureInPicture.xul (content/pictureInPicture.xul)
|
||||||
|
content/browser/pictureInPicture.js (content/pictureInPicture.js)
|
||||||
|
content/browser/pictureInPictureContent.js (content/pictureInPictureContent.js)
|
||||||
% content browser %content/browser/ contentaccessible=yes
|
% content browser %content/browser/ contentaccessible=yes
|
||||||
#ifdef XP_MACOSX
|
#ifdef XP_MACOSX
|
||||||
% overlay chrome://mozapps/content/downloads/downloads.xul chrome://browser/content/downloadManagerOverlay.xul
|
% overlay chrome://mozapps/content/downloads/downloads.xul chrome://browser/content/downloadManagerOverlay.xul
|
||||||
|
|
|
||||||
|
|
@ -777,3 +777,7 @@ you can use these alternative items. Otherwise, their values should be empty. -
|
||||||
|
|
||||||
<!ENTITY emeLearnMoreContextMenu.label "Learn more about DRM…">
|
<!ENTITY emeLearnMoreContextMenu.label "Learn more about DRM…">
|
||||||
<!ENTITY emeLearnMoreContextMenu.accesskey "D">
|
<!ENTITY emeLearnMoreContextMenu.accesskey "D">
|
||||||
|
|
||||||
|
<!ENTITY pictureInPicture.title "Picture-in-Picture">
|
||||||
|
<!ENTITY pictureInPicture.return "Return to tab">
|
||||||
|
<!ENTITY pictureInPicture.close "Close">
|
||||||
|
|
|
||||||
84
browser/modules/PictureInPicture.jsm
Normal file
84
browser/modules/PictureInPicture.jsm
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
/* 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";
|
||||||
|
|
||||||
|
this.EXPORTED_SYMBOLS = ["PictureInPicture"];
|
||||||
|
|
||||||
|
const {utils: Cu} = Components;
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
Cu.import("resource://gre/modules/Timer.jsm");
|
||||||
|
|
||||||
|
// Each content process has its own module instance. Only a weak reference to
|
||||||
|
// the source crosses between frame scripts; DOM objects never cross processes.
|
||||||
|
this.PictureInPicture = {
|
||||||
|
source: null,
|
||||||
|
window: null,
|
||||||
|
request: 0,
|
||||||
|
|
||||||
|
register(video, id) {
|
||||||
|
if (!video || video.localName != "video" || video.error ||
|
||||||
|
!video.videoWidth || !video.videoHeight || video.mediaKeys) {
|
||||||
|
throw new Error("This video is not available for Picture-in-Picture");
|
||||||
|
}
|
||||||
|
this.source = {id, video: Cu.getWeakReference(video)};
|
||||||
|
return {id, width: video.videoWidth, height: video.videoHeight};
|
||||||
|
},
|
||||||
|
|
||||||
|
takeSource(id) {
|
||||||
|
if (!this.source || this.source.id != id) {
|
||||||
|
throw new Error("Picture-in-Picture source process is unavailable");
|
||||||
|
}
|
||||||
|
let video = this.source.video.get();
|
||||||
|
this.source = null;
|
||||||
|
if (!video || !video.isConnected) {
|
||||||
|
throw new Error("Picture-in-Picture source has been removed");
|
||||||
|
}
|
||||||
|
return video;
|
||||||
|
},
|
||||||
|
|
||||||
|
open(browser, video) {
|
||||||
|
let id = Services.uuid.generateUUID().toString();
|
||||||
|
let request = ++this.request;
|
||||||
|
let mm = browser.messageManager;
|
||||||
|
let timer;
|
||||||
|
let ready = message => {
|
||||||
|
if (message.data.id != id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mm.removeMessageListener("PictureInPicture:Prepared", ready);
|
||||||
|
clearTimeout(timer);
|
||||||
|
if (request != this.request || !browser.isConnected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message.data.error) {
|
||||||
|
Cu.reportError(message.data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.openWindow(browser, message.data);
|
||||||
|
};
|
||||||
|
mm.addMessageListener("PictureInPicture:Prepared", ready);
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
mm.removeMessageListener("PictureInPicture:Prepared", ready);
|
||||||
|
}, 10000);
|
||||||
|
mm.sendAsyncMessage("ContextMenu:MediaCommand",
|
||||||
|
{command: "pictureinpicture", data: id},
|
||||||
|
{element: video});
|
||||||
|
},
|
||||||
|
|
||||||
|
openWindow(browser, data) {
|
||||||
|
if (this.window && !this.window.closed) {
|
||||||
|
this.window.close();
|
||||||
|
}
|
||||||
|
let screen = browser.ownerGlobal.screen;
|
||||||
|
let width = Math.min(480, screen.availWidth);
|
||||||
|
let height = Math.min(Math.round(width * data.height / data.width) + 48,
|
||||||
|
Math.round(screen.availHeight * 0.8));
|
||||||
|
this.window = browser.ownerGlobal.openDialog(
|
||||||
|
"chrome://browser/content/pictureInPicture.xul", "",
|
||||||
|
"chrome,dialog=no,resizable,alwaysRaised,width=" + width +
|
||||||
|
",height=" + height,
|
||||||
|
{browser, id: data.id});
|
||||||
|
return this.window;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -22,6 +22,7 @@ EXTRA_JS_MODULES += [
|
||||||
'NetworkPrioritizer.jsm',
|
'NetworkPrioritizer.jsm',
|
||||||
'offlineAppCache.jsm',
|
'offlineAppCache.jsm',
|
||||||
'PermissionUI.jsm',
|
'PermissionUI.jsm',
|
||||||
|
'PictureInPicture.jsm',
|
||||||
'PluginContent.jsm',
|
'PluginContent.jsm',
|
||||||
'ProcessHangMonitor.jsm',
|
'ProcessHangMonitor.jsm',
|
||||||
'ReaderParent.jsm',
|
'ReaderParent.jsm',
|
||||||
|
|
|
||||||
|
|
@ -5183,6 +5183,15 @@ bool HTMLMediaElement::IsActive() const
|
||||||
return ownerDoc && ownerDoc->IsActive() && ownerDoc->IsVisible();
|
return ownerDoc && ownerDoc->IsActive() && ownerDoc->IsVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HTMLMediaElement::SetMozPictureInPicture(bool aEnabled)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
mPictureInPicture = aEnabled;
|
||||||
|
if (mDecoder && !mDecoder->IsShutdown()) {
|
||||||
|
mDecoder->NotifyOwnerActivityChanged(!IsHidden());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool HTMLMediaElement::IsHidden() const
|
bool HTMLMediaElement::IsHidden() const
|
||||||
{
|
{
|
||||||
nsIDocument* ownerDoc;
|
nsIDocument* ownerDoc;
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,9 @@ public:
|
||||||
|
|
||||||
virtual bool IsHidden() const final override;
|
virtual bool IsHidden() const final override;
|
||||||
|
|
||||||
|
bool MozPictureInPicture() const { return mPictureInPicture; }
|
||||||
|
void SetMozPictureInPicture(bool aEnabled);
|
||||||
|
|
||||||
// Called by the media decoder and the video frame to get the
|
// Called by the media decoder and the video frame to get the
|
||||||
// ImageContainer containing the video data.
|
// ImageContainer containing the video data.
|
||||||
virtual VideoFrameContainer* GetVideoFrameContainer() final override;
|
virtual VideoFrameContainer* GetVideoFrameContainer() final override;
|
||||||
|
|
@ -1622,6 +1625,9 @@ protected:
|
||||||
// Info about the played media.
|
// Info about the played media.
|
||||||
MediaInfo mMediaInfo;
|
MediaInfo mMediaInfo;
|
||||||
|
|
||||||
|
// Set only by privileged Picture-in-Picture UI; cleared when it closes.
|
||||||
|
bool mPictureInPicture = false;
|
||||||
|
|
||||||
// True if the media has encryption information.
|
// True if the media has encryption information.
|
||||||
bool mIsEncrypted;
|
bool mIsEncrypted;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1250,7 +1250,11 @@ MediaDecoder::SetElementVisibility(bool aIsVisible)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
mElementVisible = aIsVisible;
|
mElementVisible = aIsVisible;
|
||||||
mIsVisible = !mForcedHidden && mElementVisible;
|
// A floating player remains visible when its source tab is in the
|
||||||
|
// background or the original video is scrolled out of view.
|
||||||
|
HTMLMediaElement* element = mOwner ? mOwner->GetMediaElement() : nullptr;
|
||||||
|
mIsVisible = (element && element->MozPictureInPicture()) ||
|
||||||
|
(!mForcedHidden && mElementVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,10 @@ partial interface HTMLVideoElement {
|
||||||
// True if the video has an audio track available.
|
// True if the video has an audio track available.
|
||||||
readonly attribute boolean mozHasAudio;
|
readonly attribute boolean mozHasAudio;
|
||||||
|
|
||||||
|
// Browser UI only: keep the source decoder active for a floating player.
|
||||||
|
[ChromeOnly]
|
||||||
|
attribute boolean mozPictureInPicture;
|
||||||
|
|
||||||
// True if the video should use a screen wake lock.
|
// True if the video should use a screen wake lock.
|
||||||
[Pref="dom.wakelock.enabled", Func="Navigator::HasWakeLockSupport"]
|
[Pref="dom.wakelock.enabled", Func="Navigator::HasWakeLockSupport"]
|
||||||
attribute boolean mozUseScreenWakeLock;
|
attribute boolean mozUseScreenWakeLock;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue