mirror of
https://github.com/streamyfin/streamyfin.git
synced 2025-08-20 18:37:18 +02:00
chore: Apply linting rules and add git hok (#611)
Co-authored-by: Fredrik Burmester <fredrik.burmester@gmail.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import {
|
||||
} from "@/packages/expo-screen-orientation";
|
||||
|
||||
function orientationToOrientationLock(
|
||||
orientation: Orientation
|
||||
orientation: Orientation,
|
||||
): OrientationLock {
|
||||
switch (orientation) {
|
||||
case Orientation.PORTRAIT_UP:
|
||||
|
||||
@@ -4,17 +4,20 @@ import {
|
||||
MediaStatus,
|
||||
} from "@/utils/jellyseerr/server/constants/media";
|
||||
import {
|
||||
hasPermission,
|
||||
Permission,
|
||||
hasPermission,
|
||||
} from "@/utils/jellyseerr/server/lib/permissions";
|
||||
import { MovieResult, TvResult } from "@/utils/jellyseerr/server/models/Search";
|
||||
import type {
|
||||
MovieResult,
|
||||
TvResult,
|
||||
} from "@/utils/jellyseerr/server/models/Search";
|
||||
import { useMemo } from "react";
|
||||
import MediaRequest from "../jellyseerr/server/entity/MediaRequest";
|
||||
import { MovieDetails } from "../jellyseerr/server/models/Movie";
|
||||
import { TvDetails } from "../jellyseerr/server/models/Tv";
|
||||
import type MediaRequest from "../jellyseerr/server/entity/MediaRequest";
|
||||
import type { MovieDetails } from "../jellyseerr/server/models/Movie";
|
||||
import type { TvDetails } from "../jellyseerr/server/models/Tv";
|
||||
|
||||
export const useJellyseerrCanRequest = (
|
||||
item?: MovieResult | TvResult | MovieDetails | TvDetails
|
||||
item?: MovieResult | TvResult | MovieDetails | TvDetails,
|
||||
) => {
|
||||
const { jellyseerrUser } = useJellyseerr();
|
||||
|
||||
@@ -25,7 +28,7 @@ export const useJellyseerrCanRequest = (
|
||||
item?.mediaInfo?.requests?.some(
|
||||
(r: MediaRequest) =>
|
||||
r.status == MediaRequestStatus.PENDING ||
|
||||
r.status == MediaRequestStatus.APPROVED
|
||||
r.status == MediaRequestStatus.APPROVED,
|
||||
) ||
|
||||
item.mediaInfo?.status === MediaStatus.AVAILABLE ||
|
||||
item.mediaInfo?.status === MediaStatus.BLACKLISTED ||
|
||||
@@ -42,26 +45,21 @@ export const useJellyseerrCanRequest = (
|
||||
: Permission.REQUEST_TV,
|
||||
],
|
||||
jellyseerrUser.permissions,
|
||||
{ type: "or" }
|
||||
{ type: "or" },
|
||||
);
|
||||
|
||||
return userHasPermission && !canNotRequest;
|
||||
}, [item, jellyseerrUser]);
|
||||
|
||||
const hasAdvancedRequestPermission = useMemo(() => {
|
||||
if (!jellyseerrUser) return false;
|
||||
if (!jellyseerrUser) return false;
|
||||
|
||||
return hasPermission(
|
||||
[
|
||||
Permission.REQUEST_ADVANCED,
|
||||
Permission.MANAGE_REQUESTS
|
||||
],
|
||||
jellyseerrUser.permissions,
|
||||
{type: 'or'}
|
||||
)
|
||||
},
|
||||
[jellyseerrUser]
|
||||
);
|
||||
return hasPermission(
|
||||
[Permission.REQUEST_ADVANCED, Permission.MANAGE_REQUESTS],
|
||||
jellyseerrUser.permissions,
|
||||
{ type: "or" },
|
||||
);
|
||||
}, [jellyseerrUser]);
|
||||
|
||||
return [canRequest, hasAdvancedRequestPermission];
|
||||
};
|
||||
|
||||
@@ -93,7 +93,7 @@ export const sortByPreferenceAtom = atomWithStorage<SortPreference>(
|
||||
removeItem: (key) => {
|
||||
storage.delete(key);
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const sortOrderPreferenceAtom = atomWithStorage<SortOrderPreference>(
|
||||
@@ -110,19 +110,19 @@ export const sortOrderPreferenceAtom = atomWithStorage<SortOrderPreference>(
|
||||
removeItem: (key) => {
|
||||
storage.delete(key);
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const getSortByPreference = (
|
||||
libraryId: string,
|
||||
preferences: SortPreference
|
||||
preferences: SortPreference,
|
||||
) => {
|
||||
return preferences?.[libraryId] || null;
|
||||
};
|
||||
|
||||
export const getSortOrderPreference = (
|
||||
libraryId: string,
|
||||
preferences: SortOrderPreference
|
||||
preferences: SortOrderPreference,
|
||||
) => {
|
||||
return preferences?.[libraryId] || null;
|
||||
};
|
||||
|
||||
@@ -2,5 +2,5 @@ import * as ScreenOrientation from "@/packages/expo-screen-orientation";
|
||||
import { atom } from "jotai";
|
||||
|
||||
export const orientationAtom = atom<number>(
|
||||
ScreenOrientation.OrientationLock.PORTRAIT_UP
|
||||
ScreenOrientation.OrientationLock.PORTRAIT_UP,
|
||||
);
|
||||
|
||||
@@ -7,9 +7,9 @@ interface ThemeColors {
|
||||
|
||||
export const calculateTextColor = (backgroundColor: string): string => {
|
||||
// Convert hex to RGB
|
||||
const r = parseInt(backgroundColor.slice(1, 3), 16);
|
||||
const g = parseInt(backgroundColor.slice(3, 5), 16);
|
||||
const b = parseInt(backgroundColor.slice(5, 7), 16);
|
||||
const r = Number.parseInt(backgroundColor.slice(1, 3), 16);
|
||||
const g = Number.parseInt(backgroundColor.slice(3, 5), 16);
|
||||
const b = Number.parseInt(backgroundColor.slice(5, 7), 16);
|
||||
|
||||
// Calculate perceived brightness
|
||||
// Using the formula: (R * 299 + G * 587 + B * 114) / 1000
|
||||
@@ -47,9 +47,9 @@ const calculateRelativeLuminance = (rgb: number[]): number => {
|
||||
};
|
||||
|
||||
export const isCloseToBlack = (color: string): boolean => {
|
||||
const r = parseInt(color.slice(1, 3), 16);
|
||||
const g = parseInt(color.slice(3, 5), 16);
|
||||
const b = parseInt(color.slice(5, 7), 16);
|
||||
const r = Number.parseInt(color.slice(1, 3), 16);
|
||||
const g = Number.parseInt(color.slice(3, 5), 16);
|
||||
const b = Number.parseInt(color.slice(5, 7), 16);
|
||||
|
||||
// Check if the color is very dark (close to black)
|
||||
return r < 20 && g < 20 && b < 20;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { processesAtom } from "@/providers/DownloadProvider";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import type { JobStatus } from "@/utils/optimize-server";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { useEffect } from "react";
|
||||
import {JobStatus} from "@/utils/optimize-server";
|
||||
import {processesAtom} from "@/providers/DownloadProvider";
|
||||
import {useSettings} from "@/utils/atoms/settings";
|
||||
|
||||
export interface Job {
|
||||
id: string;
|
||||
@@ -24,7 +24,7 @@ export const queueActions = {
|
||||
processJob: async (
|
||||
queue: Job[],
|
||||
setQueue: (update: Job[]) => void,
|
||||
setProcessing: (processing: boolean) => void
|
||||
setProcessing: (processing: boolean) => void,
|
||||
) => {
|
||||
const [job, ...rest] = queue;
|
||||
|
||||
@@ -45,7 +45,7 @@ export const queueActions = {
|
||||
},
|
||||
clear: (
|
||||
setQueue: (update: Job[]) => void,
|
||||
setProcessing: (processing: boolean) => void
|
||||
setProcessing: (processing: boolean) => void,
|
||||
) => {
|
||||
setQueue([]);
|
||||
setProcessing(false);
|
||||
@@ -59,7 +59,12 @@ export const useJobProcessor = () => {
|
||||
const [settings] = useSettings();
|
||||
|
||||
useEffect(() => {
|
||||
if (!running && queue.length > 0 && settings && processes.length < settings?.remuxConcurrentLimit) {
|
||||
if (
|
||||
!running &&
|
||||
queue.length > 0 &&
|
||||
settings &&
|
||||
processes.length < settings?.remuxConcurrentLimit
|
||||
) {
|
||||
console.info("Processing queue", queue);
|
||||
queueActions.processJob(queue, setQueue, setRunning);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { BITRATES, type Bitrate } from "@/components/BitrateSelector";
|
||||
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { Video } from "@/utils/jellyseerr/server/models/Movie";
|
||||
import { writeInfoLog } from "@/utils/log";
|
||||
import {
|
||||
type BaseItemKind,
|
||||
type CultureDto,
|
||||
type ItemFilter,
|
||||
type ItemSortBy,
|
||||
type SortOrder,
|
||||
SubtitlePlaybackMode,
|
||||
} from "@jellyfin/sdk/lib/generated-client";
|
||||
import { atom, useAtom, useAtomValue } from "jotai";
|
||||
import { useCallback, useEffect, useMemo } from "react";
|
||||
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
|
||||
import { storage } from "../mmkv";
|
||||
import { Platform } from "react-native";
|
||||
import {
|
||||
CultureDto,
|
||||
SubtitlePlaybackMode,
|
||||
ItemSortBy,
|
||||
SortOrder,
|
||||
BaseItemKind,
|
||||
ItemFilter,
|
||||
} from "@jellyfin/sdk/lib/generated-client";
|
||||
import { Bitrate, BITRATES } from "@/components/BitrateSelector";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { writeInfoLog } from "@/utils/log";
|
||||
import { Video } from "@/utils/jellyseerr/server/models/Movie";
|
||||
import { storage } from "../mmkv";
|
||||
|
||||
const STREAMYFIN_PLUGIN_ID = "1e9e5d386e6746158719e98a5c34f004";
|
||||
const STREAMYFIN_PLUGIN_SETTINGS = "STREAMYFIN_PLUGIN_SETTINGS";
|
||||
@@ -26,17 +26,30 @@ export type DownloadOption = {
|
||||
value: DownloadQuality;
|
||||
};
|
||||
|
||||
export const ScreenOrientationEnum: Record<ScreenOrientation.OrientationLock, string> = {
|
||||
[ScreenOrientation.OrientationLock.DEFAULT]: "home.settings.other.orientations.DEFAULT",
|
||||
[ScreenOrientation.OrientationLock.ALL]: "home.settings.other.orientations.ALL",
|
||||
[ScreenOrientation.OrientationLock.PORTRAIT]: "home.settings.other.orientations.PORTRAIT",
|
||||
[ScreenOrientation.OrientationLock.PORTRAIT_UP]: "home.settings.other.orientations.PORTRAIT_UP",
|
||||
[ScreenOrientation.OrientationLock.PORTRAIT_DOWN]: "home.settings.other.orientations.PORTRAIT_DOWN",
|
||||
[ScreenOrientation.OrientationLock.LANDSCAPE]: "home.settings.other.orientations.LANDSCAPE",
|
||||
[ScreenOrientation.OrientationLock.LANDSCAPE_LEFT]: "home.settings.other.orientations.LANDSCAPE_LEFT",
|
||||
[ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT]: "home.settings.other.orientations.LANDSCAPE_RIGHT",
|
||||
[ScreenOrientation.OrientationLock.OTHER]: "home.settings.other.orientations.OTHER",
|
||||
[ScreenOrientation.OrientationLock.UNKNOWN]: "home.settings.other.orientations.UNKNOWN",
|
||||
export const ScreenOrientationEnum: Record<
|
||||
ScreenOrientation.OrientationLock,
|
||||
string
|
||||
> = {
|
||||
[ScreenOrientation.OrientationLock.DEFAULT]:
|
||||
"home.settings.other.orientations.DEFAULT",
|
||||
[ScreenOrientation.OrientationLock.ALL]:
|
||||
"home.settings.other.orientations.ALL",
|
||||
[ScreenOrientation.OrientationLock.PORTRAIT]:
|
||||
"home.settings.other.orientations.PORTRAIT",
|
||||
[ScreenOrientation.OrientationLock.PORTRAIT_UP]:
|
||||
"home.settings.other.orientations.PORTRAIT_UP",
|
||||
[ScreenOrientation.OrientationLock.PORTRAIT_DOWN]:
|
||||
"home.settings.other.orientations.PORTRAIT_DOWN",
|
||||
[ScreenOrientation.OrientationLock.LANDSCAPE]:
|
||||
"home.settings.other.orientations.LANDSCAPE",
|
||||
[ScreenOrientation.OrientationLock.LANDSCAPE_LEFT]:
|
||||
"home.settings.other.orientations.LANDSCAPE_LEFT",
|
||||
[ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT]:
|
||||
"home.settings.other.orientations.LANDSCAPE_RIGHT",
|
||||
[ScreenOrientation.OrientationLock.OTHER]:
|
||||
"home.settings.other.orientations.OTHER",
|
||||
[ScreenOrientation.OrientationLock.UNKNOWN]:
|
||||
"home.settings.other.orientations.UNKNOWN",
|
||||
};
|
||||
|
||||
export const DownloadOptions: DownloadOption[] = [
|
||||
@@ -102,8 +115,8 @@ export type HomeSectionNextUpResolver = {
|
||||
|
||||
export enum VideoPlayer {
|
||||
// NATIVE, //todo: changes will make this a lot more easier to implement if we want. delete if not wanted
|
||||
VLC_3,
|
||||
VLC_4,
|
||||
VLC_3 = 0,
|
||||
VLC_4 = 1,
|
||||
}
|
||||
|
||||
export type Settings = {
|
||||
@@ -201,7 +214,8 @@ const defaultValues: Settings = {
|
||||
const loadSettings = (): Partial<Settings> => {
|
||||
try {
|
||||
const jsonValue = storage.getString("settings");
|
||||
const loadedValues: Partial<Settings> = jsonValue != null ? JSON.parse(jsonValue) : {};
|
||||
const loadedValues: Partial<Settings> =
|
||||
jsonValue != null ? JSON.parse(jsonValue) : {};
|
||||
|
||||
return loadedValues;
|
||||
} catch (error) {
|
||||
@@ -223,7 +237,9 @@ const saveSettings = (settings: Settings) => {
|
||||
};
|
||||
|
||||
export const settingsAtom = atom<Partial<Settings> | null>(null);
|
||||
export const pluginSettingsAtom = atom(storage.get<PluginLockableSettings>(STREAMYFIN_PLUGIN_SETTINGS));
|
||||
export const pluginSettingsAtom = atom(
|
||||
storage.get<PluginLockableSettings>(STREAMYFIN_PLUGIN_SETTINGS),
|
||||
);
|
||||
|
||||
export const useSettings = () => {
|
||||
const api = useAtomValue(apiAtom);
|
||||
@@ -242,7 +258,7 @@ export const useSettings = () => {
|
||||
storage.setAny(STREAMYFIN_PLUGIN_SETTINGS, settings);
|
||||
_setPluginSettings(settings);
|
||||
},
|
||||
[_setPluginSettings]
|
||||
[_setPluginSettings],
|
||||
);
|
||||
|
||||
const refreshStreamyfinPluginSettings = useCallback(async () => {
|
||||
@@ -252,7 +268,7 @@ export const useSettings = () => {
|
||||
writeInfoLog(`Got remote settings: ${data?.settings}`);
|
||||
return data?.settings;
|
||||
},
|
||||
(err) => undefined
|
||||
(err) => undefined,
|
||||
);
|
||||
setPluginSettings(settings);
|
||||
return settings;
|
||||
@@ -260,11 +276,17 @@ export const useSettings = () => {
|
||||
|
||||
const updateSettings = (update: Partial<Settings>) => {
|
||||
if (!_settings) return;
|
||||
const hasChanges = Object.entries(update).some(([key, value]) => _settings[key as keyof Settings] !== value);
|
||||
const hasChanges = Object.entries(update).some(
|
||||
([key, value]) => _settings[key as keyof Settings] !== value,
|
||||
);
|
||||
|
||||
if (hasChanges) {
|
||||
// Merge default settings, current settings, and updates to ensure all required properties exist
|
||||
const newSettings = { ...defaultValues, ..._settings, ...update } as Settings;
|
||||
const newSettings = {
|
||||
...defaultValues,
|
||||
..._settings,
|
||||
...update,
|
||||
} as Settings;
|
||||
setSettings(newSettings);
|
||||
saveSettings(newSettings);
|
||||
}
|
||||
@@ -275,24 +297,33 @@ export const useSettings = () => {
|
||||
// use user settings first and fallback on admin setting if required.
|
||||
const settings: Settings = useMemo(() => {
|
||||
let unlockedPluginDefaults = {} as Settings;
|
||||
const overrideSettings = Object.entries(pluginSettings || {}).reduce((acc, [key, setting]) => {
|
||||
if (setting) {
|
||||
const { value, locked } = setting;
|
||||
const overrideSettings = Object.entries(pluginSettings || {}).reduce(
|
||||
(acc, [key, setting]) => {
|
||||
if (setting) {
|
||||
const { value, locked } = setting;
|
||||
|
||||
// Make sure we override default settings with plugin settings when they are not locked.
|
||||
// Admin decided what users defaults should be and grants them the ability to change them too.
|
||||
if (locked === false && value && _settings?.[key as keyof Settings] !== value) {
|
||||
unlockedPluginDefaults = Object.assign(unlockedPluginDefaults, {
|
||||
[key as keyof Settings]: value,
|
||||
// Make sure we override default settings with plugin settings when they are not locked.
|
||||
// Admin decided what users defaults should be and grants them the ability to change them too.
|
||||
if (
|
||||
locked === false &&
|
||||
value &&
|
||||
_settings?.[key as keyof Settings] !== value
|
||||
) {
|
||||
unlockedPluginDefaults = Object.assign(unlockedPluginDefaults, {
|
||||
[key as keyof Settings]: value,
|
||||
});
|
||||
}
|
||||
|
||||
acc = Object.assign(acc, {
|
||||
[key]: locked
|
||||
? value
|
||||
: (_settings?.[key as keyof Settings] ?? value),
|
||||
});
|
||||
}
|
||||
|
||||
acc = Object.assign(acc, {
|
||||
[key]: locked ? value : _settings?.[key as keyof Settings] ?? value,
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, {} as Settings);
|
||||
return acc;
|
||||
},
|
||||
{} as Settings,
|
||||
);
|
||||
|
||||
return {
|
||||
...defaultValues,
|
||||
@@ -301,5 +332,11 @@ export const useSettings = () => {
|
||||
};
|
||||
}, [_settings, pluginSettings]);
|
||||
|
||||
return [settings, updateSettings, pluginSettings, setPluginSettings, refreshStreamyfinPluginSettings] as const;
|
||||
return [
|
||||
settings,
|
||||
updateSettings,
|
||||
pluginSettings,
|
||||
setPluginSettings,
|
||||
refreshStreamyfinPluginSettings,
|
||||
] as const;
|
||||
};
|
||||
|
||||
@@ -25,8 +25,7 @@ export async function unregisterBackgroundFetchAsync() {
|
||||
}
|
||||
}
|
||||
|
||||
export const BACKGROUND_FETCH_TASK_SESSIONS =
|
||||
"background-fetch-sessions";
|
||||
export const BACKGROUND_FETCH_TASK_SESSIONS = "background-fetch-sessions";
|
||||
|
||||
export async function registerBackgroundFetchAsyncSessions() {
|
||||
try {
|
||||
@@ -47,4 +46,4 @@ export async function unregisterBackgroundFetchAsyncSessions() {
|
||||
} catch (error) {
|
||||
console.log("Error unregistering background fetch task", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ export const formatBitrate = (bitrate?: number | null) => {
|
||||
|
||||
const sizes = ["bps", "Kbps", "Mbps", "Gbps", "Tbps"];
|
||||
if (bitrate === 0) return "0 bps";
|
||||
const i = parseInt(Math.floor(Math.log(bitrate) / Math.log(1000)).toString());
|
||||
const i = Number.parseInt(
|
||||
Math.floor(Math.log(bitrate) / Math.log(1000)).toString(),
|
||||
);
|
||||
return Math.round((bitrate / Math.pow(1000, i)) * 100) / 100 + " " + sizes[i];
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
readonly Folders: "folders";
|
||||
*/
|
||||
export const colletionTypeToItemType = (
|
||||
collectionType?: CollectionType | null
|
||||
collectionType?: CollectionType | null,
|
||||
): BaseItemKind | undefined => {
|
||||
if (!collectionType) return undefined;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export const getOrSetDeviceId = () => {
|
||||
};
|
||||
|
||||
export const getDeviceId = () => {
|
||||
let deviceId = storage.getString("deviceId");
|
||||
const deviceId = storage.getString("deviceId");
|
||||
|
||||
return deviceId || null;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import useImageStorage from "@/hooks/useImageStorage";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { getPrimaryImageUrlById } from "@/utils/jellyfin/image/getPrimaryImageUrlById";
|
||||
import { storage } from "@/utils/mmkv";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import { useAtom } from "jotai";
|
||||
|
||||
const useDownloadHelper = () => {
|
||||
@@ -19,7 +19,7 @@ const useDownloadHelper = () => {
|
||||
console.log(`Saving primary image for series: ${item.SeriesId}`);
|
||||
await saveImage(
|
||||
item.SeriesId,
|
||||
getPrimaryImageUrlById({ api, id: item.SeriesId })
|
||||
getPrimaryImageUrlById({ api, id: item.SeriesId }),
|
||||
);
|
||||
console.log(`Primary image saved for series: ${item.SeriesId}`);
|
||||
} else {
|
||||
|
||||
@@ -14,7 +14,7 @@ class EventBus {
|
||||
off<T = void>(event: string, callback: Listener<T>): void {
|
||||
if (!this.listeners[event]) return;
|
||||
this.listeners[event] = this.listeners[event].filter(
|
||||
(fn) => fn !== callback
|
||||
(fn) => fn !== callback,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { ImageSource } from "expo-image";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import type { ImageSource } from "expo-image";
|
||||
|
||||
interface Props {
|
||||
item: BaseItemDto;
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface SubtitleTrack {
|
||||
}
|
||||
|
||||
export async function parseM3U8ForSubtitles(
|
||||
url: string
|
||||
url: string,
|
||||
): Promise<SubtitleTrack[]> {
|
||||
try {
|
||||
const response = await axios.get(url, { responseType: "text" });
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// utils/getDefaultPlaySettings.ts
|
||||
import { BITRATES } from "@/components/BitrateSelector";
|
||||
import {
|
||||
import type {
|
||||
BaseItemDto,
|
||||
MediaSourceInfo,
|
||||
} from "@jellyfin/sdk/lib/generated-client";
|
||||
import { Settings, useSettings } from "../atoms/settings";
|
||||
import { type Settings, useSettings } from "../atoms/settings";
|
||||
import {
|
||||
AudioStreamRanker,
|
||||
StreamRanker,
|
||||
@@ -34,7 +34,7 @@ export function getDefaultPlaySettings(
|
||||
item: BaseItemDto,
|
||||
settings: Settings,
|
||||
previousIndexes?: previousIndexes,
|
||||
previousSource?: MediaSourceInfo
|
||||
previousSource?: MediaSourceInfo,
|
||||
): PlaySettings {
|
||||
if (item.Type === "Program") {
|
||||
return {
|
||||
@@ -53,14 +53,14 @@ export function getDefaultPlaySettings(
|
||||
// 2. Get default or preferred audio
|
||||
const defaultAudioIndex = mediaSource?.DefaultAudioStreamIndex;
|
||||
const preferedAudioIndex = mediaSource?.MediaStreams?.find(
|
||||
(x) => x.Type === "Audio" && x.Language === settings?.defaultAudioLanguage
|
||||
(x) => x.Type === "Audio" && x.Language === settings?.defaultAudioLanguage,
|
||||
)?.Index;
|
||||
const firstAudioIndex = mediaSource?.MediaStreams?.find(
|
||||
(x) => x.Type === "Audio"
|
||||
(x) => x.Type === "Audio",
|
||||
)?.Index;
|
||||
|
||||
// We prefer the previous track over the default track.
|
||||
let trackOptions: TrackOptions = {
|
||||
const trackOptions: TrackOptions = {
|
||||
DefaultAudioStreamIndex: defaultAudioIndex ?? -1,
|
||||
DefaultSubtitleStreamIndex: mediaSource?.DefaultSubtitleStreamIndex ?? -1,
|
||||
};
|
||||
@@ -74,7 +74,7 @@ export function getDefaultPlaySettings(
|
||||
previousIndexes.subtitleIndex,
|
||||
previousSource,
|
||||
mediaStreams,
|
||||
trackOptions
|
||||
trackOptions,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ export function getDefaultPlaySettings(
|
||||
previousIndexes.audioIndex,
|
||||
previousSource,
|
||||
mediaStreams,
|
||||
trackOptions
|
||||
trackOptions,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { getPrimaryImageUrl } from "./getPrimaryImageUrl";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
|
||||
/**
|
||||
* Retrieves the primary image URL for a given item.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import {
|
||||
BaseItemDto,
|
||||
type BaseItemDto,
|
||||
BaseItemPerson,
|
||||
} from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { isBaseItemDto } from "../jellyfin";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import {
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import type {
|
||||
BaseItemDto,
|
||||
BaseItemPerson,
|
||||
} from "@jellyfin/sdk/lib/generated-client/models";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
|
||||
/**
|
||||
* Retrieves the primary image URL for a given item.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import {
|
||||
BaseItemDto,
|
||||
type BaseItemDto,
|
||||
BaseItemPerson,
|
||||
} from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { isBaseItemDto } from "../jellyfin";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
|
||||
/**
|
||||
* Generates the authorization headers for Jellyfin API requests.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import native from "@/utils/profiles/native";
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import {
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import type {
|
||||
BaseItemDto,
|
||||
MediaSourceInfo,
|
||||
PlaybackInfoResponse,
|
||||
@@ -63,7 +63,7 @@ export const getStreamUrl = async ({
|
||||
data: {
|
||||
deviceProfile,
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
const transcodeUrl = res0.data.MediaSources?.[0].TranscodingUrl;
|
||||
sessionId = res0.data.PlaySessionId || null;
|
||||
@@ -95,7 +95,7 @@ export const getStreamUrl = async ({
|
||||
audioStreamIndex,
|
||||
subtitleStreamIndex,
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (res2.status !== 200) {
|
||||
@@ -105,7 +105,7 @@ export const getStreamUrl = async ({
|
||||
sessionId = res2.data.PlaySessionId || null;
|
||||
|
||||
mediaSource = res2.data.MediaSources?.find(
|
||||
(source: MediaSourceInfo) => source.Id === mediaSourceId
|
||||
(source: MediaSourceInfo) => source.Id === mediaSourceId,
|
||||
);
|
||||
|
||||
if (item.MediaType === "Video") {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { AxiosError } from "axios";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import type { AxiosError } from "axios";
|
||||
|
||||
interface MarkAsNotPlayedParams {
|
||||
api: Api | null | undefined;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { getPlaystateApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
|
||||
interface MarkAsPlayedParams {
|
||||
@@ -14,7 +14,11 @@ interface MarkAsPlayedParams {
|
||||
* @param params - The parameters for marking an item as played∏
|
||||
* @returns A promise that resolves to true if the operation was successful, false otherwise
|
||||
*/
|
||||
export const markAsPlayed = async ({ api, item, userId }: MarkAsPlayedParams): Promise<boolean> => {
|
||||
export const markAsPlayed = async ({
|
||||
api,
|
||||
item,
|
||||
userId,
|
||||
}: MarkAsPlayedParams): Promise<boolean> => {
|
||||
if (!api || !item?.Id || !userId || !item.RunTimeTicks) {
|
||||
console.error("Invalid parameters for markAsPlayed");
|
||||
return false;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { getAuthHeaders } from "../jellyfin";
|
||||
import { postCapabilities } from "../session/capabilities";
|
||||
import { Settings } from "@/utils/atoms/settings";
|
||||
import { getOrSetDeviceId } from "@/providers/JellyfinProvider";
|
||||
import type { Settings } from "@/utils/atoms/settings";
|
||||
import ios from "@/utils/profiles/ios";
|
||||
import native from "@/utils/profiles/native";
|
||||
import old from "@/utils/profiles/old";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import { DeviceProfile } from "@jellyfin/sdk/lib/generated-client";
|
||||
import {
|
||||
getMediaInfoApi,
|
||||
getPlaystateApi,
|
||||
getSessionApi,
|
||||
} from "@jellyfin/sdk/lib/utils/api";
|
||||
import { DeviceProfile } from "@jellyfin/sdk/lib/generated-client";
|
||||
import { getOrSetDeviceId } from "@/providers/JellyfinProvider";
|
||||
import ios from "@/utils/profiles/ios";
|
||||
import native from "@/utils/profiles/native";
|
||||
import old from "@/utils/profiles/old";
|
||||
import { getAuthHeaders } from "../jellyfin";
|
||||
import { postCapabilities } from "../session/capabilities";
|
||||
|
||||
interface ReportPlaybackProgressParams {
|
||||
api?: Api | null;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Settings } from "@/utils/atoms/settings";
|
||||
import type { Settings } from "@/utils/atoms/settings";
|
||||
import native from "@/utils/profiles/native";
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { AxiosError, AxiosResponse } from "axios";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import type { AxiosError, AxiosResponse } from "axios";
|
||||
import { getAuthHeaders } from "../jellyfin";
|
||||
|
||||
interface PostCapabilitiesParams {
|
||||
@@ -47,7 +47,7 @@ export const postCapabilities = async ({
|
||||
},
|
||||
{
|
||||
headers: getAuthHeaders(api),
|
||||
}
|
||||
},
|
||||
);
|
||||
return d;
|
||||
} catch (error: any | AxiosError) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { AxiosError } from "axios";
|
||||
import { getAuthHeaders } from "../jellyfin";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { atomWithStorage, createJSONStorage } from "jotai/utils";
|
||||
import { storage } from "./mmkv";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import React, { createContext, useContext } from "react";
|
||||
import { atomWithStorage, createJSONStorage } from "jotai/utils";
|
||||
import type React from "react";
|
||||
import { createContext, useContext } from "react";
|
||||
import { storage } from "./mmkv";
|
||||
|
||||
type LogLevel = "INFO" | "WARN" | "ERROR";
|
||||
|
||||
@@ -20,10 +21,10 @@ const mmkvStorage = createJSONStorage(() => ({
|
||||
const logsAtom = atomWithStorage("logs", [], mmkvStorage);
|
||||
|
||||
const LogContext = createContext<ReturnType<typeof useLogProvider> | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
const DownloadContext = createContext<ReturnType<typeof useLogProvider> | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
|
||||
function useLogProvider() {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { itemRouter } from "@/components/common/TouchableItemRouter";
|
||||
import {
|
||||
import { DownloadedItem } from "@/providers/DownloadProvider";
|
||||
import type {
|
||||
BaseItemDto,
|
||||
MediaSourceInfo,
|
||||
} from "@jellyfin/sdk/lib/generated-client";
|
||||
import axios from "axios";
|
||||
import { writeToLog } from "./log";
|
||||
import { DownloadedItem } from "@/providers/DownloadProvider";
|
||||
import { MMKV } from "react-native-mmkv";
|
||||
import { writeToLog } from "./log";
|
||||
|
||||
interface IJobInput {
|
||||
deviceId?: string | null;
|
||||
@@ -63,7 +63,7 @@ export async function getAllJobsByDeviceId({
|
||||
console.error(
|
||||
statusResponse.status,
|
||||
statusResponse.data,
|
||||
statusResponse.statusText
|
||||
statusResponse.statusText,
|
||||
);
|
||||
throw new Error("Failed to fetch job status");
|
||||
}
|
||||
@@ -172,7 +172,7 @@ export async function getStatistics({
|
||||
export function saveDownloadItemInfoToDiskTmp(
|
||||
item: BaseItemDto,
|
||||
mediaSource: MediaSourceInfo,
|
||||
url: string
|
||||
url: string,
|
||||
): boolean {
|
||||
try {
|
||||
const storage = new MMKV();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DeviceProfile } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import type { DeviceProfile } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
|
||||
export const chromecast: DeviceProfile = {
|
||||
Name: "Chromecast Video Profile",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DeviceProfile } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import type { DeviceProfile } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
|
||||
export const chromecasth265: DeviceProfile = {
|
||||
Name: "Chromecast Video Profile",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { createStore } from 'jotai';
|
||||
import { createStore } from "jotai";
|
||||
|
||||
export const store = createStore();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {
|
||||
import type {
|
||||
MediaSourceInfo,
|
||||
MediaStream,
|
||||
} from "@jellyfin/sdk/lib/generated-client";
|
||||
@@ -10,14 +10,14 @@ abstract class StreamRankerStrategy {
|
||||
prevIndex: number,
|
||||
prevSource: MediaSourceInfo,
|
||||
mediaStreams: MediaStream[],
|
||||
trackOptions: any
|
||||
trackOptions: any,
|
||||
): void;
|
||||
|
||||
protected rank(
|
||||
prevIndex: number,
|
||||
prevSource: MediaSourceInfo,
|
||||
mediaStreams: MediaStream[],
|
||||
trackOptions: any
|
||||
trackOptions: any,
|
||||
): void {
|
||||
if (prevIndex == -1) {
|
||||
console.debug(`AutoSet Subtitle - No Stream Set`);
|
||||
@@ -41,7 +41,7 @@ abstract class StreamRankerStrategy {
|
||||
}
|
||||
|
||||
console.debug(
|
||||
`AutoSet ${this.streamType} - Previous was ${prevStream.Index} - ${prevStream.DisplayTitle}`
|
||||
`AutoSet ${this.streamType} - Previous was ${prevStream.Index} - ${prevStream.DisplayTitle}`,
|
||||
);
|
||||
|
||||
let prevRelIndex = 0;
|
||||
@@ -74,7 +74,7 @@ abstract class StreamRankerStrategy {
|
||||
score += 2;
|
||||
|
||||
console.debug(
|
||||
`AutoSet ${this.streamType} - Score ${score} for ${stream.Index} - ${stream.DisplayTitle}`
|
||||
`AutoSet ${this.streamType} - Score ${score} for ${stream.Index} - ${stream.DisplayTitle}`,
|
||||
);
|
||||
if (score > bestStreamScore && score >= 3) {
|
||||
bestStreamScore = score;
|
||||
@@ -86,12 +86,12 @@ abstract class StreamRankerStrategy {
|
||||
|
||||
if (bestStreamIndex != null) {
|
||||
console.debug(
|
||||
`AutoSet ${this.streamType} - Using ${bestStreamIndex} score ${bestStreamScore}.`
|
||||
`AutoSet ${this.streamType} - Using ${bestStreamIndex} score ${bestStreamScore}.`,
|
||||
);
|
||||
trackOptions[`Default${this.streamType}StreamIndex`] = bestStreamIndex;
|
||||
} else {
|
||||
console.debug(
|
||||
`AutoSet ${this.streamType} - Threshold not met. Using default.`
|
||||
`AutoSet ${this.streamType} - Threshold not met. Using default.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ class SubtitleStreamRanker extends StreamRankerStrategy {
|
||||
prevIndex: number,
|
||||
prevSource: MediaSourceInfo,
|
||||
mediaStreams: MediaStream[],
|
||||
trackOptions: any
|
||||
trackOptions: any,
|
||||
): void {
|
||||
super.rank(prevIndex, prevSource, mediaStreams, trackOptions);
|
||||
}
|
||||
@@ -117,7 +117,7 @@ class AudioStreamRanker extends StreamRankerStrategy {
|
||||
prevIndex: number,
|
||||
prevSource: MediaSourceInfo,
|
||||
mediaStreams: MediaStream[],
|
||||
trackOptions: any
|
||||
trackOptions: any,
|
||||
): void {
|
||||
super.rank(prevIndex, prevSource, mediaStreams, trackOptions);
|
||||
}
|
||||
@@ -138,7 +138,7 @@ class StreamRanker {
|
||||
prevIndex: number,
|
||||
prevSource: MediaSourceInfo,
|
||||
mediaStreams: MediaStream[],
|
||||
trackOptions: any
|
||||
trackOptions: any,
|
||||
) {
|
||||
this.strategy.rankStream(prevIndex, prevSource, mediaStreams, trackOptions);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Truncate a text longer than a certain length
|
||||
*/
|
||||
export const tc = (text: string | null | undefined, length: number = 20) => {
|
||||
export const tc = (text: string | null | undefined, length = 20) => {
|
||||
if (!text) return "";
|
||||
return text.length > length ? text.substr(0, length) + "..." : text;
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @returns A string formatted as "Xh Ym" where X is hours and Y is minutes.
|
||||
*/
|
||||
export const runtimeTicksToMinutes = (
|
||||
ticks: number | null | undefined
|
||||
ticks: number | null | undefined,
|
||||
): string => {
|
||||
if (!ticks) return "0h 0m";
|
||||
|
||||
@@ -21,7 +21,7 @@ export const runtimeTicksToMinutes = (
|
||||
};
|
||||
|
||||
export const runtimeTicksToSeconds = (
|
||||
ticks: number | null | undefined
|
||||
ticks: number | null | undefined,
|
||||
): string => {
|
||||
if (!ticks) return "0h 0m";
|
||||
|
||||
@@ -39,7 +39,7 @@ export const runtimeTicksToSeconds = (
|
||||
// t: ms
|
||||
export const formatTimeString = (
|
||||
t: number | null | undefined,
|
||||
unit: "s" | "ms" | "tick" = "ms"
|
||||
unit: "s" | "ms" | "tick" = "ms",
|
||||
): string => {
|
||||
if (t === null || t === undefined) return "0:00";
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useFocusEffect } from "@react-navigation/core";
|
||||
import {
|
||||
QueryKey,
|
||||
type QueryKey,
|
||||
type UseQueryOptions,
|
||||
type UseQueryResult,
|
||||
useQuery,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
import { useCallback } from "react";
|
||||
|
||||
@@ -11,9 +11,9 @@ export function useReactNavigationQuery<
|
||||
TQueryFnData = unknown,
|
||||
TError = unknown,
|
||||
TData = TQueryFnData,
|
||||
TQueryKey extends QueryKey = QueryKey
|
||||
TQueryKey extends QueryKey = QueryKey,
|
||||
>(
|
||||
options: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>
|
||||
options: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
|
||||
): UseQueryResult<TData, TError> {
|
||||
const useQueryReturn = useQuery(options);
|
||||
|
||||
@@ -25,7 +25,7 @@ export function useReactNavigationQuery<
|
||||
options.enabled !== false
|
||||
)
|
||||
useQueryReturn.refetch();
|
||||
}, [options.enabled, options.refetchOnWindowFocus])
|
||||
}, [options.enabled, options.refetchOnWindowFocus]),
|
||||
);
|
||||
|
||||
return useQueryReturn;
|
||||
|
||||
Reference in New Issue
Block a user