一、问题提出
项目开发过程中遇到一个问题:
>基于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(谷歌浏览器可以)相关推荐

  1. html实现边下边播mp4,WebTorrent:一款可边下边播磁力链接的播放器

    网页版可以直接嵌入网页,相当于一个p2p的在线播放器,在项目官网首页就可以体验. 体验网页版WebTorrent:https://webtorrent.io/ 桌面版为WebTorrent Deskt ...

  2. html实现边下边播mp4,MP4Info: 不用流媒体也可以简单实现MP4等视频的边下边播功能。...

    /** * MP4Info 示例程序 * @author TJbaobao *=====================原理说明:http://blog.csdn.net/u013640004/art ...

  3. Android 视频边下边播,MP4头信息在后调整头信息

    mp4视频有两种格式,一种视频头信息在前,这种直接可以先缓存头信息,然后直接边下边播,还有一种是头信息在最后,这种情况下则需要处理mp4的头信息,并调整mp4的格式. mp4文件的格式如下图 图1 从 ...

  4. 关于Android HTTP边下边播

    本文简单地分享一下在Android平台做HTTP边下载边播放的一些经验,希望对初学者有所帮助. 1. 为什么播放器在播放视频文件的时候,都知道该怎么去解码.该以怎样的时间间隔去显示每一帧呢? 因为无论 ...

  5. 迅雷 android下载地址 http,Android HTTP边下边播

    本文简单地分享一下在Android平台做HTTP边下载边播放的一些经验,希望对初学者有所帮助. 1. 为什么播放器在播放视频文件的时候,都知道该怎么去解码.该以怎样的时间间隔去显示每一帧呢? 因为无论 ...

  6. Android HTTP边下边播

    本文简单地分享一下在Android平台做HTTP边下载边播放的一些经验,希望对初学者有所帮助. 1. 为什么播放器在播放视频文件的时候,都知道该怎么去解码.该以怎样的时间间隔去显示每一帧呢? 因为无论 ...

  7. 如何在小视频源码里实现边下边播

    最近正在搞几个音视频相关的开源项目,后面会持续更新,简单介绍一下: MediaSDK https://github.com/JeffMony/MediaSDK 这是一个专注音视频边下边播的库,目前已经 ...

  8. 边下载边播放的播放器Android边下边播

    看到很多朋友有提问到Android边下载边播放的播放器,小编在这里给大家做个关于这方面的分享. 首先作为一款播放器,支持转码或者支持各种视频格式是必须的,比如常见的视频格式:MP4/FLV/M3U8/ ...

  9. 关于在苹果浏览器中new Date()函数兼容性问题

    直接上代码 var currentTime = '2019-08-28 06:30:30'; document.write('result:'+new Date(currentTime)); 在谷歌浏 ...

最新文章

  1. pycharm 打开cfg高亮
  2. [JavaScript]关于div的隐藏
  3. esxi时区设置 +8_Go语言MySQL时区问题
  4. [Servlet]深入掌握Servlet
  5. [TCP/IP] TCP关闭连接为什么四次挥手
  6. C语言数据类型转换详解
  7. 力扣算法题—074搜索二维矩阵
  8. ext中ArrayStore,JsonStore,XmlStore的用
  9. python 数据库查询结果邮件提醒_python读取postgresql数据库并发送相关提醒邮件
  10. 这些互联网巨头,明年可能会纷纷杀入AI芯片战局
  11. 1.根据MAC地址抓包
  12. h2 不能访问localhost_Spring 配置的 H2 控制台 frameOptions 导致无法访问
  13. 阶段1 语言基础+高级_1-3-Java语言高级_07-网络编程_第1节 网络通信概述_4_IP地址...
  14. ac8265网卡linux驱动,英特尔8265无线网卡驱动
  15. Pdf转Word用Python轻松实现
  16. 再谈斐波那契,把数字翻译成字符串
  17. idea右键新建(new) 但是没有Scala class选项
  18. 40个web前端实战项目,练完即可就业,从入门到进阶,基础到框架,html_css【附视频+源码】
  19. 京东月薪8万快递员:真正牛逼的人,都拥有这个特质
  20. Nginx----进阶篇

热门文章

  1. 西门子bop20显示电流_SIEMENS/西门子BOP20基本操作员面板使用方法说明
  2. LabView---信号发生器
  3. Python茅台抢购脚本详细教程
  4. html中怎样写渐变色代码,如何用CSS写渐变色
  5. 车架识别手机端只是一种?
  6. 三种语句可以恢复Oracle数据库误删除数据
  7. pthon图片信息-3cv2-高阶处理
  8. 切比雪夫不等式例题讲解_14.初中数学:怎么求k的值?解一元一次不等式,基础常考题型...
  9. 【宠物商店管理系统】基于SSM的宠物商店系统(ppt+论文+源代码)
  10. Linux运维高级工程师要掌握的技能