mirror of
https://github.com/streamyfin/streamyfin.git
synced 2025-08-20 18:37:18 +02:00
wip
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
import { TouchableOpacity, View, ViewProps } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useRouter } from "expo-router";
|
||||
import { checkForExistingDownloads } from "@kesha-antonov/react-native-background-downloader";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { ProcessItem, useDownload } from "@/providers/DownloadProvider";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import { formatTimeString } from "@/utils/time";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { checkForExistingDownloads } from "@kesha-antonov/react-native-background-downloader";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
import { toast } from "sonner-native";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import { useRouter } from "expo-router";
|
||||
import { FFmpegKit } from "ffmpeg-kit-react-native";
|
||||
import { formatTimeString } from "@/utils/time";
|
||||
import { useAtom } from "jotai";
|
||||
import { useCallback } from "react";
|
||||
import { TouchableOpacity, View, ViewProps } from "react-native";
|
||||
import { toast } from "sonner-native";
|
||||
|
||||
interface Props extends ViewProps {}
|
||||
|
||||
@@ -18,6 +20,7 @@ export const ActiveDownloads: React.FC<Props> = ({ ...props }) => {
|
||||
const router = useRouter();
|
||||
const { removeProcess, processes } = useDownload();
|
||||
const [settings] = useSettings();
|
||||
const [api] = useAtom(apiAtom);
|
||||
|
||||
const cancelJobMutation = useMutation({
|
||||
mutationFn: async (id: string) => {
|
||||
@@ -29,7 +32,7 @@ export const ActiveDownloads: React.FC<Props> = ({ ...props }) => {
|
||||
settings?.optimizedVersionsServerUrl + "cancel-job/" + id,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${settings?.optimizedVersionsAuthHeader}`,
|
||||
Authorization: api?.accessToken,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import axios from "axios";
|
||||
import * as FileSystem from "expo-file-system";
|
||||
import { useRouter } from "expo-router";
|
||||
import { useAtom } from "jotai";
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
@@ -24,6 +25,7 @@ import React, {
|
||||
useState,
|
||||
} from "react";
|
||||
import { toast } from "sonner-native";
|
||||
import { apiAtom } from "./JellyfinProvider";
|
||||
|
||||
export type ProcessItem = {
|
||||
id: string;
|
||||
@@ -51,9 +53,11 @@ function useDownloadProvider() {
|
||||
const [processes, setProcesses] = useState<ProcessItem[]>([]);
|
||||
const [settings] = useSettings();
|
||||
const router = useRouter();
|
||||
const [api] = useAtom(apiAtom);
|
||||
|
||||
const authHeader = useMemo(() => {
|
||||
return `Bearer ${settings?.optimizedVersionsAuthHeader}`;
|
||||
}, [settings]);
|
||||
return api?.accessToken;
|
||||
}, [api]);
|
||||
|
||||
const { data: downloadedFiles, refetch } = useQuery({
|
||||
queryKey: ["downloadedItems"],
|
||||
@@ -113,7 +117,7 @@ function useDownloadProvider() {
|
||||
|
||||
const startDownload = useCallback(
|
||||
(process: ProcessItem) => {
|
||||
if (!process?.item.Id) throw new Error("No item id");
|
||||
if (!process?.item.Id || !authHeader) throw new Error("No item id");
|
||||
|
||||
download({
|
||||
id: process.id,
|
||||
@@ -149,12 +153,12 @@ function useDownloadProvider() {
|
||||
toast.error(`Download failed for ${process.item.Name}: ${error}`);
|
||||
});
|
||||
},
|
||||
[queryClient, settings?.optimizedVersionsServerUrl]
|
||||
[queryClient, settings?.optimizedVersionsServerUrl, authHeader]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const checkJobStatusPeriodically = async () => {
|
||||
if (!settings?.optimizedVersionsServerUrl) return;
|
||||
if (!settings?.optimizedVersionsServerUrl || !authHeader) return;
|
||||
|
||||
const updatedProcesses = await Promise.all(
|
||||
processes.map(async (process) => {
|
||||
@@ -283,10 +287,34 @@ function useDownloadProvider() {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error in startBackgroundDownload:", error);
|
||||
toast.error(`Failed to start download for ${item.Name}`);
|
||||
if (axios.isAxiosError(error)) {
|
||||
console.error("Axios error details:", {
|
||||
message: error.message,
|
||||
response: error.response?.data,
|
||||
status: error.response?.status,
|
||||
headers: error.response?.headers,
|
||||
});
|
||||
toast.error(
|
||||
`Failed to start download for ${item.Name}: ${error.message}`
|
||||
);
|
||||
if (error.response) {
|
||||
toast.error(
|
||||
`Server responded with status ${error.response.status}`
|
||||
);
|
||||
} else if (error.request) {
|
||||
toast.error("No response received from server");
|
||||
} else {
|
||||
toast.error("Error setting up the request");
|
||||
}
|
||||
} else {
|
||||
console.error("Non-Axios error:", error);
|
||||
toast.error(
|
||||
`Failed to start download for ${item.Name}: Unexpected error`
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
[settings?.optimizedVersionsServerUrl]
|
||||
[settings?.optimizedVersionsServerUrl, authHeader]
|
||||
);
|
||||
|
||||
const deleteAllFiles = async (): Promise<void> => {
|
||||
@@ -439,7 +467,7 @@ export function useDownload() {
|
||||
const checkJobStatus = async (
|
||||
id: string,
|
||||
baseUrl: string,
|
||||
authHeader?: string | null
|
||||
authHeader: string
|
||||
): Promise<{
|
||||
progress: number;
|
||||
status: "queued" | "running" | "completed" | "failed" | "cancelled";
|
||||
|
||||
@@ -140,6 +140,7 @@ export const PlaybackProvider: React.FC<{ children: ReactNode }> = ({
|
||||
api,
|
||||
itemId: state.item.Id,
|
||||
sessionId: res.data.PlaySessionId,
|
||||
deviceProfile: settings?.deviceProfile,
|
||||
});
|
||||
|
||||
setSession(res.data);
|
||||
|
||||
@@ -54,7 +54,7 @@ export type DefaultLanguageOption = {
|
||||
label: string;
|
||||
};
|
||||
|
||||
type Settings = {
|
||||
export type Settings = {
|
||||
autoRotate?: boolean;
|
||||
forceLandscapeInVideoPlayer?: boolean;
|
||||
usePopularPlugin?: boolean;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { getAuthHeaders } from "../jellyfin";
|
||||
import { postCapabilities } from "../session/capabilities";
|
||||
import { Settings } from "@/utils/atoms/settings";
|
||||
|
||||
interface ReportPlaybackProgressParams {
|
||||
api?: Api | null;
|
||||
@@ -8,6 +9,7 @@ interface ReportPlaybackProgressParams {
|
||||
itemId?: string | null;
|
||||
positionTicks?: number | null;
|
||||
IsPaused?: boolean;
|
||||
deviceProfile?: Settings["deviceProfile"];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,6 +24,7 @@ export const reportPlaybackProgress = async ({
|
||||
itemId,
|
||||
positionTicks,
|
||||
IsPaused = false,
|
||||
deviceProfile,
|
||||
}: ReportPlaybackProgressParams): Promise<void> => {
|
||||
if (!api || !sessionId || !itemId || !positionTicks) {
|
||||
return;
|
||||
@@ -34,6 +37,7 @@ export const reportPlaybackProgress = async ({
|
||||
api,
|
||||
itemId,
|
||||
sessionId,
|
||||
deviceProfile,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to post capabilities.", error);
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { Settings } from "@/utils/atoms/settings";
|
||||
import ios from "@/utils/profiles/ios";
|
||||
import native from "@/utils/profiles/native";
|
||||
import old from "@/utils/profiles/old";
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import {
|
||||
SessionApi,
|
||||
SessionApiPostCapabilitiesRequest,
|
||||
} from "@jellyfin/sdk/lib/generated-client/api/session-api";
|
||||
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { AxiosError, AxiosResponse } from "axios";
|
||||
import { useMemo } from "react";
|
||||
import { getAuthHeaders } from "../jellyfin";
|
||||
|
||||
interface PostCapabilitiesParams {
|
||||
api: Api | null | undefined;
|
||||
itemId: string | null | undefined;
|
||||
sessionId: string | null | undefined;
|
||||
deviceProfile: Settings["deviceProfile"];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,16 +24,26 @@ export const postCapabilities = async ({
|
||||
api,
|
||||
itemId,
|
||||
sessionId,
|
||||
deviceProfile,
|
||||
}: PostCapabilitiesParams): Promise<AxiosResponse> => {
|
||||
if (!api || !itemId || !sessionId) {
|
||||
throw new Error("Missing parameters for marking item as not played");
|
||||
}
|
||||
|
||||
let profile: any = ios;
|
||||
|
||||
if (deviceProfile === "Native") {
|
||||
profile = native;
|
||||
}
|
||||
if (deviceProfile === "Old") {
|
||||
profile = old;
|
||||
}
|
||||
|
||||
try {
|
||||
const d = api.axiosInstance.post(
|
||||
api.basePath + "/Sessions/Capabilities/Full",
|
||||
{
|
||||
playableMediaTypes: ["Audio", "Video", "Audio"],
|
||||
playableMediaTypes: ["Audio", "Video"],
|
||||
supportedCommands: [
|
||||
"PlayState",
|
||||
"Play",
|
||||
@@ -45,6 +56,7 @@ export const postCapabilities = async ({
|
||||
],
|
||||
supportsMediaControl: true,
|
||||
id: sessionId,
|
||||
DeviceProfile: profile,
|
||||
},
|
||||
{
|
||||
headers: getAuthHeaders(api),
|
||||
|
||||
Reference in New Issue
Block a user