From d30da286dc2f91c0c3a68ca0c41bece0f9e15826 Mon Sep 17 00:00:00 2001 From: trevor-signal <131492920+trevor-signal@users.noreply.github.com> Date: Thu, 21 Mar 2024 18:54:14 -0400 Subject: [PATCH] Update error handling in makeVideoScreenshot --- ts/types/VisualAttachment.ts | 81 +++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 30 deletions(-) diff --git a/ts/types/VisualAttachment.ts b/ts/types/VisualAttachment.ts index 67e130393c8..1a730ca9a17 100644 --- a/ts/types/VisualAttachment.ts +++ b/ts/types/VisualAttachment.ts @@ -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 }; @@ -209,37 +210,12 @@ export type MakeVideoScreenshotOptionsType = Readonly<{ logger: Pick; }>; -async function loadVideo({ - objectUrl, - logger, -}: MakeVideoScreenshotOptionsType): Promise { - const video = document.createElement('video'); - const { promise, resolve, reject } = explodePromise(); - video.addEventListener('loadeddata', resolve); - video.addEventListener('error', reject); - video.src = objectUrl; - try { - await promise; - } catch (error) { - logger.error('loadVideo error', toLogFormat(video.error)); - throw error; - } finally { - video.removeEventListener('loadeddata', resolve); - video.removeEventListener('error', reject); - } - return video; -} +const MAKE_VIDEO_SCREENSHOT_TIMEOUT = 30 * SECOND; -export async function makeVideoScreenshot({ - objectUrl, - contentType = IMAGE_PNG, - logger, -}: MakeVideoScreenshotOptionsType): Promise { - const video = await loadVideo({ objectUrl, logger }); - await new Promise(res => { - video.currentTime = 1.0; - video.addEventListener('seeked', res, { once: true }); - }); +function captureScreenshot( + video: HTMLVideoElement, + contentType: MIMEType +): Promise { const canvas = document.createElement('canvas'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; @@ -249,6 +225,51 @@ export async function makeVideoScreenshot({ return canvasToBlob(canvas, contentType); } +export async function makeVideoScreenshot({ + objectUrl, + contentType = IMAGE_PNG, + logger, +}: MakeVideoScreenshotOptionsType): Promise { + const signal = AbortSignal.timeout(MAKE_VIDEO_SCREENSHOT_TIMEOUT); + const video = document.createElement('video'); + + 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); + signal.addEventListener('abort', onAborted); + + try { + video.src = objectUrl; + await videoLoadedAndSeeked; + return await captureScreenshot(video, contentType); + } catch (error) { + logger.error('makeVideoScreenshot error:', toLogFormat(error)); + throw error; + } finally { + // 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); + } +} + export function makeObjectUrl( data: Uint8Array | ArrayBuffer, contentType: MIMEType