190 lines
4.3 KiB
TypeScript
190 lines
4.3 KiB
TypeScript
// 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
|
||
}
|
||
}
|