当前位置: 首页 > 后端技术 > PHP

视频播放、断点续传、多线程下载基础:Range

时间:2023-03-30 03:41:30 PHP

实现视频播放功能,以及大文件的下载操作等。我们只能使用echofile-content来下载文件。如果用视频文件来播放,就很难处理了。具体表现为播放视频时无法调整进度条,而如果是视频网站,对于视频只放在可以直接访问的目录下,那么这个视频就相当于是公开的,并且有谈不上贵宾。本文使用Range提供视频播放、续传、多线程下载等技术,依赖于RangeHTTP协议的实现,支持以Range形式指定获取资源的具体偏移数据。语法格式如下。详情参考Range:MDN:Range:=-Range:=-Range:=-,-Range:=-,-,-只能是bytes(暂时),指定单位一个整数,以特定单位表示范围的起始值。一个整数,以特定单位表示范围的结束值。此值是可选的,如果不存在,则表示范围扩展到文档的末尾。例如:获取0-100字节的数据和120到末尾的数据Range:bytes=0-100,120-Content-Range该头指定响应数据的内容范围,语法格式如下:Content-范围:-/Content-Range:-/*Content-Range:*/说明:数据范围使用的单位。通常是字节。一个整数,表示以给定单位表示的范围的起始值。一个整数,表示以给定单位表示的范围的结束值。整个文件的大小(如果不知道大小,用“*”表示)例如:Content-Range:bytes200-1000/67589MultipleRangeresponses网上没有直观的提到,但是HTTP协议支持多个Ranges,返回的内容信息格式如下:GEThttp://suda.dev.dx/fileHTTP/1.1Host:suda.dev.dxConnection:keep-aliveAccept-Encoding:identity;q=1,*;q=0User-Agent:Mozilla/5.0(WindowsNT10.0;WOW64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/73.0.3679.0Safari/537.36Accept:*/*Referer:http://test.dev.dx/video.htmlAccept-Language:zh-CN,zh;q=0.9Cookie:php_session=8eec314af63d994c2eeb1baca7487332Range:bytes=0-1,2-3HTTP/1.1206PartialContentDate:Sun,10Mar201909:36:59GMTServer:Apache/2.4.23(Win32)OpenSSL/1.0.2jmod_fcgid/2.3.9X-Powered-By:PHP/7.2.1Accept-Ranges:bytesContent-Length:220Keep-Alive:timeout=5,max=100Connection:Keep-AliveContent-Type:多部分/byteranges;=multiple_range_ss6bBSB6IlLi0YPpP8rK3g==--multiple_range_ss6bBSB6IlLi0YPpP8rK3g==Content-Type:video/mp4Content-Range:bytes0-1/132006090<...somedata...>--multiple_range_ss6bBSB6IlLi0YPpP8rK3g==Content-Type:video/mp4Content-Range:bytes2-3/132006090<...somedata...>Accept-Rangs服务器响应,告诉浏览器是否支持Range,语法:Accept-Ranges:bytesAccept-Rangs:nonenone不支持任何范围请求单元,因为相当于不返回这个header,所以很少用到,但是有些浏览器,比如IE9,会根据这个禁用或者去掉下载管理器的暂停按钮标头。字节范围请求的单位是字节(bytes)。码本的实现代码可以简单理解为伪代码,一些依赖关系没有给出。修改后即可在Swoole环境中使用。使用代码:onRequest($request,$response);}}依赖代码:file=$fileinstanceofSplFileObject?$文件:新的SplFileObject($文件);$this->mime=MimeType::getMimeType($this->file->getExtension());}/***处理文件请求**@param\suda\framework\Request$request*@param\suda\framework\Response$response*@returnvoid*/publicfunctiononRequest(Request$request,Response$response){$ranges=$this->getRanges($request);$response->setHeader('accept-ranges','bytes');如果($ranges===false||$request->getMethod()!=='GET'){$response->status(400);}elseif($ranges===null){$response->sendFile($this->file->getRealPath());}elseif(count($ranges)===1){$response->status(206);$range=$ranges[0];$response->setHeader('内容类型',$this->mime);$response->setHeader('继续输入范围',$this->getRangeHeader($range));$this->sendFileByRange($response,$range);}else{$response->status(206);$this->sendMultipleFileByRange($response,$ranges);}}/***发送多范围**@param\suda\framework\Response$response*@paramarray$ranges*@returnvoid*/protectedfunctionsendMultipleFileByRange(Response$response,array$ranges){$separates='multiple_range_'.base64_encode(\md5(\uniqid(),true));$response->setHeader('content-type','multipart/byteranges;boundary='.$separates);foreach($rangesas$range){$response->write('--'.$separates."\r\n");$this->sendMultipleRangePart($response,$range);$this->sendFileByRange($response,$range);$response->write("\r\n");}}/***发送范围数据**@param\suda\framework\Response$response*@param数组$range*@returnvoid*/protectedfunctionsendFileByRange(Response$response,array$range){$response->write(newDataStream($this->file->getRealPath(),$range['开始'],$range['结束']-$range['开始']+1));}/***获取范围描述**@param\suda\framework\Request$request*@returnarray|bool|null*/protectedfunctiongetRanges(Request$request){$ranges=$this->parseRangeHeader($request);如果(\is_array($ranges)){返回$this->parseRanges($ranges);}elseif($ranges===false){返回false;}返回空值;}/***写范围头**@param\suda\framework\Response$response*@paramarray$range*@returnvoid*/protectedfunctionsendMultipleRangePart(Response$response,array$range){$response->write('Content-Type:'.$this->mime."\r\n");$r响应->write('Content-Range:'.$this->getRangeHeader($range)."\r\n\r\n");}/***生成Range头**@paramarray$range*@returnstring*/protectedfunctiongetRangeHeader(array$range):string{returnsprintf('bytes%d-%d/%d',$range['开始'],$range['结束'],$this->file->getSize());}/***获取范围描述**@param\suda\framework\Request$request*@returnarray|bool|null*/protectedfunctionparseRangeHeader(Request$request){$range=$request->getHeader('range',无效的);如果(is_string($range)){$range=trim($range);如果(\strpos($range,'bytes=')!==0){返回false;}$rangesFrom=\substr($range,strlen('bytes='));返回\explode(',',$rangesFrom);}返回空值;}/***处理范围**@paramarray$ranges*@returnarray|bool*/pr受保护的函数parseRanges(array$ranges){$range=[];foreach($rangesas$value){if(($r=$this->parseRange($value))!==null){$range[]=$r;}else{返回错误;}}返回$范围;}/***处理范围**@paramstring$range*@returnarray*/protectedfunctionparseRange(string$range):?array{$range=trim($range);如果(strrpos($range,'-')===strlen($range)-1){return['start'=>intval(\rtrim($range,'-')),'end'=>$this->file->getSize()-1,];}elseif(\strpos($range,'-')!==false){list($start,$end)=\explode('-',$range,2);返回['开始'=>intval($start),'end'=>intval($end)];}返回空值;}}参考文献https://tools.ietf.org/html/r...https://tools.ietf.org/html/r...https://developer.mozilla.org...https://developer.mozilla.org...完整代码