Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Captura.Base/Services/IWebcamCapture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public interface IWebcamCapture : IDisposable

void UpdatePreview(IWindow Window, Rectangle Location);

// Control preview window visibility without tearing down the camera graph
void SetPreviewVisibility(bool IsVisible);

string GetCameraProperties();
}
}
45 changes: 45 additions & 0 deletions src/Captura.Windows/Webcam/CaptureWebcam.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class CaptureWebcam : ISampleGrabberCB, IDisposable
#region Fields
readonly Filter _videoDevice;
readonly IntPtr _previewWindow;
IntPtr _currentOwner;
readonly DummyForm _form;
readonly Action _onClick;
readonly object _lock = new object();
Expand Down Expand Up @@ -50,6 +51,7 @@ public CaptureWebcam(Filter VideoDevice, Action OnClick, IntPtr PreviewWindow)
_form.Click += (s, e) => OnClick?.Invoke();

_previewWindow = PreviewWindow != IntPtr.Zero ? PreviewWindow : _form.Handle;
_currentOwner = _previewWindow;

BuildGraph();
}
Expand Down Expand Up @@ -479,6 +481,7 @@ void SetupVideoWindow()
_videoWindow = null;
return;
}
_currentOwner = _previewWindow;

hr = _videoWindow.put_MessageDrain(_form.Handle);
if (hr < 0)
Expand Down Expand Up @@ -555,6 +558,48 @@ public void OnPreviewWindowResize(int X, int Y, int Width, int Height)
}
}

public void SetPreviewVisibility(bool isVisible)
{
lock (_lock)
{
if (_videoWindow == null)
return;

try
{
_videoWindow.put_Visible(isVisible ? OABool.True : OABool.False);
}
catch { }
}
}

public void UpdatePreviewWindow(IntPtr ownerHandle, Rectangle location)
{
lock (_lock)
{
if (_videoWindow != null)
{
try
{
if (ownerHandle != IntPtr.Zero && _currentOwner != ownerHandle)
{
// Switch owner without rebuilding the graph
_videoWindow.put_Visible(OABool.False);
_videoWindow.put_Owner(ownerHandle);
_currentOwner = ownerHandle;
_videoWindow.put_Visible(OABool.True);
}

_videoWindow.SetWindowPosition(location.X, location.Y, location.Width, location.Height);
}
catch
{
// Ignore errors
}
}
}
}

#endregion

#region Frame Capture
Expand Down
54 changes: 41 additions & 13 deletions src/Captura.Windows/Webcam/WebcamCapture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,19 @@ public WebcamCapture(Filter Filter, Action OnClick)
_filter = Filter ?? throw new ArgumentNullException(nameof(Filter));
_onClick = OnClick;

// Initialize the camera but start with preview hidden
// This ensures Width/Height are available immediately
try
{
_captureWebcam = new CaptureWebcam(Filter, OnClick, IntPtr.Zero);
_captureWebcam.StartPreview();
_lastWin = IntPtr.Zero;

// Start hidden - will be shown when UpdatePreview is called
_captureWebcam.SetPreviewVisibility(false);
}
catch (Exception ex)
{
// Don't show error dialogs during initialization to avoid dialog crashes
// Just log and rethrow
System.Diagnostics.Debug.WriteLine($"Webcam initialization failed: {ex.Message}");
_captureWebcam?.Dispose();
_captureWebcam = null;
Expand Down Expand Up @@ -94,19 +98,28 @@ public void UpdatePreview(IWindow Window, Rectangle Location)
{
try
{
if (Window != null && _lastWin != Window.Handle)
{
// Recreate capture with new window handle
_captureWebcam?.StopPreview();
_captureWebcam?.Dispose();

_captureWebcam = new CaptureWebcam(_filter, _onClick, Window.Handle);
_captureWebcam.StartPreview();
if (_captureWebcam == null)
return;

_lastWin = Window.Handle;
// Check if we need to set or switch the owner window
if (Window != null)
{
var newHandle = Window.Handle;
if (_lastWin != newHandle)
{
// Switch owner handle without rebuilding the graph
_captureWebcam.UpdatePreviewWindow(newHandle, Location);
_lastWin = newHandle;
}
else
{
// Just update window position
_captureWebcam.OnPreviewWindowResize(Location.X, Location.Y, Location.Width, Location.Height);
}
}

_captureWebcam?.OnPreviewWindowResize(Location.X, Location.Y, Location.Width, Location.Height);

// Make sure preview is visible
_captureWebcam.SetPreviewVisibility(true);
}
catch (COMException ex)
{
Expand All @@ -119,6 +132,21 @@ public void UpdatePreview(IWindow Window, Rectangle Location)
});
}

public void SetPreviewVisibility(bool isVisible)
{
if (_disposed)
return;

_syncContext.Run(() =>
{
try
{
_captureWebcam?.SetPreviewVisibility(isVisible);
}
catch { }
});
}

void HandleCameraException(Exception exception, string context)
{
// Common DirectShow/Windows error codes
Expand Down
27 changes: 22 additions & 5 deletions src/Captura/Pages/WebcamPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ await Dispatcher.InvokeAsync(() =>
}

IReadOnlyReactiveProperty<IWebcamCapture> _webcamCapture;
bool _acquired;

public void SetupPreview()
{
Expand All @@ -150,10 +151,10 @@ public void SetupPreview()
}
else
{
if (_webcamCapture != null)
// Hide preview when page is not visible but keep camera running to avoid flicker
if (_webcamCapture?.Value != null)
{
_webcamModel.ReleaseCapture();
_webcamCapture = null;
try { _webcamCapture.Value.SetPreviewVisibility(false); } catch { }
}
}
};
Expand Down Expand Up @@ -185,6 +186,17 @@ void OnRegionChange()
.Subscribe();

UpdateWebcamPreview();

Unloaded += (s, e) =>
{
// Cleanup when page is unloaded (not just hidden)
if (_acquired && _webcamCapture != null)
{
_webcamModel.ReleaseCapture();
_webcamCapture = null;
_acquired = false;
}
};
}

async void OnCameraChanged()
Expand All @@ -196,17 +208,21 @@ async void OnCameraChanged()
{
_webcamModel.ReleaseCapture();
_webcamCapture = null;
_acquired = false;
}

await InitializeCameraAsync();
}

async Task InitializeCameraAsync()
{
// Don't release and recreate if camera is already initialized
if (_webcamCapture != null)
{
_webcamModel.ReleaseCapture();
_webcamCapture = null;
// Camera already initialized, just update the preview
// UpdateWebcamPreview will automatically show the preview
UpdateWebcamPreview();
return;
}

IsCameraReady = false;
Expand Down Expand Up @@ -235,6 +251,7 @@ await Dispatcher.InvokeAsync(() =>
await Task.Yield();

_webcamCapture = _webcamModel.InitCapture();
_acquired = true;

if (_webcamCapture.Value is { } capture)
{
Expand Down