当前位置:   article > 正文

苹果手机video标签播放视频问题(播放mp4视频遇到的坑)_video标签在苹果自动播放失败

video标签在苹果自动播放失败

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

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

  1. package com.example.yonyou.dyp.com;
  2. import org.springframework.core.io.FileSystemResource;
  3. import org.springframework.core.io.Resource;
  4. import org.springframework.http.HttpHeaders;
  5. import org.springframework.http.HttpStatus;
  6. import org.springframework.http.MediaType;
  7. import org.springframework.http.ResponseEntity;
  8. import org.springframework.web.bind.annotation.GetMapping;
  9. import org.springframework.web.bind.annotation.RequestHeader;
  10. import org.springframework.web.bind.annotation.RequestMapping;
  11. import org.springframework.web.bind.annotation.RestController;
  12. import javax.servlet.http.HttpServletRequest;
  13. import java.io.File;
  14. import java.io.IOException;
  15. import java.io.InputStream;
  16. /**
  17. * @description: iOS手机无法播放,且PC端safari浏览器也无法播放问题修复
  18. * @author Lancy
  19. * @date: 2023/12/8 17:11
  20. */
  21. @RestController
  22. @RequestMapping("/videos")
  23. public class VideoController {
  24. @GetMapping("/{videoFileName}")
  25. public ResponseEntity<byte[]> streamVideo(
  26. @RequestHeader(value = "Range", required = false) String rangeHeader,
  27. HttpServletRequest request
  28. ) throws IOException {
  29. String filePath = "D:/video/20230801_093526.mp4";
  30. // 获取视频文件的Resource对象(假设convertToLocalResource提供了这个方法)
  31. Resource videoResource = convertToLocalResource(filePath);
  32. // 处理Range请求
  33. if (rangeHeader != null && rangeHeader.startsWith("bytes=")) {
  34. return handleRangeRequest(videoResource, rangeHeader);
  35. } else {
  36. return handleFullRequest(videoResource);
  37. }
  38. }
  39. private ResponseEntity<byte[]> handleRangeRequest(Resource videoResource, String rangeHeader) throws IOException {
  40. // 解析Range请求头
  41. long[] range = parseRange(rangeHeader, videoResource.contentLength());
  42. // 获取视频的部分数据
  43. byte[] videoBytes = getPartialVideo(videoResource, range[0], range[1]);
  44. // 设置Content-Range头部
  45. HttpHeaders headers = createRangeHeaders(videoBytes.length, range[0], range[1], videoResource.contentLength());
  46. return new ResponseEntity<>(videoBytes, headers, HttpStatus.PARTIAL_CONTENT);
  47. }
  48. private ResponseEntity<byte[]> handleFullRequest(Resource videoResource) throws IOException {
  49. // 获取完整视频的数据
  50. byte[] videoBytes = getFullVideo(videoResource);
  51. // 设置Content-Range头部
  52. HttpHeaders headers = createFullHeaders(videoBytes.length, videoResource.contentLength());
  53. return new ResponseEntity<>(videoBytes, headers, HttpStatus.OK);
  54. }
  55. private long[] parseRange(String rangeHeader, long contentLength) {
  56. // 解析Range请求头
  57. String[] range = rangeHeader.substring(6).split("-");
  58. long start = Long.parseLong(range[0]);
  59. long end = range.length==1 || range[1].isEmpty() ? contentLength - 1 : Long.parseLong(range[1]);
  60. return new long[]{start, end};
  61. }
  62. private byte[] getPartialVideo(Resource videoResource, long start, long end) throws IOException {
  63. // 获取部分视频数据
  64. try (InputStream videoStream = videoResource.getInputStream()) {
  65. long length = end - start + 1;
  66. byte[] videoBytes = new byte[(int) length];
  67. videoStream.skip(start);
  68. videoStream.read(videoBytes, 0, (int) length);
  69. return videoBytes;
  70. }
  71. }
  72. private HttpHeaders createRangeHeaders(long contentLength, long start, long end, long totalLength) {
  73. // 设置Content-Range头部
  74. HttpHeaders headers = new HttpHeaders();
  75. headers.setContentType(MediaType.parseMediaType("video/mp4"));
  76. headers.setContentLength(contentLength);
  77. headers.add("Content-Range", "bytes " + start + "-" + end + "/" + totalLength);
  78. return headers;
  79. }
  80. private byte[] getFullVideo(Resource videoResource) throws IOException {
  81. // 获取完整视频的数据
  82. try (InputStream videoStream = videoResource.getInputStream()) {
  83. byte[] videoBytes = new byte[(int) videoResource.contentLength()];
  84. videoStream.read(videoBytes, 0, videoBytes.length);
  85. return videoBytes;
  86. }
  87. }
  88. private HttpHeaders createFullHeaders(long contentLength, long totalLength) {
  89. // 设置Content-Range头部
  90. HttpHeaders headers = new HttpHeaders();
  91. headers.setContentType(MediaType.parseMediaType("video/mp4"));
  92. headers.setContentLength(contentLength);
  93. headers.add("Content-Range", "bytes 0-" + (contentLength - 1) + "/" + totalLength);
  94. return headers;
  95. }
  96. public Resource convertToLocalResource(String filePath) {
  97. File file = new File(filePath);
  98. if (file.exists() && file.isFile()) {
  99. return new FileSystemResource(file);
  100. } else {
  101. throw new IllegalArgumentException("File does not exist or is not a regular file: " + filePath);
  102. }
  103. }
  104. }

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/520673
推荐阅读
相关标签
  

闽ICP备14008679号