fcr-core 3.10.6 → 3.11.0-rc.10

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 (56) hide show
  1. package/lib/imports.d.ts +7 -1
  2. package/lib/imports.js +27 -1
  3. package/lib/index.d.ts +2 -0
  4. package/lib/index.js +14 -2
  5. package/lib/media-control/type.d.ts +8 -1
  6. package/lib/peer-session/index.js +4 -0
  7. package/lib/plugins/chat/chatroom.js +9 -5
  8. package/lib/plugins/chat/connector.d.ts +6 -2
  9. package/lib/plugins/chat/connector.js +11 -6
  10. package/lib/plugins/chat/type.d.ts +8 -0
  11. package/lib/plugins/chat/type.js +5 -0
  12. package/lib/remote-control/index.js +4 -0
  13. package/lib/room-control/ability-control/type.d.ts +1 -0
  14. package/lib/room-control/ability-control/type.js +1 -0
  15. package/lib/room-control/helpers/constants.d.ts +8 -0
  16. package/lib/room-control/helpers/constants.js +8 -0
  17. package/lib/room-control/index.js +145 -1
  18. package/lib/room-control/infinity-room-control/index.js +3 -4
  19. package/lib/room-control/mainroom-control/index.js +3 -4
  20. package/lib/room-control/privilege-control/index.js +22 -3
  21. package/lib/room-control/privilege-control/type.d.ts +22 -0
  22. package/lib/room-control/privilege-control/type.js +13 -1
  23. package/lib/room-control/stt-control/index.js +0 -1
  24. package/lib/room-control/type.d.ts +44 -0
  25. package/lib/room-control/user-control/index.js +68 -12
  26. package/lib/room-control/user-control/type.d.ts +24 -0
  27. package/lib/room-control/whiteboard-control-v2/base/index.d.ts +7 -0
  28. package/lib/room-control/whiteboard-control-v2/base/index.js +84 -47
  29. package/lib/room-control/whiteboard-control-v2/base/main-window.d.ts +6 -0
  30. package/lib/room-control/whiteboard-control-v2/base/main-window.js +13 -3
  31. package/lib/room-control/whiteboard-control-v2/type.d.ts +5 -0
  32. package/lib/service/api.d.ts +15 -1
  33. package/lib/service/api.js +48 -0
  34. package/lib/utilities/parameters.js +21 -4
  35. package/lib-es/imports.js +6 -0
  36. package/lib-es/index.js +4 -0
  37. package/lib-es/media-control/desktop.js +1 -0
  38. package/lib-es/peer-session/index.js +4 -0
  39. package/lib-es/plugins/chat/chatroom.js +9 -5
  40. package/lib-es/plugins/chat/connector.js +11 -6
  41. package/lib-es/plugins/chat/type.js +1 -0
  42. package/lib-es/remote-control/index.js +4 -0
  43. package/lib-es/room-control/ability-control/type.js +1 -0
  44. package/lib-es/room-control/helpers/constants.js +8 -0
  45. package/lib-es/room-control/index.js +146 -2
  46. package/lib-es/room-control/infinity-room-control/index.js +3 -4
  47. package/lib-es/room-control/mainroom-control/index.js +3 -4
  48. package/lib-es/room-control/privilege-control/index.js +23 -4
  49. package/lib-es/room-control/privilege-control/type.js +12 -0
  50. package/lib-es/room-control/stt-control/index.js +0 -1
  51. package/lib-es/room-control/user-control/index.js +67 -12
  52. package/lib-es/room-control/whiteboard-control-v2/base/index.js +84 -47
  53. package/lib-es/room-control/whiteboard-control-v2/base/main-window.js +13 -3
  54. package/lib-es/service/api.js +48 -0
  55. package/lib-es/utilities/parameters.js +21 -4
  56. package/package.json +11 -7
package/lib/imports.d.ts CHANGED
@@ -27,7 +27,13 @@ export { sleep, jsonstring, randomString } from 'agora-foundation/lib/utilities/
27
27
  export { z, ZodError, anySchema, booleanSchema, numberSchema, stringSchema, fcrRenderViewSchema, unknownSchema, objectSchema, createRecordSchemaWithKey, createUnionSchema, createArraySchema, } from 'agora-foundation/lib/utilities/schema';
28
28
  export { validateParams } from 'agora-foundation/lib/decorator/validate';
29
29
  export { Mutex } from 'agora-foundation/lib/worker/mutex';
30
- export type { AgoraRtcNetworkStats, AgoraRtcDeviceInfo, AgoraRtcDisplayInfo, AgoraRtcWindowInfo, AgoraRtePerformanceInfo, AgoraRteScenePropertiesDeletedEvent, AgoraRteStreamEncryptionConfig, AgoraRteStreamJoinConfig, AgoraRteDualVideoStreamConfig, AgoraRteMessage, AgoraRteScenePropertiesUpdatedEvent, AgoraRteRenderView, AgoraRteScene, } from 'agora-rte-sdk';
30
+ export type { AgoraRtcNetworkStats, AgoraRtcDeviceInfo, AgoraRtcDisplayInfo, AgoraRtcWindowInfo, AgoraRtePerformanceInfo, AgoraRteScenePropertiesDeletedEvent, AgoraRteStreamEncryptionConfig, AgoraRteStreamJoinConfig, AgoraRteDualVideoStreamConfig, AgoraRteMessage, AgoraRteScenePropertiesUpdatedEvent, AgoraRteRenderView, AgoraRteScene, AgoraRteClientRecordingConfig, AgoraRteClientRecordingObserver, AgoraRteRecorderInfo, } from 'agora-rte-sdk';
31
+ export { AgoraRteRecorderState, AgoraRteRecorderReasonCode } from 'agora-rte-sdk';
32
+ export type { AgoraRteClientRecordingConfig as FcrClientRecordingConfig } from 'agora-rte-sdk';
33
+ export type { AgoraRteClientRecordingObserver as FcrClientRecordingObserver } from 'agora-rte-sdk';
34
+ export type { AgoraRteRecorderInfo as FcrRecorderInfo } from 'agora-rte-sdk';
35
+ export { AgoraRteRecorderState as FcrRecorderState } from 'agora-rte-sdk';
36
+ export { AgoraRteRecorderReasonCode as FcrRecorderReasonCode } from 'agora-rte-sdk';
31
37
  export type { AgoraRtmClient } from 'agora-rte-sdk/lib/core/rtm/client';
32
38
  export type { AgoraRteEngineObserver } from 'agora-rte-sdk/lib/core/engine/type';
33
39
  export type { AgoraRteNetworkQualityEvent } from 'agora-rte-sdk/lib/core/scene/type';
package/lib/imports.js CHANGED
@@ -150,6 +150,18 @@ Object.defineProperty(exports, "AgoraRteMediaStreamType", {
150
150
  return _type.AgoraRteMediaStreamType;
151
151
  }
152
152
  });
153
+ Object.defineProperty(exports, "AgoraRteRecorderReasonCode", {
154
+ enumerable: true,
155
+ get: function () {
156
+ return _agoraRteSdk.AgoraRteRecorderReasonCode;
157
+ }
158
+ });
159
+ Object.defineProperty(exports, "AgoraRteRecorderState", {
160
+ enumerable: true,
161
+ get: function () {
162
+ return _agoraRteSdk.AgoraRteRecorderState;
163
+ }
164
+ });
153
165
  Object.defineProperty(exports, "AgoraRteRegion", {
154
166
  enumerable: true,
155
167
  get: function () {
@@ -294,6 +306,18 @@ Object.defineProperty(exports, "FcrRTMProvider_2_2", {
294
306
  return _forgeRtm.RTMProvider_2_2;
295
307
  }
296
308
  });
309
+ Object.defineProperty(exports, "FcrRecorderReasonCode", {
310
+ enumerable: true,
311
+ get: function () {
312
+ return _agoraRteSdk.AgoraRteRecorderReasonCode;
313
+ }
314
+ });
315
+ Object.defineProperty(exports, "FcrRecorderState", {
316
+ enumerable: true,
317
+ get: function () {
318
+ return _agoraRteSdk.AgoraRteRecorderState;
319
+ }
320
+ });
297
321
  Object.defineProperty(exports, "FcrRoom", {
298
322
  enumerable: true,
299
323
  get: function () {
@@ -587,9 +611,9 @@ var _misc = require("agora-foundation/lib/utilities/misc");
587
611
  var _schema = require("agora-foundation/lib/utilities/schema");
588
612
  var _validate = require("agora-foundation/lib/decorator/validate");
589
613
  var _mutex = require("agora-foundation/lib/worker/mutex");
614
+ var _agoraRteSdk = require("agora-rte-sdk");
590
615
  var _error = require("agora-rte-sdk/lib/core/utilities/error");
591
616
  var _agoraError = require("agora-foundation/lib/utilities/error/agora-error");
592
- var _agoraRteSdk = require("agora-rte-sdk");
593
617
  var _type = require("agora-rte-sdk/lib/core/scene/type");
594
618
  var _type2 = require("agora-rte-sdk/lib/core/rtc/type");
595
619
  var _type3 = require("agora-rte-sdk/lib/type");
@@ -619,4 +643,6 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
619
643
 
620
644
  // Agora RTE exports
621
645
 
646
+ // Fcr-prefixed re-exports for edu-core layer
647
+
622
648
  const localStorage = exports.localStorage = window.localStorage;
package/lib/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export type { FcrDesktopMediaControl, FcrDesktopMediaObserver, FcrMobileMediaControl, FcrMobileMediaObserver, FcrMonitorControl, FcrMonitorObserver, FcrBaseRoomControl, FcrRoomObserver, FcrUserControl, FcrUserObserver, FcrStreamControl, FcrStreamObserver, FcrPrivilegeControl, FcrPermission, FcrBoardMainWindow, FcrBoardMainWindowObserver, FcrColor, FcrBatchResult, FcrStreamUpdateResultDetail, } from './type';
2
+ export type { FcrClientRecordingConfig } from './imports';
3
+ export { FcrRecorderState, FcrRecorderReasonCode } from './imports';
2
4
  export { FcrStreamLatencyLevel, FcrPermissionAction, FcrPrivilegeUserRole, FcrUserRole, FcrStreamState, FcrRegion, FcrStreamType, FcrUserUpdatedReason, FcrRoomConnectorType, FcrVideoSourceType, FcrAudioSourceType, FcrMediaSourceState, FcrVideoRenderMode, FcrVideoStreamType, FcrCapability, FcrAiDenoiseLevel, FcrStreamPrivilegeOperation, FcrStreamPrivilegeVideoSourceType, FcrStreamPrivilegeAudioSourceType, FcrConnectionState, registerPlugin, FcrBoardToolType, FcrBoardShape, FcrVideoEncoderConfig, FcrDualVideoStreamConfig, } from './type';
3
5
  export { FcrCoreEngineImpl as FcrCoreEngine } from './engine';
4
6
  export { FcrCoreEngineConfig } from './struct';
package/lib/index.js CHANGED
@@ -87,6 +87,18 @@ Object.defineProperty(exports, "FcrPrivilegeUserRole", {
87
87
  return _type.FcrPrivilegeUserRole;
88
88
  }
89
89
  });
90
+ Object.defineProperty(exports, "FcrRecorderReasonCode", {
91
+ enumerable: true,
92
+ get: function () {
93
+ return _imports.FcrRecorderReasonCode;
94
+ }
95
+ });
96
+ Object.defineProperty(exports, "FcrRecorderState", {
97
+ enumerable: true,
98
+ get: function () {
99
+ return _imports.FcrRecorderState;
100
+ }
101
+ });
90
102
  Object.defineProperty(exports, "FcrRegion", {
91
103
  enumerable: true,
92
104
  get: function () {
@@ -183,8 +195,8 @@ Object.defineProperty(exports, "registerPlugin", {
183
195
  return _type.registerPlugin;
184
196
  }
185
197
  });
198
+ var _imports = require("./imports");
186
199
  var _type = require("./type");
187
200
  var _engine = require("./engine");
188
201
  var _struct = require("./struct");
189
- var _schema = require("./schema");
190
- var _imports = require("./imports");
202
+ var _schema = require("./schema");
@@ -163,6 +163,12 @@ export interface FcrAudioEffectEnhancer {
163
163
  * Disables AGC for the audio track.
164
164
  */
165
165
  disableAGC(): void;
166
+ /**
167
+ * Sets the Windows system-level audio preprocessing (hardware 3A) mode.
168
+ * Only effective on Electron Windows.
169
+ * @param mode `0` off (default), `1` on, `2` only enabled under speaker routing.
170
+ */
171
+ setWindowsSystemAudioPreprocessingMode(mode: number): void;
166
172
  }
167
173
  export interface FcrVideoEffectEnhancer {
168
174
  /**
@@ -404,8 +410,9 @@ export interface FcrMicrophoneTrack {
404
410
  /**
405
411
  * Starts the microphone test for the specified device.
406
412
  * @param interval The interval for the microphone test.
413
+ * @param enablePlayback Whether to enable playback during the test.
407
414
  */
408
- startTest(interval: number): number;
415
+ startTest(interval: number, enablePlayback?: boolean): number;
409
416
  /**
410
417
  * Stops the microphone test for the specified device.
411
418
  */
@@ -326,6 +326,10 @@ class FcrPeerSessionControlImpl {
326
326
  data,
327
327
  cmd
328
328
  } = payload;
329
+ if (cmd !== '1001' && cmd !== '1002' || !data || typeof data.sessionKey !== 'string' || typeof data.sessionUuid !== 'string') {
330
+ this.logger.info(`[PeerSession] ignore non peer-session message: cmd=${String(cmd)}, senderId=${senderId}, payload=${JSON.stringify(payload)}`);
331
+ return;
332
+ }
329
333
  const logContext = `cmd=${cmd}, sessionKey=${data.sessionKey}, sessionId=${data.sessionUuid}, ` + `action=${String(data.action)}, senderId=${senderId}, keepAlive=${!!data.keepAlive}`;
330
334
  this.logger.info(`[PeerSession] received peer message: ${logContext}, payload=${JSON.stringify(data.payload ?? {})}`);
331
335
  if (cmd === '1001') {
@@ -77,17 +77,19 @@ class FcrChatRoomControlImpl {
77
77
  this._observable.notifyObservers('onTextMessageReceived', this._scene.sceneId, message);
78
78
  }
79
79
  };
80
+ _chatConnectionInternalObserver = {
81
+ onReconnected: () => {
82
+ this.logger.info('chat connector reconnected, try to rejoin chat room');
83
+ this._tryJoinChatRoom(true);
84
+ }
85
+ };
80
86
  _chatConnectionObserver = {
81
87
  onConnectionStateUpdated: async connectorState => {
82
- const chatRoomConnectionState = this.getConnectionState();
83
88
  if (connectorState === _type.FcrChatConnectionState.DISCONNECTED) {
84
89
  this._setConnectionState(_type2.FcrChatRoomConnectionState.Disconnected);
85
- } else if (connectorState === _type.FcrChatConnectionState.CONNECTED && chatRoomConnectionState !== _type2.FcrChatRoomConnectionState.Connected) {
90
+ } else if (connectorState === _type.FcrChatConnectionState.CONNECTED) {
86
91
  this.logger.info('chat connector connected, try to join chat room');
87
92
  this._tryJoinChatRoom();
88
- } else if (connectorState === _type.FcrChatConnectionState.CONNECTED) {
89
- this.logger.info('chat connector reconnected, try to rejoin chat room');
90
- this._tryJoinChatRoom(true);
91
93
  }
92
94
  }
93
95
  };
@@ -202,6 +204,7 @@ class FcrChatRoomControlImpl {
202
204
  }
203
205
  leave() {
204
206
  this._chatConnection.removeObserver(this._chatConnectionObserver);
207
+ this._chatConnection.removeInternalObserver(this._chatConnectionInternalObserver);
205
208
  if (this._connectionState === _type2.FcrChatRoomConnectionState.Disconnected && !this._abortJoinChatRoom) {
206
209
  return;
207
210
  }
@@ -234,6 +237,7 @@ class FcrChatRoomControlImpl {
234
237
  }
235
238
  async join(force = false) {
236
239
  this._chatConnection.addObserver(this._chatConnectionObserver);
240
+ this._chatConnection.addInternalObserver(this._chatConnectionInternalObserver);
237
241
  const connectorState = this._chatConnection.getConnectionState();
238
242
  if (connectorState !== _type.FcrChatConnectionState.CONNECTED) {
239
243
  return _type3.FcrReturnCode.SUCCESS;
@@ -1,13 +1,15 @@
1
1
  import { EasemobChat } from '../../imports';
2
2
  import { FcrCoreServiceApi } from '../../service/api';
3
- import { FcrChatConnectionObserver, FcrChatConnectionState, FcrChatConnector } from '../../chat-connector/type';
4
- export declare class FcrChatConnectorImpl implements FcrChatConnector {
3
+ import { FcrChatConnectionObserver, FcrChatConnectionState } from '../../chat-connector/type';
4
+ import { FcrChatConnectionInternalObserver, FcrChatConnectorInternal } from './type';
5
+ export declare class FcrChatConnectorImpl implements FcrChatConnectorInternal {
5
6
  private _userId;
6
7
  private _api;
7
8
  private _chatIpList?;
8
9
  private _restIpList?;
9
10
  protected logger: import("agora-foundation/lib/logger/type").Logger;
10
11
  private _observable;
12
+ private _internalObservable;
11
13
  private _initConfig?;
12
14
  private _loginPromise;
13
15
  private _logoutPromise;
@@ -24,6 +26,8 @@ export declare class FcrChatConnectorImpl implements FcrChatConnector {
24
26
  getConnectionInstance(): EasemobChat.Connection;
25
27
  addObserver(observer: FcrChatConnectionObserver): void;
26
28
  removeObserver(observer: FcrChatConnectionObserver): void;
29
+ addInternalObserver(observer: FcrChatConnectionInternalObserver): void;
30
+ removeInternalObserver(observer: FcrChatConnectionInternalObserver): void;
27
31
  private _performLogin;
28
32
  private _addEventListeners;
29
33
  private _performLogout;
@@ -43,6 +43,7 @@ class FcrChatConnectorImpl {
43
43
  prefix: 'FcrChatConnectorImpl'
44
44
  }));
45
45
  _observable = new _imports.AgoraObservable();
46
+ _internalObservable = new _imports.AgoraObservable();
46
47
  _loginPromise = null;
47
48
  _logoutPromise = null;
48
49
  _isLogoutRequested = false;
@@ -98,6 +99,12 @@ class FcrChatConnectorImpl {
98
99
  removeObserver(observer) {
99
100
  this._observable.removeObserver(observer);
100
101
  }
102
+ addInternalObserver(observer) {
103
+ this._internalObservable.addObserver(observer);
104
+ }
105
+ removeInternalObserver(observer) {
106
+ this._internalObservable.removeObserver(observer);
107
+ }
101
108
  async _performLogin() {
102
109
  const privateConfig = this._getPrivateConfig();
103
110
  if (privateConfig) {
@@ -227,7 +234,6 @@ class FcrChatConnectorImpl {
227
234
  if (this._restIpList?.length) {
228
235
  privateConfig.apiUrl = this._restIpList[0];
229
236
  }
230
- privateConfig.url = 'wss://apaas-private-im.agoralab.co:13003/websocket';
231
237
  return privateConfig;
232
238
  }
233
239
  }
@@ -235,12 +241,11 @@ class FcrChatConnectorImpl {
235
241
  if (this._isLogoutRequested) {
236
242
  return;
237
243
  }
238
- if (this._connectionState !== _type2.FcrChatConnectionState.CONNECTED) {
239
- this._setConnectionState(_type2.FcrChatConnectionState.CONNECTED);
240
- } else {
241
- // notify again in case of reconnection
242
- this._observable.notifyObservers('onConnectionStateUpdated', _type2.FcrChatConnectionState.CONNECTED);
244
+ if (this._connectionState === _type2.FcrChatConnectionState.CONNECTED) {
245
+ this._internalObservable.notifyObservers('onReconnected');
246
+ return;
243
247
  }
248
+ this._setConnectionState(_type2.FcrChatConnectionState.CONNECTED);
244
249
  }
245
250
  _handleDisconnected() {
246
251
  if (this._connectionState !== _type2.FcrChatConnectionState.DISCONNECTED) {
@@ -0,0 +1,8 @@
1
+ import { FcrChatConnector } from '../../chat-connector/type';
2
+ export interface FcrChatConnectorInternal extends FcrChatConnector {
3
+ addInternalObserver(observer: FcrChatConnectionInternalObserver): void;
4
+ removeInternalObserver(observer: FcrChatConnectionInternalObserver): void;
5
+ }
6
+ export interface FcrChatConnectionInternalObserver {
7
+ onReconnected(): void;
8
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
@@ -78,6 +78,10 @@ class FcrRemoteControlImpl {
78
78
  data,
79
79
  cmd
80
80
  } = payload;
81
+ if (cmd !== '1001' && cmd !== '1002' || !data || typeof data.sessionKey !== 'string' || typeof data.sessionUuid !== 'string') {
82
+ this.logger.info(`[RemoteControl] ignore non remote-control message: cmd=${String(cmd)}, senderId=${senderId}, payload=${JSON.stringify(payload)}`);
83
+ return;
84
+ }
81
85
  const logContext = `cmd=${cmd}, sessionKey=${data.sessionKey}, sessionId=${data.sessionUuid}, ` + `action=${String(data.action)}, senderId=${senderId}, keepAlive=${!!data.keepAlive}`;
82
86
  if (data.sessionKey !== this._remoteControlSessionKey) {
83
87
  this.logger.info(`[RemoteControl] ignore peer message because sessionKey does not match remote-control: ${logContext}`);
@@ -8,6 +8,7 @@ export declare enum FcrAbility {
8
8
  Caption = "caption",
9
9
  Transcribe = "transcribe",
10
10
  Recording = "recording",
11
+ ClientRecording = "clientRecording",
11
12
  Board = "board",
12
13
  SettingVirtualBackground = "setting:virtualBackground",
13
14
  SettingBeautyMode = "setting:beautyMode",
@@ -13,6 +13,7 @@ let FcrAbility = exports.FcrAbility = /*#__PURE__*/function (FcrAbility) {
13
13
  FcrAbility["Caption"] = "caption";
14
14
  FcrAbility["Transcribe"] = "transcribe";
15
15
  FcrAbility["Recording"] = "recording";
16
+ FcrAbility["ClientRecording"] = "clientRecording";
16
17
  FcrAbility["Board"] = "board";
17
18
  FcrAbility["SettingVirtualBackground"] = "setting:virtualBackground";
18
19
  FcrAbility["SettingBeautyMode"] = "setting:beautyMode";
@@ -44,6 +44,14 @@ export declare const ROOM_MESSAGE_COMMANDS: {
44
44
  readonly LIVE_STREAMING_UPDATE: 700;
45
45
  /** 用户被踢出命令 */
46
46
  readonly USER_KICK_OUT: 5;
47
+ /** 用户widget属性更新命令(widget属性变更的通用cause.cmd) */
48
+ readonly USER_WIDGET_PROPERTIES_UPDATE: 10;
49
+ /** 客户端录制权限变更(角色级, cause.cmd=3260) */
50
+ readonly CLIENT_RECORDING_PERMISSION: 3260;
51
+ /** 客户端录制状态变更 cause.data.widgetCause.cmd=3261 (通过widget属性下发) */
52
+ readonly CLIENT_RECORDING_STATE: 3261;
53
+ /** 用户权限变更(用户级,含客户端录制) */
54
+ readonly USER_PERMISSION_UPDATE: 3121;
47
55
  /** 房间会话请求命令 */
48
56
  readonly ROOM_SESSION_REQUEST: 1001;
49
57
  /** 房间会话接受命令 */
@@ -53,6 +53,14 @@ const ROOM_MESSAGE_COMMANDS = exports.ROOM_MESSAGE_COMMANDS = {
53
53
  LIVE_STREAMING_UPDATE: 700,
54
54
  /** 用户被踢出命令 */
55
55
  USER_KICK_OUT: 5,
56
+ /** 用户widget属性更新命令(widget属性变更的通用cause.cmd) */
57
+ USER_WIDGET_PROPERTIES_UPDATE: 10,
58
+ /** 客户端录制权限变更(角色级, cause.cmd=3260) */
59
+ CLIENT_RECORDING_PERMISSION: 3260,
60
+ /** 客户端录制状态变更 cause.data.widgetCause.cmd=3261 (通过widget属性下发) */
61
+ CLIENT_RECORDING_STATE: 3261,
62
+ /** 用户权限变更(用户级,含客户端录制) */
63
+ USER_PERMISSION_UPDATE: 3121,
56
64
  /** 房间会话请求命令 */
57
65
  ROOM_SESSION_REQUEST: 1001,
58
66
  /** 房间会话接受命令 */
@@ -24,7 +24,9 @@ require("core-js/modules/es.array.includes.js");
24
24
  require("core-js/modules/es.array.push.js");
25
25
  require("core-js/modules/es.json.stringify.js");
26
26
  require("core-js/modules/esnext.iterator.constructor.js");
27
+ require("core-js/modules/esnext.iterator.find.js");
27
28
  require("core-js/modules/esnext.iterator.for-each.js");
29
+ require("core-js/modules/esnext.iterator.some.js");
28
30
  var _imports = require("../imports");
29
31
  var _type = require("../type");
30
32
  var _type2 = require("./type");
@@ -68,7 +70,7 @@ function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side
68
70
  */
69
71
  class FcrBaseRoomControlImpl {
70
72
  static {
71
- [_initProto] = _applyDecs(this, [[_imports.trace, 2, "getSyncTimestamp"], [_imports.trace, 2, "getRoomInfo"], [_imports.trace, 2, "getRoomSchedule"], [_imports.trace, 2, "getLocalExDataSyncTypeList"], [_joinDecs, 2, "join"], [_imports.trace, 2, "leave"], [_imports.trace, 2, "release"], [_imports.trace, 2, "start"], [_imports.trace, 2, "end"], [_imports.trace, 2, "close"], [_imports.trace, 2, "getRoomState"], [_imports.trace, 2, "getRoomProperties"], [_imports.trace, 2, "getRoomPropertiesByKeyPath"], [_updateRoomPropertiesDecs, 2, "updateRoomProperties"], [_updateIncrementRoomPropertiesDecs, 2, "updateIncrementRoomProperties"], [_deleteRoomPropertiesDecs, 2, "deleteRoomProperties"], [_startCloudRecordingDecs, 2, "startCloudRecording"], [_imports.trace, 2, "pauseCloudRecording"], [_imports.trace, 2, "resumeCloudRecording"], [_imports.trace, 2, "stopCloudRecording"], [_imports.trace, 2, "getLiveStreamingState"], [_imports.trace, 2, "getLiveStreamingConfig"], [_startLiveStreamingDecs, 2, "startLiveStreaming"], [_updateLiveStreamingLayoutDecs, 2, "updateLiveStreamingLayout"], [_imports.trace, 2, "stopLiveStreaming"], [_imports.trace, 2, "getCloudRecordingState"], [_sendRoomMessageDecs, 2, "sendRoomMessage"]], []).e;
73
+ [_initProto] = _applyDecs(this, [[_imports.trace, 2, "getSyncTimestamp"], [_imports.trace, 2, "getRoomInfo"], [_imports.trace, 2, "getRoomSchedule"], [_imports.trace, 2, "getLocalExDataSyncTypeList"], [_joinDecs, 2, "join"], [_imports.trace, 2, "leave"], [_imports.trace, 2, "release"], [_imports.trace, 2, "start"], [_imports.trace, 2, "end"], [_imports.trace, 2, "close"], [_imports.trace, 2, "getRoomState"], [_imports.trace, 2, "getRoomProperties"], [_imports.trace, 2, "getRoomPropertiesByKeyPath"], [_updateRoomPropertiesDecs, 2, "updateRoomProperties"], [_updateIncrementRoomPropertiesDecs, 2, "updateIncrementRoomProperties"], [_deleteRoomPropertiesDecs, 2, "deleteRoomProperties"], [_startCloudRecordingDecs, 2, "startCloudRecording"], [_imports.trace, 2, "pauseCloudRecording"], [_imports.trace, 2, "resumeCloudRecording"], [_imports.trace, 2, "stopCloudRecording"], [_imports.trace, 2, "startClientRecording"], [_imports.trace, 2, "resumeClientRecording"], [_imports.trace, 2, "stopClientRecording"], [_imports.trace, 2, "getLiveStreamingState"], [_imports.trace, 2, "getLiveStreamingConfig"], [_startLiveStreamingDecs, 2, "startLiveStreaming"], [_updateLiveStreamingLayoutDecs, 2, "updateLiveStreamingLayout"], [_imports.trace, 2, "stopLiveStreaming"], [_imports.trace, 2, "hasActiveClientRecording"], [_imports.trace, 2, "getCloudRecordingState"], [_sendRoomMessageDecs, 2, "sendRoomMessage"]], []).e;
72
74
  }
73
75
  //@internal
74
76
  [(_joinDecs = (0, _imports.trace)(['options']), _updateRoomPropertiesDecs = (0, _imports.trace)(['properties', 'cause']), _updateIncrementRoomPropertiesDecs = (0, _imports.trace)(['increments', 'cause']), _deleteRoomPropertiesDecs = (0, _imports.trace)(['properties', 'cause']), _startCloudRecordingDecs = (0, _imports.trace)(['config']), _startLiveStreamingDecs = (0, _imports.trace)(['data']), _updateLiveStreamingLayoutDecs = (0, _imports.trace)(['layoutType']), _sendRoomMessageDecs = (0, _imports.trace)(['payload', 'guaranteedDelivery']), "logger")] = (_initProto(this), (0, _logger.createLogger)({
@@ -78,6 +80,8 @@ class FcrBaseRoomControlImpl {
78
80
  _joinRoomSuccess = false;
79
81
  _joining = false;
80
82
  _joinState = _helpers.ROOM_CONTROL_CONSTANTS.JOIN_STATE.CANCELED;
83
+ _isClientRecordingOwner = false;
84
+ _isStartingClientRecording = false;
81
85
  constructor(_engine, _scene, _api, _config, _roomType, _chatConnection, _sharedCache, _chatRoomControl) {
82
86
  this._engine = _engine;
83
87
  this._scene = _scene;
@@ -226,8 +230,45 @@ class FcrBaseRoomControlImpl {
226
230
  this._joinState = _helpers.ROOM_CONTROL_CONSTANTS.JOIN_STATE.CANCELED;
227
231
  this._cleanup();
228
232
  }
233
+ // Handle user-level local recording permission change (cmd=3121)
234
+ if (event.cause?.cmd === _helpers.ROOM_MESSAGE_COMMANDS.USER_PERMISSION_UPDATE && event.modifiedUser.userId === this._scene.localUser.getLocalUserId()) {
235
+ const causeData = event.cause?.data;
236
+ if (causeData?.removePermissions?.includes('record:startClientRecording')) {
237
+ if (this.isLocalClientRecordingActive()) {
238
+ this.stopClientRecording().catch(e => {
239
+ this.logger.error(`auto-stop client recording failed: ${e.message}`);
240
+ });
241
+ }
242
+ }
243
+ }
244
+ }
245
+ };
246
+
247
+ // 初始化客户端录制观察者
248
+ this._clientRecordingObserver = {
249
+ onRecorderStateChanged: (_channelId, _uid, state, reason) => {
250
+ this.logger.info(`onRecorderStateChanged: state=${state}, reason=${reason}, sceneId=${this._scene.sceneId}`);
251
+ this._observable.notifyObservers('onClientRecordingStateChanged', this._scene.sceneId, state, reason);
229
252
  }
230
253
  };
254
+
255
+ // 客户端录制观察者由子类(FcrMainRoomControlImpl)注册,基类不注册
256
+ }
257
+
258
+ /**
259
+ * 注册客户端录制观察者。由主房间子类在构造时调用。
260
+ */
261
+ _registerClientRecordingObserver() {
262
+ this._engine.getMediaControl().addClientRecordingObserver(this._clientRecordingObserver);
263
+ this._isClientRecordingOwner = true;
264
+ }
265
+
266
+ /**
267
+ * 移除客户端录制观察者。由 _cleanup 调用时需要判断是否已注册。
268
+ */
269
+ _unregisterClientRecordingObserver() {
270
+ this._engine.getMediaControl().removeClientRecordingObserver(this._clientRecordingObserver);
271
+ this._isClientRecordingOwner = false;
231
272
  }
232
273
 
233
274
  /**
@@ -574,6 +615,89 @@ class FcrBaseRoomControlImpl {
574
615
  await (0, _error.handleRequestError)(() => this._api.stopCloudRecording(this._scene.sceneId), _imports.ErrorModuleCode.FCR_ROOM, 'stop cloud recording failed');
575
616
  return _type.FcrReturnCode.SUCCESS;
576
617
  }
618
+ async startClientRecording(config) {
619
+ // 防止并发调用
620
+ if (this._isStartingClientRecording) {
621
+ this.logger.warn('startClientRecording: already starting, ignoring duplicate call');
622
+ return _type.FcrReturnCode.SUCCESS;
623
+ }
624
+ const localUserId = this._scene.localUser.getLocalUserId();
625
+
626
+ // 标记正在启动本地录制,避免在 API 调用后、SDK 录制开始前的时间窗口内触发错误的提示
627
+ this._isStartingClientRecording = true;
628
+ let serverRecordingStarted = false;
629
+ try {
630
+ await (0, _error.handleRequestError)(() => this._api.startClientRecording(this._scene.sceneId, localUserId), _imports.ErrorModuleCode.FCR_ROOM, 'start client recording failed');
631
+ serverRecordingStarted = true;
632
+
633
+ // 使用 mediaControl 来启动本地录制
634
+ const mediaControl = this._engine.getMediaControl();
635
+ const isScreenShareActive = mediaControl.isScreenShareActive();
636
+ const currentScreenShareSourceId = mediaControl.getCurrentScreenShareSourceId();
637
+
638
+ // 获取本地用户的主流 streamId 作为录制 uid(streamId 是数字,userUuid 可能是十六进制字符串)
639
+ const localStreams = this._scene.getStreamsByUserId(localUserId);
640
+ const mainStream = localStreams.find(s => s.videoSourceType === _imports.AgoraRteVideoSourceType.CAMERA) || localStreams[0];
641
+ if (!mainStream) {
642
+ throw new Error(`No stream found for local user: ${localUserId}`);
643
+ }
644
+ const uid = Number(mainStream.streamId);
645
+ await mediaControl.startClientRecording(this._scene.sceneId, uid, config, isScreenShareActive, currentScreenShareSourceId);
646
+ return _type.FcrReturnCode.SUCCESS;
647
+ } catch (e) {
648
+ // API 调用成功但 SDK 启动失败时,回滚服务端状态
649
+ if (serverRecordingStarted) {
650
+ this.logger.error(`startClientRecording SDK failed, rolling back server state: ${e.message}`);
651
+ this._api.stopClientRecording(this._scene.sceneId, localUserId).catch(rollbackErr => {
652
+ this.logger.error(`startClientRecording rollback failed: ${rollbackErr.message}`);
653
+ });
654
+ }
655
+ throw e;
656
+ } finally {
657
+ this._isStartingClientRecording = false;
658
+ }
659
+ }
660
+ async resumeClientRecording() {
661
+ const localUserId = this._scene.localUser.getLocalUserId();
662
+ await (0, _error.handleRequestError)(() => this._api.resumeClientRecording(this._scene.sceneId, localUserId), _imports.ErrorModuleCode.FCR_ROOM, 'resume client recording failed');
663
+ return _type.FcrReturnCode.SUCCESS;
664
+ }
665
+ async stopClientRecording() {
666
+ const localUserId = this._scene.localUser.getLocalUserId();
667
+
668
+ // Step 1: 使用 mediaControl 的 stopClientRecording(即使失败也继续通知服务端)
669
+ const mediaControl = this._engine.getMediaControl();
670
+ try {
671
+ await mediaControl.stopClientRecording();
672
+ } catch (e) {
673
+ this.logger.error(`mediaControl.stopClientRecording failed: ${e.message}`);
674
+ }
675
+
676
+ // Step 2: 无论 SDK 停止是否成功,都通知服务器停止录制
677
+ await (0, _error.handleRequestError)(() => this._api.stopClientRecording(this._scene.sceneId, localUserId), _imports.ErrorModuleCode.FCR_ROOM, 'stop client recording failed');
678
+ return _type.FcrReturnCode.SUCCESS;
679
+ }
680
+ isLocalClientRecordingActive() {
681
+ // 如果正在启动本地录制,也返回 true,避免在启动过程中触发错误的"其他人开启录制"提示
682
+ const mediaControl = this._engine.getMediaControl();
683
+ return this._isStartingClientRecording || mediaControl.isClientRecordingActive();
684
+ }
685
+ isClientRecordingOwnsCapture() {
686
+ const mediaControl = this._engine.getMediaControl();
687
+ return mediaControl.isClientRecordingOwnsCapture();
688
+ }
689
+ async resumeClientRecordingOwnCapture() {
690
+ const mediaControl = this._engine.getMediaControl();
691
+ await mediaControl.resumeClientRecordingOwnCapture();
692
+ }
693
+ async waitForScreenCaptureStopped() {
694
+ const mediaControl = this._engine.getMediaControl();
695
+ await mediaControl.waitForScreenCaptureStopped();
696
+ }
697
+ releaseClientRecordingOwnCapture() {
698
+ const mediaControl = this._engine.getMediaControl();
699
+ mediaControl.releaseClientRecordingOwnCapture();
700
+ }
577
701
  getLiveStreamingState() {
578
702
  let liveStreamingState = this._scene.getScenePropertiesByKeyPath('live.state');
579
703
  if (liveStreamingState === _helpers.LIVE_STREAMING_STATE_VALUES.STARTED) {
@@ -613,6 +737,11 @@ class FcrBaseRoomControlImpl {
613
737
  await this._liveStreamingAction(() => this._api.stopLiveStreaming(this._scene.sceneId), 'stop live streaming failed');
614
738
  return _type.FcrReturnCode.SUCCESS;
615
739
  }
740
+ hasActiveClientRecording() {
741
+ const userControl = this.getUserControl();
742
+ const users = userControl.getUserList();
743
+ return users.some(user => userControl.getUserClientRecordingState(user.userId));
744
+ }
616
745
  getCloudRecordingState() {
617
746
  const {
618
747
  state,
@@ -641,6 +770,21 @@ class FcrBaseRoomControlImpl {
641
770
  async _cleanup() {
642
771
  this._scene.removeObserver(this._sceneObserver);
643
772
  this._engine.removeObserver(this._engineObserver);
773
+
774
+ // 只有注册过录制观察者的 room control 才拥有录制生命周期,
775
+ // 其他房间(如等候室)的 cleanup 不应停止共享 mediaControl 上的录制。
776
+ if (this._isClientRecordingOwner) {
777
+ this._unregisterClientRecordingObserver();
778
+ try {
779
+ const mediaControl = this._engine.getMediaControl();
780
+ if (mediaControl.isClientRecordingActive()) {
781
+ await mediaControl.stopClientRecording();
782
+ this.logger.info('_cleanup: stopped client recording');
783
+ }
784
+ } catch (e) {
785
+ this.logger.error(`_cleanup: failed to stop client recording: ${e.message}`);
786
+ }
787
+ }
644
788
  this._sharedCache.getRoomCache(this._scene.sceneId).clear();
645
789
  try {
646
790
  return await Promise.all([this._chatRoomControl?.leave(), this.sharingControl?.getBoardControl().close(), this.sharingControl?.getAnnotationControl().close(), this._scene.leave()]);
@@ -60,6 +60,7 @@ class FcrInfinityRoomControlImpl extends _.FcrBaseRoomControlImpl {
60
60
  this._interpreterControl = new _interpreterControl.FcrInterpreterControlImpl(api, scene, config, engine, chatConnection, sharedCache);
61
61
  this._addLogObserver();
62
62
  (0, _boardInitInfoHelper.addBoardInitInfoObserver)(this._scene, this._api, this.logger);
63
+ this._registerClientRecordingObserver();
63
64
  this.logger.info(`initialized, room id: ${this._scene.sceneId}`);
64
65
  }
65
66
  getAnnouncement() {
@@ -114,13 +115,11 @@ class FcrInfinityRoomControlImpl extends _.FcrBaseRoomControlImpl {
114
115
  _onLocalUserPermissionInfoDeleted(roomId, event) {
115
116
  if ((0, _utils.hasBoardWritePermission)(event.permissionInfo)) {
116
117
  this.logger.info(`remove board write permission`);
117
-
118
- // this.getSharingControl().getBoardControl().getMainWindow()?.setWritable(false);
118
+ this.getSharingControl().getBoardControl().getMainWindow()?.setWritable(false);
119
119
  }
120
120
  if ((0, _utils.hasAnnotationWritePermission)(event.permissionInfo)) {
121
121
  this.logger.info(`remove annotation write permission`);
122
-
123
- // this.getSharingControl().getAnnotationControl().getMainWindow()?.setWritable(false);
122
+ this.getSharingControl().getAnnotationControl().getMainWindow()?.setWritable(false);
124
123
  }
125
124
  }
126
125
  _addLogObserver() {
@@ -71,6 +71,7 @@ class FcrMainRoomControlImpl extends _.FcrBaseRoomControlImpl {
71
71
  this._interpreterControl = new _interpreterControl.FcrInterpreterControlImpl(api, scene, config, engine, chatConnection, sharedCache);
72
72
  this._addLogObserver();
73
73
  (0, _boardInitInfoHelper.addBoardInitInfoObserver)(this._scene, this._api, this.logger);
74
+ this._registerClientRecordingObserver();
74
75
  this.logger.info(`initialized, room id: ${this._scene.sceneId}`);
75
76
  }
76
77
 
@@ -138,13 +139,11 @@ class FcrMainRoomControlImpl extends _.FcrBaseRoomControlImpl {
138
139
  _onLocalUserPermissionInfoDeleted(roomId, event) {
139
140
  if ((0, _utils2.hasBoardWritePermission)(event.permissionInfo)) {
140
141
  this.logger.info(`remove board write permission`);
141
-
142
- // this.getSharingControl().getBoardControl().getMainWindow()?.setWritable(false);
142
+ this.getSharingControl().getBoardControl().getMainWindow()?.setWritable(false);
143
143
  }
144
144
  if ((0, _utils2.hasAnnotationWritePermission)(event.permissionInfo)) {
145
145
  this.logger.info(`remove annotation write permission`);
146
-
147
- // this.getSharingControl().getAnnotationControl().getMainWindow()?.setWritable(false);
146
+ this.getSharingControl().getAnnotationControl().getMainWindow()?.setWritable(false);
148
147
  }
149
148
  }
150
149
  _checkIfUpdateAnnotationMainWindow() {