mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-05-28 17:48:26 +01:00
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:
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Unified Casting Player Modal
|
* 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";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
@@ -35,7 +35,6 @@ import {
|
|||||||
shouldShowNextEpisodeCountdown,
|
shouldShowNextEpisodeCountdown,
|
||||||
truncateTitle,
|
truncateTitle,
|
||||||
} from "@/utils/casting/helpers";
|
} from "@/utils/casting/helpers";
|
||||||
import { PROTOCOL_COLORS } from "@/utils/casting/types";
|
|
||||||
|
|
||||||
export default function CastingPlayerScreen() {
|
export default function CastingPlayerScreen() {
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
@@ -178,7 +177,7 @@ export default function CastingPlayerScreen() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const protocolColor = useMemo(
|
const protocolColor = useMemo(
|
||||||
() => (protocol ? PROTOCOL_COLORS[protocol] : "#666"),
|
() => (protocol === "chromecast" ? "#F9AB00" : "#666"), // Google yellow
|
||||||
[protocol],
|
[protocol],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Unified Casting Mini Player
|
* Unified Casting Mini Player
|
||||||
* Works with both Chromecast and AirPlay
|
* Works with all supported casting protocols
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
getProtocolIcon,
|
getProtocolIcon,
|
||||||
getProtocolName,
|
getProtocolName,
|
||||||
} from "@/utils/casting/helpers";
|
} 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 = () => {
|
export const CastingMiniPlayer: React.FC = () => {
|
||||||
const api = useAtomValue(apiAtom);
|
const api = useAtomValue(apiAtom);
|
||||||
@@ -47,7 +47,7 @@ export const CastingMiniPlayer: React.FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const progressPercent = duration > 0 ? (progress / duration) * 100 : 0;
|
const progressPercent = duration > 0 ? (progress / duration) * 100 : 0;
|
||||||
const protocolColor = PROTOCOL_COLORS[protocol];
|
const protocolColor = protocol === "chromecast" ? "#F9AB00" : "#666"; // Google yellow
|
||||||
|
|
||||||
const handlePress = () => {
|
const handlePress = () => {
|
||||||
router.push("/casting-player");
|
router.push("/casting-player");
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* Unified Casting Hook
|
* 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 type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||||
import { getPlaystateApi } from "@jellyfin/sdk/lib/utils/api";
|
import { getPlaystateApi } from "@jellyfin/sdk/lib/utils/api";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { Platform } from "react-native";
|
|
||||||
import {
|
import {
|
||||||
useCastDevice,
|
useCastDevice,
|
||||||
useMediaStatus,
|
useMediaStatus,
|
||||||
@@ -18,7 +18,8 @@ import type { CastPlayerState, CastProtocol } from "@/utils/casting/types";
|
|||||||
import { DEFAULT_CAST_STATE } 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) => {
|
export const useCasting = (item: BaseItemDto | null) => {
|
||||||
const api = useAtomValue(apiAtom);
|
const api = useAtomValue(apiAtom);
|
||||||
@@ -39,21 +40,13 @@ export const useCasting = (item: BaseItemDto | null) => {
|
|||||||
|
|
||||||
// Detect which protocol is active
|
// Detect which protocol is active
|
||||||
const chromecastConnected = castDevice !== null;
|
const chromecastConnected = castDevice !== null;
|
||||||
// TODO: AirPlay detection requires integration with video player's AVRoutePickerView
|
// Future: Add detection for other protocols here
|
||||||
// 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;
|
|
||||||
|
|
||||||
const activeProtocol: CastProtocol | null = chromecastConnected
|
const activeProtocol: CastProtocol | null = chromecastConnected
|
||||||
? "chromecast"
|
? "chromecast"
|
||||||
: airplayConnected
|
: null;
|
||||||
? "airplay"
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const isConnected = chromecastConnected || airplayConnected;
|
const isConnected = chromecastConnected;
|
||||||
|
|
||||||
// Update current device
|
// Update current device
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -68,17 +61,6 @@ export const useCasting = (item: BaseItemDto | null) => {
|
|||||||
protocol: "chromecast",
|
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 {
|
} else {
|
||||||
setState((prev) => ({
|
setState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
@@ -87,7 +69,8 @@ export const useCasting = (item: BaseItemDto | null) => {
|
|||||||
currentDevice: null,
|
currentDevice: null,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}, [chromecastConnected, airplayConnected, castDevice]);
|
// Future: Add device detection for other protocols
|
||||||
|
}, [chromecastConnected, castDevice]);
|
||||||
|
|
||||||
// Chromecast: Update playback state
|
// Chromecast: Update playback state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -146,14 +129,14 @@ export const useCasting = (item: BaseItemDto | null) => {
|
|||||||
if (activeProtocol === "chromecast") {
|
if (activeProtocol === "chromecast") {
|
||||||
await client?.play();
|
await client?.play();
|
||||||
}
|
}
|
||||||
// TODO: AirPlay play control
|
// Future: Add play control for other protocols
|
||||||
}, [client, activeProtocol]);
|
}, [client, activeProtocol]);
|
||||||
|
|
||||||
const pause = useCallback(async () => {
|
const pause = useCallback(async () => {
|
||||||
if (activeProtocol === "chromecast") {
|
if (activeProtocol === "chromecast") {
|
||||||
await client?.pause();
|
await client?.pause();
|
||||||
}
|
}
|
||||||
// TODO: AirPlay pause control
|
// Future: Add pause control for other protocols
|
||||||
}, [client, activeProtocol]);
|
}, [client, activeProtocol]);
|
||||||
|
|
||||||
const togglePlayPause = useCallback(async () => {
|
const togglePlayPause = useCallback(async () => {
|
||||||
@@ -170,7 +153,7 @@ export const useCasting = (item: BaseItemDto | null) => {
|
|||||||
if (activeProtocol === "chromecast") {
|
if (activeProtocol === "chromecast") {
|
||||||
await client?.seek({ position: positionMs / 1000 });
|
await client?.seek({ position: positionMs / 1000 });
|
||||||
}
|
}
|
||||||
// TODO: AirPlay seek control
|
// Future: Add seek control for other protocols
|
||||||
},
|
},
|
||||||
[client, activeProtocol],
|
[client, activeProtocol],
|
||||||
);
|
);
|
||||||
@@ -196,7 +179,7 @@ export const useCasting = (item: BaseItemDto | null) => {
|
|||||||
if (activeProtocol === "chromecast") {
|
if (activeProtocol === "chromecast") {
|
||||||
await client?.stop();
|
await client?.stop();
|
||||||
}
|
}
|
||||||
// TODO: AirPlay stop control
|
// Future: Add stop control for other protocols
|
||||||
|
|
||||||
// Report stop to Jellyfin
|
// Report stop to Jellyfin
|
||||||
if (api && item?.Id && user?.Id) {
|
if (api && item?.Id && user?.Id) {
|
||||||
@@ -229,7 +212,7 @@ export const useCasting = (item: BaseItemDto | null) => {
|
|||||||
if (activeProtocol === "chromecast") {
|
if (activeProtocol === "chromecast") {
|
||||||
await client?.setStreamVolume(clampedVolume).catch(console.error);
|
await client?.setStreamVolume(clampedVolume).catch(console.error);
|
||||||
}
|
}
|
||||||
// TODO: AirPlay volume control
|
// Future: Add volume control for other protocols
|
||||||
}, 300);
|
}, 300);
|
||||||
},
|
},
|
||||||
[client, activeProtocol],
|
[client, activeProtocol],
|
||||||
@@ -285,7 +268,7 @@ export const useCasting = (item: BaseItemDto | null) => {
|
|||||||
|
|
||||||
// Availability
|
// Availability
|
||||||
isChromecastAvailable: true, // Always available via react-native-google-cast
|
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)
|
// Raw clients (for advanced operations)
|
||||||
remoteMediaClient: client,
|
remoteMediaClient: client,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Unified Casting Helper Functions
|
* Unified Casting Helper Functions
|
||||||
* Common utilities for both Chromecast and AirPlay
|
* Common utilities for casting protocols
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { CastProtocol, ConnectionQuality } from "./types";
|
import type { CastProtocol, ConnectionQuality } from "./types";
|
||||||
@@ -110,8 +110,7 @@ export const getProtocolName = (protocol: CastProtocol): string => {
|
|||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case "chromecast":
|
case "chromecast":
|
||||||
return "Chromecast";
|
return "Chromecast";
|
||||||
case "airplay":
|
// Future: Add cases for other protocols
|
||||||
return "AirPlay";
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -124,8 +123,7 @@ export const getProtocolIcon = (
|
|||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case "chromecast":
|
case "chromecast":
|
||||||
return "tv";
|
return "tv";
|
||||||
case "airplay":
|
// Future: Add icons for other protocols
|
||||||
return "logo-apple";
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Unified Casting Types and Options
|
* 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 {
|
export interface CastDevice {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -79,9 +80,3 @@ export const DEFAULT_CAST_STATE: CastPlayerState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ConnectionQuality = "excellent" | "good" | "fair" | "poor";
|
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;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user