agora-appbuilder-core 4.1.23-beta.5 → 4.1.24-beta.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agora-appbuilder-core",
3
- "version": "4.1.23-beta.5",
3
+ "version": "4.1.24-beta.1",
4
4
  "description": "React Native template for RTE app builder",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -209,6 +209,14 @@ interface ActiveScreenshareLifecycle {
209
209
  cleanupPromise?: Promise<void>;
210
210
  journeyData: Record<string, unknown>;
211
211
  }
212
+ interface ScreenshareJourneyContext {
213
+ screenshareAttemptId?: string;
214
+ screenshareSessionId?: string;
215
+ recordingActive?: boolean;
216
+ screenShareUid?: UID;
217
+ stopOrigin?: string;
218
+ stopActorUid?: UID;
219
+ }
212
220
  interface RemoteStream {
213
221
  audio?: IRemoteAudioTrack;
214
222
  video?: IRemoteVideoTrack;
@@ -883,24 +891,28 @@ export default class RtcEngine {
883
891
  user,
884
892
  mediaType,
885
893
  );
886
- if (this.inScreenshare && user.uid === this.screenClient.uid) {
887
- (this.eventsMap.get('onRemoteVideoStateChanged') as callbackType)(
888
- {},
889
- user.uid,
890
- 2,
891
- 0,
892
- 0,
893
- );
894
- } else {
895
- await this.client.subscribe(user, mediaType);
896
- logger.log(
897
- LogSource.AgoraSDK,
898
- 'API',
899
- 'RTC [subscribe] to track successfully done',
900
- user,
901
- mediaType,
902
- );
894
+ const isOwnScreenshare =
895
+ this.inScreenshare && user.uid === this.screenClient?.uid;
896
+ if (isOwnScreenshare) {
897
+ if (mediaType === 'video') {
898
+ (this.eventsMap.get('onRemoteVideoStateChanged') as callbackType)(
899
+ {},
900
+ user.uid,
901
+ 2,
902
+ 0,
903
+ 0,
904
+ );
905
+ }
906
+ return;
903
907
  }
908
+ await this.client.subscribe(user, mediaType);
909
+ logger.log(
910
+ LogSource.AgoraSDK,
911
+ 'API',
912
+ 'RTC [subscribe] to track successfully done',
913
+ user,
914
+ mediaType,
915
+ );
904
916
  // If the subscribed track is an audio track
905
917
  if (mediaType === 'audio') {
906
918
  const audioTrack = user.audioTrack;
@@ -948,6 +960,20 @@ export default class RtcEngine {
948
960
  user,
949
961
  mediaType,
950
962
  );
963
+ const isOwnScreenshare =
964
+ this.inScreenshare && user.uid === this.screenClient?.uid;
965
+ if (isOwnScreenshare) {
966
+ if (mediaType === 'video') {
967
+ (this.eventsMap.get('onRemoteVideoStateChanged') as callbackType)(
968
+ {},
969
+ user.uid,
970
+ 0,
971
+ 0,
972
+ 0,
973
+ );
974
+ }
975
+ return;
976
+ }
951
977
  if (mediaType === 'video') {
952
978
  this.logRemoteScreenshareTermination(
953
979
  user.uid,
@@ -1713,6 +1739,9 @@ export default class RtcEngine {
1713
1739
  return;
1714
1740
  }
1715
1741
 
1742
+ // The track-ended callback passes the exact lifecycle object it captured.
1743
+ // A different reference means a newer screen share is now active, so this
1744
+ // older callback must not clean up the current share.
1716
1745
  if (
1717
1746
  expectedLifecycle &&
1718
1747
  this.activeScreenshareLifecycle !== expectedLifecycle
@@ -1814,6 +1843,9 @@ export default class RtcEngine {
1814
1843
  throw error;
1815
1844
  } finally {
1816
1845
  lifecycle.cleaned = true;
1846
+ // Only the lifecycle that is still active may clear shared state and
1847
+ // notify the app. Object identity prevents stale cleanup from ending a
1848
+ // newer screen-share session.
1817
1849
  if (this.activeScreenshareLifecycle === lifecycle) {
1818
1850
  this.activeScreenshareLifecycle = null;
1819
1851
  this.screenStream = {};
@@ -1970,6 +2002,41 @@ export default class RtcEngine {
1970
2002
  }
1971
2003
  }
1972
2004
 
2005
+ async stopScreenshare(
2006
+ journeyContext: ScreenshareJourneyContext = {},
2007
+ ): Promise<void> {
2008
+ const journeyData = {
2009
+ action: 'stop' as const,
2010
+ screenshareAttemptId:
2011
+ journeyContext.screenshareAttemptId || 'rtc-unknown-attempt',
2012
+ screenshareSessionId:
2013
+ journeyContext.screenshareSessionId || 'unknown-session',
2014
+ recordingActive: journeyContext.recordingActive || false,
2015
+ screenShareUid:
2016
+ journeyContext.screenShareUid || this.screenClient?.uid,
2017
+ stopOrigin: journeyContext.stopOrigin || 'unknown',
2018
+ stopActorUid: journeyContext.stopActorUid,
2019
+ };
2020
+ logger.log(
2021
+ LogSource.AgoraSDK,
2022
+ 'API',
2023
+ `[SCREENSHARE_JOURNEY] screen share stop entered RTC engine from ${journeyData.stopOrigin}`,
2024
+ {
2025
+ ...journeyData,
2026
+ stage: 'rtc_stop',
2027
+ outcome: 'started',
2028
+ requestedAction: 'stop',
2029
+ executedAction: 'stop',
2030
+ operationState: this.screenshareOperationState,
2031
+ },
2032
+ );
2033
+ await this.cleanupActiveScreenshare(
2034
+ journeyData.stopOrigin,
2035
+ true,
2036
+ journeyData.screenshareAttemptId,
2037
+ );
2038
+ }
2039
+
1973
2040
  async startScreenshare(
1974
2041
  token: string,
1975
2042
  channelName: string,
@@ -1986,18 +2053,10 @@ export default class RtcEngine {
1986
2053
  encoderConfig: this.screenShareProfile,
1987
2054
  },
1988
2055
  audio: 'enable' | 'disable' | 'auto' = 'auto',
1989
- journeyContext: {
1990
- action?: 'start' | 'stop';
1991
- screenshareAttemptId?: string;
1992
- screenshareSessionId?: string;
1993
- recordingActive?: boolean;
1994
- screenShareUid?: UID;
1995
- stopOrigin?: string;
1996
- stopActorUid?: UID;
1997
- } = {},
2056
+ journeyContext: ScreenshareJourneyContext = {},
1998
2057
  ): Promise<void> {
1999
2058
  const journeyData = {
2000
- action: journeyContext.action || (this.inScreenshare ? 'stop' : 'start'),
2059
+ action: 'start' as const,
2001
2060
  screenshareAttemptId:
2002
2061
  journeyContext.screenshareAttemptId || 'rtc-unknown-attempt',
2003
2062
  screenshareSessionId:
@@ -2011,28 +2070,6 @@ export default class RtcEngine {
2011
2070
  ...screenShareConfig,
2012
2071
  encoderConfig: this.screenShareProfile,
2013
2072
  };
2014
- if (journeyData.action === 'stop') {
2015
- logger.log(
2016
- LogSource.AgoraSDK,
2017
- 'API',
2018
- `[SCREENSHARE_JOURNEY] screen share stop entered RTC engine from ${journeyData.stopOrigin}`,
2019
- {
2020
- ...journeyData,
2021
- stage: 'rtc_stop',
2022
- outcome: 'started',
2023
- requestedAction: 'stop',
2024
- executedAction: 'stop',
2025
- operationState: this.screenshareOperationState,
2026
- },
2027
- );
2028
- await this.cleanupActiveScreenshare(
2029
- journeyData.stopOrigin,
2030
- true,
2031
- journeyData.screenshareAttemptId,
2032
- );
2033
- return;
2034
- }
2035
-
2036
2073
  if (this.screenshareOperationState !== 'inactive' || this.inScreenshare) {
2037
2074
  const duplicateStartError = Object.assign(
2038
2075
  new Error(
@@ -75,7 +75,7 @@ describe('web RTC screen-share lifecycle', () => {
75
75
  let engine: any;
76
76
  let screenClient: any;
77
77
 
78
- const callScreenshare = (action: 'start' | 'stop', sessionId = 'session-1') =>
78
+ const callStartScreenshare = (sessionId = 'session-1') =>
79
79
  engine.startScreenshare(
80
80
  'token',
81
81
  'channel',
@@ -87,14 +87,21 @@ describe('web RTC screen-share lifecycle', () => {
87
87
  {encoderConfig: '1080p_2'},
88
88
  'auto',
89
89
  {
90
- action,
91
- screenshareAttemptId: `${action}-request`,
90
+ screenshareAttemptId: 'start-request',
92
91
  screenshareSessionId: sessionId,
93
92
  screenShareUid: 101,
94
93
  stopOrigin: 'toolbar',
95
94
  },
96
95
  );
97
96
 
97
+ const callStopScreenshare = (sessionId = 'session-1') =>
98
+ engine.stopScreenshare({
99
+ screenshareAttemptId: 'stop-request',
100
+ screenshareSessionId: sessionId,
101
+ screenShareUid: 101,
102
+ stopOrigin: 'toolbar',
103
+ });
104
+
98
105
  beforeAll(() => {
99
106
  (global as any).window = {};
100
107
  RtcEngine = require('../RtcEngine').default;
@@ -120,9 +127,9 @@ describe('web RTC screen-share lifecycle', () => {
120
127
  const {track} = createTrack();
121
128
  mockCreateScreenVideoTrack.mockReturnValueOnce(picker.promise);
122
129
 
123
- const firstStart = callScreenshare('start');
130
+ const firstStart = callStartScreenshare();
124
131
  await expect(
125
- callScreenshare('start', 'duplicate-session'),
132
+ callStartScreenshare('duplicate-session'),
126
133
  ).rejects.toMatchObject({code: 'SCREENSHARE_OPERATION_IN_PROGRESS'});
127
134
 
128
135
  expect(mockCreateScreenVideoTrack).toHaveBeenCalledTimes(1);
@@ -136,7 +143,7 @@ describe('web RTC screen-share lifecycle', () => {
136
143
  const stopped = jest.fn();
137
144
  engine.eventsMap.set('onScreenshareStopped', stopped);
138
145
 
139
- await callScreenshare('stop');
146
+ await callStopScreenshare();
140
147
 
141
148
  expect(mockCreateScreenVideoTrack).not.toHaveBeenCalled();
142
149
  expect(screenClient.join).not.toHaveBeenCalled();
@@ -153,7 +160,7 @@ describe('web RTC screen-share lifecycle', () => {
153
160
  }),
154
161
  );
155
162
 
156
- await expect(callScreenshare('start')).rejects.toMatchObject({
163
+ await expect(callStartScreenshare()).rejects.toMatchObject({
157
164
  code: 'INVALID_OPERATION',
158
165
  });
159
166
 
@@ -168,7 +175,7 @@ describe('web RTC screen-share lifecycle', () => {
168
175
  mockCreateScreenVideoTrack.mockResolvedValueOnce(track);
169
176
  screenClient.publish.mockRejectedValueOnce(new Error('publish failed'));
170
177
 
171
- await expect(callScreenshare('start')).rejects.toThrow('publish failed');
178
+ await expect(callStartScreenshare()).rejects.toThrow('publish failed');
172
179
 
173
180
  expect(track.stop).toHaveBeenCalledTimes(1);
174
181
  expect(track.close).toHaveBeenCalledTimes(1);
@@ -185,7 +192,7 @@ describe('web RTC screen-share lifecycle', () => {
185
192
  mockCreateScreenVideoTrack.mockResolvedValueOnce(track);
186
193
  screenClient.publish.mockRejectedValueOnce(publishError);
187
194
 
188
- await expect(callScreenshare('start')).rejects.toBe(publishError);
195
+ await expect(callStartScreenshare()).rejects.toBe(publishError);
189
196
 
190
197
  expect(track.stop).toHaveBeenCalledTimes(1);
191
198
  expect(track.close).toHaveBeenCalledTimes(1);
@@ -201,13 +208,13 @@ describe('web RTC screen-share lifecycle', () => {
201
208
  }),
202
209
  );
203
210
 
204
- await expect(callScreenshare('start')).rejects.toMatchObject({
211
+ await expect(callStartScreenshare()).rejects.toMatchObject({
205
212
  code: 'PERMISSION_DENIED',
206
213
  });
207
214
 
208
215
  const {track} = createTrack();
209
216
  mockCreateScreenVideoTrack.mockResolvedValueOnce(track);
210
- await callScreenshare('start', 'session-2');
217
+ await callStartScreenshare('session-2');
211
218
 
212
219
  expect(mockCreateScreenVideoTrack).toHaveBeenCalledTimes(2);
213
220
  expect(screenClient.join).toHaveBeenCalledTimes(1);
@@ -220,7 +227,7 @@ describe('web RTC screen-share lifecycle', () => {
220
227
  const {track} = createTrack();
221
228
  mockCreateScreenVideoTrack.mockReturnValueOnce(picker.promise);
222
229
 
223
- const pendingStart = callScreenshare('start');
230
+ const pendingStart = callStartScreenshare();
224
231
  await engine.release('end_call_cleanup');
225
232
  picker.resolve(track);
226
233
 
@@ -239,8 +246,8 @@ describe('web RTC screen-share lifecycle', () => {
239
246
  engine.eventsMap.set('onScreenshareStopped', stopped);
240
247
  mockCreateScreenVideoTrack.mockResolvedValueOnce(track);
241
248
 
242
- await callScreenshare('start');
243
- await callScreenshare('stop');
249
+ await callStartScreenshare();
250
+ await callStopScreenshare();
244
251
  await handlers['track-ended']();
245
252
 
246
253
  expect(screenClient.leave).toHaveBeenCalledTimes(1);
@@ -249,6 +256,28 @@ describe('web RTC screen-share lifecycle', () => {
249
256
  expect(stopped).toHaveBeenCalledTimes(1);
250
257
  });
251
258
 
259
+ it('does not let an older lifecycle clean up a newer screen share', async () => {
260
+ const first = createTrack();
261
+ const second = createTrack();
262
+ const stopped = jest.fn();
263
+ engine.eventsMap.set('onScreenshareStopped', stopped);
264
+ mockCreateScreenVideoTrack
265
+ .mockResolvedValueOnce(first.track)
266
+ .mockResolvedValueOnce(second.track);
267
+
268
+ await callStartScreenshare();
269
+ await callStopScreenshare();
270
+ await callStartScreenshare('session-2');
271
+ await first.handlers['track-ended']();
272
+
273
+ expect(engine.activeScreenshareLifecycle.video).toBe(second.track);
274
+ expect(engine.inScreenshare).toBe(true);
275
+ expect(screenClient.leave).toHaveBeenCalledTimes(1);
276
+ expect(second.track.stop).not.toHaveBeenCalled();
277
+ expect(second.track.close).not.toHaveBeenCalled();
278
+ expect(stopped).toHaveBeenCalledTimes(1);
279
+ });
280
+
252
281
  it('joins an existing cleanup when Stop is requested twice', async () => {
253
282
  const {track} = createTrack();
254
283
  const leave = deferred<void>();
@@ -257,9 +286,9 @@ describe('web RTC screen-share lifecycle', () => {
257
286
  mockCreateScreenVideoTrack.mockResolvedValueOnce(track);
258
287
  screenClient.leave.mockReturnValueOnce(leave.promise);
259
288
 
260
- await callScreenshare('start');
261
- const firstStop = callScreenshare('stop');
262
- const secondStop = callScreenshare('stop');
289
+ await callStartScreenshare();
290
+ const firstStop = callStopScreenshare();
291
+ const secondStop = callStopScreenshare();
263
292
  leave.resolve(undefined);
264
293
  await Promise.all([firstStop, secondStop]);
265
294
 
@@ -278,8 +307,8 @@ describe('web RTC screen-share lifecycle', () => {
278
307
  throw new Error('track stop failed');
279
308
  });
280
309
 
281
- await callScreenshare('start');
282
- await expect(callScreenshare('stop')).resolves.toBeUndefined();
310
+ await callStartScreenshare();
311
+ await expect(callStopScreenshare()).resolves.toBeUndefined();
283
312
 
284
313
  expect(track.close).toHaveBeenCalledTimes(1);
285
314
  expect(screenClient.leave).toHaveBeenCalledTimes(1);
@@ -294,7 +323,7 @@ describe('web RTC screen-share lifecycle', () => {
294
323
  mockCreateScreenVideoTrack.mockResolvedValueOnce(track);
295
324
  screenClient.leave.mockRejectedValueOnce(new Error('leave failed'));
296
325
 
297
- await callScreenshare('start');
326
+ await callStartScreenshare();
298
327
  await expect(handlers['track-ended']()).resolves.toBeUndefined();
299
328
 
300
329
  expect(screenClient.leave).toHaveBeenCalledTimes(1);
@@ -317,7 +346,7 @@ describe('web RTC screen-share lifecycle', () => {
317
346
  engine.eventsMap.set('onScreenshareStopped', stopped);
318
347
  mockCreateScreenVideoTrack.mockResolvedValueOnce(track);
319
348
 
320
- await callScreenshare('start');
349
+ await callStartScreenshare();
321
350
  await engine.release('end_call_cleanup');
322
351
 
323
352
  const {logger} = require('../../../../src/logger/AppBuilderLogger');
@@ -77,8 +77,8 @@ const DefaultConfig = {
77
77
  CHAT_ORG_NAME: "",
78
78
  CHAT_APP_NAME: "",
79
79
  CHAT_URL: "",
80
- CLI_VERSION: "3.1.23-beta.5",
81
- CORE_VERSION: "4.1.23-beta.5",
80
+ CLI_VERSION: "3.1.24-beta.1",
81
+ CORE_VERSION: "4.1.24-beta.1",
82
82
  DISABLE_LANDSCAPE_MODE: false,
83
83
  STT_AUTO_START: false,
84
84
  CLOUD_RECORDING_AUTO_START: false,
@@ -40,6 +40,8 @@ import {
40
40
  sttTranscriptPanelSearchText,
41
41
  sttTranscriptPanelViewLatestText,
42
42
  } from '../../../src/language/default-labels/videoCallScreenLabels';
43
+ import {logger, LogSource} from '../../logger/AppBuilderLogger';
44
+ import {TRANSCRIPT_JOURNEY} from './transcriptJourney';
43
45
 
44
46
  export interface TranscriptProps {
45
47
  showHeader?: boolean;
@@ -56,6 +58,7 @@ const Transcript = (props: TranscriptProps) => {
56
58
  isSTTActive,
57
59
  selectedTranslationLanguage,
58
60
  transcriptViewMode,
61
+ isSTTListenerAdded,
59
62
  } = useCaption();
60
63
 
61
64
  const settingSpokenLanguageLabel = useString(sttSettingSpokenLanguageText)();
@@ -68,6 +71,39 @@ const Transcript = (props: TranscriptProps) => {
68
71
 
69
72
  const data = meetingTranscript; // Object.entries(transcript);
70
73
  console.log('[STT_GLOBAL] meetingTranscript data: ', data);
74
+ const hasLoggedFirstEntryRef = React.useRef(false);
75
+ const transcriptEntryCountRef = React.useRef(data.length);
76
+ transcriptEntryCountRef.current = data.length;
77
+
78
+ React.useEffect(() => {
79
+ logger.log(
80
+ LogSource.Internals,
81
+ 'TRANSCRIPT',
82
+ `${TRANSCRIPT_JOURNEY} transcript panel state`,
83
+ {
84
+ stage: 'transcript_panel',
85
+ isSTTActive,
86
+ listenerRegistered: isSTTListenerAdded,
87
+ transcriptEntryCount: transcriptEntryCountRef.current,
88
+ },
89
+ );
90
+ }, [isSTTActive, isSTTListenerAdded]);
91
+
92
+ React.useEffect(() => {
93
+ if (data.length > 0 && !hasLoggedFirstEntryRef.current) {
94
+ hasLoggedFirstEntryRef.current = true;
95
+ logger.log(
96
+ LogSource.Internals,
97
+ 'TRANSCRIPT',
98
+ `${TRANSCRIPT_JOURNEY} first transcript entry available to panel`,
99
+ {
100
+ stage: 'transcript_panel_data',
101
+ outcome: 'success',
102
+ transcriptEntryCount: data.length,
103
+ },
104
+ );
105
+ }
106
+ }, [data.length]);
71
107
 
72
108
  const [showButton, setShowButton] = React.useState(false);
73
109
 
@@ -0,0 +1,7 @@
1
+ import {TRANSCRIPT_JOURNEY} from '../transcriptJourney';
2
+
3
+ describe('transcript journey logging', () => {
4
+ it('uses one searchable prefix', () => {
5
+ expect(TRANSCRIPT_JOURNEY).toBe('[TRANSCRIPT_JOURNEY]');
6
+ });
7
+ });
@@ -0,0 +1 @@
1
+ export const TRANSCRIPT_JOURNEY = '[TRANSCRIPT_JOURNEY]';