mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-01 03:28:27 +01:00
Compare commits
8 Commits
sync-subti
...
renovate/x
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b8478512e | ||
|
|
fa1c3f3947 | ||
|
|
2761de5a74 | ||
|
|
feca1d7e9c | ||
|
|
6b6bfd1a89 | ||
|
|
d585b20f49 | ||
|
|
692ccfdb2c | ||
|
|
86e39c444c |
8
.github/workflows/build-apps.yml
vendored
8
.github/workflows/build-apps.yml
vendored
@@ -219,7 +219,7 @@ jobs:
|
|||||||
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
||||||
with:
|
with:
|
||||||
# renovate: datasource=custom.xcode depName=xcode versioning=loose
|
# renovate: datasource=custom.xcode depName=xcode versioning=loose
|
||||||
xcode-version: "26.4"
|
xcode-version: "26.5"
|
||||||
|
|
||||||
- name: 🏗️ Setup EAS
|
- name: 🏗️ Setup EAS
|
||||||
uses: expo/expo-github-action@b184ff86a3c926240f1b6db41764c83a01c02eef # main
|
uses: expo/expo-github-action@b184ff86a3c926240f1b6db41764c83a01c02eef # main
|
||||||
@@ -284,7 +284,7 @@ jobs:
|
|||||||
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
||||||
with:
|
with:
|
||||||
# renovate: datasource=custom.xcode depName=xcode versioning=loose
|
# renovate: datasource=custom.xcode depName=xcode versioning=loose
|
||||||
xcode-version: "26.4"
|
xcode-version: "26.5"
|
||||||
|
|
||||||
- name: 🚀 Build iOS app
|
- name: 🚀 Build iOS app
|
||||||
env:
|
env:
|
||||||
@@ -344,7 +344,7 @@ jobs:
|
|||||||
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
||||||
with:
|
with:
|
||||||
# renovate: datasource=custom.xcode depName=xcode versioning=loose
|
# renovate: datasource=custom.xcode depName=xcode versioning=loose
|
||||||
xcode-version: "26.4"
|
xcode-version: "26.5"
|
||||||
|
|
||||||
- name: 🏗️ Setup EAS
|
- name: 🏗️ Setup EAS
|
||||||
uses: expo/expo-github-action@b184ff86a3c926240f1b6db41764c83a01c02eef # main
|
uses: expo/expo-github-action@b184ff86a3c926240f1b6db41764c83a01c02eef # main
|
||||||
@@ -412,7 +412,7 @@ jobs:
|
|||||||
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
||||||
with:
|
with:
|
||||||
# renovate: datasource=custom.xcode depName=xcode versioning=loose
|
# renovate: datasource=custom.xcode depName=xcode versioning=loose
|
||||||
xcode-version: "26.4"
|
xcode-version: "26.5"
|
||||||
|
|
||||||
- name: 🚀 Build iOS app
|
- name: 🚀 Build iOS app
|
||||||
env:
|
env:
|
||||||
|
|||||||
3
app.json
3
app.json
@@ -2,7 +2,7 @@
|
|||||||
"expo": {
|
"expo": {
|
||||||
"name": "Streamyfin",
|
"name": "Streamyfin",
|
||||||
"slug": "streamyfin",
|
"slug": "streamyfin",
|
||||||
"version": "0.54.0",
|
"version": "0.54.1",
|
||||||
"orientation": "default",
|
"orientation": "default",
|
||||||
"icon": "./assets/images/icon.png",
|
"icon": "./assets/images/icon.png",
|
||||||
"scheme": "streamyfin",
|
"scheme": "streamyfin",
|
||||||
@@ -36,7 +36,6 @@
|
|||||||
"appleTeamId": "MWD5K362T8"
|
"appleTeamId": "MWD5K362T8"
|
||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"versionCode": 93,
|
|
||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
"foregroundImage": "./assets/images/icon-android-plain.png",
|
"foregroundImage": "./assets/images/icon-android-plain.png",
|
||||||
"monochromeImage": "./assets/images/icon-android-themed.png",
|
"monochromeImage": "./assets/images/icon-android-themed.png",
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
5
bun.lock
5
bun.lock
@@ -114,11 +114,6 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
|
||||||
"react-native-ios-utilities@5.2.0": "bun-patches/react-native-ios-utilities@5.2.0.patch",
|
|
||||||
"react-native-udp@4.1.7": "bun-patches/react-native-udp@4.1.7.patch",
|
|
||||||
"react-native-bottom-tabs@1.2.0": "bun-patches/react-native-bottom-tabs@1.2.0.patch",
|
|
||||||
},
|
|
||||||
"packages": {
|
"packages": {
|
||||||
"@adobe/css-tools": ["@adobe/css-tools@4.5.0", "", {}, "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q=="],
|
"@adobe/css-tools": ["@adobe/css-tools@4.5.0", "", {}, "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q=="],
|
||||||
|
|
||||||
|
|||||||
@@ -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,7 +72,6 @@ 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({
|
groups.push({
|
||||||
title: t("item_card.quality"),
|
title: t("item_card.quality"),
|
||||||
@@ -112,7 +101,6 @@ export const MediaSourceButton: React.FC<Props> = ({
|
|||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Audio track group
|
// Audio track group
|
||||||
if (audioStreams.length > 0) {
|
if (audioStreams.length > 0) {
|
||||||
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -105,14 +105,14 @@ const AudioSlider: React.FC<AudioSliderProps> = ({ setVisibility }) => {
|
|||||||
maximumValue={max}
|
maximumValue={max}
|
||||||
thumbWidth={0}
|
thumbWidth={0}
|
||||||
onValueChange={handleValueChange}
|
onValueChange={handleValueChange}
|
||||||
|
renderBubble={() => null}
|
||||||
|
renderThumb={() => null}
|
||||||
containerStyle={{
|
containerStyle={{
|
||||||
borderRadius: 50,
|
borderRadius: 50,
|
||||||
}}
|
}}
|
||||||
theme={{
|
theme={{
|
||||||
minimumTrackTintColor: "#FDFDFD",
|
minimumTrackTintColor: "#FDFDFD",
|
||||||
maximumTrackTintColor: "#5A5A5A",
|
maximumTrackTintColor: "#5A5A5A",
|
||||||
bubbleBackgroundColor: "transparent", // Hide the value bubble
|
|
||||||
bubbleTextColor: "transparent", // Hide the value text
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
|
|||||||
@@ -88,14 +88,14 @@ const BrightnessSlider = () => {
|
|||||||
maximumValue={max}
|
maximumValue={max}
|
||||||
thumbWidth={0}
|
thumbWidth={0}
|
||||||
onValueChange={handleValueChange}
|
onValueChange={handleValueChange}
|
||||||
|
renderBubble={() => null}
|
||||||
|
renderThumb={() => null}
|
||||||
containerStyle={{
|
containerStyle={{
|
||||||
borderRadius: 50,
|
borderRadius: 50,
|
||||||
}}
|
}}
|
||||||
theme={{
|
theme={{
|
||||||
minimumTrackTintColor: "#FDFDFD",
|
minimumTrackTintColor: "#FDFDFD",
|
||||||
maximumTrackTintColor: "#5A5A5A",
|
maximumTrackTintColor: "#5A5A5A",
|
||||||
bubbleBackgroundColor: "transparent", // Hide the value bubble
|
|
||||||
bubbleTextColor: "transparent", // Hide the value text
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
|
|||||||
25
eas.json
25
eas.json
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"cli": {
|
"cli": {
|
||||||
"version": ">= 9.1.0"
|
"version": ">= 9.1.0",
|
||||||
|
"appVersionSource": "remote"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"development": {
|
"development": {
|
||||||
@@ -52,14 +53,14 @@
|
|||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"environment": "production",
|
"environment": "production",
|
||||||
"channel": "0.54.0",
|
"autoIncrement": true,
|
||||||
"android": {
|
"android": {
|
||||||
"image": "latest"
|
"image": "latest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"production-apk": {
|
"production-apk": {
|
||||||
"environment": "production",
|
"environment": "production",
|
||||||
"channel": "0.54.0",
|
"autoIncrement": true,
|
||||||
"android": {
|
"android": {
|
||||||
"buildType": "apk",
|
"buildType": "apk",
|
||||||
"image": "latest"
|
"image": "latest"
|
||||||
@@ -67,7 +68,7 @@
|
|||||||
},
|
},
|
||||||
"production-apk-tv": {
|
"production-apk-tv": {
|
||||||
"environment": "production",
|
"environment": "production",
|
||||||
"channel": "0.54.0",
|
"autoIncrement": true,
|
||||||
"android": {
|
"android": {
|
||||||
"buildType": "apk",
|
"buildType": "apk",
|
||||||
"image": "latest"
|
"image": "latest"
|
||||||
@@ -78,7 +79,7 @@
|
|||||||
},
|
},
|
||||||
"production_tv": {
|
"production_tv": {
|
||||||
"environment": "production",
|
"environment": "production",
|
||||||
"channel": "0.54.0",
|
"autoIncrement": true,
|
||||||
"env": {
|
"env": {
|
||||||
"EXPO_TV": "1"
|
"EXPO_TV": "1"
|
||||||
},
|
},
|
||||||
@@ -88,7 +89,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"submit": {
|
"submit": {
|
||||||
"production": {},
|
"production": {
|
||||||
"production_tv": {}
|
"ios": {
|
||||||
|
"appleTeamId": "MWD5K362T8",
|
||||||
|
"ascAppId": "6593660679"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"production_tv": {
|
||||||
|
"ios": {
|
||||||
|
"appleTeamId": "MWD5K362T8",
|
||||||
|
"ascAppId": "6593660679"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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] });
|
||||||
|
|||||||
@@ -715,9 +715,7 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
|||||||
// dropped), so we (re)apply here for embedded and external alike.
|
// dropped), so we (re)apply here for embedded and external alike.
|
||||||
// This is what makes a carried-over subtitle show up on the next
|
// This is what makes a carried-over subtitle show up on the next
|
||||||
// episode without a manual re-selection.
|
// episode without a manual re-selection.
|
||||||
if (initialAudioId != null && initialAudioId > 0) {
|
initialAudioId?.let { if (it > 0) setAudioTrack(it) }
|
||||||
setAudioTrack(initialAudioId)
|
|
||||||
}
|
|
||||||
initialSubtitleId?.let { setSubtitleTrack(it) } ?: disableSubtitles()
|
initialSubtitleId?.let { setSubtitleTrack(it) } ?: disableSubtitles()
|
||||||
|
|
||||||
if (!isReadyToSeek) {
|
if (!isReadyToSeek) {
|
||||||
|
|||||||
@@ -162,10 +162,5 @@
|
|||||||
},
|
},
|
||||||
"trustedDependencies": [
|
"trustedDependencies": [
|
||||||
"unrs-resolver"
|
"unrs-resolver"
|
||||||
],
|
]
|
||||||
"patchedDependencies": {
|
|
||||||
"react-native-udp@4.1.7": "bun-patches/react-native-udp@4.1.7.patch",
|
|
||||||
"react-native-bottom-tabs@1.2.0": "bun-patches/react-native-bottom-tabs@1.2.0.patch",
|
|
||||||
"react-native-ios-utilities@5.2.0": "bun-patches/react-native-ios-utilities@5.2.0.patch"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
diff --git a/node_modules/react-native-bottom-tabs/.bun-tag-b32ab1c60a5dfcf7 b/.bun-tag-b32ab1c60a5dfcf7
|
diff --git a/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift b/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift
|
||||||
new file mode 100644
|
|
||||||
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
|
|
||||||
diff --git a/ios/BottomAccessoryProvider.swift b/ios/BottomAccessoryProvider.swift
|
|
||||||
index 539efee7156599e1fc795e11bf411b7dfaf12ec7..b2af39a2e6b014e9b1ae0a51b21115c19280df69 100644
|
index 539efee7156599e1fc795e11bf411b7dfaf12ec7..b2af39a2e6b014e9b1ae0a51b21115c19280df69 100644
|
||||||
--- a/ios/BottomAccessoryProvider.swift
|
--- a/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift
|
||||||
+++ b/ios/BottomAccessoryProvider.swift
|
+++ b/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift
|
||||||
@@ -8,7 +8,7 @@ import SwiftUI
|
@@ -8,7 +8,7 @@ import SwiftUI
|
||||||
self.delegate = delegate
|
self.delegate = delegate
|
||||||
}
|
}
|
||||||
@@ -14,10 +11,10 @@ index 539efee7156599e1fc795e11bf411b7dfaf12ec7..b2af39a2e6b014e9b1ae0a51b21115c1
|
|||||||
@available(iOS 26.0, *)
|
@available(iOS 26.0, *)
|
||||||
public func emitPlacementChanged(_ placement: TabViewBottomAccessoryPlacement?) {
|
public func emitPlacementChanged(_ placement: TabViewBottomAccessoryPlacement?) {
|
||||||
var placementValue = "none"
|
var placementValue = "none"
|
||||||
diff --git a/ios/TabView/NewTabView.swift b/ios/TabView/NewTabView.swift
|
diff --git a/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift b/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift
|
||||||
index 22c52cdf25ad0f7398d89197cb431ca8dc8e0f99..81411376e68803de8bd83515d42565cfa95daf2b 100644
|
index 22c52cdf25ad0f7398d89197cb431ca8dc8e0f99..81411376e68803de8bd83515d42565cfa95daf2b 100644
|
||||||
--- a/ios/TabView/NewTabView.swift
|
--- a/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift
|
||||||
+++ b/ios/TabView/NewTabView.swift
|
+++ b/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift
|
||||||
@@ -78,11 +78,11 @@ struct ConditionalBottomAccessoryModifier: ViewModifier {
|
@@ -78,11 +78,11 @@ struct ConditionalBottomAccessoryModifier: ViewModifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,10 +53,10 @@ index 22c52cdf25ad0f7398d89197cb431ca8dc8e0f99..81411376e68803de8bd83515d42565cf
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
+
|
+
|
||||||
diff --git a/ios/TabViewImpl.swift b/ios/TabViewImpl.swift
|
diff --git a/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift b/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift
|
||||||
index 72938be90540ea3a483d7db9a80fb74c04d31272..277278ffdd9268a96cb09869eb1d0c0d5e6ad300 100644
|
index 72938be90540ea3a483d7db9a80fb74c04d31272..277278ffdd9268a96cb09869eb1d0c0d5e6ad300 100644
|
||||||
--- a/ios/TabViewImpl.swift
|
--- a/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift
|
||||||
+++ b/ios/TabViewImpl.swift
|
+++ b/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift
|
||||||
@@ -281,7 +281,7 @@ extension View {
|
@@ -281,7 +281,7 @@ extension View {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
@@ -69,10 +66,10 @@ index 72938be90540ea3a483d7db9a80fb74c04d31272..277278ffdd9268a96cb09869eb1d0c0d
|
|||||||
if #available(iOS 26.0, macOS 26.0, *) {
|
if #available(iOS 26.0, macOS 26.0, *) {
|
||||||
if let behavior {
|
if let behavior {
|
||||||
self.tabBarMinimizeBehavior(behavior.convert())
|
self.tabBarMinimizeBehavior(behavior.convert())
|
||||||
diff --git a/ios/TabViewProps.swift b/ios/TabViewProps.swift
|
diff --git a/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift b/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift
|
||||||
index 9cfb29a983b34d3f84fc7a678d19ef4ff30e0325..6a5854483e66200b71722bbac12e100742222bd3 100644
|
index 9cfb29a983b34d3f84fc7a678d19ef4ff30e0325..6a5854483e66200b71722bbac12e100742222bd3 100644
|
||||||
--- a/ios/TabViewProps.swift
|
--- a/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift
|
||||||
+++ b/ios/TabViewProps.swift
|
+++ b/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift
|
||||||
@@ -6,7 +6,7 @@ internal enum MinimizeBehavior: String {
|
@@ -6,7 +6,7 @@ internal enum MinimizeBehavior: String {
|
||||||
case onScrollUp
|
case onScrollUp
|
||||||
case onScrollDown
|
case onScrollDown
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
diff --git a/ios/Sources/Extensions+Helpers/RCTView+Helpers.swift b/ios/Sources/Extensions+Helpers/RCTView+Helpers.swift
|
diff --git a/node_modules/react-native-ios-utilities/ios/Sources/Extensions+Helpers/RCTView+Helpers.swift b/node_modules/react-native-ios-utilities/ios/Sources/Extensions+Helpers/RCTView+Helpers.swift
|
||||||
index 09be306d5aa39337c5114c2ad6ba7513218e0751..24ff8ee2c36fef8632a7e012514fd04db9bf89fd 100644
|
index 09be306d5aa39337c5114c2ad6ba7513218e0751..24ff8ee2c36fef8632a7e012514fd04db9bf89fd 100644
|
||||||
--- a/ios/Sources/Extensions+Helpers/RCTView+Helpers.swift
|
--- a/node_modules/react-native-ios-utilities/ios/Sources/Extensions+Helpers/RCTView+Helpers.swift
|
||||||
+++ b/ios/Sources/Extensions+Helpers/RCTView+Helpers.swift
|
+++ b/node_modules/react-native-ios-utilities/ios/Sources/Extensions+Helpers/RCTView+Helpers.swift
|
||||||
@@ -25,15 +25,14 @@ public extension RCTView {
|
@@ -25,15 +25,14 @@ public extension RCTView {
|
||||||
return rootView.recursivelyFindSubview(whereType: targetType);
|
return rootView.recursivelyFindSubview(whereType: targetType);
|
||||||
};
|
};
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
diff --git a/node_modules/react-native-udp/.bun-tag-ea7df8754aa4db91 b/.bun-tag-ea7df8754aa4db91
|
diff --git a/node_modules/react-native-udp/react-native-udp.podspec b/node_modules/react-native-udp/react-native-udp.podspec
|
||||||
new file mode 100644
|
|
||||||
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
|
|
||||||
diff --git a/react-native-udp.podspec b/react-native-udp.podspec
|
|
||||||
index 7450cc7d0862aadfb47d796929c801a3dc423a57..fa3e42c0152ef2d87536b8c2e484f64d525e35ec 100644
|
index 7450cc7d0862aadfb47d796929c801a3dc423a57..fa3e42c0152ef2d87536b8c2e484f64d525e35ec 100644
|
||||||
--- a/react-native-udp.podspec
|
--- a/node_modules/react-native-udp/react-native-udp.podspec
|
||||||
+++ b/react-native-udp.podspec
|
+++ b/node_modules/react-native-udp/react-native-udp.podspec
|
||||||
@@ -9,7 +9,8 @@ Pod::Spec.new do |s|
|
@@ -9,7 +9,8 @@ Pod::Spec.new do |s|
|
||||||
s.homepage = package_json["homepage"]
|
s.homepage = package_json["homepage"]
|
||||||
s.license = package_json["license"]
|
s.license = package_json["license"]
|
||||||
@@ -39,6 +39,28 @@ function buildPatch() {
|
|||||||
" end",
|
" end",
|
||||||
" end",
|
" end",
|
||||||
"",
|
"",
|
||||||
|
" # iOS 26 / Xcode 26: the APP target itself compiles ExpoModulesProvider.swift,",
|
||||||
|
" # which imports SwiftUI-based modules (ExpoUI, ExpoGlassEffect, GlassPoster, ExpoBlur, …).",
|
||||||
|
" # That emits a `-framework SwiftUICore` autolink into the app executable's OWN object",
|
||||||
|
" # files, so the pods-only flag above is not enough — the app's link still fails with",
|
||||||
|
" # `cannot link directly with 'SwiftUICore'`. Drop the autolink on the user app target",
|
||||||
|
" # too. Phone-only — tvOS has no SwiftUICore split and must stay untouched.",
|
||||||
|
" if ENV['EXPO_TV'] != '1'",
|
||||||
|
" installer.aggregate_targets.each do |agg|",
|
||||||
|
" next unless agg.user_project",
|
||||||
|
" agg.user_project.native_targets.each do |target|",
|
||||||
|
" target.build_configurations.each do |cfg|",
|
||||||
|
" existing = cfg.build_settings['OTHER_SWIFT_FLAGS'] || '$(inherited)'",
|
||||||
|
" existing = existing.join(' ') if existing.is_a?(Array)",
|
||||||
|
" unless existing.include?('-disable-autolink-framework -Xfrontend SwiftUICore')",
|
||||||
|
" cfg.build_settings['OTHER_SWIFT_FLAGS'] = existing + ' -Xfrontend -disable-autolink-framework -Xfrontend SwiftUICore'",
|
||||||
|
" end",
|
||||||
|
" end",
|
||||||
|
" end",
|
||||||
|
" agg.user_project.save",
|
||||||
|
" end",
|
||||||
|
" end",
|
||||||
|
"",
|
||||||
" # Safely patch RCTThirdPartyComponentsProvider.mm to avoid startup crash on unlinked Fabric components",
|
" # Safely patch RCTThirdPartyComponentsProvider.mm to avoid startup crash on unlinked Fabric components",
|
||||||
' filepath = "#{installer.sandbox.root}/../build/generated/ios/ReactCodegen/RCTThirdPartyComponentsProvider.mm"',
|
' filepath = "#{installer.sandbox.root}/../build/generated/ios/ReactCodegen/RCTThirdPartyComponentsProvider.mm"',
|
||||||
" if File.exist?(filepath)",
|
" if File.exist?(filepath)",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const initialApi = (() => {
|
|||||||
const id = getOrSetDeviceId();
|
const id = getOrSetDeviceId();
|
||||||
const deviceName = getDeviceNameSync();
|
const deviceName = getDeviceNameSync();
|
||||||
const jellyfinInstance = new Jellyfin({
|
const jellyfinInstance = new Jellyfin({
|
||||||
clientInfo: { name: "Streamyfin", version: "0.54.0" },
|
clientInfo: { name: "Streamyfin", version: "0.54.1" },
|
||||||
deviceInfo: {
|
deviceInfo: {
|
||||||
name: deviceName,
|
name: deviceName,
|
||||||
id,
|
id,
|
||||||
@@ -128,7 +128,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
const id = getOrSetDeviceId();
|
const id = getOrSetDeviceId();
|
||||||
const deviceName = getDeviceNameSync();
|
const deviceName = getDeviceNameSync();
|
||||||
return new Jellyfin({
|
return new Jellyfin({
|
||||||
clientInfo: { name: "Streamyfin", version: "0.54.0" },
|
clientInfo: { name: "Streamyfin", version: "0.54.1" },
|
||||||
deviceInfo: {
|
deviceInfo: {
|
||||||
name: deviceName,
|
name: deviceName,
|
||||||
id,
|
id,
|
||||||
@@ -162,7 +162,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
return {
|
return {
|
||||||
authorization: `MediaBrowser Client="Streamyfin", Device=${
|
authorization: `MediaBrowser Client="Streamyfin", Device=${
|
||||||
Platform.OS === "android" ? "Android" : "iOS"
|
Platform.OS === "android" ? "Android" : "iOS"
|
||||||
}, DeviceId="${deviceId}", Version="0.54.0"`,
|
}, DeviceId="${deviceId}", Version="0.54.1"`,
|
||||||
};
|
};
|
||||||
}, [deviceId]);
|
}, [deviceId]);
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,10 @@
|
|||||||
<string>$(MARKETING_VERSION)</string>
|
<string>$(MARKETING_VERSION)</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
|
<array>
|
||||||
|
<string>arm64</string>
|
||||||
|
</array>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionPointIdentifier</key>
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
|||||||
Reference in New Issue
Block a user