@trtc/calls-uikit-vue2 4.4.5 → 4.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trtc/calls-uikit-vue2",
3
- "version": "4.4.5",
3
+ "version": "4.4.7",
4
4
  "main": "./tuicall-uikit-vue2.umd.js",
5
5
  "module": "./tuicall-uikit-vue2.es.js",
6
6
  "types": "./types/index.d.ts",
@@ -14,9 +14,9 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@tencentcloud/tui-core-lite": "1.0.0",
17
- "@trtc/call-engine-lite-js": "~3.5.5",
17
+ "@trtc/call-engine-lite-js": "~3.5.8",
18
18
  "@tencentcloud/lite-chat": "^1.6.3",
19
- "@trtc/call-engine-lite-wx": "~3.4.7"
19
+ "@trtc/call-engine-lite-wx": "~3.4.8"
20
20
  },
21
21
  "bugs": {
22
22
  "url": "https://github.com/tencentyun/TUICallKit/issues"
@@ -1,6 +1,6 @@
1
1
  import { watch, ref, toRefs, computed } from '../../adapter-vue';
2
2
  import { useCustomUI } from './useCustomUI';
3
- import { TUIGlobal, CallStatus } from '../../TUICallService';
3
+ import { TUIGlobal, CallStatus, TUIStore, StoreName, NAME } from '../../TUICallService';
4
4
  import { add, deepClone, findValues, modify } from '../util';
5
5
  import { VirtualBackgroundMobileConfig } from '../components/common/ButtonPanel/config/VirtualBackgroundMobileConfig';
6
6
  import { useCallInfoContext } from './useCallInfoContext';
@@ -36,15 +36,46 @@ function setCloseCameraConfig(config, isVideoAvailable, isShowVirtualBackgroundI
36
36
  return newConfig;
37
37
  }
38
38
 
39
+ /**
40
+ * Hide inviteUser button when groupID is empty (not a Chat group call)
41
+ * This prevents showing the invite button when there's no group member list available
42
+ */
43
+ function setInviteUserVisibility(config, groupID) {
44
+ const newConfig = deepClone(config);
45
+ const hasValidGroupID = groupID && typeof groupID === 'string' && groupID.trim() !== '';
46
+
47
+ if (!hasValidGroupID) {
48
+ // Hide inviteUser button in all group call scenarios when no valid groupID
49
+ // PC group call video
50
+ modify(newConfig, 'pc.groupCall.video.calling[0][2].props.show', false);
51
+ modify(newConfig, 'pc.groupCall.video.connected[0][3].props.show', false);
52
+ // PC group call audio
53
+ modify(newConfig, 'pc.groupCall.audio.calling[0][2].props.show', false);
54
+ modify(newConfig, 'pc.groupCall.audio.connected[0][3].props.show', false);
55
+ }
56
+
57
+ return newConfig;
58
+ }
59
+
39
60
  export function useCustomUIButtonConfig() {
40
61
  const { isShowEnableVirtualBackground, callStatus } = toRefs(useCallInfoContext());
41
62
  const customUIConfig = useCustomUI();
42
63
  const { localUserInfoExcludeVolume: localUserInfo } = toRefs(useUserInfoExcludeVolumeContext());
43
64
  const isVideoAvailable = computed(() => localUserInfo?.value.isVideoAvailable || false);
44
65
  const isShowVirtualBackgroundIcon = computed(() => isShowEnableVirtualBackground.value && !TUIGlobal.isH5);
66
+ const groupID = ref(TUIStore.getData(StoreName.CALL, NAME.GROUP_ID));
45
67
  const results = ref([]);
46
68
 
47
- watch([customUIConfig, isShowEnableVirtualBackground, isVideoAvailable], () => {
69
+ // Watch groupID changes from TUIStore
70
+ const handleGroupIDChange = (value) => {
71
+ groupID.value = value;
72
+ };
73
+
74
+ TUIStore.watch(StoreName.CALL, {
75
+ [NAME.GROUP_ID]: handleGroupIDChange,
76
+ });
77
+
78
+ watch([customUIConfig, isShowEnableVirtualBackground, isVideoAvailable, groupID], () => {
48
79
  let initConfig = deepClone(ButtonPanelConfig);
49
80
  if (isShowVirtualBackgroundIcon.value) {
50
81
  initConfig = setVirtualBackgroundConfig(ButtonPanelConfig);
@@ -52,6 +83,10 @@ export function useCustomUIButtonConfig() {
52
83
  if(callStatus.value === CallStatus.CONNECTED) {
53
84
  initConfig = setCloseCameraConfig(initConfig, isVideoAvailable.value, isShowVirtualBackgroundIcon.value);
54
85
  }
86
+
87
+ // Hide inviteUser button when not in a Chat group (no groupID)
88
+ initConfig = setInviteUserVisibility(initConfig, groupID.value);
89
+
55
90
  const { button: buttonsConfig } = customUIConfig.value;
56
91
  const rs = [];
57
92
  function condition(value) {
@@ -27,7 +27,7 @@ const TUIGlobal: ITUIGlobal = TuiGlobal.getInstance();
27
27
  const TUIStore: ITUIStore = TuiStore.getInstance();
28
28
  const uiDesign = UIDesign.getInstance();
29
29
  uiDesign.setTUIStore(TUIStore);
30
- const version = '4.4.5';
30
+ const version = '4.4.7';
31
31
  import AIAssistant from './AIAssistant'; // 仅 web 支持 AI 实时字幕
32
32
  const frameWork = 'vue2.7';
33
33
  export { TUIGlobal, TUIStore, uiDesign };
@@ -321,7 +321,6 @@ export default class TUICallService {
321
321
  public setCameraDefaultState(isOpen: boolean) {
322
322
  uiDesign.setCameraDefaultState(isOpen);
323
323
  }
324
- // AI Subtitle functionality removed, focusing on real-time transcriber
325
324
  // =============================【实验性接口】=============================
326
325
  public callExperimentalAPI(jsonStr: string) {
327
326
  const jsonObj = JSON.parse(jsonStr);
@@ -332,6 +331,9 @@ export default class TUICallService {
332
331
 
333
332
  try {
334
333
  switch(api) {
334
+ case 'setAssetsPath':
335
+ TUICallEngine?.callExperimentalAPI?.(jsonStr);
336
+ break;
335
337
  case 'forceUseV2API':
336
338
  const { enable } = params;
337
339
  TUIStore.update(StoreName.CALL, NAME.IS_FORCE_USE_V2_API, !!enable);
@@ -579,15 +581,22 @@ export default class TUICallService {
579
581
  public executeExternalAfterCalling(): void {
580
582
  this.afterCalling && this.afterCalling();
581
583
  }
582
- // 处理用户异常退出的情况, 小程序 ”右滑“、"左上角退出"; web 页面关闭浏览器或关闭 tab 页面
584
+ // Handle exception exit: mini program "swipe right", "exit from top left"; web close browser or close tab
583
585
  public async handleExceptionExit(event?: any) {
584
586
  try {
585
587
  const callStatus = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS);
586
588
  const callRole = TUIStore.getData(StoreName.CALL, NAME.CALL_ROLE);
587
- this._tuiCallEngine?.reportLog?.({ name: 'TUICallkit.handleExceptionExit', data: { callStatus, callRole } });
589
+
590
+ this._tuiCallEngine?.reportLog?.({
591
+ name: 'TUICallkit.handleExceptionExit',
592
+ data: { callStatus, callRole, source: event?.type || 'unknown' }
593
+ });
588
594
 
589
595
  if (callStatus === CallStatus.IDLE) return;
590
- // 在呼叫状态下,被叫调用 reject,主叫调用 hangup
596
+
597
+ console.log(`${NAME.PREFIX} handleExceptionExit triggered, status: ${callStatus}, role: ${callRole}, event: ${event?.type}`);
598
+
599
+ // Execute hangup/reject based on call status and role
591
600
  if (callStatus === CallStatus.CALLING) {
592
601
  if (callRole === CallRole.CALLER) {
593
602
  await this?.hangup();
@@ -627,7 +636,7 @@ export default class TUICallService {
627
636
  }
628
637
  }
629
638
  // =========================【private methods for service use】=========================
630
- // 处理 “呼叫” 抛出的异常
639
+ // 处理 "呼叫" 抛出的异常
631
640
  private _handleCallError(error: any, methodName?: string) {
632
641
  this._permissionCheckTimer && clearInterval(this._permissionCheckTimer);
633
642
 
@@ -72,11 +72,15 @@ export class CallManager {
72
72
  // @ts-ignore
73
73
  wx.navigateTo({
74
74
  url: `/${this._globalCallPagePath}`,
75
- success: () => {},
75
+ success: () => {
76
+ const callStatus = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS);
77
+ if (callStatus === CallStatus.IDLE) {
78
+ this._handleCallStatusToIdle();
79
+ }
80
+ },
76
81
  fail: () => {
77
82
  console.error(`${PREFIX} navigateTo fail!`);
78
83
  },
79
- complete: () => {},
80
84
  });
81
85
  }
82
86
 
@@ -252,18 +252,163 @@ export function interpolate(str, data) {
252
252
  return data[key] !== undefined ? String(data[key]) : match;
253
253
  });
254
254
  }
255
- // Execute the callback when detecting browser refresh or close, but skip processing for page navigation
255
+
256
+ /**
257
+ * Detect current runtime environment
258
+ */
259
+ function detectEnvironment(): 'pc-browser' | 'mobile-browser' | 'webview' | 'miniprogram' {
260
+ const ua = navigator.userAgent || '';
261
+ const uaLower = ua.toLowerCase();
262
+
263
+ // WeChat MiniProgram WebView
264
+ if (uaLower.includes('miniprogram') ||
265
+ (window as any).__wxjs_environment === 'miniprogram') {
266
+ return 'miniprogram';
267
+ }
268
+
269
+ // App WebView
270
+ if (uaLower.includes('webview') ||
271
+ uaLower.includes(' wv') ||
272
+ (window as any).webkit?.messageHandlers) {
273
+ return 'webview';
274
+ }
275
+
276
+ // Mobile browser
277
+ if (/Android|iPhone|iPad|iPod|Mobile/i.test(ua)) {
278
+ return 'mobile-browser';
279
+ }
280
+
281
+ return 'pc-browser';
282
+ }
283
+
284
+ /**
285
+ * Detect if current device is iOS
286
+ */
287
+ function isIOS(): boolean {
288
+ return /iPad|iPhone|iPod/.test(navigator.userAgent || '');
289
+ }
290
+
291
+ /**
292
+ * Detect if current device is Android
293
+ */
294
+ function isAndroid(): boolean {
295
+ return /Android/.test(navigator.userAgent || '');
296
+ }
297
+
298
+ /**
299
+ * Enhanced browser/webview close detection for exception exit handling
300
+ * Supports: PC browsers (Chrome/Safari/Firefox/Edge), Mobile browsers (Android/iOS), WebView
301
+ *
302
+ * Note: Mobile background switching (visibilitychange to hidden) will NOT trigger hangup,
303
+ * only actual page close/navigation events will trigger the callback.
304
+ *
305
+ * @param callback - Function to call when exit is detected
306
+ */
256
307
  export function initBrowserCloseDetection(callback: Function) {
257
- if (window?.addEventListener) {
258
- // Trigger condition: close tab、refresh、navigate
259
- window.addEventListener('beforeunload', (event) => {
260
- const navigationEntries = performance?.getEntriesByType('navigation') || [];
261
- const navigationEntry = navigationEntries[0];
262
- // @ts-ignore
263
- if (navigationEntry && navigationEntry.type === 'navigate') {
264
- return;
265
- }
308
+ if (typeof window === 'undefined') return;
309
+
310
+ let isExiting = false;
311
+ const env = detectEnvironment();
312
+
313
+ console.log(`[ExitDetection] Environment: ${env}, iOS: ${isIOS()}, Android: ${isAndroid()}`);
314
+
315
+ /**
316
+ * Trigger exit handler with deduplication
317
+ */
318
+ const triggerExit = (source: string, event?: Event) => {
319
+ if (isExiting) return;
320
+ isExiting = true;
321
+
322
+ console.log(`[ExitDetection] Exit triggered by: ${source}`);
323
+
324
+ try {
266
325
  callback(event);
267
- });
326
+ } catch (error) {
327
+ console.error(`[ExitDetection] Callback error:`, error);
328
+ }
329
+
330
+ // Reset flag after delay (for bfcache scenarios)
331
+ setTimeout(() => {
332
+ isExiting = false;
333
+ }, 1000);
334
+ };
335
+
336
+ /**
337
+ * Handle beforeunload event
338
+ * Best for: PC browsers, Android browsers
339
+ */
340
+ const handleBeforeUnload = (event: BeforeUnloadEvent) => {
341
+ // Skip if it's just navigation (not actual page close)
342
+ const navigationEntries = performance?.getEntriesByType?.('navigation') || [];
343
+ const navigationEntry = navigationEntries[0] as PerformanceNavigationTiming;
344
+ if (navigationEntry && navigationEntry.type === 'navigate') {
345
+ return;
346
+ }
347
+
348
+ triggerExit('beforeunload', event);
349
+
350
+ // Required for some browsers to trigger the event
351
+ event.returnValue = '';
352
+ return '';
353
+ };
354
+
355
+ /**
356
+ * Handle pagehide event
357
+ * Best for: iOS Safari, all modern browsers
358
+ * More reliable than beforeunload on iOS
359
+ */
360
+ const handlePageHide = (event: PageTransitionEvent) => {
361
+ // persisted=true means page might be restored from bfcache
362
+ if (!event.persisted) {
363
+ triggerExit('pagehide', event);
364
+ }
365
+ };
366
+
367
+ /**
368
+ * Handle visibilitychange event
369
+ * Note: We only log this event, do NOT trigger hangup on mobile background switch
370
+ * Because user may just switch apps temporarily and come back
371
+ */
372
+ const handleVisibilityChange = () => {
373
+ if (document.visibilityState === 'hidden') {
374
+ // Only log, do NOT trigger exit on visibility change
375
+ // Mobile users often switch apps temporarily during calls
376
+ console.log(`[ExitDetection] Visibility changed to hidden (not triggering exit)`);
377
+ }
378
+ };
379
+
380
+ /**
381
+ * Handle freeze event (Page Lifecycle API, Chrome 68+)
382
+ * This indicates the page is being frozen, which is a strong signal of exit
383
+ */
384
+ const handleFreeze = () => {
385
+ triggerExit('freeze');
386
+ };
387
+
388
+ // ============ Register event listeners based on environment ============
389
+
390
+ // 1. pagehide - Most reliable for iOS, works on all modern browsers
391
+ if (window.addEventListener) {
392
+ window.addEventListener('pagehide', handlePageHide, { capture: true });
393
+ }
394
+
395
+ // 2. beforeunload - Best for PC browsers and Android
396
+ if (env === 'pc-browser' || isAndroid()) {
397
+ window.addEventListener('beforeunload', handleBeforeUnload);
268
398
  }
399
+
400
+ // 3. visibilitychange - Only for logging, not triggering exit
401
+ document.addEventListener('visibilitychange', handleVisibilityChange);
402
+
403
+ // 4. freeze - Page Lifecycle API (Chrome 68+)
404
+ if ('onfreeze' in document) {
405
+ document.addEventListener('freeze', handleFreeze as EventListener);
406
+ }
407
+
408
+ // 5. unload - Legacy fallback (deprecated but still useful in some cases)
409
+ window.addEventListener('unload', (event) => {
410
+ triggerExit('unload', event);
411
+ });
412
+
413
+ console.log(`[ExitDetection] Listeners registered for environment: ${env}`);
269
414
  }
package/src/index.ts CHANGED
@@ -37,7 +37,7 @@ const TUICallType = {
37
37
  AUDIO_CALL: 1,
38
38
  VIDEO_CALL: 2,
39
39
  };
40
- const Version = '4.4.5'; // basic-demo 原来上报使用
40
+ const Version = '4.4.7'; // basic-demo 原来上报使用
41
41
 
42
42
  // 输出产物
43
43
  export {