mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
fixed heating and rerender of playercontrols
This commit is contained in:
parent
0df9493551
commit
75602a8658
5 changed files with 77 additions and 40 deletions
|
|
@ -643,6 +643,46 @@ const KSPlayerCore: React.FC = () => {
|
||||||
controls.seekToTime(value);
|
controls.seekToTime(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Progress update throttling:
|
||||||
|
// - Always keep a ref updated (cheap, no re-render)
|
||||||
|
// - Only push to React state at a lower frequency (prevents full-tree re-render storms)
|
||||||
|
const currentTimeRef = useRef<number>(currentTime);
|
||||||
|
const bufferedRef = useRef<number>(buffered);
|
||||||
|
const lastUiProgressUpdateAtRef = useRef<number>(0);
|
||||||
|
useEffect(() => {
|
||||||
|
currentTimeRef.current = currentTime;
|
||||||
|
}, [currentTime]);
|
||||||
|
useEffect(() => {
|
||||||
|
bufferedRef.current = buffered;
|
||||||
|
}, [buffered]);
|
||||||
|
|
||||||
|
const handleProgress = useCallback((d: any) => {
|
||||||
|
const t = d?.currentTime ?? 0;
|
||||||
|
const newBuffered = d?.buffered ?? 0;
|
||||||
|
|
||||||
|
currentTimeRef.current = t;
|
||||||
|
lastPlaybackTimeRef.current = t || 0;
|
||||||
|
|
||||||
|
// Keep buffered updates coarse to avoid churn
|
||||||
|
if (Math.abs(newBuffered - bufferedRef.current) > 1) {
|
||||||
|
bufferedRef.current = newBuffered;
|
||||||
|
setBuffered(newBuffered);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't fight the slider while the user is dragging it
|
||||||
|
if (isSliderDragging) return;
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const needsTightUi =
|
||||||
|
showControls || paused || customSubs.useCustomSubtitles;
|
||||||
|
const minIntervalMs = needsTightUi ? 250 : 1000;
|
||||||
|
|
||||||
|
if (now - lastUiProgressUpdateAtRef.current < minIntervalMs) return;
|
||||||
|
lastUiProgressUpdateAtRef.current = now;
|
||||||
|
|
||||||
|
setCurrentTime(t);
|
||||||
|
}, [isSliderDragging, paused, showControls, customSubs.useCustomSubtitles, setBuffered, setCurrentTime]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, backgroundColor: '#000000' }}>
|
<View style={{ flex: 1, backgroundColor: '#000000' }}>
|
||||||
<StatusBar hidden={true} />
|
<StatusBar hidden={true} />
|
||||||
|
|
@ -695,17 +735,7 @@ const KSPlayerCore: React.FC = () => {
|
||||||
hasAviOSFailed.current = false;
|
hasAviOSFailed.current = false;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onProgress={(d) => {
|
onProgress={handleProgress}
|
||||||
if (!isSliderDragging) {
|
|
||||||
setCurrentTime(d.currentTime);
|
|
||||||
}
|
|
||||||
lastPlaybackTimeRef.current = d.currentTime || 0;
|
|
||||||
// Only update buffered if it changed by more than 0.5s to reduce re-renders
|
|
||||||
const newBuffered = d.buffered || 0;
|
|
||||||
if (Math.abs(newBuffered - buffered) > 0.5) {
|
|
||||||
setBuffered(newBuffered);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onEnd={async () => {
|
onEnd={async () => {
|
||||||
setCurrentTime(duration);
|
setCurrentTime(duration);
|
||||||
await traktAutosync.handlePlaybackEnd(duration, duration, 'ended');
|
await traktAutosync.handlePlaybackEnd(duration, duration, 'ended');
|
||||||
|
|
@ -749,16 +779,7 @@ const KSPlayerCore: React.FC = () => {
|
||||||
: ({ type: 'disabled' } as SelectedTrack))
|
: ({ type: 'disabled' } as SelectedTrack))
|
||||||
}
|
}
|
||||||
onLoad={onLoad}
|
onLoad={onLoad}
|
||||||
onProgress={(d) => {
|
onProgress={handleProgress}
|
||||||
if (!isSliderDragging) {
|
|
||||||
setCurrentTime(d.currentTime);
|
|
||||||
}
|
|
||||||
lastPlaybackTimeRef.current = d.currentTime || 0;
|
|
||||||
const newBuffered = d.buffered || 0;
|
|
||||||
if (Math.abs(newBuffered - buffered) > 0.5) {
|
|
||||||
setBuffered(newBuffered);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onEnd={async () => {
|
onEnd={async () => {
|
||||||
setCurrentTime(duration);
|
setCurrentTime(duration);
|
||||||
await traktAutosync.handlePlaybackEnd(duration, duration, 'ended');
|
await traktAutosync.handlePlaybackEnd(duration, duration, 'ended');
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ interface PlayerControlsProps {
|
||||||
useExoPlayer?: boolean;
|
useExoPlayer?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
const PlayerControlsInner: React.FC<PlayerControlsProps> = ({
|
||||||
showControls,
|
showControls,
|
||||||
fadeAnim,
|
fadeAnim,
|
||||||
paused,
|
paused,
|
||||||
|
|
@ -625,4 +625,15 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Key optimization:
|
||||||
|
// When controls are hidden, we don't need to re-render this tree on every progress tick.
|
||||||
|
// This removes a large chunk of unnecessary work (and heat) during playback.
|
||||||
|
export const PlayerControls = React.memo(
|
||||||
|
PlayerControlsInner,
|
||||||
|
(prev, next) => {
|
||||||
|
if (!prev.showControls && !next.showControls) return true;
|
||||||
|
return false; // when visible, allow normal updates
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default PlayerControls;
|
export default PlayerControls;
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export const useWatchProgress = (
|
||||||
const [savedDuration, setSavedDuration] = useState<number | null>(null);
|
const [savedDuration, setSavedDuration] = useState<number | null>(null);
|
||||||
const [initialPosition, setInitialPosition] = useState<number | null>(null);
|
const [initialPosition, setInitialPosition] = useState<number | null>(null);
|
||||||
const [showResumeOverlay, setShowResumeOverlay] = useState(false);
|
const [showResumeOverlay, setShowResumeOverlay] = useState(false);
|
||||||
const [progressSaveInterval, setProgressSaveInterval] = useState<NodeJS.Timeout | null>(null);
|
const progressSaveIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
const { settings: appSettings } = useSettings();
|
const { settings: appSettings } = useSettings();
|
||||||
const initialSeekTargetRef = useRef<number | null>(null);
|
const initialSeekTargetRef = useRef<number | null>(null);
|
||||||
|
|
@ -119,7 +119,7 @@ export const useWatchProgress = (
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
await storageService.setWatchProgress(id, type, progress, episodeId);
|
await storageService.setWatchProgress(id, type, progress, episodeId);
|
||||||
await traktAutosync.handleProgressUpdate(currentTimeRef.current, durationRef.current);
|
await traktAutosyncRef.current.handleProgressUpdate(currentTimeRef.current, durationRef.current);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[useWatchProgress] Error saving watch progress:', error);
|
logger.error('[useWatchProgress] Error saving watch progress:', error);
|
||||||
}
|
}
|
||||||
|
|
@ -128,20 +128,26 @@ export const useWatchProgress = (
|
||||||
|
|
||||||
// Save Interval
|
// Save Interval
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id && type && !paused && duration > 0) {
|
// Important: do NOT depend on currentTime here; we use refs for currentTime/duration.
|
||||||
if (progressSaveInterval) clearInterval(progressSaveInterval);
|
// Otherwise we recreate the interval on every progress tick, causing heavy re-renders.
|
||||||
|
if (progressSaveIntervalRef.current) {
|
||||||
|
clearInterval(progressSaveIntervalRef.current);
|
||||||
|
progressSaveIntervalRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
if (id && type && !paused && duration > 0) {
|
||||||
|
progressSaveIntervalRef.current = setInterval(() => {
|
||||||
saveWatchProgress();
|
saveWatchProgress();
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
setProgressSaveInterval(interval);
|
|
||||||
return () => {
|
|
||||||
clearInterval(interval);
|
|
||||||
setProgressSaveInterval(null);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}, [id, type, paused, currentTime, duration]);
|
|
||||||
|
return () => {
|
||||||
|
if (progressSaveIntervalRef.current) {
|
||||||
|
clearInterval(progressSaveIntervalRef.current);
|
||||||
|
progressSaveIntervalRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [id, type, paused, duration, episodeId, addonId]);
|
||||||
|
|
||||||
// Unmount Save - deferred to allow navigation to complete first
|
// Unmount Save - deferred to allow navigation to complete first
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -151,7 +157,7 @@ export const useWatchProgress = (
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (id && type && durationRef.current > 0) {
|
if (id && type && durationRef.current > 0) {
|
||||||
saveWatchProgress();
|
saveWatchProgress();
|
||||||
traktAutosync.handlePlaybackEnd(currentTimeRef.current, durationRef.current, 'unmount');
|
traktAutosyncRef.current.handlePlaybackEnd(currentTimeRef.current, durationRef.current, 'unmount');
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,9 @@ export const AVPlayerSurface: React.FC<AVPlayerSurfaceProps> = ({
|
||||||
onEnd={onEnd}
|
onEnd={onEnd}
|
||||||
onError={onError}
|
onError={onError}
|
||||||
onBuffer={(b: any) => onBuffer(!!b?.isBuffering)}
|
onBuffer={(b: any) => onBuffer(!!b?.isBuffering)}
|
||||||
progressUpdateInterval={250}
|
// 250ms progress updates can cause excessive JS work and device heating.
|
||||||
|
// We throttle UI updates in JS anyway; a slightly higher interval reduces event pressure.
|
||||||
|
progressUpdateInterval={500}
|
||||||
// Keep background behavior consistent with the rest of the player logic
|
// Keep background behavior consistent with the rest of the player logic
|
||||||
playInBackground={false}
|
playInBackground={false}
|
||||||
playWhenInactive={true}
|
playWhenInactive={true}
|
||||||
|
|
|
||||||
|
|
@ -97,10 +97,7 @@ export const KSPlayerSurface: React.FC<KSPlayerSurfaceProps> = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Debug: log textTrack prop changes
|
// Note: avoid debug logging here; this component receives frequent updates during playback.
|
||||||
React.useEffect(() => {
|
|
||||||
console.log('[KSPlayerSurface] textTrack prop changed to:', textTrack);
|
|
||||||
}, [textTrack]);
|
|
||||||
|
|
||||||
// Handle buffering - MPVPlayerComponent exposes buffering only via events we wire up
|
// Handle buffering - MPVPlayerComponent exposes buffering only via events we wire up
|
||||||
const handleBuffering = (data: any) => {
|
const handleBuffering = (data: any) => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue