feat(web): stream video and audio while decrypting, paste images, 16px inputs on touch
This commit is contained in:
1 parent
31ae3bc39e
commit
283196938e
12 files changed
+376
-16
No files matched your search
@@ -0,0 +1,48 @@
|
||||
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));
|
||||
}
|
||||
Reference in new issue
Block a user