softTeardownForSwitch method

Future<void> softTeardownForSwitch()

软销毁:为课堂自动切换场景设计,仅清理课堂级资源,保留:

  • IM 登录态(不调 _logout/_uninit)
  • TRTC SDK 实例(不调 TRTC.reset)
  • TCICController 单例与 rootContext(不调 resetControllerInstance)
  • _config(不调 _config.destroy)
  • EventBus(不 clear,业务回调仍可用)
  • ClassSwitcher(切换过程中不得 reset 自己)

每个阻塞步骤独立 try-catch + 2s timeout,任一步骤失败不阻断后续。

Implementation

Future<void> softTeardownForSwitch() async {
  if (_softTeardownFuture != null) {
    await _softTeardownFuture;
    return;
  }
  final completer = Completer<void>();
  _softTeardownFuture = completer.future;
  try {
    TCICLog.info('softTeardownForSwitch: start',
        actionModule: ActionModule.tcicController.name,
        actionName: 'softTeardown');

    // 1. 停心跳(同步、幂等)
    try {
      cancelHeartBeatTimer();
    } catch (e) {
      TCICLog.warn('softTeardown: cancelHeartBeatTimer failed: $e',
          actionModule: ActionModule.tcicController.name,
          actionName: ActionName.cancelHeartBeat.name);
    }

    // 2. 关闭弹窗/悬浮层/toast(避免旧课堂 UI 残留)
    try {
      Tools.closeAllDialogs();
      Tools.closeAllFloating();
      toastification.dismissAll();
    } catch (e) {
      TCICLog.warn('softTeardown: close overlays failed: $e',
          actionModule: ActionModule.tcicController.name,
          actionName: 'softTeardown');
    }

    // 3. 关闭本地采集(不 reset TRTC 实例)
    try {
      await Future.wait([
        if (_config.basicConfig?.allowPipMode ?? false) getPip().dispose(),
        TRTC.stopLocalPreview(),
        TRTC.closeMic('softTeardown'),
        TRTC.closeCamera('softTeardown'),
        TRTC.stopScreenCapture(),
      ]).timeout(const Duration(seconds: 2), onTimeout: () {
        TCICLog.warn('softTeardown: stop local capture timeout',
            actionModule: ActionModule.tcicController.name,
            actionName: 'softTeardown');
        return [];
      });
    } catch (e) {
      TCICLog.warn('softTeardown: stop local capture failed: $e',
          actionModule: ActionModule.tcicController.name,
          actionName: 'softTeardown');
    }

    // 4. 退 TRTC 房(不 reset SDK)
    try {
      await exitRTCRoom().timeout(const Duration(seconds: 2),
          onTimeout: () {
        TCICLog.warn('softTeardown: exitRTCRoom timeout',
            actionModule: ActionModule.tcicController.name,
            actionName: 'softTeardown');
      });
    } catch (e) {
      TCICLog.warn('softTeardown: exitRTCRoom failed: $e',
          actionModule: ActionModule.tcicController.name,
          actionName: 'softTeardown');
    }

    // 5. 通知服务端退旧课堂:必须等服务端确认 quitClass 完成再进新课堂,
    // 否则旧课堂成员关系仍在,新课堂 joinClass 可能被网关/业务层拒绝。
    // 失败/超时不抛出,但要等待结果,保证「先完整退课再进新课」的时序。
    try {
      await _safeQuitClassBackground().timeout(const Duration(seconds: 5),
          onTimeout: () {
        TCICLog.warn('softTeardown: quitClass timeout(5s), continue anyway',
            actionModule: ActionModule.tcicController.name,
            actionName: 'softTeardown');
      });
    } catch (e) {
      TCICLog.warn('softTeardown: quitClass failed: $e',
          actionModule: ActionModule.tcicController.name,
          actionName: 'softTeardown');
    }

    // 6. 退 IM 群(保留 IM 登录态,不调 _logout/_uninit)
    TCICLog.info('softTeardown: exitImGroup start groupId=${_config.classId}',
        actionModule: ActionModule.tcicController.name,
        actionName: 'softTeardownExitImGroup');
    try {
      await _exitImGroup().timeout(const Duration(seconds: 2),
          onTimeout: () {
        TCICLog.warn('softTeardown: exitImGroup timeout',
            actionModule: ActionModule.tcicController.name,
            actionName: 'softTeardownExitImGroup');
      });
      TCICLog.info('softTeardown: exitImGroup done',
          actionModule: ActionModule.tcicController.name,
          actionName: 'softTeardownExitImGroup');
    } catch (e) {
      TCICLog.warn('softTeardown: exitImGroup failed: $e',
          actionModule: ActionModule.tcicController.name,
          actionName: 'softTeardownExitImGroup');
    }

    // 7. 清业务 Obs + 白板单例 + 配置/Tips 相关
    try {
      clearObsData();
    } catch (e) {
      TCICLog.warn('softTeardown: clearObsData failed: $e',
          actionModule: ActionModule.tcicController.name,
          actionName: 'softTeardown');
    }
    try {
      TCICTips.resetConfig();
    } catch (e) {
      TCICLog.warn('softTeardown: TCICTips.resetConfig failed: $e',
          actionModule: ActionModule.tcicController.name,
          actionName: 'softTeardown');
    }

    TCICLog.info('softTeardownForSwitch: done',
        actionModule: ActionModule.tcicController.name,
        actionName: 'softTeardownDone');
  } finally {
    completer.complete();
    _softTeardownFuture = null;
  }
}