ep_webrtc 2.1.1 → 2.2.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 +66 -59
package/package.json
CHANGED
package/static/js/index.js
CHANGED
|
@@ -87,7 +87,7 @@ class LocalTracks extends EventTargetPolyfill {
|
|
|
87
87
|
this.videoIsScreenshare = false;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
getTracks(kind) {
|
|
91
91
|
return kind === 'audio' ? this.stream.getAudioTracks()
|
|
92
92
|
: kind === 'video' ? this.stream.getVideoTracks()
|
|
93
93
|
: this.stream.getTracks();
|
|
@@ -96,7 +96,7 @@ class LocalTracks extends EventTargetPolyfill {
|
|
|
96
96
|
setTrack(kind, newTrack) {
|
|
97
97
|
newTrack = newTrack || null; // Convert undefined to null.
|
|
98
98
|
let oldTrack = null;
|
|
99
|
-
const tracks = this.
|
|
99
|
+
const tracks = this.getTracks(kind);
|
|
100
100
|
for (const track of tracks) {
|
|
101
101
|
if (track.kind !== kind) continue;
|
|
102
102
|
if (track === newTrack) return; // No change.
|
|
@@ -109,7 +109,7 @@ class LocalTracks extends EventTargetPolyfill {
|
|
|
109
109
|
debug(`adding ${kind} track ${newTrack.id} to local stream`);
|
|
110
110
|
newTrack.addEventListener('ended', () => {
|
|
111
111
|
debug(`local ${kind} track ${newTrack.id} ended`);
|
|
112
|
-
if (!this.
|
|
112
|
+
if (!this.getTracks(kind).includes(newTrack)) return;
|
|
113
113
|
this.setTrack(kind, null);
|
|
114
114
|
});
|
|
115
115
|
this.stream.addTrack(newTrack);
|
|
@@ -163,45 +163,42 @@ class PeerState extends EventTargetPolyfill {
|
|
|
163
163
|
};
|
|
164
164
|
this._closed = false;
|
|
165
165
|
this._pc = null;
|
|
166
|
+
this._xcvrs = {};
|
|
166
167
|
this._remoteStream = null;
|
|
167
|
-
this._onremovetrack =
|
|
168
|
-
() => { if (this._remoteStream.getTracks().length === 0) this._setRemoteStream(null); };
|
|
169
168
|
this._ontrackchanged = async ({oldTrack, newTrack}) => {
|
|
170
|
-
|
|
171
|
-
this.
|
|
172
|
-
`${oldTrack ? oldTrack.id : '(null)'} with ` +
|
|
173
|
-
`${newTrack ? newTrack.id : '(null)'}`);
|
|
174
|
-
if (oldTrack != null) {
|
|
175
|
-
for (const sender of this._pc.getSenders()) {
|
|
176
|
-
if (sender.track !== oldTrack) continue;
|
|
177
|
-
if (newTrack != null) {
|
|
178
|
-
try {
|
|
179
|
-
return await sender.replaceTrack(newTrack);
|
|
180
|
-
} catch (err) {
|
|
181
|
-
this._debug('renegotiation is required');
|
|
182
|
-
logErrorToServer(err);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
this._pc.removeTrack(sender);
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
if (newTrack != null) this._pc.addTrack(newTrack, this._localTracks.stream);
|
|
169
|
+
const {kind} = oldTrack || newTrack;
|
|
170
|
+
await this._sendLocalTrack(kind, newTrack);
|
|
190
171
|
};
|
|
191
172
|
this._localTracks.addEventListener('trackchanged', this._ontrackchanged);
|
|
192
173
|
}
|
|
193
174
|
|
|
175
|
+
async _sendLocalTrack(kind, track) {
|
|
176
|
+
if (this._pc == null || this._pc.connectionState === 'closed') return;
|
|
177
|
+
const {sender: {track: old} = {}} = this._xcvrs[kind] || {};
|
|
178
|
+
if ((old == null && track == null) || old === track) return;
|
|
179
|
+
this._debug(
|
|
180
|
+
`replacing ${kind} track ${old ? old.id : '(null)'} with ${track ? track.id : '(null)'}`);
|
|
181
|
+
const xcvr = this._xcvrs[kind];
|
|
182
|
+
if (!xcvr) return;
|
|
183
|
+
try {
|
|
184
|
+
if (track != null) xcvr.direction = 'sendrecv'; // Will throw if not already bidirectional.
|
|
185
|
+
await xcvr.sender.replaceTrack(track);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
this._debug('renegotiation is required');
|
|
188
|
+
this._resetConnection(err);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
194
193
|
_setRemoteStream(stream) {
|
|
195
194
|
if (stream === this._remoteStream) return;
|
|
196
195
|
if (this._remoteStream != null) {
|
|
197
196
|
const oldStream = this._remoteStream;
|
|
198
|
-
oldStream.removeEventListener('removetrack', this._onremovetrack);
|
|
199
197
|
this._remoteStream = null;
|
|
200
198
|
this.dispatchEvent(new StreamEvent('streamgone', oldStream));
|
|
201
199
|
}
|
|
202
200
|
if (stream != null) {
|
|
203
201
|
this._remoteStream = stream;
|
|
204
|
-
stream.addEventListener('removetrack', this._onremovetrack);
|
|
205
202
|
this.dispatchEvent(new StreamEvent('stream', stream));
|
|
206
203
|
}
|
|
207
204
|
}
|
|
@@ -221,20 +218,19 @@ class PeerState extends EventTargetPolyfill {
|
|
|
221
218
|
this._ids.from.instance = ++nextInstanceId;
|
|
222
219
|
this._ids.to = peerIds;
|
|
223
220
|
const pc = new RTCPeerConnection(this._pcConfig);
|
|
224
|
-
pc.addEventListener('track', ({track,
|
|
225
|
-
|
|
226
|
-
this.
|
|
221
|
+
pc.addEventListener('track', async ({track, transceiver}) => {
|
|
222
|
+
this._debug(`Received ${track.kind} track from peer, ID: ${track.id}`);
|
|
223
|
+
const stream = this._remoteStream || new MediaStream();
|
|
224
|
+
stream.addTrack(track);
|
|
225
|
+
this._setRemoteStream(stream);
|
|
226
|
+
if (!this._caller) {
|
|
227
|
+
this._xcvrs[track.kind] = transceiver;
|
|
228
|
+
// _sendLocalTrack might call _resetConnection, so it should be called last.
|
|
229
|
+
await this._sendLocalTrack(track.kind, this._localTracks.getTracks(track.kind)[0]);
|
|
230
|
+
}
|
|
227
231
|
});
|
|
228
232
|
pc.addEventListener('icecandidate', ({candidate}) => this._sendMessage({candidate}));
|
|
229
233
|
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, so the peer might not know that it should call now. Send another
|
|
234
|
-
// invite just in case. The peer will ignore any superfluous invites.
|
|
235
|
-
this._sendMessage({invite: 'invite'});
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
234
|
try {
|
|
239
235
|
await pc.setLocalDescription();
|
|
240
236
|
this._failedSLDAttempts = 0;
|
|
@@ -291,11 +287,32 @@ class PeerState extends EventTargetPolyfill {
|
|
|
291
287
|
|
|
292
288
|
if (this._pc != null) this._pc.close();
|
|
293
289
|
this._pc = pc;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
290
|
+
this._xcvrs = {};
|
|
291
|
+
|
|
292
|
+
if (this._caller) {
|
|
293
|
+
// Adding transceivers triggers negotiation with the peer, which we want to do as soon as
|
|
294
|
+
// possible to warm up the peer connection (ICE takes a while). Adding a track implicitly adds
|
|
295
|
+
// a transceiver so we could do that instead, but:
|
|
296
|
+
//
|
|
297
|
+
// * We might not have a local track yet (maybe the user hasn't yet allowed access to the
|
|
298
|
+
// camera/mic).
|
|
299
|
+
// * Using a dummy silence track until a real track is ready might result in two
|
|
300
|
+
// unidirectional SDP m-lines (one in each direction) instead of a single bidirectional
|
|
301
|
+
// m-line. This seems to trigger an echo bug in Safari.
|
|
302
|
+
//
|
|
303
|
+
// Using transceivers to warm up the connection is the approach taken in:
|
|
304
|
+
// https://www.w3.org/TR/2021/REC-webrtc-20210126/#advanced-peer-to-peer-example-with-warm-up
|
|
305
|
+
// Also see: https://webrtcstandards.info/ice-warm-up-m-line-webrtc-transceivers/
|
|
306
|
+
Promise.all(['audio', 'video'].map(async (t) => {
|
|
307
|
+
this._debug(`adding ${t} transceiver`);
|
|
308
|
+
this._xcvrs[t] = this._pc.addTransceiver(t);
|
|
309
|
+
await this._sendLocalTrack(t, this._localTracks.getTracks(t)[0]);
|
|
310
|
+
}));
|
|
311
|
+
} else {
|
|
312
|
+
// It is possible that the last invite sent to the peer was sent before the peer was ready to
|
|
313
|
+
// accept invites, so the peer might not know that it should call now. Send another invite
|
|
314
|
+
// just in case. The peer will ignore any superfluous invites.
|
|
315
|
+
this._sendMessage({invite: 'invite'});
|
|
299
316
|
}
|
|
300
317
|
}
|
|
301
318
|
|
|
@@ -385,17 +402,7 @@ exports.rtc = new class {
|
|
|
385
402
|
constructor() {
|
|
386
403
|
this._activated = null;
|
|
387
404
|
this._settings = null;
|
|
388
|
-
this._disabledSilence = (() => {
|
|
389
|
-
const ctx = new AudioContext();
|
|
390
|
-
const gain = ctx.createGain();
|
|
391
|
-
const dst = gain.connect(ctx.createMediaStreamDestination());
|
|
392
|
-
const track = dst.stream.getAudioTracks()[0];
|
|
393
|
-
track.enabled = false;
|
|
394
|
-
return track;
|
|
395
|
-
})();
|
|
396
|
-
this._blankStream = new MediaStream([this._disabledSilence]);
|
|
397
405
|
this._localTracks = new LocalTracks();
|
|
398
|
-
this._localTracks.setTrack(this._disabledSilence.kind, this._disabledSilence);
|
|
399
406
|
this._localTracks.addEventListener('trackchanged', ({oldTrack, newTrack}) => {
|
|
400
407
|
// Normally the self-view UI only needs to be updated if the user clicks on something, but it
|
|
401
408
|
// also needs to be updated if the browser decides to end the local stream for whatever
|
|
@@ -620,8 +627,7 @@ exports.rtc = new class {
|
|
|
620
627
|
try {
|
|
621
628
|
const devices = [];
|
|
622
629
|
const addAudioTrack = updateAudio && this._selfViewButtons.audio.enabled &&
|
|
623
|
-
!this._localTracks.stream.getAudioTracks().some(
|
|
624
|
-
(t) => t !== this._disabledSilence && t.readyState === 'live');
|
|
630
|
+
!this._localTracks.stream.getAudioTracks().some((t) => t.readyState === 'live');
|
|
625
631
|
if (addAudioTrack) devices.push('mic');
|
|
626
632
|
const addVideoTrack = updateVideo && this._selfViewButtons.video.enabled &&
|
|
627
633
|
(!this._localTracks.stream.getVideoTracks().some((t) => t.readyState === 'live') ||
|
|
@@ -674,7 +680,7 @@ exports.rtc = new class {
|
|
|
674
680
|
for (const track of this._localTracks.stream.getAudioTracks()) {
|
|
675
681
|
// Re-check the state of the button because the user might have clicked it while
|
|
676
682
|
// getUserMedia() was running.
|
|
677
|
-
track.enabled =
|
|
683
|
+
track.enabled = this._selfViewButtons.audio.enabled;
|
|
678
684
|
}
|
|
679
685
|
const hasAudio = this._localTracks.stream.getAudioTracks().some(
|
|
680
686
|
(t) => t.enabled && t.readyState === 'live');
|
|
@@ -880,8 +886,9 @@ exports.rtc = new class {
|
|
|
880
886
|
minWidth: iw > width ? `${iw}px` : '',
|
|
881
887
|
minHeight: nh + ih > height ? `${nh + ih}px` : '',
|
|
882
888
|
});
|
|
883
|
-
})
|
|
884
|
-
|
|
889
|
+
});
|
|
890
|
+
if (isLocal) $videoContainer.prependTo($('#rtcbox'));
|
|
891
|
+
else $videoContainer.appendTo($('#rtcbox'));
|
|
885
892
|
this.updatePeerNameAndColor(this.getUserFromId(userId));
|
|
886
893
|
|
|
887
894
|
// For tests it is important to know when an asynchronous event handler has finishing handling
|
|
@@ -1278,7 +1285,7 @@ exports.rtc = new class {
|
|
|
1278
1285
|
logDisconnectErrorTimeout =
|
|
1279
1286
|
setTimeout(() => logErrorToServer(new Error('RTC connection lost'), 0), 10000);
|
|
1280
1287
|
($videoContainer.data('updateMinSize') || (() => {}))();
|
|
1281
|
-
await this.setStream(userId,
|
|
1288
|
+
await this.setStream(userId, new MediaStream());
|
|
1282
1289
|
});
|
|
1283
1290
|
peer.addEventListener('closed', () => {
|
|
1284
1291
|
_debug('PeerState closed');
|