Skip to content

Commit 4ef2c45

Browse files
authored
Merge pull request #15 from diffusionstudio/konstantin/refactoring/errors-and-events
Konstantin/refactoring/errors and events
2 parents a4ec610 + aa79d8c commit 4ef2c45

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+292
-124
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<img src="https://img.shields.io/badge/Made with-Typescript-blue?color=000000&logo=typescript&logoColor=ffffff" alt="Static Badge">
99
<a href="https://vitejs.dev"><img src="https://img.shields.io/badge/Powered%20by-Vite-000000?style=flat&logo=Vite&logoColor=ffffff" alt="powered by vite"></a>
1010
<a href="https://discord.gg/zPQJrNGuFB"><img src="https://img.shields.io/discord/1115673443141156924?style=flat&logo=discord&logoColor=fff&color=000000" alt="discord"></a>
11-
<a href="https://x.com/diffusionstudi0"><img src="https://img.shields.io/badge/Follow for-Updates-blue?color=000000&logo=X&logoColor=ffffff" alt="Static Badge"></a>
11+
<a href="https://x.com/diffusionmov"><img src="https://img.shields.io/badge/Follow for-Updates-blue?color=000000&logo=X&logoColor=ffffff" alt="Static Badge"></a>
1212
</p>
1313
<br/>
1414

src/clips/audio/audio.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import { MediaClip } from '../media';
99
import { AudioSource } from '../../sources';
10+
import { IOError } from '../../errors';
1011

1112
import type { Track } from '../../tracks';
1213
import type { AudioClipProps } from './audio.interfaces';
@@ -57,8 +58,10 @@ export class AudioClip extends MediaClip<AudioClipProps> {
5758
this.element.onerror = () => {
5859
this.state = 'ERROR';
5960

60-
const error = new Error('An error occurred while processing the input medium.');
61-
this.trigger('error', error);
61+
const error = new IOError({
62+
code: 'sourceNotProcessable',
63+
message: 'An error occurred while processing the input medium.',
64+
});
6265

6366
reject(this.element.error ?? error);
6467
}

src/clips/clip/clip.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Serializer, serializable } from '../../services';
1010
import { EventEmitterMixin } from '../../mixins';
1111
import { Container } from 'pixi.js';
1212
import { replaceKeyframes } from './clip.utils';
13+
import { ValidationError } from '../../errors';
1314

1415
import type { ClipEvents, ClipState, ClipType } from './clip.types';
1516
import type { frame } from '../../types';
@@ -211,10 +212,16 @@ export class Clip<Props extends ClipProps = ClipProps> extends EventEmitterMixin
211212

212213
// invalid cases
213214
if (!time || time.millis <= this.start.millis || time.millis >= this.stop.millis) {
214-
throw new Error("Cannot split clip at the specified time")
215+
throw new ValidationError({
216+
code: 'splitOutOfRange',
217+
message: 'Cannot split clip at the specified time'
218+
})
215219
}
216220
if (!this.track) {
217-
throw new Error('Split must be connected to a track')
221+
throw new ValidationError({
222+
code: 'trackNotAttached',
223+
message: 'Track must be attached to a track',
224+
})
218225
}
219226

220227
const copy = this.copy() as this;

src/clips/clip/clip.types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,9 @@ export type ClipState = 'IDLE' | 'LOADING' | 'ATTACHED' | 'READY' | 'ERROR';
1313

1414
export type ClipEvents = {
1515
offsetBy: Timestamp;
16+
update: any;
17+
frame: number | undefined;
18+
attach: undefined;
19+
detach: undefined;
20+
load: undefined;
1621
};

src/clips/html/html.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { HtmlSource } from '../../sources';
99
import { Clip } from '../clip';
1010
import { VisualMixin, visualize } from '../mixins';
1111
import { Sprite, Texture } from 'pixi.js';
12+
import { IOError } from '../../errors';
1213

1314
import type { Track } from '../../tracks';
1415
import type { HtmlClipProps } from './html.interfaces';
@@ -57,7 +58,7 @@ export class HtmlClip extends VisualMixin(Clip<HtmlClipProps>) {
5758
this.context.imageSmoothingEnabled = false;
5859
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
5960
this.context.drawImage(this.element, 0, 0);
60-
61+
6162
this.sprite.texture = Texture.from(this.canvas, true);
6263

6364
this.trigger('load', undefined);
@@ -66,8 +67,11 @@ export class HtmlClip extends VisualMixin(Clip<HtmlClipProps>) {
6667
this.element.addEventListener('error', (e) => {
6768
console.error(e);
6869
this.state = 'ERROR';
70+
this.trigger('error', new IOError({
71+
code: 'sourceNotProcessable',
72+
message: 'An error occurred while processing the input medium.',
73+
}));
6974
if (this.track) this.detach();
70-
this.trigger('error', new Error('An error occurred while processing the input medium.'));
7175
});
7276

7377
this.on('update', async () => {
@@ -85,15 +89,21 @@ export class HtmlClip extends VisualMixin(Clip<HtmlClipProps>) {
8589
const height = this.source.document?.body?.scrollHeight;
8690

8791
if (!width || !height) {
88-
return reject(new Error('This html document cannot be displayed!'))
92+
return reject(new IOError({
93+
code: 'sourceNotProcessable',
94+
message: 'Cannot display source with height or width at 0',
95+
}))
8996
}
9097

9198
this.state = 'READY';
9299
resolve();
93100
}
94101
this.element.onerror = (e) => {
95102
console.error(e);
96-
reject(new Error('An error occurred while processing the input medium.'));
103+
reject(new IOError({
104+
code: 'sourceNotProcessable',
105+
message: 'An error occurred while processing the input medium.',
106+
}));
97107
}
98108
});
99109
}

src/clips/image/image.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Sprite, Texture } from 'pixi.js';
99
import { ImageSource } from '../../sources';
1010
import { Clip } from '../clip';
1111
import { VisualMixin, visualize } from '../mixins';
12+
import { IOError } from '../../errors';
1213

1314
import type { Track } from '../../tracks';
1415
import type { ImageClipProps } from './image.interfaces';
@@ -53,7 +54,10 @@ export class ImageClip extends VisualMixin(Clip<ImageClipProps>) {
5354
this.element.onerror = (e) => {
5455
console.error(e);
5556
this.state = 'ERROR';
56-
reject(new Error('An error occurred while processing the input medium.'));
57+
reject(new IOError({
58+
code: 'sourceNotProcessable',
59+
message: 'An error occurred while processing the input medium.',
60+
}));
5761
}
5862
});
5963
}

src/clips/media/media.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { AudioSource } from '../../sources';
1010
import { RangeDeserializer } from './media.deserializer';
1111
import { serializable, } from '../../services';
1212
import { replaceKeyframes } from '../clip/clip.utils';
13-
import { ValidationError } from '../../errors';
13+
import { ReferenceError, ValidationError } from '../../errors';
1414
import { Clip } from '../clip';
1515

1616
import type { CaptionPresetStrategy, CaptionTrack } from '../../tracks';
@@ -164,7 +164,10 @@ export class MediaClip<Props extends MediaClipProps = MediaClipProps> extends Cl
164164
public seek(time: Timestamp): Promise<void> {
165165
return new Promise((resolve, reject) => {
166166
if (!this.element) {
167-
return reject(new Error("Can't seek on element becaused it's not defined"));
167+
return reject(new ReferenceError({
168+
code: 'elementNotDefined',
169+
message: 'Cannot seek on undefined element',
170+
}));
168171
}
169172
if (time.millis < this.start.millis || time.millis > this.stop.millis) {
170173
time = this.start;
@@ -194,7 +197,10 @@ export class MediaClip<Props extends MediaClipProps = MediaClipProps> extends Cl
194197

195198
// start is larger than the stop
196199
if (start.millis >= stop.millis) {
197-
throw new Error("Start can't lower than or equal the stop");
200+
throw new ValidationError({
201+
code: 'invalidKeyframe',
202+
message: "Start can't lower than or equal the stop"
203+
});
198204
}
199205
// start and/or stop are out of bounds
200206
if (start.millis < 0) {
@@ -240,10 +246,16 @@ export class MediaClip<Props extends MediaClipProps = MediaClipProps> extends Cl
240246

241247
// invalid cases
242248
if (!time || time.millis <= this.start.millis || time.millis >= this.stop.millis) {
243-
throw new Error("Cannot split clip at the specified time")
249+
throw new ValidationError({
250+
code: 'invalidKeyframe',
251+
message: 'Cannot split clip at the specified time',
252+
});
244253
}
245254
if (!this.track) {
246-
throw new Error('Split must be connected to a track')
255+
throw new ReferenceError({
256+
code: 'trackNotDefined',
257+
message: 'Clip must be attached to a track',
258+
});
247259
}
248260

249261
// slice relative to the offset
@@ -267,7 +279,7 @@ export class MediaClip<Props extends MediaClipProps = MediaClipProps> extends Cl
267279
public async addCaptions(strategy?: CaptionPresetStrategy | (new () => CaptionPresetStrategy)): Promise<CaptionTrack> {
268280
if (!this.track?.composition) {
269281
throw new ValidationError({
270-
i18n: 'compositionNotDefined',
282+
code: 'compositionNotDefined',
271283
message: 'Captions can only be generated after the clip has been added to the composition',
272284
});
273285
}

src/clips/utils/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function parseMimeType(mimeType: string): MimeType {
1919
if (!Object.keys(SUPPORTED_MIME_TYPES.MIXED).includes(mimeType)) {
2020
throw new errors.ValidationError({
2121
message: `${mimeType} is not an accepted mime type`,
22-
i18n: 'invalid_mimetype',
22+
code: 'invalid_mimetype',
2323
});
2424
}
2525
return mimeType as MimeType;

src/clips/video/video.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { MediaClip } from '../media';
1313
import { textureSwap } from './video.decorator';
1414
import { VisualMixin, visualize } from '../mixins';
1515
import { FPS_DEFAULT, Timestamp } from '../../models';
16+
import { IOError } from '../../errors';
1617

1718
import type { Track } from '../../tracks';
1819
import type { InitMessageData } from './worker.types';
@@ -90,8 +91,10 @@ export class VideoClip extends VisualMixin(MediaClip<VideoClipProps>) {
9091
this.element.onerror = () => {
9192
this.state = 'ERROR';
9293

93-
const error = new Error('An error occurred while processing the input medium.');
94-
this.trigger('error', error);
94+
const error = new IOError({
95+
code: 'sourceNotProcessable',
96+
message: 'An error occurred while processing the input medium.',
97+
});
9598

9699
reject(this.element.error ?? error);
97100
}

src/composition/composition.spec.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
* Public License, v. 2.0 that can be found in the LICENSE file.
66
*/
77

8-
import { describe, expect, it, beforeEach, vi, afterEach, afterAll } from 'vitest';
8+
import { describe, expect, it, beforeEach, vi, afterEach, afterAll, MockInstance } from 'vitest';
99
import { Composition } from './composition';
1010
import { Clip, TextClip } from '../clips';
1111
import { AudioTrack, CaptionTrack, HtmlTrack, ImageTrack, TextTrack, Track, VideoTrack } from '../tracks';
1212
import { Timestamp } from '../models';
1313

1414
describe('The composition', () => {
1515
let composition: Composition;
16+
let computeMock: MockInstance<() => void>;
17+
1618
const frameMock = vi.fn();
1719
const playMock = vi.fn();
1820
const pauseMock = vi.fn();
@@ -33,6 +35,7 @@ describe('The composition', () => {
3335
composition.state = 'IDLE';
3436

3537
localStorage.clear();
38+
computeMock = vi.spyOn(composition, 'computeFrame');
3639
});
3740

3841
it('should initialize with default settings', () => {
@@ -173,7 +176,7 @@ describe('The composition', () => {
173176
expect(composition.duration.seconds).toBe(0.5);
174177

175178
const seekMock = vi.spyOn(track, 'seek');
176-
const computeMock = vi.spyOn(composition, 'computeFrame');
179+
computeMock.mockClear();
177180

178181
const frameCallbacks: number[] = [];
179182
composition.on('currentframe', (evt) => frameCallbacks.push(evt.detail));
@@ -381,6 +384,36 @@ describe('The composition', () => {
381384
expect(track1.clips.length).toBe(2);
382385
});
383386

387+
it('should redraw the composition when it changes', async () => {
388+
expect(composition.duration.frames).toBe(0);
389+
expect(computeMock).toBeCalledTimes(0);
390+
391+
const track = composition.createTrack('base');
392+
393+
expect(composition.duration.frames).toBe(0);
394+
expect(computeMock).toBeCalledTimes(1);
395+
396+
await track.add(new Clip({ stop: 20 }));
397+
398+
expect(composition.duration.frames).toBe(20);
399+
expect(computeMock).toBeCalledTimes(2);
400+
401+
const clip = await track.add(new Clip({ start: 30, stop: 60 }));
402+
403+
expect(composition.duration.frames).toBe(60);
404+
expect(computeMock).toBeCalledTimes(3);
405+
406+
clip.stop = 80;
407+
408+
expect(composition.duration.frames).toBe(80);
409+
expect(computeMock).toBeCalledTimes(4);
410+
411+
track.remove(clip);
412+
413+
expect(composition.duration.frames).toBe(20);
414+
expect(computeMock).toBeCalledTimes(5);
415+
});
416+
384417
afterEach(() => {
385418
frameMock.mockClear();
386419
playMock.mockClear();

0 commit comments

Comments
 (0)