Files
tongxunruanjian/mobile-next/lib/company/rtc/call_session_guard.dart
T
9bde926c81 fix(mobile-next): 按 B-91 收口 Android 通话视觉打回项
呼出/通话中按钮、计时器、底色、60s 未接与进出场动效对齐视觉规范。

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-08-20 09:58:28 +08:00

72 lines
1.7 KiB
Dart

import 'dart:async';
import '../brand/app_tokens.dart';
/// 一对一语音会话防护:占线、超时、重复信令、销毁时释放计时器。
/// 不自建第二套信令,只约束官方 OpenIMLive 的进出场。
enum VoiceCallPhase { idle, outgoing, incoming, inCall }
class CallSessionGuard {
CallSessionGuard({this.inviteTimeout = AppDuration.callInviteTimeout});
final Duration inviteTimeout;
VoiceCallPhase phase = VoiceCallPhase.idle;
String? roomID;
void Function()? onTimeout;
final Set<String> _seenKeys = <String>{};
Timer? _timeout;
bool get isBusy => phase != VoiceCallPhase.idle;
bool acceptSignaling({required int customType, required String roomID}) {
if (roomID.isEmpty) return false;
return _seenKeys.add('$customType:$roomID');
}
String? startOutgoing(String roomID) {
if (isBusy) return 'busy';
this.roomID = roomID;
phase = VoiceCallPhase.outgoing;
_armTimeout();
return null;
}
String? startIncoming(String roomID) {
if (isBusy) return 'busy';
this.roomID = roomID;
phase = VoiceCallPhase.incoming;
_armTimeout();
return null;
}
void markConnected() {
if (!isBusy) return;
_timeout?.cancel();
_timeout = null;
phase = VoiceCallPhase.inCall;
}
bool sameRoom(String? other) =>
other != null && other.isNotEmpty && other == roomID;
void dispose() {
_timeout?.cancel();
_timeout = null;
_seenKeys.clear();
phase = VoiceCallPhase.idle;
roomID = null;
onTimeout = null;
}
void _armTimeout() {
_timeout?.cancel();
_timeout = Timer(inviteTimeout, () {
if (phase == VoiceCallPhase.outgoing || phase == VoiceCallPhase.incoming) {
onTimeout?.call();
}
});
}
}