Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(multi-stream): endpoint ID compatibility #1842

Merged
merged 5 commits into from
Apr 1, 2022

Conversation

paweldomas
Copy link
Member

Jicofo will forward source name capability for
each Colibri2 Endpoint. In Colibri V1 "use source
names" is always false for now.

Based on the info on whether the client uses source
names or not, will use different signaling messages
for talking to this client. All operations under
the hood are happening on source names.

Jicofo will forward source name capability for
each Colibri2 Endpoint. In Colibri V1 "use source
names" is always false for now.

Based on the info on whether the client uses source
names or not, will use different signaling messages
for talking to this client. All operations under
the hood are happening on source names.
@codecov
Copy link

codecov bot commented Mar 25, 2022

Codecov Report

Merging #1842 (613c004) into master (0e93dcc) will decrease coverage by 0.19%.
The diff coverage is 26.56%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master    #1842      +/-   ##
============================================
- Coverage     33.02%   32.83%   -0.20%     
+ Complexity     1057     1056       -1     
============================================
  Files           155      156       +1     
  Lines         10943    11031      +88     
  Branches       1511     1529      +18     
============================================
+ Hits           3614     3622       +8     
- Misses         6976     7054      +78     
- Partials        353      355       +2     
Impacted Files Coverage Δ
...n/java/org/jitsi/videobridge/AbstractEndpoint.java 22.12% <0.00%> (ø)
...rg/jitsi/videobridge/EndpointMessageTransport.java 8.33% <0.00%> (-0.27%) ⬇️
.../org/jitsi/videobridge/octo/ConfOctoTransport.java 34.43% <0.00%> (+1.98%) ⬆️
...videobridge/octo/OctoEndpointMessageTransport.java 3.75% <0.00%> (-0.15%) ⬇️
...ava/org/jitsi/videobridge/shim/ConferenceShim.java 3.08% <0.00%> (ø)
.../videobridge/colibri2/Colibri2ConferenceHandler.kt 1.41% <0.00%> (-0.01%) ⬇️
.../jitsi/videobridge/message/BridgeChannelMessage.kt 60.00% <ø> (ø)
...c/main/kotlin/org/jitsi/videobridge/relay/Relay.kt 0.21% <0.00%> (-0.01%) ⬇️
...g/jitsi/videobridge/relay/RelayMessageTransport.kt 0.00% <0.00%> (ø)
...lin/org/jitsi/videobridge/relay/RelayedEndpoint.kt 0.00% <0.00%> (ø)
... and 12 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0e93dcc...613c004. Read the comment docs.

@@ -287,6 +289,8 @@ class Endpoint @JvmOverloads constructor(
setupDtlsTransport()

conference.videobridge.statistics.totalEndpoints.incrementAndGet()

logger.info("$id source names: $isUsingSourceNames")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you log something like Created new endpoint isUsingSourceNames=$isUsingSourceNames, iceControlling=$iceControlling (the ID is already in the logger context)

logger.cdebug { "Sender constraints changed: ${senderSourceConstraintsMessage.toJson()}" }
sendMessage(senderSourceConstraintsMessage)
} else {
sendVideoConstraints(maxReceiverVideoConstraintsMap[sourceName]!!)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a null check and log an error instead of throwing

@@ -706,6 +718,10 @@ class Endpoint @JvmOverloads constructor(
* @param forwardedSources the collection of forwarded media sources (by name).
*/
fun sendForwardedSourcesMessage(forwardedSources: Collection<String>) {
if (!isUsingSourceNames) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this check MultiStreamConfig.config.enabled, too?

@paweldomas paweldomas requested a review from bgrozev March 28, 2022 12:53
Copy link
Member

@JonathanLennox JonathanLennox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if a multi-source endpoint actually has more than one source, and a non-sourceName-supporting endpoint joins the conference?

@@ -312,6 +312,8 @@ videobridge {
# The experimental multiple streams per endpoint support
multi-stream {
enabled = false
# Allows mixing source name aware clients together with legacy endpoint ID based in the same conference
compatibility-mode = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look like this is used? (And I don't see any reason why we'd want it to be false, so I think it shouldn't be.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh I thought I have removed it, thanks

@@ -44,7 +44,7 @@ class ConferenceTest : ConfigTest() {
context("Adding local endpoints should work") {
with(Conference(videobridge, "id", name, Conference.GID_NOT_SET, null, false, false)) {
endpointCount shouldBe 0
createLocalEndpoint("abcdabcd", true)
createLocalEndpoint("abcdabcd", true, false) // TODO cover the case when it's true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning to cover these testing TODOs in this PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try to see if there's anything easy, but some of them I was planning to address when the separation between legacy and multi stream flows is being removed.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have succeeded to get multiple source on Plan-B SDP by preserving desktop msid in simulcast library.

In order to get multiple stream support in Plan-B we have to modify simulcast library to keep this desktop ms-id and has support while using unified-plan conversion to plan-b and vice versa

In SDP there is media source id for camera with simulcast

msid:405fee97-1929-42f0-967e-327aa0a88cd1

and for desktop we have another msid:

msid:01cd7f5c-5f32-44dc-99f2-249c4e65f9ae

The only think that I could not succeeded is simulcast support for desktop source stream. At least it has RTX

Here the SDP content for multiple stream from one participant;

m=video 9 RTP/SAVPF 101 97 (58 more lines) mid=video
c=IN IP4 0.0.0.0
a=rtpmap:101 VP9/90000
a=rtpmap:97 rtx/90000
a=fmtp:101 profile-id=0
a=fmtp:97 apt=101
a=rtcp:9 IN IP4 0.0.0.0
a=rtcp-fb:101 goog-remb
a=rtcp-fb:101 transport-cc
a=rtcp-fb:101 ccm fir
a=rtcp-fb:101 nack
a=rtcp-fb:101 nack pli
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=setup:active
a=mid:video
a=sendrecv
a=ice-ufrag:0q/w
a=ice-pwd:OeTbyftYzM+tV335lIErckjH
a=fingerprint:sha-256 F0:D1:CD:DF:5A:D6:BD:0E:7D:70:1B:4F:2B:A7:D3:7C:FD:23:C3:56:76:C9:B4:73:64:E1:8C:E4:06:84:50:3A
a=ice-options:trickle
a=ssrc:4264132242 cname:+jsiQ6DKcFqhOv/3
a=ssrc:4264132242 msid:405fee97-1929-42f0-967e-327aa0a88cd1 afc5a0ae-2901-4e07-b2b6-aec89f36fd58
a=ssrc:4264132242 mslabel:405fee97-1929-42f0-967e-327aa0a88cd1
a=ssrc:4264132242 label:afc5a0ae-2901-4e07-b2b6-aec89f36fd58
a=ssrc:2693021993 cname:+jsiQ6DKcFqhOv/3
a=ssrc:2693021993 msid:405fee97-1929-42f0-967e-327aa0a88cd1 afc5a0ae-2901-4e07-b2b6-aec89f36fd58
a=ssrc:2693021993 mslabel:405fee97-1929-42f0-967e-327aa0a88cd1
a=ssrc:2693021993 label:afc5a0ae-2901-4e07-b2b6-aec89f36fd58
a=ssrc:1097069354 cname:+jsiQ6DKcFqhOv/3
a=ssrc:1097069354 msid:405fee97-1929-42f0-967e-327aa0a88cd1 afc5a0ae-2901-4e07-b2b6-aec89f36fd58
a=ssrc:1097069354 mslabel:405fee97-1929-42f0-967e-327aa0a88cd1
a=ssrc:1097069354 label:afc5a0ae-2901-4e07-b2b6-aec89f36fd58
a=ssrc:1259451117 cname:+jsiQ6DKcFqhOv/3
a=ssrc:1259451117 msid:405fee97-1929-42f0-967e-327aa0a88cd1 afc5a0ae-2901-4e07-b2b6-aec89f36fd58
a=ssrc:1259451117 mslabel:405fee97-1929-42f0-967e-327aa0a88cd1
a=ssrc:1259451117 label:afc5a0ae-2901-4e07-b2b6-aec89f36fd58
a=ssrc:766891600 cname:+jsiQ6DKcFqhOv/3
a=ssrc:766891600 msid:405fee97-1929-42f0-967e-327aa0a88cd1 afc5a0ae-2901-4e07-b2b6-aec89f36fd58
a=ssrc:766891600 mslabel:405fee97-1929-42f0-967e-327aa0a88cd1
a=ssrc:766891600 label:afc5a0ae-2901-4e07-b2b6-aec89f36fd58
a=ssrc:2041623614 cname:+jsiQ6DKcFqhOv/3
a=ssrc:2041623614 msid:405fee97-1929-42f0-967e-327aa0a88cd1 afc5a0ae-2901-4e07-b2b6-aec89f36fd58
a=ssrc:2041623614 mslabel:405fee97-1929-42f0-967e-327aa0a88cd1
a=ssrc:2041623614 label:afc5a0ae-2901-4e07-b2b6-aec89f36fd58
a=ssrc:2567931665 cname:+jsiQ6DKcFqhOv/3
a=ssrc:2567931665 msid:01cd7f5c-5f32-44dc-99f2-249c4e65f9ae f2f45b25-30a6-48c5-80c3-8ab3b49214b2
a=ssrc:2567931665 mslabel:01cd7f5c-5f32-44dc-99f2-249c4e65f9ae
a=ssrc:2567931665 label:f2f45b25-30a6-48c5-80c3-8ab3b49214b2
a=ssrc:3694802917 cname:+jsiQ6DKcFqhOv/3
a=ssrc:3694802917 msid:01cd7f5c-5f32-44dc-99f2-249c4e65f9ae f2f45b25-30a6-48c5-80c3-8ab3b49214b2
a=ssrc:3694802917 mslabel:01cd7f5c-5f32-44dc-99f2-249c4e65f9ae
a=ssrc:3694802917 label:f2f45b25-30a6-48c5-80c3-8ab3b49214b2
a=ssrc-group:FID 4264132242 2693021993
a=ssrc-group:FID 1097069354 766891600
a=ssrc-group:FID 1259451117 2041623614
a=ssrc-group:FID 2567931665 3694802917
a=ssrc-group:SIM 4264132242 1097069354 1259451117
a=rtcp-mux

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the code that I used in Plan-B

/* Copyright @ 2016 Atlassian Pty Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var transform = require('sdp-transform');
var transformUtils = require('./transform-utils');
var parseSsrcs = transformUtils.parseSsrcs;
var writeSsrcs = transformUtils.writeSsrcs;

//region Constants

var DEFAULT_NUM_OF_LAYERS = 3;

//endregion

function getSsrcAttribute (mLine, ssrc, attributeName) {
    return mLine
        .ssrcs
        .filter(function(ssrcInfo) { return ssrcInfo.id === ssrc; })
        .filter(function(ssrcInfo) { return ssrcInfo.attribute === attributeName; })
        .map(function(ssrcInfo) { return ssrcInfo.value; })[0];
}

//region Ctor

function Simulcast(options) {

    this.options = options ? options : {};

    if (!this.options.numOfLayers) {
        this.options.numOfLayers = DEFAULT_NUM_OF_LAYERS;
    }
    console.log("SdpSimulcast: using " + this.options.numOfLayers + " layers");

    /**
     * An IN-ORDER list of the simulcast ssrcs
     * @type {list<number>}
     */
    this.ssrcCache = [];
}

//endregion

//region Stateless private utility functions

/**
 * Returns a random integer between min (included) and max (excluded)
 * Using Math.round() gives a non-uniform distribution!
 * @returns {number}
 */
function generateSSRC() {
    var min = 0, max = 0xffffffff;
    return Math.floor(Math.random() * (max - min)) + min;
};

function processVideo(session, action) {
    if (session == null || !Array.isArray(session.media)) {
        return;
    }

    session.media.forEach(function (mLine) {
        if (mLine.type === 'video') {
            action(mLine);
        }
    });
};

function validateDescription(desc)
{
    return desc && desc != null
        && desc.type && desc.type != ''
        && desc.sdp && desc.sdp != '';
}

function explodeRemoteSimulcast(mLine) {

    if (!mLine || !Array.isArray(mLine.ssrcGroups)) {
        return;
    }

    var sources = parseSsrcs(mLine);
    var order = [];

    // Find the SIM group and explode its sources.
    var j = mLine.ssrcGroups.length;
    while (j--) {

        if (mLine.ssrcGroups[j].semantics !== 'SIM') {
            continue;
        }

        var simulcastSsrcs = mLine.ssrcGroups[j].ssrcs.split(' ');

        for (var i = 0; i < simulcastSsrcs.length; i++) {

            var ssrc = simulcastSsrcs[i];
            order.push(ssrc);

            var parts = sources[ssrc].msid.split(' ');
            sources[ssrc].msid = [parts[0], '/', i, ' ', parts[1], '/', i].join('');
            sources[ssrc].cname = [sources[ssrc].cname, '/', i].join('');

            // Remove all the groups that this SSRC participates in.
            mLine.ssrcGroups.forEach(function (relatedGroup) {
                if (relatedGroup.semantics === 'SIM') {
                    return;
                }

                var relatedSsrcs = relatedGroup.ssrcs.split(' ');
                if (relatedSsrcs.indexOf(ssrc) === -1) {
                    return;
                }

                // Nuke all the related SSRCs.
                relatedSsrcs.forEach(function (relatedSSRC) {
                    sources[relatedSSRC].msid = sources[ssrc].msid;
                    sources[relatedSSRC].cname = sources[ssrc].cname;
                    if (relatedSSRC !== ssrc) {
                        order.push(relatedSSRC);
                    }
                });

                // Schedule the related group for nuking.
            })
        }

        mLine.ssrcs = writeSsrcs(sources, order);
        mLine.ssrcGroups.splice(j, 1);
    };
}

function implodeRemoteSimulcast(mLine) {

    if (!mLine || !Array.isArray(mLine.ssrcGroups)) {
        console.info('Halt: There are no SSRC groups in the remote ' +
                'description.');
        return;
    }

    var sources = parseSsrcs(mLine);

    // Find the SIM group and nuke it.
    mLine.ssrcGroups.forEach(function (simulcastGroup) {
        if (simulcastGroup.semantics !== 'SIM') {
            return;
        }

        console.info("Imploding SIM group: " + simulcastGroup.ssrcs);
        // Schedule the SIM group for nuking.
        simulcastGroup.nuke = true;

        var simulcastSsrcs = simulcastGroup.ssrcs.split(' ');

        // Nuke all the higher layer SSRCs.
        for (var i = 1; i < simulcastSsrcs.length; i++) {

            var ssrc = simulcastSsrcs[i];
            delete sources[ssrc];

            // Remove all the groups that this SSRC participates in.
            mLine.ssrcGroups.forEach(function (relatedGroup) {
                if (relatedGroup.semantics === 'SIM') {
                    return;
                }

                var relatedSsrcs = relatedGroup.ssrcs.split(' ');
                if (relatedSsrcs.indexOf(ssrc) === -1) {
                    return;
                }

                // Nuke all the related SSRCs.
                relatedSsrcs.forEach(function (relatedSSRC) {
                    delete sources[relatedSSRC];
                });

                // Schedule the related group for nuking.
                relatedGroup.nuke = true;
            })
        }

        return;
    });

    mLine.ssrcs = writeSsrcs(sources);

    // Nuke all the scheduled groups.
    var i = mLine.ssrcGroups.length;
    while (i--) {
        if (mLine.ssrcGroups[i].nuke) {
            mLine.ssrcGroups.splice(i, 1);
        }
    }
}

function removeGoogConference(mLine) {
    if (!mLine || typeof mLine.xGoogleFlag === 'undefined') {
        return;
    }

    mLine.xGoogleFlag = undefined;
}

function assertGoogConference(mLine) {
    if (!mLine) {
        return;
    }

    if (!Array.isArray(mLine.invalid)) {
        mLine.invalid = [];
    }

    if (!mLine.invalid.some(
            function (i) { return i.value === 'x-google-flag:conference' })) {
        mLine.invalid.push({'value': 'x-google-flag:conference'});
    }
}

Simulcast.prototype.clearSsrcCache = function() {
    this.ssrcCache = [];
}

/**
 * When we start as video muted, all of the video
 *  ssrcs get generated so we can include them as part
 *  of the original session-accept.  That means we
 *  need this library to restore to those same ssrcs
 *  the first time we unmute, so we need the ability to
 *  force its cache
 */
Simulcast.prototype.setSsrcCache = function(ssrcs) {
    this.ssrcCache = ssrcs;
}

//endregion

//region "Private" functions

/**
 * Given a video mLine, return a list of the video ssrcs
 *  in simulcast layer order (returns a list of just
 *  the primary ssrc if there are no simulcast layers)
 */
Simulcast.prototype._parseSimLayers = function (mLine, desktopSSRCList) {
    // console.log("***>>> _parseSimLayers metodu çağrıldı. mLine: ", mLine);
    // console.log("***>>> _parseSimLayers metodu çağrıldı. desktopSSRCList: ", desktopSSRCList);
    var simGroup = mLine.ssrcGroups &&
        mLine.ssrcGroups.find(function(group) { return group.semantics === "SIM"; });
    if (simGroup) {
        // console.log("***>>> _parseSimLayers simGroup bulundu: ", simGroup);
        return simGroup.ssrcs
            .split(" ")
            .map(function(ssrcStr) { return parseInt(ssrcStr) });
    } else {
        // console.log("***>>> _parseSimLayers simGroup bulunamadı! mLine.ssrcs içeriğinden bulunuyor.");
        return [mLine.ssrcs && mLine.ssrcs
            .map(function(ssrcInfo) { return ssrcInfo.id; })
            .filter(function(ssrc) {
                return !desktopSSRCList || !desktopSSRCList.includes(ssrc);
            })[0]]; // mabocoglu: Hata burada. Kamera id'sini döndürmesi gerekirken masaüstününkini döndürüyor.
    }
}

Simulcast.prototype._buildNewToOldSsrcMap = function (newSsrcList, oldSsrcList) {
    var ssrcMap = {};
    for (var i = 0; i < newSsrcList.length; ++i) {
        var newSsrc = newSsrcList[i];
        var oldSsrc = oldSsrcList[i] || null;
        ssrcMap[newSsrc] = oldSsrc;
    }
    return ssrcMap;
}

Simulcast.prototype._fillInSourceDataFromCache = function(mLine, desktopSSRCList) {
    console.log("SdpSimulcast restoring from cache: ", this.ssrcCache);
    var newSimSsrcs = this._parseSimLayers(mLine, desktopSSRCList);
    console.log("SdpSimulcast Parsed new sim ssrcs: ", newSimSsrcs);
    var newMsid = getSsrcAttribute(mLine, newSimSsrcs[0], "msid");
    var newCname = getSsrcAttribute(mLine, newSimSsrcs[0], "cname");
    var ssrcsToReplace = this._buildNewToOldSsrcMap(newSimSsrcs, this.ssrcCache);
    console.log("SdpSimulcast built replacement map: ", ssrcsToReplace);
    // New sdp might only have 1 layer, so not every cached ssrc will have a new one
    //  to replace directly
    var ssrcsToAdd = this.ssrcCache
        .filter(function(ssrc) { return Object.values(ssrcsToReplace).indexOf(ssrc) === -1; });
    console.log("SdpSimulcast built ssrcs to add: ", ssrcsToAdd);

    // First do the replacements
    mLine.ssrcs.forEach(function(ssrc) {
        if (ssrcsToReplace[ssrc.id]) {
            ssrc.id = ssrcsToReplace[ssrc.id];
        }
    });
    // Now the adds
    ssrcsToAdd.forEach(function(ssrc) {
        mLine.ssrcs.push({
            id: ssrc,
            attribute: "msid",
            value: newMsid
        });
        mLine.ssrcs.push({
            id: ssrc,
            attribute: "cname",
            value: newCname
        });
    });
    mLine.ssrcGroups = mLine.ssrcGroups || [];
    mLine.ssrcGroups.push({
        semantics: "SIM",
        ssrcs: this.ssrcCache.join(" ")
    });
    return mLine;
}

Simulcast.prototype._generateSourceData = function(mLine, primarySsrc) {
    var addAssociatedStream = function(mLine, ssrc) {
        mLine.ssrcs.push({
            id: ssrc,
            attribute: "cname",
            value: primarySsrcCname
        });
        mLine.ssrcs.push({
            id: ssrc,
            attribute: "msid",
            value: primarySsrcMsid
        });
    }
    var primarySsrcMsid = getSsrcAttribute(mLine, primarySsrc, "msid");
    var primarySsrcCname = getSsrcAttribute(mLine, primarySsrc, "cname");

    // In Unified-plan mode, the a=ssrc lines with the msid attribute are not present
    // in the answers that Chrome and Safari generate for an offer received from Jicofo.
    // Generate these a=ssrc lines using the msid values from the a=msid line.
    if (this.options.usesUnifiedPlan && !primarySsrcMsid) {
        primarySsrcMsid = mLine.msid;
        var primarySsrcs = mLine.ssrcs;
        primarySsrcs.forEach(ssrc => {
            mLine.ssrcs.push({
                id: ssrc.id,
                attribute: "msid",
                value: primarySsrcMsid
            });
        });
    }

    // Generate sim layers
    var simSsrcs = [];
    for (var i = 0; i < this.options.numOfLayers - 1; ++i) {
        var simSsrc = generateSSRC();
        addAssociatedStream(mLine, simSsrc);
        simSsrcs.push(simSsrc);
    }
    mLine.ssrcGroups = mLine.ssrcGroups || [];
    mLine.ssrcGroups.push({
        semantics: "SIM",
        ssrcs: primarySsrc + " " + simSsrcs.join(" ")
    });
    return mLine;
}



// Assumptions:
//  1) 'mLine' contains only a single primary video source
//   (i.e. it will not already have simulcast streams inserted)
//  2) 'mLine' MAY already contain an RTX stream for its video source
//  3) 'mLine' is in sendrecv or sendonly state
// Guarantees:
//  1) return mLine will contain 2 additional simulcast layers
//   generated
//  2) if the base video ssrc in mLine has been seen before,
//   then the same generated simulcast streams from before will
//   be used again
//  3) if rtx is enabled for the mLine, all generated simulcast
//   streams will have rtx streams generated as well
//  4) if rtx has been generated for a src before, we will generate
//   the same rtx stream again
Simulcast.prototype._restoreSimulcast = function(mLine, desktopMSID) {
    // First, find the primary video source in the given
    // mLine and see if we've seen it before.
    var primarySsrc;
    
    const desktopSSRCList = desktopMSID && mLine.ssrcs ? findSsrcsForDesktopMsid(mLine.ssrcs, desktopMSID) : [];

    var numSsrcs = mLine.ssrcs && mLine.ssrcs
        .map(function(ssrcInfo) { return ssrcInfo.id; })
        .filter(function(ssrc, index, array) {
            return array.indexOf(ssrc) === index && (!desktopSSRCList || !desktopSSRCList.includes(ssrc));
        })
        .length || 0;
    var numGroups = (mLine.ssrcGroups && mLine.ssrcGroups.length) || 0;

    if (numSsrcs === 0 || numSsrcs > 2) {
        // console.log("***!!! _restoreSimulcast numSsrcs === 0 || numSsrcs > 2 nedeniyle simulcast değişikliği yapmadan değeri döndürüyor.");
        // Unsupported scenario
        return mLine;
    }
    if (numSsrcs == 2 && numGroups === 0) {
        // console.log("***!!! _restoreSimulcast numSsrcs == 2 && numGroups === 0 nedeniyle simulcast değişikliği yapmadan değeri döndürüyor.");
        // Unsupported scenario
        return mLine;
    }

    if (numSsrcs === 1) {
        // primarySsrc = mLine.ssrcs[0].id;
        primarySsrc = mLine.ssrcs.filter(function(ssrc) {
            return !desktopSSRCList || !desktopSSRCList.includes(ssrc);
        })[0].id;
    } else {
        // There must be an FID group, so parse
        //  that and pull the primary ssrc from there
        // var fidGroups = mLine.ssrcGroups.filter(function(group) { return group.semantics === "FID"; })[0];
        var fidGroups = mLine.ssrcGroups.filter(function(group) { return group.semantics === "FID"; });

        // Birden çok fidGrubu olabilir.
        if (fidGroups && fidGroups.length > 0) {
            for (let fidGroup of fidGroups) {
                primarySsrc = parseInt(fidGroup.ssrcs.split(" ").filter(function(ssrc) {
                    return !desktopSSRCList || !desktopSSRCList.includes(ssrc);
                })[0]);
            }
        } else {
            // console.log("***!!! _restoreSimulcast fid grubu bulunamadı.");
            // Unsupported scenario
            return mLine;
        }
    }
    console.log("SdpSimulcast: current ssrc cache: ", this.ssrcCache);
    console.log("SdpSimulcast: parsed primary ssrc " + primarySsrc);

    var seenPrimarySsrc = this.ssrcCache.indexOf(primarySsrc) !== -1;

    if (seenPrimarySsrc) {
        console.log("SdpSimulcast: Have seen primary ssrc before, " +
            "filling in data from cache. mLine: ", mLine);
        mLine = this._fillInSourceDataFromCache(mLine, desktopSSRCList);
    } else {
        console.log("SdpSimulcast: Have not seen primary ssrc before, " +
            "generating source data");
        mLine = this._generateSourceData(mLine, primarySsrc);
    }
    // Now update the cache to match whatever we've just put into this sdp
    this.ssrcCache = this._parseSimLayers(mLine, desktopSSRCList);
    return mLine;
}

findSsrcsForDesktopMsid = function(ssrcs, desktopMsid) {
    // logger.info("***$$$ desktop ssrc numaraları bulunuyor...");
    let desktopSsrcs = [];

    // TODO: mabocoglu: Dizi içinde .filter .find tarzı bir kullanım ile dizi doldurulabilir.
    for (const ssrc of ssrcs) {
        // logger.info("***$$$ ssrc inceleniyor... ssrc: ", ssrc);

        if (ssrc.attribute === "msid" && ssrc.value === desktopMsid) {
            // logger.info("***$$$ ssrc masaüstü stream'ine ait. ssrc listesine ekleniyor...");
            desktopSsrcs.push(ssrc.id);
        }
    }

    // logger.info("***$$$ masaüstü ssrc listesi döndürülüyor... desktopSsrcs: ", desktopSsrcs);
    return desktopSsrcs;
}

//endregion

//region "Public" functions

/**
 *
 * @param desc
 * @param enableConferenceFlag
 * @returns {RTCSessionDescription}
 */
Simulcast.prototype.mungeRemoteDescription = function (desc, enableConferenceFlag) {

    if (!validateDescription(desc)) {
        return desc;
    }

    var session = transform.parse(desc.sdp);

    var self = this;
    processVideo(session, function (mLine) {

        // Handle simulcast reception.
        if (self.options.explodeRemoteSimulcast) {
            explodeRemoteSimulcast(mLine);
        } else {
            implodeRemoteSimulcast(mLine);
        }

        // Add or remove "x-google-conference" from the remote description based on whether the client
        // has enabled simulcast for the local video source. For cases where we disable simulcast for desktop share,
        // it is necessary to remove the flag so that Chrome stops sending T1 temporal layers. It also fixes other
        // issues related to screensharing like https://bugs.chromium.org/p/chromium/issues/detail?id=1093819.
        if (!self.options.usesUnifiedPlan && enableConferenceFlag) {
            assertGoogConference(mLine);
        } else {
            removeGoogConference(mLine);
        }
    });

    return new RTCSessionDescription({
        type: desc.type,
        sdp: transform.write(session)
    });
};

/**
 *
 * NOTE this method should be called only if simulcast is supported by
 * the current browser, otherwise local SDP should not be munged.
 * @param desc
 * @returns {RTCSessionDescription}
 */
Simulcast.prototype.mungeLocalDescription = function (desc, desktopMSID) {

    if (!validateDescription(desc)) {
        return desc;
    }

    var session = transform.parse(desc.sdp);

    var self = this;
    processVideo(session, function (mLine) {
        if (mLine.direction == 'recvonly' || mLine.direction == 'inactive')
        {
            return;
        }
        self._restoreSimulcast(mLine, desktopMSID);
    });

    return new RTCSessionDescription({
        type: desc.type,
        sdp: transform.write(session)
    });
};

//endregion

module.exports = Simulcast;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want plan B support, because it adds a lot to the maintenance effort and is being removed from Chrome anyway.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understood a non-sourceName-supporting endpoint as Plan-B endpoint while react-native-webrtc still does not support unified-plan which is mentioned here;

[https://github.com/react-native-webrtc/react-native-webrtc/issues/1005](Meta - Unified Plan support)

Thank you

Copy link
Member Author

@paweldomas paweldomas Mar 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, a react native endpoint (plan B), with source name capability will be able to receive multiple streams, but not send. If there are still react-native endpoints not upgraded to source names yet, then you should not enable the send multi stream flag for your conferences.

@paweldomas
Copy link
Member Author

What happens if a multi-source endpoint actually has more than one source, and a non-sourceName-supporting endpoint joins the conference?

Then that endpoint will not be able to render any extra tracks (because lib-jitsi-meet doesn't support it). The goal of the compatibility mode is to be able to run a JVB with multi-stream flag enabled which can host either legacy or multi-stream conferences. On the client there's a separate flag that enables sending more than 1 streams and it should be flipped only once you know that any native/non-web clients are running the source-name capable version.

@JonathanLennox
Copy link
Member

On the client there's a separate flag that enables sending more than 1 streams and it should be flipped only once you know that any native/non-web clients are running the source-name capable version.

Is this something that would be flipped in the config, where the administrator tracks the status of all the non-web clients people are using, or do clients get presence reports telling them what the other clients in the conference support?

@paweldomas
Copy link
Member Author

On the client there's a separate flag that enables sending more than 1 streams and it should be flipped only once you know that any native/non-web clients are running the source-name capable version.

Is this something that would be flipped in the config, where the administrator tracks the status of all the non-web clients people are using, or do clients get presence reports telling them what the other clients in the conference support?

This is a config flag that needs to be adjusted by an administrator.

@JonathanLennox
Copy link
Member

This is a config flag that needs to be adjusted by an administrator.

If the administrator is wrong, and a non-multi-stream-aware client is still in the call, is it deterministic which source the old client will get?

We probably want to make sure that if this does happen, and a client is sending both a screenshare and a video, the old clients get the screenshare - that's probably the more useful failure mode.

@paweldomas
Copy link
Member Author

This is a config flag that needs to be adjusted by an administrator.

If the administrator is wrong, and a non-multi-stream-aware client is still in the call, is it deterministic which source the old client will get?

We probably want to make sure that if this does happen, and a client is sending both a screenshare and a video, the old clients get the screenshare - that's probably the more useful failure mode.

Yeah, I agree that would be the most useful failure mode, however I need to check what really happens on the client in that case. It may depend on the order in which particular tracks were turned on and probably only the first one will be described correctly via the receiver constraints. That's because endpoint ID is translated to the first video track source name (the -v0 suffix).

@JonathanLennox
Copy link
Member

Yeah, I agree that would be the most useful failure mode, however I need to check what really happens on the client in that case. It may depend on the order in which particular tracks were turned on and probably only the first one will be described correctly via the receiver constraints. That's because endpoint ID is translated to the first video track source name (the -v0 suffix).

For this PR I think the only thing we need is to make sure the server consistently picks one of the listed sources (e.g. the first one), as opposed to picking whichever one happens to come first in an unordered hash map, or anything like that. Doing the correct ordering would then be the client's problem.

This may already be the case, but I wanted to make sure of it.

@paweldomas
Copy link
Member Author

For this PR I think the only thing we need is to make sure the server consistently picks one of the listed sources (e.g. the first one), as opposed to picking whichever one happens to come first in an unordered hash map, or anything like that. Doing the correct ordering would then be the client's problem.

This may already be the case, but I wanted to make sure of it.

Looking at the source, the streams will be forwarded in the order in which they have been signaled unless sorted by receiver constraints. This is because MediaSourceDescs are stored in a list which should preserve the order in which the elements were added.

This was referenced Jul 15, 2022
@bgrozev bgrozev mentioned this pull request Jul 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants