2012-10-24 55 views
6

我試圖修改自定義Web服務器應用程序以使用HTML5視頻。HTML5視頻和部分範圍的HTTP請求

它提供一個基本的<video>標記的HTML5頁面,然後它需要處理實際內容的請求。

到目前爲止,我唯一可以使用它的唯一方法是將整個視頻文件加載到內存中,然後通過單一響應將其發回。這不是一個實際的選擇。我想要一塊一塊地發送它:發回100 kb,並等待瀏覽器請求更多。

我看到下列頭的請求:

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- 

我試着發回的部分內容響應:

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

我得到一些更多GET請求,如下

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 = ###.###.###.###:## 

(沒有跡象表明瀏覽器需要該文件的任何特定部分。)無論我發送什麼文件k到瀏覽器,視頻不播放。

如上所述,如果我嘗試在同一個HTTP數據包中一次發送整個230 MB文件,同樣的視頻將正確播放。

有沒有什麼辦法可以很好的完成部分內容請求?我使用Firefox進行測試,但最終需要與所有瀏覽器協同工作。

+0

我覺得很奇怪,你使用Firefox的測試和發送該Firefox不支持MP4文件,所以它應該不會起作用。 –

+0

到目前爲止,我在瀏覽器端嘗試Firefox(帶H264插件)和IE9,以及服務器端的MP4和WebM。我想我錯過了一些東西。 – user434507

+0

你爲什麼想要從客戶那裏獲得部分範圍的請求?只要讓他們問問他們想要什麼,然後以最方便的方式發給他們。我使用流式音頻(來自Chrome的請求)以及定製服務器 - 它實時發送聲音。我認爲這是默認和可接受的方式。如果您擔心網絡負載,請將節流降低到足以實現流暢視頻播放的速度。 – Stan

回答

4

我知道這是一個老問題,但如果它有幫助,您可以嘗試我們在代碼庫中使用的以下「模型」。

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; 
} 
} 

然後你使用這樣的:

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

它將處理髮送文件或整個文件等的一部分,很適合我們在這方面和許多其他情形。希望能幫助到你。

+1

這行代碼爲 header(「Content-Length:$ filesize」); 實際上應該是 標題(「Content-Length:$ length」); ,因爲它是傳遞的總字節數,而不是文件的大小。 Chrome的視頻播放器barfs如果設置不正確。 – frankieandshadow

+0

好點,我糾正了原來的帖子。 $ length只在$ range爲真的情況下設置,所以我確定它先設置爲$ filesize,如果$ range爲true,則會被覆蓋。 –

+0

我的問題是,即使header_remove('Conten-Type'),php提供了Content-Type:text/html; –

1

我想要部分範圍的請求,因爲我會做實時代碼轉換,我不能讓文件完全轉碼並根據請求提供。

爲了響應你不知道的全部主體內容,但(你不能猜出Content-Length,實時編碼),使用塊編碼:

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 

每個塊是發送當它可用:當幾個幀進行編碼時,或者當輸出緩衝器是滿的,100KB的產生等

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

22給在六塊字節長度(爲0x22 = 34個字節), ; 3rd chunk是額外塊信息(可選),tent...binary....data....chunk3..a是塊的內容。

然後,當編碼完成後,所有組塊被通過發送端:

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

0意味着沒有更多組塊,後面的零或多個拖車(允許的報頭字段)中的報頭定義(Trailer: ExpiresExpires: Wed, 21 Oct 2015 07:28:00 GMT不需要)提供的校驗和數字簽名等

這裏是服務器的響應相當於如果已經生成文件(沒有實時編碼):

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... 

欲瞭解更多信息:Chunked transfer encoding — WikipediaTrailer - HTTP | MDN

+0

我不認爲視頻標籤支持分塊編碼(至少在Chrome中)。 – themihai

+0

HTTP/1.1支持需要分塊傳輸編碼。我也試過,Chrome和Firefox使用分塊傳輸編碼播放視頻。 Safari(桌面和iOS)需要支持[範圍](https://en.wikipedia.org/wiki/Byte_serving)播放視頻(不需要音頻)。傳輸編碼和範圍不是不兼容的,但字節範圍需要事先知道字節大小。 爲此,您可以使用直播流協議(但在編碼視頻時需要特殊處理),如HLS或API(如MSE)。 請參閱[hls.js](https://github.com/video-dev/hls.js) – mems

+0

Chrome會發出部分請求,無論服務器是否支持它,所以我認爲沒有部分/範圍支持的分塊編碼是不足以提供html5視頻。如果服務器沒有公佈內容的總長度(即提供通配符*),則在第一次部分請求完成後播放器停止,而不是請求下一部分,所以我猜這是一個Chrome錯誤。 – themihai