Preserve scrub zoom state while addressing maintainer feedback

This commit is contained in:
nrlcode 2026-03-25 13:33:06 -07:00
parent 9eb0a89de0
commit 2e48fa47ac
4 changed files with 27 additions and 26 deletions

View File

@ -68,7 +68,7 @@ export default function HlsVideoPlayer({
videoClassName,
containerRef,
visible,
showControls = visible,
showControls = true,
currentSource,
hotKeys,
supportsFullscreen,
@ -290,7 +290,7 @@ export default function HlsVideoPlayer({
)}
video={videoRef.current}
isPlaying={isPlaying}
show={showControls && (controls || controlsOpen)}
show={visible && showControls && (controls || controlsOpen)}
muted={muted}
volume={volume}
features={{
@ -395,9 +395,8 @@ export default function HlsVideoPlayer({
<video
ref={videoRef}
className={cn(
"size-full rounded-lg bg-black md:rounded-2xl",
"size-full cursor-pointer rounded-lg bg-black md:rounded-2xl",
loadedMetadata ? "" : "invisible",
"cursor-pointer",
videoClassName,
)}
preload="auto"

View File

@ -282,12 +282,9 @@ export default function DynamicVideoPlayer({
);
const showPreview = isScrubbing || isLoading;
const previewPlayer = (
const renderPreviewPlayer = (previewClassName: string) => (
<PreviewPlayer
className={cn(
source ? "pointer-events-none absolute inset-0 z-20" : className,
showPreview ? "visible" : "invisible",
)}
className={previewClassName}
camera={camera}
timeRange={timeRange}
cameraPreviews={cameraPreviews}
@ -298,6 +295,12 @@ export default function DynamicVideoPlayer({
}
/>
);
const previewOverlay = renderPreviewPlayer(
cn(
"pointer-events-none absolute inset-0 z-20",
showPreview ? "visible" : "invisible",
),
);
return (
<>
@ -306,6 +309,8 @@ export default function DynamicVideoPlayer({
videoRef={playerRef}
videoClassName={videoClassName}
containerRef={containerRef}
// Keep transform wrapper mounted while scrubbing/loading
// so zoom and pan position are preserved.
visible={true}
showControls={!isScrubbing && !isLoading}
currentSource={source}
@ -346,12 +351,13 @@ export default function DynamicVideoPlayer({
transformedOverlay={
<>
{transformedOverlay}
{previewPlayer}
{previewOverlay}
</>
}
/>
)}
{!source && previewPlayer}
{!source &&
renderPreviewPlayer(cn(className, showPreview ? "visible" : "hidden"))}
{!isScrubbing && (isLoading || isBuffering) && !noRecording && (
<ActivityIndicator className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
)}

View File

@ -374,7 +374,6 @@ export function RecordingView({
const {
cameraSectionStyle,
isDraggingMobileSplit,
isMobilePortraitStacked,
onHandlePointerDown,
usePortraitSplitLayout,
} = useMobileVideoSplit({
@ -778,7 +777,7 @@ export function RecordingView({
? "min-w-0 px-4"
: cn(
"portrait:flex-shrink-0 portrait:flex-grow-0 portrait:basis-auto",
isMobilePortraitStacked
usePortraitSplitLayout
? "portrait:flex-none"
: "portrait:max-h-[50dvh]",
),
@ -922,7 +921,7 @@ export function RecordingView({
)}
</div>
</div>
{isMobilePortraitStacked && !fullscreen && (
{usePortraitSplitLayout && (
<button
type="button"
aria-label={t("recordings.resizeSplit")}

View File

@ -23,7 +23,6 @@ type UseMobileVideoSplitProps = {
type UseMobileVideoSplitReturn = {
cameraSectionStyle: CSSProperties | undefined;
isDraggingMobileSplit: boolean;
isMobilePortraitStacked: boolean;
onHandlePointerDown: (event: PointerEvent<HTMLButtonElement>) => void;
usePortraitSplitLayout: boolean;
};
@ -40,11 +39,10 @@ export function useMobileVideoSplit({
const [{ width: mainLayoutWidth, height: mainLayoutHeight }] =
useResizeObserver(mainLayoutRef);
const isMobilePortraitStacked = useMemo(
() => !isDesktop && mainLayoutHeight > mainLayoutWidth,
[mainLayoutHeight, mainLayoutWidth],
const usePortraitSplitLayout = useMemo(
() => !isDesktop && !fullscreen && mainLayoutHeight > mainLayoutWidth,
[fullscreen, mainLayoutHeight, mainLayoutWidth],
);
const usePortraitSplitLayout = isMobilePortraitStacked && !fullscreen;
const mobileVideoSplitSafe = useMemo(
() =>
Math.min(
@ -59,7 +57,7 @@ export function useMobileVideoSplit({
const updateMobileSplitFromClientY = useCallback(
(clientY: number) => {
if (!mainLayoutRef.current || !isMobilePortraitStacked) {
if (!mainLayoutRef.current || !usePortraitSplitLayout) {
return;
}
@ -75,12 +73,12 @@ export function useMobileVideoSplit({
);
setMobileVideoSplit(clampedSplit);
},
[isMobilePortraitStacked, mainLayoutRef, setMobileVideoSplit],
[usePortraitSplitLayout, mainLayoutRef, setMobileVideoSplit],
);
const onHandlePointerDown = useCallback(
(event: PointerEvent<HTMLButtonElement>) => {
if (!isMobilePortraitStacked) {
if (!usePortraitSplitLayout) {
return;
}
@ -88,7 +86,7 @@ export function useMobileVideoSplit({
setIsDraggingMobileSplit(true);
updateMobileSplitFromClientY(event.clientY);
},
[isMobilePortraitStacked, updateMobileSplitFromClientY],
[usePortraitSplitLayout, updateMobileSplitFromClientY],
);
useEffect(() => {
@ -117,16 +115,15 @@ export function useMobileVideoSplit({
const cameraSectionStyle = useMemo(
() =>
isMobilePortraitStacked
usePortraitSplitLayout
? { height: `${Math.round(mobileVideoSplitSafe * 100)}%` }
: undefined,
[isMobilePortraitStacked, mobileVideoSplitSafe],
[usePortraitSplitLayout, mobileVideoSplitSafe],
);
return {
cameraSectionStyle,
isDraggingMobileSplit,
isMobilePortraitStacked,
onHandlePointerDown,
usePortraitSplitLayout,
};