From bdb0bee598f3eb17896637372c9d5d402443aba3 Mon Sep 17 00:00:00 2001 From: beefchimi Date: Wed, 20 Dec 2023 17:44:46 -0500 Subject: [PATCH] :sparkles: [Sound] Manually emit ended event on .stop() but never started --- src/Sound.ts | 23 ++++++++++++++++++----- src/tests/Sound.test.ts | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/Sound.ts b/src/Sound.ts index 0feace4..6bbb5d7 100644 --- a/src/Sound.ts +++ b/src/Sound.ts @@ -35,8 +35,8 @@ export class Sound extends EmittenCommon { this.#source.connect(this.#gainNode).connect(this.destination); this.#gainNode.gain.setValueAtTime(this._volume, this.context.currentTime); - // We could `emit` a "created" event, but it wouldn't get caught - // by any listeners, since those cannot be attached until after creation. + // The `ended` event is fired either when the sound has played its full duration, + // or the `.stop()` method has been called. this.#source.addEventListener('ended', this.#handleEnded, {once: true}); } @@ -129,7 +129,16 @@ export class Sound extends EmittenCommon { stop() { this.#setState('stopping'); - if (this.#started) this.#source.stop(); + + if (this.#started) { + this.#source.stop(); + } else { + // Required to manually emit the `ended` event for "un-started" sounds. + this.#handleEnded(); + } + + // Should `disconnect` and `empty` be moved into `#handleEnded`? + // Would this actually matter? this.#source.disconnect(); this.empty(); @@ -145,7 +154,11 @@ export class Sound extends EmittenCommon { readonly #handleEnded = () => { // Intentionally not setting `stopping` state here, - // but we may want ot consider a "ending" state instead. - this.emit('ended', {id: this.id, source: this.#source}); + // but we may want to consider a "ending" state instead. + this.emit('ended', { + id: this.id, + source: this.#source, + neverStarted: !this.#started, + }); }; } diff --git a/src/tests/Sound.test.ts b/src/tests/Sound.test.ts index 0bebf4a..1cbab18 100644 --- a/src/tests/Sound.test.ts +++ b/src/tests/Sound.test.ts @@ -274,6 +274,25 @@ describe('Sound component', () => { expect(spyEnded).toBeCalledWith({ id: testSound.id, source: expect.any(AudioBufferSourceNode), + neverStarted: false, + }); + }); + + it('emits `ended` event for a stopped sound that was never started', () => { + const testSound = new Sound(...mockConstructorArgs); + const spyEnded: SoundEventMap['ended'] = vi.fn((_event) => {}); + + testSound.on('ended', spyEnded); + + expect(spyEnded).not.toBeCalled(); + vi.advanceTimersToNextTimer(); + + testSound.stop(); + + expect(spyEnded).toBeCalledWith({ + id: testSound.id, + source: expect.any(AudioBufferSourceNode), + neverStarted: true, }); }); });