396 lines
9.4 KiB
Markdown
396 lines
9.4 KiB
Markdown
# 视频异常处理逻辑总结
|
||
|
||
## 📊 **整体流程**
|
||
|
||
```
|
||
视频加载 → URL测试 → 元数据加载 → 黑屏检测 → 播放 → 错误处理
|
||
```
|
||
|
||
---
|
||
|
||
## 1️⃣ **视频URL测试(testVideoUrl)**
|
||
|
||
### **触发时机:**
|
||
- 加载视频课件时(`loadCourseware`)
|
||
|
||
### **处理逻辑:**
|
||
```javascript
|
||
async testVideoUrl(url) {
|
||
// 使用HEAD请求测试文件是否存在(不下载文件内容)
|
||
const res = await uni.request({
|
||
url: url,
|
||
method: 'HEAD',
|
||
timeout: 5000
|
||
})
|
||
|
||
if (res.statusCode === 200) {
|
||
✅ 视频文件可访问
|
||
- 记录Content-Type
|
||
- 记录Content-Length
|
||
} else if (res.statusCode === 404) {
|
||
❌ 视频文件不存在(404)
|
||
- 检查数据库filePath是否正确
|
||
} else if (res.statusCode === 403) {
|
||
❌ 没有权限访问(403)
|
||
} else {
|
||
⚠️ 其他响应码
|
||
}
|
||
}
|
||
```
|
||
|
||
### **作用:**
|
||
- ✅ 提前检测视频是否可访问
|
||
- ✅ 输出详细日志,便于调试
|
||
- ⚠️ **只是日志输出,不会阻止播放**
|
||
|
||
---
|
||
|
||
## 2️⃣ **黑屏检测与自动修复(onVideoLoadedMetadata)**
|
||
|
||
### **触发时机:**
|
||
- 视频元数据加载完成时
|
||
|
||
### **检测逻辑:**
|
||
```javascript
|
||
if (e.detail.width === 0 || e.detail.height === 0) {
|
||
⚠️ 检测到视频尺寸异常(黑屏)
|
||
|
||
if (videoRetryCount < 3) {
|
||
// 自动重试修复
|
||
videoRetryCount++
|
||
|
||
// 强制重新加载视频
|
||
this.videoId = 'course-video-' + Date.now() // 重新生成videoId
|
||
this.videoUrl = ''
|
||
await this.$nextTick()
|
||
this.videoUrl = currentUrl
|
||
this.initialTime = currentInitialTime
|
||
|
||
✅ 尝试修复黑屏(最多3次)
|
||
} else {
|
||
❌ 已达到最大重试次数,放弃修复
|
||
显示提示:'视频加载异常,请退出重试'
|
||
}
|
||
} else {
|
||
✅ 视频尺寸正常,重置重试计数器
|
||
videoRetryCount = 0
|
||
}
|
||
```
|
||
|
||
### **修复机制:**
|
||
1. **检测**:宽度或高度为0 → 黑屏
|
||
2. **重试**:最多3次
|
||
3. **方法**:重新生成videoId,强制组件重建
|
||
4. **失败**:显示提示,要求用户退出重试
|
||
|
||
---
|
||
|
||
## 3️⃣ **播放错误处理(onVideoError)**
|
||
|
||
### **触发时机:**
|
||
- 视频播放过程中发生错误
|
||
|
||
### **错误分类与处理:**
|
||
|
||
#### **A. 根据错误码判断**
|
||
```javascript
|
||
if (e.detail?.errCode === -1) {
|
||
❌ 视频文件不存在或无法访问
|
||
|
||
} else if (e.detail?.errCode === -2) {
|
||
❌ 视频格式不支持
|
||
|
||
} else if (errMsg.includes('404')) {
|
||
❌ 视频文件未找到(404)
|
||
|
||
} else if (errMsg.includes('403')) {
|
||
❌ 没有权限访问视频文件(403)
|
||
|
||
} else if (errMsg.includes('timeout')) {
|
||
❌ 视频加载超时,请检查网络
|
||
}
|
||
```
|
||
|
||
#### **B. 自动尝试URL编码切换**
|
||
```javascript
|
||
// 构建原始URL(未编码)
|
||
const originalUrl = config.FILE_BASE_URL + filePath
|
||
|
||
// 如果当前URL是编码的,尝试未编码版本
|
||
if (this.videoUrl !== originalUrl) {
|
||
console.log('尝试切换到未编码的URL')
|
||
this.videoUrl = originalUrl
|
||
|
||
// 等待500ms后重新尝试播放
|
||
setTimeout(() => {
|
||
const videoContext = uni.createVideoContext('course-video')
|
||
videoContext.play()
|
||
videoContext.pause()
|
||
}, 500)
|
||
} else {
|
||
// 已经尝试过未编码版本,显示错误提示
|
||
显示Modal:
|
||
- 标题:'视频播放失败'
|
||
- 内容:错误信息 + 服务器地址 + 文件路径
|
||
}
|
||
```
|
||
|
||
#### **C. 显示详细错误信息**
|
||
```javascript
|
||
uni.showModal({
|
||
title: '视频播放失败',
|
||
content: `
|
||
${errorMsg}
|
||
|
||
请检查:
|
||
1. 服务器是否运行
|
||
2. 视频文件是否存在
|
||
3. 网络连接是否正常
|
||
|
||
服务器: ${config.FILE_BASE_URL}
|
||
路径: ${courseware.filePath}
|
||
`,
|
||
showCancel: false,
|
||
confirmText: '知道了'
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
## 4️⃣ **视频加载失败的完整处理流程**
|
||
|
||
```mermaid
|
||
graph TD
|
||
A[开始加载视频] --> B{URL测试}
|
||
B -->|200 OK| C[加载视频]
|
||
B -->|404/403/其他| D[输出日志,继续尝试加载]
|
||
|
||
C --> E{元数据加载}
|
||
E -->|成功| F{尺寸检测}
|
||
|
||
F -->|正常| G[开始播放]
|
||
F -->|黑屏| H{重试次数<3?}
|
||
|
||
H -->|是| I[重新加载视频]
|
||
I --> C
|
||
H -->|否| J[提示用户:请退出重试]
|
||
|
||
E -->|失败| K[触发onVideoError]
|
||
G --> L{播放过程}
|
||
L -->|正常| M[播放完成]
|
||
L -->|错误| K
|
||
|
||
K --> N{URL编码尝试}
|
||
N -->|未尝试| O[切换到未编码URL]
|
||
O --> C
|
||
N -->|已尝试| P[显示错误提示Modal]
|
||
```
|
||
|
||
---
|
||
|
||
## 5️⃣ **不能播放的视频如何处理?**
|
||
|
||
### **A. 自动处理(无需用户干预)**
|
||
|
||
#### **1. 黑屏问题**
|
||
- ✅ **自动重试3次**
|
||
- ✅ **每次间隔300ms**
|
||
- ✅ **成功后重置计数器**
|
||
- ❌ **3次失败后提示用户**
|
||
|
||
#### **2. URL编码问题**
|
||
- ✅ **自动尝试未编码版本**
|
||
- ✅ **间隔500ms后重试**
|
||
- ❌ **失败后显示Modal**
|
||
|
||
---
|
||
|
||
### **B. 需要用户干预**
|
||
|
||
#### **1. 文件不存在(404)**
|
||
```
|
||
结果:显示Modal
|
||
提示:视频文件未找到(404)
|
||
服务器: http://xxx
|
||
路径: /profile/upload/xxx.mp4
|
||
|
||
建议:
|
||
- 检查服务器是否运行
|
||
- 检查视频文件是否存在
|
||
- 检查filePath是否正确
|
||
```
|
||
|
||
#### **2. 权限问题(403)**
|
||
```
|
||
结果:显示Modal
|
||
提示:没有权限访问视频文件(403)
|
||
|
||
建议:
|
||
- 检查服务器权限配置
|
||
- 检查防火墙设置
|
||
```
|
||
|
||
#### **3. 网络超时**
|
||
```
|
||
结果:显示Modal
|
||
提示:视频加载超时,请检查网络
|
||
|
||
建议:
|
||
- 检查网络连接
|
||
- 检查服务器响应速度
|
||
```
|
||
|
||
#### **4. 格式不支持(-2)**
|
||
```
|
||
结果:显示Modal
|
||
提示:视频格式不支持
|
||
|
||
建议:
|
||
- 转换视频格式为MP4
|
||
- 确保编码为H.264
|
||
```
|
||
|
||
---
|
||
|
||
### **C. 不影响学习进度**
|
||
|
||
#### **关键逻辑:**
|
||
```javascript
|
||
// 即使视频无法播放,图片/PDF仍然可以正常学习
|
||
// 进度计算:已完成课件数 / 总课件数
|
||
|
||
if ("video".equals(cw.getType())) {
|
||
if (detail != null && detail.getVideoPosition() != null) {
|
||
// 有观看记录才计入完成
|
||
if (videoPosition >= duration * 0.8) {
|
||
completedCount++
|
||
}
|
||
}
|
||
} else {
|
||
// 图片/PDF:查看过视为完成
|
||
if (detail != null) {
|
||
completedCount++ // ✅ 不影响
|
||
}
|
||
}
|
||
```
|
||
|
||
#### **示例:**
|
||
```
|
||
课程有:5个视频 + 3个图片
|
||
|
||
情况1:所有视频都能播放
|
||
- 看完3个视频 + 3个图片 → 进度 = 6/8 = 75%
|
||
|
||
情况2:有2个视频无法播放
|
||
- 看完3个视频 + 3个图片 → 进度 = 6/8 = 75%
|
||
- 无法播放的2个视频不计入(除非有观看记录)
|
||
|
||
情况3:所有视频都无法播放
|
||
- 看完3个图片 → 进度 = 3/8 = 37.5%
|
||
- 用户仍可学习图片课件
|
||
```
|
||
|
||
---
|
||
|
||
## 6️⃣ **日志输出(便于调试)**
|
||
|
||
### **加载阶段:**
|
||
```
|
||
[课程学习] 📦 加载课件数据: {...}
|
||
[课程学习] 课件ID: 893
|
||
[课程学习] 课件类型: video
|
||
[课程学习] 🔗 URL构建信息:
|
||
- FILE_BASE_URL: http://localhost:8080
|
||
- 原始filePath: /profile/upload/xxx.mp4
|
||
- 完整URL: http://localhost:8080/profile/upload/xxx.mp4
|
||
[课程学习] 🧪 测试视频URL可访问性: ...
|
||
[课程学习] ✅ 视频文件可访问
|
||
```
|
||
|
||
### **元数据加载:**
|
||
```
|
||
[课程学习] ✅ 视频元数据加载完成
|
||
[课程学习] 视频时长: 300 秒
|
||
[课程学习] 视频宽度: 1920
|
||
[课程学习] 视频高度: 1080
|
||
[课程学习] ✅ 视频尺寸正常,重置重试计数器
|
||
```
|
||
|
||
### **错误阶段:**
|
||
```
|
||
[课程学习] ❌❌❌ 视频播放错误 ❌❌❌
|
||
[课程学习] 错误码: -1
|
||
[课程学习] 错误信息: file not found
|
||
[课程学习] 视频URL: http://localhost:8080/profile/upload/xxx.mp4
|
||
[课程学习] 完整错误对象: {...}
|
||
```
|
||
|
||
---
|
||
|
||
## 7️⃣ **总结**
|
||
|
||
### **自动处理机制:**
|
||
1. ✅ **URL测试**:提前检测可访问性
|
||
2. ✅ **黑屏修复**:最多3次自动重试
|
||
3. ✅ **编码切换**:自动尝试未编码URL
|
||
4. ✅ **详细日志**:便于定位问题
|
||
|
||
### **用户体验:**
|
||
- ✅ 大部分问题自动修复,无需用户干预
|
||
- ✅ 修复失败时显示详细错误信息和建议
|
||
- ✅ 不影响其他课件的学习
|
||
- ✅ 进度计算正确,不会因个别视频问题阻塞
|
||
|
||
### **不影响进度:**
|
||
- ✅ 无法播放的视频不计入完成
|
||
- ✅ 图片/PDF可正常学习并计入进度
|
||
- ✅ 用户可跳过问题视频,继续学习其他内容
|
||
|
||
---
|
||
|
||
## 🔧 **管理员排查步骤**
|
||
|
||
### **1. 检查后端日志**
|
||
```bash
|
||
# 查看课件信息
|
||
[INFO] 视频课件 893 (测试视频): 时长=300秒
|
||
[WARN] 视频课件 894 时长信息缺失或无效: null
|
||
|
||
# 查看进度计算
|
||
[INFO] 课程 123 进度计算完成: 5个已完成课件 / 8个总课件 = 62.5%
|
||
```
|
||
|
||
### **2. 检查前端日志**
|
||
```bash
|
||
# 查看URL构建
|
||
[课程学习] 🔗 URL构建信息:
|
||
- FILE_BASE_URL: http://localhost:8080
|
||
- 原始filePath: /profile/upload/2025/12/11/xxx.mp4
|
||
|
||
# 查看错误信息
|
||
[课程学习] ❌ 视频文件不存在(404)
|
||
[课程学习] 请检查数据库中的filePath是否正确
|
||
```
|
||
|
||
### **3. 验证文件**
|
||
```bash
|
||
# 检查文件是否存在
|
||
ls -lh /path/to/upload/profile/upload/2025/12/11/xxx.mp4
|
||
|
||
# 检查文件权限
|
||
chmod 644 /path/to/upload/profile/upload/2025/12/11/xxx.mp4
|
||
```
|
||
|
||
### **4. 测试URL**
|
||
```bash
|
||
# 使用curl测试
|
||
curl -I http://localhost:8080/profile/upload/2025/12/11/xxx.mp4
|
||
|
||
# 预期结果:HTTP/1.1 200 OK
|
||
```
|
||
|
||
---
|
||
|
||
**最后更新:** 2025-12-11
|
||
**文档版本:** v1.0
|