Skip to content

Expose RtmpClientSettings on RtmpEndpoint and RtmpEndpointFactory#286

Open
alexmck wants to merge 1 commit intoThibaultBee:dev_v3_2from
alexmck:feature/add-flashvar-config
Open

Expose RtmpClientSettings on RtmpEndpoint and RtmpEndpointFactory#286
alexmck wants to merge 1 commit intoThibaultBee:dev_v3_2from
alexmck:feature/add-flashvar-config

Conversation

@alexmck
Copy link
Copy Markdown

@alexmck alexmck commented Apr 3, 2026

Summary

This change adds an optional RtmpClientSettings parameter to RtmpEndpoint and RtmpEndpointFactory, allowing developers to configure the RTMP connect command sent during the handshake with the server.

The underlying komuxer RTMP library already supports full configuration of the connect command via RtmpClientSettings and ConnectObjectBuilder, but StreamPack's RtmpEndpoint was hard coded to use RtmpClientSettings() defaults. This change threads that configuration through to the public API.

Motivation

A common requirement for RTMP streaming applications is to identify themselves to the server during the connect handshake — typically by setting a custom flashVer string. This is useful for:

  • Debugging — server operators can identify which app, version, and device a stream originates from
  • Analytics — tracking client distribution across app versions and device types
  • Access control — servers can enforce policies based on client identity

Without this change, all StreamPack clients identify as the komuxer default: FMLE/3.0 (compatible; FMSc/1.0).

What changed

A single file was modified — RtmpEndpoint.kt:

  1. RtmpEndpoint constructor — added clientSettings: RtmpClientSettings = RtmpClientSettings() parameter
  2. RtmpEndpoint.open() — passes clientSettings to connectionBuilder.connect() instead of using the implicit default
  3. RtmpEndpointFactory constructor — added matching clientSettings parameter, forwarded to every endpoint it creates

All parameters have defaults, so this is fully backwards-compatible. Existing code that constructs RtmpEndpointFactory() with no arguments behaves identically to before.

Usage

val factory = RtmpEndpointFactory(
    clientSettings = RtmpClientSettings(
        connectInfo = {
            flashVer = "MyApp/1.0"
        }
    )
)

val streamer = SingleStreamer(
    context,
    withAudio = true,
    withVideo = true,
    endpointFactory = factory
)

The connectInfo lambda receives a ConnectObjectBuilder pre-populated with defaults. Only the fields you set are overridden — everything else retains its default value.

Exposed settings

As a bonus, this change allows customisation of all RTMP options.

RtmpClientSettings (connection-level)

Parameter Type Default Description
writeWindowAcknowledgementSize Int Int.MAX_VALUE RTMP flow control — bytes before the server expects an ACK
amfVersion AmfVersion AMF0 AMF encoding format (AMF0 or AMF3)
clock RtmpClock RtmpClock.Default() Clock used for RTMP message timestamps
tooLateFrameDropTimeoutInMs Long? null (never drop) Drop frames older than this threshold — useful for reducing latency on slow connections
connectInfo ConnectObjectBuilder.() -> Unit {} Lambda to configure the RTMP connect command object (see below)

ConnectObjectBuilder (connect command fields, via connectInfo)

Property Type Default Description
flashVer String "FMLE/3.0 (compatible; FMSc/1.0)" Client identifier sent to the server
swfUrl String? null URL of the source SWF file
pageUrl String? null URL of the web page hosting the stream
fpad Boolean false Whether a proxy is being used
capabilities Int 239 Bitmask of client capabilities
audioCodecs List<AudioMediaType>? [AAC, G711_ALAW, G711_MLAW] Advertised audio codec support
videoCodecs List<VideoMediaType>? [SORENSON_H263, AVC] Advertised video codec support
videoFunction List<VideoFunction> [] Supported video functions (see below)
objectEncoding ObjectEncoding AMF0 Object encoding version
capsEx List<CapsEx>? null Enhanced RTMP v2 extended capabilities (see below)
audioTrackIdInfoMap Map<AudioMediaType, List<FourCCInfo>>? null Enhanced RTMP v2 audio track info
videoFourCcInfoMap Map<VideoMediaType, List<FourCCInfo>>? null Enhanced RTMP v2 video FourCC info

VideoFunction (Enhanced RTMP v1)

Value Description
CLIENT_SEEK Client supports seeking
CLIENT_HDR Client supports HDR video
CLIENT_PACKET_TYPE_METADATA Client supports metadata packet type
CLIENT_LARGE_SCALE_TILE Client supports large-scale tiling

CapsEx (Enhanced RTMP v2)

Value Description
RECONNECT Client supports session reconnection after a dropped connection
MULTITRACK Client supports multiple audio/video tracks in a single RTMP connection
MODEX Client supports mid-stream codec mode exchange
TIMESTAMP_NANO_OFFSET Client supports nanosecond-precision timestamp offsets

FourCCInfo (per-codec capability flags, Enhanced RTMP v2)

Value Description
CAN_DECODE Client can decode this codec
CAN_ENCODE Client can encode this codec
CAN_FORWARD Client can forward/passthrough this codec

As an example, this would allow setting a    custom `flashVer` to identify the application to the server.
Copilot AI review requested due to automatic review settings April 3, 2026 01:33
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

This PR exposes komuxer’s RtmpClientSettings through StreamPack’s RTMP endpoint API so callers can customize the RTMP connect handshake (e.g., flashVer) instead of always using komuxer defaults.

Changes:

  • Add an optional RtmpClientSettings parameter to RtmpEndpoint.
  • Thread clientSettings into RtmpConnectionBuilder.connect(...) during open().
  • Add an optional clientSettings parameter to RtmpEndpointFactory and forward it to created endpoints.

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

defaultDispatcher: CoroutineDispatcher,
val ioDispatcher: CoroutineDispatcher,
private val clientSettings: RtmpClientSettings = RtmpClientSettings()
) : IEndpointInternal {
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

Adding clientSettings with a default value removes the 2-arg JVM constructor signature ((CoroutineDispatcher, CoroutineDispatcher)) that the core module instantiates reflectively (see core/.../Endpoints.kt uses getConstructor(CoroutineDispatcher, CoroutineDispatcher)). This will make RTMP endpoint creation fail at runtime when using DynamicEndpoint/Endpoints.createRtmpEndpoint.

Consider adding an explicit 2-arg secondary constructor delegating to the new one, or annotate the constructor with @JvmOverloads so the 2-arg overload is generated and reflection keeps working.

Suggested change
) : IEndpointInternal {
) : IEndpointInternal {
internal constructor(
defaultDispatcher: CoroutineDispatcher,
ioDispatcher: CoroutineDispatcher
) : this(defaultDispatcher, ioDispatcher, RtmpClientSettings())

Copilot uses AI. Check for mistakes.
}

rtmpClient = connectionBuilder.connect(descriptor.uri.toString()).apply {
rtmpClient = connectionBuilder.connect(descriptor.uri.toString(), settings = clientSettings).apply {
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

connect(..., settings = clientSettings) uses a named argument for an API coming from an external dependency (io.github.komedia.komuxer.rtmp.connect). In Kotlin, named arguments are source-level and can break consumers if the dependency renames the parameter (even without changing behavior). Prefer using positional arguments (or an overload) here to avoid coupling to the parameter name.

Suggested change
rtmpClient = connectionBuilder.connect(descriptor.uri.toString(), settings = clientSettings).apply {
rtmpClient = connectionBuilder.connect(descriptor.uri.toString(), clientSettings).apply {

Copilot uses AI. Check for mistakes.
* endpoint created by this factory. Use the [RtmpClientSettings.connectInfo]
* lambda to customise the RTMP connect command — for example, to set a
* custom `flashVer` string that identifies your application to the server.
*/
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

RtmpEndpointFactory now relies on a default constructor parameter. Kotlin callers can still do RtmpEndpointFactory(), but Java callers will no longer have a true no-arg constructor unless you generate overloads (e.g., @JvmOverloads) or add an explicit no-arg secondary constructor. If this factory is part of the public API for Java consumers, this is a source-breaking change.

Suggested change
*/
*/
@JvmOverloads

Copilot uses AI. Check for mistakes.
@ThibaultBee
Copy link
Copy Markdown
Owner

ThibaultBee commented Apr 3, 2026

Hi,

I do understand the reason for this but this is a bad usage of flashVer.
I won't detailed more or suggest another solution as you haven't use a single of your own word. Ask AI.

@ThibaultBee ThibaultBee closed this Apr 3, 2026
@alexmck alexmck deleted the feature/add-flashvar-config branch April 3, 2026 08:34
@alexmck alexmck restored the feature/add-flashvar-config branch April 3, 2026 08:39
@alexmck
Copy link
Copy Markdown
Author

alexmck commented Apr 3, 2026

Hey ThibaultBee. This was a genuine pull request. I have a similar change that was merged into RootEncoder.

While I realise I got a bit carried away with the scope and this turned into more than just setting a custom flashVer. I was working on my own custom factory and custom endpoint that achieved the same thing, I didn't feel it was the best solution to this problem.

I was genuinely quite excited by this approach which gives the developer direct access to connect command properties and RTMP client settings in the komuxer library, even if these properties are niche and often ignored by servers, as there is no way to reach this natively currently in the StreamPack library.

If you're really not interested, I can absolutely respect that decision and I can either keep using my own fork, or go back to my original implementation. Alternatively if you would prefer I take a different approach, I'm more than happy to spend time revising this pull request.

@ThibaultBee
Copy link
Copy Markdown
Owner

Could you explain why you need to identify devices? IMHO, as you are not going to give the same RTMP URL (path and stream key) to multiple devices, the RTMP URL allows you to identify the user.
Why do you need to identify the device and not the user?

Oh, nice you are using your own factory and endpoint. I have got questions now (but they are out of this scope).

Your PR is missing the implementation in the DynamicEndpoint. Anyway I need few days to think about the best implementation.

@ThibaultBee ThibaultBee reopened this Apr 3, 2026
@alexmck
Copy link
Copy Markdown
Author

alexmck commented Apr 3, 2026

That's correct. Each user has their own unique stream key, and authentication happens server side. Setting the flashVer is not about identifying individual users, but rather helping us know which version of our app customers are using.

From a support point of view, it's often very helpful to be able to troubleshoot and tell a customer that they are using an old version of the app, or triage an issue with a newer version of the app. The number of users that have auto updates turned off is also staggering!

But user authentication via flashVer 🤯 I mean, I guess that's one way to do it, but I agree that's the wrong way.

If I can provide anymore assistance or guidance please let me know.

@ThibaultBee
Copy link
Copy Markdown
Owner

You are trying to use a streaming protocol to pass debug info. It feels lazy.
At some point you have a communication between the application and your server (at least to get the RTMP url), why don't you pass the application version (and fingerprint) at this moment?

@alexmck
Copy link
Copy Markdown
Author

alexmck commented Apr 3, 2026

It's no different to using the User Agent String from a browser to identify the browser and version number. flashVer is in some ways synonymous to this, and this was to my knowledge it's intended use case. Sending it with the RTMP connection is one way to ensure that the correct client meta data is sent when the stream is negotiating it's connection, rather than relying on extra HTTP overhead and trying to line the two up later. This becomes particularly important for slow and high latency connections, such as poor 4G connectivity.

@ThibaultBee
Copy link
Copy Markdown
Owner

Yeah but HTTP is not a streaming protocol.

About the implementation, have you consider using MediaDescriptor.customData instead?

@ThibaultBee
Copy link
Copy Markdown
Owner

Could you move your PR to dev_v3_2 (https://github.com/ThibaultBee/StreamPack/tree/dev_v3_2) branch? Somehow I am not allowed to move it.

@alexmck alexmck changed the base branch from main to dev_v3_2 April 3, 2026 23:44
@alexmck
Copy link
Copy Markdown
Author

alexmck commented Apr 3, 2026

I had not considered using MediaDescriptor.customData, but that would also work. That would limit things to the connection command parameters, and that would still include flashVer.

Would you like me to rework this to use the customData approach?

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