// 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(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 } }