49 lines
2.3 KiB
TypeScript
49 lines
2.3 KiB
TypeScript
import { postToWorker } from './workerClient';
|
|
|
|
/**
|
|
* 页面这一侧的「边下边播」:把条目登记成一个 /__wd_stream/<id> 地址交给 <video>/<audio>,
|
|
* 播放器的每个 Range 请求由 Service Worker(public/stream-sw.js)转给这里,
|
|
* 这里把 SW 给的 MessagePort 直接交给加密线程,解密后的数据不再经过页面。
|
|
*/
|
|
interface Entry { resourceId: string; itemId: string; size: number; mime: string }
|
|
interface SwMessage { type: 'wd-stream'; id: string; range: { start?: number; end?: number; suffix?: number }; port: MessagePort }
|
|
|
|
const entries = new Map<string, Entry>();
|
|
let ready: Promise<boolean> | undefined;
|
|
|
|
function setup(): Promise<boolean> {
|
|
if (!('serviceWorker' in navigator)) return Promise.resolve(false);
|
|
return (async () => {
|
|
navigator.serviceWorker.addEventListener('message', (event: MessageEvent<SwMessage>) => {
|
|
const message = event.data;
|
|
if (message?.type !== 'wd-stream') return;
|
|
const entry = entries.get(message.id);
|
|
if (!entry) { message.port.postMessage({ error: 'gone', status: 404 }); message.port.close(); return; }
|
|
postToWorker({ operation: 'streamRange', payload: { ...entry, ...message.range } }, [message.port]);
|
|
});
|
|
await navigator.serviceWorker.register(`${import.meta.env.BASE_URL}stream-sw.js`);
|
|
await navigator.serviceWorker.ready;
|
|
if (!navigator.serviceWorker.controller) {
|
|
// 第一次安装:等 SW 接管当前页面(clients.claim)
|
|
await new Promise<void>((resolve) => {
|
|
const timer = setTimeout(resolve, 2000);
|
|
navigator.serviceWorker.addEventListener('controllerchange', () => { clearTimeout(timer); resolve(); }, { once: true });
|
|
});
|
|
}
|
|
return navigator.serviceWorker.controller !== null;
|
|
})().catch(() => false);
|
|
}
|
|
|
|
/** 登记一个可以边下边播的条目,返回播放地址;浏览器或环境不支持时返回 undefined(调用方退回整个下载) */
|
|
export async function playbackUrl(resourceId: string, itemId: string, size: number, mime: string): Promise<string | undefined> {
|
|
ready ??= setup();
|
|
if (!(await ready)) return undefined;
|
|
const id = `${crypto.randomUUID()}`;
|
|
entries.set(id, { resourceId, itemId, size, mime });
|
|
return `${import.meta.env.BASE_URL}__wd_stream/${id}`;
|
|
}
|
|
|
|
export function releasePlayback(url: string): void {
|
|
entries.delete(url.slice(url.lastIndexOf('/') + 1));
|
|
}
|