@@ -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
@@ -1034,37 +1031,33 @@ export class MatrixCall extends EventEmitter {
10341031 return ;
10351032 }
10361033
1034+ const cands = ev . getContent ( ) . candidates ;
1035+ if ( ! cands ) {
1036+ logger . info ( "Ignoring candidates event with no candidates!" ) ;
1037+ return ;
1038+ }
1039+
1040+ const fromPartyId = ev . getContent ( ) . version === 0 ? null : ev . getContent ( ) . party_id || null ;
1041+
1042+ if ( this . opponentPartyId === undefined ) {
1043+ // we haven't picked an opponent yet so save the candidates
1044+ logger . info ( `Bufferring ${ cands . length } candidates until we pick an opponent` ) ;
1045+ const bufferedCands = this . remoteCandidateBuffer . get ( fromPartyId ) || [ ] ;
1046+ bufferedCands . push ( ...cands ) ;
1047+ this . remoteCandidateBuffer . set ( fromPartyId , bufferedCands ) ;
1048+ return ;
1049+ }
1050+
10371051 if ( ! this . partyIdMatches ( ev . getContent ( ) ) ) {
10381052 logger . info (
10391053 `Ignoring candidates from party ID ${ ev . getContent ( ) . party_id } : ` +
10401054 `we have chosen party ID ${ this . opponentPartyId } ` ,
10411055 ) ;
1042- return ;
1043- }
10441056
1045- const cands = ev . getContent ( ) . candidates ;
1046- if ( ! cands ) {
1047- logger . info ( "Ignoring candidates event with no candidates!" ) ;
10481057 return ;
10491058 }
10501059
1051- for ( const cand of cands ) {
1052- if (
1053- ( cand . sdpMid === null || cand . sdpMid === undefined ) &&
1054- ( cand . sdpMLineIndex === null || cand . sdpMLineIndex === undefined )
1055- ) {
1056- logger . debug ( "Ignoring remote ICE candidate with no sdpMid or sdpMLineIndex" ) ;
1057- return ;
1058- }
1059- logger . debug ( "Got remote ICE " + cand . sdpMid + " candidate: " + cand . candidate ) ;
1060- try {
1061- this . peerConn . addIceCandidate ( cand ) ;
1062- } catch ( err ) {
1063- if ( ! this . ignoreOffer ) {
1064- logger . info ( "Failed to add remore ICE candidate" , err ) ;
1065- }
1066- }
1067- }
1060+ this . addIceCandidates ( cands ) ;
10681061 }
10691062
10701063 /**
@@ -1076,20 +1069,15 @@ export class MatrixCall extends EventEmitter {
10761069 return ;
10771070 }
10781071
1079- if ( this . opponentPartyId !== null ) {
1072+ if ( this . opponentPartyId !== undefined ) {
10801073 logger . info (
10811074 `Ignoring answer from party ID ${ event . getContent ( ) . party_id } : ` +
10821075 `we already have an answer/reject from ${ this . opponentPartyId } ` ,
10831076 ) ;
10841077 return ;
10851078 }
10861079
1087- this . opponentVersion = event . getContent ( ) . version ;
1088- if ( this . opponentVersion !== 0 ) {
1089- this . opponentPartyId = event . getContent ( ) . party_id || null ;
1090- }
1091- this . opponentCaps = event . getContent ( ) . capabilities || { } ;
1092- this . opponentMember = event . sender ;
1080+ this . chooseOpponent ( event ) ;
10931081
10941082 this . setState ( CallState . Connecting ) ;
10951083
@@ -1720,9 +1708,60 @@ export class MatrixCall extends EventEmitter {
17201708
17211709 private partyIdMatches ( msg ) : boolean {
17221710 // They must either match or both be absent (in which case opponentPartyId will be null)
1723- const msgPartyId = msg . party_id || null ;
1711+ // Also we ignore party IDs on the invite/offer if the version is 0, so we must do the same
1712+ // here and use null if the version is 0 (woe betide any opponent sending messages in the
1713+ // same call with different versions)
1714+ const msgPartyId = msg . version === 0 ? null : msg . party_id || null ;
17241715 return msgPartyId === this . opponentPartyId ;
17251716 }
1717+
1718+ // Commits to an opponent for the call
1719+ // ev: An invite or answer event
1720+ private chooseOpponent ( ev : MatrixEvent ) {
1721+ // I choo-choo-choose you
1722+ const msg = ev . getContent ( ) ;
1723+
1724+ this . opponentVersion = msg . version ;
1725+ if ( this . opponentVersion === 0 ) {
1726+ // set to null to indicate that we've chosen an opponent, but because
1727+ // they're v0 they have no party ID (even if they sent one, we're ignoring it)
1728+ this . opponentPartyId = null ;
1729+ } else {
1730+ // set to their party ID, or if they're naughty and didn't send one despite
1731+ // not being v0, set it to null to indicate we picked an opponent with no
1732+ // party ID
1733+ this . opponentPartyId = msg . party_id || null ;
1734+ }
1735+ this . opponentCaps = msg . capabilities || { } ;
1736+ this . opponentMember = ev . sender ;
1737+
1738+ const bufferedCands = this . remoteCandidateBuffer . get ( this . opponentPartyId ) ;
1739+ if ( bufferedCands ) {
1740+ logger . info ( `Adding ${ bufferedCands . length } buffered candidates for opponent ${ this . opponentPartyId } ` ) ;
1741+ this . addIceCandidates ( bufferedCands ) ;
1742+ }
1743+ this . remoteCandidateBuffer = null ;
1744+ }
1745+
1746+ private addIceCandidates ( cands : RTCIceCandidate [ ] ) {
1747+ for ( const cand of cands ) {
1748+ if (
1749+ ( cand . sdpMid === null || cand . sdpMid === undefined ) &&
1750+ ( cand . sdpMLineIndex === null || cand . sdpMLineIndex === undefined )
1751+ ) {
1752+ logger . debug ( "Ignoring remote ICE candidate with no sdpMid or sdpMLineIndex" ) ;
1753+ return ;
1754+ }
1755+ logger . debug ( "Got remote ICE " + cand . sdpMid + " candidate: " + cand . candidate ) ;
1756+ try {
1757+ this . peerConn . addIceCandidate ( cand ) ;
1758+ } catch ( err ) {
1759+ if ( ! this . ignoreOffer ) {
1760+ logger . info ( "Failed to add remore ICE candidate" , err ) ;
1761+ }
1762+ }
1763+ }
1764+ }
17261765}
17271766
17281767function setTracksEnabled ( tracks : Array < MediaStreamTrack > , enabled : boolean ) {
0 commit comments