From 12cb6d4963b8115078d1fa014be04c38c1e2915c Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Sun, 29 Sep 2024 19:43:17 +0200 Subject: [PATCH] wip --- components/downloads/ActiveDownloads.tsx | 21 +++++---- providers/DownloadProvider.tsx | 44 +++++++++++++++---- providers/PlaybackProvider.tsx | 1 + utils/atoms/settings.ts | 2 +- .../playstate/reportPlaybackProgress.ts | 4 ++ utils/jellyfin/session/capabilities.ts | 24 +++++++--- 6 files changed, 72 insertions(+), 24 deletions(-) diff --git a/components/downloads/ActiveDownloads.tsx b/components/downloads/ActiveDownloads.tsx index e28a122b..1dbd46f7 100644 --- a/components/downloads/ActiveDownloads.tsx +++ b/components/downloads/ActiveDownloads.tsx @@ -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 }) => { 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 }) => { settings?.optimizedVersionsServerUrl + "cancel-job/" + id, { headers: { - Authorization: `Bearer ${settings?.optimizedVersionsAuthHeader}`, + Authorization: api?.accessToken, }, } ); diff --git a/providers/DownloadProvider.tsx b/providers/DownloadProvider.tsx index 755c9faa..67ca0aa8 100644 --- a/providers/DownloadProvider.tsx +++ b/providers/DownloadProvider.tsx @@ -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([]); 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 => { @@ -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"; diff --git a/providers/PlaybackProvider.tsx b/providers/PlaybackProvider.tsx index 22013463..00b4d284 100644 --- a/providers/PlaybackProvider.tsx +++ b/providers/PlaybackProvider.tsx @@ -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); diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts index d6aabd59..b5e687a1 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -54,7 +54,7 @@ export type DefaultLanguageOption = { label: string; }; -type Settings = { +export type Settings = { autoRotate?: boolean; forceLandscapeInVideoPlayer?: boolean; usePopularPlugin?: boolean; diff --git a/utils/jellyfin/playstate/reportPlaybackProgress.ts b/utils/jellyfin/playstate/reportPlaybackProgress.ts index 45e71ec6..d015482a 100644 --- a/utils/jellyfin/playstate/reportPlaybackProgress.ts +++ b/utils/jellyfin/playstate/reportPlaybackProgress.ts @@ -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 => { 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); diff --git a/utils/jellyfin/session/capabilities.ts b/utils/jellyfin/session/capabilities.ts index ccb068c2..b7a3e795 100644 --- a/utils/jellyfin/session/capabilities.ts +++ b/utils/jellyfin/session/capabilities.ts @@ -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 => { 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),