commit e8a836995bcbb97b125aea03ca317c42a59c3013
parent 2de57563a102ada9e8bbb5066edb8a21a084642b
Author: Andreas Pehrson <apehrson@mozilla.com>
Date: Tue, 11 Nov 2025 08:20:22 +0000
Bug 1771789 - Implement cloning for LocalTrackSource. r=jib
Differential Revision: https://phabricator.services.mozilla.com/D266384
Diffstat:
2 files changed, 182 insertions(+), 3 deletions(-)
diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp
@@ -411,6 +411,13 @@ class DeviceListener : public SupportsWeakPtr {
public:
/**
+ * Synchronously clones this device listener, setting up the device to match
+ * our current device state asynchronously. Settings, constraints and other
+ * main thread state starts applying immediately.
+ */
+ already_AddRefed<DeviceListener> Clone() const;
+
+ /**
* Posts a task to stop the device associated with this DeviceListener and
* notifies the associated window listener that a track was stopped.
*
@@ -480,6 +487,10 @@ class DeviceListener : public SupportsWeakPtr {
return mDeviceState ? mDeviceState->mDevice.get() : nullptr;
}
+ LocalTrackSource* GetTrackSource() const {
+ return mDeviceState ? mDeviceState->mTrackSource.get() : nullptr;
+ }
+
bool Activated() const { return static_cast<bool>(mDeviceState); }
bool Stopped() const { return mStopped; }
@@ -823,7 +834,7 @@ class LocalTrackSource : public MediaStreamTrackSource {
LocalTrackSource(nsIPrincipal* aPrincipal, const nsString& aLabel,
const RefPtr<DeviceListener>& aListener,
MediaSourceEnum aSource, MediaTrack* aTrack,
- RefPtr<PeerIdentity> aPeerIdentity,
+ RefPtr<const PeerIdentity> aPeerIdentity,
TrackingId aTrackingId = TrackingId())
: MediaStreamTrackSource(aPrincipal, aLabel, std::move(aTrackingId)),
mSource(aSource),
@@ -877,6 +888,20 @@ class LocalTrackSource : public MediaStreamTrackSource {
}
}
+ CloneResult Clone() override {
+ if (!mListener) {
+ return {};
+ }
+ RefPtr listener = mListener->Clone();
+ MOZ_ASSERT(listener);
+ if (!listener) {
+ return {};
+ }
+
+ return {.mSource = listener->GetTrackSource(),
+ .mInputTrack = listener->GetTrackSource()->mTrack};
+ }
+
void Disable() override {
if (mListener) {
mListener->SetDeviceEnabled(false);
@@ -1166,6 +1191,11 @@ const TrackingId& LocalMediaDevice::GetTrackingId() const {
return mSource->GetTrackingId();
}
+const dom::MediaTrackConstraints& LocalMediaDevice::Constraints() const {
+ MOZ_ASSERT(MediaManager::IsInMediaThread());
+ return mConstraints;
+}
+
// Threadsafe since mKind and mSource are const.
NS_IMETHODIMP
LocalMediaDevice::GetMediaSource(nsAString& aMediaSource) {
@@ -1190,7 +1220,12 @@ nsresult LocalMediaDevice::Allocate(const MediaTrackConstraints& aConstraints,
return NS_ERROR_FAILURE;
}
- return Source()->Allocate(aConstraints, aPrefs, aWindowID, aOutBadConstraint);
+ nsresult rv =
+ Source()->Allocate(aConstraints, aPrefs, aWindowID, aOutBadConstraint);
+ if (NS_SUCCEEDED(rv)) {
+ mConstraints = aConstraints;
+ }
+ return rv;
}
void LocalMediaDevice::SetTrack(const RefPtr<MediaTrack>& aTrack,
@@ -1234,7 +1269,11 @@ nsresult LocalMediaDevice::Reconfigure(
}
}
}
- return Source()->Reconfigure(aConstraints, aPrefs, aOutBadConstraint);
+ nsresult rv = Source()->Reconfigure(aConstraints, aPrefs, aOutBadConstraint);
+ if (NS_SUCCEEDED(rv)) {
+ mConstraints = aConstraints;
+ }
+ return rv;
}
nsresult LocalMediaDevice::FocusOnSelectedSource() {
@@ -1254,6 +1293,19 @@ nsresult LocalMediaDevice::Deallocate() {
return mSource->Deallocate();
}
+already_AddRefed<LocalMediaDevice> LocalMediaDevice::Clone() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ auto device = MakeRefPtr<LocalMediaDevice>(mRawDevice, mID, mGroupID, mName);
+#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED
+ // The source is normally created on the MediaManager thread. But for cloning,
+ // it ends up being created on main thread. Make sure its owning event target
+ // is set properly.
+ auto* src = device->Source();
+ src->_mOwningThread = mSource->_mOwningThread;
+#endif
+ return device.forget();
+}
+
MediaSourceEnum MediaDevice::GetMediaSource() const { return mMediaSource; }
static const MediaTrackConstraints& GetInvariant(
@@ -4392,6 +4444,125 @@ nsresult DeviceListener::Initialize(PrincipalHandle aPrincipal,
return rv;
}
+already_AddRefed<DeviceListener> DeviceListener::Clone() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ MediaManager* mgr = MediaManager::GetIfExists();
+ if (!mgr) {
+ return nullptr;
+ }
+ if (!mWindowListener) {
+ return nullptr;
+ }
+ auto* thisDevice = GetDevice();
+ if (!thisDevice) {
+ return nullptr;
+ }
+
+ auto* thisTrackSource = GetTrackSource();
+ if (!thisTrackSource) {
+ return nullptr;
+ }
+
+ // See PrepareDOMStream for how a gUM/gDM track is created.
+ RefPtr<MediaTrack> track;
+ MediaTrackGraph* mtg = thisTrackSource->mTrack->Graph();
+ if (const auto source = thisDevice->GetMediaSource();
+ source == dom::MediaSourceEnum::Microphone) {
+#ifdef MOZ_WEBRTC
+ if (thisDevice->IsFake()) {
+ track = mtg->CreateSourceTrack(MediaSegment::AUDIO);
+ } else {
+ track = AudioProcessingTrack::Create(mtg);
+ track->Suspend(); // Microphone source resumes in SetTrack
+ }
+#else
+ track = mtg->CreateSourceTrack(MediaSegment::AUDIO);
+#endif
+ } else if (source == dom::MediaSourceEnum::Camera ||
+ source == dom::MediaSourceEnum::Screen ||
+ source == dom::MediaSourceEnum::Window ||
+ source == dom::MediaSourceEnum::Browser) {
+ track = mtg->CreateSourceTrack(MediaSegment::VIDEO);
+ }
+
+ if (!track) {
+ return nullptr;
+ }
+
+ RefPtr device = thisDevice->Clone();
+ auto listener = MakeRefPtr<DeviceListener>();
+ auto trackSource = MakeRefPtr<LocalTrackSource>(
+ thisTrackSource->GetPrincipal(), thisTrackSource->mLabel, listener,
+ thisTrackSource->mSource, track, thisTrackSource->mPeerIdentity,
+ thisTrackSource->mTrackingId);
+
+ LOG("DeviceListener %p registering clone", this);
+ mWindowListener->Register(listener);
+ LOG("DeviceListener %p activating clone", this);
+ mWindowListener->Activate(listener, device, trackSource,
+ /*aIsAllocated=*/false);
+
+ listener->mDeviceState->mDeviceEnabled = mDeviceState->mDeviceEnabled;
+ listener->mDeviceState->mDeviceMuted = mDeviceState->mDeviceMuted;
+ listener->mDeviceState->mTrackEnabled = mDeviceState->mTrackEnabled;
+ listener->mDeviceState->mTrackEnabledTime = TimeStamp::Now();
+
+ // We have to do an async operation here, even though Clone() is sync.
+ // This is fine because JS will not be able to trigger any operation to run
+ // async on the media thread.
+ LOG("DeviceListener %p allocating clone device %p async", this, device.get());
+ InvokeAsync(
+ mgr->mMediaThread, __func__,
+ [thisDevice = RefPtr(thisDevice), device, prefs = mgr->mPrefs,
+ windowId = mWindowListener->WindowID(), listener,
+ principal = GetPrincipalHandle(), track,
+ startDevice = !listener->mDeviceState->mDeviceMuted &&
+ listener->mDeviceState->mDeviceEnabled] {
+ const char* outBadConstraint{};
+ nsresult rv = device->Source()->Allocate(
+ thisDevice->Constraints(), prefs, windowId, &outBadConstraint);
+ LOG("Allocated clone device %p. rv=%s", device.get(),
+ GetStaticErrorName(rv));
+ if (NS_FAILED(rv)) {
+ return GenericPromise::CreateAndReject(
+ rv, "DeviceListener::Clone failure #1");
+ }
+ rv = listener->Initialize(principal, device, track, startDevice);
+ if (NS_SUCCEEDED(rv)) {
+ return GenericPromise::CreateAndResolve(
+ true, "DeviceListener::Clone success");
+ }
+ return GenericPromise::CreateAndReject(
+ rv, "DeviceListener::Clone failure #2");
+ })
+ ->Then(GetMainThreadSerialEventTarget(), __func__,
+ [listener, device,
+ trackSource](GenericPromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsReject()) {
+ // Allocating/initializing failed. Stopping the device listener
+ // will destroy the MediaStreamTrackSource's MediaTrack, which
+ // will make the MediaStreamTrack's mTrack MediaTrack auto-end
+ // due to lack of inputs. This makes the MediaStreamTrack's
+ // readyState transition to "ended" as expected.
+ LOG("Allocating clone device %p failed. Stopping.",
+ device.get());
+ listener->Stop();
+ return;
+ }
+ listener->mDeviceState->mAllocated = true;
+ if (listener->mDeviceState->mStopped) {
+ MediaManager::Dispatch(NS_NewRunnableFunction(
+ "DeviceListener::Clone::Stop",
+ [device = listener->mDeviceState->mDevice]() {
+ device->Stop();
+ device->Deallocate();
+ }));
+ }
+ });
+
+ return listener.forget();
+}
+
void DeviceListener::Stop() {
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
diff --git a/dom/media/MediaManager.h b/dom/media/MediaManager.h
@@ -148,6 +148,11 @@ class LocalMediaDevice final : public nsIMediaDevice {
nsresult Stop();
nsresult Deallocate();
+ /**
+ * Clones the LocalMediaDevice and sets a cloned source.
+ */
+ already_AddRefed<LocalMediaDevice> Clone() const;
+
void GetSettings(dom::MediaTrackSettings& aOutSettings);
void GetCapabilities(dom::MediaTrackCapabilities& aOutCapabilities);
MediaEngineSource* Source();
@@ -162,6 +167,7 @@ class LocalMediaDevice final : public nsIMediaDevice {
dom::MediaDeviceKind Kind() const { return mRawDevice->mKind; }
bool IsFake() const { return mRawDevice->mIsFake; }
const nsString& RawID() { return mRawDevice->mRawID; }
+ const dom::MediaTrackConstraints& Constraints() const;
private:
virtual ~LocalMediaDevice() = default;
@@ -184,6 +190,8 @@ class LocalMediaDevice final : public nsIMediaDevice {
private:
RefPtr<MediaEngineSource> mSource;
+ // Currently applied constraints. Media thread only.
+ dom::MediaTrackConstraints mConstraints;
};
typedef nsRefPtrHashtable<nsUint64HashKey, GetUserMediaWindowListener>