Html HTML5 视频和部分范围 HTTP 请求

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13043816/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-29 03:32:41  来源:igfitidea点击:

HTML5 video and partial range HTTP requests

htmlhttpvideo

提问by Eugene Smith

I'm trying to modify a custom web server app to work with HTML5 video.

我正在尝试修改自定义 Web 服务器应用程序以处理 HTML5 视频。

It serves a HTML5 page with a basic <video>tag and then it needs to handle the requests for actual content.

它提供一个带有基本<video>标签的 HTML5 页面,然后它需要处理对实际内容的请求。

The only way I could get it to work so far is to load the entire video file into the memory and then send it back in a single response. It's not a practical option. I want to serve it piece by piece: send back, say, 100 kb, and wait for the browser to request more.

到目前为止,我可以让它工作的唯一方法是将整个视频文件加载到内存中,然后在一个响应中将其发回。这不是一个实用的选择。我想一点一点地提供它:发回,比如说,100 kb,然后等待浏览器请求更多。

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 back 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 follows

我又收到了几个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 = ###.###.###.###:##

(with no indication that the browser wants any specific 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.

如上所述,如果我尝试在同一个 HTTP 数据包中一次发送整个 230 MB 文件,同一个视频将正确播放。

Is there any way to get this all working nicely through partial content requests? I'm using Firefox for testing purposes, but it needs to work with all browsers eventually.

有没有办法通过部分内容请求使这一切正常工作?我使用 Firefox 进行测试,但它最终需要适用于所有浏览器。

回答by Luke Cousins

I know this is an old question, but if it helps you can 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;
}
}

You then use it like this:

然后像这样使用它:

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

It will deal with sending part of the file or the full file etc and works well for us in this and lots of other situations. Hope it helps.

它将处理发送文件的一部分或完整文件等,并且在这种情况和许多其他情况下对我们来说效果很好。希望能帮助到你。

回答by mems

I want partial range requests, because I'll be doing realtime transcoding, I can't have the file completely transcoded and available upon request.

我想要部分范围请求,因为我将进行实时转码,我无法将文件完全转码并根据要求提供。

For response which you don't know the full body content yet (you can't guess the Content-Length, live encoding), use chunk encoding:

对于您还不知道完整正文内容的响应(您无法猜测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

Each chunk is send when it's available: when few frames are encoded or when the output buffer is full, 100kB are generated, etc.

每个块在可用时发送:当编码的帧很少或输出缓冲区已满时,会生成 100kB 等。

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

Where 22give the chunk byte length in hexa (0x22 = 34 bytes), ; 3rd chunkis extra chunk infos (optional) and tent...binary....data....chunk3..ais the content of the chunk.

哪里22给出块字节长度(0x22 = 34 字节), ; 3rd chunk是额外的块信息(可选),tent...binary....data....chunk3..a是块的内容。

Then, when the encoding is finished and all chunks are sent, end by:

然后,当编码完成并发送所有块时,结束:

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

Where 0means there not more chunks, followed by zero or more trailer (allowed header fields) defined in the header (Trailer: Expiresand Expires: Wed, 21 Oct 2015 07:28:00 GMTare not required) to provide checksums or digital signatures, etc.

其中0意味着没有更多的块,后跟零个或多个在标头中定义的尾部(允许的标头字段)(Trailer: Expires并且Expires: Wed, 21 Oct 2015 07:28:00 GMT不是必需的)以提供校验和或数字签名等。

Here is the equivalent of the server's response if the file was 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

欲了解更多信息:分块传输编码 — 维基百科预告片 — HTTP | MDN