Skip to content

Fix background noise not playing after a page refresh#2928

Merged
lifeart merged 4 commits into
masterfrom
fix/background-noise-on-refresh
Jul 1, 2026
Merged

Fix background noise not playing after a page refresh#2928
lifeart merged 4 commits into
masterfrom
fix/background-noise-on-refresh

Conversation

@lifeart

@lifeart lifeart commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Problem

After refreshing the exercise page, the background noise (шум, played behind the words to simulate a noisy listening environment) doesn't play. Restarting the lesson fixes it.

Root cause

The AudioService is a singleton whose AudioContext lives for the page's lifetime. Browsers create an AudioContext in the suspended state under the autoplay policy — it only produces sound after an explicit resume().

The word-playback path resumes it (setAudioElements and playTask both call context.resume() on a suspended context). The noise path never does — startNoiseTask calls noise.source.start(0) with no resume. On a hard refresh the context is fresh/suspended when the noise source starts, so it's scheduled but silent. A restart reuses a context that word playback already resumed, which is exactly why restarting "fixes" it.

(The if (!level) return guard on currentExerciseNoiseLevel was considered and ruled out — it would suppress noise in both flows, so it can't explain the refresh-vs-restart difference.)

Fix

Resume the context before starting the noise source in startNoiseTask, mirroring the existing suspended-state handling in setAudioElements/playTask (same !isTesting() guard).

if (this.context && this.context.state === 'suspended' && !isTesting()) {
  await this.context.resume();
}
noise.source.start(0);

Tests

Adds startNoiseTask coverage: the no-noise-level early return, and that the noise source is created and started when a noise level is present.

Verification

lint:types (glint/tsc) and eslint pass clean. The QUnit suite was not run — ember test is broken pre-existing in this repo (aborts on a global ./package module error before any test runs).

Worth a quick manual check: hard-refresh directly on an exercise with noise, start it, and confirm the background noise plays on the first pass (not only after a restart).

🤖 Generated with Claude Code

lifeart and others added 2 commits July 1, 2026 07:11
The noise path never resumed a suspended AudioContext, unlike the word
path. After a hard refresh the singleton context is fresh and suspended
under the browser autoplay policy, so noise.source.start(0) queues
silently and the background noise never plays. A lesson restart reused a
context that word playback had already resumed, which is why restarting
"fixed" it.

Resume the context before starting the noise source in startNoiseTask,
mirroring the existing handling in setAudioElements and playTask.

Adds startNoiseTask coverage: the no-level early return, and that the
noise source is created and started when a noise level is present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the events-array + indexOf ordering check with QUnit's
assert.step / assert.verifySteps, which reads the intended sequence
declaratively and fails clearly on an out-of-order call.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Gradle Unit and Integration Test Results

523 tests  ±0   521 ✔️ ±0   37s ⏱️ -1s
116 suites ±0       2 💤 ±0 
116 files   ±0       0 ±0 

Results for commit 5f97535. ± Comparison against base commit 836a854.

♻️ This comment has been updated with latest results.

lifeart and others added 2 commits July 1, 2026 07:25
The context.resume() before source.start(0) introduces an await, so a
cancellation (stopNoise) or a resume rejection between creating and
starting the source would make the finally call stop() on a node that
never started, throwing InvalidStateError and masking the real reason.
Track a started flag and only stop when the source actually started.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The service's willDestroy() closes the context on teardown; the running
context stub lacked close(), so the test rejected with 'this.context.close
is not a function'. Mirrors the stub used in the playback-rate tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Frontend test coverage: 72.7% (+0.11% compared to 72.59% on base)

@sonarqubecloud

sonarqubecloud Bot commented Jul 1, 2026

Copy link
Copy Markdown

@lifeart
lifeart merged commit 72f1a2c into master Jul 1, 2026
10 checks passed
@lifeart
lifeart deleted the fix/background-noise-on-refresh branch July 1, 2026 08:54
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.

1 participant