Chromium Code Reviews| Index: Source/modules/webaudio/AudioContext.cpp |
| diff --git a/Source/modules/webaudio/AudioContext.cpp b/Source/modules/webaudio/AudioContext.cpp |
| index b5127c069232e519d28db54c806c8ea59c59fe53..0a4ffbd5c5342989f57d0d2e8e3058ceb19572ba 100644 |
| --- a/Source/modules/webaudio/AudioContext.cpp |
| +++ b/Source/modules/webaudio/AudioContext.cpp |
| @@ -30,6 +30,7 @@ |
| #include "bindings/core/v8/ExceptionMessages.h" |
| #include "bindings/core/v8/ExceptionState.h" |
| +#include "bindings/core/v8/ScriptState.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/ExceptionCode.h" |
| #include "core/html/HTMLMediaElement.h" |
| @@ -101,10 +102,12 @@ AudioContext::AudioContext(Document* document) |
| , m_isCleared(false) |
| , m_isInitialized(false) |
| , m_destinationNode(nullptr) |
| + , m_isResolvingResumePromises(false) |
| , m_automaticPullNodesNeedUpdating(false) |
| , m_connectionCount(0) |
| , m_audioThread(0) |
| , m_isOfflineContext(false) |
| + , m_state(Paused) |
| { |
| m_destinationNode = DefaultAudioDestinationNode::create(this); |
| @@ -121,10 +124,12 @@ AudioContext::AudioContext(Document* document, unsigned numberOfChannels, size_t |
| , m_isCleared(false) |
| , m_isInitialized(false) |
| , m_destinationNode(nullptr) |
| + , m_isResolvingResumePromises(false) |
| , m_automaticPullNodesNeedUpdating(false) |
| , m_connectionCount(0) |
| , m_audioThread(0) |
| , m_isOfflineContext(true) |
| + , m_state(Paused) |
| { |
| // Create a new destination for offline rendering. |
| m_renderTarget = AudioBuffer::create(numberOfChannels, numberOfFrames, sampleRate); |
| @@ -166,6 +171,7 @@ void AudioContext::initialize() |
| // NOTE: for now default AudioContext does not need an explicit startRendering() call from JavaScript. |
| // We may want to consider requiring it for symmetry with OfflineAudioContext. |
| m_destinationNode->startRendering(); |
|
haraken
2014/10/15 15:36:26
You can call startRendering() instead of m_destina
Raymond Toy
2014/10/15 19:50:50
Done.
|
| + setState(Running); |
|
haraken
2014/10/15 15:36:26
Then you can remove this. It's important to reduce
Raymond Toy
2014/10/15 19:50:49
Done.
|
| ++s_hardwareContextCount; |
| } |
| @@ -531,6 +537,84 @@ PeriodicWave* AudioContext::createPeriodicWave(Float32Array* real, Float32Array* |
| return PeriodicWave::create(sampleRate(), real, imag); |
| } |
| +String AudioContext::state() const |
| +{ |
| + switch (m_state) { |
| + case Paused: |
| + return "paused"; |
| + case Running: |
| + return "running"; |
| + case Released: |
| + return "released"; |
| + } |
| + ASSERT_NOT_REACHED(); |
| + return "running"; |
|
haraken
2014/10/15 15:36:26
Nit: I'd return "".
Raymond Toy
2014/10/15 19:50:50
Done.
|
| +} |
| + |
| +void AudioContext::setState(AudioContextState newState) |
| +{ |
| + // Validate the transitions |
| + switch (newState) { |
| + case Paused: |
| + ASSERT(m_state == Running); |
| + break; |
| + case Running: |
| + ASSERT(m_state == Paused); |
| + break; |
| + case Released: |
| + break; |
|
haraken
2014/10/15 15:36:26
Shall we add ASSERT(m_state == Released) ?
Raymond Toy
2014/10/15 16:31:56
When the close() method is implemented, it will be
|
| + } |
| + |
| + m_state = newState; |
| +} |
| + |
| +void AudioContext::suspendContext(ExceptionState& exceptionState) |
| +{ |
| + ASSERT(isMainThread()); |
| + AutoLocker locker(this); |
| + |
| + if (m_state == Released) { |
| + exceptionState.throwDOMException( |
| + InvalidStateError, |
| + "cannot suspend an AudioContext that has been released"); |
|
haraken
2014/10/15 15:36:26
Nit: You can immediately return here. Then you don
Raymond Toy
2014/10/15 19:50:50
Done.
|
| + } else { |
| + if (m_destinationNode && !isOfflineContext()) { |
| + m_destinationNode->stop(); |
| + setState(Paused); |
|
haraken
2014/10/15 15:36:26
Probably you want to implement a helper method for
Raymond Toy
2014/10/15 19:50:50
Done.
|
| + } |
| + } |
| +} |
| + |
| +ScriptPromise AudioContext::resumeContext(ScriptState* scriptState) |
| +{ |
| + ASSERT(isMainThread()); |
| + AutoLocker locker(this); |
| + |
| + RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState); |
| + |
| + ScriptPromise promise = resolver->promise(); |
| + |
| + if (isOfflineContext()) { |
| + // For offline context, resolve now, but reject if the context has been released. |
| + if (m_state == Released) |
| + resolver->reject(); |
| + else |
| + resolver->resolve(); |
| + } else { |
| + // Restart the destination node to pull on the audio graph. |
| + if (m_destinationNode) { |
| + m_destinationNode->startRendering(); |
|
haraken
2014/10/15 15:36:26
You can call startRendering() instead of m_destina
Raymond Toy
2014/10/15 19:50:50
Done.
|
| + setState(Running); |
|
haraken
2014/10/15 15:36:26
Then you can remove this. We want to reduce the ca
Raymond Toy
2014/10/15 19:50:50
Done.
|
| + } |
| + |
| + // Save the promise which will get resolved when the destination node starts pulling on the |
| + // graph again. |
| + m_resumePromises.append(resolver); |
| + } |
| + |
| + return promise; |
| +} |
| + |
| void AudioContext::notifyNodeFinishedProcessing(AudioNode* node) |
| { |
| ASSERT(isAudioThread()); |
| @@ -637,6 +721,8 @@ void AudioContext::handlePreRenderTasks() |
| handleDirtyAudioNodeOutputs(); |
| updateAutomaticPullNodes(); |
| + resolvePromisesForResume(); |
| + |
| unlock(); |
| } |
| } |
| @@ -802,6 +888,36 @@ void AudioContext::processAutomaticPullNodes(size_t framesToProcess) |
| m_renderingAutomaticPullNodes[i]->processIfNecessary(framesToProcess); |
| } |
| +void AudioContext::resolvePromisesForResumeOnMainThread() |
| +{ |
| + ASSERT(isMainThread()); |
| + AutoLocker locker(this); |
| + |
| + for (unsigned k = 0; k < m_resumePromises.size(); ++k) { |
| + if (m_state == Released) |
| + m_resumePromises[k]->reject(); |
|
yhirano
2014/10/15 09:02:27
[opt] In general, it would be better to reject a p
Raymond Toy
2014/10/15 19:50:49
Good idea. I've added an Exception. The spec isn'
|
| + else |
| + m_resumePromises[k]->resolve(); |
| + } |
| + |
| + m_resumePromises.clear(); |
| + m_isResolvingResumePromises = false; |
| +} |
| + |
| +void AudioContext::resolvePromisesForResume() |
| +{ |
| + // This is run inside the AudioContext's lock when handling pre-render tasks. |
|
haraken
2014/10/15 15:36:26
Nit: is run => runs
Raymond Toy
2014/10/15 19:50:50
Done.
|
| + ASSERT(isAudioThread()); |
|
haraken
2014/10/15 15:36:26
Shall we add ASSERT(isGraphOwner()) ?
Raymond Toy
2014/10/15 19:50:50
Done.
|
| + |
| + // Resolve any pending promises created by resume(). Only do this we if haven't already started |
| + // resolving these promises. This gets called very often and it takes some time to resolve the |
| + // promises in the main thread. |
| + if (!m_isResolvingResumePromises && m_resumePromises.size() > 0) { |
| + m_isResolvingResumePromises = true; |
| + callOnMainThread(bind(&AudioContext::resolvePromisesForResumeOnMainThread, this)); |
| + } |
| +} |
| + |
| const AtomicString& AudioContext::interfaceName() const |
| { |
| return EventTargetNames::AudioContext; |
| @@ -814,7 +930,9 @@ ExecutionContext* AudioContext::executionContext() const |
| void AudioContext::startRendering() |
| { |
| + ASSERT(isMainThread()); |
| destination()->startRendering(); |
| + setState(Running); |
| } |
| void AudioContext::fireCompletionEvent() |
| @@ -825,6 +943,8 @@ void AudioContext::fireCompletionEvent() |
| AudioBuffer* renderedBuffer = m_renderTarget.get(); |
| + setState(Released); |
| + |
| ASSERT(renderedBuffer); |
| if (!renderedBuffer) |
| return; |