feat(cmcdv2): implement rtp, lb, dl, pb, tpb, ttfb and ttlb#7895
feat(cmcdv2): implement rtp, lb, dl, pb, tpb, ttfb and ttlb#7895cotid-qualabs wants to merge 12 commits into
Conversation
littlespex
left a comment
There was a problem hiding this comment.
All of these changes look good for request/response based reports, but event based reports would not get these new values, or any values set in applyFragmentData for that matter. Ideally these fields would be set via reporter.update so that the values would persist for all reports. However, this would be a much larger change because the persistent values would need to be properly flagged with object type tokens. For now, I think this work leaves the feature in a better place than it was, so we can merge it.
I've been playing around with how we could workaround this and enable these keys in event mode, and here's an implementation I've been drafting for this PR: Persists those keys in the reporter's shared state via Each key is updated from the hls.js event that provides the most accurate context for it:
The behavior of request reports is unchanged — Known limitations (pre-existing or inherent to the approach):
|
| private onFragChanged(event: Events.FRAG_CHANGED, data: FragChangedData) { | ||
| const level = this.hls.levels[data.frag.level]; | ||
| if (level) { | ||
| this.playheadLevel = level; | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
You should use LEVEL_SWITCHED if all you want is an update when the level index changes. FRAG_CHANGED is only triggered for the main variant playlist and you aren't using any part of it.
hls.js/src/controller/stream-controller.ts
Lines 440 to 450 in c660ce3
If you need an enhancement like previous level/frag or Level (object), let me know so that we can make that happen prior to .0 release.
| data.br = [level.bitrate / 1000]; | ||
| const bitrateKbps = level.bitrate / 1000; | ||
| data.br = [bitrateKbps]; | ||
| const rtpSafetyFactor = this.hls.config.cmcd?.rtpSafetyFactor ?? 5; |
There was a problem hiding this comment.
Use const { cmcd } = this.config; as in other parts of this module. (avoid referencing this.hls when possible)
| private getLowestBandwidth(fragment: Fragment | MediaFragment) { | ||
| let bitrate: number = Infinity; | ||
| let levels; | ||
| const hls = this.hls; | ||
|
|
||
| if (fragment.type === 'audio') { | ||
| levels = hls.audioTracks; | ||
| } else { | ||
| const max = hls.maxAutoLevel; | ||
| const len = max > -1 ? max + 1 : hls.levels.length; | ||
| levels = hls.levels.slice(0, len); | ||
| } | ||
|
|
||
| levels.forEach((level) => { | ||
| if (level.bitrate < bitrate) { | ||
| bitrate = level.bitrate; | ||
| } | ||
| }); | ||
|
|
||
| return bitrate < Infinity ? bitrate : NaN; | ||
| } |
There was a problem hiding this comment.
Audio segments do not have bitrate. HLS variant ("level") bitrates are the combined bitrate of audio and video.
There was a problem hiding this comment.
Same goes for getTopBandwidth but it looks like I let that slip.
There was a problem hiding this comment.
If you wanted to use level.bitrate to calculate video bitrate, you'd need to subtract an estimate of the audio bitrate. audio bitrate can be guessed, but not measured until audio fragments are loaded.
| const tb = this.getTopBandwidth(frag) / 1000; | ||
| if (Number.isFinite(tb)) { | ||
| data.tb = [tb]; | ||
| } | ||
|
|
||
| const lb = this.getLowestBandwidth(frag) / 1000; | ||
| if (Number.isFinite(lb)) { | ||
| data.lb = [lb]; | ||
| } |
There was a problem hiding this comment.
audio tracks don't have a bitrate and the main playlist (variant or "level") bitrates include audio and video combined.
There was a problem hiding this comment.
This is fine for muxed content (ot=MUXED). In other cases, tb and lb will be incorrect for video and not reported for audio.
| // Single level at 1000 bps = 1 kbps → lb=(1) | ||
| expectField(url, `lb%3D%281%29`); |
There was a problem hiding this comment.
This only covers variants without audio alternates.
| const { onSuccess } = callbacks; | ||
| this.loader.load(context, config, { | ||
| ...callbacks, | ||
| onSuccess: (response, stats, ctx, networkDetails) => { | ||
| onSuccess(response, stats, ctx, networkDetails); | ||
| recordResponse(context.url, response, stats); | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Doesn't the response mode need to be enabled? Can we not do this when the response event is not registered to be reported?
Co-authored-by: Rob Walch <robwalch@users.noreply.github.com>
|
All comments have been addressed 😄. Let me know if there's anything else we can improve. We can also discuss the feasibility of enabling these keys for event reports on this PR |
|
Following up on the known limitation around event-mode reports, I've been thinking about a scoped approach that could resolve it without the larger per-object-type refactor. The idea is to gate all For non-muxed streams (alternate audio renditions), we'd skip the update entirely rather than report without Wanted to float this before going further, does this direction seem good to you? |
|
We still wouldn't want to include |
This PR will...
Complete the implementation of several CMCD v2 request and response keys in CMCDController that were previously missing.
rtp(Requested Throughput): segment bitrate ×rtpSafetyFactor(default 5), rounded to nearest 100 kbpslb(Lowest Bitrate): lowest bitrate within the currentmaxAutoLevelrangepb(Playhead Bitrate): bitrate of the level currently at the playhead, tracked viaFRAG_CHANGEDtpb(Top Playhead Bitrate): bitrate of the highest level allowed bymaxAutoLeveldl(Deadline): buffer length divided by playback rate, rounded to nearest 100 msttfb/ttlb: derived from fragment loader response timing by wrapping theonSuccesscallback to callreporter.recordResponseReceived()Adds a new config option
rtpSafetyFactor(default:5) to let operators tune the headroom multiplier used forrtp.Why is this Pull Request needed?
These keys are part of the CMCD v2 spec and were previously missing. Without them, CDN servers cannot use CMCD v2 signals to make informed caching and bitrate decisions.
Are there any points in the code the reviewer needs to double check?
playheadLeveltracking relies onFRAG_CHANGED— verify this is the right event to determine which level is currently at the playhead vs. being buffered ahead.tpbuseshls.maxAutoLevel, which may return-1when unconstrained; the code guards this but worth a second look.onSuccesswrapper in the fragment loader spreadscallbacks— verify there are no edge cases where this wrapping could affect existing loader error handling.Resolves issues:
#6090
Checklist