Skip to content

Commit

Permalink
Initiate connection when we receive SDP and don't connect when null (#…
Browse files Browse the repository at this point in the history
…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 <[email protected]>
  • Loading branch information
iterion and lf94 authored Feb 22, 2025
1 parent f2a6492 commit 9db6900
Showing 1 changed file with 68 additions and 30 deletions.
98 changes: 68 additions & 30 deletions src/lang/std/engineConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,20 @@ export enum EngineConnectionEvents {
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
// for the EngineCommandManager; namely, the underlying WebSocket
// and WebRTC connections.
Expand All @@ -250,7 +264,7 @@ class EngineConnection extends EventTarget {
mediaStream?: MediaStream
idleMode: boolean = false
promise?: Promise<void>
sdpAnswer?: Models['RtcSessionDescription_type']
sdpAnswer?: RTCSessionDescriptionInit
triggeredStart = false

onIceCandidate = function (
Expand Down Expand Up @@ -549,6 +563,50 @@ class EngineConnection extends EventTarget {
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
* establish the WebRTC connections.
Expand Down Expand Up @@ -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) => {
console.log('icecandidate', event.candidate)

// This is null when the ICE gathering state is done.
// Windows ONLY uses this to signal it's done!
if (event.candidate === null) {
initiateConnectingExclusive()
that.initiateConnectionExclusive()
return
}

Expand All @@ -643,7 +676,9 @@ class EngineConnection extends EventTarget {
// Sometimes the remote end doesn't report the end of candidates.
// They have 3 seconds to.
setTimeout(() => {
initiateConnectingExclusive()
if (that.initiateConnectionExclusive()) {
console.warn('connected after 3 second delay')
}
}, 3000)
}
this.pc?.addEventListener?.('icecandidate', this.onIceCandidate)
Expand All @@ -653,7 +688,7 @@ class EngineConnection extends EventTarget {
console.log('icegatheringstatechange', this.iceGatheringState)

if (this.iceGatheringState !== 'complete') return
initiateConnectingExclusive()
that.initiateConnectionExclusive()
}
)

Expand Down Expand Up @@ -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

case 'trickle_ice':
Expand Down

0 comments on commit 9db6900

Please sign in to comment.