mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/vue/learning-system-mobile.git
synced 2025-12-09 02:46:46 +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";
|
hm.src = "https://hm.baidu.com/hm.js?ea89f02dca369037a73c5e3907e2c14a";
|
||||||
var s = document.getElementsByTagName("script")[0];
|
var s = document.getElementsByTagName("script")[0];
|
||||||
s.parentNode.insertBefore(hm, s);
|
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