mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Add srt support on the web
This commit is contained in:
parent
8e9cd2d2f3
commit
e0ee364929
@ -34,6 +34,7 @@
|
|||||||
"react-native-video": "^6.0.0-alpha.5",
|
"react-native-video": "^6.0.0-alpha.5",
|
||||||
"react-native-web": "0.19.1",
|
"react-native-web": "0.19.1",
|
||||||
"solito": "^3.0.0",
|
"solito": "^3.0.0",
|
||||||
|
"srt-webvtt": "^2.0.0",
|
||||||
"superjson": "^1.12.2",
|
"superjson": "^1.12.2",
|
||||||
"sweetalert2": "^11.7.12",
|
"sweetalert2": "^11.7.12",
|
||||||
"yoshiki": "1.2.2",
|
"yoshiki": "1.2.2",
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getToken, Subtitle } from "@kyoo/models";
|
import { getToken, queryFn, Subtitle } from "@kyoo/models";
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
RefObject,
|
RefObject,
|
||||||
@ -37,6 +37,8 @@ import { playAtom, PlayMode, playModeAtom, subtitleAtom } from "./state";
|
|||||||
import Hls, { Level } from "hls.js";
|
import Hls, { Level } from "hls.js";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Menu } from "@kyoo/primitives";
|
import { Menu } from "@kyoo/primitives";
|
||||||
|
import toVttBlob from "srt-webvtt";
|
||||||
|
import { getDisplayName } from "./components/right-buttons";
|
||||||
|
|
||||||
let hls: Hls | null = null;
|
let hls: Hls | null = null;
|
||||||
|
|
||||||
@ -100,7 +102,7 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ref.current || paused === ref.current.paused) return;
|
if (!ref.current || paused === ref.current.paused) return;
|
||||||
if (paused) ref.current?.pause();
|
if (paused) ref.current?.pause();
|
||||||
else ref.current?.play().catch(() => { });
|
else ref.current?.play().catch(() => {});
|
||||||
}, [paused]);
|
}, [paused]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ref.current || !volume) return;
|
if (!ref.current || !volume) return;
|
||||||
@ -110,14 +112,12 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function
|
|||||||
const subtitle = useAtomValue(subtitleAtom);
|
const subtitle = useAtomValue(subtitleAtom);
|
||||||
useSubtitle(ref, subtitle, fonts);
|
useSubtitle(ref, subtitle, fonts);
|
||||||
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
if (!ref?.current || !source.uri) return;
|
if (!ref?.current || !source.uri) return;
|
||||||
if (!hls || oldHls.current !== source.hls) {
|
if (!hls || oldHls.current !== source.hls) {
|
||||||
// Reinit the hls player when we change track.
|
// Reinit the hls player when we change track.
|
||||||
if (hls)
|
if (hls) hls.destroy();
|
||||||
hls.destroy();
|
|
||||||
hls = null;
|
hls = null;
|
||||||
hls = await initHls();
|
hls = await initHls();
|
||||||
// Still load the hls source to list available qualities.
|
// Still load the hls source to list available qualities.
|
||||||
@ -134,7 +134,7 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function
|
|||||||
hls.on(Hls.Events.MANIFEST_LOADED, async () => {
|
hls.on(Hls.Events.MANIFEST_LOADED, async () => {
|
||||||
try {
|
try {
|
||||||
await ref.current?.play();
|
await ref.current?.play();
|
||||||
} catch { }
|
} catch {}
|
||||||
});
|
});
|
||||||
hls.on(Hls.Events.ERROR, (_, d) => {
|
hls.on(Hls.Events.ERROR, (_, d) => {
|
||||||
if (!d.fatal || !hls?.media) return;
|
if (!d.fatal || !hls?.media) return;
|
||||||
@ -201,7 +201,11 @@ export default Video;
|
|||||||
|
|
||||||
let htmlTrack: HTMLTrackElement | null;
|
let htmlTrack: HTMLTrackElement | null;
|
||||||
let subOcto: SubtitleOctopus | null;
|
let subOcto: SubtitleOctopus | null;
|
||||||
const useSubtitle = (player: RefObject<HTMLVideoElement>, value: Subtitle | null, fonts?: string[]) => {
|
const useSubtitle = (
|
||||||
|
player: RefObject<HTMLVideoElement>,
|
||||||
|
value: Subtitle | null,
|
||||||
|
fonts?: string[],
|
||||||
|
) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!player.current) return;
|
if (!player.current) return;
|
||||||
|
|
||||||
@ -224,20 +228,23 @@ const useSubtitle = (player: RefObject<HTMLVideoElement>, value: Subtitle | null
|
|||||||
} else if (value.codec === "vtt" || value.codec === "subrip") {
|
} else if (value.codec === "vtt" || value.codec === "subrip") {
|
||||||
removeOctoSub();
|
removeOctoSub();
|
||||||
if (player.current.textTracks.length > 0) player.current.textTracks[0].mode = "hidden";
|
if (player.current.textTracks.length > 0) player.current.textTracks[0].mode = "hidden";
|
||||||
|
const addSubtitle = async () => {
|
||||||
const track: HTMLTrackElement = htmlTrack ?? document.createElement("track");
|
const track: HTMLTrackElement = htmlTrack ?? document.createElement("track");
|
||||||
track.kind = "subtitles";
|
track.kind = "subtitles";
|
||||||
track.label = value.displayName;
|
track.label = getDisplayName(value);
|
||||||
if (value.language) track.srclang = value.language;
|
if (value.language) track.srclang = value.language;
|
||||||
track.src = value.link;
|
track.src = value.codec === "subrip" ? await toWebVtt(value.link) : value.link;
|
||||||
track.className = "subtitle_container";
|
track.className = "subtitle_container";
|
||||||
track.default = true;
|
track.default = true;
|
||||||
track.onload = () => {
|
track.onload = () => {
|
||||||
if (player.current) player.current.textTracks[0].mode = "showing";
|
if (player.current) player.current.textTracks[0].mode = "showing";
|
||||||
};
|
};
|
||||||
if (!htmlTrack) {
|
if (!htmlTrack) {
|
||||||
player.current.appendChild(track);
|
|
||||||
htmlTrack = track;
|
htmlTrack = track;
|
||||||
|
if (player.current) player.current.appendChild(track);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
addSubtitle();
|
||||||
} else if (value.codec === "ass") {
|
} else if (value.codec === "ass") {
|
||||||
removeHtmlSubtitle();
|
removeHtmlSubtitle();
|
||||||
removeOctoSub();
|
removeOctoSub();
|
||||||
@ -255,6 +262,19 @@ const useSubtitle = (player: RefObject<HTMLVideoElement>, value: Subtitle | null
|
|||||||
}, [player, value, fonts]);
|
}, [player, value, fonts]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toWebVtt = async (srtUrl: string) => {
|
||||||
|
const token = await getToken();
|
||||||
|
const query = await fetch(srtUrl, {
|
||||||
|
headers: token
|
||||||
|
? {
|
||||||
|
Authorization: token,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
const srt = await query.blob();
|
||||||
|
return await toVttBlob(srt);
|
||||||
|
};
|
||||||
|
|
||||||
export const AudiosMenu = (props: ComponentProps<typeof Menu>) => {
|
export const AudiosMenu = (props: ComponentProps<typeof Menu>) => {
|
||||||
if (!hls || hls.audioTracks.length < 2) return null;
|
if (!hls || hls.audioTracks.length < 2) return null;
|
||||||
return (
|
return (
|
||||||
@ -283,10 +303,10 @@ export const QualitiesMenu = (props: ComponentProps<typeof Menu>) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const levelName = (label: Level, auto?: boolean): string => {
|
const levelName = (label: Level, auto?: boolean): string => {
|
||||||
const height = `${label.height}p`
|
const height = `${label.height}p`;
|
||||||
if (auto) return height;
|
if (auto) return height;
|
||||||
return label.uri.includes("original") ? `${t("player.transmux")} (${height})` : height;
|
return label.uri.includes("original") ? `${t("player.transmux")} (${height})` : height;
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu {...props}>
|
<Menu {...props}>
|
||||||
|
@ -12851,6 +12851,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"srt-webvtt@npm:^2.0.0":
|
||||||
|
version: 2.0.0
|
||||||
|
resolution: "srt-webvtt@npm:2.0.0"
|
||||||
|
checksum: 457645e902929c1b4a5691bb58195a7dc3b77699a62fa551bf13ce89e4900d919d4e0595a5e17641b4c0a6e24ec9e2794b1c728e8a625a76cf0c2304cb20356e
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"ssri@npm:^8.0.1":
|
"ssri@npm:^8.0.1":
|
||||||
version: 8.0.1
|
version: 8.0.1
|
||||||
resolution: "ssri@npm:8.0.1"
|
resolution: "ssri@npm:8.0.1"
|
||||||
@ -14142,6 +14149,7 @@ __metadata:
|
|||||||
react-native-video: ^6.0.0-alpha.5
|
react-native-video: ^6.0.0-alpha.5
|
||||||
react-native-web: 0.19.1
|
react-native-web: 0.19.1
|
||||||
solito: ^3.0.0
|
solito: ^3.0.0
|
||||||
|
srt-webvtt: ^2.0.0
|
||||||
superjson: ^1.12.2
|
superjson: ^1.12.2
|
||||||
sweetalert2: ^11.7.12
|
sweetalert2: ^11.7.12
|
||||||
typescript: ^4.9.5
|
typescript: ^4.9.5
|
||||||
|
Loading…
x
Reference in New Issue
Block a user