mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-19 08:15:31 +00:00
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:
parent
43d5cf8ee3
commit
1adcbfee14
@ -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() {
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user