michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "AudioDestinationNode.h" michael@0: #include "mozilla/dom/AudioDestinationNodeBinding.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "AudioChannelAgent.h" michael@0: #include "AudioChannelService.h" michael@0: #include "AudioNodeEngine.h" michael@0: #include "AudioNodeStream.h" michael@0: #include "MediaStreamGraph.h" michael@0: #include "OfflineAudioCompletionEvent.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIPermissionManager.h" michael@0: #include "nsIScriptObjectPrincipal.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsIAppShell.h" michael@0: #include "nsWidgetsCID.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: static uint8_t gWebAudioOutputKey; michael@0: michael@0: class OfflineDestinationNodeEngine : public AudioNodeEngine michael@0: { michael@0: public: michael@0: typedef AutoFallibleTArray, 2> InputChannels; michael@0: michael@0: OfflineDestinationNodeEngine(AudioDestinationNode* aNode, michael@0: uint32_t aNumberOfChannels, michael@0: uint32_t aLength, michael@0: float aSampleRate) michael@0: : AudioNodeEngine(aNode) michael@0: , mWriteIndex(0) michael@0: , mLength(aLength) michael@0: , mSampleRate(aSampleRate) michael@0: { michael@0: // These allocations might fail if content provides a huge number of michael@0: // channels or size, but it's OK since we'll deal with the failure michael@0: // gracefully. michael@0: if (mInputChannels.SetLength(aNumberOfChannels)) { michael@0: static const fallible_t fallible = fallible_t(); michael@0: for (uint32_t i = 0; i < aNumberOfChannels; ++i) { michael@0: mInputChannels[i] = new(fallible) float[aLength]; michael@0: if (!mInputChannels[i]) { michael@0: mInputChannels.Clear(); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: virtual void ProcessBlock(AudioNodeStream* aStream, michael@0: const AudioChunk& aInput, michael@0: AudioChunk* aOutput, michael@0: bool* aFinished) MOZ_OVERRIDE michael@0: { michael@0: // Do this just for the sake of political correctness; this output michael@0: // will not go anywhere. michael@0: *aOutput = aInput; michael@0: michael@0: // Handle the case of allocation failure in the input buffer michael@0: if (mInputChannels.IsEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: if (mWriteIndex >= mLength) { michael@0: NS_ASSERTION(mWriteIndex == mLength, "Overshot length"); michael@0: // Don't record any more. michael@0: return; michael@0: } michael@0: michael@0: // Record our input buffer michael@0: MOZ_ASSERT(mWriteIndex < mLength, "How did this happen?"); michael@0: const uint32_t duration = std::min(WEBAUDIO_BLOCK_SIZE, mLength - mWriteIndex); michael@0: const uint32_t commonChannelCount = std::min(mInputChannels.Length(), michael@0: aInput.mChannelData.Length()); michael@0: // First, copy as many channels in the input as we have michael@0: for (uint32_t i = 0; i < commonChannelCount; ++i) { michael@0: if (aInput.IsNull()) { michael@0: PodZero(mInputChannels[i] + mWriteIndex, duration); michael@0: } else { michael@0: const float* inputBuffer = static_cast(aInput.mChannelData[i]); michael@0: if (duration == WEBAUDIO_BLOCK_SIZE) { michael@0: // Use the optimized version of the copy with scale operation michael@0: AudioBlockCopyChannelWithScale(inputBuffer, aInput.mVolume, michael@0: mInputChannels[i] + mWriteIndex); michael@0: } else { michael@0: if (aInput.mVolume == 1.0f) { michael@0: PodCopy(mInputChannels[i] + mWriteIndex, inputBuffer, duration); michael@0: } else { michael@0: for (uint32_t j = 0; j < duration; ++j) { michael@0: mInputChannels[i][mWriteIndex + j] = aInput.mVolume * inputBuffer[j]; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: // Then, silence all of the remaining channels michael@0: for (uint32_t i = commonChannelCount; i < mInputChannels.Length(); ++i) { michael@0: PodZero(mInputChannels[i] + mWriteIndex, duration); michael@0: } michael@0: mWriteIndex += duration; michael@0: michael@0: if (mWriteIndex >= mLength) { michael@0: NS_ASSERTION(mWriteIndex == mLength, "Overshot length"); michael@0: // Go to finished state. When the graph's current time eventually reaches michael@0: // the end of the stream, then the main thread will be notified and we'll michael@0: // shut down the AudioContext. michael@0: *aFinished = true; michael@0: } michael@0: } michael@0: michael@0: void FireOfflineCompletionEvent(AudioDestinationNode* aNode) michael@0: { michael@0: AudioContext* context = aNode->Context(); michael@0: context->Shutdown(); michael@0: // Shutdown drops self reference, but the context is still referenced by aNode, michael@0: // which is strongly referenced by the runnable that called michael@0: // AudioDestinationNode::FireOfflineCompletionEvent. michael@0: michael@0: AutoPushJSContext cx(context->GetJSContext()); michael@0: if (!cx) { michael@0: michael@0: return; michael@0: } michael@0: JSAutoRequest ar(cx); michael@0: michael@0: // Create the input buffer michael@0: ErrorResult rv; michael@0: nsRefPtr renderedBuffer = michael@0: AudioBuffer::Create(context, mInputChannels.Length(), michael@0: mLength, mSampleRate, cx, rv); michael@0: if (rv.Failed()) { michael@0: return; michael@0: } michael@0: for (uint32_t i = 0; i < mInputChannels.Length(); ++i) { michael@0: renderedBuffer->SetRawChannelContents(cx, i, mInputChannels[i]); michael@0: } michael@0: michael@0: nsRefPtr event = michael@0: new OfflineAudioCompletionEvent(context, nullptr, nullptr); michael@0: event->InitEvent(renderedBuffer); michael@0: context->DispatchTrustedEvent(event); michael@0: } michael@0: michael@0: virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE michael@0: { michael@0: size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf); michael@0: amount += mInputChannels.SizeOfExcludingThis(aMallocSizeOf); michael@0: return amount; michael@0: } michael@0: michael@0: virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: private: michael@0: // The input to the destination node is recorded in the mInputChannels buffer. michael@0: // When this buffer fills up with mLength frames, the buffered input is sent michael@0: // to the main thread in order to dispatch OfflineAudioCompletionEvent. michael@0: InputChannels mInputChannels; michael@0: // An index representing the next offset in mInputChannels to be written to. michael@0: uint32_t mWriteIndex; michael@0: // How many frames the OfflineAudioContext intends to produce. michael@0: uint32_t mLength; michael@0: float mSampleRate; michael@0: }; michael@0: michael@0: class DestinationNodeEngine : public AudioNodeEngine michael@0: { michael@0: public: michael@0: explicit DestinationNodeEngine(AudioDestinationNode* aNode) michael@0: : AudioNodeEngine(aNode) michael@0: , mVolume(1.0f) michael@0: { michael@0: } michael@0: michael@0: virtual void ProcessBlock(AudioNodeStream* aStream, michael@0: const AudioChunk& aInput, michael@0: AudioChunk* aOutput, michael@0: bool* aFinished) MOZ_OVERRIDE michael@0: { michael@0: *aOutput = aInput; michael@0: aOutput->mVolume *= mVolume; michael@0: } michael@0: michael@0: virtual void SetDoubleParameter(uint32_t aIndex, double aParam) MOZ_OVERRIDE michael@0: { michael@0: if (aIndex == VOLUME) { michael@0: mVolume = aParam; michael@0: } michael@0: } michael@0: michael@0: enum Parameters { michael@0: VOLUME, michael@0: }; michael@0: michael@0: virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: private: michael@0: float mVolume; michael@0: }; michael@0: michael@0: static bool UseAudioChannelService() michael@0: { michael@0: return Preferences::GetBool("media.useAudioChannelService"); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioDestinationNode, AudioNode, michael@0: mAudioChannelAgent) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioDestinationNode) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_END_INHERITING(AudioNode) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(AudioDestinationNode, AudioNode) michael@0: NS_IMPL_RELEASE_INHERITED(AudioDestinationNode, AudioNode) michael@0: michael@0: AudioDestinationNode::AudioDestinationNode(AudioContext* aContext, michael@0: bool aIsOffline, michael@0: AudioChannel aChannel, michael@0: uint32_t aNumberOfChannels, michael@0: uint32_t aLength, michael@0: float aSampleRate) michael@0: : AudioNode(aContext, michael@0: aIsOffline ? aNumberOfChannels : 2, michael@0: ChannelCountMode::Explicit, michael@0: ChannelInterpretation::Speakers) michael@0: , mFramesToProduce(aLength) michael@0: , mAudioChannel(AudioChannel::Normal) michael@0: , mIsOffline(aIsOffline) michael@0: , mHasFinished(false) michael@0: , mExtraCurrentTime(0) michael@0: , mExtraCurrentTimeSinceLastStartedBlocking(0) michael@0: , mExtraCurrentTimeUpdatedSinceLastStableState(false) michael@0: { michael@0: MediaStreamGraph* graph = aIsOffline ? michael@0: MediaStreamGraph::CreateNonRealtimeInstance(aSampleRate) : michael@0: MediaStreamGraph::GetInstance(); michael@0: AudioNodeEngine* engine = aIsOffline ? michael@0: new OfflineDestinationNodeEngine(this, aNumberOfChannels, michael@0: aLength, aSampleRate) : michael@0: static_cast(new DestinationNodeEngine(this)); michael@0: michael@0: mStream = graph->CreateAudioNodeStream(engine, MediaStreamGraph::EXTERNAL_STREAM); michael@0: mStream->SetAudioChannelType(aChannel); michael@0: mStream->AddMainThreadListener(this); michael@0: mStream->AddAudioOutput(&gWebAudioOutputKey); michael@0: michael@0: if (aChannel != AudioChannel::Normal) { michael@0: ErrorResult rv; michael@0: SetMozAudioChannelType(aChannel, rv); michael@0: } michael@0: michael@0: if (!aIsOffline && UseAudioChannelService()) { michael@0: nsCOMPtr target = do_QueryInterface(GetOwner()); michael@0: if (target) { michael@0: target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this, michael@0: /* useCapture = */ true, michael@0: /* wantsUntrusted = */ false); michael@0: } michael@0: michael@0: CreateAudioChannelAgent(); michael@0: } michael@0: } michael@0: michael@0: size_t michael@0: AudioDestinationNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); michael@0: // Might be useful in the future: michael@0: // - mAudioChannelAgent michael@0: return amount; michael@0: } michael@0: michael@0: size_t michael@0: AudioDestinationNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: void michael@0: AudioDestinationNode::DestroyMediaStream() michael@0: { michael@0: if (mAudioChannelAgent && !Context()->IsOffline()) { michael@0: mAudioChannelAgent->StopPlaying(); michael@0: mAudioChannelAgent = nullptr; michael@0: michael@0: nsCOMPtr target = do_QueryInterface(GetOwner()); michael@0: NS_ENSURE_TRUE_VOID(target); michael@0: michael@0: target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this, michael@0: /* useCapture = */ true); michael@0: } michael@0: michael@0: if (!mStream) michael@0: return; michael@0: michael@0: mStream->RemoveMainThreadListener(this); michael@0: MediaStreamGraph* graph = mStream->Graph(); michael@0: if (graph->IsNonRealtime()) { michael@0: MediaStreamGraph::DestroyNonRealtimeInstance(graph); michael@0: } michael@0: AudioNode::DestroyMediaStream(); michael@0: } michael@0: michael@0: void michael@0: AudioDestinationNode::NotifyMainThreadStateChanged() michael@0: { michael@0: if (mStream->IsFinished() && !mHasFinished) { michael@0: mHasFinished = true; michael@0: if (mIsOffline) { michael@0: nsCOMPtr runnable = michael@0: NS_NewRunnableMethod(this, &AudioDestinationNode::FireOfflineCompletionEvent); michael@0: NS_DispatchToCurrentThread(runnable); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: AudioDestinationNode::FireOfflineCompletionEvent() michael@0: { michael@0: AudioNodeStream* stream = static_cast(Stream()); michael@0: OfflineDestinationNodeEngine* engine = michael@0: static_cast(stream->Engine()); michael@0: engine->FireOfflineCompletionEvent(this); michael@0: } michael@0: michael@0: uint32_t michael@0: AudioDestinationNode::MaxChannelCount() const michael@0: { michael@0: return Context()->MaxChannelCount(); michael@0: } michael@0: michael@0: void michael@0: AudioDestinationNode::SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv) michael@0: { michael@0: if (aChannelCount > MaxChannelCount()) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return; michael@0: } michael@0: michael@0: AudioNode::SetChannelCount(aChannelCount, aRv); michael@0: } michael@0: michael@0: void michael@0: AudioDestinationNode::Mute() michael@0: { michael@0: MOZ_ASSERT(Context() && !Context()->IsOffline()); michael@0: SendDoubleParameterToStream(DestinationNodeEngine::VOLUME, 0.0f); michael@0: } michael@0: michael@0: void michael@0: AudioDestinationNode::Unmute() michael@0: { michael@0: MOZ_ASSERT(Context() && !Context()->IsOffline()); michael@0: SendDoubleParameterToStream(DestinationNodeEngine::VOLUME, 1.0f); michael@0: } michael@0: michael@0: void michael@0: AudioDestinationNode::OfflineShutdown() michael@0: { michael@0: MOZ_ASSERT(Context() && Context()->IsOffline(), michael@0: "Should only be called on a valid OfflineAudioContext"); michael@0: michael@0: MediaStreamGraph::DestroyNonRealtimeInstance(mStream->Graph()); michael@0: mOfflineRenderingRef.Drop(this); michael@0: } michael@0: michael@0: JSObject* michael@0: AudioDestinationNode::WrapObject(JSContext* aCx) michael@0: { michael@0: return AudioDestinationNodeBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: void michael@0: AudioDestinationNode::StartRendering() michael@0: { michael@0: mOfflineRenderingRef.Take(this); michael@0: mStream->Graph()->StartNonRealtimeProcessing(TrackRate(Context()->SampleRate()), mFramesToProduce); michael@0: } michael@0: michael@0: void michael@0: AudioDestinationNode::SetCanPlay(bool aCanPlay) michael@0: { michael@0: mStream->SetTrackEnabled(AudioNodeStream::AUDIO_TRACK, aCanPlay); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioDestinationNode::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: nsAutoString type; michael@0: aEvent->GetType(type); michael@0: michael@0: if (!type.EqualsLiteral("visibilitychange")) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr docshell = do_GetInterface(GetOwner()); michael@0: NS_ENSURE_TRUE(docshell, NS_ERROR_FAILURE); michael@0: michael@0: bool isActive = false; michael@0: docshell->GetIsActive(&isActive); michael@0: michael@0: mAudioChannelAgent->SetVisibilityState(isActive); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioDestinationNode::CanPlayChanged(int32_t aCanPlay) michael@0: { michael@0: SetCanPlay(aCanPlay == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioDestinationNode::WindowVolumeChanged() michael@0: { michael@0: MOZ_ASSERT(mAudioChannelAgent); michael@0: michael@0: if (!mStream) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: float volume; michael@0: nsresult rv = mAudioChannelAgent->GetWindowVolume(&volume); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mStream->SetAudioOutputVolume(&gWebAudioOutputKey, volume); michael@0: return NS_OK; michael@0: } michael@0: michael@0: AudioChannel michael@0: AudioDestinationNode::MozAudioChannelType() const michael@0: { michael@0: return mAudioChannel; michael@0: } michael@0: michael@0: void michael@0: AudioDestinationNode::SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv) michael@0: { michael@0: if (Context()->IsOffline()) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (aValue != mAudioChannel && michael@0: CheckAudioChannelPermissions(aValue)) { michael@0: mAudioChannel = aValue; michael@0: michael@0: if (mAudioChannelAgent) { michael@0: CreateAudioChannelAgent(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: AudioDestinationNode::CheckAudioChannelPermissions(AudioChannel aValue) michael@0: { michael@0: if (!Preferences::GetBool("media.useAudioChannelService")) { michael@0: return true; michael@0: } michael@0: michael@0: // Only normal channel doesn't need permission. michael@0: if (aValue == AudioChannel::Normal) { michael@0: return true; michael@0: } michael@0: michael@0: // Maybe this audio channel is equal to the default one. michael@0: if (aValue == AudioChannelService::GetDefaultAudioChannel()) { michael@0: return true; michael@0: } michael@0: michael@0: nsCOMPtr permissionManager = michael@0: do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); michael@0: if (!permissionManager) { michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr sop = do_QueryInterface(GetOwner()); michael@0: NS_ASSERTION(sop, "Window didn't QI to nsIScriptObjectPrincipal!"); michael@0: nsCOMPtr principal = sop->GetPrincipal(); michael@0: michael@0: uint32_t perm = nsIPermissionManager::UNKNOWN_ACTION; michael@0: michael@0: nsCString channel; michael@0: channel.AssignASCII(AudioChannelValues::strings[uint32_t(aValue)].value, michael@0: AudioChannelValues::strings[uint32_t(aValue)].length); michael@0: permissionManager->TestExactPermissionFromPrincipal(principal, michael@0: nsCString(NS_LITERAL_CSTRING("audio-channel-") + channel).get(), michael@0: &perm); michael@0: michael@0: return perm == nsIPermissionManager::ALLOW_ACTION; michael@0: } michael@0: michael@0: void michael@0: AudioDestinationNode::CreateAudioChannelAgent() michael@0: { michael@0: if (mAudioChannelAgent) { michael@0: mAudioChannelAgent->StopPlaying(); michael@0: } michael@0: michael@0: mAudioChannelAgent = new AudioChannelAgent(); michael@0: mAudioChannelAgent->InitWithWeakCallback(GetOwner(), michael@0: static_cast(mAudioChannel), michael@0: this); michael@0: michael@0: nsCOMPtr docshell = do_GetInterface(GetOwner()); michael@0: if (docshell) { michael@0: bool isActive = false; michael@0: docshell->GetIsActive(&isActive); michael@0: mAudioChannelAgent->SetVisibilityState(isActive); michael@0: } michael@0: michael@0: int32_t state = 0; michael@0: mAudioChannelAgent->StartPlaying(&state); michael@0: SetCanPlay(state == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL); michael@0: } michael@0: michael@0: void michael@0: AudioDestinationNode::NotifyStableState() michael@0: { michael@0: mExtraCurrentTimeUpdatedSinceLastStableState = false; michael@0: } michael@0: michael@0: static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); michael@0: michael@0: void michael@0: AudioDestinationNode::ScheduleStableStateNotification() michael@0: { michael@0: nsCOMPtr appShell = do_GetService(kAppShellCID); michael@0: if (appShell) { michael@0: nsCOMPtr event = michael@0: NS_NewRunnableMethod(this, &AudioDestinationNode::NotifyStableState); michael@0: appShell->RunInStableState(event); michael@0: } michael@0: } michael@0: michael@0: double michael@0: AudioDestinationNode::ExtraCurrentTime() michael@0: { michael@0: if (!mStartedBlockingDueToBeingOnlyNode.IsNull() && michael@0: !mExtraCurrentTimeUpdatedSinceLastStableState) { michael@0: mExtraCurrentTimeUpdatedSinceLastStableState = true; michael@0: mExtraCurrentTimeSinceLastStartedBlocking = michael@0: (TimeStamp::Now() - mStartedBlockingDueToBeingOnlyNode).ToSeconds(); michael@0: ScheduleStableStateNotification(); michael@0: } michael@0: return mExtraCurrentTime + mExtraCurrentTimeSinceLastStartedBlocking; michael@0: } michael@0: michael@0: void michael@0: AudioDestinationNode::SetIsOnlyNodeForContext(bool aIsOnlyNode) michael@0: { michael@0: if (!mStartedBlockingDueToBeingOnlyNode.IsNull() == aIsOnlyNode) { michael@0: // Nothing changed. michael@0: return; michael@0: } michael@0: michael@0: if (!mStream) { michael@0: // DestroyMediaStream has been called, presumably during CC Unlink(). michael@0: return; michael@0: } michael@0: michael@0: if (mIsOffline) { michael@0: // Don't block the destination stream for offline AudioContexts, since michael@0: // we expect the zero data produced when there are no other nodes to michael@0: // show up in its result buffer. Also, we would get confused by adding michael@0: // ExtraCurrentTime before StartRendering has even been called. michael@0: return; michael@0: } michael@0: michael@0: if (aIsOnlyNode) { michael@0: mStream->ChangeExplicitBlockerCount(1); michael@0: mStartedBlockingDueToBeingOnlyNode = TimeStamp::Now(); michael@0: // Don't do an update of mExtraCurrentTimeSinceLastStartedBlocking until the next stable state. michael@0: mExtraCurrentTimeUpdatedSinceLastStableState = true; michael@0: ScheduleStableStateNotification(); michael@0: } else { michael@0: // Force update of mExtraCurrentTimeSinceLastStartedBlocking if necessary michael@0: ExtraCurrentTime(); michael@0: mExtraCurrentTime += mExtraCurrentTimeSinceLastStartedBlocking; michael@0: mExtraCurrentTimeSinceLastStartedBlocking = 0; michael@0: mStream->ChangeExplicitBlockerCount(-1); michael@0: mStartedBlockingDueToBeingOnlyNode = TimeStamp(); michael@0: } michael@0: } michael@0: michael@0: } michael@0: michael@0: }