Skip to content

feat(audio): add system default audio device selection to AudioDeviceSelector#54

Open
BegoniaHe wants to merge 1 commit intorvarunrathod:mainfrom
BegoniaHe:feature/system-default-audio
Open

feat(audio): add system default audio device selection to AudioDeviceSelector#54
BegoniaHe wants to merge 1 commit intorvarunrathod:mainfrom
BegoniaHe:feature/system-default-audio

Conversation

@BegoniaHe
Copy link
Copy Markdown

@BegoniaHe BegoniaHe commented Feb 10, 2026

Summary

This PR implements a "System Default" audio output option that automatically follows macOS system audio device changes, addressing issue #48.

Problem Statement

Previously, when users had external DAC/amp devices that aren't USB-powered, HiFidelity required manual reselection of the audio device after sleep/wake cycles, even though macOS automatically switched back to the device. This created friction in the user experience, especially for users who frequently pause playback by closing their laptop lid.

Solution

Core Changes

1. Audio Device Management (DACManager.swift)

  • System Default Tracking: Added systemDefaultDeviceID and systemDefaultDevice properties to track the current system default output device
  • Real-time Monitoring: Implemented AudioObjectPropertyListener for kAudioHardwarePropertyDefaultOutputDevice to detect system audio routing changes
  • Automatic Switching: When "System Default" is selected, HiFidelity automatically follows macOS audio output changes without user intervention

2. User Interface (AudioDeviceSelector.swift)

  • New Option Row: Added a dedicated "System Default" row at the top of the device selector
  • Visual Feedback:
    • Shows the current system default device name below the "System Default" label
    • Displays checkmark indicator when "System Default" mode is active

Screenshot

image

Related Issue

Closes #48

Notes

  • The "System Default" option is now the default behavior for new users
  • Users who prefer manual control can still select specific devices, which automatically disables the follow-system-default mode
  • Technical details (sample rate, channel count) are intentionally omitted from the "System Default" row to keep the UI clean, as they're already visible in the individual device entries below

Questions for Reviewers

Should the "System Default" row display technical details (sample rate, stereo/mono) like other device entries? Currently opted for a cleaner look since:

  1. The information is already available in the actual device row below
  2. It keeps the text from becoming too long
  3. The core value of "System Default" is following system behavior, not the technical specs

Feedback welcome!

…Selector

- add system default audio device selection to AudioDeviceSelector according to ISSUE rvarunrathod#48
Copilot AI review requested due to automatic review settings February 10, 2026 19:40
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a “System Default” audio output option so HiFidelity can follow the OS-selected output device (Issue #48), improving behavior when devices (e.g., USB DACs) disconnect/reconnect or when macOS switches outputs automatically.

Changes:

  • Add a “System Default” row to the audio output device popover and route selection through a new switchToSystemDefault() action.
  • Extend DACManager to track the system default device and optionally follow it, including a CoreAudio listener for default-output changes.
  • Refactor device switching logic to separate “follow system default” mode from direct device selection.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
HiFidelity/Views/Playback/AudioDeviceSelector.swift Adds a selectable “System Default” row and updates selection logic/UI to reflect follow-default mode.
HiFidelity/Core/Audio/DACManager.swift Introduces follow-default state + default-device tracking and listens for macOS default output changes to auto-switch when enabled.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +48 to +52
nonisolated deinit {
Task { @MainActor [weak self] in
self?.removeDeviceChangeListener()
self?.removeDefaultDeviceChangeListener()
}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

The new nonisolated deinit schedules listener removal asynchronously and captures self weakly. By the time the Task runs, self will often already be deallocated, so the listeners never get removed and CoreAudio callbacks can later dereference a freed clientData pointer (use-after-free / crash). Also, nonisolated is only valid for actor-isolated/global-actor-isolated types; if DACManager isn’t @MainActor, this likely won’t compile. Remove both listeners synchronously in deinit (no Task, no weak self), and if you truly need main-actor isolation, mark the type @MainActor and keep teardown synchronous within deinit.

Suggested change
nonisolated deinit {
Task { @MainActor [weak self] in
self?.removeDeviceChangeListener()
self?.removeDefaultDeviceChangeListener()
}
deinit {
removeDeviceChangeListener()
removeDefaultDeviceChangeListener()

Copilot uses AI. Check for mistakes.
Comment on lines +120 to +137

/*
Text("•")
.font(AppFonts.captionMedium)
.foregroundColor(.secondary.opacity(0.5))

Text("\(Int(defaultDevice.sampleRate)) Hz")
.font(AppFonts.captionMedium)
.foregroundColor(.secondary)

Text("•")
.font(AppFonts.captionMedium)
.foregroundColor(.secondary.opacity(0.5))

Text(channelDescription(defaultDevice.channels))
.font(AppFonts.captionMedium)
.foregroundColor(.secondary)
*/
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

There’s a large commented-out UI block inside the System Default row (sample rate / channels). Leaving dead UI code in-line makes this view harder to maintain; either remove it or reintroduce it behind a clear condition/flag so the intent is preserved without commented code.

Suggested change
/*
Text("")
.font(AppFonts.captionMedium)
.foregroundColor(.secondary.opacity(0.5))
Text("\(Int(defaultDevice.sampleRate)) Hz")
.font(AppFonts.captionMedium)
.foregroundColor(.secondary)
Text("")
.font(AppFonts.captionMedium)
.foregroundColor(.secondary.opacity(0.5))
Text(channelDescription(defaultDevice.channels))
.font(AppFonts.captionMedium)
.foregroundColor(.secondary)
*/

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm not sure if the "System Default" row should display technical details (sample rate + channel configuration)

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.

[Feature] Follow audio device used by OS

3 participants