@webex/plugin-meetings 3.12.0-next.57 → 3.12.0-next.59
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/dist/aiEnableRequest/index.js +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +24 -1
- package/dist/breakouts/index.js.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/media/index.js +3 -1
- package/dist/media/index.js.map +1 -1
- package/dist/meeting/index.js +37 -10
- package/dist/meeting/index.js.map +1 -1
- package/dist/meetings/index.js +23 -0
- package/dist/meetings/index.js.map +1 -1
- package/dist/multistream/codec/constants.js +63 -0
- package/dist/multistream/codec/constants.js.map +1 -0
- package/dist/multistream/mediaRequestManager.js +62 -15
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/types/meeting/index.d.ts +9 -0
- package/dist/types/meetings/index.d.ts +10 -0
- package/dist/types/multistream/codec/constants.d.ts +7 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +22 -5
- package/dist/webinar/index.js +1 -1
- package/package.json +1 -1
- package/src/breakouts/index.ts +30 -0
- package/src/config.ts +1 -0
- package/src/media/index.ts +3 -0
- package/src/meeting/index.ts +41 -2
- package/src/meetings/index.ts +21 -0
- package/src/multistream/codec/constants.ts +58 -0
- package/src/multistream/mediaRequestManager.ts +119 -28
- package/test/unit/spec/breakouts/index.ts +47 -0
- package/test/unit/spec/media/index.ts +31 -0
- package/test/unit/spec/meeting/index.js +154 -0
- package/test/unit/spec/meetings/index.js +27 -0
- package/test/unit/spec/multistream/mediaRequestManager.ts +501 -37
package/src/meeting/index.ts
CHANGED
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
MediaConnectionEventNames,
|
|
23
23
|
MediaContent,
|
|
24
24
|
MediaType,
|
|
25
|
-
MediaCodecMimeType,
|
|
26
25
|
RemoteTrackType,
|
|
27
26
|
RoapMessage,
|
|
28
27
|
StatsAnalyzer,
|
|
@@ -31,7 +30,7 @@ import {
|
|
|
31
30
|
NetworkQualityMonitor,
|
|
32
31
|
StatsMonitor,
|
|
33
32
|
StatsMonitorEventNames,
|
|
34
|
-
|
|
33
|
+
MediaCodecMimeType,
|
|
35
34
|
} from '@webex/internal-media-core';
|
|
36
35
|
|
|
37
36
|
import {DataChannelTokenType} from '@webex/internal-plugin-llm';
|
|
@@ -969,6 +968,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
969
968
|
},
|
|
970
969
|
(csi: CSI) => (this.members.findMemberByCsi(csi) as any)?.id
|
|
971
970
|
);
|
|
971
|
+
|
|
972
972
|
/**
|
|
973
973
|
* Object containing helper classes for managing media requests for audio/video/screenshare (for multistream media connections)
|
|
974
974
|
* All multistream media requests sent out for this meeting have to go through them.
|
|
@@ -988,6 +988,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
988
988
|
mediaRequests
|
|
989
989
|
);
|
|
990
990
|
},
|
|
991
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
991
992
|
{
|
|
992
993
|
// @ts-ignore - config coming from registerPlugin
|
|
993
994
|
degradationPreferences: this.config.degradationPreferences,
|
|
@@ -1009,6 +1010,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1009
1010
|
mediaRequests
|
|
1010
1011
|
);
|
|
1011
1012
|
},
|
|
1013
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
1012
1014
|
{
|
|
1013
1015
|
// @ts-ignore - config coming from registerPlugin
|
|
1014
1016
|
degradationPreferences: this.config.degradationPreferences,
|
|
@@ -1030,6 +1032,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1030
1032
|
mediaRequests
|
|
1031
1033
|
);
|
|
1032
1034
|
},
|
|
1035
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
1033
1036
|
{
|
|
1034
1037
|
// @ts-ignore - config coming from registerPlugin
|
|
1035
1038
|
degradationPreferences: this.config.degradationPreferences,
|
|
@@ -1051,11 +1054,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1051
1054
|
mediaRequests
|
|
1052
1055
|
);
|
|
1053
1056
|
},
|
|
1057
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
1054
1058
|
{
|
|
1055
1059
|
// @ts-ignore - config coming from registerPlugin
|
|
1056
1060
|
degradationPreferences: this.config.degradationPreferences,
|
|
1057
1061
|
kind: 'video',
|
|
1058
1062
|
trimRequestsToNumOfSources: false,
|
|
1063
|
+
// @ts-ignore - config coming from registerPlugin
|
|
1064
|
+
enableAv1: this.config.enableAv1SlidesSupport,
|
|
1059
1065
|
}
|
|
1060
1066
|
),
|
|
1061
1067
|
};
|
|
@@ -1716,6 +1722,37 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1716
1722
|
this.mediaServerIp = undefined;
|
|
1717
1723
|
}
|
|
1718
1724
|
|
|
1725
|
+
/**
|
|
1726
|
+
* Get the ingress payload type for a given media type and codec mime type
|
|
1727
|
+
* @param {MediaType} mediaType - The media type
|
|
1728
|
+
* @param {MediaCodecMimeType} codecMimeType - The codec mime type
|
|
1729
|
+
* @returns {number | undefined} - The ingress payload type
|
|
1730
|
+
* @private
|
|
1731
|
+
* @memberof Meeting
|
|
1732
|
+
*/
|
|
1733
|
+
private getIngressPayloadTypeCallback(
|
|
1734
|
+
mediaType: MediaType,
|
|
1735
|
+
codecMimeType: MediaCodecMimeType
|
|
1736
|
+
): number | undefined {
|
|
1737
|
+
if (this.isMultistream) {
|
|
1738
|
+
try {
|
|
1739
|
+
return this.mediaProperties.webrtcMediaConnection.getIngressPayloadType(
|
|
1740
|
+
mediaType,
|
|
1741
|
+
codecMimeType
|
|
1742
|
+
);
|
|
1743
|
+
} catch (error) {
|
|
1744
|
+
LoggerProxy.logger.info(
|
|
1745
|
+
`Meeting:index#mediaRequestManager --> failed to get ingress payload type for mediaType=${mediaType}, codecMimeType=${codecMimeType}`,
|
|
1746
|
+
error
|
|
1747
|
+
);
|
|
1748
|
+
|
|
1749
|
+
return undefined;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
return undefined;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1719
1756
|
/**
|
|
1720
1757
|
* Temporary func to return webex object,
|
|
1721
1758
|
* in order to access internal plugin metrics
|
|
@@ -7768,6 +7805,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7768
7805
|
disableAudioMainDtx: this.config.experimental.disableAudioMainDtx,
|
|
7769
7806
|
// @ts-ignore - config coming from registerPlugin
|
|
7770
7807
|
enableAudioTwcc: this.config.enableAudioTwccForMultistream,
|
|
7808
|
+
// @ts-ignore - config coming from registerPlugin
|
|
7809
|
+
enableAv1SlidesSupport: this.config.enableAv1SlidesSupport,
|
|
7771
7810
|
stopIceGatheringAfterFirstRelayCandidate:
|
|
7772
7811
|
// @ts-ignore - config coming from registerPlugin
|
|
7773
7812
|
this.config.stopIceGatheringAfterFirstRelayCandidate,
|
package/src/meetings/index.ts
CHANGED
|
@@ -935,6 +935,27 @@ export default class Meetings extends WebexPlugin {
|
|
|
935
935
|
}
|
|
936
936
|
}
|
|
937
937
|
|
|
938
|
+
/**
|
|
939
|
+
* API to toggle AV1 codec support for video slides in multistream,
|
|
940
|
+
* needs to be called before webex.meetings.joinWithMedia()
|
|
941
|
+
*
|
|
942
|
+
* @param {Boolean} newValue
|
|
943
|
+
* @private
|
|
944
|
+
* @memberof Meetings
|
|
945
|
+
* @returns {undefined}
|
|
946
|
+
*/
|
|
947
|
+
private _toggleEnableAv1SlidesSupport(newValue: boolean) {
|
|
948
|
+
if (typeof newValue !== 'boolean') {
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// @ts-ignore
|
|
953
|
+
if (this.config.enableAv1SlidesSupport !== newValue) {
|
|
954
|
+
// @ts-ignore
|
|
955
|
+
this.config.enableAv1SlidesSupport = newValue;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
938
959
|
/**
|
|
939
960
|
* API to toggle stopping ICE Candidates Gathering after first relay candidate,
|
|
940
961
|
* needs to be called before webex.meetings.joinWithMedia()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {AV1EncodingParams, SupportedResolution} from '@webex/internal-media-core';
|
|
2
|
+
|
|
3
|
+
export const AV1_CODEC_PARAMETERS: Record<SupportedResolution, AV1EncodingParams> = {
|
|
4
|
+
'90p': {
|
|
5
|
+
levelIdx: 0,
|
|
6
|
+
tier: 0,
|
|
7
|
+
maxWidth: 160,
|
|
8
|
+
maxHeight: 90,
|
|
9
|
+
maxPicSize: 160 * 90,
|
|
10
|
+
maxDecodeRate: 5_529_600,
|
|
11
|
+
},
|
|
12
|
+
'180p': {
|
|
13
|
+
levelIdx: 0,
|
|
14
|
+
tier: 0,
|
|
15
|
+
maxWidth: 320,
|
|
16
|
+
maxHeight: 180,
|
|
17
|
+
maxPicSize: 320 * 180,
|
|
18
|
+
maxDecodeRate: 5_529_600,
|
|
19
|
+
},
|
|
20
|
+
'360p': {
|
|
21
|
+
levelIdx: 1,
|
|
22
|
+
tier: 0,
|
|
23
|
+
maxWidth: 640,
|
|
24
|
+
maxHeight: 360,
|
|
25
|
+
maxPicSize: 640 * 360,
|
|
26
|
+
maxDecodeRate: 10_454_400,
|
|
27
|
+
},
|
|
28
|
+
'540p': {
|
|
29
|
+
levelIdx: 4,
|
|
30
|
+
tier: 0,
|
|
31
|
+
maxWidth: 960,
|
|
32
|
+
maxHeight: 540,
|
|
33
|
+
maxPicSize: 960 * 540,
|
|
34
|
+
maxDecodeRate: 24_969_600,
|
|
35
|
+
},
|
|
36
|
+
'720p': {
|
|
37
|
+
levelIdx: 5,
|
|
38
|
+
tier: 0,
|
|
39
|
+
maxWidth: 1280,
|
|
40
|
+
maxHeight: 720,
|
|
41
|
+
maxPicSize: 1280 * 720,
|
|
42
|
+
maxDecodeRate: 39_938_400,
|
|
43
|
+
},
|
|
44
|
+
'1080p': {
|
|
45
|
+
levelIdx: 8,
|
|
46
|
+
tier: 0,
|
|
47
|
+
maxWidth: 1920,
|
|
48
|
+
maxHeight: 1080,
|
|
49
|
+
maxPicSize: 1920 * 1080,
|
|
50
|
+
maxDecodeRate: 77_856_768,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const H264_CODEC_PARAMETERS = {
|
|
55
|
+
maxFs: 8192,
|
|
56
|
+
maxFps: 3000,
|
|
57
|
+
maxMbps: 245760,
|
|
58
|
+
};
|
|
@@ -9,6 +9,11 @@ import {
|
|
|
9
9
|
getRecommendedMaxBitrateForFrameSize,
|
|
10
10
|
RecommendedOpusBitrates,
|
|
11
11
|
NamedMediaGroup,
|
|
12
|
+
AV1Codec,
|
|
13
|
+
SupportedResolution,
|
|
14
|
+
AV1EncodingParams,
|
|
15
|
+
MediaType,
|
|
16
|
+
MediaCodecMimeType,
|
|
12
17
|
} from '@webex/internal-media-core';
|
|
13
18
|
import {cloneDeepWith, debounce} from 'lodash';
|
|
14
19
|
|
|
@@ -16,6 +21,7 @@ import LoggerProxy from '../common/logs/logger-proxy';
|
|
|
16
21
|
|
|
17
22
|
import {ReceiveSlot, ReceiveSlotEvents} from './receiveSlot';
|
|
18
23
|
import {MAX_FS_VALUES} from './remoteMedia';
|
|
24
|
+
import {AV1_CODEC_PARAMETERS, H264_CODEC_PARAMETERS} from './codec/constants';
|
|
19
25
|
|
|
20
26
|
export interface ActiveSpeakerPolicyInfo {
|
|
21
27
|
policy: 'active-speaker';
|
|
@@ -54,34 +60,49 @@ export interface MediaRequest {
|
|
|
54
60
|
|
|
55
61
|
export type MediaRequestId = string;
|
|
56
62
|
|
|
57
|
-
const CODEC_DEFAULTS = {
|
|
58
|
-
h264: {
|
|
59
|
-
maxFs: 8192,
|
|
60
|
-
maxFps: 3000,
|
|
61
|
-
maxMbps: 245760,
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
|
|
65
63
|
const DEBOUNCED_SOURCE_UPDATE_TIME = 1000;
|
|
66
64
|
|
|
65
|
+
const RESOLUTION_BUCKETS: Array<[SupportedResolution, number]> = [
|
|
66
|
+
['90p', MAX_FS_VALUES['90p']],
|
|
67
|
+
['180p', MAX_FS_VALUES['180p']],
|
|
68
|
+
['360p', MAX_FS_VALUES['360p']],
|
|
69
|
+
['540p', MAX_FS_VALUES['540p']],
|
|
70
|
+
['720p', MAX_FS_VALUES['720p']],
|
|
71
|
+
];
|
|
72
|
+
|
|
67
73
|
type DegradationPreferences = {
|
|
68
74
|
maxMacroblocksLimit: number;
|
|
69
75
|
};
|
|
70
76
|
|
|
71
77
|
type SendMediaRequestsCallback = (streamRequests: StreamRequest[]) => void;
|
|
78
|
+
type GetIngressPayloadTypeCallback = (
|
|
79
|
+
mediaType: MediaType,
|
|
80
|
+
codecMimeType: MediaCodecMimeType
|
|
81
|
+
) => number | undefined;
|
|
72
82
|
type Kind = 'audio' | 'video';
|
|
73
83
|
|
|
74
|
-
type
|
|
84
|
+
type AudioMediaRequestManagerOptions = {
|
|
75
85
|
degradationPreferences: DegradationPreferences;
|
|
76
|
-
kind:
|
|
86
|
+
kind: 'audio';
|
|
77
87
|
trimRequestsToNumOfSources: boolean; // if enabled, AS speaker requests will be trimmed based on the calls to setNumCurrentSources()
|
|
78
88
|
};
|
|
79
89
|
|
|
90
|
+
type VideoMediaRequestManagerOptions = {
|
|
91
|
+
degradationPreferences: DegradationPreferences;
|
|
92
|
+
kind: 'video';
|
|
93
|
+
trimRequestsToNumOfSources: boolean;
|
|
94
|
+
enableAv1?: boolean;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
type Options = AudioMediaRequestManagerOptions | VideoMediaRequestManagerOptions;
|
|
98
|
+
|
|
80
99
|
type ClientRequestsMap = {[key: MediaRequestId]: MediaRequest};
|
|
81
100
|
|
|
82
101
|
export class MediaRequestManager {
|
|
83
102
|
private sendMediaRequestsCallback: SendMediaRequestsCallback;
|
|
84
103
|
|
|
104
|
+
private getIngressPayloadTypeCallback: GetIngressPayloadTypeCallback;
|
|
105
|
+
|
|
85
106
|
private kind: Kind;
|
|
86
107
|
|
|
87
108
|
private counter: number;
|
|
@@ -95,11 +116,17 @@ export class MediaRequestManager {
|
|
|
95
116
|
private debouncedSourceUpdateListener: () => void;
|
|
96
117
|
|
|
97
118
|
private trimRequestsToNumOfSources: boolean;
|
|
119
|
+
private enableAv1: boolean;
|
|
98
120
|
private numTotalSources: number;
|
|
99
121
|
private numLiveSources: number;
|
|
100
122
|
|
|
101
|
-
constructor(
|
|
123
|
+
constructor(
|
|
124
|
+
sendMediaRequestsCallback: SendMediaRequestsCallback,
|
|
125
|
+
getIngressPayloadTypeCallback: GetIngressPayloadTypeCallback,
|
|
126
|
+
options: Options
|
|
127
|
+
) {
|
|
102
128
|
this.sendMediaRequestsCallback = sendMediaRequestsCallback;
|
|
129
|
+
this.getIngressPayloadTypeCallback = getIngressPayloadTypeCallback;
|
|
103
130
|
this.counter = 0;
|
|
104
131
|
this.numLiveSources = 0;
|
|
105
132
|
this.numTotalSources = 0;
|
|
@@ -107,6 +134,7 @@ export class MediaRequestManager {
|
|
|
107
134
|
this.degradationPreferences = options.degradationPreferences;
|
|
108
135
|
this.kind = options.kind;
|
|
109
136
|
this.trimRequestsToNumOfSources = options.trimRequestsToNumOfSources;
|
|
137
|
+
this.enableAv1 = options.kind === 'video' && !!options.enableAv1;
|
|
110
138
|
this.sourceUpdateListener = this.commit.bind(this);
|
|
111
139
|
this.debouncedSourceUpdateListener = debounce(
|
|
112
140
|
this.sourceUpdateListener,
|
|
@@ -135,8 +163,8 @@ export class MediaRequestManager {
|
|
|
135
163
|
Object.values(clientRequests).forEach((mr) => {
|
|
136
164
|
if (mr.codecInfo) {
|
|
137
165
|
mr.codecInfo.maxFs = Math.min(
|
|
138
|
-
mr.preferredMaxFs ||
|
|
139
|
-
mr.codecInfo.maxFs ||
|
|
166
|
+
mr.preferredMaxFs || H264_CODEC_PARAMETERS.maxFs,
|
|
167
|
+
mr.codecInfo.maxFs || H264_CODEC_PARAMETERS.maxFs,
|
|
140
168
|
maxFsLimits[i]
|
|
141
169
|
);
|
|
142
170
|
// we only consider sources with "live" state
|
|
@@ -176,7 +204,7 @@ export class MediaRequestManager {
|
|
|
176
204
|
}
|
|
177
205
|
|
|
178
206
|
return getRecommendedMaxBitrateForFrameSize(
|
|
179
|
-
mediaRequest.codecInfo.maxFs ||
|
|
207
|
+
mediaRequest.codecInfo.maxFs || H264_CODEC_PARAMETERS.maxFs
|
|
180
208
|
);
|
|
181
209
|
}
|
|
182
210
|
|
|
@@ -192,12 +220,80 @@ export class MediaRequestManager {
|
|
|
192
220
|
// eslint-disable-next-line class-methods-use-this
|
|
193
221
|
private getH264MaxMbps(mediaRequest: MediaRequest): number {
|
|
194
222
|
// fallback for maxFps (not needed for maxFs, since there is a fallback already in getDegradedClientRequests)
|
|
195
|
-
const maxFps = mediaRequest.codecInfo.maxFps ||
|
|
223
|
+
const maxFps = mediaRequest.codecInfo.maxFps || H264_CODEC_PARAMETERS.maxFps;
|
|
196
224
|
|
|
197
225
|
// divided by 100 since maxFps is 3000 (for 30 frames per seconds)
|
|
198
226
|
return (mediaRequest.codecInfo.maxFs * maxFps) / 100;
|
|
199
227
|
}
|
|
200
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Returns the AV1 encoding parameters for a media request
|
|
231
|
+
* @param mediaRequest - The media request to get the AV1 encoding parameters for
|
|
232
|
+
* @returns {AV1EncodingParams} The AV1 encoding parameters
|
|
233
|
+
*/
|
|
234
|
+
// eslint-disable-next-line class-methods-use-this
|
|
235
|
+
private getAv1EncodingParams(mediaRequest: MediaRequest): AV1EncodingParams {
|
|
236
|
+
const frameSize = mediaRequest.codecInfo.maxFs || H264_CODEC_PARAMETERS.maxFs;
|
|
237
|
+
const resolution = RESOLUTION_BUCKETS.find(([, maxFs]) => frameSize <= maxFs)?.[0] ?? '1080p';
|
|
238
|
+
|
|
239
|
+
return AV1_CODEC_PARAMETERS[resolution];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private buildH264CodecInfo(mr: MediaRequest): WcmeCodecInfo | undefined {
|
|
243
|
+
if (!mr.codecInfo) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const h264PayloadType = this.getIngressPayloadTypeCallback(
|
|
248
|
+
mr.receiveSlots[0].mediaType,
|
|
249
|
+
MediaCodecMimeType.H264
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
if (h264PayloadType === undefined) {
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return WcmeCodecInfo.fromH264(
|
|
257
|
+
h264PayloadType,
|
|
258
|
+
new H264Codec(
|
|
259
|
+
mr.codecInfo.maxFs,
|
|
260
|
+
mr.codecInfo.maxFps || H264_CODEC_PARAMETERS.maxFps,
|
|
261
|
+
this.getH264MaxMbps(mr),
|
|
262
|
+
mr.codecInfo.maxWidth,
|
|
263
|
+
mr.codecInfo.maxHeight
|
|
264
|
+
)
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private buildAv1CodecInfo(mr: MediaRequest): WcmeCodecInfo | undefined {
|
|
269
|
+
if (!this.enableAv1 || !mr.codecInfo) {
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const av1PayloadType = this.getIngressPayloadTypeCallback(
|
|
274
|
+
mr.receiveSlots[0].mediaType,
|
|
275
|
+
MediaCodecMimeType.AV1
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
if (av1PayloadType === undefined) {
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const av1EncodingParams = this.getAv1EncodingParams(mr);
|
|
283
|
+
|
|
284
|
+
return WcmeCodecInfo.fromAv1(
|
|
285
|
+
av1PayloadType,
|
|
286
|
+
new AV1Codec(
|
|
287
|
+
av1EncodingParams.levelIdx,
|
|
288
|
+
av1EncodingParams.tier,
|
|
289
|
+
mr.codecInfo.maxWidth || av1EncodingParams.maxWidth,
|
|
290
|
+
mr.codecInfo.maxHeight || av1EncodingParams.maxHeight,
|
|
291
|
+
av1EncodingParams.maxPicSize,
|
|
292
|
+
av1EncodingParams.maxDecodeRate
|
|
293
|
+
)
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
201
297
|
/** Modifies the passed in clientRequests and makes sure that in total they don't ask
|
|
202
298
|
* for more streams than there are available.
|
|
203
299
|
*
|
|
@@ -298,6 +394,12 @@ export class MediaRequestManager {
|
|
|
298
394
|
// map all the client media requests to wcme stream requests
|
|
299
395
|
Object.values(clientRequests).forEach((mr) => {
|
|
300
396
|
if (mr.receiveSlots.length > 0) {
|
|
397
|
+
const codecInfos: WcmeCodecInfo[] = mr.codecInfo
|
|
398
|
+
? [this.buildH264CodecInfo(mr), this.buildAv1CodecInfo(mr)].filter(
|
|
399
|
+
(info): info is WcmeCodecInfo => info !== undefined
|
|
400
|
+
)
|
|
401
|
+
: [];
|
|
402
|
+
|
|
301
403
|
streamRequests.push(
|
|
302
404
|
new StreamRequest(
|
|
303
405
|
mr.policyInfo.policy === 'active-speaker'
|
|
@@ -314,25 +416,14 @@ export class MediaRequestManager {
|
|
|
314
416
|
: new ReceiverSelectedInfo(mr.policyInfo.csi),
|
|
315
417
|
mr.receiveSlots.map((receiveSlot) => receiveSlot.wcmeReceiveSlot),
|
|
316
418
|
this.getMaxPayloadBitsPerSecond(mr),
|
|
317
|
-
|
|
318
|
-
WcmeCodecInfo.fromH264(
|
|
319
|
-
0x80,
|
|
320
|
-
new H264Codec(
|
|
321
|
-
mr.codecInfo.maxFs,
|
|
322
|
-
mr.codecInfo.maxFps || CODEC_DEFAULTS.h264.maxFps,
|
|
323
|
-
this.getH264MaxMbps(mr),
|
|
324
|
-
mr.codecInfo.maxWidth,
|
|
325
|
-
mr.codecInfo.maxHeight
|
|
326
|
-
)
|
|
327
|
-
),
|
|
328
|
-
]
|
|
419
|
+
codecInfos
|
|
329
420
|
)
|
|
330
421
|
);
|
|
331
422
|
}
|
|
332
423
|
});
|
|
333
424
|
|
|
334
425
|
this.sendMediaRequestsCallback(streamRequests);
|
|
335
|
-
LoggerProxy.logger.info(`multistream:sendRequests --> media requests sent
|
|
426
|
+
LoggerProxy.logger.info(`multistream:sendRequests --> media requests sent.`);
|
|
336
427
|
}
|
|
337
428
|
|
|
338
429
|
public addRequest(mediaRequest: MediaRequest, commit = true): MediaRequestId {
|
|
@@ -1849,6 +1849,53 @@ describe('plugin-meetings', () => {
|
|
|
1849
1849
|
});
|
|
1850
1850
|
});
|
|
1851
1851
|
|
|
1852
|
+
describe('#removeFromBreakout', () => {
|
|
1853
|
+
it('should make a POST request with correct body and return the result', async () => {
|
|
1854
|
+
breakouts.request = sinon.stub().returns(Promise.resolve('REQUEST_RETURN_VALUE'));
|
|
1855
|
+
breakouts.set('url', 'url');
|
|
1856
|
+
breakouts.set('mainGroupId', 'mainGroupId');
|
|
1857
|
+
breakouts.set('mainSessionId', 'mainSessionId');
|
|
1858
|
+
|
|
1859
|
+
const participants = ['participant1', 'participant2'];
|
|
1860
|
+
const result = await breakouts.removeFromBreakout(participants);
|
|
1861
|
+
|
|
1862
|
+
assert.calledOnceWithExactly(breakouts.request, {
|
|
1863
|
+
method: 'POST',
|
|
1864
|
+
uri: 'url/move',
|
|
1865
|
+
body: {
|
|
1866
|
+
groups: [
|
|
1867
|
+
{
|
|
1868
|
+
id: 'mainGroupId',
|
|
1869
|
+
sessions: [
|
|
1870
|
+
{
|
|
1871
|
+
id: 'mainSessionId',
|
|
1872
|
+
participants,
|
|
1873
|
+
},
|
|
1874
|
+
],
|
|
1875
|
+
},
|
|
1876
|
+
],
|
|
1877
|
+
},
|
|
1878
|
+
});
|
|
1879
|
+
assert.equal(result, 'REQUEST_RETURN_VALUE');
|
|
1880
|
+
});
|
|
1881
|
+
|
|
1882
|
+
it('should throw an error if mainGroupId is missing', () => {
|
|
1883
|
+
breakouts.set('mainSessionId', 'mainSessionId');
|
|
1884
|
+
assert.throws(
|
|
1885
|
+
() => breakouts.removeFromBreakout(['participant1']),
|
|
1886
|
+
'Main group ID and session ID must be available to remove participants from breakout'
|
|
1887
|
+
);
|
|
1888
|
+
});
|
|
1889
|
+
|
|
1890
|
+
it('should throw an error if mainSessionId is missing', () => {
|
|
1891
|
+
breakouts.set('mainGroupId', 'mainGroupId');
|
|
1892
|
+
assert.throws(
|
|
1893
|
+
() => breakouts.removeFromBreakout(['participant1']),
|
|
1894
|
+
'Main group ID and session ID must be available to remove participants from breakout'
|
|
1895
|
+
);
|
|
1896
|
+
});
|
|
1897
|
+
});
|
|
1898
|
+
|
|
1852
1899
|
describe('#triggerReturnToMainEvent', () => {
|
|
1853
1900
|
const checkTrigger = ({breakout, shouldTrigger}) => {
|
|
1854
1901
|
breakouts.trigger = sinon.stub();
|
|
@@ -295,6 +295,7 @@ describe('createMediaConnection', () => {
|
|
|
295
295
|
bundlePolicy: 'max-bundle',
|
|
296
296
|
disableAudioMainDtx: false,
|
|
297
297
|
disableAudioTwcc: false,
|
|
298
|
+
enableAV1SlidesSupport: false,
|
|
298
299
|
},
|
|
299
300
|
'meeting id'
|
|
300
301
|
);
|
|
@@ -322,6 +323,26 @@ describe('createMediaConnection', () => {
|
|
|
322
323
|
assert.calledOnce(rtcMetrics.sendMetricsInQueue);
|
|
323
324
|
});
|
|
324
325
|
|
|
326
|
+
it('passes enableAV1SlidesSupport: true to MultistreamRoapMediaConnection when enableAv1SlidesSupport is set', () => {
|
|
327
|
+
const multistreamRoapMediaConnectionConstructorStub = sinon
|
|
328
|
+
.stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
|
|
329
|
+
.returns(fakeRoapMediaConnection);
|
|
330
|
+
|
|
331
|
+
Media.createMediaConnection(true, 'some debug id', 'meeting id', {
|
|
332
|
+
enableAv1SlidesSupport: true,
|
|
333
|
+
});
|
|
334
|
+
assert.calledOnce(multistreamRoapMediaConnectionConstructorStub);
|
|
335
|
+
assert.calledWith(
|
|
336
|
+
multistreamRoapMediaConnectionConstructorStub,
|
|
337
|
+
sinon.match({
|
|
338
|
+
iceServers: [],
|
|
339
|
+
disableAudioTwcc: true,
|
|
340
|
+
enableAV1SlidesSupport: true,
|
|
341
|
+
}),
|
|
342
|
+
'meeting id'
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
|
|
325
346
|
it('multistream non-firefox does not care about stopIceGatheringAfterFirstRelayCandidate', () => {
|
|
326
347
|
const multistreamRoapMediaConnectionConstructorStub = sinon
|
|
327
348
|
.stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
|
|
@@ -336,6 +357,7 @@ describe('createMediaConnection', () => {
|
|
|
336
357
|
{
|
|
337
358
|
iceServers: [],
|
|
338
359
|
disableAudioTwcc: true,
|
|
360
|
+
enableAV1SlidesSupport: false,
|
|
339
361
|
},
|
|
340
362
|
'meeting id'
|
|
341
363
|
);
|
|
@@ -359,6 +381,7 @@ describe('createMediaConnection', () => {
|
|
|
359
381
|
doFullIce: true,
|
|
360
382
|
stopIceGatheringAfterFirstRelayCandidate: true,
|
|
361
383
|
disableAudioTwcc: true,
|
|
384
|
+
enableAV1SlidesSupport: false,
|
|
362
385
|
},
|
|
363
386
|
'meeting id'
|
|
364
387
|
);
|
|
@@ -382,6 +405,7 @@ describe('createMediaConnection', () => {
|
|
|
382
405
|
doFullIce: true,
|
|
383
406
|
stopIceGatheringAfterFirstRelayCandidate: false,
|
|
384
407
|
disableAudioTwcc: true,
|
|
408
|
+
enableAV1SlidesSupport: false,
|
|
385
409
|
},
|
|
386
410
|
'meeting id'
|
|
387
411
|
);
|
|
@@ -418,6 +442,7 @@ describe('createMediaConnection', () => {
|
|
|
418
442
|
{
|
|
419
443
|
iceServers: [],
|
|
420
444
|
disableAudioTwcc: true,
|
|
445
|
+
enableAV1SlidesSupport: false,
|
|
421
446
|
},
|
|
422
447
|
'meeting id'
|
|
423
448
|
);
|
|
@@ -448,6 +473,7 @@ describe('createMediaConnection', () => {
|
|
|
448
473
|
{
|
|
449
474
|
iceServers: [],
|
|
450
475
|
disableAudioTwcc: true,
|
|
476
|
+
enableAV1SlidesSupport: false,
|
|
451
477
|
},
|
|
452
478
|
'meeting id'
|
|
453
479
|
);
|
|
@@ -477,6 +503,7 @@ describe('createMediaConnection', () => {
|
|
|
477
503
|
{
|
|
478
504
|
iceServers: [],
|
|
479
505
|
disableAudioTwcc: true,
|
|
506
|
+
enableAV1SlidesSupport: false,
|
|
480
507
|
},
|
|
481
508
|
'meeting id'
|
|
482
509
|
);
|
|
@@ -505,6 +532,7 @@ describe('createMediaConnection', () => {
|
|
|
505
532
|
{
|
|
506
533
|
iceServers: [],
|
|
507
534
|
disableAudioTwcc: true,
|
|
535
|
+
enableAV1SlidesSupport: false,
|
|
508
536
|
},
|
|
509
537
|
'meeting id'
|
|
510
538
|
);
|
|
@@ -591,6 +619,7 @@ describe('createMediaConnection', () => {
|
|
|
591
619
|
{
|
|
592
620
|
iceServers: [],
|
|
593
621
|
disableAudioTwcc: true,
|
|
622
|
+
enableAV1SlidesSupport: false,
|
|
594
623
|
enableInboundAudioLevelMonitoring: true,
|
|
595
624
|
}
|
|
596
625
|
);
|
|
@@ -602,6 +631,7 @@ describe('createMediaConnection', () => {
|
|
|
602
631
|
{
|
|
603
632
|
iceServers: [],
|
|
604
633
|
disableAudioTwcc: true,
|
|
634
|
+
enableAV1SlidesSupport: false,
|
|
605
635
|
enableInboundAudioLevelMonitoring: true,
|
|
606
636
|
}
|
|
607
637
|
);
|
|
@@ -613,6 +643,7 @@ describe('createMediaConnection', () => {
|
|
|
613
643
|
{
|
|
614
644
|
iceServers: [],
|
|
615
645
|
disableAudioTwcc: true,
|
|
646
|
+
enableAV1SlidesSupport: false,
|
|
616
647
|
doFullIce: true,
|
|
617
648
|
stopIceGatheringAfterFirstRelayCandidate: undefined,
|
|
618
649
|
}
|