基于Rxjs的video全功能封装

2019-07-18  本文已影响0人  是素净呀丶

  最近接触到angular项目,有一个播放视频的需求,故尝试用Rxjs实现了该封装。主要实现的功能有:播放控制、进度控制(点击控制条以及拖动进度条)、音量控制(点击控制条以及拖动进度条)、全屏、错误信息展示。
注: 以下代码中创建的Subscription皆不展示退订代码,实际开发中切记使用unsubscribe进行退订操作。

即暂停时点击按钮播放,播放时点击按钮暂停,核心代码如下:

  get isplaying(): boolean {
      return this.player && !this.player.paused;
  }

  initPlayOrPauseEvent():void {
    this.playOrPause$ = fromEvent(this.doms.playOrPause, 'click').subscribe(
      () => {
        if (!this.canOperate) {
          return;
        }

        if (this.isplaying) {
          this.pause();
        } else {
          this.play();
        }
      }
    );
  }

这里我把这两个控制操作抽象了progress.service.ts服务,核心代码(Rxjs实现拖拽)如下:

constructor(options: {
  // 传入控制条元素
  container: Element;
  // 传入控制条拖动元素
  slider?: Element;
  // 拖动方向 (视频控制条左右、音量控制条上下)
  direction?: number;
}) {
  ...
  ...
  ...
  this.init();
}

init() {
  this.mouseClick$ = fromEvent(this.$container, 'click');
  this.mouseDown$ = fromEvent(this.$slider, 'mousedown');
  this.mouseMove$ = fromEvent(document.body, 'mousemove');
  this.mouseUp$ = fromEvent(document.body, 'mouseup');

  // 通过点击控制条控制进度
  this.mouseClickSub$ = this.mouseClick$.subscribe((event: MouseEvent) => {
    const { direction } = this;
    const { clientX, clientY } = event;
    const bounds = this.$container.getBoundingClientRect();
    const { left, bottom, width, height } = bounds;
    let progress;

    if (direction === 0) {
      progress = ((clientX - left) / width) * 100;
    }

    if (direction === 1) {
      progress = ((bottom - clientY) / height) * 100;
    }

    progress = Math.min(100, Math.max(0, progress));

    this.$emit(PROGRESS_EVENT.CHANGE_END, {
      type: 'click',
      progress,
      event
    });
  });

  // 通过拖动滑块控制进度
  this.dragSub$ = this.mouseDown$
    .pipe(
      tap((e: MouseEvent) => {
        const { clientX, clientY } = e;

        this.initPos = {
          left: this.getStyle(this.$slider, 'left'),
          top: this.getStyle(this.$slider, 'top')
        };
        this.initMousePos = {
          x: clientX,
          y: clientY
        };

        this._draging = true;
        this.$emit(PROGRESS_EVENT.CHANGE_START);
      }),

      switchMap(_ =>
        this.mouseMove$.pipe(
          takeUntil(
            this.mouseUp$.pipe(
              tap(_ => {
                this._draging = false;
                this.$emit(PROGRESS_EVENT.CHANGE_END);
              })
            )
          ),
        map((e: MouseEvent) => {
            const { clientX, clientY } = e;

            return {
              x: clientX - this.initMousePos.x,
              y: clientY - this.initMousePos.y
            };
          })
        )
      )
    )
    .subscribe(movedPos => {
      const targetPos = {
        left: this.initPos.left + movedPos.x,
        top: this.initPos.top + movedPos.y
      };
      const { clientWidth, clientHeight } = this.$container;
      const {
        clientWidth: clientWidthSlider,
        clientHeight: clientHeightSlider
      } = this.$slider;

      targetPos.left = Math.min(
        clientWidth - clientWidthSlider / 2,
        Math.max(-clientWidthSlider / 2, targetPos.left)
      );
      targetPos.top = Math.min(
        clientHeight - clientHeightSlider / 2,
        Math.max(-clientHeightSlider / 2, targetPos.top)
      );

      let progress: number;
      if (this.direction === 0) {
        progress =
          ((targetPos.left + clientWidthSlider / 2) / clientWidth) * 100;
      } else {
        progress =
          ((targetPos.top + clientHeightSlider / 2) / clientHeight) * 100;
        progress = 100 - progress;
      }
      progress = Math.min(100, Math.max(0, progress));

      this.$emit(PROGRESS_EVENT.CHANGING, {
        type: 'drag',
        progress
      });
    });
}

使用:

initEvents() {
  this.videoProgress = new ProgressService({
    container: this.doms.play_progess
  }).$on(PROGRESS_EVENT.CHANGE_START, () => {})
    .$on(PROGRESS_EVENT.CHANGING, () => [})
    .$on(PROGRESS_EVENT.CHANGE_END, () => {});

  this.soundProgress = new ProgressService({
    container: this.doms.sound_progress,
    direction: 1
  }).$on(
    ...
    ...
    ...
  )
}

同进度控制,这里抽象为fullscreen.service.ts服务,核心代码如下:

  constructor(options: {
    // 触发全屏的元素 一般为按钮
    trigger: Element;
    // 全屏的目标元素
    target: Element;
   }
  ) {
    ...
    ...
    ...
    this.init();
  }

  hasFullscreen() {
    const element = document as any;

    return !!(
      element.isFullScreen ||
      element.mozIsFullScreen ||
      element.msIsFullScreen ||
      element.webkitIsFullScreen ||
      element.fullScreenElement ||
      element.msFullscreenElement ||
      element.mozFullScreenElement ||
      element.webkitFullscreenElement
    );
  }

  fullScreen() {
    const element = this.$target as any;

    if (element.requestFullscreen) {
      element.requestFullscreen();
    } else if (element.msRequestFullscreen) {
      element.msRequestFullscreen();
    } else if (element.mozRequestFullScreen) {
      element.mozRequestFullScreen();
    } else if (element.webkitRequestFullscreen) {
      element.webkitRequestFullscreen();
    }
  }

  exitFullscreen() {
    const element = document as any;

    if (element.exitFullscreen) {
      element.exitFullscreen();
    } else if ((element as any).msExitFullscreen) {
      (element as any).msExitFullscreen();
    } else if ((element as any).mozCancelFullScreen) {
      (element as any).mozCancelFullScreen();
    } else if ((element as any).webkitExitFullscreen) {
      (element as any).webkitExitFullscreen();
    }
  }

  init() {
    this.triggerClick$ = fromEvent(this.$trigger, 'click').subscribe(() => {
      if (this.hasFullscreen()) {
        this.exitFullscreen();
      } else {
        this.fullScreen();
      }

      this.$emit(FULLSCREEN_EVENTS.CHANGE, this.hasFullscreen());
    });
  }

使用:

 initQuanpinEvent() {
  this.fullscreenService = new FullscreenService({
    trigger: this.doms.quanpin,
    target: this.doms.container
  });
}

即当视频加载出现错误时给用户以提示,视频地址无效,跨域等。之前尝试监听video事件,onerroronabortonstalledonemptied...皆无果,最后解决方案如下:

  get errorTips() {
    if (!this.player) {
      return '';
    }

    const { error } = this.player;
    if (!error) {
      return '';
    }

    const { code, message } = error;
    return (
      message ||
      ['', '意外的中止', '网络错误', '视频解码错误', '无效的视频地址'][code]
    );
  }
上一篇下一篇

猜你喜欢

热点阅读