mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
removed dead code
This commit is contained in:
parent
8588aca948
commit
39498f78b7
13 changed files with 15 additions and 805 deletions
1
libmpv-android
Submodule
1
libmpv-android
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 8c4778b5aad441bb0449a7f9b3d6d827fd3d6a2a
|
||||
1
mpv-android
Submodule
1
mpv-android
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 118cd1ed3d498265e44230e5dbb015bdd59f9dad
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
};
|
||||
|
|
@ -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 };
|
||||
};
|
||||
|
|
@ -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
|
||||
};
|
||||
};
|
||||
|
|
@ -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
|
||||
};
|
||||
};
|
||||
|
|
@ -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
|
||||
};
|
||||
};
|
||||
|
|
@ -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 };
|
||||
};
|
||||
|
|
@ -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
|
||||
};
|
||||
};
|
||||
|
|
@ -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
|
||||
};
|
||||
};
|
||||
|
|
@ -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
|
||||
};
|
||||
};
|
||||
|
|
@ -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
|
||||
};
|
||||
};
|
||||
Loading…
Reference in a new issue