Skip to content

fix(audio): guard suspend/resume against uninitialized AudioContext#2963

Merged
cptbtptpbcptdtptp merged 2 commits intodev/2.0from
fix/audio-manager-null-context
Apr 14, 2026
Merged

fix(audio): guard suspend/resume against uninitialized AudioContext#2963
cptbtptpbcptdtptp merged 2 commits intodev/2.0from
fix/audio-manager-null-context

Conversation

@GuoLei1990
Copy link
Copy Markdown
Member

@GuoLei1990 GuoLei1990 commented Apr 14, 2026

Summary

  • AudioManager._context is lazily created on first audio play, but suspend()/resume() as public API can be called before that
  • Added null check to both methods — return Promise.resolve() when _context doesn't exist yet
  • No AudioContext means nothing to suspend/resume, user completely unaffected

Test plan

  • Call AudioManager.suspend() before any audio plays — should not throw
  • Call AudioManager.resume() before any audio plays — should not throw
  • Normal audio playback + suspend/resume flow still works as before

Summary by CodeRabbit

  • Bug Fixes
    • Improved audio reliability when pausing/resuming: suspend and resume now safely handle cases where the audio system hasn't been initialized yet, preventing potential crashes and ensuring more consistent audio behavior.

AudioManager._context is lazily created on first audio play. If
suspend() or resume() is called before that (e.g. editor
visibilitychange handler), it crashes with "Cannot read properties
of undefined". Return a resolved promise when _context doesn't
exist yet — no AudioContext means nothing to suspend/resume.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8e682c82-d88e-455b-9c70-dbc78086b371

📥 Commits

Reviewing files that changed from the base of the PR and between 3c5ca15 and 01f469a.

📒 Files selected for processing (1)
  • packages/core/src/audio/AudioManager.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/audio/AudioManager.ts

Walkthrough

suspend() and resume() now call AudioManager.getContext() and delegate suspend/resume to the returned context (ensuring lazy initialization) instead of calling methods directly on the internal _context, adjusting control flow around context creation while keeping method signatures unchanged.

Changes

Cohort / File(s) Summary
AudioManager context initialization & control flow
packages/core/src/audio/AudioManager.ts
suspend() and resume() now use AudioManager.getContext() to obtain the AudioContext before calling suspend()/resume(), ensuring the context is lazily created/initialized prior to those operations; no public API signature changes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I nudged the Context from its nest,
"Get up," I whispered, "then do your best."
Suspend or resume, now done with care,
No phantom nulls, just music in the air. 🎶

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly summarizes the main change: adding guards (via getContext()) to suspend/resume methods to prevent errors when AudioContext is uninitialized.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 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-manager-null-context

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

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

@GuoLei1990 GuoLei1990 added the bug Something isn't working label Apr 14, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 14, 2026

Codecov Report

❌ Patch coverage is 50.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 77.56%. Comparing base (fbd9f6f) to head (01f469a).
⚠️ Report is 1 commits behind head on dev/2.0.

Files with missing lines Patch % Lines
packages/core/src/audio/AudioManager.ts 50.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           dev/2.0    #2963      +/-   ##
===========================================
- Coverage    77.61%   77.56%   -0.05%     
===========================================
  Files          900      900              
  Lines        98724    98724              
  Branches      9817     9817              
===========================================
- Hits         76621    76573      -48     
- Misses       21937    21985      +48     
  Partials       166      166              
Flag Coverage Δ
unittests 77.56% <50.00%> (-0.05%) ⬇️

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

☔ View full report in Codecov by Sentry.
📢 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.

…sume

Ensures AudioContext is created if needed so the state always matches
user intent — calling suspend() guarantees audio is actually suspended
Copy link
Copy Markdown
Contributor

@luzhuang luzhuang left a comment

Choose a reason for hiding this comment

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

+1

Copy link
Copy Markdown
Collaborator

@cptbtptpbcptdtptp cptbtptpbcptdtptp left a comment

Choose a reason for hiding this comment

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

+1

@cptbtptpbcptdtptp cptbtptpbcptdtptp merged commit 8557031 into dev/2.0 Apr 14, 2026
10 of 12 checks passed
Copy link
Copy Markdown
Member Author

@GuoLei1990 GuoLei1990 left a comment

Choose a reason for hiding this comment

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

总结

suspend()/resume() 中的 AudioManager._context 直接访问改为 AudioManager.getContext() 调用。改动方向是正确的——避免 _context 为 null 时崩溃。但需要注意 getContext() 是 lazy initializer(不存在时会 new AudioContext()),所以这个改动实际上把语义从"直接访问可能为 null 的 context"变成了"如果 context 不存在就创建一个"。

问题

P1

  • [P1] AudioManager.ts:18,27 — getContext() 会强制创建 AudioContext,而非安全跳过

    PR 描述说"return Promise.resolve() when _context doesn't exist yet",但实际代码调用 getContext()new AudioContext() + 注册 visibilitychange/touchstart/touchend/click 监听器。这意味着:

    1. 用户在无任何音频播放意图时调用 suspend() → 创建了一个 AudioContext → 浏览器控制台可能弹出 "AudioContext was not allowed to start" 警告(非用户手势触发时)
    2. 注册了不必要的全局事件监听
    3. 与 PR 描述的行为("No AudioContext means nothing to suspend/resume")不一致

    如果目标是 null 安全,应该真正检查 _context 是否存在,而不是强制创建:

    static suspend(): Promise<void> {
      const context = AudioManager._context;
      return context ? context.suspend() : Promise.resolve();
    }
    
    static resume(): Promise<void> {
      const context = AudioManager._context;
      if (!context) return Promise.resolve();
      return (AudioManager._resumePromise ??= context
        .resume()
        .then(() => {
          AudioManager._needsUserGestureResume = false;
        })
        .finally(() => {
          AudioManager._resumePromise = null;
        }));
    }

    这才是 PR 标题 "guard suspend/resume against uninitialized AudioContext" 的正确实现

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants