Files
learning-system-mobile/components/video-player

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)

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 兼容性检测

function agentFullScreen() {
  let bool = false
  let agent = navigator.userAgent
  if (agent.indexOf('XiaoMi') != -1) {
    bool = true
  }
  return bool
}

2. 视频加载流程

2.1 元数据加载 (onMetaLoad)

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)

onLoad(e) {
  // 隐藏控制栏
  this.contrlShow = false;
  // 触发父组件事件
  this.$emit('loadeddata', e);
}

3. 播放控制流程

3.1 播放/暂停切换

videoOpreation() {
  // 根据当前状态切换播放/暂停
  this.playing ? this.videoContext.pause() : this.videoContext.play();
  this.playing = !this.playing;
}

3.2 播放开始 (onPlay)

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)

onPause() {
  this.playing = false;
  this.$emit('pause')
}

3.4 播放结束 (onEnded)

onEnded() {
  // 1. 停止播放
  this.playing = false;
  
  // 2. 退出全屏(如果在全屏状态)
  if (this.fullScreenFlag) {
    this.fullScreen()
  }
  
  // 3. 触发父组件结束事件
  this.$emit('ended')
}

4. 进度控制流程

4.1 进度更新 (onTimeUpdate)

onTimeUpdate(e) {
  // 1. 更新播放时间计数器
  this.currtimeText = this.currtimeText + 1
  
  // 2. 处理进度更新
  this.videoUpdate(e);
  
  // 3. 触发父组件时间更新事件
  this.$emit('timeupdate', e)
}

4.2 详细进度处理 (videoUpdate)

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 进度条拖拽控制

// 点击进度条更新播放进度
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 全屏切换

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)

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 倍速控制显示

showSpeedCtrl() {
  if (this.speedListShow) {
    this.speedListShow = false;
  } else {
    this.speedListShow = true;
  }
}

6.2 倍速切换

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变化监听

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 触摸结束处理

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 - 播放错误

使用示例

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