You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+Zoom Web SDK视频应用如何保留活跃摄像头仅更新变动用户画面

问题根因

你当前的问题核心是CanvasRenderer组件定义在了Session组件内部,每次sessionUsers状态变更触发Session组件重渲染时,都会生成一个全新的CanvasRenderer组件实例,React会将其识别为新的组件类型,直接卸载旧的整个画布列表再重新挂载,导致所有已有的canvas元素被销毁,之前的视频渲染自然全部失效。


修复步骤

  1. 把CanvasRenderer组件移到Session组件外部,保证组件类型稳定,让React.memo可以正常生效
  2. 自定义React.memo的比较逻辑,避免不必要的重渲染
  3. 补充新用户加入时的视频状态检查,主动渲染已开启的视频
  4. 增加待渲染队列,处理canvas还未挂载就收到渲染请求的边界情况

修复后核心代码

// 移到Session组件外部定义,保证组件类型稳定
const areCanvasPropsEqual = (prevProps, nextProps) => {
  // 仅当用户列表的成员发生变化时才触发重渲染
  const prevUserIds = prevProps.state.map(user => user.userId).sort().join()
  const nextUserIds = nextProps.state.map(user => user.userId).sort().join()
  return prevUserIds === nextUserIds
}

const CanvasRenderer = React.memo(({ state }) => {
  if (!state) return <></>
  return state.map((item) => (
    <div className="canvas_container" key={item.userId}>
      <span className="canvas_name_container">
        <p className="canvas_name_self">{item.userId || ''}</p>
      </span>
      <canvas className="canvas" data-canvas_id={item.userId}></canvas>
    </div>
  ))
}, areCanvasPropsEqual)

const Session = () => {
  // ... 其他原有状态
  const stream = useRef({})
  const [ sessionUsers, setSessionUsers ] = useState([])
  // 新增待渲染队列,处理canvas还未挂载就收到渲染请求的情况
  const pendingRenderTasks = useRef([])

  useEffect(() => {
    // 组件更新后执行待处理的渲染任务
    pendingRenderTasks.current.forEach(task => task())
    pendingRenderTasks.current = []
  }, [sessionUsers])

  useEffect(() => {
    // ... 其他监听器
    client.on('peer-video-state-change',async(payload) => {
      const { action, userId } = payload
      try {
        await participantsRender(action,userId)
      } catch (error) {
        console.log(error)
      }
    })

    client.on('user-added', (user) => {
      const participants = client.getAllUser();
      setSessionUsers(participants)
      // 新增:检查新用户是否已开启视频,主动触发渲染
      if (user.video) {
        participantsRender('Start', user.userId)
      }
    })

    client.on('user-removed', (user) => {
      const participants = client.getAllUser();
      setSessionUsers(participants)
    })
  },[client])

  const participantsRender = async(action,userId) => {
    if (!action || !userId) return console.log('Empty Param')
    const canvas = document.querySelector(`.canvas[data-canvas_id="${userId}"]`)
    // 如果canvas还未渲染,加入待执行队列
    if (!canvas) {
      pendingRenderTasks.current.push(() => participantsRender(action, userId))
      return
    }
    if (action === 'Start'){
      await stream.current.renderVideo(canvas, userId, 640, 360, 0, 0, 2);
    } else if (action === 'Stop') {
      await stream.current.stopRenderVideo(canvas, userId);
    } else {
      console.log('error')
    }
  }

  const toggleVideo = async() => { 
    // 原有toggleVideo逻辑保持不变
    if (!stream) return
    const uid = client.getCurrentUserInfo().userId
    const canvas = document.querySelector(`.canvas[data-canvas_id="${uid}"]`)
    if(!stream.current?.isCapturingVideo()) {
      try {
        await stream.current?.startVideo();
        stream.current?.renderVideo(canvas, uid, 640, 360, 0, 0, 2);
      } catch (error) {
        console.log(error)
      }
    } else if (stream.current.isCapturingVideo()){
      try {
        await stream.current.stopVideo()
        stream.current?.stopRenderVideo(canvas, uid)
      } catch (error) {
        console.log(error)
      }
    }
  }
  
  return (
    <div>
      <div className="canvas_list_container">
        <CanvasRenderer state={sessionUsers} />
      </div>
    </div>
  )
}

修复后sessionUsers变化时,React只会新增/删除对应userId的canvas元素,已有的画布不会被卸载,已渲染的视频流就不会被中断。

内容的提问来源于stack exchange,提问作者Nuintee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 10:06:03