From 0cc3a8469d6e11c4330a3b229dc105309479377d Mon Sep 17 00:00:00 2001 From: Uruk Date: Fri, 22 May 2026 02:20:51 +0200 Subject: [PATCH] fix(casting): report the real PlayMethod to Jellyfin --- hooks/useCasting.ts | 23 ++++++++++++++++++----- utils/casting/castLoad.ts | 6 ++++++ utils/casting/mediaInfo.ts | 5 +++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/hooks/useCasting.ts b/hooks/useCasting.ts index cfaecdee2..05a32a220 100644 --- a/hooks/useCasting.ts +++ b/hooks/useCasting.ts @@ -60,6 +60,13 @@ export const useCasting = (item: BaseItemDto | null) => { | undefined )?.playSessionId ?? mediaStatus?.mediaInfo?.contentId; + const playMethod = + ( + mediaStatus?.mediaInfo?.customData as + | { playMethod?: "Transcode" | "DirectPlay" } + | undefined + )?.playMethod ?? "Transcode"; + // Detect which protocol is active - use CastState for reliable detection const chromecastConnected = castState === CastState.CONNECTED; // Future: Add detection for other protocols here @@ -143,8 +150,7 @@ export const useCasting = (item: BaseItemDto | null) => { playbackStartInfo: { ItemId: item.Id, PositionTicks: Math.floor(currentState.progress * 10000), - PlayMethod: - activeProtocol === "chromecast" ? "DirectStream" : "DirectPlay", + PlayMethod: playMethod, VolumeLevel: Math.floor(currentState.volume * 100), IsMuted: currentState.volume === 0, PlaySessionId: playSessionId, @@ -183,8 +189,7 @@ export const useCasting = (item: BaseItemDto | null) => { ItemId: item.Id, PositionTicks: progressTicks, IsPaused: !s.isPlaying, - PlayMethod: - activeProtocol === "chromecast" ? "DirectStream" : "DirectPlay", + PlayMethod: playMethod, VolumeLevel: Math.floor(s.volume * 100), IsMuted: s.volume === 0, PlaySessionId: playSessionId, @@ -198,7 +203,15 @@ export const useCasting = (item: BaseItemDto | null) => { // Report progress on a fixed interval, reading latest state from ref const interval = setInterval(reportProgress, 10000); return () => clearInterval(interval); - }, [api, item?.Id, user?.Id, isConnected, activeProtocol, playSessionId]); + }, [ + api, + item?.Id, + user?.Id, + isConnected, + activeProtocol, + playSessionId, + playMethod, + ]); // Play/Pause controls const play = useCallback(async () => { diff --git a/utils/casting/castLoad.ts b/utils/casting/castLoad.ts index 75e68e399..dced70591 100644 --- a/utils/casting/castLoad.ts +++ b/utils/casting/castLoad.ts @@ -75,6 +75,11 @@ const attemptLoad = async ( throw new Error("getStreamUrl returned no URL"); } + const playMethod: "Transcode" | "DirectPlay" = data.mediaSource + ?.TranscodingUrl + ? "Transcode" + : "DirectPlay"; + await client.loadMedia({ mediaInfo: buildCastMediaInfo({ item, @@ -82,6 +87,7 @@ const attemptLoad = async ( api, playSessionId: data.sessionId ?? undefined, selection, + playMethod, }), startTime: startPositionMs / 1000, }); diff --git a/utils/casting/mediaInfo.ts b/utils/casting/mediaInfo.ts index 83d123efa..eb854b7fe 100644 --- a/utils/casting/mediaInfo.ts +++ b/utils/casting/mediaInfo.ts @@ -26,6 +26,7 @@ export const buildCastMediaInfo = ({ isLive = false, playSessionId, selection, + playMethod, }: { item: BaseItemDto; streamUrl: string; @@ -38,6 +39,8 @@ export const buildCastMediaInfo = ({ playSessionId?: string; /** Active track / quality / version selection, embedded in customData. */ selection?: CastSelection; + /** "Transcode" when the stream is a server transcode, else "DirectPlay". */ + playMethod?: "Transcode" | "DirectPlay"; }) => { if (!item.Id) { throw new Error("Missing item.Id for media load — cannot build contentId"); @@ -94,9 +97,11 @@ export const buildCastMediaInfo = ({ const slimCustomData: Partial & { playSessionId?: string; selection?: CastSelection; + playMethod?: "Transcode" | "DirectPlay"; } = { playSessionId, selection, + playMethod, Id: item.Id, Name: item.Name, Type: item.Type,