diff --git a/dom/media/webaudio/AudioContext.cpp b/dom/media/webaudio/AudioContext.cpp index 9e411fa254ab7..976ae1c778b44 100644 --- a/dom/media/webaudio/AudioContext.cpp +++ b/dom/media/webaudio/AudioContext.cpp @@ -126,11 +126,15 @@ AudioContext::AudioContext(nsPIDOMWindowInner* aWindow, nsresult AudioContext::Init() { + // We skip calling SetIsOnlyNodeForContext and the creation of the + // audioChannelAgent during mDestination's constructor, because we can only + // call them after mDestination has been set up. if (!mIsOffline) { nsresult rv = mDestination->CreateAudioChannelAgent(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + mDestination->SetIsOnlyNodeForContext(true); } return NS_OK; @@ -678,7 +682,8 @@ double AudioContext::CurrentTime() const { MediaStream* stream = Destination()->Stream(); - return stream->StreamTimeToSeconds(stream->GetCurrentTime()); + return stream->StreamTimeToSeconds(stream->GetCurrentTime() + + Destination()->ExtraCurrentTime()); } void @@ -988,6 +993,13 @@ AudioContext::RegisterNode(AudioNode* aNode) { MOZ_ASSERT(!mAllNodes.Contains(aNode)); mAllNodes.PutEntry(aNode); + // mDestinationNode may be null when we're destroying nodes unlinked by CC. + // Skipping unnecessary calls after shutdown avoids RunInStableState events + // getting stuck in CycleCollectedJSRuntime during final cycle collection + // (bug 1200514). + if (mDestination && !mIsShutDown) { + mDestination->SetIsOnlyNodeForContext(mAllNodes.Count() == 1); + } } void @@ -995,6 +1007,10 @@ AudioContext::UnregisterNode(AudioNode* aNode) { MOZ_ASSERT(mAllNodes.Contains(aNode)); mAllNodes.RemoveEntry(aNode); + // mDestinationNode may be null when we're destroying nodes unlinked by CC + if (mDestination) { + mDestination->SetIsOnlyNodeForContext(mAllNodes.Count() == 1); + } } JSObject* diff --git a/dom/media/webaudio/AudioDestinationNode.cpp b/dom/media/webaudio/AudioDestinationNode.cpp index b455d4d6edc0e..7da9c5dd901b2 100644 --- a/dom/media/webaudio/AudioDestinationNode.cpp +++ b/dom/media/webaudio/AudioDestinationNode.cpp @@ -332,6 +332,8 @@ AudioDestinationNode::AudioDestinationNode(AudioContext* aContext, , mAudioChannel(AudioChannel::Normal) , mIsOffline(aIsOffline) , mAudioChannelAgentPlaying(false) + , mExtraCurrentTimeSinceLastStartedBlocking(0) + , mExtraCurrentTimeUpdatedSinceLastStableState(false) , mCaptured(false) { MediaStreamGraph* graph = aIsOffline ? @@ -646,6 +648,73 @@ AudioDestinationNode::CreateAudioChannelAgent() return NS_OK; } +void +AudioDestinationNode::NotifyStableState() +{ + mExtraCurrentTimeUpdatedSinceLastStableState = false; +} + +void +AudioDestinationNode::ScheduleStableStateNotification() +{ + // Dispatch will fail if this is called on AudioNode destruction during + // shutdown, in which case failure can be ignored. + nsContentUtils::RunInStableState(NewRunnableMethod(this, + &AudioDestinationNode::NotifyStableState)); +} + +StreamTime +AudioDestinationNode::ExtraCurrentTime() +{ + if (!mStartedBlockingDueToBeingOnlyNode.IsNull() && + !mExtraCurrentTimeUpdatedSinceLastStableState) { + mExtraCurrentTimeUpdatedSinceLastStableState = true; + // Round to nearest processing block. + double seconds = + (TimeStamp::Now() - mStartedBlockingDueToBeingOnlyNode).ToSeconds(); + mExtraCurrentTimeSinceLastStartedBlocking = WEBAUDIO_BLOCK_SIZE * + StreamTime(seconds * Context()->SampleRate() / WEBAUDIO_BLOCK_SIZE + 0.5); + ScheduleStableStateNotification(); + } + return mExtraCurrentTimeSinceLastStartedBlocking; +} + +void +AudioDestinationNode::SetIsOnlyNodeForContext(bool aIsOnlyNode) +{ + if (!mStartedBlockingDueToBeingOnlyNode.IsNull() == aIsOnlyNode) { + // Nothing changed. + return; + } + + if (!mStream) { + // DestroyMediaStream has been called, presumably during CC Unlink(). + return; + } + + if (mIsOffline) { + // Don't block the destination stream for offline AudioContexts, since + // we expect the zero data produced when there are no other nodes to + // show up in its result buffer. Also, we would get confused by adding + // ExtraCurrentTime before StartRendering has even been called. + return; + } + + if (aIsOnlyNode) { + mStream->Suspend(); + mStartedBlockingDueToBeingOnlyNode = TimeStamp::Now(); + // Don't do an update of mExtraCurrentTimeSinceLastStartedBlocking until the next stable state. + mExtraCurrentTimeUpdatedSinceLastStableState = true; + ScheduleStableStateNotification(); + } else { + // Force update of mExtraCurrentTimeSinceLastStartedBlocking if necessary + ExtraCurrentTime(); + mStream->AdvanceAndResume(mExtraCurrentTimeSinceLastStartedBlocking); + mExtraCurrentTimeSinceLastStartedBlocking = 0; + mStartedBlockingDueToBeingOnlyNode = TimeStamp(); + } +} + void AudioDestinationNode::InputMuted(bool aMuted) { diff --git a/dom/media/webaudio/AudioDestinationNode.h b/dom/media/webaudio/AudioDestinationNode.h index 708cd1cc6e5aa..1def00a8714d3 100644 --- a/dom/media/webaudio/AudioDestinationNode.h +++ b/dom/media/webaudio/AudioDestinationNode.h @@ -65,6 +65,13 @@ class AudioDestinationNode final : public AudioNode void NotifyMainThreadStreamFinished() override; void FireOfflineCompletionEvent(); + // An amount that should be added to the MediaStream's current time to + // get the AudioContext.currentTime. + StreamTime ExtraCurrentTime(); + + // When aIsOnlyNode is true, this is the only node for the AudioContext. + void SetIsOnlyNodeForContext(bool aIsOnlyNode); + nsresult CreateAudioChannelAgent(); void DestroyAudioChannelAgent(); @@ -94,6 +101,9 @@ class AudioDestinationNode final : public AudioNode void SetCanPlay(float aVolume, bool aMuted); + void NotifyStableState(); + void ScheduleStableStateNotification(); + SelfReference mOfflineRenderingRef; uint32_t mFramesToProduce; @@ -107,6 +117,9 @@ class AudioDestinationNode final : public AudioNode bool mIsOffline; bool mAudioChannelAgentPlaying; + TimeStamp mStartedBlockingDueToBeingOnlyNode; + StreamTime mExtraCurrentTimeSinceLastStartedBlocking; + bool mExtraCurrentTimeUpdatedSinceLastStableState; bool mCaptured; };