@@ -5,6 +5,7 @@ import { cx } from "@/cva.config";
55import { isWindows } from "@/utils" ;
66import useKeyboard from "@hooks/useKeyboard" ;
77import useMouse from "@hooks/useMouse" ;
8+ import { useJsonRpc } from "@hooks/useJsonRpc" ;
89import { useRTCStore , useSettingsStore , useUiStore , useVideoStore } from "@hooks/stores" ;
910import VirtualKeyboard from "@components/VirtualKeyboard" ;
1011import Actionbar from "@components/ActionBar" ;
@@ -41,6 +42,7 @@ export default function WebRTCVideo({
4142
4243 // Store hooks
4344 const settings = useSettingsStore ( ) ;
45+ const { send } = useJsonRpc ( ) ;
4446 const { handleKeyPress, resetKeyboardState } = useKeyboard ( ) ;
4547 const {
4648 getRelMouseMoveHandler,
@@ -486,6 +488,30 @@ export default function WebRTCVideo({
486488 [ keyDownHandler , keyUpHandler , resetKeyboardState ] ,
487489 ) ;
488490
491+ // Pause/resume the server-side video feed when the tab is hidden so we
492+ // don't burn WAN bandwidth decoding-then-discarding frames the user
493+ // can't see. The encoder is restarted on resume so the first frame is
494+ // an IDR and decode is artifact-free.
495+ useEffect (
496+ function pauseVideoOnTabHidden ( ) {
497+ const sync = ( ) => {
498+ send ( document . hidden ? "pauseVideo" : "resumeVideo" , { } ) ;
499+ } ;
500+
501+ // Sync once on mount in case the tab is already hidden when we
502+ // (re)connect, then track every visibility change.
503+ sync ( ) ;
504+
505+ const abortController = new AbortController ( ) ;
506+ document . addEventListener ( "visibilitychange" , sync , {
507+ signal : abortController . signal ,
508+ } ) ;
509+
510+ return ( ) => abortController . abort ( ) ;
511+ } ,
512+ [ send ] ,
513+ ) ;
514+
489515 // Setup Video Event Listeners
490516 useEffect (
491517 function setupVideoEventListeners ( ) {
@@ -606,13 +632,8 @@ export default function WebRTCVideo({
606632 < div className = "grid h-full w-full grid-rows-(--grid-layout)" >
607633 < div className = "flex min-h-[39.5px] flex-col" >
608634 < div className = "flex flex-col" >
609- < fieldset
610- disabled = { peerConnection ?. connectionState !== "connected" }
611- className = "contents"
612- >
613- < Actionbar
614- requestFullscreen = { requestFullscreen }
615- />
635+ < fieldset disabled = { peerConnection ?. connectionState !== "connected" } className = "contents" >
636+ < Actionbar requestFullscreen = { requestFullscreen } />
616637 < MacroBar />
617638 </ fieldset >
618639 </ div >
@@ -634,9 +655,7 @@ export default function WebRTCVideo({
634655 < div className = "grid grow grid-rows-(--grid-bodyFooter) overflow-hidden" >
635656 { /* In relative mouse mode and under https, we enable the pointer lock, and to do so we need a bar to show the user to click on the video to enable mouse control */ }
636657 < PointerLockBar show = { showPointerLockBar } />
637- < div
638- className = "relative mx-4 my-2 flex items-center justify-center overflow-hidden"
639- >
658+ < div className = "relative mx-4 my-2 flex items-center justify-center overflow-hidden" >
640659 < div
641660 ref = { fullscreenContainerRef }
642661 className = "relative flex h-full w-full items-center justify-center"
@@ -652,20 +671,17 @@ export default function WebRTCVideo({
652671 disablePictureInPicture
653672 controlsList = "nofullscreen"
654673 style = { videoStyle }
655- className = { cx (
656- "h-full w-full object-contain transition-all duration-1000" ,
657- {
658- "cursor-none" : settings . isCursorHidden ,
659- "pointer-events-none" : isOcrMode ,
660- "opacity-0!" :
661- isVideoLoading ||
662- hdmiError ||
663- hasConnectionIssues ||
664- peerConnectionState !== "connected" ,
665- "opacity-60!" : showPointerLockBar ,
666- "animate-slideUpFade" : isPlaying ,
667- } ,
668- ) }
674+ className = { cx ( "h-full w-full object-contain transition-all duration-1000" , {
675+ "cursor-none" : settings . isCursorHidden ,
676+ "pointer-events-none" : isOcrMode ,
677+ "opacity-0!" :
678+ isVideoLoading ||
679+ hdmiError ||
680+ hasConnectionIssues ||
681+ peerConnectionState !== "connected" ,
682+ "opacity-60!" : showPointerLockBar ,
683+ "animate-slideUpFade" : isPlaying ,
684+ } ) }
669685 />
670686 < OcrOverlay />
671687 { peerConnection ?. connectionState == "connected" && ! hasConnectionIssues && (
0 commit comments