Commit Graph
2 Commits
Author SHA1 Message Date
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
joywayerandClaude Opus 4.7 0602b9d4b6 Phase 3.A:srcIsloop 本地音频 + 3.B/3.C/3.D handler stub
3.A 本地音频完整落地(与 msext NewRootVC initJSdata 等价):

  Source/Audio/AudioPlayer.swift(@MainActor 状态机):
    - playOnce(url) 单次按钮音;buttonPlayers 数组保活池避免多次点击
      互相打断(duration + 0.5s 后清理)
    - loopBackground(url, type:) 循环背景音;numberOfLoops = -1 +
      backgroundType 记录用于同名停止
    - stopBackground(type:) 同名校验后才停止(msext 同款语义)

  Source/Bridge/Handlers/LocalAudioHandler.swift:
    - 注册 srcIsloop handler(@MainActor enum)
    - 入参 src + isloop,按 0/1/-1 分支调 AudioPlayer.shared 对应方法
    - audioFileURL 路径:{Caches}/{gamedir}/{gamestart}/assets/wav/{src}
      与 msext NewRootVC 路径约定 1:1(H5 zip 内 assets/wav 子目录)
    - audioFileURL 标 nonisolated 因 SandboxPaths 是 nonisolated 静态
    - cb 字面 "Response from srcIsloop" 严格保持

  Source/Bridge/Handlers/RemoteAudioHandler.swift(3.B/3.C/3.D stub):
    - prepareaudio + mediaTypeAudio 仅 cb 维持契约,无真实业务
    - 待 Phase 3.B(opencore-amr .a 接入)+ 3.C(七牛 SPM + token)+
      3.D(远端播放)完整后升级
    - 按 CLAUDE.md 原则 A:H5 调啥就得有人接,即使是 stub

WebContainerViewController.registerBridgeHandlers 接入:
  - LocalAudioHandler.register(完整实现)
  - RemoteAudioHandler.register(stub)

Plan §5 Phase 3.1 勾选;3.2 真机验证留待 H5 提供音频文件后做。

Verification-Checklist.md 新增 Phase 3 章节:
  - H. 本地音频(E.14-E.16 srcIsloop 三个 isloop 值)
  - I. prepareaudio / mediaTypeAudio stub(E.17 E.18)

BuildProject 通过

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-22 19:11:34 +08:00