diff --git a/components/casting/CastingMiniPlayer.tsx b/components/casting/CastingMiniPlayer.tsx index 573f04319..f5be0f13c 100644 --- a/components/casting/CastingMiniPlayer.tsx +++ b/components/casting/CastingMiniPlayer.tsx @@ -9,7 +9,7 @@ import { Image } from "expo-image"; import { router } from "expo-router"; import { useAtomValue } from "jotai"; import React, { useEffect, useMemo, useRef, useState } from "react"; -import { Dimensions, Pressable, View } from "react-native"; +import { Pressable, View } from "react-native"; import { Slider } from "react-native-awesome-slider"; import { MediaPlayerState, @@ -23,14 +23,11 @@ import Animated, { useSharedValue, } from "react-native-reanimated"; import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { CastTrickplayBubble } from "@/components/casting/player/CastTrickplayBubble"; import { Text } from "@/components/common/Text"; import { useTrickplay } from "@/hooks/useTrickplay"; import { apiAtom } from "@/providers/JellyfinProvider"; -import { - formatTime, - formatTrickplayTime, - getPosterUrl, -} from "@/utils/casting/helpers"; +import { formatTime, getPosterUrl } from "@/utils/casting/helpers"; import { CASTING_CONSTANTS } from "@/utils/casting/types"; import { msToTicks, ticksToSeconds } from "@/utils/time"; @@ -54,7 +51,6 @@ export const CastingMiniPlayer: React.FC = () => { minutes: 0, seconds: 0, }); - const [scrubPercentage, setScrubPercentage] = useState(0); const isScrubbing = useRef(false); // Slider shared values @@ -223,11 +219,6 @@ export const CastingMiniPlayer: React.FC = () => { const minutes = Math.floor((progressInSeconds % 3600) / 60); const seconds = progressInSeconds % 60; setTrickplayTime({ hours, minutes, seconds }); - - // Track scrub percentage for bubble positioning - if (duration > 0) { - setScrubPercentage(value / duration); - } }} onSlidingComplete={(value) => { isScrubbing.current = false; @@ -241,119 +232,15 @@ export const CastingMiniPlayer: React.FC = () => { }); } }} - renderBubble={() => { - // Calculate bubble position with edge clamping - const screenWidth = Dimensions.get("window").width; - const sliderPadding = 8; - const thumbWidth = 10; // matches thumbWidth prop on Slider - const sliderWidth = screenWidth - sliderPadding * 2; - // Adjust thumb position to account for thumb width affecting travel range - const effectiveTrackWidth = sliderWidth - thumbWidth; - const thumbPosition = - thumbWidth / 2 + scrubPercentage * effectiveTrackWidth; - - if (!trickPlayUrl || !trickplayInfo) { - // Show simple time bubble when no trickplay - const timeBubbleWidth = 70; - const minLeft = -thumbPosition; - const maxLeft = sliderWidth - thumbPosition - timeBubbleWidth; - const centeredLeft = -timeBubbleWidth / 2; - const clampedLeft = Math.max( - minLeft, - Math.min(maxLeft, centeredLeft), - ); - - return ( - - - {formatTrickplayTime(trickplayTime)} - - - ); - } - - const { x, y, url } = trickPlayUrl; - const tileWidth = 140; // Smaller preview for mini player - const tileHeight = tileWidth / (trickplayInfo.aspectRatio ?? 1.78); - - // Calculate clamped position for trickplay preview - const minLeft = -thumbPosition; - const maxLeft = sliderWidth - thumbPosition - tileWidth; - const centeredLeft = -tileWidth / 2; - const clampedLeft = Math.max( - minLeft, - Math.min(maxLeft, centeredLeft), - ); - - return ( - - {/* Trickplay image preview */} - - - - {/* Time overlay */} - - - {formatTrickplayTime(trickplayTime)} - - - - ); - }} + renderBubble={() => ( + + )} + bubbleWidth={trickPlayUrl && trickplayInfo ? 140 : 60} sliderHeight={3} thumbWidth={10} panHitSlop={{ top: 20, bottom: 20, left: 5, right: 5 }} @@ -436,6 +323,19 @@ export const CastingMiniPlayer: React.FC = () => { + {/* Stop button */} + { + e.stopPropagation(); + remoteMediaClient?.stop()?.catch((error: unknown) => { + console.error("[CastingMiniPlayer] Stop error:", error); + }); + }} + style={{ padding: 8 }} + > + + + {/* Play/Pause button */} {