From e983b3d2995cd7d3fb3cc13a79a3ba1271ba89f4 Mon Sep 17 00:00:00 2001 From: Alexander Knott <156598583+alexykn@users.noreply.github.com> Date: Sat, 24 May 2025 23:32:12 +0200 Subject: [PATCH] extend TTS store and refactor playback --- frontend/src/composables/useAudioChunks.js | 7 ++- frontend/src/composables/usePlayback.js | 46 +++++++++------ frontend/src/composables/useTTS.js | 67 +++++++++++++--------- frontend/src/stores/ttsStore.js | 50 +++++++++++++++- 4 files changed, 121 insertions(+), 49 deletions(-) diff --git a/frontend/src/composables/useAudioChunks.js b/frontend/src/composables/useAudioChunks.js index 2e9f540..3fb1b6e 100644 --- a/frontend/src/composables/useAudioChunks.js +++ b/frontend/src/composables/useAudioChunks.js @@ -1,10 +1,12 @@ import { ref } from 'vue' import { useAPI } from './useAPI' import { concatenateAudioBuffers } from '../utils/audioHelpers' +import { useTTSStore } from '../stores/ttsStore' export function useAudioChunks(audioContext) { + const ttsStore = useTTSStore() + const { downloadProgress, setDownloadProgress } = ttsStore const chunkCache = new Map() - const downloadProgress = ref(0) const currentChunkIndex = ref(0) let totalChunks = 0 let validatedTotalChunks = null @@ -45,7 +47,7 @@ export function useAudioChunks(audioContext) { if (!chunkCache.has(currentChunk)) { chunkCache.set(currentChunk, audioBuffer) - downloadProgress.value = Math.round((chunkCache.size / totalChunks) * 100) + setDownloadProgress(Math.round((chunkCache.size / totalChunks) * 100)) } return { @@ -135,7 +137,6 @@ export function useAudioChunks(audioContext) { return { chunkCache, - downloadProgress, currentChunkIndex, fetchAudioChunk, fetchNextChunks, diff --git a/frontend/src/composables/usePlayback.js b/frontend/src/composables/usePlayback.js index 44285ae..d18d217 100644 --- a/frontend/src/composables/usePlayback.js +++ b/frontend/src/composables/usePlayback.js @@ -3,13 +3,20 @@ import { useTTSStore } from '../stores/ttsStore' export function usePlayback(audioContext, gainNode) { const ttsStore = useTTSStore() - const { volume, setVolume: updateVolume } = ttsStore - const isPlaying = ref(false) - const currentSource = ref(null) + const { + volume, + setVolume: updateVolume, + isPlaying, + currentSource, + playbackProgress, + currentTime, + setIsPlaying, + setCurrentSource, + setPlaybackProgress, + setCurrentTime + } = ttsStore const startTime = ref(0) const pausedTime = ref(0) - const currentTime = ref(0) - const playbackProgress = ref(0) const totalDuration = ref(0) function updatePlaybackProgress() { @@ -17,8 +24,8 @@ export function usePlayback(audioContext, gainNode) { const elapsed = audioContext.value.currentTime - startTime.value if (totalDuration.value > 0) { - currentTime.value = Math.min(totalDuration.value, elapsed) - playbackProgress.value = Math.min(100, (elapsed / totalDuration.value) * 100) + setCurrentTime(Math.min(totalDuration.value, elapsed)) + setPlaybackProgress(Math.min(100, (elapsed / totalDuration.value) * 100)) } if (isPlaying.value) { @@ -34,15 +41,15 @@ export function usePlayback(audioContext, gainNode) { function stopProgressUpdates() { if (audioContext.value) { const elapsed = audioContext.value.currentTime - startTime.value - currentTime.value = Math.min(totalDuration.value, elapsed) - playbackProgress.value = Math.min(100, (elapsed / totalDuration.value) * 100) + setCurrentTime(Math.min(totalDuration.value, elapsed)) + setPlaybackProgress(Math.min(100, (elapsed / totalDuration.value) * 100)) } } function setTotalDuration(duration) { totalDuration.value = duration - currentTime.value = 0 - playbackProgress.value = 0 + setCurrentTime(0) + setPlaybackProgress(0) } function seekToPosition(position, unifiedBuffer) { @@ -57,13 +64,14 @@ export function usePlayback(audioContext, gainNode) { currentSource.value.disconnect() } - currentSource.value = audioContext.value.createBufferSource() - currentSource.value.buffer = unifiedBuffer.value - currentSource.value.connect(gainNode.value) + const newSource = audioContext.value.createBufferSource() + newSource.buffer = unifiedBuffer.value + newSource.connect(gainNode.value) + setCurrentSource(newSource) startTime.value = audioContext.value.currentTime - newTime - currentTime.value = newTime - playbackProgress.value = boundedPosition + setCurrentTime(newTime) + setPlaybackProgress(boundedPosition) if (isPlaying.value) { currentSource.value.start(0, newTime) @@ -71,7 +79,7 @@ export function usePlayback(audioContext, gainNode) { } currentSource.value.onended = () => { - isPlaying.value = false + setIsPlaying(false) stopProgressUpdates() } } @@ -92,14 +100,14 @@ export function usePlayback(audioContext, gainNode) { if (!currentSource.value) return if (isPlaying.value) { - isPlaying.value = false + setIsPlaying(false) await audioContext.value.suspend() pausedTime.value = audioContext.value.currentTime - startTime.value stopProgressUpdates() } else { await audioContext.value.resume() startTime.value = audioContext.value.currentTime - pausedTime.value - isPlaying.value = true + setIsPlaying(true) if (unifiedBuffer && unifiedBuffer.value) { requestAnimationFrame(updatePlaybackProgress) } diff --git a/frontend/src/composables/useTTS.js b/frontend/src/composables/useTTS.js index 5e655c9..c6660b7 100644 --- a/frontend/src/composables/useTTS.js +++ b/frontend/src/composables/useTTS.js @@ -10,12 +10,27 @@ import { useAPI } from './useAPI' export function useTTS() { const ttsStore = useTTSStore() - const { isGenerating, progressMessage, unifiedBuffer, audioDuration, setUnifiedBuffer } = ttsStore + const { + isGenerating, + progressMessage, + unifiedBuffer, + audioDuration, + isPlaying, + currentSource, + playbackProgress, + currentTime, + isDownloadComplete, + downloadProgress, + setUnifiedBuffer, + setIsPlaying, + setCurrentSource, + setPlaybackProgress, + setCurrentTime, + setIsDownloadComplete, + setDownloadProgress + } = ttsStore const audioQueue = [] // For streaming chunks - // Download and duration state - const isDownloadComplete = ref(false) - // Audio context const { audioContext, gainNode, initAudio, setVolume, closeAudio } = useAudioContext() @@ -37,7 +52,7 @@ export function useTTS() { } = usePlayback(audioContext, gainNode) // Audio chunks management - const { chunkCache, downloadProgress, currentChunkIndex, fetchAudioChunk, fetchNextChunks, resetChunks } = useAudioChunks(audioContext) + const { chunkCache, currentChunkIndex, fetchAudioChunk, fetchNextChunks, resetChunks } = useAudioChunks(audioContext) let currentAbortController = null let currentSessionId = null @@ -66,8 +81,8 @@ export function useTTS() { // Reset state variables (including chunk state) resetChunks() - isDownloadComplete.value = false - downloadProgress.value = 0 + setIsDownloadComplete(false) + setDownloadProgress(0) audioQueue.length = 0 setUnifiedBuffer(null) // make sure we start in streaming mode for new generations: @@ -101,7 +116,7 @@ export function useTTS() { if (totalChunks === 1) { setUnifiedBuffer(firstChunk.buffer) setTotalDuration(unifiedBuffer.value.duration) - isDownloadComplete.value = true + setIsDownloadComplete(true) } else { fetchNextChunks(text, voice, isGenerating, unifiedBuffer, audioQueue, isDownloadComplete) } @@ -111,7 +126,7 @@ export function useTTS() { console.error('Error during speech generation:', error) progressMessage.value = `Error: ${error.message}` isGenerating.value = false - isPlaying.value = false + setIsPlaying(false) if (currentSource.value) { currentSource.value.onended = null currentSource.value.stop() @@ -143,7 +158,7 @@ export function useTTS() { currentSource.value.sessionId = currentSessionId if (!isPlaying.value) { - isPlaying.value = true + setIsPlaying(true) startTime.value = audioContext.value.currentTime - pausedTime.value startProgressUpdates() } @@ -158,7 +173,7 @@ export function useTTS() { setTimeout(() => playNextChunk(text, voice), 200) } else { // Generation complete and no queued chunk left => playback is finished. - isPlaying.value = false + setIsPlaying(false) progressMessage.value = 'Playback complete!' stopProgressUpdates() } @@ -188,14 +203,14 @@ export function useTTS() { currentSource.value.sessionId = currentSessionId if (!isPlaying.value) { - isPlaying.value = true + setIsPlaying(true) startTime.value = audioContext.value.currentTime startProgressUpdates() } currentSource.value.start(0) currentSource.value.onended = () => { - isPlaying.value = false + setIsPlaying(false) progressMessage.value = 'Playback complete!' stopProgressUpdates() } @@ -221,8 +236,8 @@ export function useTTS() { progressMessage.value = 'Initializing multi-speaker generation...' currentChunkIndex.value = 0 - isDownloadComplete.value = false - downloadProgress.value = 0 + setIsDownloadComplete(false) + setDownloadProgress(0) audioQueue.length = 0 chunkCache.clear() setUnifiedBuffer(null) @@ -253,19 +268,19 @@ export function useTTS() { setUnifiedBuffer(await audioContext.value.decodeAudioData(arrayBuffer)) setTotalDuration(unifiedBuffer.value.duration) - isDownloadComplete.value = true + setIsDownloadComplete(true) currentSource.value = audioContext.value.createBufferSource() currentSource.value.buffer = unifiedBuffer.value currentSource.value.connect(gainNode.value) currentSource.value.sessionId = sessionId - isPlaying.value = true + setIsPlaying(true) startTime.value = audioContext.value.currentTime currentSource.value.start(0) startProgressUpdates() currentSource.value.onended = () => { - isPlaying.value = false + setIsPlaying(false) progressMessage.value = 'Playback complete!' stopProgressUpdates() } @@ -275,7 +290,7 @@ export function useTTS() { console.error('Error during multi-speaker speech generation:', error) progressMessage.value = `Error: ${error.message}` isGenerating.value = false - isPlaying.value = false + setIsPlaying(false) if (currentSource.value) { currentSource.value.onended = null currentSource.value.stop() @@ -314,29 +329,29 @@ export function useTTS() { await stopGeneration() } isGenerating.value = false - + if (currentSource.value) { currentSource.value.onended = null currentSource.value.stop() currentSource.value.disconnect() - currentSource.value = null } + setCurrentSource(null) if (audioContext.value) { closeAudio() } setVolume(1) progressMessage.value = '' - isPlaying.value = false + setIsPlaying(false) // Reset our chunk state resetChunks() audioQueue.length = 0 currentChunkIndex.value = 0 setUnifiedBuffer(null) - downloadProgress.value = 0 - playbackProgress.value = 0 - isDownloadComplete.value = false - currentTime.value = 0 + setDownloadProgress(0) + setPlaybackProgress(0) + setIsDownloadComplete(false) + setCurrentTime(0) stopProgressUpdates() } diff --git a/frontend/src/stores/ttsStore.js b/frontend/src/stores/ttsStore.js index 7891139..53ebd22 100644 --- a/frontend/src/stores/ttsStore.js +++ b/frontend/src/stores/ttsStore.js @@ -8,6 +8,16 @@ export const useTTSStore = defineStore('tts', () => { const unifiedBuffer = ref(null) const audioDuration = ref(0) + // Playback state + const isPlaying = ref(false) + const currentSource = ref(null) + const playbackProgress = ref(0) + const currentTime = ref(0) + + // Download state + const isDownloadComplete = ref(false) + const downloadProgress = ref(0) + function setVolume(val) { volume.value = val } @@ -21,13 +31,51 @@ export const useTTSStore = defineStore('tts', () => { } } + // Playback actions + function setIsPlaying(val) { + isPlaying.value = val + } + + function setCurrentSource(source) { + currentSource.value = source + } + + function setPlaybackProgress(progress) { + playbackProgress.value = progress + } + + function setCurrentTime(time) { + currentTime.value = time + } + + // Download actions + function setIsDownloadComplete(val) { + isDownloadComplete.value = val + } + + function setDownloadProgress(val) { + downloadProgress.value = val + } + return { volume, isGenerating, progressMessage, unifiedBuffer, audioDuration, + isPlaying, + currentSource, + playbackProgress, + currentTime, + isDownloadComplete, + downloadProgress, setVolume, - setUnifiedBuffer + setUnifiedBuffer, + setIsPlaying, + setCurrentSource, + setPlaybackProgress, + setCurrentTime, + setIsDownloadComplete, + setDownloadProgress } })