--- 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,18 @@ 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._suppressEvents = true;
+ pc.close();
}
};
let cleanupWinId = function(list, winID) {
if (list.hasOwnProperty(winID)) {
list[winID].forEach(cleanupPcRef);
delete list[winID];
}
@@ -342,18 +343,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
@@ -584,16 +585,28 @@ class RTCPeerConnection {
try {
wrapCallback(onSucc)(await func());
} catch (e) {
wrapCallback(onErr)(e);
}
}
+ // This implements the fairly common "Queue a task" logic
+ async _queueTaskWithClosedCheck(func) {
+ return new this._win.Promise(resolve => {
+ Services.tm.dispatchToMainThread({ run() {
+ if (!this._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"} ] }
*
@@ -685,17 +698,17 @@ class RTCPeerConnection {
throw new this._win.DOMException("Peer connection is closed",
"InvalidStateError");
}
}
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) {
+ if (!this._suppressEvents) {
this.__DOM_IMPL__.dispatchEvent(event);
}
}
// Log error message to web console and window.onerror, if present.
logErrorAndCallOnError(e) {
this.logMsg(e.message, e.fileName, e.lineNumber, Ci.nsIScriptError.exceptionFlag);
@@ -752,21 +765,70 @@ class RTCPeerConnection {
set(h) {
this.logWarning(name + " is deprecated! " + msg);
return this.setEH(name, h);
}
});
}
createOffer(optionsOrOnSucc, onErr, options) {
- // This entry-point handles both new and legacy call sig. Decipher which one
+ let onSuccess = null;
if (typeof optionsOrOnSucc == "function") {
- return this._legacy(optionsOrOnSucc, onErr, () => this._createOffer(options));
+ onSuccess = optionsOrOnSucc;
+ } else {
+ options = optionsOrOnSucc;
+ }
+
+ // 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(options);
+
+ // This entry-point handles both new and legacy call sig. Decipher which one
+ if (onSuccess) {
+ return this._legacy(onSuccess, onErr, () => this._createOffer(options));
}
- return this._async(() => this._createOffer(optionsOrOnSucc));
+
+ return this._async(() => this._createOffer(options));
+ }
+
+ // Ensures that we have at least one transceiver of |kind| that is
+ // configured to receive. It will create one if necessary.
+ _ensureOfferToReceive(kind) {
+ let hasRecv = this._transceivers.some(
+ transceiver =>
+ transceiver.getKind() == kind &&
+ (transceiver.direction == "sendrecv" || transceiver.direction == "recvonly") &&
+ !transceiver.stopped);
+
+ if (!hasRecv) {
+ this._addTransceiverNoEvents(kind, {direction: "recvonly"});
+ }
+ }
+
+ // Handles offerToReceiveAudio/Video
+ _ensureTransceiversForOfferToReceive(options) {
+ if (options.offerToReceiveVideo) {
+ this._ensureOfferToReceive("video");
+ }
+
+ if (options.offerToReceiveVideo === false) {
+ this.logWarning("offerToReceiveVideo: false is ignored now. If you " +
+ "want to disallow a recv track, use " +
+ "RTCRtpTransceiver.direction");
+ }
+
+ if (options.offerToReceiveAudio) {
+ this._ensureOfferToReceive("audio");
+ }
+
+ if (options.offerToReceiveAudio === false) {
+ this.logWarning("offerToReceiveAudio: false is ignored now. If you " +
+ "want to disallow a recv track, use " +
+ "RTCRtpTransceiver.direction");
+ }
}
async _createOffer(options) {
this._checkClosed();
let origin = Cu.getWebIDLCallerPrincipal().origin;
return this._chain(async () => {
let haveAssertion;
if (this._localIdp.enabled) {
@@ -1061,118 +1123,209 @@ 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.setStreams([stream]);
+ if (transceiver.direction == "recvonly") {
+ transceiver.setDirectionInternal("sendrecv");
+ } else if (transceiver.direction == "inactive") {
+ transceiver.setDirectionInternal("sendonly");
+ }
+ } 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__);
+
+ let transceiver =
+ this._transceivers.find(transceiver => transceiver.sender == sender);
+
+ // If the transceiver was removed due to rollback, let it slide.
+ if (!transceiver || !sender.track) {
+ return;
+ }
+
+ // TODO(bug 1401983): Move to TransceiverImpl?
+ this._impl.removeTrack(sender.track);
+
+ sender.setTrack(null);
+ if (transceiver.direction == "sendrecv") {
+ transceiver.setDirectionInternal("recvonly");
+ } else if (transceiver.direction == "sendonly") {
+ transceiver.setDirectionInternal("inactive");
}
+
+ transceiver.sync();
+ this.updateNegotiationNeeded();
+ }
+
+ _addTransceiverNoEvents(sendTrackOrKind, init) {
+ let sendTrack = null;
+ let kind;
+ 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;
}
- _insertDTMF(sender, tones, duration, interToneGap) {
- return this._impl.insertDTMF(sender.__DOM_IMPL__, tones, duration, interToneGap);
+ _onTransceiverNeeded(kind, transceiverImpl) {
+ let init = {direction: "recvonly"};
+ let transceiver = this._win.RTCRtpTransceiver._create(
+ this._win,
+ new RTCRtpTransceiver(this, transceiverImpl, init, kind, null));
+ transceiver.sync();
+ this._transceivers.push(transceiver);
+ }
+
+ addTransceiver(sendTrackOrKind, init) {
+ let transceiver = this._addTransceiverNoEvents(sendTrackOrKind, init);
+ this.updateNegotiationNeeded();
+ return transceiver;
+ }
+
+ _syncTransceivers() {
+ this._transceivers.forEach(transceiver => transceiver.sync());
+ }
+
+ updateNegotiationNeeded() {
+ if (this._closed || this.signalingState != "stable") {
+ return;
+ }
+
+ let negotiationNeeded = this._impl.checkNegotiationNeeded();
+ if (!negotiationNeeded) {
+ this._negotiationNeeded = false;
+ 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) {
+ _replaceTrack(transceiverImpl, withTrack) {
this._checkClosed();
- return this._chain(() => new Promise((resolve, reject) => {
- this._onReplaceTrackSender = sender;
- this._onReplaceTrackWithTrack = withTrack;
- 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;
+ this._suppressEvents = true;
+ delete this._pc;
+ delete this._observer;
}
getLocalStreams() {
this._checkClosed();
- return this._impl.getLocalStreams();
+ let localStreams = new Set();
+ this._transceivers.forEach(transceiver => {
+ transceiver.sender.getStreams().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);
@@ -1185,16 +1338,20 @@ class RTCPeerConnection {
mozEnablePacketDump(level, type, sending) {
this._impl.enablePacketDump(level, type, sending);
}
mozDisablePacketDump(level, type, sending) {
this._impl.disablePacketDump(level, type, sending);
}
+ 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 });
}
@@ -1322,19 +1479,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 only do this if this is the first DataChannel created,
+ // but the c++ code that does the "is negotiation needed" checking will
+ // only ever return true on the first one.
+ this.updateNegotiationNeeded();
+
+ return dataChannel;
}
}
setupPrototype(RTCPeerConnection, {
classID: PC_CID,
contractID: PC_CONTRACT,
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
Ci.nsIDOMGlobalPropertyInitializer]),
_actions: {
@@ -1390,20 +1555,26 @@ class PeerConnectionObserver {
this._dompc._onCreateAnswerSuccess(sdp);
}
onCreateAnswerError(code, message) {
this._dompc._onCreateAnswerFailure(this.newError(message, code));
}
onSetLocalDescriptionSuccess() {
+ this._dompc._syncTransceivers();
+ this._negotiationNeeded = false;
+ this._dompc.updateNegotiationNeeded();
this._dompc._onSetLocalDescriptionSuccess();
}
onSetRemoteDescriptionSuccess() {
+ this._dompc._syncTransceivers();
+ this._negotiationNeeded = false;
+ this._dompc.updateNegotiationNeeded();
this._dompc._onSetRemoteDescriptionSuccess();
}
onSetLocalDescriptionError(code, message) {
this._localType = null;
this._dompc._onSetLocalDescriptionFailure(this.newError(message, code));
}
@@ -1430,20 +1601,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.
//
@@ -1553,81 +1720,85 @@ 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.remoteTrackIdIs(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);
+
+ // Get or create MediaStreams, and add the new track to them.
+ let streams = streamIds.map(id => this._dompc._getOrCreateStream(id));
+
+ 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 });
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 });
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);
- }
- }
-
- 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));
+ onTransceiverNeeded(kind, transceiverImpl) {
+ this._dompc._onTransceiverNeeded(kind, transceiverImpl);
}
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 }));
}
onPacket(level, type, sending, packet) {
var pc = this._dompc;
if (pc._onPacket) {
pc._onPacket(level, type, sending, packet);
}
}
+
+ syncTransceivers() {
+ this._dompc._syncTransceivers();
+ }
}
setupPrototype(PeerConnectionObserver, {
classID: PC_OBS_CID,
contractID: PC_OBS_CONTRACT,
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
Ci.nsIDOMGlobalPropertyInitializer])
});
@@ -1662,87 +1833,352 @@ 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;
-
- 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);
+ this._sender._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, transceiver, track, streams) {
+ let dtmf = pc._win.RTCDTMFSender._create(
+ pc._win, new RTCDTMFSender(this));
+
+ Object.assign(this, {
+ _pc: pc,
+ _transceiverImpl: transceiverImpl,
+ _transceiver: transceiver,
+ 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();
+
+ if (this._transceiver.stopped) {
+ throw new this._pc._win.DOMException(
+ "Cannot call replaceTrack when transceiver is stopped",
+ "InvalidStateError");
+ }
+
+ if (withTrack && (withTrack.kind != this._transceiver.getKind())) {
+ throw new this._pc._win.DOMException(
+ "Cannot replaceTrack with a different kind!",
+ "TypeError");
+ }
+
+ // Updates the track on the MediaPipeline; this is needed whether or not
+ // we've associated this transceiver, the spec language notwithstanding.
+ // Synchronous, and will throw on failure.
+ this._pc._replaceTrack(this._transceiverImpl, withTrack);
+
+ let setTrack = () => {
+ this.track = withTrack;
+ this._transceiver.sync();
+ };
+
+ // Spec is a little weird here; we only queue if the transceiver was
+ // associated, otherwise we update the track synchronously.
+ if (this._transceiver.mid == null) {
+ setTrack();
+ } else {
+ // We're supposed to queue a task if the transceiver is associated
+ await this._pc._queueTaskWithClosedCheck(setTrack);
+ }
}
setParameters(parameters) {
- return this._pc._win.Promise.resolve()
- .then(() => this._pc._setParameters(this, parameters));
+ return this._pc._win.Promise.resolve(this._setParameters(parameters));
+ }
+
+ async _setParameters(parameters) {
+ this._pc._checkClosed();
+
+ if (this._transceiver.stopped) {
+ throw new this._pc._win.DOMException(
+ "This sender's transceiver is stopped", "InvalidStateError");
+ }
+
+ 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;
+ }, {});
+
+ // TODO(bug 1401592): transaction ids, timing changes
+
+ await this._pc._queueTaskWithClosedCheck(() => {
+ this.parameters = parameters;
+ this._transceiver.sync();
+ });
}
getParameters() {
- return this._pc._getParameters(this);
+ // TODO(bug 1401592): transaction ids
+
+ // All the other stuff that the spec says to update is handled when
+ // transceivers are synced.
+ return this.parameters;
+ }
+
+ setStreams(streams) {
+ this._streams = streams;
+ }
+
+ getStreams() {
+ 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()
+ });
}
+ // TODO(bug 1401983): 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, this, sendTrack, streams));
+
+ let direction = (init && init.direction) || "sendrecv";
+ Object.assign(this,
+ {
+ _pc: pc,
+ mid: null,
+ sender,
+ receiver,
+ stopped: false,
+ _direction: direction,
+ currentDirection: null,
+ _remoteTrackId: null,
+ addTrackMagic: false,
+ _hasBeenUsedToSend: false,
+ // the receiver starts out without a track, so record this here
+ _kind: kind,
+ _transceiverImpl: transceiverImpl
+ });
+ }
+
+ set direction(direction) {
+ this._pc._checkClosed();
+
+ if (this.stopped) {
+ throw new this._pc._win.DOMException("Transceiver is stopped!",
+ "InvalidStateError");
+ }
+
+ if (this._direction == direction) {
+ return;
+ }
+
+ this._direction = direction;
+ this.sync();
+ this._pc.updateNegotiationNeeded();
+ }
+
+ get direction() {
+ return this._direction;
+ }
+
+ setDirectionInternal(direction) {
+ this._direction = direction;
+ }
+
+ stop() {
+ if (this.stopped) {
+ return;
+ }
+
+ this._pc._checkClosed();
+
+ 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;
+ }
+
+ setRemoteTrackId(webrtcTrackId) {
+ this._remoteTrackId = webrtcTrackId;
+ }
+
+ remoteTrackIdIs(webrtcTrackId) {
+ return this._remoteTrackId == webrtcTrackId;
+ }
+
+ getRemoteTrackId() {
+ return this._remoteTrackId;
+ }
+
+ setAddTrackMagic() {
+ this.addTrackMagic = true;
+ }
+
+ sync() {
+ if (this._syncing) {
+ throw new 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 1401983): 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,
@@ -1753,12 +2189,13 @@ this.NSGetFactory = XPCOMUtils.generateN
[GlobalPCList,
RTCDTMFSender,
RTCIceCandidate,
RTCSessionDescription,
RTCPeerConnection,
RTCPeerConnectionStatic,
RTCRtpReceiver,
RTCRtpSender,
+ RTCRtpTransceiver,
RTCStatsReport,
PeerConnectionObserver,
CreateOfferRequest]
);