feat: add ChapterPlayer component for audio chapter playback in Audiobook
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import { Book, BookOpen, Plus, Trash2, RefreshCw, Download, ChevronDown, ChevronUp, Play, Square, Pencil, Check, X, Loader2, Zap, Settings2, PanelLeftClose, PanelLeftOpen, Wand2, Volume2, Bot, Flame } from 'lucide-react'
|
||||
import { Book, BookOpen, Plus, Trash2, RefreshCw, Download, ChevronDown, ChevronUp, Play, Square, Pencil, Check, X, Loader2, Zap, Settings2, PanelLeftClose, PanelLeftOpen, Wand2, Volume2, Bot, Flame, Headphones } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
@@ -10,6 +10,7 @@ import { Progress } from '@/components/ui/progress'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Navbar } from '@/components/Navbar'
|
||||
import { AudioPlayer } from '@/components/AudioPlayer'
|
||||
import { ChapterPlayer } from '@/components/ChapterPlayer'
|
||||
import { audiobookApi, type AudiobookProject, type AudiobookProjectDetail, type AudiobookCharacter, type AudiobookSegment, type ScriptGenerationRequest, type NsfwScriptGenerationRequest } from '@/lib/api/audiobook'
|
||||
import { RotateCcw } from 'lucide-react'
|
||||
import apiClient, { formatApiError, adminApi, authApi } from '@/lib/api'
|
||||
@@ -1443,6 +1444,7 @@ function ChaptersPanel({
|
||||
const [savingSegId, setSavingSegId] = useState<number | null>(null)
|
||||
const [regeneratingSegs, setRegeneratingSegs] = useState<Set<number>>(new Set())
|
||||
const [audioVersions, setAudioVersions] = useState<Record<number, number>>({})
|
||||
const [chapterPlayerChIdx, setChapterPlayerChIdx] = useState<number | null>(null)
|
||||
const prevSegStatusRef = useRef<Record<number, string>>({})
|
||||
const initialExpandDoneRef = useRef(false)
|
||||
|
||||
@@ -1670,6 +1672,15 @@ function ChaptersPanel({
|
||||
}}>
|
||||
<RefreshCw className="h-3 w-3 mr-0.5" /><Volume2 className="h-3 w-3 mr-1" />{t('projectCard.chapters.generate')}
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant={chapterPlayerChIdx === ch.chapter_index ? 'secondary' : 'ghost'}
|
||||
className="h-6 w-6"
|
||||
onClick={() => setChapterPlayerChIdx(prev => prev === ch.chapter_index ? null : ch.chapter_index)}
|
||||
title="播放本章"
|
||||
>
|
||||
<Headphones className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button size="icon" variant="ghost" className="h-6 w-6" onClick={() => onDownload(ch.chapter_index)} title={t('projectCard.downloadAll')}>
|
||||
<Download className="h-3 w-3" />
|
||||
</Button>
|
||||
@@ -1827,6 +1838,23 @@ function ChaptersPanel({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{chapterPlayerChIdx !== null && (() => {
|
||||
const activeCh = detail?.chapters.find(c => c.chapter_index === chapterPlayerChIdx)
|
||||
const activeSegs = segments.filter(s => s.chapter_index === chapterPlayerChIdx)
|
||||
if (!activeCh) return null
|
||||
return (
|
||||
<div className="border-t shrink-0">
|
||||
<ChapterPlayer
|
||||
projectId={project.id}
|
||||
chapterIndex={chapterPlayerChIdx}
|
||||
chapterTitle={activeCh.title || t('projectCard.chapters.defaultTitle', { index: chapterPlayerChIdx + 1 })}
|
||||
segments={activeSegs}
|
||||
onClose={() => setChapterPlayerChIdx(null)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
|
||||
{doneCount > 0 && (
|
||||
<div className="px-3 py-2 border-t shrink-0">
|
||||
<SequentialPlayer segments={segments} projectId={project.id} onPlayingChange={onSequentialPlayingChange} />
|
||||
|
||||
Reference in New Issue
Block a user