苹果手机 h5网页或公众号视频无法播放问题处理

最近遇到奇葩问题,苹果手机公众号和h5网页中视频无法播放,在网络中找寻了好多解决方案,但还是没能彻底解决。

出现这个问题网上反馈多数因为两个情况,一、视频输出流问题;二、视频格式问题;围绕这两个点展开处理。

首先解决视频流输出问题,本次项目采用的java,springboot方式,项目默认结构为文件存储服务器本地,通过转换读取方式,直接访问文件地址即可获取。之后再网上找寻了很多输出视频流方式的例子;

这里将我们使用的例子代码贴出,仅供参考,具体可根据项目需求调整;其核心的断点流传输工具类可不同改动。

fileUpload.path: D:/fileUpload/
fileServic.path: http://192.168.2.198:8069/filestatic/

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;

@Slf4j
@RestController
public class CommonController {
    /**
     * 请求访问域名地址
     */
    @Value("${fileServic.path}")
    private String fileServicPath;
    /**
     * 文件存储物理路径
     */
    @Value("${fileUpload.path}")
    private String fileUploadPath;

    /**
     * 获取视频
     *
     * @param request
     * @param response
     */
    @GetMapping("/filestatic/{date}/{fileName}")
    public void getPlayResource(HttpServletRequest request, HttpServletResponse response,
                                @PathVariable(name = "date") String date,
                                @PathVariable(name = "fileName") String fileName) {
        String rangeString = request.getHeader(HttpHeaders.RANGE);
        log.info("RANGE================,{}", rangeString);
        fileName = fileUploadPath + "/" + date + "/" + fileName;
        if (StringUtils.isNotEmpty(fileName)) {
            if (fileName.indexOf("mp4") > -1) {
                play(fileName,request,response);
            } else {
                try {
                    writeBytes(fileName, response.getOutputStream());
                } catch (IOException e) {
                    log.error("下载文件失败", e);
                }
            }

        }
    }

    /**
     * 非视频类文件预览加载
     *
     * @param filePath
     * @param os
     * @throws IOException
     */
    public void writeBytes(String filePath, OutputStream os) throws IOException {
        FileInputStream fis = null;
        try {
            File file = new File(filePath);
            if (!file.exists()) {
                throw new FileNotFoundException(filePath);
            }
            fis = new FileInputStream(file);
            byte[] b = new byte[1024];
            int length;
            while ((length = fis.read(b)) > 0) {
                os.write(b, 0, length);
            }
        } catch (IOException e) {
            throw e;
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }


    /**
     * 下载视频文件 path为本地文件路劲
     *
     * @param path
     * @param request
     * @param response
     */
    public void play(String path, HttpServletRequest request, HttpServletResponse response) {

        RandomAccessFile targetFile = null;
        OutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            response.reset();
            //获取请求头中Range的值
            String rangeString = request.getHeader(HttpHeaders.RANGE);

            //打开文件
            File file = new File(path);
            if (file.exists()) {
                //使用RandomAccessFile读取文件
                targetFile = new RandomAccessFile(file, "r");
                long fileLength = targetFile.length();
                long requestSize = (int) fileLength;
                //分段下载视频
                if (StringUtils.isNotEmpty(rangeString)) {
                    //从Range中提取需要获取数据的开始和结束位置
                    long requestStart = 0, requestEnd = 0;
                    String[] ranges = rangeString.split("=");
                    if (ranges.length > 1) {
                        String[] rangeDatas = ranges[1].split("-");
                        requestStart = Integer.parseInt(rangeDatas[0]);
                        if (rangeDatas.length > 1) {
                            requestEnd = Integer.parseInt(rangeDatas[1]);
                        }
                    }
                    if (requestEnd != 0 && requestEnd > requestStart) {
                        requestSize = requestEnd - requestStart + 1;
                    }
                    //根据协议设置请求头
                    response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
                    response.setHeader(HttpHeaders.CONTENT_TYPE, "video/mp4");
                    long length;
                    if (requestEnd > 0) {
                        length = requestEnd - requestStart + 1;

                        response.setHeader(HttpHeaders.CONTENT_LENGTH, "" + length);
                        response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + requestStart + "-" + requestEnd + "/" + fileLength);
                    } else {
                        length = fileLength - requestStart;
                        response.setHeader(HttpHeaders.CONTENT_LENGTH, "" + length);
                        response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + requestStart + "-" + (fileLength - 1) + "/"
                                + fileLength);
                    }
//                    }
                    boolean scPartialContent = true;
                    //断点传输下载视频返回206
                    //如果是第一次请求,不返回206
                    if (ranges.length > 1) {
                        String[] rangeDatas = ranges[1].split("-");
                        requestStart = Integer.parseInt(rangeDatas[0]);
                        if (rangeDatas.length > 1 && requestStart == 0 && Integer.parseInt(rangeDatas[1]) == 1) {
//                            requestEnd = Integer.parseInt(rangeDatas[1]);
                            scPartialContent = false;
                            log.info("第一次请求rangeString,{}", rangeString);
                        }
                    }
                    if (scPartialContent) {
                        log.info("不是第一次请求rangeString,{}", rangeString);
                        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                    }

                    //设置targetFile,从自定义位置开始读取数据
                    targetFile.seek(requestStart);
                } else {
                    //如果Range为空则下载整个视频
                    response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=test.mp4");
                    //设置文件长度
                    response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileLength));
                }

                //从磁盘读取数据流返回
                byte[] cache = new byte[4096];
                try {
                    while (requestSize > 0) {
                        int len = targetFile.read(cache);
                        if (requestSize < cache.length) {
                            outputStream.write(cache, 0, (int) requestSize);
                        } else {
                            outputStream.write(cache, 0, len);
                            if (len < cache.length) {
                                break;
                            }
                        }
                        requestSize -= cache.length;
                    }
                } catch (IOException e) {
                    // tomcat原话。写操作IO异常几乎总是由于客户端主动关闭连接导致,所以直接吃掉异常打日志
                    //比如使用video播放视频时经常会发送Range为0- 的范围只是为了获取视频大小,之后就中断连接了
                    log.info(e.getMessage());
                }
            } else {
//                throw new RuntimeException("文件路劲有误");
            }
            outputStream.flush();
        } catch (Exception e) {
            log.error("文件传输错误", e);
//            throw new RuntimeException("文件传输错误");
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    log.error("流释放错误", e);
                }
            }
            if (targetFile != null) {
                try {
                    targetFile.close();
                } catch (IOException e) {
                    log.error("文件流释放错误", e);
                }
            }
        }
    }

}

视频格式问题,参考地址https://zhuanlan.zhihu.com/p/532430872

我们的对比发下,上传的视频帧速率为25帧/秒无法播放,帧速率为30帧/秒可以正常。这和上述链接中讲解的苹果对视频帧数支持和格式说明有关系。

于是我们使用格式工厂(视频处理工具,免费的),进行转换后上传,真的可以正常播放了。到此问题全部解决。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值