31

Ok basically I have a project that requires that videos are hidden from the users while still able to see them (by using php). here's what i got so far:

The video.php file has this:

<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'path/to/movie.mp4');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
curl_setopt($ch, CURLOPT_HEADER, 0);
$out = curl_exec($ch);
curl_close($ch);


header('Content-type: video/mp4');
header('Content-type: video/mpeg');
header('Content-disposition: inline');
header("Content-Transfer-Encoding:­ binary");
header("Content-Length: ".filesize($out));
echo $out;
exit();
?>

and the html file that is supposed to display this is using html5 as it would expect. now here's the thing.. when I straight embed this (not ) it works. but it doesn't work on my iPhone and doesn't work in the tag... if I use the direct file instead of the php wrapper, everything works fine, on my iPhone too...

so I guess my question for this one is this: what are the proper header() information to perfectly replicate an mp4 that can be streamed via iPhone and HMTL5?

Solution derived from: http://mobiforge.com/developing/story/content-delivery-mobile-devices

video.php file:

<?php
$file = 'path/to/videofile.mp4';
$fp = @fopen($file, 'rb');

$size   = filesize($file); // File size
$length = $size;           // Content length
$start  = 0;               // Start byte
$end    = $size - 1;       // End byte

header('Content-type: video/mp4');
header("Accept-Ranges: 0-$length");
if (isset($_SERVER['HTTP_RANGE'])) {

    $c_start = $start;
    $c_end   = $end;

    list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
    if (strpos($range, ',') !== false) {
        header('HTTP/1.1 416 Requested Range Not Satisfiable');
        header("Content-Range: bytes $start-$end/$size");
        exit;
    }
    if ($range == '-') {
        $c_start = $size - substr($range, 1);
    }else{
        $range  = explode('-', $range);
        $c_start = $range[0];
        $c_end   = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
    }
    $c_end = ($c_end > $end) ? $end : $c_end;
    if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
        header('HTTP/1.1 416 Requested Range Not Satisfiable');
        header("Content-Range: bytes $start-$end/$size");
        exit;
    }
    $start  = $c_start;
    $end    = $c_end;
    $length = $end - $start + 1;
    fseek($fp, $start);
    header('HTTP/1.1 206 Partial Content');
}
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: ".$length);


$buffer = 1024 * 8;
while(!feof($fp) && ($p = ftell($fp)) <= $end) {

    if ($p + $buffer > $end) {
        $buffer = $end - $p + 1;
    }
    set_time_limit(0);
    echo fread($fp, $buffer);
    flush();
}

fclose($fp);
exit();
?>
tereško
  • 58,060
  • 25
  • 98
  • 150
Zia
  • 2,735
  • 3
  • 30
  • 27
  • [Duplicate of this question](http://stackoverflow.com/questions/3128906/mp4-plays-when-accessed-directly-but-not-when-read-through-php-on-ios). – Kelly May 07 '11 at 21:28
  • 1
    It's very strange how you're sending two `Content-type` headers with different values. – icktoofay May 07 '11 at 21:32
  • Kelly, I saw that question and it didn't help really, and it didn't look like the same problem (with mine it doesn't even play when embeding in – Zia May 08 '11 at 05:03
  • edit: there was a bug with the original script so the above one is a modified version fixing it. – Zia Jul 08 '11 at 09:31
  • 3
    I had problems with `header("Accept-Ranges: 0-$length");`. Firefox didn't get the movie length of webm-movies. Changing this to `header("Accept-Ranges: bytes");` then worked. – acme Apr 24 '12 at 13:24
  • Please separate the answer from your question. – General Grievance May 04 '23 at 17:33

4 Answers4

16

Iphones use something called byte-ranges for audio and video requests. See this link for a solution. It's in Appendix A.

http://mobiforge.com/developing/story/content-delivery-mobile-devices

sreimer
  • 4,913
  • 2
  • 33
  • 43
  • 1
    works!! the issue with my code was two things. curl was not the best solution here (didn't work in html5 on computer) then once I got that part working, for it to work on mobile you can't set: header("Content-Transfer-Encoding:­ binary"); it breaks it. – Zia May 08 '11 at 05:53
  • 2
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. – josliber Jan 10 '16 at 11:53
8

Here is a code snippet that will do what you want (from this question). The PHP solution seems more elegant, and it adds a more efficient solution that might work that uses the web server to serve the content.

<?php

$path = 'file.mp4';

$size=filesize($path);

$fm=@fopen($path,'rb');
if(!$fm) {
  // You can also redirect here
  header ("HTTP/1.0 404 Not Found");
  die();
}

$begin=0;
$end=$size;

if(isset($_SERVER['HTTP_RANGE'])) {
  if(preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) {
    $begin=intval($matches[0]);
    if(!empty($matches[1])) {
      $end=intval($matches[1]);
    }
  }
}

if($begin>0||$end<$size)
  header('HTTP/1.0 206 Partial Content');
else
  header('HTTP/1.0 200 OK');

header("Content-Type: video/mp4");
header('Accept-Ranges: bytes');
header('Content-Length:'.($end-$begin));
header("Content-Disposition: inline;");
header("Content-Range: bytes $begin-$end/$size");
header("Content-Transfer-Encoding: binary\n");
header('Connection: close');

$cur=$begin;
fseek($fm,$begin,0);

while(!feof($fm)&&$cur<$end&&(connection_status()==0))
{ print fread($fm,min(1024*16,$end-$cur));
  $cur+=1024*16;
  usleep(1000);
}
die();

More Performance

Note that this is not the most efficient way to do it, because the whole file needs to go through PHP, so you will just need to try how it goes for you.

Assuming the reason you want to do this is to restrict access, and you need more efficiency later, you can use a flag for the web server.

Apache with X-Sendfile module or lightty (nginx info here)

$path = 'file.mp4';
header("X-Sendfile: $path");
die();

This is a bit more advanced and you should only use it if you need it, but it is relaxing to know you have an upgrade option when you start out with something that is rather easy but has mediocre performance.

Duck
  • 34,902
  • 47
  • 248
  • 470
cmc
  • 4,294
  • 2
  • 35
  • 34
  • Have you managed to get this working on Iphone with X-sendfile, I am having some brain frying issues trying to get it working. It will work with X-Sendfile on everything but Iphone – John J May 23 '14 at 09:16
3

This code was very handy for me, but I ran into trouble because I am using session vars, and PHP queues access to sessions. If a video was loading, all AJAX requests were impossible, etc. So make sure to call session_write_close() before you start output.

Charles
  • 50,943
  • 13
  • 104
  • 142
jefftimesten
  • 366
  • 6
  • 13
2

Yes, its easy to do. No need to set those headers manually. Let the server do it automatically.

Heres a working script -

ob_start();

if( isset($_SERVER['HTTP_RANGE']) )

$opts['http']['header']="Range: ".$_SERVER['HTTP_RANGE'];

$opts['http']['method']= "HEAD";

$conh=stream_context_create($opts);

$opts['http']['method']= "GET";

$cong= stream_context_create($opts);

$out[]= file_get_contents($real_file_location_path_or_url,false,$conh);

$out[]= $http_response_header;

ob_end_clean();

array_map("header",$http_response_header);

readfile($real_file_location_path_or_url,false,$cong);
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Tech Consultant
  • 374
  • 1
  • 7