Skip to content

Commit

Permalink
Update error handling in makeVideoScreenshot
Browse files Browse the repository at this point in the history
Co-authored-by: trevor-signal <[email protected]>
  • Loading branch information
automated-signal and trevor-signal authored Mar 22, 2024
1 parent 495f008 commit 31a7556
Showing 1 changed file with 49 additions and 28 deletions.
77 changes: 49 additions & 28 deletions ts/types/VisualAttachment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { strictAssert } from '../util/assert';
import { canvasToBlob } from '../util/canvasToBlob';
import { KIBIBYTE } from './AttachmentSize';
import { explodePromise } from '../util/explodePromise';
import { SECOND } from '../util/durations';

export { blobToArrayBuffer };

Expand Down Expand Up @@ -209,44 +210,64 @@ export type MakeVideoScreenshotOptionsType = Readonly<{
logger: Pick<LoggerType, 'error'>;
}>;

async function loadVideo({
const MAKE_VIDEO_SCREENSHOT_TIMEOUT = 30 * SECOND;

function captureScreenshot(
video: HTMLVideoElement,
contentType: MIMEType
): Promise<Blob> {
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const context = canvas.getContext('2d');
strictAssert(context, 'Failed to get canvas context');
context.drawImage(video, 0, 0, canvas.width, canvas.height);
return canvasToBlob(canvas, contentType);
}

export async function makeVideoScreenshot({
objectUrl,
contentType = IMAGE_PNG,
logger,
}: MakeVideoScreenshotOptionsType): Promise<HTMLVideoElement> {
}: MakeVideoScreenshotOptionsType): Promise<Blob> {
const signal = AbortSignal.timeout(MAKE_VIDEO_SCREENSHOT_TIMEOUT);
const video = document.createElement('video');
const { promise, resolve, reject } = explodePromise();
video.addEventListener('loadeddata', resolve);

const { promise: videoLoadedAndSeeked, resolve, reject } = explodePromise();

function onLoaded() {
if (signal.aborted) {
return;
}
video.addEventListener('seeked', resolve);
video.currentTime = 1.0;
}

function onAborted() {
reject(signal.reason);
}

video.addEventListener('loadeddata', onLoaded);
video.addEventListener('error', reject);
video.src = objectUrl;
signal.addEventListener('abort', onAborted);

try {
await promise;
video.src = objectUrl;
await videoLoadedAndSeeked;
return await captureScreenshot(video, contentType);
} catch (error) {
logger.error('loadVideo error', toLogFormat(video.error));
logger.error('makeVideoScreenshot error:', toLogFormat(error));
throw error;
} finally {
video.removeEventListener('loadeddata', resolve);
// hard reset the video element so it doesn't keep loading
video.src = '';
video.load();

video.removeEventListener('loadeddata', onLoaded);
video.removeEventListener('error', reject);
video.removeEventListener('seeked', resolve);
signal.removeEventListener('abort', onAborted);
}
return video;
}

export async function makeVideoScreenshot({
objectUrl,
contentType = IMAGE_PNG,
logger,
}: MakeVideoScreenshotOptionsType): Promise<Blob> {
const video = await loadVideo({ objectUrl, logger });
await new Promise<unknown>(res => {
video.currentTime = 1.0;
video.addEventListener('seeked', res, { once: true });
});
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const context = canvas.getContext('2d');
strictAssert(context, 'Failed to get canvas context');
context.drawImage(video, 0, 0, canvas.width, canvas.height);
return canvasToBlob(canvas, contentType);
}

export function makeObjectUrl(
Expand Down

0 comments on commit 31a7556

Please sign in to comment.