Skip to content

feat: dynamic range compressor for dialogue boost / action limiting - #3117

Open
fgmitesh wants to merge 57 commits into
recloudstream:masterfrom
fgmitesh:feat/dynamic-range-compressor
Open

feat: dynamic range compressor for dialogue boost / action limiting#3117
fgmitesh wants to merge 57 commits into
recloudstream:masterfrom
fgmitesh:feat/dynamic-range-compressor

Conversation

@fgmitesh

@fgmitesh fgmitesh commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Adds a real-time dynamic range compressor accessible via a new
"Compressor" button next to Tracks in the player controls.

Ported from VLC's compressor.c (LGPL, Steve Harris / Ronald Wright).
Implemented as a Media3 AudioProcessor injected into DefaultAudioSink
— sits directly in the audio pipeline, zero overhead when disabled.

Controls: threshold, ratio, attack, release, makeup gain.
All adjustable live via sliders with no player reload needed.
Settings are persisted across sessions.
Defaults: threshold -14dB, ratio 4:1, attack 10ms, release 50ms,
makeup +6dB.

Works with both nextlib and default decoder paths.
Full TV remote navigation. Correctly included in isDialogOpen().

This PR was developed with AI assistance (Claude by Anthropic) for implementation and bug hunting. All code has been tested on a real Android TV device and a phone.

This class implements a real-time dynamic range compressor as an AudioProcessor, allowing for audio signal compression based on specified parameters. It includes methods for configuring audio format, processing input, and managing internal state.
Added support for dynamic range compression in audio sink.
Added a DynamicRangeCompressor instance to the audio sink for live parameter updates.
@fgmitesh

fgmitesh commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

TESTING IS REMAINING = DRAFT FOR NOW

@fgmitesh

fgmitesh commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

It's almost done (Yeah it's W.I.P)

@fgmitesh

Copy link
Copy Markdown
Contributor Author

It's working now but needs little work

@fgmitesh
fgmitesh marked this pull request as ready for review August 11, 2026 07:58
@fgmitesh

Copy link
Copy Markdown
Contributor Author

done +tested+optional (player settings) defualt off

Updated compressor settings restoration logic to always restore settings when opening the dialog and when the player UI loads.
Refactor compressor dialog functions for clarity and efficiency, updating variable names and UI interactions.
Updated compressor dialog layout to follow the speed_dialog.xml pattern, including adjustments to width, orientation, and component arrangement.
Updated default compressor settings for better audio control.
@fgmitesh

Copy link
Copy Markdown
Contributor Author

Fixed some minor bugs now it's fully ready I checked it on tv and phone it works perfectly now

Removed LGPL attribution and simplified comment.

@fire-light42 fire-light42 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

First review based on quick testing and reading. It is a very good and cool feature, but needs some small refinement.

import kotlin.math.pow

/**
* Real-time dynamic range compressor ported from VLC's compressor.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this derivative of the compressor.c in VLC or is it just implementing the same algorithms used? This matters for licensing.

@Volatile var enabled: Boolean = false
@Volatile var threshold: Float = -24f // dB, -30..0
@Volatile var ratio: Float = 8f // n:1, 1..20
@Volatile var attackMs: Float = 5f // ms, 1..400

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

These are pretty much magic numbers. Please comment on what these do in a way to make it clear why these constants are chosen. Is it from a spec? What happens if we make the attackMs too low or too high, etc...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

See LiveManager for an example

On | Off →down→ slider row →down→ presets row →down→ stays (wraps)
Apply / Reset / Cancel reachable via nextFocusDown from slider row
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This dialog does not work on a phone at all.

The buttons do not fit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oh yes

binding.compressorEnableBtt to compressor.enabled,
binding.compressorDisableBtt to !compressor.enabled,
).forEach { (btn, active) ->
btn.setBackgroundColor(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This only updates the background, but the text remains white, leading to white on white.

import kotlin.math.pow

/**
* Real-time dynamic range compressor ported from VLC's compressor.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This text is only understandable for someone with a lot of knowledge already. You can keep the complexity if you want, but you must add/change the text to make it understandable for people without domain knowledge. Even a simple sentence describing the goal of a dynamic range compressors would go a long way.

)

fun syncPresetButtons(active: com.google.android.material.button.MaterialButton?) {
val ctx = requireContext()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Never use requireContext(), it will crash even when it should not. Use the nullable context object to explicitly handle null state.

@fgmitesh

Copy link
Copy Markdown
Contributor Author

I'll tweak stuff according to reviews u done thanks

@fgmitesh
fgmitesh requested a review from fire-light42 August 27, 2026 10:23
@fgmitesh

Copy link
Copy Markdown
Contributor Author

fixing minor error

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