301 lines
6.6 KiB
Markdown
301 lines
6.6 KiB
Markdown
# 自动更新视频duration - 前端实现
|
||
|
||
## ✅ **后端接口已添加**
|
||
|
||
**接口地址:** `POST /study/courseware/updateDuration`
|
||
|
||
**参数:**
|
||
- `coursewareId`: 课件ID
|
||
- `duration`: 视频时长(秒)
|
||
|
||
**特点:**
|
||
- ✅ 允许匿名访问(`@Anonymous`)
|
||
- ✅ 只更新未配置duration的视频
|
||
- ✅ 防止重复覆盖已有的duration
|
||
|
||
---
|
||
|
||
## 📱 **前端调用代码(uni-app)**
|
||
|
||
### **方式1:在视频加载完成时调用**
|
||
|
||
```javascript
|
||
// 在视频播放页面
|
||
<template>
|
||
<view>
|
||
<video
|
||
:src="videoUrl"
|
||
@loadedmetadata="onVideoLoaded"
|
||
@timeupdate="onTimeUpdate"
|
||
></video>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
data() {
|
||
return {
|
||
coursewareId: null, // 从路由参数获取
|
||
videoUrl: '',
|
||
videoDuration: 0,
|
||
durationUpdated: false // 防止重复调用
|
||
}
|
||
},
|
||
|
||
onLoad(options) {
|
||
this.coursewareId = options.coursewareId;
|
||
this.videoUrl = options.videoUrl;
|
||
},
|
||
|
||
methods: {
|
||
// 视频元数据加载完成(可获取时长)
|
||
onVideoLoaded(e) {
|
||
console.log('视频加载完成', e);
|
||
|
||
// 获取视频时长
|
||
const duration = Math.floor(e.detail.duration);
|
||
this.videoDuration = duration;
|
||
|
||
console.log('视频时长:', duration, '秒');
|
||
|
||
// 自动更新到后端
|
||
if (!this.durationUpdated && duration > 0) {
|
||
this.updateVideoDuration(duration);
|
||
}
|
||
},
|
||
|
||
// 或者在第一次播放位置更新时调用
|
||
onTimeUpdate(e) {
|
||
// 只在第一次调用
|
||
if (!this.durationUpdated && e.detail.duration > 0) {
|
||
const duration = Math.floor(e.detail.duration);
|
||
this.videoDuration = duration;
|
||
this.updateVideoDuration(duration);
|
||
}
|
||
},
|
||
|
||
// 调用后端接口更新duration
|
||
updateVideoDuration(duration) {
|
||
if (this.durationUpdated) return;
|
||
|
||
this.$http.post('/study/courseware/updateDuration', {
|
||
coursewareId: this.coursewareId,
|
||
duration: duration
|
||
}).then(res => {
|
||
if (res.code === 200) {
|
||
console.log('✅ duration更新成功:', duration, '秒');
|
||
this.durationUpdated = true;
|
||
} else {
|
||
console.log('ℹ️ duration响应:', res.msg);
|
||
}
|
||
}).catch(err => {
|
||
console.error('❌ duration更新失败:', err);
|
||
});
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
---
|
||
|
||
### **方式2:使用uni.createVideoContext**
|
||
|
||
```javascript
|
||
// 在mounted或onReady中
|
||
onReady() {
|
||
// 创建video上下文
|
||
this.videoContext = uni.createVideoContext('myVideo', this);
|
||
|
||
// 延迟获取视频信息(等待加载)
|
||
setTimeout(() => {
|
||
this.getVideoDuration();
|
||
}, 1000);
|
||
},
|
||
|
||
methods: {
|
||
getVideoDuration() {
|
||
// 注意:uni-app的video组件可能需要通过事件获取duration
|
||
// 推荐使用 @loadedmetadata 事件(方式1)
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### **方式3:直接使用HTML5 Video API(H5页面)**
|
||
|
||
```javascript
|
||
<video
|
||
id="myVideo"
|
||
:src="videoUrl"
|
||
@loadedmetadata="handleLoadedMetadata"
|
||
></video>
|
||
|
||
<script>
|
||
export default {
|
||
methods: {
|
||
handleLoadedMetadata(event) {
|
||
const video = event.target;
|
||
const duration = Math.floor(video.duration);
|
||
|
||
console.log('视频时长:', duration);
|
||
|
||
// 调用后端更新
|
||
this.updateDuration(duration);
|
||
},
|
||
|
||
updateDuration(duration) {
|
||
fetch('/study/courseware/updateDuration', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
coursewareId: this.coursewareId,
|
||
duration: duration
|
||
})
|
||
})
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
console.log('更新结果:', data);
|
||
});
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 **集成步骤**
|
||
|
||
### **步骤1:后端编译部署**
|
||
|
||
```bash
|
||
cd Study-Vue-redis
|
||
mvn clean package -DskipTests
|
||
|
||
# 重启服务
|
||
java -jar ry-study-admin/target/ry-study-admin.jar
|
||
```
|
||
|
||
---
|
||
|
||
### **步骤2:前端代码修改**
|
||
|
||
**找到视频播放页面:** `fronted_uniapp/pages/study/video.vue` (或类似文件)
|
||
|
||
**添加以下代码:**
|
||
|
||
1. 在 `<video>` 标签添加事件监听:
|
||
```html
|
||
<video @loadedmetadata="onVideoLoaded"></video>
|
||
```
|
||
|
||
2. 在 `methods` 中添加方法:
|
||
```javascript
|
||
onVideoLoaded(e) {
|
||
const duration = Math.floor(e.detail.duration);
|
||
if (duration > 0) {
|
||
this.updateVideoDuration(duration);
|
||
}
|
||
},
|
||
|
||
updateVideoDuration(duration) {
|
||
this.$http.post('/study/courseware/updateDuration', {
|
||
coursewareId: this.coursewareId,
|
||
duration: duration
|
||
}).then(res => {
|
||
console.log('duration更新:', res.msg);
|
||
});
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### **步骤3:测试验证**
|
||
|
||
1. **打开APP,观看一个视频**
|
||
2. **查看控制台日志**:应该显示"duration更新成功"
|
||
3. **查看数据库**:
|
||
```sql
|
||
SELECT id, title, duration
|
||
FROM courseware
|
||
WHERE type = 'video' AND duration IS NOT NULL;
|
||
```
|
||
|
||
4. **再次观看同一视频**:应该显示"duration已存在,无需更新"
|
||
|
||
---
|
||
|
||
## 📊 **预期效果**
|
||
|
||
### **首次观看视频:**
|
||
```
|
||
前端:加载视频 → 获取时长300秒 → 调用接口
|
||
后端:接收请求 → 检查duration为NULL → 更新为300秒 → 返回成功
|
||
日志:✅ 自动更新视频duration: 课件ID=123, 时长=300秒
|
||
```
|
||
|
||
### **再次观看视频:**
|
||
```
|
||
前端:加载视频 → 获取时长300秒 → 调用接口
|
||
后端:接收请求 → 检查duration已存在 → 跳过更新 → 返回"无需更新"
|
||
```
|
||
|
||
### **几天后效果:**
|
||
```sql
|
||
-- 查询已自动更新的视频
|
||
SELECT COUNT(*) FROM courseware
|
||
WHERE type = 'video' AND duration IS NOT NULL;
|
||
|
||
-- 预期结果:随着用户观看,越来越多视频会自动补充duration
|
||
-- 初始: 0个
|
||
-- 一周后: 100+个
|
||
-- 一个月后: 大部分常看的视频都有了
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ **优点**
|
||
|
||
1. **无需批量处理** - 自动渐进式更新
|
||
2. **用户无感知** - 后台静默完成
|
||
3. **准确可靠** - 真实视频播放器获取的时长
|
||
4. **性能友好** - 只更新一次,不重复
|
||
5. **自然覆盖** - 常看的视频优先更新
|
||
|
||
---
|
||
|
||
## 🎯 **完整流程**
|
||
|
||
```
|
||
用户打开视频
|
||
↓
|
||
前端加载视频
|
||
↓
|
||
loadedmetadata事件触发
|
||
↓
|
||
获取 video.duration
|
||
↓
|
||
调用后端接口
|
||
↓
|
||
后端检查并更新数据库
|
||
↓
|
||
完成!视频有了duration
|
||
↓
|
||
下次计算进度时使用精确的95%标准
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 **注意事项**
|
||
|
||
1. **确保视频能正常加载** - 如果视频加载失败,无法获取duration
|
||
2. **网络请求不阻塞播放** - 接口调用是异步的
|
||
3. **接口调用失败不影响观看** - 有try-catch保护
|
||
4. **已有duration的不会被覆盖** - 防止误操作
|
||
|
||
**所有代码已准备好,编译部署即可使用!** ✨
|