From 58a93628b4fcc944c6c6f6f276ffc6530feefd69 Mon Sep 17 00:00:00 2001 From: Uruk Date: Mon, 19 Jan 2026 22:58:26 +0100 Subject: [PATCH] refactor(casting): remove AirPlay references, keep extensible architecture - Remove AirPlay from CastProtocol type union (Chromecast only for now) - Replace AirPlay TODOs with generic 'Future: Add X for other protocols' comments - Remove PROTOCOL_COLORS export, use hardcoded Chromecast color (#F9AB00) - Update all component headers to be protocol-agnostic - Keep switch statements extensible for future protocol additions - Maintain clean architecture for easy integration of new casting protocols Architecture remains flexible for future protocols (AirPlay, DLNA, etc.) --- app/(auth)/casting-player.tsx | 5 +-- components/casting/CastingMiniPlayer.tsx | 6 +-- hooks/useCasting.ts | 47 ++++++++---------------- utils/casting/helpers.ts | 8 ++-- utils/casting/types.ts | 11 ++---- 5 files changed, 26 insertions(+), 51 deletions(-) diff --git a/app/(auth)/casting-player.tsx b/app/(auth)/casting-player.tsx index f45df234..18572ab9 100644 --- a/app/(auth)/casting-player.tsx +++ b/app/(auth)/casting-player.tsx @@ -1,6 +1,6 @@ /** * Unified Casting Player Modal - * Full-screen player for both Chromecast and AirPlay + * Protocol-agnostic full-screen player for all supported casting protocols */ import { Ionicons } from "@expo/vector-icons"; @@ -35,7 +35,6 @@ import { shouldShowNextEpisodeCountdown, truncateTitle, } from "@/utils/casting/helpers"; -import { PROTOCOL_COLORS } from "@/utils/casting/types"; export default function CastingPlayerScreen() { const insets = useSafeAreaInsets(); @@ -178,7 +177,7 @@ export default function CastingPlayerScreen() { ); const protocolColor = useMemo( - () => (protocol ? PROTOCOL_COLORS[protocol] : "#666"), + () => (protocol === "chromecast" ? "#F9AB00" : "#666"), // Google yellow [protocol], ); diff --git a/components/casting/CastingMiniPlayer.tsx b/components/casting/CastingMiniPlayer.tsx index fde9391c..221eae0d 100644 --- a/components/casting/CastingMiniPlayer.tsx +++ b/components/casting/CastingMiniPlayer.tsx @@ -1,6 +1,6 @@ /** * Unified Casting Mini Player - * Works with both Chromecast and AirPlay + * Works with all supported casting protocols */ import { Ionicons } from "@expo/vector-icons"; @@ -19,7 +19,7 @@ import { getProtocolIcon, getProtocolName, } from "@/utils/casting/helpers"; -import { CASTING_CONSTANTS, PROTOCOL_COLORS } from "@/utils/casting/types"; +import { CASTING_CONSTANTS } from "@/utils/casting/types"; export const CastingMiniPlayer: React.FC = () => { const api = useAtomValue(apiAtom); @@ -47,7 +47,7 @@ export const CastingMiniPlayer: React.FC = () => { ); const progressPercent = duration > 0 ? (progress / duration) * 100 : 0; - const protocolColor = PROTOCOL_COLORS[protocol]; + const protocolColor = protocol === "chromecast" ? "#F9AB00" : "#666"; // Google yellow const handlePress = () => { router.push("/casting-player"); diff --git a/hooks/useCasting.ts b/hooks/useCasting.ts index 56da17c3..aeed6d14 100644 --- a/hooks/useCasting.ts +++ b/hooks/useCasting.ts @@ -1,13 +1,13 @@ /** * Unified Casting Hook - * Manages both Chromecast and AirPlay through a common interface + * Protocol-agnostic casting interface - currently supports Chromecast + * Architecture allows for future protocol integrations */ import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client"; import { getPlaystateApi } from "@jellyfin/sdk/lib/utils/api"; import { useAtomValue } from "jotai"; import { useCallback, useEffect, useRef, useState } from "react"; -import { Platform } from "react-native"; import { useCastDevice, useMediaStatus, @@ -18,7 +18,8 @@ import type { CastPlayerState, CastProtocol } from "@/utils/casting/types"; import { DEFAULT_CAST_STATE } from "@/utils/casting/types"; /** - * Unified hook for managing casting (Chromecast + AirPlay) + * Unified hook for managing casting + * Extensible architecture supporting multiple protocols */ export const useCasting = (item: BaseItemDto | null) => { const api = useAtomValue(apiAtom); @@ -39,21 +40,13 @@ export const useCasting = (item: BaseItemDto | null) => { // Detect which protocol is active const chromecastConnected = castDevice !== null; - // TODO: AirPlay detection requires integration with video player's AVRoutePickerView - // The @douglowder/expo-av-route-picker-view package doesn't expose route state - // Options: - // 1. Create native module to detect AVAudioSession.sharedInstance().currentRoute - // 2. Use AVPlayer's isExternalPlaybackActive property - // 3. Listen to AVPlayerItemDidPlayToEndTimeNotification for AirPlay events - const airplayConnected = false; + // Future: Add detection for other protocols here const activeProtocol: CastProtocol | null = chromecastConnected ? "chromecast" - : airplayConnected - ? "airplay" - : null; + : null; - const isConnected = chromecastConnected || airplayConnected; + const isConnected = chromecastConnected; // Update current device useEffect(() => { @@ -68,17 +61,6 @@ export const useCasting = (item: BaseItemDto | null) => { protocol: "chromecast", }, })); - } else if (airplayConnected) { - setState((prev) => ({ - ...prev, - isConnected: true, - protocol: "airplay", - currentDevice: { - id: "airplay-device", - name: "AirPlay Device", // TODO: Get real device name - protocol: "airplay", - }, - })); } else { setState((prev) => ({ ...prev, @@ -87,7 +69,8 @@ export const useCasting = (item: BaseItemDto | null) => { currentDevice: null, })); } - }, [chromecastConnected, airplayConnected, castDevice]); + // Future: Add device detection for other protocols + }, [chromecastConnected, castDevice]); // Chromecast: Update playback state useEffect(() => { @@ -146,14 +129,14 @@ export const useCasting = (item: BaseItemDto | null) => { if (activeProtocol === "chromecast") { await client?.play(); } - // TODO: AirPlay play control + // Future: Add play control for other protocols }, [client, activeProtocol]); const pause = useCallback(async () => { if (activeProtocol === "chromecast") { await client?.pause(); } - // TODO: AirPlay pause control + // Future: Add pause control for other protocols }, [client, activeProtocol]); const togglePlayPause = useCallback(async () => { @@ -170,7 +153,7 @@ export const useCasting = (item: BaseItemDto | null) => { if (activeProtocol === "chromecast") { await client?.seek({ position: positionMs / 1000 }); } - // TODO: AirPlay seek control + // Future: Add seek control for other protocols }, [client, activeProtocol], ); @@ -196,7 +179,7 @@ export const useCasting = (item: BaseItemDto | null) => { if (activeProtocol === "chromecast") { await client?.stop(); } - // TODO: AirPlay stop control + // Future: Add stop control for other protocols // Report stop to Jellyfin if (api && item?.Id && user?.Id) { @@ -229,7 +212,7 @@ export const useCasting = (item: BaseItemDto | null) => { if (activeProtocol === "chromecast") { await client?.setStreamVolume(clampedVolume).catch(console.error); } - // TODO: AirPlay volume control + // Future: Add volume control for other protocols }, 300); }, [client, activeProtocol], @@ -285,7 +268,7 @@ export const useCasting = (item: BaseItemDto | null) => { // Availability isChromecastAvailable: true, // Always available via react-native-google-cast - isAirPlayAvailable: Platform.OS === "ios", + // Future: Add availability checks for other protocols // Raw clients (for advanced operations) remoteMediaClient: client, diff --git a/utils/casting/helpers.ts b/utils/casting/helpers.ts index 0edd4958..887e7609 100644 --- a/utils/casting/helpers.ts +++ b/utils/casting/helpers.ts @@ -1,6 +1,6 @@ /** * Unified Casting Helper Functions - * Common utilities for both Chromecast and AirPlay + * Common utilities for casting protocols */ import type { CastProtocol, ConnectionQuality } from "./types"; @@ -110,8 +110,7 @@ export const getProtocolName = (protocol: CastProtocol): string => { switch (protocol) { case "chromecast": return "Chromecast"; - case "airplay": - return "AirPlay"; + // Future: Add cases for other protocols } }; @@ -124,8 +123,7 @@ export const getProtocolIcon = ( switch (protocol) { case "chromecast": return "tv"; - case "airplay": - return "logo-apple"; + // Future: Add icons for other protocols } }; diff --git a/utils/casting/types.ts b/utils/casting/types.ts index 88a82042..56ee5f4a 100644 --- a/utils/casting/types.ts +++ b/utils/casting/types.ts @@ -1,9 +1,10 @@ /** * Unified Casting Types and Options - * Abstracts Chromecast and AirPlay into a common interface + * Protocol-agnostic casting interface - currently supports Chromecast + * Architecture allows for future protocols (AirPlay, DLNA, etc.) */ -export type CastProtocol = "chromecast" | "airplay"; +export type CastProtocol = "chromecast"; export interface CastDevice { id: string; @@ -79,9 +80,3 @@ export const DEFAULT_CAST_STATE: CastPlayerState = { }; export type ConnectionQuality = "excellent" | "good" | "fair" | "poor"; - -// Protocol-specific colors for UI differentiation -export const PROTOCOL_COLORS = { - chromecast: "#e50914", // Red (Google Cast) - airplay: "#007AFF", // Blue (Apple) -} as const;