16

HTML5 ビデオで動作するようにカスタム Web サーバー アプリを変更しようとしています。

基本的なタグを持つ HTML5 ページを提供し、<video>実際のコンテンツの要求を処理する必要があります。

これまでに動作させる唯一の方法は、ビデオ ファイル全体をメモリにロードしてから、1 回の応答で送り返すことでした。これは実用的なオプションではありません。私はそれを少しずつ提供したいと思っています。たとえば、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 = ###.###.###.###:##

(ブラウザがファイルの特定の部分を必要としているという兆候はありません。)ブラウザに何を送り返しても、ビデオは再生されません。

前述のように、230 MB のファイル全体を同じ HTTP パケットで一度に送信しようとすると、同じビデオが正しく再生されます。

部分的なコンテンツ リクエストを通じて、これをすべてうまく機能させる方法はありますか? テスト目的で Firefox を使用していますが、最終的にはすべてのブラウザーで動作する必要があります。

4

2 に答える 2

8

これが古い質問であることは承知していますが、コードベースで使用している次の「モデル」を試してみてください。

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();

ファイルの一部または完全なファイルなどの送信を処理し、この状況や他の多くの状況でうまく機能します。それが役に立てば幸い。

于 2013-09-24T10:00:24.803 に答える
4

リアルタイムのトランスコーディングを行うため、部分範囲リクエストが必要です。ファイルを完全にトランスコードしてリクエストに応じて利用することはできません。

本文の内容がまだわからない (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

チャンクのバイト長を 16 進数 ( 0x22 22= 34 バイト) で示し、 ; 3rd chunkは追加のチャンク情報 (オプション) でありtent...binary....data....chunk3..a、 はチャンクの内容です。

次に、エンコードが終了し、すべてのチャンクが送信されたら、次のように終了します。

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

whereは、チェックサムやデジタル署名などを提供するため0にヘッダーで定義されたゼロ以上のトレーラー (許可されたヘッダー フィールド) が後に続くチャンクがないことを意味します (必須Trailer: Expiresではありません)。Expires: 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...

詳細情報:チャンク転送エンコーディング — ウィキペディア予告編 - HTTP | MDN

于 2015-09-16T09:53:18.890 に答える