如何缓存远程视频

我想播放远程服务器上的视频。所以我写了这个函数。


$remoteFile = 'blabla.com/video_5GB.mp4';

play($remoteFile);

function play($url){

    ini_set('memory_limit', '1024M');

    set_time_limit(3600);

    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($url, false, $conh);

    $out[] = $httap_response_header;

    ob_end_clean();

    array_map("header", $http_response_header);

    readfile($url, false, $cong);

}

上面的功能在播放视频时效果很好。但我不想增加远程服务器的负担


我的问题是如何每 5 小时将视频文件缓存到我的服务器上。如果可能,缓存文件夹包含来自远程视频的小文件(5MB / 10MB)


DIEA
浏览 134回答 1
1回答

繁星淼淼

正如我在评论中提到的,以下代码仅在一小部分 MP4 文件上进行了测试。它可能需要做更多的工作,但它确实满足了您的即时需求。它使用 exec() 生成一个单独的进程,在需要时(即在第一次请求时或 5 小时后)生成缓存文件。每个视频必须有自己的缓存文件夹,因为缓存的块简称为 1、2、3 等。请参阅代码中的其他注释。play.php - 这是用户从浏览器调用的脚本<?phpini_set('memory_limit', '1024M');set_time_limit(3600);$remoteFile = 'blabla.com/video_5GB.mp4';play($remoteFile);/**&nbsp;* @param string $url&nbsp;*&nbsp;* This will serve the video from the remote url&nbsp;*/function playFromRemote($url){&nbsp; ob_start();&nbsp; $opts = array();&nbsp; if(isset($_SERVER['HTTP_RANGE']))&nbsp; {&nbsp; &nbsp; $opts['http']['header'] = "Range: ".$_SERVER['HTTP_RANGE'];&nbsp; }&nbsp; $opts['http']['method'] = "HEAD";&nbsp; $conh = stream_context_create($opts);&nbsp; $opts['http']['method'] = "GET";&nbsp; $cong = stream_context_create($opts);&nbsp; $out[] = file_get_contents($url, false, $conh);&nbsp; $out[] = $http_response_header;&nbsp; ob_end_clean();&nbsp; $fh = fopen('response.log', 'a');&nbsp; if($fh !== false)&nbsp; {&nbsp; &nbsp; fwrite($fh, print_r($http_response_header, true)."\n\n\n\n");&nbsp; &nbsp; fclose($fh);&nbsp; }&nbsp; array_map("header", $http_response_header);&nbsp; readfile($url, false, $cong);}/**&nbsp;* @param string $cacheFolder Directory in which to find the cached chunk files&nbsp;* @param string $url&nbsp;*&nbsp;* This will serve the video from the cache, it uses a "completed.log" file which holds the byte ranges of each chunk&nbsp;* this makes it easier to locate the first chunk of a range request. The file is generated by the cache script&nbsp;*/function playFromCache($cacheFolder, $url){&nbsp; $bytesFrom = 0;&nbsp; $bytesTo = 0;&nbsp; if(isset($_SERVER['HTTP_RANGE']))&nbsp; {&nbsp; &nbsp; //the client asked for a specific range, extract those from the http_range server var&nbsp; &nbsp; //can take the form "bytes=123-567" or just a from "bytes=123-"&nbsp; &nbsp; $matches = array();&nbsp; &nbsp; if(preg_match('/^bytes=(\d+)-(\d+)?$/', $_SERVER['HTTP_RANGE'], $matches))&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; $bytesFrom = intval($matches[1]);&nbsp; &nbsp; &nbsp; if(!empty($matches[2]))&nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; $bytesTo = intval($matches[2]);&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; }&nbsp; //completed log is a json_encoded file containing an array or byte ranges that directly&nbsp; //correspond with the chunk files generated by the cache script&nbsp; $log = json_decode(file_get_contents($cacheFolder.DIRECTORY_SEPARATOR.'completed.log'));&nbsp; $totalBytes = 0;&nbsp; $chunk = 0;&nbsp; foreach($log as $ind => $bytes)&nbsp; {&nbsp; &nbsp; //find the first chunk file we need to open&nbsp; &nbsp; if($bytes[0] <= $bytesFrom && $bytes[1] > $bytesFrom)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; $chunk = $ind + 1;&nbsp; &nbsp; }&nbsp; &nbsp; //and while we are at it save the last byte range "to" which is the total number of bytes of all the chunk files&nbsp; &nbsp; $totalBytes = $bytes[1];&nbsp; }&nbsp; if($bytesTo === 0)&nbsp; {&nbsp; &nbsp; if($totalBytes === 0)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; //if we get here then something is wrong with the cache, revert to serving from the remote&nbsp; &nbsp; &nbsp; playFromRemote($url);&nbsp; &nbsp; &nbsp; return;&nbsp; &nbsp; }&nbsp; &nbsp; $bytesTo = $totalBytes - 1;&nbsp; }&nbsp; //calculate how many bytes will be returned in this request&nbsp; $contentLength = $bytesTo - $bytesFrom + 1;&nbsp; //send some headers - I have hardcoded MP4 here because that is all I have developed with&nbsp; //if you are using different video formats then testing and changes will no doubt be required&nbsp; header('Content-Type: video/mp4');&nbsp; header('Content-Length: '.$contentLength);&nbsp; header('Accept-Ranges: bytes');&nbsp; //Send a header so we can recognise that the content was indeed served by the cache&nbsp; header('X-Cached-Date: '.(date('Y-m-d H:i:s', filemtime($cacheFolder.DIRECTORY_SEPARATOR.'completed.log'))));&nbsp; if($bytesFrom > 0)&nbsp; {&nbsp; &nbsp; //We are sending back a range so it needs a header and the http response must be 206: Partial Content&nbsp; &nbsp; header(sprintf('content-range: bytes %s-%s/%s', $bytesFrom, $bytesTo, $totalBytes));&nbsp; &nbsp; http_response_code(206);&nbsp; }&nbsp; $bytesSent = 0;&nbsp; while(is_file($cacheFolder.DIRECTORY_SEPARATOR.$chunk) && $bytesSent < $contentLength)&nbsp; {&nbsp; &nbsp; $cfh = fopen($cacheFolder.DIRECTORY_SEPARATOR.$chunk, 'rb');&nbsp; &nbsp; if($cfh !== false)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; //if we are fetching a range then we might need to seek the correct starting point in the first chunk we look at&nbsp; &nbsp; &nbsp; //this check will be performed on all chunks but only the first one should need seeking so no harm done&nbsp; &nbsp; &nbsp; if($log[$chunk - 1][0] < $bytesFrom)&nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; fseek($cfh, $bytesFrom - $log[$chunk - 1][0]);&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; //read and send data until the end of the file or we have sent what was requested&nbsp; &nbsp; &nbsp; while(!feof($cfh) && $bytesSent < $contentLength)&nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; $data = fread($cfh, 1024);&nbsp; &nbsp; &nbsp; &nbsp; //check we are not going to be sending too much back and if we are then truncate the data to the correct length&nbsp; &nbsp; &nbsp; &nbsp; if($bytesSent + strlen($data) > $contentLength)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; $data = substr($data, 0, $contentLength - $bytesSent);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; $bytesSent += strlen($data);&nbsp; &nbsp; &nbsp; &nbsp; echo $data;&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; fclose($cfh);&nbsp; &nbsp; }&nbsp; &nbsp; //move to the next chunk&nbsp; &nbsp; $chunk ++;&nbsp; }}function play($url){&nbsp; //I have chosen a simple way to make a folder name, this can be improved any way you need&nbsp; //IMPORTANT: Each video must have its own cache folder&nbsp; $cacheFolder = sha1($url);&nbsp; if(!is_dir($cacheFolder))&nbsp; {&nbsp; &nbsp; mkdir($cacheFolder, 0755, true);&nbsp; }&nbsp; //First check if we are currently in the process of generating the cache and so just play from remote&nbsp; if(is_file($cacheFolder.DIRECTORY_SEPARATOR.'caching.log'))&nbsp; {&nbsp; &nbsp; playFromRemote($url);&nbsp; }&nbsp; //Otherwise check if we have never completed the cache or it was completed 5 hours ago and if so spawn a process to generate the cache&nbsp; elseif(!is_file($cacheFolder.DIRECTORY_SEPARATOR.'completed.log') || filemtime($cacheFolder.DIRECTORY_SEPARATOR.'completed.log') + (5 * 60 * 60) < time())&nbsp; {&nbsp; &nbsp; //fork the caching to a separate process - the & echo $! at the end causes the process to run as a background task&nbsp; &nbsp; //and print the process ID returning immediately&nbsp; &nbsp; //The cache script can be anywhere, pass the location to sprintf in the first position&nbsp; &nbsp; //A base64 encoded url is passed in as argument 1, sprintf second position&nbsp; &nbsp; $cmd = sprintf('php %scache.php %s & echo $!', __DIR__.DIRECTORY_SEPARATOR, base64_encode($url));&nbsp; &nbsp; $pid = exec($cmd);&nbsp; &nbsp; //with that started we need to serve the request from the remote url&nbsp; &nbsp; playFromRemote($url);&nbsp; }&nbsp; else&nbsp; {&nbsp; &nbsp; //if we got this far then we have a completed cache so serve from there&nbsp; &nbsp; playFromCache($cacheFolder, $url);&nbsp; }}cache.php - 该脚本将由 play.php 通过 exec() 调用<?php//This script expects as argument 1 a base64 encoded urlif(count($argv)!==2){&nbsp; die('Invalid Request!');}$url = base64_decode($argv[1]);//make sure to use the same method of obtaining the cache folder name as the main play script//or change the code to pass it in as an argument$cacheFolder = sha1($url);if(!is_dir($cacheFolder)){&nbsp; die('Invalid Arguments!');}//double check it is not already runningif(is_file($cacheFolder.DIRECTORY_SEPARATOR.'caching.log')){&nbsp; die('Already Running');}//create a file so we know this has started, the file will be removed at the end of the scriptfile_put_contents($cacheFolder.DIRECTORY_SEPARATOR.'caching.log', date('d/m/Y H:i:s'));//get rid of the old completed logif(is_file($cacheFolder.DIRECTORY_SEPARATOR.'completed.log')){&nbsp; unlink($cacheFolder.DIRECTORY_SEPARATOR.'completed.log');}$bytesFrom = 0;$bytesWritten = 0;$totalBytes = 0;//this is the size of the chunk files, currently 10MB$maxSizeInBytes = 10 * 1024 * 1024;$chunk = 1;//open the url for binary reading and first chunk for binary writing$fh = fopen($url, 'rb');$cfh = fopen($cacheFolder.DIRECTORY_SEPARATOR.$chunk, 'wb');if($fh !== false && $cfh!==false){&nbsp; $log = array();&nbsp; while(!feof($fh))&nbsp; {&nbsp; &nbsp; $data = fread($fh, 1024);&nbsp; &nbsp; fwrite($cfh, $data);&nbsp; &nbsp; $totalBytes += strlen($data); //use actual length here&nbsp; &nbsp; $bytesWritten += strlen($data);&nbsp; &nbsp; //if we are on or passed the chunk size then close the chunk and open a new one&nbsp; &nbsp; //keeping a log of the byte range of the chunk&nbsp; &nbsp; if($bytesWritten>=$maxSizeInBytes)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; $log[$chunk-1] = array($bytesFrom,$totalBytes);&nbsp; &nbsp; &nbsp; $bytesFrom = $totalBytes;&nbsp; &nbsp; &nbsp; fclose($cfh);&nbsp; &nbsp; &nbsp; $chunk++;&nbsp; &nbsp; &nbsp; $bytesWritten = 0;&nbsp; &nbsp; &nbsp; $cfh = fopen($cacheFolder.DIRECTORY_SEPARATOR.$chunk, 'wb');&nbsp; &nbsp; }&nbsp; }&nbsp; fclose($fh);&nbsp; $log[$chunk-1] = array($bytesFrom,$totalBytes);&nbsp; fclose($cfh);&nbsp; //write the completed log. This is a json encoded string of the chunk byte ranges and will be used&nbsp; //by the play script to quickly locate the starting chunk of a range request&nbsp; file_put_contents($cacheFolder.DIRECTORY_SEPARATOR.'completed.log', json_encode($log));&nbsp; //finally remove the caching log so the play script doesn't think the process is still running&nbsp; unlink($cacheFolder.DIRECTORY_SEPARATOR.'caching.log');}
打开App,查看更多内容
随时随地看视频慕课网APP