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.)
This commit is contained in:
Uruk
2026-01-19 22:58:26 +01:00
parent a685034f29
commit 58a93628b4
5 changed files with 26 additions and 51 deletions

View File

@@ -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],
);

View File

@@ -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");

View File

@@ -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,

View File

@@ -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
}
};

View File

@@ -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;