Screen backdrop (schema v5/v6) + five-scene catalog + webcam polish: live desktop/game capture as a permanent non-deletable bottom layer (Source.IsBackdrop), auto full-screen game detection (Win32FullScreenDetector) else primary display, GraphicsCapturePicker re-designation, refcounted shared ScreenCaptureManager, Live-only backdrop by policy (HasBackdrop + v5-v6 backfill + EnforceBackdropPolicy, checkbox gone), SceneCatalog (Starting/Live/BRB/Chat/Ending) with + button re-adding missing scenes, webcam mid-session transparent-container fix (GetPreviewBitmap propagation), Chat half-screen-area cap, 'Add Webcam' always opens the picker (SwapWebcamIdentityAsync, no silent resurrect), WindowsRuntimeMarshal frame-read + 5s-throttled errors, docs updated, tests (65 passing)
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Capture;
|
||||
|
||||
namespace ytLive.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Turns a capture key ("monitor:<n>", "window:<hwnd>", "picker:<...>")
|
||||
/// into a live <see cref="IScreenCaptureSource"/>, and hosts the OS
|
||||
/// GraphicsCapturePicker used by "Change Capture…". Picker picks are transient:
|
||||
/// the picked GraphicsCaptureItem has no HWND/monitor identity, so a
|
||||
/// "picker:" key resolves only within the current session and re-detection
|
||||
/// takes over after a reload (documented v1 limit).
|
||||
/// </summary>
|
||||
public sealed class ScreenCaptureSourceFactory
|
||||
{
|
||||
private readonly Func<IntPtr> _ownerHwndProvider;
|
||||
private readonly Dictionary<string, GraphicsCaptureItem> _picked = new();
|
||||
private readonly object _gate = new();
|
||||
|
||||
public ScreenCaptureSourceFactory(Func<IntPtr> ownerHwndProvider)
|
||||
{
|
||||
_ownerHwndProvider = ownerHwndProvider;
|
||||
}
|
||||
|
||||
/// <summary>Creates the source for a persisted key, or null when it can't be resolved.</summary>
|
||||
public IScreenCaptureSource? Resolve(string key)
|
||||
{
|
||||
if (key.StartsWith("monitor:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var indexText = key.AsSpan("monitor:".Length);
|
||||
if (int.TryParse(indexText, NumberStyles.Integer, CultureInfo.InvariantCulture, out var index))
|
||||
return ScreenCaptureFrameSource.CreateForMonitor(index);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key.StartsWith("window:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var hexText = key.AsSpan("window:".Length);
|
||||
if (long.TryParse(hexText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var hwnd))
|
||||
return ScreenCaptureFrameSource.CreateForWindow(new IntPtr(hwnd));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key.StartsWith("picker:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
lock (_gate)
|
||||
return _picked.TryGetValue(key, out var item)
|
||||
? ScreenCaptureFrameSource.CreateForPicker(key, item)
|
||||
: null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the OS capture picker (must run on the UI thread, owner window set
|
||||
/// via IInitializeWithWindow). Returns the session key, or null if cancelled.
|
||||
/// </summary>
|
||||
public async Task<string?> PickAsync()
|
||||
{
|
||||
var picker = new GraphicsCapturePicker();
|
||||
CaptureInterop.SetWindowOwner(picker, _ownerHwndProvider());
|
||||
var item = await picker.PickSingleItemAsync();
|
||||
if (item == null) return null;
|
||||
|
||||
var key = $"picker:{item.DisplayName}";
|
||||
lock (_gate)
|
||||
_picked[key] = item;
|
||||
return key;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user