react 基于 flv.js 封装简单的播放器(四)

1,707 阅读1分钟

这次我已经把这个简单的直播器完成了,接下来就是自己使用,如果有改善的或者有 bug 什么的就接着记录。

今天主要说一下我解决 safari 浏览器全屏的问题。上次我也说了基于前面的代码,在 chrome 中是没有什么问题的,但是在 safari 浏览器中就会出现只有中间一小块有视频,虽然也全屏了。

出现这个的原因是因为我给全屏的那个 div 加了宽和高,在 safari 浏览器中全屏根据现象来看全屏的是当前绑定全屏 div 的上一个节点,这个是虚拟的节点,此时再来看当前节点的宽高,如果是 100% 那么就会充满整个屏幕,否则就按给定的宽高。而 chrome 浏览器全屏的是当前节点,所以没有问题。

代码部分:

<div className={`live ${liveClassName}`}>// 以前是绑定到这个节点上的,这个节点外部可以控制宽高
      <div className={'video-container'} ref={divRef}>// 现在绑定到这个节点上
        <video controls={false} ref={videoRef} className={'video'}>
          {`Your browser is too old which doesn't support HTML5 video.`}
        </video>
        ...

像现在这样就行了。

接下来就是我说的视频流的问题,关闭视频流,我觉得这个需求很扯淡,首先对于直播来说关闭视频流没有意义,而且还要考虑关闭以后还能重新恢复。我考虑这两种情况,第一不需要恢复,那也就代表用户一旦点击了关闭视频流就只能刷新页面或者通过其他方式重新打开视频流,那看直播就没有什么意义了,所以我做的是当用户退出当前页面,或者对于我的组件来说,组件被卸载的时候才关闭视频流。第二种情况,如果关闭以后可以按同样的方式打开,那使用暂停不就 OK 了嘛?当然有可能只是跟用户一种关闭视频流的错觉,所以我就把视频流点击的时候是暂停操作,并且出现视频已关闭的界面,当再次点击的时候把这行文字删除掉,重新出现界面。

效果: 当再次点击的时候就会恢复视频。

这次做这个播放器很简单,但我还是花费了不少的时间,我已经很久没有做过网页开发了, css 都要被忘了,至于 DOM 节点相关的更是忘得厉害。

我感觉今年过年回不去了,要在北京过年了。悲伤。

下面是这个组件的代码,再次我要申明一下我写的东西,我是偏向于日记型的,也就是记日记,方便自己查看。技术沉淀到一定程度了,我才会正式开始写博客。如果有人不小心看到了,如果还看了,如果发现了我说的不对的地方,或者和你看法不一样的地方,可以跟我说一下。

import React, {useCallback, useEffect, useRef, useState} from 'react';
import flvJs from 'flv.js';
import {Slider} from 'antd';
import './styles.less';
import bofang from '../../common/assets/bofang.svg';
import quanping from '../../common/assets/quanping.svg';
import shexiangtou from '../../common/assets/shexiangtou.svg';
import yangshengqi from '../../common/assets/yangshengqi.svg';
import zanting from '../../common/assets/zanting.svg';

interface DIVTypeHTML extends HTMLDivElement {
  mozRequestFullScreen: () => void;
  webkitRequestFullscreen: () => void;
  msRequestFullscreen: () => void;
  isFullscreen: boolean;
}

interface LiveProps {
  liveClassName?: string;
  url: string;
  videoSourceText: string;
}

function Live({liveClassName, url, videoSourceText}: LiveProps) {
  const [isPlay, setIsPlay] = useState(false);
  const [isShowAudio, setIsShowAudio] = useState(false);
  const flvRef = useRef<flvJs.Player>();
  const videoRef = useRef<HTMLVideoElement>(null);
  const divRef = useRef<DIVTypeHTML>(null);
  const [closeVideoText, setCloseVideo] = useState('hide');
  useEffect(() => {
    if (flvJs.isSupported()) {
      flvRef.current = flvJs.createPlayer({
        type: 'flv',
        isLive: true,
        cors: true,
        hasVideo: true,
        url,
      });
      if (videoRef.current) {
        flvRef.current.attachMediaElement(videoRef.current);
        flvRef.current.load();
      }
    }
    return () => {
      flvRef.current?.pause();
      flvRef.current?.unload();
      flvRef.current?.detachMediaElement();
      flvRef.current?.destroy();
      flvRef.current = undefined;
      setIsPlay(false);
    };
  }, [url]);
  const requestFullscreen = useCallback((document: DIVTypeHTML) => {
    if (document.requestFullscreen) {
      document.requestFullscreen();
    } else if (document.mozRequestFullScreen) {
      document.mozRequestFullScreen();
    } else if (document.webkitRequestFullscreen) {
      document.webkitRequestFullscreen();
    } else if (document.msRequestFullscreen) {
      document.msRequestFullscreen();
    }
  }, []);
  const exitFullscreen = useCallback(() => {
    const document: any = window.document;
    if (document.exitFullscreen) {
      document.exitFullscreen();
    } else if (document.mozCancelFullScreen) {
      document.mozCancelFullScreen();
    } else if (document.webkitExitFullscreen) {
      document.webkitExitFullscreen();
    }
  }, []);
  const onClickPlay = useCallback(() => {
    if (flvRef.current) {
      if (isPlay) {
        flvRef.current.pause();
      } else {
        flvRef.current.play();
      }
      setIsPlay(!isPlay);
    }
  }, [isPlay]);
  const onClickAudio = useCallback(() => {
    setIsShowAudio((isSHow) => !isSHow);
  }, []);
  const onClickFullscreen = useCallback(() => {
    const document = divRef.current;
    if (!document) {
      return;
    }
    if (document.isFullscreen) {
      exitFullscreen();
    } else {
      requestFullscreen(document);
    }
    document.isFullscreen = !document.isFullscreen;
  }, [exitFullscreen, requestFullscreen]);
  const onChange = useCallback((value: number) => {
    if (videoRef.current) {
      videoRef.current.volume = value / 100;
    }
  }, []);
  const onVideoClick = useCallback(() => {
    setCloseVideo((close) => {
      if (close === 'hide') {
        if (flvRef.current) {
          flvRef.current.pause();
          setIsPlay(false);
        }
        return 'show';
      } else {
        return 'hide';
      }
    });
  }, []);
  return (
    <div className={`live ${liveClassName}`}>
      <div className={'video-container'} ref={divRef}>
        <video controls={false} ref={videoRef} className={'video'}>
          {`Your browser is too old which doesn't support HTML5 video.`}
        </video>
        <div className={`video-text video-text-${closeVideoText}`}>
          <span>当前视频已关闭</span>
        </div>
        <div className={'control'}>
          {/* 控制播放和暂停 */}
          <div className={'img-container'}>
            <img
              src={isPlay ? zanting : bofang}
              className={'img'}
              onClick={onClickPlay}
            />
          </div>
          {/* 控制是否关闭视频流 */}
          <div className={'img-container'}>
            <img src={shexiangtou} onClick={onVideoClick} className={'img'} />
          </div>
          {/* 控制声音 */}
          <div
            className={'img-container audio-container'}
            onClick={onClickAudio}>
            <img src={yangshengqi} className={'img'} />
            <div className={`audio ${isShowAudio ? 'show' : 'hide'}`}>
              <Slider
                defaultValue={100}
                min={0}
                max={100}
                vertical
                tipFormatter={null}
                onChange={onChange}
              />
            </div>
          </div>
          {/* 当前是什么视频 全景视频/ */}
          {videoSourceText ? (
            <div className={'img-container'}>
              <span className={'text-detail'}>{videoSourceText}</span>
            </div>
          ) : null}
          {/* 是否全屏显示 */}
          <div className={'img-container'} onClick={onClickFullscreen}>
            <img src={quanping} className={'img'} />
          </div>
        </div>
      </div>
    </div>
  );
}

export default Live;