# Video.js视频播放

https://github.com/videojs/video.js

# VideoPlayer 组件

基于 video.js 的 Vue 3 视频播放器组件,提供了简单易用的接口和丰富的功能。

# 安装依赖

确保已安装 video.js:

npm install video.js
# 或
yarn add video.js
1
2
3

# 基本用法

<template>
  <VideoPlayer
    :src="videoSrc"
    :poster="posterUrl"
    :autoplay="false"
    :controls="true"
    @ready="onPlayerReady"
    @play="onPlay"
    @pause="onPause"
  />
</template>

<script setup>
import { ref } from 'vue'
import VideoPlayer from '@/components/VideoPlayer'

const videoSrc = ref('https://example.com/video.mp4')
const posterUrl = ref('https://example.com/poster.jpg')

const onPlayerReady = (player) => {
  console.log('播放器已就绪', player)
}

const onPlay = () => {
  console.log('视频开始播放')
}

const onPause = () => {
  console.log('视频已暂停')
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 属性

属性名 类型 默认值 说明
src String '' 视频源URL
poster String '' 视频封面图片URL
autoplay Boolean false 是否自动播放
controls Boolean true 是否显示控制栏
muted Boolean false 是否静音
loop Boolean false 是否循环播放
width String/Number '100%' 播放器宽度
height String/Number 'auto' 播放器高度
fluid Boolean true 是否自适应容器大小
options Object {} video.js 的其他配置选项

# 事件

事件名 参数 说明
ready player 播放器初始化完成
play player 视频开始播放
pause player 视频暂停
ended player 视频播放结束
timeupdate currentTime 播放时间更新
volumechange volume 音量变化
error error 发生错误

# 方法

通过 ref 可以访问组件实例并调用以下方法:

<template>
  <VideoPlayer ref="videoPlayerRef" :src="videoSrc" />
  <button @click="playVideo">播放</button>
</template>

<script setup>
import { ref } from 'vue'
import VideoPlayer from '@/components/VideoPlayer'

const videoPlayerRef = ref(null)
const videoSrc = ref('https://example.com/video.mp4')

const playVideo = () => {
  videoPlayerRef.value?.play()
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
方法名 参数 返回值 说明
player - videojs实例 获取video.js播放器实例
play - - 播放视频
pause - - 暂停视频
reset - - 重置视频(回到开始并暂停)
seek time: number - 跳转到指定时间(秒)
setVolume volume: number - 设置音量(0-1)
toggleMute - - 切换静音状态

# 高级用法

# 自定义控制栏

可以通过 options 属性传递 video.js 的配置选项,实现自定义控制栏:

<template>
  <VideoPlayer
    :src="videoSrc"
    :options="{
      controlBar: {
        playToggle: true,
        volumePanel: true,
        fullscreenToggle: true,
        progressControl: true,
        remainingTimeDisplay: false
      }
    }"
  />
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 支持HLS和DASH流媒体

VideoPlayer组件已内置支持常见的视频格式识别,包括HLS和DASH流媒体:

<template>
  <VideoPlayer src="https://example.com/playlist.m3u8" />
</template>
1
2
3

# 注意事项

  1. 确保在组件卸载时正确销毁播放器实例,VideoPlayer组件已自动处理
  2. 对于大型应用,建议按需导入video.js以减小打包体积
  3. 如需更多自定义功能,可以通过options属性传递video.js配置

# 最终代码

<template>
    <div class="video-player-container">
        <video ref="videoContainer" class="video-js" :poster="poster">
            <source :src="src" :type="getSourceType(src)" />
            <p class="vjs-no-js">要查看此视频,请启用JavaScript并考虑升级到支持HTML5视频的浏览器</p>
        </video>
    </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import videojs from 'video.js'
import 'video.js/dist/video-js.css'
import type { VideoPlayerProps } from './types'
import type Player from 'video.js/dist/types/player'

const props = withDefaults(defineProps<VideoPlayerProps>(), {
    src: '',
    poster: '',
    autoplay: false,
    controls: true,
    muted: false,
    loop: false,
    width: '100%',
    height: 'auto',
    fluid: true,
    options: () => ({})
})

const emit = defineEmits(['ready', 'play', 'pause', 'ended', 'timeupdate', 'volumechange', 'error'])

const videoContainer = ref(null)
let player: Player | null = null

// 初始化播放器
const initializePlayer = () => {
    if (!videoContainer.value) return

    // 合并默认选项和传入的选项
    const videoOptions = {
        controls: props.controls,
        autoplay: props.autoplay,
        muted: props.muted,
        loop: props.loop,
        fluid: props.fluid,
        width: props.width,
        height: props.height,
        poster: props.poster,
        sources: [
            {
                src: props.src,
                type: getSourceType(props.src)
            }
        ],
        ...props.options
    }

    // 创建播放器实例
    player = videojs(videoContainer.value, videoOptions, function onPlayerReady() {
        emit('ready', player)
    })
}

/**
 * 根据文件扩展名获取媒体类型
 * @param src - 视频源URL
 * @returns 媒体类型
 */
const getSourceType = (src: string) => {
    if (!src) return ''

    const extension = src.split('.').pop()?.toLowerCase()

    switch (extension) {
        case 'mp4':
            return 'video/mp4'
        case 'webm':
            return 'video/webm'
        case 'ogg':
            return 'video/ogg'
        case 'hls':
        case 'm3u8':
            return 'application/x-mpegURL'
        case 'dash':
        case 'mpd':
            return 'application/dash+xml'
        default:
            return 'video/mp4'
    }
}

// 监听src变化,更新视频源
watch(
    () => props.src,
    (newSrc) => {
        if (player && newSrc) {
            player.src({
                src: newSrc,
                type: getSourceType(newSrc)
            })
        }
    }
)

// 组件挂载时初始化播放器
onMounted(() => {
    initializePlayer()
})

// 组件卸载前销毁播放器
onBeforeUnmount(() => {
    if (player) {
        player.dispose()
        player = null
    }
})

// 暴露播放器方法
defineExpose({
    player: () => player,
    play: () => player?.play(),
    pause: () => player?.pause(),
    reset: () => {
        if (player) {
            player.currentTime(0)
            player.pause()
        }
    },
    seek: (time: number) => {
        if (player) {
            player.currentTime(time)
        }
    },
    setVolume: (volume: number) => {
        if (player) {
            player.volume(volume)
        }
    },
    toggleMute: () => {
        if (player) {
            player.muted(!player.muted())
        }
    }
})
</script>

<style scoped>
.video-player-container {
    width: 100%;
    height: 100%;
    position: relative;
    min-height: 300px;
}

.video-js-container {
    width: 100%;
    height: 100%;
}

/* 确保控制栏样式正确 */
:deep(.vjs-control-bar) {
    display: flex;
    visibility: visible;
    opacity: 1;
    transition:
        visibility 0.1s,
        opacity 0.1s;
}

:deep(.video-js:hover .vjs-control-bar) {
    visibility: visible;
    opacity: 1;
}

/* 自定义控制栏颜色 */
:deep(.video-js .vjs-control-bar) {
    background-color: rgba(43, 51, 63, 0.7);
}

:deep(.video-js .vjs-play-progress) {
    background-color: #3498db;
}

:deep(.video-js .vjs-volume-level) {
    background-color: #3498db;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187