@webex/plugin-meetings 3.8.0-next.60 → 3.8.0-next.62

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.
@@ -743,6 +743,7 @@ export declare enum SELF_POLICY {
743
743
  SUPPORT_NETWORK_BASED_RECORD = "supportNetworkBasedRecord",
744
744
  SUPPORT_PREMISE_RECORD = "supportPremiseRecord",
745
745
  SUPPORT_REALTIME_CLOSE_CAPTION = "supportRealtimeCloseCaption",
746
+ SUPPORT_REALTIME_CLOSE_CAPTION_MANUAL = "supportRealtimeCloseCaptionManual",
746
747
  SUPPORT_CHAT = "supportChat",
747
748
  SUPPORT_DESKTOP_SHARE_REMOTE = "supportDesktopShareRemote",
748
749
  SUPPORT_DESKTOP_SHARE = "supportDesktopShare",
@@ -1,4 +1,5 @@
1
1
  import { LocalCameraStream, LocalMicrophoneStream, LocalDisplayStream, LocalSystemAudioStream, RemoteStream } from '@webex/media-helpers';
2
+ import { ClientEvent } from '@webex/internal-plugin-metrics';
2
3
  export type MediaDirection = {
3
4
  sendAudio: boolean;
4
5
  sendVideo: boolean;
@@ -7,6 +8,7 @@ export type MediaDirection = {
7
8
  receiveVideo: boolean;
8
9
  receiveShare: boolean;
9
10
  };
11
+ export type IPVersion = ClientEvent['payload']['ipVersion'];
10
12
  /**
11
13
  * @class MediaProperties
12
14
  */
@@ -94,6 +96,18 @@ export default class MediaProperties {
94
96
  * @returns {Object}
95
97
  */
96
98
  private getTransportInfo;
99
+ /**
100
+ * Checks if the given IP address is IPv6
101
+ * @param {string} ip address to check
102
+ * @returns {boolean} true if the address is IPv6, false otherwise
103
+ */
104
+ private isIPv6;
105
+ /** Finds out if we connected using IPv4 or IPv6
106
+ * @param {RTCPeerConnection} webrtcMediaConnection
107
+ * @param {Array<any>} allStatsReports array of RTC stats reports
108
+ * @returns {string} IPVersion
109
+ */
110
+ private getConnectionIpVersion;
97
111
  /**
98
112
  * Returns the type of a connection that has been established
99
113
  * It should be 'UDP' | 'TCP' | 'TURN-TLS' | 'TURN-TCP' | 'TURN-UDP' | 'unknown'
@@ -111,6 +125,7 @@ export default class MediaProperties {
111
125
  */
112
126
  getCurrentConnectionInfo(): Promise<{
113
127
  connectionType: string;
128
+ ipVersion?: IPVersion;
114
129
  selectedCandidatePairChanges: number;
115
130
  numTransports: number;
116
131
  }>;
@@ -77,6 +77,8 @@ interface IInMeetingActions {
77
77
  canShareDesktop?: boolean;
78
78
  canShareContent?: boolean;
79
79
  canTransferFile?: boolean;
80
+ canRealtimeCloseCaption?: boolean;
81
+ canRealtimeCloseCaptionManual?: boolean;
80
82
  canChat?: boolean;
81
83
  canDoVideo?: boolean;
82
84
  canAnnotate?: boolean;
@@ -178,6 +180,8 @@ export default class InMeetingActions implements IInMeetingActions {
178
180
  canShareDesktop: any;
179
181
  canShareContent: any;
180
182
  canTransferFile: any;
183
+ canRealtimeCloseCaption: any;
184
+ canRealtimeCloseCaptionManual: any;
181
185
  canChat: any;
182
186
  canDoVideo: any;
183
187
  canAnnotate: any;
@@ -458,7 +458,7 @@ var Webinar = _webexCore.WebexPlugin.extend({
458
458
  }, _callee7);
459
459
  }))();
460
460
  },
461
- version: "3.8.0-next.60"
461
+ version: "3.8.0-next.62"
462
462
  });
463
463
  var _default = exports.default = Webinar;
464
464
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -43,7 +43,7 @@
43
43
  "@webex/eslint-config-legacy": "0.0.0",
44
44
  "@webex/jest-config-legacy": "0.0.0",
45
45
  "@webex/legacy-tools": "0.0.0",
46
- "@webex/plugin-meetings": "3.8.0-next.60",
46
+ "@webex/plugin-meetings": "3.8.0-next.62",
47
47
  "@webex/plugin-rooms": "3.8.0-next.21",
48
48
  "@webex/test-helper-chai": "3.8.0-next.17",
49
49
  "@webex/test-helper-mocha": "3.8.0-next.17",
@@ -71,10 +71,11 @@
71
71
  "@webex/internal-plugin-metrics": "3.8.0-next.17",
72
72
  "@webex/internal-plugin-support": "3.8.0-next.21",
73
73
  "@webex/internal-plugin-user": "3.8.0-next.17",
74
- "@webex/internal-plugin-voicea": "3.8.0-next.60",
74
+ "@webex/internal-plugin-voicea": "3.8.0-next.62",
75
75
  "@webex/media-helpers": "3.8.0-next.21",
76
76
  "@webex/plugin-people": "3.8.0-next.19",
77
77
  "@webex/plugin-rooms": "3.8.0-next.21",
78
+ "@webex/ts-sdp": "^1.8.1",
78
79
  "@webex/web-capabilities": "^1.4.0",
79
80
  "@webex/webex-core": "3.8.0-next.17",
80
81
  "ampersand-collection": "^2.0.2",
@@ -92,5 +93,5 @@
92
93
  "//": [
93
94
  "TODO: upgrade jwt-decode when moving to node 18"
94
95
  ],
95
- "version": "3.8.0-next.60"
96
+ "version": "3.8.0-next.62"
96
97
  }
package/src/constants.ts CHANGED
@@ -911,6 +911,7 @@ export enum SELF_POLICY {
911
911
  SUPPORT_NETWORK_BASED_RECORD = 'supportNetworkBasedRecord',
912
912
  SUPPORT_PREMISE_RECORD = 'supportPremiseRecord',
913
913
  SUPPORT_REALTIME_CLOSE_CAPTION = 'supportRealtimeCloseCaption',
914
+ SUPPORT_REALTIME_CLOSE_CAPTION_MANUAL = 'supportRealtimeCloseCaptionManual',
914
915
  SUPPORT_CHAT = 'supportChat',
915
916
  SUPPORT_DESKTOP_SHARE_REMOTE = 'supportDesktopShareRemote',
916
917
  SUPPORT_DESKTOP_SHARE = 'supportDesktopShare',
@@ -7,6 +7,8 @@ import {
7
7
  RemoteStream,
8
8
  } from '@webex/media-helpers';
9
9
 
10
+ import {parse} from '@webex/ts-sdp';
11
+ import {ClientEvent} from '@webex/internal-plugin-metrics';
10
12
  import {MEETINGS, QUALITY_LEVELS} from '../constants';
11
13
  import LoggerProxy from '../common/logs/logger-proxy';
12
14
  import MediaConnectionAwaiter from './MediaConnectionAwaiter';
@@ -20,6 +22,8 @@ export type MediaDirection = {
20
22
  receiveShare: boolean;
21
23
  };
22
24
 
25
+ export type IPVersion = ClientEvent['payload']['ipVersion'];
26
+
23
27
  /**
24
28
  * @class MediaProperties
25
29
  */
@@ -212,6 +216,91 @@ export default class MediaProperties {
212
216
  };
213
217
  }
214
218
 
219
+ /**
220
+ * Checks if the given IP address is IPv6
221
+ * @param {string} ip address to check
222
+ * @returns {boolean} true if the address is IPv6, false otherwise
223
+ */
224
+ private isIPv6(ip: string): boolean {
225
+ return ip.includes(':');
226
+ }
227
+
228
+ /** Finds out if we connected using IPv4 or IPv6
229
+ * @param {RTCPeerConnection} webrtcMediaConnection
230
+ * @param {Array<any>} allStatsReports array of RTC stats reports
231
+ * @returns {string} IPVersion
232
+ */
233
+ private getConnectionIpVersion(
234
+ webrtcMediaConnection: RTCPeerConnection,
235
+ allStatsReports: any[]
236
+ ): IPVersion | undefined {
237
+ const transports = allStatsReports.filter((report) => report.type === 'transport');
238
+
239
+ let selectedCandidatePair;
240
+
241
+ if (transports.length > 0 && transports[0].selectedCandidatePairId) {
242
+ selectedCandidatePair = allStatsReports.find(
243
+ (report) =>
244
+ report.type === 'candidate-pair' && report.id === transports[0].selectedCandidatePairId
245
+ );
246
+ } else {
247
+ // Firefox doesn't have selectedCandidatePairId, but has selected property on the candidate pair
248
+ selectedCandidatePair = allStatsReports.find(
249
+ (report) => report.type === 'candidate-pair' && report.selected
250
+ );
251
+ }
252
+
253
+ if (selectedCandidatePair) {
254
+ const localCandidate = allStatsReports.find(
255
+ (report) =>
256
+ report.type === 'local-candidate' && report.id === selectedCandidatePair.localCandidateId
257
+ );
258
+
259
+ if (localCandidate) {
260
+ if (localCandidate.address) {
261
+ return this.isIPv6(localCandidate.address) ? 'IPv6' : 'IPv4';
262
+ }
263
+
264
+ try {
265
+ // safari doesn't have address field on the candidate, so we have to use the port to look up the candidate in the SDP
266
+ const localSdp = webrtcMediaConnection.localDescription.sdp;
267
+
268
+ const parsedSdp = parse(localSdp);
269
+
270
+ for (const mediaLine of parsedSdp.avMedia) {
271
+ const matchingCandidate = mediaLine.iceInfo.candidates.find(
272
+ (candidate) => candidate.port === localCandidate.port
273
+ );
274
+ if (matchingCandidate) {
275
+ return this.isIPv6(matchingCandidate.connectionAddress) ? 'IPv6' : 'IPv4';
276
+ }
277
+ }
278
+
279
+ LoggerProxy.logger.warn(
280
+ `Media:properties#getConnectionIpVersion --> failed to find local candidate in the SDP for port ${localCandidate.port}`
281
+ );
282
+ } catch (error) {
283
+ LoggerProxy.logger.warn(
284
+ `Media:properties#getConnectionIpVersion --> error while trying to find candidate in local SDP:`,
285
+ error
286
+ );
287
+
288
+ return undefined;
289
+ }
290
+ } else {
291
+ LoggerProxy.logger.warn(
292
+ `Media:properties#getConnectionIpVersion --> failed to find local candidate "${selectedCandidatePair.localCandidateId}" in getStats() results`
293
+ );
294
+ }
295
+ } else {
296
+ LoggerProxy.logger.warn(
297
+ `Media:properties#getConnectionIpVersion --> failed to find selected candidate pair in getStats() results (transports.length=${transports.length}, selectedCandidatePairId=${transports[0]?.selectedCandidatePairId})`
298
+ );
299
+ }
300
+
301
+ return undefined;
302
+ }
303
+
215
304
  /**
216
305
  * Returns the type of a connection that has been established
217
306
  * It should be 'UDP' | 'TCP' | 'TURN-TLS' | 'TURN-TCP' | 'TURN-UDP' | 'unknown'
@@ -284,6 +373,7 @@ export default class MediaProperties {
284
373
  */
285
374
  async getCurrentConnectionInfo(): Promise<{
286
375
  connectionType: string;
376
+ ipVersion?: IPVersion;
287
377
  selectedCandidatePairChanges: number;
288
378
  numTransports: number;
289
379
  }> {
@@ -309,10 +399,15 @@ export default class MediaProperties {
309
399
  });
310
400
 
311
401
  const connectionType = this.getConnectionType(allStatsReports);
402
+ const rtcPeerconnection =
403
+ this.webrtcMediaConnection.multistreamConnection?.pc.pc ||
404
+ this.webrtcMediaConnection.mediaConnection?.pc;
405
+ const ipVersion = this.getConnectionIpVersion(rtcPeerconnection, allStatsReports);
312
406
  const {selectedCandidatePairChanges, numTransports} = this.getTransportInfo(allStatsReports);
313
407
 
314
408
  return {
315
409
  connectionType,
410
+ ipVersion,
316
411
  selectedCandidatePairChanges,
317
412
  numTransports,
318
413
  };
@@ -323,6 +418,7 @@ export default class MediaProperties {
323
418
 
324
419
  return {
325
420
  connectionType: 'unknown',
421
+ ipVersion: undefined,
326
422
  selectedCandidatePairChanges: -1,
327
423
  numTransports: 0,
328
424
  };
@@ -81,6 +81,8 @@ interface IInMeetingActions {
81
81
  canShareDesktop?: boolean;
82
82
  canShareContent?: boolean;
83
83
  canTransferFile?: boolean;
84
+ canRealtimeCloseCaption?: boolean;
85
+ canRealtimeCloseCaptionManual?: boolean;
84
86
  canChat?: boolean;
85
87
  canDoVideo?: boolean;
86
88
  canAnnotate?: boolean;
@@ -255,6 +257,10 @@ export default class InMeetingActions implements IInMeetingActions {
255
257
 
256
258
  canTransferFile = null;
257
259
 
260
+ canRealtimeCloseCaption = null;
261
+
262
+ canRealtimeCloseCaptionManual = null;
263
+
258
264
  canChat = null;
259
265
 
260
266
  canDoVideo = null;
@@ -379,6 +385,8 @@ export default class InMeetingActions implements IInMeetingActions {
379
385
  canShareDesktop: this.canShareDesktop,
380
386
  canShareContent: this.canShareContent,
381
387
  canTransferFile: this.canTransferFile,
388
+ canRealtimeCloseCaption: this.canRealtimeCloseCaption,
389
+ canRealtimeCloseCaptionManual: this.canRealtimeCloseCaptionManual,
382
390
  canChat: this.canChat,
383
391
  canDoVideo: this.canDoVideo,
384
392
  canAnnotate: this.canAnnotate,
@@ -4261,6 +4261,14 @@ export default class Meeting extends StatelessWebexPlugin {
4261
4261
  requiredPolicies: [SELF_POLICY.SUPPORT_FILE_TRANSFER],
4262
4262
  policies: this.selfUserPolicies,
4263
4263
  }),
4264
+ canRealtimeCloseCaption: ControlsOptionsUtil.hasPolicies({
4265
+ requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION],
4266
+ policies: this.selfUserPolicies,
4267
+ }),
4268
+ canRealtimeCloseCaptionManual: ControlsOptionsUtil.hasPolicies({
4269
+ requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION_MANUAL],
4270
+ policies: this.selfUserPolicies,
4271
+ }),
4264
4272
  canChat: ControlsOptionsUtil.hasPolicies({
4265
4273
  requiredPolicies: [SELF_POLICY.SUPPORT_CHAT],
4266
4274
  policies: this.selfUserPolicies,
@@ -7772,7 +7780,7 @@ export default class Meeting extends StatelessWebexPlugin {
7772
7780
  await this.enqueueScreenShareFloorRequest();
7773
7781
  }
7774
7782
 
7775
- const {connectionType, selectedCandidatePairChanges, numTransports} =
7783
+ const {connectionType, ipVersion, selectedCandidatePairChanges, numTransports} =
7776
7784
  await this.mediaProperties.getCurrentConnectionInfo();
7777
7785
 
7778
7786
  const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
@@ -7783,6 +7791,7 @@ export default class Meeting extends StatelessWebexPlugin {
7783
7791
  correlation_id: this.correlationId,
7784
7792
  locus_id: this.locusUrl.split('/').pop(),
7785
7793
  connectionType,
7794
+ ipVersion,
7786
7795
  selectedCandidatePairChanges,
7787
7796
  numTransports,
7788
7797
  isMultistream: this.isMultistream,
@@ -7795,6 +7804,9 @@ export default class Meeting extends StatelessWebexPlugin {
7795
7804
  // @ts-ignore
7796
7805
  this.webex.internal.newMetrics.submitClientEvent({
7797
7806
  name: 'client.media-engine.ready',
7807
+ payload: {
7808
+ ipVersion,
7809
+ },
7798
7810
  options: {
7799
7811
  meetingId: this.id,
7800
7812
  },
@@ -2,6 +2,7 @@ import 'jsdom-global/register';
2
2
  import {assert} from '@webex/test-helper-chai';
3
3
  import sinon from 'sinon';
4
4
  import {ConnectionState} from '@webex/internal-media-core';
5
+ import * as tsSdpModule from '@webex/ts-sdp';
5
6
  import MediaProperties from '@webex/plugin-meetings/src/media/properties';
6
7
  import {Defer} from '@webex/common';
7
8
  import MediaConnectionAwaiter from '../../../../src/media/MediaConnectionAwaiter';
@@ -10,15 +11,21 @@ describe('MediaProperties', () => {
10
11
  let mediaProperties;
11
12
  let mockMC;
12
13
  let clock;
14
+ let rtcPeerConnection;
13
15
 
14
16
  beforeEach(() => {
15
17
  clock = sinon.useFakeTimers();
16
18
 
19
+ rtcPeerConnection = {
20
+ localDescription: {sdp: ''},
21
+ };
22
+
17
23
  mockMC = {
18
24
  getStats: sinon.stub().resolves([]),
19
25
  on: sinon.stub(),
20
26
  off: sinon.stub(),
21
27
  getConnectionState: sinon.stub().returns(ConnectionState.Connected),
28
+ multistreamConnection: {pc: {pc: rtcPeerConnection}},
22
29
  };
23
30
 
24
31
  mediaProperties = new MediaProperties();
@@ -81,6 +88,129 @@ describe('MediaProperties', () => {
81
88
  assert.equal(numTransports, 0);
82
89
  });
83
90
 
91
+ describe('ipVersion', () => {
92
+ it('returns ipVersion=undefined if getStats() returns no candidate pairs', async () => {
93
+ mockMC.getStats.resolves([{type: 'something', id: '1234'}]);
94
+ const info = await mediaProperties.getCurrentConnectionInfo();
95
+ assert.equal(info.ipVersion, undefined);
96
+ });
97
+
98
+ it('returns ipVersion=undefined if getStats() returns no selected candidate pair', async () => {
99
+ mockMC.getStats.resolves([{type: 'candidate-pair', id: '1234', selected: false}]);
100
+ const info = await mediaProperties.getCurrentConnectionInfo();
101
+ assert.equal(info.ipVersion, undefined);
102
+ });
103
+
104
+ it('returns ipVersion="IPv4" if transport has selectedCandidatePairId and local candidate has IPv4 address', async () => {
105
+ mockMC.getStats.resolves([
106
+ {type: 'transport', id: 't1', selectedCandidatePairId: 'cp1'},
107
+ {type: 'candidate-pair', id: 'cp1', localCandidateId: 'lc1'},
108
+ {type: 'local-candidate', id: 'lc1', address: '192.168.1.1'},
109
+ ]);
110
+ const info = await mediaProperties.getCurrentConnectionInfo();
111
+ assert.equal(info.ipVersion, 'IPv4');
112
+ });
113
+
114
+ it('returns ipVersion="IPv6" if transport has selectedCandidatePairId and local candidate has IPv6 address', async () => {
115
+ mockMC.getStats.resolves([
116
+ {type: 'transport', id: 't1', selectedCandidatePairId: 'cp1'},
117
+ {type: 'candidate-pair', id: 'cp1', localCandidateId: 'lc1'},
118
+ {type: 'local-candidate', id: 'lc1', address: 'fd8f:12e6:5e53:784f:a0ba:f8d5:b906:1acc'},
119
+ ]);
120
+ const info = await mediaProperties.getCurrentConnectionInfo();
121
+ assert.equal(info.ipVersion, 'IPv6');
122
+ });
123
+
124
+ it('returns ipVersion="IPv4" if transport has no selectedCandidatePairId but finds selected candidate pair and local candidate has IPv4 address', async () => {
125
+ mockMC.getStats.resolves([
126
+ {type: 'transport', id: 't1'},
127
+ {type: 'candidate-pair', id: 'cp2', localCandidateId: 'lc2', selected: true},
128
+ {type: 'local-candidate', id: 'lc2', address: '10.0.0.1'},
129
+ ]);
130
+ const info = await mediaProperties.getCurrentConnectionInfo();
131
+ assert.equal(info.ipVersion, 'IPv4');
132
+ });
133
+
134
+ it('returns ipVersion="IPv6" if transport has no selectedCandidatePairId but finds selected candidate pair and local candidate has IPv6 address', async () => {
135
+ mockMC.getStats.resolves([
136
+ {type: 'transport', id: 't1'},
137
+ {type: 'candidate-pair', id: 'cp2', localCandidateId: 'lc2', selected: true},
138
+ {type: 'local-candidate', id: 'lc2', address: 'fe80::1ff:fe23:4567:890a'},
139
+ ]);
140
+ const info = await mediaProperties.getCurrentConnectionInfo();
141
+ assert.equal(info.ipVersion, 'IPv6');
142
+ });
143
+
144
+ describe('local candidate without address', () => {
145
+ it('return="IPv4" if candidate from SDP with matching port number has IPv4 address', async () => {
146
+ sinon.stub(tsSdpModule, 'parse').returns({
147
+ avMedia: [
148
+ {
149
+ iceInfo: {
150
+ candidates: [
151
+ {
152
+ port: 1234,
153
+ connectionAddress: '192.168.0.1',
154
+ },
155
+ ],
156
+ },
157
+ },
158
+ ],
159
+ });
160
+
161
+ mockMC.getStats.resolves([
162
+ {type: 'transport', id: 't1'},
163
+ {type: 'candidate-pair', id: 'cp2', localCandidateId: 'lc2', selected: true},
164
+ {type: 'local-candidate', id: 'lc2', port: 1234},
165
+ ]);
166
+ const info = await mediaProperties.getCurrentConnectionInfo();
167
+ assert.equal(info.ipVersion, 'IPv4');
168
+
169
+ assert.calledWith(tsSdpModule.parse, rtcPeerConnection.localDescription.sdp);
170
+ });
171
+
172
+ it('returns ipVersion="IPv6" if candidate from SDP with matching port number has IPv6 address', async () => {
173
+ sinon.stub(tsSdpModule, 'parse').returns({
174
+ avMedia: [
175
+ {
176
+ iceInfo: {
177
+ candidates: [
178
+ {
179
+ port: 5000,
180
+ connectionAddress: 'fe80::1ff:fe23:4567:890a',
181
+ },
182
+ ],
183
+ },
184
+ },
185
+ ],
186
+ });
187
+
188
+ mockMC.getStats.resolves([
189
+ {type: 'transport', id: 't1'},
190
+ {type: 'candidate-pair', id: 'cp2', localCandidateId: 'lc2', selected: true},
191
+ {type: 'local-candidate', id: 'lc2', port: 5000},
192
+ ]);
193
+ const info = await mediaProperties.getCurrentConnectionInfo();
194
+ assert.equal(info.ipVersion, 'IPv6');
195
+
196
+ assert.calledWith(tsSdpModule.parse, rtcPeerConnection.localDescription.sdp);
197
+ });
198
+
199
+ it('returns ipVersion=undefined if parsing of the SDP fails', async () => {
200
+ sinon.stub(tsSdpModule, 'parse').throws(new Error('fake error'));
201
+
202
+ mockMC.getStats.resolves([
203
+ {type: 'candidate-pair', id: 'cp2', localCandidateId: 'lc2', selected: true},
204
+ {type: 'local-candidate', id: 'lc2', port: 5000},
205
+ ]);
206
+ const info = await mediaProperties.getCurrentConnectionInfo();
207
+ assert.equal(info.ipVersion, undefined);
208
+
209
+ assert.calledWith(tsSdpModule.parse, rtcPeerConnection.localDescription.sdp);
210
+ });
211
+ });
212
+ });
213
+
84
214
  describe('selectedCandidatePairChanges and numTransports', () => {
85
215
  it('returns correct values when getStats() returns no transport stats at all', async () => {
86
216
  mockMC.getStats.resolves([{type: 'something', id: '1234'}]);
@@ -76,6 +76,8 @@ describe('plugin-meetings', () => {
76
76
  canShareDesktop: null,
77
77
  canShareContent: null,
78
78
  canTransferFile: null,
79
+ canRealtimeCloseCaption: null,
80
+ canRealtimeCloseCaptionManual: null,
79
81
  canChat: null,
80
82
  canDoVideo: null,
81
83
  canAnnotate: null,
@@ -182,6 +184,8 @@ describe('plugin-meetings', () => {
182
184
  'canShareDesktop',
183
185
  'canShareContent',
184
186
  'canTransferFile',
187
+ 'canRealtimeCloseCaption',
188
+ 'canRealtimeCloseCaptionManual',
185
189
  'canChat',
186
190
  'canDoVideo',
187
191
  'canAnnotate',
@@ -2047,7 +2047,12 @@ describe('plugin-meetings', () => {
2047
2047
  meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
2048
2048
  meeting.mediaProperties.getCurrentConnectionInfo = sinon
2049
2049
  .stub()
2050
- .resolves({connectionType: 'udp', selectedCandidatePairChanges: 2, numTransports: 1});
2050
+ .resolves({
2051
+ connectionType: 'udp',
2052
+ selectedCandidatePairChanges: 2,
2053
+ numTransports: 1,
2054
+ ipVersion: 'IPv6',
2055
+ });
2051
2056
  meeting.audio = muteStateStub;
2052
2057
  meeting.video = muteStateStub;
2053
2058
  sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
@@ -3059,6 +3064,9 @@ describe('plugin-meetings', () => {
3059
3064
  });
3060
3065
  assert.calledWith(webex.internal.newMetrics.submitClientEvent.thirdCall, {
3061
3066
  name: 'client.media-engine.ready',
3067
+ payload: {
3068
+ ipVersion: 'IPv6',
3069
+ },
3062
3070
  options: {
3063
3071
  meetingId: meeting.id,
3064
3072
  },
@@ -3115,6 +3123,7 @@ describe('plugin-meetings', () => {
3115
3123
  locus_id: meeting.locusUrl.split('/').pop(),
3116
3124
  connectionType: 'udp',
3117
3125
  selectedCandidatePairChanges: 2,
3126
+ ipVersion: 'IPv6',
3118
3127
  numTransports: 1,
3119
3128
  isMultistream: false,
3120
3129
  retriedWithTurnServer: true,
@@ -3268,6 +3277,7 @@ describe('plugin-meetings', () => {
3268
3277
  locus_id: meeting.locusUrl.split('/').pop(),
3269
3278
  connectionType: 'udp',
3270
3279
  selectedCandidatePairChanges: 2,
3280
+ ipVersion: 'IPv6',
3271
3281
  numTransports: 1,
3272
3282
  isMultistream: false,
3273
3283
  retriedWithTurnServer: false,
@@ -3443,6 +3453,7 @@ describe('plugin-meetings', () => {
3443
3453
  correlation_id: meeting.correlationId,
3444
3454
  locus_id: meeting.locusUrl.split('/').pop(),
3445
3455
  connectionType: 'udp',
3456
+ ipVersion: 'IPv6',
3446
3457
  selectedCandidatePairChanges: 2,
3447
3458
  numTransports: 1,
3448
3459
  isMultistream: false,
@@ -10995,6 +11006,16 @@ describe('plugin-meetings', () => {
10995
11006
  requiredDisplayHints: [],
10996
11007
  requiredPolicies: [SELF_POLICY.SUPPORT_FILE_TRANSFER],
10997
11008
  },
11009
+ {
11010
+ actionName: 'canRealtimeCloseCaption',
11011
+ requiredDisplayHints: [],
11012
+ requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION],
11013
+ },
11014
+ {
11015
+ actionName: 'canRealtimeCloseCaptionManual',
11016
+ requiredDisplayHints: [],
11017
+ requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION_MANUAL],
11018
+ },
10998
11019
  {
10999
11020
  actionName: 'canChat',
11000
11021
  requiredDisplayHints: [],