mediasoup 3.24.0 → 3.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
### NEXT
|
|
4
4
|
|
|
5
|
+
### 3.24.2
|
|
6
|
+
|
|
7
|
+
- Worker: Verify `DataConsumer` subchannels before cloning the message ([PR #1880](https://github.com/versatica/mediasoup/pull/1880)).
|
|
8
|
+
|
|
9
|
+
### 3.24.1
|
|
10
|
+
|
|
11
|
+
- Worker: Don't check `ignoredSubchannel` in piped `DataConsumers` ([PR #1879](https://github.com/versatica/mediasoup/pull/1879)).
|
|
12
|
+
|
|
5
13
|
### 3.24.0
|
|
6
14
|
|
|
7
15
|
- `DataProducer.send()`: Add `ignoredSubchannel` optional argument ([PR #1877](https://github.com/versatica/mediasoup/pull/1877)).
|
|
@@ -967,8 +967,8 @@ test('transport.consumeData() for a pipe DataProducer succeeds with subchannels'
|
|
|
967
967
|
/* ppid */ undefined,
|
|
968
968
|
/* subchannels */ [125],
|
|
969
969
|
/* requiredSubchannel */ 666);
|
|
970
|
-
// The `
|
|
971
|
-
//
|
|
970
|
+
// The final `directDataConsumer` is subscribed to subchannel 666 so it must
|
|
971
|
+
// not receive the message.
|
|
972
972
|
directDataProducer.send('hello5',
|
|
973
973
|
/* ppid */ undefined,
|
|
974
974
|
/* subchannels */ [123],
|
|
@@ -979,7 +979,7 @@ test('transport.consumeData() for a pipe DataProducer succeeds with subchannels'
|
|
|
979
979
|
directDataProducer.send('hello6',
|
|
980
980
|
/* ppid */ undefined,
|
|
981
981
|
/* subchannels */ undefined,
|
|
982
|
-
/* requiredSubchannel */
|
|
982
|
+
/* requiredSubchannel */ 123,
|
|
983
983
|
/* ignoredSubchannel */ 777);
|
|
984
984
|
// Send a message without subchannels so it's guaranteed that it will reach
|
|
985
985
|
// the final `directDataConsumer`. And wait for reception of this message so
|
|
@@ -997,6 +997,63 @@ test('transport.consumeData() for a pipe DataProducer succeeds with subchannels'
|
|
|
997
997
|
});
|
|
998
998
|
expect(receivedMessages).toStrictEqual(['hello1', 'hello2', 'bye']);
|
|
999
999
|
}, 2000);
|
|
1000
|
+
test('transport.consumeData() for a pipe DataProducer succeeds with ignoredSubchannel', async () => {
|
|
1001
|
+
const directTransport1 = await ctx.router1.createDirectTransport();
|
|
1002
|
+
const directDataProducer = await directTransport1.produceData({
|
|
1003
|
+
label: 'foo',
|
|
1004
|
+
protocol: 'bar',
|
|
1005
|
+
});
|
|
1006
|
+
const { pipeDataConsumer } = await ctx.router1.pipeToRouter({
|
|
1007
|
+
dataProducerId: directDataProducer.id,
|
|
1008
|
+
router: ctx.router2,
|
|
1009
|
+
});
|
|
1010
|
+
const directTransport2 = await ctx.router2.createDirectTransport();
|
|
1011
|
+
const directDataConsumer1 = await directTransport2.consumeData({
|
|
1012
|
+
dataProducerId: directDataProducer.id,
|
|
1013
|
+
subchannels: [111],
|
|
1014
|
+
});
|
|
1015
|
+
const directDataConsumer2 = await directTransport2.consumeData({
|
|
1016
|
+
dataProducerId: directDataProducer.id,
|
|
1017
|
+
subchannels: [222],
|
|
1018
|
+
});
|
|
1019
|
+
// A pipe DataConsumer must be subscribed to the union of the subchannels of
|
|
1020
|
+
// all the DataConsumers behind it.
|
|
1021
|
+
await pipeDataConsumer.setSubchannels([111, 222]);
|
|
1022
|
+
const receivedMessages1 = [];
|
|
1023
|
+
const receivedMessages2 = [];
|
|
1024
|
+
// Resolved once both final DataConsumers have received the last sent message.
|
|
1025
|
+
const allReceived = Promise.all([
|
|
1026
|
+
[directDataConsumer1, receivedMessages1],
|
|
1027
|
+
[directDataConsumer2, receivedMessages2],
|
|
1028
|
+
].map(([dataConsumer, receivedMessages]) => new Promise(resolve => {
|
|
1029
|
+
dataConsumer.on('message', message => {
|
|
1030
|
+
const receivedMessage = message.toString('utf8');
|
|
1031
|
+
receivedMessages.push(receivedMessage);
|
|
1032
|
+
// Resolve once the last sent message is received.
|
|
1033
|
+
if (receivedMessage === 'bye') {
|
|
1034
|
+
resolve();
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
})));
|
|
1038
|
+
// The `pipeDataConsumer` is subscribed to subchannel 111, but being a pipe
|
|
1039
|
+
// DataConsumer it must not apply `ignoredSubchannel` itself. Otherwise it
|
|
1040
|
+
// would drop the message for every DataConsumer behind it rather than just
|
|
1041
|
+
// for `directDataConsumer1`. `ignoredSubchannel` travels within the message
|
|
1042
|
+
// and is applied by the final DataConsumers, so `directDataConsumer2` must
|
|
1043
|
+
// still receive it.
|
|
1044
|
+
directDataProducer.send('hello1',
|
|
1045
|
+
/* ppid */ undefined,
|
|
1046
|
+
/* subchannels */ undefined,
|
|
1047
|
+
/* requiredSubchannel */ undefined,
|
|
1048
|
+
/* ignoredSubchannel */ 111);
|
|
1049
|
+
// Send a message without subchannels so it's guaranteed that it will reach
|
|
1050
|
+
// both final DataConsumers. And wait for reception of this message so at
|
|
1051
|
+
// this time we know whether the previous one reached them.
|
|
1052
|
+
directDataProducer.send('bye');
|
|
1053
|
+
await allReceived;
|
|
1054
|
+
expect(receivedMessages1).toStrictEqual(['bye']);
|
|
1055
|
+
expect(receivedMessages2).toStrictEqual(['hello1', 'bye']);
|
|
1056
|
+
}, 2000);
|
|
1000
1057
|
test('dataProducer.close() is transmitted to pipe DataConsumer', async () => {
|
|
1001
1058
|
const { pipeDataProducer } = await ctx.router1.pipeToRouter({
|
|
1002
1059
|
dataProducerId: ctx.dataProducer.id,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mediasoup",
|
|
3
|
-
"version": "3.24.
|
|
3
|
+
"version": "3.24.2",
|
|
4
4
|
"description": "Cutting Edge WebRTC Video Conferencing",
|
|
5
5
|
"contributors": [
|
|
6
6
|
"Iñaki Baz Castillo <ibc@aliax.net> (https://inakibaz.me)",
|
|
@@ -121,22 +121,22 @@
|
|
|
121
121
|
"@types/debug": "^4.1.13",
|
|
122
122
|
"@types/ini": "^4.1.1",
|
|
123
123
|
"@types/jest": "^30.0.0",
|
|
124
|
-
"@types/node": "^26.
|
|
125
|
-
"eslint": "^10.8.
|
|
124
|
+
"@types/node": "^26.2.0",
|
|
125
|
+
"eslint": "^10.8.1",
|
|
126
126
|
"eslint-config-prettier": "^10.1.8",
|
|
127
127
|
"eslint-plugin-jest": "^29.16.0",
|
|
128
128
|
"eslint-plugin-prettier": "^5.5.6",
|
|
129
129
|
"globals": "^17.9.0",
|
|
130
130
|
"ini": "^7.0.0",
|
|
131
131
|
"jest": "^30.4.2",
|
|
132
|
-
"knip": "^6.
|
|
133
|
-
"marked": "^18.0.
|
|
132
|
+
"knip": "^6.32.0",
|
|
133
|
+
"marked": "^18.0.9",
|
|
134
134
|
"open-cli": "^9.0.0",
|
|
135
135
|
"pick-port": "^2.2.1",
|
|
136
136
|
"prettier": "^3.9.6",
|
|
137
137
|
"ts-jest": "^29.4.12",
|
|
138
138
|
"typescript": "^6.0.3",
|
|
139
|
-
"typescript-eslint": "^8.
|
|
139
|
+
"typescript-eslint": "^8.66.0",
|
|
140
140
|
"werift-sctp": "^0.0.11"
|
|
141
141
|
}
|
|
142
142
|
}
|
|
@@ -93,6 +93,18 @@ namespace RTC
|
|
|
93
93
|
void SctpBufferedAmountLow(uint32_t bufferedAmount) const;
|
|
94
94
|
void SctpSendBufferFull() const;
|
|
95
95
|
void DataProducerClosed();
|
|
96
|
+
/**
|
|
97
|
+
* Verifies if given subchannels would allow a message to be delivered to
|
|
98
|
+
* the DataConsumer endpoint or not.
|
|
99
|
+
*
|
|
100
|
+
* @remarks
|
|
101
|
+
* - This method must be called before invoking `SendMessage()` if subchannels
|
|
102
|
+
* are given to `SendMessage()`.
|
|
103
|
+
*/
|
|
104
|
+
bool VerifySubchannels(
|
|
105
|
+
const std::vector<uint16_t>& subchannels,
|
|
106
|
+
std::optional<uint16_t> requiredSubchannel,
|
|
107
|
+
std::optional<uint16_t> ignoredSubchannel) const;
|
|
96
108
|
bool SendMessage(
|
|
97
109
|
RTC::SCTP::Message message,
|
|
98
110
|
std::vector<uint16_t>& subchannels,
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
"node_modules/brace-expansion": {
|
|
24
|
-
"version": "5.0.
|
|
25
|
-
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.
|
|
26
|
-
"integrity": "sha512-
|
|
24
|
+
"version": "5.0.9",
|
|
25
|
+
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
|
26
|
+
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"balanced-match": "^4.0.2"
|
|
@@ -105,9 +105,9 @@
|
|
|
105
105
|
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="
|
|
106
106
|
},
|
|
107
107
|
"brace-expansion": {
|
|
108
|
-
"version": "5.0.
|
|
109
|
-
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.
|
|
110
|
-
"integrity": "sha512-
|
|
108
|
+
"version": "5.0.9",
|
|
109
|
+
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
|
110
|
+
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
|
111
111
|
"requires": {
|
|
112
112
|
"balanced-match": "^4.0.2"
|
|
113
113
|
}
|
|
@@ -482,96 +482,93 @@ namespace RTC
|
|
|
482
482
|
this->listener->OnDataConsumerDataProducerClosed(this);
|
|
483
483
|
}
|
|
484
484
|
|
|
485
|
-
bool DataConsumer::
|
|
486
|
-
|
|
487
|
-
std::vector<uint16_t>& subchannels,
|
|
485
|
+
bool DataConsumer::VerifySubchannels(
|
|
486
|
+
const std::vector<uint16_t>& subchannels,
|
|
488
487
|
std::optional<uint16_t> requiredSubchannel,
|
|
489
|
-
std::optional<uint16_t> ignoredSubchannel
|
|
490
|
-
const onQueuedCallback* cb)
|
|
488
|
+
std::optional<uint16_t> ignoredSubchannel) const
|
|
491
489
|
{
|
|
492
490
|
MS_TRACE();
|
|
493
491
|
|
|
494
|
-
if
|
|
492
|
+
// Only verify subchannels if this is not a piped DataConsumer or if this
|
|
493
|
+
// is a piped DataConsumer and it's subscribed to at least one subchannel.
|
|
494
|
+
const bool verifySubchannels = !this->pipe || !this->subchannels.empty();
|
|
495
|
+
|
|
496
|
+
if (!verifySubchannels)
|
|
495
497
|
{
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
(*cb)(false, false);
|
|
499
|
-
delete cb;
|
|
500
|
-
}
|
|
498
|
+
return true;
|
|
499
|
+
}
|
|
501
500
|
|
|
501
|
+
// If a required subchannel is given, verify that this data consumer is
|
|
502
|
+
// subscribed to it.
|
|
503
|
+
if (requiredSubchannel.has_value() && !this->subchannels.contains(requiredSubchannel.value()))
|
|
504
|
+
{
|
|
502
505
|
return false;
|
|
503
506
|
}
|
|
504
507
|
|
|
505
|
-
//
|
|
506
|
-
//
|
|
507
|
-
|
|
508
|
+
// If an ignored subchannel is given, verify that this data consumer is
|
|
509
|
+
// not subscribed to it, otherwise don't send this message to it.
|
|
510
|
+
// NOTE: We don't check this in piped DataConsumers on purpose. Otherwise
|
|
511
|
+
// a message generated by peer X and forwarded to all peers in another
|
|
512
|
+
// host wouldn't pass this filter in the local PipeTransport.
|
|
513
|
+
if (!this->pipe && ignoredSubchannel.has_value() && this->subchannels.contains(ignoredSubchannel.value()))
|
|
514
|
+
{
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
508
517
|
|
|
509
|
-
|
|
518
|
+
// If subchannels are given, verify that this data consumer is subscribed
|
|
519
|
+
// to at least one of them.
|
|
520
|
+
if (!subchannels.empty())
|
|
510
521
|
{
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
522
|
+
bool subchannelMatched{ false };
|
|
523
|
+
|
|
524
|
+
for (const auto subchannel : subchannels)
|
|
514
525
|
{
|
|
515
|
-
if (
|
|
526
|
+
if (this->subchannels.contains(subchannel))
|
|
516
527
|
{
|
|
517
|
-
|
|
518
|
-
delete cb;
|
|
519
|
-
}
|
|
528
|
+
subchannelMatched = true;
|
|
520
529
|
|
|
521
|
-
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
522
532
|
}
|
|
523
533
|
|
|
524
|
-
|
|
525
|
-
// subscribed to it, otherwise don't send this message to it.
|
|
526
|
-
if (ignoredSubchannel.has_value() && this->subchannels.contains(ignoredSubchannel.value()))
|
|
534
|
+
if (!subchannelMatched)
|
|
527
535
|
{
|
|
528
|
-
if (cb)
|
|
529
|
-
{
|
|
530
|
-
(*cb)(false, false);
|
|
531
|
-
delete cb;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
536
|
return false;
|
|
535
537
|
}
|
|
538
|
+
}
|
|
536
539
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
if (!subchannels.empty())
|
|
540
|
-
{
|
|
541
|
-
bool subchannelMatched{ false };
|
|
542
|
-
|
|
543
|
-
for (const auto subchannel : subchannels)
|
|
544
|
-
{
|
|
545
|
-
if (this->subchannels.contains(subchannel))
|
|
546
|
-
{
|
|
547
|
-
subchannelMatched = true;
|
|
548
|
-
|
|
549
|
-
break;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
552
542
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
543
|
+
bool DataConsumer::SendMessage(
|
|
544
|
+
RTC::SCTP::Message message,
|
|
545
|
+
std::vector<uint16_t>& subchannels,
|
|
546
|
+
std::optional<uint16_t> requiredSubchannel,
|
|
547
|
+
std::optional<uint16_t> ignoredSubchannel,
|
|
548
|
+
const onQueuedCallback* cb)
|
|
549
|
+
{
|
|
550
|
+
MS_TRACE();
|
|
560
551
|
|
|
561
|
-
|
|
562
|
-
|
|
552
|
+
if (!IsActive())
|
|
553
|
+
{
|
|
554
|
+
if (cb)
|
|
555
|
+
{
|
|
556
|
+
(*cb)(false, false);
|
|
557
|
+
delete cb;
|
|
563
558
|
}
|
|
559
|
+
|
|
560
|
+
return false;
|
|
564
561
|
}
|
|
565
562
|
|
|
566
|
-
// If this is a piped DataConsumer, encode given subchannels
|
|
567
|
-
//
|
|
568
|
-
//
|
|
563
|
+
// If this is a piped DataConsumer, encode given subchannels at the beginning
|
|
564
|
+
// of the message payload so the receiving PipeTransport can decode them and
|
|
565
|
+
// apply them to its own DataConsumers.
|
|
569
566
|
if (this->pipe)
|
|
570
567
|
{
|
|
571
|
-
//
|
|
572
|
-
//
|
|
568
|
+
// Encode just those subchannels this DataConsumer is subscribed to.
|
|
569
|
+
// Otherwise the receiving Router would deliver the message to
|
|
573
570
|
// DataConsumers subscribed to subchannels that this pipe does not carry.
|
|
574
|
-
const bool reduceSubchannels =
|
|
571
|
+
const bool reduceSubchannels = !this->subchannels.empty();
|
|
575
572
|
|
|
576
573
|
std::vector<uint16_t> reducedSubchannels;
|
|
577
574
|
|
|
@@ -917,44 +917,57 @@ namespace RTC
|
|
|
917
917
|
|
|
918
918
|
auto& dataConsumers = this->mapDataProducerDataConsumers.at(dataProducer);
|
|
919
919
|
|
|
920
|
-
|
|
920
|
+
const auto getStreamId = [](const RTC::DataConsumer* dataConsumer) -> uint16_t
|
|
921
921
|
{
|
|
922
|
-
|
|
922
|
+
return dataConsumer->GetType() == DataConsumer::Type::SCTP
|
|
923
|
+
? dataConsumer->GetSctpStreamParameters().streamId
|
|
924
|
+
: 0;
|
|
925
|
+
};
|
|
923
926
|
|
|
924
|
-
|
|
927
|
+
// NOTE: We don't send the message to a matching DataConsumer right away.
|
|
928
|
+
// Instead we hold it as pending until we know whether there is another
|
|
929
|
+
// matching DataConsumer after it. This way just the last matching one needs
|
|
930
|
+
// no message clone since it can take ownership of the original message.
|
|
931
|
+
RTC::DataConsumer* pendingDataConsumer{ nullptr };
|
|
932
|
+
|
|
933
|
+
for (auto* dataConsumer : dataConsumers)
|
|
934
|
+
{
|
|
935
|
+
// Verify subchannels first to avoid cloning the message needlessly.
|
|
936
|
+
if (!dataConsumer->IsActive() || !dataConsumer->VerifySubchannels(subchannels, requiredSubchannel, ignoredSubchannel))
|
|
925
937
|
{
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
? dataConsumer->GetSctpStreamParameters().streamId
|
|
929
|
-
: 0);
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
930
940
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
dataConsumer->SendMessage(
|
|
941
|
-
std::move(message), subchannels, requiredSubchannel, ignoredSubchannel);
|
|
942
|
-
}
|
|
943
|
-
// NOTE: Here we are cloning the message before passing it to each
|
|
944
|
-
// DataConsumer because each DataConsumer will pass std::move(message)
|
|
945
|
-
// internally to its SCTP association.
|
|
946
|
-
else
|
|
947
|
-
{
|
|
948
|
-
auto clonedMessage = message.Clone();
|
|
941
|
+
// There is a matching DataConsumer after the pending one, so the pending
|
|
942
|
+
// one must be given a clone of the message.
|
|
943
|
+
// NOTE: Here we are cloning the message before passing it to the
|
|
944
|
+
// DataConsumer because each DataConsumer will pass std::move(message)
|
|
945
|
+
// internally to its SCTP association.
|
|
946
|
+
if (pendingDataConsumer)
|
|
947
|
+
{
|
|
948
|
+
auto clonedMessage = message.Clone();
|
|
949
949
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
950
|
+
// We must update the message`s `streamId` for each destination
|
|
951
|
+
// DataConsumer.
|
|
952
|
+
clonedMessage.SetStreamId(getStreamId(pendingDataConsumer));
|
|
953
953
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
}
|
|
954
|
+
pendingDataConsumer->SendMessage(
|
|
955
|
+
std::move(clonedMessage), subchannels, requiredSubchannel, ignoredSubchannel);
|
|
957
956
|
}
|
|
957
|
+
|
|
958
|
+
pendingDataConsumer = dataConsumer;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// The last matching DataConsumer (if any) takes ownership of the original
|
|
962
|
+
// message.
|
|
963
|
+
if (pendingDataConsumer)
|
|
964
|
+
{
|
|
965
|
+
// We must update the message`s `streamId` for each destination
|
|
966
|
+
// DataConsumer.
|
|
967
|
+
message.SetStreamId(getStreamId(pendingDataConsumer));
|
|
968
|
+
|
|
969
|
+
pendingDataConsumer->SendMessage(
|
|
970
|
+
std::move(message), subchannels, requiredSubchannel, ignoredSubchannel);
|
|
958
971
|
}
|
|
959
972
|
}
|
|
960
973
|
|