1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/webaudio/AudioDestinationNode.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,603 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "AudioDestinationNode.h" 1.11 +#include "mozilla/dom/AudioDestinationNodeBinding.h" 1.12 +#include "mozilla/Preferences.h" 1.13 +#include "AudioChannelAgent.h" 1.14 +#include "AudioChannelService.h" 1.15 +#include "AudioNodeEngine.h" 1.16 +#include "AudioNodeStream.h" 1.17 +#include "MediaStreamGraph.h" 1.18 +#include "OfflineAudioCompletionEvent.h" 1.19 +#include "nsIInterfaceRequestorUtils.h" 1.20 +#include "nsIDocShell.h" 1.21 +#include "nsIPermissionManager.h" 1.22 +#include "nsIScriptObjectPrincipal.h" 1.23 +#include "nsServiceManagerUtils.h" 1.24 +#include "nsIAppShell.h" 1.25 +#include "nsWidgetsCID.h" 1.26 + 1.27 +namespace mozilla { 1.28 +namespace dom { 1.29 + 1.30 +static uint8_t gWebAudioOutputKey; 1.31 + 1.32 +class OfflineDestinationNodeEngine : public AudioNodeEngine 1.33 +{ 1.34 +public: 1.35 + typedef AutoFallibleTArray<nsAutoArrayPtr<float>, 2> InputChannels; 1.36 + 1.37 + OfflineDestinationNodeEngine(AudioDestinationNode* aNode, 1.38 + uint32_t aNumberOfChannels, 1.39 + uint32_t aLength, 1.40 + float aSampleRate) 1.41 + : AudioNodeEngine(aNode) 1.42 + , mWriteIndex(0) 1.43 + , mLength(aLength) 1.44 + , mSampleRate(aSampleRate) 1.45 + { 1.46 + // These allocations might fail if content provides a huge number of 1.47 + // channels or size, but it's OK since we'll deal with the failure 1.48 + // gracefully. 1.49 + if (mInputChannels.SetLength(aNumberOfChannels)) { 1.50 + static const fallible_t fallible = fallible_t(); 1.51 + for (uint32_t i = 0; i < aNumberOfChannels; ++i) { 1.52 + mInputChannels[i] = new(fallible) float[aLength]; 1.53 + if (!mInputChannels[i]) { 1.54 + mInputChannels.Clear(); 1.55 + break; 1.56 + } 1.57 + } 1.58 + } 1.59 + } 1.60 + 1.61 + virtual void ProcessBlock(AudioNodeStream* aStream, 1.62 + const AudioChunk& aInput, 1.63 + AudioChunk* aOutput, 1.64 + bool* aFinished) MOZ_OVERRIDE 1.65 + { 1.66 + // Do this just for the sake of political correctness; this output 1.67 + // will not go anywhere. 1.68 + *aOutput = aInput; 1.69 + 1.70 + // Handle the case of allocation failure in the input buffer 1.71 + if (mInputChannels.IsEmpty()) { 1.72 + return; 1.73 + } 1.74 + 1.75 + if (mWriteIndex >= mLength) { 1.76 + NS_ASSERTION(mWriteIndex == mLength, "Overshot length"); 1.77 + // Don't record any more. 1.78 + return; 1.79 + } 1.80 + 1.81 + // Record our input buffer 1.82 + MOZ_ASSERT(mWriteIndex < mLength, "How did this happen?"); 1.83 + const uint32_t duration = std::min(WEBAUDIO_BLOCK_SIZE, mLength - mWriteIndex); 1.84 + const uint32_t commonChannelCount = std::min(mInputChannels.Length(), 1.85 + aInput.mChannelData.Length()); 1.86 + // First, copy as many channels in the input as we have 1.87 + for (uint32_t i = 0; i < commonChannelCount; ++i) { 1.88 + if (aInput.IsNull()) { 1.89 + PodZero(mInputChannels[i] + mWriteIndex, duration); 1.90 + } else { 1.91 + const float* inputBuffer = static_cast<const float*>(aInput.mChannelData[i]); 1.92 + if (duration == WEBAUDIO_BLOCK_SIZE) { 1.93 + // Use the optimized version of the copy with scale operation 1.94 + AudioBlockCopyChannelWithScale(inputBuffer, aInput.mVolume, 1.95 + mInputChannels[i] + mWriteIndex); 1.96 + } else { 1.97 + if (aInput.mVolume == 1.0f) { 1.98 + PodCopy(mInputChannels[i] + mWriteIndex, inputBuffer, duration); 1.99 + } else { 1.100 + for (uint32_t j = 0; j < duration; ++j) { 1.101 + mInputChannels[i][mWriteIndex + j] = aInput.mVolume * inputBuffer[j]; 1.102 + } 1.103 + } 1.104 + } 1.105 + } 1.106 + } 1.107 + // Then, silence all of the remaining channels 1.108 + for (uint32_t i = commonChannelCount; i < mInputChannels.Length(); ++i) { 1.109 + PodZero(mInputChannels[i] + mWriteIndex, duration); 1.110 + } 1.111 + mWriteIndex += duration; 1.112 + 1.113 + if (mWriteIndex >= mLength) { 1.114 + NS_ASSERTION(mWriteIndex == mLength, "Overshot length"); 1.115 + // Go to finished state. When the graph's current time eventually reaches 1.116 + // the end of the stream, then the main thread will be notified and we'll 1.117 + // shut down the AudioContext. 1.118 + *aFinished = true; 1.119 + } 1.120 + } 1.121 + 1.122 + void FireOfflineCompletionEvent(AudioDestinationNode* aNode) 1.123 + { 1.124 + AudioContext* context = aNode->Context(); 1.125 + context->Shutdown(); 1.126 + // Shutdown drops self reference, but the context is still referenced by aNode, 1.127 + // which is strongly referenced by the runnable that called 1.128 + // AudioDestinationNode::FireOfflineCompletionEvent. 1.129 + 1.130 + AutoPushJSContext cx(context->GetJSContext()); 1.131 + if (!cx) { 1.132 + 1.133 + return; 1.134 + } 1.135 + JSAutoRequest ar(cx); 1.136 + 1.137 + // Create the input buffer 1.138 + ErrorResult rv; 1.139 + nsRefPtr<AudioBuffer> renderedBuffer = 1.140 + AudioBuffer::Create(context, mInputChannels.Length(), 1.141 + mLength, mSampleRate, cx, rv); 1.142 + if (rv.Failed()) { 1.143 + return; 1.144 + } 1.145 + for (uint32_t i = 0; i < mInputChannels.Length(); ++i) { 1.146 + renderedBuffer->SetRawChannelContents(cx, i, mInputChannels[i]); 1.147 + } 1.148 + 1.149 + nsRefPtr<OfflineAudioCompletionEvent> event = 1.150 + new OfflineAudioCompletionEvent(context, nullptr, nullptr); 1.151 + event->InitEvent(renderedBuffer); 1.152 + context->DispatchTrustedEvent(event); 1.153 + } 1.154 + 1.155 + virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE 1.156 + { 1.157 + size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf); 1.158 + amount += mInputChannels.SizeOfExcludingThis(aMallocSizeOf); 1.159 + return amount; 1.160 + } 1.161 + 1.162 + virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE 1.163 + { 1.164 + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 1.165 + } 1.166 + 1.167 +private: 1.168 + // The input to the destination node is recorded in the mInputChannels buffer. 1.169 + // When this buffer fills up with mLength frames, the buffered input is sent 1.170 + // to the main thread in order to dispatch OfflineAudioCompletionEvent. 1.171 + InputChannels mInputChannels; 1.172 + // An index representing the next offset in mInputChannels to be written to. 1.173 + uint32_t mWriteIndex; 1.174 + // How many frames the OfflineAudioContext intends to produce. 1.175 + uint32_t mLength; 1.176 + float mSampleRate; 1.177 +}; 1.178 + 1.179 +class DestinationNodeEngine : public AudioNodeEngine 1.180 +{ 1.181 +public: 1.182 + explicit DestinationNodeEngine(AudioDestinationNode* aNode) 1.183 + : AudioNodeEngine(aNode) 1.184 + , mVolume(1.0f) 1.185 + { 1.186 + } 1.187 + 1.188 + virtual void ProcessBlock(AudioNodeStream* aStream, 1.189 + const AudioChunk& aInput, 1.190 + AudioChunk* aOutput, 1.191 + bool* aFinished) MOZ_OVERRIDE 1.192 + { 1.193 + *aOutput = aInput; 1.194 + aOutput->mVolume *= mVolume; 1.195 + } 1.196 + 1.197 + virtual void SetDoubleParameter(uint32_t aIndex, double aParam) MOZ_OVERRIDE 1.198 + { 1.199 + if (aIndex == VOLUME) { 1.200 + mVolume = aParam; 1.201 + } 1.202 + } 1.203 + 1.204 + enum Parameters { 1.205 + VOLUME, 1.206 + }; 1.207 + 1.208 + virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE 1.209 + { 1.210 + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 1.211 + } 1.212 + 1.213 +private: 1.214 + float mVolume; 1.215 +}; 1.216 + 1.217 +static bool UseAudioChannelService() 1.218 +{ 1.219 + return Preferences::GetBool("media.useAudioChannelService"); 1.220 +} 1.221 + 1.222 +NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioDestinationNode, AudioNode, 1.223 + mAudioChannelAgent) 1.224 + 1.225 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioDestinationNode) 1.226 + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) 1.227 + NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback) 1.228 + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) 1.229 +NS_INTERFACE_MAP_END_INHERITING(AudioNode) 1.230 + 1.231 +NS_IMPL_ADDREF_INHERITED(AudioDestinationNode, AudioNode) 1.232 +NS_IMPL_RELEASE_INHERITED(AudioDestinationNode, AudioNode) 1.233 + 1.234 +AudioDestinationNode::AudioDestinationNode(AudioContext* aContext, 1.235 + bool aIsOffline, 1.236 + AudioChannel aChannel, 1.237 + uint32_t aNumberOfChannels, 1.238 + uint32_t aLength, 1.239 + float aSampleRate) 1.240 + : AudioNode(aContext, 1.241 + aIsOffline ? aNumberOfChannels : 2, 1.242 + ChannelCountMode::Explicit, 1.243 + ChannelInterpretation::Speakers) 1.244 + , mFramesToProduce(aLength) 1.245 + , mAudioChannel(AudioChannel::Normal) 1.246 + , mIsOffline(aIsOffline) 1.247 + , mHasFinished(false) 1.248 + , mExtraCurrentTime(0) 1.249 + , mExtraCurrentTimeSinceLastStartedBlocking(0) 1.250 + , mExtraCurrentTimeUpdatedSinceLastStableState(false) 1.251 +{ 1.252 + MediaStreamGraph* graph = aIsOffline ? 1.253 + MediaStreamGraph::CreateNonRealtimeInstance(aSampleRate) : 1.254 + MediaStreamGraph::GetInstance(); 1.255 + AudioNodeEngine* engine = aIsOffline ? 1.256 + new OfflineDestinationNodeEngine(this, aNumberOfChannels, 1.257 + aLength, aSampleRate) : 1.258 + static_cast<AudioNodeEngine*>(new DestinationNodeEngine(this)); 1.259 + 1.260 + mStream = graph->CreateAudioNodeStream(engine, MediaStreamGraph::EXTERNAL_STREAM); 1.261 + mStream->SetAudioChannelType(aChannel); 1.262 + mStream->AddMainThreadListener(this); 1.263 + mStream->AddAudioOutput(&gWebAudioOutputKey); 1.264 + 1.265 + if (aChannel != AudioChannel::Normal) { 1.266 + ErrorResult rv; 1.267 + SetMozAudioChannelType(aChannel, rv); 1.268 + } 1.269 + 1.270 + if (!aIsOffline && UseAudioChannelService()) { 1.271 + nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner()); 1.272 + if (target) { 1.273 + target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this, 1.274 + /* useCapture = */ true, 1.275 + /* wantsUntrusted = */ false); 1.276 + } 1.277 + 1.278 + CreateAudioChannelAgent(); 1.279 + } 1.280 +} 1.281 + 1.282 +size_t 1.283 +AudioDestinationNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const 1.284 +{ 1.285 + size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); 1.286 + // Might be useful in the future: 1.287 + // - mAudioChannelAgent 1.288 + return amount; 1.289 +} 1.290 + 1.291 +size_t 1.292 +AudioDestinationNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const 1.293 +{ 1.294 + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 1.295 +} 1.296 + 1.297 +void 1.298 +AudioDestinationNode::DestroyMediaStream() 1.299 +{ 1.300 + if (mAudioChannelAgent && !Context()->IsOffline()) { 1.301 + mAudioChannelAgent->StopPlaying(); 1.302 + mAudioChannelAgent = nullptr; 1.303 + 1.304 + nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner()); 1.305 + NS_ENSURE_TRUE_VOID(target); 1.306 + 1.307 + target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this, 1.308 + /* useCapture = */ true); 1.309 + } 1.310 + 1.311 + if (!mStream) 1.312 + return; 1.313 + 1.314 + mStream->RemoveMainThreadListener(this); 1.315 + MediaStreamGraph* graph = mStream->Graph(); 1.316 + if (graph->IsNonRealtime()) { 1.317 + MediaStreamGraph::DestroyNonRealtimeInstance(graph); 1.318 + } 1.319 + AudioNode::DestroyMediaStream(); 1.320 +} 1.321 + 1.322 +void 1.323 +AudioDestinationNode::NotifyMainThreadStateChanged() 1.324 +{ 1.325 + if (mStream->IsFinished() && !mHasFinished) { 1.326 + mHasFinished = true; 1.327 + if (mIsOffline) { 1.328 + nsCOMPtr<nsIRunnable> runnable = 1.329 + NS_NewRunnableMethod(this, &AudioDestinationNode::FireOfflineCompletionEvent); 1.330 + NS_DispatchToCurrentThread(runnable); 1.331 + } 1.332 + } 1.333 +} 1.334 + 1.335 +void 1.336 +AudioDestinationNode::FireOfflineCompletionEvent() 1.337 +{ 1.338 + AudioNodeStream* stream = static_cast<AudioNodeStream*>(Stream()); 1.339 + OfflineDestinationNodeEngine* engine = 1.340 + static_cast<OfflineDestinationNodeEngine*>(stream->Engine()); 1.341 + engine->FireOfflineCompletionEvent(this); 1.342 +} 1.343 + 1.344 +uint32_t 1.345 +AudioDestinationNode::MaxChannelCount() const 1.346 +{ 1.347 + return Context()->MaxChannelCount(); 1.348 +} 1.349 + 1.350 +void 1.351 +AudioDestinationNode::SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv) 1.352 +{ 1.353 + if (aChannelCount > MaxChannelCount()) { 1.354 + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); 1.355 + return; 1.356 + } 1.357 + 1.358 + AudioNode::SetChannelCount(aChannelCount, aRv); 1.359 +} 1.360 + 1.361 +void 1.362 +AudioDestinationNode::Mute() 1.363 +{ 1.364 + MOZ_ASSERT(Context() && !Context()->IsOffline()); 1.365 + SendDoubleParameterToStream(DestinationNodeEngine::VOLUME, 0.0f); 1.366 +} 1.367 + 1.368 +void 1.369 +AudioDestinationNode::Unmute() 1.370 +{ 1.371 + MOZ_ASSERT(Context() && !Context()->IsOffline()); 1.372 + SendDoubleParameterToStream(DestinationNodeEngine::VOLUME, 1.0f); 1.373 +} 1.374 + 1.375 +void 1.376 +AudioDestinationNode::OfflineShutdown() 1.377 +{ 1.378 + MOZ_ASSERT(Context() && Context()->IsOffline(), 1.379 + "Should only be called on a valid OfflineAudioContext"); 1.380 + 1.381 + MediaStreamGraph::DestroyNonRealtimeInstance(mStream->Graph()); 1.382 + mOfflineRenderingRef.Drop(this); 1.383 +} 1.384 + 1.385 +JSObject* 1.386 +AudioDestinationNode::WrapObject(JSContext* aCx) 1.387 +{ 1.388 + return AudioDestinationNodeBinding::Wrap(aCx, this); 1.389 +} 1.390 + 1.391 +void 1.392 +AudioDestinationNode::StartRendering() 1.393 +{ 1.394 + mOfflineRenderingRef.Take(this); 1.395 + mStream->Graph()->StartNonRealtimeProcessing(TrackRate(Context()->SampleRate()), mFramesToProduce); 1.396 +} 1.397 + 1.398 +void 1.399 +AudioDestinationNode::SetCanPlay(bool aCanPlay) 1.400 +{ 1.401 + mStream->SetTrackEnabled(AudioNodeStream::AUDIO_TRACK, aCanPlay); 1.402 +} 1.403 + 1.404 +NS_IMETHODIMP 1.405 +AudioDestinationNode::HandleEvent(nsIDOMEvent* aEvent) 1.406 +{ 1.407 + nsAutoString type; 1.408 + aEvent->GetType(type); 1.409 + 1.410 + if (!type.EqualsLiteral("visibilitychange")) { 1.411 + return NS_ERROR_FAILURE; 1.412 + } 1.413 + 1.414 + nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner()); 1.415 + NS_ENSURE_TRUE(docshell, NS_ERROR_FAILURE); 1.416 + 1.417 + bool isActive = false; 1.418 + docshell->GetIsActive(&isActive); 1.419 + 1.420 + mAudioChannelAgent->SetVisibilityState(isActive); 1.421 + return NS_OK; 1.422 +} 1.423 + 1.424 +NS_IMETHODIMP 1.425 +AudioDestinationNode::CanPlayChanged(int32_t aCanPlay) 1.426 +{ 1.427 + SetCanPlay(aCanPlay == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL); 1.428 + return NS_OK; 1.429 +} 1.430 + 1.431 +NS_IMETHODIMP 1.432 +AudioDestinationNode::WindowVolumeChanged() 1.433 +{ 1.434 + MOZ_ASSERT(mAudioChannelAgent); 1.435 + 1.436 + if (!mStream) { 1.437 + return NS_OK; 1.438 + } 1.439 + 1.440 + float volume; 1.441 + nsresult rv = mAudioChannelAgent->GetWindowVolume(&volume); 1.442 + NS_ENSURE_SUCCESS(rv, rv); 1.443 + 1.444 + mStream->SetAudioOutputVolume(&gWebAudioOutputKey, volume); 1.445 + return NS_OK; 1.446 +} 1.447 + 1.448 +AudioChannel 1.449 +AudioDestinationNode::MozAudioChannelType() const 1.450 +{ 1.451 + return mAudioChannel; 1.452 +} 1.453 + 1.454 +void 1.455 +AudioDestinationNode::SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv) 1.456 +{ 1.457 + if (Context()->IsOffline()) { 1.458 + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1.459 + return; 1.460 + } 1.461 + 1.462 + if (aValue != mAudioChannel && 1.463 + CheckAudioChannelPermissions(aValue)) { 1.464 + mAudioChannel = aValue; 1.465 + 1.466 + if (mAudioChannelAgent) { 1.467 + CreateAudioChannelAgent(); 1.468 + } 1.469 + } 1.470 +} 1.471 + 1.472 +bool 1.473 +AudioDestinationNode::CheckAudioChannelPermissions(AudioChannel aValue) 1.474 +{ 1.475 + if (!Preferences::GetBool("media.useAudioChannelService")) { 1.476 + return true; 1.477 + } 1.478 + 1.479 + // Only normal channel doesn't need permission. 1.480 + if (aValue == AudioChannel::Normal) { 1.481 + return true; 1.482 + } 1.483 + 1.484 + // Maybe this audio channel is equal to the default one. 1.485 + if (aValue == AudioChannelService::GetDefaultAudioChannel()) { 1.486 + return true; 1.487 + } 1.488 + 1.489 + nsCOMPtr<nsIPermissionManager> permissionManager = 1.490 + do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); 1.491 + if (!permissionManager) { 1.492 + return false; 1.493 + } 1.494 + 1.495 + nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner()); 1.496 + NS_ASSERTION(sop, "Window didn't QI to nsIScriptObjectPrincipal!"); 1.497 + nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal(); 1.498 + 1.499 + uint32_t perm = nsIPermissionManager::UNKNOWN_ACTION; 1.500 + 1.501 + nsCString channel; 1.502 + channel.AssignASCII(AudioChannelValues::strings[uint32_t(aValue)].value, 1.503 + AudioChannelValues::strings[uint32_t(aValue)].length); 1.504 + permissionManager->TestExactPermissionFromPrincipal(principal, 1.505 + nsCString(NS_LITERAL_CSTRING("audio-channel-") + channel).get(), 1.506 + &perm); 1.507 + 1.508 + return perm == nsIPermissionManager::ALLOW_ACTION; 1.509 +} 1.510 + 1.511 +void 1.512 +AudioDestinationNode::CreateAudioChannelAgent() 1.513 +{ 1.514 + if (mAudioChannelAgent) { 1.515 + mAudioChannelAgent->StopPlaying(); 1.516 + } 1.517 + 1.518 + mAudioChannelAgent = new AudioChannelAgent(); 1.519 + mAudioChannelAgent->InitWithWeakCallback(GetOwner(), 1.520 + static_cast<int32_t>(mAudioChannel), 1.521 + this); 1.522 + 1.523 + nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner()); 1.524 + if (docshell) { 1.525 + bool isActive = false; 1.526 + docshell->GetIsActive(&isActive); 1.527 + mAudioChannelAgent->SetVisibilityState(isActive); 1.528 + } 1.529 + 1.530 + int32_t state = 0; 1.531 + mAudioChannelAgent->StartPlaying(&state); 1.532 + SetCanPlay(state == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL); 1.533 +} 1.534 + 1.535 +void 1.536 +AudioDestinationNode::NotifyStableState() 1.537 +{ 1.538 + mExtraCurrentTimeUpdatedSinceLastStableState = false; 1.539 +} 1.540 + 1.541 +static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); 1.542 + 1.543 +void 1.544 +AudioDestinationNode::ScheduleStableStateNotification() 1.545 +{ 1.546 + nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID); 1.547 + if (appShell) { 1.548 + nsCOMPtr<nsIRunnable> event = 1.549 + NS_NewRunnableMethod(this, &AudioDestinationNode::NotifyStableState); 1.550 + appShell->RunInStableState(event); 1.551 + } 1.552 +} 1.553 + 1.554 +double 1.555 +AudioDestinationNode::ExtraCurrentTime() 1.556 +{ 1.557 + if (!mStartedBlockingDueToBeingOnlyNode.IsNull() && 1.558 + !mExtraCurrentTimeUpdatedSinceLastStableState) { 1.559 + mExtraCurrentTimeUpdatedSinceLastStableState = true; 1.560 + mExtraCurrentTimeSinceLastStartedBlocking = 1.561 + (TimeStamp::Now() - mStartedBlockingDueToBeingOnlyNode).ToSeconds(); 1.562 + ScheduleStableStateNotification(); 1.563 + } 1.564 + return mExtraCurrentTime + mExtraCurrentTimeSinceLastStartedBlocking; 1.565 +} 1.566 + 1.567 +void 1.568 +AudioDestinationNode::SetIsOnlyNodeForContext(bool aIsOnlyNode) 1.569 +{ 1.570 + if (!mStartedBlockingDueToBeingOnlyNode.IsNull() == aIsOnlyNode) { 1.571 + // Nothing changed. 1.572 + return; 1.573 + } 1.574 + 1.575 + if (!mStream) { 1.576 + // DestroyMediaStream has been called, presumably during CC Unlink(). 1.577 + return; 1.578 + } 1.579 + 1.580 + if (mIsOffline) { 1.581 + // Don't block the destination stream for offline AudioContexts, since 1.582 + // we expect the zero data produced when there are no other nodes to 1.583 + // show up in its result buffer. Also, we would get confused by adding 1.584 + // ExtraCurrentTime before StartRendering has even been called. 1.585 + return; 1.586 + } 1.587 + 1.588 + if (aIsOnlyNode) { 1.589 + mStream->ChangeExplicitBlockerCount(1); 1.590 + mStartedBlockingDueToBeingOnlyNode = TimeStamp::Now(); 1.591 + // Don't do an update of mExtraCurrentTimeSinceLastStartedBlocking until the next stable state. 1.592 + mExtraCurrentTimeUpdatedSinceLastStableState = true; 1.593 + ScheduleStableStateNotification(); 1.594 + } else { 1.595 + // Force update of mExtraCurrentTimeSinceLastStartedBlocking if necessary 1.596 + ExtraCurrentTime(); 1.597 + mExtraCurrentTime += mExtraCurrentTimeSinceLastStartedBlocking; 1.598 + mExtraCurrentTimeSinceLastStartedBlocking = 0; 1.599 + mStream->ChangeExplicitBlockerCount(-1); 1.600 + mStartedBlockingDueToBeingOnlyNode = TimeStamp(); 1.601 + } 1.602 +} 1.603 + 1.604 +} 1.605 + 1.606 +}