90 lines
2.6 KiB
TypeScript
90 lines
2.6 KiB
TypeScript
"use client";
|
|
import { useRef, useState, useEffect } from "react";
|
|
import { Button } from "./ui/button";
|
|
import { Loader, Pause, Play } from "lucide-react";
|
|
import {
|
|
Accordion,
|
|
AccordionContent,
|
|
AccordionItem,
|
|
AccordionTrigger,
|
|
} from "./ui/accordion";
|
|
import { Label } from "./ui/label";
|
|
import { useTTS } from "./TTSProvider";
|
|
|
|
export default function KokoroReader({ pages }: { pages: any[] }) {
|
|
const {
|
|
voices,
|
|
selectedSpeaker,
|
|
setSelectedSpeaker,
|
|
skipToSentence,
|
|
currentSentence,
|
|
setCurrentSentence,
|
|
playSentence,
|
|
playInOrder,
|
|
status,
|
|
} = useTTS();
|
|
|
|
const [playing, setPlaying] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setCurrentSentence(0); // this might just jumpstart the audio
|
|
}, [status === "ready"]);
|
|
|
|
const play = () => {
|
|
if (playing) {
|
|
setPlaying(false);
|
|
return;
|
|
}
|
|
|
|
setPlaying(true);
|
|
playInOrder(currentSentence || 0);
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col items-center justify-center pt-4 relative overflow-hidden font-sans">
|
|
<div className="max-w-3xl w-full relative z-[2]">
|
|
<div className="items-center justify-center text-center">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-10 w-10"
|
|
onClick={play}
|
|
disabled={status === null}
|
|
>
|
|
{status === "running" ? (
|
|
<Loader className="animate-spin" />
|
|
) : (
|
|
<span className="sr-only">Play</span>
|
|
)}
|
|
{playing ? <Pause /> : <Play />}
|
|
</Button>
|
|
</div>
|
|
|
|
<Accordion type="single" collapsible>
|
|
<AccordionItem value="item-1">
|
|
<AccordionTrigger className="text-white pb-2">
|
|
Settings
|
|
</AccordionTrigger>
|
|
<AccordionContent className="pb-2">
|
|
<Label>Voice</Label>
|
|
<select
|
|
value={selectedSpeaker}
|
|
onChange={(e) => setSelectedSpeaker(e.target.value)}
|
|
className="w-full bg-gray-700/50 backdrop-blur-sm border-2 border-gray-600 rounded-md text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
>
|
|
{Object.entries(voices).map(([id, voice]) => (
|
|
<option key={id} value={id}>
|
|
{voice.name} (
|
|
{voice.language === "en-us" ? "American" : "British"}{" "}
|
|
{voice.gender})
|
|
</option>
|
|
))}
|
|
</select>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
</Accordion>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|