java实现video标签视频流播放
问题:
在遇到video标签播放后端视频源时问题。直接返回文件流的话 video需要将文件整个下载一次才会播放。这样如果小文件没有问题。如果文件大的话就比较恶心了。
解决方案:通过模拟video标签默认的range bytes规范方法分段获取视频信息。
video标签是通过请求头带上 Range: bytes=1179648- 意思是:告诉服务端我要1179648字节开始之后的内容
然后服务端响应头返回:
accept-ranges: bytes #告诉浏览器我响应的数据的范围是字节形式的。
Content-Length: 286233105 #这次响应内容的大小。
Content-Range: bytes 1179648-287412752/287412753 #响应内容的范围。287412753 是整个视频总大小。文章来源:https://www.toymoban.com/news/detail-628999.html
实现代码仅供参考主要是理解思想:文章来源地址https://www.toymoban.com/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.如果没有这个协议则直接走流下载
if (StringUtils.isEmpty(header)){
fileService.downloadFileStream(env,app,id,response);
return;
}
//3.获取数据的数据的范围
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根据协议设置请求头
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.写数据
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("写数据报错");
}
}
private void writeDataSharding(String env,String app,ShardingFileInfo fi, long requestStart, long requestEnd, BufferedOutputStream bos) throws Exception {
//1.计算当前字节请求范围在那些分片上。
int startChuck= (int) ((requestStart+1)%chunkSize==0?(requestStart+1)/chunkSize:((requestStart+1)/chunkSize)+1);
int endChuck = (int) ((requestEnd+1)/chunkSize)+1;
//2.将需要请求的分片数据下载下来 放入一个buffer 这里为什么要用buffer 因为这种请求是很频繁的使用堆内存 比使用堆外内存消耗大。因为堆内存要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的那个字节开始读 以及这次读取字节的范围
int startLen=(int) ((requestStart) % chunkSize);
int len= (int) ((requestEnd - requestStart)+ 1);
//4.将需要读取的字节写回去。并且释放掉这次的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);
}
到了这里,关于java实现video标签视频流播放的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!