mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-20 04:01:20 -08:00
refactor(vtt-thumbnail): [PKG-VTT-THUMB-03] own pending requests and preview resources
This commit is contained in:
1 parent
05244d1104
commit
524ddf784f
23 files changed
+3045
-262
No files matched your search
@@ -4,6 +4,59 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
function createLifetime(art) {
|
||||
let closed = Boolean(art.isDestroy);
|
||||
const cleanups = /* @__PURE__ */ new Set();
|
||||
let cancel;
|
||||
const cancelled = new Promise((resolve) => {
|
||||
cancel = resolve;
|
||||
});
|
||||
const lifetime = {
|
||||
get closed() {
|
||||
return closed;
|
||||
},
|
||||
own(cleanup) {
|
||||
if (closed)
|
||||
run(cleanup);
|
||||
else
|
||||
cleanups.add(cleanup);
|
||||
return () => cleanups.delete(cleanup);
|
||||
},
|
||||
listen(name, callback) {
|
||||
if (closed)
|
||||
return;
|
||||
lifetime.own(() => art.off(name, callback));
|
||||
art.on(name, callback);
|
||||
},
|
||||
wait(value) {
|
||||
return Promise.race([value, cancelled]);
|
||||
},
|
||||
dispose() {
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
cancel();
|
||||
const pending = [...cleanups].reverse();
|
||||
cleanups.clear();
|
||||
for (const cleanup of pending)
|
||||
run(cleanup);
|
||||
}
|
||||
};
|
||||
function run(cleanup) {
|
||||
try {
|
||||
cleanup();
|
||||
} catch (error) {
|
||||
console.warn("Failed to clean up VTT thumbnails:", error);
|
||||
}
|
||||
}
|
||||
try {
|
||||
lifetime.listen("destroy", lifetime.dispose);
|
||||
} catch (error) {
|
||||
lifetime.dispose();
|
||||
throw error;
|
||||
}
|
||||
return lifetime;
|
||||
}
|
||||
function padEnd(str, targetLength, padString) {
|
||||
if (str.length > targetLength) {
|
||||
return String(str);
|
||||
@@ -25,8 +78,7 @@ function t2d(time) {
|
||||
const s = Number(left[left.length - 1] || 0);
|
||||
return h + m + s + ms;
|
||||
}
|
||||
async function getVttArray(vttUrl = "") {
|
||||
const vttString = await (await fetch(vttUrl)).text();
|
||||
function parseVtt(vttString, vttUrl = "") {
|
||||
const lines = vttString.split(/[\n\r]/g).filter((item) => item.trim());
|
||||
const vttArray = [];
|
||||
for (let i = 1; i < lines.length; i += 2) {
|
||||
@@ -58,65 +110,128 @@ async function getVttArray(vttUrl = "") {
|
||||
}
|
||||
return vttArray;
|
||||
}
|
||||
function createPreview({ lifetime, thumbnails, progress, duration, setStyle, isMobile }) {
|
||||
let timer = null;
|
||||
let generation = 0;
|
||||
lifetime.own(() => {
|
||||
generation++;
|
||||
if (timer !== null) {
|
||||
const previous = timer;
|
||||
timer = null;
|
||||
clearTimeout(previous);
|
||||
}
|
||||
});
|
||||
function style(control, key, value) {
|
||||
if (!lifetime.closed)
|
||||
setStyle(control, key, value);
|
||||
}
|
||||
function show(control, cue, width) {
|
||||
style(control, "backgroundImage", `url(${cue.url})`);
|
||||
style(control, "height", `${cue.h}px`);
|
||||
style(control, "width", `${cue.w}px`);
|
||||
style(control, "backgroundPosition", `-${cue.x}px -${cue.y}px`);
|
||||
if (width <= cue.w / 2)
|
||||
style(control, "left", 0);
|
||||
else if (width > progress.clientWidth - cue.w / 2)
|
||||
style(control, "left", `${progress.clientWidth - cue.w}px`);
|
||||
else
|
||||
style(control, "left", `${width - cue.w / 2}px`);
|
||||
}
|
||||
return (control) => async (type, percentage, event) => {
|
||||
if (lifetime.closed)
|
||||
return;
|
||||
const dragging = type === "played" && event && isMobile;
|
||||
if (type !== "hover" && !dragging)
|
||||
return;
|
||||
const width = progress.clientWidth * percentage;
|
||||
const second = percentage * duration();
|
||||
style(control, "display", "flex");
|
||||
if (lifetime.closed)
|
||||
return;
|
||||
const cue = thumbnails.find((item) => second >= item.start && second <= item.end);
|
||||
if (!cue)
|
||||
return style(control, "display", "none");
|
||||
if (width > 0 && width < progress.clientWidth)
|
||||
show(control, cue, width);
|
||||
else if (!isMobile)
|
||||
style(control, "display", "none");
|
||||
if (dragging && !lifetime.closed) {
|
||||
const current = ++generation;
|
||||
if (timer !== null)
|
||||
clearTimeout(timer);
|
||||
if (lifetime.closed)
|
||||
return;
|
||||
const id = setTimeout(() => {
|
||||
if (current !== generation || lifetime.closed)
|
||||
return;
|
||||
timer = null;
|
||||
style(control, "display", "none");
|
||||
}, 500);
|
||||
if (lifetime.closed || current !== generation)
|
||||
clearTimeout(id);
|
||||
else
|
||||
timer = id;
|
||||
}
|
||||
};
|
||||
}
|
||||
async function requestVtt(url = "", lifetime) {
|
||||
if (lifetime.closed)
|
||||
return;
|
||||
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
||||
const release = lifetime.own(() => controller?.abort());
|
||||
try {
|
||||
if (lifetime.closed)
|
||||
return;
|
||||
const response = await lifetime.wait(controller ? fetch(url, { signal: controller.signal }) : fetch(url));
|
||||
if (lifetime.closed)
|
||||
return;
|
||||
if (response.ok === false)
|
||||
throw new Error(`Failed to fetch VTT thumbnails: HTTP ${response.status}`);
|
||||
if (lifetime.closed)
|
||||
return;
|
||||
return await lifetime.wait(response.text());
|
||||
} finally {
|
||||
release();
|
||||
}
|
||||
}
|
||||
function artplayerPluginVttThumbnail(option) {
|
||||
return async (art) => {
|
||||
const {
|
||||
constructor: {
|
||||
utils: { setStyle, isMobile, addClass }
|
||||
},
|
||||
template: { $progress }
|
||||
} = art;
|
||||
let timer = null;
|
||||
const thumbnails = await getVttArray(option.vtt);
|
||||
function showThumbnails($control, find, width) {
|
||||
setStyle($control, "backgroundImage", `url(${find.url})`);
|
||||
setStyle($control, "height", `${find.h}px`);
|
||||
setStyle($control, "width", `${find.w}px`);
|
||||
setStyle($control, "backgroundPosition", `-${find.x}px -${find.y}px`);
|
||||
if (width <= find.w / 2) {
|
||||
setStyle($control, "left", 0);
|
||||
} else if (width > $progress.clientWidth - find.w / 2) {
|
||||
setStyle($control, "left", `${$progress.clientWidth - find.w}px`);
|
||||
} else {
|
||||
setStyle($control, "left", `${width - find.w / 2}px`);
|
||||
}
|
||||
const { constructor: { utils: { setStyle, isMobile, addClass } }, template: { $progress } } = art;
|
||||
const lifetime = createLifetime(art);
|
||||
const result = { name: "artplayerPluginVttThumbnail" };
|
||||
try {
|
||||
if (lifetime.closed)
|
||||
return result;
|
||||
const url = option.vtt;
|
||||
const text = await requestVtt(url, lifetime);
|
||||
if (lifetime.closed)
|
||||
return result;
|
||||
const thumbnails = parseVtt(text, url);
|
||||
const preview = createPreview({ lifetime, thumbnails, progress: $progress, duration: () => art.duration, setStyle, isMobile });
|
||||
const style = option.style || {};
|
||||
if (lifetime.closed)
|
||||
return result;
|
||||
art.controls.add({
|
||||
name: "vtt-thumbnail",
|
||||
position: "top",
|
||||
index: 20,
|
||||
style,
|
||||
mounted(control) {
|
||||
lifetime.own(() => {
|
||||
if (art.controls["vtt-thumbnail"] === control)
|
||||
art.controls.remove("vtt-thumbnail");
|
||||
});
|
||||
if (lifetime.closed)
|
||||
return;
|
||||
addClass(control, "art-control-thumbnails");
|
||||
lifetime.listen("setBar", preview(control));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
lifetime.dispose();
|
||||
throw error;
|
||||
}
|
||||
art.controls.add({
|
||||
name: "vtt-thumbnail",
|
||||
position: "top",
|
||||
index: 20,
|
||||
style: option.style || {},
|
||||
mounted($control) {
|
||||
addClass($control, "art-control-thumbnails");
|
||||
art.on("setBar", async (type, percentage, event) => {
|
||||
const isMobileDroging = type === "played" && event && isMobile;
|
||||
if (type === "hover" || isMobileDroging) {
|
||||
const width = $progress.clientWidth * percentage;
|
||||
const second = percentage * art.duration;
|
||||
setStyle($control, "display", "flex");
|
||||
const find = thumbnails.find((item) => second >= item.start && second <= item.end);
|
||||
if (!find)
|
||||
return setStyle($control, "display", "none");
|
||||
if (width > 0 && width < $progress.clientWidth) {
|
||||
showThumbnails($control, find, width);
|
||||
} else {
|
||||
if (!isMobile) {
|
||||
setStyle($control, "display", "none");
|
||||
}
|
||||
}
|
||||
if (isMobileDroging) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
setStyle($control, "display", "none");
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return {
|
||||
name: "artplayerPluginVttThumbnail"
|
||||
};
|
||||
};
|
||||
}
|
||||
export {
|
||||
|
||||
Reference in new issue
Block a user