fixed heating and rerender of playercontrols

This commit is contained in:
tapframe 2026-01-10 15:24:18 +05:30
parent 0df9493551
commit 75602a8658
5 changed files with 77 additions and 40 deletions

View file

@ -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');

View file

@ -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;

View file

@ -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);
}; };

View file

@ -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}

View file

@ -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) => {