mirror of
https://github.com/streamyfin/streamyfin.git
synced 2025-08-20 18:37:18 +02:00
fix
This commit is contained in:
@@ -85,6 +85,16 @@ export default function index() {
|
||||
staleTime: 60,
|
||||
});
|
||||
|
||||
if (isError)
|
||||
return (
|
||||
<View className="flex flex-col items-center justify-center h-full -mt-12">
|
||||
<Text className="text-3xl font-bold mb-2">Oops!</Text>
|
||||
<Text className="text-center opacity-70">
|
||||
Something went wrong.{"\n"}Please log out and in again.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<View className="justify-center items-center h-full">
|
||||
@@ -92,8 +102,6 @@ export default function index() {
|
||||
</View>
|
||||
);
|
||||
|
||||
if (isError) return <Text>Error loading items</Text>;
|
||||
|
||||
if (!data || data.length === 0) return <Text>No data...</Text>;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Button } from "@/components/Button";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { runningProcesses } from "@/components/DownloadItem";
|
||||
import { ListItem } from "@/components/ListItem";
|
||||
import ProgressCircle from "@/components/ProgressCircle";
|
||||
import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { runningProcesses } from "@/utils/atoms/downloads";
|
||||
import { readFromLog } from "@/utils/log";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
@@ -38,14 +38,11 @@ const deleteFile = async (id: string | null | undefined) => {
|
||||
(err) => console.error(err)
|
||||
);
|
||||
|
||||
AsyncStorage.setItem(
|
||||
"downloaded_files",
|
||||
JSON.stringify([
|
||||
JSON.parse(
|
||||
(await AsyncStorage.getItem("downloaded_files")) || "[]"
|
||||
).filter((f: string) => f !== id),
|
||||
])
|
||||
const currentFiles = JSON.parse(
|
||||
(await AsyncStorage.getItem("downloaded_files")) ?? "[]"
|
||||
);
|
||||
const updatedFiles = currentFiles.filter((f: string) => f !== id);
|
||||
await AsyncStorage.setItem("downloaded_files", JSON.stringify(updatedFiles));
|
||||
};
|
||||
|
||||
const listDownloadedFiles = async () => {
|
||||
@@ -81,6 +78,11 @@ export default function settings() {
|
||||
(await AsyncStorage.getItem("downloaded_files")) || "[]"
|
||||
) as BaseItemDto[];
|
||||
|
||||
console.log(
|
||||
"Files",
|
||||
data.map((i) => i.Name)
|
||||
);
|
||||
|
||||
setFiles(data);
|
||||
})();
|
||||
}, [key]);
|
||||
@@ -160,8 +162,8 @@ export default function settings() {
|
||||
<Button
|
||||
className="mb-2"
|
||||
color="red"
|
||||
onPress={() => {
|
||||
deleteAllFiles();
|
||||
onPress={async () => {
|
||||
await deleteAllFiles();
|
||||
setKey((prevKey) => prevKey + 1);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -103,7 +103,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({ itemId }) => {
|
||||
const { data: playbackURL } = useQuery({
|
||||
queryKey: ["playbackUrl", itemId, maxBitrate, forceTranscoding],
|
||||
queryFn: async () => {
|
||||
if (!api || !user?.Id) return null;
|
||||
if (!api || !user?.Id || !sessionData) return null;
|
||||
|
||||
const url = await getStreamUrl({
|
||||
api,
|
||||
@@ -111,6 +111,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({ itemId }) => {
|
||||
item,
|
||||
startTimeTicks: item?.UserData?.PlaybackPositionTicks || 0,
|
||||
maxStreamingBitrate: maxBitrate,
|
||||
sessionData,
|
||||
forceTranscoding: forceTranscoding,
|
||||
});
|
||||
|
||||
@@ -118,7 +119,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({ itemId }) => {
|
||||
|
||||
return url;
|
||||
},
|
||||
enabled: !!itemId && !!api && !!user?.Id && !!item,
|
||||
enabled: !!itemId && !!api && !!user?.Id && !!item && !!sessionData,
|
||||
staleTime: 0,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import {
|
||||
BaseItemDto,
|
||||
PlaybackInfoResponse,
|
||||
} from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import {
|
||||
getMediaInfoApi,
|
||||
getUserLibraryApi,
|
||||
@@ -59,14 +62,17 @@ export const useDownloadMedia = (api: Api | null) => {
|
||||
console.log("File downloaded to:", uri);
|
||||
|
||||
const currentFiles: BaseItemDto[] = JSON.parse(
|
||||
(await AsyncStorage.getItem("downloaded_files")) || "[]"
|
||||
(await AsyncStorage.getItem("downloaded_files")) ?? "[]"
|
||||
);
|
||||
|
||||
const otherItems = currentFiles.filter((i) => i.Id !== itemId);
|
||||
const updatedFiles = [
|
||||
...currentFiles.filter((file) => file.Id !== itemId),
|
||||
item,
|
||||
];
|
||||
|
||||
await AsyncStorage.setItem(
|
||||
"downloaded_files",
|
||||
JSON.stringify([...otherItems, item])
|
||||
JSON.stringify(updatedFiles)
|
||||
);
|
||||
|
||||
setIsDownloading(false);
|
||||
@@ -429,6 +435,7 @@ export const getStreamUrl = async ({
|
||||
startTimeTicks = 0,
|
||||
maxStreamingBitrate = 140000000,
|
||||
forceTranscoding = false,
|
||||
sessionData,
|
||||
}: {
|
||||
api: Api | null | undefined;
|
||||
item: BaseItemDto | null | undefined;
|
||||
@@ -436,6 +443,7 @@ export const getStreamUrl = async ({
|
||||
startTimeTicks: number;
|
||||
maxStreamingBitrate?: number;
|
||||
forceTranscoding?: boolean;
|
||||
sessionData: PlaybackInfoResponse;
|
||||
}) => {
|
||||
if (!api || !userId || !item?.Id) {
|
||||
return null;
|
||||
@@ -469,38 +477,72 @@ export const getStreamUrl = async ({
|
||||
);
|
||||
|
||||
const data = response.data;
|
||||
const mediaSource = item.MediaSources?.[0];
|
||||
const sessionId = sessionData.PlaySessionId;
|
||||
|
||||
if (item.MediaSources?.[0].SupportsDirectPlay) {
|
||||
console.log("Direct play supported");
|
||||
}
|
||||
if (!mediaSource) throw new Error("no media source");
|
||||
if (!sessionId) throw new Error("no sessionId");
|
||||
|
||||
if (
|
||||
item.MediaSources?.[0].SupportsTranscoding &&
|
||||
data.MediaSources?.[0].TranscodingUrl
|
||||
) {
|
||||
console.log("Supports transcoding");
|
||||
}
|
||||
const streamParams = new URLSearchParams({
|
||||
Static: "true",
|
||||
api_key: api.accessToken,
|
||||
playSessionId: sessionData.PlaySessionId || "",
|
||||
videoCodec: "hevc,h264",
|
||||
audioCodec: "aac,mp3,ac3,eac3,flac,alac",
|
||||
maxAudioChannels: "6",
|
||||
mediaSourceId: itemId,
|
||||
Tag: mediaSource.ETag || "",
|
||||
VideoBitrate: "324036",
|
||||
TranscodingMaxAudioChannels: "2",
|
||||
RequireAvc: "false",
|
||||
SegmentContainer: "mp4",
|
||||
MinSegments: "2",
|
||||
BreakOnNonKeyFrames: "True",
|
||||
"h264-level": "40",
|
||||
"h264-videobitdepth": "8",
|
||||
"h264-profile": "high",
|
||||
"h264-audiochannels": "2",
|
||||
"aac-profile": "lc",
|
||||
"h264-rangetype": "SDR",
|
||||
"h264-deinterlace": "true",
|
||||
TranscodeReasons: "ContainerBitrateExceedsLimit",
|
||||
});
|
||||
|
||||
if (
|
||||
item.MediaSources?.[0].SupportsTranscoding &&
|
||||
!data.MediaSources?.[0].TranscodingUrl
|
||||
) {
|
||||
console.log("Supports transcoding, but no URL found");
|
||||
}
|
||||
url = `${
|
||||
api.basePath
|
||||
}/Videos/${itemId}/main.m3u8?${streamParams.toString()}`;
|
||||
|
||||
if (data.MediaSources?.[0].TranscodingUrl) {
|
||||
url = api.basePath + data.MediaSources?.[0].TranscodingUrl;
|
||||
} else {
|
||||
url = buildStreamUrl({
|
||||
apiKey: api.accessToken || "",
|
||||
sessionId: "",
|
||||
itemId: itemId,
|
||||
serverUrl: api.basePath || "",
|
||||
deviceId: "unique-device-id",
|
||||
mediaSourceId: data.MediaSources?.[0].Id || "",
|
||||
tag: data.MediaSources?.[0]?.ETag || "",
|
||||
}).toString();
|
||||
}
|
||||
// if (item.MediaSources?.[0].SupportsDirectPlay) {
|
||||
// console.log("Direct play supported");
|
||||
// }
|
||||
|
||||
// if (
|
||||
// item.MediaSources?.[0].SupportsTranscoding &&
|
||||
// data.MediaSources?.[0].TranscodingUrl
|
||||
// ) {
|
||||
// console.log("Supports transcoding");
|
||||
// }
|
||||
|
||||
// if (
|
||||
// item.MediaSources?.[0].SupportsTranscoding &&
|
||||
// !data.MediaSources?.[0].TranscodingUrl
|
||||
// ) {
|
||||
// console.log("Supports transcoding, but no URL found");
|
||||
// }
|
||||
|
||||
// if (data.MediaSources?.[0].TranscodingUrl) {
|
||||
// url = api.basePath + data.MediaSources?.[0].TranscodingUrl;
|
||||
// } else {
|
||||
// url = buildStreamUrl({
|
||||
// apiKey: api.accessToken || "",
|
||||
// sessionId: "",
|
||||
// itemId: itemId,
|
||||
// serverUrl: api.basePath || "",
|
||||
// deviceId: "unique-device-id",
|
||||
// mediaSourceId: data.MediaSources?.[0].Id || "",
|
||||
// tag: data.MediaSources?.[0]?.ETag || "",
|
||||
// }).toString();
|
||||
// }
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
76
utils/video/createVideoUrl.ts
Normal file
76
utils/video/createVideoUrl.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { MediaSourceInfo } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
|
||||
export function createVideoUrl(mediaSource: MediaSourceInfo): string {
|
||||
const baseUrl = `/videos/${mediaSource.Id}/main.m3u8`;
|
||||
const urlParams = new URLSearchParams();
|
||||
|
||||
// Extract query parameters from TranscodingUrl
|
||||
const transcodingUrlParts = mediaSource.TranscodingUrl?.split("?") ?? [];
|
||||
if (transcodingUrlParts.length > 1) {
|
||||
const queryParams = new URLSearchParams(transcodingUrlParts[1]);
|
||||
queryParams.forEach((value, key) => {
|
||||
urlParams.append(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
// Add or update specific parameters based on the mediaSource object
|
||||
if (mediaSource.DefaultAudioStreamIndex !== undefined) {
|
||||
urlParams.set(
|
||||
"AudioStreamIndex",
|
||||
mediaSource.DefaultAudioStreamIndex?.toString() || ""
|
||||
);
|
||||
}
|
||||
|
||||
if (mediaSource.DefaultSubtitleStreamIndex !== undefined) {
|
||||
urlParams.set(
|
||||
"SubtitleStreamIndex",
|
||||
mediaSource.DefaultSubtitleStreamIndex?.toString() || ""
|
||||
);
|
||||
}
|
||||
|
||||
// Add information about available streams
|
||||
if (mediaSource.MediaStreams) {
|
||||
const videoStreams = mediaSource.MediaStreams.filter(
|
||||
(stream) => stream.Type === "Video"
|
||||
);
|
||||
const audioStreams = mediaSource.MediaStreams.filter(
|
||||
(stream) => stream.Type === "Audio"
|
||||
);
|
||||
const subtitleStreams = mediaSource.MediaStreams.filter(
|
||||
(stream) => stream.Type === "Subtitle"
|
||||
);
|
||||
|
||||
if (videoStreams.length > 0) {
|
||||
urlParams.set(
|
||||
"VideoStreamIndex",
|
||||
videoStreams[0].Index?.toString() || ""
|
||||
);
|
||||
}
|
||||
|
||||
if (audioStreams.length > 0) {
|
||||
const defaultAudioStream =
|
||||
audioStreams.find((stream) => stream.IsDefault) || audioStreams[0];
|
||||
urlParams.set(
|
||||
"AudioStreamIndex",
|
||||
defaultAudioStream.Index?.toString() || ""
|
||||
);
|
||||
urlParams.set("AudioCodec", defaultAudioStream.Codec || "");
|
||||
}
|
||||
|
||||
if (subtitleStreams.length > 0) {
|
||||
const defaultSubtitleStream = subtitleStreams.find(
|
||||
(stream) => stream.IsDefault
|
||||
);
|
||||
if (defaultSubtitleStream?.Index) {
|
||||
urlParams.set(
|
||||
"SubtitleStreamIndex",
|
||||
defaultSubtitleStream.Index.toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("createVideoUrl ~", `${baseUrl}?${urlParams.toString()}`);
|
||||
|
||||
return `${baseUrl}?${urlParams.toString()}`;
|
||||
}
|
||||
Reference in New Issue
Block a user