mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-11-03 19:17:16 -05:00 
			
		
		
		
	Add audio track selector
This commit is contained in:
		
							parent
							
								
									8fea8b1fe7
								
							
						
					
					
						commit
						c5f237771c
					
				@ -27,6 +27,7 @@
 | 
			
		||||
        "expo-status-bar": "~3.0.8",
 | 
			
		||||
        "expo-updates": "~29.0.11",
 | 
			
		||||
        "i18next-http-backend": "^3.0.2",
 | 
			
		||||
        "langmap": "^0.0.16",
 | 
			
		||||
        "react": "19.1.0",
 | 
			
		||||
        "react-dom": "19.1.0",
 | 
			
		||||
        "react-i18next": "^16.1.0",
 | 
			
		||||
@ -1004,6 +1005,8 @@
 | 
			
		||||
 | 
			
		||||
    "lan-network": ["lan-network@0.1.7", "", { "bin": { "lan-network": "dist/lan-network-cli.js" } }, "sha512-mnIlAEMu4OyEvUNdzco9xpuB9YVcPkQec+QsgycBCtPZvEqWPCDPfbAE4OJMdBBWpZWtpCn1xw9jJYlwjWI5zQ=="],
 | 
			
		||||
 | 
			
		||||
    "langmap": ["langmap@0.0.16", "", {}, "sha512-AtYvBK7BsDvWwnSfmO7CfgeUy7GUT1wK3QX8eKH/Ey/eXodqoHuAtvdQ82hmWD9QVFVKnuiNjym9fGY4qSJeLA=="],
 | 
			
		||||
 | 
			
		||||
    "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
 | 
			
		||||
 | 
			
		||||
    "lighthouse-logger": ["lighthouse-logger@1.4.2", "", { "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" } }, "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g=="],
 | 
			
		||||
@ -1262,7 +1265,7 @@
 | 
			
		||||
 | 
			
		||||
    "react-native-svg-transformer": ["react-native-svg-transformer@1.5.1", "", { "dependencies": { "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0", "@svgr/plugin-svgo": "^8.1.0", "path-dirname": "^1.0.2" }, "peerDependencies": { "react-native": ">=0.59.0", "react-native-svg": ">=12.0.0" } }, "sha512-dFvBNR8A9VPum9KCfh+LE49YiJEF8zUSnEFciKQroR/bEOhlPoZA0SuQ0qNk7m2iZl2w59FYjdRe0pMHWMDl0Q=="],
 | 
			
		||||
 | 
			
		||||
    "react-native-video": ["react-native-video@github:zoriya/react-native-video#77df6b8", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": ">=0.27.2" } }, "zoriya-react-native-video-77df6b8"],
 | 
			
		||||
    "react-native-video": ["react-native-video@github:zoriya/react-native-video#3f30e52", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": ">=0.27.2" } }, "zoriya-react-native-video-3f30e52"],
 | 
			
		||||
 | 
			
		||||
    "react-native-web": ["react-native-web@0.21.2", "", { "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", "fbjs": "^3.0.4", "inline-style-prefixer": "^7.0.1", "memoize-one": "^6.0.0", "nullthrows": "^1.1.1", "postcss-value-parser": "^4.2.0", "styleq": "^0.1.3" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg=="],
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -36,6 +36,7 @@
 | 
			
		||||
		"expo-status-bar": "~3.0.8",
 | 
			
		||||
		"expo-updates": "~29.0.11",
 | 
			
		||||
		"i18next-http-backend": "^3.0.2",
 | 
			
		||||
		"langmap": "^0.0.16",
 | 
			
		||||
		"react": "19.1.0",
 | 
			
		||||
		"react-dom": "19.1.0",
 | 
			
		||||
		"react-i18next": "^16.1.0",
 | 
			
		||||
 | 
			
		||||
@ -1,43 +1,4 @@
 | 
			
		||||
import type { Subtitle, Track } from "@kyoo/models";
 | 
			
		||||
 | 
			
		||||
import intl from "langmap";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
 | 
			
		||||
export const useLanguageName = () => {
 | 
			
		||||
	return (lang: string) => intl[lang]?.nativeName;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useDisplayName = () => {
 | 
			
		||||
	const getLanguageName = useLanguageName();
 | 
			
		||||
	const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
	return (sub: Track) => {
 | 
			
		||||
		const lng = sub.language ? getLanguageName(sub.language) : null;
 | 
			
		||||
 | 
			
		||||
		if (lng && sub.title && sub.title !== lng) return `${lng} - ${sub.title}`;
 | 
			
		||||
		if (lng) return lng;
 | 
			
		||||
		if (sub.title) return sub.title;
 | 
			
		||||
		if (sub.index !== null) return `${t("mediainfo.unknown")} (${sub.index})`;
 | 
			
		||||
		return t("mediainfo.unknown");
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useSubtitleName = () => {
 | 
			
		||||
	const getDisplayName = useDisplayName();
 | 
			
		||||
	const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
	return (sub: Subtitle) => {
 | 
			
		||||
		const name = getDisplayName(sub);
 | 
			
		||||
		const attributes = [name];
 | 
			
		||||
 | 
			
		||||
		if (sub.isDefault) attributes.push(t("mediainfo.default"));
 | 
			
		||||
		if (sub.isForced) attributes.push(t("mediainfo.forced"));
 | 
			
		||||
		if (sub.isHearingImpaired) attributes.push(t("mediainfo.hearing-impaired"));
 | 
			
		||||
		if (sub.isExternal) attributes.push(t("mediainfo.external"));
 | 
			
		||||
 | 
			
		||||
		return attributes.join(" - ");
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const seenNativeNames = new Set();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										39
									
								
								front/src/track-utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								front/src/track-utils.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
import intl from "langmap";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import type { Subtitle } from "./models";
 | 
			
		||||
 | 
			
		||||
export const useLanguageName = () => {
 | 
			
		||||
	return (lang: string) => intl[lang]?.nativeName;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useDisplayName = () => {
 | 
			
		||||
	const getLanguageName = useLanguageName();
 | 
			
		||||
	const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
	return (sub: { language?: string; title?: string; index?: number }) => {
 | 
			
		||||
		const lng = sub.language ? getLanguageName(sub.language) : null;
 | 
			
		||||
 | 
			
		||||
		if (lng && sub.title && sub.title !== lng) return `${lng} - ${sub.title}`;
 | 
			
		||||
		if (lng) return lng;
 | 
			
		||||
		if (sub.title) return sub.title;
 | 
			
		||||
		if (sub.index !== null) return `${t("mediainfo.unknown")} (${sub.index})`;
 | 
			
		||||
		return t("mediainfo.unknown");
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useSubtitleName = () => {
 | 
			
		||||
	const getDisplayName = useDisplayName();
 | 
			
		||||
	const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
	return (sub: Subtitle) => {
 | 
			
		||||
		const name = getDisplayName(sub);
 | 
			
		||||
		const attributes = [name];
 | 
			
		||||
 | 
			
		||||
		if (sub.isDefault) attributes.push(t("mediainfo.default"));
 | 
			
		||||
		if (sub.isForced) attributes.push(t("mediainfo.forced"));
 | 
			
		||||
		if (sub.isHearingImpaired) attributes.push(t("mediainfo.hearing-impaired"));
 | 
			
		||||
		if (sub.isExternal) attributes.push(t("mediainfo.external"));
 | 
			
		||||
 | 
			
		||||
		return attributes.join(" - ");
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
@ -19,7 +19,7 @@ import {
 | 
			
		||||
} from "~/primitives";
 | 
			
		||||
import { FullscreenButton, PlayButton, VolumeSlider } from "./misc";
 | 
			
		||||
import { ProgressBar, ProgressText } from "./progress";
 | 
			
		||||
import { AudioMenu, QualityMenu, SubtitleMenu } from "./tracks-menu";
 | 
			
		||||
import { AudioMenu, QualityMenu, SubtitleMenu, VideoMenu } from "./tracks-menu";
 | 
			
		||||
 | 
			
		||||
export const BottomControls = ({
 | 
			
		||||
	player,
 | 
			
		||||
@ -164,7 +164,8 @@ const ControlButtons = ({
 | 
			
		||||
			</View>
 | 
			
		||||
			<View {...css({ flexDirection: "row" })}>
 | 
			
		||||
				<SubtitleMenu {...menuProps} />
 | 
			
		||||
				<AudioMenu {...menuProps} />
 | 
			
		||||
				<AudioMenu player={player} {...menuProps} />
 | 
			
		||||
				<VideoMenu {...menuProps} />
 | 
			
		||||
				<QualityMenu {...menuProps} />
 | 
			
		||||
				{Platform.OS === "web" && <FullscreenButton {...spacing} />}
 | 
			
		||||
			</View>
 | 
			
		||||
 | 
			
		||||
@ -42,7 +42,7 @@ export const MiddleControls = ({
 | 
			
		||||
			<IconButton
 | 
			
		||||
				icon={SkipPrevious}
 | 
			
		||||
				as={Link}
 | 
			
		||||
				href={previous}
 | 
			
		||||
				href={previous ?? ""}
 | 
			
		||||
				replace
 | 
			
		||||
				size={ts(4)}
 | 
			
		||||
				{...css([!previous && { opacity: 0, pointerEvents: "none" }], common)}
 | 
			
		||||
@ -51,7 +51,7 @@ export const MiddleControls = ({
 | 
			
		||||
			<IconButton
 | 
			
		||||
				icon={SkipNext}
 | 
			
		||||
				as={Link}
 | 
			
		||||
				href={next}
 | 
			
		||||
				href={next ?? ""}
 | 
			
		||||
				replace
 | 
			
		||||
				size={ts(4)}
 | 
			
		||||
				{...css([!next && { opacity: 0, pointerEvents: "none" }], common)}
 | 
			
		||||
 | 
			
		||||
@ -6,9 +6,9 @@ import VolumeDown from "@material-symbols/svg-400/rounded/volume_down-fill.svg";
 | 
			
		||||
import VolumeMute from "@material-symbols/svg-400/rounded/volume_mute-fill.svg";
 | 
			
		||||
import VolumeOff from "@material-symbols/svg-400/rounded/volume_off-fill.svg";
 | 
			
		||||
import VolumeUp from "@material-symbols/svg-400/rounded/volume_up-fill.svg";
 | 
			
		||||
import { type ComponentProps, useState, useEffect } from "react";
 | 
			
		||||
import { type ComponentProps, useEffect, useState } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { type PressableProps, View, Platform } from "react-native";
 | 
			
		||||
import { type PressableProps, View } from "react-native";
 | 
			
		||||
import { useEvent, type VideoPlayer } from "react-native-video";
 | 
			
		||||
import { px, useYoshiki } from "yoshiki/native";
 | 
			
		||||
import {
 | 
			
		||||
 | 
			
		||||
@ -98,7 +98,7 @@ export const ProgressText = ({
 | 
			
		||||
 | 
			
		||||
const toTimerString = (timer?: number, duration?: number) => {
 | 
			
		||||
	if (!duration) duration = timer;
 | 
			
		||||
	if (timer === undefined || Number.isNaN(timer)) return "??:??";
 | 
			
		||||
	if (timer === undefined || !Number.isFinite(timer)) return "??:??";
 | 
			
		||||
 | 
			
		||||
	const h = Math.floor(timer / 3600);
 | 
			
		||||
	const min = Math.floor((timer / 60) % 60);
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,13 @@
 | 
			
		||||
import ClosedCaption from "@material-symbols/svg-400/rounded/closed_caption-fill.svg";
 | 
			
		||||
import MusicNote from "@material-symbols/svg-400/rounded/music_note-fill.svg";
 | 
			
		||||
import SettingsIcon from "@material-symbols/svg-400/rounded/settings-fill.svg";
 | 
			
		||||
import type { ComponentProps } from "react";
 | 
			
		||||
import VideoSettings from "@material-symbols/svg-400/rounded/video_settings-fill.svg";
 | 
			
		||||
import { type ComponentProps, createContext, useContext } from "react";
 | 
			
		||||
import { useEvent, type VideoPlayer } from "react-native-video";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { IconButton, Menu, tooltip } from "~/primitives";
 | 
			
		||||
import { useDisplayName } from "~/track-utils";
 | 
			
		||||
import { useForceRerender } from "yoshiki";
 | 
			
		||||
 | 
			
		||||
type MenuProps = ComponentProps<typeof Menu<ComponentProps<typeof IconButton>>>;
 | 
			
		||||
 | 
			
		||||
@ -41,8 +45,19 @@ export const SubtitleMenu = (props: Partial<MenuProps>) => {
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const AudioMenu = (props: Partial<MenuProps>) => {
 | 
			
		||||
export const AudioMenu = ({
 | 
			
		||||
	player,
 | 
			
		||||
	...props
 | 
			
		||||
}: { player: VideoPlayer } & Partial<MenuProps>) => {
 | 
			
		||||
	const { t } = useTranslation();
 | 
			
		||||
	const getDisplayName = useDisplayName();
 | 
			
		||||
	const rerender = useForceRerender();
 | 
			
		||||
 | 
			
		||||
	useEvent(player, "onAudioTrackChange", rerender);
 | 
			
		||||
 | 
			
		||||
	const tracks = player.getAvailableAudioTracks();
 | 
			
		||||
 | 
			
		||||
	if (tracks.length === 0) return null;
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<Menu
 | 
			
		||||
@ -50,12 +65,39 @@ export const AudioMenu = (props: Partial<MenuProps>) => {
 | 
			
		||||
			icon={MusicNote}
 | 
			
		||||
			{...tooltip(t("player.audios"), true)}
 | 
			
		||||
			{...props}
 | 
			
		||||
		>
 | 
			
		||||
			{tracks.map((x) => (
 | 
			
		||||
				<Menu.Item
 | 
			
		||||
					key={x.id}
 | 
			
		||||
					label={getDisplayName({ title: x.label, language: x.language })}
 | 
			
		||||
					selected={x.selected}
 | 
			
		||||
					onSelect={() => player.selectAudioTrack(x)}
 | 
			
		||||
				/>
 | 
			
		||||
			))}
 | 
			
		||||
		</Menu>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const VideoMenu = (props: Partial<MenuProps>) => {
 | 
			
		||||
	const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<Menu
 | 
			
		||||
			Trigger={IconButton}
 | 
			
		||||
			icon={VideoSettings}
 | 
			
		||||
			{...tooltip(t("player.audios"), true)}
 | 
			
		||||
			{...props}
 | 
			
		||||
		></Menu>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const PlayModeContext = createContext<
 | 
			
		||||
	["direct" | "hls", (val: "direct" | "hls") => void]
 | 
			
		||||
>(null!);
 | 
			
		||||
 | 
			
		||||
export const QualityMenu = (props: Partial<MenuProps>) => {
 | 
			
		||||
	const { t } = useTranslation();
 | 
			
		||||
	const [playMode, setPlayMode] = useContext(PlayModeContext);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<Menu
 | 
			
		||||
@ -63,6 +105,41 @@ export const QualityMenu = (props: Partial<MenuProps>) => {
 | 
			
		||||
			icon={SettingsIcon}
 | 
			
		||||
			{...tooltip(t("player.quality"), true)}
 | 
			
		||||
			{...props}
 | 
			
		||||
		></Menu>
 | 
			
		||||
		>
 | 
			
		||||
			<Menu.Item
 | 
			
		||||
				label={t("player.direct")}
 | 
			
		||||
				selected={playMode === "direct"}
 | 
			
		||||
				onSelect={() => setPlayMode("direct")}
 | 
			
		||||
			/>
 | 
			
		||||
			{/* <Menu.Item */}
 | 
			
		||||
			{/* 	label={ */}
 | 
			
		||||
			{/* 		hls?.autoLevelEnabled && hls.currentLevel >= 0 */}
 | 
			
		||||
			{/* 			? `${t("player.auto")} (${levelName(hls.levels[hls.currentLevel], true)})` */}
 | 
			
		||||
			{/* 			: t("player.auto") */}
 | 
			
		||||
			{/* 	} */}
 | 
			
		||||
			{/* 	selected={hls?.autoLevelEnabled && mode === PlayMode.Hls} */}
 | 
			
		||||
			{/* 	onSelect={() => { */}
 | 
			
		||||
			{/* 		setPlayMode(PlayMode.Hls); */}
 | 
			
		||||
			{/* 		if (hls) hls.currentLevel = -1; */}
 | 
			
		||||
			{/* 	}} */}
 | 
			
		||||
			{/* /> */}
 | 
			
		||||
			{/* {hls?.levels */}
 | 
			
		||||
			{/* 	.map((x, i) => ( */}
 | 
			
		||||
			{/* 		<Menu.Item */}
 | 
			
		||||
			{/* 			key={i.toString()} */}
 | 
			
		||||
			{/* 			label={levelName(x)} */}
 | 
			
		||||
			{/* 			selected={ */}
 | 
			
		||||
			{/* 				mode === PlayMode.Hls && */}
 | 
			
		||||
			{/* 				hls!.currentLevel === i && */}
 | 
			
		||||
			{/* 				!hls?.autoLevelEnabled */}
 | 
			
		||||
			{/* 			} */}
 | 
			
		||||
			{/* 			onSelect={() => { */}
 | 
			
		||||
			{/* 				setPlayMode(PlayMode.Hls); */}
 | 
			
		||||
			{/* 				hls!.currentLevel = i; */}
 | 
			
		||||
			{/* 			}} */}
 | 
			
		||||
			{/* 		/> */}
 | 
			
		||||
			{/* 	)) */}
 | 
			
		||||
			{/* 	.reverse()} */}
 | 
			
		||||
		</Menu>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ import { Stack, useRouter } from "expo-router";
 | 
			
		||||
import { Platform, StyleSheet, View } from "react-native";
 | 
			
		||||
import { useEvent, useVideoPlayer, VideoView } from "react-native-video";
 | 
			
		||||
import { entryDisplayNumber } from "~/components/entries";
 | 
			
		||||
import { FullVideo, VideoInfo } from "~/models";
 | 
			
		||||
import { FullVideo, type KyooError, VideoInfo } from "~/models";
 | 
			
		||||
import { ContrastArea, Head } from "~/primitives";
 | 
			
		||||
import { useToken } from "~/providers/account-context";
 | 
			
		||||
import { useLocalSetting } from "~/providers/settings";
 | 
			
		||||
@ -15,6 +15,7 @@ import { toggleFullscreen } from "./controls/misc";
 | 
			
		||||
import { Back } from "./controls/back";
 | 
			
		||||
import { useYoshiki } from "yoshiki/native";
 | 
			
		||||
import { ErrorView } from "../errors";
 | 
			
		||||
import { PlayModeContext } from "./controls/tracks-menu";
 | 
			
		||||
 | 
			
		||||
const clientId = uuidv4();
 | 
			
		||||
 | 
			
		||||
@ -27,14 +28,19 @@ export const Player = () => {
 | 
			
		||||
	// TODO: map current entry using entries' duration & the current playtime
 | 
			
		||||
	const currentEntry = 0;
 | 
			
		||||
	const entry = data?.entries[currentEntry] ?? data?.entries[0];
 | 
			
		||||
	const title = entry ? `${entry.name} (${entryDisplayNumber(entry)})` : null;
 | 
			
		||||
	const title = entry
 | 
			
		||||
		? entry.kind === "movie"
 | 
			
		||||
			? entry.name
 | 
			
		||||
			: `${entry.name} (${entryDisplayNumber(entry)})`
 | 
			
		||||
		: null;
 | 
			
		||||
 | 
			
		||||
	const { apiUrl, authToken } = useToken();
 | 
			
		||||
	const [defaultPlayMode] = useLocalSetting<"direct" | "hls">(
 | 
			
		||||
		"playMode",
 | 
			
		||||
		"direct",
 | 
			
		||||
	);
 | 
			
		||||
	const [playMode, setPlayMode] = useState(defaultPlayMode);
 | 
			
		||||
	const playModeState = useState(defaultPlayMode);
 | 
			
		||||
	const [playMode, setPlayMode] = playModeState;
 | 
			
		||||
	const player = useVideoPlayer(
 | 
			
		||||
		{
 | 
			
		||||
			uri: `${apiUrl}/api/videos/${slug}/${playMode === "direct" ? "direct" : "master.m3u8"}?clientId=${clientId}`,
 | 
			
		||||
@ -50,8 +56,8 @@ export const Player = () => {
 | 
			
		||||
				: {},
 | 
			
		||||
			metadata: {
 | 
			
		||||
				title: title ?? undefined,
 | 
			
		||||
				description: entry?.description ?? undefined,
 | 
			
		||||
				artist: data?.show?.name ?? undefined,
 | 
			
		||||
				description: entry?.description ?? undefined,
 | 
			
		||||
				imageUri: data?.show?.thumbnail?.high ?? undefined,
 | 
			
		||||
			},
 | 
			
		||||
			externalSubtitles: info?.subtitles
 | 
			
		||||
@ -124,15 +130,14 @@ export const Player = () => {
 | 
			
		||||
		};
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
	const [playbackError, setPlaybackError] = useState<string | undefined>();
 | 
			
		||||
	const [playbackError, setPlaybackError] = useState<KyooError | undefined>();
 | 
			
		||||
	useEvent(player, "onError", (error) => {
 | 
			
		||||
		console.log("error", error, "code", error.code, "playbackMode", playMode);
 | 
			
		||||
		if (
 | 
			
		||||
			error.code === "source/unsupported-content-type" &&
 | 
			
		||||
			playMode === "direct"
 | 
			
		||||
		)
 | 
			
		||||
			setPlayMode("hls");
 | 
			
		||||
		else setPlaybackError(error);
 | 
			
		||||
		else setPlaybackError({ status: error.code, message: error.message });
 | 
			
		||||
	});
 | 
			
		||||
	const { css } = useYoshiki();
 | 
			
		||||
	if (error || infoError || playbackError) {
 | 
			
		||||
@ -142,7 +147,7 @@ export const Player = () => {
 | 
			
		||||
					name={data?.show?.name ?? "Error"}
 | 
			
		||||
					{...css({ position: "relative", bg: (theme) => theme.accent })}
 | 
			
		||||
				/>
 | 
			
		||||
				<ErrorView error={error ?? infoError ?? { errors: [playbackError!] }} />
 | 
			
		||||
				<ErrorView error={error ?? infoError ?? playbackError!} />
 | 
			
		||||
			</>
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
@ -177,21 +182,23 @@ export const Player = () => {
 | 
			
		||||
			/>
 | 
			
		||||
			<ContrastArea mode="dark">
 | 
			
		||||
				<LoadingIndicator player={player} />
 | 
			
		||||
				<Controls
 | 
			
		||||
					player={player}
 | 
			
		||||
					name={data?.show?.name}
 | 
			
		||||
					poster={data?.show?.poster}
 | 
			
		||||
					subName={
 | 
			
		||||
						entry
 | 
			
		||||
							? [entryDisplayNumber(entry), entry.name]
 | 
			
		||||
									.filter((x) => x)
 | 
			
		||||
									.join(" - ")
 | 
			
		||||
							: undefined
 | 
			
		||||
					}
 | 
			
		||||
					chapters={info?.chapters ?? []}
 | 
			
		||||
					previous={data?.previous?.video}
 | 
			
		||||
					next={data?.next?.video}
 | 
			
		||||
				/>
 | 
			
		||||
				<PlayModeContext.Provider value={playModeState}>
 | 
			
		||||
					<Controls
 | 
			
		||||
						player={player}
 | 
			
		||||
						name={data?.show?.name}
 | 
			
		||||
						poster={data?.show?.poster}
 | 
			
		||||
						subName={
 | 
			
		||||
							entry
 | 
			
		||||
								? [entryDisplayNumber(entry), entry.name]
 | 
			
		||||
										.filter((x) => x)
 | 
			
		||||
										.join(" - ")
 | 
			
		||||
								: undefined
 | 
			
		||||
						}
 | 
			
		||||
						chapters={info?.chapters ?? []}
 | 
			
		||||
						previous={data?.previous?.video}
 | 
			
		||||
						next={data?.next?.video}
 | 
			
		||||
					/>
 | 
			
		||||
				</PlayModeContext.Provider>
 | 
			
		||||
			</ContrastArea>
 | 
			
		||||
		</View>
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user