Skip to content

fix(audio): allow gesture resume after blocked autoplay - #3112

Open
luzhuang wants to merge 1 commit into
dev/2.0from
fix/audio-resume-user-gesture
Open

luzhuang wants to merge 1 commit into
dev/2.0from
fix/audio-resume-user-gesture

Conversation

@luzhuang

@luzhuang luzhuang commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

背景与实际影响

在 Cocos → Galacean 的「跳跃英雄漫威皮肤」中,用户在正常 Chrome 页面多次点击后,背景音乐和音效仍全部无声。独立无头浏览器先前报告播放成功,未能覆盖严格 autoplay 策略。

直接检查用户受影响页面确认:音乐/音效开关均为 1,AudioClip 已加载、volume=1,但多个 AudioSource 的 pendingPlay=true;AudioContext.state=suspended、currentTime=0、playingCount=0,AudioManager 缓存的恢复 Promise 长期未结束。

根因

AudioManager.resume() 使用 _resumePromise ??= 合并恢复请求。浏览器可以让缺少用户激活的 AudioContext.resume() 一直 pending,而不是 reject。后续可信点击虽然再次调用 AudioManager.resume(),却只获得旧 Promise,原生 resume() 没有在新的手势调用栈中执行,因而无法利用新的激活机会。

在同一受影响标签页,下一次真实点击直接调用原生 resume() 后,context 恢复 running、currentTime 开始推进,旧等待中的播放也恢复。这确认问题位于恢复请求去重,而不是资源、音量或渲染。

修复

  • 删除 _resumePromise 缓存,每次显式 resume() 都调用原生 AudioContext.resume()。
  • 保留返回 Promise、caller-suspend 和中断恢复的现有语义。
  • 不添加重试计时器、不重建上下文、不改业务音量与资源。
  • 保留已有前后台恢复延迟与恢复期间的保护,本 PR 不扩大到其他生命周期策略。

验证

  • pnpm b:module 通过。
  • pnpm exec vitest run --browser.enabled=true --browser.headless=true tests/src/core/audio:2 文件、36 项通过。
  • 更新原先强制 Promise 合并的测试,覆盖首次恢复仍 pending 时第二次恢复必须触达原生 API,以及前台恢复 pending 时后续手势仍可重试。
  • 严格 --autoplay-policy=document-user-activation-required 浏览器复现:先用无 userGesture 的 CDP 调用发起 resume,再真实鼠标点击。旧缓存逻辑仍 suspended/time=0/pendingPlay=true;修复后 running、BGM 播放,输出 RMS=0.00555,页面错误为 0。

边界

  • iOS/Safari 真机、电话中断与真人听感未在本次验证中覆盖,不宣称全设备 PASS。
  • 游戏的实验版依赖接入与游戏侧临时解锁逻辑不属于这个 Engine PR。

Summary by CodeRabbit

  • Bug Fixes
    • Audio resume attempts can now be retried while an earlier recovery attempt is still pending.
    • Gesture-triggered retries proceed independently instead of being merged with pending automatic recovery attempts.
    • Playback blocked by autoplay restrictions is correctly dropped during the audio lifecycle.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 4ae58f12-0a2e-45c2-a89b-0f14d5e279c2

📥 Commits

Reviewing files that changed from the base of the PR and between 1d06406 and 052910f.

📒 Files selected for processing (2)
  • packages/core/src/audio/AudioManager.ts
  • tests/src/core/audio/AudioSourcePendingPlayback.test.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


Walkthrough

AudioManager.resume() no longer coalesces concurrent calls through _resumePromise. Each call reaches the native audio context, and tests verify overlapping automatic and gesture-triggered resume attempts.

Changes

Audio resume behavior

Layer / File(s) Summary
Direct audio context resume
packages/core/src/audio/AudioManager.ts
Removed _resumePromise. Each resume() call now invokes getContext().resume() directly.
Concurrent resume validation
tests/src/core/audio/AudioSourcePendingPlayback.test.ts
Updated lifecycle tests to expect separate native resume attempts during overlapping calls and click storms.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 05291

Audio can retry native resume after autoplay blocks an earlier attempt, allowing playback to recover on a later user gesture. The updated behavior is covered by audio lifecycle tests and presents no remaining merge-blocking risk.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: allowing a user gesture to retry audio resume after autoplay blocks the initial attempt.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/audio-resume-user-gesture

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit taps the audio gate
Each resume call proceeds straight
No shared promise holds the line
Ten quick clicks all now align
The context wakes, one try at a time

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.75%. Comparing base (1d06406) to head (052910f).

Additional details and impacted files
@@             Coverage Diff             @@
##           dev/2.0    #3112      +/-   ##
===========================================
- Coverage    85.94%   85.75%   -0.19%     
===========================================
  Files          811      811              
  Lines        94826    94745      -81     
  Branches     11620    11623       +3     
===========================================
- Hits         81497    81249     -248     
- Misses       13239    13404     +165     
- Partials        90       92       +2     
Flag Coverage Δ
unittests 85.75% <100.00%> (-0.19%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫧 尘小沫

结论

阻塞级别:P1。实际 review 动作:REQUEST_CHANGES(未批准)。目标 HEAD:052910f1ed9d2ffa8e411e79795b04e69741aa46
自动 CR 不替代人工 Reviewer 的合入门禁。

问题

  • P1 — 首次 autoplay 的 pending 恢复不会被后续手势重新提交
    • 位置:packages/core/src/audio/AudioManager.ts:34
    • AudioSource.play() 在非 running context 中把自身置为 _pendingPlay 后只调用一次 AudioManager.resume()AudioSource.ts:150-165);若该原生调用因缺少 activation 而长期 pending,后续 play() 会被 _pendingPlay 早退。
    • 虽然 getContext() 已注册 document 的 click/touch fallback,但 _resumeAfterInterruption() 只有 _needsUserGestureResume 为 true 才会再次调用 resume()AudioManager.ts:129-137)。这个 flag 目前仅由前后台恢复路径写入(AudioManager.ts:107-119),首次 autoplay 路径没有写入它。因此普通后续手势不会发起新的 native context.resume();删除 _resumePromise 仅在业务侧恰好再次直接调用 AudioManager.resume() 时才生效,无法独立修复 PR 所述的 pending AudioSource 场景。
    • 请让 AudioManager 保持唯一 owner:将“有一次尚未成功、需要手势重试的 resume”收口到现有 _needsUserGestureResume,在非 running context 发起 resume 时同步 arm、成功时清除,并继续由 _suspendedByCaller / _recovering 约束该 fallback。不要在每个 AudioSource 上再挂监听器、镜像 pending 状态或恢复旧的共享 Promise。
    • 补一条用户链路回归:非手势 AudioSource.play() 的第一次 resume 保持 pending,随后 dispatch 真实 document gesture,断言 native resume() 的调用数增加,并在首次 Promise 落定后断言原 AudioSource 开始播放。该用例应在当前提交失败,同时保留“明确 reject 的 one-shot playback 不回放”的既有契约。

架构、熵增与测试治理

  • 浏览器 AudioContext 是实际运行状态与 activation 的权威 owner;AudioManager 应独占全局 resume 意图和手势 fallback,AudioSource 只拥有单个播放请求的 _pendingPlay。本 PR 删除 _resumePromise、其 finally 和对应测试清理,方向上消除了把浏览器调用资格镜像为全局 Promise 的重复 owner;不应把它加回。
  • 但目前 AudioSource 的 pending 请求与 AudioManager 的手势重试 flag 没有在首次 autoplay 控制流上接通,形成两段平行状态。应在 Manager 内复用并统一该 flag 的语义,而非把重试责任转移到业务或新增第二条同步路径。
  • 更新后的测试覆盖了两次直接 AudioManager.resume() 以及既有前后台恢复已 arm flag 的场景,未覆盖上述 AudioSource.play() -> pending -> 后续真实手势 路径。GitHub CI 的 lint、三平台 build、codecov 与四组 e2e 均通过;codecov/project 仅报告整体覆盖率下降,不能证明这条控制流已经闭合。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants