Phase 8.4:PhoneStateMonitor → phonestate 反向 callback
契约 §3.1 [14]phonestate 落地。msext 用已废弃的 CTCallCenter(iOS 10+
deprecated);新外壳改用 CallKit CXCallObserver,对 H5 输出字符串一致:
- "2" = 来电响铃中(hasConnected=false / isOutgoing=false / hasEnded=false)
或已连接(hasConnected=true)
- "0" = 挂断(hasEnded=true)
- 其它(拨出未连接、保持)不发
新增:
- Source/Resource/PhoneStateMonitor.swift(@MainActor singleton,与 BatteryMonitor /
NetworkMonitor 同款 pattern;onChange closure + start/stop 生命周期)
接入:
- SubGameViewController.setupExternalSubscriptions:start + onChange
- SubGameViewController.teardownExternalSubscriptions:onChange=nil + stop
- 大厅 WebContainerViewController 不订阅(msext gameController 同款边界,
契约表注「已注释」表示大厅不发;H5 子游戏才依赖此事件做哑麦/暂停)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
8fc37b0827
commit
ded6b5d86b
@@ -680,9 +680,11 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin
|
||||
- `createRoom` stub → `cb("createRoom")`
|
||||
- `getVideoinfo` stub → `cb("getVideoinfo")`
|
||||
- `exitRoom` stub → `cb("exitRoom")`
|
||||
- [ ] **8.4** `Source/Telephony/CallCenterMonitor.swift`:CXCallObserver 监听通话状态
|
||||
- 来电 → `bridge.call("phonestate", "2")`
|
||||
- 挂断 → `bridge.call("phonestate", "0")`
|
||||
- [x] **8.4** `Source/Resource/PhoneStateMonitor.swift`:CXCallObserver 监听通话状态(CallKit;不用废弃的 CTCallCenter)
|
||||
- 来电响铃中(hasConnected=false, isOutgoing=false, hasEnded=false)或 已连接(hasConnected=true)→ "2"
|
||||
- 挂断(hasEnded=true)→ "0"
|
||||
- 仅 SubGameViewController.setupExternalSubscriptions 挂载 + teardown 时 stop(大厅不订阅)
|
||||
- 反向 callback `bridge.call("phonestate", data:.string(state))`
|
||||
- [ ] **8.5** `RecordUploader.onUploaded` 已在 Phase 3,需要在 SubGameHandlers 里**额外**触发 `recordSuccess({fileUrl, fileName, fileKey})`(契约 §3.2 表 [15])
|
||||
- [ ] **8.6** `AgoraVideoRoom.swift` 留蓝图骨架(`#if AGORA_ENABLED` 包裹,默认 OFF)
|
||||
|
||||
@@ -966,7 +968,7 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin
|
||||
- [ ] 8.1 VideoRoom 协议
|
||||
- [ ] 8.2 NoopVideoRoom
|
||||
- [ ] 8.3 3 个 stub handler
|
||||
- [ ] 8.4 CallCenterMonitor → phonestate
|
||||
- [x] 8.4 PhoneStateMonitor → phonestate(CXCallObserver;仅子游戏订阅)
|
||||
- [ ] 8.5 recordSuccess 双 callback
|
||||
- [ ] 8.6 AgoraVideoRoom 蓝图(#if AGORA_ENABLED OFF)
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,6 +218,8 @@ public final class SubGameViewController: UIViewController {
|
||||
NetworkMonitor.shared.start()
|
||||
BatteryMonitor.shared.start()
|
||||
AppLifecycleObserver.shared.start()
|
||||
// 仅子游戏挂电话状态监听(大厅不订阅,与 msext gameController 同款边界)
|
||||
PhoneStateMonitor.shared.start()
|
||||
|
||||
let bridge = bridgedWebView.bridge
|
||||
let webView = bridgedWebView.webView
|
||||
@@ -244,6 +246,11 @@ public final class SubGameViewController: UIViewController {
|
||||
bridge.call("appservice", data: .string("2"), callback: nil)
|
||||
}
|
||||
|
||||
// 电话状态 — 契约 §3.1 [14]:来电 "2" / 挂断 "0"
|
||||
PhoneStateMonitor.shared.onChange = { state in
|
||||
bridge.call("phonestate", data: .string(state), callback: nil)
|
||||
}
|
||||
|
||||
// overlay(Phase 7)pop 回子游戏时反向 callback getWebdata。挂在子游戏栈顶
|
||||
// 期间生效;overlay push 上来时 viewWillDisappear → teardown 自动解绑,避免双发。
|
||||
subGameReturnObserver = NotificationCenter.default.addObserver(
|
||||
@@ -263,6 +270,8 @@ public final class SubGameViewController: UIViewController {
|
||||
NetworkMonitor.shared.onChange = nil
|
||||
AppLifecycleObserver.shared.onBackground = nil
|
||||
AppLifecycleObserver.shared.onForeground = nil
|
||||
PhoneStateMonitor.shared.onChange = nil
|
||||
PhoneStateMonitor.shared.stop()
|
||||
|
||||
if let token = subGameReturnObserver {
|
||||
NotificationCenter.default.removeObserver(token)
|
||||
|
||||
Reference in New Issue
Block a user