Implement audio player component with HTML5 audio and download functionality
This commit is contained in:
@@ -214,7 +214,7 @@
|
||||
- Test speed slider enforces min/max bounds
|
||||
- _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
|
||||
- Display HTML5 audio element with controls
|
||||
- Add "Download audiobook" button
|
||||
|
||||
@@ -2,8 +2,9 @@ import { useState } from 'react'
|
||||
import './App.css'
|
||||
import TextInputSection from './components/TextInputSection'
|
||||
import VoiceAssignmentSection from './components/VoiceAssignmentSection'
|
||||
import AudioPlayerSection from './components/AudioPlayerSection'
|
||||
import type { Role } from './api/tts'
|
||||
import { detectRoles } from './api/tts'
|
||||
import { detectRoles, generateAudiobook } from './api/tts'
|
||||
|
||||
function App() {
|
||||
const [text, setText] = useState('')
|
||||
@@ -15,6 +16,7 @@ function App() {
|
||||
const [speed, setSpeed] = useState(1.0) // Default: 1.0x
|
||||
const [isGenerating, setIsGenerating] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [audioUrl, setAudioUrl] = useState<string | null>(null)
|
||||
|
||||
const handleTextChange = (newText: string) => {
|
||||
setText(newText)
|
||||
@@ -63,26 +65,46 @@ function App() {
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
// TODO: Implement audio generation
|
||||
console.log('Generating audiobook with:', {
|
||||
// Convert voice assignments to the format expected by the API
|
||||
const rolesArray = Array.from(voiceAssignments.entries()).map(([id, voice]) => ({
|
||||
id,
|
||||
voice
|
||||
}))
|
||||
|
||||
// Generate the audiobook
|
||||
const audioBlob = await generateAudiobook({
|
||||
text,
|
||||
language,
|
||||
voiceAssignments: Array.from(voiceAssignments.entries()),
|
||||
lang: language,
|
||||
roles: rolesArray,
|
||||
speed
|
||||
})
|
||||
|
||||
// Placeholder - will be implemented in later tasks
|
||||
setTimeout(() => {
|
||||
setIsGenerating(false)
|
||||
setPhase('generated')
|
||||
}, 1000)
|
||||
// Create a blob URL for the audio
|
||||
const url = URL.createObjectURL(audioBlob)
|
||||
setAudioUrl(url)
|
||||
|
||||
// Move to generated phase
|
||||
setPhase('generated')
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to generate audiobook')
|
||||
console.error('Generation error:', err)
|
||||
} finally {
|
||||
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
|
||||
const allRolesAssigned = roles.length > 0 && roles.every(role => voiceAssignments.has(role.id))
|
||||
|
||||
@@ -122,10 +144,11 @@ function App() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{phase === 'generated' && (
|
||||
<div className="text-center">
|
||||
<p>Audio player phase (to be implemented)</p>
|
||||
</div>
|
||||
{phase === 'generated' && audioUrl && (
|
||||
<AudioPlayerSection
|
||||
audioUrl={audioUrl}
|
||||
onDownload={handleDownload}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
36
frontend/src/components/AudioPlayerSection.tsx
Normal file
36
frontend/src/components/AudioPlayerSection.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user