diff --git a/app/(auth)/player/_layout.tsx b/app/(auth)/player/_layout.tsx
index 1d6c7188..bee527e5 100644
--- a/app/(auth)/player/_layout.tsx
+++ b/app/(auth)/player/_layout.tsx
@@ -36,15 +36,6 @@ export default function Layout() {
animation: "fade",
}}
/>
-
>
);
diff --git a/app/(auth)/player/transcoding-player.tsx b/app/(auth)/player/transcoding-player.tsx
deleted file mode 100644
index b1768b36..00000000
--- a/app/(auth)/player/transcoding-player.tsx
+++ /dev/null
@@ -1,546 +0,0 @@
-import { Text } from "@/components/common/Text";
-import { Loader } from "@/components/Loader";
-import { Controls } from "@/components/video-player/controls/Controls";
-import { useOrientation } from "@/hooks/useOrientation";
-import { useOrientationSettings } from "@/hooks/useOrientationSettings";
-import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache";
-import { useWebSocket } from "@/hooks/useWebsockets";
-import { TrackInfo } from "@/modules/vlc-player";
-import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
-import { useSettings } from "@/utils/atoms/settings";
-import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
-import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
-import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
-import transcoding from "@/utils/profiles/transcoding";
-import { secondsToTicks } from "@/utils/secondsToTicks";
-import { Api } from "@jellyfin/sdk";
-import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
-import {
- getPlaystateApi,
- getUserLibraryApi,
-} from "@jellyfin/sdk/lib/utils/api";
-import { useQuery } from "@tanstack/react-query";
-import { useHaptic } from "@/hooks/useHaptic";
-import { useFocusEffect, useLocalSearchParams } from "expo-router";
-import { useAtomValue } from "jotai";
-import React, {
- useCallback,
- useEffect,
- useMemo,
- useRef,
- useState,
-} from "react";
-import { View } from "react-native";
-import { useSharedValue } from "react-native-reanimated";
-import Video, {
- OnProgressData,
- SelectedTrack,
- SelectedTrackType,
- VideoRef,
-} from "react-native-video";
-import { SubtitleHelper } from "@/utils/SubtitleHelper";
-import { useTranslation } from "react-i18next";
-
-const Player = () => {
- console.log("Transcoding Player");
-
- const api = useAtomValue(apiAtom);
- const user = useAtomValue(userAtom);
- const [settings] = useSettings();
- const videoRef = useRef(null);
- const { t } = useTranslation();
-
- const firstTime = useRef(true);
- const revalidateProgressCache = useInvalidatePlaybackProgressCache();
- const lightHapticFeedback = useHaptic("light");
-
- const [isPlaybackStopped, setIsPlaybackStopped] = useState(false);
- const [showControls, _setShowControls] = useState(true);
- const [ignoreSafeAreas, setIgnoreSafeAreas] = useState(false);
- const [isPlaying, setIsPlaying] = useState(false);
- const [isBuffering, setIsBuffering] = useState(true);
- const [isVideoLoaded, setIsVideoLoaded] = useState(false);
-
- const setShowControls = useCallback((show: boolean) => {
- _setShowControls(show);
- lightHapticFeedback();
- }, []);
-
- const progress = useSharedValue(0);
- const isSeeking = useSharedValue(false);
- const cacheProgress = useSharedValue(0);
-
- const {
- itemId,
- audioIndex: audioIndexStr,
- subtitleIndex: subtitleIndexStr,
- mediaSourceId,
- bitrateValue: bitrateValueStr,
- } = useLocalSearchParams<{
- itemId: string;
- audioIndex: string;
- subtitleIndex: string;
- mediaSourceId: string;
- bitrateValue: string;
- }>();
-
- const audioIndex = audioIndexStr ? parseInt(audioIndexStr, 10) : undefined;
- const subtitleIndex = subtitleIndexStr
- ? parseInt(subtitleIndexStr, 10)
- : undefined;
- const bitrateValue = bitrateValueStr
- ? parseInt(bitrateValueStr, 10)
- : undefined;
-
- const {
- data: item,
- isLoading: isLoadingItem,
- isError: isErrorItem,
- } = useQuery({
- queryKey: ["item", itemId],
- queryFn: async () => {
- if (!api) {
- throw new Error("No api");
- }
-
- if (!itemId) {
- console.warn("No itemId");
- return null;
- }
-
- const res = await getUserLibraryApi(api).getItem({
- itemId,
- userId: user?.Id,
- });
-
- return res.data;
- },
- staleTime: 0,
- });
-
- // TODO: NEED TO FIND A WAY TO FROM SWITCHING TO IMAGE BASED TO TEXT BASED SUBTITLES, THERE IS A BUG.
- // MOST LIKELY LIKELY NEED A MASSIVE REFACTOR.
- const {
- data: stream,
- isLoading: isLoadingStreamUrl,
- isError: isErrorStreamUrl,
- } = useQuery({
- queryKey: ["stream-url", itemId, bitrateValue, mediaSourceId, audioIndex],
-
- queryFn: async () => {
- if (!api) {
- throw new Error("No api");
- }
-
- if (!item) {
- console.warn("No item", itemId, item);
- return null;
- }
-
- const res = await getStreamUrl({
- api,
- item,
- startTimeTicks: item?.UserData?.PlaybackPositionTicks!,
- userId: user?.Id,
- audioStreamIndex: audioIndex,
- maxStreamingBitrate: bitrateValue,
- mediaSourceId: mediaSourceId,
- subtitleStreamIndex: subtitleIndex,
- deviceProfile: transcoding,
- });
-
- if (!res) return null;
-
- const { mediaSource, sessionId, url } = res;
-
- if (!sessionId || !mediaSource || !url) {
- console.warn("No sessionId or mediaSource or url", url);
- return null;
- }
-
- return {
- mediaSource,
- sessionId,
- url,
- };
- },
- enabled: !!item,
- staleTime: 0,
- });
-
- const poster = usePoster(item, api);
- const videoSource = useVideoSource(item, api, poster, stream?.url);
-
- const togglePlay = useCallback(async () => {
- lightHapticFeedback();
- if (isPlaying) {
- videoRef.current?.pause();
- await getPlaystateApi(api!).onPlaybackProgress({
- itemId: item?.Id!,
- audioStreamIndex: audioIndex ? audioIndex : undefined,
- subtitleStreamIndex: subtitleIndex ? subtitleIndex : undefined,
- mediaSourceId: mediaSourceId,
- positionTicks: Math.floor(progress.value),
- isPaused: true,
- playMethod: stream?.url.includes("m3u8") ? "Transcode" : "DirectStream",
- playSessionId: stream?.sessionId,
- });
- } else {
- videoRef.current?.resume();
- await getPlaystateApi(api!).onPlaybackProgress({
- itemId: item?.Id!,
- audioStreamIndex: audioIndex ? audioIndex : undefined,
- subtitleStreamIndex: subtitleIndex ? subtitleIndex : undefined,
- mediaSourceId: mediaSourceId,
- positionTicks: Math.floor(progress.value),
- isPaused: false,
- playMethod: stream?.url.includes("m3u8") ? "Transcode" : "DirectStream",
- playSessionId: stream?.sessionId,
- });
- }
- }, [
- isPlaying,
- api,
- item,
- videoRef,
- settings,
- stream,
- audioIndex,
- subtitleIndex,
- mediaSourceId,
- ]);
-
- const play = useCallback(() => {
- videoRef.current?.resume();
- reportPlaybackStart();
- }, [videoRef]);
-
- const pause = useCallback(() => {
- videoRef.current?.pause();
- }, [videoRef]);
-
- const seek = useCallback(
- (seconds: number) => {
- videoRef.current?.seek(seconds);
- },
- [videoRef]
- );
-
- const reportPlaybackStopped = async () => {
- if (!item?.Id) return;
- await getPlaystateApi(api!).onPlaybackStopped({
- itemId: item.Id,
- mediaSourceId: mediaSourceId,
- positionTicks: Math.floor(progress.value),
- playSessionId: stream?.sessionId,
- });
- revalidateProgressCache();
- };
-
- const stop = useCallback(() => {
- reportPlaybackStopped();
- videoRef.current?.pause();
- setIsPlaybackStopped(true);
- }, [videoRef, reportPlaybackStopped]);
-
- const reportPlaybackStart = async () => {
- if (!item?.Id) return;
- await getPlaystateApi(api!).onPlaybackStart({
- itemId: item.Id,
- audioStreamIndex: audioIndex ? audioIndex : undefined,
- subtitleStreamIndex: subtitleIndex ? subtitleIndex : undefined,
- mediaSourceId: mediaSourceId,
- playMethod: stream?.url.includes("m3u8") ? "Transcode" : "DirectStream",
- playSessionId: stream?.sessionId,
- });
- };
-
- const onProgress = useCallback(
- async (data: OnProgressData) => {
- if (isSeeking.value === true) return;
- if (isPlaybackStopped === true) return;
-
- const ticks = secondsToTicks(data.currentTime);
-
- progress.value = ticks;
- cacheProgress.value = secondsToTicks(data.playableDuration);
-
- // TODO: Use this when streaming with HLS url, but NOT when direct playing
- // TODO: since playable duration is always 0 then.
- setIsBuffering(data.playableDuration === 0);
-
- if (!item?.Id || data.currentTime === 0) {
- return;
- }
-
- await getPlaystateApi(api!).onPlaybackProgress({
- itemId: item.Id,
- audioStreamIndex: audioIndex ? audioIndex : undefined,
- subtitleStreamIndex: subtitleIndex ? subtitleIndex : undefined,
- mediaSourceId: mediaSourceId,
- positionTicks: Math.round(ticks),
- isPaused: !isPlaying,
- playMethod: stream?.url.includes("m3u8") ? "Transcode" : "DirectStream",
- playSessionId: stream?.sessionId,
- });
- },
- [
- item,
- isPlaying,
- api,
- isPlaybackStopped,
- isSeeking,
- stream,
- mediaSourceId,
- audioIndex,
- subtitleIndex,
- ]
- );
-
- useWebSocket({
- isPlaying: isPlaying,
- togglePlay: togglePlay,
- stopPlayback: stop,
- offline: false,
- });
-
- const [selectedTextTrack, setSelectedTextTrack] = useState<
- SelectedTrack | undefined
- >();
-
- const [embededTextTracks, setEmbededTextTracks] = useState<
- {
- index: number;
- language?: string | undefined;
- selected?: boolean | undefined;
- title?: string | undefined;
- type: any;
- }[]
- >([]);
-
- const [audioTracks, setAudioTracks] = useState([]);
- const [selectedAudioTrack, setSelectedAudioTrack] = useState<
- SelectedTrack | undefined
- >(undefined);
-
- useEffect(() => {
- if (selectedTextTrack === undefined) {
- const subtitleHelper = new SubtitleHelper(
- stream?.mediaSource.MediaStreams ?? []
- );
- const embeddedTrackIndex = subtitleHelper.getEmbeddedTrackIndex(
- subtitleIndex!
- );
-
- // Most likely the subtitle is burned in.
- if (embeddedTrackIndex === -1) return;
-
- setSelectedTextTrack({
- type: SelectedTrackType.INDEX,
- value: embeddedTrackIndex,
- });
- }
- }, [embededTextTracks]);
-
- const getAudioTracks = (): TrackInfo[] => {
- return audioTracks.map((t) => ({
- name: t.name,
- index: t.index,
- }));
- };
-
- const getSubtitleTracks = (): TrackInfo[] => {
- return embededTextTracks.map((t) => ({
- name: t.title ?? "",
- index: t.index,
- language: t.language,
- }));
- };
-
- useFocusEffect(
- React.useCallback(() => {
- return async () => {
- stop();
- };
- }, [])
- );
-
- if (isLoadingItem || isLoadingStreamUrl)
- return (
-
-
-
- );
-
- if (isErrorItem || isErrorStreamUrl)
- return (
-
- {t("player.error")}
-
- );
-
- return (
-
-
- {videoSource ? (
- <>
-
-
- {item && (
- {
- if (i === -1) {
- setSelectedTextTrack({
- type: SelectedTrackType.DISABLED,
- value: undefined,
- });
- return;
- }
- setSelectedTextTrack({
- type: SelectedTrackType.INDEX,
- value: i,
- });
- }}
- getAudioTracks={getAudioTracks}
- setAudioTrack={(i) => {
- setSelectedAudioTrack({
- type: SelectedTrackType.INDEX,
- value: i,
- });
- }}
- />
- )}
-
- );
-};
-
-export function usePoster(
- item: BaseItemDto | null | undefined,
- api: Api | null
-): string | undefined {
- const poster = useMemo(() => {
- if (!item || !api) return undefined;
- return item.Type === "Audio"
- ? `${api.basePath}/Items/${item.AlbumId}/Images/Primary?tag=${item.AlbumPrimaryImageTag}&quality=90&maxHeight=200&maxWidth=200`
- : getBackdropUrl({
- api,
- item: item,
- quality: 70,
- width: 200,
- });
- }, [item, api]);
-
- return poster ?? undefined;
-}
-
-export function useVideoSource(
- item: BaseItemDto | null | undefined,
- api: Api | null,
- poster: string | undefined,
- url?: string | null
-) {
- const videoSource = useMemo(() => {
- if (!item || !api || !url) {
- return null;
- }
-
- const startPosition = item?.UserData?.PlaybackPositionTicks
- ? Math.round(item.UserData.PlaybackPositionTicks / 10000)
- : 0;
-
- return {
- uri: url,
- isNetwork: true,
- startPosition,
- headers: getAuthHeaders(api),
- metadata: {
- title: item?.Name || "Unknown",
- description: item?.Overview ?? undefined,
- imageUri: poster,
- subtitle: item?.Album ?? undefined,
- },
- };
- }, [item, api, poster, url]);
-
- return videoSource;
-}
-
-export default Player;
diff --git a/components/PlayButton.tsx b/components/PlayButton.tsx
index a9195aa6..4a6a2e30 100644
--- a/components/PlayButton.tsx
+++ b/components/PlayButton.tsx
@@ -73,11 +73,7 @@ export const PlayButton: React.FC = ({
const goToPlayer = useCallback(
(q: string, bitrateValue: number | undefined) => {
- if (!bitrateValue) {
- router.push(`/player/direct-player?${q}`);
- return;
- }
- router.push(`/player/transcoding-player?${q}`);
+ router.push(`/player/direct-player?${q}`);
},
[router]
);
diff --git a/components/PlayButton.tv.tsx b/components/PlayButton.tv.tsx
index 65f70ad3..128c2184 100644
--- a/components/PlayButton.tv.tsx
+++ b/components/PlayButton.tv.tsx
@@ -58,11 +58,7 @@ export const PlayButton: React.FC = ({
const goToPlayer = useCallback(
(q: string, bitrateValue: number | undefined) => {
- if (!bitrateValue) {
- router.push(`/player/direct-player?${q}`);
- return;
- }
- router.push(`/player/transcoding-player?${q}`);
+ router.push(`/player/direct-player?${q}`);
},
[router]
);
diff --git a/components/video-player/controls/Controls.tsx b/components/video-player/controls/Controls.tsx
index b52e98c7..4198259e 100644
--- a/components/video-player/controls/Controls.tsx
+++ b/components/video-player/controls/Controls.tsx
@@ -24,7 +24,7 @@ import {
ticksToMs,
ticksToSeconds,
} from "@/utils/time";
-import {Ionicons, MaterialIcons} from "@expo/vector-icons";
+import { Ionicons, MaterialIcons } from "@expo/vector-icons";
import {
BaseItemDto,
MediaSourceInfo,
@@ -35,7 +35,12 @@ import * as ScreenOrientation from "@/packages/expo-screen-orientation";
import { useAtom } from "jotai";
import { debounce } from "lodash";
import React, { useCallback, useEffect, useRef, useState } from "react";
-import {Platform, TouchableOpacity, useWindowDimensions, View} from "react-native";
+import {
+ Platform,
+ TouchableOpacity,
+ useWindowDimensions,
+ View,
+} from "react-native";
import { Slider } from "react-native-awesome-slider";
import {
runOnJS,
@@ -214,15 +219,10 @@ export const Controls: React.FC = ({
bitrateValue: bitrateValue.toString(),
}).toString();
- stop()
+ stop();
- if (!bitrateValue) {
- // @ts-expect-error
- router.replace(`player/direct-player?${queryParams}`);
- return;
- }
// @ts-expect-error
- router.replace(`player/transcoding-player?${queryParams}`);
+ router.replace(`player/direct-player?${queryParams}`);
}, [previousItem, settings, subtitleIndex, audioIndex]);
const goToNextItem = useCallback(() => {
@@ -254,15 +254,10 @@ export const Controls: React.FC = ({
bitrateValue: bitrateValue.toString(),
}).toString();
- stop()
+ stop();
- if (!bitrateValue) {
- // @ts-expect-error
- router.replace(`player/direct-player?${queryParams}`);
- return;
- }
// @ts-expect-error
- router.replace(`player/transcoding-player?${queryParams}`);
+ router.replace(`player/direct-player?${queryParams}`);
}, [nextItem, settings, subtitleIndex, audioIndex]);
const updateTimes = useCallback(
@@ -419,15 +414,10 @@ export const Controls: React.FC = ({
bitrateValue: bitrateValue.toString(),
}).toString();
- stop()
+ stop();
- if (!bitrateValue) {
- // @ts-expect-error
- router.replace(`player/direct-player?${queryParams}`);
- return;
- }
// @ts-expect-error
- router.replace(`player/transcoding-player?${queryParams}`);
+ router.replace(`player/direct-player?${queryParams}`);
} catch (error) {
console.error("Error in gotoEpisode:", error);
}
@@ -508,7 +498,7 @@ export const Controls: React.FC = ({
}, [trickPlayUrl, trickplayInfo, time]);
const onClose = async () => {
- stop()
+ stop();
lightHapticFeedback();
await ScreenOrientation.lockAsync(
ScreenOrientation.OrientationLock.PORTRAIT_UP
@@ -569,9 +559,7 @@ export const Controls: React.FC = ({
{!Platform.isTV && (
-
+
= ({ showControls }) => {
return [disableSubtitle, ...transcodedSubtitle];
}, [item, isVideoLoaded, subtitleTracks, mediaSource?.MediaStreams]);
+ // TODO: Make image based subtitles work with transcoding streams in VLC
const changeToImageBasedSub = useCallback(
(subtitleIndex: number) => {
const queryParams = new URLSearchParams({
@@ -88,8 +89,7 @@ const DropdownView: React.FC = ({ showControls }) => {
bitrateValue: bitrateValue,
}).toString();
- // @ts-expect-error
- router.replace(`player/transcoding-player?${queryParams}`);
+ // router.replace(`player/transcoding-player?${queryParams}`);
},
[mediaSource]
);
@@ -101,6 +101,7 @@ const DropdownView: React.FC = ({ showControls }) => {
index: x.Index!,
})) || [];
+ // TODO: Make audio work with transcoding streams in VLC
const ChangeTranscodingAudio = useCallback(
(audioIndex: number) => {
const queryParams = new URLSearchParams({
@@ -111,8 +112,7 @@ const DropdownView: React.FC = ({ showControls }) => {
bitrateValue: bitrateValue,
}).toString();
- // @ts-expect-error
- router.replace(`player/transcoding-player?${queryParams}`);
+ // router.replace(`player/transcoding-player?${queryParams}`);
},
[mediaSource, subtitleIndex, audioIndex]
);