Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fifty-drinks-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lottiefiles/dotlottie-web': minor
---

added a renderError event fire when a render error occurs.
3 changes: 3 additions & 0 deletions packages/web/src/dotlottie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,9 @@ export class DotLottie {
} catch (error) {
console.error('Error in animation frame:', error);

// Allows users to catch rendering errors
this._eventManager.dispatch({ type: 'renderError', error: new Error(`Error in animation frame: ${error}`) });

if (this._animationFrameId !== null) {
this._frameManager.cancelAnimationFrame(this._animationFrameId);
this._animationFrameId = null;
Expand Down
12 changes: 12 additions & 0 deletions packages/web/src/event-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type EventType =
| 'frame'
| 'load'
| 'loadError'
| 'renderError'
| 'loop'
| 'pause'
| 'play'
Expand All @@ -27,6 +28,8 @@ type EventByType<T> = T extends 'complete'
? LoadEvent
: T extends 'loadError'
? LoadErrorEvent
: T extends 'renderError'
? RenderErrorEvent
: T extends 'loop'
? LoopEvent
: T extends 'pause'
Expand Down Expand Up @@ -105,6 +108,14 @@ export interface LoadErrorEvent extends BaseEvent {
type: 'loadError';
}

/**
* Event fired when a loading error occurs.
*/
export interface RenderErrorEvent extends BaseEvent {
error: Error;
type: 'renderError';
}

/**
* Event fired when a completion action occurs.
*/
Expand Down Expand Up @@ -148,6 +159,7 @@ export type Event =
| FrameEvent
| LoadEvent
| LoadErrorEvent
| RenderErrorEvent
| CompleteEvent
| PauseEvent
| PlayEvent
Expand Down
6 changes: 6 additions & 0 deletions packages/web/src/worker/dotlottie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export class DotLottieWorker {
| 'onPlay'
| 'onStop'
| 'onLoadError'
| 'onRenderError'
| 'onReady'
| 'onLoop'
> = event.data;
Expand Down Expand Up @@ -233,6 +234,11 @@ export class DotLottieWorker {
this._eventManager.dispatch(rpcResponse.result.event);
}

if (rpcResponse.method === 'onRenderError' && rpcResponse.result.instanceId === this._id) {
// Dont update the instance, since the Core crashed its no long accessible. Calling _updateDotLottieInstanceState will cause it to hang indefinitely.
this._eventManager.dispatch(rpcResponse.result.event);
}

if (rpcResponse.method === 'onReady' && rpcResponse.result.instanceId === this._id) {
await this._updateDotLottieInstanceState();
this._eventManager.dispatch(rpcResponse.result.event);
Expand Down
15 changes: 15 additions & 0 deletions packages/web/src/worker/dotlottie.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
FrameEvent,
FreezeEvent,
LoadErrorEvent,
RenderErrorEvent,
LoadEvent,
LoopEvent,
PauseEvent,
Expand Down Expand Up @@ -74,6 +75,19 @@ const eventHandlerMap: Record<EventType, (instanceId: string) => (event: Event)

self.postMessage(response);
},
renderError: (instanceId: string) => (event: Event) => {
const renderErrorEvent = event as RenderErrorEvent;
const response: RpcResponse<'onRenderError'> = {
id: '',
method: 'onRenderError',
result: {
instanceId,
event: renderErrorEvent,
},
};

self.postMessage(response);
},
loop: (instanceId: string) => (event: Event) => {
const loopEvent = event as LoopEvent;
const response: RpcResponse<'onLoop'> = {
Expand Down Expand Up @@ -397,6 +411,7 @@ const commands: {
'frame',
'load',
'loadError',
'renderError',
'loop',
'pause',
'play',
Expand Down
5 changes: 5 additions & 0 deletions packages/web/src/worker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
PauseEvent,
PlayEvent,
ReadyEvent,
RenderErrorEvent,
RenderEvent,
StopEvent,
UnfreezeEvent,
Expand Down Expand Up @@ -224,6 +225,10 @@ export interface MethodResultMap {
event: RenderEvent;
instanceId: string;
};
onRenderError: {
event: RenderErrorEvent;
instanceId: string;
};
onStop: {
event: StopEvent;
instanceId: string;
Expand Down
25 changes: 25 additions & 0 deletions packages/web/tests/dotlottie.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { describe, beforeEach, afterEach, test, expect, vi } from 'vitest';

import type { Config, Layout, Mode } from '../src';
import { DotLottie as DotLottieClass, DotLottieWorker as DotLottieWorkerClass } from '../src';
import type { DotLottiePlayer } from '../src/core';
import { getDefaultDPR } from '../src/utils';

import { createCanvas, sleep } from './test-utils';
Expand Down Expand Up @@ -550,6 +551,30 @@ describe.each([
});
});

(isWorker ? test.skip : test)('trigger renderError event when failed to render', async () => {
const onReady = vi.fn();
const onRenderError = vi.fn();

dotLottie = new DotLottie({
canvas,
src: jsonSrc,
autoplay: true,
});

dotLottie.addEventListener('renderError', onRenderError);
dotLottie.addEventListener('ready', onReady);

await vi.waitFor(() => expect(onReady).toHaveBeenCalledTimes(1));

const dotLottieCore = (dotLottie as DotLottieClass)['_dotLottieCore'] as DotLottiePlayer;

vi.spyOn(dotLottieCore, 'render').mockImplementationOnce(() => {
throw new Error('Failed to render');
});

await vi.waitFor(() => expect(onRenderError).toHaveBeenCalledTimes(1));
});

describe('resize', () => {
test('resize the canvas drawing surface to maintain high quality animation', async () => {
dotLottie = new DotLottie({
Expand Down
Loading