Implement audio player component with HTML5 audio and download functionality

This commit is contained in:
2025-12-09 09:29:05 +00:00
parent e92d0349bf
commit ea77b17363
3 changed files with 74 additions and 15 deletions

View File

@@ -214,7 +214,7 @@
- Test speed slider enforces min/max bounds - Test speed slider enforces min/max bounds
- _Requirements: 3.1, 3.3, 4.2, 4.3, 5.3_ - _Requirements: 3.1, 3.3, 4.2, 4.3, 5.3_
- [ ] 11. Implement audio player component - [x] 11. Implement audio player component
- Create AudioPlayerSection component - Create AudioPlayerSection component
- Display HTML5 audio element with controls - Display HTML5 audio element with controls
- Add "Download audiobook" button - Add "Download audiobook" button

View File

@@ -2,8 +2,9 @@ import { useState } from 'react'
import './App.css' import './App.css'
import TextInputSection from './components/TextInputSection' import TextInputSection from './components/TextInputSection'
import VoiceAssignmentSection from './components/VoiceAssignmentSection' import VoiceAssignmentSection from './components/VoiceAssignmentSection'
import AudioPlayerSection from './components/AudioPlayerSection'
import type { Role } from './api/tts' import type { Role } from './api/tts'
import { detectRoles } from './api/tts' import { detectRoles, generateAudiobook } from './api/tts'
function App() { function App() {
const [text, setText] = useState('') const [text, setText] = useState('')
@@ -15,6 +16,7 @@ function App() {
const [speed, setSpeed] = useState(1.0) // Default: 1.0x const [speed, setSpeed] = useState(1.0) // Default: 1.0x
const [isGenerating, setIsGenerating] = useState(false) const [isGenerating, setIsGenerating] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [audioUrl, setAudioUrl] = useState<string | null>(null)
const handleTextChange = (newText: string) => { const handleTextChange = (newText: string) => {
setText(newText) setText(newText)
@@ -63,26 +65,46 @@ function App() {
setError(null) setError(null)
try { try {
// TODO: Implement audio generation // Convert voice assignments to the format expected by the API
console.log('Generating audiobook with:', { const rolesArray = Array.from(voiceAssignments.entries()).map(([id, voice]) => ({
id,
voice
}))
// Generate the audiobook
const audioBlob = await generateAudiobook({
text, text,
language, lang: language,
voiceAssignments: Array.from(voiceAssignments.entries()), roles: rolesArray,
speed speed
}) })
// Placeholder - will be implemented in later tasks // Create a blob URL for the audio
setTimeout(() => { const url = URL.createObjectURL(audioBlob)
setIsGenerating(false) setAudioUrl(url)
setPhase('generated')
}, 1000) // Move to generated phase
setPhase('generated')
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : 'Failed to generate audiobook') setError(err instanceof Error ? err.message : 'Failed to generate audiobook')
console.error('Generation error:', err) console.error('Generation error:', err)
} finally {
setIsGenerating(false) setIsGenerating(false)
} }
} }
const handleDownload = () => {
if (!audioUrl) return
// Create a temporary anchor element to trigger download
const link = document.createElement('a')
link.href = audioUrl
link.download = 'audiobook.wav'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
// Check if all roles have assigned voices // Check if all roles have assigned voices
const allRolesAssigned = roles.length > 0 && roles.every(role => voiceAssignments.has(role.id)) const allRolesAssigned = roles.length > 0 && roles.every(role => voiceAssignments.has(role.id))
@@ -122,10 +144,11 @@ function App() {
/> />
)} )}
{phase === 'generated' && ( {phase === 'generated' && audioUrl && (
<div className="text-center"> <AudioPlayerSection
<p>Audio player phase (to be implemented)</p> audioUrl={audioUrl}
</div> onDownload={handleDownload}
/>
)} )}
</div> </div>
</div> </div>

View File

@@ -0,0 +1,36 @@
interface AudioPlayerSectionProps {
audioUrl: string;
onDownload: () => void;
}
export default function AudioPlayerSection({
audioUrl,
onDownload
}: AudioPlayerSectionProps) {
return (
<div className="w-full max-w-4xl mx-auto p-6">
<h2 className="text-2xl font-bold mb-6">Your Audiobook is Ready!</h2>
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
<h3 className="text-lg font-semibold mb-4">Listen to your audiobook</h3>
<audio
controls
src={audioUrl}
className="w-full"
preload="metadata"
>
Your browser does not support the audio element.
</audio>
</div>
<div className="flex justify-center">
<button
onClick={onDownload}
className="px-6 py-3 bg-green-600 hover:bg-green-700 text-white font-semibold rounded-lg transition-colors cursor-pointer"
>
Download audiobook
</button>
</div>
</div>
);
}