ep_webrtc 2.0.4 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/static/js/index.js +26 -62
package/package.json
CHANGED
package/static/js/index.js
CHANGED
|
@@ -135,10 +135,6 @@ class ClosedEvent extends CustomEvent {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
// The WebRTC negotiation logic used here is based on the "Perfect Negotiation Example" at
|
|
139
|
-
// https://www.w3.org/TR/2021/REC-webrtc-20210126/#perfect-negotiation-example. See there for
|
|
140
|
-
// details about how it works.
|
|
141
|
-
//
|
|
142
138
|
// Events:
|
|
143
139
|
// * 'stream' (see StreamEvent): Emitted when the remote stream is ready. For every 'stream' event
|
|
144
140
|
// there will be a corresponding 'streamgone' event. Once a stream is added another stream will
|
|
@@ -148,15 +144,15 @@ class ClosedEvent extends CustomEvent {
|
|
|
148
144
|
// * 'closed' (see ClosedEvent): Emitted when the PeerState is closed, except when closed by a
|
|
149
145
|
// call to close(). The PeerState must not be used after it is closed.
|
|
150
146
|
class PeerState extends EventTargetPolyfill {
|
|
151
|
-
constructor(pcConfig,
|
|
147
|
+
constructor(pcConfig, caller, sendMessage, localTracks, debug) {
|
|
152
148
|
super();
|
|
153
149
|
this._pcConfig = pcConfig;
|
|
154
|
-
this.
|
|
150
|
+
this._caller = caller;
|
|
155
151
|
this._sendMessage = (msg) => sendMessage(Object.assign({ids: this._ids}, msg));
|
|
156
152
|
this._localTracks = localTracks;
|
|
157
153
|
this._failedSLDAttempts = 0;
|
|
158
154
|
this._debug = debug;
|
|
159
|
-
this._debug(`I am the ${this.
|
|
155
|
+
this._debug(`I am the ${this._caller ? 'calling' : 'answering'} peer`);
|
|
160
156
|
this._ids = {
|
|
161
157
|
from: {
|
|
162
158
|
// Only changes when the user reloads the page.
|
|
@@ -224,15 +220,6 @@ class PeerState extends EventTargetPolyfill {
|
|
|
224
220
|
this._setRemoteStream(null);
|
|
225
221
|
this._ids.from.instance = ++nextInstanceId;
|
|
226
222
|
this._ids.to = peerIds;
|
|
227
|
-
// This negotiation state is captured in closures (instead of doing something like
|
|
228
|
-
// this._negotiationState) because this._resetConnection() needs to be sure that all of the
|
|
229
|
-
// event handlers for the old RTCPeerConnection do not mutate the negotiation state for the new
|
|
230
|
-
// RTCPeerConnection.
|
|
231
|
-
const negotiationState = {
|
|
232
|
-
makingOffer: false,
|
|
233
|
-
ignoreOffer: false,
|
|
234
|
-
isSettingRemoteAnswerPending: false,
|
|
235
|
-
};
|
|
236
223
|
const pc = new RTCPeerConnection(this._pcConfig);
|
|
237
224
|
pc.addEventListener('track', ({track, streams}) => {
|
|
238
225
|
if (streams.length !== 1) throw new Error('Expected track to be in exactly one stream');
|
|
@@ -240,8 +227,16 @@ class PeerState extends EventTargetPolyfill {
|
|
|
240
227
|
});
|
|
241
228
|
pc.addEventListener('icecandidate', ({candidate}) => this._sendMessage({candidate}));
|
|
242
229
|
pc.addEventListener('negotiationneeded', async () => {
|
|
230
|
+
if (!this._caller) {
|
|
231
|
+
this._debug('Waiting for peer to call');
|
|
232
|
+
// It is possible that the last invite sent to the peer was sent before the peer was ready
|
|
233
|
+
// to accept invites. If that is the case and there are no local tracks to trigger a WebRTC
|
|
234
|
+
// message exchange, the peer won't know that it is OK to connect back. Send another invite
|
|
235
|
+
// just in case. The peer will ignore any superfluous invites.
|
|
236
|
+
this._sendMessage({invite: 'invite'});
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
243
239
|
try {
|
|
244
|
-
negotiationState.makingOffer = true;
|
|
245
240
|
await pc.setLocalDescription();
|
|
246
241
|
this._failedSLDAttempts = 0;
|
|
247
242
|
this._sendMessage({description: pc.localDescription});
|
|
@@ -250,8 +245,6 @@ class PeerState extends EventTargetPolyfill {
|
|
|
250
245
|
if (++this._failedSLDAttempts > 10) throw err; // Avoid an infinite loop.
|
|
251
246
|
this._resetConnection(err);
|
|
252
247
|
return;
|
|
253
|
-
} finally {
|
|
254
|
-
negotiationState.makingOffer = false;
|
|
255
248
|
}
|
|
256
249
|
});
|
|
257
250
|
pc.addEventListener('connectionstatechange', () => {
|
|
@@ -299,46 +292,12 @@ class PeerState extends EventTargetPolyfill {
|
|
|
299
292
|
|
|
300
293
|
if (this._pc != null) this._pc.close();
|
|
301
294
|
this._pc = pc;
|
|
302
|
-
this._setRemoteDescription = async (description) => {
|
|
303
|
-
const readyForOffer = !negotiationState.makingOffer &&
|
|
304
|
-
(pc.signalingState === 'stable' || negotiationState.isSettingRemoteAnswerPending);
|
|
305
|
-
const offerCollision = description.type === 'offer' && !readyForOffer;
|
|
306
|
-
negotiationState.ignoreOffer = !this._polite && offerCollision;
|
|
307
|
-
if (negotiationState.ignoreOffer) {
|
|
308
|
-
this._debug('ignoring offer due to offer collision');
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
negotiationState.isSettingRemoteAnswerPending = description.type === 'answer';
|
|
312
|
-
await pc.setRemoteDescription(description);
|
|
313
|
-
// The "Perfect Negotiation Example" doesn't put this next line inside a `finally` block. It
|
|
314
|
-
// is unclear whether that is intentional. Fortunately it doesn't matter here: If the above
|
|
315
|
-
// pc.setRemoteDescription() call throws, _resetConnection() is called to restart the
|
|
316
|
-
// negotiation anyway.
|
|
317
|
-
negotiationState.isSettingRemoteAnswerPending = false;
|
|
318
|
-
if (description.type === 'offer') {
|
|
319
|
-
await pc.setLocalDescription();
|
|
320
|
-
this._sendMessage({description: pc.localDescription});
|
|
321
|
-
}
|
|
322
|
-
};
|
|
323
|
-
this._addIceCandidate = async (candidate) => {
|
|
324
|
-
try {
|
|
325
|
-
await pc.addIceCandidate(candidate);
|
|
326
|
-
} catch (err) {
|
|
327
|
-
if (!negotiationState.ignoreOffer) throw err;
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
295
|
|
|
331
296
|
const tracks = this._localTracks.stream.getTracks();
|
|
332
297
|
for (const track of tracks) {
|
|
333
298
|
this._debug(`start streaming ${track.kind} track ${track.id}`);
|
|
334
299
|
pc.addTrack(track, this._localTracks.stream);
|
|
335
300
|
}
|
|
336
|
-
// Creating an RTCPeerConnection doesn't actually generate any control messages until
|
|
337
|
-
// RTCPeerConnection.addTrack() is called. It is possible that the last invite sent to the peer
|
|
338
|
-
// was sent before the peer was ready to accept invites. If that is the case and there are no
|
|
339
|
-
// local tracks to trigger a WebRTC message exchange, the peer won't know that it is OK to
|
|
340
|
-
// connect back. Send another invite just in case. The peer will ignore any superfluous invites.
|
|
341
|
-
if (tracks.length === 0) this._sendMessage({invite: 'invite'});
|
|
342
301
|
}
|
|
343
302
|
|
|
344
303
|
async receiveMessage(msg) {
|
|
@@ -374,8 +333,14 @@ class PeerState extends EventTargetPolyfill {
|
|
|
374
333
|
}
|
|
375
334
|
if (this._pc == null) this._resetConnection(null, this._ids.to);
|
|
376
335
|
try {
|
|
377
|
-
if (description != null)
|
|
378
|
-
|
|
336
|
+
if (description != null) {
|
|
337
|
+
await this._pc.setRemoteDescription(description);
|
|
338
|
+
if (description.type === 'offer') {
|
|
339
|
+
await this._pc.setLocalDescription();
|
|
340
|
+
this._sendMessage({description: this._pc.localDescription});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (candidate != null) await this._pc.addIceCandidate(candidate);
|
|
379
344
|
} catch (err) {
|
|
380
345
|
err.peerMessage = msg;
|
|
381
346
|
console.error('Error processing message from peer:', err);
|
|
@@ -396,14 +361,13 @@ class PeerState extends EventTargetPolyfill {
|
|
|
396
361
|
}
|
|
397
362
|
}
|
|
398
363
|
|
|
399
|
-
const
|
|
400
|
-
// Compare user IDs to determine which of the two users
|
|
401
|
-
const
|
|
402
|
-
if ((otherId.localeCompare(myId) < 0) ===
|
|
403
|
-
// One peer must be polite and the other must be impolite.
|
|
364
|
+
const isCaller = (myId, otherId) => {
|
|
365
|
+
// Compare user IDs to determine which of the two users will initiate the call.
|
|
366
|
+
const caller = myId.localeCompare(otherId) < 0;
|
|
367
|
+
if ((otherId.localeCompare(myId) < 0) === caller) {
|
|
404
368
|
throw new Error(`Peer ID ${otherId} compares equivalent to own ID ${myId}`);
|
|
405
369
|
}
|
|
406
|
-
return
|
|
370
|
+
return caller;
|
|
407
371
|
};
|
|
408
372
|
|
|
409
373
|
// Periods in element IDs make it hard to build a selector string because period is for class match.
|
|
@@ -1292,7 +1256,7 @@ exports.rtc = new class {
|
|
|
1292
1256
|
_debug('creating PeerState');
|
|
1293
1257
|
peer = new PeerState(
|
|
1294
1258
|
{iceServers: this._settings.iceServers},
|
|
1295
|
-
|
|
1259
|
+
isCaller(this.getUserId(), userId),
|
|
1296
1260
|
(msg) => this.sendMessage(userId, msg),
|
|
1297
1261
|
this._localTracks,
|
|
1298
1262
|
_debug);
|