更新:课程视频播放改成原生video组件

This commit is contained in:
2025-11-21 18:09:24 +08:00
parent 754865e23e
commit ac60a863e3
15 changed files with 1729 additions and 333 deletions

View File

@@ -0,0 +1,189 @@
// components/video-player/composables/useVideoPlayer.ts
import { ref, computed } from 'vue'
import { useVideoAPI } from './useVideoAPI'
import { useVideoProgress } from './useVideoProgress'
import type { IVideoInfo, IVideoPlayerProps, VideoErrorType } from '@/types/video'
/**
* 视频播放器核心逻辑
*/
export function useVideoPlayer(props: IVideoPlayerProps) {
const videoAPI = useVideoAPI()
const videoProgress = useVideoProgress()
// 状态
const currentVideoData = ref<IVideoInfo | null>(null)
const currentIndex = ref(props.currentIndex)
const isSetFirstTime = ref(false)
const isChanging = ref(false)
const showError = ref(false)
const errorMessage = ref('')
const canRetry = ref(false)
const platform = ref('')
// 获取平台信息
// #ifdef APP-PLUS
platform.value = uni.getSystemInfoSync().platform
// #endif
// #ifdef H5
platform.value = 'h5'
// #endif
/**
* 计算视频 URL
*/
const videoUrl = computed(() => {
if (!currentVideoData.value) return ''
if (currentVideoData.value.type === 0) {
// MP4 视频
return currentVideoData.value.mp4Url || currentVideoData.value.videoUrl || ''
} else {
// M3U8 视频
return currentVideoData.value.m3u8Url || ''
}
})
/**
* 计算封面图 URL
*/
const posterUrl = computed(() => {
if (!currentVideoData.value) return ''
// 如果是 MP4 视频,可以使用 OSS 视频截图功能
if (currentVideoData.value.type === 0 && currentVideoData.value.mp4Url) {
return `${currentVideoData.value.mp4Url}?x-oss-process=video/snapshot,t_1,f_jpg`
}
return ''
})
/**
* 检查视频是否可以播放
*/
const canPlayVideo = (videoInfo: IVideoInfo): boolean => {
// iOS 平台检查
if (platform.value === 'ios') {
if (videoInfo.type === 1 && !videoInfo.m3u8Url) {
return false
}
}
return true
}
/**
* 加载视频
*/
const loadVideo = async (index: number) => {
if (index < 0 || index >= props.videoList.length) {
showError.value = true
errorMessage.value = '视频索引超出范围'
canRetry.value = false
return
}
const videoItem = props.videoList[index]
currentIndex.value = index
// 重置状态
isSetFirstTime.value = false
showError.value = false
errorMessage.value = ''
canRetry.value = false
// 调用 API 获取视频信息
const videoInfo = await videoAPI.fetchVideoInfo({
id: videoItem.id
})
if (!videoInfo) {
showError.value = true
errorMessage.value = videoAPI.error.value?.message || '获取视频信息失败'
canRetry.value = true
return
}
// 检查视频类型和 URL
if (videoInfo.type === 1 && !videoInfo.m3u8Url) {
// M3U8 视频但没有 URL报告错误
await videoAPI.reportErrorVideo({
chapterId: videoInfo.chapterId,
videoId: videoInfo.id,
sort: videoInfo.sort
})
showError.value = true
errorMessage.value = '抱歉,本视频加密信息错误,已经提交错误信息,请过一段时间再来观看或联系客服'
canRetry.value = false
return
}
// 设置当前视频数据
currentVideoData.value = videoInfo
// 获取初始播放位置
const initialPosition = videoProgress.getInitialPosition(videoInfo)
videoProgress.updateCurrentTime(initialPosition)
return videoInfo
}
/**
* 切换视频
*/
const changeVideo = async (newIndex: number) => {
if (isChanging.value) return
isChanging.value = true
try {
// 保存当前视频进度
if (currentVideoData.value) {
await videoProgress.saveNow(currentVideoData.value)
}
// 停止进度保存
videoProgress.stopSaving()
// 加载新视频
await loadVideo(newIndex)
} finally {
isChanging.value = false
}
}
/**
* 重试加载视频
*/
const retry = () => {
loadVideo(currentIndex.value)
}
return {
// 状态
currentVideoData,
currentIndex,
isSetFirstTime,
isChanging,
showError,
errorMessage,
canRetry,
platform,
isLoading: videoAPI.isLoading,
// 计算属性
videoUrl,
posterUrl,
// 方法
loadVideo,
changeVideo,
retry,
canPlayVideo,
// 进度管理
videoProgress
}
}