diff --git a/components/Chromecast.tsx b/components/Chromecast.tsx index dbc36f9c..a4c823bb 100644 --- a/components/Chromecast.tsx +++ b/components/Chromecast.tsx @@ -1,5 +1,8 @@ import { Feather } from "@expo/vector-icons"; -import { useCallback, useEffect } from "react"; +import type { PlaybackProgressInfo } from "@jellyfin/sdk/lib/generated-client/models"; +import { getPlaystateApi } from "@jellyfin/sdk/lib/utils/api"; +import { useAtomValue } from "jotai"; +import { useCallback, useEffect, useRef } from "react"; import { Platform } from "react-native"; import { Pressable } from "react-native-gesture-handler"; import GoogleCast, { @@ -10,6 +13,7 @@ import GoogleCast, { useMediaStatus, useRemoteMediaClient, } from "react-native-google-cast"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { RoundButton } from "./RoundButton"; export function Chromecast({ @@ -24,6 +28,10 @@ export function Chromecast({ const sessionManager = GoogleCast.getSessionManager(); const discoveryManager = GoogleCast.getDiscoveryManager(); const mediaStatus = useMediaStatus(); + const api = useAtomValue(apiAtom); + const user = useAtomValue(userAtom); + + const lastReportedProgressRef = useRef(0); useEffect(() => { (async () => { @@ -36,6 +44,53 @@ export function Chromecast({ })(); }, [client, devices, castDevice, sessionManager, discoveryManager]); + // Report video progress to Jellyfin server + useEffect(() => { + if ( + !api || + !user?.Id || + !mediaStatus || + !mediaStatus.mediaInfo?.contentId + ) { + return; + } + + const streamPosition = mediaStatus.streamPosition || 0; + + // Report every 10 seconds + if (Math.abs(streamPosition - lastReportedProgressRef.current) < 10) { + return; + } + + const contentId = mediaStatus.mediaInfo.contentId; + const positionTicks = Math.floor(streamPosition * 10000000); + const isPaused = mediaStatus.playerState === "paused"; + const streamUrl = mediaStatus.mediaInfo.contentUrl || ""; + const isTranscoding = streamUrl.includes("m3u8"); + + const progressInfo: PlaybackProgressInfo = { + ItemId: contentId, + PositionTicks: positionTicks, + IsPaused: isPaused, + PlayMethod: isTranscoding ? "Transcode" : "DirectStream", + PlaySessionId: contentId, + }; + + getPlaystateApi(api) + .reportPlaybackProgress({ playbackProgressInfo: progressInfo }) + .then(() => { + lastReportedProgressRef.current = streamPosition; + }) + .catch((error) => { + console.error("Failed to report Chromecast progress:", error); + }); + }, [ + api, + user?.Id, + mediaStatus?.streamPosition, + mediaStatus?.mediaInfo?.contentId, + ]); + // Android requires the cast button to be present for startDiscovery to work const AndroidCastButton = useCallback( () =>