Files
streamyfin/utils/casting/mediaInfo.ts
Uruk 7c81c0ff33 Fix: Improves Chromecast casting experience
Fixes several issues and improves the overall Chromecast casting experience:

- Implements an AbortController for fetching item data to prevent race conditions.
- Syncs live progress in the mini player more accurately using elapsed real time.
- Prevents event propagation in the mini player's play/pause button.
- Ensures the disconnect callback in the connection menu is always called.
- Retries scrolling in the episode list on failure.
- Handles unmute failures gracefully in volume controls.
- Clamps seek positions to prevent exceeding duration.
- Fixes reporting playback start multiple times
- Improves segment calculation in `useChromecastSegments`
- Prevents race condition with `isPlaying` state in `Controls` component

Also includes minor UI and timing adjustments for a smoother user experience.
2026-02-08 15:23:01 +01:00

91 lines
2.4 KiB
TypeScript

/**
* Shared helper to build Chromecast media metadata.
* Eliminates duplication between PlayButton, casting-player reloadWithSettings, and loadEpisode.
*/
import type { Api } from "@jellyfin/sdk";
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
import { MediaStreamType } from "react-native-google-cast";
import { getParentBackdropImageUrl } from "@/utils/jellyfin/image/getParentBackdropImageUrl";
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
/**
* Build a MediaInfo object suitable for `remoteMediaClient.loadMedia()`.
*/
export const buildCastMediaInfo = ({
item,
streamUrl,
api,
}: {
item: BaseItemDto;
streamUrl: string;
api: Api;
}) => {
if (!item.Id) {
throw new Error("Missing item.Id for media load — cannot build contentId");
}
const itemId: string = item.Id;
const streamDuration = item.RunTimeTicks
? item.RunTimeTicks / 10000000
: undefined;
const buildImages = (urls: (string | null | undefined)[]) =>
urls.filter(Boolean).map((url) => ({ url: url as string }));
const metadata =
item.Type === "Episode"
? {
type: "tvShow" as const,
title: item.Name || "",
episodeNumber: item.IndexNumber || 0,
seasonNumber: item.ParentIndexNumber || 0,
seriesTitle: item.SeriesName || "",
images: buildImages([
getParentBackdropImageUrl({
api,
item,
quality: 90,
width: 2000,
}),
]),
}
: item.Type === "Movie"
? {
type: "movie" as const,
title: item.Name || "",
subtitle: item.Overview || "",
images: buildImages([
getPrimaryImageUrl({
api,
item,
quality: 90,
width: 2000,
}),
]),
}
: {
type: "generic" as const,
title: item.Name || "",
subtitle: item.Overview || "",
images: buildImages([
getPrimaryImageUrl({
api,
item,
quality: 90,
width: 2000,
}),
]),
};
return {
contentId: itemId,
contentUrl: streamUrl,
contentType: "video/mp4",
streamType: MediaStreamType.BUFFERED,
streamDuration,
customData: item,
metadata,
};
};