Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/webaudio/OscillatorNode.cpp
Line
Count
Source (jump to first uncovered line)
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 "OscillatorNode.h"
8
#include "AudioNodeEngine.h"
9
#include "AudioNodeStream.h"
10
#include "AudioDestinationNode.h"
11
#include "nsContentUtils.h"
12
#include "WebAudioUtils.h"
13
#include "blink/PeriodicWave.h"
14
15
namespace mozilla {
16
namespace dom {
17
18
NS_IMPL_CYCLE_COLLECTION_INHERITED(OscillatorNode, AudioScheduledSourceNode,
19
                                   mPeriodicWave, mFrequency, mDetune)
20
21
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OscillatorNode)
22
0
NS_INTERFACE_MAP_END_INHERITING(AudioScheduledSourceNode)
23
24
NS_IMPL_ADDREF_INHERITED(OscillatorNode, AudioScheduledSourceNode)
25
NS_IMPL_RELEASE_INHERITED(OscillatorNode, AudioScheduledSourceNode)
26
27
class OscillatorNodeEngine final : public AudioNodeEngine
28
{
29
public:
30
  OscillatorNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination)
31
    : AudioNodeEngine(aNode)
32
    , mSource(nullptr)
33
    , mDestination(aDestination->Stream())
34
    , mStart(-1)
35
    , mStop(STREAM_TIME_MAX)
36
    // Keep the default values in sync with OscillatorNode::OscillatorNode.
37
    , mFrequency(440.f)
38
    , mDetune(0.f)
39
    , mType(OscillatorType::Sine)
40
    , mPhase(0.)
41
    , mFinalFrequency(0.)
42
    , mPhaseIncrement(0.)
43
    , mRecomputeParameters(true)
44
    , mCustomDisableNormalization(false)
45
0
  {
46
0
    MOZ_ASSERT(NS_IsMainThread());
47
0
    mBasicWaveFormCache = aDestination->Context()->GetBasicWaveFormCache();
48
0
  }
49
50
  void SetSourceStream(AudioNodeStream* aSource)
51
0
  {
52
0
    mSource = aSource;
53
0
  }
54
55
  enum Parameters {
56
    FREQUENCY,
57
    DETUNE,
58
    TYPE,
59
    DISABLE_NORMALIZATION,
60
    START,
61
    STOP,
62
  };
63
  void RecvTimelineEvent(uint32_t aIndex,
64
                         AudioTimelineEvent& aEvent) override
65
0
  {
66
0
    mRecomputeParameters = true;
67
0
68
0
    MOZ_ASSERT(mDestination);
69
0
70
0
    WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent,
71
0
                                                    mDestination);
72
0
73
0
    switch (aIndex) {
74
0
    case FREQUENCY:
75
0
      mFrequency.InsertEvent<int64_t>(aEvent);
76
0
      break;
77
0
    case DETUNE:
78
0
      mDetune.InsertEvent<int64_t>(aEvent);
79
0
      break;
80
0
    default:
81
0
      NS_ERROR("Bad OscillatorNodeEngine TimelineParameter");
82
0
    }
83
0
  }
84
85
  void SetStreamTimeParameter(uint32_t aIndex, StreamTime aParam) override
86
0
  {
87
0
    switch (aIndex) {
88
0
    case START:
89
0
      mStart = aParam;
90
0
      mSource->SetActive();
91
0
      break;
92
0
    case STOP: mStop = aParam; break;
93
0
    default:
94
0
      NS_ERROR("Bad OscillatorNodeEngine StreamTimeParameter");
95
0
    }
96
0
  }
97
98
  void SetInt32Parameter(uint32_t aIndex, int32_t aParam) override
99
0
  {
100
0
    switch (aIndex) {
101
0
      case TYPE:
102
0
        // Set the new type.
103
0
        mType = static_cast<OscillatorType>(aParam);
104
0
        if (mType == OscillatorType::Sine) {
105
0
          // Forget any previous custom data.
106
0
          mCustomDisableNormalization = false;
107
0
          mPeriodicWave = nullptr;
108
0
          mRecomputeParameters = true;
109
0
        }
110
0
        switch (mType) {
111
0
          case OscillatorType::Sine:
112
0
            mPhase = 0.0;
113
0
            break;
114
0
          case OscillatorType::Square:
115
0
          case OscillatorType::Triangle:
116
0
          case OscillatorType::Sawtooth:
117
0
            mPeriodicWave = mBasicWaveFormCache->GetBasicWaveForm(mType);
118
0
            break;
119
0
          case OscillatorType::Custom:
120
0
            break;
121
0
          default:
122
0
            NS_ERROR("Bad OscillatorNodeEngine type parameter.");
123
0
        }
124
0
        // End type switch.
125
0
        break;
126
0
      case DISABLE_NORMALIZATION:
127
0
        MOZ_ASSERT(aParam >= 0, "negative custom array length");
128
0
        mCustomDisableNormalization = static_cast<uint32_t>(aParam);
129
0
        break;
130
0
      default:
131
0
        NS_ERROR("Bad OscillatorNodeEngine Int32Parameter.");
132
0
    }
133
0
    // End index switch.
134
0
  }
135
136
  void SetBuffer(AudioChunk&& aBuffer) override
137
0
  {
138
0
    MOZ_ASSERT(aBuffer.ChannelCount() == 2,
139
0
               "PeriodicWave should have sent two channels");
140
0
    MOZ_ASSERT(aBuffer.mVolume == 1.0f);
141
0
    mPeriodicWave =
142
0
      WebCore::PeriodicWave::create(mSource->SampleRate(),
143
0
                                    aBuffer.ChannelData<float>()[0],
144
0
                                    aBuffer.ChannelData<float>()[1],
145
0
                                    aBuffer.mDuration,
146
0
                                    mCustomDisableNormalization);
147
0
  }
148
149
  void IncrementPhase()
150
0
  {
151
0
    const float twoPiFloat = float(2 * M_PI);
152
0
    mPhase += mPhaseIncrement;
153
0
    if (mPhase > twoPiFloat) {
154
0
      mPhase -= twoPiFloat;
155
0
    } else if (mPhase < -twoPiFloat) {
156
0
      mPhase += twoPiFloat;
157
0
    }
158
0
  }
159
160
  // Returns true if the final frequency (and thus the phase increment) changed,
161
  // false otherwise. This allow some optimizations at callsite.
162
  bool UpdateParametersIfNeeded(StreamTime ticks, size_t count)
163
0
  {
164
0
    double frequency, detune;
165
0
166
0
    // Shortcut if frequency-related AudioParam are not automated, and we
167
0
    // already have computed the frequency information and related parameters.
168
0
    if (!ParametersMayNeedUpdate()) {
169
0
      return false;
170
0
    }
171
0
172
0
    bool simpleFrequency = mFrequency.HasSimpleValue();
173
0
    bool simpleDetune = mDetune.HasSimpleValue();
174
0
175
0
    if (simpleFrequency) {
176
0
      frequency = mFrequency.GetValue();
177
0
    } else {
178
0
      frequency = mFrequency.GetValueAtTime(ticks, count);
179
0
    }
180
0
    if (simpleDetune) {
181
0
      detune = mDetune.GetValue();
182
0
    } else {
183
0
      detune = mDetune.GetValueAtTime(ticks, count);
184
0
    }
185
0
186
0
    float finalFrequency = frequency * pow(2., detune / 1200.);
187
0
    float signalPeriod = mSource->SampleRate() / finalFrequency;
188
0
    mRecomputeParameters = false;
189
0
190
0
    mPhaseIncrement = 2 * M_PI / signalPeriod;
191
0
192
0
    if (finalFrequency != mFinalFrequency) {
193
0
      mFinalFrequency = finalFrequency;
194
0
      return true;
195
0
    }
196
0
    return false;
197
0
  }
198
199
  void FillBounds(float* output, StreamTime ticks,
200
                  uint32_t& start, uint32_t& end)
201
0
  {
202
0
    MOZ_ASSERT(output);
203
0
    static_assert(StreamTime(WEBAUDIO_BLOCK_SIZE) < UINT_MAX,
204
0
        "WEBAUDIO_BLOCK_SIZE overflows interator bounds.");
205
0
    start = 0;
206
0
    if (ticks < mStart) {
207
0
      start = mStart - ticks;
208
0
      for (uint32_t i = 0; i < start; ++i) {
209
0
        output[i] = 0.0;
210
0
      }
211
0
    }
212
0
    end = WEBAUDIO_BLOCK_SIZE;
213
0
    if (ticks + end > mStop) {
214
0
      end = mStop - ticks;
215
0
      for (uint32_t i = end; i < WEBAUDIO_BLOCK_SIZE; ++i) {
216
0
        output[i] = 0.0;
217
0
      }
218
0
    }
219
0
  }
220
221
  void ComputeSine(float * aOutput, StreamTime ticks, uint32_t aStart, uint32_t aEnd)
222
0
  {
223
0
    for (uint32_t i = aStart; i < aEnd; ++i) {
224
0
      // We ignore the return value, changing the frequency has no impact on
225
0
      // performances here.
226
0
      UpdateParametersIfNeeded(ticks, i);
227
0
228
0
      aOutput[i] = sin(mPhase);
229
0
230
0
      IncrementPhase();
231
0
    }
232
0
  }
233
234
  bool ParametersMayNeedUpdate()
235
0
  {
236
0
    return !mDetune.HasSimpleValue() ||
237
0
           !mFrequency.HasSimpleValue() ||
238
0
           mRecomputeParameters;
239
0
  }
240
241
  void ComputeCustom(float* aOutput,
242
                     StreamTime ticks,
243
                     uint32_t aStart,
244
                     uint32_t aEnd)
245
0
  {
246
0
    MOZ_ASSERT(mPeriodicWave, "No custom waveform data");
247
0
248
0
    uint32_t periodicWaveSize = mPeriodicWave->periodicWaveSize();
249
0
    // Mask to wrap wave data indices into the range [0,periodicWaveSize).
250
0
    uint32_t indexMask = periodicWaveSize - 1;
251
0
    MOZ_ASSERT(periodicWaveSize && (periodicWaveSize & indexMask) == 0,
252
0
               "periodicWaveSize must be power of 2");
253
0
    float* higherWaveData = nullptr;
254
0
    float* lowerWaveData = nullptr;
255
0
    float tableInterpolationFactor;
256
0
    // Phase increment at frequency of 1 Hz.
257
0
    // mPhase runs [0,periodicWaveSize) here instead of [0,2*M_PI).
258
0
    float basePhaseIncrement = mPeriodicWave->rateScale();
259
0
260
0
    bool needToFetchWaveData = UpdateParametersIfNeeded(ticks, aStart);
261
0
262
0
    bool parametersMayNeedUpdate = ParametersMayNeedUpdate();
263
0
    mPeriodicWave->waveDataForFundamentalFrequency(mFinalFrequency,
264
0
                                                   lowerWaveData,
265
0
                                                   higherWaveData,
266
0
                                                   tableInterpolationFactor);
267
0
268
0
    for (uint32_t i = aStart; i < aEnd; ++i) {
269
0
      if (parametersMayNeedUpdate) {
270
0
        if (needToFetchWaveData) {
271
0
          mPeriodicWave->waveDataForFundamentalFrequency(mFinalFrequency,
272
0
                                                         lowerWaveData,
273
0
                                                         higherWaveData,
274
0
                                                         tableInterpolationFactor);
275
0
        }
276
0
        needToFetchWaveData = UpdateParametersIfNeeded(ticks, i);
277
0
      }
278
0
      // Bilinear interpolation between adjacent samples in each table.
279
0
      float floorPhase = floorf(mPhase);
280
0
      int j1Signed = static_cast<int>(floorPhase);
281
0
      uint32_t j1 = j1Signed & indexMask;
282
0
      uint32_t j2 = j1 + 1;
283
0
      j2 &= indexMask;
284
0
285
0
      float sampleInterpolationFactor = mPhase - floorPhase;
286
0
287
0
      float lower = (1.0f - sampleInterpolationFactor) * lowerWaveData[j1] +
288
0
                    sampleInterpolationFactor * lowerWaveData[j2];
289
0
      float higher = (1.0f - sampleInterpolationFactor) * higherWaveData[j1] +
290
0
                    sampleInterpolationFactor * higherWaveData[j2];
291
0
      aOutput[i] = (1.0f - tableInterpolationFactor) * lower +
292
0
                   tableInterpolationFactor * higher;
293
0
294
0
      // Calculate next phase position from wrapped value j1 to avoid loss of
295
0
      // precision at large values.
296
0
      mPhase =
297
0
        j1 + sampleInterpolationFactor + basePhaseIncrement * mFinalFrequency;
298
0
    }
299
0
  }
300
301
  void ComputeSilence(AudioBlock *aOutput)
302
0
  {
303
0
    aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
304
0
  }
305
306
  void ProcessBlock(AudioNodeStream* aStream,
307
                    GraphTime aFrom,
308
                    const AudioBlock& aInput,
309
                    AudioBlock* aOutput,
310
                    bool* aFinished) override
311
0
  {
312
0
    MOZ_ASSERT(mSource == aStream, "Invalid source stream");
313
0
314
0
    StreamTime ticks = mDestination->GraphTimeToStreamTime(aFrom);
315
0
    if (mStart == -1) {
316
0
      ComputeSilence(aOutput);
317
0
      return;
318
0
    }
319
0
320
0
    if (ticks + WEBAUDIO_BLOCK_SIZE <= mStart || ticks >= mStop) {
321
0
      ComputeSilence(aOutput);
322
0
323
0
    } else {
324
0
      aOutput->AllocateChannels(1);
325
0
      float* output = aOutput->ChannelFloatsForWrite(0);
326
0
327
0
      uint32_t start, end;
328
0
      FillBounds(output, ticks, start, end);
329
0
330
0
      // Synthesize the correct waveform.
331
0
      switch(mType) {
332
0
        case OscillatorType::Sine:
333
0
          ComputeSine(output, ticks, start, end);
334
0
          break;
335
0
        case OscillatorType::Square:
336
0
        case OscillatorType::Triangle:
337
0
        case OscillatorType::Sawtooth:
338
0
        case OscillatorType::Custom:
339
0
          ComputeCustom(output, ticks, start, end);
340
0
          break;
341
0
        default:
342
0
          ComputeSilence(aOutput);
343
0
      };
344
0
    }
345
0
346
0
    if (ticks + WEBAUDIO_BLOCK_SIZE >= mStop) {
347
0
      // We've finished playing.
348
0
      *aFinished = true;
349
0
    }
350
0
  }
351
352
  bool IsActive() const override
353
0
  {
354
0
    // start() has been called.
355
0
    return mStart != -1;
356
0
  }
357
358
  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
359
0
  {
360
0
    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
361
0
362
0
    // Not owned:
363
0
    // - mSource
364
0
    // - mDestination
365
0
    // - mFrequency (internal ref owned by node)
366
0
    // - mDetune (internal ref owned by node)
367
0
368
0
    if (mPeriodicWave) {
369
0
      amount += mPeriodicWave->sizeOfIncludingThis(aMallocSizeOf);
370
0
    }
371
0
372
0
    return amount;
373
0
  }
374
375
  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
376
0
  {
377
0
    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
378
0
  }
379
380
  // mSource deletes this engine in its destructor
381
  AudioNodeStream* MOZ_NON_OWNING_REF mSource;
382
  RefPtr<AudioNodeStream> mDestination;
383
  StreamTime mStart;
384
  StreamTime mStop;
385
  AudioParamTimeline mFrequency;
386
  AudioParamTimeline mDetune;
387
  OscillatorType mType;
388
  float mPhase;
389
  float mFinalFrequency;
390
  float mPhaseIncrement;
391
  bool mRecomputeParameters;
392
  RefPtr<BasicWaveFormCache> mBasicWaveFormCache;
393
  bool mCustomDisableNormalization;
394
  RefPtr<WebCore::PeriodicWave> mPeriodicWave;
395
};
396
397
OscillatorNode::OscillatorNode(AudioContext* aContext)
398
  : AudioScheduledSourceNode(aContext,
399
                             2,
400
                             ChannelCountMode::Max,
401
                             ChannelInterpretation::Speakers)
402
  , mType(OscillatorType::Sine)
403
  , mFrequency(
404
    new AudioParam(this, OscillatorNodeEngine::FREQUENCY, "frequency", 440.0f,
405
                   -(aContext->SampleRate() / 2), aContext->SampleRate() / 2))
406
  , mDetune(new AudioParam(this, OscillatorNodeEngine::DETUNE, "detune", 0.0f))
407
  , mStartCalled(false)
408
0
{
409
0
410
0
  OscillatorNodeEngine* engine = new OscillatorNodeEngine(this, aContext->Destination());
411
0
  mStream = AudioNodeStream::Create(aContext, engine,
412
0
                                    AudioNodeStream::NEED_MAIN_THREAD_FINISHED,
413
0
                                    aContext->Graph());
414
0
  engine->SetSourceStream(mStream);
415
0
  mStream->AddMainThreadListener(this);
416
0
}
417
418
/* static */ already_AddRefed<OscillatorNode>
419
OscillatorNode::Create(AudioContext& aAudioContext,
420
                       const OscillatorOptions& aOptions,
421
                       ErrorResult& aRv)
422
0
{
423
0
  if (aAudioContext.CheckClosed(aRv)) {
424
0
    return nullptr;
425
0
  }
426
0
427
0
  RefPtr<OscillatorNode> audioNode = new OscillatorNode(&aAudioContext);
428
0
429
0
  audioNode->Initialize(aOptions, aRv);
430
0
  if (NS_WARN_IF(aRv.Failed())) {
431
0
    return nullptr;
432
0
  }
433
0
434
0
  audioNode->SetType(aOptions.mType, aRv);
435
0
  if (NS_WARN_IF(aRv.Failed())) {
436
0
    return nullptr;
437
0
  }
438
0
439
0
  audioNode->Frequency()->SetValue(aOptions.mFrequency);
440
0
  audioNode->Detune()->SetValue(aOptions.mDetune);
441
0
442
0
  if (aOptions.mPeriodicWave.WasPassed()) {
443
0
    audioNode->SetPeriodicWave(aOptions.mPeriodicWave.Value());
444
0
  }
445
0
446
0
  return audioNode.forget();
447
0
}
448
449
size_t
450
OscillatorNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
451
0
{
452
0
  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
453
0
454
0
  // For now only report if we know for sure that it's not shared.
455
0
  if (mPeriodicWave) {
456
0
    amount += mPeriodicWave->SizeOfIncludingThisIfNotShared(aMallocSizeOf);
457
0
  }
458
0
  amount += mFrequency->SizeOfIncludingThis(aMallocSizeOf);
459
0
  amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
460
0
  return amount;
461
0
}
462
463
size_t
464
OscillatorNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
465
0
{
466
0
  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
467
0
}
468
469
JSObject*
470
OscillatorNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
471
0
{
472
0
  return OscillatorNode_Binding::Wrap(aCx, this, aGivenProto);
473
0
}
474
475
void
476
OscillatorNode::DestroyMediaStream()
477
0
{
478
0
  if (mStream) {
479
0
    mStream->RemoveMainThreadListener(this);
480
0
  }
481
0
  AudioNode::DestroyMediaStream();
482
0
}
483
484
void
485
OscillatorNode::SendTypeToStream()
486
0
{
487
0
  if (!mStream) {
488
0
    return;
489
0
  }
490
0
  if (mType == OscillatorType::Custom) {
491
0
    // The engine assumes we'll send the custom data before updating the type.
492
0
    SendPeriodicWaveToStream();
493
0
  }
494
0
  SendInt32ParameterToStream(OscillatorNodeEngine::TYPE, static_cast<int32_t>(mType));
495
0
}
496
497
void OscillatorNode::SendPeriodicWaveToStream()
498
0
{
499
0
  NS_ASSERTION(mType == OscillatorType::Custom,
500
0
               "Sending custom waveform to engine thread with non-custom type");
501
0
  MOZ_ASSERT(mStream, "Missing node stream.");
502
0
  MOZ_ASSERT(mPeriodicWave, "Send called without PeriodicWave object.");
503
0
  SendInt32ParameterToStream(OscillatorNodeEngine::DISABLE_NORMALIZATION,
504
0
                             mPeriodicWave->DisableNormalization());
505
0
  AudioChunk data = mPeriodicWave->GetThreadSharedBuffer();
506
0
  mStream->SetBuffer(std::move(data));
507
0
}
508
509
void
510
OscillatorNode::Start(double aWhen, ErrorResult& aRv)
511
0
{
512
0
  if (!WebAudioUtils::IsTimeValid(aWhen)) {
513
0
    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
514
0
    return;
515
0
  }
516
0
517
0
  if (mStartCalled) {
518
0
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
519
0
    return;
520
0
  }
521
0
  mStartCalled = true;
522
0
523
0
  if (!mStream) {
524
0
    // Nothing to play, or we're already dead for some reason
525
0
    return;
526
0
  }
527
0
528
0
  // TODO: Perhaps we need to do more here.
529
0
  mStream->SetStreamTimeParameter(OscillatorNodeEngine::START,
530
0
                                  Context(), aWhen);
531
0
532
0
  MarkActive();
533
0
}
534
535
void
536
OscillatorNode::Stop(double aWhen, ErrorResult& aRv)
537
0
{
538
0
  if (!WebAudioUtils::IsTimeValid(aWhen)) {
539
0
    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
540
0
    return;
541
0
  }
542
0
543
0
  if (!mStartCalled) {
544
0
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
545
0
    return;
546
0
  }
547
0
548
0
  if (!mStream || !Context()) {
549
0
    // We've already stopped and had our stream shut down
550
0
    return;
551
0
  }
552
0
553
0
  // TODO: Perhaps we need to do more here.
554
0
  mStream->SetStreamTimeParameter(OscillatorNodeEngine::STOP,
555
0
                                  Context(), std::max(0.0, aWhen));
556
0
}
557
558
void
559
OscillatorNode::NotifyMainThreadStreamFinished()
560
0
{
561
0
  MOZ_ASSERT(mStream->IsFinished());
562
0
563
0
  class EndedEventDispatcher final : public Runnable
564
0
  {
565
0
  public:
566
0
    explicit EndedEventDispatcher(OscillatorNode* aNode)
567
0
      : mozilla::Runnable("EndedEventDispatcher")
568
0
      , mNode(aNode)
569
0
    {
570
0
    }
571
0
    NS_IMETHOD Run() override
572
0
    {
573
0
      // If it's not safe to run scripts right now, schedule this to run later
574
0
      if (!nsContentUtils::IsSafeToRunScript()) {
575
0
        nsContentUtils::AddScriptRunner(this);
576
0
        return NS_OK;
577
0
      }
578
0
579
0
      mNode->DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
580
0
      // Release stream resources.
581
0
      mNode->DestroyMediaStream();
582
0
      return NS_OK;
583
0
    }
584
0
  private:
585
0
    RefPtr<OscillatorNode> mNode;
586
0
  };
587
0
588
0
  Context()->Dispatch(do_AddRef(new EndedEventDispatcher(this)));
589
0
590
0
  // Drop the playing reference
591
0
  // Warning: The below line might delete this.
592
0
  MarkInactive();
593
0
}
594
595
} // namespace dom
596
} // namespace mozilla