agora-appbuilder-core 4.1.23 → 4.1.24-beta.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.
@@ -55,6 +55,11 @@ import {
55
55
  getScreenshareStopDecision,
56
56
  } from './screenshareOperation';
57
57
  import {createScreenshareRecordingLayoutReconciler} from './screenshareRecordingLayoutReconciler';
58
+ import {
59
+ captureScreenshareRecoveryCandidate,
60
+ getScreenshareRecoveryDecision,
61
+ ScreenshareRecoveryCandidate,
62
+ } from './screenshareInterruptionRecovery';
58
63
 
59
64
  type ScreenshareAction = 'start' | 'stop';
60
65
 
@@ -93,6 +98,14 @@ export const ScreenshareConfigure = (props: {
93
98
  const changeLayout = useChangeDefaultLayout();
94
99
  const {currentLayout} = useLayout();
95
100
  const currentLayoutRef = useRef({currentLayout: currentLayout});
101
+ const recoveryCandidateRef = useRef<ScreenshareRecoveryCandidate | null>(
102
+ null,
103
+ );
104
+ const previousLayoutStateRef = useRef({
105
+ activeUids,
106
+ pinnedUid,
107
+ currentLayout,
108
+ });
96
109
 
97
110
  const updateOperationState = (state: ScreenshareOperationState) => {
98
111
  operationStateRef.current = state;
@@ -201,6 +214,142 @@ export const ScreenshareConfigure = (props: {
201
214
  }
202
215
  };
203
216
 
217
+ useEffect(() => {
218
+ const previousLayoutState = previousLayoutStateRef.current;
219
+ const existingCandidate = recoveryCandidateRef.current;
220
+ // Detect when the previously pinned screen-share UID disappears from the
221
+ // RTC render list while RTM still says the logical share is active.
222
+ const capturedCandidate = captureScreenshareRecoveryCandidate({
223
+ previousActiveUids: previousLayoutState.activeUids,
224
+ currentActiveUids: activeUids,
225
+ previousPinnedUid: previousLayoutState.pinnedUid,
226
+ previousLayout: previousLayoutState.currentLayout,
227
+ screenShareData,
228
+ detectedAt: Date.now(),
229
+ });
230
+
231
+ if (!existingCandidate && capturedCandidate) {
232
+ recoveryCandidateRef.current = capturedCandidate;
233
+ logger.log(
234
+ LogSource.Internals,
235
+ 'SCREENSHARE',
236
+ `${SCREENSHARE_JOURNEY} pinned screen share removed by RTC; waiting for same UID to recover`,
237
+ {
238
+ action: 'recover',
239
+ stage: 'rtc_interruption',
240
+ outcome: 'started',
241
+ screenshareSessionId:
242
+ activeScreenshareSessionIdRef.current || 'unknown-session',
243
+ screenShareUid: capturedCandidate.uid,
244
+ previousPinnedUid: capturedCandidate.previousPinnedUid,
245
+ previousLayout: capturedCandidate.previousLayout,
246
+ },
247
+ );
248
+ }
249
+
250
+ const candidate = recoveryCandidateRef.current;
251
+ if (candidate) {
252
+ // Recovery is driven by render state: first wait for the same UID to
253
+ // rejoin, then wait for its video to be published before restoring it.
254
+ const decision = getScreenshareRecoveryDecision({
255
+ candidate,
256
+ activeUids,
257
+ pinnedUid,
258
+ isVideoPublished: defaultContent?.[candidate.uid]?.video === 1,
259
+ screenShareData,
260
+ });
261
+
262
+ if (decision === 'waiting_for_video' && !candidate.joinedLogged) {
263
+ // The RTC user is back, but restoring now could show a blank tile.
264
+ candidate.joinedLogged = true;
265
+ logger.log(
266
+ LogSource.Internals,
267
+ 'SCREENSHARE',
268
+ `${SCREENSHARE_JOURNEY} interrupted screen share UID rejoined; waiting for video publication`,
269
+ {
270
+ action: 'recover',
271
+ stage: 'rtc_rejoin',
272
+ outcome: 'waiting',
273
+ screenshareSessionId:
274
+ activeScreenshareSessionIdRef.current || 'unknown-session',
275
+ screenShareUid: candidate.uid,
276
+ elapsedMs: Date.now() - candidate.detectedAt,
277
+ },
278
+ );
279
+ } else if (decision === 'restore') {
280
+ // Restore only the layout state that existed before interruption. A
281
+ // newer user pin or screen-share session cancels this path below.
282
+ isPinned.current = candidate.uid;
283
+ dispatch({type: 'UserPin', value: [candidate.uid]});
284
+ if (
285
+ candidate.previousLayout === getPinnedLayoutName() &&
286
+ currentLayout !== getPinnedLayoutName()
287
+ ) {
288
+ setPinnedLayout();
289
+ }
290
+ recoveryCandidateRef.current = null;
291
+ logger.log(
292
+ LogSource.Internals,
293
+ 'SCREENSHARE',
294
+ `${SCREENSHARE_JOURNEY} interrupted screen share video republished; previous pin restored`,
295
+ {
296
+ action: 'recover',
297
+ stage: 'layout_restore',
298
+ outcome: 'success',
299
+ screenshareSessionId:
300
+ activeScreenshareSessionIdRef.current || 'unknown-session',
301
+ screenShareUid: candidate.uid,
302
+ restoredPinnedUid: candidate.previousPinnedUid,
303
+ restoredLayout: candidate.previousLayout,
304
+ elapsedMs: Date.now() - candidate.detectedAt,
305
+ },
306
+ );
307
+ } else if (decision.startsWith('cancel_')) {
308
+ // An authoritative stop, replacement share, or user layout choice
309
+ // takes precedence over automatic interruption recovery.
310
+ recoveryCandidateRef.current = null;
311
+ logger.log(
312
+ LogSource.Internals,
313
+ 'SCREENSHARE',
314
+ `${SCREENSHARE_JOURNEY} interrupted screen share pin restoration skipped`,
315
+ {
316
+ action: 'recover',
317
+ stage: 'layout_restore',
318
+ outcome: 'skipped',
319
+ screenshareSessionId:
320
+ activeScreenshareSessionIdRef.current || 'unknown-session',
321
+ screenShareUid: candidate.uid,
322
+ skipReason: decision,
323
+ currentPinnedUid: pinnedUid,
324
+ currentLayout,
325
+ elapsedMs: Date.now() - candidate.detectedAt,
326
+ },
327
+ );
328
+ }
329
+ }
330
+
331
+ previousLayoutStateRef.current = {
332
+ activeUids,
333
+ pinnedUid,
334
+ currentLayout,
335
+ };
336
+ }, [
337
+ activeUids,
338
+ currentLayout,
339
+ defaultContent,
340
+ dispatch,
341
+ pinnedUid,
342
+ screenShareData,
343
+ setPinnedLayout,
344
+ ]);
345
+
346
+ useEffect(
347
+ () => () => {
348
+ recoveryCandidateRef.current = null;
349
+ },
350
+ [],
351
+ );
352
+
204
353
  useEffect(() => {
205
354
  const unsubKickScreenshare = events.on(
206
355
  controlMessageEnum.kickScreenshare,
@@ -452,63 +601,80 @@ export const ScreenshareConfigure = (props: {
452
601
  );
453
602
  }, []);
454
603
 
455
- const executeRecordingQuery = async (
456
- isScreenActive: boolean,
604
+ const executeStartRecordingLayoutQuery = async (
457
605
  screenshareAttemptId: string,
458
606
  screenshareSessionId: string,
459
607
  stopOrigin: ScreenshareStopOrigin,
460
608
  stopActorUid?: UidType,
461
609
  ) => {
462
- const action: ScreenshareAction = isScreenActive ? 'start' : 'stop';
463
- if (isScreenActive) {
464
- // If recording is going on, set the presenter query
465
- logger.log(
466
- LogSource.Internals,
467
- 'SCREENSHARE',
468
- `${SCREENSHARE_JOURNEY} screen share start recording presenter layout query started`,
469
- {
470
- action,
471
- stage: 'recording_layout',
472
- outcome: 'started',
473
- screenshareAttemptId,
474
- screenshareSessionId,
475
- recordingActive: props.isRecordingActive,
476
- screenShareUid,
477
- stopOrigin,
478
- stopActorUid,
479
- },
480
- );
481
- await executePresenterQuery(screenShareUid);
482
- recordingLayoutReconcilerRef.current.markApplied(
610
+ logger.log(
611
+ LogSource.Internals,
612
+ 'SCREENSHARE',
613
+ `${SCREENSHARE_JOURNEY} screen share start recording presenter layout query started`,
614
+ {
615
+ action: 'start',
616
+ stage: 'recording_layout',
617
+ outcome: 'started',
618
+ screenshareAttemptId,
483
619
  screenshareSessionId,
484
- recordingActiveRef.current,
485
- );
486
- } else {
487
- logger.log(
488
- LogSource.Internals,
489
- 'SCREENSHARE',
490
- `${SCREENSHARE_JOURNEY} screen share stop recording normal layout query started`,
491
- {
492
- action,
493
- stage: 'recording_layout',
494
- outcome: 'started',
495
- screenshareAttemptId,
496
- screenshareSessionId,
497
- recordingActive: props.isRecordingActive,
498
- screenShareUid,
499
- stopOrigin,
500
- stopActorUid,
501
- },
502
- );
503
- // If no recording is going on, set the normal query
504
- await executeNormalQuery();
505
- }
620
+ recordingActive: props.isRecordingActive,
621
+ screenShareUid,
622
+ stopOrigin,
623
+ stopActorUid,
624
+ },
625
+ );
626
+ await executePresenterQuery(screenShareUid);
627
+ recordingLayoutReconcilerRef.current.markApplied(
628
+ screenshareSessionId,
629
+ recordingActiveRef.current,
630
+ );
631
+ logger.log(
632
+ LogSource.Internals,
633
+ 'SCREENSHARE',
634
+ `${SCREENSHARE_JOURNEY} screen share start recording layout query completed successfully`,
635
+ {
636
+ action: 'start',
637
+ stage: 'recording_layout',
638
+ outcome: 'success',
639
+ screenshareAttemptId,
640
+ screenshareSessionId,
641
+ recordingActive: props.isRecordingActive,
642
+ screenShareUid,
643
+ stopOrigin,
644
+ stopActorUid,
645
+ },
646
+ );
647
+ };
648
+
649
+ const executeStopRecordingLayoutQuery = async (
650
+ screenshareAttemptId: string,
651
+ screenshareSessionId: string,
652
+ stopOrigin: ScreenshareStopOrigin,
653
+ stopActorUid?: UidType,
654
+ ) => {
655
+ logger.log(
656
+ LogSource.Internals,
657
+ 'SCREENSHARE',
658
+ `${SCREENSHARE_JOURNEY} screen share stop recording normal layout query started`,
659
+ {
660
+ action: 'stop',
661
+ stage: 'recording_layout',
662
+ outcome: 'started',
663
+ screenshareAttemptId,
664
+ screenshareSessionId,
665
+ recordingActive: props.isRecordingActive,
666
+ screenShareUid,
667
+ stopOrigin,
668
+ stopActorUid,
669
+ },
670
+ );
671
+ await executeNormalQuery();
506
672
  logger.log(
507
673
  LogSource.Internals,
508
674
  'SCREENSHARE',
509
- `${SCREENSHARE_JOURNEY} screen share ${action} recording layout query completed successfully`,
675
+ `${SCREENSHARE_JOURNEY} screen share stop recording layout query completed successfully`,
510
676
  {
511
- action,
677
+ action: 'stop',
512
678
  stage: 'recording_layout',
513
679
  outcome: 'success',
514
680
  screenshareAttemptId,
@@ -744,8 +910,9 @@ export const ScreenshareConfigure = (props: {
744
910
  return;
745
911
  }
746
912
  updateOperationState('stopping');
747
- const stopped = await userScreenshare(
748
- false,
913
+ // Run only the stop workflow. RTC cleanup will invoke
914
+ // ScreenshareStoppedCallback to update local, RTM, and layout state.
915
+ const stopped = await executeStopScreenshareWorkflow(
749
916
  screenshareAttemptId,
750
917
  screenshareSessionId,
751
918
  stopOrigin,
@@ -839,8 +1006,9 @@ export const ScreenshareConfigure = (props: {
839
1006
  },
840
1007
  );
841
1008
  logStartRequested();
842
- const started = await userScreenshare(
843
- true,
1009
+ // Run only the start workflow. Stop requests received while this is
1010
+ // pending are queued above and executed after startup completes.
1011
+ const started = await executeStartScreenshareWorkflow(
844
1012
  screenshareAttemptId,
845
1013
  screenshareSessionId,
846
1014
  'unknown',
@@ -891,24 +1059,22 @@ export const ScreenshareConfigure = (props: {
891
1059
  };
892
1060
  stopScreenshareRef.current = stopScreenshare;
893
1061
 
894
- const userScreenshare = async (
895
- isActive: boolean,
1062
+ const executeStartScreenshareWorkflow = async (
896
1063
  screenshareAttemptId: string,
897
1064
  screenshareSessionId: string,
898
1065
  stopOrigin: ScreenshareStopOrigin,
899
1066
  stopActorUid?: UidType,
900
1067
  ) => {
901
1068
  const startedAt = Date.now();
902
- const action: ScreenshareAction = isActive ? 'start' : 'stop';
903
1069
  let stage = 'recording_layout';
904
1070
  let recordingLayoutFailed = false;
905
1071
  let rtcOperationSucceeded = false;
906
1072
  logger.log(
907
1073
  LogSource.Internals,
908
1074
  'SCREENSHARE',
909
- `${SCREENSHARE_JOURNEY} screen share ${action} workflow started`,
1075
+ `${SCREENSHARE_JOURNEY} screen share start workflow started`,
910
1076
  {
911
- action,
1077
+ action: 'start',
912
1078
  stage: 'journey',
913
1079
  outcome: 'started',
914
1080
  screenshareAttemptId,
@@ -923,8 +1089,7 @@ export const ScreenshareConfigure = (props: {
923
1089
  try {
924
1090
  if (props.isRecordingActive) {
925
1091
  try {
926
- await executeRecordingQuery(
927
- isActive,
1092
+ await executeStartRecordingLayoutQuery(
928
1093
  screenshareAttemptId,
929
1094
  screenshareSessionId,
930
1095
  stopOrigin,
@@ -935,10 +1100,10 @@ export const ScreenshareConfigure = (props: {
935
1100
  logger.error(
936
1101
  LogSource.Internals,
937
1102
  'SCREENSHARE',
938
- `${SCREENSHARE_JOURNEY} screen share ${action} recording layout query failed; continuing screen share ${action}`,
1103
+ `${SCREENSHARE_JOURNEY} screen share start recording layout query failed; continuing screen share start`,
939
1104
  recordingError,
940
1105
  {
941
- action,
1106
+ action: 'start',
942
1107
  stage,
943
1108
  outcome: 'partial_failure',
944
1109
  screenshareAttemptId,
@@ -955,9 +1120,9 @@ export const ScreenshareConfigure = (props: {
955
1120
  logger.log(
956
1121
  LogSource.Internals,
957
1122
  'SCREENSHARE',
958
- `${SCREENSHARE_JOURNEY} screen share ${action} recording layout query skipped because recording is inactive`,
1123
+ `${SCREENSHARE_JOURNEY} screen share start recording layout query skipped because recording is inactive`,
959
1124
  {
960
- action,
1125
+ action: 'start',
961
1126
  stage,
962
1127
  outcome: 'skipped',
963
1128
  screenshareAttemptId,
@@ -973,9 +1138,9 @@ export const ScreenshareConfigure = (props: {
973
1138
  logger.log(
974
1139
  LogSource.Internals,
975
1140
  'SCREENSHARE',
976
- `${SCREENSHARE_JOURNEY} screen share ${action} calling RtcEngine.startScreenshare`,
1141
+ `${SCREENSHARE_JOURNEY} screen share start calling RtcEngine.startScreenshare`,
977
1142
  {
978
- action,
1143
+ action: 'start',
979
1144
  stage,
980
1145
  outcome: 'started',
981
1146
  screenshareAttemptId,
@@ -987,6 +1152,7 @@ export const ScreenshareConfigure = (props: {
987
1152
  },
988
1153
  );
989
1154
  // @ts-ignore
1155
+ // The RTC start API only creates, joins, and publishes screen tracks.
990
1156
  await rtc.RtcEngineUnsafe.startScreenshare(
991
1157
  screenShareToken,
992
1158
  channel,
@@ -998,7 +1164,6 @@ export const ScreenshareConfigure = (props: {
998
1164
  {encoderConfig: '1080p_2', optimizationMode: 'detail'},
999
1165
  'auto',
1000
1166
  {
1001
- action,
1002
1167
  screenshareAttemptId,
1003
1168
  screenshareSessionId,
1004
1169
  recordingActive: props.isRecordingActive,
@@ -1011,9 +1176,9 @@ export const ScreenshareConfigure = (props: {
1011
1176
  logger.log(
1012
1177
  LogSource.Internals,
1013
1178
  'SCREENSHARE',
1014
- `${SCREENSHARE_JOURNEY} screen share ${action} RtcEngine.startScreenshare completed successfully`,
1179
+ `${SCREENSHARE_JOURNEY} screen share start RtcEngine.startScreenshare completed successfully`,
1015
1180
  {
1016
- action,
1181
+ action: 'start',
1017
1182
  stage,
1018
1183
  outcome: 'success',
1019
1184
  screenshareAttemptId,
@@ -1025,99 +1190,299 @@ export const ScreenshareConfigure = (props: {
1025
1190
  },
1026
1191
  );
1027
1192
 
1028
- if (isActive) {
1029
- activeScreenshareSessionIdRef.current = screenshareSessionId;
1193
+ activeScreenshareSessionIdRef.current = screenshareSessionId;
1194
+ pendingScreenshareSessionIdRef.current = null;
1195
+ updateOperationState('active');
1196
+ setScreenshareActive(true);
1197
+ stage = 'local_active_state';
1198
+ logger.log(
1199
+ LogSource.Internals,
1200
+ 'SCREENSHARE',
1201
+ `${SCREENSHARE_JOURNEY} screen share start local active state set to active`,
1202
+ {
1203
+ action: 'start',
1204
+ stage,
1205
+ outcome: 'success',
1206
+ screenshareAttemptId,
1207
+ screenshareSessionId,
1208
+ recordingActive: props.isRecordingActive,
1209
+ screenShareUid,
1210
+ stopOrigin,
1211
+ stopActorUid,
1212
+ },
1213
+ );
1214
+ stage = 'screenshare_context';
1215
+ setScreenShareData(prevState => {
1216
+ return {
1217
+ ...prevState,
1218
+ [screenShareUid]: {
1219
+ name: defaultContentRef.current.defaultContent[screenShareUid]
1220
+ ?.name,
1221
+ isActive: true,
1222
+ ts: timeNow(),
1223
+ },
1224
+ };
1225
+ });
1226
+ logger.log(
1227
+ LogSource.Internals,
1228
+ 'SCREENSHARE',
1229
+ `${SCREENSHARE_JOURNEY} screen share start local context set to active`,
1230
+ {
1231
+ action: 'start',
1232
+ stage,
1233
+ outcome: 'success',
1234
+ screenshareAttemptId,
1235
+ screenshareSessionId,
1236
+ recordingActive: props.isRecordingActive,
1237
+ screenShareUid,
1238
+ stopOrigin,
1239
+ stopActorUid,
1240
+ },
1241
+ );
1242
+ stage = 'rtm_event';
1243
+ const rtmEventSent = await events.send(
1244
+ EventNames.SCREENSHARE_ATTRIBUTE,
1245
+ JSON.stringify({
1246
+ action: EventActions.SCREENSHARE_STARTED,
1247
+ value: timeNow(),
1248
+ screenUidOfUser: screenShareUid,
1249
+ }),
1250
+ PersistanceLevel.Sender,
1251
+ );
1252
+ logger.log(
1253
+ LogSource.Internals,
1254
+ 'SCREENSHARE',
1255
+ `${SCREENSHARE_JOURNEY} screen share start RTM event ${
1256
+ rtmEventSent
1257
+ ? 'sent to remote users'
1258
+ : 'failed to send to remote users'
1259
+ }`,
1260
+ {
1261
+ action: 'start',
1262
+ stage,
1263
+ outcome: rtmEventSent ? 'success' : 'failure',
1264
+ screenshareAttemptId,
1265
+ screenshareSessionId,
1266
+ recordingActive: props.isRecordingActive,
1267
+ screenShareUid,
1268
+ stopOrigin,
1269
+ stopActorUid,
1270
+ },
1271
+ );
1272
+ logger.log(
1273
+ LogSource.Internals,
1274
+ 'SCREENSHARE',
1275
+ `${SCREENSHARE_JOURNEY} screen share start workflow ${
1276
+ recordingLayoutFailed
1277
+ ? 'completed with recording layout failure'
1278
+ : 'completed successfully'
1279
+ }`,
1280
+ {
1281
+ action: 'start',
1282
+ stage: 'complete',
1283
+ outcome: recordingLayoutFailed ? 'partial_success' : 'success',
1284
+ screenshareAttemptId,
1285
+ screenshareSessionId,
1286
+ recordingActive: props.isRecordingActive,
1287
+ screenShareUid,
1288
+ stopOrigin,
1289
+ stopActorUid,
1290
+ elapsedMs: Date.now() - startedAt,
1291
+ },
1292
+ );
1293
+ return true;
1294
+ } catch (e) {
1295
+ const userCancelOrPermissionDenied = isUserCancelOrPermissionDenied(e);
1296
+ logger.error(
1297
+ LogSource.Internals,
1298
+ 'SCREENSHARE',
1299
+ `${SCREENSHARE_JOURNEY} screen share start workflow failed at ${stage}: ${
1300
+ userCancelOrPermissionDenied
1301
+ ? 'user cancelled picker or permission was denied'
1302
+ : 'unexpected failure'
1303
+ }`,
1304
+ {
1305
+ action: 'start',
1306
+ stage,
1307
+ outcome: userCancelOrPermissionDenied
1308
+ ? 'user_cancel_or_permission_denied'
1309
+ : 'failure',
1310
+ screenshareAttemptId,
1311
+ screenshareSessionId,
1312
+ recordingActive: props.isRecordingActive,
1313
+ screenShareUid,
1314
+ stopOrigin,
1315
+ stopActorUid,
1316
+ elapsedMs: Date.now() - startedAt,
1317
+ ...getScreenshareError(e),
1318
+ },
1319
+ );
1320
+ Toast.show({
1321
+ leadingIconName: 'alert',
1322
+ type: 'error',
1323
+ text1: toastHeading,
1324
+ text2: toastSubHeading,
1325
+ visibilityTime: 1000 * 10,
1326
+ primaryBtn: null,
1327
+ secondaryBtn: null,
1328
+ });
1329
+ if (!rtcOperationSucceeded) {
1030
1330
  pendingScreenshareSessionIdRef.current = null;
1031
- updateOperationState('active');
1032
- setScreenshareActive(true);
1033
- stage = 'local_active_state';
1331
+ activeScreenshareSessionIdRef.current = null;
1332
+ setScreenshareActive(false);
1333
+ updateOperationState('inactive');
1034
1334
  logger.log(
1035
1335
  LogSource.Internals,
1036
1336
  'SCREENSHARE',
1037
- `${SCREENSHARE_JOURNEY} screen share start local active state set to active`,
1337
+ getScreenshareSessionBoundaryMessage('end', screenshareSessionId),
1038
1338
  {
1039
- action,
1040
- stage,
1041
- outcome: 'success',
1339
+ action: 'start',
1340
+ stage: 'session_boundary',
1341
+ outcome: 'ended_with_start_failure',
1042
1342
  screenshareAttemptId,
1043
1343
  screenshareSessionId,
1044
1344
  recordingActive: props.isRecordingActive,
1045
1345
  screenShareUid,
1046
1346
  stopOrigin,
1047
1347
  stopActorUid,
1348
+ operationState: operationStateRef.current,
1048
1349
  },
1049
1350
  );
1050
- // 1. Set local state
1051
- stage = 'screenshare_context';
1052
- setScreenShareData(prevState => {
1053
- return {
1054
- ...prevState,
1055
- [screenShareUid]: {
1056
- name: defaultContentRef.current.defaultContent[screenShareUid]
1057
- ?.name,
1058
- isActive: true,
1059
- ts: timeNow(),
1060
- },
1061
- };
1062
- });
1063
- logger.log(
1064
- LogSource.Internals,
1065
- 'SCREENSHARE',
1066
- `${SCREENSHARE_JOURNEY} screen share start local context set to active`,
1067
- {
1068
- action,
1069
- stage,
1070
- outcome: 'success',
1351
+ }
1352
+ return rtcOperationSucceeded;
1353
+ }
1354
+ };
1355
+
1356
+ const executeStopScreenshareWorkflow = async (
1357
+ screenshareAttemptId: string,
1358
+ screenshareSessionId: string,
1359
+ stopOrigin: ScreenshareStopOrigin,
1360
+ stopActorUid?: UidType,
1361
+ ) => {
1362
+ const startedAt = Date.now();
1363
+ let stage = 'recording_layout';
1364
+ let recordingLayoutFailed = false;
1365
+ let rtcOperationSucceeded = false;
1366
+ logger.log(
1367
+ LogSource.Internals,
1368
+ 'SCREENSHARE',
1369
+ `${SCREENSHARE_JOURNEY} screen share stop workflow started`,
1370
+ {
1371
+ action: 'stop',
1372
+ stage: 'journey',
1373
+ outcome: 'started',
1374
+ screenshareAttemptId,
1375
+ screenshareSessionId,
1376
+ recordingActive: props.isRecordingActive,
1377
+ screenShareUid,
1378
+ stopOrigin,
1379
+ stopActorUid,
1380
+ channel,
1381
+ },
1382
+ );
1383
+ try {
1384
+ if (props.isRecordingActive) {
1385
+ try {
1386
+ await executeStopRecordingLayoutQuery(
1071
1387
  screenshareAttemptId,
1072
1388
  screenshareSessionId,
1073
- recordingActive: props.isRecordingActive,
1074
- screenShareUid,
1075
1389
  stopOrigin,
1076
1390
  stopActorUid,
1077
- },
1078
- );
1079
- // 2. Inform everyone in the channel screenshare is actice
1080
- stage = 'rtm_event';
1081
- const rtmEventSent = await events.send(
1082
- EventNames.SCREENSHARE_ATTRIBUTE,
1083
- JSON.stringify({
1084
- action: EventActions.SCREENSHARE_STARTED,
1085
- value: timeNow(),
1086
- screenUidOfUser: screenShareUid,
1087
- }),
1088
- PersistanceLevel.Sender,
1089
- );
1391
+ );
1392
+ } catch (recordingError) {
1393
+ recordingLayoutFailed = true;
1394
+ logger.error(
1395
+ LogSource.Internals,
1396
+ 'SCREENSHARE',
1397
+ `${SCREENSHARE_JOURNEY} screen share stop recording layout query failed; continuing screen share stop`,
1398
+ recordingError,
1399
+ {
1400
+ action: 'stop',
1401
+ stage,
1402
+ outcome: 'partial_failure',
1403
+ screenshareAttemptId,
1404
+ screenshareSessionId,
1405
+ recordingActive: props.isRecordingActive,
1406
+ screenShareUid,
1407
+ stopOrigin,
1408
+ stopActorUid,
1409
+ ...getScreenshareError(recordingError),
1410
+ },
1411
+ );
1412
+ }
1413
+ } else {
1090
1414
  logger.log(
1091
1415
  LogSource.Internals,
1092
1416
  'SCREENSHARE',
1093
- `${SCREENSHARE_JOURNEY} screen share start RTM event ${
1094
- rtmEventSent
1095
- ? 'sent to remote users'
1096
- : 'failed to send to remote users'
1097
- }`,
1417
+ `${SCREENSHARE_JOURNEY} screen share stop recording layout query skipped because recording is inactive`,
1098
1418
  {
1099
- action,
1419
+ action: 'stop',
1100
1420
  stage,
1101
- outcome: rtmEventSent ? 'success' : 'failure',
1421
+ outcome: 'skipped',
1102
1422
  screenshareAttemptId,
1103
1423
  screenshareSessionId,
1104
- recordingActive: props.isRecordingActive,
1424
+ recordingActive: false,
1105
1425
  screenShareUid,
1106
1426
  stopOrigin,
1107
1427
  stopActorUid,
1108
1428
  },
1109
1429
  );
1110
1430
  }
1431
+ stage = 'rtc_operation';
1111
1432
  logger.log(
1112
1433
  LogSource.Internals,
1113
1434
  'SCREENSHARE',
1114
- `${SCREENSHARE_JOURNEY} screen share ${action} workflow ${
1435
+ `${SCREENSHARE_JOURNEY} screen share stop calling RtcEngine.stopScreenshare`,
1436
+ {
1437
+ action: 'stop',
1438
+ stage,
1439
+ outcome: 'started',
1440
+ screenshareAttemptId,
1441
+ screenshareSessionId,
1442
+ recordingActive: props.isRecordingActive,
1443
+ screenShareUid,
1444
+ stopOrigin,
1445
+ stopActorUid,
1446
+ },
1447
+ );
1448
+ // @ts-ignore
1449
+ // The RTC stop API performs idempotent track/client cleanup and then
1450
+ // invokes ScreenshareStoppedCallback for application-state cleanup.
1451
+ await rtc.RtcEngineUnsafe.stopScreenshare({
1452
+ screenshareAttemptId,
1453
+ screenshareSessionId,
1454
+ recordingActive: props.isRecordingActive,
1455
+ screenShareUid,
1456
+ stopOrigin,
1457
+ stopActorUid,
1458
+ });
1459
+ rtcOperationSucceeded = true;
1460
+ logger.log(
1461
+ LogSource.Internals,
1462
+ 'SCREENSHARE',
1463
+ `${SCREENSHARE_JOURNEY} screen share stop RtcEngine.stopScreenshare completed successfully`,
1464
+ {
1465
+ action: 'stop',
1466
+ stage,
1467
+ outcome: 'success',
1468
+ screenshareAttemptId,
1469
+ screenshareSessionId,
1470
+ recordingActive: props.isRecordingActive,
1471
+ screenShareUid,
1472
+ stopOrigin,
1473
+ stopActorUid,
1474
+ },
1475
+ );
1476
+ logger.log(
1477
+ LogSource.Internals,
1478
+ 'SCREENSHARE',
1479
+ `${SCREENSHARE_JOURNEY} screen share stop workflow ${
1115
1480
  recordingLayoutFailed
1116
1481
  ? 'completed with recording layout failure'
1117
1482
  : 'completed successfully'
1118
1483
  }`,
1119
1484
  {
1120
- action,
1485
+ action: 'stop',
1121
1486
  stage: 'complete',
1122
1487
  outcome: recordingLayoutFailed ? 'partial_success' : 'success',
1123
1488
  screenshareAttemptId,
@@ -1135,13 +1500,13 @@ export const ScreenshareConfigure = (props: {
1135
1500
  logger.error(
1136
1501
  LogSource.Internals,
1137
1502
  'SCREENSHARE',
1138
- `${SCREENSHARE_JOURNEY} screen share ${action} workflow failed at ${stage}: ${
1503
+ `${SCREENSHARE_JOURNEY} screen share stop workflow failed at ${stage}: ${
1139
1504
  userCancelOrPermissionDenied
1140
1505
  ? 'user cancelled picker or permission was denied'
1141
1506
  : 'unexpected failure'
1142
1507
  }`,
1143
1508
  {
1144
- action,
1509
+ action: 'stop',
1145
1510
  stage,
1146
1511
  outcome: userCancelOrPermissionDenied
1147
1512
  ? 'user_cancel_or_permission_denied'
@@ -1165,29 +1530,6 @@ export const ScreenshareConfigure = (props: {
1165
1530
  primaryBtn: null,
1166
1531
  secondaryBtn: null,
1167
1532
  });
1168
- if (isActive && !rtcOperationSucceeded) {
1169
- pendingScreenshareSessionIdRef.current = null;
1170
- activeScreenshareSessionIdRef.current = null;
1171
- setScreenshareActive(false);
1172
- updateOperationState('inactive');
1173
- logger.log(
1174
- LogSource.Internals,
1175
- 'SCREENSHARE',
1176
- getScreenshareSessionBoundaryMessage('end', screenshareSessionId),
1177
- {
1178
- action: 'start',
1179
- stage: 'session_boundary',
1180
- outcome: 'ended_with_start_failure',
1181
- screenshareAttemptId,
1182
- screenshareSessionId,
1183
- recordingActive: props.isRecordingActive,
1184
- screenShareUid,
1185
- stopOrigin,
1186
- stopActorUid,
1187
- operationState: operationStateRef.current,
1188
- },
1189
- );
1190
- }
1191
1533
  return rtcOperationSucceeded;
1192
1534
  }
1193
1535
  };