@@ -251,8 +251,6 @@ export class MatrixCall extends EventEmitter {
251251 private localAVStream : MediaStream ;
252252 private inviteOrAnswerSent : boolean ;
253253 private waitForLocalAVStream : boolean ;
254- // XXX: This is either the invite or answer from remote...
255- private msg : any ;
256254 // XXX: I don't know why this is called 'config'.
257255 private config : MediaStreamConstraints ;
258256 private successor : MatrixCall ;
@@ -284,16 +282,18 @@ export class MatrixCall extends EventEmitter {
284282 private makingOffer : boolean ;
285283 private ignoreOffer : boolean ;
286284
285+ // If candidates arrive before we've picked an opponent (which, in particular,
286+ // will happen if the opponent sends candidates eagerly before the user answers
287+ // the call) we buffer them up here so we can then add the ones from the party we pick
288+ private remoteCandidateBuffer = new Map < string , RTCIceCandidate [ ] > ( ) ;
289+
287290 constructor ( opts : CallOpts ) {
288291 super ( ) ;
289292 this . roomId = opts . roomId ;
290293 this . client = opts . client ;
291294 this . type = null ;
292295 this . forceTURN = opts . forceTURN ;
293296 this . ourPartyId = this . client . deviceId ;
294- // We compare this to null to checks the presence of a party ID:
295- // make sure it's null, not undefined
296- this . opponentPartyId = null ;
297297 // Array of Objects with urls, username, credential keys
298298 this . turnServers = opts . turnServers || [ ] ;
299299 if ( this . turnServers . length === 0 && this . client . isFallbackICEServerAllowed ( ) ) {
@@ -541,11 +541,16 @@ export class MatrixCall extends EventEmitter {
541541 * @param {MatrixEvent } event The m.call.invite event
542542 */
543543 async initWithInvite ( event : MatrixEvent ) {
544- this . msg = event . getContent ( ) ;
544+ const invite = event . getContent ( ) ;
545545 this . direction = CallDirection . Inbound ;
546+
546547 this . peerConn = this . createPeerConnection ( ) ;
548+ // we must set the party ID before await-ing on anything: the call event
549+ // handler will start giving us more call events (eg. candidates) so if
550+ // we haven't set the party ID, we'll ignore them.
551+ this . chooseOpponent ( event ) ;
547552 try {
548- await this . peerConn . setRemoteDescription ( this . msg . offer ) ;
553+ await this . peerConn . setRemoteDescription ( invite . offer ) ;
549554 } catch ( e ) {
550555 logger . debug ( "Failed to set remote description" , e ) ;
551556 this . terminate ( CallParty . Local , CallErrorCode . SetRemoteDescription , false ) ;
@@ -564,13 +569,6 @@ export class MatrixCall extends EventEmitter {
564569 this . type = this . remoteStream . getTracks ( ) . some ( t => t . kind === 'video' ) ? CallType . Video : CallType . Voice ;
565570
566571 this . setState ( CallState . Ringing ) ;
567- this . opponentVersion = this . msg . version ;
568- if ( this . opponentVersion !== 0 ) {
569- // ignore party ID in v0 calls: party ID isn't a thing until v1
570- this . opponentPartyId = this . msg . party_id || null ;
571- }
572- this . opponentCaps = this . msg . capabilities || { } ;
573- this . opponentMember = event . sender ;
574572
575573 if ( event . getLocalAge ( ) ) {
576574 setTimeout ( ( ) => {
@@ -584,7 +582,7 @@ export class MatrixCall extends EventEmitter {
584582 }
585583 this . emit ( CallEvent . Hangup ) ;
586584 }
587- } , this . msg . lifetime - event . getLocalAge ( ) ) ;
585+ } , invite . lifetime - event . getLocalAge ( ) ) ;
588586 }
589587 }
590588
@@ -596,7 +594,6 @@ export class MatrixCall extends EventEmitter {
596594 // perverse as it may seem, sometimes we want to instantiate a call with a
597595 // hangup message (because when getting the state of the room on load, events
598596 // come in reverse order and we want to remember that a call has been hung up)
599- this . msg = event . getContent ( ) ;
600597 this . setState ( CallState . Ended ) ;
601598 }
602599
@@ -1030,37 +1027,33 @@ export class MatrixCall extends EventEmitter {
10301027 return ;
10311028 }
10321029
1030+ const cands = ev . getContent ( ) . candidates ;
1031+ if ( ! cands ) {
1032+ logger . info ( "Ignoring candidates event with no candidates!" ) ;
1033+ return ;
1034+ }
1035+
1036+ const fromPartyId = ev . getContent ( ) . version === 0 ? null : ev . getContent ( ) . party_id || null ;
1037+
1038+ if ( this . opponentPartyId === undefined ) {
1039+ // we haven't picked an opponent yet so save the candidates
1040+ logger . info ( `Bufferring ${ cands . length } candidates until we pick an opponent` ) ;
1041+ const bufferedCands = this . remoteCandidateBuffer . get ( fromPartyId ) || [ ] ;
1042+ bufferedCands . push ( ...cands ) ;
1043+ this . remoteCandidateBuffer . set ( fromPartyId , bufferedCands ) ;
1044+ return ;
1045+ }
1046+
10331047 if ( ! this . partyIdMatches ( ev . getContent ( ) ) ) {
10341048 logger . info (
10351049 `Ignoring candidates from party ID ${ ev . getContent ( ) . party_id } : ` +
10361050 `we have chosen party ID ${ this . opponentPartyId } ` ,
10371051 ) ;
1038- return ;
1039- }
10401052
1041- const cands = ev . getContent ( ) . candidates ;
1042- if ( ! cands ) {
1043- logger . info ( "Ignoring candidates event with no candidates!" ) ;
10441053 return ;
10451054 }
10461055
1047- for ( const cand of cands ) {
1048- if (
1049- ( cand . sdpMid === null || cand . sdpMid === undefined ) &&
1050- ( cand . sdpMLineIndex === null || cand . sdpMLineIndex === undefined )
1051- ) {
1052- logger . debug ( "Ignoring remote ICE candidate with no sdpMid or sdpMLineIndex" ) ;
1053- return ;
1054- }
1055- logger . debug ( "Got remote ICE " + cand . sdpMid + " candidate: " + cand . candidate ) ;
1056- try {
1057- this . peerConn . addIceCandidate ( cand ) ;
1058- } catch ( err ) {
1059- if ( ! this . ignoreOffer ) {
1060- logger . info ( "Failed to add remore ICE candidate" , err ) ;
1061- }
1062- }
1063- }
1056+ this . addIceCandidates ( cands ) ;
10641057 }
10651058
10661059 /**
@@ -1072,20 +1065,15 @@ export class MatrixCall extends EventEmitter {
10721065 return ;
10731066 }
10741067
1075- if ( this . opponentPartyId !== null ) {
1068+ if ( this . opponentPartyId !== undefined ) {
10761069 logger . info (
10771070 `Ignoring answer from party ID ${ event . getContent ( ) . party_id } : ` +
10781071 `we already have an answer/reject from ${ this . opponentPartyId } ` ,
10791072 ) ;
10801073 return ;
10811074 }
10821075
1083- this . opponentVersion = event . getContent ( ) . version ;
1084- if ( this . opponentVersion !== 0 ) {
1085- this . opponentPartyId = event . getContent ( ) . party_id || null ;
1086- }
1087- this . opponentCaps = event . getContent ( ) . capabilities || { } ;
1088- this . opponentMember = event . sender ;
1076+ this . chooseOpponent ( event ) ;
10891077
10901078 this . setState ( CallState . Connecting ) ;
10911079
@@ -1702,9 +1690,60 @@ export class MatrixCall extends EventEmitter {
17021690
17031691 private partyIdMatches ( msg ) : boolean {
17041692 // They must either match or both be absent (in which case opponentPartyId will be null)
1705- const msgPartyId = msg . party_id || null ;
1693+ // Also we ignore party IDs on the invite/offer if the version is 0, so we must do the same
1694+ // here and use null if the version is 0 (woe betide any opponent sending messages in the
1695+ // same call with different versions)
1696+ const msgPartyId = msg . version === 0 ? null : msg . party_id || null ;
17061697 return msgPartyId === this . opponentPartyId ;
17071698 }
1699+
1700+ // Commits to an opponent for the call
1701+ // ev: An invite or answer event
1702+ private chooseOpponent ( ev : MatrixEvent ) {
1703+ // I choo-choo-choose you
1704+ const msg = ev . getContent ( ) ;
1705+
1706+ this . opponentVersion = msg . version ;
1707+ if ( this . opponentVersion === 0 ) {
1708+ // set to null to indicate that we've chosen an opponent, but because
1709+ // they're v0 they have no party ID (even if they sent one, we're ignoring it)
1710+ this . opponentPartyId = null ;
1711+ } else {
1712+ // set to their party ID, or if they're naughty and didn't send one despite
1713+ // not being v0, set it to null to indicate we picked an opponent with no
1714+ // party ID
1715+ this . opponentPartyId = msg . party_id || null ;
1716+ }
1717+ this . opponentCaps = msg . capabilities || { } ;
1718+ this . opponentMember = ev . sender ;
1719+
1720+ const bufferedCands = this . remoteCandidateBuffer . get ( this . opponentPartyId ) ;
1721+ if ( bufferedCands ) {
1722+ logger . info ( `Adding ${ bufferedCands . length } buffered candidates for opponent ${ this . opponentPartyId } ` ) ;
1723+ this . addIceCandidates ( bufferedCands ) ;
1724+ }
1725+ this . remoteCandidateBuffer = null ;
1726+ }
1727+
1728+ private addIceCandidates ( cands : RTCIceCandidate [ ] ) {
1729+ for ( const cand of cands ) {
1730+ if (
1731+ ( cand . sdpMid === null || cand . sdpMid === undefined ) &&
1732+ ( cand . sdpMLineIndex === null || cand . sdpMLineIndex === undefined )
1733+ ) {
1734+ logger . debug ( "Ignoring remote ICE candidate with no sdpMid or sdpMLineIndex" ) ;
1735+ return ;
1736+ }
1737+ logger . debug ( "Got remote ICE " + cand . sdpMid + " candidate: " + cand . candidate ) ;
1738+ try {
1739+ this . peerConn . addIceCandidate ( cand ) ;
1740+ } catch ( err ) {
1741+ if ( ! this . ignoreOffer ) {
1742+ logger . info ( "Failed to add remore ICE candidate" , err ) ;
1743+ }
1744+ }
1745+ }
1746+ }
17081747}
17091748
17101749function setTracksEnabled ( tracks : Array < MediaStreamTrack > , enabled : boolean ) {
0 commit comments