我的任务是从 rtsp 流获取视频,然后准备并发送该流以供浏览器上的 React 客户端使用。 但我无法在客户端启动 oneicecandidate 和 ontrack 处理程序。 我想我已经在我的
p2p
服务器和 nodejs
客户端之间建立了 react
连接。但是oneicecandidate
处理程序两侧都没有开火,任何人都可以帮我解决这个问题吗?
这是反应组件:
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
这是服务器:
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)
这里是报价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'
}
这是答案 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'
}
}
我想我应该提到客户端和服务器运行在同一台电脑和网络上
我的目标是让rtsp流在浏览器中工作
您没有向 RTCPeerConnection 添加任何 MediaStreamTrack 或数据通道。因此,您的 SDP 仅显示所谓的“会话部分”,并且不用于协商任何ice 候选者。因此,不会收集 ICE 候选人,并且 onicecandidate 不会触发。
如果您想从服务器接收数据,请使用
createOffer({offerToReceiveVideo: true})
或调用 addTransceiver('video')
。
另请注意,“muazkh”大约在十年前更改了该 TURN 帐户的凭据。