@webex/plugin-meetings 3.11.0-next.46 → 3.11.0-next.48

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.
Files changed (37) hide show
  1. package/dist/aiEnableRequest/index.js +1 -1
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/interceptors/dataChannelAuthToken.js +103 -46
  5. package/dist/interceptors/dataChannelAuthToken.js.map +1 -1
  6. package/dist/interceptors/utils.js +27 -0
  7. package/dist/interceptors/utils.js.map +1 -0
  8. package/dist/interpretation/index.js +1 -1
  9. package/dist/interpretation/siLanguage.js +1 -1
  10. package/dist/meeting/index.js +4 -6
  11. package/dist/meeting/index.js.map +1 -1
  12. package/dist/meeting/request.js +2 -2
  13. package/dist/meeting/request.js.map +1 -1
  14. package/dist/multistream/mediaRequestManager.js +9 -60
  15. package/dist/multistream/mediaRequestManager.js.map +1 -1
  16. package/dist/reconnection-manager/index.js +0 -1
  17. package/dist/reconnection-manager/index.js.map +1 -1
  18. package/dist/types/interceptors/dataChannelAuthToken.d.ts +8 -0
  19. package/dist/types/interceptors/utils.d.ts +1 -0
  20. package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
  21. package/dist/webinar/index.js +8 -2
  22. package/dist/webinar/index.js.map +1 -1
  23. package/package.json +4 -3
  24. package/src/interceptors/dataChannelAuthToken.ts +28 -0
  25. package/src/interceptors/utils.ts +16 -0
  26. package/src/meeting/index.ts +5 -5
  27. package/src/meeting/request.ts +4 -4
  28. package/src/multistream/mediaRequestManager.ts +3 -53
  29. package/src/reconnection-manager/index.ts +0 -1
  30. package/src/webinar/index.ts +5 -0
  31. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +69 -0
  32. package/test/unit/spec/interceptors/utils.ts +75 -0
  33. package/test/unit/spec/meeting/index.js +6 -6
  34. package/test/unit/spec/meeting/request.js +18 -12
  35. package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
  36. package/test/unit/spec/reconnection-manager/index.js +4 -8
  37. package/test/unit/spec/webinar/index.ts +18 -0
@@ -1159,13 +1159,13 @@ export default class MeetingRequest extends StatelessWebexPlugin {
1159
1159
  method: HTTP_VERBS.GET,
1160
1160
  uri,
1161
1161
  }).catch((err) => {
1162
- LoggerProxy.logger.error(
1163
- `Meeting:request#fetchDatachannelToken --> Error retrieving ${
1162
+ LoggerProxy.logger.warn(
1163
+ `Meeting:request#fetchDatachannelToken --> Failed to retrieve ${
1164
1164
  isPracticeSession ? 'practice session ' : ''
1165
- }datachannel token, error ${err}`
1165
+ }datachannel token: ${err?.message || err}`
1166
1166
  );
1167
1167
 
1168
- throw err;
1168
+ return null;
1169
1169
  });
1170
1170
  }
1171
1171
  }
@@ -10,7 +10,7 @@ import {
10
10
  RecommendedOpusBitrates,
11
11
  NamedMediaGroup,
12
12
  } from '@webex/internal-media-core';
13
- import {cloneDeepWith, debounce, isEmpty} from 'lodash';
13
+ import {cloneDeepWith, debounce} from 'lodash';
14
14
 
15
15
  import LoggerProxy from '../common/logs/logger-proxy';
16
16
 
@@ -94,8 +94,6 @@ export class MediaRequestManager {
94
94
 
95
95
  private debouncedSourceUpdateListener: () => void;
96
96
 
97
- private previousStreamRequests: Array<StreamRequest> = [];
98
-
99
97
  private trimRequestsToNumOfSources: boolean;
100
98
  private numTotalSources: number;
101
99
  private numLiveSources: number;
@@ -161,36 +159,6 @@ export class MediaRequestManager {
161
159
  }
162
160
  }
163
161
 
164
- /**
165
- * Returns true if two stream requests are the same, false otherwise.
166
- *
167
- * @param {StreamRequest} streamRequestA - Stream request A for comparison.
168
- * @param {StreamRequest} streamRequestB - Stream request B for comparison.
169
- * @returns {boolean} - Whether they are equal.
170
- */
171
- // eslint-disable-next-line class-methods-use-this
172
- public isEqual(streamRequestA: StreamRequest, streamRequestB: StreamRequest) {
173
- return (
174
- JSON.stringify(streamRequestA._toJmpStreamRequest()) ===
175
- JSON.stringify(streamRequestB._toJmpStreamRequest())
176
- );
177
- }
178
-
179
- /**
180
- * Compares new stream requests to previous ones and determines
181
- * if they are the same.
182
- *
183
- * @param {StreamRequest[]} newRequests - Array with new requests.
184
- * @returns {boolean} - True if they are equal, false otherwise.
185
- */
186
- private checkIsNewRequestsEqualToPrev(newRequests: StreamRequest[]) {
187
- return (
188
- !isEmpty(this.previousStreamRequests) &&
189
- this.previousStreamRequests.length === newRequests.length &&
190
- this.previousStreamRequests.every((req, idx) => this.isEqual(req, newRequests[idx]))
191
- );
192
- }
193
-
194
162
  /**
195
163
  * Returns the maxPayloadBitsPerSecond per Stream
196
164
  *
@@ -230,15 +198,6 @@ export class MediaRequestManager {
230
198
  return (mediaRequest.codecInfo.maxFs * maxFps) / 100;
231
199
  }
232
200
 
233
- /**
234
- * Clears the previous stream requests.
235
- *
236
- * @returns {void}
237
- */
238
- public clearPreviousRequests(): void {
239
- this.previousStreamRequests = [];
240
- }
241
-
242
201
  /** Modifies the passed in clientRequests and makes sure that in total they don't ask
243
202
  * for more streams than there are available.
244
203
  *
@@ -372,17 +331,8 @@ export class MediaRequestManager {
372
331
  }
373
332
  });
374
333
 
375
- //! IMPORTANT: this is only a temporary fix. This will soon be done in the jmp layer (@webex/json-multistream)
376
- // https://jira-eng-gpk2.cisco.com/jira/browse/WEBEX-326713
377
- if (!this.checkIsNewRequestsEqualToPrev(streamRequests)) {
378
- this.sendMediaRequestsCallback(streamRequests);
379
- this.previousStreamRequests = streamRequests;
380
- LoggerProxy.logger.info(`multistream:sendRequests --> media requests sent. `);
381
- } else {
382
- LoggerProxy.logger.info(
383
- `multistream:sendRequests --> detected duplicate WCME requests, skipping them... `
384
- );
385
- }
334
+ this.sendMediaRequestsCallback(streamRequests);
335
+ LoggerProxy.logger.info(`multistream:sendRequests --> media requests sent. `);
386
336
  }
387
337
 
388
338
  public addRequest(mediaRequest: MediaRequest, commit = true): MediaRequestId {
@@ -609,7 +609,6 @@ export default class ReconnectionManager {
609
609
  if (this.meeting.isMultistream) {
610
610
  Object.values(this.meeting.mediaRequestManagers).forEach(
611
611
  (mediaRequestManager: MediaRequestManager) => {
612
- mediaRequestManager.clearPreviousRequests();
613
612
  mediaRequestManager.commit();
614
613
  }
615
614
  );
@@ -177,6 +177,8 @@ const Webinar = WebexPlugin.extend({
177
177
 
178
178
  const finalToken = currentToken ?? practiceSessionDatachannelToken;
179
179
 
180
+ const isCaptionBoxOn = this.webex.internal.voicea.getIsCaptionBoxOn();
181
+
180
182
  if (!currentToken && practiceSessionDatachannelToken) {
181
183
  // @ts-ignore
182
184
  this.webex.internal.llm.setDatachannelToken(
@@ -219,6 +221,9 @@ const Webinar = WebexPlugin.extend({
219
221
  );
220
222
  // @ts-ignore - Fix type
221
223
  this.webex.internal.voicea?.announce?.();
224
+ if (isCaptionBoxOn) {
225
+ this.webex.internal.voicea.updateSubchannelSubscriptions({subscribe: ['transcription']});
226
+ }
222
227
  LoggerProxy.logger.info(
223
228
  `Webinar:index#updatePSDataChannel --> enabled to receive relay events for default session for ${LLM_PRACTICE_SESSION}!`
224
229
  );
@@ -5,6 +5,7 @@ import MockWebex from '@webex/test-helper-mock-webex';
5
5
  import {WebexHttpError} from '@webex/webex-core';
6
6
  import DataChannelAuthTokenInterceptor from '@webex/plugin-meetings/src/interceptors/dataChannelAuthToken';
7
7
  import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
8
+ import * as utils from '@webex/plugin-meetings/src/interceptors/utils';
8
9
  import {DATA_CHANNEL_AUTH_HEADER, MAX_RETRY} from '@webex/plugin-meetings/src/interceptors/constant';
9
10
 
10
11
  describe('plugin-meetings', () => {
@@ -14,6 +15,10 @@ describe('plugin-meetings', () => {
14
15
 
15
16
  beforeEach(() => {
16
17
  clock = sinon.useFakeTimers();
18
+ sinon.stub(LoggerProxy, 'logger').value({
19
+ error: sinon.stub(),
20
+ warn: sinon.stub(),
21
+ });
17
22
 
18
23
  webex = new MockWebex({children: {}});
19
24
  webex.request = sinon.stub().resolves({});
@@ -25,6 +30,7 @@ describe('plugin-meetings', () => {
25
30
  });
26
31
 
27
32
  afterEach(() => {
33
+ sinon.restore();
28
34
  clock.restore();
29
35
  });
30
36
 
@@ -86,6 +92,69 @@ describe('plugin-meetings', () => {
86
92
  });
87
93
  });
88
94
 
95
+ describe('#onRequest', () => {
96
+ let isJwtTokenExpiredStub;
97
+
98
+ beforeEach(() => {
99
+ isJwtTokenExpiredStub = sinon.stub(utils, 'isJwtTokenExpired').returns(false);
100
+ });
101
+
102
+ it('does nothing when token is missing', async () => {
103
+ const options = {headers: {}};
104
+
105
+ const res = await interceptor.onRequest(options);
106
+
107
+ expect(res).to.equal(options);
108
+ sinon.assert.notCalled(isJwtTokenExpiredStub);
109
+ });
110
+
111
+ it('does nothing when feature is disabled', async () => {
112
+ interceptor._isDataChannelTokenEnabled.resolves(false);
113
+
114
+ const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'}};
115
+ const res = await interceptor.onRequest(options);
116
+
117
+ expect(res).to.equal(options);
118
+ sinon.assert.notCalled(isJwtTokenExpiredStub);
119
+ });
120
+
121
+ it('does not refresh when token is not expired', async () => {
122
+ interceptor._isDataChannelTokenEnabled.resolves(true);
123
+ isJwtTokenExpiredStub.returns(false);
124
+
125
+ const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'}};
126
+ const res = await interceptor.onRequest(options);
127
+
128
+ sinon.assert.notCalled(interceptor._refreshDataChannelToken);
129
+ expect(res.headers[DATA_CHANNEL_AUTH_HEADER]).to.equal('old-token');
130
+ });
131
+
132
+ it('refreshes token when expired', async () => {
133
+ interceptor._isDataChannelTokenEnabled.resolves(true);
134
+ isJwtTokenExpiredStub.returns(true);
135
+
136
+ interceptor._refreshDataChannelToken.resolves('new-token');
137
+
138
+ const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'}};
139
+ const res = await interceptor.onRequest(options);
140
+
141
+ sinon.assert.calledOnce(interceptor._refreshDataChannelToken);
142
+ expect(res.headers[DATA_CHANNEL_AUTH_HEADER]).to.equal('new-token');
143
+ });
144
+
145
+ it('continues request when refresh fails', async () => {
146
+ interceptor._isDataChannelTokenEnabled.resolves(true);
147
+ isJwtTokenExpiredStub.returns(true);
148
+
149
+ interceptor._refreshDataChannelToken.rejects(new Error('refresh failed'));
150
+
151
+ const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'}};
152
+ const res = await interceptor.onRequest(options);
153
+
154
+ expect(res.headers[DATA_CHANNEL_AUTH_HEADER]).to.equal('old-token');
155
+ });
156
+ });
157
+
89
158
  describe('#refreshTokenAndRetryWithDelay', () => {
90
159
  const options = {
91
160
  headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'},
@@ -0,0 +1,75 @@
1
+ import 'jsdom-global/register';
2
+ import {expect} from '@webex/test-helper-chai';
3
+ import sinon from 'sinon';
4
+ import {isJwtTokenExpired} from '@webex/plugin-meetings/src/interceptors/utils';
5
+
6
+ const makeJwt = (payload) =>
7
+ [
8
+ Buffer.from(JSON.stringify({alg: 'none', typ: 'JWT'})).toString('base64url'),
9
+ Buffer.from(JSON.stringify(payload)).toString('base64url'),
10
+ ''
11
+ ].join('.');
12
+
13
+ describe('plugin-meetings', () => {
14
+ describe('Interceptors', () => {
15
+ describe('utils - isJwtTokenExpired', () => {
16
+ let clock;
17
+
18
+ beforeEach(() => {
19
+ clock = sinon.useFakeTimers();
20
+ });
21
+
22
+ afterEach(() => {
23
+ sinon.restore();
24
+ clock.restore();
25
+ });
26
+
27
+ it('returns false when token has no exp', () => {
28
+ const token = makeJwt({}); // no exp
29
+
30
+ const result = isJwtTokenExpired(token);
31
+
32
+ expect(result).to.equal(false);
33
+ });
34
+
35
+ it('returns false when token is not expired', () => {
36
+ const now = Date.now();
37
+ const futureExp = Math.floor((now + 60 * 1000) / 1000);
38
+
39
+ const token = makeJwt({exp: futureExp});
40
+
41
+ const result = isJwtTokenExpired(token);
42
+
43
+ expect(result).to.equal(false);
44
+ });
45
+
46
+ it('returns true when token is expired', () => {
47
+ const now = Date.now();
48
+ const pastExp = Math.floor((now - 60 * 1000) / 1000);
49
+
50
+ const token = makeJwt({exp: pastExp});
51
+
52
+ const result = isJwtTokenExpired(token);
53
+
54
+ expect(result).to.equal(true);
55
+ });
56
+
57
+ it('returns true when token expires within EXPIRY_BUFFER', () => {
58
+ const now = Date.now();
59
+ const expSoon = Math.floor((now + 10 * 1000) / 1000);
60
+
61
+ const token = makeJwt({exp: expSoon});
62
+
63
+ const result = isJwtTokenExpired(token);
64
+
65
+ expect(result).to.equal(true);
66
+ });
67
+
68
+ it('returns true when token is invalid', () => {
69
+ const result = isJwtTokenExpired('not-a-jwt');
70
+
71
+ expect(result).to.equal(true);
72
+ });
73
+ });
74
+ });
75
+ });
@@ -13029,7 +13029,7 @@ describe('plugin-meetings', () => {
13029
13029
  'a datachannel url',
13030
13030
  'token-123'
13031
13031
  );
13032
- assert.calledWithExactly(webex.internal.llm.setDatachannelToken, 'token-123', 'default');
13032
+ assert.calledWithExactly(webex.internal.llm.setDatachannelToken, 'token-123', 'llm-default-session');
13033
13033
  });
13034
13034
  it('prefers refreshed token over locus self token', async () => {
13035
13035
  meeting.joinedWith = {state: 'JOINED'};
@@ -13039,7 +13039,7 @@ describe('plugin-meetings', () => {
13039
13039
  self: {datachannelToken: 'locus-token'},
13040
13040
  };
13041
13041
 
13042
- webex.internal.llm.getDatachannelToken.withArgs('default').returns('refreshed-token');
13042
+ webex.internal.llm.getDatachannelToken.withArgs('llm-default-session').returns('refreshed-token');
13043
13043
 
13044
13044
  await meeting.updateLLMConnection();
13045
13045
 
@@ -13072,7 +13072,7 @@ describe('plugin-meetings', () => {
13072
13072
  'a datachannel url',
13073
13073
  'token-123'
13074
13074
  );
13075
- assert.calledWithExactly(webex.internal.llm.setDatachannelToken, 'token-123', 'default');
13075
+ assert.calledWithExactly(webex.internal.llm.setDatachannelToken, 'token-123', 'llm-default-session');
13076
13076
  });
13077
13077
 
13078
13078
  describe('#clearMeetingData', () => {
@@ -14735,7 +14735,7 @@ describe('plugin-meetings', () => {
14735
14735
  expect(result).to.deep.equal({
14736
14736
  body: {
14737
14737
  datachannelToken: 'mock-token',
14738
- dataChannelTokenType: 'practiceSession',
14738
+ dataChannelTokenType: 'llm-practice-session',
14739
14739
  },
14740
14740
  });
14741
14741
  });
@@ -14748,7 +14748,7 @@ describe('plugin-meetings', () => {
14748
14748
 
14749
14749
  const result = meeting.getDataChannelTokenType();
14750
14750
 
14751
- expect(result).to.equal('practiceSession');
14751
+ expect(result).to.equal('llm-practice-session');
14752
14752
  });
14753
14753
 
14754
14754
  it('returns Default when not in practice session mode', () => {
@@ -14758,7 +14758,7 @@ describe('plugin-meetings', () => {
14758
14758
 
14759
14759
  const result = meeting.getDataChannelTokenType();
14760
14760
 
14761
- expect(result).to.equal('default');
14761
+ expect(result).to.equal('llm-default-session');
14762
14762
  });
14763
14763
  });
14764
14764
  describe('#stopKeepAlive', () => {
@@ -924,7 +924,14 @@ describe('plugin-meetings', () => {
924
924
  const locusUrl = 'https://locus.example.com/locus/api/v1/loci/123';
925
925
  const participantId = 'participant-123';
926
926
 
927
+ beforeEach(() => {
928
+ sinon.restore();
929
+ locusDeltaRequestSpy = sinon.stub(meetingsRequest, 'locusDeltaRequest');
930
+ });
931
+
927
932
  it('sends GET request to regular datachannel token endpoint', async () => {
933
+ locusDeltaRequestSpy.resolves({body: {}});
934
+
928
935
  await meetingsRequest.fetchDatachannelToken({
929
936
  locusUrl,
930
937
  requestingParticipantId: participantId,
@@ -938,6 +945,8 @@ describe('plugin-meetings', () => {
938
945
  });
939
946
 
940
947
  it('sends GET request to practice session datachannel token endpoint', async () => {
948
+ locusDeltaRequestSpy.resolves({body: {}});
949
+
941
950
  await meetingsRequest.fetchDatachannelToken({
942
951
  locusUrl,
943
952
  requestingParticipantId: participantId,
@@ -950,7 +959,7 @@ describe('plugin-meetings', () => {
950
959
  });
951
960
  });
952
961
 
953
- it('throws if locusUrl or participantId is missing', async () => {
962
+ it('rejects when locusUrl or participantId is missing', async () => {
954
963
  await assert.isRejected(
955
964
  meetingsRequest.fetchDatachannelToken({
956
965
  locusUrl: null,
@@ -968,18 +977,15 @@ describe('plugin-meetings', () => {
968
977
  );
969
978
  });
970
979
 
971
- it('logs and rethrows error when locusDeltaRequest fails', async () => {
972
- const error = new Error('network error');
973
- locusDeltaRequestSpy.restore();
974
- sinon.stub(meetingsRequest, 'locusDeltaRequest').rejects(error);
980
+ it('returns null when locusDeltaRequest fails', async () => {
981
+ locusDeltaRequestSpy.rejects(new Error('network error'));
975
982
 
976
- await assert.isRejected(
977
- meetingsRequest.fetchDatachannelToken({
978
- locusUrl,
979
- requestingParticipantId: participantId,
980
- }),
981
- /network error/
982
- );
983
+ const result = await meetingsRequest.fetchDatachannelToken({
984
+ locusUrl,
985
+ requestingParticipantId: participantId,
986
+ });
987
+
988
+ assert.equal(result, null);
983
989
  });
984
990
  });
985
991
  });
@@ -666,8 +666,8 @@ describe('MediaRequestManager', () => {
666
666
  ]);
667
667
  });
668
668
 
669
- it('avoids sending duplicate requests and clears all the requests on reset()', () => {
670
- // send some requests and commit them one by one
669
+ it('clears all the requests on reset()', () => {
670
+ // send some requests and commit them
671
671
  addReceiverSelectedRequest(1500, fakeReceiveSlots[0], MAX_FS_1080p, false);
672
672
  addReceiverSelectedRequest(1501, fakeReceiveSlots[1], MAX_FS_1080p, false);
673
673
  addActiveSpeakerRequest(
@@ -722,95 +722,12 @@ describe('MediaRequestManager', () => {
722
722
  },
723
723
  ]);
724
724
 
725
- // check that when calling commit()
726
- // all requests are not re-sent again (avoid duplicate requests)
727
- mediaRequestManager.commit();
728
-
729
- assert.notCalled(sendMediaRequestsCallback);
730
-
731
- // now reset everything
732
- mediaRequestManager.reset();
733
-
734
- // calling commit now should not cause any requests to be sent out
735
- mediaRequestManager.commit();
736
- checkMediaRequestsSent([]);
737
- });
738
-
739
- it('makes sure to call requests correctly after reset was called and another request was added', () => {
740
- addReceiverSelectedRequest(1500, fakeReceiveSlots[0], MAX_FS_1080p, false);
741
-
742
- assert.notCalled(sendMediaRequestsCallback);
743
-
744
- mediaRequestManager.commit();
745
- checkMediaRequestsSent([
746
- {
747
- policy: 'receiver-selected',
748
- csi: 1500,
749
- receiveSlot: fakeWcmeSlots[0],
750
- maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_1080p,
751
- maxFs: MAX_FS_1080p,
752
- maxMbps: MAX_MBPS_1080p,
753
- },
754
- ]);
755
-
756
725
  // now reset everything
757
726
  mediaRequestManager.reset();
758
727
 
759
728
  // calling commit now should not cause any requests to be sent out
760
729
  mediaRequestManager.commit();
761
730
  checkMediaRequestsSent([]);
762
-
763
- //add new request
764
- addReceiverSelectedRequest(1501, fakeReceiveSlots[1], MAX_FS_1080p, false);
765
-
766
- // commit
767
- mediaRequestManager.commit();
768
-
769
- // check the new request was sent
770
- checkMediaRequestsSent([
771
- {
772
- policy: 'receiver-selected',
773
- csi: 1501,
774
- receiveSlot: fakeWcmeSlots[1],
775
- maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_1080p,
776
- maxFs: MAX_FS_1080p,
777
- maxMbps: MAX_MBPS_1080p,
778
- },
779
- ]);
780
- });
781
-
782
- it('can send same media request after previous requests have been cleared', () => {
783
- // add a request and commit
784
- addReceiverSelectedRequest(1500, fakeReceiveSlots[0], MAX_FS_1080p, false);
785
- mediaRequestManager.commit();
786
- checkMediaRequestsSent([
787
- {
788
- policy: 'receiver-selected',
789
- csi: 1500,
790
- maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_1080p,
791
- receiveSlot: fakeWcmeSlots[0],
792
- maxFs: MAX_FS_1080p,
793
- maxMbps: MAX_MBPS_1080p,
794
- },
795
- ]);
796
-
797
- // clear previous requests
798
- mediaRequestManager.clearPreviousRequests();
799
-
800
- // commit same request
801
- mediaRequestManager.commit();
802
-
803
- // check the request was sent
804
- checkMediaRequestsSent([
805
- {
806
- policy: 'receiver-selected',
807
- csi: 1500,
808
- maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_1080p,
809
- receiveSlot: fakeWcmeSlots[0],
810
- maxFs: MAX_FS_1080p,
811
- maxMbps: MAX_MBPS_1080p,
812
- },
813
- ]);
814
731
  });
815
732
 
816
733
  it('re-sends media requests after degradation preferences are set', () => {
@@ -54,8 +54,8 @@ describe('plugin-meetings', () => {
54
54
  webrtcMediaConnection: fakeMediaConnection,
55
55
  },
56
56
  mediaRequestManagers: {
57
- audio: {commit: sinon.stub(), clearPreviousRequests: sinon.stub()},
58
- video: {commit: sinon.stub(), clearPreviousRequests: sinon.stub()},
57
+ audio: {commit: sinon.stub()},
58
+ video: {commit: sinon.stub()},
59
59
  },
60
60
  roap: {
61
61
  doTurnDiscovery: sinon.stub().resolves({
@@ -179,26 +179,22 @@ describe('plugin-meetings', () => {
179
179
  });
180
180
  });
181
181
 
182
- it('does not clear previous requests and re-request media for non-multistream meetings', async () => {
182
+ it('does not re-request media for non-multistream meetings', async () => {
183
183
  fakeMeeting.isMultistream = false;
184
184
  const rm = new ReconnectionManager(fakeMeeting);
185
185
 
186
186
  await rm.reconnect();
187
187
 
188
- assert.notCalled(fakeMeeting.mediaRequestManagers.audio.clearPreviousRequests);
189
- assert.notCalled(fakeMeeting.mediaRequestManagers.video.clearPreviousRequests);
190
188
  assert.notCalled(fakeMeeting.mediaRequestManagers.audio.commit);
191
189
  assert.notCalled(fakeMeeting.mediaRequestManagers.video.commit);
192
190
  });
193
191
 
194
- it('does clear previous requests and re-request media for multistream meetings', async () => {
192
+ it('does re-request media for multistream meetings', async () => {
195
193
  fakeMeeting.isMultistream = true;
196
194
  const rm = new ReconnectionManager(fakeMeeting);
197
195
 
198
196
  await rm.reconnect();
199
197
 
200
- assert.calledOnce(fakeMeeting.mediaRequestManagers.audio.clearPreviousRequests);
201
- assert.calledOnce(fakeMeeting.mediaRequestManagers.video.clearPreviousRequests);
202
198
  assert.calledOnce(fakeMeeting.mediaRequestManagers.audio.commit);
203
199
  assert.calledOnce(fakeMeeting.mediaRequestManagers.video.commit);
204
200
  });
@@ -233,6 +233,8 @@ describe('plugin-meetings', () => {
233
233
  // Ensure connect path is eligible
234
234
  webinar.selfIsPanelist = true;
235
235
  webinar.practiceSessionEnabled = true;
236
+ webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
237
+ webex.internal.voicea.updateSubchannelSubscriptions = sinon.stub();
236
238
  });
237
239
 
238
240
  it('no-ops when practice session join eligibility is false', async () => {
@@ -342,6 +344,22 @@ describe('plugin-meetings', () => {
342
344
  processRelayEvent
343
345
  );
344
346
  });
347
+
348
+ it('subscribes to transcription when caption intent is enabled', async () => {
349
+ webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(true);
350
+
351
+ await webinar.updatePSDataChannel();
352
+
353
+ assert.calledOnceWithExactly(webex.internal.voicea.updateSubchannelSubscriptions, { subscribe: ['transcription'] });
354
+ });
355
+
356
+ it('does not subscribe to transcription when caption intent is disabled', async () => {
357
+ webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
358
+
359
+ await webinar.updatePSDataChannel();
360
+
361
+ assert.notCalled(webex.internal.voicea.updateSubchannelSubscriptions);
362
+ });
345
363
  });
346
364
 
347
365
  describe('#updateStatusByRole', () => {