ワンクリックで
webaudio-node-lifecycle
How to safely manage AudioBufferSourceNode ownership so nodes are never orphaned
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
How to safely manage AudioBufferSourceNode ownership so nodes are never orphaned
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | webaudio-node-lifecycle |
| description | How to safely manage AudioBufferSourceNode ownership so nodes are never orphaned |
| domain | audio-engine |
| confidence | high |
| source | earned |
AudioBufferSourceNode starts playing the moment .start() is called. If the reference to the node is lost (e.g., a completion timer gets cancelled before promoting the node to a named field), the node becomes orphaned: still playing, no way to stop it. This surfaces as overlapping audio that the user cannot see or dismiss.
Applies to any Web Audio pattern where a node is started before it is "officially" owned — crossfade entry, filler loops, any two-phase play/promote flow.
When you call node.source.start(), assign the node to a named field on the class before the async gap:
// GOOD — node is owned before the gap
this._incomingFillerNode = fillerNode
fillerNode.source.start(0, offset)
this.xfadeCompletionTimer = setTimeout(() => {
this.currentNode = fillerNode
this._incomingFillerNode = null // clear once promoted
}, xfadeSecs * 1000)
Cancelling a completion timer does NOT stop the node it was going to promote:
// Clear the timer that would have promoted the node
this._clearTimer('fillerLoopCompletion')
// Also handle the node — fade it out or tear it down
if (this._incomingFillerNode) {
const g = this._incomingFillerNode.gain.gain
g.cancelScheduledValues(now)
g.setValueAtTime(g.value, now)
g.linearRampToValueAtTime(0, now + xfadeSecs)
}
If mode-exit logic must cancel a downstream completion timer, also cancel the upstream trigger. In filler mode: exitFillerMode must cancel the xfadeCompletionTimer set by enterFillerMode, or it will fire late and re-enter the mode.
exitFillerMode() {
this._clearTimer('fillerLoop')
this._clearTimer('fillerLoopCompletion')
this._clearTimer('xfadeCompletion') // cancel enterFillerMode's late-fire too
// ...
}
_teardown in _resetAlways include every tracked node field in _reset():
_reset() {
this._teardown(this.currentNode)
this._teardown(this.incomingNode)
this._teardown(this._incomingFillerNode) // don't forget phase-1 nodes
this.currentNode = null
this.incomingNode = null
this._incomingFillerNode = null
}
setTimeout callback — the callback can be cancelled, the node lives on.currentNode assignment with if (this.currentNode === oldNode) — if anything changed currentNode during an async window, the guard silently fails and the new node is never promoted.cancelScheduledValues.