苹果浏览器无法边下边播MP4(谷歌浏览器可以)
一、问题提出
项目开发过程中遇到一个问题:
>基于webkit内核的浏览器H5的Video标签(获取android手机,一般也是webkit浏览器)可以正常播放MP4文件,但是基于苹果操作系统的safari浏览器或者苹果微信小程序内置浏览器都无法播放远程后台的MP4文件。
发现问题:
为了能发现android端与IOS端微信小程序内置浏览器的不同,通过对比两个浏览器发送给后台的包,可以发现如下端倪:
android浏览器:
苹果浏览器:
对比之后发现没有什么区别,最后发现问题并不是没有区别,而是真实的IOS系统或者IOS微信公众号内置浏览器发出来的包与上述IOS模拟器发出来的请求时不一样的!为了还原真相,我特意搭建了一个“黑苹果操作系统”模拟IOS浏览器发出的请求。得到如下结果:
以上可以看到android或webkit与IOS苹果浏览器播放的区别:Range字段,通过查询可以知道两者区别在于:
> android或webkit播放文件是一次请求到所有的数据,然后下载后进行播放(这对于移动手机来说会消耗很大流量),苹果针对这个问题进行了改进,所以才有了分段请求数据的问题,也就是我们常说的http1.1中的断点续传。
二、问题解决
知道两者的区别之后,我们其实在后台支持两种请求协议即可,一种是不包含Range的请求,一种是包含Range的分段请求:
我后台节后如下:
```
@ApiOperation("文件下载")
@GetMapping("/download")
@ApiImplicitParams({@ApiImplicitParam(paramType = "query", dataType = "String", name = "path", value = "文件路径", required = true)})
public void downLoad(@RequestParam(value="path", required=true) String path,
@RequestHeader(value="range", required=false) String range,
HttpServletRequest request,
HttpServletResponse response) throws IOException {
try {
if (CheckUtil.isNull(path)) {
response.sendError(-1, "参数不合法");
}
printHeaders(request);
// 端点续传:如果是苹果是分段请求,如果是android或webkit则直接下载整个文件
int start = 0;
int end = -1;
if (!CheckUtil.isNull(range)){
// bytes=0-1 or bytes=0-
String v = range.trim().split("=")[1];
String[] range_size = v.split("-");
if (range_size.length >= 1) {
start = Integer.valueOf(range_size[0]);
}
if (range_size.length >= 2) {
end = Integer.valueOf(range_size[1]);
}
}
System.out.println("start:" + start + " end:" + end);
if (path.startsWith("group")) {
downloadFromFDFS(path, start, end, response);
} else {
downloadFromHDFS(path, start, end, response);
}
} catch (Exception e) {
log.error("下载文件出错:{}", e.getMessage());
}
}
```
由于我后台的数据存储包括两种方式:基于FastDFS的图片存储和基于HDFS的大文件(如视频附件等)存储,这里的视频主要就存储在HDFS中,接口中我们主要分析了请求头参数Range,得到请求的数据的start和请求结束end,如果包含Range我们就发送range指定的内容给前端,如果没有指定我们就发送0-end的所有文件数据给前端(一次性),所以我们主要看HDFS下载接口即可:
```
/**
* @功能描述: 从HDFS中下载文件
* @编写作者: lixx2048@163.com
* @开发日期: 2020年4月4日
* @历史版本: V1.0
* @参数说明:
*/
private boolean downloadFromHDFS(String path,int start, int end, HttpServletResponse response) {
String fileName = path.substring(path.lastIndexOf('/')+1);
String extName = FilenameUtils.getExtension(fileName);
// 创建文件
HdfsProxy hdfsProxy = new HdfsProxy(HadoopConfig);
hdfsProxy.open();
// 写文件
ServletOutputStream out = null;
FSDataInputStream in = null;
try {
// 获取输出流
out = response.getOutputStream();
// 设置相应类型application/octet-stream(注:applicatoin/octet-stream 为通用,一些其它的类型苹果浏览器下载内容可能为空)
response.setContentType(getContentTypeByExtName(extName));
// 设置头信息 Content-Disposition为属性名 附件形式打开下载文件 指定名称 为 设定的fileName
//response.setHeader("Content-Disposition", "attachment;inline;filename=" + URLEncoder.encode(fileName, "UTF-8"));
response.setHeader("Content-Disposition", "filename=" + URLEncoder.encode(fileName, "UTF-8"));
response.setHeader("Accept-Ranges", "bytes");
long size = hdfsProxy.getFileSize(path);
in = hdfsProxy.Open(path);
if (null == in){
return false;
}
// webkit可以不设置文件大小
int need = 1024*1024;
if (end > 0){
need = end - start + 1;
response.setHeader("Content-Length", String.valueOf(need));
} else {
response.setHeader("Content-Length", String.valueOf(size));
}
byte buffer[] = new byte[need];
in.seek(start);
int total = 0;
boolean toFileEnd = false;
while (true) {
int read = in.read(buffer);
if (read <= 0) {
toFileEnd = true;
break;
}
out.write(buffer, 0, read);
total += read;
if (end > 0 && total >= end + 1) {
break;
}
}
// 苹果分段请求(HTTP续传方式)
if (end > 0){
// 未达到文件末尾
if(!toFileEnd){
response.setStatus(HttpStatus.SC_PARTIAL_CONTENT);
}
String value = String.format("bytes %d-%d/%d", start, end, size);
response.setHeader("Content-Range", value);
}
/*
// 从HDFS中下载文件
ServletOutputStream out2 = out;
status = hdfsProxy.download(path, new ReadProgress() {
@Override
public void progress(byte[] buffer, long size) {
try {
out2.write(buffer, 0, (int)size);
} catch (IOException e) {
e.printStackTrace();
}
}
});
*/
} catch (Exception e) {
log.error("读取HDFS文件文件异常:{}", e.getMessage());
} finally {
if(null != out) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (in != null) {
hdfsProxy.close(in);
}
hdfsProxy.close();
}
return false;
}
```
从以上代码中我们可以看到如果是苹果的分段请求,我们需要注意一下几点:
- 我们在回复的请求头中多了“Content-Range”字段,表名本次请求我回复的内容大小以及数据的**总长度**(这个很关键,苹果浏览器分段请求前发送的分段请求为0-1,目的就是探测到文件的总长度以便后续进行播放控制)
- 分段请求回复的数据为真实的分段数据,如请求10-20的分段,我们直接定位到文件偏移量为10的位置然后发送20-10+1=11个字节数据
- Content-Length指定为本次真实发送的数据长度
- 如果分段请求没有到文件末尾,我们回复的http状态码为206(表示只返回部分数据),如果最终读取达到文件末尾则http状态码返回200(默认)
经过以上修改后,后端代码就支持android和苹果浏览器视频播放了!
技术交流群:961179337
微信交流:lixiang1653
邮箱:lixx2048@163.com
苹果浏览器无法边下边播MP4(谷歌浏览器可以)相关推荐
- html实现边下边播mp4,WebTorrent:一款可边下边播磁力链接的播放器
网页版可以直接嵌入网页,相当于一个p2p的在线播放器,在项目官网首页就可以体验. 体验网页版WebTorrent:https://webtorrent.io/ 桌面版为WebTorrent Deskt ...
- html实现边下边播mp4,MP4Info: 不用流媒体也可以简单实现MP4等视频的边下边播功能。...
/** * MP4Info 示例程序 * @author TJbaobao *=====================原理说明:http://blog.csdn.net/u013640004/art ...
- Android 视频边下边播,MP4头信息在后调整头信息
mp4视频有两种格式,一种视频头信息在前,这种直接可以先缓存头信息,然后直接边下边播,还有一种是头信息在最后,这种情况下则需要处理mp4的头信息,并调整mp4的格式. mp4文件的格式如下图 图1 从 ...
- 关于Android HTTP边下边播
本文简单地分享一下在Android平台做HTTP边下载边播放的一些经验,希望对初学者有所帮助. 1. 为什么播放器在播放视频文件的时候,都知道该怎么去解码.该以怎样的时间间隔去显示每一帧呢? 因为无论 ...
- 迅雷 android下载地址 http,Android HTTP边下边播
本文简单地分享一下在Android平台做HTTP边下载边播放的一些经验,希望对初学者有所帮助. 1. 为什么播放器在播放视频文件的时候,都知道该怎么去解码.该以怎样的时间间隔去显示每一帧呢? 因为无论 ...
- Android HTTP边下边播
本文简单地分享一下在Android平台做HTTP边下载边播放的一些经验,希望对初学者有所帮助. 1. 为什么播放器在播放视频文件的时候,都知道该怎么去解码.该以怎样的时间间隔去显示每一帧呢? 因为无论 ...
- 如何在小视频源码里实现边下边播
最近正在搞几个音视频相关的开源项目,后面会持续更新,简单介绍一下: MediaSDK https://github.com/JeffMony/MediaSDK 这是一个专注音视频边下边播的库,目前已经 ...
- 边下载边播放的播放器Android边下边播
看到很多朋友有提问到Android边下载边播放的播放器,小编在这里给大家做个关于这方面的分享. 首先作为一款播放器,支持转码或者支持各种视频格式是必须的,比如常见的视频格式:MP4/FLV/M3U8/ ...
- 关于在苹果浏览器中new Date()函数兼容性问题
直接上代码 var currentTime = '2019-08-28 06:30:30'; document.write('result:'+new Date(currentTime)); 在谷歌浏 ...
最新文章
- pycharm 打开cfg高亮
- [JavaScript]关于div的隐藏
- esxi时区设置 +8_Go语言MySQL时区问题
- [Servlet]深入掌握Servlet
- [TCP/IP] TCP关闭连接为什么四次挥手
- C语言数据类型转换详解
- 力扣算法题—074搜索二维矩阵
- ext中ArrayStore,JsonStore,XmlStore的用
- python 数据库查询结果邮件提醒_python读取postgresql数据库并发送相关提醒邮件
- 这些互联网巨头,明年可能会纷纷杀入AI芯片战局
- 1.根据MAC地址抓包
- h2 不能访问localhost_Spring 配置的 H2 控制台 frameOptions 导致无法访问
- 阶段1 语言基础+高级_1-3-Java语言高级_07-网络编程_第1节 网络通信概述_4_IP地址...
- ac8265网卡linux驱动,英特尔8265无线网卡驱动
- Pdf转Word用Python轻松实现
- 再谈斐波那契,把数字翻译成字符串
- idea右键新建(new) 但是没有Scala class选项
- 40个web前端实战项目,练完即可就业,从入门到进阶,基础到框架,html_css【附视频+源码】
- 京东月薪8万快递员:真正牛逼的人,都拥有这个特质
- Nginx----进阶篇
热门文章
- 西门子bop20显示电流_SIEMENS/西门子BOP20基本操作员面板使用方法说明
- LabView---信号发生器
- Python茅台抢购脚本详细教程
- html中怎样写渐变色代码,如何用CSS写渐变色
- 车架识别手机端只是一种?
- 三种语句可以恢复Oracle数据库误删除数据
- pthon图片信息-3cv2-高阶处理
- 切比雪夫不等式例题讲解_14.初中数学:怎么求k的值?解一元一次不等式,基础常考题型...
- 【宠物商店管理系统】基于SSM的宠物商店系统(ppt+论文+源代码)
- Linux运维高级工程师要掌握的技能