Browse Source

Improved camera window stream management

maziggy 4 months ago
parent
commit
59fe2a6ec7

+ 38 - 36
frontend/src/pages/CameraPage.tsx

@@ -46,39 +46,36 @@ export function CameraPage() {
   }, [printer]);
 
   // Cleanup on unmount - stop the camera stream
+  // Track if we've already sent the stop signal to avoid duplicate calls
+  const stopSentRef = useRef(false);
+
   useEffect(() => {
     const stopUrl = `/api/v1/printers/${id}/camera/stop`;
+    stopSentRef.current = false;
 
-    // Handle page unload/close with sendBeacon (more reliable than fetch on unload)
-    const handleBeforeUnload = () => {
-      if (id > 0) {
+    const sendStopOnce = () => {
+      if (id > 0 && !stopSentRef.current) {
+        stopSentRef.current = true;
         navigator.sendBeacon(stopUrl);
       }
     };
 
-    // Handle visibility change (tab hidden/closed)
-    const handleVisibilityChange = () => {
-      if (document.visibilityState === 'hidden' && id > 0) {
-        navigator.sendBeacon(stopUrl);
-      }
+    // Handle page unload/close with sendBeacon (more reliable than fetch on unload)
+    const handleBeforeUnload = () => {
+      sendStopOnce();
     };
 
     window.addEventListener('beforeunload', handleBeforeUnload);
-    document.addEventListener('visibilitychange', handleVisibilityChange);
 
     return () => {
       window.removeEventListener('beforeunload', handleBeforeUnload);
-      document.removeEventListener('visibilitychange', handleVisibilityChange);
 
-      // Clear the image source
+      // Clear the image source first to stop the stream
       if (imgRef.current) {
         imgRef.current.src = '';
       }
-      // Call the stop endpoint to terminate ffmpeg processes
-      if (id > 0) {
-        // Use sendBeacon for reliability during unmount
-        navigator.sendBeacon(stopUrl);
-      }
+      // Send stop signal only once
+      sendStopOnce();
     };
   }, [id]);
 
@@ -93,19 +90,31 @@ export function CameraPage() {
     }
   }, [streamMode, streamLoading, imageKey, transitioning]);
 
-  // Fullscreen change listener
+  // Fullscreen change listener - refresh stream after fullscreen transition
   useEffect(() => {
     const handleFullscreenChange = () => {
-      setIsFullscreen(!!document.fullscreenElement);
+      const nowFullscreen = !!document.fullscreenElement;
+      setIsFullscreen(nowFullscreen);
+
+      // Refresh stream after fullscreen transition to prevent stall
+      if (streamMode === 'stream' && !transitioning) {
+        // Clear image src first, then set new key after delay
+        if (imgRef.current) {
+          imgRef.current.src = '';
+        }
+        setTimeout(() => {
+          setStreamLoading(true);
+          setImageKey(Date.now());
+        }, 200);
+      }
     };
     document.addEventListener('fullscreenchange', handleFullscreenChange);
     return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);
-  }, []);
+  }, [streamMode, transitioning]);
 
-  // Save window size and position when user resizes or moves (only for popup windows)
+  // Save window size and position when user resizes or moves
+  // Works for both popup windows and standalone camera pages
   useEffect(() => {
-    if (!window.opener) return;
-
     let saveTimeout: NodeJS.Timeout;
     const saveWindowState = () => {
       // Debounce to avoid saving during drag
@@ -121,20 +130,9 @@ export function CameraPage() {
     };
 
     window.addEventListener('resize', saveWindowState);
-    // Use interval to detect position changes (no native 'move' event)
-    const positionInterval = setInterval(() => {
-      const saved = localStorage.getItem('cameraWindowState');
-      if (saved) {
-        const state = JSON.parse(saved);
-        if (state.left !== window.screenX || state.top !== window.screenY) {
-          saveWindowState();
-        }
-      }
-    }, 1000);
 
     return () => {
       clearTimeout(saveTimeout);
-      clearInterval(positionInterval);
       window.removeEventListener('resize', saveWindowState);
     };
   }, []);
@@ -263,8 +261,8 @@ export function CameraPage() {
       clearInterval(countdownIntervalRef.current);
     }
 
-    // Auto-resize popup window to fit video content (only if no saved preference)
-    if (window.opener && imgRef.current && !localStorage.getItem('cameraWindowState')) {
+    // Auto-resize window to fit video content (only if no saved preference)
+    if (imgRef.current && !localStorage.getItem('cameraWindowState')) {
       const img = imgRef.current;
       const videoWidth = img.naturalWidth;
       const videoHeight = img.naturalHeight;
@@ -281,7 +279,11 @@ export function CameraPage() {
         const targetWidth = videoWidth + padding + chromeWidth;
         const targetHeight = videoHeight + headerHeight + padding + chromeHeight;
 
-        window.resizeTo(targetWidth, targetHeight);
+        try {
+          window.resizeTo(targetWidth, targetHeight);
+        } catch {
+          // resizeTo may not be allowed in all contexts
+        }
       }
     }
   };

+ 1 - 1
frontend/src/pages/PrintersPage.tsx

@@ -2370,7 +2370,7 @@ function PrinterCard({
                     `height=${state.height}`,
                     state.left !== undefined ? `left=${state.left}` : '',
                     state.top !== undefined ? `top=${state.top}` : '',
-                    'menubar=no,toolbar=no,location=no,status=no',
+                    'menubar=no,toolbar=no,location=no,status=no,noopener',
                   ].filter(Boolean).join(',');
                   window.open(`/camera/${printer.id}`, `camera-${printer.id}`, features);
                 }}

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-BuRs1RuZ.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-BqcFmQ1s.js"></script>
+    <script type="module" crossorigin src="/assets/index-BuRs1RuZ.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-DgCUmTZF.css">
   </head>
   <body>

Some files were not shown because too many files changed in this diff