8

We're writing a Spring service that makes an HTTP endpoint available through which a video (or audio) file from an Amazon S3 store can be streamed. The basic idea is that you can type in an url in the Google Chrome address bar, and the service will fetch the file from S3 and stream it, in such a way that the user can start watching immediately without having to wait for a download to complete, and that the user can click on a random spot in the video's progress bar and immediately start watching the video from that spot.

The way I understand this should work in theory, is that Chrome starts downloading the file. The service responds with HTTP 200 and includes an Accept-Ranges: bytes and a Content-Length: filesize header. The filesize is known, because we can query that as metadata from S3 without fetching the entire file. Including these headers causes the browser to cancel the download, and request the file again with a Range: bytes=0-whatever header (where whatever is some chunk size that Chrome decides). The service then responds with HTTP 206 (Partial content) and the requested byte range, which we can determine easily because S3 supports the same range protocol. Chrome then requests successive chunks from the service, until the stream ends.

On the Spring side, we're sending the data out in a ResponseEntity<InputStreamResource> (as per this SO answer).

However, we observe in practice that while Chrome's cancels its first request after a few hundred bytes. However, it sends a second request with a Range: bytes=0- header, effectively asking for the entire file. The server responds with an HTTP 206. As a result, is has only downloaded a few hundred bytes of video, and the video obviously doesn't start playing.

Interestingly, in Firefox it all works properly. Unfortunately, our app needs to support Chrome. Are we missing some part of the protocol?

jqno
  • 15,133
  • 7
  • 57
  • 84
  • 3
    Are you using any media container format for playing the video such as MPEG-DASH or HLS or directly sending a single large MP4 or WebM file? – vvg Aug 28 '20 at 21:50
  • 3
    Also, what Content-Type are you setting on the HTTP 206 Partial Content response? It needs to be: HTTP/1.1 206 Partial Content Content-Type: multipart/byteranges; boundary= Content-Length: – vvg Aug 28 '20 at 21:59
  • 3
    See this Chrome support thread discussion. They seem to be talking a similar issue like what you have. Unfortunately, they are pinning it on a Chrome issue. https://support.google.com/chrome/thread/25510119?hl=en – vvg Aug 28 '20 at 22:03
  • 1
    We're sending MP4 directly. The content-type seems like a possible explanation and thanks for the URL. We've got a few things to try now. – jhkuperus Aug 31 '20 at 04:21
  • 1
    Thanks for the suggestions. They didn't provide us with an answer directly, but they did inspire to look further and eventually to find the actual problem: an off-by-one error in the `Content-Range` header. – jqno Sep 01 '20 at 14:29

1 Answers1

2

It turns out we had an off-by-one error in the Content-Range response header.

The syntax is Content-Range: bytes start-end/total. With a total of 10, if you want to get the entire range, you need to specify bytes 0-9/10, not 0-10/10, which was what we were doing.

Of course with the larger sizes of real files, and the actual ranges of chunks in the middle of such files, this error was a lot harder to notice than in the contrived example in the previous paragraph... ಠ_ಠ

jqno
  • 15,133
  • 7
  • 57
  • 84