I have a web project in which the client streams an audio file from the server and plays it using an <audio>
element.
The challenging thing here is that the stream itself is lazy-loaded by the server, so only parts of the stream are known in the beginning (but the total size of the stream is known).
My previous approach was to implement range requests and stall the connection until new data arrives. This has been working perfectly fine for over a year. But now, when using recent Chrome versions, multiple requests are made in the beginning to ensure that the client supports range requests, taken from https://www.zeng.dev/post/2023-http-range-and-play-mp4-in-browser/ and I was able to reproduce that behaviour:
1. send request, read the onflight reponse header, close connection when range support detected
Chrome Server
+------------------------+ ------------> +-------------------------------------+
| GET /a.mp4 HTTP/1.1 | close conn when | HTTP/1.1 200 OK |
| Host: example.com | <----x------- | Accept-Ranges: bytes |
+------------------------+ range support detected | Content-Length: 828908177 |
| ... |
| (body: some first bytes of a.mp4) |
+-------------------------------------+
2. send trivial range request 1, fetch head parts, verify server's support,
Chrome Server
+------------------------+ ------------> +---------------------------------------------+
| GET /a.mp4 HTTP/1.1 | close conn when | HTTP/1.1 206 Partial Content |
| Host: example.com | <----x------- | Accept-Ranges: bytes |
| Range: [bytes=0-] | verify success | Content-Range: bytes 0-828908176/828908177 |
+------------------------+ | Content-Length: 828908177 |
| ... |
| (body: some first bytes of a.mp4) |
+---------------------------------------------+
3. send trivial range request 2, fetch tail parts, verify server's support
Chrome Server
+--------------------------+ +-----------------------------------------------------+
| GET /a.mp4 HTTP/1.1 | | HTTP/1.1 206 Partial Content |
| Host: example.com | -----------> | Accept-Ranges: bytes |
| Range: bytes=828604416- | | Content-Range: bytes 828604416-828908176/828908177 |
+--------------------------+ | Content-Length: 303761 |
| ... |
| (body: some end bytes of a.mp4) |
+-----------------------------------------------------+
4. sending range request for remaining bytes
Chrome Server
+--------------------------+ +-----------------------------------------------------+
| GET /a.mp4 HTTP/1.1 | ------------> | HTTP/1.1 206 Partial Content |
| Host: example.com | <------------ | Accept-Ranges: bytes |
| Range: bytes=720896- | | Content-Range: bytes 720896-828908176/828908177 |
+--------------------------+ | Content-Length: 828187281 |
| ... |
| (body: rest bytes of a.mp4) |
+-----------------------------------------------------+
More details are explained in the article. What causes the issue is step 3 (Content-Range: bytes 828604416-828908176/828908177
), as the server waits for the tail of the audio data and then the audio starts to play, resulting in minutes of delay.
Note: The author says Firefox also uses tail requests (but no head requests), but mine does vice versa - thus, my application works fine in Firefox.
What I tried:
- Disable range requests (works, but skipping inside known parts of the stream is impossible then)
- Send
416 Range Not Satisfiable
if tail is requested (breaks playback completely) - Detect tail requests and send zeros instead (seems like it does not alter the audio data used for playback as it still comes from the primary request) but I feel guilty for this
Questions that arise:
- Why are these requests made at all? Doesn't the server indicate support for range requests by the
Accept-Ranges: bytes
header? - Can I reliably tell Chrome not to do this? Are there any better workarounds for this apart from using a custom player?
Any help would be greatly appreciated.