React函数式和类组件实现相同功能,函数式组件执行错误?

同一个功能,类组件改为函数式组件后执行错误了,帮忙看看是哪里语法错误了吗?

import React, { useState  } from "react"

import { Button,Input, Tooltip } from 'antd'

import { CopyOutlined } from "@ant-design/icons"

import './index.less'

class CeratRtc extends React.Component {

// 创建本地/远程 SDP 描述, 用于描述本地/远程的媒体流

state = {

offerSdp: '',

offerSdp2: '',

answerSdp: '',

answerSdp2: ''

}

// 内网中使用

pc = new RTCPeerConnection()

componentDidMount() {

this.init()

}

async init () {

// 获取本地端视频标签

const localVideo = document.getElementById('local') as HTMLVideoElement

// 获取远程端视频标签

const remoteVideo = document.getElementById('remote') as HTMLVideoElement

// 采集本地媒体流

const localStream = await navigator.mediaDevices.getUserMedia({

video: true,

audio: true,

})

// 设置本地视频流

localVideo.srcObject = localStream

// 添加本地媒体流的轨道都添加到 RTCPeerConnection 中

localStream.getTracks().forEach((track) => {

this.pc.addTrack(track, localStream)

})

// 监听远程流,方法一:

this.pc.ontrack = (event) => {

remoteVideo.srcObject = event.streams[0]

}

// 方法二:你也可以使用 addStream API,来更加详细的控制流的添加

// const remoteStream: MediaStream = new MediaStream()

// pc.ontrack = (event) => {

// event.streams[0].getTracks().forEach((track) => {

// remoteStream.addTrack(track)

// })

// // 设置远程视频流

// remoteVideo.srcObject = remoteStream

// }

}

/**

*建立连接的主要过程,就是通过 RTCPeerConnection 对象的 createOffer 方法来创建本地的 SDP 描述

然后通过 RTCPeerConnection 对象的 setLocalDescription 方法来设置本地的 SDP 描述

最后通过 RTCPeerConnection 对象的 setRemoteDescription 方法来设置远程的 SDP 描述

*

* @memberof CeratRtc

*/

createOffer = async () => {

// 创建offer

const offer = await this.pc.createOffer()

// 设置本地描述

await this.pc.setLocalDescription(offer)

// 到这里,我们本地的 offer 就创建好了,一般在这里通过信令服务器发送 offerSdp 给远端

// 监听 RTCPeerConnection 的 onicecandidate 事件,当 ICE 服务器返回一个新的候选地址时,就会触发该事件

this.pc.onicecandidate = async (event) => {

if (event.candidate) {

this.setState({

offerSdp: JSON.stringify(this.pc.localDescription)

})

}

}

}

/**

*作为接收方,在拿到 offer 后

就可以创建 answer 并设置到本地描述中

然后通过信令服务器发送 answer 给对端。

*

* @memberof CeratRtc

*/

createAnswer = async () => {

// 解析字符串

const offer = JSON.parse(this.state.offerSdp2)

this.pc.onicecandidate = async (evet) => {

if (evet.candidate) {

this.setState({

answerSdp: JSON.stringify(this.pc.localDescription)

})

}

}

await this.pc.setRemoteDescription(offer)

const answer = await this.pc.createAnswer()

await this.pc.setLocalDescription(answer)

}

/**

*添加answer的应答

接收方拿到 answer 后,就可以设置到远程端的描述中。

*

* @memberof CeratRtc

*/

addAnswer = async () => {

const answer = JSON.parse(this.state.answerSdp2)

if (!this.pc.currentRemoteDescription) {

this.pc.setRemoteDescription(answer)

}

}

copyToClipboard = async (str: string) => {

navigator.clipboard.writeText(str)

}

handleChange2 = (e: { target: { value: string } }) => {

this.setState({

offerSdp2: e.target.value

})

}

handleChange3 = (e: { target: { value: string } }) => {

this.setState({

answerSdp2: e.target.value

})

}

render(): React.ReactNode {

return (

<div className="page-container">

<div className="video-container">

<div className="video-box">

<video id="local" autoPlay playsInline muted></video>

<div className="video-title">我</div>

</div>

<div className="video-box">

<video id="remote" autoPlay playsInline></video>

<div className="video-title">远程视频</div>

</div>

</div>

<div className="operation">

{/* step1 */}

<div className="step">

<div className="user">

用户1的操作区域

</div>

<p className="desc">

点击 Create Offer,生成 SDP offer,把下面生成的offer 复制给用户 2

<Button id="create-offer" type="primary" onClick={this.createOffer}>

创建 Offer

</Button>

</p>

<p>SDP offer:</p>

<Input.Group compact >

<Input

style={{ width: 'calc(100% - 200px)' }}

defaultValue={this.state.offerSdp}

value={this.state.offerSdp}

/>

<Tooltip title="copy address">

<Button id="create-offer" type="primary" onClick={this.copyToClipboard.bind(this, this.state.offerSdp)} icon={<CopyOutlined />} />

</Tooltip>

</Input.Group>

</div>

{/* step2 */}

<div className="step">

<div className="user">

用户2的操作区域

</div>

<p className="desc">

用户 2将用户1 刚才生成的SDP offer 粘贴到下方,点击 "创建答案

"来生成SDP答案,然后将 SDP Answer 复制给用户 1。

</p>

<Input.Group compact>

<Input style={{ width: 'calc(100% - 200px)' }} defaultValue={this.state.offerSdp2} onChange={this.handleChange2} />

<Button type="primary" onClick={this.createAnswer}>创建Answer</Button>

</Input.Group>

<p>SDP Answer:</p>

<Input.Group compact >

<Input

style={{ width: 'calc(100% - 200px)' }}

defaultValue={this.state.answerSdp}

value={this.state.answerSdp}

/>

<Tooltip title="copy address">

<Button id="create-offer" type="primary" onClick={this.copyToClipboard.bind(this, this.state.answerSdp)} icon={<CopyOutlined />} />

</Tooltip>

</Input.Group>

</div>

{/* <!-- step3 --> */}

<div className="step">

<div className="user">用户 1 的操作区域</div>

<p>将用户 2 创建的 Answer 粘贴到下方,然后点击 Add Answer。</p>

<p>SDP Answer:</p>

<Input.Group compact>

<Input style={{ width: 'calc(100% - 200px)' }} defaultValue={this.state.answerSdp2} onChange={this.handleChange3} />

<Button type="primary" onClick={this.addAnswer}>添加Answer</Button>

</Input.Group>

</div>

</div>

</div>

)

}

}

export default CeratRtc

import React, { useEffect, useState  } from "react"

import { Button,Input, Tooltip } from 'antd'

import { CopyOutlined } from "@ant-design/icons"

import './index.less'

const CeratRtc = () => {

useEffect(() => {

init()

}, [])

// 创建本地/远程 SDP 描述, 用于描述本地/远程的媒体流

const [offerSdp, setOffer] = useState('')

const [offerSdp2, setOfferSdp2] = useState('')

const [answerSdp, setAnswerSdp] = useState('')

const [answerSdp2, setAnswerSdp2] = useState('')

// 内网中使用

const pc = new RTCPeerConnection()

const init = async () => {

// 获取本地端视频标签

const localVideo = document.getElementById('local') as HTMLVideoElement

// 获取远程端视频标签

const remoteVideo = document.getElementById('remote') as HTMLVideoElement

// 采集本地媒体流

const localStream = await navigator.mediaDevices.getUserMedia({

video: true,

audio: true,

})

// 设置本地视频流

localVideo.srcObject = localStream

// 添加本地媒体流的轨道都添加到 RTCPeerConnection 中

localStream.getTracks().forEach((track) => {

pc.addTrack(track, localStream)

})

// 监听远程流,方法一:

pc.ontrack = (event) => {

remoteVideo.srcObject = event.streams[0]

}

// 方法二:你也可以使用 addStream API,来更加详细的控制流的添加

// const remoteStream: MediaStream = new MediaStream()

// pc.ontrack = (event) => {

// event.streams[0].getTracks().forEach((track) => {

// remoteStream.addTrack(track)

// })

// // 设置远程视频流

// remoteVideo.srcObject = remoteStream

// }

}

/**

*建立连接的主要过程,就是通过 RTCPeerConnection 对象的 createOffer 方法来创建本地的 SDP 描述

然后通过 RTCPeerConnection 对象的 setLocalDescription 方法来设置本地的 SDP 描述

最后通过 RTCPeerConnection 对象的 setRemoteDescription 方法来设置远程的 SDP 描述

*

* @memberof CeratRtc

*/

const createOffer = async () => {

// 创建offer

const offer = await pc.createOffer()

// 设置本地描述

await pc.setLocalDescription(offer)

// 到这里,我们本地的 offer 就创建好了,一般在这里通过信令服务器发送 offerSdp 给远端

// 监听 RTCPeerConnection 的 onicecandidate 事件,当 ICE 服务器返回一个新的候选地址时,就会触发该事件

pc.onicecandidate = async (event) => {

if (event.candidate) {

setOffer(JSON.stringify(pc.localDescription))

}

}

}

/**

*作为接收方,在拿到 offer 后

就可以创建 answer 并设置到本地描述中

然后通过信令服务器发送 answer 给对端。

*

* @memberof CeratRtc

*/

const createAnswer = async () => {

// 解析字符串

const offer = JSON.parse(offerSdp2)

pc.onicecandidate = async (evet) => {

if (evet.candidate) {

setAnswerSdp(JSON.stringify(pc.localDescription))

}

}

await pc.setRemoteDescription(offer)

const answer = await pc.createAnswer()

await pc.setLocalDescription(answer)

}

/**

*添加answer的应答

接收方拿到 answer 后,就可以设置到远程端的描述中。

*

* @memberof CeratRtc

*/

const addAnswer = async () => {

const answer = JSON.parse(answerSdp2)

if (!pc.currentRemoteDescription) {

pc.setRemoteDescription(answer)

}

}

const copyToClipboard = async (str: string) => {

navigator.clipboard.writeText(str)

}

const handleChange2 = (e: { target: { value: string } }) => {

setOfferSdp2(e.target.value)

}

const handleChange3 = (e: { target: { value: string } }) => {

setAnswerSdp2(e.target.value)

}

return (

<div className="page-container">

<div className="video-container">

<div className="video-box">

<video id="local" autoPlay playsInline muted></video>

<div className="video-title">我</div>

</div>

<div className="video-box">

<video id="remote" autoPlay playsInline></video>

<div className="video-title">远程视频</div>

</div>

</div>

<div className="operation">

{/* step1 */}

<div className="step">

<div className="user">

用户1的操作区域

</div>

<p className="desc">

点击 Create Offer,生成 SDP offer,把下面生成的offer 复制给用户 2

<Button id="create-offer" type="primary" onClick={createOffer}>

创建 Offer

</Button>

</p>

<p>SDP offer:</p>

<Input.Group compact >

<Input

style={{ width: 'calc(100% - 200px)' }}

defaultValue={offerSdp}

value={offerSdp}

/>

<Tooltip title="copy address">

<Button id="create-offer" type="primary" onClick={copyToClipboard.bind(this, offerSdp)} icon={<CopyOutlined />} />

</Tooltip>

</Input.Group>

</div>

{/* step2 */}

<div className="step">

<div className="user">

用户2的操作区域

</div>

<p className="desc">

用户 2将用户1 刚才生成的SDP offer 粘贴到下方,点击 "创建答案

"来生成SDP答案,然后将 SDP Answer 复制给用户 1。

</p>

<Input.Group compact>

<Input style={{ width: 'calc(100% - 200px)' }} defaultValue={offerSdp2} onChange={handleChange2} />

<Button type="primary" onClick={createAnswer}>创建Answer</Button>

</Input.Group>

<p>SDP Answer:</p>

<Input.Group compact >

<Input

style={{ width: 'calc(100% - 200px)' }}

defaultValue={answerSdp}

value={answerSdp}

/>

<Tooltip title="copy address">

<Button id="create-offer" type="primary" onClick={copyToClipboard.bind(this, answerSdp)} icon={<CopyOutlined />} />

</Tooltip>

</Input.Group>

</div>

{/* <!-- step3 --> */}

<div className="step">

<div className="user">用户 1 的操作区域</div>

<p>将用户 2 创建的 Answer 粘贴到下方,然后点击 Add Answer。</p>

<p>SDP Answer:</p>

<Input.Group compact>

<Input style={{ width: 'calc(100% - 200px)' }} defaultValue={answerSdp2} onChange={handleChange3} />

<Button type="primary" onClick={addAnswer}>添加Answer</Button>

</Input.Group>

</div>

</div>

</div>

)

}

export default CeratRtc

函数式组件点击addAnswer后报错:

// Uncaught (in promise) DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp: Called in wrong state: stable


回答:

代码太长,我猜测是init捕获的 pc 和回调函数捕获的pc不一致导致的问题,因为按照这种写法,组件每渲染一次,都会生成全新的 pc 。除非是单例模式,不然这些 pc 之间彼此可能是没有关系的,浪费计算和信道资源不说,能不能构成连续的会话都是个问题。
所以第一点要改的,是 pc 要跨渲染周期,也就是确保每一次渲染前后访问到的都是同一个 RTCPeerConnection 实例,这个目标可以使用 useRef 来达成:

const CreateRTC = () => {

const pc = useRef<RTCPeerConnection|null>(null);

useEffect(() => {

pc.current = new RTCPeerConnection();

// 别的地方也记住用 pc.current 访问 RTCPeerConnection 实例:

// const peer = pc.current as RTCPeerConnection;

return () => {

// 使用 Effect 最好在返回的函数里负责地将其销毁

pc.current.close();

}

}, [])

}


回答:

把const pc = new RTCPeerConnection()放到CeratRtc方法上面就可以了,不知道为什么

以上是 React函数式和类组件实现相同功能,函数式组件执行错误? 的全部内容, 来源链接: utcz.com/p/933590.html

回到顶部