mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-31 04:04:21 -04:00
Fix subtitles on android with transcode
This commit is contained in:
parent
5d4e251251
commit
de7aa58195
@ -220,8 +220,10 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
|||||||
if (e.nativeEvent.pointerType === "mouse") setHover(false);
|
if (e.nativeEvent.pointerType === "mouse") setHover(false);
|
||||||
}}
|
}}
|
||||||
onPointerDown={(e) => {
|
onPointerDown={(e) => {
|
||||||
if (!displayControls) onPointerDown(e);
|
if (!displayControls) {
|
||||||
if (Platform.OS === "web") e.preventDefault();
|
onPointerDown(e);
|
||||||
|
if (Platform.OS === "web") e.preventDefault();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onMenuOpen={() => setMenuOpen(true)}
|
onMenuOpen={() => setMenuOpen(true)}
|
||||||
onMenuClose={() => {
|
onMenuClose={() => {
|
||||||
|
@ -123,8 +123,6 @@ export const Video = memo(function _Video({
|
|||||||
return () => document.removeEventListener("fullscreenchange", handler);
|
return () => document.removeEventListener("fullscreenchange", handler);
|
||||||
});
|
});
|
||||||
|
|
||||||
const subtitle = useAtomValue(subtitleAtom);
|
|
||||||
|
|
||||||
if (!source || !links) return null;
|
if (!source || !links) return null;
|
||||||
return (
|
return (
|
||||||
<NativeVideo
|
<NativeVideo
|
||||||
@ -151,25 +149,11 @@ export const Video = memo(function _Video({
|
|||||||
setDuration(info.duration);
|
setDuration(info.duration);
|
||||||
}}
|
}}
|
||||||
onPlayPause={setPlay}
|
onPlayPause={setPlay}
|
||||||
textTracks={subtitles?.map(x => ({
|
|
||||||
type: "text/x-ssa" as any, //MimeTypes[x.codec],
|
|
||||||
uri: x.link!,
|
|
||||||
title: x.title!,
|
|
||||||
language: x.language!,
|
|
||||||
}))}
|
|
||||||
selectedTextTrack={
|
|
||||||
subtitle
|
|
||||||
? {
|
|
||||||
type: "index",
|
|
||||||
value: subtitle.trackIndex,
|
|
||||||
}
|
|
||||||
: { type: "disabled" }
|
|
||||||
}
|
|
||||||
fonts={fonts}
|
fonts={fonts}
|
||||||
|
subtitles={subtitles}
|
||||||
onMediaUnsupported={() => {
|
onMediaUnsupported={() => {
|
||||||
if (mode == PlayMode.Direct) setPlayMode(PlayMode.Hls);
|
if (mode == PlayMode.Direct) setPlayMode(PlayMode.Hls);
|
||||||
}}
|
}}
|
||||||
// TODO: textTracks: external subtitles
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -26,22 +26,29 @@ declare module "react-native-video" {
|
|||||||
}
|
}
|
||||||
export type VideoProps = Omit<VideoProperties, "source"> & {
|
export type VideoProps = Omit<VideoProperties, "source"> & {
|
||||||
source: { uri: string; hls: string };
|
source: { uri: string; hls: string };
|
||||||
|
subtitles?: WatchItem["subtitles"];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export * from "react-native-video";
|
export * from "react-native-video";
|
||||||
|
|
||||||
import { Font, getToken } from "@kyoo/models";
|
import { Font, getToken, WatchItem } from "@kyoo/models";
|
||||||
import { IconButton, Menu } from "@kyoo/primitives";
|
import { IconButton, Menu } from "@kyoo/primitives";
|
||||||
import { ComponentProps, forwardRef, useEffect, useRef } from "react";
|
import { ComponentProps, forwardRef, useEffect, useRef } from "react";
|
||||||
import { atom, useAtom, useAtomValue, useSetAtom } from "jotai";
|
import { atom, useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||||
import NativeVideo, { OnLoadData, VideoProps } from "react-native-video";
|
import NativeVideo, { OnLoadData, VideoProps } from "react-native-video";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { PlayMode, playModeAtom } from "./state";
|
import { PlayMode, playModeAtom, subtitleAtom } from "./state";
|
||||||
import uuid from "react-native-uuid";
|
import uuid from "react-native-uuid";
|
||||||
import { Pressable } from "react-native";
|
import { Pressable } from "react-native";
|
||||||
import { useYoshiki } from "yoshiki/native";
|
import { useYoshiki } from "yoshiki/native";
|
||||||
|
|
||||||
|
const MimeTypes: Map<string, string> = new Map([
|
||||||
|
["subrip", "application/x-subrip"],
|
||||||
|
["ass", "text/x-ssa"],
|
||||||
|
["vtt", "text/vtt"],
|
||||||
|
]);
|
||||||
|
|
||||||
const infoAtom = atom<OnLoadData | null>(null);
|
const infoAtom = atom<OnLoadData | null>(null);
|
||||||
const videoAtom = atom(0);
|
const videoAtom = atom(0);
|
||||||
const audioAtom = atom(0);
|
const audioAtom = atom(0);
|
||||||
@ -49,7 +56,7 @@ const audioAtom = atom(0);
|
|||||||
const clientId = uuid.v4() as string;
|
const clientId = uuid.v4() as string;
|
||||||
|
|
||||||
const Video = forwardRef<NativeVideo, VideoProps>(function _NativeVideo(
|
const Video = forwardRef<NativeVideo, VideoProps>(function _NativeVideo(
|
||||||
{ onLoad, source, onPointerDown, ...props },
|
{ onLoad, source, onPointerDown, subtitles, ...props },
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
@ -57,9 +64,7 @@ const Video = forwardRef<NativeVideo, VideoProps>(function _NativeVideo(
|
|||||||
const setInfo = useSetAtom(infoAtom);
|
const setInfo = useSetAtom(infoAtom);
|
||||||
const video = useAtomValue(videoAtom);
|
const video = useAtomValue(videoAtom);
|
||||||
const audio = useAtomValue(audioAtom);
|
const audio = useAtomValue(audioAtom);
|
||||||
|
const subtitle = useAtomValue(subtitleAtom);
|
||||||
const info = useAtomValue(infoAtom);
|
|
||||||
console.log(info);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function run() {
|
async function run() {
|
||||||
@ -73,7 +78,7 @@ const Video = forwardRef<NativeVideo, VideoProps>(function _NativeVideo(
|
|||||||
focusable={false}
|
focusable={false}
|
||||||
onPress={() => onPointerDown?.({ nativeEvent: { pointerType: "pointer" } } as any)}
|
onPress={() => onPointerDown?.({ nativeEvent: { pointerType: "pointer" } } as any)}
|
||||||
{...css({ flexGrow: 1, flexShrink: 1 })}
|
{...css({ flexGrow: 1, flexShrink: 1 })}
|
||||||
>
|
>
|
||||||
<NativeVideo
|
<NativeVideo
|
||||||
ref={ref}
|
ref={ref}
|
||||||
source={{
|
source={{
|
||||||
@ -81,7 +86,7 @@ const Video = forwardRef<NativeVideo, VideoProps>(function _NativeVideo(
|
|||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer: ${token.current}`,
|
Authorization: `Bearer: ${token.current}`,
|
||||||
"X-CLIENT-ID": clientId,
|
"X-CLIENT-ID": clientId,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
onLoad={(info) => {
|
onLoad={(info) => {
|
||||||
setInfo(info);
|
setInfo(info);
|
||||||
@ -89,8 +94,23 @@ const Video = forwardRef<NativeVideo, VideoProps>(function _NativeVideo(
|
|||||||
}}
|
}}
|
||||||
selectedVideoTrack={video === -1 ? { type: "auto" } : { type: "resolution", value: video }}
|
selectedVideoTrack={video === -1 ? { type: "auto" } : { type: "resolution", value: video }}
|
||||||
selectedAudioTrack={{ type: "index", value: audio }}
|
selectedAudioTrack={{ type: "index", value: audio }}
|
||||||
|
textTracks={subtitles?.map((x) => ({
|
||||||
|
type: MimeTypes.get(x.codec) as any,
|
||||||
|
uri: x.link!,
|
||||||
|
title: x.title ?? "Unknown",
|
||||||
|
language: x.language ?? "Unknown",
|
||||||
|
}))}
|
||||||
|
selectedTextTrack={
|
||||||
|
subtitle
|
||||||
|
? {
|
||||||
|
type: "index",
|
||||||
|
value: subtitles?.indexOf(subtitle),
|
||||||
|
}
|
||||||
|
: { type: "disabled" }
|
||||||
|
}
|
||||||
{...props}
|
{...props}
|
||||||
/></Pressable>
|
/>
|
||||||
|
</Pressable>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -107,7 +107,6 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function
|
|||||||
ref.current.volume = Math.max(0, Math.min(volume, 100)) / 100;
|
ref.current.volume = Math.max(0, Math.min(volume, 100)) / 100;
|
||||||
}, [volume]);
|
}, [volume]);
|
||||||
|
|
||||||
// This should use the selectedTextTrack prop instead of the atom but this is so much simpler
|
|
||||||
const subtitle = useAtomValue(subtitleAtom);
|
const subtitle = useAtomValue(subtitleAtom);
|
||||||
useSubtitle(ref, subtitle, fonts);
|
useSubtitle(ref, subtitle, fonts);
|
||||||
|
|
||||||
|
@ -39,7 +39,10 @@ impl Transcoder {
|
|||||||
// TODO: Find codecs in the RFC 6381 format.
|
// TODO: Find codecs in the RFC 6381 format.
|
||||||
// master.push_str("CODECS=\"avc1.640028\",");
|
// master.push_str("CODECS=\"avc1.640028\",");
|
||||||
// TODO: With multiple audio qualities, maybe switch qualities depending on the video quality.
|
// TODO: With multiple audio qualities, maybe switch qualities depending on the video quality.
|
||||||
master.push_str("AUDIO=\"audio\"\n");
|
master.push_str("AUDIO=\"audio\",");
|
||||||
|
// Not adding this attribute result in some players (like android's exoplayer) to
|
||||||
|
// assume a non existant closed caption exist
|
||||||
|
master.push_str("CLOSED-CAPTIONS=NONE\n");
|
||||||
master.push_str(format!("./{}/index.m3u8\n", Quality::Original).as_str());
|
master.push_str(format!("./{}/index.m3u8\n", Quality::Original).as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +64,10 @@ impl Transcoder {
|
|||||||
);
|
);
|
||||||
master.push_str("CODECS=\"avc1.640028\",");
|
master.push_str("CODECS=\"avc1.640028\",");
|
||||||
// TODO: With multiple audio qualities, maybe switch qualities depending on the video quality.
|
// TODO: With multiple audio qualities, maybe switch qualities depending on the video quality.
|
||||||
master.push_str("AUDIO=\"audio\"\n");
|
master.push_str("AUDIO=\"audio\",");
|
||||||
|
// Not adding this attribute result in some players (like android's exoplayer) to
|
||||||
|
// assume a non existant closed caption exist
|
||||||
|
master.push_str("CLOSED-CAPTIONS=NONE\n");
|
||||||
master.push_str(format!("./{}/index.m3u8\n", quality).as_str());
|
master.push_str(format!("./{}/index.m3u8\n", quality).as_str());
|
||||||
}
|
}
|
||||||
for audio in info.audios {
|
for audio in info.audios {
|
||||||
@ -87,23 +93,6 @@ impl Transcoder {
|
|||||||
master.push_str(format!("URI=\"./audio/{}/index.m3u8\"\n", audio.index).as_str());
|
master.push_str(format!("URI=\"./audio/{}/index.m3u8\"\n", audio.index).as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
for subtitle in info.subtitles {
|
|
||||||
master.push_str("#EXT-X-MEDIA:TYPE=SUBTITLES,");
|
|
||||||
master.push_str("GROUP-ID=\"subtitles\",");
|
|
||||||
if let Some(language) = subtitle.language.clone() {
|
|
||||||
master.push_str(format!("LANGUAGE=\"{}\",", language).as_str());
|
|
||||||
}
|
|
||||||
if let Some(title) = subtitle.title {
|
|
||||||
master.push_str(format!("NAME=\"{}\",", title).as_str());
|
|
||||||
} else if let Some(language) = subtitle.language {
|
|
||||||
master.push_str(format!("NAME=\"{}\",", language).as_str());
|
|
||||||
} else {
|
|
||||||
master.push_str(format!("NAME=\"Subtitle {}\",", subtitle.index).as_str());
|
|
||||||
}
|
|
||||||
master.push_str("URI=\"https://kyoo.sdg.moe/api/subtitle/akudama-drive-s1e1.eng.subtitle\"\n");
|
|
||||||
// master.push_str(format!("URI=\"./subtitles/{}/index.m3u8\"\n", subtitle.index).as_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(master)
|
Some(master)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user