diff --git a/app/(auth)/casting-player.tsx b/app/(auth)/casting-player.tsx
index 2a7ddaa73..d1735bd4d 100644
--- a/app/(auth)/casting-player.tsx
+++ b/app/(auth)/casting-player.tsx
@@ -36,6 +36,8 @@ import Animated, {
} from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { BITRATES } from "@/components/BitrateSelector";
+import { CastPlayerHeader } from "@/components/casting/player/CastPlayerHeader";
+import { CastPlayerTitle } from "@/components/casting/player/CastPlayerTitle";
import { ChromecastDeviceSheet } from "@/components/chromecast/ChromecastDeviceSheet";
import { ChromecastEpisodeList } from "@/components/chromecast/ChromecastEpisodeList";
import { ChromecastSettingsMenu } from "@/components/chromecast/ChromecastSettingsMenu";
@@ -53,7 +55,6 @@ import {
formatTime,
formatTrickplayTime,
getPosterUrl,
- truncateTitle,
} from "@/utils/casting/helpers";
import { resolveSelection } from "@/utils/casting/selection";
import type { CastSelection } from "@/utils/casting/types";
@@ -667,113 +668,22 @@ export default function CastingPlayerScreen() {
]}
>
{/* Header - Fixed at top */}
-
-
-
-
-
- {/* Connection indicator */}
- setShowDeviceSheet(true)}
- style={{
- flexDirection: "row",
- alignItems: "center",
- gap: 6,
- paddingHorizontal: 12,
- paddingVertical: 6,
- backgroundColor: "#1a1a1a",
- borderRadius: 16,
- }}
- >
-
-
- {currentDevice || t("casting_player.unknown_device")}
-
-
-
- setShowSettings(true)}
- style={{ padding: 8, marginRight: -8 }}
- >
-
-
-
+ setShowDeviceSheet(true)}
+ onPressSettings={() => setShowSettings(true)}
+ />
{/* Title Area */}
-
- {/* Title */}
-
- {truncateTitle(
- currentItem.Name || t("casting_player.unknown"),
- 50,
- )}
-
-
- {/* Grey episode/season info */}
- {currentItem.Type === "Episode" &&
- currentItem.ParentIndexNumber !== undefined &&
- currentItem.IndexNumber !== undefined && (
-
- {t("casting_player.season_episode_format", {
- season: currentItem.ParentIndexNumber,
- episode: currentItem.IndexNumber,
- })}
-
- )}
-
+
{/* Scrollable content area */}
void;
+ /** Open the device sheet (connection indicator press). */
+ onPressConnectionIndicator: () => void;
+ /** Open the settings menu. */
+ onPressSettings: () => void;
+}
+
+export function CastPlayerHeader({
+ insetTop,
+ protocolColor,
+ currentDevice,
+ t,
+ onDismiss,
+ onPressConnectionIndicator,
+ onPressSettings,
+}: CastPlayerHeaderProps) {
+ return (
+
+
+
+
+
+ {/* Connection indicator */}
+
+
+
+ {currentDevice || t("casting_player.unknown_device")}
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/casting/player/CastPlayerTitle.tsx b/components/casting/player/CastPlayerTitle.tsx
new file mode 100644
index 000000000..fcf9ff94f
--- /dev/null
+++ b/components/casting/player/CastPlayerTitle.tsx
@@ -0,0 +1,72 @@
+/**
+ * Casting Player Title Area
+ * Fixed title bar: item title and optional grey episode/season info.
+ */
+
+import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
+import type { TFunction } from "i18next";
+import { View } from "react-native";
+import { Text } from "@/components/common/Text";
+import { truncateTitle } from "@/utils/casting/helpers";
+
+interface CastPlayerTitleProps {
+ /** Top safe-area inset, used to offset the fixed title area. */
+ insetTop: number;
+ /** The currently playing item. */
+ currentItem: BaseItemDto;
+ /** Translation function. */
+ t: TFunction;
+}
+
+export function CastPlayerTitle({
+ insetTop,
+ currentItem,
+ t,
+}: CastPlayerTitleProps) {
+ return (
+
+ {/* Title */}
+
+ {truncateTitle(currentItem.Name || t("casting_player.unknown"), 50)}
+
+
+ {/* Grey episode/season info */}
+ {currentItem.Type === "Episode" &&
+ currentItem.ParentIndexNumber !== undefined &&
+ currentItem.IndexNumber !== undefined && (
+
+ {t("casting_player.season_episode_format", {
+ season: currentItem.ParentIndexNumber,
+ episode: currentItem.IndexNumber,
+ })}
+
+ )}
+
+ );
+}