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
,
51 CallerType aCallerType
) -> 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::GetCapabilities(MediaTrackCapabilities
& aResult
,
312 CallerType aCallerType
) {
313 GetSource().GetCapabilities(aResult
);
316 void MediaStreamTrack::GetConstraints(dom::MediaTrackConstraints
& aResult
) {
317 aResult
= mConstraints
;
320 void MediaStreamTrack::GetSettings(dom::MediaTrackSettings
& aResult
,
321 CallerType aCallerType
) {
322 GetSource().GetSettings(aResult
);
324 // Spoof values when privacy.resistFingerprinting is true.
325 nsIGlobalObject
* global
= mWindow
? mWindow
->AsGlobal() : nullptr;
326 if (!nsContentUtils::ShouldResistFingerprinting(
327 aCallerType
, global
, RFPTarget::StreamVideoFacingMode
)) {
330 if (aResult
.mFacingMode
.WasPassed()) {
331 aResult
.mFacingMode
.Value().AssignASCII(
332 GetEnumString(VideoFacingModeEnum::User
));
336 already_AddRefed
<Promise
> MediaStreamTrack::ApplyConstraints(
337 const MediaTrackConstraints
& aConstraints
, CallerType aCallerType
,
339 if (MOZ_LOG_TEST(gMediaStreamTrackLog
, LogLevel::Info
)) {
341 aConstraints
.ToJSON(str
);
343 LOG(LogLevel::Info
, ("MediaStreamTrack %p ApplyConstraints() with "
345 this, NS_ConvertUTF16toUTF8(str
).get()));
348 nsIGlobalObject
* go
= mWindow
? mWindow
->AsGlobal() : nullptr;
350 RefPtr
<Promise
> promise
= Promise::Create(go
, aRv
);
355 // Forward constraints to the source.
357 // After GetSource().ApplyConstraints succeeds (after it's been to
358 // media-thread and back), and no sooner, do we set mConstraints to the newly
361 // Keep a reference to this, to make sure it's still here when we get back.
362 RefPtr
<MediaStreamTrack
> self(this);
364 .ApplyConstraints(aConstraints
, aCallerType
)
366 GetCurrentSerialEventTarget(), __func__
,
367 [this, self
, promise
, aConstraints
](bool aDummy
) {
368 if (!mWindow
|| !mWindow
->IsCurrentInnerWindow()) {
369 return; // Leave Promise pending after navigation by design.
371 mConstraints
= aConstraints
;
372 promise
->MaybeResolve(false);
374 [this, self
, promise
](const RefPtr
<MediaMgrError
>& aError
) {
375 if (!mWindow
|| !mWindow
->IsCurrentInnerWindow()) {
376 return; // Leave Promise pending after navigation by design.
378 promise
->MaybeReject(
379 MakeRefPtr
<MediaStreamError
>(mWindow
, *aError
));
381 return promise
.forget();
384 ProcessedMediaTrack
* MediaStreamTrack::GetTrack() const {
385 MOZ_DIAGNOSTIC_ASSERT(!Ended());
389 MediaTrackGraph
* MediaStreamTrack::Graph() const {
390 MOZ_DIAGNOSTIC_ASSERT(!Ended());
391 return mTrack
->Graph();
394 MediaTrackGraphImpl
* MediaStreamTrack::GraphImpl() const {
395 MOZ_DIAGNOSTIC_ASSERT(!Ended());
396 return mTrack
->GraphImpl();
399 void MediaStreamTrack::SetPrincipal(nsIPrincipal
* aPrincipal
) {
400 if (aPrincipal
== mPrincipal
) {
403 mPrincipal
= aPrincipal
;
406 ("MediaStreamTrack %p principal changed to %p. Now: "
407 "null=%d, codebase=%d, expanded=%d, system=%d",
408 this, mPrincipal
.get(), mPrincipal
->GetIsNullPrincipal(),
409 mPrincipal
->GetIsContentPrincipal(),
410 mPrincipal
->GetIsExpandedPrincipal(), mPrincipal
->IsSystemPrincipal()));
411 for (PrincipalChangeObserver
<MediaStreamTrack
>* observer
:
412 mPrincipalChangeObservers
) {
413 observer
->PrincipalChanged(this);
417 void MediaStreamTrack::PrincipalChanged() {
418 mPendingPrincipal
= GetSource().GetPrincipal();
419 nsCOMPtr
<nsIPrincipal
> newPrincipal
= mPrincipal
;
420 LOG(LogLevel::Info
, ("MediaStreamTrack %p Principal changed on main thread "
421 "to %p (pending). Combining with existing principal %p.",
422 this, mPendingPrincipal
.get(), mPrincipal
.get()));
423 if (nsContentUtils::CombineResourcePrincipals(&newPrincipal
,
424 mPendingPrincipal
)) {
425 SetPrincipal(newPrincipal
);
429 void MediaStreamTrack::NotifyPrincipalHandleChanged(
430 const PrincipalHandle
& aNewPrincipalHandle
) {
431 PrincipalHandle
handle(aNewPrincipalHandle
);
432 LOG(LogLevel::Info
, ("MediaStreamTrack %p principalHandle changed on "
433 "MediaTrackGraph thread to %p. Current principal: %p, "
435 this, GetPrincipalFromHandle(handle
), mPrincipal
.get(),
436 mPendingPrincipal
.get()));
437 if (PrincipalHandleMatches(handle
, mPendingPrincipal
)) {
438 SetPrincipal(mPendingPrincipal
);
439 mPendingPrincipal
= nullptr;
443 void MediaStreamTrack::MutedChanged(bool aNewState
) {
444 MOZ_ASSERT(NS_IsMainThread());
447 * 4.3.1 Life-cycle and Media flow - Media flow
448 * To set a track's muted state to newState, the User Agent MUST run the
450 * 1. Let track be the MediaStreamTrack in question.
451 * 2. Set track's muted attribute to newState.
452 * 3. If newState is true let eventName be mute, otherwise unmute.
453 * 4. Fire a simple event named eventName on track.
456 if (mMuted
== aNewState
) {
461 ("MediaStreamTrack %p became %s", this, aNewState
? "muted" : "unmuted"));
469 nsString eventName
= aNewState
? u
"mute"_ns
: u
"unmute"_ns
;
470 DispatchTrustedEvent(eventName
);
473 void MediaStreamTrack::NotifyEnded() {
474 MOZ_ASSERT(mReadyState
== MediaStreamTrackState::Ended
);
476 for (const auto& consumer
: mConsumers
.Clone()) {
478 consumer
->NotifyEnded(this);
480 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
481 mConsumers
.RemoveElement(consumer
);
486 void MediaStreamTrack::NotifyEnabledChanged() {
487 GetSource().SinkEnabledStateChanged();
489 for (const auto& consumer
: mConsumers
.Clone()) {
491 consumer
->NotifyEnabledChanged(this, Enabled());
493 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
494 mConsumers
.RemoveElement(consumer
);
499 bool MediaStreamTrack::AddPrincipalChangeObserver(
500 PrincipalChangeObserver
<MediaStreamTrack
>* aObserver
) {
501 // XXX(Bug 1631371) Check if this should use a fallible operation as it
502 // pretended earlier.
503 mPrincipalChangeObservers
.AppendElement(aObserver
);
507 bool MediaStreamTrack::RemovePrincipalChangeObserver(
508 PrincipalChangeObserver
<MediaStreamTrack
>* aObserver
) {
509 return mPrincipalChangeObservers
.RemoveElement(aObserver
);
512 void MediaStreamTrack::AddConsumer(MediaStreamTrackConsumer
* aConsumer
) {
513 MOZ_ASSERT(!mConsumers
.Contains(aConsumer
));
514 mConsumers
.AppendElement(aConsumer
);
516 // Remove destroyed consumers for cleanliness
517 while (mConsumers
.RemoveElement(nullptr)) {
518 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
522 void MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer
* aConsumer
) {
523 mConsumers
.RemoveElement(aConsumer
);
525 // Remove destroyed consumers for cleanliness
526 while (mConsumers
.RemoveElement(nullptr)) {
527 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
531 already_AddRefed
<MediaStreamTrack
> MediaStreamTrack::Clone() {
532 RefPtr
<MediaStreamTrack
> newTrack
= CloneInternal();
533 newTrack
->SetEnabled(Enabled());
534 newTrack
->SetMuted(Muted());
535 return newTrack
.forget();
538 void MediaStreamTrack::SetReadyState(MediaStreamTrackState aState
) {
539 MOZ_ASSERT(!(mReadyState
== MediaStreamTrackState::Ended
&&
540 aState
== MediaStreamTrackState::Live
),
541 "We don't support overriding the ready state from ended to live");
547 if (mReadyState
== MediaStreamTrackState::Live
&&
548 aState
== MediaStreamTrackState::Ended
) {
550 mSource
->UnregisterSink(mSink
.get());
553 RemoveListener(mMTGListener
);
563 mMTGListener
= nullptr;
566 mReadyState
= aState
;
569 void MediaStreamTrack::OverrideEnded() {
570 MOZ_ASSERT(NS_IsMainThread());
576 LOG(LogLevel::Info
, ("MediaStreamTrack %p ended", this));
578 SetReadyState(MediaStreamTrackState::Ended
);
582 DispatchTrustedEvent(u
"ended"_ns
);
585 void MediaStreamTrack::AddListener(MediaTrackListener
* aListener
) {
587 ("MediaStreamTrack %p adding listener %p", this, aListener
));
588 mTrackListeners
.AppendElement(aListener
);
593 mTrack
->AddListener(aListener
);
596 void MediaStreamTrack::RemoveListener(MediaTrackListener
* aListener
) {
598 ("MediaStreamTrack %p removing listener %p", this, aListener
));
599 mTrackListeners
.RemoveElement(aListener
);
604 mTrack
->RemoveListener(aListener
);
607 void MediaStreamTrack::AddDirectListener(DirectMediaTrackListener
* aListener
) {
608 LOG(LogLevel::Debug
, ("MediaStreamTrack %p (%s) adding direct listener %p to "
610 this, AsAudioStreamTrack() ? "audio" : "video",
611 aListener
, mTrack
.get()));
612 mDirectTrackListeners
.AppendElement(aListener
);
617 mTrack
->AddDirectListener(aListener
);
620 void MediaStreamTrack::RemoveDirectListener(
621 DirectMediaTrackListener
* aListener
) {
623 ("MediaStreamTrack %p removing direct listener %p from track %p", this,
624 aListener
, mTrack
.get()));
625 mDirectTrackListeners
.RemoveElement(aListener
);
630 mTrack
->RemoveDirectListener(aListener
);
633 already_AddRefed
<MediaInputPort
> MediaStreamTrack::ForwardTrackContentsTo(
634 ProcessedMediaTrack
* aTrack
) {
635 MOZ_ASSERT(NS_IsMainThread());
636 MOZ_RELEASE_ASSERT(aTrack
);
637 return aTrack
->AllocateInputPort(mTrack
);
640 } // namespace mozilla::dom