一、环境说明
- JDK 1.8
- Springboot 2.7.5
- Minio 8.4.5
- Vue3实现的微信公众号网页
二、问题描述
当前项目是基于springboot和vue3的前后端分离架构,前端目前主要是基于H5展示在微信公众号的网页中。在实现视频上传、在线播放时遇到问题:前端同事说苹果手机播放不了视频,刚开始是统一用的video标签,安卓可以正常播放,但是苹果手机就出现“视频播放失败”。前端同事尝试换过video.js、vue3-play、html5 api、avplay、mui-player,都无法解决该问题,于是开始尝试后端寻找解决方案。
三、后端解决思路
第一次,是尝试将视频请求的Content-Disposition由attachment;filename=**改成inline;filename=**,这样视频请求可以直接在浏览器播放,而不是下载。但是依旧没有解决苹果手机视频播放失败的问题。并尝试《iOS无法播放MP4视频文件的解决方案 mp4视频iphone播放不了怎么办》在nginx中加入“add_header Accept-Ranges bytes;”,未能解决该问题。
第二次,分析网上找的视频,将该视频嵌入video是可以在苹果手机播放的。通过观察该请求,发现是有206的响应码,开始研究断点下载,最终在《05.springboot使用minio实现分段下载》、《H5 Video播放视频iOS 断点下载处理》两篇文章的启发下,通过minio分段下载解决了该问题。
四、关键知识点
对于下载请求的响应需要包含以下几个特殊属性:
Accept-Ranges: bytes 接收字节请求
Content-Length: 2 相应长度
Content-Range: bytes 0-1/18494715 bytes后面的空格不能少,0开始位置,1结束位置。“/”后面的是文件总大小长度
Content-Disposition inline表示浏览器直接使用,attachment表示下载,fileName表示下载的文件名
HttpResponse
Status Code: 206 Partial Content 指示请求已成功并且主体包含所请求的数据范围文章来源:https://www.toymoban.com/news/detail-509618.html
五、后端下载方法代码片段文章来源地址https://www.toymoban.com/news/detail-509618.html
public void downloadSlice(String bucketName, String filename, HttpServletResponse response,
HttpServletRequest request) throws Exception{
if (StringUtils.isNotBlank(filename)) {
String range = request.getHeader("Range");
//获取文件信息
StatObjectResponse statObjectResponse = minioClient.statObject(
StatObjectArgs.builder().bucket(bucketName).object(filename).build());
//开始下载位置
long startByte = 0;
//结束下载位置
long endByte = statObjectResponse.size() - 1;
//有range的话
if (StringUtils.isNotBlank(range) && range.contains("bytes=") && range.contains("-")) {
range = range.substring(range.lastIndexOf("=") + 1).trim();
String[] ranges = range.split("-");
try {
//判断range的类型
if (ranges.length == 1) {
//类型一:bytes=-2343
if (range.startsWith("-")) {
endByte = Long.parseLong(ranges[0]);
}
//类型二:bytes=2343-
else if (range.endsWith("-")) {
startByte = Long.parseLong(ranges[0]);
}
}
//类型三:bytes=22-2343
else if (ranges.length == 2) {
startByte = Long.parseLong(ranges[0]);
endByte = Long.parseLong(ranges[1]);
}
} catch (NumberFormatException e) {
startByte = 0;
endByte = statObjectResponse.size() - 1;
}
}
//要下载的长度
long contentLength = endByte - startByte + 1;
//文件类型
String contentType = request.getServletContext().getMimeType(filename);
//解决下载文件时文件名乱码问题
byte[] fileNameBytes = filename.getBytes(StandardCharsets.UTF_8);
filename = new String(fileNameBytes, 0, fileNameBytes.length, StandardCharsets.ISO_8859_1);
//各种响应头设置
//支持断点续传,获取部分字节内容:
response.setHeader("Accept-Ranges", "bytes");
//http状态码要为206:表示获取部分内容
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
response.setContentType(contentType);
response.setHeader("Last-Modified", statObjectResponse.lastModified().toString());
//inline表示浏览器直接使用,attachment表示下载,fileName表示下载的文件名
response.setHeader("Content-Disposition", "inline;filename=" + filename);
response.setHeader("Content-Length", String.valueOf(contentLength));
//Content-Range,格式为:[要下载的开始位置]-[结束位置]/[文件总大小]
response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + statObjectResponse.size());
response.setHeader("ETag", "\"".concat(statObjectResponse.etag()).concat("\""));
try {
GetObjectResponse stream = minioClient.getObject(
GetObjectArgs.builder()
.bucket(statObjectResponse.bucket())
.object(statObjectResponse.object())
.offset(startByte)
.length(contentLength)
.build());
BufferedOutputStream os = new BufferedOutputStream(response.getOutputStream());
byte[] buffer = new byte[1024];
int len;
while ((len = stream.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
os.flush();
os.close();
response.flushBuffer();
} catch (IOException e) {
e.printStackTrace();
}
}
}
到了这里,关于Springboot+Minio通过分片下载解决IOS下H5无法播放视频问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!