summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/jsapi/RTCRtpSender.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webrtc/jsapi/RTCRtpSender.cpp')
-rw-r--r--dom/media/webrtc/jsapi/RTCRtpSender.cpp1654
1 files changed, 1654 insertions, 0 deletions
diff --git a/dom/media/webrtc/jsapi/RTCRtpSender.cpp b/dom/media/webrtc/jsapi/RTCRtpSender.cpp
new file mode 100644
index 0000000000..568a83e8d1
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCRtpSender.cpp
@@ -0,0 +1,1654 @@
+/* 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 "RTCRtpSender.h"
+#include "transport/logging.h"
+#include "mozilla/dom/MediaStreamTrack.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "nsPIDOMWindow.h"
+#include "nsString.h"
+#include "mozilla/dom/VideoStreamTrack.h"
+#include "jsep/JsepTransceiver.h"
+#include "mozilla/dom/RTCRtpSenderBinding.h"
+#include "RTCStatsReport.h"
+#include "mozilla/Preferences.h"
+#include "RTCRtpTransceiver.h"
+#include "PeerConnectionImpl.h"
+#include "libwebrtcglue/AudioConduit.h"
+#include <vector>
+#include "call/call.h"
+
+namespace mozilla::dom {
+
+LazyLogModule gSenderLog("RTCRtpSender");
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpSender)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RTCRtpSender)
+ // We do not do anything here, we wait for BreakCycles to be called
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RTCRtpSender)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mPc, mSenderTrack, mTransceiver,
+ mStreams, mDtmf)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpSender)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpSender)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpSender)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+#define INIT_CANONICAL(name, val) \
+ name(AbstractThread::MainThread(), val, "RTCRtpSender::" #name " (Canonical)")
+
+RTCRtpSender::RTCRtpSender(nsPIDOMWindowInner* aWindow, PeerConnectionImpl* aPc,
+ MediaTransportHandler* aTransportHandler,
+ AbstractThread* aCallThread,
+ nsISerialEventTarget* aStsThread,
+ MediaSessionConduit* aConduit,
+ dom::MediaStreamTrack* aTrack,
+ const Sequence<RTCRtpEncodingParameters>& aEncodings,
+ RTCRtpTransceiver* aTransceiver)
+ : mWatchManager(this, AbstractThread::MainThread()),
+ mWindow(aWindow),
+ mPc(aPc),
+ mSenderTrack(aTrack),
+ mTransportHandler(aTransportHandler),
+ mTransceiver(aTransceiver),
+ INIT_CANONICAL(mSsrcs, Ssrcs()),
+ INIT_CANONICAL(mVideoRtxSsrcs, Ssrcs()),
+ INIT_CANONICAL(mLocalRtpExtensions, RtpExtList()),
+ INIT_CANONICAL(mAudioCodec, Nothing()),
+ INIT_CANONICAL(mVideoCodec, Nothing()),
+ INIT_CANONICAL(mVideoRtpRtcpConfig, Nothing()),
+ INIT_CANONICAL(mVideoCodecMode, webrtc::VideoCodecMode::kRealtimeVideo),
+ INIT_CANONICAL(mCname, std::string()),
+ INIT_CANONICAL(mTransmitting, false) {
+ mPipeline = new MediaPipelineTransmit(
+ mPc->GetHandle(), aTransportHandler, aCallThread, aStsThread,
+ aConduit->type() == MediaSessionConduit::VIDEO, aConduit);
+ mPipeline->InitControl(this);
+
+ if (aConduit->type() == MediaSessionConduit::AUDIO) {
+ mDtmf = new RTCDTMFSender(aWindow, mTransceiver);
+ }
+ mPipeline->SetTrack(mSenderTrack);
+
+ mozilla::glean::rtcrtpsender::count.Add(1);
+
+ if (mPc->ShouldAllowOldSetParameters()) {
+ mAllowOldSetParameters = true;
+ mozilla::glean::rtcrtpsender::count_setparameters_compat.Add(1);
+ }
+
+ if (aEncodings.Length()) {
+ // This sender was created by addTransceiver with sendEncodings.
+ mParameters.mEncodings = aEncodings;
+ mSimulcastEnvelopeSet = true;
+ mozilla::glean::rtcrtpsender::used_sendencodings.AddToNumerator(1);
+ } else {
+ // This sender was created by addTrack, sRD(offer), or addTransceiver
+ // without sendEncodings.
+ RTCRtpEncodingParameters defaultEncoding;
+ defaultEncoding.mActive = true;
+ if (aConduit->type() == MediaSessionConduit::VIDEO) {
+ defaultEncoding.mScaleResolutionDownBy.Construct(1.0f);
+ }
+ Unused << mParameters.mEncodings.AppendElement(defaultEncoding, fallible);
+ UpdateRestorableEncodings(mParameters.mEncodings);
+ MaybeGetJsepRids();
+ }
+
+ if (mDtmf) {
+ mWatchManager.Watch(mTransmitting, &RTCRtpSender::UpdateDtmfSender);
+ }
+}
+
+#undef INIT_CANONICAL
+
+RTCRtpSender::~RTCRtpSender() = default;
+
+JSObject* RTCRtpSender::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return RTCRtpSender_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+RTCDtlsTransport* RTCRtpSender::GetTransport() const {
+ if (!mTransceiver) {
+ return nullptr;
+ }
+ return mTransceiver->GetDtlsTransport();
+}
+
+RTCDTMFSender* RTCRtpSender::GetDtmf() const { return mDtmf; }
+
+already_AddRefed<Promise> RTCRtpSender::GetStats(ErrorResult& aError) {
+ RefPtr<Promise> promise = MakePromise(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ if (NS_WARN_IF(!mPipeline)) {
+ // TODO(bug 1056433): When we stop nulling this out when the PC is closed
+ // (or when the transceiver is stopped), we can remove this code. We
+ // resolve instead of reject in order to make this eventual change in
+ // behavior a little smaller.
+ promise->MaybeResolve(new RTCStatsReport(mWindow));
+ return promise.forget();
+ }
+
+ if (!mSenderTrack) {
+ promise->MaybeResolve(new RTCStatsReport(mWindow));
+ return promise.forget();
+ }
+
+ mTransceiver->ChainToDomPromiseWithCodecStats(GetStatsInternal(), promise);
+ return promise.forget();
+}
+
+nsTArray<RefPtr<dom::RTCStatsPromise>> RTCRtpSender::GetStatsInternal(
+ bool aSkipIceStats) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsTArray<RefPtr<RTCStatsPromise>> promises(2);
+ if (!mSenderTrack || !mPipeline) {
+ return promises;
+ }
+
+ nsAutoString trackName;
+ if (auto track = mPipeline->GetTrack()) {
+ track->GetId(trackName);
+ }
+
+ {
+ // Add bandwidth estimation stats
+ promises.AppendElement(InvokeAsync(
+ mPipeline->mCallThread, __func__,
+ [conduit = mPipeline->mConduit, trackName]() mutable {
+ auto report = MakeUnique<dom::RTCStatsCollection>();
+ Maybe<webrtc::Call::Stats> stats = conduit->GetCallStats();
+ stats.apply([&](const auto aStats) {
+ dom::RTCBandwidthEstimationInternal bw;
+ bw.mTrackIdentifier = trackName;
+ bw.mSendBandwidthBps.Construct(aStats.send_bandwidth_bps / 8);
+ bw.mMaxPaddingBps.Construct(aStats.max_padding_bitrate_bps / 8);
+ bw.mReceiveBandwidthBps.Construct(aStats.recv_bandwidth_bps / 8);
+ bw.mPacerDelayMs.Construct(aStats.pacer_delay_ms);
+ if (aStats.rtt_ms >= 0) {
+ bw.mRttMs.Construct(aStats.rtt_ms);
+ }
+ if (!report->mBandwidthEstimations.AppendElement(std::move(bw),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ });
+ return RTCStatsPromise::CreateAndResolve(std::move(report), __func__);
+ }));
+ }
+
+ promises.AppendElement(InvokeAsync(
+ mPipeline->mCallThread, __func__, [pipeline = mPipeline, trackName] {
+ auto report = MakeUnique<dom::RTCStatsCollection>();
+ auto asAudio = pipeline->mConduit->AsAudioSessionConduit();
+ auto asVideo = pipeline->mConduit->AsVideoSessionConduit();
+
+ nsString kind = asVideo.isNothing() ? u"audio"_ns : u"video"_ns;
+ nsString idstr = kind + u"_"_ns;
+ idstr.AppendInt(static_cast<uint32_t>(pipeline->Level()));
+
+ for (uint32_t ssrc : pipeline->mConduit->GetLocalSSRCs()) {
+ nsString localId = u"outbound_rtp_"_ns + idstr + u"_"_ns;
+ localId.AppendInt(ssrc);
+ nsString remoteId;
+ Maybe<uint16_t> base_seq =
+ pipeline->mConduit->RtpSendBaseSeqFor(ssrc);
+
+ auto constructCommonRemoteInboundRtpStats =
+ [&](RTCRemoteInboundRtpStreamStats& aRemote,
+ const webrtc::ReportBlockData& aRtcpData) {
+ remoteId = u"outbound_rtcp_"_ns + idstr + u"_"_ns;
+ remoteId.AppendInt(ssrc);
+ aRemote.mTimestamp.Construct(
+ RTCStatsTimestamp::FromNtp(
+ pipeline->GetTimestampMaker(),
+ webrtc::Timestamp::Micros(
+ aRtcpData.report_block_timestamp_utc_us()) +
+ webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970))
+ .ToDom());
+ aRemote.mId.Construct(remoteId);
+ aRemote.mType.Construct(RTCStatsType::Remote_inbound_rtp);
+ aRemote.mSsrc = ssrc;
+ aRemote.mKind = kind;
+ aRemote.mMediaType.Construct(
+ kind); // mediaType is the old name for kind.
+ aRemote.mLocalId.Construct(localId);
+ if (base_seq) {
+ if (aRtcpData.report_block()
+ .extended_highest_sequence_number < *base_seq) {
+ aRemote.mPacketsReceived.Construct(0);
+ } else {
+ aRemote.mPacketsReceived.Construct(
+ aRtcpData.report_block()
+ .extended_highest_sequence_number -
+ aRtcpData.report_block().packets_lost - *base_seq + 1);
+ }
+ }
+ };
+
+ auto constructCommonOutboundRtpStats =
+ [&](RTCOutboundRtpStreamStats& aLocal) {
+ aLocal.mSsrc = ssrc;
+ aLocal.mTimestamp.Construct(
+ pipeline->GetTimestampMaker().GetNow().ToDom());
+ aLocal.mId.Construct(localId);
+ aLocal.mType.Construct(RTCStatsType::Outbound_rtp);
+ aLocal.mKind = kind;
+ aLocal.mMediaType.Construct(
+ kind); // mediaType is the old name for kind.
+ if (remoteId.Length()) {
+ aLocal.mRemoteId.Construct(remoteId);
+ }
+ };
+
+ asAudio.apply([&](auto& aConduit) {
+ Maybe<webrtc::AudioSendStream::Stats> audioStats =
+ aConduit->GetSenderStats();
+ if (audioStats.isNothing()) {
+ return;
+ }
+
+ if (audioStats->packets_sent == 0) {
+ // By spec: "The lifetime of all RTP monitored objects starts
+ // when the RTP stream is first used: When the first RTP packet
+ // is sent or received on the SSRC it represents"
+ return;
+ }
+
+ // First, fill in remote stat with rtcp receiver data, if present.
+ // ReceiverReports have less information than SenderReports, so fill
+ // in what we can.
+ Maybe<webrtc::ReportBlockData> reportBlockData;
+ {
+ if (const auto remoteSsrc = aConduit->GetRemoteSSRC();
+ remoteSsrc) {
+ for (auto& data : audioStats->report_block_datas) {
+ if (data.report_block().source_ssrc == ssrc &&
+ data.report_block().sender_ssrc == *remoteSsrc) {
+ reportBlockData.emplace(data);
+ break;
+ }
+ }
+ }
+ }
+ reportBlockData.apply([&](auto& aReportBlockData) {
+ RTCRemoteInboundRtpStreamStats remote;
+ constructCommonRemoteInboundRtpStats(remote, aReportBlockData);
+ if (audioStats->jitter_ms >= 0) {
+ remote.mJitter.Construct(audioStats->jitter_ms / 1000.0);
+ }
+ if (audioStats->packets_lost >= 0) {
+ remote.mPacketsLost.Construct(audioStats->packets_lost);
+ }
+ if (audioStats->rtt_ms >= 0) {
+ remote.mRoundTripTime.Construct(
+ static_cast<double>(audioStats->rtt_ms) / 1000.0);
+ }
+ remote.mFractionLost.Construct(audioStats->fraction_lost);
+ remote.mTotalRoundTripTime.Construct(
+ double(aReportBlockData.sum_rtt_ms()) / 1000);
+ remote.mRoundTripTimeMeasurements.Construct(
+ aReportBlockData.num_rtts());
+ if (!report->mRemoteInboundRtpStreamStats.AppendElement(
+ std::move(remote), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ });
+
+ // Then, fill in local side (with cross-link to remote only if
+ // present)
+ RTCOutboundRtpStreamStats local;
+ constructCommonOutboundRtpStats(local);
+ local.mPacketsSent.Construct(audioStats->packets_sent);
+ local.mBytesSent.Construct(audioStats->payload_bytes_sent);
+ local.mNackCount.Construct(
+ audioStats->rtcp_packet_type_counts.nack_packets);
+ local.mHeaderBytesSent.Construct(
+ audioStats->header_and_padding_bytes_sent);
+ local.mRetransmittedPacketsSent.Construct(
+ audioStats->retransmitted_packets_sent);
+ local.mRetransmittedBytesSent.Construct(
+ audioStats->retransmitted_bytes_sent);
+ /*
+ * Potential new stats that are now available upstream.
+ * Note: when we last tried exposing this we were getting
+ * targetBitrate for audio was ending up as 0. We did not
+ * investigate why.
+ local.mTargetBitrate.Construct(audioStats->target_bitrate_bps);
+ */
+ if (!report->mOutboundRtpStreamStats.AppendElement(std::move(local),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ });
+
+ asVideo.apply([&](auto& aConduit) {
+ Maybe<webrtc::VideoSendStream::Stats> videoStats =
+ aConduit->GetSenderStats();
+ if (videoStats.isNothing()) {
+ return;
+ }
+
+ Maybe<webrtc::VideoSendStream::StreamStats> streamStats;
+ auto kv = videoStats->substreams.find(ssrc);
+ if (kv != videoStats->substreams.end()) {
+ streamStats = Some(kv->second);
+ }
+
+ if (!streamStats) {
+ // By spec: "The lifetime of all RTP monitored objects starts
+ // when the RTP stream is first used: When the first RTP packet
+ // is sent or received on the SSRC it represents"
+ return;
+ }
+
+ aConduit->GetAssociatedLocalRtxSSRC(ssrc).apply(
+ [&](const auto rtxSsrc) {
+ auto kv = videoStats->substreams.find(rtxSsrc);
+ if (kv != videoStats->substreams.end()) {
+ streamStats->rtp_stats.Add(kv->second.rtp_stats);
+ }
+ });
+
+ if (streamStats->rtp_stats.first_packet_time_ms == -1) {
+ return;
+ }
+
+ // First, fill in remote stat with rtcp receiver data, if present.
+ // ReceiverReports have less information than SenderReports, so fill
+ // in what we can.
+ if (streamStats->report_block_data) {
+ const webrtc::ReportBlockData& rtcpReportData =
+ *streamStats->report_block_data;
+ RTCRemoteInboundRtpStreamStats remote;
+ remote.mJitter.Construct(
+ static_cast<double>(rtcpReportData.report_block().jitter) /
+ webrtc::kVideoPayloadTypeFrequency);
+ remote.mPacketsLost.Construct(
+ rtcpReportData.report_block().packets_lost);
+ if (rtcpReportData.has_rtt()) {
+ remote.mRoundTripTime.Construct(
+ static_cast<double>(rtcpReportData.last_rtt_ms()) / 1000.0);
+ }
+ constructCommonRemoteInboundRtpStats(remote, rtcpReportData);
+ remote.mTotalRoundTripTime.Construct(
+ streamStats->report_block_data->sum_rtt_ms() / 1000.0);
+ remote.mFractionLost.Construct(
+ static_cast<float>(
+ rtcpReportData.report_block().fraction_lost) /
+ (1 << 8));
+ remote.mRoundTripTimeMeasurements.Construct(
+ streamStats->report_block_data->num_rtts());
+ if (!report->mRemoteInboundRtpStreamStats.AppendElement(
+ std::move(remote), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+
+ // Then, fill in local side (with cross-link to remote only if
+ // present)
+ RTCOutboundRtpStreamStats local;
+ constructCommonOutboundRtpStats(local);
+ local.mPacketsSent.Construct(
+ streamStats->rtp_stats.transmitted.packets);
+ local.mBytesSent.Construct(
+ streamStats->rtp_stats.transmitted.payload_bytes);
+ local.mNackCount.Construct(
+ streamStats->rtcp_packet_type_counts.nack_packets);
+ local.mFirCount.Construct(
+ streamStats->rtcp_packet_type_counts.fir_packets);
+ local.mPliCount.Construct(
+ streamStats->rtcp_packet_type_counts.pli_packets);
+ local.mFramesEncoded.Construct(streamStats->frames_encoded);
+ if (streamStats->qp_sum) {
+ local.mQpSum.Construct(*streamStats->qp_sum);
+ }
+ local.mHeaderBytesSent.Construct(
+ streamStats->rtp_stats.transmitted.header_bytes +
+ streamStats->rtp_stats.transmitted.padding_bytes);
+ local.mRetransmittedPacketsSent.Construct(
+ streamStats->rtp_stats.retransmitted.packets);
+ local.mRetransmittedBytesSent.Construct(
+ streamStats->rtp_stats.retransmitted.payload_bytes);
+ local.mTotalEncodedBytesTarget.Construct(
+ videoStats->total_encoded_bytes_target);
+ local.mFrameWidth.Construct(streamStats->width);
+ local.mFrameHeight.Construct(streamStats->height);
+ local.mFramesSent.Construct(streamStats->frames_encoded);
+ local.mHugeFramesSent.Construct(streamStats->huge_frames_sent);
+ local.mTotalEncodeTime.Construct(
+ double(streamStats->total_encode_time_ms) / 1000.);
+ /*
+ * Potential new stats that are now available upstream.
+ local.mTargetBitrate.Construct(videoStats->target_media_bitrate_bps);
+ */
+ if (!report->mOutboundRtpStreamStats.AppendElement(std::move(local),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ });
+ }
+
+ auto constructCommonMediaSourceStats =
+ [&](RTCMediaSourceStats& aStats) {
+ nsString id = u"mediasource_"_ns + idstr + trackName;
+ aStats.mTimestamp.Construct(
+ pipeline->GetTimestampMaker().GetNow().ToDom());
+ aStats.mId.Construct(id);
+ aStats.mType.Construct(RTCStatsType::Media_source);
+ aStats.mTrackIdentifier = trackName;
+ aStats.mKind = kind;
+ };
+
+ // TODO(bug 1804678): Use RTCAudioSourceStats/RTCVideoSourceStats
+ RTCMediaSourceStats mediaSourceStats;
+ constructCommonMediaSourceStats(mediaSourceStats);
+ if (!report->mMediaSourceStats.AppendElement(
+ std::move(mediaSourceStats), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+
+ return RTCStatsPromise::CreateAndResolve(std::move(report), __func__);
+ }));
+
+ if (!aSkipIceStats && GetJsepTransceiver().mTransport.mComponents) {
+ promises.AppendElement(mTransportHandler->GetIceStats(
+ GetJsepTransceiver().mTransport.mTransportId,
+ mPipeline->GetTimestampMaker().GetNow().ToDom()));
+ }
+
+ return promises;
+}
+
+void RTCRtpSender::GetCapabilities(const GlobalObject&, const nsAString& aKind,
+ Nullable<dom::RTCRtpCapabilities>& aResult) {
+ PeerConnectionImpl::GetCapabilities(aKind, aResult, sdp::Direction::kSend);
+}
+
+void RTCRtpSender::WarnAboutBadSetParameters(const nsCString& aError) {
+ nsCString warning(
+ "WARNING! Invalid setParameters call detected! The good news? Firefox "
+ "supports sendEncodings in addTransceiver now, so we ask that you switch "
+ "over to using the parameters code you use for other browsers. Thank you "
+ "for your patience and support. The specific error was: ");
+ warning += aError;
+ mPc->SendWarningToConsole(warning);
+}
+
+nsCString RTCRtpSender::GetEffectiveTLDPlus1() const {
+ return mPc->GetEffectiveTLDPlus1();
+}
+
+already_AddRefed<Promise> RTCRtpSender::SetParameters(
+ const dom::RTCRtpSendParameters& aParameters, ErrorResult& aError) {
+ dom::RTCRtpSendParameters paramsCopy(aParameters);
+ // When the setParameters method is called, the user agent MUST run the
+ // following steps:
+ // Let parameters be the method's first argument.
+ // Let sender be the RTCRtpSender object on which setParameters is invoked.
+ // Let transceiver be the RTCRtpTransceiver object associated with sender
+ // (i.e.sender is transceiver.[[Sender]]).
+
+ RefPtr<dom::Promise> p = MakePromise(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (mPc->IsClosed()) {
+ p->MaybeRejectWithInvalidStateError("Peer connection is closed");
+ return p.forget();
+ }
+
+ // If transceiver.[[Stopped]] is true, return a promise rejected with a newly
+ // created InvalidStateError.
+ if (mTransceiver->Stopped()) {
+ p->MaybeRejectWithInvalidStateError("This sender's transceiver is stopped");
+ return p.forget();
+ }
+
+ // If sender.[[LastReturnedParameters]] is null, return a promise rejected
+ // with a newly created InvalidStateError.
+ if (!mLastReturnedParameters.isSome()) {
+ nsCString error(
+ "Cannot call setParameters without first calling getParameters");
+ if (mAllowOldSetParameters) {
+ if (!mHaveWarnedBecauseNoGetParameters) {
+ mHaveWarnedBecauseNoGetParameters = true;
+ mozilla::glean::rtcrtpsender_setparameters::warn_no_getparameters
+ .AddToNumerator(1);
+#ifdef EARLY_BETA_OR_EARLIER
+ mozilla::glean::rtcrtpsender_setparameters::blame_no_getparameters
+ .Get(GetEffectiveTLDPlus1())
+ .Add(1);
+#endif
+ }
+ WarnAboutBadSetParameters(error);
+ } else {
+ if (!mHaveFailedBecauseNoGetParameters) {
+ mHaveFailedBecauseNoGetParameters = true;
+ mozilla::glean::rtcrtpsender_setparameters::fail_no_getparameters
+ .AddToNumerator(1);
+ }
+ p->MaybeRejectWithInvalidStateError(error);
+ return p.forget();
+ }
+ }
+
+ // According to the spec, our consistency checking is based on
+ // [[LastReturnedParameters]], but if we're letting
+ // [[LastReturnedParameters]]==null slide, we still want to do
+ // consistency checking on _something_ so we can warn implementers if they
+ // are messing that up also. Just find something, _anything_, to do that
+ // checking with.
+ // TODO(bug 1803388): Remove this stuff once it is no longer needed.
+ // TODO(bug 1803389): Remove the glean errors once they are no longer needed.
+ Maybe<RTCRtpSendParameters> oldParams;
+ if (mAllowOldSetParameters) {
+ if (mLastReturnedParameters.isSome()) {
+ oldParams = mLastReturnedParameters;
+ } else if (mPendingParameters.isSome()) {
+ oldParams = mPendingParameters;
+ } else {
+ oldParams = Some(mParameters);
+ }
+ MOZ_ASSERT(oldParams.isSome());
+ } else {
+ oldParams = mLastReturnedParameters;
+ }
+ MOZ_ASSERT(oldParams.isSome());
+
+ // Validate parameters by running the following steps:
+ // Let encodings be parameters.encodings.
+ // Let codecs be parameters.codecs.
+ // Let N be the number of RTCRtpEncodingParameters stored in
+ // sender.[[SendEncodings]].
+ // If any of the following conditions are met,
+ // return a promise rejected with a newly created InvalidModificationError:
+
+ bool pendingRidChangeFromCompatMode = false;
+ // encodings.length is different from N.
+ if (paramsCopy.mEncodings.Length() != oldParams->mEncodings.Length()) {
+ nsCString error("Cannot change the number of encodings with setParameters");
+ if (!mAllowOldSetParameters) {
+ if (!mHaveFailedBecauseEncodingCountChange) {
+ mHaveFailedBecauseEncodingCountChange = true;
+ mozilla::glean::rtcrtpsender_setparameters::fail_length_changed
+ .AddToNumerator(1);
+ }
+ p->MaybeRejectWithInvalidModificationError(error);
+ return p.forget();
+ }
+ // Make sure we don't use the old rids in SyncToJsep while we wait for the
+ // queued task below to update mParameters.
+ pendingRidChangeFromCompatMode = true;
+ mSimulcastEnvelopeSet = true;
+ if (!mHaveWarnedBecauseEncodingCountChange) {
+ mHaveWarnedBecauseEncodingCountChange = true;
+ mozilla::glean::rtcrtpsender_setparameters::warn_length_changed
+ .AddToNumerator(1);
+#ifdef EARLY_BETA_OR_EARLIER
+ mozilla::glean::rtcrtpsender_setparameters::blame_length_changed
+ .Get(GetEffectiveTLDPlus1())
+ .Add(1);
+#endif
+ }
+ WarnAboutBadSetParameters(error);
+ } else {
+ // encodings has been re-ordered.
+ for (size_t i = 0; i < paramsCopy.mEncodings.Length(); ++i) {
+ const auto& oldEncoding = oldParams->mEncodings[i];
+ const auto& newEncoding = paramsCopy.mEncodings[i];
+ if (oldEncoding.mRid != newEncoding.mRid) {
+ nsCString error("Cannot change rid, or reorder encodings");
+ if (!mHaveFailedBecauseRidChange) {
+ mHaveFailedBecauseRidChange = true;
+ mozilla::glean::rtcrtpsender_setparameters::fail_rid_changed
+ .AddToNumerator(1);
+ }
+ p->MaybeRejectWithInvalidModificationError(error);
+ return p.forget();
+ }
+ }
+ }
+
+ // TODO(bug 1803388): Handle this in webidl, once we stop allowing the old
+ // setParameters style.
+ if (!paramsCopy.mTransactionId.WasPassed()) {
+ nsCString error("transactionId is not set!");
+ if (!mAllowOldSetParameters) {
+ if (!mHaveFailedBecauseNoTransactionId) {
+ mHaveFailedBecauseNoTransactionId = true;
+ mozilla::glean::rtcrtpsender_setparameters::fail_no_transactionid
+ .AddToNumerator(1);
+ }
+ p->MaybeRejectWithTypeError(error);
+ return p.forget();
+ }
+ if (!mHaveWarnedBecauseNoTransactionId) {
+ mHaveWarnedBecauseNoTransactionId = true;
+ mozilla::glean::rtcrtpsender_setparameters::warn_no_transactionid
+ .AddToNumerator(1);
+#ifdef EARLY_BETA_OR_EARLIER
+ mozilla::glean::rtcrtpsender_setparameters::blame_no_transactionid
+ .Get(GetEffectiveTLDPlus1())
+ .Add(1);
+#endif
+ }
+ WarnAboutBadSetParameters(error);
+ } else if (oldParams->mTransactionId != paramsCopy.mTransactionId) {
+ // Any parameter in parameters is marked as a Read-only parameter (such as
+ // RID) and has a value that is different from the corresponding parameter
+ // value in sender.[[LastReturnedParameters]]. Note that this also applies
+ // to transactionId.
+ nsCString error(
+ "Cannot change transaction id: call getParameters, modify the result, "
+ "and then call setParameters");
+ if (!mAllowOldSetParameters) {
+ if (!mHaveFailedBecauseStaleTransactionId) {
+ mHaveFailedBecauseStaleTransactionId = true;
+ mozilla::glean::rtcrtpsender_setparameters::fail_stale_transactionid
+ .AddToNumerator(1);
+ }
+ p->MaybeRejectWithInvalidModificationError(error);
+ return p.forget();
+ }
+ if (!mHaveWarnedBecauseStaleTransactionId) {
+ mHaveWarnedBecauseStaleTransactionId = true;
+ mozilla::glean::rtcrtpsender_setparameters::warn_stale_transactionid
+ .AddToNumerator(1);
+#ifdef EARLY_BETA_OR_EARLIER
+ mozilla::glean::rtcrtpsender_setparameters::blame_stale_transactionid
+ .Get(GetEffectiveTLDPlus1())
+ .Add(1);
+#endif
+ }
+ WarnAboutBadSetParameters(error);
+ }
+
+ // This could conceivably happen if we are allowing the old setParameters
+ // behavior.
+ if (!paramsCopy.mEncodings.Length()) {
+ nsCString error("Cannot set an empty encodings array");
+ if (!mAllowOldSetParameters) {
+ if (!mHaveFailedBecauseNoEncodings) {
+ mHaveFailedBecauseNoEncodings = true;
+ mozilla::glean::rtcrtpsender_setparameters::fail_no_encodings
+ .AddToNumerator(1);
+ }
+
+ p->MaybeRejectWithInvalidModificationError(error);
+ return p.forget();
+ }
+ // TODO: Add some warning telemetry here
+ WarnAboutBadSetParameters(error);
+ // Just don't do this; it's stupid.
+ paramsCopy.mEncodings = oldParams->mEncodings;
+ }
+
+ // TODO: Verify remaining read-only parameters
+ // headerExtensions (bug 1765851)
+ // rtcp (bug 1765852)
+ // codecs (bug 1534687)
+
+ // CheckAndRectifyEncodings handles the following steps:
+ // If transceiver kind is "audio", remove the scaleResolutionDownBy member
+ // from all encodings that contain one.
+ //
+ // If transceiver kind is "video", and any encoding in encodings contains a
+ // scaleResolutionDownBy member whose value is less than 1.0, return a
+ // promise rejected with a newly created RangeError.
+ //
+ // Verify that each encoding in encodings has a maxFramerate member whose
+ // value is greater than or equal to 0.0. If one of the maxFramerate values
+ // does not meet this requirement, return a promise rejected with a newly
+ // created RangeError.
+ ErrorResult rv;
+ CheckAndRectifyEncodings(paramsCopy.mEncodings, mTransceiver->IsVideo(), rv);
+ if (rv.Failed()) {
+ if (!mHaveFailedBecauseOtherError) {
+ mHaveFailedBecauseOtherError = true;
+ mozilla::glean::rtcrtpsender_setparameters::fail_other.AddToNumerator(1);
+ }
+ p->MaybeReject(std::move(rv));
+ return p.forget();
+ }
+
+ // If transceiver kind is "video", then for each encoding in encodings that
+ // doesn't contain a scaleResolutionDownBy member, add a
+ // scaleResolutionDownBy member with the value 1.0.
+ if (mTransceiver->IsVideo()) {
+ for (auto& encoding : paramsCopy.mEncodings) {
+ if (!encoding.mScaleResolutionDownBy.WasPassed()) {
+ encoding.mScaleResolutionDownBy.Construct(1.0);
+ }
+ }
+ }
+
+ // Let p be a new promise. (see above)
+
+ // In parallel, configure the media stack to use parameters to transmit
+ // sender.[[SenderTrack]].
+ // Right now this is infallible. That may change someday.
+
+ // We need to put this in a member variable, since MaybeUpdateConduit needs it
+ // This also allows PeerConnectionImpl to detect when there is a pending
+ // setParameters, which has implcations for the handling of
+ // setRemoteDescription.
+ mPendingRidChangeFromCompatMode = pendingRidChangeFromCompatMode;
+ mPendingParameters = Some(paramsCopy);
+ uint32_t serialNumber = ++mNumSetParametersCalls;
+ MaybeUpdateConduit();
+
+ // If the media stack is successfully configured with parameters,
+ // queue a task to run the following steps:
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ __func__,
+ [this, self = RefPtr<RTCRtpSender>(this), p, paramsCopy, serialNumber] {
+ // Set sender.[[LastReturnedParameters]] to null.
+ mLastReturnedParameters = Nothing();
+ // Set sender.[[SendEncodings]] to parameters.encodings.
+ mParameters = paramsCopy;
+ UpdateRestorableEncodings(mParameters.mEncodings);
+ // Only clear mPendingParameters if it matches; there could have been
+ // back-to-back calls to setParameters, and we only want to clear this
+ // if no subsequent setParameters is pending.
+ if (serialNumber == mNumSetParametersCalls) {
+ mPendingParameters = Nothing();
+ // Ok, nothing has called SyncToJsep while this async task was
+ // pending. No need for special handling anymore.
+ mPendingRidChangeFromCompatMode = false;
+ }
+ MOZ_ASSERT(mParameters.mEncodings.Length());
+ // Resolve p with undefined.
+ p->MaybeResolveWithUndefined();
+ }));
+
+ // Return p.
+ return p.forget();
+}
+
+// static
+void RTCRtpSender::CheckAndRectifyEncodings(
+ Sequence<RTCRtpEncodingParameters>& aEncodings, bool aVideo,
+ ErrorResult& aRv) {
+ // If any encoding contains a rid member whose value does not conform to the
+ // grammar requirements specified in Section 10 of [RFC8851], throw a
+ // TypeError.
+ for (const auto& encoding : aEncodings) {
+ if (encoding.mRid.WasPassed()) {
+ std::string utf8Rid = NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get();
+ std::string error;
+ if (!SdpRidAttributeList::CheckRidValidity(utf8Rid, &error)) {
+ aRv.ThrowTypeError(nsCString(error));
+ return;
+ }
+ if (utf8Rid.size() > SdpRidAttributeList::kMaxRidLength) {
+ std::ostringstream ss;
+ ss << "Rid can be at most " << SdpRidAttributeList::kMaxRidLength
+ << " characters long (due to internal limitations)";
+ aRv.ThrowTypeError(nsCString(ss.str()));
+ return;
+ }
+ }
+ }
+
+ if (aEncodings.Length() > 1) {
+ // If some but not all encodings contain a rid member, throw a TypeError.
+ // rid must be set if there is more than one encoding
+ // NOTE: Since rid is read-only, and the number of encodings cannot grow,
+ // this should never happen in setParameters.
+ for (const auto& encoding : aEncodings) {
+ if (!encoding.mRid.WasPassed()) {
+ aRv.ThrowTypeError("Missing rid");
+ return;
+ }
+ }
+
+ // If any encoding contains a rid member whose value is the same as that of
+ // a rid contained in another encoding in sendEncodings, throw a TypeError.
+ // NOTE: Since rid is read-only, and the number of encodings cannot grow,
+ // this should never happen in setParameters.
+ std::set<nsString> uniqueRids;
+ for (const auto& encoding : aEncodings) {
+ if (uniqueRids.count(encoding.mRid.Value())) {
+ aRv.ThrowTypeError("Duplicate rid");
+ return;
+ }
+ uniqueRids.insert(encoding.mRid.Value());
+ }
+ }
+ // TODO: ptime/adaptivePtime validation (bug 1733647)
+
+ // If kind is "audio", remove the scaleResolutionDownBy member from all
+ // encodings that contain one.
+ if (!aVideo) {
+ for (auto& encoding : aEncodings) {
+ if (encoding.mScaleResolutionDownBy.WasPassed()) {
+ encoding.mScaleResolutionDownBy.Reset();
+ }
+ if (encoding.mMaxFramerate.WasPassed()) {
+ encoding.mMaxFramerate.Reset();
+ }
+ }
+ }
+
+ // If any encoding contains a scaleResolutionDownBy member whose value is
+ // less than 1.0, throw a RangeError.
+ for (const auto& encoding : aEncodings) {
+ if (encoding.mScaleResolutionDownBy.WasPassed()) {
+ if (encoding.mScaleResolutionDownBy.Value() < 1.0f) {
+ aRv.ThrowRangeError("scaleResolutionDownBy must be >= 1.0");
+ return;
+ }
+ }
+ }
+
+ // Verify that the value of each maxFramerate member in sendEncodings that is
+ // defined is greater than 0.0. If one of the maxFramerate values does not
+ // meet this requirement, throw a RangeError.
+ for (const auto& encoding : aEncodings) {
+ if (encoding.mMaxFramerate.WasPassed()) {
+ if (encoding.mMaxFramerate.Value() < 0.0f) {
+ aRv.ThrowRangeError("maxFramerate must be non-negative");
+ return;
+ }
+ }
+ }
+}
+
+void RTCRtpSender::GetParameters(RTCRtpSendParameters& aParameters) {
+ MOZ_ASSERT(mParameters.mEncodings.Length());
+ // If sender.[[LastReturnedParameters]] is not null, return
+ // sender.[[LastReturnedParameters]], and abort these steps.
+ if (mLastReturnedParameters.isSome()) {
+ aParameters = *mLastReturnedParameters;
+ return;
+ }
+
+ // Let result be a new RTCRtpSendParameters dictionary constructed as follows:
+
+ // transactionId is set to a new unique identifier
+ aParameters.mTransactionId.Construct(mPc->GenerateUUID());
+
+ // encodings is set to the value of the [[SendEncodings]] internal slot.
+ aParameters.mEncodings = mParameters.mEncodings;
+
+ // The headerExtensions sequence is populated based on the header extensions
+ // that have been negotiated for sending
+ // TODO(bug 1765851): We do not support this yet
+ // aParameters.mHeaderExtensions.Construct();
+
+ // codecs is set to the value of the [[SendCodecs]] internal slot
+ // TODO(bug 1534687): We do not support this yet
+
+ // rtcp.cname is set to the CNAME of the associated RTCPeerConnection.
+ // rtcp.reducedSize is set to true if reduced-size RTCP has been negotiated
+ // for sending, and false otherwise.
+ // TODO(bug 1765852): We do not support this yet
+ aParameters.mRtcp.Construct();
+ aParameters.mRtcp.Value().mCname.Construct();
+ aParameters.mRtcp.Value().mReducedSize.Construct(false);
+ aParameters.mHeaderExtensions.Construct();
+ aParameters.mCodecs.Construct();
+
+ // Set sender.[[LastReturnedParameters]] to result.
+ mLastReturnedParameters = Some(aParameters);
+
+ // Queue a task that sets sender.[[LastReturnedParameters]] to null.
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr<RTCRtpSender>(this)] {
+ mLastReturnedParameters = Nothing();
+ }));
+}
+
+bool operator==(const RTCRtpEncodingParameters& a1,
+ const RTCRtpEncodingParameters& a2) {
+ // webidl does not generate types that are equality comparable
+ return a1.mActive == a2.mActive && a1.mFec == a2.mFec &&
+ a1.mMaxBitrate == a2.mMaxBitrate &&
+ a1.mMaxFramerate == a2.mMaxFramerate && a1.mPriority == a2.mPriority &&
+ a1.mRid == a2.mRid && a1.mRtx == a2.mRtx &&
+ a1.mScaleResolutionDownBy == a2.mScaleResolutionDownBy &&
+ a1.mSsrc == a2.mSsrc;
+}
+
+// static
+void RTCRtpSender::ApplyJsEncodingToConduitEncoding(
+ const RTCRtpEncodingParameters& aJsEncoding,
+ VideoCodecConfig::Encoding* aConduitEncoding) {
+ aConduitEncoding->active = aJsEncoding.mActive;
+ if (aJsEncoding.mMaxBitrate.WasPassed()) {
+ aConduitEncoding->constraints.maxBr = aJsEncoding.mMaxBitrate.Value();
+ }
+ if (aJsEncoding.mMaxFramerate.WasPassed()) {
+ aConduitEncoding->constraints.maxFps =
+ Some(aJsEncoding.mMaxFramerate.Value());
+ }
+ if (aJsEncoding.mScaleResolutionDownBy.WasPassed()) {
+ // Optional does not have a valueOr, despite being based on Maybe
+ // :(
+ aConduitEncoding->constraints.scaleDownBy =
+ aJsEncoding.mScaleResolutionDownBy.Value();
+ } else {
+ aConduitEncoding->constraints.scaleDownBy = 1.0f;
+ }
+}
+
+void RTCRtpSender::UpdateRestorableEncodings(
+ const Sequence<RTCRtpEncodingParameters>& aEncodings) {
+ MOZ_ASSERT(aEncodings.Length());
+
+ if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()) {
+ // Once initial negotiation completes, we are no longer allowed to restore
+ // the unicast encoding.
+ mUnicastEncoding.reset();
+ } else if (mParameters.mEncodings.Length() == 1 &&
+ !mParameters.mEncodings[0].mRid.WasPassed()) {
+ // If we have not completed the initial negotiation, and we currently are
+ // ridless unicast, we need to save our unicast encoding in case a
+ // rollback occurs.
+ mUnicastEncoding = Some(mParameters.mEncodings[0]);
+ }
+}
+
+Sequence<RTCRtpEncodingParameters> RTCRtpSender::ToSendEncodings(
+ const std::vector<std::string>& aRids) const {
+ MOZ_ASSERT(!aRids.empty());
+
+ Sequence<RTCRtpEncodingParameters> result;
+ // If sendEncodings is given as input to this algorithm, and is non-empty,
+ // set the [[SendEncodings]] slot to sendEncodings.
+ for (const auto& rid : aRids) {
+ MOZ_ASSERT(!rid.empty());
+ RTCRtpEncodingParameters encoding;
+ encoding.mActive = true;
+ encoding.mRid.Construct(NS_ConvertUTF8toUTF16(rid.c_str()));
+ Unused << result.AppendElement(encoding, fallible);
+ }
+
+ // If sendEncodings is non-empty, set each encoding's scaleResolutionDownBy
+ // to 2^(length of sendEncodings - encoding index - 1).
+ if (mTransceiver->IsVideo()) {
+ double scale = 1.0f;
+ for (auto it = result.rbegin(); it != result.rend(); ++it) {
+ it->mScaleResolutionDownBy.Construct(scale);
+ scale *= 2;
+ }
+ }
+
+ return result;
+}
+
+void RTCRtpSender::MaybeGetJsepRids() {
+ MOZ_ASSERT(!mSimulcastEnvelopeSet);
+ MOZ_ASSERT(mParameters.mEncodings.Length());
+
+ auto jsepRids = GetJsepTransceiver().mSendTrack.GetRids();
+ if (!jsepRids.empty()) {
+ UpdateRestorableEncodings(mParameters.mEncodings);
+ if (jsepRids.size() != 1 || !jsepRids[0].empty()) {
+ // JSEP is using at least one rid. Stomp our single ridless encoding
+ mParameters.mEncodings = ToSendEncodings(jsepRids);
+ }
+ mSimulcastEnvelopeSet = true;
+ mSimulcastEnvelopeSetByJSEP = true;
+ }
+}
+
+Sequence<RTCRtpEncodingParameters> RTCRtpSender::GetMatchingEncodings(
+ const std::vector<std::string>& aRids) const {
+ Sequence<RTCRtpEncodingParameters> result;
+
+ if (!aRids.empty() && !aRids[0].empty()) {
+ // Simulcast, or unicast with rid
+ for (const auto& encoding : mParameters.mEncodings) {
+ for (const auto& rid : aRids) {
+ auto utf16Rid = NS_ConvertUTF8toUTF16(rid.c_str());
+ if (!encoding.mRid.WasPassed() || (utf16Rid == encoding.mRid.Value())) {
+ auto encodingCopy(encoding);
+ if (!encodingCopy.mRid.WasPassed()) {
+ encodingCopy.mRid.Construct(NS_ConvertUTF8toUTF16(rid.c_str()));
+ }
+ Unused << result.AppendElement(encodingCopy, fallible);
+ break;
+ }
+ }
+ }
+ }
+
+ // If we're allowing the old setParameters behavior, we _might_ be able to
+ // get into this situation even if there were rids above. Be extra careful.
+ // Under normal circumstances, this just handles the ridless case.
+ if (!result.Length()) {
+ // Unicast with no specified rid. Restore mUnicastEncoding, if
+ // it exists, otherwise pick the first encoding.
+ if (mUnicastEncoding.isSome()) {
+ Unused << result.AppendElement(*mUnicastEncoding, fallible);
+ } else {
+ Unused << result.AppendElement(mParameters.mEncodings[0], fallible);
+ }
+ }
+
+ return result;
+}
+
+void RTCRtpSender::SetStreams(
+ const Sequence<OwningNonNull<DOMMediaStream>>& aStreams, ErrorResult& aRv) {
+ if (mPc->IsClosed()) {
+ aRv.ThrowInvalidStateError(
+ "Cannot call setStreams if the peer connection is closed");
+ return;
+ }
+
+ SetStreamsImpl(aStreams);
+ mPc->UpdateNegotiationNeeded();
+}
+
+void RTCRtpSender::SetStreamsImpl(
+ const Sequence<OwningNonNull<DOMMediaStream>>& aStreams) {
+ mStreams.Clear();
+ std::set<nsString> ids;
+ for (const auto& stream : aStreams) {
+ nsString id;
+ stream->GetId(id);
+ if (!ids.count(id)) {
+ ids.insert(id);
+ mStreams.AppendElement(stream);
+ }
+ }
+}
+
+void RTCRtpSender::GetStreams(nsTArray<RefPtr<DOMMediaStream>>& aStreams) {
+ aStreams = mStreams.Clone();
+}
+
+class ReplaceTrackOperation final : public PeerConnectionImpl::Operation {
+ public:
+ ReplaceTrackOperation(PeerConnectionImpl* aPc,
+ const RefPtr<RTCRtpTransceiver>& aTransceiver,
+ const RefPtr<MediaStreamTrack>& aTrack,
+ ErrorResult& aError);
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ReplaceTrackOperation,
+ PeerConnectionImpl::Operation)
+
+ private:
+ MOZ_CAN_RUN_SCRIPT
+ RefPtr<dom::Promise> CallImpl(ErrorResult& aError) override;
+ ~ReplaceTrackOperation() = default;
+ RefPtr<RTCRtpTransceiver> mTransceiver;
+ RefPtr<MediaStreamTrack> mNewTrack;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ReplaceTrackOperation,
+ PeerConnectionImpl::Operation, mTransceiver,
+ mNewTrack)
+
+NS_IMPL_ADDREF_INHERITED(ReplaceTrackOperation, PeerConnectionImpl::Operation)
+NS_IMPL_RELEASE_INHERITED(ReplaceTrackOperation, PeerConnectionImpl::Operation)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReplaceTrackOperation)
+NS_INTERFACE_MAP_END_INHERITING(PeerConnectionImpl::Operation)
+
+ReplaceTrackOperation::ReplaceTrackOperation(
+ PeerConnectionImpl* aPc, const RefPtr<RTCRtpTransceiver>& aTransceiver,
+ const RefPtr<MediaStreamTrack>& aTrack, ErrorResult& aError)
+ : PeerConnectionImpl::Operation(aPc, aError),
+ mTransceiver(aTransceiver),
+ mNewTrack(aTrack) {}
+
+RefPtr<dom::Promise> ReplaceTrackOperation::CallImpl(ErrorResult& aError) {
+ RefPtr<RTCRtpSender> sender = mTransceiver->Sender();
+ // If transceiver.[[Stopped]] is true, return a promise rejected with a newly
+ // created InvalidStateError.
+ if (mTransceiver->Stopped()) {
+ RefPtr<dom::Promise> error = sender->MakePromise(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ MOZ_LOG(gSenderLog, LogLevel::Debug,
+ ("%s Cannot call replaceTrack when transceiver is stopped",
+ __FUNCTION__));
+ error->MaybeRejectWithInvalidStateError(
+ "Cannot call replaceTrack when transceiver is stopped");
+ return error;
+ }
+
+ // Let p be a new promise.
+ RefPtr<dom::Promise> p = sender->MakePromise(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (!sender->SeamlessTrackSwitch(mNewTrack)) {
+ MOZ_LOG(gSenderLog, LogLevel::Info,
+ ("%s Could not seamlessly replace track", __FUNCTION__));
+ p->MaybeRejectWithInvalidModificationError(
+ "Could not seamlessly replace track");
+ return p;
+ }
+
+ // Queue a task that runs the following steps:
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ __func__, [p, sender, track = mNewTrack]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ // If connection.[[IsClosed]] is true, abort these steps.
+ // Set sender.[[SenderTrack]] to withTrack.
+ if (sender->SetSenderTrackWithClosedCheck(track)) {
+ // Resolve p with undefined.
+ p->MaybeResolveWithUndefined();
+ }
+ }));
+
+ // Return p.
+ return p;
+}
+
+already_AddRefed<dom::Promise> RTCRtpSender::ReplaceTrack(
+ dom::MediaStreamTrack* aWithTrack, ErrorResult& aError) {
+ // If withTrack is non-null and withTrack.kind differs from the transceiver
+ // kind of transceiver, return a promise rejected with a newly created
+ // TypeError.
+ if (aWithTrack) {
+ nsString newKind;
+ aWithTrack->GetKind(newKind);
+ nsString oldKind;
+ mTransceiver->GetKind(oldKind);
+ if (newKind != oldKind) {
+ RefPtr<dom::Promise> error = MakePromise(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ error->MaybeRejectWithTypeError(
+ "Cannot replaceTrack with a different kind!");
+ return error.forget();
+ }
+ }
+
+ MOZ_LOG(gSenderLog, LogLevel::Debug,
+ ("%s[%s]: %s (%p to %p)", mPc->GetHandle().c_str(), GetMid().c_str(),
+ __FUNCTION__, mSenderTrack.get(), aWithTrack));
+
+ // Return the result of chaining the following steps to connection's
+ // operations chain:
+ RefPtr<PeerConnectionImpl::Operation> op =
+ new ReplaceTrackOperation(mPc, mTransceiver, aWithTrack, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ // Static analysis forces us to use a temporary.
+ auto pc = mPc;
+ return pc->Chain(op, aError);
+}
+
+nsPIDOMWindowInner* RTCRtpSender::GetParentObject() const { return mWindow; }
+
+already_AddRefed<dom::Promise> RTCRtpSender::MakePromise(
+ ErrorResult& aError) const {
+ return mPc->MakePromise(aError);
+}
+
+bool RTCRtpSender::SeamlessTrackSwitch(
+ const RefPtr<MediaStreamTrack>& aWithTrack) {
+ // We do not actually update mSenderTrack here! Spec says that happens in a
+ // queued task after this is done (this happens in
+ // SetSenderTrackWithClosedCheck).
+
+ mPipeline->SetTrack(aWithTrack);
+
+ MaybeUpdateConduit();
+
+ // There may eventually be cases where a renegotiation is necessary to switch.
+ return true;
+}
+
+void RTCRtpSender::SetTrack(const RefPtr<MediaStreamTrack>& aTrack) {
+ // Used for RTCPeerConnection.removeTrack and RTCPeerConnection.addTrack
+ mSenderTrack = aTrack;
+ SeamlessTrackSwitch(aTrack);
+ if (aTrack) {
+ // RFC says (in the section on remote rollback):
+ // However, an RtpTransceiver MUST NOT be removed if a track was attached
+ // to the RtpTransceiver via the addTrack method.
+ mAddTrackCalled = true;
+ }
+}
+
+bool RTCRtpSender::SetSenderTrackWithClosedCheck(
+ const RefPtr<MediaStreamTrack>& aTrack) {
+ if (!mPc->IsClosed()) {
+ mSenderTrack = aTrack;
+ return true;
+ }
+
+ return false;
+}
+
+void RTCRtpSender::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mWatchManager.Shutdown();
+ mPipeline->Shutdown();
+ mPipeline = nullptr;
+}
+
+void RTCRtpSender::BreakCycles() {
+ mWindow = nullptr;
+ mPc = nullptr;
+ mSenderTrack = nullptr;
+ mTransceiver = nullptr;
+ mStreams.Clear();
+ mDtmf = nullptr;
+}
+
+void RTCRtpSender::UpdateTransport() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mHaveSetupTransport) {
+ mPipeline->SetLevel(GetJsepTransceiver().GetLevel());
+ mHaveSetupTransport = true;
+ }
+
+ mPipeline->UpdateTransport_m(GetJsepTransceiver().mTransport.mTransportId,
+ nullptr);
+}
+
+void RTCRtpSender::MaybeUpdateConduit() {
+ // NOTE(pkerr) - the Call API requires the both local_ssrc and remote_ssrc be
+ // set to a non-zero value or the CreateVideo...Stream call will fail.
+ if (NS_WARN_IF(GetJsepTransceiver().mSendTrack.GetSsrcs().empty())) {
+ MOZ_ASSERT(
+ false,
+ "No local ssrcs! This is a bug in the jsep engine, and should never "
+ "happen!");
+ return;
+ }
+
+ if (!mPipeline) {
+ return;
+ }
+
+ bool wasTransmitting = mTransmitting;
+
+ if (mPipeline->mConduit->type() == MediaSessionConduit::VIDEO) {
+ Maybe<VideoConfig> newConfig = GetNewVideoConfig();
+ if (newConfig.isSome()) {
+ ApplyVideoConfig(*newConfig);
+ }
+ } else {
+ Maybe<AudioConfig> newConfig = GetNewAudioConfig();
+ if (newConfig.isSome()) {
+ ApplyAudioConfig(*newConfig);
+ }
+ }
+
+ if (!mSenderTrack && !wasTransmitting && mTransmitting) {
+ MOZ_LOG(gSenderLog, LogLevel::Debug,
+ ("%s[%s]: %s Starting transmit conduit without send track!",
+ mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__));
+ }
+}
+
+void RTCRtpSender::SyncFromJsep(const JsepTransceiver& aJsepTransceiver) {
+ if (!mSimulcastEnvelopeSet) {
+ // JSEP is establishing the simulcast envelope for the first time, right now
+ // This is the addTrack (or addTransceiver without sendEncodings) case.
+ MaybeGetJsepRids();
+ } else if (!aJsepTransceiver.mSendTrack.GetNegotiatedDetails() ||
+ !aJsepTransceiver.mSendTrack.IsInHaveRemote()) {
+ // Spec says that we do not update our encodings until we're in stable,
+ // _unless_ this is the first negotiation.
+ std::vector<std::string> rids = aJsepTransceiver.mSendTrack.GetRids();
+ if (mSimulcastEnvelopeSetByJSEP && rids.empty()) {
+ // JSEP previously set the simulcast envelope, but now it has no opinion
+ // regarding unicast/simulcast. This can only happen on rollback of the
+ // initial remote offer.
+ mParameters.mEncodings = GetMatchingEncodings(rids);
+ MOZ_ASSERT(mParameters.mEncodings.Length());
+ mSimulcastEnvelopeSetByJSEP = false;
+ mSimulcastEnvelopeSet = false;
+ } else if (!rids.empty()) {
+ // JSEP has an opinion on the simulcast envelope, which trumps anything
+ // we have already.
+ mParameters.mEncodings = GetMatchingEncodings(rids);
+ MOZ_ASSERT(mParameters.mEncodings.Length());
+ }
+ }
+
+ MaybeUpdateConduit();
+}
+
+void RTCRtpSender::SyncToJsep(JsepTransceiver& aJsepTransceiver) const {
+ std::vector<std::string> streamIds;
+ for (const auto& stream : mStreams) {
+ nsString wideStreamId;
+ stream->GetId(wideStreamId);
+ std::string streamId = NS_ConvertUTF16toUTF8(wideStreamId).get();
+ MOZ_ASSERT(!streamId.empty());
+ streamIds.push_back(streamId);
+ }
+
+ aJsepTransceiver.mSendTrack.UpdateStreamIds(streamIds);
+
+ if (mSimulcastEnvelopeSet) {
+ std::vector<std::string> rids;
+ Maybe<RTCRtpSendParameters> parameters;
+ if (mPendingRidChangeFromCompatMode) {
+ // *sigh* If we have just let a setParameters change our rids, but we have
+ // not yet updated mParameters because the queued task hasn't run yet,
+ // we want to set the _new_ rids on the JsepTrack. So, we are forced to
+ // grab them from mPendingParameters.
+ parameters = mPendingParameters;
+ } else {
+ parameters = Some(mParameters);
+ }
+ for (const auto& encoding : parameters->mEncodings) {
+ if (encoding.mRid.WasPassed()) {
+ rids.push_back(NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get());
+ } else {
+ rids.push_back("");
+ }
+ }
+
+ aJsepTransceiver.mSendTrack.SetRids(rids);
+ }
+
+ if (mTransceiver->IsVideo()) {
+ aJsepTransceiver.mSendTrack.SetMaxEncodings(webrtc::kMaxSimulcastStreams);
+ } else {
+ aJsepTransceiver.mSendTrack.SetMaxEncodings(1);
+ }
+
+ if (mAddTrackCalled) {
+ aJsepTransceiver.SetOnlyExistsBecauseOfSetRemote(false);
+ }
+}
+
+Maybe<RTCRtpSender::VideoConfig> RTCRtpSender::GetNewVideoConfig() {
+ // It is possible for SDP to signal that there is a send track, but there not
+ // actually be a send track, according to the specification; all that needs to
+ // happen is for the transceiver to be configured to send...
+ if (!GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()) {
+ return Nothing();
+ }
+
+ VideoConfig oldConfig;
+ oldConfig.mSsrcs = mSsrcs;
+ oldConfig.mLocalRtpExtensions = mLocalRtpExtensions;
+ oldConfig.mCname = mCname;
+ oldConfig.mTransmitting = mTransmitting;
+ oldConfig.mVideoRtxSsrcs = mVideoRtxSsrcs;
+ oldConfig.mVideoCodec = mVideoCodec;
+ oldConfig.mVideoRtpRtcpConfig = mVideoRtpRtcpConfig;
+ oldConfig.mVideoCodecMode = mVideoCodecMode;
+
+ VideoConfig newConfig(oldConfig);
+
+ UpdateBaseConfig(&newConfig);
+
+ newConfig.mVideoRtxSsrcs = GetJsepTransceiver().mSendTrack.GetRtxSsrcs();
+
+ const JsepTrackNegotiatedDetails details(
+ *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails());
+
+ if (mSenderTrack) {
+ RefPtr<mozilla::dom::VideoStreamTrack> videotrack =
+ mSenderTrack->AsVideoStreamTrack();
+
+ if (!videotrack) {
+ MOZ_CRASH(
+ "In ConfigureVideoCodecMode, mSenderTrack is not video! This should "
+ "never happen!");
+ }
+
+ dom::MediaSourceEnum source = videotrack->GetSource().GetMediaSource();
+ switch (source) {
+ case dom::MediaSourceEnum::Browser:
+ case dom::MediaSourceEnum::Screen:
+ case dom::MediaSourceEnum::Window:
+ case dom::MediaSourceEnum::Application:
+ newConfig.mVideoCodecMode = webrtc::VideoCodecMode::kScreensharing;
+ break;
+
+ case dom::MediaSourceEnum::Camera:
+ case dom::MediaSourceEnum::Other:
+ // Other is used by canvas capture, which we treat as realtime video.
+ // This seems debatable, but we've been doing it this way for a long
+ // time, so this is likely fine.
+ newConfig.mVideoCodecMode = webrtc::VideoCodecMode::kRealtimeVideo;
+ break;
+
+ case dom::MediaSourceEnum::Microphone:
+ case dom::MediaSourceEnum::AudioCapture:
+ case dom::MediaSourceEnum::EndGuard_:
+ MOZ_ASSERT(false);
+ break;
+ }
+ }
+
+ std::vector<VideoCodecConfig> configs;
+ RTCRtpTransceiver::NegotiatedDetailsToVideoCodecConfigs(details, &configs);
+
+ if (configs.empty()) {
+ // TODO: Are we supposed to plumb this error back to JS? This does not
+ // seem like a failure to set an answer, it just means that codec
+ // negotiation failed. For now, we're just doing the same thing we do
+ // if negotiation as a whole failed.
+ MOZ_LOG(gSenderLog, LogLevel::Error,
+ ("%s[%s]: %s No video codecs were negotiated (send).",
+ mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__));
+ return Nothing();
+ }
+
+ newConfig.mVideoCodec = Some(configs[0]);
+ // Spec says that we start using new parameters right away, _before_ we
+ // update the parameters that are visible to JS (ie; mParameters).
+ const RTCRtpSendParameters& parameters =
+ mPendingParameters.isSome() ? *mPendingParameters : mParameters;
+ for (VideoCodecConfig::Encoding& conduitEncoding :
+ newConfig.mVideoCodec->mEncodings) {
+ for (const RTCRtpEncodingParameters& jsEncoding : parameters.mEncodings) {
+ std::string rid;
+ if (jsEncoding.mRid.WasPassed()) {
+ rid = NS_ConvertUTF16toUTF8(jsEncoding.mRid.Value()).get();
+ }
+ if (conduitEncoding.rid == rid) {
+ ApplyJsEncodingToConduitEncoding(jsEncoding, &conduitEncoding);
+ break;
+ }
+ }
+ }
+
+ newConfig.mVideoRtpRtcpConfig = Some(details.GetRtpRtcpConfig());
+
+ if (newConfig == oldConfig) {
+ MOZ_LOG(gSenderLog, LogLevel::Debug,
+ ("%s[%s]: %s No change in video config", mPc->GetHandle().c_str(),
+ GetMid().c_str(), __FUNCTION__));
+ return Nothing();
+ }
+
+ if (newConfig.mVideoCodec.isSome()) {
+ MOZ_ASSERT(newConfig.mSsrcs.size() ==
+ newConfig.mVideoCodec->mEncodings.size());
+ }
+ return Some(newConfig);
+}
+
+Maybe<RTCRtpSender::AudioConfig> RTCRtpSender::GetNewAudioConfig() {
+ AudioConfig oldConfig;
+ oldConfig.mSsrcs = mSsrcs;
+ oldConfig.mLocalRtpExtensions = mLocalRtpExtensions;
+ oldConfig.mCname = mCname;
+ oldConfig.mTransmitting = mTransmitting;
+ oldConfig.mAudioCodec = mAudioCodec;
+
+ AudioConfig newConfig(oldConfig);
+
+ UpdateBaseConfig(&newConfig);
+
+ if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails() &&
+ GetJsepTransceiver().mSendTrack.GetActive()) {
+ const auto& details(
+ *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails());
+
+ std::vector<AudioCodecConfig> configs;
+ RTCRtpTransceiver::NegotiatedDetailsToAudioCodecConfigs(details, &configs);
+ if (configs.empty()) {
+ // TODO: Are we supposed to plumb this error back to JS? This does not
+ // seem like a failure to set an answer, it just means that codec
+ // negotiation failed. For now, we're just doing the same thing we do
+ // if negotiation as a whole failed.
+ MOZ_LOG(gSenderLog, LogLevel::Error,
+ ("%s[%s]: %s No audio codecs were negotiated (send)",
+ mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__));
+ return Nothing();
+ }
+
+ std::vector<AudioCodecConfig> dtmfConfigs;
+ std::copy_if(
+ configs.begin(), configs.end(), std::back_inserter(dtmfConfigs),
+ [](const auto& value) { return value.mName == "telephone-event"; });
+
+ const AudioCodecConfig& sendCodec = configs[0];
+
+ if (!dtmfConfigs.empty()) {
+ // There is at least one telephone-event codec.
+ // We primarily choose the codec whose frequency matches the send codec.
+ // Secondarily we choose the one with the lowest frequency.
+ auto dtmfIterator =
+ std::find_if(dtmfConfigs.begin(), dtmfConfigs.end(),
+ [&sendCodec](const auto& dtmfCodec) {
+ return dtmfCodec.mFreq == sendCodec.mFreq;
+ });
+ if (dtmfIterator == dtmfConfigs.end()) {
+ dtmfIterator = std::min_element(
+ dtmfConfigs.begin(), dtmfConfigs.end(),
+ [](const auto& a, const auto& b) { return a.mFreq < b.mFreq; });
+ }
+ MOZ_ASSERT(dtmfIterator != dtmfConfigs.end());
+ newConfig.mDtmfPt = dtmfIterator->mType;
+ newConfig.mDtmfFreq = dtmfIterator->mFreq;
+ }
+
+ newConfig.mAudioCodec = Some(sendCodec);
+ }
+
+ if (newConfig == oldConfig) {
+ MOZ_LOG(gSenderLog, LogLevel::Debug,
+ ("%s[%s]: %s No change in audio config", mPc->GetHandle().c_str(),
+ GetMid().c_str(), __FUNCTION__));
+ return Nothing();
+ }
+
+ return Some(newConfig);
+}
+
+void RTCRtpSender::UpdateBaseConfig(BaseConfig* aConfig) {
+ aConfig->mSsrcs = GetJsepTransceiver().mSendTrack.GetSsrcs();
+ aConfig->mCname = GetJsepTransceiver().mSendTrack.GetCNAME();
+
+ if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails() &&
+ GetJsepTransceiver().mSendTrack.GetActive()) {
+ const auto& details(
+ *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails());
+ {
+ std::vector<webrtc::RtpExtension> extmaps;
+ // @@NG read extmap from track
+ details.ForEachRTPHeaderExtension(
+ [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) {
+ extmaps.emplace_back(extmap.extensionname, extmap.entry);
+ });
+ aConfig->mLocalRtpExtensions = extmaps;
+ }
+ }
+ // RTCRtpTransceiver::IsSending is updated after negotiation completes, in a
+ // queued task (which we may be in right now). Don't use
+ // JsepTrack::GetActive, because that updates before the queued task, which
+ // is too early for some of the things we interact with here (eg;
+ // RTCDTMFSender).
+ aConfig->mTransmitting = mTransceiver->IsSending();
+}
+
+void RTCRtpSender::ApplyVideoConfig(const VideoConfig& aConfig) {
+ if (aConfig.mVideoCodec.isSome()) {
+ MOZ_ASSERT(aConfig.mSsrcs.size() == aConfig.mVideoCodec->mEncodings.size());
+ }
+
+ mSsrcs = aConfig.mSsrcs;
+ mCname = aConfig.mCname;
+ mLocalRtpExtensions = aConfig.mLocalRtpExtensions;
+
+ mVideoRtxSsrcs = aConfig.mVideoRtxSsrcs;
+ mVideoCodec = aConfig.mVideoCodec;
+ mVideoRtpRtcpConfig = aConfig.mVideoRtpRtcpConfig;
+ mVideoCodecMode = aConfig.mVideoCodecMode;
+
+ mTransmitting = aConfig.mTransmitting;
+}
+
+void RTCRtpSender::ApplyAudioConfig(const AudioConfig& aConfig) {
+ mTransmitting = false;
+
+ mSsrcs = aConfig.mSsrcs;
+ mCname = aConfig.mCname;
+ mLocalRtpExtensions = aConfig.mLocalRtpExtensions;
+
+ mAudioCodec = aConfig.mAudioCodec;
+
+ if (aConfig.mDtmfPt >= 0) {
+ mDtmf->SetPayloadType(aConfig.mDtmfPt, aConfig.mDtmfFreq);
+ }
+
+ mTransmitting = aConfig.mTransmitting;
+}
+
+void RTCRtpSender::Stop() {
+ MOZ_ASSERT(mTransceiver->Stopped());
+ mTransmitting = false;
+}
+
+bool RTCRtpSender::HasTrack(const dom::MediaStreamTrack* aTrack) const {
+ if (!mSenderTrack) {
+ return false;
+ }
+
+ if (!aTrack) {
+ return true;
+ }
+
+ return mSenderTrack.get() == aTrack;
+}
+
+RefPtr<MediaPipelineTransmit> RTCRtpSender::GetPipeline() const {
+ return mPipeline;
+}
+
+std::string RTCRtpSender::GetMid() const { return mTransceiver->GetMidAscii(); }
+
+JsepTransceiver& RTCRtpSender::GetJsepTransceiver() {
+ return mTransceiver->GetJsepTransceiver();
+}
+
+void RTCRtpSender::UpdateDtmfSender() {
+ if (!mDtmf) {
+ return;
+ }
+
+ if (mTransmitting) {
+ return;
+ }
+
+ mDtmf->StopPlayout();
+}
+
+} // namespace mozilla::dom
+
+#undef LOGTAG