Skip to content

File servers stream to EOF for open-ended media ranges; more than ~6 <video> on one file starves the browser's connections (check hangs) #6029

Open

Description

@HuntsDesk

What happens

Every HyperFrames file server answers an open-ended range request (Range: bytes=N-) with the rest of the file. Browsers send that form for <video>/<audio>, read until their media buffer is full, then stop reading but keep the response open. Each media element therefore holds one HTTP/1.1 connection for as long as the page is open.

Chrome allows 6 connections per host. When a composition has more than about six <video> elements on the server's host, the rest of the page's requests wait for a free connection that never comes.

The pattern is common: one long recording split into trimmed segments, one <video> per kept range on the same source file.

Seen with

  • hyperframes check hangs. It stalls at 0% CPU with the default 9 samples on a composition with 58 <video> segments of one 858 MB recording; --samples 5 gets through. Confirmed on 0.8.148 by capping open-ended answers at 4 MB in the bundled serveFileWithRange: the default check then passed. The code is unchanged on main as of v0.8.183.
  • Unbounded memory in a long-open preview tab. A separate local server with the same behaviour grew Safari's WebKit Networking process to about 21 GB over a day. The page reloaded often, and every reload re-requested each segment's whole tail. A 4 MB cap fixed it.

Where

All three handlers resolve bytes=N- to end = size - 1:

  • packages/cli/src/utils/staticProjectServer.ts: serveFileWithRange
  • packages/cli/src/utils/compositionServer.ts: buildRangeResponse
  • packages/studio-server/src/routes/preview.ts: the inline Range branch in the static route

Suggested fix

For audio/video, cap an open-ended range at a fixed piece (4 MB worked here): end = min(size - 1, start + PIECE - 1), returned as a normal 206 with Content-Range: bytes start-end/size. Media elements already request the next range as they play and seek, so playback is unchanged. Bounded start-end and suffix -N requests stay as they are. Each response then finishes and frees its connection.

Repro shape

  1. Make a long video (a few hundred MB is enough).
  2. In a composition, add 8 or more <video> clips with the same src, each with its own data-start, data-duration and data-media-start.
  3. Run npx hyperframes check . with the default sample count.

I'm happy to send a PR covering the three handlers if this is the direction you want.

Activity

  1. pittamallasrinu8-hash commented on Oct 22, 2026

    @pittamallasrinu8-hash
  2. pittamallasrinu8-hash commented on Oct 22, 2026

    @pittamallasrinu8-hash
  3. pittamallasrinu8-hash commented on Oct 22, 2026

    @pittamallasrinu8-hash
  4. added
    triage/readyAccepted scope, current evidence, acceptance criteria, and validation path are recorded.
    on Oct 23, 2026
  5. miguel-heygen commented on Oct 24, 2026

    @miguel-heygen
    Collaborator

    Thanks for the detailed analysis and for opening #6033. I checked the code on current main (0.8.193), and you're right: all three file servers answer Range: bytes=N- with the rest of the file.

    I couldn't reproduce the check hang itself, though. I used a 405 MB 1080p H.264 file (40 Mbit/s) with 60 <video> clips on the same src, each with its own data-media-start, and default check passed in about 10 s on 0.8.193 and in about 40 s on 0.8.148 (macOS). Something in your setup differs from mine. Could you share:

    • your OS and the output of npx hyperframes doctor
    • the source file's codec, container, resolution and bitrate (ffprobe output is enough)
    • whether the <video> elements set preload or anything else beyond src, muted and the data-* timing

    With a fixture that hangs, the fix in #6033 can carry a regression test that fails without it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    triage/readyAccepted scope, current evidence, acceptance criteria, and validation path are recorded.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions