Per-app send fixes + apps-mode UI rework (no send-all) + truly-global remembered lists
Per-app capture (the "foobar alone = no sound" report):
- ProcessLoopbackCapture: take ActivateAudioInterfaceAsync's out operation as a raw
IntPtr (released after completion) instead of a typed interface. The eager RCW cast
of the not-yet-realised operation object threw InvalidCastException / E_NOINTERFACE
and killed EVERY specific-app capture at start.
- CompositeCaptureBackend: single IsPushEligible authority shared by Start,
UpdateSources and the coalesced rebuild. The update paths were missing the
ProcessLoopback exclusion, so switching whole-device -> one app kept the push
backend and fed it "proc:<pid>" (GetDevice ArgumentException).
- PushModeWasapiBackend: loud backstop rejecting process-loopback specs.
- Self-test: lifecycle test now FAILS on an activation error (it previously passed
green with the feature completely dead) + pure routing-rule checks.
Apps-mode UI (Ed 2026-07-16): the "Send all applications" checkbox is GONE from the
main window - applications mode always means picking specific apps; whole-system
audio is devices mode's job. Active list = running apps + any ticked app that is not
running ("(not running)" so it can be unticked); Remembered list = global address
book minus whatever is ticked. In-place list reconcile (no Clear+rebuild) kills the
NVDA double-read of the toggled row. Profile.SendAllApplications stays for the
SERVICE (deliberate divergence - a headless lock-screen sender wants system audio).
Remembered lists now genuinely machine-wide (AppConfig-backed): the settings store is
an intra-process cache, so remembered applications were forgotten on every exit and
remembered peers were per-profile in practice. Both books moved to AppConfig; legacy
per-profile peers are unioned in on profile load; profile save snapshots the global
book back for old-build compat. Cross-instance persistence pinned by self-test.
Service (issue #23): 15s capture pulse (callbacks/bytes/pre-encode peak/frames sent)
while sending - distinguishes "endpoint mix is genuinely silent at the lock screen"
from a pipeline fault, which callbacks alone cannot.
Gate: 38/38.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
92ad77ff52
commit
1003bbfdf2
@@ -71,9 +71,29 @@ public sealed class ServiceSendHost : IDisposable
|
||||
private long captureWatchStartTick;
|
||||
private bool loggedFirstCallback;
|
||||
private bool loggedZeroCallbacks;
|
||||
private long lastCapturePulseTick;
|
||||
|
||||
// How often the periodic capture pulse is written while the service is sending. 15s keeps a
|
||||
// boot-to-login window (typically 30s+) covered by at least two readings without bloating the log.
|
||||
private const int CapturePulseIntervalMs = 15_000;
|
||||
|
||||
private void WatchCaptureHealth()
|
||||
{
|
||||
// Jonathan's follow-up log proved callbacks CAN flow at the lock screen while peers still hear
|
||||
// nothing — callbacks alone can't distinguish real sound from the silence keepalive feeding back.
|
||||
// So while sending, also pulse the loudest pre-encode sample + frames actually sent every 15s:
|
||||
// peak≈0.000 pre-login flipping to real values at login = the captured endpoint mix is genuinely
|
||||
// silent on the lock screen (device/session routing), NOT a service pipeline fault.
|
||||
var now = Environment.TickCount64;
|
||||
if (now - lastCapturePulseTick >= CapturePulseIntervalMs)
|
||||
{
|
||||
lastCapturePulseTick = now;
|
||||
var peak = sender.TakeMaxSenderPreEncodePeak();
|
||||
var frames = sender.TakeSenderAudioFramesSent();
|
||||
log?.Invoke($"service: capture pulse — callbacks={sender.CaptureCallbacks} bytes={sender.CaptureBytes} peak={peak:F3} framesSent={frames}"
|
||||
+ (peak < 0.001f ? " (capturing SILENCE — nothing audible in the endpoint mix)" : ""));
|
||||
}
|
||||
|
||||
if (loggedFirstCallback) return;
|
||||
if (sender.CaptureCallbacks > 0)
|
||||
{
|
||||
@@ -139,6 +159,7 @@ public sealed class ServiceSendHost : IDisposable
|
||||
captureWatchStartTick = Environment.TickCount64;
|
||||
loggedFirstCallback = false;
|
||||
loggedZeroCallbacks = false;
|
||||
lastCapturePulseTick = Environment.TickCount64; // first pulse lands one interval after start
|
||||
// Come up on the network too, so the peers can discover and connect to us — not just receive a
|
||||
// blind push. Same well-known audio port and the same components the interactive app uses.
|
||||
presence.Start(RemPacket.DefaultPort, endpoints);
|
||||
|
||||
Reference in New Issue
Block a user