50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
import { useMemo } from "react";
|
|
import type { Transcript } from "@/api/types";
|
|
|
|
type Sentence = Transcript["paragraphs"][number]["sentences"][number];
|
|
|
|
interface TranscriptPlaybackState {
|
|
/** The sentence currently being spoken, or null if between sentences */
|
|
activeSentence: Sentence | null;
|
|
/** Index of the active word within the transcript's flat words array */
|
|
activeWordIndex: number | null;
|
|
}
|
|
|
|
export function useTranscriptPlayback(
|
|
transcript: Transcript | undefined,
|
|
currentTime: number,
|
|
): TranscriptPlaybackState {
|
|
return useMemo(() => {
|
|
if (!transcript) return { activeSentence: null, activeWordIndex: null };
|
|
|
|
// Find the active sentence across all paragraphs
|
|
let activeSentence: Sentence | null = null;
|
|
for (const paragraph of transcript.paragraphs) {
|
|
const sentence = paragraph.sentences.find(
|
|
(s) => currentTime >= s.start && currentTime <= s.end,
|
|
);
|
|
if (sentence) {
|
|
activeSentence = sentence;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Binary search for active word
|
|
const words = transcript.words;
|
|
let activeWordIndex: number | null = null;
|
|
let lo = 0;
|
|
let hi = words.length - 1;
|
|
while (lo <= hi) {
|
|
const mid = (lo + hi) >>> 1;
|
|
if (currentTime < words[mid].start) hi = mid - 1;
|
|
else if (currentTime > words[mid].end) lo = mid + 1;
|
|
else {
|
|
activeWordIndex = mid;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return { activeSentence, activeWordIndex };
|
|
}, [transcript, currentTime]);
|
|
}
|