浏览代码

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
tags/v0.0.2
Paweł Domas 8 年前
父节点
当前提交
d0ce2d6866

+ 6
- 4
JitsiConference.js 查看文件

527
         }
527
         }
528
         // Set up the ssrcHandler for the new track before we add it at the lower levels
528
         // Set up the ssrcHandler for the new track before we add it at the lower levels
529
         newTrack.ssrcHandler = function (conference, ssrcMap) {
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
                 conference.rtc.removeListener(
533
                 conference.rtc.removeListener(
533
                     RTCEvents.SENDRECV_STREAMS_CHANGED,
534
                     RTCEvents.SENDRECV_STREAMS_CHANGED,
534
                     this.ssrcHandler);
535
                     this.ssrcHandler);
1039
     this.rtc.initializeDataChannels(jingleSession.peerconnection);
1040
     this.rtc.initializeDataChannels(jingleSession.peerconnection);
1040
     // Add local Tracks to the ChatRoom
1041
     // Add local Tracks to the ChatRoom
1041
     this.getLocalTracks().forEach(function(localTrack) {
1042
     this.getLocalTracks().forEach(function(localTrack) {
1042
-        var ssrcInfo = null;
1043
+        let ssrcInfo = null;
1043
         /**
1044
         /**
1044
          * We don't do this for Firefox because, on Firefox, we keep the
1045
          * We don't do this for Firefox because, on Firefox, we keep the
1045
          *  stream in the peer connection and just set 'enabled' on the
1046
          *  stream in the peer connection and just set 'enabled' on the
1070
             ssrcInfo = {
1071
             ssrcInfo = {
1071
                 mtype: localTrack.getType(),
1072
                 mtype: localTrack.getType(),
1072
                 type: "addMuted",
1073
                 type: "addMuted",
1073
-                ssrc: localTrack.ssrc,
1074
+                ssrcs: localTrack.ssrc.ssrcs,
1075
+                groups: localTrack.ssrc.groups,
1074
                 msid: localTrack.initialMSID
1076
                 msid: localTrack.initialMSID
1075
             };
1077
             };
1076
         }
1078
         }

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

370
         return Promise.resolve();
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
             resolve,
376
             resolve,
379
             (error) => reject(new Error(error)),
377
             (error) => reject(new Error(error)),
380
             {
378
             {
381
-                mtype: self.type,
379
+                mtype: this.type,
382
                 type: "unmute",
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
         {
405
         {
407
             mtype: this.type,
406
             mtype: this.type,
408
             type: "mute",
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
         return Promise.resolve();
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
                 ? 'setAudioMute'
428
                 ? 'setAudioMute'
431
                 : 'setVideoMute'](mute, resolve);
429
                 : 'setVideoMute'](mute, resolve);
432
     });
430
     });
521
  */
519
  */
522
 JitsiLocalTrack.prototype.getSSRC = function () {
520
 JitsiLocalTrack.prototype.getSSRC = function () {
523
     if(this.ssrc && this.ssrc.groups && this.ssrc.groups.length)
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
     else if(this.ssrc && this.ssrc.ssrcs && this.ssrc.ssrcs.length)
523
     else if(this.ssrc && this.ssrc.ssrcs && this.ssrc.ssrcs.length)
526
         return this.ssrc.ssrcs[0];
524
         return this.ssrc.ssrcs[0];
527
     else
525
     else

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

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
 function extractSSRCMap(desc) {
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
     if (typeof desc !== 'object' || desc === null ||
428
     if (typeof desc !== 'object' || desc === null ||
407
         typeof desc.sdp !== 'string') {
429
         typeof desc.sdp !== 'string') {
408
         logger.warn('An empty description was passed as an argument.');
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
                 if (typeof group.semantics !== 'undefined' &&
447
                 if (typeof group.semantics !== 'undefined' &&
430
                     typeof group.ssrcs !== 'undefined') {
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
     if (stream)
609
     if (stream)
569
         this.peerconnection.addStream(stream);
610
         this.peerconnection.addStream(stream);
570
     if (ssrcInfo && ssrcInfo.type === "addMuted") {
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
         if (simGroup) {
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
         if (fidGroups) {
621
         if (fidGroups) {
585
             const rtxSsrcMapping = new Map();
622
             const rtxSsrcMapping = new Map();
586
             fidGroups.forEach(fidGroup => {
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
                 rtxSsrcMapping.set(primarySsrc, rtxSsrc);
626
                 rtxSsrcMapping.set(primarySsrc, rtxSsrc);
592
             });
627
             });
593
             this.rtxModifier.setSsrcCache(rtxSsrcMapping);
628
             this.rtxModifier.setSsrcCache(rtxSsrcMapping);
898
             ssrcInfo.ssrcs.push(SDPUtil.generateSsrc());
933
             ssrcInfo.ssrcs.push(SDPUtil.generateSsrc());
899
         }
934
         }
900
         ssrcInfo.groups.push({
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
     } else {
939
     } else {
905
-        ssrcInfo = {ssrcs: [SDPUtil.generateSsrc()], groups: []};
940
+        ssrcInfo = {
941
+            ssrcs: [SDPUtil.generateSsrc()],
942
+            groups: []
943
+        };
906
     }
944
     }
907
     if (!this.options.disableRtx && !RTCBrowserType.isFirefox()) {
945
     if (!this.options.disableRtx && !RTCBrowserType.isFirefox()) {
908
         // Specifically use a for loop here because we'll
946
         // Specifically use a for loop here because we'll
915
             const rtxSsrc = SDPUtil.generateSsrc();
953
             const rtxSsrc = SDPUtil.generateSsrc();
916
             ssrcInfo.ssrcs.push(rtxSsrc);
954
             ssrcInfo.ssrcs.push(rtxSsrc);
917
             ssrcInfo.groups.push({
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
                     ssrcObj.mtype + "\"]>description");
1431
                     ssrcObj.mtype + "\"]>description");
1432
                 if (!desc || !desc.length)
1432
                 if (!desc || !desc.length)
1433
                     return;
1433
                     return;
1434
-                ssrcObj.ssrc.ssrcs.forEach(function (ssrc) {
1434
+                ssrcObj.ssrcs.forEach(function (ssrc) {
1435
                     const sourceNode = desc.find(">source[ssrc=\"" +
1435
                     const sourceNode = desc.find(">source[ssrc=\"" +
1436
                         ssrc + "\"]");
1436
                         ssrc + "\"]");
1437
                     sourceNode.remove();
1437
                     sourceNode.remove();
1438
                 });
1438
                 });
1439
-                ssrcObj.ssrc.groups.forEach(function (group) {
1439
+                ssrcObj.groups.forEach(function (group) {
1440
                     const groupNode = desc.find(">ssrc-group[semantics=\"" +
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
                     groupNode.remove();
1443
                     groupNode.remove();
1445
                 });
1444
                 });
1446
             });
1445
             });
1454
                     = JingleSessionPC.createDescriptionNode(
1453
                     = JingleSessionPC.createDescriptionNode(
1455
                         jingle, ssrcObj.mtype);
1454
                         jingle, ssrcObj.mtype);
1456
                 const cname = Math.random().toString(36).substring(2);
1455
                 const cname = Math.random().toString(36).substring(2);
1457
-                ssrcObj.ssrc.ssrcs.forEach(function (ssrc) {
1456
+                ssrcObj.ssrcs.forEach(function (ssrc) {
1458
                     const sourceNode
1457
                     const sourceNode
1459
                         = desc.find(">source[ssrc=\"" + ssrc + "\"]");
1458
                         = desc.find(">source[ssrc=\"" + ssrc + "\"]");
1460
                     sourceNode.remove();
1459
                     sourceNode.remove();
1468
                         "</source>";
1467
                         "</source>";
1469
                     desc.append(sourceXML);
1468
                     desc.append(sourceXML);
1470
                 });
1469
                 });
1471
-                ssrcObj.ssrc.groups.forEach(function (group) {
1470
+                ssrcObj.groups.forEach(function (group) {
1472
                     const groupNode
1471
                     const groupNode
1473
                         = desc.find(">ssrc-group[semantics=\"" +
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
                     groupNode.remove();
1475
                     groupNode.remove();
1477
                     desc.append(
1476
                     desc.append(
1478
-                        "<ssrc-group semantics=\"" + group.group.semantics +
1477
+                        "<ssrc-group semantics=\"" + group.semantics +
1479
                         "\" xmlns=\"urn:xmpp:jingle:apps:rtp:ssma:0\">" +
1478
                         "\" xmlns=\"urn:xmpp:jingle:apps:rtp:ssma:0\">" +
1480
                         "<source ssrc=\"" +
1479
                         "<source ssrc=\"" +
1481
-                            group.group.ssrcs.split(" ")
1482
-                                .join("\"/>" + "<source ssrc=\"") + "\"/>" +
1480
+                            group.ssrcs.join("\"/>" + "<source ssrc=\"") +
1481
+                            "\"/>" +
1483
                         "</ssrc-group>");
1482
                         "</ssrc-group>");
1484
                 });
1483
                 });
1485
             });
1484
             });
1497
         this.modifiedSSRCs["mute"] = [];
1496
         this.modifiedSSRCs["mute"] = [];
1498
         if (ssrcs && ssrcs.length)
1497
         if (ssrcs && ssrcs.length)
1499
             ssrcs.forEach(function (ssrcObj) {
1498
             ssrcs.forEach(function (ssrcObj) {
1500
-                ssrcObj.ssrc.ssrcs.forEach(function (ssrc) {
1499
+                ssrcObj.ssrcs.forEach(function (ssrc) {
1501
                     const sourceNode
1500
                     const sourceNode
1502
                         = $(jingle.tree()).find(">jingle>content[name=\"" +
1501
                         = $(jingle.tree()).find(">jingle>content[name=\"" +
1503
                             ssrcObj.mtype + "\"]>description>source[ssrc=\"" +
1502
                             ssrcObj.mtype + "\"]>description>source[ssrc=\"" +
1504
                             ssrc + "\"]");
1503
                             ssrc + "\"]");
1505
                     sourceNode.remove();
1504
                     sourceNode.remove();
1506
                 });
1505
                 });
1507
-                ssrcObj.ssrc.groups.forEach(function (group) {
1506
+                ssrcObj.groups.forEach(function (group) {
1508
                     const groupNode
1507
                     const groupNode
1509
                         = $(jingle.tree()).find(
1508
                         = $(jingle.tree()).find(
1510
                             ">jingle>content[name=\"" + ssrcObj.mtype +
1509
                             ">jingle>content[name=\"" + ssrcObj.mtype +
1511
                             "\"]>description>ssrc-group[semantics=\"" +
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
                     groupNode.remove();
1513
                     groupNode.remove();
1515
                 });
1514
                 });
1516
             });
1515
             });
1522
                 const desc
1521
                 const desc
1523
                     = JingleSessionPC.createDescriptionNode(
1522
                     = JingleSessionPC.createDescriptionNode(
1524
                         jingle, ssrcObj.mtype);
1523
                         jingle, ssrcObj.mtype);
1525
-                ssrcObj.ssrc.ssrcs.forEach(function (ssrc) {
1524
+                ssrcObj.ssrcs.forEach(function (ssrc) {
1526
                     const sourceNode
1525
                     const sourceNode
1527
                         = desc.find(">source[ssrc=\"" + ssrc + "\"]");
1526
                         = desc.find(">source[ssrc=\"" + ssrc + "\"]");
1528
                     if (!sourceNode || !sourceNode.length) {
1527
                     if (!sourceNode || !sourceNode.length) {
1533
                             "ssrc=\"" + ssrc + "\"></source>");
1532
                             "ssrc=\"" + ssrc + "\"></source>");
1534
                     }
1533
                     }
1535
                 });
1534
                 });
1536
-                ssrcObj.ssrc.groups.forEach(function (group) {
1535
+                ssrcObj.groups.forEach(function (group) {
1537
                     const groupNode
1536
                     const groupNode
1538
                         = desc.find(">ssrc-group[semantics=\"" +
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
                     if (!groupNode || !groupNode.length) {
1540
                     if (!groupNode || !groupNode.length) {
1542
                         desc.append("<ssrc-group semantics=\"" +
1541
                         desc.append("<ssrc-group semantics=\"" +
1543
-                            group.group.semantics +
1542
+                            group.semantics +
1544
                             "\" xmlns=\"urn:xmpp:jingle:apps:rtp:ssma:0\">" +
1543
                             "\" xmlns=\"urn:xmpp:jingle:apps:rtp:ssma:0\">" +
1545
                             "<source ssrc=\"" +
1544
                             "<source ssrc=\"" +
1546
-                                group.group.ssrcs.split(" ")
1547
-                                    .join("\"/><source ssrc=\"") + "\"/>" +
1545
+                                group.ssrcs.join("\"/><source ssrc=\"") +
1546
+                                "\"/>" +
1548
                             "</ssrc-group>");
1547
                             "</ssrc-group>");
1549
                     }
1548
                     }
1550
                 });
1549
                 });

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

1
+/* global __filename */
2
+
1
 import { getLogger } from "jitsi-meet-logger";
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
 import * as SDPUtil from "./SDPUtil";
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
  * Updates or inserts the appropriate rtx information for primarySsrc with
13
  * Updates or inserts the appropriate rtx information for primarySsrc with
63
  *  the given rtxSsrc.  If no rtx ssrc for primarySsrc currently exists, it will
14
  *  the given rtxSsrc.  If no rtx ssrc for primarySsrc currently exists, it will
64
  *  add the appropriate ssrc and ssrc group lines.  If primarySsrc already has
15
  *  add the appropriate ssrc and ssrc group lines.  If primarySsrc already has
65
  *  an rtx ssrc, the appropriate ssrc and group lines will be updated
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
  *  primary ssrc
19
  *  primary ssrc
69
  * @param {number} rtxSsrc the rtx ssrc to associate with the primary ssrc
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
         return;
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
         // Stream already had an rtx ssrc that is different than the one given,
40
         // Stream already had an rtx ssrc that is different than the one given,
89
         //  remove all trace of the old one
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
         id: rtxSsrc,
50
         id: rtxSsrc,
104
         attribute: "cname",
51
         attribute: "cname",
105
         value: primarySsrcCname
52
         value: primarySsrcCname
106
     });
53
     });
107
-    videoMLine.ssrcs.push({
54
+    mLine.addSSRCAttribute({
108
         id: rtxSsrc,
55
         id: rtxSsrc,
109
         attribute: "msid",
56
         attribute: "msid",
110
         value: primarySsrcMsid
57
         value: primarySsrcMsid
111
     });
58
     });
112
-    videoMLine.ssrcGroups = videoMLine.ssrcGroups || [];
113
-    videoMLine.ssrcGroups.push({
59
+    mLine.addSSRCGroup({
114
         semantics: "FID",
60
         semantics: "FID",
115
         ssrcs: primarySsrc + " " + rtxSsrc
61
         ssrcs: primarySsrc + " " + rtxSsrc
116
     });
62
     });
163
      * @param {string} sdpStr sdp in raw string format
109
      * @param {string} sdpStr sdp in raw string format
164
      */
110
      */
165
     modifyRtxSsrcs (sdpStr) {
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
             logger.debug("RtxModifier doing nothing, video " +
120
             logger.debug("RtxModifier doing nothing, video " +
172
                 "m line is inactive or recvonly");
121
                 "m line is inactive or recvonly");
173
             return sdpStr;
122
             return sdpStr;
174
         }
123
         }
175
-        if (!videoMLine.ssrcs) {
124
+        if (videoMLine.getSSRCCount() < 1) {
176
           logger.debug("RtxModifier doing nothing, no video ssrcs present");
125
           logger.debug("RtxModifier doing nothing, no video ssrcs present");
177
           return sdpStr;
126
           return sdpStr;
178
         }
127
         }
179
         logger.debug("Current ssrc mapping: ", this.correspondingRtxSsrcs);
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
             let correspondingRtxSsrc = this.correspondingRtxSsrcs.get(ssrc);
135
             let correspondingRtxSsrc = this.correspondingRtxSsrcs.get(ssrc);
187
             if (correspondingRtxSsrc) {
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
             } else {
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
                 // If there's one in the sdp already for it, we'll just set
143
                 // If there's one in the sdp already for it, we'll just set
195
                 //  that as the corresponding one
144
                 //  that as the corresponding one
196
-                let previousAssociatedRtxStream = 
197
-                    getRtxSsrc (videoMLine, ssrc);
145
+                let previousAssociatedRtxStream = videoMLine.getRtxSSRC(ssrc);
198
                 if (previousAssociatedRtxStream) {
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
                     correspondingRtxSsrc = previousAssociatedRtxStream;
151
                     correspondingRtxSsrc = previousAssociatedRtxStream;
203
                 } else {
152
                 } else {
204
                     correspondingRtxSsrc = SDPUtil.generateSsrc();
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
                 this.correspondingRtxSsrcs.set(ssrc, correspondingRtxSsrc);
159
                 this.correspondingRtxSsrcs.set(ssrc, correspondingRtxSsrc);
211
             }
160
             }
212
             updateAssociatedRtxStream(
161
             updateAssociatedRtxStream(
213
-                videoMLine, 
162
+                videoMLine,
214
                 {
163
                 {
215
                     id: ssrc,
164
                     id: ssrc,
216
                     cname: cname,
165
                     cname: cname,
217
                     msid: msid
166
                     msid: msid
218
                 },
167
                 },
219
                 correspondingRtxSsrc);
168
                 correspondingRtxSsrc);
220
-        });
221
-        return transform.write(parsedSdp);
169
+        }
170
+        return sdpTransformer.toRawSDP();
222
     }
171
     }
223
 
172
 
224
     /**
173
     /**
227
      * @returns {string} sdp string with all rtx streams stripped
176
      * @returns {string} sdp string with all rtx streams stripped
228
      */
177
      */
229
     stripRtx (sdpStr) {
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
             logger.debug("RtxModifier doing nothing, video " +
187
             logger.debug("RtxModifier doing nothing, video " +
236
                 "m line is inactive or recvonly");
188
                 "m line is inactive or recvonly");
237
             return sdpStr;
189
             return sdpStr;
238
         }
190
         }
239
-        if (!videoMLine.ssrcs) {
191
+        if (videoMLine.getSSRCCount() < 1) {
240
           logger.debug("RtxModifier doing nothing, no video ssrcs present");
192
           logger.debug("RtxModifier doing nothing, no video ssrcs present");
241
           return sdpStr;
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
               "no video ssrcGroups present");
197
               "no video ssrcGroups present");
246
           return sdpStr;
198
           return sdpStr;
247
         }
199
         }
248
-        const fidGroups = videoMLine.ssrcGroups
249
-            .filter(group => group.semantics === "FID");
200
+        const fidGroups = videoMLine.findGroups("FID");
250
         // Remove the fid groups from the mline
201
         // Remove the fid groups from the mline
251
-        videoMLine.ssrcGroups = videoMLine.ssrcGroups
252
-            .filter(group => group.semantics !== "FID");
202
+        videoMLine.removeGroupsBySemantics("FID");
253
         // Get the rtx ssrcs and remove them from the mline
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
-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
  * Handles the work of keeping video ssrcs consistent across multiple
11
  * Handles the work of keeping video ssrcs consistent across multiple
76
      *  makeVideoPrimarySsrcsConsistent
36
      *  makeVideoPrimarySsrcsConsistent
77
      * @param {number} primarySsrc the primarySsrc to be used
37
      * @param {number} primarySsrc the primarySsrc to be used
78
      *  in future calls to makeVideoPrimarySsrcsConsistent
38
      *  in future calls to makeVideoPrimarySsrcsConsistent
39
+     * @throws Error if <tt>primarySsrc</tt> is not a number
79
      */
40
      */
80
     setPrimarySsrc (primarySsrc) {
41
     setPrimarySsrc (primarySsrc) {
42
+        if (typeof primarySsrc !== 'number') {
43
+            throw new Error("Primary SSRC must be a number!");
44
+        }
81
         this.cachedPrimarySsrc = primarySsrc;
45
         this.cachedPrimarySsrc = primarySsrc;
82
     }
46
     }
83
 
47
 
93
      *  with ssrcs consistent with this class' cache
57
      *  with ssrcs consistent with this class' cache
94
      */
58
      */
95
     makeVideoPrimarySsrcsConsistent (sdpStr) {
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
         if (videoMLine.direction === "inactive") {
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
             return sdpStr;
69
             return sdpStr;
103
         }
70
         }
104
         if (videoMLine.direction === "recvonly") {
71
         if (videoMLine.direction === "recvonly") {
105
             // If the mline is recvonly, we'll add the primary
72
             // If the mline is recvonly, we'll add the primary
106
             //  ssrc as a recvonly ssrc
73
             //  ssrc as a recvonly ssrc
107
-            videoMLine.ssrcs = videoMLine.ssrcs || [];
108
             if (this.cachedPrimarySsrc) {
74
             if (this.cachedPrimarySsrc) {
109
-                videoMLine.ssrcs.push({
75
+                videoMLine.addSSRCAttribute({
110
                     id: this.cachedPrimarySsrc,
76
                     id: this.cachedPrimarySsrc,
111
                     attribute: "cname",
77
                     attribute: "cname",
112
-                    value: "recvonly-" + this.cachedPrimarySsrc
78
+                    value: `recvonly-${this.cachedPrimarySsrc}`
113
                 });
79
                 });
114
             } else {
80
             } else {
115
                 logger.error("No SSRC found for the recvonly video stream!");
81
                 logger.error("No SSRC found for the recvonly video stream!");
116
             }
82
             }
117
         } else {
83
         } else {
118
-            let newPrimarySsrc = getPrimarySsrc(videoMLine);
84
+            let newPrimarySsrc = videoMLine.getPrimaryVideoSsrc();
119
             if (!newPrimarySsrc) {
85
             if (!newPrimarySsrc) {
120
                 logger.info("Sdp-consistency couldn't parse new primary ssrc");
86
                 logger.info("Sdp-consistency couldn't parse new primary ssrc");
121
                 return sdpStr;
87
                 return sdpStr;
122
             }
88
             }
123
             if (!this.cachedPrimarySsrc) {
89
             if (!this.cachedPrimarySsrc) {
124
                 this.cachedPrimarySsrc = newPrimarySsrc;
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
             } else {
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 查看文件

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
+}

正在加载...
取消
保存