diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index 6b06853c..99a346c6 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -189,6 +189,11 @@ const AndroidVideoPlayer: React.FC = () => { const metadataResult = useMetadata({ id: id || 'placeholder', type: (type as any) }); const { metadata, cast } = Boolean(id && type) ? (metadataResult as any) : { metadata: null, cast: [] }; + + // For content with provider IDs (e.g. kitsu:123), imdbId from route params may be null at + // navigation time. useMetadata resolves it asynchronously via ARM + TMDB. Use that resolved + // value as a fallback so Trakt scrobbling and watched status use a real IMDb ID. + const resolvedImdbId: string | undefined = imdbId || (metadataResult as any).imdbId || undefined; const hasLogo = metadata && metadata.logo; const openingAnimation = useOpeningAnimation(backdrop, metadata); @@ -212,12 +217,12 @@ const AndroidVideoPlayer: React.FC = () => { type: type === 'series' ? 'series' : 'movie', title: episodeTitle || title, year: year || 0, - imdbId: imdbId || '', + imdbId: resolvedImdbId || '', season: season, episode: episode, showTitle: title, showYear: year, - showImdbId: imdbId, + showImdbId: resolvedImdbId, episodeId: episodeId }); @@ -244,7 +249,7 @@ const AndroidVideoPlayer: React.FC = () => { traktAutosync, controlsHook.seekToTime, currentStreamProvider, - imdbId, + resolvedImdbId, season, episode, releaseDate, @@ -267,7 +272,7 @@ const AndroidVideoPlayer: React.FC = () => { const nextEpisodeHook = useNextEpisode(type, season, episode, groupedEpisodes, (metadataResult as any)?.groupedEpisodes, episodeId); const { segments: skipIntervals, outroSegment } = useSkipSegments({ - imdbId: imdbId || (id?.startsWith('tt') ? id : undefined), + imdbId: resolvedImdbId || (id?.startsWith('tt') ? id : undefined), type, season, episode, @@ -731,7 +736,7 @@ const AndroidVideoPlayer: React.FC = () => { id, type: 'series', episodeId: ep.stremioId || `${id}:${ep.season_number}:${ep.episode_number}`, - imdbId: imdbId ?? undefined, + imdbId: resolvedImdbId ?? undefined, backdrop: backdrop || undefined, availableStreams: {}, groupedEpisodes: groupedEpisodes, @@ -741,7 +746,7 @@ const AndroidVideoPlayer: React.FC = () => { // Subtitle addon fetching const fetchAvailableSubtitles = useCallback(async () => { - const targetImdbId = imdbId; + const targetImdbId = resolvedImdbId; setIsLoadingSubtitleList(true); try { @@ -820,7 +825,7 @@ const AndroidVideoPlayer: React.FC = () => { } finally { setIsLoadingSubtitleList(false); } - }, [imdbId, type, season, episode, id]); + }, [resolvedImdbId, type, season, episode, id]); const loadWyzieSubtitle = useCallback(async (subtitle: WyzieSubtitle) => { if (!subtitle.url) return; @@ -1046,7 +1051,10 @@ const AndroidVideoPlayer: React.FC = () => { onLongPressActivated={speedControl.activateSpeedBoost} onLongPressEnd={speedControl.deactivateSpeedBoost} onLongPressStateChange={(e) => { - if (e.nativeEvent.state !== 4 && e.nativeEvent.state !== 2) speedControl.deactivateSpeedBoost(); + const state = e.nativeEvent.state; + if (state === 5 || state === 3 || state === 1) { // END, CANCELLED, FAILED + speedControl.deactivateSpeedBoost(); + } }} toggleControls={toggleControls} showControls={playerState.showControls} @@ -1119,7 +1127,7 @@ const AndroidVideoPlayer: React.FC = () => { canEnterPictureInPicture={canShowPipButton} onEnterPictureInPicture={handleEnterPictureInPicture} isBuffering={playerState.isBuffering} - imdbId={imdbId} + imdbId={resolvedImdbId} /> { {/* Parental Guide Overlay - Shows after controls first hide */} { {/* Skip Intro Button - Shows during intro section of TV episodes */} { visible={modals.showSubmitIntroModal} onClose={() => modals.setShowSubmitIntroModal(false)} currentTime={playerState.currentTime} - imdbId={imdbId} + imdbId={resolvedImdbId} season={season} episode={episode} /> diff --git a/src/components/player/android/hooks/useSpeedControl.ts b/src/components/player/android/hooks/useSpeedControl.ts index d185f0e6..a398922c 100644 --- a/src/components/player/android/hooks/useSpeedControl.ts +++ b/src/components/player/android/hooks/useSpeedControl.ts @@ -59,23 +59,22 @@ export const useSpeedControl = (initialSpeed: number = 1.0) => { useNativeDriver: true }).start(); - setTimeout(() => { - Animated.timing(speedActivatedOverlayOpacity, { - toValue: 0, - duration: 300, - useNativeDriver: true - }).start(() => setShowSpeedActivatedOverlay(false)); - }, 2000); - - }, [holdToSpeedEnabled, isSpeedBoosted, playbackSpeed, holdToSpeedValue]); + }, [holdToSpeedEnabled, isSpeedBoosted, playbackSpeed, holdToSpeedValue, speedActivatedOverlayOpacity]); const deactivateSpeedBoost = useCallback(() => { if (isSpeedBoosted) { setPlaybackSpeed(originalSpeed); setIsSpeedBoosted(false); - Animated.timing(speedActivatedOverlayOpacity, { toValue: 0, duration: 100, useNativeDriver: true }).start(); + + Animated.timing(speedActivatedOverlayOpacity, { + toValue: 0, + duration: 100, + useNativeDriver: true + }).start(() => { + setShowSpeedActivatedOverlay(false); + }); } - }, [isSpeedBoosted, originalSpeed]); + }, [isSpeedBoosted, originalSpeed, speedActivatedOverlayOpacity]); return { playbackSpeed, diff --git a/src/components/player/components/GestureControls.tsx b/src/components/player/components/GestureControls.tsx index 599a22af..7fbff0cc 100644 --- a/src/components/player/components/GestureControls.tsx +++ b/src/components/player/components/GestureControls.tsx @@ -191,13 +191,9 @@ export const GestureControls: React.FC = ({ height: '100%' as const, }; - // Full gesture area style + // Full gesture area style covering the entire video const gestureAreaStyle = { - position: 'absolute' as const, - top: screenDimensions.height * 0.15, - left: 0, - width: screenDimensions.width, - height: screenDimensions.height * 0.7, + ...StyleSheet.absoluteFillObject, zIndex: 10, }; @@ -237,41 +233,39 @@ export const GestureControls: React.FC = ({ failOffsetY={[-20, 20]} maxPointers={1} > - - {/* Left side gestures */} - - - - - - - - - - - - - - - + + + {/* Left side gestures */} + + + + + + + + + + + {/* Center area tap handler */} = ({ }} /> - {/* Right side gestures */} - - - - - - - - - - - - - - - - + {/* Right side gestures */} + + + + + + + + + + + + + {/* Volume/Brightness Pill Overlay */} diff --git a/src/components/player/hooks/useSpeedControl.ts b/src/components/player/hooks/useSpeedControl.ts index a8a0c022..2a263205 100644 --- a/src/components/player/hooks/useSpeedControl.ts +++ b/src/components/player/hooks/useSpeedControl.ts @@ -63,23 +63,22 @@ export const useSpeedControl = (initialSpeed: number = 1.0) => { useNativeDriver: true }).start(); - setTimeout(() => { - Animated.timing(speedActivatedOverlayOpacity, { - toValue: 0, - duration: 300, - useNativeDriver: true - }).start(() => setShowSpeedActivatedOverlay(false)); - }, 2000); - - }, [holdToSpeedEnabled, isSpeedBoosted, playbackSpeed, holdToSpeedValue]); + }, [holdToSpeedEnabled, isSpeedBoosted, playbackSpeed, holdToSpeedValue, speedActivatedOverlayOpacity]); const deactivateSpeedBoost = useCallback(() => { if (isSpeedBoosted) { setPlaybackSpeed(originalSpeed); setIsSpeedBoosted(false); - Animated.timing(speedActivatedOverlayOpacity, { toValue: 0, duration: 100, useNativeDriver: true }).start(); + + Animated.timing(speedActivatedOverlayOpacity, { + toValue: 0, + duration: 100, + useNativeDriver: true + }).start(() => { + setShowSpeedActivatedOverlay(false); + }); } - }, [isSpeedBoosted, originalSpeed]); + }, [isSpeedBoosted, originalSpeed, speedActivatedOverlayOpacity]); return { playbackSpeed,