no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / media / MediaStreamTrack.cpp
blob8ddb16d8e1fc7ae8ad569854428123b4c9299d91
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"
21 #ifdef LOG
22 # undef LOG
23 #endif
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)
36 NS_INTERFACE_MAP_END
38 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrackSource)
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
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, ""),
54 __func__);
57 /**
58 * MTGListener monitors state changes of the media flowing through the
59 * MediaTrackGraph.
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
70 * to the new one.
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 {
80 public:
81 explicit MTGListener(MediaStreamTrack* aTrack) : mTrack(aTrack) {}
83 void DoNotifyPrincipalHandleChanged(
84 const PrincipalHandle& aNewPrincipalHandle) {
85 MOZ_ASSERT(NS_IsMainThread());
87 if (!mTrack) {
88 return;
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());
117 if (!mTrack) {
118 return;
121 if (!mTrack->GetParentObject()) {
122 return;
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));
136 protected:
137 // Main thread only.
138 WeakPtr<MediaStreamTrack> mTrack;
141 class MediaStreamTrack::TrackSink : public MediaStreamTrackSource::Sink {
142 public:
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 {
152 if (!mTrack) {
153 return false;
155 return mTrack->Enabled();
158 void PrincipalChanged() override {
159 if (mTrack) {
160 mTrack->PrincipalChanged();
164 void MutedChanged(bool aNewState) override {
165 if (mTrack) {
166 mTrack->MutedChanged(aNewState);
170 void OverrideEnded() override {
171 if (mTrack) {
172 mTrack->OverrideEnded();
176 private:
177 WeakPtr<MediaStreamTrack> mTrack;
180 MediaStreamTrack::MediaStreamTrack(nsPIDOMWindowInner* aWindow,
181 mozilla::MediaTrack* aInputTrack,
182 MediaStreamTrackSource* aSource,
183 MediaStreamTrackState aReadyState,
184 bool aMuted,
185 const MediaTrackConstraints& aConstraints)
186 : mWindow(aWindow),
187 mInputTrack(aInputTrack),
188 mSource(aSource),
189 mSink(MakeUnique<TrackSink>(this)),
190 mPrincipal(aSource->GetPrincipal()),
191 mReadyState(aReadyState),
192 mEnabled(true),
193 mMuted(aMuted),
194 mConstraints(aConstraints) {
195 if (!Ended()) {
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);
218 nsresult rv;
219 nsCOMPtr<nsIUUIDGenerator> uuidgen =
220 do_GetService("@mozilla.org/uuid-generator;1", &rv);
222 nsID uuid;
223 memset(&uuid, 0, sizeof(uuid));
224 if (uuidgen) {
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)
251 tmp->Destroy();
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) {
280 LOG(LogLevel::Info,
281 ("MediaStreamTrack %p %s", this, aEnabled ? "Enabled" : "Disabled"));
283 if (mEnabled == aEnabled) {
284 return;
287 mEnabled = aEnabled;
289 if (Ended()) {
290 return;
293 mTrack->SetDisabledTrackMode(mEnabled ? DisabledTrackMode::ENABLED
294 : DisabledTrackMode::SILENCE_BLACK);
295 NotifyEnabledChanged();
298 void MediaStreamTrack::Stop() {
299 LOG(LogLevel::Info, ("MediaStreamTrack %p Stop()", this));
301 if (Ended()) {
302 LOG(LogLevel::Warning, ("MediaStreamTrack %p Already ended", this));
303 return;
306 SetReadyState(MediaStreamTrackState::Ended);
308 NotifyEnded();
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)) {
323 return;
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,
333 ErrorResult& aRv) {
334 if (MOZ_LOG_TEST(gMediaStreamTrackLog, LogLevel::Info)) {
335 nsString str;
336 aConstraints.ToJSON(str);
338 LOG(LogLevel::Info, ("MediaStreamTrack %p ApplyConstraints() with "
339 "constraints %s",
340 this, NS_ConvertUTF16toUTF8(str).get()));
343 nsIGlobalObject* go = mWindow ? mWindow->AsGlobal() : nullptr;
345 RefPtr<Promise> promise = Promise::Create(go, aRv);
346 if (aRv.Failed()) {
347 return nullptr;
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
354 // applied values.
356 // Keep a reference to this, to make sure it's still here when we get back.
357 RefPtr<MediaStreamTrack> self(this);
358 GetSource()
359 .ApplyConstraints(aConstraints, aCallerType)
360 ->Then(
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());
381 return mTrack;
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) {
396 return;
398 mPrincipal = aPrincipal;
400 LOG(LogLevel::Info,
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, "
429 "pending: %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
444 * following steps:
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) {
452 return;
455 LOG(LogLevel::Info,
456 ("MediaStreamTrack %p became %s", this, aNewState ? "muted" : "unmuted"));
458 mMuted = aNewState;
460 if (Ended()) {
461 return;
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()) {
472 if (consumer) {
473 consumer->NotifyEnded(this);
474 } else {
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()) {
485 if (consumer) {
486 consumer->NotifyEnabledChanged(this, Enabled());
487 } else {
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);
499 return true;
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");
538 if (Ended()) {
539 return;
542 if (mReadyState == MediaStreamTrackState::Live &&
543 aState == MediaStreamTrackState::Ended) {
544 if (mSource) {
545 mSource->UnregisterSink(mSink.get());
547 if (mMTGListener) {
548 RemoveListener(mMTGListener);
550 if (mPort) {
551 mPort->Destroy();
553 if (mTrack) {
554 mTrack->Destroy();
556 mPort = nullptr;
557 mTrack = nullptr;
558 mMTGListener = nullptr;
561 mReadyState = aState;
564 void MediaStreamTrack::OverrideEnded() {
565 MOZ_ASSERT(NS_IsMainThread());
567 if (Ended()) {
568 return;
571 LOG(LogLevel::Info, ("MediaStreamTrack %p ended", this));
573 SetReadyState(MediaStreamTrackState::Ended);
575 NotifyEnded();
577 DispatchTrustedEvent(u"ended"_ns);
580 void MediaStreamTrack::AddListener(MediaTrackListener* aListener) {
581 LOG(LogLevel::Debug,
582 ("MediaStreamTrack %p adding listener %p", this, aListener));
583 mTrackListeners.AppendElement(aListener);
585 if (Ended()) {
586 return;
588 mTrack->AddListener(aListener);
591 void MediaStreamTrack::RemoveListener(MediaTrackListener* aListener) {
592 LOG(LogLevel::Debug,
593 ("MediaStreamTrack %p removing listener %p", this, aListener));
594 mTrackListeners.RemoveElement(aListener);
596 if (Ended()) {
597 return;
599 mTrack->RemoveListener(aListener);
602 void MediaStreamTrack::AddDirectListener(DirectMediaTrackListener* aListener) {
603 LOG(LogLevel::Debug, ("MediaStreamTrack %p (%s) adding direct listener %p to "
604 "track %p",
605 this, AsAudioStreamTrack() ? "audio" : "video",
606 aListener, mTrack.get()));
607 mDirectTrackListeners.AppendElement(aListener);
609 if (Ended()) {
610 return;
612 mTrack->AddDirectListener(aListener);
615 void MediaStreamTrack::RemoveDirectListener(
616 DirectMediaTrackListener* aListener) {
617 LOG(LogLevel::Debug,
618 ("MediaStreamTrack %p removing direct listener %p from track %p", this,
619 aListener, mTrack.get()));
620 mDirectTrackListeners.RemoveElement(aListener);
622 if (Ended()) {
623 return;
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