trtc-cloud-js-sdk 1.0.13 → 2.0.0

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 (143) hide show
  1. package/.eslintrc.js +88 -0
  2. package/.prettierrc +5 -0
  3. package/CHANGELOG.md +58 -0
  4. package/build/jsdoc/clean-doc.js +12 -0
  5. package/build/jsdoc/fix-doc.js +141 -0
  6. package/build/jsdoc/jsdoc.json +42 -0
  7. package/build/package-bundle.js +29 -0
  8. package/build/rollup.config.dev.js +88 -0
  9. package/build/rollup.config.prod.js +93 -0
  10. package/build/rollup.js +359 -0
  11. package/build/template/npm-package/package.json +24 -0
  12. package/coverage/Chrome 103.0.5060 (Mac OS X 10.15.7)/base.css +213 -0
  13. package/coverage/Chrome 103.0.5060 (Mac OS X 10.15.7)/index.html +80 -0
  14. package/coverage/Chrome 103.0.5060 (Mac OS X 10.15.7)/prettify.css +1 -0
  15. package/coverage/Chrome 103.0.5060 (Mac OS X 10.15.7)/prettify.js +1 -0
  16. package/coverage/Chrome 103.0.5060 (Mac OS X 10.15.7)/sort-arrow-sprite.png +0 -0
  17. package/coverage/Chrome 103.0.5060 (Mac OS X 10.15.7)/sorter.js +158 -0
  18. package/examples/apiExample/.env +2 -0
  19. package/examples/apiExample/README.md +70 -0
  20. package/examples/apiExample/package-lock.json +30915 -0
  21. package/examples/apiExample/package.json +51 -0
  22. package/examples/apiExample/public/audio.js +195 -0
  23. package/examples/apiExample/public/audio.js.map +7 -0
  24. package/examples/apiExample/public/av_processing.js +1 -0
  25. package/examples/apiExample/public/basic/av_processing.wasm +0 -0
  26. package/examples/apiExample/public/basic/worker.js +10434 -0
  27. package/examples/apiExample/public/favicon.ico +0 -0
  28. package/examples/apiExample/public/index.html +47 -0
  29. package/examples/apiExample/public/logo192.png +0 -0
  30. package/examples/apiExample/public/logo512.png +0 -0
  31. package/examples/apiExample/public/manifest.json +25 -0
  32. package/examples/apiExample/public/robots.txt +3 -0
  33. package/examples/apiExample/src/App.css +37 -0
  34. package/examples/apiExample/src/App.js +25 -0
  35. package/examples/apiExample/src/api/http.js +127 -0
  36. package/examples/apiExample/src/api/nav.js +44 -0
  37. package/examples/apiExample/src/components/BasicInfoComponent.css +16 -0
  38. package/examples/apiExample/src/components/BasicInfoComponent.js +27 -0
  39. package/examples/apiExample/src/config/gen-test-user-sig.js +64 -0
  40. package/examples/apiExample/src/config/lib-generate-test-usersig.min.js +7052 -0
  41. package/examples/apiExample/src/config/nav.js +136 -0
  42. package/examples/apiExample/src/home.js +16 -0
  43. package/examples/apiExample/src/index.css +21 -0
  44. package/examples/apiExample/src/index.js +12 -0
  45. package/examples/apiExample/src/logo.svg +1 -0
  46. package/examples/apiExample/src/page/basic/screen-share/index.css +52 -0
  47. package/examples/apiExample/src/page/basic/screen-share/index.js +223 -0
  48. package/examples/apiExample/src/page/basic/setDevice/index.js +262 -0
  49. package/examples/apiExample/src/page/basic/setDevice/index.scss +93 -0
  50. package/examples/apiExample/src/page/basic/video-call/index.js +521 -0
  51. package/examples/apiExample/src/page/basic/video-call/index.scss +93 -0
  52. package/examples/apiExample/src/page/basic/video-call-init/index.js +382 -0
  53. package/examples/apiExample/src/page/basic/video-call-init/index.scss +93 -0
  54. package/examples/apiExample/src/page/basic/video-live/index.css +37 -0
  55. package/examples/apiExample/src/page/basic/video-live/index.js +188 -0
  56. package/examples/apiExample/src/page/layout.js +22 -0
  57. package/examples/apiExample/src/page/layout.scss +76 -0
  58. package/examples/apiExample/src/utils/utils.js +35 -0
  59. package/examples/jsExample/assets/css/bootstrap-material-design.css +12169 -0
  60. package/examples/jsExample/assets/css/bootstrap-material-design.min.css +8 -0
  61. package/examples/jsExample/assets/css/common.css +48 -0
  62. package/examples/jsExample/assets/icon/iconfont.js +1 -0
  63. package/examples/jsExample/assets/js/bootstrap-material-design.js +6939 -0
  64. package/examples/jsExample/assets/js/bootstrap-material-design.js.map +1 -0
  65. package/examples/jsExample/assets/js/bootstrap-material-design.min.js +1 -0
  66. package/examples/jsExample/assets/js/graph.js +695 -0
  67. package/examples/jsExample/assets/js/jquery-3.2.1.min.js +4 -0
  68. package/examples/jsExample/assets/js/jquery-3.2.1.slim.min.js +4 -0
  69. package/examples/jsExample/assets/js/lib-generate-test-usersig.min.js +2 -0
  70. package/examples/jsExample/assets/js/popper.js +2442 -0
  71. package/examples/jsExample/index.html +57 -0
  72. package/examples/jsExample/rtc/css/common.css +82 -0
  73. package/examples/jsExample/rtc/index.html +107 -0
  74. package/examples/jsExample/rtc/js/index.js +142 -0
  75. package/examples/vueDemo/LICENSE +21 -0
  76. package/examples/vueDemo/README.md +144 -0
  77. package/examples/vueDemo/README_EN.md +136 -0
  78. package/examples/vueDemo/av_processing.wasm +0 -0
  79. package/examples/vueDemo/index.html +23 -0
  80. package/examples/vueDemo/package-lock.json +1375 -0
  81. package/examples/vueDemo/package.json +36 -0
  82. package/examples/vueDemo/src/App.vue +12 -0
  83. package/examples/vueDemo/src/api/index.js +59 -0
  84. package/examples/vueDemo/src/assets/css/color-dark.css +28 -0
  85. package/examples/vueDemo/src/assets/css/icon.css +4 -0
  86. package/examples/vueDemo/src/assets/css/main.css +177 -0
  87. package/examples/vueDemo/src/assets/img/img.jpg +0 -0
  88. package/examples/vueDemo/src/assets/img/login-bg.jpg +0 -0
  89. package/examples/vueDemo/src/components/Header.vue +172 -0
  90. package/examples/vueDemo/src/components/Sidebar.vue +117 -0
  91. package/examples/vueDemo/src/components/Tags.vue +174 -0
  92. package/examples/vueDemo/src/components/tendency.vue +206 -0
  93. package/examples/vueDemo/src/components/trtc/main-menu.vue +50 -0
  94. package/examples/vueDemo/src/components/trtc/nav-bar.vue +53 -0
  95. package/examples/vueDemo/src/components/trtc/show-screen-capture.vue +118 -0
  96. package/examples/vueDemo/src/components/trtc/trtc-state-check.vue +117 -0
  97. package/examples/vueDemo/src/config/gen-test-user-sig.js +67 -0
  98. package/examples/vueDemo/src/config/lib-generate-test-usersig.min.js +7052 -0
  99. package/examples/vueDemo/src/main.js +11 -0
  100. package/examples/vueDemo/src/plugins/element.js +17 -0
  101. package/examples/vueDemo/src/router/index.js +73 -0
  102. package/examples/vueDemo/src/store/sidebar.js +17 -0
  103. package/examples/vueDemo/src/store/tags.js +48 -0
  104. package/examples/vueDemo/src/utils/i18n.js +24 -0
  105. package/examples/vueDemo/src/utils/request.js +34 -0
  106. package/examples/vueDemo/src/utils/utils.js +35 -0
  107. package/examples/vueDemo/src/views/Home.vue +46 -0
  108. package/examples/vueDemo/src/views/I18n.vue +40 -0
  109. package/examples/vueDemo/src/views/Icon.vue +229 -0
  110. package/examples/vueDemo/src/views/basic/trtc.vue +194 -0
  111. package/examples/vueDemo/src/views/feature/index.vue +259 -0
  112. package/examples/vueDemo/src/views/github/index.vue +243 -0
  113. package/examples/vueDemo/src/views/improve/live-index.vue +256 -0
  114. package/examples/vueDemo/src/views/improve/live-room-anchor.vue +689 -0
  115. package/examples/vueDemo/src/views/improve/live-room-audience.vue +383 -0
  116. package/examples/vueDemo/src/views/sdkAppId/index.vue +284 -0
  117. package/examples/vueDemo/vite.config.js +18 -0
  118. package/examples/vueDemo/worker.js +22 -0
  119. package/karma.conf.js +99 -0
  120. package/package.json +57 -7
  121. package/scripts/publish.js +86 -0
  122. package/src/Camera.ts +80 -0
  123. package/src/Mic.ts +145 -0
  124. package/src/common/IError.ts +6 -0
  125. package/src/common/ITRTCCloud.ts +68 -0
  126. package/src/common/constants.ts +116 -0
  127. package/src/common/trtc-code.ts +43 -0
  128. package/src/common/trtc-define.ts +1007 -0
  129. package/src/common/trtc-event.ts +29 -0
  130. package/src/index.ts +1672 -0
  131. package/src/utils/environment.js +297 -0
  132. package/src/utils/raf.js +131 -0
  133. package/src/utils/time.js +22 -0
  134. package/src/utils/utils.ts +71 -0
  135. package/src/utils/uuid.js +12 -0
  136. package/test/unit/env.test.js +25 -0
  137. package/test/unit/get-user-media.test.js +40 -0
  138. package/test/unit/ice-parser.test.js +23 -0
  139. package/test/unit/sdp.test.js +45 -0
  140. package/test/unit/signal.test.js +78 -0
  141. package/tsconfig.json +32 -0
  142. package/trtc-cloud-js-sdk.js +0 -1
  143. /package/{README.md → build/template/npm-package/README.md} +0 -0
package/src/index.ts ADDED
@@ -0,0 +1,1672 @@
1
+ import { EventEmitter } from 'events';
2
+ import {
3
+ TRTCAppScene,
4
+ TRTCVideoEncParam,
5
+ TRTCRenderParams,
6
+ TRTCVideoMirrorType,
7
+ TRTCVideoFillMode,
8
+ TRTCVideoStreamType,
9
+ TRTCRoleType,
10
+ TRTCDeviceInfo,
11
+ TRTCVideoResolution,
12
+ TRTCAudioQuality,
13
+ TRTCDeviceType,
14
+ TRTCDeviceState,
15
+ TRTCQualityInfo,
16
+ TRTCQuality,
17
+ TRTCVideoRotation,
18
+ } from './common/trtc-define';
19
+ import { EnterRoomFailure, RoleMap, SceneMap, NAME, audioQualityMap } from './common/constants';
20
+ import { MixinsClass, performanceNow, isUndefined, debounce } from './utils/utils';
21
+ import { VideoFillMode, ITRTCCloudConfig, IEnterRoomParams, IVideoProfile, IVideoPlayOption } from './common/ITRTCCloud';
22
+ import { IError } from './common/IError';
23
+ import { NotSupportError, jsExecuteError, ParametersError } from './common/trtc-code';
24
+ import { version } from '../package.json';
25
+ import TRTC from 'trtc-sdk-v5';
26
+ export * from './common/trtc-define';
27
+ let trtcCloudInstance: TRTC_Cloud | null = null;
28
+ const LOG_PREFIX = '【TRTCCloud】';
29
+
30
+ TRTC._loggerManager.info(`TRTCCloud Version: ${version}`);
31
+
32
+ class TRTC_Cloud extends MixinsClass(EventEmitter) {
33
+ private _version: string = '';
34
+ // frameWorkType: https://doc.weixin.qq.com/sheet/e3_AHIAowbJACcZN50GGNIQYSRacTgpe?scode=AJEAIQdfAAoWyUZ6CdAHIAowbJACc
35
+ public _frameWorkType: number = 30;
36
+ public _component: number = 0;
37
+ private _log: any;
38
+ public _trtc: any;
39
+ private _testTrtc: any;
40
+ private _localView: HTMLElement | string | null = null; // 用来记录本地预览的 view, updateLocalVideo 时传入全量数据
41
+ private _localTestView: HTMLElement | string | null = null; // 用来记录本地测试的 view, updateLocalVideo 时传入全量数据
42
+ private _isVideoPublish: boolean = true; // 默认是推流(call 需要在预览时不推流) TODO: 应该可以删掉
43
+ private _localRenderParams = {
44
+ rotation: TRTCVideoRotation.TRTCVideoRotation0,
45
+ fillMode: TRTCVideoFillMode.TRTCVideoFillMode_Fill,
46
+ mirrorType: TRTCVideoMirrorType.TRTCVideoMirrorType_Auto,
47
+ };
48
+ private _videoProfile: IVideoProfile = {};
49
+ private _videoPlayOption: IVideoPlayOption = {};
50
+ private _isAudioPublish: boolean = true;
51
+ private _audioProfile: string = Object.keys(audioQualityMap)[0];
52
+ private _isSharingScreen: boolean = false;
53
+ private _remoteStreamConfig: Record<string, any> = new Map();
54
+ private _remoteStreamMap: Record<string, any> = new Map();
55
+ private _cameraList: Array<any> = [];
56
+ private _microphoneList: Array<any> = [];
57
+ private _speakerList: Array<any> = [];
58
+ private _currentCamera: Record<string, any> = {};
59
+ private _currentMicrophone: Record<string, any> = {};
60
+ private _currentSpeaker: Record<string, any> = {};
61
+ private _currentCameraId: string = '';
62
+ private _currentMicrophoneId: string = '';
63
+ private _currentSpeakerId: string = '';
64
+ private _screenShareParams: any = { option: {} };
65
+ // 根据 startLocalPreview 传入的参数记录是否为移动端
66
+ private _isMobile: boolean = false;
67
+ // 该属性仅对移动端有效
68
+ private _isFrontCamera: boolean = false;
69
+ private _defaultVideoProfile = {
70
+ width: 640,
71
+ height: 480,
72
+ frameRate: 15,
73
+ bitrate: 900,
74
+ };
75
+ // 这里兼容旧版 trtcCloud,设置默认屏幕分享分辨率,帧率,码率
76
+ private _defaultScreenProfile = {
77
+ width: 1920,
78
+ height: 1080,
79
+ frameRate: 15,
80
+ bitrate: 1500,
81
+ };
82
+ constructor(config: ITRTCCloudConfig = {}) {
83
+ super();
84
+ this._version = version;
85
+ // frameWorkType:https://doc.weixin.qq.com/sheet/e3_AHIAowbJACcZN50GGNIQYSRacTgpe?scode=AJEAIQdfAAoWyUZ6CdAHIAowbJACc
86
+ const { frameWorkType = 30, component = 0 } = config;
87
+ // @ts-ignore
88
+ this._trtc = TRTC.create({ frameWorkType, component });
89
+ // 用于本地设备测试的实例
90
+ this._testTrtc = TRTC.create();
91
+ // @ts-ignore
92
+ this._log = TRTC._loggerManager; // TODO: 日志上报模块儿
93
+ this._addTRTCEvents();
94
+
95
+ this.handleDeviceChange = this.handleDeviceChange.bind(this);
96
+
97
+ if (navigator && navigator.mediaDevices) {
98
+ navigator.mediaDevices.addEventListener('devicechange', debounce(this.handleDeviceChange, 30));
99
+ }
100
+ }
101
+ /**
102
+ * 创建 TRTCCloud 对象单例
103
+ * @param {ITRTCCloudConfig} config 构造函数初始化
104
+ * @memberof TRTCCloud
105
+ * @returns {TRTC_Cloud}
106
+ * @example
107
+ * import TRTCCloud from 'trtc-cloud-js-sdk';
108
+ * const trtcCloud = TRTCCloud.getTRTCShareInstance(); // 创建 TRTCCloud 对象单例
109
+ */
110
+ static getTRTCShareInstance(config: ITRTCCloudConfig) {
111
+ if (!trtcCloudInstance) {
112
+ trtcCloudInstance = new TRTC_Cloud(config);
113
+ }
114
+ return trtcCloudInstance;
115
+ }
116
+ /**
117
+ * 释放 TRTCCloud 对象并清理资源
118
+ * @memberof TRTCCloud
119
+ * @example
120
+ * import TRTCCloud from 'trtc-cloud-js-sdk';
121
+ * TRTCCloud.destroyTRTCShareInstance(); // 释放 TRTCCloud 对象并清理资源
122
+ */
123
+ public destroyTRTCShareInstance(): void {
124
+ if (trtcCloudInstance) {
125
+ this._removeTRTCEvents();
126
+ trtcCloudInstance._destroy();
127
+ trtcCloudInstance = null;
128
+ }
129
+ }
130
+ /**
131
+ * 获取 SDK 版本信息
132
+ * @memberof TRTCCloud
133
+ * @returns {String}
134
+ */
135
+ public getSDKVersion(): string {
136
+ return this._version || '';
137
+ }
138
+ /**
139
+ * 绑定事件<br>
140
+ * @param {String} event 事件名称
141
+ * @param {Function} callback 事件回调
142
+ * @memberof TRTCCloud
143
+ * @example
144
+ * trtcCloud.on('onEnterRoom', callback);
145
+ */
146
+ // public on(event: string, callback: Function = () => { }): void {}
147
+ /**
148
+ * 取消事件绑定<br>
149
+ * @param {String} event 事件名称,传入通配符 '*' 会解除所有事件绑定。
150
+ * @param {Function} callback 事件回调
151
+ * @memberof TRTCCloud
152
+ * @example
153
+ * trtcCloud.off('onEnterRoom', callback);
154
+ * trtcCloud.off('*'); // 取消所有绑定的事件
155
+ */
156
+ public off(event: string, callback: Function = () => { }): void {
157
+ try {
158
+ if (event === '*') {
159
+ this.removeAllListeners();
160
+ } else {
161
+ this.removeListener(event, callback);
162
+ }
163
+ } catch (error: any) {
164
+ console.log('off error ', error); // TODO:
165
+ }
166
+ }
167
+ /**
168
+ * 进房<br>
169
+ * 调用接口后,您会收到来自 [TRTCCallback](https://web.sdk.qcloud.com/trtc/webrtc/trtcCloud/doc/TRTCCallback.html) 中的 [onEnterRoom](https://web.sdk.qcloud.com/trtc/webrtc/trtcCloud/doc/TRTCCallback.html#event:onEnterRoom) 回调
170
+ * - 如果加入成功,result 会是一个正数(result > 0),表示加入房间所消耗的时间,单位是毫秒(ms)
171
+ * - 如果加入失败,result 会是一个负数(result < 0),表示进房失败的错误码
172
+ *
173
+ * 参数 scene 的枚举值如下:
174
+ * - {@link TRTCAppSceneVideoCall}:<br>
175
+ * 视频通话场景,支持720P、1080P高清画质,单个房间最多支持300人同时在线,最高支持50人同时发言。<br>
176
+ * 适合:[1对1视频通话]、[300人视频会议]、[在线问诊]、[视频聊天]、[远程面试]等。<br>
177
+ * - {@link TRTCAppSceneAudioCall}:<br>
178
+ * 语音通话场景,支持 48kHz,支持双声道。单个房间最多支持300人同时在线,最高支持50人同时发言。<br>
179
+ * 适合:[1对1语音通话]、[300人语音会议]、[语音聊天]、[语音会议]、[在线狼人杀]等。<br>
180
+ * - {@link TRTCAppSceneLIVE}:<br>
181
+ * 视频互动直播,支持平滑上下麦,切换过程无需等待,主播延时小于300ms;支持十万级别观众同时播放,播放延时低至1000ms。<br>
182
+ * 适合:[视频低延时直播]、[十万人互动课堂]、[视频直播 PK]、[视频相亲房]、[互动课堂]、[远程培训]、[超大型会议]等。<br>
183
+ * - {@link TRTCAppSceneVoiceChatRoom}:<br>
184
+ * 语音互动直播,支持平滑上下麦,切换过程无需等待,主播延时小于300ms;支持十万级别观众同时播放,播放延时低至1000ms。<br>
185
+ * 适合:[语音低延时直播]、[语音直播连麦]、[语聊房]、[K 歌房]、[FM 电台]等。<br>
186
+ *
187
+ * **Note:**
188
+ * 1. 当 scene 选择为 TRTCAppSceneLIVE 或 TRTCAppSceneVoiceChatRoom 时,您必须通过 TRTCParams 中的 role 字段指定当前用户的角色。
189
+ * 2. 不管进房是否成功,{@link TRTCCloud#enterRoom enterRoom()} 都必须与 {@link TRTCCloud#exitRoom exitRoom()} 配对使用。
190
+ * 在调用 {@link TRTCCloud#exitRoom exitRoom()} 前再次调用 {@link TRTCCloud#enterRoom enterRoom()} 函数会导致不可预期的错误问题。
191
+ *
192
+ * @param {TRTCParams} params - 进房参数
193
+ * @param {Number} params.sdkAppId - 应用标识(必填)
194
+ * @param {String} params.userId - 用户标识(必填)
195
+ * @param {String} params.userSig - 用户签名(必填)
196
+ * @param {Number} params.roomId - 房间号码, roomId 和 strRoomId 必须填一个, 若您选用 strRoomId,则 roomId 需要填写为0。
197
+ * @param {String} params.strRoomId - 字符串房间号码 [选填],在同一个房间内的用户可以看到彼此并进行视频通话,
198
+ * roomId 和 strRoomId 必须填一个。若两者都填,则优先选择 roomId
199
+ * @param {TRTCRoleType} params.role - 直播场景下的角色,默认值:主播
200
+ * - TRTCRoleAnchor: 主播,可以上行视频和音频,一个房间里最多支持50个主播同时上行音视频。
201
+ * - TRTCRoleAudience: 观众,只能观看,不能上行视频和音频,一个房间里的观众人数没有上限。
202
+ * @param {String=} params.privateMapKey - 房间签名(非必填)
203
+ * @param {String=} params.streamId - 自定义 CDN 播放地址(非必填)
204
+ * @param {String=} params.userDefineRecordId - 设置云端录制完成后的回调消息中的 "userdefinerecordid" 字段内容,便于您更方便的识别录制回调(非必填)
205
+ * @param {TRTCAppScene} scene 应用场景,目前支持视频通话(TRTCAppSceneVideoCall)、语音通话(TRTCAppSceneAudioCall)、
206
+ * 在线直播(TRTCAppSceneLIVE)、语音聊天室(VTRTCAppSceneVoiceChatRoom)四种场景,详见 [TrtcDefines] 中 TRTCAppScene 参数定义
207
+ * @memberof TRTCCloud
208
+ * @example
209
+ * import TRTCCloud from 'trtc-cloud-js-sdk';
210
+ * const trtcCloud = TRTCCloud.getTRTCShareInstance(); // 创建实例,只需创建一次
211
+ * const params = {
212
+ * sdkAppId: 0,
213
+ * userId: 'denny',
214
+ * roomId: 12345,
215
+ * userSig: 'xxx'
216
+ * };
217
+ * trtcCloud.enterRoom(params, TRTCAppScene.TRTCAppSceneVideoCall);
218
+ */
219
+ public async enterRoom(params: any, scene: TRTCAppScene): Promise<void> {
220
+ const {
221
+ sdkAppId,
222
+ userId,
223
+ userSig,
224
+ roomId,
225
+ strRoomId,
226
+ role,
227
+ privateMapKey,
228
+ businessInfo,
229
+ enableAutoPlayDialog,
230
+ proxy,
231
+ streamId,
232
+ userDefineRecordId,
233
+ } = params;
234
+ if (sdkAppId && userId && userSig) {
235
+ // 字符串房间号码 [选填],在同一个房间内的用户可以看到彼此并进行视频通话, roomId 和 strRoomId 必须填一个。若两者都填,则优先选择 roomId
236
+ try {
237
+ let clientConfig: IEnterRoomParams = {
238
+ sdkAppId,
239
+ userId,
240
+ userSig,
241
+ roomId: roomId || strRoomId,
242
+ role: RoleMap[role],
243
+ scene: SceneMap[scene],
244
+ frameWorkType: this._frameWorkType,
245
+ component: this._component,
246
+ };
247
+ clientConfig = privateMapKey ? { ...clientConfig, privateMapKey } : clientConfig;
248
+ clientConfig = businessInfo ? { ...clientConfig, businessInfo } : clientConfig;
249
+ clientConfig = enableAutoPlayDialog ? { ...clientConfig, enableAutoPlayDialog } : clientConfig;
250
+ clientConfig = proxy ? { ...clientConfig, proxy } : clientConfig;
251
+ clientConfig = streamId ? { ...clientConfig, streamId } : clientConfig;
252
+ clientConfig = userDefineRecordId ? { ...clientConfig, userDefineRecordId } : clientConfig;
253
+ const startJoinTimestamp: number = performanceNow();
254
+ await this._trtc.enterRoom(clientConfig);
255
+ const delta: number = performanceNow() - startJoinTimestamp;
256
+ this.emit('onEnterRoom', delta);
257
+ } catch (error: any) {
258
+ this.emit('onEnterRoom', EnterRoomFailure);
259
+ this._callFunctionErrorManage(error, 'enterRoom');
260
+ }
261
+ } else {
262
+ this._emitError(ParametersError);
263
+ // this._log.error(`(enterRoom) failed - ${ParametersError.message}: ${JSON.stringify(params)}`);
264
+ }
265
+ }
266
+ /**
267
+ * 退房<br>
268
+ * 执行退出房间的相关逻辑释放资源后,SDK 会通过 [onExitRoom](https://web.sdk.qcloud.com/trtc/webrtc/trtcCloud/doc/TRTCCallback.html#event:onExitRoom) 回调通知到您
269
+ * **Note:**
270
+ * 1. 如果您要再次调用 {@link TRTCCloud#enterRoom enterRoom()} 或者切换到其它的音视频 SDK,请等待 [onExitRoom()](https://web.sdk.qcloud.com/trtc/webrtc/trtcCloud/doc/TRTCCallback.html#event:onExitRoom) 回调到来后再执行相关操作,否则可能会遇到如摄像头、麦克风设备被强占等各种异常问题。
271
+ * @memberof TRTCCloud
272
+ * @example
273
+ * await trtcCloud.exitRoom();
274
+ */
275
+ public async exitRoom(): Promise<void> {
276
+ try {
277
+ console.warn(`${LOG_PREFIX} exitRoom`);
278
+ if (this._isSharingScreen) {
279
+ await this.stopScreenShare();
280
+ }
281
+ await this._trtc.exitRoom();
282
+ } catch (error: any) {
283
+ this._callFunctionErrorManage(error, 'exitRoom');
284
+ }
285
+ }
286
+ /**
287
+ * 切换角色,**仅适用于直播场景(TRTCAppSceneLIVE 和 TRTCAppSceneVoiceChatRoom)**<br>
288
+ * 在直播场景下,一个用户可能需要在“观众”和“主播”之间来回切换。
289
+ * 您可以在进房前通过 TRTCParams 中的 role 指定角色,也可以通过 {@link TRTCCloud#switchRole switchRole()} 在进房后切换角色。<br>
290
+ * @param {TRTCRoleType} role - 目标角色,默认为主播
291
+ * - TRTCRoleAnchor: 主播,可以上行视频和音频,一个房间里最多支持50个主播同时上行音视频
292
+ * - TRTCRoleAudience: 观众,只能观看,不能上行视频和音频,一个房间里的观众人数没有上限
293
+ * @memberof TRTCCloud
294
+ * @example
295
+ * import TRTCCloud, { TRTCRoleType } from 'trtc-cloud-js-sdk';
296
+ * const trtcCloud = TRTCCloud.getTRTCShareInstance();
297
+ * await trtcCloud.switchRole(TRTCRoleType.TRTCRoleAudience);
298
+ */
299
+ public async switchRole(role: TRTCRoleType) {
300
+ try {
301
+ await this._trtc.switchRole(RoleMap[role]);
302
+ this.emit('onSwitchRole', 0, `switch role success, role = ${role}, ${RoleMap[role]}`);
303
+ } catch (error: any) {
304
+ this.emit('onSwitchRole', error?.getCode(), error.message);
305
+ }
306
+ }
307
+ // ==============================【 Video 】=============================
308
+
309
+ /**
310
+ * 更新本地视频配置
311
+ */
312
+ private async _updateLocalVideo() {
313
+ try {
314
+ await this._trtc.updateLocalVideo(this._generateLocalVideoData());
315
+ } catch (error: any) {
316
+ if (error.code === TRTC.ERROR_CODE.OPERATION_ABORT) {
317
+ return;
318
+ }
319
+ throw error;
320
+ }
321
+ }
322
+
323
+ /**
324
+ * 更新本地测试视频配置
325
+ */
326
+ private async _updateLocalTestVideo() {
327
+ try {
328
+ await this._testTrtc.updateLocalVideo(this._generateLocalTestVideoData());
329
+ } catch (error: any) {
330
+ // 当没有 startLocalVideo 的时候,调用 updateLocalVideo 会报错,此时忽略该错误不做处理
331
+ if (error.code === TRTC.ERROR_CODE.OPERATION_ABORT) {
332
+ return;
333
+ }
334
+ throw error;
335
+ }
336
+ }
337
+
338
+ /**
339
+ * 更新远端流视频配置
340
+ * @param {string} userId 远端用户名
341
+ * @param {TRTCVideoStreamType} streamType 远端流类型
342
+ */
343
+ private async _updateRemoteVideo(userId, streamType) {
344
+ try {
345
+ await this._trtc.updateRemoteVideo(this._generateRemoteVideoData(userId, streamType));
346
+ } catch (error: any) {
347
+ if (error.code === TRTC.ERROR_CODE.OPERATION_ABORT) {
348
+ return;
349
+ }
350
+ throw error;
351
+ }
352
+ }
353
+
354
+ /**
355
+ * 开启本地视频的预览画面<br>
356
+ * 这个接口会启动默认的摄像头,可以通过 [setCurrentCameraDevice](https://web.sdk.qcloud.com/trtc/webrtc/trtcCloud/doc/TRTCCloud.html#setCurrentCameraDevice) 接口选用其它摄像头<br>
357
+ * @param {HTMLElement} view - 承载预览画面的 DOM
358
+ * @memberof TRTCCloud
359
+ * @example
360
+ * // 桌面端预览本地画面
361
+ * const view = document.getElementById('local-view');
362
+ * trtcCloud.startLocalPreview(view);
363
+ *
364
+ * // 移动端预览本地画面
365
+ * const view = document.getElementById('local-view');
366
+ * // 移动端使用前置摄像头预览本地画面
367
+ * await trtcCloud.startLocalPreview(true, view);
368
+ * // 移动端使用前置摄像头预览本地画面
369
+ * await trtcCloud.startLocalPreview(false, view);
370
+ */
371
+ public async startLocalPreview(...args): Promise<void> {
372
+ this._log.info(`(startLocalPreview) start - start local preview with params: [${args}]`);
373
+ let isFrontCamera;
374
+ let view;
375
+ if ([...args].length === 1) {
376
+ [view] = [...args];
377
+ } else if ([...args].length === 2) {
378
+ [isFrontCamera, view] = [...args];
379
+ }
380
+
381
+ if (!isUndefined(isFrontCamera)) {
382
+ this._setIsMobile(true);
383
+ this._setIsFrontCamera(isFrontCamera);
384
+ }
385
+ this._setLocalView(view);
386
+
387
+ try {
388
+ try {
389
+ await this._trtc.startLocalVideo(this._generateLocalVideoData());
390
+ } catch (error: any) {
391
+ if (error.code === TRTC.ERROR_CODE.OPERATION_ABORT) {
392
+ // 重复调用 startLocalVideo 报错时需要更新 view
393
+ await this._updateLocalVideo();
394
+ return;
395
+ }
396
+ throw error;
397
+ }
398
+ this.emit('onCameraDidReady');
399
+ this.emit('onFirstVideoFrame', {
400
+ userId: '',
401
+ streamType: TRTCVideoStreamType.TRTCVideoStreamTypeBig,
402
+ width: this._getVideoProfile().width,
403
+ height: this._getVideoProfile().height,
404
+ });
405
+ } catch (error: any) {
406
+ this._callFunctionErrorManage(error, 'startLocalPreview');
407
+ }
408
+ }
409
+
410
+ /**
411
+ * 修改本地摄像头预览的 HTML 元素
412
+ * @memberof TRTCCloud
413
+ * @param {HTMLElement | string | null} view 接收本地摄像头采集视频渲染的 HTML 元素
414
+ * @example
415
+ * await trtcCloud.startLocalPreview('preview');
416
+ * await trtcCloud.updateLocalView('newView');
417
+ */
418
+ async updateLocalView(view: HTMLElement | string | null) {
419
+ this._setLocalView(view);
420
+ await this._updateLocalVideo();
421
+ }
422
+
423
+ /**
424
+ * 停止本地摄像头采集和预览<br>
425
+ * 首先停止播放,然后关闭本地流(关闭摄像头和麦克风访问权限)
426
+ * @memberof TRTCCloud
427
+ * @example
428
+ * trtcCloud.stopLocalPreview();
429
+ */
430
+ public async stopLocalPreview() {
431
+ try {
432
+ this._setLocalView(null);
433
+ await this._trtc.stopLocalVideo();
434
+ } catch (error: any) {
435
+ console.warn(`${LOG_PREFIX} stopLocalPreview error: ${error}`);
436
+ this._callFunctionErrorManage(error, 'stopLocalPreview');
437
+ }
438
+ }
439
+ /**
440
+ * 暂停/恢复发布本地的视频流<br>
441
+ * 当屏蔽本地视频后,房间里的其它成员将会收到 [onUserVideoAvailable](https://web.sdk.qcloud.com/trtc/webrtc/trtcCloud/doc/TRTCCallback.html#event:onUserVideoAvailable) 回调通知
442
+ * @param {Boolean} mute - true:屏蔽;false:开启,默认值:false
443
+ * @memberof TRTCCloud
444
+ * @example
445
+ * trtcCloud.muteLocalVideo(true);
446
+ */
447
+ public async muteLocalVideo(mute: boolean = false) {
448
+ try {
449
+ this._setIsVideoPublish(!mute);
450
+ await this._updateLocalVideo();
451
+ } catch (error: any) {
452
+ this._callFunctionErrorManage(error, 'muteLocalVideo');
453
+ }
454
+ }
455
+ /**
456
+ * 显示远端视频或辅流<br>
457
+ * 在收到 SDK 的 [onUserVideoAvailable](https://web.sdk.qcloud.com/trtc/webrtc/trtcCloud/doc/TRTCCallback.html#event:onUserVideoAvailable) 通知时,可以获知该远程用户开启了视频,
458
+ * 此后调用 [startRemoteView](https://web.sdk.qcloud.com/trtc/webrtc/trtcCloud/doc/TRTCCloud.html#startRemoteView) 接口加载该用户的远程画面时,可以用 loading 动画优化加载过程中的等待体验。
459
+ * @param {String} userId - 对方的用户标识
460
+ * @param {HTMLElement} view - 承载预览画面的 DOM
461
+ * @param {TRTCVideoStreamType} streamType - 视频流类型
462
+ * - 高清大画面:TRTCVideoStreamType.TRTCVideoStreamTypeBig
463
+ * - 低清小画面:TRTCVideoStreamType.TRTCVideoStreamTypeSmall
464
+ * - 辅流(屏幕分享):TRTCVideoStreamType.TRTCVideoStreamTypeSub
465
+ * @memberof TRTCCloud
466
+ * @example
467
+ * import { TRTCVideoStreamType } from 'trtc-cloud-js-sdk';
468
+ * const view = document.getElementById('remote-view');
469
+ * await trtcCloud.startRemoteView('denny', view, TRTCVideoStreamType.TRTCVideoStreamTypeBig);
470
+ */
471
+ public async startRemoteView(userId: string, view: HTMLElement, streamType: TRTCVideoStreamType) {
472
+ try {
473
+ this._setRemoteStreamConfig(userId, streamType, { view });
474
+ if (!this._remoteStreamMap.get(`${userId}_${this._getTRTCStreamType(streamType)}`)) {
475
+ return;
476
+ }
477
+ try {
478
+ await this._trtc.startRemoteVideo(this._generateRemoteVideoData(userId, streamType));
479
+ } catch (error: any) {
480
+ // 连续调用两次 startRemoteVideo 失败时应该执行 updateRemoteVideo
481
+ if (error.code === TRTC.ERROR_CODE.OPERATION_ABORT) {
482
+ await this._updateRemoteVideo(userId, streamType);
483
+ return;
484
+ }
485
+ throw error;
486
+ }
487
+ } catch (error: any) {
488
+ this._callFunctionErrorManage(error, 'startRemoteView');
489
+ }
490
+ }
491
+
492
+ /**
493
+ * 修改远端视频渲染的 HTML 元素
494
+ * @param {string} userId 远端视频流的用户 ID
495
+ * @param {HTMLElement | string} view 接受远端视频流渲染的 HTML 元素
496
+ * @param {TRTCVideoStreamType} streamType 视频流类型
497
+ */
498
+ async updateRemoteView(userId: string, view: HTMLElement | string, streamType: TRTCVideoStreamType) {
499
+ this._setRemoteStreamConfig(userId, streamType, { view });
500
+ await this._updateRemoteVideo(userId, streamType);
501
+ }
502
+
503
+ /**
504
+ * 停止显示远端视频画面,同时不再拉取该远端用户的视频数据流<br>
505
+ * 指定要停止观看的 userId 的视频流类型<br>
506
+ * @param {String} userId - userId 指定的远端用户 ID
507
+ * @param {TRTCVideoStreamType} streamType - 视频流类型
508
+ * - 高清大画面:TRTCVideoStreamType.TRTCVideoStreamTypeBig
509
+ * - 低清小画面:TRTCVideoStreamType.TRTCVideoStreamTypeSmall
510
+ * - 辅流(屏幕分享):TRTCVideoStreamType.TRTCVideoStreamTypeSub
511
+ * @memberof TRTCCloud
512
+ * @example
513
+ * import { TRTCVideoStreamType } from 'trtc-cloud-js-sdk';
514
+ * await trtcCloud.stopRemoteView('denny', TRTCVideoStreamType.TRTCVideoStreamTypeBig);
515
+ */
516
+ public async stopRemoteView(userId: string, streamType: TRTCVideoStreamType) {
517
+ try {
518
+ await this._trtc.stopRemoteVideo({
519
+ userId,
520
+ streamType: this._getTRTCStreamType(streamType),
521
+ });
522
+ } catch (error: any) {
523
+ this._callFunctionErrorManage(error, 'stopRemoteView');
524
+ }
525
+ }
526
+ /**
527
+ * 停止显示所有的远端视频画面<br>
528
+ * @memberof TRTCCloud
529
+ * @example
530
+ * trtcCloud.stopAllRemoteView();
531
+ */
532
+ public async stopAllRemoteView() {
533
+ try {
534
+ await this._trtc.stopRemoteVideo({ userId: '*' });
535
+ } catch (error) {
536
+ this._callFunctionErrorManage(error, 'stopAllRemoteView');
537
+ }
538
+ }
539
+ /**
540
+ * 设置视频编码器相关参数<br>
541
+ * 该设置决定了远端用户看到的画面质量(同时也是云端录制出的视频文件的画面质量)<br>
542
+ * @param {TRTCVideoEncParam} params - 视频编码参数
543
+ * @param {TRTCVideoResolution} params.videoResolution - 视频分辨率
544
+ * @param {Number} params.videoFps - 视频采集帧率
545
+ * @param {Number} params.videoBitrate - 视频上行码率
546
+ * @memberof TRTCCloud
547
+ * import { TRTCVideoResolution } from 'trtc-cloud-js-sdk';
548
+ * const params = {
549
+ * videoResolution: TRTCVideoResolution.TRTCVideoResolution_640_480,
550
+ * videoFps: 15,
551
+ * videoBitrate: 900,
552
+ * };
553
+ * trtcCloud.setVideoEncoderParam(params);
554
+ */
555
+ public async setVideoEncoderParam(params: TRTCVideoEncParam) {
556
+ try {
557
+ const videoProfile: IVideoProfile = this._getTRTCVideoProfile('video', params);
558
+ this._setVideoProfile(videoProfile);
559
+ await this._updateLocalVideo();
560
+ await this._updateLocalTestVideo();
561
+ } catch (error: any) {
562
+ this._callFunctionErrorManage(error, 'setVideoEncoderParam');
563
+ }
564
+ }
565
+ /**
566
+ * 设置本地流渲染参数
567
+ * @param {TRTCRenderParams} params 本地流渲染参数
568
+ * @param {TRTCVideoRotation} rotation 旋转角度
569
+ * @param {TRTCVideoFillMode} fillMode 填充模式
570
+ * @param {TRTCVideoMirrorType} mirrorType 画面渲染镜像
571
+ * @examples
572
+ * import { TRTCVideoRotation, TRTCVideoFillMode, TRTCVideoMirrorType } from 'trtc-cloud-js-sdk';
573
+ * const params = {
574
+ * rotation: TRTCVideoRotation.TRTCVideoRotation_0,
575
+ * fillMode: TRTCVideoFillMode.TRTCVideoFillMode_Fill,
576
+ * mirrorType: TRTCVideoMirrorType.TRTCVideoMirrorType_Enable,
577
+ * };
578
+ * trtcCloud.setRemoteRenderParams(params);
579
+ */
580
+ public async setLocalRenderParams(params: TRTCRenderParams) {
581
+ Object.assign(this._localRenderParams, params);
582
+ try {
583
+ const videoPlayOption: IVideoPlayOption = {};
584
+ if (params.mirrorType) {
585
+ videoPlayOption.mirror = this._getTRTCMirror(params.mirrorType);
586
+ }
587
+ if (params.fillMode) {
588
+ videoPlayOption.fillMode = this._getTRTCFillMode(params.fillMode);
589
+ }
590
+ this._setVideoPlayOption(videoPlayOption);
591
+ await this._updateLocalVideo();
592
+ await this._updateLocalTestVideo();
593
+ } catch (error: any) {
594
+ this._callFunctionErrorManage(error, 'setLocalRenderParams');
595
+ }
596
+ }
597
+ /**
598
+ * 设置远端流渲染参数
599
+ * @param {String} userId 流 userId
600
+ * @param {TRTCVideoStreamType} streamType 流类型
601
+ * - 高清大画面:TRTCVideoStreamType.TRTCVideoStreamTypeBig
602
+ * - 低清小画面:TRTCVideoStreamType.TRTCVideoStreamTypeSmall
603
+ * - 辅流(屏幕分享):TRTCVideoStreamType.TRTCVideoStreamTypeSub
604
+ * @param {TRTCRenderParams} params 远端流渲染参数
605
+ * @param {TRTCVideoFillMode} params.fillMode 填充模式
606
+ * @param {TRTCVideoMirrorType} params.mirrorType 画面渲染镜像
607
+ * @memberof TRTCCloud
608
+ * @examples
609
+ * import { TRTCVideoStreamType, TRTCVideoFillMode, TRTCVideoMirrorType } from 'trtc-cloud-js-sdk';
610
+ * const params = {
611
+ * fillMode: TRTCVideoFillMode.TRTCVideoFillMode_Fill,
612
+ * mirrorType: TRTCVideoMirrorType.TRTCVideoMirrorType_Enable,
613
+ * };
614
+ * await trtcCloud.setRemoteRenderParams('denny', TRTCVideoStreamType.TRTCVideoStreamTypeBig, params)
615
+ */
616
+ public async setRemoteRenderParams(userId: string, streamType: TRTCVideoStreamType, params: TRTCRenderParams) {
617
+ try {
618
+ this._setRemoteStreamConfig(userId, streamType, params);
619
+
620
+ await this._updateRemoteVideo(userId, streamType);
621
+ } catch (error: any) {
622
+ this._callFunctionErrorManage(error, 'setRemoteRenderParams');
623
+ }
624
+ }
625
+ // ==============================【 Audio 】=============================
626
+
627
+ private async _updateLocalAudio() {
628
+ try {
629
+ await this._trtc.updateLocalAudio(this._generateLocalAudioData());
630
+ } catch (error: any) {
631
+ // 当没有 startLocalAudio 的时候,调用 updateLocalAudio 会报错,此时忽略该错误不做处理
632
+ if (error.code === TRTC.ERROR_CODE.OPERATION_ABORT) {
633
+ return;
634
+ }
635
+ throw error;
636
+ }
637
+ }
638
+
639
+ /**
640
+ * 开启本地音频的采集和上行, 并设置音频质量<br>
641
+ * 该函数会启动麦克风采集,并将音频数据传输给房间里的其他用户。 SDK 不会默认开启本地音频采集和上行,您需要调用该函数开启,否则房间里的其他用户将无法听到您的声音<br>
642
+ * 主播端的音质越高,观众端的听感越好,但传输所依赖的带宽也就越高,在带宽有限的场景下也更容易出现卡顿<br>
643
+ * **Note:**
644
+ * 1. 音质参数 TRTCAudioQuality 不同于 [Electron startLocalAudio](https://web.sdk.qcloud.com/trtc/electron/doc/zh-cn/trtc_electron_sdk/TRTCCloud.html#startLocalAudio)
645
+ * @param {TRTCAudioQuality} quality 声音音质
646
+ * - TRTCAudioQualityDefault, 样率:48k;单声道;码率:40kbps,默认使用 TRTCAudioQualityDefault
647
+ * - TRTCAudioQualitySpeech, 采样率:48k;单声道;码率:128kbps
648
+ * - TRTCAudioQualityMusic, 采样率:48k;双声道;码率:192kbps
649
+ * @memberof TRTCCloud
650
+ * @example
651
+ * import { TRTCAudioQuality } from 'trtc-cloud-js-sdk';
652
+ * trtcCloud.startLocalAudio(TRTCAudioQuality.TRTCAudioQualityDefault);
653
+ */
654
+ public async startLocalAudio(quality: TRTCAudioQuality = TRTCAudioQuality.TRTCAudioQualityDefault) {
655
+ try {
656
+ this._setAudioProfile(audioQualityMap[quality]);
657
+ try {
658
+ await this._trtc.startLocalAudio(this._generateLocalAudioData());
659
+ } catch (error: any) {
660
+ if (error.code === TRTC.ERROR_CODE.OPERATION_ABORT) {
661
+ // 当重复 start 的时候报错,此时执行 update
662
+ await this._updateLocalAudio();
663
+ return;
664
+ }
665
+ throw error;
666
+ }
667
+ // 抛出 onMicDidReady 事件,ref: https://tapd.woa.com/TerWebG/prong/stories/view/1020396022877875239
668
+ this.emit('onMicDidReady');
669
+ } catch (error: any) {
670
+ console.warn(`${LOG_PREFIX} startLocalAudio error: ${error}`);
671
+ this._callFunctionErrorManage(error, 'startLocalAudio');
672
+ }
673
+ }
674
+ /**
675
+ * 关闭本地音频的采集和上行<br>
676
+ * 当关闭本地音频的采集和上行,房间里的其它成员会收到 [onUserAudioAvailable](https://web.sdk.qcloud.com/trtc/webrtc/trtcCloud/doc/TRTCCallback.html#event:onUserAudioAvailable) 回调通知。<br>
677
+ * @memberof TRTCCloud
678
+ * @example
679
+ * await trtcCloud.stopLocalAudio();
680
+ */
681
+ public async stopLocalAudio() {
682
+ try {
683
+ await this._trtc.stopLocalAudio();
684
+ } catch (error: any) {
685
+ this._callFunctionErrorManage(error, 'stopLocalAudio');
686
+ }
687
+ }
688
+ /**
689
+ * 静音本地的音频<br>
690
+ * 当静音本地音频后,房间里的其它成员会收到 [onUserAudioAvailable](https://web.sdk.qcloud.com/trtc/webrtc/trtcCloud/doc/TRTCCallback.html#event:onUserAudioAvailable) 回调通知。
691
+ * 与 [stopLocalAudio](https://web.sdk.qcloud.com/trtc/webrtc/trtcCloud/doc/TRTCCloud.html#stopLocalAudio) 不同之处在于,[muteLocalAudio](https://web.sdk.qcloud.com/trtc/webrtc/trtcCloud/doc/TRTCCloud.html#muteLocalAudio)
692
+ * 并不会停止发送音视频数据,而是会继续发送码率极低的静音包。
693
+ * 在对录制质量要求很高的场景中,选择 [muteLocalAudio](https://web.sdk.qcloud.com/trtc/webrtc/trtcCloud/doc/TRTCCloud.html#muteLocalAudio) 是更好的选择,能录制出兼容性更好的 MP4 文件。
694
+ * 这是由于 MP4 等视频文件格式,对于音频的连续性是要求很高的,简单粗暴地 [stopLocalAudio](https://web.sdk.qcloud.com/trtc/webrtc/trtcCloud/doc/TRTCCloud.html#stopLocalAudio) 会导致录制出的 MP4 不易播放。<br>
695
+ * @param {Boolean} mute - true:屏蔽;false:开启,默认值:false
696
+ * @memberof TRTCCloud
697
+ * @example
698
+ * await trtcCloud.muteLocalAudio(true);
699
+ */
700
+ public async muteLocalAudio(mute: boolean = false) {
701
+ try {
702
+ this._setIsAudioPublish(!mute);
703
+ await this._updateLocalAudio();
704
+ } catch (error: any) {
705
+ this._callFunctionErrorManage(error, 'muteLocalAudio');
706
+ }
707
+ }
708
+ /**
709
+ * 静音掉某个用户的声音,同时不再拉取该远端用户的音频数据流<br>
710
+ * @param {String} userId - 用户 ID
711
+ * @param {Boolean} mute - true:静音;false:非静音
712
+ * @memberof TRTCCloud
713
+ * @example
714
+ * await trtcCloud.muteRemoteAudio('denny', true);
715
+ */
716
+ public async muteRemoteAudio(userId: string, mute: boolean = false) {
717
+ try {
718
+ if (!userId) {
719
+ this._emitError(ParametersError);
720
+ // this._log.error(`(muteRemoteVideoStream) failed - ${ParametersError.message}: userId = ${userId}`);
721
+ return;
722
+ }
723
+ await this._trtc.muteRemoteAudio(userId, mute);
724
+ } catch (error: any) {
725
+ this._callFunctionErrorManage(error, 'muteRemoteAudio');
726
+ }
727
+ }
728
+ /**
729
+ * 静音掉远端所有用户的声音,同时不再拉取远端用户的音频数据流<br>
730
+ * @param {Boolean} mute - true:静音;false:非静音
731
+ * @memberof TRTCCloud
732
+ * @example
733
+ * trtcCloud.muteAllRemoteAudio(true);
734
+ */
735
+ public muteAllRemoteAudio(mute: boolean): void {
736
+ try {
737
+ this._trtc.muteRemoteAudio('*', mute);
738
+ } catch (error: any) {
739
+ this._callFunctionErrorManage(error, 'muteAllRemoteAudio');
740
+ }
741
+ }
742
+ /**
743
+ * 设置某个远程用户的播放音量<br>
744
+ * @param {String} userId 远程用户 ID
745
+ * @param {Number} volume 音量大小,100为原始音量,范围是:[0 ~ 100],默认值为100
746
+ * @memberof TRTCCloud
747
+ * @example
748
+ * trtcCloud.setRemoteAudioVolume('denny', 80);
749
+ */
750
+ public setRemoteAudioVolume(userId: string, volume: number) {
751
+ try {
752
+ this._trtc.setRemoteAudioVolume(userId, volume);
753
+ } catch (error: any) {
754
+ this._callFunctionErrorManage(error, 'setRemoteAudioVolume');
755
+ }
756
+ }
757
+ /**
758
+ * 开启或关闭音量大小回调<br>
759
+ * 开启此功能后,SDK 会在 [onUserVoiceVolume](https://web.sdk.qcloud.com/trtc/webrtc/trtcCloud/doc/TRTCCallback.html#event:onUserVoiceVolume) 中反馈对每一路声音音量大小值的评估。<br>
760
+ * **Note:**
761
+ * - 如需打开此功能,请在 {@link TRTCCloud#startLocalAudio startLocalAudio()} 之前调用才可以生效。
762
+ * @param {Number} interval - 设置 onUserVoiceVolume 回调的触发间隔,单位为ms,最小间隔为100ms,如果小于等于0则会关闭回调,建议设置为300ms
763
+ * @memberof TRTCCloud
764
+ * @example
765
+ * trtcCloud.enableAudioVolumeEvaluation(300);
766
+ */
767
+ enableAudioVolumeEvaluation(interval: number): void {
768
+ try {
769
+ this._trtc.enableAudioVolumeEvaluation(interval);
770
+ } catch (error: any) {
771
+ this._callFunctionErrorManage(error, 'enableAudioVolumeEvaluation');
772
+ }
773
+ }
774
+ // ==============================【屏幕分享】==============================
775
+ /**
776
+ * 开始屏幕分享<br>
777
+ * @param {HTMLElement | null} view - 承载预览画面的 DOM
778
+ * **Note:**
779
+ * - 如果不想本地预览,需要传入 null
780
+ * - 本地预览自己的屏幕分享时,不能全屏
781
+ * @param {TRTCVideoStreamType} type 屏幕分享使用的线路,默认使用辅路。
782
+ * @param {Object} params 屏幕分享参数
783
+ * @param {TRTCVideoResolution} params.videoResolution 屏幕分享分辨率
784
+ * @param {Number} params.videoFps 帧率
785
+ * @param {Number} params.videoBitrate 码率
786
+ * @param {Boolean=} params.screenAudio 屏幕分享是否采集系统音频, 默认 false 不采集
787
+ * **Note:**
788
+ * - 采集系统声音只支持 Chrome M74+ ,在 Windows 和 Chrome OS 上,可以捕获整个系统的音频
789
+ * 在 Linux 和 Mac 上,只能捕获选项卡的音频。其它 Chrome 版本、其它系统、其它浏览器均不支持。
790
+ * @returns {Promise}
791
+ * @memberof TRTCCloud
792
+ * @example
793
+ * const view = document.getElementById('local-share-view');
794
+ * const params = {
795
+ * screenAudio: true,
796
+ * };
797
+ * await trtcCloud.startScreenShare(view, TRTCVideoStreamType.TRTCVideoStreamTypeSub, params);
798
+ */
799
+ public async startScreenShare(view: HTMLElement | null, type, params) {
800
+ try {
801
+ // 仅支持辅流推屏幕分享
802
+ const { screenAudio = false } = params || {};
803
+
804
+ const screenShareVideoProfile: IVideoProfile = this._getTRTCVideoProfile('screen', params);
805
+
806
+ this._setScreenShareParams({
807
+ view,
808
+ profile: screenShareVideoProfile,
809
+ systemAudio: screenAudio,
810
+ });
811
+ await this._trtc.startScreenShare(this._getScreenShareParams());
812
+ this._isSharingScreen = true;
813
+ this.emit('onFirstVideoFrame', {
814
+ userId: '',
815
+ streamType: TRTCVideoStreamType.TRTCVideoStreamTypeSub,
816
+ width: screenShareVideoProfile.width,
817
+ height: screenShareVideoProfile.height,
818
+ });
819
+ } catch (error: any) {
820
+ if (error.code === TRTC.ERROR_CODE.OPERATION_ABORT) {
821
+ return;
822
+ }
823
+ console.error('Start share screen error:', error, error.code, error.name, error.message);
824
+ throw error;
825
+ }
826
+ }
827
+ /**
828
+ * 设置屏幕分享的视频编码参数<br>
829
+ * **Note:**
830
+ * - setSubStreamEncoderParam 需在调用 {@link TRTCCloud#startScreenShare startScreenShare()} 进行屏幕分享之前调用
831
+ * - 默认分辨率:1920*1080,视频采集帧率:15fps,视频码率:1500kps
832
+ * @memberof TRTCCloud
833
+ * @param {TRTCVideoEncParam} params 屏幕分享视频编码参数
834
+ * @param {TRTCVideoResolution} params.videoResolution 视频分辨率
835
+ * @param {Number} params.videoFps 视频采集帧率
836
+ * @param {Number} params.videoBitrate 视频码率
837
+ * @example
838
+ * import { TRTCVideoResolution } from 'trtc-cloud-js-sdk';
839
+ * const params = {
840
+ * videoResolution: TRTCVideoResolution.TRTCVideoResolution_640_480,
841
+ * videoFps: 15,
842
+ * videoBitrate: 1500,
843
+ * };
844
+ * await trtcCloud.setSubStreamEncoderParam(params);
845
+ */
846
+ public async setSubStreamEncoderParam(params: TRTCVideoEncParam) {
847
+ try {
848
+ const screenShareVideoProfile: IVideoProfile = this._getTRTCVideoProfile('screen', params);
849
+ this._setScreenShareParams({
850
+ profile: screenShareVideoProfile,
851
+ });
852
+ await this._trtc.updateScreenShare(this._getScreenShareParams());
853
+ } catch (error: any) {
854
+ this._callFunctionErrorManage(error, 'setSubStreamEncoderParam');
855
+ }
856
+ }
857
+ /**
858
+ * 停止屏幕分享
859
+ * @memberof TRTCCloud
860
+ * @example
861
+ * await trtcCloud.stopScreenShare();
862
+ */
863
+ public async stopScreenShare(): Promise<void> {
864
+ try {
865
+ await this._trtc.stopScreenShare();
866
+ this._isSharingScreen = false;
867
+ this.emit('onScreenCaptureStopped', { reason: 0 });
868
+ } catch (error: any) {
869
+ this._callFunctionErrorManage(error, 'stopScreenShare');
870
+ }
871
+ }
872
+
873
+ // ============ TUIRoom 访问时需返回 NotSupportError, 需保留 =========
874
+ startScreenCapture() {
875
+ this.emitError(NotSupportError);
876
+ }
877
+ pauseScreenCapture() {
878
+ this.emitError(NotSupportError);
879
+ }
880
+ resumeScreenCapture() {
881
+ this.emitError(NotSupportError);
882
+ }
883
+ getScreenCaptureSources() {
884
+ this.emitError(NotSupportError);
885
+ }
886
+ selectScreenCaptureTarget() {
887
+ this.emitError(NotSupportError);
888
+ }
889
+ // ==============================【摄像头设备】==============================
890
+ /**
891
+ * 获取摄像头设备列表<br>
892
+ * @returns {Promise<TRTCDeviceInfo[]>} 摄像头管理器列表
893
+ * @memberof TRTCCloud
894
+ * @example
895
+ * var cameralist = await trtcCloud.getCameraDevicesList();
896
+ * for (i = 0; i < cameralist.length; i++) {
897
+ * var camera = cameralist[i];
898
+ * console.info("camera deviceName: " + camera.deviceName + " deviceId:" + camera.deviceId);
899
+ * }
900
+ */
901
+ public async getCameraDevicesList(): Promise<TRTCDeviceInfo[]> {
902
+ try {
903
+ let cameraList = await TRTC.getCameraList();
904
+ cameraList = cameraList.map(item => ({
905
+ ...item,
906
+ deviceName: item.label, // 就是 deviceName
907
+ }));
908
+ this._cameraList = cameraList;
909
+ this._currentCamera = this.getDefaultDeviceInfo(cameraList);
910
+ this._currentCameraId = this._currentCamera.deviceId;
911
+ return Promise.resolve(cameraList);
912
+ } catch (error: any) {
913
+ this._callFunctionErrorManage(error, 'getCameraDevicesList');
914
+ return Promise.resolve([]);
915
+ }
916
+ }
917
+ /**
918
+ * 设置要使用的摄像头<br>
919
+ * @param {String} deviceId - 从 getCameraDevicesList 中得到的设备 ID
920
+ * @returns {Promise}
921
+ * @memberof TRTCCloud
922
+ */
923
+ public async setCurrentCameraDevice(deviceId: string): Promise<boolean> {
924
+ try {
925
+ if (!deviceId) return false;
926
+ this._currentCameraId = deviceId;
927
+ this._currentCamera = this._cameraList.filter(item => item.deviceId === deviceId);
928
+ await this._updateLocalVideo();
929
+ await this._updateLocalTestVideo();
930
+ return true;
931
+ } catch (error: any) {
932
+ this._callFunctionErrorManage(error, 'setCurrentCameraDevice');
933
+ return false;
934
+ }
935
+ }
936
+ /**
937
+ * 获取当前使用的摄像头<br>
938
+ * @memberof TRTCCloud
939
+ * @returns {TRTCDeviceInfo} 设备信息,能获取设备 ID 和设备名称
940
+ */
941
+ public getCurrentCameraDevice(): TRTCDeviceInfo { // TODO: 待确认
942
+ // return (this.cameraList || []).filter((obj: any) => obj.deviceId === this.currentCameraId)[0] || {};
943
+ return new TRTCDeviceInfo();
944
+ }
945
+
946
+ /**
947
+ * 判断是否为前置摄像头(仅适用于移动端)
948
+ * @returns {boolean}
949
+ */
950
+ public isFrontCamera() {
951
+ return this._getIsFrontCamera();
952
+ }
953
+
954
+ /**
955
+ * 切换前置或者后置摄像头(仅适用于移动端)
956
+ * @param { boolean } isFrontCamera 是否为前置摄像头
957
+ *
958
+ * @example
959
+ * // 移动端使用前置摄像头预览本地画面
960
+ * await trtcCloud.startLocalPreview(true, view);
961
+ * // 切换使用后置摄像头预览本地画面
962
+ * await trtcCloud.switchCamera(false);
963
+ */
964
+ public async switchCamera(isFrontCamera: boolean) {
965
+ this._setIsFrontCamera(isFrontCamera);
966
+
967
+ const videoPlayOption = this._getVideoPlayOption();
968
+ videoPlayOption.mirror = this._getTRTCMirror(this._localRenderParams.mirrorType);
969
+ this._setVideoPlayOption(videoPlayOption);
970
+
971
+ await this._updateLocalVideo();
972
+ }
973
+
974
+ // ==============================【麦克风设备】==============================
975
+ /**
976
+ * 获取麦克风设备列表<br>
977
+ * @returns {Promise<TRTCDeviceInfo[]>} 麦克风管理器列表
978
+ * @memberof TRTCCloud
979
+ * @example
980
+ * var micList = await trtcCloud.getMicDevicesList();
981
+ * for (i = 0; i < micList.length; i++) {
982
+ * var mic = micList[i];
983
+ * console.info("mic deviceName: " + mic.deviceName + " deviceId:" + mic.deviceId);
984
+ * }
985
+ */
986
+ public async getMicDevicesList(): Promise<TRTCDeviceInfo[]> {
987
+ try {
988
+ let micList = await TRTC.getMicrophoneList();
989
+ micList = micList.map(item => ({
990
+ ...item,
991
+ deviceName: item.label, // 就是 deviceName
992
+ }));
993
+ this._microphoneList = micList;
994
+ this._currentMicrophone = this.getDefaultDeviceInfo(micList);
995
+ this._currentMicrophoneId = this._currentMicrophone.deviceId;
996
+ return Promise.resolve(micList);
997
+ } catch (error: any) {
998
+ this._callFunctionErrorManage(error, 'getMicDevicesList');
999
+ return Promise.resolve([]);
1000
+ }
1001
+ }
1002
+ /**
1003
+ * 设置要使用的麦克风<br>
1004
+ * 选择指定的麦克风作为录音设备,不调用该接口时,默认选择索引为 0 的麦克风<br>
1005
+ * @param {String} micId - 从 getMicDevicesList 中得到的设备 ID
1006
+ * @memberof TRTCCloud
1007
+ * @returns {Promise}
1008
+ */
1009
+ public async setCurrentMicDevice(micId: string): Promise<boolean> {
1010
+ try {
1011
+ if (!micId) return false;
1012
+ this._setCurrentMicrophoneId(micId);
1013
+ this._currentMicrophone = this._microphoneList.filter(item => item.deviceId === micId);
1014
+ await this._trtc.updateLocalAudio(this._generateLocalAudioData());
1015
+ return true;
1016
+ } catch (error: any) {
1017
+ this._callFunctionErrorManage(error, 'setCurrentMicDevice');
1018
+ return false;
1019
+ }
1020
+ }
1021
+ /**
1022
+ * 获取当前选择的麦克风<br>
1023
+ * @memberof TRTCCloud
1024
+ * @returns {TRTCDeviceInfo} 设备信息,能获取设备 ID 和设备名称
1025
+ */
1026
+ public getCurrentMicDevice(): TRTCDeviceInfo {
1027
+ return new TRTCDeviceInfo();
1028
+ }
1029
+ // ==============================【麦克风设备】==============================
1030
+ /**
1031
+ * 获取扬声器设备列表<br>
1032
+ * - 出于安全的考虑,在用户未授权摄像头或麦克风访问权限前,deviceId 字段可能都是空的。因此建议在用户授权访问后 再调用该接口获取设备详情
1033
+ * - 移动端不支持获取扬声器列表
1034
+ * @returns {Promise<TRTCDeviceInfo[]>} 扬声器管理器列表
1035
+ * @memberof TRTCCloud
1036
+ * @example
1037
+ * var speakerList = await trtcCloud.getSpeakerDevicesList();
1038
+ * for (i = 0; i < speakerList.length; i++) {
1039
+ * var speaker = speakerList[i];
1040
+ * console.info("mic deviceName: " + speaker.deviceName + " deviceId:" + speaker.deviceId);
1041
+ * }
1042
+ */
1043
+ public async getSpeakerDevicesList(): Promise<TRTCDeviceInfo[]> {
1044
+ try {
1045
+ let speakerList = await TRTC.getSpeakerList();
1046
+ speakerList = speakerList.map((item: TRTCDeviceInfo) => ({
1047
+ ...item,
1048
+ deviceName: item.label, // 就是 deviceName
1049
+ }));
1050
+ this._speakerList = speakerList;
1051
+ this._currentSpeaker = this.getDefaultDeviceInfo(speakerList);
1052
+ this._currentSpeakerId = this._currentSpeaker.deviceId;
1053
+ return Promise.resolve(speakerList);
1054
+ } catch (error: any) {
1055
+ this._callFunctionErrorManage(error, 'getSpeakerDevicesList');
1056
+ return Promise.resolve([]);
1057
+ }
1058
+ }
1059
+ /**
1060
+ * 设置要使用的扬声器<br>
1061
+ * - 移动端不支持设置扬声器。
1062
+ * @param {String} speakerId - 从 getSpeakerDevicesList 中得到的设备 ID
1063
+ * @returns {Promise}
1064
+ * @memberof TRTCCloud
1065
+ */
1066
+ public async setCurrentSpeakerDevice(speakerId: string): Promise<boolean> {
1067
+ try {
1068
+ if (!speakerId) return false;
1069
+ this._setCurrentSpeakerId(speakerId);
1070
+ this._currentSpeaker = this._speakerList.filter(item => item.deviceId === speakerId);
1071
+ await TRTC.setCurrentSpeaker(speakerId);
1072
+ return true;
1073
+ } catch (error: any) {
1074
+ this._callFunctionErrorManage(error, 'setCurrentSpeakerDevice');
1075
+ return false;
1076
+ }
1077
+ }
1078
+ /**
1079
+ * 获取当前的扬声器设备<br>
1080
+ * @memberof TRTCCloud
1081
+ * @returns {TRTCDeviceInfo} 设备信息,能获取设备 ID 和设备名称
1082
+ */
1083
+ public getCurrentSpeakerDevice(): TRTCDeviceInfo {
1084
+ return new TRTCDeviceInfo();
1085
+ }
1086
+
1087
+ /**
1088
+ * 摄像头测试接口
1089
+ * @param {HTMLElement} view 测试接口播放区域
1090
+ */
1091
+ async startCameraDeviceTest(view: HTMLElement) {
1092
+ this._log.info('(startCameraDeviceTest) start - start test camera Device', view);
1093
+ if (!view) {
1094
+ return;
1095
+ }
1096
+ this._setLocalTestView(view);
1097
+ await this._testTrtc.startLocalVideo(this._generateLocalTestVideoData());
1098
+ }
1099
+
1100
+ /**
1101
+ * 停止摄像头测试接口
1102
+ */
1103
+ async stopCameraDeviceTest() {
1104
+ this._setLocalTestView(null);
1105
+ await this._testTrtc.stopLocalVideo();
1106
+ }
1107
+
1108
+ /**
1109
+ * 麦克风测试接口
1110
+ * @param {number} interval 麦克风音量回调间隔
1111
+ */
1112
+ async startMicDeviceTest(interval: number) {
1113
+ this._log.info('(startMicDeviceTest) start - start test mic Device', interval);
1114
+
1115
+ await this._testTrtc.startLocalAudio({
1116
+ publish: false,
1117
+ option: {
1118
+ microphoneId: this._currentMicrophoneId,
1119
+ profile: audioQualityMap[TRTCAudioQuality.TRTCAudioQualityDefault],
1120
+ },
1121
+ });
1122
+
1123
+ this._testTrtc.on(TRTC.EVENT.AUDIO_VOLUME, (event: any) => {
1124
+ event?.result.forEach(({ userId, volume }) => {
1125
+ const isMe = userId === '';
1126
+ if (isMe) {
1127
+ this.emit('onTestMicVolume', volume);
1128
+ }
1129
+ });
1130
+ });
1131
+
1132
+ await this._testTrtc.enableAudioVolumeEvaluation(interval);
1133
+ }
1134
+
1135
+ stopMicDeviceTest() {
1136
+ this._log.info('(stopMicDeviceTest) stop - stop test mic Device');
1137
+
1138
+ this._testTrtc.stopLocalAudio();
1139
+ }
1140
+
1141
+ // ///////////////////////////////////////////////////////////////////////////////
1142
+ //
1143
+ // 内部方法
1144
+ //
1145
+ // ///////////////////////////////////////////////////////////////////////////////
1146
+ // 解除所有事件绑定
1147
+ private _unbindEvents() {
1148
+ }
1149
+ // 解绑 client 事件, 释放 client 资源
1150
+ private _destroy() {
1151
+ this._unbindEvents();
1152
+ }
1153
+ // 抛出 onError, 上层应用监听该事件
1154
+ private _emitError(error: IError): void {
1155
+ if (!error || !this.emit) {
1156
+ return;
1157
+ }
1158
+ this.emit(NAME.ON_ERROR, error.code, error.message);
1159
+ }
1160
+ /**
1161
+ * 调用方法失败处理
1162
+ * @param {Any} error Error 实例或 trtc-js-sdk 中定义的 RtcError 实例
1163
+ * @param {Function} functionName 函数名称, 调用函数的名称用以记录出错的位置
1164
+ */
1165
+ private _callFunctionErrorManage(error: any, functionName: string): void {
1166
+ if (error && error.code === TRTC.ERROR_CODE.OPERATION_ABORT) {
1167
+ return;
1168
+ }
1169
+ if (error && error.getCode && error.getCode()) {
1170
+ this.emit(NAME.ON_ERROR, error.getCode(), error.message);
1171
+ this._log.error(`(${functionName}) failed - ${error.message}`);
1172
+ } else {
1173
+ this.emit(NAME.ON_ERROR, jsExecuteError.code, error.message);
1174
+ this._log.error(`(${functionName}) failed - ${error.message}`);
1175
+ }
1176
+ }
1177
+ /**
1178
+ * 解析 TRTCVideoResolution 将其转成 width、height。例如:TRTCVideoResolution_120_120 得到 { width: 120, height: 120 }
1179
+ * @param {TRTCVideoResolution} videoResolution 分辨率
1180
+ * @returns {Object} 返回值
1181
+ */
1182
+ private _getTRTCResolution(videoResolution: TRTCVideoResolution): { width, height } {
1183
+ const TRTCVideoResolutionString = TRTCVideoResolution[videoResolution];
1184
+ const videoResolutionList = (TRTCVideoResolutionString || '').split('_');
1185
+ const width = videoResolutionList.length > 1 && videoResolutionList[1];
1186
+ const height = videoResolutionList.length > 2 && videoResolutionList[2];
1187
+ return { width: +width, height: +height };
1188
+ }
1189
+
1190
+ // 根据用户传入的参数获取 TRTC videoProfile 和 screenProfile
1191
+ private _getTRTCVideoProfile(type: 'video' | 'screen', params): IVideoProfile {
1192
+ const { videoResolution, videoFps, videoBitrate } = params;
1193
+ const videoProfile: IVideoProfile = type === 'screen' ? this._defaultScreenProfile : this._defaultVideoProfile;
1194
+ if (videoResolution) {
1195
+ const resolution = this._getTRTCResolution(videoResolution);
1196
+ videoProfile.width = resolution.width;
1197
+ videoProfile.height = resolution.height;
1198
+ }
1199
+ if (videoFps) {
1200
+ videoProfile.frameRate = videoFps;
1201
+ }
1202
+ if (videoBitrate) {
1203
+ videoProfile.bitrate = videoBitrate;
1204
+ }
1205
+ return videoProfile;
1206
+ }
1207
+
1208
+ // 根据传入的 streamType 获取对应的 TRTC streamType
1209
+ private _getTRTCStreamType(streamType: TRTCVideoStreamType) {
1210
+ const relationObj = {
1211
+ [TRTCVideoStreamType.TRTCVideoStreamTypeBig]: TRTC.TYPE.STREAM_TYPE_MAIN,
1212
+ [TRTCVideoStreamType.TRTCVideoStreamTypeSmall]: TRTC.TYPE.STREAM_TYPE_MAIN,
1213
+ [TRTCVideoStreamType.TRTCVideoStreamTypeSub]: TRTC.TYPE.STREAM_TYPE_SUB,
1214
+ };
1215
+ return relationObj[streamType];
1216
+ }
1217
+ // 根据传入的 filleMode 获取对应的 TRTC fillMode
1218
+ private _getTRTCFillMode(fillMode: TRTCVideoFillMode) {
1219
+ const relationObj = {
1220
+ [TRTCVideoFillMode.TRTCVideoFillMode_Fill]: VideoFillMode.COVER,
1221
+ [TRTCVideoFillMode.TRTCVideoFillMode_Fit]: VideoFillMode.CONTAIN,
1222
+ };
1223
+ return relationObj[fillMode];
1224
+ }
1225
+ // 根据传入的 mirror 获取对应的 TRTC mirror
1226
+ private _getTRTCMirror(mirror: TRTCVideoMirrorType) {
1227
+ if (mirror === TRTCVideoMirrorType.TRTCVideoMirrorType_Auto) {
1228
+ if (this._getIsMobile()) {
1229
+ return this._getIsFrontCamera();
1230
+ }
1231
+ return true;
1232
+ }
1233
+ const relationObj = {
1234
+ [TRTCVideoMirrorType.TRTCVideoMirrorType_Enable]: true,
1235
+ [TRTCVideoMirrorType.TRTCVideoMirrorType_Disable]: false,
1236
+ };
1237
+ return relationObj[mirror];
1238
+ }
1239
+ private _getTRTCCloudDeviceType(mediaType: 'camera' | 'microphone' | 'speaker') {
1240
+ const deviceTypeRelationObj = {
1241
+ camera: TRTCDeviceType.TRTCDeviceTypeCamera,
1242
+ microphone: TRTCDeviceType.TRTCDeviceTypeMic,
1243
+ speaker: TRTCDeviceType.TRTCDeviceTypeSpeaker,
1244
+ };
1245
+ return deviceTypeRelationObj[mediaType];
1246
+ }
1247
+ private _getTRTCCloudDeviceState(state: 'add' | 'remove' | 'active') {
1248
+ const deviceStateRelationObj = {
1249
+ add: TRTCDeviceState.TRTCDeviceStateAdd,
1250
+ remove: TRTCDeviceState.TRTCDeviceStateRemove,
1251
+ active: TRTCDeviceState.TRTCDeviceStateActive,
1252
+ };
1253
+ return deviceStateRelationObj[state];
1254
+ }
1255
+ // 返回调用 trtc.updateLocalVideo() 方法所需的数据
1256
+ private _generateLocalVideoData() {
1257
+ const localVideoData = {
1258
+ view: this._getLocalView(),
1259
+ publish: this._getIsVideoPublish(),
1260
+ option: {
1261
+ profile: this._getVideoProfile(),
1262
+ ...this._getVideoPlayOption(),
1263
+ },
1264
+ };
1265
+ if (this._getIsMobile()) {
1266
+ localVideoData && Object.assign(localVideoData.option, { useFrontCamera: this._getIsFrontCamera() });
1267
+ } else {
1268
+ localVideoData && Object.assign(localVideoData.option, { cameraId: this._getCurrentCameraId() });
1269
+ }
1270
+ return localVideoData;
1271
+ }
1272
+ // 返回测试 trtc 更新本地流所需的数据
1273
+ private _generateLocalTestVideoData() {
1274
+ return {
1275
+ view: this._getLocalTestView(),
1276
+ publish: false,
1277
+ option: {
1278
+ cameraId: this._getCurrentCameraId(),
1279
+ profile: this._getVideoProfile(),
1280
+ ...this._getVideoPlayOption(),
1281
+ },
1282
+ };
1283
+ }
1284
+ // 返回调用 trtc.updateLocalAudio() 方法所需的数据
1285
+ private _generateLocalAudioData() {
1286
+ return {
1287
+ publish: this._getIsAudioPublish(),
1288
+ option: {
1289
+ microphoneId: this._getCurrentMicrophoneId(),
1290
+ profile: this._getAudioProfile(),
1291
+ },
1292
+ };
1293
+ }
1294
+ // 返回远端流的配置数据
1295
+ private _generateRemoteVideoData(userId: string, streamType: TRTCVideoStreamType) {
1296
+ return this._remoteStreamConfig.get(`${userId}_${this._getTRTCStreamType(streamType)}`);
1297
+ }
1298
+
1299
+ // 添加 webRTC 事件监听
1300
+ private _addTRTCEvents() {
1301
+ this._trtc.on(TRTC.EVENT.ERROR, (error: any) => {
1302
+ error && this.emit('onError', error?.getCode?.getCode(), error.message);
1303
+ });
1304
+ this._trtc.on(TRTC.EVENT.REMOTE_USER_ENTER, (event: any) => {
1305
+ event?.userId && this.emit('onRemoteUserEnterRoom', event.userId);
1306
+ });
1307
+ this._trtc.on(TRTC.EVENT.REMOTE_USER_EXIT, (event: any) => {
1308
+ event?.userId && this.emit('onRemoteUserLeaveRoom', event.userId);
1309
+ });
1310
+ this._trtc.on(TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, (event: any) => {
1311
+ event?.userId && this.emit('onUserAudioAvailable', event.userId, true);
1312
+ });
1313
+ this._trtc.on(TRTC.EVENT.REMOTE_AUDIO_UNAVAILABLE, (event: any) => {
1314
+ event?.userId && this.emit('onUserAudioAvailable', event.userId, false);
1315
+ });
1316
+ this._trtc.on(TRTC.EVENT.REMOTE_VIDEO_AVAILABLE, (event: any) => {
1317
+ this._emitVideoAvailable(event, true);
1318
+ });
1319
+ this._trtc.on(TRTC.EVENT.REMOTE_VIDEO_UNAVAILABLE, (event: any) => {
1320
+ this._emitVideoAvailable(event, false);
1321
+ });
1322
+ this._trtc.on(TRTC.EVENT.AUDIO_VOLUME, (event: any) => {
1323
+ event?.result && this.emit('onUserVoiceVolume', event?.result, (event?.result || []).length);
1324
+ });
1325
+ this._trtc.on(TRTC.EVENT.NETWORK_QUALITY, (event: any) => {
1326
+ const { uplinkNetworkQuality, downlinkNetworkQuality } = event;
1327
+ const userId = this.client ? this.client.getUserId() : '';
1328
+ const TRTCQualityRelation = [
1329
+ TRTCQuality.TRTCQuality_Unknown,
1330
+ TRTCQuality.TRTCQuality_Excellent,
1331
+ TRTCQuality.TRTCQuality_Good,
1332
+ TRTCQuality.TRTCQuality_Poor,
1333
+ TRTCQuality.TRTCQuality_Bad,
1334
+ TRTCQuality.TRTCQuality_Vbad,
1335
+ TRTCQuality.TRTCQuality_Down,
1336
+ ];
1337
+ const localQuality = new TRTCQualityInfo(userId, TRTCQualityRelation[uplinkNetworkQuality]);
1338
+ const remoteQuality = new TRTCQualityInfo('remote', TRTCQualityRelation[downlinkNetworkQuality]);
1339
+ this.emit('onNetworkQuality', localQuality, [remoteQuality]);
1340
+ });
1341
+ this._trtc.on(TRTC.EVENT.SCREEN_SHARE_STOPPED, () => {
1342
+ this.emit('onScreenCaptureStopped', { reason: 0 });
1343
+ this._isSharingScreen = false;
1344
+ });
1345
+ this._trtc.on(TRTC.EVENT.PUBLISH_STATE_CHANGED, (event) => {
1346
+ const { mediaType, state } = event;
1347
+ if (state === 'started') {
1348
+ if (mediaType === 'audio') {
1349
+ this.emit('onSendFirstLocalAudioFrame');
1350
+ } else if (mediaType === 'video') {
1351
+ this.emit('onSendFirstLocalVideoFrame', { streamType: TRTCVideoStreamType.TRTCVideoStreamTypeBig });
1352
+ } else if (mediaType === 'screen') {
1353
+ this.emit('onSendFirstLocalVideoFrame', { streamType: TRTCVideoStreamType.TRTCVideoStreamTypeSub });
1354
+ }
1355
+ }
1356
+ });
1357
+ }
1358
+ private _removeTRTCEvents(): void {
1359
+ this._trtc.off('*');
1360
+ }
1361
+ private _emitVideoAvailable(event: any, isVideoAvailable: boolean): void {
1362
+ const { userId, streamType } = event;
1363
+ if (isVideoAvailable) {
1364
+ this._remoteStreamMap.set(`${userId}_${streamType}`, true);
1365
+ } else {
1366
+ this._remoteStreamMap.delete(`${userId}_${streamType}`);
1367
+ }
1368
+ if (streamType === TRTC.TYPE.STREAM_TYPE_SUB) {
1369
+ userId && this.emit('onUserSubStreamAvailable', userId, isVideoAvailable);
1370
+ } else {
1371
+ userId && this.emit('onUserVideoAvailable', userId, isVideoAvailable);
1372
+ }
1373
+ }
1374
+ // --------------------------------- 变量获取、设置 ---------------------------------
1375
+ private _setLocalView(view: HTMLElement | string | null) {
1376
+ this._localView = view;
1377
+ }
1378
+ private _getLocalView(): HTMLElement | string | null {
1379
+ return this._localView;
1380
+ }
1381
+ private _setIsMobile(value: boolean) {
1382
+ this._isMobile = value;
1383
+ }
1384
+ private _getIsMobile(): boolean {
1385
+ return this._isMobile;
1386
+ }
1387
+ private _setIsFrontCamera(value: boolean) {
1388
+ this._isFrontCamera = value;
1389
+ }
1390
+ private _getIsFrontCamera() {
1391
+ return this._isFrontCamera;
1392
+ }
1393
+ private _setIsVideoPublish(value: boolean) {
1394
+ this._isVideoPublish = value;
1395
+ }
1396
+ private _getIsVideoPublish(): boolean {
1397
+ return this._isVideoPublish;
1398
+ }
1399
+ private _setVideoProfile(videoProfile: IVideoProfile) {
1400
+ this._videoProfile = videoProfile;
1401
+ }
1402
+ private _getVideoProfile(): IVideoProfile {
1403
+ return this._videoProfile;
1404
+ }
1405
+ private _setVideoPlayOption(videoPlayOption: IVideoPlayOption) {
1406
+ this._videoPlayOption = videoPlayOption;
1407
+ }
1408
+ private _getVideoPlayOption(): IVideoPlayOption {
1409
+ return this._videoPlayOption;
1410
+ }
1411
+ private _setLocalTestView(view: HTMLElement | string | null) {
1412
+ this._localTestView = view;
1413
+ }
1414
+ private _getLocalTestView(): HTMLElement | string | null {
1415
+ return this._localTestView;
1416
+ }
1417
+ private _setScreenShareParams(params: {
1418
+ view?: HTMLElement | string | null,
1419
+ systemAudio?: boolean,
1420
+ fillMode?: VideoFillMode,
1421
+ profile?,
1422
+ }) {
1423
+ const { view, systemAudio, fillMode, profile } = params;
1424
+ if (!isUndefined(view)) {
1425
+ this._screenShareParams.view = view;
1426
+ }
1427
+ if (!isUndefined(systemAudio)) {
1428
+ this._screenShareParams.option.systemAudio = systemAudio;
1429
+ }
1430
+ if (!isUndefined(fillMode)) {
1431
+ this._screenShareParams.option.fillMode = fillMode;
1432
+ }
1433
+ if (!isUndefined(profile)) {
1434
+ this._screenShareParams.option.profile = profile;
1435
+ }
1436
+ }
1437
+ private _getScreenShareParams() {
1438
+ return this._screenShareParams;
1439
+ }
1440
+ private _setIsAudioPublish(value: boolean) {
1441
+ this._isAudioPublish = value;
1442
+ }
1443
+ private _getIsAudioPublish(): boolean {
1444
+ return this._isAudioPublish;
1445
+ }
1446
+ private _setAudioProfile(audioProfile: string) {
1447
+ this._audioProfile = audioProfile;
1448
+ }
1449
+ private _getAudioProfile(): string {
1450
+ return this._audioProfile;
1451
+ }
1452
+ private _setCurrentCameraId(deviceId: string) {
1453
+ this._currentCameraId = deviceId;
1454
+ }
1455
+ private _getCurrentCameraId(): string {
1456
+ return this._currentCameraId;
1457
+ }
1458
+ private _setCurrentMicrophoneId(deviceId: string) {
1459
+ this._currentMicrophoneId = deviceId;
1460
+ }
1461
+ private _getCurrentMicrophoneId(): string {
1462
+ return this._currentMicrophoneId;
1463
+ }
1464
+ private _setCurrentSpeakerId(deviceId: string) {
1465
+ this._currentSpeakerId = deviceId;
1466
+ }
1467
+ private _getCurrentSpeakerId(): string {
1468
+ return this._currentSpeakerId;
1469
+ }
1470
+ private _setRemoteStreamConfig(userId: string, streamType: TRTCVideoStreamType, data: {
1471
+ view?: HTMLElement | string,
1472
+ mirrorType?: TRTCVideoMirrorType,
1473
+ fillMode?: TRTCVideoFillMode,
1474
+ }) {
1475
+ const config: Record<string, any> = {
1476
+ userId,
1477
+ streamType: this._getTRTCStreamType(streamType),
1478
+ option: {},
1479
+ };
1480
+ const { view, mirrorType, fillMode } = data;
1481
+ if (!isUndefined(view)) {
1482
+ config.view = view;
1483
+ }
1484
+ if (!isUndefined(mirrorType)) {
1485
+ config.option.mirror = this._getTRTCMirror(mirrorType as TRTCVideoMirrorType);
1486
+ }
1487
+ if (!isUndefined(fillMode)) {
1488
+ config.option.fillMode = this._getTRTCFillMode(fillMode as TRTCVideoFillMode);
1489
+ }
1490
+ this._remoteStreamConfig.set(`${userId}_${this._getTRTCStreamType(streamType)}`, config);
1491
+ }
1492
+
1493
+ // ------------ 处理设备监听 -----------
1494
+ /**
1495
+ * 内部方法:设备插拔事件处理
1496
+ */
1497
+ async handleDeviceChange() {
1498
+ // 重新获取 camera 设备列表
1499
+ TRTC.getCameraList().then(async (list) => {
1500
+ if (this._cameraList.length === list.length) return;
1501
+ await this.deviceChangeManage(this._cameraList, list, TRTCDeviceType.TRTCDeviceTypeCamera);
1502
+ this._cameraList = list;
1503
+ });
1504
+ // 重新获取 mic 设备列表
1505
+ TRTC.getMicrophoneList().then(async (list) => {
1506
+ await this.deviceChangeManage(this._microphoneList, list, TRTCDeviceType.TRTCDeviceTypeMic);
1507
+ this._microphoneList = list;
1508
+ });
1509
+ // 重新获取 speaker 设备列表
1510
+ TRTC.getSpeakerList().then(async (list) => {
1511
+ await this.deviceChangeManage(this._speakerList, list, TRTCDeviceType.TRTCDeviceTypeSpeaker);
1512
+ this._speakerList = list;
1513
+ });
1514
+ }
1515
+
1516
+ private isSameDevice(currentDevice, device) {
1517
+ const isNormalCurrentDevice = currentDevice && currentDevice.deviceId && currentDevice.groupId && currentDevice.label;
1518
+ const isNormalDevice = device && device.deviceId && device.groupId && device.label;
1519
+ if (isNormalCurrentDevice && isNormalDevice) {
1520
+ return currentDevice.deviceId === device.deviceId && currentDevice.groupId === device.groupId && currentDevice.label === device.label;
1521
+ }
1522
+ return false;
1523
+ }
1524
+
1525
+ /**
1526
+ * 内部方法:设备插拔数据处理<br>
1527
+ * @param {Array<TRTCDeviceInfo>} currentDeviceList 原来设备列表
1528
+ * @param {Array<TRTCDeviceInfo>} list 当前获取的设备列表
1529
+ * @param {TRTCDeviceType} type 设备类型
1530
+ */
1531
+ // eslint-disable-next-line max-len
1532
+ async deviceChangeManage(currentDeviceList: Array<TRTCDeviceInfo>, list, type: TRTCDeviceType) {
1533
+ // 先处理设备插拔
1534
+ let state: TRTCDeviceState | undefined = undefined;
1535
+ if (currentDeviceList.length !== list.length) {
1536
+ let deviceIdList: Array<String> = (list || []).map(obj => obj.deviceId);
1537
+ let deviceInfo: TRTCDeviceInfo = new TRTCDeviceInfo();
1538
+ // 拔出设备
1539
+ if (currentDeviceList.length > list.length) {
1540
+ deviceInfo = currentDeviceList.filter(obj => !deviceIdList.includes(obj.deviceId))[0] || {};
1541
+ state = TRTCDeviceState.TRTCDeviceStateRemove;
1542
+ } else {
1543
+ // 插入设备
1544
+ deviceIdList = (currentDeviceList || []).map(obj => obj.deviceId);
1545
+ deviceInfo = list.filter(obj => !deviceIdList.includes(obj.deviceId))[0] || {};
1546
+ state = TRTCDeviceState.TRTCDeviceStateAdd;
1547
+ }
1548
+ const { deviceId: changedDeviceId } = deviceInfo;
1549
+ this.emitOnDeviceChange(changedDeviceId, type, state);
1550
+ }
1551
+
1552
+ const defaultDeviceInfo: TRTCDeviceInfo = this.getDefaultDeviceInfo(list);
1553
+
1554
+ // 拔出 camera,底层 SDK 会进行切换,抛出 active 事件
1555
+ if (type === TRTCDeviceType.TRTCDeviceTypeCamera && state === TRTCDeviceState.TRTCDeviceStateRemove) {
1556
+ if (this.isSameDevice(this._currentCamera, defaultDeviceInfo)) {
1557
+ return;
1558
+ }
1559
+ defaultDeviceInfo.deviceId && await this.autoChangeDevice(type, defaultDeviceInfo);
1560
+ }
1561
+
1562
+ // 新增麦克风或者拔出麦克风,导致麦克风 default 发生改变时,处理 active 事件
1563
+ if (type === TRTCDeviceType.TRTCDeviceTypeMic) {
1564
+ if (this.isSameDevice(this._currentMicrophone, defaultDeviceInfo)) {
1565
+ return;
1566
+ }
1567
+ defaultDeviceInfo.deviceId && await this.autoChangeDevice(type, defaultDeviceInfo);
1568
+ }
1569
+
1570
+ // 新增扬声器或者拔出扬声器,导致扬声器 default 发生改变时,处理 active 事件
1571
+ if (type === TRTCDeviceType.TRTCDeviceTypeSpeaker) {
1572
+ if (this.isSameDevice(this._currentSpeaker, defaultDeviceInfo)) {
1573
+ return;
1574
+ }
1575
+ defaultDeviceInfo.deviceId && await this.autoChangeDevice(type, defaultDeviceInfo);
1576
+ }
1577
+ }
1578
+
1579
+ /**
1580
+ * 获取默认的设备信息:TRTC 获取设备列表时,里面会包含 deviceId = 'default' 的设备
1581
+ * - 设备列表中,含有 deviceId = 'default' 的设备,通过 groupId 获取该设备的信息
1582
+ * - 设备列表中,不含有 deviceId = 'default' 的设备,返回设备列表的第一个设备信息
1583
+ * @param {Array<TRTCDeviceInfo>} deviceList 传入的设备列表
1584
+ * @private
1585
+ * @returns {TRTCDeviceInfo} defaultInfo
1586
+ */
1587
+ private getDefaultDeviceInfo(deviceList): TRTCDeviceInfo {
1588
+ let defaultDeviceInfo: TRTCDeviceInfo = new TRTCDeviceInfo();
1589
+ if (deviceList.length === 0) return defaultDeviceInfo;
1590
+
1591
+ const list = deviceList.filter(obj => obj.deviceId === 'default');
1592
+ if (list.length > 0) {
1593
+ [defaultDeviceInfo] = list;
1594
+ } else {
1595
+ [defaultDeviceInfo] = deviceList;
1596
+ }
1597
+ return defaultDeviceInfo;
1598
+ }
1599
+
1600
+ /**
1601
+ * 内部方法:设备插拔后,自动切换设备<br>
1602
+ * @param {TRTCDeviceType} type 设备类型
1603
+ * @param {DeviceInfo} deviceInfo 设备信息
1604
+ */
1605
+ private async autoChangeDevice(type: TRTCDeviceType, deviceInfo) {
1606
+ const { deviceId } = deviceInfo;
1607
+ // 拔出摄像头,切换当前摄像头,返回当前活跃的摄像头
1608
+ if (type === TRTCDeviceType.TRTCDeviceTypeCamera) {
1609
+ try {
1610
+ await this._trtc.updateLocalVideo({ option: { cameraId: deviceId } });
1611
+ } catch (error: any) {
1612
+ console.log('error', JSON.stringify(error));
1613
+ if (error.code === TRTC.ERROR_CODE.OPERATION_ABORT) {
1614
+ // ignore
1615
+ }
1616
+ }
1617
+ try {
1618
+ await this._testTrtc.updateLocalVideo({ option: { cameraId: deviceId } });
1619
+ } catch (error: any) {
1620
+ console.log('testTRTC error', JSON.stringify(error));
1621
+ if (error.code === TRTC.ERROR_CODE.OPERATION_ABORT) {
1622
+ // ignore
1623
+ }
1624
+ }
1625
+ this._currentCameraId = deviceId;
1626
+ this._currentCamera = deviceInfo;
1627
+ this.emitOnDeviceChange(deviceId, type, TRTCDeviceState.TRTCDeviceStateActive);
1628
+ }
1629
+ // 插入、拔出麦克风,自动切换至插入的麦克风;如果当前 mic 不是插入的 mic,则不进行切换也不抛出事件
1630
+ if (type === TRTCDeviceType.TRTCDeviceTypeMic) {
1631
+ try {
1632
+ await this._trtc.updateLocalAudio({ option: { microphoneId: deviceId } });
1633
+ } catch (error: any) {
1634
+ console.log('error', JSON.stringify(error));
1635
+ if (error.code === TRTC.ERROR_CODE.OPERATION_ABORT) {
1636
+ // ignore
1637
+ }
1638
+ }
1639
+ try {
1640
+ await this._testTrtc.updateLocalAudio({ option: { microphoneId: deviceId } });
1641
+ } catch (error: any) {
1642
+ console.log('testTRTC error', JSON.stringify(error));
1643
+ if (error.code === TRTC.ERROR_CODE.OPERATION_ABORT) {
1644
+ // ignore
1645
+ }
1646
+ }
1647
+ this._currentMicId = deviceId;
1648
+ this._currentMicrophone = deviceInfo;
1649
+ this.emitOnDeviceChange(deviceId, type, TRTCDeviceState.TRTCDeviceStateActive);
1650
+ }
1651
+ // 插入、拔出 speaker,自动切换至插入的 speaker
1652
+ if (type === TRTCDeviceType.TRTCDeviceTypeSpeaker) {
1653
+ await TRTC.setCurrentSpeaker(deviceId);
1654
+ this._currentSpeakerId = deviceId;
1655
+ this._currentSpeaker = deviceInfo;
1656
+ this.emitOnDeviceChange(deviceId, type, TRTCDeviceState.TRTCDeviceStateActive);
1657
+ }
1658
+ }
1659
+
1660
+ /**
1661
+ * 设备插拔事件<br>
1662
+ * @param {String} deviceId 设备 ID
1663
+ * @param {TRTCDeviceType} type 设备类型
1664
+ * @param {TRTCDeviceState} state 设备状态
1665
+ * @event TRTCCallback#onDeviceChange
1666
+ */
1667
+ emitOnDeviceChange(deviceId: string, type: TRTCDeviceType, state: TRTCDeviceState) {
1668
+ this.emit('onDeviceChange', deviceId, type, state);
1669
+ }
1670
+ }
1671
+
1672
+ export default TRTC_Cloud;