import assert from 'assert'; import logger from './logger.js'; import { createMediaSourceProcess, readOneJpegFrame as readOneJpegFrameRaw } from './ffmpeg.js'; export function createMediaSourceStream({ path, videoStreamIndex, audioStreamIndex, seekTo, size, fps }: { path: string, videoStreamIndex?: number | undefined, audioStreamIndex?: number | undefined, seekTo: number, size?: number | undefined, fps?: number | undefined, }) { const abortController = new AbortController(); logger.info('Starting preview process', { videoStreamIndex, audioStreamIndex, seekTo }); const process = createMediaSourceProcess({ path, videoStreamIndex, audioStreamIndex, seekTo, size, fps }); // eslint-disable-next-line unicorn/prefer-add-event-listener abortController.signal.onabort = () => { logger.info('Aborting preview process', { videoStreamIndex, audioStreamIndex, seekTo }); process.kill('SIGKILL'); }; const { stdout } = process; assert(stdout != null); stdout.pause(); const readChunk = async () => new Promise((resolve, reject) => { let cleanup: () => void; const onClose = () => { cleanup(); resolve(null); }; const onData = (chunk: Buffer) => { stdout.pause(); cleanup(); resolve(chunk); }; const onError = (err: Error) => { cleanup(); reject(err); }; cleanup = () => { stdout.off('data', onData); stdout.off('error', onError); stdout.off('close', onClose); }; stdout.once('data', onData); stdout.once('error', onError); stdout.once('close', onClose); stdout.resume(); }); function abort() { abortController.abort(); } let stderr = Buffer.alloc(0); process.stderr?.on('data', (chunk) => { stderr = Buffer.concat([stderr, chunk]); }); (async () => { try { await process; } catch (err) { if (err instanceof Error && err.name === 'AbortError') { return; } // @ts-expect-error todo if (!(err.killed)) { // @ts-expect-error todo console.warn(err.message); console.warn(stderr.toString('utf8')); } } })(); return { abort, readChunk }; } export function readOneJpegFrame({ path, seekTo, videoStreamIndex }: { path: string, seekTo: number, videoStreamIndex: number }) { const abortController = new AbortController(); const process = readOneJpegFrameRaw({ path, seekTo, videoStreamIndex }); // eslint-disable-next-line unicorn/prefer-add-event-listener abortController.signal.onabort = () => process.kill('SIGKILL'); function abort() { abortController.abort(); } const promise = (async () => { try { const { stdout } = await process; return stdout; } catch (err) { // @ts-expect-error todo logger.error('renderOneJpegFrame', err.shortMessage); throw new Error('Failed to render JPEG frame'); } })(); return { promise, abort }; }