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
=
126 nsGlobalWindowInner::Cast(mTrack
->GetParentObject())
127 ->AbstractMainThreadFor(TaskCategory::Other
);
128 mainThread
->Dispatch(NewRunnableMethod("MediaStreamTrack::OverrideEnded",
130 &MediaStreamTrack::OverrideEnded
));
133 void NotifyEnded(MediaTrackGraph
* aGraph
) override
{
134 aGraph
->DispatchToMainThreadStableState(
135 NewRunnableMethod("MediaStreamTrack::MTGListener::DoNotifyEnded", this,
136 &MTGListener::DoNotifyEnded
));
141 WeakPtr
<MediaStreamTrack
> mTrack
;
144 class MediaStreamTrack::TrackSink
: public MediaStreamTrackSource::Sink
{
146 explicit TrackSink(MediaStreamTrack
* aTrack
) : mTrack(aTrack
) {}
149 * Keep the track source alive. This track and any clones are controlling the
150 * lifetime of the source by being registered as its sinks.
152 bool KeepsSourceAlive() const override
{ return true; }
154 bool Enabled() const override
{
158 return mTrack
->Enabled();
161 void PrincipalChanged() override
{
163 mTrack
->PrincipalChanged();
167 void MutedChanged(bool aNewState
) override
{
169 mTrack
->MutedChanged(aNewState
);
173 void OverrideEnded() override
{
175 mTrack
->OverrideEnded();
180 WeakPtr
<MediaStreamTrack
> mTrack
;
183 MediaStreamTrack::MediaStreamTrack(nsPIDOMWindowInner
* aWindow
,
184 mozilla::MediaTrack
* aInputTrack
,
185 MediaStreamTrackSource
* aSource
,
186 MediaStreamTrackState aReadyState
,
188 const MediaTrackConstraints
& aConstraints
)
190 mInputTrack(aInputTrack
),
192 mSink(MakeUnique
<TrackSink
>(this)),
193 mPrincipal(aSource
->GetPrincipal()),
194 mReadyState(aReadyState
),
197 mConstraints(aConstraints
) {
199 GetSource().RegisterSink(mSink
.get());
201 // Even if the input track is destroyed we need mTrack so that methods
202 // like AddListener still work. Keeping the number of paths to a minimum
203 // also helps prevent bugs elsewhere. We'll be ended through the
204 // MediaStreamTrackSource soon enough.
205 auto graph
= mInputTrack
->IsDestroyed()
206 ? MediaTrackGraph::GetInstanceIfExists(
207 mWindow
, mInputTrack
->mSampleRate
,
208 MediaTrackGraph::DEFAULT_OUTPUT_DEVICE
)
209 : mInputTrack
->Graph();
210 MOZ_DIAGNOSTIC_ASSERT(graph
,
211 "A destroyed input track is only expected when "
212 "cloning, but since we're live there must be another "
213 "live track that is keeping the graph alive");
215 mTrack
= graph
->CreateForwardedInputTrack(mInputTrack
->mType
);
216 mPort
= mTrack
->AllocateInputPort(mInputTrack
);
217 mMTGListener
= new MTGListener(this);
218 AddListener(mMTGListener
);
222 nsCOMPtr
<nsIUUIDGenerator
> uuidgen
=
223 do_GetService("@mozilla.org/uuid-generator;1", &rv
);
226 memset(&uuid
, 0, sizeof(uuid
));
228 uuidgen
->GenerateUUIDInPlace(&uuid
);
231 char chars
[NSID_LENGTH
];
232 uuid
.ToProvidedString(chars
);
233 mID
= NS_ConvertASCIItoUTF16(chars
);
236 MediaStreamTrack::~MediaStreamTrack() { Destroy(); }
238 void MediaStreamTrack::Destroy() {
239 SetReadyState(MediaStreamTrackState::Ended
);
240 // Remove all listeners -- avoid iterating over the list we're removing from
241 for (const auto& listener
: mTrackListeners
.Clone()) {
242 RemoveListener(listener
);
244 // Do the same as above for direct listeners
245 for (const auto& listener
: mDirectTrackListeners
.Clone()) {
246 RemoveDirectListener(listener
);
250 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack
)
252 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack
,
253 DOMEventTargetHelper
)
255 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow
)
256 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource
)
257 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal
)
258 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPrincipal
)
259 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
260 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
262 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack
,
263 DOMEventTargetHelper
)
264 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow
)
265 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource
)
266 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal
)
267 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPrincipal
)
268 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
270 NS_IMPL_ADDREF_INHERITED(MediaStreamTrack
, DOMEventTargetHelper
)
271 NS_IMPL_RELEASE_INHERITED(MediaStreamTrack
, DOMEventTargetHelper
)
272 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrack
)
273 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
275 JSObject
* MediaStreamTrack::WrapObject(JSContext
* aCx
,
276 JS::Handle
<JSObject
*> aGivenProto
) {
277 return MediaStreamTrack_Binding::Wrap(aCx
, this, aGivenProto
);
280 void MediaStreamTrack::GetId(nsAString
& aID
) const { aID
= mID
; }
282 void MediaStreamTrack::SetEnabled(bool aEnabled
) {
284 ("MediaStreamTrack %p %s", this, aEnabled
? "Enabled" : "Disabled"));
286 if (mEnabled
== aEnabled
) {
296 mTrack
->SetDisabledTrackMode(mEnabled
? DisabledTrackMode::ENABLED
297 : DisabledTrackMode::SILENCE_BLACK
);
298 NotifyEnabledChanged();
301 void MediaStreamTrack::Stop() {
302 LOG(LogLevel::Info
, ("MediaStreamTrack %p Stop()", this));
305 LOG(LogLevel::Warning
, ("MediaStreamTrack %p Already ended", this));
309 SetReadyState(MediaStreamTrackState::Ended
);
314 void MediaStreamTrack::GetConstraints(dom::MediaTrackConstraints
& aResult
) {
315 aResult
= mConstraints
;
318 void MediaStreamTrack::GetSettings(dom::MediaTrackSettings
& aResult
,
319 CallerType aCallerType
) {
320 GetSource().GetSettings(aResult
);
322 // Spoof values when privacy.resistFingerprinting is true.
323 nsIGlobalObject
* global
= mWindow
? mWindow
->AsGlobal() : nullptr;
324 if (!nsContentUtils::ShouldResistFingerprinting(
325 aCallerType
, global
, RFPTarget::StreamVideoFacingMode
)) {
328 if (aResult
.mFacingMode
.WasPassed()) {
329 aResult
.mFacingMode
.Value().AssignASCII(
330 VideoFacingModeEnumValues::GetString(VideoFacingModeEnum::User
));
334 already_AddRefed
<Promise
> MediaStreamTrack::ApplyConstraints(
335 const MediaTrackConstraints
& aConstraints
, CallerType aCallerType
,
337 if (MOZ_LOG_TEST(gMediaStreamTrackLog
, LogLevel::Info
)) {
339 aConstraints
.ToJSON(str
);
341 LOG(LogLevel::Info
, ("MediaStreamTrack %p ApplyConstraints() with "
343 this, NS_ConvertUTF16toUTF8(str
).get()));
346 nsIGlobalObject
* go
= mWindow
? mWindow
->AsGlobal() : nullptr;
348 RefPtr
<Promise
> promise
= Promise::Create(go
, aRv
);
353 // Forward constraints to the source.
355 // After GetSource().ApplyConstraints succeeds (after it's been to
356 // media-thread and back), and no sooner, do we set mConstraints to the newly
359 // Keep a reference to this, to make sure it's still here when we get back.
360 RefPtr
<MediaStreamTrack
> self(this);
362 .ApplyConstraints(aConstraints
, aCallerType
)
364 GetCurrentSerialEventTarget(), __func__
,
365 [this, self
, promise
, aConstraints
](bool aDummy
) {
366 if (!mWindow
|| !mWindow
->IsCurrentInnerWindow()) {
367 return; // Leave Promise pending after navigation by design.
369 mConstraints
= aConstraints
;
370 promise
->MaybeResolve(false);
372 [this, self
, promise
](const RefPtr
<MediaMgrError
>& aError
) {
373 if (!mWindow
|| !mWindow
->IsCurrentInnerWindow()) {
374 return; // Leave Promise pending after navigation by design.
376 promise
->MaybeReject(
377 MakeRefPtr
<MediaStreamError
>(mWindow
, *aError
));
379 return promise
.forget();
382 ProcessedMediaTrack
* MediaStreamTrack::GetTrack() const {
383 MOZ_DIAGNOSTIC_ASSERT(!Ended());
387 MediaTrackGraph
* MediaStreamTrack::Graph() const {
388 MOZ_DIAGNOSTIC_ASSERT(!Ended());
389 return mTrack
->Graph();
392 MediaTrackGraphImpl
* MediaStreamTrack::GraphImpl() const {
393 MOZ_DIAGNOSTIC_ASSERT(!Ended());
394 return mTrack
->GraphImpl();
397 void MediaStreamTrack::SetPrincipal(nsIPrincipal
* aPrincipal
) {
398 if (aPrincipal
== mPrincipal
) {
401 mPrincipal
= aPrincipal
;
404 ("MediaStreamTrack %p principal changed to %p. Now: "
405 "null=%d, codebase=%d, expanded=%d, system=%d",
406 this, mPrincipal
.get(), mPrincipal
->GetIsNullPrincipal(),
407 mPrincipal
->GetIsContentPrincipal(),
408 mPrincipal
->GetIsExpandedPrincipal(), mPrincipal
->IsSystemPrincipal()));
409 for (PrincipalChangeObserver
<MediaStreamTrack
>* observer
:
410 mPrincipalChangeObservers
) {
411 observer
->PrincipalChanged(this);
415 void MediaStreamTrack::PrincipalChanged() {
416 mPendingPrincipal
= GetSource().GetPrincipal();
417 nsCOMPtr
<nsIPrincipal
> newPrincipal
= mPrincipal
;
418 LOG(LogLevel::Info
, ("MediaStreamTrack %p Principal changed on main thread "
419 "to %p (pending). Combining with existing principal %p.",
420 this, mPendingPrincipal
.get(), mPrincipal
.get()));
421 if (nsContentUtils::CombineResourcePrincipals(&newPrincipal
,
422 mPendingPrincipal
)) {
423 SetPrincipal(newPrincipal
);
427 void MediaStreamTrack::NotifyPrincipalHandleChanged(
428 const PrincipalHandle
& aNewPrincipalHandle
) {
429 PrincipalHandle
handle(aNewPrincipalHandle
);
430 LOG(LogLevel::Info
, ("MediaStreamTrack %p principalHandle changed on "
431 "MediaTrackGraph thread to %p. Current principal: %p, "
433 this, GetPrincipalFromHandle(handle
), mPrincipal
.get(),
434 mPendingPrincipal
.get()));
435 if (PrincipalHandleMatches(handle
, mPendingPrincipal
)) {
436 SetPrincipal(mPendingPrincipal
);
437 mPendingPrincipal
= nullptr;
441 void MediaStreamTrack::MutedChanged(bool aNewState
) {
442 MOZ_ASSERT(NS_IsMainThread());
445 * 4.3.1 Life-cycle and Media flow - Media flow
446 * To set a track's muted state to newState, the User Agent MUST run the
448 * 1. Let track be the MediaStreamTrack in question.
449 * 2. Set track's muted attribute to newState.
450 * 3. If newState is true let eventName be mute, otherwise unmute.
451 * 4. Fire a simple event named eventName on track.
454 if (mMuted
== aNewState
) {
459 ("MediaStreamTrack %p became %s", this, aNewState
? "muted" : "unmuted"));
467 nsString eventName
= aNewState
? u
"mute"_ns
: u
"unmute"_ns
;
468 DispatchTrustedEvent(eventName
);
471 void MediaStreamTrack::NotifyEnded() {
472 MOZ_ASSERT(mReadyState
== MediaStreamTrackState::Ended
);
474 for (const auto& consumer
: mConsumers
.Clone()) {
476 consumer
->NotifyEnded(this);
478 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
479 mConsumers
.RemoveElement(consumer
);
484 void MediaStreamTrack::NotifyEnabledChanged() {
485 GetSource().SinkEnabledStateChanged();
487 for (const auto& consumer
: mConsumers
.Clone()) {
489 consumer
->NotifyEnabledChanged(this, Enabled());
491 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
492 mConsumers
.RemoveElement(consumer
);
497 bool MediaStreamTrack::AddPrincipalChangeObserver(
498 PrincipalChangeObserver
<MediaStreamTrack
>* aObserver
) {
499 // XXX(Bug 1631371) Check if this should use a fallible operation as it
500 // pretended earlier.
501 mPrincipalChangeObservers
.AppendElement(aObserver
);
505 bool MediaStreamTrack::RemovePrincipalChangeObserver(
506 PrincipalChangeObserver
<MediaStreamTrack
>* aObserver
) {
507 return mPrincipalChangeObservers
.RemoveElement(aObserver
);
510 void MediaStreamTrack::AddConsumer(MediaStreamTrackConsumer
* aConsumer
) {
511 MOZ_ASSERT(!mConsumers
.Contains(aConsumer
));
512 mConsumers
.AppendElement(aConsumer
);
514 // Remove destroyed consumers for cleanliness
515 while (mConsumers
.RemoveElement(nullptr)) {
516 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
520 void MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer
* aConsumer
) {
521 mConsumers
.RemoveElement(aConsumer
);
523 // Remove destroyed consumers for cleanliness
524 while (mConsumers
.RemoveElement(nullptr)) {
525 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
529 already_AddRefed
<MediaStreamTrack
> MediaStreamTrack::Clone() {
530 RefPtr
<MediaStreamTrack
> newTrack
= CloneInternal();
531 newTrack
->SetEnabled(Enabled());
532 newTrack
->SetMuted(Muted());
533 return newTrack
.forget();
536 void MediaStreamTrack::SetReadyState(MediaStreamTrackState aState
) {
537 MOZ_ASSERT(!(mReadyState
== MediaStreamTrackState::Ended
&&
538 aState
== MediaStreamTrackState::Live
),
539 "We don't support overriding the ready state from ended to live");
545 if (mReadyState
== MediaStreamTrackState::Live
&&
546 aState
== MediaStreamTrackState::Ended
) {
548 mSource
->UnregisterSink(mSink
.get());
551 RemoveListener(mMTGListener
);
561 mMTGListener
= nullptr;
564 mReadyState
= aState
;
567 void MediaStreamTrack::OverrideEnded() {
568 MOZ_ASSERT(NS_IsMainThread());
574 LOG(LogLevel::Info
, ("MediaStreamTrack %p ended", this));
576 SetReadyState(MediaStreamTrackState::Ended
);
580 DispatchTrustedEvent(u
"ended"_ns
);
583 void MediaStreamTrack::AddListener(MediaTrackListener
* aListener
) {
585 ("MediaStreamTrack %p adding listener %p", this, aListener
));
586 mTrackListeners
.AppendElement(aListener
);
591 mTrack
->AddListener(aListener
);
594 void MediaStreamTrack::RemoveListener(MediaTrackListener
* aListener
) {
596 ("MediaStreamTrack %p removing listener %p", this, aListener
));
597 mTrackListeners
.RemoveElement(aListener
);
602 mTrack
->RemoveListener(aListener
);
605 void MediaStreamTrack::AddDirectListener(DirectMediaTrackListener
* aListener
) {
606 LOG(LogLevel::Debug
, ("MediaStreamTrack %p (%s) adding direct listener %p to "
608 this, AsAudioStreamTrack() ? "audio" : "video",
609 aListener
, mTrack
.get()));
610 mDirectTrackListeners
.AppendElement(aListener
);
615 mTrack
->AddDirectListener(aListener
);
618 void MediaStreamTrack::RemoveDirectListener(
619 DirectMediaTrackListener
* aListener
) {
621 ("MediaStreamTrack %p removing direct listener %p from track %p", this,
622 aListener
, mTrack
.get()));
623 mDirectTrackListeners
.RemoveElement(aListener
);
628 mTrack
->RemoveDirectListener(aListener
);
631 already_AddRefed
<MediaInputPort
> MediaStreamTrack::ForwardTrackContentsTo(
632 ProcessedMediaTrack
* aTrack
) {
633 MOZ_ASSERT(NS_IsMainThread());
634 MOZ_RELEASE_ASSERT(aTrack
);
635 return aTrack
->AllocateInputPort(mTrack
);
638 } // namespace mozilla::dom