Skip to content

Add streaming stats callback with byte-accurate queue monitoring - #2168

Merged
pedroSG94 merged 7 commits into
pedroSG94:masterfrom
diamondkinetics:feature/streaming-stats
Aug 4, 2026
Merged

Add streaming stats callback with byte-accurate queue monitoring#2168
pedroSG94 merged 7 commits into
pedroSG94:masterfrom
diamondkinetics:feature/streaming-stats

Conversation

@bratta-dk

Copy link
Copy Markdown
Contributor

Summary

  • Add onStreamingStats(StreamingStatsReport) to BitrateChecker for byte-accurate send-queue depth and 3-interval bandwidth trend classification.
  • Add StreamingStatsMonitor in common, wired through BaseSender (all protocols: RTMP, RTSP, SRT, UDP, WHIP).
  • Add StreamBlockingQueue.getTotalSize() to sum queued frame bytes.
  • Mirrors HaishinKit NetworkMonitor behavior.
  • Fully backward compatible: default empty callback; onNewBitrate, hasCongestion, and BitrateAdapter unchanged.

Motivation

We ship a cross-platform streaming app: HaishinKit on iOS, RootEncoder on Android. Adaptive bitrate and congestion handling live in shared Kotlin common code — one strategy layer that expects the same stats shape on both platforms.

On iOS, HaishinKit exposes byte-accurate queue depth, send throughput, and sustained congestion trends via NetworkMonitorReport and throughput delegate events (publishInsufficientBWOccured / publishSufficientBWOccured). RootEncoder's existing hasCongestion() API only reports a single frame-count snapshot (% of send queue full). Strategies that need byte queue depth and trend classification over multiple intervals cannot derive that from frame counts alone, and cannot feed the shared common layer the same signals iOS already provides.

This PR adds an equivalent RootEncoder-native API so Android can supply matching telemetry to shared adaptive-bitrate logic — without polling or estimating queue size from frame count.

API

New types in com.pedro.common:

Type Purpose
StreamingStatsReport Per-second report: bytesOutPerSecond, queueBytesOut, totalBytesOut, throughput, bitrate, smoothedBitrate
Throughput Unknown, Sufficient, Insufficient — classified over 3 consecutive 1-second intervals

New callback on BitrateChecker:

default void onStreamingStats(StreamingStatsReport report) {}

Delivered on the main thread once per second while streaming, alongside existing onNewBitrate.

HaishinKit mapping

HaishinKit RootEncoder
NetworkMonitorReport.currentBytesOutPerSecond StreamingStatsReport.bytesOutPerSecond
NetworkMonitorReport.currentQueueBytesOut StreamingStatsReport.queueBytesOut
NetworkMonitorEvent.publishInsufficientBWOccured Throughput.Insufficient
Stable queue over 3 samples Throughput.Sufficient

Expose HaishinKit-equivalent streaming telemetry via a new
StreamingStatsMonitor that reports queue bytes, send rate, and
3-interval throughput classification (Sufficient/Insufficient/Unknown)
through BitrateChecker.onStreamingStats(StreamingStatsReport).

Wire the monitor through BaseSender so all protocols benefit, add
StreamBlockingQueue.getTotalSize() for byte-accurate queue depth.
@pedroSG94

Copy link
Copy Markdown
Owner

Hello,

Thank you for the PR. I like it
I found a possible bug and other 2 minor changes. I did reviews explaining it

queueBytesOut: Long,
bytesOutPerSecond: Long,
totalBytesOut: Long,
totalBytesIn: Long,

@pedroSG94 pedroSG94 Aug 3, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

totalBytesIn is always 0, Since it is the bytes read from server and it is not relevant for the case I recommend remove it directly

package com.pedro.common

enum class Throughput {
Unknown,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Please, use UPPER_SNAKE_CASE to follow the library convention

}
if (countQueuedBytesGrowing == measureInterval - 1) {
throughput = Throughput.Insufficient
} else if (countQueuedBytesGrowing == 0) {

@pedroSG94 pedroSG94 Aug 3, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

You have a bug here. This library has a max size in the queue but HaishinKit increase the queue infinite, so if you fill the queue this value is 0 because can't increase if the limit is reached, but the queue is saturated so you should return Insufficient. I recommend you check queue congestion using hasCongestion method and set the result in collect method to avoid this side case. You can use a percent that suit to you (for example 95%).

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.

Good catch! 95% seems reasonable as a default, but do you think it makes sense to expose a way for callers to set this and allow them to tune this? We could add a mutable property to BaseSender for this.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

That is an option but maybe to reduce the complexity and avoid create other property we can just send the congestion percent inside StreamingStatsReport and let the user decide what to do depend of the percent when Insufficient is received. This also avoid possible bugs if the user set wrongs values like 0 (queue empty, you will receive Insufficient always) or values not in a range of 0 to 100.

Anyway, you can do as your preference if you make sure that the value set is in a range of 0 to 100.

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.

I like the idea of exposing the percent as part of the StreamingStatsReport that is much cleaner.

@bratta-dk
bratta-dk requested a review from pedroSG94 August 3, 2026 15:47
@pedroSG94
pedroSG94 merged commit 0c3ce57 into pedroSG94:master Aug 4, 2026
@pedroSG94

pedroSG94 commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Hello,

PR merged, I did a commit to fix a possible bug that already exists in my side and could affect this case.
Also, I removed onNewBitrate method from the rotation demo app since with the new method is enough. Maybe we can mark onNewBitrate as deprecated with plans of remove it in the future.
You can check changes here if you want:
#2172

@bratta-dk
bratta-dk deleted the feature/streaming-stats branch August 4, 2026 11:23
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