事情的起因是在博主把项目部署到服务器上后,发现由于视频太大,加上服务器太垃圾,导致稍微大点的视频加载很久才能播放(指十多分钟…),然后就上网查找资料,看下咋实现。
这里涉及到有关http请求的知识“HTTP Header里的Range和Content-Range参数,Range是在请求头里
Range: bytes=start-end,这个表示告诉后端只获取start到end字节的文件
Content-Range: bytes start-end/文件总大小,返回浏览器,说明这次请求到的文件范围,还有总文件的大小;
晓得这两个Http参数就好办了,大体上是浏览器发送带有Range的请求头,然后后端根据Range指定的范围返回对应大小的文件片段
#1.使用标签
标签的src属性指向服务器链接,当服务器响应的HTTP状态码为206时,浏览器会自动开启分段式播放,在每次的HTTP请求头中自动加入Range请求头,服务端只需要根据前端传过来的Range信息截取视频的指定区间来响应即可。
#2.使用vue-video-player(vue里面的插件)
一开始我发现vue-video-player和video不同,每次发送的请求的Range里都只有字节开始,没有字节结尾!
以至于找了很久怎么改vue-video-player请求头的参数,后来发现只要有开头就能够实现断点下载,vue-video-player下次的Range开头一定是上一次的开头加上返回头里content-length(后台返回的下载文件的大小,response自动返回的,不用自己设置)
大体流程是vue-video-player发送断点请求,Range:btyes=start-,后台知道是断点请求后,利用RandomAccessFile类里的seek(start)方法将读取文件的光标移到Range指定位置,然后自己设置每次返回文件大小,返回给前端
前端看response里状态代码是206,就会自动发送后续请求,Range:bytes=start+content-length里的字节大小
代码
vue-video-player
<template>
<div class="my_video">
<video-player class="video-player vjs-custom-skin"
ref="videoPlayer"
:playsinline="true"
:options="videoPlayerOptions"
@play="onPlayerPlay($event)"
@pause="onPlayerPause($event)"
@ended="onPlayerEnded($event)"
@waiting="onPlayerWaiting($event)"
@playing="onPlayerPlaying($event)"
@loadeddata="onPlayerLoadeddata($event)"
@timeupdate="onPlayerTimeupdate($event)"
@canplay="onPlayerCanplay($event)"
@canplaythrough="onPlayerCanplaythrough($event)"
@statechanged="playerStateChanged($event)"
@ready="playerReadied"
></video-player>
</div>
</template>
<script>
// 导入组件
import {videoPlayer} from 'vue-video-player'
export default {
name: 'VideoPlayer',
props:{
videoStr:{default:""}
},
components: {
videoPlayer
},
data () {
return {
video:'', //具体视频
fileType: 'mp4', // 资源的类型
videoUrl: '', // 资源的路径地址
posterUrl:'', //封面地址
headers:{
'Range':'bytes=0-1000'
}
}
},
mounted(){
},
methods:{
// 播放回调
onPlayerPlay(player) {
console.log('player play!', player)
},
// 暂停回调
onPlayerPause(player) {
console.log('player pause!', player)
},
// 视频播完回调
onPlayerEnded($event) {
// this.$refs.videoPlayer.player.src(this.fileUrl)
$event
},
// DOM元素上的readyState更改导致播放停止
onPlayerWaiting($event) {
console.log($event)
},
// 已开始播放回调
onPlayerPlaying($event) {
console.log($event)
},
// 当播放器在当前播放位置下载数据时触发
onPlayerLoadeddata($event) {
console.log($event)
},
// 当前播放位置发生变化时触发。
onPlayerTimeupdate($event) {
console.log($event)
},
//媒体的readyState为HAVE_FUTURE_DATA或更高
onPlayerCanplay(player) {
console.log('player Canplay!', player)
},
//媒体的readyState为HAVE_ENOUGH_DATA或更高。这意味着可以在不缓冲的情况下播放整个媒体文件。
onPlayerCanplaythrough(player) {
console.log('player Canplaythrough!', player)
},
//播放状态改变回调
playerStateChanged(playerCurrentState) {
console.log('player current update state', playerCurrentState)
},
//将侦听器绑定到组件的就绪状态。与事件监听器的不同之处在于,如果ready事件已经发生,它将立即触发该函数。。
playerReadied(player) {
console.log('example player 1 readied', player);
},
},
computed: {
videoPlayerOptions () {
const videoPlayerOptions = {
playbackRates: [0.75, 1.0, 1.25, 1.5,2.0], //播放速度
autoplay: false, // 是否自动播放。
muted: false, // 是否静音播放,默认情况下将会消除任何音频。
loop: false, // 是否循环播放。
preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
language: 'zh-CN',
aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
fluid: true, // 是否流体从而按比例缩放以适应其容器。
flash:{hls:{withCreadentials:false}},//可以播放rtmp视频
html5:{hls:{withCreadentials:false}},//可以播放m3u8视频
sources: [{
type: 'video/' + this.fileType,
src:this.videoStr, // 视频url地址
}],
poster: this.posterUrl, // 封面地址
width: '100%',
notSupportedMessage: '此视频暂无法播放...', // 当无法播放时允许覆盖Video.js,显示的默认信息。
controlBar: {
timeDivider: true,
durationDisplay: true,
remainingTimeDisplay: false,
fullscreenToggle: true
}
}
return videoPlayerOptions
}
},
}
</script>
<style scoped >
.my_video{
width: 100%;
height: 60%;
}
.video-js .vjs-big-play-button{
/*对播放按钮的样式进行设置*/
width: 100%;
height: 100%;
border-radius: 50%;
}
</style>
后端:
```javascript
```java
@RequestMapping("downVideoByStep")
public void play(String flag, HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("downVideoByStep");
String basePath = System.getProperty("user.dir") + "/video/";
response.reset();
File file = new File(basePath+flag);
long fileLength = file.length();
// 随机读文件
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
//获取从那个字节开始读取文件
String rangeString = request.getHeader("Range");
long range=0;
if (StrUtil.isNotBlank(rangeString)) {
range = Long.valueOf(rangeString.substring(rangeString.indexOf("=") + 1, rangeString.indexOf("-")));
}
//获取响应的输出流
OutputStream outputStream = response.getOutputStream();
//设置内容类型
response.setHeader("Content-Type", "video/mp4");
//返回码需要为206,代表只处理了部分请求,响应了部分数据
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
// 移动访问指针到指定位置
randomAccessFile.seek(range);
// 每次请求只返回1MB的视频流
byte[] bytes = new byte[1024 * 1024];
int len = randomAccessFile.read(bytes);
//设置此次相应返回的数据长度
// response.setContentLength(len);
//设置此次相应返回的数据范围
response.setHeader("Content-Range", "bytes "+range+"-"+(fileLength-1)+"/"+fileLength);
// 将这1MB的视频流响应给客户端
outputStream.write(bytes, 0, len);
outputStream.close();
randomAccessFile.close();
System.out.println("返回数据区间:【"+range+"-"+(range+len)+"】");
}