removed dead code

This commit is contained in:
tapframe 2025-12-24 21:38:02 +05:30
parent 8588aca948
commit 39498f78b7
13 changed files with 15 additions and 805 deletions

1
libmpv-android Submodule

@ -0,0 +1 @@
Subproject commit 8c4778b5aad441bb0449a7f9b3d6d827fd3d6a2a

1
mpv-android Submodule

@ -0,0 +1 @@
Subproject commit 118cd1ed3d498265e44230e5dbb015bdd59f9dad

View file

@ -81,22 +81,29 @@ export const usePlayerSetup = (config: PlayerSetupConfig) => {
};
}, [isOpeningAnimationComplete]);
// Handle Orientation (Lock to Landscape after opening)
const orientationLocked = useRef(false);
useEffect(() => {
if (isOpeningAnimationComplete) {
if (isOpeningAnimationComplete && !orientationLocked.current) {
const task = InteractionManager.runAfterInteractions(() => {
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE)
.then(() => {
if (__DEV__) logger.log('[VideoPlayer] Locked to landscape orientation');
orientationLocked.current = true;
})
.catch((error) => {
logger.warn('[VideoPlayer] Failed to lock orientation:', error);
});
.catch(() => { });
});
return () => task.cancel();
}
}, [isOpeningAnimationComplete]);
useEffect(() => {
return () => {
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.DEFAULT)
.then(() => ScreenOrientation.unlockAsync())
.catch(() => { });
};
}, []);
// Handle App State
useEffect(() => {
const onAppStateChange = (state: string) => {

View file

@ -1,58 +0,0 @@
import { useState } from 'react';
import {
DEFAULT_SUBTITLE_SIZE,
SubtitleCue,
SubtitleSegment,
WyzieSubtitle
} from '../../utils/playerTypes';
export const useCustomSubtitles = () => {
// Data State
const [customSubtitles, setCustomSubtitles] = useState<SubtitleCue[]>([]);
const [currentSubtitle, setCurrentSubtitle] = useState<string>('');
const [currentFormattedSegments, setCurrentFormattedSegments] = useState<SubtitleSegment[][]>([]);
const [availableSubtitles, setAvailableSubtitles] = useState<WyzieSubtitle[]>([]);
const [useCustomSubtitles, setUseCustomSubtitles] = useState<boolean>(false);
// Loading State
const [isLoadingSubtitles, setIsLoadingSubtitles] = useState<boolean>(false);
const [isLoadingSubtitleList, setIsLoadingSubtitleList] = useState<boolean>(false);
// Styling State
const [subtitleSize, setSubtitleSize] = useState<number>(DEFAULT_SUBTITLE_SIZE);
const [subtitleBackground, setSubtitleBackground] = useState<boolean>(false);
const [subtitleTextColor, setSubtitleTextColor] = useState<string>('#FFFFFF');
const [subtitleBgOpacity, setSubtitleBgOpacity] = useState<number>(0.7);
const [subtitleTextShadow, setSubtitleTextShadow] = useState<boolean>(true);
const [subtitleOutline, setSubtitleOutline] = useState<boolean>(true);
const [subtitleOutlineColor, setSubtitleOutlineColor] = useState<string>('#000000');
const [subtitleOutlineWidth, setSubtitleOutlineWidth] = useState<number>(4);
const [subtitleAlign, setSubtitleAlign] = useState<'center' | 'left' | 'right'>('center');
const [subtitleBottomOffset, setSubtitleBottomOffset] = useState<number>(10);
const [subtitleLetterSpacing, setSubtitleLetterSpacing] = useState<number>(0);
const [subtitleLineHeightMultiplier, setSubtitleLineHeightMultiplier] = useState<number>(1.2);
const [subtitleOffsetSec, setSubtitleOffsetSec] = useState<number>(0);
return {
customSubtitles, setCustomSubtitles,
currentSubtitle, setCurrentSubtitle,
currentFormattedSegments, setCurrentFormattedSegments,
availableSubtitles, setAvailableSubtitles,
useCustomSubtitles, setUseCustomSubtitles,
isLoadingSubtitles, setIsLoadingSubtitles,
isLoadingSubtitleList, setIsLoadingSubtitleList,
subtitleSize, setSubtitleSize,
subtitleBackground, setSubtitleBackground,
subtitleTextColor, setSubtitleTextColor,
subtitleBgOpacity, setSubtitleBgOpacity,
subtitleTextShadow, setSubtitleTextShadow,
subtitleOutline, setSubtitleOutline,
subtitleOutlineColor, setSubtitleOutlineColor,
subtitleOutlineWidth, setSubtitleOutlineWidth,
subtitleAlign, setSubtitleAlign,
subtitleBottomOffset, setSubtitleBottomOffset,
subtitleLetterSpacing, setSubtitleLetterSpacing,
subtitleLineHeightMultiplier, setSubtitleLineHeightMultiplier,
subtitleOffsetSec, setSubtitleOffsetSec
};
};

View file

@ -1,58 +0,0 @@
import { useMemo } from 'react';
export const useNextEpisode = (
type: string | undefined,
season: number | undefined,
episode: number | undefined,
groupedEpisodes: any,
metadataGroupedEpisodes: any,
episodeId: string | undefined
) => {
// Current description
const currentEpisodeDescription = useMemo(() => {
try {
if (type !== 'series') return '';
const allEpisodes = Object.values(groupedEpisodes || {}).flat() as any[];
if (!allEpisodes || allEpisodes.length === 0) return '';
let match: any | null = null;
if (episodeId) {
match = allEpisodes.find(ep => ep?.stremioId === episodeId || String(ep?.id) === String(episodeId));
}
if (!match && season && episode) {
match = allEpisodes.find(ep => ep?.season_number === season && ep?.episode_number === episode);
}
return (match?.overview || '').trim();
} catch {
return '';
}
}, [type, groupedEpisodes, episodeId, season, episode]);
// Next Episode
const nextEpisode = useMemo(() => {
try {
if (type !== 'series' || !season || !episode) return null;
const sourceGroups = groupedEpisodes && Object.keys(groupedEpisodes || {}).length > 0
? groupedEpisodes
: (metadataGroupedEpisodes || {});
const allEpisodes = Object.values(sourceGroups || {}).flat() as any[];
if (!allEpisodes || allEpisodes.length === 0) return null;
let nextEp = allEpisodes.find((ep: any) =>
ep.season_number === season && ep.episode_number === episode + 1
);
if (!nextEp) {
nextEp = allEpisodes.find((ep: any) =>
ep.season_number === season + 1 && ep.episode_number === 1
);
}
return nextEp;
} catch {
return null;
}
}, [type, season, episode, groupedEpisodes, metadataGroupedEpisodes]);
return { currentEpisodeDescription, nextEpisode };
};

View file

@ -1,149 +0,0 @@
import { useRef, useState, useEffect } from 'react';
import { Animated, InteractionManager } from 'react-native';
import FastImage from '@d11/react-native-fast-image';
import { logger } from '../../../../utils/logger';
export const useOpeningAnimation = (backdrop: string | undefined, metadata: any) => {
// Animation Values
const fadeAnim = useRef(new Animated.Value(1)).current;
const openingFadeAnim = useRef(new Animated.Value(0)).current;
const openingScaleAnim = useRef(new Animated.Value(0.8)).current;
const backgroundFadeAnim = useRef(new Animated.Value(1)).current;
const backdropImageOpacityAnim = useRef(new Animated.Value(0)).current;
const logoScaleAnim = useRef(new Animated.Value(0.8)).current;
const logoOpacityAnim = useRef(new Animated.Value(0)).current;
const pulseAnim = useRef(new Animated.Value(1)).current;
const [isOpeningAnimationComplete, setIsOpeningAnimationComplete] = useState(false);
const [shouldHideOpeningOverlay, setShouldHideOpeningOverlay] = useState(false);
const [isBackdropLoaded, setIsBackdropLoaded] = useState(false);
// Prefetch Background
useEffect(() => {
const task = InteractionManager.runAfterInteractions(() => {
if (backdrop && typeof backdrop === 'string') {
setIsBackdropLoaded(false);
backdropImageOpacityAnim.setValue(0);
try {
FastImage.preload([{ uri: backdrop }]);
setIsBackdropLoaded(true);
Animated.timing(backdropImageOpacityAnim, {
toValue: 1,
duration: 400,
useNativeDriver: true,
}).start();
} catch (error) {
setIsBackdropLoaded(true);
backdropImageOpacityAnim.setValue(1);
}
} else {
setIsBackdropLoaded(true);
backdropImageOpacityAnim.setValue(0);
}
});
return () => task.cancel();
}, [backdrop]);
// Prefetch Logo
useEffect(() => {
const task = InteractionManager.runAfterInteractions(() => {
const logoUrl = metadata?.logo;
if (logoUrl && typeof logoUrl === 'string') {
try {
FastImage.preload([{ uri: logoUrl }]);
} catch (error) { }
}
});
return () => task.cancel();
}, [metadata]);
const startOpeningAnimation = () => {
Animated.parallel([
Animated.timing(logoOpacityAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}),
Animated.spring(logoScaleAnim, {
toValue: 1,
tension: 80,
friction: 8,
useNativeDriver: true,
}),
]).start();
const createPulseAnimation = () => {
return Animated.sequence([
Animated.timing(pulseAnim, {
toValue: 1.05,
duration: 800,
useNativeDriver: true,
}),
Animated.timing(pulseAnim, {
toValue: 1,
duration: 800,
useNativeDriver: true,
}),
]);
};
const loopPulse = () => {
createPulseAnimation().start(() => {
if (!isOpeningAnimationComplete) {
loopPulse();
}
});
};
loopPulse();
};
const completeOpeningAnimation = () => {
pulseAnim.stopAnimation();
Animated.parallel([
Animated.timing(openingFadeAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}),
Animated.timing(openingScaleAnim, {
toValue: 1,
duration: 350,
useNativeDriver: true,
}),
Animated.timing(backgroundFadeAnim, {
toValue: 0,
duration: 400,
useNativeDriver: true,
}),
]).start(() => {
setIsOpeningAnimationComplete(true);
setTimeout(() => {
setShouldHideOpeningOverlay(true);
}, 450);
});
setTimeout(() => {
if (!isOpeningAnimationComplete) {
// logger.warn('[VideoPlayer] Opening animation fallback triggered');
setIsOpeningAnimationComplete(true);
}
}, 1000);
};
return {
fadeAnim,
openingFadeAnim,
openingScaleAnim,
backgroundFadeAnim,
backdropImageOpacityAnim,
logoScaleAnim,
logoOpacityAnim,
pulseAnim,
isOpeningAnimationComplete,
shouldHideOpeningOverlay,
isBackdropLoaded,
startOpeningAnimation,
completeOpeningAnimation
};
};

View file

@ -1,63 +0,0 @@
import { useRef, useCallback } from 'react';
import { Platform } from 'react-native';
import { logger } from '../../../../utils/logger';
const DEBUG_MODE = false;
const END_EPSILON = 0.3;
export const usePlayerControls = (
ksPlayerRef: any,
paused: boolean,
setPaused: (paused: boolean) => void,
currentTime: number,
duration: number,
isSeeking: React.MutableRefObject<boolean>,
isMounted: React.MutableRefObject<boolean>
) => {
// iOS seeking helpers
const iosWasPausedDuringSeekRef = useRef<boolean | null>(null);
const togglePlayback = useCallback(() => {
setPaused(!paused);
}, [paused, setPaused]);
const seekToTime = useCallback((rawSeconds: number) => {
const timeInSeconds = Math.max(0, Math.min(rawSeconds, duration > 0 ? duration - END_EPSILON : rawSeconds));
if (ksPlayerRef.current && duration > 0 && !isSeeking.current) {
if (DEBUG_MODE) logger.log(`[usePlayerControls] Seeking to ${timeInSeconds}`);
isSeeking.current = true;
// iOS optimization: pause while seeking for smoother experience
iosWasPausedDuringSeekRef.current = paused;
if (!paused) setPaused(true);
// Actually perform the seek
ksPlayerRef.current.seek(timeInSeconds);
// Debounce the seeking state reset
setTimeout(() => {
if (isMounted.current && isSeeking.current) {
isSeeking.current = false;
// Resume if it was playing
if (iosWasPausedDuringSeekRef.current === false) {
setPaused(false);
iosWasPausedDuringSeekRef.current = null;
}
}
}, 500);
}
}, [duration, paused, setPaused, ksPlayerRef, isSeeking, isMounted]);
const skip = useCallback((seconds: number) => {
seekToTime(currentTime + seconds);
}, [currentTime, seekToTime]);
return {
togglePlayback,
seekToTime,
skip,
iosWasPausedDuringSeekRef
};
};

View file

@ -1,34 +0,0 @@
import { useState } from 'react';
import { Episode } from '../../../../types/metadata';
export const usePlayerModals = () => {
const [showAudioModal, setShowAudioModal] = useState(false);
const [showSubtitleModal, setShowSubtitleModal] = useState(false);
const [showSpeedModal, setShowSpeedModal] = useState(false);
const [showSourcesModal, setShowSourcesModal] = useState(false);
const [showEpisodesModal, setShowEpisodesModal] = useState(false);
const [showEpisodeStreamsModal, setShowEpisodeStreamsModal] = useState(false);
const [showErrorModal, setShowErrorModal] = useState(false);
const [showSubtitleLanguageModal, setShowSubtitleLanguageModal] = useState(false);
const [showCastDetails, setShowCastDetails] = useState(false);
// Some modals have associated data
const [selectedEpisodeForStreams, setSelectedEpisodeForStreams] = useState<Episode | null>(null);
const [errorDetails, setErrorDetails] = useState<string>('');
const [selectedCastMember, setSelectedCastMember] = useState<any>(null);
return {
showAudioModal, setShowAudioModal,
showSubtitleModal, setShowSubtitleModal,
showSpeedModal, setShowSpeedModal,
showSourcesModal, setShowSourcesModal,
showEpisodesModal, setShowEpisodesModal,
showEpisodeStreamsModal, setShowEpisodeStreamsModal,
showErrorModal, setShowErrorModal,
showSubtitleLanguageModal, setShowSubtitleLanguageModal,
showCastDetails, setShowCastDetails,
selectedEpisodeForStreams, setSelectedEpisodeForStreams,
errorDetails, setErrorDetails,
selectedCastMember, setSelectedCastMember
};
};

View file

@ -1,103 +0,0 @@
import { useEffect, useRef, useCallback } from 'react';
import { StatusBar, Dimensions, AppState, InteractionManager } from 'react-native';
import * as Brightness from 'expo-brightness';
import * as ScreenOrientation from 'expo-screen-orientation';
import { logger } from '../../../../utils/logger';
import { useFocusEffect } from '@react-navigation/native';
export const usePlayerSetup = (
setScreenDimensions: (dim: any) => void,
setVolume: (vol: number) => void,
setBrightness: (bri: number) => void,
isOpeningAnimationComplete: boolean
) => {
const isAppBackgrounded = useRef(false);
const enableImmersiveMode = () => {
StatusBar.setHidden(true, 'none');
};
const disableImmersiveMode = () => {
StatusBar.setHidden(false, 'fade');
};
useFocusEffect(
useCallback(() => {
if (isOpeningAnimationComplete) {
enableImmersiveMode();
}
return () => { };
}, [isOpeningAnimationComplete])
);
useEffect(() => {
// Initial Setup
const subscription = Dimensions.addEventListener('change', ({ screen }) => {
setScreenDimensions(screen);
if (isOpeningAnimationComplete) {
enableImmersiveMode();
}
});
StatusBar.setHidden(true, 'none');
if (isOpeningAnimationComplete) {
enableImmersiveMode();
}
// Initialize volume (KSPlayer uses 0-100)
setVolume(100);
// Initialize Brightness
const initBrightness = () => {
InteractionManager.runAfterInteractions(async () => {
try {
const currentBrightness = await Brightness.getBrightnessAsync();
setBrightness(currentBrightness);
} catch (error) {
logger.warn('[usePlayerSetup] Error getting initial brightness:', error);
setBrightness(1.0);
}
});
};
initBrightness();
return () => {
subscription?.remove();
disableImmersiveMode();
};
}, [isOpeningAnimationComplete]);
// Handle Orientation (Lock to Landscape after opening)
useEffect(() => {
if (isOpeningAnimationComplete) {
const task = InteractionManager.runAfterInteractions(() => {
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE)
.then(() => {
if (__DEV__) logger.log('[VideoPlayer] Locked to landscape orientation');
})
.catch((error) => {
logger.warn('[VideoPlayer] Failed to lock orientation:', error);
});
});
return () => task.cancel();
}
}, [isOpeningAnimationComplete]);
// Handle App State
useEffect(() => {
const onAppStateChange = (state: string) => {
if (state === 'active') {
isAppBackgrounded.current = false;
if (isOpeningAnimationComplete) {
enableImmersiveMode();
}
} else if (state === 'background' || state === 'inactive') {
isAppBackgrounded.current = true;
}
};
const sub = AppState.addEventListener('change', onAppStateChange);
return () => sub.remove();
}, [isOpeningAnimationComplete]);
return { isAppBackgrounded };
};

View file

@ -1,83 +0,0 @@
import { useState, useRef } from 'react';
import { Dimensions, Platform } from 'react-native';
// Use a specific type for resizeMode that matches what KSPlayerComponent supports
type PlayerResizeMode = 'contain' | 'cover' | 'stretch';
export const usePlayerState = () => {
// Playback State
const [paused, setPaused] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const [buffered, setBuffered] = useState(0);
const [isBuffering, setIsBuffering] = useState(false);
const [isVideoLoaded, setIsVideoLoaded] = useState(false);
const [isPlayerReady, setIsPlayerReady] = useState(false);
// UI State
const [showControls, setShowControls] = useState(true);
const [resizeMode, setResizeMode] = useState<PlayerResizeMode>('contain');
const [videoAspectRatio, setVideoAspectRatio] = useState<number | null>(null);
const [is16by9Content, setIs16by9Content] = useState(false);
const screenData = Dimensions.get('screen');
const [screenDimensions, setScreenDimensions] = useState(screenData);
// Zoom State
const [zoomScale, setZoomScale] = useState(1);
const [zoomTranslateX, setZoomTranslateX] = useState(0);
const [zoomTranslateY, setZoomTranslateY] = useState(0);
const [lastZoomScale, setLastZoomScale] = useState(1);
const [lastTranslateX, setLastTranslateX] = useState(0);
const [lastTranslateY, setLastTranslateY] = useState(0);
// AirPlay State
const [isAirPlayActive, setIsAirPlayActive] = useState<boolean>(false);
const [allowsAirPlay, setAllowsAirPlay] = useState<boolean>(true);
// Logic State
const isSeeking = useRef(false);
const isDragging = useRef(false);
const isMounted = useRef(true);
const seekDebounceTimer = useRef<NodeJS.Timeout | null>(null);
const pendingSeekValue = useRef<number | null>(null);
const lastSeekTime = useRef<number>(0);
const wasPlayingBeforeDragRef = useRef<boolean>(false);
// Helper for iPad/macOS fullscreen
const isIPad = Platform.OS === 'ios' && (screenData.width > 1000 || screenData.height > 1000);
const isMacOS = Platform.OS === 'ios' && Platform.isPad === true;
const shouldUseFullscreen = isIPad || isMacOS;
const windowData = Dimensions.get('window');
const effectiveDimensions = shouldUseFullscreen ? windowData : screenDimensions;
return {
paused, setPaused,
currentTime, setCurrentTime,
duration, setDuration,
buffered, setBuffered,
isBuffering, setIsBuffering,
isVideoLoaded, setIsVideoLoaded,
isPlayerReady, setIsPlayerReady,
showControls, setShowControls,
resizeMode, setResizeMode,
videoAspectRatio, setVideoAspectRatio,
is16by9Content, setIs16by9Content,
screenDimensions, setScreenDimensions,
zoomScale, setZoomScale,
zoomTranslateX, setZoomTranslateX,
zoomTranslateY, setZoomTranslateY,
lastZoomScale, setLastZoomScale,
lastTranslateX, setLastTranslateX,
lastTranslateY, setLastTranslateY,
isAirPlayActive, setIsAirPlayActive,
allowsAirPlay, setAllowsAirPlay,
isSeeking,
isDragging,
isMounted,
seekDebounceTimer,
pendingSeekValue,
lastSeekTime,
wasPlayingBeforeDragRef,
effectiveDimensions
};
};

View file

@ -1,38 +0,0 @@
import { useState, useMemo, useCallback } from 'react';
import { AudioTrack, TextTrack } from '../../utils/playerTypes';
export const usePlayerTracks = () => {
const [audioTracks, setAudioTracks] = useState<AudioTrack[]>([]);
const [selectedAudioTrack, setSelectedAudioTrack] = useState<number | null>(null);
const [textTracks, setTextTracks] = useState<TextTrack[]>([]);
const [selectedTextTrack, setSelectedTextTrack] = useState<number>(-1);
const [ksAudioTracks, setKsAudioTracks] = useState<Array<{ id: number, name: string, language?: string }>>([]);
const [ksTextTracks, setKsTextTracks] = useState<Array<{ id: number, name: string, language?: string }>>([]);
// Derived states or logic
const hasAudioTracks = audioTracks.length > 0;
const hasTextTracks = textTracks.length > 0;
// Track selection functions
const selectAudioTrack = useCallback((trackId: number) => {
setSelectedAudioTrack(trackId);
}, []);
const selectTextTrack = useCallback((trackId: number) => {
setSelectedTextTrack(trackId);
}, []);
return {
audioTracks, setAudioTracks,
selectedAudioTrack, setSelectedAudioTrack,
textTracks, setTextTracks,
selectedTextTrack, setSelectedTextTrack,
ksAudioTracks, setKsAudioTracks,
ksTextTracks, setKsTextTracks,
hasAudioTracks,
hasTextTracks,
selectAudioTrack,
selectTextTrack
};
};

View file

@ -1,93 +0,0 @@
import { useState, useRef, useCallback, useEffect } from 'react';
import { Animated } from 'react-native';
import { mmkvStorage } from '../../../../services/mmkvStorage';
import { logger } from '../../../../utils/logger';
const SPEED_SETTINGS_KEY = '@nuvio_speed_settings';
export const useSpeedControl = (initialSpeed: number = 1.0) => {
const [playbackSpeed, setPlaybackSpeed] = useState<number>(initialSpeed);
const [holdToSpeedEnabled, setHoldToSpeedEnabled] = useState(true);
const [holdToSpeedValue, setHoldToSpeedValue] = useState(2.0);
const [isSpeedBoosted, setIsSpeedBoosted] = useState(false);
const [originalSpeed, setOriginalSpeed] = useState<number>(initialSpeed);
const [showSpeedActivatedOverlay, setShowSpeedActivatedOverlay] = useState(false);
const speedActivatedOverlayOpacity = useRef(new Animated.Value(0)).current;
// Load Settings
useEffect(() => {
const loadSettings = async () => {
try {
const saved = await mmkvStorage.getItem(SPEED_SETTINGS_KEY);
if (saved) {
const settings = JSON.parse(saved);
if (typeof settings.holdToSpeedEnabled === 'boolean') setHoldToSpeedEnabled(settings.holdToSpeedEnabled);
if (typeof settings.holdToSpeedValue === 'number') setHoldToSpeedValue(settings.holdToSpeedValue);
}
} catch (e) {
logger.warn('[useSpeedControl] Error loading settings', e);
}
};
loadSettings();
}, []);
// Save Settings
useEffect(() => {
const saveSettings = async () => {
try {
await mmkvStorage.setItem(SPEED_SETTINGS_KEY, JSON.stringify({
holdToSpeedEnabled,
holdToSpeedValue
}));
} catch (e) { }
};
saveSettings();
}, [holdToSpeedEnabled, holdToSpeedValue]);
const activateSpeedBoost = useCallback(() => {
if (!holdToSpeedEnabled || isSpeedBoosted || playbackSpeed === holdToSpeedValue) return;
setOriginalSpeed(playbackSpeed);
setPlaybackSpeed(holdToSpeedValue);
setIsSpeedBoosted(true);
setShowSpeedActivatedOverlay(true);
Animated.timing(speedActivatedOverlayOpacity, {
toValue: 1,
duration: 200,
useNativeDriver: true
}).start();
setTimeout(() => {
Animated.timing(speedActivatedOverlayOpacity, {
toValue: 0,
duration: 300,
useNativeDriver: true
}).start(() => setShowSpeedActivatedOverlay(false));
}, 2000);
}, [holdToSpeedEnabled, isSpeedBoosted, playbackSpeed, holdToSpeedValue]);
const deactivateSpeedBoost = useCallback(() => {
if (isSpeedBoosted) {
setPlaybackSpeed(originalSpeed);
setIsSpeedBoosted(false);
Animated.timing(speedActivatedOverlayOpacity, { toValue: 0, duration: 100, useNativeDriver: true }).start();
}
}, [isSpeedBoosted, originalSpeed]);
return {
playbackSpeed,
setPlaybackSpeed,
holdToSpeedEnabled,
setHoldToSpeedEnabled,
holdToSpeedValue,
setHoldToSpeedValue,
isSpeedBoosted,
activateSpeedBoost,
deactivateSpeedBoost,
showSpeedActivatedOverlay,
speedActivatedOverlayOpacity
};
};

View file

@ -1,120 +0,0 @@
import { useState, useEffect, useRef } from 'react';
import { storageService } from '../../../../services/storageService';
import { logger } from '../../../../utils/logger';
import { useSettings } from '../../../../hooks/useSettings';
export const useWatchProgress = (
id: string | undefined,
type: string | undefined,
episodeId: string | undefined,
currentTime: number,
duration: number,
paused: boolean,
traktAutosync: any,
seekToTime: (time: number) => void
) => {
const [resumePosition, setResumePosition] = useState<number | null>(null);
const [savedDuration, setSavedDuration] = useState<number | null>(null);
const [initialPosition, setInitialPosition] = useState<number | null>(null);
const [showResumeOverlay, setShowResumeOverlay] = useState(false);
const [progressSaveInterval, setProgressSaveInterval] = useState<NodeJS.Timeout | null>(null);
const { settings: appSettings } = useSettings();
const initialSeekTargetRef = useRef<number | null>(null);
// Values refs for unmount cleanup
const currentTimeRef = useRef(currentTime);
const durationRef = useRef(duration);
useEffect(() => {
currentTimeRef.current = currentTime;
}, [currentTime]);
useEffect(() => {
durationRef.current = duration;
}, [duration]);
// Load Watch Progress
useEffect(() => {
const loadWatchProgress = async () => {
if (id && type) {
try {
const savedProgress = await storageService.getWatchProgress(id, type, episodeId);
if (savedProgress) {
const progressPercent = (savedProgress.currentTime / savedProgress.duration) * 100;
if (progressPercent < 85) {
setResumePosition(savedProgress.currentTime);
setSavedDuration(savedProgress.duration);
if (appSettings.alwaysResume) {
setInitialPosition(savedProgress.currentTime);
initialSeekTargetRef.current = savedProgress.currentTime;
seekToTime(savedProgress.currentTime);
} else {
setShowResumeOverlay(true);
}
}
}
} catch (error) {
logger.error('[useWatchProgress] Error loading watch progress:', error);
}
}
};
loadWatchProgress();
}, [id, type, episodeId, appSettings.alwaysResume]);
const saveWatchProgress = async () => {
if (id && type && currentTimeRef.current > 0 && durationRef.current > 0) {
const progress = {
currentTime: currentTimeRef.current,
duration: durationRef.current,
lastUpdated: Date.now()
};
try {
await storageService.setWatchProgress(id, type, progress, episodeId);
await traktAutosync.handleProgressUpdate(currentTimeRef.current, durationRef.current);
} catch (error) {
logger.error('[useWatchProgress] Error saving watch progress:', error);
}
}
};
// Save Interval
useEffect(() => {
if (id && type && !paused && duration > 0) {
if (progressSaveInterval) clearInterval(progressSaveInterval);
const interval = setInterval(() => {
saveWatchProgress();
}, 10000);
setProgressSaveInterval(interval);
return () => {
clearInterval(interval);
setProgressSaveInterval(null);
};
}
}, [id, type, paused, currentTime, duration]);
// Unmount Save
useEffect(() => {
return () => {
if (id && type && durationRef.current > 0) {
saveWatchProgress();
traktAutosync.handlePlaybackEnd(currentTimeRef.current, durationRef.current, 'unmount');
}
};
}, [id, type]);
return {
resumePosition,
savedDuration,
initialPosition,
setInitialPosition,
showResumeOverlay,
setShowResumeOverlay,
saveWatchProgress,
initialSeekTargetRef
};
};