Skip to content

feat: Add ABR startup quality attributes#1315

Merged
luwes merged 12 commits intomuxinc:mainfrom
jondahl:fix/abr-startup-config
Apr 28, 2026
Merged

feat: Add ABR startup quality attributes#1315
luwes merged 12 commits intomuxinc:mainfrom
jondahl:fix/abr-startup-config

Conversation

@jondahl
Copy link
Copy Markdown
Contributor

@jondahl jondahl commented Apr 17, 2026

Summary

Adds three new opt-in HTML attributes to improve ABR startup quality across all Mux media elements:

  • initial-bandwidth-estimate-kbps — Seeds the HLS.js EWMA bandwidth estimator instead of relying on TCP slow start, which poisons the first measurement and causes segments 2-5 to drop to low renditions.
  • initial-estimate-segments — Number of segments that use the initial estimate before switching to measured bandwidth. Setting to 3 means the first three segments use the initial estimate, segment four is the first to use real data.
  • min-preload-segments — Buffers N segments before allowing playback to start (autoplay or user-initiated). Gives ABR real bandwidth data and builds buffer runway before playback begins.

No defaults changed. All three attributes are opt-in and must be explicitly set.

Packages updated

Package Changes
playback-core Core implementation: HLS.js config seeding, estimator reset logic, autoplay gating with play/pause interception
mux-video Attribute constants, getter/setter properties
mux-audio Attribute constants, getter/setter properties
mux-player Getter/setters, getProps(), template forwarding to <mux-video>
mux-video-react PropTypes entries
mux-audio-react PropTypes entries
mux-player-react TypeScript type definitions in MuxMediaPropTypes
mux-player-astro TypeScript type definitions in MuxPlayerProps

Key implementation details

  • initialBandwidthEstimateKbps sets abrEwmaDefaultEstimate in HLS.js config via conditional spread (no Record<string, any> type widening)
  • initialEstimateSegments resets the EWMA estimator after each of the first N-1 segments via FRAG_BUFFERED events
  • minPreloadSegments gates playback in setupAutoplay() by intercepting play events, pausing immediately, and resuming once enough segments buffer. Handles both autoplay and user-initiated play, with proper cleanup of event listeners

Test Coverage

  • 6 new tests for initial-bandwidth-estimate-kbps (3 tests) and initial-estimate-segments (3 tests)
  • Tests use hls.trigger() with stats: { aborted: true }, elementaryStreams: {} stubs to avoid HLS.js internal handler crashes
  • All 83 playback-core tests pass

Reference docs updated

  • packages/mux-video/README.md
  • packages/mux-player/REFERENCE.md
  • packages/mux-player-react/REFERENCE.md
  • packages/mux-player-astro/REFERENCE.md

Test plan

  • All 83 playback-core tests pass (0 failures)
  • TypeScript compiles clean across playback-core, mux-video, mux-audio, mux-player, mux-player-react
  • Manual testing with abr-startup-test.html harness (included in examples/)

🤖 Generated with Claude Code


Note

Medium Risk
Touches core playback/ABR behavior (HLS.js config and fragment buffering event handling) and manipulates playbackRate during startup, which could affect playback edge-cases, though changes are opt-in and covered by new tests.

Overview
Adds three new opt-in ABR startup controls across Mux media elements: initial-bandwidth-estimate-kbps, initial-estimate-segments, and min-preload-segments.

In @mux/playback-core, HLS.js initialization can now seed abrEwmaDefaultEstimate from initialBandwidthEstimateKbps, reset the EWMA estimator for the first N buffered segments (initialEstimateSegments), and delay visible playback until N main segments buffer by clamping mediaEl.playbackRate to 0 (minPreloadSegments).

Plumbs these attributes through mux-video, mux-audio, and mux-player (props extraction + template forwarding), updates React/Astro typings/docs, adds playback-core tests for estimate seeding/reset behavior, and includes a new abr-startup-test.html manual test harness.

Reviewed by Cursor Bugbot for commit 960f52f. Bugbot is set up for automated code reviews on this repo. Configure here.

jondahl and others added 4 commits April 17, 2026 11:26
Add three opt-in attributes to improve ABR startup quality by
counteracting TCP slow start's effect on initial bandwidth estimates:

- initial-bandwidth-estimate-kbps: override HLS.js default estimate
- initial-estimate-segments: keep using initial estimate for first N segments
- min-preload-segments: buffer N segments before starting playback

All three require explicit opt-in, no defaults changed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When minPreloadSegments is set and autoplay="muted", if the user
manually plays before segments buffer, the deferred autoplay would
still fire and force-mute the session. Check hasPlayed before
triggering deferred autoplay.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When minPreloadSegments is set, intercept play events and pause
playback until the buffer threshold is met. Resumes automatically
once enough segments have buffered. Also guards against setting
pendingAutoplay when autoplay is falsy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jondahl jondahl requested a review from a team as a code owner April 17, 2026 19:01
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 17, 2026

@jondahl is attempting to deploy a commit to the Mux Team on Vercel.

A member of the Team first needs to authorize it.

@snyk-io
Copy link
Copy Markdown

snyk-io Bot commented Apr 17, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Visual test page for verifying initial-bandwidth-estimate-kbps,
initial-estimate-segments, and min-preload-segments behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread packages/playback-core/src/autoplay.ts Outdated
Comment thread examples/vanilla-ts-esm/public/abr-startup-test.html
Adds initial-bandwidth-estimate-kbps, initial-estimate-segments, and
min-preload-segments to all remaining media elements and wrappers:
- mux-audio: attribute constants + getter/setter properties
- mux-video-react, mux-audio-react: PropTypes entries
- mux-player-react, mux-player-astro: TypeScript type definitions
- Reference docs for mux-video, mux-player, mux-player-react, mux-player-astro

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 17, 2026

Codecov Report

❌ Patch coverage is 48.25175% with 74 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.04%. Comparing base (9eaef73) to head (a39eb51).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
packages/playback-core/src/autoplay.ts 31.03% 40 Missing ⚠️
packages/mux-audio/src/index.ts 41.17% 30 Missing ⚠️
packages/mux-player/src/template.ts 20.00% 4 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1315      +/-   ##
==========================================
- Coverage   68.09%   68.04%   -0.06%     
==========================================
  Files          47       47              
  Lines        7645     7864     +219     
  Branches      536      565      +29     
==========================================
+ Hits         5206     5351     +145     
- Misses       2435     2509      +74     
  Partials        4        4              
Files with missing lines Coverage Δ
packages/playback-core/src/index.ts 57.51% <100.00%> (+1.63%) ⬆️
packages/playback-core/src/types.ts 99.58% <100.00%> (+<0.01%) ⬆️
packages/mux-player/src/template.ts 84.93% <20.00%> (-2.57%) ⬇️
packages/mux-audio/src/index.ts 61.17% <41.17%> (-1.76%) ⬇️
packages/playback-core/src/autoplay.ts 72.00% <31.03%> (-14.55%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread packages/playback-core/src/autoplay.ts Outdated
Copy link
Copy Markdown
Contributor

@luwes luwes left a comment

Choose a reason for hiding this comment

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

Questioning minPreloadSegments

luwes added 2 commits April 28, 2026 14:02
Register the preload-gate `play` and `pause` listeners via
`addEventListenerWithTeardown` so they are removed when the media
element fires `teardown` (e.g. on source change). Previously they were
attached with raw `addEventListener` and only removed inside the
`FRAG_BUFFERED` callback once the preload threshold was reached. If the
player tore down mid-preload, the listeners leaked, and the `onPlay`
closure captured `preloadReady = false` permanently, pausing every
subsequent play attempt for any future source on the same media element.

Made-with: Cursor
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
elements-demo-vanilla Ready Ready Preview, Comment Apr 28, 2026 11:24pm

Request Review

Comment thread packages/playback-core/src/autoplay.ts Outdated
Copy link
Copy Markdown
Contributor

@luwes luwes left a comment

Choose a reason for hiding this comment

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

LGTM! I do think the API needs some more rethinking to make it more developer friendly. I'd like to help with that in Video.js.

@luwes
Copy link
Copy Markdown
Contributor

luwes commented Apr 28, 2026

actually, we need to trigger a manual waiting event for the minPreloadSegments. looking into it

Replace play/pause interception with playbackRate=0 clamping while
preloading. Lets autoplay and user-initiated play run unchanged — the
element just doesn't advance frames — and avoids racing on async
play/pause event ordering.

Restores autoplay.ts to its pre-PR state and isolates the new behavior
in setupMinPreload, called as a sibling step in index.ts. Captures
user-driven ratechange events during preload so the user's intended
playback rate is preserved when the buffer threshold is reached.

Made-with: Cursor
Comment thread packages/playback-core/src/min-preload.ts Outdated
Comment thread packages/playback-core/src/min-preload.ts Outdated
Comment thread packages/playback-core/src/min-preload.ts
luwes added 2 commits April 28, 2026 16:00
The synchronous flag never blocked anything because `ratechange` is
queued as a media element task and dispatched async, so the flag was
always cleared by the time the handler ran. The actual safeguard was
the `playbackRate !== 0` check filtering our zero-writes, plus removing
the listener before the restoring (non-zero) write.

Drop the dead flag and document the real invariant so future maintainers
don't add a non-zero internal write while the listener is attached.

Made-with: Cursor
If teardown fires before the min-preload threshold is reached, the
ratechange listener is removed but playbackRate stays at 0, leaving
the media element frozen on any subsequent reuse without
minPreloadSegments. Add a teardown listener that restores the captured
userPlaybackRate when not yet restored.

Made-with: Cursor
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 960f52f. Configure here.

mediaEl.removeEventListener('ratechange', onRateChange);
mediaEl.playbackRate = userPlaybackRate;
}
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Video stuck if segments fewer than minPreloadSegments threshold

Medium Severity

If the total number of main segments in a video is less than minPreloadSegments, the FRAG_BUFFERED threshold is never reached and playbackRate remains pinned at 0 indefinitely. The video appears to be "playing" (play button toggles to pause) but never visually advances. Only a teardown (source change / destroy) would restore playbackRate. There's no fallback for when all segments have been buffered but the count is still below the threshold — e.g. a short video with 5 segments and min-preload-segments="10" would be permanently stuck.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 960f52f. Configure here.

(hls as any).abrController.resetEstimator(hls.config.abrEwmaDefaultEstimate);
}
});
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

setupInitialEstimate never removes its FRAG_BUFFERED listener

Low Severity

setupInitialEstimate registers an hls.on(FRAG_BUFFERED) listener that never gets removed — neither after the initial estimate period ends, nor on teardown. In contrast, setupMinPreload carefully detaches its FRAG_BUFFERED handler both on completion and via a teardown listener. The listener here continues to fire for every main fragment for the entire playback session, needlessly incrementing mainSegmentsBuffered and evaluating the condition.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 960f52f. Configure here.

@luwes luwes merged commit 3cdf0bc into muxinc:main Apr 28, 2026
9 of 13 checks passed
@github-actions github-actions Bot mentioned this pull request Apr 28, 2026
luwes pushed a commit that referenced this pull request Apr 28, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>@mux/playback-core: 0.35.0</summary>

##
[0.35.0](https://github.com/muxinc/elements/compare/@mux/playback-core@0.34.1...@mux/playback-core@0.35.0)
(2026-04-28)


### Features

* Add ABR startup quality attributes
([#1315](#1315))
([3cdf0bc](3cdf0bc))
</details>

<details><summary>@mux/mux-player: 3.13.0</summary>

##
[3.13.0](https://github.com/muxinc/elements/compare/@mux/mux-player@3.12.0...@mux/mux-player@3.13.0)
(2026-04-28)


### Features

* Add ABR startup quality attributes
([#1315](#1315))
([3cdf0bc](3cdf0bc))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @mux/mux-video bumped from 0.30.7 to 0.31.0
    * @mux/playback-core bumped from 0.34.1 to 0.35.0
</details>

<details><summary>@mux/mux-player-astro: 3.13.0</summary>

##
[3.13.0](https://github.com/muxinc/elements/compare/@mux/mux-player-astro@3.12.0...@mux/mux-player-astro@3.13.0)
(2026-04-28)


### Features

* Add ABR startup quality attributes
([#1315](#1315))
([3cdf0bc](3cdf0bc))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @mux/mux-player bumped from 3.12.0 to 3.13.0
    * @mux/playback-core bumped from 0.34.1 to 0.35.0
</details>

<details><summary>@mux/mux-player-react: 3.13.0</summary>

##
[3.13.0](https://github.com/muxinc/elements/compare/@mux/mux-player-react@3.12.0...@mux/mux-player-react@3.13.0)
(2026-04-28)


### Features

* Add ABR startup quality attributes
([#1315](#1315))
([3cdf0bc](3cdf0bc))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @mux/mux-player bumped from 3.12.0 to 3.13.0
    * @mux/playback-core bumped from 0.34.1 to 0.35.0
</details>

<details><summary>@mux/mux-audio: 0.16.0</summary>

##
[0.16.0](https://github.com/muxinc/elements/compare/@mux/mux-audio@0.15.26...@mux/mux-audio@0.16.0)
(2026-04-28)


### Features

* Add ABR startup quality attributes
([#1315](#1315))
([3cdf0bc](3cdf0bc))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @mux/playback-core bumped from 0.34.1 to 0.35.0
</details>

<details><summary>@mux/mux-audio-react: 0.16.0</summary>

##
[0.16.0](https://github.com/muxinc/elements/compare/@mux/mux-audio-react@0.15.26...@mux/mux-audio-react@0.16.0)
(2026-04-28)


### Features

* Add ABR startup quality attributes
([#1315](#1315))
([3cdf0bc](3cdf0bc))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @mux/playback-core bumped from 0.34.1 to 0.35.0
</details>

<details><summary>@mux/mux-video: 0.31.0</summary>

##
[0.31.0](https://github.com/muxinc/elements/compare/@mux/mux-video@0.30.7...@mux/mux-video@0.31.0)
(2026-04-28)


### Features

* Add ABR startup quality attributes
([#1315](#1315))
([3cdf0bc](3cdf0bc))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @mux/playback-core bumped from 0.34.1 to 0.35.0
</details>

<details><summary>@mux/mux-video-react: 0.31.0</summary>

##
[0.31.0](https://github.com/muxinc/elements/compare/@mux/mux-video-react@0.30.7...@mux/mux-video-react@0.31.0)
(2026-04-28)


### Features

* Add ABR startup quality attributes
([#1315](#1315))
([3cdf0bc](3cdf0bc))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @mux/playback-core bumped from 0.34.1 to 0.35.0
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: this PR is primarily version/changelog/lockfile updates with
dependency bumps and no direct source code changes in the diff.
> 
> **Overview**
> Publishes a coordinated release across `@mux/playback-core`,
`@mux/mux-player` (+ React/Astro wrappers), and
`@mux/mux-video`/`@mux/mux-audio` (+ React wrappers), adding changelog
entries for **ABR startup quality attributes**.
> 
> Updates package versions and workspace dependency pins (notably
`@mux/playback-core` to `0.35.0` and `@mux/mux-video` to `0.31.0`),
along with the release manifest and `package-lock.json` to reflect the
new release set.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
a854f91. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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