Skip to content

feat(audio): keep host default sink with keep_default_sink - #5935

Open
mikefaille wants to merge 16 commits into
LizardByte:masterfrom
mikefaille:feat/audio-keep-default-sink
Open

mikefaille wants to merge 16 commits into
LizardByte:masterfrom
mikefaille:feat/audio-keep-default-sink

Conversation

@mikefaille

@mikefaille mikefaille commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds a shared keep_default_sink audio option that lets Sunshine capture a
configured audio_sink without making the Sunshine sink the host default.

Mechanism: a sink-routing policy object is resolved from the audio settings
and handed to capture_sink. When the option is enabled, capture only sets
the requested sink and logs that the host default is being left alone — no
swap at startup, no restore at teardown. The capture-only path targets the
configured sink, or the saved host sink when nothing is configured, so muted
hosts never capture a silent virtual sink. The option defaults to off,
preserving current behavior unless explicitly opted in. Wired through config,
the web UI audio tab, and unit tests.

Backend support: Linux adopts pre-existing null sinks instead of unloading
them on peer teardown; Windows captures the target endpoint directly and
skips the default reassertion callback, the startup default-device reset,
and the Steam driver install while the option is enabled; macOS honors the
option natively since tap/input capture never touches the default output.

Note: this addresses the reported default-sink takeover only when the new
option is enabled; existing audio_sink-only configs still swap by default.

Screenshot

The change adds a "Keep Default Sink" checkbox to the Audio/Video settings
tab. Screenshot to be attached by the author.

Issues Fixed or Closed

Roadmap Issues

None.

Testing

  • cmake --build build -j2 (Release, Ninja, CUDA enabled): exit 0
  • Filtered run --gtest_filter='*Audio*:*Sink*:*KeepDefault*:*Config*':
    311 tests from 37 suites, all passed — including
    KeepDefaultSinkAudioTest.CaptureSinkKeepsHostDefaultSink,
    NullSinkOwnershipTest.AdoptedSinksSurvivePeerTeardown,
    SinkRoutingPolicyTest (x2), CaptureRoutingTest (x4),
    AudioControlAdapterTest, and KeepDefaultSinkConfigTest (x2)
  • Full suite: 873 tests from 154 suites — 866 passed, 7 skipped
    (pre-existing platform skips), 0 failed
  • Web: npx vitest run tests/web/ExternalAudio.test.js tests/web/KeepDefaultSink.test.js --coverage.enabled=false: 8 passed
  • uv run --locked lb-update-clang-format: no changes (format clean)
  • SonarCloud Quality Gate: passed, 0 new issues (re-scan pending on latest head)
  • Windows/macOS backend code and tests are compile-guarded and could not be
    built on this Linux host; they follow existing seam patterns and await
    platform CI (fork workflows await maintainer approval)

Type of Change

  • feat: New feature (non-breaking change which adds functionality)
  • fix: Bug fix (non-breaking change which fixes an issue)
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semicolons, etc.)
  • refactor: Code change that neither fixes a bug nor adds a feature
  • perf: Code change that improves performance
  • test: Adding missing tests or correcting existing tests
  • build: Changes that affect the build system or external dependencies
  • ci: Changes to CI configuration files and scripts
  • chore: Other changes that don't modify src or test files
  • revert: Reverts a previous commit
  • BREAKING CHANGE: Introduces a breaking change (can be combined with any type above)

Checklist

  • Code follows the style guidelines of this project
  • Code has been self-reviewed
  • Code has been commented, particularly in hard-to-understand areas
  • Code docstring/documentation-blocks for new or existing methods/components have been added or updated
  • Tests have been added or updated for new or modified functionality, including regression tests for bug fixes

AI Usage

See our AI usage policy.

  • AI tools were used while preparing this PR

AI-assisted implementation and tests; reviewed and tested by the author.

… host default

Adds the keep_default_sink config key controlling whether audio capture swaps the host default sink.

- Config: new keep_default_sink key (default disabled) with validation.
- Policy: when enabled, capture keeps the host default sink untouched; teardown restores only state this session changed.
- UI: AudioVideo tab toggle plus locale strings.
- Docs: configuration.md documents the new key.
- Tests: config, audio, and linux audio unit coverage.
Rename shadowed module field (S1669), extract adopt_sink/ensure_sink
helpers from the duplicated sink setup blocks (S3776), shrink the
oversized enumeration lambda (S1188), and guard destructor teardown
against throwing unloads (S1048). Behavior unchanged.
Give the destructor's catch-all a log statement so the intentionally
suppressed unload path is not an empty handler (S2486). Teardown
stays non-throwing.

@ReenigneArcher ReenigneArcher left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why doesn't your agent follow the AGENTS.md file?

@mikefaille

Copy link
Copy Markdown
Contributor Author

@ReenigneArcher I will review.

@mikefaille

Copy link
Copy Markdown
Contributor Author

Why doesn't your agent follow the AGENTS.md file?

I reviewed myself and I don't see the issue.

@ReenigneArcher

Copy link
Copy Markdown
Member

When asked to create an issue or pull request, use the templates provided by the LizardByte/.github repo or

https://docs.lizardbyte.dev/latest/developers/contributing.html

@ReenigneArcher ReenigneArcher left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Requesting changes for the silent-capture case and the web-test regression detailed inline.

The configured-equalizer scenario from #4950 is handled by the new capture-only path in focused routing tests. Latest master still contains the default-sink swap, so this scope is not superseded.

Cross-platform scope: please keep this as a shared routing option and extend support beyond Linux. Windows already resolves explicit WASAPI endpoints directly, and its existing external_audio mode preserves defaults; reconcile the new option with that mode's broader contract. A Windows implementation needs a capture-only path plus protection against the default-endpoint reassertion callback, startup default-device reset, and any automatic driver-installation path that changes defaults. macOS already captures through a system tap or selected input without changing the default output, so preserve and document that behavior. Add platform-specific tests for startup, device-change notifications, and teardown. The earlier inline recommendation to hide the setting outside Linux is withdrawn.

Validation: git diff --check and clang-format checks passed for all changed native files. Clang syntax checks passed for src/audio.cpp, src/config.cpp, and their two changed cross-platform test files. The existing ExternalAudio.test.js suite has 1 failure / 4 passes on this head; updating the Windows control-list assertion for the shared checkbox gives 5 passes. A GoogleTest harness using the PR's extracted routing/policy/monitor-selection code has 1 failure / 4 passes; the suggested capture-target change gives 5 passes. These are focused checks, not a full Sunshine build or a live PulseAudio playback test.

Verification limit: GitHub Actions awaits maintainer approval. I inspected the failed GH-Pages Read the Docs log: it timed out waiting for call-jekyll-build / Build Jekyll, rather than reporting a source/documentation defect. SonarCloud and the main documentation check passed.

The head is still 83155a7823c89870f05390fa8977d09a5049b693 and GitHub reports it mergeable. The earlier attribution request remains unaddressed: please name the AI agent, model, and thinking level at the end of the PR body and repeat that attribution in a separate comment, as required by the repository instructions.

Review completed by Codex | Model: gpt-6.1-sol | Thinking: xhigh

Comment thread src/audio.cpp Resolved
Route the capture-only path at the configured sink, or the saved host sink
when nothing is configured, instead of the virtual sink. Without a swap,
applications stay on the host default, so capturing the virtual sink's
monitor yields silence when host playback is muted.

Extract the startup selection plus swap-vs-capture policy into the pure
resolve_capture_routing() helper so the empty/configured sink by HOST_AUDIO
matrix is covered by unit tests, and pin the historical virtual-sink
selection on the swap path.
Include the new shared checkbox in the Windows control-list assertion and
add round-trip coverage rendering it on Windows, Linux, and macOS.
Share the keep-default routing contract across backends. Windows records
the capture target for direct WASAPI capture and guards the default
reassertion callback, the startup default-device reset, and the Steam
driver install behind hands-off-mode predicates, composing with the
existing external_audio behavior. macOS reports support natively since
tap/input capture never touches the default output. Document the
per-platform behavior.
Satisfies Sonar cpp:S3562 on the new code. Behavior unchanged: layouts
other than stereo/5.1/7.1 keep the previously selected sink.
Extract the duplicated mountAudioVideo helper into audioVideoMount.js so
the keep_default_sink suite and the external_audio suite use one copy.
Clears the Sonar new-code duplication gate.
@mikefaille

Copy link
Copy Markdown
Contributor Author

I tried for a common interface for Linux Mac and Windows but it's would be more sane to split the PR to focus only on Linux as I can't test Windows and Mac right now.

Unless someone want to test this patch directly with Windows and Mac ?

@ReenigneArcher ReenigneArcher left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Rereviewed 6747ba142b37c48bc9643ce291793278f2b1b722, including the follow-up changes since 83155a7 and the complete diff against current master (1582fff). Both earlier code findings are fixed: capture-only routing selects the configured/host sink, and the shared checkbox is covered by the updated web tests. I found no additional runtime defect in this review.

The Steam-driver statement matches the implementation. Installation is conditional in Windows audio_control(), when the controller is created and the driver is missing. Configuration is loaded before platform/audio initialization, so the option can be enabled before that installation attempt. An already-installed driver is left installed. The setting suppresses a future automatic installation attempt; it does not remove an existing installation.

The remaining required change is the attribution previously requested. The current PR body still ends with generic “AI-assisted implementation and tests” text, and the discussion has no corresponding agent/model/thinking attribution. Please add the actual AI agent, model, and thinking level at the end of the body and repeat it in a separate comment, as required by the repository contribution instructions. This rereview requests changes for that outstanding requirement; the two earlier code findings are resolved.

Validation: all 8 tests in ExternalAudio.test.js and KeepDefaultSink.test.js passed. I compiled and ran the 7 current SinkRoutingPolicyTest, CaptureRoutingTest, and AudioControlAdapterTest cases in a focused binary against the PR's actual header; all passed. Clang syntax checks passed for src/audio.cpp, src/config.cpp, their changed common test files, src/platform/macos/microphone.mm, and test_av_audio.mm. Formatting checks passed for all 13 changed native files, and git diff --check passed. These checks do not constitute a full Sunshine build or live audio-device validation.

Verification limit: Windows and Linux platform execution remains unverified locally. Hosted workflows await maintainer approval; the failed GH-Pages Read the Docs build timed out waiting for its build workflow. SonarCloud and the main documentation check passed. GitHub reports this head mergeable.

Review completed by Codex | Model: gpt-6.1-sol | Thinking: xhigh

@ReenigneArcher

Copy link
Copy Markdown
Member

I tried for a common interface for Linux Mac and Windows but it's would be more sane to split the PR to focus only on Linux as I can't test Windows and Mac right now.

Unless someone want to test this patch directly with Windows and Mac ?

I can test it.

@sonarqubecloud

Copy link
Copy Markdown

@DanielGaaA

Copy link
Copy Markdown

I saw in release notes of new version that this was approved but for Windows only #5647 It would probably be best to use same variable for windows and linux.

@mikefaille

mikefaille commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor Author

I saw in release notes of new version that this was approved but for Windows only #5647 It would probably be best to use same variable for windows and linux.

I will have an eye on it today. I will try to see if we can unify even more the implementation.

This branch has not been deployed

No deployments
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.

3 participants