mirror of https://github.com/OpenVidu/openvidu.git
openvidu-browser: rewrite generateOffer to use Transceivers
Uses Transceivers if these are available in the underlying WebRTC implementation; otherwise it falls back to the legacy "offerToReceiveX" config of pc.createOffer()pull/577/head
parent
9b5fffe30e
commit
610132b17d
|
@ -37,29 +37,37 @@ export interface WebRtcPeerConfiguration {
|
||||||
};
|
};
|
||||||
simulcast: boolean;
|
simulcast: boolean;
|
||||||
onicecandidate: (event) => void;
|
onicecandidate: (event) => void;
|
||||||
iceServers: RTCIceServer[] | undefined;
|
iceServers?: RTCIceServer[];
|
||||||
mediaStream?: MediaStream;
|
mediaStream?: MediaStream | null;
|
||||||
mode?: 'sendonly' | 'recvonly' | 'sendrecv';
|
mode?: 'sendonly' | 'recvonly' | 'sendrecv';
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebRtcPeer {
|
export class WebRtcPeer {
|
||||||
|
public pc: RTCPeerConnection;
|
||||||
|
public remoteCandidatesQueue: RTCIceCandidate[] = [];
|
||||||
|
public localCandidatesQueue: RTCIceCandidate[] = [];
|
||||||
|
|
||||||
pc: RTCPeerConnection;
|
// Same as WebRtcPeerConfiguration but without optional fields.
|
||||||
id: string;
|
protected configuration: Required<WebRtcPeerConfiguration>;
|
||||||
remoteCandidatesQueue: RTCIceCandidate[] = [];
|
|
||||||
localCandidatesQueue: RTCIceCandidate[] = [];
|
|
||||||
|
|
||||||
iceCandidateList: RTCIceCandidate[] = [];
|
|
||||||
|
|
||||||
|
private iceCandidateList: RTCIceCandidate[] = [];
|
||||||
private candidategatheringdone = false;
|
private candidategatheringdone = false;
|
||||||
|
|
||||||
constructor(protected configuration: WebRtcPeerConfiguration) {
|
constructor(configuration: WebRtcPeerConfiguration) {
|
||||||
platform = PlatformUtils.getInstance();
|
platform = PlatformUtils.getInstance();
|
||||||
this.configuration.iceServers = (!!this.configuration.iceServers && this.configuration.iceServers.length > 0) ? this.configuration.iceServers : freeice();
|
|
||||||
|
this.configuration = {
|
||||||
|
...configuration,
|
||||||
|
iceServers: (!!configuration.iceServers && configuration.iceServers.length > 0) ? configuration.iceServers : freeice(),
|
||||||
|
mediaStream: !!configuration.mediaStream
|
||||||
|
? configuration.mediaStream
|
||||||
|
: null,
|
||||||
|
mode: !!configuration.mode ? configuration.mode : "sendrecv",
|
||||||
|
id: !!configuration.id ? configuration.id : this.generateUniqueId(),
|
||||||
|
};
|
||||||
|
|
||||||
this.pc = new RTCPeerConnection({ iceServers: this.configuration.iceServers });
|
this.pc = new RTCPeerConnection({ iceServers: this.configuration.iceServers });
|
||||||
this.id = !!configuration.id ? configuration.id : this.generateUniqueId();
|
|
||||||
|
|
||||||
this.pc.onicecandidate = event => {
|
this.pc.onicecandidate = event => {
|
||||||
if (!!event.candidate) {
|
if (!!event.candidate) {
|
||||||
|
@ -128,73 +136,110 @@ export class WebRtcPeer {
|
||||||
*/
|
*/
|
||||||
generateOffer(): Promise<string> {
|
generateOffer(): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let offerAudio, offerVideo = true;
|
const useAudio = this.configuration.mediaConstraints.audio;
|
||||||
|
const useVideo = this.configuration.mediaConstraints.video;
|
||||||
|
|
||||||
// Constraints must have both blocks
|
let offerPromise: Promise<RTCSessionDescriptionInit>;
|
||||||
if (!!this.configuration.mediaConstraints) {
|
|
||||||
offerAudio = (typeof this.configuration.mediaConstraints.audio === 'boolean') ?
|
// TODO: Delete this conditional when all supported browsers are
|
||||||
this.configuration.mediaConstraints.audio : true;
|
// modern enough to implement the getTransceivers() method.
|
||||||
offerVideo = (typeof this.configuration.mediaConstraints.video === 'boolean') ?
|
if ("getTransceivers" in this.pc) {
|
||||||
this.configuration.mediaConstraints.video : true;
|
logger.debug("[generateOffer] Method pc.getTransceivers() is available; using it");
|
||||||
|
|
||||||
|
// At this point, all "send" audio/video tracks have been added
|
||||||
|
// with pc.addTrack(), which in modern versions of libwebrtc
|
||||||
|
// will have created Transceivers with "sendrecv" direction.
|
||||||
|
// Source: [addTrack/9.3](https://www.w3.org/TR/2020/CRD-webrtc-20201203/#dom-rtcpeerconnection-addtrack).
|
||||||
|
//
|
||||||
|
// Here we just need to enforce that those Transceivers have the
|
||||||
|
// correct direction, either "sendrecv" or "sendonly".
|
||||||
|
//
|
||||||
|
// Otherwise, if the tracks are "recv", no Transceiver should
|
||||||
|
// have been added yet.
|
||||||
|
|
||||||
|
const tcs = this.pc.getTransceivers();
|
||||||
|
|
||||||
|
if (tcs.length > 0) {
|
||||||
|
// Assert correct mode.
|
||||||
|
if (
|
||||||
|
this.configuration.mode !== "sendrecv" &&
|
||||||
|
this.configuration.mode !== "sendonly"
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"BUG: Transceivers added, but direction is not send"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tc of tcs) {
|
||||||
|
tc.direction = this.configuration.mode;
|
||||||
|
logger.debug(
|
||||||
|
`RTCRtpTransceiver direction: ${tc.direction}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.configuration.mode !== "recvonly") {
|
||||||
|
throw new Error(
|
||||||
|
"BUG: Transceivers missing, but direction is not recv"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useAudio) {
|
||||||
|
this.pc.addTransceiver("audio", {
|
||||||
|
direction: this.configuration.mode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useVideo) {
|
||||||
|
this.pc.addTransceiver("video", {
|
||||||
|
direction: this.configuration.mode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offerPromise = this.pc.createOffer();
|
||||||
|
} else {
|
||||||
|
logger.debug("[generateOffer] Method pc.getTransceivers() 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 constraints: RTCOfferOptions = {
|
||||||
|
offerToReceiveAudio:
|
||||||
|
this.configuration.mode !== "sendonly" && useAudio,
|
||||||
|
offerToReceiveVideo:
|
||||||
|
this.configuration.mode !== "sendonly" && useVideo,
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"RTCPeerConnection constraints: " +
|
||||||
|
JSON.stringify(constraints)
|
||||||
|
);
|
||||||
|
|
||||||
|
// @ts-ignore: Compiler is too clever and thinks this branch
|
||||||
|
// will never execute.
|
||||||
|
offerPromise = this.pc.createOffer(constraints);
|
||||||
}
|
}
|
||||||
|
|
||||||
const constraints: RTCOfferOptions = {
|
offerPromise
|
||||||
offerToReceiveAudio: (this.configuration.mode !== 'sendonly' && offerAudio),
|
.then((offer) => {
|
||||||
offerToReceiveVideo: (this.configuration.mode !== 'sendonly' && offerVideo)
|
logger.debug("Created SDP offer");
|
||||||
};
|
|
||||||
|
|
||||||
logger.debug('RTCPeerConnection constraints: ' + JSON.stringify(constraints));
|
|
||||||
|
|
||||||
if (platform.isSafariBrowser() && !platform.isIonicIos()) {
|
|
||||||
// Safari (excluding Ionic), at least on iOS just seems to support unified plan, whereas in other browsers is not yet ready and considered experimental
|
|
||||||
if (offerAudio) {
|
|
||||||
this.pc.addTransceiver('audio', {
|
|
||||||
direction: this.configuration.mode,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (offerVideo) {
|
|
||||||
this.pc.addTransceiver('video', {
|
|
||||||
direction: this.configuration.mode,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pc
|
|
||||||
.createOffer()
|
|
||||||
.then(offer => {
|
|
||||||
logger.debug('Created SDP offer');
|
|
||||||
return this.pc.setLocalDescription(offer);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
const localDescription = this.pc.localDescription;
|
|
||||||
|
|
||||||
if (!!localDescription) {
|
|
||||||
logger.debug('Local description set', localDescription.sdp);
|
|
||||||
resolve(localDescription.sdp);
|
|
||||||
} else {
|
|
||||||
reject('Local description is not defined');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => reject(error));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Rest of platforms
|
|
||||||
this.pc.createOffer(constraints).then(offer => {
|
|
||||||
logger.debug('Created SDP offer');
|
|
||||||
return this.pc.setLocalDescription(offer);
|
return this.pc.setLocalDescription(offer);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const localDescription = this.pc.localDescription;
|
const localDescription = this.pc.localDescription;
|
||||||
if (!!localDescription) {
|
|
||||||
logger.debug('Local description set', localDescription.sdp);
|
if (!!localDescription) {
|
||||||
resolve(localDescription.sdp);
|
logger.debug(
|
||||||
} else {
|
"Local description set:",
|
||||||
reject('Local description is not defined');
|
localDescription.sdp
|
||||||
}
|
);
|
||||||
})
|
resolve(localDescription.sdp);
|
||||||
.catch(error => reject(error));
|
} else {
|
||||||
}
|
reject("Local description is not defined");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => reject(error));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,25 +318,25 @@ export class WebRtcPeer {
|
||||||
switch (iceConnectionState) {
|
switch (iceConnectionState) {
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
// Possible network disconnection
|
// Possible network disconnection
|
||||||
logger.warn('IceConnectionState of RTCPeerConnection ' + this.id + ' (' + otherId + ') change to "disconnected". Possible network disconnection');
|
logger.warn('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "disconnected". Possible network disconnection');
|
||||||
break;
|
break;
|
||||||
case 'failed':
|
case 'failed':
|
||||||
logger.error('IceConnectionState of RTCPeerConnection ' + this.id + ' (' + otherId + ') to "failed"');
|
logger.error('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') to "failed"');
|
||||||
break;
|
break;
|
||||||
case 'closed':
|
case 'closed':
|
||||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.id + ' (' + otherId + ') change to "closed"');
|
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "closed"');
|
||||||
break;
|
break;
|
||||||
case 'new':
|
case 'new':
|
||||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.id + ' (' + otherId + ') change to "new"');
|
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "new"');
|
||||||
break;
|
break;
|
||||||
case 'checking':
|
case 'checking':
|
||||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.id + ' (' + otherId + ') change to "checking"');
|
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "checking"');
|
||||||
break;
|
break;
|
||||||
case 'connected':
|
case 'connected':
|
||||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.id + ' (' + otherId + ') change to "connected"');
|
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "connected"');
|
||||||
break;
|
break;
|
||||||
case 'completed':
|
case 'completed':
|
||||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.id + ' (' + otherId + ') change to "completed"');
|
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "completed"');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -326,4 +371,4 @@ export class WebRtcPeerSendrecv extends WebRtcPeer {
|
||||||
configuration.mode = 'sendrecv';
|
configuration.mode = 'sendrecv';
|
||||||
super(configuration);
|
super(configuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue