mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-15 23:59:08 +00:00
fix(player): audio and subtitle dropdown not visible in player
This commit is contained in:
@@ -833,6 +833,7 @@ export default function page() {
|
|||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
setIsBuffering(false);
|
setIsBuffering(false);
|
||||||
setHasPlaybackStarted(true);
|
setHasPlaybackStarted(true);
|
||||||
|
setTracksReady(true); // VLC tracks are ready when playback starts
|
||||||
if (item?.Id) {
|
if (item?.Id) {
|
||||||
playbackManager.reportPlaybackProgress(
|
playbackManager.reportPlaybackProgress(
|
||||||
currentPlayStateInfo() as PlaybackProgressInfo,
|
currentPlayStateInfo() as PlaybackProgressInfo,
|
||||||
@@ -1053,6 +1054,7 @@ export default function page() {
|
|||||||
mediaSource={stream?.mediaSource}
|
mediaSource={stream?.mediaSource}
|
||||||
isVideoLoaded={isVideoLoaded}
|
isVideoLoaded={isVideoLoaded}
|
||||||
tracksReady={tracksReady}
|
tracksReady={tracksReady}
|
||||||
|
useVlcPlayer={useVlcPlayer}
|
||||||
>
|
>
|
||||||
<VideoProvider>
|
<VideoProvider>
|
||||||
<View
|
<View
|
||||||
@@ -1082,8 +1084,9 @@ export default function page() {
|
|||||||
onVideoStateChange={onPlaybackStateChangedVlc}
|
onVideoStateChange={onPlaybackStateChangedVlc}
|
||||||
onPipStarted={onPipStartedVlc}
|
onPipStarted={onPipStartedVlc}
|
||||||
onVideoLoadEnd={() => {
|
onVideoLoadEnd={() => {
|
||||||
|
// Note: VLC only fires this on error, not on successful load
|
||||||
|
// tracksReady is set in onPlaybackStateChangedVlc when state is "Playing"
|
||||||
setIsVideoLoaded(true);
|
setIsVideoLoaded(true);
|
||||||
setTracksReady(true);
|
|
||||||
}}
|
}}
|
||||||
onVideoError={(e: PlaybackStatePayload) => {
|
onVideoError={(e: PlaybackStatePayload) => {
|
||||||
console.error("Video Error:", e.nativeEvent);
|
console.error("Video Error:", e.nativeEvent);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ interface PlayerContextProps {
|
|||||||
mediaSource: MediaSourceInfo | null | undefined;
|
mediaSource: MediaSourceInfo | null | undefined;
|
||||||
isVideoLoaded: boolean;
|
isVideoLoaded: boolean;
|
||||||
tracksReady: boolean;
|
tracksReady: boolean;
|
||||||
|
useVlcPlayer: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PlayerContext = createContext<PlayerContextProps | undefined>(undefined);
|
const PlayerContext = createContext<PlayerContextProps | undefined>(undefined);
|
||||||
@@ -31,6 +32,7 @@ interface PlayerProviderProps {
|
|||||||
mediaSource: MediaSourceInfo | null | undefined;
|
mediaSource: MediaSourceInfo | null | undefined;
|
||||||
isVideoLoaded: boolean;
|
isVideoLoaded: boolean;
|
||||||
tracksReady: boolean;
|
tracksReady: boolean;
|
||||||
|
useVlcPlayer: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PlayerProvider: React.FC<PlayerProviderProps> = ({
|
export const PlayerProvider: React.FC<PlayerProviderProps> = ({
|
||||||
@@ -40,10 +42,18 @@ export const PlayerProvider: React.FC<PlayerProviderProps> = ({
|
|||||||
mediaSource,
|
mediaSource,
|
||||||
isVideoLoaded,
|
isVideoLoaded,
|
||||||
tracksReady,
|
tracksReady,
|
||||||
|
useVlcPlayer,
|
||||||
}) => {
|
}) => {
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() => ({ playerRef, item, mediaSource, isVideoLoaded, tracksReady }),
|
() => ({
|
||||||
[playerRef, item, mediaSource, isVideoLoaded, tracksReady],
|
playerRef,
|
||||||
|
item,
|
||||||
|
mediaSource,
|
||||||
|
isVideoLoaded,
|
||||||
|
tracksReady,
|
||||||
|
useVlcPlayer,
|
||||||
|
}),
|
||||||
|
[playerRef, item, mediaSource, isVideoLoaded, tracksReady, useVlcPlayer],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ import {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import type { SfAudioTrack } from "@/modules";
|
import type { SfAudioTrack, TrackInfo } from "@/modules";
|
||||||
import { isImageBasedSubtitle } from "@/utils/jellyfin/subtitleUtils";
|
import { isImageBasedSubtitle } from "@/utils/jellyfin/subtitleUtils";
|
||||||
import type { Track } from "../types";
|
import type { Track } from "../types";
|
||||||
import { usePlayerContext, usePlayerControls } from "./PlayerContext";
|
import { usePlayerContext, usePlayerControls } from "./PlayerContext";
|
||||||
@@ -75,7 +75,7 @@ export const VideoProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
const [subtitleTracks, setSubtitleTracks] = useState<Track[] | null>(null);
|
const [subtitleTracks, setSubtitleTracks] = useState<Track[] | null>(null);
|
||||||
const [audioTracks, setAudioTracks] = useState<Track[] | null>(null);
|
const [audioTracks, setAudioTracks] = useState<Track[] | null>(null);
|
||||||
|
|
||||||
const { tracksReady, mediaSource } = usePlayerContext();
|
const { tracksReady, mediaSource, useVlcPlayer } = usePlayerContext();
|
||||||
const playerControls = usePlayerControls();
|
const playerControls = usePlayerControls();
|
||||||
|
|
||||||
const { itemId, audioIndex, bitrateValue, subtitleIndex, playbackPosition } =
|
const { itemId, audioIndex, bitrateValue, subtitleIndex, playbackPosition } =
|
||||||
@@ -131,6 +131,94 @@ export const VideoProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
if (!tracksReady) return;
|
if (!tracksReady) return;
|
||||||
|
|
||||||
const fetchTracks = async () => {
|
const fetchTracks = async () => {
|
||||||
|
// For VLC player, use simpler track handling with server indices
|
||||||
|
if (useVlcPlayer) {
|
||||||
|
// Get VLC track info (VLC returns TrackInfo[] with 'index' property)
|
||||||
|
const vlcSubtitleData = (await playerControls
|
||||||
|
.getSubtitleTracks()
|
||||||
|
.catch(() => null)) as TrackInfo[] | null;
|
||||||
|
const vlcAudioData = (await playerControls
|
||||||
|
.getAudioTracks()
|
||||||
|
.catch(() => null)) as TrackInfo[] | null;
|
||||||
|
|
||||||
|
// VLC reverses HLS subtitles during transcoding
|
||||||
|
let vlcSubs: TrackInfo[] = vlcSubtitleData ? [...vlcSubtitleData] : [];
|
||||||
|
if (isTranscoding && vlcSubs.length > 1) {
|
||||||
|
vlcSubs = [vlcSubs[0], ...vlcSubs.slice(1).reverse()];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build subtitle tracks for VLC
|
||||||
|
const subs: Track[] = [];
|
||||||
|
let vlcSubIndex = 1; // VLC track indices start at 1 (0 is usually "Disable")
|
||||||
|
|
||||||
|
for (const sub of allSubs) {
|
||||||
|
const isTextBased =
|
||||||
|
sub.DeliveryMethod === SubtitleDeliveryMethod.Embed ||
|
||||||
|
sub.DeliveryMethod === SubtitleDeliveryMethod.Hls ||
|
||||||
|
sub.DeliveryMethod === SubtitleDeliveryMethod.External;
|
||||||
|
|
||||||
|
// Get VLC's internal index for this track
|
||||||
|
const vlcTrackIndex = vlcSubs[vlcSubIndex]?.index ?? -1;
|
||||||
|
if (isTextBased) vlcSubIndex++;
|
||||||
|
|
||||||
|
// For image-based subs during transcoding, or non-text subs, use replacePlayer
|
||||||
|
const needsPlayerRefresh =
|
||||||
|
(isTranscoding && isImageBasedSubtitle(sub)) || !isTextBased;
|
||||||
|
|
||||||
|
subs.push({
|
||||||
|
name: sub.DisplayTitle || "Unknown",
|
||||||
|
index: sub.Index ?? -1,
|
||||||
|
mpvIndex: vlcTrackIndex,
|
||||||
|
setTrack: () => {
|
||||||
|
if (needsPlayerRefresh) {
|
||||||
|
replacePlayer({ subtitleIndex: String(sub.Index) });
|
||||||
|
} else if (vlcTrackIndex !== -1) {
|
||||||
|
playerControls.setSubtitleTrack(vlcTrackIndex);
|
||||||
|
router.setParams({ subtitleIndex: String(sub.Index) });
|
||||||
|
} else {
|
||||||
|
replacePlayer({ subtitleIndex: String(sub.Index) });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add "Disable" option
|
||||||
|
subs.unshift({
|
||||||
|
name: "Disable",
|
||||||
|
index: -1,
|
||||||
|
mpvIndex: -1,
|
||||||
|
setTrack: () => {
|
||||||
|
playerControls.setSubtitleTrack(-1);
|
||||||
|
router.setParams({ subtitleIndex: "-1" });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build audio tracks for VLC
|
||||||
|
const vlcAudio: TrackInfo[] = vlcAudioData ? [...vlcAudioData] : [];
|
||||||
|
const audio: Track[] = allAudio.map((a, idx) => {
|
||||||
|
const vlcTrackIndex = vlcAudio[idx + 1]?.index ?? idx;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: a.DisplayTitle || "Unknown",
|
||||||
|
index: a.Index ?? -1,
|
||||||
|
mpvIndex: vlcTrackIndex,
|
||||||
|
setTrack: () => {
|
||||||
|
if (isTranscoding) {
|
||||||
|
replacePlayer({ audioIndex: String(a.Index) });
|
||||||
|
} else {
|
||||||
|
playerControls.setAudioTrack(vlcTrackIndex);
|
||||||
|
router.setParams({ audioIndex: String(a.Index) });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setSubtitleTracks(subs.sort((a, b) => a.index - b.index));
|
||||||
|
setAudioTracks(audio);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// KSPlayer track handling (original logic)
|
||||||
const audioData = await playerControls.getAudioTracks().catch(() => null);
|
const audioData = await playerControls.getAudioTracks().catch(() => null);
|
||||||
const playerAudio = (audioData as SfAudioTrack[]) ?? [];
|
const playerAudio = (audioData as SfAudioTrack[]) ?? [];
|
||||||
|
|
||||||
@@ -259,7 +347,7 @@ export const VideoProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchTracks();
|
fetchTracks();
|
||||||
}, [tracksReady, mediaSource]);
|
}, [tracksReady, mediaSource, useVlcPlayer]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VideoContext.Provider value={{ subtitleTracks, audioTracks }}>
|
<VideoContext.Provider value={{ subtitleTracks, audioTracks }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user