void AudioContext::initialize() { if (isInitialized()) return; FFTFrame::initialize(); m_listener = AudioListener::create(); if (m_destinationNode.get()) { m_destinationNode->handler().initialize(); if (!isOfflineContext()) { // This starts the audio thread. The destination node's provideInput() method will now be called repeatedly to render audio. // Each time provideInput() is called, a portion of the audio stream is rendered. Let's call this time period a "render quantum". // 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. startRendering(); ++s_hardwareContextCount; } m_contextId = s_contextId++; m_isInitialized = true; #if DEBUG_AUDIONODE_REFERENCES fprintf(stderr, "%p: AudioContext::AudioContext(): %u #%u\n", this, m_contextId, AudioContext::s_hardwareContextCount); #endif } }
ScriptPromise AudioContext::resumeContext(ScriptState* scriptState) { ASSERT(isMainThread()); AutoLocker locker(this); if (isOfflineContext()) { return ScriptPromise::rejectWithDOMException( scriptState, DOMException::create( InvalidStateError, "cannot resume an OfflineAudioContext")); } if (isContextClosed()) { return ScriptPromise::rejectWithDOMException( scriptState, DOMException::create( InvalidStateError, "cannot resume a closed AudioContext")); } RefPtrWillBeRawPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState); ScriptPromise promise = resolver->promise(); // Restart the destination node to pull on the audio graph. if (m_destinationNode) startRendering(); // Save the resolver which will get resolved when the destination node starts pulling on the // graph again. m_resumeResolvers.append(resolver); return promise; }
void AudioContext::lazyInitialize() { if (m_isInitialized) return; // Don't allow the context to initialize a second time after it's already been explicitly uninitialized. ASSERT(!m_isAudioThreadFinished); if (m_isAudioThreadFinished) return; if (m_destinationNode.get()) { m_destinationNode->initialize(); if (!isOfflineContext()) { document()->addAudioProducer(this); // This starts the audio thread. The destination node's provideInput() method will now be called repeatedly to render audio. // Each time provideInput() is called, a portion of the audio stream is rendered. Let's call this time period a "render quantum". // 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. startRendering(); ++s_hardwareContextCount; } } m_isInitialized = true; }
void AudioContext::uninitialize() { ASSERT(isMainThread()); if (!m_isInitialized) return; // This stops the audio thread and all audio rendering. m_destinationNode->uninitialize(); // Don't allow the context to initialize a second time after it's already been explicitly uninitialized. m_isAudioThreadFinished = true; if (!isOfflineContext()) { document()->removeAudioProducer(this); ASSERT(s_hardwareContextCount); --s_hardwareContextCount; // Offline contexts move to 'Closed' state when dispatching the completion event. setState(State::Closed); } // Get rid of the sources which may still be playing. derefUnfinishedSourceNodes(); m_isInitialized = false; }
void AudioContext::resume(Promise&& promise) { if (isOfflineContext()) { promise.reject(INVALID_STATE_ERR); return; } if (m_state == State::Running) { promise.resolve(nullptr); return; } if (m_state == State::Closed || !m_destinationNode) { promise.reject(0); return; } addReaction(State::Running, WTFMove(promise)); if (!willBeginPlayback()) return; lazyInitialize(); RefPtr<AudioContext> strongThis(this); m_destinationNode->resume([strongThis] { strongThis->setState(State::Running); }); }
void AudioContext::suspend(Promise&& promise) { if (isOfflineContext()) { promise.reject(INVALID_STATE_ERR); return; } if (m_state == State::Suspended) { promise.resolve(nullptr); return; } if (m_state == State::Closed || m_state == State::Interrupted || !m_destinationNode) { promise.reject(0); return; } addReaction(State::Suspended, WTFMove(promise)); if (!willPausePlayback()) return; lazyInitialize(); RefPtr<AudioContext> strongThis(this); m_destinationNode->suspend([strongThis] { strongThis->setState(State::Suspended); }); }
void AudioContext::uninitialize() { ASSERT(isMainThread()); if (m_isInitialized) { // This stops the audio thread and all audio rendering. m_destinationNode->uninitialize(); // Don't allow the context to initialize a second time after it's already been explicitly uninitialized. m_isAudioThreadFinished = true; // We have to release our reference to the destination node before the context will ever be deleted since the destination node holds a reference to the context. m_destinationNode.clear(); if (!isOfflineContext()) { ASSERT(s_hardwareContextCount); --s_hardwareContextCount; } // Get rid of the sources which may still be playing. derefUnfinishedSourceNodes(); deleteMarkedNodes(); // Because the AudioBuffers are garbage collected, we can't delete them here. // Instead, at least release the potentially large amount of allocated memory for the audio data. // Note that we do this *after* the context is uninitialized and stops processing audio. for (unsigned i = 0; i < m_allocatedBuffers.size(); ++i) m_allocatedBuffers[i]->releaseMemory(); m_allocatedBuffers.clear(); m_isInitialized = false; } }
void AudioContext::uninitialize() { ASSERT(isMainThread()); if (m_isInitialized) { // Protect this object from being deleted before we finish uninitializing. RefPtr<AudioContext> protect(this); // This stops the audio thread and all audio rendering. m_destinationNode->uninitialize(); // Don't allow the context to initialize a second time after it's already been explicitly uninitialized. m_isAudioThreadFinished = true; // We have to release our reference to the destination node before the context will ever be deleted since the destination node holds a reference to the context. m_destinationNode.clear(); if (!isOfflineContext()) { ASSERT(s_hardwareContextCount); --s_hardwareContextCount; } // Get rid of the sources which may still be playing. derefUnfinishedSourceNodes(); deleteMarkedNodes(); m_isInitialized = false; } }
void AudioContext::stopRendering() { ASSERT(isMainThread()); ASSERT(m_destinationNode); ASSERT(!isOfflineContext()); if (m_contextState == Running) { destination()->audioDestinationHandler().stopRendering(); setContextState(Suspended); } }
void AudioContext::uninitialize() { ASSERT(isMainThread()); if (!isInitialized()) return; m_isInitialized = false; // This stops the audio thread and all audio rendering. if (m_destinationNode) m_destinationNode->handler().uninitialize(); if (!isOfflineContext()) { ASSERT(s_hardwareContextCount); --s_hardwareContextCount; } // Get rid of the sources which may still be playing. derefUnfinishedSourceNodes(); // Reject any pending resolvers before we go away. rejectPendingResolvers(); // For an offline audio context, the completion event will set the state to closed. For an // online context, we need to do it here. We only want to set the closed state once. if (!isOfflineContext()) setContextState(Closed); // Resolve the promise now, if any if (m_closeResolver) m_closeResolver->resolve(); ASSERT(m_listener); m_listener->waitForHRTFDatabaseLoaderThreadCompletion(); clear(); }
ScriptPromise AudioContext::suspendContext(ScriptState* scriptState) { ASSERT(isMainThread()); AutoLocker locker(this); if (isOfflineContext()) { return ScriptPromise::rejectWithDOMException( scriptState, DOMException::create( InvalidStateError, "cannot suspend an OfflineAudioContext")); } RefPtrWillBeRawPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState); ScriptPromise promise = resolver->promise(); // Save the resolver which will get resolved at the end of the rendering quantum. m_suspendResolvers.append(resolver); return promise; }
void AudioContext::close(Promise&& promise) { if (isOfflineContext()) { promise.reject(INVALID_STATE_ERR); return; } if (m_state == State::Closed || !m_destinationNode) { promise.resolve(nullptr); return; } addReaction(State::Closed, WTFMove(promise)); lazyInitialize(); RefPtr<AudioContext> strongThis(this); m_destinationNode->close([strongThis] { strongThis->setState(State::Closed); strongThis->uninitialize(); }); }
ScriptPromise AudioContext::closeContext(ScriptState* scriptState) { if (isOfflineContext()) { return ScriptPromise::rejectWithDOMException( scriptState, DOMException::create(InvalidStateError, "Cannot call close() on an OfflineAudioContext.")); } if (isContextClosed()) { // We've already closed the context previously, but it hasn't yet been resolved, so just // create a new promise and reject it. return ScriptPromise::rejectWithDOMException( scriptState, DOMException::create(InvalidStateError, "Cannot close a context that is being closed or has already been closed.")); } m_closeResolver = ScriptPromiseResolver::create(scriptState); ScriptPromise promise = m_closeResolver->promise(); // Before closing the context go and disconnect all nodes, allowing them to be collected. This // will also break any connections to the destination node. Any unfinished sourced nodes will // get stopped when the context is unitialized. for (auto& node : m_liveNodes) { if (node) { for (unsigned k = 0; k < node->numberOfOutputs(); ++k) node->handler().disconnectWithoutException(k); } } // Stop the audio context. This will stop the destination node from pulling audio anymore. And // since we have disconnected the destination from the audio graph, and thus has no references, // the destination node can GCed if JS has no references. stop() will also resolve the Promise // created here. stop(); return promise; }
void AudioContext::uninitialize() { ASSERT(isMainThread()); if (!m_isInitialized) return; // This stops the audio thread and all audio rendering. m_destinationNode->uninitialize(); // Don't allow the context to initialize a second time after it's already been explicitly uninitialized. m_isAudioThreadFinished = true; if (!isOfflineContext()) { ASSERT(s_hardwareContextCount); --s_hardwareContextCount; } // Get rid of the sources which may still be playing. derefUnfinishedSourceNodes(); m_isInitialized = false; }