<template>
<div class="video-container" :style="containerStyle">
<video
ref="videoRef"
class="video-player"
:width="width"
:height="height"
:poster="poster"
:autoplay="autoplay"
:loop="loop"
:muted="muted"
:controls="showControls"
:preload="preload"
@play="onPlay"
@pause="onPause"
@ended="onEnded"
@timeupdate="onTimeUpdate"
@volumechange="onVolumeChange"
@error="onError"
>
<source v-for="source in sources" :key="source.src" :src="source.src" :type="source.type" />
您的浏览器不支持 HTML5 视频标签。
</video>
<!-- 自定义控制条(可选) -->
<div v-if="customControls" class="custom-controls">
<button @click="togglePlay">
{{ isPlaying ? '暂停' : '播放' }}
</button>
<input
type="range"
min="0"
:max="duration"
v-model="currentTime"
@input="seek"
/>
<button @click="toggleMute">
{{ isMuted ? '取消静音' : '静音' }}
</button>
<input
type="range"
min="0"
max="1"
step="0.01"
v-model="volume"
@input="changeVolume"
/>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue'
const props = defineProps({
// 视频源,支持多个源格式
sources: {
type: Array,
required: true,
validator: (value) => {
return value.every(source => source.src && source.type)
}
},
// 视频宽度
width: {
type: [String, Number],
default: '100%'
},
// 视频高度
height: {
type: [String, Number],
default: 'auto'
},
// 封面图
poster: {
type: String,
default: ''
},
// 是否自动播放
autoplay: {
type: Boolean,
default: false
},
// 是否循环播放
loop: {
type: Boolean,
default: false
},
// 是否静音
muted: {
type: Boolean,
default: false
},
// 是否显示原生控制条
showControls: {
type: Boolean,
default: true
},
// 预加载方式
preload: {
type: String,
default: 'auto',
validator: (value) => ['auto', 'metadata', 'none'].includes(value)
},
// 是否使用自定义控制条
customControls: {
type: Boolean,
default: false
},
// 容器样式
containerStyle: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits([
'play',
'pause',
'ended',
'timeupdate',
'volumechange',
'error',
'ready'
])
const videoRef = ref(null)
const isPlaying = ref(false)
const isMuted = ref(props.muted)
const duration = ref(0)
const currentTime = ref(0)
const volume = ref(props.muted ? 0 : 1)
// 初始化视频
onMounted(() => {
if (videoRef.value) {
// 确保muted属性与props同步
videoRef.value.muted = props.muted
isMuted.value = props.muted
// 获取视频时长
const updateDuration = () => {
duration.value = videoRef.value.duration || 0
}
videoRef.value.addEventListener('loadedmetadata', updateDuration)
videoRef.value.addEventListener('durationchange', updateDuration)
emit('ready', videoRef.value)
}
})
// 播放/暂停切换
const togglePlay = () => {
if (!videoRef.value) return
if (isPlaying.value) {
videoRef.value.pause()
} else {
videoRef.value.play()
}
}
// 跳转到指定时间
const seek = () => {
if (videoRef.value) {
videoRef.value.currentTime = currentTime.value
}
}
// 音量调整
const changeVolume = () => {
if (videoRef.value) {
videoRef.value.volume = volume.value
isMuted.value = volume.value === 0
}
}
// 静音切换
const toggleMute = () => {
if (videoRef.value) {
videoRef.value.muted = !isMuted.value
isMuted.value = !isMuted.value
if (!isMuted.value) {
volume.value = videoRef.value.volume
}
}
}
// 事件处理
const onPlay = () => {
isPlaying.value = true
emit('play')
}
const onPause = () => {
isPlaying.value = false
emit('pause')
}
const onEnded = () => {
isPlaying.value = false
emit('ended')
}
const onTimeUpdate = () => {
if (videoRef.value) {
currentTime.value = videoRef.value.currentTime
emit('timeupdate', currentTime.value)
}
}
const onVolumeChange = () => {
if (videoRef.value) {
volume.value = videoRef.value.volume
isMuted.value = videoRef.value.muted
emit('volumechange', {
volume: volume.value,
muted: isMuted.value
})
}
}
const onError = () => {
if (videoRef.value) {
emit('error', videoRef.value.error)
}
}
// 暴露方法给父组件
defineExpose({
play: () => videoRef.value?.play(),
pause: () => videoRef.value?.pause(),
togglePlay,
toggleMute,
seek,
setVolume: (val) => {
if (videoRef.value) {
videoRef.value.volume = val
volume.value = val
}
},
getVideoElement: () => videoRef.value
})
</script>
<style scoped>
.video-container {
position: relative;
display: inline-block;
line-height: 0;
}
.video-player {
max-width: 100%;
height: auto;
display: block;
background-color: #000;
}
.custom-controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.5);
padding: 10px;
display: flex;
align-items: center;
gap: 10px;
}
.custom-controls button {
background: #fff;
border: none;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
}
.custom-controls input[type="range"] {
flex-grow: 1;
}
</style>
使用
<template>
<div>
<VideoPlayer
:sources="[
{ src: 'https://example.com/video.mp4', type: 'video/mp4' },
{ src: 'https://example.com/video.webm', type: 'video/webm' }
]"
poster="https://example.com/poster.jpg"
:autoplay="false"
:loop="false"
:customControls="true"
@play="handlePlay"
@pause="handlePause"
ref="videoPlayer"
/>
<button @click="$refs.videoPlayer.togglePlay()">外部控制播放/暂停</button>
</div>
</template>
<script setup>
import VideoPlayer from './components/VideoPlayer.vue'
const handlePlay = () => {
console.log('视频开始播放')
}
const handlePause = () => {
console.log('视频暂停')
}
</script>