https://www.ckplayer.com/
2、后台实现 2.1、参考博客https://blog.csdn.net/waleit/article/details/117693364
2.2、通过request的Range来实现 2.3、后台代码package cn.lyf.minio.controller;
import cn.lyf.minio.core.MinioTemplate;
import cn.lyf.minio.entity.MinioObject;
import io.minio.StatObjectResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
@RestController
@Slf4j
@RequestMapping(value = "/video")
public class VideoController {
private static final String OBJECT_INFO_LIST = "cn:lyf:minio:media:object:info:list";
@Autowired
private MinioTemplate minioTemplate;
@Resource(name = "jsonRedisTemplate")
private RedisTemplate jsonRedisTemplate;
@GetMapping(value = "/play")
public void getVideoOutStream(HttpServletRequest request, HttpServletResponse response,
@RequestParam(value = "bucketName") String bucketName,
@RequestParam(value = "objectName") String objectName) {
// 设置响应报头
// 需要查询redis
String key = bucketName + ":" + objectName;
Object obj = jsonRedisTemplate.boundHashOps(OBJECT_INFO_LIST).get(key);
MinioObject minioObject;
if (obj == null) {
StatObjectResponse objectInfo = minioTemplate.getObjectInfo(bucketName, objectName);
// 存入Redis中
if (objectInfo == null) {
throw new IllegalArgumentException(key + "不存在");
}
// 判断是否是视频,是否为mp4格式
String filenameExtension = StringUtils.getFilenameExtension(objectName);
if (!"mp4".equalsIgnoreCase(filenameExtension)) {
throw new IllegalArgumentException("不支持的媒体类型");
}
minioObject = new MinioObject();
BeanUtils.copyProperties(objectInfo, minioObject);
jsonRedisTemplate.boundHashOps(OBJECT_INFO_LIST).put(key, minioObject);
} else {
minioObject = (MinioObject) obj;
}
long fileSize = minioObject.getSize();
// Accept-Ranges: bytes
response.setHeader("Accept-Ranges", "bytes");
//pos开始读取位置; last最后读取位置
long startPos = 0;
long endPos = fileSize - 1;
String rangeHeader = request.getHeader("Range");
if (!ObjectUtils.isEmpty(rangeHeader) && rangeHeader.startsWith("bytes=")) {
try {
// 情景一:RANGE: bytes=2000070- 情景二:RANGE: bytes=2000070-2000970
String numRang = request.getHeader("Range").replaceAll("bytes=", "");
if (numRang.startsWith("-")) {
endPos = fileSize - 1;
startPos = endPos - Long.parseLong(new String(numRang.getBytes(StandardCharsets.UTF_8), 1,
numRang.length() - 1)) + 1;
} else if (numRang.endsWith("-")) {
endPos = fileSize - 1;
startPos = Long.parseLong(new String(numRang.getBytes(StandardCharsets.UTF_8), 0,
numRang.length() - 1));
} else {
String[] strRange = numRang.split("-");
if (strRange.length == 2) {
startPos = Long.parseLong(strRange[0].trim());
endPos = Long.parseLong(strRange[1].trim());
} else {
startPos = Long.parseLong(numRang.replaceAll("-", "").trim());
}
}
if (startPos < 0 || endPos < 0 || endPos >= fileSize || startPos > endPos) {
// SC 要求的范围不满足
response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
return;
}
// 断点续传 状态码206
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
} catch (NumberFormatException e) {
log.error(request.getHeader("Range") + " is not Number!");
startPos = 0;
}
}
// 总共需要读取的字节
long rangLength = endPos - startPos + 1;
response.setHeader("Content-Range", String.format("bytes %d-%d/%d", startPos, endPos, fileSize));
response.addHeader("Content-Length", String.valueOf(rangLength));
response.addHeader("Content-Type", "video/mp4");
try (BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
BufferedInputStream bis = new BufferedInputStream(
minioTemplate.getObject(bucketName, objectName, startPos, rangLength))) {
IOUtils.copy(bis, bos);
} catch (
IOException e) {
if (e instanceof ClientAbortException) {
// ignore
} else {
log.error(e.getMessage());
}
}
}
}
2.4、相关依赖代码见博客
https://blog.csdn.net/lyf_zm/article/details/124627842
3、测试使用 3.1、包结构 3.2、video.html 3.2.1、创建视频对象// 创建视频对象
let videoObject = {
container: '.video',//视频容器的ID
volume: 0.8,//默认音量,范围0-1
video: 'http://localhost:18002/video/play?bucketName=minio-test&objectName=488b4f54-a5be-4796-883c-954e0cd83742.mp4',//视频地址
};
// 调用播放器并赋值给变量player
let player = new ckplayer(videoObject)
3.2.2、完整代码
ckplayer
播放容器
全功能演示:www.ckplayer.com/demo.html
控制示例:
3.3、播放



