Skip to content

feat(core): preview health names the media element that held play - #7159

Merged
miguel-heygen merged 1 commit into
mainfrom
dperf/preview-health-held-by
Nov 8, 2026
Merged

miguel-heygen merged 1 commit into
mainfrom
dperf/preview-health-held-by

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

What

A preview-health report now says what held play when the film waited: heldBy gives the media element's kind (audio or video), its readyState and networkState, and the ms since that element's fetch started. It is null when nothing held play in the window. parsePlaybackHealth in studio carries it through.

Why

waitingShare says how long play waited but not on what. When a film with no video sits at 0 fps waiting, the share alone cannot tell a slow audio fetch from a clip that never started loading, or an audio hold from a video one. Choppy-playback reports built on this health can then name the cause.

Related work

None: a self-contained addition to the preview-health message.

How

  • init.ts: the transport tick already picked "waiting" as the first element the hold is for (bufferingFor[0]), else a playing video below HAVE_FUTURE_DATA. It now keeps that element and passes it as heldBy, so waiting is unchanged.
  • playbackHealth.ts: watchMediaFetches(document) notes each element's loadstart (capture phase, since media events do not bubble), installed and cleaned up beside watchAudioLoadErrors. heldByMedia turns the element into numbers only. An element that started loading before the runtime did falls back to its resource-timing entry, and is null when it has none. The report carries the last holder in the window.
  • types.ts: PlaybackHeldBy, exported from @hyperframes/core and used by studio's PlaybackHealth. A malformed heldBy, including a fetchMs that is neither a number nor null, parses to undefined, like the other optional fields.

No visible change

No UI: the studio change is the health parser only (no .tsx, .css or .html).

Test plan

  • Unit tests added/updated
  • Manual testing performed
  • Documentation updated (if applicable)
  • Comments follow CONTRIBUTING.md "Comments"

On a Linux box: core playbackHealth.test.ts and init.buffering.test.ts (26 tests), studio previewQualitySlice.test.ts and previewMessageRouter.test.ts (13), tsc --noEmit in core and studio, oxfmt and oxlint on the changed files: all pass.

Each change is caught when broken: with heldBy: null at the tick, init.buffering's "a film held for data reads as choppy" fails. With the report's heldBy dropped, or the loadstart stamp removed, playbackHealth fails. With the parse line removed, previewQualitySlice fails.

Not exercised: a real browser playing a slow-loading file. The tests use jsdom and stubbed ready states.

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Requesting changes at 6ae304f2. The code works, but two required checks fail on this head.

Blockers

  1. Comments ratchet fails (run 41833097818). The comment share went up in three files: playbackHealth.ts (+3 lines), types.ts (+3), previewQualitySlice.ts (+2). The body ticks "Comments follow CONTRIBUTING.md", and that box is not true until this check passes. Cut the new doc comments or move what they say into names or types. For example, PlaybackHeldBy.fetchMs's "null = fetch not seen to start" could be a named type. The heldBy field docs in PlaybackHealthInput, RuntimePreviewHealthMessage and PlaybackHealth repeat each other.
  2. PR captures fails (run 41833097688). The PR touches packages/studio, and the body has no ## Before/## After. The studio source change is 13 lines with no .tsx, .css or .html, so a ## No visible change section is enough.

Non-blocking

  • parseHeldBy: when fetchMs is present but not a number (for example a string), it turns into null, which means "fetch not seen to start". The body says a malformed heldBy parses to undefined. Return undefined when fetchMs is neither a number nor null, so bad input doesn't pass as a real reading.
  • watchMediaFetches only sees loadstart events that fire after runtime init, and only in the main document (media events aren't composed, so it misses shadow roots). Elements that started loading earlier report fetchMs: null. That's acceptable as documented, but a choppy report on first load will usually show null for exactly the case this PR is meant to diagnose. Consider seeding from performance.getEntriesByName(el.currentSrc) or noting the limit.

Checked

  • bufferingFor is HTMLMediaElement[], so bufferingFor[0] ?? find(...) gives the same waiting value as the old length > 0 || some(...).
  • heldBy is the last non-null sample in the window. It resets on restart, and the test covers both cases.
  • Not a fix PR, so no fails without the fix: line is needed.

@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 106 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (8)

Unstable (1)

  • seqplainkeys-none-px-r0-root-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo false, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true

@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Fixed in 692f0e3.

  • Comments ratchet: the new doc comments are gone from playbackHealth.ts, types.ts and previewQualitySlice.ts, so the diff adds no comment lines. The one fact they carried is now a named type, fetchMs: number | FetchNotSeenToStart.
  • Captures: the body has a ## No visible change section. The studio change is the health parser only.
  • parseHeldBy: a fetchMs that is neither a number nor null now parses the whole heldBy to undefined, with a test.
  • Early fetches: heldByMedia falls back to the element's resource-timing entry (performance.getEntriesByName(currentSrc)) when it never saw a loadstart, so a first-load hold gets a fetch time when the browser recorded one. With neither, it is null, with a test.

Core 26 and studio 13 tests, both typechecks, oxfmt and oxlint pass on a Linux box. Removing the fallback or the fetchMs check each fails its test.

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving at 692f0e3c. All the blockers from the review at 6ae304f2 are fixed:

  • Comments ratchet: the new doc comments are gone, and the "not seen to start" meaning now lives in the FetchNotSeenToStart type. The Comments run is green on this head.
  • Captures: the body has a ## No visible change section, and PR captures is green.
  • parseHeldBy: a fetchMs that is neither a number nor null now parses to undefined. A test covers it (fetchMs: "3500").
  • Early fetches: heldByMedia falls back to the last resource-timing entry for currentSrc, and a test covers it.

The CI, preview-regression, Player perf and Player Safari audio runs are green on this head. Safari perf, Windows render, regression and CodeQL were still running when I approved.

Non-blocking: the timing fallback is best-effort. It can miss when the resource-timing buffer is full (250 entries by default), and with range requests .at(-1) measures the latest range, not the first byte. Both are fine for a diagnostic field.

@miguel-heygen
miguel-heygen force-pushed the dperf/preview-health-held-by branch from 692f0e3 to 8728883 Compare November 8, 2026 02:08

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving at 87288832. This is a rebase of the head I approved at 692f0e3c, and the patch is unchanged.

What I ran on this head

  • Patch content: the diff from the merge base is line-for-line identical to 692f0e3c's. The PR is one commit on top of main at 26956ea1.
  • Main changes under the patch: main has moved under init.ts, playbackHealth.ts and types.ts since the first review (#7373, #7378, #7379 and others). The tick block still reads the same: bufferingFor is still HTMLMediaElement[], and holder still feeds waiting and heldBy as before.
  • Tests: core playbackHealth.test.ts and init.buffering.test.ts pass (26). Studio previewQualitySlice.test.ts and previewMessageRouter.test.ts pass (13).
  • CI: CI, CodeQL, Comments, PR captures, preview-regression, Player perf and Player Safari audio are green on this head. Player Safari perf, regression and Windows render were still running when I approved.

@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 8, 2026
Merged via the queue into main with commit fd05a38 Nov 8, 2026
97 of 98 checks passed
@miguel-heygen
miguel-heygen deleted the dperf/preview-health-held-by branch November 8, 2026 02:44
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.

1 participant