更新:课程详情的初步代码
This commit is contained in:
242
components/course/VideoPlayer.vue
Normal file
242
components/course/VideoPlayer.vue
Normal file
@@ -0,0 +1,242 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user