mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 08:41:57 +00:00
videoplayer fix
This commit is contained in:
parent
877a4c5dc6
commit
c710a173f2
2 changed files with 82 additions and 1 deletions
|
|
@ -276,6 +276,9 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
const loadStartAtRef = useRef<number | null>(null);
|
const loadStartAtRef = useRef<number | null>(null);
|
||||||
const firstFrameAtRef = useRef<number | null>(null);
|
const firstFrameAtRef = useRef<number | null>(null);
|
||||||
|
|
||||||
|
// iOS playback state tracking for system interruptions
|
||||||
|
const wasPlayingBeforeIOSInterruptionRef = useRef<boolean>(false);
|
||||||
|
|
||||||
// Pause overlay state
|
// Pause overlay state
|
||||||
const [showPauseOverlay, setShowPauseOverlay] = useState(false);
|
const [showPauseOverlay, setShowPauseOverlay] = useState(false);
|
||||||
const pauseOverlayTimerRef = useRef<NodeJS.Timeout | null>(null);
|
const pauseOverlayTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
@ -615,13 +618,34 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
const onAppStateChange = (state: string) => {
|
const onAppStateChange = (state: string) => {
|
||||||
if (state === 'active') {
|
if (state === 'active') {
|
||||||
enableImmersiveMode();
|
enableImmersiveMode();
|
||||||
|
// On iOS, if we were playing before system interruption and the app becomes active again,
|
||||||
|
// ensure playback resumes (handles status bar pull-down case)
|
||||||
|
if (Platform.OS === 'ios' && wasPlayingBeforeIOSInterruptionRef.current && isPlayerReady) {
|
||||||
|
logger.log('[AndroidVideoPlayer] iOS app active - resuming playback after system interruption');
|
||||||
|
// Small delay to allow system UI to settle
|
||||||
|
setTimeout(() => {
|
||||||
|
if (isMounted.current && wasPlayingBeforeIOSInterruptionRef.current) {
|
||||||
|
setPaused(false); // Resume playback
|
||||||
|
wasPlayingBeforeIOSInterruptionRef.current = false; // Reset flag
|
||||||
|
}
|
||||||
|
}, 300); // Slightly longer delay for iOS
|
||||||
|
}
|
||||||
|
} else if (state === 'background' || state === 'inactive') {
|
||||||
|
// On iOS, when app goes inactive (like status bar pull), track if we were playing
|
||||||
|
if (Platform.OS === 'ios') {
|
||||||
|
wasPlayingBeforeIOSInterruptionRef.current = !paused;
|
||||||
|
if (!paused) {
|
||||||
|
logger.log('[AndroidVideoPlayer] iOS app inactive - tracking playing state for resume');
|
||||||
|
setPaused(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const sub = AppState.addEventListener('change', onAppStateChange);
|
const sub = AppState.addEventListener('change', onAppStateChange);
|
||||||
return () => {
|
return () => {
|
||||||
sub.remove();
|
sub.remove();
|
||||||
};
|
};
|
||||||
}, []);
|
}, [paused, isPlayerReady]);
|
||||||
|
|
||||||
const startOpeningAnimation = () => {
|
const startOpeningAnimation = () => {
|
||||||
// Logo entrance animation - optimized for faster appearance
|
// Logo entrance animation - optimized for faster appearance
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,11 @@ const VideoPlayer: React.FC = () => {
|
||||||
const controlsTimeout = useRef<NodeJS.Timeout | null>(null);
|
const controlsTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||||
const [isSyncingBeforeClose, setIsSyncingBeforeClose] = useState(false);
|
const [isSyncingBeforeClose, setIsSyncingBeforeClose] = useState(false);
|
||||||
|
|
||||||
|
// Silent startup-timeout retry state
|
||||||
|
const startupRetryCountRef = useRef(0);
|
||||||
|
const startupRetryTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const MAX_STARTUP_RETRIES = 3;
|
||||||
|
|
||||||
// Pause overlay state
|
// Pause overlay state
|
||||||
const [showPauseOverlay, setShowPauseOverlay] = useState(false);
|
const [showPauseOverlay, setShowPauseOverlay] = useState(false);
|
||||||
const pauseOverlayTimerRef = useRef<NodeJS.Timeout | null>(null);
|
const pauseOverlayTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
@ -979,6 +984,12 @@ const VideoPlayer: React.FC = () => {
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
logger.log('[VideoPlayer] Video loaded:', data);
|
logger.log('[VideoPlayer] Video loaded:', data);
|
||||||
}
|
}
|
||||||
|
// Clear any pending startup silent retry timers and counters on success
|
||||||
|
if (startupRetryTimerRef.current) {
|
||||||
|
clearTimeout(startupRetryTimerRef.current);
|
||||||
|
startupRetryTimerRef.current = null;
|
||||||
|
}
|
||||||
|
startupRetryCountRef.current = 0;
|
||||||
if (!isMounted.current) {
|
if (!isMounted.current) {
|
||||||
logger.warn('[VideoPlayer] Component unmounted, skipping onLoad');
|
logger.warn('[VideoPlayer] Component unmounted, skipping onLoad');
|
||||||
return;
|
return;
|
||||||
|
|
@ -1374,6 +1385,48 @@ const VideoPlayer: React.FC = () => {
|
||||||
try {
|
try {
|
||||||
logger.error('[VideoPlayer] Playback Error:', error);
|
logger.error('[VideoPlayer] Playback Error:', error);
|
||||||
|
|
||||||
|
// Detect KSPlayer startup timeout and silently retry without UI
|
||||||
|
const errText = typeof error === 'string'
|
||||||
|
? error
|
||||||
|
: (error?.message || error?.error?.message || error?.title || '');
|
||||||
|
const isStartupTimeout = /timeout/i.test(errText) && /stream.*ready/i.test(errText);
|
||||||
|
if (isStartupTimeout && !isVideoLoaded) {
|
||||||
|
// Suppress any error modal and retry silently
|
||||||
|
if (errorTimeoutRef.current) {
|
||||||
|
clearTimeout(errorTimeoutRef.current);
|
||||||
|
errorTimeoutRef.current = null;
|
||||||
|
}
|
||||||
|
setShowErrorModal(false);
|
||||||
|
|
||||||
|
const attempt = startupRetryCountRef.current;
|
||||||
|
if (attempt < MAX_STARTUP_RETRIES) {
|
||||||
|
const backoffMs = [4000, 8000, 12000][attempt] ?? 8000;
|
||||||
|
startupRetryCountRef.current = attempt + 1;
|
||||||
|
logger.warn(`[VideoPlayer] Startup timeout; retrying (${attempt + 1}/${MAX_STARTUP_RETRIES}) in ${backoffMs}ms`);
|
||||||
|
|
||||||
|
if (startupRetryTimerRef.current) {
|
||||||
|
clearTimeout(startupRetryTimerRef.current);
|
||||||
|
}
|
||||||
|
startupRetryTimerRef.current = setTimeout(() => {
|
||||||
|
if (!ksPlayerRef.current) return;
|
||||||
|
try {
|
||||||
|
// Reload the same source silently using native bridge
|
||||||
|
ksPlayerRef.current.setSource({
|
||||||
|
uri: currentStreamUrl,
|
||||||
|
headers: headers && Object.keys(headers).length > 0 ? headers : undefined
|
||||||
|
});
|
||||||
|
// Ensure playback resumes if not paused
|
||||||
|
ksPlayerRef.current.setPaused(paused);
|
||||||
|
logger.log('[VideoPlayer] Retried source load via KSPlayer.setSource');
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('[VideoPlayer] Error during silent retry setSource:', e);
|
||||||
|
}
|
||||||
|
}, backoffMs);
|
||||||
|
return; // Exit handler; do not show UI
|
||||||
|
}
|
||||||
|
logger.error('[VideoPlayer] Max startup retries reached; proceeding to normal error handling');
|
||||||
|
}
|
||||||
|
|
||||||
// Check for audio codec errors (TrueHD, DTS, Dolby, etc.)
|
// Check for audio codec errors (TrueHD, DTS, Dolby, etc.)
|
||||||
const isAudioCodecError =
|
const isAudioCodecError =
|
||||||
(error?.message && /(trhd|truehd|true\s?hd|dts|dolby|atmos|e-ac3|ac3)/i.test(error.message)) ||
|
(error?.message && /(trhd|truehd|true\s?hd|dts|dolby|atmos|e-ac3|ac3)/i.test(error.message)) ||
|
||||||
|
|
@ -2063,6 +2116,10 @@ const VideoPlayer: React.FC = () => {
|
||||||
if (brightnessOverlayTimeout.current) {
|
if (brightnessOverlayTimeout.current) {
|
||||||
clearTimeout(brightnessOverlayTimeout.current);
|
clearTimeout(brightnessOverlayTimeout.current);
|
||||||
}
|
}
|
||||||
|
if (startupRetryTimerRef.current) {
|
||||||
|
clearTimeout(startupRetryTimerRef.current);
|
||||||
|
startupRetryTimerRef.current = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue