Bug 1586370 - Use MozPromise for AudioContextOperations and NotifyWhenGraphStarted. r=padenot

This removes a level of indirection where the graph had to call back into
AudioContext. It also removes a dependency on the graph from GraphDriver, where
it can now just resolve a MozPromiseHolder instead.

Differential Revision: https://phabricator.services.mozilla.com/D56075

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Andreas Pehrson 2019-12-18 22:50:52 +00:00
parent 43d5cf8ee3
commit 1adcbfee14
8 changed files with 249 additions and 226 deletions

View File

@ -7,6 +7,7 @@
#include <MediaTrackGraphImpl.h>
#include "mozilla/dom/AudioContext.h"
#include "mozilla/dom/AudioDeviceInfo.h"
#include "mozilla/dom/BaseAudioContextBinding.h"
#include "mozilla/dom/WorkletThread.h"
#include "mozilla/SharedThreadPool.h"
#include "mozilla/ClearOnShutdown.h"
@ -401,12 +402,20 @@ AsyncCubebTask::Run() {
}
TrackAndPromiseForOperation::TrackAndPromiseForOperation(
MediaTrack* aTrack, void* aPromise, dom::AudioContextOperation aOperation,
dom::AudioContextOperationFlags aFlags)
MediaTrack* aTrack, dom::AudioContextOperation aOperation,
AbstractThread* aMainThread,
MozPromiseHolder<MediaTrackGraph::AudioContextOperationPromise>&& aHolder)
: mTrack(aTrack),
mPromise(aPromise),
mOperation(aOperation),
mFlags(aFlags) {}
mMainThread(aMainThread),
mHolder(std::move(aHolder)) {}
TrackAndPromiseForOperation::TrackAndPromiseForOperation(
TrackAndPromiseForOperation&& aOther) noexcept
: mTrack(std::move(aOther.mTrack)),
mOperation(aOther.mOperation),
mMainThread(std::move(aOther.mMainThread)),
mHolder(std::move(aOther.mHolder)) {}
AudioCallbackDriver::AudioCallbackDriver(
MediaTrackGraphImpl* aGraphImpl, uint32_t aSampleRate,
@ -422,6 +431,7 @@ AudioCallbackDriver::AudioCallbackDriver(
mStarted(false),
mInitShutdownThread(
SharedThreadPool::Get(NS_LITERAL_CSTRING("CubebOperation"), 1)),
mPromisesForOperation("AudioCallbackDriver::mPromisesForOperation"),
mAudioThreadId(std::thread::id()),
mAudioThreadRunning(false),
mShouldFallbackIfError(false),
@ -449,7 +459,12 @@ AudioCallbackDriver::AudioCallbackDriver(
}
AudioCallbackDriver::~AudioCallbackDriver() {
MOZ_ASSERT(mPromisesForOperation.IsEmpty());
#ifdef DEBUG
{
auto promises = mPromisesForOperation.Lock();
MOZ_ASSERT(promises->IsEmpty());
}
#endif
#if defined(XP_WIN)
if (XRE_IsContentProcess()) {
audio::AudioNotificationReceiver::Unregister(this);
@ -979,48 +994,48 @@ uint32_t AudioCallbackDriver::IterationDuration() {
bool AudioCallbackDriver::IsStarted() { return mStarted; }
void AudioCallbackDriver::EnqueueTrackAndPromiseForOperation(
MediaTrack* aTrack, void* aPromise, dom::AudioContextOperation aOperation,
dom::AudioContextOperationFlags aFlags) {
MediaTrack* aTrack, dom::AudioContextOperation aOperation,
AbstractThread* aMainThread,
MozPromiseHolder<MediaTrackGraph::AudioContextOperationPromise>&& aHolder) {
MOZ_ASSERT(OnGraphThread() || !ThreadRunning());
MonitorAutoLock mon(mGraphImpl->GetMonitor());
MOZ_ASSERT((aFlags | dom::AudioContextOperationFlags::SendStateChange) ||
!aPromise);
if (aFlags == dom::AudioContextOperationFlags::SendStateChange) {
mPromisesForOperation.AppendElement(
TrackAndPromiseForOperation(aTrack, aPromise, aOperation, aFlags));
}
auto promises = mPromisesForOperation.Lock();
promises->AppendElement(TrackAndPromiseForOperation(
aTrack, aOperation, aMainThread, std::move(aHolder)));
}
void AudioCallbackDriver::CompleteAudioContextOperations(
AsyncCubebOperation aOperation) {
MOZ_ASSERT(OnCubebOperationThread());
AutoTArray<TrackAndPromiseForOperation, 1> array;
// We can't lock for the whole function because AudioContextOperationCompleted
// will grab the monitor
{
MonitorAutoLock mon(GraphImpl()->GetMonitor());
array.SwapElements(mPromisesForOperation);
}
for (uint32_t i = 0; i < array.Length(); i++) {
TrackAndPromiseForOperation& s = array[i];
auto promises = mPromisesForOperation.Lock();
for (uint32_t i = 0; i < promises->Length(); i++) {
TrackAndPromiseForOperation& s = promises.ref()[i];
if ((aOperation == AsyncCubebOperation::INIT &&
s.mOperation == dom::AudioContextOperation::Resume) ||
(aOperation == AsyncCubebOperation::SHUTDOWN &&
s.mOperation != dom::AudioContextOperation::Resume)) {
MOZ_ASSERT(s.mFlags == dom::AudioContextOperationFlags::SendStateChange);
GraphImpl()->AudioContextOperationCompleted(s.mTrack, s.mPromise,
s.mOperation, s.mFlags);
array.RemoveElementAt(i);
AudioContextState state;
switch (s.mOperation) {
case dom::AudioContextOperation::Resume:
state = dom::AudioContextState::Running;
break;
case dom::AudioContextOperation::Suspend:
state = dom::AudioContextState::Suspended;
break;
case dom::AudioContextOperation::Close:
state = dom::AudioContextState::Closed;
break;
default:
MOZ_CRASH("Unexpected operation");
}
s.mMainThread->Dispatch(NS_NewRunnableFunction(
"AudioContextOperation::Resolve",
[holder = std::move(s.mHolder), state]() mutable {
holder.Resolve(state, __func__);
}));
promises->RemoveElementAt(i);
i--;
}
}
if (!array.IsEmpty()) {
MonitorAutoLock mon(GraphImpl()->GetMonitor());
mPromisesForOperation.AppendElements(array);
}
}
TimeDuration AudioCallbackDriver::AudioOutputLatency() {

View File

@ -13,6 +13,7 @@
#include "SelfRef.h"
#include "mozilla/Atomics.h"
#include "mozilla/dom/AudioContext.h"
#include "mozilla/DataMutex.h"
#include "mozilla/SharedThreadPool.h"
#include "mozilla/StaticPtr.h"
@ -381,13 +382,16 @@ class OfflineClockDriver : public ThreadedDriver {
};
struct TrackAndPromiseForOperation {
TrackAndPromiseForOperation(MediaTrack* aTrack, void* aPromise,
dom::AudioContextOperation aOperation,
dom::AudioContextOperationFlags aFlags);
TrackAndPromiseForOperation(
MediaTrack* aTrack, dom::AudioContextOperation aOperation,
AbstractThread* aMainThread,
MozPromiseHolder<MediaTrackGraph::AudioContextOperationPromise>&&
aHolder);
TrackAndPromiseForOperation(TrackAndPromiseForOperation&& aOther) noexcept;
RefPtr<MediaTrack> mTrack;
void* mPromise;
dom::AudioContextOperation mOperation;
dom::AudioContextOperationFlags mFlags;
RefPtr<AbstractThread> mMainThread;
MozPromiseHolder<MediaTrackGraph::AudioContextOperationPromise> mHolder;
};
enum class AsyncCubebOperation { INIT, SHUTDOWN };
@ -478,11 +482,13 @@ class AudioCallbackDriver : public GraphDriver,
return AudioInputType::Unknown;
}
/* Enqueue a promise that is going to be resolved when a specific operation
* occurs on the cubeb stream. */
/* Enqueue a promise that is going to be resolved on the given main thread
* when a specific operation occurs on the cubeb stream. */
void EnqueueTrackAndPromiseForOperation(
MediaTrack* aTrack, void* aPromise, dom::AudioContextOperation aOperation,
dom::AudioContextOperationFlags aFlags);
MediaTrack* aTrack, dom::AudioContextOperation aOperation,
AbstractThread* aMainThread,
MozPromiseHolder<MediaTrackGraph::AudioContextOperationPromise>&&
aHolder);
std::thread::id ThreadId() { return mAudioThreadId.load(); }
@ -577,8 +583,7 @@ class AudioCallbackDriver : public GraphDriver,
/* Shared thread pool with up to one thread for off-main-thread
* initialization and shutdown of the audio stream via AsyncCubebTask. */
const RefPtr<SharedThreadPool> mInitShutdownThread;
/* This must be accessed with the graph monitor held. */
AutoTArray<TrackAndPromiseForOperation, 1> mPromisesForOperation;
DataMutex<AutoTArray<TrackAndPromiseForOperation, 1>> mPromisesForOperation;
cubeb_device_pref mInputDevicePreference;
/* The mixer that the graph mixes into during an iteration. Audio thread only.
*/

View File

@ -3249,52 +3249,65 @@ void MediaTrackGraphImpl::RemoveTrack(MediaTrack* aTrack) {
}
}
class GraphStartedRunnable final : public Runnable {
public:
GraphStartedRunnable(AudioNodeTrack* aTrack, MediaTrackGraph* aGraph)
: Runnable("GraphStartedRunnable"), mTrack(aTrack), mGraph(aGraph) {}
NS_IMETHOD Run() override {
mGraph->NotifyWhenGraphStarted(mTrack);
return NS_OK;
}
private:
RefPtr<AudioNodeTrack> mTrack;
MediaTrackGraph* mGraph;
};
void MediaTrackGraph::NotifyWhenGraphStarted(AudioNodeTrack* aTrack) {
auto MediaTrackGraph::NotifyWhenGraphStarted(AudioNodeTrack* aTrack)
-> RefPtr<GraphStartedPromise> {
MOZ_ASSERT(NS_IsMainThread());
MozPromiseHolder<GraphStartedPromise> h;
RefPtr<GraphStartedPromise> p = h.Ensure(__func__);
aTrack->GraphImpl()->NotifyWhenGraphStarted(aTrack, std::move(h));
return p;
}
void MediaTrackGraphImpl::NotifyWhenGraphStarted(
RefPtr<AudioNodeTrack> aTrack,
MozPromiseHolder<GraphStartedPromise>&& aHolder) {
class GraphStartedNotificationControlMessage : public ControlMessage {
RefPtr<AudioNodeTrack> mAudioNodeTrack;
MozPromiseHolder<GraphStartedPromise> mHolder;
public:
explicit GraphStartedNotificationControlMessage(AudioNodeTrack* aTrack)
: ControlMessage(aTrack) {}
GraphStartedNotificationControlMessage(
RefPtr<AudioNodeTrack> aTrack,
MozPromiseHolder<GraphStartedPromise>&& aHolder)
: ControlMessage(nullptr),
mAudioNodeTrack(std::move(aTrack)),
mHolder(std::move(aHolder)) {}
void Run() override {
// This runs on the graph thread, so when this runs, and the current
// driver is an AudioCallbackDriver, we know the audio hardware is
// started. If not, we are going to switch soon, keep reposting this
// ControlMessage.
MediaTrackGraphImpl* graphImpl = mTrack->GraphImpl();
MediaTrackGraphImpl* graphImpl = mAudioNodeTrack->GraphImpl();
if (graphImpl->CurrentDriver()->AsAudioCallbackDriver()) {
nsCOMPtr<nsIRunnable> event = new dom::StateChangeTask(
mTrack->AsAudioNodeTrack(), nullptr, AudioContextState::Running);
graphImpl->Dispatch(event.forget());
// Avoid Resolve's locking on the graph thread by doing it on main.
graphImpl->Dispatch(NS_NewRunnableFunction(
"MediaTrackGraphImpl::NotifyWhenGraphStarted::Resolver",
[holder = std::move(mHolder)]() mutable {
holder.Resolve(true, __func__);
}));
} else {
nsCOMPtr<nsIRunnable> event = new GraphStartedRunnable(
mTrack->AsAudioNodeTrack(), mTrack->Graph());
graphImpl->Dispatch(event.forget());
graphImpl->DispatchToMainThreadStableState(
NewRunnableMethod<
StoreCopyPassByRRef<RefPtr<AudioNodeTrack>>,
StoreCopyPassByRRef<MozPromiseHolder<GraphStartedPromise>>>(
"MediaTrackGraphImpl::NotifyWhenGraphStarted", graphImpl,
&MediaTrackGraphImpl::NotifyWhenGraphStarted,
std::move(mAudioNodeTrack), std::move(mHolder)));
}
}
void RunDuringShutdown() override {}
void RunDuringShutdown() override {
mHolder.Reject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
}
};
if (!aTrack->IsDestroyed()) {
MediaTrackGraphImpl* graphImpl = static_cast<MediaTrackGraphImpl*>(this);
graphImpl->AppendMessage(
MakeUnique<GraphStartedNotificationControlMessage>(aTrack));
if (aTrack->IsDestroyed()) {
aHolder.Reject(NS_ERROR_NOT_AVAILABLE, __func__);
return;
}
MediaTrackGraphImpl* graph = aTrack->GraphImpl();
graph->AppendMessage(MakeUnique<GraphStartedNotificationControlMessage>(
std::move(aTrack), std::move(aHolder)));
}
void MediaTrackGraphImpl::IncrementSuspendCount(MediaTrack* aTrack) {
@ -3353,40 +3366,10 @@ void MediaTrackGraphImpl::SuspendOrResumeTracks(
#endif
}
void MediaTrackGraphImpl::AudioContextOperationCompleted(
MediaTrack* aTrack, void* aPromise, AudioContextOperation aOperation,
AudioContextOperationFlags aFlags) {
if (aFlags != AudioContextOperationFlags::SendStateChange) {
MOZ_ASSERT(!aPromise);
return;
}
// This can be called from the thread created to do cubeb operation, or the
// MTG thread. The pointers passed back here are refcounted, so are still
// alive.
AudioContextState state;
switch (aOperation) {
case AudioContextOperation::Suspend:
state = AudioContextState::Suspended;
break;
case AudioContextOperation::Resume:
state = AudioContextState::Running;
break;
case AudioContextOperation::Close:
state = AudioContextState::Closed;
break;
default:
MOZ_CRASH("Not handled.");
}
nsCOMPtr<nsIRunnable> event =
new dom::StateChangeTask(aTrack->AsAudioNodeTrack(), aPromise, state);
mAbstractMainThread->Dispatch(event.forget());
}
void MediaTrackGraphImpl::ApplyAudioContextOperationImpl(
MediaTrack* aDestinationTrack, const nsTArray<MediaTrack*>& aTracks,
AudioContextOperation aOperation, void* aPromise,
AudioContextOperationFlags aFlags) {
AudioContextOperation aOperation,
MozPromiseHolder<AudioContextOperationPromise>&& aHolder) {
MOZ_ASSERT(OnGraphThread());
SuspendOrResumeTracks(aOperation, aTracks);
@ -3401,13 +3384,13 @@ void MediaTrackGraphImpl::ApplyAudioContextOperationImpl(
}
}
// If we have suspended the last AudioContext, and we don't have other
// tracks that have audio, this graph will automatically switch to a
// SystemCallbackDriver, because it can't find a MediaTrack that has an audio
// track. When resuming, force switching to an AudioCallbackDriver (if we're
// not already switching). It would have happened at the next iteration
// anyways, but doing this now save some time.
if (aOperation == AudioContextOperation::Resume) {
// If we have suspended the last AudioContext, and we don't have other
// tracks that have audio, this graph will automatically switch to a
// SystemCallbackDriver, because it can't find a MediaTrack that has an
// audio track. When resuming, force switching to an AudioCallbackDriver (if
// we're not already switching). It would have happened at the next
// iteration anyways, but doing this now save some time.
if (!CurrentDriver()->AsAudioCallbackDriver()) {
AudioCallbackDriver* driver;
if (switching) {
@ -3421,28 +3404,44 @@ void MediaTrackGraphImpl::ApplyAudioContextOperationImpl(
MonitorAutoLock lock(mMonitor);
CurrentDriver()->SwitchAtNextIteration(driver);
}
driver->EnqueueTrackAndPromiseForOperation(aDestinationTrack, aPromise,
aOperation, aFlags);
driver->EnqueueTrackAndPromiseForOperation(aDestinationTrack, aOperation,
mAbstractMainThread,
std::move(aHolder));
} else {
// We are resuming a context, but we are already using an
// AudioCallbackDriver, we can resolve the promise now.
AudioContextOperationCompleted(aDestinationTrack, aPromise, aOperation,
aFlags);
// AudioCallbackDriver, we can resolve the promise now. We do this on main
// thread to avoid locking the holder's mutex on the graph thread.
DispatchToMainThreadStableState(NS_NewRunnableFunction(
"MediaTrackGraphImpl::ApplyAudioContextOperationImpl::Resolve1",
[holder = std::move(aHolder)]() mutable {
holder.Resolve(AudioContextState::Running, __func__);
}));
}
} else {
// Close, suspend: check if we are going to switch to a
// SystemAudioCallbackDriver, and pass the promise to the
// AudioCallbackDriver if that's the case, so it can notify the content.
// This is the same logic as in UpdateTrackOrder, but it's simpler to have
// it here as well so we don't have to store the Promise(s) on the Graph.
AudioContextState state;
switch (aOperation) {
case AudioContextOperation::Suspend:
state = AudioContextState::Suspended;
break;
case AudioContextOperation::Close:
state = AudioContextState::Closed;
break;
default:
MOZ_CRASH("Unexpected operation");
}
}
// Close, suspend: check if we are going to switch to a
// SystemAudioCallbackDriver, and pass the promise to the AudioCallbackDriver
// if that's the case, so it can notify the content.
// This is the same logic as in UpdateTrackOrder, but it's simpler to have it
// here as well so we don't have to store the Promise(s) on the Graph.
if (aOperation != AudioContextOperation::Resume) {
bool audioTrackPresent = AudioTrackPresent();
if (!audioTrackPresent && CurrentDriver()->AsAudioCallbackDriver()) {
CurrentDriver()
->AsAudioCallbackDriver()
->EnqueueTrackAndPromiseForOperation(aDestinationTrack, aPromise,
aOperation, aFlags);
->EnqueueTrackAndPromiseForOperation(aDestinationTrack, aOperation,
mAbstractMainThread,
std::move(aHolder));
SystemClockDriver* driver;
if (!nextDriver) {
@ -3458,7 +3457,8 @@ void MediaTrackGraphImpl::ApplyAudioContextOperationImpl(
nextDriver->AsSystemClockDriver()->IsFallback());
if (nextDriver->AsAudioCallbackDriver()) {
nextDriver->AsAudioCallbackDriver()->EnqueueTrackAndPromiseForOperation(
aDestinationTrack, aPromise, aOperation, aFlags);
aDestinationTrack, aOperation, mAbstractMainThread,
std::move(aHolder));
} else {
// If this is not an AudioCallbackDriver, this means we failed opening
// an AudioCallbackDriver in the past, and we're constantly trying to
@ -3468,41 +3468,48 @@ void MediaTrackGraphImpl::ApplyAudioContextOperationImpl(
// (because suspend or close have been called on an AudioContext, or
// we've closed the page), but we're already running one. We can just
// resolve the promise now: we're already running off a system thread.
AudioContextOperationCompleted(aDestinationTrack, aPromise, aOperation,
aFlags);
// We do this on main
// thread to avoid locking the holder's mutex on the graph thread.
DispatchToMainThreadStableState(NS_NewRunnableFunction(
"MediaTrackGraphImpl::ApplyAudioContextOperationImpl::Resolve2",
[holder = std::move(aHolder), state]() mutable {
holder.Resolve(state, __func__);
}));
}
} else {
// We are closing or suspending an AudioContext, but something else is
// using the audio track, we can resolve the promise now.
AudioContextOperationCompleted(aDestinationTrack, aPromise, aOperation,
aFlags);
// using the audio track, we can resolve the promise now. We do this on
// main thread to avoid locking the holder's mutex on the graph thread.
DispatchToMainThreadStableState(NS_NewRunnableFunction(
"MediaTrackGraphImpl::ApplyAudioContextOperationImpl::Resolve3",
[holder = std::move(aHolder), state]() mutable {
holder.Resolve(state, __func__);
}));
}
}
}
void MediaTrackGraph::ApplyAudioContextOperation(
auto MediaTrackGraph::ApplyAudioContextOperation(
MediaTrack* aDestinationTrack, const nsTArray<MediaTrack*>& aTracks,
AudioContextOperation aOperation, void* aPromise,
AudioContextOperationFlags aFlags) {
AudioContextOperation aOperation) -> RefPtr<AudioContextOperationPromise> {
class AudioContextOperationControlMessage : public ControlMessage {
public:
AudioContextOperationControlMessage(MediaTrack* aDestinationTrack,
const nsTArray<MediaTrack*>& aTracks,
AudioContextOperation aOperation,
void* aPromise,
AudioContextOperationFlags aFlags)
AudioContextOperationControlMessage(
MediaTrack* aDestinationTrack, const nsTArray<MediaTrack*>& aTracks,
AudioContextOperation aOperation,
MozPromiseHolder<AudioContextOperationPromise>&& aHolder)
: ControlMessage(aDestinationTrack),
mTracks(aTracks),
mAudioContextOperation(aOperation),
mPromise(aPromise),
mFlags(aFlags) {}
mHolder(std::move(aHolder)) {}
void Run() override {
mTrack->GraphImpl()->ApplyAudioContextOperationImpl(
mTrack, mTracks, mAudioContextOperation, mPromise, mFlags);
mTrack, mTracks, mAudioContextOperation, std::move(mHolder));
}
void RunDuringShutdown() override {
MOZ_ASSERT(mAudioContextOperation == AudioContextOperation::Close,
"We should be reviving the graph?");
mHolder.Resolve(AudioContextState::Closed, __func__);
}
private:
@ -3510,13 +3517,15 @@ void MediaTrackGraph::ApplyAudioContextOperation(
// doesn't.
nsTArray<MediaTrack*> mTracks;
AudioContextOperation mAudioContextOperation;
void* mPromise;
AudioContextOperationFlags mFlags;
MozPromiseHolder<AudioContextOperationPromise> mHolder;
};
MozPromiseHolder<AudioContextOperationPromise> holder;
RefPtr<AudioContextOperationPromise> p = holder.Ensure(__func__);
MediaTrackGraphImpl* graphImpl = static_cast<MediaTrackGraphImpl*>(this);
graphImpl->AppendMessage(MakeUnique<AudioContextOperationControlMessage>(
aDestinationTrack, aTracks, aOperation, aPromise, aFlags));
aDestinationTrack, aTracks, aOperation, std::move(holder)));
return p;
}
uint32_t MediaTrackGraphImpl::AudioOutputChannelCount() const {

View File

@ -48,6 +48,7 @@ extern LazyLogModule gMediaTrackGraphLog;
namespace dom {
enum class AudioContextOperation;
enum class AudioContextOperationFlags;
enum class AudioContextState : uint8_t;
} // namespace dom
/*
@ -1053,9 +1054,13 @@ class MediaTrackGraph {
*/
void AddTrack(MediaTrack* aTrack);
/* From the main thread, ask the MTG to send back an event when the graph
* thread is running, and audio is being processed. */
void NotifyWhenGraphStarted(AudioNodeTrack* aNodeTrack);
/* From the main thread, ask the MTG to tell us when the graph
* thread is running, and audio is being processed, by resolving the returned
* promise. The promise is rejected with NS_ERROR_NOT_AVAILABLE if aNodeTrack
* is destroyed, or NS_ERROR_ILLEGAL_DURING_SHUTDOWN if the graph is shut
* down, before the promise could be resolved. */
using GraphStartedPromise = GenericPromise;
RefPtr<GraphStartedPromise> NotifyWhenGraphStarted(AudioNodeTrack* aTrack);
/* From the main thread, suspend, resume or close an AudioContext.
* aTracks are the tracks of all the AudioNodes of the AudioContext that
* need to be suspended or resumed. This can be empty if this is a second
@ -1064,13 +1069,13 @@ class MediaTrackGraph {
* This can possibly pause the graph thread, releasing system resources, if
* all tracks have been suspended/closed.
*
* When the operation is complete, aPromise is resolved.
* When the operation is complete, the returned promise is resolved.
*/
void ApplyAudioContextOperation(MediaTrack* aDestinationTrack,
const nsTArray<MediaTrack*>& aTracks,
dom::AudioContextOperation aState,
void* aPromise,
dom::AudioContextOperationFlags aFlags);
using AudioContextOperationPromise =
MozPromise<dom::AudioContextState, bool, true>;
RefPtr<AudioContextOperationPromise> ApplyAudioContextOperation(
MediaTrack* aDestinationTrack, const nsTArray<MediaTrack*>& aTracks,
dom::AudioContextOperation aOperation);
bool IsNonRealtime() const;
/**

View File

@ -301,23 +301,17 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
*/
void RunMessageAfterProcessing(UniquePtr<ControlMessage> aMessage);
/**
* Called when a suspend/resume/close operation has been completed, on the
* graph thread.
*/
void AudioContextOperationCompleted(MediaTrack* aTrack, void* aPromise,
dom::AudioContextOperation aOperation,
dom::AudioContextOperationFlags aFlags);
void NotifyWhenGraphStarted(RefPtr<AudioNodeTrack> aTrack,
MozPromiseHolder<GraphStartedPromise>&& aHolder);
/**
* Apply and AudioContext operation (suspend/resume/closed), on the graph
* Apply an AudioContext operation (suspend/resume/close), on the graph
* thread.
*/
void ApplyAudioContextOperationImpl(MediaTrack* aDestinationTrack,
const nsTArray<MediaTrack*>& aTracks,
dom::AudioContextOperation aOperation,
void* aPromise,
dom::AudioContextOperationFlags aSource);
void ApplyAudioContextOperationImpl(
MediaTrack* aDestinationTrack, const nsTArray<MediaTrack*>& aTracks,
dom::AudioContextOperation aOperation,
MozPromiseHolder<AudioContextOperationPromise>&& aHolder);
/**
* Increment suspend count on aTrack and move it to mSuspendedTracks if

View File

@ -715,6 +715,14 @@ double AudioContext::CurrentTime() {
GetRandomTimelineSeed());
}
nsISerialEventTarget* AudioContext::GetMainThread() const {
if (nsPIDOMWindowInner* window = GetParentObject()) {
return window->AsGlobal()->EventTargetFor(TaskCategory::Other);
}
return GetCurrentThreadSerialEventTarget();
}
void AudioContext::DisconnectFromOwner() {
mIsDisconnecting = true;
Shutdown();
@ -777,54 +785,6 @@ void AudioContext::Shutdown() {
}
}
StateChangeTask::StateChangeTask(AudioContext* aAudioContext, void* aPromise,
AudioContextState aNewState)
: Runnable("dom::StateChangeTask"),
mAudioContext(aAudioContext),
mPromise(aPromise),
mAudioNodeTrack(nullptr),
mNewState(aNewState) {
MOZ_ASSERT(NS_IsMainThread(),
"This constructor should be used from the main thread.");
}
StateChangeTask::StateChangeTask(AudioNodeTrack* aTrack, void* aPromise,
AudioContextState aNewState)
: Runnable("dom::StateChangeTask"),
mAudioContext(nullptr),
mPromise(aPromise),
mAudioNodeTrack(aTrack),
mNewState(aNewState) {
MOZ_ASSERT(!NS_IsMainThread(),
"This constructor should be used from the graph thread.");
}
NS_IMETHODIMP
StateChangeTask::Run() {
MOZ_ASSERT(NS_IsMainThread());
if (!mAudioContext && !mAudioNodeTrack) {
return NS_OK;
}
if (mAudioNodeTrack) {
AudioNode* node = mAudioNodeTrack->Engine()->NodeMainThread();
if (!node) {
return NS_OK;
}
mAudioContext = node->Context();
if (!mAudioContext) {
return NS_OK;
}
}
mAudioContext->OnStateChanged(mPromise, mNewState);
// We have can't call Release() on the AudioContext on the MTG thread, so we
// unref it here, on the main thread.
mAudioContext = nullptr;
return NS_OK;
}
/* This runnable allows to fire the "statechange" event */
class OnStateChangeTask final : public Runnable {
public:
@ -992,6 +952,7 @@ void AudioContext::SuspendFromChrome() {
void AudioContext::SuspendInternal(void* aPromise,
AudioContextOperationFlags aFlags) {
MOZ_ASSERT(NS_IsMainThread());
Destination()->Suspend();
nsTArray<mozilla::MediaTrack*> tracks;
@ -1002,9 +963,17 @@ void AudioContext::SuspendInternal(void* aPromise,
if (!mSuspendCalled) {
tracks = GetAllTracks();
}
Graph()->ApplyAudioContextOperation(DestinationTrack(), tracks,
AudioContextOperation::Suspend, aPromise,
aFlags);
auto promise = Graph()->ApplyAudioContextOperation(
DestinationTrack(), tracks, AudioContextOperation::Suspend);
if ((aFlags & AudioContextOperationFlags::SendStateChange)) {
promise->Then(
GetMainThread(), "AudioContext::OnStateChanged",
[self = RefPtr<AudioContext>(this),
aPromise](AudioContextState aNewState) {
self->OnStateChanged(aPromise, aNewState);
},
[] { MOZ_CRASH("Unexpected rejection"); });
}
mSuspendCalled = true;
}
@ -1065,9 +1034,16 @@ void AudioContext::ResumeInternal(AudioContextOperationFlags aFlags) {
if (mSuspendCalled) {
tracks = GetAllTracks();
}
Graph()->ApplyAudioContextOperation(DestinationTrack(), tracks,
AudioContextOperation::Resume, nullptr,
aFlags);
auto promise = Graph()->ApplyAudioContextOperation(
DestinationTrack(), tracks, AudioContextOperation::Resume);
if (aFlags & AudioContextOperationFlags::SendStateChange) {
promise->Then(
GetMainThread(), "AudioContext::OnStateChanged",
[self = RefPtr<AudioContext>(this)](AudioContextState aNewState) {
self->OnStateChanged(nullptr, aNewState);
},
[] { MOZ_CRASH("Unexpected rejection"); });
}
mSuspendCalled = false;
}
@ -1183,8 +1159,17 @@ void AudioContext::CloseInternal(void* aPromise,
if (!mSuspendCalled && !mCloseCalled) {
tracks = GetAllTracks();
}
Graph()->ApplyAudioContextOperation(
ds, tracks, AudioContextOperation::Close, aPromise, aFlags);
auto promise = Graph()->ApplyAudioContextOperation(
ds, tracks, AudioContextOperation::Close);
if ((aFlags & AudioContextOperationFlags::SendStateChange)) {
promise->Then(
GetMainThread(), "AudioContext::OnStateChanged",
[self = RefPtr<AudioContext>(this),
aPromise](AudioContextState aNewState) {
self->OnStateChanged(aPromise, aNewState);
},
[] { MOZ_CRASH("Unexpected rejection"); });
}
}
mCloseCalled = true;
}

View File

@ -152,6 +152,8 @@ class AudioContext final : public DOMEventTargetHelper,
nsPIDOMWindowInner* GetParentObject() const { return GetOwner(); }
nsISerialEventTarget* GetMainThread() const;
virtual void DisconnectFromOwner() override;
virtual void BindToOwner(nsIGlobalObject* aNew) override;

View File

@ -342,7 +342,15 @@ AudioDestinationNode::AudioDestinationNode(AudioContext* aContext,
mTrack->AddAudioOutput(nullptr);
if (aAllowedToStart) {
graph->NotifyWhenGraphStarted(mTrack);
graph->NotifyWhenGraphStarted(mTrack)->Then(
aContext->GetMainThread(), "AudioDestinationNode OnRunning",
[context = RefPtr<AudioContext>(aContext)] {
context->OnStateChanged(nullptr, AudioContextState::Running);
},
[] {
NS_WARNING(
"AudioDestinationNode's graph never started processing audio");
});
}
}