Dactyloidae/browser/base/content/pictureInPicture.js

176 lines
6 KiB
JavaScript
Raw Normal View History

2026-09-13 10:51:36 -07:00
/* 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";
// The browser XBL bindings resolve these names in their owning window.
const {utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
2026-09-13 10:51:36 -07:00
var Player = {
browser: null,
source: null,
sourceTab: null,
2026-09-13 12:18:07 -07:00
seeking: false,
state: null,
2026-09-13 10:51:36 -07:00
init() {
2026-09-13 11:52:14 -07:00
if (this.browser) {
return;
}
2026-09-13 10:51:36 -07:00
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;
2026-09-13 11:52:14 -07:00
let initialized = false;
let ready = () => {
if (initialized) {
return;
}
initialized = true;
mm.removeMessageListener("PictureInPicture:Ready", ready);
2026-09-13 10:51:36 -07:00
mm.sendAsyncMessage("PictureInPicture:Init", {id: args.id});
2026-09-13 11:52:14 -07:00
};
mm.addMessageListener("PictureInPicture:Ready", ready);
2026-09-13 10:51:36 -07:00
mm.addMessageListener("PictureInPicture:Close", this.close);
mm.addMessageListener("PictureInPicture:State", message => {
2026-09-13 12:18:07 -07:00
this.updateState(message.data);
2026-09-13 10:51:36 -07:00
});
browser.addEventListener("oop-browser-crashed", this.close);
mm.loadFrameScript("chrome://browser/content/pictureInPictureContent.js", false);
},
2026-09-13 12:18:07 -07:00
command(command, data = {}) {
2026-09-13 10:51:36 -07:00
if (this.browser) {
2026-09-13 12:18:07 -07:00
this.browser.messageManager.sendAsyncMessage("PictureInPicture:Command",
Object.assign({command}, data));
2026-09-13 10:51:36 -07:00
}
},
2026-09-13 12:18:07 -07:00
formatTime(seconds) {
if (!Number.isFinite(seconds) || seconds < 0) {
return "0:00";
}
seconds = Math.floor(seconds);
let hours = Math.floor(seconds / 3600);
let minutes = Math.floor(seconds / 60) % 60;
let pad = value => value < 10 ? "0" + value : String(value);
return (hours ? hours + ":" + pad(minutes) : String(minutes)) +
":" + pad(seconds % 60);
},
updateTime(position) {
let duration = this.state && this.state.duration;
let total = Number.isFinite(duration) ? this.formatTime(duration) :
document.getElementById("live-label").value;
document.getElementById("time").value = this.formatTime(position) + " / " + total;
},
updateState(state) {
this.state = state;
document.getElementById("play").label = state.paused ?
this.playLabel : document.getElementById("pause-label").value;
document.getElementById("mute").label = state.muted ?
document.getElementById("unmute-label").value : this.muteLabel;
if (!this.seeking) {
let seek = document.getElementById("seek");
seek.disabled = !(state.seekableEnd > state.seekableStart);
seek.min = state.seekableStart;
seek.max = state.seekableEnd || 1;
seek.value = state.currentTime;
this.updateTime(state.currentTime);
}
},
seek(value) {
let position = Number(value);
if (!Number.isFinite(position) || document.getElementById("seek").disabled) {
return;
}
this.seeking = true;
document.getElementById("player-overlay").setAttribute("seeking", "true");
this.updateTime(position);
this.command("seek", {time: position});
},
finishSeeking() {
this.seeking = false;
document.getElementById("player-overlay").removeAttribute("seeking");
},
2026-09-13 10:51:36 -07:00
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;
},
};
2026-09-13 11:52:14 -07:00
// Only consume the listener for the player document's own load, rather than
// a load from the embedded browser.
window.addEventListener("load", function onLoad(event) {
if (event.target == document) {
window.removeEventListener("load", onLoad);
Player.init();
}
});
window.addEventListener("unload", function onUnload(event) {
if (event.target == document) {
window.removeEventListener("unload", onUnload);
Player.destroy();
}
});
2026-09-13 10:51:36 -07:00
window.addEventListener("keydown", event => {
2026-09-13 12:18:07 -07:00
document.getElementById("player-overlay").setAttribute("keyboard", "true");
2026-09-13 10:51:36 -07:00
if (event.key == "Escape") {
window.close();
2026-09-13 12:18:07 -07:00
} else if (event.target.localName == "input" || event.target.localName == "button") {
return;
} else if (event.key == " ") {
2026-09-13 10:51:36 -07:00
event.preventDefault();
Player.command("playpause");
2026-09-13 12:18:07 -07:00
} else if (event.key == "ArrowLeft" || event.key == "ArrowRight") {
event.preventDefault();
if (Player.state) {
Player.command("seek", {time: Player.state.currentTime +
(event.key == "ArrowLeft" ? -5 : 5)});
}
2026-09-13 10:51:36 -07:00
}
});
2026-09-13 12:18:07 -07:00
window.addEventListener("mousemove", () => {
document.getElementById("player-overlay").removeAttribute("keyboard");
});