softTeardownForSwitch method
软销毁:为课堂自动切换场景设计,仅清理课堂级资源,保留:
- 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;
}
}