From 4ad4583c6115b2d0426fd79a0813e3442f3082e8 Mon Sep 17 00:00:00 2001 From: R-J Lim Date: Mon, 15 Apr 2024 22:19:19 +0900 Subject: [PATCH] Stop audio playback when anki dialog is closed --- common/audio-clip/audio-clip.ts | 71 +++++++++++++++++++++++++------- common/components/AnkiDialog.tsx | 8 +++- 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/common/audio-clip/audio-clip.ts b/common/audio-clip/audio-clip.ts index 33c95c0c..00a27d51 100755 --- a/common/audio-clip/audio-clip.ts +++ b/common/audio-clip/audio-clip.ts @@ -22,6 +22,7 @@ interface AudioData { start: number; end: number; play: () => Promise; + stop: () => void; blob: () => Promise; base64: () => Promise; slice: (start: number, end: number) => AudioData; @@ -86,10 +87,7 @@ class Base64AudioData implements AudioData { async play(): Promise { if (this.playingAudio) { - this.stopAudio(this.playingAudio); - clearTimeout(this.stopAudioTimeout!); - this.playingAudio = undefined; - this.stopAudioTimeout = undefined; + this.stop(); return; } @@ -108,6 +106,17 @@ class Base64AudioData implements AudioData { }, (this._end - this._start) / this.playbackRate + 100); } + stop() { + if (!this.playingAudio) { + return; + } + + this.stopAudio(this.playingAudio); + clearTimeout(this.stopAudioTimeout!); + this.playingAudio = undefined; + this.stopAudioTimeout = undefined; + } + private stopAudio(audio: HTMLAudioElement) { audio.pause(); const src = audio.src; @@ -210,10 +219,7 @@ class FileAudioData implements AudioData { } if (this.playingAudio) { - this.stopAudio(this.playingAudio, true); - clearTimeout(this.stopAudioTimeout!); - this.playingAudio = undefined; - this.stopAudioTimeout = undefined; + this._stopPlayingAudio(); return; } @@ -228,6 +234,16 @@ class FileAudioData implements AudioData { }, (this._end - this._start) / this.playbackRate + 100); } + stop() { + if (this.playingAudio) { + this._stopPlayingAudio(); + } + + if (this.clippingAudio) { + this._stopClippingAudio(); + } + } + async blob() { if (!this._blob) { this._blob = await this._clipAudio(); @@ -242,12 +258,7 @@ class FileAudioData implements AudioData { async _clipAudio(): Promise { if (this.clippingAudio) { - this.stopAudio(this.clippingAudio, false); - clearTimeout(this.stopClippingTimeout!); - this.clippingAudioReject?.('Did not finish recording blob'); - this.clippingAudio = undefined; - this.stopClippingTimeout = undefined; - this.clippingAudioReject = undefined; + this._stopClippingAudio(); return undefined; } @@ -295,6 +306,30 @@ class FileAudioData implements AudioData { }); } + private _stopClippingAudio() { + if (!this.clippingAudio) { + return; + } + + this.stopAudio(this.clippingAudio, false); + clearTimeout(this.stopClippingTimeout!); + this.clippingAudioReject?.('Did not finish recording blob'); + this.clippingAudio = undefined; + this.stopClippingTimeout = undefined; + this.clippingAudioReject = undefined; + } + + private _stopPlayingAudio() { + if (!this.playingAudio) { + return; + } + + this.stopAudio(this.playingAudio, true); + clearTimeout(this.stopAudioTimeout!); + this.playingAudio = undefined; + this.stopAudioTimeout = undefined; + } + private _audioElement(blobUrl: string, selectTrack: boolean): Promise { const audio = new Audio() as ExperimentalAudioElement; audio.preload = 'metadata'; @@ -424,6 +459,10 @@ class Mp3AudioData implements AudioData { await this.data.play(); } + stop() { + this.data.stop(); + } + async blob() { if (!this._blob) { this._blob = await Mp3Encoder.encode(await this.data.blob(), this.workerFactory); @@ -524,6 +563,10 @@ export default class AudioClip { await this.data.play(); } + stop() { + this.data.stop(); + } + async base64() { return await this.data.base64(); } diff --git a/common/components/AnkiDialog.tsx b/common/components/AnkiDialog.tsx index 164dfc37..08a15afb 100755 --- a/common/components/AnkiDialog.tsx +++ b/common/components/AnkiDialog.tsx @@ -475,7 +475,7 @@ const AnkiDialog = ({ e.preventDefault(); e.stopPropagation(); - audioClip!.play(); + audioClip!.play().catch(console.info); }, [audioClip] ); @@ -660,6 +660,12 @@ const AnkiDialog = ({ const { audioHelperText, audioClipPlayable } = useAudioHelperText(audioClip, onRerecord); const { imageHelperText, imageAvailable } = useImageHelperText(image); + useEffect(() => { + if (!open) { + audioClip?.stop(); + } + }, [open, audioClip]); + return (