diff --git a/web/src/player/components/VideoPlayer.tsx b/web/src/player/components/VideoPlayer.tsx index 86d8138a..116cdcb2 100644 --- a/web/src/player/components/VideoPlayer.tsx +++ b/web/src/player/components/VideoPlayer.tsx @@ -95,6 +95,7 @@ interface VideoPlayerProps { onPlaybackTransportReady?: (transport: PlayerPlaybackTransport | null) => void; onReturnFromPostRoll?: () => void; onRealtimeEvent?: (event: PlaybackRealtimeEventEnvelope) => void; + onRealtimeConnectionStateChange?: (state: "disconnected" | "connecting" | "connected") => void; watchTogetherRoomId?: string | null; watchTogetherConnection?: WatchTogetherRoomConnectionResult; } @@ -181,6 +182,7 @@ export function VideoPlayer({ onPlaybackTransportReady, onReturnFromPostRoll, onRealtimeEvent, + onRealtimeConnectionStateChange, watchTogetherRoomId, watchTogetherConnection, }: VideoPlayerProps) { @@ -1714,12 +1716,16 @@ export function VideoPlayer({ [handleExit, performPlayerSeek], ); - usePlaybackRealtime({ + const realtime = usePlaybackRealtime({ sessionId, onCommand: executeRealtimeCommand, onEvent: onRealtimeEvent, }); + useEffect(() => { + onRealtimeConnectionStateChange?.(realtime.connectionState); + }, [onRealtimeConnectionStateChange, realtime.connectionState]); + // -- Postroll mini-player resize -- const [miniPlayerWidth, setMiniPlayerWidth] = useState(320); const isDraggingRef = useRef(false); diff --git a/web/src/player/components/WatchPage.tsx b/web/src/player/components/WatchPage.tsx index 82d4f403..dadeaebd 100644 --- a/web/src/player/components/WatchPage.tsx +++ b/web/src/player/components/WatchPage.tsx @@ -99,7 +99,11 @@ export function WatchPage({ const playbackController = useWatchPlaybackController(); const chapterRefreshAttemptsRef = useRef>(new Set()); const handledSelectionRevisionRef = useRef(null); + const markerRealtimeReconcileKeyRef = useRef(null); const [playbackVersions, setPlaybackVersions] = useState(versions); + const [realtimeConnectionState, setRealtimeConnectionState] = useState< + "disconnected" | "connecting" | "connected" + >("disconnected"); const watchTogetherConnection = useWatchTogetherRoomConnection({ roomId: watchTogetherRoomId, roomToken: watchTogetherRoomToken, @@ -197,6 +201,7 @@ export function WatchPage({ useEffect(() => { chapterRefreshAttemptsRef.current.clear(); + markerRealtimeReconcileKeyRef.current = null; }, [contentId, playbackRequestKey]); useEffect(() => { @@ -274,6 +279,51 @@ export function WatchPage({ playbackVersions, ]); + useEffect(() => { + if ( + realtimeConnectionState !== "connected" || + !session.sessionId || + !session.mediaFileId || + session.loading || + session.replacing + ) { + return; + } + + const activeFileId = session.mediaFileId; + const reconcileKey = `${session.sessionId}:${activeFileId}`; + if (markerRealtimeReconcileKeyRef.current === reconcileKey) { + return; + } + markerRealtimeReconcileKeyRef.current = reconcileKey; + + let cancelled = false; + void queryClient + .fetchQuery({ + queryKey: itemKeys.watchDetail(contentId, activeFileId, libraryId), + queryFn: () => fetchWatchDetail(contentId, activeFileId, libraryId), + staleTime: 0, + }) + .then((detail) => { + if (!cancelled) { + setPlaybackVersions(detail.versions); + } + }); + + return () => { + cancelled = true; + }; + }, [ + contentId, + libraryId, + queryClient, + realtimeConnectionState, + session.loading, + session.mediaFileId, + session.replacing, + session.sessionId, + ]); + const handleRealtimeEvent = useCallback( (event: PlaybackRealtimeEventEnvelope) => { if (event.name === "chapter_thumbnail_ready") { @@ -427,6 +477,7 @@ export function WatchPage({ onPlaybackStateChange={onPlaybackStateChange} onPlaybackTransportReady={onPlaybackTransportReady} onRealtimeEvent={handleRealtimeEvent} + onRealtimeConnectionStateChange={setRealtimeConnectionState} onExit={onExit} onMinimize={onMinimize} onEnded={handleEnded}