Skip to content

Commit

Permalink
try to improve canvas player #272
Browse files Browse the repository at this point in the history
  • Loading branch information
mifi committed Feb 26, 2022
1 parent b5ba38a commit 4733ba6
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 61 deletions.
51 changes: 27 additions & 24 deletions src/Canvas.jsx
Original file line number Diff line number Diff line change
@@ -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]);

Expand Down
80 changes: 43 additions & 37 deletions src/CanvasPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,59 +22,68 @@ 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) {
if (terminated) return;
if (!playing && commandedTime === seekTo) return;
playing = false;
commandedTime = seekTo;

abortAll();
command();
}

Expand All @@ -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,
};
};

0 comments on commit 4733ba6

Please sign in to comment.