/** * Chromecast Settings Menu * Configure version, quality (bitrate cap), audio, subtitles, and playback speed. * Every "selected" row is driven by the active CastSelection — no [0] fallbacks. */ import { Ionicons } from "@expo/vector-icons"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { Modal, Pressable, ScrollView, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Text } from "@/components/common/Text"; import type { AudioTrack, SubtitleTrack } from "@/utils/casting/types"; export interface VersionOption { id: string; name: string; } export interface QualityOption { key: string; value: number | undefined; } interface ChromecastSettingsMenuProps { visible: boolean; onClose: () => void; versions: VersionOption[]; selectedVersionId: string; onVersionChange: (id: string) => void; qualities: QualityOption[]; selectedMaxBitrate: number | undefined; onQualityChange: (value: number | undefined) => void; audioTracks: AudioTrack[]; selectedAudioIndex: number; onAudioChange: (index: number) => void; subtitleTracks: SubtitleTrack[]; /** -1 = subtitles off. */ selectedSubtitleIndex: number; onSubtitleChange: (index: number) => void; playbackSpeed: number; onPlaybackSpeedChange: (speed: number) => void; } const PLAYBACK_SPEEDS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]; const ACCENT = "#a855f7"; export const ChromecastSettingsMenu: React.FC = ({ visible, onClose, versions, selectedVersionId, onVersionChange, qualities, selectedMaxBitrate, onQualityChange, audioTracks, selectedAudioIndex, onAudioChange, subtitleTracks, selectedSubtitleIndex, onSubtitleChange, playbackSpeed, onPlaybackSpeedChange, }) => { const insets = useSafeAreaInsets(); const { t } = useTranslation(); const [expandedSection, setExpandedSection] = useState(null); const toggleSection = (section: string) => { setExpandedSection(expandedSection === section ? null : section); }; const renderSectionHeader = ( title: string, icon: keyof typeof Ionicons.glyphMap, sectionKey: string, ) => ( toggleSection(sectionKey)} style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center", padding: 16, borderBottomWidth: 1, borderBottomColor: "#333", }} > {title} ); const renderRow = ( key: string | number, label: string, sublabel: string | null, selected: boolean, onPress: () => void, ) => ( { onPress(); setExpandedSection(null); }} style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center", padding: 16, backgroundColor: selected ? "#2a2a2a" : "transparent", }} > {label} {sublabel ? ( {sublabel} ) : null} {selected ? : null} ); return ( e.stopPropagation()} > {t("casting_player.playback_settings")} {/* Version — only when the item has more than one MediaSource */} {versions.length > 1 && renderSectionHeader( t("casting_player.version"), "albums-outline", "version", )} {versions.length > 1 && expandedSection === "version" && ( {versions.map((v) => renderRow( v.id, v.name, null, v.id === selectedVersionId, () => onVersionChange(v.id), ), )} )} {/* Quality (bitrate cap) */} {renderSectionHeader( t("casting_player.quality"), "film-outline", "quality", )} {expandedSection === "quality" && ( {qualities.map((q) => renderRow( q.key, q.key, null, q.value === selectedMaxBitrate, () => onQualityChange(q.value), ), )} )} {/* Audio — only when more than one track */} {audioTracks.length > 1 && renderSectionHeader( t("casting_player.audio"), "musical-notes", "audio", )} {audioTracks.length > 1 && expandedSection === "audio" && ( {audioTracks.map((track) => renderRow( track.index, track.displayTitle || track.language || t("casting_player.unknown"), track.codec ? track.codec.toUpperCase() : null, track.index === selectedAudioIndex, () => onAudioChange(track.index), ), )} )} {/* Subtitles */} {subtitleTracks.length > 0 && renderSectionHeader( t("casting_player.subtitles"), "text", "subtitles", )} {subtitleTracks.length > 0 && expandedSection === "subtitles" && ( {renderRow( "off", t("casting_player.none"), null, selectedSubtitleIndex < 0, () => onSubtitleChange(-1), )} {subtitleTracks.map((track) => renderRow( track.index, track.displayTitle || track.language || t("casting_player.unknown"), [ track.codec ? track.codec.toUpperCase() : "", track.isForced ? t("casting_player.forced") : "", ] .filter(Boolean) .join(" • ") || null, track.index === selectedSubtitleIndex, () => onSubtitleChange(track.index), ), )} )} {/* Playback speed */} {renderSectionHeader( t("casting_player.playback_speed"), "speedometer", "speed", )} {expandedSection === "speed" && ( {PLAYBACK_SPEEDS.map((speed) => renderRow( speed, speed === 1 ? t("casting_player.normal") : `${speed}x`, null, Math.abs(playbackSpeed - speed) < 0.01, () => onPlaybackSpeedChange(speed), ), )} )} ); };