// // PhoneStateMonitor.swift // ylgamehall // // 电话状态监听(Phase 8.4,契约 §3.1 [14] phonestate 反向 callback)。 // // msext 用已废弃的 `CTCallCenter`(iOS 10+ deprecated);本项目用 CallKit // `CXCallObserver`(iOS 10+ 推荐),语义等价,对 H5 输出的字符串一致: // - "2" = 来电响铃中(hasConnected=false, isOutgoing=false, hasEnded=false) // 或 已连接(hasConnected=true,无论 incoming/outgoing) // - "0" = 挂断(hasEnded=true) // - 其它(拨出中未连接、保持等)不发 // // 仅子游戏注册,大厅不发(msext gameController.m 同款;契约表注「已注释」表示 // msext 在线版当前不主动触发,但 H5 子游戏仍按契约处理收到的事件)。 // // 使用模式(与 BatteryMonitor / NetworkMonitor 一致): // - viewDidLoad / setupExternalSubscriptions:`shared.start()` + `onChange = { ... }` // - viewWillDisappear / teardownExternalSubscriptions:`onChange = nil` + `shared.stop()` // import Foundation import CallKit @MainActor public final class PhoneStateMonitor: NSObject { public static let shared = PhoneStateMonitor() /// 状态变化回调,参数即给 H5 的 `phonestate` 字符串("2" / "0")。 /// closure 在 main actor 上调,调用方可直接拿 bridge 反向 callback。 public var onChange: ((String) -> Void)? private var observer: CXCallObserver? public override init() { super.init() } /// 开始监听。重复调安全(已 start 则无操作)。 public func start() { guard observer == nil else { return } let o = CXCallObserver() // queue: nil → main queue(callObserver:callChanged: 回调线程一致, // 子游戏页 bridge.call 也走主线程,无并发问题) o.setDelegate(self, queue: nil) observer = o } /// 停止监听。stop 之后再 start 重新创建 observer。 public func stop() { observer?.setDelegate(nil, queue: nil) observer = nil } } extension PhoneStateMonitor: CXCallObserverDelegate { /// CXCallObserver 状态切换。本方法 `@objc` 由 CallKit 在主队列调(init 时传 nil)。 public nonisolated func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) { let state: String if call.hasEnded { state = "0" } else if call.hasConnected { // 已接通(任何方向)— 视频房 / 录音应静音 state = "2" } else if !call.isOutgoing { // 来电响铃中(hasEnded=false, hasConnected=false, isOutgoing=false) state = "2" } else { // 拨出中未连接 / 保持等中间态 — H5 不关心,丢弃 return } Task { @MainActor in Self.shared.onChange?(state) } } }