其实断点续传和视频选择从任一时间开始播放都很简单,本质上都是服务端读取文件的某一个范围返回给客户端,而服务端怎么知道客户端要从哪读到哪,客户端如何知道服务端是否支持断点续传,这就需要了解HTTP的“Range”约定。
客户端首次访问资源时,在请求头中携带range:
#意味读取从0到文件尾的范围
range: bytes=0-
Range头域可以请求实体的一个或者多个子范围,例如:
表示头500个字节:bytes=0-499
表示第二个500字节:bytes=500-999
表示最后500个字节:bytes=-500
表示500字节以后的范围:bytes=500-
第一个和最后一个字节:bytes=0-0,-1
同时指定几个范围:bytes=500-600,601-999
如果服务端支持range,那么会先返回206状态码,以及如下响应头,然后再按照Content-Range响应头里设置的范围把文件输出给客户端。
//文件的MIMEType
response.setContentType(MIMEType.getMIMEType("mp4"));
//文件总大小,这里总大小是2000byte
response.setHeader("Content-Length", String.valueOf(2000));
//本次返回的范围与文件总大小,这里是从0到100byte,总大小未2000
response.setHeader("Content-Range", "bytes " + 0 + "-" + 100 + "/" + 2000);
//写死的内容
response.setHeader("Access-Control-Allow-Origin", "*");
//写死的内容
response.setHeader("Accept-Ranges", "bytes");
//206状态码
response.setStatus(HttpServletresponseonse.SC_PARTIAL_CONTENT);
代码如下所示
public void fileOutputStream(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//目标文件的路径
String fileAllPath = (String) request.getAttribute("filePath");
//文件扩展名
int index = fileAllPath.lastIndexOf(".");
String suffix = fileAllPath.substring(index + 1);
//检查是否是Range请求
if (request.getHeader("Range") != null) {
//读取文件
File targetFile = new File(fileAllPath);
BufferedInputStream in = new BufferedInputStream(new FileInputStream(targetFile));
Long fileSize = targetFile.length();
//解析Range
Map<String, Integer> range = this.analyzeRange(request.getHeader("Range"), fileSize.intValue());
//设置响应头
response.setContentType(MIMEType.getMIMEType(suffix));
response.setHeader("Content-Length", String.valueOf(fileSize.intValue()));
response.setHeader("Content-Range", "bytes " + range.get("startByte") + "-" + range.get("endByte") + "/" + fileSize.intValue());
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Accept-Ranges", "bytes");
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
//开始输出
OutputStream os = response.getOutputStream();
int length = range.get("endByte") - range.get("startByte");
System.out.println("length:" + length);
byte[] buffer = new byte[length < 1024 ? length : 1024];
in.skip(range.get("startByte"));
int i = in.read(buffer);
length = length - buffer.length;
while (i != -1) {
os.write(buffer, 0, i);
if (length <= 0) {
break;
}
i = in.read(buffer);
length = length - buffer.length;
}
os.flush();
//关闭
os.close();
in.close();
return;
}
}
/**
* 解析range,解析出起始byte(startByte)和结束byte(endBytes)
*
* @param range 请求发来的range
* @param fileSize 目标文件的大小
* @return
*/
private Map<String, Integer> analyzeRange(String range, Integer fileSize) {
String[] split = range.split("-");
Map<String, Integer> result = new HashMap<>();
if (split.length == 1) {
//从xxx长度读取到结尾
Integer startBytes = new Integer(range.replaceAll("bytes=", "").replaceAll("-", ""));
result.put("startByte", startBytes);
result.put("endByte", fileSize - 1);
} else if (split.length == 2) {
//从xxx长度读取到yyy长度
Integer startBytes = new Integer(split[0].replaceAll("bytes=", "").replaceAll("-", ""));
Integer endBytes = new Integer(split[1].replaceAll("bytes=", "").replaceAll("-", ""));
result.put("startByte", startBytes);
result.put("endByte", endBytes > fileSize ? fileSize : endBytes);
} else {
logger.info("未识别的range:", range);
}
return result;
}
上述代码可以做文件的断点续传,也可以做视频在线播放,不过需要前端配合一个H5播放器