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

View File

@ -282,12 +282,9 @@ export default function DynamicVideoPlayer({
); );
const showPreview = isScrubbing || isLoading; const showPreview = isScrubbing || isLoading;
const previewPlayer = ( const renderPreviewPlayer = (previewClassName: string) => (
<PreviewPlayer <PreviewPlayer
className={cn( className={previewClassName}
source ? "pointer-events-none absolute inset-0 z-20" : className,
showPreview ? "visible" : "invisible",
)}
camera={camera} camera={camera}
timeRange={timeRange} timeRange={timeRange}
cameraPreviews={cameraPreviews} 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 ( return (
<> <>
@ -306,6 +309,8 @@ export default function DynamicVideoPlayer({
videoRef={playerRef} videoRef={playerRef}
videoClassName={videoClassName} videoClassName={videoClassName}
containerRef={containerRef} containerRef={containerRef}
// Keep transform wrapper mounted while scrubbing/loading
// so zoom and pan position are preserved.
visible={true} visible={true}
showControls={!isScrubbing && !isLoading} showControls={!isScrubbing && !isLoading}
currentSource={source} currentSource={source}
@ -346,12 +351,13 @@ export default function DynamicVideoPlayer({
transformedOverlay={ transformedOverlay={
<> <>
{transformedOverlay} {transformedOverlay}
{previewPlayer} {previewOverlay}
</> </>
} }
/> />
)} )}
{!source && previewPlayer} {!source &&
renderPreviewPlayer(cn(className, showPreview ? "visible" : "hidden"))}
{!isScrubbing && (isLoading || isBuffering) && !noRecording && ( {!isScrubbing && (isLoading || isBuffering) && !noRecording && (
<ActivityIndicator className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" /> <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 { const {
cameraSectionStyle, cameraSectionStyle,
isDraggingMobileSplit, isDraggingMobileSplit,
isMobilePortraitStacked,
onHandlePointerDown, onHandlePointerDown,
usePortraitSplitLayout, usePortraitSplitLayout,
} = useMobileVideoSplit({ } = useMobileVideoSplit({
@ -778,7 +777,7 @@ export function RecordingView({
? "min-w-0 px-4" ? "min-w-0 px-4"
: cn( : cn(
"portrait:flex-shrink-0 portrait:flex-grow-0 portrait:basis-auto", "portrait:flex-shrink-0 portrait:flex-grow-0 portrait:basis-auto",
isMobilePortraitStacked usePortraitSplitLayout
? "portrait:flex-none" ? "portrait:flex-none"
: "portrait:max-h-[50dvh]", : "portrait:max-h-[50dvh]",
), ),
@ -922,7 +921,7 @@ export function RecordingView({
)} )}
</div> </div>
</div> </div>
{isMobilePortraitStacked && !fullscreen && ( {usePortraitSplitLayout && (
<button <button
type="button" type="button"
aria-label={t("recordings.resizeSplit")} aria-label={t("recordings.resizeSplit")}

View File

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