Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions frontend/src/composables/useAudioChunks.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -135,7 +137,6 @@ export function useAudioChunks(audioContext) {

return {
chunkCache,
downloadProgress,
currentChunkIndex,
fetchAudioChunk,
fetchNextChunks,
Expand Down
46 changes: 27 additions & 19 deletions frontend/src/composables/usePlayback.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,29 @@ 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() {
if (!isPlaying.value || !audioContext.value) return

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) {
Expand All @@ -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) {
Expand All @@ -57,21 +64,22 @@ 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)
startProgressUpdates()
}

currentSource.value.onended = () => {
isPlaying.value = false
setIsPlaying(false)
stopProgressUpdates()
}
}
Expand All @@ -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)
}
Expand Down
67 changes: 41 additions & 26 deletions frontend/src/composables/useTTS.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
}
Expand All @@ -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()
Expand Down Expand Up @@ -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()
}
Expand All @@ -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()
}
Expand Down Expand Up @@ -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()
}
Expand All @@ -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)
Expand Down Expand Up @@ -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()
}
Expand All @@ -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()
Expand Down Expand Up @@ -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()
}

Expand Down
50 changes: 49 additions & 1 deletion frontend/src/stores/ttsStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
})