mirror of
https://github.com/streamyfin/streamyfin.git
synced 2025-08-20 18:37:18 +02:00
wip: external subs and cleanup
This commit is contained in:
@@ -2,6 +2,10 @@ import { Text } from "@/components/common/Text";
|
||||
import { ItemContent } from "@/components/ItemContent";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
|
||||
import {
|
||||
getMediaInfoApi,
|
||||
getUserLibraryApi,
|
||||
} from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useAtom } from "jotai";
|
||||
@@ -22,16 +26,16 @@ const Page: React.FC = () => {
|
||||
const { data: item, isError } = useQuery({
|
||||
queryKey: ["item", id],
|
||||
queryFn: async () => {
|
||||
const res = await getUserItemData({
|
||||
api,
|
||||
userId: user?.Id,
|
||||
if (!api) return;
|
||||
const res = await getUserLibraryApi(api).getItem({
|
||||
itemId: id,
|
||||
userId: user?.Id,
|
||||
});
|
||||
|
||||
return res;
|
||||
return res.data;
|
||||
},
|
||||
enabled: !!id && !!api,
|
||||
staleTime: 60 * 1000 * 5, // 5 minutes
|
||||
staleTime: 0,
|
||||
});
|
||||
|
||||
const opacity = useSharedValue(1);
|
||||
|
||||
@@ -52,7 +52,6 @@ export default function page() {
|
||||
|
||||
const togglePlay = useCallback(
|
||||
async (ticks: number) => {
|
||||
console.log("togglePlay");
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
if (isPlaying) {
|
||||
videoRef.current?.pause();
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ProgressUpdatePayload,
|
||||
VlcPlayerViewRef,
|
||||
} from "@/modules/vlc-player/src/VlcPlayer.types";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import {
|
||||
PlaybackType,
|
||||
usePlaySettings,
|
||||
@@ -17,9 +17,11 @@ import {
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
||||
import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
|
||||
import native from "@/utils/profiles/native";
|
||||
import { ticksToSeconds } from "@/utils/time";
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { getPlaystateApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { getMediaInfoApi, getPlaystateApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import * as Haptics from "expo-haptics";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import { useAtomValue } from "jotai";
|
||||
@@ -35,13 +37,13 @@ import { useSharedValue } from "react-native-reanimated";
|
||||
import { SelectedTrackType } from "react-native-video";
|
||||
|
||||
export default function page() {
|
||||
const { playSettings, playUrl, playSessionId } = usePlaySettings();
|
||||
const { playSettings, playUrl, playSessionId, mediaSource } =
|
||||
usePlaySettings();
|
||||
const api = useAtomValue(apiAtom);
|
||||
const [settings] = useSettings();
|
||||
const videoRef = useRef<VlcPlayerViewRef>(null);
|
||||
const poster = usePoster(playSettings, api);
|
||||
const videoSource = useVideoSource(playSettings, api, poster, playUrl);
|
||||
const firstTime = useRef(true);
|
||||
|
||||
const screenDimensions = Dimensions.get("screen");
|
||||
|
||||
@@ -54,12 +56,20 @@ export default function page() {
|
||||
const progress = useSharedValue(0);
|
||||
const isSeeking = useSharedValue(false);
|
||||
const cacheProgress = useSharedValue(0);
|
||||
const user = useAtomValue(userAtom);
|
||||
|
||||
const [playbackState, setPlaybackState] = useState<
|
||||
PlaybackStatePayload["nativeEvent"] | null
|
||||
>(null);
|
||||
|
||||
if (!playSettings || !playUrl || !api || !videoSource || !playSettings.item)
|
||||
if (
|
||||
!playSettings ||
|
||||
!playUrl ||
|
||||
!api ||
|
||||
!videoSource ||
|
||||
!playSettings.item ||
|
||||
!mediaSource
|
||||
)
|
||||
return null;
|
||||
|
||||
const togglePlay = useCallback(
|
||||
@@ -99,7 +109,7 @@ export default function page() {
|
||||
});
|
||||
}
|
||||
},
|
||||
[isPlaying, api, playSettings?.item?.Id, videoRef, settings]
|
||||
[isPlaying, api, playSettings?.item?.Id, videoRef]
|
||||
);
|
||||
|
||||
const play = useCallback(() => {
|
||||
@@ -151,13 +161,6 @@ export default function page() {
|
||||
|
||||
setIsBuffering(isBuffering);
|
||||
|
||||
// console.log("onProgress ~", {
|
||||
// currentTime,
|
||||
// duration,
|
||||
// isBuffering,
|
||||
// isPlaying,
|
||||
// });
|
||||
|
||||
progress.value = currentTime;
|
||||
|
||||
// cacheProgress.value = secondsToTicks(data.playableDuration);
|
||||
@@ -204,46 +207,9 @@ export default function page() {
|
||||
stopPlayback: stop,
|
||||
});
|
||||
|
||||
const selectedSubtitleTrack = useMemo(() => {
|
||||
const a = playSettings?.mediaSource?.MediaStreams?.find(
|
||||
(s) => s.Index === playSettings.subtitleIndex
|
||||
);
|
||||
console.log(a);
|
||||
return a;
|
||||
}, [playSettings]);
|
||||
|
||||
const [hlsSubTracks, setHlsSubTracks] = useState<
|
||||
{
|
||||
index: number;
|
||||
language?: string | undefined;
|
||||
selected?: boolean | undefined;
|
||||
title?: string | undefined;
|
||||
type: any;
|
||||
}[]
|
||||
>([]);
|
||||
|
||||
const selectedTextTrack = useMemo(() => {
|
||||
for (let st of hlsSubTracks) {
|
||||
if (st.title === selectedSubtitleTrack?.DisplayTitle) {
|
||||
return {
|
||||
type: SelectedTrackType.TITLE,
|
||||
value: selectedSubtitleTrack?.DisplayTitle ?? "",
|
||||
};
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}, [hlsSubTracks]);
|
||||
|
||||
const onPlaybackStateChanged = (e: PlaybackStatePayload) => {
|
||||
const { target, state, isBuffering, isPlaying } = e.nativeEvent;
|
||||
|
||||
console.log("onPlaybackStateChanged", {
|
||||
target,
|
||||
state,
|
||||
isBuffering,
|
||||
isPlaying,
|
||||
});
|
||||
|
||||
if (state === "Playing") {
|
||||
setIsPlaying(true);
|
||||
return;
|
||||
@@ -299,12 +265,9 @@ export default function page() {
|
||||
onVideoProgress={onProgress}
|
||||
progressUpdateInterval={1000}
|
||||
onVideoStateChange={onPlaybackStateChanged}
|
||||
onVideoLoadStart={() => {
|
||||
console.log("onVideoLoadStart");
|
||||
}}
|
||||
onVideoLoadStart={() => {}}
|
||||
onVideoLoadEnd={() => {
|
||||
setIsVideoLoaded(true);
|
||||
console.log("onVideoLoadEnd");
|
||||
}}
|
||||
/>
|
||||
</Pressable>
|
||||
@@ -325,6 +288,7 @@ export default function page() {
|
||||
/> */}
|
||||
|
||||
<Controls
|
||||
mediaSource={mediaSource}
|
||||
item={playSettings.item}
|
||||
videoRef={videoRef}
|
||||
togglePlay={togglePlay}
|
||||
|
||||
@@ -103,12 +103,6 @@ TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
|
||||
if (!token || !deviceId || !baseDirectory)
|
||||
return BackgroundFetch.BackgroundFetchResult.NoData;
|
||||
|
||||
console.log({
|
||||
token,
|
||||
url,
|
||||
deviceId,
|
||||
});
|
||||
|
||||
const jobs = await getAllJobsByDeviceId({
|
||||
deviceId,
|
||||
authHeader: token,
|
||||
@@ -120,14 +114,6 @@ TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
|
||||
for (let job of jobs) {
|
||||
if (job.status === "completed") {
|
||||
const downloadUrl = url + "download/" + job.id;
|
||||
console.log({
|
||||
token,
|
||||
deviceId,
|
||||
baseDirectory,
|
||||
url,
|
||||
downloadUrl,
|
||||
});
|
||||
|
||||
const tasks = await checkForExistingDownloads();
|
||||
|
||||
if (tasks.find((task) => task.id === job.id)) {
|
||||
@@ -137,7 +123,7 @@ TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
|
||||
|
||||
download({
|
||||
id: job.id,
|
||||
url: url + "download/" + job.id,
|
||||
url: downloadUrl,
|
||||
destination: `${baseDirectory}${job.item.Id}.mp4`,
|
||||
headers: {
|
||||
Authorization: token,
|
||||
|
||||
@@ -128,9 +128,9 @@ const Login: React.FC = () => {
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
if (error.name === "AbortError") {
|
||||
console.log(`Request to ${protocol}${url} timed out`);
|
||||
console.error(`Request to ${protocol}${url} timed out`);
|
||||
} else {
|
||||
console.log(`Error checking ${protocol}${url}:`, error);
|
||||
console.error(`Error checking ${protocol}${url}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ import { useDownload } from "@/providers/DownloadProvider";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { queueActions, queueAtom } from "@/utils/atoms/queue";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import ios from "@/utils/profiles/ios";
|
||||
import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings";
|
||||
import iosFmp4 from "@/utils/profiles/iosFmp4";
|
||||
import native from "@/utils/profiles/native";
|
||||
import old from "@/utils/profiles/old";
|
||||
import Ionicons from "@expo/vector-icons/Ionicons";
|
||||
@@ -20,18 +21,16 @@ import {
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import { useAtom } from "jotai";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { Alert, TouchableOpacity, View, ViewProps } from "react-native";
|
||||
import { TouchableOpacity, View, ViewProps } from "react-native";
|
||||
import { toast } from "sonner-native";
|
||||
import { AudioTrackSelector } from "./AudioTrackSelector";
|
||||
import { Bitrate, BITRATES, BitrateSelector } from "./BitrateSelector";
|
||||
import { Bitrate, BitrateSelector } from "./BitrateSelector";
|
||||
import { Button } from "./Button";
|
||||
import { Text } from "./common/Text";
|
||||
import { Loader } from "./Loader";
|
||||
import { MediaSourceSelector } from "./MediaSourceSelector";
|
||||
import ProgressCircle from "./ProgressCircle";
|
||||
import { SubtitleTrackSelector } from "./SubtitleTrackSelector";
|
||||
import { toast } from "sonner-native";
|
||||
import iosFmp4 from "@/utils/profiles/iosFmp4";
|
||||
import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings";
|
||||
|
||||
interface DownloadProps extends ViewProps {
|
||||
item: BaseItemDto;
|
||||
@@ -46,7 +45,7 @@ export const DownloadItem: React.FC<DownloadProps> = ({ item, ...props }) => {
|
||||
const { startRemuxing } = useRemuxHlsToMp4(item);
|
||||
|
||||
const [selectedMediaSource, setSelectedMediaSource] = useState<
|
||||
MediaSourceInfo | undefined
|
||||
MediaSourceInfo | undefined | null
|
||||
>(undefined);
|
||||
const [selectedAudioStream, setSelectedAudioStream] = useState<number>(-1);
|
||||
const [selectedSubtitleStream, setSelectedSubtitleStream] =
|
||||
|
||||
@@ -105,7 +105,6 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo(
|
||||
}, [playSettings?.bitrate]);
|
||||
|
||||
const setMaxBitrate = (bitrate: Bitrate | undefined) => {
|
||||
console.log("setMaxBitrate", bitrate);
|
||||
setPlaySettings((prev) => ({
|
||||
...prev,
|
||||
bitrate,
|
||||
|
||||
@@ -205,7 +205,7 @@ export const PlayButton: React.FC<Props> = ({ ...props }) => {
|
||||
});
|
||||
break;
|
||||
case 1:
|
||||
router.push("/play-video");
|
||||
router.push("/vlc-player");
|
||||
break;
|
||||
case cancelButtonIndex:
|
||||
break;
|
||||
|
||||
@@ -85,7 +85,7 @@ const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
|
||||
toast.success("Download canceled");
|
||||
},
|
||||
onError: (e) => {
|
||||
console.log(e);
|
||||
console.error(e);
|
||||
toast.error("Could not cancel download");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ import { formatTimeString, secondsToMs, ticksToMs } from "@/utils/time";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import {
|
||||
BaseItemDto,
|
||||
MediaSourceInfo,
|
||||
type MediaStream,
|
||||
} from "@jellyfin/sdk/lib/generated-client";
|
||||
import { Image } from "expo-image";
|
||||
@@ -26,6 +27,7 @@ import React, {
|
||||
useState,
|
||||
} from "react";
|
||||
import {
|
||||
Alert,
|
||||
Dimensions,
|
||||
Platform,
|
||||
Pressable,
|
||||
@@ -43,6 +45,8 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import * as DropdownMenu from "zeego/dropdown-menu";
|
||||
import { Text } from "../common/Text";
|
||||
import { Loader } from "../Loader";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
|
||||
interface Props {
|
||||
item: BaseItemDto;
|
||||
@@ -60,6 +64,7 @@ interface Props {
|
||||
setShowControls: (shown: boolean) => void;
|
||||
offline?: boolean;
|
||||
isVideoLoaded?: boolean;
|
||||
mediaSource: MediaSourceInfo;
|
||||
}
|
||||
|
||||
export const Controls: React.FC<Props> = ({
|
||||
@@ -75,14 +80,15 @@ export const Controls: React.FC<Props> = ({
|
||||
setShowControls,
|
||||
ignoreSafeAreas,
|
||||
setIgnoreSafeAreas,
|
||||
mediaSource,
|
||||
isVideoLoaded,
|
||||
offline = false,
|
||||
}) => {
|
||||
const [settings] = useSettings();
|
||||
const router = useRouter();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { setPlaySettings } = usePlaySettings();
|
||||
|
||||
const { setPlaySettings, playSettings } = usePlaySettings();
|
||||
const api = useAtomValue(apiAtom);
|
||||
const windowDimensions = Dimensions.get("window");
|
||||
|
||||
const { previousItem, nextItem } = useAdjacentItems({ item });
|
||||
@@ -209,7 +215,6 @@ export const Controls: React.FC<Props> = ({
|
||||
}, [showControls, isPlaying]);
|
||||
|
||||
const handleSkipBackward = useCallback(async () => {
|
||||
console.log("handleSkipBackward");
|
||||
if (!settings?.rewindSkipTime) return;
|
||||
wasPlayingRef.current = isPlaying;
|
||||
try {
|
||||
@@ -232,7 +237,6 @@ export const Controls: React.FC<Props> = ({
|
||||
const curr = progress.value;
|
||||
if (curr !== undefined) {
|
||||
const newTime = curr + secondsToMs(settings.forwardSkipTime);
|
||||
console.log("handleSkipForward", newTime);
|
||||
await videoRef.current?.seekTo(Math.max(0, newTime));
|
||||
if (wasPlayingRef.current === true) videoRef.current?.play();
|
||||
}
|
||||
@@ -261,8 +265,6 @@ export const Controls: React.FC<Props> = ({
|
||||
const subtitles = await videoRef.current.getSubtitleTracks();
|
||||
setAudioTracks(audio);
|
||||
setSubtitleTracks(subtitles);
|
||||
console.log("embedded audio", audio);
|
||||
console.log("embedded sutitles", subtitles);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -292,12 +294,12 @@ export const Controls: React.FC<Props> = ({
|
||||
})) || [];
|
||||
|
||||
const externalSubs =
|
||||
item.MediaStreams?.filter(
|
||||
mediaSource?.MediaStreams?.filter(
|
||||
(stream) => stream.Type === "Subtitle" && stream.IsExternal
|
||||
).map((s) => ({
|
||||
name: s.DisplayTitle!,
|
||||
index: s.Index!,
|
||||
isExternal: s.DeliveryMethod === "External",
|
||||
isExternal: true,
|
||||
deliveryUrl: s.DeliveryUrl,
|
||||
})) || [];
|
||||
|
||||
@@ -309,13 +311,12 @@ export const Controls: React.FC<Props> = ({
|
||||
(sub) => !embeddedSubNames.has(sub.name)
|
||||
);
|
||||
|
||||
console.log([...embeddedSubs, ...uniqueExternalSubs]);
|
||||
// Combine embedded and unique external subs
|
||||
return [...embeddedSubs, ...uniqueExternalSubs] as (
|
||||
| EmbeddedSubtitle
|
||||
| ExternalSubtitle
|
||||
)[];
|
||||
}, [item, isVideoLoaded, subtitleTracks]);
|
||||
}, [item, isVideoLoaded, subtitleTracks, mediaSource]);
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -386,17 +387,13 @@ export const Controls: React.FC<Props> = ({
|
||||
value="off"
|
||||
onValueChange={() => {
|
||||
if (sub.isExternal) {
|
||||
videoRef.current?.setSubtitleURL(sub.deliveryUrl);
|
||||
console.log(
|
||||
"Setting external subtitle:",
|
||||
sub.deliveryUrl
|
||||
videoRef.current?.setSubtitleURL(
|
||||
api?.basePath + sub.deliveryUrl
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Settings embedded subtitle", sub.name);
|
||||
videoRef.current?.setSubtitleTrack(sub.index);
|
||||
console.log(sub);
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.ItemIndicator />
|
||||
|
||||
@@ -28,8 +28,6 @@ export const useAdjacentItems = ({ item }: AdjacentEpisodesProps) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log("Getting previous item for " + indexNumber);
|
||||
|
||||
const newIndexNumber = indexNumber - 2;
|
||||
|
||||
const res = await getItemsApi(api).getItems({
|
||||
|
||||
@@ -30,7 +30,6 @@ export const useCreditSkipper = (
|
||||
queryKey: ["creditTimestamps", itemId],
|
||||
queryFn: async () => {
|
||||
if (!itemId) {
|
||||
console.log("No item id");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -61,7 +60,6 @@ export const useCreditSkipper = (
|
||||
}, [creditTimestamps, currentTime]);
|
||||
|
||||
const skipCredit = useCallback(() => {
|
||||
console.log("skipCredits");
|
||||
if (!creditTimestamps || !videoRef.current) return;
|
||||
try {
|
||||
videoRef.current.seek(creditTimestamps.Credits.End);
|
||||
|
||||
@@ -24,9 +24,6 @@ export const useFileOpener = () => {
|
||||
|
||||
try {
|
||||
const files = await FileSystem.readDirectoryAsync(directory);
|
||||
for (let f of files) {
|
||||
console.log(f);
|
||||
}
|
||||
const path = item.Id!;
|
||||
const matchingFile = files.find((file) => file.startsWith(path));
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ const useImageStorage = () => {
|
||||
try {
|
||||
// Save the base64 string to AsyncStorage
|
||||
storage.set(key, base64);
|
||||
console.log("Image saved successfully");
|
||||
} catch (error) {
|
||||
console.error("Error saving image:", error);
|
||||
throw error;
|
||||
|
||||
@@ -26,7 +26,6 @@ export const useIntroSkipper = (
|
||||
queryKey: ["introTimestamps", itemId],
|
||||
queryFn: async () => {
|
||||
if (!itemId) {
|
||||
console.log("No item id");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,8 +26,7 @@ export const useRemuxHlsToMp4 = (item: BaseItemDto) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { saveDownloadedItemInfo, setProcesses } = useDownload();
|
||||
const router = useRouter();
|
||||
const { loadImage, saveImage, image2Base64, saveBase64Image } =
|
||||
useImageStorage();
|
||||
const { saveImage } = useImageStorage();
|
||||
|
||||
if (!item.Id || !item.Name) {
|
||||
writeToLog("ERROR", "useRemuxHlsToMp4 ~ missing arguments");
|
||||
|
||||
@@ -174,7 +174,7 @@ function useDownloadProvider() {
|
||||
url: settings?.optimizedVersionsServerUrl,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
[settings?.optimizedVersionsServerUrl, authHeader]
|
||||
@@ -184,7 +184,6 @@ function useDownloadProvider() {
|
||||
async (process: JobStatus) => {
|
||||
if (!process?.item.Id || !authHeader) throw new Error("No item id");
|
||||
|
||||
console.log("[0] Setting process to downloading");
|
||||
setProcesses((prev) =>
|
||||
prev.map((p) =>
|
||||
p.id === process.id
|
||||
@@ -239,7 +238,6 @@ function useDownloadProvider() {
|
||||
})
|
||||
.progress((data) => {
|
||||
const percent = (data.bytesDownloaded / data.bytesTotal) * 100;
|
||||
console.log("Download progress:", percent);
|
||||
setProcesses((prev) =>
|
||||
prev.map((p) =>
|
||||
p.id === process.id
|
||||
@@ -467,7 +465,6 @@ function useDownloadProvider() {
|
||||
if (itemNameWithoutExtension === id) {
|
||||
const filePath = `${directory}${item}`;
|
||||
await FileSystem.deleteAsync(filePath, { idempotent: true });
|
||||
console.log(`Successfully deleted file: ${item}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -480,10 +477,6 @@ function useDownloadProvider() {
|
||||
}
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ["downloadedItems"] });
|
||||
|
||||
console.log(
|
||||
`Successfully deleted file and AsyncStorage entry for ID ${id}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to delete file and AsyncStorage entry for ID ${id}:`,
|
||||
|
||||
@@ -7,6 +7,7 @@ import old from "@/utils/profiles/old";
|
||||
import {
|
||||
BaseItemDto,
|
||||
MediaSourceInfo,
|
||||
PlaybackInfoResponse,
|
||||
} from "@jellyfin/sdk/lib/generated-client";
|
||||
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useAtomValue } from "jotai";
|
||||
@@ -30,6 +31,7 @@ export type PlaybackType = {
|
||||
|
||||
type PlaySettingsContextType = {
|
||||
playSettings: PlaybackType | null;
|
||||
mediaSource: MediaSourceInfo | null;
|
||||
setPlaySettings: (
|
||||
dataOrUpdater:
|
||||
| PlaybackType
|
||||
@@ -51,6 +53,7 @@ export const PlaySettingsProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [playSettings, _setPlaySettings] = useState<PlaybackType | null>(null);
|
||||
const [mediaSource, setMediaSource] = useState<MediaSourceInfo | null>(null);
|
||||
const [playUrl, setPlayUrl] = useState<string | null>(null);
|
||||
const [playSessionId, setPlaySessionId] = useState<string | null>(null);
|
||||
|
||||
@@ -109,11 +112,10 @@ export const PlaySettingsProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
forceDirectPlay: settings.forceDirectPlay,
|
||||
});
|
||||
|
||||
console.log("getStreamUrl ~ ", data?.url);
|
||||
|
||||
_setPlaySettings(newSettings);
|
||||
setPlayUrl(data?.url!);
|
||||
setPlaySessionId(data?.sessionId!);
|
||||
setMediaSource(data?.mediaSource!);
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
@@ -158,6 +160,7 @@ export const PlaySettingsProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
setMusicPlaySettings,
|
||||
setOfflineSettings,
|
||||
playSessionId,
|
||||
mediaSource,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
|
||||
export const getPlaybackInfo = async (
|
||||
api?: Api | null | undefined,
|
||||
itemId?: string | null | undefined,
|
||||
userId?: string | null | undefined,
|
||||
) => {
|
||||
if (!api || !itemId || !userId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const a = await getMediaInfoApi(api).getPlaybackInfo({
|
||||
itemId,
|
||||
userId,
|
||||
});
|
||||
|
||||
return a.data;
|
||||
};
|
||||
@@ -37,6 +37,7 @@ export const getStreamUrl = async ({
|
||||
}): Promise<{
|
||||
url: string | null;
|
||||
sessionId: string | null;
|
||||
mediaSource: MediaSourceInfo | undefined;
|
||||
} | null> => {
|
||||
if (!api || !userId || !item?.Id) {
|
||||
return null;
|
||||
@@ -70,7 +71,11 @@ export const getStreamUrl = async ({
|
||||
sessionId = res0.data.PlaySessionId || null;
|
||||
|
||||
if (transcodeUrl) {
|
||||
return { url: `${api.basePath}${transcodeUrl}`, sessionId };
|
||||
return {
|
||||
url: `${api.basePath}${transcodeUrl}`,
|
||||
sessionId,
|
||||
mediaSource: res0.data.MediaSources?.[0],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,13 +113,12 @@ export const getStreamUrl = async ({
|
||||
(source: MediaSourceInfo) => source.Id === mediaSourceId
|
||||
);
|
||||
|
||||
console.log("getStreamUrl ~ ", item.MediaType);
|
||||
|
||||
if (item.MediaType === "Video") {
|
||||
if (mediaSource?.SupportsDirectPlay || forceDirectPlay === true) {
|
||||
return {
|
||||
url: `${api.basePath}/Videos/${itemId}/stream.mp4?playSessionId=${sessionData?.PlaySessionId}&mediaSourceId=${mediaSource?.Id}&static=true&subtitleStreamIndex=${subtitleStreamIndex}&audioStreamIndex=${audioStreamIndex}&deviceId=${api.deviceInfo.id}&api_key=${api.accessToken}`,
|
||||
sessionId: sessionId,
|
||||
mediaSource,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -122,15 +126,18 @@ export const getStreamUrl = async ({
|
||||
return {
|
||||
url: `${api.basePath}${mediaSource.TranscodingUrl}`,
|
||||
sessionId: sessionId,
|
||||
mediaSource,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (item.MediaType === "Audio") {
|
||||
console.log("getStreamUrl ~ Audio");
|
||||
|
||||
if (mediaSource?.TranscodingUrl) {
|
||||
return { url: `${api.basePath}${mediaSource.TranscodingUrl}`, sessionId };
|
||||
return {
|
||||
url: `${api.basePath}${mediaSource.TranscodingUrl}`,
|
||||
sessionId,
|
||||
mediaSource,
|
||||
};
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams({
|
||||
@@ -153,6 +160,7 @@ export const getStreamUrl = async ({
|
||||
api.basePath
|
||||
}/Audio/${itemId}/universal?${searchParams.toString()}`,
|
||||
sessionId,
|
||||
mediaSource,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -259,11 +259,11 @@ export default {
|
||||
],
|
||||
SubtitleProfiles: [
|
||||
{
|
||||
Format: "pgssub",
|
||||
Method: "embed",
|
||||
Format: "srt",
|
||||
Method: "external",
|
||||
},
|
||||
{
|
||||
Format: "subrip",
|
||||
Format: "pgssub",
|
||||
Method: "embed",
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user