80

I'm trying to setup a very basic html5 page that loads a .mp4 video that is 20MB. It appears that the browser needs to download the entire thing rather than just playing the first part of the video and streaming in the rest.

This post is the closest thing I've found while searching... I tried both Hand Brake and Data Go Round by neither appeared to make a difference:

Any ideas on how to do this or if it's possible?

Here is the code I'm using:

<video controls="controls">
    <source src="/video.mp4" type="video/mp4" />
    Your browser does not support the video tag.
</video>
Community
  • 1
  • 1
longda
  • 9,445
  • 6
  • 44
  • 66

2 Answers2

154
  1. Ensure that the moov (metadata) is before the mdat (audio/video data). This is also called "fast start" or "web optimized". For example, Handbrake has a "Web Optimized" checkbox, and ffmpeg and avconv have the output option -movflags faststart.
  2. Ensure that your web server is reporting the correct Content-Type (video/mp4).
  3. Ensure that your web server is configured to serve byte range requests.
  4. Ensure that your web server is not applying gzip or deflate compression on top of the compression in the mp4 file.

You can check the headers being sent by your web server using curl -I http://yoursite/video.mp4 or using the developer tools in your browser (Chrome, Firefox) (reload the page if it is cached). The HTTP Response Header should include Content-Type: video/mp4 and Accept-Ranges: bytes, and no Content-Encoding:.

mark4o
  • 52,963
  • 16
  • 81
  • 99
  • I haven't found a good answer online... is there an application or easy way to check the moov metadata and its location in the file? – longda Jun 19 '14 at 00:27
  • 2
    @longda: command line utilities that can show the mp4 file structure include L-SMASH `boxdumper`, Atomic Parsley `-T`, and mp4v2 `mp4file --dump`. – mark4o Jun 30 '14 at 11:29
  • 3
    For the record (mainly because I'm an idiot), you call via command line like so: `atomicparsley -T` (the options go at the very end). Thanks again for all the help @mark4o! – longda Jun 30 '14 at 18:52
  • From my testing, I think that `"fast start" or "web optimized"` option could prevent jumping through the file. Even though my server delivers byte ranges, when playing an mp4 with videojs support, and clicking in the end of the progress bar, the player stops working. – Avatar Jul 12 '15 at 06:29
  • 1
    @Matheretter: "fast start" or "web optimized" just means that the `moov` is at the beginning rather than the end, which allows seeking using byte range requests before the whole file is downloaded. If this causes seeking to not work then check for a bug in your code that is handling the byte range requests, which would not be used if the `moov` is at the end (by the time it knew which bytes it needed it would have already downloaded the whole file). I see in your other question that you have written custom php code for this. – mark4o Jul 13 '15 at 05:53
  • atomicparsley has a better output format than mp4file – Sisyphus Feb 25 '16 at 02:51
  • 1
    Do you have a reference for this? – 0xcaff May 31 '16 at 01:26
  • @caffinatedmonkey: Note that #1 and #3 are links already. A reference for the HTTP headers like Content-Type is [RFC 7231](https://tools.ietf.org/html/rfc7231#section-3.1.1.5); is that what you mean? – mark4o Jun 01 '16 at 00:07
  • Yes, I was looking for more of an official spec but I guess there isn't one. – 0xcaff Jun 02 '16 at 01:20
  • Good answer @mark4o ! just wanted to note that this streaming method is called progressive download - in case someone wants to go deeper. The main diff to streaming HLS files is that it does not support variable bit rate and uses HLS files instead of mp4... – Uri Meirav Mar 27 '17 at 07:02
  • @mark4o I've written my webserver and have this problem too. When I request from desktop browser it plays video even if it's content-encoding is gzip and file is compressed, but won't show in browser. I should send all gzip video without compression if want to play in mobile browser? – Hamed_gibago Dec 09 '18 at 11:28
  • @Hamed_gibago: With Content-Encoding: gzip, byte ranges apply to the gzipped content, which makes them useless for seeking in the video. Even if it could start playing the video, it would at least be unable to seek until all of the content from the beginning to the target position is downloaded and uncompressed. Furthermore mp4 is already compressed using its own method so trying to compress again using gzip is usually a waste of time and may even make it larger. So yes, you should disable gzip compression on mp4 files (regardless of browser). – mark4o Dec 09 '18 at 14:59
  • @mark4o Thank you for your detailed answered. So I disable gzip for mp4 extentions. What about other videos? do you offer convert and save them as mp4 in my NAS and just let clients upload just mp4? or for other extensions send them as gzip? – Hamed_gibago Dec 10 '18 at 06:48
  • my video file was not web optimized, so player couldn't buffer it. Great thanks to you. – Hadi hashemi Dec 30 '18 at 13:45
7

Here is the solution I used to create a Web API Controller in C# (MVC) that will serve video files with Byte Ranges (partial requests). Partial requests allow a browser to only download as much of the video as it needs to play rather than downloading the entire video. This makes it far more efficient.

Note this only works in recent versions.

var stream = new FileStream(videoFilename, FileMode.Open, FileAccess.Read , FileShare.Read);

var mediaType = MediaTypeHeaderValue.Parse($"video/{videoFormat}");

if (Request.Headers.Range != null)
{
    try
    {
        var partialResponse = Request.CreateResponse(HttpStatusCode.PartialContent);
        partialResponse.Content = new ByteRangeStreamContent(stream, Request.Headers.Range, mediaType);

        return partialResponse;
    }
    catch (InvalidByteRangeException invalidByteRangeException)
    {
        return Request.CreateErrorResponse(invalidByteRangeException);
    }
}
else
{
    // If it is not a range request we just send the whole thing as normal
    var fullResponse = Request.CreateResponse(HttpStatusCode.OK);

    fullResponse.Content = new StreamContent(stream);
    fullResponse.Content.Headers.ContentType = mediaType;

    return fullResponse;
}
Chris
  • 24,827
  • 43
  • 179
  • 317