import type { BaseItemDto, MediaSourceInfo, } from "@jellyfin/sdk/lib/generated-client"; import { useAtomValue } from "jotai"; import type React from "react"; import { createContext, useCallback, useContext, useState } from "react"; import type { Bitrate } from "@/components/BitrateSelector"; import { settingsAtom } from "@/utils/atoms/settings"; import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl"; import { generateDeviceProfile } from "@/utils/profiles/native"; import { apiAtom, userAtom } from "./JellyfinProvider"; export type PlaybackType = { item?: BaseItemDto | null; mediaSource?: MediaSourceInfo | null; subtitleIndex?: number | null; audioIndex?: number | null; bitrate?: Bitrate | null; }; type PlaySettingsContextType = { playSettings: PlaybackType | null; mediaSource: MediaSourceInfo | null; setPlaySettings: ( dataOrUpdater: | PlaybackType | null | ((prev: PlaybackType | null) => PlaybackType | null), ) => Promise<{ url: string | null; sessionId: string | null } | null>; playUrl?: string | null; setPlayUrl: React.Dispatch>; playSessionId?: string | null; setOfflineSettings: (data: PlaybackType) => void; }; const PlaySettingsContext = createContext( undefined, ); export const PlaySettingsProvider: React.FC<{ children: React.ReactNode }> = ({ children, }) => { const [playSettings, _setPlaySettings] = useState(null); const [mediaSource, setMediaSource] = useState(null); const [playUrl, setPlayUrl] = useState(null); const [playSessionId, setPlaySessionId] = useState(null); const api = useAtomValue(apiAtom); const settings = useAtomValue(settingsAtom); const user = useAtomValue(userAtom); const setOfflineSettings = useCallback((data: PlaybackType) => { _setPlaySettings(data); }, []); const setPlaySettings = useCallback( async ( dataOrUpdater: | PlaybackType | null | ((prev: PlaybackType | null) => PlaybackType | null), ): Promise<{ url: string | null; sessionId: string | null } | null> => { if (!api || !user || !settings) { _setPlaySettings(null); return null; } const newSettings = typeof dataOrUpdater === "function" ? dataOrUpdater(playSettings) : dataOrUpdater; if (newSettings === null) { _setPlaySettings(null); return null; } try { const native = generateDeviceProfile(); const data = await getStreamUrl({ api, deviceProfile: native, item: newSettings?.item, mediaSourceId: newSettings?.mediaSource?.Id, startTimeTicks: 0, maxStreamingBitrate: newSettings?.bitrate?.value, audioStreamIndex: newSettings?.audioIndex ?? 0, subtitleStreamIndex: newSettings?.subtitleIndex ?? -1, userId: user.Id, }); console.log("getStreamUrl ~"); console.log(`${data?.url?.slice(0, 100)}...${data?.url?.slice(-50)}`); _setPlaySettings(newSettings); setPlayUrl(data?.url!); setPlaySessionId(data?.sessionId!); setMediaSource(data?.mediaSource!); return data; } catch (error) { console.warn("Error getting stream URL:", error); return null; } }, [api, user, settings, playSettings], ); // useEffect(() => { // const postCaps = async () => { // if (!api) return; // await getSessionApi(api).postFullCapabilities({ // clientCapabilitiesDto: { // AppStoreUrl: "https://apps.apple.com/us/app/streamyfin/id6593660679", // DeviceProfile: native as any, // IconUrl: // "https://raw.githubusercontent.com/retardgerman/streamyfinweb/refs/heads/main/public/assets/images/icon_new_withoutBackground.png", // PlayableMediaTypes: ["Audio", "Video"], // SupportedCommands: ["Play"], // SupportsMediaControl: true, // SupportsPersistentIdentifier: true, // }, // }); // }; // postCaps(); // }, [settings, api]); return ( {children} ); }; export const usePlaySettings = () => { const context = useContext(PlaySettingsContext); if (context === undefined) { throw new Error( "usePlaySettings must be used within a PlaySettingsProvider", ); } return context; };