mirror of
https://github.com/imgfloat/server.git
synced 2026-02-05 03:39:26 +00:00
Segregate script canvas context
This commit is contained in:
@@ -1120,10 +1120,22 @@ button:disabled:hover {
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#broadcast-script-canvas {
|
.broadcast-script-layer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.broadcast-script-layer canvas {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.broadcast-body {
|
.broadcast-body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { BroadcastRenderer } from "./broadcast/renderer.js";
|
import { BroadcastRenderer } from "./broadcast/renderer.js";
|
||||||
|
|
||||||
const canvas = document.getElementById("broadcast-canvas");
|
const canvas = document.getElementById("broadcast-canvas");
|
||||||
const scriptCanvas = document.getElementById("broadcast-script-canvas");
|
const scriptLayer = document.getElementById("broadcast-script-layer");
|
||||||
const renderer = new BroadcastRenderer({ canvas, scriptCanvas, broadcaster, showToast });
|
const renderer = new BroadcastRenderer({ canvas, scriptLayer, broadcaster, showToast });
|
||||||
|
|
||||||
renderer.start();
|
renderer.start();
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import { createMediaManager } from "./mediaManager.js";
|
|||||||
import { createModelManager } from "../media/modelManager.js";
|
import { createModelManager } from "../media/modelManager.js";
|
||||||
|
|
||||||
export class BroadcastRenderer {
|
export class BroadcastRenderer {
|
||||||
constructor({ canvas, scriptCanvas, broadcaster, showToast }) {
|
constructor({ canvas, scriptLayer, broadcaster, showToast }) {
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
this.ctx = canvas.getContext("2d");
|
this.ctx = canvas.getContext("2d");
|
||||||
this.scriptCanvas = scriptCanvas;
|
this.scriptLayer = scriptLayer;
|
||||||
|
this.scriptCanvases = new Map();
|
||||||
this.broadcaster = broadcaster;
|
this.broadcaster = broadcaster;
|
||||||
this.showToast = showToast;
|
this.showToast = showToast;
|
||||||
this.state = createBroadcastState();
|
this.state = createBroadcastState();
|
||||||
@@ -146,13 +147,12 @@ export class BroadcastRenderer {
|
|||||||
this.canvas.height = this.state.canvasSettings.height;
|
this.canvas.height = this.state.canvasSettings.height;
|
||||||
this.canvas.style.width = `${this.state.canvasSettings.width}px`;
|
this.canvas.style.width = `${this.state.canvasSettings.width}px`;
|
||||||
this.canvas.style.height = `${this.state.canvasSettings.height}px`;
|
this.canvas.style.height = `${this.state.canvasSettings.height}px`;
|
||||||
if (this.scriptCanvas) {
|
if (this.scriptLayer) {
|
||||||
this.scriptCanvas.width = this.state.canvasSettings.width;
|
this.scriptLayer.style.width = `${this.state.canvasSettings.width}px`;
|
||||||
this.scriptCanvas.height = this.state.canvasSettings.height;
|
this.scriptLayer.style.height = `${this.state.canvasSettings.height}px`;
|
||||||
this.scriptCanvas.style.width = `${this.state.canvasSettings.width}px`;
|
|
||||||
this.scriptCanvas.style.height = `${this.state.canvasSettings.height}px`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.resizeScriptCanvases();
|
||||||
this.updateScriptWorkerCanvas();
|
this.updateScriptWorkerCanvas();
|
||||||
this.draw();
|
this.draw();
|
||||||
}
|
}
|
||||||
@@ -395,40 +395,31 @@ export class BroadcastRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ensureScriptWorker() {
|
ensureScriptWorker() {
|
||||||
if (this.scriptWorker || !this.scriptCanvas) {
|
if (this.scriptWorker) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof this.scriptCanvas.transferControlToOffscreen !== "function") {
|
|
||||||
console.warn("OffscreenCanvas is not supported in this environment.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const offscreen = this.scriptCanvas.transferControlToOffscreen();
|
|
||||||
this.scriptWorker = new Worker("/js/broadcast/script-worker.js");
|
this.scriptWorker = new Worker("/js/broadcast/script-worker.js");
|
||||||
this.scriptWorker.addEventListener("message", (event) => this.handleScriptWorkerMessage(event));
|
this.scriptWorker.addEventListener("message", (event) => this.handleScriptWorkerMessage(event));
|
||||||
this.scriptWorker.postMessage(
|
this.scriptWorker.postMessage(
|
||||||
{
|
{
|
||||||
type: "init",
|
type: "init",
|
||||||
payload: {
|
payload: {
|
||||||
canvas: offscreen,
|
|
||||||
width: this.scriptCanvas.width,
|
|
||||||
height: this.scriptCanvas.height,
|
|
||||||
channelName: this.broadcaster,
|
channelName: this.broadcaster,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[offscreen],
|
|
||||||
);
|
);
|
||||||
this.scriptWorkerReady = true;
|
this.scriptWorkerReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateScriptWorkerCanvas() {
|
updateScriptWorkerCanvas() {
|
||||||
if (!this.scriptWorker || !this.scriptWorkerReady || !this.scriptCanvas) {
|
if (!this.scriptWorker || !this.scriptWorkerReady) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.scriptWorker.postMessage({
|
this.scriptWorker.postMessage({
|
||||||
type: "resize",
|
type: "resize",
|
||||||
payload: {
|
payload: {
|
||||||
width: this.scriptCanvas.width,
|
width: this.state.canvasSettings.width,
|
||||||
height: this.scriptCanvas.height,
|
height: this.state.canvasSettings.height,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -483,6 +474,15 @@ export class BroadcastRenderer {
|
|||||||
if (!this.scriptWorkerReady) {
|
if (!this.scriptWorkerReady) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const scriptCanvas = this.ensureScriptCanvas(asset.id);
|
||||||
|
if (!scriptCanvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof scriptCanvas.transferControlToOffscreen !== "function") {
|
||||||
|
console.warn("OffscreenCanvas is not supported in this environment.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const offscreen = scriptCanvas.transferControlToOffscreen();
|
||||||
let assetSource;
|
let assetSource;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(asset.url);
|
const response = await fetch(asset.url);
|
||||||
@@ -499,9 +499,12 @@ export class BroadcastRenderer {
|
|||||||
payload: {
|
payload: {
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
source: assetSource,
|
source: assetSource,
|
||||||
|
canvas: offscreen,
|
||||||
|
width: scriptCanvas.width,
|
||||||
|
height: scriptCanvas.height,
|
||||||
attachments: await this.resolveScriptAttachments(asset.scriptAttachments),
|
attachments: await this.resolveScriptAttachments(asset.scriptAttachments),
|
||||||
},
|
},
|
||||||
});
|
}, [offscreen]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateScriptWorkerAttachments(asset) {
|
async updateScriptWorkerAttachments(asset) {
|
||||||
@@ -525,6 +528,53 @@ export class BroadcastRenderer {
|
|||||||
type: "removeScript",
|
type: "removeScript",
|
||||||
payload: { id: assetId },
|
payload: { id: assetId },
|
||||||
});
|
});
|
||||||
|
this.removeScriptCanvas(assetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureScriptCanvas(assetId) {
|
||||||
|
if (!assetId || !this.scriptLayer) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const existing = this.scriptCanvases.get(assetId);
|
||||||
|
if (existing) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.className = "broadcast-script-canvas";
|
||||||
|
canvas.dataset.scriptId = assetId;
|
||||||
|
this.applyScriptCanvasSize(canvas);
|
||||||
|
this.scriptLayer.appendChild(canvas);
|
||||||
|
this.scriptCanvases.set(assetId, canvas);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeScriptCanvas(assetId) {
|
||||||
|
const canvas = this.scriptCanvases.get(assetId);
|
||||||
|
if (!canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
canvas.remove();
|
||||||
|
this.scriptCanvases.delete(assetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeScriptCanvases() {
|
||||||
|
this.scriptCanvases.forEach((canvas) => {
|
||||||
|
this.applyScriptCanvasSize(canvas);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
applyScriptCanvasSize(canvas) {
|
||||||
|
if (!canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Number.isFinite(this.state.canvasSettings.width)) {
|
||||||
|
canvas.width = this.state.canvasSettings.width;
|
||||||
|
canvas.style.width = `${this.state.canvasSettings.width}px`;
|
||||||
|
}
|
||||||
|
if (Number.isFinite(this.state.canvasSettings.height)) {
|
||||||
|
canvas.height = this.state.canvasSettings.height;
|
||||||
|
canvas.style.height = `${this.state.canvasSettings.height}px`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolveScriptAttachments(attachments) {
|
async resolveScriptAttachments(attachments) {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
const scripts = new Map();
|
const scripts = new Map();
|
||||||
const allowedFetchUrls = new Set();
|
const allowedFetchUrls = new Set();
|
||||||
let canvas = null;
|
|
||||||
let ctx = null;
|
|
||||||
let channelName = "";
|
let channelName = "";
|
||||||
let tickIntervalId = null;
|
let tickIntervalId = null;
|
||||||
let lastTick = 0;
|
let lastTick = 0;
|
||||||
@@ -134,11 +132,11 @@ function updateScriptContexts() {
|
|||||||
if (!script.context) {
|
if (!script.context) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
script.context.canvas = canvas;
|
script.context.canvas = script.canvas;
|
||||||
script.context.ctx = ctx;
|
script.context.ctx = script.ctx;
|
||||||
script.context.channelName = channelName;
|
script.context.channelName = channelName;
|
||||||
script.context.width = canvas?.width ?? 0;
|
script.context.width = script.canvas?.width ?? 0;
|
||||||
script.context.height = canvas?.height ?? 0;
|
script.context.height = script.canvas?.height ?? 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +147,7 @@ function ensureTickLoop() {
|
|||||||
startTime = performance.now();
|
startTime = performance.now();
|
||||||
lastTick = startTime;
|
lastTick = startTime;
|
||||||
tickIntervalId = setInterval(() => {
|
tickIntervalId = setInterval(() => {
|
||||||
if (!ctx || scripts.size === 0) {
|
if (scripts.size === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
@@ -158,7 +156,7 @@ function ensureTickLoop() {
|
|||||||
lastTick = now;
|
lastTick = now;
|
||||||
|
|
||||||
scripts.forEach((script) => {
|
scripts.forEach((script) => {
|
||||||
if (!script.tick) {
|
if (!script.tick || !script.ctx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
script.context.now = now;
|
script.context.now = now;
|
||||||
@@ -199,22 +197,19 @@ function createScriptHandlers(source, context, state, sourceLabel = "") {
|
|||||||
self.addEventListener("message", (event) => {
|
self.addEventListener("message", (event) => {
|
||||||
const { type, payload } = event.data || {};
|
const { type, payload } = event.data || {};
|
||||||
if (type === "init") {
|
if (type === "init") {
|
||||||
canvas = payload.canvas;
|
|
||||||
channelName = payload.channelName || "";
|
channelName = payload.channelName || "";
|
||||||
if (canvas) {
|
|
||||||
canvas.width = payload.width || canvas.width;
|
|
||||||
canvas.height = payload.height || canvas.height;
|
|
||||||
ctx = canvas.getContext("2d");
|
|
||||||
}
|
|
||||||
updateScriptContexts();
|
updateScriptContexts();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === "resize") {
|
if (type === "resize") {
|
||||||
if (canvas) {
|
scripts.forEach((script) => {
|
||||||
canvas.width = payload.width || canvas.width;
|
if (!script.canvas) {
|
||||||
canvas.height = payload.height || canvas.height;
|
return;
|
||||||
}
|
}
|
||||||
|
script.canvas.width = payload.width || script.canvas.width;
|
||||||
|
script.canvas.height = payload.height || script.canvas.height;
|
||||||
|
});
|
||||||
updateScriptContexts();
|
updateScriptContexts();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -226,16 +221,20 @@ self.addEventListener("message", (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type === "addScript") {
|
if (type === "addScript") {
|
||||||
if (!payload?.id || !payload?.source) {
|
if (!payload?.id || !payload?.source || !payload?.canvas) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const canvas = payload.canvas;
|
||||||
|
canvas.width = payload.width || canvas.width;
|
||||||
|
canvas.height = payload.height || canvas.height;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
const state = {};
|
const state = {};
|
||||||
const context = {
|
const context = {
|
||||||
canvas,
|
canvas,
|
||||||
ctx,
|
ctx,
|
||||||
channelName,
|
channelName,
|
||||||
width: canvas?.width ?? 0,
|
width: canvas.width ?? 0,
|
||||||
height: canvas?.height ?? 0,
|
height: canvas.height ?? 0,
|
||||||
now: 0,
|
now: 0,
|
||||||
deltaMs: 0,
|
deltaMs: 0,
|
||||||
elapsedMs: 0,
|
elapsedMs: 0,
|
||||||
@@ -251,6 +250,8 @@ self.addEventListener("message", (event) => {
|
|||||||
}
|
}
|
||||||
const script = {
|
const script = {
|
||||||
id: payload.id,
|
id: payload.id,
|
||||||
|
canvas,
|
||||||
|
ctx,
|
||||||
context,
|
context,
|
||||||
state,
|
state,
|
||||||
init: handlers.init,
|
init: handlers.init,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body class="broadcast-body">
|
<body class="broadcast-body">
|
||||||
<canvas id="broadcast-canvas"></canvas>
|
<canvas id="broadcast-canvas"></canvas>
|
||||||
<canvas id="broadcast-script-canvas"></canvas>
|
<div id="broadcast-script-layer" class="broadcast-script-layer"></div>
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
const broadcaster = /*[[${broadcaster}]]*/ "";
|
const broadcaster = /*[[${broadcaster}]]*/ "";
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user