diff --git a/__tests__/component.tsx b/__tests__/component.tsx index 9daa065..002efc8 100644 --- a/__tests__/component.tsx +++ b/__tests__/component.tsx @@ -23,6 +23,7 @@ describe('TextToSpeech', () => { act(() => { fireEvent.click(getByRole('button', { name: 'Play' })) }) + expect(global.speechSynthesis.cancel).toHaveBeenCalled() expect(global.speechSynthesis.speak).toHaveBeenCalled() expect(getByRole('button', { name: 'Pause' })).toBeInTheDocument() @@ -39,8 +40,8 @@ describe('TextToSpeech', () => { fireEvent.click(getByRole('button', { name: 'Mute' })) }) expect(onMuteToggled).toHaveBeenCalledWith(false) - // Because we are currently paused (so no reset) - expect(global.speechSynthesis.cancel).not.toHaveBeenCalled() + // Cancel only called once thus far as we are currently paused (so no reset) + expect(global.speechSynthesis.cancel).toHaveBeenCalledTimes(1) expect(global.speechSynthesis.resume).not.toHaveBeenCalled() expect(global.speechSynthesis.speak).toHaveBeenCalledTimes(1) }) diff --git a/__tests__/controller.ts b/__tests__/controller.ts index 92b5d1c..f5fb18c 100644 --- a/__tests__/controller.ts +++ b/__tests__/controller.ts @@ -68,6 +68,8 @@ describe('Controller', () => { expect(controller.preservesPitch).toBe(false) expect(controller.paused).toBe(false) controller.play() + // Cancel before play to clear any utterances in the queue + expect(global.speechSynthesis.cancel).toHaveBeenCalled() expect(global.speechSynthesis.speak).toHaveBeenCalledWith( expect.objectContaining({ text: SpeechSynthesisMock.textForTest }) ) @@ -76,21 +78,22 @@ describe('Controller', () => { controller.resume() expect(global.speechSynthesis.resume).toHaveBeenCalled() controller.clear() - expect(global.speechSynthesis.cancel).toHaveBeenCalled() + expect(global.speechSynthesis.cancel).toHaveBeenCalledTimes(2) controller.play() expect(global.speechSynthesis.speak).toHaveBeenCalledTimes(2) + expect(global.speechSynthesis.cancel).toHaveBeenCalledTimes(3) controller.mute() expect(global.speechSynthesis.resume).toHaveBeenCalledTimes(2) - expect(global.speechSynthesis.cancel).toHaveBeenCalledTimes(2) + expect(global.speechSynthesis.cancel).toHaveBeenCalledTimes(4) expect(global.speechSynthesis.speak).toHaveBeenCalledTimes(3) controller.unmute(0.5) expect(controller.volume).toBe(0.5) expect(global.speechSynthesis.resume).toHaveBeenCalledTimes(3) - expect(global.speechSynthesis.cancel).toHaveBeenCalledTimes(3) + expect(global.speechSynthesis.cancel).toHaveBeenCalledTimes(5) expect(global.speechSynthesis.speak).toHaveBeenCalledTimes(4) controller.reset() expect(global.speechSynthesis.resume).toHaveBeenCalledTimes(4) - expect(global.speechSynthesis.cancel).toHaveBeenCalledTimes(4) + expect(global.speechSynthesis.cancel).toHaveBeenCalledTimes(6) expect(global.speechSynthesis.speak).toHaveBeenCalledTimes(5) }) @@ -131,8 +134,10 @@ describe('Controller', () => { controller.reset() expect(global.HTMLMediaElement.prototype.load).toHaveBeenCalled() expect(global.HTMLMediaElement.prototype.play).toHaveBeenCalledTimes(3) + // Play request a clear() which for HTMLAudioElement calls pause + expect(global.HTMLAudioElement.prototype.pause).toHaveBeenCalledTimes(2) controller.clear() - expect(global.HTMLMediaElement.prototype.pause).toHaveBeenCalledTimes(2) + expect(global.HTMLMediaElement.prototype.pause).toHaveBeenCalledTimes(3) expect(synth.currentTime).toBe(0) // Check that the rate getter/setter abstracts playbackRate diff --git a/__tests__/hook.tsx b/__tests__/hook.tsx index 8ffecae..3712e77 100644 --- a/__tests__/hook.tsx +++ b/__tests__/hook.tsx @@ -153,6 +153,7 @@ describe('useTts', () => { act(() => { result.current.onPlay() }) + expect(global.speechSynthesis.cancel).toHaveBeenCalled() expect(result.current.state.isPlaying).toBe(true) // Now mute while playing against a SpeechSynthesis instance of the backing controller @@ -169,7 +170,7 @@ describe('useTts', () => { * This is effectively an onReset(). */ expect(global.speechSynthesis.resume).toHaveBeenCalled() - expect(global.speechSynthesis.cancel).toHaveBeenCalled() + expect(global.speechSynthesis.cancel).toHaveBeenCalledTimes(2) expect(global.speechSynthesis.speak).toHaveBeenCalledTimes(2) expect(result.current.state.isPlaying).toBe(true) @@ -186,7 +187,7 @@ describe('useTts', () => { }) expect(result.current.state.isPaused).toBe(false) expect(global.speechSynthesis.resume).toHaveBeenCalledTimes(2) - expect(global.speechSynthesis.cancel).toHaveBeenCalledTimes(2) + expect(global.speechSynthesis.cancel).toHaveBeenCalledTimes(3) expect(global.speechSynthesis.speak).toHaveBeenCalledTimes(3) expect(result.current.state.isPlaying).toBe(true) }) diff --git a/package-lock.json b/package-lock.json index e8e97a3..9a7fbfb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tts-react", - "version": "0.8.0", + "version": "0.8.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "tts-react", - "version": "0.8.0", + "version": "0.8.1", "license": "MIT", "devDependencies": { "@babel/cli": "^7.18.10", diff --git a/package.json b/package.json index 889af81..7e39780 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tts-react", - "version": "0.8.0", + "version": "0.8.1", "description": "React component to convert text to speech.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/controller.ts b/src/controller.ts index e8bd4c0..c806d7f 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -391,9 +391,9 @@ class Controller extends EventTarget { } async play(): Promise { - if (this.paused) { - await this.resume() - } else if (this.synthesizer instanceof HTMLAudioElement) { + this.clear() + + if (this.synthesizer instanceof HTMLAudioElement) { await this.playHtmlAudio() } else { this.synthesizer.speak(this.target as SpeechSynthesisUtterance) diff --git a/src/hook.tsx b/src/hook.tsx index 5f9e40c..ce71c20 100644 --- a/src/hook.tsx +++ b/src/hook.tsx @@ -271,9 +271,14 @@ const useTts = ({ [lang, voice, fetchAudioData, markTextAsSpoken] ) const onPlay = useCallback(() => { - controller.play() + if (state.isPaused) { + controller.resume() + } else { + controller.play() + } + dispatch({ type: 'play' }) - }, [controller]) + }, [controller, state.isPaused]) const onPause = useCallback(() => { controller.pause() dispatch({ type: 'pause' })