HTML5Video ops: use async/await

This commit is contained in:
Enrico Ros
2024-02-03 00:33:52 -08:00
parent 53255d5524
commit 843a8dcd69
2 changed files with 41 additions and 25 deletions
@@ -7,7 +7,7 @@ import InfoIcon from '@mui/icons-material/Info';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import { InlineError } from '~/common/components/InlineError';
import { downloadVideoFrameAsPNG, renderVideoFrameToFile } from '~/common/util/videoUtils';
import { downloadVideoFrameAsPNG, renderVideoFrameAsPNGFile } from '~/common/util/videoUtils';
import { useCameraCapture } from '~/common/components/useCameraCapture';
@@ -16,11 +16,12 @@ export function CameraCaptureModal(props: {
onAttachImage: (file: File) => void
// onOCR: (ocrText: string) => void }
}) {
// state
// const [ocrProgress/*, setOCRProgress*/] = React.useState<number | null>(null);
const [showInfo, setShowInfo] = React.useState(false);
// camera operations
// state
const [showInfo, setShowInfo] = React.useState(false);
// const [ocrProgress/*, setOCRProgress*/] = React.useState<number | null>(null);
// external state
const {
videoRef,
cameras, cameraIdx, setCameraIdx,
@@ -29,10 +30,14 @@ export function CameraCaptureModal(props: {
} = useCameraCapture();
const stopAndClose = () => {
// derived state
const { onCloseModal, onAttachImage } = props;
const stopAndClose = React.useCallback(() => {
resetVideo();
props.onCloseModal();
};
onCloseModal();
}, [onCloseModal, resetVideo]);
/*const handleVideoOCRClicked = async () => {
if (!videoRef.current) return;
@@ -53,18 +58,21 @@ export function CameraCaptureModal(props: {
props.onOCR(result.data.text);
};*/
const handleVideoSnapClicked = () => {
const handleVideoSnapClicked = React.useCallback(async () => {
if (!videoRef.current) return;
renderVideoFrameToFile(videoRef.current, 'camera', (file) => {
props.onAttachImage(file);
try {
const file = await renderVideoFrameAsPNGFile(videoRef.current, 'camera');
onAttachImage(file);
stopAndClose();
});
};
} catch (error) {
console.error('Error capturing video frame:', error);
}
}, [onAttachImage, stopAndClose, videoRef]);
const handleVideoDownloadClicked = () => {
const handleVideoDownloadClicked = React.useCallback(() => {
if (!videoRef.current) return;
downloadVideoFrameAsPNG(videoRef.current, 'camera');
};
}, [videoRef]);
return (
+18 -10
View File
@@ -5,7 +5,7 @@
*/
export function downloadVideoFrameAsPNG(videoElement: HTMLVideoElement, prefixName: string) {
// video to canvas to png
// current video frame -> canvas -> dataURL PNG
const renderedFrame = _renderVideoFrameToCanvas(videoElement);
const imageDataURL = renderedFrame.toDataURL('image/png');
@@ -13,20 +13,20 @@ export function downloadVideoFrameAsPNG(videoElement: HTMLVideoElement, prefixNa
const link = document.createElement('a');
link.download = _prettyFileName(prefixName, renderedFrame);
link.href = imageDataURL;
document.body.appendChild(link); // Ensure visibility in the DOM for Firefox
link.click();
document.body.removeChild(link); // Clean up
}
export function renderVideoFrameToFile(videoElement: HTMLVideoElement, prefixName: string, callback: (file: File) => void) {
// video to canvas
export async function renderVideoFrameAsPNGFile(videoElement: HTMLVideoElement, prefixName: string): Promise<File> {
// current video frame -> canvas -> Blob PNG
const renderedFrame = _renderVideoFrameToCanvas(videoElement);
const blob = await _canvasToBlob(renderedFrame, 'image/png');
// canvas to blob to file to callback
renderedFrame.toBlob((blob) => {
if (blob) {
const file = new File([blob], _prettyFileName(prefixName, renderedFrame), { type: blob.type });
callback(file);
}
}, 'image/png');
// to File
if (!blob)
throw new Error('Failed to convert canvas to Blob');
return new File([blob], _prettyFileName(prefixName, renderedFrame), { type: blob.type });
}
function _prettyFileName(prefixName: string, renderedFrame: HTMLCanvasElement) {
@@ -45,3 +45,11 @@ function _renderVideoFrameToCanvas(videoElement: HTMLVideoElement): HTMLCanvasEl
return canvas;
}
/**
* Creates a Blob object representing the image contained in the canvas
* @param canvas The canvas element to convert to a Blob.
* @param imageFormat Browsers are required to support image/png; many will support additional formats including image/jpeg and image/webp.
*/
async function _canvasToBlob(canvas: HTMLCanvasElement, imageFormat: string = 'image/png'): Promise<Blob | null> {
return new Promise((resolve) => canvas.toBlob(resolve, imageFormat));
}