Search code examples
reactjsnode.jswebrtcrtsprtcpeerconnection

cant get iceCandidate when p2p connection


My mission is to get a video from rtsp stream then prepare and send that stream for react client on browser. But i cant make fire oneicecandidate and ontrack handlers on client side. i think i've established p2p connection between my nodejs server and react client. but oneicecandidate handler does not fire on both sides, can anyone help me to handle this problem?

here is the react component:

import {useEffect, useRef} from 'react'
import {io} from 'socket.io-client'

function VideoPlayer() {
  const videoRef = useRef<HTMLVideoElement>(null)

  const socketRef = useRef(io('ws://localhost:3333'))

  useEffect(() => {
    console.log('mount')
    const peer = new RTCPeerConnection({
      iceServers: [
        {
          urls: [
            'stun:stun.l.google.com:19302',
            'stun:stun1.l.google.com:19302',
            'stun:stun2.l.google.com:19302',
            'turn:numb.viagenie.ca',
          ],
          credential: 'muazkh',
          username: '[email protected]',
        },
      ],
    })

    socketRef.current.on('open', () => {
      console.log('connected')
    })

    socketRef.current.on('error', (err) => {
      console.log(err)
    })

    socketRef.current.on('toClient', async (message) => {
      console.log(message)
      if (message.answer) {
        await peer.setRemoteDescription(message.answer)
      }

      if (message.iceCandidate) {
        await peer.addIceCandidate(message.iceCandidate)
      }
    })

    peer.onicecandidate = (event) => {
      console.log('onicecandidate', {event})
      socketRef.current.emit('fromClient', {iceCandidate: event.candidate})
    }

    peer.ontrack = (event) => {
      console.log('ontrack', event)

      if (videoRef.current) {
        videoRef.current.srcObject = event.streams[0]
        console.log(event.streams)
        videoRef.current.play()
      }
    }

    const init = async () => {
      const offer = await peer.createOffer()
      await peer.setLocalDescription(offer)

      console.log({offer})
      socketRef.current.emit('fromClient', {offer})
    }

    init()
  }, [])

  return <video ref={videoRef} id='remoteVideo' autoPlay />
}

export default VideoPlayer

and here is server:

const express = require('express')
const {spawn} = require('child_process')
const {MediaStream, RTCPeerConnection, nonstandard} = require('wrtc')
const {RTCVideoSource} = nonstandard
const Chunker = require('./utils/chunker')
const {Server} = require('socket.io')
const {createServer} = require('node:http')
const cors = require('cors')

const rtspUrl = 'rtsp://192.168.1.136/main_stream'

const app = express()
app.use(cors())

const server = createServer(app)
const socketIoServer = new Server({
  server,
  cors: {
    origin: '*',
  },
})

const width = 1920
const height = 1080
const ffmpeg = spawn('ffmpeg', ['-i', rtspUrl, '-c', 'copy', '-f', 'mpegts', 'pipe:1'])

const videoStream = new MediaStream()
const videoSource = new RTCVideoSource()
const videoTrack = videoSource.createTrack()
const videoChunker = new Chunker(width * height * 1.5)
ffmpeg.stdout.pipe(videoChunker)

videoChunker.on('data', (data) => {
  const i420Frame = {
    width,
    height,
    data: new Uint8ClampedArray(data),
  }
  videoSource.onFrame(i420Frame)
})

const pc = new RTCPeerConnection()
videoStream.getTracks().forEach((track) => pc.addTrack(track, videoStream))

const fromClientCb = async (message) => {
  if (message.offer) {
    console.log(message.offer)
    await pc.setRemoteDescription(message.offer)
    const answer = await pc.createAnswer()
    console.log({answer})
    await pc.setLocalDescription(answer)

    socketIoServer.emit('toClient', {answer})
  }

  if (message.iceCandidate) {
    console.log({iceCandidate: message.iceCandidate})
    pc.addIceCandidate(message.iceCandidate)
  }
}

pc.onicecandidate = (event) => {
  console.log('onicecandidate', event)
  socketIoServer.emit('toClient', {iceCandidate: event.candidate})
}

socketIoServer.on('connect', (socket) => {
  console.log('connected')
  socket.on('fromClient', (message) => fromClientCb(message))
})

socketIoServer.listen(3333)

here is offer sdp:

{
  type: 'offer',
  sdp: 'v=0\r\n' +
    'o=mozilla...THIS_IS_SDPARTA-99.0 4698947050461943875 0 IN IP4 0.0.0.0\r\n' +
    's=-\r\n' +
    't=0 0\r\n' +
    'a=fingerprint:sha-256 4A:18:D0:76:03:B8:75:FE:E8:9B:01:A3:1C:87:5D:2D:35:22:B3:03:EC:8F:4B:A9:18:37:86:56:B7:58:86:B8\r\n' +
    'a=ice-options:trickle\r\n' +
    'a=msid-semantic:WMS *\r\n'
}

and here is answer sdp

{
  answer: {
    type: 'answer',
    sdp: 'v=0\r\n' +
      'o=- 4690817293092286367 12 IN IP4 127.0.0.1\r\n' +
      's=-\r\n' +
      't=0 0\r\n' +
      'a=msid-semantic: WMS\r\n'
  }
}

and i think i should mention that the client and server are running on the same PC and net

my aim is to make rtsp stream work in browser


Solution

  • You are not adding any MediaStreamTrack or datachannel to the RTCPeerConnection. As such, your SDP only shows what is called the "session part" and that is not used to negotiate any ice candidates. Consquently, no ICE candidates are gathered and onicecandidate does not trigger.

    If you want to receive data from your server, either use createOffer({offerToReceiveVideo: true}) or call addTransceiver('video').

    Also note that "muazkh" changed the credentials for that TURN account roughly ten years ago.