Skip to content

fix(audio): a failed output switch no longer rolls back the session - #109

Merged
lippdev merged 1 commit into
mainfrom
fix/audio-switch-failure-not-fatal
Sep 29, 2026
Merged

lippdev merged 1 commit into
mainfrom
fix/audio-switch-failure-not-fatal

Conversation

@lippdev

@lippdev lippdev commented Sep 29, 2026

Copy link
Copy Markdown
Owner

Hardening, not a reproduced bug. I checked that switching works (see below); this covers the case where it doesn't.

Problem

With SoundVolumeView (1.5) a failed /SetDefault was ignored: the session went on without the audio switch. The Core Audio backend (1.6 alpha, #91) throws instead (Marshal.ThrowExceptionForHR), and ConsoleEngine.Start doesn't catch it. An error in Audio.SetOutput therefore made MainViewModel roll the whole start back with Engine.Stop(), after the desk screens had already been switched. The two polling paths (UpdateAutoSwitch, UpdatePendingTarget) called SetOutput unguarded too (the loop catches and logs, but then never completes the audio watch and retries forever).

Fix

ConsoleEngine.TrySetOutput logs Áudio: não foi possível trocar para … and lets the session continue. The switch is only recorded (AudioDeviceId, LastAudioSwitchName) when it worked, and the audio watch is completed either way (same as 1.5). The session menu's manual output choice is untouched: it is a user action and still shows its error.

What I checked on a real machine

Driving the shipped ConsoleMode.dll by reflection against SoundVolumeView as reference:

  • The output list (with /ShowDisabledDevices 1 /ShowUnpluggedDevices 1, as 1.5 called it) is identical: same 4 outputs, same names, same FriendlyIds, same default, volume 67 vs 66.9 %.
  • Volume and mute writes on a non-default output are confirmed by SVV and restored.
  • IPolicyConfig.SetDefaultEndpoint and SetEndpointVisibility return S_OK (on the endpoints that were already default / visible, so nothing changed).

So the happy path works; this PR is about not turning a rare COM error into an aborted session.

Tests

ConsoleEngine needs Win32, so there is no unit test; added a manual item to docs/TESTING.md. Build OK, 144 tests pass. No changelog entry: nothing user-visible changes when audio works.

🤖 Generated with Claude Code

With SoundVolumeView a failed /SetDefault was ignored and the session went on. The Core
Audio backend throws instead, and ConsoleEngine.Start does not catch it: an error in
SetOutput made MainViewModel roll the whole start back (Engine.Stop) after the screens
had already been switched. The two polling paths (auto-switch and pending target) called
SetOutput unguarded too.

TrySetOutput logs the error in Portuguese and lets the session continue; the switch is only
recorded (AudioDeviceId, LastAudioSwitchName) when it worked. The session menu's manual
output choice still reports its error to the user.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The fixed-output failure path leaves session audio state inconsistent with the actual output, so the session menu can report and control the wrong device.

Reviewed changes The review covers the best-effort output-switch handling and its manual test checklist.

  • Best-effort switching ConsoleEngine logs failed startup and polling switches, continues the session, and completes audio watching.
  • Manual verification docs/TESTING.md adds a checklist item for a failed switch during a session start.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using GPT Luna | 𝕏

if (target is { IsActive: true })
{
Audio.SetOutput(config.AudioDeviceId);
TrySetOutput(config.AudioDeviceId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this switch fails, State.AudioDeviceId still contains the configured target set earlier in Start, but the OS default remains on the previous output. The session menu treats this field as the current output for its label, selection, and volume/mute actions, so it can show and control the wrong device.

Technical details
# Keep session audio state accurate after a failed startup switch

## Affected sites
- `src/ConsoleMode/Services/ConsoleEngine.cs:45` — initializes `State.AudioDeviceId` to the configured target before the switch attempt.
- `src/ConsoleMode/Services/ConsoleEngine.cs:141` — now ignores the failed `TrySetOutput` result and continues with that target in state.
- `src/ConsoleMode/ViewModels/MainViewModel.SessionMenu.cs:131-145` — treats `State.AudioDeviceId` as the current output for the menu label, volume, and mute.
- `src/ConsoleMode/ViewModels/MainViewModel.SessionMenu.cs:202-204` — marks that target as selected in the output picker.

## Required outcome
- After a failed startup switch, the session menu's displayed/selected output and volume/mute operations must correspond to the actual current output, rather than the requested target that failed.

@lippdev
lippdev merged commit 498f603 into main Sep 29, 2026
5 checks passed
@lippdev
lippdev deleted the fix/audio-switch-failure-not-fatal branch September 29, 2026 13:13
lippdev added a commit to nextestudios/consolemode that referenced this pull request Sep 30, 2026
The fixes merged since 1.6.0-alpha.2 were missing from CHANGELOG.md and
CHANGELOG.en-US.md: the failed audio output switch (lippdev#109) and the primary
display from Windows' own flag (lippdev#110), plus the rtss-cli-only package.
The Playnite item now points at the PR number (lippdev#83) instead of the issue.

docs/TESTING.md gets the two manual checks those fixes need (cloned screens
and a slow-starting Playnite).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant