From 4733ba6a4aa4ad806ea5c7a68d9b068b55f0efd9 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Sat, 26 Feb 2022 15:04:02 +0800 Subject: [PATCH] try to improve canvas player #272 --- src/Canvas.jsx | 51 +++++++++++++++-------------- src/CanvasPlayer.js | 80 ++++++++++++++++++++++++--------------------- 2 files changed, 70 insertions(+), 61 deletions(-) diff --git a/src/Canvas.jsx b/src/Canvas.jsx index 46772e8f241..e4ba8ad64cc 100644 --- a/src/Canvas.jsx +++ b/src/Canvas.jsx @@ -1,42 +1,45 @@ -import React, { memo, useEffect, useRef, useMemo, useState } from 'react'; -import useDebounce from 'react-use/lib/useDebounce'; +import React, { memo, useEffect, useRef, useMemo } from 'react'; +import { useDebounce } from 'use-debounce'; import CanvasPlayer from './CanvasPlayer'; const Canvas = memo(({ rotate, filePath, width, height, playerTime, streamIndex, commandedTime, playing }) => { const canvasRef = useRef(); - const canvasPlayer = useMemo(() => CanvasPlayer({ path: filePath, width, height, streamIndex }), [filePath, width, height, streamIndex]); + const canvasPlayer = useMemo(() => CanvasPlayer({ path: filePath, width, height, streamIndex, getCanvas: () => canvasRef.current }), [filePath, width, height, streamIndex]); - useEffect(() => { - canvasPlayer.setCanvas(canvasRef.current); - - return () => { - canvasPlayer.setCanvas(); - canvasPlayer.dispose(); - }; + useEffect(() => () => { + canvasPlayer.terminate(); }, [canvasPlayer]); - const [debouncedPlayerTime, setDebouncedPlayerTime] = useState(0); - const [debouncedCommandedTime, setDebouncedCommandedTime] = useState(0); + const state = useMemo(() => { + if (playing) { + return { time: commandedTime, playing }; + } + return { time: playerTime, playing }; + }, [commandedTime, playerTime, playing]); - const [, cancelPlayerTimeDebounce] = useDebounce(() => { - setDebouncedPlayerTime(playerTime); - }, 100, [playerTime]); + const [debouncedState, { cancel }] = useDebounce(state, 300, { leading: true, equalityFn: ({ time: time1, playing: playing1 }, { time: time2, playing: playing2 }) => time1 === time2 && playing1 === playing2 }); - const [, cancelCommandedTimeDebounce] = useDebounce(() => { - setDebouncedCommandedTime(commandedTime); - }, 300, [commandedTime]); + /* useEffect(() => { + console.log('state', state); + }, [state]); */ useEffect(() => () => { - cancelPlayerTimeDebounce(); - cancelCommandedTimeDebounce(); - }, [cancelPlayerTimeDebounce, cancelCommandedTimeDebounce]); + cancel(); + }, [cancel]); useEffect(() => { - if (playing) canvasPlayer.play(debouncedCommandedTime); - else canvasPlayer.pause(debouncedPlayerTime); - }, [canvasPlayer, debouncedCommandedTime, debouncedPlayerTime, playing]); + // console.log('debouncedState', debouncedState); + + if (debouncedState.time == null) return; + + if (debouncedState.playing) { + canvasPlayer.play(debouncedState.time); + } else { + canvasPlayer.pause(debouncedState.time); + } + }, [debouncedState, canvasPlayer]); const canvasStyle = useMemo(() => ({ display: 'block', width: '100%', height: '100%', objectFit: 'contain', transform: rotate ? `rotate(${rotate}deg)` : undefined }), [rotate]); diff --git a/src/CanvasPlayer.js b/src/CanvasPlayer.js index 24e9aeedefe..4c32f7ef774 100644 --- a/src/CanvasPlayer.js +++ b/src/CanvasPlayer.js @@ -3,15 +3,14 @@ import { encodeLiveRawStream, getOneRawFrame } from './ffmpeg'; // TODO keep everything in electron land? const strtok3 = window.require('strtok3'); -export default ({ path, width: inWidth, height: inHeight, streamIndex }) => { - let canvas; - +export default ({ path, width: inWidth, height: inHeight, streamIndex, getCanvas }) => { let terminated; - let cancel; + let aborters = []; let commandedTime; let playing; function drawOnCanvas(rgbaImage, width, height) { + const canvas = getCanvas(); if (!canvas || rgbaImage.length === 0) return; canvas.width = width; @@ -23,52 +22,59 @@ export default ({ path, width: inWidth, height: inHeight, streamIndex }) => { ctx.putImageData(new ImageData(Uint8ClampedArray.from(rgbaImage), width, height), 0, 0); } - async function run() { + async function command() { let process; - let cancelled; + let aborted = false; + + function killProcess() { + if (process) { + process.kill(); + process = undefined; + } + } - cancel = () => { - cancelled = true; - if (process) process.cancel(); - cancel = undefined; - }; + function abort() { + aborted = true; + killProcess(); + aborters = aborters.filter(((aborter) => aborter !== abort)); + } + aborters.push(abort); - if (playing) { - try { + 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 buf = Buffer.allocUnsafe(size); + const rgbaImage = Buffer.allocUnsafe(size); - while (!cancelled) { + while (!aborted) { // eslint-disable-next-line no-await-in-loop - await tokenizer.readBuffer(buf, { length: size }); - if (!cancelled) drawOnCanvas(buf, width, height); + await tokenizer.readBuffer(rgbaImage, { length: size }); + if (aborted) return; + drawOnCanvas(rgbaImage, width, height); } - } catch (err) { - if (!err.isCanceled) console.warn(err.message); - } - } else { - try { + } else { const { process: processIn, width, height } = getOneRawFrame({ path, inWidth, inHeight, streamIndex, seekTo: commandedTime, outSize: 1000 }); process = processIn; const { stdout: rgbaImage } = await process; - - if (!cancelled) drawOnCanvas(rgbaImage, width, height); - } catch (err) { - if (!err.isCanceled) console.warn(err.message); + if (aborted) return; + drawOnCanvas(rgbaImage, width, height); } + } catch (err) { + if (!err.killed) console.warn(err.message); + } finally { + killProcess(); } } - function command() { - if (cancel) cancel(); - run(); + function abortAll() { + aborters.forEach((aborter) => aborter()); } function pause(seekTo) { @@ -76,6 +82,8 @@ export default ({ path, width: inWidth, height: inHeight, streamIndex }) => { if (!playing && commandedTime === seekTo) return; playing = false; commandedTime = seekTo; + + abortAll(); command(); } @@ -84,22 +92,20 @@ export default ({ path, width: inWidth, height: inHeight, streamIndex }) => { if (playing && commandedTime === playFrom) return; playing = true; commandedTime = playFrom; - command(); - } - function setCanvas(c) { - canvas = c; + abortAll(); + command(); } - function dispose() { + function terminate() { + if (terminated) return; terminated = true; - if (cancel) cancel(); + abortAll(); } return { play, pause, - setCanvas, - dispose, + terminate, }; };