3.A/3.F 修复:voicePlaying 入参类型兼容 + mediaTypeAudio canPlay 诊断

## 发现的根因

实测日志 mediaTypeAudio 进入了 native(amrFileCodec frame=107 打印两次确认
转码运行),但缺 gameui_play_voice 触发 log → canPlay=false 被短路。
原因是 voicePlaying handler 解析 H5 入参时类型不容忍。

之前实现:
  let on = (data?.asInt ?? 0) == 1

asInt 只对 BridgeData.number case 返回值。若 H5 传 string "1"(实测确实
会传)→ asInt 返回 nil → `?? 0 = 0` → `0 == 1 = false` → 开关被关闭。

daoqi 用 ObjC `[data intValue]`(NSString/NSNumber 都能转),永远能
正确处理两种类型。

## 修复

VoicePlayingHandler:改用 asLooseString 比较 "1" 字符串,兼容 H5 传:
  - string "1"  → asLooseString → "1" → on=true ✓
  - number 1    → asLooseString → "1" → on=true ✓
  - string "0"  → asLooseString → "0" → on=false ✓
  - number 0    → asLooseString → "0" → on=false ✓
  - 其它       → on=false(保守拒绝)

同时加 log.debug 记录每次 voicePlaying 调用的 raw + 解析后值,便于后续
诊断 H5 业务行为。

## 诊断辅助

RemoteAudioHandler.handleMediaTypeAudio 增加 log.debug:
  mediaTypeAudio user=X transcoded=Y canPlay=Z

让真机测试能直接看到 canPlay 实际值,避免再次盲猜。

## 验证

BuildProject 通过 0 错误。真机重试 mediaTypeAudio 应能看到:
  voicePlaying H5 → raw=1 → isEnabled=true
  mediaTypeAudio user=0 transcoded=true canPlay=true
  gameui_play_voice user=0
This commit is contained in:
joywayer
2026-06-27 12:07:20 +08:00
parent 522087f398
commit 90a523736e
2 changed files with 15 additions and 4 deletions
@@ -321,6 +321,7 @@ public enum RemoteAudioHandler {
// (仅转码成功 + 开关 enabled 时触发,对齐 daoqi // (仅转码成功 + 开关 enabled 时触发,对齐 daoqi
// NewRootVC.m:343-353 if (canbofang) { play + callHandler }) // NewRootVC.m:343-353 if (canbofang) { play + callHandler })
let canPlay = await VoiceCenter.shared.isEnabled let canPlay = await VoiceCenter.shared.isEnabled
log.debug("mediaTypeAudio user=\(user, privacy: .public) transcoded=\(transcoded, privacy: .public) canPlay=\(canPlay, privacy: .public)")
if transcoded, canPlay { if transcoded, canPlay {
do { do {
try VoicePlayer.shared.play(wavLocal) try VoicePlayer.shared.play(wavLocal)
@@ -5,14 +5,18 @@
// H5 → Native handler:voicePlaying — 远端语音播放总开关 // H5 → Native handler:voicePlaying — 远端语音播放总开关
// 契约:docs/H5-Native-Contract.md §3.1 [6] // 契约:docs/H5-Native-Contract.md §3.1 [6]
// //
// Phase 2.4 仅实现"开关位",真正的 mediaTypeAudio 播放在 Phase 3 AudioKit // 开关位由 actor VoiceCenter 持有,跨 handler 共享。Phase 3.F
// 完整时接入。开关位由 actor VoiceCenter 持有,跨 handler 共享。 // mediaTypeAudio handler 读 isEnabled 决定是否播放。
// //
import Foundation import Foundation
import os.log
/// 远端语音播放总开关(actor 隔离,跨 bridge handler 安全共享)。 /// 远端语音播放总开关(actor 隔离,跨 bridge handler 安全共享)。
/// Phase 3 mediaTypeAudio handler 读此 isEnabled 决定是否播放。 ///
/// 默认值 `true` 对齐 daoqi NewRootVC.m:255 / gameController.m:343 viewDidLoad
/// 早期 `canbofang=YES` 行为(ObjC BOOL 默认是 NO,但 daoqi 在 VC init 时主动
/// set YES)。
public actor VoiceCenter { public actor VoiceCenter {
public static let shared = VoiceCenter() public static let shared = VoiceCenter()
public private(set) var isEnabled: Bool = true public private(set) var isEnabled: Bool = true
@@ -21,13 +25,19 @@ public actor VoiceCenter {
public enum VoicePlayingHandler { public enum VoicePlayingHandler {
nonisolated private static let log = Logger(subsystem: "ylgamehall", category: "VoicePlaying")
public static func register(on bridge: any BridgeProtocol) { public static func register(on bridge: any BridgeProtocol) {
// 【6】voicePlaying // 【6】voicePlaying
// 入参 int 1=允许 / 其它=静默 // 入参 int 1=允许 / 其它=静默
// cb:"voicePlaying"(字面与 handler 名相同,不是 "Response from voicePlaying") // cb:"voicePlaying"(字面与 handler 名相同,不是 "Response from voicePlaying")
bridge.register("voicePlaying") { data, callback in bridge.register("voicePlaying") { data, callback in
let on = (data?.asInt ?? 0) == 1 // 兼容 H5 传 number 或 string("1" / 1 / 0 / "0")。原 asInt 实现遇到
// string 类型会返回 nil,导致 H5 调 voicePlaying("1") 时被当作 0 关闭开关。
let raw = data?.asLooseString ?? "<nil>"
let on = raw == "1"
Task { await VoiceCenter.shared.setEnabled(on) } Task { await VoiceCenter.shared.setEnabled(on) }
log.debug("voicePlaying H5 → raw=\(raw, privacy: .public) → isEnabled=\(on, privacy: .public)")
// 注意回包字面是 "voicePlaying",不是 "Response from voicePlaying"。 // 注意回包字面是 "voicePlaying",不是 "Response from voicePlaying"。
// 与其它 handler 的 "Response from XXX" 模式不一致是原项目历史包袱 // 与其它 handler 的 "Response from XXX" 模式不一致是原项目历史包袱
// (daoqi/msext gameController.m:573、NewRootVC.m:451 都是 responseCallback(@"voicePlaying")) // (daoqi/msext gameController.m:573、NewRootVC.m:451 都是 responseCallback(@"voicePlaying"))