summaryrefslogtreecommitdiffstats
path: root/media/webrtc/signaling/src/peerconnection
diff options
context:
space:
mode:
Diffstat (limited to 'media/webrtc/signaling/src/peerconnection')
-rw-r--r--media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp1076
-rw-r--r--media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.h82
-rw-r--r--media/webrtc/signaling/src/peerconnection/MediaStreamList.cpp104
-rw-r--r--media/webrtc/signaling/src/peerconnection/MediaStreamList.h54
-rw-r--r--media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp452
-rw-r--r--media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h109
-rw-r--r--media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp4176
-rw-r--r--media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h894
-rw-r--r--media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp1672
-rw-r--r--media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h586
-rw-r--r--media/webrtc/signaling/src/peerconnection/WebrtcGlobalChild.h40
-rw-r--r--media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp1241
-rw-r--r--media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.h56
-rw-r--r--media/webrtc/signaling/src/peerconnection/WebrtcGlobalParent.h53
14 files changed, 10595 insertions, 0 deletions
diff --git a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
new file mode 100644
index 000000000..61c2719cd
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
@@ -0,0 +1,1076 @@
+/* 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 "logging.h"
+#include "nsIGfxInfo.h"
+#include "nsServiceManagerUtils.h"
+
+#include "PeerConnectionImpl.h"
+#include "PeerConnectionMedia.h"
+#include "MediaPipelineFactory.h"
+#include "MediaPipelineFilter.h"
+#include "transportflow.h"
+#include "transportlayer.h"
+#include "transportlayerdtls.h"
+#include "transportlayerice.h"
+
+#include "signaling/src/jsep/JsepTrack.h"
+#include "signaling/src/jsep/JsepTransport.h"
+#include "signaling/src/common/PtrVector.h"
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+#include "MediaStreamTrack.h"
+#include "nsIPrincipal.h"
+#include "nsIDocument.h"
+#include "mozilla/Preferences.h"
+#include "MediaEngine.h"
+#endif
+
+#include "GmpVideoCodec.h"
+#ifdef MOZ_WEBRTC_OMX
+#include "OMXVideoCodec.h"
+#include "OMXCodecWrapper.h"
+#endif
+
+#ifdef MOZ_WEBRTC_MEDIACODEC
+#include "MediaCodecVideoCodec.h"
+#endif
+
+#ifdef MOZILLA_INTERNAL_API
+#include "mozilla/Preferences.h"
+#endif
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+#include "WebrtcGmpVideoCodec.h"
+#endif
+
+#include <stdlib.h>
+
+namespace mozilla {
+
+MOZ_MTLOG_MODULE("MediaPipelineFactory")
+
+static nsresult
+JsepCodecDescToCodecConfig(const JsepCodecDescription& aCodec,
+ AudioCodecConfig** aConfig)
+{
+ MOZ_ASSERT(aCodec.mType == SdpMediaSection::kAudio);
+ if (aCodec.mType != SdpMediaSection::kAudio)
+ return NS_ERROR_INVALID_ARG;
+
+ const JsepAudioCodecDescription& desc =
+ static_cast<const JsepAudioCodecDescription&>(aCodec);
+
+ uint16_t pt;
+
+ if (!desc.GetPtAsInt(&pt)) {
+ MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << desc.mDefaultPt);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aConfig = new AudioCodecConfig(pt,
+ desc.mName,
+ desc.mClock,
+ desc.mPacketSize,
+ desc.mForceMono ? 1 : desc.mChannels,
+ desc.mBitrate,
+ desc.mFECEnabled);
+ (*aConfig)->mMaxPlaybackRate = desc.mMaxPlaybackRate;
+ (*aConfig)->mDtmfEnabled = desc.mDtmfEnabled;
+
+ return NS_OK;
+}
+
+static std::vector<JsepCodecDescription*>
+GetCodecs(const JsepTrackNegotiatedDetails& aDetails)
+{
+ // We do not try to handle cases where a codec is not used on the primary
+ // encoding.
+ if (aDetails.GetEncodingCount()) {
+ return aDetails.GetEncoding(0).GetCodecs();
+ }
+ return std::vector<JsepCodecDescription*>();
+}
+
+static nsresult
+NegotiatedDetailsToAudioCodecConfigs(const JsepTrackNegotiatedDetails& aDetails,
+ PtrVector<AudioCodecConfig>* aConfigs)
+{
+ std::vector<JsepCodecDescription*> codecs(GetCodecs(aDetails));
+ for (const JsepCodecDescription* codec : codecs) {
+ AudioCodecConfig* config;
+ if (NS_FAILED(JsepCodecDescToCodecConfig(*codec, &config))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ aConfigs->values.push_back(config);
+ }
+ return NS_OK;
+}
+
+static nsresult
+JsepCodecDescToCodecConfig(const JsepCodecDescription& aCodec,
+ VideoCodecConfig** aConfig)
+{
+ MOZ_ASSERT(aCodec.mType == SdpMediaSection::kVideo);
+ if (aCodec.mType != SdpMediaSection::kVideo) {
+ MOZ_ASSERT(false, "JsepCodecDescription has wrong type");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const JsepVideoCodecDescription& desc =
+ static_cast<const JsepVideoCodecDescription&>(aCodec);
+
+ uint16_t pt;
+
+ if (!desc.GetPtAsInt(&pt)) {
+ MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << desc.mDefaultPt);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ UniquePtr<VideoCodecConfigH264> h264Config;
+
+ if (desc.mName == "H264") {
+ h264Config = MakeUnique<VideoCodecConfigH264>();
+ size_t spropSize = sizeof(h264Config->sprop_parameter_sets);
+ strncpy(h264Config->sprop_parameter_sets,
+ desc.mSpropParameterSets.c_str(),
+ spropSize);
+ h264Config->sprop_parameter_sets[spropSize - 1] = '\0';
+ h264Config->packetization_mode = desc.mPacketizationMode;
+ h264Config->profile_level_id = desc.mProfileLevelId;
+ h264Config->tias_bw = 0; // TODO. Issue 165.
+ }
+
+ VideoCodecConfig* configRaw;
+ configRaw = new VideoCodecConfig(
+ pt, desc.mName, desc.mConstraints, h264Config.get());
+
+ configRaw->mAckFbTypes = desc.mAckFbTypes;
+ configRaw->mNackFbTypes = desc.mNackFbTypes;
+ configRaw->mCcmFbTypes = desc.mCcmFbTypes;
+ configRaw->mRembFbSet = desc.RtcpFbRembIsSet();
+ configRaw->mFECFbSet = desc.mFECEnabled;
+
+ *aConfig = configRaw;
+ return NS_OK;
+}
+
+static nsresult
+NegotiatedDetailsToVideoCodecConfigs(const JsepTrackNegotiatedDetails& aDetails,
+ PtrVector<VideoCodecConfig>* aConfigs)
+{
+ std::vector<JsepCodecDescription*> codecs(GetCodecs(aDetails));
+ for (const JsepCodecDescription* codec : codecs) {
+ VideoCodecConfig* config;
+ if (NS_FAILED(JsepCodecDescToCodecConfig(*codec, &config))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ for (size_t i = 0; i < aDetails.GetEncodingCount(); ++i) {
+ const JsepTrackEncoding& jsepEncoding(aDetails.GetEncoding(i));
+ if (jsepEncoding.HasFormat(codec->mDefaultPt)) {
+ VideoCodecConfig::SimulcastEncoding encoding;
+ encoding.rid = jsepEncoding.mRid;
+ encoding.constraints = jsepEncoding.mConstraints;
+ config->mSimulcastEncodings.push_back(encoding);
+ }
+ }
+ aConfigs->values.push_back(config);
+ }
+
+ return NS_OK;
+}
+
+// Accessing the PCMedia should be safe here because we shouldn't
+// have enqueued this function unless it was still active and
+// the ICE data is destroyed on the STS.
+static void
+FinalizeTransportFlow_s(RefPtr<PeerConnectionMedia> aPCMedia,
+ RefPtr<TransportFlow> aFlow, size_t aLevel,
+ bool aIsRtcp,
+ nsAutoPtr<PtrVector<TransportLayer> > aLayerList)
+{
+ TransportLayerIce* ice =
+ static_cast<TransportLayerIce*>(aLayerList->values.front());
+ ice->SetParameters(aPCMedia->ice_ctx(),
+ aPCMedia->ice_media_stream(aLevel),
+ aIsRtcp ? 2 : 1);
+ nsAutoPtr<std::queue<TransportLayer*> > layerQueue(
+ new std::queue<TransportLayer*>);
+ for (auto i = aLayerList->values.begin(); i != aLayerList->values.end();
+ ++i) {
+ layerQueue->push(*i);
+ }
+ aLayerList->values.clear();
+ (void)aFlow->PushLayers(layerQueue); // TODO(bug 854518): Process errors.
+}
+
+static void
+AddNewIceStreamForRestart_s(RefPtr<PeerConnectionMedia> aPCMedia,
+ RefPtr<TransportFlow> aFlow,
+ size_t aLevel,
+ bool aIsRtcp)
+{
+ TransportLayerIce* ice =
+ static_cast<TransportLayerIce*>(aFlow->GetLayer("ice"));
+ ice->SetParameters(aPCMedia->ice_ctx(),
+ aPCMedia->ice_media_stream(aLevel),
+ aIsRtcp ? 2 : 1);
+}
+
+nsresult
+MediaPipelineFactory::CreateOrGetTransportFlow(
+ size_t aLevel,
+ bool aIsRtcp,
+ const JsepTransport& aTransport,
+ RefPtr<TransportFlow>* aFlowOutparam)
+{
+ nsresult rv;
+ RefPtr<TransportFlow> flow;
+
+ flow = mPCMedia->GetTransportFlow(aLevel, aIsRtcp);
+ if (flow) {
+ if (mPCMedia->IsIceRestarting()) {
+ MOZ_MTLOG(ML_INFO, "Flow[" << flow->id() << "]: "
+ << "detected ICE restart - level: "
+ << aLevel << " rtcp: " << aIsRtcp);
+
+ rv = mPCMedia->GetSTSThread()->Dispatch(
+ WrapRunnableNM(AddNewIceStreamForRestart_s,
+ mPCMedia, flow, aLevel, aIsRtcp),
+ NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, "Failed to dispatch AddNewIceStreamForRestart_s");
+ return rv;
+ }
+ }
+
+ *aFlowOutparam = flow;
+ return NS_OK;
+ }
+
+ std::ostringstream osId;
+ osId << mPC->GetHandle() << ":" << aLevel << ","
+ << (aIsRtcp ? "rtcp" : "rtp");
+ flow = new TransportFlow(osId.str());
+
+ // The media streams are made on STS so we need to defer setup.
+ auto ice = MakeUnique<TransportLayerIce>(mPC->GetHandle());
+ auto dtls = MakeUnique<TransportLayerDtls>();
+ dtls->SetRole(aTransport.mDtls->GetRole() ==
+ JsepDtlsTransport::kJsepDtlsClient
+ ? TransportLayerDtls::CLIENT
+ : TransportLayerDtls::SERVER);
+
+ RefPtr<DtlsIdentity> pcid = mPC->Identity();
+ if (!pcid) {
+ MOZ_MTLOG(ML_ERROR, "Failed to get DTLS identity.");
+ return NS_ERROR_FAILURE;
+ }
+ dtls->SetIdentity(pcid);
+
+ const SdpFingerprintAttributeList& fingerprints =
+ aTransport.mDtls->GetFingerprints();
+ for (auto fp = fingerprints.mFingerprints.begin();
+ fp != fingerprints.mFingerprints.end();
+ ++fp) {
+ std::ostringstream ss;
+ ss << fp->hashFunc;
+ rv = dtls->SetVerificationDigest(ss.str(), &fp->fingerprint[0],
+ fp->fingerprint.size());
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, "Could not set fingerprint");
+ return rv;
+ }
+ }
+
+ std::vector<uint16_t> srtpCiphers;
+ srtpCiphers.push_back(SRTP_AES128_CM_HMAC_SHA1_80);
+ srtpCiphers.push_back(SRTP_AES128_CM_HMAC_SHA1_32);
+
+ rv = dtls->SetSrtpCiphers(srtpCiphers);
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't set SRTP ciphers");
+ return rv;
+ }
+
+ // Always permits negotiation of the confidential mode.
+ // Only allow non-confidential (which is an allowed default),
+ // if we aren't confidential.
+ std::set<std::string> alpn;
+ std::string alpnDefault = "";
+ alpn.insert("c-webrtc");
+ if (!mPC->PrivacyRequested()) {
+ alpnDefault = "webrtc";
+ alpn.insert(alpnDefault);
+ }
+ rv = dtls->SetAlpn(alpn, alpnDefault);
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't set ALPN");
+ return rv;
+ }
+
+ nsAutoPtr<PtrVector<TransportLayer> > layers(new PtrVector<TransportLayer>);
+ layers->values.push_back(ice.release());
+ layers->values.push_back(dtls.release());
+
+ rv = mPCMedia->GetSTSThread()->Dispatch(
+ WrapRunnableNM(FinalizeTransportFlow_s, mPCMedia, flow, aLevel, aIsRtcp,
+ layers),
+ NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, "Failed to dispatch FinalizeTransportFlow_s");
+ return rv;
+ }
+
+ mPCMedia->AddTransportFlow(aLevel, aIsRtcp, flow);
+
+ *aFlowOutparam = flow;
+
+ return NS_OK;
+}
+
+nsresult
+MediaPipelineFactory::GetTransportParameters(
+ const JsepTrackPair& aTrackPair,
+ const JsepTrack& aTrack,
+ size_t* aLevelOut,
+ RefPtr<TransportFlow>* aRtpOut,
+ RefPtr<TransportFlow>* aRtcpOut,
+ nsAutoPtr<MediaPipelineFilter>* aFilterOut)
+{
+ *aLevelOut = aTrackPair.mLevel;
+
+ size_t transportLevel = aTrackPair.mBundleLevel.isSome() ?
+ *aTrackPair.mBundleLevel :
+ aTrackPair.mLevel;
+
+ nsresult rv = CreateOrGetTransportFlow(
+ transportLevel, false, *aTrackPair.mRtpTransport, aRtpOut);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ MOZ_ASSERT(aRtpOut);
+
+ if (aTrackPair.mRtcpTransport) {
+ rv = CreateOrGetTransportFlow(
+ transportLevel, true, *aTrackPair.mRtcpTransport, aRtcpOut);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ MOZ_ASSERT(aRtcpOut);
+ }
+
+ if (aTrackPair.mBundleLevel.isSome()) {
+ bool receiving = aTrack.GetDirection() == sdp::kRecv;
+
+ *aFilterOut = new MediaPipelineFilter;
+
+ if (receiving) {
+ // Add remote SSRCs so we can distinguish which RTP packets actually
+ // belong to this pipeline (also RTCP sender reports).
+ for (auto i = aTrack.GetSsrcs().begin();
+ i != aTrack.GetSsrcs().end(); ++i) {
+ (*aFilterOut)->AddRemoteSSRC(*i);
+ }
+
+ // TODO(bug 1105005): Tell the filter about the mid for this track
+
+ // Add unique payload types as a last-ditch fallback
+ auto uniquePts = aTrack.GetNegotiatedDetails()->GetUniquePayloadTypes();
+ for (auto i = uniquePts.begin(); i != uniquePts.end(); ++i) {
+ (*aFilterOut)->AddUniquePT(*i);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+MediaPipelineFactory::CreateOrUpdateMediaPipeline(
+ const JsepTrackPair& aTrackPair,
+ const JsepTrack& aTrack)
+{
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // The GMP code is all the way on the other side of webrtc.org, and it is not
+ // feasible to plumb this information all the way through. So, we set it (for
+ // the duration of this call) in a global variable. This allows the GMP code
+ // to report errors to the PC.
+ WebrtcGmpPCHandleSetter setter(mPC->GetHandle());
+#endif
+
+ MOZ_ASSERT(aTrackPair.mRtpTransport);
+
+ bool receiving = aTrack.GetDirection() == sdp::kRecv;
+
+ size_t level;
+ RefPtr<TransportFlow> rtpFlow;
+ RefPtr<TransportFlow> rtcpFlow;
+ nsAutoPtr<MediaPipelineFilter> filter;
+
+ nsresult rv = GetTransportParameters(aTrackPair,
+ aTrack,
+ &level,
+ &rtpFlow,
+ &rtcpFlow,
+ &filter);
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, "Failed to get transport parameters for pipeline, rv="
+ << static_cast<unsigned>(rv));
+ return rv;
+ }
+
+ if (aTrack.GetMediaType() == SdpMediaSection::kApplication) {
+ // GetTransportParameters has already done everything we need for
+ // datachannel.
+ return NS_OK;
+ }
+
+ // Find the stream we need
+ SourceStreamInfo* stream;
+ if (receiving) {
+ stream = mPCMedia->GetRemoteStreamById(aTrack.GetStreamId());
+ } else {
+ stream = mPCMedia->GetLocalStreamById(aTrack.GetStreamId());
+ }
+
+ if (!stream) {
+ MOZ_MTLOG(ML_ERROR, "Negotiated " << (receiving ? "recv" : "send")
+ << " stream id " << aTrack.GetStreamId() << " was never added");
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!stream->HasTrack(aTrack.GetTrackId())) {
+ MOZ_MTLOG(ML_ERROR, "Negotiated " << (receiving ? "recv" : "send")
+ << " track id " << aTrack.GetTrackId() << " was never added");
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<MediaSessionConduit> conduit;
+ if (aTrack.GetMediaType() == SdpMediaSection::kAudio) {
+ rv = GetOrCreateAudioConduit(aTrackPair, aTrack, &conduit);
+ if (NS_FAILED(rv))
+ return rv;
+ } else if (aTrack.GetMediaType() == SdpMediaSection::kVideo) {
+ rv = GetOrCreateVideoConduit(aTrackPair, aTrack, &conduit);
+ if (NS_FAILED(rv))
+ return rv;
+ } else {
+ // We've created the TransportFlow, nothing else to do here.
+ return NS_OK;
+ }
+
+ if (aTrack.GetActive()) {
+ if (receiving) {
+ auto error = conduit->StartReceiving();
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, "StartReceiving failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ auto error = conduit->StartTransmitting();
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, "StartTransmitting failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+ }
+ } else {
+ if (receiving) {
+ auto error = conduit->StopReceiving();
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, "StopReceiving failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ auto error = conduit->StopTransmitting();
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, "StopTransmitting failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ RefPtr<MediaPipeline> pipeline =
+ stream->GetPipelineByTrackId_m(aTrack.GetTrackId());
+
+ if (pipeline && pipeline->level() != static_cast<int>(level)) {
+ MOZ_MTLOG(ML_WARNING, "Track " << aTrack.GetTrackId() <<
+ " has moved from level " << pipeline->level() <<
+ " to level " << level <<
+ ". This requires re-creating the MediaPipeline.");
+ RefPtr<dom::MediaStreamTrack> domTrack =
+ stream->GetTrackById(aTrack.GetTrackId());
+ MOZ_ASSERT(domTrack, "MediaPipeline existed for a track, but no MediaStreamTrack");
+
+ // Since we do not support changing the conduit on a pre-existing
+ // MediaPipeline
+ pipeline = nullptr;
+ stream->RemoveTrack(aTrack.GetTrackId());
+ stream->AddTrack(aTrack.GetTrackId(), domTrack);
+ }
+
+ if (pipeline) {
+ pipeline->UpdateTransport_m(level, rtpFlow, rtcpFlow, filter);
+ return NS_OK;
+ }
+
+ MOZ_MTLOG(ML_DEBUG,
+ "Creating media pipeline"
+ << " m-line index=" << aTrackPair.mLevel
+ << " type=" << aTrack.GetMediaType()
+ << " direction=" << aTrack.GetDirection());
+
+ if (receiving) {
+ rv = CreateMediaPipelineReceiving(aTrackPair, aTrack,
+ level, rtpFlow, rtcpFlow, filter,
+ conduit);
+ if (NS_FAILED(rv))
+ return rv;
+ } else {
+ rv = CreateMediaPipelineSending(aTrackPair, aTrack,
+ level, rtpFlow, rtcpFlow, filter,
+ conduit);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+MediaPipelineFactory::CreateMediaPipelineReceiving(
+ const JsepTrackPair& aTrackPair,
+ const JsepTrack& aTrack,
+ size_t aLevel,
+ RefPtr<TransportFlow> aRtpFlow,
+ RefPtr<TransportFlow> aRtcpFlow,
+ nsAutoPtr<MediaPipelineFilter> aFilter,
+ const RefPtr<MediaSessionConduit>& aConduit)
+{
+ // We will error out earlier if this isn't here.
+ RefPtr<RemoteSourceStreamInfo> stream =
+ mPCMedia->GetRemoteStreamById(aTrack.GetStreamId());
+
+ RefPtr<MediaPipelineReceive> pipeline;
+
+ TrackID numericTrackId = stream->GetNumericTrackId(aTrack.GetTrackId());
+ MOZ_ASSERT(IsTrackIDExplicit(numericTrackId));
+
+ MOZ_MTLOG(ML_DEBUG, __FUNCTION__ << ": Creating pipeline for "
+ << numericTrackId << " -> " << aTrack.GetTrackId());
+
+ if (aTrack.GetMediaType() == SdpMediaSection::kAudio) {
+ pipeline = new MediaPipelineReceiveAudio(
+ mPC->GetHandle(),
+ mPC->GetMainThread().get(),
+ mPC->GetSTSThread(),
+ stream->GetMediaStream()->GetInputStream()->AsSourceStream(),
+ aTrack.GetTrackId(),
+ numericTrackId,
+ aLevel,
+ static_cast<AudioSessionConduit*>(aConduit.get()), // Ugly downcast.
+ aRtpFlow,
+ aRtcpFlow,
+ aFilter);
+ } else if (aTrack.GetMediaType() == SdpMediaSection::kVideo) {
+ pipeline = new MediaPipelineReceiveVideo(
+ mPC->GetHandle(),
+ mPC->GetMainThread().get(),
+ mPC->GetSTSThread(),
+ stream->GetMediaStream()->GetInputStream()->AsSourceStream(),
+ aTrack.GetTrackId(),
+ numericTrackId,
+ aLevel,
+ static_cast<VideoSessionConduit*>(aConduit.get()), // Ugly downcast.
+ aRtpFlow,
+ aRtcpFlow,
+ aFilter);
+ } else {
+ MOZ_ASSERT(false);
+ MOZ_MTLOG(ML_ERROR, "Invalid media type in CreateMediaPipelineReceiving");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = pipeline->Init();
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't initialize receiving pipeline");
+ return rv;
+ }
+
+ rv = stream->StorePipeline(aTrack.GetTrackId(),
+ RefPtr<MediaPipeline>(pipeline));
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't store receiving pipeline " <<
+ static_cast<unsigned>(rv));
+ return rv;
+ }
+
+ stream->SyncPipeline(pipeline);
+
+ return NS_OK;
+}
+
+nsresult
+MediaPipelineFactory::CreateMediaPipelineSending(
+ const JsepTrackPair& aTrackPair,
+ const JsepTrack& aTrack,
+ size_t aLevel,
+ RefPtr<TransportFlow> aRtpFlow,
+ RefPtr<TransportFlow> aRtcpFlow,
+ nsAutoPtr<MediaPipelineFilter> aFilter,
+ const RefPtr<MediaSessionConduit>& aConduit)
+{
+ nsresult rv;
+
+ // This is checked earlier
+ RefPtr<LocalSourceStreamInfo> stream =
+ mPCMedia->GetLocalStreamById(aTrack.GetStreamId());
+
+ dom::MediaStreamTrack* track =
+ stream->GetTrackById(aTrack.GetTrackId());
+ MOZ_ASSERT(track);
+
+ // Now we have all the pieces, create the pipeline
+ RefPtr<MediaPipelineTransmit> pipeline = new MediaPipelineTransmit(
+ mPC->GetHandle(),
+ mPC->GetMainThread().get(),
+ mPC->GetSTSThread(),
+ track,
+ aTrack.GetTrackId(),
+ aLevel,
+ aConduit,
+ aRtpFlow,
+ aRtcpFlow,
+ aFilter);
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // implement checking for peerIdentity (where failure == black/silence)
+ nsIDocument* doc = mPC->GetWindow()->GetExtantDoc();
+ if (doc) {
+ pipeline->UpdateSinkIdentity_m(track,
+ doc->NodePrincipal(),
+ mPC->GetPeerIdentity());
+ } else {
+ MOZ_MTLOG(ML_ERROR, "Cannot initialize pipeline without attached doc");
+ return NS_ERROR_FAILURE; // Don't remove this till we know it's safe.
+ }
+#endif
+
+ rv = pipeline->Init();
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't initialize sending pipeline");
+ return rv;
+ }
+
+ rv = stream->StorePipeline(aTrack.GetTrackId(),
+ RefPtr<MediaPipeline>(pipeline));
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't store receiving pipeline " <<
+ static_cast<unsigned>(rv));
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+MediaPipelineFactory::GetOrCreateAudioConduit(
+ const JsepTrackPair& aTrackPair,
+ const JsepTrack& aTrack,
+ RefPtr<MediaSessionConduit>* aConduitp)
+{
+
+ if (!aTrack.GetNegotiatedDetails()) {
+ MOZ_ASSERT(false, "Track is missing negotiated details");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ bool receiving = aTrack.GetDirection() == sdp::kRecv;
+
+ RefPtr<AudioSessionConduit> conduit =
+ mPCMedia->GetAudioConduit(aTrackPair.mLevel);
+
+ if (!conduit) {
+ conduit = AudioSessionConduit::Create();
+ if (!conduit) {
+ MOZ_MTLOG(ML_ERROR, "Could not create audio conduit");
+ return NS_ERROR_FAILURE;
+ }
+
+ mPCMedia->AddAudioConduit(aTrackPair.mLevel, conduit);
+ }
+
+ PtrVector<AudioCodecConfig> configs;
+ nsresult rv = NegotiatedDetailsToAudioCodecConfigs(
+ *aTrack.GetNegotiatedDetails(), &configs);
+
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, "Failed to convert JsepCodecDescriptions to "
+ "AudioCodecConfigs.");
+ return rv;
+ }
+
+ if (configs.values.empty()) {
+ MOZ_MTLOG(ML_ERROR, "Can't set up a conduit with 0 codecs");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (receiving) {
+ auto error = conduit->ConfigureRecvMediaCodecs(configs.values);
+
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, "ConfigureRecvMediaCodecs failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aTrackPair.mSending) {
+ // No send track, but we still need to configure an SSRC for receiver
+ // reports.
+ if (!conduit->SetLocalSSRC(aTrackPair.mRecvonlySsrc)) {
+ MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed");
+ return NS_ERROR_FAILURE;
+ }
+ }
+ } else {
+ // For now we only expect to have one ssrc per local track.
+ auto ssrcs = aTrack.GetSsrcs();
+ if (!ssrcs.empty()) {
+ if (!conduit->SetLocalSSRC(ssrcs.front())) {
+ MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ conduit->SetLocalCNAME(aTrack.GetCNAME().c_str());
+
+ if (configs.values.size() > 1
+ && configs.values.back()->mName == "telephone-event") {
+ // we have a telephone event codec, so we need to make sure
+ // the dynamic pt is set properly
+ conduit->SetDtmfPayloadType(configs.values.back()->mType);
+ }
+
+ auto error = conduit->ConfigureSendMediaCodec(configs.values[0]);
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, "ConfigureSendMediaCodec failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+
+ const SdpExtmapAttributeList::Extmap* audioLevelExt =
+ aTrack.GetNegotiatedDetails()->GetExt(
+ "urn:ietf:params:rtp-hdrext:ssrc-audio-level");
+
+ if (audioLevelExt) {
+ MOZ_MTLOG(ML_DEBUG, "Calling EnableAudioLevelExtension");
+ error = conduit->EnableAudioLevelExtension(true, audioLevelExt->entry);
+
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, "EnableAudioLevelExtension failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ *aConduitp = conduit;
+
+ return NS_OK;
+}
+
+nsresult
+MediaPipelineFactory::GetOrCreateVideoConduit(
+ const JsepTrackPair& aTrackPair,
+ const JsepTrack& aTrack,
+ RefPtr<MediaSessionConduit>* aConduitp)
+{
+
+ if (!aTrack.GetNegotiatedDetails()) {
+ MOZ_ASSERT(false, "Track is missing negotiated details");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ bool receiving = aTrack.GetDirection() == sdp::kRecv;
+
+ RefPtr<VideoSessionConduit> conduit =
+ mPCMedia->GetVideoConduit(aTrackPair.mLevel);
+
+ if (!conduit) {
+ conduit = VideoSessionConduit::Create();
+ if (!conduit) {
+ MOZ_MTLOG(ML_ERROR, "Could not create video conduit");
+ return NS_ERROR_FAILURE;
+ }
+
+ mPCMedia->AddVideoConduit(aTrackPair.mLevel, conduit);
+ }
+
+ PtrVector<VideoCodecConfig> configs;
+ nsresult rv = NegotiatedDetailsToVideoCodecConfigs(
+ *aTrack.GetNegotiatedDetails(), &configs);
+
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, "Failed to convert JsepCodecDescriptions to "
+ "VideoCodecConfigs.");
+ return rv;
+ }
+
+ if (configs.values.empty()) {
+ MOZ_MTLOG(ML_ERROR, "Can't set up a conduit with 0 codecs");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (receiving) {
+ if (aTrackPair.mSending) {
+ auto ssrcs = &aTrackPair.mSending->GetSsrcs();
+ if (!ssrcs->empty()) {
+ if (!conduit->SetLocalSSRC(ssrcs->front())) {
+ MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed(1)");
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ MOZ_MTLOG(ML_ERROR, "Sending without an SSRC??");
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ // No send track, but we still need to configure an SSRC for receiver
+ // reports.
+ if (!conduit->SetLocalSSRC(aTrackPair.mRecvonlySsrc)) {
+ MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed(2)");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // Prune out stuff we cannot actually do. We should work to eliminate the
+ // need for this.
+ bool configuredH264 = false;
+ for (size_t i = 0; i < configs.values.size();) {
+ // TODO(bug 1200768): We can only handle configuring one recv H264 codec
+ if (configuredH264 && (configs.values[i]->mName == "H264")) {
+ delete configs.values[i];
+ configs.values.erase(configs.values.begin() + i);
+ continue;
+ }
+
+ // TODO(bug 1018791): This really should be checked sooner
+ if (EnsureExternalCodec(*conduit, configs.values[i], false)) {
+ delete configs.values[i];
+ configs.values.erase(configs.values.begin() + i);
+ continue;
+ }
+
+ if (configs.values[i]->mName == "H264") {
+ configuredH264 = true;
+ }
+ ++i;
+ }
+
+ auto error = conduit->ConfigureRecvMediaCodecs(configs.values);
+
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, "ConfigureRecvMediaCodecs failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ // For now we only expect to have one ssrc per local track.
+ auto ssrcs = aTrack.GetSsrcs();
+ if (!ssrcs.empty()) {
+ if (!conduit->SetLocalSSRC(ssrcs.front())) {
+ MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ conduit->SetLocalCNAME(aTrack.GetCNAME().c_str());
+
+ rv = ConfigureVideoCodecMode(aTrack,*conduit);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // TODO(bug 1018791): This really should be checked sooner
+ if (EnsureExternalCodec(*conduit, configs.values[0], true)) {
+ MOZ_MTLOG(ML_ERROR, "External codec not available");
+ return NS_ERROR_FAILURE;
+ }
+
+
+ auto error = conduit->ConfigureSendMediaCodec(configs.values[0]);
+
+ const SdpExtmapAttributeList::Extmap* rtpStreamIdExt =
+ aTrack.GetNegotiatedDetails()->GetExt(
+ "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id");
+
+ if (rtpStreamIdExt) {
+ MOZ_MTLOG(ML_DEBUG, "Calling EnableRTPSenderIdExtension");
+ error = conduit->EnableRTPStreamIdExtension(true, rtpStreamIdExt->entry);
+
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, "EnableRTPSenderIdExtension failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, "ConfigureSendMediaCodec failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ *aConduitp = conduit;
+
+ return NS_OK;
+}
+
+nsresult
+MediaPipelineFactory::ConfigureVideoCodecMode(const JsepTrack& aTrack,
+ VideoSessionConduit& aConduit)
+{
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ RefPtr<LocalSourceStreamInfo> stream =
+ mPCMedia->GetLocalStreamByTrackId(aTrack.GetTrackId());
+
+ //get video track
+ RefPtr<mozilla::dom::MediaStreamTrack> track =
+ stream->GetTrackById(aTrack.GetTrackId());
+
+ RefPtr<mozilla::dom::VideoStreamTrack> videotrack =
+ track->AsVideoStreamTrack();
+
+ if (!videotrack) {
+ MOZ_MTLOG(ML_ERROR, "video track not available");
+ return NS_ERROR_FAILURE;
+ }
+
+ dom::MediaSourceEnum source = videotrack->GetSource().GetMediaSource();
+ webrtc::VideoCodecMode mode = webrtc::kRealtimeVideo;
+ switch (source) {
+ case dom::MediaSourceEnum::Browser:
+ case dom::MediaSourceEnum::Screen:
+ case dom::MediaSourceEnum::Application:
+ case dom::MediaSourceEnum::Window:
+ mode = webrtc::kScreensharing;
+ break;
+
+ case dom::MediaSourceEnum::Camera:
+ default:
+ mode = webrtc::kRealtimeVideo;
+ break;
+ }
+
+ auto error = aConduit.ConfigureCodecMode(mode);
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, "ConfigureCodecMode failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+
+#endif
+ return NS_OK;
+}
+
+/*
+ * Add external H.264 video codec.
+ */
+MediaConduitErrorCode
+MediaPipelineFactory::EnsureExternalCodec(VideoSessionConduit& aConduit,
+ VideoCodecConfig* aConfig,
+ bool aIsSend)
+{
+ if (aConfig->mName == "VP8") {
+#ifdef MOZ_WEBRTC_MEDIACODEC
+ if (aIsSend) {
+#ifdef MOZILLA_INTERNAL_API
+ bool enabled = mozilla::Preferences::GetBool("media.navigator.hardware.vp8_encode.acceleration_enabled", false);
+#else
+ bool enabled = false;
+#endif
+ if (enabled) {
+ nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
+ if (gfxInfo) {
+ int32_t status;
+ nsCString discardFailureId;
+ if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_ENCODE, discardFailureId, &status))) {
+ if (status != nsIGfxInfo::FEATURE_STATUS_OK) {
+ NS_WARNING("VP8 encoder hardware is not whitelisted: disabling.\n");
+ } else {
+ VideoEncoder* encoder = nullptr;
+ encoder = MediaCodecVideoCodec::CreateEncoder(MediaCodecVideoCodec::CodecType::CODEC_VP8);
+ if (encoder) {
+ return aConduit.SetExternalSendCodec(aConfig, encoder);
+ }
+ return kMediaConduitNoError;
+ }
+ }
+ }
+ }
+ } else {
+#ifdef MOZILLA_INTERNAL_API
+ bool enabled = mozilla::Preferences::GetBool("media.navigator.hardware.vp8_decode.acceleration_enabled", false);
+#else
+ bool enabled = false;
+#endif
+ if (enabled) {
+ nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
+ if (gfxInfo) {
+ int32_t status;
+ nsCString discardFailureId;
+ if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_DECODE, discardFailureId, &status))) {
+ if (status != nsIGfxInfo::FEATURE_STATUS_OK) {
+ NS_WARNING("VP8 decoder hardware is not whitelisted: disabling.\n");
+ } else {
+ VideoDecoder* decoder;
+ decoder = MediaCodecVideoCodec::CreateDecoder(MediaCodecVideoCodec::CodecType::CODEC_VP8);
+ if (decoder) {
+ return aConduit.SetExternalRecvCodec(aConfig, decoder);
+ }
+ return kMediaConduitNoError;
+ }
+ }
+ }
+ }
+ }
+#endif
+ return kMediaConduitNoError;
+ }
+ if (aConfig->mName == "VP9") {
+ return kMediaConduitNoError;
+ }
+ if (aConfig->mName == "H264") {
+ if (aConduit.CodecPluginID() != 0) {
+ return kMediaConduitNoError;
+ }
+ // Register H.264 codec.
+ if (aIsSend) {
+ VideoEncoder* encoder = nullptr;
+#ifdef MOZ_WEBRTC_OMX
+ encoder =
+ OMXVideoCodec::CreateEncoder(OMXVideoCodec::CodecType::CODEC_H264);
+#else
+ encoder = GmpVideoCodec::CreateEncoder();
+#endif
+ if (encoder) {
+ return aConduit.SetExternalSendCodec(aConfig, encoder);
+ }
+ return kMediaConduitInvalidSendCodec;
+ }
+ VideoDecoder* decoder = nullptr;
+#ifdef MOZ_WEBRTC_OMX
+ decoder =
+ OMXVideoCodec::CreateDecoder(OMXVideoCodec::CodecType::CODEC_H264);
+#else
+ decoder = GmpVideoCodec::CreateDecoder();
+#endif
+ if (decoder) {
+ return aConduit.SetExternalRecvCodec(aConfig, decoder);
+ }
+ return kMediaConduitInvalidReceiveCodec;
+ }
+ MOZ_MTLOG(ML_ERROR,
+ "Invalid video codec configured: " << aConfig->mName.c_str());
+ return aIsSend ? kMediaConduitInvalidSendCodec
+ : kMediaConduitInvalidReceiveCodec;
+}
+
+} // namespace mozilla
diff --git a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.h b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.h
new file mode 100644
index 000000000..972c4368a
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.h
@@ -0,0 +1,82 @@
+/* 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/. */
+#ifndef _MEDIAPIPELINEFACTORY_H_
+#define _MEDIAPIPELINEFACTORY_H_
+
+#include "MediaConduitInterface.h"
+#include "PeerConnectionMedia.h"
+#include "transportflow.h"
+
+#include "signaling/src/jsep/JsepTrack.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+class MediaPipelineFactory
+{
+public:
+ explicit MediaPipelineFactory(PeerConnectionMedia* aPCMedia)
+ : mPCMedia(aPCMedia), mPC(aPCMedia->GetPC())
+ {
+ }
+
+ nsresult CreateOrUpdateMediaPipeline(const JsepTrackPair& aTrackPair,
+ const JsepTrack& aTrack);
+
+private:
+ nsresult CreateMediaPipelineReceiving(
+ const JsepTrackPair& aTrackPair,
+ const JsepTrack& aTrack,
+ size_t level,
+ RefPtr<TransportFlow> aRtpFlow,
+ RefPtr<TransportFlow> aRtcpFlow,
+ nsAutoPtr<MediaPipelineFilter> filter,
+ const RefPtr<MediaSessionConduit>& aConduit);
+
+ nsresult CreateMediaPipelineSending(
+ const JsepTrackPair& aTrackPair,
+ const JsepTrack& aTrack,
+ size_t level,
+ RefPtr<TransportFlow> aRtpFlow,
+ RefPtr<TransportFlow> aRtcpFlow,
+ nsAutoPtr<MediaPipelineFilter> filter,
+ const RefPtr<MediaSessionConduit>& aConduit);
+
+ nsresult GetOrCreateAudioConduit(const JsepTrackPair& aTrackPair,
+ const JsepTrack& aTrack,
+ RefPtr<MediaSessionConduit>* aConduitp);
+
+ nsresult GetOrCreateVideoConduit(const JsepTrackPair& aTrackPair,
+ const JsepTrack& aTrack,
+ RefPtr<MediaSessionConduit>* aConduitp);
+
+ MediaConduitErrorCode EnsureExternalCodec(VideoSessionConduit& aConduit,
+ VideoCodecConfig* aConfig,
+ bool aIsSend);
+
+ nsresult CreateOrGetTransportFlow(size_t aLevel, bool aIsRtcp,
+ const JsepTransport& transport,
+ RefPtr<TransportFlow>* out);
+
+ nsresult GetTransportParameters(const JsepTrackPair& aTrackPair,
+ const JsepTrack& aTrack,
+ size_t* aLevelOut,
+ RefPtr<TransportFlow>* aRtpOut,
+ RefPtr<TransportFlow>* aRtcpOut,
+ nsAutoPtr<MediaPipelineFilter>* aFilterOut);
+
+ nsresult ConfigureVideoCodecMode(const JsepTrack& aTrack,
+ VideoSessionConduit& aConduit);
+
+private:
+ // Not owned, and assumed to exist as long as the factory.
+ // The factory is a transient object, so this is fairly easy.
+ PeerConnectionMedia* mPCMedia;
+ PeerConnectionImpl* mPC;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/media/webrtc/signaling/src/peerconnection/MediaStreamList.cpp b/media/webrtc/signaling/src/peerconnection/MediaStreamList.cpp
new file mode 100644
index 000000000..f10b4447f
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/MediaStreamList.cpp
@@ -0,0 +1,104 @@
+/* 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 "CSFLog.h"
+#include "base/basictypes.h"
+#include "MediaStreamList.h"
+#ifdef MOZILLA_INTERNAL_API
+#include "mozilla/dom/MediaStreamListBinding.h"
+#endif
+#include "nsIScriptGlobalObject.h"
+#include "PeerConnectionImpl.h"
+#include "PeerConnectionMedia.h"
+
+namespace mozilla {
+namespace dom {
+
+MediaStreamList::MediaStreamList(PeerConnectionImpl* peerConnection,
+ StreamType type)
+ : mPeerConnection(peerConnection),
+ mType(type)
+{
+}
+
+MediaStreamList::~MediaStreamList()
+{
+}
+
+#ifdef MOZILLA_INTERNAL_API
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(MediaStreamList)
+#else
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamList)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaStreamList)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaStreamList)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(MediaStreamList)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+#endif
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamList)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamList)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject*
+MediaStreamList::WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
+{
+#ifdef MOZILLA_INTERNAL_API
+ return MediaStreamListBinding::Wrap(cx, this, aGivenProto);
+#else
+ return nullptr;
+#endif
+}
+
+nsISupports*
+MediaStreamList::GetParentObject()
+{
+ return mPeerConnection->GetWindow();
+}
+
+template<class T>
+static DOMMediaStream*
+GetStreamFromInfo(T* info, bool& found)
+{
+ if (!info) {
+ found = false;
+ return nullptr;
+ }
+
+ found = true;
+ return info->GetMediaStream();
+}
+
+DOMMediaStream*
+MediaStreamList::IndexedGetter(uint32_t index, bool& found)
+{
+ if (!mPeerConnection->media()) { // PeerConnection closed
+ found = false;
+ return nullptr;
+ }
+ if (mType == Local) {
+ return GetStreamFromInfo(mPeerConnection->media()->
+ GetLocalStreamByIndex(index), found);
+ }
+
+ return GetStreamFromInfo(mPeerConnection->media()->
+ GetRemoteStreamByIndex(index), found);
+}
+
+uint32_t
+MediaStreamList::Length()
+{
+ if (!mPeerConnection->media()) { // PeerConnection closed
+ return 0;
+ }
+ return mType == Local ? mPeerConnection->media()->LocalStreamsLength() :
+ mPeerConnection->media()->RemoteStreamsLength();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/media/webrtc/signaling/src/peerconnection/MediaStreamList.h b/media/webrtc/signaling/src/peerconnection/MediaStreamList.h
new file mode 100644
index 000000000..de9040227
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/MediaStreamList.h
@@ -0,0 +1,54 @@
+/* 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/. */
+
+#ifndef MediaStreamList_h__
+#define MediaStreamList_h__
+
+#include "mozilla/ErrorResult.h"
+#include "nsISupportsImpl.h"
+#include "nsAutoPtr.h"
+#include "nsWrapperCache.h"
+
+#ifdef USE_FAKE_MEDIA_STREAMS
+#include "FakeMediaStreams.h"
+#else
+#include "DOMMediaStream.h"
+#endif
+
+namespace mozilla {
+class PeerConnectionImpl;
+namespace dom {
+
+class MediaStreamList : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ enum StreamType {
+ Local,
+ Remote
+ };
+
+ MediaStreamList(PeerConnectionImpl* peerConnection, StreamType type);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaStreamList)
+
+ virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+ override;
+ nsISupports* GetParentObject();
+
+ DOMMediaStream* IndexedGetter(uint32_t index, bool& found);
+ uint32_t Length();
+
+private:
+ virtual ~MediaStreamList();
+
+ RefPtr<PeerConnectionImpl> mPeerConnection;
+ StreamType mType;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // MediaStreamList_h__
diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp
new file mode 100644
index 000000000..515258efb
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp
@@ -0,0 +1,452 @@
+/* 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 "CSFLog.h"
+
+#include "PeerConnectionImpl.h"
+#include "PeerConnectionCtx.h"
+#include "runnable_utils.h"
+#include "prcvar.h"
+
+#include "mozilla/Telemetry.h"
+#include "browser_logging/WebRtcLog.h"
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+#include "mozilla/dom/RTCPeerConnectionBinding.h"
+#include "mozilla/Preferences.h"
+#include <mozilla/Types.h>
+#endif
+
+#include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID
+#include "nsServiceManagerUtils.h" // do_GetService
+#include "nsIObserverService.h"
+#include "nsIObserver.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+
+#include "gmp-video-decode.h" // GMP_API_VIDEO_DECODER
+#include "gmp-video-encode.h" // GMP_API_VIDEO_ENCODER
+
+static const char* logTag = "PeerConnectionCtx";
+
+namespace mozilla {
+
+using namespace dom;
+
+class PeerConnectionCtxShutdown : public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ PeerConnectionCtxShutdown() {}
+
+ void Init()
+ {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (!observerService)
+ return;
+
+ nsresult rv = NS_OK;
+
+#ifdef MOZILLA_INTERNAL_API
+ rv = observerService->AddObserver(this,
+ NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+ false);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+#endif
+ (void) rv;
+ }
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ CSFLogDebug(logTag, "Shutting down PeerConnectionCtx");
+ PeerConnectionCtx::Destroy();
+
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (!observerService)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = observerService->RemoveObserver(this,
+ NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+
+ // Make sure we're not deleted while still inside ::Observe()
+ RefPtr<PeerConnectionCtxShutdown> kungFuDeathGrip(this);
+ PeerConnectionCtx::gPeerConnectionCtxShutdown = nullptr;
+ }
+ return NS_OK;
+ }
+
+private:
+ virtual ~PeerConnectionCtxShutdown()
+ {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (observerService)
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+};
+
+NS_IMPL_ISUPPORTS(PeerConnectionCtxShutdown, nsIObserver);
+}
+
+namespace mozilla {
+
+PeerConnectionCtx* PeerConnectionCtx::gInstance;
+nsIThread* PeerConnectionCtx::gMainThread;
+StaticRefPtr<PeerConnectionCtxShutdown> PeerConnectionCtx::gPeerConnectionCtxShutdown;
+
+const std::map<const std::string, PeerConnectionImpl *>&
+PeerConnectionCtx::mGetPeerConnections()
+{
+ return mPeerConnections;
+}
+
+nsresult PeerConnectionCtx::InitializeGlobal(nsIThread *mainThread,
+ nsIEventTarget* stsThread) {
+ if (!gMainThread) {
+ gMainThread = mainThread;
+ } else {
+ MOZ_ASSERT(gMainThread == mainThread);
+ }
+
+ nsresult res;
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gInstance) {
+ CSFLogDebug(logTag, "Creating PeerConnectionCtx");
+ PeerConnectionCtx *ctx = new PeerConnectionCtx();
+
+ res = ctx->Initialize();
+ PR_ASSERT(NS_SUCCEEDED(res));
+ if (!NS_SUCCEEDED(res))
+ return res;
+
+ gInstance = ctx;
+
+ if (!PeerConnectionCtx::gPeerConnectionCtxShutdown) {
+ PeerConnectionCtx::gPeerConnectionCtxShutdown = new PeerConnectionCtxShutdown();
+ PeerConnectionCtx::gPeerConnectionCtxShutdown->Init();
+ }
+ }
+
+ EnableWebRtcLog();
+ return NS_OK;
+}
+
+PeerConnectionCtx* PeerConnectionCtx::GetInstance() {
+ MOZ_ASSERT(gInstance);
+ return gInstance;
+}
+
+bool PeerConnectionCtx::isActive() {
+ return gInstance;
+}
+
+void PeerConnectionCtx::Destroy() {
+ CSFLogDebug(logTag, "%s", __FUNCTION__);
+
+ if (gInstance) {
+ gInstance->Cleanup();
+ delete gInstance;
+ gInstance = nullptr;
+ }
+
+ StopWebRtcLog();
+}
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+typedef Vector<nsAutoPtr<RTCStatsQuery>> RTCStatsQueries;
+
+// Telemetry reporting every second after start of first call.
+// The threading model around the media pipelines is weird:
+// - The pipelines are containers,
+// - containers that are only safe on main thread, with members only safe on STS,
+// - hence the there and back again approach.
+
+static auto
+FindId(const Sequence<RTCInboundRTPStreamStats>& aArray,
+ const nsString &aId) -> decltype(aArray.Length()) {
+ for (decltype(aArray.Length()) i = 0; i < aArray.Length(); i++) {
+ if (aArray[i].mId.Value() == aId) {
+ return i;
+ }
+ }
+ return aArray.NoIndex;
+}
+
+static auto
+FindId(const nsTArray<nsAutoPtr<RTCStatsReportInternal>>& aArray,
+ const nsString &aId) -> decltype(aArray.Length()) {
+ for (decltype(aArray.Length()) i = 0; i < aArray.Length(); i++) {
+ if (aArray[i]->mPcid == aId) {
+ return i;
+ }
+ }
+ return aArray.NoIndex;
+}
+
+static void
+FreeOnMain_m(nsAutoPtr<RTCStatsQueries> aQueryList) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+static void
+EverySecondTelemetryCallback_s(nsAutoPtr<RTCStatsQueries> aQueryList) {
+ using namespace Telemetry;
+
+ if(!PeerConnectionCtx::isActive()) {
+ return;
+ }
+ PeerConnectionCtx *ctx = PeerConnectionCtx::GetInstance();
+
+ for (auto q = aQueryList->begin(); q != aQueryList->end(); ++q) {
+ PeerConnectionImpl::ExecuteStatsQuery_s(*q);
+ auto& r = *(*q)->report;
+ if (r.mInboundRTPStreamStats.WasPassed()) {
+ // First, get reports from a second ago, if any, for calculations below
+ const Sequence<RTCInboundRTPStreamStats> *lastInboundStats = nullptr;
+ {
+ auto i = FindId(ctx->mLastReports, r.mPcid);
+ if (i != ctx->mLastReports.NoIndex) {
+ lastInboundStats = &ctx->mLastReports[i]->mInboundRTPStreamStats.Value();
+ }
+ }
+ // Then, look for the things we want telemetry on
+ auto& array = r.mInboundRTPStreamStats.Value();
+ for (decltype(array.Length()) i = 0; i < array.Length(); i++) {
+ auto& s = array[i];
+ bool isAudio = (s.mId.Value().Find("audio") != -1);
+ if (s.mPacketsLost.WasPassed() && s.mPacketsReceived.WasPassed() &&
+ (s.mPacketsLost.Value() + s.mPacketsReceived.Value()) != 0) {
+ ID id;
+ if (s.mIsRemote) {
+ id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_PACKETLOSS_RATE :
+ WEBRTC_VIDEO_QUALITY_OUTBOUND_PACKETLOSS_RATE;
+ } else {
+ id = isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_PACKETLOSS_RATE :
+ WEBRTC_VIDEO_QUALITY_INBOUND_PACKETLOSS_RATE;
+ }
+ // *1000 so we can read in 10's of a percent (permille)
+ Accumulate(id,
+ (s.mPacketsLost.Value() * 1000) /
+ (s.mPacketsLost.Value() + s.mPacketsReceived.Value()));
+ }
+ if (s.mJitter.WasPassed()) {
+ ID id;
+ if (s.mIsRemote) {
+ id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_JITTER :
+ WEBRTC_VIDEO_QUALITY_OUTBOUND_JITTER;
+ } else {
+ id = isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_JITTER :
+ WEBRTC_VIDEO_QUALITY_INBOUND_JITTER;
+ }
+ Accumulate(id, s.mJitter.Value());
+ }
+ if (s.mMozRtt.WasPassed()) {
+ MOZ_ASSERT(s.mIsRemote);
+ ID id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_RTT :
+ WEBRTC_VIDEO_QUALITY_OUTBOUND_RTT;
+ Accumulate(id, s.mMozRtt.Value());
+ }
+ if (lastInboundStats && s.mBytesReceived.WasPassed()) {
+ auto& laststats = *lastInboundStats;
+ auto i = FindId(laststats, s.mId.Value());
+ if (i != laststats.NoIndex) {
+ auto& lasts = laststats[i];
+ if (lasts.mBytesReceived.WasPassed()) {
+ auto delta_ms = int32_t(s.mTimestamp.Value() -
+ lasts.mTimestamp.Value());
+ // In theory we're called every second, so delta *should* be in that range.
+ // Small deltas could cause errors due to division
+ if (delta_ms > 500 && delta_ms < 60000) {
+ ID id;
+ if (s.mIsRemote) {
+ id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_BANDWIDTH_KBITS :
+ WEBRTC_VIDEO_QUALITY_OUTBOUND_BANDWIDTH_KBITS;
+ } else {
+ id = isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_BANDWIDTH_KBITS :
+ WEBRTC_VIDEO_QUALITY_INBOUND_BANDWIDTH_KBITS;
+ }
+ Accumulate(id, ((s.mBytesReceived.Value() -
+ lasts.mBytesReceived.Value()) * 8) / delta_ms);
+ }
+ // We could accumulate values until enough time has passed
+ // and then Accumulate() but this isn't that important.
+ }
+ }
+ }
+ }
+ }
+ }
+ // Steal and hang on to reports for the next second
+ ctx->mLastReports.Clear();
+ for (auto q = aQueryList->begin(); q != aQueryList->end(); ++q) {
+ ctx->mLastReports.AppendElement((*q)->report.forget()); // steal avoids copy
+ }
+ // Container must be freed back on main thread
+ NS_DispatchToMainThread(WrapRunnableNM(&FreeOnMain_m, aQueryList),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionCtx::EverySecondTelemetryCallback_m(nsITimer* timer, void *closure) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(PeerConnectionCtx::isActive());
+ auto ctx = static_cast<PeerConnectionCtx*>(closure);
+ if (ctx->mPeerConnections.empty()) {
+ return;
+ }
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> stsThread =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ MOZ_ASSERT(stsThread);
+
+ nsAutoPtr<RTCStatsQueries> queries(new RTCStatsQueries);
+ for (auto p = ctx->mPeerConnections.begin();
+ p != ctx->mPeerConnections.end(); ++p) {
+ if (p->second->HasMedia()) {
+ if (!queries->append(nsAutoPtr<RTCStatsQuery>(new RTCStatsQuery(true)))) {
+ return;
+ }
+ if (NS_WARN_IF(NS_FAILED(p->second->BuildStatsQuery_m(nullptr, // all tracks
+ queries->back())))) {
+ queries->popBack();
+ } else {
+ MOZ_ASSERT(queries->back()->report);
+ }
+ }
+ }
+ if (!queries->empty()) {
+ rv = RUN_ON_THREAD(stsThread,
+ WrapRunnableNM(&EverySecondTelemetryCallback_s, queries),
+ NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+}
+#endif
+
+nsresult PeerConnectionCtx::Initialize() {
+ initGMP();
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ mTelemetryTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ MOZ_ASSERT(mTelemetryTimer);
+ nsresult rv = mTelemetryTimer->SetTarget(gMainThread);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mTelemetryTimer->InitWithFuncCallback(EverySecondTelemetryCallback_m, this, 1000,
+ nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP);
+
+ if (XRE_IsContentProcess()) {
+ WebrtcGlobalChild::Create();
+ }
+#endif // MOZILLA_INTERNAL_API
+
+ return NS_OK;
+}
+
+static void GMPReady_m() {
+ if (PeerConnectionCtx::isActive()) {
+ PeerConnectionCtx::GetInstance()->onGMPReady();
+ }
+};
+
+static void GMPReady() {
+ PeerConnectionCtx::gMainThread->Dispatch(WrapRunnableNM(&GMPReady_m),
+ NS_DISPATCH_NORMAL);
+};
+
+void PeerConnectionCtx::initGMP()
+{
+ mGMPService = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+
+ if (!mGMPService) {
+ CSFLogError(logTag, "%s failed to get the gecko-media-plugin-service",
+ __FUNCTION__);
+ return;
+ }
+
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = mGMPService->GetThread(getter_AddRefs(thread));
+
+ if (NS_FAILED(rv)) {
+ mGMPService = nullptr;
+ CSFLogError(logTag,
+ "%s failed to get the gecko-media-plugin thread, err=%u",
+ __FUNCTION__,
+ static_cast<unsigned>(rv));
+ return;
+ }
+
+ // presumes that all GMP dir scans have been queued for the GMPThread
+ thread->Dispatch(WrapRunnableNM(&GMPReady), NS_DISPATCH_NORMAL);
+}
+
+nsresult PeerConnectionCtx::Cleanup() {
+ CSFLogDebug(logTag, "%s", __FUNCTION__);
+
+ mQueuedJSEPOperations.Clear();
+ mGMPService = nullptr;
+ return NS_OK;
+}
+
+PeerConnectionCtx::~PeerConnectionCtx() {
+ // ensure mTelemetryTimer ends on main thread
+ MOZ_ASSERT(NS_IsMainThread());
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ if (mTelemetryTimer) {
+ mTelemetryTimer->Cancel();
+ }
+#endif
+};
+
+void PeerConnectionCtx::queueJSEPOperation(nsIRunnable* aOperation) {
+ mQueuedJSEPOperations.AppendElement(aOperation);
+}
+
+void PeerConnectionCtx::onGMPReady() {
+ mGMPReady = true;
+ for (size_t i = 0; i < mQueuedJSEPOperations.Length(); ++i) {
+ mQueuedJSEPOperations[i]->Run();
+ }
+ mQueuedJSEPOperations.Clear();
+}
+
+bool PeerConnectionCtx::gmpHasH264() {
+ if (!mGMPService) {
+ return false;
+ }
+
+ // XXX I'd prefer if this was all known ahead of time...
+
+ nsTArray<nsCString> tags;
+ tags.AppendElement(NS_LITERAL_CSTRING("h264"));
+
+ bool has_gmp;
+ nsresult rv;
+ rv = mGMPService->HasPluginForAPI(NS_LITERAL_CSTRING(GMP_API_VIDEO_ENCODER),
+ &tags,
+ &has_gmp);
+ if (NS_FAILED(rv) || !has_gmp) {
+ return false;
+ }
+
+ rv = mGMPService->HasPluginForAPI(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
+ &tags,
+ &has_gmp);
+ if (NS_FAILED(rv) || !has_gmp) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace mozilla
diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h
new file mode 100644
index 000000000..3f7d6250b
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h
@@ -0,0 +1,109 @@
+/* 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/. */
+
+#ifndef peerconnectionctx_h___h__
+#define peerconnectionctx_h___h__
+
+#include <string>
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+#include "WebrtcGlobalChild.h"
+#endif
+
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPtr.h"
+#include "PeerConnectionImpl.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsIRunnable.h"
+
+namespace mozilla {
+class PeerConnectionCtxShutdown;
+
+namespace dom {
+class WebrtcGlobalInformation;
+}
+
+// A class to hold some of the singleton objects we need:
+// * The global PeerConnectionImpl table and its associated lock.
+// * Stats report objects for PCs that are gone
+// * GMP related state
+class PeerConnectionCtx {
+ public:
+ static nsresult InitializeGlobal(nsIThread *mainThread, nsIEventTarget *stsThread);
+ static PeerConnectionCtx* GetInstance();
+ static bool isActive();
+ static void Destroy();
+
+ bool isReady() {
+ // If mGMPService is not set, we aren't using GMP.
+ if (mGMPService) {
+ return mGMPReady;
+ }
+ return true;
+ }
+
+ void queueJSEPOperation(nsIRunnable* aJSEPOperation);
+ void onGMPReady();
+
+ bool gmpHasH264();
+
+ // Make these classes friend so that they can access mPeerconnections.
+ friend class PeerConnectionImpl;
+ friend class PeerConnectionWrapper;
+ friend class mozilla::dom::WebrtcGlobalInformation;
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // WebrtcGlobalInformation uses this; we put it here so we don't need to
+ // create another shutdown observer class.
+ mozilla::dom::Sequence<mozilla::dom::RTCStatsReportInternal>
+ mStatsForClosedPeerConnections;
+#endif
+
+ const std::map<const std::string, PeerConnectionImpl *>& mGetPeerConnections();
+ private:
+ // We could make these available only via accessors but it's too much trouble.
+ std::map<const std::string, PeerConnectionImpl *> mPeerConnections;
+
+ PeerConnectionCtx() : mGMPReady(false) {}
+ // This is a singleton, so don't copy construct it, etc.
+ PeerConnectionCtx(const PeerConnectionCtx& other) = delete;
+ void operator=(const PeerConnectionCtx& other) = delete;
+ virtual ~PeerConnectionCtx();
+
+ nsresult Initialize();
+ nsresult Cleanup();
+
+ void initGMP();
+
+ static void
+ EverySecondTelemetryCallback_m(nsITimer* timer, void *);
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ nsCOMPtr<nsITimer> mTelemetryTimer;
+
+public:
+ // TODO(jib): If we ever enable move semantics on std::map...
+ //std::map<nsString,nsAutoPtr<mozilla::dom::RTCStatsReportInternal>> mLastReports;
+ nsTArray<nsAutoPtr<mozilla::dom::RTCStatsReportInternal>> mLastReports;
+private:
+#endif
+
+ // We cannot form offers/answers properly until the Gecko Media Plugin stuff
+ // has been initted, which is a complicated mess of thread dispatches,
+ // including sync dispatches to main. So, we need to be able to queue up
+ // offer creation (or SetRemote, when we're the answerer) until all of this is
+ // ready to go, since blocking on this init is just begging for deadlock.
+ nsCOMPtr<mozIGeckoMediaPluginService> mGMPService;
+ bool mGMPReady;
+ nsTArray<nsCOMPtr<nsIRunnable>> mQueuedJSEPOperations;
+
+ static PeerConnectionCtx *gInstance;
+public:
+ static nsIThread *gMainThread;
+ static mozilla::StaticRefPtr<mozilla::PeerConnectionCtxShutdown> gPeerConnectionCtxShutdown;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
new file mode 100644
index 000000000..33422ed7a
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -0,0 +1,4176 @@
+/* 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 <cstdlib>
+#include <cerrno>
+#include <deque>
+#include <set>
+#include <sstream>
+#include <vector>
+
+#include "CSFLog.h"
+#include "timecard.h"
+
+#include "jsapi.h"
+#include "nspr.h"
+#include "nss.h"
+#include "pk11pub.h"
+
+#include "nsNetCID.h"
+#include "nsIProperty.h"
+#include "nsIPropertyBag2.h"
+#include "nsIServiceManager.h"
+#include "nsISimpleEnumerator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsISocketTransportService.h"
+#include "nsIConsoleService.h"
+#include "nsThreadUtils.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsProxyRelease.h"
+#include "nsQueryObject.h"
+#include "prtime.h"
+
+#include "AudioConduit.h"
+#include "VideoConduit.h"
+#include "runnable_utils.h"
+#include "PeerConnectionCtx.h"
+#include "PeerConnectionImpl.h"
+#include "PeerConnectionMedia.h"
+#include "nsDOMDataChannelDeclarations.h"
+#include "dtlsidentity.h"
+#include "signaling/src/sdp/SdpAttribute.h"
+
+#include "signaling/src/jsep/JsepTrack.h"
+#include "signaling/src/jsep/JsepSession.h"
+#include "signaling/src/jsep/JsepSessionImpl.h"
+
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Sprintf.h"
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+#ifdef XP_WIN
+// We need to undef the MS macro for nsIDocument::CreateEvent
+#ifdef CreateEvent
+#undef CreateEvent
+#endif
+#endif // XP_WIN
+
+#include "nsIDocument.h"
+#include "nsGlobalWindow.h"
+#include "nsDOMDataChannel.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PublicSSL.h"
+#include "nsXULAppAPI.h"
+#include "nsContentUtils.h"
+#include "nsDOMJSUtils.h"
+#include "nsIScriptError.h"
+#include "nsPrintfCString.h"
+#include "nsURLHelper.h"
+#include "nsNetUtil.h"
+#include "nsIURLParser.h"
+#include "nsIDOMDataChannel.h"
+#include "nsIDOMLocation.h"
+#include "nsNullPrincipal.h"
+#include "mozilla/PeerIdentity.h"
+#include "mozilla/dom/RTCCertificate.h"
+#include "mozilla/dom/RTCConfigurationBinding.h"
+#include "mozilla/dom/RTCDTMFSenderBinding.h"
+#include "mozilla/dom/RTCDTMFToneChangeEvent.h"
+#include "mozilla/dom/RTCRtpSenderBinding.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "mozilla/dom/RTCPeerConnectionBinding.h"
+#include "mozilla/dom/PeerConnectionImplBinding.h"
+#include "mozilla/dom/DataChannelBinding.h"
+#include "mozilla/dom/PerformanceTiming.h"
+#include "mozilla/dom/PluginCrashedEvent.h"
+#include "MediaStreamList.h"
+#include "MediaStreamTrack.h"
+#include "AudioStreamTrack.h"
+#include "VideoStreamTrack.h"
+#include "nsIScriptGlobalObject.h"
+#include "MediaStreamGraph.h"
+#include "DOMMediaStream.h"
+#include "rlogconnector.h"
+#include "WebrtcGlobalInformation.h"
+#include "mozilla/dom/Event.h"
+#include "nsIDOMCustomEvent.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/net/DataChannelProtocol.h"
+#endif
+
+#ifndef USE_FAKE_MEDIA_STREAMS
+#include "MediaStreamGraphImpl.h"
+#endif
+
+#ifdef XP_WIN
+// We need to undef the MS macro again in case the windows include file
+// got imported after we included nsIDocument.h
+#ifdef CreateEvent
+#undef CreateEvent
+#endif
+#endif // XP_WIN
+
+#ifndef USE_FAKE_MEDIA_STREAMS
+#include "MediaSegment.h"
+#endif
+
+#ifdef USE_FAKE_PCOBSERVER
+#include "FakePCObserver.h"
+#else
+#include "mozilla/dom/PeerConnectionObserverBinding.h"
+#endif
+#include "mozilla/dom/PeerConnectionObserverEnumsBinding.h"
+
+#ifdef MOZ_WEBRTC_OMX
+#include "OMXVideoCodec.h"
+#include "OMXCodecWrapper.h"
+#endif
+
+#define ICE_PARSING "In RTCConfiguration passed to RTCPeerConnection constructor"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+typedef PCObserverString ObString;
+
+static const char* logTag = "PeerConnectionImpl";
+
+// Getting exceptions back down from PCObserver is generally not harmful.
+namespace {
+// This is a terrible hack. The problem is that SuppressException is not
+// inline, and we link this file without libxul in some cases (e.g. for our test
+// setup). So we can't use ErrorResult or IgnoredErrorResult because those call
+// SuppressException... And we can't use FastErrorResult because we can't
+// include BindingUtils.h, because our linking is completely fucked up. Use
+// BaseErrorResult directly. Please do not let me see _anyone_ doing this
+// without really careful review from someone who knows what they are doing.
+class JSErrorResult :
+ public binding_danger::TErrorResult<binding_danger::JustAssertCleanupPolicy>
+{
+public:
+ ~JSErrorResult()
+ {
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ SuppressException();
+#endif
+ }
+};
+
+// The WrapRunnable() macros copy passed-in args and passes them to the function
+// later on the other thread. ErrorResult cannot be passed like this because it
+// disallows copy-semantics.
+//
+// This WrappableJSErrorResult hack solves this by not actually copying the
+// ErrorResult, but creating a new one instead, which works because we don't
+// care about the result.
+//
+// Since this is for JS-calls, these can only be dispatched to the main thread.
+
+class WrappableJSErrorResult {
+public:
+ WrappableJSErrorResult()
+ : mRv(MakeUnique<JSErrorResult>()),
+ isCopy(false) {}
+ WrappableJSErrorResult(const WrappableJSErrorResult &other)
+ : mRv(MakeUnique<JSErrorResult>()),
+ isCopy(true) {}
+ ~WrappableJSErrorResult() {
+ if (isCopy) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+ }
+ operator JSErrorResult &() { return *mRv; }
+ operator ErrorResult &() { return *mRv; }
+private:
+ mozilla::UniquePtr<JSErrorResult> mRv;
+ bool isCopy;
+};
+}
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+static nsresult InitNSSInContent()
+{
+ NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
+
+ if (!XRE_IsContentProcess()) {
+ MOZ_ASSERT_UNREACHABLE("Must be called in content process");
+ return NS_ERROR_FAILURE;
+ }
+
+ static bool nssStarted = false;
+ if (nssStarted) {
+ return NS_OK;
+ }
+
+ if (NSS_NoDB_Init(nullptr) != SECSuccess) {
+ CSFLogError(logTag, "NSS_NoDB_Init failed.");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) {
+ CSFLogError(logTag, "Fail to set up nss cipher suite.");
+ return NS_ERROR_FAILURE;
+ }
+
+ mozilla::psm::DisableMD5();
+
+ nssStarted = true;
+
+ return NS_OK;
+}
+#endif // MOZILLA_INTERNAL_API
+
+namespace mozilla {
+ class DataChannel;
+}
+
+class nsIDOMDataChannel;
+
+PRLogModuleInfo *signalingLogInfo() {
+ static PRLogModuleInfo *logModuleInfo = nullptr;
+ if (!logModuleInfo) {
+ logModuleInfo = PR_NewLogModule("signaling");
+ }
+ return logModuleInfo;
+}
+
+// XXX Workaround for bug 998092 to maintain the existing broken semantics
+template<>
+struct nsISupportsWeakReference::COMTypeInfo<nsSupportsWeakReference, void> {
+ static const nsIID kIID;
+};
+const nsIID nsISupportsWeakReference::COMTypeInfo<nsSupportsWeakReference, void>::kIID = NS_ISUPPORTSWEAKREFERENCE_IID;
+
+namespace mozilla {
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+RTCStatsQuery::RTCStatsQuery(bool internal) :
+ failed(false),
+ internalStats(internal),
+ grabAllLevels(false) {
+}
+
+RTCStatsQuery::~RTCStatsQuery() {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+#endif
+
+NS_IMPL_ISUPPORTS0(PeerConnectionImpl)
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+bool
+PeerConnectionImpl::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector)
+{
+ return PeerConnectionImplBinding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+#endif
+
+bool PCUuidGenerator::Generate(std::string* idp) {
+ nsresult rv;
+
+ if(!mGenerator) {
+ mGenerator = do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ if (!mGenerator) {
+ return false;
+ }
+ }
+
+ nsID id;
+ rv = mGenerator->GenerateUUIDInPlace(&id);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ char buffer[NSID_LENGTH];
+ id.ToProvidedString(buffer);
+ idp->assign(buffer);
+
+ return true;
+}
+
+bool IsPrivateBrowsing(nsPIDOMWindowInner* aWindow)
+{
+#if defined(MOZILLA_EXTERNAL_LINKAGE)
+ return false;
+#else
+ if (!aWindow) {
+ return false;
+ }
+
+ nsIDocument *doc = aWindow->GetExtantDoc();
+ if (!doc) {
+ return false;
+ }
+
+ nsILoadContext *loadContext = doc->GetLoadContext();
+ return loadContext && loadContext->UsePrivateBrowsing();
+#endif
+}
+
+PeerConnectionImpl::PeerConnectionImpl(const GlobalObject* aGlobal)
+: mTimeCard(MOZ_LOG_TEST(signalingLogInfo(),LogLevel::Error) ?
+ create_timecard() : nullptr)
+ , mSignalingState(PCImplSignalingState::SignalingStable)
+ , mIceConnectionState(PCImplIceConnectionState::New)
+ , mIceGatheringState(PCImplIceGatheringState::New)
+ , mDtlsConnected(false)
+ , mWindow(nullptr)
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ , mCertificate(nullptr)
+#else
+ , mIdentity(nullptr)
+#endif
+ , mPrivacyRequested(false)
+ , mSTSThread(nullptr)
+ , mAllowIceLoopback(false)
+ , mAllowIceLinkLocal(false)
+ , mMedia(nullptr)
+ , mUuidGen(MakeUnique<PCUuidGenerator>())
+ , mNumAudioStreams(0)
+ , mNumVideoStreams(0)
+ , mHaveConfiguredCodecs(false)
+ , mHaveDataStream(false)
+ , mAddCandidateErrorCount(0)
+ , mTrickle(true) // TODO(ekr@rtfm.com): Use pref
+ , mNegotiationNeeded(false)
+ , mPrivateWindow(false)
+{
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ MOZ_ASSERT(NS_IsMainThread());
+ auto log = RLogConnector::CreateInstance();
+ if (aGlobal) {
+ mWindow = do_QueryInterface(aGlobal->GetAsSupports());
+ if (IsPrivateBrowsing(mWindow)) {
+ mPrivateWindow = true;
+ log->EnterPrivateMode();
+ }
+ }
+#endif
+ CSFLogInfo(logTag, "%s: PeerConnectionImpl constructor for %s",
+ __FUNCTION__, mHandle.c_str());
+ STAMP_TIMECARD(mTimeCard, "Constructor Completed");
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ mAllowIceLoopback = Preferences::GetBool(
+ "media.peerconnection.ice.loopback", false);
+ mAllowIceLinkLocal = Preferences::GetBool(
+ "media.peerconnection.ice.link_local", false);
+#endif
+ memset(mMaxReceiving, 0, sizeof(mMaxReceiving));
+ memset(mMaxSending, 0, sizeof(mMaxSending));
+}
+
+PeerConnectionImpl::~PeerConnectionImpl()
+{
+ if (mTimeCard) {
+ STAMP_TIMECARD(mTimeCard, "Destructor Invoked");
+ print_timecard(mTimeCard);
+ destroy_timecard(mTimeCard);
+ mTimeCard = nullptr;
+ }
+ // This aborts if not on main thread (in Debug builds)
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ if (mPrivateWindow) {
+ auto * log = RLogConnector::GetInstance();
+ if (log) {
+ log->ExitPrivateMode();
+ }
+ mPrivateWindow = false;
+ }
+#endif
+ if (PeerConnectionCtx::isActive()) {
+ PeerConnectionCtx::GetInstance()->mPeerConnections.erase(mHandle);
+ } else {
+ CSFLogError(logTag, "PeerConnectionCtx is already gone. Ignoring...");
+ }
+
+ CSFLogInfo(logTag, "%s: PeerConnectionImpl destructor invoked for %s",
+ __FUNCTION__, mHandle.c_str());
+
+ Close();
+
+ // Since this and Initialize() occur on MainThread, they can't both be
+ // running at once
+
+ // Right now, we delete PeerConnectionCtx at XPCOM shutdown only, but we
+ // probably want to shut it down more aggressively to save memory. We
+ // could shut down here when there are no uses. It might be more optimal
+ // to release off a timer (and XPCOM Shutdown) to avoid churn
+}
+
+already_AddRefed<DOMMediaStream>
+PeerConnectionImpl::MakeMediaStream()
+{
+ MediaStreamGraph* graph =
+ MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
+ AudioChannel::Normal);
+
+ RefPtr<DOMMediaStream> stream =
+ DOMMediaStream::CreateSourceStreamAsInput(GetWindow(), graph);
+
+ CSFLogDebug(logTag, "Created media stream %p, inner: %p", stream.get(), stream->GetInputStream());
+
+ return stream.forget();
+}
+
+nsresult
+PeerConnectionImpl::CreateRemoteSourceStreamInfo(RefPtr<RemoteSourceStreamInfo>*
+ aInfo,
+ const std::string& aStreamID)
+{
+ MOZ_ASSERT(aInfo);
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+
+ RefPtr<DOMMediaStream> stream = MakeMediaStream();
+ if (!stream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<RemoteSourceStreamInfo> remote;
+ remote = new RemoteSourceStreamInfo(stream.forget(), mMedia, aStreamID);
+ *aInfo = remote;
+
+ return NS_OK;
+}
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+/**
+ * In JS, an RTCConfiguration looks like this:
+ *
+ * { "iceServers": [ { url:"stun:stun.example.org" },
+ * { url:"turn:turn.example.org?transport=udp",
+ * username: "jib", credential:"mypass"} ] }
+ *
+ * This function converts that into an internal PeerConnectionConfiguration
+ * object.
+ */
+nsresult
+PeerConnectionConfiguration::Init(const RTCConfiguration& aSrc)
+{
+ if (aSrc.mIceServers.WasPassed()) {
+ for (size_t i = 0; i < aSrc.mIceServers.Value().Length(); i++) {
+ nsresult rv = AddIceServer(aSrc.mIceServers.Value()[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ switch (aSrc.mBundlePolicy) {
+ case dom::RTCBundlePolicy::Balanced:
+ setBundlePolicy(kBundleBalanced);
+ break;
+ case dom::RTCBundlePolicy::Max_compat:
+ setBundlePolicy(kBundleMaxCompat);
+ break;
+ case dom::RTCBundlePolicy::Max_bundle:
+ setBundlePolicy(kBundleMaxBundle);
+ break;
+ default:
+ MOZ_CRASH();
+ }
+
+ switch (aSrc.mIceTransportPolicy) {
+ case dom::RTCIceTransportPolicy::Relay:
+ setIceTransportPolicy(NrIceCtx::ICE_POLICY_RELAY);
+ break;
+ case dom::RTCIceTransportPolicy::All:
+ if (Preferences::GetBool("media.peerconnection.ice.no_host", false)) {
+ setIceTransportPolicy(NrIceCtx::ICE_POLICY_NO_HOST);
+ } else {
+ setIceTransportPolicy(NrIceCtx::ICE_POLICY_ALL);
+ }
+ break;
+ default:
+ MOZ_CRASH();
+ }
+ return NS_OK;
+}
+
+nsresult
+PeerConnectionConfiguration::AddIceServer(const RTCIceServer &aServer)
+{
+ NS_ENSURE_STATE(aServer.mUrls.WasPassed());
+ NS_ENSURE_STATE(aServer.mUrls.Value().IsStringSequence());
+ auto &urls = aServer.mUrls.Value().GetAsStringSequence();
+ for (size_t i = 0; i < urls.Length(); i++) {
+ // Without STUN/TURN handlers, NS_NewURI returns nsSimpleURI rather than
+ // nsStandardURL. To parse STUN/TURN URI's to spec
+ // http://tools.ietf.org/html/draft-nandakumar-rtcweb-stun-uri-02#section-3
+ // http://tools.ietf.org/html/draft-petithuguenin-behave-turn-uri-03#section-3
+ // we parse out the query-string, and use ParseAuthority() on the rest
+ RefPtr<nsIURI> url;
+ nsresult rv = NS_NewURI(getter_AddRefs(url), urls[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isStun = false, isStuns = false, isTurn = false, isTurns = false;
+ url->SchemeIs("stun", &isStun);
+ url->SchemeIs("stuns", &isStuns);
+ url->SchemeIs("turn", &isTurn);
+ url->SchemeIs("turns", &isTurns);
+ if (!(isStun || isStuns || isTurn || isTurns)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (isTurns || isStuns) {
+ continue; // TODO: Support TURNS and STUNS (Bug 1056934)
+ }
+ nsAutoCString spec;
+ rv = url->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // TODO(jib@mozilla.com): Revisit once nsURI supports STUN/TURN (Bug 833509)
+ int32_t port;
+ nsAutoCString host;
+ nsAutoCString transport;
+ {
+ uint32_t hostPos;
+ int32_t hostLen;
+ nsAutoCString path;
+ rv = url->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Tolerate query-string + parse 'transport=[udp|tcp]' by hand.
+ int32_t questionmark = path.FindChar('?');
+ if (questionmark >= 0) {
+ const nsCString match = NS_LITERAL_CSTRING("transport=");
+
+ for (int32_t i = questionmark, endPos; i >= 0; i = endPos) {
+ endPos = path.FindCharInSet("&", i + 1);
+ const nsDependentCSubstring fieldvaluepair = Substring(path, i + 1,
+ endPos);
+ if (StringBeginsWith(fieldvaluepair, match)) {
+ transport = Substring(fieldvaluepair, match.Length());
+ ToLowerCase(transport);
+ }
+ }
+ path.SetLength(questionmark);
+ }
+
+ rv = net_GetAuthURLParser()->ParseAuthority(path.get(), path.Length(),
+ nullptr, nullptr,
+ nullptr, nullptr,
+ &hostPos, &hostLen, &port);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!hostLen) {
+ return NS_ERROR_FAILURE;
+ }
+ if (hostPos > 1) /* The username was removed */
+ return NS_ERROR_FAILURE;
+ path.Mid(host, hostPos, hostLen);
+ }
+ if (port == -1)
+ port = (isStuns || isTurns)? 5349 : 3478;
+
+ if (isTurn || isTurns) {
+ NS_ConvertUTF16toUTF8 credential(aServer.mCredential.Value());
+ NS_ConvertUTF16toUTF8 username(aServer.mUsername.Value());
+
+ if (!addTurnServer(host.get(), port,
+ username.get(),
+ credential.get(),
+ (transport.IsEmpty() ?
+ kNrIceTransportUdp : transport.get()))) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ if (!addStunServer(host.get(), port, (transport.IsEmpty() ?
+ kNrIceTransportUdp : transport.get()))) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+ return NS_OK;
+}
+#endif
+
+nsresult
+PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver,
+ nsGlobalWindow* aWindow,
+ const PeerConnectionConfiguration& aConfiguration,
+ nsISupports* aThread)
+{
+ nsresult res;
+
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aThread);
+ if (!mThread) {
+ mThread = do_QueryInterface(aThread);
+ MOZ_ASSERT(mThread);
+ }
+ CheckThread();
+
+ mPCObserver = do_GetWeakReference(&aObserver);
+
+ // Find the STS thread
+
+ mSTSThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res);
+ MOZ_ASSERT(mSTSThread);
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+
+ // Initialize NSS if we are in content process. For chrome process, NSS should already
+ // been initialized.
+ if (XRE_IsParentProcess()) {
+ // This code interferes with the C++ unit test startup code.
+ nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &res);
+ NS_ENSURE_SUCCESS(res, res);
+ } else {
+ NS_ENSURE_SUCCESS(res = InitNSSInContent(), res);
+ }
+
+ // Currently no standalone unit tests for DataChannel,
+ // which is the user of mWindow
+ MOZ_ASSERT(aWindow);
+ mWindow = aWindow->AsInner();
+ NS_ENSURE_STATE(mWindow);
+#endif // MOZILLA_INTERNAL_API
+
+ PRTime timestamp = PR_Now();
+ // Ok if we truncate this.
+ char temp[128];
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ nsAutoCString locationCStr;
+
+ if (nsCOMPtr<nsIDOMLocation> location = mWindow->GetLocation()) {
+ nsAutoString locationAStr;
+ location->ToString(locationAStr);
+
+ CopyUTF16toUTF8(locationAStr, locationCStr);
+ }
+
+ SprintfLiteral(temp,
+ "%" PRIu64 " (id=%" PRIu64 " url=%s)",
+ static_cast<uint64_t>(timestamp),
+ static_cast<uint64_t>(mWindow ? mWindow->WindowID() : 0),
+ locationCStr.get() ? locationCStr.get() : "NULL");
+
+#else
+ SprintfLiteral(temp, "%" PRIu64, static_cast<uint64_t>(timestamp));
+#endif // MOZILLA_INTERNAL_API
+
+ mName = temp;
+
+ // Generate a random handle
+ unsigned char handle_bin[8];
+ SECStatus rv;
+ rv = PK11_GenerateRandom(handle_bin, sizeof(handle_bin));
+ if (rv != SECSuccess) {
+ MOZ_CRASH();
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ char hex[17];
+ SprintfLiteral(hex, "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x",
+ handle_bin[0],
+ handle_bin[1],
+ handle_bin[2],
+ handle_bin[3],
+ handle_bin[4],
+ handle_bin[5],
+ handle_bin[6],
+ handle_bin[7]);
+
+ mHandle = hex;
+
+ STAMP_TIMECARD(mTimeCard, "Initializing PC Ctx");
+ res = PeerConnectionCtx::InitializeGlobal(mThread, mSTSThread);
+ NS_ENSURE_SUCCESS(res, res);
+
+ mMedia = new PeerConnectionMedia(this);
+
+ // Connect ICE slots.
+ mMedia->SignalIceGatheringStateChange.connect(
+ this,
+ &PeerConnectionImpl::IceGatheringStateChange);
+ mMedia->SignalUpdateDefaultCandidate.connect(
+ this,
+ &PeerConnectionImpl::UpdateDefaultCandidate);
+ mMedia->SignalEndOfLocalCandidates.connect(
+ this,
+ &PeerConnectionImpl::EndOfLocalCandidates);
+ mMedia->SignalIceConnectionStateChange.connect(
+ this,
+ &PeerConnectionImpl::IceConnectionStateChange);
+
+ mMedia->SignalCandidate.connect(this, &PeerConnectionImpl::CandidateReady);
+
+ // Initialize the media object.
+ res = mMedia->Init(aConfiguration.getStunServers(),
+ aConfiguration.getTurnServers(),
+ aConfiguration.getIceTransportPolicy());
+ if (NS_FAILED(res)) {
+ CSFLogError(logTag, "%s: Couldn't initialize media object", __FUNCTION__);
+ return res;
+ }
+
+ PeerConnectionCtx::GetInstance()->mPeerConnections[mHandle] = this;
+
+ mJsepSession = MakeUnique<JsepSessionImpl>(mName,
+ MakeUnique<PCUuidGenerator>());
+
+ res = mJsepSession->Init();
+ if (NS_FAILED(res)) {
+ CSFLogError(logTag, "%s: Couldn't init JSEP Session, res=%u",
+ __FUNCTION__,
+ static_cast<unsigned>(res));
+ return res;
+ }
+
+ res = mJsepSession->SetIceCredentials(mMedia->ice_ctx()->ufrag(),
+ mMedia->ice_ctx()->pwd());
+ if (NS_FAILED(res)) {
+ CSFLogError(logTag, "%s: Couldn't set ICE credentials, res=%u",
+ __FUNCTION__,
+ static_cast<unsigned>(res));
+ return res;
+ }
+
+#if defined(MOZILLA_EXTERNAL_LINKAGE)
+ {
+ mIdentity = DtlsIdentity::Generate();
+ if (!mIdentity) {
+ return NS_ERROR_FAILURE;
+ }
+
+ std::vector<uint8_t> fingerprint;
+ res = CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM,
+ &fingerprint);
+ NS_ENSURE_SUCCESS(res, res);
+
+ res = mJsepSession->AddDtlsFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM,
+ fingerprint);
+ NS_ENSURE_SUCCESS(res, res);
+ }
+#endif
+
+ res = mJsepSession->SetBundlePolicy(aConfiguration.getBundlePolicy());
+ if (NS_FAILED(res)) {
+ CSFLogError(logTag, "%s: Couldn't set bundle policy, res=%u, error=%s",
+ __FUNCTION__,
+ static_cast<unsigned>(res),
+ mJsepSession->GetLastError().c_str());
+ return res;
+ }
+
+ return NS_OK;
+}
+
+#ifndef MOZILLA_EXTERNAL_LINKAGE
+void
+PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver,
+ nsGlobalWindow& aWindow,
+ const RTCConfiguration& aConfiguration,
+ nsISupports* aThread,
+ ErrorResult &rv)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aThread);
+ mThread = do_QueryInterface(aThread);
+
+ PeerConnectionConfiguration converted;
+ nsresult res = converted.Init(aConfiguration);
+ if (NS_FAILED(res)) {
+ CSFLogError(logTag, "%s: Invalid RTCConfiguration", __FUNCTION__);
+ rv.Throw(res);
+ return;
+ }
+
+ res = Initialize(aObserver, &aWindow, converted, aThread);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ }
+
+ if (!aConfiguration.mPeerIdentity.IsEmpty()) {
+ mPeerIdentity = new PeerIdentity(aConfiguration.mPeerIdentity);
+ mPrivacyRequested = true;
+ }
+}
+#endif
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+void
+PeerConnectionImpl::SetCertificate(mozilla::dom::RTCCertificate& aCertificate)
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(!mCertificate, "This can only be called once");
+ mCertificate = &aCertificate;
+
+ std::vector<uint8_t> fingerprint;
+ nsresult rv = CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM,
+ &fingerprint);
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "%s: Couldn't calculate fingerprint, rv=%u",
+ __FUNCTION__, static_cast<unsigned>(rv));
+ mCertificate = nullptr;
+ return;
+ }
+ rv = mJsepSession->AddDtlsFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM,
+ fingerprint);
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "%s: Couldn't set DTLS credentials, rv=%u",
+ __FUNCTION__, static_cast<unsigned>(rv));
+ mCertificate = nullptr;
+ }
+}
+
+const RefPtr<mozilla::dom::RTCCertificate>&
+PeerConnectionImpl::Certificate() const
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mCertificate;
+}
+#endif
+
+RefPtr<DtlsIdentity>
+PeerConnectionImpl::Identity() const
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ MOZ_ASSERT(mCertificate);
+ return mCertificate->CreateDtlsIdentity();
+#else
+ RefPtr<DtlsIdentity> id = mIdentity;
+ return id;
+#endif
+}
+
+class CompareCodecPriority {
+ public:
+ void SetPreferredCodec(int32_t preferredCodec) {
+ // This pref really ought to be a string, preferably something like
+ // "H264" or "VP8" instead of a payload type.
+ // Bug 1101259.
+ std::ostringstream os;
+ os << preferredCodec;
+ mPreferredCodec = os.str();
+ }
+
+ bool operator()(JsepCodecDescription* lhs,
+ JsepCodecDescription* rhs) const {
+ if (!mPreferredCodec.empty() &&
+ lhs->mDefaultPt == mPreferredCodec &&
+ rhs->mDefaultPt != mPreferredCodec) {
+ return true;
+ }
+
+ if (lhs->mStronglyPreferred && !rhs->mStronglyPreferred) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private:
+ std::string mPreferredCodec;
+};
+
+class ConfigureCodec {
+ public:
+ explicit ConfigureCodec(nsCOMPtr<nsIPrefBranch>& branch) :
+ mHardwareH264Enabled(false),
+ mHardwareH264Supported(false),
+ mSoftwareH264Enabled(false),
+ mH264Enabled(false),
+ mVP9Enabled(false),
+ mH264Level(13), // minimum suggested for WebRTC spec
+ mH264MaxBr(0), // Unlimited
+ mH264MaxMbps(0), // Unlimited
+ mVP8MaxFs(0),
+ mVP8MaxFr(0),
+ mUseTmmbr(false),
+ mUseRemb(false),
+ mUseAudioFec(false),
+ mRedUlpfecEnabled(false),
+ mDtmfEnabled(false)
+ {
+#ifdef MOZ_WEBRTC_OMX
+ // Check to see if what HW codecs are available (not in use) at this moment.
+ // Note that streaming video decode can reserve a decoder
+
+ // XXX See bug 1018791 Implement W3 codec reservation policy
+ // Note that currently, OMXCodecReservation needs to be held by an sp<> because it puts
+ // 'this' into an sp<EventListener> to talk to the resource reservation code
+
+ // This pref is a misnomer; it is solely for h264 _hardware_ support.
+ branch->GetBoolPref("media.peerconnection.video.h264_enabled",
+ &mHardwareH264Enabled);
+
+ if (mHardwareH264Enabled) {
+ // Ok, it is preffed on. Can we actually do it?
+ android::sp<android::OMXCodecReservation> encode = new android::OMXCodecReservation(true);
+ android::sp<android::OMXCodecReservation> decode = new android::OMXCodecReservation(false);
+
+ // Currently we just check if they're available right now, which will fail if we're
+ // trying to call ourself, for example. It will work for most real-world cases, like
+ // if we try to add a person to a 2-way call to make a 3-way mesh call
+ if (encode->ReserveOMXCodec() && decode->ReserveOMXCodec()) {
+ CSFLogDebug( logTag, "%s: H264 hardware codec available", __FUNCTION__);
+ mHardwareH264Supported = true;
+ }
+ }
+
+#endif // MOZ_WEBRTC_OMX
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ mSoftwareH264Enabled = PeerConnectionCtx::GetInstance()->gmpHasH264();
+#else
+ // For unit-tests
+ mSoftwareH264Enabled = true;
+#endif
+
+ mH264Enabled = mHardwareH264Supported || mSoftwareH264Enabled;
+
+ branch->GetIntPref("media.navigator.video.h264.level", &mH264Level);
+ mH264Level &= 0xFF;
+
+ branch->GetIntPref("media.navigator.video.h264.max_br", &mH264MaxBr);
+
+#ifdef MOZ_WEBRTC_OMX
+ // Level 1.2; but let's allow CIF@30 or QVGA@30+ by default
+ mH264MaxMbps = 11880;
+#endif
+
+ branch->GetIntPref("media.navigator.video.h264.max_mbps", &mH264MaxMbps);
+
+ branch->GetBoolPref("media.peerconnection.video.vp9_enabled",
+ &mVP9Enabled);
+
+ branch->GetIntPref("media.navigator.video.max_fs", &mVP8MaxFs);
+ if (mVP8MaxFs <= 0) {
+ mVP8MaxFs = 12288; // We must specify something other than 0
+ }
+
+ branch->GetIntPref("media.navigator.video.max_fr", &mVP8MaxFr);
+ if (mVP8MaxFr <= 0) {
+ mVP8MaxFr = 60; // We must specify something other than 0
+ }
+
+ // TMMBR is enabled from a pref in about:config
+ branch->GetBoolPref("media.navigator.video.use_tmmbr", &mUseTmmbr);
+
+ // REMB is enabled by default, but can be disabled from about:config
+ branch->GetBoolPref("media.navigator.video.use_remb", &mUseRemb);
+
+ branch->GetBoolPref("media.navigator.audio.use_fec", &mUseAudioFec);
+
+ branch->GetBoolPref("media.navigator.video.red_ulpfec_enabled",
+ &mRedUlpfecEnabled);
+
+ // media.peerconnection.dtmf.enabled controls both sdp generation for
+ // DTMF support as well as DTMF exposure to DOM
+ branch->GetBoolPref("media.peerconnection.dtmf.enabled", &mDtmfEnabled);
+ }
+
+ void operator()(JsepCodecDescription* codec) const
+ {
+ switch (codec->mType) {
+ case SdpMediaSection::kAudio:
+ {
+ JsepAudioCodecDescription& audioCodec =
+ static_cast<JsepAudioCodecDescription&>(*codec);
+ if (audioCodec.mName == "opus") {
+ audioCodec.mFECEnabled = mUseAudioFec;
+ } else if (audioCodec.mName == "telephone-event") {
+ audioCodec.mEnabled = mDtmfEnabled;
+ }
+ }
+ break;
+ case SdpMediaSection::kVideo:
+ {
+ JsepVideoCodecDescription& videoCodec =
+ static_cast<JsepVideoCodecDescription&>(*codec);
+
+ if (videoCodec.mName == "H264") {
+ // Override level
+ videoCodec.mProfileLevelId &= 0xFFFF00;
+ videoCodec.mProfileLevelId |= mH264Level;
+
+ videoCodec.mConstraints.maxBr = mH264MaxBr;
+
+ videoCodec.mConstraints.maxMbps = mH264MaxMbps;
+
+ // Might disable it, but we set up other params anyway
+ videoCodec.mEnabled = mH264Enabled;
+
+ if (videoCodec.mPacketizationMode == 0 && !mSoftwareH264Enabled) {
+ // We're assuming packetization mode 0 is unsupported by
+ // hardware.
+ videoCodec.mEnabled = false;
+ }
+
+ if (mHardwareH264Supported) {
+ videoCodec.mStronglyPreferred = true;
+ }
+ } else if (videoCodec.mName == "red") {
+ videoCodec.mEnabled = mRedUlpfecEnabled;
+ } else if (videoCodec.mName == "ulpfec") {
+ videoCodec.mEnabled = mRedUlpfecEnabled;
+ } else if (videoCodec.mName == "VP8" || videoCodec.mName == "VP9") {
+ if (videoCodec.mName == "VP9" && !mVP9Enabled) {
+ videoCodec.mEnabled = false;
+ break;
+ }
+ videoCodec.mConstraints.maxFs = mVP8MaxFs;
+ videoCodec.mConstraints.maxFps = mVP8MaxFr;
+ }
+
+ if (mUseTmmbr) {
+ videoCodec.EnableTmmbr();
+ }
+ if (mUseRemb) {
+ videoCodec.EnableRemb();
+ }
+ }
+ break;
+ case SdpMediaSection::kText:
+ case SdpMediaSection::kApplication:
+ case SdpMediaSection::kMessage:
+ {} // Nothing to configure for these.
+ }
+ }
+
+ private:
+ bool mHardwareH264Enabled;
+ bool mHardwareH264Supported;
+ bool mSoftwareH264Enabled;
+ bool mH264Enabled;
+ bool mVP9Enabled;
+ int32_t mH264Level;
+ int32_t mH264MaxBr;
+ int32_t mH264MaxMbps;
+ int32_t mVP8MaxFs;
+ int32_t mVP8MaxFr;
+ bool mUseTmmbr;
+ bool mUseRemb;
+ bool mUseAudioFec;
+ bool mRedUlpfecEnabled;
+ bool mDtmfEnabled;
+};
+
+class ConfigureRedCodec {
+ public:
+ explicit ConfigureRedCodec(nsCOMPtr<nsIPrefBranch>& branch,
+ std::vector<uint8_t>* redundantEncodings) :
+ mRedundantEncodings(redundantEncodings)
+ {
+ // if we wanted to override or modify which encodings are considered
+ // for redundant encodings, we'd probably want to handle it here by
+ // checking prefs modifying the operator() code below
+ }
+
+ void operator()(JsepCodecDescription* codec) const
+ {
+ if (codec->mType == SdpMediaSection::kVideo &&
+ codec->mEnabled == false) {
+ uint8_t pt = (uint8_t)strtoul(codec->mDefaultPt.c_str(), nullptr, 10);
+ // don't search for the codec payload type unless we have a valid
+ // conversion (non-zero)
+ if (pt != 0) {
+ std::vector<uint8_t>::iterator it =
+ std::find(mRedundantEncodings->begin(),
+ mRedundantEncodings->end(),
+ pt);
+ if (it != mRedundantEncodings->end()) {
+ mRedundantEncodings->erase(it);
+ }
+ }
+ }
+ }
+
+ private:
+ std::vector<uint8_t>* mRedundantEncodings;
+};
+
+nsresult
+PeerConnectionImpl::ConfigureJsepSessionCodecs() {
+ nsresult res;
+ nsCOMPtr<nsIPrefService> prefs =
+ do_GetService("@mozilla.org/preferences-service;1", &res);
+
+ if (NS_FAILED(res)) {
+ CSFLogError(logTag, "%s: Couldn't get prefs service, res=%u",
+ __FUNCTION__,
+ static_cast<unsigned>(res));
+ return res;
+ }
+
+ nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
+ if (!branch) {
+ CSFLogError(logTag, "%s: Couldn't get prefs branch", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ ConfigureCodec configurer(branch);
+ mJsepSession->ForEachCodec(configurer);
+
+ // first find the red codec description
+ std::vector<JsepCodecDescription*>& codecs = mJsepSession->Codecs();
+ JsepVideoCodecDescription* redCodec = nullptr;
+ for (auto codec : codecs) {
+ // we only really care about finding the RED codec if it is
+ // enabled
+ if (codec->mName == "red" && codec->mEnabled) {
+ redCodec = static_cast<JsepVideoCodecDescription*>(codec);
+ break;
+ }
+ }
+ // if red codec was found, configure it for the other enabled codecs
+ if (redCodec) {
+ ConfigureRedCodec configureRed(branch, &(redCodec->mRedundantEncodings));
+ mJsepSession->ForEachCodec(configureRed);
+ }
+
+ // We use this to sort the list of codecs once everything is configured
+ CompareCodecPriority comparator;
+
+ // Sort by priority
+ int32_t preferredCodec = 0;
+ branch->GetIntPref("media.navigator.video.preferred_codec",
+ &preferredCodec);
+
+ if (preferredCodec) {
+ comparator.SetPreferredCodec(preferredCodec);
+ }
+
+ mJsepSession->SortCodecs(comparator);
+ return NS_OK;
+}
+
+// Data channels won't work without a window, so in order for the C++ unit
+// tests to work (it doesn't have a window available) we ifdef the following
+// two implementations.
+NS_IMETHODIMP
+PeerConnectionImpl::EnsureDataConnection(uint16_t aNumstreams)
+{
+ PC_AUTO_ENTER_API_CALL(false);
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ if (mDataConnection) {
+ CSFLogDebug(logTag,"%s DataConnection already connected",__FUNCTION__);
+ // Ignore the request to connect when already connected. This entire
+ // implementation is temporary. Ignore aNumstreams as it's merely advisory
+ // and we increase the number of streams dynamically as needed.
+ return NS_OK;
+ }
+ mDataConnection = new DataChannelConnection(this);
+ if (!mDataConnection->Init(5000, aNumstreams, true)) {
+ CSFLogError(logTag,"%s DataConnection Init Failed",__FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+ CSFLogDebug(logTag,"%s DataChannelConnection %p attached to %s",
+ __FUNCTION__, (void*) mDataConnection.get(), mHandle.c_str());
+#endif
+ return NS_OK;
+}
+
+nsresult
+PeerConnectionImpl::GetDatachannelParameters(
+ const mozilla::JsepApplicationCodecDescription** datachannelCodec,
+ uint16_t* level) const {
+
+ auto trackPairs = mJsepSession->GetNegotiatedTrackPairs();
+ for (auto j = trackPairs.begin(); j != trackPairs.end(); ++j) {
+ JsepTrackPair& trackPair = *j;
+
+ bool sendDataChannel =
+ trackPair.mSending &&
+ trackPair.mSending->GetMediaType() == SdpMediaSection::kApplication;
+ bool recvDataChannel =
+ trackPair.mReceiving &&
+ trackPair.mReceiving->GetMediaType() == SdpMediaSection::kApplication;
+ (void)recvDataChannel;
+ MOZ_ASSERT(sendDataChannel == recvDataChannel);
+
+ if (sendDataChannel) {
+ // This will release assert if there is no such index, and that's ok
+ const JsepTrackEncoding& encoding =
+ trackPair.mSending->GetNegotiatedDetails()->GetEncoding(0);
+
+ if (encoding.GetCodecs().empty()) {
+ CSFLogError(logTag, "%s: Negotiated m=application with no codec. "
+ "This is likely to be broken.",
+ __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ for (const JsepCodecDescription* codec : encoding.GetCodecs()) {
+ if (codec->mType != SdpMediaSection::kApplication) {
+ CSFLogError(logTag, "%s: Codec type for m=application was %u, this "
+ "is a bug.",
+ __FUNCTION__,
+ static_cast<unsigned>(codec->mType));
+ MOZ_ASSERT(false, "Codec for m=application was not \"application\"");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (codec->mName != "webrtc-datachannel") {
+ CSFLogWarn(logTag, "%s: Codec for m=application was not "
+ "webrtc-datachannel (was instead %s). ",
+ __FUNCTION__,
+ codec->mName.c_str());
+ continue;
+ }
+
+ *datachannelCodec =
+ static_cast<const JsepApplicationCodecDescription*>(codec);
+ if (trackPair.mBundleLevel.isSome()) {
+ *level = static_cast<uint16_t>(*trackPair.mBundleLevel);
+ } else {
+ *level = static_cast<uint16_t>(trackPair.mLevel);
+ }
+ return NS_OK;
+ }
+ }
+ }
+
+ *datachannelCodec = nullptr;
+ *level = 0;
+ return NS_OK;
+}
+
+/* static */
+void
+PeerConnectionImpl::DeferredAddTrackToJsepSession(
+ const std::string& pcHandle,
+ SdpMediaSection::MediaType type,
+ const std::string& streamId,
+ const std::string& trackId)
+{
+ PeerConnectionWrapper wrapper(pcHandle);
+
+ if (wrapper.impl()) {
+ if (!PeerConnectionCtx::GetInstance()->isReady()) {
+ MOZ_CRASH("Why is DeferredAddTrackToJsepSession being executed when the "
+ "PeerConnectionCtx isn't ready?");
+ }
+ wrapper.impl()->AddTrackToJsepSession(type, streamId, trackId);
+ }
+}
+
+nsresult
+PeerConnectionImpl::AddTrackToJsepSession(SdpMediaSection::MediaType type,
+ const std::string& streamId,
+ const std::string& trackId)
+{
+ nsresult res = ConfigureJsepSessionCodecs();
+ if (NS_FAILED(res)) {
+ CSFLogError(logTag, "Failed to configure codecs");
+ return res;
+ }
+
+ res = mJsepSession->AddTrack(
+ new JsepTrack(type, streamId, trackId, sdp::kSend));
+
+ if (NS_FAILED(res)) {
+ std::string errorString = mJsepSession->GetLastError();
+ CSFLogError(logTag, "%s (%s) : pc = %s, error = %s",
+ __FUNCTION__,
+ type == SdpMediaSection::kAudio ? "audio" : "video",
+ mHandle.c_str(),
+ errorString.c_str());
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+PeerConnectionImpl::InitializeDataChannel()
+{
+ PC_AUTO_ENTER_API_CALL(false);
+ CSFLogDebug(logTag, "%s", __FUNCTION__);
+
+ const JsepApplicationCodecDescription* codec;
+ uint16_t level;
+ nsresult rv = GetDatachannelParameters(&codec, &level);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!codec) {
+ CSFLogDebug(logTag, "%s: We did not negotiate datachannel", __FUNCTION__);
+ return NS_OK;
+ }
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ uint32_t channels = codec->mChannels;
+ if (channels > MAX_NUM_STREAMS) {
+ channels = MAX_NUM_STREAMS;
+ }
+
+ rv = EnsureDataConnection(channels);
+ if (NS_SUCCEEDED(rv)) {
+ uint16_t localport = 5000;
+ uint16_t remoteport = 0;
+ // The logic that reflects the remote payload type is what sets the remote
+ // port here.
+ if (!codec->GetPtAsInt(&remoteport)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // use the specified TransportFlow
+ RefPtr<TransportFlow> flow = mMedia->GetTransportFlow(level, false).get();
+ CSFLogDebug(logTag, "Transportflow[%u] = %p",
+ static_cast<unsigned>(level), flow.get());
+ if (flow) {
+ if (mDataConnection->ConnectViaTransportFlow(flow,
+ localport,
+ remoteport)) {
+ return NS_OK;
+ }
+ }
+ // If we inited the DataConnection, call Destroy() before releasing it
+ mDataConnection->Destroy();
+ }
+ mDataConnection = nullptr;
+#endif
+ return NS_ERROR_FAILURE;
+}
+
+already_AddRefed<nsDOMDataChannel>
+PeerConnectionImpl::CreateDataChannel(const nsAString& aLabel,
+ const nsAString& aProtocol,
+ uint16_t aType,
+ bool outOfOrderAllowed,
+ uint16_t aMaxTime,
+ uint16_t aMaxNum,
+ bool aExternalNegotiated,
+ uint16_t aStream,
+ ErrorResult &rv)
+{
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ RefPtr<nsDOMDataChannel> result;
+ rv = CreateDataChannel(aLabel, aProtocol, aType, outOfOrderAllowed,
+ aMaxTime, aMaxNum, aExternalNegotiated,
+ aStream, getter_AddRefs(result));
+ return result.forget();
+#else
+ return nullptr;
+#endif
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::CreateDataChannel(const nsAString& aLabel,
+ const nsAString& aProtocol,
+ uint16_t aType,
+ bool outOfOrderAllowed,
+ uint16_t aMaxTime,
+ uint16_t aMaxNum,
+ bool aExternalNegotiated,
+ uint16_t aStream,
+ nsDOMDataChannel** aRetval)
+{
+ PC_AUTO_ENTER_API_CALL(false);
+ MOZ_ASSERT(aRetval);
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ RefPtr<DataChannel> dataChannel;
+ DataChannelConnection::Type theType =
+ static_cast<DataChannelConnection::Type>(aType);
+
+ nsresult rv = EnsureDataConnection(WEBRTC_DATACHANNEL_STREAMS_DEFAULT);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ dataChannel = mDataConnection->Open(
+ NS_ConvertUTF16toUTF8(aLabel), NS_ConvertUTF16toUTF8(aProtocol), theType,
+ !outOfOrderAllowed,
+ aType == DataChannelConnection::PARTIAL_RELIABLE_REXMIT ? aMaxNum :
+ (aType == DataChannelConnection::PARTIAL_RELIABLE_TIMED ? aMaxTime : 0),
+ nullptr, nullptr, aExternalNegotiated, aStream
+ );
+ NS_ENSURE_TRUE(dataChannel,NS_ERROR_FAILURE);
+
+ CSFLogDebug(logTag, "%s: making DOMDataChannel", __FUNCTION__);
+
+ if (!mHaveDataStream) {
+
+ std::string streamId;
+ std::string trackId;
+
+ // Generate random ids because these aren't linked to any local streams.
+ if (!mUuidGen->Generate(&streamId)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!mUuidGen->Generate(&trackId)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<JsepTrack> track(new JsepTrack(
+ mozilla::SdpMediaSection::kApplication,
+ streamId,
+ trackId,
+ sdp::kSend));
+
+ rv = mJsepSession->AddTrack(track);
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "%s: Failed to add application track.",
+ __FUNCTION__);
+ return rv;
+ }
+ mHaveDataStream = true;
+ OnNegotiationNeeded();
+ }
+ nsIDOMDataChannel *retval;
+ rv = NS_NewDOMDataChannel(dataChannel.forget(), mWindow, &retval);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *aRetval = static_cast<nsDOMDataChannel*>(retval);
+#endif
+ return NS_OK;
+}
+
+// do_QueryObjectReferent() - Helps get PeerConnectionObserver from nsWeakPtr.
+//
+// nsWeakPtr deals in XPCOM interfaces, while webidl bindings are concrete objs.
+// TODO: Turn this into a central (template) function somewhere (Bug 939178)
+//
+// Without it, each weak-ref call in this file would look like this:
+//
+// nsCOMPtr<nsISupportsWeakReference> tmp = do_QueryReferent(mPCObserver);
+// if (!tmp) {
+// return;
+// }
+// RefPtr<nsSupportsWeakReference> tmp2 = do_QueryObject(tmp);
+// RefPtr<PeerConnectionObserver> pco = static_cast<PeerConnectionObserver*>(&*tmp2);
+
+static already_AddRefed<PeerConnectionObserver>
+do_QueryObjectReferent(nsIWeakReference* aRawPtr) {
+ nsCOMPtr<nsISupportsWeakReference> tmp = do_QueryReferent(aRawPtr);
+ if (!tmp) {
+ return nullptr;
+ }
+ RefPtr<nsSupportsWeakReference> tmp2 = do_QueryObject(tmp);
+ RefPtr<PeerConnectionObserver> tmp3 = static_cast<PeerConnectionObserver*>(&*tmp2);
+ return tmp3.forget();
+}
+
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+// Not a member function so that we don't need to keep the PC live.
+static void NotifyDataChannel_m(RefPtr<nsIDOMDataChannel> aChannel,
+ RefPtr<PeerConnectionObserver> aObserver)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ JSErrorResult rv;
+ RefPtr<nsDOMDataChannel> channel = static_cast<nsDOMDataChannel*>(&*aChannel);
+ aObserver->NotifyDataChannel(*channel, rv);
+ NS_DataChannelAppReady(aChannel);
+}
+#endif
+
+void
+PeerConnectionImpl::NotifyDataChannel(already_AddRefed<DataChannel> aChannel)
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+
+ // XXXkhuey this is completely fucked up. We can't use RefPtr<DataChannel>
+ // here because DataChannel's AddRef/Release are non-virtual and not visible
+ // if !MOZILLA_INTERNAL_API, but this function leaks the DataChannel if
+ // !MOZILLA_INTERNAL_API because it never transfers the ref to
+ // NS_NewDOMDataChannel.
+ DataChannel* channel = aChannel.take();
+ MOZ_ASSERT(channel);
+
+ CSFLogDebug(logTag, "%s: channel: %p", __FUNCTION__, channel);
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ nsCOMPtr<nsIDOMDataChannel> domchannel;
+ nsresult rv = NS_NewDOMDataChannel(already_AddRefed<DataChannel>(channel),
+ mWindow, getter_AddRefs(domchannel));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ mHaveDataStream = true;
+
+ RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
+ if (!pco) {
+ return;
+ }
+
+ RUN_ON_THREAD(mThread,
+ WrapRunnableNM(NotifyDataChannel_m,
+ domchannel.get(),
+ pco),
+ NS_DISPATCH_NORMAL);
+#endif
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::CreateOffer(const RTCOfferOptions& aOptions)
+{
+ JsepOfferOptions options;
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // convert the RTCOfferOptions to JsepOfferOptions
+ if (aOptions.mOfferToReceiveAudio.WasPassed()) {
+ options.mOfferToReceiveAudio =
+ mozilla::Some(size_t(aOptions.mOfferToReceiveAudio.Value()));
+ }
+
+ if (aOptions.mOfferToReceiveVideo.WasPassed()) {
+ options.mOfferToReceiveVideo =
+ mozilla::Some(size_t(aOptions.mOfferToReceiveVideo.Value()));
+ }
+
+ options.mIceRestart = mozilla::Some(aOptions.mIceRestart);
+
+ if (aOptions.mMozDontOfferDataChannel.WasPassed()) {
+ options.mDontOfferDataChannel =
+ mozilla::Some(aOptions.mMozDontOfferDataChannel.Value());
+ }
+#endif
+ return CreateOffer(options);
+}
+
+static void DeferredCreateOffer(const std::string& aPcHandle,
+ const JsepOfferOptions& aOptions) {
+ PeerConnectionWrapper wrapper(aPcHandle);
+
+ if (wrapper.impl()) {
+ if (!PeerConnectionCtx::GetInstance()->isReady()) {
+ MOZ_CRASH("Why is DeferredCreateOffer being executed when the "
+ "PeerConnectionCtx isn't ready?");
+ }
+ wrapper.impl()->CreateOffer(aOptions);
+ }
+}
+
+// Used by unit tests and the IDL CreateOffer.
+NS_IMETHODIMP
+PeerConnectionImpl::CreateOffer(const JsepOfferOptions& aOptions)
+{
+ PC_AUTO_ENTER_API_CALL(true);
+ bool restartIce = aOptions.mIceRestart.isSome() && *(aOptions.mIceRestart);
+ if (!restartIce &&
+ mMedia->GetIceRestartState() ==
+ PeerConnectionMedia::ICE_RESTART_PROVISIONAL) {
+ RollbackIceRestart();
+ }
+
+ RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
+ if (!pco) {
+ return NS_OK;
+ }
+
+ if (!PeerConnectionCtx::GetInstance()->isReady()) {
+ // Uh oh. We're not ready yet. Enqueue this operation.
+ PeerConnectionCtx::GetInstance()->queueJSEPOperation(
+ WrapRunnableNM(DeferredCreateOffer, mHandle, aOptions));
+ STAMP_TIMECARD(mTimeCard, "Deferring CreateOffer (not ready)");
+ return NS_OK;
+ }
+
+ CSFLogDebug(logTag, "CreateOffer()");
+
+ nsresult nrv;
+ if (restartIce && !mJsepSession->GetLocalDescription().empty()) {
+ // If restart is requested and a restart is already in progress, we
+ // need to make room for the restart request so we either rollback
+ // or finalize to "clear" the previous restart.
+ if (mMedia->GetIceRestartState() ==
+ PeerConnectionMedia::ICE_RESTART_PROVISIONAL) {
+ // we're mid-restart and can rollback
+ RollbackIceRestart();
+ } else if (mMedia->GetIceRestartState() ==
+ PeerConnectionMedia::ICE_RESTART_COMMITTED) {
+ // we're mid-restart and can't rollback, finalize restart even
+ // though we're not really ready yet
+ FinalizeIceRestart();
+ }
+
+ CSFLogInfo(logTag, "Offerer restarting ice");
+ nrv = SetupIceRestart();
+ if (NS_FAILED(nrv)) {
+ CSFLogError(logTag, "%s: SetupIceRestart failed, res=%u",
+ __FUNCTION__,
+ static_cast<unsigned>(nrv));
+ return nrv;
+ }
+ }
+
+ nrv = ConfigureJsepSessionCodecs();
+ if (NS_FAILED(nrv)) {
+ CSFLogError(logTag, "Failed to configure codecs");
+ return nrv;
+ }
+
+ STAMP_TIMECARD(mTimeCard, "Create Offer");
+
+ std::string offer;
+
+ nrv = mJsepSession->CreateOffer(aOptions, &offer);
+ JSErrorResult rv;
+ if (NS_FAILED(nrv)) {
+ Error error;
+ switch (nrv) {
+ case NS_ERROR_UNEXPECTED:
+ error = kInvalidState;
+ break;
+ default:
+ error = kInternalError;
+ }
+ std::string errorString = mJsepSession->GetLastError();
+
+ CSFLogError(logTag, "%s: pc = %s, error = %s",
+ __FUNCTION__, mHandle.c_str(), errorString.c_str());
+ pco->OnCreateOfferError(error, ObString(errorString.c_str()), rv);
+ } else {
+ pco->OnCreateOfferSuccess(ObString(offer.c_str()), rv);
+ }
+
+ UpdateSignalingState();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::CreateAnswer()
+{
+ PC_AUTO_ENTER_API_CALL(true);
+
+ RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
+ if (!pco) {
+ return NS_OK;
+ }
+
+ CSFLogDebug(logTag, "CreateAnswer()");
+
+ nsresult nrv;
+ if (mJsepSession->RemoteIceIsRestarting()) {
+ if (mMedia->GetIceRestartState() ==
+ PeerConnectionMedia::ICE_RESTART_COMMITTED) {
+ FinalizeIceRestart();
+ } else if (!mMedia->IsIceRestarting()) {
+ CSFLogInfo(logTag, "Answerer restarting ice");
+ nrv = SetupIceRestart();
+ if (NS_FAILED(nrv)) {
+ CSFLogError(logTag, "%s: SetupIceRestart failed, res=%u",
+ __FUNCTION__,
+ static_cast<unsigned>(nrv));
+ return nrv;
+ }
+ }
+ }
+
+ STAMP_TIMECARD(mTimeCard, "Create Answer");
+ // TODO(bug 1098015): Once RTCAnswerOptions is standardized, we'll need to
+ // add it as a param to CreateAnswer, and convert it here.
+ JsepAnswerOptions options;
+ std::string answer;
+
+ nrv = mJsepSession->CreateAnswer(options, &answer);
+ JSErrorResult rv;
+ if (NS_FAILED(nrv)) {
+ Error error;
+ switch (nrv) {
+ case NS_ERROR_UNEXPECTED:
+ error = kInvalidState;
+ break;
+ default:
+ error = kInternalError;
+ }
+ std::string errorString = mJsepSession->GetLastError();
+
+ CSFLogError(logTag, "%s: pc = %s, error = %s",
+ __FUNCTION__, mHandle.c_str(), errorString.c_str());
+ pco->OnCreateAnswerError(error, ObString(errorString.c_str()), rv);
+ } else {
+ pco->OnCreateAnswerSuccess(ObString(answer.c_str()), rv);
+ }
+
+ UpdateSignalingState();
+
+ return NS_OK;
+}
+
+nsresult
+PeerConnectionImpl::SetupIceRestart()
+{
+ if (mMedia->IsIceRestarting()) {
+ CSFLogError(logTag, "%s: ICE already restarting",
+ __FUNCTION__);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ std::string ufrag = mMedia->ice_ctx()->GetNewUfrag();
+ std::string pwd = mMedia->ice_ctx()->GetNewPwd();
+ if (ufrag.empty() || pwd.empty()) {
+ CSFLogError(logTag, "%s: Bad ICE credentials (ufrag:'%s'/pwd:'%s')",
+ __FUNCTION__,
+ ufrag.c_str(), pwd.c_str());
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // hold on to the current ice creds in case of rollback
+ mPreviousIceUfrag = mJsepSession->GetUfrag();
+ mPreviousIcePwd = mJsepSession->GetPwd();
+ mMedia->BeginIceRestart(ufrag, pwd);
+
+ nsresult nrv = mJsepSession->SetIceCredentials(ufrag, pwd);
+ if (NS_FAILED(nrv)) {
+ CSFLogError(logTag, "%s: Couldn't set ICE credentials, res=%u",
+ __FUNCTION__,
+ static_cast<unsigned>(nrv));
+ return nrv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+PeerConnectionImpl::RollbackIceRestart()
+{
+ mMedia->RollbackIceRestart();
+ // put back the previous ice creds
+ nsresult nrv = mJsepSession->SetIceCredentials(mPreviousIceUfrag,
+ mPreviousIcePwd);
+ if (NS_FAILED(nrv)) {
+ CSFLogError(logTag, "%s: Couldn't set ICE credentials, res=%u",
+ __FUNCTION__,
+ static_cast<unsigned>(nrv));
+ return nrv;
+ }
+ mPreviousIceUfrag = "";
+ mPreviousIcePwd = "";
+
+ return NS_OK;
+}
+
+void
+PeerConnectionImpl::FinalizeIceRestart()
+{
+ mMedia->FinalizeIceRestart();
+ // clear the previous ice creds since they are no longer needed
+ mPreviousIceUfrag = "";
+ mPreviousIcePwd = "";
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP)
+{
+ PC_AUTO_ENTER_API_CALL(true);
+
+ if (!aSDP) {
+ CSFLogError(logTag, "%s - aSDP is NULL", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ JSErrorResult rv;
+ RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
+ if (!pco) {
+ return NS_OK;
+ }
+
+ STAMP_TIMECARD(mTimeCard, "Set Local Description");
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ bool isolated = mMedia->AnyLocalTrackHasPeerIdentity();
+ mPrivacyRequested = mPrivacyRequested || isolated;
+#endif
+
+ mLocalRequestedSDP = aSDP;
+
+ JsepSdpType sdpType;
+ switch (aAction) {
+ case IPeerConnection::kActionOffer:
+ sdpType = mozilla::kJsepSdpOffer;
+ break;
+ case IPeerConnection::kActionAnswer:
+ sdpType = mozilla::kJsepSdpAnswer;
+ break;
+ case IPeerConnection::kActionPRAnswer:
+ sdpType = mozilla::kJsepSdpPranswer;
+ break;
+ case IPeerConnection::kActionRollback:
+ sdpType = mozilla::kJsepSdpRollback;
+ break;
+ default:
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+
+ }
+ nsresult nrv = mJsepSession->SetLocalDescription(sdpType,
+ mLocalRequestedSDP);
+ if (NS_FAILED(nrv)) {
+ Error error;
+ switch (nrv) {
+ case NS_ERROR_INVALID_ARG:
+ error = kInvalidSessionDescription;
+ break;
+ case NS_ERROR_UNEXPECTED:
+ error = kInvalidState;
+ break;
+ default:
+ error = kInternalError;
+ }
+
+ std::string errorString = mJsepSession->GetLastError();
+ CSFLogError(logTag, "%s: pc = %s, error = %s",
+ __FUNCTION__, mHandle.c_str(), errorString.c_str());
+ pco->OnSetLocalDescriptionError(error, ObString(errorString.c_str()), rv);
+ } else {
+ pco->OnSetLocalDescriptionSuccess(rv);
+ }
+
+ UpdateSignalingState(sdpType == mozilla::kJsepSdpRollback);
+ return NS_OK;
+}
+
+static void DeferredSetRemote(const std::string& aPcHandle,
+ int32_t aAction,
+ const std::string& aSdp) {
+ PeerConnectionWrapper wrapper(aPcHandle);
+
+ if (wrapper.impl()) {
+ if (!PeerConnectionCtx::GetInstance()->isReady()) {
+ MOZ_CRASH("Why is DeferredSetRemote being executed when the "
+ "PeerConnectionCtx isn't ready?");
+ }
+ wrapper.impl()->SetRemoteDescription(aAction, aSdp.c_str());
+ }
+}
+
+static void StartTrack(MediaStream* aSource,
+ TrackID aTrackId,
+ nsAutoPtr<MediaSegment>&& aSegment) {
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ class Message : public ControlMessage {
+ public:
+ Message(MediaStream* aStream,
+ TrackID aTrack,
+ nsAutoPtr<MediaSegment>&& aSegment)
+ : ControlMessage(aStream),
+ track_id_(aTrack),
+ segment_(aSegment) {}
+
+ virtual void Run() override {
+ TrackRate track_rate = segment_->GetType() == MediaSegment::AUDIO ?
+ WEBRTC_DEFAULT_SAMPLE_RATE : mStream->GraphRate();
+ StreamTime current_end = mStream->GetTracksEnd();
+ TrackTicks current_ticks =
+ mStream->TimeToTicksRoundUp(track_rate, current_end);
+
+ // Add a track 'now' to avoid possible underrun, especially if we add
+ // a track "later".
+
+ if (current_end != 0L) {
+ CSFLogDebug(logTag, "added track @ %u -> %f",
+ static_cast<unsigned>(current_end),
+ mStream->StreamTimeToSeconds(current_end));
+ }
+
+ // To avoid assertions, we need to insert a dummy segment that covers up
+ // to the "start" time for the track
+ segment_->AppendNullData(current_ticks);
+ if (segment_->GetType() == MediaSegment::AUDIO) {
+ mStream->AsSourceStream()->AddAudioTrack(
+ track_id_,
+ WEBRTC_DEFAULT_SAMPLE_RATE,
+ 0,
+ static_cast<AudioSegment*>(segment_.forget()));
+ } else {
+ mStream->AsSourceStream()->AddTrack(track_id_, 0, segment_.forget());
+ }
+ }
+ private:
+ TrackID track_id_;
+ nsAutoPtr<MediaSegment> segment_;
+ };
+
+ aSource->GraphImpl()->AppendMessage(
+ MakeUnique<Message>(aSource, aTrackId, Move(aSegment)));
+ CSFLogInfo(logTag, "Dispatched track-add for track id %u on stream %p",
+ aTrackId, aSource);
+#endif
+}
+
+
+nsresult
+PeerConnectionImpl::CreateNewRemoteTracks(RefPtr<PeerConnectionObserver>& aPco)
+{
+ JSErrorResult jrv;
+
+ std::vector<RefPtr<JsepTrack>> newTracks =
+ mJsepSession->GetRemoteTracksAdded();
+
+ // Group new tracks by stream id
+ std::map<std::string, std::vector<RefPtr<JsepTrack>>> tracksByStreamId;
+ for (auto i = newTracks.begin(); i != newTracks.end(); ++i) {
+ RefPtr<JsepTrack> track = *i;
+
+ if (track->GetMediaType() == mozilla::SdpMediaSection::kApplication) {
+ // Ignore datachannel
+ continue;
+ }
+
+ tracksByStreamId[track->GetStreamId()].push_back(track);
+ }
+
+ for (auto i = tracksByStreamId.begin(); i != tracksByStreamId.end(); ++i) {
+ std::string streamId = i->first;
+ std::vector<RefPtr<JsepTrack>>& tracks = i->second;
+
+ bool newStream = false;
+ RefPtr<RemoteSourceStreamInfo> info =
+ mMedia->GetRemoteStreamById(streamId);
+ if (!info) {
+ newStream = true;
+ nsresult nrv = CreateRemoteSourceStreamInfo(&info, streamId);
+ if (NS_FAILED(nrv)) {
+ aPco->OnSetRemoteDescriptionError(
+ kInternalError,
+ ObString("CreateRemoteSourceStreamInfo failed"),
+ jrv);
+ return nrv;
+ }
+
+ nrv = mMedia->AddRemoteStream(info);
+ if (NS_FAILED(nrv)) {
+ aPco->OnSetRemoteDescriptionError(
+ kInternalError,
+ ObString("AddRemoteStream failed"),
+ jrv);
+ return nrv;
+ }
+
+ CSFLogDebug(logTag, "Added remote stream %s", info->GetId().c_str());
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ info->GetMediaStream()->AssignId(NS_ConvertUTF8toUTF16(streamId.c_str()));
+ info->GetMediaStream()->SetLogicalStreamStartTime(
+ info->GetMediaStream()->GetPlaybackStream()->GetCurrentTime());
+#else
+ info->GetMediaStream()->AssignId((streamId));
+#endif
+ }
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ Sequence<OwningNonNull<DOMMediaStream>> streams;
+ if (!streams.AppendElement(OwningNonNull<DOMMediaStream>(
+ *info->GetMediaStream()),
+ fallible)) {
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Set the principal used for creating the tracks. This makes the stream
+ // data (audio/video samples) accessible to the receiving page. We're
+ // only certain that privacy hasn't been requested if we're connected.
+ nsCOMPtr<nsIPrincipal> principal;
+ nsIDocument* doc = GetWindow()->GetExtantDoc();
+ MOZ_ASSERT(doc);
+ if (mDtlsConnected && !PrivacyRequested()) {
+ principal = doc->NodePrincipal();
+ } else {
+ // we're either certain that we need isolation for the streams, OR
+ // we're not sure and we can fix the stream in SetDtlsConnected
+ principal = nsNullPrincipal::CreateWithInheritedAttributes(doc->NodePrincipal());
+ }
+#endif
+
+ // We need to select unique ids, just use max + 1
+ TrackID maxTrackId = 0;
+ {
+ nsTArray<RefPtr<dom::MediaStreamTrack>> domTracks;
+ info->GetMediaStream()->GetTracks(domTracks);
+ for (auto& track : domTracks) {
+ maxTrackId = std::max(maxTrackId, track->mTrackID);
+ }
+ }
+
+ for (RefPtr<JsepTrack>& track : tracks) {
+ std::string webrtcTrackId(track->GetTrackId());
+ if (!info->HasTrack(webrtcTrackId)) {
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ RefPtr<RemoteTrackSource> source =
+ new RemoteTrackSource(principal, nsString());
+#else
+ RefPtr<MediaStreamTrackSource> source = new MediaStreamTrackSource();
+#endif
+ TrackID trackID = ++maxTrackId;
+ RefPtr<MediaStreamTrack> domTrack;
+ nsAutoPtr<MediaSegment> segment;
+ if (track->GetMediaType() == SdpMediaSection::kAudio) {
+ domTrack =
+ info->GetMediaStream()->CreateDOMTrack(trackID,
+ MediaSegment::AUDIO,
+ source);
+ info->GetMediaStream()->AddTrackInternal(domTrack);
+ segment = new AudioSegment;
+ } else {
+ domTrack =
+ info->GetMediaStream()->CreateDOMTrack(trackID,
+ MediaSegment::VIDEO,
+ source);
+ info->GetMediaStream()->AddTrackInternal(domTrack);
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ segment = new VideoSegment;
+#endif
+ }
+
+ StartTrack(info->GetMediaStream()->GetInputStream()->AsSourceStream(),
+ trackID, Move(segment));
+ info->AddTrack(webrtcTrackId, domTrack);
+ CSFLogDebug(logTag, "Added remote track %s/%s",
+ info->GetId().c_str(), webrtcTrackId.c_str());
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ domTrack->AssignId(NS_ConvertUTF8toUTF16(webrtcTrackId.c_str()));
+ aPco->OnAddTrack(*domTrack, streams, jrv);
+ if (jrv.Failed()) {
+ CSFLogError(logTag, ": OnAddTrack(%s) failed! Error: %u",
+ webrtcTrackId.c_str(),
+ jrv.ErrorCodeAsInt());
+ }
+#endif
+ }
+ }
+
+ if (newStream) {
+ aPco->OnAddStream(*info->GetMediaStream(), jrv);
+ if (jrv.Failed()) {
+ CSFLogError(logTag, ": OnAddStream() failed! Error: %u",
+ jrv.ErrorCodeAsInt());
+ }
+ }
+ }
+ return NS_OK;
+}
+
+void
+PeerConnectionImpl::RemoveOldRemoteTracks(RefPtr<PeerConnectionObserver>& aPco)
+{
+ JSErrorResult jrv;
+
+ std::vector<RefPtr<JsepTrack>> removedTracks =
+ mJsepSession->GetRemoteTracksRemoved();
+
+ for (auto i = removedTracks.begin(); i != removedTracks.end(); ++i) {
+ const std::string& streamId = (*i)->GetStreamId();
+ const std::string& trackId = (*i)->GetTrackId();
+
+ RefPtr<RemoteSourceStreamInfo> info = mMedia->GetRemoteStreamById(streamId);
+ if (!info) {
+ MOZ_ASSERT(false, "A stream/track was removed that wasn't in PCMedia. "
+ "This is a bug.");
+ continue;
+ }
+
+ mMedia->RemoveRemoteTrack(streamId, trackId);
+
+ DOMMediaStream* stream = info->GetMediaStream();
+ nsTArray<RefPtr<MediaStreamTrack>> tracks;
+ stream->GetTracks(tracks);
+ for (auto& track : tracks) {
+ if (PeerConnectionImpl::GetTrackId(*track) == trackId) {
+ aPco->OnRemoveTrack(*track, jrv);
+ break;
+ }
+ }
+
+ // We might be holding the last ref, but that's ok.
+ if (!info->GetTrackCount()) {
+ aPco->OnRemoveStream(*stream, jrv);
+ }
+ }
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::SetRemoteDescription(int32_t action, const char* aSDP)
+{
+ PC_AUTO_ENTER_API_CALL(true);
+
+ if (!aSDP) {
+ CSFLogError(logTag, "%s - aSDP is NULL", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ JSErrorResult jrv;
+ RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
+ if (!pco) {
+ return NS_OK;
+ }
+
+ if (action == IPeerConnection::kActionOffer) {
+ if (!PeerConnectionCtx::GetInstance()->isReady()) {
+ // Uh oh. We're not ready yet. Enqueue this operation. (This must be a
+ // remote offer, or else we would not have gotten this far)
+ PeerConnectionCtx::GetInstance()->queueJSEPOperation(
+ WrapRunnableNM(DeferredSetRemote,
+ mHandle,
+ action,
+ std::string(aSDP)));
+ STAMP_TIMECARD(mTimeCard, "Deferring SetRemote (not ready)");
+ return NS_OK;
+ }
+
+ nsresult nrv = ConfigureJsepSessionCodecs();
+ if (NS_FAILED(nrv)) {
+ CSFLogError(logTag, "Failed to configure codecs");
+ return nrv;
+ }
+ }
+
+ STAMP_TIMECARD(mTimeCard, "Set Remote Description");
+
+ mRemoteRequestedSDP = aSDP;
+ JsepSdpType sdpType;
+ switch (action) {
+ case IPeerConnection::kActionOffer:
+ sdpType = mozilla::kJsepSdpOffer;
+ break;
+ case IPeerConnection::kActionAnswer:
+ sdpType = mozilla::kJsepSdpAnswer;
+ break;
+ case IPeerConnection::kActionPRAnswer:
+ sdpType = mozilla::kJsepSdpPranswer;
+ break;
+ case IPeerConnection::kActionRollback:
+ sdpType = mozilla::kJsepSdpRollback;
+ break;
+ default:
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult nrv = mJsepSession->SetRemoteDescription(sdpType,
+ mRemoteRequestedSDP);
+ if (NS_FAILED(nrv)) {
+ Error error;
+ switch (nrv) {
+ case NS_ERROR_INVALID_ARG:
+ error = kInvalidSessionDescription;
+ break;
+ case NS_ERROR_UNEXPECTED:
+ error = kInvalidState;
+ break;
+ default:
+ error = kInternalError;
+ }
+
+ std::string errorString = mJsepSession->GetLastError();
+ CSFLogError(logTag, "%s: pc = %s, error = %s",
+ __FUNCTION__, mHandle.c_str(), errorString.c_str());
+ pco->OnSetRemoteDescriptionError(error, ObString(errorString.c_str()), jrv);
+ } else {
+ nrv = CreateNewRemoteTracks(pco);
+ if (NS_FAILED(nrv)) {
+ // aPco was already notified, just return early.
+ return NS_OK;
+ }
+
+ RemoveOldRemoteTracks(pco);
+
+ pco->OnSetRemoteDescriptionSuccess(jrv);
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ startCallTelem();
+#endif
+ }
+
+ UpdateSignalingState(sdpType == mozilla::kJsepSdpRollback);
+ return NS_OK;
+}
+
+// WebRTC uses highres time relative to the UNIX epoch (Jan 1, 1970, UTC).
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+nsresult
+PeerConnectionImpl::GetTimeSinceEpoch(DOMHighResTimeStamp *result) {
+ MOZ_ASSERT(NS_IsMainThread());
+ Performance *perf = mWindow->GetPerformance();
+ NS_ENSURE_TRUE(perf && perf->Timing(), NS_ERROR_UNEXPECTED);
+ *result = perf->Now() + perf->Timing()->NavigationStart();
+ return NS_OK;
+}
+
+class RTCStatsReportInternalConstruct : public RTCStatsReportInternal {
+public:
+ RTCStatsReportInternalConstruct(const nsString &pcid, DOMHighResTimeStamp now) {
+ mPcid = pcid;
+ mInboundRTPStreamStats.Construct();
+ mOutboundRTPStreamStats.Construct();
+ mMediaStreamTrackStats.Construct();
+ mMediaStreamStats.Construct();
+ mTransportStats.Construct();
+ mIceComponentStats.Construct();
+ mIceCandidatePairStats.Construct();
+ mIceCandidateStats.Construct();
+ mCodecStats.Construct();
+ mTimestamp.Construct(now);
+ }
+};
+#endif
+
+NS_IMETHODIMP
+PeerConnectionImpl::GetStats(MediaStreamTrack *aSelector) {
+ PC_AUTO_ENTER_API_CALL(true);
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ if (!mMedia) {
+ // Since we zero this out before the d'tor, we should check.
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoPtr<RTCStatsQuery> query(new RTCStatsQuery(false));
+
+ nsresult rv = BuildStatsQuery_m(aSelector, query.get());
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RUN_ON_THREAD(mSTSThread,
+ WrapRunnableNM(&PeerConnectionImpl::GetStatsForPCObserver_s,
+ mHandle,
+ query),
+ NS_DISPATCH_NORMAL);
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::AddIceCandidate(const char* aCandidate, const char* aMid, unsigned short aLevel) {
+ PC_AUTO_ENTER_API_CALL(true);
+
+ JSErrorResult rv;
+ RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
+ if (!pco) {
+ return NS_OK;
+ }
+
+ STAMP_TIMECARD(mTimeCard, "Add Ice Candidate");
+
+ CSFLogDebug(logTag, "AddIceCandidate: %s", aCandidate);
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // When remote candidates are added before our ICE ctx is up and running
+ // (the transition to New is async through STS, so this is not impossible),
+ // we won't record them as trickle candidates. Is this what we want?
+ if(!mIceStartTime.IsNull()) {
+ TimeDuration timeDelta = TimeStamp::Now() - mIceStartTime;
+ if (mIceConnectionState == PCImplIceConnectionState::Failed) {
+ Telemetry::Accumulate(Telemetry::WEBRTC_ICE_LATE_TRICKLE_ARRIVAL_TIME,
+ timeDelta.ToMilliseconds());
+ } else {
+ Telemetry::Accumulate(Telemetry::WEBRTC_ICE_ON_TIME_TRICKLE_ARRIVAL_TIME,
+ timeDelta.ToMilliseconds());
+ }
+ }
+#endif
+
+ nsresult res = mJsepSession->AddRemoteIceCandidate(aCandidate, aMid, aLevel);
+
+ if (NS_SUCCEEDED(res)) {
+ // We do not bother PCMedia about this before offer/answer concludes.
+ // Once offer/answer concludes, PCMedia will extract these candidates from
+ // the remote SDP.
+ if (mSignalingState == PCImplSignalingState::SignalingStable) {
+ mMedia->AddIceCandidate(aCandidate, aMid, aLevel);
+ }
+ pco->OnAddIceCandidateSuccess(rv);
+ } else {
+ ++mAddCandidateErrorCount;
+ Error error;
+ switch (res) {
+ case NS_ERROR_UNEXPECTED:
+ error = kInvalidState;
+ break;
+ case NS_ERROR_INVALID_ARG:
+ error = kInvalidCandidate;
+ break;
+ default:
+ error = kInternalError;
+ }
+
+ std::string errorString = mJsepSession->GetLastError();
+
+ CSFLogError(logTag, "Failed to incorporate remote candidate into SDP:"
+ " res = %u, candidate = %s, level = %u, error = %s",
+ static_cast<unsigned>(res),
+ aCandidate,
+ static_cast<unsigned>(aLevel),
+ errorString.c_str());
+
+ pco->OnAddIceCandidateError(error, ObString(errorString.c_str()), rv);
+ }
+
+ UpdateSignalingState();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::CloseStreams() {
+ PC_AUTO_ENTER_API_CALL(false);
+
+ return NS_OK;
+}
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+nsresult
+PeerConnectionImpl::SetPeerIdentity(const nsAString& aPeerIdentity)
+{
+ PC_AUTO_ENTER_API_CALL(true);
+ MOZ_ASSERT(!aPeerIdentity.IsEmpty());
+
+ // once set, this can't be changed
+ if (mPeerIdentity) {
+ if (!mPeerIdentity->Equals(aPeerIdentity)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ mPeerIdentity = new PeerIdentity(aPeerIdentity);
+ nsIDocument* doc = GetWindow()->GetExtantDoc();
+ if (!doc) {
+ CSFLogInfo(logTag, "Can't update principal on streams; document gone");
+ return NS_ERROR_FAILURE;
+ }
+ MediaStreamTrack* allTracks = nullptr;
+ mMedia->UpdateSinkIdentity_m(allTracks, doc->NodePrincipal(), mPeerIdentity);
+ }
+ return NS_OK;
+}
+#endif
+
+nsresult
+PeerConnectionImpl::SetDtlsConnected(bool aPrivacyRequested)
+{
+ PC_AUTO_ENTER_API_CALL(false);
+
+ // For this, as with mPrivacyRequested, once we've connected to a peer, we
+ // fixate on that peer. Dealing with multiple peers or connections is more
+ // than this run-down wreck of an object can handle.
+ // Besides, this is only used to say if we have been connected ever.
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ if (!mPrivacyRequested && !aPrivacyRequested && !mDtlsConnected) {
+ // now we know that privacy isn't needed for sure
+ nsIDocument* doc = GetWindow()->GetExtantDoc();
+ if (!doc) {
+ CSFLogInfo(logTag, "Can't update principal on streams; document gone");
+ return NS_ERROR_FAILURE;
+ }
+ mMedia->UpdateRemoteStreamPrincipals_m(doc->NodePrincipal());
+ }
+#endif
+ mDtlsConnected = true;
+ mPrivacyRequested = mPrivacyRequested || aPrivacyRequested;
+ return NS_OK;
+}
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+void
+PeerConnectionImpl::PrincipalChanged(MediaStreamTrack* aTrack) {
+ nsIDocument* doc = GetWindow()->GetExtantDoc();
+ if (doc) {
+ mMedia->UpdateSinkIdentity_m(aTrack, doc->NodePrincipal(), mPeerIdentity);
+ } else {
+ CSFLogInfo(logTag, "Can't update sink principal; document gone");
+ }
+}
+#endif
+
+std::string
+PeerConnectionImpl::GetTrackId(const MediaStreamTrack& aTrack)
+{
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ nsString wideTrackId;
+ aTrack.GetId(wideTrackId);
+ return NS_ConvertUTF16toUTF8(wideTrackId).get();
+#else
+ return aTrack.GetId();
+#endif
+}
+
+std::string
+PeerConnectionImpl::GetStreamId(const DOMMediaStream& aStream)
+{
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ nsString wideStreamId;
+ aStream.GetId(wideStreamId);
+ return NS_ConvertUTF16toUTF8(wideStreamId).get();
+#else
+ return aStream.GetId();
+#endif
+}
+
+void
+PeerConnectionImpl::OnMediaError(const std::string& aError)
+{
+ CSFLogError(logTag, "Encountered media error! %s", aError.c_str());
+ // TODO: Let content know about this somehow.
+}
+
+nsresult
+PeerConnectionImpl::AddTrack(MediaStreamTrack& aTrack,
+ const Sequence<OwningNonNull<DOMMediaStream>>& aStreams)
+{
+ PC_AUTO_ENTER_API_CALL(true);
+
+ if (!aStreams.Length()) {
+ CSFLogError(logTag, "%s: At least one stream arg required", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ return AddTrack(aTrack, aStreams[0]);
+}
+
+nsresult
+PeerConnectionImpl::AddTrack(MediaStreamTrack& aTrack,
+ DOMMediaStream& aMediaStream)
+{
+ std::string streamId = PeerConnectionImpl::GetStreamId(aMediaStream);
+ std::string trackId = PeerConnectionImpl::GetTrackId(aTrack);
+ nsresult res = mMedia->AddTrack(aMediaStream, streamId, aTrack, trackId);
+ if (NS_FAILED(res)) {
+ return res;
+ }
+
+ CSFLogDebug(logTag, "Added track (%s) to stream %s",
+ trackId.c_str(), streamId.c_str());
+
+ aTrack.AddPrincipalChangeObserver(this);
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ PrincipalChanged(&aTrack);
+#endif
+
+ if (aTrack.AsAudioStreamTrack()) {
+ res = AddTrackToJsepSession(SdpMediaSection::kAudio, streamId, trackId);
+ if (NS_FAILED(res)) {
+ return res;
+ }
+ mNumAudioStreams++;
+ }
+
+ if (aTrack.AsVideoStreamTrack()) {
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ if (!Preferences::GetBool("media.peerconnection.video.enabled", true)) {
+ // Before this code was moved, this would silently ignore just like it
+ // does now. Is this actually what we want to do?
+ return NS_OK;
+ }
+#endif
+
+ res = AddTrackToJsepSession(SdpMediaSection::kVideo, streamId, trackId);
+ if (NS_FAILED(res)) {
+ return res;
+ }
+ mNumVideoStreams++;
+ }
+ OnNegotiationNeeded();
+ return NS_OK;
+}
+
+nsresult
+PeerConnectionImpl::SelectSsrc(MediaStreamTrack& aRecvTrack,
+ unsigned short aSsrcIndex)
+{
+ for (size_t i = 0; i < mMedia->RemoteStreamsLength(); ++i) {
+ if (mMedia->GetRemoteStreamByIndex(i)->GetMediaStream()->
+ HasTrack(aRecvTrack)) {
+ auto& pipelines = mMedia->GetRemoteStreamByIndex(i)->GetPipelines();
+ std::string trackId = PeerConnectionImpl::GetTrackId(aRecvTrack);
+ auto it = pipelines.find(trackId);
+ if (it != pipelines.end()) {
+ it->second->SelectSsrc_m(aSsrcIndex);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::RemoveTrack(MediaStreamTrack& aTrack) {
+ PC_AUTO_ENTER_API_CALL(true);
+
+ std::string trackId = PeerConnectionImpl::GetTrackId(aTrack);
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ nsString wideTrackId;
+ aTrack.GetId(wideTrackId);
+ for (size_t i = 0; i < mDTMFStates.Length(); ++i) {
+ if (mDTMFStates[i]->mTrackId == wideTrackId) {
+ mDTMFStates[i]->mSendTimer->Cancel();
+ mDTMFStates.RemoveElementAt(i);
+ break;
+ }
+ }
+#endif
+
+ RefPtr<LocalSourceStreamInfo> info = media()->GetLocalStreamByTrackId(trackId);
+
+ if (!info) {
+ CSFLogError(logTag, "%s: Unknown stream", __FUNCTION__);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult rv =
+ mJsepSession->RemoveTrack(info->GetId(), trackId);
+
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "%s: Unknown stream/track ids %s %s",
+ __FUNCTION__,
+ info->GetId().c_str(),
+ trackId.c_str());
+ return rv;
+ }
+
+ media()->RemoveLocalTrack(info->GetId(), trackId);
+
+ aTrack.RemovePrincipalChangeObserver(this);
+
+ OnNegotiationNeeded();
+
+ return NS_OK;
+}
+
+static int GetDTMFToneCode(uint16_t c)
+{
+ const char* DTMF_TONECODES = "0123456789*#ABCD";
+
+ if (c == ',') {
+ // , is a special character indicating a 2 second delay
+ return -1;
+ }
+
+ const char* i = strchr(DTMF_TONECODES, c);
+ MOZ_ASSERT(i);
+ return i - DTMF_TONECODES;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::InsertDTMF(mozilla::dom::RTCRtpSender& sender,
+ const nsAString& tones, uint32_t duration,
+ uint32_t interToneGap) {
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ PC_AUTO_ENTER_API_CALL(false);
+
+ // Check values passed in from PeerConnection.js
+ MOZ_ASSERT(duration >= 40, "duration must be at least 40");
+ MOZ_ASSERT(duration <= 6000, "duration must be at most 6000");
+ MOZ_ASSERT(interToneGap >= 30, "interToneGap must be at least 30");
+
+ JSErrorResult jrv;
+
+ // Retrieve track
+ RefPtr<MediaStreamTrack> mst = sender.GetTrack(jrv);
+ if (jrv.Failed()) {
+ NS_WARNING("Failed to retrieve track for RTCRtpSender!");
+ return jrv.StealNSResult();
+ }
+
+ nsString senderTrackId;
+ mst->GetId(senderTrackId);
+
+ // Attempt to locate state for the DTMFSender
+ RefPtr<DTMFState> state;
+ for (auto& dtmfState : mDTMFStates) {
+ if (dtmfState->mTrackId == senderTrackId) {
+ state = dtmfState;
+ break;
+ }
+ }
+
+ // No state yet, create a new one
+ if (!state) {
+ state = *mDTMFStates.AppendElement(new DTMFState);
+ state->mPeerConnectionImpl = this;
+ state->mTrackId = senderTrackId;
+ state->mSendTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ MOZ_ASSERT(state->mSendTimer);
+ }
+ MOZ_ASSERT(state);
+
+ auto trackPairs = mJsepSession->GetNegotiatedTrackPairs();
+ state->mLevel = -1;
+ for (auto& trackPair : trackPairs) {
+ if (state->mTrackId.EqualsASCII(trackPair.mSending->GetTrackId().c_str())) {
+ if (trackPair.mBundleLevel.isSome()) {
+ state->mLevel = *trackPair.mBundleLevel;
+ } else {
+ state->mLevel = trackPair.mLevel;
+ }
+ break;
+ }
+ }
+
+ state->mTones = tones;
+ state->mDuration = duration;
+ state->mInterToneGap = interToneGap;
+ if (!state->mTones.IsEmpty()) {
+ state->mSendTimer->InitWithCallback(state, 0, nsITimer::TYPE_ONE_SHOT);
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::GetDTMFToneBuffer(mozilla::dom::RTCRtpSender& sender,
+ nsAString& outToneBuffer) {
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ PC_AUTO_ENTER_API_CALL(false);
+
+ JSErrorResult jrv;
+
+ // Retrieve track
+ RefPtr<MediaStreamTrack> mst = sender.GetTrack(jrv);
+ if (jrv.Failed()) {
+ NS_WARNING("Failed to retrieve track for RTCRtpSender!");
+ return jrv.StealNSResult();
+ }
+
+ nsString senderTrackId;
+ mst->GetId(senderTrackId);
+
+ // Attempt to locate state for the DTMFSender
+ for (auto& dtmfState : mDTMFStates) {
+ if (dtmfState->mTrackId == senderTrackId) {
+ outToneBuffer = dtmfState->mTones;
+ break;
+ }
+ }
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::ReplaceTrack(MediaStreamTrack& aThisTrack,
+ MediaStreamTrack& aWithTrack) {
+ PC_AUTO_ENTER_API_CALL(true);
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ nsString trackId;
+ aThisTrack.GetId(trackId);
+
+ for (size_t i = 0; i < mDTMFStates.Length(); ++i) {
+ if (mDTMFStates[i]->mTrackId == trackId) {
+ mDTMFStates[i]->mSendTimer->Cancel();
+ mDTMFStates.RemoveElementAt(i);
+ break;
+ }
+ }
+#endif
+
+ RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
+ if (!pco) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ JSErrorResult jrv;
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ if (&aThisTrack == &aWithTrack) {
+ pco->OnReplaceTrackSuccess(jrv);
+ if (jrv.Failed()) {
+ CSFLogError(logTag, "Error firing replaceTrack success callback");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+ }
+
+ nsString thisKind;
+ aThisTrack.GetKind(thisKind);
+ nsString withKind;
+ aWithTrack.GetKind(withKind);
+
+ if (thisKind != withKind) {
+ pco->OnReplaceTrackError(kIncompatibleMediaStreamTrack,
+ ObString(mJsepSession->GetLastError().c_str()),
+ jrv);
+ if (jrv.Failed()) {
+ CSFLogError(logTag, "Error firing replaceTrack success callback");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+ }
+#endif
+ std::string origTrackId = PeerConnectionImpl::GetTrackId(aThisTrack);
+ std::string newTrackId = PeerConnectionImpl::GetTrackId(aWithTrack);
+
+ RefPtr<LocalSourceStreamInfo> info =
+ media()->GetLocalStreamByTrackId(origTrackId);
+ if (!info) {
+ CSFLogError(logTag, "Could not find stream from trackId");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ std::string origStreamId = info->GetId();
+ std::string newStreamId =
+ PeerConnectionImpl::GetStreamId(*aWithTrack.mOwningStream);
+
+ nsresult rv = mJsepSession->ReplaceTrack(origStreamId,
+ origTrackId,
+ newStreamId,
+ newTrackId);
+ if (NS_FAILED(rv)) {
+ pco->OnReplaceTrackError(kInvalidMediastreamTrack,
+ ObString(mJsepSession->GetLastError().c_str()),
+ jrv);
+ if (jrv.Failed()) {
+ CSFLogError(logTag, "Error firing replaceTrack error callback");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+ }
+
+ rv = media()->ReplaceTrack(origStreamId,
+ origTrackId,
+ aWithTrack,
+ newStreamId,
+ newTrackId);
+
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "Unexpected error in ReplaceTrack: %d",
+ static_cast<int>(rv));
+ pco->OnReplaceTrackError(kInvalidMediastreamTrack,
+ ObString("Failed to replace track"),
+ jrv);
+ if (jrv.Failed()) {
+ CSFLogError(logTag, "Error firing replaceTrack error callback");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+ }
+ aThisTrack.RemovePrincipalChangeObserver(this);
+ aWithTrack.AddPrincipalChangeObserver(this);
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ PrincipalChanged(&aWithTrack);
+#endif
+
+ // We update the media pipelines here so we can apply different codec
+ // settings for different sources (e.g. screensharing as opposed to camera.)
+ // TODO: We should probably only do this if the source has in fact changed.
+ mMedia->UpdateMediaPipelines(*mJsepSession);
+
+ pco->OnReplaceTrackSuccess(jrv);
+ if (jrv.Failed()) {
+ CSFLogError(logTag, "Error firing replaceTrack success callback");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+NS_IMETHODIMP
+PeerConnectionImpl::SetParameters(MediaStreamTrack& aTrack,
+ const RTCRtpParameters& aParameters) {
+ PC_AUTO_ENTER_API_CALL(true);
+
+ std::vector<JsepTrack::JsConstraints> constraints;
+ if (aParameters.mEncodings.WasPassed()) {
+ for (auto& encoding : aParameters.mEncodings.Value()) {
+ JsepTrack::JsConstraints constraint;
+ if (encoding.mRid.WasPassed()) {
+ constraint.rid = NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get();
+ }
+ if (encoding.mMaxBitrate.WasPassed()) {
+ constraint.constraints.maxBr = encoding.mMaxBitrate.Value();
+ }
+ constraint.constraints.scaleDownBy = encoding.mScaleResolutionDownBy;
+ constraints.push_back(constraint);
+ }
+ }
+ return SetParameters(aTrack, constraints);
+}
+#endif
+
+nsresult
+PeerConnectionImpl::SetParameters(
+ MediaStreamTrack& aTrack,
+ const std::vector<JsepTrack::JsConstraints>& aConstraints)
+{
+ std::string trackId = PeerConnectionImpl::GetTrackId(aTrack);
+ RefPtr<LocalSourceStreamInfo> info = media()->GetLocalStreamByTrackId(trackId);
+ if (!info) {
+ CSFLogError(logTag, "%s: Unknown stream", __FUNCTION__);
+ return NS_ERROR_INVALID_ARG;
+ }
+ std::string streamId = info->GetId();
+
+ return mJsepSession->SetParameters(streamId, trackId, aConstraints);
+}
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+NS_IMETHODIMP
+PeerConnectionImpl::GetParameters(MediaStreamTrack& aTrack,
+ RTCRtpParameters& aOutParameters) {
+ PC_AUTO_ENTER_API_CALL(true);
+
+ std::vector<JsepTrack::JsConstraints> constraints;
+ nsresult rv = GetParameters(aTrack, &constraints);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ aOutParameters.mEncodings.Construct();
+ for (auto& constraint : constraints) {
+ RTCRtpEncodingParameters encoding;
+ encoding.mRid.Construct(NS_ConvertASCIItoUTF16(constraint.rid.c_str()));
+ encoding.mMaxBitrate.Construct(constraint.constraints.maxBr);
+ encoding.mScaleResolutionDownBy = constraint.constraints.scaleDownBy;
+ aOutParameters.mEncodings.Value().AppendElement(Move(encoding), fallible);
+ }
+ return NS_OK;
+}
+#endif
+
+nsresult
+PeerConnectionImpl::GetParameters(
+ MediaStreamTrack& aTrack,
+ std::vector<JsepTrack::JsConstraints>* aOutConstraints)
+{
+ std::string trackId = PeerConnectionImpl::GetTrackId(aTrack);
+ RefPtr<LocalSourceStreamInfo> info = media()->GetLocalStreamByTrackId(trackId);
+ if (!info) {
+ CSFLogError(logTag, "%s: Unknown stream", __FUNCTION__);
+ return NS_ERROR_INVALID_ARG;
+ }
+ std::string streamId = info->GetId();
+
+ return mJsepSession->GetParameters(streamId, trackId, aOutConstraints);
+}
+
+nsresult
+PeerConnectionImpl::CalculateFingerprint(
+ const std::string& algorithm,
+ std::vector<uint8_t>* fingerprint) const {
+ uint8_t buf[DtlsIdentity::HASH_ALGORITHM_MAX_LENGTH];
+ size_t len = 0;
+
+ MOZ_ASSERT(fingerprint);
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ const UniqueCERTCertificate& cert = mCertificate->Certificate();
+#else
+ const UniqueCERTCertificate& cert = mIdentity->cert();
+#endif
+ nsresult rv = DtlsIdentity::ComputeFingerprint(cert, algorithm,
+ &buf[0], sizeof(buf),
+ &len);
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "Unable to calculate certificate fingerprint, rv=%u",
+ static_cast<unsigned>(rv));
+ return rv;
+ }
+ MOZ_ASSERT(len > 0 && len <= DtlsIdentity::HASH_ALGORITHM_MAX_LENGTH);
+ fingerprint->assign(buf, buf + len);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::GetFingerprint(char** fingerprint)
+{
+ MOZ_ASSERT(fingerprint);
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ MOZ_ASSERT(mCertificate);
+#endif
+ std::vector<uint8_t> fp;
+ nsresult rv = CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, &fp);
+ NS_ENSURE_SUCCESS(rv, rv);
+ std::ostringstream os;
+ os << DtlsIdentity::DEFAULT_HASH_ALGORITHM << ' '
+ << SdpFingerprintAttributeList::FormatFingerprint(fp);
+ std::string fpStr = os.str();
+
+ char* tmp = new char[fpStr.size() + 1];
+ std::copy(fpStr.begin(), fpStr.end(), tmp);
+ tmp[fpStr.size()] = '\0';
+
+ *fingerprint = tmp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::GetLocalDescription(char** aSDP)
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(aSDP);
+ std::string localSdp = mJsepSession->GetLocalDescription();
+
+ char* tmp = new char[localSdp.size() + 1];
+ std::copy(localSdp.begin(), localSdp.end(), tmp);
+ tmp[localSdp.size()] = '\0';
+
+ *aSDP = tmp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::GetRemoteDescription(char** aSDP)
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(aSDP);
+ std::string remoteSdp = mJsepSession->GetRemoteDescription();
+
+ char* tmp = new char[remoteSdp.size() + 1];
+ std::copy(remoteSdp.begin(), remoteSdp.end(), tmp);
+ tmp[remoteSdp.size()] = '\0';
+
+ *aSDP = tmp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::SignalingState(PCImplSignalingState* aState)
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(aState);
+
+ *aState = mSignalingState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::IceConnectionState(PCImplIceConnectionState* aState)
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(aState);
+
+ *aState = mIceConnectionState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::IceGatheringState(PCImplIceGatheringState* aState)
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(aState);
+
+ *aState = mIceGatheringState;
+ return NS_OK;
+}
+
+nsresult
+PeerConnectionImpl::CheckApiState(bool assert_ice_ready) const
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(mTrickle || !assert_ice_ready ||
+ (mIceGatheringState == PCImplIceGatheringState::Complete));
+
+ if (IsClosed()) {
+ CSFLogError(logTag, "%s: called API while closed", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+ if (!mMedia) {
+ CSFLogError(logTag, "%s: called API with disposed mMedia", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::Close()
+{
+ CSFLogDebug(logTag, "%s: for %s", __FUNCTION__, mHandle.c_str());
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+
+ SetSignalingState_m(PCImplSignalingState::SignalingClosed);
+
+ return NS_OK;
+}
+
+bool
+PeerConnectionImpl::PluginCrash(uint32_t aPluginID,
+ const nsAString& aPluginName)
+{
+ // fire an event to the DOM window if this is "ours"
+ bool result = mMedia ? mMedia->AnyCodecHasPluginID(aPluginID) : false;
+ if (!result) {
+ return false;
+ }
+
+ CSFLogError(logTag, "%s: Our plugin %llu crashed", __FUNCTION__, static_cast<unsigned long long>(aPluginID));
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
+ if (!doc) {
+ NS_WARNING("Couldn't get document for PluginCrashed event!");
+ return true;
+ }
+
+ PluginCrashedEventInit init;
+ init.mPluginID = aPluginID;
+ init.mPluginName = aPluginName;
+ init.mSubmittedCrashReport = false;
+ init.mGmpPlugin = true;
+ init.mBubbles = true;
+ init.mCancelable = true;
+
+ RefPtr<PluginCrashedEvent> event =
+ PluginCrashedEvent::Constructor(doc, NS_LITERAL_STRING("PluginCrashed"), init);
+
+ event->SetTrusted(true);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+
+ EventDispatcher::DispatchDOMEvent(mWindow, nullptr, event, nullptr, nullptr);
+#endif
+
+ return true;
+}
+
+void
+PeerConnectionImpl::RecordEndOfCallTelemetry() const
+{
+ if (!mJsepSession) {
+ return;
+ }
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // Bitmask used for WEBRTC/LOOP_CALL_TYPE telemetry reporting
+ static const uint32_t kAudioTypeMask = 1;
+ static const uint32_t kVideoTypeMask = 2;
+ static const uint32_t kDataChannelTypeMask = 4;
+
+ // Report end-of-call Telemetry
+ if (mJsepSession->GetNegotiations() > 0) {
+ Telemetry::Accumulate(Telemetry::WEBRTC_RENEGOTIATIONS,
+ mJsepSession->GetNegotiations()-1);
+ }
+ Telemetry::Accumulate(Telemetry::WEBRTC_MAX_VIDEO_SEND_TRACK,
+ mMaxSending[SdpMediaSection::MediaType::kVideo]);
+ Telemetry::Accumulate(Telemetry::WEBRTC_MAX_VIDEO_RECEIVE_TRACK,
+ mMaxReceiving[SdpMediaSection::MediaType::kVideo]);
+ Telemetry::Accumulate(Telemetry::WEBRTC_MAX_AUDIO_SEND_TRACK,
+ mMaxSending[SdpMediaSection::MediaType::kAudio]);
+ Telemetry::Accumulate(Telemetry::WEBRTC_MAX_AUDIO_RECEIVE_TRACK,
+ mMaxReceiving[SdpMediaSection::MediaType::kAudio]);
+ // DataChannels appear in both Sending and Receiving
+ Telemetry::Accumulate(Telemetry::WEBRTC_DATACHANNEL_NEGOTIATED,
+ mMaxSending[SdpMediaSection::MediaType::kApplication]);
+ // Enumerated/bitmask: 1 = Audio, 2 = Video, 4 = DataChannel
+ // A/V = 3, A/V/D = 7, etc
+ uint32_t type = 0;
+ if (mMaxSending[SdpMediaSection::MediaType::kAudio] ||
+ mMaxReceiving[SdpMediaSection::MediaType::kAudio]) {
+ type = kAudioTypeMask;
+ }
+ if (mMaxSending[SdpMediaSection::MediaType::kVideo] ||
+ mMaxReceiving[SdpMediaSection::MediaType::kVideo]) {
+ type |= kVideoTypeMask;
+ }
+ if (mMaxSending[SdpMediaSection::MediaType::kApplication]) {
+ type |= kDataChannelTypeMask;
+ }
+ Telemetry::Accumulate(Telemetry::WEBRTC_CALL_TYPE,
+ type);
+#endif
+}
+
+nsresult
+PeerConnectionImpl::CloseInt()
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+
+ for (auto& dtmfState : mDTMFStates) {
+ dtmfState->mSendTimer->Cancel();
+ }
+
+ // We do this at the end of the call because we want to make sure we've waited
+ // for all trickle ICE candidates to come in; this can happen well after we've
+ // transitioned to connected. As a bonus, this allows us to detect race
+ // conditions where a stats dispatch happens right as the PC closes.
+ if (!mPrivateWindow) {
+ RecordLongtermICEStatistics();
+ }
+ RecordEndOfCallTelemetry();
+ CSFLogInfo(logTag, "%s: Closing PeerConnectionImpl %s; "
+ "ending call", __FUNCTION__, mHandle.c_str());
+ if (mJsepSession) {
+ mJsepSession->Close();
+ }
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ if (mDataConnection) {
+ CSFLogInfo(logTag, "%s: Destroying DataChannelConnection %p for %s",
+ __FUNCTION__, (void *) mDataConnection.get(), mHandle.c_str());
+ mDataConnection->Destroy();
+ mDataConnection = nullptr; // it may not go away until the runnables are dead
+ }
+#endif
+ ShutdownMedia();
+
+ // DataConnection will need to stay alive until all threads/runnables exit
+
+ return NS_OK;
+}
+
+void
+PeerConnectionImpl::ShutdownMedia()
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+
+ if (!mMedia)
+ return;
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // before we destroy references to local tracks, detach from them
+ for(uint32_t i = 0; i < media()->LocalStreamsLength(); ++i) {
+ LocalSourceStreamInfo *info = media()->GetLocalStreamByIndex(i);
+ for (const auto& pair : info->GetMediaStreamTracks()) {
+ pair.second->RemovePrincipalChangeObserver(this);
+ }
+ }
+
+ // End of call to be recorded in Telemetry
+ if (!mStartTime.IsNull()){
+ TimeDuration timeDelta = TimeStamp::Now() - mStartTime;
+ Telemetry::Accumulate(Telemetry::WEBRTC_CALL_DURATION,
+ timeDelta.ToSeconds());
+ }
+#endif
+
+ // Forget the reference so that we can transfer it to
+ // SelfDestruct().
+ mMedia.forget().take()->SelfDestruct();
+}
+
+void
+PeerConnectionImpl::SetSignalingState_m(PCImplSignalingState aSignalingState,
+ bool rollback)
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ if (mSignalingState == aSignalingState ||
+ mSignalingState == PCImplSignalingState::SignalingClosed) {
+ return;
+ }
+
+ if (aSignalingState == PCImplSignalingState::SignalingHaveLocalOffer ||
+ (aSignalingState == PCImplSignalingState::SignalingStable &&
+ mSignalingState == PCImplSignalingState::SignalingHaveRemoteOffer &&
+ !rollback)) {
+ mMedia->EnsureTransports(*mJsepSession);
+ }
+
+ mSignalingState = aSignalingState;
+
+ bool fireNegotiationNeeded = false;
+ if (mSignalingState == PCImplSignalingState::SignalingStable) {
+ if (mMedia->GetIceRestartState() ==
+ PeerConnectionMedia::ICE_RESTART_PROVISIONAL) {
+ if (rollback) {
+ RollbackIceRestart();
+ } else {
+ mMedia->CommitIceRestart();
+ }
+ }
+
+ // Either negotiation is done, or we've rolled back. In either case, we
+ // need to re-evaluate whether further negotiation is required.
+ mNegotiationNeeded = false;
+ // If we're rolling back a local offer, we might need to remove some
+ // transports, but nothing further needs to be done.
+ mMedia->ActivateOrRemoveTransports(*mJsepSession);
+ if (!rollback) {
+ mMedia->UpdateMediaPipelines(*mJsepSession);
+ InitializeDataChannel();
+ mMedia->StartIceChecks(*mJsepSession);
+ }
+
+ if (!mJsepSession->AllLocalTracksAreAssigned()) {
+ CSFLogInfo(logTag, "Not all local tracks were assigned to an "
+ "m-section, either because the offerer did not offer"
+ " to receive enough tracks, or because tracks were "
+ "added after CreateOffer/Answer, but before "
+ "offer/answer completed. This requires "
+ "renegotiation.");
+ fireNegotiationNeeded = true;
+ }
+
+ // Telemetry: record info on the current state of streams/renegotiations/etc
+ // Note: this code gets run on rollbacks as well!
+
+ // Update the max channels used with each direction for each type
+ uint16_t receiving[SdpMediaSection::kMediaTypes];
+ uint16_t sending[SdpMediaSection::kMediaTypes];
+ mJsepSession->CountTracks(receiving, sending);
+ for (size_t i = 0; i < SdpMediaSection::kMediaTypes; i++) {
+ if (mMaxReceiving[i] < receiving[i]) {
+ mMaxReceiving[i] = receiving[i];
+ }
+ if (mMaxSending[i] < sending[i]) {
+ mMaxSending[i] = sending[i];
+ }
+ }
+ }
+
+ if (mSignalingState == PCImplSignalingState::SignalingClosed) {
+ CloseInt();
+ }
+
+ RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
+ if (!pco) {
+ return;
+ }
+ JSErrorResult rv;
+ pco->OnStateChange(PCObserverStateType::SignalingState, rv);
+
+ if (fireNegotiationNeeded) {
+ // We don't use MaybeFireNegotiationNeeded here, since content might have
+ // already cased a transition from stable.
+ OnNegotiationNeeded();
+ }
+}
+
+void
+PeerConnectionImpl::UpdateSignalingState(bool rollback) {
+ mozilla::JsepSignalingState state =
+ mJsepSession->GetState();
+
+ PCImplSignalingState newState;
+
+ switch(state) {
+ case kJsepStateStable:
+ newState = PCImplSignalingState::SignalingStable;
+ break;
+ case kJsepStateHaveLocalOffer:
+ newState = PCImplSignalingState::SignalingHaveLocalOffer;
+ break;
+ case kJsepStateHaveRemoteOffer:
+ newState = PCImplSignalingState::SignalingHaveRemoteOffer;
+ break;
+ case kJsepStateHaveLocalPranswer:
+ newState = PCImplSignalingState::SignalingHaveLocalPranswer;
+ break;
+ case kJsepStateHaveRemotePranswer:
+ newState = PCImplSignalingState::SignalingHaveRemotePranswer;
+ break;
+ case kJsepStateClosed:
+ newState = PCImplSignalingState::SignalingClosed;
+ break;
+ default:
+ MOZ_CRASH();
+ }
+
+ SetSignalingState_m(newState, rollback);
+}
+
+bool
+PeerConnectionImpl::IsClosed() const
+{
+ return mSignalingState == PCImplSignalingState::SignalingClosed;
+}
+
+bool
+PeerConnectionImpl::HasMedia() const
+{
+ return mMedia;
+}
+
+PeerConnectionWrapper::PeerConnectionWrapper(const std::string& handle)
+ : impl_(nullptr) {
+ if (PeerConnectionCtx::GetInstance()->mPeerConnections.find(handle) ==
+ PeerConnectionCtx::GetInstance()->mPeerConnections.end()) {
+ return;
+ }
+
+ PeerConnectionImpl *impl = PeerConnectionCtx::GetInstance()->mPeerConnections[handle];
+
+ if (!impl->media())
+ return;
+
+ impl_ = impl;
+}
+
+const std::string&
+PeerConnectionImpl::GetHandle()
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mHandle;
+}
+
+const std::string&
+PeerConnectionImpl::GetName()
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mName;
+}
+
+static mozilla::dom::PCImplIceConnectionState
+toDomIceConnectionState(NrIceCtx::ConnectionState state) {
+ switch (state) {
+ case NrIceCtx::ICE_CTX_INIT:
+ return PCImplIceConnectionState::New;
+ case NrIceCtx::ICE_CTX_CHECKING:
+ return PCImplIceConnectionState::Checking;
+ case NrIceCtx::ICE_CTX_CONNECTED:
+ return PCImplIceConnectionState::Connected;
+ case NrIceCtx::ICE_CTX_COMPLETED:
+ return PCImplIceConnectionState::Completed;
+ case NrIceCtx::ICE_CTX_FAILED:
+ return PCImplIceConnectionState::Failed;
+ case NrIceCtx::ICE_CTX_DISCONNECTED:
+ return PCImplIceConnectionState::Disconnected;
+ case NrIceCtx::ICE_CTX_CLOSED:
+ return PCImplIceConnectionState::Closed;
+ }
+ MOZ_CRASH();
+}
+
+static mozilla::dom::PCImplIceGatheringState
+toDomIceGatheringState(NrIceCtx::GatheringState state) {
+ switch (state) {
+ case NrIceCtx::ICE_CTX_GATHER_INIT:
+ return PCImplIceGatheringState::New;
+ case NrIceCtx::ICE_CTX_GATHER_STARTED:
+ return PCImplIceGatheringState::Gathering;
+ case NrIceCtx::ICE_CTX_GATHER_COMPLETE:
+ return PCImplIceGatheringState::Complete;
+ }
+ MOZ_CRASH();
+}
+
+void
+PeerConnectionImpl::CandidateReady(const std::string& candidate,
+ uint16_t level) {
+ PC_AUTO_ENTER_API_CALL_VOID_RETURN(false);
+
+ std::string mid;
+ bool skipped = false;
+ nsresult res = mJsepSession->AddLocalIceCandidate(candidate,
+ level,
+ &mid,
+ &skipped);
+
+ if (NS_FAILED(res)) {
+ std::string errorString = mJsepSession->GetLastError();
+
+ CSFLogError(logTag, "Failed to incorporate local candidate into SDP:"
+ " res = %u, candidate = %s, level = %u, error = %s",
+ static_cast<unsigned>(res),
+ candidate.c_str(),
+ static_cast<unsigned>(level),
+ errorString.c_str());
+ return;
+ }
+
+ if (skipped) {
+ CSFLogDebug(logTag, "Skipped adding local candidate %s (level %u) to SDP, "
+ "this typically happens because the m-section is "
+ "bundled, which means it doesn't make sense for it to "
+ "have its own transport-related attributes.",
+ candidate.c_str(),
+ static_cast<unsigned>(level));
+ return;
+ }
+
+ CSFLogDebug(logTag, "Passing local candidate to content: %s",
+ candidate.c_str());
+ SendLocalIceCandidateToContent(level, mid, candidate);
+
+ UpdateSignalingState();
+}
+
+static void
+SendLocalIceCandidateToContentImpl(nsWeakPtr weakPCObserver,
+ uint16_t level,
+ const std::string& mid,
+ const std::string& candidate) {
+ RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(weakPCObserver);
+ if (!pco) {
+ return;
+ }
+
+ JSErrorResult rv;
+ pco->OnIceCandidate(level,
+ ObString(mid.c_str()),
+ ObString(candidate.c_str()),
+ rv);
+}
+
+void
+PeerConnectionImpl::SendLocalIceCandidateToContent(
+ uint16_t level,
+ const std::string& mid,
+ const std::string& candidate) {
+ // We dispatch this because OnSetLocalDescriptionSuccess does a setTimeout(0)
+ // to unwind the stack, but the event handlers don't. We need to ensure that
+ // the candidates do not skip ahead of the callback.
+ NS_DispatchToMainThread(
+ WrapRunnableNM(&SendLocalIceCandidateToContentImpl,
+ mPCObserver,
+ level,
+ mid,
+ candidate),
+ NS_DISPATCH_NORMAL);
+}
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+static bool isDone(PCImplIceConnectionState state) {
+ return state != PCImplIceConnectionState::Checking &&
+ state != PCImplIceConnectionState::New;
+}
+
+static bool isSucceeded(PCImplIceConnectionState state) {
+ return state == PCImplIceConnectionState::Connected ||
+ state == PCImplIceConnectionState::Completed;
+}
+
+static bool isFailed(PCImplIceConnectionState state) {
+ return state == PCImplIceConnectionState::Failed;
+}
+#endif
+
+void PeerConnectionImpl::IceConnectionStateChange(
+ NrIceCtx* ctx,
+ NrIceCtx::ConnectionState state) {
+ PC_AUTO_ENTER_API_CALL_VOID_RETURN(false);
+
+ CSFLogDebug(logTag, "%s", __FUNCTION__);
+
+ auto domState = toDomIceConnectionState(state);
+ if (domState == mIceConnectionState) {
+ // no work to be done since the states are the same.
+ // this can happen during ICE rollback situations.
+ return;
+ }
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ if (!isDone(mIceConnectionState) && isDone(domState)) {
+ // mIceStartTime can be null if going directly from New to Closed, in which
+ // case we don't count it as a success or a failure.
+ if (!mIceStartTime.IsNull()){
+ TimeDuration timeDelta = TimeStamp::Now() - mIceStartTime;
+ if (isSucceeded(domState)) {
+ Telemetry::Accumulate(Telemetry::WEBRTC_ICE_SUCCESS_TIME,
+ timeDelta.ToMilliseconds());
+ } else if (isFailed(domState)) {
+ Telemetry::Accumulate(Telemetry::WEBRTC_ICE_FAILURE_TIME,
+ timeDelta.ToMilliseconds());
+ }
+ }
+
+ if (isSucceeded(domState)) {
+ Telemetry::Accumulate(
+ Telemetry::WEBRTC_ICE_ADD_CANDIDATE_ERRORS_GIVEN_SUCCESS,
+ mAddCandidateErrorCount);
+ } else if (isFailed(domState)) {
+ Telemetry::Accumulate(
+ Telemetry::WEBRTC_ICE_ADD_CANDIDATE_ERRORS_GIVEN_FAILURE,
+ mAddCandidateErrorCount);
+ }
+ }
+#endif
+
+ mIceConnectionState = domState;
+
+ if (mIceConnectionState == PCImplIceConnectionState::Connected ||
+ mIceConnectionState == PCImplIceConnectionState::Completed ||
+ mIceConnectionState == PCImplIceConnectionState::Failed) {
+ if (mMedia->IsIceRestarting()) {
+ FinalizeIceRestart();
+ }
+ }
+
+ // Would be nice if we had a means of converting one of these dom enums
+ // to a string that wasn't almost as much text as this switch statement...
+ switch (mIceConnectionState) {
+ case PCImplIceConnectionState::New:
+ STAMP_TIMECARD(mTimeCard, "Ice state: new");
+ break;
+ case PCImplIceConnectionState::Checking:
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // For telemetry
+ mIceStartTime = TimeStamp::Now();
+#endif
+ STAMP_TIMECARD(mTimeCard, "Ice state: checking");
+ break;
+ case PCImplIceConnectionState::Connected:
+ STAMP_TIMECARD(mTimeCard, "Ice state: connected");
+ break;
+ case PCImplIceConnectionState::Completed:
+ STAMP_TIMECARD(mTimeCard, "Ice state: completed");
+ break;
+ case PCImplIceConnectionState::Failed:
+ STAMP_TIMECARD(mTimeCard, "Ice state: failed");
+ break;
+ case PCImplIceConnectionState::Disconnected:
+ STAMP_TIMECARD(mTimeCard, "Ice state: disconnected");
+ break;
+ case PCImplIceConnectionState::Closed:
+ STAMP_TIMECARD(mTimeCard, "Ice state: closed");
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected mIceConnectionState!");
+ }
+
+ RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
+ if (!pco) {
+ return;
+ }
+ WrappableJSErrorResult rv;
+ RUN_ON_THREAD(mThread,
+ WrapRunnable(pco,
+ &PeerConnectionObserver::OnStateChange,
+ PCObserverStateType::IceConnectionState,
+ rv, static_cast<JSCompartment*>(nullptr)),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionImpl::IceGatheringStateChange(
+ NrIceCtx* ctx,
+ NrIceCtx::GatheringState state)
+{
+ PC_AUTO_ENTER_API_CALL_VOID_RETURN(false);
+
+ CSFLogDebug(logTag, "%s", __FUNCTION__);
+
+ mIceGatheringState = toDomIceGatheringState(state);
+
+ // Would be nice if we had a means of converting one of these dom enums
+ // to a string that wasn't almost as much text as this switch statement...
+ switch (mIceGatheringState) {
+ case PCImplIceGatheringState::New:
+ STAMP_TIMECARD(mTimeCard, "Ice gathering state: new");
+ break;
+ case PCImplIceGatheringState::Gathering:
+ STAMP_TIMECARD(mTimeCard, "Ice gathering state: gathering");
+ break;
+ case PCImplIceGatheringState::Complete:
+ STAMP_TIMECARD(mTimeCard, "Ice gathering state: complete");
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected mIceGatheringState!");
+ }
+
+ RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
+ if (!pco) {
+ return;
+ }
+ WrappableJSErrorResult rv;
+ mThread->Dispatch(WrapRunnable(pco,
+ &PeerConnectionObserver::OnStateChange,
+ PCObserverStateType::IceGatheringState,
+ rv, static_cast<JSCompartment*>(nullptr)),
+ NS_DISPATCH_NORMAL);
+
+ if (mIceGatheringState == PCImplIceGatheringState::Complete) {
+ SendLocalIceCandidateToContent(0, "", "");
+ }
+}
+
+void
+PeerConnectionImpl::UpdateDefaultCandidate(const std::string& defaultAddr,
+ uint16_t defaultPort,
+ const std::string& defaultRtcpAddr,
+ uint16_t defaultRtcpPort,
+ uint16_t level) {
+ CSFLogDebug(logTag, "%s", __FUNCTION__);
+ mJsepSession->UpdateDefaultCandidate(defaultAddr,
+ defaultPort,
+ defaultRtcpAddr,
+ defaultRtcpPort,
+ level);
+}
+
+void
+PeerConnectionImpl::EndOfLocalCandidates(uint16_t level) {
+ CSFLogDebug(logTag, "%s", __FUNCTION__);
+ mJsepSession->EndOfLocalCandidates(level);
+}
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+nsresult
+PeerConnectionImpl::BuildStatsQuery_m(
+ mozilla::dom::MediaStreamTrack *aSelector,
+ RTCStatsQuery *query) {
+
+ if (!HasMedia()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mThread) {
+ CSFLogError(logTag, "Could not build stats query, no MainThread");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = GetTimeSinceEpoch(&(query->now));
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "Could not build stats query, could not get timestamp");
+ return rv;
+ }
+
+ // Note: mMedia->ice_ctx() is deleted on STS thread; so make sure we grab and hold
+ // a ref instead of making multiple calls. NrIceCtx uses threadsafe refcounting.
+ // NOTE: Do this after all other failure tests, to ensure we don't
+ // accidentally release the Ctx on Mainthread.
+ query->iceCtx = mMedia->ice_ctx();
+ if (!query->iceCtx) {
+ CSFLogError(logTag, "Could not build stats query, no ice_ctx");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // We do not use the pcHandle here, since that's risky to expose to content.
+ query->report = new RTCStatsReportInternalConstruct(
+ NS_ConvertASCIItoUTF16(mName.c_str()),
+ query->now);
+
+ query->iceStartTime = mIceStartTime;
+ query->failed = isFailed(mIceConnectionState);
+
+ // Populate SDP on main
+ if (query->internalStats) {
+ if (mJsepSession) {
+ std::string localDescription = mJsepSession->GetLocalDescription();
+ std::string remoteDescription = mJsepSession->GetRemoteDescription();
+ query->report->mLocalSdp.Construct(
+ NS_ConvertASCIItoUTF16(localDescription.c_str()));
+ query->report->mRemoteSdp.Construct(
+ NS_ConvertASCIItoUTF16(remoteDescription.c_str()));
+ }
+ }
+
+ // Gather up pipelines from mMedia so they may be inspected on STS
+
+ std::string trackId;
+ if (aSelector) {
+ trackId = PeerConnectionImpl::GetTrackId(*aSelector);
+ }
+
+ for (int i = 0, len = mMedia->LocalStreamsLength(); i < len; i++) {
+ for (auto pipeline : mMedia->GetLocalStreamByIndex(i)->GetPipelines()) {
+ if (!aSelector || pipeline.second->trackid() == trackId) {
+ query->pipelines.AppendElement(pipeline.second);
+ }
+ }
+ }
+ for (int i = 0, len = mMedia->RemoteStreamsLength(); i < len; i++) {
+ for (auto pipeline : mMedia->GetRemoteStreamByIndex(i)->GetPipelines()) {
+ if (!aSelector || pipeline.second->trackid() == trackId) {
+ query->pipelines.AppendElement(pipeline.second);
+ }
+ }
+ }
+
+ if (!aSelector) {
+ query->grabAllLevels = true;
+ }
+
+ return rv;
+}
+
+static void ToRTCIceCandidateStats(
+ const std::vector<NrIceCandidate>& candidates,
+ RTCStatsType candidateType,
+ const nsString& componentId,
+ DOMHighResTimeStamp now,
+ RTCStatsReportInternal* report) {
+
+ MOZ_ASSERT(report);
+ for (auto c = candidates.begin(); c != candidates.end(); ++c) {
+ RTCIceCandidateStats cand;
+ cand.mType.Construct(candidateType);
+ NS_ConvertASCIItoUTF16 codeword(c->codeword.c_str());
+ cand.mComponentId.Construct(componentId);
+ cand.mId.Construct(codeword);
+ cand.mTimestamp.Construct(now);
+ cand.mCandidateType.Construct(
+ RTCStatsIceCandidateType(c->type));
+ cand.mIpAddress.Construct(
+ NS_ConvertASCIItoUTF16(c->cand_addr.host.c_str()));
+ cand.mPortNumber.Construct(c->cand_addr.port);
+ cand.mTransport.Construct(
+ NS_ConvertASCIItoUTF16(c->cand_addr.transport.c_str()));
+ if (candidateType == RTCStatsType::Localcandidate) {
+ cand.mMozLocalTransport.Construct(
+ NS_ConvertASCIItoUTF16(c->local_addr.transport.c_str()));
+ }
+ report->mIceCandidateStats.Value().AppendElement(cand, fallible);
+ }
+}
+
+static void RecordIceStats_s(
+ NrIceMediaStream& mediaStream,
+ bool internalStats,
+ DOMHighResTimeStamp now,
+ RTCStatsReportInternal* report) {
+
+ NS_ConvertASCIItoUTF16 componentId(mediaStream.name().c_str());
+
+ std::vector<NrIceCandidatePair> candPairs;
+ nsresult res = mediaStream.GetCandidatePairs(&candPairs);
+ if (NS_FAILED(res)) {
+ CSFLogError(logTag, "%s: Error getting candidate pairs", __FUNCTION__);
+ return;
+ }
+
+ for (auto p = candPairs.begin(); p != candPairs.end(); ++p) {
+ NS_ConvertASCIItoUTF16 codeword(p->codeword.c_str());
+ NS_ConvertASCIItoUTF16 localCodeword(p->local.codeword.c_str());
+ NS_ConvertASCIItoUTF16 remoteCodeword(p->remote.codeword.c_str());
+ // Only expose candidate-pair statistics to chrome, until we've thought
+ // through the implications of exposing it to content.
+
+ RTCIceCandidatePairStats s;
+ s.mId.Construct(codeword);
+ s.mComponentId.Construct(componentId);
+ s.mTimestamp.Construct(now);
+ s.mType.Construct(RTCStatsType::Candidatepair);
+ s.mLocalCandidateId.Construct(localCodeword);
+ s.mRemoteCandidateId.Construct(remoteCodeword);
+ s.mNominated.Construct(p->nominated);
+ s.mPriority.Construct(p->priority);
+ s.mSelected.Construct(p->selected);
+ s.mState.Construct(RTCStatsIceCandidatePairState(p->state));
+ report->mIceCandidatePairStats.Value().AppendElement(s, fallible);
+ }
+
+ std::vector<NrIceCandidate> candidates;
+ if (NS_SUCCEEDED(mediaStream.GetLocalCandidates(&candidates))) {
+ ToRTCIceCandidateStats(candidates,
+ RTCStatsType::Localcandidate,
+ componentId,
+ now,
+ report);
+ }
+ candidates.clear();
+
+ if (NS_SUCCEEDED(mediaStream.GetRemoteCandidates(&candidates))) {
+ ToRTCIceCandidateStats(candidates,
+ RTCStatsType::Remotecandidate,
+ componentId,
+ now,
+ report);
+ }
+}
+
+nsresult
+PeerConnectionImpl::ExecuteStatsQuery_s(RTCStatsQuery *query) {
+
+ ASSERT_ON_THREAD(query->iceCtx->thread());
+
+ // Gather stats from pipelines provided (can't touch mMedia + stream on STS)
+
+ for (size_t p = 0; p < query->pipelines.Length(); ++p) {
+ const MediaPipeline& mp = *query->pipelines[p];
+ bool isAudio = (mp.Conduit()->type() == MediaSessionConduit::AUDIO);
+ nsString mediaType = isAudio ?
+ NS_LITERAL_STRING("audio") : NS_LITERAL_STRING("video");
+ nsString idstr = mediaType;
+ idstr.AppendLiteral("_");
+ idstr.AppendInt(mp.level());
+
+ // Gather pipeline stats.
+ switch (mp.direction()) {
+ case MediaPipeline::TRANSMIT: {
+ nsString localId = NS_LITERAL_STRING("outbound_rtp_") + idstr;
+ nsString remoteId;
+ nsString ssrc;
+ unsigned int ssrcval;
+ if (mp.Conduit()->GetLocalSSRC(&ssrcval)) {
+ ssrc.AppendInt(ssrcval);
+ }
+ {
+ // First, fill in remote stat with rtcp receiver data, if present.
+ // ReceiverReports have less information than SenderReports,
+ // so fill in what we can.
+ DOMHighResTimeStamp timestamp;
+ uint32_t jitterMs;
+ uint32_t packetsReceived;
+ uint64_t bytesReceived;
+ uint32_t packetsLost;
+ int32_t rtt;
+ if (mp.Conduit()->GetRTCPReceiverReport(&timestamp, &jitterMs,
+ &packetsReceived,
+ &bytesReceived,
+ &packetsLost,
+ &rtt)) {
+ remoteId = NS_LITERAL_STRING("outbound_rtcp_") + idstr;
+ RTCInboundRTPStreamStats s;
+ s.mTimestamp.Construct(timestamp);
+ s.mId.Construct(remoteId);
+ s.mType.Construct(RTCStatsType::Inboundrtp);
+ if (ssrc.Length()) {
+ s.mSsrc.Construct(ssrc);
+ }
+ s.mMediaType.Construct(mediaType);
+ s.mJitter.Construct(double(jitterMs)/1000);
+ s.mRemoteId.Construct(localId);
+ s.mIsRemote = true;
+ s.mPacketsReceived.Construct(packetsReceived);
+ s.mBytesReceived.Construct(bytesReceived);
+ s.mPacketsLost.Construct(packetsLost);
+ s.mMozRtt.Construct(rtt);
+ query->report->mInboundRTPStreamStats.Value().AppendElement(s,
+ fallible);
+ }
+ }
+ // Then, fill in local side (with cross-link to remote only if present)
+ {
+ RTCOutboundRTPStreamStats s;
+ s.mTimestamp.Construct(query->now);
+ s.mId.Construct(localId);
+ s.mType.Construct(RTCStatsType::Outboundrtp);
+ if (ssrc.Length()) {
+ s.mSsrc.Construct(ssrc);
+ }
+ s.mMediaType.Construct(mediaType);
+ s.mRemoteId.Construct(remoteId);
+ s.mIsRemote = false;
+ s.mPacketsSent.Construct(mp.rtp_packets_sent());
+ s.mBytesSent.Construct(mp.rtp_bytes_sent());
+
+ // Lastly, fill in video encoder stats if this is video
+ if (!isAudio) {
+ double framerateMean;
+ double framerateStdDev;
+ double bitrateMean;
+ double bitrateStdDev;
+ uint32_t droppedFrames;
+ if (mp.Conduit()->GetVideoEncoderStats(&framerateMean,
+ &framerateStdDev,
+ &bitrateMean,
+ &bitrateStdDev,
+ &droppedFrames)) {
+ s.mFramerateMean.Construct(framerateMean);
+ s.mFramerateStdDev.Construct(framerateStdDev);
+ s.mBitrateMean.Construct(bitrateMean);
+ s.mBitrateStdDev.Construct(bitrateStdDev);
+ s.mDroppedFrames.Construct(droppedFrames);
+ }
+ }
+ query->report->mOutboundRTPStreamStats.Value().AppendElement(s,
+ fallible);
+ }
+ break;
+ }
+ case MediaPipeline::RECEIVE: {
+ nsString localId = NS_LITERAL_STRING("inbound_rtp_") + idstr;
+ nsString remoteId;
+ nsString ssrc;
+ unsigned int ssrcval;
+ if (mp.Conduit()->GetRemoteSSRC(&ssrcval)) {
+ ssrc.AppendInt(ssrcval);
+ }
+ {
+ // First, fill in remote stat with rtcp sender data, if present.
+ DOMHighResTimeStamp timestamp;
+ uint32_t packetsSent;
+ uint64_t bytesSent;
+ if (mp.Conduit()->GetRTCPSenderReport(&timestamp,
+ &packetsSent, &bytesSent)) {
+ remoteId = NS_LITERAL_STRING("inbound_rtcp_") + idstr;
+ RTCOutboundRTPStreamStats s;
+ s.mTimestamp.Construct(timestamp);
+ s.mId.Construct(remoteId);
+ s.mType.Construct(RTCStatsType::Outboundrtp);
+ if (ssrc.Length()) {
+ s.mSsrc.Construct(ssrc);
+ }
+ s.mMediaType.Construct(mediaType);
+ s.mRemoteId.Construct(localId);
+ s.mIsRemote = true;
+ s.mPacketsSent.Construct(packetsSent);
+ s.mBytesSent.Construct(bytesSent);
+ query->report->mOutboundRTPStreamStats.Value().AppendElement(s,
+ fallible);
+ }
+ }
+ // Then, fill in local side (with cross-link to remote only if present)
+ RTCInboundRTPStreamStats s;
+ s.mTimestamp.Construct(query->now);
+ s.mId.Construct(localId);
+ s.mType.Construct(RTCStatsType::Inboundrtp);
+ if (ssrc.Length()) {
+ s.mSsrc.Construct(ssrc);
+ }
+ s.mMediaType.Construct(mediaType);
+ unsigned int jitterMs, packetsLost;
+ if (mp.Conduit()->GetRTPStats(&jitterMs, &packetsLost)) {
+ s.mJitter.Construct(double(jitterMs)/1000);
+ s.mPacketsLost.Construct(packetsLost);
+ }
+ if (remoteId.Length()) {
+ s.mRemoteId.Construct(remoteId);
+ }
+ s.mIsRemote = false;
+ s.mPacketsReceived.Construct(mp.rtp_packets_received());
+ s.mBytesReceived.Construct(mp.rtp_bytes_received());
+
+ if (query->internalStats && isAudio) {
+ int32_t jitterBufferDelay;
+ int32_t playoutBufferDelay;
+ int32_t avSyncDelta;
+ if (mp.Conduit()->GetAVStats(&jitterBufferDelay,
+ &playoutBufferDelay,
+ &avSyncDelta)) {
+ s.mMozJitterBufferDelay.Construct(jitterBufferDelay);
+ s.mMozAvSyncDelay.Construct(avSyncDelta);
+ }
+ }
+ // Lastly, fill in video decoder stats if this is video
+ if (!isAudio) {
+ double framerateMean;
+ double framerateStdDev;
+ double bitrateMean;
+ double bitrateStdDev;
+ uint32_t discardedPackets;
+ if (mp.Conduit()->GetVideoDecoderStats(&framerateMean,
+ &framerateStdDev,
+ &bitrateMean,
+ &bitrateStdDev,
+ &discardedPackets)) {
+ s.mFramerateMean.Construct(framerateMean);
+ s.mFramerateStdDev.Construct(framerateStdDev);
+ s.mBitrateMean.Construct(bitrateMean);
+ s.mBitrateStdDev.Construct(bitrateStdDev);
+ s.mDiscardedPackets.Construct(discardedPackets);
+ }
+ }
+ query->report->mInboundRTPStreamStats.Value().AppendElement(s,
+ fallible);
+ break;
+ }
+ }
+
+ if (!query->grabAllLevels) {
+ // If we're grabbing all levels, that means we want datachannels too,
+ // which don't have pipelines.
+ if (query->iceCtx->GetStream(p)) {
+ RecordIceStats_s(*query->iceCtx->GetStream(p),
+ query->internalStats,
+ query->now,
+ query->report);
+ }
+ }
+ }
+
+ if (query->grabAllLevels) {
+ for (size_t i = 0; i < query->iceCtx->GetStreamCount(); ++i) {
+ if (query->iceCtx->GetStream(i)) {
+ RecordIceStats_s(*query->iceCtx->GetStream(i),
+ query->internalStats,
+ query->now,
+ query->report);
+ }
+ }
+ }
+
+ // NrIceCtx must be destroyed on STS, so it is not safe
+ // to dispatch it back to main.
+ query->iceCtx = nullptr;
+ return NS_OK;
+}
+
+void PeerConnectionImpl::GetStatsForPCObserver_s(
+ const std::string& pcHandle, // The Runnable holds the memory
+ nsAutoPtr<RTCStatsQuery> query) {
+
+ MOZ_ASSERT(query);
+ MOZ_ASSERT(query->iceCtx);
+ ASSERT_ON_THREAD(query->iceCtx->thread());
+
+ nsresult rv = PeerConnectionImpl::ExecuteStatsQuery_s(query.get());
+
+ NS_DispatchToMainThread(
+ WrapRunnableNM(
+ &PeerConnectionImpl::DeliverStatsReportToPCObserver_m,
+ pcHandle,
+ rv,
+ query),
+ NS_DISPATCH_NORMAL);
+}
+
+void PeerConnectionImpl::DeliverStatsReportToPCObserver_m(
+ const std::string& pcHandle,
+ nsresult result,
+ nsAutoPtr<RTCStatsQuery> query) {
+
+ // Is the PeerConnectionImpl still around?
+ PeerConnectionWrapper pcw(pcHandle);
+ if (pcw.impl()) {
+ RefPtr<PeerConnectionObserver> pco =
+ do_QueryObjectReferent(pcw.impl()->mPCObserver);
+ if (pco) {
+ JSErrorResult rv;
+ if (NS_SUCCEEDED(result)) {
+ pco->OnGetStatsSuccess(*query->report, rv);
+ } else {
+ pco->OnGetStatsError(kInternalError,
+ ObString("Failed to fetch statistics"),
+ rv);
+ }
+
+ if (rv.Failed()) {
+ CSFLogError(logTag, "Error firing stats observer callback");
+ }
+ }
+ }
+}
+
+#endif
+
+void
+PeerConnectionImpl::RecordLongtermICEStatistics() {
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ WebrtcGlobalInformation::StoreLongTermICEStatistics(*this);
+#endif
+}
+
+void
+PeerConnectionImpl::OnNegotiationNeeded()
+{
+ if (mSignalingState != PCImplSignalingState::SignalingStable) {
+ // We will check whether we need to renegotiate when we reach stable again
+ return;
+ }
+
+ if (mNegotiationNeeded) {
+ return;
+ }
+
+ mNegotiationNeeded = true;
+
+ RUN_ON_THREAD(mThread,
+ WrapRunnableNM(&MaybeFireNegotiationNeeded_static, mHandle),
+ NS_DISPATCH_NORMAL);
+}
+
+/* static */
+void
+PeerConnectionImpl::MaybeFireNegotiationNeeded_static(
+ const std::string& pcHandle)
+{
+ PeerConnectionWrapper wrapper(pcHandle);
+ if (!wrapper.impl()) {
+ return;
+ }
+
+ wrapper.impl()->MaybeFireNegotiationNeeded();
+}
+
+void
+PeerConnectionImpl::MaybeFireNegotiationNeeded()
+{
+ if (!mNegotiationNeeded) {
+ return;
+ }
+
+ RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
+ if (!pco) {
+ return;
+ }
+
+ JSErrorResult rv;
+ pco->OnNegotiationNeeded(rv);
+}
+
+void
+PeerConnectionImpl::IceStreamReady(NrIceMediaStream *aStream)
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(aStream);
+
+ CSFLogDebug(logTag, "%s: %s", __FUNCTION__, aStream->name().c_str());
+}
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+//Telemetry for when calls start
+void
+PeerConnectionImpl::startCallTelem() {
+ if (!mStartTime.IsNull()) {
+ return;
+ }
+
+ // Start time for calls
+ mStartTime = TimeStamp::Now();
+
+ // Increment session call counter
+ // If we want to track Loop calls independently here, we need two histograms.
+ Telemetry::Accumulate(Telemetry::WEBRTC_CALL_COUNT_2, 1);
+}
+#endif
+
+NS_IMETHODIMP
+PeerConnectionImpl::GetLocalStreams(nsTArray<RefPtr<DOMMediaStream > >& result)
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ for(uint32_t i=0; i < media()->LocalStreamsLength(); i++) {
+ LocalSourceStreamInfo *info = media()->GetLocalStreamByIndex(i);
+ NS_ENSURE_TRUE(info, NS_ERROR_UNEXPECTED);
+ result.AppendElement(info->GetMediaStream());
+ }
+ return NS_OK;
+#else
+ return NS_ERROR_FAILURE;
+#endif
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::GetRemoteStreams(nsTArray<RefPtr<DOMMediaStream > >& result)
+{
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ for(uint32_t i=0; i < media()->RemoteStreamsLength(); i++) {
+ RemoteSourceStreamInfo *info = media()->GetRemoteStreamByIndex(i);
+ NS_ENSURE_TRUE(info, NS_ERROR_UNEXPECTED);
+ result.AppendElement(info->GetMediaStream());
+ }
+ return NS_OK;
+#else
+ return NS_ERROR_FAILURE;
+#endif
+}
+
+nsresult
+PeerConnectionImpl::DTMFState::Notify(nsITimer* timer)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsString eventTone;
+ if (!mTones.IsEmpty()) {
+ uint16_t toneChar = mTones.CharAt(0);
+ int tone = GetDTMFToneCode(toneChar);
+
+ eventTone.Assign(toneChar);
+
+ mTones.Cut(0, 1);
+
+ if (tone == -1) {
+ mSendTimer->InitWithCallback(this, 2000, nsITimer::TYPE_ONE_SHOT);
+ } else {
+ // Reset delay if necessary
+ mSendTimer->InitWithCallback(this,
+ mDuration + mInterToneGap,
+ nsITimer::TYPE_ONE_SHOT);
+
+ RefPtr<AudioSessionConduit> conduit =
+ mPeerConnectionImpl->mMedia->GetAudioConduit(mLevel);
+
+ if (conduit) {
+ uint32_t duration = mDuration;
+ mPeerConnectionImpl->mSTSThread->Dispatch(WrapRunnableNM([conduit, tone, duration] () {
+ //Note: We default to channel 0, not inband, and 6dB attenuation.
+ // here. We might want to revisit these choices in the future.
+ conduit->InsertDTMFTone(0, tone, true, duration, 6);
+ }), NS_DISPATCH_NORMAL);
+ }
+ }
+ } else {
+ mSendTimer->Cancel();
+ }
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPeerConnectionImpl->mPCObserver);
+ if (!pco) {
+ NS_WARNING("Failed to dispatch the RTCDTMFToneChange event!");
+ return NS_OK; // Return is ignored anyhow
+ }
+
+ JSErrorResult jrv;
+ pco->OnDTMFToneChange(mTrackId, eventTone, jrv);
+
+ if (jrv.Failed()) {
+ NS_WARNING("Failed to dispatch the RTCDTMFToneChange event!");
+ }
+#endif
+
+ return NS_OK;
+}
+
+PeerConnectionImpl::DTMFState::DTMFState() = default;
+PeerConnectionImpl::DTMFState::~DTMFState() = default;
+
+NS_IMPL_ISUPPORTS(PeerConnectionImpl::DTMFState, nsITimerCallback)
+
+} // end mozilla namespace
diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
new file mode 100644
index 000000000..c29d08180
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -0,0 +1,894 @@
+/* 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/. */
+
+#ifndef _PEER_CONNECTION_IMPL_H_
+#define _PEER_CONNECTION_IMPL_H_
+
+#include <deque>
+#include <string>
+#include <vector>
+#include <map>
+#include <cmath>
+
+#include "prlock.h"
+#include "mozilla/RefPtr.h"
+#include "nsWeakPtr.h"
+#include "nsAutoPtr.h"
+#include "nsIWeakReferenceUtils.h" // for the definition of nsWeakPtr
+#include "IPeerConnection.h"
+#include "sigslot.h"
+#include "nricectx.h"
+#include "nricemediastream.h"
+#include "nsComponentManagerUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsIUUIDGenerator.h"
+#include "nsIThread.h"
+
+#include "signaling/src/jsep/JsepSession.h"
+#include "signaling/src/jsep/JsepSessionImpl.h"
+#include "signaling/src/sdp/SdpMediaSection.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/PeerConnectionImplEnumsBinding.h"
+#include "PrincipalChangeObserver.h"
+#include "StreamTracks.h"
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+#include "mozilla/TimeStamp.h"
+#include "mozilla/net/DataChannel.h"
+#include "VideoUtils.h"
+#include "VideoSegment.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "nsIPrincipal.h"
+#include "mozilla/PeerIdentity.h"
+#endif
+
+namespace test {
+#ifdef USE_FAKE_PCOBSERVER
+class AFakePCObserver;
+#endif
+}
+
+#ifdef USE_FAKE_MEDIA_STREAMS
+class Fake_DOMMediaStream;
+class Fake_MediaStreamTrack;
+#endif
+
+class nsGlobalWindow;
+class nsDOMDataChannel;
+
+namespace mozilla {
+class DataChannel;
+class DtlsIdentity;
+class NrIceCtx;
+class NrIceMediaStream;
+class NrIceStunServer;
+class NrIceTurnServer;
+class MediaPipeline;
+
+#ifdef USE_FAKE_MEDIA_STREAMS
+typedef Fake_DOMMediaStream DOMMediaStream;
+#else
+class DOMMediaStream;
+#endif
+
+namespace dom {
+class RTCCertificate;
+struct RTCConfiguration;
+class RTCDTMFSender;
+struct RTCIceServer;
+struct RTCOfferOptions;
+struct RTCRtpParameters;
+class RTCRtpSender;
+#ifdef USE_FAKE_MEDIA_STREAMS
+typedef Fake_MediaStreamTrack MediaStreamTrack;
+#else
+class MediaStreamTrack;
+#endif
+
+#ifdef USE_FAKE_PCOBSERVER
+typedef test::AFakePCObserver PeerConnectionObserver;
+typedef const char *PCObserverString;
+#else
+class PeerConnectionObserver;
+typedef NS_ConvertUTF8toUTF16 PCObserverString;
+#endif
+}
+}
+
+#if defined(__cplusplus) && __cplusplus >= 201103L
+typedef struct Timecard Timecard;
+#else
+#include "timecard.h"
+#endif
+
+// To preserve blame, convert nsresult to ErrorResult with wrappers. These macros
+// help declare wrappers w/function being wrapped when there are no differences.
+
+#define NS_IMETHODIMP_TO_ERRORRESULT(func, rv, ...) \
+NS_IMETHODIMP func(__VA_ARGS__); \
+void func (__VA_ARGS__, rv)
+
+#define NS_IMETHODIMP_TO_ERRORRESULT_RETREF(resulttype, func, rv, ...) \
+NS_IMETHODIMP func(__VA_ARGS__, resulttype **result); \
+already_AddRefed<resulttype> func (__VA_ARGS__, rv)
+
+struct MediaStreamTable;
+
+namespace mozilla {
+
+using mozilla::dom::PeerConnectionObserver;
+using mozilla::dom::RTCConfiguration;
+using mozilla::dom::RTCIceServer;
+using mozilla::dom::RTCOfferOptions;
+using mozilla::DOMMediaStream;
+using mozilla::NrIceCtx;
+using mozilla::NrIceMediaStream;
+using mozilla::DtlsIdentity;
+using mozilla::ErrorResult;
+using mozilla::NrIceStunServer;
+using mozilla::NrIceTurnServer;
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+using mozilla::PeerIdentity;
+#endif
+
+class PeerConnectionWrapper;
+class PeerConnectionMedia;
+class RemoteSourceStreamInfo;
+
+// Uuid Generator
+class PCUuidGenerator : public mozilla::JsepUuidGenerator {
+ public:
+ virtual bool Generate(std::string* idp) override;
+
+ private:
+ nsCOMPtr<nsIUUIDGenerator> mGenerator;
+};
+
+class PeerConnectionConfiguration
+{
+public:
+ PeerConnectionConfiguration()
+ : mBundlePolicy(kBundleBalanced),
+ mIceTransportPolicy(NrIceCtx::ICE_POLICY_ALL) {}
+
+ bool addStunServer(const std::string& addr, uint16_t port,
+ const char* transport)
+ {
+ UniquePtr<NrIceStunServer> server(NrIceStunServer::Create(addr, port, transport));
+ if (!server) {
+ return false;
+ }
+ addStunServer(*server);
+ return true;
+ }
+ bool addTurnServer(const std::string& addr, uint16_t port,
+ const std::string& username,
+ const std::string& pwd,
+ const char* transport)
+ {
+ // TODO(ekr@rtfm.com): Need support for SASLprep for
+ // username and password. Bug # ???
+ std::vector<unsigned char> password(pwd.begin(), pwd.end());
+
+ UniquePtr<NrIceTurnServer> server(NrIceTurnServer::Create(addr, port, username, password,
+ transport));
+ if (!server) {
+ return false;
+ }
+ addTurnServer(*server);
+ return true;
+ }
+ void addStunServer(const NrIceStunServer& server) { mStunServers.push_back (server); }
+ void addTurnServer(const NrIceTurnServer& server) { mTurnServers.push_back (server); }
+ const std::vector<NrIceStunServer>& getStunServers() const { return mStunServers; }
+ const std::vector<NrIceTurnServer>& getTurnServers() const { return mTurnServers; }
+ void setBundlePolicy(JsepBundlePolicy policy) { mBundlePolicy = policy;}
+ JsepBundlePolicy getBundlePolicy() const { return mBundlePolicy; }
+ void setIceTransportPolicy(NrIceCtx::Policy policy) { mIceTransportPolicy = policy;}
+ NrIceCtx::Policy getIceTransportPolicy() const { return mIceTransportPolicy; }
+
+#ifndef MOZILLA_EXTERNAL_LINKAGE
+ nsresult Init(const RTCConfiguration& aSrc);
+ nsresult AddIceServer(const RTCIceServer& aServer);
+#endif
+
+private:
+ std::vector<NrIceStunServer> mStunServers;
+ std::vector<NrIceTurnServer> mTurnServers;
+ JsepBundlePolicy mBundlePolicy;
+ NrIceCtx::Policy mIceTransportPolicy;
+};
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+// Not an inner class so we can forward declare.
+class RTCStatsQuery {
+ public:
+ explicit RTCStatsQuery(bool internalStats);
+ ~RTCStatsQuery();
+
+ nsAutoPtr<mozilla::dom::RTCStatsReportInternal> report;
+ std::string error;
+ // A timestamp to help with telemetry.
+ mozilla::TimeStamp iceStartTime;
+ // Just for convenience, maybe integrate into the report later
+ bool failed;
+
+ private:
+ friend class PeerConnectionImpl;
+ std::string pcName;
+ bool internalStats;
+ nsTArray<RefPtr<mozilla::MediaPipeline>> pipelines;
+ RefPtr<NrIceCtx> iceCtx;
+ bool grabAllLevels;
+ DOMHighResTimeStamp now;
+};
+#endif // MOZILLA_INTERNAL_API
+
+// Enter an API call and check that the state is OK,
+// the PC isn't closed, etc.
+#define PC_AUTO_ENTER_API_CALL(assert_ice_ready) \
+ do { \
+ /* do/while prevents res from conflicting with locals */ \
+ nsresult res = CheckApiState(assert_ice_ready); \
+ if (NS_FAILED(res)) return res; \
+ } while(0)
+#define PC_AUTO_ENTER_API_CALL_VOID_RETURN(assert_ice_ready) \
+ do { \
+ /* do/while prevents res from conflicting with locals */ \
+ nsresult res = CheckApiState(assert_ice_ready); \
+ if (NS_FAILED(res)) return; \
+ } while(0)
+#define PC_AUTO_ENTER_API_CALL_NO_CHECK() CheckThread()
+
+class PeerConnectionImpl final : public nsISupports,
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ public mozilla::DataChannelConnection::DataConnectionListener,
+ public dom::PrincipalChangeObserver<dom::MediaStreamTrack>,
+#endif
+ public sigslot::has_slots<>
+{
+ struct Internal; // Avoid exposing c includes to bindings
+
+public:
+ explicit PeerConnectionImpl(const mozilla::dom::GlobalObject* aGlobal = nullptr);
+
+ enum Error {
+ kNoError = 0,
+ kInvalidCandidate = 2,
+ kInvalidMediastreamTrack = 3,
+ kInvalidState = 4,
+ kInvalidSessionDescription = 5,
+ kIncompatibleSessionDescription = 6,
+ kIncompatibleMediaStreamTrack = 8,
+ kInternalError = 9
+ };
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aReflector);
+#endif
+
+ static already_AddRefed<PeerConnectionImpl>
+ Constructor(const mozilla::dom::GlobalObject& aGlobal, ErrorResult& rv);
+ static PeerConnectionImpl* CreatePeerConnection();
+ already_AddRefed<DOMMediaStream> MakeMediaStream();
+
+ nsresult CreateRemoteSourceStreamInfo(RefPtr<RemoteSourceStreamInfo>* aInfo,
+ const std::string& aId);
+
+ // DataConnection observers
+ void NotifyDataChannel(already_AddRefed<mozilla::DataChannel> aChannel)
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // PeerConnectionImpl only inherits from mozilla::DataChannelConnection
+ // inside libxul.
+ override
+#endif
+ ;
+
+ // Get the media object
+ const RefPtr<PeerConnectionMedia>& media() const {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mMedia;
+ }
+
+ // Configure the ability to use localhost.
+ void SetAllowIceLoopback(bool val) { mAllowIceLoopback = val; }
+ bool GetAllowIceLoopback() const { return mAllowIceLoopback; }
+
+ // Configure the ability to use IPV6 link-local addresses.
+ void SetAllowIceLinkLocal(bool val) { mAllowIceLinkLocal = val; }
+ bool GetAllowIceLinkLocal() const { return mAllowIceLinkLocal; }
+
+ // Handle system to allow weak references to be passed through C code
+ virtual const std::string& GetHandle();
+
+ // Name suitable for exposing to content
+ virtual const std::string& GetName();
+
+ // ICE events
+ void IceConnectionStateChange(NrIceCtx* ctx,
+ NrIceCtx::ConnectionState state);
+ void IceGatheringStateChange(NrIceCtx* ctx,
+ NrIceCtx::GatheringState state);
+ void UpdateDefaultCandidate(const std::string& defaultAddr,
+ uint16_t defaultPort,
+ const std::string& defaultRtcpAddr,
+ uint16_t defaultRtcpPort,
+ uint16_t level);
+ void EndOfLocalCandidates(uint16_t level);
+ void IceStreamReady(NrIceMediaStream *aStream);
+
+ static void ListenThread(void *aData);
+ static void ConnectThread(void *aData);
+
+ // Get the main thread
+ nsCOMPtr<nsIThread> GetMainThread() {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mThread;
+ }
+
+ // Get the STS thread
+ nsIEventTarget* GetSTSThread() {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mSTSThread;
+ }
+
+ nsPIDOMWindowInner* GetWindow() const {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mWindow;
+ }
+
+ // Initialize PeerConnection from a PeerConnectionConfiguration object
+ // (used directly by unit-tests, and indirectly by the JS entry point)
+ // This is necessary because RTCConfiguration can't be used by unit-tests
+ nsresult Initialize(PeerConnectionObserver& aObserver,
+ nsGlobalWindow* aWindow,
+ const PeerConnectionConfiguration& aConfiguration,
+ nsISupports* aThread);
+
+#ifndef MOZILLA_EXTERNAL_LINKAGE
+ // Initialize PeerConnection from an RTCConfiguration object (JS entrypoint)
+ void Initialize(PeerConnectionObserver& aObserver,
+ nsGlobalWindow& aWindow,
+ const RTCConfiguration& aConfiguration,
+ nsISupports* aThread,
+ ErrorResult &rv);
+#endif
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ void SetCertificate(mozilla::dom::RTCCertificate& aCertificate);
+ const RefPtr<mozilla::dom::RTCCertificate>& Certificate() const;
+#endif
+ // This is a hack to support external linkage.
+ RefPtr<DtlsIdentity> Identity() const;
+
+ NS_IMETHODIMP_TO_ERRORRESULT(CreateOffer, ErrorResult &rv,
+ const RTCOfferOptions& aOptions)
+ {
+ rv = CreateOffer(aOptions);
+ }
+
+ NS_IMETHODIMP CreateAnswer();
+ void CreateAnswer(ErrorResult &rv)
+ {
+ rv = CreateAnswer();
+ }
+
+ NS_IMETHODIMP CreateOffer(
+ const mozilla::JsepOfferOptions& aConstraints);
+
+ NS_IMETHODIMP SetLocalDescription (int32_t aAction, const char* aSDP);
+
+ void SetLocalDescription (int32_t aAction, const nsAString& aSDP, ErrorResult &rv)
+ {
+ rv = SetLocalDescription(aAction, NS_ConvertUTF16toUTF8(aSDP).get());
+ }
+
+ nsresult CreateNewRemoteTracks(RefPtr<PeerConnectionObserver>& aPco);
+
+ void RemoveOldRemoteTracks(RefPtr<PeerConnectionObserver>& aPco);
+
+ NS_IMETHODIMP SetRemoteDescription (int32_t aAction, const char* aSDP);
+
+ void SetRemoteDescription (int32_t aAction, const nsAString& aSDP, ErrorResult &rv)
+ {
+ rv = SetRemoteDescription(aAction, NS_ConvertUTF16toUTF8(aSDP).get());
+ }
+
+ NS_IMETHODIMP_TO_ERRORRESULT(GetStats, ErrorResult &rv,
+ mozilla::dom::MediaStreamTrack *aSelector)
+ {
+ rv = GetStats(aSelector);
+ }
+
+ NS_IMETHODIMP AddIceCandidate(const char* aCandidate, const char* aMid,
+ unsigned short aLevel);
+
+ void AddIceCandidate(const nsAString& aCandidate, const nsAString& aMid,
+ unsigned short aLevel, ErrorResult &rv)
+ {
+ rv = AddIceCandidate(NS_ConvertUTF16toUTF8(aCandidate).get(),
+ NS_ConvertUTF16toUTF8(aMid).get(), aLevel);
+ }
+
+ NS_IMETHODIMP CloseStreams();
+
+ void CloseStreams(ErrorResult &rv)
+ {
+ rv = CloseStreams();
+ }
+
+ NS_IMETHODIMP_TO_ERRORRESULT(AddTrack, ErrorResult &rv,
+ mozilla::dom::MediaStreamTrack& aTrack,
+ const mozilla::dom::Sequence<mozilla::OwningNonNull<DOMMediaStream>>& aStreams)
+ {
+ rv = AddTrack(aTrack, aStreams);
+ }
+
+ NS_IMETHODIMP_TO_ERRORRESULT(RemoveTrack, ErrorResult &rv,
+ mozilla::dom::MediaStreamTrack& aTrack)
+ {
+ rv = RemoveTrack(aTrack);
+ }
+
+ nsresult
+ AddTrack(mozilla::dom::MediaStreamTrack& aTrack, DOMMediaStream& aStream);
+
+ NS_IMETHODIMP_TO_ERRORRESULT(InsertDTMF, ErrorResult &rv,
+ dom::RTCRtpSender& sender,
+ const nsAString& tones,
+ uint32_t duration, uint32_t interToneGap) {
+ rv = InsertDTMF(sender, tones, duration, interToneGap);
+ }
+
+ NS_IMETHODIMP_TO_ERRORRESULT(GetDTMFToneBuffer, ErrorResult &rv,
+ dom::RTCRtpSender& sender,
+ nsAString& outToneBuffer) {
+ rv = GetDTMFToneBuffer(sender, outToneBuffer);
+ }
+
+ NS_IMETHODIMP_TO_ERRORRESULT(ReplaceTrack, ErrorResult &rv,
+ mozilla::dom::MediaStreamTrack& aThisTrack,
+ mozilla::dom::MediaStreamTrack& aWithTrack)
+ {
+ rv = ReplaceTrack(aThisTrack, aWithTrack);
+ }
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ NS_IMETHODIMP_TO_ERRORRESULT(SetParameters, ErrorResult &rv,
+ dom::MediaStreamTrack& aTrack,
+ const dom::RTCRtpParameters& aParameters)
+ {
+ rv = SetParameters(aTrack, aParameters);
+ }
+
+ NS_IMETHODIMP_TO_ERRORRESULT(GetParameters, ErrorResult &rv,
+ dom::MediaStreamTrack& aTrack,
+ dom::RTCRtpParameters& aOutParameters)
+ {
+ rv = GetParameters(aTrack, aOutParameters);
+ }
+#endif
+
+ nsresult
+ SetParameters(dom::MediaStreamTrack& aTrack,
+ const std::vector<JsepTrack::JsConstraints>& aConstraints);
+
+ nsresult
+ GetParameters(dom::MediaStreamTrack& aTrack,
+ std::vector<JsepTrack::JsConstraints>* aOutConstraints);
+
+ NS_IMETHODIMP_TO_ERRORRESULT(SelectSsrc, ErrorResult &rv,
+ dom::MediaStreamTrack& aRecvTrack,
+ unsigned short aSsrcIndex)
+ {
+ rv = SelectSsrc(aRecvTrack, aSsrcIndex);
+ }
+
+ nsresult GetPeerIdentity(nsAString& peerIdentity)
+ {
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ if (mPeerIdentity) {
+ peerIdentity = mPeerIdentity->ToString();
+ return NS_OK;
+ }
+#endif
+
+ peerIdentity.SetIsVoid(true);
+ return NS_OK;
+ }
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ const PeerIdentity* GetPeerIdentity() const { return mPeerIdentity; }
+ nsresult SetPeerIdentity(const nsAString& peerIdentity);
+
+ const std::string& GetIdAsAscii() const
+ {
+ return mName;
+ }
+
+ nsresult GetId(nsAString& id)
+ {
+ id = NS_ConvertASCIItoUTF16(mName.c_str());
+ return NS_OK;
+ }
+
+ nsresult SetId(const nsAString& id)
+ {
+ mName = NS_ConvertUTF16toUTF8(id).get();
+ return NS_OK;
+ }
+#endif
+
+ // this method checks to see if we've made a promise to protect media.
+ bool PrivacyRequested() const { return mPrivacyRequested; }
+
+ NS_IMETHODIMP GetFingerprint(char** fingerprint);
+ void GetFingerprint(nsAString& fingerprint)
+ {
+ char *tmp;
+ GetFingerprint(&tmp);
+ fingerprint.AssignASCII(tmp);
+ delete[] tmp;
+ }
+
+ NS_IMETHODIMP GetLocalDescription(char** aSDP);
+
+ void GetLocalDescription(nsAString& aSDP)
+ {
+ char *tmp;
+ GetLocalDescription(&tmp);
+ aSDP.AssignASCII(tmp);
+ delete[] tmp;
+ }
+
+ NS_IMETHODIMP GetRemoteDescription(char** aSDP);
+
+ void GetRemoteDescription(nsAString& aSDP)
+ {
+ char *tmp;
+ GetRemoteDescription(&tmp);
+ aSDP.AssignASCII(tmp);
+ delete[] tmp;
+ }
+
+ NS_IMETHODIMP SignalingState(mozilla::dom::PCImplSignalingState* aState);
+
+ mozilla::dom::PCImplSignalingState SignalingState()
+ {
+ mozilla::dom::PCImplSignalingState state;
+ SignalingState(&state);
+ return state;
+ }
+
+ NS_IMETHODIMP IceConnectionState(
+ mozilla::dom::PCImplIceConnectionState* aState);
+
+ mozilla::dom::PCImplIceConnectionState IceConnectionState()
+ {
+ mozilla::dom::PCImplIceConnectionState state;
+ IceConnectionState(&state);
+ return state;
+ }
+
+ NS_IMETHODIMP IceGatheringState(
+ mozilla::dom::PCImplIceGatheringState* aState);
+
+ mozilla::dom::PCImplIceGatheringState IceGatheringState()
+ {
+ mozilla::dom::PCImplIceGatheringState state;
+ IceGatheringState(&state);
+ return state;
+ }
+
+ NS_IMETHODIMP Close();
+
+ void Close(ErrorResult &rv)
+ {
+ rv = Close();
+ }
+
+ bool PluginCrash(uint32_t aPluginID,
+ const nsAString& aPluginName);
+
+ void RecordEndOfCallTelemetry() const;
+
+ nsresult InitializeDataChannel();
+
+ NS_IMETHODIMP_TO_ERRORRESULT_RETREF(nsDOMDataChannel,
+ CreateDataChannel, ErrorResult &rv,
+ const nsAString& aLabel,
+ const nsAString& aProtocol,
+ uint16_t aType,
+ bool outOfOrderAllowed,
+ uint16_t aMaxTime,
+ uint16_t aMaxNum,
+ bool aExternalNegotiated,
+ uint16_t aStream);
+
+ NS_IMETHODIMP_TO_ERRORRESULT(GetLocalStreams, ErrorResult &rv,
+ nsTArray<RefPtr<DOMMediaStream > >& result)
+ {
+ rv = GetLocalStreams(result);
+ }
+
+ NS_IMETHODIMP_TO_ERRORRESULT(GetRemoteStreams, ErrorResult &rv,
+ nsTArray<RefPtr<DOMMediaStream > >& result)
+ {
+ rv = GetRemoteStreams(result);
+ }
+
+ // Called whenever something is unrecognized by the parser
+ // May be called more than once and does not necessarily mean
+ // that parsing was stopped, only that something was unrecognized.
+ void OnSdpParseError(const char* errorMessage);
+
+ // Called when OnLocal/RemoteDescriptionSuccess/Error
+ // is called to start the list over.
+ void ClearSdpParseErrorMessages();
+
+ // Called to retreive the list of parsing errors.
+ const std::vector<std::string> &GetSdpParseErrors();
+
+ // Sets the RTC Signaling State
+ void SetSignalingState_m(mozilla::dom::PCImplSignalingState aSignalingState,
+ bool rollback = false);
+
+ // Updates the RTC signaling state based on the JsepSession state
+ void UpdateSignalingState(bool rollback = false);
+
+ bool IsClosed() const;
+ // called when DTLS connects; we only need this once
+ nsresult SetDtlsConnected(bool aPrivacyRequested);
+
+ bool HasMedia() const;
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // initialize telemetry for when calls start
+ void startCallTelem();
+
+ nsresult BuildStatsQuery_m(
+ mozilla::dom::MediaStreamTrack *aSelector,
+ RTCStatsQuery *query);
+
+ static nsresult ExecuteStatsQuery_s(RTCStatsQuery *query);
+
+ // for monitoring changes in track ownership
+ // PeerConnectionMedia can't do it because it doesn't know about principals
+ virtual void PrincipalChanged(dom::MediaStreamTrack* aTrack) override;
+
+#endif
+
+ static std::string GetStreamId(const DOMMediaStream& aStream);
+ static std::string GetTrackId(const dom::MediaStreamTrack& track);
+
+ void OnMediaError(const std::string& aError);
+
+private:
+ virtual ~PeerConnectionImpl();
+ PeerConnectionImpl(const PeerConnectionImpl&rhs);
+ PeerConnectionImpl& operator=(PeerConnectionImpl);
+ nsresult CalculateFingerprint(const std::string& algorithm,
+ std::vector<uint8_t>* fingerprint) const;
+ nsresult ConfigureJsepSessionCodecs();
+
+ NS_IMETHODIMP EnsureDataConnection(uint16_t aNumstreams);
+
+ nsresult CloseInt();
+ nsresult CheckApiState(bool assert_ice_ready) const;
+ void CheckThread() const {
+ MOZ_ASSERT(CheckThreadInt(), "Wrong thread");
+ }
+ bool CheckThreadInt() const {
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // Thread assertions are disabled in the C++ unit tests because those
+ // make API calls off the main thread.
+ // This affects the standalone version of WebRTC since it is also used
+ // for an alternate build of the unit tests.
+ // TODO(ekr@rtfm.com): Fix the unit tests so they don't do that.
+ bool on;
+ NS_ENSURE_SUCCESS(mThread->IsOnCurrentThread(&on), false);
+ NS_ENSURE_TRUE(on, false);
+#endif
+ return true;
+ }
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ nsresult GetTimeSinceEpoch(DOMHighResTimeStamp *result);
+#endif
+
+ // Shut down media - called on main thread only
+ void ShutdownMedia();
+
+ void CandidateReady(const std::string& candidate, uint16_t level);
+ void SendLocalIceCandidateToContent(uint16_t level,
+ const std::string& mid,
+ const std::string& candidate);
+
+ nsresult GetDatachannelParameters(
+ const mozilla::JsepApplicationCodecDescription** codec,
+ uint16_t* level) const;
+
+ static void DeferredAddTrackToJsepSession(const std::string& pcHandle,
+ SdpMediaSection::MediaType type,
+ const std::string& streamId,
+ const std::string& trackId);
+
+ nsresult AddTrackToJsepSession(SdpMediaSection::MediaType type,
+ const std::string& streamId,
+ const std::string& trackId);
+
+ nsresult SetupIceRestart();
+ nsresult RollbackIceRestart();
+ void FinalizeIceRestart();
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ static void GetStatsForPCObserver_s(
+ const std::string& pcHandle,
+ nsAutoPtr<RTCStatsQuery> query);
+
+ // Sends an RTCStatsReport to JS. Must run on main thread.
+ static void DeliverStatsReportToPCObserver_m(
+ const std::string& pcHandle,
+ nsresult result,
+ nsAutoPtr<RTCStatsQuery> query);
+#endif
+
+ // When ICE completes, we record a bunch of statistics that outlive the
+ // PeerConnection. This is just telemetry right now, but this can also
+ // include things like dumping the RLogConnector somewhere, saving away
+ // an RTCStatsReport somewhere so it can be inspected after the call is over,
+ // or other things.
+ void RecordLongtermICEStatistics();
+
+ void OnNegotiationNeeded();
+ static void MaybeFireNegotiationNeeded_static(const std::string& pcHandle);
+ void MaybeFireNegotiationNeeded();
+
+ // Timecard used to measure processing time. This should be the first class
+ // attribute so that we accurately measure the time required to instantiate
+ // any other attributes of this class.
+ Timecard *mTimeCard;
+
+ mozilla::dom::PCImplSignalingState mSignalingState;
+
+ // ICE State
+ mozilla::dom::PCImplIceConnectionState mIceConnectionState;
+ mozilla::dom::PCImplIceGatheringState mIceGatheringState;
+
+ // DTLS
+ // this is true if we have been connected ever, see SetDtlsConnected
+ bool mDtlsConnected;
+
+ nsCOMPtr<nsIThread> mThread;
+ // TODO: Remove if we ever properly wire PeerConnection for cycle-collection.
+ nsWeakPtr mPCObserver;
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+
+ // The SDP sent in from JS - here for debugging.
+ std::string mLocalRequestedSDP;
+ std::string mRemoteRequestedSDP;
+
+ // DTLS fingerprint
+ std::string mFingerprint;
+ std::string mRemoteFingerprint;
+
+ // identity-related fields
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // The entity on the other end of the peer-to-peer connection;
+ // void if they are not yet identified, and no identity setting has been set
+ RefPtr<PeerIdentity> mPeerIdentity;
+ // The certificate we are using.
+ RefPtr<mozilla::dom::RTCCertificate> mCertificate;
+#else
+ RefPtr<DtlsIdentity> mIdentity;
+#endif
+ // Whether an app should be prevented from accessing media produced by the PC
+ // If this is true, then media will not be sent until mPeerIdentity matches
+ // local streams PeerIdentity; and remote streams are protected from content
+ //
+ // This can be false if mPeerIdentity is set, in the case where identity is
+ // provided, but the media is not protected from the app on either side
+ bool mPrivacyRequested;
+
+ // A handle to refer to this PC with
+ std::string mHandle;
+
+ // A name for this PC that we are willing to expose to content.
+ std::string mName;
+
+ // The target to run stuff on
+ nsCOMPtr<nsIEventTarget> mSTSThread;
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // DataConnection that's used to get all the DataChannels
+ RefPtr<mozilla::DataChannelConnection> mDataConnection;
+#endif
+
+ bool mAllowIceLoopback;
+ bool mAllowIceLinkLocal;
+ RefPtr<PeerConnectionMedia> mMedia;
+
+ // The JSEP negotiation session.
+ mozilla::UniquePtr<PCUuidGenerator> mUuidGen;
+ mozilla::UniquePtr<mozilla::JsepSession> mJsepSession;
+ std::string mPreviousIceUfrag; // used during rollback of ice restart
+ std::string mPreviousIcePwd; // used during rollback of ice restart
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // Start time of ICE, used for telemetry
+ mozilla::TimeStamp mIceStartTime;
+ // Start time of call used for Telemetry
+ mozilla::TimeStamp mStartTime;
+#endif
+
+ // Temporary: used to prevent multiple audio streams or multiple video streams
+ // in a single PC. This is tied up in the IETF discussion around proper
+ // representation of multiple streams in SDP, and strongly related to
+ // Bug 840728.
+ int mNumAudioStreams;
+ int mNumVideoStreams;
+ bool mHaveConfiguredCodecs;
+
+ bool mHaveDataStream;
+
+ unsigned int mAddCandidateErrorCount;
+
+ bool mTrickle;
+
+ bool mNegotiationNeeded;
+
+ bool mPrivateWindow;
+
+ // storage for Telemetry data
+ uint16_t mMaxReceiving[SdpMediaSection::kMediaTypes];
+ uint16_t mMaxSending[SdpMediaSection::kMediaTypes];
+
+ // DTMF
+ class DTMFState : public nsITimerCallback {
+ virtual ~DTMFState();
+ public:
+ DTMFState();
+
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ PeerConnectionImpl* mPeerConnectionImpl;
+ nsCOMPtr<nsITimer> mSendTimer;
+ nsString mTrackId;
+ nsString mTones;
+ size_t mLevel;
+ uint32_t mDuration;
+ uint32_t mInterToneGap;
+ };
+
+ nsTArray<RefPtr<DTMFState>> mDTMFStates;
+
+public:
+ //these are temporary until the DataChannel Listen/Connect API is removed
+ unsigned short listenPort;
+ unsigned short connectPort;
+ char *connectStr; // XXX ownership/free
+};
+
+// This is what is returned when you acquire on a handle
+class PeerConnectionWrapper
+{
+ public:
+ explicit PeerConnectionWrapper(const std::string& handle);
+
+ PeerConnectionImpl *impl() { return impl_; }
+
+ private:
+ RefPtr<PeerConnectionImpl> impl_;
+};
+
+} // end mozilla namespace
+
+#undef NS_IMETHODIMP_TO_ERRORRESULT
+#undef NS_IMETHODIMP_TO_ERRORRESULT_RETREF
+#endif // _PEER_CONNECTION_IMPL_H_
diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
new file mode 100644
index 000000000..4f42b0bb7
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -0,0 +1,1672 @@
+/* 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 <ostream>
+#include <string>
+#include <vector>
+
+#include "CSFLog.h"
+
+#include "nspr.h"
+
+#include "nricectx.h"
+#include "nricemediastream.h"
+#include "MediaPipelineFactory.h"
+#include "PeerConnectionImpl.h"
+#include "PeerConnectionMedia.h"
+#include "AudioConduit.h"
+#include "VideoConduit.h"
+#include "runnable_utils.h"
+#include "transportlayerice.h"
+#include "transportlayerdtls.h"
+#include "signaling/src/jsep/JsepSession.h"
+#include "signaling/src/jsep/JsepTransport.h"
+
+#ifdef USE_FAKE_STREAMS
+#include "DOMMediaStream.h"
+#include "FakeMediaStreams.h"
+#else
+#include "MediaSegment.h"
+#ifdef MOZILLA_INTERNAL_API
+#include "MediaStreamGraph.h"
+#endif
+#endif
+
+#ifndef USE_FAKE_MEDIA_STREAMS
+#include "MediaStreamGraphImpl.h"
+#endif
+
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIURI.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsICancelable.h"
+#include "nsILoadInfo.h"
+#include "nsIContentPolicy.h"
+#include "nsIProxyInfo.h"
+#include "nsIProtocolProxyService.h"
+
+#include "nsProxyRelease.h"
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+#include "MediaStreamList.h"
+#include "nsIScriptGlobalObject.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "MediaStreamTrack.h"
+#include "VideoStreamTrack.h"
+#include "MediaStreamError.h"
+#include "MediaManager.h"
+#endif
+
+
+
+namespace mozilla {
+using namespace dom;
+
+static const char* logTag = "PeerConnectionMedia";
+
+nsresult
+PeerConnectionMedia::ReplaceTrack(const std::string& aOldStreamId,
+ const std::string& aOldTrackId,
+ MediaStreamTrack& aNewTrack,
+ const std::string& aNewStreamId,
+ const std::string& aNewTrackId)
+{
+ RefPtr<LocalSourceStreamInfo> oldInfo(GetLocalStreamById(aOldStreamId));
+
+ if (!oldInfo) {
+ CSFLogError(logTag, "Failed to find stream id %s", aOldStreamId.c_str());
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = AddTrack(*aNewTrack.mOwningStream, aNewStreamId,
+ aNewTrack, aNewTrackId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<LocalSourceStreamInfo> newInfo(GetLocalStreamById(aNewStreamId));
+
+ if (!newInfo) {
+ CSFLogError(logTag, "Failed to add track id %s", aNewTrackId.c_str());
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = newInfo->TakePipelineFrom(oldInfo, aOldTrackId, aNewTrack, aNewTrackId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RemoveLocalTrack(aOldStreamId, aOldTrackId);
+}
+
+static void
+PipelineReleaseRef_m(RefPtr<MediaPipeline> pipeline)
+{}
+
+static void
+PipelineDetachTransport_s(RefPtr<MediaPipeline> pipeline,
+ nsCOMPtr<nsIThread> mainThread)
+{
+ pipeline->DetachTransport_s();
+ mainThread->Dispatch(
+ // Make sure we let go of our reference before dispatching
+ // If the dispatch fails, well, we're hosed anyway.
+ WrapRunnableNM(PipelineReleaseRef_m, pipeline.forget()),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+SourceStreamInfo::EndTrack(MediaStream* stream, dom::MediaStreamTrack* track)
+{
+ if (!stream || !stream->AsSourceStream()) {
+ return;
+ }
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ class Message : public ControlMessage {
+ public:
+ Message(MediaStream* stream, TrackID track)
+ : ControlMessage(stream),
+ track_id_(track) {}
+
+ virtual void Run() override {
+ mStream->AsSourceStream()->EndTrack(track_id_);
+ }
+ private:
+ TrackID track_id_;
+ };
+
+ stream->GraphImpl()->AppendMessage(
+ MakeUnique<Message>(stream, track->mTrackID));
+#endif
+
+}
+
+void
+SourceStreamInfo::RemoveTrack(const std::string& trackId)
+{
+ mTracks.erase(trackId);
+
+ RefPtr<MediaPipeline> pipeline = GetPipelineByTrackId_m(trackId);
+ if (pipeline) {
+ mPipelines.erase(trackId);
+ pipeline->ShutdownMedia_m();
+ mParent->GetSTSThread()->Dispatch(
+ WrapRunnableNM(PipelineDetachTransport_s,
+ pipeline.forget(),
+ mParent->GetMainThread()),
+ NS_DISPATCH_NORMAL);
+ }
+}
+
+void SourceStreamInfo::DetachTransport_s()
+{
+ ASSERT_ON_THREAD(mParent->GetSTSThread());
+ // walk through all the MediaPipelines and call the shutdown
+ // transport functions. Must be on the STS thread.
+ for (auto it = mPipelines.begin(); it != mPipelines.end(); ++it) {
+ it->second->DetachTransport_s();
+ }
+}
+
+void SourceStreamInfo::DetachMedia_m()
+{
+ ASSERT_ON_THREAD(mParent->GetMainThread());
+
+ // walk through all the MediaPipelines and call the shutdown
+ // media functions. Must be on the main thread.
+ for (auto it = mPipelines.begin(); it != mPipelines.end(); ++it) {
+ it->second->ShutdownMedia_m();
+ }
+ mMediaStream = nullptr;
+}
+
+already_AddRefed<PeerConnectionImpl>
+PeerConnectionImpl::Constructor(const dom::GlobalObject& aGlobal, ErrorResult& rv)
+{
+ RefPtr<PeerConnectionImpl> pc = new PeerConnectionImpl(&aGlobal);
+
+ CSFLogDebug(logTag, "Created PeerConnection: %p", pc.get());
+
+ return pc.forget();
+}
+
+PeerConnectionImpl* PeerConnectionImpl::CreatePeerConnection()
+{
+ PeerConnectionImpl *pc = new PeerConnectionImpl();
+
+ CSFLogDebug(logTag, "Created PeerConnection: %p", pc);
+
+ return pc;
+}
+
+NS_IMETHODIMP PeerConnectionMedia::ProtocolProxyQueryHandler::
+OnProxyAvailable(nsICancelable *request,
+ nsIChannel *aChannel,
+ nsIProxyInfo *proxyinfo,
+ nsresult result) {
+
+ if (!pcm_->mProxyRequest) {
+ // PeerConnectionMedia is no longer waiting
+ return NS_OK;
+ }
+
+ CSFLogInfo(logTag, "%s: Proxy Available: %d", __FUNCTION__, (int)result);
+
+ if (NS_SUCCEEDED(result) && proxyinfo) {
+ SetProxyOnPcm(*proxyinfo);
+ }
+
+ pcm_->mProxyResolveCompleted = true;
+ pcm_->mProxyRequest = nullptr;
+ pcm_->FlushIceCtxOperationQueueIfReady();
+
+ return NS_OK;
+}
+
+void
+PeerConnectionMedia::ProtocolProxyQueryHandler::SetProxyOnPcm(
+ nsIProxyInfo& proxyinfo)
+{
+ CSFLogInfo(logTag, "%s: Had proxyinfo", __FUNCTION__);
+ nsresult rv;
+ nsCString httpsProxyHost;
+ int32_t httpsProxyPort;
+
+ rv = proxyinfo.GetHost(httpsProxyHost);
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "%s: Failed to get proxy server host", __FUNCTION__);
+ return;
+ }
+
+ rv = proxyinfo.GetPort(&httpsProxyPort);
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "%s: Failed to get proxy server port", __FUNCTION__);
+ return;
+ }
+
+ if (pcm_->mIceCtxHdlr.get()) {
+ assert(httpsProxyPort >= 0 && httpsProxyPort < (1 << 16));
+ // Note that this could check if PrivacyRequested() is set on the PC and
+ // remove "webrtc" from the ALPN list. But that would only work if the PC
+ // was constructed with a peerIdentity constraint, not when isolated
+ // streams are added. If we ever need to signal to the proxy that the
+ // media is isolated, then we would need to restructure this code.
+ pcm_->mProxyServer.reset(
+ new NrIceProxyServer(httpsProxyHost.get(),
+ static_cast<uint16_t>(httpsProxyPort),
+ "webrtc,c-webrtc"));
+ } else {
+ CSFLogError(logTag, "%s: Failed to set proxy server (ICE ctx unavailable)",
+ __FUNCTION__);
+ }
+}
+
+NS_IMPL_ISUPPORTS(PeerConnectionMedia::ProtocolProxyQueryHandler, nsIProtocolProxyCallback)
+
+PeerConnectionMedia::PeerConnectionMedia(PeerConnectionImpl *parent)
+ : mParent(parent),
+ mParentHandle(parent->GetHandle()),
+ mParentName(parent->GetName()),
+ mIceCtxHdlr(nullptr),
+ mDNSResolver(new NrIceResolver()),
+ mUuidGen(MakeUnique<PCUuidGenerator>()),
+ mMainThread(mParent->GetMainThread()),
+ mSTSThread(mParent->GetSTSThread()),
+ mProxyResolveCompleted(false),
+ mIceRestartState(ICE_RESTART_NONE) {
+}
+
+nsresult
+PeerConnectionMedia::InitProxy()
+{
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // Allow mochitests to disable this, since mochitest configures a fake proxy
+ // that serves up content.
+ bool disable = Preferences::GetBool("media.peerconnection.disable_http_proxy",
+ false);
+ if (disable) {
+ mProxyResolveCompleted = true;
+ return NS_OK;
+ }
+#endif
+
+ nsresult rv;
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "%s: Failed to get proxy service: %d", __FUNCTION__, (int)rv);
+ return NS_ERROR_FAILURE;
+ }
+
+ // We use the following URL to find the "default" proxy address for all HTTPS
+ // connections. We will only attempt one HTTP(S) CONNECT per peer connection.
+ // "example.com" is guaranteed to be unallocated and should return the best default.
+ nsCOMPtr<nsIURI> fakeHttpsLocation;
+ rv = NS_NewURI(getter_AddRefs(fakeHttpsLocation), "https://example.com");
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "%s: Failed to set URI: %d", __FUNCTION__, (int)rv);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIScriptSecurityManager> secMan(
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "%s: Failed to get IOService: %d",
+ __FUNCTION__, (int)rv);
+ CSFLogError(logTag, "%s: Failed to get securityManager: %d", __FUNCTION__, (int)rv);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secMan->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "%s: Failed to get systemPrincipal: %d", __FUNCTION__, (int)rv);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ fakeHttpsLocation,
+ systemPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "%s: Failed to get channel from URI: %d",
+ __FUNCTION__, (int)rv);
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<ProtocolProxyQueryHandler> handler = new ProtocolProxyQueryHandler(this);
+ rv = pps->AsyncResolve(channel,
+ nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
+ nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
+ handler, getter_AddRefs(mProxyRequest));
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "%s: Failed to resolve protocol proxy: %d", __FUNCTION__, (int)rv);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult PeerConnectionMedia::Init(const std::vector<NrIceStunServer>& stun_servers,
+ const std::vector<NrIceTurnServer>& turn_servers,
+ NrIceCtx::Policy policy)
+{
+ nsresult rv = InitProxy();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ bool ice_tcp = Preferences::GetBool("media.peerconnection.ice.tcp", false);
+#else
+ bool ice_tcp = false;
+#endif
+
+ // TODO(ekr@rtfm.com): need some way to set not offerer later
+ // Looks like a bug in the NrIceCtx API.
+ mIceCtxHdlr = NrIceCtxHandler::Create("PC:" + mParentName,
+ true, // Offerer
+ mParent->GetAllowIceLoopback(),
+ ice_tcp,
+ mParent->GetAllowIceLinkLocal(),
+ policy);
+ if(!mIceCtxHdlr) {
+ CSFLogError(logTag, "%s: Failed to create Ice Context", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(rv = mIceCtxHdlr->ctx()->SetStunServers(stun_servers))) {
+ CSFLogError(logTag, "%s: Failed to set stun servers", __FUNCTION__);
+ return rv;
+ }
+ // Give us a way to globally turn off TURN support
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ bool disabled = Preferences::GetBool("media.peerconnection.turn.disable", false);
+#else
+ bool disabled = false;
+#endif
+ if (!disabled) {
+ if (NS_FAILED(rv = mIceCtxHdlr->ctx()->SetTurnServers(turn_servers))) {
+ CSFLogError(logTag, "%s: Failed to set turn servers", __FUNCTION__);
+ return rv;
+ }
+ } else if (turn_servers.size() != 0) {
+ CSFLogError(logTag, "%s: Setting turn servers disabled", __FUNCTION__);
+ }
+ if (NS_FAILED(rv = mDNSResolver->Init())) {
+ CSFLogError(logTag, "%s: Failed to initialize dns resolver", __FUNCTION__);
+ return rv;
+ }
+ if (NS_FAILED(rv =
+ mIceCtxHdlr->ctx()->SetResolver(mDNSResolver->AllocateResolver()))) {
+ CSFLogError(logTag, "%s: Failed to get dns resolver", __FUNCTION__);
+ return rv;
+ }
+ ConnectSignals(mIceCtxHdlr->ctx().get());
+
+ return NS_OK;
+}
+
+void
+PeerConnectionMedia::EnsureTransports(const JsepSession& aSession)
+{
+ auto transports = aSession.GetTransports();
+ for (size_t i = 0; i < transports.size(); ++i) {
+ RefPtr<JsepTransport> transport = transports[i];
+ RUN_ON_THREAD(
+ GetSTSThread(),
+ WrapRunnable(RefPtr<PeerConnectionMedia>(this),
+ &PeerConnectionMedia::EnsureTransport_s,
+ i,
+ transport->mComponents),
+ NS_DISPATCH_NORMAL);
+ }
+
+ GatherIfReady();
+}
+
+void
+PeerConnectionMedia::EnsureTransport_s(size_t aLevel, size_t aComponentCount)
+{
+ RefPtr<NrIceMediaStream> stream(mIceCtxHdlr->ctx()->GetStream(aLevel));
+ if (!stream) {
+ CSFLogDebug(logTag, "%s: Creating ICE media stream=%u components=%u",
+ mParentHandle.c_str(),
+ static_cast<unsigned>(aLevel),
+ static_cast<unsigned>(aComponentCount));
+
+ std::ostringstream os;
+ os << mParentName << " aLevel=" << aLevel;
+ RefPtr<NrIceMediaStream> stream =
+ mIceCtxHdlr->CreateStream(os.str().c_str(),
+ aComponentCount);
+
+ if (!stream) {
+ CSFLogError(logTag, "Failed to create ICE stream.");
+ return;
+ }
+
+ stream->SetLevel(aLevel);
+ stream->SignalReady.connect(this, &PeerConnectionMedia::IceStreamReady_s);
+ stream->SignalCandidate.connect(this,
+ &PeerConnectionMedia::OnCandidateFound_s);
+ mIceCtxHdlr->ctx()->SetStream(aLevel, stream);
+ }
+}
+
+void
+PeerConnectionMedia::ActivateOrRemoveTransports(const JsepSession& aSession)
+{
+ auto transports = aSession.GetTransports();
+ for (size_t i = 0; i < transports.size(); ++i) {
+ RefPtr<JsepTransport> transport = transports[i];
+
+ std::string ufrag;
+ std::string pwd;
+ std::vector<std::string> candidates;
+
+ if (transport->mComponents) {
+ MOZ_ASSERT(transport->mIce);
+ CSFLogDebug(logTag, "Transport %u is active", static_cast<unsigned>(i));
+ ufrag = transport->mIce->GetUfrag();
+ pwd = transport->mIce->GetPassword();
+ candidates = transport->mIce->GetCandidates();
+ } else {
+ CSFLogDebug(logTag, "Transport %u is disabled", static_cast<unsigned>(i));
+ // Make sure the MediaPipelineFactory doesn't try to use these.
+ RemoveTransportFlow(i, false);
+ RemoveTransportFlow(i, true);
+ }
+
+ RUN_ON_THREAD(
+ GetSTSThread(),
+ WrapRunnable(RefPtr<PeerConnectionMedia>(this),
+ &PeerConnectionMedia::ActivateOrRemoveTransport_s,
+ i,
+ transport->mComponents,
+ ufrag,
+ pwd,
+ candidates),
+ NS_DISPATCH_NORMAL);
+ }
+
+ // We can have more streams than m-lines due to rollback.
+ RUN_ON_THREAD(
+ GetSTSThread(),
+ WrapRunnable(RefPtr<PeerConnectionMedia>(this),
+ &PeerConnectionMedia::RemoveTransportsAtOrAfter_s,
+ transports.size()),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionMedia::ActivateOrRemoveTransport_s(
+ size_t aMLine,
+ size_t aComponentCount,
+ const std::string& aUfrag,
+ const std::string& aPassword,
+ const std::vector<std::string>& aCandidateList) {
+
+ if (!aComponentCount) {
+ CSFLogDebug(logTag, "%s: Removing ICE media stream=%u",
+ mParentHandle.c_str(),
+ static_cast<unsigned>(aMLine));
+ mIceCtxHdlr->ctx()->SetStream(aMLine, nullptr);
+ return;
+ }
+
+ RefPtr<NrIceMediaStream> stream(mIceCtxHdlr->ctx()->GetStream(aMLine));
+ if (!stream) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ if (!stream->HasParsedAttributes()) {
+ CSFLogDebug(logTag, "%s: Activating ICE media stream=%u components=%u",
+ mParentHandle.c_str(),
+ static_cast<unsigned>(aMLine),
+ static_cast<unsigned>(aComponentCount));
+
+ std::vector<std::string> attrs;
+ for (auto i = aCandidateList.begin(); i != aCandidateList.end(); ++i) {
+ attrs.push_back("candidate:" + *i);
+ }
+ attrs.push_back("ice-ufrag:" + aUfrag);
+ attrs.push_back("ice-pwd:" + aPassword);
+
+ nsresult rv = stream->ParseAttributes(attrs);
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "Couldn't parse ICE attributes, rv=%u",
+ static_cast<unsigned>(rv));
+ }
+
+ for (size_t c = aComponentCount; c < stream->components(); ++c) {
+ // components are 1-indexed
+ stream->DisableComponent(c + 1);
+ }
+ }
+}
+
+void
+PeerConnectionMedia::RemoveTransportsAtOrAfter_s(size_t aMLine)
+{
+ for (size_t i = aMLine; i < mIceCtxHdlr->ctx()->GetStreamCount(); ++i) {
+ mIceCtxHdlr->ctx()->SetStream(i, nullptr);
+ }
+}
+
+nsresult PeerConnectionMedia::UpdateMediaPipelines(
+ const JsepSession& session) {
+ auto trackPairs = session.GetNegotiatedTrackPairs();
+ MediaPipelineFactory factory(this);
+ nsresult rv;
+
+ for (auto i = trackPairs.begin(); i != trackPairs.end(); ++i) {
+ JsepTrackPair pair = *i;
+
+ if (pair.mReceiving) {
+ rv = factory.CreateOrUpdateMediaPipeline(pair, *pair.mReceiving);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (pair.mSending) {
+ rv = factory.CreateOrUpdateMediaPipeline(pair, *pair.mSending);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ for (auto& stream : mRemoteSourceStreams) {
+ stream->StartReceiving();
+ }
+
+ return NS_OK;
+}
+
+void
+PeerConnectionMedia::StartIceChecks(const JsepSession& aSession)
+{
+ nsCOMPtr<nsIRunnable> runnable(
+ WrapRunnable(
+ RefPtr<PeerConnectionMedia>(this),
+ &PeerConnectionMedia::StartIceChecks_s,
+ aSession.IsIceControlling(),
+ aSession.RemoteIsIceLite(),
+ // Copy, just in case API changes to return a ref
+ std::vector<std::string>(aSession.GetIceOptions())));
+
+ PerformOrEnqueueIceCtxOperation(runnable);
+}
+
+void
+PeerConnectionMedia::StartIceChecks_s(
+ bool aIsControlling,
+ bool aIsIceLite,
+ const std::vector<std::string>& aIceOptionsList) {
+
+ CSFLogDebug(logTag, "Starting ICE Checking");
+
+ std::vector<std::string> attributes;
+ if (aIsIceLite) {
+ attributes.push_back("ice-lite");
+ }
+
+ if (!aIceOptionsList.empty()) {
+ attributes.push_back("ice-options:");
+ for (auto i = aIceOptionsList.begin(); i != aIceOptionsList.end(); ++i) {
+ attributes.back() += *i + ' ';
+ }
+ }
+
+ nsresult rv = mIceCtxHdlr->ctx()->ParseGlobalAttributes(attributes);
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "%s: couldn't parse global parameters", __FUNCTION__ );
+ }
+
+ mIceCtxHdlr->ctx()->SetControlling(aIsControlling ?
+ NrIceCtx::ICE_CONTROLLING :
+ NrIceCtx::ICE_CONTROLLED);
+
+ mIceCtxHdlr->ctx()->StartChecks();
+}
+
+bool
+PeerConnectionMedia::IsIceRestarting() const
+{
+ ASSERT_ON_THREAD(mMainThread);
+
+ return (mIceRestartState != ICE_RESTART_NONE);
+}
+
+PeerConnectionMedia::IceRestartState
+PeerConnectionMedia::GetIceRestartState() const
+{
+ ASSERT_ON_THREAD(mMainThread);
+
+ return mIceRestartState;
+}
+
+void
+PeerConnectionMedia::BeginIceRestart(const std::string& ufrag,
+ const std::string& pwd)
+{
+ ASSERT_ON_THREAD(mMainThread);
+ if (IsIceRestarting()) {
+ return;
+ }
+
+ RefPtr<NrIceCtx> new_ctx = mIceCtxHdlr->CreateCtx(ufrag, pwd);
+
+ RUN_ON_THREAD(GetSTSThread(),
+ WrapRunnable(
+ RefPtr<PeerConnectionMedia>(this),
+ &PeerConnectionMedia::BeginIceRestart_s,
+ new_ctx),
+ NS_DISPATCH_NORMAL);
+
+ mIceRestartState = ICE_RESTART_PROVISIONAL;
+}
+
+void
+PeerConnectionMedia::BeginIceRestart_s(RefPtr<NrIceCtx> new_ctx)
+{
+ ASSERT_ON_THREAD(mSTSThread);
+
+ // hold the original context so we can disconnect signals if needed
+ RefPtr<NrIceCtx> originalCtx = mIceCtxHdlr->ctx();
+
+ if (mIceCtxHdlr->BeginIceRestart(new_ctx)) {
+ ConnectSignals(mIceCtxHdlr->ctx().get(), originalCtx.get());
+ }
+}
+
+void
+PeerConnectionMedia::CommitIceRestart()
+{
+ ASSERT_ON_THREAD(mMainThread);
+ if (mIceRestartState != ICE_RESTART_PROVISIONAL) {
+ return;
+ }
+
+ mIceRestartState = ICE_RESTART_COMMITTED;
+}
+
+void
+PeerConnectionMedia::FinalizeIceRestart()
+{
+ ASSERT_ON_THREAD(mMainThread);
+ if (!IsIceRestarting()) {
+ return;
+ }
+
+ RUN_ON_THREAD(GetSTSThread(),
+ WrapRunnable(
+ RefPtr<PeerConnectionMedia>(this),
+ &PeerConnectionMedia::FinalizeIceRestart_s),
+ NS_DISPATCH_NORMAL);
+
+ mIceRestartState = ICE_RESTART_NONE;
+}
+
+void
+PeerConnectionMedia::FinalizeIceRestart_s()
+{
+ ASSERT_ON_THREAD(mSTSThread);
+
+ // reset old streams since we don't need them anymore
+ for (auto i = mTransportFlows.begin();
+ i != mTransportFlows.end();
+ ++i) {
+ RefPtr<TransportFlow> aFlow = i->second;
+ if (!aFlow) continue;
+ TransportLayerIce* ice =
+ static_cast<TransportLayerIce*>(aFlow->GetLayer(TransportLayerIce::ID()));
+ ice->ResetOldStream();
+ }
+
+ mIceCtxHdlr->FinalizeIceRestart();
+}
+
+void
+PeerConnectionMedia::RollbackIceRestart()
+{
+ ASSERT_ON_THREAD(mMainThread);
+ if (mIceRestartState != ICE_RESTART_PROVISIONAL) {
+ return;
+ }
+
+ RUN_ON_THREAD(GetSTSThread(),
+ WrapRunnable(
+ RefPtr<PeerConnectionMedia>(this),
+ &PeerConnectionMedia::RollbackIceRestart_s),
+ NS_DISPATCH_NORMAL);
+
+ mIceRestartState = ICE_RESTART_NONE;
+}
+
+void
+PeerConnectionMedia::RollbackIceRestart_s()
+{
+ ASSERT_ON_THREAD(mSTSThread);
+
+ // hold the restart context so we can disconnect signals
+ RefPtr<NrIceCtx> restartCtx = mIceCtxHdlr->ctx();
+
+ // restore old streams since we're rolling back
+ for (auto i = mTransportFlows.begin();
+ i != mTransportFlows.end();
+ ++i) {
+ RefPtr<TransportFlow> aFlow = i->second;
+ if (!aFlow) continue;
+ TransportLayerIce* ice =
+ static_cast<TransportLayerIce*>(aFlow->GetLayer(TransportLayerIce::ID()));
+ ice->RestoreOldStream();
+ }
+
+ mIceCtxHdlr->RollbackIceRestart();
+ ConnectSignals(mIceCtxHdlr->ctx().get(), restartCtx.get());
+}
+
+bool
+PeerConnectionMedia::GetPrefDefaultAddressOnly() const
+{
+ ASSERT_ON_THREAD(mMainThread); // will crash on STS thread
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ uint64_t winId = mParent->GetWindow()->WindowID();
+
+ bool default_address_only = Preferences::GetBool(
+ "media.peerconnection.ice.default_address_only", false);
+ default_address_only |=
+ !MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId);
+#else
+ bool default_address_only = true;
+#endif
+ return default_address_only;
+}
+
+bool
+PeerConnectionMedia::GetPrefProxyOnly() const
+{
+ ASSERT_ON_THREAD(mMainThread); // will crash on STS thread
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ return Preferences::GetBool("media.peerconnection.ice.proxy_only", false);
+#else
+ return false;
+#endif
+}
+
+void
+PeerConnectionMedia::ConnectSignals(NrIceCtx *aCtx, NrIceCtx *aOldCtx)
+{
+ aCtx->SignalGatheringStateChange.connect(
+ this,
+ &PeerConnectionMedia::IceGatheringStateChange_s);
+ aCtx->SignalConnectionStateChange.connect(
+ this,
+ &PeerConnectionMedia::IceConnectionStateChange_s);
+
+ if (aOldCtx) {
+ MOZ_ASSERT(aCtx != aOldCtx);
+ aOldCtx->SignalGatheringStateChange.disconnect(this);
+ aOldCtx->SignalConnectionStateChange.disconnect(this);
+
+ // if the old and new connection state and/or gathering state is
+ // different fire the state update. Note: we don't fire the update
+ // if the state is *INIT since updates for the INIT state aren't
+ // sent during the normal flow. (mjf)
+ if (aOldCtx->connection_state() != aCtx->connection_state() &&
+ aCtx->connection_state() != NrIceCtx::ICE_CTX_INIT) {
+ aCtx->SignalConnectionStateChange(aCtx, aCtx->connection_state());
+ }
+
+ if (aOldCtx->gathering_state() != aCtx->gathering_state() &&
+ aCtx->gathering_state() != NrIceCtx::ICE_CTX_GATHER_INIT) {
+ aCtx->SignalGatheringStateChange(aCtx, aCtx->gathering_state());
+ }
+ }
+}
+
+void
+PeerConnectionMedia::AddIceCandidate(const std::string& candidate,
+ const std::string& mid,
+ uint32_t aMLine) {
+ RUN_ON_THREAD(GetSTSThread(),
+ WrapRunnable(
+ RefPtr<PeerConnectionMedia>(this),
+ &PeerConnectionMedia::AddIceCandidate_s,
+ std::string(candidate), // Make copies.
+ std::string(mid),
+ aMLine),
+ NS_DISPATCH_NORMAL);
+}
+void
+PeerConnectionMedia::AddIceCandidate_s(const std::string& aCandidate,
+ const std::string& aMid,
+ uint32_t aMLine) {
+ RefPtr<NrIceMediaStream> stream(mIceCtxHdlr->ctx()->GetStream(aMLine));
+ if (!stream) {
+ CSFLogError(logTag, "No ICE stream for candidate at level %u: %s",
+ static_cast<unsigned>(aMLine), aCandidate.c_str());
+ return;
+ }
+
+ nsresult rv = stream->ParseTrickleCandidate(aCandidate);
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "Couldn't process ICE candidate at level %u",
+ static_cast<unsigned>(aMLine));
+ return;
+ }
+}
+
+void
+PeerConnectionMedia::FlushIceCtxOperationQueueIfReady()
+{
+ ASSERT_ON_THREAD(mMainThread);
+
+ if (IsIceCtxReady()) {
+ for (auto i = mQueuedIceCtxOperations.begin();
+ i != mQueuedIceCtxOperations.end();
+ ++i) {
+ GetSTSThread()->Dispatch(*i, NS_DISPATCH_NORMAL);
+ }
+ mQueuedIceCtxOperations.clear();
+ }
+}
+
+void
+PeerConnectionMedia::PerformOrEnqueueIceCtxOperation(nsIRunnable* runnable)
+{
+ ASSERT_ON_THREAD(mMainThread);
+
+ if (IsIceCtxReady()) {
+ GetSTSThread()->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ } else {
+ mQueuedIceCtxOperations.push_back(runnable);
+ }
+}
+
+void
+PeerConnectionMedia::GatherIfReady() {
+ ASSERT_ON_THREAD(mMainThread);
+
+ nsCOMPtr<nsIRunnable> runnable(WrapRunnable(
+ RefPtr<PeerConnectionMedia>(this),
+ &PeerConnectionMedia::EnsureIceGathering_s,
+ GetPrefDefaultAddressOnly(),
+ GetPrefProxyOnly()));
+
+ PerformOrEnqueueIceCtxOperation(runnable);
+}
+
+void
+PeerConnectionMedia::EnsureIceGathering_s(bool aDefaultRouteOnly,
+ bool aProxyOnly) {
+ if (mProxyServer) {
+ mIceCtxHdlr->ctx()->SetProxyServer(*mProxyServer);
+ } else if (aProxyOnly) {
+ IceGatheringStateChange_s(mIceCtxHdlr->ctx().get(),
+ NrIceCtx::ICE_CTX_GATHER_COMPLETE);
+ return;
+ }
+
+ // Start gathering, but only if there are streams
+ for (size_t i = 0; i < mIceCtxHdlr->ctx()->GetStreamCount(); ++i) {
+ if (mIceCtxHdlr->ctx()->GetStream(i)) {
+ mIceCtxHdlr->ctx()->StartGathering(aDefaultRouteOnly, aProxyOnly);
+ return;
+ }
+ }
+
+ // If there are no streams, we're probably in a situation where we've rolled
+ // back while still waiting for our proxy configuration to come back. Make
+ // sure content knows that the rollback has stuck wrt gathering.
+ IceGatheringStateChange_s(mIceCtxHdlr->ctx().get(),
+ NrIceCtx::ICE_CTX_GATHER_COMPLETE);
+}
+
+nsresult
+PeerConnectionMedia::AddTrack(DOMMediaStream& aMediaStream,
+ const std::string& streamId,
+ MediaStreamTrack& aTrack,
+ const std::string& trackId)
+{
+ ASSERT_ON_THREAD(mMainThread);
+
+ CSFLogDebug(logTag, "%s: MediaStream: %p", __FUNCTION__, &aMediaStream);
+
+ RefPtr<LocalSourceStreamInfo> localSourceStream =
+ GetLocalStreamById(streamId);
+
+ if (!localSourceStream) {
+ localSourceStream = new LocalSourceStreamInfo(&aMediaStream, this, streamId);
+ mLocalSourceStreams.AppendElement(localSourceStream);
+ }
+
+ localSourceStream->AddTrack(trackId, &aTrack);
+ return NS_OK;
+}
+
+nsresult
+PeerConnectionMedia::RemoveLocalTrack(const std::string& streamId,
+ const std::string& trackId)
+{
+ ASSERT_ON_THREAD(mMainThread);
+
+ CSFLogDebug(logTag, "%s: stream: %s track: %s", __FUNCTION__,
+ streamId.c_str(), trackId.c_str());
+
+ RefPtr<LocalSourceStreamInfo> localSourceStream =
+ GetLocalStreamById(streamId);
+ if (!localSourceStream) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ localSourceStream->RemoveTrack(trackId);
+ if (!localSourceStream->GetTrackCount()) {
+ mLocalSourceStreams.RemoveElement(localSourceStream);
+ }
+ return NS_OK;
+}
+
+nsresult
+PeerConnectionMedia::RemoveRemoteTrack(const std::string& streamId,
+ const std::string& trackId)
+{
+ ASSERT_ON_THREAD(mMainThread);
+
+ CSFLogDebug(logTag, "%s: stream: %s track: %s", __FUNCTION__,
+ streamId.c_str(), trackId.c_str());
+
+ RefPtr<RemoteSourceStreamInfo> remoteSourceStream =
+ GetRemoteStreamById(streamId);
+ if (!remoteSourceStream) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ remoteSourceStream->RemoveTrack(trackId);
+ if (!remoteSourceStream->GetTrackCount()) {
+ mRemoteSourceStreams.RemoveElement(remoteSourceStream);
+ }
+ return NS_OK;
+}
+
+void
+PeerConnectionMedia::SelfDestruct()
+{
+ ASSERT_ON_THREAD(mMainThread);
+
+ CSFLogDebug(logTag, "%s: ", __FUNCTION__);
+
+ // Shut down the media
+ for (uint32_t i=0; i < mLocalSourceStreams.Length(); ++i) {
+ mLocalSourceStreams[i]->DetachMedia_m();
+ }
+
+ for (uint32_t i=0; i < mRemoteSourceStreams.Length(); ++i) {
+ mRemoteSourceStreams[i]->DetachMedia_m();
+ }
+
+ if (mProxyRequest) {
+ mProxyRequest->Cancel(NS_ERROR_ABORT);
+ mProxyRequest = nullptr;
+ }
+
+ // Shutdown the transport (async)
+ RUN_ON_THREAD(mSTSThread, WrapRunnable(
+ this, &PeerConnectionMedia::ShutdownMediaTransport_s),
+ NS_DISPATCH_NORMAL);
+
+ CSFLogDebug(logTag, "%s: Media shut down", __FUNCTION__);
+}
+
+void
+PeerConnectionMedia::SelfDestruct_m()
+{
+ CSFLogDebug(logTag, "%s: ", __FUNCTION__);
+
+ ASSERT_ON_THREAD(mMainThread);
+
+ mLocalSourceStreams.Clear();
+ mRemoteSourceStreams.Clear();
+
+ mMainThread = nullptr;
+
+ // Final self-destruct.
+ this->Release();
+}
+
+void
+PeerConnectionMedia::ShutdownMediaTransport_s()
+{
+ ASSERT_ON_THREAD(mSTSThread);
+
+ CSFLogDebug(logTag, "%s: ", __FUNCTION__);
+
+ // Here we access m{Local|Remote}SourceStreams off the main thread.
+ // That's OK because by here PeerConnectionImpl has forgotten about us,
+ // so there is no chance of getting a call in here from outside.
+ // The dispatches from SelfDestruct() and to SelfDestruct_m() provide
+ // memory barriers that protect us from badness.
+ for (uint32_t i=0; i < mLocalSourceStreams.Length(); ++i) {
+ mLocalSourceStreams[i]->DetachTransport_s();
+ }
+
+ for (uint32_t i=0; i < mRemoteSourceStreams.Length(); ++i) {
+ mRemoteSourceStreams[i]->DetachTransport_s();
+ }
+
+ disconnect_all();
+ mTransportFlows.clear();
+ mIceCtxHdlr = nullptr;
+
+ mMainThread->Dispatch(WrapRunnable(this, &PeerConnectionMedia::SelfDestruct_m),
+ NS_DISPATCH_NORMAL);
+}
+
+LocalSourceStreamInfo*
+PeerConnectionMedia::GetLocalStreamByIndex(int aIndex)
+{
+ ASSERT_ON_THREAD(mMainThread);
+ if(aIndex < 0 || aIndex >= (int) mLocalSourceStreams.Length()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(mLocalSourceStreams[aIndex]);
+ return mLocalSourceStreams[aIndex];
+}
+
+LocalSourceStreamInfo*
+PeerConnectionMedia::GetLocalStreamById(const std::string& id)
+{
+ ASSERT_ON_THREAD(mMainThread);
+ for (size_t i = 0; i < mLocalSourceStreams.Length(); ++i) {
+ if (id == mLocalSourceStreams[i]->GetId()) {
+ return mLocalSourceStreams[i];
+ }
+ }
+
+ return nullptr;
+}
+
+LocalSourceStreamInfo*
+PeerConnectionMedia::GetLocalStreamByTrackId(const std::string& id)
+{
+ ASSERT_ON_THREAD(mMainThread);
+ for (RefPtr<LocalSourceStreamInfo>& info : mLocalSourceStreams) {
+ if (info->HasTrack(id)) {
+ return info;
+ }
+ }
+
+ return nullptr;
+}
+
+RemoteSourceStreamInfo*
+PeerConnectionMedia::GetRemoteStreamByIndex(size_t aIndex)
+{
+ ASSERT_ON_THREAD(mMainThread);
+ MOZ_ASSERT(mRemoteSourceStreams.SafeElementAt(aIndex));
+ return mRemoteSourceStreams.SafeElementAt(aIndex);
+}
+
+RemoteSourceStreamInfo*
+PeerConnectionMedia::GetRemoteStreamById(const std::string& id)
+{
+ ASSERT_ON_THREAD(mMainThread);
+ for (size_t i = 0; i < mRemoteSourceStreams.Length(); ++i) {
+ if (id == mRemoteSourceStreams[i]->GetId()) {
+ return mRemoteSourceStreams[i];
+ }
+ }
+
+ return nullptr;
+}
+
+RemoteSourceStreamInfo*
+PeerConnectionMedia::GetRemoteStreamByTrackId(const std::string& id)
+{
+ ASSERT_ON_THREAD(mMainThread);
+ for (RefPtr<RemoteSourceStreamInfo>& info : mRemoteSourceStreams) {
+ if (info->HasTrack(id)) {
+ return info;
+ }
+ }
+
+ return nullptr;
+}
+
+
+nsresult
+PeerConnectionMedia::AddRemoteStream(RefPtr<RemoteSourceStreamInfo> aInfo)
+{
+ ASSERT_ON_THREAD(mMainThread);
+
+ mRemoteSourceStreams.AppendElement(aInfo);
+
+ return NS_OK;
+}
+
+void
+PeerConnectionMedia::IceGatheringStateChange_s(NrIceCtx* ctx,
+ NrIceCtx::GatheringState state)
+{
+ ASSERT_ON_THREAD(mSTSThread);
+
+ if (state == NrIceCtx::ICE_CTX_GATHER_COMPLETE) {
+ // Fire off EndOfLocalCandidates for each stream
+ for (size_t i = 0; ; ++i) {
+ RefPtr<NrIceMediaStream> stream(ctx->GetStream(i));
+ if (!stream) {
+ break;
+ }
+
+ NrIceCandidate candidate;
+ NrIceCandidate rtcpCandidate;
+ GetDefaultCandidates(*stream, &candidate, &rtcpCandidate);
+ EndOfLocalCandidates(candidate.cand_addr.host,
+ candidate.cand_addr.port,
+ rtcpCandidate.cand_addr.host,
+ rtcpCandidate.cand_addr.port,
+ i);
+ }
+ }
+
+ // ShutdownMediaTransport_s has not run yet because it unhooks this function
+ // from its signal, which means that SelfDestruct_m has not been dispatched
+ // yet either, so this PCMedia will still be around when this dispatch reaches
+ // main.
+ GetMainThread()->Dispatch(
+ WrapRunnable(this,
+ &PeerConnectionMedia::IceGatheringStateChange_m,
+ ctx,
+ state),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionMedia::IceConnectionStateChange_s(NrIceCtx* ctx,
+ NrIceCtx::ConnectionState state)
+{
+ ASSERT_ON_THREAD(mSTSThread);
+ // ShutdownMediaTransport_s has not run yet because it unhooks this function
+ // from its signal, which means that SelfDestruct_m has not been dispatched
+ // yet either, so this PCMedia will still be around when this dispatch reaches
+ // main.
+ GetMainThread()->Dispatch(
+ WrapRunnable(this,
+ &PeerConnectionMedia::IceConnectionStateChange_m,
+ ctx,
+ state),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionMedia::OnCandidateFound_s(NrIceMediaStream *aStream,
+ const std::string &aCandidateLine)
+{
+ ASSERT_ON_THREAD(mSTSThread);
+ MOZ_ASSERT(aStream);
+ MOZ_RELEASE_ASSERT(mIceCtxHdlr);
+
+ CSFLogDebug(logTag, "%s: %s", __FUNCTION__, aStream->name().c_str());
+
+ NrIceCandidate candidate;
+ NrIceCandidate rtcpCandidate;
+ GetDefaultCandidates(*aStream, &candidate, &rtcpCandidate);
+
+ // ShutdownMediaTransport_s has not run yet because it unhooks this function
+ // from its signal, which means that SelfDestruct_m has not been dispatched
+ // yet either, so this PCMedia will still be around when this dispatch reaches
+ // main.
+ GetMainThread()->Dispatch(
+ WrapRunnable(this,
+ &PeerConnectionMedia::OnCandidateFound_m,
+ aCandidateLine,
+ candidate.cand_addr.host,
+ candidate.cand_addr.port,
+ rtcpCandidate.cand_addr.host,
+ rtcpCandidate.cand_addr.port,
+ aStream->GetLevel()),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionMedia::EndOfLocalCandidates(const std::string& aDefaultAddr,
+ uint16_t aDefaultPort,
+ const std::string& aDefaultRtcpAddr,
+ uint16_t aDefaultRtcpPort,
+ uint16_t aMLine)
+{
+ GetMainThread()->Dispatch(
+ WrapRunnable(this,
+ &PeerConnectionMedia::EndOfLocalCandidates_m,
+ aDefaultAddr,
+ aDefaultPort,
+ aDefaultRtcpAddr,
+ aDefaultRtcpPort,
+ aMLine),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionMedia::GetDefaultCandidates(const NrIceMediaStream& aStream,
+ NrIceCandidate* aCandidate,
+ NrIceCandidate* aRtcpCandidate)
+{
+ nsresult res = aStream.GetDefaultCandidate(1, aCandidate);
+ // Optional; component won't exist if doing rtcp-mux
+ if (NS_FAILED(aStream.GetDefaultCandidate(2, aRtcpCandidate))) {
+ aRtcpCandidate->cand_addr.host.clear();
+ aRtcpCandidate->cand_addr.port = 0;
+ }
+ if (NS_FAILED(res)) {
+ aCandidate->cand_addr.host.clear();
+ aCandidate->cand_addr.port = 0;
+ CSFLogError(logTag, "%s: GetDefaultCandidates failed for level %u, "
+ "res=%u",
+ __FUNCTION__,
+ static_cast<unsigned>(aStream.GetLevel()),
+ static_cast<unsigned>(res));
+ }
+}
+
+void
+PeerConnectionMedia::IceGatheringStateChange_m(NrIceCtx* ctx,
+ NrIceCtx::GatheringState state)
+{
+ ASSERT_ON_THREAD(mMainThread);
+ SignalIceGatheringStateChange(ctx, state);
+}
+
+void
+PeerConnectionMedia::IceConnectionStateChange_m(NrIceCtx* ctx,
+ NrIceCtx::ConnectionState state)
+{
+ ASSERT_ON_THREAD(mMainThread);
+ SignalIceConnectionStateChange(ctx, state);
+}
+
+void
+PeerConnectionMedia::IceStreamReady_s(NrIceMediaStream *aStream)
+{
+ MOZ_ASSERT(aStream);
+
+ CSFLogDebug(logTag, "%s: %s", __FUNCTION__, aStream->name().c_str());
+}
+
+void
+PeerConnectionMedia::OnCandidateFound_m(const std::string& aCandidateLine,
+ const std::string& aDefaultAddr,
+ uint16_t aDefaultPort,
+ const std::string& aDefaultRtcpAddr,
+ uint16_t aDefaultRtcpPort,
+ uint16_t aMLine)
+{
+ ASSERT_ON_THREAD(mMainThread);
+ if (!aDefaultAddr.empty()) {
+ SignalUpdateDefaultCandidate(aDefaultAddr,
+ aDefaultPort,
+ aDefaultRtcpAddr,
+ aDefaultRtcpPort,
+ aMLine);
+ }
+ SignalCandidate(aCandidateLine, aMLine);
+}
+
+void
+PeerConnectionMedia::EndOfLocalCandidates_m(const std::string& aDefaultAddr,
+ uint16_t aDefaultPort,
+ const std::string& aDefaultRtcpAddr,
+ uint16_t aDefaultRtcpPort,
+ uint16_t aMLine) {
+ ASSERT_ON_THREAD(mMainThread);
+ if (!aDefaultAddr.empty()) {
+ SignalUpdateDefaultCandidate(aDefaultAddr,
+ aDefaultPort,
+ aDefaultRtcpAddr,
+ aDefaultRtcpPort,
+ aMLine);
+ }
+ SignalEndOfLocalCandidates(aMLine);
+}
+
+void
+PeerConnectionMedia::DtlsConnected_s(TransportLayer *layer,
+ TransportLayer::State state)
+{
+ MOZ_ASSERT(layer->id() == "dtls");
+ TransportLayerDtls* dtlsLayer = static_cast<TransportLayerDtls*>(layer);
+ dtlsLayer->SignalStateChange.disconnect(this);
+
+ bool privacyRequested = (dtlsLayer->GetNegotiatedAlpn() == "c-webrtc");
+ GetMainThread()->Dispatch(
+ WrapRunnableNM(&PeerConnectionMedia::DtlsConnected_m,
+ mParentHandle, privacyRequested),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionMedia::DtlsConnected_m(const std::string& aParentHandle,
+ bool aPrivacyRequested)
+{
+ PeerConnectionWrapper pcWrapper(aParentHandle);
+ PeerConnectionImpl* pc = pcWrapper.impl();
+ if (pc) {
+ pc->SetDtlsConnected(aPrivacyRequested);
+ }
+}
+
+void
+PeerConnectionMedia::AddTransportFlow(int aIndex, bool aRtcp,
+ const RefPtr<TransportFlow> &aFlow)
+{
+ int index_inner = GetTransportFlowIndex(aIndex, aRtcp);
+
+ MOZ_ASSERT(!mTransportFlows[index_inner]);
+ mTransportFlows[index_inner] = aFlow;
+
+ GetSTSThread()->Dispatch(
+ WrapRunnable(this, &PeerConnectionMedia::ConnectDtlsListener_s, aFlow),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionMedia::RemoveTransportFlow(int aIndex, bool aRtcp)
+{
+ int index_inner = GetTransportFlowIndex(aIndex, aRtcp);
+ NS_ProxyRelease(GetSTSThread(), mTransportFlows[index_inner].forget());
+}
+
+void
+PeerConnectionMedia::ConnectDtlsListener_s(const RefPtr<TransportFlow>& aFlow)
+{
+ TransportLayer* dtls = aFlow->GetLayer(TransportLayerDtls::ID());
+ if (dtls) {
+ dtls->SignalStateChange.connect(this, &PeerConnectionMedia::DtlsConnected_s);
+ }
+}
+
+nsresult
+LocalSourceStreamInfo::TakePipelineFrom(RefPtr<LocalSourceStreamInfo>& info,
+ const std::string& oldTrackId,
+ MediaStreamTrack& aNewTrack,
+ const std::string& newTrackId)
+{
+ if (mPipelines.count(newTrackId)) {
+ CSFLogError(logTag, "%s: Pipeline already exists for %s/%s",
+ __FUNCTION__, mId.c_str(), newTrackId.c_str());
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<MediaPipeline> pipeline(info->ForgetPipelineByTrackId_m(oldTrackId));
+
+ if (!pipeline) {
+ // Replacetrack can potentially happen in the middle of offer/answer, before
+ // the pipeline has been created.
+ CSFLogInfo(logTag, "%s: Replacing track before the pipeline has been "
+ "created, nothing to do.", __FUNCTION__);
+ return NS_OK;
+ }
+
+ nsresult rv =
+ static_cast<MediaPipelineTransmit*>(pipeline.get())->ReplaceTrack(aNewTrack);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mPipelines[newTrackId] = pipeline;
+
+ return NS_OK;
+}
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+/**
+ * Tells you if any local track is isolated to a specific peer identity.
+ * Obviously, we want all the tracks to be isolated equally so that they can
+ * all be sent or not. We check once when we are setting a local description
+ * and that determines if we flip the "privacy requested" bit on. Once the bit
+ * is on, all media originating from this peer connection is isolated.
+ *
+ * @returns true if any track has a peerIdentity set on it
+ */
+bool
+PeerConnectionMedia::AnyLocalTrackHasPeerIdentity() const
+{
+ ASSERT_ON_THREAD(mMainThread);
+
+ for (uint32_t u = 0; u < mLocalSourceStreams.Length(); u++) {
+ for (auto pair : mLocalSourceStreams[u]->GetMediaStreamTracks()) {
+ if (pair.second->GetPeerIdentity() != nullptr) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void
+PeerConnectionMedia::UpdateRemoteStreamPrincipals_m(nsIPrincipal* aPrincipal)
+{
+ ASSERT_ON_THREAD(mMainThread);
+
+ for (uint32_t u = 0; u < mRemoteSourceStreams.Length(); u++) {
+ mRemoteSourceStreams[u]->UpdatePrincipal_m(aPrincipal);
+ }
+}
+
+void
+PeerConnectionMedia::UpdateSinkIdentity_m(MediaStreamTrack* aTrack,
+ nsIPrincipal* aPrincipal,
+ const PeerIdentity* aSinkIdentity)
+{
+ ASSERT_ON_THREAD(mMainThread);
+
+ for (uint32_t u = 0; u < mLocalSourceStreams.Length(); u++) {
+ mLocalSourceStreams[u]->UpdateSinkIdentity_m(aTrack, aPrincipal,
+ aSinkIdentity);
+ }
+}
+
+void
+LocalSourceStreamInfo::UpdateSinkIdentity_m(MediaStreamTrack* aTrack,
+ nsIPrincipal* aPrincipal,
+ const PeerIdentity* aSinkIdentity)
+{
+ for (auto it = mPipelines.begin(); it != mPipelines.end(); ++it) {
+ MediaPipelineTransmit* pipeline =
+ static_cast<MediaPipelineTransmit*>((*it).second.get());
+ pipeline->UpdateSinkIdentity_m(aTrack, aPrincipal, aSinkIdentity);
+ }
+}
+
+void RemoteSourceStreamInfo::UpdatePrincipal_m(nsIPrincipal* aPrincipal)
+{
+ // This blasts away the existing principal.
+ // We only do this when we become certain that the all tracks are safe to make
+ // accessible to the script principal.
+ for (auto& trackPair : mTracks) {
+ MOZ_RELEASE_ASSERT(trackPair.second);
+ RemoteTrackSource& source =
+ static_cast<RemoteTrackSource&>(trackPair.second->GetSource());
+ source.SetPrincipal(aPrincipal);
+
+ RefPtr<MediaPipeline> pipeline = GetPipelineByTrackId_m(trackPair.first);
+ if (pipeline) {
+ MOZ_ASSERT(pipeline->direction() == MediaPipeline::RECEIVE);
+ static_cast<MediaPipelineReceive*>(pipeline.get())
+ ->SetPrincipalHandle_m(MakePrincipalHandle(aPrincipal));
+ }
+ }
+}
+#endif // MOZILLA_INTERNAL_API
+
+bool
+PeerConnectionMedia::AnyCodecHasPluginID(uint64_t aPluginID)
+{
+ for (uint32_t i=0; i < mLocalSourceStreams.Length(); ++i) {
+ if (mLocalSourceStreams[i]->AnyCodecHasPluginID(aPluginID)) {
+ return true;
+ }
+ }
+ for (uint32_t i=0; i < mRemoteSourceStreams.Length(); ++i) {
+ if (mRemoteSourceStreams[i]->AnyCodecHasPluginID(aPluginID)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+SourceStreamInfo::AnyCodecHasPluginID(uint64_t aPluginID)
+{
+ // Scan the videoConduits for this plugin ID
+ for (auto it = mPipelines.begin(); it != mPipelines.end(); ++it) {
+ if (it->second->Conduit()->CodecPluginID() == aPluginID) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsresult
+SourceStreamInfo::StorePipeline(
+ const std::string& trackId,
+ const RefPtr<mozilla::MediaPipeline>& aPipeline)
+{
+ MOZ_ASSERT(mPipelines.find(trackId) == mPipelines.end());
+ if (mPipelines.find(trackId) != mPipelines.end()) {
+ CSFLogError(logTag, "%s: Storing duplicate track", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ mPipelines[trackId] = aPipeline;
+ return NS_OK;
+}
+
+void
+RemoteSourceStreamInfo::DetachMedia_m()
+{
+ for (auto& webrtcIdAndTrack : mTracks) {
+ EndTrack(mMediaStream->GetInputStream(), webrtcIdAndTrack.second);
+ }
+ SourceStreamInfo::DetachMedia_m();
+}
+
+void
+RemoteSourceStreamInfo::RemoveTrack(const std::string& trackId)
+{
+ auto it = mTracks.find(trackId);
+ if (it != mTracks.end()) {
+ EndTrack(mMediaStream->GetInputStream(), it->second);
+ }
+
+ SourceStreamInfo::RemoveTrack(trackId);
+}
+
+void
+RemoteSourceStreamInfo::SyncPipeline(
+ RefPtr<MediaPipelineReceive> aPipeline)
+{
+ // See if we have both audio and video here, and if so cross the streams and
+ // sync them
+ // TODO: Do we need to prevent multiple syncs if there is more than one audio
+ // or video track in a single media stream? What are we supposed to do in this
+ // case?
+ for (auto i = mPipelines.begin(); i != mPipelines.end(); ++i) {
+ if (i->second->IsVideo() != aPipeline->IsVideo()) {
+ // Ok, we have one video, one non-video - cross the streams!
+ WebrtcAudioConduit *audio_conduit =
+ static_cast<WebrtcAudioConduit*>(aPipeline->IsVideo() ?
+ i->second->Conduit() :
+ aPipeline->Conduit());
+ WebrtcVideoConduit *video_conduit =
+ static_cast<WebrtcVideoConduit*>(aPipeline->IsVideo() ?
+ aPipeline->Conduit() :
+ i->second->Conduit());
+ video_conduit->SyncTo(audio_conduit);
+ CSFLogDebug(logTag, "Syncing %p to %p, %s to %s",
+ video_conduit, audio_conduit,
+ i->first.c_str(), aPipeline->trackid().c_str());
+ }
+ }
+}
+
+void
+RemoteSourceStreamInfo::StartReceiving()
+{
+ if (mReceiving || mPipelines.empty()) {
+ return;
+ }
+
+ mReceiving = true;
+
+ SourceMediaStream* source = GetMediaStream()->GetInputStream()->AsSourceStream();
+ source->SetPullEnabled(true);
+ // AdvanceKnownTracksTicksTime(HEAT_DEATH_OF_UNIVERSE) means that in
+ // theory per the API, we can't add more tracks before that
+ // time. However, the impl actually allows it, and it avoids a whole
+ // bunch of locking that would be required (and potential blocking)
+ // if we used smaller values and updated them on each NotifyPull.
+ source->AdvanceKnownTracksTime(STREAM_TIME_MAX);
+ CSFLogDebug(logTag, "Finished adding tracks to MediaStream %p", source);
+}
+
+RefPtr<MediaPipeline> SourceStreamInfo::GetPipelineByTrackId_m(
+ const std::string& trackId) {
+ ASSERT_ON_THREAD(mParent->GetMainThread());
+
+ // Refuse to hand out references if we're tearing down.
+ // (Since teardown involves a dispatch to and from STS before MediaPipelines
+ // are released, it is safe to start other dispatches to and from STS with a
+ // RefPtr<MediaPipeline>, since that reference won't be the last one
+ // standing)
+ if (mMediaStream) {
+ if (mPipelines.count(trackId)) {
+ return mPipelines[trackId];
+ }
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<MediaPipeline>
+LocalSourceStreamInfo::ForgetPipelineByTrackId_m(const std::string& trackId)
+{
+ ASSERT_ON_THREAD(mParent->GetMainThread());
+
+ // Refuse to hand out references if we're tearing down.
+ // (Since teardown involves a dispatch to and from STS before MediaPipelines
+ // are released, it is safe to start other dispatches to and from STS with a
+ // RefPtr<MediaPipeline>, since that reference won't be the last one
+ // standing)
+ if (mMediaStream) {
+ if (mPipelines.count(trackId)) {
+ RefPtr<MediaPipeline> pipeline(mPipelines[trackId]);
+ mPipelines.erase(trackId);
+ return pipeline.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+auto
+RemoteTrackSource::ApplyConstraints(
+ nsPIDOMWindowInner* aWindow,
+ const dom::MediaTrackConstraints& aConstraints) -> already_AddRefed<PledgeVoid>
+{
+ RefPtr<PledgeVoid> p = new PledgeVoid();
+ p->Reject(new dom::MediaStreamError(aWindow,
+ NS_LITERAL_STRING("OverconstrainedError"),
+ NS_LITERAL_STRING("")));
+ return p.forget();
+}
+#endif
+
+} // namespace mozilla
diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
new file mode 100644
index 000000000..c0001a5e5
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -0,0 +1,586 @@
+/* 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/. */
+
+#ifndef _PEER_CONNECTION_MEDIA_H_
+#define _PEER_CONNECTION_MEDIA_H_
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "nspr.h"
+#include "prlock.h"
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIProtocolProxyCallback.h"
+
+#include "signaling/src/jsep/JsepSession.h"
+#include "AudioSegment.h"
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+#include "Layers.h"
+#include "VideoUtils.h"
+#include "ImageLayers.h"
+#include "VideoSegment.h"
+#include "MediaStreamTrack.h"
+#endif
+
+class nsIPrincipal;
+
+namespace mozilla {
+class DataChannel;
+class PeerIdentity;
+class MediaPipelineFactory;
+namespace dom {
+struct RTCInboundRTPStreamStats;
+struct RTCOutboundRTPStreamStats;
+}
+}
+
+#include "nricectxhandler.h"
+#include "nriceresolver.h"
+#include "nricemediastream.h"
+#include "MediaPipeline.h"
+
+namespace mozilla {
+
+class PeerConnectionImpl;
+class PeerConnectionMedia;
+class PCUuidGenerator;
+
+class SourceStreamInfo {
+public:
+ SourceStreamInfo(DOMMediaStream* aMediaStream,
+ PeerConnectionMedia *aParent,
+ const std::string& aId)
+ : mMediaStream(aMediaStream),
+ mParent(aParent),
+ mId(aId) {
+ MOZ_ASSERT(mMediaStream);
+ }
+
+ SourceStreamInfo(already_AddRefed<DOMMediaStream>& aMediaStream,
+ PeerConnectionMedia *aParent,
+ const std::string& aId)
+ : mMediaStream(aMediaStream),
+ mParent(aParent),
+ mId(aId) {
+ MOZ_ASSERT(mMediaStream);
+ }
+
+ virtual ~SourceStreamInfo() {}
+
+ DOMMediaStream* GetMediaStream() const {
+ return mMediaStream;
+ }
+
+ nsresult StorePipeline(const std::string& trackId,
+ const RefPtr<MediaPipeline>& aPipeline);
+
+ virtual void AddTrack(const std::string& trackId,
+ const RefPtr<dom::MediaStreamTrack>& aTrack)
+ {
+ mTracks.insert(std::make_pair(trackId, aTrack));
+ }
+ virtual void RemoveTrack(const std::string& trackId);
+ bool HasTrack(const std::string& trackId) const
+ {
+ return !!mTracks.count(trackId);
+ }
+ size_t GetTrackCount() const { return mTracks.size(); }
+
+ // This method exists for stats and the unittests.
+ // It allows visibility into the pipelines and flows.
+ const std::map<std::string, RefPtr<MediaPipeline>>&
+ GetPipelines() const { return mPipelines; }
+ RefPtr<MediaPipeline> GetPipelineByTrackId_m(const std::string& trackId);
+ // This is needed so PeerConnectionImpl can unregister itself as
+ // PrincipalChangeObserver from each track.
+ const std::map<std::string, RefPtr<dom::MediaStreamTrack>>&
+ GetMediaStreamTracks() const { return mTracks; }
+ dom::MediaStreamTrack* GetTrackById(const std::string& trackId) const
+ {
+ auto it = mTracks.find(trackId);
+ if (it == mTracks.end()) {
+ return nullptr;
+ }
+
+ return it->second;
+ }
+ const std::string& GetId() const { return mId; }
+
+ void DetachTransport_s();
+ virtual void DetachMedia_m();
+ bool AnyCodecHasPluginID(uint64_t aPluginID);
+protected:
+ void EndTrack(MediaStream* stream, dom::MediaStreamTrack* track);
+ RefPtr<DOMMediaStream> mMediaStream;
+ PeerConnectionMedia *mParent;
+ const std::string mId;
+ // These get set up before we generate our local description, the pipelines
+ // and conduits are set up once offer/answer completes.
+ std::map<std::string, RefPtr<dom::MediaStreamTrack>> mTracks;
+ std::map<std::string, RefPtr<MediaPipeline>> mPipelines;
+};
+
+// TODO(ekr@rtfm.com): Refactor {Local,Remote}SourceStreamInfo
+// bug 837539.
+class LocalSourceStreamInfo : public SourceStreamInfo {
+ ~LocalSourceStreamInfo() {
+ mMediaStream = nullptr;
+ }
+public:
+ LocalSourceStreamInfo(DOMMediaStream *aMediaStream,
+ PeerConnectionMedia *aParent,
+ const std::string& aId)
+ : SourceStreamInfo(aMediaStream, aParent, aId) {}
+
+ nsresult TakePipelineFrom(RefPtr<LocalSourceStreamInfo>& info,
+ const std::string& oldTrackId,
+ dom::MediaStreamTrack& aNewTrack,
+ const std::string& newTrackId);
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ void UpdateSinkIdentity_m(dom::MediaStreamTrack* aTrack,
+ nsIPrincipal* aPrincipal,
+ const PeerIdentity* aSinkIdentity);
+#endif
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(LocalSourceStreamInfo)
+
+private:
+ already_AddRefed<MediaPipeline> ForgetPipelineByTrackId_m(
+ const std::string& trackId);
+};
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+class RemoteTrackSource : public dom::MediaStreamTrackSource
+{
+public:
+ explicit RemoteTrackSource(nsIPrincipal* aPrincipal, const nsString& aLabel)
+ : dom::MediaStreamTrackSource(aPrincipal, aLabel) {}
+
+ dom::MediaSourceEnum GetMediaSource() const override
+ {
+ return dom::MediaSourceEnum::Other;
+ }
+
+ already_AddRefed<PledgeVoid>
+ ApplyConstraints(nsPIDOMWindowInner* aWindow,
+ const dom::MediaTrackConstraints& aConstraints) override;
+
+ void Stop() override
+ {
+ // XXX (Bug 1314270): Implement rejection logic if necessary when we have
+ // clarity in the spec.
+ }
+
+ void SetPrincipal(nsIPrincipal* aPrincipal)
+ {
+ mPrincipal = aPrincipal;
+ PrincipalChanged();
+ }
+
+protected:
+ virtual ~RemoteTrackSource() {}
+};
+#endif
+
+class RemoteSourceStreamInfo : public SourceStreamInfo {
+ ~RemoteSourceStreamInfo() {}
+ public:
+ RemoteSourceStreamInfo(already_AddRefed<DOMMediaStream> aMediaStream,
+ PeerConnectionMedia *aParent,
+ const std::string& aId)
+ : SourceStreamInfo(aMediaStream, aParent, aId),
+ mReceiving(false)
+ {
+ }
+
+ void DetachMedia_m() override;
+ void RemoveTrack(const std::string& trackId) override;
+ void SyncPipeline(RefPtr<MediaPipelineReceive> aPipeline);
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ void UpdatePrincipal_m(nsIPrincipal* aPrincipal);
+#endif
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteSourceStreamInfo)
+
+ void AddTrack(const std::string& trackId,
+ const RefPtr<dom::MediaStreamTrack>& aTrack) override
+ {
+ SourceStreamInfo::AddTrack(trackId, aTrack);
+ }
+
+ TrackID GetNumericTrackId(const std::string& trackId) const
+ {
+ dom::MediaStreamTrack* track = GetTrackById(trackId);
+ if (!track) {
+ return TRACK_INVALID;
+ }
+ return track->mTrackID;
+ }
+
+ void StartReceiving();
+
+ private:
+ // True iff SetPullEnabled(true) has been called on the DOMMediaStream. This
+ // happens when offer/answer concludes.
+ bool mReceiving;
+};
+
+class PeerConnectionMedia : public sigslot::has_slots<> {
+ ~PeerConnectionMedia()
+ {
+ MOZ_RELEASE_ASSERT(!mMainThread);
+ }
+
+ public:
+ explicit PeerConnectionMedia(PeerConnectionImpl *parent);
+
+ enum IceRestartState { ICE_RESTART_NONE,
+ ICE_RESTART_PROVISIONAL,
+ ICE_RESTART_COMMITTED
+ };
+
+ PeerConnectionImpl* GetPC() { return mParent; }
+ nsresult Init(const std::vector<NrIceStunServer>& stun_servers,
+ const std::vector<NrIceTurnServer>& turn_servers,
+ NrIceCtx::Policy policy);
+ // WARNING: This destroys the object!
+ void SelfDestruct();
+
+ RefPtr<NrIceCtxHandler> ice_ctx_hdlr() const { return mIceCtxHdlr; }
+ RefPtr<NrIceCtx> ice_ctx() const { return mIceCtxHdlr->ctx(); }
+
+ RefPtr<NrIceMediaStream> ice_media_stream(size_t i) const {
+ return mIceCtxHdlr->ctx()->GetStream(i);
+ }
+
+ size_t num_ice_media_streams() const {
+ return mIceCtxHdlr->ctx()->GetStreamCount();
+ }
+
+ // Ensure ICE transports exist that we might need when offer/answer concludes
+ void EnsureTransports(const JsepSession& aSession);
+
+ // Activate or remove ICE transports at the conclusion of offer/answer,
+ // or when rollback occurs.
+ void ActivateOrRemoveTransports(const JsepSession& aSession);
+
+ // Start ICE checks.
+ void StartIceChecks(const JsepSession& session);
+
+ bool IsIceRestarting() const;
+ IceRestartState GetIceRestartState() const;
+
+ // Begin ICE restart
+ void BeginIceRestart(const std::string& ufrag,
+ const std::string& pwd);
+ // Commit ICE Restart - offer/answer complete, no rollback possible
+ void CommitIceRestart();
+ // Finalize ICE restart
+ void FinalizeIceRestart();
+ // Abort ICE restart
+ void RollbackIceRestart();
+
+ // Process a trickle ICE candidate.
+ void AddIceCandidate(const std::string& candidate, const std::string& mid,
+ uint32_t aMLine);
+
+ // Handle complete media pipelines.
+ nsresult UpdateMediaPipelines(const JsepSession& session);
+
+ // Add a track (main thread only)
+ nsresult AddTrack(DOMMediaStream& aMediaStream,
+ const std::string& streamId,
+ dom::MediaStreamTrack& aTrack,
+ const std::string& trackId);
+
+ nsresult RemoveLocalTrack(const std::string& streamId,
+ const std::string& trackId);
+ nsresult RemoveRemoteTrack(const std::string& streamId,
+ const std::string& trackId);
+
+ // Get a specific local stream
+ uint32_t LocalStreamsLength()
+ {
+ return mLocalSourceStreams.Length();
+ }
+ LocalSourceStreamInfo* GetLocalStreamByIndex(int index);
+ LocalSourceStreamInfo* GetLocalStreamById(const std::string& id);
+ LocalSourceStreamInfo* GetLocalStreamByTrackId(const std::string& id);
+
+ // Get a specific remote stream
+ uint32_t RemoteStreamsLength()
+ {
+ return mRemoteSourceStreams.Length();
+ }
+
+ RemoteSourceStreamInfo* GetRemoteStreamByIndex(size_t index);
+ RemoteSourceStreamInfo* GetRemoteStreamById(const std::string& id);
+ RemoteSourceStreamInfo* GetRemoteStreamByTrackId(const std::string& id);
+
+ // Add a remote stream.
+ nsresult AddRemoteStream(RefPtr<RemoteSourceStreamInfo> aInfo);
+
+ nsresult ReplaceTrack(const std::string& aOldStreamId,
+ const std::string& aOldTrackId,
+ dom::MediaStreamTrack& aNewTrack,
+ const std::string& aNewStreamId,
+ const std::string& aNewTrackId);
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+ // In cases where the peer isn't yet identified, we disable the pipeline (not
+ // the stream, that would potentially affect others), so that it sends
+ // black/silence. Once the peer is identified, re-enable those streams.
+ // aTrack will be set if this update came from a principal change on aTrack.
+ void UpdateSinkIdentity_m(dom::MediaStreamTrack* aTrack,
+ nsIPrincipal* aPrincipal,
+ const PeerIdentity* aSinkIdentity);
+ // this determines if any track is peerIdentity constrained
+ bool AnyLocalTrackHasPeerIdentity() const;
+ // When we finally learn who is on the other end, we need to change the ownership
+ // on streams
+ void UpdateRemoteStreamPrincipals_m(nsIPrincipal* aPrincipal);
+#endif
+
+ bool AnyCodecHasPluginID(uint64_t aPluginID);
+
+ const nsCOMPtr<nsIThread>& GetMainThread() const { return mMainThread; }
+ const nsCOMPtr<nsIEventTarget>& GetSTSThread() const { return mSTSThread; }
+
+ static size_t GetTransportFlowIndex(int aStreamIndex, bool aRtcp)
+ {
+ return aStreamIndex * 2 + (aRtcp ? 1 : 0);
+ }
+
+ // Get a transport flow either RTP/RTCP for a particular stream
+ // A stream can be of audio/video/datachannel/budled(?) types
+ RefPtr<TransportFlow> GetTransportFlow(int aStreamIndex, bool aIsRtcp) {
+ int index_inner = GetTransportFlowIndex(aStreamIndex, aIsRtcp);
+
+ if (mTransportFlows.find(index_inner) == mTransportFlows.end())
+ return nullptr;
+
+ return mTransportFlows[index_inner];
+ }
+
+ // Add a transport flow
+ void AddTransportFlow(int aIndex, bool aRtcp,
+ const RefPtr<TransportFlow> &aFlow);
+ void RemoveTransportFlow(int aIndex, bool aRtcp);
+ void ConnectDtlsListener_s(const RefPtr<TransportFlow>& aFlow);
+ void DtlsConnected_s(TransportLayer* aFlow,
+ TransportLayer::State state);
+ static void DtlsConnected_m(const std::string& aParentHandle,
+ bool aPrivacyRequested);
+
+ RefPtr<AudioSessionConduit> GetAudioConduit(size_t level) {
+ auto it = mConduits.find(level);
+ if (it == mConduits.end()) {
+ return nullptr;
+ }
+
+ if (it->second.first) {
+ MOZ_ASSERT(false, "In GetAudioConduit, we found a video conduit!");
+ return nullptr;
+ }
+
+ return RefPtr<AudioSessionConduit>(
+ static_cast<AudioSessionConduit*>(it->second.second.get()));
+ }
+
+ RefPtr<VideoSessionConduit> GetVideoConduit(size_t level) {
+ auto it = mConduits.find(level);
+ if (it == mConduits.end()) {
+ return nullptr;
+ }
+
+ if (!it->second.first) {
+ MOZ_ASSERT(false, "In GetVideoConduit, we found an audio conduit!");
+ return nullptr;
+ }
+
+ return RefPtr<VideoSessionConduit>(
+ static_cast<VideoSessionConduit*>(it->second.second.get()));
+ }
+
+ // Add a conduit
+ void AddAudioConduit(size_t level, const RefPtr<AudioSessionConduit> &aConduit) {
+ mConduits[level] = std::make_pair(false, aConduit);
+ }
+
+ void AddVideoConduit(size_t level, const RefPtr<VideoSessionConduit> &aConduit) {
+ mConduits[level] = std::make_pair(true, aConduit);
+ }
+
+ // ICE state signals
+ sigslot::signal2<NrIceCtx*, NrIceCtx::GatheringState>
+ SignalIceGatheringStateChange;
+ sigslot::signal2<NrIceCtx*, NrIceCtx::ConnectionState>
+ SignalIceConnectionStateChange;
+ // This passes a candidate:... attribute and level
+ sigslot::signal2<const std::string&, uint16_t> SignalCandidate;
+ // This passes address, port, level of the default candidate.
+ sigslot::signal5<const std::string&, uint16_t,
+ const std::string&, uint16_t, uint16_t>
+ SignalUpdateDefaultCandidate;
+ sigslot::signal1<uint16_t>
+ SignalEndOfLocalCandidates;
+
+ private:
+ nsresult InitProxy();
+ class ProtocolProxyQueryHandler : public nsIProtocolProxyCallback {
+ public:
+ explicit ProtocolProxyQueryHandler(PeerConnectionMedia *pcm) :
+ pcm_(pcm) {}
+
+ NS_IMETHOD OnProxyAvailable(nsICancelable *request,
+ nsIChannel *aChannel,
+ nsIProxyInfo *proxyinfo,
+ nsresult result) override;
+ NS_DECL_ISUPPORTS
+
+ private:
+ void SetProxyOnPcm(nsIProxyInfo& proxyinfo);
+ RefPtr<PeerConnectionMedia> pcm_;
+ virtual ~ProtocolProxyQueryHandler() {}
+ };
+
+ // Shutdown media transport. Must be called on STS thread.
+ void ShutdownMediaTransport_s();
+
+ // Final destruction of the media stream. Must be called on the main
+ // thread.
+ void SelfDestruct_m();
+
+ // Manage ICE transports.
+ void EnsureTransport_s(size_t aLevel, size_t aComponentCount);
+ void ActivateOrRemoveTransport_s(
+ size_t aMLine,
+ size_t aComponentCount,
+ const std::string& aUfrag,
+ const std::string& aPassword,
+ const std::vector<std::string>& aCandidateList);
+ void RemoveTransportsAtOrAfter_s(size_t aMLine);
+
+ void GatherIfReady();
+ void FlushIceCtxOperationQueueIfReady();
+ void PerformOrEnqueueIceCtxOperation(nsIRunnable* runnable);
+ void EnsureIceGathering_s(bool aDefaultRouteOnly, bool aProxyOnly);
+ void StartIceChecks_s(bool aIsControlling,
+ bool aIsIceLite,
+ const std::vector<std::string>& aIceOptionsList);
+
+ void BeginIceRestart_s(RefPtr<NrIceCtx> new_ctx);
+ void FinalizeIceRestart_s();
+ void RollbackIceRestart_s();
+ bool GetPrefDefaultAddressOnly() const;
+ bool GetPrefProxyOnly() const;
+
+ void ConnectSignals(NrIceCtx *aCtx, NrIceCtx *aOldCtx=nullptr);
+
+ // Process a trickle ICE candidate.
+ void AddIceCandidate_s(const std::string& aCandidate, const std::string& aMid,
+ uint32_t aMLine);
+
+
+ // ICE events
+ void IceGatheringStateChange_s(NrIceCtx* ctx,
+ NrIceCtx::GatheringState state);
+ void IceConnectionStateChange_s(NrIceCtx* ctx,
+ NrIceCtx::ConnectionState state);
+ void IceStreamReady_s(NrIceMediaStream *aStream);
+ void OnCandidateFound_s(NrIceMediaStream *aStream,
+ const std::string& aCandidate);
+ void EndOfLocalCandidates(const std::string& aDefaultAddr,
+ uint16_t aDefaultPort,
+ const std::string& aDefaultRtcpAddr,
+ uint16_t aDefaultRtcpPort,
+ uint16_t aMLine);
+ void GetDefaultCandidates(const NrIceMediaStream& aStream,
+ NrIceCandidate* aCandidate,
+ NrIceCandidate* aRtcpCandidate);
+
+ void IceGatheringStateChange_m(NrIceCtx* ctx,
+ NrIceCtx::GatheringState state);
+ void IceConnectionStateChange_m(NrIceCtx* ctx,
+ NrIceCtx::ConnectionState state);
+ void OnCandidateFound_m(const std::string& aCandidateLine,
+ const std::string& aDefaultAddr,
+ uint16_t aDefaultPort,
+ const std::string& aDefaultRtcpAddr,
+ uint16_t aDefaultRtcpPort,
+ uint16_t aMLine);
+ void EndOfLocalCandidates_m(const std::string& aDefaultAddr,
+ uint16_t aDefaultPort,
+ const std::string& aDefaultRtcpAddr,
+ uint16_t aDefaultRtcpPort,
+ uint16_t aMLine);
+ bool IsIceCtxReady() const {
+ return mProxyResolveCompleted;
+ }
+
+ // The parent PC
+ PeerConnectionImpl *mParent;
+ // and a loose handle on it for event driven stuff
+ std::string mParentHandle;
+ std::string mParentName;
+
+ // A list of streams returned from GetUserMedia
+ // This is only accessed on the main thread (with one special exception)
+ nsTArray<RefPtr<LocalSourceStreamInfo> > mLocalSourceStreams;
+
+ // A list of streams provided by the other side
+ // This is only accessed on the main thread (with one special exception)
+ nsTArray<RefPtr<RemoteSourceStreamInfo> > mRemoteSourceStreams;
+
+ std::map<size_t, std::pair<bool, RefPtr<MediaSessionConduit>>> mConduits;
+
+ // ICE objects
+ RefPtr<NrIceCtxHandler> mIceCtxHdlr;
+
+ // DNS
+ RefPtr<NrIceResolver> mDNSResolver;
+
+ // Transport flows: even is RTP, odd is RTCP
+ std::map<int, RefPtr<TransportFlow> > mTransportFlows;
+
+ // UUID Generator
+ UniquePtr<PCUuidGenerator> mUuidGen;
+
+ // The main thread.
+ nsCOMPtr<nsIThread> mMainThread;
+
+ // The STS thread.
+ nsCOMPtr<nsIEventTarget> mSTSThread;
+
+ // Used whenever we need to dispatch a runnable to STS to tweak something
+ // on our ICE ctx, but are not ready to do so at the moment (eg; we are
+ // waiting to get a callback with our http proxy config before we start
+ // gathering or start checking)
+ std::vector<nsCOMPtr<nsIRunnable>> mQueuedIceCtxOperations;
+
+ // Used to cancel any ongoing proxy request.
+ nsCOMPtr<nsICancelable> mProxyRequest;
+
+ // Used to track the state of the request.
+ bool mProxyResolveCompleted;
+
+ // Used to store the result of the request.
+ UniquePtr<NrIceProxyServer> mProxyServer;
+
+ // Used to track the state of ice restart
+ IceRestartState mIceRestartState;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PeerConnectionMedia)
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/media/webrtc/signaling/src/peerconnection/WebrtcGlobalChild.h b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalChild.h
new file mode 100644
index 000000000..544315a3e
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalChild.h
@@ -0,0 +1,40 @@
+/* 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/. */
+
+#ifndef _WEBRTC_GLOBAL_CHILD_H_
+#define _WEBRTC_GLOBAL_CHILD_H_
+
+#include "mozilla/dom/PWebrtcGlobalChild.h"
+
+namespace mozilla {
+namespace dom {
+
+class WebrtcGlobalChild :
+ public PWebrtcGlobalChild
+{
+ friend class ContentChild;
+
+ bool mShutdown;
+
+ MOZ_IMPLICIT WebrtcGlobalChild();
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual bool RecvGetStatsRequest(const int& aRequestId,
+ const nsString& aPcIdFilter) override;
+ virtual bool RecvClearStatsRequest() override;
+ virtual bool RecvGetLogRequest(const int& aReqestId,
+ const nsCString& aPattern) override;
+ virtual bool RecvClearLogRequest() override;
+ virtual bool RecvSetAecLogging(const bool& aEnable) override;
+ virtual bool RecvSetDebugMode(const int& aLevel) override;
+
+public:
+ virtual ~WebrtcGlobalChild();
+ static WebrtcGlobalChild* Create();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _WEBRTC_GLOBAL_CHILD_H_
diff --git a/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp
new file mode 100644
index 000000000..96bdd5b70
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp
@@ -0,0 +1,1241 @@
+/* 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 "WebrtcGlobalInformation.h"
+#include "mozilla/media/webrtc/WebrtcGlobal.h"
+#include "WebrtcGlobalChild.h"
+#include "WebrtcGlobalParent.h"
+
+#include <deque>
+#include <string>
+#include <algorithm>
+#include <vector>
+#include <map>
+#include <queue>
+
+#include "CSFLog.h"
+#include "WebRtcLog.h"
+#include "mozilla/dom/WebrtcGlobalInformationBinding.h"
+#include "mozilla/dom/ContentChild.h"
+
+#include "nsAutoPtr.h"
+#include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID
+#include "nsServiceManagerUtils.h" // do_GetService
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Vector.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/RefPtr.h"
+
+#include "rlogconnector.h"
+#include "runnable_utils.h"
+#include "PeerConnectionCtx.h"
+#include "PeerConnectionImpl.h"
+#include "webrtc/system_wrappers/interface/trace.h"
+
+static const char* logTag = "WebrtcGlobalInformation";
+
+namespace mozilla {
+namespace dom {
+
+typedef Vector<nsAutoPtr<RTCStatsQuery>> RTCStatsQueries;
+typedef nsTArray<RTCStatsReportInternal> Stats;
+
+template<class Request, typename Callback,
+ typename Result, typename QueryParam>
+class RequestManager
+{
+public:
+
+ static Request* Create(Callback& aCallback, QueryParam& aParam)
+ {
+ mozilla::StaticMutexAutoLock lock(sMutex);
+
+ int id = ++sLastRequestId;
+ auto result = sRequests.insert(
+ std::make_pair(id, Request(id, aCallback, aParam)));
+
+ if (!result.second) {
+ return nullptr;
+ }
+
+ return &result.first->second;
+ }
+
+ static void Delete(int aId)
+ {
+ mozilla::StaticMutexAutoLock lock(sMutex);
+ sRequests.erase(aId);
+ }
+
+ static Request* Get(int aId)
+ {
+ mozilla::StaticMutexAutoLock lock(sMutex);
+ auto r = sRequests.find(aId);
+
+ if (r == sRequests.end()) {
+ return nullptr;
+ }
+
+ return &r->second;
+ }
+
+ Result mResult;
+ std::queue<RefPtr<WebrtcGlobalParent>> mContactList;
+ const int mRequestId;
+
+ RefPtr<WebrtcGlobalParent> GetNextParent()
+ {
+ while (!mContactList.empty()) {
+ RefPtr<WebrtcGlobalParent> next = mContactList.front();
+ mContactList.pop();
+ if (next->IsActive()) {
+ return next;
+ }
+ }
+
+ return nullptr;
+ }
+
+ void Complete()
+ {
+ ErrorResult rv;
+ mCallback.get()->Call(mResult, rv);
+
+ if (rv.Failed()) {
+ CSFLogError(logTag, "Error firing stats observer callback");
+ }
+ }
+
+protected:
+ // The mutex is used to protect two related operations involving the sRequest map
+ // and the sLastRequestId. For the map, it prevents more than one thread from
+ // adding or deleting map entries at the same time. For id generation,
+ // it creates an atomic allocation and increment.
+ static mozilla::StaticMutex sMutex;
+ static std::map<int, Request> sRequests;
+ static int sLastRequestId;
+
+ Callback mCallback;
+
+ explicit RequestManager(int aId, Callback& aCallback)
+ : mRequestId(aId)
+ , mCallback(aCallback)
+ {}
+ ~RequestManager() {}
+private:
+
+ RequestManager() = delete;
+ RequestManager& operator=(const RequestManager&) = delete;
+};
+
+template<class Request, typename Callback,
+ typename Result, typename QueryParam>
+mozilla::StaticMutex RequestManager<Request, Callback, Result, QueryParam>::sMutex;
+template<class Request, typename Callback,
+ typename Result, typename QueryParam>
+std::map<int, Request> RequestManager<Request, Callback, Result, QueryParam>::sRequests;
+template<class Request, typename Callback,
+ typename Result, typename QueryParam>
+int RequestManager<Request, Callback, Result, QueryParam>::sLastRequestId;
+
+typedef nsMainThreadPtrHandle<WebrtcGlobalStatisticsCallback> StatsRequestCallback;
+
+class StatsRequest
+ : public RequestManager<StatsRequest,
+ StatsRequestCallback,
+ WebrtcGlobalStatisticsReport,
+ nsAString>
+{
+public:
+ const nsString mPcIdFilter;
+ explicit StatsRequest(int aId, StatsRequestCallback& aCallback, nsAString& aFilter)
+ : RequestManager(aId, aCallback)
+ , mPcIdFilter(aFilter)
+ {
+ mResult.mReports.Construct();
+ }
+
+private:
+ StatsRequest() = delete;
+ StatsRequest& operator=(const StatsRequest&) = delete;
+};
+
+typedef nsMainThreadPtrHandle<WebrtcGlobalLoggingCallback> LogRequestCallback;
+
+class LogRequest
+ : public RequestManager<LogRequest,
+ LogRequestCallback,
+ Sequence<nsString>,
+ const nsACString>
+{
+public:
+ const nsCString mPattern;
+ explicit LogRequest(int aId, LogRequestCallback& aCallback, const nsACString& aPattern)
+ : RequestManager(aId, aCallback)
+ , mPattern(aPattern)
+ {}
+
+private:
+ LogRequest() = delete;
+ LogRequest& operator=(const LogRequest&) = delete;
+};
+
+class WebrtcContentParents
+{
+public:
+ static WebrtcGlobalParent* Alloc();
+ static void Dealloc(WebrtcGlobalParent* aParent);
+ static bool Empty()
+ {
+ return sContentParents.empty();
+ }
+ static const std::vector<RefPtr<WebrtcGlobalParent>>& GetAll()
+ {
+ return sContentParents;
+ }
+private:
+ static std::vector<RefPtr<WebrtcGlobalParent>> sContentParents;
+ WebrtcContentParents() = delete;
+ WebrtcContentParents(const WebrtcContentParents&) = delete;
+ WebrtcContentParents& operator=(const WebrtcContentParents&) = delete;
+};
+
+std::vector<RefPtr<WebrtcGlobalParent>> WebrtcContentParents::sContentParents;
+
+WebrtcGlobalParent* WebrtcContentParents::Alloc()
+{
+ RefPtr<WebrtcGlobalParent> cp = new WebrtcGlobalParent;
+ sContentParents.push_back(cp);
+ return cp.get();
+}
+
+void WebrtcContentParents::Dealloc(WebrtcGlobalParent* aParent)
+{
+ if (aParent) {
+ aParent->mShutdown = true;
+ auto cp = std::find(sContentParents.begin(), sContentParents.end(), aParent);
+ if (cp != sContentParents.end()) {
+ sContentParents.erase(cp);
+ }
+ }
+}
+
+static PeerConnectionCtx* GetPeerConnectionCtx()
+{
+ if(PeerConnectionCtx::isActive()) {
+ MOZ_ASSERT(PeerConnectionCtx::GetInstance());
+ return PeerConnectionCtx::GetInstance();
+ }
+ return nullptr;
+}
+
+static void
+OnStatsReport_m(WebrtcGlobalChild* aThisChild,
+ const int aRequestId,
+ nsAutoPtr<RTCStatsQueries> aQueryList)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aQueryList);
+
+ if (aThisChild) {
+ Stats stats;
+
+ // Copy stats generated for the currently active PeerConnections
+ for (auto&& query : *aQueryList) {
+ stats.AppendElement(*(query->report));
+ }
+ // Reports saved for closed/destroyed PeerConnections
+ auto ctx = PeerConnectionCtx::GetInstance();
+ if (ctx) {
+ for (auto&& pc : ctx->mStatsForClosedPeerConnections) {
+ stats.AppendElement(pc);
+ }
+ }
+
+ Unused << aThisChild->SendGetStatsResult(aRequestId, stats);
+ return;
+ }
+
+ // This is the last stats report to be collected. (Must be the gecko process).
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ StatsRequest* request = StatsRequest::Get(aRequestId);
+
+ if (!request) {
+ CSFLogError(logTag, "Bad RequestId");
+ return;
+ }
+
+ for (auto&& query : *aQueryList) {
+ request->mResult.mReports.Value().AppendElement(*(query->report), fallible);
+ }
+
+ // Reports saved for closed/destroyed PeerConnections
+ auto ctx = PeerConnectionCtx::GetInstance();
+ if (ctx) {
+ for (auto&& pc : ctx->mStatsForClosedPeerConnections) {
+ request->mResult.mReports.Value().AppendElement(pc, fallible);
+ }
+ }
+
+ request->Complete();
+ StatsRequest::Delete(aRequestId);
+}
+
+static void
+GetAllStats_s(WebrtcGlobalChild* aThisChild,
+ const int aRequestId,
+ nsAutoPtr<RTCStatsQueries> aQueryList)
+{
+ MOZ_ASSERT(aQueryList);
+ // The call to PeerConnetionImpl must happen from a runnable
+ // dispatched on the STS thread.
+
+ // Get stats from active connections.
+ for (auto&& query : *aQueryList) {
+ PeerConnectionImpl::ExecuteStatsQuery_s(query);
+ }
+
+ // After the RTCStatsQueries have been filled in, control must return
+ // to the main thread before their eventual destruction.
+ NS_DispatchToMainThread(WrapRunnableNM(&OnStatsReport_m,
+ aThisChild,
+ aRequestId,
+ aQueryList),
+ NS_DISPATCH_NORMAL);
+}
+
+static void OnGetLogging_m(WebrtcGlobalChild* aThisChild,
+ const int aRequestId,
+ nsAutoPtr<std::deque<std::string>> aLogList)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aThisChild) {
+ // Add this log to the collection of logs and call into
+ // the next content process.
+ Sequence<nsString> nsLogs;
+
+ if (!aLogList->empty()) {
+ for (auto& line : *aLogList) {
+ nsLogs.AppendElement(NS_ConvertUTF8toUTF16(line.c_str()), fallible);
+ }
+ nsLogs.AppendElement(NS_LITERAL_STRING("+++++++ END ++++++++"), fallible);
+ }
+
+ Unused << aThisChild->SendGetLogResult(aRequestId, nsLogs);
+ return;
+ }
+
+ // This is the last log to be collected. (Must be the gecko process).
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ LogRequest* request = LogRequest::Get(aRequestId);
+
+ if (!request) {
+ CSFLogError(logTag, "Bad RequestId");
+ return;
+ }
+
+ if (!aLogList->empty()) {
+ for (auto& line : *aLogList) {
+ request->mResult.AppendElement(NS_ConvertUTF8toUTF16(line.c_str()),
+ fallible);
+ }
+ request->mResult.AppendElement(NS_LITERAL_STRING("+++++++ END ++++++++"),
+ fallible);
+ }
+
+ request->Complete();
+ LogRequest::Delete(aRequestId);
+}
+
+static void GetLogging_s(WebrtcGlobalChild* aThisChild,
+ const int aRequestId,
+ const std::string& aPattern)
+{
+ // Request log while not on the main thread.
+ RLogConnector* logs = RLogConnector::GetInstance();
+ nsAutoPtr<std::deque<std::string>> result(new std::deque<std::string>);
+ // Might not exist yet.
+ if (logs) {
+ logs->Filter(aPattern, 0, result);
+ }
+ // Return to main thread to complete processing.
+ NS_DispatchToMainThread(WrapRunnableNM(&OnGetLogging_m,
+ aThisChild,
+ aRequestId,
+ result),
+ NS_DISPATCH_NORMAL);
+}
+
+static nsresult
+BuildStatsQueryList(
+ const std::map<const std::string, PeerConnectionImpl *>& aPeerConnections,
+ const nsAString& aPcIdFilter,
+ RTCStatsQueries* queries)
+{
+ nsresult rv;
+
+ for (auto&& pc : aPeerConnections) {
+ MOZ_ASSERT(pc.second);
+ if (aPcIdFilter.IsEmpty() ||
+ aPcIdFilter.EqualsASCII(pc.second->GetIdAsAscii().c_str())) {
+ if (pc.second->HasMedia()) {
+ if (!queries->append(nsAutoPtr<RTCStatsQuery>(new RTCStatsQuery(true)))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ rv = pc.second->BuildStatsQuery_m(nullptr, queries->back()); // all tracks
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ MOZ_ASSERT(queries->back()->report);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+RunStatsQuery(
+ const std::map<const std::string, PeerConnectionImpl *>& aPeerConnections,
+ const nsAString& aPcIdFilter,
+ WebrtcGlobalChild* aThisChild,
+ const int aRequestId)
+{
+ nsAutoPtr<RTCStatsQueries> queries(new RTCStatsQueries);
+ nsresult rv = BuildStatsQueryList(aPeerConnections, aPcIdFilter, queries);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIEventTarget> stsThread =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ } else if (!stsThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = RUN_ON_THREAD(stsThread,
+ WrapRunnableNM(&GetAllStats_s,
+ aThisChild,
+ aRequestId,
+ queries),
+ NS_DISPATCH_NORMAL);
+ return rv;
+}
+
+void ClearClosedStats()
+{
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+
+ if (ctx) {
+ ctx->mStatsForClosedPeerConnections.Clear();
+ }
+}
+
+void
+WebrtcGlobalInformation::ClearAllStats(
+ const GlobalObject& aGlobal)
+{
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ // Chrome-only API
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!WebrtcContentParents::Empty()) {
+ // Pass on the request to any content process based PeerConnections.
+ for (auto& cp : WebrtcContentParents::GetAll()) {
+ Unused << cp->SendClearStatsRequest();
+ }
+ }
+
+ // Flush the history for the chrome process
+ ClearClosedStats();
+}
+
+void
+WebrtcGlobalInformation::GetAllStats(
+ const GlobalObject& aGlobal,
+ WebrtcGlobalStatisticsCallback& aStatsCallback,
+ const Optional<nsAString>& pcIdFilter,
+ ErrorResult& aRv)
+{
+ if (!NS_IsMainThread()) {
+ aRv.Throw(NS_ERROR_NOT_SAME_THREAD);
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // CallbackObject does not support threadsafe refcounting, and must be
+ // used and destroyed on main.
+ StatsRequestCallback callbackHandle(
+ new nsMainThreadPtrHolder<WebrtcGlobalStatisticsCallback>(&aStatsCallback));
+
+ nsString filter;
+ if (pcIdFilter.WasPassed()) {
+ filter = pcIdFilter.Value();
+ }
+
+ auto* request = StatsRequest::Create(callbackHandle, filter);
+
+ if (!request) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (!WebrtcContentParents::Empty()) {
+ // Pass on the request to any content based PeerConnections.
+ for (auto& cp : WebrtcContentParents::GetAll()) {
+ request->mContactList.push(cp);
+ }
+
+ auto next = request->GetNextParent();
+ if (next) {
+ aRv = next->SendGetStatsRequest(request->mRequestId, request->mPcIdFilter) ?
+ NS_OK : NS_ERROR_FAILURE;
+ return;
+ }
+ }
+ // No content resident PeerConnectionCtx instances.
+ // Check this process.
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+ nsresult rv;
+
+ if (ctx) {
+ rv = RunStatsQuery(ctx->mGetPeerConnections(),
+ filter, nullptr, request->mRequestId);
+
+ if (NS_FAILED(rv)) {
+ StatsRequest::Delete(request->mRequestId);
+ }
+ } else {
+ // Just send back an empty report.
+ rv = NS_OK;
+ request->Complete();
+ StatsRequest::Delete(request->mRequestId);
+ }
+
+ aRv = rv;
+ return;
+}
+
+static nsresult
+RunLogQuery(const nsCString& aPattern,
+ WebrtcGlobalChild* aThisChild,
+ const int aRequestId)
+{
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> stsThread =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ } else if (!stsThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = RUN_ON_THREAD(stsThread,
+ WrapRunnableNM(&GetLogging_s,
+ aThisChild,
+ aRequestId,
+ aPattern.get()),
+ NS_DISPATCH_NORMAL);
+ return rv;
+}
+
+static void ClearLogs_s()
+{
+ // Make call off main thread.
+ RLogConnector* logs = RLogConnector::GetInstance();
+ if (logs) {
+ logs->Clear();
+ }
+}
+
+static nsresult
+RunLogClear()
+{
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> stsThread =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!stsThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return RUN_ON_THREAD(stsThread,
+ WrapRunnableNM(&ClearLogs_s),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+WebrtcGlobalInformation::ClearLogging(
+ const GlobalObject& aGlobal)
+{
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ // Chrome-only API
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!WebrtcContentParents::Empty()) {
+ // Clear content process signaling logs
+ for (auto& cp : WebrtcContentParents::GetAll()) {
+ Unused << cp->SendClearLogRequest();
+ }
+ }
+
+ // Clear chrome process signaling logs
+ Unused << RunLogClear();
+}
+
+void
+WebrtcGlobalInformation::GetLogging(
+ const GlobalObject& aGlobal,
+ const nsAString& aPattern,
+ WebrtcGlobalLoggingCallback& aLoggingCallback,
+ ErrorResult& aRv)
+{
+ if (!NS_IsMainThread()) {
+ aRv.Throw(NS_ERROR_NOT_SAME_THREAD);
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // CallbackObject does not support threadsafe refcounting, and must be
+ // destroyed on main.
+ LogRequestCallback callbackHandle(
+ new nsMainThreadPtrHolder<WebrtcGlobalLoggingCallback>(&aLoggingCallback));
+
+ nsAutoCString pattern;
+ CopyUTF16toUTF8(aPattern, pattern);
+
+ LogRequest* request = LogRequest::Create(callbackHandle, pattern);
+
+ if (!request) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (!WebrtcContentParents::Empty()) {
+ // Pass on the request to any content based PeerConnections.
+ for (auto& cp : WebrtcContentParents::GetAll()) {
+ request->mContactList.push(cp);
+ }
+
+ auto next = request->GetNextParent();
+ if (next) {
+ aRv = next->SendGetLogRequest(request->mRequestId, request->mPattern) ?
+ NS_OK : NS_ERROR_FAILURE;
+ return;
+ }
+ }
+
+ nsresult rv = RunLogQuery(request->mPattern, nullptr, request->mRequestId);
+
+ if (NS_FAILED(rv)) {
+ LogRequest::Delete(request->mRequestId);
+ }
+
+ aRv = rv;
+ return;
+}
+
+static int32_t sLastSetLevel = 0;
+static bool sLastAECDebug = false;
+
+void
+WebrtcGlobalInformation::SetDebugLevel(const GlobalObject& aGlobal, int32_t aLevel)
+{
+ if (aLevel) {
+ StartWebRtcLog(webrtc::TraceLevel(aLevel));
+ } else {
+ StopWebRtcLog();
+ }
+ sLastSetLevel = aLevel;
+
+ for (auto& cp : WebrtcContentParents::GetAll()){
+ Unused << cp->SendSetDebugMode(aLevel);
+ }
+}
+
+int32_t
+WebrtcGlobalInformation::DebugLevel(const GlobalObject& aGlobal)
+{
+ return sLastSetLevel;
+}
+
+void
+WebrtcGlobalInformation::SetAecDebug(const GlobalObject& aGlobal, bool aEnable)
+{
+ if (aEnable) {
+ StartAecLog();
+ } else {
+ StopAecLog();
+ }
+
+ sLastAECDebug = aEnable;
+
+ for (auto& cp : WebrtcContentParents::GetAll()){
+ Unused << cp->SendSetAecLogging(aEnable);
+ }
+}
+
+bool
+WebrtcGlobalInformation::AecDebug(const GlobalObject& aGlobal)
+{
+ return sLastAECDebug;
+}
+
+bool
+WebrtcGlobalParent::RecvGetStatsResult(const int& aRequestId,
+ nsTArray<RTCStatsReportInternal>&& Stats)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv = NS_OK;
+
+ StatsRequest* request = StatsRequest::Get(aRequestId);
+
+ if (!request) {
+ CSFLogError(logTag, "Bad RequestId");
+ return false;
+ }
+
+ for (auto&& s : Stats) {
+ request->mResult.mReports.Value().AppendElement(s, fallible);
+ }
+
+ auto next = request->GetNextParent();
+ if (next) {
+ // There are more content instances to query.
+ return next->SendGetStatsRequest(request->mRequestId, request->mPcIdFilter);
+ }
+
+ // Content queries complete, run chrome instance query if applicable
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+
+ if (ctx) {
+ rv = RunStatsQuery(ctx->mGetPeerConnections(),
+ request->mPcIdFilter, nullptr, aRequestId);
+ } else {
+ // No instance in the process, return the collections as is
+ request->Complete();
+ StatsRequest::Delete(aRequestId);
+ }
+
+ return NS_SUCCEEDED(rv);
+}
+
+bool
+WebrtcGlobalParent::RecvGetLogResult(const int& aRequestId,
+ const WebrtcGlobalLog& aLog)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LogRequest* request = LogRequest::Get(aRequestId);
+
+ if (!request) {
+ CSFLogError(logTag, "Bad RequestId");
+ return false;
+ }
+ request->mResult.AppendElements(aLog, fallible);
+
+ auto next = request->GetNextParent();
+ if (next) {
+ // There are more content instances to query.
+ return next->SendGetLogRequest(request->mRequestId, request->mPattern);
+ }
+
+ // Content queries complete, run chrome instance query if applicable
+ nsresult rv = RunLogQuery(request->mPattern, nullptr, aRequestId);
+
+ if (NS_FAILED(rv)) {
+ //Unable to get gecko process log. Return what has been collected.
+ CSFLogError(logTag, "Unable to extract chrome process log");
+ request->Complete();
+ LogRequest::Delete(aRequestId);
+ }
+
+ return true;
+}
+
+WebrtcGlobalParent*
+WebrtcGlobalParent::Alloc()
+{
+ return WebrtcContentParents::Alloc();
+}
+
+bool
+WebrtcGlobalParent::Dealloc(WebrtcGlobalParent * aActor)
+{
+ WebrtcContentParents::Dealloc(aActor);
+ return true;
+}
+
+void
+WebrtcGlobalParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ mShutdown = true;
+ return;
+}
+
+bool
+WebrtcGlobalParent::Recv__delete__()
+{
+ return true;
+}
+
+MOZ_IMPLICIT WebrtcGlobalParent::WebrtcGlobalParent()
+ : mShutdown(false)
+{
+ MOZ_COUNT_CTOR(WebrtcGlobalParent);
+}
+
+MOZ_IMPLICIT WebrtcGlobalParent::~WebrtcGlobalParent()
+{
+ MOZ_COUNT_DTOR(WebrtcGlobalParent);
+}
+
+bool
+WebrtcGlobalChild::RecvGetStatsRequest(const int& aRequestId,
+ const nsString& aPcIdFilter)
+{
+ if (mShutdown) {
+ return true;
+ }
+
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+
+ if (ctx) {
+ nsresult rv = RunStatsQuery(ctx->mGetPeerConnections(),
+ aPcIdFilter, this, aRequestId);
+ return NS_SUCCEEDED(rv);
+ }
+
+ nsTArray<RTCStatsReportInternal> empty_stats;
+ SendGetStatsResult(aRequestId, empty_stats);
+
+ return true;
+}
+
+bool
+WebrtcGlobalChild::RecvClearStatsRequest()
+{
+ if (mShutdown) {
+ return true;
+ }
+
+ ClearClosedStats();
+ return true;
+}
+
+bool
+WebrtcGlobalChild::RecvGetLogRequest(const int& aRequestId,
+ const nsCString& aPattern)
+{
+ if (mShutdown) {
+ return true;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> stsThread =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (NS_SUCCEEDED(rv) && stsThread) {
+ rv = RUN_ON_THREAD(stsThread,
+ WrapRunnableNM(&GetLogging_s, this, aRequestId, aPattern.get()),
+ NS_DISPATCH_NORMAL);
+
+ if (NS_SUCCEEDED(rv)) {
+ return true;
+ }
+ }
+
+ Sequence<nsString> empty_log;
+ SendGetLogResult(aRequestId, empty_log);
+
+ return true;
+}
+
+bool
+WebrtcGlobalChild::RecvClearLogRequest()
+{
+ if (mShutdown) {
+ return true;
+ }
+
+ RunLogClear();
+ return true;
+}
+
+bool
+WebrtcGlobalChild::RecvSetAecLogging(const bool& aEnable)
+{
+ if (!mShutdown) {
+ if (aEnable) {
+ StartAecLog();
+ } else {
+ StopAecLog();
+ }
+ }
+ return true;
+}
+
+bool
+WebrtcGlobalChild::RecvSetDebugMode(const int& aLevel)
+{
+ if (!mShutdown) {
+ if (aLevel) {
+ StartWebRtcLog(webrtc::TraceLevel(aLevel));
+ } else {
+ StopWebRtcLog();
+ }
+ }
+ return true;
+}
+
+WebrtcGlobalChild*
+WebrtcGlobalChild::Create()
+{
+ WebrtcGlobalChild* child =
+ static_cast<WebrtcGlobalChild*>(
+ ContentChild::GetSingleton()->SendPWebrtcGlobalConstructor());
+ return child;
+}
+
+void
+WebrtcGlobalChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+ mShutdown = true;
+}
+
+MOZ_IMPLICIT WebrtcGlobalChild::WebrtcGlobalChild()
+ : mShutdown(false)
+{
+ MOZ_COUNT_CTOR(WebrtcGlobalChild);
+}
+
+MOZ_IMPLICIT WebrtcGlobalChild::~WebrtcGlobalChild()
+{
+ MOZ_COUNT_DTOR(WebrtcGlobalChild);
+}
+
+struct StreamResult {
+ StreamResult() : candidateTypeBitpattern(0), streamSucceeded(false) {}
+ uint32_t candidateTypeBitpattern;
+ bool streamSucceeded;
+};
+
+static uint32_t GetCandidateIpAndTransportMask(const RTCIceCandidateStats *cand) {
+
+ enum {
+ CANDIDATE_BITMASK_UDP = 1,
+ CANDIDATE_BITMASK_TCP = 1 << 1,
+ CANDIDATE_BITMASK_IPV6 = 1 << 2,
+ };
+
+ uint32_t res = 0;
+
+ nsAutoCString transport;
+ // prefer local transport for local relay candidates
+ if (cand->mMozLocalTransport.WasPassed()) {
+ transport.Assign(NS_ConvertUTF16toUTF8(cand->mMozLocalTransport.Value()));
+ } else {
+ transport.Assign(NS_ConvertUTF16toUTF8(cand->mTransport.Value()));
+ }
+ if (transport == kNrIceTransportUdp) {
+ res |= CANDIDATE_BITMASK_UDP;
+ } else if (transport == kNrIceTransportTcp) {
+ res |= CANDIDATE_BITMASK_TCP;
+ }
+
+ if (cand->mIpAddress.Value().FindChar(':') != -1) {
+ res |= CANDIDATE_BITMASK_IPV6;
+ }
+
+ return res;
+};
+
+static void StoreLongTermICEStatisticsImpl_m(
+ nsresult result,
+ nsAutoPtr<RTCStatsQuery> query) {
+
+ using namespace Telemetry;
+
+ if (NS_FAILED(result) ||
+ !query->error.empty() ||
+ !query->report->mIceCandidateStats.WasPassed()) {
+ return;
+ }
+
+ query->report->mClosed.Construct(true);
+
+ // TODO(bcampen@mozilla.com): Do we need to watch out for cases where the
+ // components within a stream didn't have the same types of relayed
+ // candidates? I have a feeling that late trickle could cause this, but right
+ // now we don't have enough information to detect it (we would need to know
+ // the ICE component id for each candidate pair and candidate)
+
+ std::map<std::string, StreamResult> streamResults;
+
+ // Build list of streams, and whether or not they failed.
+ for (size_t i = 0;
+ i < query->report->mIceCandidatePairStats.Value().Length();
+ ++i) {
+ const RTCIceCandidatePairStats &pair =
+ query->report->mIceCandidatePairStats.Value()[i];
+
+ if (!pair.mState.WasPassed() || !pair.mComponentId.WasPassed()) {
+ MOZ_CRASH();
+ continue;
+ }
+
+ // Note: this is not a "component" in the ICE definition, this is really a
+ // stream ID. This is just the way the stats API is standardized right now.
+ // Very confusing.
+ std::string streamId(
+ NS_ConvertUTF16toUTF8(pair.mComponentId.Value()).get());
+
+ streamResults[streamId].streamSucceeded |=
+ pair.mState.Value() == RTCStatsIceCandidatePairState::Succeeded;
+ }
+
+ for (size_t i = 0;
+ i < query->report->mIceCandidateStats.Value().Length();
+ ++i) {
+ const RTCIceCandidateStats &cand =
+ query->report->mIceCandidateStats.Value()[i];
+
+ if (!cand.mType.WasPassed() ||
+ !cand.mCandidateType.WasPassed() ||
+ !cand.mTransport.WasPassed() ||
+ !cand.mIpAddress.WasPassed() ||
+ !cand.mComponentId.WasPassed()) {
+ // Crash on debug, ignore this candidate otherwise.
+ MOZ_CRASH();
+ continue;
+ }
+
+ /* The bitmask after examaning a candidate should look like this:
+ * REMOTE_GATHERED_HOST_UDP = 1,
+ * REMOTE_GATHERED_HOST_TCP = 1 << 1,
+ * REMOTE_GATHERED_HOST_IPV6 = 1 << 2,
+ * REMOTE_GATHERED_SERVER_REFLEXIVE_UDP = 1 << 3,
+ * REMOTE_GATHERED_SERVER_REFLEXIVE_TCP = 1 << 4,
+ * REMOTE_GATHERED_SERVER_REFLEXIVE_IPV6 = 1 << 5,
+ * REMOTE_GATHERED_TURN_UDP = 1 << 6,
+ * REMOTE_GATHERED_TURN_TCP = 1 << 7, // dummy place holder
+ * REMOTE_GATHERED_TURN_IPV6 = 1 << 8,
+ * REMOTE_GATHERED_PEER_REFLEXIVE_UDP = 1 << 9,
+ * REMOTE_GATHERED_PEER_REFLEXIVE_TCP = 1 << 10,
+ * REMOTE_GATHERED_PEER_REFLEXIVE_IPV6 = 1 << 11,
+ * LOCAL_GATHERED_HOST_UDP = 1 << 16,
+ * LOCAL_GATHERED_HOST_TCP = 1 << 17,
+ * LOCAL_GATHERED_HOST_IPV6 = 1 << 18,
+ * LOCAL_GATHERED_SERVER_REFLEXIVE_UDP = 1 << 19,
+ * LOCAL_GATHERED_SERVER_REFLEXIVE_TCP = 1 << 20,
+ * LOCAL_GATHERED_SERVER_REFLEXIVE_IPV6 = 1 << 21,
+ * LOCAL_GATHERED_TURN_UDP = 1 << 22,
+ * LOCAL_GATHERED_TURN_TCP = 1 << 23,
+ * LOCAL_GATHERED_TURN_IPV6 = 1 << 24,
+ * LOCAL_GATHERED_PEERREFLEXIVE_UDP = 1 << 25,
+ * LOCAL_GATHERED_PEERREFLEXIVE_TCP = 1 << 26,
+ * LOCAL_GATHERED_PEERREFLEXIVE_IPV6 = 1 << 27,
+ *
+ * This results in following shift values
+ */
+ static const uint32_t kLocalShift = 16;
+ static const uint32_t kSrflxShift = 3;
+ static const uint32_t kRelayShift = 6;
+ static const uint32_t kPrflxShift = 9;
+
+ uint32_t candBitmask = GetCandidateIpAndTransportMask(&cand);
+
+ // Note: shift values need to result in the above enum table
+ if (cand.mType.Value() == RTCStatsType::Localcandidate) {
+ candBitmask <<= kLocalShift;
+ }
+
+ if (cand.mCandidateType.Value() == RTCStatsIceCandidateType::Serverreflexive) {
+ candBitmask <<= kSrflxShift;
+ } else if (cand.mCandidateType.Value() == RTCStatsIceCandidateType::Relayed) {
+ candBitmask <<= kRelayShift;
+ } else if (cand.mCandidateType.Value() == RTCStatsIceCandidateType::Peerreflexive) {
+ candBitmask <<= kPrflxShift;
+ }
+
+ // Note: this is not a "component" in the ICE definition, this is really a
+ // stream ID. This is just the way the stats API is standardized right now.
+ // Very confusing.
+ std::string streamId(
+ NS_ConvertUTF16toUTF8(cand.mComponentId.Value()).get());
+
+ streamResults[streamId].candidateTypeBitpattern |= candBitmask;
+ }
+
+ for (auto i = streamResults.begin(); i != streamResults.end(); ++i) {
+ Telemetry::RecordWebrtcIceCandidates(i->second.candidateTypeBitpattern,
+ i->second.streamSucceeded);
+ }
+
+ // Beyond ICE, accumulate telemetry for various PER_CALL settings here.
+
+ if (query->report->mOutboundRTPStreamStats.WasPassed()) {
+ auto& array = query->report->mOutboundRTPStreamStats.Value();
+ for (decltype(array.Length()) i = 0; i < array.Length(); i++) {
+ auto& s = array[i];
+ bool isVideo = (s.mId.Value().Find("video") != -1);
+ if (!isVideo || s.mIsRemote) {
+ continue;
+ }
+ if (s.mBitrateMean.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_ENCODER_BITRATE_AVG_PER_CALL_KBPS,
+ uint32_t(s.mBitrateMean.Value() / 1000));
+ }
+ if (s.mBitrateStdDev.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_ENCODER_BITRATE_STD_DEV_PER_CALL_KBPS,
+ uint32_t(s.mBitrateStdDev.Value() / 1000));
+ }
+ if (s.mFramerateMean.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_ENCODER_FRAMERATE_AVG_PER_CALL,
+ uint32_t(s.mFramerateMean.Value()));
+ }
+ if (s.mFramerateStdDev.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_ENCODER_FRAMERATE_10X_STD_DEV_PER_CALL,
+ uint32_t(s.mFramerateStdDev.Value() * 10));
+ }
+ if (s.mDroppedFrames.WasPassed() && !query->iceStartTime.IsNull()) {
+ double mins = (TimeStamp::Now() - query->iceStartTime).ToSeconds() / 60;
+ if (mins > 0) {
+ Accumulate(WEBRTC_VIDEO_ENCODER_DROPPED_FRAMES_PER_CALL_FPM,
+ uint32_t(double(s.mDroppedFrames.Value()) / mins));
+ }
+ }
+ }
+ }
+
+ if (query->report->mInboundRTPStreamStats.WasPassed()) {
+ auto& array = query->report->mInboundRTPStreamStats.Value();
+ for (decltype(array.Length()) i = 0; i < array.Length(); i++) {
+ auto& s = array[i];
+ bool isVideo = (s.mId.Value().Find("video") != -1);
+ if (!isVideo || s.mIsRemote) {
+ continue;
+ }
+ if (s.mBitrateMean.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_DECODER_BITRATE_AVG_PER_CALL_KBPS,
+ uint32_t(s.mBitrateMean.Value() / 1000));
+ }
+ if (s.mBitrateStdDev.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_DECODER_BITRATE_STD_DEV_PER_CALL_KBPS,
+ uint32_t(s.mBitrateStdDev.Value() / 1000));
+ }
+ if (s.mFramerateMean.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_DECODER_FRAMERATE_AVG_PER_CALL,
+ uint32_t(s.mFramerateMean.Value()));
+ }
+ if (s.mFramerateStdDev.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_DECODER_FRAMERATE_10X_STD_DEV_PER_CALL,
+ uint32_t(s.mFramerateStdDev.Value() * 10));
+ }
+ if (s.mDiscardedPackets.WasPassed() && !query->iceStartTime.IsNull()) {
+ double mins = (TimeStamp::Now() - query->iceStartTime).ToSeconds() / 60;
+ if (mins > 0) {
+ Accumulate(WEBRTC_VIDEO_DECODER_DISCARDED_PACKETS_PER_CALL_PPM,
+ uint32_t(double(s.mDiscardedPackets.Value()) / mins));
+ }
+ }
+ }
+ }
+
+ // Finally, store the stats
+
+ PeerConnectionCtx *ctx = GetPeerConnectionCtx();
+ if (ctx) {
+ ctx->mStatsForClosedPeerConnections.AppendElement(*query->report, fallible);
+ }
+}
+
+static void GetStatsForLongTermStorage_s(
+ nsAutoPtr<RTCStatsQuery> query) {
+
+ MOZ_ASSERT(query);
+
+ nsresult rv = PeerConnectionImpl::ExecuteStatsQuery_s(query.get());
+
+ // Check whether packets were dropped due to rate limiting during
+ // this call. (These calls must be made on STS)
+ unsigned char rate_limit_bit_pattern = 0;
+ if (!mozilla::nr_socket_short_term_violation_time().IsNull() &&
+ !query->iceStartTime.IsNull() &&
+ mozilla::nr_socket_short_term_violation_time() >= query->iceStartTime) {
+ rate_limit_bit_pattern |= 1;
+ }
+ if (!mozilla::nr_socket_long_term_violation_time().IsNull() &&
+ !query->iceStartTime.IsNull() &&
+ mozilla::nr_socket_long_term_violation_time() >= query->iceStartTime) {
+ rate_limit_bit_pattern |= 2;
+ }
+
+ if (query->failed) {
+ Telemetry::Accumulate(
+ Telemetry::WEBRTC_STUN_RATE_LIMIT_EXCEEDED_BY_TYPE_GIVEN_FAILURE,
+ rate_limit_bit_pattern);
+ } else {
+ Telemetry::Accumulate(
+ Telemetry::WEBRTC_STUN_RATE_LIMIT_EXCEEDED_BY_TYPE_GIVEN_SUCCESS,
+ rate_limit_bit_pattern);
+ }
+
+ // Even if Telemetry::Accumulate is threadsafe, we still need to send the
+ // query back to main, since that is where it must be destroyed.
+ NS_DispatchToMainThread(
+ WrapRunnableNM(
+ &StoreLongTermICEStatisticsImpl_m,
+ rv,
+ query),
+ NS_DISPATCH_NORMAL);
+}
+
+void WebrtcGlobalInformation::StoreLongTermICEStatistics(
+ PeerConnectionImpl& aPc) {
+ Telemetry::Accumulate(Telemetry::WEBRTC_ICE_FINAL_CONNECTION_STATE,
+ static_cast<uint32_t>(aPc.IceConnectionState()));
+
+ if (aPc.IceConnectionState() == PCImplIceConnectionState::New) {
+ // ICE has not started; we won't have any remote candidates, so recording
+ // statistics on gathered candidates is pointless.
+ return;
+ }
+
+ nsAutoPtr<RTCStatsQuery> query(new RTCStatsQuery(true));
+
+ nsresult rv = aPc.BuildStatsQuery_m(nullptr, query.get());
+
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ RUN_ON_THREAD(aPc.GetSTSThread(),
+ WrapRunnableNM(&GetStatsForLongTermStorage_s,
+ query),
+ NS_DISPATCH_NORMAL);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.h b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.h
new file mode 100644
index 000000000..fb3789c20
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.h
@@ -0,0 +1,56 @@
+/* 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/. */
+
+#ifndef _WEBRTC_GLOBAL_INFORMATION_H_
+#define _WEBRTC_GLOBAL_INFORMATION_H_
+
+#include "nsString.h"
+#include "mozilla/dom/BindingDeclarations.h" // for Optional
+
+namespace mozilla {
+class PeerConnectionImpl;
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+class WebrtcGlobalStatisticsCallback;
+class WebrtcGlobalLoggingCallback;
+
+class WebrtcGlobalInformation
+{
+public:
+ static void GetAllStats(const GlobalObject& aGlobal,
+ WebrtcGlobalStatisticsCallback& aStatsCallback,
+ const Optional<nsAString>& pcIdFilter,
+ ErrorResult& aRv);
+
+ static void ClearAllStats(const GlobalObject& aGlobal);
+
+ static void GetLogging(const GlobalObject& aGlobal,
+ const nsAString& aPattern,
+ WebrtcGlobalLoggingCallback& aLoggingCallback,
+ ErrorResult& aRv);
+
+ static void ClearLogging(const GlobalObject& aGlobal);
+
+ static void SetDebugLevel(const GlobalObject& aGlobal, int32_t aLevel);
+ static int32_t DebugLevel(const GlobalObject& aGlobal);
+
+ static void SetAecDebug(const GlobalObject& aGlobal, bool aEnable);
+ static bool AecDebug(const GlobalObject& aGlobal);
+
+ static void StoreLongTermICEStatistics(PeerConnectionImpl& aPc);
+
+private:
+ WebrtcGlobalInformation() = delete;
+ WebrtcGlobalInformation(const WebrtcGlobalInformation& aOrig) = delete;
+ WebrtcGlobalInformation& operator=(
+ const WebrtcGlobalInformation& aRhs) = delete;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _WEBRTC_GLOBAL_INFORMATION_H_
diff --git a/media/webrtc/signaling/src/peerconnection/WebrtcGlobalParent.h b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalParent.h
new file mode 100644
index 000000000..4e2d0509f
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalParent.h
@@ -0,0 +1,53 @@
+/* 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/. */
+
+#ifndef _WEBRTC_GLOBAL_PARENT_H_
+#define _WEBRTC_GLOBAL_PARENT_H_
+
+#include "mozilla/dom/PWebrtcGlobalParent.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace dom {
+
+class WebrtcParents;
+
+class WebrtcGlobalParent
+ : public PWebrtcGlobalParent
+{
+ friend class ContentParent;
+ friend class WebrtcGlobalInformation;
+ friend class WebrtcContentParents;
+
+ bool mShutdown;
+
+ MOZ_IMPLICIT WebrtcGlobalParent();
+
+ static WebrtcGlobalParent* Alloc();
+ static bool Dealloc(WebrtcGlobalParent* aActor);
+
+ virtual bool RecvGetStatsResult(const int& aRequestId,
+ nsTArray<RTCStatsReportInternal>&& aStats) override;
+ virtual bool RecvGetLogResult(const int& aRequestId,
+ const WebrtcGlobalLog& aLog) override;
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+ virtual bool Recv__delete__() override;
+
+ virtual ~WebrtcGlobalParent();
+public:
+ NS_INLINE_DECL_REFCOUNTING(WebrtcGlobalParent)
+
+ bool IsActive()
+ {
+ return !mShutdown;
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _WEBRTC_GLOBAL_PARENT_H_