mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-07-15 08:52:59 +01:00
ExoPlayer:
- Pass the resume position into setMediaSource() instead of prepare()-ing
from 0 and then seekTo()-ing, which replayed the first few seconds before
jumping to the resume point.
- Drop the redundant initial seek that mirrors the resume position (fired by
the JS direct-player layer once tracks are ready). ExoPlayer re-buffers on
every seek — even a no-op to the current position — so it stuttered startup.
MPV (Android TV):
- Recover the video pipeline when returning from the screensaver / app
background while paused. TV uses zero-copy hwdec=mediacodec, which binds
MediaCodec directly to the display surface; when the screensaver invalidates
that surface the decoder is left bound to dead buffers and mpv disables the
video track. Register TV-only activity-lifecycle callbacks and reload at the
cached position on resume to recreate the decoder against the live surface
(Android counterpart to iOS's performDecoderReset()).
Video time controls:
- Initialize remainingTime to 0 instead of Infinity and guard non-finite
values, so the first paint shows "0:00" (and "—" for the end time) rather
than "Infinityh NaNm NaNs" and an Invalid Date before the first progress
update
Signed-off-by: Lance Chant <13349722+lancechant@users.noreply.github.com>
74 lines
2.1 KiB
TypeScript
74 lines
2.1 KiB
TypeScript
import { useCallback, useRef, useState } from "react";
|
|
import {
|
|
runOnJS,
|
|
type SharedValue,
|
|
useAnimatedReaction,
|
|
} from "react-native-reanimated";
|
|
|
|
interface UseVideoTimeProps {
|
|
progress: SharedValue<number>;
|
|
max: SharedValue<number>;
|
|
isSeeking: SharedValue<boolean>;
|
|
}
|
|
|
|
/**
|
|
* Hook to manage video time display.
|
|
* MPV player uses milliseconds for time values.
|
|
*/
|
|
export function useVideoTime({ progress, max, isSeeking }: UseVideoTimeProps) {
|
|
const [currentTime, setCurrentTime] = useState(0);
|
|
// Start at 0 (not Infinity) so the controls' first paint — before the first
|
|
// progress/max update — shows "0:00" instead of formatting Infinity into
|
|
// "Infinityh NaNm NaNs" and an Invalid Date for "ends at".
|
|
const [remainingTime, setRemainingTime] = useState(0);
|
|
|
|
const lastCurrentTimeRef = useRef(0);
|
|
const lastRemainingTimeRef = useRef(0);
|
|
|
|
const updateTimes = useCallback(
|
|
(currentProgress: number, maxValue: number) => {
|
|
// MPV uses milliseconds
|
|
const current = currentProgress;
|
|
const remaining = maxValue - currentProgress;
|
|
|
|
// Only update state if the displayed time actually changed (avoid sub-second updates)
|
|
const currentSeconds = Math.floor(current / 1000);
|
|
const remainingSeconds = Math.floor(remaining / 1000);
|
|
const lastCurrentSeconds = Math.floor(lastCurrentTimeRef.current / 1000);
|
|
const lastRemainingSeconds = Math.floor(
|
|
lastRemainingTimeRef.current / 1000,
|
|
);
|
|
|
|
if (
|
|
currentSeconds !== lastCurrentSeconds ||
|
|
remainingSeconds !== lastRemainingSeconds
|
|
) {
|
|
setCurrentTime(current);
|
|
setRemainingTime(remaining);
|
|
lastCurrentTimeRef.current = current;
|
|
lastRemainingTimeRef.current = remaining;
|
|
}
|
|
},
|
|
[],
|
|
);
|
|
|
|
useAnimatedReaction(
|
|
() => ({
|
|
progress: progress.value,
|
|
max: max.value,
|
|
isSeeking: isSeeking.value,
|
|
}),
|
|
(result) => {
|
|
if (!result.isSeeking) {
|
|
runOnJS(updateTimes)(result.progress, result.max);
|
|
}
|
|
},
|
|
[updateTimes],
|
|
);
|
|
|
|
return {
|
|
currentTime,
|
|
remainingTime,
|
|
};
|
|
}
|