Files
ArtPlayer/docs/compiled/artplayer-plugin-ambilight.mjs
T

310 lines
8.0 KiB
JavaScript

/*!
* artplayer-plugin-ambilight.js v1.1.0
* Github: https://github.com/zhw2590582/ArtPlayer
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
function isCanvas(source) {
return source.nodeName === "CANVAS";
}
function createColorSampler(canvas, video, createCanvas) {
let context;
try {
context = canvas.getContext("2d");
canvas.width = 3;
canvas.height = 3;
} catch (error) {
canvas.width = 0;
canvas.height = 0;
throw error;
}
let destroyed = false;
let replaceCanvas = false;
function read(active) {
if (destroyed || !active())
return null;
const canvasOutput = isCanvas(video);
const width = canvasOutput ? video.width : video.videoWidth;
const height = canvasOutput ? video.height : video.videoHeight;
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0)
return null;
const w = width / 3;
const h = height / 3;
const colors = [];
try {
if (replaceCanvas) {
const replacement = createCanvas();
if (destroyed || !active()) {
replacement.width = 0;
replacement.height = 0;
return null;
}
canvas = replacement;
context = null;
context = canvas.getContext("2d");
if (destroyed || !active()) {
canvas.width = 0;
canvas.height = 0;
return null;
}
canvas.width = 3;
canvas.height = 3;
replaceCanvas = false;
}
if (!context)
return null;
for (let row = 0; row < 3; row++) {
for (let column = 0; column < 3; column++) {
if (destroyed || !active())
return null;
context.drawImage(video, column * w, row * h, w, h, 0, 0, 1, 1);
if (destroyed || !active())
return null;
const [r, g, b] = context.getImageData(0, 0, 1, 1).data;
colors.push(`rgb(${r}, ${g}, ${b})`);
}
}
return colors;
} catch {
if (!destroyed) {
replaceCanvas = true;
canvas.width = 0;
canvas.height = 0;
}
return null;
}
}
function destroy() {
if (destroyed)
return;
destroyed = true;
canvas.width = 0;
canvas.height = 0;
}
return { read, destroy };
}
function createFrameLoop(frequency, playing, update) {
let state = "stopped";
let pending = null;
let generation = 0;
let inFrame = false;
let lastUpdateTime = 0;
function schedule() {
const current = generation;
try {
pending = requestAnimationFrame(() => {
if (state !== "running" || current !== generation)
return;
pending = null;
tick();
});
} catch (error) {
state = "stopped";
generation++;
throw error;
}
}
function tick() {
const current = generation;
const active = () => state === "running" && current === generation;
inFrame = true;
try {
const now = performance.now();
if (now - lastUpdateTime < 1e3 / frequency || !playing() || !active())
return;
lastUpdateTime = now;
update(active);
} finally {
inFrame = false;
if (state === "running" && pending === null)
schedule();
}
}
function start() {
if (state !== "stopped")
return;
state = "running";
generation++;
if (!inFrame)
tick();
}
function stop() {
if (state === "destroyed")
return;
state = "stopped";
generation++;
const frame = pending;
pending = null;
if (frame !== null)
cancelAnimationFrame(frame);
}
function destroy() {
if (state === "destroyed")
return;
state = "destroyed";
generation++;
const frame = pending;
pending = null;
if (frame !== null)
cancelAnimationFrame(frame);
}
return { start, stop, destroy };
}
function createAmbilightView(utils) {
const element = utils.createElement("div");
element.innerHTML = Array.from({ length: 9 }).fill("<div></div>").join("");
const items = Array.from(element.children);
function mount(video, option, active) {
if (!active())
return;
utils.addClass(element, "artplayer-plugin-ambilight");
if (!active())
return;
video.parentNode.insertBefore(element, video);
if (!active())
return;
utils.setStyles(element, { position: "absolute", top: 0, left: 0, zIndex: 9, inset: 0, display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gridTemplateRows: "1fr 1fr 1fr" });
for (const item of items) {
if (!active())
return;
utils.setStyles(item, { opacity: option.opacity, filter: `blur(${option.blur})`, transition: `background-color ${option.duration}s ease` });
}
}
function render(colors, active) {
for (let index2 = 0; index2 < colors.length; index2++) {
if (!active())
return;
const item = items[index2];
const color = colors[index2];
if (item && color !== void 0)
item.style.backgroundColor = color;
}
}
function destroy() {
try {
element.parentNode?.removeChild(element);
} finally {
items.length = 0;
}
}
return { mount, render, destroy };
}
function artplayerPluginAmbilight(option = {}) {
return (art) => {
let host = art;
let closed = art.isDestroy;
let view = null;
let sampler = null;
let loop = null;
const subscriptions = [];
const result = {
name: "artplayerPluginAmbilight",
start() {
if (!closed)
loop?.start();
},
stop() {
if (!closed)
loop?.stop();
}
};
if (closed) {
host = null;
return result;
}
function releaseResources() {
const releases = [loop?.destroy, sampler?.destroy, view?.destroy];
loop = null;
sampler = null;
view = null;
const failures = [];
for (const release of releases) {
try {
release?.();
} catch (error) {
failures.push(error);
}
}
if (failures.length)
throw failures[0];
}
function destroy() {
if (closed)
return;
closed = true;
const current = host;
host = null;
const failures = [];
for (const [event, callback] of subscriptions.splice(0)) {
try {
current?.off(event, callback);
} catch (error) {
failures.push(error);
}
}
try {
releaseResources();
} catch (error) {
failures.push(error);
}
if (failures.length)
throw failures[0];
}
function listen(event, callback) {
subscriptions.push([event, callback]);
host?.on(event, callback);
}
try {
listen("destroy", destroy);
if (closed)
return result;
const { $video } = art.template;
const utils = art.constructor.utils;
const { blur = "50px", opacity = 0.5, frequency = 10, duration = 0.3 } = option;
if (closed)
return result;
view = createAmbilightView(utils);
if (!closed)
view.mount($video, { blur, opacity, duration }, () => !closed);
if (closed) {
releaseResources();
return result;
}
const canvas = utils.createElement("canvas");
if (closed) {
canvas.width = 0;
canvas.height = 0;
return result;
}
sampler = createColorSampler(canvas, $video, () => utils.createElement("canvas"));
if (closed) {
releaseResources();
return result;
}
loop = createFrameLoop(frequency, () => Boolean(host?.playing), (valid) => {
const colors = sampler?.read(valid);
if (colors && valid())
view?.render(colors, valid);
});
listen("ready", result.start);
if (closed)
releaseResources();
} catch (error) {
try {
destroy();
} catch {
}
try {
releaseResources();
} catch {
}
throw error;
}
return result;
};
}
const index = Object.assign(artplayerPluginAmbilight, { default: artplayerPluginAmbilight });
export {
index as default
};