summaryrefslogtreecommitdiffstats
path: root/dom/media/MediaStreamTrack.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/MediaStreamTrack.cpp')
-rw-r--r--dom/media/MediaStreamTrack.cpp635
1 files changed, 635 insertions, 0 deletions
diff --git a/dom/media/MediaStreamTrack.cpp b/dom/media/MediaStreamTrack.cpp
new file mode 100644
index 0000000000..8ddb16d8e1
--- /dev/null
+++ b/dom/media/MediaStreamTrack.cpp
@@ -0,0 +1,635 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaStreamTrack.h"
+
+#include "DOMMediaStream.h"
+#include "MediaSegment.h"
+#include "MediaStreamError.h"
+#include "MediaTrackGraphImpl.h"
+#include "MediaTrackListener.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/Promise.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindowInner.h"
+#include "nsIUUIDGenerator.h"
+#include "nsServiceManagerUtils.h"
+#include "systemservices/MediaUtils.h"
+
+#ifdef LOG
+# undef LOG
+#endif
+
+static mozilla::LazyLogModule gMediaStreamTrackLog("MediaStreamTrack");
+#define LOG(type, msg) MOZ_LOG(gMediaStreamTrackLog, type, msg)
+
+using namespace mozilla::media;
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamTrackSource)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamTrackSource)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackSource)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrackSource)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaStreamTrackSource)
+ tmp->Destroy();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaStreamTrackSource)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+auto MediaStreamTrackSource::ApplyConstraints(
+ const dom::MediaTrackConstraints& aConstraints, CallerType aCallerType)
+ -> RefPtr<ApplyConstraintsPromise> {
+ return ApplyConstraintsPromise::CreateAndReject(
+ MakeRefPtr<MediaMgrError>(MediaMgrError::Name::OverconstrainedError, ""),
+ __func__);
+}
+
+/**
+ * MTGListener monitors state changes of the media flowing through the
+ * MediaTrackGraph.
+ *
+ *
+ * For changes to PrincipalHandle the following applies:
+ *
+ * When the main thread principal for a MediaStreamTrack changes, its principal
+ * will be set to the combination of the previous principal and the new one.
+ *
+ * As a PrincipalHandle change later happens on the MediaTrackGraph thread, we
+ * will be notified. If the latest principal on main thread matches the
+ * PrincipalHandle we just saw on MTG thread, we will set the track's principal
+ * to the new one.
+ *
+ * We know at this point that the old principal has been flushed out and data
+ * under it cannot leak to consumers.
+ *
+ * In case of multiple changes to the main thread state, the track's principal
+ * will be a combination of its old principal and all the new ones until the
+ * latest main thread principal matches the PrincipalHandle on the MTG thread.
+ */
+class MediaStreamTrack::MTGListener : public MediaTrackListener {
+ public:
+ explicit MTGListener(MediaStreamTrack* aTrack) : mTrack(aTrack) {}
+
+ void DoNotifyPrincipalHandleChanged(
+ const PrincipalHandle& aNewPrincipalHandle) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mTrack) {
+ return;
+ }
+
+ mTrack->NotifyPrincipalHandleChanged(aNewPrincipalHandle);
+ }
+
+ void NotifyPrincipalHandleChanged(
+ MediaTrackGraph* aGraph,
+ const PrincipalHandle& aNewPrincipalHandle) override {
+ aGraph->DispatchToMainThreadStableState(
+ NewRunnableMethod<StoreCopyPassByConstLRef<PrincipalHandle>>(
+ "dom::MediaStreamTrack::MTGListener::"
+ "DoNotifyPrincipalHandleChanged",
+ this, &MTGListener::DoNotifyPrincipalHandleChanged,
+ aNewPrincipalHandle));
+ }
+
+ void NotifyRemoved(MediaTrackGraph* aGraph) override {
+ // `mTrack` is a WeakPtr and must be destroyed on main thread.
+ // We dispatch ourselves to main thread here in case the MediaTrackGraph
+ // is holding the last reference to us.
+ aGraph->DispatchToMainThreadStableState(
+ NS_NewRunnableFunction("MediaStreamTrack::MTGListener::mTrackReleaser",
+ [self = RefPtr<MTGListener>(this)]() {}));
+ }
+
+ void DoNotifyEnded() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mTrack) {
+ return;
+ }
+
+ if (!mTrack->GetParentObject()) {
+ return;
+ }
+
+ AbstractThread::MainThread()->Dispatch(
+ NewRunnableMethod("MediaStreamTrack::OverrideEnded", mTrack.get(),
+ &MediaStreamTrack::OverrideEnded));
+ }
+
+ void NotifyEnded(MediaTrackGraph* aGraph) override {
+ aGraph->DispatchToMainThreadStableState(
+ NewRunnableMethod("MediaStreamTrack::MTGListener::DoNotifyEnded", this,
+ &MTGListener::DoNotifyEnded));
+ }
+
+ protected:
+ // Main thread only.
+ WeakPtr<MediaStreamTrack> mTrack;
+};
+
+class MediaStreamTrack::TrackSink : public MediaStreamTrackSource::Sink {
+ public:
+ explicit TrackSink(MediaStreamTrack* aTrack) : mTrack(aTrack) {}
+
+ /**
+ * Keep the track source alive. This track and any clones are controlling the
+ * lifetime of the source by being registered as its sinks.
+ */
+ bool KeepsSourceAlive() const override { return true; }
+
+ bool Enabled() const override {
+ if (!mTrack) {
+ return false;
+ }
+ return mTrack->Enabled();
+ }
+
+ void PrincipalChanged() override {
+ if (mTrack) {
+ mTrack->PrincipalChanged();
+ }
+ }
+
+ void MutedChanged(bool aNewState) override {
+ if (mTrack) {
+ mTrack->MutedChanged(aNewState);
+ }
+ }
+
+ void OverrideEnded() override {
+ if (mTrack) {
+ mTrack->OverrideEnded();
+ }
+ }
+
+ private:
+ WeakPtr<MediaStreamTrack> mTrack;
+};
+
+MediaStreamTrack::MediaStreamTrack(nsPIDOMWindowInner* aWindow,
+ mozilla::MediaTrack* aInputTrack,
+ MediaStreamTrackSource* aSource,
+ MediaStreamTrackState aReadyState,
+ bool aMuted,
+ const MediaTrackConstraints& aConstraints)
+ : mWindow(aWindow),
+ mInputTrack(aInputTrack),
+ mSource(aSource),
+ mSink(MakeUnique<TrackSink>(this)),
+ mPrincipal(aSource->GetPrincipal()),
+ mReadyState(aReadyState),
+ mEnabled(true),
+ mMuted(aMuted),
+ mConstraints(aConstraints) {
+ if (!Ended()) {
+ GetSource().RegisterSink(mSink.get());
+
+ // Even if the input track is destroyed we need mTrack so that methods
+ // like AddListener still work. Keeping the number of paths to a minimum
+ // also helps prevent bugs elsewhere. We'll be ended through the
+ // MediaStreamTrackSource soon enough.
+ auto graph = mInputTrack->IsDestroyed()
+ ? MediaTrackGraph::GetInstanceIfExists(
+ mWindow, mInputTrack->mSampleRate,
+ MediaTrackGraph::DEFAULT_OUTPUT_DEVICE)
+ : mInputTrack->Graph();
+ MOZ_DIAGNOSTIC_ASSERT(graph,
+ "A destroyed input track is only expected when "
+ "cloning, but since we're live there must be another "
+ "live track that is keeping the graph alive");
+
+ mTrack = graph->CreateForwardedInputTrack(mInputTrack->mType);
+ mPort = mTrack->AllocateInputPort(mInputTrack);
+ mMTGListener = new MTGListener(this);
+ AddListener(mMTGListener);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ do_GetService("@mozilla.org/uuid-generator;1", &rv);
+
+ nsID uuid;
+ memset(&uuid, 0, sizeof(uuid));
+ if (uuidgen) {
+ uuidgen->GenerateUUIDInPlace(&uuid);
+ }
+
+ char chars[NSID_LENGTH];
+ uuid.ToProvidedString(chars);
+ mID = NS_ConvertASCIItoUTF16(chars);
+}
+
+MediaStreamTrack::~MediaStreamTrack() { Destroy(); }
+
+void MediaStreamTrack::Destroy() {
+ SetReadyState(MediaStreamTrackState::Ended);
+ // Remove all listeners -- avoid iterating over the list we're removing from
+ for (const auto& listener : mTrackListeners.Clone()) {
+ RemoveListener(listener);
+ }
+ // Do the same as above for direct listeners
+ for (const auto& listener : mDirectTrackListeners.Clone()) {
+ RemoveDirectListener(listener);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack,
+ DOMEventTargetHelper)
+ tmp->Destroy();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPrincipal)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrack)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+JSObject* MediaStreamTrack::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return MediaStreamTrack_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void MediaStreamTrack::GetId(nsAString& aID) const { aID = mID; }
+
+void MediaStreamTrack::SetEnabled(bool aEnabled) {
+ LOG(LogLevel::Info,
+ ("MediaStreamTrack %p %s", this, aEnabled ? "Enabled" : "Disabled"));
+
+ if (mEnabled == aEnabled) {
+ return;
+ }
+
+ mEnabled = aEnabled;
+
+ if (Ended()) {
+ return;
+ }
+
+ mTrack->SetDisabledTrackMode(mEnabled ? DisabledTrackMode::ENABLED
+ : DisabledTrackMode::SILENCE_BLACK);
+ NotifyEnabledChanged();
+}
+
+void MediaStreamTrack::Stop() {
+ LOG(LogLevel::Info, ("MediaStreamTrack %p Stop()", this));
+
+ if (Ended()) {
+ LOG(LogLevel::Warning, ("MediaStreamTrack %p Already ended", this));
+ return;
+ }
+
+ SetReadyState(MediaStreamTrackState::Ended);
+
+ NotifyEnded();
+}
+
+void MediaStreamTrack::GetConstraints(dom::MediaTrackConstraints& aResult) {
+ aResult = mConstraints;
+}
+
+void MediaStreamTrack::GetSettings(dom::MediaTrackSettings& aResult,
+ CallerType aCallerType) {
+ GetSource().GetSettings(aResult);
+
+ // Spoof values when privacy.resistFingerprinting is true.
+ nsIGlobalObject* global = mWindow ? mWindow->AsGlobal() : nullptr;
+ if (!nsContentUtils::ShouldResistFingerprinting(
+ aCallerType, global, RFPTarget::StreamVideoFacingMode)) {
+ return;
+ }
+ if (aResult.mFacingMode.WasPassed()) {
+ aResult.mFacingMode.Value().AssignASCII(
+ VideoFacingModeEnumValues::GetString(VideoFacingModeEnum::User));
+ }
+}
+
+already_AddRefed<Promise> MediaStreamTrack::ApplyConstraints(
+ const MediaTrackConstraints& aConstraints, CallerType aCallerType,
+ ErrorResult& aRv) {
+ if (MOZ_LOG_TEST(gMediaStreamTrackLog, LogLevel::Info)) {
+ nsString str;
+ aConstraints.ToJSON(str);
+
+ LOG(LogLevel::Info, ("MediaStreamTrack %p ApplyConstraints() with "
+ "constraints %s",
+ this, NS_ConvertUTF16toUTF8(str).get()));
+ }
+
+ nsIGlobalObject* go = mWindow ? mWindow->AsGlobal() : nullptr;
+
+ RefPtr<Promise> promise = Promise::Create(go, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Forward constraints to the source.
+ //
+ // After GetSource().ApplyConstraints succeeds (after it's been to
+ // media-thread and back), and no sooner, do we set mConstraints to the newly
+ // applied values.
+
+ // Keep a reference to this, to make sure it's still here when we get back.
+ RefPtr<MediaStreamTrack> self(this);
+ GetSource()
+ .ApplyConstraints(aConstraints, aCallerType)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [this, self, promise, aConstraints](bool aDummy) {
+ if (!mWindow || !mWindow->IsCurrentInnerWindow()) {
+ return; // Leave Promise pending after navigation by design.
+ }
+ mConstraints = aConstraints;
+ promise->MaybeResolve(false);
+ },
+ [this, self, promise](const RefPtr<MediaMgrError>& aError) {
+ if (!mWindow || !mWindow->IsCurrentInnerWindow()) {
+ return; // Leave Promise pending after navigation by design.
+ }
+ promise->MaybeReject(
+ MakeRefPtr<MediaStreamError>(mWindow, *aError));
+ });
+ return promise.forget();
+}
+
+ProcessedMediaTrack* MediaStreamTrack::GetTrack() const {
+ MOZ_DIAGNOSTIC_ASSERT(!Ended());
+ return mTrack;
+}
+
+MediaTrackGraph* MediaStreamTrack::Graph() const {
+ MOZ_DIAGNOSTIC_ASSERT(!Ended());
+ return mTrack->Graph();
+}
+
+MediaTrackGraphImpl* MediaStreamTrack::GraphImpl() const {
+ MOZ_DIAGNOSTIC_ASSERT(!Ended());
+ return mTrack->GraphImpl();
+}
+
+void MediaStreamTrack::SetPrincipal(nsIPrincipal* aPrincipal) {
+ if (aPrincipal == mPrincipal) {
+ return;
+ }
+ mPrincipal = aPrincipal;
+
+ LOG(LogLevel::Info,
+ ("MediaStreamTrack %p principal changed to %p. Now: "
+ "null=%d, codebase=%d, expanded=%d, system=%d",
+ this, mPrincipal.get(), mPrincipal->GetIsNullPrincipal(),
+ mPrincipal->GetIsContentPrincipal(),
+ mPrincipal->GetIsExpandedPrincipal(), mPrincipal->IsSystemPrincipal()));
+ for (PrincipalChangeObserver<MediaStreamTrack>* observer :
+ mPrincipalChangeObservers) {
+ observer->PrincipalChanged(this);
+ }
+}
+
+void MediaStreamTrack::PrincipalChanged() {
+ mPendingPrincipal = GetSource().GetPrincipal();
+ nsCOMPtr<nsIPrincipal> newPrincipal = mPrincipal;
+ LOG(LogLevel::Info, ("MediaStreamTrack %p Principal changed on main thread "
+ "to %p (pending). Combining with existing principal %p.",
+ this, mPendingPrincipal.get(), mPrincipal.get()));
+ if (nsContentUtils::CombineResourcePrincipals(&newPrincipal,
+ mPendingPrincipal)) {
+ SetPrincipal(newPrincipal);
+ }
+}
+
+void MediaStreamTrack::NotifyPrincipalHandleChanged(
+ const PrincipalHandle& aNewPrincipalHandle) {
+ PrincipalHandle handle(aNewPrincipalHandle);
+ LOG(LogLevel::Info, ("MediaStreamTrack %p principalHandle changed on "
+ "MediaTrackGraph thread to %p. Current principal: %p, "
+ "pending: %p",
+ this, GetPrincipalFromHandle(handle), mPrincipal.get(),
+ mPendingPrincipal.get()));
+ if (PrincipalHandleMatches(handle, mPendingPrincipal)) {
+ SetPrincipal(mPendingPrincipal);
+ mPendingPrincipal = nullptr;
+ }
+}
+
+void MediaStreamTrack::MutedChanged(bool aNewState) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ /**
+ * 4.3.1 Life-cycle and Media flow - Media flow
+ * To set a track's muted state to newState, the User Agent MUST run the
+ * following steps:
+ * 1. Let track be the MediaStreamTrack in question.
+ * 2. Set track's muted attribute to newState.
+ * 3. If newState is true let eventName be mute, otherwise unmute.
+ * 4. Fire a simple event named eventName on track.
+ */
+
+ if (mMuted == aNewState) {
+ return;
+ }
+
+ LOG(LogLevel::Info,
+ ("MediaStreamTrack %p became %s", this, aNewState ? "muted" : "unmuted"));
+
+ mMuted = aNewState;
+
+ if (Ended()) {
+ return;
+ }
+
+ nsString eventName = aNewState ? u"mute"_ns : u"unmute"_ns;
+ DispatchTrustedEvent(eventName);
+}
+
+void MediaStreamTrack::NotifyEnded() {
+ MOZ_ASSERT(mReadyState == MediaStreamTrackState::Ended);
+
+ for (const auto& consumer : mConsumers.Clone()) {
+ if (consumer) {
+ consumer->NotifyEnded(this);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
+ mConsumers.RemoveElement(consumer);
+ }
+ }
+}
+
+void MediaStreamTrack::NotifyEnabledChanged() {
+ GetSource().SinkEnabledStateChanged();
+
+ for (const auto& consumer : mConsumers.Clone()) {
+ if (consumer) {
+ consumer->NotifyEnabledChanged(this, Enabled());
+ } else {
+ MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
+ mConsumers.RemoveElement(consumer);
+ }
+ }
+}
+
+bool MediaStreamTrack::AddPrincipalChangeObserver(
+ PrincipalChangeObserver<MediaStreamTrack>* aObserver) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ mPrincipalChangeObservers.AppendElement(aObserver);
+ return true;
+}
+
+bool MediaStreamTrack::RemovePrincipalChangeObserver(
+ PrincipalChangeObserver<MediaStreamTrack>* aObserver) {
+ return mPrincipalChangeObservers.RemoveElement(aObserver);
+}
+
+void MediaStreamTrack::AddConsumer(MediaStreamTrackConsumer* aConsumer) {
+ MOZ_ASSERT(!mConsumers.Contains(aConsumer));
+ mConsumers.AppendElement(aConsumer);
+
+ // Remove destroyed consumers for cleanliness
+ while (mConsumers.RemoveElement(nullptr)) {
+ MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
+ }
+}
+
+void MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer* aConsumer) {
+ mConsumers.RemoveElement(aConsumer);
+
+ // Remove destroyed consumers for cleanliness
+ while (mConsumers.RemoveElement(nullptr)) {
+ MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
+ }
+}
+
+already_AddRefed<MediaStreamTrack> MediaStreamTrack::Clone() {
+ RefPtr<MediaStreamTrack> newTrack = CloneInternal();
+ newTrack->SetEnabled(Enabled());
+ newTrack->SetMuted(Muted());
+ return newTrack.forget();
+}
+
+void MediaStreamTrack::SetReadyState(MediaStreamTrackState aState) {
+ MOZ_ASSERT(!(mReadyState == MediaStreamTrackState::Ended &&
+ aState == MediaStreamTrackState::Live),
+ "We don't support overriding the ready state from ended to live");
+
+ if (Ended()) {
+ return;
+ }
+
+ if (mReadyState == MediaStreamTrackState::Live &&
+ aState == MediaStreamTrackState::Ended) {
+ if (mSource) {
+ mSource->UnregisterSink(mSink.get());
+ }
+ if (mMTGListener) {
+ RemoveListener(mMTGListener);
+ }
+ if (mPort) {
+ mPort->Destroy();
+ }
+ if (mTrack) {
+ mTrack->Destroy();
+ }
+ mPort = nullptr;
+ mTrack = nullptr;
+ mMTGListener = nullptr;
+ }
+
+ mReadyState = aState;
+}
+
+void MediaStreamTrack::OverrideEnded() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (Ended()) {
+ return;
+ }
+
+ LOG(LogLevel::Info, ("MediaStreamTrack %p ended", this));
+
+ SetReadyState(MediaStreamTrackState::Ended);
+
+ NotifyEnded();
+
+ DispatchTrustedEvent(u"ended"_ns);
+}
+
+void MediaStreamTrack::AddListener(MediaTrackListener* aListener) {
+ LOG(LogLevel::Debug,
+ ("MediaStreamTrack %p adding listener %p", this, aListener));
+ mTrackListeners.AppendElement(aListener);
+
+ if (Ended()) {
+ return;
+ }
+ mTrack->AddListener(aListener);
+}
+
+void MediaStreamTrack::RemoveListener(MediaTrackListener* aListener) {
+ LOG(LogLevel::Debug,
+ ("MediaStreamTrack %p removing listener %p", this, aListener));
+ mTrackListeners.RemoveElement(aListener);
+
+ if (Ended()) {
+ return;
+ }
+ mTrack->RemoveListener(aListener);
+}
+
+void MediaStreamTrack::AddDirectListener(DirectMediaTrackListener* aListener) {
+ LOG(LogLevel::Debug, ("MediaStreamTrack %p (%s) adding direct listener %p to "
+ "track %p",
+ this, AsAudioStreamTrack() ? "audio" : "video",
+ aListener, mTrack.get()));
+ mDirectTrackListeners.AppendElement(aListener);
+
+ if (Ended()) {
+ return;
+ }
+ mTrack->AddDirectListener(aListener);
+}
+
+void MediaStreamTrack::RemoveDirectListener(
+ DirectMediaTrackListener* aListener) {
+ LOG(LogLevel::Debug,
+ ("MediaStreamTrack %p removing direct listener %p from track %p", this,
+ aListener, mTrack.get()));
+ mDirectTrackListeners.RemoveElement(aListener);
+
+ if (Ended()) {
+ return;
+ }
+ mTrack->RemoveDirectListener(aListener);
+}
+
+already_AddRefed<MediaInputPort> MediaStreamTrack::ForwardTrackContentsTo(
+ ProcessedMediaTrack* aTrack) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(aTrack);
+ return aTrack->AllocateInputPort(mTrack);
+}
+
+} // namespace mozilla::dom