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

React+WebRTC+Firebase视频通话ontrack事件未触发求助

React+WebRTC+Firebase视频通话无法触发ontrack接收远程流问题

我正在使用React、WebRTC和Firebase开发视频通话应用,已成功建立本地与远程连接,但无法接收远程MediaStream,ontrack函数始终未触发,不知道原因。

完整代码如下:

import logo from './logo.svg';
import { useEffect, useRef } from 'react';
import './App.css';
import { useState } from 'react';
import { initializeApp } from 'firebase/app'
import { addDoc, collection, doc, getDoc, getFirestore, onSnapshot, setDoc, updateDoc } from 'firebase/firestore'

function App() {
  const myVideoRef = useRef(null)
  const yourVideoRef = useRef(null)
  const [cameras, setcameras] = useState()
  const [myCallId, setmyCallId] = useState()
  const [callId, setcallId] = useState()
  
  const [roomId ,setroomId] = useState()

  const servers = {
    iceServers: [
      {
        urls: ['stun:stun1.l.google.com:19302', 'stun:stun2.l.google.com:19302'],
      },
    ],
    iceCandidatePoolSize: 10,
  }

  const pc = new RTCPeerConnection(servers)


  const firebaseConfig = {
    // 我的配置信息
  }

  const app = initializeApp(firebaseConfig)
  const db = getFirestore(app)

  async function getConnectedDevices(type){
    const devices = await navigator.mediaDevices.enumerateDevices()
    return devices.filter(device => device.kind === type)
  }

  async function openCamera(cameraId){
    const constraints = {
      'audio': {'echoCancellation': true},
      'video': {
        'deviceId': cameraId,
      }
    }

    navigator.mediaDevices.getUserMedia(constraints)
    .then((stream) => {
      stream.getTracks().forEach((track) => {
        pc.addTrack(track,stream)
      })
      myVideoRef.current.srcObject = stream
    })
    
    // 这段代码始终不触发
    let remoteStream = new MediaStream()
    pc.ontrack = (event) => {
      event.streams[0].getTracks().forEach((track) => {
          remoteStream.addTrack(track)
      })
    }

    yourVideoRef.current.srcObject = remoteStream

  }

  async function call(){

    const callDoc = doc(collection(db,"calls"))
    const offerCandidates = collection(callDoc,"offerCandidates")
    const answerCandidates = collection(callDoc,"answerCandidates")
    setmyCallId(callDoc.id)

    pc.onicecandidate = (event) => {
      console.log(event.candidate)
        event.candidate && addDoc(offerCandidates,event.candidate.toJSON())
    }

    const offerDescription = await pc.createOffer()
    await pc.setLocalDescription(offerDescription)

    const offer = {
        sdp: offerDescription.sdp,
        type: offerDescription.type
    }

    await setDoc(callDoc, { offer } )

    onSnapshot(callDoc, snapshot => {
        const data = snapshot.data()
        if(!pc.currentRemoteDescription && data?.answer){
            const answerDescription = new RTCSessionDescription(data.answer)
            pc.setRemoteDescription(answerDescription)
        }
    })

    onSnapshot(answerCandidates, snapshot => {
        snapshot.docChanges().forEach((change) => {
            if(change.type === 'added'){
                console.log(change.doc.data())
                const candidate = new RTCIceCandidate(change.doc.data())
                pc.addIceCandidate(candidate)
                console.log(yourVideoRef.current.srcObject)
                console.log(myVideoRef.current.srcObject)
            }
        })
    })
  }

  async function answer(){
    const callDoc = doc(collection(db,"calls"),callId)
    const offerCandidates = collection(callDoc,"offerCandidates")
    const answerCandidates = collection(callDoc,"answerCandidates")

    pc.onicecandidate = (event) => {
        event.candidate && addDoc(answerCandidates,event.candidate.toJSON())
    }
    
    const callData = (await getDoc(callDoc)).data()
    console.log(callData)

    const offerDescription = callData.offer
    await pc.setRemoteDescription(new RTCSessionDescription(offerDescription))

    const answerDescription = await pc.createAnswer()
    await pc.setLocalDescription(answerDescription)

    const answer = {
        type: answerDescription.type,
        sdp: answerDescription.sdp,
    }

    await updateDoc(callDoc, {answer})

    onSnapshot(offerCandidates, snapshot => {
        snapshot.docChanges().forEach((change) => {
            console.log(change)
            if(change.type === 'added'){
                let data = change.doc.data()
                pc.addIceCandidate(new RTCIceCandidate(data))
            }
        })
    })
  }


  useEffect(() => {
    async function fetch(){
      const cameras = await getConnectedDevices('videoinput')
      setcameras(cameras)
    }

    fetch()
  },[])


  return (
    <div className="App">
      <div className='flex flex-row'>
        {cameras && cameras.map( camera => <h2 className='m-3' key={camera.deviceId} onClick={() => openCamera(camera.deviceId)} >{camera.label}</h2>)}
      </div>
      <div className='flex flex-row'>
        <video className='w-1/2' ref={myVideoRef} autoPlay  />
        <video className='w-1/2' ref={yourVideoRef} autoPlay />
      </div>
      <button onClick={call} className='bg-gray-200'>发起通话</button>
      <div className='flex flex-row'>
        <input onChange={(e) => setcallId(e.target.value)} className='border-[4px] border-black' placeholder="输入通话ID" />
        <button onClick={answer} className='bg-gray-200'>接听通话</button>
      </div>
      <h2>我的通话ID:{myCallId}</h2>
    </div>
  );
}

export default App;

我尝试过将ontrack函数放在useEffect、全局作用域、setRemoteDescription之前,但它仍然无法触发,相关代码片段:

let remoteStream = new MediaStream()
pc.ontrack = (event) => {
  event.streams[0].getTracks().forEach((track) => {
      remoteStream.addTrack(track)
  })
}

yourVideoRef.current.srcObject = remoteStream

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 23:01:13