视频元素在重新渲染时重置

时间:2021-07-24 13:42:58

标签: reactjs

我一直在 React 中使用视频元素。通过使用 ref 来设置 srcObject,它可以正常工作。但是,一旦应用程序重新呈现,视频就会丢失并变黑。

无论如何允许视频通过状态更新(重新渲染)继续播放?

以下代码的行为:

  1. 第一次调用 getMedia 什么都不做。
  2. 第二次调用 getMedia 正确呈现视频元素中的相机源。
  3. 第一次调用 startCall 会终止视频源(变黑)。
export const CallTestingPage: FunctionComponent = () => {
  const classes = useStyles();

  const localVideoElement = useRef<HTMLVideoElement>(null);
  const remoteVideoElement = useRef<HTMLVideoElement>(null);

  const [startCallBtnDisabled, setStartCallBtnDisabled] = useState(true);

  async function getMedia() {
    // console.log("getMedia");
    try {
      const cameraStream = await navigator.mediaDevices.getUserMedia({video: true});
      localVideoElement!.current!.srcObject = cameraStream;
      console.log(localVideoElement.current?.srcObject);
      setStartCallBtnDisabled(false);
    } catch (e) {
      const message = `getUserMedia error: ${e.name} :: PermissionDeniedError may mean invalid constraints.`;
      alert(message);
    }
  }

  function startCall() {
    setStartCallBtnDisabled(true);
  }

  function endCall() {}

  function LocalVideo () {
    const classes = useStyles();
    return (
      <>
        <div id="localVideo">
          <div>LOCAL VIDEO</div>
          <br />
          <video autoPlay muted className={classes.videoPlayer} ref={localVideoElement} />
        </div>
      </>
    )
  }
  
  function RemoteVideo () {
    const classes = useStyles();
    return (
      <>
        <div id="remoteVideo">
          <div>REMOTE VIDEO</div>
          <br />
          <video autoPlay muted className={classes.videoPlayer} ref={remoteVideoElement} />
        </div>
      </>
    )
  }

  return (
    <PageSection>
      <div className={classes.root}>
        <Grid container spacing={3}>

          <Grid item xs={12} sm={5}>
            <Paper className={classes.paper}>
              <LocalVideo />
            </Paper>
          </Grid>

          <Grid item xs={12} sm={2}>
            <Paper className={classes.paper}>
              ACTIVE CANDIDATES
              <br />
              <br />
              LOCAL
              <br />
              <br />
              REMOTE
              <br />
            </Paper>
          </Grid>

          <Grid item xs={12} sm={5}>
            <Paper className={classes.paper}>
              <RemoteVideo />
            </Paper>
          </Grid>

          <Grid item xs={12}>
            <Paper className={classes.paper}>
            <ButtonGroup color="primary" aria-label="outlined primary button group">
              <Button onClick={getMedia}>Get Media</Button>
              <Button onClick={startCall} disabled={startCallBtnDisabled}>Start Call</Button>
              <Button onClick={endCall}>Hang Up</Button>
            </ButtonGroup>
            </Paper>
          </Grid>

          <Grid item xs={12}>
            <Paper className={classes.paper}> 
              STATS 
              <br />
              <br />
              <br />
              {/* <div ref={stats}></div> */}
            </Paper>
          </Grid>
        </Grid>
      </div>
    </PageSection>
  );
}

编辑:

useEffect(() => {
  console.log(localVideoElement.current?.srcObject)
}, [startCallBtnDisabled])

// this returns null every time

1 个答案:

答案 0 :(得分:1)

我很确定问题在于您已经在另一个功能组件内部定义了功能组件。这通常是不好的做法,但在您的情况下尤其成问题,因为这意味着,每次顶级功能组件重新渲染时,这些组件都会被全新的组件定义替换,这意味着 React 不知道它可以重新-使用现有的 <video> 元素。试试这个:

export const CallTestingPage: FunctionComponent = () => {
  const classes = useStyles();

  const localVideoElement = useRef<HTMLVideoElement>(null);
  const remoteVideoElement = useRef<HTMLVideoElement>(null);

  const [startCallBtnDisabled, setStartCallBtnDisabled] = useState(true);

  async function getMedia() {
    // console.log("getMedia");
    try {
      const cameraStream = await navigator.mediaDevices.getUserMedia({video: true});
      localVideoElement!.current!.srcObject = cameraStream;
      console.log(localVideoElement.current?.srcObject);
      setStartCallBtnDisabled(false);
    } catch (e) {
      const message = `getUserMedia error: ${e.name} :: PermissionDeniedError may mean invalid constraints.`;
      alert(message);
    }
  }

  function startCall() {
    setStartCallBtnDisabled(true);
  }

  function endCall() {}

  return (
    <PageSection>
      <div className={classes.root}>
        <Grid container spacing={3}>

          <Grid item xs={12} sm={5}>
            <Paper className={classes.paper}>
              <LocalVideo localVideoElement={localVideoElement} />
            </Paper>
          </Grid>

          <Grid item xs={12} sm={2}>
            <Paper className={classes.paper}>
              ACTIVE CANDIDATES
              <br />
              <br />
              LOCAL
              <br />
              <br />
              REMOTE
              <br />
            </Paper>
          </Grid>

          <Grid item xs={12} sm={5}>
            <Paper className={classes.paper}>
              <RemoteVideo remoteVideoElement={remoteVideoElement} />
            </Paper>
          </Grid>

          <Grid item xs={12}>
            <Paper className={classes.paper}>
            <ButtonGroup color="primary" aria-label="outlined primary button group">
              <Button onClick={getMedia}>Get Media</Button>
              <Button onClick={startCall} disabled={startCallBtnDisabled}>Start Call</Button>
              <Button onClick={endCall}>Hang Up</Button>
            </ButtonGroup>
            </Paper>
          </Grid>

          <Grid item xs={12}>
            <Paper className={classes.paper}> 
              STATS 
              <br />
              <br />
              <br />
              {/* <div ref={stats}></div> */}
            </Paper>
          </Grid>
        </Grid>
      </div>
    </PageSection>
  );
}

function LocalVideo ({localVideoElement}) {
  const classes = useStyles();
  return (
    <>
      <div id="localVideo">
        <div>LOCAL VIDEO</div>
        <br />
        <video autoPlay muted className={classes.videoPlayer} ref={localVideoElement} />
      </div>
    </>
  )
}
  
function RemoteVideo ({remoteVideoElement}) {
  const classes = useStyles();
  return (
    <>
      <div id="remoteVideo">
        <div>REMOTE VIDEO</div>
        <br />
        <video autoPlay muted className={classes.videoPlayer} ref={remoteVideoElement} />
      </div>
    </>
  )
}