java實現(xiàn)video標(biāo)簽視頻流播放
問題:
在遇到video標(biāo)簽播放后端視頻源時問題。直接返回文件流的話 video需要將文件整個下載一次才會播放。這樣如果小文件沒有問題。如果文件大的話就比較惡心了。
解決方案:通過模擬video標(biāo)簽?zāi)J(rèn)的range bytes規(guī)范方法分段獲取視頻信息。
video標(biāo)簽是通過請求頭帶上 Range: bytes=1179648- 意思是:告訴服務(wù)端我要1179648字節(jié)開始之后的內(nèi)容
然后服務(wù)端響應(yīng)頭返回:
accept-ranges: bytes #告訴瀏覽器我響應(yīng)的數(shù)據(jù)的范圍是字節(jié)形式的。
Content-Length: 286233105 #這次響應(yīng)內(nèi)容的大小。
Content-Range: bytes 1179648-287412752/287412753 #響應(yīng)內(nèi)容的范圍。287412753 是整個視頻總大小。文章來源:http://www.zghlxwxcb.cn/news/detail-628999.html
實現(xiàn)代碼僅供參考主要是理解思想:文章來源地址http://www.zghlxwxcb.cn/news/detail-628999.html
public void videoStream(String env,String app,String id, HttpServletRequest request, HttpServletResponse response) throws Exception {
//1.拿到文件引用
FileInfo fi = getCacheFileInfo(env, app, id);
if (fi==null){
log.info("找不到文件:"+getKey(env,app,id));
return;
}
String header = request.getHeader(HttpHeaders.RANGE);
//2.如果沒有這個協(xié)議則直接走流下載
if (StringUtils.isEmpty(header)){
fileService.downloadFileStream(env,app,id,response);
return;
}
//3.獲取數(shù)據(jù)的數(shù)據(jù)的范圍
String[] rangeArr = header.replace("bytes=","").split("-");
long fileLength = getFileLength(fi);
long requestStart = getRequestStart(rangeArr);
long requestEnd = getRequestEnd(rangeArr);
if (requestEnd==0){
requestEnd=requestStart+ chunkSize -1;
}
if (requestEnd>=fileLength){
requestEnd=fileLength-1;
}
//4根據(jù)協(xié)議設(shè)置請求頭
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
response.setHeader(HttpHeaders.CONTENT_TYPE, "video/mp4");
long length = requestEnd - requestStart + 1;
if (requestStart==0){
length=fileLength;
}
response.setHeader(HttpHeaders.CONTENT_LENGTH, "" + length);
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + requestStart + "-" + (requestStart==0?fileLength-1:requestEnd) + "/" + fileLength);
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
//5.寫數(shù)據(jù)
writeData(env, app,fi,requestStart,requestEnd,response);
}
private void writeData(String env,String app ,FileInfo fi, long requestStart, long requestEnd, HttpServletResponse response) {
try (BufferedOutputStream bos=new BufferedOutputStream(response.getOutputStream())){
//這個是下載普通文件
if (fi instanceof StandaloneFileInfo){
writeDataStandalone((StandaloneFileInfo) fi,requestStart,requestEnd,bos);
//這個是下載分片文件
}else if ( fi instanceof ShardingFileInfo){
writeDataSharding(env,app,(ShardingFileInfo) fi,requestStart,requestEnd,bos);
}
}catch (Exception e){
log.error("寫數(shù)據(jù)報錯");
}
}
private void writeDataSharding(String env,String app,ShardingFileInfo fi, long requestStart, long requestEnd, BufferedOutputStream bos) throws Exception {
//1.計算當(dāng)前字節(jié)請求范圍在那些分片上。
int startChuck= (int) ((requestStart+1)%chunkSize==0?(requestStart+1)/chunkSize:((requestStart+1)/chunkSize)+1);
int endChuck = (int) ((requestEnd+1)/chunkSize)+1;
//2.將需要請求的分片數(shù)據(jù)下載下來 放入一個buffer 這里為什么要用buffer 因為這種請求是很頻繁的使用堆內(nèi)存 比使用堆外內(nèi)存消耗大。因為堆內(nèi)存要gc
ByteBuf buffer= ByteBufAllocator.DEFAULT.heapBuffer(chunkSize);
List<String> shards = fi.getShardingMappingIds();
for (int i = startChuck-1; i <shards.size()&&i<endChuck; i++) {
StandaloneFileInfo info = (StandaloneFileInfo) fileService.downloadFile(env, app, shards.get(i));
buffer.writeBytes(info.getData());
}
//3.計算具體從buff的那個字節(jié)開始讀 以及這次讀取字節(jié)的范圍
int startLen=(int) ((requestStart) % chunkSize);
int len= (int) ((requestEnd - requestStart)+ 1);
//4.將需要讀取的字節(jié)寫回去。并且釋放掉這次的buffer。
byte[] data=new byte[buffer.writerIndex()];
buffer.getBytes(buffer.readerIndex(),data);
bos.write(data,startLen,len);
buffer.release();
}
private void writeDataStandalone(StandaloneFileInfo sfi,long requestStart, long requestEnd, OutputStream sos) throws IOException {
byte[] bytes = Arrays.copyOfRange(sfi.getData(), (int) requestStart, (int) requestEnd);
sos.write(bytes);
}
到了這里,關(guān)于java實現(xiàn)video標(biāo)簽視頻流播放的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!