fix(player): handle remote streams and live tv containers correctly

This commit is contained in:
Fredrik Burmester
2026-01-28 19:40:18 +01:00
parent 05a2627c94
commit 9763c26046
2 changed files with 46 additions and 8 deletions

View File

@@ -262,6 +262,7 @@ export default function page() {
mediaSource: MediaSourceInfo;
sessionId: string;
url: string;
requiredHttpHeaders?: Record<string, string>;
}
const [stream, setStream] = useState<Stream | null>(null);
@@ -324,7 +325,7 @@ export default function page() {
deviceProfile: generateDeviceProfile(),
});
if (!res) return null;
const { mediaSource, sessionId, url } = res;
const { mediaSource, sessionId, url, requiredHttpHeaders } = res;
if (!sessionId || !mediaSource || !url) {
Alert.alert(
@@ -333,7 +334,7 @@ export default function page() {
);
return null;
}
result = { mediaSource, sessionId, url };
result = { mediaSource, sessionId, url, requiredHttpHeaders };
}
setStream(result);
setStreamStatus({ isLoading: false, isError: false });
@@ -601,17 +602,32 @@ export default function page() {
source.externalSubtitles = externalSubs;
}
// Add auth headers only for online streaming (not for local file:// URLs)
if (!offline && api?.accessToken) {
source.headers = {
Authorization: `MediaBrowser Token="${api.accessToken}"`,
};
// Add headers for online streaming (not for local file:// URLs)
if (!offline) {
const headers: Record<string, string> = {};
const isRemoteStream =
mediaSource?.IsRemote && mediaSource?.Protocol === "Http";
// Add auth header only for Jellyfin API requests (not for external/remote streams)
if (api?.accessToken && !isRemoteStream) {
headers.Authorization = `MediaBrowser Token="${api.accessToken}"`;
}
// Add any required headers from the media source (e.g., for external/remote streams)
if (stream?.requiredHttpHeaders) {
Object.assign(headers, stream.requiredHttpHeaders);
}
if (Object.keys(headers).length > 0) {
source.headers = headers;
}
}
return source;
}, [
stream?.url,
stream?.mediaSource,
stream?.requiredHttpHeaders,
item?.UserData?.PlaybackPositionTicks,
playbackPositionFromUrl,
api?.basePath,

View File

@@ -12,6 +12,7 @@ interface StreamResult {
url: string;
sessionId: string | null;
mediaSource: MediaSourceInfo | undefined;
requiredHttpHeaders?: Record<string, string>;
}
/**
@@ -50,10 +51,24 @@ const getPlaybackUrl = (
return `${api.basePath}${transcodeUrl}`;
}
// Handle remote/external streams (like live TV with external URLs)
// These have Protocol "Http" and IsRemote true, with the actual URL in Path
if (
mediaSource?.IsRemote &&
mediaSource?.Protocol === "Http" &&
mediaSource?.Path
) {
console.log("Video is remote stream, using direct Path:", mediaSource.Path);
return mediaSource.Path;
}
// Fall back to direct play
// Use the mediaSource's actual container when available (important for live TV
// where the container may be ts/hls, not mp4)
const container = params.container || mediaSource?.Container || "mp4";
const streamParams = new URLSearchParams({
static: params.static || "true",
container: params.container || "mp4",
container,
mediaSourceId: mediaSource?.Id || "",
subtitleStreamIndex: params.subtitleStreamIndex?.toString() || "",
audioStreamIndex: params.audioStreamIndex?.toString() || "",
@@ -163,6 +178,7 @@ export const getStreamUrl = async ({
url: string | null;
sessionId: string | null;
mediaSource: MediaSourceInfo | undefined;
requiredHttpHeaders?: Record<string, string>;
} | null> => {
if (!api || !userId || !item?.Id) {
console.warn("Missing required parameters for getStreamUrl");
@@ -210,6 +226,9 @@ export const getStreamUrl = async ({
url,
sessionId: sessionId || null,
mediaSource,
requiredHttpHeaders: mediaSource?.RequiredHttpHeaders as
| Record<string, string>
| undefined,
};
}
@@ -254,6 +273,9 @@ export const getStreamUrl = async ({
url,
sessionId: sessionId || null,
mediaSource,
requiredHttpHeaders: mediaSource?.RequiredHttpHeaders as
| Record<string, string>
| undefined,
};
};