mirror of https://github.com/OpenVidu/openvidu.git
WebRtcPeer: finish rewriting createOffer() & createAnswer() with Transceivers
createOffer() can be used either for sending a "send" or "recv" offer to the media server; this is needed for the KMS SDP negotiation semantics. createAnswer() is used when in MEDIASOUP SDP negotiation mode, where the media server is the one sending an SDP Offer to the browser.pull/577/head
parent
c16f4b9664
commit
a1e67b5c47
|
@ -118,52 +118,78 @@ export class WebRtcPeer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an SDP offer from the local RTCPeerConnection to send to the other peer
|
* Creates an SDP offer from the local RTCPeerConnection to send to the other peer
|
||||||
* Only if the negotiation was initiated by the this peer
|
* Only if the negotiation was initiated by this peer
|
||||||
*/
|
*/
|
||||||
createOffer(): Promise<RTCSessionDescriptionInit> {
|
createOffer(): Promise<RTCSessionDescriptionInit> {
|
||||||
const hasAudio = this.configuration.mediaConstraints.audio;
|
return new Promise((resolve, reject) => {
|
||||||
const hasVideo = this.configuration.mediaConstraints.video;
|
// TODO: Delete this conditional when all supported browsers are
|
||||||
|
// modern enough to implement the Transceiver methods.
|
||||||
|
if ("addTransceiver" in this.pc) {
|
||||||
|
logger.debug("[createOffer] Method RTCPeerConnection.addTransceiver() is available; using it");
|
||||||
|
|
||||||
let promise: Promise<RTCSessionDescriptionInit>;
|
if (this.configuration.mode !== "recvonly") {
|
||||||
|
// To send media, assume that all desired media tracks
|
||||||
|
// have been already added by higher level code to our
|
||||||
|
// MediaStream.
|
||||||
|
|
||||||
// TODO: Delete this conditional when all supported browsers are
|
if (!this.configuration.mediaStream) {
|
||||||
// modern enough to implement the Transceiver methods.
|
reject(new Error(`${this.configuration.mode} direction requested, but no stream was configured to be sent`));
|
||||||
if ("addTransceiver" in this.pc) {
|
return;
|
||||||
logger.debug("[createOffer] Method RTCPeerConnection.addTransceiver() is available; using it");
|
}
|
||||||
|
|
||||||
if (this.configuration.mediaStream) {
|
for (const track of this.configuration.mediaStream.getTracks()) {
|
||||||
for (const track of this.configuration.mediaStream.getTracks()) {
|
this.pc.addTransceiver(track, {
|
||||||
this.pc.addTransceiver(track, {
|
direction: this.configuration.mode,
|
||||||
direction: this.configuration.mode,
|
streams: [this.configuration.mediaStream],
|
||||||
streams: [this.configuration.mediaStream],
|
sendEncodings: [],
|
||||||
sendEncodings: [],
|
});
|
||||||
});
|
}
|
||||||
|
} else {
|
||||||
|
// To just receive media, create new recvonly transceivers.
|
||||||
|
for (const kind of ["audio", "video"]) {
|
||||||
|
// Check if the media kind should be used.
|
||||||
|
if (!this.configuration.mediaConstraints[kind]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.configuration.mediaStream = new MediaStream();
|
||||||
|
this.pc.addTransceiver(kind, {
|
||||||
|
direction: this.configuration.mode,
|
||||||
|
streams: [this.configuration.mediaStream],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.pc
|
||||||
|
.createOffer()
|
||||||
|
.then((sdpOffer) => resolve(sdpOffer))
|
||||||
|
.catch((error) => reject(error));
|
||||||
|
} else {
|
||||||
|
logger.debug("[createOffer] Method RTCPeerConnection.addTransceiver() is NOT available; using LEGACY offerToReceive{Audio,Video}");
|
||||||
|
|
||||||
|
// DEPRECATED LEGACY METHOD: Old WebRTC versions don't implement
|
||||||
|
// Transceivers, and instead depend on the deprecated
|
||||||
|
// "offerToReceiveAudio" and "offerToReceiveVideo".
|
||||||
|
|
||||||
|
const hasAudio = this.configuration.mediaConstraints.audio;
|
||||||
|
const hasVideo = this.configuration.mediaConstraints.video;
|
||||||
|
|
||||||
|
const options: RTCOfferOptions = {
|
||||||
|
offerToReceiveAudio:
|
||||||
|
this.configuration.mode !== "sendonly" && hasAudio,
|
||||||
|
offerToReceiveVideo:
|
||||||
|
this.configuration.mode !== "sendonly" && hasVideo,
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.debug("RTCPeerConnection.createOffer() options:", JSON.stringify(options));
|
||||||
|
|
||||||
|
this.pc
|
||||||
|
// @ts-ignore - Compiler is too clever and thinks this branch will never execute.
|
||||||
|
.createOffer(options)
|
||||||
|
.then((sdpOffer) => resolve(sdpOffer))
|
||||||
|
.catch((error) => reject(error));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
promise = this.pc.createOffer();
|
|
||||||
} else {
|
|
||||||
logger.debug("[createOffer] Method RTCPeerConnection.addTransceiver() is NOT available; using LEGACY offerToReceive{Audio,Video}");
|
|
||||||
|
|
||||||
// DEPRECATED: LEGACY METHOD: Old WebRTC versions don't implement
|
|
||||||
// Transceivers, and instead depend on the deprecated
|
|
||||||
// "offerToReceiveAudio" and "offerToReceiveVideo".
|
|
||||||
|
|
||||||
const options: RTCOfferOptions = {
|
|
||||||
offerToReceiveAudio:
|
|
||||||
this.configuration.mode !== "sendonly" && hasAudio,
|
|
||||||
offerToReceiveVideo:
|
|
||||||
this.configuration.mode !== "sendonly" && hasVideo,
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.debug("RTCPeerConnection.createOffer() options:", JSON.stringify(options));
|
|
||||||
|
|
||||||
// @ts-ignore: Compiler is too clever and thinks this branch
|
|
||||||
// will never execute.
|
|
||||||
promise = this.pc.createOffer(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -171,32 +197,44 @@ export class WebRtcPeer {
|
||||||
* Only if the negotiation was initiated by the other peer
|
* Only if the negotiation was initiated by the other peer
|
||||||
*/
|
*/
|
||||||
createAnswer(): Promise<RTCSessionDescriptionInit> {
|
createAnswer(): Promise<RTCSessionDescriptionInit> {
|
||||||
const hasAudio = this.configuration.mediaConstraints.audio;
|
return new Promise((resolve, reject) => {
|
||||||
const hasVideo = this.configuration.mediaConstraints.video;
|
// TODO: Delete this conditional when all supported browsers are
|
||||||
|
// modern enough to implement the Transceiver methods.
|
||||||
|
if ("getTransceivers" in this.pc) {
|
||||||
|
logger.debug("[createAnswer] Method RTCPeerConnection.getTransceivers() is available; using it");
|
||||||
|
|
||||||
let promise: Promise<RTCSessionDescriptionInit>;
|
// Ensure that the PeerConnection already contains one Transceiver
|
||||||
|
// for each kind of media.
|
||||||
|
// The Transceivers should have been already created internally by
|
||||||
|
// the PC itself, when `pc.setRemoteDescription(sdpOffer)` was called.
|
||||||
|
|
||||||
// TODO: Delete this conditional when all supported browsers are
|
for (const kind of ["audio", "video"]) {
|
||||||
// modern enough to implement the Transceiver methods.
|
// Check if the media kind should be used.
|
||||||
if ("addTransceiver" in this.pc) {
|
if (!this.configuration.mediaConstraints[kind]) {
|
||||||
logger.debug("[createAnswer] Method RTCPeerConnection.addTransceiver() is available; using it");
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasAudio) {
|
let tc = this.pc
|
||||||
this.pc.addTransceiver("audio", {
|
.getTransceivers()
|
||||||
direction: this.configuration.mode,
|
.find((tc) => tc.receiver.track.kind === kind);
|
||||||
});
|
|
||||||
|
if (tc) {
|
||||||
|
// Enforce our desired direction.
|
||||||
|
tc.direction = this.configuration.mode;
|
||||||
|
} else {
|
||||||
|
reject(new Error(`${kind} requested, but no transceiver was created from remote description`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pc
|
||||||
|
.createAnswer()
|
||||||
|
.then((sdpAnswer) => resolve(sdpAnswer))
|
||||||
|
.catch((error) => reject(error));
|
||||||
}
|
}
|
||||||
if (hasVideo) {
|
|
||||||
this.pc.addTransceiver("video", {
|
|
||||||
direction: this.configuration.mode,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// else, there is nothing to do; the legacy createAnswer() options do
|
// else, there is nothing to do; the legacy createAnswer() options do
|
||||||
// not offer any control over what tracks are included in the answer.
|
// not offer any control over which tracks are included in the answer.
|
||||||
|
});
|
||||||
return this.pc.createAnswer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue