зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
5daf41d807
Коммит
cf90c6a366
|
@ -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]
|
||||
|
|
Загрузка…
Ссылка в новой задаче