Files
youle_app_ios_v2/ylgamehall/Source/WebView
joywayer f2b456cf00 3.E:prepareaudio 完整实现 + 大厅/子游戏分流接入
替换 RemoteAudioHandler 中 prepareaudio 的 stub 为完整链路:权限检查 →
录音浮层 → 转码 → 七牛上传 → 反向 callback getaudiourl + 子游戏额外
recordSuccess。mediaTypeAudio 仍为 stub(Phase 3.F 处理)。

## 完整链路

1. H5 调 prepareaudio
2. `AVAudioSession.requestRecordPermission`
   - 拒绝:弹 Alert("需要访问您的麦克风..."),**不触发 cb**
     (对齐 daoqi NewRootVC.m:314-319)
3. 同意 → `RecordingPresenter.start(on: hostVC, fileName:)`
   - 启动失败:log + return,**不触发 cb**
   - 启动成功:立即触发 `cb("Response from prepareaudio")`,对齐 daoqi
     NewRootVC.m:322 `[recorderVC beginRecordByFileName:..]` 之后 cb 行为
4. 用户松手 / 60s 自动停 → `processCompletedRecording` 后台 Task
   - 用 AVAudioPlayer 读 wav `duration` → `round` 取整秒(对齐 daoqi
     NewRootVC.m:1891-1893)
   - 生成 amr 文件名:
     - 大厅 `yyyyMMddHHmmss%08X.amr`(无下划线,NewRootVC.m:1908)
     - 子游戏 `yyyyMMddHHmmss_%08X.amr`(有下划线,gameController.m:2269)
   - `AMRCodec.wavToAmr`
   - `QiniuUploader.upload`(key = 本地文件名,对齐 daoqi 上传命名)
5. 反向 callback(MainActor):
   - `getaudiourl({audiourl: String, time: String})`
     ⚠️ time 是字符串(%ld 整秒),不是 number — 对齐 daoqi
     NewRootVC.m:1923 / gameController.m:2305
   - **仅子游戏**额外 `recordSuccess({fileUrl, fileName, fileKey})`
     对齐 daoqi gameController.m:2310;大厅 NewRootVC 无此 callHandler

## register 签名扩展

新增参数:
- `hostVC: @escaping @MainActor @Sendable () -> UIViewController?`
  —— 每次录音重新求值,避免捕获过时引用
- `isSubGame: Bool` —— 决定是否触发 recordSuccess

WebContainerViewController(大厅)传 `isSubGame: false`;
SubGameViewController(子游戏)传 `isSubGame: true`。

## QiniuUploader 改动

key 生成方式:UUID → 本地文件名(lastPathComponent)。让调用方完全控制
key 命名,对齐 daoqi 录音文件命名规则。H5 通过 audiourl 转发给对端,对端
按文件名规律假设,必须严格对齐。

## 错误处理

启动失败 / 转码失败 / 上传失败:log + return,不触发反向 callback。
对齐 daoqi 同款 silent return 行为(mediaTypeAudio 下载失败、转码失败
也是同样 silent return 模式)。

## 验证

BuildProject 通过 0 错误。
2026-06-24 01:36:25 +08:00
..