video.js 视频截图、录制、自定义全屏,hls、flv、mp4视频播放
video.js内嵌 截图、录制功能 (图片、视频会下载到本地)自定义全屏播放hls、flv、mp4功能集合成Vue组件
·
功能
- video.js内嵌 截图、录制功能 (图片、视频会下载到本地)
- 自定义全屏
- 播放hls、flv、mp4
- 功能集合成Vue组件
参考
播放hls、flv、mp4
安装
// video.js
npm install video.js
// 播放hls
npm install videojs-contrib-hls
// 播放flv
npm install videojs-flvjs-es6
npm install flv.js
引入
import "videojs-contrib-hls";
import "videojs-flvjs-es6";
import videojs from "video.js";
import video_zhCN from "video.js/dist/lang/zh-CN.json";
videojs.addLanguage("zh-CN", video_zhCN);
export default {
props: {
name: {
type: String,
default: "my-video",
},
// 视频地址
videoUrl: {
type: String,
default: "",
},
//视频宽度
videoWidth: {
type: String,
default: "100%",
},
//视频高度
videoHeight: {
type: String,
default: "100%",
},
},
data() {
const options =
{
// 封面图
poster: '',
language: "zh-CN",
// 设置自动播放
autoplay: true,
// 设置了它为true,才可实现自动播放,同时视频也被静音 (Chrome66及以上版本,禁止音视频的自动播放)
muted: true,
// 预加载
preload: "none",
// 显示播放的控件
controls: true,
// 进度条
liveui: true,
notSupportedMessage: "此视频暂无法播放,请稍后再试", // 允许覆盖Video.js无法播放媒体源时显示的默认信息。
// 播放速率
playbackRates: [0.5, 1, 2, 4, 8, 16],
controlBar: {
// 画中画
pictureInPictureToggle: false,
// 当前时间和持续时间的分隔符
timeDivider: true,
// 显示持续时间
durationDisplay: true,
// 是否显示剩余时间功能
remainingTimeDisplay: true,
// 是否显示全屏按钮
fullscreenToggle: true,
},
}
return {
options,
// videoUrl 从无到有时会显示一会视频错误信息
// 使用该字段判断 no-video 来避免显示错误信息
hasVideoUrl: false,
// 视频流类型
videoTypeObj: {
mp4: 'video/mp4',
flv: 'video/x-flv',
m3u8: 'application/x-mpegURL'
}
};
},
methods: {
getVideo(nowPlayVideoUrl) {
if (!nowPlayVideoUrl) this.hasVideoUrl = false;
if (!this.player) {
this.player = videojs(this.$refs.player, this.options);
}
this.player.src([
{
src: nowPlayVideoUrl,
type: this.videoUrl
?
this.videoTypeObj[this.videoUrl.split('.').slice(-1)]
:
''
},
]);
setTimeout(() => {
this.hasVideoUrl = !!nowPlayVideoUrl;
}, 100);
}
},
watch: {
//监听视频地址、video的id值
videoUrl: {
deep: true,
immediate: true,
handler(val) {
this.$nextTick(() => {
this.getVideo(val);
});
},
},
},
beforeDestroy() {
// 组件销毁时,清除播放器
if (this.player) this.player.dispose();
},
};
html
<div
:style="{ width: videoWidth, height: videoHeight, position: 'relative' }"
class="display"
>
<video
style="width: 100%; height: 100%"
class="video-js"
ref="player"
v-show="videoUrl"
>
</video>
<!-- 视频路径为空时 -->
<div
v-show="!hasVideoUrl"
:style="{
height: videoWidth,
width: videoHeight,
position: 'absolute',
left: 0,
top: 0,
zIndex: 2,
background: '#000',
}"
>
no video
</div>
</div>
引入组件(视频地址使用的时西瓜的测试视频)
import Player from './components/Player.vue'
export default {
name: 'App',
components: {
Player
},
data: {
// flv
nowPlayVideoUrl: "//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv",
// mp4
// nowPlayVideoUrl: "//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4",
// hls
// nowPlayVideoUrl: "//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/hls/xgplayer-demo.m3u8",
}
...
}
<template>
<div style="height:400px;width:600px">
<Player :videoUrl="nowPlayVideoUrl"/>
</div>
</template>
加入自定义全屏
在props中加入 name 、fullscreenType、fullscreenChange属性
props:{
...
// 当存在多个直播时,用于判断是哪个直播需要全屏
name: {
type: String,
default: "my-video",
},
// 全屏类型
fullscreenType: {
type: String,
default: "initial",
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ["none", "DIY", "initial"].indexOf(value) !== -1;
},
// 全屏状态改变 当fullscreenType为DIY时有效
fullscreenChange: {
type: Function,
default: () => {},
},
}
修改options
const options= {
...
controlBar: {
// 画中画
pictureInPictureToggle: false,
// 当前时间和持续时间的分隔符
timeDivider: true,
// 显示持续时间
durationDisplay: true,
// 是否显示剩余时间功能
remainingTimeDisplay: true,
// 是否显示全屏按钮
fullscreenToggle: this.fullscreenType === "initial" ? true : false,
},
}
修改getVideo方法
if (!nowPlayVideoUrl) this.hasVideoUrl = false;
if (!this.player) {
this.player = videojs(this.$refs.player, this.options);
// 自定义全屏功能
if (this.fullscreenType === "DIY") this.setDIYFullscreen();
}
...
}
编写setDIYFullscreen方法
setDIYFullscreen() {
const fullscreenButton = this.player.controlBar.addChild("button");
fullscreenButton.controlText("全屏");
fullscreenButton.addClass("vjs-fullscreen-control");
const fullscreenButtonDom = fullscreenButton.el();
fullscreenButtonDom.onclick = () => {
this.isDIYFullscreen = !this.isDIYFullscreen;
if (this.isDIYFullscreen) {
this.player.addClass("vjs-fullscreen");
fullscreenButton.controlText("退出全屏");
console.log('全屏');
} else {
this.player.removeClass("vjs-fullscreen");
fullscreenButton.controlText("全屏");
console.log('退出全屏');
}
// 父组件回调
this.fullscreenChange(this.isDIYFullscreen, this.name);
};
}
修改父组件引入
<template>
<div style="height:400px;width:600px">
<Player fullscreenType="DIY" :videoUrl="nowPlayVideoUrl"/>
</div>
</template>
效果
自定义全屏
加入截图、录制功能
安装
// 录制所需插件
npm i recordrtc
创建video.js文件,将video.js相关引入、逻辑都放整合放在这里(自定义全屏除外)
import videojs from "video.js";
import video_zhCN from "video.js/dist/lang/zh-CN.json";
import RecordRTC from "recordrtc";
import "videojs-contrib-hls";
import "videojs-flvjs-es6";
// 截图图片
import cameraImg from '../assets/images/camera.png'
// 录像图片
import monitorImg from '../assets/images/monitor.png'
videojs.addLanguage("zh-CN", video_zhCN);
// 创建 截图、录像
var Component = videojs.getComponent("Component");
var CustomBar = videojs.extend(Component, {
constructor: function (player, options) {},
createEl: function () {
return videojs.dom.createEl('div', {
innerHTML: `这是一个自定义组件`
})
}
})
// 注册 截图、录像组件
videojs.registerComponent('CustomBar', CustomBar);
export default videojs
创建custom-video.css文件,video相关css放在这里
@import "video.js/dist/video-js.css";
更改组件引入,以及在options中加入customBar
import videojs from '../utils/video'
import '../assets/css/custom-video.css'
....
options = {
customBar: {}
...
}
效果
修改CustomBar,界面显示
...
// 截图图片
import cameraImg from '../assets/images/camera.png'
// 录制图片
import monitorImg from '../assets/images/monitor.png'
var CustomBar = videojs.extend(Component, {
...
createEl: function () {
const divDom = videojs.dom.createEl('div', {
className: 'vjs-custom-bar',
innerHTML: `
<div class="vjs-custom-control-bar vjs-button ac">
<img src="${cameraImg}" style="width:13px" />
<span class="ml10">截图</span>
</div>
<div class="mt10 vjs-custom-control-bar ac" >
<img src="${monitorImg}" style="width:13px" />
<span class="ml10">录像</span>
</div>
`
})
return divDom
}
}
修改custom-video.css
customBar位于右侧中间显示,鼠标活动以及悬浮在customBar显示customBar,不活动时隐藏
录制中时红点闪烁
@import "video.js/dist/video-js.css";
.vjs-custom-bar {
position: absolute;
color: #fff;
right: 10px;
transform: translateY(-50%);
top: 50%;
}
.vjs-custom-bar:hover {
opacity: 1 !important;
}
.vjs-custom-control-bar {
padding: 10px;
background: rgba(43, 51, 63, 0.7);
border-radius: 5px;
cursor: pointer;
}
/* 开始录制 闪烁 */
.record-procees {
display: inline-block;
height: 10px;
width: 10px;
background: red;
border-radius: 8px;
animation: blings 1s linear infinite;
}
.mt10 {
margin-top: 10px;
}
.ml10 {
margin-left: 10px;
}
.ac {
display: flex;
align-items: center;
}
@keyframes blings {
0% {
opacity: 0.1;
}
100% {
opacity: 1;
}
}
.video-js .vjs-custom-bar {
color: white;
/* font-size: 2em; */
padding: .5em;
}
.vjs-has-started .vjs-custom-bar {
/* display: flex; */
visibility: visible;
opacity: 1;
transition: visibility 0.1s, opacity 0.1s;
}
/* 用户不活动时设计title bar自动隐藏 */
.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-custom-bar {
visibility: visible;
/*visibility: hidden;*/
opacity: 0;
transition: visibility 1s, opacity 1s;
}
.vjs-controls-disabled .vjs-custom-bar,
.vjs-using-native-controls .vjs-custom-bar,
.vjs-error .vjs-custom-bar {
display: none !important;
}
.vjs-audio.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-custom-bar {
opacity: 0;
visibility: visible;
/*visibility: hidden;*/
}
.vjs-has-started.vjs-no-flex .vjs-custom-bar {
display: table;
}
CustomBar加入隐藏逻辑
...
constructor: function (player, options) {
// Equivalent of `super(this, arguments)`
Component.apply(this, arguments);
// 隐藏截图
if (options.screenshot === false) this.hiddenEl(0)
// 隐藏录像
if (options.recorder === false) this.hiddenEl(1)
},
createEl: function () {
...
}
hiddenEl (index) {
const myDom = this.el().querySelectorAll('div')[index]
myDom.setAttribute('style', 'display:none')
}
当需要隐藏录像时,修改options中customBar属性
options = {
// 截图、录制Bar
customBar: {
screenshot: true,
recorder: false
}
....
}
customBar加入截图、录制逻辑
...
constructor: function (player, options) {
// Equivalent of `super(this, arguments)`
Component.apply(this, arguments);
// player 实列
this.player = player
// 录像所需要的 canvas
this.canvas = null
// 录像实列
this.recorder = null
// 停止循环帧 需要用到的参数
this.animationFrame = null
// 录像状态 false 未录像 true 录像中
this.isRecorder = false
// 隐藏截图
if (options.screenshot === false) this.hiddenEl(0)
// 隐藏录像
if (options.recorder === false) this.hiddenEl(1)
},
createEl: function () {
const divDom = videojs.dom.createEl('div', {
className: 'vjs-custom-bar',
innerHTML: `
<div class="vjs-custom-control-bar vjs-button ac">
<img src="${cameraImg}" style="width:13px" />
<span class="ml10">截图</span>
</div>
<div class="mt10 vjs-custom-control-bar ac" >
<img src="${monitorImg}" style="width:13px" />
<span class="ml10">录像</span>
</div>
`
})
const [screenshotDom, recordDom] = divDom.querySelectorAll('div')
screenshotDom.onclick = () => this.screenshotHandle()
recordDom.onclick = () => this.recordHandle(recordDom)
return divDom
},
...
截图 screenshotHandle方法
// 截图
screenshotHandle() {
const fileType = "png";
// 找到需要截图的video标签
// video 实列
const video = this.player.el().querySelector('video')
// const video = this.video;
console.log(video, 'video');
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
console.log(canvas, 'canvas')
canvas
.getContext("2d")
.drawImage(video, 0, 0, canvas.width, canvas.height); // 图片大小和视频分辨率一致
const strDataURL = canvas.toDataURL("image/" + fileType); // canvas中video中取一帧图片并转成dataURL
let arr = strDataURL.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
const blob = new Blob([u8arr], {
type: mime,
});
const url = window.URL.createObjectURL(blob);
this.downloadFile(url, "png");
},
录制 recordHandle方法,
// 录像
recordHandle (recordDom) {
this.isRecorder = !this.isRecorder
if (this.isRecorder) {
recordDom.innerHTML = `<i class="record-procees"></i><span class="ml10">结束</span>`
if (!this.canvas) this.canvas = document.createElement("canvas");
this.recorder = RecordRTC(this.canvas, {
type: "canvas",
});
this.recorder.startRecording();
this.drawMedia();
} else {
// recordDom.innerHTML = `<i class="el-icon-video-camera-solid"></i><span class="ml10">录像</span>`
recordDom.innerHTML = `<img src="${monitorImg}" style="width:13px" /><span class="ml10">录像</span>`
this.recorder.stopRecording(() => {
const url = window.URL.createObjectURL(this.recorder.getBlob());
this.downloadFile(url, "mp4");
cancelAnimationFrame(this.animationFrame);
this.canvas = null;
this.animationFrame = null;
});
}
},
// 刷新canvas
drawMedia() {
const ctx = this.canvas.getContext("2d");
// 找到需要截图的video标签
const video = this.player.el().querySelector('video')
this.canvas.setAttribute("width", video.videoWidth);
this.canvas.setAttribute("height", video.videoHeight);
ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
// requestAnimationFrame 根据电脑显示帧数进行循环
this.animationFrame = requestAnimationFrame(() => this.drawMedia());
},
文件下载downloadFile
// 下载
downloadFile: function (blob, fileType) {
const a = document.createElement("a");
a.style.display = "none";
a.href = blob;
// const time = this.parseTime(new Date())
const time = new Date().getTime();
a.download = `${time}.${fileType}`;
document.body.appendChild(a);
a.click();
setTimeout(function () {
document.body.removeChild(a);
window.URL.revokeObjectURL(blob);
}, 1000);
},
解决RecordRTC录制报错
在public下index.html引入该文件
<!-- 下载到本地引入 -->
<script src="screenshot.js"></script>
<!-- 官方路径引入 -->
<!-- <script src="https://www.webrtc-experiment.com/screenshot.js"></script> -->
效果
截图和视频录制
其中使用到的图片
更多推荐
已为社区贡献1条内容
所有评论(0)