当前位置: 首页 > article >正文

苹果手机video标签播放视频问题(播放mp4视频遇到的坑)

1.场景描述
服务端上传MP4视频文件,iOS客户端通过URL播放该视频文件。提供视频接口,可以进行视频下载或者直接播放,但是iOS手机无法播放,且PC端safari浏览器也无法播放。
2.问题描述
安卓手机可以正常播放视频,iOS手机无法播放,且PC段safari浏览器也无法播放。
3.问题分析
(1)safari不支持整个文件流,服务器必须支持分段请求。
(2)safari对于文件流的请求需要包含一个请求头Range, 和一个响应头Content-Range

4.针对问题分析,进行文件分段传输,以下代码已经验证,可行,代码如下:

package com.example.yonyou.dyp.com;

import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

/**
 * @description: iOS手机无法播放,且PC端safari浏览器也无法播放问题修复
 * @author Lancy
 * @date: 2023/12/8 17:11
 */
@RestController
@RequestMapping("/videos")
public class VideoController {

    @GetMapping("/{videoFileName}")
    public ResponseEntity<byte[]> streamVideo(
            @RequestHeader(value = "Range", required = false) String rangeHeader,
            HttpServletRequest request
    ) throws IOException {

        String filePath = "D:/video/20230801_093526.mp4";

        // 获取视频文件的Resource对象(假设convertToLocalResource提供了这个方法)
        Resource videoResource = convertToLocalResource(filePath);

        // 处理Range请求
        if (rangeHeader != null && rangeHeader.startsWith("bytes=")) {
            return handleRangeRequest(videoResource, rangeHeader);
        } else {
            return handleFullRequest(videoResource);
        }
    }

    private ResponseEntity<byte[]> handleRangeRequest(Resource videoResource, String rangeHeader) throws IOException {
        // 解析Range请求头
        long[] range = parseRange(rangeHeader, videoResource.contentLength());

        // 获取视频的部分数据
        byte[] videoBytes = getPartialVideo(videoResource, range[0], range[1]);

        // 设置Content-Range头部
        HttpHeaders headers = createRangeHeaders(videoBytes.length, range[0], range[1], videoResource.contentLength());

        return new ResponseEntity<>(videoBytes, headers, HttpStatus.PARTIAL_CONTENT);
    }

    private ResponseEntity<byte[]> handleFullRequest(Resource videoResource) throws IOException {
        // 获取完整视频的数据
        byte[] videoBytes = getFullVideo(videoResource);

        // 设置Content-Range头部
        HttpHeaders headers = createFullHeaders(videoBytes.length, videoResource.contentLength());

        return new ResponseEntity<>(videoBytes, headers, HttpStatus.OK);
    }

    private long[] parseRange(String rangeHeader, long contentLength) {
        // 解析Range请求头
        String[] range = rangeHeader.substring(6).split("-");
        long start = Long.parseLong(range[0]);
        long end = range.length==1 || range[1].isEmpty() ? contentLength - 1 : Long.parseLong(range[1]);
        return new long[]{start, end};
    }

    private byte[] getPartialVideo(Resource videoResource, long start, long end) throws IOException {
        // 获取部分视频数据
        try (InputStream videoStream = videoResource.getInputStream()) {
            long length = end - start + 1;
            byte[] videoBytes = new byte[(int) length];
            videoStream.skip(start);
            videoStream.read(videoBytes, 0, (int) length);
            return videoBytes;
        }
    }

    private HttpHeaders createRangeHeaders(long contentLength, long start, long end, long totalLength) {
        // 设置Content-Range头部
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType("video/mp4"));
        headers.setContentLength(contentLength);
        headers.add("Content-Range", "bytes " + start + "-" + end + "/" + totalLength);
        return headers;
    }

    private byte[] getFullVideo(Resource videoResource) throws IOException {
        // 获取完整视频的数据
        try (InputStream videoStream = videoResource.getInputStream()) {
            byte[] videoBytes = new byte[(int) videoResource.contentLength()];
            videoStream.read(videoBytes, 0, videoBytes.length);
            return videoBytes;
        }
    }

    private HttpHeaders createFullHeaders(long contentLength, long totalLength) {
        // 设置Content-Range头部
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType("video/mp4"));
        headers.setContentLength(contentLength);
        headers.add("Content-Range", "bytes 0-" + (contentLength - 1) + "/" + totalLength);
        return headers;
    }

    public  Resource convertToLocalResource(String filePath) {
        File file = new File(filePath);
        if (file.exists() && file.isFile()) {
            return new FileSystemResource(file);
        } else {
            throw new IllegalArgumentException("File does not exist or is not a regular file: " + filePath);
        }
    }

}

5.使用上述方案可以实现各环境的视频嵌套播放,已经验证过,可以直接用,各位根据自己的代码稍作调整即可。


http://www.kler.cn/a/163230.html

相关文章:

  • Android音频架构
  • uni-app移动端与PC端兼容预览PDF文件
  • WPF学习之路,控件的只读、是否可以、是否可见属性控制
  • 在 Service Worker 中caches.put() 和 caches.add()/caches.addAll() 方法他们之间的区别
  • L10.【LeetCode笔记】回文链表
  • ElasticSearch学习笔记一:简单使用
  • WPS论文写作——公式和公式序号格式化
  • 文本转图像 学习笔记
  • web前端开发html/css练习
  • 第75讲:MySQL数据库MVCC多版本并发控制核心概念以及底层原理
  • 无人机高空巡查+智能视频监控技术,打造森林防火智慧方案
  • 结构化布线系统
  • 树莓派 5 - Raspberry Pi 5 入门教程
  • C/C++——内存管理
  • 微软NativeApi-NtQuerySystemInformation
  • 【WPF.NET开发】WPF中的对话框
  • 拆分降采样与归一化(LN和BN)
  • websocket vue操作
  • 快速学会绘制Pyqt5中的所有图(下)
  • Kafka安全性探究:构建可信赖的分布式消息系统
  • 二叉树的非递归遍历(详解)
  • 一款可无限扩展的软件定时器开源框架项目代码
  • 三星AI笔电:年底大战一触即发,行业变革在即
  • 【数据结构和算法】种花问题
  • 快速搭建MyBatis源码调试环境
  • 麒麟V10服务器安装Apache+PHP