feat: add danmuku plugin documentation and i18n setup guide

- Created a new documentation file for the danmuku plugin with code examples.
- Added a comprehensive guide for language settings (i18n) including default languages, importing languages, adding new languages, and modifying existing languages.
- Included code snippets for initializing the Artplayer with different language configurations.
This commit is contained in:
Harvey Zhao committed 2026-02-12 16:55:43 +08:00
1 parent 687a34c6b3
commit cfaaea6a5c
72 files changed
+436 -392

No files matched your search

+57 -28
View File
@@ -45,8 +45,8 @@ export default class Upscaler {
},
};
this.networkSize = options.networkSize || "medium";
this.weightsBaseUrl = weightsBaseUrl;
this.networkSize = options.networkSize || "medium";
this.workerUrl = options.workerUrl || "/worker/main.js";
this.timeouts = {
@@ -72,6 +72,8 @@ export default class Upscaler {
this.realtimeLoopId = null;
this.realtimeState = null;
this.init();
}
init({ prewarm = true } = {}) {
@@ -117,6 +119,7 @@ export default class Upscaler {
handleWorkerMessage(event) {
const { data } = event;
console.debug("Worker message:", data);
if (!data.cmd) return;
const { cmd } = data;
@@ -253,7 +256,6 @@ export default class Upscaler {
mimeType,
);
const weights = await this.loadWeights(size);
const network = this.networks[size];
const scale =
@@ -323,7 +325,6 @@ export default class Upscaler {
this.timeouts.METADATA,
);
const weights = await this.loadWeights(size);
const network = this.networks[size];
const scale =
@@ -416,41 +417,52 @@ export default class Upscaler {
throw new Error("Invalid video dimensions");
}
const firstInit = !canvasElement._upscalerTransferred;
canvasElement.width = width * scale;
canvasElement.height = height * scale;
let offscreen = null;
if (firstInit) {
canvasElement.width = width * scale;
canvasElement.height = height * scale;
if (typeof canvasElement.transferControlToOffscreen !== "function") {
throw new Error("OffscreenCanvas is not supported in this environment");
if (typeof canvasElement.transferControlToOffscreen !== "function") {
throw new Error("OffscreenCanvas is not supported in this environment");
}
offscreen = canvasElement.transferControlToOffscreen();
canvasElement._upscalerTransferred = true;
}
const offscreen = canvasElement.transferControlToOffscreen();
const captureCanvas = document.createElement("canvas");
captureCanvas.width = width;
captureCanvas.height = height;
const captureCtx = captureCanvas.getContext("2d");
if (this.realtimeLoopId) {
cancelAnimationFrame(this.realtimeLoopId);
this.realtimeLoopId = null;
}
this.getWorker().postMessage(
{
cmd: "realtimeInit",
data: {
upscaled: offscreen,
resolution: {
width,
height,
scale,
outputWidth: width * scale,
outputHeight: height * scale,
},
network_name: network.name,
weights,
const initPayload = {
cmd: "realtimeInit",
data: {
upscaled: firstInit ? offscreen : null,
resolution: {
width,
height,
scale,
outputWidth: width * scale,
outputHeight: height * scale,
},
network_name: network.name,
weights,
},
[offscreen],
);
};
if (firstInit && offscreen) {
this.getWorker().postMessage(initPayload, [offscreen]);
} else {
this.getWorker().postMessage(initPayload);
}
this.realtimeState = {
running: true,
@@ -459,6 +471,8 @@ export default class Upscaler {
scale,
busy: false,
frameIndex: 0,
captureCanvas,
captureCtx,
};
const loop = async () => {
@@ -466,12 +480,27 @@ export default class Upscaler {
const state = this.realtimeState;
const v = state.video;
if (!v.paused && !v.ended && !state.busy) {
const haveCurrentData = v.HAVE_CURRENT_DATA || 2;
if (
!v.paused &&
!v.ended &&
!state.busy &&
v.readyState >= haveCurrentData &&
v.videoWidth > 0 &&
v.videoHeight > 0
) {
state.busy = true;
try {
const frame = await createImageBitmap(v);
const captureCanvas = state.captureCanvas;
const captureCtx = state.captureCtx;
if (!captureCanvas || !captureCtx) {
throw new Error("Missing capture canvas");
}
captureCtx.drawImage(v, 0, 0, captureCanvas.width, captureCanvas.height);
const frame = await createImageBitmap(captureCanvas);
state.frameIndex += 1;
this.getWorker().postMessage(
{
cmd: "realtimeFrame",
+124 -24
View File
@@ -1,12 +1,15 @@
import Upscaler from "./Upscaler";
import Upscaler from "./Upscaler.js";
export default function artplayerPluginWebsr(
function artplayerPluginWebsr(
option = {
networkSize: "medium",
compare: false,
weightsBaseUrl: "/weights",
workerUrl: "/worker/main.js",
videoScale: 2,
},
) {
return async (art) => {
return (art) => {
const { $video, $player } = art.template;
const $canvas = document.createElement("canvas");
@@ -18,6 +21,8 @@ export default function artplayerPluginWebsr(
$canvas.style.top = "50%";
$canvas.style.left = "50%";
$canvas.style.transform = "translate(-50%, -50%)";
$canvas.style.opacity = "0";
$canvas.style.transition = "opacity 0.2s ease";
function calcCanvasSize() {
const videoElement = art.video;
@@ -36,35 +41,82 @@ export default function artplayerPluginWebsr(
return { displayWidth, displayHeight };
}
const upscaler = new Upscaler(option);
upscaler.init();
await upscaler.startRealtimeUpscale(
$video,
$canvas,
option.networkSize,
);
if (!Upscaler || !Upscaler.isSupported || !Upscaler.isSupported()) {
console.warn("Upscaler is not supported in this environment");
return;
}
const upscaler = new Upscaler({
networkSize: option.networkSize || "medium",
weightsBaseUrl: option.weightsBaseUrl || "/weights",
workerUrl: option.workerUrl || "/worker/main.js",
videoScale: option.videoScale || 2,
});
let realtimeStarted = false;
async function startRealtime() {
if (realtimeStarted) return;
realtimeStarted = true;
try {
await new Promise((resolve, reject) => {
if ($video.readyState >= 1) {
resolve();
return;
}
const onLoaded = () => {
cleanup();
resolve();
};
const onError = () => {
cleanup();
reject(new Error("Failed to load video metadata"));
};
const cleanup = () => {
$video.removeEventListener("loadedmetadata", onLoaded);
$video.removeEventListener("error", onError);
};
$video.addEventListener("loadedmetadata", onLoaded);
$video.addEventListener("error", onError);
});
await upscaler.startRealtimeUpscale(
$video,
$canvas,
option.networkSize || upscaler.networkSize,
);
requestAnimationFrame(() => {
$canvas.style.opacity = "1";
});
} catch (e) {
console.error(
"artplayer-plugin-upscaler: failed to start realtime",
e,
);
}
}
// 初始化 canvas 显示尺寸
const { displayWidth, displayHeight } = calcCanvasSize();
$canvas.style.width = displayWidth + "px";
$canvas.style.height = displayHeight + "px";
// 对比模式
let comparePosition = 50;
let isDragging = false;
let compareEnabled = !!option.compare;
// 创建对比手柄
const $handler = document.createElement("div");
$handler.style.position = "absolute";
$handler.style.width = "3px";
$handler.style.backgroundColor = "rgba(255, 255, 255, 0.5)";
$handler.style.backgroundColor = "rgba(255, 255, 255, 0.75)";
$handler.style.cursor = "ew-resize";
$handler.style.zIndex = "12";
$handler.style.pointerEvents = "auto";
$handler.style.display = option.compare ? "block" : "none";
$handler.style.boxShadow = "0 0 4px rgba(0, 0, 0, 0.1)";
$handler.style.display = compareEnabled ? "block" : "none";
$handler.style.boxShadow = "0 0 2px rgba(0, 0, 0, 0.1)";
if (option.compare) {
if (compareEnabled) {
$player.appendChild($handler);
$handler.addEventListener("mousedown", handleMouseDown);
document.addEventListener("mousemove", handleMouseMove);
@@ -73,7 +125,7 @@ export default function artplayerPluginWebsr(
}
function updateCompareMask() {
if (option.compare) {
if (compareEnabled) {
const { displayWidth, displayHeight } = calcCanvasSize();
const gradient = `linear-gradient(to right, transparent 0%, transparent ${comparePosition}%, black ${comparePosition}%, black 100%)`;
$canvas.style.maskImage = gradient;
@@ -95,13 +147,13 @@ export default function artplayerPluginWebsr(
}
function handleMouseDown(e) {
if (option.compare) {
if (compareEnabled) {
isDragging = true;
}
}
function handleMouseMove(e) {
if (option.compare && isDragging) {
if (compareEnabled && isDragging) {
const rect = $player.getBoundingClientRect();
const { displayWidth } = calcCanvasSize();
const containerWidth = $player.offsetWidth || 640;
@@ -119,7 +171,9 @@ export default function artplayerPluginWebsr(
}
art.on("destroy", () => {
upscaler.dispose();
if (upscaler && typeof upscaler.dispose === "function") {
upscaler.dispose();
}
$handler.removeEventListener("mousedown", handleMouseDown);
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
@@ -132,12 +186,58 @@ export default function artplayerPluginWebsr(
updateCompareMask();
});
art.on("play", () => {
startRealtime();
});
function update(newOption = {}) {
if (!newOption || typeof newOption !== "object") return;
if (Object.prototype.hasOwnProperty.call(newOption, "compare")) {
const nextCompare = !!newOption.compare;
option.compare = nextCompare;
compareEnabled = nextCompare;
if (compareEnabled) {
if (!$handler.parentNode) {
$player.appendChild($handler);
}
$handler.style.display = "block";
$handler.addEventListener("mousedown", handleMouseDown);
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
updateCompareMask();
} else {
$canvas.style.maskImage = "none";
$handler.style.display = "none";
$handler.removeEventListener("mousedown", handleMouseDown);
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
}
}
if (newOption.networkSize) {
const nextSize = newOption.networkSize;
option.networkSize = nextSize;
if (
upscaler &&
typeof upscaler.stopRealtimeUpscale === "function"
) {
upscaler.stopRealtimeUpscale();
}
realtimeStarted = false;
if (!art.paused) {
startRealtime();
}
}
}
return {
name: "artplayerPluginWebsr",
upscaler,
update,
};
};
}
if (typeof window !== "undefined") {
window.artplayerPluginWebsr = artplayerPluginWebsr;
}
window.artplayerPluginWebsr = artplayerPluginWebsr;