Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
4
* You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
#include "MediaStreamTrack.h"
7
8
#include "DOMMediaStream.h"
9
#include "MediaStreamError.h"
10
#include "MediaStreamGraphImpl.h"
11
#include "MediaStreamListener.h"
12
#include "mozilla/BasePrincipal.h"
13
#include "mozilla/dom/Promise.h"
14
#include "nsContentUtils.h"
15
#include "nsGlobalWindowInner.h"
16
#include "nsIUUIDGenerator.h"
17
#include "nsServiceManagerUtils.h"
18
#include "systemservices/MediaUtils.h"
19
20
#ifdef LOG
21
# undef LOG
22
#endif
23
24
static mozilla::LazyLogModule gMediaStreamTrackLog("MediaStreamTrack");
25
#define LOG(type, msg) MOZ_LOG(gMediaStreamTrackLog, type, msg)
26
27
using namespace mozilla::media;
28
29
namespace mozilla {
30
namespace dom {
31
32
NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamTrackSource)
33
NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamTrackSource)
34
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackSource)
35
NS_INTERFACE_MAP_ENTRY(nsISupports)
36
NS_INTERFACE_MAP_END
37
38
NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrackSource)
39
40
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaStreamTrackSource)
41
tmp->Destroy();
42
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
43
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
44
45
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaStreamTrackSource)
46
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
47
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
48
49
auto MediaStreamTrackSource::ApplyConstraints(
50
const dom::MediaTrackConstraints& aConstraints, CallerType aCallerType)
51
-> RefPtr<ApplyConstraintsPromise> {
52
return ApplyConstraintsPromise::CreateAndReject(
53
MakeRefPtr<MediaMgrError>(MediaMgrError::Name::OverconstrainedError,
54
NS_LITERAL_STRING("")),
55
__func__);
56
}
57
58
/**
59
* MSGListener monitors state changes of the media flowing through the
60
* MediaStreamGraph.
61
*
62
*
63
* For changes to PrincipalHandle the following applies:
64
*
65
* When the main thread principal for a MediaStreamTrack changes, its principal
66
* will be set to the combination of the previous principal and the new one.
67
*
68
* As a PrincipalHandle change later happens on the MediaStreamGraph thread, we
69
* will be notified. If the latest principal on main thread matches the
70
* PrincipalHandle we just saw on MSG thread, we will set the track's principal
71
* to the new one.
72
*
73
* We know at this point that the old principal has been flushed out and data
74
* under it cannot leak to consumers.
75
*
76
* In case of multiple changes to the main thread state, the track's principal
77
* will be a combination of its old principal and all the new ones until the
78
* latest main thread principal matches the PrincipalHandle on the MSG thread.
79
*/
80
class MediaStreamTrack::MSGListener : public MediaStreamTrackListener {
81
public:
82
explicit MSGListener(MediaStreamTrack* aTrack) : mTrack(aTrack) {}
83
84
void DoNotifyPrincipalHandleChanged(
85
const PrincipalHandle& aNewPrincipalHandle) {
86
MOZ_ASSERT(NS_IsMainThread());
87
88
if (!mTrack) {
89
return;
90
}
91
92
mTrack->NotifyPrincipalHandleChanged(aNewPrincipalHandle);
93
}
94
95
void NotifyPrincipalHandleChanged(
96
MediaStreamGraph* aGraph,
97
const PrincipalHandle& aNewPrincipalHandle) override {
98
aGraph->DispatchToMainThreadStableState(
99
NewRunnableMethod<StoreCopyPassByConstLRef<PrincipalHandle>>(
100
"dom::MediaStreamTrack::MSGListener::"
101
"DoNotifyPrincipalHandleChanged",
102
this, &MSGListener::DoNotifyPrincipalHandleChanged,
103
aNewPrincipalHandle));
104
}
105
106
void NotifyRemoved(MediaStreamGraph* aGraph) override {
107
// `mTrack` is a WeakPtr and must be destroyed on main thread.
108
// We dispatch ourselves to main thread here in case the MediaStreamGraph
109
// is holding the last reference to us.
110
aGraph->DispatchToMainThreadStableState(
111
NS_NewRunnableFunction("MediaStreamTrack::MSGListener::mTrackReleaser",
112
[self = RefPtr<MSGListener>(this)]() {}));
113
}
114
115
void DoNotifyEnded() {
116
MOZ_ASSERT(NS_IsMainThread());
117
118
if (!mTrack) {
119
return;
120
}
121
122
if (!mTrack->GetParentObject()) {
123
return;
124
}
125
126
AbstractThread* mainThread =
127
nsGlobalWindowInner::Cast(mTrack->GetParentObject())
128
->AbstractMainThreadFor(TaskCategory::Other);
129
mainThread->Dispatch(NewRunnableMethod("MediaStreamTrack::OverrideEnded",
130
mTrack.get(),
131
&MediaStreamTrack::OverrideEnded));
132
}
133
134
void NotifyEnded(MediaStreamGraph* aGraph) override {
135
aGraph->DispatchToMainThreadStableState(
136
NewRunnableMethod("MediaStreamTrack::MSGListener::DoNotifyEnded", this,
137
&MSGListener::DoNotifyEnded));
138
}
139
140
protected:
141
// Main thread only.
142
WeakPtr<MediaStreamTrack> mTrack;
143
};
144
145
class MediaStreamTrack::TrackSink : public MediaStreamTrackSource::Sink {
146
public:
147
explicit TrackSink(MediaStreamTrack* aTrack) : mTrack(aTrack) {}
148
149
/**
150
* Keep the track source alive. This track and any clones are controlling the
151
* lifetime of the source by being registered as its sinks.
152
*/
153
bool KeepsSourceAlive() const override { return true; }
154
155
bool Enabled() const override {
156
if (!mTrack) {
157
return false;
158
}
159
return mTrack->Enabled();
160
}
161
162
void PrincipalChanged() override {
163
if (mTrack) {
164
mTrack->PrincipalChanged();
165
}
166
}
167
168
void MutedChanged(bool aNewState) override {
169
if (mTrack) {
170
mTrack->MutedChanged(aNewState);
171
}
172
}
173
174
void OverrideEnded() override {
175
if (mTrack) {
176
mTrack->OverrideEnded();
177
}
178
}
179
180
private:
181
WeakPtr<MediaStreamTrack> mTrack;
182
};
183
184
MediaStreamTrack::MediaStreamTrack(nsPIDOMWindowInner* aWindow,
185
MediaStream* aInputStream, TrackID aTrackID,
186
MediaStreamTrackSource* aSource,
187
MediaStreamTrackState aReadyState,
188
const MediaTrackConstraints& aConstraints)
189
: mWindow(aWindow),
190
mInputStream(aInputStream),
191
mTrackID(aTrackID),
192
mSource(aSource),
193
mSink(MakeUnique<TrackSink>(this)),
194
mPrincipal(aSource->GetPrincipal()),
195
mReadyState(aReadyState),
196
mEnabled(true),
197
mMuted(false),
198
mConstraints(aConstraints) {
199
if (!Ended()) {
200
GetSource().RegisterSink(mSink.get());
201
202
// Even if the input stream is destroyed we need mStream so that methods
203
// like AddListener still work. Keeping the number of paths to a minimum
204
// also helps prevent bugs elsewhere. We'll be ended through the
205
// MediaStreamTrackSource soon enough.
206
auto graph = mInputStream->IsDestroyed()
207
? MediaStreamGraph::GetInstanceIfExists(
208
mWindow, mInputStream->GraphRate())
209
: mInputStream->Graph();
210
MOZ_DIAGNOSTIC_ASSERT(graph,
211
"A destroyed input stream is only expected when "
212
"cloning, but since we're live there must be another "
213
"live track that is keeping the graph alive");
214
215
mStream = graph->CreateTrackUnionStream();
216
mPort = mStream->AllocateInputPort(mInputStream);
217
mMSGListener = new MSGListener(this);
218
AddListener(mMSGListener);
219
}
220
221
nsresult rv;
222
nsCOMPtr<nsIUUIDGenerator> uuidgen =
223
do_GetService("@mozilla.org/uuid-generator;1", &rv);
224
225
nsID uuid;
226
memset(&uuid, 0, sizeof(uuid));
227
if (uuidgen) {
228
uuidgen->GenerateUUIDInPlace(&uuid);
229
}
230
231
char chars[NSID_LENGTH];
232
uuid.ToProvidedString(chars);
233
mID = NS_ConvertASCIItoUTF16(chars);
234
}
235
236
MediaStreamTrack::~MediaStreamTrack() { Destroy(); }
237
238
void MediaStreamTrack::Destroy() {
239
SetReadyState(MediaStreamTrackState::Ended);
240
// Remove all listeners -- avoid iterating over the list we're removing from
241
const nsTArray<RefPtr<MediaStreamTrackListener>> trackListeners(
242
mTrackListeners);
243
for (auto listener : trackListeners) {
244
RemoveListener(listener);
245
}
246
// Do the same as above for direct listeners
247
const nsTArray<RefPtr<DirectMediaStreamTrackListener>> directTrackListeners(
248
mDirectTrackListeners);
249
for (auto listener : directTrackListeners) {
250
RemoveDirectListener(listener);
251
}
252
}
253
254
NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack)
255
256
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack,
257
DOMEventTargetHelper)
258
tmp->Destroy();
259
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
260
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource)
261
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
262
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPrincipal)
263
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
264
265
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack,
266
DOMEventTargetHelper)
267
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
268
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource)
269
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
270
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPrincipal)
271
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
272
273
NS_IMPL_ADDREF_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
274
NS_IMPL_RELEASE_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
275
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrack)
276
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
277
278
JSObject* MediaStreamTrack::WrapObject(JSContext* aCx,
279
JS::Handle<JSObject*> aGivenProto) {
280
return MediaStreamTrack_Binding::Wrap(aCx, this, aGivenProto);
281
}
282
283
void MediaStreamTrack::GetId(nsAString& aID) const { aID = mID; }
284
285
void MediaStreamTrack::SetEnabled(bool aEnabled) {
286
LOG(LogLevel::Info,
287
("MediaStreamTrack %p %s", this, aEnabled ? "Enabled" : "Disabled"));
288
289
if (mEnabled == aEnabled) {
290
return;
291
}
292
293
mEnabled = aEnabled;
294
295
if (Ended()) {
296
return;
297
}
298
299
mStream->SetTrackEnabled(mTrackID, mEnabled
300
? DisabledTrackMode::ENABLED
301
: DisabledTrackMode::SILENCE_BLACK);
302
GetSource().SinkEnabledStateChanged();
303
}
304
305
void MediaStreamTrack::Stop() {
306
LOG(LogLevel::Info, ("MediaStreamTrack %p Stop()", this));
307
308
if (Ended()) {
309
LOG(LogLevel::Warning, ("MediaStreamTrack %p Already ended", this));
310
return;
311
}
312
313
SetReadyState(MediaStreamTrackState::Ended);
314
315
NotifyEnded();
316
}
317
318
void MediaStreamTrack::GetConstraints(dom::MediaTrackConstraints& aResult) {
319
aResult = mConstraints;
320
}
321
322
void MediaStreamTrack::GetSettings(dom::MediaTrackSettings& aResult,
323
CallerType aCallerType) {
324
GetSource().GetSettings(aResult);
325
326
// Spoof values when privacy.resistFingerprinting is true.
327
if (!nsContentUtils::ResistFingerprinting(aCallerType)) {
328
return;
329
}
330
if (aResult.mFacingMode.WasPassed()) {
331
aResult.mFacingMode.Value().Assign(NS_ConvertASCIItoUTF16(
332
VideoFacingModeEnumValues::strings[uint8_t(VideoFacingModeEnum::User)]
333
.value));
334
}
335
}
336
337
already_AddRefed<Promise> MediaStreamTrack::ApplyConstraints(
338
const MediaTrackConstraints& aConstraints, CallerType aCallerType,
339
ErrorResult& aRv) {
340
if (MOZ_LOG_TEST(gMediaStreamTrackLog, LogLevel::Info)) {
341
nsString str;
342
aConstraints.ToJSON(str);
343
344
LOG(LogLevel::Info, ("MediaStreamTrack %p ApplyConstraints() with "
345
"constraints %s",
346
this, NS_ConvertUTF16toUTF8(str).get()));
347
}
348
349
nsIGlobalObject* go = mWindow ? mWindow->AsGlobal() : nullptr;
350
351
RefPtr<Promise> promise = Promise::Create(go, aRv);
352
if (aRv.Failed()) {
353
return nullptr;
354
}
355
356
// Forward constraints to the source.
357
//
358
// After GetSource().ApplyConstraints succeeds (after it's been to
359
// media-thread and back), and no sooner, do we set mConstraints to the newly
360
// applied values.
361
362
// Keep a reference to this, to make sure it's still here when we get back.
363
RefPtr<MediaStreamTrack> self(this);
364
GetSource()
365
.ApplyConstraints(aConstraints, aCallerType)
366
->Then(
367
GetCurrentThreadSerialEventTarget(), __func__,
368
[this, self, promise, aConstraints](bool aDummy) {
369
if (!mWindow || !mWindow->IsCurrentInnerWindow()) {
370
return; // Leave Promise pending after navigation by design.
371
}
372
mConstraints = aConstraints;
373
promise->MaybeResolve(false);
374
},
375
[this, self, promise](const RefPtr<MediaMgrError>& aError) {
376
if (!mWindow || !mWindow->IsCurrentInnerWindow()) {
377
return; // Leave Promise pending after navigation by design.
378
}
379
promise->MaybeReject(
380
MakeRefPtr<MediaStreamError>(mWindow, *aError));
381
});
382
return promise.forget();
383
}
384
385
ProcessedMediaStream* MediaStreamTrack::GetStream() const {
386
MOZ_DIAGNOSTIC_ASSERT(!Ended());
387
return mStream;
388
}
389
390
MediaStreamGraph* MediaStreamTrack::Graph() const {
391
MOZ_DIAGNOSTIC_ASSERT(!Ended());
392
return mStream->Graph();
393
}
394
395
MediaStreamGraphImpl* MediaStreamTrack::GraphImpl() const {
396
MOZ_DIAGNOSTIC_ASSERT(!Ended());
397
return mStream->GraphImpl();
398
}
399
400
void MediaStreamTrack::SetPrincipal(nsIPrincipal* aPrincipal) {
401
if (aPrincipal == mPrincipal) {
402
return;
403
}
404
mPrincipal = aPrincipal;
405
406
LOG(LogLevel::Info,
407
("MediaStreamTrack %p principal changed to %p. Now: "
408
"null=%d, codebase=%d, expanded=%d, system=%d",
409
this, mPrincipal.get(), mPrincipal->GetIsNullPrincipal(),
410
mPrincipal->GetIsContentPrincipal(),
411
mPrincipal->GetIsExpandedPrincipal(), mPrincipal->IsSystemPrincipal()));
412
for (PrincipalChangeObserver<MediaStreamTrack>* observer :
413
mPrincipalChangeObservers) {
414
observer->PrincipalChanged(this);
415
}
416
}
417
418
void MediaStreamTrack::PrincipalChanged() {
419
mPendingPrincipal = GetSource().GetPrincipal();
420
nsCOMPtr<nsIPrincipal> newPrincipal = mPrincipal;
421
LOG(LogLevel::Info, ("MediaStreamTrack %p Principal changed on main thread "
422
"to %p (pending). Combining with existing principal %p.",
423
this, mPendingPrincipal.get(), mPrincipal.get()));
424
if (nsContentUtils::CombineResourcePrincipals(&newPrincipal,
425
mPendingPrincipal)) {
426
SetPrincipal(newPrincipal);
427
}
428
}
429
430
void MediaStreamTrack::NotifyPrincipalHandleChanged(
431
const PrincipalHandle& aNewPrincipalHandle) {
432
PrincipalHandle handle(aNewPrincipalHandle);
433
LOG(LogLevel::Info, ("MediaStreamTrack %p principalHandle changed on "
434
"MediaStreamGraph thread to %p. Current principal: %p, "
435
"pending: %p",
436
this, GetPrincipalFromHandle(handle), mPrincipal.get(),
437
mPendingPrincipal.get()));
438
if (PrincipalHandleMatches(handle, mPendingPrincipal)) {
439
SetPrincipal(mPendingPrincipal);
440
mPendingPrincipal = nullptr;
441
}
442
}
443
444
void MediaStreamTrack::MutedChanged(bool aNewState) {
445
MOZ_ASSERT(NS_IsMainThread());
446
447
/**
448
* 4.3.1 Life-cycle and Media flow - Media flow
449
* To set a track's muted state to newState, the User Agent MUST run the
450
* following steps:
451
* 1. Let track be the MediaStreamTrack in question.
452
* 2. Set track's muted attribute to newState.
453
* 3. If newState is true let eventName be mute, otherwise unmute.
454
* 4. Fire a simple event named eventName on track.
455
*/
456
457
if (mMuted == aNewState) {
458
return;
459
}
460
461
LOG(LogLevel::Info,
462
("MediaStreamTrack %p became %s", this, aNewState ? "muted" : "unmuted"));
463
464
mMuted = aNewState;
465
nsString eventName =
466
aNewState ? NS_LITERAL_STRING("mute") : NS_LITERAL_STRING("unmute");
467
DispatchTrustedEvent(eventName);
468
}
469
470
void MediaStreamTrack::NotifyEnded() {
471
MOZ_ASSERT(mReadyState == MediaStreamTrackState::Ended);
472
473
auto consumers(mConsumers);
474
for (const auto& consumer : consumers) {
475
if (consumer) {
476
consumer->NotifyEnded(this);
477
} else {
478
MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
479
mConsumers.RemoveElement(consumer);
480
}
481
}
482
}
483
484
bool MediaStreamTrack::AddPrincipalChangeObserver(
485
PrincipalChangeObserver<MediaStreamTrack>* aObserver) {
486
return mPrincipalChangeObservers.AppendElement(aObserver) != nullptr;
487
}
488
489
bool MediaStreamTrack::RemovePrincipalChangeObserver(
490
PrincipalChangeObserver<MediaStreamTrack>* aObserver) {
491
return mPrincipalChangeObservers.RemoveElement(aObserver);
492
}
493
494
void MediaStreamTrack::AddConsumer(MediaStreamTrackConsumer* aConsumer) {
495
MOZ_ASSERT(!mConsumers.Contains(aConsumer));
496
mConsumers.AppendElement(aConsumer);
497
498
// Remove destroyed consumers for cleanliness
499
while (mConsumers.RemoveElement(nullptr)) {
500
MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
501
}
502
}
503
504
void MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer* aConsumer) {
505
mConsumers.RemoveElement(aConsumer);
506
507
// Remove destroyed consumers for cleanliness
508
while (mConsumers.RemoveElement(nullptr)) {
509
MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
510
}
511
}
512
513
already_AddRefed<MediaStreamTrack> MediaStreamTrack::Clone() {
514
RefPtr<MediaStreamTrack> newTrack = CloneInternal();
515
newTrack->SetEnabled(Enabled());
516
newTrack->SetMuted(Muted());
517
return newTrack.forget();
518
}
519
520
void MediaStreamTrack::SetReadyState(MediaStreamTrackState aState) {
521
MOZ_ASSERT(!(mReadyState == MediaStreamTrackState::Ended &&
522
aState == MediaStreamTrackState::Live),
523
"We don't support overriding the ready state from ended to live");
524
525
if (Ended()) {
526
return;
527
}
528
529
if (mReadyState == MediaStreamTrackState::Live &&
530
aState == MediaStreamTrackState::Ended) {
531
if (mSource) {
532
mSource->UnregisterSink(mSink.get());
533
}
534
if (mMSGListener) {
535
RemoveListener(mMSGListener);
536
}
537
if (mPort) {
538
mPort->Destroy();
539
}
540
if (mStream) {
541
mStream->Destroy();
542
}
543
mPort = nullptr;
544
mStream = nullptr;
545
mMSGListener = nullptr;
546
}
547
548
mReadyState = aState;
549
}
550
551
void MediaStreamTrack::OverrideEnded() {
552
MOZ_ASSERT(NS_IsMainThread());
553
554
if (Ended()) {
555
return;
556
}
557
558
LOG(LogLevel::Info, ("MediaStreamTrack %p ended", this));
559
560
SetReadyState(MediaStreamTrackState::Ended);
561
562
NotifyEnded();
563
564
DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
565
}
566
567
void MediaStreamTrack::AddListener(MediaStreamTrackListener* aListener) {
568
LOG(LogLevel::Debug,
569
("MediaStreamTrack %p adding listener %p", this, aListener));
570
mTrackListeners.AppendElement(aListener);
571
572
if (Ended()) {
573
return;
574
}
575
mStream->AddTrackListener(aListener, mTrackID);
576
}
577
578
void MediaStreamTrack::RemoveListener(MediaStreamTrackListener* aListener) {
579
LOG(LogLevel::Debug,
580
("MediaStreamTrack %p removing listener %p", this, aListener));
581
mTrackListeners.RemoveElement(aListener);
582
583
if (Ended()) {
584
return;
585
}
586
mStream->RemoveTrackListener(aListener, mTrackID);
587
}
588
589
void MediaStreamTrack::AddDirectListener(
590
DirectMediaStreamTrackListener* aListener) {
591
LOG(LogLevel::Debug, ("MediaStreamTrack %p (%s) adding direct listener %p to "
592
"stream %p, track %d",
593
this, AsAudioStreamTrack() ? "audio" : "video",
594
aListener, mStream.get(), mTrackID));
595
mDirectTrackListeners.AppendElement(aListener);
596
597
if (Ended()) {
598
return;
599
}
600
mStream->AddDirectTrackListener(aListener, mTrackID);
601
}
602
603
void MediaStreamTrack::RemoveDirectListener(
604
DirectMediaStreamTrackListener* aListener) {
605
LOG(LogLevel::Debug,
606
("MediaStreamTrack %p removing direct listener %p from stream %p", this,
607
aListener, mStream.get()));
608
mDirectTrackListeners.RemoveElement(aListener);
609
610
if (Ended()) {
611
return;
612
}
613
mStream->RemoveDirectTrackListener(aListener, mTrackID);
614
}
615
616
already_AddRefed<MediaInputPort> MediaStreamTrack::ForwardTrackContentsTo(
617
ProcessedMediaStream* aStream) {
618
MOZ_ASSERT(NS_IsMainThread());
619
MOZ_RELEASE_ASSERT(aStream);
620
return aStream->AllocateInputPort(mStream, mTrackID, mTrackID);
621
}
622
623
} // namespace dom
624
} // namespace mozilla