Flv.js 是 HTML5 Flash 视频(FLV)播放器,纯原生 JavaScript 开发,没有用到 Flash。由 bilibili 网站开源。它的工作原理是将 FLV 文件流转码复用成 ISO BMFF(MP4 碎片)片段,然后通过 Media Source Extensions 将 MP4 片段喂进浏览器。
使用方法
<template><div class="video" :style="{ height: voidHeight }"><video ref="videoElement" muted controls autoplay controlslist="nodownload noplaybackrate noremoteplayback" disablePictureInPicture="true" v-if="!imgError"></video><div class="img_error" v-if="imgError"><p>无法连接相关设备</p></div></div>
</template><script>
import flvjs from "flv.js";
export default {name: "assemblyFlv",props: ["url", "height", "destroy","playon"], // 视频流路径,播放器高度,是否销毁播放器data() {return {flvPlayer: "",imgError: false,voidHeight: "",playOn:true};},mounted() {// 判断是否传入高度,如果没有,高度100%this.height ? (this.voidHeight = this.height) : (this.voidHeight = "100%");// 页面加载完成后,初始化this.$nextTick(() => {this.init(this.url);});},methods: {// 初始化init(source) {if (flvjs.isSupported()) {this.flvPlayer = flvjs.createPlayer({type: "flv",url: source,isLive: true,},{enableWorker: false, //不启用分离线程enableStashBuffer: false, //关闭IO隐藏缓冲区reuseRedirectedURL: true, //重用301/302重定向url,用于随后的请求,如查找、重新连接等。autoCleanupSourceBuffer: true, //自动清除缓存});var videoElement = this.$refs.videoElement;this.flvPlayer.attachMediaElement(this.$refs.videoElement);if (this.url !== "" && this.url !== null) {this.flvPlayer.load();//this.flvPlayer.play();setTimeout(() => { this.flvPlayer.play(); }, 100);// 加载完成this.flvPlayer.on(flvjs.Events.LOADING_COMPLETE, () => {this.imgError = false;});// 加载失败this.flvPlayer.on(flvjs.Events.ERROR,() => {if (this.flvPlayer) {this.reloadVideo(this.flvPlayer);}else{this.imgError = true;}},(error) => {console.log(error);});this.flvPlayer.on(flvjs.Events.STATISTICS_INFO, (res) =>{if(this.playon != false){if (this.lastDecodedFrame == 0) {this.lastDecodedFrame = res.decodedFrames;console.log(this.lastDecodedFrame)return;}if (this.lastDecodedFrame != res.decodedFrames) {this.lastDecodedFrame = res.decodedFrames;} else {this.lastDecodedFrame = 0;console.log('卡住重连')if (this.flvPlayer) {this.reloadVideo(this.flvPlayer);console.log('卡住重连完成')}}}});videoElement.addEventListener("progress", () => {if(videoElement.buffered.length != 0){let end = videoElement.buffered.end(0); //获取当前buffered值(缓冲区末尾)let delta = end - videoElement.currentTime; //获取buffered与当前播放位置的差值// 延迟过大,通过跳帧的方式更新视频if (delta > 10 || delta < 0) {this.flvPlayer.currentTime = this.flvPlayer.buffered.end(0) - 1;console.log('跳帧')return;}// 追帧if (delta > 1) {videoElement.playbackRate = 1.1;console.log('追帧')} else {videoElement.playbackRate = 1;console.log('正常')}}});// 点击播放按钮后,更新视频videoElement.addEventListener("play", () => {if(videoElement.buffered.length > 0){let end = videoElement.buffered.end(0) - 1;this.flvPlayer.currentTime = end;console.log('播放最新')}});// 网页重新激活后,更新视频window.onfocus = () => {if(videoElement.buffered.length > 0){let end1 = videoElement.buffered.end(0) - 1;this.flvPlayer.currentTime = end1;console.log('页面切换')}};}} else {this.imgError = true;}},//断线重连reloadVideo(flvPlayer) {this.detachMediaElement();this.init(this.url);console.log('断线重连')},// 销毁detachMediaElement() {this.flvPlayer.pause();this.flvPlayer.unload();this.flvPlayer.detachMediaElement();this.flvPlayer.destroy();this.flvPlayer = "";},},watch: {url() {this.imgError = false;// 切换流之前,判断之前的流是否销毁this.flvPlayer == "" ? "" : this.detachMediaElement();// 初始化this.init(this.url);},destroy() {// 传入开关值if (this.destroy) {this.init(this.url);} else {this.flvPlayer == "" ? "" : this.detachMediaElement();}},playon() {this.reloadVideo(this.flvPlayer);}},beforeDestroy() {this.detachMediaElement();},
};
</script><style scoped>
.video {position: relative;height: 100%;
}
.video video {width: 100%;height: 100%;object-fit: fill;
}
.video video::-webkit-media-controls-play-button{display: none;
}
.video video::-webkit-media-controls-toggle-closed-captions-button {display: none;
}
.video video::-webkit-media-controls-timeline {display: none;
}
.video video::-webkit-media-controls-current-time-display {display: none;
}
.video video::-webkit-media-controls-time-remaining-display {display: none;
}
.img_error {position: absolute;top: 30%;left: 50%;margin-left: -120px;text-align: center;
}
.img_error > img {margin-bottom: 1em;
}
.img_error > p {color: #00fdff;font-weight: bold;font-size: 1.2em;
}
</style>
封装:
子组件封装:
<template><div class="video-container"><video ref="videoElement" class="centeredVideo" controls autoplay muted></video> </div>
</template><script>
import flvjs from "flv.js"; //引入flv
export default {props: {url : String,},data() {return {// src: ["http://172.21.1.111/live?port=1935&app=myapp&stream=streamname"],};},mounted() {this.flv_load(this.url);},methods: {flv_load(url) {if (flvjs.isSupported()) {let videoElement = this.$refs.videoElement;this.flvPlayer = flvjs.createPlayer({type: "flv", //媒体类型url: url, //flv格式媒体URLisLive: true, //数据源是否为直播流hasAudio: false, //数据源是否包含有音频hasVideo: true, //数据源是否包含有视频enableStashBuffer: false, //是否启用缓存区},{enableWorker: false, // 是否启用分离的线程进行转换enableStashBuffer: false, //关闭IO隐藏缓冲区autoCleanupSourceBuffer: true, //自动清除缓存});this.flvPlayer.attachMediaElement(videoElement); //将播放实例注册到节点this.flvPlayer.load(); //加载数据流this.flvPlayer.play(); //播放数据流}},},
};
</script><style scoped>
/* .video-container {display: inline-block;margin-right: 10px;width: 32%;height: 45%;
} */
.centeredVideo {width: 100%;
}
</style>
父组件调用:
<template><el-card class="box-card"><div class="flvbox" v-for="(item,index) in src" :key="index"><!-- <VideoFlv url="http://172.21.1.111/live?port=1935&app=myapp&stream=streamname" /> --><VideoFlv :url="item" /></div></el-card>
</template><script>
import VideoFlv from "./VideoFlv.vue";
export default {components:{VideoFlv},data() {return {src: ["https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv","https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv","https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv","https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv","http://172.21.1.111/live?port=1935&app=myapp&stream=streamname","https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv",],};},
};
</script><style scoped>
.flvbox {display: inline-block;margin-right: 10px;width: 32%;
}
</style>
因为视频需要实时的 后边发现上边写法暂停之后和切换页面之后 会有延迟 所以开发让新加个刷新按钮 也已满足 然后这个api的写法我尝试很多 追帧啊 更新视频啊 都没生效 父组件重新传值 因为值没有变化 所以也没有重新渲染 所以用到了key 属性 vue每次渲染的时候会去拿这个key 值做对比,如果这一次的key 值和上一次的key值是不一样的才会重新渲染dom 元素,否则保持上一次的元素状态。所以我用了一个时间戳方法