--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -683,16 +683,22 @@ DOMInterfaces = {
},
'PeerConnectionImpl': {
'nativeType': 'mozilla::PeerConnectionImpl',
'headerFile': 'PeerConnectionImpl.h',
'wrapperCache': False
},
+'TransceiverImpl': {
+ 'nativeType': 'mozilla::TransceiverImpl',
+ 'headerFile': 'TransceiverImpl.h',
+ 'wrapperCache': False
+},
+
'Plugin': {
'headerFile' : 'nsPluginArray.h',
'nativeType': 'nsPluginElement',
},
'PluginArray': {
'nativeType': 'nsPluginArray',
},
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -20,28 +20,30 @@ const PC_CONTRACT = "@mozilla.org/dom/pe
const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1";
const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
const PC_MANAGER_CONTRACT = "@mozilla.org/dom/peerconnectionmanager;1";
const PC_STATS_CONTRACT = "@mozilla.org/dom/rtcstatsreport;1";
const PC_STATIC_CONTRACT = "@mozilla.org/dom/peerconnectionstatic;1";
const PC_SENDER_CONTRACT = "@mozilla.org/dom/rtpsender;1";
const PC_RECEIVER_CONTRACT = "@mozilla.org/dom/rtpreceiver;1";
+const PC_TRANSCEIVER_CONTRACT = "@mozilla.org/dom/rtptransceiver;1";
const PC_COREQUEST_CONTRACT = "@mozilla.org/dom/createofferrequest;1";
const PC_DTMF_SENDER_CONTRACT = "@mozilla.org/dom/rtcdtmfsender;1";
const PC_CID = Components.ID("{bdc2e533-b308-4708-ac8e-a8bfade6d851}");
const PC_OBS_CID = Components.ID("{d1748d4c-7f6a-4dc5-add6-d55b7678537e}");
const PC_ICE_CID = Components.ID("{02b9970c-433d-4cc2-923d-f7028ac66073}");
const PC_SESSION_CID = Components.ID("{1775081b-b62d-4954-8ffe-a067bbf508a7}");
const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}");
const PC_STATS_CID = Components.ID("{7fe6e18b-0da3-4056-bf3b-440ef3809e06}");
const PC_STATIC_CID = Components.ID("{0fb47c47-a205-4583-a9fc-cbadf8c95880}");
const PC_SENDER_CID = Components.ID("{4fff5d46-d827-4cd4-a970-8fd53977440e}");
const PC_RECEIVER_CID = Components.ID("{d974b814-8fde-411c-8c45-b86791b81030}");
+const PC_TRANSCEIVER_CID = Components.ID("{09475754-103a-41f5-a2d0-e1f27eb0b537}");
const PC_COREQUEST_CID = Components.ID("{74b2122d-65a8-4824-aa9e-3d664cb75dc2}");
const PC_DTMF_SENDER_CID = Components.ID("{3610C242-654E-11E6-8EC0-6D1BE389A607}");
function logMsg(msg, file, line, flag, winID) {
let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
scriptError.initWithWindowID(msg, file, null, line, 0, flag,
"content javascript", winID);
@@ -143,19 +145,17 @@ class GlobalPCList {
this.handleGMPCrash(data);
}
}
observe(subject, topic, data) {
let cleanupPcRef = function(pcref) {
let pc = pcref.get();
if (pc) {
- pc._pc.close();
- delete pc._observer;
- pc._pc = null;
+ pc.close();
}
};
let cleanupWinId = function(list, winID) {
if (list.hasOwnProperty(winID)) {
list[winID].forEach(cleanupPcRef);
delete list[winID];
}
@@ -338,18 +338,18 @@ setupPrototype(RTCStatsReport, {
"candidate-pair": "candidatepair",
"local-candidate": "localcandidate",
"remote-candidate": "remotecandidate"
}
});
class RTCPeerConnection {
constructor() {
- this._senders = [];
- this._receivers = [];
+ this._receiveStreams = new Map();
+ this._transceivers = [];
this._pc = null;
this._closed = false;
this._localType = null;
this._remoteType = null;
// http://rtcweb-wg.github.io/jsep/#rfc.section.4.1.9
// canTrickle == null means unknown; when a remote description is received it
@@ -580,16 +580,29 @@ class RTCPeerConnection {
try {
wrapCallback(onSucc)(await func());
} catch (e) {
wrapCallback(onErr)(e);
}
}
+ // This implements the fairly common "Queue a task" logic
+ async _queueTaskWithClosedCheck(func) {
+ let pc = this;
+ return new this._win.Promise(resolve => {
+ Services.tm.dispatchToMainThread({ run() {
+ if (!pc._closed) {
+ func();
+ resolve();
+ }
+ }});
+ });
+ }
+
/**
* An RTCConfiguration may look like this:
*
* { "iceServers": [ { urls: "stun:stun.example.org", },
* { url: "stun:stun.example.org", }, // deprecated version
* { urls: ["turn:turn1.x.org", "turn:turn2.x.org"],
* username:"jib", credential:"mypass"} ] }
*
@@ -673,16 +686,25 @@ class RTCPeerConnection {
// spec. See Bug 831756.
_checkClosed() {
if (this._closed) {
throw new this._win.DOMException("Peer connection is closed",
"InvalidStateError");
}
}
+ _getTransceiverWithSender(sender) {
+ let transceiver = this._transceivers.find(t => t.sender == sender);
+ if (!transceiver) {
+ throw new this._win.DOMException("This isn't one of my senders!",
+ "InvalidAccessError");
+ }
+ return transceiver;
+ }
+
dispatchEvent(event) {
// PC can close while events are firing if there is an async dispatch
// in c++ land. But let through "closed" signaling and ice connection events.
if (!this._closed || this._inClose) {
this.__DOM_IMPL__.dispatchEvent(event);
}
}
@@ -743,23 +765,87 @@ class RTCPeerConnection {
set(h) {
this.logWarning(name + " is deprecated! " + msg);
return this.setEH(name, h);
}
});
}
createOffer(optionsOrOnSucc, onErr, options) {
+ // Spec language implies that this needs to happen as if it were called
+ // before createOffer, so we do this as early as possible.
+ this._ensureTransceiversForOfferToReceive(optionsOrOnSucc);
+
// This entry-point handles both new and legacy call sig. Decipher which one
if (typeof optionsOrOnSucc == "function") {
return this._legacy(optionsOrOnSucc, onErr, () => this._createOffer(options));
}
return this._async(() => this._createOffer(optionsOrOnSucc));
}
+ _enableReceive(transceiver) {
+ if (transceiver.direction == "sendonly") {
+ transceiver.setDirection("sendrecv");
+ return true;
+ } else if (transceiver.direction == "inactive") {
+ transceiver.setDirection("recvonly");
+ return true;
+ }
+ return false;
+ }
+
+ _enableSend(transceiver) {
+ if (transceiver.direction == "recvonly") {
+ transceiver.setDirection("sendrecv");
+ return true;
+ } else if (transceiver.direction == "inactive") {
+ transceiver.setDirection("sendonly");
+ return true;
+ }
+ return false;
+ }
+
+ _disableSend(transceiver) {
+ if (transceiver.direction == "sendrecv") {
+ transceiver.setDirection("recvonly");
+ return true;
+ } else if (transceiver.direction == "sendonly") {
+ transceiver.setDirection("inactive");
+ return true;
+ }
+ return false;
+ }
+
+ // This ensures there are at least |count| |kind| transceivers that are
+ // configured to receive. It will create transceivers if necessary.
+ _applyOfferToReceive(kind, count) {
+ this._transceivers.forEach(transceiver => {
+ if (count && transceiver.getKind() == kind && !transceiver.stopped) {
+ this._enableReceive(transceiver);
+ count--;
+ }
+ });
+
+ while (count) {
+ this._addTransceiverNoEvents(kind, {direction: "recvonly"});
+ --count;
+ }
+ }
+
+ // Handles legacy offerToReceiveAudio/Video
+ _ensureTransceiversForOfferToReceive(options) {
+ if (options.offerToReceiveVideo) {
+ this._applyOfferToReceive("video", options.offerToReceiveVideo);
+ }
+
+ if (options.offerToReceiveAudio) {
+ this._applyOfferToReceive("audio", options.offerToReceiveAudio);
+ }
+ }
+
async _createOffer(options) {
this._checkClosed();
let origin = Cu.getWebIDLCallerPrincipal().origin;
return this._chain(async () => {
let haveAssertion;
if (this._localIdp.enabled) {
haveAssertion = this._getIdentityAssertion(origin);
}
@@ -1051,128 +1137,238 @@ class RTCPeerConnection {
stream.getTracks().forEach(track => this.addTrack(track, stream));
}
addTrack(track, stream) {
if (stream.currentTime === undefined) {
throw new this._win.DOMException("invalid stream.", "InvalidParameterError");
}
this._checkClosed();
- this._senders.forEach(sender => {
- if (sender.track == track) {
- throw new this._win.DOMException("already added.",
- "InvalidParameterError");
- }
+
+ if (this._transceivers.some(
+ transceiver => transceiver.sender.track === track)) {
+ throw new this._win.DOMException("This track is already set on a sender.",
+ "InvalidAccessError");
+ }
+
+ let transceiver = this._transceivers.find(transceiver => {
+ return transceiver.sender.track === null &&
+ transceiver.getKind() === track.kind &&
+ !transceiver.stopped &&
+ !transceiver.hasBeenUsedToSend();
});
- this._impl.addTrack(track, stream);
- let sender = this._win.RTCRtpSender._create(this._win,
- new RTCRtpSender(this, track,
- stream));
- this._senders.push(sender);
- return sender;
+
+ if (transceiver) {
+ transceiver.sender.setTrack(track);
+ transceiver.sender.streams = [stream];
+ this._enableSend(transceiver);
+ } else {
+ transceiver = this._addTransceiverNoEvents(
+ track,
+ {
+ streams: [stream],
+ direction: "sendrecv"
+ });
+ }
+
+ transceiver.setAddTrackMagic();
+ transceiver.sync();
+ this.updateNegotiationNeeded();
+ return transceiver.sender;
}
removeTrack(sender) {
this._checkClosed();
- var i = this._senders.indexOf(sender);
- if (i >= 0) {
- this._senders.splice(i, 1);
- this._impl.removeTrack(sender.track); // fires negotiation needed
+
+ sender.checkWasCreatedByPc(this.__DOM_IMPL__);
+
+ // If the transceiver was removed due to rollback, let it slide.
+ let transceiver;
+ try {
+ transceiver = this._getTransceiverWithSender(sender);
+ } catch (e) {
+ return;
+ }
+
+ // TODO(bug XXXXX): Handle in TransceiverImpl::SyncWithJS?
+ this._impl.removeTrack(sender.track);
+
+ sender.setTrack(null);
+ if (this._disableSend(transceiver)) {
+ transceiver.sync();
+ this.updateNegotiationNeeded();
}
}
- _insertDTMF(sender, tones, duration, interToneGap) {
- return this._impl.insertDTMF(sender.__DOM_IMPL__, tones, duration, interToneGap);
+ mozGetWebrtcTrackId(track) {
+ let matchingTransceiver = this._transceivers.find(
+ transceiver => transceiver.receiver.track == track);
+ if (!matchingTransceiver) {
+ return null;
+ }
+
+ return matchingTransceiver.receiver.webrtcTrackId;
+ }
+
+ _addTransceiverNoEvents(sendTrackOrKind, init) {
+ let kind = "";
+ let sendTrack = null;
+ if (typeof(sendTrackOrKind) == "string") {
+ kind = sendTrackOrKind;
+ switch (kind) {
+ case "audio":
+ case "video":
+ break;
+ default:
+ throw new this._win.TypeError("Invalid media kind");
+ }
+ } else {
+ sendTrack = sendTrackOrKind;
+ kind = sendTrack.kind;
+ }
+
+ let transceiverImpl = this._impl.createTransceiverImpl(kind, sendTrack);
+ let transceiver = this._win.RTCRtpTransceiver._create(
+ this._win,
+ new RTCRtpTransceiver(this, transceiverImpl, init, kind, sendTrack));
+ transceiver.sync();
+ this._transceivers.push(transceiver);
+ return transceiver;
+ }
+
+ _onTransceiverNeeded(kind, transceiverImpl) {
+ let init = {direction: "recvonly"};
+ let transceiver = this._win.RTCRtpTransceiver._create(
+ this._win,
+ new RTCRtpTransceiver(this, transceiverImpl, init, kind, null));
+ // We don't sync this transceiver here; we wait until SRD finishes
+ this._transceivers.push(transceiver);
+ }
+
+ addTransceiver(sendTrackOrKind, init) {
+ let transceiver = this._addTransceiverNoEvents(sendTrackOrKind, init);
+ this.updateNegotiationNeeded();
+ return transceiver;
+ }
+
+ _syncTransceivers() {
+ this._transceivers.forEach(transceiver => transceiver.sync());
+ }
+
+ clearNegotiationNeeded() {
+ this._negotiationNeeded = false;
+ }
+
+ updateNegotiationNeeded() {
+ this._checkClosed();
+ if (this.signalingState != "stable") {
+ return;
+ }
+
+ let negotiationNeeded = this._impl.checkNegotiationNeeded();
+ if (!negotiationNeeded) {
+ this.clearNegotiationNeeded();
+ return;
+ }
+
+ if (this._negotiationNeeded) {
+ return;
+ }
+
+ this._negotiationNeeded = true;
+
+ this._queueTaskWithClosedCheck(() => {
+ if (this._negotiationNeeded) {
+ this.dispatchEvent(new this._win.Event("negotiationneeded"));
+ }
+ });
+ }
+
+ _getOrCreateStream(id) {
+ if (!this._receiveStreams.has(id)) {
+ let stream = new this._win.MediaStream();
+ stream.assignId(id);
+ // Legacy event, remove eventually
+ let ev = new this._win.MediaStreamEvent("addstream", { stream });
+ this.dispatchEvent(ev);
+ this._receiveStreams.set(id, stream);
+ }
+
+ return this._receiveStreams.get(id);
+ }
+
+ _insertDTMF(transceiverImpl, tones, duration, interToneGap) {
+ return this._impl.insertDTMF(transceiverImpl, tones, duration, interToneGap);
}
_getDTMFToneBuffer(sender) {
return this._impl.getDTMFToneBuffer(sender.__DOM_IMPL__);
}
- async _replaceTrack(sender, withTrack) {
+ async _replaceTrack(transceiverImpl, withTrack) {
this._checkClosed();
- return this._chain(() => new Promise((resolve, reject) => {
- this._onReplaceTrackSender = sender;
- this._onReplaceTrackWithTrack = withTrack;
+
+ return new Promise((resolve, reject) => {
this._onReplaceTrackSuccess = resolve;
this._onReplaceTrackFailure = reject;
- this._impl.replaceTrack(sender.track, withTrack);
- }));
- }
-
- _setParameters({ track }, parameters) {
- if (!Services.prefs.getBoolPref("media.peerconnection.simulcast")) {
- return;
- }
- // validate parameters input
- var encodings = parameters.encodings || [];
-
- encodings.reduce((uniqueRids, { rid, scaleResolutionDownBy }) => {
- if (scaleResolutionDownBy < 1.0) {
- throw new this._win.RangeError("scaleResolutionDownBy must be >= 1.0");
- }
- if (!rid && encodings.length > 1) {
- throw new this._win.DOMException("Missing rid", "TypeError");
- }
- if (uniqueRids[rid]) {
- throw new this._win.DOMException("Duplicate rid", "TypeError");
- }
- uniqueRids[rid] = true;
- return uniqueRids;
- }, {});
-
- this._impl.setParameters(track, parameters);
- }
-
- _getParameters({ track }) {
- if (!Services.prefs.getBoolPref("media.peerconnection.simulcast")) {
- return null;
- }
- return this._impl.getParameters(track);
+ this._impl.replaceTrackNoRenegotiation(transceiverImpl, withTrack);
+ });
}
close() {
if (this._closed) {
return;
}
this._closed = true;
this._inClose = true;
this.changeIceConnectionState("closed");
this._localIdp.close();
this._remoteIdp.close();
this._impl.close();
this._inClose = false;
+ delete this._pc;
+ delete this._observer;
}
getLocalStreams() {
this._checkClosed();
- return this._impl.getLocalStreams();
+ let localStreams = new Set();
+ this._transceivers.forEach(transceiver => {
+ transceiver.sender.mozGetStreams().forEach(stream => {
+ localStreams.add(stream);
+ });
+ });
+ return [...localStreams.values()];
}
getRemoteStreams() {
this._checkClosed();
- return this._impl.getRemoteStreams();
+ return [...this._receiveStreams.values()];
}
getSenders() {
- return this._senders;
+ return this.getTransceivers().map(transceiver => transceiver.sender);
}
getReceivers() {
- return this._receivers;
+ return this.getTransceivers().map(transceiver => transceiver.receiver);
}
mozAddRIDExtension(receiver, extensionId) {
this._impl.addRIDExtension(receiver.track, extensionId);
}
mozAddRIDFilter(receiver, rid) {
this._impl.addRIDFilter(receiver.track, rid);
}
+ getTransceivers() {
+ return this._transceivers;
+ }
+
get localDescription() {
this._checkClosed();
let sdp = this._impl.localDescription;
if (sdp.length == 0) {
return null;
}
return new this._win.RTCSessionDescription({ type: this._localType, sdp });
}
@@ -1300,19 +1496,27 @@ class RTCPeerConnection {
if (maxPacketLifeTime) {
type = Ci.IPeerConnection.kDataChannelPartialReliableTimed;
} else if (maxRetransmits) {
type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit;
} else {
type = Ci.IPeerConnection.kDataChannelReliable;
}
// Synchronous since it doesn't block.
- return this._impl.createDataChannel(label, protocol, type, ordered,
- maxPacketLifeTime, maxRetransmits,
- negotiated, id);
+ let dataChannel =
+ this._impl.createDataChannel(label, protocol, type, ordered,
+ maxPacketLifeTime, maxRetransmits,
+ negotiated, id);
+
+ // Spec says to do this in the background. Close enough.
+ this._queueTaskWithClosedCheck(() => {
+ this.updateNegotiationNeeded();
+ });
+
+ return dataChannel;
}
}
setupPrototype(RTCPeerConnection, {
classID: PC_CID,
contractID: PC_CONTRACT,
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
Ci.nsIDOMGlobalPropertyInitializer]),
_actions: {
@@ -1368,20 +1572,26 @@ class PeerConnectionObserver {
this._dompc._onCreateAnswerSuccess(sdp);
}
onCreateAnswerError(code, message) {
this._dompc._onCreateAnswerFailure(this.newError(message, code));
}
onSetLocalDescriptionSuccess() {
+ this._dompc._syncTransceivers();
+ this._dompc.clearNegotiationNeeded();
+ this._dompc.updateNegotiationNeeded();
this._dompc._onSetLocalDescriptionSuccess();
}
onSetRemoteDescriptionSuccess() {
+ this._dompc._syncTransceivers();
+ this._dompc.clearNegotiationNeeded();
+ this._dompc.updateNegotiationNeeded();
this._dompc._onSetRemoteDescriptionSuccess();
}
onSetLocalDescriptionError(code, message) {
this._localType = null;
this._dompc._onSetLocalDescriptionFailure(this.newError(message, code));
}
@@ -1408,20 +1618,16 @@ class PeerConnectionObserver {
} else {
candidate = null;
}
this.dispatchEvent(new win.RTCPeerConnectionIceEvent("icecandidate",
{ candidate }));
}
- onNegotiationNeeded() {
- this.dispatchEvent(new this._win.Event("negotiationneeded"));
- }
-
// This method is primarily responsible for updating iceConnectionState.
// This state is defined in the WebRTC specification as follows:
//
// iceConnectionState:
// -------------------
// new Any of the RTCIceTransports are in the new state and none
// of them are in the checking, failed or disconnected state.
//
@@ -1543,71 +1749,97 @@ class PeerConnectionObserver {
pc._onGetStatsIsLegacy);
pc._onGetStatsSuccess(webidlobj);
}
onGetStatsError(code, message) {
this._dompc._onGetStatsFailure(this.newError(message, code));
}
- onAddStream(stream) {
- let ev = new this._dompc._win.MediaStreamEvent("addstream", { stream });
- this.dispatchEvent(ev);
- }
-
onRemoveStream(stream) {
this.dispatchEvent(new this._dompc._win.MediaStreamEvent("removestream",
{ stream }));
}
- onAddTrack(track, streams) {
+ _getTransceiverWithRecvTrack(webrtcTrackId) {
+ return this._dompc.getTransceivers().find(
+ transceiver => transceiver.receiver.webrtcTrackId == webrtcTrackId);
+ }
+
+ onTrack(webrtcTrackId, streamIds) {
let pc = this._dompc;
- let receiver = pc._win.RTCRtpReceiver._create(pc._win,
- new RTCRtpReceiver(pc,
- track));
- pc._receivers.push(receiver);
- let ev = new pc._win.RTCTrackEvent("track", { receiver, track, streams });
+ let matchingTransceiver = this._getTransceiverWithRecvTrack(webrtcTrackId);
+ if (!matchingTransceiver) {
+ throw new pc._win.DOMException(
+ "No transceiver with receive track " + webrtcTrackId,
+ "InternalError");
+ }
+
+ // Get or create MediaStreams, and add the new track to them.
+ let streams = streamIds.map(id => this._dompc._getOrCreateStream(id));
+
+ if (!streams.length) {
+ throw new pc._win.DOMException(
+ "No streams for receive track " + webrtcTrackId,
+ "InternalError");
+ }
+
+ streams.forEach(
+ stream => {
+ stream.addTrack(matchingTransceiver.receiver.track);
+ // Adding tracks from JS does not result in the stream getting
+ // onaddtrack, so we need to do that here. The mediacapture spec says
+ // this needs to be queued, also.
+ pc._queueTaskWithClosedCheck(() => {
+ stream.dispatchEvent(
+ new pc._win.MediaStreamTrackEvent(
+ "addtrack", { track: matchingTransceiver.receiver.track }));
+ });
+ });
+
+
+ let ev = new pc._win.RTCTrackEvent("track", {
+ receiver: matchingTransceiver.receiver,
+ track: matchingTransceiver.receiver.track,
+ streams,
+ transceiver: matchingTransceiver });
+ // TODO(bug XXXXX): Queue a task for this, and fix the tests, unless the
+ // spec changes.
this.dispatchEvent(ev);
// Fire legacy event as well for a little bit.
- ev = new pc._win.MediaStreamTrackEvent("addtrack", { track });
+ ev = new pc._win.MediaStreamTrackEvent("addtrack",
+ { track: matchingTransceiver.receiver.track });
+ // TODO(bug XXXXX): Queue a task for this, and fix the tests, unless the
+ // spec changes.
this.dispatchEvent(ev);
}
- onRemoveTrack(track) {
- let pc = this._dompc;
- let i = pc._receivers.findIndex(receiver => receiver.track == track);
- if (i >= 0) {
- pc._receivers.splice(i, 1);
- }
+ onTransceiverNeeded(kind, transceiverImpl) {
+ this._dompc._onTransceiverNeeded(kind, transceiverImpl);
}
onReplaceTrackSuccess() {
var pc = this._dompc;
- pc._onReplaceTrackSender.track = pc._onReplaceTrackWithTrack;
- pc._onReplaceTrackWithTrack = null;
- pc._onReplaceTrackSender = null;
pc._onReplaceTrackSuccess();
}
onReplaceTrackError(code, message) {
var pc = this._dompc;
- pc._onReplaceTrackWithTrack = null;
- pc._onReplaceTrackSender = null;
pc._onReplaceTrackFailure(this.newError(message, code));
}
notifyDataChannel(channel) {
this.dispatchEvent(new this._dompc._win.RTCDataChannelEvent("datachannel",
{ channel }));
}
- onDTMFToneChange(trackId, tone) {
+ onDTMFToneChange(track, tone) {
var pc = this._dompc;
- var sender = pc._senders.find(({track}) => track.id == trackId);
+ var sender = pc.getSenders().find(sender => sender.track == track);
sender.dtmf.dispatchEvent(new pc._win.RTCDTMFToneChangeEvent("tonechange",
{ tone }));
}
}
setupPrototype(PeerConnectionObserver, {
classID: PC_OBS_CID,
contractID: PC_OBS_CONTRACT,
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
@@ -1646,86 +1878,326 @@ class RTCDTMFSender {
set ontonechange(handler) {
this.__DOM_IMPL__.setEventHandler("ontonechange", handler);
}
insertDTMF(tones, duration, interToneGap) {
this._sender._pc._checkClosed();
- if (this._sender._pc._senders.indexOf(this._sender.__DOM_IMPL__) == -1) {
- throw new this._sender._pc._win.DOMException("RTCRtpSender is stopped",
- "InvalidStateError");
- }
-
- duration = Math.max(40, Math.min(duration, 6000));
- if (interToneGap < 30) interToneGap = 30;
+ let transceiver =
+ this._sender._pc._getTransceiverWithSender(this._sender.__DOM_IMPL__);
- tones = tones.toUpperCase();
-
- if (tones.match(/[^0-9A-D#*,]/)) {
- throw new this._sender._pc._win.DOMException("Invalid DTMF characters",
- "InvalidCharacterError");
- }
-
- this._sender._pc._insertDTMF(this._sender, tones, duration, interToneGap);
+ transceiver.insertDTMF(tones, duration, interToneGap);
}
}
setupPrototype(RTCDTMFSender, {
classID: PC_DTMF_SENDER_CID,
contractID: PC_DTMF_SENDER_CONTRACT,
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
});
class RTCRtpSender {
- constructor(pc, track, stream) {
- let dtmf = pc._win.RTCDTMFSender._create(pc._win, new RTCDTMFSender(this));
- Object.assign(this, { _pc: pc, track, _stream: stream, dtmf });
+ constructor(pc, transceiverImpl, track, streams) {
+ let dtmf = pc._win.RTCDTMFSender._create(
+ pc._win, new RTCDTMFSender(this));
+
+ Object.assign(this, {
+ _pc: pc,
+ _transceiverImpl: transceiverImpl,
+ track,
+ _streams: streams,
+ dtmf });
}
replaceTrack(withTrack) {
- return this._pc._async(() => this._pc._replaceTrack(this, withTrack));
+ // async functions in here return a chrome promise, which is not something
+ // content can use. This wraps that promise in something content can use.
+ return this._pc._win.Promise.resolve(this._replaceTrack(withTrack));
+ }
+
+ async _replaceTrack(withTrack) {
+ this._pc._checkClosed();
+
+ let transceiver = this._pc._getTransceiverWithSender(this.__DOM_IMPL__);
+
+ if (transceiver.stopped) {
+ throw new this._pc._win.DOMException(
+ "Cannot call replaceTrack when transceiver is stopped",
+ "InvalidStateError");
+ }
+
+ if (withTrack && (withTrack.kind != transceiver.getKind())) {
+ throw new this._pc._win.DOMException(
+ "Cannot replaceTrack with a different kind!",
+ "TypeError");
+ }
+
+ // TODO (bug XXXXX): Technically, if the transceiver is not yet associated,
+ // we're supposed to synchronously set the track and return a resolved
+ // promise. However, PeerConnectionImpl::ReplaceTrackNoNegotiation does
+ // stuff like updating principal change observers. We might want to set
+ // that stuff up later than CreateTransceiverInternal so we don't need to
+ // do this for tracks that haven't sent anything yet.
+
+ await this._pc._replaceTrack(this._transceiverImpl, withTrack);
+
+ // We're supposed to queue a task to do these next steps.
+ await this._pc._queueTaskWithClosedCheck(() => {
+ this.track = withTrack;
+ transceiver.sync();
+ });
}
setParameters(parameters) {
- return this._pc._win.Promise.resolve()
- .then(() => this._pc._setParameters(this, parameters));
+ let copy = Object.create(parameters);
+
+ return this._pc._win.Promise.resolve(this._setParameters(copy));
+ }
+
+ async _setParameters(parameters) {
+ this._pc._checkClosed();
+
+ if (!Services.prefs.getBoolPref("media.peerconnection.simulcast")) {
+ return;
+ }
+
+ parameters.encodings = parameters.encodings || [];
+
+ parameters.encodings.reduce((uniqueRids, { rid, scaleResolutionDownBy }) => {
+ if (scaleResolutionDownBy < 1.0) {
+ throw new this._pc._win.RangeError("scaleResolutionDownBy must be >= 1.0");
+ }
+ if (!rid && parameters.encodings.length > 1) {
+ throw new this._pc._win.DOMException("Missing rid", "TypeError");
+ }
+ if (uniqueRids[rid]) {
+ throw new this._pc._win.DOMException("Duplicate rid", "TypeError");
+ }
+ uniqueRids[rid] = true;
+ return uniqueRids;
+ }, {});
+
+ let transceiver = this._pc._getTransceiverWithSender(this.__DOM_IMPL__);
+
+ if (transceiver.stopped) {
+ throw new this._pc._win.DOMException(
+ "This sender's transceiver is stopped", "InvalidStateError");
+ }
+
+ // TODO: transaction ids
+
+ // Spec says this stuff needs to be done async. May change.
+ await this._pc._queueTaskWithClosedCheck(() => {
+ this.parameters = parameters;
+ transceiver.sync();
+ });
}
getParameters() {
- return this._pc._getParameters(this);
+ // TODO: transaction ids
+
+ // All the other stuff that the spec says to update is handled when
+ // transceivers are synced.
+ return this.parameters;
+ }
+
+ mozGetStreams() {
+ return this._streams;
+ }
+
+ setTrack(track) {
+ this.track = track;
}
getStats() {
return this._pc._async(
async () => this._pc._getStats(this.track));
}
+
+ checkWasCreatedByPc(pc) {
+ if (pc != this._pc.__DOM_IMPL__) {
+ throw new this._pc._win.DOMException(
+ "This sender was not created by this PeerConnection",
+ "InvalidAccessError");
+ }
+ }
}
setupPrototype(RTCRtpSender, {
classID: PC_SENDER_CID,
contractID: PC_SENDER_CONTRACT,
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
});
class RTCRtpReceiver {
- constructor(pc, track) {
- Object.assign(this, { _pc: pc, track });
+ constructor(pc, transceiverImpl) {
+ // We do not set the track here; that is done when _transceiverImpl is set
+ Object.assign(this,
+ {
+ _pc: pc,
+ _transceiverImpl: transceiverImpl,
+ track: transceiverImpl.getReceiveTrack(),
+ webrtcTrackId: ""
+ });
}
+ // TODO(bug XXXXX): Create a getStats binding on TransceiverImpl, and use
+ // that here.
getStats() {
return this._pc._async(
async () => this._pc.getStats(this.track));
}
}
setupPrototype(RTCRtpReceiver, {
classID: PC_RECEIVER_CID,
contractID: PC_RECEIVER_CONTRACT,
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
});
+class RTCRtpTransceiver {
+ constructor(pc, transceiverImpl, init, kind, sendTrack) {
+ let receiver = pc._win.RTCRtpReceiver._create(
+ pc._win, new RTCRtpReceiver(pc, transceiverImpl, kind));
+ let streams = (init && init.streams) || [];
+ let sender = pc._win.RTCRtpSender._create(
+ pc._win, new RTCRtpSender(pc, transceiverImpl, sendTrack, streams));
+
+ let direction = (init && init.direction) || "sendrecv";
+ Object.assign(this,
+ {
+ _pc: pc,
+ mid: null,
+ sender,
+ receiver,
+ stopped: false,
+ direction,
+ currentDirection: null,
+ addTrackMagic: false,
+ _hasBeenUsedToSend: false,
+ // the receiver starts out without a track, so record this here
+ _kind: kind,
+ _transceiverImpl: transceiverImpl
+ });
+ }
+
+ setDirection(direction) {
+ if (this.direction == direction) {
+ return;
+ }
+
+ this.direction = direction;
+ this.sync();
+ this._pc.updateNegotiationNeeded();
+ }
+
+ stop() {
+ if (this.stopped) {
+ return;
+ }
+
+ this.setStopped();
+ this.sync();
+ this._pc.updateNegotiationNeeded();
+ }
+
+ setStopped() {
+ this.stopped = true;
+ this.currentDirection = null;
+ }
+
+ remove() {
+ var index = this._pc._transceivers.indexOf(this.__DOM_IMPL__);
+ if (index != -1) {
+ this._pc._transceivers.splice(index, 1);
+ }
+ }
+
+ getKind() {
+ return this._kind;
+ }
+
+ hasBeenUsedToSend() {
+ return this._hasBeenUsedToSend;
+ }
+
+ setAddTrackMagic() {
+ this.addTrackMagic = true;
+ }
+
+ sync() {
+ if (this._syncing) {
+ throw new this._pc._win.DOMException(
+ "Reentrant sync! This is a bug!",
+ "InternalError");
+ }
+ this._syncing = true;
+ this._transceiverImpl.syncWithJS(this.__DOM_IMPL__);
+ this._syncing = false;
+ }
+
+ // Used by _transceiverImpl.syncWithJS, don't call sync again!
+ setCurrentDirection(direction) {
+ if (this.stopped) {
+ return;
+ }
+
+ switch (direction) {
+ case "sendrecv":
+ case "sendonly":
+ this._hasBeenUsedToSend = true;
+ break;
+ default:
+ }
+
+ this.currentDirection = direction;
+ }
+
+ // Used by _transceiverImpl.syncWithJS, don't call sync again!
+ setMid(mid) {
+ this.mid = mid;
+ }
+
+ // Used by _transceiverImpl.syncWithJS, don't call sync again!
+ unsetMid() {
+ this.mid = null;
+ }
+
+ insertDTMF(tones, duration, interToneGap) {
+ if (this.stopped) {
+ throw new this._pc._win.DOMException("Transceiver is stopped!",
+ "InvalidStateError");
+ }
+
+ if (!this.sender.track) {
+ throw new this._pc._win.DOMException("RTCRtpSender has no track",
+ "InvalidStateError");
+ }
+
+ duration = Math.max(40, Math.min(duration, 6000));
+ if (interToneGap < 30) interToneGap = 30;
+
+ tones = tones.toUpperCase();
+
+ if (tones.match(/[^0-9A-D#*,]/)) {
+ throw new this._pc._win.DOMException("Invalid DTMF characters",
+ "InvalidCharacterError");
+ }
+
+ // TODO (bug XXXXX): Move this API to TransceiverImpl so we don't need the
+ // extra hops through RTCPeerConnection and PeerConnectionImpl
+ this._pc._insertDTMF(this._transceiverImpl, tones, duration, interToneGap);
+ }
+}
+
+setupPrototype(RTCRtpTransceiver, {
+ classID: PC_TRANSCEIVER_CID,
+ contractID: PC_TRANSCEIVER_CONTRACT,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
+});
+
class CreateOfferRequest {
constructor(windowID, innerWindowID, callID, isSecure) {
Object.assign(this, { windowID, innerWindowID, callID, isSecure });
}
}
setupPrototype(CreateOfferRequest, {
classID: PC_COREQUEST_CID,
contractID: PC_COREQUEST_CONTRACT,
@@ -1736,12 +2208,13 @@ this.NSGetFactory = XPCOMUtils.generateN
[GlobalPCList,
RTCDTMFSender,
RTCIceCandidate,
RTCSessionDescription,
RTCPeerConnection,
RTCPeerConnectionStatic,
RTCRtpReceiver,
RTCRtpSender,
+ RTCRtpTransceiver,
RTCStatsReport,
PeerConnectionObserver,
CreateOfferRequest]
);
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -87,16 +87,17 @@ skip-if = toolkit == 'android' # no scre
[test_getUserMedia_stopVideoStream.html]
[test_getUserMedia_stopVideoStreamWithFollowupVideo.html]
[test_getUserMedia_trackCloneCleanup.html]
[test_getUserMedia_trackEnded.html]
[test_getUserMedia_peerIdentity.html]
[test_peerConnection_addIceCandidate.html]
[test_peerConnection_addtrack_removetrack_events.html]
skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
+[test_peerConnection_transceivers.html]
[test_peerConnection_basicAudio.html]
skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
[test_peerConnection_basicAudioNATSrflx.html]
skip-if = toolkit == 'android' # websockets don't work on android (bug 1266217)
[test_peerConnection_basicAudioNATRelay.html]
skip-if = toolkit == 'android' # websockets don't work on android (bug 1266217)
[test_peerConnection_basicAudioNATRelayTCP.html]
skip-if = toolkit == 'android' # websockets don't work on android (bug 1266217)
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -145,17 +145,17 @@ PeerConnectionTest.prototype.closePC = f
Promise.all(pc._pc.getReceivers()
.filter(receiver => receiver.track.readyState == "live")
.map(receiver => {
info("Waiting for track " + receiver.track.id + " (" +
receiver.track.kind + ") to end.");
return haveEvent(receiver.track, "ended", wait(50000))
.then(event => {
is(event.target, receiver.track, "Event target should be the correct track");
- info("ended fired for track " + receiver.track.id);
+ info(pc + " ended fired for track " + receiver.track.id);
}, e => e ? Promise.reject(e)
: ok(false, "ended never fired for track " +
receiver.track.id));
}))
]);
pc.close();
return promise;
};
@@ -756,18 +756,20 @@ function PeerConnectionWrapper(label, co
this._local_ice_candidates = [];
this._remote_ice_candidates = [];
this.localRequiresTrickleIce = false;
this.remoteRequiresTrickleIce = false;
this.localMediaElements = [];
this.remoteMediaElements = [];
this.audioElementsOnly = false;
+ this._sendStreams = [];
+
this.expectedLocalTrackInfoById = {};
- this.expectedRemoteTrackInfoById = {};
+ this.expectedSignalledTrackInfoById = {};
this.observedRemoteTrackInfoById = {};
this.disableRtpCountChecking = false;
this.iceConnectedResolve;
this.iceConnectedReject;
this.iceConnected = new Promise((resolve, reject) => {
this.iceConnectedResolve = resolve;
@@ -890,36 +892,64 @@ PeerConnectionWrapper.prototype = {
get iceConnectionState() {
return this._pc.iceConnectionState;
},
setIdentityProvider: function(provider, protocol, identity) {
this._pc.setIdentityProvider(provider, protocol, identity);
},
- ensureMediaElement : function(track, direction) {
+ getMediaElementForTrack : (track, direction) =>
+ {
const idPrefix = [this.label, direction].join('_');
- var element = getMediaElementForTrack(track, idPrefix);
+ return getMediaElementForTrack(track, idPrefix);
+ },
+ createMediaElementForTrack : (track, direction) =>
+ {
+ const idPrefix = [this.label, direction].join('_');
+ return createMediaElementForTrack(track, idPrefix);
+ },
+
+ ensureMediaElement : function(track, direction) {
+ var element = this.getMediaElementForTrack(track, direction);
if (!element) {
- element = createMediaElementForTrack(track, idPrefix);
+ element = this.createMediaElementForTrack(track, direction);
if (direction == "local") {
this.localMediaElements.push(element);
} else if (direction == "remote") {
this.remoteMediaElements.push(element);
}
}
// We do this regardless, because sometimes we end up with a new stream with
// an old id (ie; the rollback tests cause the same stream to be added
// twice)
element.srcObject = new MediaStream([track]);
element.play();
},
+ addSendStream : function(stream)
+ {
+ // The PeerConnection will not necessarily know about this stream
+ // automatically, because replaceTrack is not told about any streams the
+ // new track might be associated with. Only content really knows.
+ this._sendStreams.push(stream);
+ },
+
+ getStreamForSendTrack : function(track)
+ {
+ return this._sendStreams.find(str => str.getTrackById(track.id));
+ },
+
+ getStreamForRecvTrack : function(track)
+ {
+ return this._pc.getRemoteStreams().find(s => !!s.getTrackById(track.id));
+ },
+
/**
* Attaches a local track to this RTCPeerConnection using
* RTCPeerConnection.addTrack().
*
* Also creates a media element playing a MediaStream containing all
* tracks that have been added to `stream` using `attachLocalTrack()`.
*
* @param {MediaStreamTrack} track
@@ -936,16 +966,20 @@ PeerConnectionWrapper.prototype = {
ok(track.id, "track has id");
ok(track.kind, "track has kind");
ok(stream.id, "stream has id");
this.expectedLocalTrackInfoById[track.id] = {
type: track.kind,
streamId: stream.id,
};
+ this.expectedSignalledTrackInfoById[track.id] =
+ this.expectedLocalTrackInfoById[track.id];
+
+ this.addSendStream(stream);
// This will create one media element per track, which might not be how
// we set up things with the RTCPeerConnection. It's the only way
// we can ensure all sent tracks are flowing however.
this.ensureMediaElement(track, "local");
return this.observedNegotiationNeeded;
},
@@ -971,75 +1005,101 @@ PeerConnectionWrapper.prototype = {
} else {
info("Using addTrack (on PC).");
stream.getTracks().forEach(track => {
var sender = this._pc.addTrack(track, stream);
is(sender.track, track, "addTrack returns sender");
});
}
+ this.addSendStream(stream);
+
stream.getTracks().forEach(track => {
ok(track.id, "track has id");
ok(track.kind, "track has kind");
this.expectedLocalTrackInfoById[track.id] = {
type: track.kind,
streamId: stream.id
};
+ this.expectedSignalledTrackInfoById[track.id] =
+ this.expectedLocalTrackInfoById[track.id];
this.ensureMediaElement(track, "local");
});
+
+ return this.observedNegotiationNeeded;
},
removeSender : function(index) {
var sender = this._pc.getSenders()[index];
delete this.expectedLocalTrackInfoById[sender.track.id];
this.expectNegotiationNeeded();
this._pc.removeTrack(sender);
return this.observedNegotiationNeeded;
},
- senderReplaceTrack : function(index, withTrack, withStreamId) {
- var sender = this._pc.getSenders()[index];
+ senderReplaceTrack : function(sender, withTrack, stream) {
delete this.expectedLocalTrackInfoById[sender.track.id];
this.expectedLocalTrackInfoById[withTrack.id] = {
type: withTrack.kind,
- streamId: withStreamId
+ streamId: stream.id
};
+ this.addSendStream(stream);
+ this.ensureMediaElement(withTrack, 'local');
return sender.replaceTrack(withTrack);
},
+ getUserMedia : async function(constraints) {
+ var stream = await getUserMedia(constraints);
+ if (constraints.audio) {
+ stream.getAudioTracks().forEach(track => {
+ info(this + " gUM local stream " + stream.id +
+ " with audio track " + track.id);
+ });
+ }
+ if (constraints.video) {
+ stream.getVideoTracks().forEach(track => {
+ info(this + " gUM local stream " + stream.id +
+ " with video track " + track.id);
+ });
+ }
+ return stream;
+ },
+
/**
* Requests all the media streams as specified in the constrains property.
*
* @param {array} constraintsList
* Array of constraints for GUM calls
*/
getAllUserMedia : function(constraintsList) {
if (constraintsList.length === 0) {
info("Skipping GUM: no UserMedia requested");
return Promise.resolve();
}
info("Get " + constraintsList.length + " local streams");
- return Promise.all(constraintsList.map(constraints => {
- return getUserMedia(constraints).then(stream => {
- if (constraints.audio) {
- stream.getAudioTracks().forEach(track => {
- info(this + " gUM local stream " + stream.id +
- " with audio track " + track.id);
- });
- }
- if (constraints.video) {
- stream.getVideoTracks().forEach(track => {
- info(this + " gUM local stream " + stream.id +
- " with video track " + track.id);
- });
- }
- return this.attachLocalStream(stream);
- });
- }));
+
+ return Promise.all(
+ constraintsList.map(constraints => this.getUserMedia(constraints)));
+ },
+
+ getAllUserMediaAndAddTracks : async function(constraintsList) {
+ var streams = await this.getAllUserMedia(constraintsList);
+ if (!streams) {
+ return;
+ }
+ return Promise.all(streams.map(stream => this.attachLocalStream(stream)));
+ },
+
+ getAllUserMediaAndAddTransceivers : async function(constraintsList) {
+ var streams = await this.getAllUserMedia(constraintsList);
+ if (!streams) {
+ return;
+ }
+ return Promise.all(streams.map(stream => this.attachLocalStream(stream)));
},
/**
* Create a new data channel instance. Also creates a promise called
* `this.nextDataChannel` that resolves when the next data channel arrives.
*/
expectDataChannel: function(message) {
this.nextDataChannel = new Promise(resolve => {
@@ -1179,44 +1239,55 @@ PeerConnectionWrapper.prototype = {
});
},
/**
* Checks whether a given track is expected, has not been observed yet, and
* is of the correct type. Then, moves the track from
* |expectedTrackInfoById| to |observedTrackInfoById|.
*/
- checkTrackIsExpected : function(track,
+ checkTrackIsExpected : function(trackId,
+ kind,
expectedTrackInfoById,
observedTrackInfoById) {
- ok(expectedTrackInfoById[track.id], "track id " + track.id + " was expected");
- ok(!observedTrackInfoById[track.id], "track id " + track.id + " was not yet observed");
- var observedKind = track.kind;
- var expectedKind = expectedTrackInfoById[track.id].type;
+ ok(expectedTrackInfoById[trackId], "track id " + trackId + " was expected");
+ ok(!observedTrackInfoById[trackId], "track id " + trackId + " was not yet observed");
+ var observedKind = kind;
+ var expectedKind = expectedTrackInfoById[trackId].type;
is(observedKind, expectedKind,
- "track id " + track.id + " was of kind " +
+ "track id " + trackId + " was of kind " +
observedKind + ", which matches " + expectedKind);
- observedTrackInfoById[track.id] = expectedTrackInfoById[track.id];
+ observedTrackInfoById[trackId] = expectedTrackInfoById[trackId];
},
isTrackOnPC: function(track) {
- return this._pc.getRemoteStreams().some(s => !!s.getTrackById(track.id));
+ return !!this.getStreamForRecvTrack(track);
},
allExpectedTracksAreObserved: function(expected, observed) {
return Object.keys(expected).every(trackId => observed[trackId]);
},
setupTrackEventHandler: function() {
this._pc.addEventListener('track', event => {
- info(this + ": 'ontrack' event fired for " + JSON.stringify(event.track));
+ info(this + ": 'ontrack' event fired for " + event.track.id +
+ "(SDP msid is " + this._pc.mozGetWebrtcTrackId(event.track) +
+ ")");
- this.checkTrackIsExpected(event.track,
- this.expectedRemoteTrackInfoById,
- this.observedRemoteTrackInfoById);
+ // TODO(bug XXXXX): Checking for remote tracks needs to be completely
+ // reworked, because with the latest spec the identifiers aren't the same
+ // as they are on the other end. Ultimately, what we need to check is
+ // whether the _transceivers_ are in line with what is expected, and
+ // whether the callbacks are consistent with the transceivers.
+ let trackId = this._pc.mozGetWebrtcTrackId(event.track);
+ ok(!this.observedRemoteTrackInfoById[trackId],
+ "track id " + trackId + " was not yet observed");
+ this.observedRemoteTrackInfoById[trackId] = {
+ type: event.track.kind
+ };
ok(this.isTrackOnPC(event.track), "Found track " + event.track.id);
this.ensureMediaElement(event.track, 'remote');
});
},
/**
* Either adds a given ICE candidate right away or stores it to be added
@@ -1339,53 +1410,47 @@ PeerConnectionWrapper.prototype = {
candidateHandler(this.label, anEvent.candidate);
};
},
checkLocalMediaTracks : function() {
var observed = {};
info(this + " Checking local tracks " + JSON.stringify(this.expectedLocalTrackInfoById));
this._pc.getSenders().forEach(sender => {
- this.checkTrackIsExpected(sender.track, this.expectedLocalTrackInfoById, observed);
+ if (sender.track) {
+ this.checkTrackIsExpected(sender.track.id,
+ sender.track.kind,
+ this.expectedLocalTrackInfoById,
+ observed);
+ }
});
Object.keys(this.expectedLocalTrackInfoById).forEach(
id => ok(observed[id], this + " local id " + id + " was observed"));
},
/**
* Checks that we are getting the media tracks we expect.
*/
checkMediaTracks : function() {
this.checkLocalMediaTracks();
-
- info(this + " Checking remote tracks " +
- JSON.stringify(this.expectedRemoteTrackInfoById));
-
- ok(this.allExpectedTracksAreObserved(this.expectedRemoteTrackInfoById,
- this.observedRemoteTrackInfoById),
- "All expected tracks have been observed"
- + "\nexpected: " + JSON.stringify(this.expectedRemoteTrackInfoById)
- + "\nobserved: " + JSON.stringify(this.observedRemoteTrackInfoById));
},
checkMsids: function() {
var checkSdpForMsids = (desc, expectedTrackInfo, side) => {
Object.keys(expectedTrackInfo).forEach(trackId => {
var streamId = expectedTrackInfo[trackId].streamId;
ok(desc.sdp.match(new RegExp("a=msid:" + streamId + " " + trackId)),
this + ": " + side + " SDP contains stream " + streamId +
" and track " + trackId );
});
};
- checkSdpForMsids(this.localDescription, this.expectedLocalTrackInfoById,
+ checkSdpForMsids(this.localDescription, this.expectedSignalledTrackInfoById,
"local");
- checkSdpForMsids(this.remoteDescription, this.expectedRemoteTrackInfoById,
- "remote");
},
markRemoteTracksAsNegotiated: function() {
Object.values(this.observedRemoteTrackInfoById).forEach(
trackInfo => trackInfo.negotiated = true);
},
rollbackRemoteTracksIfNotNegotiated: function() {
@@ -1476,32 +1541,65 @@ PeerConnectionWrapper.prototype = {
return stats;
}
await wait(retryInterval);
}
throw new Error("Timeout checking for stats for track " + track.id
+ " after at least" + timeout + "ms");
},
+ getExpectedActiveReceiveTracks : function() {
+ return this._pc.getTransceivers()
+ .filter(t => {
+ return !t.stopped &&
+ t.currentDirection &&
+ (t.currentDirection != "inactive") &&
+ (t.currentDirection != "sendonly");
+ })
+ .map(t => {
+ info("Found transceiver that should be receiving RTP: mid=" + t.mid +
+ " currentDirection=" + t.currentDirection + " kind=" +
+ t.receiver.track.kind + " track-id=" + t.receiver.track.id);
+ return t.receiver.track;
+ });
+ },
+
+ getExpectedSendTracks : function() {
+ return Object.keys(this.expectedLocalTrackInfoById)
+ .map(id => this.findSendTrackByWebrtcId(id));
+ },
+
+ findReceiveTrackByWebrtcId : function(webrtcId) {
+ return this._pc.getReceivers().map(receiver => receiver.track)
+ .find(track => this._pc.mozGetWebrtcTrackId(track) == webrtcId);
+ },
+
+ // Send tracks use the same identifiers that go in the signaling
+ findSendTrackByWebrtcId : function(webrtcId) {
+ return this._pc.getSenders().map(sender => sender.track)
+ .filter(track => track) // strip out null
+ .find(track => track.id == webrtcId);
+ },
+
/**
* Wait for presence of video flow on all media elements and rtp flow on
* all sending and receiving track involved in this test.
*
* @returns {Promise}
* A promise that resolves when media flows for all elements and tracks
*/
waitForMediaFlow : function() {
return Promise.all([].concat(
this.localMediaElements.map(element => this.waitForMediaElementFlow(element)),
- Object.keys(this.expectedRemoteTrackInfoById)
- .map(id => this.remoteMediaElements
- .find(e => e.srcObject.getTracks().some(t => t.id == id)))
- .map(e => this.waitForMediaElementFlow(e)),
- this._pc.getSenders().map(sender => this.waitForRtpFlow(sender.track)),
- this._pc.getReceivers().map(receiver => this.waitForRtpFlow(receiver.track))));
+ this.remoteMediaElements
+ .filter(elem =>
+ this.getExpectedActiveReceiveTracks().some(track => elem.srcObject.getTracks().some(t => t == track)))
+ .map(elem => this.waitForMediaElementFlow(elem)),
+ this.getExpectedActiveReceiveTracks().map(track => this.waitForRtpFlow(track)),
+ this.getExpectedSendTracks().map(track => this.waitForRtpFlow(track))));
},
async waitForSyncedRtcp() {
// Ensures that RTCP is present
let ensureSyncedRtcp = async () => {
let report = await this._pc.getStats();
for (let [k, v] of report) {
if (v.type.endsWith("bound-rtp") && !v.remoteId) {
@@ -1537,64 +1635,104 @@ PeerConnectionWrapper.prototype = {
await wait(waitPeriod);
}
throw Error("Waiting for synced RTCP timed out after at least "
+ maxTime + "ms");
},
/**
* Check that correct audio (typically a flat tone) is flowing to this
- * PeerConnection. Uses WebAudio AnalyserNodes to compare input and output
- * audio data in the frequency domain.
+ * PeerConnection for each transceiver that should be receiving. Uses
+ * WebAudio AnalyserNodes to compare input and output audio data in the
+ * frequency domain.
*
* @param {object} from
* A PeerConnectionWrapper whose audio RTPSender we use as source for
* the audio flow check.
* @returns {Promise}
- * A promise that resolves when we're receiving the tone from |from|.
+ * A promise that resolves when we're receiving the tone/s from |from|.
*/
checkReceivingToneFrom : function(audiocontext, from) {
- var inputElem = from.localMediaElements[0];
+ var localTransceivers = this._pc.getTransceivers()
+ .filter(t => t.mid)
+ .sort((t1, t2) => t1.mid < t2.mid);
+ var remoteTransceivers = from._pc.getTransceivers()
+ .filter(t => t.mid)
+ .sort((t1, t2) => t1.mid < t2.mid);
+ var promises = [];
- // As input we use the stream of |from|'s first available audio sender.
- var inputSenderTracks = from._pc.getSenders().map(sn => sn.track);
- var inputAudioStream = from._pc.getLocalStreams()
- .find(s => inputSenderTracks.some(t => t.kind == "audio" && s.getTrackById(t.id)));
- var inputAnalyser = new AudioStreamAnalyser(audiocontext, inputAudioStream);
-
- // It would have been nice to have a working getReceivers() here, but until
- // we do, let's use what remote streams we have.
- var outputAudioStream = this._pc.getRemoteStreams()
- .find(s => s.getAudioTracks().length > 0);
- var outputAnalyser = new AudioStreamAnalyser(audiocontext, outputAudioStream);
+ is(localTransceivers.length, remoteTransceivers.length,
+ "Same number of associated transceivers on remote and local.");
- var maxWithIndex = (a, b, i) => (b >= a.value) ? { value: b, index: i } : a;
- var initial = { value: -1, index: -1 };
-
- return new Promise((resolve, reject) => inputElem.ontimeupdate = () => {
- var inputData = inputAnalyser.getByteFrequencyData();
- var outputData = outputAnalyser.getByteFrequencyData();
+ for (var i = 0; i < localTransceivers.length; i++) {
+ is(localTransceivers[i].receiver.track.kind,
+ remoteTransceivers[i].receiver.track.kind,
+ "Transceivers at index " + i + " are the same kind.");
- var inputMax = inputData.reduce(maxWithIndex, initial);
- var outputMax = outputData.reduce(maxWithIndex, initial);
- info("Comparing maxima; input[" + inputMax.index + "] = " + inputMax.value +
- ", output[" + outputMax.index + "] = " + outputMax.value);
- if (!inputMax.value || !outputMax.value) {
- return;
+ if (localTransceivers[i].receiver.track.kind != "audio") {
+ continue;
+ }
+
+ if (!remoteTransceivers[i].sender.track) {
+ continue;
+ }
+
+ if (remoteTransceivers[i].currentDirection == "recvonly" ||
+ remoteTransceivers[i].currentDirection == "inactive") {
+ continue;
}
- // When the input and output maxima are within reasonable distance
- // from each other, we can be sure that the input tone has made it
- // through the peer connection.
- if (Math.abs(inputMax.index - outputMax.index) < 10) {
- ok(true, "input and output audio data matches");
- inputElem.ontimeupdate = null;
- resolve();
- }
- });
+ var sendTrack = remoteTransceivers[i].sender.track;
+ var inputElem = from.getMediaElementForTrack(sendTrack, "local");
+ ok(inputElem,
+ "Remote wrapper should have a media element for track id " +
+ sendTrack.id);
+ var inputAudioStream = from.getStreamForSendTrack(sendTrack);
+ ok(inputAudioStream,
+ "Remote wrapper should have a stream for track id " + sendTrack.id);
+ var inputAnalyser =
+ new AudioStreamAnalyser(audiocontext, inputAudioStream);
+
+ var recvTrack = localTransceivers[i].receiver.track;
+ var outputAudioStream = this.getStreamForRecvTrack(recvTrack);
+ ok(outputAudioStream,
+ "Local wrapper should have a stream for track id " + recvTrack.id);
+ var outputAnalyser =
+ new AudioStreamAnalyser(audiocontext, outputAudioStream);
+
+ var maxWithIndex = (a, b, i) => (b >= a.value) ? { value: b, index: i } : a;
+ var initial = { value: -1, index: -1 };
+
+ promises.push(
+ new Promise((resolve, reject) => inputElem.ontimeupdate = () => {
+ var inputData = inputAnalyser.getByteFrequencyData();
+ var outputData = outputAnalyser.getByteFrequencyData();
+
+ var inputMax = inputData.reduce(maxWithIndex, initial);
+ var outputMax = outputData.reduce(maxWithIndex, initial);
+ info("Comparing maxima; input[" + inputMax.index + "] = " + inputMax.value +
+ ", output[" + outputMax.index + "] = " + outputMax.value);
+ if (!inputMax.value || !outputMax.value) {
+ return;
+ }
+
+ // When the input and output maxima are within reasonable distance
+ // from each other, we can be sure that the input tone has made it
+ // through the peer connection.
+ if (Math.abs(inputMax.index - outputMax.index) < 10) {
+ ok(true, "input and output audio data matches");
+ inputElem.ontimeupdate = null;
+ resolve();
+ }
+ }));
+ }
+
+ isnot(promises.length, 0, "Found at least one audio transceiver to check.");
+
+ return Promise.all(promises);
},
/**
* Get stats from the "legacy" getStats callback interface
*/
getStatsLegacy : function(selector, onSuccess, onFail) {
let wrapper = stats => {
info(this + ": Got legacy stats: " + JSON.stringify(stats));
@@ -1632,16 +1770,17 @@ PeerConnectionWrapper.prototype = {
* The stats to check from this PeerConnectionWrapper
*/
checkStats : function(stats, twoMachines) {
const isWinXP = navigator.userAgent.indexOf("Windows NT 5.1") != -1;
// Use spec way of enumerating stats
var counters = {};
for (let [key, res] of stats) {
+ info("Checking stats for " + key + " : " + res);
// validate stats
ok(res.id == key, "Coherent stats id");
var nowish = Date.now() + 1000; // TODO: clock drift observed
var minimum = this.whenCreated - 1000; // on Windows XP (Bug 979649)
if (isWinXP) {
todo(false, "Can't reliably test rtcp timestamps on WinXP (Bug 979649)");
} else if (false) { // Bug 1325430 - timestamps aren't working properly in update 49
@@ -1665,21 +1804,27 @@ PeerConnectionWrapper.prototype = {
if (res.isRemote) {
continue;
}
counters[res.type] = (counters[res.type] || 0) + 1;
switch (res.type) {
case "inbound-rtp":
case "outbound-rtp": {
- // ssrc is a 32 bit number returned as a string by spec
- ok(res.ssrc.length > 0, "Ssrc has length");
- ok(res.ssrc.length < 11, "Ssrc not lengthy");
- ok(!/[^0-9]/.test(res.ssrc), "Ssrc numeric");
- ok(parseInt(res.ssrc) < Math.pow(2,32), "Ssrc within limits");
+ // Inbound tracks won't have an ssrc if RTP is not flowing.
+ // (eg; negotiated inactive)
+ ok(res.ssrc || res.type == "inbound-rtp", "Outbound RTP stats has an ssrc.");
+
+ if (res.ssrc) {
+ // ssrc is a 32 bit number returned as a string by spec
+ ok(res.ssrc.length > 0, "Ssrc has length");
+ ok(res.ssrc.length < 11, "Ssrc not lengthy");
+ ok(!/[^0-9]/.test(res.ssrc), "Ssrc numeric");
+ ok(parseInt(res.ssrc) < Math.pow(2,32), "Ssrc within limits");
+ }
if (res.type == "outbound-rtp") {
ok(res.packetsSent !== undefined, "Rtp packetsSent");
// We assume minimum payload to be 1 byte (guess from RFC 3550)
ok(res.bytesSent >= res.packetsSent, "Rtp bytesSent");
} else {
ok(res.packetsReceived !== undefined, "Rtp packetsReceived");
ok(res.bytesReceived >= res.packetsReceived, "Rtp bytesReceived");
@@ -1744,17 +1889,22 @@ PeerConnectionWrapper.prototype = {
var res = stats[key];
var type = legacyToSpecMapping[res.type] || res.type;
if (!res.isRemote) {
counters2[type] = (counters2[type] || 0) + 1;
}
}
is(JSON.stringify(counters), JSON.stringify(counters2),
"Spec and legacy variant of RTCStatsReport enumeration agree");
- var nin = Object.keys(this.expectedRemoteTrackInfoById).length;
+ var nin = this._pc.getTransceivers()
+ .filter(t => {
+ return !t.stopped &&
+ (t.currentDirection != "inactive") &&
+ (t.currentDirection != "sendonly");
+ }).length;
var nout = Object.keys(this.expectedLocalTrackInfoById).length;
var ndata = this.dataChannels.length;
// TODO(Bug 957145): Restore stronger inbound-rtp test once Bug 948249 is fixed
//is((counters["inbound-rtp"] || 0), nin, "Have " + nin + " inbound-rtp stat(s)");
ok((counters["inbound-rtp"] || 0) >= nin, "Have at least " + nin + " inbound-rtp stat(s) *");
is(counters["outbound-rtp"] || 0, nout, "Have " + nout + " outbound-rtp stat(s)");
@@ -1820,49 +1970,46 @@ PeerConnectionWrapper.prototype = {
/**
* Compares amount of established ICE connection according to ICE candidate
* pairs in the stats reporting with the expected amount of connection based
* on the constraints.
*
* @param {object} stats
* The stats to check for ICE candidate pairs
- * @param {object} counters
- * The counters for media and data tracks based on constraints
* @param {object} testOptions
* The test options object from the PeerConnectionTest
*/
- checkStatsIceConnections : function(stats,
- offerConstraintsList, offerOptions, testOptions) {
+ checkStatsIceConnections : function(stats, testOptions) {
var numIceConnections = 0;
stats.forEach(stat => {
if ((stat.type === "candidate-pair") && stat.selected) {
numIceConnections += 1;
}
});
info("ICE connections according to stats: " + numIceConnections);
isnot(numIceConnections, 0, "Number of ICE connections according to stats is not zero");
if (testOptions.bundle) {
if (testOptions.rtcpmux) {
is(numIceConnections, 1, "stats reports exactly 1 ICE connection");
} else {
is(numIceConnections, 2, "stats report exactly 2 ICE connections for media and RTCP");
}
} else {
- // This code assumes that no media sections have been rejected due to
- // codec mismatch or other unrecoverable negotiation failures.
- var numAudioTracks =
- sdputils.countTracksInConstraint('audio', offerConstraintsList) ||
- ((offerOptions && offerOptions.offerToReceiveAudio) ? 1 : 0);
+ var numAudioTransceivers =
+ this._pc.getTransceivers().filter((transceiver) => {
+ return (!transceiver.stopped) && transceiver.receiver.track.kind == "audio";
+ }).length;
- var numVideoTracks =
- sdputils.countTracksInConstraint('video', offerConstraintsList) ||
- ((offerOptions && offerOptions.offerToReceiveVideo) ? 1 : 0);
+ var numVideoTransceivers =
+ this._pc.getTransceivers().filter((transceiver) => {
+ return (!transceiver.stopped) && transceiver.receiver.track.kind == "video";
+ }).length;
- var numExpectedTransports = numAudioTracks + numVideoTracks;
+ var numExpectedTransports = numAudioTransceivers + numVideoTransceivers;
if (!testOptions.rtcpmux) {
numExpectedTransports *= 2;
}
if (this.dataChannels.length) {
++numExpectedTransports;
}
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -78,18 +78,17 @@ function waitForAnIceCandidate(pc) {
}).then(() => {
ok(pc._local_ice_candidates.length > 0,
pc + " received local trickle ICE candidates");
isnot(pc._pc.iceGatheringState, GATH_NEW,
pc + " ICE gathering state is not 'new'");
});
}
-function checkTrackStats(pc, rtpSenderOrReceiver, outbound) {
- var track = rtpSenderOrReceiver.track;
+function checkTrackStats(pc, track, outbound) {
var audio = (track.kind == "audio");
var msg = pc + " stats " + (outbound ? "outbound " : "inbound ") +
(audio ? "audio" : "video") + " rtp track id " + track.id;
return pc.getStats(track).then(stats => {
ok(pc.hasStat(stats, {
type: outbound ? "outbound-rtp" : "inbound-rtp",
isRemote: false,
mediaType: audio ? "audio" : "video"
@@ -101,18 +100,18 @@ function checkTrackStats(pc, rtpSenderOr
ok(!pc.hasStat(stats, {
mediaType: audio ? "video" : "audio"
}), msg + " - did not find extra stats with wrong media type");
});
}
var checkAllTrackStats = pc => {
return Promise.all([].concat(
- pc._pc.getSenders().map(sender => checkTrackStats(pc, sender, true)),
- pc._pc.getReceivers().map(receiver => checkTrackStats(pc, receiver, false))));
+ pc.getExpectedActiveReceiveTracks().map(track => checkTrackStats(pc, track, false)),
+ pc.getExpectedSendTracks().map(track => checkTrackStats(pc, track, true))));
}
// Commands run once at the beginning of each test, even when performing a
// renegotiation test.
var commandsPeerConnectionInitial = [
function PC_SETUP_SIGNALING_CLIENT(test) {
if (test.testOptions.steeplechase) {
test.setupSignalingClient();
@@ -178,21 +177,21 @@ var commandsPeerConnectionInitial = [
function PC_REMOTE_CHECK_INITIAL_CAN_TRICKLE_SYNC(test) {
is(test.pcRemote._pc.canTrickleIceCandidates, null,
"Remote trickle status should start out unknown");
},
];
var commandsGetUserMedia = [
function PC_LOCAL_GUM(test) {
- return test.pcLocal.getAllUserMedia(test.pcLocal.constraints);
+ return test.pcLocal.getAllUserMediaAndAddTracks(test.pcLocal.constraints);
},
function PC_REMOTE_GUM(test) {
- return test.pcRemote.getAllUserMedia(test.pcRemote.constraints);
+ return test.pcRemote.getAllUserMediaAndAddTracks(test.pcRemote.constraints);
},
];
var commandsPeerConnectionOfferAnswer = [
function PC_LOCAL_SETUP_ICE_HANDLER(test) {
test.pcLocal.setupIceCandidateHandler(test);
},
@@ -209,42 +208,16 @@ var commandsPeerConnectionOfferAnswer =
function PC_REMOTE_STEEPLECHASE_SIGNAL_EXPECTED_LOCAL_TRACKS(test) {
if (test.testOptions.steeplechase) {
send_message({"type": "remote_expected_tracks",
"expected_tracks": test.pcRemote.expectedLocalTrackInfoById});
}
},
- function PC_LOCAL_GET_EXPECTED_REMOTE_TRACKS(test) {
- if (test.testOptions.steeplechase) {
- return test.getSignalingMessage("remote_expected_tracks").then(
- message => {
- test.pcLocal.expectedRemoteTrackInfoById = message.expected_tracks;
- });
- }
-
- // Deep copy, as similar to steeplechase as possible
- test.pcLocal.expectedRemoteTrackInfoById =
- JSON.parse(JSON.stringify(test.pcRemote.expectedLocalTrackInfoById));
- },
-
- function PC_REMOTE_GET_EXPECTED_REMOTE_TRACKS(test) {
- if (test.testOptions.steeplechase) {
- return test.getSignalingMessage("local_expected_tracks").then(
- message => {
- test.pcRemote.expectedRemoteTrackInfoById = message.expected_tracks;
- });
- }
-
- // Deep copy, as similar to steeplechase as possible
- test.pcRemote.expectedRemoteTrackInfoById =
- JSON.parse(JSON.stringify(test.pcLocal.expectedLocalTrackInfoById));
- },
-
function PC_LOCAL_CREATE_OFFER(test) {
return test.createOffer(test.pcLocal).then(offer => {
is(test.pcLocal.signalingState, STABLE,
"Local create offer does not change signaling state");
});
},
function PC_LOCAL_STEEPLECHASE_SIGNAL_OFFER(test) {
@@ -430,29 +403,23 @@ var commandsPeerConnectionOfferAnswer =
return test.pcRemote.getStats().then(stats => {
test.pcRemote.checkStatsIceConnectionType(stats,
test.testOptions.expectedRemoteCandidateType);
});
},
function PC_LOCAL_CHECK_ICE_CONNECTIONS(test) {
return test.pcLocal.getStats().then(stats => {
- test.pcLocal.checkStatsIceConnections(stats,
- test._offer_constraints,
- test._offer_options,
- test.testOptions);
+ test.pcLocal.checkStatsIceConnections(stats, test.testOptions);
});
},
function PC_REMOTE_CHECK_ICE_CONNECTIONS(test) {
return test.pcRemote.getStats().then(stats => {
- test.pcRemote.checkStatsIceConnections(stats,
- test._offer_constraints,
- test._offer_options,
- test.testOptions);
+ test.pcRemote.checkStatsIceConnections(stats, test.testOptions);
});
},
function PC_LOCAL_CHECK_MSID(test) {
return test.pcLocal.checkMsids();
},
function PC_REMOTE_CHECK_MSID(test) {
return test.pcRemote.checkMsids();
--- a/dom/media/tests/mochitest/test_peerConnection_addSecondAudioStream.html
+++ b/dom/media/tests/mochitest/test_peerConnection_addSecondAudioStream.html
@@ -13,17 +13,17 @@
runNetworkTest(function (options) {
const test = new PeerConnectionTest(options);
addRenegotiation(test.chain,
[
function PC_LOCAL_ADD_SECOND_STREAM(test) {
test.setMediaConstraints([{audio: true}, {audio: true}],
[{audio: true}]);
- return test.pcLocal.getAllUserMedia([{audio: true}]);
+ return test.pcLocal.getAllUserMediaAndAddTracks([{audio: true}]);
},
],
[
function PC_REMOTE_CHECK_ADDED_TRACK(test) {
// We test both tracks to avoid an ordering problem
is(test.pcRemote._pc.getReceivers().length, 2,
"pcRemote should have two receivers");
return Promise.all(test.pcRemote._pc.getReceivers().map(r => {
--- a/dom/media/tests/mochitest/test_peerConnection_addSecondAudioStreamNoBundle.html
+++ b/dom/media/tests/mochitest/test_peerConnection_addSecondAudioStreamNoBundle.html
@@ -17,17 +17,17 @@
addRenegotiation(test.chain,
[
function PC_LOCAL_ADD_SECOND_STREAM(test) {
test.setMediaConstraints([{audio: true}, {audio: true}],
[{audio: true}]);
// Since this is a NoBundle variant, adding a track will cause us to
// go back to checking.
test.pcLocal.expectIceChecking();
- return test.pcLocal.getAllUserMedia([{audio: true}]);
+ return test.pcLocal.getAllUserMediaAndAddTracks([{audio: true}]);
},
function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
test.pcRemote.expectIceChecking();
},
],
[
function PC_REMOTE_CHECK_ADDED_TRACK(test) {
// We test both tracks to avoid an ordering problem
--- a/dom/media/tests/mochitest/test_peerConnection_addSecondVideoStream.html
+++ b/dom/media/tests/mochitest/test_peerConnection_addSecondVideoStream.html
@@ -16,17 +16,17 @@
const test = new PeerConnectionTest(options);
addRenegotiation(test.chain,
[
function PC_LOCAL_ADD_SECOND_STREAM(test) {
test.setMediaConstraints([{video: true}, {video: true}],
[{video: true}]);
// Use fake:true here since the native fake device on linux doesn't
// change color as needed by checkVideoPlaying() below.
- return test.pcLocal.getAllUserMedia([{video: true, fake: true}]);
+ return test.pcLocal.getAllUserMediaAndAddTracks([{video: true, fake: true}]);
},
],
[
function PC_REMOTE_CHECK_VIDEO_FLOW(test) {
const h = new VideoStreamHelper();
is(test.pcRemote.remoteMediaElements.length, 2,
"Should have two remote media elements after renegotiation");
return Promise.all(test.pcRemote.remoteMediaElements.map(video =>
--- a/dom/media/tests/mochitest/test_peerConnection_addSecondVideoStreamNoBundle.html
+++ b/dom/media/tests/mochitest/test_peerConnection_addSecondVideoStreamNoBundle.html
@@ -20,17 +20,17 @@
function PC_LOCAL_ADD_SECOND_STREAM(test) {
test.setMediaConstraints([{video: true}, {video: true}],
[{video: true}]);
// Since this is a NoBundle variant, adding a track will cause us to
// go back to checking.
test.pcLocal.expectIceChecking();
// Use fake:true here since the native fake device on linux doesn't
// change color as needed by checkVideoPlaying() below.
- return test.pcLocal.getAllUserMedia([{video: true, fake: true}]);
+ return test.pcLocal.getAllUserMediaAndAddTracks([{video: true, fake: true}]);
},
function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
test.pcRemote.expectIceChecking();
},
],
[
function PC_REMOTE_CHECK_VIDEO_FLOW(test) {
const h = new VideoStreamHelper();
--- a/dom/media/tests/mochitest/test_peerConnection_addtrack_removetrack_events.html
+++ b/dom/media/tests/mochitest/test_peerConnection_addtrack_removetrack_events.html
@@ -15,53 +15,54 @@ createHTML({
runNetworkTest(function (options) {
let test = new PeerConnectionTest(options);
let eventsPromise;
addRenegotiation(test.chain,
[
function PC_LOCAL_SWAP_VIDEO_TRACKS(test) {
return getUserMedia({video: true}).then(stream => {
+ var videoTransceiver = test.pcLocal._pc.getTransceivers()[1];
+ is(videoTransceiver.currentDirection, "sendonly",
+ "Video transceiver's current direction is sendonly");
+ is(videoTransceiver.direction, "sendrecv",
+ "Video transceiver's desired direction is sendrecv");
+
const localStream = test.pcLocal._pc.getLocalStreams()[0];
ok(localStream, "Should have local stream");
const remoteStream = test.pcRemote._pc.getRemoteStreams()[0];
ok(remoteStream, "Should have remote stream");
const newTrack = stream.getTracks()[0];
const videoSenderIndex =
test.pcLocal._pc.getSenders().findIndex(s => s.track.kind == "video");
isnot(videoSenderIndex, -1, "Should have video sender");
test.pcLocal.removeSender(videoSenderIndex);
+ is(videoTransceiver.direction, "recvonly",
+ "Video transceiver should be recvonly after removeTrack");
test.pcLocal.attachLocalTrack(stream.getTracks()[0], localStream);
+ is(videoTransceiver.direction, "recvonly",
+ "Video transceiver should be recvonly after addTrack");
- const addTrackPromise = haveEvent(remoteStream, "addtrack",
- wait(50000, new Error("No addtrack event")))
+ eventsPromise = haveEvent(remoteStream, "addtrack",
+ wait(50000, new Error("No addtrack event for " + newTrack.id)))
.then(trackEvent => {
ok(trackEvent instanceof MediaStreamTrackEvent,
"Expected event to be instance of MediaStreamTrackEvent");
is(trackEvent.type, "addtrack",
"Expected addtrack event type");
- is(trackEvent.track.id, newTrack.id, "Expected track in event");
+ is(test.pcRemote._pc.mozGetWebrtcTrackId(trackEvent.track), newTrack.id, "Expected track in event");
is(trackEvent.track.readyState, "live",
"added track should be live");
})
.then(() => haveNoEvent(remoteStream, "addtrack"));
- const remoteTrack = test.pcRemote._pc.getReceivers()
- .map(r => r.track)
- .find(t => t.kind == "video");
- ok(remoteTrack, "Should have received remote track");
- const endedPromise = haveEvent(remoteTrack, "ended",
- wait(50000, new Error("No ended event")));
-
- eventsPromise = Promise.all([addTrackPromise, endedPromise]);
-
remoteStream.addEventListener("removetrack",
function onRemovetrack(trackEvent) {
ok(false, "UA shouldn't raise 'removetrack' when receiving peer connection");
})
});
},
],
[
--- a/dom/media/tests/mochitest/test_peerConnection_answererAddSecondAudioStream.html
+++ b/dom/media/tests/mochitest/test_peerConnection_answererAddSecondAudioStream.html
@@ -14,17 +14,17 @@
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
addRenegotiationAnswerer(test.chain,
[
function PC_LOCAL_ADD_SECOND_STREAM(test) {
test.setMediaConstraints([{audio: true}, {audio: true}],
[{audio: true}]);
- return test.pcLocal.getAllUserMedia([{audio: true}]);
+ return test.pcLocal.getAllUserMediaAndAddTracks([{audio: true}]);
},
]
);
test.setMediaConstraints([{audio: true}], [{audio: true}]);
test.run();
});
</script>
--- a/dom/media/tests/mochitest/test_peerConnection_bug1064223.html
+++ b/dom/media/tests/mochitest/test_peerConnection_bug1064223.html
@@ -11,17 +11,17 @@
title: "CreateOffer fails without streams or modern RTCOfferOptions"
});
runNetworkTest(function () {
var pc = new mozRTCPeerConnection();
var options = { mandatory: { OfferToReceiveVideo: true } }; // obsolete
pc.createOffer(options).then(() => ok(false, "createOffer must fail"),
- e => is(e.name, "InternalError",
+ e => is(e.name, "InvalidStateError",
"createOffer must fail"))
.catch(e => ok(false, e.message))
.then(() => {
pc.close();
networkTestFinished();
})
.catch(e => ok(false, e.message));
});
--- a/dom/media/tests/mochitest/test_peerConnection_constructedStream.html
+++ b/dom/media/tests/mochitest/test_peerConnection_constructedStream.html
@@ -45,17 +45,17 @@ runNetworkTest(() => {
ok(receivedStream, "We should receive a stream with with the sent stream's id (" + sentStreamId + ")");
if (!receivedStream) {
return;
}
is(receivedStream.getTracks().length, sentTracks.length,
"Should receive same number of tracks as were sent");
sentTracks.forEach(t =>
- ok(receivedStream.getTracks().find(t2 => t.id == t2.id),
+ ok(receivedStream.getTracks().find(t2 => t.id == test.pcRemote._pc.mozGetWebrtcTrackId(t2)),
"The sent track (" + t.id + ") should exist on the receive side"));
};
test.chain.append([
function PC_REMOTE_CHECK_RECEIVED_CONSTRUCTED_STREAM() {
checkSentTracksReceived(constructedStream.id, constructedStream.getTracks());
},
function PC_REMOTE_CHECK_RECEIVED_DUMMY_STREAM() {
--- a/dom/media/tests/mochitest/test_peerConnection_localReofferRollback.html
+++ b/dom/media/tests/mochitest/test_peerConnection_localReofferRollback.html
@@ -13,17 +13,17 @@
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
addRenegotiation(test.chain, [
function PC_LOCAL_ADD_SECOND_STREAM(test) {
test.setMediaConstraints([{audio: true}, {audio: true}],
[{audio: true}]);
- return test.pcLocal.getAllUserMedia([{audio: true}]);
+ return test.pcLocal.getAllUserMediaAndAddTracks([{audio: true}]);
},
function PC_REMOTE_SETUP_ICE_HANDLER(test) {
test.pcRemote.setupIceCandidateHandler(test);
if (test.testOptions.steeplechase) {
test.pcRemote.endOfTrickleIce.then(() => {
send_message({"type": "end_of_trickle_ice"});
});
@@ -32,16 +32,19 @@
function PC_REMOTE_CREATE_AND_SET_OFFER(test) {
return test.createOffer(test.pcRemote).then(offer => {
return test.setLocalDescription(test.pcRemote, offer, HAVE_LOCAL_OFFER);
});
},
function PC_REMOTE_ROLLBACK(test) {
+ // the negotiationNeeded slot should have been true both before and
+ // after this SLD, so the event should fire again.
+ test.pcRemote.expectNegotiationNeeded();
return test.setLocalDescription(test.pcRemote,
{ type: "rollback", sdp: "" },
STABLE);
},
// Rolling back should shut down gathering
function PC_REMOTE_WAIT_FOR_END_OF_TRICKLE(test) {
return test.pcRemote.endOfTrickleIce;
--- a/dom/media/tests/mochitest/test_peerConnection_localRollback.html
+++ b/dom/media/tests/mochitest/test_peerConnection_localRollback.html
@@ -18,16 +18,19 @@
test.chain.insertBefore('PC_LOCAL_CREATE_OFFER', [
function PC_REMOTE_CREATE_AND_SET_OFFER(test) {
return test.createOffer(test.pcRemote).then(offer => {
return test.setLocalDescription(test.pcRemote, offer, HAVE_LOCAL_OFFER);
});
},
function PC_REMOTE_ROLLBACK(test) {
+ // the negotiationNeeded slot should have been true both before and
+ // after this SLD, so the event should fire again.
+ test.pcRemote.expectNegotiationNeeded();
return test.setLocalDescription(test.pcRemote,
{ type: "rollback", sdp: "" },
STABLE);
},
// Rolling back should shut down gathering
function PC_REMOTE_WAIT_FOR_END_OF_TRICKLE(test) {
return test.pcRemote.endOfTrickleIce;
--- a/dom/media/tests/mochitest/test_peerConnection_remoteReofferRollback.html
+++ b/dom/media/tests/mochitest/test_peerConnection_remoteReofferRollback.html
@@ -14,17 +14,17 @@
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
addRenegotiation(test.chain,
[
function PC_LOCAL_ADD_SECOND_STREAM(test) {
test.setMediaConstraints([{audio: true}, {audio: true}],
[{audio: true}]);
- return test.pcLocal.getAllUserMedia([{audio: true}]);
+ return test.pcLocal.getAllUserMediaAndAddTracks([{audio: true}]);
},
]
);
test.chain.replaceAfter('PC_REMOTE_SET_REMOTE_DESCRIPTION',
[
function PC_LOCAL_SETUP_ICE_HANDLER(test) {
test.pcLocal.setupIceCandidateHandler(test);
if (test.testOptions.steeplechase) {
--- a/dom/media/tests/mochitest/test_peerConnection_removeAudioTrack.html
+++ b/dom/media/tests/mochitest/test_peerConnection_removeAudioTrack.html
@@ -32,20 +32,21 @@
},
function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
test.setOfferOptions({ offerToReceiveAudio: true });
return test.pcLocal.removeSender(0);
},
],
[
function PC_REMOTE_CHECK_FLOW_STOPPED(test) {
- is(test.pcRemote._pc.getReceivers().length, 0,
- "pcRemote should have no more receivers");
- is(receivedTrack.readyState, "ended",
- "The received track should have ended");
+ // Simply removing a track is not enough to cause it to be
+ // signaled as ended. Spec may change though.
+ // TODO: One last check of the spec is in order
+ is(receivedTrack.readyState, "live",
+ "The received track should not have ended");
return analyser.waitForAnalysisSuccess(arr => arr[freq] < 50);
},
]
);
test.setMediaConstraints([{audio: true}], [{audio: true}]);
test.run();
--- a/dom/media/tests/mochitest/test_peerConnection_removeThenAddAudioTrack.html
+++ b/dom/media/tests/mochitest/test_peerConnection_removeThenAddAudioTrack.html
@@ -24,31 +24,40 @@
function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
return test.pcLocal.removeSender(0);
},
function PC_LOCAL_ADD_AUDIO_TRACK(test) {
// The new track's pipeline will start with a packet count of
// 0, but the remote side will keep its old pipeline and packet
// count.
test.pcLocal.disableRtpCountChecking = true;
- return test.pcLocal.getAllUserMedia([{audio: true}]);
+ return test.pcLocal.getAllUserMediaAndAddTracks([{audio: true}]);
},
],
[
function PC_REMOTE_CHECK_ADDED_TRACK(test) {
- is(test.pcRemote._pc.getReceivers().length, 1,
- "pcRemote should still have one receiver");
- const track = test.pcRemote._pc.getReceivers()[0].track;
- isnot(originalTrack.id, track.id, "Receiver should have changed");
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[1].receiver.track;
const analyser = new AudioStreamAnalyser(
new AudioContext(), new MediaStream([track]));
const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
},
+ function PC_REMOTE_CHECK_REMOVED_TRACK(test) {
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[0].receiver.track;
+
+ const analyser = new AudioStreamAnalyser(
+ new AudioContext(), new MediaStream([track]));
+ const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+ return analyser.waitForAnalysisSuccess(arr => arr[freq] < 50);
+ }
]
);
test.setMediaConstraints([{audio: true}], [{audio: true}]);
test.run();
});
</script>
</pre>
--- a/dom/media/tests/mochitest/test_peerConnection_removeThenAddAudioTrackNoBundle.html
+++ b/dom/media/tests/mochitest/test_peerConnection_removeThenAddAudioTrackNoBundle.html
@@ -7,16 +7,18 @@
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1017888",
title: "Renegotiation: remove then add audio track"
});
runNetworkTest(function (options) {
+ options = options || { };
+ options.bundle = false;
const test = new PeerConnectionTest(options);
let originalTrack;
addRenegotiation(test.chain,
[
function PC_REMOTE_FIND_RECEIVER(test) {
is(test.pcRemote._pc.getReceivers().length, 1,
"pcRemote should have one receiver");
originalTrack = test.pcRemote._pc.getReceivers()[0].track;
@@ -24,31 +26,46 @@
function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
// The new track's pipeline will start with a packet count of
// 0, but the remote side will keep its old pipeline and packet
// count.
test.pcLocal.disableRtpCountChecking = true;
return test.pcLocal.removeSender(0);
},
function PC_LOCAL_ADD_AUDIO_TRACK(test) {
- return test.pcLocal.getAllUserMedia([{audio: true}]);
+ return test.pcLocal.getAllUserMediaAndAddTracks([{audio: true}]);
+ },
+ function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+ test.pcLocal.expectIceChecking();
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.expectIceChecking();
},
],
[
function PC_REMOTE_CHECK_ADDED_TRACK(test) {
- is(test.pcRemote._pc.getReceivers().length, 1,
- "pcRemote should still have one receiver");
- const track = test.pcRemote._pc.getReceivers()[0].track;
- isnot(originalTrack.id, track.id, "Receiver should have changed");
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[1].receiver.track;
const analyser = new AudioStreamAnalyser(
new AudioContext(), new MediaStream([track]));
const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
},
+ function PC_REMOTE_CHECK_REMOVED_TRACK(test) {
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[0].receiver.track;
+
+ const analyser = new AudioStreamAnalyser(
+ new AudioContext(), new MediaStream([track]));
+ const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+ return analyser.waitForAnalysisSuccess(arr => arr[freq] < 50);
+ }
]
);
test.chain.insertAfterEach('PC_LOCAL_CREATE_OFFER',
PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER);
test.setMediaConstraints([{audio: true}], [{audio: true}]);
test.run();
--- a/dom/media/tests/mochitest/test_peerConnection_removeThenAddVideoTrack.html
+++ b/dom/media/tests/mochitest/test_peerConnection_removeThenAddVideoTrack.html
@@ -28,33 +28,38 @@
// 0, but the remote side will keep its old pipeline and packet
// count.
test.pcLocal.disableRtpCountChecking = true;
return test.pcLocal.removeSender(0);
},
function PC_LOCAL_ADD_VIDEO_TRACK(test) {
// Use fake:true here since the native fake device on linux doesn't
// change color as needed by checkVideoPlaying() below.
- return test.pcLocal.getAllUserMedia([{video: true, fake: true}]);
+ return test.pcLocal.getAllUserMediaAndAddTracks([{video: true, fake: true}]);
},
],
[
function PC_REMOTE_CHECK_ADDED_TRACK(test) {
- is(test.pcRemote._pc.getReceivers().length, 1,
- "pcRemote should still have one receiver");
- const track = test.pcRemote._pc.getReceivers()[0].track;
- isnot(originalTrack.id, track.id, "Receiver should have changed");
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[1].receiver.track;
- const vOriginal = test.pcRemote.remoteMediaElements.find(
- elem => elem.id.includes(originalTrack.id));
const vAdded = test.pcRemote.remoteMediaElements.find(
elem => elem.id.includes(track.id));
- ok(vOriginal.ended, "Original video element should have ended");
return helper.checkVideoPlaying(vAdded, 10, 10, 16);
},
+ function PC_REMOTE_CHECK_REMOVED_TRACK(test) {
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[0].receiver.track;
+
+ const vAdded = test.pcRemote.remoteMediaElements.find(
+ elem => elem.id.includes(track.id));
+ return helper.checkVideoPaused(vAdded, 10, 10, 16, 5000);
+ }
]
);
test.setMediaConstraints([{video: true}], [{video: true}]);
test.run();
});
</script>
</pre>
--- a/dom/media/tests/mochitest/test_peerConnection_removeThenAddVideoTrackNoBundle.html
+++ b/dom/media/tests/mochitest/test_peerConnection_removeThenAddVideoTrackNoBundle.html
@@ -8,16 +8,18 @@
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1017888",
title: "Renegotiation: remove then add video track, no bundle"
});
runNetworkTest(function (options) {
+ options = options || { };
+ options.bundle = false;
const test = new PeerConnectionTest(options);
const helper = new VideoStreamHelper();
var originalTrack;
addRenegotiation(test.chain,
[
function PC_REMOTE_FIND_RECEIVER(test) {
is(test.pcRemote._pc.getReceivers().length, 1,
"pcRemote should have one receiver");
@@ -28,33 +30,44 @@
// 0, but the remote side will keep its old pipeline and packet
// count.
test.pcLocal.disableRtpCountChecking = true;
return test.pcLocal.removeSender(0);
},
function PC_LOCAL_ADD_VIDEO_TRACK(test) {
// Use fake:true here since the native fake device on linux doesn't
// change color as needed by checkVideoPlaying() below.
- return test.pcLocal.getAllUserMedia([{video: true, fake: true}]);
+ return test.pcLocal.getAllUserMediaAndAddTracks([{video: true, fake: true}]);
+ },
+ function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+ test.pcLocal.expectIceChecking();
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.expectIceChecking();
},
],
[
function PC_REMOTE_CHECK_ADDED_TRACK(test) {
- is(test.pcRemote._pc.getReceivers().length, 1,
- "pcRemote should still have one receiver");
- const track = test.pcRemote._pc.getReceivers()[0].track;
- isnot(originalTrack.id, track.id, "Receiver should have changed");
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[1].receiver.track;
- const vOriginal = test.pcRemote.remoteMediaElements.find(
- elem => elem.id.includes(originalTrack.id));
const vAdded = test.pcRemote.remoteMediaElements.find(
elem => elem.id.includes(track.id));
- ok(vOriginal.ended, "Original video element should have ended");
return helper.checkVideoPlaying(vAdded, 10, 10, 16);
},
+ function PC_REMOTE_CHECK_REMOVED_TRACK(test) {
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[0].receiver.track;
+
+ const vAdded = test.pcRemote.remoteMediaElements.find(
+ elem => elem.id.includes(track.id));
+ return helper.checkVideoPaused(vAdded, 10, 10, 16, 5000);
+ },
]
);
test.chain.insertAfterEach('PC_LOCAL_CREATE_OFFER',
PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER);
test.setMediaConstraints([{video: true}], [{video: true}]);
test.run();
--- a/dom/media/tests/mochitest/test_peerConnection_removeVideoTrack.html
+++ b/dom/media/tests/mochitest/test_peerConnection_removeVideoTrack.html
@@ -1,12 +1,13 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1017888",
title: "Renegotiation: remove video track"
});
@@ -31,22 +32,24 @@
function PC_LOCAL_REMOVE_VIDEO_TRACK(test) {
test.setOfferOptions({ offerToReceiveVideo: true });
test.setMediaConstraints([], [{video: true}]);
return test.pcLocal.removeSender(0);
},
],
[
function PC_REMOTE_CHECK_FLOW_STOPPED(test) {
- is(test.pcRemote._pc.getReceivers().length, 0,
- "pcRemote should have no more receivers");
- is(receivedTrack.readyState, "ended",
- "The received track should have ended");
- is(element.ended, true,
- "Element playing the removed track should have ended");
+ is(test.pcRemote._pc.getTransceivers().length, 1,
+ "pcRemote should have one transceiver");
+ const track = test.pcRemote._pc.getTransceivers()[0].receiver.track;
+
+ const vAdded = test.pcRemote.remoteMediaElements.find(
+ elem => elem.id.includes(track.id));
+ const helper = new VideoStreamHelper();
+ return helper.checkVideoPaused(vAdded, 10, 10, 16, 5000);
},
]
);
test.setMediaConstraints([{video: true}], [{video: true}]);
test.run();
});
</script>
--- a/dom/media/tests/mochitest/test_peerConnection_replaceTrack.html
+++ b/dom/media/tests/mochitest/test_peerConnection_replaceTrack.html
@@ -42,29 +42,31 @@
return navigator.mediaDevices.getUserMedia({video:true, audio:true})
.then(newStream => {
window.grip = newStream;
newTrack = newStream.getVideoTracks()[0];
audiotrack = newStream.getAudioTracks()[0];
isnot(newTrack, sender.track, "replacing with a different track");
ok(!pc.getLocalStreams().some(s => s == newStream),
"from a different stream");
- return sender.replaceTrack(newTrack);
+ // Use wrapper function, since it updates expected tracks
+ return wrapper.senderReplaceTrack(sender, newTrack, newStream);
})
.then(() => {
is(pc.getSenders().length, oldSenderCount, "same sender count");
is(sender.track, newTrack, "sender.track has been replaced");
ok(!pc.getSenders().map(sn => sn.track).some(t => t == oldTrack),
"old track not among senders");
- ok(pc.getLocalStreams().some(s => s.getTracks()
+ // Spec does not say we add this new track to any stream
+ ok(!pc.getLocalStreams().some(s => s.getTracks()
.some(t => t == sender.track)),
- "track exists among pc's local streams");
+ "track does not exist among pc's local streams");
return sender.replaceTrack(audiotrack)
.then(() => ok(false, "replacing with different kind should fail"),
- e => is(e.name, "IncompatibleMediaStreamTrackError",
+ e => is(e.name, "TypeError",
"replacing with different kind should fail"));
});
}
runNetworkTest(function () {
test = new PeerConnectionTest();
test.audioCtx = new AudioContext();
test.setMediaConstraints([{video: true, audio: true}], [{video: true}]);
@@ -125,53 +127,64 @@
// (440Hz for loopback devices, 1kHz for fake tracks).
sourceNode.frequency.value = 2000;
sourceNode.start();
var destNode = test.audioCtx.createMediaStreamDestination();
sourceNode.connect(destNode);
var newTrack = destNode.stream.getAudioTracks()[0];
- return sender.replaceTrack(newTrack)
+ return test.pcLocal.senderReplaceTrack(
+ sender, newTrack, destNode.stream)
.then(() => {
is(pc.getSenders().length, oldSenderCount, "same sender count");
ok(!pc.getSenders().some(sn => sn.track == oldTrack),
"Replaced track should be removed from senders");
- ok(allLocalStreamsHaveSender(pc),
- "Shouldn't have any streams without a corresponding sender");
+ // TODO: Should PC remove local streams when there are no senders
+ // associated with it? getLocalStreams() isn't in the spec anymore,
+ // so I guess it is pretty arbitrary?
is(sender.track, newTrack, "sender.track has been replaced");
- ok(pc.getLocalStreams().some(s => s.getTracks()
+ // Spec does not say we add this new track to any stream
+ ok(!pc.getLocalStreams().some(s => s.getTracks()
.some(t => t == sender.track)),
"track exists among pc's local streams");
});
}
]);
test.chain.append([
function PC_LOCAL_CHECK_WEBAUDIO_FLOW_PRESENT(test) {
return test.pcRemote.checkReceivingToneFrom(test.audioCtx, test.pcLocal);
}
]);
test.chain.append([
function PC_LOCAL_INVALID_ADD_VIDEOTRACKS(test) {
- var stream = test.pcLocal._pc.getLocalStreams()[0];
- var track = stream.getVideoTracks()[0];
- try {
- test.pcLocal._pc.addTrack(track, stream);
- ok(false, "addTrack existing track should fail");
- } catch (e) {
- is(e.name, "InvalidParameterError",
- "addTrack existing track should fail");
- }
- try {
- test.pcLocal._pc.addTrack(track, stream);
- ok(false, "addTrack existing track should fail");
- } catch (e) {
- is(e.name, "InvalidParameterError",
- "addTrack existing track should fail");
- }
+ test.pcLocal._pc.getTransceivers()
+ .filter(transceiver => {
+ return !transceiver.stopped &&
+ transceiver.receiver.track.kind == "video" &&
+ transceiver.sender.track;
+ })
+ .forEach(transceiver => {
+ var stream = transceiver.sender.mozGetStreams()[0];
+ var track = transceiver.sender.track;
+ try {
+ test.pcLocal._pc.addTrack(track, stream);
+ ok(false, "addTrack existing track should fail");
+ } catch (e) {
+ is(e.name, "InvalidAccessError",
+ "addTrack existing track should fail");
+ }
+ try {
+ test.pcLocal._pc.addTrack(track, stream);
+ ok(false, "addTrack existing track should fail");
+ } catch (e) {
+ is(e.name, "InvalidAccessError",
+ "addTrack existing track should fail");
+ }
+ });
}
]);
test.run();
});
</script>
</pre>
</body>
</html>
--- a/dom/media/tests/mochitest/test_peerConnection_replaceVideoThenRenegotiate.html
+++ b/dom/media/tests/mochitest/test_peerConnection_replaceVideoThenRenegotiate.html
@@ -31,60 +31,38 @@
]);
addRenegotiation(test.chain,
[
function PC_LOCAL_REPLACE_VIDEO_TRACK_THEN_ADD_SECOND_STREAM(test) {
emitter1.stop();
emitter2.start();
const newstream = emitter2.stream();
const newtrack = newstream.getVideoTracks()[0];
- return test.pcLocal.senderReplaceTrack(0, newtrack, newstream.id)
+ var sender = test.pcLocal._pc.getSenders()[0];
+ return test.pcLocal.senderReplaceTrack(sender, newtrack, newstream)
.then(() => {
test.setMediaConstraints([{video: true}, {video: true}],
[{video: true}]);
- // Use fake:true here since the native fake device on linux
- // doesn't change color as needed by checkVideoPlaying() below.
- return test.pcLocal.getAllUserMedia([{video: true, fake: true}]);
});
},
],
[
- function PC_REMOTE_CHECK_ORIGINAL_TRACK_ENDED(test) {
+ function PC_REMOTE_CHECK_ORIGINAL_TRACK_NOT_ENDED(test) {
+ is(test.pcRemote._pc.getTransceivers().length, 1,
+ "pcRemote should have one transceiver");
+ const track = test.pcRemote._pc.getTransceivers()[0].receiver.track;
+
const vremote = test.pcRemote.remoteMediaElements.find(
- elem => elem.id.includes(emitter1.stream().getTracks()[0].id));
- if (!vremote) {
- return Promise.reject(new Error("Couldn't find video element"));
- }
- ok(vremote.ended, "Original track should have ended after renegotiation");
- },
- function PC_REMOTE_CHECK_REPLACED_TRACK_FLOW(test) {
- const vremote = test.pcRemote.remoteMediaElements.find(
- elem => elem.id.includes(test.pcLocal._pc.getSenders()[0].track.id));
+ elem => elem.id.includes(track.id));
if (!vremote) {
return Promise.reject(new Error("Couldn't find video element"));
}
- return addFinallyToPromise(helper.checkVideoPlaying(vremote, 10, 10, 16))
- .finally(() => emitter2.stop())
- .then(() => {
- const px = helper._helper.getPixel(vremote, 10, 10);
- const isBlue = helper._helper.isPixel(
- px, CaptureStreamTestHelper.prototype.blue, 5);
- const isGrey = helper._helper.isPixel(
- px, CaptureStreamTestHelper.prototype.grey, 5);
- ok(isBlue || isGrey, "replaced track should be blue or grey");
- });
- },
- function PC_REMOTE_CHECK_ADDED_TRACK_FLOW(test) {
- const vremote = test.pcRemote.remoteMediaElements.find(
- elem => elem.id.includes(test.pcLocal._pc.getSenders()[1].track.id));
- if (!vremote) {
- return Promise.reject(new Error("Couldn't find video element"));
- }
+ ok(!vremote.ended, "Original track should not have ended after renegotiation (replaceTrack is not signalled!)");
return helper.checkVideoPlaying(vremote, 10, 10, 16);
- },
+ }
]
);
test.run();
});
});
</script>
--- a/dom/media/tests/mochitest/test_peerConnection_scaleResolution.html
+++ b/dom/media/tests/mochitest/test_peerConnection_scaleResolution.html
@@ -17,71 +17,78 @@
var mustRejectWith = (msg, reason, f) =>
f().then(() => ok(false, msg),
e => is(e.name, reason, msg));
var removeAllButCodec = (d, codec) =>
(d.sdp = d.sdp.replace(/m=video (\w) UDP\/TLS\/RTP\/SAVPF \w.*\r\n/,
"m=video $1 UDP/TLS/RTP/SAVPF " + codec + "\r\n"), d);
- function testScale(codec) {
+ async function testScale(codec) {
var pc1 = new RTCPeerConnection();
var pc2 = new RTCPeerConnection();
var add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
pc1.onicecandidate = e => add(pc2, e.candidate, generateErrorCallback());
pc2.onicecandidate = e => add(pc1, e.candidate, generateErrorCallback());
info("testing scaling with " + codec);
- pc1.onnegotiationneeded = e =>
- pc1.createOffer()
- .then(d => pc1.setLocalDescription(codec == "VP8" ? d : removeAllButCodec(d, 126)))
- .then(() => pc2.setRemoteDescription(pc1.localDescription))
- .then(() => pc2.createAnswer()).then(d => pc2.setLocalDescription(d))
- .then(() => pc1.setRemoteDescription(pc2.localDescription))
- .catch(generateErrorCallback());
+
+ let stream = await navigator.mediaDevices.getUserMedia({ video: true });
+
+ var v1 = createMediaElement('video', 'v1');
+ var v2 = createMediaElement('video', 'v2');
+
+ var ontrackfired = new Promise(resolve => pc2.ontrack = e => resolve(e));
+ var v2loadedmetadata = new Promise(resolve => v2.onloadedmetadata = resolve);
+
+ is(v2.currentTime, 0, "v2.currentTime is zero at outset");
- return navigator.mediaDevices.getUserMedia({ video: true })
- .then(stream => {
- var v1 = createMediaElement('video', 'v1');
- var v2 = createMediaElement('video', 'v2');
+ v1.srcObject = stream;
+ var sender = pc1.addTrack(stream.getVideoTracks()[0], stream);
- is(v2.currentTime, 0, "v2.currentTime is zero at outset");
+ await mustRejectWith(
+ "Invalid scaleResolutionDownBy must reject", "RangeError",
+ () => sender.setParameters(
+ { encodings:[{ scaleResolutionDownBy: 0.5 } ] })
+ );
- v1.srcObject = stream;
- var sender = pc1.addTrack(stream.getVideoTracks()[0], stream);
+ await sender.setParameters({ encodings: [{ maxBitrate: 60000,
+ scaleResolutionDownBy: 2 }] });
- return mustRejectWith("Invalid scaleResolutionDownBy must reject", "RangeError",
- () => sender.setParameters({ encodings:
- [{ scaleResolutionDownBy: 0.5 } ] }))
- .then(() => sender.setParameters({ encodings: [{ maxBitrate: 60000,
- scaleResolutionDownBy: 2 }] }))
- .then(() => new Promise(resolve => pc2.ontrack = e => resolve(e)))
- .then(e => v2.srcObject = e.streams[0])
- .then(() => new Promise(resolve => v2.onloadedmetadata = resolve))
- .then(() => waitUntil(() => v2.currentTime > 0 && v2.srcObject.currentTime > 0))
- .then(() => ok(v2.currentTime > 0, "v2.currentTime is moving (" + v2.currentTime + ")"))
- .then(() => wait(3000)) // TODO: Bug 1248154
- .then(() => {
- ok(v1.videoWidth > 0, "source width is positive");
- ok(v1.videoHeight > 0, "source height is positive");
- if (v2.videoWidth == 640 && v2.videoHeight == 480) { // TODO: Bug 1248154
- info("Skipping test due to Bug 1248154");
- } else {
- is(v2.videoWidth, v1.videoWidth / 2, "sink is half the width of source");
- is(v2.videoHeight, v1.videoHeight / 2, "sink is half the height of source");
- }
- })
- .then(() => {
- stream.getTracks().forEach(track => track.stop());
- v1.srcObject = v2.srcObject = null;
- })
- })
- .catch(generateErrorCallback());
+ let offer = await pc1.createOffer();
+ await pc1.setLocalDescription(
+ codec == "VP8" ? offer : removeAllButCodec(offer, 126));
+ await pc2.setRemoteDescription(pc1.localDescription);
+
+ let answer = await pc2.createAnswer();
+ await pc2.setLocalDescription(answer);
+ await pc1.setRemoteDescription(pc2.localDescription);
+ let trackevent = await ontrackfired;
+
+ v2.srcObject = trackevent.streams[0];
+
+ await v2loadedmetadata;
+
+ await waitUntil(() => v2.currentTime > 0 && v2.srcObject.currentTime > 0);
+ ok(v2.currentTime > 0, "v2.currentTime is moving (" + v2.currentTime + ")");
+
+ await wait(3000); // TODO: Bug 1248154
+
+ ok(v1.videoWidth > 0, "source width is positive");
+ ok(v1.videoHeight > 0, "source height is positive");
+ if (v2.videoWidth == 640 && v2.videoHeight == 480) { // TODO: Bug 1248154
+ info("Skipping test due to Bug 1248154");
+ } else {
+ is(v2.videoWidth, v1.videoWidth / 2, "sink is half the width of source");
+ is(v2.videoHeight, v1.videoHeight / 2, "sink is half the height of source");
+ }
+ stream.getTracks().forEach(track => track.stop());
+ v1.srcObject = v2.srcObject = null;
}
pushPrefs(['media.peerconnection.video.lock_scaling', true]).then(() => {
if (!navigator.appVersion.includes("Android")) {
runNetworkTest(() => testScale("VP8").then(() => testScale("H264"))
.then(networkTestFinished));
} else {
// No support for H.264 on Android in automation, see Bug 1355786
--- a/dom/media/tests/mochitest/test_peerConnection_setParameters.html
+++ b/dom/media/tests/mochitest/test_peerConnection_setParameters.html
@@ -12,20 +12,21 @@ createHTML({
visible: true
});
function parameterstest(pc) {
ok(pc.getSenders().length > 0, "have senders");
var sender = pc.getSenders()[0];
var testParameters = (params, errorName, errorMsg) => {
+ info("Trying to set " + JSON.stringify(params));
var validateParameters = (a, b) => {
var validateEncoding = (a, b) => {
- is(a.rid, b.rid || "", "same rid");
+ is(a.rid, b.rid, "same rid");
is(a.maxBitrate, b.maxBitrate, "same maxBitrate");
is(a.scaleResolutionDownBy, b.scaleResolutionDownBy,
"same scaleResolutionDownBy");
};
is(a.encodings.length, (b.encodings || []).length, "same encodings");
a.encodings.forEach((en, i) => validateEncoding(en, b.encodings[i]));
};
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_transceivers.html
@@ -0,0 +1,976 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1290948",
+ title: "Transceivers API tests"
+ });
+
+ var logExpected = (expected) => {
+ info("(expected " + JSON.stringify(expected) + ")");
+ };
+
+ var dictCompare = (observed, expected) => {
+
+ if (observed === expected) {
+ return true;
+ }
+
+ // If we are expecting an array, iterate over it
+ if (Array.isArray(expected)) {
+ if (!Array.isArray(observed)) {
+ ok(false, "Expected an array, but didn't get one.");
+ logExpected(expected);
+ return false;
+ }
+
+ if (observed.length !== expected.length) {
+ ok(false, "Expected array to be " + expected.length + " long, but it was " + observed.length + " long instead");
+ logExpected(expected);
+ return false;
+ }
+
+ for (var i = 0; i < expected.length; i++) {
+ if (!dictCompare(observed[i], expected[i])) {
+ logExpected(expected);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // If we are expecting an object, check its props
+ if (typeof expected === "object" && expected !== null) {
+ var propsWeCareAbout = Object.getOwnPropertyNames(expected);
+ for (var i in propsWeCareAbout) {
+ var prop = propsWeCareAbout[i];
+ if (!dictCompare(observed[prop], expected[prop])) {
+ logExpected(expected);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ok(false, "Expected (" + JSON.stringify(expected) + ") did not match " +
+ "observed (" + JSON.stringify(observed) + ")");
+ return false;
+ };
+
+ var checkAddTransceiverNoTrack = async function(options) {
+ var pc = new RTCPeerConnection();
+ dictCompare(pc.getTransceivers(), []);
+
+ pc.addTransceiver("audio");
+ pc.addTransceiver("video");
+
+ dictCompare(pc.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: null},
+ direction: "sendrecv",
+ mid: null,
+ currentDirection: null,
+ stopped: false
+ },
+ {
+ receiver: {track: {kind: "video"}},
+ sender: {track: null},
+ direction: "sendrecv",
+ mid: null,
+ currentDirection: null,
+ stopped: false
+ }
+ ]);
+
+ pc.close();
+ };
+
+ var checkAddTransceiverWithTrack = async function(options) {
+ var pc = new RTCPeerConnection();
+ dictCompare(pc.getTransceivers(), []);
+
+ var stream = await getUserMedia({audio: true, video: true});
+ var audio = stream.getAudioTracks()[0];
+ var video = stream.getVideoTracks()[0];
+
+ pc.addTransceiver(audio);
+ pc.addTransceiver(video);
+
+ dictCompare(pc.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: audio},
+ direction: "sendrecv",
+ mid: null,
+ currentDirection: null,
+ stopped: false
+ },
+ {
+ receiver: {track: {kind: "video"}},
+ sender: {track: video},
+ direction: "sendrecv",
+ mid: null,
+ currentDirection: null,
+ stopped: false
+ }
+ ]);
+
+ pc.close();
+ };
+
+ var checkAddTransceiverWithAddTrack = async function(options) {
+ var pc = new RTCPeerConnection();
+ dictCompare(pc.getTransceivers(), []);
+
+ var stream = await getUserMedia({audio: true, video: true});
+ var audio = stream.getAudioTracks()[0];
+ var video = stream.getVideoTracks()[0];
+
+ pc.addTrack(audio, stream);
+ pc.addTrack(video, stream);
+
+ dictCompare(pc.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: audio},
+ direction: "sendrecv",
+ mid: null,
+ currentDirection: null,
+ stopped: false
+ },
+ {
+ receiver: {track: {kind: "video"}},
+ sender: {track: video},
+ direction: "sendrecv",
+ mid: null,
+ currentDirection: null,
+ stopped: false
+ }
+ ]);
+
+ pc.close();
+ };
+
+ var checkAddTransceiverWithDirection = async function(options) {
+ var pc = new RTCPeerConnection();
+ dictCompare(pc.getTransceivers(), []);
+
+ pc.addTransceiver("audio", {direction: "recvonly"});
+ pc.addTransceiver("video", {direction: "recvonly"});
+
+ dictCompare(pc.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: null},
+ direction: "recvonly",
+ mid: null,
+ currentDirection: null,
+ stopped: false
+ },
+ {
+ receiver: {track: {kind: "video"}},
+ sender: {track: null},
+ direction: "recvonly",
+ mid: null,
+ currentDirection: null,
+ stopped: false
+ }
+ ]);
+
+ pc.close();
+ };
+
+ var checkAddTransceiverWithStream = async function(options) {
+ var pc = new RTCPeerConnection();
+ dictCompare(pc.getTransceivers(), []);
+
+ var audioStream = await getUserMedia({audio: true});
+ var videoStream = await getUserMedia({video: true});
+ var audio = audioStream.getAudioTracks()[0];
+ var video = videoStream.getVideoTracks()[0];
+
+ pc.addTransceiver(audio, {streams: [audioStream]});
+ pc.addTransceiver(video, {streams: [videoStream]});
+
+ dictCompare(pc.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: audio},
+ direction: "sendrecv",
+ mid: null,
+ currentDirection: null,
+ stopped: false
+ },
+ {
+ receiver: {track: {kind: "video"}},
+ sender: {track: video},
+ direction: "sendrecv",
+ mid: null,
+ currentDirection: null,
+ stopped: false
+ }
+ ]);
+
+ var offer = await pc.createOffer();
+ ok(offer.sdp.includes("a=msid:" + audioStream.id + " " + audio.id),
+ "offer contains the expected audio msid");
+ ok(offer.sdp.includes("a=msid:" + videoStream.id + " " + video.id),
+ "offer contains the expected video msid");
+
+ pc.close();
+ };
+
+ var checkAddTransceiverWithOfferToReceive = async function(options, kind) {
+ var pc = new RTCPeerConnection();
+
+ if (kind == "audio") {
+ pc.createOffer({offerToReceiveAudio: true});
+ } else {
+ pc.createOffer({offerToReceiveVideo: true});
+ }
+
+ dictCompare(pc.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: kind}},
+ sender: {track: null},
+ direction: "recvonly",
+ mid: null,
+ currentDirection: null,
+ stopped: false
+ }
+ ]);
+
+ pc.close();
+ };
+
+ var checkAddTransceiverWithSetRemoteOfferSending = async function(options) {
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+
+ var stream = await getUserMedia({audio: true});
+ var track = stream.getAudioTracks()[0];
+ pc1.addTransceiver(track);
+
+ var offer = await pc1.createOffer();
+ await pc2.setRemoteDescription(offer);
+
+ dictCompare(pc2.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: null},
+ // rtcweb-jsep says this is recvonly, w3c-webrtc does not...
+ direction: "recvonly",
+ mid: "sdparta_0",
+ currentDirection: "inactive",
+ stopped: false
+ }
+ ]);
+
+ pc1.close();
+ pc2.close();
+ };
+
+ var checkAddTransceiverWithSetRemoteOfferNoSend = async function(options) {
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+
+ var stream = await getUserMedia({audio: true});
+ var track = stream.getAudioTracks()[0];
+ pc1.addTransceiver(track);
+ pc1.getTransceivers()[0].setDirection("recvonly");
+
+ var offer = await pc1.createOffer();
+ await pc2.setRemoteDescription(offer);
+
+ dictCompare(pc2.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: null},
+ // rtcweb-jsep says this is recvonly, w3c-webrtc does not...
+ direction: "recvonly",
+ mid: "sdparta_0",
+ currentDirection: "inactive",
+ stopped: false
+ }
+ ]);
+
+ pc1.close();
+ pc2.close();
+ };
+
+ var checkAddTransceiverBadKind = async function(options) {
+ var pc = new RTCPeerConnection();
+ try {
+ pc.addTransceiver("foo");
+ ok(false, 'addTransceiver("foo") throws');
+ }
+ catch (e if e instanceof TypeError) {
+ ok(true, 'addTransceiver("foo") throws a TypeError');
+ }
+ catch (e) {
+ ok(false, 'addTransceiver("foo") throws a TypeError');
+ }
+
+ dictCompare(pc.getTransceivers(), []);
+
+ pc.close();
+ };
+
+ var checkAddTransceiverNoTrackDoesntPair = async function(options) {
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+ pc1.addTransceiver("audio");
+ pc2.addTransceiver("audio");
+
+ var offer = await pc1.createOffer();
+ await pc2.setRemoteDescription(offer);
+ dictCompare(pc2.getTransceivers(),
+ [
+ {mid: null}, // no addTrack magic, doesn't auto-pair
+ {mid: "sdparta_0"} // Created by SRD
+ ]);
+
+ pc1.close();
+ pc2.close();
+ };
+
+ var checkAddTransceiverWithTrackDoesntPair = async function(options) {
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+ pc1.addTransceiver("audio");
+
+ var stream = await getUserMedia({audio: true});
+ var track = stream.getAudioTracks()[0];
+ pc2.addTransceiver(track);
+
+ var offer = await pc1.createOffer();
+ await pc2.setRemoteDescription(offer);
+ dictCompare(pc2.getTransceivers(),
+ [
+ {mid: null, sender: {track: track}},
+ {mid: "sdparta_0", sender: {track: null}} // Created by SRD
+ ]);
+
+ pc1.close();
+ pc2.close();
+ };
+
+ var checkAddTransceiverThenReplaceTrackDoesntPair = async function(options) {
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+ pc1.addTransceiver("audio");
+ pc2.addTransceiver("audio");
+
+ var stream = await getUserMedia({audio: true});
+ var track = stream.getAudioTracks()[0];
+ pc2.getTransceivers()[0].sender.replaceTrack(track);
+
+ var offer = await pc1.createOffer();
+ await pc2.setRemoteDescription(offer);
+ dictCompare(pc2.getTransceivers(),
+ [
+ {mid: null, sender: {track: track}},
+ {mid: "sdparta_0", sender: {track: null}} // Created by SRD
+ ]);
+
+ pc1.close();
+ pc2.close();
+ };
+
+ var checkAddTransceiverThenAddTrackPairs = async function(options) {
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+ pc1.addTransceiver("audio");
+ pc2.addTransceiver("audio");
+
+ var stream = await getUserMedia({audio: true});
+ var track = stream.getAudioTracks()[0];
+ pc2.addTrack(track, stream);
+
+ var offer = await pc1.createOffer();
+ await pc2.setRemoteDescription(offer);
+ dictCompare(pc2.getTransceivers(),
+ [
+ {mid: "sdparta_0", sender: {track: track}}
+ ]);
+
+ pc1.close();
+ pc2.close();
+ };
+
+ var checkAddTrackPairs = async function(options) {
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+ pc1.addTransceiver("audio");
+
+ var stream = await getUserMedia({audio: true});
+ var track = stream.getAudioTracks()[0];
+ pc2.addTrack(track, stream);
+
+ var offer = await pc1.createOffer();
+ await pc2.setRemoteDescription(offer);
+ dictCompare(pc2.getTransceivers(),
+ [
+ {mid: "sdparta_0", sender: {track: track}}
+ ]);
+
+ pc1.close();
+ pc2.close();
+ };
+
+ var checkReplaceTrackNullDoesntPreventPairing = async function(options) {
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+ pc1.addTransceiver("audio");
+
+ var stream = await getUserMedia({audio: true});
+ var track = stream.getAudioTracks()[0];
+ pc2.addTrack(track, stream);
+ pc2.getTransceivers()[0].sender.replaceTrack(null);
+
+ var offer = await pc1.createOffer();
+ await pc2.setRemoteDescription(offer);
+ dictCompare(pc2.getTransceivers(),
+ [
+ {mid: "sdparta_0", sender: {track: track}}
+ ]);
+
+ pc1.close();
+ pc2.close();
+ };
+
+ var checkSetDirection = async function(options) {
+ var pc = new RTCPeerConnection();
+ pc.addTransceiver("audio");
+
+ pc.getTransceivers()[0].setDirection("sendonly");
+ dictCompare(pc.getTransceivers(),[{direction: "sendonly"}]);
+ pc.getTransceivers()[0].setDirection("recvonly");
+ dictCompare(pc.getTransceivers(),[{direction: "recvonly"}]);
+ pc.getTransceivers()[0].setDirection("inactive");
+ dictCompare(pc.getTransceivers(),[{direction: "inactive"}]);
+ pc.getTransceivers()[0].setDirection("sendrecv");
+ dictCompare(pc.getTransceivers(),[{direction: "sendrecv"}]);
+
+ pc.close();
+ };
+
+ var checkCurrentDirection = async function(options) {
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+
+ var stream = await getUserMedia({audio: true});
+ var track = stream.getAudioTracks()[0];
+ pc1.addTrack(track, stream);
+ pc2.addTrack(track, stream);
+ dictCompare(pc1.getTransceivers(), [{currentDirection: null}]);
+
+ var offer = await pc1.createOffer();
+ dictCompare(pc1.getTransceivers(), [{currentDirection: null}]);
+
+ await pc1.setLocalDescription(offer);
+ // Debatable, spec is kinda vague
+ dictCompare(pc1.getTransceivers(), [{currentDirection: "inactive"}]);
+
+ await pc2.setRemoteDescription(offer);
+ // Debatable, spec is kinda vague
+ dictCompare(pc2.getTransceivers(), [{currentDirection: "inactive"}]);
+
+ var answer = await pc2.createAnswer();
+ dictCompare(pc2.getTransceivers(), [{currentDirection: "inactive"}]);
+
+ await pc2.setLocalDescription(answer);
+ dictCompare(pc2.getTransceivers(), [{currentDirection: "sendrecv"}]);
+
+ await pc1.setRemoteDescription(answer);
+ dictCompare(pc1.getTransceivers(), [{currentDirection: "sendrecv"}]);
+
+ pc2.getTransceivers()[0].setDirection("sendonly");
+
+ offer = await pc2.createOffer();
+ dictCompare(pc2.getTransceivers(), [{currentDirection: "sendrecv"}]);
+
+ await pc2.setLocalDescription(offer);
+ // Debatable, spec is kinda vague
+ dictCompare(pc2.getTransceivers(), [{currentDirection: "sendrecv"}]);
+
+ await pc1.setRemoteDescription(offer);
+ // Debatable, spec is kinda vague
+ dictCompare(pc1.getTransceivers(), [{currentDirection: "sendrecv"}]);
+
+ answer = await pc1.createAnswer();
+ dictCompare(pc1.getTransceivers(), [{currentDirection: "sendrecv"}]);
+
+ await pc1.setLocalDescription(answer);
+ dictCompare(pc1.getTransceivers(), [{currentDirection: "recvonly"}]);
+
+ await pc2.setRemoteDescription(answer);
+ dictCompare(pc2.getTransceivers(), [{currentDirection: "sendonly"}]);
+
+ pc1.close();
+ pc2.close();
+ };
+
+ var checkStop = async function(options) {
+ var pc1 = new RTCPeerConnection();
+ var stream = await getUserMedia({audio: true});
+ var track = stream.getAudioTracks()[0];
+ pc1.addTrack(track, stream);
+
+ var offer = await pc1.createOffer();
+ await pc1.setLocalDescription(offer);
+
+ var pc2 = new RTCPeerConnection();
+ await pc2.setRemoteDescription(offer);
+
+ stream = await getUserMedia({audio: true});
+ track = stream.getAudioTracks()[0];
+ pc2.addTrack(track, stream);
+
+ var answer = await pc2.createAnswer();
+ await pc2.setLocalDescription(answer);
+ await pc1.setRemoteDescription(answer);
+
+ pc1.getTransceivers()[0].stop();
+
+ dictCompare(pc1.getTransceivers(),
+ [
+ {
+ sender: {track: {kind: "audio"}},
+ receiver: {track: {kind: "audio"}},
+ stopped: true,
+ mid: "sdparta_0",
+ currentDirection: null,
+ direction: "sendrecv" // Debatable?
+ }
+ ]);
+
+ offer = await pc1.createOffer();
+ await pc1.setLocalDescription(offer);
+ await pc2.setRemoteDescription(offer);
+
+ dictCompare(pc2.getTransceivers(),
+ [
+ {
+ sender: {track: {kind: "audio"}},
+ receiver: {track: {kind: "audio"}},
+ stopped: true,
+ mid: null,
+ currentDirection: null,
+ direction: "sendrecv"
+ }
+ ]);
+
+ pc1.close();
+ pc2.close();
+ };
+
+ var checkRollback = async function(options) {
+ var pc1 = new RTCPeerConnection();
+
+ var stream = await getUserMedia({audio: true});
+ var track = stream.getAudioTracks()[0];
+ pc1.addTrack(track, stream);
+
+ var offer = await pc1.createOffer();
+ await pc1.setLocalDescription(offer);
+
+ dictCompare(pc1.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: track},
+ direction: "sendrecv",
+ mid: "sdparta_0",
+ currentDirection: "inactive",
+ stopped: false
+ }
+ ]);
+
+ // Verify that rollback doesn't stomp things it should not
+ pc1.getTransceivers()[0].setDirection("sendonly");
+ stream = await getUserMedia({audio: true});
+ track = stream.getAudioTracks()[0];
+ await pc1.getTransceivers()[0].sender.replaceTrack(track);
+
+ await pc1.setLocalDescription({sdp: "", type: "rollback"});
+
+ dictCompare(pc1.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: track},
+ direction: "sendonly",
+ mid: null,
+ currentDirection: "inactive",
+ stopped: false
+ }
+ ]);
+
+ var pc2 = new RTCPeerConnection();
+
+ await pc2.setRemoteDescription(offer);
+ dictCompare(pc2.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: null},
+ direction: "recvonly",
+ mid: "sdparta_0",
+ currentDirection: "inactive",
+ stopped: false
+ }
+ ]);
+
+ await pc2.setRemoteDescription({sdp: "", type: "rollback"});
+
+ // Transceiver should be _gone_
+ dictCompare(pc2.getTransceivers(), []);
+
+ await pc2.setRemoteDescription(offer);
+ dictCompare(pc2.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: null},
+ direction: "recvonly",
+ mid: "sdparta_0",
+ currentDirection: "inactive",
+ stopped: false
+ }
+ ]);
+
+ stream = await getUserMedia({audio: true});
+ track = stream.getAudioTracks()[0];
+ await pc2.getTransceivers()[0].sender.replaceTrack(track);
+ pc2.getTransceivers()[0].setDirection("sendrecv");
+ dictCompare(pc2.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: track},
+ direction: "sendrecv",
+ mid: "sdparta_0",
+ currentDirection: "inactive",
+ stopped: false
+ }
+ ]);
+
+ await pc2.setRemoteDescription({sdp: "", type: "rollback"});
+
+ // Transceiver should be _gone_, again. replaceTrack doesn't prevent this,
+ // nor does setDirection.
+ dictCompare(pc2.getTransceivers(), []);
+
+ await pc2.setRemoteDescription(offer);
+ dictCompare(pc2.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: null},
+ direction: "recvonly",
+ mid: "sdparta_0",
+ currentDirection: "inactive",
+ stopped: false
+ }
+ ]);
+
+ pc2.addTrack(track, stream);
+ dictCompare(pc2.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: track},
+ direction: "sendrecv",
+ mid: "sdparta_0",
+ currentDirection: "inactive",
+ stopped: false
+ }
+ ]);
+
+ await pc2.setRemoteDescription({sdp: "", type: "rollback"});
+ // Transceiver should _not_ be gone this time, because addTrack touched it.
+ dictCompare(pc2.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: track},
+ direction: "sendrecv",
+ mid: null,
+ currentDirection: "inactive",
+ stopped: false
+ }
+ ]);
+
+ // Complete negotiation so we can test interactions with transceiver.stop()
+ offer = await pc1.createOffer();
+ await pc1.setLocalDescription(offer);
+ await pc2.setRemoteDescription(offer);
+ var answer = await pc2.createAnswer();
+ await pc2.setLocalDescription(answer);
+ await pc1.setRemoteDescription(answer);
+
+ // Don't bother waiting for ICE and such
+
+ offer = await pc1.createOffer();
+ await pc1.setLocalDescription(offer);
+ pc1.getTransceivers()[0].stop();
+ await pc1.setLocalDescription({sdp: "", type: "rollback"});
+ dictCompare(pc1.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: {kind: "audio"}},
+ direction: "sendonly",
+ mid: "sdparta_0",
+ currentDirection: null,
+ stopped: true
+ }
+ ]);
+
+ offer = await pc1.createOffer();
+
+ await pc2.setRemoteDescription(offer);
+ dictCompare(pc2.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: {kind: "audio"}},
+ direction: "sendrecv",
+ mid: null,
+ currentDirection: null,
+ stopped: true
+ }
+ ]);
+
+ // stop() cannot be rolled back!
+ await pc2.setRemoteDescription({sdp: "", type: "rollback"});
+ dictCompare(pc2.getTransceivers(),
+ [
+ {
+ receiver: {track: {kind: "audio"}},
+ sender: {track: {kind: "audio"}},
+ direction: "sendrecv",
+ mid: null,
+ currentDirection: null,
+ stopped: true
+ }
+ ]);
+
+ pc1.close();
+ pc2.close();
+ };
+
+ var checkMsectionReuse = async function(options) {
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+
+ var stream = await getUserMedia({audio: true});
+ var track = stream.getAudioTracks()[0];
+ pc1.addTrack(track, stream);
+
+ var offer = await pc1.createOffer();
+ await pc1.setLocalDescription(offer);
+ await pc2.setRemoteDescription(offer);
+
+ // answerer stops transceiver to reject m-section
+ pc2.getTransceivers()[0].stop();
+
+ var answer = await pc2.createAnswer();
+ await pc2.setLocalDescription(answer);
+ await pc1.setRemoteDescription(answer);
+
+ dictCompare(pc1.getTransceivers(),
+ [
+ {
+ mid: null,
+ currentDirection: null,
+ stopped: true
+ }
+ ]);
+
+ dictCompare(pc2.getTransceivers(),
+ [
+ {
+ mid: null,
+ currentDirection: null,
+ stopped: true
+ }
+ ]);
+
+ // Check that m-section is reused on both ends
+ stream = await getUserMedia({audio: true});
+ track = stream.getAudioTracks()[0];
+
+ pc1.addTrack(track, stream);
+ offer = await pc1.createOffer();
+ // Exactly one m= line
+ is(1, offer.sdp.match(/m=/g).length);
+ dictCompare(pc1.getTransceivers(),
+ [
+ {
+ stopped: true
+ },
+ {
+ sender: {track: track}
+ }
+ ]);
+
+
+ pc2.addTrack(track, stream);
+ offer = await pc2.createOffer();
+ // Exactly one m= line
+ is(1, offer.sdp.match(/m=/g).length);
+ dictCompare(pc2.getTransceivers(),
+ [
+ {
+ stopped: true
+ },
+ {
+ sender: {track: track}
+ }
+ ]);
+
+ await pc2.setLocalDescription(offer);
+ await pc1.setRemoteDescription(offer);
+ answer = await pc1.createAnswer();
+ await pc1.setLocalDescription(answer);
+ await pc2.setRemoteDescription(answer);
+ dictCompare(pc1.getTransceivers(),
+ [
+ {},
+ {
+ sender: {track: track},
+ currentDirection: "sendrecv"
+ }
+ ]);
+
+ dictCompare(pc2.getTransceivers(),
+ [
+ {},
+ {
+ sender: {track: track},
+ currentDirection: "sendrecv"
+ }
+ ]);
+
+ // stop the transceiver, and add a track. Verify that we don't reuse
+ // prematurely in our offer. (There should be one rejected m-section, and a
+ // new one for the new track)
+ pc1.getTransceivers()[1].stop();
+ stream = await getUserMedia({audio: true});
+ track = stream.getAudioTracks()[0];
+ pc1.addTrack(track, stream);
+ offer = await pc1.createOffer();
+ // Exactly two m= lines
+ is(2, offer.sdp.match(/m=/g).length);
+
+ await pc1.setLocalDescription(offer);
+ await pc2.setRemoteDescription(offer);
+ answer = await pc2.createAnswer();
+ await pc2.setLocalDescription(answer);
+ await pc1.setRemoteDescription(answer);
+
+ dictCompare(pc2.getTransceivers(),
+ [
+ {},
+ {
+ stopped: true
+ },
+ {
+ mid: "sdparta_1",
+ sender: {track: null},
+ currentDirection: "recvonly"
+ }
+ ]);
+
+ pc2.addTrack(track, stream);
+ // There are two ways to handle this new track; reuse the recvonly
+ // transceiver created above, or create a new transceiver and reuse the
+ // disabled m-section. We're supposed to do the former.
+ offer = await pc2.createOffer();
+ // Exactly two m= lines, as before
+ is(2, offer.sdp.match(/m=/g).length);
+
+ dictCompare(pc2.getTransceivers(),
+ [
+ {},
+ {
+ stopped: true
+ },
+ {
+ mid: "sdparta_1",
+ sender: {track: track},
+ currentDirection: "recvonly",
+ direction: "sendrecv"
+ }
+ ]);
+
+ // Add _another_ track; this should reuse the disabled m-section
+ stream = await getUserMedia({audio: true});
+ track = stream.getAudioTracks()[0];
+ pc2.addTrack(track, stream);
+ offer = await pc2.createOffer();
+ await pc2.setLocalDescription(offer);
+ dictCompare(pc2.getTransceivers(),
+ [
+ {}, {},
+ {
+ mid: "sdparta_1",
+ },
+ {
+ sender: {track: track},
+ mid: "sdparta_0"
+ }
+ ]);
+ // Exactly two m= lines, as before
+ is(2, offer.sdp.match(/m=/g).length);
+
+ pc1.close();
+ pc2.close();
+ };
+
+ runNetworkTest(async function (options) {
+ await checkAddTransceiverNoTrack(options);
+ await checkAddTransceiverWithTrack(options);
+ await checkAddTransceiverWithAddTrack(options);
+ await checkAddTransceiverWithDirection(options);
+ await checkAddTransceiverWithStream(options);
+ await checkAddTransceiverWithOfferToReceive(options, "audio");
+ await checkAddTransceiverWithOfferToReceive(options, "video");
+ await checkAddTransceiverWithSetRemoteOfferSending(options);
+ await checkAddTransceiverWithSetRemoteOfferNoSend(options);
+ await checkAddTransceiverBadKind(options);
+ await checkSetDirection(options);
+ await checkCurrentDirection(options);
+ await checkAddTransceiverNoTrackDoesntPair(options);
+ await checkAddTransceiverWithTrackDoesntPair(options);
+ await checkAddTransceiverThenReplaceTrackDoesntPair(options);
+ await checkAddTransceiverThenAddTrackPairs(options);
+ await checkAddTrackPairs(options);
+ await checkReplaceTrackNullDoesntPreventPairing(options);
+ await checkStop(options);
+ await checkRollback(options);
+ await checkMsectionReuse(options);
+ return networkTestFinished();
+ });
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/tests/mochitest/test_peerConnection_twoAudioTracksInOneStream.html
+++ b/dom/media/tests/mochitest/test_peerConnection_twoAudioTracksInOneStream.html
@@ -14,35 +14,23 @@
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
test.chain.insertAfter("PC_REMOTE_GET_OFFER", [
function PC_REMOTE_OVERRIDE_STREAM_IDS_IN_OFFER(test) {
test._local_offer.sdp = test._local_offer.sdp.replace(
/a=msid:[^\s]*/g,
"a=msid:foo");
- },
- function PC_REMOTE_OVERRIDE_EXPECTED_STREAM_IDS(test) {
- Object.keys(
- test.pcRemote.expectedRemoteTrackInfoById).forEach(trackId => {
- test.pcRemote.expectedRemoteTrackInfoById[trackId].streamId = "foo";
- });
}
]);
test.chain.insertAfter("PC_LOCAL_GET_ANSWER", [
function PC_LOCAL_OVERRIDE_STREAM_IDS_IN_ANSWER(test) {
test._remote_answer.sdp = test._remote_answer.sdp.replace(
/a=msid:[^\s]*/g,
"a=msid:foo");
- },
- function PC_LOCAL_OVERRIDE_EXPECTED_STREAM_IDS(test) {
- Object.keys(
- test.pcLocal.expectedRemoteTrackInfoById).forEach(trackId => {
- test.pcLocal.expectedRemoteTrackInfoById[trackId].streamId = "foo";
- });
}
]);
test.setMediaConstraints([{audio: true}, {audio: true}],
[{audio: true}, {audio: true}]);
test.run();
});
</script>
</pre>
--- a/dom/media/tests/mochitest/test_peerConnection_twoVideoTracksInOneStream.html
+++ b/dom/media/tests/mochitest/test_peerConnection_twoVideoTracksInOneStream.html
@@ -14,35 +14,23 @@
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
test.chain.insertAfter("PC_REMOTE_GET_OFFER", [
function PC_REMOTE_OVERRIDE_STREAM_IDS_IN_OFFER(test) {
test._local_offer.sdp = test._local_offer.sdp.replace(
/a=msid:[^\s]*/g,
"a=msid:foo");
- },
- function PC_REMOTE_OVERRIDE_EXPECTED_STREAM_IDS(test) {
- Object.keys(
- test.pcRemote.expectedRemoteTrackInfoById).forEach(trackId => {
- test.pcRemote.expectedRemoteTrackInfoById[trackId].streamId = "foo";
- });
}
]);
test.chain.insertAfter("PC_LOCAL_GET_ANSWER", [
function PC_LOCAL_OVERRIDE_STREAM_IDS_IN_ANSWER(test) {
test._remote_answer.sdp = test._remote_answer.sdp.replace(
/a=msid:[^\s]*/g,
"a=msid:foo");
- },
- function PC_LOCAL_OVERRIDE_EXPECTED_STREAM_IDS(test) {
- Object.keys(
- test.pcLocal.expectedRemoteTrackInfoById).forEach(trackId => {
- test.pcLocal.expectedRemoteTrackInfoById[trackId].streamId = "foo";
- });
}
]);
test.setMediaConstraints([{video: true}, {video: true}],
[{video: true}, {video: true}]);
test.run();
});
</script>
</pre>
--- a/dom/media/tests/mochitest/test_peerConnection_verifyAudioAfterRenegotiation.html
+++ b/dom/media/tests/mochitest/test_peerConnection_verifyAudioAfterRenegotiation.html
@@ -43,17 +43,17 @@
}
]);
addRenegotiation(test.chain,
[
function PC_LOCAL_ADD_SECOND_STREAM(test) {
test.setMediaConstraints([{audio: true}],
[]);
- return test.pcLocal.getAllUserMedia([{audio: true}]);
+ return test.pcLocal.getAllUserMediaAndAddTracks([{audio: true}]);
},
]
);
test.chain.append([
function CHECK_ASSUMPTIONS2() {
is(test.pcLocal.localMediaElements.length, 2,
"pcLocal should have two media elements");
--- a/dom/media/tests/mochitest/test_peerConnection_verifyVideoAfterRenegotiation.html
+++ b/dom/media/tests/mochitest/test_peerConnection_verifyVideoAfterRenegotiation.html
@@ -69,17 +69,17 @@ runNetworkTest(() => {
addRenegotiation(test.chain,
[
function PC_LOCAL_ADD_SECOND_STREAM(test) {
canvas2 = h2.createAndAppendElement('canvas', 'source_canvas2');
h2.drawColor(canvas2, h2.blue);
stream2 = canvas2.captureStream(0);
- // can't use test.pcLocal.getAllUserMedia([{video: true}]);
+ // can't use test.pcLocal.getAllUserMediaAndAddTracks([{video: true}]);
// because it doesn't let us substitute the capture stream
test.pcLocal.attachLocalStream(stream2);
}
]
);
test.chain.append([
function FIND_REMOTE2_VIDEO() {
--- a/dom/webidl/MediaStream.webidl
+++ b/dom/webidl/MediaStream.webidl
@@ -35,9 +35,14 @@ interface MediaStream : EventTarget {
MediaStreamTrack? getTrackById (DOMString trackId);
void addTrack (MediaStreamTrack track);
void removeTrack (MediaStreamTrack track);
MediaStream clone ();
readonly attribute boolean active;
attribute EventHandler onaddtrack;
// attribute EventHandler onremovetrack;
readonly attribute double currentTime;
+
+ // Webrtc allows the remote side to name a stream whatever it wants, and we
+ // need to surface this to content.
+ [ChromeOnly]
+ void assignId(DOMString id);
};
deleted file mode 100644
--- a/dom/webidl/MediaStreamList.webidl
+++ /dev/null
@@ -1,11 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-[ChromeOnly]
-interface MediaStreamList {
- getter MediaStream? (unsigned long index);
- readonly attribute unsigned long length;
-};
--- a/dom/webidl/PeerConnectionImpl.webidl
+++ b/dom/webidl/PeerConnectionImpl.webidl
@@ -40,34 +40,32 @@ interface PeerConnectionImpl {
void getStats(MediaStreamTrack? selector);
/* Adds the tracks created by GetUserMedia */
[Throws]
void addTrack(MediaStreamTrack track, MediaStream... streams);
[Throws]
void removeTrack(MediaStreamTrack track);
[Throws]
- void insertDTMF(RTCRtpSender sender, DOMString tones,
+ TransceiverImpl createTransceiverImpl(DOMString kind,
+ MediaStreamTrack? track);
+ [Throws]
+ boolean checkNegotiationNeeded();
+ [Throws]
+ void insertDTMF(TransceiverImpl transceiver, DOMString tones,
optional unsigned long duration = 100,
optional unsigned long interToneGap = 70);
[Throws]
DOMString getDTMFToneBuffer(RTCRtpSender sender);
[Throws]
- void replaceTrack(MediaStreamTrack thisTrack, MediaStreamTrack withTrack);
- [Throws]
- void setParameters(MediaStreamTrack track,
- optional RTCRtpParameters parameters);
- [Throws]
- RTCRtpParameters getParameters(MediaStreamTrack track);
+ void replaceTrackNoRenegotiation(TransceiverImpl transceiverImpl,
+ MediaStreamTrack? withTrack);
[Throws]
void closeStreams();
- sequence<MediaStream> getLocalStreams();
- sequence<MediaStream> getRemoteStreams();
-
void addRIDExtension(MediaStreamTrack recvTrack, unsigned short extensionId);
void addRIDFilter(MediaStreamTrack recvTrack, DOMString rid);
/* As the ICE candidates roll in this one should be called each time
* in order to keep the candidate list up-to-date for the next SDP-related
* call PeerConnectionImpl does not parse ICE candidates, just sticks them
* into the SDP.
*/
--- a/dom/webidl/PeerConnectionObserver.webidl
+++ b/dom/webidl/PeerConnectionObserver.webidl
@@ -18,33 +18,34 @@ interface PeerConnectionObserver
void onCreateAnswerError(unsigned long name, DOMString message);
void onSetLocalDescriptionSuccess();
void onSetRemoteDescriptionSuccess();
void onSetLocalDescriptionError(unsigned long name, DOMString message);
void onSetRemoteDescriptionError(unsigned long name, DOMString message);
void onAddIceCandidateSuccess();
void onAddIceCandidateError(unsigned long name, DOMString message);
void onIceCandidate(unsigned short level, DOMString mid, DOMString candidate);
- void onNegotiationNeeded();
/* Stats callbacks */
void onGetStatsSuccess(optional RTCStatsReportInternal report);
void onGetStatsError(unsigned long name, DOMString message);
/* replaceTrack callbacks */
void onReplaceTrackSuccess();
void onReplaceTrackError(unsigned long name, DOMString message);
/* Data channel callbacks */
void notifyDataChannel(DataChannel channel);
/* Notification of one of several types of state changed */
void onStateChange(PCObserverStateType state);
/* Changes to MediaStreamTracks */
- void onAddStream(MediaStream stream);
void onRemoveStream(MediaStream stream);
- void onAddTrack(MediaStreamTrack track, sequence<MediaStream> streams);
- void onRemoveTrack(MediaStreamTrack track);
+ void onTrack(DOMString webrtcTrackId, sequence<DOMString> streamIds);
+
+ /* Transceiver management; called when setRemoteDescription causes a
+ transceiver to be created on the C++ side */
+ void onTransceiverNeeded(DOMString kind, TransceiverImpl transceiverImpl);
/* DTMF callback */
- void onDTMFToneChange(DOMString trackId, DOMString tone);
+ void onDTMFToneChange(MediaStreamTrack track, DOMString tone);
};
--- a/dom/webidl/RTCPeerConnection.webidl
+++ b/dom/webidl/RTCPeerConnection.webidl
@@ -107,18 +107,25 @@ interface RTCPeerConnection : EventTarge
// because a track can be part of multiple streams, stream parameters
// indicate which particular streams should be referenced in signaling
RTCRtpSender addTrack(MediaStreamTrack track,
MediaStream stream,
MediaStream... moreStreams);
void removeTrack(RTCRtpSender sender);
+ // Gets the track id that was in the SDP for this track
+ DOMString mozGetWebrtcTrackId(MediaStreamTrack track);
+
+ RTCRtpTransceiver addTransceiver((MediaStreamTrack or DOMString) trackOrKind,
+ optional RTCRtpTransceiverInit init);
+
sequence<RTCRtpSender> getSenders();
sequence<RTCRtpReceiver> getReceivers();
+ sequence<RTCRtpTransceiver> getTransceivers();
[ChromeOnly]
void mozAddRIDExtension(RTCRtpReceiver receiver, unsigned short extensionId);
[ChromeOnly]
void mozAddRIDFilter(RTCRtpReceiver receiver, DOMString rid);
void close ();
attribute EventHandler onnegotiationneeded;
--- a/dom/webidl/RTCRtpReceiver.webidl
+++ b/dom/webidl/RTCRtpReceiver.webidl
@@ -7,9 +7,12 @@
* http://lists.w3.org/Archives/Public/public-webrtc/2014May/0067.html
*/
[Pref="media.peerconnection.enabled",
JSImplementation="@mozilla.org/dom/rtpreceiver;1"]
interface RTCRtpReceiver {
readonly attribute MediaStreamTrack track;
Promise<RTCStatsReport> getStats();
+
+ [ChromeOnly]
+ attribute DOMString? webrtcTrackId;
};
--- a/dom/webidl/RTCRtpSender.webidl
+++ b/dom/webidl/RTCRtpSender.webidl
@@ -64,16 +64,21 @@ dictionary RTCRtpParameters {
sequence<RTCRtpHeaderExtensionParameters> headerExtensions;
RTCRtcpParameters rtcp;
sequence<RTCRtpCodecParameters> codecs;
};
[Pref="media.peerconnection.enabled",
JSImplementation="@mozilla.org/dom/rtpsender;1"]
interface RTCRtpSender {
- readonly attribute MediaStreamTrack track;
+ readonly attribute MediaStreamTrack? track;
Promise<void> setParameters (optional RTCRtpParameters parameters);
RTCRtpParameters getParameters();
- Promise<void> replaceTrack(MediaStreamTrack track);
+ Promise<void> replaceTrack(MediaStreamTrack? track);
Promise<RTCStatsReport> getStats();
[Pref="media.peerconnection.dtmf.enabled"]
readonly attribute RTCDTMFSender? dtmf;
+ sequence<MediaStream> mozGetStreams();
+ [ChromeOnly]
+ void setTrack(MediaStreamTrack? track);
+ [ChromeOnly]
+ void checkWasCreatedByPc(RTCPeerConnection pc);
};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/RTCRtpTransceiver.webidl
@@ -0,0 +1,65 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://w3c.github.io/webrtc-pc/#rtcrtptransceiver-interface
+ */
+
+enum RTCRtpTransceiverDirection {
+ "sendrecv",
+ "sendonly",
+ "recvonly",
+ "inactive"
+};
+
+dictionary RTCRtpTransceiverInit {
+ RTCRtpTransceiverDirection direction = "sendrecv";
+ sequence<MediaStream> streams;
+ // sequence<RTCRtpEncodingParameters> sendEncodings;
+};
+
+[Pref="media.peerconnection.enabled",
+ JSImplementation="@mozilla.org/dom/rtptransceiver;1"]
+interface RTCRtpTransceiver {
+ readonly attribute DOMString? mid;
+ [SameObject]
+ readonly attribute RTCRtpSender sender;
+ [SameObject]
+ readonly attribute RTCRtpReceiver receiver;
+ readonly attribute boolean stopped;
+ readonly attribute RTCRtpTransceiverDirection direction;
+ readonly attribute RTCRtpTransceiverDirection? currentDirection;
+ void setDirection(RTCRtpTransceiverDirection direction);
+ void stop();
+ // void setCodecPreferences(sequence<RTCRtpCodecCapability> codecs);
+
+ [ChromeOnly]
+ void setAddTrackMagic();
+ [ChromeOnly]
+ readonly attribute boolean addTrackMagic;
+ [ChromeOnly]
+ void setCurrentDirection(RTCRtpTransceiverDirection direction);
+ [ChromeOnly]
+ void setMid(DOMString mid);
+ [ChromeOnly]
+ void unsetMid();
+ [ChromeOnly]
+ void setStopped();
+ [ChromeOnly]
+ void remove();
+
+ [ChromeOnly]
+ DOMString getKind();
+ [ChromeOnly]
+ boolean hasBeenUsedToSend();
+ [ChromeOnly]
+ void sync();
+
+ [ChromeOnly]
+ void insertDTMF(DOMString tones,
+ optional unsigned long duration = 100,
+ optional unsigned long interToneGap = 70);
+};
+
--- a/dom/webidl/RTCTrackEvent.webidl
+++ b/dom/webidl/RTCTrackEvent.webidl
@@ -6,22 +6,24 @@
* The origin of this IDL file is
* http://w3c.github.io/webrtc-pc/#idl-def-RTCTrackEvent
*/
dictionary RTCTrackEventInit : EventInit {
required RTCRtpReceiver receiver;
required MediaStreamTrack track;
sequence<MediaStream> streams = [];
+ required RTCRtpTransceiver transceiver;
};
[Pref="media.peerconnection.enabled",
Constructor(DOMString type, RTCTrackEventInit eventInitDict)]
interface RTCTrackEvent : Event {
readonly attribute RTCRtpReceiver receiver;
readonly attribute MediaStreamTrack track;
// TODO: Use FrozenArray once available. (Bug 1236777)
// readonly attribute FrozenArray<MediaStream> streams;
[Frozen, Cached, Pure]
readonly attribute sequence<MediaStream> streams; // workaround
+ readonly attribute RTCRtpTransceiver transceiver;
};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/TransceiverImpl.webidl
@@ -0,0 +1,25 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * PeerConnection.js' interface to the C++ TransceiverImpl.
+ *
+ * Do not confuse with RTCRtpTransceiver. This interface is purely for
+ * communication between the PeerConnection JS DOM binding and the C++
+ * implementation.
+ *
+ * See media/webrtc/signaling/src/peerconnection/TransceiverImpl.h
+ *
+ */
+
+interface nsISupports;
+
+// Constructed by PeerConnectionImpl::CreateTransceiverImpl.
+[ChromeOnly]
+interface TransceiverImpl {
+ MediaStreamTrack getReceiveTrack();
+ [Throws]
+ void syncWithJS(RTCRtpTransceiver transceiver);
+};
+
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -200,19 +200,16 @@ with Files("MediaEncryptedEvent.webidl")
BUG_COMPONENT = ("Core", "Audio/Video")
with Files("MediaKey*"):
BUG_COMPONENT = ("Core", "Audio/Video: Playback")
with Files("Media*List*"):
BUG_COMPONENT = ("Core", "CSS Parsing and Computation")
-with Files("MediaStreamList.webidl"):
- BUG_COMPONENT = ("Core", "Web Audio")
-
with Files("*Record*"):
BUG_COMPONENT = ("Core", "Audio/Video: Recording")
with Files("Media*Track*"):
BUG_COMPONENT = ("Core", "WebRTC: Audio/Video")
with Files("Mouse*"):
BUG_COMPONENT = ("Core", "DOM: Events")
@@ -981,32 +978,33 @@ WEBIDL_FILES = [
'XULDocument.webidl',
'XULElement.webidl',
'XULTemplateBuilder.webidl',
]
if CONFIG['MOZ_WEBRTC']:
WEBIDL_FILES += [
'DataChannel.webidl',
- 'MediaStreamList.webidl',
'PeerConnectionImpl.webidl',
'PeerConnectionImplEnums.webidl',
'PeerConnectionObserver.webidl',
'PeerConnectionObserverEnums.webidl',
'RTCCertificate.webidl',
'RTCConfiguration.webidl',
'RTCDTMFSender.webidl',
'RTCIceCandidate.webidl',
'RTCIdentityAssertion.webidl',
'RTCIdentityProvider.webidl',
'RTCPeerConnection.webidl',
'RTCPeerConnectionStatic.webidl',
'RTCRtpReceiver.webidl',
'RTCRtpSender.webidl',
+ 'RTCRtpTransceiver.webidl',
'RTCSessionDescription.webidl',
+ 'TransceiverImpl.webidl',
'WebrtcDeprecated.webidl',
'WebrtcGlobalInformation.webidl',
]
if CONFIG['MOZ_WEBSPEECH']:
WEBIDL_FILES += [
'SpeechGrammar.webidl',
'SpeechGrammarList.webidl',
--- a/media/mtransport/test/transport_unittests.cpp
+++ b/media/mtransport/test/transport_unittests.cpp
@@ -609,18 +609,18 @@ class TransportTestPeer : public sigslot
ice_ctx_->ctx()->SetStream(streams_.size(), stream);
streams_.push_back(stream);
// Listen for candidates
stream->SignalCandidate.
connect(this, &TransportTestPeer::GotCandidate);
// Create the transport layer
- ice_ = new TransportLayerIce(name);
- ice_->SetParameters(ice_ctx_->ctx(), stream, 1);
+ ice_ = new TransportLayerIce();
+ ice_->SetParameters(stream, 1);
// Assemble the stack
nsAutoPtr<std::queue<mozilla::TransportLayer *> > layers(
new std::queue<mozilla::TransportLayer *>);
layers->push(ice_);
layers->push(dtls_);
test_utils_->sts_target()->Dispatch(
--- a/media/mtransport/transportlayerice.cpp
+++ b/media/mtransport/transportlayerice.cpp
@@ -79,52 +79,47 @@ extern "C" {
namespace mozilla {
#ifdef ERROR
#undef ERROR
#endif
MOZ_MTLOG_MODULE("mtransport")
-TransportLayerIce::TransportLayerIce(const std::string& name)
- : name_(name),
- ctx_(nullptr), stream_(nullptr), component_(0),
+TransportLayerIce::TransportLayerIce()
+ : stream_(nullptr), component_(0),
old_stream_(nullptr)
{
// setup happens later
}
TransportLayerIce::~TransportLayerIce() {
// No need to do anything here, since we use smart pointers
}
-void TransportLayerIce::SetParameters(RefPtr<NrIceCtx> ctx,
- RefPtr<NrIceMediaStream> stream,
+void TransportLayerIce::SetParameters(RefPtr<NrIceMediaStream> stream,
int component) {
// If SetParameters is called and we already have a stream_, this means
// we're handling an ICE restart. We need to hold the old stream until
// we know the new stream is working.
if (stream_ && !old_stream_ && (stream_ != stream)) {
// Here we leave the old stream's signals connected until we don't need
// it anymore. They will be disconnected if ice restart is successful.
old_stream_ = stream_;
MOZ_MTLOG(ML_INFO, LAYER_INFO << "SetParameters save old stream("
<< old_stream_->name() << ")");
}
- ctx_ = ctx;
stream_ = stream;
component_ = component;
PostSetup();
}
void TransportLayerIce::PostSetup() {
- target_ = ctx_->thread();
-
stream_->SignalReady.connect(this, &TransportLayerIce::IceReady);
stream_->SignalFailed.connect(this, &TransportLayerIce::IceFailed);
stream_->SignalPacketReceived.connect(this,
&TransportLayerIce::IcePacketReceived);
if (stream_->state() == NrIceMediaStream::ICE_OPEN) {
TL_SET_STATE(TS_OPEN);
}
}
--- a/media/mtransport/transportlayerice.h
+++ b/media/mtransport/transportlayerice.h
@@ -25,22 +25,21 @@
#include "transportflow.h"
#include "transportlayer.h"
// An ICE transport layer -- corresponds to a single ICE
namespace mozilla {
class TransportLayerIce : public TransportLayer {
public:
- explicit TransportLayerIce(const std::string& name);
+ TransportLayerIce();
virtual ~TransportLayerIce();
- void SetParameters(RefPtr<NrIceCtx> ctx,
- RefPtr<NrIceMediaStream> stream,
+ void SetParameters(RefPtr<NrIceMediaStream> stream,
int component);
void ResetOldStream(); // called after successful ice restart
void RestoreOldStream(); // called after unsuccessful ice restart
// Transport layer overrides.
TransportResult SendPacket(const unsigned char *data, size_t len) override;
@@ -52,18 +51,16 @@ class TransportLayerIce : public Transpo
const unsigned char *data, int len);
TRANSPORT_LAYER_ID("ice")
private:
DISALLOW_COPY_ASSIGN(TransportLayerIce);
void PostSetup();
- const std::string name_;
- RefPtr<NrIceCtx> ctx_;
RefPtr<NrIceMediaStream> stream_;
int component_;
// used to hold the old stream
RefPtr<NrIceMediaStream> old_stream_;
};
} // close namespace
--- a/media/webrtc/moz.build
+++ b/media/webrtc/moz.build
@@ -95,21 +95,20 @@ if CONFIG['MOZ_WEBRTC_SIGNALING']:
'signaling/src/jsep/JsepSessionImpl.cpp',
'signaling/src/media-conduit/AudioConduit.cpp',
'signaling/src/media-conduit/MediaCodecVideoCodec.cpp',
'signaling/src/media-conduit/VideoConduit.cpp',
'signaling/src/media-conduit/WebrtcMediaCodecVP8VideoCodec.cpp',
'signaling/src/mediapipeline/MediaPipeline.cpp',
'signaling/src/mediapipeline/MediaPipelineFilter.cpp',
'signaling/src/mediapipeline/SrtpFlow.cpp',
- 'signaling/src/peerconnection/MediaPipelineFactory.cpp',
- 'signaling/src/peerconnection/MediaStreamList.cpp',
'signaling/src/peerconnection/PeerConnectionCtx.cpp',
'signaling/src/peerconnection/PeerConnectionImpl.cpp',
'signaling/src/peerconnection/PeerConnectionMedia.cpp',
+ 'signaling/src/peerconnection/TransceiverImpl.cpp',
'signaling/src/peerconnection/WebrtcGlobalInformation.cpp',
'signaling/src/sdp/sipcc/cpr_string.c',
'signaling/src/sdp/sipcc/sdp_access.c',
'signaling/src/sdp/sipcc/sdp_attr.c',
'signaling/src/sdp/sipcc/sdp_attr_access.c',
'signaling/src/sdp/sipcc/sdp_base64.c',
'signaling/src/sdp/sipcc/sdp_config.c',
'signaling/src/sdp/sipcc/sdp_main.c',
--- a/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
+++ b/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
@@ -103,52 +103,158 @@ protected:
// Values here semi-borrowed from JSEP draft.
tdata.mIceUfrag = session.GetName() + "-ufrag";
tdata.mIcePwd = session.GetName() + "-1234567890";
session.SetIceCredentials(tdata.mIceUfrag, tdata.mIcePwd);
AddDtlsFingerprint("sha-1", session, tdata);
AddDtlsFingerprint("sha-256", session, tdata);
}
+ void
+ CheckTransceiverInvariants(
+ const std::vector<RefPtr<JsepTransceiver>>& oldTransceivers,
+ const std::vector<RefPtr<JsepTransceiver>>& newTransceivers)
+ {
+ ASSERT_LE(oldTransceivers.size(), newTransceivers.size());
+ std::set<size_t> levels;
+
+ for (const RefPtr<JsepTransceiver>& newTransceiver : newTransceivers) {
+ if (newTransceiver->HasLevel()) {
+ ASSERT_FALSE(levels.count(newTransceiver->GetLevel()))
+ << "Two new transceivers are mapped to level "
+ << newTransceiver->GetLevel();
+ levels.insert(newTransceiver->GetLevel());
+ }
+ }
+
+ auto last = levels.rbegin();
+ if (last != levels.rend()) {
+ ASSERT_LE(*last, levels.size())
+ << "Max level observed in transceivers was " << *last
+ << ", but there are only " << levels.size() << " levels in the "
+ "transceivers.";
+ }
+
+ for (const RefPtr<JsepTransceiver>& oldTransceiver : oldTransceivers) {
+ if (oldTransceiver->HasLevel()) {
+ ASSERT_TRUE(levels.count(oldTransceiver->GetLevel()))
+ << "Level " << oldTransceiver->GetLevel()
+ << " had a transceiver in the old, but not the new (or, "
+ "perhaps this level had more than one transceiver in the "
+ "old)";
+ levels.erase(oldTransceiver->GetLevel());
+ }
+ }
+ }
+
+ std::vector<RefPtr<JsepTransceiver>>
+ DeepCopy(const std::vector<RefPtr<JsepTransceiver>>& transceivers)
+ {
+ std::vector<RefPtr<JsepTransceiver>> copy;
+ for (const RefPtr<JsepTransceiver>& transceiver : transceivers) {
+ copy.push_back(new JsepTransceiver(*transceiver));
+ }
+ return copy;
+ }
+
std::string
CreateOffer(const Maybe<JsepOfferOptions>& options = Nothing())
{
+ std::vector<RefPtr<JsepTransceiver>> transceiversBefore =
+ DeepCopy(mSessionOff->GetTransceivers());
JsepOfferOptions defaultOptions;
const JsepOfferOptions& optionsRef = options ? *options : defaultOptions;
std::string offer;
nsresult rv;
rv = mSessionOff->CreateOffer(optionsRef, &offer);
EXPECT_EQ(NS_OK, rv) << mSessionOff->GetLastError();
std::cerr << "OFFER: " << offer << std::endl;
ValidateTransport(*mOffererTransport, offer);
+ if (transceiversBefore.size() != mSessionOff->GetTransceivers().size()) {
+ EXPECT_TRUE(false) << "CreateOffer changed number of transceivers!";
+ return offer;
+ }
+
+ CheckTransceiverInvariants(transceiversBefore,
+ mSessionOff->GetTransceivers());
+
+ for (size_t i = 0; i < transceiversBefore.size(); ++i) {
+ RefPtr<JsepTransceiver>& oldTransceiver = transceiversBefore[i];
+ RefPtr<JsepTransceiver>& newTransceiver = mSessionOff->GetTransceivers()[i];
+ EXPECT_EQ(oldTransceiver->IsStopped(), newTransceiver->IsStopped());
+
+ if (oldTransceiver->IsStopped()) {
+ if (!newTransceiver->HasLevel()) {
+ // Tolerate unmapping of stopped transceivers by removing this
+ // difference.
+ oldTransceiver->ClearLevel();
+ }
+ } else if (!oldTransceiver->HasLevel()) {
+ EXPECT_TRUE(newTransceiver->HasLevel());
+ // Tolerate new mappings.
+ oldTransceiver->SetLevel(newTransceiver->GetLevel());
+ }
+
+ EXPECT_TRUE(Equals(*oldTransceiver, *newTransceiver));
+ }
+
return offer;
}
+ typedef enum {
+ NO_ADDTRACK_MAGIC,
+ ADDTRACK_MAGIC
+ } AddTrackMagic;
+
void
- AddTracks(JsepSessionImpl& side)
+ AddTracks(JsepSessionImpl& side, AddTrackMagic magic = ADDTRACK_MAGIC)
{
// Add tracks.
if (types.empty()) {
types = BuildTypes(GetParam());
}
- AddTracks(side, types);
-
- // Now that we have added streams, we expect audio, then video, then
- // application in the SDP, regardless of the order in which the streams were
- // added.
- std::sort(types.begin(), types.end());
+ AddTracks(side, types, magic);
}
void
- AddTracks(JsepSessionImpl& side, const std::string& mediatypes)
+ AddTracks(JsepSessionImpl& side,
+ const std::string& mediatypes,
+ AddTrackMagic magic = ADDTRACK_MAGIC)
{
- AddTracks(side, BuildTypes(mediatypes));
+ AddTracks(side, BuildTypes(mediatypes), magic);
+ }
+
+ JsepTrack
+ RemoveTrack(JsepSession& side, size_t index) {
+ if (side.GetTransceivers().size() <= index) {
+ EXPECT_TRUE(false) << "Index " << index << " out of bounds!";
+ return JsepTrack(SdpMediaSection::kAudio, sdp::kSend);
+ }
+
+ RefPtr<JsepTransceiver>& transceiver(side.GetTransceivers()[index]);
+ JsepTrack& track = transceiver->mSending;
+ EXPECT_FALSE(track.GetTrackId().empty()) << "No track at index " << index;
+
+ JsepTrack original(track);
+ track.ClearTrack();
+ transceiver->mJsDirection &= SdpDirectionAttribute::Direction::kRecvonly;
+ return original;
+ }
+
+ void
+ SetDirection(JsepSession& side,
+ size_t index,
+ SdpDirectionAttribute::Direction direction) {
+ ASSERT_LT(index, side.GetTransceivers().size())
+ << "Index " << index << " out of bounds!";
+
+ side.GetTransceivers()[index]->mJsDirection = direction;
}
std::vector<SdpMediaSection::MediaType>
BuildTypes(const std::string& mediatypes)
{
std::vector<SdpMediaSection::MediaType> result;
size_t ptr = 0;
@@ -173,87 +279,177 @@ protected:
ptr = comma + 1;
}
return result;
}
void
AddTracks(JsepSessionImpl& side,
- const std::vector<SdpMediaSection::MediaType>& mediatypes)
+ const std::vector<SdpMediaSection::MediaType>& mediatypes,
+ AddTrackMagic magic = ADDTRACK_MAGIC)
{
FakeUuidGenerator uuid_gen;
std::string stream_id;
std::string track_id;
ASSERT_TRUE(uuid_gen.Generate(&stream_id));
- AddTracksToStream(side, stream_id, mediatypes);
+ AddTracksToStream(side, stream_id, mediatypes, magic);
}
void
AddTracksToStream(JsepSessionImpl& side,
const std::string stream_id,
- const std::string& mediatypes)
+ const std::string& mediatypes,
+ AddTrackMagic magic = ADDTRACK_MAGIC)
{
- AddTracksToStream(side, stream_id, BuildTypes(mediatypes));
+ AddTracksToStream(side, stream_id, BuildTypes(mediatypes), magic);
+ }
+
+ // A bit of a hack. JsepSessionImpl populates the track-id automatically, just
+ // in case, because the w3c spec requires msid to be set even when there's no
+ // send track.
+ bool IsNull(const JsepTrack& track) const {
+ return track.GetStreamIds().empty() &&
+ (track.GetMediaType() != SdpMediaSection::MediaType::kApplication);
}
void
AddTracksToStream(JsepSessionImpl& side,
const std::string stream_id,
- const std::vector<SdpMediaSection::MediaType>& mediatypes)
+ const std::vector<SdpMediaSection::MediaType>& mediatypes,
+ AddTrackMagic magic = ADDTRACK_MAGIC)
{
FakeUuidGenerator uuid_gen;
std::string track_id;
- for (auto track = mediatypes.begin(); track != mediatypes.end(); ++track) {
+ for (auto type : mediatypes) {
ASSERT_TRUE(uuid_gen.Generate(&track_id));
- RefPtr<JsepTrack> mst(new JsepTrack(*track, stream_id, track_id));
- side.AddTrack(mst);
+ std::vector<RefPtr<JsepTransceiver>>& transceivers(side.GetTransceivers());
+ size_t i = transceivers.size();
+ if (magic == ADDTRACK_MAGIC) {
+ for (i = 0; i < transceivers.size(); ++i) {
+ if (transceivers[i]->mSending.GetMediaType() != type) {
+ continue;
+ }
+
+ if (IsNull(transceivers[i]->mSending) ||
+ type == SdpMediaSection::MediaType::kApplication) {
+ break;
+ }
+ }
+ }
+
+ if (i == transceivers.size()) {
+ side.AddTransceiver(new JsepTransceiver(type));
+ MOZ_ASSERT(i < transceivers.size());
+ }
+
+ std::cerr << "Updating send track for transceiver " << i << std::endl;
+ if (magic == ADDTRACK_MAGIC) {
+ transceivers[i]->SetAddTrackMagic();
+ }
+ transceivers[i]->mJsDirection |=
+ SdpDirectionAttribute::Direction::kSendonly;
+ transceivers[i]->mSending.UpdateTrack(
+ std::vector<std::string>(1, stream_id), track_id);
}
}
- bool HasMediaStream(std::vector<RefPtr<JsepTrack>> tracks) const {
- for (auto i = tracks.begin(); i != tracks.end(); ++i) {
- if ((*i)->GetMediaType() != SdpMediaSection::kApplication) {
+ bool HasMediaStream(const std::vector<JsepTrack>& tracks) const {
+ for (const auto& track : tracks) {
+ if (track.GetMediaType() != SdpMediaSection::kApplication) {
return 1;
}
}
return 0;
}
const std::string GetFirstLocalStreamId(JsepSessionImpl& side) const {
- auto tracks = side.GetLocalTracks();
- return (*tracks.begin())->GetStreamId();
+ auto tracks = GetLocalTracks(side);
+ return tracks.begin()->GetStreamIds()[0];
+ }
+
+ std::vector<JsepTrack>
+ GetLocalTracks(const JsepSession& session) const {
+ std::vector<JsepTrack> result;
+ for (const auto& transceiver : session.GetTransceivers()) {
+ if (!IsNull(transceiver->mSending)) {
+ result.push_back(transceiver->mSending);
+ }
+ }
+ return result;
+ }
+
+ std::vector<JsepTrack>
+ GetRemoteTracks(const JsepSession& session) const {
+ std::vector<JsepTrack> result;
+ for (const auto& transceiver : session.GetTransceivers()) {
+ if (!IsNull(transceiver->mReceiving)) {
+ result.push_back(transceiver->mReceiving);
+ }
+ }
+ return result;
+ }
+
+ JsepTransceiver*
+ GetDatachannelTransceiver(JsepSession& side) {
+ for (const auto& transceiver : side.GetTransceivers()) {
+ if (transceiver->mSending.GetMediaType() ==
+ SdpMediaSection::MediaType::kApplication) {
+ return transceiver.get();
+ }
+ }
+
+ return nullptr;
+ }
+
+ JsepTransceiver*
+ GetNegotiatedTransceiver(JsepSession& side, size_t index) {
+ for (RefPtr<JsepTransceiver>& transceiver : side.GetTransceivers()) {
+ if (transceiver->mSending.GetNegotiatedDetails() ||
+ transceiver->mReceiving.GetNegotiatedDetails()) {
+ if (index) {
+ --index;
+ continue;
+ }
+
+ return transceiver.get();
+ }
+ }
+
+ return nullptr;
}
std::vector<std::string>
- GetMediaStreamIds(std::vector<RefPtr<JsepTrack>> tracks) const {
+ GetMediaStreamIds(const std::vector<JsepTrack>& tracks) const {
std::vector<std::string> ids;
- for (auto i = tracks.begin(); i != tracks.end(); ++i) {
+ for (const auto& track : tracks) {
// data channels don't have msid's
- if ((*i)->GetMediaType() == SdpMediaSection::kApplication) {
+ if (track.GetMediaType() == SdpMediaSection::kApplication) {
continue;
}
- ids.push_back((*i)->GetStreamId());
+ ids.insert(ids.end(),
+ track.GetStreamIds().begin(),
+ track.GetStreamIds().end());
}
return ids;
}
std::vector<std::string>
GetLocalMediaStreamIds(JsepSessionImpl& side) const {
- return GetMediaStreamIds(side.GetLocalTracks());
+ return GetMediaStreamIds(GetLocalTracks(side));
}
std::vector<std::string>
GetRemoteMediaStreamIds(JsepSessionImpl& side) const {
- return GetMediaStreamIds(side.GetRemoteTracks());
+ return GetMediaStreamIds(GetRemoteTracks(side));
}
std::vector<std::string>
sortUniqueStrVector(std::vector<std::string> in) const {
std::sort(in.begin(), in.end());
auto it = std::unique(in.begin(), in.end());
in.resize( std::distance(in.begin(), it));
return in;
@@ -264,59 +460,49 @@ protected:
return sortUniqueStrVector(GetLocalMediaStreamIds(side));
}
std::vector<std::string>
GetRemoteUniqueStreamIds(JsepSessionImpl& side) const {
return sortUniqueStrVector(GetRemoteMediaStreamIds(side));
}
- RefPtr<JsepTrack> GetTrack(JsepSessionImpl& side,
- SdpMediaSection::MediaType type,
- size_t index) const {
- auto tracks = side.GetLocalTracks();
-
- for (auto i = tracks.begin(); i != tracks.end(); ++i) {
- if ((*i)->GetMediaType() != type) {
+ JsepTrack GetTrack(JsepSessionImpl& side,
+ SdpMediaSection::MediaType type,
+ size_t index) const {
+ for (const auto& transceiver : side.GetTransceivers()) {
+ if (IsNull(transceiver->mSending) ||
+ transceiver->mSending.GetMediaType() != type) {
continue;
}
if (index != 0) {
--index;
continue;
}
- return *i;
+ return transceiver->mSending;
}
- return RefPtr<JsepTrack>(nullptr);
+ return JsepTrack(type, sdp::kSend);
}
- RefPtr<JsepTrack> GetTrackOff(size_t index,
- SdpMediaSection::MediaType type) {
+ JsepTrack GetTrackOff(size_t index, SdpMediaSection::MediaType type) {
return GetTrack(*mSessionOff, type, index);
}
- RefPtr<JsepTrack> GetTrackAns(size_t index,
- SdpMediaSection::MediaType type) {
+ JsepTrack GetTrackAns(size_t index, SdpMediaSection::MediaType type) {
return GetTrack(*mSessionAns, type, index);
}
- class ComparePairsByLevel {
- public:
- bool operator()(const JsepTrackPair& lhs,
- const JsepTrackPair& rhs) const {
- return lhs.mLevel < rhs.mLevel;
- }
- };
-
- std::vector<JsepTrackPair> GetTrackPairsByLevel(JsepSessionImpl& side) const {
- auto pairs = side.GetNegotiatedTrackPairs();
- std::sort(pairs.begin(), pairs.end(), ComparePairsByLevel());
- return pairs;
+ size_t CountRtpTypes() const {
+ return std::count_if(
+ types.begin(), types.end(),
+ [](SdpMediaSection::MediaType type)
+ {return type != SdpMediaSection::MediaType::kApplication;});
}
bool Equals(const SdpFingerprintAttributeList::Fingerprint& f1,
const SdpFingerprintAttributeList::Fingerprint& f2) const {
if (f1.hashFunc != f2.hashFunc) {
return false;
}
@@ -380,75 +566,126 @@ protected:
if (t1->GetPassword() != t2->GetPassword()) {
return false;
}
return true;
}
- bool Equals(const RefPtr<JsepTransport>& t1,
- const RefPtr<JsepTransport>& t2) const {
- if (!t1 && !t2) {
- return true;
- }
-
- if (!t1 || !t2) {
+ bool Equals(const JsepTransport& t1,
+ const JsepTransport& t2) const {
+ if (t1.mTransportId != t2.mTransportId) {
+ std::cerr << "Transport id differs: " << t1.mTransportId << " vs "
+ << t2.mTransportId << std::endl;
return false;
}
- if (t1->mTransportId != t2->mTransportId) {
+ if (t1.mComponents != t2.mComponents) {
+ std::cerr << "Component count differs" << std::endl;
return false;
}
- if (t1->mComponents != t2->mComponents) {
- return false;
- }
-
- if (!Equals(t1->mIce, t2->mIce)) {
+ if (!Equals(t1.mIce, t2.mIce)) {
+ std::cerr << "ICE differs" << std::endl;
return false;
}
return true;
}
- bool Equals(const JsepTrackPair& p1,
- const JsepTrackPair& p2) const {
- if (p1.mLevel != p2.mLevel) {
+ bool Equals(const JsepTrack& t1, const JsepTrack& t2) const {
+ if (t1.GetMediaType() != t2.GetMediaType()) {
+ return false;
+ }
+
+ if (t1.GetDirection() != t2.GetDirection()) {
+ return false;
+ }
+
+ if (t1.GetStreamIds() != t2.GetStreamIds()) {
+ return false;
+ }
+
+ if (t1.GetTrackId() != t2.GetTrackId()) {
+ return false;
+ }
+
+ if (t1.GetActive() != t2.GetActive()) {
+ return false;
+ }
+
+ if (t1.GetCNAME() != t2.GetCNAME()) {
+ return false;
+ }
+
+ if (t1.GetSsrcs() != t2.GetSsrcs()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool Equals(const JsepTransceiver& p1,
+ const JsepTransceiver& p2) const {
+ if (p1.HasLevel() != p2.HasLevel()) {
+ std::cerr << "One transceiver has a level, the other doesn't"
+ << std::endl;
+ return false;
+ }
+
+ if (p1.HasLevel() && (p1.GetLevel() != p2.GetLevel())) {
+ std::cerr << "Level differs: " << p1.GetLevel() << " vs " << p2.GetLevel()
+ << std::endl;
return false;
}
// We don't check things like BundleLevel(), since that can change without
// any changes to the transport, which is what we're really interested in.
- if (p1.mSending.get() != p2.mSending.get()) {
+ if (p1.IsStopped() != p2.IsStopped()) {
+ std::cerr << "One transceiver is stopped, the other is not" << std::endl;
return false;
}
- if (p1.mReceiving.get() != p2.mReceiving.get()) {
+ if (p1.IsAssociated() != p2.IsAssociated()) {
+ std::cerr << "One transceiver has a mid, the other doesn't"
+ << std::endl;
return false;
}
- if (!Equals(p1.mRtpTransport, p2.mRtpTransport)) {
+ if (p1.IsAssociated() && (p1.GetMid() != p2.GetMid())) {
+ std::cerr << "mid differs: " << p1.GetMid() << " vs " << p2.GetMid()
+ << std::endl;
return false;
}
- if (!Equals(p1.mRtcpTransport, p2.mRtcpTransport)) {
+ if (!Equals(p1.mSending, p2.mSending)) {
+ std::cerr << "Send track differs" << std::endl;
+ return false;
+ }
+
+ if (!Equals(p1.mReceiving, p2.mReceiving)) {
+ std::cerr << "Receive track differs" << std::endl;
+ return false;
+ }
+
+ if (!Equals(p1.mTransport, p2.mTransport)) {
+ std::cerr << "Transport differs" << std::endl;
return false;
}
return true;
}
size_t GetTrackCount(JsepSessionImpl& side,
SdpMediaSection::MediaType type) const {
- auto tracks = side.GetLocalTracks();
size_t result = 0;
- for (auto i = tracks.begin(); i != tracks.end(); ++i) {
- if ((*i)->GetMediaType() == type) {
+ for (const auto& track : GetLocalTracks(side)) {
+ if (track.GetMediaType() == type) {
++result;
}
}
return result;
}
UniquePtr<Sdp> GetParsedLocalDescription(const JsepSessionImpl& side) const {
return Parse(side.GetLocalDescription(kJsepDescriptionCurrent));
@@ -497,44 +734,45 @@ protected:
}
}
}
void
EnsureNegotiationFailure(SdpMediaSection::MediaType type,
const std::string& codecName)
{
- for (auto i = mSessionOff->Codecs().begin(); i != mSessionOff->Codecs().end();
- ++i) {
- auto* codec = *i;
+ for (auto* codec : mSessionOff->Codecs()) {
if (codec->mType == type && codec->mName != codecName) {
codec->mEnabled = false;
}
}
- for (auto i = mSessionAns->Codecs().begin(); i != mSessionAns->Codecs().end();
- ++i) {
- auto* codec = *i;
+ for (auto* codec : mSessionAns->Codecs()) {
if (codec->mType == type && codec->mName == codecName) {
codec->mEnabled = false;
}
}
}
std::string
CreateAnswer()
{
+ std::vector<RefPtr<JsepTransceiver>> transceiversBefore =
+ DeepCopy(mSessionAns->GetTransceivers());
+
JsepAnswerOptions options;
std::string answer;
nsresult rv = mSessionAns->CreateAnswer(options, &answer);
EXPECT_EQ(NS_OK, rv);
std::cerr << "ANSWER: " << answer << std::endl;
ValidateTransport(*mAnswererTransport, answer);
+ CheckTransceiverInvariants(transceiversBefore,
+ mSessionAns->GetTransceivers());
return answer;
}
static const uint32_t NO_CHECKS = 0;
static const uint32_t CHECK_SUCCESS = 1;
static const uint32_t CHECK_TRACKS = 1 << 2;
static const uint32_t ALL_CHECKS = CHECK_SUCCESS | CHECK_TRACKS;
@@ -548,154 +786,186 @@ protected:
std::string answer = CreateAnswer();
SetLocalAnswer(answer, checkFlags);
SetRemoteAnswer(answer, checkFlags);
}
void
SetLocalOffer(const std::string& offer, uint32_t checkFlags = ALL_CHECKS)
{
+ std::vector<RefPtr<JsepTransceiver>> transceiversBefore =
+ DeepCopy(mSessionOff->GetTransceivers());
+
nsresult rv = mSessionOff->SetLocalDescription(kJsepSdpOffer, offer);
+ CheckTransceiverInvariants(transceiversBefore,
+ mSessionOff->GetTransceivers());
+
if (checkFlags & CHECK_SUCCESS) {
ASSERT_EQ(NS_OK, rv);
}
if (checkFlags & CHECK_TRACKS) {
- // Check that the transports exist.
- ASSERT_EQ(types.size(), mSessionOff->GetTransports().size());
- auto tracks = mSessionOff->GetLocalTracks();
- for (size_t i = 0; i < types.size(); ++i) {
- ASSERT_NE("", tracks[i]->GetStreamId());
- ASSERT_NE("", tracks[i]->GetTrackId());
- if (tracks[i]->GetMediaType() != SdpMediaSection::kApplication) {
+ // This assumes no recvonly or inactive transceivers.
+ ASSERT_EQ(types.size(), mSessionOff->GetTransceivers().size());
+ for (const auto& transceiver : mSessionOff->GetTransceivers()) {
+ if (!transceiver->HasLevel()) {
+ continue;
+ }
+ const auto& track(transceiver->mSending);
+ size_t level = transceiver->GetLevel();
+ ASSERT_FALSE(IsNull(track));
+ ASSERT_EQ(types[level], track.GetMediaType());
+ if (track.GetMediaType() != SdpMediaSection::kApplication) {
std::string msidAttr("a=msid:");
- msidAttr += tracks[i]->GetStreamId();
+ msidAttr += track.GetStreamIds()[0];
msidAttr += " ";
- msidAttr += tracks[i]->GetTrackId();
+ msidAttr += track.GetTrackId();
ASSERT_NE(std::string::npos, offer.find(msidAttr))
<< "Did not find " << msidAttr << " in offer";
}
}
if (types.size() == 1 &&
- tracks[0]->GetMediaType() == SdpMediaSection::kApplication) {
+ types[0] == SdpMediaSection::kApplication) {
ASSERT_EQ(std::string::npos, offer.find("a=ssrc"))
<< "Data channel should not contain SSRC";
}
}
}
void
SetRemoteOffer(const std::string& offer, uint32_t checkFlags = ALL_CHECKS)
{
+ std::vector<RefPtr<JsepTransceiver>> transceiversBefore =
+ DeepCopy(mSessionAns->GetTransceivers());
+
nsresult rv = mSessionAns->SetRemoteDescription(kJsepSdpOffer, offer);
+ CheckTransceiverInvariants(transceiversBefore,
+ mSessionAns->GetTransceivers());
+
if (checkFlags & CHECK_SUCCESS) {
ASSERT_EQ(NS_OK, rv);
}
if (checkFlags & CHECK_TRACKS) {
- auto tracks = mSessionAns->GetRemoteTracks();
- // Now verify that the right stuff is in the tracks.
- ASSERT_EQ(types.size(), tracks.size());
- for (size_t i = 0; i < tracks.size(); ++i) {
- ASSERT_EQ(types[i], tracks[i]->GetMediaType());
- ASSERT_NE("", tracks[i]->GetStreamId());
- ASSERT_NE("", tracks[i]->GetTrackId());
- if (tracks[i]->GetMediaType() != SdpMediaSection::kApplication) {
+ ASSERT_EQ(types.size(), mSessionAns->GetTransceivers().size());
+ for (const auto& transceiver : mSessionAns->GetTransceivers()) {
+ if (!transceiver->HasLevel()) {
+ continue;
+ }
+ const auto& track(transceiver->mReceiving);
+ size_t level = transceiver->GetLevel();
+ ASSERT_FALSE(IsNull(track));
+ ASSERT_EQ(types[level], track.GetMediaType());
+ if (track.GetMediaType() != SdpMediaSection::kApplication) {
std::string msidAttr("a=msid:");
- msidAttr += tracks[i]->GetStreamId();
+ msidAttr += track.GetStreamIds()[0];
msidAttr += " ";
- msidAttr += tracks[i]->GetTrackId();
+ msidAttr += track.GetTrackId();
ASSERT_NE(std::string::npos, offer.find(msidAttr))
<< "Did not find " << msidAttr << " in offer";
}
}
}
}
void
SetLocalAnswer(const std::string& answer, uint32_t checkFlags = ALL_CHECKS)
{
+ std::vector<RefPtr<JsepTransceiver>> transceiversBefore =
+ DeepCopy(mSessionAns->GetTransceivers());
+
nsresult rv = mSessionAns->SetLocalDescription(kJsepSdpAnswer, answer);
if (checkFlags & CHECK_SUCCESS) {
ASSERT_EQ(NS_OK, rv);
}
+ CheckTransceiverInvariants(transceiversBefore,
+ mSessionAns->GetTransceivers());
+
if (checkFlags & CHECK_TRACKS) {
// Verify that the right stuff is in the tracks.
- auto pairs = mSessionAns->GetNegotiatedTrackPairs();
- ASSERT_EQ(types.size(), pairs.size());
- for (size_t i = 0; i < types.size(); ++i) {
- ASSERT_TRUE(pairs[i].mSending);
- ASSERT_EQ(types[i], pairs[i].mSending->GetMediaType());
- ASSERT_TRUE(pairs[i].mReceiving);
- ASSERT_EQ(types[i], pairs[i].mReceiving->GetMediaType());
- ASSERT_NE("", pairs[i].mSending->GetStreamId());
- ASSERT_NE("", pairs[i].mSending->GetTrackId());
+ ASSERT_EQ(types.size(), mSessionAns->GetTransceivers().size());
+ for (const auto& transceiver : mSessionAns->GetTransceivers()) {
+ if (!transceiver->HasLevel()) {
+ continue;
+ }
+ const auto& sendTrack(transceiver->mSending);
+ const auto& recvTrack(transceiver->mReceiving);
+ size_t level = transceiver->GetLevel();
+ ASSERT_FALSE(IsNull(sendTrack));
+ ASSERT_EQ(types[level], sendTrack.GetMediaType());
// These might have been in the SDP, or might have been randomly
// chosen by JsepSessionImpl
- ASSERT_NE("", pairs[i].mReceiving->GetStreamId());
- ASSERT_NE("", pairs[i].mReceiving->GetTrackId());
-
- if (pairs[i].mReceiving->GetMediaType() != SdpMediaSection::kApplication) {
+ ASSERT_FALSE(IsNull(recvTrack));
+ ASSERT_EQ(types[level], recvTrack.GetMediaType());
+
+ if (recvTrack.GetMediaType() != SdpMediaSection::kApplication) {
std::string msidAttr("a=msid:");
- msidAttr += pairs[i].mSending->GetStreamId();
+ msidAttr += sendTrack.GetStreamIds()[0];
msidAttr += " ";
- msidAttr += pairs[i].mSending->GetTrackId();
+ msidAttr += sendTrack.GetTrackId();
ASSERT_NE(std::string::npos, answer.find(msidAttr))
- << "Did not find " << msidAttr << " in offer";
+ << "Did not find " << msidAttr << " in answer";
}
}
if (types.size() == 1 &&
- pairs[0].mReceiving->GetMediaType() == SdpMediaSection::kApplication) {
+ types[0] == SdpMediaSection::kApplication) {
ASSERT_EQ(std::string::npos, answer.find("a=ssrc"))
<< "Data channel should not contain SSRC";
}
}
- std::cerr << "OFFER pairs:" << std::endl;
- DumpTrackPairs(*mSessionOff);
+ std::cerr << "Answerer transceivers:" << std::endl;
+ DumpTransceivers(*mSessionAns);
}
void
SetRemoteAnswer(const std::string& answer, uint32_t checkFlags = ALL_CHECKS)
{
+ std::vector<RefPtr<JsepTransceiver>> transceiversBefore =
+ DeepCopy(mSessionOff->GetTransceivers());
+
nsresult rv = mSessionOff->SetRemoteDescription(kJsepSdpAnswer, answer);
if (checkFlags & CHECK_SUCCESS) {
ASSERT_EQ(NS_OK, rv);
}
+ CheckTransceiverInvariants(transceiversBefore,
+ mSessionOff->GetTransceivers());
+
if (checkFlags & CHECK_TRACKS) {
// Verify that the right stuff is in the tracks.
- auto pairs = mSessionOff->GetNegotiatedTrackPairs();
- ASSERT_EQ(types.size(), pairs.size());
- for (size_t i = 0; i < types.size(); ++i) {
- ASSERT_TRUE(pairs[i].mSending);
- ASSERT_EQ(types[i], pairs[i].mSending->GetMediaType());
- ASSERT_TRUE(pairs[i].mReceiving);
- ASSERT_EQ(types[i], pairs[i].mReceiving->GetMediaType());
- ASSERT_NE("", pairs[i].mSending->GetStreamId());
- ASSERT_NE("", pairs[i].mSending->GetTrackId());
+ ASSERT_EQ(types.size(), mSessionOff->GetTransceivers().size());
+ for (const auto& transceiver : mSessionOff->GetTransceivers()) {
+ if (!transceiver->HasLevel()) {
+ continue;
+ }
+ const auto& sendTrack(transceiver->mSending);
+ const auto& recvTrack(transceiver->mReceiving);
+ size_t level = transceiver->GetLevel();
+ ASSERT_FALSE(IsNull(sendTrack));
+ ASSERT_EQ(types[level], sendTrack.GetMediaType());
// These might have been in the SDP, or might have been randomly
// chosen by JsepSessionImpl
- ASSERT_NE("", pairs[i].mReceiving->GetStreamId());
- ASSERT_NE("", pairs[i].mReceiving->GetTrackId());
-
- if (pairs[i].mReceiving->GetMediaType() != SdpMediaSection::kApplication) {
+ ASSERT_FALSE(IsNull(recvTrack));
+ ASSERT_EQ(types[level], recvTrack.GetMediaType());
+
+ if (recvTrack.GetMediaType() != SdpMediaSection::kApplication) {
std::string msidAttr("a=msid:");
- msidAttr += pairs[i].mReceiving->GetStreamId();
+ msidAttr += recvTrack.GetStreamIds()[0];
msidAttr += " ";
- msidAttr += pairs[i].mReceiving->GetTrackId();
+ msidAttr += recvTrack.GetTrackId();
ASSERT_NE(std::string::npos, answer.find(msidAttr))
<< "Did not find " << msidAttr << " in answer";
}
}
}
- std::cerr << "ANSWER pairs:" << std::endl;
- DumpTrackPairs(*mSessionAns);
+ std::cerr << "Offerer transceivers:" << std::endl;
+ DumpTransceivers(*mSessionOff);
}
typedef enum {
RTP = 1,
RTCP = 2
} ComponentType;
class CandidateSet {
@@ -935,23 +1205,22 @@ protected:
<< context << " (level " << msection.GetLevel() << ")";
} else {
ASSERT_FALSE(msection.GetAttributeList().HasAttribute(
SdpAttribute::kEndOfCandidatesAttribute))
<< context << " (level " << msection.GetLevel() << ")";
}
}
- void CheckPairs(const JsepSession& session, const std::string& context)
+ void CheckTransceiversAreBundled(const JsepSession& session,
+ const std::string& context)
{
- auto pairs = session.GetNegotiatedTrackPairs();
-
- for (JsepTrackPair& pair : pairs) {
- ASSERT_TRUE(pair.HasBundleLevel()) << context;
- ASSERT_EQ(0U, pair.BundleLevel()) << context;
+ for (const auto& transceiver : session.GetTransceivers()) {
+ ASSERT_TRUE(transceiver->HasBundleLevel()) << context;
+ ASSERT_EQ(0U, transceiver->BundleLevel()) << context;
}
}
void
DisableMsid(std::string* sdp) const {
size_t pos = sdp->find("a=msid-semantic");
ASSERT_NE(std::string::npos, pos);
(*sdp)[pos + 2] = 'X'; // garble, a=Xsid-semantic
@@ -1029,16 +1298,21 @@ protected:
} else {
// Not that we would have any test which tests this...
ASSERT_EQ("19", msection->GetFormats()[0]);
const SdpRtpmapAttributeList::Rtpmap* rtpmap(msection->FindRtpmap("19"));
ASSERT_TRUE(rtpmap);
ASSERT_EQ("19", rtpmap->pt);
ASSERT_EQ("reserved", rtpmap->name);
}
+
+ ASSERT_FALSE(msection->GetAttributeList().HasAttribute(
+ SdpAttribute::kMsidAttribute));
+ ASSERT_FALSE(msection->GetAttributeList().HasAttribute(
+ SdpAttribute::kMidAttribute));
}
void
ValidateSetupAttribute(const JsepSessionImpl& side,
const SdpSetupAttribute::Role expectedRole)
{
auto sdp = GetParsedLocalDescription(side);
for (size_t i = 0; sdp && i < sdp->GetMediaSectionCount(); ++i) {
@@ -1049,17 +1323,22 @@ protected:
}
}
}
void
DumpTrack(const JsepTrack& track)
{
const JsepTrackNegotiatedDetails* details = track.GetNegotiatedDetails();
- std::cerr << " type=" << track.GetMediaType() << std::endl;
+ std::cerr << " type=" << track.GetMediaType() << " track-id="
+ << track.GetTrackId() << std::endl;
+ if (!details) {
+ std::cerr << " not negotiated" << std::endl;
+ return;
+ }
std::cerr << " encodings=" << std::endl;
for (size_t i = 0; i < details->GetEncodingCount(); ++i) {
const JsepTrackEncoding& encoding = details->GetEncoding(i);
std::cerr << " id=" << encoding.mRid << std::endl;
for (const JsepCodecDescription* codec : encoding.GetCodecs()) {
std::cerr << " " << codec->mName
<< " enabled(" << (codec->mEnabled?"yes":"no") << ")";
if (track.GetMediaType() == SdpMediaSection::kAudio) {
@@ -1068,28 +1347,36 @@ protected:
std::cerr << " dtmf(" << (audioCodec->mDtmfEnabled?"yes":"no") << ")";
}
std::cerr << std::endl;
}
}
}
void
- DumpTrackPairs(const JsepSessionImpl& session)
+ DumpTransceivers(const JsepSessionImpl& session)
{
- auto pairs = mSessionAns->GetNegotiatedTrackPairs();
- for (auto i = pairs.begin(); i != pairs.end(); ++i) {
- std::cerr << "Track pair " << i->mLevel << std::endl;
- if (i->mSending) {
+ for (const auto& transceiver : mSessionAns->GetTransceivers()) {
+ std::cerr << "Transceiver ";
+ if (transceiver->HasLevel()) {
+ std::cerr << transceiver->GetLevel() << std::endl;
+ } else {
+ std::cerr << "<NO LEVEL>" << std::endl;
+ }
+ if (transceiver->HasBundleLevel()) {
+ std::cerr << "(bundle level is " << transceiver->BundleLevel() << ")"
+ << std::endl;
+ }
+ if (!IsNull(transceiver->mSending)) {
std::cerr << "Sending-->" << std::endl;
- DumpTrack(*i->mSending);
+ DumpTrack(transceiver->mSending);
}
- if (i->mReceiving) {
+ if (!IsNull(transceiver->mReceiving)) {
std::cerr << "Receiving-->" << std::endl;
- DumpTrack(*i->mReceiving);
+ DumpTrack(transceiver->mReceiving);
}
}
}
UniquePtr<Sdp>
Parse(const std::string& sdp) const
{
SipccSdpParser parser;
@@ -1301,34 +1588,36 @@ TEST_P(JsepSessionTest, RenegotiationNoC
{
AddTracks(*mSessionOff);
std::string offer = CreateOffer();
SetLocalOffer(offer);
SetRemoteOffer(offer);
auto added = mSessionAns->GetRemoteTracksAdded();
auto removed = mSessionAns->GetRemoteTracksRemoved();
- ASSERT_EQ(types.size(), added.size());
+ ASSERT_EQ(CountRtpTypes(), added.size());
ASSERT_EQ(0U, removed.size());
AddTracks(*mSessionAns);
std::string answer = CreateAnswer();
SetLocalAnswer(answer);
SetRemoteAnswer(answer);
added = mSessionOff->GetRemoteTracksAdded();
removed = mSessionOff->GetRemoteTracksRemoved();
- ASSERT_EQ(types.size(), added.size());
+ ASSERT_EQ(CountRtpTypes(), added.size());
ASSERT_EQ(0U, removed.size());
ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
+ std::vector<RefPtr<JsepTransceiver>> origOffererTransceivers
+ = DeepCopy(mSessionOff->GetTransceivers());
+ std::vector<RefPtr<JsepTransceiver>> origAnswererTransceivers
+ = DeepCopy(mSessionAns->GetTransceivers());
std::string reoffer = CreateOffer();
SetLocalOffer(reoffer);
SetRemoteOffer(reoffer);
added = mSessionAns->GetRemoteTracksAdded();
removed = mSessionAns->GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
@@ -1341,27 +1630,29 @@ TEST_P(JsepSessionTest, RenegotiationNoC
added = mSessionOff->GetRemoteTracksAdded();
removed = mSessionOff->GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(0U, removed.size());
ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
- auto newOffererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto newAnswererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
- for (size_t i = 0; i < offererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
+ auto newOffererTransceivers = mSessionOff->GetTransceivers();
+ auto newAnswererTransceivers = mSessionAns->GetTransceivers();
+
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+ for (size_t i = 0; i < origOffererTransceivers.size(); ++i) {
+ ASSERT_TRUE(Equals(*origOffererTransceivers[i],
+ *newOffererTransceivers[i]));
}
- ASSERT_EQ(answererPairs.size(), newAnswererPairs.size());
- for (size_t i = 0; i < answererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
+ ASSERT_EQ(origAnswererTransceivers.size(), newAnswererTransceivers.size());
+ for (size_t i = 0; i < origAnswererTransceivers.size(); ++i) {
+ ASSERT_TRUE(Equals(*origAnswererTransceivers[i],
+ *newAnswererTransceivers[i]));
}
}
// Disabled: See Bug 1329028
TEST_P(JsepSessionTest, DISABLED_RenegotiationSwappedRolesNoChange)
{
AddTracks(*mSessionOff);
std::string offer = CreateOffer();
@@ -1381,18 +1672,18 @@ TEST_P(JsepSessionTest, DISABLED_Renegot
added = mSessionOff->GetRemoteTracksAdded();
removed = mSessionOff->GetRemoteTracksRemoved();
ASSERT_EQ(types.size(), added.size());
ASSERT_EQ(0U, removed.size());
ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
+ auto offererTransceivers = DeepCopy(mSessionOff->GetTransceivers());
+ auto answererTransceivers = DeepCopy(mSessionAns->GetTransceivers());
SwapOfferAnswerRoles();
std::string reoffer = CreateOffer();
SetLocalOffer(reoffer);
SetRemoteOffer(reoffer);
added = mSessionAns->GetRemoteTracksAdded();
@@ -1407,108 +1698,114 @@ TEST_P(JsepSessionTest, DISABLED_Renegot
added = mSessionOff->GetRemoteTracksAdded();
removed = mSessionOff->GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(0U, removed.size());
ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kPassive);
- auto newOffererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto newAnswererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- ASSERT_EQ(offererPairs.size(), newAnswererPairs.size());
- for (size_t i = 0; i < offererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(offererPairs[i], newAnswererPairs[i]));
+ auto newOffererTransceivers = mSessionOff->GetTransceivers();
+ auto newAnswererTransceivers = mSessionAns->GetTransceivers();
+
+ ASSERT_EQ(offererTransceivers.size(), newAnswererTransceivers.size());
+ for (size_t i = 0; i < offererTransceivers.size(); ++i) {
+ ASSERT_TRUE(Equals(*offererTransceivers[i], *newAnswererTransceivers[i]));
}
- ASSERT_EQ(answererPairs.size(), newOffererPairs.size());
- for (size_t i = 0; i < answererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(answererPairs[i], newOffererPairs[i]));
+ ASSERT_EQ(answererTransceivers.size(), newOffererTransceivers.size());
+ for (size_t i = 0; i < answererTransceivers.size(); ++i) {
+ ASSERT_TRUE(Equals(*answererTransceivers[i], *newOffererTransceivers[i]));
}
}
TEST_P(JsepSessionTest, RenegotiationOffererAddsTrack)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
OfferAnswer();
ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
+ std::vector<RefPtr<JsepTransceiver>> origOffererTransceivers
+ = DeepCopy(mSessionOff->GetTransceivers());
+ std::vector<RefPtr<JsepTransceiver>> origAnswererTransceivers
+ = DeepCopy(mSessionAns->GetTransceivers());
std::vector<SdpMediaSection::MediaType> extraTypes;
extraTypes.push_back(SdpMediaSection::kAudio);
extraTypes.push_back(SdpMediaSection::kVideo);
AddTracks(*mSessionOff, extraTypes);
types.insert(types.end(), extraTypes.begin(), extraTypes.end());
OfferAnswer(CHECK_SUCCESS);
ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
auto added = mSessionAns->GetRemoteTracksAdded();
auto removed = mSessionAns->GetRemoteTracksRemoved();
ASSERT_EQ(2U, added.size());
ASSERT_EQ(0U, removed.size());
- ASSERT_EQ(SdpMediaSection::kAudio, added[0]->GetMediaType());
- ASSERT_EQ(SdpMediaSection::kVideo, added[1]->GetMediaType());
+ ASSERT_EQ(SdpMediaSection::kAudio, added[0].GetMediaType());
+ ASSERT_EQ(SdpMediaSection::kVideo, added[1].GetMediaType());
added = mSessionOff->GetRemoteTracksAdded();
removed = mSessionOff->GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(0U, removed.size());
- auto newOffererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto newAnswererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- ASSERT_EQ(offererPairs.size() + 2, newOffererPairs.size());
- for (size_t i = 0; i < offererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
+ auto newOffererTransceivers = mSessionOff->GetTransceivers();
+ auto newAnswererTransceivers = mSessionAns->GetTransceivers();
+
+ ASSERT_EQ(origOffererTransceivers.size() + 2, newOffererTransceivers.size());
+ for (size_t i = 0; i < origOffererTransceivers.size(); ++i) {
+ ASSERT_TRUE(Equals(*origOffererTransceivers[i],
+ *newOffererTransceivers[i]));
}
- ASSERT_EQ(answererPairs.size() + 2, newAnswererPairs.size());
- for (size_t i = 0; i < answererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
+ ASSERT_EQ(origAnswererTransceivers.size() + 2,
+ newAnswererTransceivers.size());
+ for (size_t i = 0; i < origAnswererTransceivers.size(); ++i) {
+ ASSERT_TRUE(Equals(*origAnswererTransceivers[i],
+ *newAnswererTransceivers[i]));
}
}
TEST_P(JsepSessionTest, RenegotiationAnswererAddsTrack)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
OfferAnswer();
ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
+ std::vector<RefPtr<JsepTransceiver>> origOffererTransceivers
+ = DeepCopy(mSessionOff->GetTransceivers());
+ std::vector<RefPtr<JsepTransceiver>> origAnswererTransceivers
+ = DeepCopy(mSessionAns->GetTransceivers());
std::vector<SdpMediaSection::MediaType> extraTypes;
extraTypes.push_back(SdpMediaSection::kAudio);
extraTypes.push_back(SdpMediaSection::kVideo);
AddTracks(*mSessionAns, extraTypes);
types.insert(types.end(), extraTypes.begin(), extraTypes.end());
// We need to add a recvonly m-section to the offer for this to work
- JsepOfferOptions options;
- options.mOfferToReceiveAudio =
- Some(GetTrackCount(*mSessionOff, SdpMediaSection::kAudio) + 1);
- options.mOfferToReceiveVideo =
- Some(GetTrackCount(*mSessionOff, SdpMediaSection::kVideo) + 1);
-
- std::string offer = CreateOffer(Some(options));
+ mSessionOff->AddTransceiver(new JsepTransceiver(
+ SdpMediaSection::kAudio, SdpDirectionAttribute::Direction::kRecvonly));
+ mSessionOff->AddTransceiver(new JsepTransceiver(
+ SdpMediaSection::kVideo, SdpDirectionAttribute::Direction::kRecvonly));
+
+ std::string offer = CreateOffer();
SetLocalOffer(offer, CHECK_SUCCESS);
SetRemoteOffer(offer, CHECK_SUCCESS);
std::string answer = CreateAnswer();
SetLocalAnswer(answer, CHECK_SUCCESS);
SetRemoteAnswer(answer, CHECK_SUCCESS);
ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
@@ -1518,45 +1815,50 @@ TEST_P(JsepSessionTest, RenegotiationAns
auto removed = mSessionAns->GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(0U, removed.size());
added = mSessionOff->GetRemoteTracksAdded();
removed = mSessionOff->GetRemoteTracksRemoved();
ASSERT_EQ(2U, added.size());
ASSERT_EQ(0U, removed.size());
- ASSERT_EQ(SdpMediaSection::kAudio, added[0]->GetMediaType());
- ASSERT_EQ(SdpMediaSection::kVideo, added[1]->GetMediaType());
-
- auto newOffererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto newAnswererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- ASSERT_EQ(offererPairs.size() + 2, newOffererPairs.size());
- for (size_t i = 0; i < offererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
+ ASSERT_EQ(SdpMediaSection::kAudio, added[0].GetMediaType());
+ ASSERT_EQ(SdpMediaSection::kVideo, added[1].GetMediaType());
+
+ auto newOffererTransceivers = mSessionOff->GetTransceivers();
+ auto newAnswererTransceivers = mSessionAns->GetTransceivers();
+
+ ASSERT_EQ(origOffererTransceivers.size() + 2, newOffererTransceivers.size());
+ for (size_t i = 0; i < origOffererTransceivers.size(); ++i) {
+ ASSERT_TRUE(Equals(*origOffererTransceivers[i],
+ *newOffererTransceivers[i]));
}
- ASSERT_EQ(answererPairs.size() + 2, newAnswererPairs.size());
- for (size_t i = 0; i < answererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
+ ASSERT_EQ(origAnswererTransceivers.size() + 2,
+ newAnswererTransceivers.size());
+ for (size_t i = 0; i < origAnswererTransceivers.size(); ++i) {
+ ASSERT_TRUE(Equals(*origAnswererTransceivers[i],
+ *newAnswererTransceivers[i]));
}
}
TEST_P(JsepSessionTest, RenegotiationBothAddTrack)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
OfferAnswer();
ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
+ std::vector<RefPtr<JsepTransceiver>> origOffererTransceivers
+ = DeepCopy(mSessionOff->GetTransceivers());
+ std::vector<RefPtr<JsepTransceiver>> origAnswererTransceivers
+ = DeepCopy(mSessionAns->GetTransceivers());
std::vector<SdpMediaSection::MediaType> extraTypes;
extraTypes.push_back(SdpMediaSection::kAudio);
extraTypes.push_back(SdpMediaSection::kVideo);
AddTracks(*mSessionAns, extraTypes);
AddTracks(*mSessionOff, extraTypes);
types.insert(types.end(), extraTypes.begin(), extraTypes.end());
@@ -1564,591 +1866,551 @@ TEST_P(JsepSessionTest, RenegotiationBot
ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
auto added = mSessionAns->GetRemoteTracksAdded();
auto removed = mSessionAns->GetRemoteTracksRemoved();
ASSERT_EQ(2U, added.size());
ASSERT_EQ(0U, removed.size());
- ASSERT_EQ(SdpMediaSection::kAudio, added[0]->GetMediaType());
- ASSERT_EQ(SdpMediaSection::kVideo, added[1]->GetMediaType());
+ ASSERT_EQ(SdpMediaSection::kAudio, added[0].GetMediaType());
+ ASSERT_EQ(SdpMediaSection::kVideo, added[1].GetMediaType());
added = mSessionOff->GetRemoteTracksAdded();
removed = mSessionOff->GetRemoteTracksRemoved();
ASSERT_EQ(2U, added.size());
ASSERT_EQ(0U, removed.size());
- ASSERT_EQ(SdpMediaSection::kAudio, added[0]->GetMediaType());
- ASSERT_EQ(SdpMediaSection::kVideo, added[1]->GetMediaType());
-
- auto newOffererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto newAnswererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- ASSERT_EQ(offererPairs.size() + 2, newOffererPairs.size());
- for (size_t i = 0; i < offererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
+ ASSERT_EQ(SdpMediaSection::kAudio, added[0].GetMediaType());
+ ASSERT_EQ(SdpMediaSection::kVideo, added[1].GetMediaType());
+
+ auto newOffererTransceivers = mSessionOff->GetTransceivers();
+ auto newAnswererTransceivers = mSessionAns->GetTransceivers();
+
+ ASSERT_EQ(origOffererTransceivers.size() + 2, newOffererTransceivers.size());
+ for (size_t i = 0; i < origOffererTransceivers.size(); ++i) {
+ ASSERT_TRUE(Equals(*origOffererTransceivers[i],
+ *newOffererTransceivers[i]));
}
- ASSERT_EQ(answererPairs.size() + 2, newAnswererPairs.size());
- for (size_t i = 0; i < answererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
+ ASSERT_EQ(origAnswererTransceivers.size() + 2,
+ newAnswererTransceivers.size());
+ for (size_t i = 0; i < origAnswererTransceivers.size(); ++i) {
+ ASSERT_TRUE(Equals(*origAnswererTransceivers[i],
+ *newAnswererTransceivers[i]));
}
}
TEST_P(JsepSessionTest, RenegotiationBothAddTracksToExistingStream)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
if (GetParam() == "datachannel") {
return;
}
OfferAnswer();
- auto oHasStream = HasMediaStream(mSessionOff->GetLocalTracks());
- auto aHasStream = HasMediaStream(mSessionAns->GetLocalTracks());
+ auto oHasStream = HasMediaStream(GetLocalTracks(*mSessionOff));
+ auto aHasStream = HasMediaStream(GetLocalTracks(*mSessionAns));
ASSERT_EQ(oHasStream, !GetLocalUniqueStreamIds(*mSessionOff).empty());
ASSERT_EQ(aHasStream, !GetLocalUniqueStreamIds(*mSessionAns).empty());
ASSERT_EQ(aHasStream, !GetRemoteUniqueStreamIds(*mSessionOff).empty());
ASSERT_EQ(oHasStream, !GetRemoteUniqueStreamIds(*mSessionAns).empty());
auto firstOffId = GetFirstLocalStreamId(*mSessionOff);
auto firstAnsId = GetFirstLocalStreamId(*mSessionAns);
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
+ auto offererTransceivers = DeepCopy(mSessionOff->GetTransceivers());
+ auto answererTransceivers = DeepCopy(mSessionAns->GetTransceivers());
std::vector<SdpMediaSection::MediaType> extraTypes;
extraTypes.push_back(SdpMediaSection::kAudio);
extraTypes.push_back(SdpMediaSection::kVideo);
AddTracksToStream(*mSessionOff, firstOffId, extraTypes);
AddTracksToStream(*mSessionAns, firstAnsId, extraTypes);
types.insert(types.end(), extraTypes.begin(), extraTypes.end());
OfferAnswer(CHECK_SUCCESS);
- oHasStream = HasMediaStream(mSessionOff->GetLocalTracks());
- aHasStream = HasMediaStream(mSessionAns->GetLocalTracks());
+ oHasStream = HasMediaStream(GetLocalTracks(*mSessionOff));
+ aHasStream = HasMediaStream(GetLocalTracks(*mSessionAns));
ASSERT_EQ(oHasStream, !GetLocalUniqueStreamIds(*mSessionOff).empty());
ASSERT_EQ(aHasStream, !GetLocalUniqueStreamIds(*mSessionAns).empty());
ASSERT_EQ(aHasStream, !GetRemoteUniqueStreamIds(*mSessionOff).empty());
ASSERT_EQ(oHasStream, !GetRemoteUniqueStreamIds(*mSessionAns).empty());
if (oHasStream) {
ASSERT_STREQ(firstOffId.c_str(),
GetFirstLocalStreamId(*mSessionOff).c_str());
}
if (aHasStream) {
ASSERT_STREQ(firstAnsId.c_str(),
GetFirstLocalStreamId(*mSessionAns).c_str());
- auto oHasStream = HasMediaStream(mSessionOff->GetLocalTracks());
- auto aHasStream = HasMediaStream(mSessionAns->GetLocalTracks());
- ASSERT_EQ(oHasStream, !GetLocalUniqueStreamIds(*mSessionOff).empty());
- ASSERT_EQ(aHasStream, !GetLocalUniqueStreamIds(*mSessionAns).empty());
+ auto oHasStream = HasMediaStream(GetLocalTracks(*mSessionOff));
+ auto aHasStream = HasMediaStream(GetLocalTracks(*mSessionAns));
+ ASSERT_EQ(oHasStream, !GetLocalUniqueStreamIds(*mSessionOff).empty());
+ ASSERT_EQ(aHasStream, !GetLocalUniqueStreamIds(*mSessionAns).empty());
}
}
-TEST_P(JsepSessionTest, RenegotiationOffererRemovesTrack)
+// The JSEP draft explicitly forbids changing the msid on an m-section, but
+// that is a new restriction that older versions of Firefox do not follow.
+TEST_P(JsepSessionTest, RenegotiationOffererChangesMsid)
+{
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+ if (types.front() == SdpMediaSection::kApplication) {
+ return;
+ }
+
+ OfferAnswer();
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+
+ JsepTransceiver* transceiver = GetNegotiatedTransceiver(*mSessionOff, 0);
+ ASSERT_TRUE(transceiver);
+ std::string streamId = transceiver->mSending.GetStreamIds()[0];
+ std::string trackId = transceiver->mSending.GetTrackId();
+ std::string msidToReplace("a=msid:");
+ msidToReplace += streamId;
+ msidToReplace += " ";
+ msidToReplace += trackId;
+ size_t msidOffset = offer.find(msidToReplace);
+ ASSERT_NE(std::string::npos, msidOffset);
+ offer.replace(msidOffset, msidToReplace.size(), "a=msid:foo bar");
+
+ SetRemoteOffer(offer);
+
+ std::vector<JsepTrack> removedTracks = mSessionAns->GetRemoteTracksRemoved();
+ std::vector<JsepTrack> addedTracks = mSessionAns->GetRemoteTracksAdded();
+
+ ASSERT_EQ(1U, removedTracks.size());
+ ASSERT_FALSE(IsNull(removedTracks[0]));
+ ASSERT_EQ(streamId, removedTracks[0].GetStreamIds()[0]);
+ ASSERT_EQ(trackId, removedTracks[0].GetTrackId());
+
+ ASSERT_EQ(1U, addedTracks.size());
+ ASSERT_FALSE(IsNull(addedTracks[0]));
+ ASSERT_EQ("foo", addedTracks[0].GetStreamIds()[0]);
+ ASSERT_EQ("bar", addedTracks[0].GetTrackId());
+
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+}
+
+// The JSEP draft explicitly forbids changing the msid on an m-section, but
+// that is a new restriction that older versions of Firefox do not follow.
+TEST_P(JsepSessionTest, RenegotiationAnswererChangesMsid)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
if (types.front() == SdpMediaSection::kApplication) {
return;
}
OfferAnswer();
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- RefPtr<JsepTrack> removedTrack = GetTrackOff(0, types.front());
- ASSERT_TRUE(removedTrack);
- ASSERT_EQ(NS_OK, mSessionOff->RemoveTrack(removedTrack->GetStreamId(),
- removedTrack->GetTrackId()));
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+
+ JsepTransceiver* transceiver = GetNegotiatedTransceiver(*mSessionAns, 0);
+ ASSERT_TRUE(transceiver);
+ std::string streamId = transceiver->mSending.GetStreamIds()[0];
+ std::string trackId = transceiver->mSending.GetTrackId();
+ std::string msidToReplace("a=msid:");
+ msidToReplace += streamId;
+ msidToReplace += " ";
+ msidToReplace += trackId;
+ size_t msidOffset = answer.find(msidToReplace);
+ ASSERT_NE(std::string::npos, msidOffset);
+ answer.replace(msidOffset, msidToReplace.size(), "a=msid:foo bar");
+
+ SetRemoteAnswer(answer);
+
+ std::vector<JsepTrack> removedTracks = mSessionOff->GetRemoteTracksRemoved();
+ std::vector<JsepTrack> addedTracks = mSessionOff->GetRemoteTracksAdded();
+
+ ASSERT_EQ(1U, removedTracks.size());
+ ASSERT_FALSE(IsNull(removedTracks[0]));
+ ASSERT_EQ(streamId, removedTracks[0].GetStreamIds()[0]);
+ ASSERT_EQ(trackId, removedTracks[0].GetTrackId());
+
+ ASSERT_EQ(1U, addedTracks.size());
+ ASSERT_FALSE(IsNull(addedTracks[0]));
+ ASSERT_EQ("foo", addedTracks[0].GetStreamIds()[0]);
+ ASSERT_EQ("bar", addedTracks[0].GetTrackId());
+}
+
+TEST_P(JsepSessionTest, RenegotiationOffererStopsTransceiver)
+{
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+ if (types.back() == SdpMediaSection::kApplication) {
+ return;
+ }
+
+ OfferAnswer();
+
+ std::vector<RefPtr<JsepTransceiver>> origOffererTransceivers =
+ DeepCopy(mSessionOff->GetTransceivers());
+ std::vector<RefPtr<JsepTransceiver>> origAnswererTransceivers =
+ DeepCopy(mSessionAns->GetTransceivers());
+
+ // Avoid bundle transport side effects; don't stop the BUNDLE-tag!
+ mSessionOff->GetTransceivers().back()->Stop();
+ JsepTrack removedTrack(mSessionOff->GetTransceivers().back()->mSending);
OfferAnswer(CHECK_SUCCESS);
auto added = mSessionAns->GetRemoteTracksAdded();
auto removed = mSessionAns->GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(1U, removed.size());
- ASSERT_EQ(removedTrack->GetMediaType(), removed[0]->GetMediaType());
- ASSERT_EQ(removedTrack->GetStreamId(), removed[0]->GetStreamId());
- ASSERT_EQ(removedTrack->GetTrackId(), removed[0]->GetTrackId());
+ ASSERT_EQ(removedTrack.GetMediaType(), removed[0].GetMediaType());
+ ASSERT_EQ(removedTrack.GetStreamIds(), removed[0].GetStreamIds());
+ ASSERT_EQ(removedTrack.GetTrackId(), removed[0].GetTrackId());
added = mSessionOff->GetRemoteTracksAdded();
removed = mSessionOff->GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
- ASSERT_EQ(0U, removed.size());
-
- // First m-section should be recvonly
+ ASSERT_EQ(1U, removed.size());
+
+ // Last m-section should be disabled
auto offer = GetParsedLocalDescription(*mSessionOff);
- auto* msection = GetMsection(*offer, types.front(), 0);
+ const SdpMediaSection* msection =
+ &offer->GetMediaSection(offer->GetMediaSectionCount() - 1);
ASSERT_TRUE(msection);
- ASSERT_TRUE(msection->IsReceiving());
- ASSERT_FALSE(msection->IsSending());
-
- // First audio m-section should be sendonly
+ ValidateDisabledMSection(msection);
+
+ // Last m-section should be disabled
auto answer = GetParsedLocalDescription(*mSessionAns);
- msection = GetMsection(*answer, types.front(), 0);
+ msection = &answer->GetMediaSection(answer->GetMediaSectionCount() - 1);
ASSERT_TRUE(msection);
- ASSERT_FALSE(msection->IsReceiving());
- ASSERT_TRUE(msection->IsSending());
-
- auto newOffererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto newAnswererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- // Will be the same size since we still have a track on one side.
- ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
-
- // This should be the only difference.
- ASSERT_TRUE(offererPairs[0].mSending);
- ASSERT_FALSE(newOffererPairs[0].mSending);
-
- // Remove this difference, let loop below take care of the rest
- offererPairs[0].mSending = nullptr;
- for (size_t i = 0; i < offererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
+ ValidateDisabledMSection(msection);
+
+ auto newOffererTransceivers = mSessionOff->GetTransceivers();
+ auto newAnswererTransceivers = mSessionAns->GetTransceivers();
+
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+
+ ASSERT_FALSE(origOffererTransceivers.back()->IsStopped());
+ ASSERT_TRUE(newOffererTransceivers.back()->IsStopped());
+
+ for (size_t i = 0; i < origOffererTransceivers.size() - 1; ++i) {
+ ASSERT_TRUE(Equals(*origOffererTransceivers[i],
+ *newOffererTransceivers[i]));
}
- // Will be the same size since we still have a track on one side.
- ASSERT_EQ(answererPairs.size(), newAnswererPairs.size());
-
- // This should be the only difference.
- ASSERT_TRUE(answererPairs[0].mReceiving);
- ASSERT_FALSE(newAnswererPairs[0].mReceiving);
-
- // Remove this difference, let loop below take care of the rest
- answererPairs[0].mReceiving = nullptr;
- for (size_t i = 0; i < answererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
+ ASSERT_EQ(origAnswererTransceivers.size(), newAnswererTransceivers.size());
+
+ ASSERT_FALSE(origAnswererTransceivers.back()->IsStopped());
+ ASSERT_TRUE(newAnswererTransceivers.back()->IsStopped());
+
+ for (size_t i = 0; i < origAnswererTransceivers.size() - 1; ++i) {
+ ASSERT_TRUE(Equals(*origAnswererTransceivers[i],
+ *newAnswererTransceivers[i]));
}
}
-TEST_P(JsepSessionTest, RenegotiationAnswererRemovesTrack)
+TEST_P(JsepSessionTest, RenegotiationAnswererStopsTransceiver)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
- if (types.front() == SdpMediaSection::kApplication) {
+ if (types.back() == SdpMediaSection::kApplication) {
return;
}
OfferAnswer();
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- RefPtr<JsepTrack> removedTrack = GetTrackAns(0, types.front());
- ASSERT_TRUE(removedTrack);
- ASSERT_EQ(NS_OK, mSessionAns->RemoveTrack(removedTrack->GetStreamId(),
- removedTrack->GetTrackId()));
+ std::vector<RefPtr<JsepTransceiver>> origOffererTransceivers
+ = DeepCopy(mSessionOff->GetTransceivers());
+ std::vector<RefPtr<JsepTransceiver>> origAnswererTransceivers
+ = DeepCopy(mSessionAns->GetTransceivers());
+
+ // Avoid bundle transport side effects; don't stop the BUNDLE-tag!
+ mSessionAns->GetTransceivers().back()->Stop();
+ JsepTrack removedTrack(mSessionAns->GetTransceivers().back()->mSending);
OfferAnswer(CHECK_SUCCESS);
auto added = mSessionAns->GetRemoteTracksAdded();
auto removed = mSessionAns->GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(0U, removed.size());
added = mSessionOff->GetRemoteTracksAdded();
removed = mSessionOff->GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(1U, removed.size());
- ASSERT_EQ(removedTrack->GetMediaType(), removed[0]->GetMediaType());
- ASSERT_EQ(removedTrack->GetStreamId(), removed[0]->GetStreamId());
- ASSERT_EQ(removedTrack->GetTrackId(), removed[0]->GetTrackId());
-
- // First m-section should be sendrecv
+ ASSERT_EQ(removedTrack.GetMediaType(), removed[0].GetMediaType());
+ ASSERT_EQ(removedTrack.GetStreamIds(), removed[0].GetStreamIds());
+ ASSERT_EQ(removedTrack.GetTrackId(), removed[0].GetTrackId());
+
+ // Last m-section should be sendrecv
auto offer = GetParsedLocalDescription(*mSessionOff);
- auto* msection = GetMsection(*offer, types.front(), 0);
+ const SdpMediaSection* msection =
+ &offer->GetMediaSection(offer->GetMediaSectionCount() - 1);
ASSERT_TRUE(msection);
ASSERT_TRUE(msection->IsReceiving());
ASSERT_TRUE(msection->IsSending());
- // First audio m-section should be recvonly
+ // Last m-section should be disabled
auto answer = GetParsedLocalDescription(*mSessionAns);
- msection = GetMsection(*answer, types.front(), 0);
+ msection = &answer->GetMediaSection(answer->GetMediaSectionCount() - 1);
ASSERT_TRUE(msection);
- ASSERT_TRUE(msection->IsReceiving());
- ASSERT_FALSE(msection->IsSending());
-
- auto newOffererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto newAnswererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- // Will be the same size since we still have a track on one side.
- ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
-
- // This should be the only difference.
- ASSERT_TRUE(offererPairs[0].mReceiving);
- ASSERT_FALSE(newOffererPairs[0].mReceiving);
-
- // Remove this difference, let loop below take care of the rest
- offererPairs[0].mReceiving = nullptr;
- for (size_t i = 0; i < offererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
+ ValidateDisabledMSection(msection);
+
+ auto newOffererTransceivers = mSessionOff->GetTransceivers();
+ auto newAnswererTransceivers = mSessionAns->GetTransceivers();
+
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+
+ ASSERT_FALSE(origOffererTransceivers.back()->IsStopped());
+ ASSERT_TRUE(newOffererTransceivers.back()->IsStopped());
+
+ for (size_t i = 0; i < origOffererTransceivers.size() - 1; ++i) {
+ ASSERT_TRUE(Equals(*origOffererTransceivers[i],
+ *newOffererTransceivers[i]));
}
- // Will be the same size since we still have a track on one side.
- ASSERT_EQ(answererPairs.size(), newAnswererPairs.size());
-
- // This should be the only difference.
- ASSERT_TRUE(answererPairs[0].mSending);
- ASSERT_FALSE(newAnswererPairs[0].mSending);
-
- // Remove this difference, let loop below take care of the rest
- answererPairs[0].mSending = nullptr;
- for (size_t i = 0; i < answererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
+ ASSERT_EQ(origAnswererTransceivers.size(), newAnswererTransceivers.size());
+
+ ASSERT_FALSE(origAnswererTransceivers.back()->IsStopped());
+ ASSERT_TRUE(newAnswererTransceivers.back()->IsStopped());
+
+ for (size_t i = 0; i < origAnswererTransceivers.size() - 1; ++i) {
+ ASSERT_TRUE(Equals(*origAnswererTransceivers[i],
+ *newAnswererTransceivers[i]));
}
}
-TEST_P(JsepSessionTest, RenegotiationBothRemoveTrack)
+TEST_P(JsepSessionTest, RenegotiationBothStopSameTransceiver)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
- if (types.front() == SdpMediaSection::kApplication) {
+ if (types.back() == SdpMediaSection::kApplication) {
return;
}
OfferAnswer();
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- RefPtr<JsepTrack> removedTrackAnswer = GetTrackAns(0, types.front());
- ASSERT_TRUE(removedTrackAnswer);
- ASSERT_EQ(NS_OK, mSessionAns->RemoveTrack(removedTrackAnswer->GetStreamId(),
- removedTrackAnswer->GetTrackId()));
-
- RefPtr<JsepTrack> removedTrackOffer = GetTrackOff(0, types.front());
- ASSERT_TRUE(removedTrackOffer);
- ASSERT_EQ(NS_OK, mSessionOff->RemoveTrack(removedTrackOffer->GetStreamId(),
- removedTrackOffer->GetTrackId()));
+ std::vector<RefPtr<JsepTransceiver>> origOffererTransceivers
+ = DeepCopy(mSessionOff->GetTransceivers());
+ std::vector<RefPtr<JsepTransceiver>> origAnswererTransceivers
+ = DeepCopy(mSessionAns->GetTransceivers());
+
+ // Avoid bundle transport side effects; don't stop the BUNDLE-tag!
+ mSessionOff->GetTransceivers().back()->Stop();
+ JsepTrack removedTrackOffer(mSessionOff->GetTransceivers().back()->mSending);
+ mSessionAns->GetTransceivers().back()->Stop();
+ JsepTrack removedTrackAnswer(mSessionAns->GetTransceivers().back()->mSending);
OfferAnswer(CHECK_SUCCESS);
auto added = mSessionAns->GetRemoteTracksAdded();
auto removed = mSessionAns->GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(1U, removed.size());
- ASSERT_EQ(removedTrackOffer->GetMediaType(), removed[0]->GetMediaType());
- ASSERT_EQ(removedTrackOffer->GetStreamId(), removed[0]->GetStreamId());
- ASSERT_EQ(removedTrackOffer->GetTrackId(), removed[0]->GetTrackId());
+ ASSERT_EQ(removedTrackOffer.GetMediaType(), removed[0].GetMediaType());
+ ASSERT_EQ(removedTrackOffer.GetStreamIds(), removed[0].GetStreamIds());
+ ASSERT_EQ(removedTrackOffer.GetTrackId(), removed[0].GetTrackId());
added = mSessionOff->GetRemoteTracksAdded();
removed = mSessionOff->GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(1U, removed.size());
- ASSERT_EQ(removedTrackAnswer->GetMediaType(), removed[0]->GetMediaType());
- ASSERT_EQ(removedTrackAnswer->GetStreamId(), removed[0]->GetStreamId());
- ASSERT_EQ(removedTrackAnswer->GetTrackId(), removed[0]->GetTrackId());
-
- // First m-section should be recvonly
+ ASSERT_EQ(removedTrackAnswer.GetMediaType(), removed[0].GetMediaType());
+ ASSERT_EQ(removedTrackAnswer.GetStreamIds(), removed[0].GetStreamIds());
+ ASSERT_EQ(removedTrackAnswer.GetTrackId(), removed[0].GetTrackId());
+
+ // Last m-section should be disabled
auto offer = GetParsedLocalDescription(*mSessionOff);
- auto* msection = GetMsection(*offer, types.front(), 0);
+ const SdpMediaSection* msection =
+ &offer->GetMediaSection(offer->GetMediaSectionCount() - 1);
ASSERT_TRUE(msection);
- ASSERT_TRUE(msection->IsReceiving());
- ASSERT_FALSE(msection->IsSending());
-
- // First m-section should be inactive, and rejected
+ ValidateDisabledMSection(msection);
+
+ // Last m-section should be disabled
auto answer = GetParsedLocalDescription(*mSessionAns);
- msection = GetMsection(*answer, types.front(), 0);
+ msection = &answer->GetMediaSection(answer->GetMediaSectionCount() - 1);
ASSERT_TRUE(msection);
- ASSERT_FALSE(msection->IsReceiving());
- ASSERT_FALSE(msection->IsSending());
- ASSERT_FALSE(msection->GetPort());
-
- auto newOffererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto newAnswererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- ASSERT_EQ(offererPairs.size(), newOffererPairs.size() + 1);
-
- for (size_t i = 0; i < newOffererPairs.size(); ++i) {
- JsepTrackPair oldPair(offererPairs[i + 1]);
- JsepTrackPair newPair(newOffererPairs[i]);
- ASSERT_EQ(oldPair.mLevel, newPair.mLevel);
- ASSERT_EQ(oldPair.mSending.get(), newPair.mSending.get());
- ASSERT_EQ(oldPair.mReceiving.get(), newPair.mReceiving.get());
- ASSERT_TRUE(oldPair.HasBundleLevel());
- ASSERT_TRUE(newPair.HasBundleLevel());
- ASSERT_EQ(0U, oldPair.BundleLevel());
- ASSERT_EQ(1U, newPair.BundleLevel());
+ ValidateDisabledMSection(msection);
+
+ auto newOffererTransceivers = mSessionOff->GetTransceivers();
+ auto newAnswererTransceivers = mSessionAns->GetTransceivers();
+
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+
+ ASSERT_FALSE(origOffererTransceivers.back()->IsStopped());
+ ASSERT_TRUE(newOffererTransceivers.back()->IsStopped());
+
+ for (size_t i = 0; i < origOffererTransceivers.size() - 1; ++i) {
+ ASSERT_TRUE(Equals(*origOffererTransceivers[i],
+ *newOffererTransceivers[i]));
}
- ASSERT_EQ(answererPairs.size(), newAnswererPairs.size() + 1);
-
- for (size_t i = 0; i < newAnswererPairs.size(); ++i) {
- JsepTrackPair oldPair(answererPairs[i + 1]);
- JsepTrackPair newPair(newAnswererPairs[i]);
- ASSERT_EQ(oldPair.mLevel, newPair.mLevel);
- ASSERT_EQ(oldPair.mSending.get(), newPair.mSending.get());
- ASSERT_EQ(oldPair.mReceiving.get(), newPair.mReceiving.get());
- ASSERT_TRUE(oldPair.HasBundleLevel());
- ASSERT_TRUE(newPair.BundleLevel());
- ASSERT_EQ(0U, oldPair.BundleLevel());
- ASSERT_EQ(1U, newPair.BundleLevel());
+ ASSERT_EQ(origAnswererTransceivers.size(), newAnswererTransceivers.size());
+
+ ASSERT_FALSE(origAnswererTransceivers.back()->IsStopped());
+ ASSERT_TRUE(newAnswererTransceivers.back()->IsStopped());
+
+ for (size_t i = 0; i < origAnswererTransceivers.size() - 1; ++i) {
+ ASSERT_TRUE(Equals(*origAnswererTransceivers[i],
+ *newAnswererTransceivers[i]));
}
}
-TEST_P(JsepSessionTest, RenegotiationBothRemoveThenAddTrack)
+TEST_P(JsepSessionTest, RenegotiationBothStopTransceiverThenAddTrack)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
- if (types.front() == SdpMediaSection::kApplication) {
+ if (types.back() == SdpMediaSection::kApplication) {
return;
}
- SdpMediaSection::MediaType removedType = types.front();
+ SdpMediaSection::MediaType removedType = types.back();
OfferAnswer();
- RefPtr<JsepTrack> removedTrackAnswer = GetTrackAns(0, removedType);
- ASSERT_TRUE(removedTrackAnswer);
- ASSERT_EQ(NS_OK, mSessionAns->RemoveTrack(removedTrackAnswer->GetStreamId(),
- removedTrackAnswer->GetTrackId()));
-
- RefPtr<JsepTrack> removedTrackOffer = GetTrackOff(0, removedType);
- ASSERT_TRUE(removedTrackOffer);
- ASSERT_EQ(NS_OK, mSessionOff->RemoveTrack(removedTrackOffer->GetStreamId(),
- removedTrackOffer->GetTrackId()));
+ // Avoid bundle transport side effects; don't stop the BUNDLE-tag!
+ mSessionOff->GetTransceivers().back()->Stop();
+ JsepTrack removedTrackOffer(mSessionOff->GetTransceivers().back()->mSending);
+ mSessionOff->GetTransceivers().back()->Stop();
+ JsepTrack removedTrackAnswer(mSessionOff->GetTransceivers().back()->mSending);
OfferAnswer(CHECK_SUCCESS);
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
+ std::vector<RefPtr<JsepTransceiver>> origOffererTransceivers
+ = DeepCopy(mSessionOff->GetTransceivers());
+ std::vector<RefPtr<JsepTransceiver>> origAnswererTransceivers
+ = DeepCopy(mSessionAns->GetTransceivers());
std::vector<SdpMediaSection::MediaType> extraTypes;
extraTypes.push_back(removedType);
AddTracks(*mSessionAns, extraTypes);
AddTracks(*mSessionOff, extraTypes);
types.insert(types.end(), extraTypes.begin(), extraTypes.end());
OfferAnswer(CHECK_SUCCESS);
auto added = mSessionAns->GetRemoteTracksAdded();
auto removed = mSessionAns->GetRemoteTracksRemoved();
ASSERT_EQ(1U, added.size());
ASSERT_EQ(0U, removed.size());
- ASSERT_EQ(removedType, added[0]->GetMediaType());
+ ASSERT_EQ(removedType, added[0].GetMediaType());
added = mSessionOff->GetRemoteTracksAdded();
removed = mSessionOff->GetRemoteTracksRemoved();
ASSERT_EQ(1U, added.size());
ASSERT_EQ(0U, removed.size());
- ASSERT_EQ(removedType, added[0]->GetMediaType());
-
- auto newOffererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto newAnswererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- ASSERT_EQ(offererPairs.size() + 1, newOffererPairs.size());
- ASSERT_EQ(answererPairs.size() + 1, newAnswererPairs.size());
+ ASSERT_EQ(removedType, added[0].GetMediaType());
+
+ auto newOffererTransceivers = mSessionOff->GetTransceivers();
+ auto newAnswererTransceivers = mSessionAns->GetTransceivers();
+
+ ASSERT_EQ(origOffererTransceivers.size() + 1, newOffererTransceivers.size());
+ ASSERT_EQ(origAnswererTransceivers.size() + 1,
+ newAnswererTransceivers.size());
// Ensure that the m-section was re-used; no gaps
- for (size_t i = 0; i < newOffererPairs.size(); ++i) {
- ASSERT_EQ(i, newOffererPairs[i].mLevel);
- }
- for (size_t i = 0; i < newAnswererPairs.size(); ++i) {
- ASSERT_EQ(i, newAnswererPairs[i].mLevel);
- }
+ ASSERT_EQ(origOffererTransceivers.back()->GetLevel(),
+ newOffererTransceivers.back()->GetLevel());
+
+ ASSERT_EQ(origAnswererTransceivers.back()->GetLevel(),
+ newAnswererTransceivers.back()->GetLevel());
}
-TEST_P(JsepSessionTest, RenegotiationBothRemoveTrackDifferentMsection)
+TEST_P(JsepSessionTest, RenegotiationBothStopTransceiverDifferentMsection)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
- if (types.front() == SdpMediaSection::kApplication) {
+
+ if (types.size() < 2) {
return;
}
- if (types.size() < 2 || types[0] != types[1]) {
- // For simplicity, just run in cases where we have two of the same type
+ if (types[0] == SdpMediaSection::kApplication ||
+ types[1] == SdpMediaSection::kApplication) {
return;
}
OfferAnswer();
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- RefPtr<JsepTrack> removedTrackAnswer = GetTrackAns(0, types.front());
- ASSERT_TRUE(removedTrackAnswer);
- ASSERT_EQ(NS_OK, mSessionAns->RemoveTrack(removedTrackAnswer->GetStreamId(),
- removedTrackAnswer->GetTrackId()));
-
- // Second instance of the same type
- RefPtr<JsepTrack> removedTrackOffer = GetTrackOff(1, types.front());
- ASSERT_TRUE(removedTrackOffer);
- ASSERT_EQ(NS_OK, mSessionOff->RemoveTrack(removedTrackOffer->GetStreamId(),
- removedTrackOffer->GetTrackId()));
+ mSessionOff->GetTransceivers()[0]->Stop();
+ mSessionOff->GetTransceivers()[1]->Stop();
OfferAnswer(CHECK_SUCCESS);
auto added = mSessionAns->GetRemoteTracksAdded();
auto removed = mSessionAns->GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
- ASSERT_EQ(1U, removed.size());
-
- ASSERT_EQ(removedTrackOffer->GetMediaType(), removed[0]->GetMediaType());
- ASSERT_EQ(removedTrackOffer->GetStreamId(), removed[0]->GetStreamId());
- ASSERT_EQ(removedTrackOffer->GetTrackId(), removed[0]->GetTrackId());
+ ASSERT_EQ(2U, removed.size());
added = mSessionOff->GetRemoteTracksAdded();
removed = mSessionOff->GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
- ASSERT_EQ(1U, removed.size());
-
- ASSERT_EQ(removedTrackAnswer->GetMediaType(), removed[0]->GetMediaType());
- ASSERT_EQ(removedTrackAnswer->GetStreamId(), removed[0]->GetStreamId());
- ASSERT_EQ(removedTrackAnswer->GetTrackId(), removed[0]->GetTrackId());
-
- // Second m-section should be recvonly
- auto offer = GetParsedLocalDescription(*mSessionOff);
- auto* msection = GetMsection(*offer, types.front(), 1);
- ASSERT_TRUE(msection);
- ASSERT_TRUE(msection->IsReceiving());
- ASSERT_FALSE(msection->IsSending());
-
- // First m-section should be recvonly
- auto answer = GetParsedLocalDescription(*mSessionAns);
- msection = GetMsection(*answer, types.front(), 0);
- ASSERT_TRUE(msection);
- ASSERT_TRUE(msection->IsReceiving());
- ASSERT_FALSE(msection->IsSending());
-
- auto newOffererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto newAnswererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
-
- // This should be the only difference.
- ASSERT_TRUE(offererPairs[0].mReceiving);
- ASSERT_FALSE(newOffererPairs[0].mReceiving);
-
- // Remove this difference, let loop below take care of the rest
- offererPairs[0].mReceiving = nullptr;
-
- // This should be the only difference.
- ASSERT_TRUE(offererPairs[1].mSending);
- ASSERT_FALSE(newOffererPairs[1].mSending);
-
- // Remove this difference, let loop below take care of the rest
- offererPairs[1].mSending = nullptr;
-
- for (size_t i = 0; i < newOffererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
- }
-
- ASSERT_EQ(answererPairs.size(), newAnswererPairs.size());
-
- // This should be the only difference.
- ASSERT_TRUE(answererPairs[0].mSending);
- ASSERT_FALSE(newAnswererPairs[0].mSending);
-
- // Remove this difference, let loop below take care of the rest
- answererPairs[0].mSending = nullptr;
-
- // This should be the only difference.
- ASSERT_TRUE(answererPairs[1].mReceiving);
- ASSERT_FALSE(newAnswererPairs[1].mReceiving);
-
- // Remove this difference, let loop below take care of the rest
- answererPairs[1].mReceiving = nullptr;
-
- for (size_t i = 0; i < newAnswererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
- }
+ ASSERT_EQ(2U, removed.size());
}
TEST_P(JsepSessionTest, RenegotiationOffererReplacesTrack)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
if (types.front() == SdpMediaSection::kApplication) {
return;
}
OfferAnswer();
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- RefPtr<JsepTrack> removedTrack = GetTrackOff(0, types.front());
- ASSERT_TRUE(removedTrack);
- ASSERT_EQ(NS_OK, mSessionOff->RemoveTrack(removedTrack->GetStreamId(),
- removedTrack->GetTrackId()));
- RefPtr<JsepTrack> addedTrack(
- new JsepTrack(types.front(), "newstream", "newtrack"));
- ASSERT_EQ(NS_OK, mSessionOff->AddTrack(addedTrack));
+ mSessionOff->GetTransceivers()[0]->mSending.UpdateTrack(
+ std::vector<std::string>(1, "newstream"), "newtrack");
OfferAnswer(CHECK_SUCCESS);
+ // Latest JSEP spec says the msid never changes, so the other side will not
+ // notice track replacement.
auto added = mSessionAns->GetRemoteTracksAdded();
auto removed = mSessionAns->GetRemoteTracksRemoved();
- ASSERT_EQ(1U, added.size());
- ASSERT_EQ(1U, removed.size());
-
- ASSERT_EQ(removedTrack->GetMediaType(), removed[0]->GetMediaType());
- ASSERT_EQ(removedTrack->GetStreamId(), removed[0]->GetStreamId());
- ASSERT_EQ(removedTrack->GetTrackId(), removed[0]->GetTrackId());
-
- ASSERT_EQ(addedTrack->GetMediaType(), added[0]->GetMediaType());
- ASSERT_EQ(addedTrack->GetStreamId(), added[0]->GetStreamId());
- ASSERT_EQ(addedTrack->GetTrackId(), added[0]->GetTrackId());
-
- added = mSessionOff->GetRemoteTracksAdded();
- removed = mSessionOff->GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(0U, removed.size());
-
- // First audio m-section should be sendrecv
- auto offer = GetParsedLocalDescription(*mSessionOff);
- auto* msection = GetMsection(*offer, types.front(), 0);
- ASSERT_TRUE(msection);
- ASSERT_TRUE(msection->IsReceiving());
- ASSERT_TRUE(msection->IsSending());
-
- // First audio m-section should be sendrecv
- auto answer = GetParsedLocalDescription(*mSessionAns);
- msection = GetMsection(*answer, types.front(), 0);
- ASSERT_TRUE(msection);
- ASSERT_TRUE(msection->IsReceiving());
- ASSERT_TRUE(msection->IsSending());
-
- auto newOffererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto newAnswererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
-
- ASSERT_NE(offererPairs[0].mSending->GetStreamId(),
- newOffererPairs[0].mSending->GetStreamId());
- ASSERT_NE(offererPairs[0].mSending->GetTrackId(),
- newOffererPairs[0].mSending->GetTrackId());
-
- // Skip first pair
- for (size_t i = 1; i < offererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
+}
+
+TEST_P(JsepSessionTest, RenegotiationAnswererReplacesTrack)
+{
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ if (types.front() == SdpMediaSection::kApplication) {
+ return;
}
- ASSERT_EQ(answererPairs.size(), newAnswererPairs.size());
-
- ASSERT_NE(answererPairs[0].mReceiving->GetStreamId(),
- newAnswererPairs[0].mReceiving->GetStreamId());
- ASSERT_NE(answererPairs[0].mReceiving->GetTrackId(),
- newAnswererPairs[0].mReceiving->GetTrackId());
-
- // Skip first pair
- for (size_t i = 1; i < newAnswererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
- }
+ OfferAnswer();
+
+ mSessionAns->GetTransceivers()[0]->mSending.UpdateTrack(
+ std::vector<std::string>(1, "newstream"), "newtrack");
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Latest JSEP spec says the msid never changes, so the other side will not
+ // notice track replacement.
+ auto added = mSessionOff->GetRemoteTracksAdded();
+ auto removed = mSessionOff->GetRemoteTracksRemoved();
+ ASSERT_EQ(0U, added.size());
+ ASSERT_EQ(0U, removed.size());
}
// Tests whether auto-assigned remote msids (ie; what happens when the other
// side doesn't use msid attributes) are stable across renegotiation.
TEST_P(JsepSessionTest, RenegotiationAutoAssignedMsidIsStable)
{
AddTracks(*mSessionOff);
std::string offer = CreateOffer();
@@ -2157,120 +2419,118 @@ TEST_P(JsepSessionTest, RenegotiationAut
AddTracks(*mSessionAns);
std::string answer = CreateAnswer();
SetLocalAnswer(answer);
DisableMsid(&answer);
SetRemoteAnswer(answer, CHECK_SUCCESS);
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
-
- // Make sure that DisableMsid actually worked, since it is kinda hacky
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
- ASSERT_EQ(offererPairs.size(), answererPairs.size());
- for (size_t i = 0; i < offererPairs.size(); ++i) {
- ASSERT_TRUE(offererPairs[i].mReceiving);
- ASSERT_TRUE(answererPairs[i].mSending);
+ std::vector<RefPtr<JsepTransceiver>> origOffererTransceivers
+ = DeepCopy(mSessionOff->GetTransceivers());
+ std::vector<RefPtr<JsepTransceiver>> origAnswererTransceivers
+ = DeepCopy(mSessionAns->GetTransceivers());
+
+ ASSERT_EQ(origOffererTransceivers.size(), origAnswererTransceivers.size());
+ for (size_t i = 0; i < origOffererTransceivers.size(); ++i) {
+ ASSERT_FALSE(IsNull(origOffererTransceivers[i]->mReceiving));
+ ASSERT_FALSE(IsNull(origAnswererTransceivers[i]->mSending));
// These should not match since we've monkeyed with the msid
- ASSERT_NE(offererPairs[i].mReceiving->GetStreamId(),
- answererPairs[i].mSending->GetStreamId());
- ASSERT_NE(offererPairs[i].mReceiving->GetTrackId(),
- answererPairs[i].mSending->GetTrackId());
+ ASSERT_NE(origOffererTransceivers[i]->mReceiving.GetStreamIds(),
+ origAnswererTransceivers[i]->mSending.GetStreamIds());
+ ASSERT_NE(origOffererTransceivers[i]->mReceiving.GetTrackId(),
+ origAnswererTransceivers[i]->mSending.GetTrackId());
}
offer = CreateOffer();
SetLocalOffer(offer);
SetRemoteOffer(offer);
- AddTracks(*mSessionAns);
answer = CreateAnswer();
SetLocalAnswer(answer);
DisableMsid(&answer);
SetRemoteAnswer(answer, CHECK_SUCCESS);
- auto newOffererPairs = mSessionOff->GetNegotiatedTrackPairs();
-
- ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
- for (size_t i = 0; i < offererPairs.size(); ++i) {
- ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
+ auto newOffererTransceivers = mSessionOff->GetTransceivers();
+
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+ for (size_t i = 0; i < origOffererTransceivers.size(); ++i) {
+ ASSERT_TRUE(Equals(*origOffererTransceivers[i],
+ *newOffererTransceivers[i]));
}
}
TEST_P(JsepSessionTest, RenegotiationOffererDisablesTelephoneEvent)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
OfferAnswer();
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
-
// check all the audio tracks to make sure they have 2 codecs (109 and 101),
// and dtmf is enabled on all audio tracks
- for (size_t i = 0; i < offererPairs.size(); ++i) {
- std::vector<JsepTrack*> tracks;
- tracks.push_back(offererPairs[i].mSending.get());
- tracks.push_back(offererPairs[i].mReceiving.get());
- for (JsepTrack *track : tracks) {
- if (track->GetMediaType() != SdpMediaSection::kAudio) {
- continue;
- }
- const JsepTrackNegotiatedDetails* details = track->GetNegotiatedDetails();
- ASSERT_EQ(1U, details->GetEncodingCount());
- const JsepTrackEncoding& encoding = details->GetEncoding(0);
- ASSERT_EQ(2U, encoding.GetCodecs().size());
- ASSERT_TRUE(encoding.HasFormat("109"));
- ASSERT_TRUE(encoding.HasFormat("101"));
- for (JsepCodecDescription* codec: encoding.GetCodecs()) {
- ASSERT_TRUE(codec);
- // we can cast here because we've already checked for audio track
- JsepAudioCodecDescription *audioCodec =
- static_cast<JsepAudioCodecDescription*>(codec);
- ASSERT_TRUE(audioCodec->mDtmfEnabled);
- }
+ std::vector<JsepTrack> tracks;
+ for (const auto& transceiver : mSessionOff->GetTransceivers()) {
+ tracks.push_back(transceiver->mSending);
+ tracks.push_back(transceiver->mReceiving);
+ }
+
+ for (const JsepTrack& track : tracks) {
+ if (track.GetMediaType() != SdpMediaSection::kAudio) {
+ continue;
+ }
+ const JsepTrackNegotiatedDetails* details = track.GetNegotiatedDetails();
+ ASSERT_EQ(1U, details->GetEncodingCount());
+ const JsepTrackEncoding& encoding = details->GetEncoding(0);
+ ASSERT_EQ(2U, encoding.GetCodecs().size());
+ ASSERT_TRUE(encoding.HasFormat("109"));
+ ASSERT_TRUE(encoding.HasFormat("101"));
+ for (JsepCodecDescription* codec: encoding.GetCodecs()) {
+ ASSERT_TRUE(codec);
+ // we can cast here because we've already checked for audio track
+ JsepAudioCodecDescription *audioCodec =
+ static_cast<JsepAudioCodecDescription*>(codec);
+ ASSERT_TRUE(audioCodec->mDtmfEnabled);
}
}
std::string offer = CreateOffer();
ReplaceInSdp(&offer, " 109 101 ", " 109 ");
ReplaceInSdp(&offer, "a=fmtp:101 0-15\r\n", "");
ReplaceInSdp(&offer, "a=rtpmap:101 telephone-event/8000/1\r\n", "");
std::cerr << "modified OFFER: " << offer << std::endl;
SetLocalOffer(offer);
SetRemoteOffer(offer);
- AddTracks(*mSessionAns);
std::string answer = CreateAnswer();
SetLocalAnswer(answer);
SetRemoteAnswer(answer);
- auto newOffererPairs = GetTrackPairsByLevel(*mSessionOff);
-
// check all the audio tracks to make sure they have 1 codec (109),
// and dtmf is disabled on all audio tracks
- for (size_t i = 0; i < newOffererPairs.size(); ++i) {
- std::vector<JsepTrack*> tracks;
- tracks.push_back(newOffererPairs[i].mSending.get());
- tracks.push_back(newOffererPairs[i].mReceiving.get());
- for (JsepTrack* track : tracks) {
- if (track->GetMediaType() != SdpMediaSection::kAudio) {
- continue;
- }
- const JsepTrackNegotiatedDetails* details = track->GetNegotiatedDetails();
- ASSERT_EQ(1U, details->GetEncodingCount());
- const JsepTrackEncoding& encoding = details->GetEncoding(0);
- ASSERT_EQ(1U, encoding.GetCodecs().size());
- ASSERT_TRUE(encoding.HasFormat("109"));
- // we can cast here because we've already checked for audio track
- JsepAudioCodecDescription *audioCodec =
- static_cast<JsepAudioCodecDescription*>(encoding.GetCodecs()[0]);
- ASSERT_TRUE(audioCodec);
- ASSERT_FALSE(audioCodec->mDtmfEnabled);
+ tracks.clear();
+ for (const auto& transceiver : mSessionOff->GetTransceivers()) {
+ tracks.push_back(transceiver->mSending);
+ tracks.push_back(transceiver->mReceiving);
+ }
+
+ for (const JsepTrack& track : tracks) {
+ if (track.GetMediaType() != SdpMediaSection::kAudio) {
+ continue;
}
+ const JsepTrackNegotiatedDetails* details = track.GetNegotiatedDetails();
+ ASSERT_EQ(1U, details->GetEncodingCount());
+ const JsepTrackEncoding& encoding = details->GetEncoding(0);
+ ASSERT_EQ(1U, encoding.GetCodecs().size());
+ ASSERT_TRUE(encoding.HasFormat("109"));
+ // we can cast here because we've already checked for audio track
+ JsepAudioCodecDescription *audioCodec =
+ static_cast<JsepAudioCodecDescription*>(encoding.GetCodecs()[0]);
+ ASSERT_TRUE(audioCodec);
+ ASSERT_FALSE(audioCodec->mDtmfEnabled);
}
}
// Tests behavior when the answerer does not use msid in the initial exchange,
// but does on renegotiation.
TEST_P(JsepSessionTest, RenegotiationAnswererEnablesMsid)
{
AddTracks(*mSessionOff);
@@ -2280,93 +2540,93 @@ TEST_P(JsepSessionTest, RenegotiationAns
AddTracks(*mSessionAns);
std::string answer = CreateAnswer();
SetLocalAnswer(answer);
DisableMsid(&answer);
SetRemoteAnswer(answer, CHECK_SUCCESS);
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
+ std::vector<RefPtr<JsepTransceiver>> origOffererTransceivers
+ = DeepCopy(mSessionOff->GetTransceivers());
+ std::vector<RefPtr<JsepTransceiver>> origAnswererTransceivers
+ = DeepCopy(mSessionAns->GetTransceivers());
offer = CreateOffer();
SetLocalOffer(offer);
SetRemoteOffer(offer);
- AddTracks(*mSessionAns);
answer = CreateAnswer();
SetLocalAnswer(answer);
SetRemoteAnswer(answer, CHECK_SUCCESS);
- auto newOffererPairs = mSessionOff->GetNegotiatedTrackPairs();
-
- ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
- for (size_t i = 0; i < offererPairs.size(); ++i) {
- ASSERT_EQ(offererPairs[i].mReceiving->GetMediaType(),
- newOffererPairs[i].mReceiving->GetMediaType());
-
- ASSERT_EQ(offererPairs[i].mSending, newOffererPairs[i].mSending);
- ASSERT_TRUE(Equals(offererPairs[i].mRtpTransport,
- newOffererPairs[i].mRtpTransport));
- ASSERT_TRUE(Equals(offererPairs[i].mRtcpTransport,
- newOffererPairs[i].mRtcpTransport));
-
- if (offererPairs[i].mReceiving->GetMediaType() ==
+ auto newOffererTransceivers = mSessionOff->GetTransceivers();
+
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+ for (size_t i = 0; i < origOffererTransceivers.size(); ++i) {
+ ASSERT_EQ(origOffererTransceivers[i]->mReceiving.GetMediaType(),
+ newOffererTransceivers[i]->mReceiving.GetMediaType());
+
+ ASSERT_TRUE(Equals(origOffererTransceivers[i]->mSending,
+ newOffererTransceivers[i]->mSending));
+ ASSERT_TRUE(Equals(origOffererTransceivers[i]->mTransport,
+ newOffererTransceivers[i]->mTransport));
+
+ if (origOffererTransceivers[i]->mReceiving.GetMediaType() ==
SdpMediaSection::kApplication) {
- ASSERT_EQ(offererPairs[i].mReceiving, newOffererPairs[i].mReceiving);
+ ASSERT_TRUE(Equals(origOffererTransceivers[i]->mReceiving,
+ newOffererTransceivers[i]->mReceiving));
} else {
// This should be the only difference
- ASSERT_NE(offererPairs[i].mReceiving, newOffererPairs[i].mReceiving);
+ ASSERT_FALSE(Equals(origOffererTransceivers[i]->mReceiving,
+ newOffererTransceivers[i]->mReceiving));
}
}
}
TEST_P(JsepSessionTest, RenegotiationAnswererDisablesMsid)
{
AddTracks(*mSessionOff);
std::string offer = CreateOffer();
SetLocalOffer(offer);
SetRemoteOffer(offer);
AddTracks(*mSessionAns);
std::string answer = CreateAnswer();
SetLocalAnswer(answer);
SetRemoteAnswer(answer, CHECK_SUCCESS);
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
+ std::vector<RefPtr<JsepTransceiver>> origOffererTransceivers
+ = DeepCopy(mSessionOff->GetTransceivers());
+ std::vector<RefPtr<JsepTransceiver>> origAnswererTransceivers
+ = DeepCopy(mSessionAns->GetTransceivers());
offer = CreateOffer();
SetLocalOffer(offer);
SetRemoteOffer(offer);
- AddTracks(*mSessionAns);
answer = CreateAnswer();
SetLocalAnswer(answer);
DisableMsid(&answer);
SetRemoteAnswer(answer, CHECK_SUCCESS);
- auto newOffererPairs = mSessionOff->GetNegotiatedTrackPairs();
-
- ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
- for (size_t i = 0; i < offererPairs.size(); ++i) {
- ASSERT_EQ(offererPairs[i].mReceiving->GetMediaType(),
- newOffererPairs[i].mReceiving->GetMediaType());
-
- ASSERT_EQ(offererPairs[i].mSending, newOffererPairs[i].mSending);
- ASSERT_TRUE(Equals(offererPairs[i].mRtpTransport,
- newOffererPairs[i].mRtpTransport));
- ASSERT_TRUE(Equals(offererPairs[i].mRtcpTransport,
- newOffererPairs[i].mRtcpTransport));
-
- if (offererPairs[i].mReceiving->GetMediaType() ==
- SdpMediaSection::kApplication) {
- ASSERT_EQ(offererPairs[i].mReceiving, newOffererPairs[i].mReceiving);
- } else {
- // This should be the only difference
- ASSERT_NE(offererPairs[i].mReceiving, newOffererPairs[i].mReceiving);
- }
+ auto newOffererTransceivers = mSessionOff->GetTransceivers();
+
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+ for (size_t i = 0; i < origOffererTransceivers.size(); ++i) {
+ ASSERT_EQ(origOffererTransceivers[i]->mReceiving.GetMediaType(),
+ newOffererTransceivers[i]->mReceiving.GetMediaType());
+
+ ASSERT_TRUE(Equals(origOffererTransceivers[i]->mSending,
+ newOffererTransceivers[i]->mSending));
+ ASSERT_TRUE(Equals(origOffererTransceivers[i]->mTransport,
+ newOffererTransceivers[i]->mTransport));
+
+ // If the msid is missing, we just assume it is the same
+ ASSERT_TRUE(Equals(origOffererTransceivers[i]->mReceiving,
+ newOffererTransceivers[i]->mReceiving));
}
}
// Tests behavior when offerer does not use bundle on the initial offer/answer,
// but does on renegotiation.
TEST_P(JsepSessionTest, RenegotiationOffererEnablesBundle)
{
AddTracks(*mSessionOff);
@@ -2382,178 +2642,163 @@ TEST_P(JsepSessionTest, RenegotiationOff
DisableBundle(&offer);
SetLocalOffer(offer);
SetRemoteOffer(offer);
std::string answer = CreateAnswer();
SetLocalAnswer(answer);
SetRemoteAnswer(answer);
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
+ std::vector<RefPtr<JsepTransceiver>> origOffererTransceivers
+ = DeepCopy(mSessionOff->GetTransceivers());
+ std::vector<RefPtr<JsepTransceiver>> origAnswererTransceivers
+ = DeepCopy(mSessionAns->GetTransceivers());
OfferAnswer();
- auto newOffererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto newAnswererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- ASSERT_EQ(newOffererPairs.size(), newAnswererPairs.size());
- ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
- ASSERT_EQ(answererPairs.size(), newAnswererPairs.size());
-
- for (size_t i = 0; i < newOffererPairs.size(); ++i) {
+ auto newOffererTransceivers = mSessionOff->GetTransceivers();
+ auto newAnswererTransceivers = mSessionAns->GetTransceivers();
+
+ ASSERT_EQ(newOffererTransceivers.size(), newAnswererTransceivers.size());
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+ ASSERT_EQ(origAnswererTransceivers.size(), newAnswererTransceivers.size());
+
+ for (size_t i = 0; i < newOffererTransceivers.size(); ++i) {
// No bundle initially
- ASSERT_FALSE(offererPairs[i].HasBundleLevel());
- ASSERT_FALSE(answererPairs[i].HasBundleLevel());
+ ASSERT_FALSE(origOffererTransceivers[i]->HasBundleLevel());
+ ASSERT_FALSE(origAnswererTransceivers[i]->HasBundleLevel());
if (i != 0) {
- ASSERT_NE(offererPairs[0].mRtpTransport.get(),
- offererPairs[i].mRtpTransport.get());
- if (offererPairs[0].mRtcpTransport) {
- ASSERT_NE(offererPairs[0].mRtcpTransport.get(),
- offererPairs[i].mRtcpTransport.get());
- }
- ASSERT_NE(answererPairs[0].mRtpTransport.get(),
- answererPairs[i].mRtpTransport.get());
- if (answererPairs[0].mRtcpTransport) {
- ASSERT_NE(answererPairs[0].mRtcpTransport.get(),
- answererPairs[i].mRtcpTransport.get());
- }
+ ASSERT_NE(origOffererTransceivers[0]->mTransport.get(),
+ origOffererTransceivers[i]->mTransport.get());
+ ASSERT_NE(origAnswererTransceivers[0]->mTransport.get(),
+ origAnswererTransceivers[i]->mTransport.get());
}
// Verify that bundle worked after renegotiation
- ASSERT_TRUE(newOffererPairs[i].HasBundleLevel());
- ASSERT_TRUE(newAnswererPairs[i].HasBundleLevel());
- ASSERT_EQ(newOffererPairs[0].mRtpTransport.get(),
- newOffererPairs[i].mRtpTransport.get());
- ASSERT_EQ(newOffererPairs[0].mRtcpTransport.get(),
- newOffererPairs[i].mRtcpTransport.get());
- ASSERT_EQ(newAnswererPairs[0].mRtpTransport.get(),
- newAnswererPairs[i].mRtpTransport.get());
- ASSERT_EQ(newAnswererPairs[0].mRtcpTransport.get(),
- newAnswererPairs[i].mRtcpTransport.get());
+ ASSERT_TRUE(newOffererTransceivers[i]->HasBundleLevel());
+ ASSERT_TRUE(newAnswererTransceivers[i]->HasBundleLevel());
+ ASSERT_EQ(newOffererTransceivers[0]->mTransport.get(),
+ newOffererTransceivers[i]->mTransport.get());
+ ASSERT_EQ(newAnswererTransceivers[0]->mTransport.get(),
+ newAnswererTransceivers[i]->mTransport.get());
}
}
TEST_P(JsepSessionTest, RenegotiationOffererDisablesBundleTransport)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
if (types.size() < 2) {
return;
}
OfferAnswer();
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- std::string reoffer = CreateOffer();
-
- DisableMsection(&reoffer, 0);
-
- SetLocalOffer(reoffer, CHECK_SUCCESS);
- SetRemoteOffer(reoffer, CHECK_SUCCESS);
- std::string reanswer = CreateAnswer();
- SetLocalAnswer(reanswer, CHECK_SUCCESS);
- SetRemoteAnswer(reanswer, CHECK_SUCCESS);
-
- auto newOffererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto newAnswererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- ASSERT_EQ(newOffererPairs.size(), newAnswererPairs.size());
- ASSERT_EQ(offererPairs.size(), newOffererPairs.size() + 1);
- ASSERT_EQ(answererPairs.size(), newAnswererPairs.size() + 1);
-
- for (size_t i = 0; i < newOffererPairs.size(); ++i) {
- ASSERT_TRUE(newOffererPairs[i].HasBundleLevel());
- ASSERT_TRUE(newAnswererPairs[i].HasBundleLevel());
- ASSERT_EQ(1U, newOffererPairs[i].BundleLevel());
- ASSERT_EQ(1U, newAnswererPairs[i].BundleLevel());
- ASSERT_EQ(newOffererPairs[0].mRtpTransport.get(),
- newOffererPairs[i].mRtpTransport.get());
- ASSERT_EQ(newOffererPairs[0].mRtcpTransport.get(),
- newOffererPairs[i].mRtcpTransport.get());
- ASSERT_EQ(newAnswererPairs[0].mRtpTransport.get(),
- newAnswererPairs[i].mRtpTransport.get());
- ASSERT_EQ(newAnswererPairs[0].mRtcpTransport.get(),
- newAnswererPairs[i].mRtcpTransport.get());
+ mSessionOff->GetTransceivers()[0]->Stop();
+
+ std::vector<RefPtr<JsepTransceiver>> origOffererTransceivers
+ = DeepCopy(mSessionOff->GetTransceivers());
+ std::vector<RefPtr<JsepTransceiver>> origAnswererTransceivers
+ = DeepCopy(mSessionAns->GetTransceivers());
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ auto newOffererTransceivers = mSessionOff->GetTransceivers();
+ auto newAnswererTransceivers = mSessionAns->GetTransceivers();
+
+ ASSERT_EQ(newOffererTransceivers.size(), newAnswererTransceivers.size());
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+ ASSERT_EQ(origAnswererTransceivers.size(), newAnswererTransceivers.size());
+
+ ASSERT_FALSE(newOffererTransceivers[0]->HasBundleLevel());
+ ASSERT_FALSE(newAnswererTransceivers[0]->HasBundleLevel());
+
+ ASSERT_NE(newOffererTransceivers[0]->mTransport.get(),
+ origOffererTransceivers[0]->mTransport.get());
+ ASSERT_NE(newAnswererTransceivers[0]->mTransport.get(),
+ origAnswererTransceivers[0]->mTransport.get());
+
+ ASSERT_EQ(0U, newOffererTransceivers[0]->mTransport->mComponents);
+ ASSERT_EQ(0U, newAnswererTransceivers[0]->mTransport->mComponents);
+
+ for (size_t i = 1; i < newOffererTransceivers.size(); ++i) {
+ ASSERT_TRUE(newOffererTransceivers[i]->HasBundleLevel());
+ ASSERT_TRUE(newAnswererTransceivers[i]->HasBundleLevel());
+ ASSERT_EQ(1U, newOffererTransceivers[i]->BundleLevel());
+ ASSERT_EQ(1U, newAnswererTransceivers[i]->BundleLevel());
+ ASSERT_NE(newOffererTransceivers[0]->mTransport.get(),
+ newOffererTransceivers[i]->mTransport.get());
+ ASSERT_NE(newAnswererTransceivers[0]->mTransport.get(),
+ newAnswererTransceivers[i]->mTransport.get());
}
-
- ASSERT_NE(newOffererPairs[0].mRtpTransport.get(),
- offererPairs[0].mRtpTransport.get());
- ASSERT_NE(newAnswererPairs[0].mRtpTransport.get(),
- answererPairs[0].mRtpTransport.get());
-
- ASSERT_LE(1U, mSessionOff->GetTransports().size());
- ASSERT_LE(1U, mSessionAns->GetTransports().size());
-
- ASSERT_EQ(0U, mSessionOff->GetTransports()[0]->mComponents);
- ASSERT_EQ(0U, mSessionAns->GetTransports()[0]->mComponents);
}
TEST_P(JsepSessionTest, RenegotiationAnswererDisablesBundleTransport)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
if (types.size() < 2) {
return;
}
OfferAnswer();
- auto offererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto answererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- std::string reoffer = CreateOffer();
- SetLocalOffer(reoffer, CHECK_SUCCESS);
- SetRemoteOffer(reoffer, CHECK_SUCCESS);
- std::string reanswer = CreateAnswer();
-
- CopyTransportAttributes(&reanswer, 0, 1);
- DisableMsection(&reanswer, 0);
-
- SetLocalAnswer(reanswer, CHECK_SUCCESS);
- SetRemoteAnswer(reanswer, CHECK_SUCCESS);
-
- auto newOffererPairs = GetTrackPairsByLevel(*mSessionOff);
- auto newAnswererPairs = GetTrackPairsByLevel(*mSessionAns);
-
- ASSERT_EQ(newOffererPairs.size(), newAnswererPairs.size());
- ASSERT_EQ(offererPairs.size(), newOffererPairs.size() + 1);
- ASSERT_EQ(answererPairs.size(), newAnswererPairs.size() + 1);
-
- for (size_t i = 0; i < newOffererPairs.size(); ++i) {
- ASSERT_TRUE(newOffererPairs[i].HasBundleLevel());
- ASSERT_TRUE(newAnswererPairs[i].HasBundleLevel());
- ASSERT_EQ(1U, newOffererPairs[i].BundleLevel());
- ASSERT_EQ(1U, newAnswererPairs[i].BundleLevel());
- ASSERT_EQ(newOffererPairs[0].mRtpTransport.get(),
- newOffererPairs[i].mRtpTransport.get());
- ASSERT_EQ(newOffererPairs[0].mRtcpTransport.get(),
- newOffererPairs[i].mRtcpTransport.get());
- ASSERT_EQ(newAnswererPairs[0].mRtpTransport.get(),
- newAnswererPairs[i].mRtpTransport.get());
- ASSERT_EQ(newAnswererPairs[0].mRtcpTransport.get(),
- newAnswererPairs[i].mRtcpTransport.get());
+ std::vector<RefPtr<JsepTransceiver>> origOffererTransceivers
+ = DeepCopy(mSessionOff->GetTransceivers());
+ std::vector<RefPtr<JsepTransceiver>> origAnswererTransceivers
+ = DeepCopy(mSessionAns->GetTransceivers());
+
+ mSessionAns->GetTransceivers()[0]->Stop();
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ auto newOffererTransceivers = mSessionOff->GetTransceivers();
+ auto newAnswererTransceivers = mSessionAns->GetTransceivers();
+
+ ASSERT_EQ(newOffererTransceivers.size(), newAnswererTransceivers.size());
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+ ASSERT_EQ(origAnswererTransceivers.size(), newAnswererTransceivers.size());
+
+ ASSERT_FALSE(newOffererTransceivers[0]->HasBundleLevel());
+ ASSERT_FALSE(newAnswererTransceivers[0]->HasBundleLevel());
+
+ ASSERT_NE(newOffererTransceivers[0]->mTransport.get(),
+ origOffererTransceivers[0]->mTransport.get());
+ ASSERT_NE(newAnswererTransceivers[0]->mTransport.get(),
+ origAnswererTransceivers[0]->mTransport.get());
+
+ ASSERT_EQ(0U, newOffererTransceivers[0]->mTransport->mComponents);
+ ASSERT_EQ(0U, newAnswererTransceivers[0]->mTransport->mComponents);
+
+ for (size_t i = 1; i < newOffererTransceivers.size(); ++i) {
+ if (newOffererTransceivers.size() > 2) {
+ ASSERT_TRUE(newOffererTransceivers[i]->HasBundleLevel());
+ ASSERT_TRUE(newAnswererTransceivers[i]->HasBundleLevel());
+ ASSERT_EQ(1U, newOffererTransceivers[i]->BundleLevel());
+ ASSERT_EQ(1U, newAnswererTransceivers[i]->BundleLevel());
+ } else {
+ // Only one remaining m-section, no bundle will happen here.
+ ASSERT_FALSE(newOffererTransceivers[i]->HasBundleLevel());
+ ASSERT_FALSE(newAnswererTransceivers[i]->HasBundleLevel());
+ }
+ ASSERT_NE(newOffererTransceivers[0]->mTransport.get(),
+ newOffererTransceivers[i]->mTransport.get());
+ ASSERT_NE(newAnswererTransceivers[0]->mTransport.get(),
+ newAnswererTransceivers[i]->mTransport.get());
}
-
- ASSERT_NE(newOffererPairs[0].mRtpTransport.get(),
- offererPairs[0].mRtpTransport.get());
- ASSERT_NE(newAnswererPairs[0].mRtpTransport.get(),
- answererPairs[0].mRtpTransport.get());
}
TEST_P(JsepSessionTest, ParseRejectsBadMediaFormat)
{
- if (GetParam() == "datachannel") {
+ AddTracks(*mSessionOff);
+ if (types.front() == SdpMediaSection::MediaType::kApplication) {
return;
}
- AddTracks(*mSessionOff);
std::string offer = CreateOffer();
UniquePtr<Sdp> munge(Parse(offer));
SdpMediaSection& mediaSection = munge->GetMediaSection(0);
mediaSection.AddCodec("75", "DummyFormatVal", 8000, 1);
std::string sdpString = munge->ToString();
nsresult rv = mSessionOff->SetLocalDescription(kJsepSdpOffer, sdpString);
ASSERT_EQ(NS_ERROR_INVALID_ARG, rv);
}
@@ -2872,23 +3117,23 @@ TEST_P(JsepSessionTest, RenegotiationAns
msection.SetReceiving(false);
}
}
answer = parsedAnswer->ToString();
SetRemoteAnswer(answer);
- for (const RefPtr<JsepTrack>& track : mSessionOff->GetLocalTracks()) {
- if (track->GetMediaType() != SdpMediaSection::kApplication) {
- ASSERT_FALSE(track->GetActive());
+ for (const JsepTrack& track : GetLocalTracks(*mSessionOff)) {
+ if (track.GetMediaType() != SdpMediaSection::kApplication) {
+ ASSERT_FALSE(track.GetActive());
}
}
- ASSERT_EQ(types.size(), mSessionOff->GetNegotiatedTrackPairs().size());
+ ASSERT_EQ(types.size(), mSessionOff->GetTransceivers().size());
}
TEST_P(JsepSessionTest, RenegotiationAnswererInactive)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
OfferAnswer();
@@ -2906,23 +3151,23 @@ TEST_P(JsepSessionTest, RenegotiationAns
msection.SetSending(false);
}
}
answer = parsedAnswer->ToString();
SetRemoteAnswer(answer, CHECK_SUCCESS); // Won't have answerer tracks
- for (const RefPtr<JsepTrack>& track : mSessionOff->GetLocalTracks()) {
- if (track->GetMediaType() != SdpMediaSection::kApplication) {
- ASSERT_FALSE(track->GetActive());
+ for (const JsepTrack& track : GetLocalTracks(*mSessionOff)) {
+ if (track.GetMediaType() != SdpMediaSection::kApplication) {
+ ASSERT_FALSE(track.GetActive());
}
}
- ASSERT_EQ(types.size(), mSessionOff->GetNegotiatedTrackPairs().size());
+ ASSERT_EQ(types.size(), mSessionOff->GetTransceivers().size());
}
INSTANTIATE_TEST_CASE_P(
Variants,
JsepSessionTest,
::testing::Values("audio",
"video",
@@ -2945,20 +3190,23 @@ INSTANTIATE_TEST_CASE_P(
"audio,video,video",
"audio,audio,video,video",
"audio,audio,video,video,datachannel"));
// offerToReceiveXxx variants
TEST_F(JsepSessionTest, OfferAnswerRecvOnlyLines)
{
- JsepOfferOptions options;
- options.mOfferToReceiveAudio = Some(static_cast<size_t>(1U));
- options.mOfferToReceiveVideo = Some(static_cast<size_t>(2U));
- std::string offer = CreateOffer(Some(options));
+ mSessionOff->AddTransceiver(new JsepTransceiver(
+ SdpMediaSection::kAudio, SdpDirectionAttribute::kRecvonly));
+ mSessionOff->AddTransceiver(new JsepTransceiver(
+ SdpMediaSection::kVideo, SdpDirectionAttribute::kRecvonly));
+ mSessionOff->AddTransceiver(new JsepTransceiver(
+ SdpMediaSection::kVideo, SdpDirectionAttribute::kRecvonly));
+ std::string offer = CreateOffer();
UniquePtr<Sdp> parsedOffer(Parse(offer));
ASSERT_TRUE(!!parsedOffer);
ASSERT_EQ(3U, parsedOffer->GetMediaSectionCount());
ASSERT_EQ(SdpMediaSection::kAudio,
parsedOffer->GetMediaSection(0).GetMediaType());
ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
@@ -3007,34 +3255,32 @@ TEST_F(JsepSessionTest, OfferAnswerRecvO
ASSERT_EQ(SdpMediaSection::kVideo,
parsedAnswer->GetMediaSection(2).GetMediaType());
ASSERT_EQ(SdpDirectionAttribute::kInactive,
parsedAnswer->GetMediaSection(2).GetAttributeList().GetDirection());
SetLocalAnswer(answer, CHECK_SUCCESS);
SetRemoteAnswer(answer, CHECK_SUCCESS);
- std::vector<JsepTrackPair> trackPairs(mSessionOff->GetNegotiatedTrackPairs());
- ASSERT_EQ(2U, trackPairs.size());
- for (auto pair : trackPairs) {
- auto ssrcs = parsedOffer->GetMediaSection(pair.mLevel).GetAttributeList()
- .GetSsrc().mSsrcs;
+ std::vector<RefPtr<JsepTransceiver>> transceivers(mSessionOff->GetTransceivers());
+ ASSERT_EQ(3U, transceivers.size());
+ for (auto transceiver : transceivers) {
+ auto ssrcs = parsedOffer->GetMediaSection(transceiver->GetLevel())
+ .GetAttributeList().GetSsrc().mSsrcs;
ASSERT_EQ(1U, ssrcs.size());
- ASSERT_EQ(pair.mRecvonlySsrc, ssrcs.front().ssrc);
}
}
TEST_F(JsepSessionTest, OfferAnswerSendOnlyLines)
{
AddTracks(*mSessionOff, "audio,video,video");
- JsepOfferOptions options;
- options.mOfferToReceiveAudio = Some(static_cast<size_t>(0U));
- options.mOfferToReceiveVideo = Some(static_cast<size_t>(1U));
- std::string offer = CreateOffer(Some(options));
+ SetDirection(*mSessionOff, 0, SdpDirectionAttribute::kSendonly);
+ SetDirection(*mSessionOff, 2, SdpDirectionAttribute::kSendonly);
+ std::string offer = CreateOffer();
UniquePtr<Sdp> outputSdp(Parse(offer));
ASSERT_TRUE(!!outputSdp);
ASSERT_EQ(3U, outputSdp->GetMediaSectionCount());
ASSERT_EQ(SdpMediaSection::kAudio,
outputSdp->GetMediaSection(0).GetMediaType());
ASSERT_EQ(SdpDirectionAttribute::kSendonly,
@@ -3075,20 +3321,20 @@ TEST_F(JsepSessionTest, OfferAnswerSendO
ASSERT_EQ(SdpMediaSection::kVideo,
outputSdp->GetMediaSection(2).GetMediaType());
ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
outputSdp->GetMediaSection(2).GetAttributeList().GetDirection());
}
TEST_F(JsepSessionTest, OfferToReceiveAudioNotUsed)
{
- JsepOfferOptions options;
- options.mOfferToReceiveAudio = Some<size_t>(1);
-
- OfferAnswer(CHECK_SUCCESS, Some(options));
+ mSessionOff->AddTransceiver(new JsepTransceiver(
+ SdpMediaSection::kAudio, SdpDirectionAttribute::kRecvonly));
+
+ OfferAnswer(CHECK_SUCCESS);
UniquePtr<Sdp> offer(Parse(
mSessionOff->GetLocalDescription(kJsepDescriptionCurrent)));
ASSERT_TRUE(offer.get());
ASSERT_EQ(1U, offer->GetMediaSectionCount());
ASSERT_EQ(SdpMediaSection::kAudio,
offer->GetMediaSection(0).GetMediaType());
ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
@@ -3101,20 +3347,20 @@ TEST_F(JsepSessionTest, OfferToReceiveAu
ASSERT_EQ(SdpMediaSection::kAudio,
answer->GetMediaSection(0).GetMediaType());
ASSERT_EQ(SdpDirectionAttribute::kInactive,
answer->GetMediaSection(0).GetAttributeList().GetDirection());
}
TEST_F(JsepSessionTest, OfferToReceiveVideoNotUsed)
{
- JsepOfferOptions options;
- options.mOfferToReceiveVideo = Some<size_t>(1);
-
- OfferAnswer(CHECK_SUCCESS, Some(options));
+ mSessionOff->AddTransceiver(new JsepTransceiver(
+ SdpMediaSection::kVideo, SdpDirectionAttribute::kRecvonly));
+
+ OfferAnswer(CHECK_SUCCESS);
UniquePtr<Sdp> offer(Parse(
mSessionOff->GetLocalDescription(kJsepDescriptionCurrent)));
ASSERT_TRUE(offer.get());
ASSERT_EQ(1U, offer->GetMediaSectionCount());
ASSERT_EQ(SdpMediaSection::kVideo,
offer->GetMediaSection(0).GetMediaType());
ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
@@ -3127,23 +3373,25 @@ TEST_F(JsepSessionTest, OfferToReceiveVi
ASSERT_EQ(SdpMediaSection::kVideo,
answer->GetMediaSection(0).GetMediaType());
ASSERT_EQ(SdpDirectionAttribute::kInactive,
answer->GetMediaSection(0).GetAttributeList().GetDirection());
}
TEST_F(JsepSessionTest, CreateOfferNoDatachannelDefault)
{
- RefPtr<JsepTrack> msta(
- new JsepTrack(SdpMediaSection::kAudio, "offerer_stream", "a1"));
- mSessionOff->AddTrack(msta);
-
- RefPtr<JsepTrack> mstv1(
- new JsepTrack(SdpMediaSection::kVideo, "offerer_stream", "v1"));
- mSessionOff->AddTrack(mstv1);
+ RefPtr<JsepTransceiver> audio(new JsepTransceiver(SdpMediaSection::kAudio));
+ audio->mSending.UpdateTrack(
+ std::vector<std::string>(1, "offerer_stream"), "a1");
+ mSessionOff->AddTransceiver(audio);
+
+ RefPtr<JsepTransceiver> video(new JsepTransceiver(SdpMediaSection::kVideo));
+ video->mSending.UpdateTrack(
+ std::vector<std::string>(1, "offerer_stream"), "v1");
+ mSessionOff->AddTransceiver(video);
std::string offer = CreateOffer();
UniquePtr<Sdp> outputSdp(Parse(offer));
ASSERT_TRUE(!!outputSdp);
ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
ASSERT_EQ(SdpMediaSection::kAudio,
@@ -3152,22 +3400,23 @@ TEST_F(JsepSessionTest, CreateOfferNoDat
outputSdp->GetMediaSection(1).GetMediaType());
}
TEST_F(JsepSessionTest, ValidateOfferedVideoCodecParams)
{
types.push_back(SdpMediaSection::kAudio);
types.push_back(SdpMediaSection::kVideo);
- RefPtr<JsepTrack> msta(
- new JsepTrack(SdpMediaSection::kAudio, "offerer_stream", "a1"));
- mSessionOff->AddTrack(msta);
- RefPtr<JsepTrack> mstv1(
- new JsepTrack(SdpMediaSection::kVideo, "offerer_stream", "v2"));
- mSessionOff->AddTrack(mstv1);
+ RefPtr<JsepTransceiver> audio(new JsepTransceiver(SdpMediaSection::kAudio));
+ audio->mSending.UpdateTrack(std::vector<std::string>(1, "offerer_stream"), "a1");
+ mSessionOff->AddTransceiver(audio);
+
+ RefPtr<JsepTransceiver> video(new JsepTransceiver(SdpMediaSection::kVideo));
+ video->mSending.UpdateTrack(std::vector<std::string>(1, "offerer_stream"), "v1");
+ mSessionOff->AddTransceiver(video);
std::string offer = CreateOffer();
UniquePtr<Sdp> outputSdp(Parse(offer));
ASSERT_TRUE(!!outputSdp);
ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
auto& video_section = outputSdp->GetMediaSection(1);
@@ -3279,22 +3528,23 @@ TEST_F(JsepSessionTest, ValidateOfferedV
ASSERT_EQ(123, parsed_red_params.encodings[4]);
}
TEST_F(JsepSessionTest, ValidateOfferedAudioCodecParams)
{
types.push_back(SdpMediaSection::kAudio);
types.push_back(SdpMediaSection::kVideo);
- RefPtr<JsepTrack> msta(
- new JsepTrack(SdpMediaSection::kAudio, "offerer_stream", "a1"));
- mSessionOff->AddTrack(msta);
- RefPtr<JsepTrack> mstv1(
- new JsepTrack(SdpMediaSection::kVideo, "offerer_stream", "v2"));
- mSessionOff->AddTrack(mstv1);
+ RefPtr<JsepTransceiver> audio(new JsepTransceiver(SdpMediaSection::kAudio));
+ audio->mSending.UpdateTrack(std::vector<std::string>(1, "offerer_stream"), "a1");
+ mSessionOff->AddTransceiver(audio);
+
+ RefPtr<JsepTransceiver> video(new JsepTransceiver(SdpMediaSection::kVideo));
+ video->mSending.UpdateTrack(std::vector<std::string>(1, "offerer_stream"), "v1");
+ mSessionOff->AddTransceiver(video);
std::string offer = CreateOffer();
UniquePtr<Sdp> outputSdp(Parse(offer));
ASSERT_TRUE(!!outputSdp);
ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
auto& audio_section = outputSdp->GetMediaSection(0);
@@ -3361,39 +3611,29 @@ TEST_F(JsepSessionTest, ValidateOfferedA
ASSERT_EQ("0-15", parsed_dtmf_params.dtmfTones);
}
TEST_F(JsepSessionTest, ValidateNoFmtpLineForRedInOfferAndAnswer)
{
types.push_back(SdpMediaSection::kAudio);
types.push_back(SdpMediaSection::kVideo);
- RefPtr<JsepTrack> msta(
- new JsepTrack(SdpMediaSection::kAudio, "offerer_stream", "a1"));
- mSessionOff->AddTrack(msta);
- RefPtr<JsepTrack> mstv1(
- new JsepTrack(SdpMediaSection::kVideo, "offerer_stream", "v1"));
- mSessionOff->AddTrack(mstv1);
+ AddTracksToStream(*mSessionOff, "offerer_stream", "audio,video");
std::string offer = CreateOffer();
// look for line with fmtp:122 and remove it
size_t start = offer.find("a=fmtp:122");
size_t end = offer.find("\r\n", start);
offer.replace(start, end+2-start, "");
SetLocalOffer(offer);
SetRemoteOffer(offer);
- RefPtr<JsepTrack> msta_ans(
- new JsepTrack(SdpMediaSection::kAudio, "answerer_stream", "a1"));
- mSessionAns->AddTrack(msta);
- RefPtr<JsepTrack> mstv1_ans(
- new JsepTrack(SdpMediaSection::kVideo, "answerer_stream", "v1"));
- mSessionAns->AddTrack(mstv1);
+ AddTracksToStream(*mSessionAns, "answerer_stream", "audio,video");
std::string answer = CreateAnswer();
// because parsing will throw out the malformed fmtp, make sure it is not
// in the answer sdp string
ASSERT_EQ(std::string::npos, answer.find("a=fmtp:122"));
UniquePtr<Sdp> outputSdp(Parse(answer));
ASSERT_TRUE(!!outputSdp);
@@ -3430,40 +3670,40 @@ TEST_F(JsepSessionTest, ValidateNoFmtpLi
ASSERT_EQ("126", fmtps[0].format);
ASSERT_EQ("97", fmtps[1].format);
ASSERT_EQ("120", fmtps[2].format);
ASSERT_EQ("121", fmtps[3].format);
SetLocalAnswer(answer);
SetRemoteAnswer(answer);
- auto offerPairs = mSessionOff->GetNegotiatedTrackPairs();
- ASSERT_EQ(2U, offerPairs.size());
- ASSERT_TRUE(offerPairs[1].mSending);
- ASSERT_TRUE(offerPairs[1].mReceiving);
- ASSERT_TRUE(offerPairs[1].mSending->GetNegotiatedDetails());
- ASSERT_TRUE(offerPairs[1].mReceiving->GetNegotiatedDetails());
+ auto offerTransceivers = mSessionOff->GetTransceivers();
+ ASSERT_EQ(2U, offerTransceivers.size());
+ ASSERT_FALSE(IsNull(offerTransceivers[1]->mSending));
+ ASSERT_FALSE(IsNull(offerTransceivers[1]->mReceiving));
+ ASSERT_TRUE(offerTransceivers[1]->mSending.GetNegotiatedDetails());
+ ASSERT_TRUE(offerTransceivers[1]->mReceiving.GetNegotiatedDetails());
ASSERT_EQ(6U,
- offerPairs[1].mSending->GetNegotiatedDetails()->GetEncoding(0)
+ offerTransceivers[1]->mSending.GetNegotiatedDetails()->GetEncoding(0)
.GetCodecs().size());
ASSERT_EQ(6U,
- offerPairs[1].mReceiving->GetNegotiatedDetails()->GetEncoding(0)
+ offerTransceivers[1]->mReceiving.GetNegotiatedDetails()->GetEncoding(0)
.GetCodecs().size());
- auto answerPairs = mSessionAns->GetNegotiatedTrackPairs();
- ASSERT_EQ(2U, answerPairs.size());
- ASSERT_TRUE(answerPairs[1].mSending);
- ASSERT_TRUE(answerPairs[1].mReceiving);
- ASSERT_TRUE(answerPairs[1].mSending->GetNegotiatedDetails());
- ASSERT_TRUE(answerPairs[1].mReceiving->GetNegotiatedDetails());
+ auto answerTransceivers = mSessionAns->GetTransceivers();
+ ASSERT_EQ(2U, answerTransceivers.size());
+ ASSERT_FALSE(IsNull(answerTransceivers[1]->mSending));
+ ASSERT_FALSE(IsNull(answerTransceivers[1]->mReceiving));
+ ASSERT_TRUE(answerTransceivers[1]->mSending.GetNegotiatedDetails());
+ ASSERT_TRUE(answerTransceivers[1]->mReceiving.GetNegotiatedDetails());
ASSERT_EQ(6U,
- answerPairs[1].mSending->GetNegotiatedDetails()->GetEncoding(0)
+ answerTransceivers[1]->mSending.GetNegotiatedDetails()->GetEncoding(0)
.GetCodecs().size());
ASSERT_EQ(6U,
- answerPairs[1].mReceiving->GetNegotiatedDetails()->GetEncoding(0)
+ answerTransceivers[1]->mReceiving.GetNegotiatedDetails()->GetEncoding(0)
.GetCodecs().size());
}
TEST_F(JsepSessionTest, ValidateAnsweredCodecParams)
{
// TODO(bug 1099351): Once fixed, we can allow red in this offer,
// which will also cause multiple codecs in answer. For now,
// red/ulpfec for video are behind a pref to mitigate potential for
@@ -3483,33 +3723,23 @@ TEST_F(JsepSessionTest, ValidateAnswered
h264->mDefaultPt = "126";
}
}
}
types.push_back(SdpMediaSection::kAudio);
types.push_back(SdpMediaSection::kVideo);
- RefPtr<JsepTrack> msta(
- new JsepTrack(SdpMediaSection::kAudio, "offerer_stream", "a1"));
- mSessionOff->AddTrack(msta);
- RefPtr<JsepTrack> mstv1(
- new JsepTrack(SdpMediaSection::kVideo, "offerer_stream", "v1"));
- mSessionOff->AddTrack(mstv1);
+ AddTracksToStream(*mSessionOff, "offerer_stream", "audio,video");
std::string offer = CreateOffer();
SetLocalOffer(offer);
SetRemoteOffer(offer);
- RefPtr<JsepTrack> msta_ans(
- new JsepTrack(SdpMediaSection::kAudio, "answerer_stream", "a1"));
- mSessionAns->AddTrack(msta);
- RefPtr<JsepTrack> mstv1_ans(
- new JsepTrack(SdpMediaSection::kVideo, "answerer_stream", "v1"));
- mSessionAns->AddTrack(mstv1);
+ AddTracksToStream(*mSessionAns, "answerer_stream", "audio,video");
std::string answer = CreateAnswer();
UniquePtr<Sdp> outputSdp(Parse(answer));
ASSERT_TRUE(!!outputSdp);
ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
auto& video_section = outputSdp->GetMediaSection(1);
@@ -3561,40 +3791,40 @@ TEST_F(JsepSessionTest, ValidateAnswered
ASSERT_EQ((uint32_t)12288, parsed_vp8_params.max_fs);
ASSERT_EQ((uint32_t)60, parsed_vp8_params.max_fr);
SetLocalAnswer(answer);
SetRemoteAnswer(answer);
- auto offerPairs = mSessionOff->GetNegotiatedTrackPairs();
- ASSERT_EQ(2U, offerPairs.size());
- ASSERT_TRUE(offerPairs[1].mSending);
- ASSERT_TRUE(offerPairs[1].mReceiving);
- ASSERT_TRUE(offerPairs[1].mSending->GetNegotiatedDetails());
- ASSERT_TRUE(offerPairs[1].mReceiving->GetNegotiatedDetails());
+ auto offerTransceivers = mSessionOff->GetTransceivers();
+ ASSERT_EQ(2U, offerTransceivers.size());
+ ASSERT_FALSE(IsNull(offerTransceivers[1]->mSending));
+ ASSERT_FALSE(IsNull(offerTransceivers[1]->mReceiving));
+ ASSERT_TRUE(offerTransceivers[1]->mSending.GetNegotiatedDetails());
+ ASSERT_TRUE(offerTransceivers[1]->mReceiving.GetNegotiatedDetails());
ASSERT_EQ(1U,
- offerPairs[1].mSending->GetNegotiatedDetails()->GetEncoding(0)
+ offerTransceivers[1]->mSending.GetNegotiatedDetails()->GetEncoding(0)
.GetCodecs().size());
ASSERT_EQ(1U,
- offerPairs[1].mReceiving->GetNegotiatedDetails()->GetEncoding(0)
+ offerTransceivers[1]->mReceiving.GetNegotiatedDetails()->GetEncoding(0)
.GetCodecs().size());
- auto answerPairs = mSessionAns->GetNegotiatedTrackPairs();
- ASSERT_EQ(2U, answerPairs.size());
- ASSERT_TRUE(answerPairs[1].mSending);
- ASSERT_TRUE(answerPairs[1].mReceiving);
- ASSERT_TRUE(answerPairs[1].mSending->GetNegotiatedDetails());
- ASSERT_TRUE(answerPairs[1].mReceiving->GetNegotiatedDetails());
+ auto answerTransceivers = mSessionAns->GetTransceivers();
+ ASSERT_EQ(2U, answerTransceivers.size());
+ ASSERT_FALSE(IsNull(answerTransceivers[1]->mSending));
+ ASSERT_FALSE(IsNull(answerTransceivers[1]->mReceiving));
+ ASSERT_TRUE(answerTransceivers[1]->mSending.GetNegotiatedDetails());
+ ASSERT_TRUE(answerTransceivers[1]->mReceiving.GetNegotiatedDetails());
ASSERT_EQ(1U,
- answerPairs[1].mSending->GetNegotiatedDetails()->GetEncoding(0)
+ answerTransceivers[1]->mSending.GetNegotiatedDetails()->GetEncoding(0)
.GetCodecs().size());
ASSERT_EQ(1U,
- answerPairs[1].mReceiving->GetNegotiatedDetails()->GetEncoding(0)
+ answerTransceivers[1]->mReceiving.GetNegotiatedDetails()->GetEncoding(0)
.GetCodecs().size());
#if 0
// H264 packetization mode 1
ASSERT_EQ("126", fmtps[1].format);
ASSERT_TRUE(fmtps[1].parameters);
ASSERT_EQ(SdpRtpmapAttributeList::kH264, fmtps[1].parameters->codec_type);
@@ -3637,35 +3867,34 @@ static void ReplaceAll(const std::string
{
while (in->find(toReplace) != std::string::npos) {
Replace(toReplace, with, in);
}
}
static void
GetCodec(JsepSession& session,
- size_t pairIndex,
+ size_t transceiverIndex,
sdp::Direction direction,
size_t encodingIndex,
size_t codecIndex,
const JsepCodecDescription** codecOut)
{
*codecOut = nullptr;
- ASSERT_LT(pairIndex, session.GetNegotiatedTrackPairs().size());
- JsepTrackPair pair(session.GetNegotiatedTrackPairs().front());
- RefPtr<JsepTrack> track(
- (direction == sdp::kSend) ? pair.mSending : pair.mReceiving);
- ASSERT_TRUE(track);
- ASSERT_TRUE(track->GetNegotiatedDetails());
- ASSERT_LT(encodingIndex, track->GetNegotiatedDetails()->GetEncodingCount());
+ ASSERT_LT(transceiverIndex, session.GetTransceivers().size());
+ RefPtr<JsepTransceiver> transceiver(session.GetTransceivers()[transceiverIndex]);
+ JsepTrack& track =
+ (direction == sdp::kSend) ? transceiver->mSending : transceiver->mReceiving;
+ ASSERT_TRUE(track.GetNegotiatedDetails());
+ ASSERT_LT(encodingIndex, track.GetNegotiatedDetails()->GetEncodingCount());
ASSERT_LT(codecIndex,
- track->GetNegotiatedDetails()->GetEncoding(encodingIndex)
+ track.GetNegotiatedDetails()->GetEncoding(encodingIndex)
.GetCodecs().size());
*codecOut =
- track->GetNegotiatedDetails()->GetEncoding(encodingIndex)
+ track.GetNegotiatedDetails()->GetEncoding(encodingIndex)
.GetCodecs()[codecIndex];
}
static void
ForceH264(JsepSession& session, uint32_t profileLevelId)
{
for (JsepCodecDescription* codec : session.Codecs()) {
if (codec->mName == "H264") {
@@ -3738,18 +3967,18 @@ TEST_F(JsepSessionTest, TestH264Negotiat
SetLocalOffer(offer, CHECK_SUCCESS);
SetRemoteOffer(offer, CHECK_SUCCESS);
std::string answer(CreateAnswer());
SetRemoteAnswer(answer, CHECK_SUCCESS);
SetLocalAnswer(answer, CHECK_SUCCESS);
- ASSERT_EQ(0U, mSessionOff->GetNegotiatedTrackPairs().size());
- ASSERT_EQ(0U, mSessionAns->GetNegotiatedTrackPairs().size());
+ ASSERT_EQ(nullptr, GetNegotiatedTransceiver(*mSessionOff, 0));
+ ASSERT_EQ(nullptr, GetNegotiatedTransceiver(*mSessionAns, 0));
}
TEST_F(JsepSessionTest, TestH264NegotiationOffererDefault)
{
ForceH264(*mSessionOff, 0x42000d);
ForceH264(*mSessionAns, 0x42000d);
AddTracks(*mSessionOff, "video");
@@ -3971,17 +4200,16 @@ TEST_F(JsepSessionTest, TestH264LevelAsy
// it did not set level-asymmetry-required, and we already check that
// elsewhere
}
TEST_P(JsepSessionTest, TestRejectMline)
{
// We need to do this before adding tracks
types = BuildTypes(GetParam());
- std::sort(types.begin(), types.end());
switch (types.front()) {
case SdpMediaSection::kAudio:
// Sabotage audio
EnsureNegotiationFailure(types.front(), "opus");
break;
case SdpMediaSection::kVideo:
// Sabotage video
@@ -4022,26 +4250,30 @@ TEST_P(JsepSessionTest, TestRejectMline)
ASSERT_EQ(0U, failed_section->GetPort());
mSessionAns->SetLocalDescription(kJsepSdpAnswer, answer);
mSessionOff->SetRemoteDescription(kJsepSdpAnswer, answer);
size_t numRejected = std::count(types.begin(), types.end(), types.front());
size_t numAccepted = types.size() - numRejected;
- ASSERT_EQ(numAccepted, mSessionOff->GetNegotiatedTrackPairs().size());
- ASSERT_EQ(numAccepted, mSessionAns->GetNegotiatedTrackPairs().size());
-
- ASSERT_EQ(types.size(), mSessionOff->GetTransports().size());
- ASSERT_EQ(types.size(), mSessionOff->GetLocalTracks().size());
- ASSERT_EQ(numAccepted, mSessionOff->GetRemoteTracks().size());
-
- ASSERT_EQ(types.size(), mSessionAns->GetTransports().size());
- ASSERT_EQ(types.size(), mSessionAns->GetLocalTracks().size());
- ASSERT_EQ(types.size(), mSessionAns->GetRemoteTracks().size());
+ if (types.front() == SdpMediaSection::MediaType::kApplication) {
+ ASSERT_TRUE(GetDatachannelTransceiver(*mSessionOff));
+ ASSERT_FALSE(
+ GetDatachannelTransceiver(*mSessionOff)->mReceiving.GetActive());
+ ASSERT_TRUE(GetDatachannelTransceiver(*mSessionAns));
+ ASSERT_FALSE(
+ GetDatachannelTransceiver(*mSessionAns)->mReceiving.GetActive());
+ } else {
+ ASSERT_EQ(types.size(), GetLocalTracks(*mSessionOff).size());
+ ASSERT_EQ(numAccepted, GetRemoteTracks(*mSessionOff).size());
+
+ ASSERT_EQ(types.size(), GetLocalTracks(*mSessionAns).size());
+ ASSERT_EQ(types.size(), GetRemoteTracks(*mSessionAns).size());
+ }
}
TEST_F(JsepSessionTest, CreateOfferNoMlines)
{
JsepOfferOptions options;
std::string offer;
nsresult rv = mSessionOff->CreateOffer(options, &offer);
ASSERT_NE(NS_OK, rv);
@@ -4141,20 +4373,20 @@ TEST_F(JsepSessionTest, TestRtcpFbStar)
offer = parsedOffer->ToString();
SetLocalOffer(offer, CHECK_SUCCESS);
SetRemoteOffer(offer, CHECK_SUCCESS);
std::string answer = CreateAnswer();
SetLocalAnswer(answer, CHECK_SUCCESS);
SetRemoteAnswer(answer, CHECK_SUCCESS);
- ASSERT_EQ(1U, mSessionAns->GetRemoteTracks().size());
- RefPtr<JsepTrack> track = mSessionAns->GetRemoteTracks()[0];
- ASSERT_TRUE(track->GetNegotiatedDetails());
- auto* details = track->GetNegotiatedDetails();
+ ASSERT_EQ(1U, GetRemoteTracks(*mSessionAns).size());
+ JsepTrack track = GetRemoteTracks(*mSessionAns)[0];
+ ASSERT_TRUE(track.GetNegotiatedDetails());
+ auto* details = track.GetNegotiatedDetails();
for (const JsepCodecDescription* codec :
details->GetEncoding(0).GetCodecs()) {
const JsepVideoCodecDescription* videoCodec =
static_cast<const JsepVideoCodecDescription*>(codec);
ASSERT_EQ(1U, videoCodec->mNackFbTypes.size());
ASSERT_EQ("", videoCodec->mNackFbTypes[0]);
}
}
@@ -4168,55 +4400,55 @@ TEST_F(JsepSessionTest, TestUniquePayloa
std::string offer = CreateOffer();
SetLocalOffer(offer, CHECK_SUCCESS);
SetRemoteOffer(offer, CHECK_SUCCESS);
std::string answer = CreateAnswer();
SetLocalAnswer(answer, CHECK_SUCCESS);
SetRemoteAnswer(answer, CHECK_SUCCESS);
- auto offerPairs = mSessionOff->GetNegotiatedTrackPairs();
- auto answerPairs = mSessionAns->GetNegotiatedTrackPairs();
- ASSERT_EQ(3U, offerPairs.size());
- ASSERT_EQ(3U, answerPairs.size());
-
- ASSERT_TRUE(offerPairs[0].mReceiving);
- ASSERT_TRUE(offerPairs[0].mReceiving->GetNegotiatedDetails());
+ auto offerTransceivers = mSessionOff->GetTransceivers();
+ auto answerTransceivers = mSessionAns->GetTransceivers();
+ ASSERT_EQ(3U, offerTransceivers.size());
+ ASSERT_EQ(3U, answerTransceivers.size());
+
+ ASSERT_FALSE(IsNull(offerTransceivers[0]->mReceiving));
+ ASSERT_TRUE(offerTransceivers[0]->mReceiving.GetNegotiatedDetails());
ASSERT_EQ(0U,
- offerPairs[0].mReceiving->GetNegotiatedDetails()->
+ offerTransceivers[0]->mReceiving.GetNegotiatedDetails()->
GetUniquePayloadTypes().size());
- ASSERT_TRUE(offerPairs[1].mReceiving);
- ASSERT_TRUE(offerPairs[1].mReceiving->GetNegotiatedDetails());
+ ASSERT_FALSE(IsNull(offerTransceivers[1]->mReceiving));
+ ASSERT_TRUE(offerTransceivers[1]->mReceiving.GetNegotiatedDetails());
ASSERT_EQ(0U,
- offerPairs[1].mReceiving->GetNegotiatedDetails()->
+ offerTransceivers[1]->mReceiving.GetNegotiatedDetails()->
GetUniquePayloadTypes().size());
- ASSERT_TRUE(offerPairs[2].mReceiving);
- ASSERT_TRUE(offerPairs[2].mReceiving->GetNegotiatedDetails());
+ ASSERT_FALSE(IsNull(offerTransceivers[2]->mReceiving));
+ ASSERT_TRUE(offerTransceivers[2]->mReceiving.GetNegotiatedDetails());
ASSERT_NE(0U,
- offerPairs[2].mReceiving->GetNegotiatedDetails()->
+ offerTransceivers[2]->mReceiving.GetNegotiatedDetails()->
GetUniquePayloadTypes().size());
- ASSERT_TRUE(answerPairs[0].mReceiving);
- ASSERT_TRUE(answerPairs[0].mReceiving->GetNegotiatedDetails());
+ ASSERT_FALSE(IsNull(answerTransceivers[0]->mReceiving));
+ ASSERT_TRUE(answerTransceivers[0]->mReceiving.GetNegotiatedDetails());
ASSERT_EQ(0U,
- answerPairs[0].mReceiving->GetNegotiatedDetails()->
+ answerTransceivers[0]->mReceiving.GetNegotiatedDetails()->
GetUniquePayloadTypes().size());
- ASSERT_TRUE(answerPairs[1].mReceiving);
- ASSERT_TRUE(answerPairs[1].mReceiving->GetNegotiatedDetails());
+ ASSERT_FALSE(IsNull(answerTransceivers[1]->mReceiving));
+ ASSERT_TRUE(answerTransceivers[1]->mReceiving.GetNegotiatedDetails());
ASSERT_EQ(0U,
- answerPairs[1].mReceiving->GetNegotiatedDetails()->
+ answerTransceivers[1]->mReceiving.GetNegotiatedDetails()->
GetUniquePayloadTypes().size());
- ASSERT_TRUE(answerPairs[2].mReceiving);
- ASSERT_TRUE(answerPairs[2].mReceiving->GetNegotiatedDetails());
+ ASSERT_FALSE(IsNull(answerTransceivers[2]->mReceiving));
+ ASSERT_TRUE(answerTransceivers[2]->mReceiving.GetNegotiatedDetails());
ASSERT_NE(0U,
- answerPairs[2].mReceiving->GetNegotiatedDetails()->
+ answerTransceivers[2]->mReceiving.GetNegotiatedDetails()->
GetUniquePayloadTypes().size());
}
TEST_F(JsepSessionTest, UnknownFingerprintAlgorithm)
{
types.push_back(SdpMediaSection::kAudio);
AddTracks(*mSessionOff, "audio");
AddTracks(*mSessionAns, "audio");
@@ -4381,17 +4613,17 @@ TEST_P(JsepSessionTest, TestRejectOfferR
std::string offer = CreateOffer();
SetLocalOffer(offer);
SetRemoteOffer(offer);
ASSERT_EQ(NS_OK,
mSessionAns->SetRemoteDescription(kJsepSdpRollback, ""));
ASSERT_EQ(kJsepStateStable, mSessionAns->GetState());
- ASSERT_EQ(types.size(), mSessionAns->GetRemoteTracksRemoved().size());
+ ASSERT_EQ(CountRtpTypes(), mSessionAns->GetRemoteTracksRemoved().size());
ASSERT_EQ(NS_OK,
mSessionOff->SetLocalDescription(kJsepSdpRollback, ""));
ASSERT_EQ(kJsepStateStable, mSessionOff->GetState());
OfferAnswer();
}
@@ -4433,20 +4665,22 @@ TEST_P(JsepSessionTest, TestInvalidRollb
ASSERT_EQ(NS_ERROR_UNEXPECTED,
mSessionOff->SetLocalDescription(kJsepSdpRollback, ""));
ASSERT_EQ(NS_ERROR_UNEXPECTED,
mSessionOff->SetRemoteDescription(kJsepSdpRollback, ""));
}
size_t GetActiveTransportCount(const JsepSession& session)
{
- auto transports = session.GetTransports();
size_t activeTransportCount = 0;
- for (RefPtr<JsepTransport>& transport : transports) {
- activeTransportCount += transport->mComponents;
+ for (const auto& transceiver : session.GetTransceivers()) {
+ if (!transceiver->HasBundleLevel() ||
+ (transceiver->BundleLevel() == transceiver->GetLevel())) {
+ activeTransportCount += transceiver->mTransport->mComponents;
+ }
}
return activeTransportCount;
}
TEST_P(JsepSessionTest, TestBalancedBundle)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
@@ -4472,18 +4706,18 @@ TEST_P(JsepSessionTest, TestBalancedBund
}
SetLocalOffer(offer);
SetRemoteOffer(offer);
std::string answer = CreateAnswer();
SetLocalAnswer(answer);
SetRemoteAnswer(answer);
- CheckPairs(*mSessionOff, "Offerer pairs");
- CheckPairs(*mSessionAns, "Answerer pairs");
+ CheckTransceiversAreBundled(*mSessionOff, "Offerer transceivers");
+ CheckTransceiversAreBundled(*mSessionAns, "Answerer transceivers");
EXPECT_EQ(1U, GetActiveTransportCount(*mSessionOff));
EXPECT_EQ(1U, GetActiveTransportCount(*mSessionAns));
}
TEST_P(JsepSessionTest, TestMaxBundle)
{
AddTracks(*mSessionOff);
AddTracks(*mSessionAns);
@@ -4503,18 +4737,18 @@ TEST_P(JsepSessionTest, TestMaxBundle)
for (size_t i = 1; i < parsedOffer->GetMediaSectionCount(); ++i) {
ASSERT_TRUE(
parsedOffer->GetMediaSection(i).GetAttributeList().HasAttribute(
SdpAttribute::kBundleOnlyAttribute));
ASSERT_EQ(0U, parsedOffer->GetMediaSection(i).GetPort());
}
- CheckPairs(*mSessionOff, "Offerer pairs");
- CheckPairs(*mSessionAns, "Answerer pairs");
+ CheckTransceiversAreBundled(*mSessionOff, "Offerer transceivers");
+ CheckTransceiversAreBundled(*mSessionAns, "Answerer transceivers");
EXPECT_EQ(1U, GetActiveTransportCount(*mSessionOff));
EXPECT_EQ(1U, GetActiveTransportCount(*mSessionAns));
}
TEST_F(JsepSessionTest, TestNonDefaultProtocol)
{
AddTracks(*mSessionOff, "audio,video,datachannel");
AddTracks(*mSessionAns, "audio,video,datachannel");
@@ -4636,60 +4870,48 @@ TEST_F(JsepSessionTest, CreateOfferDontR
}
TEST_F(JsepSessionTest, CreateOfferRemoveAudioTrack)
{
types.push_back(SdpMediaSection::kAudio);
types.push_back(SdpMediaSection::kVideo);
AddTracks(*mSessionOff, "audio,video");
- JsepOfferOptions options;
- options.mOfferToReceiveAudio = Some(static_cast<size_t>(1U));
- options.mOfferToReceiveVideo = Some(static_cast<size_t>(0U));
-
- RefPtr<JsepTrack> removedTrack = GetTrackOff(0, types.front());
- ASSERT_TRUE(removedTrack);
- ASSERT_EQ(NS_OK, mSessionOff->RemoveTrack(removedTrack->GetStreamId(),
- removedTrack->GetTrackId()));
-
- CreateOffer(Some(options));
+ SetDirection(*mSessionOff, 1, SdpDirectionAttribute::kSendonly);
+ JsepTrack removedTrack = RemoveTrack(*mSessionOff, 0);
+ ASSERT_FALSE(IsNull(removedTrack));
+
+ CreateOffer();
}
TEST_F(JsepSessionTest, CreateOfferDontReceiveAudioRemoveAudioTrack)
{
types.push_back(SdpMediaSection::kAudio);
types.push_back(SdpMediaSection::kVideo);
AddTracks(*mSessionOff, "audio,video");
- JsepOfferOptions options;
- options.mOfferToReceiveAudio = Some(static_cast<size_t>(0U));
- options.mOfferToReceiveVideo = Some(static_cast<size_t>(1U));
-
- RefPtr<JsepTrack> removedTrack = GetTrackOff(0, types.front());
- ASSERT_TRUE(removedTrack);
- ASSERT_EQ(NS_OK, mSessionOff->RemoveTrack(removedTrack->GetStreamId(),
- removedTrack->GetTrackId()));
-
- CreateOffer(Some(options));
+ SetDirection(*mSessionOff, 0, SdpDirectionAttribute::kSendonly);
+ JsepTrack removedTrack = RemoveTrack(*mSessionOff, 0);
+ ASSERT_FALSE(IsNull(removedTrack));
+
+ CreateOffer();
}
TEST_F(JsepSessionTest, CreateOfferDontReceiveVideoRemoveVideoTrack)
{
types.push_back(SdpMediaSection::kAudio);
types.push_back(SdpMediaSection::kVideo);
AddTracks(*mSessionOff, "audio,video");
JsepOfferOptions options;
options.mOfferToReceiveAudio = Some(static_cast<size_t>(1U));
options.mOfferToReceiveVideo = Some(static_cast<size_t>(0U));
- RefPtr<JsepTrack> removedTrack = GetTrackOff(0, types.back());
- ASSERT_TRUE(removedTrack);
- ASSERT_EQ(NS_OK, mSessionOff->RemoveTrack(removedTrack->GetStreamId(),
- removedTrack->GetTrackId()));
+ JsepTrack removedTrack = RemoveTrack(*mSessionOff, 0);
+ ASSERT_FALSE(IsNull(removedTrack));
CreateOffer(Some(options));
}
static const std::string strSampleCandidate =
"a=candidate:1 1 UDP 2130706431 192.168.2.1 50005 typ host\r\n";
static const unsigned short nSamplelevel = 2;
@@ -5336,19 +5558,19 @@ TEST_F(JsepSessionTest, AudioCallMismatc
std::string active = "\r\na=setup:active";
match = answer.find(active);
ASSERT_NE(match, std::string::npos);
answer.replace(match, active.length(), "\r\na=setup:passive");
SetRemoteAnswer(answer);
// This is as good as it gets in a JSEP test (w/o starting DTLS)
ASSERT_EQ(JsepDtlsTransport::kJsepDtlsClient,
- mSessionOff->GetTransports()[0]->mDtls->GetRole());
+ mSessionOff->GetTransceivers()[0]->mTransport->mDtls->GetRole());
ASSERT_EQ(JsepDtlsTransport::kJsepDtlsClient,
- mSessionAns->GetTransports()[0]->mDtls->GetRole());
+ mSessionAns->GetTransceivers()[0]->mTransport->mDtls->GetRole());
}
// Verify that missing a=setup in offer gets rejected
TEST_F(JsepSessionTest, AudioCallOffererNoSetup)
{
types.push_back(SdpMediaSection::kAudio);
AddTracks(*mSessionOff, "audio");
AddTracks(*mSessionAns, "audio");
@@ -5388,19 +5610,19 @@ TEST_F(JsepSessionTest, AudioCallAnswerN
match = answer.find(active);
ASSERT_NE(match, std::string::npos);
answer.replace(match, active.length(), "");
SetRemoteAnswer(answer);
ASSERT_EQ(kJsepStateStable, mSessionAns->GetState());
// This is as good as it gets in a JSEP test (w/o starting DTLS)
ASSERT_EQ(JsepDtlsTransport::kJsepDtlsServer,
- mSessionOff->GetTransports()[0]->mDtls->GetRole());
+ mSessionOff->GetTransceivers()[0]->mTransport->mDtls->GetRole());
ASSERT_EQ(JsepDtlsTransport::kJsepDtlsClient,
- mSessionAns->GetTransports()[0]->mDtls->GetRole());
+ mSessionAns->GetTransceivers()[0]->mTransport->mDtls->GetRole());
}
// Verify that 'holdconn' gets rejected
TEST_F(JsepSessionTest, AudioCallDtlsRoleHoldconn)
{
types.push_back(SdpMediaSection::kAudio);
AddTracks(*mSessionOff, "audio");
AddTracks(*mSessionAns, "audio");
@@ -5554,9 +5776,656 @@ TEST_F(JsepSessionTest, AnswerWithoutVP8
}
std::string answer = CreateAnswer();
SetLocalAnswer(answer);
SetRemoteAnswer(answer);
}
+// Ok. Hear me out.
+// The JSEP spec specifies very different behavior for the following two cases:
+// 1. AddTrack either caused a transceiver to be created, or set the send
+// track on a preexisting transceiver.
+// 2. The transceiver was not created as a side-effect of AddTrack, and the
+// send track was put in place by some other means than AddTrack.
+//
+// All together now...
+//
+// SADFACE :(
+//
+// Ok, enough of that. The upshot is we need to test two different codepaths for
+// the same thing here. Most of this unit-test suite tests the "magic" case
+// (case 1 above). Case 2 (the non-magic case) is simpler, so we have just a
+// handful of tests.
+TEST_F(JsepSessionTest, OffererNoAddTrackMagic)
+{
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff, NO_ADDTRACK_MAGIC);
+ AddTracks(*mSessionAns);
+
+ // Offerer's transceivers aren't "magic"; they will not associate with the
+ // remote side's m-sections automatically. But, since they went into the
+ // offer, everything works normally.
+ OfferAnswer();
+
+ ASSERT_EQ(2U, mSessionOff->GetTransceivers().size());
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers().size());
+}
+
+TEST_F(JsepSessionTest, AnswererNoAddTrackMagic)
+{
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns, NO_ADDTRACK_MAGIC);
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ ASSERT_EQ(2U, mSessionOff->GetTransceivers().size());
+ // Since answerer's transceivers aren't "magic", they cannot automatically be
+ // attached to the offerer's m-sections.
+ ASSERT_EQ(4U, mSessionAns->GetTransceivers().size());
+
+ // TODO: Once nils' code for switching offer/answer roles lands, switch and
+ // have the other side reoffer to negotiate the new transceivers.
+}
+
+// JSEP has rules about when a disabled m-section can be reused; the gist is
+// that the m-section has to be negotiated disabled, then it becomes a candidate
+// for reuse on the next renegotiation. Stopping a transceiver does not allow
+// you to reuse on the next negotiation.
+TEST_F(JsepSessionTest, OffererRecycle)
+{
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, mSessionOff->GetTransceivers().size());
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers().size());
+ mSessionOff->GetTransceivers()[0]->Stop();
+ AddTracks(*mSessionOff, "audio");
+ ASSERT_EQ(3U, mSessionOff->GetTransceivers().size());
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ // It is too soon to recycle msection 0, so the new track should have been
+ // given a new msection.
+ ASSERT_EQ(3U, mSessionOff->GetTransceivers().size());
+ ASSERT_EQ(0U, mSessionOff->GetTransceivers()[0]->GetLevel());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[0]->IsStopped());
+ ASSERT_EQ(1U, mSessionOff->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[1]->IsStopped());
+ ASSERT_EQ(2U, mSessionOff->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[2]->IsStopped());
+
+ ASSERT_EQ(3U, mSessionAns->GetTransceivers().size());
+ ASSERT_EQ(0U, mSessionAns->GetTransceivers()[0]->GetLevel());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsStopped());
+ ASSERT_EQ(1U, mSessionAns->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsStopped());
+
+ UniquePtr<Sdp> offer = GetParsedLocalDescription(*mSessionOff);
+ ASSERT_EQ(3U, offer->GetMediaSectionCount());
+ ValidateDisabledMSection(&offer->GetMediaSection(0));
+
+ UniquePtr<Sdp> answer = GetParsedLocalDescription(*mSessionAns);
+ ASSERT_EQ(3U, answer->GetMediaSectionCount());
+ ValidateDisabledMSection(&answer->GetMediaSection(0));
+
+ // Ok. Now renegotiating should recycle m-section 0.
+ AddTracks(*mSessionOff, "audio");
+ ASSERT_EQ(4U, mSessionOff->GetTransceivers().size());
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Transceiver 3 should now be attached to m-section 0
+ ASSERT_EQ(4U, mSessionOff->GetTransceivers().size());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[0]->HasLevel());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[0]->IsStopped());
+ ASSERT_EQ(1U, mSessionOff->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[1]->IsStopped());
+ ASSERT_EQ(2U, mSessionOff->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[2]->IsStopped());
+ ASSERT_EQ(0U, mSessionOff->GetTransceivers()[3]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[3]->IsStopped());
+
+ ASSERT_EQ(4U, mSessionAns->GetTransceivers().size());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->HasLevel());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsStopped());
+ ASSERT_EQ(1U, mSessionAns->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsStopped());
+ ASSERT_EQ(0U, mSessionAns->GetTransceivers()[3]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[3]->IsStopped());
+}
+
+TEST_F(JsepSessionTest, RecycleAnswererStopsTransceiver)
+{
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, mSessionOff->GetTransceivers().size());
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers().size());
+ mSessionAns->GetTransceivers()[0]->Stop();
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ ASSERT_EQ(2U, mSessionOff->GetTransceivers().size());
+ ASSERT_EQ(0U, mSessionOff->GetTransceivers()[0]->GetLevel());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[0]->IsStopped());
+ ASSERT_EQ(1U, mSessionOff->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[1]->IsStopped());
+
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers().size());
+ ASSERT_EQ(0U, mSessionAns->GetTransceivers()[0]->GetLevel());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsStopped());
+ ASSERT_EQ(1U, mSessionAns->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
+
+ UniquePtr<Sdp> offer = GetParsedLocalDescription(*mSessionOff);
+ ASSERT_EQ(2U, offer->GetMediaSectionCount());
+
+ UniquePtr<Sdp> answer = GetParsedLocalDescription(*mSessionAns);
+ ASSERT_EQ(2U, answer->GetMediaSectionCount());
+ ValidateDisabledMSection(&answer->GetMediaSection(0));
+
+ // Renegotiating should recycle m-section 0.
+ AddTracks(*mSessionOff, "audio");
+ ASSERT_EQ(3U, mSessionOff->GetTransceivers().size());
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Transceiver 3 should now be attached to m-section 0
+ ASSERT_EQ(3U, mSessionOff->GetTransceivers().size());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[0]->HasLevel());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[0]->IsStopped());
+ ASSERT_EQ(1U, mSessionOff->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[1]->IsStopped());
+ ASSERT_EQ(0U, mSessionOff->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[2]->IsStopped());
+
+ ASSERT_EQ(3U, mSessionAns->GetTransceivers().size());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->HasLevel());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsStopped());
+ ASSERT_EQ(1U, mSessionAns->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
+ ASSERT_EQ(0U, mSessionAns->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsStopped());
+}
+
+// TODO: Have a test where offerer stops, and answerer adds a track and reoffers
+// once Nils' role swap code lands.
+
+// TODO: Have a test where answerer stops and adds a track.
+
+TEST_F(JsepSessionTest, OffererRecycleNoMagic)
+{
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, mSessionOff->GetTransceivers().size());
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers().size());
+ mSessionOff->GetTransceivers()[0]->Stop();
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Ok. Now renegotiating should recycle m-section 0.
+ AddTracks(*mSessionOff, "audio", NO_ADDTRACK_MAGIC);
+ ASSERT_EQ(3U, mSessionOff->GetTransceivers().size());
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Transceiver 2 should now be attached to m-section 0
+ ASSERT_EQ(3U, mSessionOff->GetTransceivers().size());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[0]->HasLevel());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[0]->IsStopped());
+ ASSERT_EQ(1U, mSessionOff->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[1]->IsStopped());
+ ASSERT_EQ(0U, mSessionOff->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[2]->IsStopped());
+
+ ASSERT_EQ(3U, mSessionAns->GetTransceivers().size());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->HasLevel());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsStopped());
+ ASSERT_EQ(1U, mSessionAns->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
+ ASSERT_EQ(0U, mSessionAns->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsStopped());
+}
+
+TEST_F(JsepSessionTest, OffererRecycleNoMagicAnswererStopsTransceiver)
+{
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, mSessionOff->GetTransceivers().size());
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers().size());
+ mSessionAns->GetTransceivers()[0]->Stop();
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Ok. Now renegotiating should recycle m-section 0.
+ AddTracks(*mSessionOff, "audio", NO_ADDTRACK_MAGIC);
+ ASSERT_EQ(3U, mSessionOff->GetTransceivers().size());
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Transceiver 2 should now be attached to m-section 0
+ ASSERT_EQ(3U, mSessionOff->GetTransceivers().size());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[0]->HasLevel());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[0]->IsStopped());
+ ASSERT_EQ(1U, mSessionOff->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[1]->IsStopped());
+ ASSERT_EQ(0U, mSessionOff->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[2]->IsStopped());
+
+ ASSERT_EQ(3U, mSessionAns->GetTransceivers().size());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->HasLevel());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsStopped());
+ ASSERT_EQ(1U, mSessionAns->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
+ ASSERT_EQ(0U, mSessionAns->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsStopped());
+}
+
+TEST_F(JsepSessionTest, RecycleRollback)
+{
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, mSessionOff->GetTransceivers().size());
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers().size());
+ mSessionOff->GetTransceivers()[0]->Stop();
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ AddTracks(*mSessionOff, "audio");
+
+ ASSERT_EQ(3U, mSessionOff->GetTransceivers().size());
+ ASSERT_EQ(0U, mSessionOff->GetTransceivers()[0]->GetLevel());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[0]->IsStopped());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[0]->IsAssociated());
+ ASSERT_EQ(1U, mSessionOff->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[1]->IsStopped());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[1]->IsAssociated());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[2]->HasLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[2]->IsStopped());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[2]->IsAssociated());
+
+ std::string offer = CreateOffer();
+ ASSERT_EQ(3U, mSessionOff->GetTransceivers().size());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[0]->HasLevel());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[0]->IsStopped());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[0]->IsAssociated());
+ ASSERT_EQ(1U, mSessionOff->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[1]->IsStopped());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[1]->IsAssociated());
+ ASSERT_EQ(0U, mSessionOff->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[2]->IsStopped());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[2]->IsAssociated());
+
+ SetLocalOffer(offer, CHECK_SUCCESS);
+
+ ASSERT_EQ(3U, mSessionOff->GetTransceivers().size());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[0]->HasLevel());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[0]->IsStopped());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[0]->IsAssociated());
+ ASSERT_EQ(1U, mSessionOff->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[1]->IsStopped());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[1]->IsAssociated());
+ ASSERT_EQ(0U, mSessionOff->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[2]->IsStopped());
+ // This should now be associated
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[2]->IsAssociated());
+
+ ASSERT_EQ(NS_OK,
+ mSessionOff->SetLocalDescription(kJsepSdpRollback, ""));
+
+ // Rollback should not change the levels of any of these, since those are set
+ // in CreateOffer.
+ ASSERT_EQ(3U, mSessionOff->GetTransceivers().size());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[0]->HasLevel());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[0]->IsStopped());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[0]->IsAssociated());
+ ASSERT_EQ(1U, mSessionOff->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[1]->IsStopped());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[1]->IsAssociated());
+ ASSERT_EQ(0U, mSessionOff->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[2]->IsStopped());
+ // This should no longer be associated
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[2]->IsAssociated());
+}
+
+TEST_F(JsepSessionTest, AddTrackMagicWithNullReplaceTrack)
+{
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, mSessionOff->GetTransceivers().size());
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers().size());
+
+ AddTracks(*mSessionAns, "audio");
+ AddTracks(*mSessionOff, "audio");
+
+ ASSERT_EQ(3U, mSessionAns->GetTransceivers().size());
+ ASSERT_EQ(0U, mSessionAns->GetTransceivers()[0]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsAssociated());
+ ASSERT_EQ(1U, mSessionAns->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[1]->IsAssociated());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->HasLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsStopped());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsAssociated());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[2]->HasAddTrackMagic());
+
+ // Ok, transceiver 2 is "magical". Ensure it still has this "magical"
+ // auto-matching property even if we null it out with replaceTrack.
+ mSessionAns->GetTransceivers()[2]->mSending.ClearTrack();
+ mSessionAns->GetTransceivers()[2]->mJsDirection =
+ SdpDirectionAttribute::Direction::kRecvonly;
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ ASSERT_EQ(3U, mSessionAns->GetTransceivers().size());
+ ASSERT_EQ(0U, mSessionAns->GetTransceivers()[0]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsAssociated());
+ ASSERT_EQ(1U, mSessionAns->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[1]->IsAssociated());
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[2]->IsAssociated());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[2]->HasAddTrackMagic());
+
+ ASSERT_EQ(3U, mSessionOff->GetTransceivers().size());
+ ASSERT_EQ(0U, mSessionOff->GetTransceivers()[0]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[0]->IsStopped());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[0]->IsAssociated());
+ ASSERT_EQ(1U, mSessionOff->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[1]->IsStopped());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[1]->IsAssociated());
+ ASSERT_EQ(2U, mSessionOff->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[2]->IsStopped());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[2]->IsAssociated());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[2]->HasAddTrackMagic());
+}
+
+// Flipside of AddTrackMagicWithNullReplaceTrack; we want to check that
+// auto-matching does not work for transceivers that were created without a
+// track, but were later given a track with replaceTrack.
+TEST_F(JsepSessionTest, NoAddTrackMagicReplaceTrack)
+{
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, mSessionOff->GetTransceivers().size());
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers().size());
+ AddTracks(*mSessionOff, "audio");
+ mSessionAns->AddTransceiver(
+ new JsepTransceiver(SdpMediaSection::MediaType::kAudio));
+
+ mSessionAns->GetTransceivers()[2]->mSending.UpdateTrack(
+ {"newstream"}, "newtrack");
+
+ ASSERT_EQ(3U, mSessionAns->GetTransceivers().size());
+ ASSERT_EQ(0U, mSessionAns->GetTransceivers()[0]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsAssociated());
+ ASSERT_EQ(1U, mSessionAns->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[1]->IsAssociated());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->HasLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsStopped());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsAssociated());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->HasAddTrackMagic());
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ ASSERT_EQ(4U, mSessionAns->GetTransceivers().size());
+ ASSERT_EQ(0U, mSessionAns->GetTransceivers()[0]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsAssociated());
+ ASSERT_EQ(1U, mSessionAns->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[1]->IsAssociated());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->HasLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsStopped());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsAssociated());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->HasAddTrackMagic());
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers()[3]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[3]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[3]->IsAssociated());
+}
+
+// Check that transceivers that were created without a send track, but that
+// were subsequently given a send track with addTrack, are now "magical".
+TEST_F(JsepSessionTest, AddTrackMakesTransceiverMagical)
+{
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, mSessionOff->GetTransceivers().size());
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers().size());
+ AddTracks(*mSessionOff, "audio");
+ mSessionAns->AddTransceiver(
+ new JsepTransceiver(SdpMediaSection::MediaType::kAudio));
+
+ ASSERT_EQ(3U, mSessionAns->GetTransceivers().size());
+ ASSERT_EQ(0U, mSessionAns->GetTransceivers()[0]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsAssociated());
+ ASSERT_EQ(1U, mSessionAns->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[1]->IsAssociated());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->HasLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsStopped());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsAssociated());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->HasAddTrackMagic());
+
+ // :D MAGIC! D:
+ AddTracks(*mSessionAns, "audio");
+
+ ASSERT_EQ(3U, mSessionAns->GetTransceivers().size());
+ ASSERT_EQ(0U, mSessionAns->GetTransceivers()[0]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsAssociated());
+ ASSERT_EQ(1U, mSessionAns->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[1]->IsAssociated());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->HasLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsStopped());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsAssociated());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[2]->HasAddTrackMagic());
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ ASSERT_EQ(3U, mSessionAns->GetTransceivers().size());
+ ASSERT_EQ(0U, mSessionAns->GetTransceivers()[0]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsAssociated());
+ ASSERT_EQ(1U, mSessionAns->GetTransceivers()[1]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[1]->IsAssociated());
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[2]->IsAssociated());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[2]->HasAddTrackMagic());
+}
+
+TEST_F(JsepSessionTest, ComplicatedRemoteRollback)
+{
+ AddTracks(*mSessionOff, "audio,audio,audio,video");
+ AddTracks(*mSessionAns, "video,video");
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, CHECK_SUCCESS);
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+
+ // Three recvonly for audio, one sendrecv for video, and one (unmapped) for
+ // the second video track.
+ ASSERT_EQ(5U, mSessionAns->GetTransceivers().size());
+ // First video transceiver; auto matched with offer
+ ASSERT_EQ(3U, mSessionAns->GetTransceivers()[0]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsAssociated());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->HasAddTrackMagic());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->WasCreatedBySetRemote());
+
+ // Second video transceiver, not matched with offer
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->HasLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsAssociated());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[1]->HasAddTrackMagic());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->WasCreatedBySetRemote());
+
+ // Audio transceiver, created due to application of SetRemote
+ ASSERT_EQ(0U, mSessionAns->GetTransceivers()[2]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[2]->IsAssociated());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->HasAddTrackMagic());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[2]->WasCreatedBySetRemote());
+
+ // Audio transceiver, created due to application of SetRemote
+ ASSERT_EQ(1U, mSessionAns->GetTransceivers()[3]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[3]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[3]->IsAssociated());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[3]->HasAddTrackMagic());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[3]->WasCreatedBySetRemote());
+
+ // Audio transceiver, created due to application of SetRemote
+ ASSERT_EQ(2U, mSessionAns->GetTransceivers()[4]->GetLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[4]->IsStopped());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[4]->IsAssociated());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[4]->HasAddTrackMagic());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[4]->WasCreatedBySetRemote());
+
+ // This will cause the first audio transceiver to become "magical", and
+ // thereby it will stick around after rollback, even though we clear it out
+ // with replaceTrack.
+ AddTracks(*mSessionAns, "audio");
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[2]->HasAddTrackMagic());
+ mSessionAns->GetTransceivers()[2]->mSending.ClearTrack();
+ mSessionAns->GetTransceivers()[2]->mJsDirection =
+ SdpDirectionAttribute::Direction::kRecvonly;
+
+ // We do nothing with the second audio transceiver; when we rollback, it will
+ // disappear entirely.
+
+ // This will not cause the third audio transceiver to stick around; having a
+ // track is _not_ enough to preserve it. It must have addTrack "magic"!
+ mSessionAns->GetTransceivers()[4]->mSending.UpdateTrack(
+ {"newstream"}, "newtrack");
+
+ // Create a fourth audio transceiver. Rollback will leave it alone, since we
+ // created it.
+ mSessionAns->AddTransceiver(new JsepTransceiver(
+ SdpMediaSection::MediaType::kAudio,
+ SdpDirectionAttribute::Direction::kRecvonly));
+
+ ASSERT_EQ(NS_OK,
+ mSessionAns->SetRemoteDescription(kJsepSdpRollback, ""));
+
+ // Three recvonly for audio, one sendrecv for video, and one (unmapped) for
+ // the second video track.
+ ASSERT_EQ(4U, mSessionAns->GetTransceivers().size());
+
+ // First video transceiver
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->HasLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->IsStopped());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->IsAssociated());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->HasAddTrackMagic());
+ ASSERT_FALSE(IsNull(mSessionAns->GetTransceivers()[0]->mSending));
+
+ // Second video transceiver
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->HasLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsAssociated());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[1]->HasAddTrackMagic());
+ ASSERT_FALSE(IsNull(mSessionAns->GetTransceivers()[1]->mSending));
+
+ // First audio transceiver, kept because AddTrack touched it, even though we
+ // removed the send track after.
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->HasLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsStopped());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsAssociated());
+ ASSERT_TRUE(mSessionAns->GetTransceivers()[2]->HasAddTrackMagic());
+ ASSERT_TRUE(IsNull(mSessionAns->GetTransceivers()[2]->mSending));
+
+ // Second audio transceiver should be gone.
+
+ // Third audio transceiver should also be gone.
+
+ // Fourth audio transceiver, created after SetRemote
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[3]->HasLevel());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[3]->IsStopped());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[3]->IsAssociated());
+ ASSERT_FALSE(mSessionAns->GetTransceivers()[3]->HasAddTrackMagic());
+ ASSERT_TRUE(
+ mSessionAns->GetTransceivers()[3]->mSending.GetStreamIds().empty());
+}
+
+TEST_F(JsepSessionTest, LocalRollback)
+{
+ AddTracks(*mSessionOff, "audio,video");
+ AddTracks(*mSessionAns, "audio,video");
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, CHECK_SUCCESS);
+
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[0]->IsAssociated());
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[1]->IsAssociated());
+ ASSERT_EQ(NS_OK,
+ mSessionOff->SetLocalDescription(kJsepSdpRollback, ""));
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[0]->IsAssociated());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[1]->IsAssociated());
+}
+
+TEST_F(JsepSessionTest, JsStopsTransceiverBeforeAnswer)
+{
+ AddTracks(*mSessionOff, "audio,video");
+ AddTracks(*mSessionAns, "audio,video");
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, CHECK_SUCCESS);
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+
+ // Now JS decides to stop a transceiver. Make sure transport stuff is still
+ // ready to go when the answer is set. This should only prevent the flow of
+ // media for that transceiver.
+
+ mSessionOff->GetTransceivers()[0]->Stop();
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ ASSERT_TRUE(mSessionOff->GetTransceivers()[0]->IsStopped());
+ ASSERT_EQ(1U, mSessionOff->GetTransceivers()[0]->mTransport->mComponents);
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[0]->mSending.GetActive());
+ ASSERT_FALSE(mSessionOff->GetTransceivers()[0]->mReceiving.GetActive());
+}
+
} // namespace mozilla
+
--- a/media/webrtc/signaling/gtest/jsep_track_unittest.cpp
+++ b/media/webrtc/signaling/gtest/jsep_track_unittest.cpp
@@ -11,17 +11,22 @@
#include "signaling/src/sdp/SipccSdp.h"
#include "signaling/src/sdp/SdpHelper.h"
namespace mozilla {
class JsepTrackTest : public ::testing::Test
{
public:
- JsepTrackTest() {}
+ JsepTrackTest() :
+ mSendOff(SdpMediaSection::kAudio, sdp::kSend),
+ mRecvOff(SdpMediaSection::kAudio, sdp::kRecv),
+ mSendAns(SdpMediaSection::kAudio, sdp::kSend),
+ mRecvAns(SdpMediaSection::kAudio, sdp::kRecv)
+ {}
std::vector<JsepCodecDescription*>
MakeCodecs(bool addFecCodecs = false,
bool preferRed = false,
bool addDtmfCodec = false) const
{
std::vector<JsepCodecDescription*> results;
results.push_back(
@@ -98,101 +103,112 @@ class JsepTrackTest : public ::testing::
void InitCodecs() {
mOffCodecs.values = MakeCodecs();
mAnsCodecs.values = MakeCodecs();
}
void InitTracks(SdpMediaSection::MediaType type)
{
- mSendOff = new JsepTrack(type, "stream_id", "track_id", sdp::kSend);
- mRecvOff = new JsepTrack(type, "stream_id", "track_id", sdp::kRecv);
- mSendOff->PopulateCodecs(mOffCodecs.values);
- mRecvOff->PopulateCodecs(mOffCodecs.values);
+ mSendOff = JsepTrack(type, sdp::kSend);
+ if (type != SdpMediaSection::MediaType::kApplication) {
+ mSendOff.UpdateTrack(
+ std::vector<std::string>(1, "stream_id"), "track_id");
+ }
+ mRecvOff = JsepTrack(type, sdp::kRecv);
+ mSendOff.PopulateCodecs(mOffCodecs.values);
+ mRecvOff.PopulateCodecs(mOffCodecs.values);
- mSendAns = new JsepTrack(type, "stream_id", "track_id", sdp::kSend);
- mRecvAns = new JsepTrack(type, "stream_id", "track_id", sdp::kRecv);
- mSendAns->PopulateCodecs(mAnsCodecs.values);
- mRecvAns->PopulateCodecs(mAnsCodecs.values);
+ mSendAns = JsepTrack(type, sdp::kSend);
+ if (type != SdpMediaSection::MediaType::kApplication) {
+ mSendAns.UpdateTrack(
+ std::vector<std::string>(1, "stream_id"), "track_id");
+ }
+ mRecvAns = JsepTrack(type, sdp::kRecv);
+ mSendAns.PopulateCodecs(mAnsCodecs.values);
+ mRecvAns.PopulateCodecs(mAnsCodecs.values);
}
void InitSdp(SdpMediaSection::MediaType type)
{
+ std::vector<std::string> msids(1, "*");
+ std::string error;
+ SdpHelper helper(&error);
+
mOffer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, "")));
mOffer->AddMediaSection(
type,
- SdpDirectionAttribute::kInactive,
+ SdpDirectionAttribute::kSendrecv,
0,
SdpHelper::GetProtocolForMediaType(type),
sdp::kIPv4,
"0.0.0.0");
+ // JsepTrack doesn't set msid-semantic
+ helper.SetupMsidSemantic(msids, mOffer.get());
+
mAnswer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, "")));
mAnswer->AddMediaSection(
type,
- SdpDirectionAttribute::kInactive,
+ SdpDirectionAttribute::kSendrecv,
0,
SdpHelper::GetProtocolForMediaType(type),
sdp::kIPv4,
"0.0.0.0");
+ // JsepTrack doesn't set msid-semantic
+ helper.SetupMsidSemantic(msids, mAnswer.get());
}
SdpMediaSection& GetOffer()
{
return mOffer->GetMediaSection(0);
}
SdpMediaSection& GetAnswer()
{
return mAnswer->GetMediaSection(0);
}
void CreateOffer()
{
- if (mSendOff) {
- mSendOff->AddToOffer(&GetOffer());
- }
-
- if (mRecvOff) {
- mRecvOff->AddToOffer(&GetOffer());
- }
+ mSendOff.AddToOffer(mSsrcGenerator, &GetOffer());
+ mRecvOff.AddToOffer(mSsrcGenerator, &GetOffer());
}
void CreateAnswer()
{
- if (mSendAns && GetOffer().IsReceiving()) {
- mSendAns->AddToAnswer(GetOffer(), &GetAnswer());
- }
+ mRecvAns.UpdateRecvTrack(*mOffer, GetOffer());
- if (mRecvAns && GetOffer().IsSending()) {
- mRecvAns->AddToAnswer(GetOffer(), &GetAnswer());
- }
+ mSendAns.AddToAnswer(GetOffer(), mSsrcGenerator, &GetAnswer());
+ mRecvAns.AddToAnswer(GetOffer(), mSsrcGenerator, &GetAnswer());
}
void Negotiate()
{
std::cerr << "Offer SDP: " << std::endl;
mOffer->Serialize(std::cerr);
std::cerr << "Answer SDP: " << std::endl;
mAnswer->Serialize(std::cerr);
- if (mSendAns && GetAnswer().IsSending()) {
- mSendAns->Negotiate(GetAnswer(), GetOffer());
+ mRecvOff.UpdateRecvTrack(*mAnswer, GetAnswer());
+
+ if (GetAnswer().IsSending()) {
+ mSendAns.Negotiate(GetAnswer(), GetOffer());
}
- if (mRecvAns && GetAnswer().IsReceiving()) {
- mRecvAns->Negotiate(GetAnswer(), GetOffer());
+ if (GetAnswer().IsReceiving()) {
+ mRecvAns.Negotiate(GetAnswer(), GetOffer());
}
- if (mSendOff && GetAnswer().IsReceiving()) {
- mSendOff->Negotiate(GetAnswer(), GetAnswer());
+ if (GetAnswer().IsReceiving()) {
+ mSendOff.Negotiate(GetAnswer(), GetAnswer());
}
- if (mRecvOff && GetAnswer().IsSending()) {
- mRecvOff->Negotiate(GetAnswer(), GetAnswer());
+ if (GetAnswer().IsSending()) {
+ mRecvOff.Negotiate(GetAnswer(), GetAnswer());
}
}
void OfferAnswer()
{
CreateOffer();
CreateAnswer();
Negotiate();
@@ -204,32 +220,30 @@ class JsepTrackTest : public ::testing::
return track->GetNegotiatedDetails()->GetEncodingCount();
}
// TODO: Look into writing a macro that wraps an ASSERT_ and returns false
// if it fails (probably requires writing a bool-returning function that
// takes a void-returning lambda with a bool outparam, which will in turn
// invokes the ASSERT_)
static void CheckEncodingCount(size_t expected,
- const RefPtr<JsepTrack>& send,
- const RefPtr<JsepTrack>& recv)
+ const JsepTrack& send,
+ const JsepTrack& recv)
{
if (expected) {
- ASSERT_TRUE(!!send);
- ASSERT_TRUE(send->GetNegotiatedDetails());
- ASSERT_TRUE(!!recv);
- ASSERT_TRUE(recv->GetNegotiatedDetails());
+ ASSERT_TRUE(send.GetNegotiatedDetails());
+ ASSERT_TRUE(recv.GetNegotiatedDetails());
}
- if (send && send->GetNegotiatedDetails()) {
- ASSERT_EQ(expected, send->GetNegotiatedDetails()->GetEncodingCount());
+ if (!send.GetTrackId().empty() && send.GetNegotiatedDetails()) {
+ ASSERT_EQ(expected, send.GetNegotiatedDetails()->GetEncodingCount());
}
- if (recv && recv->GetNegotiatedDetails()) {
- ASSERT_EQ(expected, recv->GetNegotiatedDetails()->GetEncodingCount());
+ if (!recv.GetTrackId().empty() && recv.GetNegotiatedDetails()) {
+ ASSERT_EQ(expected, recv.GetNegotiatedDetails()->GetEncodingCount());
}
}
void CheckOffEncodingCount(size_t expected) const
{
CheckEncodingCount(expected, mSendOff, mRecvAns);
}
@@ -309,16 +323,17 @@ class JsepTrackTest : public ::testing::
void SanityCheckCodecs(const JsepCodecDescription& a,
const JsepCodecDescription& b) const
{
ASSERT_EQ(a.mType, b.mType);
if (a.mType != SdpMediaSection::kApplication) {
ASSERT_EQ(a.mDefaultPt, b.mDefaultPt);
}
+ std::cerr << a.mName << " vs " << b.mName << std::endl;
ASSERT_EQ(a.mName, b.mName);
ASSERT_EQ(a.mClock, b.mClock);
ASSERT_EQ(a.mChannels, b.mChannels);
ASSERT_NE(a.mDirection, b.mDirection);
// These constraints are for fmtp and rid, which _are_ signaled
ASSERT_EQ(a.mConstraints, b.mConstraints);
if (a.mType == SdpMediaSection::kVideo) {
@@ -360,48 +375,45 @@ class JsepTrackTest : public ::testing::
if (!a.GetNegotiatedDetails()) {
ASSERT_FALSE(!!b.GetNegotiatedDetails());
return;
}
ASSERT_TRUE(!!a.GetNegotiatedDetails());
ASSERT_TRUE(!!b.GetNegotiatedDetails());
ASSERT_EQ(a.GetMediaType(), b.GetMediaType());
- ASSERT_EQ(a.GetStreamId(), b.GetStreamId());
+ ASSERT_EQ(a.GetStreamIds(), b.GetStreamIds());
ASSERT_EQ(a.GetTrackId(), b.GetTrackId());
ASSERT_EQ(a.GetCNAME(), b.GetCNAME());
ASSERT_NE(a.GetDirection(), b.GetDirection());
ASSERT_EQ(a.GetSsrcs().size(), b.GetSsrcs().size());
for (size_t i = 0; i < a.GetSsrcs().size(); ++i) {
ASSERT_EQ(a.GetSsrcs()[i], b.GetSsrcs()[i]);
}
SanityCheckNegotiatedDetails(*a.GetNegotiatedDetails(),
*b.GetNegotiatedDetails());
}
void SanityCheck() const
{
- if (mSendOff && mRecvAns) {
- SanityCheckTracks(*mSendOff, *mRecvAns);
- }
- if (mRecvOff && mSendAns) {
- SanityCheckTracks(*mRecvOff, *mSendAns);
- }
+ SanityCheckTracks(mSendOff, mRecvAns);
+ SanityCheckTracks(mRecvOff, mSendAns);
}
protected:
- RefPtr<JsepTrack> mSendOff;
- RefPtr<JsepTrack> mRecvOff;
- RefPtr<JsepTrack> mSendAns;
- RefPtr<JsepTrack> mRecvAns;
+ JsepTrack mSendOff;
+ JsepTrack mRecvOff;
+ JsepTrack mSendAns;
+ JsepTrack mRecvAns;
PtrVector<JsepCodecDescription> mOffCodecs;
PtrVector<JsepCodecDescription> mAnsCodecs;
UniquePtr<Sdp> mOffer;
UniquePtr<Sdp> mAnswer;
+ SsrcGenerator mSsrcGenerator;
};
TEST_F(JsepTrackTest, CreateDestroy)
{
Init(SdpMediaSection::kAudio);
}
TEST_F(JsepTrackTest, AudioNegotiation)
@@ -440,30 +452,28 @@ private:
};
TEST_F(JsepTrackTest, CheckForMismatchedAudioCodecAndVideoTrack)
{
PtrVector<JsepCodecDescription> offerCodecs;
// make codecs including telephone-event (an audio codec)
offerCodecs.values = MakeCodecs(false, false, true);
- RefPtr<JsepTrack> videoTrack = new JsepTrack(SdpMediaSection::kVideo,
- "stream_id",
- "track_id",
- sdp::kSend);
+ JsepTrack videoTrack(SdpMediaSection::kVideo, sdp::kSend);
+ videoTrack.UpdateTrack(std::vector<std::string>(1, "stream_id"), "track_id");
// populate codecs and then make sure we don't have any audio codecs
// in the video track
- videoTrack->PopulateCodecs(offerCodecs.values);
+ videoTrack.PopulateCodecs(offerCodecs.values);
bool found = false;
- videoTrack->ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+ videoTrack.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
ASSERT_FALSE(found);
found = false;
- videoTrack->ForEachCodec(CheckForCodecType(SdpMediaSection::kVideo, &found));
+ videoTrack.ForEachCodec(CheckForCodecType(SdpMediaSection::kVideo, &found));
ASSERT_TRUE(found); // for sanity, make sure we did find video codecs
}
TEST_F(JsepTrackTest, CheckVideoTrackWithHackedDtmfSdp)
{
Init(SdpMediaSection::kVideo);
CreateOffer();
// make sure we don't find sdp containing telephone-event in video track
@@ -486,31 +496,26 @@ TEST_F(JsepTrackTest, CheckVideoTrackWit
std::string::npos);
Negotiate();
SanityCheck();
CheckOffEncodingCount(1);
CheckAnsEncodingCount(1);
- ASSERT_TRUE(mSendOff.get());
- ASSERT_TRUE(mRecvOff.get());
- ASSERT_TRUE(mSendAns.get());
- ASSERT_TRUE(mRecvAns.get());
-
// make sure we still don't find any audio codecs in the video track after
// hacking the sdp
bool found = false;
- mSendOff->ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+ mSendOff.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
ASSERT_FALSE(found);
- mRecvOff->ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+ mRecvOff.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
ASSERT_FALSE(found);
- mSendAns->ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+ mSendAns.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
ASSERT_FALSE(found);
- mRecvAns->ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+ mRecvAns.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
ASSERT_FALSE(found);
}
TEST_F(JsepTrackTest, AudioNegotiationOffererDtmf)
{
mOffCodecs.values = MakeCodecs(false, false, true);
mAnsCodecs.values = MakeCodecs(false, false, false);
@@ -525,23 +530,23 @@ TEST_F(JsepTrackTest, AudioNegotiationOf
std::string::npos);
ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
std::string::npos);
ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
const JsepAudioCodecDescription* track = nullptr;
- ASSERT_TRUE((track = GetAudioCodec(*mSendOff)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvOff)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mSendAns)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvAns)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns)));
ASSERT_EQ("1", track->mDefaultPt);
}
TEST_F(JsepTrackTest, AudioNegotiationAnswererDtmf)
{
mOffCodecs.values = MakeCodecs(false, false, false);
mAnsCodecs.values = MakeCodecs(false, false, true);
@@ -556,23 +561,23 @@ TEST_F(JsepTrackTest, AudioNegotiationAn
std::string::npos);
ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
std::string::npos);
ASSERT_EQ(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
const JsepAudioCodecDescription* track = nullptr;
- ASSERT_TRUE((track = GetAudioCodec(*mSendOff)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvOff)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mSendAns)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvAns)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns)));
ASSERT_EQ("1", track->mDefaultPt);
}
TEST_F(JsepTrackTest, AudioNegotiationOffererAnswererDtmf)
{
mOffCodecs.values = MakeCodecs(false, false, true);
mAnsCodecs.values = MakeCodecs(false, false, true);
@@ -587,32 +592,32 @@ TEST_F(JsepTrackTest, AudioNegotiationOf
std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
std::string::npos);
ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
const JsepAudioCodecDescription* track = nullptr;
- ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
}
TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererNoFmtpAnswererFmtp)
{
mOffCodecs.values = MakeCodecs(false, false, true);
mAnsCodecs.values = MakeCodecs(false, false, true);
@@ -634,32 +639,32 @@ TEST_F(JsepTrackTest, AudioNegotiationDt
std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
std::string::npos);
ASSERT_EQ(mOffer->ToString().find("a=fmtp:101"), std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
const JsepAudioCodecDescription* track = nullptr;
- ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
}
TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererFmtpAnswererNoFmtp)
{
mOffCodecs.values = MakeCodecs(false, false, true);
mAnsCodecs.values = MakeCodecs(false, false, true);
@@ -681,32 +686,32 @@ TEST_F(JsepTrackTest, AudioNegotiationDt
std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
std::string::npos);
ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
const JsepAudioCodecDescription* track = nullptr;
- ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
}
TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererNoFmtpAnswererNoFmtp)
{
mOffCodecs.values = MakeCodecs(false, false, true);
mAnsCodecs.values = MakeCodecs(false, false, true);
@@ -729,32 +734,32 @@ TEST_F(JsepTrackTest, AudioNegotiationDt
std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
std::string::npos);
ASSERT_EQ(mOffer->ToString().find("a=fmtp:101"), std::string::npos);
ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
const JsepAudioCodecDescription* track = nullptr;
- ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2)));
ASSERT_EQ("1", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
- ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2, 1)));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2, 1)));
ASSERT_EQ("101", track->mDefaultPt);
}
TEST_F(JsepTrackTest, VideoNegotationOffererFEC)
{
mOffCodecs.values = MakeCodecs(true);
mAnsCodecs.values = MakeCodecs(false);
@@ -769,23 +774,23 @@ TEST_F(JsepTrackTest, VideoNegotationOff
ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
ASSERT_EQ(mAnswer->ToString().find("a=fmtp:122"), std::string::npos);
const JsepVideoCodecDescription* track = nullptr;
- ASSERT_TRUE((track = GetVideoCodec(*mSendOff)));
+ ASSERT_TRUE((track = GetVideoCodec(mSendOff)));
ASSERT_EQ("120", track->mDefaultPt);
- ASSERT_TRUE((track = GetVideoCodec(*mRecvOff)));
+ ASSERT_TRUE((track = GetVideoCodec(mRecvOff)));
ASSERT_EQ("120", track->mDefaultPt);
- ASSERT_TRUE((track = GetVideoCodec(*mSendAns)));
+ ASSERT_TRUE((track = GetVideoCodec(mSendAns)));
ASSERT_EQ("120", track->mDefaultPt);
- ASSERT_TRUE((track = GetVideoCodec(*mRecvAns)));
+ ASSERT_TRUE((track = GetVideoCodec(mRecvAns)));
ASSERT_EQ("120", track->mDefaultPt);
}
TEST_F(JsepTrackTest, VideoNegotationAnswererFEC)
{
mOffCodecs.values = MakeCodecs(false);
mAnsCodecs.values = MakeCodecs(true);
@@ -800,23 +805,23 @@ TEST_F(JsepTrackTest, VideoNegotationAns
ASSERT_EQ(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
ASSERT_EQ(mOffer->ToString().find("a=fmtp:122"), std::string::npos);
ASSERT_EQ(mAnswer->ToString().find("a=fmtp:122"), std::string::npos);
const JsepVideoCodecDescription* track = nullptr;
- ASSERT_TRUE((track = GetVideoCodec(*mSendOff)));
+ ASSERT_TRUE((track = GetVideoCodec(mSendOff)));
ASSERT_EQ("120", track->mDefaultPt);
- ASSERT_TRUE((track = GetVideoCodec(*mRecvOff)));
+ ASSERT_TRUE((track = GetVideoCodec(mRecvOff)));
ASSERT_EQ("120", track->mDefaultPt);
- ASSERT_TRUE((track = GetVideoCodec(*mSendAns)));
+ ASSERT_TRUE((track = GetVideoCodec(mSendAns)));
ASSERT_EQ("120", track->mDefaultPt);
- ASSERT_TRUE((track = GetVideoCodec(*mRecvAns)));
+ ASSERT_TRUE((track = GetVideoCodec(mRecvAns)));
ASSERT_EQ("120", track->mDefaultPt);
}
TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFEC)
{
mOffCodecs.values = MakeCodecs(true);
mAnsCodecs.values = MakeCodecs(true);
@@ -831,23 +836,23 @@ TEST_F(JsepTrackTest, VideoNegotationOff
ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
const JsepVideoCodecDescription* track = nullptr;
- ASSERT_TRUE((track = GetVideoCodec(*mSendOff, 4)));
+ ASSERT_TRUE((track = GetVideoCodec(mSendOff, 4)));
ASSERT_EQ("120", track->mDefaultPt);
- ASSERT_TRUE((track = GetVideoCodec(*mRecvOff, 4)));
+ ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 4)));
ASSERT_EQ("120", track->mDefaultPt);
- ASSERT_TRUE((track = GetVideoCodec(*mSendAns, 4)));
+ ASSERT_TRUE((track = GetVideoCodec(mSendAns, 4)));
ASSERT_EQ("120", track->mDefaultPt);
- ASSERT_TRUE((track = GetVideoCodec(*mRecvAns, 4)));
+ ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 4)));
ASSERT_EQ("120", track->mDefaultPt);
}
TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECPreferred)
{
mOffCodecs.values = MakeCodecs(true, true);
mAnsCodecs.values = MakeCodecs(true);
@@ -862,23 +867,23 @@ TEST_F(JsepTrackTest, VideoNegotationOff
ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
const JsepVideoCodecDescription* track = nullptr;
- ASSERT_TRUE((track = GetVideoCodec(*mSendOff, 4)));
+ ASSERT_TRUE((track = GetVideoCodec(mSendOff, 4)));
ASSERT_EQ("122", track->mDefaultPt);
- ASSERT_TRUE((track = GetVideoCodec(*mRecvOff, 4)));
+ ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 4)));
ASSERT_EQ("122", track->mDefaultPt);
- ASSERT_TRUE((track = GetVideoCodec(*mSendAns, 4)));
+ ASSERT_TRUE((track = GetVideoCodec(mSendAns, 4)));
ASSERT_EQ("122", track->mDefaultPt);
- ASSERT_TRUE((track = GetVideoCodec(*mRecvAns, 4)));
+ ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 4)));
ASSERT_EQ("122", track->mDefaultPt);
}
// Make sure we only put the right things in the fmtp:122 120/.... line
TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECMismatch)
{
mOffCodecs.values = MakeCodecs(true, true);
mAnsCodecs.values = MakeCodecs(true);
@@ -897,23 +902,23 @@ TEST_F(JsepTrackTest, VideoNegotationOff
ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/123"), std::string::npos);
const JsepVideoCodecDescription* track = nullptr;
- ASSERT_TRUE((track = GetVideoCodec(*mSendOff, 3)));
+ ASSERT_TRUE((track = GetVideoCodec(mSendOff, 3)));
ASSERT_EQ("122", track->mDefaultPt);
- ASSERT_TRUE((track = GetVideoCodec(*mRecvOff, 3)));
+ ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 3)));
ASSERT_EQ("122", track->mDefaultPt);
- ASSERT_TRUE((track = GetVideoCodec(*mSendAns, 3)));
+ ASSERT_TRUE((track = GetVideoCodec(mSendAns, 3)));
ASSERT_EQ("122", track->mDefaultPt);
- ASSERT_TRUE((track = GetVideoCodec(*mRecvAns, 3)));
+ ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 3)));
ASSERT_EQ("122", track->mDefaultPt);
}
TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECZeroVP9Codec)
{
mOffCodecs.values = MakeCodecs(true);
JsepVideoCodecDescription* vp9 =
new JsepVideoCodecDescription("0", "VP9", 90000);
@@ -959,21 +964,21 @@ TEST_F(JsepTrackTest, VideoNegotiationOf
// make sure REMB is on offer and not on answer
ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 goog-remb"),
std::string::npos);
ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 goog-remb"),
std::string::npos);
CheckOffEncodingCount(1);
CheckAnsEncodingCount(1);
- CheckOtherFbsSize(*mSendOff, 0);
- CheckOtherFbsSize(*mRecvAns, 0);
+ CheckOtherFbsSize(mSendOff, 0);
+ CheckOtherFbsSize(mRecvAns, 0);
- CheckOtherFbsSize(*mSendAns, 0);
- CheckOtherFbsSize(*mRecvOff, 0);
+ CheckOtherFbsSize(mSendAns, 0);
+ CheckOtherFbsSize(mRecvOff, 0);
}
TEST_F(JsepTrackTest, VideoNegotiationAnswerRemb)
{
InitCodecs();
// enable remb on the answer codecs
((JsepVideoCodecDescription*)mAnsCodecs.values[2])->EnableRemb();
InitTracks(SdpMediaSection::kVideo);
@@ -983,21 +988,21 @@ TEST_F(JsepTrackTest, VideoNegotiationAn
// make sure REMB is not on offer and not on answer
ASSERT_EQ(mOffer->ToString().find("a=rtcp-fb:120 goog-remb"),
std::string::npos);
ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 goog-remb"),
std::string::npos);
CheckOffEncodingCount(1);
CheckAnsEncodingCount(1);
- CheckOtherFbsSize(*mSendOff, 0);
- CheckOtherFbsSize(*mRecvAns, 0);
+ CheckOtherFbsSize(mSendOff, 0);
+ CheckOtherFbsSize(mRecvAns, 0);
- CheckOtherFbsSize(*mSendAns, 0);
- CheckOtherFbsSize(*mRecvOff, 0);
+ CheckOtherFbsSize(mSendAns, 0);
+ CheckOtherFbsSize(mRecvOff, 0);
}
TEST_F(JsepTrackTest, VideoNegotiationOfferAnswerRemb)
{
InitCodecs();
// enable remb on the offer and answer codecs
((JsepVideoCodecDescription*)mOffCodecs.values[2])->EnableRemb();
((JsepVideoCodecDescription*)mAnsCodecs.values[2])->EnableRemb();
@@ -1008,96 +1013,98 @@ TEST_F(JsepTrackTest, VideoNegotiationOf
// make sure REMB is on offer and on answer
ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 goog-remb"),
std::string::npos);
ASSERT_NE(mAnswer->ToString().find("a=rtcp-fb:120 goog-remb"),
std::string::npos);
CheckOffEncodingCount(1);
CheckAnsEncodingCount(1);
- CheckOtherFbsSize(*mSendOff, 1);
- CheckOtherFbsSize(*mRecvAns, 1);
- CheckOtherFbExists(*mSendOff, SdpRtcpFbAttributeList::kRemb);
- CheckOtherFbExists(*mRecvAns, SdpRtcpFbAttributeList::kRemb);
+ CheckOtherFbsSize(mSendOff, 1);
+ CheckOtherFbsSize(mRecvAns, 1);
+ CheckOtherFbExists(mSendOff, SdpRtcpFbAttributeList::kRemb);
+ CheckOtherFbExists(mRecvAns, SdpRtcpFbAttributeList::kRemb);
- CheckOtherFbsSize(*mSendAns, 1);
- CheckOtherFbsSize(*mRecvOff, 1);
- CheckOtherFbExists(*mSendAns, SdpRtcpFbAttributeList::kRemb);
- CheckOtherFbExists(*mRecvOff, SdpRtcpFbAttributeList::kRemb);
+ CheckOtherFbsSize(mSendAns, 1);
+ CheckOtherFbsSize(mRecvOff, 1);
+ CheckOtherFbExists(mSendAns, SdpRtcpFbAttributeList::kRemb);
+ CheckOtherFbExists(mRecvOff, SdpRtcpFbAttributeList::kRemb);
}
TEST_F(JsepTrackTest, AudioOffSendonlyAnsRecvonly)
{
Init(SdpMediaSection::kAudio);
- mRecvOff = nullptr;
- mSendAns = nullptr;
+ GetOffer().SetDirection(SdpDirectionAttribute::kSendonly);
+ GetAnswer().SetDirection(SdpDirectionAttribute::kRecvonly);
OfferAnswer();
CheckOffEncodingCount(1);
CheckAnsEncodingCount(0);
}
TEST_F(JsepTrackTest, VideoOffSendonlyAnsRecvonly)
{
Init(SdpMediaSection::kVideo);
- mRecvOff = nullptr;
- mSendAns = nullptr;
+ GetOffer().SetDirection(SdpDirectionAttribute::kSendonly);
+ GetAnswer().SetDirection(SdpDirectionAttribute::kRecvonly);
OfferAnswer();
CheckOffEncodingCount(1);
CheckAnsEncodingCount(0);
}
TEST_F(JsepTrackTest, AudioOffSendrecvAnsRecvonly)
{
Init(SdpMediaSection::kAudio);
- mSendAns = nullptr;
+ GetAnswer().SetDirection(SdpDirectionAttribute::kRecvonly);
OfferAnswer();
CheckOffEncodingCount(1);
CheckAnsEncodingCount(0);
}
TEST_F(JsepTrackTest, VideoOffSendrecvAnsRecvonly)
{
Init(SdpMediaSection::kVideo);
- mSendAns = nullptr;
+ GetAnswer().SetDirection(SdpDirectionAttribute::kRecvonly);
OfferAnswer();
CheckOffEncodingCount(1);
CheckAnsEncodingCount(0);
}
-TEST_F(JsepTrackTest, AudioOffRecvonlyAnsSendrecv)
+TEST_F(JsepTrackTest, AudioOffRecvonlyAnsSendonly)
{
Init(SdpMediaSection::kAudio);
- mSendOff = nullptr;
+ GetOffer().SetDirection(SdpDirectionAttribute::kRecvonly);
+ GetAnswer().SetDirection(SdpDirectionAttribute::kSendonly);
OfferAnswer();
CheckOffEncodingCount(0);
CheckAnsEncodingCount(1);
}
-TEST_F(JsepTrackTest, VideoOffRecvonlyAnsSendrecv)
+TEST_F(JsepTrackTest, VideoOffRecvonlyAnsSendonly)
{
Init(SdpMediaSection::kVideo);
- mSendOff = nullptr;
+ GetOffer().SetDirection(SdpDirectionAttribute::kRecvonly);
+ GetAnswer().SetDirection(SdpDirectionAttribute::kSendonly);
OfferAnswer();
CheckOffEncodingCount(0);
CheckAnsEncodingCount(1);
}
TEST_F(JsepTrackTest, AudioOffSendrecvAnsSendonly)
{
Init(SdpMediaSection::kAudio);
- mRecvAns = nullptr;
+ GetAnswer().SetDirection(SdpDirectionAttribute::kSendonly);
OfferAnswer();
CheckOffEncodingCount(0);
CheckAnsEncodingCount(1);
}
TEST_F(JsepTrackTest, VideoOffSendrecvAnsSendonly)
{
Init(SdpMediaSection::kVideo);
- mRecvAns = nullptr;
+ GetAnswer().SetDirection(SdpDirectionAttribute::kSendonly);
OfferAnswer();
CheckOffEncodingCount(0);
CheckAnsEncodingCount(1);
}
TEST_F(JsepTrackTest, DataChannelDraft05)
{
Init(SdpMediaSection::kApplication);
@@ -1156,25 +1163,25 @@ TEST_F(JsepTrackTest, DataChannelDraft21
{
mOffCodecs.values = MakeCodecs(false, false, false);
mAnsCodecs.values = MakeCodecs(false, false, false);
InitTracks(SdpMediaSection::kApplication);
mOffer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, "")));
mOffer->AddMediaSection(
SdpMediaSection::kApplication,
- SdpDirectionAttribute::kInactive,
+ SdpDirectionAttribute::kSendrecv,
0,
SdpMediaSection::kUdpDtlsSctp,
sdp::kIPv4,
"0.0.0.0");
mAnswer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, "")));
mAnswer->AddMediaSection(
SdpMediaSection::kApplication,
- SdpDirectionAttribute::kInactive,
+ SdpDirectionAttribute::kSendrecv,
0,
SdpMediaSection::kUdpDtlsSctp,
sdp::kIPv4,
"0.0.0.0");
OfferAnswer();
CheckOffEncodingCount(1);
CheckAnsEncodingCount(1);
@@ -1197,84 +1204,86 @@ MakeConstraints(const std::string& rid,
}
TEST_F(JsepTrackTest, SimulcastRejected)
{
Init(SdpMediaSection::kVideo);
std::vector<JsepTrack::JsConstraints> constraints;
constraints.push_back(MakeConstraints("foo", 40000));
constraints.push_back(MakeConstraints("bar", 10000));
- mSendOff->SetJsConstraints(constraints);
+ mSendOff.SetJsConstraints(constraints);
OfferAnswer();
CheckOffEncodingCount(1);
CheckAnsEncodingCount(1);
}
TEST_F(JsepTrackTest, SimulcastPrevented)
{
Init(SdpMediaSection::kVideo);
std::vector<JsepTrack::JsConstraints> constraints;
constraints.push_back(MakeConstraints("foo", 40000));
constraints.push_back(MakeConstraints("bar", 10000));
- mSendAns->SetJsConstraints(constraints);
+ mSendAns.SetJsConstraints(constraints);
OfferAnswer();
CheckOffEncodingCount(1);
CheckAnsEncodingCount(1);
}
TEST_F(JsepTrackTest, SimulcastOfferer)
{
Init(SdpMediaSection::kVideo);
std::vector<JsepTrack::JsConstraints> constraints;
constraints.push_back(MakeConstraints("foo", 40000));
constraints.push_back(MakeConstraints("bar", 10000));
- mSendOff->SetJsConstraints(constraints);
+ mSendOff.SetJsConstraints(constraints);
CreateOffer();
CreateAnswer();
// Add simulcast/rid to answer
- JsepTrack::AddToMsection(constraints, sdp::kRecv, &GetAnswer());
+ mRecvAns.AddToMsection(
+ constraints, sdp::kRecv, mSsrcGenerator, &GetAnswer());
Negotiate();
- ASSERT_TRUE(mSendOff->GetNegotiatedDetails());
- ASSERT_EQ(2U, mSendOff->GetNegotiatedDetails()->GetEncodingCount());
- ASSERT_EQ("foo", mSendOff->GetNegotiatedDetails()->GetEncoding(0).mRid);
+ ASSERT_TRUE(mSendOff.GetNegotiatedDetails());
+ ASSERT_EQ(2U, mSendOff.GetNegotiatedDetails()->GetEncodingCount());
+ ASSERT_EQ("foo", mSendOff.GetNegotiatedDetails()->GetEncoding(0).mRid);
ASSERT_EQ(40000U,
- mSendOff->GetNegotiatedDetails()->GetEncoding(0).mConstraints.maxBr);
- ASSERT_EQ("bar", mSendOff->GetNegotiatedDetails()->GetEncoding(1).mRid);
+ mSendOff.GetNegotiatedDetails()->GetEncoding(0).mConstraints.maxBr);
+ ASSERT_EQ("bar", mSendOff.GetNegotiatedDetails()->GetEncoding(1).mRid);
ASSERT_EQ(10000U,
- mSendOff->GetNegotiatedDetails()->GetEncoding(1).mConstraints.maxBr);
+ mSendOff.GetNegotiatedDetails()->GetEncoding(1).mConstraints.maxBr);
ASSERT_NE(std::string::npos,
mOffer->ToString().find("a=simulcast: send rid=foo;bar"));
ASSERT_NE(std::string::npos,
mAnswer->ToString().find("a=simulcast: recv rid=foo;bar"));
ASSERT_NE(std::string::npos, mOffer->ToString().find("a=rid:foo send"));
ASSERT_NE(std::string::npos, mOffer->ToString().find("a=rid:bar send"));
ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=rid:foo recv"));
ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=rid:bar recv"));
}
TEST_F(JsepTrackTest, SimulcastAnswerer)
{
Init(SdpMediaSection::kVideo);
std::vector<JsepTrack::JsConstraints> constraints;
constraints.push_back(MakeConstraints("foo", 40000));
constraints.push_back(MakeConstraints("bar", 10000));
- mSendAns->SetJsConstraints(constraints);
+ mSendAns.SetJsConstraints(constraints);
CreateOffer();
// Add simulcast/rid to offer
- JsepTrack::AddToMsection(constraints, sdp::kRecv, &GetOffer());
+ mRecvOff.AddToMsection(
+ constraints, sdp::kRecv, mSsrcGenerator, &GetOffer());
CreateAnswer();
Negotiate();
- ASSERT_TRUE(mSendAns->GetNegotiatedDetails());
- ASSERT_EQ(2U, mSendAns->GetNegotiatedDetails()->GetEncodingCount());
- ASSERT_EQ("foo", mSendAns->GetNegotiatedDetails()->GetEncoding(0).mRid);
+ ASSERT_TRUE(mSendAns.GetNegotiatedDetails());
+ ASSERT_EQ(2U, mSendAns.GetNegotiatedDetails()->GetEncodingCount());
+ ASSERT_EQ("foo", mSendAns.GetNegotiatedDetails()->GetEncoding(0).mRid);
ASSERT_EQ(40000U,
- mSendAns->GetNegotiatedDetails()->GetEncoding(0).mConstraints.maxBr);
- ASSERT_EQ("bar", mSendAns->GetNegotiatedDetails()->GetEncoding(1).mRid);
+ mSendAns.GetNegotiatedDetails()->GetEncoding(0).mConstraints.maxBr);
+ ASSERT_EQ("bar", mSendAns.GetNegotiatedDetails()->GetEncoding(1).mRid);
ASSERT_EQ(10000U,
- mSendAns->GetNegotiatedDetails()->GetEncoding(1).mConstraints.maxBr);
+ mSendAns.GetNegotiatedDetails()->GetEncoding(1).mConstraints.maxBr);
ASSERT_NE(std::string::npos,
mOffer->ToString().find("a=simulcast: recv rid=foo;bar"));
ASSERT_NE(std::string::npos,
mAnswer->ToString().find("a=simulcast: send rid=foo;bar"));
ASSERT_NE(std::string::npos, mOffer->ToString().find("a=rid:foo recv"));
ASSERT_NE(std::string::npos, mOffer->ToString().find("a=rid:bar recv"));
ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=rid:foo send"));
ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=rid:bar send"));
@@ -1309,24 +1318,24 @@ TEST_F(JsepTrackTest, SimulcastAnswerer)
}; \
}
TEST_F(JsepTrackTest, DefaultOpusParameters)
{
Init(SdpMediaSection::kAudio);
OfferAnswer();
- VERIFY_OPUS_MAX_PLAYBACK_RATE(*mSendOff,
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(mSendOff,
SdpFmtpAttributeList::OpusParameters::kDefaultMaxPlaybackRate);
- VERIFY_OPUS_MAX_PLAYBACK_RATE(*mSendAns,
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(mSendAns,
SdpFmtpAttributeList::OpusParameters::kDefaultMaxPlaybackRate);
- VERIFY_OPUS_MAX_PLAYBACK_RATE(*mRecvOff, 0U);
- VERIFY_OPUS_FORCE_MONO(*mRecvOff, false);
- VERIFY_OPUS_MAX_PLAYBACK_RATE(*mRecvAns, 0U);
- VERIFY_OPUS_FORCE_MONO(*mRecvAns, false);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(mRecvOff, 0U);
+ VERIFY_OPUS_FORCE_MONO(mRecvOff, false);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(mRecvAns, 0U);
+ VERIFY_OPUS_FORCE_MONO(mRecvAns, false);
}
TEST_F(JsepTrackTest, NonDefaultOpusParameters)
{
InitCodecs();
for (auto& codec : mAnsCodecs.values) {
if (codec->mName == "opus") {
JsepAudioCodecDescription* audioCodec =
@@ -1334,20 +1343,20 @@ TEST_F(JsepTrackTest, NonDefaultOpusPara
audioCodec->mMaxPlaybackRate = 16000;
audioCodec->mForceMono = true;
}
}
InitTracks(SdpMediaSection::kAudio);
InitSdp(SdpMediaSection::kAudio);
OfferAnswer();
- VERIFY_OPUS_MAX_PLAYBACK_RATE(*mSendOff, 16000U);
- VERIFY_OPUS_FORCE_MONO(*mSendOff, true);
- VERIFY_OPUS_MAX_PLAYBACK_RATE(*mSendAns,
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(mSendOff, 16000U);
+ VERIFY_OPUS_FORCE_MONO(mSendOff, true);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(mSendAns,
SdpFmtpAttributeList::OpusParameters::kDefaultMaxPlaybackRate);
- VERIFY_OPUS_FORCE_MONO(*mSendAns, false);
- VERIFY_OPUS_MAX_PLAYBACK_RATE(*mRecvOff, 0U);
- VERIFY_OPUS_FORCE_MONO(*mRecvOff, false);
- VERIFY_OPUS_MAX_PLAYBACK_RATE(*mRecvAns, 16000U);
- VERIFY_OPUS_FORCE_MONO(*mRecvAns, true);
+ VERIFY_OPUS_FORCE_MONO(mSendAns, false);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(mRecvOff, 0U);
+ VERIFY_OPUS_FORCE_MONO(mRecvOff, false);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(mRecvAns, 16000U);
+ VERIFY_OPUS_FORCE_MONO(mRecvAns, true);
}
} // namespace mozilla
--- a/media/webrtc/signaling/gtest/mediapipeline_unittest.cpp
+++ b/media/webrtc/signaling/gtest/mediapipeline_unittest.cpp
@@ -238,17 +238,18 @@ class TransportInfo {
TransportLayerDtls *dtls_;
};
class TestAgent {
public:
TestAgent() :
audio_config_(109, "opus", 48000, 960, 2, 64000, false),
audio_conduit_(mozilla::AudioSessionConduit::Create()),
- audio_pipeline_() {
+ audio_pipeline_(),
+ use_bundle_(false) {
}
static void ConnectRtp(TestAgent *client, TestAgent *server) {
TransportInfo::InitAndConnect(client->audio_rtp_transport_,
server->audio_rtp_transport_);
}
static void ConnectRtcp(TestAgent *client, TestAgent *server) {
@@ -256,22 +257,17 @@ class TestAgent {
server->audio_rtcp_transport_);
}
static void ConnectBundle(TestAgent *client, TestAgent *server) {
TransportInfo::InitAndConnect(client->bundle_transport_,
server->bundle_transport_);
}
- virtual void CreatePipelines_s(bool aIsRtcpMux) = 0;
-
- void Start() {
- MOZ_MTLOG(ML_DEBUG, "Starting");
- audio_pipeline_->Init();
- }
+ virtual void CreatePipeline(bool aIsRtcpMux) = 0;
void StopInt() {
}
void Stop() {
MOZ_MTLOG(ML_DEBUG, "Stopping");
if (audio_pipeline_)
@@ -324,117 +320,125 @@ class TestAgent {
int GetAudioRtcpCountSent() {
return audio_pipeline_->rtcp_packets_sent();
}
int GetAudioRtcpCountReceived() {
return audio_pipeline_->rtcp_packets_received();
}
+
+ void SetUsingBundle(bool use_bundle) {
+ use_bundle_ = use_bundle;
+ }
+
protected:
mozilla::AudioCodecConfig audio_config_;
RefPtr<mozilla::MediaSessionConduit> audio_conduit_;
RefPtr<FakeAudioStreamTrack> audio_stream_track_;
// TODO(bcampen@mozilla.com): Right now this does not let us test RTCP in
// both directions; only the sender's RTCP is sent, but the receiver should
// be sending it too.
RefPtr<mozilla::MediaPipeline> audio_pipeline_;
TransportInfo audio_rtp_transport_;
TransportInfo audio_rtcp_transport_;
TransportInfo bundle_transport_;
+ bool use_bundle_;
};
class TestAgentSend : public TestAgent {
public:
- TestAgentSend() : use_bundle_(false) {
+ TestAgentSend() {
mozilla::MediaConduitErrorCode err =
static_cast<mozilla::AudioSessionConduit *>(audio_conduit_.get())->
ConfigureSendMediaCodec(&audio_config_);
EXPECT_EQ(mozilla::kMediaConduitNoError, err);
audio_stream_track_ = new FakeAudioStreamTrack();
}
- virtual void CreatePipelines_s(bool aIsRtcpMux) {
+ virtual void CreatePipeline(bool aIsRtcpMux) {
std::string test_pc("PC");
if (aIsRtcpMux) {
ASSERT_FALSE(audio_rtcp_transport_.flow_);
}
+ RefPtr<MediaPipelineTransmit> audio_pipeline =
+ new mozilla::MediaPipelineTransmit(
+ test_pc,
+ nullptr,
+ test_utils->sts_target(),
+ false,
+ audio_stream_track_.get(),
+ audio_conduit_);
+
+ audio_pipeline->AttachToTrack();
+
+ audio_pipeline_ = audio_pipeline;
+
RefPtr<TransportFlow> rtp(audio_rtp_transport_.flow_);
RefPtr<TransportFlow> rtcp(audio_rtcp_transport_.flow_);
if (use_bundle_) {
rtp = bundle_transport_.flow_;
rtcp = nullptr;
}
- audio_pipeline_ = new mozilla::MediaPipelineTransmit(
- test_pc,
- nullptr,
- test_utils->sts_target(),
- audio_stream_track_.get(),
- "audio_track_fake_uuid",
- 1,
- audio_conduit_,
- rtp,
- rtcp,
- nsAutoPtr<MediaPipelineFilter>());
+ audio_pipeline_->UpdateTransport_m(
+ rtp, rtcp, nsAutoPtr<MediaPipelineFilter>(nullptr));
}
-
- void SetUsingBundle(bool use_bundle) {
- use_bundle_ = use_bundle;
- }
-
- private:
- bool use_bundle_;
};
class TestAgentReceive : public TestAgent {
public:
TestAgentReceive() {
std::vector<mozilla::AudioCodecConfig *> codecs;
codecs.push_back(&audio_config_);
mozilla::MediaConduitErrorCode err =
static_cast<mozilla::AudioSessionConduit *>(audio_conduit_.get())->
ConfigureRecvMediaCodecs(codecs);
EXPECT_EQ(mozilla::kMediaConduitNoError, err);
}
- virtual void CreatePipelines_s(bool aIsRtcpMux) {
- std::string test_pc("PC");
+ virtual void CreatePipeline(bool aIsRtcpMux) {
+ std::string test_pc("PC");
if (aIsRtcpMux) {
ASSERT_FALSE(audio_rtcp_transport_.flow_);
}
audio_pipeline_ = new mozilla::MediaPipelineReceiveAudio(
test_pc,
nullptr,
test_utils->sts_target(),
- new FakeSourceMediaStream(), "audio_track_fake_uuid", 1, 1,
- static_cast<mozilla::AudioSessionConduit *>(audio_conduit_.get()),
- audio_rtp_transport_.flow_,
- audio_rtcp_transport_.flow_,
- bundle_filter_);
+ static_cast<mozilla::AudioSessionConduit *>(audio_conduit_.get()));
+
+ RefPtr<TransportFlow> rtp(audio_rtp_transport_.flow_);
+ RefPtr<TransportFlow> rtcp(audio_rtcp_transport_.flow_);
+
+ if (use_bundle_) {
+ rtp = bundle_transport_.flow_;
+ rtcp = nullptr;
+ }
+
+ audio_pipeline_->UpdateTransport_m(rtp, rtcp, bundle_filter_);
}
void SetBundleFilter(nsAutoPtr<MediaPipelineFilter> filter) {
bundle_filter_ = filter;
}
void UpdateFilter_s(
nsAutoPtr<MediaPipelineFilter> filter) {
- audio_pipeline_->UpdateTransport_s(1,
- audio_rtp_transport_.flow_,
+ audio_pipeline_->UpdateTransport_s(audio_rtp_transport_.flow_,
audio_rtcp_transport_.flow_,
filter);
}
private:
nsAutoPtr<MediaPipelineFilter> bundle_filter_;
};
@@ -489,26 +493,18 @@ class MediaPipelineTest : public ::testi
// make any sense.
ASSERT_FALSE(!aIsRtcpMux && bundle);
p2_.SetBundleFilter(initialFilter);
// Setup transport flows
InitTransports(aIsRtcpMux);
- mozilla::SyncRunnable::DispatchToThread(
- test_utils->sts_target(),
- WrapRunnable(&p1_, &TestAgent::CreatePipelines_s, aIsRtcpMux), NS_DISPATCH_SYNC);
-
- mozilla::SyncRunnable::DispatchToThread(
- test_utils->sts_target(),
- WrapRunnable(&p2_, &TestAgent::CreatePipelines_s, aIsRtcpMux), NS_DISPATCH_SYNC);
-
- p2_.Start();
- p1_.Start();
+ p1_.CreatePipeline(aIsRtcpMux);
+ p2_.CreatePipeline(aIsRtcpMux);
if (bundle) {
PR_Sleep(ms_until_filter_update);
// Leaving refinedFilter not set implies we want to just update with
// the other side's SSRC
if (!refinedFilter) {
refinedFilter = new MediaPipelineFilter;
--- a/media/webrtc/signaling/signaling.gyp
+++ b/media/webrtc/signaling/signaling.gyp
@@ -103,24 +103,25 @@
'./src/common/browser_logging/CSFLog.cpp',
'./src/common/browser_logging/CSFLog.h',
'./src/common/browser_logging/WebRtcLog.cpp',
'./src/common/browser_logging/WebRtcLog.h',
# Browser Logging
'./src/common/time_profiling/timecard.c',
'./src/common/time_profiling/timecard.h',
# PeerConnection
- './src/peerconnection/MediaPipelineFactory.cpp',
- './src/peerconnection/MediaPipelineFactory.h',
'./src/peerconnection/PeerConnectionCtx.cpp',
'./src/peerconnection/PeerConnectionCtx.h',
'./src/peerconnection/PeerConnectionImpl.cpp',
'./src/peerconnection/PeerConnectionImpl.h',
'./src/peerconnection/PeerConnectionMedia.cpp',
'./src/peerconnection/PeerConnectionMedia.h',
+ './src/peerconnection/RemoteTrackSource.h',
+ './src/peerconnection/TransceiverImpl.cpp',
+ './src/peerconnection/TransceiverImpl.h',
# Media pipeline
'./src/mediapipeline/MediaPipeline.h',
'./src/mediapipeline/MediaPipeline.cpp',
'./src/mediapipeline/MediaPipelineFilter.h',
'./src/mediapipeline/MediaPipelineFilter.cpp',
'./src/mediapipeline/RtpLogger.h',
'./src/mediapipeline/RtpLogger.cpp',
# SDP
@@ -162,17 +163,19 @@
# JSEP
'./src/jsep/JsepCodecDescription.h',
'./src/jsep/JsepSession.h',
'./src/jsep/JsepSessionImpl.cpp',
'./src/jsep/JsepSessionImpl.h',
'./src/jsep/JsepTrack.cpp',
'./src/jsep/JsepTrack.h',
'./src/jsep/JsepTrackEncoding.h',
- './src/jsep/JsepTransport.h'
+ './src/jsep/JsepTransport.h',
+ './src/jsep/SsrcGenerator.cpp',
+ './src/jsep/SsrcGenerator.h'
],
#
# DEFINES
#
'defines' : [
'LOG4CXX_STATIC',
@@ -226,18 +229,16 @@
'./src/media-conduit/WebrtcMediaCodecVP8VideoCodec.cpp',
],
'defines' : [
'MOZ_WEBRTC_MEDIACODEC',
],
}],
['(build_for_test==0) and (build_for_standalone==0)', {
'sources': [
- './src/peerconnection/MediaStreamList.cpp',
- './src/peerconnection/MediaStreamList.h',
'./src/peerconnection/WebrtcGlobalInformation.cpp',
'./src/peerconnection/WebrtcGlobalInformation.h',
],
}],
['build_for_test!=0', {
'include_dirs': [
'./test'
],
--- a/media/webrtc/signaling/src/jsep/JsepSession.h
+++ b/media/webrtc/signaling/src/jsep/JsepSession.h
@@ -112,67 +112,40 @@ public:
// manipulation (which will be unwieldy), or allowing functors to be injected
// that manipulate the data structure (still pretty unwieldy).
virtual std::vector<JsepCodecDescription*>& Codecs() = 0;
template <class UnaryFunction>
void ForEachCodec(UnaryFunction& function)
{
std::for_each(Codecs().begin(), Codecs().end(), function);
- for (RefPtr<JsepTrack>& track : GetLocalTracks()) {
- track->ForEachCodec(function);
- }
- for (RefPtr<JsepTrack>& track : GetRemoteTracks()) {
- track->ForEachCodec(function);
+ for (auto& transceiver : GetTransceivers()) {
+ transceiver->mSending.ForEachCodec(function);
+ transceiver->mReceiving.ForEachCodec(function);
}
}
template <class BinaryPredicate>
void SortCodecs(BinaryPredicate& sorter)
{
std::stable_sort(Codecs().begin(), Codecs().end(), sorter);
- for (RefPtr<JsepTrack>& track : GetLocalTracks()) {
- track->SortCodecs(sorter);
- }
- for (RefPtr<JsepTrack>& track : GetRemoteTracks()) {
- track->SortCodecs(sorter);
+ for (auto& transceiver : GetTransceivers()) {
+ transceiver->mSending.SortCodecs(sorter);
+ transceiver->mReceiving.SortCodecs(sorter);
}
}
- // Manage tracks. We take shared ownership of any track.
- virtual nsresult AddTrack(const RefPtr<JsepTrack>& track) = 0;
- virtual nsresult RemoveTrack(const std::string& streamId,
- const std::string& trackId) = 0;
- virtual nsresult ReplaceTrack(const std::string& oldStreamId,
- const std::string& oldTrackId,
- const std::string& newStreamId,
- const std::string& newTrackId) = 0;
- virtual nsresult SetParameters(
- const std::string& streamId,
- const std::string& trackId,
- const std::vector<JsepTrack::JsConstraints>& constraints) = 0;
+ // Helpful for firing events.
+ virtual std::vector<JsepTrack> GetRemoteTracksAdded() const = 0;
+ virtual std::vector<JsepTrack> GetRemoteTracksRemoved() const = 0;
- virtual nsresult GetParameters(
- const std::string& streamId,
- const std::string& trackId,
- std::vector<JsepTrack::JsConstraints>* outConstraints) = 0;
-
- virtual std::vector<RefPtr<JsepTrack>> GetLocalTracks() const = 0;
-
- virtual std::vector<RefPtr<JsepTrack>> GetRemoteTracks() const = 0;
-
- virtual std::vector<RefPtr<JsepTrack>> GetRemoteTracksAdded() const = 0;
-
- virtual std::vector<RefPtr<JsepTrack>> GetRemoteTracksRemoved() const = 0;
-
- // Access the negotiated track pairs.
- virtual std::vector<JsepTrackPair> GetNegotiatedTrackPairs() const = 0;
-
- // Access transports.
- virtual std::vector<RefPtr<JsepTransport>> GetTransports() const = 0;
+ virtual const std::vector<RefPtr<JsepTransceiver>>&
+ GetTransceivers() const = 0;
+ virtual std::vector<RefPtr<JsepTransceiver>>& GetTransceivers() = 0;
+ virtual nsresult AddTransceiver(RefPtr<JsepTransceiver> transceiver) = 0;
// Basic JSEP operations.
virtual nsresult CreateOffer(const JsepOfferOptions& options,
std::string* offer) = 0;
virtual nsresult CreateAnswer(const JsepAnswerOptions& options,
std::string* answer) = 0;
virtual std::string GetLocalDescription(JsepDescriptionPendingOrCurrent type)
const = 0;
@@ -213,39 +186,38 @@ public:
{
static const char* states[] = { "stable", "have-local-offer",
"have-remote-offer", "have-local-pranswer",
"have-remote-pranswer", "closed" };
return states[state];
}
- virtual bool AllLocalTracksAreAssigned() const = 0;
+ virtual bool CheckNegotiationNeeded() const = 0;
void
CountTracks(uint16_t (&receiving)[SdpMediaSection::kMediaTypes],
uint16_t (&sending)[SdpMediaSection::kMediaTypes]) const
{
- auto trackPairs = GetNegotiatedTrackPairs();
-
memset(receiving, 0, sizeof(receiving));
memset(sending, 0, sizeof(sending));
- for (auto& pair : trackPairs) {
- if (pair.mReceiving) {
- receiving[pair.mReceiving->GetMediaType()]++;
+ for (const auto& transceiver : GetTransceivers()) {
+ if (!transceiver->mReceiving.GetTrackId().empty()) {
+ receiving[transceiver->mReceiving.GetMediaType()]++;
}
- if (pair.mSending) {
- sending[pair.mSending->GetMediaType()]++;
+ if (!transceiver->mSending.GetTrackId().empty()) {
+ sending[transceiver->mSending.GetMediaType()]++;
}
}
}
protected:
+
const std::string mName;
JsepSignalingState mState;
uint32_t mNegotiations;
};
} // namespace mozilla
#endif
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -1,14 +1,15 @@
/* 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 "signaling/src/jsep/JsepSessionImpl.h"
+#include <iterator>
#include <string>
#include <set>
#include <bitset>
#include <stdlib.h>
#include "nspr.h"
#include "nss.h"
#include "pk11pub.h"
@@ -16,17 +17,16 @@
#include "logging.h"
#include "mozilla/Move.h"
#include "mozilla/UniquePtr.h"
#include "webrtc/config.h"
#include "signaling/src/jsep/JsepTrack.h"
-#include "signaling/src/jsep/JsepTrack.h"
#include "signaling/src/jsep/JsepTransport.h"
#include "signaling/src/sdp/Sdp.h"
#include "signaling/src/sdp/SipccSdp.h"
#include "signaling/src/sdp/SipccSdpParser.h"
#include "mozilla/net/DataChannelProtocol.h"
namespace mozilla {
@@ -62,125 +62,47 @@ JsepSessionImpl::Init()
NS_ENSURE_SUCCESS(rv, rv);
SetupDefaultCodecs();
SetupDefaultRtpExtensions();
return NS_OK;
}
-// Helper function to find the track for a given m= section.
-template <class T>
-typename std::vector<T>::iterator
-FindTrackByLevel(std::vector<T>& tracks, size_t level)
-{
- for (auto t = tracks.begin(); t != tracks.end(); ++t) {
- if (t->mAssignedMLine.isSome() &&
- (*t->mAssignedMLine == level)) {
- return t;
- }
- }
-
- return tracks.end();
-}
-
-template <class T>
-typename std::vector<T>::iterator
-FindTrackByIds(std::vector<T>& tracks,
- const std::string& streamId,
- const std::string& trackId)
-{
- for (auto t = tracks.begin(); t != tracks.end(); ++t) {
- if (t->mTrack->GetStreamId() == streamId &&
- (t->mTrack->GetTrackId() == trackId)) {
- return t;
- }
- }
-
- return tracks.end();
-}
-
-template <class T>
-typename std::vector<T>::iterator
-FindUnassignedTrackByType(std::vector<T>& tracks,
- SdpMediaSection::MediaType type)
-{
- for (auto t = tracks.begin(); t != tracks.end(); ++t) {
- if (!t->mAssignedMLine.isSome() &&
- (t->mTrack->GetMediaType() == type)) {
- return t;
- }
- }
-
- return tracks.end();
-}
-
nsresult
-JsepSessionImpl::AddTrack(const RefPtr<JsepTrack>& track)
+JsepSessionImpl::AddTransceiver(RefPtr<JsepTransceiver> transceiver)
{
mLastError.clear();
- MOZ_ASSERT(track->GetDirection() == sdp::kSend);
- MOZ_MTLOG(ML_DEBUG, "Adding track.");
- if (track->GetMediaType() != SdpMediaSection::kApplication) {
- track->SetCNAME(mCNAME);
- // Establish minimum number of required SSRCs
- // Note that AddTrack is only for send direction
- size_t minimumSsrcCount = 0;
- std::vector<JsepTrack::JsConstraints> constraints;
- track->GetJsConstraints(&constraints);
- for (auto constraint : constraints) {
- if (!constraint.rid.empty()) {
- minimumSsrcCount++;
+ MOZ_MTLOG(ML_DEBUG, "Adding transceiver.");
+
+ if (transceiver->mSending.GetMediaType() != SdpMediaSection::kApplication) {
+ // Make sure we have an ssrc. Might already be set.
+ transceiver->mSending.EnsureSsrcs(mSsrcGenerator);
+ transceiver->mSending.SetCNAME(mCNAME);
+
+ // Make sure we have identifiers for send track, just in case.
+ // (man I hate this)
+ if (transceiver->mSending.GetTrackId().empty()) {
+ std::string trackId;
+ if (!mUuidGen->Generate(&trackId)) {
+ JSEP_SET_ERROR("Failed to generate UUID for JsepTrack");
+ return NS_ERROR_FAILURE;
}
+
+ transceiver->mSending.UpdateTrack(std::vector<std::string>(), trackId);
}
- // We need at least 1 SSRC
- minimumSsrcCount = std::max<size_t>(1, minimumSsrcCount);
- size_t currSsrcCount = track->GetSsrcs().size();
- if (currSsrcCount < minimumSsrcCount ) {
- MOZ_MTLOG(ML_DEBUG,
- "Adding " << (minimumSsrcCount - currSsrcCount) << " SSRCs.");
- }
- while (track->GetSsrcs().size() < minimumSsrcCount) {
- uint32_t ssrc=0;
- nsresult rv = CreateSsrc(&ssrc);
- NS_ENSURE_SUCCESS(rv, rv);
- // Don't add duplicate ssrcs
- std::vector<uint32_t> ssrcs = track->GetSsrcs();
- if (std::find(ssrcs.begin(), ssrcs.end(), ssrc) == ssrcs.end()) {
- track->AddSsrc(ssrc);
- }
- }
+ } else {
+ transceiver->mJsDirection = SdpDirectionAttribute::Direction::kSendrecv;
}
- track->PopulateCodecs(mSupportedCodecs.values);
-
- JsepSendingTrack strack;
- strack.mTrack = track;
-
- mLocalTracks.push_back(strack);
-
- return NS_OK;
-}
+ transceiver->mSending.PopulateCodecs(mSupportedCodecs.values);
+ transceiver->mReceiving.PopulateCodecs(mSupportedCodecs.values);
+ // We do not set mLevel yet, we do that either on createOffer, or setRemote
-nsresult
-JsepSessionImpl::RemoveTrack(const std::string& streamId,
- const std::string& trackId)
-{
- if (mState != kJsepStateStable) {
- JSEP_SET_ERROR("Removing tracks outside of stable is unsupported.");
- return NS_ERROR_UNEXPECTED;
- }
-
- auto track = FindTrackByIds(mLocalTracks, streamId, trackId);
-
- if (track == mLocalTracks.end()) {
- return NS_ERROR_INVALID_ARG;
- }
-
- mLocalTracks.erase(track);
+ mTransceivers.push_back(transceiver);
return NS_OK;
}
nsresult
JsepSessionImpl::SetIceCredentials(const std::string& ufrag,
const std::string& pwd)
{
mLastError.clear();
@@ -251,386 +173,92 @@ JsepSessionImpl::AddAudioRtpExtension(co
nsresult
JsepSessionImpl::AddVideoRtpExtension(const std::string& extensionName,
SdpDirectionAttribute::Direction direction)
{
return AddRtpExtension(mVideoRtpExtensions, extensionName, direction);
}
-template<class T>
-std::vector<RefPtr<JsepTrack>>
-GetTracks(const std::vector<T>& wrappedTracks)
-{
- std::vector<RefPtr<JsepTrack>> result;
- for (auto i = wrappedTracks.begin(); i != wrappedTracks.end(); ++i) {
- result.push_back(i->mTrack);
- }
- return result;
-}
-
-nsresult
-JsepSessionImpl::ReplaceTrack(const std::string& oldStreamId,
- const std::string& oldTrackId,
- const std::string& newStreamId,
- const std::string& newTrackId)
+std::vector<JsepTrack>
+JsepSessionImpl::GetRemoteTracksAdded() const
{
- auto it = FindTrackByIds(mLocalTracks, oldStreamId, oldTrackId);
-
- if (it == mLocalTracks.end()) {
- JSEP_SET_ERROR("Track " << oldStreamId << "/" << oldTrackId
- << " was never added.");
- return NS_ERROR_INVALID_ARG;
- }
-
- if (FindTrackByIds(mLocalTracks, newStreamId, newTrackId) !=
- mLocalTracks.end()) {
- JSEP_SET_ERROR("Track " << newStreamId << "/" << newTrackId
- << " was already added.");
- return NS_ERROR_INVALID_ARG;
- }
-
- it->mTrack->SetStreamId(newStreamId);
- it->mTrack->SetTrackId(newTrackId);
-
- return NS_OK;
+ return mRemoteTracksAdded;
}
-nsresult
-JsepSessionImpl::SetParameters(const std::string& streamId,
- const std::string& trackId,
- const std::vector<JsepTrack::JsConstraints>& constraints)
-{
- auto it = FindTrackByIds(mLocalTracks, streamId, trackId);
-
- if (it == mLocalTracks.end()) {
- JSEP_SET_ERROR("Track " << streamId << "/" << trackId << " was never added.");
- return NS_ERROR_INVALID_ARG;
- }
-
- // Add RtpStreamId Extmap
- // SdpDirectionAttribute::Direction is a bitmask
- SdpDirectionAttribute::Direction addVideoExt = SdpDirectionAttribute::kInactive;
- SdpDirectionAttribute::Direction addAudioExt = SdpDirectionAttribute::kInactive;
- for (auto constraintEntry: constraints) {
- if (!constraintEntry.rid.empty()) {
- switch (it->mTrack->GetMediaType()) {
- case SdpMediaSection::kVideo: {
- addVideoExt = static_cast<SdpDirectionAttribute::Direction>(addVideoExt
- | it->mTrack->GetDirection());
- break;
- }
- case SdpMediaSection::kAudio: {
- addAudioExt = static_cast<SdpDirectionAttribute::Direction>(addAudioExt
- | it->mTrack->GetDirection());
- break;
- }
- default: {
- MOZ_ASSERT(false);
- return NS_ERROR_INVALID_ARG;
- }
- }
- }
- }
- if (addVideoExt != SdpDirectionAttribute::kInactive) {
- AddVideoRtpExtension("urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id", addVideoExt);
- }
-
- it->mTrack->SetJsConstraints(constraints);
-
- auto track = it->mTrack;
- if (track->GetDirection() == sdp::kSend) {
- // Establish minimum number of required SSRCs
- // Note that AddTrack is only for send direction
- size_t minimumSsrcCount = 0;
- std::vector<JsepTrack::JsConstraints> constraints;
- track->GetJsConstraints(&constraints);
- for (auto constraint : constraints) {
- if (!constraint.rid.empty()) {
- minimumSsrcCount++;
- }
- }
- // We need at least 1 SSRC
- minimumSsrcCount = std::max<size_t>(1, minimumSsrcCount);
- size_t currSsrcCount = track->GetSsrcs().size();
- if (currSsrcCount < minimumSsrcCount ) {
- MOZ_MTLOG(ML_DEBUG,
- "Adding " << (minimumSsrcCount - currSsrcCount) << " SSRCs.");
- }
- while (track->GetSsrcs().size() < minimumSsrcCount) {
- uint32_t ssrc=0;
- nsresult rv = CreateSsrc(&ssrc);
- NS_ENSURE_SUCCESS(rv, rv);
- // Don't add duplicate ssrcs
- std::vector<uint32_t> ssrcs = track->GetSsrcs();
- if (std::find(ssrcs.begin(), ssrcs.end(), ssrc) == ssrcs.end()) {
- track->AddSsrc(ssrc);
- }
- }
- }
- return NS_OK;
-}
-
-nsresult
-JsepSessionImpl::GetParameters(const std::string& streamId,
- const std::string& trackId,
- std::vector<JsepTrack::JsConstraints>* outConstraints)
-{
- auto it = FindTrackByIds(mLocalTracks, streamId, trackId);
-
- if (it == mLocalTracks.end()) {
- JSEP_SET_ERROR("Track " << streamId << "/" << trackId << " was never added.");
- return NS_ERROR_INVALID_ARG;
- }
-
- it->mTrack->GetJsConstraints(outConstraints);
- return NS_OK;
-}
-
-std::vector<RefPtr<JsepTrack>>
-JsepSessionImpl::GetLocalTracks() const
-{
- return GetTracks(mLocalTracks);
-}
-
-std::vector<RefPtr<JsepTrack>>
-JsepSessionImpl::GetRemoteTracks() const
-{
- return GetTracks(mRemoteTracks);
-}
-
-std::vector<RefPtr<JsepTrack>>
-JsepSessionImpl::GetRemoteTracksAdded() const
-{
- return GetTracks(mRemoteTracksAdded);
-}
-
-std::vector<RefPtr<JsepTrack>>
+std::vector<JsepTrack>
JsepSessionImpl::GetRemoteTracksRemoved() const
{
- return GetTracks(mRemoteTracksRemoved);
+ return mRemoteTracksRemoved;
}
nsresult
-JsepSessionImpl::SetupOfferMSections(const JsepOfferOptions& options, Sdp* sdp)
+JsepSessionImpl::CreateOfferMsection(const JsepOfferOptions& options,
+ JsepTransceiver& transceiver,
+ Sdp* local)
{
- // First audio, then video, then datachannel, for interop
- // TODO(bug 1121756): We need to group these by stream-id, _then_ by media
- // type, according to the spec. However, this is not going to interop with
- // older versions of Firefox if a video-only stream is added before an
- // audio-only stream.
- // We should probably wait until 38 is ESR before trying to do this.
- nsresult rv = SetupOfferMSectionsByType(
- SdpMediaSection::kAudio, options.mOfferToReceiveAudio, sdp);
-
- NS_ENSURE_SUCCESS(rv, rv);
+ JsepTrack& sendTrack(transceiver.mSending);
+ JsepTrack& recvTrack(transceiver.mReceiving);
- rv = SetupOfferMSectionsByType(
- SdpMediaSection::kVideo, options.mOfferToReceiveVideo, sdp);
-
- NS_ENSURE_SUCCESS(rv, rv);
+ SdpMediaSection::Protocol protocol(
+ mSdpHelper.GetProtocolForMediaType(sendTrack.GetMediaType()));
- rv = SetupOfferMSectionsByType(
- SdpMediaSection::kApplication, Maybe<size_t>(), sdp);
-
- NS_ENSURE_SUCCESS(rv, rv);
-
- if (!sdp->GetMediaSectionCount()) {
- JSEP_SET_ERROR("Cannot create an offer with no local tracks, "
- "no offerToReceiveAudio/Video, and no DataChannel.");
- return NS_ERROR_INVALID_ARG;
+ const Sdp* answer(GetAnswer());
+ if (answer &&
+ (local->GetMediaSectionCount() < answer->GetMediaSectionCount())) {
+ // Use the protocol the answer used, even if it is not what we would have
+ // used.
+ protocol =
+ answer->GetMediaSection(local->GetMediaSectionCount()).GetProtocol();
}
- return NS_OK;
-}
-
-nsresult
-JsepSessionImpl::SetupOfferMSectionsByType(SdpMediaSection::MediaType mediatype,
- const Maybe<size_t>& offerToReceiveMaybe,
- Sdp* sdp)
-{
- // Convert the Maybe into a size_t*, since that is more readable, especially
- // when using it as an in/out param.
- size_t offerToReceiveCount;
- size_t* offerToReceiveCountPtr = nullptr;
-
- if (offerToReceiveMaybe) {
- offerToReceiveCount = *offerToReceiveMaybe;
- offerToReceiveCountPtr = &offerToReceiveCount;
- }
+ SdpMediaSection* msection = &local->AddMediaSection(
+ sendTrack.GetMediaType(),
+ transceiver.mJsDirection,
+ 0,
+ protocol,
+ sdp::kIPv4,
+ "0.0.0.0");
- // Make sure every local track has an m-section
- nsresult rv = BindLocalTracks(mediatype, sdp);
- NS_ENSURE_SUCCESS(rv, rv);
-
- // Make sure that m-sections that previously had a remote track have the
- // recv bit set. Only matters for renegotiation.
- rv = BindRemoteTracks(mediatype, sdp, offerToReceiveCountPtr);
- NS_ENSURE_SUCCESS(rv, rv);
-
- // If we need more recv sections, start setting the recv bit on other
- // msections. If not, disable msections that have no tracks.
- rv = SetRecvAsNeededOrDisable(mediatype,
- sdp,
- offerToReceiveCountPtr);
- NS_ENSURE_SUCCESS(rv, rv);
-
- // If we still don't have enough recv m-sections, add some.
- if (offerToReceiveCountPtr && *offerToReceiveCountPtr) {
- rv = AddRecvonlyMsections(mediatype, *offerToReceiveCountPtr, sdp);
- NS_ENSURE_SUCCESS(rv, rv);
+ if (transceiver.IsStopped()) {
+ mSdpHelper.DisableMsection(local, msection);
+ return NS_OK;
}
- return NS_OK;
-}
-
-nsresult
-JsepSessionImpl::BindLocalTracks(SdpMediaSection::MediaType mediatype, Sdp* sdp)
-{
- for (JsepSendingTrack& track : mLocalTracks) {
- if (mediatype != track.mTrack->GetMediaType()) {
- continue;
- }
-
- SdpMediaSection* msection;
- if (track.mAssignedMLine.isSome()) {
- msection = &sdp->GetMediaSection(*track.mAssignedMLine);
- } else {
- nsresult rv = GetFreeMsectionForSend(track.mTrack->GetMediaType(),
- sdp,
- &msection);
- NS_ENSURE_SUCCESS(rv, rv);
- track.mAssignedMLine = Some(msection->GetLevel());
- }
-
- track.mTrack->AddToOffer(msection);
- }
- return NS_OK;
-}
+ msection->SetPort(9);
-nsresult
-JsepSessionImpl::BindRemoteTracks(SdpMediaSection::MediaType mediatype,
- Sdp* sdp,
- size_t* offerToReceive)
-{
- for (JsepReceivingTrack& track : mRemoteTracks) {
- if (mediatype != track.mTrack->GetMediaType()) {
- continue;
- }
-
- if (!track.mAssignedMLine.isSome()) {
- MOZ_ASSERT(false);
- continue;
- }
-
- auto& msection = sdp->GetMediaSection(*track.mAssignedMLine);
-
- if (mSdpHelper.MsectionIsDisabled(msection)) {
- // TODO(bug 1095226) Content probably disabled this? Should we allow
- // content to do this?
- continue;
- }
-
- track.mTrack->AddToOffer(&msection);
-
- if (offerToReceive && *offerToReceive) {
- --(*offerToReceive);
- }
+ // We don't do this in AddTransportAttributes because that is also used for
+ // making answers, and we don't want to unconditionally set rtcp-mux there.
+ if (mSdpHelper.HasRtcp(msection->GetProtocol())) {
+ // Set RTCP-MUX.
+ msection->GetAttributeList().SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
}
- return NS_OK;
-}
+ nsresult rv = AddTransportAttributes(msection, SdpSetupAttribute::kActpass);
+ NS_ENSURE_SUCCESS(rv, rv);
-nsresult
-JsepSessionImpl::SetRecvAsNeededOrDisable(SdpMediaSection::MediaType mediatype,
- Sdp* sdp,
- size_t* offerToRecv)
-{
- for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) {
- auto& msection = sdp->GetMediaSection(i);
+ sendTrack.AddToOffer(mSsrcGenerator, msection);
+ recvTrack.AddToOffer(mSsrcGenerator, msection);
+
+ AddExtmap(msection);
- if (mSdpHelper.MsectionIsDisabled(msection) ||
- msection.GetMediaType() != mediatype ||
- msection.IsReceiving()) {
- continue;
- }
-
- if (offerToRecv) {
- if (*offerToRecv) {
- SetupOfferToReceiveMsection(&msection);
- --(*offerToRecv);
- continue;
- }
- } else if (msection.IsSending()) {
- SetupOfferToReceiveMsection(&msection);
- continue;
- }
-
- if (!msection.IsSending()) {
- // Unused m-section, and no reason to offer to recv on it
- mSdpHelper.DisableMsection(sdp, &msection);
- }
+ std::string mid;
+ // We do not set the mid on the transceiver, that happens when a description
+ // is set.
+ if (transceiver.IsAssociated()) {
+ mid = transceiver.GetMid();
+ } else {
+ std::ostringstream osMid;
+ osMid << "sdparta_" << msection->GetLevel();
+ mid = osMid.str();
}
- return NS_OK;
-}
-
-void
-JsepSessionImpl::SetupOfferToReceiveMsection(SdpMediaSection* offer)
-{
- // Create a dummy recv track, and have it add codecs, set direction, etc.
- RefPtr<JsepTrack> dummy = new JsepTrack(offer->GetMediaType(),
- "",
- "",
- sdp::kRecv);
- dummy->PopulateCodecs(mSupportedCodecs.values);
- dummy->AddToOffer(offer);
-}
-
-nsresult
-JsepSessionImpl::AddRecvonlyMsections(SdpMediaSection::MediaType mediatype,
- size_t count,
- Sdp* sdp)
-{
- while (count--) {
- nsresult rv = CreateOfferMSection(
- mediatype,
- mSdpHelper.GetProtocolForMediaType(mediatype),
- SdpDirectionAttribute::kRecvonly,
- sdp);
-
- NS_ENSURE_SUCCESS(rv, rv);
- SetupOfferToReceiveMsection(
- &sdp->GetMediaSection(sdp->GetMediaSectionCount() - 1));
- }
- return NS_OK;
-}
-
-// This function creates a skeleton SDP based on the old descriptions
-// (ie; all m-sections are inactive).
-nsresult
-JsepSessionImpl::AddReofferMsections(const Sdp& oldLocalSdp,
- const Sdp& oldAnswer,
- Sdp* newSdp)
-{
- nsresult rv;
-
- for (size_t i = 0; i < oldLocalSdp.GetMediaSectionCount(); ++i) {
- // We do not set the direction in this function (or disable when previously
- // disabled), that happens in |SetupOfferMSectionsByType|
- rv = CreateOfferMSection(oldLocalSdp.GetMediaSection(i).GetMediaType(),
- oldLocalSdp.GetMediaSection(i).GetProtocol(),
- SdpDirectionAttribute::kInactive,
- newSdp);
- NS_ENSURE_SUCCESS(rv, rv);
-
- rv = mSdpHelper.CopyStickyParams(oldAnswer.GetMediaSection(i),
- &newSdp->GetMediaSection(i));
- NS_ENSURE_SUCCESS(rv, rv);
- }
+ msection->GetAttributeList().SetAttribute(
+ new SdpStringAttribute(SdpAttribute::kMidAttribute, mid));
return NS_OK;
}
void
JsepSessionImpl::SetupBundle(Sdp* sdp) const
{
std::vector<std::string> mids;
@@ -678,92 +306,72 @@ JsepSessionImpl::SetupBundle(Sdp* sdp) c
groupAttr->PushEntry(SdpGroupAttributeList::kBundle, mids);
sdp->GetAttributeList().SetAttribute(groupAttr.release());
}
}
nsresult
JsepSessionImpl::GetRemoteIds(const Sdp& sdp,
const SdpMediaSection& msection,
- std::string* streamId,
+ std::vector<std::string>* streamIds,
std::string* trackId)
{
- nsresult rv = mSdpHelper.GetIdsFromMsid(sdp, msection, streamId, trackId);
+ nsresult rv = mSdpHelper.GetIdsFromMsid(sdp, msection, streamIds, trackId);
if (rv == NS_ERROR_NOT_AVAILABLE) {
- *streamId = mDefaultRemoteStreamId;
-
- if (!mDefaultRemoteTrackIdsByLevel.count(msection.GetLevel())) {
- // Generate random track ids.
- if (!mUuidGen->Generate(trackId)) {
- JSEP_SET_ERROR("Failed to generate UUID for JsepTrack");
- return NS_ERROR_FAILURE;
- }
+ streamIds->push_back(mDefaultRemoteStreamId);
- mDefaultRemoteTrackIdsByLevel[msection.GetLevel()] = *trackId;
- } else {
- *trackId = mDefaultRemoteTrackIdsByLevel[msection.GetLevel()];
+ // Generate random track ids.
+ if (!mUuidGen->Generate(trackId)) {
+ JSEP_SET_ERROR("Failed to generate UUID for JsepTrack");
+ return NS_ERROR_FAILURE;
}
+
return NS_OK;
}
- if (NS_SUCCEEDED(rv)) {
- // If, for whatever reason, the other end renegotiates with an msid where
- // there wasn't one before, don't allow the old default to pop up again
- // later.
- mDefaultRemoteTrackIdsByLevel.erase(msection.GetLevel());
- }
-
return rv;
}
nsresult
JsepSessionImpl::CreateOffer(const JsepOfferOptions& options,
std::string* offer)
{
mLastError.clear();
if (mState != kJsepStateStable) {
JSEP_SET_ERROR("Cannot create offer in state " << GetStateStr(mState));
return NS_ERROR_UNEXPECTED;
}
- // Undo track assignments from a previous call to CreateOffer
- // (ie; if the track has not been negotiated yet, it doesn't necessarily need
- // to stay in the same m-section that it was in)
- for (JsepSendingTrack& trackWrapper : mLocalTracks) {
- if (!trackWrapper.mTrack->GetNegotiatedDetails()) {
- trackWrapper.mAssignedMLine.reset();
- }
- }
-
UniquePtr<Sdp> sdp;
// Make the basic SDP that is common to offer/answer.
nsresult rv = CreateGenericSDP(&sdp);
NS_ENSURE_SUCCESS(rv, rv);
- if (mCurrentLocalDescription) {
- rv = AddReofferMsections(*mCurrentLocalDescription,
- *GetAnswer(),
- sdp.get());
- NS_ENSURE_SUCCESS(rv, rv);
+ for (size_t level = 0;
+ JsepTransceiver* transceiver = GetTransceiverForLocal(level);
+ ++level) {
+ rv = CreateOfferMsection(options, *transceiver, sdp.get());
}
- // Ensure that we have all the m-sections we need, and disable extras
- rv = SetupOfferMSections(options, sdp.get());
- NS_ENSURE_SUCCESS(rv, rv);
+ if (!sdp->GetMediaSectionCount()) {
+ JSEP_SET_ERROR("Cannot create offer when there are no valid transceivers.");
+ return NS_ERROR_UNEXPECTED;
+ }
SetupBundle(sdp.get());
if (mCurrentLocalDescription) {
rv = CopyPreviousTransportParams(*GetAnswer(),
*mCurrentLocalDescription,
*sdp,
sdp.get());
NS_ENSURE_SUCCESS(rv,rv);
+ CopyPreviousMsid(*mCurrentLocalDescription, sdp.get());
}
*offer = sdp->ToString();
mGeneratedLocalDescription = Move(sdp);
++mSessionVersion;
return NS_OK;
}
@@ -786,317 +394,173 @@ JsepSessionImpl::GetRemoteDescription(Js
mozilla::Sdp* sdp = GetParsedRemoteDescription(type);
if (sdp) {
sdp->Serialize(os);
}
return os.str();
}
void
-JsepSessionImpl::AddExtmap(SdpMediaSection* msection) const
+JsepSessionImpl::AddExtmap(SdpMediaSection* msection)
{
- const auto* extensions = GetRtpExtensions(msection->GetMediaType());
+ auto extensions = GetRtpExtensions(*msection);
- if (extensions && !extensions->empty()) {
+ if (!extensions.empty()) {
SdpExtmapAttributeList* extmap = new SdpExtmapAttributeList;
- extmap->mExtmaps = *extensions;
+ extmap->mExtmaps = extensions;
msection->GetAttributeList().SetAttribute(extmap);
}
}
void
JsepSessionImpl::AddMid(const std::string& mid,
SdpMediaSection* msection) const
{
msection->GetAttributeList().SetAttribute(new SdpStringAttribute(
SdpAttribute::kMidAttribute, mid));
}
-const std::vector<SdpExtmapAttributeList::Extmap>*
-JsepSessionImpl::GetRtpExtensions(SdpMediaSection::MediaType type) const
+std::vector<SdpExtmapAttributeList::Extmap>
+JsepSessionImpl::GetRtpExtensions(const SdpMediaSection& msection)
{
- switch (type) {
+ std::vector<SdpExtmapAttributeList::Extmap> result;
+ switch (msection.GetMediaType()) {
case SdpMediaSection::kAudio:
- return &mAudioRtpExtensions;
+ result = mAudioRtpExtensions;
+ break;
case SdpMediaSection::kVideo:
- return &mVideoRtpExtensions;
+ result = mVideoRtpExtensions;
+ if (msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kRidAttribute)) {
+ // We need RID support
+ // TODO: Would it be worth checking that the direction is sane?
+ AddRtpExtension(result,
+ "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id",
+ SdpDirectionAttribute::kSendonly);
+ }
+ break;
default:
- return nullptr;
+ ;
}
+ return result;
}
void
JsepSessionImpl::AddCommonExtmaps(const SdpMediaSection& remoteMsection,
SdpMediaSection* msection)
{
- auto* ourExtensions = GetRtpExtensions(remoteMsection.GetMediaType());
-
- if (ourExtensions) {
- mSdpHelper.AddCommonExtmaps(remoteMsection, *ourExtensions, msection);
- }
+ mSdpHelper.AddCommonExtmaps(
+ remoteMsection, GetRtpExtensions(*msection), msection);
}
nsresult
JsepSessionImpl::CreateAnswer(const JsepAnswerOptions& options,
std::string* answer)
{
mLastError.clear();
if (mState != kJsepStateHaveRemoteOffer) {
JSEP_SET_ERROR("Cannot create answer in state " << GetStateStr(mState));
return NS_ERROR_UNEXPECTED;
}
- // This is the heart of the negotiation code. Depressing that it's
- // so bad.
- //
- // Here's the current algorithm:
- // 1. Walk through all the m-lines on the other side.
- // 2. For each m-line, walk through all of our local tracks
- // in sequence and see if any are unassigned. If so, assign
- // them and mark it sendrecv, otherwise it's recvonly.
- // 3. Just replicate their media attributes.
- // 4. Profit.
UniquePtr<Sdp> sdp;
// Make the basic SDP that is common to offer/answer.
nsresult rv = CreateGenericSDP(&sdp);
NS_ENSURE_SUCCESS(rv, rv);
const Sdp& offer = *mPendingRemoteDescription;
// Copy the bundle groups into our answer
UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
mSdpHelper.GetBundleGroups(offer, &groupAttr->mGroups);
sdp->GetAttributeList().SetAttribute(groupAttr.release());
- // Disable send for local tracks if the offer no longer allows it
- // (i.e., the m-section is recvonly, inactive or disabled)
- for (JsepSendingTrack& trackWrapper : mLocalTracks) {
- if (!trackWrapper.mAssignedMLine.isSome()) {
- continue;
- }
-
- // Get rid of all m-line assignments that have not been negotiated
- if (!trackWrapper.mTrack->GetNegotiatedDetails()) {
- trackWrapper.mAssignedMLine.reset();
- continue;
+ for (size_t i = 0; i < offer.GetMediaSectionCount(); ++i) {
+ // The transceivers are already in place, due to setRemote
+ JsepTransceiver* transceiver(GetTransceiverForLevel(i));
+ if (!transceiver) {
+ JSEP_SET_ERROR("No transceiver for level " << i);
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
}
-
- if (!offer.GetMediaSection(*trackWrapper.mAssignedMLine).IsReceiving()) {
- trackWrapper.mAssignedMLine.reset();
- }
- }
-
- size_t numMsections = offer.GetMediaSectionCount();
-
- for (size_t i = 0; i < numMsections; ++i) {
- const SdpMediaSection& remoteMsection = offer.GetMediaSection(i);
- rv = CreateAnswerMSection(options, i, remoteMsection, sdp.get());
+ rv = CreateAnswerMsection(options,
+ *transceiver,
+ offer.GetMediaSection(i),
+ sdp.get());
NS_ENSURE_SUCCESS(rv, rv);
}
if (mCurrentLocalDescription) {
// per discussion with bwc, 3rd parm here should be offer, not *sdp. (mjf)
rv = CopyPreviousTransportParams(*GetAnswer(),
*mCurrentRemoteDescription,
offer,
sdp.get());
NS_ENSURE_SUCCESS(rv,rv);
+ CopyPreviousMsid(*mCurrentLocalDescription, sdp.get());
}
*answer = sdp->ToString();
mGeneratedLocalDescription = Move(sdp);
++mSessionVersion;
return NS_OK;
}
nsresult
-JsepSessionImpl::CreateOfferMSection(SdpMediaSection::MediaType mediatype,
- SdpMediaSection::Protocol proto,
- SdpDirectionAttribute::Direction dir,
- Sdp* sdp)
-{
- SdpMediaSection* msection =
- &sdp->AddMediaSection(mediatype, dir, 0, proto, sdp::kIPv4, "0.0.0.0");
-
- return EnableOfferMsection(msection);
-}
-
-nsresult
-JsepSessionImpl::GetFreeMsectionForSend(
- SdpMediaSection::MediaType type,
- Sdp* sdp,
- SdpMediaSection** msectionOut)
-{
- for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) {
- SdpMediaSection& msection = sdp->GetMediaSection(i);
- // draft-ietf-rtcweb-jsep-08 says we should reclaim disabled m-sections
- // regardless of media type. This breaks some pretty fundamental rules of
- // SDP offer/answer, so we probably should not do it.
- if (msection.GetMediaType() != type) {
- continue;
- }
-
- if (FindTrackByLevel(mLocalTracks, i) != mLocalTracks.end()) {
- // Not free
- continue;
- }
-
- if (mSdpHelper.MsectionIsDisabled(msection)) {
- // Was disabled; revive
- nsresult rv = EnableOfferMsection(&msection);
- NS_ENSURE_SUCCESS(rv, rv);
- }
-
- *msectionOut = &msection;
- return NS_OK;
- }
-
- // Ok, no pre-existing m-section. Make a new one.
- nsresult rv = CreateOfferMSection(type,
- mSdpHelper.GetProtocolForMediaType(type),
- SdpDirectionAttribute::kInactive,
- sdp);
- NS_ENSURE_SUCCESS(rv, rv);
-
- *msectionOut = &sdp->GetMediaSection(sdp->GetMediaSectionCount() - 1);
- return NS_OK;
-}
-
-nsresult
-JsepSessionImpl::CreateAnswerMSection(const JsepAnswerOptions& options,
- size_t mlineIndex,
+JsepSessionImpl::CreateAnswerMsection(const JsepAnswerOptions& options,
+ JsepTransceiver& transceiver,
const SdpMediaSection& remoteMsection,
Sdp* sdp)
{
+ SdpDirectionAttribute::Direction direction =
+ reverse(remoteMsection.GetDirection()) & transceiver.mJsDirection;
SdpMediaSection& msection =
sdp->AddMediaSection(remoteMsection.GetMediaType(),
- SdpDirectionAttribute::kInactive,
+ direction,
9,
remoteMsection.GetProtocol(),
sdp::kIPv4,
"0.0.0.0");
nsresult rv = mSdpHelper.CopyStickyParams(remoteMsection, &msection);
NS_ENSURE_SUCCESS(rv, rv);
- if (mSdpHelper.MsectionIsDisabled(remoteMsection)) {
+ if (mSdpHelper.MsectionIsDisabled(remoteMsection) ||
+ // JS might have stopped this
+ transceiver.IsStopped()) {
mSdpHelper.DisableMsection(sdp, &msection);
return NS_OK;
}
SdpSetupAttribute::Role role;
rv = DetermineAnswererSetupRole(remoteMsection, &role);
NS_ENSURE_SUCCESS(rv, rv);
rv = AddTransportAttributes(&msection, role);
NS_ENSURE_SUCCESS(rv, rv);
- rv = SetRecvonlySsrc(&msection);
- NS_ENSURE_SUCCESS(rv, rv);
+ transceiver.mSending.AddToAnswer(remoteMsection, mSsrcGenerator, &msection);
+ transceiver.mReceiving.AddToAnswer(remoteMsection, mSsrcGenerator, &msection);
- // Only attempt to match up local tracks if the offerer has elected to
- // receive traffic.
- if (remoteMsection.IsReceiving()) {
- rv = BindMatchingLocalTrackToAnswer(&msection);
- NS_ENSURE_SUCCESS(rv, rv);
- }
-
- if (remoteMsection.IsSending()) {
- BindMatchingRemoteTrackToAnswer(&msection);
- }
-
- // Add extmap attributes.
+ // Add extmap attributes. This logic will probably be moved to the track,
+ // since it can be specified on a per-sender basis in JS.
AddCommonExtmaps(remoteMsection, &msection);
if (msection.GetFormats().empty()) {
// Could not negotiate anything. Disable m-section.
mSdpHelper.DisableMsection(sdp, &msection);
}
return NS_OK;
}
nsresult
-JsepSessionImpl::SetRecvonlySsrc(SdpMediaSection* msection)
-{
- if (msection->GetMediaType() == SdpMediaSection::kApplication) {
- return NS_OK;
- }
-
- // If previous m-sections are disabled, we do not call this function for them
- while (mRecvonlySsrcs.size() <= msection->GetLevel()) {
- uint32_t ssrc;
- nsresult rv = CreateSsrc(&ssrc);
- NS_ENSURE_SUCCESS(rv, rv);
- mRecvonlySsrcs.push_back(ssrc);
- }
-
- std::vector<uint32_t> ssrcs;
- ssrcs.push_back(mRecvonlySsrcs[msection->GetLevel()]);
- msection->SetSsrcs(ssrcs, mCNAME);
- return NS_OK;
-}
-
-nsresult
-JsepSessionImpl::BindMatchingLocalTrackToAnswer(SdpMediaSection* msection)
-{
- auto track = FindTrackByLevel(mLocalTracks, msection->GetLevel());
-
- if (track == mLocalTracks.end()) {
- track = FindUnassignedTrackByType(mLocalTracks, msection->GetMediaType());
- }
-
- if (track == mLocalTracks.end() &&
- msection->GetMediaType() == SdpMediaSection::kApplication) {
- // If we are offered datachannel, we need to play along even if no track
- // for it has been added yet.
- std::string streamId;
- std::string trackId;
-
- if (!mUuidGen->Generate(&streamId) || !mUuidGen->Generate(&trackId)) {
- JSEP_SET_ERROR("Failed to generate UUIDs for datachannel track");
- return NS_ERROR_FAILURE;
- }
-
- AddTrack(RefPtr<JsepTrack>(
- new JsepTrack(SdpMediaSection::kApplication, streamId, trackId)));
- track = FindUnassignedTrackByType(mLocalTracks, msection->GetMediaType());
- MOZ_ASSERT(track != mLocalTracks.end());
- }
-
- if (track != mLocalTracks.end()) {
- track->mAssignedMLine = Some(msection->GetLevel());
- track->mTrack->AddToAnswer(
- mPendingRemoteDescription->GetMediaSection(msection->GetLevel()),
- msection);
- }
-
- return NS_OK;
-}
-
-nsresult
-JsepSessionImpl::BindMatchingRemoteTrackToAnswer(SdpMediaSection* msection)
-{
- auto it = FindTrackByLevel(mRemoteTracks, msection->GetLevel());
- if (it == mRemoteTracks.end()) {
- MOZ_ASSERT(false);
- JSEP_SET_ERROR("Failed to find remote track for local answer m-section");
- return NS_ERROR_FAILURE;
- }
-
- it->mTrack->AddToAnswer(
- mPendingRemoteDescription->GetMediaSection(msection->GetLevel()),
- msection);
- return NS_OK;
-}
-
-nsresult
JsepSessionImpl::DetermineAnswererSetupRole(
const SdpMediaSection& remoteMsection,
SdpSetupAttribute::Role* rolep)
{
// Determine the role.
// RFC 5763 says:
//
// The endpoint MUST use the setup attribute defined in [RFC4145].
@@ -1149,18 +613,17 @@ JsepSessionImpl::SetLocalDescription(Jse
if (mState != kJsepStateHaveLocalOffer) {
JSEP_SET_ERROR("Cannot rollback local description in "
<< GetStateStr(mState));
return NS_ERROR_UNEXPECTED;
}
mPendingLocalDescription.reset();
SetState(kJsepStateStable);
- mTransports = mOldTransports;
- mOldTransports.clear();
+ RollbackLocalOffer();
return NS_OK;
}
switch (mState) {
case kJsepStateStable:
if (type != kJsepSdpOffer) {
JSEP_SET_ERROR("Cannot set local answer in state "
<< GetStateStr(mState));
@@ -1184,22 +647,35 @@ JsepSessionImpl::SetLocalDescription(Jse
UniquePtr<Sdp> parsed;
nsresult rv = ParseSdp(sdp, &parsed);
NS_ENSURE_SUCCESS(rv, rv);
// Check that content hasn't done anything unsupported with the SDP
rv = ValidateLocalDescription(*parsed);
NS_ENSURE_SUCCESS(rv, rv);
- // Create transport objects.
- mOldTransports = mTransports; // Save in case we need to rollback
- mTransports.clear();
- for (size_t t = 0; t < parsed->GetMediaSectionCount(); ++t) {
- mTransports.push_back(RefPtr<JsepTransport>(new JsepTransport));
- InitTransport(parsed->GetMediaSection(t), mTransports[t].get());
+ if (type == kJsepSdpOffer) {
+ // For rollback
+ mOldTransceivers.clear();
+ for (const auto& transceiver : mTransceivers) {
+ mOldTransceivers.push_back(new JsepTransceiver(*transceiver));
+ }
+ }
+
+ for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
+ JsepTransceiver* transceiver(GetTransceiverForLevel(i));
+ if (!transceiver) {
+ MOZ_ASSERT(false);
+ JSEP_SET_ERROR("No transceiver for level " << i);
+ return NS_ERROR_FAILURE;
+ }
+ transceiver->Associate(
+ parsed->GetMediaSection(i).GetAttributeList().GetMid());
+ transceiver->mTransport = new JsepTransport;
+ InitTransport(parsed->GetMediaSection(i), transceiver->mTransport.get());
}
switch (type) {
case kJsepSdpOffer:
rv = SetLocalDescriptionOffer(Move(parsed));
break;
case kJsepSdpAnswer:
case kJsepSdpPranswer:
@@ -1243,34 +719,32 @@ JsepSessionImpl::SetLocalDescriptionAnsw
SetState(kJsepStateStable);
return NS_OK;
}
nsresult
JsepSessionImpl::SetRemoteDescription(JsepSdpType type, const std::string& sdp)
{
mLastError.clear();
- mRemoteTracksAdded.clear();
- mRemoteTracksRemoved.clear();
MOZ_MTLOG(ML_DEBUG, "SetRemoteDescription type=" << type << "\nSDP=\n"
<< sdp);
if (type == kJsepSdpRollback) {
if (mState != kJsepStateHaveRemoteOffer) {
JSEP_SET_ERROR("Cannot rollback remote description in "
<< GetStateStr(mState));
return NS_ERROR_UNEXPECTED;
}
mPendingRemoteDescription.reset();
SetState(kJsepStateStable);
+ RollbackRemoteOffer();
- // Update the remote tracks to what they were before the SetRemote
- return SetRemoteTracksFromDescription(mCurrentRemoteDescription.get());
+ return NS_OK;
}
switch (mState) {
case kJsepStateStable:
if (type != kJsepSdpOffer) {
JSEP_SET_ERROR("Cannot set remote answer in state "
<< GetStateStr(mState));
return NS_ERROR_UNEXPECTED;
@@ -1325,16 +799,33 @@ JsepSessionImpl::SetRemoteDescription(Js
}
std::vector<std::string> iceOptions;
if (parsed->GetAttributeList().HasAttribute(
SdpAttribute::kIceOptionsAttribute)) {
iceOptions = parsed->GetAttributeList().GetIceOptions().mValues;
}
+ // For rollback.
+ if (type == kJsepSdpOffer) {
+ mOldTransceivers.clear();
+ for (const auto& transceiver : mTransceivers) {
+ mOldTransceivers.push_back(new JsepTransceiver(*transceiver));
+ }
+ }
+
+ // TODO(bug 1095780): Note that we create remote tracks even when
+ // They contain only codecs we can't negotiate or other craziness.
+ rv = UpdateTransceiversFromRemoteDescription(*parsed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
+ MOZ_ASSERT(GetTransceiverForLevel(i));
+ }
+
switch (type) {
case kJsepSdpOffer:
rv = SetRemoteDescriptionOffer(Move(parsed));
break;
case kJsepSdpAnswer:
case kJsepSdpPranswer:
rv = SetRemoteDescriptionAnswer(type, Move(parsed));
break;
@@ -1361,174 +852,152 @@ JsepSessionImpl::HandleNegotiatedSession
mIceControlling = remoteIceLite || mIsOfferer;
const Sdp& answer = mIsOfferer ? *remote : *local;
SdpHelper::BundledMids bundledMids;
nsresult rv = mSdpHelper.GetBundledMids(answer, &bundledMids);
NS_ENSURE_SUCCESS(rv, rv);
- if (mTransports.size() < local->GetMediaSectionCount()) {
- JSEP_SET_ERROR("Fewer transports set up than m-lines");
- MOZ_ASSERT(false);
- return NS_ERROR_FAILURE;
- }
-
- for (JsepSendingTrack& trackWrapper : mLocalTracks) {
- trackWrapper.mTrack->ClearNegotiatedDetails();
- }
+ // Now walk through the m-sections, perform negotiation, and update the
+ // transceivers.
+ for (size_t i = 0; i < local->GetMediaSectionCount(); ++i) {
+ JsepTransceiver* transceiver(GetTransceiverForLevel(i));
+ if (!transceiver) {
+ MOZ_ASSERT(false);
+ JSEP_SET_ERROR("No transceiver for level " << i);
+ return NS_ERROR_FAILURE;
+ }
- for (JsepReceivingTrack& trackWrapper : mRemoteTracks) {
- trackWrapper.mTrack->ClearNegotiatedDetails();
- }
-
- std::vector<JsepTrackPair> trackPairs;
-
- // Now walk through the m-sections, make sure they match, and create
- // track pairs that describe the media to be set up.
- for (size_t i = 0; i < local->GetMediaSectionCount(); ++i) {
// Skip disabled m-sections.
if (answer.GetMediaSection(i).GetPort() == 0) {
- mTransports[i]->Close();
+ transceiver->mTransport->Close();
+ transceiver->Stop();
+ transceiver->Disassociate();
+ transceiver->ClearBundleLevel();
+ transceiver->mSending.SetActive(false);
+ transceiver->mReceiving.SetActive(false);
+ // Do not clear mLevel yet! That will happen on the next negotiation.
continue;
}
// The transport details are not necessarily on the m-section we're
// currently processing.
size_t transportLevel = i;
bool usingBundle = false;
{
const SdpMediaSection& answerMsection(answer.GetMediaSection(i));
if (answerMsection.GetAttributeList().HasAttribute(
SdpAttribute::kMidAttribute)) {
if (bundledMids.count(answerMsection.GetAttributeList().GetMid())) {
const SdpMediaSection* masterBundleMsection =
bundledMids[answerMsection.GetAttributeList().GetMid()];
transportLevel = masterBundleMsection->GetLevel();
usingBundle = true;
- if (i != transportLevel) {
- mTransports[i]->Close();
- }
}
}
}
- RefPtr<JsepTransport> transport = mTransports[transportLevel];
-
- rv = FinalizeTransport(
- remote->GetMediaSection(transportLevel).GetAttributeList(),
- answer.GetMediaSection(transportLevel).GetAttributeList(),
- transport);
+ rv = MakeNegotiatedTransceiver(remote->GetMediaSection(i),
+ local->GetMediaSection(i),
+ usingBundle,
+ transportLevel,
+ transceiver);
NS_ENSURE_SUCCESS(rv, rv);
-
- JsepTrackPair trackPair;
- rv = MakeNegotiatedTrackPair(remote->GetMediaSection(i),
- local->GetMediaSection(i),
- transport,
- usingBundle,
- transportLevel,
- &trackPair);
- NS_ENSURE_SUCCESS(rv, rv);
-
- trackPairs.push_back(trackPair);
}
- JsepTrack::SetUniquePayloadTypes(GetTracks(mRemoteTracks));
-
- // Ouch, this probably needs some dirty bit instead of just clearing
- // stuff for renegotiation.
- mNegotiatedTrackPairs = trackPairs;
+ std::vector<JsepTrack*> remoteTracks;
+ for (const RefPtr<JsepTransceiver>& transceiver : mTransceivers) {
+ remoteTracks.push_back(&transceiver->mReceiving);
+ }
+ JsepTrack::SetUniquePayloadTypes(remoteTracks);
mGeneratedLocalDescription.reset();
mNegotiations++;
return NS_OK;
}
nsresult
-JsepSessionImpl::MakeNegotiatedTrackPair(const SdpMediaSection& remote,
- const SdpMediaSection& local,
- const RefPtr<JsepTransport>& transport,
- bool usingBundle,
- size_t transportLevel,
- JsepTrackPair* trackPairOut)
+JsepSessionImpl::MakeNegotiatedTransceiver(const SdpMediaSection& remote,
+ const SdpMediaSection& local,
+ bool usingBundle,
+ size_t transportLevel,
+ JsepTransceiver* transceiver)
{
- MOZ_ASSERT(transport->mComponents);
const SdpMediaSection& answer = mIsOfferer ? remote : local;
- bool sending;
- bool receiving;
+ bool sending = false;
+ bool receiving = false;
- if (mIsOfferer) {
- receiving = answer.IsSending();
- sending = answer.IsReceiving();
- } else {
- sending = answer.IsSending();
- receiving = answer.IsReceiving();
+ // JS could stop the transceiver after the answer was created.
+ if (!transceiver->IsStopped()) {
+ if (mIsOfferer) {
+ receiving = answer.IsSending();
+ sending = answer.IsReceiving();
+ } else {
+ sending = answer.IsSending();
+ receiving = answer.IsReceiving();
+ }
}
MOZ_MTLOG(ML_DEBUG, "Negotiated m= line"
<< " index=" << local.GetLevel()
<< " type=" << local.GetMediaType()
<< " sending=" << sending
<< " receiving=" << receiving);
- trackPairOut->mLevel = local.GetLevel();
-
- if (local.GetMediaType() != SdpMediaSection::kApplication) {
- MOZ_ASSERT(mRecvonlySsrcs.size() > local.GetLevel(),
- "Failed to set the default ssrc for an active m-section");
- trackPairOut->mRecvonlySsrc = mRecvonlySsrcs[local.GetLevel()];
- }
-
if (usingBundle) {
- trackPairOut->SetBundleLevel(transportLevel);
+ transceiver->SetBundleLevel(transportLevel);
+ } else {
+ transceiver->ClearBundleLevel();
}
- auto sendTrack = FindTrackByLevel(mLocalTracks, local.GetLevel());
- if (sendTrack != mLocalTracks.end()) {
- sendTrack->mTrack->Negotiate(answer, remote);
- sendTrack->mTrack->SetActive(sending);
- trackPairOut->mSending = sendTrack->mTrack;
- } else if (sending) {
- JSEP_SET_ERROR("Failed to find local track for level " <<
- local.GetLevel()
- << " in local SDP. This should never happen.");
- NS_ASSERTION(false, "Failed to find local track for level");
- return NS_ERROR_FAILURE;
+ if (transportLevel != remote.GetLevel()) {
+ JsepTransceiver* bundleTransceiver(GetTransceiverForLevel(transportLevel));
+ if (!bundleTransceiver) {
+ MOZ_ASSERT(false);
+ JSEP_SET_ERROR("No transceiver for level " << transportLevel);
+ return NS_ERROR_FAILURE;
+ }
+ transceiver->mTransport = bundleTransceiver->mTransport;
+ } else {
+ // Ensures we only finalize once, when we process the master level
+ nsresult rv = FinalizeTransport(
+ remote.GetAttributeList(),
+ answer.GetAttributeList(),
+ transceiver->mTransport);
+ NS_ENSURE_SUCCESS(rv, rv);
}
- auto recvTrack = FindTrackByLevel(mRemoteTracks, local.GetLevel());
- if (recvTrack != mRemoteTracks.end()) {
- recvTrack->mTrack->Negotiate(answer, remote);
- recvTrack->mTrack->SetActive(receiving);
- trackPairOut->mReceiving = recvTrack->mTrack;
+ transceiver->mSending.SetActive(sending);
+ if (sending) {
+ transceiver->mSending.Negotiate(answer, remote);
+ }
- if (receiving &&
- trackPairOut->HasBundleLevel() &&
- recvTrack->mTrack->GetSsrcs().empty() &&
- recvTrack->mTrack->GetMediaType() != SdpMediaSection::kApplication) {
+ JsepTrack& recvTrack = transceiver->mReceiving;
+ recvTrack.SetActive(receiving);
+ if (receiving) {
+ recvTrack.Negotiate(answer, remote);
+
+ if (transceiver->HasBundleLevel() &&
+ recvTrack.GetSsrcs().empty() &&
+ recvTrack.GetMediaType() != SdpMediaSection::kApplication) {
+ // TODO(bug 1105005): Once we have urn:ietf:params:rtp-hdrext:sdes:mid
+ // support, we should only fire this warning if that extension was not
+ // negotiated.
MOZ_MTLOG(ML_ERROR, "Bundled m-section has no ssrc attributes. "
"This may cause media packets to be dropped.");
}
- } else if (receiving) {
- JSEP_SET_ERROR("Failed to find remote track for level "
- << local.GetLevel()
- << " in remote SDP. This should never happen.");
- NS_ASSERTION(false, "Failed to find remote track for level");
- return NS_ERROR_FAILURE;
}
- trackPairOut->mRtpTransport = transport;
-
- if (transport->mComponents == 2) {
+ if (transceiver->mTransport->mComponents == 2) {
// RTCP MUX or not.
// TODO(bug 1095743): verify that the PTs are consistent with mux.
MOZ_MTLOG(ML_DEBUG, "RTCP-MUX is off");
- trackPairOut->mRtcpTransport = transport;
}
return NS_OK;
}
void
JsepSessionImpl::InitTransport(const SdpMediaSection& msection,
JsepTransport* transport)
@@ -1639,28 +1108,52 @@ JsepSessionImpl::CopyPreviousTransportPa
offerersPreviousSdp,
newOffer,
i) &&
!mRemoteIceIsRestarting
) {
// If newLocal is an offer, this will be the number of components we used
// last time, and if it is an answer, this will be the number of
// components we've decided we're using now.
- size_t numComponents = mTransports[i]->mComponents;
+ JsepTransceiver* transceiver(GetTransceiverForLevel(i));
+ if (!transceiver) {
+ MOZ_ASSERT(false);
+ JSEP_SET_ERROR("No transceiver for level " << i);
+ return NS_ERROR_FAILURE;
+ }
+ size_t numComponents = transceiver->mTransport->mComponents;
nsresult rv = mSdpHelper.CopyTransportParams(
numComponents,
mCurrentLocalDescription->GetMediaSection(i),
&newLocal->GetMediaSection(i));
NS_ENSURE_SUCCESS(rv, rv);
}
}
return NS_OK;
}
+void
+JsepSessionImpl::CopyPreviousMsid(const Sdp& oldLocal, Sdp* newLocal)
+{
+ for (size_t i = 0; i < oldLocal.GetMediaSectionCount(); ++i) {
+ const SdpMediaSection& oldMsection(oldLocal.GetMediaSection(i));
+ SdpMediaSection& newMsection(newLocal->GetMediaSection(i));
+ if (oldMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kMsidAttribute) &&
+ !mSdpHelper.MsectionIsDisabled(newMsection)) {
+ // JSEP says this cannot change, no matter what is happening in JS land.
+ // It can only be updated if there is an intermediate SDP that clears the
+ // msid.
+ newMsection.GetAttributeList().SetAttribute(new SdpMsidAttributeList(
+ oldMsection.GetAttributeList().GetMsid()));
+ }
+ }
+}
+
nsresult
JsepSessionImpl::ParseSdp(const std::string& sdp, UniquePtr<Sdp>* parsedp)
{
UniquePtr<Sdp> parsed = mParser.Parse(sdp);
if (!parsed) {
std::string error = "Failed to parse SDP: ";
mSdpHelper.appendSdpParseErrors(mParser.GetParseErrors(), &error);
JSEP_SET_ERROR(error);
@@ -1710,21 +1203,21 @@ JsepSessionImpl::ParseSdp(const std::str
if (mediaAttrs.HasAttribute(SdpAttribute::kSetupAttribute, true) &&
mediaAttrs.GetSetup().mRole == SdpSetupAttribute::kHoldconn) {
JSEP_SET_ERROR("Description has illegal setup attribute "
"\"holdconn\" in m-section at level "
<< i);
return NS_ERROR_INVALID_ARG;
}
- std::string streamId;
+ std::vector<std::string> streamIds;
std::string trackId;
nsresult rv = mSdpHelper.GetIdsFromMsid(*parsed,
parsed->GetMediaSection(i),
- &streamId,
+ &streamIds,
&trackId);
if (NS_SUCCEEDED(rv)) {
if (trackIds.count(trackId)) {
JSEP_SET_ERROR("track id:" << trackId
<< " appears in more than one m-section at level " << i);
return NS_ERROR_INVALID_ARG;
}
@@ -1767,21 +1260,16 @@ JsepSessionImpl::ParseSdp(const std::str
nsresult
JsepSessionImpl::SetRemoteDescriptionOffer(UniquePtr<Sdp> offer)
{
MOZ_ASSERT(mState == kJsepStateStable);
nsresult rv = ValidateOffer(*offer);
NS_ENSURE_SUCCESS(rv, rv);
- // TODO(bug 1095780): Note that we create remote tracks even when
- // They contain only codecs we can't negotiate or other craziness.
- rv = SetRemoteTracksFromDescription(offer.get());
- NS_ENSURE_SUCCESS(rv, rv);
-
mPendingRemoteDescription = Move(offer);
SetState(kJsepStateHaveRemoteOffer);
return NS_OK;
}
nsresult
JsepSessionImpl::SetRemoteDescriptionAnswer(JsepSdpType type,
@@ -1791,98 +1279,266 @@ JsepSessionImpl::SetRemoteDescriptionAns
mState == kJsepStateHaveRemotePranswer);
mPendingRemoteDescription = Move(answer);
nsresult rv = ValidateAnswer(*mPendingLocalDescription,
*mPendingRemoteDescription);
NS_ENSURE_SUCCESS(rv, rv);
- // TODO(bug 1095780): Note that this creates remote tracks even if
- // we offered sendonly and other side offered sendrecv or recvonly.
- rv = SetRemoteTracksFromDescription(mPendingRemoteDescription.get());
- NS_ENSURE_SUCCESS(rv, rv);
-
rv = HandleNegotiatedSession(mPendingLocalDescription,
mPendingRemoteDescription);
NS_ENSURE_SUCCESS(rv, rv);
mCurrentRemoteDescription = Move(mPendingRemoteDescription);
mCurrentLocalDescription = Move(mPendingLocalDescription);
mWasOffererLastTime = mIsOfferer;
SetState(kJsepStateStable);
return NS_OK;
}
-nsresult
-JsepSessionImpl::SetRemoteTracksFromDescription(const Sdp* remoteDescription)
+static bool
+TrackIdCompare(const JsepTrack& t1, const JsepTrack& t2)
{
- // Unassign all remote tracks
- for (auto& remoteTrack : mRemoteTracks) {
- remoteTrack.mAssignedMLine.reset();
+ return t1.GetTrackId() < t2.GetTrackId();
+}
+
+JsepTransceiver*
+JsepSessionImpl::GetTransceiverForLevel(size_t level)
+{
+ for (RefPtr<JsepTransceiver>& transceiver : mTransceivers) {
+ if (transceiver->HasLevel() && (transceiver->GetLevel() == level)) {
+ return transceiver.get();
+ }
}
- // This will not exist if we're rolling back the first remote description
- if (remoteDescription) {
- size_t numMlines = remoteDescription->GetMediaSectionCount();
- nsresult rv;
-
- // Iterate over the sdp, re-assigning or creating remote tracks as we go
- for (size_t i = 0; i < numMlines; ++i) {
- const SdpMediaSection& msection = remoteDescription->GetMediaSection(i);
-
- if (mSdpHelper.MsectionIsDisabled(msection) || !msection.IsSending()) {
- continue;
- }
-
- std::vector<JsepReceivingTrack>::iterator track;
+ return nullptr;
+}
- if (msection.GetMediaType() == SdpMediaSection::kApplication) {
- // Datachannel doesn't have msid, just search by type
- track = FindUnassignedTrackByType(mRemoteTracks,
- msection.GetMediaType());
- } else {
- std::string streamId;
- std::string trackId;
- rv = GetRemoteIds(*remoteDescription, msection, &streamId, &trackId);
- NS_ENSURE_SUCCESS(rv, rv);
-
- track = FindTrackByIds(mRemoteTracks, streamId, trackId);
+JsepTransceiver*
+JsepSessionImpl::GetTransceiverForLocal(size_t level)
+{
+ if (JsepTransceiver* transceiver = GetTransceiverForLevel(level)) {
+ if (WasMsectionDisabledLastNegotiation(level) && transceiver->IsStopped()) {
+ // Attempt to recycle. If this fails, the old transceiver stays put.
+ transceiver->Disassociate();
+ JsepTransceiver* newTransceiver = FindUnassociatedTransceiver(
+ transceiver->mSending.GetMediaType(), false);
+ if (newTransceiver) {
+ newTransceiver->SetLevel(level);
+ transceiver->ClearLevel();
+ return newTransceiver;
}
+ }
- if (track == mRemoteTracks.end()) {
- RefPtr<JsepTrack> track;
- rv = CreateReceivingTrack(i, *remoteDescription, msection, &track);
- NS_ENSURE_SUCCESS(rv, rv);
+ return transceiver;
+ }
+
+ // There is no transceiver for |level| right now.
- JsepReceivingTrack rtrack;
- rtrack.mTrack = track;
- rtrack.mAssignedMLine = Some(i);
- mRemoteTracks.push_back(rtrack);
- mRemoteTracksAdded.push_back(rtrack);
- } else {
- track->mAssignedMLine = Some(i);
- }
+ for (RefPtr<JsepTransceiver>& transceiver : mTransceivers) {
+ if (!transceiver->IsStopped() && !transceiver->HasLevel()) {
+ transceiver->SetLevel(level);
+ return transceiver.get();
}
}
- // Remove any unassigned remote track ids
- for (size_t i = 0; i < mRemoteTracks.size();) {
- if (!mRemoteTracks[i].mAssignedMLine.isSome()) {
- mRemoteTracksRemoved.push_back(mRemoteTracks[i]);
- mRemoteTracks.erase(mRemoteTracks.begin() + i);
+ return nullptr;
+}
+
+JsepTransceiver*
+JsepSessionImpl::GetTransceiverForRemote(const SdpMediaSection& msection)
+{
+ size_t level = msection.GetLevel();
+ if (JsepTransceiver* transceiver = GetTransceiverForLevel(level)) {
+ if (!WasMsectionDisabledLastNegotiation(level) ||
+ !transceiver->IsStopped()) {
+ return transceiver;
+ }
+ transceiver->Disassociate();
+ transceiver->ClearLevel();
+ }
+
+ // No transceiver for |level|
+
+ JsepTransceiver* transceiver = FindUnassociatedTransceiver(
+ msection.GetMediaType(), true /*magic!*/);
+ if (transceiver) {
+ transceiver->SetLevel(level);
+ return transceiver;
+ }
+
+ // Make a new transceiver
+ RefPtr<JsepTransceiver> newTransceiver(
+ new JsepTransceiver(msection.GetMediaType(),
+ SdpDirectionAttribute::kRecvonly));
+ newTransceiver->SetLevel(level);
+ newTransceiver->SetCreatedBySetRemote();
+ nsresult rv = AddTransceiver(newTransceiver);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ return mTransceivers.back().get();
+}
+
+nsresult
+JsepSessionImpl::UpdateTransceiversFromRemoteDescription(const Sdp& remote)
+{
+ std::vector<JsepTrack> oldRemoteTracks;
+ std::vector<JsepTrack> newRemoteTracks;
+
+ // Iterate over the sdp, updating remote tracks as we go
+ for (size_t i = 0; i < remote.GetMediaSectionCount(); ++i) {
+ const SdpMediaSection& msection = remote.GetMediaSection(i);
+
+ JsepTransceiver* transceiver(GetTransceiverForRemote(msection));
+ if (!transceiver) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!transceiver->mReceiving.GetTrackId().empty()) {
+ oldRemoteTracks.push_back(transceiver->mReceiving);
+ }
+
+ if (!mSdpHelper.MsectionIsDisabled(msection)) {
+ transceiver->Associate(msection.GetAttributeList().GetMid());
} else {
- ++i;
+ transceiver->Disassociate();
+ // This cannot be rolled back.
+ transceiver->Stop();
+ continue;
+ }
+
+ // Interop workaround for endpoints that don't support msid.
+ // If the receiver has no ids, set some initial values, one way or another.
+ if (msection.IsSending() &&
+ msection.GetMediaType() != SdpMediaSection::MediaType::kApplication &&
+ transceiver->mReceiving.GetTrackId().empty()) {
+ std::vector<std::string> streamIds;
+ std::string trackId;
+
+ nsresult rv = GetRemoteIds(remote, msection, &streamIds, &trackId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ transceiver->mReceiving.UpdateTrack(streamIds, trackId);
+ }
+
+ transceiver->mReceiving.UpdateRecvTrack(remote, msection);
+
+ if (!transceiver->mReceiving.GetTrackId().empty()) {
+ newRemoteTracks.push_back(transceiver->mReceiving);
}
}
+ std::sort(oldRemoteTracks.begin(), oldRemoteTracks.end(), TrackIdCompare);
+ std::sort(newRemoteTracks.begin(), newRemoteTracks.end(), TrackIdCompare);
+
+ mRemoteTracksAdded.clear();
+ mRemoteTracksRemoved.clear();
+
+ std::set_difference(
+ oldRemoteTracks.begin(),
+ oldRemoteTracks.end(),
+ newRemoteTracks.begin(),
+ newRemoteTracks.end(),
+ std::inserter(mRemoteTracksRemoved, mRemoteTracksRemoved.begin()),
+ TrackIdCompare);
+
+ std::set_difference(
+ newRemoteTracks.begin(),
+ newRemoteTracks.end(),
+ oldRemoteTracks.begin(),
+ oldRemoteTracks.end(),
+ std::inserter(mRemoteTracksAdded, mRemoteTracksAdded.begin()),
+ TrackIdCompare);
+
return NS_OK;
}
+
+bool
+JsepSessionImpl::WasMsectionDisabledLastNegotiation(size_t level) const
+{
+ const Sdp* answer(GetAnswer());
+
+ if (answer &&
+ (level < answer->GetMediaSectionCount()) &&
+ mSdpHelper.MsectionIsDisabled(answer->GetMediaSection(level))) {
+ return true;
+ }
+
+ return false;
+}
+
+JsepTransceiver*
+JsepSessionImpl::FindUnassociatedTransceiver(
+ SdpMediaSection::MediaType type, bool magic)
+{
+ // Look through transceivers that are not mapped to an m-section
+ for (RefPtr<JsepTransceiver>& transceiver : mTransceivers) {
+ if (!transceiver->IsStopped() &&
+ !transceiver->HasLevel() &&
+ (!magic || transceiver->HasAddTrackMagic()) &&
+ transceiver->mSending.GetMediaType() == type) {
+ return transceiver.get();
+ }
+ }
+
+ return nullptr;
+}
+
+void
+JsepSessionImpl::RollbackLocalOffer()
+{
+ for (size_t i = 0; i < mTransceivers.size(); ++i) {
+ RefPtr<JsepTransceiver>& transceiver(mTransceivers[i]);
+ if (i < mOldTransceivers.size()) {
+ transceiver->Rollback(*mOldTransceivers[i]);
+ continue;
+ }
+
+ RefPtr<JsepTransceiver> temp(
+ new JsepTransceiver(transceiver->mSending.GetMediaType()));
+ transceiver->Rollback(*temp);
+ }
+
+ mOldTransceivers.clear();
+}
+
+void
+JsepSessionImpl::RollbackRemoteOffer()
+{
+ for (size_t i = 0; i < mTransceivers.size(); ++i) {
+ RefPtr<JsepTransceiver>& transceiver(mTransceivers[i]);
+ if (i < mOldTransceivers.size()) {
+ transceiver->Rollback(*mOldTransceivers[i]);
+ continue;
+ }
+
+ // New transceiver!
+ if (!transceiver->HasAddTrackMagic() &&
+ transceiver->WasCreatedBySetRemote()) {
+ transceiver->Stop();
+ transceiver->Disassociate();
+ transceiver->ClearLevel();
+ transceiver->SetRemoved();
+ mTransceivers.erase(mTransceivers.begin() + i);
+ --i;
+ continue;
+ }
+
+ // Transceiver has been "touched" by addTrack; let it live, but unhook it
+ // from everything.
+ RefPtr<JsepTransceiver> temp(
+ new JsepTransceiver(transceiver->mSending.GetMediaType()));
+ transceiver->Rollback(*temp);
+ }
+
+ mOldTransceivers.clear();
+ std::swap(mRemoteTracksAdded, mRemoteTracksRemoved);
+}
+
nsresult
JsepSessionImpl::ValidateLocalDescription(const Sdp& description)
{
// TODO(bug 1095226): Better checking.
if (!mGeneratedLocalDescription) {
JSEP_SET_ERROR("Calling SetLocal without first calling CreateOffer/Answer"
" is not supported.");
return NS_ERROR_UNEXPECTED;
@@ -2121,39 +1777,16 @@ JsepSessionImpl::ValidateAnswer(const Sd
}
}
}
return NS_OK;
}
nsresult
-JsepSessionImpl::CreateReceivingTrack(size_t mline,
- const Sdp& sdp,
- const SdpMediaSection& msection,
- RefPtr<JsepTrack>* track)
-{
- std::string streamId;
- std::string trackId;
-
- nsresult rv = GetRemoteIds(sdp, msection, &streamId, &trackId);
- NS_ENSURE_SUCCESS(rv, rv);
-
- *track = new JsepTrack(msection.GetMediaType(),
- streamId,
- trackId,
- sdp::kRecv);
-
- (*track)->SetCNAME(mSdpHelper.GetCNAME(msection));
- (*track)->PopulateCodecs(mSupportedCodecs.values);
-
- return NS_OK;
-}
-
-nsresult
JsepSessionImpl::CreateGenericSDP(UniquePtr<Sdp>* sdpp)
{
// draft-ietf-rtcweb-jsep-08 Section 5.2.1:
// o The second SDP line MUST be an "o=" line, as specified in
// [RFC4566], Section 5.2. The value of the <username> field SHOULD
// be "-". The value of the <sess-id> field SHOULD be a
// cryptographically random number. To ensure uniqueness, this
// number SHOULD be at least 64 bits long. The value of the <sess-
@@ -2220,32 +1853,16 @@ JsepSessionImpl::SetupIds()
if (!mUuidGen->Generate(&mCNAME)) {
JSEP_SET_ERROR("Failed to generate CNAME");
return NS_ERROR_FAILURE;
}
return NS_OK;
}
-nsresult
-JsepSessionImpl::CreateSsrc(uint32_t* ssrc)
-{
- do {
- SECStatus rv = PK11_GenerateRandom(
- reinterpret_cast<unsigned char*>(ssrc), sizeof(uint32_t));
- if (rv != SECSuccess) {
- JSEP_SET_ERROR("Failed to generate SSRC, error=" << rv);
- return NS_ERROR_FAILURE;
- }
- } while (mSsrcs.count(*ssrc));
- mSsrcs.insert(*ssrc);
-
- return NS_OK;
-}
-
void
JsepSessionImpl::SetupDefaultCodecs()
{
// Supported audio codecs.
// Per jmspeex on IRC:
// For 32KHz sampling, 28 is ok, 32 is good, 40 should be really good
// quality. Note that 1-2Kbps will be wasted on a stereo Opus channel
// with mono input compared to configuring it for mono.
@@ -2469,20 +2086,29 @@ JsepSessionImpl::UpdateDefaultCandidate(
return NS_ERROR_UNEXPECTED;
}
if (level >= sdp->GetMediaSectionCount()) {
return NS_OK;
}
std::string defaultRtcpCandidateAddrCopy(defaultRtcpCandidateAddr);
- if (mState == kJsepStateStable && mTransports[level]->mComponents == 1) {
- // We know we're doing rtcp-mux by now. Don't create an rtcp attr.
- defaultRtcpCandidateAddrCopy = "";
- defaultRtcpCandidatePort = 0;
+ if (mState == kJsepStateStable) {
+ JsepTransceiver* transceiver(GetTransceiverForLevel(level));
+ if (!transceiver) {
+ MOZ_ASSERT(false);
+ JSEP_SET_ERROR("No transceiver for level " << level);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (transceiver->mTransport->mComponents == 1) {
+ // We know we're doing rtcp-mux by now. Don't create an rtcp attr.
+ defaultRtcpCandidateAddrCopy = "";
+ defaultRtcpCandidatePort = 0;
+ }
}
// If offer/answer isn't done, it is too early to tell whether these defaults
// need to be applied to other m-sections.
SdpHelper::BundledMids bundledMids;
if (mState == kJsepStateStable) {
nsresult rv = GetNegotiatedBundledMids(&bundledMids);
if (NS_FAILED(rv)) {
@@ -2547,48 +2173,16 @@ JsepSessionImpl::GetNegotiatedBundledMid
if (!answerSdp) {
return NS_OK;
}
return mSdpHelper.GetBundledMids(*answerSdp, bundledMids);
}
-nsresult
-JsepSessionImpl::EnableOfferMsection(SdpMediaSection* msection)
-{
- // We assert here because adding rtcp-mux to a non-disabled m-section that
- // did not already have rtcp-mux can cause problems.
- MOZ_ASSERT(mSdpHelper.MsectionIsDisabled(*msection));
-
- msection->SetPort(9);
-
- // We don't do this in AddTransportAttributes because that is also used for
- // making answers, and we don't want to unconditionally set rtcp-mux there.
- if (mSdpHelper.HasRtcp(msection->GetProtocol())) {
- // Set RTCP-MUX.
- msection->GetAttributeList().SetAttribute(
- new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
- }
-
- nsresult rv = AddTransportAttributes(msection, SdpSetupAttribute::kActpass);
- NS_ENSURE_SUCCESS(rv, rv);
-
- rv = SetRecvonlySsrc(msection);
- NS_ENSURE_SUCCESS(rv, rv);
-
- AddExtmap(msection);
-
- std::ostringstream osMid;
- osMid << "sdparta_" << msection->GetLevel();
- AddMid(osMid.str(), msection);
-
- return NS_OK;
-}
-
mozilla::Sdp*
JsepSessionImpl::GetParsedLocalDescription(JsepDescriptionPendingOrCurrent type) const
{
if (type == kJsepDescriptionPending) {
return mPendingLocalDescription.get();
} else if (mPendingLocalDescription &&
type == kJsepDescriptionPendingOrCurrent) {
return mPendingLocalDescription.get();
@@ -2625,20 +2219,78 @@ JsepSessionImpl::Close()
const std::string
JsepSessionImpl::GetLastError() const
{
return mLastError;
}
bool
-JsepSessionImpl::AllLocalTracksAreAssigned() const
+JsepSessionImpl::CheckNegotiationNeeded() const
{
- for (const auto& localTrack : mLocalTracks) {
- if (!localTrack.mAssignedMLine.isSome()) {
- return false;
+ MOZ_ASSERT(mState == kJsepStateStable);
+
+ for (const auto& transceiver : mTransceivers) {
+ if (transceiver->IsStopped()) {
+ if (transceiver->IsAssociated()) {
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: Negotiation needed because of "
+ "stopped transceiver that still has a mid.");
+ return true;
+ }
+ continue;
+ }
+
+ if (!transceiver->IsAssociated()) {
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: Negotiation needed because of "
+ "unassociated (but not stopped) transceiver.");
+ return true;
+ }
+
+ if (!mCurrentLocalDescription || !mCurrentRemoteDescription) {
+ MOZ_CRASH("Transceivers should not be associated if we're in stable "
+ "before the first negotiation.");
+ continue;
+ }
+
+ if (!transceiver->HasLevel()) {
+ MOZ_CRASH("Associated transceivers should always have a level.");
+ continue;
+ }
+
+ if (transceiver->mSending.GetMediaType() == SdpMediaSection::kApplication) {
+ continue;
+ }
+
+ size_t level = transceiver->GetLevel();
+ const SdpMediaSection& local =
+ mCurrentLocalDescription->GetMediaSection(level);
+ const SdpMediaSection& remote =
+ mCurrentRemoteDescription->GetMediaSection(level);
+
+ if (!local.GetAttributeList().HasAttribute(SdpAttribute::kMsidAttribute) &&
+ (transceiver->mJsDirection & sdp::kSend)) {
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: Negotiation needed because of "
+ "lack of a=msid, and transceiver is sending.");
+ return true;
+ }
+
+ if (IsOfferer()) {
+ if ((local.GetDirection() != transceiver->mJsDirection) &&
+ reverse(remote.GetDirection()) != transceiver->mJsDirection) {
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: Negotiation needed because "
+ "the direction on our offer, and the remote answer, does not "
+ "match the direction on a transceiver.");
+ return true;
+ }
+ } else if (local.GetDirection() !=
+ (transceiver->mJsDirection & reverse(remote.GetDirection()))) {
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: Negotiation needed because "
+ "the direction on our answer doesn't match the direction on a "
+ "transceiver, even though the remote offer would have allowed "
+ "it.");
+ return true;
}
}
- return true;
+ return false;
}
} // namespace mozilla
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
@@ -5,19 +5,19 @@
#ifndef _JSEPSESSIONIMPL_H_
#define _JSEPSESSIONIMPL_H_
#include <set>
#include <string>
#include <vector>
#include "signaling/src/jsep/JsepCodecDescription.h"
-#include "signaling/src/jsep/JsepTrack.h"
#include "signaling/src/jsep/JsepSession.h"
#include "signaling/src/jsep/JsepTrack.h"
+#include "signaling/src/jsep/SsrcGenerator.h"
#include "signaling/src/sdp/SipccSdpParser.h"
#include "signaling/src/sdp/SdpHelper.h"
#include "signaling/src/common/PtrVector.h"
namespace mozilla {
class JsepUuidGenerator
{
@@ -42,21 +42,16 @@ public:
mUuidGen(Move(uuidgen)),
mSdpHelper(&mLastError)
{
}
// Implement JsepSession methods.
virtual nsresult Init() override;
- virtual nsresult AddTrack(const RefPtr<JsepTrack>& track) override;
-
- virtual nsresult RemoveTrack(const std::string& streamId,
- const std::string& trackId) override;
-
virtual nsresult SetIceCredentials(const std::string& ufrag,
const std::string& pwd) override;
virtual const std::string& GetUfrag() const override { return mIceUfrag; }
virtual const std::string& GetPwd() const override { return mIcePwd; }
nsresult SetBundlePolicy(JsepBundlePolicy policy) override;
virtual bool
RemoteIsIceLite() const override
@@ -93,40 +88,19 @@ public:
SdpDirectionAttribute::Direction::kSendrecv) override;
virtual std::vector<JsepCodecDescription*>&
Codecs() override
{
return mSupportedCodecs.values;
}
- virtual nsresult ReplaceTrack(const std::string& oldStreamId,
- const std::string& oldTrackId,
- const std::string& newStreamId,
- const std::string& newTrackId) override;
-
- virtual nsresult SetParameters(
- const std::string& streamId,
- const std::string& trackId,
- const std::vector<JsepTrack::JsConstraints>& constraints) override;
+ virtual std::vector<JsepTrack> GetRemoteTracksAdded() const override;
- virtual nsresult GetParameters(
- const std::string& streamId,
- const std::string& trackId,
- std::vector<JsepTrack::JsConstraints>* outConstraints) override;
-
- virtual std::vector<RefPtr<JsepTrack>> GetLocalTracks() const override;
-
- virtual std::vector<RefPtr<JsepTrack>> GetRemoteTracks() const override;
-
- virtual std::vector<RefPtr<JsepTrack>>
- GetRemoteTracksAdded() const override;
-
- virtual std::vector<RefPtr<JsepTrack>>
- GetRemoteTracksRemoved() const override;
+ virtual std::vector<JsepTrack> GetRemoteTracksRemoved() const override;
virtual nsresult CreateOffer(const JsepOfferOptions& options,
std::string* offer) override;
virtual nsresult CreateAnswer(const JsepAnswerOptions& options,
std::string* answer) override;
virtual std::string GetLocalDescription(JsepDescriptionPendingOrCurrent type)
@@ -170,157 +144,128 @@ public:
}
virtual bool
IsOfferer() const override
{
return mIsOfferer;
}
- // Access transports.
- virtual std::vector<RefPtr<JsepTransport>>
- GetTransports() const override
- {
- return mTransports;
+ virtual const std::vector<RefPtr<JsepTransceiver>>&
+ GetTransceivers() const override {
+ return mTransceivers;
}
- virtual std::vector<JsepTrackPair>
- GetNegotiatedTrackPairs() const override
- {
- return mNegotiatedTrackPairs;
+ virtual std::vector<RefPtr<JsepTransceiver>>&
+ GetTransceivers() override {
+ return mTransceivers;
}
- virtual bool AllLocalTracksAreAssigned() const override;
+ virtual nsresult AddTransceiver(RefPtr<JsepTransceiver> transceiver) override;
+
+ virtual bool CheckNegotiationNeeded() const override;
private:
struct JsepDtlsFingerprint {
std::string mAlgorithm;
std::vector<uint8_t> mValue;
};
- struct JsepSendingTrack {
- RefPtr<JsepTrack> mTrack;
- Maybe<size_t> mAssignedMLine;
- };
-
- struct JsepReceivingTrack {
- RefPtr<JsepTrack> mTrack;
- Maybe<size_t> mAssignedMLine;
- };
-
// Non-const so it can set mLastError
nsresult CreateGenericSDP(UniquePtr<Sdp>* sdp);
- void AddExtmap(SdpMediaSection* msection) const;
+ void AddExtmap(SdpMediaSection* msection);
void AddMid(const std::string& mid, SdpMediaSection* msection) const;
- const std::vector<SdpExtmapAttributeList::Extmap>* GetRtpExtensions(
- SdpMediaSection::MediaType type) const;
+ std::vector<SdpExtmapAttributeList::Extmap> GetRtpExtensions(
+ const SdpMediaSection& msection);
void AddCommonExtmaps(const SdpMediaSection& remoteMsection,
SdpMediaSection* msection);
nsresult SetupIds();
- nsresult CreateSsrc(uint32_t* ssrc);
void SetupDefaultCodecs();
void SetupDefaultRtpExtensions();
void SetState(JsepSignalingState state);
// Non-const so it can set mLastError
nsresult ParseSdp(const std::string& sdp, UniquePtr<Sdp>* parsedp);
nsresult SetLocalDescriptionOffer(UniquePtr<Sdp> offer);
nsresult SetLocalDescriptionAnswer(JsepSdpType type, UniquePtr<Sdp> answer);
nsresult SetRemoteDescriptionOffer(UniquePtr<Sdp> offer);
nsresult SetRemoteDescriptionAnswer(JsepSdpType type, UniquePtr<Sdp> answer);
nsresult ValidateLocalDescription(const Sdp& description);
nsresult ValidateRemoteDescription(const Sdp& description);
nsresult ValidateOffer(const Sdp& offer);
nsresult ValidateAnswer(const Sdp& offer, const Sdp& answer);
- nsresult SetRemoteTracksFromDescription(const Sdp* remoteDescription);
- // Non-const because we use our Uuid generator
- nsresult CreateReceivingTrack(size_t mline,
- const Sdp& sdp,
- const SdpMediaSection& msection,
- RefPtr<JsepTrack>* track);
+ nsresult UpdateTransceiversFromRemoteDescription(const Sdp& remote);
+ bool WasMsectionDisabledLastNegotiation(size_t level) const;
+ JsepTransceiver* GetTransceiverForLevel(size_t level);
+ JsepTransceiver* GetTransceiverForLocal(size_t level);
+ JsepTransceiver* GetTransceiverForRemote(const SdpMediaSection& msection);
+ // The w3c and IETF specs have a lot of "magical" behavior that happens when
+ // addTrack is used. This was a deliberate design choice. Sadface.
+ JsepTransceiver* FindUnassociatedTransceiver(
+ SdpMediaSection::MediaType type, bool magic);
+ // Called for rollback of local description
+ void RollbackLocalOffer();
+ // Called for rollback of remote description
+ void RollbackRemoteOffer();
nsresult HandleNegotiatedSession(const UniquePtr<Sdp>& local,
const UniquePtr<Sdp>& remote);
nsresult AddTransportAttributes(SdpMediaSection* msection,
SdpSetupAttribute::Role dtlsRole);
nsresult CopyPreviousTransportParams(const Sdp& oldAnswer,
const Sdp& offerersPreviousSdp,
const Sdp& newOffer,
Sdp* newLocal);
- nsresult SetupOfferMSections(const JsepOfferOptions& options, Sdp* sdp);
- // Non-const so it can assign m-line index to tracks
- nsresult SetupOfferMSectionsByType(SdpMediaSection::MediaType type,
- const Maybe<size_t>& offerToReceive,
- Sdp* sdp);
- nsresult BindLocalTracks(SdpMediaSection::MediaType mediatype,
- Sdp* sdp);
- nsresult BindRemoteTracks(SdpMediaSection::MediaType mediatype,
- Sdp* sdp,
- size_t* offerToReceive);
- nsresult SetRecvAsNeededOrDisable(SdpMediaSection::MediaType mediatype,
- Sdp* sdp,
- size_t* offerToRecv);
- void SetupOfferToReceiveMsection(SdpMediaSection* offer);
- nsresult AddRecvonlyMsections(SdpMediaSection::MediaType mediatype,
- size_t count,
- Sdp* sdp);
- nsresult AddReofferMsections(const Sdp& oldLocalSdp,
- const Sdp& oldAnswer,
- Sdp* newSdp);
+ void CopyPreviousMsid(const Sdp& oldLocal, Sdp* newLocal);
+ void EnsureMsid(Sdp* remote);
void SetupBundle(Sdp* sdp) const;
nsresult GetRemoteIds(const Sdp& sdp,
const SdpMediaSection& msection,
- std::string* streamId,
+ std::vector<std::string>* streamIds,
std::string* trackId);
- nsresult CreateOfferMSection(SdpMediaSection::MediaType type,
- SdpMediaSection::Protocol proto,
- SdpDirectionAttribute::Direction direction,
- Sdp* sdp);
- nsresult GetFreeMsectionForSend(SdpMediaSection::MediaType type,
- Sdp* sdp,
- SdpMediaSection** msection);
- nsresult CreateAnswerMSection(const JsepAnswerOptions& options,
- size_t mlineIndex,
+ nsresult CreateOfferMsection(const JsepOfferOptions& options,
+ JsepTransceiver& transceiver,
+ Sdp* local);
+ nsresult CreateAnswerMsection(const JsepAnswerOptions& options,
+ JsepTransceiver& transceiver,
const SdpMediaSection& remoteMsection,
Sdp* sdp);
- nsresult SetRecvonlySsrc(SdpMediaSection* msection);
- nsresult BindMatchingLocalTrackToAnswer(SdpMediaSection* msection);
- nsresult BindMatchingRemoteTrackToAnswer(SdpMediaSection* msection);
+ nsresult CreateReceivingTrack(const Sdp& sdp,
+ const SdpMediaSection& msection,
+ RefPtr<JsepTrack>* track);
nsresult DetermineAnswererSetupRole(const SdpMediaSection& remoteMsection,
SdpSetupAttribute::Role* rolep);
- nsresult MakeNegotiatedTrackPair(const SdpMediaSection& remote,
- const SdpMediaSection& local,
- const RefPtr<JsepTransport>& transport,
- bool usingBundle,
- size_t transportLevel,
- JsepTrackPair* trackPairOut);
+ nsresult MakeNegotiatedTransceiver(const SdpMediaSection& remote,
+ const SdpMediaSection& local,
+ bool usingBundle,
+ size_t transportLevel,
+ JsepTransceiver* transceiverOut);
void InitTransport(const SdpMediaSection& msection, JsepTransport* transport);
nsresult FinalizeTransport(const SdpAttributeList& remote,
const SdpAttributeList& answer,
const RefPtr<JsepTransport>& transport);
nsresult GetNegotiatedBundledMids(SdpHelper::BundledMids* bundledMids);
nsresult EnableOfferMsection(SdpMediaSection* msection);
mozilla::Sdp* GetParsedLocalDescription(JsepDescriptionPendingOrCurrent type)
const;
mozilla::Sdp* GetParsedRemoteDescription(JsepDescriptionPendingOrCurrent type)
const;
const Sdp* GetAnswer() const;
- std::vector<JsepSendingTrack> mLocalTracks;
- std::vector<JsepReceivingTrack> mRemoteTracks;
// By the most recent SetRemoteDescription
- std::vector<JsepReceivingTrack> mRemoteTracksAdded;
- std::vector<JsepReceivingTrack> mRemoteTracksRemoved;
- std::vector<RefPtr<JsepTransport> > mTransports;
- // So we can rollback
- std::vector<RefPtr<JsepTransport> > mOldTransports;
- std::vector<JsepTrackPair> mNegotiatedTrackPairs;
+ std::vector<JsepTrack> mRemoteTracksAdded;
+ std::vector<JsepTrack> mRemoteTracksRemoved;
+ // !!!NOT INDEXED BY LEVEL!!! These are in the order they were created in. The
+ // level mapping is done with JsepTransceiver::mLevel.
+ std::vector<RefPtr<JsepTransceiver>> mTransceivers;
+ // So we can rollback. Not as simple as just going back to the old, though...
+ std::vector<RefPtr<JsepTransceiver>> mOldTransceivers;
bool mIsOfferer;
bool mWasOffererLastTime;
bool mIceControlling;
std::string mIceUfrag;
std::string mIcePwd;
bool mRemoteIsIceLite;
bool mRemoteIceIsRestarting;
@@ -328,30 +273,27 @@ private:
JsepBundlePolicy mBundlePolicy;
std::vector<JsepDtlsFingerprint> mDtlsFingerprints;
uint64_t mSessionId;
uint64_t mSessionVersion;
std::vector<SdpExtmapAttributeList::Extmap> mAudioRtpExtensions;
std::vector<SdpExtmapAttributeList::Extmap> mVideoRtpExtensions;
UniquePtr<JsepUuidGenerator> mUuidGen;
std::string mDefaultRemoteStreamId;
- std::map<size_t, std::string> mDefaultRemoteTrackIdsByLevel;
std::string mCNAME;
// Used to prevent duplicate local SSRCs. Not used to prevent local/remote or
// remote-only duplication, which will be important for EKT but not now.
std::set<uint32_t> mSsrcs;
- // When an m-section doesn't have a local track, it still needs an ssrc, which
- // is stored here.
- std::vector<uint32_t> mRecvonlySsrcs;
UniquePtr<Sdp> mGeneratedLocalDescription; // Created but not set.
UniquePtr<Sdp> mCurrentLocalDescription;
UniquePtr<Sdp> mCurrentRemoteDescription;
UniquePtr<Sdp> mPendingLocalDescription;
UniquePtr<Sdp> mPendingRemoteDescription;
PtrVector<JsepCodecDescription> mSupportedCodecs;
std::string mLastError;
SipccSdpParser mParser;
SdpHelper mSdpHelper;
+ SsrcGenerator mSsrcGenerator;
};
} // namespace mozilla
#endif
--- a/media/webrtc/signaling/src/jsep/JsepTrack.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.cpp
@@ -2,21 +2,22 @@
* 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 "signaling/src/jsep/JsepTrack.h"
#include "signaling/src/jsep/JsepCodecDescription.h"
#include "signaling/src/jsep/JsepTrackEncoding.h"
#include <algorithm>
+#include <iostream>
namespace mozilla
{
void
-JsepTrack::GetNegotiatedPayloadTypes(std::vector<uint16_t>* payloadTypes)
+JsepTrack::GetNegotiatedPayloadTypes(std::vector<uint16_t>* payloadTypes) const
{
if (!mNegotiatedDetails) {
return;
}
for (const auto* encoding : mNegotiatedDetails->mEncodings.values) {
GetPayloadTypes(encoding->GetCodecs(), payloadTypes);
}
@@ -84,80 +85,113 @@ JsepTrack::EnsureNoDuplicatePayloadTypes
codec->mDefaultPt = os.str();
break;
}
}
}
}
void
+JsepTrack::EnsureSsrcs(SsrcGenerator& ssrcGenerator)
+{
+ if (mSsrcs.empty()) {
+ uint32_t ssrc;
+ if (!ssrcGenerator.GenerateSsrc(&ssrc)) {
+ return;
+ }
+ mSsrcs.push_back(ssrc);
+ }
+}
+
+void
JsepTrack::PopulateCodecs(const std::vector<JsepCodecDescription*>& prototype)
{
for (const JsepCodecDescription* prototypeCodec : prototype) {
if (prototypeCodec->mType == mType) {
mPrototypeCodecs.values.push_back(prototypeCodec->Clone());
mPrototypeCodecs.values.back()->mDirection = mDirection;
}
}
EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs.values);
}
void
-JsepTrack::AddToOffer(SdpMediaSection* offer) const
+JsepTrack::AddToOffer(SsrcGenerator& ssrcGenerator, SdpMediaSection* offer)
{
AddToMsection(mPrototypeCodecs.values, offer);
+
if (mDirection == sdp::kSend) {
- AddToMsection(mJsEncodeConstraints, sdp::kSend, offer);
+ std::vector<JsConstraints> constraints;
+ if (offer->IsSending()) {
+ constraints = mJsEncodeConstraints;
+ }
+ AddToMsection(mJsEncodeConstraints, sdp::kSend, ssrcGenerator, offer);
}
}
void
JsepTrack::AddToAnswer(const SdpMediaSection& offer,
- SdpMediaSection* answer) const
+ SsrcGenerator& ssrcGenerator,
+ SdpMediaSection* answer)
{
// We do not modify mPrototypeCodecs here, since we're only creating an
// answer. Once offer/answer concludes, we will update mPrototypeCodecs.
PtrVector<JsepCodecDescription> codecs;
codecs.values = GetCodecClones();
NegotiateCodecs(offer, &codecs.values);
if (codecs.values.empty()) {
return;
}
AddToMsection(codecs.values, answer);
if (mDirection == sdp::kSend) {
- std::vector<JsConstraints> constraints(mJsEncodeConstraints);
- std::vector<SdpRidAttributeList::Rid> rids;
- GetRids(offer, sdp::kRecv, &rids);
- NegotiateRids(rids, &constraints);
- AddToMsection(constraints, sdp::kSend, answer);
+ std::vector<JsConstraints> constraints;
+ if (answer->IsSending()) {
+ constraints = mJsEncodeConstraints;
+ std::vector<SdpRidAttributeList::Rid> rids;
+ GetRids(offer, sdp::kRecv, &rids);
+ NegotiateRids(rids, &constraints);
+ }
+ AddToMsection(constraints, sdp::kSend, ssrcGenerator, answer);
}
}
void
+JsepTrack::SetJsConstraints(
+ const std::vector<JsConstraints>& constraintsList)
+{
+ mJsEncodeConstraints = constraintsList;
+}
+
+void
JsepTrack::AddToMsection(const std::vector<JsepCodecDescription*>& codecs,
- SdpMediaSection* msection) const
+ SdpMediaSection* msection)
{
MOZ_ASSERT(msection->GetMediaType() == mType);
MOZ_ASSERT(!codecs.empty());
for (const JsepCodecDescription* codec : codecs) {
codec->AddToMediaSection(*msection);
}
- if (mDirection == sdp::kSend) {
- if (msection->GetMediaType() != SdpMediaSection::kApplication) {
- msection->SetSsrcs(mSsrcs, mCNAME);
- msection->AddMsid(mStreamId, mTrackId);
+ if (mDirection == sdp::kSend && mType != SdpMediaSection::kApplication) {
+ if (msection->IsSending()) {
+ if (mStreamIds.empty()) {
+ msection->AddMsid("-", mTrackId);
+ } else {
+ for (const std::string& streamId : mStreamIds) {
+ msection->AddMsid(streamId, mTrackId);
+ // TODO() Interop hack; older Firefox barfs if there is more than one
+ // msid. Remove when safe.
+ break;
+ }
+ }
}
- msection->SetSending(true);
- } else {
- msection->SetReceiving(true);
}
}
// Updates the |id| values in |constraintsList| with the rid values in |rids|,
// where necessary.
void
JsepTrack::NegotiateRids(const std::vector<SdpRidAttributeList::Rid>& rids,
std::vector<JsConstraints>* constraintsList) const
@@ -168,20 +202,42 @@ JsepTrack::NegotiateRids(const std::vect
JsConstraints* constraints = FindConstraints("", *constraintsList);
if (constraints) {
constraints->rid = rid.id;
}
}
}
}
-/* static */
+void
+JsepTrack::UpdateSsrcs(SsrcGenerator& ssrcGenerator, size_t encodings)
+{
+ MOZ_ASSERT(mDirection == sdp::kSend);
+ MOZ_ASSERT(mType != SdpMediaSection::kApplication);
+ size_t numSsrcs = std::max<size_t>(encodings, 1U);
+
+ // Right now, the spec does not permit changing the number of encodings after
+ // the initial creation of the sender, so we don't need to worry about things
+ // like a new encoding inserted in between two pre-existing encodings.
+ while (mSsrcs.size() < numSsrcs) {
+ uint32_t ssrc;
+ if (!ssrcGenerator.GenerateSsrc(&ssrc)) {
+ return;
+ }
+ mSsrcs.push_back(ssrc);
+ }
+
+ mSsrcs.resize(numSsrcs);
+ MOZ_ASSERT(!mSsrcs.empty());
+}
+
void
JsepTrack::AddToMsection(const std::vector<JsConstraints>& constraintsList,
sdp::Direction direction,
+ SsrcGenerator& ssrcGenerator,
SdpMediaSection* msection)
{
UniquePtr<SdpSimulcastAttribute> simulcast(new SdpSimulcastAttribute);
UniquePtr<SdpRidAttributeList> rids(new SdpRidAttributeList);
for (const JsConstraints& constraints : constraintsList) {
if (!constraints.rid.empty()) {
SdpRidAttributeList::Rid rid;
rid.id = constraints.rid;
@@ -193,19 +249,26 @@ JsepTrack::AddToMsection(const std::vect
if (direction == sdp::kSend) {
simulcast->sendVersions.push_back(version);
} else {
simulcast->recvVersions.push_back(version);
}
}
}
- if (!rids->mRids.empty()) {
+ if (rids->mRids.size() > 1) {
msection->GetAttributeList().SetAttribute(simulcast.release());
- msection->GetAttributeList().SetAttribute(rids.release());
+ if (!rids->mRids.empty()) {
+ msection->GetAttributeList().SetAttribute(rids.release());
+ }
+ }
+
+ if (mType != SdpMediaSection::kApplication && mDirection == sdp::kSend) {
+ UpdateSsrcs(ssrcGenerator, constraintsList.size());
+ msection->SetSsrcs(mSsrcs, mCNAME);
}
}
void
JsepTrack::GetRids(const SdpMediaSection& msection,
sdp::Direction direction,
std::vector<SdpRidAttributeList::Rid>* rids) const
{
@@ -276,16 +339,17 @@ JsepTrack::CreateEncodings(
}
size_t max_streams = 1;
if (!mJsEncodeConstraints.empty()) {
max_streams = std::min(rids.size(), mJsEncodeConstraints.size());
}
// Drop SSRCs if less RIDs were offered than we have encoding constraints
+ // Just in case.
if (mSsrcs.size() > max_streams) {
mSsrcs.resize(max_streams);
}
// For each stream make sure we have an encoding, and configure
// that encoding appropriately.
for (size_t i = 0; i < max_streams; ++i) {
if (i == negotiatedDetails->mEncodings.values.size()) {
@@ -329,16 +393,17 @@ CompareCodec(const JsepCodecDescription*
}
void
JsepTrack::NegotiateCodecs(
const SdpMediaSection& remote,
std::vector<JsepCodecDescription*>* codecs,
std::map<std::string, std::string>* formatChanges) const
{
+ MOZ_ASSERT(codecs->size());
PtrVector<JsepCodecDescription> unnegotiatedCodecs;
std::swap(unnegotiatedCodecs.values, *codecs);
// Outer loop establishes the remote side's preference
for (const std::string& fmt : remote.GetFormats()) {
for (size_t i = 0; i < unnegotiatedCodecs.values.size(); ++i) {
JsepCodecDescription* codec = unnegotiatedCodecs.values[i];
if (!codec || !codec->mEnabled || !codec->Matches(fmt, remote)) {
@@ -483,41 +548,32 @@ JsepTrack::Negotiate(const SdpMediaSecti
}
if (direction & mDirection) {
negotiatedDetails->mExtmap[extmapAttr.extensionname] = extmapAttr;
}
}
}
- if (mDirection == sdp::kRecv) {
- mSsrcs.clear();
- if (remote.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
- for (auto& ssrcAttr : remote.GetAttributeList().GetSsrc().mSsrcs) {
- AddSsrc(ssrcAttr.ssrc);
- }
- }
- }
-
mNegotiatedDetails = Move(negotiatedDetails);
}
// When doing bundle, if all else fails we can try to figure out which m-line a
// given RTP packet belongs to by looking at the payload type field. This only
// works, however, if that payload type appeared in only one m-section.
// We figure that out here.
/* static */
void
-JsepTrack::SetUniquePayloadTypes(const std::vector<RefPtr<JsepTrack>>& tracks)
+JsepTrack::SetUniquePayloadTypes(std::vector<JsepTrack*>& tracks)
{
// Maps to track details if no other track contains the payload type,
// otherwise maps to nullptr.
std::map<uint16_t, JsepTrackNegotiatedDetails*> payloadTypeToDetailsMap;
- for (const RefPtr<JsepTrack>& track : tracks) {
+ for (JsepTrack* track : tracks) {
if (track->GetMediaType() == SdpMediaSection::kApplication) {
continue;
}
auto* details = track->GetNegotiatedDetails();
if (!details) {
// Can happen if negotiation fails on a track
continue;
--- a/media/webrtc/signaling/src/jsep/JsepTrack.h
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.h
@@ -6,38 +6,49 @@
#define _JSEPTRACK_H_
#include <functional>
#include <algorithm>
#include <string>
#include <map>
#include <set>
-#include <mozilla/RefPtr.h>
+#include <mozilla/OwningNonNull.h>
#include <mozilla/UniquePtr.h>
#include <mozilla/Maybe.h>
#include "nsISupportsImpl.h"
#include "nsError.h"
#include "signaling/src/jsep/JsepTransport.h"
#include "signaling/src/jsep/JsepTrackEncoding.h"
+#include "signaling/src/jsep/SsrcGenerator.h"
#include "signaling/src/sdp/Sdp.h"
#include "signaling/src/sdp/SdpAttribute.h"
#include "signaling/src/sdp/SdpMediaSection.h"
#include "signaling/src/common/PtrVector.h"
namespace mozilla {
class JsepTrackNegotiatedDetails
{
public:
JsepTrackNegotiatedDetails() :
mTias(0)
{}
+ JsepTrackNegotiatedDetails(const JsepTrackNegotiatedDetails& orig) :
+ mExtmap(orig.mExtmap),
+ mUniquePayloadTypes(orig.mUniquePayloadTypes),
+ mTias(orig.mTias)
+ {
+ for (const JsepTrackEncoding* encoding : orig.mEncodings.values) {
+ mEncodings.values.push_back(new JsepTrackEncoding(*encoding));
+ }
+ }
+
size_t
GetEncodingCount() const
{
return mEncodings.values.size();
}
const JsepTrackEncoding&
GetEncoding(size_t index) const
@@ -83,56 +94,108 @@ private:
PtrVector<JsepTrackEncoding> mEncodings;
uint32_t mTias; // bits per second
};
class JsepTrack
{
public:
JsepTrack(mozilla::SdpMediaSection::MediaType type,
- const std::string& streamid,
- const std::string& trackid,
- sdp::Direction direction = sdp::kSend)
+ sdp::Direction direction)
: mType(type),
- mStreamId(streamid),
- mTrackId(trackid),
mDirection(direction),
mActive(false)
- {}
+ {
+ }
+
+ void UpdateTrack(const std::vector<std::string>& streamIds,
+ const std::string& trackId)
+ {
+ mStreamIds = streamIds;
+ mTrackId = trackId;
+ }
+
+ void ClearTrack()
+ {
+ mStreamIds.clear();
+ mTrackId.clear();
+ }
+
+ void UpdateRecvTrack(const Sdp& sdp, const SdpMediaSection& msection)
+ {
+ MOZ_ASSERT(mDirection == sdp::kRecv);
+ std::string error;
+ SdpHelper helper(&error);
+
+ if (msection.IsSending()) {
+ if (msection.GetMediaType() != SdpMediaSection::MediaType::kApplication) {
+ (void)helper.GetIdsFromMsid(sdp, msection, &mStreamIds, &mTrackId);
+ }
+ }
+
+ // We do this whether or not the track is active
+ SetCNAME(helper.GetCNAME(msection));
+ mSsrcs.clear();
+ if (msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kSsrcAttribute)) {
+ for (auto& ssrcAttr : msection.GetAttributeList().GetSsrc().mSsrcs) {
+ mSsrcs.push_back(ssrcAttr.ssrc);
+ }
+ }
+ }
+
+ JsepTrack(const JsepTrack& orig)
+ {
+ *this = orig;
+ }
+
+ JsepTrack(JsepTrack&& orig) = default;
+ JsepTrack& operator=(JsepTrack&& rhs) = default;
+
+ JsepTrack& operator=(const JsepTrack& rhs)
+ {
+ if (this != &rhs) {
+ mType = rhs.mType;
+ mStreamIds = rhs.mStreamIds;
+ mTrackId = rhs.mTrackId;
+ mCNAME = rhs.mCNAME;
+ mDirection = rhs.mDirection;
+ mJsEncodeConstraints = rhs.mJsEncodeConstraints;
+ mSsrcs = rhs.mSsrcs;
+ mActive = rhs.mActive;
+
+ for (const JsepCodecDescription* codec : rhs.mPrototypeCodecs.values) {
+ mPrototypeCodecs.values.push_back(codec->Clone());
+ }
+ if (rhs.mNegotiatedDetails) {
+ mNegotiatedDetails.reset(
+ new JsepTrackNegotiatedDetails(*rhs.mNegotiatedDetails));
+ }
+ }
+ return *this;
+ }
virtual mozilla::SdpMediaSection::MediaType
GetMediaType() const
{
return mType;
}
- virtual const std::string&
- GetStreamId() const
+ virtual const std::vector<std::string>&
+ GetStreamIds() const
{
- return mStreamId;
- }
-
- virtual void
- SetStreamId(const std::string& id)
- {
- mStreamId = id;
+ return mStreamIds;
}
virtual const std::string&
GetTrackId() const
{
return mTrackId;
}
- virtual void
- SetTrackId(const std::string& id)
- {
- mTrackId = id;
- }
-
virtual const std::string&
GetCNAME() const
{
return mCNAME;
}
virtual void
SetCNAME(const std::string& cname)
@@ -147,23 +210,17 @@ public:
}
virtual const std::vector<uint32_t>&
GetSsrcs() const
{
return mSsrcs;
}
- virtual void
- AddSsrc(uint32_t ssrc)
- {
- if (mType != SdpMediaSection::kApplication) {
- mSsrcs.push_back(ssrc);
- }
- }
+ virtual void EnsureSsrcs(SsrcGenerator& ssrcGenerator);
bool
GetActive() const
{
return mActive;
}
void
@@ -184,24 +241,28 @@ public:
template <class BinaryPredicate>
void SortCodecs(BinaryPredicate sorter)
{
std::stable_sort(mPrototypeCodecs.values.begin(),
mPrototypeCodecs.values.end(), sorter);
}
- virtual void AddToOffer(SdpMediaSection* offer) const;
+ // These two are non-const because this is where ssrcs are chosen.
+ virtual void AddToOffer(SsrcGenerator& ssrcGenerator,
+ SdpMediaSection* offer);
virtual void AddToAnswer(const SdpMediaSection& offer,
- SdpMediaSection* answer) const;
+ SsrcGenerator& ssrcGenerator,
+ SdpMediaSection* answer);
+
virtual void Negotiate(const SdpMediaSection& answer,
const SdpMediaSection& remote);
- static void SetUniquePayloadTypes(
- const std::vector<RefPtr<JsepTrack>>& tracks);
- virtual void GetNegotiatedPayloadTypes(std::vector<uint16_t>* payloadTypes);
+ static void SetUniquePayloadTypes(std::vector<JsepTrack*>& tracks);
+ virtual void GetNegotiatedPayloadTypes(
+ std::vector<uint16_t>* payloadTypes) const;
// This will be set when negotiation is carried out.
virtual const JsepTrackNegotiatedDetails*
GetNegotiatedDetails() const
{
if (mNegotiatedDetails) {
return mNegotiatedDetails.get();
}
@@ -218,53 +279,47 @@ public:
}
virtual void
ClearNegotiatedDetails()
{
mNegotiatedDetails.reset();
}
- NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JsepTrack);
-
struct JsConstraints
{
std::string rid;
EncodingConstraints constraints;
};
- void SetJsConstraints(const std::vector<JsConstraints>& constraintsList)
- {
- mJsEncodeConstraints = constraintsList;
- }
+ void SetJsConstraints(const std::vector<JsConstraints>& constraintsList);
void GetJsConstraints(std::vector<JsConstraints>* outConstraintsList) const
{
MOZ_ASSERT(outConstraintsList);
*outConstraintsList = mJsEncodeConstraints;
}
- static void AddToMsection(const std::vector<JsConstraints>& constraintsList,
- sdp::Direction direction,
- SdpMediaSection* msection);
+ void AddToMsection(const std::vector<JsConstraints>& constraintsList,
+ sdp::Direction direction,
+ SsrcGenerator& ssrcGenerator,
+ SdpMediaSection* msection);
-protected:
- virtual ~JsepTrack() {}
private:
std::vector<JsepCodecDescription*> GetCodecClones() const;
static void EnsureNoDuplicatePayloadTypes(
std::vector<JsepCodecDescription*>* codecs);
static void GetPayloadTypes(
const std::vector<JsepCodecDescription*>& codecs,
std::vector<uint16_t>* pts);
static void EnsurePayloadTypeIsUnique(std::set<uint16_t>* uniquePayloadTypes,
JsepCodecDescription* codec);
void AddToMsection(const std::vector<JsepCodecDescription*>& codecs,
- SdpMediaSection* msection) const;
+ SdpMediaSection* msection);
void GetRids(const SdpMediaSection& msection,
sdp::Direction direction,
std::vector<SdpRidAttributeList::Rid>* rids) const;
void CreateEncodings(
const SdpMediaSection& remote,
const std::vector<JsepCodecDescription*>& negotiatedCodecs,
JsepTrackNegotiatedDetails* details);
@@ -276,53 +331,217 @@ private:
std::vector<JsepCodecDescription*>* codecs,
std::map<std::string, std::string>* formatChanges = nullptr) const;
JsConstraints* FindConstraints(
const std::string& rid,
std::vector<JsConstraints>& constraintsList) const;
void NegotiateRids(const std::vector<SdpRidAttributeList::Rid>& rids,
std::vector<JsConstraints>* constraints) const;
+ void UpdateSsrcs(SsrcGenerator& ssrcGenerator, size_t encodings);
- const mozilla::SdpMediaSection::MediaType mType;
- std::string mStreamId;
+ mozilla::SdpMediaSection::MediaType mType;
+ // These are the ids that everyone outside of JsepSession care about
+ std::vector<std::string> mStreamIds;
std::string mTrackId;
std::string mCNAME;
- const sdp::Direction mDirection;
+ sdp::Direction mDirection;
PtrVector<JsepCodecDescription> mPrototypeCodecs;
// Holds encoding params/constraints from JS. Simulcast happens when there are
// multiple of these. If there are none, we assume unconstrained unicast with
// no rid.
std::vector<JsConstraints> mJsEncodeConstraints;
UniquePtr<JsepTrackNegotiatedDetails> mNegotiatedDetails;
std::vector<uint32_t> mSsrcs;
bool mActive;
};
-// Need a better name for this.
-struct JsepTrackPair {
- size_t mLevel;
- // Is this track pair sharing a transport with another?
- size_t mBundleLevel = SIZE_MAX; // SIZE_MAX if no bundle level
- uint32_t mRecvonlySsrc;
- RefPtr<JsepTrack> mSending;
- RefPtr<JsepTrack> mReceiving;
- RefPtr<JsepTransport> mRtpTransport;
- RefPtr<JsepTransport> mRtcpTransport;
+class JsepTransceiver {
+ private:
+ ~JsepTransceiver() {};
+
+ public:
+ explicit JsepTransceiver(SdpMediaSection::MediaType type,
+ SdpDirectionAttribute::Direction jsDirection =
+ SdpDirectionAttribute::kSendrecv) :
+ mJsDirection(jsDirection),
+ mSending(type, sdp::kSend),
+ mReceiving(type, sdp::kRecv),
+ mTransport(*(new JsepTransport)),
+ mLevel(SIZE_MAX),
+ mBundleLevel(SIZE_MAX),
+ mAddTrackMagic(false),
+ mWasCreatedBySetRemote(false),
+ mStopped(false),
+ mRemoved(false)
+ {}
+
+ // Can't use default copy c'tor because of the refcount members. Ugh.
+ JsepTransceiver(const JsepTransceiver& orig) :
+ mJsDirection(orig.mJsDirection),
+ mSending(orig.mSending),
+ mReceiving(orig.mReceiving),
+ mTransport(*(new JsepTransport(orig.mTransport))),
+ mMid(orig.mMid),
+ mLevel(orig.mLevel),
+ mBundleLevel(orig.mBundleLevel),
+ mAddTrackMagic(orig.mAddTrackMagic),
+ mWasCreatedBySetRemote(orig.mWasCreatedBySetRemote),
+ mStopped(orig.mStopped),
+ mRemoved(orig.mRemoved)
+ {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JsepTransceiver);
+
+ void Rollback(JsepTransceiver& oldTransceiver)
+ {
+ *mTransport = *oldTransceiver.mTransport;
+ mLevel = oldTransceiver.mLevel;
+ mBundleLevel = oldTransceiver.mBundleLevel;
+ mReceiving = oldTransceiver.mReceiving;
+
+ // stop() caused by a disabled m-section in a remote offer cannot be
+ // rolled back.
+ if (!IsStopped()) {
+ mMid = oldTransceiver.mMid;
+ }
+ }
+
+ bool IsAssociated() const
+ {
+ return !mMid.empty();
+ }
+
+ const std::string& GetMid() const
+ {
+ MOZ_ASSERT(IsAssociated());
+ return mMid;
+ }
+
+ void Associate(const std::string& mid)
+ {
+ MOZ_ASSERT(HasLevel());
+ mMid = mid;
+ }
+
+ void Disassociate()
+ {
+ mMid.clear();
+ }
+
+ bool HasLevel() const
+ {
+ return mLevel != SIZE_MAX;
+ }
+
+ void SetLevel(size_t level)
+ {
+ MOZ_ASSERT(!HasLevel());
+ MOZ_ASSERT(!IsStopped());
+
+ mLevel = level;
+ }
+
+ void ClearLevel()
+ {
+ MOZ_ASSERT(mStopped);
+ MOZ_ASSERT(!IsAssociated());
+ mLevel = SIZE_MAX;
+ }
- bool HasBundleLevel() const {
- return mBundleLevel != SIZE_MAX;
- }
+ size_t GetLevel() const
+ {
+ MOZ_ASSERT(HasLevel());
+ return mLevel;
+ }
+
+ void Stop()
+ {
+ mStopped = true;
+ }
+
+ bool IsStopped() const
+ {
+ return mStopped;
+ }
+
+ void SetRemoved()
+ {
+ mRemoved = true;
+ }
+
+ bool IsRemoved() const
+ {
+ return mRemoved;
+ }
+
+ bool HasBundleLevel() const {
+ return mBundleLevel != SIZE_MAX;
+ }
+
+ size_t BundleLevel() const {
+ MOZ_ASSERT(HasBundleLevel());
+ return mBundleLevel;
+ }
+
+ void SetBundleLevel(size_t aBundleLevel) {
+ MOZ_ASSERT(aBundleLevel != SIZE_MAX);
+ mBundleLevel = aBundleLevel;
+ }
+
+ void ClearBundleLevel()
+ {
+ mBundleLevel = SIZE_MAX;
+ }
- size_t BundleLevel() const {
- MOZ_ASSERT(HasBundleLevel());
- return mBundleLevel;
- }
+ size_t GetTransportLevel() const
+ {
+ MOZ_ASSERT(HasLevel());
+ if (HasBundleLevel()) {
+ return BundleLevel();
+ }
+ return GetLevel();
+ }
+
+ void SetAddTrackMagic()
+ {
+ mAddTrackMagic = true;
+ }
+
+ bool HasAddTrackMagic() const
+ {
+ return mAddTrackMagic;
+ }
+
+ void SetCreatedBySetRemote()
+ {
+ mWasCreatedBySetRemote = true;
+ }
- void SetBundleLevel(size_t aBundleLevel) {
- MOZ_ASSERT(aBundleLevel != SIZE_MAX);
- mBundleLevel = aBundleLevel;
- }
+ bool WasCreatedBySetRemote() const
+ {
+ return mWasCreatedBySetRemote;
+ }
+
+ // This is the direction JS wants. It might not actually happen.
+ SdpDirectionAttribute::Direction mJsDirection;
+
+ JsepTrack mSending;
+ JsepTrack mReceiving;
+ OwningNonNull<JsepTransport> mTransport;
+
+ private:
+ // Stuff that is not negotiated
+ std::string mMid;
+ size_t mLevel;
+ // Is this track pair sharing a transport with another?
+ size_t mBundleLevel; // SIZE_MAX if no bundle level
+ // The w3c and IETF specs have a lot of "magical" behavior that happens
+ // when addTrack is used. This was a deliberate design choice. Sadface.
+ bool mAddTrackMagic;
+ bool mWasCreatedBySetRemote;
+ bool mStopped;
+ bool mRemoved;
};
} // namespace mozilla
#endif
--- a/media/webrtc/signaling/src/jsep/JsepTrackEncoding.h
+++ b/media/webrtc/signaling/src/jsep/JsepTrackEncoding.h
@@ -14,16 +14,26 @@
namespace mozilla {
// Represents a single encoding of a media track. When simulcast is used, there
// may be multiple. Each encoding may have some constraints (imposed by JS), and
// may be able to use any one of multiple codecs (JsepCodecDescription) at any
// given time.
class JsepTrackEncoding
{
public:
+ JsepTrackEncoding() = default;
+ JsepTrackEncoding(const JsepTrackEncoding& orig) :
+ mConstraints(orig.mConstraints),
+ mRid(orig.mRid)
+ {
+ for (const JsepCodecDescription* codec : orig.mCodecs.values) {
+ mCodecs.values.push_back(codec->Clone());
+ }
+ }
+
const std::vector<JsepCodecDescription*>& GetCodecs() const
{
return mCodecs.values;
}
void AddCodec(const JsepCodecDescription& codec)
{
mCodecs.values.push_back(codec.Clone());
--- a/media/webrtc/signaling/src/jsep/JsepTransport.h
+++ b/media/webrtc/signaling/src/jsep/JsepTransport.h
@@ -82,16 +82,32 @@ private:
class JsepTransport
{
public:
JsepTransport()
: mComponents(0)
{
}
+ JsepTransport(const JsepTransport& orig)
+ {
+ *this = orig;
+ }
+
+ JsepTransport& operator=(const JsepTransport& orig)
+ {
+ if (this != &orig) {
+ mIce.reset(orig.mIce ? new JsepIceTransport(*orig.mIce) : nullptr);
+ mDtls.reset(orig.mDtls ? new JsepDtlsTransport(*orig.mDtls) : nullptr);
+ mTransportId = orig.mTransportId;
+ mComponents = orig.mComponents;
+ }
+ return *this;
+ }
+
void Close()
{
mComponents = 0;
mTransportId.clear();
mIce.reset();
mDtls.reset();
}
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/jsep/SsrcGenerator.cpp
@@ -0,0 +1,24 @@
+/* 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 "signaling/src/jsep/SsrcGenerator.h"
+#include "pk11pub.h"
+
+namespace mozilla {
+
+bool
+SsrcGenerator::GenerateSsrc(uint32_t* ssrc)
+{
+ do {
+ SECStatus rv = PK11_GenerateRandom(
+ reinterpret_cast<unsigned char*>(ssrc), sizeof(uint32_t));
+ if (rv != SECSuccess) {
+ return false;
+ }
+ } while (mSsrcs.count(*ssrc));
+ mSsrcs.insert(*ssrc);
+
+ return true;
+}
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/jsep/SsrcGenerator.h
@@ -0,0 +1,20 @@
+/* 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 _SSRCGENERATOR_H_
+#define _SSRCGENERATOR_H_
+
+#include <set>
+
+namespace mozilla {
+class SsrcGenerator {
+ public:
+ bool GenerateSsrc(uint32_t* ssrc);
+ private:
+ std::set<uint32_t> mSsrcs;
+};
+} // namespace mozilla
+
+#endif // _SSRCGENERATOR_H_
+
--- a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
@@ -931,18 +931,23 @@ WebrtcAudioConduit::SendRtp(const uint8_
}
}
ReentrantMonitorAutoEnter enter(mTransportMonitor);
// XXX(pkerr) - the PacketOptions are being ignored. This parameter was added along
// with the Call API update in the webrtc.org codebase.
// The only field in it is the packet_id, which is used when the header
// extension for TransportSequenceNumber is being used, which we don't.
(void)options;
- if(mTransmitterTransport &&
- (mTransmitterTransport->SendRtpPacket(data, len) == NS_OK))
+
+ if (!mTransmitterTransport) {
+ // Not set yet, don't complain
+ return false;
+ }
+
+ if(mTransmitterTransport->SendRtpPacket(data, len) == NS_OK)
{
CSFLogDebug(logTag, "%s Sent RTP Packet ", __FUNCTION__);
return true;
}
CSFLogError(logTag, "%s RTP Packet Send Failed ", __FUNCTION__);
return false;
}
@@ -954,16 +959,21 @@ WebrtcAudioConduit::SendRtcp(const uint8
__FUNCTION__,
(unsigned long) len,
static_cast<unsigned>(data[1]));
// We come here if we have only one pipeline/conduit setup,
// such as for unidirectional streams.
// We also end up here if we are receiving
ReentrantMonitorAutoEnter enter(mTransportMonitor);
+ if (!mTransmitterTransport && !mReceiverTransport) {
+ // Not set yet, don't complain
+ return false;
+ }
+
if(mReceiverTransport &&
mReceiverTransport->SendRtcpPacket(data, len) == NS_OK)
{
// Might be a sender report, might be a receiver report, we don't know.
CSFLogDebug(logTag, "%s Sent RTCP Packet ", __FUNCTION__);
return true;
}
if (mTransmitterTransport &&
--- a/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
+++ b/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
@@ -325,17 +325,17 @@ public:
mUsingTmmbr(false),
mUsingFEC(false) {}
virtual ~VideoSessionConduit() {}
virtual Type type() const { return VIDEO; }
/**
- * Adds negotiated RTP extensions
+ * Sets negotiated RTP extensions
* XXX Move to MediaSessionConduit
*/
virtual void SetLocalRTPExtensions(bool aIsSend,
const std::vector<webrtc::RtpExtension>& extensions) = 0;
/**
* Returns the negotiated RTP extensions
*/
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
@@ -326,16 +326,18 @@ WebrtcVideoConduit::SetLocalRTPExtension
std::vector<webrtc::RtpExtension>
WebrtcVideoConduit::GetLocalRTPExtensions(bool aIsSend) const
{
return aIsSend ? mSendStreamConfig.rtp.extensions : mRecvStreamConfig.rtp.extensions;
}
bool WebrtcVideoConduit::SetLocalSSRCs(const std::vector<unsigned int> & aSSRCs)
{
+ MOZ_RELEASE_ASSERT(!aSSRCs.empty());
+
// Special case: the local SSRCs are the same - do nothing.
if (mSendStreamConfig.rtp.ssrcs == aSSRCs) {
return true;
}
// Update the value of the ssrcs in the config structure.
mSendStreamConfig.rtp.ssrcs = aSSRCs;
@@ -728,17 +730,18 @@ WebrtcVideoConduit::ConfigureSendMediaCo
// SetSendCodec()
if (mSendingWidth != 0) {
// We're already in a call and are reconfiguring (perhaps due to
// ReplaceTrack).
bool resolutionChanged;
{
MutexAutoLock lock(mCodecMutex);
- resolutionChanged = !mCurSendCodecConfig->ResolutionEquals(*codecConfig);
+ resolutionChanged = mCurSendCodecConfig &&
+ !mCurSendCodecConfig->ResolutionEquals(*codecConfig);
}
if (resolutionChanged) {
// We're already in a call and due to renegotiation an encoder parameter
// that requires reconfiguration has changed. Resetting these members
// triggers reconfig on the next frame.
mLastWidth = 0;
mLastHeight = 0;
@@ -849,21 +852,17 @@ WebrtcVideoConduit::ConfigureSendMediaCo
bool
WebrtcVideoConduit::SetRemoteSSRC(unsigned int ssrc)
{
CSFLogDebug(logTag, "%s: SSRC %u (0x%x)", __FUNCTION__, ssrc, ssrc);
mRecvStreamConfig.rtp.remote_ssrc = ssrc;
unsigned int current_ssrc;
- if (!GetRemoteSSRC(¤t_ssrc)) {
- return false;
- }
-
- if (current_ssrc == ssrc) {
+ if (GetRemoteSSRC(¤t_ssrc) && (current_ssrc == ssrc)) {
return true;
}
bool wasReceiving = mEngineReceiving;
if (StopReceiving() != kMediaConduitNoError) {
return false;
}
@@ -1942,16 +1941,17 @@ WebrtcVideoConduit::OnSinkWantsChanged(
MediaConduitErrorCode
WebrtcVideoConduit::SendVideoFrame(webrtc::VideoFrame& frame)
{
// XXX Google uses a "timestamp_aligner" to translate timestamps from the
// camera via TranslateTimestamp(); we should look at doing the same. This
// avoids sampling error when capturing frames, but google had to deal with some
// broken cameras, include Logitech c920's IIRC.
+ MOZ_RELEASE_ASSERT(!mSendStreamConfig.rtp.ssrcs.empty());
CSFLogVerbose(logTag, "%s (send SSRC %u (0x%x))", __FUNCTION__,
mSendStreamConfig.rtp.ssrcs.front(), mSendStreamConfig.rtp.ssrcs.front());
// See if we need to recalculate what we're sending.
// Don't compute mSendingWidth/Height, since those may not be the same as the input.
{
MutexAutoLock lock(mCodecMutex);
if (mInReconfig) {
// Waiting for it to finish
@@ -2108,16 +2108,17 @@ WebrtcVideoConduit::ReceivedRTPPacket(co
return kMediaConduitRTPProcessingFailed;
}
NS_DispatchToMainThread(media::NewRunnableFrom([self, thread, ssrc]() mutable {
// Normally this is done in CreateOrUpdateMediaPipeline() for
// initial creation and renegotiation, but here we're rebuilding the
// Receive channel at a lower level. This is needed whenever we're
// creating a GMPVideoCodec (in particular, H264) so it can communicate
// errors to the PC.
+ MOZ_ASSERT(!self->mPCHandle.empty());
WebrtcGmpPCHandleSetter setter(self->mPCHandle);
self->SetRemoteSSRC(ssrc); // this will likely re-create the VideoReceiveStream
// We want to unblock the queued packets on the original thread
thread->Dispatch(media::NewRunnableFrom([self, ssrc]() mutable {
if (ssrc == self->mRecvSSRC) {
// SSRC is set; insert queued packets
for (auto& packet : self->mQueuedPackets) {
CSFLogDebug(logTag, "Inserting queued packets: seq# %u, Len %d ",
@@ -2181,16 +2182,17 @@ WebrtcVideoConduit::StopTransmitting()
mEngineTransmitting = false;
}
return kMediaConduitNoError;
}
MediaConduitErrorCode
WebrtcVideoConduit::StartTransmitting()
{
+ MOZ_RELEASE_ASSERT(!mSendStreamConfig.rtp.ssrcs.empty());
if (mEngineTransmitting) {
return kMediaConduitNoError;
}
CSFLogDebug(logTag, "%s Attemping to start... ", __FUNCTION__);
{
// Start Transmitting on the video engine
MutexAutoLock lock(mCodecMutex);
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.h
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.h
@@ -74,17 +74,17 @@ public:
static const uint32_t kDefaultStartBitrate_bps;
/* Default maximum bitrate for video streams. */
static const uint32_t kDefaultMaxBitrate_bps;
//VoiceEngine defined constant for Payload Name Size.
static const unsigned int CODEC_PLNAME_SIZE;
/**
- * Add rtp extensions to the the VideoSendStream
+ * Set rtp extensions on the VideoSendStream
* TODO(@@NG) promote this the MediaConduitInterface when the VoE rework
* hits Webrtc.org.
*/
void SetLocalRTPExtensions(bool aIsSend,
const std::vector<webrtc::RtpExtension>& extensions) override;
std::vector<webrtc::RtpExtension> GetLocalRTPExtensions(bool aIsSend) const override;
/**
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -20,16 +20,17 @@
#include "ImageTypes.h"
#include "ImageContainer.h"
#include "DOMMediaStream.h"
#include "MediaStreamTrack.h"
#include "MediaStreamListener.h"
#include "MediaStreamVideoSink.h"
#include "VideoUtils.h"
#include "VideoStreamTrack.h"
+#include "MediaEngine.h"
#include "nsError.h"
#include "AudioSegment.h"
#include "MediaSegment.h"
#include "MediaPipelineFilter.h"
#include "RtpLogger.h"
#include "databuffer.h"
#include "transportflow.h"
@@ -555,83 +556,63 @@ protected:
};
static char kDTLSExporterLabel[] = "EXTRACTOR-dtls_srtp";
MediaPipeline::MediaPipeline(const std::string& pc,
Direction direction,
nsCOMPtr<nsIEventTarget> main_thread,
nsCOMPtr<nsIEventTarget> sts_thread,
- const std::string& track_id,
- int level,
- RefPtr<MediaSessionConduit> conduit,
- RefPtr<TransportFlow> rtp_transport,
- RefPtr<TransportFlow> rtcp_transport,
- nsAutoPtr<MediaPipelineFilter> filter)
+ RefPtr<MediaSessionConduit> conduit)
: direction_(direction),
- track_id_(track_id),
- level_(level),
conduit_(conduit),
- rtp_(rtp_transport, rtcp_transport ? RTP : MUX),
- rtcp_(rtcp_transport ? rtcp_transport : rtp_transport,
- rtcp_transport ? RTCP : MUX),
+ rtp_(nullptr, RTP),
+ rtcp_(nullptr, RTCP),
main_thread_(main_thread),
sts_thread_(sts_thread),
rtp_packets_sent_(0),
rtcp_packets_sent_(0),
rtp_packets_received_(0),
rtcp_packets_received_(0),
rtp_bytes_sent_(0),
rtp_bytes_received_(0),
pc_(pc),
description_(),
- filter_(filter),
rtp_parser_(webrtc::RtpHeaderParser::Create()){
- // To indicate rtcp-mux rtcp_transport should be nullptr.
- // Therefore it's an error to send in the same flow for
- // both rtp and rtcp.
- MOZ_ASSERT(rtp_transport != rtcp_transport);
-
// PipelineTransport() will access this->sts_thread_; moved here for safety
transport_ = new PipelineTransport(this);
-}
-
-MediaPipeline::~MediaPipeline() {
- ASSERT_ON_THREAD(main_thread_);
- MOZ_MTLOG(ML_INFO, "Destroying MediaPipeline: " << description_);
-}
-
-nsresult MediaPipeline::Init() {
- ASSERT_ON_THREAD(main_thread_);
if (direction_ == RECEIVE) {
conduit_->SetReceiverTransport(transport_);
} else {
conduit_->SetTransmitterTransport(transport_);
}
+}
+
+MediaPipeline::~MediaPipeline() {
+ MOZ_MTLOG(ML_INFO, "Destroying MediaPipeline: " << description_);
+ // MediaSessionConduit insists that it be released on main.
+ RUN_ON_THREAD(main_thread_, WrapRelease(conduit_.forget()),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+MediaPipeline::Shutdown_m()
+{
+ MOZ_MTLOG(ML_INFO, description_ << " in " << __FUNCTION__);
+
+ ShutdownMedia_m();
RUN_ON_THREAD(sts_thread_,
WrapRunnable(
RefPtr<MediaPipeline>(this),
- &MediaPipeline::Init_s),
+ &MediaPipeline::DetachTransport_s),
NS_DISPATCH_NORMAL);
-
- return NS_OK;
}
-nsresult MediaPipeline::Init_s() {
- ASSERT_ON_THREAD(sts_thread_);
-
- return AttachTransport_s();
-}
-
-
-// Disconnect us from the transport so that we can cleanly destruct the
-// pipeline on the main thread. ShutdownMedia_m() must have already been
-// called
void
MediaPipeline::DetachTransport_s()
{
ASSERT_ON_THREAD(sts_thread_);
disconnect_all();
transport_->Detach();
rtp_.Detach();
@@ -658,54 +639,51 @@ MediaPipeline::AttachTransport_s()
}
transport_->Attach(this);
return NS_OK;
}
void
-MediaPipeline::UpdateTransport_m(int level,
- RefPtr<TransportFlow> rtp_transport,
+MediaPipeline::UpdateTransport_m(RefPtr<TransportFlow> rtp_transport,
RefPtr<TransportFlow> rtcp_transport,
nsAutoPtr<MediaPipelineFilter> filter)
{
RUN_ON_THREAD(sts_thread_,
WrapRunnable(
- this,
+ RefPtr<MediaPipeline>(this),
&MediaPipeline::UpdateTransport_s,
- level,
rtp_transport,
rtcp_transport,
filter),
NS_DISPATCH_NORMAL);
}
void
-MediaPipeline::UpdateTransport_s(int level,
- RefPtr<TransportFlow> rtp_transport,
+MediaPipeline::UpdateTransport_s(RefPtr<TransportFlow> rtp_transport,
RefPtr<TransportFlow> rtcp_transport,
nsAutoPtr<MediaPipelineFilter> filter)
{
bool rtcp_mux = false;
if (!rtcp_transport) {
rtcp_transport = rtp_transport;
rtcp_mux = true;
}
if ((rtp_transport != rtp_.transport_) ||
(rtcp_transport != rtcp_.transport_)) {
DetachTransport_s();
- rtp_ = TransportInfo(rtp_transport, rtcp_mux ? MUX : RTP);
- rtcp_ = TransportInfo(rtcp_transport, rtcp_mux ? MUX : RTCP);
- AttachTransport_s();
+ if (rtp_transport && rtcp_transport) {
+ rtp_ = TransportInfo(rtp_transport, rtcp_mux ? MUX : RTP);
+ rtcp_ = TransportInfo(rtcp_transport, rtcp_mux ? MUX : RTCP);
+ AttachTransport_s();
+ }
}
- level_ = level;
-
if (filter_ && filter) {
// Use the new filter, but don't forget any remote SSRCs that we've learned
// by receiving traffic.
filter_->Update(*filter);
} else {
filter_ = filter;
}
}
@@ -1290,24 +1268,24 @@ public:
void OnVideoFrameConverted(unsigned char* aVideoFrame,
unsigned int aVideoFrameLength,
unsigned short aWidth,
unsigned short aHeight,
VideoType aVideoType,
uint64_t aCaptureTime)
{
- MOZ_ASSERT(conduit_->type() == MediaSessionConduit::VIDEO);
+ MOZ_RELEASE_ASSERT(conduit_->type() == MediaSessionConduit::VIDEO);
static_cast<VideoSessionConduit*>(conduit_.get())->SendVideoFrame(
aVideoFrame, aVideoFrameLength, aWidth, aHeight, aVideoType, aCaptureTime);
}
void OnVideoFrameConverted(webrtc::VideoFrame& aVideoFrame)
{
- MOZ_ASSERT(conduit_->type() == MediaSessionConduit::VIDEO);
+ MOZ_RELEASE_ASSERT(conduit_->type() == MediaSessionConduit::VIDEO);
static_cast<VideoSessionConduit*>(conduit_.get())->SendVideoFrame(aVideoFrame);
}
// Implement MediaStreamTrackListener
void NotifyQueuedChanges(MediaStreamGraph* aGraph,
StreamTime aTrackOffset,
const MediaSegment& aQueuedMedia) override;
@@ -1412,28 +1390,26 @@ protected:
RefPtr<PipelineListener> listener_;
Mutex mutex_;
};
MediaPipelineTransmit::MediaPipelineTransmit(
const std::string& pc,
nsCOMPtr<nsIEventTarget> main_thread,
nsCOMPtr<nsIEventTarget> sts_thread,
+ bool is_video,
dom::MediaStreamTrack* domtrack,
- const std::string& track_id,
- int level,
- RefPtr<MediaSessionConduit> conduit,
- RefPtr<TransportFlow> rtp_transport,
- RefPtr<TransportFlow> rtcp_transport,
- nsAutoPtr<MediaPipelineFilter> filter) :
- MediaPipeline(pc, TRANSMIT, main_thread, sts_thread, track_id, level,
- conduit, rtp_transport, rtcp_transport, filter),
+ RefPtr<MediaSessionConduit> conduit) :
+ MediaPipeline(pc, TRANSMIT, main_thread, sts_thread, conduit),
listener_(new PipelineListener(conduit)),
- domtrack_(domtrack)
+ is_video_(is_video),
+ domtrack_(domtrack),
+ track_attached_(false)
{
+ SetDescription();
if (!IsVideo()) {
audio_processing_ = MakeAndAddRef<AudioProxyThread>(static_cast<AudioSessionConduit*>(conduit.get()));
listener_->SetAudioProxy(audio_processing_);
}
else { // Video
// For video we send frames to an async VideoFrameConverter that calls
// back to a VideoFrameFeeder that feeds I420 frames to VideoConduit.
@@ -1446,32 +1422,64 @@ MediaPipelineTransmit::MediaPipelineTran
}
}
MediaPipelineTransmit::~MediaPipelineTransmit()
{
if (feeder_) {
feeder_->Detach();
}
+
+ MOZ_ASSERT(!domtrack_);
}
-nsresult MediaPipelineTransmit::Init() {
- AttachToTrack(track_id_);
-
- return MediaPipeline::Init();
-}
-
-void MediaPipelineTransmit::AttachToTrack(const std::string& track_id) {
- ASSERT_ON_THREAD(main_thread_);
-
+void MediaPipelineTransmit::SetDescription() {
description_ = pc_ + "| ";
description_ += conduit_->type() == MediaSessionConduit::AUDIO ?
"Transmit audio[" : "Transmit video[";
+
+ if (!domtrack_) {
+ description_ += "no track]";
+ return;
+ }
+
+ nsString nsTrackId;
+ domtrack_->GetId(nsTrackId);
+ std::string track_id(NS_ConvertUTF16toUTF8(nsTrackId).get());
description_ += track_id;
description_ += "]";
+}
+
+void MediaPipelineTransmit::DetachFromTrack() {
+ ASSERT_ON_THREAD(main_thread_);
+
+ if (!domtrack_ || !track_attached_) {
+ return;
+ }
+
+ track_attached_ = false;
+
+ if (domtrack_->AsAudioStreamTrack()) {
+ domtrack_->RemoveDirectListener(listener_);
+ domtrack_->RemoveListener(listener_);
+ } else if (VideoStreamTrack* video = domtrack_->AsVideoStreamTrack()) {
+ video->RemoveVideoOutput(listener_);
+ } else {
+ MOZ_ASSERT(false, "Unknown track type");
+ }
+}
+
+void MediaPipelineTransmit::AttachToTrack() {
+ ASSERT_ON_THREAD(main_thread_);
+
+ if (!domtrack_ || track_attached_) {
+ return;
+ }
+
+ track_attached_ = true;
// TODO(ekr@rtfm.com): Check for errors
MOZ_MTLOG(ML_DEBUG, "Attaching pipeline to track "
<< static_cast<void *>(domtrack_) << " conduit type=" <<
(conduit_->type() == MediaSessionConduit::AUDIO ?"audio":"video"));
#if !defined(MOZILLA_EXTERNAL_LINKAGE)
// With full duplex we don't risk having audio come in late to the MSG
@@ -1496,17 +1504,17 @@ void MediaPipelineTransmit::AttachToTrac
} else {
MOZ_ASSERT(false, "Unknown track type");
}
}
bool
MediaPipelineTransmit::IsVideo() const
{
- return !!domtrack_->AsVideoStreamTrack();
+ return is_video_;
}
void MediaPipelineTransmit::UpdateSinkIdentity_m(MediaStreamTrack* track,
nsIPrincipal* principal,
const PeerIdentity* sinkIdentity) {
ASSERT_ON_THREAD(main_thread_);
if (track != nullptr && track != domtrack_) {
@@ -1530,59 +1538,58 @@ void MediaPipelineTransmit::UpdateSinkId
listener_->SetEnabled(enableTrack);
}
void
MediaPipelineTransmit::DetachMedia()
{
ASSERT_ON_THREAD(main_thread_);
- if (domtrack_) {
- if (domtrack_->AsAudioStreamTrack()) {
- domtrack_->RemoveDirectListener(listener_);
- domtrack_->RemoveListener(listener_);
- } else if (VideoStreamTrack* video = domtrack_->AsVideoStreamTrack()) {
- video->RemoveVideoOutput(listener_);
- } else {
- MOZ_ASSERT(false, "Unknown track type");
- }
- domtrack_ = nullptr;
- }
+
+ DetachFromTrack();
+ domtrack_ = nullptr;
// Let the listener be destroyed with the pipeline (or later).
}
nsresult MediaPipelineTransmit::TransportReady_s(TransportInfo &info) {
ASSERT_ON_THREAD(sts_thread_);
// Call base ready function.
MediaPipeline::TransportReady_s(info);
// Should not be set for a transmitter
if (&info == &rtp_) {
listener_->SetActive(true);
}
return NS_OK;
}
-nsresult MediaPipelineTransmit::ReplaceTrack(MediaStreamTrack& domtrack) {
+nsresult MediaPipelineTransmit::ReplaceTrack(RefPtr<MediaStreamTrack>& domtrack) {
// MainThread, checked in calls we make
- nsString nsTrackId;
- domtrack.GetId(nsTrackId);
- std::string track_id(NS_ConvertUTF16toUTF8(nsTrackId).get());
- MOZ_MTLOG(ML_DEBUG, "Reattaching pipeline " << description_ << " to track "
- << static_cast<void *>(&domtrack)
- << " track " << track_id << " conduit type=" <<
- (conduit_->type() == MediaSessionConduit::AUDIO ?"audio":"video"));
+ if (domtrack) {
+ nsString nsTrackId;
+ domtrack->GetId(nsTrackId);
+ std::string track_id(NS_ConvertUTF16toUTF8(nsTrackId).get());
+ MOZ_MTLOG(ML_DEBUG, "Reattaching pipeline " << description_ << " to track "
+ << static_cast<void *>(domtrack.get())
+ << " track " << track_id << " conduit type=" <<
+ (conduit_->type() == MediaSessionConduit::AUDIO ?"audio":"video"));
+ }
- DetachMedia();
- domtrack_ = &domtrack; // Detach clears it
- // Unsets the track id after RemoveListener() takes effect.
- listener_->UnsetTrackId(domtrack_->GraphImpl());
- track_id_ = track_id;
- AttachToTrack(track_id);
+ RefPtr<dom::MediaStreamTrack> oldTrack = domtrack_;
+ DetachFromTrack();
+ domtrack_ = domtrack;
+ SetDescription();
+
+ if (oldTrack) {
+ // Unsets the track id after RemoveListener() takes effect.
+ listener_->UnsetTrackId(oldTrack->GraphImpl());
+ }
+
+ AttachToTrack();
return NS_OK;
}
void MediaPipeline::DisconnectTransport_s(TransportInfo &info) {
MOZ_ASSERT(info.transport_);
ASSERT_ON_THREAD(sts_thread_);
info.transport_->SignalStateChange.disconnect(this);
@@ -1889,28 +1896,54 @@ static void AddListener(MediaStream* sou
class GenericReceiveListener : public MediaStreamListener
{
public:
GenericReceiveListener(SourceMediaStream *source, TrackID track_id)
: source_(source),
track_id_(track_id),
played_ticks_(0),
last_log_(0),
- principal_handle_(PRINCIPAL_HANDLE_NONE) {}
+ principal_handle_(PRINCIPAL_HANDLE_NONE)
+ {
+ MOZ_ASSERT(source);
+ }
virtual ~GenericReceiveListener() {}
void AddSelf()
{
AddListener(source_, this);
}
void EndTrack()
{
- source_->EndTrack(track_id_);
+ MOZ_MTLOG(ML_DEBUG, "GenericReceiveListener ending track");
+
+ // We do this on MSG to avoid it racing against StartTrack.
+ class Message : public ControlMessage
+ {
+ public:
+ Message(SourceMediaStream* stream,
+ TrackID track_id)
+ : ControlMessage(stream),
+ source_(stream),
+ track_id_(track_id)
+ {}
+
+ void Run() override {
+ source_->EndTrack(track_id_);
+ }
+
+ RefPtr<SourceMediaStream> source_;
+ const TrackID track_id_;
+ };
+
+ source_->GraphImpl()->AppendMessage(MakeUnique<Message>(source_, track_id_));
+ // This breaks the cycle with source_
+ source_->RemoveListener(this);
}
// Must be called on the main thread
void SetPrincipalHandle_m(const PrincipalHandle& principal_handle)
{
class Message : public ControlMessage
{
public:
@@ -1935,55 +1968,44 @@ class GenericReceiveListener : public Me
// Must be called on the MediaStreamGraph thread
void SetPrincipalHandle_msg(const PrincipalHandle& principal_handle)
{
principal_handle_ = principal_handle;
}
protected:
- SourceMediaStream *source_;
+ RefPtr<SourceMediaStream> source_;
const TrackID track_id_;
TrackTicks played_ticks_;
TrackTicks last_log_; // played_ticks_ when we last logged
PrincipalHandle principal_handle_;
};
MediaPipelineReceive::MediaPipelineReceive(
const std::string& pc,
nsCOMPtr<nsIEventTarget> main_thread,
nsCOMPtr<nsIEventTarget> sts_thread,
- SourceMediaStream *stream,
- const std::string& track_id,
- int level,
- RefPtr<MediaSessionConduit> conduit,
- RefPtr<TransportFlow> rtp_transport,
- RefPtr<TransportFlow> rtcp_transport,
- nsAutoPtr<MediaPipelineFilter> filter) :
- MediaPipeline(pc, RECEIVE, main_thread, sts_thread,
- track_id, level, conduit, rtp_transport,
- rtcp_transport, filter),
- stream_(stream),
+ RefPtr<MediaSessionConduit> conduit) :
+ MediaPipeline(pc, RECEIVE, main_thread, sts_thread, conduit),
segments_added_(0)
{
- MOZ_ASSERT(stream_);
}
MediaPipelineReceive::~MediaPipelineReceive()
{
- MOZ_ASSERT(!stream_); // Check that we have shut down already.
}
class MediaPipelineReceiveAudio::PipelineListener
: public GenericReceiveListener
{
public:
- PipelineListener(SourceMediaStream * source, TrackID track_id,
+ PipelineListener(SourceMediaStream * source,
const RefPtr<MediaSessionConduit>& conduit)
- : GenericReceiveListener(source, track_id),
+ : GenericReceiveListener(source, kAudioTrack),
conduit_(conduit)
{
}
~PipelineListener()
{
if (!NS_IsMainThread()) {
// release conduit on mainthread. Must use forget()!
@@ -2090,67 +2112,49 @@ public:
private:
RefPtr<MediaSessionConduit> conduit_;
};
MediaPipelineReceiveAudio::MediaPipelineReceiveAudio(
const std::string& pc,
nsCOMPtr<nsIEventTarget> main_thread,
nsCOMPtr<nsIEventTarget> sts_thread,
- SourceMediaStream* stream,
- const std::string& media_stream_track_id,
- TrackID numeric_track_id,
- int level,
- RefPtr<AudioSessionConduit> conduit,
- RefPtr<TransportFlow> rtp_transport,
- RefPtr<TransportFlow> rtcp_transport,
- nsAutoPtr<MediaPipelineFilter> filter) :
- MediaPipelineReceive(pc, main_thread, sts_thread,
- stream, media_stream_track_id, level, conduit,
- rtp_transport, rtcp_transport, filter),
- listener_(new PipelineListener(stream, numeric_track_id, conduit))
-{}
+ RefPtr<AudioSessionConduit> conduit) :
+ MediaPipelineReceive(pc, main_thread, sts_thread, conduit)
+{
+ description_ = pc_ + "| Receive audio";
+}
void MediaPipelineReceiveAudio::DetachMedia()
{
ASSERT_ON_THREAD(main_thread_);
- if (stream_ && listener_) {
+ if (listener_) {
listener_->EndTrack();
-
- if (stream_->GraphImpl()) {
- stream_->RemoveListener(listener_);
- }
- stream_ = nullptr;
+ listener_ = nullptr;
}
}
-nsresult MediaPipelineReceiveAudio::Init()
-{
- ASSERT_ON_THREAD(main_thread_);
- MOZ_MTLOG(ML_DEBUG, __FUNCTION__);
-
- description_ = pc_ + "| Receive audio[";
- description_ += track_id_;
- description_ += "]";
-
- listener_->AddSelf();
-
- return MediaPipelineReceive::Init();
-}
-
void MediaPipelineReceiveAudio::SetPrincipalHandle_m(const PrincipalHandle& principal_handle)
{
listener_->SetPrincipalHandle_m(principal_handle);
}
+void
+MediaPipelineReceiveAudio::AttachMedia(SourceMediaStream* aStream)
+{
+ ASSERT_ON_THREAD(main_thread_);
+ listener_ = new PipelineListener(aStream, conduit_);
+ listener_->AddSelf();
+}
+
class MediaPipelineReceiveVideo::PipelineListener
: public GenericReceiveListener {
public:
- PipelineListener(SourceMediaStream* source, TrackID track_id)
- : GenericReceiveListener(source, track_id)
+ explicit PipelineListener(SourceMediaStream* source)
+ : GenericReceiveListener(source, kVideoTrack)
, image_container_()
, image_()
, mutex_("Video PipelineListener")
{
image_container_ =
LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS);
}
@@ -2269,64 +2273,37 @@ private:
MediaPipelineReceiveVideo *pipeline_; // Raw pointer to avoid cycles
};
MediaPipelineReceiveVideo::MediaPipelineReceiveVideo(
const std::string& pc,
nsCOMPtr<nsIEventTarget> main_thread,
nsCOMPtr<nsIEventTarget> sts_thread,
- SourceMediaStream *stream,
- const std::string& media_stream_track_id,
- TrackID numeric_track_id,
- int level,
- RefPtr<VideoSessionConduit> conduit,
- RefPtr<TransportFlow> rtp_transport,
- RefPtr<TransportFlow> rtcp_transport,
- nsAutoPtr<MediaPipelineFilter> filter) :
- MediaPipelineReceive(pc, main_thread, sts_thread,
- stream, media_stream_track_id, level, conduit,
- rtp_transport, rtcp_transport, filter),
- renderer_(new PipelineRenderer(this)),
- listener_(new PipelineListener(stream, numeric_track_id))
-{}
+ RefPtr<VideoSessionConduit> conduit) :
+ MediaPipelineReceive(pc, main_thread, sts_thread, conduit)
+{
+ description_ = pc_ + "| Receive video";
+}
void MediaPipelineReceiveVideo::DetachMedia()
{
ASSERT_ON_THREAD(main_thread_);
// stop generating video and thus stop invoking the PipelineRenderer
// and PipelineListener - the renderer has a raw ptr to the Pipeline to
// avoid cycles, and the render callbacks are invoked from a different
// thread so simple null-checks would cause TSAN bugs without locks.
static_cast<VideoSessionConduit*>(conduit_.get())->DetachRenderer();
- if (stream_ && listener_) {
+ if (listener_) {
listener_->EndTrack();
- stream_->RemoveListener(listener_);
- stream_ = nullptr;
+ listener_ = nullptr;
}
}
-nsresult MediaPipelineReceiveVideo::Init() {
- ASSERT_ON_THREAD(main_thread_);
- MOZ_MTLOG(ML_DEBUG, __FUNCTION__);
-
- description_ = pc_ + "| Receive video[";
- description_ += track_id_;
- description_ += "]";
-
- listener_->AddSelf();
-
- // Always happens before we can DetachMedia()
- static_cast<VideoSessionConduit *>(conduit_.get())->
- AttachRenderer(renderer_);
-
- return MediaPipelineReceive::Init();
-}
-
void MediaPipelineReceiveVideo::SetPrincipalHandle_m(const PrincipalHandle& principal_handle)
{
listener_->SetPrincipalHandle_m(principal_handle);
}
DOMHighResTimeStamp MediaPipeline::GetNow() {
return webrtc::Clock::GetRealTimeClock()->TimeInMilliseconds();
}
@@ -2353,9 +2330,19 @@ MediaPipeline::RtpCSRCStats::GetWebidlIn
statId.AppendInt(mCsrc);
aWebidlObj.mId.Construct(statId);
aWebidlObj.mType.Construct(RTCStatsType::Csrc);
aWebidlObj.mTimestamp.Construct(mTimestamp);
aWebidlObj.mContributorSsrc.Construct(mCsrc);
aWebidlObj.mInboundRtpStreamId.Construct(aInboundRtpStreamId);
}
+void
+MediaPipelineReceiveVideo::AttachMedia(SourceMediaStream* aStream)
+{
+ ASSERT_ON_THREAD(main_thread_);
+ renderer_ = new PipelineRenderer(this);
+ listener_ = new PipelineListener(aStream);
+ listener_->AddSelf();
+ static_cast<VideoSessionConduit *>(conduit_.get())->AttachRenderer(renderer_);
+}
+
} // end namespace
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
@@ -7,23 +7,23 @@
#ifndef mediapipeline_h__
#define mediapipeline_h__
#include <map>
#include "sigslot.h"
-#include "MediaConduitInterface.h"
+#include "signaling/src/media-conduit/MediaConduitInterface.h"
#include "mozilla/ReentrantMonitor.h"
#include "mozilla/Atomics.h"
#include "SrtpFlow.h"
#include "databuffer.h"
-#include "runnable_utils.h"
-#include "transportflow.h"
+#include "mtransport/runnable_utils.h"
+#include "mtransport/transportflow.h"
#include "AudioPacketizer.h"
#include "StreamTracks.h"
#include "webrtc/modules/rtp_rtcp/include/rtp_header_parser.h"
// Should come from MediaEngine.h, but that's a pain to include here
// because of the MOZILLA_EXTERNAL_LINKAGE stuff.
#define WEBRTC_DEFAULT_SAMPLE_RATE 32000
@@ -77,63 +77,55 @@ class SourceMediaStream;
class MediaPipeline : public sigslot::has_slots<> {
public:
enum Direction { TRANSMIT, RECEIVE };
enum State { MP_CONNECTING, MP_OPEN, MP_CLOSED };
MediaPipeline(const std::string& pc,
Direction direction,
nsCOMPtr<nsIEventTarget> main_thread,
nsCOMPtr<nsIEventTarget> sts_thread,
- const std::string& track_id,
- int level,
- RefPtr<MediaSessionConduit> conduit,
- RefPtr<TransportFlow> rtp_transport,
- RefPtr<TransportFlow> rtcp_transport,
- nsAutoPtr<MediaPipelineFilter> filter);
+ RefPtr<MediaSessionConduit> conduit);
// Must be called on the STS thread. Must be called after ShutdownMedia_m().
void DetachTransport_s();
// Must be called on the main thread.
+ void Shutdown_m();
+
+ // Must be called on the main thread.
void ShutdownMedia_m()
{
ASSERT_ON_THREAD(main_thread_);
if (direction_ == RECEIVE) {
conduit_->StopReceiving();
} else {
conduit_->StopTransmitting();
}
DetachMedia();
}
- virtual nsresult Init();
-
- void UpdateTransport_m(int level,
- RefPtr<TransportFlow> rtp_transport,
+ void UpdateTransport_m(RefPtr<TransportFlow> rtp_transport,
RefPtr<TransportFlow> rtcp_transport,
nsAutoPtr<MediaPipelineFilter> filter);
- void UpdateTransport_s(int level,
- RefPtr<TransportFlow> rtp_transport,
+ void UpdateTransport_s(RefPtr<TransportFlow> rtp_transport,
RefPtr<TransportFlow> rtcp_transport,
nsAutoPtr<MediaPipelineFilter> filter);
// Used only for testing; adds RTP header extension for RTP Stream Id with
// the given id.
void AddRIDExtension_m(size_t extension_id);
void AddRIDExtension_s(size_t extension_id);
// Used only for testing; installs a MediaPipelineFilter that filters
// everything but the given RID
void AddRIDFilter_m(const std::string& rid);
void AddRIDFilter_s(const std::string& rid);
virtual Direction direction() const { return direction_; }
- virtual const std::string& trackid() const { return track_id_; }
- virtual int level() const { return level_; }
virtual bool IsVideo() const = 0;
bool IsDoingRtcpMux() const {
return (rtp_.type_ == MUX);
}
class RtpCSRCStats {
public:
@@ -207,37 +199,33 @@ class MediaPipeline : public sigslot::ha
virtual nsresult SendRtpPacket(const uint8_t* data, size_t len);
virtual nsresult SendRtcpPacket(const uint8_t* data, size_t len);
private:
nsresult SendRtpRtcpPacket_s(nsAutoPtr<DataBuffer> data,
bool is_rtp);
- MediaPipeline *pipeline_; // Raw pointer to avoid cycles
+ // Creates a cycle, which we break with Detach
+ RefPtr<MediaPipeline> pipeline_;
nsCOMPtr<nsIEventTarget> sts_thread_;
};
- RefPtr<PipelineTransport> GetPiplelineTransport() {
- return transport_;
- }
-
protected:
virtual ~MediaPipeline();
virtual void DetachMedia() {}
nsresult AttachTransport_s();
friend class PipelineTransport;
class TransportInfo {
public:
TransportInfo(RefPtr<TransportFlow> flow, RtpType type) :
transport_(flow),
state_(MP_CONNECTING),
type_(type) {
- MOZ_ASSERT(flow);
}
void Detach()
{
transport_ = nullptr;
send_srtp_ = nullptr;
recv_srtp_ = nullptr;
}
@@ -273,64 +261,54 @@ class MediaPipeline : public sigslot::ha
void RtpPacketReceived(TransportLayer *layer, const unsigned char *data,
size_t len);
void RtcpPacketReceived(TransportLayer *layer, const unsigned char *data,
size_t len);
void PacketReceived(TransportLayer *layer, const unsigned char *data,
size_t len);
Direction direction_;
- std::string track_id_; // The track on the stream.
- // Written on the main thread.
- // Used on STS and MediaStreamGraph threads.
- // Not used outside initialization in MediaPipelineTransmit
- // The m-line index (starting at 0, to match convention) Atomic because
- // this value is updated from STS, but read on main, and we don't want to
- // bother with dispatches just to get an int occasionally.
- Atomic<int> level_;
RefPtr<MediaSessionConduit> conduit_; // Our conduit. Written on the main
// thread. Read on STS thread.
// The transport objects. Read/written on STS thread.
TransportInfo rtp_;
TransportInfo rtcp_;
// Pointers to the threads we need. Initialized at creation
// and used all over the place.
nsCOMPtr<nsIEventTarget> main_thread_;
nsCOMPtr<nsIEventTarget> sts_thread_;
- // Created on Init. Referenced by the conduit and eventually
- // destroyed on the STS thread.
+ // Created in c'tor. Referenced by the conduit.
RefPtr<PipelineTransport> transport_;
// Only safe to access from STS thread.
// Build into TransportInfo?
int32_t rtp_packets_sent_;
int32_t rtcp_packets_sent_;
int32_t rtp_packets_received_;
int32_t rtcp_packets_received_;
int64_t rtp_bytes_sent_;
int64_t rtp_bytes_received_;
// Only safe to access from STS thread.
std::map<uint32_t, RtpCSRCStats> csrc_stats_;
- // Written on Init. Read on STS thread.
+ // Written in c'tor. Read on STS thread.
std::string pc_;
std::string description_;
- // Written on Init, all following accesses are on the STS thread.
+ // Written in c'tor, all following accesses are on the STS thread.
nsAutoPtr<MediaPipelineFilter> filter_;
nsAutoPtr<webrtc::RtpHeaderParser> rtp_parser_;
private:
// Gets the current time as a DOMHighResTimeStamp
static DOMHighResTimeStamp GetNow();
- nsresult Init_s();
bool IsRtp(const unsigned char *data, size_t len);
};
class ConduitDeleteEvent: public Runnable
{
public:
explicit ConduitDeleteEvent(already_AddRefed<MediaSessionConduit> aConduit) :
@@ -346,28 +324,22 @@ private:
// A specialization of pipeline for reading from an input device
// and transmitting to the network.
class MediaPipelineTransmit : public MediaPipeline {
public:
// Set rtcp_transport to nullptr to use rtcp-mux
MediaPipelineTransmit(const std::string& pc,
nsCOMPtr<nsIEventTarget> main_thread,
nsCOMPtr<nsIEventTarget> sts_thread,
+ bool is_video,
dom::MediaStreamTrack* domtrack,
- const std::string& track_id,
- int level,
- RefPtr<MediaSessionConduit> conduit,
- RefPtr<TransportFlow> rtp_transport,
- RefPtr<TransportFlow> rtcp_transport,
- nsAutoPtr<MediaPipelineFilter> filter);
+ RefPtr<MediaSessionConduit> conduit);
- // Initialize (stuff here may fail)
- nsresult Init() override;
-
- virtual void AttachToTrack(const std::string& track_id);
+ virtual void AttachToTrack();
+ virtual void DetachFromTrack();
// written and used from MainThread
bool IsVideo() const override;
// When the principal of the domtrack changes, it calls through to here
// so that we can determine whether to enable track transmission.
// `track` has to be null or equal `domtrack_` for us to apply the update.
virtual void UpdateSinkIdentity_m(dom::MediaStreamTrack* track,
@@ -379,130 +351,107 @@ public:
// Override MediaPipeline::TransportReady.
nsresult TransportReady_s(TransportInfo &info) override;
// Replace a track with a different one
// In non-compliance with the likely final spec, allow the new
// track to be part of a different stream (since we don't support
// multiple tracks of a type in a stream yet). bug 1056650
- virtual nsresult ReplaceTrack(dom::MediaStreamTrack& domtrack);
+ virtual nsresult ReplaceTrack(RefPtr<dom::MediaStreamTrack>& domtrack);
// Separate classes to allow ref counting
class PipelineListener;
class VideoFrameFeeder;
protected:
~MediaPipelineTransmit();
+ void SetDescription();
+
private:
RefPtr<PipelineListener> listener_;
RefPtr<AudioProxyThread> audio_processing_;
RefPtr<VideoFrameFeeder> feeder_;
RefPtr<VideoFrameConverter> converter_;
- dom::MediaStreamTrack* domtrack_;
+ bool is_video_;
+ RefPtr<dom::MediaStreamTrack> domtrack_;
+ bool track_attached_;
};
// A specialization of pipeline for reading from the network and
// rendering video.
class MediaPipelineReceive : public MediaPipeline {
public:
// Set rtcp_transport to nullptr to use rtcp-mux
MediaPipelineReceive(const std::string& pc,
nsCOMPtr<nsIEventTarget> main_thread,
nsCOMPtr<nsIEventTarget> sts_thread,
- SourceMediaStream *stream,
- const std::string& track_id,
- int level,
- RefPtr<MediaSessionConduit> conduit,
- RefPtr<TransportFlow> rtp_transport,
- RefPtr<TransportFlow> rtcp_transport,
- nsAutoPtr<MediaPipelineFilter> filter);
+ RefPtr<MediaSessionConduit> conduit);
int segments_added() const { return segments_added_; }
// Sets the PrincipalHandle we set on the media chunks produced by this
// pipeline. Must be called on the main thread.
virtual void SetPrincipalHandle_m(const PrincipalHandle& principal_handle) = 0;
+
+ virtual void AttachMedia(SourceMediaStream* aStream) = 0;
protected:
~MediaPipelineReceive();
- RefPtr<SourceMediaStream> stream_;
int segments_added_;
private:
};
// A specialization of pipeline for reading from the network and
// rendering audio.
class MediaPipelineReceiveAudio : public MediaPipelineReceive {
public:
MediaPipelineReceiveAudio(const std::string& pc,
nsCOMPtr<nsIEventTarget> main_thread,
nsCOMPtr<nsIEventTarget> sts_thread,
- SourceMediaStream* stream,
- // This comes from an msid attribute. Everywhere
- // but MediaStreamGraph uses this.
- const std::string& media_stream_track_id,
- // This is an integer identifier that is only
- // unique within a single DOMMediaStream, which is
- // used by MediaStreamGraph
- TrackID numeric_track_id,
- int level,
- RefPtr<AudioSessionConduit> conduit,
- RefPtr<TransportFlow> rtp_transport,
- RefPtr<TransportFlow> rtcp_transport,
- nsAutoPtr<MediaPipelineFilter> filter);
+ RefPtr<AudioSessionConduit> conduit);
void DetachMedia() override;
- nsresult Init() override;
bool IsVideo() const override { return false; }
void SetPrincipalHandle_m(const PrincipalHandle& principal_handle) override;
+ void AttachMedia(SourceMediaStream* aStream) override;
+
private:
// Separate class to allow ref counting
class PipelineListener;
RefPtr<PipelineListener> listener_;
};
// A specialization of pipeline for reading from the network and
// rendering video.
class MediaPipelineReceiveVideo : public MediaPipelineReceive {
public:
MediaPipelineReceiveVideo(const std::string& pc,
nsCOMPtr<nsIEventTarget> main_thread,
nsCOMPtr<nsIEventTarget> sts_thread,
- SourceMediaStream *stream,
- // This comes from an msid attribute. Everywhere
- // but MediaStreamGraph uses this.
- const std::string& media_stream_track_id,
- // This is an integer identifier that is only
- // unique within a single DOMMediaStream, which is
- // used by MediaStreamGraph
- TrackID numeric_track_id,
- int level,
- RefPtr<VideoSessionConduit> conduit,
- RefPtr<TransportFlow> rtp_transport,
- RefPtr<TransportFlow> rtcp_transport,
- nsAutoPtr<MediaPipelineFilter> filter);
+ RefPtr<VideoSessionConduit> conduit);
// Called on the main thread.
void DetachMedia() override;
- nsresult Init() override;
bool IsVideo() const override { return true; }
void SetPrincipalHandle_m(const PrincipalHandle& principal_handle) override;
+ void AttachMedia(SourceMediaStream* aStream) override;
+
private:
class PipelineRenderer;
friend class PipelineRenderer;
// Separate class to allow ref counting
class PipelineListener;
RefPtr<PipelineRenderer> renderer_;
deleted file mode 100644
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
+++ /dev/null
@@ -1,936 +0,0 @@
-/* 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"
-
-#include "MediaStreamTrack.h"
-#include "nsIPrincipal.h"
-#include "nsIDocument.h"
-#include "mozilla/Preferences.h"
-#include "MediaEngine.h"
-
-#include "mozilla/Preferences.h"
-
-#include "WebrtcGmpVideoCodec.h"
-
-#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;
- if (desc.mFECEnabled) {
- configRaw->mREDPayloadType = desc.mREDPayloadType;
- configRaw->mULPFECPayloadType = desc.mULPFECPayloadType;
- }
-
- *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;
- }
-
- config->mTias = aDetails.GetTias();
-
- 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& value : aLayerList->values) {
- layerQueue->push(value);
- }
- 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 (const auto& fingerprint : fingerprints.mFingerprints) {
- std::ostringstream ss;
- ss << fingerprint.hashFunc;
- rv = dtls->SetVerificationDigest(ss.str(), &fingerprint.fingerprint[0],
- fingerprint.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.HasBundleLevel() ?
- aTrackPair.BundleLevel() :
- 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.HasBundleLevel()) {
- 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 (unsigned int ssrc : aTrack.GetSsrcs()) {
- (*aFilterOut)->AddRemoteSSRC(ssrc);
- }
-
- // 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 (unsigned char& uniquePt : uniquePts) {
- (*aFilterOut)->AddUniquePT(uniquePt);
- }
- }
- }
-
- return NS_OK;
-}
-
-nsresult
-MediaPipelineFactory::CreateOrUpdateMediaPipeline(
- const JsepTrackPair& aTrackPair,
- const JsepTrack& aTrack)
-{
- // 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());
-
- 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;
- }
- conduit->SetPCHandle(mPC->GetHandle());
- } 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);
-
- // 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.
- }
-
- 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->SetLocalSSRCs(std::vector<unsigned int>(1,aTrackPair.mRecvonlySsrc))) {
- MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed");
- return NS_ERROR_FAILURE;
- }
- }
- } else {
- auto ssrcs = aTrack.GetSsrcs();
- if (!ssrcs.empty()) {
- if (!conduit->SetLocalSSRCs(ssrcs)) {
- MOZ_MTLOG(ML_ERROR, "SetLocalSSRCs 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,
- configs.values.back()->mFreq);
- }
-
- 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(mPCMedia->mCall);
- 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;
- }
-
- const std::vector<uint32_t>* ssrcs;
-
- const JsepTrackNegotiatedDetails* details = aTrack.GetNegotiatedDetails();
- std::vector<webrtc::RtpExtension> extmaps;
- if (details) {
- // @@NG read extmap from track
- details->ForEachRTPHeaderExtension(
- [&extmaps](const SdpExtmapAttributeList::Extmap& extmap)
- {
- extmaps.emplace_back(extmap.extensionname,extmap.entry);
- });
- }
-
- if (receiving) {
- // NOTE(pkerr) - the Call API requires the both local_ssrc and remote_ssrc be
- // set to a non-zero value or the CreateVideo...Stream call will fail.
- if (aTrackPair.mSending) {
- ssrcs = &aTrackPair.mSending->GetSsrcs();
- if (!ssrcs->empty()) {
- conduit->SetLocalSSRCs(*ssrcs);
- }
- } else {
- // No send track, but we still need to configure an SSRC for receiver
- // reports.
- if (!conduit->SetLocalSSRCs(std::vector<unsigned int>(1,aTrackPair.mRecvonlySsrc))) {
- MOZ_MTLOG(ML_ERROR, "SetLocalSSRCs failed");
- return NS_ERROR_FAILURE;
- }
- }
-
- ssrcs = &aTrack.GetSsrcs();
- // NOTE(pkerr) - this is new behavior. Needed because the CreateVideoReceiveStream
- // method of the Call API will assert (in debug) and fail if a value is not provided
- // for the remote_ssrc that will be used by the far-end sender.
- if (!ssrcs->empty()) {
- conduit->SetRemoteSSRC(ssrcs->front());
- }
-
- if (!extmaps.empty()) {
- conduit->SetLocalRTPExtensions(false, extmaps);
- }
- auto error = conduit->ConfigureRecvMediaCodecs(configs.values);
- if (error) {
- MOZ_MTLOG(ML_ERROR, "ConfigureRecvMediaCodecs failed: " << error);
- return NS_ERROR_FAILURE;
- }
- } else { //Create a send side
- // For now we only expect to have one ssrc per local track.
- ssrcs = &aTrack.GetSsrcs();
- if (ssrcs->empty()) {
- MOZ_MTLOG(ML_ERROR, "No SSRC set for send track");
- return NS_ERROR_FAILURE;
- }
-
- if (!conduit->SetLocalSSRCs(*ssrcs)) {
- 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;
- }
-
- if (!extmaps.empty()) {
- conduit->SetLocalRTPExtensions(true, extmaps);
- }
- auto error = conduit->ConfigureSendMediaCodec(configs.values[0]);
- 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)
-{
- 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;
- }
-
- return NS_OK;
-}
-
-
-} // namespace mozilla
deleted file mode 100644
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/* 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);
-
- 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
deleted file mode 100644
--- a/media/webrtc/signaling/src/peerconnection/MediaStreamList.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/* 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"
-#include "mozilla/dom/MediaStreamListBinding.h"
-#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()
-{
-}
-
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(MediaStreamList)
-
-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)
-{
- return MediaStreamListBinding::Wrap(cx, this, aGivenProto);
-}
-
-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
deleted file mode 100644
--- a/media/webrtc/signaling/src/peerconnection/MediaStreamList.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/* 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__
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -28,31 +28,35 @@
#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 "MediaEngine.h"
#include "AudioConduit.h"
#include "VideoConduit.h"
#include "runnable_utils.h"
#include "PeerConnectionCtx.h"
#include "PeerConnectionImpl.h"
#include "PeerConnectionMedia.h"
+#include "RemoteTrackSource.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 "signaling/src/mediapipeline/MediaPipeline.h"
+
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/Sprintf.h"
#ifdef XP_WIN
// We need to undef the MS macro for nsIDocument::CreateEvent
#ifdef CreateEvent
#undef CreateEvent
#endif
@@ -77,24 +81,24 @@
#include "nsIURLParser.h"
#include "nsIDOMDataChannel.h"
#include "NullPrincipal.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/RTCRtpReceiverBinding.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"
@@ -241,16 +245,35 @@ RTCStatsQuery::RTCStatsQuery(bool intern
RTCStatsQuery::~RTCStatsQuery() {
MOZ_ASSERT(NS_IsMainThread());
}
NS_IMPL_ISUPPORTS0(PeerConnectionImpl)
+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;
+}
+
bool
PeerConnectionImpl::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto,
JS::MutableHandle<JSObject*> aReflector)
{
return PeerConnectionImplBinding::Wrap(aCx, this, aGivenProto, aReflector);
}
@@ -309,17 +332,16 @@ PeerConnectionImpl::PeerConnectionImpl(c
, mAllowIceLinkLocal(false)
, mForceIceTcp(false)
, mMedia(nullptr)
, mUuidGen(MakeUnique<PCUuidGenerator>())
, mHaveConfiguredCodecs(false)
, mHaveDataStream(false)
, mAddCandidateErrorCount(0)
, mTrickle(true) // TODO(ekr@rtfm.com): Use pref
- , mNegotiationNeeded(false)
, mPrivateWindow(false)
, mActiveOnWindow(false)
{
MOZ_ASSERT(NS_IsMainThread());
auto log = RLogConnector::CreateInstance();
if (aGlobal) {
mWindow = do_QueryInterface(aGlobal->GetAsSupports());
if (IsPrivateBrowsing(mWindow)) {
@@ -382,48 +404,28 @@ PeerConnectionImpl::~PeerConnectionImpl(
// 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>
+OwningNonNull<DOMMediaStream>
PeerConnectionImpl::MakeMediaStream()
{
MediaStreamGraph* graph =
MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER, GetWindow());
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;
+ return *stream;
}
/**
* In JS, an RTCConfiguration looks like this:
*
* { "iceServers": [ { url:"stun:stun.example.org" },
* { url:"turn:turn.example.org?transport=udp",
* username: "jib", credential:"mypass"} ] }
@@ -1127,31 +1129,28 @@ nsresult
PeerConnectionImpl::GetDatachannelParameters(
uint32_t* channels,
uint16_t* localport,
uint16_t* remoteport,
uint32_t* remotemaxmessagesize,
bool* mmsset,
uint16_t* level) const {
- auto trackPairs = mJsepSession->GetNegotiatedTrackPairs();
- for (auto& trackPair : trackPairs) {
+ for (const auto& transceiver : mJsepSession->GetTransceivers()) {
bool sendDataChannel =
- trackPair.mSending &&
- trackPair.mSending->GetMediaType() == SdpMediaSection::kApplication;
+ transceiver->mSending.GetMediaType() == SdpMediaSection::kApplication;
bool recvDataChannel =
- trackPair.mReceiving &&
- trackPair.mReceiving->GetMediaType() == SdpMediaSection::kApplication;
+ transceiver->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);
+ transceiver->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;
}
@@ -1181,81 +1180,134 @@ PeerConnectionImpl::GetDatachannelParame
*localport =
static_cast<const JsepApplicationCodecDescription*>(codec)->mLocalPort;
*remoteport =
static_cast<const JsepApplicationCodecDescription*>(codec)->mRemotePort;
*remotemaxmessagesize = static_cast<const JsepApplicationCodecDescription*>
(codec)->mRemoteMaxMessageSize;
*mmsset = static_cast<const JsepApplicationCodecDescription*>
(codec)->mRemoteMMSSet;
- if (trackPair.HasBundleLevel()) {
- *level = static_cast<uint16_t>(trackPair.BundleLevel());
+ if (transceiver->HasBundleLevel()) {
+ *level = static_cast<uint16_t>(transceiver->BundleLevel());
} else {
- *level = static_cast<uint16_t>(trackPair.mLevel);
+ *level = static_cast<uint16_t>(transceiver->GetLevel());
}
return NS_OK;
}
}
}
*channels = 0;
*localport = 0;
*remoteport = 0;
*remotemaxmessagesize = 0;
*mmsset = false;
*level = 0;
return NS_ERROR_FAILURE;
}
-/* 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)
+PeerConnectionImpl::AddTransceiverToJsepSession(
+ RefPtr<JsepTransceiver>& transceiver)
{
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));
+ res = mJsepSession->AddTransceiver(transceiver);
if (NS_FAILED(res)) {
std::string errorString = mJsepSession->GetLastError();
CSFLogError(logTag, "%s (%s) : pc = %s, error = %s",
__FUNCTION__,
- type == SdpMediaSection::kAudio ? "audio" : "video",
+ transceiver->mSending.GetMediaType() == SdpMediaSection::kAudio ?
+ "audio" : "video",
mHandle.c_str(),
errorString.c_str());
return NS_ERROR_FAILURE;
}
return NS_OK;
}
+already_AddRefed<TransceiverImpl>
+PeerConnectionImpl::CreateTransceiverImpl(
+ RefPtr<JsepTransceiver>& aJsepTransceiver,
+ RefPtr<dom::MediaStreamTrack> aSendTrack,
+ ErrorResult& aRv)
+{
+ // TODO: Maybe this should be done in PeerConnectionMedia?
+ if (aSendTrack) {
+ aSendTrack->AddPrincipalChangeObserver(this);
+ }
+
+ OwningNonNull<DOMMediaStream> receiveStream =
+ CreateReceiveStreamWithTrack(aJsepTransceiver->mReceiving.GetMediaType());
+
+ RefPtr<TransceiverImpl> transceiverImpl;
+
+ aRv = mMedia->AddTransceiver(aJsepTransceiver,
+ receiveStream,
+ aSendTrack,
+ &transceiverImpl);
+
+ return transceiverImpl.forget();
+}
+
+already_AddRefed<TransceiverImpl>
+PeerConnectionImpl::CreateTransceiverImpl(
+ const nsAString& aKind,
+ RefPtr<dom::MediaStreamTrack> aSendTrack,
+ ErrorResult& jrv)
+{
+ SdpMediaSection::MediaType type;
+ if (aKind.EqualsASCII("audio")) {
+ type = SdpMediaSection::MediaType::kAudio;
+ } else if (aKind.EqualsASCII("video")) {
+ type = SdpMediaSection::MediaType::kVideo;
+ } else {
+ MOZ_ASSERT(false);
+ jrv = NS_ERROR_INVALID_ARG;
+ return nullptr;
+ }
+
+ RefPtr<JsepTransceiver> jsepTransceiver = new JsepTransceiver(type);
+
+ RefPtr<TransceiverImpl> transceiverImpl =
+ CreateTransceiverImpl(jsepTransceiver, aSendTrack, jrv);
+
+ if (jrv.Failed()) {
+ // Would be nice if we could peek at the rv without stealing it, so we
+ // could log...
+ CSFLogError(logTag, "%s: failed", __FUNCTION__);
+ return nullptr;
+ }
+
+ // Do this last, since it is not possible to roll back.
+ nsresult rv = AddTransceiverToJsepSession(jsepTransceiver);
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "%s: AddTransceiverToJsepSession failed, res=%u",
+ __FUNCTION__,
+ static_cast<unsigned>(rv));
+ jrv = rv;
+ return nullptr;
+ }
+
+ return transceiverImpl.forget();
+}
+
+bool
+PeerConnectionImpl::CheckNegotiationNeeded(ErrorResult &rv)
+{
+ MOZ_ASSERT(mSignalingState == PCImplSignalingState::SignalingStable);
+ return mJsepSession->CheckNegotiationNeeded();
+}
+
nsresult
PeerConnectionImpl::InitializeDataChannel()
{
PC_AUTO_ENTER_API_CALL(false);
CSFLogDebug(logTag, "%s", __FUNCTION__);
uint32_t channels = 0;
uint16_t localport = 0;
@@ -1345,42 +1397,19 @@ PeerConnectionImpl::CreateDataChannel(co
(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;
- }
+ mJsepSession->AddTransceiver(
+ new JsepTransceiver(SdpMediaSection::MediaType::kApplication));
mHaveDataStream = true;
- OnNegotiationNeeded();
}
nsIDOMDataChannel *retval;
rv = NS_NewDOMDataChannel(dataChannel.forget(), mWindow, &retval);
if (NS_FAILED(rv)) {
return rv;
}
*aRetval = static_cast<nsDOMDataChannel*>(retval);
return NS_OK;
@@ -1566,20 +1595,20 @@ PeerConnectionImpl::CreateOffer(const Js
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 {
+ UpdateSignalingState();
pco->OnCreateOfferSuccess(ObString(offer.c_str()), rv);
}
- UpdateSignalingState();
return NS_OK;
}
NS_IMETHODIMP
PeerConnectionImpl::CreateAnswer()
{
PC_AUTO_ENTER_API_CALL(true);
@@ -1625,21 +1654,20 @@ PeerConnectionImpl::CreateAnswer()
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 {
+ UpdateSignalingState();
pco->OnCreateAnswerSuccess(ObString(answer.c_str()), rv);
}
- UpdateSignalingState();
-
return NS_OK;
}
nsresult
PeerConnectionImpl::SetupIceRestart()
{
if (mMedia->IsIceRestarting()) {
CSFLogError(logTag, "%s: ICE already restarting",
@@ -1757,20 +1785,20 @@ PeerConnectionImpl::SetLocalDescription(
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 {
+ UpdateSignalingState(sdpType == mozilla::kJsepSdpRollback);
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);
@@ -1778,243 +1806,51 @@ static void DeferredSetRemote(const std:
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) {
- 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);
-}
-
-
nsresult
-PeerConnectionImpl::CreateNewRemoteTracks(RefPtr<PeerConnectionObserver>& aPco)
+PeerConnectionImpl::FireOnTrackEvents(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 track : newTracks) {
- if (track->GetMediaType() == mozilla::SdpMediaSection::kApplication) {
+ for (auto& track : mJsepSession->GetRemoteTracksAdded()) {
+ if (track.GetMediaType() == mozilla::SdpMediaSection::kApplication) {
// Ignore datachannel
continue;
}
- tracksByStreamId[track->GetStreamId()].push_back(track);
- }
-
- for (auto& id : tracksByStreamId) {
- std::string streamId = id.first;
- std::vector<RefPtr<JsepTrack>>& tracks = id.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());
-
- info->GetMediaStream()->AssignId(NS_ConvertUTF8toUTF16(streamId.c_str()));
- info->GetMediaStream()->SetLogicalStreamStartTime(
- info->GetMediaStream()->GetPlaybackStream()->GetCurrentTime());
- }
-
- Sequence<OwningNonNull<DOMMediaStream>> streams;
- if (!streams.AppendElement(OwningNonNull<DOMMediaStream>(
- *info->GetMediaStream()),
- fallible)) {
+ if (track.GetTrackId().empty()) {
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 = NullPrincipal::CreateWithInheritedAttributes(doc->NodePrincipal());
+ return NS_ERROR_UNEXPECTED;
}
- // 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);
- }
+ nsString trackId = NS_ConvertUTF8toUTF16(track.GetTrackId().c_str());
+
+ dom::Sequence<nsString> streamIds;
+ for (const std::string& streamId : track.GetStreamIds()) {
+ // If this fails, oh well.
+ streamIds.AppendElement(
+ NS_ConvertASCIItoUTF16(streamId.c_str()), fallible);
}
- for (RefPtr<JsepTrack>& track : tracks) {
- std::string webrtcTrackId(track->GetTrackId());
- if (!info->HasTrack(webrtcTrackId)) {
- RefPtr<RemoteTrackSource> source =
- new RemoteTrackSource(principal, nsString());
- 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);
- segment = new VideoSegment;
- }
-
- 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());
-
- 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());
- }
- }
- }
-
- if (newStream) {
- aPco->OnAddStream(*info->GetMediaStream(), jrv);
- if (jrv.Failed()) {
- CSFLogError(logTag, ": OnAddStream() failed! Error: %u",
- jrv.ErrorCodeAsInt());
- }
+ JSErrorResult jrv;
+ aPco->OnTrack(trackId, streamIds, jrv);
+ if (jrv.Failed()) {
+ CSFLogError(logTag, ": OnTrack(%s) failed! Error: %u",
+ track.GetTrackId().c_str(),
+ jrv.ErrorCodeAsInt());
}
}
+
return NS_OK;
}
-void
-PeerConnectionImpl::RemoveOldRemoteTracks(RefPtr<PeerConnectionObserver>& aPco)
-{
- JSErrorResult jrv;
-
- std::vector<RefPtr<JsepTrack>> removedTracks =
- mJsepSession->GetRemoteTracksRemoved();
-
- for (auto& removedTrack : removedTracks) {
- const std::string& streamId = removedTrack->GetStreamId();
- const std::string& trackId = removedTrack->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;
@@ -2063,16 +1899,17 @@ PeerConnectionImpl::SetRemoteDescription
case IPeerConnection::kActionRollback:
sdpType = mozilla::kJsepSdpRollback;
break;
default:
MOZ_ASSERT(false);
return NS_ERROR_FAILURE;
}
+ size_t transceiverCount = mJsepSession->GetTransceivers().size();
nsresult nrv = mJsepSession->SetRemoteDescription(sdpType,
mRemoteRequestedSDP);
if (NS_FAILED(nrv)) {
Error error;
switch (nrv) {
case NS_ERROR_INVALID_ARG:
error = kInvalidSessionDescription;
break;
@@ -2083,29 +1920,74 @@ PeerConnectionImpl::SetRemoteDescription
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);
+ // Iterate over the JSEP transceivers that were just created
+ while (transceiverCount < mJsepSession->GetTransceivers().size()) {
+ RefPtr<JsepTransceiver> jsepTransceiver =
+ mJsepSession->GetTransceivers()[transceiverCount++];
+
+ if (jsepTransceiver->mReceiving.GetMediaType() ==
+ SdpMediaSection::MediaType::kApplication) {
+ continue;
+ }
+
+ // Audio or video transceiver, need to tell JS about it.
+ RefPtr<TransceiverImpl> transceiverImpl =
+ CreateTransceiverImpl(jsepTransceiver, nullptr, jrv);
+ if (jrv.Failed()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const JsepTrack& receiving(jsepTransceiver->mReceiving);
+ CSFLogInfo(logTag, "%s: pc = %s, asking JS to create transceiver for %s",
+ __FUNCTION__, mHandle.c_str(), receiving.GetTrackId().c_str());
+ switch (receiving.GetMediaType()) {
+ case SdpMediaSection::MediaType::kAudio:
+ pco->OnTransceiverNeeded(
+ NS_ConvertASCIItoUTF16("audio"), *transceiverImpl, jrv);
+ break;
+ case SdpMediaSection::MediaType::kVideo:
+ pco->OnTransceiverNeeded(
+ NS_ConvertASCIItoUTF16("video"), *transceiverImpl, jrv);
+ break;
+ default:
+ MOZ_RELEASE_ASSERT(false);
+ }
+
+ if (jrv.Failed()) {
+ nsresult rv = jrv.StealNSResult();
+ CSFLogError(logTag, "%s: pc = %s, OnTransceiverNeeded failed. "
+ "This should never happen. rv = %d",
+ __FUNCTION__, mHandle.c_str(), static_cast<int>(rv));
+ MOZ_CRASH();
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ UpdateSignalingState(sdpType == mozilla::kJsepSdpRollback);
+
+ // This needs to come first, because it is what prompts JS to sync their
+ // transceivers with ours.
+ pco->OnSetRemoteDescriptionSuccess(jrv);
+
+ nrv = FireOnTrackEvents(pco);
if (NS_FAILED(nrv)) {
// aPco was already notified, just return early.
return NS_OK;
}
- RemoveOldRemoteTracks(pco);
-
- pco->OnSetRemoteDescriptionSuccess(jrv);
startCallTelem();
}
- UpdateSignalingState(sdpType == mozilla::kJsepSdpRollback);
return NS_OK;
}
// WebRTC uses highres time relative to the UNIX epoch (Jan 1, 1970, UTC).
nsresult
PeerConnectionImpl::GetTimeSinceEpoch(DOMHighResTimeStamp *result) {
MOZ_ASSERT(NS_IsMainThread());
@@ -2295,173 +2177,77 @@ PeerConnectionImpl::PrincipalChanged(Med
nsIDocument* doc = GetWindow()->GetExtantDoc();
if (doc) {
mMedia->UpdateSinkIdentity_m(aTrack, doc->NodePrincipal(), mPeerIdentity);
} else {
CSFLogInfo(logTag, "Can't update sink principal; document gone");
}
}
-std::string
-PeerConnectionImpl::GetTrackId(const MediaStreamTrack& aTrack)
-{
- nsString wideTrackId;
- aTrack.GetId(wideTrackId);
- return NS_ConvertUTF16toUTF8(wideTrackId).get();
-}
-
-std::string
-PeerConnectionImpl::GetStreamId(const DOMMediaStream& aStream)
-{
- nsString wideStreamId;
- aStream.GetId(wideStreamId);
- return NS_ConvertUTF16toUTF8(wideStreamId).get();
-}
-
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);
- PrincipalChanged(&aTrack);
-
- if (aTrack.AsAudioStreamTrack()) {
- res = AddTrackToJsepSession(SdpMediaSection::kAudio, streamId, trackId);
- if (NS_FAILED(res)) {
- return res;
- }
- }
-
- if (aTrack.AsVideoStreamTrack()) {
- 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;
- }
-
- res = AddTrackToJsepSession(SdpMediaSection::kVideo, streamId, trackId);
- if (NS_FAILED(res)) {
- return res;
- }
- }
- OnNegotiationNeeded();
return NS_OK;
}
-RefPtr<MediaPipeline>
-PeerConnectionImpl::GetMediaPipelineForTrack(MediaStreamTrack& aRecvTrack)
-{
- 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()) {
- return it->second;
- }
- }
- }
-
- return nullptr;
-}
-
nsresult
PeerConnectionImpl::AddRIDExtension(MediaStreamTrack& aRecvTrack,
unsigned short aExtensionId)
{
- RefPtr<MediaPipeline> pipeline = GetMediaPipelineForTrack(aRecvTrack);
- if (pipeline) {
- pipeline->AddRIDExtension_m(aExtensionId);
- }
- return NS_OK;
+ return mMedia->AddRIDExtension(aRecvTrack, aExtensionId);
}
nsresult
PeerConnectionImpl::AddRIDFilter(MediaStreamTrack& aRecvTrack,
const nsAString& aRid)
{
- RefPtr<MediaPipeline> pipeline = GetMediaPipelineForTrack(aRecvTrack);
- if (pipeline) {
- pipeline->AddRIDFilter_m(NS_ConvertUTF16toUTF8(aRid).get());
- }
- return NS_OK;
+ return mMedia->AddRIDFilter(aRecvTrack, aRid);
}
NS_IMETHODIMP
PeerConnectionImpl::RemoveTrack(MediaStreamTrack& aTrack) {
PC_AUTO_ENTER_API_CALL(true);
- std::string trackId = PeerConnectionImpl::GetTrackId(aTrack);
-
- 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);
+ std::vector<RefPtr<TransceiverImpl>>& transceivers =
+ mMedia->GetTransceivers();
+
+ nsresult rv = NS_ERROR_INVALID_ARG;
+
+ for (RefPtr<TransceiverImpl>& transceiver : transceivers) {
+ if (transceiver->HasSendTrack(&aTrack)) {
+ // TODO(bug XXXXX): Move DTMF stuff to TransceiverImpl
+ for (size_t i = 0; i < mDTMFStates.Length(); ++i) {
+ if (mDTMFStates[i].mTransceiver.get() == transceiver.get()) {
+ mDTMFStates[i].mSendTimer->Cancel();
+ mDTMFStates.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ rv = transceiver->UpdateSendTrack(nullptr);
break;
}
}
- 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());
+ CSFLogError(logTag, "Error updating send track on transceiver");
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 == ',') {
@@ -2469,71 +2255,87 @@ static int GetDTMFToneCode(uint16_t c)
return -1;
}
const char* i = strchr(DTMF_TONECODES, c);
MOZ_ASSERT(i);
return i - DTMF_TONECODES;
}
+OwningNonNull<DOMMediaStream>
+PeerConnectionImpl::CreateReceiveStreamWithTrack(
+ SdpMediaSection::MediaType type) {
+
+ OwningNonNull<DOMMediaStream> stream = MakeMediaStream();
+
+ // 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 = NullPrincipal::CreateWithInheritedAttributes(doc->NodePrincipal());
+ }
+
+ RefPtr<RemoteTrackSource> source =
+ new RemoteTrackSource(principal, nsString());
+
+ RefPtr<MediaStreamTrack> track;
+ switch (type) {
+ case SdpMediaSection::MediaType::kAudio:
+ track = stream->CreateDOMTrack(kAudioTrack, MediaSegment::AUDIO, source);
+ break;
+ case SdpMediaSection::MediaType::kVideo:
+ track = stream->CreateDOMTrack(kVideoTrack, MediaSegment::VIDEO, source);
+ break;
+ default:
+ MOZ_ASSERT(false, "Bad media kind; our JS passed some garbage");
+ }
+ stream->AddTrackInternal(track);
+
+ return stream;
+}
+
NS_IMETHODIMP
-PeerConnectionImpl::InsertDTMF(mozilla::dom::RTCRtpSender& sender,
+PeerConnectionImpl::InsertDTMF(TransceiverImpl& transceiver,
const nsAString& tones, uint32_t duration,
uint32_t interToneGap) {
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
DTMFState* state = nullptr;
for (auto& dtmfState : mDTMFStates) {
- if (dtmfState.mTrackId == senderTrackId) {
+ if (dtmfState.mTransceiver.get() == &transceiver) {
state = &dtmfState;
break;
}
}
// No state yet, create a new one
if (!state) {
state = mDTMFStates.AppendElement();
- state->mPeerConnectionImpl = this;
- state->mTrackId = senderTrackId;
+ state->mPCObserver = mPCObserver;
+ state->mTransceiver = &transceiver;
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.HasBundleLevel()) {
- state->mLevel = trackPair.BundleLevel();
- } else {
- state->mLevel = trackPair.mLevel;
- }
- break;
- }
- }
-
state->mTones = tones;
state->mDuration = duration;
state->mInterToneGap = interToneGap;
if (!state->mTones.IsEmpty()) {
state->mSendTimer->InitWithNamedFuncCallback(DTMFSendTimerCallback_m, state, 0,
nsITimer::TYPE_ONE_SHOT,
"DTMFSendTimerCallback_m");
}
@@ -2549,220 +2351,83 @@ PeerConnectionImpl::GetDTMFToneBuffer(mo
// 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) {
+ if (dtmfState.mTransceiver->HasSendTrack(mst)) {
outToneBuffer = dtmfState.mTones;
break;
}
}
return NS_OK;
}
NS_IMETHODIMP
-PeerConnectionImpl::ReplaceTrack(MediaStreamTrack& aThisTrack,
- MediaStreamTrack& aWithTrack) {
+PeerConnectionImpl::ReplaceTrackNoRenegotiation(TransceiverImpl& aTransceiver,
+ MediaStreamTrack* aWithTrack) {
PC_AUTO_ENTER_API_CALL(true);
- nsString trackId;
- aThisTrack.GetId(trackId);
+ RefPtr<dom::MediaStreamTrack> oldSendTrack(aTransceiver.GetSendTrack());
+ if (oldSendTrack) {
+ oldSendTrack->RemovePrincipalChangeObserver(this);
+ }
+
+ nsresult rv = aTransceiver.UpdateSendTrack(aWithTrack);
+
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag,
+ "Failed to update transceiver: %d", static_cast<int>(rv));
+ return rv;
+ }
for (size_t i = 0; i < mDTMFStates.Length(); ++i) {
- if (mDTMFStates[i].mTrackId == trackId) {
+ if (mDTMFStates[i].mTransceiver.get() == &aTransceiver) {
mDTMFStates[i].mSendTimer->Cancel();
mDTMFStates.RemoveElementAt(i);
break;
}
}
- RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
- if (!pco) {
- return NS_ERROR_UNEXPECTED;
- }
- JSErrorResult jrv;
-
- 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;
- }
- 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;
+ if (aWithTrack) {
+ aWithTrack->AddPrincipalChangeObserver(this);
+ PrincipalChanged(aWithTrack);
}
- 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);
- PrincipalChanged(&aWithTrack);
-
// 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.
- if (NS_FAILED((rv = mMedia->UpdateMediaPipelines(*mJsepSession)))) {
+ if (NS_FAILED((rv = mMedia->UpdateMediaPipelines()))) {
CSFLogError(logTag, "Error Updating MediaPipelines");
return rv;
}
+ RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
+ if (!pco) {
+ CSFLogError(logTag, "Error getting PC observer");
+ return NS_ERROR_UNEXPECTED;
+ }
+ JSErrorResult jrv;
+
pco->OnReplaceTrackSuccess(jrv);
if (jrv.Failed()) {
CSFLogError(logTag, "Error firing replaceTrack success callback");
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
-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);
-}
-
-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);
-}
-
-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;
-}
-
-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);
@@ -3056,20 +2721,20 @@ void
PeerConnectionImpl::ShutdownMedia()
{
PC_AUTO_ENTER_API_CALL_NO_CHECK();
if (!mMedia)
return;
// 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);
+ for(RefPtr<TransceiverImpl>& transceiver : mMedia->GetTransceivers()) {
+ RefPtr<dom::MediaStreamTrack> track = transceiver->GetSendTrack();
+ if (track) {
+ track->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());
@@ -3094,53 +2759,42 @@ PeerConnectionImpl::SetSignalingState_m(
(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.
+ // transports, and stomp some MediaPipeline setup, but nothing further
+ // needs to be done.
mMedia->ActivateOrRemoveTransports(*mJsepSession, mForceIceTcp);
+ mMedia->UpdateTransceiverTransports(*mJsepSession);
+ if (NS_FAILED(mMedia->UpdateMediaPipelines())) {
+ CSFLogError(logTag, "Error Updating MediaPipelines");
+ NS_ASSERTION(false, "Error Updating MediaPipelines in SetSignalingState_m()");
+ // XXX what now? Not much we can do but keep going, without major restructuring
+ }
+
if (!rollback) {
- if (NS_FAILED(mMedia->UpdateMediaPipelines(*mJsepSession))) {
- CSFLogError(logTag, "Error Updating MediaPipelines");
- NS_ASSERTION(false, "Error Updating MediaPipelines in SetSignalingState_m()");
- // XXX what now? Not much we can do but keep going, without major restructuring
- }
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++) {
@@ -3163,28 +2817,21 @@ PeerConnectionImpl::SetSignalingState_m(
}
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();
+ mozilla::JsepSignalingState state = mJsepSession->GetState();
PCImplSignalingState newState;
switch(state) {
case kJsepStateStable:
newState = PCImplSignalingState::SignalingStable;
break;
case kJsepStateHaveLocalOffer:
@@ -3576,36 +3223,18 @@ PeerConnectionImpl::BuildStatsQuery_m(
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);
- }
- }
- }
+ mMedia->GetTransmitPipelinesMatching(aSelector, &query->pipelines);
+ mMedia->GetReceivePipelinesMatching(aSelector, &query->pipelines);
if (!aSelector) {
query->grabAllLevels = true;
}
return rv;
}
@@ -3709,17 +3338,17 @@ PeerConnectionImpl::ExecuteStatsQuery_s(
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());
+ idstr.AppendInt((uint32_t)p);
// TODO(@@NG):ssrcs handle Conduits having multiple stats at the same level
// This is pending spec work
// Gather pipeline stats.
switch (mp.direction()) {
case MediaPipeline::TRANSMIT: {
nsString localId = NS_LITERAL_STRING("outbound_rtp_") + idstr;
nsString remoteId;
@@ -3999,64 +3628,16 @@ void PeerConnectionImpl::DeliverStatsRep
}
void
PeerConnectionImpl::RecordLongtermICEStatistics() {
WebrtcGlobalInformation::StoreLongTermICEStatistics(*this);
}
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());
}
@@ -4070,40 +3651,16 @@ PeerConnectionImpl::startCallTelem() {
// 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);
}
-NS_IMETHODIMP
-PeerConnectionImpl::GetLocalStreams(nsTArray<RefPtr<DOMMediaStream > >& result)
-{
- PC_AUTO_ENTER_API_CALL_NO_CHECK();
- 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;
-}
-
-NS_IMETHODIMP
-PeerConnectionImpl::GetRemoteStreams(nsTArray<RefPtr<DOMMediaStream > >& result)
-{
- PC_AUTO_ENTER_API_CALL_NO_CHECK();
- 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;
-}
-
void
PeerConnectionImpl::DTMFSendTimerCallback_m(nsITimer* timer, void* closure)
{
MOZ_ASSERT(NS_IsMainThread());
auto state = static_cast<DTMFState*>(closure);
nsString eventTone;
@@ -4121,41 +3678,36 @@ PeerConnectionImpl::DTMFSendTimerCallbac
"DTMFSendTimerCallback_m");
} else {
// Reset delay if necessary
state->mSendTimer->InitWithNamedFuncCallback(DTMFSendTimerCallback_m, state,
state->mDuration + state->mInterToneGap,
nsITimer::TYPE_ONE_SHOT,
"DTMFSendTimerCallback_m");
- RefPtr<AudioSessionConduit> conduit =
- state->mPeerConnectionImpl->mMedia->GetAudioConduit(state->mLevel);
-
- if (conduit) {
- uint32_t duration = state->mDuration;
- state->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);
- }
-
+ state->mTransceiver->InsertDTMFTone(tone, state->mDuration);
}
} else {
state->mSendTimer->Cancel();
}
- RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(state->mPeerConnectionImpl->mPCObserver);
+ RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(state->mPCObserver);
if (!pco) {
NS_WARNING("Failed to dispatch the RTCDTMFToneChange event!");
return;
}
JSErrorResult jrv;
- pco->OnDTMFToneChange(state->mTrackId, eventTone, jrv);
+ pco->OnDTMFToneChange(*state->mTransceiver->GetSendTrack(), eventTone, jrv);
if (jrv.Failed()) {
NS_WARNING("Failed to dispatch the RTCDTMFToneChange event!");
return;
}
}
+PeerConnectionImpl::DTMFState::DTMFState()
+{}
+
+PeerConnectionImpl::DTMFState::~DTMFState()
+{}
+
} // end mozilla namespace
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -26,16 +26,17 @@
#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 "mozilla/dom/RTCRtpTransceiverBinding.h"
#include "PrincipalChangeObserver.h"
#include "StreamTracks.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/net/DataChannel.h"
#include "VideoUtils.h"
#include "VideoSegment.h"
#include "mozilla/dom/RTCStatsReportBinding.h"
@@ -54,16 +55,17 @@ class nsDOMDataChannel;
namespace mozilla {
class DataChannel;
class DtlsIdentity;
class NrIceCtx;
class NrIceMediaStream;
class NrIceStunServer;
class NrIceTurnServer;
class MediaPipeline;
+class TransceiverImpl;
class DOMMediaStream;
namespace dom {
class RTCCertificate;
struct RTCConfiguration;
class RTCDTMFSender;
struct RTCIceServer;
@@ -244,17 +246,17 @@ public:
NS_DECL_THREADSAFE_ISUPPORTS
bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aReflector);
static already_AddRefed<PeerConnectionImpl>
Constructor(const mozilla::dom::GlobalObject& aGlobal, ErrorResult& rv);
static PeerConnectionImpl* CreatePeerConnection();
- already_AddRefed<DOMMediaStream> MakeMediaStream();
+ OwningNonNull<DOMMediaStream> MakeMediaStream();
nsresult CreateRemoteSourceStreamInfo(RefPtr<RemoteSourceStreamInfo>* aInfo,
const std::string& aId);
// DataConnection observers
void NotifyDataChannel(already_AddRefed<mozilla::DataChannel> aChannel)
// PeerConnectionImpl only inherits from mozilla::DataChannelConnection
// inside libxul.
@@ -351,19 +353,17 @@ public:
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);
+ nsresult FireOnTrackEvents(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());
}
@@ -403,34 +403,44 @@ public:
mozilla::dom::MediaStreamTrack& aTrack)
{
rv = RemoveTrack(aTrack);
}
nsresult
AddTrack(mozilla::dom::MediaStreamTrack& aTrack, DOMMediaStream& aStream);
+ already_AddRefed<TransceiverImpl> CreateTransceiverImpl(
+ const nsAString& aKind,
+ RefPtr<dom::MediaStreamTrack> aSendTrack,
+ ErrorResult& rv);
+
+ OwningNonNull<DOMMediaStream> CreateReceiveStreamWithTrack(
+ SdpMediaSection::MediaType type);
+
+ bool CheckNegotiationNeeded(ErrorResult &rv);
+
NS_IMETHODIMP_TO_ERRORRESULT(InsertDTMF, ErrorResult &rv,
- dom::RTCRtpSender& sender,
+ TransceiverImpl& transceiver,
const nsAString& tones,
uint32_t duration, uint32_t interToneGap) {
- rv = InsertDTMF(sender, tones, duration, interToneGap);
+ rv = InsertDTMF(transceiver, 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)
+ NS_IMETHODIMP_TO_ERRORRESULT(ReplaceTrackNoRenegotiation, ErrorResult &rv,
+ TransceiverImpl& aTransceiver,
+ mozilla::dom::MediaStreamTrack* aWithTrack)
{
- rv = ReplaceTrack(aThisTrack, aWithTrack);
+ rv = ReplaceTrackNoRenegotiation(aTransceiver, aWithTrack);
}
NS_IMETHODIMP_TO_ERRORRESULT(SetParameters, ErrorResult &rv,
dom::MediaStreamTrack& aTrack,
const dom::RTCRtpParameters& aParameters)
{
rv = SetParameters(aTrack, aParameters);
}
@@ -566,28 +576,16 @@ public:
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();
@@ -616,19 +614,16 @@ public:
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;
- 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;
@@ -667,24 +662,21 @@ private:
nsresult GetDatachannelParameters(
uint32_t* channels,
uint16_t* localport,
uint16_t* remoteport,
uint32_t* maxmessagesize,
bool* mmsset,
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 AddTransceiverToJsepSession(RefPtr<JsepTransceiver>& transceiver);
+ already_AddRefed<TransceiverImpl> CreateTransceiverImpl(
+ RefPtr<JsepTransceiver>& aJsepTransceiver,
+ RefPtr<dom::MediaStreamTrack> aSendTrack,
+ ErrorResult& aRv);
nsresult SetupIceRestart();
nsresult RollbackIceRestart();
void FinalizeIceRestart();
static void GetStatsForPCObserver_s(
const std::string& pcHandle,
nsAutoPtr<RTCStatsQuery> query);
@@ -697,20 +689,16 @@ private:
// 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
@@ -766,48 +754,46 @@ private:
bool mForceIceTcp;
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
-
// Start time of ICE, used for telemetry
mozilla::TimeStamp mIceStartTime;
// Start time of call used for Telemetry
mozilla::TimeStamp mStartTime;
bool mHaveConfiguredCodecs;
bool mHaveDataStream;
unsigned int mAddCandidateErrorCount;
bool mTrickle;
- bool mNegotiationNeeded;
-
bool mPrivateWindow;
// Whether this PeerConnection is being counted as active by mWindow
bool mActiveOnWindow;
// storage for Telemetry data
uint16_t mMaxReceiving[SdpMediaSection::kMediaTypes];
uint16_t mMaxSending[SdpMediaSection::kMediaTypes];
// DTMF
struct DTMFState {
- PeerConnectionImpl* mPeerConnectionImpl;
+ DTMFState();
+ ~DTMFState();
+ nsWeakPtr mPCObserver;
+ RefPtr<TransceiverImpl> mTransceiver;
nsCOMPtr<nsITimer> mSendTimer;
- nsString mTrackId;
nsString mTones;
- size_t mLevel;
uint32_t mDuration;
uint32_t mInterToneGap;
};
static void
DTMFSendTimerCallback_m(nsITimer* timer, void*);
nsTArray<DTMFState> mDTMFStates;
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -7,199 +7,57 @@
#include <vector>
#include "CSFLog.h"
#include "nspr.h"
#include "nricectx.h"
#include "nricemediastream.h"
-#include "MediaPipelineFactory.h"
+#include "MediaPipelineFilter.h"
+#include "MediaPipeline.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"
-#include "MediaSegment.h"
-#include "MediaStreamGraph.h"
-
-#include "MediaStreamGraphImpl.h"
-
#include "nsContentUtils.h"
#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"
-#include "MediaStreamList.h"
#include "nsIScriptGlobalObject.h"
#include "mozilla/Preferences.h"
#include "mozilla/Telemetry.h"
-#include "mozilla/dom/RTCStatsReportBinding.h"
-#include "MediaStreamTrack.h"
-#include "VideoStreamTrack.h"
-#include "MediaStreamError.h"
#include "MediaManager.h"
-
-
+#include "WebrtcGmpVideoCodec.h"
namespace mozilla {
using namespace dom;
static const char* logTag = "PeerConnectionMedia";
//XXX(pkerr) What about bitrate settings? Going with the defaults for now.
RefPtr<WebRtcCallWrapper>
CreateCall()
{
return WebRtcCallWrapper::Create();
}
-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;
- }
-
- 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));
-}
-
-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& pipeline : mPipelines) {
- pipeline.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& pipeline : mPipelines) {
- pipeline.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
@@ -283,16 +141,21 @@ PeerConnectionMedia::PeerConnectionMedia
mUuidGen(MakeUnique<PCUuidGenerator>()),
mMainThread(mParent->GetMainThread()),
mSTSThread(mParent->GetSTSThread()),
mProxyResolveCompleted(false),
mIceRestartState(ICE_RESTART_NONE),
mLocalAddrsCompleted(false) {
}
+PeerConnectionMedia::~PeerConnectionMedia()
+{
+ MOZ_RELEASE_ASSERT(!mMainThread);
+}
+
void
PeerConnectionMedia::InitLocalAddrs()
{
if (XRE_IsContentProcess()) {
CSFLogDebug(logTag, "%s: Get stun addresses via IPC",
mParentHandle.c_str());
nsCOMPtr<nsIEventTarget> target = mParent->GetWindow()
@@ -424,24 +287,27 @@ nsresult PeerConnectionMedia::Init(const
mCall = CreateCall();
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];
+ for (const auto& transceiver : aSession.GetTransceivers()) {
+ if (!transceiver->HasLevel()) {
+ continue;
+ }
+
+ RefPtr<JsepTransport> transport = transceiver->mTransport;
RUN_ON_THREAD(
GetSTSThread(),
WrapRunnable(RefPtr<PeerConnectionMedia>(this),
&PeerConnectionMedia::EnsureTransport_s,
- i,
+ transceiver->GetLevel(),
transport->mComponents),
NS_DISPATCH_NORMAL);
}
GatherIfReady();
}
void
@@ -468,69 +334,95 @@ PeerConnectionMedia::EnsureTransport_s(s
stream->SetLevel(aLevel);
stream->SignalReady.connect(this, &PeerConnectionMedia::IceStreamReady_s);
stream->SignalCandidate.connect(this,
&PeerConnectionMedia::OnCandidateFound_s);
mIceCtxHdlr->ctx()->SetStream(aLevel, stream);
}
}
-void
+nsresult
PeerConnectionMedia::ActivateOrRemoveTransports(const JsepSession& aSession,
const bool forceIceTcp)
{
- auto transports = aSession.GetTransports();
- for (size_t i = 0; i < transports.size(); ++i) {
- RefPtr<JsepTransport> transport = transports[i];
+ for (const auto& transceiver : aSession.GetTransceivers()) {
+ if (!transceiver->HasLevel()) {
+ continue;
+ }
std::string ufrag;
std::string pwd;
std::vector<std::string> candidates;
+ size_t components = 0;
- if (transport->mComponents) {
- MOZ_ASSERT(transport->mIce);
- CSFLogDebug(logTag, "Transport %u is active", static_cast<unsigned>(i));
+ RefPtr<JsepTransport> transport = transceiver->mTransport;
+ unsigned level = transceiver->GetLevel();
+
+ bool levelHasTransport =
+ transport->mComponents &&
+ transport->mIce &&
+ (!transceiver->HasBundleLevel() || (transceiver->BundleLevel() == level));
+
+ if (levelHasTransport) {
+ CSFLogDebug(logTag, "ACTIVATING TRANSPORT! - PC %s: level=%u components=%u",
+ mParentHandle.c_str(), (unsigned)level,
+ (unsigned)transport->mComponents);
+
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);
- }
-
- if (forceIceTcp) {
- candidates.erase(std::remove_if(candidates.begin(),
- candidates.end(),
- [](const std::string & s) {
- return s.find(" UDP ") != std::string::npos ||
- s.find(" udp ") != std::string::npos; }),
- candidates.end());
+ components = transport->mComponents;
+ if (forceIceTcp) {
+ candidates.erase(std::remove_if(candidates.begin(),
+ candidates.end(),
+ [](const std::string & s) {
+ return s.find(" UDP ") != std::string::npos ||
+ s.find(" udp ") != std::string::npos; }),
+ candidates.end());
+ }
}
RUN_ON_THREAD(
GetSTSThread(),
WrapRunnable(RefPtr<PeerConnectionMedia>(this),
&PeerConnectionMedia::ActivateOrRemoveTransport_s,
- i,
- transport->mComponents,
+ transceiver->GetLevel(),
+ components,
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()),
+ aSession.GetTransceivers().size()),
NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+nsresult
+PeerConnectionMedia::UpdateTransceiverTransports(const JsepSession& aSession)
+{
+ for (const auto& transceiver : aSession.GetTransceivers()) {
+ nsresult rv = UpdateTransportFlows(*transceiver);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ for (const auto& transceiverImpl : mTransceivers) {
+ transceiverImpl->UpdateTransport(*this);
+ }
+
+ return NS_OK;
}
void
PeerConnectionMedia::ActivateOrRemoveTransport_s(
size_t aMLine,
size_t aComponentCount,
const std::string& aUfrag,
const std::string& aPassword,
@@ -580,42 +472,210 @@ PeerConnectionMedia::ActivateOrRemoveTra
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
+PeerConnectionMedia::UpdateMediaPipelines()
+{
+ // The GMP code is all the way on the other side of webrtc.org, and it is not
+ // feasible to plumb error information all the way back. So, we set up a
+ // handle to the PC (for the duration of this call) in a global variable.
+ // This allows the GMP code to report errors to the PC.
+ WebrtcGmpPCHandleSetter setter(mParentHandle);
+
+ for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ nsresult rv = transceiver->UpdateConduit();
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH();
+ return rv;
+ }
+
+ if (!transceiver->IsVideo()) {
+ rv = transceiver->SyncWithMatchingVideoConduits(mTransceivers);
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH();
+ return rv;
+ }
+ // TODO: If there is no audio, we should probably de-sync. However, this
+ // has never been done before, and it is unclear whether it is safe...
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+PeerConnectionMedia::UpdateTransportFlows(const JsepTransceiver& aTransceiver)
+{
+ if (!aTransceiver.HasLevel()) {
+ // Nothing to do
+ return NS_OK;
+ }
+
+ size_t transportLevel = aTransceiver.GetTransportLevel();
+
+ nsresult rv =
+ UpdateTransportFlow(transportLevel, false, *aTransceiver.mTransport);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return UpdateTransportFlow(transportLevel, true, *aTransceiver.mTransport);
+}
+
+// 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_media_stream(aLevel),
+ aIsRtcp ? 2 : 1);
+ nsAutoPtr<std::queue<TransportLayer*> > layerQueue(
+ new std::queue<TransportLayer*>);
+ for (auto& value : aLayerList->values) {
+ layerQueue->push(value);
+ }
+ 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_media_stream(aLevel),
+ aIsRtcp ? 2 : 1);
+}
+
+nsresult
+PeerConnectionMedia::UpdateTransportFlow(
+ size_t aLevel,
+ bool aIsRtcp,
+ const JsepTransport& aTransport)
+{
+ if (aIsRtcp && aTransport.mComponents < 2) {
+ RemoveTransportFlow(aLevel, aIsRtcp);
+ return NS_OK;
+ }
+
+ if (!aIsRtcp && !aTransport.mComponents) {
+ RemoveTransportFlow(aLevel, aIsRtcp);
+ return NS_OK;
+ }
+
nsresult rv;
- for (auto pair : trackPairs) {
- if (pair.mReceiving) {
+ RefPtr<TransportFlow> flow = GetTransportFlow(aLevel, aIsRtcp);
+ if (flow) {
+ if (IsIceRestarting()) {
+ CSFLogInfo(logTag, "Flow[%s]: detected ICE restart - level: %u rtcp: %d",
+ flow->id().c_str(), (unsigned)aLevel, aIsRtcp);
- rv = factory.CreateOrUpdateMediaPipeline(pair, *pair.mReceiving);
+ RefPtr<PeerConnectionMedia> pcMedia(this);
+ rv = GetSTSThread()->Dispatch(
+ WrapRunnableNM(AddNewIceStreamForRestart_s,
+ pcMedia, flow, aLevel, aIsRtcp),
+ NS_DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "Failed to dispatch AddNewIceStreamForRestart_s");
return rv;
}
}
- if (pair.mSending) {
- rv = factory.CreateOrUpdateMediaPipeline(pair, *pair.mSending);
- if (NS_FAILED(rv)) {
- return rv;
- }
+ return NS_OK;
+ }
+
+ std::ostringstream osId;
+ osId << mParentHandle << ":" << 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>();
+ auto dtls = MakeUnique<TransportLayerDtls>();
+ dtls->SetRole(aTransport.mDtls->GetRole() ==
+ JsepDtlsTransport::kJsepDtlsClient
+ ? TransportLayerDtls::CLIENT
+ : TransportLayerDtls::SERVER);
+
+ RefPtr<DtlsIdentity> pcid = mParent->Identity();
+ if (!pcid) {
+ CSFLogError(logTag, "Failed to get DTLS identity.");
+ return NS_ERROR_FAILURE;
+ }
+ dtls->SetIdentity(pcid);
+
+ const SdpFingerprintAttributeList& fingerprints =
+ aTransport.mDtls->GetFingerprints();
+ for (const auto& fingerprint : fingerprints.mFingerprints) {
+ std::ostringstream ss;
+ ss << fingerprint.hashFunc;
+ rv = dtls->SetVerificationDigest(ss.str(), &fingerprint.fingerprint[0],
+ fingerprint.fingerprint.size());
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "Could not set fingerprint");
+ return rv;
}
}
- for (auto& stream : mRemoteSourceStreams) {
- stream->StartReceiving();
+ 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)) {
+ CSFLogError(logTag, "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 (!mParent->PrivacyRequested()) {
+ alpnDefault = "webrtc";
+ alpn.insert(alpnDefault);
}
+ rv = dtls->SetAlpn(alpn, alpnDefault);
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "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());
+
+ RefPtr<PeerConnectionMedia> pcMedia(this);
+ rv = GetSTSThread()->Dispatch(
+ WrapRunnableNM(FinalizeTransportFlow_s, pcMedia, flow, aLevel, aIsRtcp,
+ layers),
+ NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ CSFLogError(logTag, "Failed to dispatch FinalizeTransportFlow_s");
+ return rv;
+ }
+
+ AddTransportFlow(aLevel, aIsRtcp, flow);
return NS_OK;
}
void
PeerConnectionMedia::StartIceChecks(const JsepSession& aSession)
{
nsCOMPtr<nsIRunnable> runnable(
@@ -966,152 +1026,67 @@ PeerConnectionMedia::EnsureIceGathering_
// 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 (mStunAddrsRequest) {
mStunAddrsRequest->Cancel();
mStunAddrsRequest = nullptr;
}
if (mProxyRequest) {
mProxyRequest->Cancel(NS_ERROR_ABORT);
mProxyRequest = nullptr;
}
+ for (auto transceiver : mTransceivers) {
+ // transceivers are garbage-collected, so we need to poke them to perform
+ // cleanup right now so the appropriate events fire.
+ transceiver->Shutdown_m();
+ }
+
// 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();
#if !defined(MOZILLA_EXTERNAL_LINKAGE)
NrIceStats stats = mIceCtxHdlr->Destroy();
CSFLogDebug(logTag, "Ice Telemetry: stun (retransmits: %d)"
" turn (401s: %d 403s: %d 438s: %d)",
@@ -1130,96 +1105,104 @@ PeerConnectionMedia::ShutdownMediaTransp
mIceCtxHdlr = nullptr;
// we're holding a ref to 'this' that's released by SelfDestruct_m
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)
+nsresult
+PeerConnectionMedia::AddTransceiver(
+ RefPtr<JsepTransceiver> aJsepTransceiver,
+ OwningNonNull<DOMMediaStream>& aReceiveStream,
+ RefPtr<dom::MediaStreamTrack>& aSendTrack,
+ RefPtr<TransceiverImpl>* aTransceiverImpl)
{
- ASSERT_ON_THREAD(mMainThread);
- for (size_t i = 0; i < mLocalSourceStreams.Length(); ++i) {
- if (id == mLocalSourceStreams[i]->GetId()) {
- return mLocalSourceStreams[i];
- }
- }
+ RefPtr<TransceiverImpl> transceiver = new TransceiverImpl(
+ mParent->GetHandle(),
+ aJsepTransceiver,
+ mMainThread.get(),
+ mSTSThread.get(),
+ aReceiveStream,
+ aSendTrack,
+ mCall);
- return nullptr;
-}
-
-LocalSourceStreamInfo*
-PeerConnectionMedia::GetLocalStreamByTrackId(const std::string& id)
-{
- ASSERT_ON_THREAD(mMainThread);
- for (RefPtr<LocalSourceStreamInfo>& info : mLocalSourceStreams) {
- if (info->HasTrack(id)) {
- return info;
+ if (aSendTrack) {
+ // implement checking for peerIdentity (where failure == black/silence)
+ nsIDocument* doc = mParent->GetWindow()->GetExtantDoc();
+ if (doc) {
+ transceiver->UpdateSinkIdentity(nullptr,
+ doc->NodePrincipal(),
+ mParent->GetPeerIdentity());
+ } else {
+ MOZ_CRASH();
+ return NS_ERROR_FAILURE; // Don't remove this till we know it's safe.
}
}
- return nullptr;
+ mTransceivers.push_back(transceiver);
+ *aTransceiverImpl = transceiver;
+
+ return NS_OK;
}
-RemoteSourceStreamInfo*
-PeerConnectionMedia::GetRemoteStreamByIndex(size_t aIndex)
+void
+PeerConnectionMedia::GetTransmitPipelinesMatching(
+ MediaStreamTrack* aTrack,
+ nsTArray<RefPtr<MediaPipeline>>* aPipelines)
{
- 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];
+ for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ if (transceiver->HasSendTrack(aTrack)) {
+ aPipelines->AppendElement(transceiver->GetSendPipeline());
}
}
- return nullptr;
+ if (!aPipelines->Length()) {
+ CSFLogWarn(logTag, "%s: none found for %p", __FUNCTION__, aTrack);
+ }
}
-RemoteSourceStreamInfo*
-PeerConnectionMedia::GetRemoteStreamByTrackId(const std::string& id)
+void
+PeerConnectionMedia::GetReceivePipelinesMatching(
+ MediaStreamTrack* aTrack,
+ nsTArray<RefPtr<MediaPipeline>>* aPipelines)
{
- ASSERT_ON_THREAD(mMainThread);
- for (RefPtr<RemoteSourceStreamInfo>& info : mRemoteSourceStreams) {
- if (info->HasTrack(id)) {
- return info;
+ for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ if (transceiver->HasReceiveTrack(aTrack)) {
+ aPipelines->AppendElement(transceiver->GetReceivePipeline());
}
}
- return nullptr;
+ if (!aPipelines->Length()) {
+ CSFLogWarn(logTag, "%s: none found for %p", __FUNCTION__, aTrack);
+ }
}
-
nsresult
-PeerConnectionMedia::AddRemoteStream(RefPtr<RemoteSourceStreamInfo> aInfo)
+PeerConnectionMedia::AddRIDExtension(MediaStreamTrack& aRecvTrack,
+ unsigned short aExtensionId)
{
- ASSERT_ON_THREAD(mMainThread);
+ for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ if (transceiver->HasReceiveTrack(&aRecvTrack)) {
+ transceiver->AddRIDExtension(aExtensionId);
+ }
+ }
+ return NS_OK;
+}
- mRemoteSourceStreams.AppendElement(aInfo);
-
+nsresult
+PeerConnectionMedia::AddRIDFilter(MediaStreamTrack& aRecvTrack,
+ const nsAString& aRid)
+{
+ for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ if (transceiver->HasReceiveTrack(&aRecvTrack)) {
+ transceiver->AddRIDFilter(aRid);
+ }
+ }
return NS_OK;
}
void
PeerConnectionMedia::IceGatheringStateChange_s(NrIceCtx* ctx,
NrIceCtx::GatheringState state)
{
ASSERT_ON_THREAD(mSTSThread);
@@ -1455,282 +1438,65 @@ 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;
-}
-
/**
* 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;
- }
+ for (const RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ if (transceiver->GetSendTrack() &&
+ transceiver->GetSendTrack()->GetPeerIdentity()) {
+ 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);
+ for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ transceiver->UpdatePrincipal(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& pipeline_ : mPipelines) {
- MediaPipelineTransmit* pipeline =
- static_cast<MediaPipelineTransmit*>(pipeline_.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));
- }
+ for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ transceiver->UpdateSinkIdentity(aTrack, aPrincipal, aSinkIdentity);
}
}
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& pipeline : mPipelines) {
- if (pipeline.second->Conduit()->CodecPluginID() == aPluginID) {
+ for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ if (transceiver->ConduitHasPluginID(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;
-}
-
-auto
-RemoteTrackSource::ApplyConstraints(
- nsPIDOMWindowInner* aWindow,
- const dom::MediaTrackConstraints& aConstraints,
- dom::CallerType aCallerType) -> already_AddRefed<PledgeVoid>
-{
- RefPtr<PledgeVoid> p = new PledgeVoid();
- p->Reject(new dom::MediaStreamError(aWindow,
- NS_LITERAL_STRING("OverconstrainedError"),
- NS_LITERAL_STRING("")));
- return p.forget();
-}
-
} // namespace mozilla
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -4,239 +4,53 @@
#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 "mozilla/net/StunAddrsRequestChild.h"
-#include "nsComponentManagerUtils.h"
#include "nsIProtocolProxyCallback.h"
-#include "signaling/src/jsep/JsepSession.h"
-#include "AudioSegment.h"
-
-#include "Layers.h"
-#include "VideoUtils.h"
-#include "ImageLayers.h"
-#include "VideoSegment.h"
-#include "MediaStreamTrack.h"
+#include "TransceiverImpl.h"
class nsIPrincipal;
namespace mozilla {
class DataChannel;
class PeerIdentity;
-class MediaPipelineFactory;
namespace dom {
struct RTCInboundRTPStreamStats;
struct RTCOutboundRTPStreamStats;
+class MediaStreamTrack;
}
}
#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) {}
+class MediaPipeline;
+class MediaPipelineFilter;
+class JsepSession;
- nsresult TakePipelineFrom(RefPtr<LocalSourceStreamInfo>& info,
- const std::string& oldTrackId,
- dom::MediaStreamTrack& aNewTrack,
- const std::string& newTrackId);
-
- void UpdateSinkIdentity_m(dom::MediaStreamTrack* aTrack,
- nsIPrincipal* aPrincipal,
- const PeerIdentity* aSinkIdentity);
-
- NS_INLINE_DECL_THREADSAFE_REFCOUNTING(LocalSourceStreamInfo)
-
-private:
- already_AddRefed<MediaPipeline> ForgetPipelineByTrackId_m(
- const std::string& trackId);
-};
-
-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,
- dom::CallerType aCallerType) 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() {}
-};
-
-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);
-
- void UpdatePrincipal_m(nsIPrincipal* aPrincipal);
-
- 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;
-};
-
+// TODO(bug XXXXX): If we move the TransceiverImpl stuff out of here, this will
+// be a class that handles just the transport stuff, and we can rename it to
+// something more explanatory (say, PeerConnectionTransportManager).
class PeerConnectionMedia : public sigslot::has_slots<> {
- ~PeerConnectionMedia()
- {
- MOZ_RELEASE_ASSERT(!mMainThread);
- }
+ ~PeerConnectionMedia();
public:
explicit PeerConnectionMedia(PeerConnectionImpl *parent);
enum IceRestartState { ICE_RESTART_NONE,
ICE_RESTART_PROVISIONAL,
ICE_RESTART_COMMITTED
};
@@ -259,18 +73,21 @@ class PeerConnectionMedia : public sigsl
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,
- const bool forceIceTcp);
+ nsresult ActivateOrRemoveTransports(const JsepSession& aSession,
+ const bool forceIceTcp);
+
+ // Update the transports on the TransceiverImpls
+ nsresult UpdateTransceiverTransports(const JsepSession& aSession);
// Start ICE checks.
void StartIceChecks(const JsepSession& session);
bool IsIceRestarting() const;
IceRestartState GetIceRestartState() const;
// Begin ICE restart
@@ -286,61 +103,48 @@ class PeerConnectionMedia : public sigsl
// Process a trickle ICE candidate.
void AddIceCandidate(const std::string& candidate, const std::string& mid,
uint32_t aMLine);
// Handle notifications of network online/offline events.
void UpdateNetworkState(bool online);
// Handle complete media pipelines.
- nsresult UpdateMediaPipelines(const JsepSession& session);
+ // This updates codec parameters, starts/stops send/receive, and other
+ // stuff that doesn't necessarily require negotiation. This can be called at
+ // any time, not just when an offer/answer exchange completes.
+ // TODO: Let's move this to PeerConnectionImpl
+ nsresult UpdateMediaPipelines();
- // 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);
+ // TODO: Let's move the TransceiverImpl stuff to PeerConnectionImpl.
+ nsresult AddTransceiver(
+ RefPtr<JsepTransceiver> aJsepTransceiver,
+ OwningNonNull<DOMMediaStream>& aReceiveStream,
+ RefPtr<dom::MediaStreamTrack>& aSendTrack,
+ RefPtr<TransceiverImpl>* aTransceiverImpl);
- // 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);
+ void GetTransmitPipelinesMatching(
+ dom::MediaStreamTrack* aTrack,
+ nsTArray<RefPtr<MediaPipeline>>* aPipelines);
- // Get a specific remote stream
- uint32_t RemoteStreamsLength()
- {
- return mRemoteSourceStreams.Length();
- }
+ void GetReceivePipelinesMatching(
+ dom::MediaStreamTrack* aTrack,
+ nsTArray<RefPtr<MediaPipeline>>* aPipelines);
- RemoteSourceStreamInfo* GetRemoteStreamByIndex(size_t index);
- RemoteSourceStreamInfo* GetRemoteStreamById(const std::string& id);
- RemoteSourceStreamInfo* GetRemoteStreamByTrackId(const std::string& id);
+ nsresult AddRIDExtension(dom::MediaStreamTrack& aRecvTrack,
+ unsigned short aExtensionId);
- // 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);
+ nsresult AddRIDFilter(dom::MediaStreamTrack& aRecvTrack,
+ const nsAString& aRid);
// 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.
+ // TODO: Move to PeerConnectionImpl
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);
@@ -361,79 +165,48 @@ class PeerConnectionMedia : public sigsl
int index_inner = GetTransportFlowIndex(aStreamIndex, aIsRtcp);
if (mTransportFlows.find(index_inner) == mTransportFlows.end())
return nullptr;
return mTransportFlows[index_inner];
}
+ // Used by PCImpl in a couple of places. Might be good to move that code in
+ // here.
+ std::vector<RefPtr<TransceiverImpl>>& GetTransceivers()
+ {
+ return mTransceivers;
+ }
+
// 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()));
- }
-
- void AddVideoConduit(size_t level, const RefPtr<VideoSessionConduit> &aConduit) {
- mConduits[level] = std::make_pair(true, aConduit);
- }
-
- // Add a conduit
- void AddAudioConduit(size_t level, const RefPtr<AudioSessionConduit> &aConduit) {
- mConduits[level] = std::make_pair(false, 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;
+ // TODO: Move to PeerConnectionImpl
RefPtr<WebRtcCallWrapper> mCall;
private:
void InitLocalAddrs(); // for stun local address IPC request
nsresult InitProxy();
class ProtocolProxyQueryHandler : public nsIProtocolProxyCallback {
public:
explicit ProtocolProxyQueryHandler(PeerConnectionMedia *pcm) :
@@ -473,16 +246,20 @@ class PeerConnectionMedia : public sigsl
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);
+ nsresult UpdateTransportFlows(const JsepTransceiver& transceiver);
+ nsresult UpdateTransportFlow(size_t aLevel,
+ bool aIsRtcp,
+ const JsepTransport& aTransport);
void GatherIfReady();
void FlushIceCtxOperationQueueIfReady();
void PerformOrEnqueueIceCtxOperation(nsIRunnable* runnable);
void EnsureIceGathering_s(bool aDefaultRouteOnly, bool aProxyOnly);
void StartIceChecks_s(bool aIsControlling,
bool aIsOfferer,
bool aIsIceLite,
@@ -539,25 +316,17 @@ class PeerConnectionMedia : public sigsl
}
// 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;
+ std::vector<RefPtr<TransceiverImpl>> mTransceivers;
// ICE objects
RefPtr<NrIceCtxHandler> mIceCtxHdlr;
// DNS
RefPtr<NrIceResolver> mDNSResolver;
// Transport flows: even is RTP, odd is RTCP
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/RemoteTrackSource.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 _REMOTE_TRACK_SOURCE_H_
+#define _REMOTE_TRACK_SOURCE_H_
+
+#include "MediaStreamTrack.h"
+#include "MediaStreamError.h"
+
+namespace mozilla {
+
+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,
+ dom::CallerType aCallerType) override
+ {
+ RefPtr<PledgeVoid> p = new PledgeVoid();
+ p->Reject(
+ new dom::MediaStreamError(aWindow,
+ NS_LITERAL_STRING("OverconstrainedError"),
+ NS_LITERAL_STRING("")));
+ return p.forget();
+ }
+
+ 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() {}
+};
+
+} // namespace mozilla
+
+#endif // _REMOTE_TRACK_SOURCE_H_
+
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
@@ -0,0 +1,1030 @@
+/* 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 "TransceiverImpl.h"
+#include "mtransport/runnable_utils.h"
+#include "mozilla/UniquePtr.h"
+#include <sstream>
+#include <string>
+#include <vector>
+#include <queue>
+#include "AudioConduit.h"
+#include "VideoConduit.h"
+#include "MediaStreamGraph.h"
+#include "MediaPipeline.h"
+#include "MediaPipelineFilter.h"
+#include "jsep/JsepTrack.h"
+#include "MediaStreamGraphImpl.h"
+#include "logging.h"
+#include "MediaEngine.h"
+#include "nsIPrincipal.h"
+#include "MediaSegment.h"
+#include "RemoteTrackSource.h"
+#include "MediaConduitInterface.h"
+#include "PeerConnectionMedia.h"
+#include "mozilla/dom/RTCRtpReceiverBinding.h"
+#include "mozilla/dom/RTCRtpSenderBinding.h"
+#include "mozilla/dom/RTCRtpTransceiverBinding.h"
+#include "mozilla/dom/TransceiverImplBinding.h"
+
+namespace mozilla {
+
+MOZ_MTLOG_MODULE("mediapipeline")
+
+TransceiverImpl::TransceiverImpl(
+ const std::string& aPCHandle,
+ RefPtr<JsepTransceiver> aJsepTransceiver,
+ nsIEventTarget* aMainThread,
+ nsIEventTarget* aStsThread,
+ OwningNonNull<DOMMediaStream>& aReceiveStream,
+ RefPtr<dom::MediaStreamTrack>& aSendTrack,
+ RefPtr<WebRtcCallWrapper>& aCallWrapper) :
+ mPCHandle(aPCHandle),
+ mJsepTransceiver(aJsepTransceiver),
+ mHaveStartedReceiving(false),
+ mMainThread(aMainThread),
+ mStsThread(aStsThread),
+ mReceiveStream(aReceiveStream),
+ mSendTrack(aSendTrack),
+ mCallWrapper(aCallWrapper)
+{
+ if (IsVideo()) {
+ InitVideo();
+ } else {
+ InitAudio();
+ }
+
+ mConduit->SetPCHandle(mPCHandle);
+
+ SourceMediaStream* source(mReceiveStream->GetInputStream()->AsSourceStream());
+ mReceivePipeline->AttachMedia(source);
+ StartReceiveStream();
+
+ mTransmitPipeline = new MediaPipelineTransmit(
+ mPCHandle,
+ mMainThread.get(),
+ mStsThread.get(),
+ IsVideo(),
+ mSendTrack,
+ mConduit);
+}
+
+NS_IMPL_ISUPPORTS0(TransceiverImpl)
+
+void
+TransceiverImpl::InitAudio()
+{
+ mConduit = AudioSessionConduit::Create();
+
+ mReceivePipeline = new MediaPipelineReceiveAudio(
+ mPCHandle,
+ mMainThread.get(),
+ mStsThread.get(),
+ static_cast<AudioSessionConduit*>(mConduit.get()));
+}
+
+void
+TransceiverImpl::InitVideo()
+{
+ mConduit = VideoSessionConduit::Create(mCallWrapper);
+
+ mReceivePipeline = new MediaPipelineReceiveVideo(
+ mPCHandle,
+ mMainThread.get(),
+ mStsThread.get(),
+ static_cast<VideoSessionConduit*>(mConduit.get()));
+}
+
+nsresult
+TransceiverImpl::UpdateSinkIdentity(dom::MediaStreamTrack* aTrack,
+ nsIPrincipal* aPrincipal,
+ const PeerIdentity* aSinkIdentity)
+{
+ mTransmitPipeline->UpdateSinkIdentity_m(aTrack, aPrincipal, aSinkIdentity);
+ return NS_OK;
+}
+
+void
+TransceiverImpl::Shutdown_m()
+{
+ mReceivePipeline->Shutdown_m();
+ mTransmitPipeline->Shutdown_m();
+ mReceivePipeline = nullptr;
+ mTransmitPipeline = nullptr;
+ mSendTrack = nullptr;
+ mConduit = nullptr;
+ RUN_ON_THREAD(mStsThread, WrapRelease(mRtpFlow.forget()), NS_DISPATCH_NORMAL);
+ RUN_ON_THREAD(mStsThread, WrapRelease(mRtcpFlow.forget()), NS_DISPATCH_NORMAL);
+}
+
+TransceiverImpl::~TransceiverImpl()
+{}
+
+nsresult
+TransceiverImpl::UpdateSendTrack(dom::MediaStreamTrack* aSendTrack)
+{
+ MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: "
+ "In " << __FUNCTION__ << "(" << aSendTrack << ")");
+ mSendTrack = aSendTrack;
+ return mTransmitPipeline->ReplaceTrack(mSendTrack);
+}
+
+nsresult
+TransceiverImpl::UpdateTransport(PeerConnectionMedia& aTransportManager)
+{
+ if (!mJsepTransceiver->HasLevel()) {
+ return NS_OK;
+ }
+
+ ASSERT_ON_THREAD(mMainThread);
+ nsAutoPtr<MediaPipelineFilter> filter;
+
+ mRtpFlow = aTransportManager.GetTransportFlow(
+ mJsepTransceiver->GetTransportLevel(), false);
+ mRtcpFlow = aTransportManager.GetTransportFlow(
+ mJsepTransceiver->GetTransportLevel(), true);
+
+ if (mJsepTransceiver->HasBundleLevel() &&
+ mJsepTransceiver->mReceiving.GetNegotiatedDetails()) {
+ filter = new MediaPipelineFilter;
+
+ // Add remote SSRCs so we can distinguish which RTP packets actually
+ // belong to this pipeline (also RTCP sender reports).
+ for (unsigned int ssrc : mJsepTransceiver->mReceiving.GetSsrcs()) {
+ filter->AddRemoteSSRC(ssrc);
+ }
+
+ // TODO(bug 1105005): Tell the filter about the mid for this track
+
+ // Add unique payload types as a last-ditch fallback
+ auto uniquePts =
+ mJsepTransceiver->mReceiving.GetNegotiatedDetails()->GetUniquePayloadTypes();
+ for (unsigned char& uniquePt : uniquePts) {
+ filter->AddUniquePT(uniquePt);
+ }
+ }
+
+ mReceivePipeline->UpdateTransport_m(mRtpFlow, mRtcpFlow, filter);
+ mTransmitPipeline->UpdateTransport_m(mRtpFlow, mRtcpFlow, nsAutoPtr<MediaPipelineFilter>());
+ return NS_OK;
+}
+
+nsresult
+TransceiverImpl::UpdateConduit()
+{
+ MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: "
+ "In UpdateConduit");
+
+ if (mJsepTransceiver->IsAssociated()) {
+ mMid = mJsepTransceiver->GetMid();
+ } else {
+ mMid.clear();
+ }
+
+ MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: "
+ "Stopping transmit/receive conduits");
+ mConduit->StopReceiving();
+ mConduit->StopTransmitting();
+
+ if (mJsepTransceiver->IsStopped()) {
+ MOZ_MTLOG(ML_DEBUG, this << " " << mPCHandle << " [" << mMid << "]: "
+ "Transceiver is stopped. (receive pipeline = " << mReceivePipeline << ")");
+ // Make sure that stats queries stop working on this transceiver.
+ UpdateSendTrack(nullptr);
+ mHaveStartedReceiving = false;
+ return NS_OK;
+ }
+
+ // NOTE(pkerr) - the Call API requires the both local_ssrc and remote_ssrc be
+ // set to a non-zero value or the CreateVideo...Stream call will fail.
+ if (mJsepTransceiver->mSending.GetSsrcs().empty()) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: "
+ "No local SSRC set! (Should be set regardless of "
+ "whether we're sending RTP; we need a local SSRC in "
+ "all cases)");
+ return NS_ERROR_FAILURE;
+ }
+
+ if(!mConduit->SetLocalSSRCs(mJsepTransceiver->mSending.GetSsrcs())) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: "
+ "SetLocalSSRCs failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ mConduit->SetLocalCNAME(mJsepTransceiver->mSending.GetCNAME().c_str());
+
+ nsresult rv;
+
+ if (IsVideo()) {
+ rv = UpdateVideoConduit();
+ } else {
+ rv = UpdateAudioConduit();
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (mJsepTransceiver->mReceiving.GetActive()) {
+ MOZ_ASSERT(mReceiveStream);
+ mConduit->StartReceiving();
+ mHaveStartedReceiving = true;
+ } else {
+ mConduit->StopReceiving(); // Just in case...
+ }
+
+ if (mJsepTransceiver->mSending.GetActive()) {
+ if (!mSendTrack) {
+ MOZ_MTLOG(ML_WARNING, mPCHandle << "[" << mMid << "]: "
+ "Starting transmit conduit without send track!");
+ }
+ mConduit->StartTransmitting();
+ mTransmitPipeline->AttachToTrack();
+ } else {
+ mTransmitPipeline->DetachFromTrack();
+ mConduit->StopTransmitting(); // Just in case...
+ }
+
+ return NS_OK;
+}
+
+nsresult
+TransceiverImpl::UpdatePrincipal(nsIPrincipal* aPrincipal)
+{
+ nsTArray<RefPtr<dom::MediaStreamTrack>> receiveTracks;
+ mReceiveStream->GetTracks(receiveTracks);
+ if (receiveTracks.Length() != 1) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: "
+ "mReceiveStream doesn't have exactly one track (it has "
+ << receiveTracks.Length() << ")");
+ MOZ_CRASH();
+ return NS_ERROR_FAILURE;
+ }
+
+ // 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.
+ RemoteTrackSource& source =
+ static_cast<RemoteTrackSource&>(receiveTracks[0]->GetSource());
+ source.SetPrincipal(aPrincipal);
+
+ mReceivePipeline->SetPrincipalHandle_m(MakePrincipalHandle(aPrincipal));
+ return NS_OK;
+}
+
+nsresult
+TransceiverImpl::SyncWithMatchingVideoConduits(
+ std::vector<RefPtr<TransceiverImpl>>& transceivers)
+{
+ if (IsVideo()) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: "
+ << __FUNCTION__ << " called when transceiver is not "
+ "video! This should never happen.");
+ MOZ_CRASH();
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ std::set<std::string> myReceiveStreamIds;
+ myReceiveStreamIds.insert(mJsepTransceiver->mReceiving.GetStreamIds().begin(),
+ mJsepTransceiver->mReceiving.GetStreamIds().end());
+
+ for (RefPtr<TransceiverImpl>& transceiver : transceivers) {
+ if (!transceiver->IsVideo()) {
+ // |this| is an audio transceiver, so we skip other audio transceivers
+ continue;
+ }
+
+ // Maybe could make this more efficient by cacheing this set, but probably
+ // not worth it.
+ for (const std::string& streamId :
+ transceiver->mJsepTransceiver->mReceiving.GetStreamIds()) {
+ if (myReceiveStreamIds.count(streamId)) {
+ // Ok, we have one video, one non-video - cross the streams!
+ WebrtcAudioConduit *audio_conduit =
+ static_cast<WebrtcAudioConduit*>(mConduit.get());
+ WebrtcVideoConduit *video_conduit =
+ static_cast<WebrtcVideoConduit*>(transceiver->mConduit.get());
+
+ video_conduit->SyncTo(audio_conduit);
+ MOZ_MTLOG(ML_DEBUG, "Syncing " << video_conduit << " to "
+ << audio_conduit);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+TransceiverImpl::ConduitHasPluginID(uint64_t aPluginID)
+{
+ return mConduit->CodecPluginID() == aPluginID;
+}
+
+bool
+TransceiverImpl::HasSendTrack(const dom::MediaStreamTrack* aSendTrack) const
+{
+ if (!mSendTrack) {
+ return false;
+ }
+
+ if (!aSendTrack) {
+ return true;
+ }
+
+ return mSendTrack.get() == aSendTrack;
+}
+
+void
+TransceiverImpl::SyncWithJS(dom::RTCRtpTransceiver& aJsTransceiver,
+ ErrorResult& aRv)
+{
+ MOZ_MTLOG(ML_DEBUG, "Syncing with JS transceiver");
+
+ // Update stopped, both ways, since either JSEP or JS can stop these
+ if (mJsepTransceiver->IsStopped()) {
+ // We don't call Stop(), because that causes another sync
+ aJsTransceiver.SetStopped(aRv);
+ } else if (aJsTransceiver.GetStopped(aRv)) {
+ mJsepTransceiver->Stop();
+ }
+
+ // Lots of this in here for simple getters that should never fail. Lame.
+ // Just propagate the exception and let JS log it.
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Update direction from JS only
+ dom::RTCRtpTransceiverDirection direction = aJsTransceiver.GetDirection(aRv);
+
+ if (aRv.Failed()) {
+ return;
+ }
+
+ switch (direction) {
+ case dom::RTCRtpTransceiverDirection::Sendrecv:
+ mJsepTransceiver->mJsDirection =
+ SdpDirectionAttribute::Direction::kSendrecv;
+ break;
+ case dom::RTCRtpTransceiverDirection::Sendonly:
+ mJsepTransceiver->mJsDirection =
+ SdpDirectionAttribute::Direction::kSendonly;
+ break;
+ case dom::RTCRtpTransceiverDirection::Recvonly:
+ mJsepTransceiver->mJsDirection =
+ SdpDirectionAttribute::Direction::kRecvonly;
+ break;
+ case dom::RTCRtpTransceiverDirection::Inactive:
+ mJsepTransceiver->mJsDirection =
+ SdpDirectionAttribute::Direction::kInactive;
+ break;
+ default:
+ MOZ_ASSERT(false);
+ aRv = NS_ERROR_INVALID_ARG;
+ return;
+ }
+
+ // Update send track ids in JSEP
+ RefPtr<dom::RTCRtpSender> sender = aJsTransceiver.GetSender(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ RefPtr<dom::MediaStreamTrack> sendTrack = sender->GetTrack(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (sendTrack) {
+ nsString wideTrackId;
+ sendTrack->GetId(wideTrackId);
+ std::string trackId = NS_ConvertUTF16toUTF8(wideTrackId).get();
+ MOZ_ASSERT(!trackId.empty());
+
+ nsTArray<RefPtr<DOMMediaStream>> streams;
+ sender->MozGetStreams(streams, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ std::vector<std::string> streamIds;
+ for (const auto& stream : streams) {
+ nsString wideStreamId;
+ stream->GetId(wideStreamId);
+ std::string streamId = NS_ConvertUTF16toUTF8(wideStreamId).get();
+ MOZ_ASSERT(!streamId.empty());
+ streamIds.push_back(streamId);
+ }
+
+ mJsepTransceiver->mSending.UpdateTrack(streamIds, trackId);
+ }
+
+ // Update RTCRtpParameters
+ // TODO: Both ways for things like ssrc, codecs, header extensions, etc
+
+ dom::RTCRtpParameters parameters;
+ sender->GetParameters(parameters, aRv);
+
+ if (aRv.Failed()) {
+ return;
+ }
+
+ std::vector<JsepTrack::JsConstraints> constraints;
+
+ if (parameters.mEncodings.WasPassed()) {
+ for (auto& encoding : parameters.mEncodings.Value()) {
+ JsepTrack::JsConstraints constraint;
+ if (encoding.mRid.WasPassed()) {
+ // TODO: Either turn on the RID RTP header extension in JsepSession, or
+ // just leave that extension on all the time?
+ 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);
+ }
+ }
+
+ // TODO: Update conduits?
+
+ mJsepTransceiver->mSending.SetJsConstraints(constraints);
+
+ // Update webrtc track id in JS; the ids in SDP are not surfaced to content,
+ // because they don't follow the rules that track/stream ids must. Our JS
+ // code must be able to map the SDP ids to the actual tracks/streams, and
+ // this is how the mapping for track ids is updated.
+ RefPtr<dom::RTCRtpReceiver> receiver = aJsTransceiver.GetReceiver(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ nsString webrtcTrackId =
+ NS_ConvertUTF8toUTF16(mJsepTransceiver->mReceiving.GetTrackId().c_str());
+ MOZ_MTLOG(ML_DEBUG, "Setting webrtc track id: "
+ << mJsepTransceiver->mReceiving.GetTrackId().c_str());
+ receiver->SetWebrtcTrackId(webrtcTrackId, aRv);
+
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // mid from JSEP
+ if (mJsepTransceiver->IsAssociated()) {
+ aJsTransceiver.SetMid(
+ NS_ConvertUTF8toUTF16(mJsepTransceiver->GetMid().c_str()),
+ aRv);
+ } else {
+ aJsTransceiver.UnsetMid(aRv);
+ }
+
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // currentDirection from JSEP, but not if "this transceiver has never been
+ // represented in an offer/answer exchange"
+ if (mJsepTransceiver->HasLevel()) {
+ dom::RTCRtpTransceiverDirection currentDirection;
+ if (mJsepTransceiver->mSending.GetActive()) {
+ if (mJsepTransceiver->mReceiving.GetActive()) {
+ currentDirection = dom::RTCRtpTransceiverDirection::Sendrecv;
+ } else {
+ currentDirection = dom::RTCRtpTransceiverDirection::Sendonly;
+ }
+ } else {
+ if (mJsepTransceiver->mReceiving.GetActive()) {
+ currentDirection = dom::RTCRtpTransceiverDirection::Recvonly;
+ } else {
+ currentDirection = dom::RTCRtpTransceiverDirection::Inactive;
+ }
+ }
+
+ aJsTransceiver.SetCurrentDirection(currentDirection, aRv);
+
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // AddTrack magic from JS
+ if (aJsTransceiver.GetAddTrackMagic(aRv)) {
+ mJsepTransceiver->SetAddTrackMagic();
+ }
+
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (mJsepTransceiver->IsRemoved()) {
+ aJsTransceiver.Remove(aRv);
+ }
+}
+
+void
+TransceiverImpl::InsertDTMFTone(int tone, uint32_t duration)
+{
+ RefPtr<AudioSessionConduit> conduit(static_cast<AudioSessionConduit*>(
+ mConduit.get()));
+ 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);
+}
+
+bool
+TransceiverImpl::HasReceiveTrack(const dom::MediaStreamTrack* aRecvTrack) const
+{
+ if (!mHaveStartedReceiving) {
+ return false;
+ }
+
+ if (!aRecvTrack) {
+ return true;
+ }
+
+ return mReceiveStream->HasTrack(*aRecvTrack);
+}
+
+bool
+TransceiverImpl::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector)
+{
+ return dom::TransceiverImplBinding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+already_AddRefed<dom::MediaStreamTrack>
+TransceiverImpl::GetReceiveTrack()
+{
+ nsTArray<RefPtr<dom::MediaStreamTrack>> receiveTracks;
+ mReceiveStream->GetTracks(receiveTracks);
+ if (receiveTracks.Length() != 1) {
+ return nullptr;
+ }
+
+ return receiveTracks[0].forget();
+}
+
+RefPtr<MediaPipeline>
+TransceiverImpl::GetSendPipeline()
+{
+ return mTransmitPipeline;
+}
+
+RefPtr<MediaPipeline>
+TransceiverImpl::GetReceivePipeline()
+{
+ MOZ_MTLOG(ML_WARNING, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ <<
+ " returning " << mReceivePipeline.get());
+ return mReceivePipeline;
+}
+
+void
+TransceiverImpl::AddRIDExtension(unsigned short aExtensionId)
+{
+ mReceivePipeline->AddRIDExtension_m(aExtensionId);
+}
+
+void
+TransceiverImpl::AddRIDFilter(const nsAString& aRid)
+{
+ mReceivePipeline->AddRIDFilter_m(NS_ConvertUTF16toUTF8(aRid).get());
+}
+
+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
+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 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);
+ }
+
+ if (aConfigs->values.empty()) {
+ MOZ_MTLOG(ML_ERROR, "Can't set up a conduit with 0 codecs");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+TransceiverImpl::UpdateAudioConduit()
+{
+ RefPtr<AudioSessionConduit> conduit = static_cast<AudioSessionConduit*>(
+ mConduit.get());
+
+ if (mJsepTransceiver->mReceiving.GetNegotiatedDetails() &&
+ mJsepTransceiver->mReceiving.GetActive()) {
+ const auto& details(*mJsepTransceiver->mReceiving.GetNegotiatedDetails());
+ PtrVector<AudioCodecConfig> configs;
+ nsresult rv = NegotiatedDetailsToAudioCodecConfigs(details, &configs);
+
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: "
+ "Failed to convert JsepCodecDescriptions to "
+ "AudioCodecConfigs (recv).");
+ return rv;
+ }
+
+ auto error = conduit->ConfigureRecvMediaCodecs(configs.values);
+
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: "
+ "ConfigureRecvMediaCodecs failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (mJsepTransceiver->mSending.GetNegotiatedDetails() &&
+ mJsepTransceiver->mSending.GetActive()) {
+ const auto& details(*mJsepTransceiver->mSending.GetNegotiatedDetails());
+ PtrVector<AudioCodecConfig> configs;
+ nsresult rv = NegotiatedDetailsToAudioCodecConfigs(details, &configs);
+
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: "
+ "Failed to convert JsepCodecDescriptions to "
+ "AudioCodecConfigs (send).");
+ return rv;
+ }
+
+ 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,
+ configs.values.back()->mFreq);
+ }
+
+ auto error = conduit->ConfigureSendMediaCodec(configs.values[0]);
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: "
+ "ConfigureSendMediaCodec failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+
+ const SdpExtmapAttributeList::Extmap* audioLevelExt =
+ details.GetExt("urn:ietf:params:rtp-hdrext:ssrc-audio-level");
+
+ if (audioLevelExt) {
+ error = conduit->EnableAudioLevelExtension(true, audioLevelExt->entry);
+
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: "
+ "EnableAudioLevelExtension failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ 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;
+ if (desc.mFECEnabled) {
+ configRaw->mREDPayloadType = desc.mREDPayloadType;
+ configRaw->mULPFECPayloadType = desc.mULPFECPayloadType;
+ }
+
+ *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;
+ }
+
+ config->mTias = aDetails.GetTias();
+
+ 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;
+}
+
+nsresult
+TransceiverImpl::UpdateVideoConduit()
+{
+ RefPtr<VideoSessionConduit> conduit = static_cast<VideoSessionConduit*>(
+ mConduit.get());
+
+ // NOTE(pkerr) - this is new behavior. Needed because the CreateVideoReceiveStream
+ // method of the Call API will assert (in debug) and fail if a value is not provided
+ // for the remote_ssrc that will be used by the far-end sender.
+ if (!mJsepTransceiver->mReceiving.GetSsrcs().empty()) {
+ MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: "
+ "Setting remote SSRC " <<
+ mJsepTransceiver->mReceiving.GetSsrcs().front());
+ conduit->SetRemoteSSRC(mJsepTransceiver->mReceiving.GetSsrcs().front());
+ }
+
+ if (mJsepTransceiver->mReceiving.GetNegotiatedDetails() &&
+ mJsepTransceiver->mReceiving.GetActive()) {
+ const auto& details(*mJsepTransceiver->mReceiving.GetNegotiatedDetails());
+
+ UpdateVideoExtmap(details, false);
+
+ PtrVector<VideoCodecConfig> configs;
+ nsresult rv = NegotiatedDetailsToVideoCodecConfigs(details, &configs);
+
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: "
+ "Failed to convert JsepCodecDescriptions to "
+ "VideoCodecConfigs (recv).");
+ return rv;
+ }
+
+ auto error = conduit->ConfigureRecvMediaCodecs(configs.values);
+
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: "
+ "ConfigureRecvMediaCodecs failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // It is possible for SDP to signal that there is a send track, but there not
+ // actually be a send track, according to the specification; all that needs to
+ // happen is for the transceiver to be configured to send...
+ if (mJsepTransceiver->mSending.GetNegotiatedDetails() &&
+ mJsepTransceiver->mSending.GetActive() &&
+ mSendTrack) {
+ const auto& details(*mJsepTransceiver->mSending.GetNegotiatedDetails());
+
+ UpdateVideoExtmap(details, true);
+
+ nsresult rv = ConfigureVideoCodecMode(*conduit);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ PtrVector<VideoCodecConfig> configs;
+ rv = NegotiatedDetailsToVideoCodecConfigs(details, &configs);
+
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: "
+ "Failed to convert JsepCodecDescriptions to "
+ "VideoCodecConfigs (send).");
+ return rv;
+ }
+
+ auto error = conduit->ConfigureSendMediaCodec(configs.values[0]);
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: "
+ "ConfigureSendMediaCodec failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+TransceiverImpl::ConfigureVideoCodecMode(VideoSessionConduit& aConduit)
+{
+ RefPtr<mozilla::dom::VideoStreamTrack> videotrack =
+ mSendTrack->AsVideoStreamTrack();
+
+ if (!videotrack) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: "
+ "mSendTrack is not video! This should never happen!");
+ MOZ_CRASH();
+ 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, mPCHandle << "[" << mMid << "]: "
+ "ConfigureCodecMode failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void
+TransceiverImpl::UpdateVideoExtmap(const JsepTrackNegotiatedDetails& aDetails,
+ bool aSending)
+{
+ std::vector<webrtc::RtpExtension> extmaps;
+ // @@NG read extmap from track
+ aDetails.ForEachRTPHeaderExtension(
+ [&extmaps](const SdpExtmapAttributeList::Extmap& extmap)
+ {
+ extmaps.emplace_back(extmap.extensionname,extmap.entry);
+ });
+
+ RefPtr<VideoSessionConduit> conduit = static_cast<VideoSessionConduit*>(
+ mConduit.get());
+
+ if (!extmaps.empty()) {
+ conduit->SetLocalRTPExtensions(aSending, extmaps);
+ }
+}
+
+static void StartTrack(MediaStream* aSource,
+ nsAutoPtr<MediaSegment>&& aSegment)
+{
+ class Message : public ControlMessage {
+ public:
+ Message(MediaStream* aStream, nsAutoPtr<MediaSegment>&& aSegment)
+ : ControlMessage(aStream),
+ segment_(aSegment) {}
+
+ virtual void Run() override {
+ TrackRate track_rate = segment_->GetType() == MediaSegment::AUDIO ?
+ WEBRTC_DEFAULT_SAMPLE_RATE : mStream->GraphRate();
+ StreamTime current_end = mStream->GetTracksEnd();
+ MOZ_MTLOG(ML_DEBUG, "current_end = " << current_end);
+ 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) {
+ MOZ_MTLOG(ML_DEBUG, "added track @ " << 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);
+ MOZ_MTLOG(ML_DEBUG, "segment_->GetDuration() = " << segment_->GetDuration());
+ if (segment_->GetType() == MediaSegment::AUDIO) {
+ MOZ_MTLOG(ML_DEBUG, "Calling AddAudioTrack");
+ mStream->AsSourceStream()->AddAudioTrack(
+ kAudioTrack,
+ WEBRTC_DEFAULT_SAMPLE_RATE,
+ 0,
+ static_cast<AudioSegment*>(segment_.forget()));
+ } else {
+ mStream->AsSourceStream()->AddTrack(kVideoTrack, 0, segment_.forget());
+ }
+
+ mStream->AsSourceStream()->SetPullEnabled(true);
+ mStream->AsSourceStream()->AdvanceKnownTracksTime(STREAM_TIME_MAX);
+ }
+ private:
+ nsAutoPtr<MediaSegment> segment_;
+ };
+
+ aSource->GraphImpl()->AppendMessage(
+ MakeUnique<Message>(aSource, Move(aSegment)));
+ MOZ_MTLOG(ML_INFO, "Dispatched track-add on stream " << aSource);
+}
+
+void
+TransceiverImpl::StartReceiveStream()
+{
+ MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: "
+ "In StartReceiveStream");
+ // TODO: Can this be simplified? There's an awful lot of moving pieces here.
+ SourceMediaStream* source(mReceiveStream->GetInputStream()->AsSourceStream());
+ mReceiveStream->SetLogicalStreamStartTime(
+ mReceiveStream->GetPlaybackStream()->GetCurrentTime());
+
+ nsAutoPtr<MediaSegment> segment;
+ if (IsVideo()) {
+ segment = new VideoSegment;
+ } else {
+ segment = new AudioSegment;
+ }
+
+ StartTrack(source, Move(segment));
+}
+
+bool
+TransceiverImpl::IsVideo() const
+{
+ return mJsepTransceiver->mReceiving.GetMediaType() ==
+ SdpMediaSection::MediaType::kVideo;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/TransceiverImpl.h
@@ -0,0 +1,145 @@
+/* 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 _TRANSCEIVERIMPL_H_
+#define _TRANSCEIVERIMPL_H_
+
+#include <string>
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIEventTarget.h"
+#include "nsTArray.h"
+#include "DOMMediaStream.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/dom/MediaStreamTrack.h"
+#include "ErrorList.h"
+#include "mtransport/transportflow.h"
+#include "signaling/src/jsep/JsepTrack.h"
+
+class nsIPrincipal;
+
+namespace mozilla {
+class PeerIdentity;
+class PeerConnectionMedia;
+class JsepTransceiver;
+class MediaSessionConduit;
+class VideoSessionConduit;
+class MediaPipelineReceive;
+class MediaPipelineTransmit;
+class MediaPipeline;
+class MediaPipelineFilter;
+class WebRtcCallWrapper;
+
+namespace dom {
+class RTCRtpTransceiver;
+}
+
+/**
+ * This is what ties all the various pieces that make up a transceiver
+ * together. This includes:
+ * DOMMediaStream, MediaStreamTrack, SourceMediaStream for rendering and capture
+ * TransportFlow for RTP transmission/reception
+ * Audio/VideoConduit for feeding RTP/RTCP into webrtc.org for decoding, and
+ * feeding audio/video frames into webrtc.org for encoding into RTP/RTCP.
+*/
+class TransceiverImpl : public nsISupports {
+public:
+ /**
+ * |aReceiveStream| is always set; this holds even if the remote end has not
+ * negotiated one for this transceiver. |aSendTrack| might or might not be
+ * set.
+ */
+ TransceiverImpl(const std::string& aPCHandle,
+ RefPtr<JsepTransceiver> aJsepTransceiver,
+ nsIEventTarget* aMainThread,
+ nsIEventTarget* aStsThread,
+ OwningNonNull<DOMMediaStream>& aReceiveStream,
+ RefPtr<dom::MediaStreamTrack>& aSendTrack,
+ RefPtr<WebRtcCallWrapper>& aCallWrapper);
+
+ nsresult UpdateSendTrack(dom::MediaStreamTrack* aSendTrack);
+
+ nsresult UpdateSinkIdentity(dom::MediaStreamTrack* aTrack,
+ nsIPrincipal* aPrincipal,
+ const PeerIdentity* aSinkIdentity);
+
+ nsresult UpdateTransport(PeerConnectionMedia& aTransportManager);
+
+ nsresult UpdateConduit();
+
+ nsresult UpdatePrincipal(nsIPrincipal* aPrincipal);
+
+ // TODO: We probably need to de-Sync when transceivers are stopped.
+ nsresult SyncWithMatchingVideoConduits(
+ std::vector<RefPtr<TransceiverImpl>>& transceivers);
+
+ void Shutdown_m();
+
+ bool ConduitHasPluginID(uint64_t aPluginID);
+
+ bool HasSendTrack(const dom::MediaStreamTrack* aSendTrack) const;
+
+ // This is so PCImpl can unregister from PrincipalChanged callbacks; maybe we
+ // should have TransceiverImpl handle these callbacks instead? It would need
+ // to be able to get a ref to PCImpl though.
+ RefPtr<dom::MediaStreamTrack> GetSendTrack()
+ {
+ return mSendTrack;
+ }
+
+ // for webidl
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector);
+ already_AddRefed<dom::MediaStreamTrack> GetReceiveTrack();
+ void SyncWithJS(dom::RTCRtpTransceiver& aJsTransceiver, ErrorResult& aRv);
+
+ void InsertDTMFTone(int tone, uint32_t duration);
+
+ bool HasReceiveTrack(const dom::MediaStreamTrack* aReceiveTrack) const;
+
+ // TODO: These are for stats; try to find a cleaner way.
+ RefPtr<MediaPipeline> GetSendPipeline();
+
+ RefPtr<MediaPipeline> GetReceivePipeline();
+
+ void AddRIDExtension(unsigned short aExtensionId);
+
+ void AddRIDFilter(const nsAString& aRid);
+
+ bool IsVideo() const;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+private:
+ virtual ~TransceiverImpl();
+ void InitAudio();
+ void InitVideo();
+ nsresult UpdateAudioConduit();
+ nsresult UpdateVideoConduit();
+ nsresult ConfigureVideoCodecMode(VideoSessionConduit& aConduit);
+ // This will eventually update audio extmap too
+ void UpdateVideoExtmap(const JsepTrackNegotiatedDetails& aDetails,
+ bool aSending);
+ void StartReceiveStream();
+
+ const std::string mPCHandle;
+ RefPtr<JsepTransceiver> mJsepTransceiver;
+ std::string mMid;
+ bool mHaveStartedReceiving;
+ nsCOMPtr<nsIEventTarget> mMainThread;
+ nsCOMPtr<nsIEventTarget> mStsThread;
+ RefPtr<DOMMediaStream> mReceiveStream;
+ RefPtr<dom::MediaStreamTrack> mSendTrack;
+ // state for webrtc.org that is shared between all transceivers
+ RefPtr<WebRtcCallWrapper> mCallWrapper;
+ RefPtr<TransportFlow> mRtpFlow;
+ RefPtr<TransportFlow> mRtcpFlow;
+ RefPtr<MediaSessionConduit> mConduit;
+ RefPtr<MediaPipelineReceive> mReceivePipeline;
+ RefPtr<MediaPipelineTransmit> mTransmitPipeline;
+};
+
+} // namespace mozilla
+
+#endif // _TRANSCEIVERIMPL_H_
+
--- a/media/webrtc/signaling/src/sdp/SdpAttribute.h
+++ b/media/webrtc/signaling/src/sdp/SdpAttribute.h
@@ -249,16 +249,32 @@ operator|(SdpDirectionAttribute::Directi
inline SdpDirectionAttribute::Direction
operator&(SdpDirectionAttribute::Direction d1,
SdpDirectionAttribute::Direction d2)
{
return (SdpDirectionAttribute::Direction)((unsigned)d1 & (unsigned)d2);
}
+inline SdpDirectionAttribute::Direction
+operator|=(SdpDirectionAttribute::Direction& d1,
+ SdpDirectionAttribute::Direction d2)
+{
+ d1 = d1 | d2;
+ return d1;
+}
+
+inline SdpDirectionAttribute::Direction
+operator&=(SdpDirectionAttribute::Direction& d1,
+ SdpDirectionAttribute::Direction d2)
+{
+ d1 = d1 & d2;
+ return d1;
+}
+
///////////////////////////////////////////////////////////////////////////
// a=dtls-message, draft-rescorla-dtls-in-sdp
//-------------------------------------------------------------------------
// attribute =/ dtls-message-attribute
//
// dtls-message-attribute = "dtls-message" ":" role SP value
//
// role = "client" / "server"
@@ -533,17 +549,18 @@ public:
RemoveMid(const std::string& mid)
{
for (auto i = mGroups.begin(); i != mGroups.end();) {
auto tag = std::find(i->tags.begin(), i->tags.end(), mid);
if (tag != i->tags.end()) {
i->tags.erase(tag);
}
- if (i->tags.empty()) {
+ if (i->tags.empty() ||
+ ((i->tags.size() == 1) && (i->semantics == Semantics::kBundle))) {
i = mGroups.erase(i);
} else {
++i;
}
}
}
virtual void Serialize(std::ostream& os) const override;
--- a/media/webrtc/signaling/src/sdp/SdpHelper.cpp
+++ b/media/webrtc/signaling/src/sdp/SdpHelper.cpp
@@ -441,19 +441,19 @@ SdpHelper::SetDefaultAddresses(const std
sdp::kInternet,
ipVersion,
defaultRtcpCandidateAddr));
}
}
nsresult
SdpHelper::GetIdsFromMsid(const Sdp& sdp,
- const SdpMediaSection& msection,
- std::string* streamId,
- std::string* trackId)
+ const SdpMediaSection& msection,
+ std::vector<std::string>* streamIds,
+ std::string* trackId)
{
if (!sdp.GetAttributeList().HasAttribute(
SdpAttribute::kMsidSemanticAttribute)) {
return NS_ERROR_NOT_AVAILABLE;
}
auto& msidSemantics = sdp.GetAttributeList().GetMsidSemantic().mMsidSemantics;
std::vector<SdpMsidAttributeList::Msid> allMsids;
@@ -481,24 +481,26 @@ SdpHelper::GetIdsFromMsid(const Sdp& sdp
for (auto i = allMsids.begin(); i != allMsids.end(); ++i) {
if (allMsidsAreWebrtc || webrtcMsids.count(i->identifier)) {
if (i->appdata.empty()) {
SDP_SET_ERROR("Invalid webrtc msid at level " << msection.GetLevel()
<< ": Missing track id.");
return NS_ERROR_INVALID_ARG;
}
if (!found) {
- *streamId = i->identifier;
*trackId = i->appdata;
found = true;
- } else if ((*streamId != i->identifier) || (*trackId != i->appdata)) {
- MOZ_MTLOG(ML_WARNING, "Found multiple different webrtc msids in "
- "m-section " << msection.GetLevel() << ". The "
- "behavior w/o transceivers is undefined.");
+ } else if ((*trackId != i->appdata)) {
+ SDP_SET_ERROR("Found multiple different webrtc track ids in m-section "
+ << msection.GetLevel() << ". The behavior here is "
+ "undefined.");
+ return NS_ERROR_INVALID_ARG;
}
+ streamIds->clear();
+ streamIds->push_back(i->identifier);
}
}
if (!found) {
return NS_ERROR_NOT_AVAILABLE;
}
return NS_OK;
--- a/media/webrtc/signaling/src/sdp/SdpHelper.h
+++ b/media/webrtc/signaling/src/sdp/SdpHelper.h
@@ -58,17 +58,17 @@ class SdpHelper {
const Sdp& sdp,
std::vector<SdpGroupAttributeList::Group>* groups) const;
nsresult GetMidFromLevel(const Sdp& sdp,
uint16_t level,
std::string* mid);
nsresult GetIdsFromMsid(const Sdp& sdp,
const SdpMediaSection& msection,
- std::string* streamId,
+ std::vector<std::string>* streamId,
std::string* trackId);
nsresult GetMsids(const SdpMediaSection& msection,
std::vector<SdpMsidAttributeList::Msid>* msids);
nsresult ParseMsid(const std::string& msidAttribute,
std::string* streamId,
std::string* trackId);
nsresult AddCandidateToSdp(Sdp* sdp,
const std::string& candidate,
--- a/media/webrtc/signaling/src/sdp/SdpMediaSection.h
+++ b/media/webrtc/signaling/src/sdp/SdpMediaSection.h
@@ -103,60 +103,65 @@ public:
GetLevel() const
{
return mLevel;
}
inline bool
IsReceiving() const
{
- return GetDirectionAttribute().mValue & sdp::kRecv;
+ return GetDirection() & sdp::kRecv;
}
inline bool
IsSending() const
{
- return GetDirectionAttribute().mValue & sdp::kSend;
+ return GetDirection() & sdp::kSend;
}
inline void
SetReceiving(bool receiving)
{
- auto direction = GetDirectionAttribute().mValue;
+ auto direction = GetDirection();
if (direction & sdp::kSend) {
SetDirection(receiving ?
SdpDirectionAttribute::kSendrecv :
SdpDirectionAttribute::kSendonly);
} else {
SetDirection(receiving ?
SdpDirectionAttribute::kRecvonly :
SdpDirectionAttribute::kInactive);
}
}
inline void
SetSending(bool sending)
{
- auto direction = GetDirectionAttribute().mValue;
+ auto direction = GetDirection();
if (direction & sdp::kRecv) {
SetDirection(sending ?
SdpDirectionAttribute::kSendrecv :
SdpDirectionAttribute::kRecvonly);
} else {
SetDirection(sending ?
SdpDirectionAttribute::kSendonly :
SdpDirectionAttribute::kInactive);
}
}
inline void SetDirection(SdpDirectionAttribute::Direction direction)
{
GetAttributeList().SetAttribute(new SdpDirectionAttribute(direction));
}
+ inline SdpDirectionAttribute::Direction GetDirection() const
+ {
+ return GetDirectionAttribute().mValue;
+ }
+
const SdpFmtpAttributeList::Parameters* FindFmtp(const std::string& pt) const;
void SetFmtp(const SdpFmtpAttributeList::Fmtp& fmtp);
void RemoveFmtp(const std::string& pt);
const SdpRtpmapAttributeList::Rtpmap* FindRtpmap(const std::string& pt) const;
const SdpSctpmapAttributeList::Sctpmap* GetSctpmap() const;
uint32_t GetSctpPort() const;
bool GetMaxMessageSize(uint32_t* size) const;
bool HasRtcpFb(const std::string& pt,