Skip to content

Monitor status bar 1/5: Extract audio device handling into AudioDevices - #90

Merged
punk-kaos merged 2 commits into
JS8Call-improved:masterfrom
tekstrand:audio-devices
Sep 5, 2026
Merged

Monitor status bar 1/5: Extract audio device handling into AudioDevices#90
punk-kaos merged 2 commits into
JS8Call-improved:masterfrom
tekstrand:audio-devices

Conversation

@tekstrand

Copy link
Copy Markdown
Contributor

Part 1 of 5 replacing the Monitor status card with a compact status strip.

  1. AudioDevices extraction (this PR)
  2. The UI with the audio picker behind an overflow menu
  3. Rig link indicator on the strip, fed by a new rig status broadcast
  4. Error detection state display (i might combine this with 3, we'll see)
  5. Add the same audio device selection menu to Settings

What

  1. Adds AudioDevices, one home for the audio input list, the stored choice and the live device switch
  2. Rewires the Monitor spinner and the start path to read from it
  3. Removes the TruSDX coercion from the start path
  4. Points the service's device name table and TruSDX labels at the same shared source

Why

  1. The strip moves the picker into an overflow menu and Settings offers the same choice later, so the logic has to leave the fragment first.
  2. A pick now stores immediately instead of only while the engine is running, so a choice made before pressing Start survives a tab switch instead of silently reverting.
  3. With a TruSDX rig the list itself holds only the two TruSDX inputs, so a stale selection resolves to TruSDX Serial by construction instead of being forced there at start.
  4. The service carried a 1-to-1 copy of the name table and the TruSDX labels appeared as literals in three files, so a rename could make the spinner and the status line disagree about the same device.

Test

  1. ./gradlew :app:testDebugUnitTest
  2. On the Fire HD 10 profile emulator: the spinner lists the same devices, Start logs the chosen device id, and with rig_type=trusdx_serial the list swaps to the two TruSDX inputs. Picking TruSDX Speaker while stopped stores the choice and it survives a tab round trip.

…tch out of the Monitor fragment into a shared AudioDevices object. The fragment keeps its spinner but only renders what the object reports: the list comes from AudioDevices.list, the restored selection from AudioDevices.selected, and a user pick goes through AudioDevices.select, which stores the choice and moves a running capture onto it. The choice is now stored even while the engine is stopped, so a pick made before pressing Start survives the fragment being recreated; before, an unsaved pick silently reverted on a tab switch. Start reads the stored choice instead of the spinner position, which also removes the TruSDX coercion from the start path: with a TruSDX rig the list itself holds only the two TruSDX inputs, so a stale non-TruSDX selection resolves to TruSDX Serial by construction instead of being forced there. The Settings screen offers the same audio choice next, which is what the shared object is for.

The extraction absorbed the duplicates around it. The service's getDeviceName carried a verbatim copy of the device name table, so it delegates to AudioDevices.nameFor and the table exists once; the TruSDX display labels became constants beside the ids they name instead of literals in three files; and the trusdx check deciding whether the mic permission applies reads the same predicate the list uses. select skips the store and the dispatch when the choice is unchanged and reports whether it moved a live capture, so the switching snackbar keys off that answer and the fragment dropped its own copy of the selected id. Two leftovers went with it: the isUpdatingSpinner flag guarded programmatic selections that the user-initiated check already absorbs, because setSelection fires its callback only after the flag has been reset, and the refresh at the end of spinner setup duplicated the one onResume always makes. refreshAudioDevices resolves the saved choice against the list it just built instead of enumerating the devices twice, and selected cannot return null because the list is never empty.
@punk-kaos

Copy link
Copy Markdown
Contributor

I think there’s one edge case in AudioDevices.select() worth fixing before merge.

Right now it returns early if the newly selected device matches the saved preference. That assumes the saved device is also the device the engine is currently using, but those can get out of sync. For example, if the saved USB device is unplugged at startup, the app falls back to another input without changing the saved preference. If that USB device later comes back and the user selects it, select() sees that it already matches the saved ID and does nothing, so the UI can show the USB device while the engine is still using the fallback input.

I think the preference check should only control whether the preference needs updating. If the engine is running, it should still send the switch request and let JS8EngineService decide whether the requested device is already active, since it already has that check.

…ed preference. The saved device and the device the engine is capturing on can differ: a saved input that is unplugged at startup resolves to a fallback without touching the preference, so when it comes back and gets picked, the early return concluded there was nothing to do and the engine stayed on the fallback while the UI showed the pick. The preference comparison now gates only the preference write, and a running engine always gets the switch request, since the service is the only party that knows the active device and already ignores a request for it.
@tekstrand

Copy link
Copy Markdown
Contributor Author

♻️ thanks!

@tekstrand

Copy link
Copy Markdown
Contributor Author

I think the preference check should only control whether the preference needs updating.

Yeah, you're right. The saved id and the active capture can drift exactly the way you describe, and the service is the only side that knows the active device. The check now gates only the preference write and a running engine always gets the switch request. Funny enough the old fragment code had this right by accident, it deduped against the resolved selection rather than the raw preference, and I dropped that in the cleanup. Pushed as a follow up commit.

@punk-kaos
punk-kaos merged commit 4288656 into JS8Call-improved:master Sep 5, 2026
1 check passed
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.

2 participants