Skip to content

Commit bb62428

Browse files
committed
improved track/clip removal
1 parent b819951 commit bb62428

File tree

6 files changed

+162
-38
lines changed

6 files changed

+162
-38
lines changed

src/clips/clip/clip.spec.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,7 @@ describe('The Clip Object', () => {
108108
expect(track.clips.findIndex((n) => n.id == clip.id)).toBe(-1);
109109
expect(track.clips.findIndex((n) => n.id == clip1.id)).toBe(0);
110110
expect(clip.state).toBe('READY');
111-
expect(detachFn).toBeCalledTimes(1);
112111
expect(track.view.children.length).toBe(0);
113-
114-
// try again
115-
clip.detach();
116-
expect(clip.state).toBe('READY');
117-
expect(detachFn).toBeCalledTimes(1);
118112
});
119113

120114
it('should not remove error state on detach', async () => {

src/clips/clip/clip.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -196,20 +196,7 @@ export class Clip<Props extends ClipProps = ClipProps> extends EventEmitterMixin
196196
* Remove the clip from the track
197197
*/
198198
public detach(): this {
199-
const index = this.track?.clips.findIndex((n) => n.id == this.id);
200-
201-
if (this.state == 'ATTACHED') {
202-
this.state = 'READY';
203-
}
204-
205-
if (this.view.parent && this.track) {
206-
this.track.view.removeChild(this.view);
207-
}
208-
209-
if (index != undefined && index >= 0) {
210-
this.track?.clips.splice(index, 1);
211-
this.trigger('detach', undefined);
212-
}
199+
this.track?.remove(this);
213200

214201
return this;
215202
}

src/composition/composition.spec.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,71 @@ describe('The composition', () => {
317317
expect(composition.tracks[0].clips[0]).toBeInstanceOf(TextClip);
318318
});
319319

320+
it('should be able to remove tracks', () => {
321+
const track0 = composition.createTrack('video');
322+
const track1 = composition.createTrack('text');
323+
const track2 = composition.createTrack('image');
324+
325+
expect(composition.tracks.length).toBe(3)
326+
expect(composition.stage.children.length).toBe(3);
327+
328+
const detachFn = vi.fn();
329+
composition.on('detach', detachFn);
330+
331+
let res = composition.removeTrack(track1);
332+
333+
expect(detachFn).toBeCalledTimes(1);
334+
expect(res).toBeInstanceOf(TextTrack);
335+
336+
expect(composition.tracks.length).toBe(2);
337+
expect(composition.stage.children.length).toBe(2);
338+
expect(composition.tracks[0]).toBeInstanceOf(ImageTrack);
339+
expect(composition.tracks[1]).toBeInstanceOf(VideoTrack);
340+
expect(composition.tracks.findIndex((l) => l.id == track1.id)).toBe(-1);
341+
expect(composition.stage.children.findIndex(c => c.uid == track0.view.uid)).toBe(0);
342+
expect(composition.stage.children.findIndex(c => c.uid == track2.view.uid)).toBe(1);
343+
344+
// try again
345+
res = composition.removeTrack(track1);
346+
347+
expect(res).toBeUndefined();
348+
expect(composition.tracks.length).toBe(2);
349+
expect(composition.stage.children.length).toBe(2);
350+
});
351+
352+
it('should be able to remove clips', async () => {
353+
const track0 = composition.createTrack('base');
354+
const track1 = composition.createTrack('base');
355+
356+
const clip = new Clip({ stop: 10 });
357+
358+
await track0.add(clip);
359+
await track0.add(new Clip({ stop: 20, start: 10 }));
360+
await track1.add(new Clip({ stop: 9 }));
361+
await track1.add(new Clip({ stop: 12, start: 9 }));
362+
363+
expect(track0.clips.length).toBe(2);
364+
expect(track1.clips.length).toBe(2);
365+
366+
// clip that does not exist
367+
let res = composition.remove(new Clip());
368+
369+
expect(res).toBe(undefined);
370+
expect(track0.clips.length).toBe(2);
371+
expect(track1.clips.length).toBe(2);
372+
373+
res = composition.remove(clip);
374+
375+
expect(res?.id).toBe(clip.id);
376+
expect(track0.clips.length).toBe(1);
377+
expect(track1.clips.length).toBe(2);
378+
379+
res = composition.remove(clip);
380+
381+
expect(res).toBe(undefined);
382+
expect(track0.clips.length).toBe(1);
383+
expect(track1.clips.length).toBe(2);
384+
});
320385

321386
afterEach(() => {
322387
frameMock.mockClear();

src/composition/composition.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,18 @@ export class Composition extends EventEmitterMixin<CompositionEvents, typeof Ser
240240
return clip;
241241
}
242242

243+
/**
244+
* Remove a given clip from the composition
245+
* @returns `Clip` when it has been successfully removed `undefined` otherwise
246+
*/
247+
public remove<L extends Clip>(clip: L): L | undefined {
248+
for (const track of this.tracks) {
249+
if (track.clips.find(c => c.id == clip.id)) {
250+
return track.remove(clip);
251+
}
252+
}
253+
}
254+
243255
/**
244256
* Remove all tracks that are of the specified type
245257
* @param track type to be removed
@@ -306,7 +318,7 @@ export class Composition extends EventEmitterMixin<CompositionEvents, typeof Ser
306318
*/
307319
public computeFrame(): void {
308320
if (!this.renderer) return;
309-
321+
310322
for (let i = 0; i < this.tracks.length; i++) {
311323
this.tracks[i].update(Timestamp.fromFrames(this.frame));
312324
}
@@ -461,6 +473,24 @@ export class Composition extends EventEmitterMixin<CompositionEvents, typeof Ser
461473
);
462474
}
463475

476+
/**
477+
* Remove a given track from the composition
478+
* @returns `Track` when it has been successfully removed `undefined` otherwise
479+
*/
480+
public removeTrack<T extends Track<Clip>>(track: T): T | undefined {
481+
const index = this.tracks.findIndex((t) => t.id == track.id);
482+
483+
if (track.view.parent) {
484+
this.stage.removeChild(track.view);
485+
}
486+
487+
if (index != undefined && index >= 0) {
488+
this.tracks.splice(index, 1);
489+
this.trigger('detach', undefined);
490+
return track;
491+
}
492+
}
493+
464494
private async ticker() {
465495
const interval = 1000 / FPS_DEFAULT;
466496

src/tracks/track/track.spec.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,20 +201,56 @@ describe('The Track Object', () => {
201201
expect(updateSpy2).not.toHaveBeenCalled();
202202
});
203203

204-
it('should be able to detach clips', () => {
204+
it('should be able to detach itself from the composition', () => {
205205
const comp1 = new Composition();
206206
const track1 = comp1.createTrack('base');
207207

208208
expect(comp1.stage.children.length).toBe(1);
209209

210-
const detachFn = vi.fn();
211-
track1.on('detach', detachFn);
212210
track1.detach();
213211
expect(comp1.tracks.findIndex((l) => l.id == track1.id)).toBe(-1);
214-
expect(detachFn).toBeCalledTimes(1);
215212
expect(comp1.stage.children.length).toBe(0);
216213
});
217214

215+
it('should be remove clips from the track', async () => {
216+
const clip0 = new Clip({ stop: 300 });
217+
const clip1 = new Clip({ stop: 600, start: 300 });
218+
219+
const composition = new Composition();
220+
const track = composition.createTrack('base');
221+
222+
await track.add(clip0);
223+
await track.add(clip1);
224+
225+
expect(track.clips.length).toBe(2);
226+
227+
composition.computeFrame();
228+
229+
// clip is currently getting rendered
230+
expect(track.view.children.length).toBe(1);
231+
232+
const detachFn = vi.fn();
233+
track.on('detach', detachFn);
234+
235+
track.remove(clip0);
236+
237+
expect(track.clips.length).toBe(1);
238+
expect(track.clips.findIndex((n) => n.id == clip0.id)).toBe(-1);
239+
expect(track.clips.findIndex((n) => n.id == clip1.id)).toBe(0);
240+
241+
expect(clip0.state).toBe('READY');
242+
expect(clip1.state).toBe('ATTACHED');
243+
244+
expect(detachFn).toBeCalledTimes(1);
245+
expect(track.view.children.length).toBe(0);
246+
247+
// try again
248+
track.remove(clip0);
249+
expect(clip0.state).toBe('READY');
250+
expect(detachFn).toBeCalledTimes(1);
251+
expect(track.clips.length).toBe(1);
252+
});
253+
218254
it('should realign the clips when stacked', async () => {
219255
track.stacked();
220256

src/tracks/track/track.ts

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,6 @@ export class Track<Clp extends Clip> extends EventEmitterMixin(Serializer) {
178178
clip.on('frame', () => {
179179
this.strategy.update(clip, this);
180180
});
181-
clip.on('detach', () => {
182-
this.strategy.update(clip, this);
183-
});
184181

185182
this.bubble('frame', clip);
186183
this.bubble('update', clip);
@@ -194,6 +191,30 @@ export class Track<Clp extends Clip> extends EventEmitterMixin(Serializer) {
194191
return this;
195192
}
196193

194+
/**
195+
* Remove a given clip from the track
196+
* @returns `Track` when it has been successfully removed `undefined` otherwise
197+
*/
198+
public remove<L extends Clp>(clip: L): L | undefined {
199+
const index = this.clips.findIndex((c) => c.id == clip.id);
200+
201+
if (clip.state == 'ATTACHED') {
202+
clip.state = 'READY';
203+
}
204+
205+
if (clip.view.parent) {
206+
this.view.removeChild(clip.view);
207+
}
208+
209+
if (index != undefined && index >= 0) {
210+
this.clips.splice(index, 1);
211+
this.strategy.update(clip, this);
212+
this.trigger('detach', undefined);
213+
214+
return clip;
215+
}
216+
}
217+
197218
/**
198219
* Get the first visible frame of the clip
199220
*/
@@ -219,16 +240,7 @@ export class Track<Clp extends Clip> extends EventEmitterMixin(Serializer) {
219240
* Remove the track from the composition
220241
*/
221242
public detach(): this {
222-
const index = this.composition?.tracks.findIndex((n) => n.id == this.id);
223-
224-
if (this.view.parent && this.composition) {
225-
this.composition.stage.removeChild(this.view);
226-
}
227-
228-
if (index != undefined && index >= 0) {
229-
this.composition?.tracks.splice(index, 1);
230-
this.trigger('detach', undefined);
231-
}
243+
this.composition?.removeTrack(this);
232244

233245
return this;
234246
}

0 commit comments

Comments
 (0)