-
Notifications
You must be signed in to change notification settings - Fork 992
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
Conversation
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 Report
@@ 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
Continue to review full report at Codecov.
|
@@ -287,6 +289,8 @@ class Endpoint @JvmOverloads constructor( | |||
setupDtlsTransport() | |||
|
|||
conference.videobridge.statistics.totalEndpoints.incrementAndGet() | |||
|
|||
logger.info("$id source names: $isUsingSourceNames") |
There was a problem hiding this comment.
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]!!) |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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;
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
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. |
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. |
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 |
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 |
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.