angular开发中自定义HTML5的video控制面板实现刷新、播放、进度条控制、倍数、声音、全屏等功能

开发中遇到需要自定义HTML5的video控制面板功能,结合网上资料进行了优化和实现,还可以自定义其它功能,备份下来:

1.实现效果图:

2.html代码片段:

<div class="video-box">
  <!--video 盒子-->
  <div class="video-box-body">
    <video class="video-body" [src]="playPath">
      您的浏览器不支持 video 标签。
    </video>
    <!--控制条盒子-->
    <div class="video-control">
      <div class="video-control-left">
        <!--刷新键-->
        <div class="control-btn pull-left" (click)="loadHandle()" title="刷新"><i class="fa fa-refresh"></i></div>
        <!--暂停/播放键-->
        <div class="control-btn pull-left" [hidden]="errorFlag" (click)="playPauseHandle()" [title]="playPauseTitle"><i id="playPauseFa" class="fa fa-play"></i></div>
        <div class="control-btn pull-left errorColor" [hidden]="!errorFlag" [title]="playPauseTitle"><i class="fa fa-play"></i></div>
      </div>
      <div class="progress-box">
        <div class="progress-box-body">
          <!--播放时长-->
          <div class="current-time pull-left" id="currentTime">00:00</div>
          <div class="durationbar-box pull-left">
            <!--总视频长度进度条-->
            <div class="durationbar" id="durationBar">
              <!--缓冲进度条-->
              <div class="bufferbar" id="bufferBar"></div>
              <!--正在播放进度条-->
              <div class="currentbar" id="currentBar"></div>
              <div class="drawbar" id="drawBar"></div>
            </div>
          </div>
          <!--总时长-->
          <div class="duration-time pull-left" id="durationTime">00:00</div>
        </div>
      </div>
      <div class="video-control-right">
        <!--倍数键-->
        <div class="control-multiple pull-left" title="倍数">
          <p-dropdown id="multipleId" [options]="multiples" [(ngModel)]="multiple" (onChange)="multipleHandle()"
                      [disabled]="errorFlag" [style]="{'min-width':'20px', 'width': '100%'}"></p-dropdown>
        </div>
        <!--音量键-->
        <div class="control-btn pull-left" [hidden]="errorFlag" (click)="mutedHandle()" [title]="mutedTitle"><i id="mutedFa" class="fa fa-volume-up"></i></div>
        <div class="control-btn pull-left errorColor" [hidden]="!errorFlag" [title]="mutedTitle"><i class="fa fa-volume-up"></i></div>
        <!--全屏键-->
        <div class="control-btn pull-left" *ngIf="!isFullScreen" (click)="fullScreenHandle()" title="全屏"><i class="fa fa-expand"></i></div>
        <div class="control-btn pull-left" *ngIf="isFullScreen" (click)="exitFullScreenHandle()" title="还原"><i class="fa fa-compress"></i></div>
      </div>
    </div>
  </div>
</div>

3.scss样式代码:

/*video样式*/
.video-box {
  overflow: hidden;
  background: #000;
  width: 100%;
  height: calc(100% - 10px);
  display: block;
  margin: 0 auto;
  -webkit-transition-duration: 300ms;
  -moz-transition-duration: 300ms;
  -ms-transition-duration: 300ms;
  -o-transition-duration: 300ms;
  transition-duration: 300ms;
  z-index: 10;
}
/*video body样式*/
.video-box-body {
  position: relative;
  width: 100%;
  height: 100%;
  margin: 0 auto;
  overflow: hidden;

  .video-body {
    display: block;
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
    z-index: 15;
    object-fit: fill;
  }
}
/*控制条样式*/
.video-control {
  position: absolute;
  width: 100%;
  height: 50px;
  padding: 10px;
  line-height: 30px;
  background: rgba(0, 0, 0, .5);
  z-index: 99999999999;
  left: 0;
  right: 0;
  bottom: 0;

  div {
    display: inline-block;
  }

  .control-btn {
    display: inline-block;
    width: 30px;
    height: 30px;
    background: transparent;
    cursor: pointer;
    font-size: 20px;
    color: white;
    border-radius: 100%;
    text-align: center;

    &:hover {
      background-color: rgba(0, 0, 0, 0.5);
      border-color: rgba(0, 0, 0, 0.5);
    }
  }

  // 左
  .video-control-left {
    position: absolute;
    height: 30px;
    z-index: 5;

    .control-btn {
      margin-right: 10px;

      &:last-child {
        margin: 0;
      }
    }
  }

  // 中
  .progress-box {
    width: 100%;
    height: 30px;
    padding: 0 130px 0 70px;

    .progress-box-body {
      width: 100%;
      height: 100%;

      .current-time, .duration-time {
        width: 60px;
        text-align: center;
        color: #fff;
      }
      .current-time {
        margin-right: -60px;
        position: relative;
        z-index: 5;
      }
      .duration-time {
        margin-left: -60px;
        position: relative;
        z-index: 5;
      }
      .durationbar-box {
        width: 100%;
        padding: 0 70px;
      }
      .durationbar {
        width: 100%;
        height: 10px;
        margin-top: 10px;
        background: rgba(65, 62, 62, 1);
        -webkit-border-radius: 50px;
        -moz-border-radius: 50px;
        -ms-border-radius: 50px;
        -o-border-radius: 50px;
        border-radius: 50px;
        position: relative;
      }
      .bufferbar, .currentbar {
        position: absolute;
        left: 0;
        top: 0;
        height: 100%;
        width: 0;
        background: rgba(117, 114, 114, 1);
        -webkit-border-radius: 50px;
        -moz-border-radius: 50px;
        -ms-border-radius: 50px;
        -o-border-radius: 50px;
        border-radius: 50px;
        z-index: 5;
        cursor: pointer;
      }
      .currentbar {
        background: #fff;
        z-index: 10;
      }
      .drawbar {
        position: absolute;
        background: #fff;
        width: 20px;
        height: 20px;
        left: 0;
        top: -5px;
        z-index: 10;
        -webkit-border-radius: 50px;
        -moz-border-radius: 50px;
        -ms-border-radius: 50px;
        -o-border-radius: 50px;
        border-radius: 50px;
        cursor: pointer;
      }
    }
  }

  // 右
  .video-control-right {
    position: absolute;
    height: 30px;
    margin-left: -130px;
    z-index: 5;

    .control-btn {
      margin-left: 10px;

      &:first-child {
        margin: 0;
      }
    }
  }
}

// 倍数
:host ::ng-deep .control-multiple {
  width: 50px;
  height: 30px;
  border-radius: 5px;

  .ui-dropdown {
    border: none;

    &:not(.ui-state-disabled):hover {

      background-color: rgba(0, 0, 0, 0.5);
      border-color: rgba(0, 0, 0, 0.5);

      .ui-dropdown-label {
        background: transparent;
      }
    }

    .ui-inputtext {
      color: white;
    }

    .ui-dropdown-label {
      background: transparent;
    }
  }
}

video:-webkit-full-screen {
  z-index: 9 !important;
  width: 100% !important;
  height: 100% !important;
}
video::-webkit-media-controls {
  display: none !important;
}
.errorColor {
  color: #838383 !important;
}

4.ts代码处理:

public playPath: any; // 播放路径
  public videoDom: any; // 播放器对象
  public playPauseDom: any; // 播放/暂停图标对象
  public playPauseTitle: string = "播放"; // 播放/暂停提示
  public mutedDom: any; // 音量图标对象
  public mutedTitle: string = "关闭"; // 开启/关闭提示
  public isFullScreen: boolean = false; // 是否全屏
  public multiples: any = []; // 播放的倍数
  public multiple: number = 1; // 选择的倍数
  public errorFlag: boolean = true; // 加载错误

  constructor(
    public viewVideoService:ViewVideoService,
    private renderer: Renderer2,
    private el: ElementRef
  ) {
    this.multiples = [{
      label: '5.0X',
      value: 5
    },{
      label: '2.0X',
      value: 2
    },{
      label: '1.5X',
      value: 1.5
    },{
      label: '1.0X',
      value: 1
    },{
      label: '0.5X',
      value: 0.5
    }];
  }

  ngOnInit() {
    // 路径源
    this.playPath=this.viewVideoService.getPath();
  }

  ngAfterViewInit() {
    // video等元素
    this.videoDom = this.el.nativeElement.querySelector("video");
    this.playPauseDom = this.el.nativeElement.querySelector("#playPauseFa");
    this.mutedDom = this.el.nativeElement.querySelector("#mutedFa");
    // 去除倍数播放下拉框多余元素
    let multipleElement = this.el.nativeElement.querySelector("#multipleId");
    let dropdownElement = multipleElement ? multipleElement.querySelector(".ui-dropdown-trigger"):null;
    if (dropdownElement) dropdownElement.remove();
    // 事件
    this.eventHandle();
  }

  /**
   * 结束或暂停播放状态图标切换
   **/
  private pauseHandle() { // 结束或暂停
    // 刷新后已经暂停
    this.playPauseTitle = '播放';
    if (this.playPauseDom) {
      //通过操作不同的class,来切换键的形态
      this.renderer.removeClass(this.playPauseDom, 'fa-pause');
      this.renderer.addClass(this.playPauseDom, 'fa-play');
    }
  }

  /**
   * 刷新视频,直接调用load()方法就行
   **/
  public loadHandle() { // 刷新
    this.videoDom.load();
    this.multipleHandle();
    // 刷新后已经暂停
    this.pauseHandle();
  }

  /**
   * 播放/暂停功能
   **/
  public playPauseHandle() { // 播放暂停
    //paused 返回的是视频是否是暂停状态,返回的是一个布尔值
    if(this.videoDom.paused){
      this.videoDom.play();
      this.playPauseTitle = '暂停';
      if (this.playPauseDom) {
        //通过操作不同的class,来切换键的形态
        this.renderer.removeClass(this.playPauseDom, 'fa-play');
        this.renderer.addClass(this.playPauseDom, 'fa-pause');
      }
    }else{
      this.videoDom.pause();
      this.pauseHandle();
    }
  }

  /**
   * 播放倍数调节
   **/
  public multipleHandle() {
    this.videoDom.playbackRate = this.multiple;
  }

  /**
   * 音量键的开启和关闭
   **/
  public mutedHandle() {
    //如果为静音则开启,如果为开启状态则关闭
    this.videoDom.muted=!this.videoDom.muted;
    if (this.videoDom.muted) { // 禁音状态
      this.mutedTitle = '开启';
      if (this.mutedDom) {
        //通过操作不同的class,来切换键的形态
        this.renderer.removeClass(this.mutedDom, 'fa-volume-up');
        this.renderer.addClass(this.mutedDom, 'fa-volume-off');
      }
    } else { // 开启状态
      this.mutedTitle = '关闭';
      if (this.mutedDom) {
        //通过操作不同的class,来切换键的形态
        this.renderer.removeClass(this.mutedDom, 'fa-volume-off');
        this.renderer.addClass(this.mutedDom, 'fa-volume-up');
      }
    }
  }

  /**
   * 全屏调节
   **/
  public fullScreenHandle() {
    const videoBox = this.el.nativeElement.querySelector(".video-box");
    if(videoBox.webkitRequestFullScreen){
      videoBox.webkitRequestFullScreen();
    } else if (videoBox.mozRequestFullScreen){
      videoBox.mozRequestFullScreen();
    } else {
      videoBox.requestFullScreen();
    }
    this.isFullScreen = true;
  }

  /**
   * 退出全屏
   **/
  public exitFullScreenHandle() {
    const videoBox = this.el.nativeElement.querySelector(".video-box");
    if(videoBox.ownerDocument.webkitCancelFullScreen){
      videoBox.ownerDocument.webkitCancelFullScreen();
    } else if (videoBox.ownerDocument.mozCancelFullScreen){
      videoBox.ownerDocument.mozCancelFullScreen();
    } else {
      videoBox.ownerDocument.exitFullscreen();
    }
    this.isFullScreen = false;
  }

  /**
   * 事件监听定义
   **/
  public eventHandle() {
    // 键盘事件监听
    this.keyEvent();
    // 退出全屏监听
    this.escExitFullScreen();
    // 播放时间监听
    this.playTimeHanle();
    // video控制面板显示/隐藏处理
    this.showHideControlsHandle();
  }

  /**
   * 键盘事件处理
   **/
  private keyEvent() {
    let that = this;
    let vol = 0.1;  //1代表100%音量,每次增减0.1
    let time = 5; //单位秒,每次增减5秒

    //键盘事件
    document.onkeyup = function (event) {
      let e = event || window.event || arguments.callee.caller.arguments[0];
      //鼠标上下键控制视频音量
      if (e && e.keyCode === 38) {
        // 按 向上键
        that.videoDom.volume !== 1 ? that.videoDom.volume += vol : 1;
        return false;
      } else if (e && e.keyCode === 40) {
        // 按 向下键
        that.videoDom.volume !== 0 ? that.videoDom.volume -= vol : 1;
        return false;
      } else if (e && e.keyCode === 37) {
        // 按 向左键
        that.videoDom.currentTime !== 0 ? that.videoDom.currentTime -= time : 1;
        return false;
      } else if (e && e.keyCode === 39) {
        // 按 向右键
        that.videoDom.currentTime !== that.videoDom.duration ? that.videoDom.currentTime += time : 1;
        return false;
      } else if (e && e.keyCode === 32) {
        // 按空格键 判断当前是否暂停
        that.playPauseHandle();
        return false;
      }
    };
  }

  /**
   * 验证是否全屏ESC退出处理
   **/
  private escExitFullScreen(){
    const videoBox = this.el.nativeElement.querySelector(".video-box");
    let videoDoc = videoBox.ownerDocument;
    let that = this;
    let exitHandler = function() {
      let isFull =  videoDoc.webkitIsFullScreen;
      if(isFull === undefined) isFull = false;
      if (!isFull) that.isFullScreen = false;
    };
    videoBox.addEventListener('fullscreenchange', exitHandler);
    videoBox.addEventListener('webkitfullscreenchange', exitHandler);
    videoBox.addEventListener('mozfullscreenchange', exitHandler);
  }

  /**
   * 因为我们获取的视频时长是秒数,所以我们需要将秒数转化为我们常见的时间格式
   **/
  private initTimeLength(timeLength) { // 根据秒数格式化时间
    timeLength = parseInt(timeLength);
    let second = timeLength % 60;
    let minute = (timeLength - second) / 60;
    return (minute<10?"0"+minute:minute)+":"+(second<10?"0"+second:second);
  };

  /**
   * 视频时间等效果处理
   **/
  private playTimeHanle() {
    this.videoDom.addEventListener('loadeddata', () => {
      const durationTime = this.el.nativeElement.querySelector("#durationTime");
      durationTime.innerHTML = this.initTimeLength(this.videoDom.duration);
      // 缓冲进度
      const bufferBar = this.el.nativeElement.querySelector("#bufferBar");
      bufferBar.style.width = '0px';
      this.bufferHandle(); // 缓冲进度条处理
      this.videoSeekHandle(); // 点击进度条播放处理
      this.errorFlag = false;
    });

    // ontimeupdate 当前视频播放位置反生改变触发的事件;
    this.videoDom.addEventListener('timeupdate', () => {
      // 视频时长
      let durationProgress=this.videoDom.duration;
      // currentTime 当前播放时长
      let currentTimeProgress=this.videoDom.currentTime;
      // 将当前播放时长填入左边时长元素中
      const currentTime = this.el.nativeElement.querySelector("#currentTime");
      currentTime.innerHTML = this.initTimeLength(this.videoDom.currentTime);
      // 求当前播放时长的进度,从而显示出来进度条
      let currentWidth=100*(currentTimeProgress/durationProgress);
      const drawBar = this.el.nativeElement.querySelector("#drawBar");
      const durationBar = this.el.nativeElement.querySelector("#durationBar");
      const currentBar = this.el.nativeElement.querySelector("#currentBar");
      let currentLeft=currentWidth-((drawBar.offsetWidth/2)/durationBar.offsetWidth*100);
      currentBar.style.width = currentWidth > 0 ? currentWidth.toFixed(1)+'%' : '0px';
      drawBar.style.left = currentLeft > 0 ? currentLeft.toFixed(1)+'%': '0px';
    });

    // 播放结束状态
    this.videoDom.addEventListener('ended', () => {
      // 播放结束后已经暂停
      this.pauseHandle();
      // 是否需要展示隐藏控制面板
      this.hideControls();
    });

    // 播放错误
    this.videoDom.addEventListener('error', () => {
      this.errorFlag = true;
    });
  }

  /**
   * 缓冲进度条处理
   **/
  private bufferHandle() {
    //视频时长
    var maxduration = this.videoDom.duration;
    //当前缓冲进度时长结束位置
    var currentBuffer = this.videoDom.buffered.end(0);
    // 求取百分比
    var percentage = 100 * currentBuffer / maxduration;
    const bufferBar = this.el.nativeElement.querySelector("#bufferBar");
    bufferBar.style.width = percentage.toFixed(1)+'%';
    // 在范围内每500毫秒进行一次递归,也就是调用一下自己;
    if(currentBuffer<maxduration){
      setTimeout( () => {
        this.bufferHandle();
      },500);
    }
  }

  /**
   * video控制面板显示/隐藏处理
   **/
  private showHideControlsHandle() {
    this.videoDom.addEventListener('mouseover', () => { this.showControls(); });
    this.videoDom.addEventListener('mouseout', () => { this.hideControls(); });
    const videoControls = this.el.nativeElement.querySelector(".video-control");
    videoControls.addEventListener('mouseover', () => { this.showControls(); });
    videoControls.addEventListener('mouseout', () => { this.hideControls(); });
  }

  // 显示video的控制面板
  private showControls(){
    const videoControls = this.el.nativeElement.querySelector(".video-control");
    videoControls.style.opacity = 1;
  }

  // 隐藏video的控制面板
  private hideControls(){
    const videoControls = this.el.nativeElement.querySelector(".video-control");
    if(this.videoDom.paused){ // 暂停
      videoControls.style.opacity = 1;
    } else {
      videoControls.style.opacity = 0;
    }
  }

  /**
   * 点击进度条播放处理
   **/
  private videoSeekHandle() {
    const currentBar = this.el.nativeElement.querySelector("#currentBar");
    const bufferBar = this.el.nativeElement.querySelector("#bufferBar");
    currentBar.addEventListener('mousedown', (e) => { this.videoSeekChange(e); });
    bufferBar.addEventListener('mousedown', (e) => { this.videoSeekChange(e); });
  }

  // 点击进度条的处理方法
  private videoSeekChange(e) {
    const durationBar = this.el.nativeElement.querySelector("#durationBar");
    let length = e.offsetX;
    let percent = length / durationBar.offsetWidth;
    this.videoDom.currentTime = percent * this.videoDom.duration; // 更改currentTime后会触发timeupdate事件
  }

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

txp1993

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值