简体   繁体   English

用PHP流式传输视频

[英]Streaming a video with PHP

I currently have this code: 我目前有以下代码:

function Stream($file)
{
header_remove(); 
$arr = get_headers($file);
foreach ($arr as &$value) {if((strpos($value,'Content-Type')!== false)){header($value);}}

    if (isset($_SERVER['HTTP_RANGE']))  {
        rangeDownload($file);
    }
    else {
header('HTTP/1.1 206 Partial Content');
header("Content-Length:1");
    //foreach ($arr as &$value) {if((strpos($value,'Content-Length')!== false)){header($value);}}
//header("Content-Range:bytes 21056-21056/243957100");
        readfile($file);
    }
}

function rangeDownload($file) {

    $fp = @fopen($file, 'rb');

    $size   = filesize($file); // File size
    $length = $size;           // Content length
    $start  = 0;               // Start byte
    $end    = $size - 1;       // End byte
    // Now that we've gotten so far without errors we send the accept range header
    /* At the moment we only support single ranges.
     * Multiple ranges requires some more work to ensure it works correctly
     * and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
     *
     * Multirange support annouces itself with:
     * header('Accept-Ranges: bytes');
     *
     * Multirange content must be sent with multipart/byteranges mediatype,
     * (mediatype = mimetype)
     * as well as a boundry header to indicate the various chunks of data.
     */
    header("Accept-Ranges: 0-$length");
    // header('Accept-Ranges: bytes');
    // multipart/byteranges
    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
    if (isset($_SERVER['HTTP_RANGE'])) {

        $c_start = $start;
        $c_end   = $end;
        // Extract the range string
        list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
        // Make sure the client hasn't sent us a multibyte range
        if (strpos($range, ',') !== false) {

            // (?) Shoud this be issued here, or should the first
            // range be used? Or should the header be ignored and
            // we output the whole content?
            header('HTTP/1.1 416 Requested Range Not Satisfiable');
            header("Content-Range: bytes $start-$end/$size");
            // (?) Echo some info to the client?
            exit;
        }
        // If the range starts with an '-' we start from the beginning
        // If not, we forward the file pointer
        // And make sure to get the end byte if spesified
        if ($range0 == '-') {

            // The n-number of the last bytes is requested
            $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;
        }
        /* Check the range and make sure it's treated according to the specs.
         * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
         */
        // End bytes can not be larger than $end.
        $c_end = ($c_end > $end) ? $end : $c_end;
        // Validate the requested range and return an error if it's not correct.
        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");
            // (?) Echo some info to the client?
            exit;
        }
        $start  = $c_start;
        $end    = $c_end;
        $length = $end - $start + 1; // Calculate new content length
        fseek($fp, $start);
        header('HTTP/1.1 206 Partial Content');
    }
    // Notify the client the byte range we'll be outputting
    header("Content-Range: bytes $start-$end/$size");
    header("Content-Length: $length");

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

        if ($p + $buffer > $end) {

            // In case we're only outputtin a chunk, make sure we don't
            // read past the length
            $buffer = $end - $p + 1;
        }
        set_time_limit(0); // Reset time limit for big files
        echo fread($fp, $buffer);
        flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
    }

    fclose($fp);

}

I can see that all 3 requests are made and the file download begins yet instead of the video screen appearing the stream cancels. 我可以看到所有3个请求均已发出并且文件下载尚未开始,而不是视频流出现时取消了。 I have tried mimics standards apache headers without success, I am trying to have it serve video content with the widest possible support (eg content type detection). 我尝试模仿标准的Apache标头没有成功,但我试图使其以尽可能广泛的支持(例如内容类型检测)提供视频内容。

The issue is in the range download function.In this function you are headers are correct.but you are printing the complete file as the response. 问题出在范围下载功能中。在此功能中,您的标题正确无误,但您正在打印完整文件作为响应。

These changes should fix your problem. 这些更改将解决您的问题。

// Notify the client the byte range we'll be outputting
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: $length");

Here you need to seek the file to start specified in the content-range headers. 在这里,您需要查找内容范围标题中指定的要启动的文件。

fseek($fp,$start) /* MISSING CODE */ 

Inserting the above line should fix your problem. 插入以上行应该可以解决您的问题。

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

    if ($p + $buffer > $end) {

        // In case we're only outputtin a chunk, make sure we don't
        // read past the length
        $buffer = $end - $p + 1;
    }
    set_time_limit(0); // Reset time limit for big files
    echo fread($fp, $buffer);
    flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
}

fclose($fp);

I just had to go through the same thing for a job I was asked to do. 我只需要经历同样的事情就可以被要求做一份工作。 I had no issues making it work for chrome/firefox but I had issues with Safari on the Mac/iPad. 我没有让它适用于chrome / firefox的问题,但是在Mac / iPad上的Safari方面却有问题。 I had referenced multiple sources and tried many things, in the end I threw everything I had to get a clean slate, and decided to try fix the issue using the code that was in this issue since it was very similar to what I already had, but different enough to give me more clarity. 我参考了多个资源并尝试了许多方法,最后我扔掉了一切,以使自己一清二楚,并决定尝试使用此问题中的代码来解决此问题,因为它与我已经拥有的代码非常相似,但又有所不同,使我更加清楚。

Here is the final working version using the code from the original question as a base but with the necessary fixes that worked for me. 这是最终的工作版本,使用原始问题中的代码作为基础,但有对我有用的必要修复。 Hopefully this helps someone else. 希望这可以帮助其他人。

if (isset($_SERVER['HTTP_RANGE'])) {
  header('Content-Type: video/'.$ext); // reference your own mime variable here
  $fp = @fopen($filepath, 'rb');

  $size   = filesize($filepath); // File size
  $length = $size;           // Content length
  $start  = 0;               // Start byte
  $end    = $size - 1;       // End byte
  // Now that we've gotten so far without errors we send the accept range header
  /* At the moment we only support single ranges.
   * Multiple ranges requires some more work to ensure it works correctly
   * and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
   *
   * Multirange support annouces itself with:
   * header('Accept-Ranges: bytes');
   *
   * Multirange content must be sent with multipart/byteranges mediatype,
   * (mediatype = mimetype)
   * as well as a boundry header to indicate the various chunks of data.
   */
  header("Accept-Ranges: 0-$length");
  // multipart/byteranges
  // http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
  $c_start = $start;
  $c_end   = $end;
  // Extract the range string
  list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
  // Make sure the client hasn't sent us a multibyte range
  if (strpos($range, ',') !== false) {
    // (?) Shoud this be issued here, or should the first
    // range be used? Or should the header be ignored and
    // we output the whole content?
    header('HTTP/1.1 416 Requested Range Not Satisfiable');
    header("Content-Range: bytes $start-$end/$size");
    // (?) Echo some info to the client?
    exit;
  }
  // If the range starts with an '-' we start from the beginning
  // If not, we forward the file pointer
  // And make sure to get the end byte if spesified
  if ($range == '-') {
    // The n-number of the last bytes is requested
    $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;
  }

  /* Check the range and make sure it's treated according to the specs.
   * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
   */
  // End bytes can not be larger than $end.
  $c_end = ($c_end > $end) ? $end : $c_end;
  // Validate the requested range and return an error if it's not correct.
  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");
    // (?) Echo some info to the client?
    exit;
  }
  $start  = $c_start;
  $end    = $c_end;
  $length = $end - $start + 1; // Calculate new content length
  header('HTTP/1.1 206 Partial Content');
  // Notify the client the byte range we'll be outputting
  header("Content-Range: bytes $start-$end/$size");
  header("Content-Length: $length");

  // Start buffered download
  $buffer = 1024 * 8;
  fseek($fp,$start);
  while(!feof($fp) && ($p = ftell($fp)) <= $end) {
    if ($p + $buffer > $end) {
      // In case we're only outputtin a chunk, make sure we don't
      // read past the length
      $buffer = $end - $p + 1;
    }
    set_time_limit(0); // Reset time limit for big files
    echo fread($fp, $buffer);
    flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
  }

  fclose($fp);
}

This is the ENTIRE code that I use, with the exception of a few variables before it to build the filepath and video extension/mime type. 这是我使用的整个代码,除了用于构建文件路径和视频扩展名/ MIME类型的几个变量之外。 This has been tested on Chrome(latest), Firefox(latest), Safari 7.0.6 on macbook pro, Safari on iOS 7 已在Chrome(最新),Firefox(最新),macbook pro上的Safari 7.0.6,iOS 7上的Safari上进行过测试

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM