/** * Chromecast Device Info Sheet * Shows device details, volume control, and disconnect option */ import { Ionicons } from "@expo/vector-icons"; import React, { useEffect, useState } from "react"; import { Modal, Pressable, View } from "react-native"; import { Slider } from "react-native-awesome-slider"; import type { Device } from "react-native-google-cast"; import { useCastSession, useRemoteMediaClient } from "react-native-google-cast"; import { useSharedValue } from "react-native-reanimated"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Text } from "@/components/common/Text"; interface ChromecastDeviceSheetProps { visible: boolean; onClose: () => void; device: Device | null; onDisconnect: () => Promise; volume?: number; onVolumeChange?: (volume: number) => Promise; } export const ChromecastDeviceSheet: React.FC = ({ visible, onClose, device, onDisconnect, volume = 0.5, onVolumeChange, }) => { const insets = useSafeAreaInsets(); const [isDisconnecting, setIsDisconnecting] = useState(false); const volumeValue = useSharedValue(volume * 100); const minimumValue = useSharedValue(0); const maximumValue = useSharedValue(100); const castSession = useCastSession(); const remoteMediaClient = useRemoteMediaClient(); // Sync volume slider with prop changes (updates from physical buttons) useEffect(() => { volumeValue.value = volume * 100; }, [volume, volumeValue]); // Poll for volume updates when sheet is visible to catch physical button changes useEffect(() => { if (!visible || !remoteMediaClient) return; // Request status update to get latest volume from device const interval = setInterval(() => { remoteMediaClient.requestStatus().catch(() => { // Ignore errors - device might be disconnected }); }, 1000); return () => clearInterval(interval); }, [visible, remoteMediaClient]); const handleDisconnect = async () => { setIsDisconnecting(true); try { await onDisconnect(); onClose(); } catch (error) { console.error("Failed to disconnect:", error); } finally { setIsDisconnecting(false); } }; const handleVolumeComplete = async (value: number) => { const newVolume = value / 100; try { // Use CastSession.setVolume for DEVICE volume control // This works even when no media is playing, unlike setStreamVolume if (castSession) { castSession.setVolume(newVolume); console.log("[Volume] Set device volume via CastSession:", newVolume); } else if (onVolumeChange) { // Fallback to prop method if session not available await onVolumeChange(newVolume); } } catch (error) { console.error("[Volume] Error setting volume:", error); } }; return ( e.stopPropagation()} > {/* Header */} Chromecast {/* Device info */} Device Name {device?.friendlyName || device?.deviceId || "Unknown Device"} {device?.deviceId && ( Device ID {device?.deviceId} )} {/* Volume control */} Volume {Math.round((volume || 0) * 100)}% { console.log( "[Volume] Sliding started", volumeValue.value, ); }} onValueChange={(value) => { volumeValue.value = value; console.log("[Volume] Value changed", value); }} onSlidingComplete={handleVolumeComplete} panHitSlop={{ top: 20, bottom: 20, left: 0, right: 0 }} /> {/* Disconnect button */} {isDisconnecting ? "Disconnecting..." : "Stop Casting"} ); };