随着网络直播的流行,如何在Web页面中集成高质量的视频播放功能成为了前端开发中的一个热点问题。本文将详细介绍如何封装一个具备自动播放、截图、重播和放大缩小功能的直播视频组件。
效果图
组件设计
为了实现一个功能全面的直播视频组件,我们设计了两个主要的Vue组件:LiveVideo
和 CanvasVideo
。
LiveVideo 组件
LiveVideo
组件负责获取直播视频的URL,并将其传递给 CanvasVideo
组件进行播放。
播和放大缩小功能的直播视频组件。
<template>
<!-- 使用CanvasVideo组件来播放视频 -->
<CanvasVideo :src="liveUrl" ref="videoRef" :autoPlay="false" videoType="mp4"></CanvasVideo>
</template>
<script>
import CanvasVideo from '@/components/CanvasVideo/index'
import { queryChannelViewTemp } from '@/api/bigScreen_sxycpc'
export default {
name: 'LiveVideo',
props: {
channelId: {
type: String,
default: '',
},
},
components: { CanvasVideo },
data() {
return {
liveUrl: '', // 视频路径
}
},
mounted() {
// 组件挂载后立即获取视频URL
this.channelId && this.getLiveUrl()
},
methods: {
async getLiveUrl() {
// 通过API获取视频URL
const res = await queryChannelViewTemp({ channelId: this.channelId, mappingIp: location.hostname, protocol: 'http-fmp4' })
this.liveUrl = res.data.data.url
// 视频URL获取后,通知CanvasVideo组件开始播放
this.$nextTick(() => {
this.$refs.videoRef.controlPlay(true)
})
},
},
}
</script>
CanvasVideo 组件
CanvasVideo
组件是一个基于canvas
的自定义视频播放器,支持视频播放控制和高级功能。
模板功能
<template>
<div class="canvasVideo" ref="canvasVideo" v-loading="loading">
<!-- 视频播放区域 -->
<canvas ref="canvas" :data-src="src"></canvas>
<!-- 视频控制栏 -->
<div class="canvasVideo_control" v-if="control" @mouseover="showControl" @mouseleave="hideControl">
<div class="canvasVideo_control__content">
<!-- 播放/暂停、截图、重播、全屏按钮 -->
<div class="canvasVideo_control__left">
<i class="el-icon-video-play" v-if="!isPlaying" @click="togglePlay"></i>
<i class="el-icon-video-pause" v-else @click="togglePlay"></i>
<i class="el-icon-picture-outline" @click="takeScreenshot"></i>
<slot name="control_left"></slot>
</div>
<div class="canvasVideo_control__right">
<slot name="control_right"></slot>
<i class="el-icon-refresh" @click="resetCanvas"></i>
<i class="el-icon-full-screen" @click="toggleFullscreen"></i>
</div>
</div>
</div>
<slot name="water"></slot>
</div>
</template>
<script>
import mpegts from 'mpegts.js'
export default {
name: 'index',
props: {
src: {
type: String,
default: '',
},
//是否显示控件
control: {
type: Boolean,
default: true,
},
//视频流类型
videoType: {
type: String,
default: 'mse', // 'mse', 'mpegts', 'm2ts', 'flv' or 'mp4'
},
//是否自动播放
autoPlay: {
type: Boolean,
default: true,
},
//是否直播
isLive: {
type: Boolean,
default: true,
},
},
data() {
return {
player: null,
video: null,
canvas: null,
ctx: null,
isPlaying: true,
isControlVisible: false,
}
},
watch: {
src(val) {
if (val) {
this.destroyVideo()
this.drawVideo()
} else {
this.destroyVideo()
}
},
autoPlay: {
handler(val) {
if (val) this.isPlaying = true
else this.isPlaying = false
},
immediate: true,
},
},
computed: {
loading() {
return false
},
},
mounted() {
this.initCanvas()
this.src && this.drawVideo()
//窗口大小变化
window.addEventListener('resize', () => {
this.$emit('resize', {
width: this.$refs.canvasVideo.offsetWidth,
height: this.$refs.canvasVideo.offsetHeight,
})
})
this.$on('take-screenshot', this.takeScreenshot)
},
methods: {
// 将canvas元素转换为图片并下载
takeScreenshot() {
// ...
},
// 初始化canvas元素并设置初始样式
initCanvas() {
// ...
},
// 根据视频源绘制视频
drawVideo() {
// 根据视频类型创建播放器实例并加载视频
// ...
this.player = mpegts.createPlayer(
{
type: this.videoType,
isLive: this.isLive,
// ...
},
//性能优化相关配置 有问题可以相应修改 或者全部注释掉
{
enableWorker: false, // 启用分离线程进行转封装(目前不稳定)
enableStashBuffer: false, // 启用IO缓存,如果需要实时(最小延迟)播放直播流,请设置为false,但可能会因网络抖动而停顿
// ...
},
)
},
// 当视频元数据加载完成后设置canvas尺寸并绘制第一帧
loadedmetadata() {
// ...
},
// 当视频可播放时绘制视频帧
canplay() {
// ...
},
// 定时绘制视频帧以实现连续播放
draw() {
// ...
},
// 开始或暂停视频播放
play() {
// ...
},
// 切换播放/暂停状态
togglePlay() {
// ...
},
// 根据传入参数控制播放状态
controlPlay(control) {
// ...
},
// 重置canvas,用于重播
resetCanvas() {
// ...
},
// 切换全屏播放
toggleFullscreen(){
// ...
},
// 显示视频控制栏
showControl() {
this.isControlVisible = true
},
// 隐藏视频控制栏
hideControl() {
this.isControlVisible = false
},
// 销毁视频资源,清理播放器和video元素
destroyVideo() {
// ...
},
// 获取视频宽度
getVideoWidth() {
return this.video.videoWidth
},
// 获取视频高度
getVideoHeight() {
return this.video.videoHeight
},
},
//释放所有资源 断开视频直播流等
beforeDestroy() {
this.destroyVideo()
//清除画布
this.ctx && this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
//释放画布
this.canvas = null
this.ctx = null
},
}
</script>
组件样式
<style lang="scss" scoped>
.canvasVideo {
width: 100%;
height: 100%;
background-color: #000;
position: relative;
canvas {
position: absolute;
width: 100%;
height: 100%;
left: 0;
}
&:hover {
.canvasVideo_control {
display: block;
}
}
}
.canvasVideo_control {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 40px;
display: none;
background-color: rgba(0, 0, 0, 0.5);
&__content {
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
padding: 0 pxToRem(10);
.canvasVideo_control__left {
display: flex;
align-items: center;
i {
color: #fff;
font-size: 20px;
margin-right: 10px;
cursor: pointer;
}
}
.canvasVideo_control__right {
display: flex;
align-items: center;
i {
color: #fff;
font-size: 20px;
margin-left: 10px;
cursor: pointer;
}
}
}
}
</style>
获取完整代码
由于篇幅限制,以上展示的代码省略了大部分的实现逻辑和数据结构定义。如需获取完整的组件代码,请访问我的个人主页-资源库下载。
功能介绍
自动播放
在 CanvasVideo
组件中,我们通过 autoPlay
属性来控制视频的自动播放行为。该属性接受一个布尔值,当设置为 true
时,视频将在组件加载完成后立即开始播放,无需用户手动点击播放按钮。这一特性特别适合直播场景,因为它可以确保用户进入页面即刻获得内容,提升用户体验。
视频截图
为了增强用户互动并提供更多价值,CanvasVideo
组件提供了一个截图功能。用户可以通过点击界面上的截图按钮,触发 takeScreenshot
方法。此方法利用 canvas
元素实时渲染视频帧,并将其转换为图片数据 URL。随后,组件创建一个临时的下载链接,引导浏览器让用户下载当前的视频画面作为图片。这个功能对于用户保存重要直播时刻非常实用。
重播
直播视频通常具有时效性,但用户可能希望在直播结束后重新观看。为此,CanvasVideo
组件实现了 resetCanvas
方法,它能够重置 canvas
状态并重新加载视频流。当直播结束或用户希望重新观看时,只需点击重播按钮,组件就会清空当前 canvas
内容,并从头开始加载视频流,实现无缝重播。
放大缩小
视频的观看体验与屏幕大小密切相关。为了适应不同设备的屏幕尺寸和用户的操作习惯,toggleFullscreen
方法允许用户切换视频播放的全屏状态。当用户点击全屏按钮时,视频容器会扩展至浏览器的全屏模式,从而放大视频画面。退出全屏模式则可以缩小视频画面,使其适应原容器大小。这一功能使得视频播放更加灵活,满足用户在不同场景下的观看需求。
结论
通过精心设计的 LiveVideo
和 CanvasVideo
组件,我们实现了一个功能全面且易于集成的直播视频播放解决方案。该方案不仅简化了前端开发流程,还显著提升了代码的可维护性和可重用性。组件化的设计使得未来功能的扩展和维护变得更加简单。