浏览代码

edge: add initial ORTC shim for Edge support

dev1
Iñaki Baz Castillo 8 年前
父节点
当前提交
8620317f30
共有 5 个文件被更改,包括 883 次插入10 次删除
  1. 8
    8
      modules/RTC/RTCUtils.js
  2. 772
    0
      modules/RTC/ortc/RTCPeerConnection.js
  3. 79
    0
      modules/RTC/ortc/RTCSessionDescription.js
  4. 21
    0
      modules/RTC/ortc/errors.js
  5. 3
    2
      package.json

+ 8
- 8
modules/RTC/RTCUtils.js 查看文件

@@ -22,12 +22,16 @@ import * as MediaType from '../../service/RTC/MediaType';
22 22
 import Resolutions from '../../service/RTC/Resolutions';
23 23
 import RTCBrowserType from './RTCBrowserType';
24 24
 import RTCEvents from '../../service/RTC/RTCEvents';
25
+import ortcRTCPeerConnection from './ortc/RTCPeerConnection';
25 26
 import screenObtainer from './ScreenObtainer';
26 27
 import SDPUtil from '../xmpp/SDPUtil';
27 28
 import VideoType from '../../service/RTC/VideoType';
28 29
 
29 30
 const logger = getLogger(__filename);
30 31
 
32
+// Disable Edge until fully implemented.
33
+const ENABLE_EDGE = false;
34
+
31 35
 // XXX Don't require Temasys unless it's to be used because it doesn't run on
32 36
 // React Native, for example.
33 37
 const AdapterJS
@@ -864,19 +868,15 @@ class RTCUtils extends Listenable {
864 868
                     };
865 869
                 }
866 870
             } else if (RTCBrowserType.isEdge()) {
867
-                // TODO: Remove when EDGE is fully supported. For now ensure
868
-                // that, if EDGE is detected, it's just unsupported.
869
-                if (RTCBrowserType.isEdge()) {
871
+                // Disable until fully implemented.
872
+                if (!ENABLE_EDGE) {
870 873
                     rejectWithWebRTCNotSupported(
871
-                        'Microsoft EDGE not yet supported', reject);
874
+                        'Microsoft Edge not yet supported', reject);
872 875
 
873 876
                     return;
874 877
                 }
875 878
 
876
-                // TODO: Uncomment when done. For now use the Edge native
877
-                // RTCPeerConnection.
878
-                // this.RTCPeerConnectionType = ortcRTCPeerConnection;
879
-                this.RTCPeerConnectionType = RTCPeerConnection;
879
+                this.RTCPeerConnectionType = ortcRTCPeerConnection;
880 880
                 this.getUserMedia
881 881
                     = wrapGetUserMedia(
882 882
                         navigator.mediaDevices.getUserMedia.bind(

+ 772
- 0
modules/RTC/ortc/RTCPeerConnection.js 查看文件

@@ -0,0 +1,772 @@
1
+/* global __filename, RTCIceGatherer, RTCIceTransport */
2
+
3
+import { getLogger } from 'jitsi-meet-logger';
4
+import yaeti from 'yaeti';
5
+
6
+import { InvalidStateError } from './errors';
7
+
8
+const logger = getLogger(__filename);
9
+
10
+const RTCSignalingState = {
11
+    stable: 'stable',
12
+    haveLocalOffer: 'have-local-offer',
13
+    haveRemoteOffer: 'have-remote-offer',
14
+    closed: 'closed'
15
+};
16
+
17
+const RTCIceGatheringState = {
18
+    new: 'new',
19
+    gathering: 'gathering',
20
+    complete: 'complete'
21
+};
22
+
23
+/**
24
+ * RTCPeerConnection shim for ORTC based endpoints (such as Edge).
25
+ *
26
+ * The interface is based on the W3C specification of 2015, which matches
27
+ * the implementation of Chrome nowadays:
28
+ *
29
+ *   https://www.w3.org/TR/2015/WD-webrtc-20150210/
30
+ */
31
+export default class ortcRTCPeerConnection extends yaeti.EventTarget {
32
+    /**
33
+     */
34
+    constructor(pcConfig) {
35
+        super();
36
+
37
+        logger.debug('constructor() pcConfig:', pcConfig);
38
+
39
+        // Closed flag.
40
+        // @type {boolean}
41
+        this._closed = false;
42
+
43
+        // Create a RTCIceGatherer.
44
+        // @type {RTCIceGatherer}
45
+        this._iceGatherer = this._createIceGatherer(pcConfig);
46
+
47
+        // RTCPeerConnection iceGatheringState.
48
+        // NOTE: This should not be needed, but Edge does not implement
49
+        // iceGatherer.state.
50
+        // @type {RTCIceGatheringState}
51
+        this._iceGatheringState = RTCIceGatheringState.new;
52
+
53
+        // Create a RTCIceTransport.
54
+        // @type {RTCIceTransport}
55
+        this._iceTransport = this._createIceTransport(this._iceGatherer);
56
+
57
+        // Local RTCSessionDescription.
58
+        // @type {RTCSessionDescription}
59
+        this._localDescription = null;
60
+
61
+        // Set of local MediaStreams.
62
+        // @type {Set<MediaStream>}
63
+        this._localStreams = new Set();
64
+
65
+        // Remote RTCSessionDescription.
66
+        // @type {RTCSessionDescription}
67
+        this._remoteDescription = null;
68
+
69
+        // Set of remote MediaStreams.
70
+        // @type {Set<MediaStream>}
71
+        this._remoteStreams = new Set();
72
+
73
+        // RTCPeerConnection signalingState.
74
+        // @type {RTCSignalingState}
75
+        this._signalingState = RTCSignalingState.stable;
76
+    }
77
+
78
+    /**
79
+     * Gets the current signaling state.
80
+     * @return {RTCSignalingState}
81
+     */
82
+    get signalingState() {
83
+        return this._signalingState;
84
+    }
85
+
86
+    /**
87
+     * Gets the current ICE gathering state.
88
+     * @return {RTCIceGatheringState}
89
+     */
90
+    get iceGatheringState() {
91
+        return this._iceGatheringState;
92
+    }
93
+
94
+    /**
95
+     * Gets the current ICE connection state.
96
+     * @return {RTCIceConnectionState}
97
+     */
98
+    get iceConnectionState() {
99
+        return this._iceTransport.state;
100
+    }
101
+
102
+    /**
103
+     * Gets the local description.
104
+     * @return {RTCSessionDescription}
105
+     */
106
+    get localDescription() {
107
+        return this._localDescription;
108
+    }
109
+
110
+    /**
111
+     * Gets the remote description.
112
+     * @return {RTCSessionDescription}
113
+     */
114
+    get remoteDescription() {
115
+        return this._remoteDescription;
116
+    }
117
+
118
+    /**
119
+     * Closes the RTCPeerConnection.
120
+     */
121
+    close() {
122
+        if (this._closed) {
123
+            return;
124
+        }
125
+
126
+        this._closed = true;
127
+
128
+        logger.debug('close()');
129
+
130
+        this._updateAndEmitSignalingStateChange(RTCSignalingState.closed);
131
+
132
+        // Close iceGatherer.
133
+        // NOTE: Not yet implemented by Edge.
134
+        try {
135
+            this._iceGatherer.close();
136
+        } catch (error) {
137
+            logger.warn(`iceGatherer.close() failed:${error}`);
138
+        }
139
+
140
+        // Close iceTransport.
141
+        try {
142
+            this._iceTransport.stop();
143
+        } catch (error) {
144
+            logger.warn(`iceTransport.stop() failed:${error}`);
145
+        }
146
+
147
+        // Clear local/remote streams.
148
+        this._localStreams.clear();
149
+        this._remoteStreams.clear();
150
+
151
+        // TODO: Close and emit more stuff.
152
+    }
153
+
154
+    /**
155
+     * Creates a local offer. Implements both the old callbacks based signature
156
+     * and the new Promise based style.
157
+     *
158
+     * Arguments in Promise mode:
159
+     * @param {RTCOfferOptions} options
160
+     *
161
+     * Arguments in callbacks mode:
162
+     * @param {function(desc)} callback
163
+     * @param {function(error)} errback
164
+     * @param {MediaConstraints} constraints
165
+     */
166
+    createOffer(...args) {
167
+        let usePromise;
168
+        let options;
169
+        let callback;
170
+        let errback;
171
+
172
+        if (args.length <= 1) {
173
+            usePromise = true;
174
+            options = args[0];
175
+        } else {
176
+            usePromise = false;
177
+            callback = args[0];
178
+            errback = args[1];
179
+            options = args[2];
180
+
181
+            if (typeof callback !== 'function') {
182
+                throw new TypeError('callback missing');
183
+            }
184
+
185
+            if (typeof errback !== 'function') {
186
+                throw new TypeError('errback missing');
187
+            }
188
+        }
189
+
190
+        logger.debug('createOffer() options:', options);
191
+
192
+        if (usePromise) {
193
+            return this._createOffer(options);
194
+        }
195
+
196
+        this._createOffer(options)
197
+            .then(desc => callback(desc))
198
+            .catch(error => errback(error));
199
+    }
200
+
201
+    /**
202
+     * Creates a local answer. Implements both the old callbacks based signature
203
+     * and the new Promise based style.
204
+     *
205
+     * Arguments in Promise mode:
206
+     * @param {RTCOfferOptions} options
207
+     *
208
+     * Arguments in callbacks mode:
209
+     * @param {function(desc)} callback
210
+     * @param {function(error)} errback
211
+     * @param {MediaConstraints} constraints
212
+     */
213
+    createAnswer(...args) {
214
+        let usePromise;
215
+        let options;
216
+        let callback;
217
+        let errback;
218
+
219
+        if (args.length <= 1) {
220
+            usePromise = true;
221
+            options = args[0];
222
+        } else {
223
+            usePromise = false;
224
+            callback = args[0];
225
+            errback = args[1];
226
+            options = args[2];
227
+
228
+            if (typeof callback !== 'function') {
229
+                throw new TypeError('callback missing');
230
+            }
231
+
232
+            if (typeof errback !== 'function') {
233
+                throw new TypeError('errback missing');
234
+            }
235
+        }
236
+
237
+        logger.debug('createAnswer() options:', options);
238
+
239
+        if (usePromise) {
240
+            return this._createAnswer(options);
241
+        }
242
+
243
+        this._createAnswer(options)
244
+            .then(desc => callback(desc))
245
+            .catch(error => errback(error));
246
+    }
247
+
248
+    /**
249
+     * Applies a local description. Implements both the old callbacks based
250
+     * signature and the new Promise based style.
251
+     *
252
+     * Arguments in Promise mode:
253
+     * @param {RTCSessionDescriptionInit} desc
254
+     *
255
+     * Arguments in callbacks mode:
256
+     * @param {RTCSessionDescription} desc
257
+     * @param {function()} callback
258
+     * @param {function(error)} errback
259
+     */
260
+    setLocalDescription(desc, ...args) {
261
+        let usePromise;
262
+        let callback;
263
+        let errback;
264
+
265
+        if (!desc) {
266
+            throw new TypeError('description missing');
267
+        }
268
+
269
+        if (args.length === 0) {
270
+            usePromise = true;
271
+        } else {
272
+            usePromise = false;
273
+            callback = args[0];
274
+            errback = args[1];
275
+
276
+            if (typeof callback !== 'function') {
277
+                throw new TypeError('callback missing');
278
+            }
279
+
280
+            if (typeof errback !== 'function') {
281
+                throw new TypeError('errback missing');
282
+            }
283
+        }
284
+
285
+        logger.debug('setLocalDescription() desc:', desc);
286
+
287
+        if (usePromise) {
288
+            return this._setLocalDescription(desc);
289
+        }
290
+
291
+        this._setLocalDescription(desc)
292
+            .then(() => callback())
293
+            .catch(error => errback(error));
294
+    }
295
+
296
+    /**
297
+     * Applies a remote description. Implements both the old callbacks based
298
+     * signature and the new Promise based style.
299
+     *
300
+     * Arguments in Promise mode:
301
+     * @param {RTCSessionDescriptionInit} desc
302
+     *
303
+     * Arguments in callbacks mode:
304
+     * @param {RTCSessionDescription} desc
305
+     * @param {function()} callback
306
+     * @param {function(error)} errback
307
+     */
308
+    setRemoteDescription(desc, ...args) {
309
+        let usePromise;
310
+        let callback;
311
+        let errback;
312
+
313
+        if (!desc) {
314
+            throw new TypeError('description missing');
315
+        }
316
+
317
+        if (args.length === 0) {
318
+            usePromise = true;
319
+        } else {
320
+            usePromise = false;
321
+            callback = args[0];
322
+            errback = args[1];
323
+
324
+            if (typeof callback !== 'function') {
325
+                throw new TypeError('callback missing');
326
+            }
327
+
328
+            if (typeof errback !== 'function') {
329
+                throw new TypeError('errback missing');
330
+            }
331
+        }
332
+
333
+        logger.debug('setRemoteDescription() desc:', desc);
334
+
335
+        if (usePromise) {
336
+            return this._setRemoteDescription(desc);
337
+        }
338
+
339
+        this._setRemoteDescription(desc)
340
+            .then(() => callback())
341
+            .catch(error => errback(error));
342
+    }
343
+
344
+    /**
345
+     * Adds a remote ICE candidate. Implements both the old callbacks based
346
+     * signature and the new Promise based style.
347
+     *
348
+     * Arguments in Promise mode:
349
+     * @param {RTCIceCandidate} candidate
350
+     *
351
+     * Arguments in callbacks mode:
352
+     * @param {RTCIceCandidate} candidate
353
+     * @param {function()} callback
354
+     * @param {function(error)} errback
355
+     */
356
+    addIceCandidate(candidate, ...args) {
357
+        let usePromise;
358
+        let callback;
359
+        let errback;
360
+
361
+        if (!candidate) {
362
+            throw new TypeError('candidate missing');
363
+        }
364
+
365
+        if (args.length === 0) {
366
+            usePromise = true;
367
+        } else {
368
+            usePromise = false;
369
+            callback = args[0];
370
+            errback = args[1];
371
+
372
+            if (typeof callback !== 'function') {
373
+                throw new TypeError('callback missing');
374
+            }
375
+
376
+            if (typeof errback !== 'function') {
377
+                throw new TypeError('errback missing');
378
+            }
379
+        }
380
+
381
+        logger.debug('addIceCandidate() candidate:', candidate);
382
+
383
+        if (usePromise) {
384
+            return this._addIceCandidate(candidate);
385
+        }
386
+
387
+        this._addIceCandidate(candidate)
388
+            .then(() => callback())
389
+            .catch(error => errback(error));
390
+    }
391
+
392
+    /**
393
+     * Adds a local MediaStream.
394
+     * @param {MediaStream} stream.
395
+     * NOTE: Deprecated API.
396
+     */
397
+    addStream(stream) {
398
+        logger.debug('addStream()');
399
+
400
+        this._addStream(stream);
401
+    }
402
+
403
+    /**
404
+     * Removes a local MediaStream.
405
+     * @param {MediaStream} stream.
406
+     * NOTE: Deprecated API.
407
+     */
408
+    removeStream(stream) {
409
+        logger.debug('removeStream()');
410
+
411
+        this._removeStream(stream);
412
+    }
413
+
414
+    /**
415
+     * Creates a RTCDataChannel.
416
+     * TBD
417
+     */
418
+    createDataChannel() {
419
+        logger.debug('createDataChannel()');
420
+    }
421
+
422
+    /**
423
+     * Gets a sequence of local MediaStreams.
424
+     */
425
+    getLocalStreams() {
426
+        return Array.from(this._localStreams);
427
+    }
428
+
429
+    /**
430
+     * Gets a sequence of remote MediaStreams.
431
+     */
432
+    getRemoteStreams() {
433
+        return Array.from(this._remoteStreams);
434
+    }
435
+
436
+    /**
437
+     * TBD
438
+     */
439
+    getStats() {
440
+        // TBD
441
+    }
442
+
443
+    /**
444
+     * Creates and returns a RTCIceGatherer.
445
+     * @return {RTCIceGatherer}
446
+     * @private
447
+     */
448
+    _createIceGatherer(pcConfig) {
449
+        const iceGatherOptions = {
450
+            gatherPolicy: pcConfig.iceTransportPolicy || 'all',
451
+            iceServers: pcConfig.iceServers || []
452
+        };
453
+        const iceGatherer = new RTCIceGatherer(iceGatherOptions);
454
+
455
+        // NOTE: Not yet implemented by Edge.
456
+        iceGatherer.onstatechange = () => {
457
+            logger.debug(
458
+                `iceGatherer "statechange" event, state:${iceGatherer.state}`);
459
+
460
+            this._updateAndEmitIceGatheringStateChange(iceGatherer.state);
461
+        };
462
+
463
+        iceGatherer.onlocalcandidate = ev => {
464
+            let candidate = ev.candidate;
465
+
466
+            // NOTE: Not yet implemented by Edge.
467
+            const complete = ev.complete;
468
+
469
+            logger.debug(
470
+                'iceGatherer "localcandidate" event, candidate:', candidate);
471
+
472
+            // NOTE: Instead of null candidate or complete:true, current Edge
473
+            // signals end of gathering with an empty candidate object.
474
+            if (complete
475
+                || !candidate
476
+                || Object.keys(candidate).length === 0) {
477
+
478
+                candidate = null;
479
+
480
+                this._updateAndEmitIceGatheringStateChange(
481
+                    RTCIceGatheringState.complete);
482
+                this._emitIceCandidate(null);
483
+            } else {
484
+                this._emitIceCandidate(candidate);
485
+            }
486
+        };
487
+
488
+        iceGatherer.onerror = ev => {
489
+            const errorCode = ev.errorCode;
490
+            const errorText = ev.errorText;
491
+
492
+            logger.error(
493
+                `iceGatherer "error" event, errorCode:${errorCode}, `
494
+                + `errorText:${errorText}`);
495
+        };
496
+
497
+        // NOTE: Not yet implemented by Edge, which starts gathering
498
+        // automatically.
499
+        try {
500
+            iceGatherer.gather();
501
+        } catch (error) {
502
+            logger.warn(`iceGatherer.gather() failed:${error}`);
503
+        }
504
+
505
+        return iceGatherer;
506
+    }
507
+
508
+    /**
509
+     * Creates and returns a RTCIceTransport.
510
+     * @return {RTCIceTransport}
511
+     * @private
512
+     */
513
+    _createIceTransport(iceGatherer) {
514
+        const iceTransport = new RTCIceTransport(iceGatherer);
515
+
516
+        // NOTE: Not yet implemented by Edge.
517
+        iceTransport.onstatechange = () => {
518
+            logger.debug(
519
+                'iceTransport "statechange" event, '
520
+                + `state:${iceTransport.state}`);
521
+
522
+            this._emitIceConnectionStateChange();
523
+        };
524
+
525
+        // NOTE: Not standard, but implemented by Edge.
526
+        iceTransport.onicestatechange = () => {
527
+            logger.debug(
528
+                'iceTransport "icestatechange" event, '
529
+                + `state:${iceTransport.state}`);
530
+
531
+            this._emitIceConnectionStateChange();
532
+        };
533
+
534
+        // TODO: More stuff to be done.
535
+
536
+        return iceTransport;
537
+    }
538
+
539
+    /**
540
+     * Promise based implementation for createOffer().
541
+     * @returns {Promise}
542
+     * @private
543
+     */
544
+    _createOffer(options) { // eslint-disable-line no-unused-vars
545
+        if (this._closed) {
546
+            return Promise.reject(
547
+                new InvalidStateError('RTCPeerConnection closed'));
548
+        }
549
+
550
+        if (this.signalingState !== RTCSignalingState.stable) {
551
+            return Promise.reject(new InvalidStateError(
552
+                `invalid signalingState "${this.signalingState}"`));
553
+        }
554
+
555
+        // TODO: More stuff to be done.
556
+    }
557
+
558
+    /**
559
+     * Promise based implementation for createAnswer().
560
+     * @returns {Promise}
561
+     * @private
562
+     */
563
+    _createAnswer(options) { // eslint-disable-line no-unused-vars
564
+        if (this._closed) {
565
+            return Promise.reject(
566
+                new InvalidStateError('RTCPeerConnection closed'));
567
+        }
568
+
569
+        if (this.signalingState !== RTCSignalingState.haveRemoteOffer) {
570
+            return Promise.reject(new InvalidStateError(
571
+                `invalid signalingState "${this.signalingState}"`));
572
+        }
573
+
574
+        // TODO: More stuff to be done.
575
+    }
576
+
577
+    /**
578
+     * Promise based implementation for setLocalDescription().
579
+     * @returns {Promise}
580
+     * @private
581
+     */
582
+    _setLocalDescription(desc) {
583
+        if (this._closed) {
584
+            return Promise.reject(
585
+                new InvalidStateError('RTCPeerConnection closed'));
586
+        }
587
+
588
+        switch (desc.type) {
589
+        case 'offer':
590
+            if (this.signalingState !== RTCSignalingState.stable) {
591
+                return Promise.reject(new InvalidStateError(
592
+                    `invalid signalingState "${this.signalingState}"`));
593
+            }
594
+
595
+            break;
596
+
597
+        case 'answer':
598
+            if (this.signalingState !== RTCSignalingState.haveRemoteOffer) {
599
+                return Promise.reject(new InvalidStateError(
600
+                    `invalid signalingState "${this.signalingState}"`));
601
+            }
602
+
603
+            break;
604
+
605
+        default:
606
+            throw new TypeError(`unsupported description.type "${desc.type}"`);
607
+        }
608
+
609
+        // TODO: More stuff to be done.
610
+    }
611
+
612
+    /**
613
+     * Promise based implementation for setRemoteDescription().
614
+     * @returns {Promise}
615
+     * @private
616
+     */
617
+    _setRemoteDescription(desc) {
618
+        if (this._closed) {
619
+            return Promise.reject(
620
+                new InvalidStateError('RTCPeerConnection closed'));
621
+        }
622
+
623
+        switch (desc.type) {
624
+        case 'offer':
625
+            if (this.signalingState !== RTCSignalingState.stable) {
626
+                return Promise.reject(new InvalidStateError(
627
+                    `invalid signalingState "${this.signalingState}"`));
628
+            }
629
+
630
+            break;
631
+
632
+        case 'answer':
633
+            if (this.signalingState !== RTCSignalingState.haveLocalOffer) {
634
+                return Promise.reject(new InvalidStateError(
635
+                    `invalid signalingState "${this.signalingState}"`));
636
+            }
637
+
638
+            break;
639
+
640
+        default:
641
+            throw new TypeError(`unsupported description.type "${desc.type}"`);
642
+        }
643
+
644
+        // TODO: More stuff to be done.
645
+    }
646
+
647
+    /**
648
+     * Implementation for addStream().
649
+     * @private
650
+     */
651
+    _addStream(stream) {
652
+        if (this._closed) {
653
+            throw new InvalidStateError('RTCPeerConnection closed');
654
+        }
655
+
656
+        if (this._localStreams.has(stream)) {
657
+            return;
658
+        }
659
+
660
+        this._localStreams.add(stream);
661
+
662
+        // It may need to renegotiate.
663
+        this._emitNegotiationNeeded();
664
+    }
665
+
666
+    /**
667
+     * Implementation for removeStream().
668
+     * @private
669
+     */
670
+    _removeStream(stream) {
671
+        if (this._closed) {
672
+            throw new InvalidStateError('RTCPeerConnection closed');
673
+        }
674
+
675
+        if (!this._localStreams.has(stream)) {
676
+            return;
677
+        }
678
+
679
+        this._localStreams.delete(stream);
680
+
681
+        // It may need to renegotiate.
682
+        this._emitNegotiationNeeded();
683
+    }
684
+
685
+    /**
686
+     * May update signalingState and emit 'signalingstatechange' event.
687
+     */
688
+    _updateAndEmitSignalingStateChange(state) {
689
+        if (state === this.signalingState) {
690
+            return;
691
+        }
692
+
693
+        this._signalingState = state;
694
+
695
+        logger.debug(
696
+            'emitting "signalingstatechange", signalingState:',
697
+            this.signalingState);
698
+
699
+        const event = new yaeti.Event('signalingstatechange');
700
+
701
+        this.dispatchEvent(event);
702
+    }
703
+
704
+    /**
705
+     * May emit 'negotiationneeded' event.
706
+     */
707
+    _emitNegotiationNeeded() {
708
+        // Ignore if signalingState is not 'stable'.
709
+        if (this.signalingState !== RTCSignalingState.stable) {
710
+            return;
711
+        }
712
+
713
+        logger.debug('emitting "negotiationneeded"');
714
+
715
+        const event = new yaeti.Event('negotiationneeded');
716
+
717
+        this.dispatchEvent(event);
718
+    }
719
+
720
+    /**
721
+     * May update iceGatheringState and emit 'icegatheringstatechange' event.
722
+     */
723
+    _updateAndEmitIceGatheringStateChange(state) {
724
+        if (this._closed || state === this.iceGatheringState) {
725
+            return;
726
+        }
727
+
728
+        this._iceGatheringState = state;
729
+
730
+        logger.debug(
731
+            'emitting "icegatheringstatechange", iceGatheringState:',
732
+            this.iceGatheringState);
733
+
734
+        const event = new yaeti.Event('icegatheringstatechange');
735
+
736
+        this.dispatchEvent(event);
737
+    }
738
+
739
+    /**
740
+     * May emit 'iceconnectionstatechange' event.
741
+     */
742
+    _emitIceConnectionStateChange() {
743
+        if (this._closed && this.iceConnectionState !== 'closed') {
744
+            return;
745
+        }
746
+
747
+        logger.debug(
748
+            'emitting "iceconnectionstatechange", iceConnectionState:',
749
+            this.iceConnectionState);
750
+
751
+        const event = new yaeti.Event('iceconnectionstatechange');
752
+
753
+        this.dispatchEvent(event);
754
+    }
755
+
756
+    /**
757
+     * May emit 'icecandidate' event.
758
+     */
759
+    _emitIceCandidate(candidate) {
760
+        if (this._closed) {
761
+            return;
762
+        }
763
+
764
+        const event = new yaeti.Event('icecandidate');
765
+
766
+        logger.debug(
767
+            'emitting "icecandidate", candidate:', candidate);
768
+
769
+        event.candidate = candidate;
770
+        this.dispatchEvent(event);
771
+    }
772
+}

+ 79
- 0
modules/RTC/ortc/RTCSessionDescription.js 查看文件

@@ -0,0 +1,79 @@
1
+import sdpTransform from 'sdp-transform';
2
+
3
+/**
4
+ * RTCSessionDescription implementation.
5
+ */
6
+export default class RTCSessionDescription {
7
+    /**
8
+     * RTCSessionDescription constructor.
9
+     * @param {object} [data]
10
+     * @param {string} [data.type] "offer" / "answer".
11
+     * @param {string} [data.sdp] SDP string.
12
+     * @param {object} [data._sdpObject] SDP object generated by the
13
+     * sdp-transform library.
14
+     */
15
+    constructor(data) {
16
+        // @type {string}
17
+        this._sdp = null;
18
+
19
+        // @type {object}
20
+        this._sdpObject = null;
21
+
22
+        // @type {string}
23
+        this._type = null;
24
+
25
+        switch (data.type) {
26
+        case 'offer':
27
+            break;
28
+        case 'answer':
29
+            break;
30
+        default:
31
+            throw new TypeError(`invalid type "${data.type}"`);
32
+        }
33
+
34
+        this._type = data.type;
35
+
36
+        if (typeof data.sdp === 'string') {
37
+            this._sdp = data.sdp;
38
+            try {
39
+                this._sdpObject = sdpTransform.parse(data.sdp);
40
+            } catch (error) {
41
+                throw new Error(`invalid sdp: ${error}`);
42
+            }
43
+        } else if (typeof data._sdpObject === 'object') {
44
+            this._sdpObject = data._sdpObject;
45
+            try {
46
+                this._sdp = sdpTransform.write(data._sdpObject);
47
+            } catch (error) {
48
+                throw new Error(`invalid sdp object: ${error}`);
49
+            }
50
+        } else {
51
+            throw new TypeError('invalid sdp or _sdpObject');
52
+        }
53
+    }
54
+
55
+    /**
56
+     * Get type field.
57
+     * @return {string}
58
+     */
59
+    get type() {
60
+        return this._type;
61
+    }
62
+
63
+    /**
64
+     * Get sdp field.
65
+     * @return {string}
66
+     */
67
+    get sdp() {
68
+        return this._sdp;
69
+    }
70
+
71
+    /**
72
+     * Gets the internal sdp object.
73
+     * @return {object}
74
+     * @private
75
+     */
76
+    get sdpObject() {
77
+        return this._sdpObject;
78
+    }
79
+}

+ 21
- 0
modules/RTC/ortc/errors.js 查看文件

@@ -0,0 +1,21 @@
1
+/**
2
+ * Create a class inheriting from Error.
3
+ */
4
+function createErrorClass(name) {
5
+    const klass = class extends Error {
6
+        /**
7
+         * Custom error class constructor.
8
+         * @param {string} message
9
+         */
10
+        constructor(message) {
11
+            super(message);
12
+
13
+            // Override `name` property value and make it non enumerable.
14
+            Object.defineProperty(this, 'name', { value: name });
15
+        }
16
+    };
17
+
18
+    return klass;
19
+}
20
+
21
+export const InvalidStateError = createErrorClass('InvalidStateError');

+ 3
- 2
package.json 查看文件

@@ -23,10 +23,11 @@
23 23
     "retry": "0.6.1",
24 24
     "sdp-interop": "0.1.11",
25 25
     "sdp-simulcast": "0.1.11",
26
-    "sdp-transform": "1.5.3",
26
+    "sdp-transform": "2.3.0",
27 27
     "socket.io-client": "1.4.5",
28 28
     "strophe": "1.2.4",
29
-    "strophejs-plugins": "0.0.7"
29
+    "strophejs-plugins": "0.0.7",
30
+    "yaeti": "1.0.0"
30 31
   },
31 32
   "devDependencies": {
32 33
     "babel-core": "6.24.1",

正在加载...
取消
保存