import 'dart:async'; /// 一对一语音会话防护:占线、超时、重复信令、销毁时释放计时器。 /// 不自建第二套信令,只约束官方 OpenIMLive 的进出场。 enum VoiceCallPhase { idle, outgoing, incoming, inCall } class CallSessionGuard { CallSessionGuard({this.inviteTimeout = const Duration(seconds: 30)}); final Duration inviteTimeout; VoiceCallPhase phase = VoiceCallPhase.idle; String? roomID; void Function()? onTimeout; final Set _seenKeys = {}; 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(); } }); } }