import { encodeLiveRawStream, getOneRawFrame } from './ffmpeg'; // TODO keep everything in electron land? const strtok3 = window.require('strtok3'); export default ({ path, width: inWidth, height: inHeight, streamIndex, getCanvas }) => { let terminated; let aborters = []; let commandedTime; let playing; function drawOnCanvas(rgbaImage, width, height) { const canvas = getCanvas(); if (!canvas || rgbaImage.length === 0) return; canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); // https://developer.mozilla.org/en-US/docs/Web/API/ImageData/ImageData // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/putImageData ctx.putImageData(new ImageData(Uint8ClampedArray.from(rgbaImage), width, height), 0, 0); } async function command() { let process; let aborted = false; function killProcess() { if (process) { process.kill(); process = undefined; } } function abort() { aborted = true; killProcess(); aborters = aborters.filter(((aborter) => aborter !== abort)); } aborters.push(abort); try { if (playing) { const { process: processIn, channels, width, height } = encodeLiveRawStream({ path, inWidth, inHeight, streamIndex, seekTo: commandedTime }); process = processIn; // process.stderr.on('data', data => console.log(data.toString('utf-8'))); const tokenizer = await strtok3.fromStream(process.stdout); if (aborted) return; const size = width * height * channels; const rgbaImage = Buffer.allocUnsafe(size); while (!aborted) { // eslint-disable-next-line no-await-in-loop await tokenizer.readBuffer(rgbaImage, { length: size }); if (aborted) return; drawOnCanvas(rgbaImage, width, height); } } else { const { process: processIn, width, height } = getOneRawFrame({ path, inWidth, inHeight, streamIndex, seekTo: commandedTime, outSize: 1000 }); process = processIn; const { stdout: rgbaImage } = await process; if (aborted) return; drawOnCanvas(rgbaImage, width, height); } } catch (err) { if (!err.killed) console.warn(err.message); } finally { killProcess(); } } function abortAll() { aborters.forEach((aborter) => aborter()); } function pause(seekTo) { if (terminated) return; if (!playing && commandedTime === seekTo) return; playing = false; commandedTime = seekTo; abortAll(); command(); } function play(playFrom) { if (terminated) return; if (playing && commandedTime === playFrom) return; playing = true; commandedTime = playFrom; abortAll(); command(); } function terminate() { if (terminated) return; terminated = true; abortAll(); } return { play, pause, terminate, }; };