瀏覽代碼

feat: add SdpTransformUtil

* ref(TraceablePeerConnection): rename var to what it is

* ref(ssrc info): get rid of 'ssrc' and 'group' roots

Removes 'ssrc' and 'group' root Objects from "ssrc info" structure.
Also removed redundant 'primarySSRC' from the group.
Do not split and join back and forth the SSRCs, but store them as
numbers.

* feat: add SdpTransformUtil

* ref(rtxModifier): rename "previousAssociatedRtxStream"

* ref(RtxModifier): use for .. of

* fix(SdpConsistency): organize imports

* fix(RtxModifier): organize imports

* ref(JitsiLocalTrack): simplify the expression

* extract MLineWrap (ongoing)

* ref(TPC.extractSSRCMap): some ES6 cleanup

* ref(RtxModifier): use template strings

* doc(SdpTransformUtil): update and fill missing

* ref(SdpTransformUtil): const, template strings

* style(TPC): object formatting

* ref(JitsiLocalTrack): simplify expression + syntax

* ref(TPC): more "extractSSRCMap" improvements

* ref(RtxModifier): const all the things

* ref(SdpConsistency): template strings

* ref(SdpTransformUtil): syntax + other

* doc(SdpConsistency): add throws description

* fix(TPC): broken "extractSSCMap"

* fix(JitsiConference): adopt to Map

* ref(SdpTransformUtil): remove forEachSSRCGroup and static methods
dev1
Paweł Domas 8 年之前
父節點
當前提交
d0ce2d6866

+ 6
- 4
JitsiConference.js 查看文件

@@ -527,8 +527,9 @@ JitsiConference.prototype.replaceTrack = function (oldTrack, newTrack) {
527 527
         }
528 528
         // Set up the ssrcHandler for the new track before we add it at the lower levels
529 529
         newTrack.ssrcHandler = function (conference, ssrcMap) {
530
-            if (ssrcMap[this.getMSID()]) {
531
-                this._setSSRC(ssrcMap[this.getMSID()]);
530
+            const trackSSRCInfo = ssrcMap.get(this.getMSID());
531
+            if (trackSSRCInfo) {
532
+                this._setSSRC(trackSSRCInfo);
532 533
                 conference.rtc.removeListener(
533 534
                     RTCEvents.SENDRECV_STREAMS_CHANGED,
534 535
                     this.ssrcHandler);
@@ -1039,7 +1040,7 @@ function (jingleSession, jingleOffer, now) {
1039 1040
     this.rtc.initializeDataChannels(jingleSession.peerconnection);
1040 1041
     // Add local Tracks to the ChatRoom
1041 1042
     this.getLocalTracks().forEach(function(localTrack) {
1042
-        var ssrcInfo = null;
1043
+        let ssrcInfo = null;
1043 1044
         /**
1044 1045
          * We don't do this for Firefox because, on Firefox, we keep the
1045 1046
          *  stream in the peer connection and just set 'enabled' on the
@@ -1070,7 +1071,8 @@ function (jingleSession, jingleOffer, now) {
1070 1071
             ssrcInfo = {
1071 1072
                 mtype: localTrack.getType(),
1072 1073
                 type: "addMuted",
1073
-                ssrc: localTrack.ssrc,
1074
+                ssrcs: localTrack.ssrc.ssrcs,
1075
+                groups: localTrack.ssrc.groups,
1074 1076
                 msid: localTrack.initialMSID
1075 1077
             };
1076 1078
         }

+ 13
- 15
modules/RTC/JitsiLocalTrack.js 查看文件

@@ -370,18 +370,17 @@ JitsiLocalTrack.prototype._addStreamToConferenceAsUnmute = function () {
370 370
         return Promise.resolve();
371 371
     }
372 372
 
373
-    var self = this;
374
-
375
-    return new Promise(function(resolve, reject) {
376
-        self.conference._addLocalStream(
377
-            self.stream,
373
+    return new Promise((resolve, reject) => {
374
+        this.conference._addLocalStream(
375
+            this.stream,
378 376
             resolve,
379 377
             (error) => reject(new Error(error)),
380 378
             {
381
-                mtype: self.type,
379
+                mtype: this.type,
382 380
                 type: "unmute",
383
-                ssrc: self.ssrc,
384
-                msid: self.getMSID()
381
+                ssrcs: this.ssrc && this.ssrc.ssrcs,
382
+                groups: this.ssrc && this.ssrc.groups,
383
+                msid: this.getMSID()
385 384
             });
386 385
     });
387 386
 };
@@ -406,7 +405,8 @@ function (successCallback, errorCallback) {
406 405
         {
407 406
             mtype: this.type,
408 407
             type: "mute",
409
-            ssrc: this.ssrc
408
+            ssrcs: this.ssrc && this.ssrc.ssrcs,
409
+            groups: this.ssrc && this.ssrc.groups
410 410
         });
411 411
 };
412 412
 
@@ -422,11 +422,9 @@ JitsiLocalTrack.prototype._sendMuteStatus = function(mute) {
422 422
         return Promise.resolve();
423 423
     }
424 424
 
425
-    var self = this;
426
-
427
-    return new Promise(function(resolve) {
428
-        self.conference.room[
429
-            self.isAudioTrack()
425
+    return new Promise((resolve) => {
426
+        this.conference.room[
427
+            this.isAudioTrack()
430 428
                 ? 'setAudioMute'
431 429
                 : 'setVideoMute'](mute, resolve);
432 430
     });
@@ -521,7 +519,7 @@ JitsiLocalTrack.prototype._setConference = function(conference) {
521 519
  */
522 520
 JitsiLocalTrack.prototype.getSSRC = function () {
523 521
     if(this.ssrc && this.ssrc.groups && this.ssrc.groups.length)
524
-        return this.ssrc.groups[0].primarySSRC;
522
+        return this.ssrc.groups[0].ssrcs[0];
525 523
     else if(this.ssrc && this.ssrc.ssrcs && this.ssrc.ssrcs.length)
526 524
         return this.ssrc.ssrcs[0];
527 525
     else

+ 93
- 58
modules/RTC/TraceablePeerConnection.js 查看文件

@@ -399,57 +399,98 @@ TraceablePeerConnection.prototype._remoteTrackRemoved
399 399
 };
400 400
 
401 401
 /**
402
- * Returns map with keys msid and values ssrc.
403
- * @param desc the SDP that will be modified.
402
+ * @typedef {Object} SSRCGroupInfo
403
+ * @property {Array<number>} ssrcs group's SSRCs
404
+ * @property {string} semantics
405
+ */
406
+/**
407
+ * @typedef {Object} TrackSSRCInfo
408
+ * @property {Array<number>} ssrcs track's SSRCs
409
+ * @property {Array<SSRCGroupInfo>} groups track's SSRC groups
410
+ */
411
+/**
412
+ * Returns map with keys msid and <tt>TrackSSRCInfo</tt> values.
413
+ * @param {Object} desc the WebRTC SDP instance.
414
+ * @return {Map<string,TrackSSRCInfo>}
404 415
  */
405 416
 function extractSSRCMap(desc) {
417
+    /**
418
+     * Track SSRC infos mapped by stream ID (msid)
419
+     * @type {Map<string,TrackSSRCInfo>}
420
+     */
421
+    const ssrcMap = new Map();
422
+    /**
423
+     * Groups mapped by primary SSRC number
424
+     * @type {Map<number,Array<SSRCGroupInfo>>}
425
+     */
426
+    const groupsMap = new Map();
427
+
406 428
     if (typeof desc !== 'object' || desc === null ||
407 429
         typeof desc.sdp !== 'string') {
408 430
         logger.warn('An empty description was passed as an argument.');
409
-        return desc;
431
+        return ssrcMap;
410 432
     }
411 433
 
412
-    var ssrcList = {};
413
-    var ssrcGroups = {};
414
-    var session = transform.parse(desc.sdp);
415
-    if (!Array.isArray(session.media))
416
-    {
417
-        return;
434
+    const session = transform.parse(desc.sdp);
435
+
436
+    if (!Array.isArray(session.media)) {
437
+        return ssrcMap;
418 438
     }
419 439
 
420
-    session.media.forEach(function (bLine) {
421
-        if (!Array.isArray(bLine.ssrcs))
422
-        {
423
-            return;
440
+    for (const mLine of session.media) {
441
+        if (!Array.isArray(mLine.ssrcs)) {
442
+            continue;
424 443
         }
425 444
 
426
-        if (typeof bLine.ssrcGroups !== 'undefined' &&
427
-            Array.isArray(bLine.ssrcGroups)) {
428
-            bLine.ssrcGroups.forEach(function (group) {
445
+        if (Array.isArray(mLine.ssrcGroups)) {
446
+            for (const group of mLine.ssrcGroups) {
429 447
                 if (typeof group.semantics !== 'undefined' &&
430 448
                     typeof group.ssrcs !== 'undefined') {
431
-                    var primarySSRC = Number(group.ssrcs.split(' ')[0]);
432
-                    ssrcGroups[primarySSRC] = ssrcGroups[primarySSRC] || [];
433
-                    ssrcGroups[primarySSRC].push(group);
449
+                    // Parse SSRCs and store as numbers
450
+                    const groupSSRCs
451
+                        = group.ssrcs.split(' ')
452
+                                     .map(ssrcStr => parseInt(ssrcStr));
453
+                    const primarySSRC = groupSSRCs[0];
454
+                    // Note that group.semantics is already present
455
+                    group.ssrcs = groupSSRCs;
456
+                    if (!groupsMap.has(primarySSRC)) {
457
+                        groupsMap.set(primarySSRC, []);
458
+                    }
459
+                    groupsMap.get(primarySSRC).push(group);
434 460
                 }
435
-            });
461
+            }
436 462
         }
437
-        bLine.ssrcs.forEach(function (ssrc) {
438
-            if(ssrc.attribute !== 'msid')
439
-                return;
440
-            ssrcList[ssrc.value] = ssrcList[ssrc.value] ||
441
-                {groups: [], ssrcs: []};
442
-            ssrcList[ssrc.value].ssrcs.push(ssrc.id);
443
-            if(ssrcGroups[ssrc.id]){
444
-                ssrcGroups[ssrc.id].forEach(function (group) {
445
-                    ssrcList[ssrc.value].groups.push(
446
-                        {primarySSRC: ssrc.id, group: group});
447
-                });
463
+        for (const ssrc of mLine.ssrcs) {
464
+            if (ssrc.attribute !== 'msid') {
465
+                continue;
448 466
             }
449
-        });
450
-    });
451 467
 
452
-    return ssrcList;
468
+            const msid = ssrc.value;
469
+            let ssrcInfo = ssrcMap.get(msid);
470
+
471
+            if (!ssrcInfo) {
472
+                ssrcInfo = {
473
+                    ssrcs: [],
474
+                    groups: []
475
+                };
476
+                ssrcMap.set(msid, ssrcInfo);
477
+            }
478
+
479
+            const ssrcNumber = ssrc.id;
480
+
481
+            ssrcInfo.ssrcs.push(ssrcNumber);
482
+
483
+            if (groupsMap.has(ssrcNumber)) {
484
+                const ssrcGroups = groupsMap.get(ssrcNumber);
485
+
486
+                for (const group of ssrcGroups) {
487
+                    ssrcInfo.groups.push(group);
488
+                }
489
+            }
490
+        }
491
+    }
492
+
493
+    return ssrcMap;
453 494
 }
454 495
 
455 496
 /**
@@ -568,26 +609,20 @@ TraceablePeerConnection.prototype.addStream = function (stream, ssrcInfo) {
568 609
     if (stream)
569 610
         this.peerconnection.addStream(stream);
570 611
     if (ssrcInfo && ssrcInfo.type === "addMuted") {
571
-        this.sdpConsistency.setPrimarySsrc(ssrcInfo.ssrc.ssrcs[0]);
572
-        const simGroup =
573
-            ssrcInfo.ssrc.groups.find(groupInfo => {
574
-                return groupInfo.group.semantics === "SIM";
575
-            });
612
+        this.sdpConsistency.setPrimarySsrc(ssrcInfo.ssrcs[0]);
613
+        const simGroup
614
+            = ssrcInfo.groups.find(groupInfo => groupInfo.semantics === "SIM");
576 615
         if (simGroup) {
577
-            const simSsrcs = SDPUtil.parseGroupSsrcs(simGroup.group);
578
-            this.simulcast.setSsrcCache(simSsrcs);
616
+            this.simulcast.setSsrcCache(simGroup.ssrcs);
579 617
         }
580
-        const fidGroups =
581
-            ssrcInfo.ssrc.groups.filter(groupInfo => {
582
-                return groupInfo.group.semantics === "FID";
583
-            });
618
+        const fidGroups
619
+            = ssrcInfo.groups.filter(
620
+                groupInfo => groupInfo.semantics === "FID");
584 621
         if (fidGroups) {
585 622
             const rtxSsrcMapping = new Map();
586 623
             fidGroups.forEach(fidGroup => {
587
-                const fidGroupSsrcs =
588
-                    SDPUtil.parseGroupSsrcs(fidGroup.group);
589
-                const primarySsrc = fidGroupSsrcs[0];
590
-                const rtxSsrc = fidGroupSsrcs[1];
624
+                const primarySsrc = fidGroup.ssrcs[0];
625
+                const rtxSsrc = fidGroup.ssrcs[1];
591 626
                 rtxSsrcMapping.set(primarySsrc, rtxSsrc);
592 627
             });
593 628
             this.rtxModifier.setSsrcCache(rtxSsrcMapping);
@@ -898,11 +933,14 @@ TraceablePeerConnection.prototype.generateNewStreamSSRCInfo = function () {
898 933
             ssrcInfo.ssrcs.push(SDPUtil.generateSsrc());
899 934
         }
900 935
         ssrcInfo.groups.push({
901
-            primarySSRC: ssrcInfo.ssrcs[0],
902
-            group: {ssrcs: ssrcInfo.ssrcs.join(" "), semantics: "SIM"}});
903
-        ssrcInfo;
936
+            ssrcs: ssrcInfo.ssrcs.slice(),
937
+            semantics: "SIM"
938
+        });
904 939
     } else {
905
-        ssrcInfo = {ssrcs: [SDPUtil.generateSsrc()], groups: []};
940
+        ssrcInfo = {
941
+            ssrcs: [SDPUtil.generateSsrc()],
942
+            groups: []
943
+        };
906 944
     }
907 945
     if (!this.options.disableRtx && !RTCBrowserType.isFirefox()) {
908 946
         // Specifically use a for loop here because we'll
@@ -915,11 +953,8 @@ TraceablePeerConnection.prototype.generateNewStreamSSRCInfo = function () {
915 953
             const rtxSsrc = SDPUtil.generateSsrc();
916 954
             ssrcInfo.ssrcs.push(rtxSsrc);
917 955
             ssrcInfo.groups.push({
918
-                primarySSRC: primarySsrc,
919
-                group: {
920
-                    ssrcs: primarySsrc + " " + rtxSsrc,
921
-                    semantics: "FID"
922
-                }
956
+                ssrcs: [primarySsrc, rtxSsrc],
957
+                semantics: "FID"
923 958
             });
924 959
         }
925 960
     }

+ 22
- 23
modules/xmpp/JingleSessionPC.js 查看文件

@@ -1431,16 +1431,15 @@ export default class JingleSessionPC extends JingleSession {
1431 1431
                     ssrcObj.mtype + "\"]>description");
1432 1432
                 if (!desc || !desc.length)
1433 1433
                     return;
1434
-                ssrcObj.ssrc.ssrcs.forEach(function (ssrc) {
1434
+                ssrcObj.ssrcs.forEach(function (ssrc) {
1435 1435
                     const sourceNode = desc.find(">source[ssrc=\"" +
1436 1436
                         ssrc + "\"]");
1437 1437
                     sourceNode.remove();
1438 1438
                 });
1439
-                ssrcObj.ssrc.groups.forEach(function (group) {
1439
+                ssrcObj.groups.forEach(function (group) {
1440 1440
                     const groupNode = desc.find(">ssrc-group[semantics=\"" +
1441
-                        group.group.semantics + "\"]:has(source[ssrc=\"" +
1442
-                        group.primarySSRC +
1443
-                        "\"])");
1441
+                        group.semantics + "\"]:has(source[ssrc=\"" +
1442
+                        group.ssrcs[0] + "\"])");
1444 1443
                     groupNode.remove();
1445 1444
                 });
1446 1445
             });
@@ -1454,7 +1453,7 @@ export default class JingleSessionPC extends JingleSession {
1454 1453
                     = JingleSessionPC.createDescriptionNode(
1455 1454
                         jingle, ssrcObj.mtype);
1456 1455
                 const cname = Math.random().toString(36).substring(2);
1457
-                ssrcObj.ssrc.ssrcs.forEach(function (ssrc) {
1456
+                ssrcObj.ssrcs.forEach(function (ssrc) {
1458 1457
                     const sourceNode
1459 1458
                         = desc.find(">source[ssrc=\"" + ssrc + "\"]");
1460 1459
                     sourceNode.remove();
@@ -1468,18 +1467,18 @@ export default class JingleSessionPC extends JingleSession {
1468 1467
                         "</source>";
1469 1468
                     desc.append(sourceXML);
1470 1469
                 });
1471
-                ssrcObj.ssrc.groups.forEach(function (group) {
1470
+                ssrcObj.groups.forEach(function (group) {
1472 1471
                     const groupNode
1473 1472
                         = desc.find(">ssrc-group[semantics=\"" +
1474
-                            group.group.semantics + "\"]:has(source[ssrc=\""
1475
-                            + group.primarySSRC + "\"])");
1473
+                            group.semantics + "\"]:has(source[ssrc=\""
1474
+                            + group.ssrcs[0] + "\"])");
1476 1475
                     groupNode.remove();
1477 1476
                     desc.append(
1478
-                        "<ssrc-group semantics=\"" + group.group.semantics +
1477
+                        "<ssrc-group semantics=\"" + group.semantics +
1479 1478
                         "\" xmlns=\"urn:xmpp:jingle:apps:rtp:ssma:0\">" +
1480 1479
                         "<source ssrc=\"" +
1481
-                            group.group.ssrcs.split(" ")
1482
-                                .join("\"/>" + "<source ssrc=\"") + "\"/>" +
1480
+                            group.ssrcs.join("\"/>" + "<source ssrc=\"") +
1481
+                            "\"/>" +
1483 1482
                         "</ssrc-group>");
1484 1483
                 });
1485 1484
             });
@@ -1497,20 +1496,20 @@ export default class JingleSessionPC extends JingleSession {
1497 1496
         this.modifiedSSRCs["mute"] = [];
1498 1497
         if (ssrcs && ssrcs.length)
1499 1498
             ssrcs.forEach(function (ssrcObj) {
1500
-                ssrcObj.ssrc.ssrcs.forEach(function (ssrc) {
1499
+                ssrcObj.ssrcs.forEach(function (ssrc) {
1501 1500
                     const sourceNode
1502 1501
                         = $(jingle.tree()).find(">jingle>content[name=\"" +
1503 1502
                             ssrcObj.mtype + "\"]>description>source[ssrc=\"" +
1504 1503
                             ssrc + "\"]");
1505 1504
                     sourceNode.remove();
1506 1505
                 });
1507
-                ssrcObj.ssrc.groups.forEach(function (group) {
1506
+                ssrcObj.groups.forEach(function (group) {
1508 1507
                     const groupNode
1509 1508
                         = $(jingle.tree()).find(
1510 1509
                             ">jingle>content[name=\"" + ssrcObj.mtype +
1511 1510
                             "\"]>description>ssrc-group[semantics=\"" +
1512
-                            group.group.semantics + "\"]:has(source[ssrc=\"" +
1513
-                            group.primarySSRC + "\"])");
1511
+                            group.semantics + "\"]:has(source[ssrc=\"" +
1512
+                            group.ssrcs[0] + "\"])");
1514 1513
                     groupNode.remove();
1515 1514
                 });
1516 1515
             });
@@ -1522,7 +1521,7 @@ export default class JingleSessionPC extends JingleSession {
1522 1521
                 const desc
1523 1522
                     = JingleSessionPC.createDescriptionNode(
1524 1523
                         jingle, ssrcObj.mtype);
1525
-                ssrcObj.ssrc.ssrcs.forEach(function (ssrc) {
1524
+                ssrcObj.ssrcs.forEach(function (ssrc) {
1526 1525
                     const sourceNode
1527 1526
                         = desc.find(">source[ssrc=\"" + ssrc + "\"]");
1528 1527
                     if (!sourceNode || !sourceNode.length) {
@@ -1533,18 +1532,18 @@ export default class JingleSessionPC extends JingleSession {
1533 1532
                             "ssrc=\"" + ssrc + "\"></source>");
1534 1533
                     }
1535 1534
                 });
1536
-                ssrcObj.ssrc.groups.forEach(function (group) {
1535
+                ssrcObj.groups.forEach(function (group) {
1537 1536
                     const groupNode
1538 1537
                         = desc.find(">ssrc-group[semantics=\"" +
1539
-                            group.group.semantics + "\"]:has(source[ssrc=\"" +
1540
-                            group.primarySSRC + "\"])");
1538
+                            group.semantics + "\"]:has(source[ssrc=\"" +
1539
+                            group.ssrcs[0] + "\"])");
1541 1540
                     if (!groupNode || !groupNode.length) {
1542 1541
                         desc.append("<ssrc-group semantics=\"" +
1543
-                            group.group.semantics +
1542
+                            group.semantics +
1544 1543
                             "\" xmlns=\"urn:xmpp:jingle:apps:rtp:ssma:0\">" +
1545 1544
                             "<source ssrc=\"" +
1546
-                                group.group.ssrcs.split(" ")
1547
-                                    .join("\"/><source ssrc=\"") + "\"/>" +
1545
+                                group.ssrcs.join("\"/><source ssrc=\"") +
1546
+                                "\"/>" +
1548 1547
                             "</ssrc-group>");
1549 1548
                     }
1550 1549
                 });

+ 82
- 136
modules/xmpp/RtxModifier.js 查看文件

@@ -1,116 +1,62 @@
1
+/* global __filename */
2
+
1 3
 import { getLogger } from "jitsi-meet-logger";
2
-const logger = getLogger(__filename);
3
-import * as transform from 'sdp-transform';
4
+import { parseSecondarySSRC, SdpTransformWrap  } from './SdpTransformUtil';
4 5
 import * as SDPUtil from "./SDPUtil";
5 6
 
6
-/**
7
- * Begin helper functions
8
- */
9
-/**
10
- * Given a videoMLine, returns a list of the video
11
- *  ssrcs (those used to actually send video, not
12
- *  any associated secondary streams)
13
- * @param {object} videoMLine media line object from transform.parse
14
- * @returns {list<string>} list of primary video ssrcs
15
- */
16
-function getPrimaryVideoSsrcs (videoMLine) {
17
-    let videoSsrcs = videoMLine.ssrcs
18
-        .map(ssrcInfo => ssrcInfo.id)
19
-        .filter((ssrc, index, array) => array.indexOf(ssrc) === index);
20
-
21
-    if (videoMLine.ssrcGroups) {
22
-        videoMLine.ssrcGroups.forEach((ssrcGroupInfo) => {
23
-            // Right now, FID groups are the only ones we parse to 
24
-            //  disqualify streams.  If/when others arise we'll
25
-            //  need to add support for them here
26
-            if (ssrcGroupInfo.semantics === "FID") {
27
-                // secondary FID streams should be filtered out
28
-                let secondarySsrc = ssrcGroupInfo.ssrcs.split(" ")[1];
29
-                videoSsrcs.splice(
30
-                  videoSsrcs.indexOf(parseInt(secondarySsrc)), 1);
31
-            }
32
-        });
33
-    }
34
-    return videoSsrcs;
35
-}
7
+const logger = getLogger(__filename);
36 8
 
37 9
 /**
38
- * Given a video mline (as parsed from transform.parse),
39
- *  and a primary ssrc, return the corresponding rtx ssrc
40
- *  (if there is one) for that video ssrc
41
- * @param {object} videoMLine the video MLine from which to extract the
42
- *  rtx video ssrc
43
- * @param {number} primarySsrc the video ssrc for which to find the
44
- *  corresponding rtx ssrc
45
- * @returns {number} the rtx ssrc (or undefined if there isn't one)
10
+ * Begin helper functions
46 11
  */
47
-function getRtxSsrc (videoMLine, primarySsrc) {
48
-    if (videoMLine.ssrcGroups) {
49
-        let fidGroup = videoMLine.ssrcGroups.find(group => {
50
-            if (group.semantics === "FID") {
51
-                let groupPrimarySsrc = SDPUtil.parseGroupSsrcs(group)[0];
52
-                return groupPrimarySsrc === primarySsrc;
53
-            }
54
-        });
55
-        if (fidGroup) {
56
-          return SDPUtil.parseGroupSsrcs(fidGroup)[1];
57
-        }
58
-    }
59
-}
60
-
61 12
 /**
62 13
  * Updates or inserts the appropriate rtx information for primarySsrc with
63 14
  *  the given rtxSsrc.  If no rtx ssrc for primarySsrc currently exists, it will
64 15
  *  add the appropriate ssrc and ssrc group lines.  If primarySsrc already has
65 16
  *  an rtx ssrc, the appropriate ssrc and group lines will be updated
66
- * @param {object} videoMLine video mline object that will be updated (in place)
67
- * @param {object} primarySsrcInfo the info (ssrc, msid & cname) for the 
17
+ * @param {MLineWrap} mLine
18
+ * @param {object} primarySsrcInfo the info (ssrc, msid & cname) for the
68 19
  *  primary ssrc
69 20
  * @param {number} rtxSsrc the rtx ssrc to associate with the primary ssrc
70 21
  */
71
-function updateAssociatedRtxStream (videoMLine, primarySsrcInfo, rtxSsrc) {
72
-    logger.debug("Updating mline to associate " + rtxSsrc + 
73
-        " rtx ssrc with primary stream ", primarySsrcInfo.id);
74
-    let primarySsrc = primarySsrcInfo.id;
75
-    let primarySsrcMsid = primarySsrcInfo.msid;
76
-    let primarySsrcCname = primarySsrcInfo.cname;
22
+function updateAssociatedRtxStream (mLine, primarySsrcInfo, rtxSsrc) {
23
+    logger.debug(
24
+        `Updating mline to associate ${rtxSsrc}` +
25
+        `rtx ssrc with primary stream, ${primarySsrcInfo.id}`);
26
+    const primarySsrc = primarySsrcInfo.id;
27
+    const primarySsrcMsid = primarySsrcInfo.msid;
28
+    const primarySsrcCname = primarySsrcInfo.cname;
77 29
 
78
-    let previousAssociatedRtxStream = 
79
-        getRtxSsrc (videoMLine, primarySsrc);
80
-    if (previousAssociatedRtxStream === rtxSsrc) {
81
-        logger.debug(rtxSsrc + " was already associated with " +
82
-            primarySsrc);
30
+    const previousRtxSSRC = mLine.getRtxSSRC(primarySsrc);
31
+    if (previousRtxSSRC === rtxSsrc) {
32
+        logger.debug(`${rtxSsrc} was already associated with ${primarySsrc}`);
83 33
         return;
84 34
     }
85
-    if (previousAssociatedRtxStream) {
86
-        logger.debug(primarySsrc + " was previously assocaited with rtx " +
87
-            previousAssociatedRtxStream + ", removing all references to it");
35
+    if (previousRtxSSRC) {
36
+        logger.debug(
37
+            `${primarySsrc} was previously associated with rtx` +
38
+            `${previousRtxSSRC}, removing all references to it`);
39
+
88 40
         // Stream already had an rtx ssrc that is different than the one given,
89 41
         //  remove all trace of the old one
90
-        videoMLine.ssrcs = videoMLine.ssrcs
91
-            .filter(ssrcInfo => ssrcInfo.id !== previousAssociatedRtxStream);
92
-        logger.debug("groups before filtering for " + 
93
-            previousAssociatedRtxStream);
94
-        logger.debug(JSON.stringify(videoMLine.ssrcGroups));
95
-        videoMLine.ssrcGroups = videoMLine.ssrcGroups
96
-            .filter(groupInfo => {
97
-                return groupInfo
98
-                    .ssrcs
99
-                    .indexOf(previousAssociatedRtxStream + "") === -1;
100
-            });
42
+        mLine.removeSSRC(previousRtxSSRC);
43
+
44
+        logger.debug(`groups before filtering for ${previousRtxSSRC}`);
45
+        logger.debug(mLine.dumpSSRCGroups());
46
+
47
+        mLine.removeGroupsWithSSRC(previousRtxSSRC);
101 48
     }
102
-    videoMLine.ssrcs.push({
49
+    mLine.addSSRCAttribute({
103 50
         id: rtxSsrc,
104 51
         attribute: "cname",
105 52
         value: primarySsrcCname
106 53
     });
107
-    videoMLine.ssrcs.push({
54
+    mLine.addSSRCAttribute({
108 55
         id: rtxSsrc,
109 56
         attribute: "msid",
110 57
         value: primarySsrcMsid
111 58
     });
112
-    videoMLine.ssrcGroups = videoMLine.ssrcGroups || [];
113
-    videoMLine.ssrcGroups.push({
59
+    mLine.addSSRCGroup({
114 60
         semantics: "FID",
115 61
         ssrcs: primarySsrc + " " + rtxSsrc
116 62
     });
@@ -163,62 +109,65 @@ export default class RtxModifier {
163 109
      * @param {string} sdpStr sdp in raw string format
164 110
      */
165 111
     modifyRtxSsrcs (sdpStr) {
166
-        let parsedSdp = transform.parse(sdpStr);
167
-        let videoMLine = 
168
-            parsedSdp.media.find(mLine => mLine.type === "video");
169
-        if (videoMLine.direction === "inactive" ||
170
-                videoMLine.direction === "recvonly") {
112
+        const sdpTransformer = new SdpTransformWrap(sdpStr);
113
+        const videoMLine = sdpTransformer.selectMedia("video");
114
+        if (!videoMLine) {
115
+            logger.error(`No 'video' media found in the sdp: ${sdpStr}`);
116
+            return sdpStr;
117
+        }
118
+        if (videoMLine.direction === "inactive"
119
+                || videoMLine.direction === "recvonly") {
171 120
             logger.debug("RtxModifier doing nothing, video " +
172 121
                 "m line is inactive or recvonly");
173 122
             return sdpStr;
174 123
         }
175
-        if (!videoMLine.ssrcs) {
124
+        if (videoMLine.getSSRCCount() < 1) {
176 125
           logger.debug("RtxModifier doing nothing, no video ssrcs present");
177 126
           return sdpStr;
178 127
         }
179 128
         logger.debug("Current ssrc mapping: ", this.correspondingRtxSsrcs);
180
-        let primaryVideoSsrcs = getPrimaryVideoSsrcs(videoMLine);
181
-        logger.debug("Parsed primary video ssrcs ", primaryVideoSsrcs, " " +
182
-            "making sure all have rtx streams");
183
-        primaryVideoSsrcs.forEach(ssrc => {
184
-            let msid = SDPUtil.getSsrcAttribute(videoMLine, ssrc, "msid");
185
-            let cname = SDPUtil.getSsrcAttribute(videoMLine, ssrc, "cname");
129
+        const primaryVideoSsrcs = videoMLine.getPrimaryVideoSSRCs();
130
+        logger.debug("Parsed primary video ssrcs ", primaryVideoSsrcs,
131
+            " making sure all have rtx streams");
132
+        for (const ssrc of primaryVideoSsrcs) {
133
+            let msid = videoMLine.getSSRCAttrValue(ssrc, "msid");
134
+            let cname = videoMLine.getSSRCAttrValue(ssrc, "cname");
186 135
             let correspondingRtxSsrc = this.correspondingRtxSsrcs.get(ssrc);
187 136
             if (correspondingRtxSsrc) {
188
-                logger.debug("Already have an associated rtx ssrc for " +
189
-                    " video ssrc " + ssrc + ": " + 
190
-                    correspondingRtxSsrc);
137
+                logger.debug(
138
+                    `Already have an associated rtx ssrc for` +
139
+                    `video ssrc ${ssrc}: ${correspondingRtxSsrc}`);
191 140
             } else {
192
-                logger.debug("No previously associated rtx ssrc for " +
193
-                    " video ssrc " + ssrc);
141
+                logger.debug(
142
+                    `No previously associated rtx ssrc for video ssrc ${ssrc}`);
194 143
                 // If there's one in the sdp already for it, we'll just set
195 144
                 //  that as the corresponding one
196
-                let previousAssociatedRtxStream = 
197
-                    getRtxSsrc (videoMLine, ssrc);
145
+                let previousAssociatedRtxStream = videoMLine.getRtxSSRC(ssrc);
198 146
                 if (previousAssociatedRtxStream) {
199
-                    logger.debug("Rtx stream " + previousAssociatedRtxStream + 
200
-                        " already existed in the sdp as an rtx stream for " +
201
-                        ssrc);
147
+                    logger.debug(
148
+                        `Rtx stream ${previousAssociatedRtxStream} ` +
149
+                        `already existed in the sdp as an rtx stream for ` +
150
+                        `${ssrc}`);
202 151
                     correspondingRtxSsrc = previousAssociatedRtxStream;
203 152
                 } else {
204 153
                     correspondingRtxSsrc = SDPUtil.generateSsrc();
205
-                    logger.debug("Generated rtx ssrc " + correspondingRtxSsrc + 
206
-                        " for ssrc " + ssrc);
154
+                    logger.debug(`Generated rtx ssrc ${correspondingRtxSsrc} ` +
155
+                                 `for ssrc ${ssrc}`);
207 156
                 }
208
-                logger.debug("Caching rtx ssrc " + correspondingRtxSsrc + 
209
-                    " for video ssrc " + ssrc);
157
+                logger.debug(`Caching rtx ssrc ${correspondingRtxSsrc} ` +
158
+                             `for video ssrc ${ssrc}`);
210 159
                 this.correspondingRtxSsrcs.set(ssrc, correspondingRtxSsrc);
211 160
             }
212 161
             updateAssociatedRtxStream(
213
-                videoMLine, 
162
+                videoMLine,
214 163
                 {
215 164
                     id: ssrc,
216 165
                     cname: cname,
217 166
                     msid: msid
218 167
                 },
219 168
                 correspondingRtxSsrc);
220
-        });
221
-        return transform.write(parsedSdp);
169
+        }
170
+        return sdpTransformer.toRawSDP();
222 171
     }
223 172
 
224 173
     /**
@@ -227,39 +176,36 @@ export default class RtxModifier {
227 176
      * @returns {string} sdp string with all rtx streams stripped
228 177
      */
229 178
     stripRtx (sdpStr) {
230
-        const parsedSdp = transform.parse(sdpStr);
231
-        const videoMLine = 
232
-            parsedSdp.media.find(mLine => mLine.type === "video");
233
-        if (videoMLine.direction === "inactive" ||
234
-                videoMLine.direction === "recvonly") {
179
+        const sdpTransformer = new SdpTransformWrap(sdpStr);
180
+        const videoMLine = sdpTransformer.selectMedia("video");
181
+        if (!videoMLine) {
182
+            logger.error(`No 'video' media found in the sdp: ${sdpStr}`);
183
+            return sdpStr;
184
+        }
185
+        if (videoMLine.direction === "inactive"
186
+                || videoMLine.direction === "recvonly") {
235 187
             logger.debug("RtxModifier doing nothing, video " +
236 188
                 "m line is inactive or recvonly");
237 189
             return sdpStr;
238 190
         }
239
-        if (!videoMLine.ssrcs) {
191
+        if (videoMLine.getSSRCCount() < 1) {
240 192
           logger.debug("RtxModifier doing nothing, no video ssrcs present");
241 193
           return sdpStr;
242 194
         }
243
-        if (!videoMLine.ssrcGroups) {
244
-          logger.debug("RtxModifier doing nothing, " + 
195
+        if (!videoMLine.containsAnySSRCGroups()) {
196
+          logger.debug("RtxModifier doing nothing, " +
245 197
               "no video ssrcGroups present");
246 198
           return sdpStr;
247 199
         }
248
-        const fidGroups = videoMLine.ssrcGroups
249
-            .filter(group => group.semantics === "FID");
200
+        const fidGroups = videoMLine.findGroups("FID");
250 201
         // Remove the fid groups from the mline
251
-        videoMLine.ssrcGroups = videoMLine.ssrcGroups
252
-            .filter(group => group.semantics !== "FID");
202
+        videoMLine.removeGroupsBySemantics("FID");
253 203
         // Get the rtx ssrcs and remove them from the mline
254
-        const ssrcsToRemove = [];
255
-        fidGroups.forEach(fidGroup => {
256
-            const groupSsrcs = SDPUtil.parseGroupSsrcs(fidGroup);
257
-            const rtxSsrc = groupSsrcs[1];
258
-            ssrcsToRemove.push(rtxSsrc);
259
-        });
260
-        videoMLine.ssrcs = videoMLine.ssrcs
261
-            .filter(line => ssrcsToRemove.indexOf(line.id) === -1);
262
-        
263
-        return transform.write(parsedSdp);
204
+        for (const fidGroup of fidGroups) {
205
+            const rtxSsrc = parseSecondarySSRC(fidGroup);
206
+            videoMLine.removeSSRC(rtxSsrc);
207
+        }
208
+
209
+        return sdpTransformer.toRawSDP();
264 210
     }
265 211
 }

+ 39
- 77
modules/xmpp/SdpConsistency.js 查看文件

@@ -1,51 +1,11 @@
1
-import { getLogger } from "jitsi-meet-logger";
2
-const logger = getLogger(__filename);
3
-import * as transform from 'sdp-transform';
4
-import * as SDPUtil from "./SDPUtil";
1
+/* global __filename */
5 2
 
6
-/**
7
- * Begin helper functions
8
- */
9
-/**
10
- * Given a video mline (as parsed from transform.parse),
11
- *  return the single primary video ssrcs
12
- * @param {object} videoMLine the video MLine from which to extract the
13
- *  primary video ssrc
14
- * @returns {number} the primary video ssrc
15
- */
16
-function getPrimarySsrc (videoMLine) {
17
-    if (!videoMLine.ssrcs) {
18
-        return;
19
-    }
20
-    let numSsrcs = videoMLine.ssrcs
21
-        .map(ssrcInfo => ssrcInfo.id)
22
-        .filter((ssrc, index, array) => array.indexOf(ssrc) === index)
23
-        .length;
24
-    if (numSsrcs === 1) {
25
-        return videoMLine.ssrcs[0].id;
26
-    } else {
27
-        let findGroup = (mLine, groupName) => {
28
-            return mLine
29
-                .ssrcGroups
30
-                .filter(group => group.semantics === groupName)[0];
31
-        };
32
-        // Look for a SIM or FID group
33
-        if (videoMLine.ssrcGroups) {
34
-            let simGroup = findGroup(videoMLine, "SIM");
35
-            if (simGroup) {
36
-                return SDPUtil.parseGroupSsrcs(simGroup)[0];
37
-            }
38
-            let fidGroup = findGroup(videoMLine, "FID");
39
-            if (fidGroup) {
40
-                return SDPUtil.parseGroupSsrcs(fidGroup)[0];
41
-            }
42
-        }
43
-    }
44
-}
3
+import { getLogger } from "jitsi-meet-logger";
4
+import { parsePrimarySSRC,
5
+         parseSecondarySSRC,
6
+         SdpTransformWrap } from './SdpTransformUtil';
45 7
 
46
-/**
47
- * End helper functions
48
- */
8
+const logger = getLogger(__filename);
49 9
 
50 10
 /**
51 11
  * Handles the work of keeping video ssrcs consistent across multiple
@@ -76,8 +36,12 @@ export default class SdpConsistency {
76 36
      *  makeVideoPrimarySsrcsConsistent
77 37
      * @param {number} primarySsrc the primarySsrc to be used
78 38
      *  in future calls to makeVideoPrimarySsrcsConsistent
39
+     * @throws Error if <tt>primarySsrc</tt> is not a number
79 40
      */
80 41
     setPrimarySsrc (primarySsrc) {
42
+        if (typeof primarySsrc !== 'number') {
43
+            throw new Error("Primary SSRC must be a number!");
44
+        }
81 45
         this.cachedPrimarySsrc = primarySsrc;
82 46
     }
83 47
 
@@ -93,61 +57,59 @@ export default class SdpConsistency {
93 57
      *  with ssrcs consistent with this class' cache
94 58
      */
95 59
     makeVideoPrimarySsrcsConsistent (sdpStr) {
96
-        let parsedSdp = transform.parse(sdpStr);
97
-        let videoMLine =
98
-            parsedSdp.media.find(mLine => mLine.type === "video");
60
+        const sdpTransformer = new SdpTransformWrap(sdpStr);
61
+        const videoMLine = sdpTransformer.selectMedia("video");
62
+        if (!videoMLine) {
63
+            logger.error(`No 'video' media found in the sdp: ${sdpStr}`);
64
+            return sdpStr;
65
+        }
99 66
         if (videoMLine.direction === "inactive") {
100
-            logger.info("Sdp-consistency doing nothing, " +
101
-                "video mline is inactive");
67
+            logger.info(
68
+                "Sdp-consistency doing nothing, video mline is inactive");
102 69
             return sdpStr;
103 70
         }
104 71
         if (videoMLine.direction === "recvonly") {
105 72
             // If the mline is recvonly, we'll add the primary
106 73
             //  ssrc as a recvonly ssrc
107
-            videoMLine.ssrcs = videoMLine.ssrcs || [];
108 74
             if (this.cachedPrimarySsrc) {
109
-                videoMLine.ssrcs.push({
75
+                videoMLine.addSSRCAttribute({
110 76
                     id: this.cachedPrimarySsrc,
111 77
                     attribute: "cname",
112
-                    value: "recvonly-" + this.cachedPrimarySsrc
78
+                    value: `recvonly-${this.cachedPrimarySsrc}`
113 79
                 });
114 80
             } else {
115 81
                 logger.error("No SSRC found for the recvonly video stream!");
116 82
             }
117 83
         } else {
118
-            let newPrimarySsrc = getPrimarySsrc(videoMLine);
84
+            let newPrimarySsrc = videoMLine.getPrimaryVideoSsrc();
119 85
             if (!newPrimarySsrc) {
120 86
                 logger.info("Sdp-consistency couldn't parse new primary ssrc");
121 87
                 return sdpStr;
122 88
             }
123 89
             if (!this.cachedPrimarySsrc) {
124 90
                 this.cachedPrimarySsrc = newPrimarySsrc;
125
-                logger.info("Sdp-consistency caching primary ssrc " + 
126
-                    this.cachedPrimarySsrc);
91
+                logger.info(
92
+                    "Sdp-consistency caching primary ssrc "
93
+                    + this.cachedPrimarySsrc);
127 94
             } else {
128
-                logger.info("Sdp-consistency replacing new ssrc " + 
129
-                    newPrimarySsrc + " with cached " + this.cachedPrimarySsrc);
130
-                videoMLine.ssrcs.forEach(ssrcInfo => {
131
-                    if (ssrcInfo.id === newPrimarySsrc) {
132
-                        ssrcInfo.id = this.cachedPrimarySsrc;
133
-                    }
134
-                });
135
-                if (videoMLine.ssrcGroups) {
136
-                    videoMLine.ssrcGroups.forEach(group => {
137
-                        if (group.semantics === "FID") {
138
-                            let fidGroupSsrcs = SDPUtil.parseGroupSsrcs(group);
139
-                            let primarySsrc = fidGroupSsrcs[0];
140
-                            let rtxSsrc = fidGroupSsrcs[1];
141
-                            if (primarySsrc === newPrimarySsrc) {
142
-                                group.ssrcs = 
143
-                                    this.cachedPrimarySsrc + " " + 
144
-                                        rtxSsrc;
145
-                            }
95
+                logger.info(
96
+                    `Sdp-consistency replacing new ssrc ` +
97
+                    `${newPrimarySsrc} with cached ${this.cachedPrimarySsrc}`);
98
+                videoMLine.replaceSSRC(
99
+                    newPrimarySsrc, this.cachedPrimarySsrc);
100
+                for (const group of videoMLine.ssrcGroups) {
101
+                    if (group.semantics === "FID") {
102
+                        let primarySsrc = parsePrimarySSRC(group);
103
+                        let rtxSsrc = parseSecondarySSRC(group);
104
+                        if (primarySsrc === newPrimarySsrc) {
105
+                            group.ssrcs =
106
+                                this.cachedPrimarySsrc + " " +
107
+                                    rtxSsrc;
146 108
                         }
147
-                    });
109
+                    }
148 110
                 }
149 111
             }
150 112
         }
151
-        return transform.write(parsedSdp);
113
+        return sdpTransformer.toRawSDP();
152 114
     }
153 115
 }

+ 397
- 0
modules/xmpp/SdpTransformUtil.js 查看文件

@@ -0,0 +1,397 @@
1
+import * as transform from 'sdp-transform';
2
+
3
+/**
4
+ * Parses the primary SSRC of given SSRC group.
5
+ * @param {object} group the SSRC group object as defined by the 'sdp-transform'
6
+ * @return {Number} the primary SSRC number
7
+ */
8
+export function parsePrimarySSRC(group) {
9
+    return parseInt(group.ssrcs.split(" ")[0]);
10
+}
11
+
12
+/**
13
+ * Parses the secondary SSRC of given SSRC group.
14
+ * @param {object} group the SSRC group object as defined by the 'sdp-transform'
15
+ * @return {Number} the secondary SSRC number
16
+ */
17
+export function parseSecondarySSRC(group) {
18
+    return parseInt(group.ssrcs.split(" ")[1]);
19
+}
20
+
21
+/**
22
+ * Tells how many distinct SSRCs are contained in given media line.
23
+ * @param {Object} mLine the media line object as defined by 'sdp-transform' lib
24
+ * @return {number}
25
+ */
26
+function _getSSRCCount(mLine) {
27
+    if (!mLine.ssrcs) {
28
+        return 0;
29
+    } else {
30
+        return mLine.ssrcs
31
+            .map(ssrcInfo => ssrcInfo.id)
32
+            .filter((ssrc, index, array) => array.indexOf(ssrc) === index)
33
+            .length;
34
+    }
35
+}
36
+
37
+/**
38
+ * Utility class for SDP manipulation using the 'sdp-transform' library.
39
+ *
40
+ * Typical use usage scenario:
41
+ *
42
+ * const transformer = new SdpTransformWrap(rawSdp);
43
+ * const videoMLine = transformer.selectMedia('video);
44
+ * if (videoMLine) {
45
+ *     videoMLiner.addSSRCAttribute({
46
+ *         id: 2342343,
47
+ *         attribute: "cname",
48
+ *         value: "someCname"
49
+ *     });
50
+ *     rawSdp = transformer.toRawSdp();
51
+ * }
52
+ */
53
+export class SdpTransformWrap {
54
+
55
+    /**
56
+     * Creates new instance and parses the raw SDP into objects using
57
+     * 'sdp-transform' lib.
58
+     * @param {string} rawSDP the SDP in raw text format.
59
+     */
60
+    constructor(rawSDP) {
61
+        this.parsedSDP = transform.parse(rawSDP);
62
+    }
63
+
64
+    /**
65
+     * Selects the first media SDP of given name.
66
+     * @param {string} mediaType the name of the media e.g. 'audio', 'video',
67
+     * 'data'.
68
+     * @return {MLineWrap|null} return {@link MLineWrap} instance for the media
69
+     * line or <tt>null</tt> if not found. The object returned references
70
+     * the underlying SDP state held by this <tt>SdpTransformWrap</tt> instance
71
+     * (it's not a copy).
72
+     */
73
+    selectMedia(mediaType) {
74
+        const selectedMLine
75
+            = this.parsedSDP.media.find(mLine => mLine.type === mediaType);
76
+        return selectedMLine ? new MLineWrap(selectedMLine) : null;
77
+    }
78
+
79
+    /**
80
+     * Converts the currently stored SDP state in this instance to raw text SDP
81
+     * format.
82
+     * @return {string}
83
+     */
84
+    toRawSDP() {
85
+        return transform.write(this.parsedSDP);
86
+    }
87
+}
88
+
89
+/**
90
+ * A wrapper around 'sdp-transform' media description object which provides
91
+ * utility methods for common SDP/SSRC related operations.
92
+ */
93
+class MLineWrap {
94
+
95
+    /**
96
+     * Creates new <tt>MLineWrap</t>>
97
+     * @param {Object} mLine the media line object as defined by 'sdp-transform'
98
+     * lib.
99
+     */
100
+    constructor(mLine) {
101
+        if (!mLine) {
102
+            throw new Error("mLine is undefined");
103
+        }
104
+
105
+        this.mLine = mLine;
106
+    }
107
+
108
+    get _ssrcs () {
109
+        if (!this.mLine.ssrcs) {
110
+            this.mLine.ssrcs = [];
111
+        }
112
+        return this.mLine.ssrcs;
113
+    }
114
+
115
+    /**
116
+     * Returns the direction of the underlying media description.
117
+     * @return {string} the media direction name as defined in the SDP.
118
+     */
119
+    get direction() {
120
+        return this.mLine.direction;
121
+    }
122
+
123
+    /**
124
+     * Modifies the direction of the underlying media description.
125
+     * @param {string} direction the new direction to be set
126
+     */
127
+    set direction (direction) {
128
+        this.mLine.direction = direction;
129
+    }
130
+
131
+    /**
132
+     * Exposes the SSRC group array of the underlying media description object.
133
+     * @return {Array.<Object>}
134
+     */
135
+    get ssrcGroups () {
136
+        if (!this.mLine.ssrcGroups) {
137
+            this.mLine.ssrcGroups = [];
138
+        }
139
+        return this.mLine.ssrcGroups;
140
+    }
141
+
142
+    /**
143
+     * Modifies the SSRC groups array of the underlying media description
144
+     * object.
145
+     * @param {Array.<Object>} ssrcGroups
146
+     */
147
+    set ssrcGroups (ssrcGroups) {
148
+        this.mLine.ssrcGroups = ssrcGroups;
149
+    }
150
+
151
+    /**
152
+     * Checks whether the underlying media description contains given SSRC
153
+     * number.
154
+     * @param {string} ssrcNumber
155
+     * @return {boolean} <tt>true</tt> if given SSRC has been found or
156
+     * <tt>false</tt> otherwise.
157
+     */
158
+    containsSSRC(ssrcNumber) {
159
+        return !!this._ssrcs.find(
160
+            ssrcObj => { return ssrcObj.id == ssrcNumber; });
161
+    }
162
+
163
+    /**
164
+     * Obtains value from SSRC attribute.
165
+     * @param {number} ssrcNumber the SSRC number for which attribute is to be
166
+     * found
167
+     * @param {string} attrName the name of the SSRC attribute to be found.
168
+     * @return {string|undefined} the value of SSRC attribute or
169
+     * <tt>undefined</tt> if no such attribute exists.
170
+     */
171
+    getSSRCAttrValue(ssrcNumber, attrName) {
172
+        const attribute = this._ssrcs.find(
173
+            ssrcObj => ssrcObj.id == ssrcNumber
174
+            && ssrcObj.attribute === attrName);
175
+        return attribute && attribute.value;
176
+    }
177
+
178
+    /**
179
+     * Removes all attributes for given SSRC number.
180
+     * @param {number} ssrcNum the SSRC number for which all attributes will be
181
+     * removed.
182
+     */
183
+    removeSSRC(ssrcNum) {
184
+        if (!this.mLine.ssrcs || !this.mLine.ssrcs.length) {
185
+            return;
186
+        }
187
+
188
+        this.mLine.ssrcs
189
+            = this.mLine.ssrcs.filter(ssrcObj => ssrcObj.id !== ssrcNum);
190
+    }
191
+
192
+    /**
193
+     * Adds SSRC attribute
194
+     * @param {object} ssrcObj the SSRC attribute object as defined in
195
+     * the 'sdp-transform' lib.
196
+     */
197
+    addSSRCAttribute(ssrcObj) {
198
+        this._ssrcs.push(ssrcObj);
199
+    }
200
+
201
+    /**
202
+     * Finds a SSRC group matching both semantics and SSRCs in order.
203
+     * @param {string} semantics the name of the semantics
204
+     * @param {string} [ssrcs] group SSRCs as a string (like it's defined in
205
+     * SSRC group object of the 'sdp-transform' lib) e.g. "1232546 342344 25434"
206
+     * @return {object|undefined} the SSRC group object or <tt>undefined</tt> if
207
+     * not found.
208
+     */
209
+    findGroup(semantics, ssrcs) {
210
+        return this.ssrcGroups.find(
211
+            group => group.semantics === semantics
212
+                && !ssrcs || ssrcs === group.ssrcs);
213
+    }
214
+
215
+    /**
216
+     * Finds all groups matching given semantic's name.
217
+     * @param {string} semantics the name of the semantics
218
+     * @return {Array.<object>} an array of SSRC group objects as defined by
219
+     * the 'sdp-transform' lib.
220
+     */
221
+    findGroups(semantics) {
222
+        return this.ssrcGroups.filter(
223
+            group => group.semantics === semantics);
224
+    }
225
+
226
+    /**
227
+     * Finds all groups matching given semantic's name and group's primary SSRC.
228
+     * @param {string} semantics the name of the semantics
229
+     * @param {number} primarySSRC the primary SSRC number to be matched
230
+     * @return {Object} SSRC group object as defined by the 'sdp-transform' lib.
231
+     */
232
+    findGroupByPrimarySSRC(semantics, primarySSRC) {
233
+        return this.ssrcGroups.find(
234
+            group => group.semantics === semantics
235
+                && parsePrimarySSRC(group) === primarySSRC);
236
+    }
237
+
238
+    /**
239
+     * Gets the SSRC count for the underlying media description.
240
+     * @return {number}
241
+     */
242
+    getSSRCCount() {
243
+        return _getSSRCCount(this.mLine);
244
+    }
245
+
246
+    /**
247
+     * Checks whether the underlying media description contains any SSRC groups.
248
+     * @return {boolean} <tt>true</tt> if there are any SSRC groups or
249
+     * <tt>false</tt> otherwise.
250
+     */
251
+    containsAnySSRCGroups() {
252
+        return !!this.mLine.ssrcGroups;
253
+    }
254
+
255
+    /**
256
+     * Finds the primary video SSRC.
257
+     * @returns {number|undefined} the primary video ssrc
258
+     * @throws Error if the underlying media description is not a video
259
+     */
260
+    getPrimaryVideoSsrc () {
261
+        const mediaType = this.mLine.type;
262
+
263
+        if (mediaType !== 'video') {
264
+            throw new Error(
265
+                `getPrimarySsrc doesn't work with '${mediaType}'`);
266
+        }
267
+
268
+        const numSsrcs = _getSSRCCount(this.mLine);
269
+        if (numSsrcs === 1) {
270
+            // Not using _ssrcs on purpose here
271
+            return this.mLine.ssrcs[0].id;
272
+        } else {
273
+            // Look for a SIM or FID group
274
+            if (this.mLine.ssrcGroups) {
275
+                const simGroup = this.findGroup("SIM");
276
+                if (simGroup) {
277
+                    return parsePrimarySSRC(simGroup);
278
+                }
279
+                const fidGroup = this.findGroup("FID");
280
+                if (fidGroup) {
281
+                    return parsePrimarySSRC(fidGroup);
282
+                }
283
+            }
284
+        }
285
+    }
286
+
287
+    /**
288
+     * Obtains RTX SSRC from the underlying video description (the
289
+     * secondary SSRC of the first "FID" group found)
290
+     * @param {number} primarySsrc the video ssrc for which to find the
291
+     * corresponding rtx ssrc
292
+     * @returns {number|undefined} the rtx ssrc (or undefined if there isn't
293
+     * one)
294
+     */
295
+    getRtxSSRC (primarySsrc) {
296
+        const fidGroup = this.findGroupByPrimarySSRC("FID", primarySsrc);
297
+        return fidGroup && parseSecondarySSRC(fidGroup);
298
+    }
299
+
300
+    /**
301
+     * Obtains all SSRCs contained in the underlying media description.
302
+     * @return {Array.<number>} an array with all SSRC as numbers.
303
+     */
304
+    getSSRCs () {
305
+        return this._ssrcs
306
+            .map(ssrcInfo => ssrcInfo.id)
307
+            .filter((ssrc, index, array) => array.indexOf(ssrc) === index);
308
+    }
309
+
310
+    /**
311
+     * Obtains primary video SSRCs.
312
+     * @return {Array.<number>} an array of all primary video SSRCs as numbers.
313
+     * @throws Error if the wrapped media description is not a video.
314
+     */
315
+    getPrimaryVideoSSRCs () {
316
+        const mediaType = this.mLine.type;
317
+
318
+        if (mediaType !== 'video') {
319
+            throw new Error(
320
+                `getPrimaryVideoSSRCs doesn't work with ${mediaType}`);
321
+        }
322
+
323
+        const videoSSRCs = this.getSSRCs();
324
+
325
+        for (const ssrcGroupInfo of this.ssrcGroups) {
326
+            // Right now, FID groups are the only ones we parse to
327
+            // disqualify streams.  If/when others arise we'll
328
+            // need to add support for them here
329
+            if (ssrcGroupInfo.semantics === "FID") {
330
+                // secondary FID streams should be filtered out
331
+                const secondarySsrc = parseSecondarySSRC(ssrcGroupInfo);
332
+                videoSSRCs.splice(
333
+                    videoSSRCs.indexOf(secondarySsrc), 1);
334
+            }
335
+        }
336
+        return videoSSRCs;
337
+    }
338
+
339
+    /**
340
+     * Dumps all SSRC groups of this media description to JSON.
341
+     */
342
+    dumpSSRCGroups() {
343
+        return JSON.stringify(this.mLine.ssrcGroups);
344
+    }
345
+
346
+    /**
347
+     * Removes all SSRC groups which contain given SSRC number at any position.
348
+     * @param {number} ssrc the SSRC for which all matching groups are to be
349
+     * removed.
350
+     */
351
+    removeGroupsWithSSRC(ssrc) {
352
+        if (!this.mLine.ssrcGroups) {
353
+            return;
354
+        }
355
+
356
+        this.mLine.ssrcGroups = this.mLine.ssrcGroups
357
+            .filter(groupInfo => groupInfo.ssrcs.indexOf(ssrc + "") === -1);
358
+    }
359
+
360
+    /**
361
+     * Removes groups that match given semantics.
362
+     * @param {string} semantics e.g. "SIM" or "FID"
363
+     */
364
+    removeGroupsBySemantics(semantics) {
365
+        if (!this.mLine.ssrcGroups) {
366
+            return;
367
+        }
368
+
369
+        this.mLine.ssrcGroups
370
+            = this.mLine.ssrcGroups
371
+                  .filter(groupInfo => groupInfo.semantics !== semantics);
372
+    }
373
+
374
+    /**
375
+     * Replaces SSRC (does not affect SSRC groups, but only attributes).
376
+     * @param {number} oldSSRC the old SSRC number
377
+     * @param {number} newSSRC the new SSRC number
378
+     */
379
+    replaceSSRC(oldSSRC, newSSRC) {
380
+        if (this.mLine.ssrcs) {
381
+            this.mLine.ssrcs.forEach(ssrcInfo => {
382
+                if (ssrcInfo.id === oldSSRC) {
383
+                    ssrcInfo.id = newSSRC;
384
+                }
385
+            });
386
+        }
387
+    }
388
+
389
+    /**
390
+     * Adds given SSRC group to this media description.
391
+     * @param {object} group the SSRC group object as defined by
392
+     * the 'sdp-transform' lib.
393
+     */
394
+    addSSRCGroup(group) {
395
+        this.ssrcGroups.push(group);
396
+    }
397
+}

Loading…
取消
儲存