The MP4 Mime Type

About the correct mime type for MP4 files, delivering video files from PHP, working with video codecs and containers, editing, and more.

5960 views
d

By. Jacob

Edited: 2021-04-19 17:27

MP4 Mime Type, HTTP

The content-type for mp4 video files is video/mp4, while the file extension can either be .mp4 or .m4v—both extensions refers to the same video container format.

The response headers of a .mp4 video may look like this:

HTTP/1.1 200 OK
content-type: video/mp4
content-length: 40454

To deliver video files from PHP, ideally, we will want to support the HTTP Range Header; supporting the range header will allow visitors to request a specific part of a video by using the video controls. If the range header is not supported, the controls might not work as intended.

Delivering a mp4 video file from PHP is done by sending the content-type header:

$video_data = file_get_contents('/path/to/video_file.m4v');

header('content-type: video/mp4');
echo $video_data;
exit();

But, while this does allow playing the video from the beginning, the controls probably will not work. They might work if the browser caches the file - at least in theory. But, to make it work while streaming the file, we will need to support the range header.

Enabling video controls for skipping

By supporting the HTTP range header, we allow the use of "video controls" typically offered by the browser on video files. This enables the user to "skip" in the video; of course, this is something we want to implement if we are going to deliver video files from PHP.

When a user clicks the progress-bar in a video or audio file, to either go forward or backwards, their browser will automatically send a "range request".

Note. A range request is simply a standard HTTP GET that includes the range request header.

This appears to happen silently, so you may not be able to monitor this in the developer tools of your browser; instead of using developer tools while testing, cURL can be used with the --HEAD option.

A web server will typically handle the range request header automatically for static files, but when we want to do this from PHP, we will need to manually write a HTTP Range implementation.

Supporting the range header in PHP

Note. The below code does not only apply for audio and video files. The range header is also used to enable clients to resume downloads.

I included a function in this article that shows how to deal with range requests, but you may want to use a file handler for error handling, file locking, and other features.

A simple function for delivering video files can look like this:

function http_stream_file($file_path) {
    if (!file_exists($file_path)) {
      throw new Exception('The file did not exist');
    }
    if (($file_size = filesize($file_path)) === false) {
      throw new Exception('Unable to get filesize.');
    }
  
  // Define start and end of stream
  $start = 0;
  $end = $file_size - 1; // Minus 1 (Byte ranges are zero-indexed)
  
  // Attempt to Open file for (r) reading (b=binary safe)
  if (($fp = @fopen($file_path, 'rb')) == false) {
    throw new Exception('Unable to open file.');
  }
  
  
  // -----------------------
  // Handle "range" requests
  // -----------------------
  // A Range request is sent when a client requests a specific part of a file
  // such as when using the video controls or when a download is resumed.
  // We need to handle range requests in order to send back the requested part of a file.
  
  // Determine if the "range" Request Header was set
  if (isset($_SERVER['HTTP_RANGE'])) {
  
    // Parse the range header
    if (preg_match('|=([0-9]+)-([0-9]+)$|', $_SERVER['HTTP_RANGE'], $matches)) {
      $start = $matches["1"];
      $end = $matches["2"] - 1;
    } elseif (preg_match('|=([0-9]+)-?$|', $_SERVER['HTTP_RANGE'], $matches)) {
      $start = $matches["1"];
    }
  
    // Make sure we are not out of range
    if (($start > $end) || ($start > $file_size) || ($end > $file_size) || ($end <= $start)) {
      http_response_code(416);
      exit();
    }
  
    // Position the file pointer at the requested range
    fseek($fp, $start);
  
    // Respond with 206 Partial Content
    http_response_code(206);
  
    // A "content-range" response header should only be sent if the "range" header was used in the request
      $response_headers['content-range'] = 'bytes ' . $start . '-' . $end . '/' . $file_size;
  } else {
    // If the range header is not used, respond with a 200 code and start sending some content
    http_response_code(200);
  }
  
  // Tell the client we support range-requests
  $response_headers['accept-ranges'] = 'bytes';
  // Set the content length to whatever remains
  $response_headers['content-length'] = ($file_size - $start);
  
  // ---------------------
  // Send the file headers
  // ---------------------
  // Send the "last-modified" response header
  // and compare with the "if-modified-since" request header (if present)
  
  if (($timestamp = filemtime($file_path)) !== false) {
    $response_headers['last-modified'] = gmdate("D, d M Y H:i:s", $timestamp) . ' GMT';
    if ((isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) && ($_SERVER['HTTP_IF_MODIFIED_SINCE'] == $response_headers['last-modified'])) {
      http_response_code(304); // Not Modified
      exit();
    }
  }
  
  // Set HTTP response headers
  $response_headers['content-type'] = 'video/mp4';

  foreach ($response_headers as $header => $value) {
    header($header . ': ' . $value);
  }
  
  // ---------------------
  // Start the file output
  // ---------------------
  $buffer = 8192;
  while (!feof($fp) && ($pointer = ftell($fp)) <= $end) {
  
    // If next $buffer will pass $end,
    // calculate remaining size
    if ($pointer + $buffer > $end) {
      $buffer = $end - $pointer + 1;
    }
    echo @fread($fp, $buffer);
      flush();
    }
    fclose($fp);
    exit();
  }

To call this function on a file that resides in the same directory, we can do like this:

http_stream_file('some-video-file.mp4');

Working with video formats

Working with video file formats is not as straight forward as simply looking at the file extension. If you carefully make sure that files on your server have the correct file extension (and codec), then this is of less concern. But, to truly know the format of a user-uploaded file, you will need to look at the content of the file.

The file extension usually only indicates the container format, and not the codec used to encode the video file. Some video codecs are better supported by browsers than other codecs.

Browsers are not necessarily as forgiving as VLC Media Player (which practically supports everything). Codecs must be chosen with care when a video is to be played on the internet.

This means that user-uploaded files might need to be converted before they can be included on a website.

While .mp4 (with the h.264 codec) is a decent choice for sharing videos on the internet, there are also other codecs and container formats, such as, AVI and MPG. The .avi format may contain a variety of different codecs, while .mpg only contains video encoded as MPEG.

Video editing and conversion

To convert videos from one format to another, the open source tool, called handbrake, works quite well.

For editing, you can use kdenlive, which I personally find very easy to use. But, the best editing software is still paid, and includes well known programs such as Adobe premiere, Vegas Pro, Final Cut Pro; as well as many others.

The best video editors are very similar, and therefor easy to learn if you have used others.

Tell us what you think:

  1. In this Tutorial, it is shown how to redirect all HTTP requests to a index.php file using htaccess or Apache configuration files.
  2. How to create a router in PHP to handle different request types, paths, and request parameters.
  3. Tutorial on how to use proxy servers with cURL and PHP
  4. When using file_get_contents to perform HTTP requests, the server response headers is stored in a reserved variable after each successful request; we can iterate over this when we need to access individual response headers.
  5. How to effectively use variables within strings to insert bits of data where needed.

More in: PHP Tutorials