fix(start): a second instance can no longer kill the app on Go Live

Reported 2026-11-06: a Release instance crashed when clicking Go Live in test mode, while a Debug
instance was recording its window. The Windows event log gave the cause:

    System.IO.IOException: All pipe instances are busy.
    at System.IO.Pipes.NamedPipeServerStream.Create(...)
    at ytLive.Services.Audio.NamedPipeAudioWriter.Start(...)
    at ytLive.Services.Audio.AudioMixer.StartLive(...)
    at MainViewModel.StartStreamingSession(...)
    at MainViewModel.BeginTestStream(...)
    at MainViewModel.StartFromPrimaryButton(...)
    at System.Windows.Controls.Button.OnClick()

Two defects, both fixed:

1. **A button click could end the process.** `StartLive` throws when the pipe name is taken, and nothing
   on the go-live path caught it. `TryStartAudioPipe` now catches, logs, toasts ("Another copy of
   LlamaCasty is already using the audio pipe") and rolls the session back. Recording-only takes the same
   path.
2. **The pipe name was a shared constant**, so a second instance could never go live on the same machine.
   `InstanceProfile.AudioPipeName` appends the dev instance id (DEBUG only); Release keeps the plain name
   so the normal single-instance case is unchanged. The encoder reads the same derived name, so mixer and
   ffmpeg cannot disagree.

Tests: `AudioPipeInstanceTests` — a second server on one name throws (the collision is real), dev names
derive per instance, and a writer on a taken name throws rather than silently reusing the pipe.
Debug and Release both build clean.
This commit is contained in:
2026-11-06 08:41:16 -08:00
parent 3261293d57
commit 0f08fd7ade
5 changed files with 124 additions and 3 deletions
+12
View File
@@ -69,6 +69,18 @@ public static class InstanceProfile
public static string? WebViewDataFolder => null;
#endif
/// <summary>The audio pipe name for this process. The pipe server allows exactly ONE instance, so a
/// shared name meant the second app on the machine could never go live — it threw
/// "All pipe instances are busy" out of the Go Live click and ended the process (2026-11-06: two
/// instances, one recording the other). A dev instance appends its id; Release, and therefore the
/// first instance, keeps the plain name.</summary>
public static string AudioPipeName(string baseName)
#if DEBUG
=> Id is { } id ? $"{baseName}_{id}" : baseName;
#else
=> baseName;
#endif
/// <summary>The one real user profile: <c>%APPDATA%\ytLlive</c>. Note the historical
/// double-L — do not "fix" it, users already have their layouts there.</summary>
public static string DefaultRoot => Path.Combine(