Skip to content

Commit 9e170f5

Browse files
committed
Testing syncing active cue
1 parent f7601a9 commit 9e170f5

File tree

1 file changed

+72
-22
lines changed

1 file changed

+72
-22
lines changed

dotcom-rendering/src/lib/useSubtitles.ts

Lines changed: 72 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ export const useSubtitles = ({
2020
}: Props): ActiveCue | null => {
2121
const [activeTrack, setActiveTrack] = useState<TextTrack | null>(null);
2222
const [activeCue, setActiveCue] = useState<ActiveCue | null>(null);
23-
// only show subtitles if the video is actively playing or if its paused
23+
24+
// Only show subtitles if the video is actively playing or if it's paused after having started
2425
const shouldShow = playerState === 'PLAYING' || currentTime > 0;
2526

27+
// Select and "wake" the single text track as soon as possible.
2628
useEffect(() => {
2729
if (!video) return;
2830

@@ -33,59 +35,107 @@ export const useSubtitles = ({
3335
if (!t) return;
3436

3537
// Trigger cue fetching immediately (don’t wait for play state)
36-
if (t.mode !== 'hidden' && t.mode !== 'showing') {
37-
t.mode = 'hidden';
38-
}
38+
if (t.mode === 'disabled') t.mode = 'hidden';
3939

4040
setActiveTrack(t);
4141
};
4242

43-
// 1) pick immediately if already present
43+
// 1) If already present
4444
pickTrack();
4545

46-
// 2) react when HLS adds tracks later (common on mobile)
46+
// 2) Track can appear later on mobile HLS
4747
const onAdd = () => pickTrack();
48-
tracks.addEventListener('addtrack', onAdd);
48+
// Some engines support addEventListener on TextTrackList; guard just in case
49+
(tracks as unknown as EventTarget).addEventListener?.(
50+
'addtrack',
51+
onAdd as EventListener,
52+
);
4953

50-
// 3) also after metadata (some browsers populate then)
54+
// 3) Some browsers populate textTracks on metadata
5155
const onMeta = () => pickTrack();
5256
video.addEventListener('loadedmetadata', onMeta);
5357

5458
return () => {
55-
tracks.removeEventListener('addtrack', onAdd);
59+
(tracks as unknown as EventTarget).removeEventListener?.(
60+
'addtrack',
61+
onAdd as EventListener,
62+
);
5663
video.removeEventListener('loadedmetadata', onMeta);
5764
};
5865
}, [video]);
5966

67+
// Keep activeCue in sync; use cuechange + timeupdate fallback to avoid stalls on mobile.
6068
useEffect(() => {
6169
const track = activeTrack;
6270

63-
if (!track || !shouldShow) {
71+
if (!video || !track) {
6472
setActiveCue(null);
6573
return;
6674
}
6775

68-
// if we have a track and can show it, hide the native track
69-
track.mode = 'hidden';
76+
// Ensure the browser continues fetching cues even if it flips the mode
77+
if (track.mode === 'disabled') track.mode = 'hidden';
7078

71-
const onCueChange = () => {
79+
const computeActive = () => {
80+
if (!shouldShow) {
81+
// We still keep the track loading, but hide our custom renderer state
82+
setActiveCue(null);
83+
return;
84+
}
85+
86+
// Prefer activeCues when available
7287
const list = track.activeCues;
73-
if (!list || list.length === 0) {
88+
if (list && list.length > 0) {
89+
const cue = list[0] as VTTCue;
90+
setActiveCue({
91+
startTime: cue.startTime,
92+
endTime: cue.endTime,
93+
text: cue.text,
94+
});
7495
return;
7596
}
76-
const cue = list[0] as VTTCue;
77-
setActiveCue({
78-
startTime: cue.startTime,
79-
endTime: cue.endTime,
80-
text: cue.text,
81-
});
97+
98+
// Fallback: derive from all cues + currentTime (helps when cuechange stalls on mobile)
99+
const cues = track.cues;
100+
if (cues?.length != null) {
101+
const t = video.currentTime;
102+
let found: VTTCue | null = null;
103+
// Typical subtitle counts are small; linear scan is fine.
104+
for (let i = 0; i < cues.length; i++) {
105+
const c = cues[i] as VTTCue;
106+
if (t >= c.startTime && t < c.endTime) {
107+
found = c;
108+
break;
109+
}
110+
}
111+
if (found) {
112+
setActiveCue({
113+
startTime: found.startTime,
114+
endTime: found.endTime,
115+
text: found.text,
116+
});
117+
return;
118+
}
119+
}
120+
121+
// No active cue → clear to avoid “stuck on first cue”
122+
setActiveCue(null);
82123
};
124+
125+
const onCueChange = () => computeActive();
126+
const onTimeUpdate = () => computeActive();
127+
83128
track.addEventListener('cuechange', onCueChange);
84-
onCueChange();
129+
video.addEventListener('timeupdate', onTimeUpdate);
130+
131+
// Kick once in case we're already mid-cue
132+
computeActive();
133+
85134
return () => {
86135
track.removeEventListener('cuechange', onCueChange);
136+
video.removeEventListener('timeupdate', onTimeUpdate);
87137
};
88-
}, [activeTrack, shouldShow]);
138+
}, [activeTrack, video, shouldShow]);
89139

90140
return activeCue;
91141
};

0 commit comments

Comments
 (0)