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 if (!nsContentUtils::ResistFingerprinting(aCallerType
)) {
326 if (aResult
.mFacingMode
.WasPassed()) {
327 aResult
.mFacingMode
.Value().AssignASCII(
328 VideoFacingModeEnumValues::GetString(VideoFacingModeEnum::User
));
332 already_AddRefed
<Promise
> MediaStreamTrack::ApplyConstraints(
333 const MediaTrackConstraints
& aConstraints
, CallerType aCallerType
,
335 if (MOZ_LOG_TEST(gMediaStreamTrackLog
, LogLevel::Info
)) {
337 aConstraints
.ToJSON(str
);
339 LOG(LogLevel::Info
, ("MediaStreamTrack %p ApplyConstraints() with "
341 this, NS_ConvertUTF16toUTF8(str
).get()));
344 nsIGlobalObject
* go
= mWindow
? mWindow
->AsGlobal() : nullptr;
346 RefPtr
<Promise
> promise
= Promise::Create(go
, aRv
);
351 // Forward constraints to the source.
353 // After GetSource().ApplyConstraints succeeds (after it's been to
354 // media-thread and back), and no sooner, do we set mConstraints to the newly
357 // Keep a reference to this, to make sure it's still here when we get back.
358 RefPtr
<MediaStreamTrack
> self(this);
360 .ApplyConstraints(aConstraints
, aCallerType
)
362 GetCurrentSerialEventTarget(), __func__
,
363 [this, self
, promise
, aConstraints
](bool aDummy
) {
364 if (!mWindow
|| !mWindow
->IsCurrentInnerWindow()) {
365 return; // Leave Promise pending after navigation by design.
367 mConstraints
= aConstraints
;
368 promise
->MaybeResolve(false);
370 [this, self
, promise
](const RefPtr
<MediaMgrError
>& aError
) {
371 if (!mWindow
|| !mWindow
->IsCurrentInnerWindow()) {
372 return; // Leave Promise pending after navigation by design.
374 promise
->MaybeReject(
375 MakeRefPtr
<MediaStreamError
>(mWindow
, *aError
));
377 return promise
.forget();
380 ProcessedMediaTrack
* MediaStreamTrack::GetTrack() const {
381 MOZ_DIAGNOSTIC_ASSERT(!Ended());
385 MediaTrackGraph
* MediaStreamTrack::Graph() const {
386 MOZ_DIAGNOSTIC_ASSERT(!Ended());
387 return mTrack
->Graph();
390 MediaTrackGraphImpl
* MediaStreamTrack::GraphImpl() const {
391 MOZ_DIAGNOSTIC_ASSERT(!Ended());
392 return mTrack
->GraphImpl();
395 void MediaStreamTrack::SetPrincipal(nsIPrincipal
* aPrincipal
) {
396 if (aPrincipal
== mPrincipal
) {
399 mPrincipal
= aPrincipal
;
402 ("MediaStreamTrack %p principal changed to %p. Now: "
403 "null=%d, codebase=%d, expanded=%d, system=%d",
404 this, mPrincipal
.get(), mPrincipal
->GetIsNullPrincipal(),
405 mPrincipal
->GetIsContentPrincipal(),
406 mPrincipal
->GetIsExpandedPrincipal(), mPrincipal
->IsSystemPrincipal()));
407 for (PrincipalChangeObserver
<MediaStreamTrack
>* observer
:
408 mPrincipalChangeObservers
) {
409 observer
->PrincipalChanged(this);
413 void MediaStreamTrack::PrincipalChanged() {
414 mPendingPrincipal
= GetSource().GetPrincipal();
415 nsCOMPtr
<nsIPrincipal
> newPrincipal
= mPrincipal
;
416 LOG(LogLevel::Info
, ("MediaStreamTrack %p Principal changed on main thread "
417 "to %p (pending). Combining with existing principal %p.",
418 this, mPendingPrincipal
.get(), mPrincipal
.get()));
419 if (nsContentUtils::CombineResourcePrincipals(&newPrincipal
,
420 mPendingPrincipal
)) {
421 SetPrincipal(newPrincipal
);
425 void MediaStreamTrack::NotifyPrincipalHandleChanged(
426 const PrincipalHandle
& aNewPrincipalHandle
) {
427 PrincipalHandle
handle(aNewPrincipalHandle
);
428 LOG(LogLevel::Info
, ("MediaStreamTrack %p principalHandle changed on "
429 "MediaTrackGraph thread to %p. Current principal: %p, "
431 this, GetPrincipalFromHandle(handle
), mPrincipal
.get(),
432 mPendingPrincipal
.get()));
433 if (PrincipalHandleMatches(handle
, mPendingPrincipal
)) {
434 SetPrincipal(mPendingPrincipal
);
435 mPendingPrincipal
= nullptr;
439 void MediaStreamTrack::MutedChanged(bool aNewState
) {
440 MOZ_ASSERT(NS_IsMainThread());
443 * 4.3.1 Life-cycle and Media flow - Media flow
444 * To set a track's muted state to newState, the User Agent MUST run the
446 * 1. Let track be the MediaStreamTrack in question.
447 * 2. Set track's muted attribute to newState.
448 * 3. If newState is true let eventName be mute, otherwise unmute.
449 * 4. Fire a simple event named eventName on track.
452 if (mMuted
== aNewState
) {
457 ("MediaStreamTrack %p became %s", this, aNewState
? "muted" : "unmuted"));
465 nsString eventName
= aNewState
? u
"mute"_ns
: u
"unmute"_ns
;
466 DispatchTrustedEvent(eventName
);
469 void MediaStreamTrack::NotifyEnded() {
470 MOZ_ASSERT(mReadyState
== MediaStreamTrackState::Ended
);
472 for (const auto& consumer
: mConsumers
.Clone()) {
474 consumer
->NotifyEnded(this);
476 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
477 mConsumers
.RemoveElement(consumer
);
482 void MediaStreamTrack::NotifyEnabledChanged() {
483 GetSource().SinkEnabledStateChanged();
485 for (const auto& consumer
: mConsumers
.Clone()) {
487 consumer
->NotifyEnabledChanged(this, Enabled());
489 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
490 mConsumers
.RemoveElement(consumer
);
495 bool MediaStreamTrack::AddPrincipalChangeObserver(
496 PrincipalChangeObserver
<MediaStreamTrack
>* aObserver
) {
497 // XXX(Bug 1631371) Check if this should use a fallible operation as it
498 // pretended earlier.
499 mPrincipalChangeObservers
.AppendElement(aObserver
);
503 bool MediaStreamTrack::RemovePrincipalChangeObserver(
504 PrincipalChangeObserver
<MediaStreamTrack
>* aObserver
) {
505 return mPrincipalChangeObservers
.RemoveElement(aObserver
);
508 void MediaStreamTrack::AddConsumer(MediaStreamTrackConsumer
* aConsumer
) {
509 MOZ_ASSERT(!mConsumers
.Contains(aConsumer
));
510 mConsumers
.AppendElement(aConsumer
);
512 // Remove destroyed consumers for cleanliness
513 while (mConsumers
.RemoveElement(nullptr)) {
514 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
518 void MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer
* aConsumer
) {
519 mConsumers
.RemoveElement(aConsumer
);
521 // Remove destroyed consumers for cleanliness
522 while (mConsumers
.RemoveElement(nullptr)) {
523 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
527 already_AddRefed
<MediaStreamTrack
> MediaStreamTrack::Clone() {
528 RefPtr
<MediaStreamTrack
> newTrack
= CloneInternal();
529 newTrack
->SetEnabled(Enabled());
530 newTrack
->SetMuted(Muted());
531 return newTrack
.forget();
534 void MediaStreamTrack::SetReadyState(MediaStreamTrackState aState
) {
535 MOZ_ASSERT(!(mReadyState
== MediaStreamTrackState::Ended
&&
536 aState
== MediaStreamTrackState::Live
),
537 "We don't support overriding the ready state from ended to live");
543 if (mReadyState
== MediaStreamTrackState::Live
&&
544 aState
== MediaStreamTrackState::Ended
) {
546 mSource
->UnregisterSink(mSink
.get());
549 RemoveListener(mMTGListener
);
559 mMTGListener
= nullptr;
562 mReadyState
= aState
;
565 void MediaStreamTrack::OverrideEnded() {
566 MOZ_ASSERT(NS_IsMainThread());
572 LOG(LogLevel::Info
, ("MediaStreamTrack %p ended", this));
574 SetReadyState(MediaStreamTrackState::Ended
);
578 DispatchTrustedEvent(u
"ended"_ns
);
581 void MediaStreamTrack::AddListener(MediaTrackListener
* aListener
) {
583 ("MediaStreamTrack %p adding listener %p", this, aListener
));
584 mTrackListeners
.AppendElement(aListener
);
589 mTrack
->AddListener(aListener
);
592 void MediaStreamTrack::RemoveListener(MediaTrackListener
* aListener
) {
594 ("MediaStreamTrack %p removing listener %p", this, aListener
));
595 mTrackListeners
.RemoveElement(aListener
);
600 mTrack
->RemoveListener(aListener
);
603 void MediaStreamTrack::AddDirectListener(DirectMediaTrackListener
* aListener
) {
604 LOG(LogLevel::Debug
, ("MediaStreamTrack %p (%s) adding direct listener %p to "
606 this, AsAudioStreamTrack() ? "audio" : "video",
607 aListener
, mTrack
.get()));
608 mDirectTrackListeners
.AppendElement(aListener
);
613 mTrack
->AddDirectListener(aListener
);
616 void MediaStreamTrack::RemoveDirectListener(
617 DirectMediaTrackListener
* aListener
) {
619 ("MediaStreamTrack %p removing direct listener %p from track %p", this,
620 aListener
, mTrack
.get()));
621 mDirectTrackListeners
.RemoveElement(aListener
);
626 mTrack
->RemoveDirectListener(aListener
);
629 already_AddRefed
<MediaInputPort
> MediaStreamTrack::ForwardTrackContentsTo(
630 ProcessedMediaTrack
* aTrack
) {
631 MOZ_ASSERT(NS_IsMainThread());
632 MOZ_RELEASE_ASSERT(aTrack
);
633 return aTrack
->AllocateInputPort(mTrack
);
636 } // namespace mozilla::dom