diff --git a/packages/2d/src/lib/components/Audio.ts b/packages/2d/src/lib/components/Audio.ts index c85ce536..adab4b39 100644 --- a/packages/2d/src/lib/components/Audio.ts +++ b/packages/2d/src/lib/components/Audio.ts @@ -40,8 +40,8 @@ export class Audio extends Media { } DependencyContext.collectPromise( - new Promise(resolve => { - this.waitForCanPlay(audio, resolve); + new Promise((resolve, reject) => { + this.waitForCanPlay(audio, resolve, reject); }), ); diff --git a/packages/2d/src/lib/components/Img.ts b/packages/2d/src/lib/components/Img.ts index e803d5b6..8daee330 100644 --- a/packages/2d/src/lib/components/Img.ts +++ b/packages/2d/src/lib/components/Img.ts @@ -2,7 +2,6 @@ import { BBox, Color, DependencyContext, - DetailedError, PossibleVector2, SerializedVector2, SignalValue, @@ -159,18 +158,9 @@ export class Img extends Asset { image.addEventListener('load', resolve); image.addEventListener('error', () => reject( - new DetailedError({ - message: `Failed to load an image`, - remarks: `\ -The src property was set to: -
${rawSrc}
-...which resolved to the following url: -
${src}
-Make sure that source is correct and that the image exists.
-Learn more -about working with images.`, - inspect: this.key, - }), + new Error( + `Failed to load the image: ${src} for node ${this.key}. If you are sure that the url is correct, this might be a CORS error.`, + ), ), ); }), diff --git a/packages/2d/src/lib/components/Media.ts b/packages/2d/src/lib/components/Media.ts index 0cd16183..82781674 100644 --- a/packages/2d/src/lib/components/Media.ts +++ b/packages/2d/src/lib/components/Media.ts @@ -218,12 +218,18 @@ export abstract class Media extends Asset { } protected scheduleSeek(time: number) { - this.waitForCanPlay(this.mediaElement(), () => { - const media = this.mediaElement(); - // Wait until the media is ready to seek again as - // setting the time before the video doesn't work reliably. - media.currentTime = time; - }); + this.waitForCanPlay( + this.mediaElement(), + () => { + const media = this.mediaElement(); + // Wait until the media is ready to seek again as + // setting the time before the video doesn't work reliably. + media.currentTime = time; + }, + (error: string) => { + throw new Error(error); + }, + ); } /** @@ -233,7 +239,11 @@ export abstract class Media extends Asset { * @param onCanPlay - The function to call when the media is ready to play. * @returns */ - protected waitForCanPlay(media: HTMLMediaElement, onCanPlay: () => void) { + protected waitForCanPlay( + media: HTMLMediaElement, + onCanPlay: () => void, + onErrorMessage: (message: string) => void, + ) { if (media.readyState >= 2) { onCanPlay(); return; @@ -246,8 +256,9 @@ export abstract class Media extends Asset { const onError = () => { const reason = this.getErrorReason(media.error?.code); - console.log(`ERROR: Error loading video: ${this.src()}, ${reason}`); - media.removeEventListener('error', onError); + onErrorMessage( + `Failed to load media: ${this.src()} for node ${this.key}: ${reason}. If you are sure that the url is correct, this might be a CORS error.`, + ); }; media.addEventListener('canplay', onCanPlayWrapper); diff --git a/packages/2d/src/lib/components/Video.ts b/packages/2d/src/lib/components/Video.ts index ac1fe3ed..6079a90d 100644 --- a/packages/2d/src/lib/components/Video.ts +++ b/packages/2d/src/lib/components/Video.ts @@ -136,8 +136,8 @@ export class Video extends Media { } DependencyContext.collectPromise( - new Promise(resolve => { - this.waitForCanPlay(video, resolve); + new Promise((resolve, reject) => { + this.waitForCanPlay(video, resolve, reject); }), ); diff --git a/packages/2d/src/lib/utils/video/ffmpeg-client.ts b/packages/2d/src/lib/utils/video/ffmpeg-client.ts index 02f10408..b2eb4db8 100644 --- a/packages/2d/src/lib/utils/video/ffmpeg-client.ts +++ b/packages/2d/src/lib/utils/video/ffmpeg-client.ts @@ -1,3 +1,5 @@ +import {verifyFetchResponse} from '@revideo/core'; + export class ImageCommunication { public constructor() { if (!import.meta.hot) { @@ -27,9 +29,10 @@ export class ImageCommunication { }), }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + await verifyFetchResponse( + response, + "Didn't get frame from /revideo-ffmpeg-decoder/video-frame", + ); const width = parseInt(response.headers.get('X-Frame-Width') || '1080', 10); const height = parseInt( diff --git a/packages/core/src/app/Renderer.ts b/packages/core/src/app/Renderer.ts index 11e9f0e7..8802bb0b 100644 --- a/packages/core/src/app/Renderer.ts +++ b/packages/core/src/app/Renderer.ts @@ -167,6 +167,8 @@ export class Renderer { this.finished.dispatch(result); this.sharedWebGLContext.dispose(); this.lock.release(); + + return result; } /** diff --git a/packages/core/src/exporter/FFmpegExporter.ts b/packages/core/src/exporter/FFmpegExporter.ts index 366afc75..07f346c6 100644 --- a/packages/core/src/exporter/FFmpegExporter.ts +++ b/packages/core/src/exporter/FFmpegExporter.ts @@ -8,6 +8,7 @@ import {EventDispatcher} from '../events'; import {BoolMetaField, MetaField, ObjectMetaField, ValueOf} from '../meta'; import {Exporter} from './Exporter'; import {download} from './download-videos'; +import {verifyFetchResponse} from './utils'; type ServerResponse = | { @@ -113,7 +114,7 @@ export class FFmpegExporterClient implements Exporter { public async stop(result: RendererResult): Promise { await this.invoke('end', result); - await fetch('/revideo-ffmpeg-decoder/finished', { + const response = await fetch('/revideo-ffmpeg-decoder/finished', { method: 'POST', headers: { // eslint-disable-next-line @@ -121,6 +122,8 @@ export class FFmpegExporterClient implements Exporter { }, body: JSON.stringify({}), }); + + await verifyFetchResponse(response, '/revideo-ffmpeg-decoder/finished'); } public async kill(): Promise { @@ -136,7 +139,7 @@ export class FFmpegExporterClient implements Exporter { startFrame: number, endFrame: number, ): Promise { - await fetch('/audio-processing/generate-audio', { + const response = await fetch('/audio-processing/generate-audio', { method: 'POST', body: JSON.stringify({ tempDir: `revideo-${this.settings.name}-${this.settings.hiddenFolderId}`, @@ -146,19 +149,23 @@ export class FFmpegExporterClient implements Exporter { fps: this.settings.fps, }), }); + + await verifyFetchResponse(response, '/audio-processing/generate-audio'); } public async mergeMedia(): Promise { const outputFilename = this.settings.name; const tempDir = `revideo-${this.settings.name}-${this.settings.hiddenFolderId}`; - await fetch('/audio-processing/merge-media', { + const response = await fetch('/audio-processing/merge-media', { method: 'POST', body: JSON.stringify({ outputFilename, tempDir, }), }); + + await verifyFetchResponse(response, '/audio-processing/merge-media'); } /** diff --git a/packages/core/src/exporter/WasmExporter.ts b/packages/core/src/exporter/WasmExporter.ts index da307842..c42184c2 100644 --- a/packages/core/src/exporter/WasmExporter.ts +++ b/packages/core/src/exporter/WasmExporter.ts @@ -4,6 +4,7 @@ import type {AssetInfo, RendererSettings} from '../app/Renderer'; import {MetaField, ObjectMetaField} from '../meta'; import {Exporter} from './Exporter'; import {download} from './download-videos'; +import {verifyFetchResponse} from './utils'; export class WasmExporter implements Exporter { public static readonly id = '@revideo/core/wasm'; @@ -42,6 +43,7 @@ export class WasmExporter implements Exporter { public async handleFrame(canvas: HTMLCanvasElement): Promise { const frame = new VideoFrame(canvas, {timestamp: 0}); await this.encoder.addFrame(frame); + frame.close(); } @@ -55,7 +57,7 @@ export class WasmExporter implements Exporter { `revideo-${this.settings.name}-${this.settings.hiddenFolderId}`, ); - await fetch('/revideo-ffmpeg-decoder/finished', { + const finishResponse = await fetch('/revideo-ffmpeg-decoder/finished', { method: 'POST', headers: { // eslint-disable-next-line @@ -64,10 +66,17 @@ export class WasmExporter implements Exporter { body: JSON.stringify({}), }); - await fetch('/uploadVideoFile', { + await verifyFetchResponse( + finishResponse, + '/revideo-ffmpeg-decoder/finished', + ); + + const uploadResponse = await fetch('/uploadVideoFile', { method: 'POST', body: formData, }); + + await verifyFetchResponse(uploadResponse, '/uploadVideoFile'); } public async generateAudio( @@ -75,7 +84,7 @@ export class WasmExporter implements Exporter { startFrame: number, endFrame: number, ): Promise { - await fetch('/audio-processing/generate-audio', { + const response = await fetch('/audio-processing/generate-audio', { method: 'POST', body: JSON.stringify({ tempDir: `revideo-${this.settings.name}-${this.settings.hiddenFolderId}`, @@ -85,19 +94,25 @@ export class WasmExporter implements Exporter { fps: this.settings.fps, }), }); + + console.log('generate audio response aahhh OK????', response.ok); + + await verifyFetchResponse(response, '/audio-processing/generate-audio'); } public async mergeMedia(): Promise { const outputFilename = this.settings.name; const tempDir = `revideo-${this.settings.name}-${this.settings.hiddenFolderId}`; - await fetch('/audio-processing/merge-media', { + const response = await fetch('/audio-processing/merge-media', { method: 'POST', body: JSON.stringify({ outputFilename, tempDir, }), }); + + await verifyFetchResponse(response, '/audio-processing/merge-media'); } public async downloadVideos(assets: AssetInfo[][]): Promise { diff --git a/packages/core/src/exporter/download-videos.ts b/packages/core/src/exporter/download-videos.ts index 3342fd61..7f01253c 100644 --- a/packages/core/src/exporter/download-videos.ts +++ b/packages/core/src/exporter/download-videos.ts @@ -1,4 +1,5 @@ import {AssetInfo} from '../app'; +import {verifyFetchResponse} from './utils'; export async function download(assets: AssetInfo[][]): Promise { const videoRanges: Map = new Map(); @@ -47,9 +48,10 @@ export async function download(assets: AssetInfo[][]): Promise { }, ); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + await verifyFetchResponse( + response, + '/revideo-ffmpeg-decoder/download-video-chunks', + ); const result = await response.json(); diff --git a/packages/core/src/exporter/index.ts b/packages/core/src/exporter/index.ts index e6e09663..8c541235 100644 --- a/packages/core/src/exporter/index.ts +++ b/packages/core/src/exporter/index.ts @@ -2,3 +2,4 @@ export * from './Exporter'; export * from './FFmpegExporter'; export * from './ImageExporter'; export * from './WasmExporter'; +export * from './utils'; diff --git a/packages/core/src/exporter/utils.ts b/packages/core/src/exporter/utils.ts new file mode 100644 index 00000000..a8022e30 --- /dev/null +++ b/packages/core/src/exporter/utils.ts @@ -0,0 +1,9 @@ +export async function verifyFetchResponse(response: Response, remarks: string) { + if (response.ok) { + return; + } + + throw new Error( + `ERROR: ${remarks}. response returned status code ${response.status}.`, + ); +} diff --git a/packages/renderer/client/render.ts b/packages/renderer/client/render.ts index 3c33437e..a23eb6c5 100644 --- a/packages/renderer/client/render.ts +++ b/packages/renderer/client/render.ts @@ -1,4 +1,4 @@ -import {Project, Renderer, Vector2} from '@revideo/core'; +import {Project, Renderer, RendererResult, Vector2} from '@revideo/core'; declare global { interface Window { @@ -54,10 +54,15 @@ export const render = async ( renderSettings.size = new Vector2({x: videoWidth, y: videoHeight}); } - await renderer.render(renderSettings); - window.onRenderComplete(); + const result = await renderer.render(renderSettings); + if (result === RendererResult.Success) { + window.onRenderComplete(); + } else { + await new Promise(resolve => setTimeout(resolve, 3000)); // wait for two seconds for error to be handled + window.onRenderFailed('Render process failed'); + } } catch (e: any) { - window.onRenderFailed(e.message); + window.onRenderFailed(e.message ?? e); } }; diff --git a/packages/renderer/server/render-video.ts b/packages/renderer/server/render-video.ts index ef070114..8d3841d5 100644 --- a/packages/renderer/server/render-video.ts +++ b/packages/renderer/server/render-video.ts @@ -207,10 +207,6 @@ async function renderVideoOnPage( clearInterval(interval); reject(new Error(errorMessage)); }); - - page.exposeFunction('browserError', (message: string) => { - reject(new Error(message)); - }); }); await page.goto(url);