hookehuyr

feat(课程页面): 添加视频播放错误处理和媒体文件预览功能

为视频播放器添加错误处理覆盖层和自动重试机制,优化视频加载配置
在课程详情页增加音频和视频播放弹窗,改进文件预览功能
根据设备类型显示不同的文件操作按钮,支持多种媒体格式预览
......@@ -10,6 +10,14 @@
@play="handlePlay"
@pause="handlePause"
/>
<!-- 错误提示覆盖层 -->
<div v-if="showErrorOverlay" class="error-overlay">
<div class="error-content">
<div class="error-icon">⚠️</div>
<div class="error-message">{{ errorMessage }}</div>
<button @click="retryLoad" class="retry-button">重试</button>
</div>
</div>
</div>
</template>
......@@ -45,20 +53,47 @@ const emit = defineEmits(["onPlay", "onPause"]);
const videoRef = ref(null);
const player = ref(null);
const state = ref(null);
const showErrorOverlay = ref(false);
const errorMessage = ref('');
const retryCount = ref(0);
const maxRetries = 3;
const videoOptions = computed(() => ({
controls: true,
preload: "auto",
preload: "metadata", // 改为metadata以减少初始加载
responsive: true,
autoplay: props.autoplay,
// 启用倍速播放功能
playbackRates: [0.5, 0.75, 1, 1.25, 1.5, 2],
// 添加多种格式支持
sources: [
{
src: props.videoUrl,
type: "video/mp4",
},
// 备用源,如果主源失败则尝试其他格式
{
src: props.videoUrl,
type: "video/webm",
},
{
src: props.videoUrl,
type: "video/ogg",
},
],
// HTML5配置优化
html5: {
vhs: {
overrideNative: !videojs.browser.IS_SAFARI,
},
nativeVideoTracks: false,
nativeAudioTracks: false,
nativeTextTracks: false,
},
// 错误处理配置
errorDisplay: true,
// 网络和加载配置
techOrder: ['html5'],
// onPlay: () => emit("onPlay"),
// onPause: () => emit("onPause"),
userActions: {
......@@ -81,9 +116,72 @@ const handleMounted = (payload) => {
state.value = payload.state;
player.value = payload.player;
if (player.value) {
// 添加错误处理监听器
player.value.on('error', (error) => {
console.error('VideoJS播放错误:', error);
const errorCode = player.value.error();
if (errorCode) {
console.error('错误代码:', errorCode.code, '错误信息:', errorCode.message);
// 显示用户友好的错误信息
showErrorOverlay.value = true;
// 根据错误类型进行处理
switch (errorCode.code) {
case 4: // MEDIA_ERR_SRC_NOT_SUPPORTED
errorMessage.value = '视频格式不支持或无法加载,请检查网络连接';
console.warn('视频格式不支持,尝试重新加载...');
// 自动重试(如果重试次数未超限)
if (retryCount.value < maxRetries) {
setTimeout(() => {
retryLoad();
}, 1000);
}
break;
case 3: // MEDIA_ERR_DECODE
errorMessage.value = '视频解码失败,可能是文件损坏';
console.warn('视频解码错误');
break;
case 2: // MEDIA_ERR_NETWORK
errorMessage.value = '网络连接错误,请检查网络后重试';
console.warn('网络错误,尝试重新加载...');
if (retryCount.value < maxRetries) {
setTimeout(() => {
retryLoad();
}, 2000);
}
break;
case 1: // MEDIA_ERR_ABORTED
errorMessage.value = '视频加载被中止';
console.warn('视频加载被中止');
break;
default:
errorMessage.value = '视频播放出现未知错误';
}
}
});
// 添加加载状态监听
player.value.on('loadstart', () => {
console.log('开始加载视频');
showErrorOverlay.value = false; // 隐藏错误提示
});
player.value.on('canplay', () => {
console.log('视频可以播放');
showErrorOverlay.value = false; // 隐藏错误提示
retryCount.value = 0; // 重置重试计数
});
player.value.on('loadedmetadata', () => {
console.log('视频元数据加载完成');
});
// TAG: 自动播放
if (props.autoplay) {
player.value.play();
player.value.play().catch(error => {
console.warn('自动播放失败:', error);
});
}
// if (!wxInfo().isPc && !wxInfo().isWeiXinDesktop) { // 非PC端,且非微信PC端
......@@ -125,6 +223,24 @@ const handlePause = (payload) => {
emit("onPause", payload)
}
/**
* 重试加载视频
*/
const retryLoad = () => {
if (retryCount.value >= maxRetries) {
errorMessage.value = '重试次数已达上限,请稍后再试';
return;
}
retryCount.value++;
showErrorOverlay.value = false;
if (player.value && !player.value.isDisposed()) {
console.log(`第${retryCount.value}次重试加载视频`);
player.value.load();
}
};
onBeforeUnmount(() => {
if (videoRef.value?.$player) {
videoRef.value.$player.dispose();
......@@ -133,8 +249,8 @@ onBeforeUnmount(() => {
defineExpose({
pause() {
if (player && typeof player?.pause === 'function') {
player?.pause();
if (player.value && typeof player.value.pause === 'function') {
player.value.pause();
emit('onPause', player.value);
}
},
......@@ -171,6 +287,52 @@ defineExpose({
opacity: 0.6;
}
/* 错误覆盖层样式 */
.error-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.error-content {
text-align: center;
color: white;
padding: 20px;
}
.error-icon {
font-size: 48px;
margin-bottom: 16px;
}
.error-message {
font-size: 16px;
margin-bottom: 20px;
line-height: 1.5;
}
.retry-button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
.retry-button:hover {
background: #0056b3;
}
:deep(.vjs-big-play-button) {
display: none !important;
}
......
......@@ -7,7 +7,7 @@
</div>
<!-- Featured Course Banner -->
<div class="px-4 mb-5">
<div class="px-4 mb-5" v-if="bannerList.length">
<van-swipe
class="rounded-xl overflow-hidden shadow-lg h-40"
:autoplay="3000"
......
This diff is collapsed. Click to expand it.