mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-05-31 11:08:26 +01:00
Compare commits
1 Commits
sync-subti
...
renovate/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
302055c415 |
@@ -147,9 +147,9 @@ export default function DirectPlayerPage() {
|
|||||||
const audioIndexFromUrl = audioIndexStr
|
const audioIndexFromUrl = audioIndexStr
|
||||||
? Number.parseInt(audioIndexStr, 10)
|
? Number.parseInt(audioIndexStr, 10)
|
||||||
: undefined;
|
: undefined;
|
||||||
const subtitleIndexFromUrl = subtitleIndexStr
|
const subtitleIndex = subtitleIndexStr
|
||||||
? Number.parseInt(subtitleIndexStr, 10)
|
? Number.parseInt(subtitleIndexStr, 10)
|
||||||
: undefined;
|
: -1;
|
||||||
const bitrateValue = bitrateValueStr
|
const bitrateValue = bitrateValueStr
|
||||||
? Number.parseInt(bitrateValueStr, 10)
|
? Number.parseInt(bitrateValueStr, 10)
|
||||||
: BITRATES[0].value;
|
: BITRATES[0].value;
|
||||||
@@ -185,23 +185,6 @@ export default function DirectPlayerPage() {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}, [audioIndexFromUrl, offline, downloadedItem?.userData?.audioStreamIndex]);
|
}, [audioIndexFromUrl, offline, downloadedItem?.userData?.audioStreamIndex]);
|
||||||
|
|
||||||
// Resolve subtitle index: use URL param if provided, otherwise use stored index for offline playback
|
|
||||||
const subtitleIndex = useMemo(() => {
|
|
||||||
if (subtitleIndexFromUrl !== undefined) {
|
|
||||||
return subtitleIndexFromUrl;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
offline &&
|
|
||||||
downloadedItem?.userData?.subtitleStreamIndex !== undefined
|
|
||||||
) {
|
|
||||||
return downloadedItem.userData.subtitleStreamIndex;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}, [
|
|
||||||
subtitleIndexFromUrl,
|
|
||||||
offline,
|
|
||||||
downloadedItem?.userData?.subtitleStreamIndex,
|
|
||||||
]);
|
|
||||||
// Initialize TV audio/subtitle indices from URL params.
|
// Initialize TV audio/subtitle indices from URL params.
|
||||||
// No undefined guard: when a new episode's URL omits audioIndex, reset to
|
// No undefined guard: when a new episode's URL omits audioIndex, reset to
|
||||||
// undefined (media default) rather than leaking the previous episode's track.
|
// undefined (media default) rather than leaking the previous episode's track.
|
||||||
|
|||||||
6
bun.lock
6
bun.lock
@@ -106,7 +106,7 @@
|
|||||||
"@types/react": "~19.2.10",
|
"@types/react": "~19.2.10",
|
||||||
"@types/react-test-renderer": "19.1.0",
|
"@types/react-test-renderer": "19.1.0",
|
||||||
"cross-env": "10.1.0",
|
"cross-env": "10.1.0",
|
||||||
"expo-doctor": "1.19.7",
|
"expo-doctor": "1.19.8",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
"lint-staged": "17.0.5",
|
"lint-staged": "17.0.5",
|
||||||
"react-test-renderer": "19.2.3",
|
"react-test-renderer": "19.2.3",
|
||||||
@@ -968,7 +968,7 @@
|
|||||||
|
|
||||||
"expo-device": ["expo-device@56.0.4", "", { "dependencies": { "ua-parser-js": "^0.7.33" }, "peerDependencies": { "expo": "*" } }, "sha512-ucVcGPkvBrl2QHuy7XcYex2Y6BETvJ6TREutZrwLGUDnlvbpKS8KfQoNZOpvkyo5Nmm9RrasYQ0CrXmBHho2mg=="],
|
"expo-device": ["expo-device@56.0.4", "", { "dependencies": { "ua-parser-js": "^0.7.33" }, "peerDependencies": { "expo": "*" } }, "sha512-ucVcGPkvBrl2QHuy7XcYex2Y6BETvJ6TREutZrwLGUDnlvbpKS8KfQoNZOpvkyo5Nmm9RrasYQ0CrXmBHho2mg=="],
|
||||||
|
|
||||||
"expo-doctor": ["expo-doctor@1.19.7", "", { "bin": { "expo-doctor": "bin/expo-doctor.js" } }, "sha512-pzn7QtCifRlvGIQz8k7kszeYFaI5Yn81WTHlk/20tmd3jwnXxPjlcdyhFSkuRtO2v4a9gA/6aUWVBOosfffj9w=="],
|
"expo-doctor": ["expo-doctor@1.19.8", "", { "bin": { "expo-doctor": "bin/expo-doctor.js" } }, "sha512-ZHpQM+BfJe1DNaA+/ObtLYazC2x78tIV3kkfoSGR46Tj1EvzOFh1p9gFHsILI9TOyXPEcgxCkFw9AVvf8C4c1g=="],
|
||||||
|
|
||||||
"expo-file-system": ["expo-file-system@56.0.7", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-dcKzo8ShPloM7jgfnMcJStgQebhP8owVjCkNI/aX6NMFV1CYB8bxKGMdnzJ3mXk5nfaiW+F/lSKr2UIJ02WAUA=="],
|
"expo-file-system": ["expo-file-system@56.0.7", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-dcKzo8ShPloM7jgfnMcJStgQebhP8owVjCkNI/aX6NMFV1CYB8bxKGMdnzJ3mXk5nfaiW+F/lSKr2UIJ02WAUA=="],
|
||||||
|
|
||||||
@@ -1596,7 +1596,7 @@
|
|||||||
|
|
||||||
"react-native-text-ticker": ["react-native-text-ticker@1.15.0", "", {}, "sha512-d/uK+PIOhsYMy1r8h825iq/nADiHsabz3WMbRJSnkpQYn+K9aykUAXRRhu8ZbTAzk4CgnUWajJEFxS5ZDygsdg=="],
|
"react-native-text-ticker": ["react-native-text-ticker@1.15.0", "", {}, "sha512-d/uK+PIOhsYMy1r8h825iq/nADiHsabz3WMbRJSnkpQYn+K9aykUAXRRhu8ZbTAzk4CgnUWajJEFxS5ZDygsdg=="],
|
||||||
|
|
||||||
"react-native-track-player": ["react-native-track-player@github:lovegaoshi/react-native-track-player#33a3ecd", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-windows": "*", "shaka-player": "^4.7.9" }, "optionalPeers": ["react-native-windows", "shaka-player"] }, "lovegaoshi-react-native-track-player-33a3ecd"],
|
"react-native-track-player": ["react-native-track-player@github:lovegaoshi/react-native-track-player#33a3ecd", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-windows": "*", "shaka-player": "^4.7.9" }, "optionalPeers": ["react-native-windows", "shaka-player"] }, "lovegaoshi-react-native-track-player-33a3ecd", "sha512-vfkld2jUj7EPkAjIc/Vbx4Q4MtOOLmYtCYCE2dWJsyLnPqgj1f0xVzBxbeVP7dfT+eSh4KIXfdxESXaHgrXIlw=="],
|
||||||
|
|
||||||
"react-native-udp": ["react-native-udp@4.1.7", "", { "dependencies": { "buffer": "^5.6.0", "events": "^3.1.0" } }, "sha512-NUE3zewu61NCdSsLlj+l0ad6qojcVEZPT4hVG/x6DU9U4iCzwtfZSASh9vm7teAcVzLkdD+cO3411LHshAi/wA=="],
|
"react-native-udp": ["react-native-udp@4.1.7", "", { "dependencies": { "buffer": "^5.6.0", "events": "^3.1.0" } }, "sha512-NUE3zewu61NCdSsLlj+l0ad6qojcVEZPT4hVG/x6DU9U4iCzwtfZSASh9vm7teAcVzLkdD+cO3411LHshAi/wA=="],
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import useDefaultPlaySettings from "@/hooks/useDefaultPlaySettings";
|
|||||||
import { useImageColorsReturn } from "@/hooks/useImageColorsReturn";
|
import { useImageColorsReturn } from "@/hooks/useImageColorsReturn";
|
||||||
import { useOrientation } from "@/hooks/useOrientation";
|
import { useOrientation } from "@/hooks/useOrientation";
|
||||||
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
|
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
|
||||||
import { useDownload } from "@/providers/DownloadProvider";
|
|
||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
import { useOfflineMode } from "@/providers/OfflineModeProvider";
|
import { useOfflineMode } from "@/providers/OfflineModeProvider";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
@@ -60,9 +59,6 @@ const ItemContentMobile: React.FC<ItemContentProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
const isOffline = useOfflineMode();
|
const isOffline = useOfflineMode();
|
||||||
const { getDownloadedItemById } = useDownload();
|
|
||||||
const downloadedItem =
|
|
||||||
isOffline && item?.Id ? getDownloadedItemById(item.Id) : null;
|
|
||||||
const { settings } = useSettings();
|
const { settings } = useSettings();
|
||||||
const { orientation } = useOrientation();
|
const { orientation } = useOrientation();
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
@@ -109,30 +105,17 @@ const ItemContentMobile: React.FC<ItemContentProps> = ({
|
|||||||
|
|
||||||
// Needs to automatically change the selected to the default values for default indexes.
|
// Needs to automatically change the selected to the default values for default indexes.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// When offline, use the indices stored in userData (the last-used tracks for this file)
|
|
||||||
// rather than the server's defaults, so MediaSourceButton reflects what will actually play.
|
|
||||||
const offlineUserData = downloadedItem?.userData;
|
|
||||||
|
|
||||||
setSelectedOptions(() => ({
|
setSelectedOptions(() => ({
|
||||||
bitrate: defaultBitrate,
|
bitrate: defaultBitrate,
|
||||||
mediaSource: defaultMediaSource ?? undefined,
|
mediaSource: defaultMediaSource ?? undefined,
|
||||||
subtitleIndex:
|
subtitleIndex: defaultSubtitleIndex ?? -1,
|
||||||
offlineUserData && !offlineUserData.isTranscoded
|
audioIndex: defaultAudioIndex,
|
||||||
? offlineUserData.subtitleStreamIndex
|
|
||||||
: (defaultSubtitleIndex ?? -1),
|
|
||||||
audioIndex:
|
|
||||||
offlineUserData && !offlineUserData.isTranscoded
|
|
||||||
? offlineUserData.audioStreamIndex
|
|
||||||
: defaultAudioIndex,
|
|
||||||
}));
|
}));
|
||||||
}, [
|
}, [
|
||||||
defaultAudioIndex,
|
defaultAudioIndex,
|
||||||
defaultBitrate,
|
defaultBitrate,
|
||||||
defaultSubtitleIndex,
|
defaultSubtitleIndex,
|
||||||
defaultMediaSource,
|
defaultMediaSource,
|
||||||
downloadedItem?.userData?.audioStreamIndex,
|
|
||||||
downloadedItem?.userData?.subtitleStreamIndex,
|
|
||||||
downloadedItem?.userData?.isTranscoded,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
|
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
|
||||||
import type { ThemeColors } from "@/hooks/useImageColorsReturn";
|
import type { ThemeColors } from "@/hooks/useImageColorsReturn";
|
||||||
import { useDownload } from "@/providers/DownloadProvider";
|
|
||||||
import { useOfflineMode } from "@/providers/OfflineModeProvider";
|
|
||||||
import { BITRATES } from "./BitRateSheet";
|
import { BITRATES } from "./BitRateSheet";
|
||||||
import type { SelectedOptions } from "./ItemContent";
|
import type { SelectedOptions } from "./ItemContent";
|
||||||
import { type OptionGroup, PlatformDropdown } from "./PlatformDropdown";
|
import { type OptionGroup, PlatformDropdown } from "./PlatformDropdown";
|
||||||
@@ -30,14 +28,6 @@ export const MediaSourceButton: React.FC<Props> = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const isOffline = useOfflineMode();
|
|
||||||
const { getDownloadedItemById } = useDownload();
|
|
||||||
|
|
||||||
// For transcoded downloads there's only one burned-in track — nothing to pick
|
|
||||||
const isTranscodedDownload = useMemo(() => {
|
|
||||||
if (!isOffline || !item?.Id) return false;
|
|
||||||
return getDownloadedItemById(item.Id)?.userData?.isTranscoded === true;
|
|
||||||
}, [isOffline, item?.Id, getDownloadedItemById]);
|
|
||||||
|
|
||||||
const effectiveColors = colors || {
|
const effectiveColors = colors || {
|
||||||
primary: "#7c3aed",
|
primary: "#7c3aed",
|
||||||
@@ -82,36 +72,34 @@ export const MediaSourceButton: React.FC<Props> = ({
|
|||||||
const optionGroups: OptionGroup[] = useMemo(() => {
|
const optionGroups: OptionGroup[] = useMemo(() => {
|
||||||
const groups: OptionGroup[] = [];
|
const groups: OptionGroup[] = [];
|
||||||
|
|
||||||
if (!isOffline) {
|
// Bitrate group
|
||||||
// Bitrate group
|
groups.push({
|
||||||
|
title: t("item_card.quality"),
|
||||||
|
options: BITRATES.map((bitrate) => ({
|
||||||
|
type: "radio" as const,
|
||||||
|
label: bitrate.key,
|
||||||
|
value: bitrate,
|
||||||
|
selected: bitrate.value === selectedOptions.bitrate?.value,
|
||||||
|
onPress: () =>
|
||||||
|
setSelectedOptions((prev) => prev && { ...prev, bitrate }),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Media Source group (only if multiple sources)
|
||||||
|
if (item?.MediaSources && item.MediaSources.length > 1) {
|
||||||
groups.push({
|
groups.push({
|
||||||
title: t("item_card.quality"),
|
title: t("item_card.video"),
|
||||||
options: BITRATES.map((bitrate) => ({
|
options: item.MediaSources.map((source) => ({
|
||||||
type: "radio" as const,
|
type: "radio" as const,
|
||||||
label: bitrate.key,
|
label: getMediaSourceDisplayName(source),
|
||||||
value: bitrate,
|
value: source,
|
||||||
selected: bitrate.value === selectedOptions.bitrate?.value,
|
selected: source.Id === selectedOptions.mediaSource?.Id,
|
||||||
onPress: () =>
|
onPress: () =>
|
||||||
setSelectedOptions((prev) => prev && { ...prev, bitrate }),
|
setSelectedOptions(
|
||||||
|
(prev) => prev && { ...prev, mediaSource: source },
|
||||||
|
),
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Media Source group (only if multiple sources)
|
|
||||||
if (item?.MediaSources && item.MediaSources.length > 1) {
|
|
||||||
groups.push({
|
|
||||||
title: t("item_card.video"),
|
|
||||||
options: item.MediaSources.map((source) => ({
|
|
||||||
type: "radio" as const,
|
|
||||||
label: getMediaSourceDisplayName(source),
|
|
||||||
value: source,
|
|
||||||
selected: source.Id === selectedOptions.mediaSource?.Id,
|
|
||||||
onPress: () =>
|
|
||||||
setSelectedOptions(
|
|
||||||
(prev) => prev && { ...prev, mediaSource: source },
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Audio track group
|
// Audio track group
|
||||||
@@ -162,7 +150,6 @@ export const MediaSourceButton: React.FC<Props> = ({
|
|||||||
return groups;
|
return groups;
|
||||||
}, [
|
}, [
|
||||||
item,
|
item,
|
||||||
isOffline,
|
|
||||||
selectedOptions,
|
selectedOptions,
|
||||||
audioStreams,
|
audioStreams,
|
||||||
subtitleStreams,
|
subtitleStreams,
|
||||||
@@ -191,8 +178,6 @@ export const MediaSourceButton: React.FC<Props> = ({
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isTranscodedDownload) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlatformDropdown
|
<PlatformDropdown
|
||||||
groups={optionGroups}
|
groups={optionGroups}
|
||||||
|
|||||||
@@ -96,23 +96,14 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
|
|
||||||
const queryParams = new URLSearchParams({
|
const queryParams = new URLSearchParams({
|
||||||
itemId: item.Id!,
|
itemId: item.Id!,
|
||||||
|
audioIndex: selectedOptions.audioIndex?.toString() ?? "",
|
||||||
|
subtitleIndex: selectedOptions.subtitleIndex?.toString() ?? "",
|
||||||
mediaSourceId: selectedOptions.mediaSource?.Id ?? "",
|
mediaSourceId: selectedOptions.mediaSource?.Id ?? "",
|
||||||
bitrateValue: selectedOptions.bitrate?.value?.toString() ?? "",
|
bitrateValue: selectedOptions.bitrate?.value?.toString() ?? "",
|
||||||
playbackPosition: item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
playbackPosition: item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
||||||
offline: isOffline ? "true" : "false",
|
offline: isOffline ? "true" : "false",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (selectedOptions.audioIndex !== undefined) {
|
|
||||||
queryParams.set("audioIndex", selectedOptions.audioIndex.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedOptions.subtitleIndex !== undefined) {
|
|
||||||
queryParams.set(
|
|
||||||
"subtitleIndex",
|
|
||||||
selectedOptions.subtitleIndex.toString(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryString = queryParams.toString();
|
const queryString = queryParams.toString();
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
@@ -301,29 +292,6 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
t,
|
t,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const buildOfflineQueryParams = useCallback(
|
|
||||||
(downloadedItem: NonNullable<ReturnType<typeof getDownloadedItemById>>) => {
|
|
||||||
const isTranscoded = downloadedItem.userData?.isTranscoded === true;
|
|
||||||
const audioIdx = isTranscoded
|
|
||||||
? downloadedItem.userData?.audioStreamIndex
|
|
||||||
: selectedOptions.audioIndex;
|
|
||||||
const subtitleIdx = isTranscoded
|
|
||||||
? downloadedItem.userData?.subtitleStreamIndex
|
|
||||||
: selectedOptions.subtitleIndex;
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
itemId: item.Id!,
|
|
||||||
offline: "true",
|
|
||||||
playbackPosition:
|
|
||||||
item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
|
||||||
});
|
|
||||||
if (audioIdx !== undefined) params.set("audioIndex", audioIdx.toString());
|
|
||||||
if (subtitleIdx !== undefined)
|
|
||||||
params.set("subtitleIndex", subtitleIdx.toString());
|
|
||||||
return params;
|
|
||||||
},
|
|
||||||
[item, selectedOptions],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onPress = useCallback(async () => {
|
const onPress = useCallback(async () => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
@@ -334,7 +302,13 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
|
|
||||||
// If already in offline mode, play downloaded file directly
|
// If already in offline mode, play downloaded file directly
|
||||||
if (isOffline && downloadedItem) {
|
if (isOffline && downloadedItem) {
|
||||||
goToPlayer(buildOfflineQueryParams(downloadedItem).toString());
|
const queryParams = new URLSearchParams({
|
||||||
|
itemId: item.Id!,
|
||||||
|
offline: "true",
|
||||||
|
playbackPosition:
|
||||||
|
item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
||||||
|
});
|
||||||
|
goToPlayer(queryParams.toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,9 +331,13 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
<Button
|
<Button
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
hideModal();
|
hideModal();
|
||||||
goToPlayer(
|
const queryParams = new URLSearchParams({
|
||||||
buildOfflineQueryParams(downloadedItem).toString(),
|
itemId: item.Id!,
|
||||||
);
|
offline: "true",
|
||||||
|
playbackPosition:
|
||||||
|
item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
||||||
|
});
|
||||||
|
goToPlayer(queryParams.toString());
|
||||||
}}
|
}}
|
||||||
color='purple'
|
color='purple'
|
||||||
>
|
>
|
||||||
@@ -396,7 +374,13 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
{
|
{
|
||||||
text: t("player.downloaded_file_yes"),
|
text: t("player.downloaded_file_yes"),
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
goToPlayer(buildOfflineQueryParams(downloadedItem).toString());
|
const queryParams = new URLSearchParams({
|
||||||
|
itemId: item.Id!,
|
||||||
|
offline: "true",
|
||||||
|
playbackPosition:
|
||||||
|
item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
||||||
|
});
|
||||||
|
goToPlayer(queryParams.toString());
|
||||||
},
|
},
|
||||||
isPreferred: true,
|
isPreferred: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import useRouter from "@/hooks/useAppRouter";
|
import useRouter from "@/hooks/useAppRouter";
|
||||||
import { getDownloadedItemById } from "@/providers/Downloads/database";
|
|
||||||
import { usePlaySettings } from "@/providers/PlaySettingsProvider";
|
import { usePlaySettings } from "@/providers/PlaySettingsProvider";
|
||||||
import { writeToLog } from "@/utils/log";
|
import { writeToLog } from "@/utils/log";
|
||||||
|
|
||||||
@@ -16,27 +15,12 @@ export const useDownloadedFileOpener = () => {
|
|||||||
console.error("Attempted to open a file without an ID.");
|
console.error("Attempted to open a file without an ID.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const downloadedItem = getDownloadedItemById(item.Id);
|
|
||||||
const queryParams = new URLSearchParams({
|
const queryParams = new URLSearchParams({
|
||||||
itemId: item.Id,
|
itemId: item.Id,
|
||||||
offline: "true",
|
offline: "true",
|
||||||
playbackPosition:
|
playbackPosition:
|
||||||
item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (downloadedItem?.userData?.audioStreamIndex !== undefined) {
|
|
||||||
queryParams.set(
|
|
||||||
"audioIndex",
|
|
||||||
downloadedItem.userData.audioStreamIndex.toString(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (downloadedItem?.userData?.subtitleStreamIndex !== undefined) {
|
|
||||||
queryParams.set(
|
|
||||||
"subtitleIndex",
|
|
||||||
downloadedItem.userData.subtitleStreamIndex.toString(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
router.push(`/player/direct-player?${queryParams.toString()}`);
|
router.push(`/player/direct-player?${queryParams.toString()}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -186,20 +186,6 @@ export const usePlaybackManager = ({
|
|||||||
: playedPercentage,
|
: playedPercentage,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Sync selected audio/subtitle tracks so next playback resumes with
|
|
||||||
// the same tracks the user had active — but only for non-transcoded
|
|
||||||
// downloads where the user can freely switch tracks.
|
|
||||||
userData: localItem.userData.isTranscoded
|
|
||||||
? localItem.userData
|
|
||||||
: {
|
|
||||||
...localItem.userData,
|
|
||||||
audioStreamIndex:
|
|
||||||
playbackProgressInfo.AudioStreamIndex ??
|
|
||||||
localItem.userData.audioStreamIndex,
|
|
||||||
subtitleStreamIndex:
|
|
||||||
playbackProgressInfo.SubtitleStreamIndex ??
|
|
||||||
localItem.userData.subtitleStreamIndex,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
// Force invalidate queries so they refetch from updated local database
|
// Force invalidate queries so they refetch from updated local database
|
||||||
queryClient.invalidateQueries({ queryKey: ["item", itemId] });
|
queryClient.invalidateQueries({ queryKey: ["item", itemId] });
|
||||||
|
|||||||
@@ -127,7 +127,7 @@
|
|||||||
"@types/react": "~19.2.10",
|
"@types/react": "~19.2.10",
|
||||||
"@types/react-test-renderer": "19.1.0",
|
"@types/react-test-renderer": "19.1.0",
|
||||||
"cross-env": "10.1.0",
|
"cross-env": "10.1.0",
|
||||||
"expo-doctor": "1.19.7",
|
"expo-doctor": "1.19.8",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
"lint-staged": "17.0.5",
|
"lint-staged": "17.0.5",
|
||||||
"react-test-renderer": "19.2.3",
|
"react-test-renderer": "19.2.3",
|
||||||
|
|||||||
Reference in New Issue
Block a user