Skip to content

perf(core): Safari plays a long source's audio from its element, not a whole-file decode - #7447

Draft
miguel-heygen wants to merge 3 commits into
mainfrom
perf/safari-long-source-audio
Draft

miguel-heygen wants to merge 3 commits into
mainfrom
perf/safari-long-source-audio

Conversation

@miguel-heygen

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

Copy link
Copy Markdown
Collaborator

What

Draft, not asked for review: on Safari, audio of a source longer than 120 s plays from its media element instead of a whole-file decoded buffer. Built on the in-point fragment, fetch-ahead and eight slots of #7436, and stacked on it.

Why

The decode route fetches the whole file before it can start, which for a 68 MB source takes the link from the picture for most of the first minute on a slow connection. On a real iPhone (page over USB at 8 MB/s, 68 MB film, first 25 s, 3 interleaved plays) with the changes of #7436 the playhead stood still 8.3 to 11.9 s; adding this brought it to 0.8 to 8.9 s (and 0.25 to 3.4 s in two other runs), 275 to 338 MB, with frozen picture 7 to 31% against 28 to 35%. This limit alone, without #7436, did nothing: it needs the fragment and fetch-ahead.

What a long source loses

Safari restarts a media element's audio 150 to 400 ms late; a decoded buffer starts on the audio clock, on time (the comment on decodesFirst in init.ts). Without the buffer, the audio of a source over the limit is played by its element, so that late start returns for it. The decode route also only handles a clip at normal rate, so nothing else about rate changes. Short sources and other browsers are unchanged.

Why 120 s

An unmeasured threshold, chosen so a source of about 25 MB still decodes (the 5.6 MB and 12 MB test films do) and the 69 MB one does not. The right number depends on what the link can fetch before the first clip needs sound, which the page does not know; it is the part that needs a product decision, as is the trade itself.

Where the remaining bytes go

With this change the phone moves 275 to 338 MB against 580 for main. About 25 to 35 MB are reads from the head of the file; the rest, about 250 to 300 MB, are ranged reads of the clips' own spans and the read-ahead WebKit does past each in-point, 118 to 135 requests against 33 for a player that keeps one or two elements alive. Cutting those needs fewer live elements (an element pool), which this branch does not attempt.

Test plan

  • Unit tests added/updated: none for the limit yet; the existing 600 s and 650 s decode-first tests in init.test.ts assert the old behaviour and are left alone until the decision is made.
  • Manual testing performed: real iPhone runs above.
  • Documentation updated (not applicable)
  • Comments follow CONTRIBUTING.md "Comments"

@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

Fallow audit report

Quality gate failed

Found 3 findings: Dead code 1, Duplication 2.

Dead code (1)
Severity Rule Location Description
major fallow/unused-export packages/core/src/runtime/preloadMedia.ts:105 Export 'openAtInPoint' is never imported by other modules
Duplication (2)
Severity Rule Location Description
minor fallow/code-duplication packages/core/src/runtime/mediaProxy.ts:763 Code clone dup:7886fd80 (19 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/mediaProxy.ts:794 Code clone dup:7886fd80 (19 lines, 2 instances)

Generated by fallow.

Gate outcomes: failed audit-verdict.

@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 101 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)

This branch has not been deployed

No deployments
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