java大视频在线预览(支持断点下载)
1.说明
大视频的在线预览,如果不支持断点下载,将无法在苹果手机上播放,同时不支持进度条拖动.
之所以这样,是因为视频文件太大了,通过二进制流向浏览器传输时,整个文件尚未传输完成时,会被浏览器强制关闭流,不再接收,等缓存播放到一定程度时,浏览器会再次向后端请求视频文件,同时附带range参数,指定获取数据范围,后端需支持对range参数的处理.
2.代码
@ResponseBody
@GetMapping(value = "/preview")
@Operation(summary = "在线预览", description = "文件在浏览器中预览")
public void Preview(@RequestParam String fileInfoId, HttpServletRequest request, HttpServletResponse response) throws Exception {
//获取存储文件
var fileInfo = fileInfoService.Get(fileInfoId);
if (fileInfo == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found!");
return;
}
//设置页面缓存
if (MisFileType.PICTURE.equals(fileInfo.getFile_type()) || MisFileType.TEXT.equals(fileInfo.getFile_type())) {
response.setHeader("Cache-Control", "max-age=60");//页面缓存时间60秒
}
//断点下载
ResumeDownload(request, response, fileInfo, "inline");
}
/**
* 支持断点重新下载文件
*
* @param fileInfo 文件
* @param disposition 下载方式 inline:内嵌 attachment:附件
*/
private static void ResumeDownload(HttpServletRequest request, HttpServletResponse response,
FileInfoOutput fileInfo, String disposition) throws IOException {
//获取存储文件
String storageFilePath = FileConfig.Path + File.separator + fileInfo.getFile_path();
File storageFile = new File(storageFilePath);
if (!StringUtils.hasLength(storageFilePath) || !storageFile.exists()) {//相对路径未找到文件
storageFilePath = fileInfo.getFile_absolute_path();//根据绝对路径寻找文件
storageFile = new File(storageFilePath);
if (!StringUtils.hasLength(storageFilePath) || !storageFile.exists()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found!");
return;
}
}
//推断类型
String mimeType = Files.probeContentType(storageFile.toPath());
if (!StringUtils.hasLength(mimeType)) {
URL url = new URL("file:///" + storageFilePath);
mimeType = url.openConnection().getContentType();
}
//下载开始位置
long startByte = 0;
//下载结束位置
long endByte = storageFile.length() - 1;
//获取下载范围
String range = request.getHeader("range");
if (range != null && range.contains("bytes=") && range.contains("-")) {
range = range.substring(range.lastIndexOf("=") + 1).trim();
String[] rangeArray = range.split("-");
if (rangeArray.length == 1) {
//Example: bytes=1024-
if (range.endsWith("-")) {
startByte = Long.parseLong(rangeArray[0]);
} else { //Example: bytes=-1024
endByte = Long.parseLong(rangeArray[0]);
}
}
//Example: bytes=2048-4096
else if (rangeArray.length == 2) {
startByte = Long.parseLong(rangeArray[0]);
endByte = Long.parseLong(rangeArray[1]);
}
}
long contentLength = endByte - startByte + 1;
//HTTP 响应头设置
//断点续传,HTTP 状态码必须为 206,否则不设置,如果非断点续传设置 206 状态码,则浏览器无法下载
if (range != null) {
log.trace("断点下载range:{},总大小:{},{}({})", range, storageFile.length(), fileInfo.getFile_name(), fileInfo.getId());
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
}
if (StringUtils.hasLength(mimeType)) {
response.setContentType(mimeType);
response.setHeader("Content-Type", mimeType);
}
response.setHeader("Content-Length", String.valueOf(contentLength));
response.setHeader("Accept-Ranges", "bytes");
//Content-Range: 下载开始位置-下载结束位置/文件大小
response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + storageFile.length());
//Content-disposition: inline; filename=xxx.xxx 表示浏览器内嵌显示该文件
response.setHeader("Content-Disposition", disposition + "; filename=" + URLEncoder.encode(fileInfo.getFile_name(), StandardCharsets.UTF_8));
//传输文件流
BufferedOutputStream outputStream = null;
RandomAccessFile randomAccessFile = null;
//已传送数据大小
long transmittedLength = 0;
try {
//以只读模式设置文件指针偏移量
randomAccessFile = new RandomAccessFile(storageFile, "r");
randomAccessFile.seek(startByte);
outputStream = new BufferedOutputStream(response.getOutputStream());
byte[] buff = new byte[4096];
int len;
while (transmittedLength < contentLength && (len = randomAccessFile.read(buff)) != -1) {
outputStream.write(buff, 0, len);
transmittedLength += len;
}
outputStream.flush();
response.flushBuffer();
log.trace("下载完毕:{}-{},下载量:{},总大小:{},{}({})", startByte, endByte, transmittedLength,
storageFile.length(), fileInfo.getFile_name(), fileInfo.getId());
} catch (IOException e) {
if (StringUtils.hasLength(range)) {
response.setHeader("Content-Range", "bytes " + startByte + "-" + (startByte + transmittedLength) + "/" + storageFile.length());
log.trace("断点下载完毕:{}-{},下载量:{},总大小:{},{}({})", startByte, endByte, transmittedLength, storageFile.length(),
fileInfo.getFile_name(), fileInfo.getId());
} else {
log.info("下载停止:{}-{},下载量:{},总大小:{},{}({})", startByte, endByte, transmittedLength, storageFile.length(),
fileInfo.getFile_name(), fileInfo.getId());
}
} finally {
try {
if (randomAccessFile != null) {
randomAccessFile.close();
}
} catch (IOException e) {
log.error("下载异常," + fileInfo.getId(), e);
}
}
}