Initiate connection when we receive SDP and don't connect when null (#5451)
* Initiate connection when we receive SDP and don't connect when null Sometimes clients were gathering ice candidates faster than we returning the SDP answer, which meant we tried to parse a null as the remote description. Clean up tsc error and add log on timeout * Add fallback for windows CI WIP * If we get sdp answer just connect * typo * Fmt --------- Co-authored-by: 49fl <ircsurfer33@gmail.com>
This commit is contained in:
@ -240,6 +240,20 @@ export enum EngineConnectionEvents {
|
|||||||
NewTrack = 'new-track', // (track: NewTrackArgs) => void
|
NewTrack = 'new-track', // (track: NewTrackArgs) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toRTCSessionDescriptionInit(
|
||||||
|
desc: Models['RtcSessionDescription_type']
|
||||||
|
): RTCSessionDescriptionInit | undefined {
|
||||||
|
if (desc.type === 'unspecified') {
|
||||||
|
console.error('Invalid SDP answer: type is "unspecified".')
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
sdp: desc.sdp,
|
||||||
|
// Force the type to be one of the valid RTCSdpType values
|
||||||
|
type: desc.type as RTCSdpType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// EngineConnection encapsulates the connection(s) to the Engine
|
// EngineConnection encapsulates the connection(s) to the Engine
|
||||||
// for the EngineCommandManager; namely, the underlying WebSocket
|
// for the EngineCommandManager; namely, the underlying WebSocket
|
||||||
// and WebRTC connections.
|
// and WebRTC connections.
|
||||||
@ -250,7 +264,7 @@ class EngineConnection extends EventTarget {
|
|||||||
mediaStream?: MediaStream
|
mediaStream?: MediaStream
|
||||||
idleMode: boolean = false
|
idleMode: boolean = false
|
||||||
promise?: Promise<void>
|
promise?: Promise<void>
|
||||||
sdpAnswer?: Models['RtcSessionDescription_type']
|
sdpAnswer?: RTCSessionDescriptionInit
|
||||||
triggeredStart = false
|
triggeredStart = false
|
||||||
|
|
||||||
onIceCandidate = function (
|
onIceCandidate = function (
|
||||||
@ -549,6 +563,50 @@ class EngineConnection extends EventTarget {
|
|||||||
this.disconnectAll()
|
this.disconnectAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initiateConnectionExclusive(): boolean {
|
||||||
|
// Only run if:
|
||||||
|
// - A peer connection exists,
|
||||||
|
// - ICE gathering is complete,
|
||||||
|
// - We have an SDP answer,
|
||||||
|
// - And we haven’t already triggered this connection.
|
||||||
|
if (!this.pc || this.triggeredStart || !this.sdpAnswer) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
this.triggeredStart = true
|
||||||
|
|
||||||
|
// Transition to the connecting state
|
||||||
|
this.state = {
|
||||||
|
type: EngineConnectionStateType.Connecting,
|
||||||
|
value: { type: ConnectingType.WebRTCConnecting },
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to set the remote description to initiate connection
|
||||||
|
this.pc
|
||||||
|
.setRemoteDescription(this.sdpAnswer)
|
||||||
|
.then(() => {
|
||||||
|
// Update state once the remote description has been set
|
||||||
|
this.state = {
|
||||||
|
type: EngineConnectionStateType.Connecting,
|
||||||
|
value: { type: ConnectingType.SetRemoteDescription },
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error: Error) => {
|
||||||
|
console.error('Failed to set remote description:', error)
|
||||||
|
this.state = {
|
||||||
|
type: EngineConnectionStateType.Disconnecting,
|
||||||
|
value: {
|
||||||
|
type: DisconnectingType.Error,
|
||||||
|
value: {
|
||||||
|
error: ConnectionError.LocalDescriptionInvalid,
|
||||||
|
context: error,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
this.disconnectAll()
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to connect to the Engine over a WebSocket, and
|
* Attempts to connect to the Engine over a WebSocket, and
|
||||||
* establish the WebRTC connections.
|
* establish the WebRTC connections.
|
||||||
@ -588,38 +646,13 @@ class EngineConnection extends EventTarget {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const initiateConnectingExclusive = () => {
|
|
||||||
if (that.triggeredStart) return
|
|
||||||
that.triggeredStart = true
|
|
||||||
|
|
||||||
// Start connecting.
|
|
||||||
that.state = {
|
|
||||||
type: EngineConnectionStateType.Connecting,
|
|
||||||
value: {
|
|
||||||
type: ConnectingType.WebRTCConnecting,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// As soon as this is set, RTCPeerConnection tries to
|
|
||||||
// establish a connection.
|
|
||||||
// @ts-expect-error: Have to ignore because dom.ts doesn't have the right type
|
|
||||||
void that.pc?.setRemoteDescription(that.sdpAnswer)
|
|
||||||
|
|
||||||
that.state = {
|
|
||||||
type: EngineConnectionStateType.Connecting,
|
|
||||||
value: {
|
|
||||||
type: ConnectingType.SetRemoteDescription,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onIceCandidate = (event: RTCPeerConnectionIceEvent) => {
|
this.onIceCandidate = (event: RTCPeerConnectionIceEvent) => {
|
||||||
console.log('icecandidate', event.candidate)
|
console.log('icecandidate', event.candidate)
|
||||||
|
|
||||||
// This is null when the ICE gathering state is done.
|
// This is null when the ICE gathering state is done.
|
||||||
// Windows ONLY uses this to signal it's done!
|
// Windows ONLY uses this to signal it's done!
|
||||||
if (event.candidate === null) {
|
if (event.candidate === null) {
|
||||||
initiateConnectingExclusive()
|
that.initiateConnectionExclusive()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -643,7 +676,9 @@ class EngineConnection extends EventTarget {
|
|||||||
// Sometimes the remote end doesn't report the end of candidates.
|
// Sometimes the remote end doesn't report the end of candidates.
|
||||||
// They have 3 seconds to.
|
// They have 3 seconds to.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
initiateConnectingExclusive()
|
if (that.initiateConnectionExclusive()) {
|
||||||
|
console.warn('connected after 3 second delay')
|
||||||
|
}
|
||||||
}, 3000)
|
}, 3000)
|
||||||
}
|
}
|
||||||
this.pc?.addEventListener?.('icecandidate', this.onIceCandidate)
|
this.pc?.addEventListener?.('icecandidate', this.onIceCandidate)
|
||||||
@ -653,7 +688,7 @@ class EngineConnection extends EventTarget {
|
|||||||
console.log('icegatheringstatechange', this.iceGatheringState)
|
console.log('icegatheringstatechange', this.iceGatheringState)
|
||||||
|
|
||||||
if (this.iceGatheringState !== 'complete') return
|
if (this.iceGatheringState !== 'complete') return
|
||||||
initiateConnectingExclusive()
|
that.initiateConnectionExclusive()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1192,8 +1227,11 @@ class EngineConnection extends EventTarget {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sdpAnswer = answer
|
this.sdpAnswer = toRTCSessionDescriptionInit(answer)
|
||||||
|
|
||||||
|
// We might have received this after ice candidates finish
|
||||||
|
// Make sure we attempt to connect when we do.
|
||||||
|
this.initiateConnectionExclusive()
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'trickle_ice':
|
case 'trickle_ice':
|
||||||
|
Reference in New Issue
Block a user