|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "AudioDestinationNode.h" |
|
8 #include "mozilla/dom/AudioDestinationNodeBinding.h" |
|
9 #include "mozilla/Preferences.h" |
|
10 #include "AudioChannelAgent.h" |
|
11 #include "AudioChannelService.h" |
|
12 #include "AudioNodeEngine.h" |
|
13 #include "AudioNodeStream.h" |
|
14 #include "MediaStreamGraph.h" |
|
15 #include "OfflineAudioCompletionEvent.h" |
|
16 #include "nsIInterfaceRequestorUtils.h" |
|
17 #include "nsIDocShell.h" |
|
18 #include "nsIPermissionManager.h" |
|
19 #include "nsIScriptObjectPrincipal.h" |
|
20 #include "nsServiceManagerUtils.h" |
|
21 #include "nsIAppShell.h" |
|
22 #include "nsWidgetsCID.h" |
|
23 |
|
24 namespace mozilla { |
|
25 namespace dom { |
|
26 |
|
27 static uint8_t gWebAudioOutputKey; |
|
28 |
|
29 class OfflineDestinationNodeEngine : public AudioNodeEngine |
|
30 { |
|
31 public: |
|
32 typedef AutoFallibleTArray<nsAutoArrayPtr<float>, 2> InputChannels; |
|
33 |
|
34 OfflineDestinationNodeEngine(AudioDestinationNode* aNode, |
|
35 uint32_t aNumberOfChannels, |
|
36 uint32_t aLength, |
|
37 float aSampleRate) |
|
38 : AudioNodeEngine(aNode) |
|
39 , mWriteIndex(0) |
|
40 , mLength(aLength) |
|
41 , mSampleRate(aSampleRate) |
|
42 { |
|
43 // These allocations might fail if content provides a huge number of |
|
44 // channels or size, but it's OK since we'll deal with the failure |
|
45 // gracefully. |
|
46 if (mInputChannels.SetLength(aNumberOfChannels)) { |
|
47 static const fallible_t fallible = fallible_t(); |
|
48 for (uint32_t i = 0; i < aNumberOfChannels; ++i) { |
|
49 mInputChannels[i] = new(fallible) float[aLength]; |
|
50 if (!mInputChannels[i]) { |
|
51 mInputChannels.Clear(); |
|
52 break; |
|
53 } |
|
54 } |
|
55 } |
|
56 } |
|
57 |
|
58 virtual void ProcessBlock(AudioNodeStream* aStream, |
|
59 const AudioChunk& aInput, |
|
60 AudioChunk* aOutput, |
|
61 bool* aFinished) MOZ_OVERRIDE |
|
62 { |
|
63 // Do this just for the sake of political correctness; this output |
|
64 // will not go anywhere. |
|
65 *aOutput = aInput; |
|
66 |
|
67 // Handle the case of allocation failure in the input buffer |
|
68 if (mInputChannels.IsEmpty()) { |
|
69 return; |
|
70 } |
|
71 |
|
72 if (mWriteIndex >= mLength) { |
|
73 NS_ASSERTION(mWriteIndex == mLength, "Overshot length"); |
|
74 // Don't record any more. |
|
75 return; |
|
76 } |
|
77 |
|
78 // Record our input buffer |
|
79 MOZ_ASSERT(mWriteIndex < mLength, "How did this happen?"); |
|
80 const uint32_t duration = std::min(WEBAUDIO_BLOCK_SIZE, mLength - mWriteIndex); |
|
81 const uint32_t commonChannelCount = std::min(mInputChannels.Length(), |
|
82 aInput.mChannelData.Length()); |
|
83 // First, copy as many channels in the input as we have |
|
84 for (uint32_t i = 0; i < commonChannelCount; ++i) { |
|
85 if (aInput.IsNull()) { |
|
86 PodZero(mInputChannels[i] + mWriteIndex, duration); |
|
87 } else { |
|
88 const float* inputBuffer = static_cast<const float*>(aInput.mChannelData[i]); |
|
89 if (duration == WEBAUDIO_BLOCK_SIZE) { |
|
90 // Use the optimized version of the copy with scale operation |
|
91 AudioBlockCopyChannelWithScale(inputBuffer, aInput.mVolume, |
|
92 mInputChannels[i] + mWriteIndex); |
|
93 } else { |
|
94 if (aInput.mVolume == 1.0f) { |
|
95 PodCopy(mInputChannels[i] + mWriteIndex, inputBuffer, duration); |
|
96 } else { |
|
97 for (uint32_t j = 0; j < duration; ++j) { |
|
98 mInputChannels[i][mWriteIndex + j] = aInput.mVolume * inputBuffer[j]; |
|
99 } |
|
100 } |
|
101 } |
|
102 } |
|
103 } |
|
104 // Then, silence all of the remaining channels |
|
105 for (uint32_t i = commonChannelCount; i < mInputChannels.Length(); ++i) { |
|
106 PodZero(mInputChannels[i] + mWriteIndex, duration); |
|
107 } |
|
108 mWriteIndex += duration; |
|
109 |
|
110 if (mWriteIndex >= mLength) { |
|
111 NS_ASSERTION(mWriteIndex == mLength, "Overshot length"); |
|
112 // Go to finished state. When the graph's current time eventually reaches |
|
113 // the end of the stream, then the main thread will be notified and we'll |
|
114 // shut down the AudioContext. |
|
115 *aFinished = true; |
|
116 } |
|
117 } |
|
118 |
|
119 void FireOfflineCompletionEvent(AudioDestinationNode* aNode) |
|
120 { |
|
121 AudioContext* context = aNode->Context(); |
|
122 context->Shutdown(); |
|
123 // Shutdown drops self reference, but the context is still referenced by aNode, |
|
124 // which is strongly referenced by the runnable that called |
|
125 // AudioDestinationNode::FireOfflineCompletionEvent. |
|
126 |
|
127 AutoPushJSContext cx(context->GetJSContext()); |
|
128 if (!cx) { |
|
129 |
|
130 return; |
|
131 } |
|
132 JSAutoRequest ar(cx); |
|
133 |
|
134 // Create the input buffer |
|
135 ErrorResult rv; |
|
136 nsRefPtr<AudioBuffer> renderedBuffer = |
|
137 AudioBuffer::Create(context, mInputChannels.Length(), |
|
138 mLength, mSampleRate, cx, rv); |
|
139 if (rv.Failed()) { |
|
140 return; |
|
141 } |
|
142 for (uint32_t i = 0; i < mInputChannels.Length(); ++i) { |
|
143 renderedBuffer->SetRawChannelContents(cx, i, mInputChannels[i]); |
|
144 } |
|
145 |
|
146 nsRefPtr<OfflineAudioCompletionEvent> event = |
|
147 new OfflineAudioCompletionEvent(context, nullptr, nullptr); |
|
148 event->InitEvent(renderedBuffer); |
|
149 context->DispatchTrustedEvent(event); |
|
150 } |
|
151 |
|
152 virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE |
|
153 { |
|
154 size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf); |
|
155 amount += mInputChannels.SizeOfExcludingThis(aMallocSizeOf); |
|
156 return amount; |
|
157 } |
|
158 |
|
159 virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE |
|
160 { |
|
161 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
|
162 } |
|
163 |
|
164 private: |
|
165 // The input to the destination node is recorded in the mInputChannels buffer. |
|
166 // When this buffer fills up with mLength frames, the buffered input is sent |
|
167 // to the main thread in order to dispatch OfflineAudioCompletionEvent. |
|
168 InputChannels mInputChannels; |
|
169 // An index representing the next offset in mInputChannels to be written to. |
|
170 uint32_t mWriteIndex; |
|
171 // How many frames the OfflineAudioContext intends to produce. |
|
172 uint32_t mLength; |
|
173 float mSampleRate; |
|
174 }; |
|
175 |
|
176 class DestinationNodeEngine : public AudioNodeEngine |
|
177 { |
|
178 public: |
|
179 explicit DestinationNodeEngine(AudioDestinationNode* aNode) |
|
180 : AudioNodeEngine(aNode) |
|
181 , mVolume(1.0f) |
|
182 { |
|
183 } |
|
184 |
|
185 virtual void ProcessBlock(AudioNodeStream* aStream, |
|
186 const AudioChunk& aInput, |
|
187 AudioChunk* aOutput, |
|
188 bool* aFinished) MOZ_OVERRIDE |
|
189 { |
|
190 *aOutput = aInput; |
|
191 aOutput->mVolume *= mVolume; |
|
192 } |
|
193 |
|
194 virtual void SetDoubleParameter(uint32_t aIndex, double aParam) MOZ_OVERRIDE |
|
195 { |
|
196 if (aIndex == VOLUME) { |
|
197 mVolume = aParam; |
|
198 } |
|
199 } |
|
200 |
|
201 enum Parameters { |
|
202 VOLUME, |
|
203 }; |
|
204 |
|
205 virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE |
|
206 { |
|
207 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
|
208 } |
|
209 |
|
210 private: |
|
211 float mVolume; |
|
212 }; |
|
213 |
|
214 static bool UseAudioChannelService() |
|
215 { |
|
216 return Preferences::GetBool("media.useAudioChannelService"); |
|
217 } |
|
218 |
|
219 NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioDestinationNode, AudioNode, |
|
220 mAudioChannelAgent) |
|
221 |
|
222 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioDestinationNode) |
|
223 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) |
|
224 NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback) |
|
225 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
|
226 NS_INTERFACE_MAP_END_INHERITING(AudioNode) |
|
227 |
|
228 NS_IMPL_ADDREF_INHERITED(AudioDestinationNode, AudioNode) |
|
229 NS_IMPL_RELEASE_INHERITED(AudioDestinationNode, AudioNode) |
|
230 |
|
231 AudioDestinationNode::AudioDestinationNode(AudioContext* aContext, |
|
232 bool aIsOffline, |
|
233 AudioChannel aChannel, |
|
234 uint32_t aNumberOfChannels, |
|
235 uint32_t aLength, |
|
236 float aSampleRate) |
|
237 : AudioNode(aContext, |
|
238 aIsOffline ? aNumberOfChannels : 2, |
|
239 ChannelCountMode::Explicit, |
|
240 ChannelInterpretation::Speakers) |
|
241 , mFramesToProduce(aLength) |
|
242 , mAudioChannel(AudioChannel::Normal) |
|
243 , mIsOffline(aIsOffline) |
|
244 , mHasFinished(false) |
|
245 , mExtraCurrentTime(0) |
|
246 , mExtraCurrentTimeSinceLastStartedBlocking(0) |
|
247 , mExtraCurrentTimeUpdatedSinceLastStableState(false) |
|
248 { |
|
249 MediaStreamGraph* graph = aIsOffline ? |
|
250 MediaStreamGraph::CreateNonRealtimeInstance(aSampleRate) : |
|
251 MediaStreamGraph::GetInstance(); |
|
252 AudioNodeEngine* engine = aIsOffline ? |
|
253 new OfflineDestinationNodeEngine(this, aNumberOfChannels, |
|
254 aLength, aSampleRate) : |
|
255 static_cast<AudioNodeEngine*>(new DestinationNodeEngine(this)); |
|
256 |
|
257 mStream = graph->CreateAudioNodeStream(engine, MediaStreamGraph::EXTERNAL_STREAM); |
|
258 mStream->SetAudioChannelType(aChannel); |
|
259 mStream->AddMainThreadListener(this); |
|
260 mStream->AddAudioOutput(&gWebAudioOutputKey); |
|
261 |
|
262 if (aChannel != AudioChannel::Normal) { |
|
263 ErrorResult rv; |
|
264 SetMozAudioChannelType(aChannel, rv); |
|
265 } |
|
266 |
|
267 if (!aIsOffline && UseAudioChannelService()) { |
|
268 nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner()); |
|
269 if (target) { |
|
270 target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this, |
|
271 /* useCapture = */ true, |
|
272 /* wantsUntrusted = */ false); |
|
273 } |
|
274 |
|
275 CreateAudioChannelAgent(); |
|
276 } |
|
277 } |
|
278 |
|
279 size_t |
|
280 AudioDestinationNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
|
281 { |
|
282 size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); |
|
283 // Might be useful in the future: |
|
284 // - mAudioChannelAgent |
|
285 return amount; |
|
286 } |
|
287 |
|
288 size_t |
|
289 AudioDestinationNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
|
290 { |
|
291 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
|
292 } |
|
293 |
|
294 void |
|
295 AudioDestinationNode::DestroyMediaStream() |
|
296 { |
|
297 if (mAudioChannelAgent && !Context()->IsOffline()) { |
|
298 mAudioChannelAgent->StopPlaying(); |
|
299 mAudioChannelAgent = nullptr; |
|
300 |
|
301 nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner()); |
|
302 NS_ENSURE_TRUE_VOID(target); |
|
303 |
|
304 target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this, |
|
305 /* useCapture = */ true); |
|
306 } |
|
307 |
|
308 if (!mStream) |
|
309 return; |
|
310 |
|
311 mStream->RemoveMainThreadListener(this); |
|
312 MediaStreamGraph* graph = mStream->Graph(); |
|
313 if (graph->IsNonRealtime()) { |
|
314 MediaStreamGraph::DestroyNonRealtimeInstance(graph); |
|
315 } |
|
316 AudioNode::DestroyMediaStream(); |
|
317 } |
|
318 |
|
319 void |
|
320 AudioDestinationNode::NotifyMainThreadStateChanged() |
|
321 { |
|
322 if (mStream->IsFinished() && !mHasFinished) { |
|
323 mHasFinished = true; |
|
324 if (mIsOffline) { |
|
325 nsCOMPtr<nsIRunnable> runnable = |
|
326 NS_NewRunnableMethod(this, &AudioDestinationNode::FireOfflineCompletionEvent); |
|
327 NS_DispatchToCurrentThread(runnable); |
|
328 } |
|
329 } |
|
330 } |
|
331 |
|
332 void |
|
333 AudioDestinationNode::FireOfflineCompletionEvent() |
|
334 { |
|
335 AudioNodeStream* stream = static_cast<AudioNodeStream*>(Stream()); |
|
336 OfflineDestinationNodeEngine* engine = |
|
337 static_cast<OfflineDestinationNodeEngine*>(stream->Engine()); |
|
338 engine->FireOfflineCompletionEvent(this); |
|
339 } |
|
340 |
|
341 uint32_t |
|
342 AudioDestinationNode::MaxChannelCount() const |
|
343 { |
|
344 return Context()->MaxChannelCount(); |
|
345 } |
|
346 |
|
347 void |
|
348 AudioDestinationNode::SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv) |
|
349 { |
|
350 if (aChannelCount > MaxChannelCount()) { |
|
351 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
|
352 return; |
|
353 } |
|
354 |
|
355 AudioNode::SetChannelCount(aChannelCount, aRv); |
|
356 } |
|
357 |
|
358 void |
|
359 AudioDestinationNode::Mute() |
|
360 { |
|
361 MOZ_ASSERT(Context() && !Context()->IsOffline()); |
|
362 SendDoubleParameterToStream(DestinationNodeEngine::VOLUME, 0.0f); |
|
363 } |
|
364 |
|
365 void |
|
366 AudioDestinationNode::Unmute() |
|
367 { |
|
368 MOZ_ASSERT(Context() && !Context()->IsOffline()); |
|
369 SendDoubleParameterToStream(DestinationNodeEngine::VOLUME, 1.0f); |
|
370 } |
|
371 |
|
372 void |
|
373 AudioDestinationNode::OfflineShutdown() |
|
374 { |
|
375 MOZ_ASSERT(Context() && Context()->IsOffline(), |
|
376 "Should only be called on a valid OfflineAudioContext"); |
|
377 |
|
378 MediaStreamGraph::DestroyNonRealtimeInstance(mStream->Graph()); |
|
379 mOfflineRenderingRef.Drop(this); |
|
380 } |
|
381 |
|
382 JSObject* |
|
383 AudioDestinationNode::WrapObject(JSContext* aCx) |
|
384 { |
|
385 return AudioDestinationNodeBinding::Wrap(aCx, this); |
|
386 } |
|
387 |
|
388 void |
|
389 AudioDestinationNode::StartRendering() |
|
390 { |
|
391 mOfflineRenderingRef.Take(this); |
|
392 mStream->Graph()->StartNonRealtimeProcessing(TrackRate(Context()->SampleRate()), mFramesToProduce); |
|
393 } |
|
394 |
|
395 void |
|
396 AudioDestinationNode::SetCanPlay(bool aCanPlay) |
|
397 { |
|
398 mStream->SetTrackEnabled(AudioNodeStream::AUDIO_TRACK, aCanPlay); |
|
399 } |
|
400 |
|
401 NS_IMETHODIMP |
|
402 AudioDestinationNode::HandleEvent(nsIDOMEvent* aEvent) |
|
403 { |
|
404 nsAutoString type; |
|
405 aEvent->GetType(type); |
|
406 |
|
407 if (!type.EqualsLiteral("visibilitychange")) { |
|
408 return NS_ERROR_FAILURE; |
|
409 } |
|
410 |
|
411 nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner()); |
|
412 NS_ENSURE_TRUE(docshell, NS_ERROR_FAILURE); |
|
413 |
|
414 bool isActive = false; |
|
415 docshell->GetIsActive(&isActive); |
|
416 |
|
417 mAudioChannelAgent->SetVisibilityState(isActive); |
|
418 return NS_OK; |
|
419 } |
|
420 |
|
421 NS_IMETHODIMP |
|
422 AudioDestinationNode::CanPlayChanged(int32_t aCanPlay) |
|
423 { |
|
424 SetCanPlay(aCanPlay == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL); |
|
425 return NS_OK; |
|
426 } |
|
427 |
|
428 NS_IMETHODIMP |
|
429 AudioDestinationNode::WindowVolumeChanged() |
|
430 { |
|
431 MOZ_ASSERT(mAudioChannelAgent); |
|
432 |
|
433 if (!mStream) { |
|
434 return NS_OK; |
|
435 } |
|
436 |
|
437 float volume; |
|
438 nsresult rv = mAudioChannelAgent->GetWindowVolume(&volume); |
|
439 NS_ENSURE_SUCCESS(rv, rv); |
|
440 |
|
441 mStream->SetAudioOutputVolume(&gWebAudioOutputKey, volume); |
|
442 return NS_OK; |
|
443 } |
|
444 |
|
445 AudioChannel |
|
446 AudioDestinationNode::MozAudioChannelType() const |
|
447 { |
|
448 return mAudioChannel; |
|
449 } |
|
450 |
|
451 void |
|
452 AudioDestinationNode::SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv) |
|
453 { |
|
454 if (Context()->IsOffline()) { |
|
455 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
|
456 return; |
|
457 } |
|
458 |
|
459 if (aValue != mAudioChannel && |
|
460 CheckAudioChannelPermissions(aValue)) { |
|
461 mAudioChannel = aValue; |
|
462 |
|
463 if (mAudioChannelAgent) { |
|
464 CreateAudioChannelAgent(); |
|
465 } |
|
466 } |
|
467 } |
|
468 |
|
469 bool |
|
470 AudioDestinationNode::CheckAudioChannelPermissions(AudioChannel aValue) |
|
471 { |
|
472 if (!Preferences::GetBool("media.useAudioChannelService")) { |
|
473 return true; |
|
474 } |
|
475 |
|
476 // Only normal channel doesn't need permission. |
|
477 if (aValue == AudioChannel::Normal) { |
|
478 return true; |
|
479 } |
|
480 |
|
481 // Maybe this audio channel is equal to the default one. |
|
482 if (aValue == AudioChannelService::GetDefaultAudioChannel()) { |
|
483 return true; |
|
484 } |
|
485 |
|
486 nsCOMPtr<nsIPermissionManager> permissionManager = |
|
487 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); |
|
488 if (!permissionManager) { |
|
489 return false; |
|
490 } |
|
491 |
|
492 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner()); |
|
493 NS_ASSERTION(sop, "Window didn't QI to nsIScriptObjectPrincipal!"); |
|
494 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal(); |
|
495 |
|
496 uint32_t perm = nsIPermissionManager::UNKNOWN_ACTION; |
|
497 |
|
498 nsCString channel; |
|
499 channel.AssignASCII(AudioChannelValues::strings[uint32_t(aValue)].value, |
|
500 AudioChannelValues::strings[uint32_t(aValue)].length); |
|
501 permissionManager->TestExactPermissionFromPrincipal(principal, |
|
502 nsCString(NS_LITERAL_CSTRING("audio-channel-") + channel).get(), |
|
503 &perm); |
|
504 |
|
505 return perm == nsIPermissionManager::ALLOW_ACTION; |
|
506 } |
|
507 |
|
508 void |
|
509 AudioDestinationNode::CreateAudioChannelAgent() |
|
510 { |
|
511 if (mAudioChannelAgent) { |
|
512 mAudioChannelAgent->StopPlaying(); |
|
513 } |
|
514 |
|
515 mAudioChannelAgent = new AudioChannelAgent(); |
|
516 mAudioChannelAgent->InitWithWeakCallback(GetOwner(), |
|
517 static_cast<int32_t>(mAudioChannel), |
|
518 this); |
|
519 |
|
520 nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner()); |
|
521 if (docshell) { |
|
522 bool isActive = false; |
|
523 docshell->GetIsActive(&isActive); |
|
524 mAudioChannelAgent->SetVisibilityState(isActive); |
|
525 } |
|
526 |
|
527 int32_t state = 0; |
|
528 mAudioChannelAgent->StartPlaying(&state); |
|
529 SetCanPlay(state == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL); |
|
530 } |
|
531 |
|
532 void |
|
533 AudioDestinationNode::NotifyStableState() |
|
534 { |
|
535 mExtraCurrentTimeUpdatedSinceLastStableState = false; |
|
536 } |
|
537 |
|
538 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); |
|
539 |
|
540 void |
|
541 AudioDestinationNode::ScheduleStableStateNotification() |
|
542 { |
|
543 nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID); |
|
544 if (appShell) { |
|
545 nsCOMPtr<nsIRunnable> event = |
|
546 NS_NewRunnableMethod(this, &AudioDestinationNode::NotifyStableState); |
|
547 appShell->RunInStableState(event); |
|
548 } |
|
549 } |
|
550 |
|
551 double |
|
552 AudioDestinationNode::ExtraCurrentTime() |
|
553 { |
|
554 if (!mStartedBlockingDueToBeingOnlyNode.IsNull() && |
|
555 !mExtraCurrentTimeUpdatedSinceLastStableState) { |
|
556 mExtraCurrentTimeUpdatedSinceLastStableState = true; |
|
557 mExtraCurrentTimeSinceLastStartedBlocking = |
|
558 (TimeStamp::Now() - mStartedBlockingDueToBeingOnlyNode).ToSeconds(); |
|
559 ScheduleStableStateNotification(); |
|
560 } |
|
561 return mExtraCurrentTime + mExtraCurrentTimeSinceLastStartedBlocking; |
|
562 } |
|
563 |
|
564 void |
|
565 AudioDestinationNode::SetIsOnlyNodeForContext(bool aIsOnlyNode) |
|
566 { |
|
567 if (!mStartedBlockingDueToBeingOnlyNode.IsNull() == aIsOnlyNode) { |
|
568 // Nothing changed. |
|
569 return; |
|
570 } |
|
571 |
|
572 if (!mStream) { |
|
573 // DestroyMediaStream has been called, presumably during CC Unlink(). |
|
574 return; |
|
575 } |
|
576 |
|
577 if (mIsOffline) { |
|
578 // Don't block the destination stream for offline AudioContexts, since |
|
579 // we expect the zero data produced when there are no other nodes to |
|
580 // show up in its result buffer. Also, we would get confused by adding |
|
581 // ExtraCurrentTime before StartRendering has even been called. |
|
582 return; |
|
583 } |
|
584 |
|
585 if (aIsOnlyNode) { |
|
586 mStream->ChangeExplicitBlockerCount(1); |
|
587 mStartedBlockingDueToBeingOnlyNode = TimeStamp::Now(); |
|
588 // Don't do an update of mExtraCurrentTimeSinceLastStartedBlocking until the next stable state. |
|
589 mExtraCurrentTimeUpdatedSinceLastStableState = true; |
|
590 ScheduleStableStateNotification(); |
|
591 } else { |
|
592 // Force update of mExtraCurrentTimeSinceLastStartedBlocking if necessary |
|
593 ExtraCurrentTime(); |
|
594 mExtraCurrentTime += mExtraCurrentTimeSinceLastStartedBlocking; |
|
595 mExtraCurrentTimeSinceLastStartedBlocking = 0; |
|
596 mStream->ChangeExplicitBlockerCount(-1); |
|
597 mStartedBlockingDueToBeingOnlyNode = TimeStamp(); |
|
598 } |
|
599 } |
|
600 |
|
601 } |
|
602 |
|
603 } |