Files
taimed-international-app/components/course/VideoPlayer.vue

243 lines
4.5 KiB
Vue

<template>
<view class="video-player">
<!-- 视频播放器 -->
<video
v-if="currentVideo"
:id="videoId"
:src="currentVideo.url"
:title="currentVideo.title"
:controls="true"
:show-fullscreen-btn="true"
:show-play-btn="true"
:enable-progress-gesture="true"
:object-fit="objectFit"
class="video-element"
@fullscreenchange="handleFullscreenChange"
@ended="handleVideoEnd"
@error="handleVideoError"
/>
<!-- 自动播放下一个提示 -->
<view v-if="showCountDown && hasNext" class="countdown-overlay">
<view class="countdown-content">
<text class="countdown-text">{{ countDownSeconds }}秒后自动播放下一个</text>
<wd-button size="small" @click="cancelAutoPlay">
取消自动播放
</wd-button>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import type { IVideo } from '@/types/course'
interface Props {
videoList: IVideo[]
currentIndex: number
noRecored?: boolean // 是否为试听(不记录进度)
}
const props = defineProps<Props>()
const emit = defineEmits<{
end: []
fullscreen: [isFullScreen: boolean]
change: [index: number]
}>()
const videoId = 'course-video-player'
const videoContext = ref<any>(null)
const objectFit = ref<'contain' | 'fill' | 'cover'>('contain')
const showCountDown = ref(false)
const countDownSeconds = ref(10)
const countDownTimer = ref<any>(null)
/**
* 当前视频
*/
const currentVideo = computed(() => {
if (props.videoList.length === 0) return null
return props.videoList[props.currentIndex] || null
})
/**
* 是否有下一个视频
*/
const hasNext = computed(() => {
return props.currentIndex < props.videoList.length - 1
})
/**
* 初始化视频上下文
*/
const initVideoContext = () => {
videoContext.value = uni.createVideoContext(videoId)
}
/**
* 视频播放结束
*/
const handleVideoEnd = () => {
emit('end')
// 如果有下一个视频,开始倒计时
if (hasNext.value) {
startCountDown()
}
}
/**
* 开始倒计时
*/
const startCountDown = () => {
showCountDown.value = true
countDownSeconds.value = 10
countDownTimer.value = setInterval(() => {
countDownSeconds.value--
if (countDownSeconds.value <= 0) {
stopCountDown()
playNext()
}
}, 1000)
}
/**
* 停止倒计时
*/
const stopCountDown = () => {
if (countDownTimer.value) {
clearInterval(countDownTimer.value)
countDownTimer.value = null
}
showCountDown.value = false
}
/**
* 取消自动播放
*/
const cancelAutoPlay = () => {
stopCountDown()
}
/**
* 播放下一个视频
*/
const playNext = () => {
if (hasNext.value) {
emit('change', props.currentIndex + 1)
}
}
/**
* 全屏变化
*/
const handleFullscreenChange = (e: any) => {
const isFullScreen = e.detail.fullScreen
emit('fullscreen', isFullScreen)
// 全屏时使用 cover 模式
objectFit.value = isFullScreen ? 'cover' : 'contain'
}
/**
* 视频错误
*/
const handleVideoError = (e: any) => {
console.error('视频播放错误:', e)
uni.showToast({
title: '课程视频播放组件正在开发中,现在还不能播放视频',
icon: 'none'
})
}
/**
* 播放视频
*/
const play = () => {
if (videoContext.value) {
videoContext.value.play()
}
}
/**
* 暂停视频
*/
const pause = () => {
if (videoContext.value) {
videoContext.value.pause()
}
}
/**
* 停止视频
*/
const stop = () => {
if (videoContext.value) {
videoContext.value.stop()
}
}
// 监听视频变化,重新播放
watch(() => props.currentIndex, () => {
stopCountDown()
// 延迟播放,确保视频元素已更新
setTimeout(() => {
play()
}, 300)
})
onMounted(() => {
initVideoContext()
})
onUnmounted(() => {
stopCountDown()
stop()
})
// 暴露方法给父组件
defineExpose({
play,
pause,
stop,
cancelAutoPlay: stopCountDown
})
</script>
<style lang="scss" scoped>
.video-player {
position: relative;
width: 100%;
background-color: #000;
.video-element {
width: 100%;
height: 400rpx;
}
.countdown-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
padding: 20rpx;
.countdown-content {
display: flex;
justify-content: space-between;
align-items: center;
.countdown-text {
color: #fff;
font-size: 28rpx;
}
}
}
}
</style>