feat: generate transcript and event-driven particle processing
This generates the transcript and shows the caption experience on the client side for media particles. It also simplifies other side effects that we must perform such as updating the `last_child_created_at` field for stream and container particles.
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { useMemo } from "react";
|
||||
import type { Transcript } from "@/api/types";
|
||||
|
||||
interface TranscriptPlaybackState {
|
||||
/** The paragraph currently being spoken, or null if before/after speech */
|
||||
activeParagraph: Transcript["paragraphs"][number] | 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 { activeParagraph: null, activeWordIndex: null };
|
||||
|
||||
const activeParagraph =
|
||||
transcript.paragraphs.find(
|
||||
(p) => currentTime >= p.start && currentTime <= p.end,
|
||||
) ?? null;
|
||||
|
||||
// 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 { activeParagraph, activeWordIndex };
|
||||
}, [transcript, currentTime]);
|
||||
}
|
||||
Reference in New Issue
Block a user