HTML5 video and partial HTTP requests

I am trying to change a custom web server application to work with HTML5 video.

It serves an HTML5 page with a base <video> , and then it needs to process requests for the actual content.

The only way to get it working so far is to load the entire video file into memory, and then send it back in one response. This is not a practical option. I want to serve it in parts: send back, say, 100 kb, and wait for the browser to request more.

I see a request with the following headers:

 http_version = 1.1 request_method = GET Host = ###.###.###.###:## User-Agent = Mozilla/5.0 (Windows NT 6.1; WOW64; rv:16.0) Gecko/20100101 Firefox/16.0 Accept = video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5 Accept-Language = en-US,en;q=0.5 Connection = keep-alive Range = bytes=0- 

I tried to send a partial content response:

 HTTP/1.1 206 Partial content Content-Type: video/mp4 Content-Range: bytes 0-99999 / 232725251 Content-Length: 100000 

I get a few more GET requests as shown below.

 Cache-Control = no-cache Connection = Keep-Alive Pragma = getIfoFileURI.dlna.org Accept = */* User-Agent = NSPlayer/12.00.7601.17514 WMFSDK/12.00.7601.17514 GetContentFeatures.DLNA.ORG = 1 Host = ###.###.###.###:## 

(without indicating that the browser wants any particular part of the file.) No matter what I send back to the browser, the video does not play.

As stated above, the same video will play correctly if I try to send the entire 230 MB file at once in the same HTTP packet.

Is there any way for this to work with partial content requests? I use Firefox for testing, but ultimately it should work with all browsers.

+6
source share
2 answers

I know this is an old question, but if it helps you try the following β€œmodel” that we use in our code base.

 class Model_DownloadableFile { private $full_path; function __construct($full_path) { $this->full_path = $full_path; } public function get_full_path() { return $this->full_path; } // Function borrowed from (been cleaned up and modified slightly): http://stackoverflow.com/questions/157318/resumable-downloads-when-using-php-to-send-the-file/4451376#4451376 // Allows for resuming paused downloads etc public function download_file_in_browser() { // Avoid sending unexpected errors to the client - we should be serving a file, // we don't want to corrupt the data we send @error_reporting(0); // Make sure the files exists, otherwise we are wasting our time if (!file_exists($this->full_path)) { header('HTTP/1.1 404 Not Found'); exit; } // Get the 'Range' header if one was sent if (isset($_SERVER['HTTP_RANGE'])) { $range = $_SERVER['HTTP_RANGE']; // IIS/Some Apache versions } else if ($apache = apache_request_headers()) { // Try Apache again $headers = array(); foreach ($apache as $header => $val) { $headers[strtolower($header)] = $val; } if (isset($headers['range'])) { $range = $headers['range']; } else { $range = false; // We can't get the header/there isn't one set } } else { $range = false; // We can't get the header/there isn't one set } // Get the data range requested (if any) $filesize = filesize($this->full_path); $length = $filesize; if ($range) { $partial = true; list($param, $range) = explode('=', $range); if (strtolower(trim($param)) != 'bytes') { // Bad request - range unit is not 'bytes' header("HTTP/1.1 400 Invalid Request"); exit; } $range = explode(',', $range); $range = explode('-', $range[0]); // We only deal with the first requested range if (count($range) != 2) { // Bad request - 'bytes' parameter is not valid header("HTTP/1.1 400 Invalid Request"); exit; } if ($range[0] === '') { // First number missing, return last $range[1] bytes $end = $filesize - 1; $start = $end - intval($range[0]); } else if ($range[1] === '') { // Second number missing, return from byte $range[0] to end $start = intval($range[0]); $end = $filesize - 1; } else { // Both numbers present, return specific range $start = intval($range[0]); $end = intval($range[1]); if ($end >= $filesize || (!$start && (!$end || $end == ($filesize - 1)))) { $partial = false; } // Invalid range/whole file specified, return whole file } $length = $end - $start + 1; } else { $partial = false; // No range requested } // Determine the content type $finfo = finfo_open(FILEINFO_MIME_TYPE); $contenttype = finfo_file($finfo, $this->full_path); finfo_close($finfo); // Send standard headers header("Content-Type: $contenttype"); header("Content-Length: $length"); header('Content-Disposition: attachment; filename="' . basename($this->full_path) . '"'); header('Accept-Ranges: bytes'); // if requested, send extra headers and part of file... if ($partial) { header('HTTP/1.1 206 Partial Content'); header("Content-Range: bytes $start-$end/$filesize"); if (!$fp = fopen($this->full_path, 'r')) { // Error out if we can't read the file header("HTTP/1.1 500 Internal Server Error"); exit; } if ($start) { fseek($fp, $start); } while ($length) { // Read in blocks of 8KB so we don't chew up memory on the server $read = ($length > 8192) ? 8192 : $length; $length -= $read; print(fread($fp, $read)); } fclose($fp); } else { readfile($this->full_path); // ...otherwise just send the whole file } // Exit here to avoid accidentally sending extra content on the end of the file exit; } } 

Then you use it as follows:

 (new Model_DownloadableFile('FULL/PATH/TO/FILE'))->download_file_in_browser(); 

It will handle sending part of a file or full file, etc. and works well for us in this and many other situations. Hope this helps.

+4
source

I need partial range requests, because I will transcode in real time, I can not completely transcode the file and get it on demand.

For an answer that you do not know yet, the full body content (you cannot guess the Content-Length , live encoding), use the chunk encoding:

 HTTP/1.1 200 OK Content-Type: video/mp4 Transfer-Encoding: chunked Trailer: Expires 1E; 1st chunk ...binary....data...chunk1..my 24; 2nd chunk video..binary....data....chunk2..con 22; 3rd chunk tent...binary....data....chunk3..a 2A; 4th chunk nd...binary......data......chunk4...etc... 0 Expires: Wed, 21 Oct 2015 07:28:00 GMT 

Each piece is sent when it is available: when several frames are encoded or when the output buffer is full, 100kB, etc. are generated.

 22; 3rd chunk tent...binary....data....chunk3..a 

Where 22 indicate the length of the byte of the block in hex (0x22 = 34 bytes),; ; 3rd chunk - additional information about the object (optional), and tent...binary....data....chunk3..a - the contents of the fragment.

Then, when the encoding is complete and all the pieces are sent, complete with:

 0 Expires: Wed, 21 Oct 2015 07:28:00 GMT 

Where 0 means that no more pieces are followed by zero or more trailers (allowed header fields) defined in the header ( Trailer: Expires and Expires: Wed, 21 Oct 2015 07:28:00 GMT not required) to provide checksums or digital signatures, etc.

Here is the equivalent of a server response if the file is already generated (no live encoding):

 HTTP/1.1 200 OK Content-Type: video/mp4 Content-Length: 142 Expires: Wed, 21 Oct 2015 07:28:00 GMT ...binary....data...chunk1..myvideo..binary....data....chunk2..content...binary....data....chunk3..and...binary......data......chunk4...etc... 

For more information: Chunked transfer encoding - Wikipedia , Trailer - HTTP | MDN

+1
source

All Articles