openvidu-browser: mediasoup refactoring

pull/600/head
pabloFuente 2020-09-01 12:53:32 +02:00
parent 6f976246b4
commit 0d3d54351e
2 changed files with 190 additions and 87 deletions

View File

@ -822,7 +822,7 @@ export class Stream extends EventDispatcher {
simulcast: false
};
const successCallback = (sdpOfferParam) => {
const successOfferCallback = (sdpOfferParam) => {
logger.debug('Sending SDP offer to publish as '
+ this.streamId, sdpOfferParam);
@ -859,7 +859,7 @@ export class Stream extends EventDispatcher {
reject('Error on publishVideo: ' + JSON.stringify(error));
}
} else {
this.webRtcPeer.processAnswer(response.sdpAnswer, false)
this.webRtcPeer.processRemoteAnswer(response.sdpAnswer)
.then(() => {
this.streamId = response.id;
this.creationTime = response.createdAt;
@ -894,10 +894,15 @@ export class Stream extends EventDispatcher {
this.webRtcPeer = new WebRtcPeerSendonly(options);
}
this.webRtcPeer.addIceConnectionStateChangeListener('publisher of ' + this.connection.connectionId);
this.webRtcPeer.generateOffer().then(sdpOffer => {
successCallback(sdpOffer);
this.webRtcPeer.createOffer().then(sdpOffer => {
this.webRtcPeer.processLocalOffer(sdpOffer)
.then(() => {
successOfferCallback(sdpOffer.sdp);
}).catch(error => {
reject(new Error('(publish) SDP process local offer error: ' + JSON.stringify(error)));
});
}).catch(error => {
reject(new Error('(publish) SDP offer error: ' + JSON.stringify(error)));
reject(new Error('(publish) SDP create offer error: ' + JSON.stringify(error)));
});
});
}
@ -906,6 +911,30 @@ export class Stream extends EventDispatcher {
* @hidden
*/
initWebRtcPeerReceive(reconnect: boolean): Promise<any> {
return new Promise((resolve, reject) => {
this.session.openvidu.sendRequest('prepareReceiveVideFrom', { sender: this.streamId }, (error, response) => {
if (error) {
reject(new Error('Error on prepareReceiveVideFrom: ' + JSON.stringify(error)));
} else {
this.completeWebRtcPeerReceive(response.sdpOffer, reconnect)
.then(() => {
logger.info("'Subscriber' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "subscribed"));
this.remotePeerSuccessfullyEstablished();
this.initWebRtcStats();
resolve();
})
.catch(error => {
reject(error);
});
}
});
});
}
/**
* @hidden
*/
completeWebRtcPeerReceive(sdpOffer: string, reconnect: boolean): Promise<any> {
return new Promise((resolve, reject) => {
const offerConstraints = {
@ -921,50 +950,60 @@ export class Stream extends EventDispatcher {
simulcast: false
};
const successCallback = (sdpOfferParam) => {
logger.debug('Sending SDP offer to subscribe to '
+ this.streamId, sdpOfferParam);
const successAnswerCallback = (sdpAnswer) => {
logger.debug('Sending SDP answer to subscribe to '
+ this.streamId, sdpAnswer);
const method = reconnect ? 'reconnectStream' : 'receiveVideoFrom';
const params = { sdpOffer: sdpOfferParam };
const params = { sdpAnswer };
params[reconnect ? 'stream' : 'sender'] = this.streamId;
this.session.openvidu.sendRequest(method, params, (error, response) => {
if (error) {
reject(new Error('Error on recvVideoFrom: ' + JSON.stringify(error)));
reject(new Error('Error on receiveVideFrom: ' + JSON.stringify(error)));
} else {
// Ios Ionic. Limitation: some bug in iosrtc cordova plugin makes it necessary
// to add a timeout before calling PeerConnection#setRemoteDescription during
// some time (400 ms) from the moment first subscriber stream is received
if (this.session.isFirstIonicIosSubscriber) {
this.session.isFirstIonicIosSubscriber = false;
setTimeout(() => {
// After 400 ms Ionic iOS subscribers won't need to run
// PeerConnection#setRemoteDescription after 250 ms timeout anymore
this.session.countDownForIonicIosSubscribersActive = false;
}, 400);
}
const needsTimeoutOnProcessAnswer = this.session.countDownForIonicIosSubscribersActive;
this.webRtcPeer.processAnswer(response.sdpAnswer, needsTimeoutOnProcessAnswer).then(() => {
logger.info("'Subscriber' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "subscribed"));
this.remotePeerSuccessfullyEstablished();
this.initWebRtcStats();
resolve();
}).catch(error => {
reject(error);
});
resolve();
// // Ios Ionic. Limitation: some bug in iosrtc cordova plugin makes it necessary
// // to add a timeout before calling PeerConnection#setRemoteDescription during
// // some time (400 ms) from the moment first subscriber stream is received
// if (this.session.isFirstIonicIosSubscriber) {
// this.session.isFirstIonicIosSubscriber = false;
// setTimeout(() => {
// // After 400 ms Ionic iOS subscribers won't need to run
// // PeerConnection#setRemoteDescription after 250 ms timeout anymore
// this.session.countDownForIonicIosSubscribersActive = false;
// }, 400);
// }
// const needsTimeoutOnProcessAnswer = this.session.countDownForIonicIosSubscribersActive;
// this.webRtcPeer.processAnswer(response.sdpAnswer, needsTimeoutOnProcessAnswer).then(() => {
// logger.info("'Subscriber' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "subscribed"));
// this.remotePeerSuccessfullyEstablished();
// this.initWebRtcStats();
// resolve();
// }).catch(error => {
// reject(error);
// });
}
});
};
this.webRtcPeer = new WebRtcPeerRecvonly(options);
this.webRtcPeer.addIceConnectionStateChangeListener(this.streamId);
this.webRtcPeer.generateOffer()
.then(sdpOffer => {
successCallback(sdpOffer);
this.webRtcPeer.processRemoteOffer(sdpOffer)
.then(() => {
this.webRtcPeer.createAnswer().then(sdpAnswer => {
this.webRtcPeer.processLocalAnswer(sdpAnswer)
.then(() => {
successAnswerCallback(sdpAnswer.sdp);
}).catch(error => {
reject(new Error('(subscribe) SDP process local answer error: ' + JSON.stringify(error)));
});
}).catch(error => {
reject(new Error('(subscribe) SDP create answer error: ' + JSON.stringify(error)));
});
})
.catch(error => {
reject(new Error('(subscribe) SDP offer error: ' + JSON.stringify(error)));
reject(new Error('(subscribe) SDP process remote offer error: ' + JSON.stringify(error)));
});
});
}

View File

@ -117,10 +117,10 @@ export class WebRtcPeer {
}
/**
* Function that creates an offer, sets it as local description and returns the offer param
* to send to OpenVidu Server (will be the remote description of 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
*/
generateOffer(): Promise<string> {
createOffer(): Promise<RTCSessionDescriptionInit> {
return new Promise((resolve, reject) => {
let offerAudio, offerVideo = true;
@ -140,52 +140,32 @@ export class WebRtcPeer {
logger.debug('RTCPeerConnection constraints: ' + JSON.stringify(constraints));
if (platform.name === 'Safari' && platform.ua!!.indexOf('Safari') !== -1) {
// 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()
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');
}
resolve(offer);
})
.catch(error => reject(error));
} else {
// Rest of platforms
this.pc.createOffer(constraints).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');
}
this.pc.createOffer(constraints)
.then(offer => {
logger.debug('Created SDP offer');
resolve(offer);
})
.catch(error => reject(error));
}
@ -193,10 +173,95 @@ export class WebRtcPeer {
}
/**
* Function invoked when a SDP answer is received. Final step in SDP negotiation, the peer
* just needs to set the answer as its remote description
* Creates an SDP answer from the local RTCPeerConnection to send to the other peer
* Only if the negotiation was initiated by the other peer
*/
processAnswer(sdpAnswer: string, needsTimeoutOnProcessAnswer: boolean): Promise<string> {
createAnswer(): Promise<RTCSessionDescriptionInit> {
return new Promise((resolve, reject) => {
let offerAudio, offerVideo = true;
if (!!this.configuration.mediaConstraints) {
offerAudio = (typeof this.configuration.mediaConstraints.audio === 'boolean') ?
this.configuration.mediaConstraints.audio : true;
offerVideo = (typeof this.configuration.mediaConstraints.video === 'boolean') ?
this.configuration.mediaConstraints.video : true;
}
const constraints: RTCOfferOptions = {
offerToReceiveAudio: offerAudio,
offerToReceiveVideo: offerVideo
};
this.pc.createAnswer(constraints).then(sdpAnswer => {
resolve(sdpAnswer);
}).catch(error => {
reject(error);
});
});
}
/**
* This peer initiated negotiation. Step 1/4 of SDP offer-answer protocol
*/
processLocalOffer(offer: RTCSessionDescriptionInit): Promise<void> {
return new Promise((resolve, reject) => {
this.pc.setLocalDescription(offer)
.then(() => {
const localDescription = this.pc.localDescription;
if (!!localDescription) {
logger.debug('Local description set', localDescription.sdp);
resolve();
} else {
reject('Local description is not defined');
}
})
.catch(error => {
reject(error);
});
});
}
/**
* Other peer initiated negotiation. Step 2/4 of SDP offer-answer protocol
*/
processRemoteOffer(sdpOffer: string): Promise<void> {
return new Promise((resolve, reject) => {
const offer: RTCSessionDescriptionInit = {
type: 'offer',
sdp: sdpOffer
};
logger.debug('SDP offer received, setting remote description', offer);
if (this.pc.signalingState === 'closed') {
reject('RTCPeerConnection is closed when trying to set remote description');
}
// TODO: check if Ionic iOS still needs timeout on setting first remote description when subscribing
this.setRemoteDescription(offer, false)
.then(() => {
resolve();
})
.catch(error => {
reject(error);
});
});
}
/**
* Other peer initiated negotiation. Step 3/4 of SDP offer-answer protocol
*/
processLocalAnswer(answer: RTCSessionDescriptionInit): Promise<void> {
return new Promise((resolve, reject) => {
logger.debug('SDP answer created, setting local description');
if (this.pc.signalingState === 'closed') {
reject('RTCPeerConnection is closed when trying to set local description');
}
this.pc.setLocalDescription(answer)
.then(() => resolve())
.catch(error => reject(error));
});
}
/**
* This peer initiated negotiation. Step 4/4 of SDP offer-answer protocol
*/
processRemoteAnswer(sdpAnswer: string): Promise<void> {
return new Promise((resolve, reject) => {
const answer: RTCSessionDescriptionInit = {
type: 'answer',
@ -205,34 +270,33 @@ export class WebRtcPeer {
logger.debug('SDP answer received, setting remote description');
if (this.pc.signalingState === 'closed') {
reject('RTCPeerConnection is closed');
reject('RTCPeerConnection is closed when trying to set remote description');
}
this.setRemoteDescription(answer, needsTimeoutOnProcessAnswer, resolve, reject);
this.setRemoteDescription(answer, false)
.then(() => resolve())
.catch(error => reject(error));
});
}
/**
* @hidden
*/
setRemoteDescription(answer: RTCSessionDescriptionInit, needsTimeoutOnProcessAnswer: boolean, resolve: (value?: string | PromiseLike<string> | undefined) => void, reject: (reason?: any) => void) {
if (platform['isIonicIos']) {
// Ionic iOS platform
if (needsTimeoutOnProcessAnswer) {
// 400 ms have not elapsed yet since first remote stream triggered Stream#initWebRtcPeerReceive
setTimeout(() => {
logger.info('setRemoteDescription run after timeout for Ionic iOS device');
this.pc.setRemoteDescription(new RTCSessionDescription(answer)).then(() => resolve()).catch(error => reject(error));
}, 250);
} else {
// 400 ms have elapsed
this.pc.setRemoteDescription(new RTCSessionDescription(answer)).then(() => resolve()).catch(error => reject(error));
}
} else {
async setRemoteDescription(sdp: RTCSessionDescriptionInit, needsTimeoutOnProcessAnswer: boolean): Promise<void> {
// if (platform['isIonicIos']) {
// // Ionic iOS platform
// if (needsTimeoutOnProcessAnswer) {
// // 400 ms have not elapsed yet since first remote stream triggered Stream#initWebRtcPeerReceive
// await new Promise(resolve => setTimeout(resolve, 250)); // Sleep for 250ms
// logger.info('setRemoteDescription run after timeout for Ionic iOS device');
// return this.pc.setRemoteDescription(sdp);
// } else {
// // 400 ms have elapsed
// return this.pc.setRemoteDescription(sdp);
// }
// } else {
// Rest of platforms
this.pc.setRemoteDescription(answer).then(() => resolve()).catch(error => reject(error));
}
return this.pc.setRemoteDescription(sdp);
// }
}
/**