Index: Source/modules/webaudio/AudioContext.cpp |
diff --git a/Source/modules/webaudio/AudioContext.cpp b/Source/modules/webaudio/AudioContext.cpp |
index 306485e48560889c5626c5c53bb6a3836b9f377e..e4fd7b7dee28250b02e6a236100dc514e36243b3 100644 |
--- a/Source/modules/webaudio/AudioContext.cpp |
+++ b/Source/modules/webaudio/AudioContext.cpp |
@@ -115,6 +115,7 @@ AudioContext::AudioContext(Document* document) |
, m_isOfflineContext(false) |
, m_contextState(Suspended) |
, m_cachedSampleFrame(0) |
+ , m_closedContextSampleRate(-1) |
{ |
m_didInitializeContextGraphMutex = true; |
m_destinationNode = DefaultAudioDestinationNode::create(this); |
@@ -136,6 +137,7 @@ AudioContext::AudioContext(Document* document, unsigned numberOfChannels, size_t |
, m_isOfflineContext(true) |
, m_contextState(Suspended) |
, m_cachedSampleFrame(0) |
+ , m_closedContextSampleRate(-1) |
{ |
m_didInitializeContextGraphMutex = true; |
// Create a new destination for offline rendering. |
@@ -152,12 +154,14 @@ AudioContext::~AudioContext() |
fprintf(stderr, "%p: AudioContext::~AudioContext(): %u\n", this, m_contextId); |
#endif |
// AudioNodes keep a reference to their context, so there should be no way to be in the destructor if there are still AudioNodes around. |
+ |
ASSERT(!m_isInitialized); |
ASSERT(!m_referencedNodes.size()); |
ASSERT(!m_finishedNodes.size()); |
ASSERT(!m_suspendResolvers.size()); |
ASSERT(!m_isResolvingResumePromises); |
ASSERT(!m_resumeResolvers.size()); |
+ ASSERT(!m_audioDecoderResolvers.size()); |
} |
void AudioContext::initialize() |
@@ -233,6 +237,14 @@ void AudioContext::uninitialize() |
ASSERT(m_listener); |
m_listener->waitForHRTFDatabaseLoaderThreadCompletion(); |
+ // Reject any decodeAudioData promises that haven't been fulfilled yet. |
+ for (auto& resolver : m_audioDecoderResolvers) { |
+ resolver->reject(DOMException::create(InvalidStateError, "Audio context is going away")); |
+ } |
+ m_audioDecoderResolvers.clear(); |
+ |
+ // Uninitialization done, so clear flags to indicate that the AudioContext has no pending |
+ // activity anymore. |
clear(); |
} |
@@ -269,20 +281,30 @@ AudioBuffer* AudioContext::createBuffer(unsigned numberOfChannels, size_t number |
return AudioBuffer::create(numberOfChannels, numberOfFrames, sampleRate, exceptionState); |
} |
-void AudioContext::decodeAudioData(DOMArrayBuffer* audioData, AudioBufferCallback* successCallback, AudioBufferCallback* errorCallback, ExceptionState& exceptionState) |
+ScriptPromise AudioContext::decodeAudioData(ScriptState* scriptState, DOMArrayBuffer* audioData, AudioBufferCallback* successCallback, AudioBufferCallback* errorCallback, ExceptionState& exceptionState) |
{ |
- if (isContextClosed()) { |
- throwExceptionForClosedState(exceptionState); |
- return; |
- } |
- |
if (!audioData) { |
- exceptionState.throwDOMException( |
- SyntaxError, |
+ RefPtrWillBeRawPtr<DOMException> error = DOMException::create( |
+ NotSupportedError, |
"invalid ArrayBuffer for audioData."); |
- return; |
+ if (errorCallback) { |
+ errorCallback->handleEvent(error.get()); |
+ } |
+ return ScriptPromise::rejectWithDOMException(scriptState, error); |
} |
- m_audioDecoder.decodeAsync(audioData, sampleRate(), successCallback, errorCallback); |
+ |
+ RefPtrWillBeRawPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState); |
+ ScriptPromise promise = resolver->promise(); |
+ |
+ m_audioDecoderResolvers.append(resolver); |
+ |
+ float rate = isContextClosed() ? m_closedContextSampleRate : sampleRate(); |
+ |
+ ASSERT(rate > 0); |
+ |
+ m_audioDecoder.decodeAsync(audioData, rate, successCallback, errorCallback, resolver.get(), this); |
+ |
+ return promise; |
} |
AudioBufferSourceNode* AudioContext::createBufferSource(ExceptionState& exceptionState) |
@@ -912,6 +934,19 @@ void AudioContext::handleStoppableSourceNodes() |
} |
} |
} |
+ |
+void AudioContext::removeAudioDecoderResolver(ScriptPromiseResolver* resolver) |
+{ |
+ ASSERT(isMainThread()); |
+ |
+ for (size_t k = 0; k < m_audioDecoderResolvers.size(); ++k) { |
+ if (resolver == m_audioDecoderResolvers.at(k)) { |
+ m_audioDecoderResolvers.remove(k); |
+ break; |
+ } |
+ } |
+} |
+ |
void AudioContext::handlePreRenderTasks() |
{ |
ASSERT(isAudioThread()); |
@@ -1129,8 +1164,7 @@ void AudioContext::rejectPendingResolvers() |
{ |
ASSERT(isMainThread()); |
- // Audio context is closing down so reject any suspend or resume promises that are still |
- // pending. |
+ // Audio context is closing down so reject any promises that are still pending. |
for (auto& resolver : m_suspendResolvers) { |
resolver->reject(DOMException::create(InvalidStateError, "Audio context is going away")); |
@@ -1204,6 +1238,7 @@ void AudioContext::fireCompletionEvent() |
DEFINE_TRACE(AudioContext) |
{ |
+ visitor->trace(m_audioDecoderResolvers); |
visitor->trace(m_closeResolver); |
visitor->trace(m_offlineResolver); |
visitor->trace(m_renderTarget); |
@@ -1271,6 +1306,9 @@ ScriptPromise AudioContext::closeContext(ScriptState* scriptState) |
"Cannot close a context that is being closed or has already been closed.")); |
} |
+ // Save the current sample rate for any subsequent decodeAudioData calls. |
+ m_closedContextSampleRate = sampleRate(); |
+ |
m_closeResolver = ScriptPromiseResolver::create(scriptState); |
ScriptPromise promise = m_closeResolver->promise(); |