This commit is contained in:
joshen
2025-09-04 23:40:45 +08:00
parent 56b6cf8d88
commit 726f232be4
6 changed files with 495 additions and 3 deletions

View 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. **内存管理**: 组件销毁时需要清理定时器和事件监听器

View File

@@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long