Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/TrackUnionStream.cpp
Line
Count
Source (jump to first uncovered line)
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "MediaStreamGraphImpl.h"
6
#include "MediaStreamListener.h"
7
#include "mozilla/MathAlgorithms.h"
8
#include "mozilla/Unused.h"
9
10
#include "AudioSegment.h"
11
#include "VideoSegment.h"
12
#include "nsContentUtils.h"
13
#include "nsIAppShell.h"
14
#include "nsIObserver.h"
15
#include "nsPrintfCString.h"
16
#include "nsServiceManagerUtils.h"
17
#include "nsWidgetsCID.h"
18
#include "prerror.h"
19
#include "mozilla/Logging.h"
20
#include "mozilla/Attributes.h"
21
#include "TrackUnionStream.h"
22
#include "ImageContainer.h"
23
#include "AudioChannelService.h"
24
#include "AudioNodeEngine.h"
25
#include "AudioNodeStream.h"
26
#include "AudioNodeExternalInputStream.h"
27
#include "webaudio/MediaStreamAudioDestinationNode.h"
28
#include <algorithm>
29
#include "DOMMediaStream.h"
30
#include "GeckoProfiler.h"
31
32
using namespace mozilla::layers;
33
using namespace mozilla::dom;
34
using namespace mozilla::gfx;
35
36
namespace mozilla {
37
38
#ifdef STREAM_LOG
39
#undef STREAM_LOG
40
#endif
41
42
LazyLogModule gTrackUnionStreamLog("TrackUnionStream");
43
0
#define STREAM_LOG(type, msg) MOZ_LOG(gTrackUnionStreamLog, type, msg)
44
45
TrackUnionStream::TrackUnionStream()
46
  : ProcessedMediaStream()
47
  , mNextAvailableTrackID(1)
48
0
{
49
0
}
50
51
  void TrackUnionStream::RemoveInput(MediaInputPort* aPort)
52
0
  {
53
0
    STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p removing input %p", this, aPort));
54
0
    for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
55
0
      if (mTrackMap[i].mInputPort == aPort) {
56
0
        STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p removing trackmap entry %d", this, i));
57
0
        nsTArray<RefPtr<DirectMediaStreamTrackListener>> listeners(
58
0
          mTrackMap[i].mOwnedDirectListeners);
59
0
        for (auto listener : listeners) {
60
0
          // Remove listeners while the entry still exists.
61
0
          RemoveDirectTrackListenerImpl(listener, mTrackMap[i].mOutputTrackID);
62
0
        }
63
0
        EndTrack(i);
64
0
        mTrackMap.RemoveElementAt(i);
65
0
      }
66
0
    }
67
0
    ProcessedMediaStream::RemoveInput(aPort);
68
0
  }
69
  void TrackUnionStream::ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags)
70
0
  {
71
0
    TRACE_AUDIO_CALLBACK_COMMENT("TrackUnionStream %p", this);
72
0
    if (IsFinishedOnGraphThread()) {
73
0
      return;
74
0
    }
75
0
    AutoTArray<bool,8> mappedTracksFinished;
76
0
    AutoTArray<bool,8> mappedTracksWithMatchingInputTracks;
77
0
    for (uint32_t i = 0; i < mTrackMap.Length(); ++i) {
78
0
      mappedTracksFinished.AppendElement(true);
79
0
      mappedTracksWithMatchingInputTracks.AppendElement(false);
80
0
    }
81
0
82
0
    AutoTArray<MediaInputPort*, 32> inputs(mInputs);
83
0
    inputs.AppendElements(mSuspendedInputs);
84
0
85
0
    bool allFinished = !inputs.IsEmpty();
86
0
    bool allHaveCurrentData = !inputs.IsEmpty();
87
0
    for (uint32_t i = 0; i < inputs.Length(); ++i) {
88
0
      MediaStream* stream = inputs[i]->GetSource();
89
0
      if (!stream->IsFinishedOnGraphThread()) {
90
0
        // XXX we really should check whether 'stream' has finished within time aTo,
91
0
        // not just that it's finishing when all its queued data eventually runs
92
0
        // out.
93
0
        allFinished = false;
94
0
      }
95
0
      if (!stream->HasCurrentData()) {
96
0
        allHaveCurrentData = false;
97
0
      }
98
0
      bool trackAdded = false;
99
0
      for (StreamTracks::TrackIter tracks(stream->GetStreamTracks());
100
0
           !tracks.IsEnded(); tracks.Next()) {
101
0
        bool found = false;
102
0
        for (uint32_t j = 0; j < mTrackMap.Length(); ++j) {
103
0
          TrackMapEntry* map = &mTrackMap[j];
104
0
          if (map->mInputPort == inputs[i] && map->mInputTrackID == tracks->GetID()) {
105
0
            bool trackFinished = false;
106
0
            StreamTracks::Track* outputTrack = mTracks.FindTrack(map->mOutputTrackID);
107
0
            found = true;
108
0
            if (!outputTrack || outputTrack->IsEnded() ||
109
0
                !inputs[i]->PassTrackThrough(tracks->GetID())) {
110
0
              trackFinished = true;
111
0
            } else {
112
0
              CopyTrackData(tracks.get(), j, aFrom, aTo, &trackFinished);
113
0
            }
114
0
            mappedTracksFinished[j] = trackFinished;
115
0
            mappedTracksWithMatchingInputTracks[j] = true;
116
0
            break;
117
0
          }
118
0
        }
119
0
        if (!found && inputs[i]->AllowCreationOf(tracks->GetID())) {
120
0
          bool trackFinished = false;
121
0
          trackAdded = true;
122
0
          uint32_t mapIndex = AddTrack(inputs[i], tracks.get(), aFrom);
123
0
          CopyTrackData(tracks.get(), mapIndex, aFrom, aTo, &trackFinished);
124
0
          mappedTracksFinished.AppendElement(trackFinished);
125
0
          mappedTracksWithMatchingInputTracks.AppendElement(true);
126
0
        }
127
0
      }
128
0
      if (trackAdded) {
129
0
        for (MediaStreamListener* l : mListeners) {
130
0
          l->NotifyFinishedTrackCreation(Graph());
131
0
        }
132
0
      }
133
0
    }
134
0
    for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
135
0
      if (mappedTracksFinished[i]) {
136
0
        EndTrack(i);
137
0
      } else {
138
0
        allFinished = false;
139
0
      }
140
0
      if (!mappedTracksWithMatchingInputTracks[i]) {
141
0
        for (auto listener : mTrackMap[i].mOwnedDirectListeners) {
142
0
          // Remove listeners while the entry still exists.
143
0
          RemoveDirectTrackListenerImpl(listener, mTrackMap[i].mOutputTrackID);
144
0
        }
145
0
        mTrackMap.RemoveElementAt(i);
146
0
      }
147
0
    }
148
0
    if (allFinished && mAutofinish && (aFlags & ALLOW_FINISH)) {
149
0
      // All streams have finished and won't add any more tracks, and
150
0
      // all our tracks have actually finished and been removed from our map,
151
0
      // so we're finished now.
152
0
      FinishOnGraphThread();
153
0
    } else {
154
0
      mTracks.AdvanceKnownTracksTime(GraphTimeToStreamTimeWithBlocking(aTo));
155
0
    }
156
0
    if (allHaveCurrentData) {
157
0
      // We can make progress if we're not blocked
158
0
      mHasCurrentData = true;
159
0
    }
160
0
  }
161
162
  uint32_t TrackUnionStream::AddTrack(MediaInputPort* aPort, StreamTracks::Track* aTrack,
163
                    GraphTime aFrom)
164
0
  {
165
0
    STREAM_LOG(LogLevel::Verbose, ("TrackUnionStream %p adding track %d for "
166
0
                                   "input stream %p track %d, desired id %d",
167
0
                                   this, aTrack->GetID(), aPort->GetSource(),
168
0
                                   aTrack->GetID(),
169
0
                                   aPort->GetDestinationTrackId()));
170
0
171
0
    TrackID id;
172
0
    if (IsTrackIDExplicit(id = aPort->GetDestinationTrackId())) {
173
0
      MOZ_ASSERT(id >= mNextAvailableTrackID &&
174
0
                 !mUsedTracks.ContainsSorted(id),
175
0
                 "Desired destination id taken. Only provide a destination ID "
176
0
                 "if you can assure its availability, or we may not be able "
177
0
                 "to bind to the correct DOM-side track.");
178
#ifdef DEBUG
179
      AutoTArray<MediaInputPort*, 32> inputs(mInputs);
180
      inputs.AppendElements(mSuspendedInputs);
181
      for (size_t i = 0; inputs[i] != aPort; ++i) {
182
        MOZ_ASSERT(inputs[i]->GetSourceTrackId() != TRACK_ANY,
183
                   "You are adding a MediaInputPort with a track mapping "
184
                   "while there already exist generic MediaInputPorts for this "
185
                   "destination stream. This can lead to TrackID collisions!");
186
      }
187
#endif
188
      mUsedTracks.InsertElementSorted(id);
189
0
    } else if ((id = aTrack->GetID()) &&
190
0
               id > mNextAvailableTrackID &&
191
0
               !mUsedTracks.ContainsSorted(id)) {
192
0
      // Input id available. Mark it used in mUsedTracks.
193
0
      mUsedTracks.InsertElementSorted(id);
194
0
    } else {
195
0
      // No desired destination id and Input id taken, allocate a new one.
196
0
      id = mNextAvailableTrackID;
197
0
198
0
      // Update mNextAvailableTrackID and prune any mUsedTracks members it now
199
0
      // covers.
200
0
      while (1) {
201
0
        if (!mUsedTracks.RemoveElementSorted(++mNextAvailableTrackID)) {
202
0
          // Not in use. We're done.
203
0
          break;
204
0
        }
205
0
      }
206
0
    }
207
0
208
0
    // Round up the track start time so the track, if anything, starts a
209
0
    // little later than the true time. This means we'll have enough
210
0
    // samples in our input stream to go just beyond the destination time.
211
0
    StreamTime outputStart = GraphTimeToStreamTimeWithBlocking(aFrom);
212
0
213
0
    nsAutoPtr<MediaSegment> segment;
214
0
    segment = aTrack->GetSegment()->CreateEmptyClone();
215
0
    for (uint32_t j = 0; j < mListeners.Length(); ++j) {
216
0
      MediaStreamListener* l = mListeners[j];
217
0
      l->NotifyQueuedTrackChanges(Graph(), id, outputStart,
218
0
                                  TrackEventCommand::TRACK_EVENT_CREATED,
219
0
                                  *segment,
220
0
                                  aPort->GetSource(), aTrack->GetID());
221
0
    }
222
0
    segment->AppendNullData(outputStart);
223
0
    StreamTracks::Track* track =
224
0
      &mTracks.AddTrack(id, outputStart, segment.forget());
225
0
    STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p added track %d for input stream %p track %d, start ticks %lld",
226
0
                                 this, track->GetID(), aPort->GetSource(), aTrack->GetID(),
227
0
                                 (long long)outputStart));
228
0
229
0
    TrackMapEntry* map = mTrackMap.AppendElement();
230
0
    map->mEndOfConsumedInputTicks = 0;
231
0
    map->mEndOfLastInputIntervalInInputStream = -1;
232
0
    map->mEndOfLastInputIntervalInOutputStream = -1;
233
0
    map->mInputPort = aPort;
234
0
    map->mInputTrackID = aTrack->GetID();
235
0
    map->mOutputTrackID = track->GetID();
236
0
    map->mSegment = aTrack->GetSegment()->CreateEmptyClone();
237
0
238
0
    for (int32_t i = mPendingDirectTrackListeners.Length() - 1; i >= 0; --i) {
239
0
      TrackBound<DirectMediaStreamTrackListener>& bound =
240
0
        mPendingDirectTrackListeners[i];
241
0
      if (bound.mTrackID != map->mOutputTrackID) {
242
0
        continue;
243
0
      }
244
0
      MediaStream* source = map->mInputPort->GetSource();
245
0
      map->mOwnedDirectListeners.AppendElement(bound.mListener);
246
0
      DisabledTrackMode currentMode = GetDisabledTrackMode(bound.mTrackID);
247
0
      if (currentMode != DisabledTrackMode::ENABLED) {
248
0
        bound.mListener->IncreaseDisabled(currentMode);
249
0
      }
250
0
      STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p adding direct listener "
251
0
                                   "%p for track %d. Forwarding to input "
252
0
                                   "stream %p track %d.",
253
0
                                   this, bound.mListener.get(), bound.mTrackID,
254
0
                                   source, map->mInputTrackID));
255
0
      source->AddDirectTrackListenerImpl(bound.mListener.forget(),
256
0
                                         map->mInputTrackID);
257
0
      mPendingDirectTrackListeners.RemoveElementAt(i);
258
0
    }
259
0
260
0
    return mTrackMap.Length() - 1;
261
0
  }
262
263
  void TrackUnionStream::EndTrack(uint32_t aIndex)
264
0
  {
265
0
    StreamTracks::Track* outputTrack = mTracks.FindTrack(mTrackMap[aIndex].mOutputTrackID);
266
0
    if (!outputTrack || outputTrack->IsEnded())
267
0
      return;
268
0
    STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p ending track %d", this, outputTrack->GetID()));
269
0
    for (uint32_t j = 0; j < mListeners.Length(); ++j) {
270
0
      MediaStreamListener* l = mListeners[j];
271
0
      StreamTime offset = outputTrack->GetSegment()->GetDuration();
272
0
      nsAutoPtr<MediaSegment> segment;
273
0
      segment = outputTrack->GetSegment()->CreateEmptyClone();
274
0
      l->NotifyQueuedTrackChanges(Graph(), outputTrack->GetID(), offset,
275
0
                                  TrackEventCommand::TRACK_EVENT_ENDED,
276
0
                                  *segment,
277
0
                                  mTrackMap[aIndex].mInputPort->GetSource(),
278
0
                                  mTrackMap[aIndex].mInputTrackID);
279
0
    }
280
0
    for (TrackBound<MediaStreamTrackListener>& b : mTrackListeners) {
281
0
      if (b.mTrackID == outputTrack->GetID()) {
282
0
        b.mListener->NotifyEnded();
283
0
      }
284
0
    }
285
0
    outputTrack->SetEnded();
286
0
  }
287
288
  void TrackUnionStream::CopyTrackData(StreamTracks::Track* aInputTrack,
289
                     uint32_t aMapIndex, GraphTime aFrom, GraphTime aTo,
290
                     bool* aOutputTrackFinished)
291
0
  {
292
0
    TrackMapEntry* map = &mTrackMap[aMapIndex];
293
0
    TRACE_AUDIO_CALLBACK_COMMENT("Input stream %p track %i -> TrackUnionStream %p track %i",
294
0
                                 map->mInputPort->GetSource(),
295
0
                                 map->mInputTrackID,
296
0
                                 this,
297
0
                                 map->mOutputTrackID);
298
0
    StreamTracks::Track* outputTrack = mTracks.FindTrack(map->mOutputTrackID);
299
0
    MOZ_ASSERT(outputTrack && !outputTrack->IsEnded(), "Can't copy to ended track");
300
0
301
0
    MediaSegment* segment = map->mSegment;
302
0
    MediaStream* source = map->mInputPort->GetSource();
303
0
304
0
    GraphTime next;
305
0
    *aOutputTrackFinished = false;
306
0
    for (GraphTime t = aFrom; t < aTo; t = next) {
307
0
      MediaInputPort::InputInterval interval = map->mInputPort->GetNextInputInterval(t);
308
0
      interval.mEnd = std::min(interval.mEnd, aTo);
309
0
      StreamTime inputEnd = source->GraphTimeToStreamTimeWithBlocking(interval.mEnd);
310
0
      StreamTime inputTrackEndPoint = STREAM_TIME_MAX;
311
0
312
0
      if (aInputTrack->IsEnded() &&
313
0
          aInputTrack->GetEnd() <= inputEnd) {
314
0
        inputTrackEndPoint = aInputTrack->GetEnd();
315
0
        *aOutputTrackFinished = true;
316
0
        break;
317
0
      }
318
0
319
0
      if (interval.mStart >= interval.mEnd) {
320
0
        break;
321
0
      }
322
0
      StreamTime ticks = interval.mEnd - interval.mStart;
323
0
      next = interval.mEnd;
324
0
325
0
      StreamTime outputStart = outputTrack->GetEnd();
326
0
327
0
      if (interval.mInputIsBlocked) {
328
0
        segment->AppendNullData(ticks);
329
0
        STREAM_LOG(LogLevel::Verbose, ("TrackUnionStream %p appending %lld ticks of null data to track %d",
330
0
                   this, (long long)ticks, outputTrack->GetID()));
331
0
      } else if (InMutedCycle()) {
332
0
        segment->AppendNullData(ticks);
333
0
      } else {
334
0
        if (source->IsSuspended()) {
335
0
          segment->AppendNullData(aTo - aFrom);
336
0
        } else {
337
0
          MOZ_ASSERT(outputTrack->GetEnd() == GraphTimeToStreamTimeWithBlocking(interval.mStart),
338
0
                     "Samples missing");
339
0
          StreamTime inputStart = source->GraphTimeToStreamTimeWithBlocking(interval.mStart);
340
0
          segment->AppendSlice(*aInputTrack->GetSegment(),
341
0
                               std::min(inputTrackEndPoint, inputStart),
342
0
                               std::min(inputTrackEndPoint, inputEnd));
343
0
        }
344
0
      }
345
0
      ApplyTrackDisabling(outputTrack->GetID(), segment);
346
0
      for (uint32_t j = 0; j < mListeners.Length(); ++j) {
347
0
        MediaStreamListener* l = mListeners[j];
348
0
        // Separate Audio and Video.
349
0
        if (segment->GetType() == MediaSegment::AUDIO) {
350
0
          l->NotifyQueuedAudioData(Graph(), outputTrack->GetID(),
351
0
                                   outputStart,
352
0
                                   *static_cast<AudioSegment*>(segment),
353
0
                                   map->mInputPort->GetSource(),
354
0
                                   map->mInputTrackID);
355
0
        }
356
0
      }
357
0
      for (TrackBound<MediaStreamTrackListener>& b : mTrackListeners) {
358
0
        if (b.mTrackID != outputTrack->GetID()) {
359
0
          continue;
360
0
        }
361
0
        b.mListener->NotifyQueuedChanges(Graph(), outputStart, *segment);
362
0
      }
363
0
      outputTrack->GetSegment()->AppendFrom(segment);
364
0
    }
365
0
  }
366
367
void
368
0
TrackUnionStream::SetTrackEnabledImpl(TrackID aTrackID, DisabledTrackMode aMode) {
369
0
  bool enabled = aMode == DisabledTrackMode::ENABLED;
370
0
  for (TrackMapEntry& entry : mTrackMap) {
371
0
    if (entry.mOutputTrackID == aTrackID) {
372
0
      STREAM_LOG(LogLevel::Info, ("TrackUnionStream %p track %d was explicitly %s",
373
0
                                   this, aTrackID, enabled ? "enabled" : "disabled"));
374
0
      for (DirectMediaStreamTrackListener* listener : entry.mOwnedDirectListeners) {
375
0
        DisabledTrackMode oldMode = GetDisabledTrackMode(aTrackID);
376
0
        bool oldEnabled = oldMode == DisabledTrackMode::ENABLED;
377
0
        if (!oldEnabled && enabled) {
378
0
          STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p track %d setting "
379
0
                                       "direct listener enabled",
380
0
                                       this, aTrackID));
381
0
          listener->DecreaseDisabled(oldMode);
382
0
        } else if (oldEnabled && !enabled) {
383
0
          STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p track %d setting "
384
0
                                       "direct listener disabled",
385
0
                                       this, aTrackID));
386
0
          listener->IncreaseDisabled(aMode);
387
0
        }
388
0
      }
389
0
    }
390
0
  }
391
0
  MediaStream::SetTrackEnabledImpl(aTrackID, aMode);
392
0
}
393
394
MediaStream*
395
TrackUnionStream::GetInputStreamFor(TrackID aTrackID)
396
0
{
397
0
  for (TrackMapEntry& entry : mTrackMap) {
398
0
    if (entry.mOutputTrackID == aTrackID && entry.mInputPort) {
399
0
      return entry.mInputPort->GetSource();
400
0
    }
401
0
  }
402
0
403
0
  return nullptr;
404
0
}
405
406
TrackID
407
TrackUnionStream::GetInputTrackIDFor(TrackID aTrackID)
408
0
{
409
0
  for (TrackMapEntry& entry : mTrackMap) {
410
0
    if (entry.mOutputTrackID == aTrackID) {
411
0
      return entry.mInputTrackID;
412
0
    }
413
0
  }
414
0
415
0
  return TRACK_NONE;
416
0
}
417
418
void
419
TrackUnionStream::AddDirectTrackListenerImpl(already_AddRefed<DirectMediaStreamTrackListener> aListener,
420
                                             TrackID aTrackID)
421
0
{
422
0
  RefPtr<DirectMediaStreamTrackListener> listener = aListener;
423
0
424
0
  for (TrackMapEntry& entry : mTrackMap) {
425
0
    if (entry.mOutputTrackID == aTrackID) {
426
0
      MediaStream* source = entry.mInputPort->GetSource();
427
0
      STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p adding direct listener "
428
0
                                   "%p for track %d. Forwarding to input "
429
0
                                   "stream %p track %d.",
430
0
                                   this, listener.get(), aTrackID, source,
431
0
                                   entry.mInputTrackID));
432
0
      entry.mOwnedDirectListeners.AppendElement(listener);
433
0
      DisabledTrackMode currentMode = GetDisabledTrackMode(aTrackID);
434
0
      if (currentMode != DisabledTrackMode::ENABLED) {
435
0
        listener->IncreaseDisabled(currentMode);
436
0
      }
437
0
      source->AddDirectTrackListenerImpl(listener.forget(),
438
0
                                         entry.mInputTrackID);
439
0
      return;
440
0
    }
441
0
  }
442
0
443
0
  TrackBound<DirectMediaStreamTrackListener>* bound =
444
0
    mPendingDirectTrackListeners.AppendElement();
445
0
  bound->mListener = listener.forget();
446
0
  bound->mTrackID = aTrackID;
447
0
}
448
449
void
450
TrackUnionStream::RemoveDirectTrackListenerImpl(DirectMediaStreamTrackListener* aListener,
451
                                                TrackID aTrackID)
452
0
{
453
0
  for (TrackMapEntry& entry : mTrackMap) {
454
0
    // OutputTrackID is unique to this stream so we only need to do this once.
455
0
    if (entry.mOutputTrackID != aTrackID) {
456
0
      continue;
457
0
    }
458
0
    for (size_t i = 0; i < entry.mOwnedDirectListeners.Length(); ++i) {
459
0
      if (entry.mOwnedDirectListeners[i] == aListener) {
460
0
        STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p removing direct "
461
0
                                     "listener %p for track %d, forwarding "
462
0
                                     "to input stream %p track %d",
463
0
                                     this, aListener, aTrackID,
464
0
                                     entry.mInputPort->GetSource(),
465
0
                                     entry.mInputTrackID));
466
0
        DisabledTrackMode currentMode = GetDisabledTrackMode(aTrackID);
467
0
        if (currentMode != DisabledTrackMode::ENABLED) {
468
0
          // Reset the listener's state.
469
0
          aListener->DecreaseDisabled(currentMode);
470
0
        }
471
0
        entry.mOwnedDirectListeners.RemoveElementAt(i);
472
0
        break;
473
0
      }
474
0
    }
475
0
    // Forward to the input
476
0
    MediaStream* source = entry.mInputPort->GetSource();
477
0
    source->RemoveDirectTrackListenerImpl(aListener, entry.mInputTrackID);
478
0
    return;
479
0
  }
480
0
481
0
  for (size_t i = 0; i < mPendingDirectTrackListeners.Length(); ++i) {
482
0
    TrackBound<DirectMediaStreamTrackListener>& bound =
483
0
      mPendingDirectTrackListeners[i];
484
0
    if (bound.mListener == aListener && bound.mTrackID == aTrackID) {
485
0
      mPendingDirectTrackListeners.RemoveElementAt(i);
486
0
      return;
487
0
    }
488
0
  }
489
0
}
490
491
void TrackUnionStream::RemoveAllDirectListenersImpl()
492
0
{
493
0
  for (TrackMapEntry& entry : mTrackMap) {
494
0
    nsTArray<RefPtr<DirectMediaStreamTrackListener>>
495
0
      listeners(entry.mOwnedDirectListeners);
496
0
    for (const auto& listener : listeners) {
497
0
      RemoveDirectTrackListenerImpl(listener, entry.mOutputTrackID);
498
0
    }
499
0
    MOZ_DIAGNOSTIC_ASSERT(entry.mOwnedDirectListeners.IsEmpty());
500
0
  }
501
0
502
0
  nsTArray<TrackBound<DirectMediaStreamTrackListener>>
503
0
    boundListeners(mPendingDirectTrackListeners);
504
0
  for (const auto& binding : boundListeners) {
505
0
    RemoveDirectTrackListenerImpl(binding.mListener, binding.mTrackID);
506
0
  }
507
0
  MOZ_DIAGNOSTIC_ASSERT(mPendingDirectTrackListeners.IsEmpty());
508
0
}
509
510
} // namespace mozilla