mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-30 17:42:51 +01:00
fix(subtitles): order detail-page & TV subtitle menus like jellyfin-web
The detail-page selector (MediaSourceButton, the #1176 replacement for TrackSheet) and the TV detail/refresh paths (ItemContent.tv, Controls.tv refreshSubtitleTracks) still listed subtitles in raw MediaStreams order (externals first). Apply compareTracksForMenu there too so every menu matches web. The in-player TV modal was already covered (fed from the sorted VideoContext tracks).
This commit is contained in:
@@ -56,6 +56,7 @@ import { useSettings } from "@/utils/atoms/settings";
|
|||||||
import type { TVOptionItem } from "@/utils/atoms/tvOptionModal";
|
import type { TVOptionItem } from "@/utils/atoms/tvOptionModal";
|
||||||
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
|
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
|
||||||
import { getPrimaryImageUrlById } from "@/utils/jellyfin/image/getPrimaryImageUrlById";
|
import { getPrimaryImageUrlById } from "@/utils/jellyfin/image/getPrimaryImageUrlById";
|
||||||
|
import { compareTracksForMenu } from "@/utils/jellyfin/subtitleUtils";
|
||||||
import { formatDuration, runtimeTicksToMinutes } from "@/utils/time";
|
import { formatDuration, runtimeTicksToMinutes } from "@/utils/time";
|
||||||
|
|
||||||
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get("window");
|
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get("window");
|
||||||
@@ -232,12 +233,13 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
|
|||||||
return streams ?? [];
|
return streams ?? [];
|
||||||
}, [selectedOptions?.mediaSource]);
|
}, [selectedOptions?.mediaSource]);
|
||||||
|
|
||||||
// Get available subtitle tracks (raw MediaStream[] for label lookup)
|
// Get available subtitle tracks (raw MediaStream[] for label lookup),
|
||||||
|
// ordered like jellyfin-web (embedded first, externals last, forced/default up).
|
||||||
const subtitleStreams = useMemo(() => {
|
const subtitleStreams = useMemo(() => {
|
||||||
const streams = selectedOptions?.mediaSource?.MediaStreams?.filter(
|
const streams = selectedOptions?.mediaSource?.MediaStreams?.filter(
|
||||||
(s) => s.Type === "Subtitle",
|
(s) => s.Type === "Subtitle",
|
||||||
);
|
);
|
||||||
return streams ?? [];
|
return streams ? [...streams].sort(compareTracksForMenu) : [];
|
||||||
}, [selectedOptions?.mediaSource]);
|
}, [selectedOptions?.mediaSource]);
|
||||||
|
|
||||||
// Store handleSubtitleChange in a ref for stable callback reference
|
// Store handleSubtitleChange in a ref for stable callback reference
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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 { compareTracksForMenu } from "@/utils/jellyfin/subtitleUtils";
|
||||||
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";
|
||||||
@@ -63,9 +64,12 @@ export const MediaSourceButton: React.FC<Props> = ({
|
|||||||
|
|
||||||
const subtitleStreams = useMemo(
|
const subtitleStreams = useMemo(
|
||||||
() =>
|
() =>
|
||||||
selectedOptions.mediaSource?.MediaStreams?.filter(
|
// Order like jellyfin-web (embedded first, externals last, forced/default up).
|
||||||
(x) => x.Type === "Subtitle",
|
[
|
||||||
) || [],
|
...(selectedOptions.mediaSource?.MediaStreams?.filter(
|
||||||
|
(x) => x.Type === "Subtitle",
|
||||||
|
) || []),
|
||||||
|
].sort(compareTracksForMenu),
|
||||||
[selectedOptions.mediaSource],
|
[selectedOptions.mediaSource],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import { useOfflineMode } from "@/providers/OfflineModeProvider";
|
|||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
import type { TVOptionItem } from "@/utils/atoms/tvOptionModal";
|
import type { TVOptionItem } from "@/utils/atoms/tvOptionModal";
|
||||||
import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings";
|
import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings";
|
||||||
|
import { compareTracksForMenu } from "@/utils/jellyfin/subtitleUtils";
|
||||||
import { formatTimeString, msToTicks, ticksToMs } from "@/utils/time";
|
import { formatTimeString, msToTicks, ticksToMs } from "@/utils/time";
|
||||||
import { CONTROLS_CONSTANTS } from "./constants";
|
import { CONTROLS_CONSTANTS } from "./constants";
|
||||||
import { useVideoContext } from "./contexts/VideoContext";
|
import { useVideoContext } from "./contexts/VideoContext";
|
||||||
@@ -317,8 +318,10 @@ export const Controls: FC<Props> = ({
|
|||||||
try {
|
try {
|
||||||
const streams = (await onRefreshSubtitleTracks?.()) ?? [];
|
const streams = (await onRefreshSubtitleTracks?.()) ?? [];
|
||||||
// Skip streams without a real index: `?? -1` would alias them to the
|
// Skip streams without a real index: `?? -1` would alias them to the
|
||||||
// "disable subtitles" sentinel and mis-route selection.
|
// "disable subtitles" sentinel and mis-route selection. Order like
|
||||||
return streams
|
// jellyfin-web (embedded first, externals last, forced/default up).
|
||||||
|
return [...streams]
|
||||||
|
.sort(compareTracksForMenu)
|
||||||
.filter((stream) => typeof stream.Index === "number")
|
.filter((stream) => typeof stream.Index === "number")
|
||||||
.map((stream) => {
|
.map((stream) => {
|
||||||
const index = stream.Index as number;
|
const index = stream.Index as number;
|
||||||
|
|||||||
Reference in New Issue
Block a user