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/. */
6 #include "MediaStreamTrack.h"
8 #include "DOMMediaStream.h"
9 #include "MediaSegment.h"
10 #include "MediaStreamError.h"
11 #include "MediaTrackGraphImpl.h"
12 #include "MediaTrackListener.h"
13 #include "mozilla/BasePrincipal.h"
14 #include "mozilla/dom/Promise.h"
15 #include "nsContentUtils.h"
16 #include "nsGlobalWindowInner.h"
17 #include "nsIUUIDGenerator.h"
18 #include "nsServiceManagerUtils.h"
19 #include "systemservices/MediaUtils.h"
25 static mozilla::LazyLogModule
gMediaStreamTrackLog("MediaStreamTrack");
26 #define LOG(type, msg) MOZ_LOG(gMediaStreamTrackLog, type, msg)
28 using namespace mozilla::media
;
30 namespace mozilla::dom
{
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
)
38 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrackSource
)
40 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaStreamTrackSource
)
42 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal
)
43 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
45 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaStreamTrackSource
)
46 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal
)
47 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
49 auto MediaStreamTrackSource::ApplyConstraints(
50 const dom::MediaTrackConstraints
& aConstraints
, CallerType aCallerType
)
51 -> RefPtr
<ApplyConstraintsPromise
> {
52 return ApplyConstraintsPromise::CreateAndReject(
53 MakeRefPtr
<MediaMgrError
>(MediaMgrError::Name::OverconstrainedError
, ""),
58 * MTGListener monitors state changes of the media flowing through the
62 * For changes to PrincipalHandle the following applies:
64 * When the main thread principal for a MediaStreamTrack changes, its principal
65 * will be set to the combination of the previous principal and the new one.
67 * As a PrincipalHandle change later happens on the MediaTrackGraph thread, we
68 * will be notified. If the latest principal on main thread matches the
69 * PrincipalHandle we just saw on MTG thread, we will set the track's principal
72 * We know at this point that the old principal has been flushed out and data
73 * under it cannot leak to consumers.
75 * In case of multiple changes to the main thread state, the track's principal
76 * will be a combination of its old principal and all the new ones until the
77 * latest main thread principal matches the PrincipalHandle on the MTG thread.
79 class MediaStreamTrack::MTGListener
: public MediaTrackListener
{
81 explicit MTGListener(MediaStreamTrack
* aTrack
) : mTrack(aTrack
) {}
83 void DoNotifyPrincipalHandleChanged(
84 const PrincipalHandle
& aNewPrincipalHandle
) {
85 MOZ_ASSERT(NS_IsMainThread());
91 mTrack
->NotifyPrincipalHandleChanged(aNewPrincipalHandle
);
94 void NotifyPrincipalHandleChanged(
95 MediaTrackGraph
* aGraph
,
96 const PrincipalHandle
& aNewPrincipalHandle
) override
{
97 aGraph
->DispatchToMainThreadStableState(
98 NewRunnableMethod
<StoreCopyPassByConstLRef
<PrincipalHandle
>>(
99 "dom::MediaStreamTrack::MTGListener::"
100 "DoNotifyPrincipalHandleChanged",
101 this, &MTGListener::DoNotifyPrincipalHandleChanged
,
102 aNewPrincipalHandle
));
105 void NotifyRemoved(MediaTrackGraph
* aGraph
) override
{
106 // `mTrack` is a WeakPtr and must be destroyed on main thread.
107 // We dispatch ourselves to main thread here in case the MediaTrackGraph
108 // is holding the last reference to us.
109 aGraph
->DispatchToMainThreadStableState(
110 NS_NewRunnableFunction("MediaStreamTrack::MTGListener::mTrackReleaser",
111 [self
= RefPtr
<MTGListener
>(this)]() {}));
114 void DoNotifyEnded() {
115 MOZ_ASSERT(NS_IsMainThread());
121 if (!mTrack
->GetParentObject()) {
125 AbstractThread::MainThread()->Dispatch(
126 NewRunnableMethod("MediaStreamTrack::OverrideEnded", mTrack
.get(),
127 &MediaStreamTrack::OverrideEnded
));
130 void NotifyEnded(MediaTrackGraph
* aGraph
) override
{
131 aGraph
->DispatchToMainThreadStableState(
132 NewRunnableMethod("MediaStreamTrack::MTGListener::DoNotifyEnded", this,
133 &MTGListener::DoNotifyEnded
));
138 WeakPtr
<MediaStreamTrack
> mTrack
;
141 class MediaStreamTrack::TrackSink
: public MediaStreamTrackSource::Sink
{
143 explicit TrackSink(MediaStreamTrack
* aTrack
) : mTrack(aTrack
) {}
146 * Keep the track source alive. This track and any clones are controlling the
147 * lifetime of the source by being registered as its sinks.
149 bool KeepsSourceAlive() const override
{ return true; }
151 bool Enabled() const override
{
155 return mTrack
->Enabled();
158 void PrincipalChanged() override
{
160 mTrack
->PrincipalChanged();
164 void MutedChanged(bool aNewState
) override
{
166 mTrack
->MutedChanged(aNewState
);
170 void OverrideEnded() override
{
172 mTrack
->OverrideEnded();
177 WeakPtr
<MediaStreamTrack
> mTrack
;
180 MediaStreamTrack::MediaStreamTrack(nsPIDOMWindowInner
* aWindow
,
181 mozilla::MediaTrack
* aInputTrack
,
182 MediaStreamTrackSource
* aSource
,
183 MediaStreamTrackState aReadyState
,
185 const MediaTrackConstraints
& aConstraints
)
187 mInputTrack(aInputTrack
),
189 mSink(MakeUnique
<TrackSink
>(this)),
190 mPrincipal(aSource
->GetPrincipal()),
191 mReadyState(aReadyState
),
194 mConstraints(aConstraints
) {
196 GetSource().RegisterSink(mSink
.get());
198 // Even if the input track is destroyed we need mTrack so that methods
199 // like AddListener still work. Keeping the number of paths to a minimum
200 // also helps prevent bugs elsewhere. We'll be ended through the
201 // MediaStreamTrackSource soon enough.
202 auto graph
= mInputTrack
->IsDestroyed()
203 ? MediaTrackGraph::GetInstanceIfExists(
204 mWindow
, mInputTrack
->mSampleRate
,
205 MediaTrackGraph::DEFAULT_OUTPUT_DEVICE
)
206 : mInputTrack
->Graph();
207 MOZ_DIAGNOSTIC_ASSERT(graph
,
208 "A destroyed input track is only expected when "
209 "cloning, but since we're live there must be another "
210 "live track that is keeping the graph alive");
212 mTrack
= graph
->CreateForwardedInputTrack(mInputTrack
->mType
);
213 mPort
= mTrack
->AllocateInputPort(mInputTrack
);
214 mMTGListener
= new MTGListener(this);
215 AddListener(mMTGListener
);
219 nsCOMPtr
<nsIUUIDGenerator
> uuidgen
=
220 do_GetService("@mozilla.org/uuid-generator;1", &rv
);
223 memset(&uuid
, 0, sizeof(uuid
));
225 uuidgen
->GenerateUUIDInPlace(&uuid
);
228 char chars
[NSID_LENGTH
];
229 uuid
.ToProvidedString(chars
);
230 mID
= NS_ConvertASCIItoUTF16(chars
);
233 MediaStreamTrack::~MediaStreamTrack() { Destroy(); }
235 void MediaStreamTrack::Destroy() {
236 SetReadyState(MediaStreamTrackState::Ended
);
237 // Remove all listeners -- avoid iterating over the list we're removing from
238 for (const auto& listener
: mTrackListeners
.Clone()) {
239 RemoveListener(listener
);
241 // Do the same as above for direct listeners
242 for (const auto& listener
: mDirectTrackListeners
.Clone()) {
243 RemoveDirectListener(listener
);
247 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack
)
249 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack
,
250 DOMEventTargetHelper
)
252 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow
)
253 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource
)
254 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal
)
255 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPrincipal
)
256 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
257 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
259 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack
,
260 DOMEventTargetHelper
)
261 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow
)
262 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource
)
263 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal
)
264 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPrincipal
)
265 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
267 NS_IMPL_ADDREF_INHERITED(MediaStreamTrack
, DOMEventTargetHelper
)
268 NS_IMPL_RELEASE_INHERITED(MediaStreamTrack
, DOMEventTargetHelper
)
269 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrack
)
270 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
272 JSObject
* MediaStreamTrack::WrapObject(JSContext
* aCx
,
273 JS::Handle
<JSObject
*> aGivenProto
) {
274 return MediaStreamTrack_Binding::Wrap(aCx
, this, aGivenProto
);
277 void MediaStreamTrack::GetId(nsAString
& aID
) const { aID
= mID
; }
279 void MediaStreamTrack::SetEnabled(bool aEnabled
) {
281 ("MediaStreamTrack %p %s", this, aEnabled
? "Enabled" : "Disabled"));
283 if (mEnabled
== aEnabled
) {
293 mTrack
->SetDisabledTrackMode(mEnabled
? DisabledTrackMode::ENABLED
294 : DisabledTrackMode::SILENCE_BLACK
);
295 NotifyEnabledChanged();
298 void MediaStreamTrack::Stop() {
299 LOG(LogLevel::Info
, ("MediaStreamTrack %p Stop()", this));
302 LOG(LogLevel::Warning
, ("MediaStreamTrack %p Already ended", this));
306 SetReadyState(MediaStreamTrackState::Ended
);
311 void MediaStreamTrack::GetConstraints(dom::MediaTrackConstraints
& aResult
) {
312 aResult
= mConstraints
;
315 void MediaStreamTrack::GetSettings(dom::MediaTrackSettings
& aResult
,
316 CallerType aCallerType
) {
317 GetSource().GetSettings(aResult
);
319 // Spoof values when privacy.resistFingerprinting is true.
320 nsIGlobalObject
* global
= mWindow
? mWindow
->AsGlobal() : nullptr;
321 if (!nsContentUtils::ShouldResistFingerprinting(
322 aCallerType
, global
, RFPTarget::StreamVideoFacingMode
)) {
325 if (aResult
.mFacingMode
.WasPassed()) {
326 aResult
.mFacingMode
.Value().AssignASCII(
327 VideoFacingModeEnumValues::GetString(VideoFacingModeEnum::User
));
331 already_AddRefed
<Promise
> MediaStreamTrack::ApplyConstraints(
332 const MediaTrackConstraints
& aConstraints
, CallerType aCallerType
,
334 if (MOZ_LOG_TEST(gMediaStreamTrackLog
, LogLevel::Info
)) {
336 aConstraints
.ToJSON(str
);
338 LOG(LogLevel::Info
, ("MediaStreamTrack %p ApplyConstraints() with "
340 this, NS_ConvertUTF16toUTF8(str
).get()));
343 nsIGlobalObject
* go
= mWindow
? mWindow
->AsGlobal() : nullptr;
345 RefPtr
<Promise
> promise
= Promise::Create(go
, aRv
);
350 // Forward constraints to the source.
352 // After GetSource().ApplyConstraints succeeds (after it's been to
353 // media-thread and back), and no sooner, do we set mConstraints to the newly
356 // Keep a reference to this, to make sure it's still here when we get back.
357 RefPtr
<MediaStreamTrack
> self(this);
359 .ApplyConstraints(aConstraints
, aCallerType
)
361 GetCurrentSerialEventTarget(), __func__
,
362 [this, self
, promise
, aConstraints
](bool aDummy
) {
363 if (!mWindow
|| !mWindow
->IsCurrentInnerWindow()) {
364 return; // Leave Promise pending after navigation by design.
366 mConstraints
= aConstraints
;
367 promise
->MaybeResolve(false);
369 [this, self
, promise
](const RefPtr
<MediaMgrError
>& aError
) {
370 if (!mWindow
|| !mWindow
->IsCurrentInnerWindow()) {
371 return; // Leave Promise pending after navigation by design.
373 promise
->MaybeReject(
374 MakeRefPtr
<MediaStreamError
>(mWindow
, *aError
));
376 return promise
.forget();
379 ProcessedMediaTrack
* MediaStreamTrack::GetTrack() const {
380 MOZ_DIAGNOSTIC_ASSERT(!Ended());
384 MediaTrackGraph
* MediaStreamTrack::Graph() const {
385 MOZ_DIAGNOSTIC_ASSERT(!Ended());
386 return mTrack
->Graph();
389 MediaTrackGraphImpl
* MediaStreamTrack::GraphImpl() const {
390 MOZ_DIAGNOSTIC_ASSERT(!Ended());
391 return mTrack
->GraphImpl();
394 void MediaStreamTrack::SetPrincipal(nsIPrincipal
* aPrincipal
) {
395 if (aPrincipal
== mPrincipal
) {
398 mPrincipal
= aPrincipal
;
401 ("MediaStreamTrack %p principal changed to %p. Now: "
402 "null=%d, codebase=%d, expanded=%d, system=%d",
403 this, mPrincipal
.get(), mPrincipal
->GetIsNullPrincipal(),
404 mPrincipal
->GetIsContentPrincipal(),
405 mPrincipal
->GetIsExpandedPrincipal(), mPrincipal
->IsSystemPrincipal()));
406 for (PrincipalChangeObserver
<MediaStreamTrack
>* observer
:
407 mPrincipalChangeObservers
) {
408 observer
->PrincipalChanged(this);
412 void MediaStreamTrack::PrincipalChanged() {
413 mPendingPrincipal
= GetSource().GetPrincipal();
414 nsCOMPtr
<nsIPrincipal
> newPrincipal
= mPrincipal
;
415 LOG(LogLevel::Info
, ("MediaStreamTrack %p Principal changed on main thread "
416 "to %p (pending). Combining with existing principal %p.",
417 this, mPendingPrincipal
.get(), mPrincipal
.get()));
418 if (nsContentUtils::CombineResourcePrincipals(&newPrincipal
,
419 mPendingPrincipal
)) {
420 SetPrincipal(newPrincipal
);
424 void MediaStreamTrack::NotifyPrincipalHandleChanged(
425 const PrincipalHandle
& aNewPrincipalHandle
) {
426 PrincipalHandle
handle(aNewPrincipalHandle
);
427 LOG(LogLevel::Info
, ("MediaStreamTrack %p principalHandle changed on "
428 "MediaTrackGraph thread to %p. Current principal: %p, "
430 this, GetPrincipalFromHandle(handle
), mPrincipal
.get(),
431 mPendingPrincipal
.get()));
432 if (PrincipalHandleMatches(handle
, mPendingPrincipal
)) {
433 SetPrincipal(mPendingPrincipal
);
434 mPendingPrincipal
= nullptr;
438 void MediaStreamTrack::MutedChanged(bool aNewState
) {
439 MOZ_ASSERT(NS_IsMainThread());
442 * 4.3.1 Life-cycle and Media flow - Media flow
443 * To set a track's muted state to newState, the User Agent MUST run the
445 * 1. Let track be the MediaStreamTrack in question.
446 * 2. Set track's muted attribute to newState.
447 * 3. If newState is true let eventName be mute, otherwise unmute.
448 * 4. Fire a simple event named eventName on track.
451 if (mMuted
== aNewState
) {
456 ("MediaStreamTrack %p became %s", this, aNewState
? "muted" : "unmuted"));
464 nsString eventName
= aNewState
? u
"mute"_ns
: u
"unmute"_ns
;
465 DispatchTrustedEvent(eventName
);
468 void MediaStreamTrack::NotifyEnded() {
469 MOZ_ASSERT(mReadyState
== MediaStreamTrackState::Ended
);
471 for (const auto& consumer
: mConsumers
.Clone()) {
473 consumer
->NotifyEnded(this);
475 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
476 mConsumers
.RemoveElement(consumer
);
481 void MediaStreamTrack::NotifyEnabledChanged() {
482 GetSource().SinkEnabledStateChanged();
484 for (const auto& consumer
: mConsumers
.Clone()) {
486 consumer
->NotifyEnabledChanged(this, Enabled());
488 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
489 mConsumers
.RemoveElement(consumer
);
494 bool MediaStreamTrack::AddPrincipalChangeObserver(
495 PrincipalChangeObserver
<MediaStreamTrack
>* aObserver
) {
496 // XXX(Bug 1631371) Check if this should use a fallible operation as it
497 // pretended earlier.
498 mPrincipalChangeObservers
.AppendElement(aObserver
);
502 bool MediaStreamTrack::RemovePrincipalChangeObserver(
503 PrincipalChangeObserver
<MediaStreamTrack
>* aObserver
) {
504 return mPrincipalChangeObservers
.RemoveElement(aObserver
);
507 void MediaStreamTrack::AddConsumer(MediaStreamTrackConsumer
* aConsumer
) {
508 MOZ_ASSERT(!mConsumers
.Contains(aConsumer
));
509 mConsumers
.AppendElement(aConsumer
);
511 // Remove destroyed consumers for cleanliness
512 while (mConsumers
.RemoveElement(nullptr)) {
513 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
517 void MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer
* aConsumer
) {
518 mConsumers
.RemoveElement(aConsumer
);
520 // Remove destroyed consumers for cleanliness
521 while (mConsumers
.RemoveElement(nullptr)) {
522 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
526 already_AddRefed
<MediaStreamTrack
> MediaStreamTrack::Clone() {
527 RefPtr
<MediaStreamTrack
> newTrack
= CloneInternal();
528 newTrack
->SetEnabled(Enabled());
529 newTrack
->SetMuted(Muted());
530 return newTrack
.forget();
533 void MediaStreamTrack::SetReadyState(MediaStreamTrackState aState
) {
534 MOZ_ASSERT(!(mReadyState
== MediaStreamTrackState::Ended
&&
535 aState
== MediaStreamTrackState::Live
),
536 "We don't support overriding the ready state from ended to live");
542 if (mReadyState
== MediaStreamTrackState::Live
&&
543 aState
== MediaStreamTrackState::Ended
) {
545 mSource
->UnregisterSink(mSink
.get());
548 RemoveListener(mMTGListener
);
558 mMTGListener
= nullptr;
561 mReadyState
= aState
;
564 void MediaStreamTrack::OverrideEnded() {
565 MOZ_ASSERT(NS_IsMainThread());
571 LOG(LogLevel::Info
, ("MediaStreamTrack %p ended", this));
573 SetReadyState(MediaStreamTrackState::Ended
);
577 DispatchTrustedEvent(u
"ended"_ns
);
580 void MediaStreamTrack::AddListener(MediaTrackListener
* aListener
) {
582 ("MediaStreamTrack %p adding listener %p", this, aListener
));
583 mTrackListeners
.AppendElement(aListener
);
588 mTrack
->AddListener(aListener
);
591 void MediaStreamTrack::RemoveListener(MediaTrackListener
* aListener
) {
593 ("MediaStreamTrack %p removing listener %p", this, aListener
));
594 mTrackListeners
.RemoveElement(aListener
);
599 mTrack
->RemoveListener(aListener
);
602 void MediaStreamTrack::AddDirectListener(DirectMediaTrackListener
* aListener
) {
603 LOG(LogLevel::Debug
, ("MediaStreamTrack %p (%s) adding direct listener %p to "
605 this, AsAudioStreamTrack() ? "audio" : "video",
606 aListener
, mTrack
.get()));
607 mDirectTrackListeners
.AppendElement(aListener
);
612 mTrack
->AddDirectListener(aListener
);
615 void MediaStreamTrack::RemoveDirectListener(
616 DirectMediaTrackListener
* aListener
) {
618 ("MediaStreamTrack %p removing direct listener %p from track %p", this,
619 aListener
, mTrack
.get()));
620 mDirectTrackListeners
.RemoveElement(aListener
);
625 mTrack
->RemoveDirectListener(aListener
);
628 already_AddRefed
<MediaInputPort
> MediaStreamTrack::ForwardTrackContentsTo(
629 ProcessedMediaTrack
* aTrack
) {
630 MOZ_ASSERT(NS_IsMainThread());
631 MOZ_RELEASE_ASSERT(aTrack
);
632 return aTrack
->AllocateInputPort(mTrack
);
635 } // namespace mozilla::dom