Bug 1290948 - Part 3: Implement JS part of RTCRtpTransceiver. r=jib

MozReview-Commit-ID: FOZSqKbqHtC

--HG--
extra : rebase_source : 988624d3fe5f44273e7f2d279d25f67a33f7dfae
This commit is contained in:
Byron Campen [:bwc] 2017-08-23 15:51:21 -05:00
Родитель 5daf41d807
Коммит cf90c6a366
1 изменённых файлов: 576 добавлений и 139 удалений

Просмотреть файл

@ -25,6 +25,7 @@ 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";
@ -37,6 +38,7 @@ 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}");
@ -148,9 +150,8 @@ class GlobalPCList {
let cleanupPcRef = function(pcref) {
let pc = pcref.get();
if (pc) {
pc._pc.close();
delete pc._observer;
pc._pc = null;
pc._suppressEvents = true;
pc.close();
}
};
@ -347,8 +348,8 @@ setupPrototype(RTCStatsReport, {
class RTCPeerConnection {
constructor() {
this._senders = [];
this._receivers = [];
this._receiveStreams = new Map();
this._transceivers = [];
this._pc = null;
this._closed = false;
@ -589,6 +590,18 @@ class RTCPeerConnection {
}
}
// 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:
*
@ -690,7 +703,7 @@ class RTCPeerConnection {
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);
}
}
@ -757,11 +770,60 @@ class RTCPeerConnection {
}
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(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");
}
return this._async(() => this._createOffer(optionsOrOnSucc));
}
async _createOffer(options) {
@ -1066,77 +1128,161 @@ class RTCPeerConnection {
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();
}
_insertDTMF(sender, tones, duration, interToneGap) {
return this._impl.insertDTMF(sender.__DOM_IMPL__, tones, duration, interToneGap);
_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;
}
_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() {
@ -1144,30 +1290,37 @@ class RTCPeerConnection {
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) {
@ -1190,6 +1343,10 @@ class RTCPeerConnection {
this._impl.disablePacketDump(level, type, sending);
}
getTransceivers() {
return this._transceivers;
}
get localDescription() {
this._checkClosed();
let sdp = this._impl.localDescription;
@ -1327,9 +1484,17 @@ class RTCPeerConnection {
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, {
@ -1395,10 +1560,16 @@ class PeerConnectionObserver {
}
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();
}
@ -1435,10 +1606,6 @@ class PeerConnectionObserver {
{ 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:
//
@ -1558,51 +1725,51 @@ class PeerConnectionObserver {
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) {
@ -1610,9 +1777,9 @@ class PeerConnectionObserver {
{ 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 }));
}
@ -1623,6 +1790,10 @@ class PeerConnectionObserver {
pc._onPacket(level, type, sending, packet);
}
}
syncTransceivers() {
this._dompc._syncTransceivers();
}
}
setupPrototype(PeerConnectionObserver, {
classID: PC_OBS_CID,
@ -1667,23 +1838,7 @@ class RTCDTMFSender {
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, {
@ -1693,28 +1848,132 @@ setupPrototype(RTCDTMFSender, {
});
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,
@ -1723,10 +1982,18 @@ setupPrototype(RTCRtpSender, {
});
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));
@ -1738,6 +2005,175 @@ setupPrototype(RTCRtpReceiver, {
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 });
@ -1758,6 +2194,7 @@ this.NSGetFactory = XPCOMUtils.generateNSGetFactory(
RTCPeerConnectionStatic,
RTCRtpReceiver,
RTCRtpSender,
RTCRtpTransceiver,
RTCStatsReport,
PeerConnectionObserver,
CreateOfferRequest]