自定义视频组件:集成 video.js
概述
本节介绍如何在 Vue 组件中集成 Video.js 视频播放器。Video.js 提供了三种嵌入方式:基于 <video> 标签增强、基于 <div> 元素创建、以及基于自定义元素 <video-js>。推荐使用 div 方式,更适合 Vue/React 等框架集成。
Video.js 三种嵌入方式
方式一:video 标签嵌入(Video Embed)
<!-- 直接在 video 标签上添加 class 和 data-setup -->
<video
class="video-js"
controls
data-setup='{}'
>
<source src="video.mp4" type="video/mp4" />
</video>
html
适合简单场景,需要手动创建 <video> 标签。
方式二:div 元素嵌入(Player Div Embed)— 推荐
<!-- Video.js 自动创建 video 标签 -->
<div
class="video-js"
data-setup='{"controls": true, "autoplay": false}'
>
</div>
html
推荐理由:Video.js 自动创建和管理 <video> 标签,适合 Vue/React 等框架中组件化集成。
方式三:自定义元素(Custom Element)
<video-js
class="video-js"
controls
data-setup='{}'
>
<source src="video.mp4" type="video/mp4" />
</video-js>
html
使用 Web Components 自定义元素方式。
方式对比
| 方式 | 适用场景 | Vue 适配度 |
|---|---|---|
| video 标签 | 简单页面 | 低 |
| div 元素 | 框架集成 | 高(推荐) |
| 自定义元素 | Web Components 项目 | 中 |
Vue 组件封装
安装依赖
npm install video.js
bash
组件实现
<!-- components/video/VideoPlayer.vue -->
<template>
<div ref="videoContainer" class="video-player-container">
<div ref="videoElement" class="video-js"></div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
import videojs from 'video.js'
import 'video.js/dist/video-js.css'
import type Player from 'video.js/dist/types/player'
interface VideoPlayerProps {
src: string
poster?: string
autoplay?: boolean
controls?: boolean
loop?: boolean
muted?: boolean
fluid?: boolean
width?: number
height?: number
playbackRates?: number[]
options?: Record<string, any>
}
interface VideoPlayerEmits {
(e: 'ready', player: Player): void
(e: 'play'): void
(e: 'pause'): void
(e: 'ended'): void
(e: 'timeupdate', currentTime: number): void
}
const props = withDefaults(defineProps<VideoPlayerProps>(), {
autoplay: false,
controls: true,
loop: false,
muted: false,
fluid: true,
playbackRates: () => [0.5, 1, 1.5, 2]
})
const emit = defineEmits<VideoPlayerEmits>()
const videoElement = ref<HTMLElement>()
let player: Player | null = null
onMounted(() => {
initPlayer()
})
onUnmounted(() => {
disposePlayer()
})
function initPlayer() {
if (!videoElement.value) return
player = videojs(videoElement.value, {
controls: props.controls,
autoplay: props.autoplay,
loop: props.loop,
muted: props.muted,
fluid: props.fluid,
width: props.width,
height: props.height,
poster: props.poster,
playbackRates: props.playbackRates,
sources: [{ src: props.src, type: getVideoType(props.src) }],
...props.options
})
// 事件绑定
player.on('ready', () => emit('ready', player!))
player.on('play', () => emit('play'))
player.on('pause', () => emit('pause'))
player.on('ended', () => emit('ended'))
player.on('timeupdate', () => {
emit('timeupdate', player!.currentTime()!)
})
}
function disposePlayer() {
if (player) {
player.dispose()
player = null
}
}
function getVideoType(src: string): string {
const ext = src.split('.').pop()?.toLowerCase()
const typeMap: Record<string, string> = {
mp4: 'video/mp4',
webm: 'video/webm',
ogg: 'video/ogg',
m3u8: 'application/x-mpegURL',
flv: 'video/x-flv'
}
return typeMap[ext || ''] || 'video/mp4'
}
// 监听 src 变化
watch(
() => props.src,
(newSrc) => {
if (player && newSrc) {
player.src({ src: newSrc, type: getVideoType(newSrc) })
}
}
)
defineExpose({
getPlayer: () => player,
play: () => player?.play(),
pause: () => player?.pause(),
currentTime: (time?: number) => {
if (time !== undefined) player?.currentTime(time)
return player?.currentTime()
},
dispose: disposePlayer
})
</script>
<style scoped>
.video-player-container {
width: 100%;
}
</style>
vue
使用方式
<template>
<VideoPlayer
src="https://example.com/video.mp4"
poster="cover.jpg"
:playback-rates="[0.5, 1, 1.5, 2]"
@play="onPlay"
@pause="onPause"
/>
</template>
<script setup lang="ts">
import VideoPlayer from '@/components/video/VideoPlayer.vue'
function onPlay() {
console.log('Video playing')
}
function onPause() {
console.log('Video paused')
}
</script>
vue
默认配置选项
const defaultOptions = {
controls: true, // 显示控制栏
autoplay: false, // 自动播放
muted: false, // 静音
loop: false, // 循环播放
fluid: true, // 响应式宽度
preload: 'auto', // 预加载策略
playbackRates: [0.5, 1, 1.5, 2], // 倍速选项
controlBar: {
children: [
'playToggle',
'volumePanel',
'currentTimeDisplay',
'timeDivider',
'durationDisplay',
'playbackRateMenuButton',
'fullscreenToggle'
]
}
}
typescript
控制栏定制
// 自定义控制栏按钮顺序
controlBar: {
children: [
'playToggle', // 播放/暂停
'volumePanel', // 音量
'currentTimeDisplay', // 当前时间
'timeDivider', // 时间分隔符
'durationDisplay', // 总时长
'progressControl', // 进度条
'playbackRateMenuButton', // 倍速菜单
'fullscreenToggle' // 全屏
]
}
typescript
实践要点
- 推荐使用 div 嵌入方式(方式二),Video.js 自动管理 video 标签生命周期
player.dispose()必须在onUnmounted中调用,释放资源- 监听
src变化时使用player.src()方法切换视频源 playbackRates配置倍速菜单,提供 0.5x/1x/1.5x/2x 常用选项- 通过
defineExpose暴露播放控制方法,支持父组件操作播放器
↑