Files
ytLlive/Services/index.md
T
gramps b00a4cbd5e Mic audio UX: realtime read-only meter + mic picker
- Meter is a READ-ONLY realtime level display: fill = Math.Min(1, AudioLevel * MicVolume) (AudioLevel = live input from the future mixer, 0 with no input; MicVolume is gain on ambient noise). While the slider is dragged the bar previews the slider position (PreviewMouseLeftButtonDown/Up + LostMouseCapture -> SetVolumeAdjusting); on release it returns to the live level (bounces to 0 with no input). Clicking the meter does nothing.
- MicVolume drives MicMuted (read-only, muted <=> volume 0): sliding to 0 flips the speaker to the red slashed mute icon, sliding up from 0 clears it; speaker button runs volume to 0 or restores the prior level (_volumeBeforeMute, default 0.8) and flashes the meter to the restored position for ~300ms (BeginVolumeFlash/EndVolumeFlash DispatcherTimer, cancelled if the slider is grabbed).
- MIC label opens MicPickerDialog (WinRT audio-capture device enumeration, mirrors camera picker, no new packages); the chosen voice source name shows left-justified INSIDE the meter bar (fill at 75% opacity so text + ruler markings show through).
- New files: Services/IMicrophoneEnumerator, MicrophoneDeviceInfo, WinRtMicrophoneEnumerator; ViewModels/MicPickerViewModel; MicPickerDialog.
- Docs updated (ai.md, TASKS.md, Services/index.md, ViewModels/index.md); build 0 warnings, 65 tests passing
2026-08-07 17:44:16 -07:00

34 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Services — index
External-facing logic: YouTube API, persistence. See
[`schema.md`](../schema.md) for the memory-map conventions.
| File | Purpose |
|------|---------|
| `YouTubeAuthService.cs` | OAuth2 via Google: loopback callback (`http://localhost:8765/oauth2/callback`), token exchange, refresh, channel fetch. Constructor takes optional `HttpClient` + `sessionChanged` callback (test seam + save hook); session persists via `Helpers/TokenStore` (DPAPI); `ClearSession()` signs out (called by `MainViewModel.StopStream` on End Livestream) |
| `YouTubeStreamService.cs` | Broadcast/stream management via the v3 API (`enableAutoStart/Stop`). **Not yet switched to the `variable` reusable stream** |
| `YouTubeChatService.cs` | Polls `liveChat/messages`, raises `MessageReceived`; `IDisposable` |
| `LayoutStore.cs` | SQLite persistence (`Microsoft.Data.Sqlite`) at `%APPDATA%\ytLlive\ytLlive.db`; assets stored as BLOBs keyed by SHA-256 content hash; save/open layout files; schema `user_version` 6 (`Source.ClipShape`/`IsMirrored` via `ALTER TABLE` for pre-v2 DBs; v3 = singleton `Webcam` + per-scene `WebcamSceneConfig`, migrated idempotently **without backfill** — the stale `Source.DeviceId` column remains but is no longer read/written; v4 = `WebcamSceneConfig.RectWidth`/`RectHeight`, the pre-Round rect for the round-to-rect restore; v5 = `Source.IsBackdrop` + `Source.CaptureKey`, the live-capture backdrop; v6 = `Scene.HasBackdrop` — Live-only policy, one-time backfill turns Starting/BRB/Chat/Ending off + drops their backdrop sources; `MainViewModel.EnforceBackdropPolicy` re-normalizes on every load) |
| `VideoFrame.cs` | Normalized CPU frame seam (`Width`/`Height`/tightly-packed BGRA `byte[]`) — the only pixel type the rest of the app knows about; future capture sources (screen, background-removed webcam) feed the same seam |
| `CameraDeviceInfo.cs` | `(Id, DisplayName)` for a physical capture device |
| `ICameraEnumerator.cs` | `GetCamerasAsync()` — seam so the picker/`CameraManager` never touch WinRT (tests inject fakes) |
| `ICameraFrameSource.cs` | `StartAsync`/`StopAsync`/`FrameAvailable(VideoFrame)` — seam for a running capture source |
| `MediaCaptureCameraEnumerator.cs` | WinRT enumeration via `DeviceInformation.FindAllAsync(DeviceClass.VideoCapture)` |
| `MicrophoneDeviceInfo.cs` | `(Id, DisplayName)` for an audio capture (mic) device |
| `IMicrophoneEnumerator.cs` | `GetMicrophonesAsync()` — seam so the mic picker never touches WinRT (tests inject fakes) |
| `WinRtMicrophoneEnumerator.cs` | WinRT mic enumeration via `DeviceInformation.FindAllAsync(DeviceClass.AudioCapture)` (no NAudio needed — capture libs stay deferred to the audio pipeline milestone) |
| `MediaCaptureFrameSource.cs` | CPU-first MediaCapture source (`MemoryPreference = Cpu`, BGRA8 via `CreateFrameReaderAsync`); frames arrive on a worker thread |
| `CameraManager.cs` | Webcam capture ownership: refcounted by `DeviceId`, one shared `WriteableBitmap` per camera, dispatcher-coalesced ~render-rate UI updates (latest-frame drop); `PreviewBitmapChanged`/`CameraFailed` events. `ReleaseAsync` decrements a ref (stops at zero); `ReleaseAllAsync` force-drops every ref + stops the source (webcam swap / layout reload). `GetPreviewBitmap(deviceId)` returns the current shared bitmap so a `WebcamSceneConfig` added mid-session (after the first frame already created the bitmap) still receives the live frames |
| `IFullScreenDetector.cs` | Seam for the win32 full-screen detector: `int? GetForegroundFullScreenMonitorIndex()`, `int PrimaryMonitorIndex()`, `IReadOnlyList<DisplayInfo> GetDisplays()` |
| `Win32FullScreenDetector.cs` | `GetForegroundWindow` + `DwmGetWindowAttribute(DWMWA_EXTENDED_FRAME_BOUNDS)` + `MonitorFromWindow` + `GetMonitorInfo`; monitor covers all four edges → full-screen; own process excluded; monitor order = `EnumDisplayMonitors` order (static `GetMonitorHandle(int)` maps index→HMONITOR for the capture factory). `GetDisplays()` returns `DisplayInfo` (index/name/resolution/bounds/`IsPrimary`, friendly name via `EnumDisplayDevices`) for the in-app "Capture Display" picker; `PrimaryMonitorIndex()` is the auto-key fallback when no full-screen game is detected |
| `IScreenCaptureSource.cs` | `Key` + `StartAsync`/`StopAsync`/`FrameAvailable(VideoFrame)` — seam so `ScreenCaptureManager` never touches WinRT (tests inject fakes) |
| `Direct3D11Helper.cs` | The one shared `IDirect3DDevice` per process: P/Invoke `d3d11.dll!D3D11CreateDevice` (hardware, BGRA_SUPPORT, explicit 11.1-first feature array) → QI `IDXGIDevice` → the WinRT interop export `CreateDirect3D11DeviceFromDXGIDevice``MarshalInterface<IDirect3DDevice>.FromAbi`. CsWinRT does **not** project `Windows.Graphics.Direct3D11.Direct3D11Helper`, so this hand-rolls it. Do **not** revert to QI-for-`IDirect3DDxgiInterfaceAccess` — the device no longer exposes it on newer Windows (E_NOINTERFACE on build 26200) |
| `CaptureInterop.cs` | ComImport bridges the WinRT-projection gap: `IGraphicsCaptureItemInterop` (`CreateForMonitor`/`CreateForWindow`), `IInitializeWithWindow` (`GraphicsCapturePicker` owner window so the OS picker shows) |
| `ScreenCaptureFrameSource.cs` | One `Direct3D11CaptureFramePool` (free-threaded, 2 buffers) + session per target; frames → `SoftwareBitmap.CreateCopyFromSurfaceAsync` (BGRA, alpha ignored) → `VideoFrame`, bytes read via `WindowsRuntimeMarshal.TryGetDataUnsafe` (CsWinRT-safe — the `IMemoryBufferByteAccess` ComImport cast fails on every frame and is gone). Surfaces > 1920×1080 downscaled bilinearly to the master; conversion failures logged ≤ once/5 s. DRM content = black frames (OS limit). `CreateForMonitor`/`CreateForWindow`/`CreateForPicker` |
| `ScreenCaptureManager.cs` | Screen-capture ownership mirroring `CameraManager`: refcounted by target key, one shared `WriteableBitmap`, dispatcher-coalesced latest-frame copies; `PreviewBitmapChanged`/`CaptureFailed` events; `ReleaseAllAsync` used on re-designation |
| `ScreenCaptureSourceFactory.cs` | `Resolve(key)` parses `monitor:<n>` / `window:<hwnd>` / `picker:<name>` into a source; `PickAsync()` shows the OS `GraphicsCapturePicker` and returns the `picker:` key (transient — a reload falls back to auto-detection) |
Related: constructed in [`ViewModels/MainViewModel.cs`](../ViewModels/MainViewModel.cs)
(no DI container yet). Models in [`Models/index.md`](../Models/index.md).
OAuth credentials live in [`Helpers/OAuthCredentials.cs`](../Helpers/OAuthCredentials.cs).