Skip to content

feat: Volume Boost setting with system LoudnessEnhancer (#88) - #134

Merged
ProdigyV21 merged 1 commit into
mainfrom
feat/volume-boost
Apr 5, 2026
Merged

feat: Volume Boost setting with system LoudnessEnhancer (#88)#134
ProdigyV21 merged 1 commit into
mainfrom
feat/volume-boost

Conversation

@ProdigyV21

Copy link
Copy Markdown
Owner

Closes #88.

Adds a Settings row General > Audio > Volume Boost that cycles through 0 / 3 / 6 / 9 / 12 / 15 dB. 0 dB is the default and attaches no effect. Above 0 dB, the player creates an Android android.media.audiofx.LoudnessEnhancer bound to the ExoPlayer audio session and applies the target gain (dB * 100 millibels).

Useful for content whose source audio is very quiet relative to the user's TV/speaker setup \u2014 repeatedly requested in #88 and comparable to Debrify TV's volume boost feature that the OP referenced.

Plumbing

  • SettingsUiState / SettingsViewModel: new volumeBoostDb: Int. Stored as a string in DataStore (ProfileManager has no int helper) and parsed back with a 0-15 clamp. cycleVolumeBoost() mutator cycles through the steps and triggers cloud sync.
  • SettingsScreen: new Audio subsection at the bottom of General (below Network) containing a single SettingsRow for Volume Boost. Placed at focusedIndex == 14 so no existing indices shift. Max-index clamps bumped from 13 to 14. DNS Provider stays at index 13.
  • PlayerUiState / PlayerViewModel: new volumeBoostDb: Int, loaded at loadDetails init from the same DataStore key.
  • PlayerScreen: new DisposableEffect(uiState.volumeBoostDb, exoPlayer.audioSessionId) that creates a LoudnessEnhancer when targetDb > 0 and the session id is valid, sets target gain, enables, and releases on dispose. Wrapped in try/catch because some Android TV devices reject audio-session effects when HDMI passthrough is on for DTS/AC3 \u2014 fails silently and playback still works.
  • CloudSyncRepository: volumeBoostDb added to CloudProfileSettings, new volumeBoostDbKeyFor(profileId) helper, push/pull wiring.

Cap and safety

Max +15 dB (1500 mB). Higher values tend to introduce audible distortion on already-compressed streaming audio, and the LoudnessEnhancer class can support more but we intentionally don't expose it.

Merge-conflict note with PR #131

PR #131 (Show Budget toggle, #72) also modifies SettingsScreen.kt, SettingsViewModel.kt, and CloudSyncRepository.kt \u2014 both PRs add a new settings row at the end of the General section and a new field in CloudProfileSettings. Whoever merges second will need a small rebase to:

  • Bump the General max-index to 15 items (max 15) with both rows present.
  • Add both showBudget and volumeBoostDb to CloudProfileSettings.
  • Include both Enter-handler entries.

The conflict is purely additive. Both features coexist on main.

Risk

Low. LoudnessEnhancer is a stable AOSP class (API 19+, and minSdk on ARVIO is well above that). Default 0 means no effect instance is created, so non-boosted users see zero overhead. The try/catch handles the known HDMI passthrough edge case.

Adds a new Settings row "Volume Boost" under the Audio subsection that
cycles through 0 / 3 / 6 / 9 / 12 / 15 dB. 0 dB is the default and
attaches no effect. Above 0 dB, the player creates an Android
`android.media.audiofx.LoudnessEnhancer` bound to the ExoPlayer audio
session and applies the target gain. Useful for content whose source
audio is very quiet relative to the user's TV/speaker setup \u2014
repeatedly requested in #88 and comparable to the volume boost feature
in Debrify TV that the OP referenced.

Changes:

- `SettingsUiState` / `SettingsViewModel`: new `volumeBoostDb: Int`
  field. Stored as a string in DataStore via
  `profileManager.profileStringKey("volume_boost_db")` because
  ProfileManager has no int helper. Parsed back to Int on read with a
  0-15 clamp. `cycleVolumeBoost()` mutator advances through the steps
  and triggers cloud sync.
- `SettingsScreen`: new `Audio` subsection at the bottom of General
  (below `Network`) containing a single `SettingsRow` for "Volume Boost".
  The row is placed at `focusedIndex == 14` so no existing indices
  shift. Both the auto-scroll max-index and the D-pad-down max-index
  clamps are bumped from 13 to 14. The Enter handler maps
  `14 -> viewModel.cycleVolumeBoost()`. DNS Provider stays at index 13.
- `PlayerUiState` / `PlayerViewModel`: new `volumeBoostDb: Int` field,
  loaded from the same DataStore key during `loadDetails` initialization.
- `PlayerScreen`: new `DisposableEffect(uiState.volumeBoostDb,
  exoPlayer.audioSessionId)` that creates a `LoudnessEnhancer` when
  targetDb > 0 and the session id is valid, sets target gain in millibels
  (dB * 100), enables the effect, and releases it on dispose. Wrapped in
  try/catch because some Android TV devices reject audio-session effects
  when HDMI passthrough is enabled for DTS/AC3 \u2014 we fail silently and
  the user gets unboosted audio but playback still works.
- `CloudSyncRepository`: `volumeBoostDb` added to `CloudProfileSettings`,
  `volumeBoostDbKeyFor(profileId)` helper, push/pull wiring so the
  setting syncs across devices. Default is 0 so existing users see no
  change. Stored as string via profileStringKeyFor for the same reason
  as the SettingsViewModel side.

Cap: +15 dB (1500 millibels). Higher values tend to introduce audible
distortion on already-compressed streaming audio. The LoudnessEnhancer
class supports more but we intentionally don't expose it.

This PR touches `SettingsScreen.kt`, `SettingsViewModel.kt`, and
`CloudSyncRepository.kt` which PR #131 (Hide Budget, #72) also
modifies. Both PRs add a new settings row with different indices and
different fields in `CloudProfileSettings`. Whoever merges second will
need a small rebase to resolve:
- General section max-index should become 15 items (max 15) with both
  rows present.
- `CloudProfileSettings` needs both `showBudget` and `volumeBoostDb`
  fields.
- The Enter handler needs both `13 -> cycleVolumeBoost` and
  `14 -> openDnsProviderPicker` if #131 lands first, or an additional
  `15 -> setShowBudget` entry if this PR lands first.

The conflict is purely additive and both PRs can coexist on main.

Closes #88
@ProdigyV21
ProdigyV21 merged commit 48d4e5f into main Apr 5, 2026
2 checks passed
@ProdigyV21 ProdigyV21 mentioned this pull request Apr 5, 2026
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.

Volume Boost

1 participant