mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/vue/learning-system-mobile.git
synced 2025-12-06 17:36:45 +08:00
biuld
This commit is contained in:
488
components/video-player/README.md
Normal file
488
components/video-player/README.md
Normal file
@@ -0,0 +1,488 @@
|
||||
# Video Player 组件播放流程文档
|
||||
|
||||
## 概述
|
||||
|
||||
`video-player` 是一个功能完整的Vue视频播放器组件,支持自定义控制栏、全屏播放、倍速播放、进度控制等功能,专为移动端优化。
|
||||
|
||||
## 组件结构
|
||||
|
||||
### 核心依赖
|
||||
- `progress-bar.vue` - 自定义进度条组件
|
||||
- `vuex` - 状态管理,获取用户信息
|
||||
- `study.js` - 学习工具类
|
||||
- `manage.js` - 管理接口
|
||||
|
||||
### 主要Props
|
||||
- `url` (String, required) - 视频播放地址
|
||||
- `blobId` (String) - 视频内容ID,用于进度记录
|
||||
- `watermark` (Boolean, default: true) - 是否显示水印
|
||||
- `name` (String) - 视频名称
|
||||
- `drag` (Boolean, default: true) - 是否允许拖拽进度条
|
||||
- `inittime` (Number, default: 0) - 初始播放位置(秒)
|
||||
|
||||
## 视频播放流程详解
|
||||
|
||||
### 1. 组件初始化流程
|
||||
|
||||
#### 1.1 挂载阶段 (mounted)
|
||||
```javascript
|
||||
mounted() {
|
||||
// 1. 设置初始播放时间
|
||||
this.initPlayingTime = this.inittime;
|
||||
|
||||
// 2. 创建视频上下文对象
|
||||
this.videoContext = uni.createVideoContext("xvideoPlayer", this);
|
||||
|
||||
// 3. 获取系统信息(屏幕尺寸)
|
||||
uni.getSystemInfo({
|
||||
success: function (info) {
|
||||
_this.videoPageWidth = info.windowWidth;
|
||||
_this.videoPageHeight = info.windowHeight;
|
||||
}
|
||||
});
|
||||
|
||||
// 4. 检查移动端兼容性
|
||||
if (this.xvideoBool) this.dictCode();
|
||||
|
||||
// 5. 启动定时器更新进度
|
||||
setInterval(() => {
|
||||
// 更新iOS播放时间
|
||||
if (this.playing && !this.isMousedownProgress) {
|
||||
this.iosCurrentTime = this.iosCurrentTime + 1
|
||||
}
|
||||
// 更新进度文字显示
|
||||
this.updateProgressText();
|
||||
}, 1000);
|
||||
|
||||
// 6. 绑定全局事件监听器
|
||||
window.addEventListener("touchstart", (e) => {
|
||||
if (this.speedListShow && !e.target.closest('.speed-control')) {
|
||||
this.speedListShow = false
|
||||
}
|
||||
}, {passive: false});
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 兼容性检测
|
||||
```javascript
|
||||
function agentFullScreen() {
|
||||
let bool = false
|
||||
let agent = navigator.userAgent
|
||||
if (agent.indexOf('XiaoMi') != -1) {
|
||||
bool = true
|
||||
}
|
||||
return bool
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 视频加载流程
|
||||
|
||||
#### 2.1 元数据加载 (onMetaLoad)
|
||||
```javascript
|
||||
onMetaLoad(e) {
|
||||
// 1. 获取视频总时长
|
||||
this.curVideo.duration = Number(e.detail.duration);
|
||||
|
||||
// 2. 格式化并显示总时长
|
||||
this.druationTime = this.formatSeconds(this.curVideo.duration);
|
||||
|
||||
// 3. 显示控制栏
|
||||
this.contrlShow = true;
|
||||
|
||||
// 4. 触发父组件事件
|
||||
this.$emit('loadedmetadata', e);
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 视频数据加载 (onLoad)
|
||||
```javascript
|
||||
onLoad(e) {
|
||||
// 隐藏控制栏
|
||||
this.contrlShow = false;
|
||||
// 触发父组件事件
|
||||
this.$emit('loadeddata', e);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 播放控制流程
|
||||
|
||||
#### 3.1 播放/暂停切换
|
||||
```javascript
|
||||
videoOpreation() {
|
||||
// 根据当前状态切换播放/暂停
|
||||
this.playing ? this.videoContext.pause() : this.videoContext.play();
|
||||
this.playing = !this.playing;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 播放开始 (onPlay)
|
||||
```javascript
|
||||
onPlay() {
|
||||
// 1. 开始播放
|
||||
this.videoContext.play();
|
||||
this.playing = true;
|
||||
|
||||
// 2. 设置控制栏自动隐藏
|
||||
if (this.contrlShow) {
|
||||
this.contrlShowTimeout = window.setTimeout(function () {
|
||||
$this.contrlShow = false;
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// 3. 触发父组件播放事件
|
||||
this.$emit('play')
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3 播放暂停 (onPause)
|
||||
```javascript
|
||||
onPause() {
|
||||
this.playing = false;
|
||||
this.$emit('pause')
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.4 播放结束 (onEnded)
|
||||
```javascript
|
||||
onEnded() {
|
||||
// 1. 停止播放
|
||||
this.playing = false;
|
||||
|
||||
// 2. 退出全屏(如果在全屏状态)
|
||||
if (this.fullScreenFlag) {
|
||||
this.fullScreen()
|
||||
}
|
||||
|
||||
// 3. 触发父组件结束事件
|
||||
this.$emit('ended')
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 进度控制流程
|
||||
|
||||
#### 4.1 进度更新 (onTimeUpdate)
|
||||
```javascript
|
||||
onTimeUpdate(e) {
|
||||
// 1. 更新播放时间计数器
|
||||
this.currtimeText = this.currtimeText + 1
|
||||
|
||||
// 2. 处理进度更新
|
||||
this.videoUpdate(e);
|
||||
|
||||
// 3. 触发父组件时间更新事件
|
||||
this.$emit('timeupdate', e)
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 详细进度处理 (videoUpdate)
|
||||
```javascript
|
||||
videoUpdate(e) {
|
||||
// 1. 记录历史时间
|
||||
this.historyTime = e.detail.currentTime
|
||||
|
||||
// 2. 计算进度条数值
|
||||
let duration = this.curVideo.duration;
|
||||
let sliderValue = e.detail.currentTime / duration * 100;
|
||||
let second = sliderValue / 100 * duration;
|
||||
|
||||
// 3. 更新进度条(仅在非拖拽状态)
|
||||
if (this.updateState) {
|
||||
this.sliderValue = sliderValue;
|
||||
}
|
||||
|
||||
// 4. 更新时间显示
|
||||
this.druationTime = this.formatSeconds(duration);
|
||||
this.currtime = this.formatSeconds(second);
|
||||
this.iosCurrentTime = second;
|
||||
|
||||
// 5. 保存播放进度(禁止拖拽模式下)
|
||||
if (!this.drag) {
|
||||
var time = localStorage.getItem('videoProgressData')
|
||||
var arr = time && JSON.parse(time) || {}
|
||||
if (arr[this.blobId] < this.sliderValue / 100 || !arr[this.blobId]) {
|
||||
arr[this.blobId] = parseFloat((this.sliderValue / 100).toFixed(8))
|
||||
if (arr[this.blobId]) localStorage.setItem('videoProgressData', JSON.stringify(arr))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3 进度条拖拽控制
|
||||
```javascript
|
||||
// 点击进度条更新播放进度
|
||||
updateProgressByClickBar(type, value) {
|
||||
let duration = this.curVideo.duration;
|
||||
let second = Math.round(value * duration);
|
||||
|
||||
this.curVideo.currentTime = second;
|
||||
this.sliderValue = value * 100;
|
||||
this.druationTime = this.formatSeconds(duration);
|
||||
this.currtime = this.formatSeconds(second);
|
||||
this.iosCurrentTime = second;
|
||||
|
||||
if (duration) {
|
||||
if (type === 'start') {
|
||||
this.updateState = false; // 拖拽开始,停止自动更新
|
||||
}
|
||||
if (type === 'end') {
|
||||
this.videoContext.seek(second); // 跳转到指定位置
|
||||
this.curVideo.currentTime = second;
|
||||
this.updateState = true // 拖拽结束,恢复自动更新
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 全屏控制流程
|
||||
|
||||
#### 5.1 全屏切换
|
||||
```javascript
|
||||
fullScreen() {
|
||||
// 1. iOS特殊处理
|
||||
const u = navigator.userAgent, app = navigator.appVersion;
|
||||
const isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
|
||||
if (isIOS) {
|
||||
document.querySelector("#xvideoPlayer").webkitEnterFullscreen()
|
||||
}
|
||||
|
||||
// 2. 全屏状态切换
|
||||
if (this.fullScreenFlag) {
|
||||
// 退出全屏
|
||||
this.fullScreenFlag = false;
|
||||
this.videoFullHeight = 190
|
||||
} else {
|
||||
// 进入全屏
|
||||
this.fullScreenFlag = true;
|
||||
// 重新计算控制栏高度
|
||||
uni.getSystemInfo({
|
||||
success: function (info) {
|
||||
_this.videoFullHeight = info.windowHeight / 2 + 130;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 触发父组件全屏事件
|
||||
!isIOS && this.$listeners['fullscreenchange'](this.fullScreenFlag)
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.2 全屏状态变化处理 (onFullScreen)
|
||||
```javascript
|
||||
onFullScreen(e) {
|
||||
this.fullScreenFlag = e.detail.fullScreen;
|
||||
let divId = 'videowatermark';
|
||||
let full = e.detail.fullScreen;
|
||||
let divControlId = 'xplayer-control';
|
||||
let box = document.getElementById('xvideoPlayer-box');
|
||||
let control = document.getElementById(divControlId);
|
||||
|
||||
setTimeout(() => {
|
||||
var div = document.getElementById("xvideoPlayer");
|
||||
var div1 = div.firstChild;
|
||||
|
||||
if (full) {
|
||||
// 全屏模式:添加水印和控制栏
|
||||
if ($this.watermark) {
|
||||
// 创建水印元素
|
||||
var div3 = document.createElement("div");
|
||||
div3.id = divId;
|
||||
div3.setAttribute("class", "fullmark");
|
||||
div3.innerHTML = '';
|
||||
for (var i = 0; i < 4; i++) {
|
||||
div3.innerHTML += '<div style="...">用户水印</div>';
|
||||
}
|
||||
div1.appendChild(div3);
|
||||
}
|
||||
// 移动控制栏到视频内部
|
||||
div1.appendChild(control);
|
||||
} else {
|
||||
// 退出全屏:移除水印,恢复控制栏位置
|
||||
var hasControlDiv = div1.querySelector("#" + divControlId);
|
||||
if (hasControlDiv) {
|
||||
box.appendChild(hasControlDiv);
|
||||
}
|
||||
if ($this.watermark) {
|
||||
var markDiv = div1.querySelector("#" + divId);
|
||||
if (markDiv) {
|
||||
div1.removeChild(markDiv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
|
||||
this.$emit('fullscreenchange', e);
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 倍速播放流程
|
||||
|
||||
#### 6.1 倍速控制显示
|
||||
```javascript
|
||||
showSpeedCtrl() {
|
||||
if (this.speedListShow) {
|
||||
this.speedListShow = false;
|
||||
} else {
|
||||
this.speedListShow = true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.2 倍速切换
|
||||
```javascript
|
||||
changeSpeed(e) {
|
||||
// 1. 获取选择的倍速
|
||||
let value = e;
|
||||
this.videoSpeed = Number(value);
|
||||
|
||||
// 2. 设置播放器倍速
|
||||
this.videoContext.playbackRate(this.videoSpeed);
|
||||
|
||||
// 3. 保存倍速设置
|
||||
studyUtil.setVideoSpeed(this.videoSpeed);
|
||||
|
||||
// 4. 隐藏倍速选择框
|
||||
this.speedListShow = false;
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 监听器流程
|
||||
|
||||
#### 7.1 URL变化监听
|
||||
```javascript
|
||||
watch: {
|
||||
url(newVal, oldVal) {
|
||||
// 1. 停止当前播放
|
||||
this.videoContext.stop();
|
||||
this.playing = false;
|
||||
|
||||
// 2. 重置播放状态
|
||||
this.curVideo.currentTime = 0;
|
||||
this.iosCurrentTime = 0;
|
||||
this.sliderValue = 0;
|
||||
this.videoSpeed = 1.0;
|
||||
this.currtime = "00:00";
|
||||
|
||||
// 3. 重新加载视频
|
||||
document.getElementsByTagName('video')[0].load()
|
||||
setTimeout(() => {
|
||||
document.getElementsByTagName('video')[0].play()
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8. 触摸交互流程
|
||||
|
||||
#### 8.1 触摸结束处理
|
||||
```javascript
|
||||
onTouchend(e) {
|
||||
// 1. 清除控制栏隐藏定时器
|
||||
if (this.contrlShowTimeout != null) {
|
||||
window.clearTimeout(this.contrlShowTimeout);
|
||||
}
|
||||
|
||||
// 2. 控制栏显示/隐藏逻辑
|
||||
if (!this.fullScreenFlag) {
|
||||
this.contrlShow = !this.contrlShow;
|
||||
} else {
|
||||
if (!this.contrlShow) {
|
||||
this.contrlShow = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 组件事件
|
||||
|
||||
### 对外事件
|
||||
- `@loadeddata` - 视频数据加载完成
|
||||
- `@loadedmetadata` - 视频元数据加载完成
|
||||
- `@play` - 开始播放
|
||||
- `@pause` - 播放暂停
|
||||
- `@ended` - 播放结束
|
||||
- `@timeupdate` - 播放进度更新
|
||||
- `@fullscreenchange` - 全屏状态变化
|
||||
- `@error` - 播放错误
|
||||
|
||||
## 使用示例
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<video-player
|
||||
:url="videoUrl"
|
||||
:blobId="contentId"
|
||||
:watermark="true"
|
||||
:drag="true"
|
||||
:inittime="0"
|
||||
@play="onVideoPlay"
|
||||
@pause="onVideoPause"
|
||||
@ended="onVideoEnded"
|
||||
@timeupdate="onTimeUpdate"
|
||||
@fullscreenchange="onFullscreenChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VideoPlayer from '@/components/video-player/video-player.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
VideoPlayer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
videoUrl: 'https://example.com/video.mp4',
|
||||
contentId: 'video_123'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onVideoPlay() {
|
||||
console.log('视频开始播放')
|
||||
},
|
||||
onVideoPause() {
|
||||
console.log('视频暂停')
|
||||
},
|
||||
onVideoEnded() {
|
||||
console.log('视频播放结束')
|
||||
},
|
||||
onTimeUpdate(e) {
|
||||
console.log('播放进度更新:', e.detail)
|
||||
},
|
||||
onFullscreenChange(isFullscreen) {
|
||||
console.log('全屏状态:', isFullscreen)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 关键特性
|
||||
|
||||
### 1. 移动端优化
|
||||
- 支持触摸手势控制
|
||||
- 响应式布局适配
|
||||
- iOS/Android兼容性处理
|
||||
|
||||
### 2. 进度记录
|
||||
- 本地存储播放进度
|
||||
- 禁拖拽模式下自动记录
|
||||
- 支持断点续播
|
||||
|
||||
### 3. 安全特性
|
||||
- 水印功能防录屏
|
||||
- 可控制拖拽权限
|
||||
- 播放行为监控
|
||||
|
||||
### 4. 用户体验
|
||||
- 自定义控制栏
|
||||
- 倍速播放支持
|
||||
- 全屏播放体验
|
||||
- 自动隐藏控制栏
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **兼容性**: 小米设备需要特殊处理,会显示兼容性提示
|
||||
2. **权限控制**: `drag` 属性为false时禁止拖拽进度条
|
||||
3. **进度保存**: 仅在禁拖拽模式下才会自动保存播放进度
|
||||
4. **全屏处理**: iOS设备使用webkit全屏API,其他设备使用自定义全屏
|
||||
5. **内存管理**: 组件销毁时需要清理定时器和事件监听器
|
||||
2
unpackage/dist/build/h5/index.html
vendored
2
unpackage/dist/build/h5/index.html
vendored
@@ -19,4 +19,4 @@
|
||||
hm.src = "https://hm.baidu.com/hm.js?ea89f02dca369037a73c5e3907e2c14a";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();</script></head><body><div id=app></div><script src=/mobile/static/js/chunk-vendors.b01deae1.js></script><script src=/mobile/static/js/index.00f37937.js></script></body></html>
|
||||
})();</script></head><body><div id=app></div><script src=/mobile/static/js/chunk-vendors.b01deae1.js></script><script src=/mobile/static/js/index.9d06cab6.js></script></body></html>
|
||||
File diff suppressed because one or more lines are too long
5
unpackage/dist/build/h5/static/js/index.9d06cab6.js
vendored
Normal file
5
unpackage/dist/build/h5/static/js/index.9d06cab6.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
unpackage/dist/build/h5/static/js/pages-study-courseStudy.7a410340.js
vendored
Normal file
1
unpackage/dist/build/h5/static/js/pages-study-courseStudy.7a410340.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user