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
|
- 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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
// Move to generated phase
|
||||||
setPhase('generated')
|
setPhase('generated')
|
||||||
}, 1000)
|
|
||||||
} 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>
|
||||||
|
|||||||
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