1.場(chǎng)景描述
服務(wù)端上傳MP4視頻文件,iOS客戶端通過(guò)URL播放該視頻文件。提供視頻接口,可以進(jìn)行視頻下載或者直接播放,但是iOS手機(jī)無(wú)法播放,且PC端safari瀏覽器也無(wú)法播放。
2.問(wèn)題描述
安卓手機(jī)可以正常播放視頻,iOS手機(jī)無(wú)法播放,且PC段safari瀏覽器也無(wú)法播放。
3.問(wèn)題分析
(1)safari不支持整個(gè)文件流,服務(wù)器必須支持分段請(qǐng)求。
(2)safari對(duì)于文件流的請(qǐng)求需要包含一個(gè)請(qǐng)求頭Range, 和一個(gè)響應(yīng)頭Content-Range
4.針對(duì)問(wèn)題分析,進(jìn)行文件分段傳輸,以下代碼已經(jīng)驗(yàn)證,可行,代碼如下:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-836633.html
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手機(jī)無(wú)法播放,且PC端safari瀏覽器也無(wú)法播放問(wèn)題修復(fù)
* @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對(duì)象(假設(shè)convertToLocalResource提供了這個(gè)方法)
Resource videoResource = convertToLocalResource(filePath);
// 處理Range請(qǐng)求
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請(qǐng)求頭
long[] range = parseRange(rangeHeader, videoResource.contentLength());
// 獲取視頻的部分?jǐn)?shù)據(jù)
byte[] videoBytes = getPartialVideo(videoResource, range[0], range[1]);
// 設(shè)置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 {
// 獲取完整視頻的數(shù)據(jù)
byte[] videoBytes = getFullVideo(videoResource);
// 設(shè)置Content-Range頭部
HttpHeaders headers = createFullHeaders(videoBytes.length, videoResource.contentLength());
return new ResponseEntity<>(videoBytes, headers, HttpStatus.OK);
}
private long[] parseRange(String rangeHeader, long contentLength) {
// 解析Range請(qǐng)求頭
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 {
// 獲取部分視頻數(shù)據(jù)
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) {
// 設(shè)置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 {
// 獲取完整視頻的數(shù)據(jù)
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) {
// 設(shè)置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.使用上述方案可以實(shí)現(xiàn)各環(huán)境的視頻嵌套播放,已經(jīng)驗(yàn)證過(guò),可以直接用,各位根據(jù)自己的代碼稍作調(diào)整即可。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-836633.html
到了這里,關(guān)于蘋(píng)果手機(jī)video標(biāo)簽播放視頻問(wèn)題(播放mp4視頻遇到的坑)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!