Index: Source/modules/webaudio/AudioBufferSourceNode.cpp |
diff --git a/Source/modules/webaudio/AudioBufferSourceNode.cpp b/Source/modules/webaudio/AudioBufferSourceNode.cpp |
index f792fca3a3aacad3f96d4fcf6e30df3a18d9d619..d23747803e7177275aaf67d7a0dd30e0f6dbff63 100644 |
--- a/Source/modules/webaudio/AudioBufferSourceNode.cpp |
+++ b/Source/modules/webaudio/AudioBufferSourceNode.cpp |
@@ -47,6 +47,12 @@ const double DefaultGrainDuration = 0.020; // 20ms |
// to minimize linear interpolation aliasing. |
const double MaxRate = 1024; |
+// Number of extra frames to use when determining if a source node can be stopped. This should be |
+// at least one rendering quantum, but we add one more quantum for good measure. This doesn't need |
+// to be extra precise, just more than one rendering quantum. See |handleStoppableSourceNode()|. |
+// FIXME: Expose the rendering quantum somehow instead of hardwiring a value here. |
+const int kExtraStopFrames = 256; |
+ |
AudioBufferSourceHandler::AudioBufferSourceHandler(AudioNode& node, float sampleRate, AudioParamHandler& playbackRate, AudioParamHandler& detune) |
: AudioScheduledSourceHandler(NodeTypeAudioBufferSource, node, sampleRate) |
, m_buffer(nullptr) |
@@ -490,8 +496,10 @@ double AudioBufferSourceHandler::computePlaybackRate() |
// Normally it's not an issue because buffers are loaded at the |
// AudioContext's sample-rate, but we can handle it in any case. |
double sampleRateFactor = 1.0; |
- if (buffer()) |
- sampleRateFactor = buffer()->sampleRate() / sampleRate(); |
+ if (buffer()) { |
+ // Use doubles to compute this to full accuracy. |
+ sampleRateFactor = buffer()->sampleRate() / static_cast<double>(sampleRate()); |
+ } |
// Use finalValue() to incorporate changes of AudioParamTimeline and |
// AudioSummingJunction from m_playbackRate AudioParam. |
@@ -542,10 +550,20 @@ void AudioBufferSourceHandler::clearPannerNode() |
void AudioBufferSourceHandler::handleStoppableSourceNode() |
{ |
- // If the source node is not looping, and we have a buffer, we can determine when the |
- // source would stop playing. |
+ // If the source node is not looping, and we have a buffer, we can determine when the source |
+ // would stop playing. This is intended to handle the (uncommon) scenario where start() has |
+ // been called but is never connected to the destination (directly or indirectly). By stopping |
+ // the node, the node can be collected. Otherwise, the node will never get collected, leaking |
+ // memory. |
if (!loop() && buffer() && isPlayingOrScheduled()) { |
- double stopTime = m_startTime + buffer()->duration(); |
+ // See crbug.com/478301. If a source node is started via start(), the source may not start |
+ // at that time but one quantum (128 frames) later. But we compute the stop time based on |
+ // the start time and the duration, so we end up stopping one quantum early. Thus, add a |
+ // little extra time; we just need to stop the source sometime after it should have stopped |
+ // if it hadn't already. We don't need to be super precise on when to stop. |
+ double extraStopTime = kExtraStopFrames / static_cast<double>(context()->sampleRate()); |
+ double stopTime = m_startTime + buffer()->duration() + extraStopTime; |
+ |
if (context()->currentTime() > stopTime) { |
// The context time has passed the time when the source nodes should have stopped |
// playing. Stop the node now and deref it. (But don't run the onEnded event because the |