From 2b240c8155557d287cda771681febff1ff236b2b Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 18 Oct 2023 16:54:52 +0200 Subject: [PATCH] Fix subtitles not showing up in safari, using a blob --- .../player/internals/VideoContainer.tsx | 46 +++++++++++++++---- src/components/player/utils/captions.ts | 10 +++- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/components/player/internals/VideoContainer.tsx b/src/components/player/internals/VideoContainer.tsx index a9e22eb5..45476eab 100644 --- a/src/components/player/internals/VideoContainer.tsx +++ b/src/components/player/internals/VideoContainer.tsx @@ -1,7 +1,7 @@ import { ReactNode, useEffect, useMemo, useRef } from "react"; import { makeVideoElementDisplayInterface } from "@/components/player/display/base"; -import { convertSubtitlesToDataurl } from "@/components/player/utils/captions"; +import { convertSubtitlesToObjectUrl } from "@/components/player/utils/captions"; import { playerStatus } from "@/stores/player/slices/source"; import { usePlayerStore } from "@/stores/player/store"; @@ -37,16 +37,39 @@ export function useShouldShowVideoElement() { return true; } +function useObjectUrl(cb: () => string | null, deps: any[]) { + const lastObjectUrl = useRef(null); + const output = useMemo(() => { + if (lastObjectUrl.current) URL.revokeObjectURL(lastObjectUrl.current); + const data = cb(); + lastObjectUrl.current = data; + return data; + // deps are passed in, cb is known not to be changed + // eslint-disable-next-line react-hooks/exhaustive-deps + }, deps); + + useEffect(() => { + return () => { + // this is intentionally done only in cleanup + // eslint-disable-next-line react-hooks/exhaustive-deps + if (lastObjectUrl.current) URL.revokeObjectURL(lastObjectUrl.current); + }; + }, []); + + return output; +} + function VideoElement() { const videoEl = useRef(null); + const trackEl = useRef(null); const display = usePlayerStore((s) => s.display); const srtData = usePlayerStore((s) => s.caption.selected?.srtData); const captionAsTrack = usePlayerStore((s) => s.caption.asTrack); const language = usePlayerStore((s) => s.caption.selected?.language); - - const trackData = useMemo(() => { - return srtData ? convertSubtitlesToDataurl(srtData) : null; - }, [srtData]); + const trackObjectUrl = useObjectUrl( + () => (srtData ? convertSubtitlesToObjectUrl(srtData) : null), + [srtData] + ); // report video element to display interface useEffect(() => { @@ -55,14 +78,21 @@ function VideoElement() { } }, [display, videoEl]); + // select track as showing if it exists + useEffect(() => { + if (trackEl.current) { + trackEl.current.track.mode = "showing"; + } + }, [trackEl]); + let subtitleTrack: ReactNode = null; - if (captionAsTrack && trackData && language) + if (captionAsTrack && trackObjectUrl && language) subtitleTrack = ( ); diff --git a/src/components/player/utils/captions.ts b/src/components/player/utils/captions.ts index 64fa4691..182a3d08 100644 --- a/src/components/player/utils/captions.ts +++ b/src/components/player/utils/captions.ts @@ -41,5 +41,13 @@ export function parseSubtitles(text: string): CaptionCueType[] { } export function convertSubtitlesToDataurl(text: string): string { - return `data:text/vtt;${convertSubtitlesToVtt(text)}`; + return `data:text/vtt,${convertSubtitlesToVtt(text)}`; +} + +export function convertSubtitlesToObjectUrl(text: string): string { + return URL.createObjectURL( + new Blob([convertSubtitlesToVtt(text)], { + type: "text/vtt", + }) + ); }