2017-11-11 05:05:51 +08:00
|
|
|
'use strict';
|
2017-10-13 21:50:17 +08:00
|
|
|
|
|
|
|
const config = require('config');
|
|
|
|
const kurentoUrl = config.get('kurentoUrl');
|
2017-11-11 05:05:51 +08:00
|
|
|
const MCSApi = require('../mcs-core/lib/media/MCSApiStub');
|
2017-11-25 02:59:40 +08:00
|
|
|
const C = require('../bbb/messages/Constants');
|
2018-01-26 12:33:40 +08:00
|
|
|
const Logger = require('../utils/Logger');
|
2018-03-20 01:50:38 +08:00
|
|
|
const Messaging = require('../bbb/messages/Messaging');
|
2018-03-14 22:38:18 +08:00
|
|
|
const h264_sdp = require('../h264-sdp');
|
2017-10-13 21:50:17 +08:00
|
|
|
|
2018-01-26 12:33:40 +08:00
|
|
|
var sharedWebcams = {};
|
2017-10-13 21:50:17 +08:00
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
module.exports = class Video {
|
2018-03-20 01:50:38 +08:00
|
|
|
constructor(_bbbGW, _meetingId, _id, _shared, _sessionId) {
|
2017-11-11 09:35:01 +08:00
|
|
|
this.mcs = new MCSApi();
|
2017-11-25 02:59:40 +08:00
|
|
|
this.bbbGW = _bbbGW;
|
2017-11-11 09:35:01 +08:00
|
|
|
this.id = _id;
|
2017-11-25 02:59:40 +08:00
|
|
|
this.sessionId = _sessionId;
|
2018-03-20 01:50:38 +08:00
|
|
|
this.meetingId = _meetingId;
|
2017-11-11 09:35:01 +08:00
|
|
|
this.shared = _shared;
|
2017-11-25 02:59:40 +08:00
|
|
|
this.role = this.shared? 'share' : 'view'
|
2017-11-11 09:35:01 +08:00
|
|
|
this.mediaId = null;
|
2018-01-18 21:56:08 +08:00
|
|
|
this.iceQueue = null;
|
2017-11-11 09:35:01 +08:00
|
|
|
|
|
|
|
this.candidatesQueue = [];
|
2018-02-01 04:12:08 +08:00
|
|
|
this.notFlowingTimeout = null;
|
2017-11-11 05:05:51 +08:00
|
|
|
}
|
2017-10-13 21:50:17 +08:00
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
onIceCandidate (_candidate) {
|
|
|
|
if (this.mediaId) {
|
|
|
|
try {
|
|
|
|
this.flushCandidatesQueue();
|
|
|
|
this.mcs.addIceCandidate(this.mediaId, _candidate);
|
|
|
|
}
|
|
|
|
catch (err) {
|
2018-01-26 12:33:40 +08:00
|
|
|
Logger.error("[video] ICE candidate could not be added to media controller.", err);
|
2017-11-11 05:05:51 +08:00
|
|
|
}
|
2017-10-13 21:50:17 +08:00
|
|
|
}
|
|
|
|
else {
|
2017-11-11 05:05:51 +08:00
|
|
|
this.candidatesQueue.push(_candidate);
|
2017-10-13 21:50:17 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
flushCandidatesQueue () {
|
|
|
|
if (this.mediaId) {
|
|
|
|
try {
|
|
|
|
while(this.candidatesQueue.length) {
|
|
|
|
let candidate = this.candidatesQueue.shift();
|
|
|
|
this.mcs.addIceCandidate(this.mediaId, candidate);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (err) {
|
2018-01-26 12:33:40 +08:00
|
|
|
Logger.error("[video] ICE candidate could not be added to media controller.", err);
|
2017-11-11 05:05:51 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-10-13 21:50:17 +08:00
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
mediaState (event) {
|
|
|
|
let msEvent = event.event;
|
2017-11-11 09:35:01 +08:00
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
switch (event.eventTag) {
|
2017-11-11 09:35:01 +08:00
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
case "OnIceCandidate":
|
|
|
|
let candidate = msEvent.candidate;
|
2018-01-29 02:29:49 +08:00
|
|
|
Logger.debug("[video] Sending ICE candidate to user", this.id, "with candidate", candidate);
|
2017-11-25 02:59:40 +08:00
|
|
|
this.bbbGW.publish(JSON.stringify({
|
|
|
|
connectionId: this.sessionId,
|
|
|
|
type: 'video',
|
|
|
|
role: this.role,
|
|
|
|
id : 'iceCandidate',
|
|
|
|
cameraId: this.id,
|
|
|
|
candidate: candidate
|
|
|
|
}), C.FROM_VIDEO);
|
2017-11-11 05:05:51 +08:00
|
|
|
break;
|
2017-11-11 09:35:01 +08:00
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
case "MediaStateChanged":
|
|
|
|
break;
|
2017-11-11 09:35:01 +08:00
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
case "MediaFlowOutStateChange":
|
|
|
|
case "MediaFlowInStateChange":
|
2018-01-26 12:33:40 +08:00
|
|
|
Logger.info('[video] ' + msEvent.type + '[' + msEvent.state + ']' + ' for media session', event.id, "for video", this.id);
|
2017-11-11 09:35:01 +08:00
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
if (msEvent.state === 'NOT_FLOWING') {
|
2018-02-01 04:12:08 +08:00
|
|
|
Logger.warn("Setting up a timeout for " + this.sessionId + " camera " + this.id);
|
|
|
|
if (!this.notFlowingTimeout) {
|
|
|
|
this.notFlowingTimeout = setTimeout(() => {
|
2018-03-20 01:50:38 +08:00
|
|
|
|
|
|
|
if (this.shared) {
|
|
|
|
let userCamEvent =
|
|
|
|
Messaging.generateUserCamBroadcastStoppedEventMessage2x(this.meetingId, this.id, this.id);
|
|
|
|
this.bbbGW.publish(userCamEvent, C.TO_AKKA_APPS, function(error) {});
|
|
|
|
}
|
2018-02-01 04:12:08 +08:00
|
|
|
}, config.get('mediaFlowTimeoutDuration'));
|
|
|
|
}
|
2017-12-21 00:56:28 +08:00
|
|
|
}
|
2017-11-11 09:35:01 +08:00
|
|
|
else if (msEvent.state === 'FLOWING') {
|
2018-02-01 04:12:08 +08:00
|
|
|
if (this.notFlowingTimeout) {
|
|
|
|
Logger.warn("Received a media flow before stopping " + this.sessionId + " camera " + this.id);
|
|
|
|
clearTimeout(this.notFlowingTimeout);
|
|
|
|
this.notFlowingTimeout = null;
|
|
|
|
}
|
|
|
|
|
2017-11-25 02:59:40 +08:00
|
|
|
this.bbbGW.publish(JSON.stringify({
|
|
|
|
connectionId: this.sessionId,
|
|
|
|
type: 'video',
|
|
|
|
role: this.role,
|
|
|
|
id : 'playStart',
|
|
|
|
cameraId: this.id,
|
|
|
|
}), C.FROM_VIDEO);
|
2017-11-11 05:05:51 +08:00
|
|
|
}
|
2017-11-11 09:35:01 +08:00
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
break;
|
2017-11-11 09:35:01 +08:00
|
|
|
|
2018-01-26 12:33:40 +08:00
|
|
|
default: Logger.warn("[video] Unrecognized event");
|
2017-11-11 05:05:51 +08:00
|
|
|
}
|
|
|
|
}
|
2017-10-13 21:50:17 +08:00
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
async start (sdpOffer, callback) {
|
2018-01-26 12:33:40 +08:00
|
|
|
Logger.info("[video] Starting video instance for", this.id);
|
2017-11-11 05:05:51 +08:00
|
|
|
let sdpAnswer;
|
2017-10-13 21:50:17 +08:00
|
|
|
|
2018-03-14 22:38:18 +08:00
|
|
|
// Force H264
|
|
|
|
sdpOffer = h264_sdp.transform(sdpOffer);
|
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
try {
|
2017-11-18 02:57:39 +08:00
|
|
|
this.userId = await this.mcs.join(this.meetingId, 'SFU', {});
|
2018-01-26 12:33:40 +08:00
|
|
|
Logger.info("[video] MCS join for", this.id, "returned", this.userId);
|
2017-10-13 21:50:17 +08:00
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
if (this.shared) {
|
|
|
|
const ret = await this.mcs.publish(this.userId, this.meetingId, 'WebRtcEndpoint', {descriptor: sdpOffer});
|
2017-11-18 02:57:39 +08:00
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
this.mediaId = ret.sessionId;
|
|
|
|
sharedWebcams[this.id] = this.mediaId;
|
|
|
|
sdpAnswer = ret.answer;
|
|
|
|
this.flushCandidatesQueue();
|
|
|
|
this.mcs.on('MediaEvent' + this.mediaId, this.mediaState.bind(this));
|
2017-10-13 21:50:17 +08:00
|
|
|
|
2018-01-26 12:33:40 +08:00
|
|
|
Logger.info("[video] MCS publish for user", this.userId, "returned", this.mediaId);
|
2017-10-13 21:50:17 +08:00
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
return callback(null, sdpAnswer);
|
|
|
|
}
|
|
|
|
else {
|
2017-11-18 02:57:39 +08:00
|
|
|
const ret = await this.mcs.subscribe(this.userId, sharedWebcams[this.id], 'WebRtcEndpoint', {descriptor: sdpOffer});
|
2017-11-11 09:35:01 +08:00
|
|
|
|
2017-11-11 05:05:51 +08:00
|
|
|
this.mediaId = ret.sessionId;
|
|
|
|
sdpAnswer = ret.answer;
|
|
|
|
this.flushCandidatesQueue();
|
|
|
|
this.mcs.on('MediaEvent' + this.mediaId, this.mediaState.bind(this));
|
2017-10-13 21:50:17 +08:00
|
|
|
|
2018-01-26 12:33:40 +08:00
|
|
|
Logger.info("[video] MCS subscribe for user", this.userId, "returned", this.mediaId);
|
2017-11-11 05:05:51 +08:00
|
|
|
|
|
|
|
return callback(null, sdpAnswer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (err) {
|
2018-01-26 12:33:40 +08:00
|
|
|
Logger.error("[video] MCS returned error => " + err);
|
2017-11-11 05:05:51 +08:00
|
|
|
return callback(err);
|
|
|
|
}
|
2017-10-13 21:50:17 +08:00
|
|
|
};
|
|
|
|
|
2017-11-18 02:57:39 +08:00
|
|
|
async stop () {
|
2018-01-26 12:33:40 +08:00
|
|
|
Logger.info('[video] Releasing endpoints for user', this.userId, 'at room', this.meetingId);
|
2017-11-18 02:57:39 +08:00
|
|
|
|
|
|
|
try {
|
|
|
|
await this.mcs.leave(this.meetingId, this.userId);
|
|
|
|
if (this.shared) {
|
|
|
|
sharedWebcams[this.id] = null;
|
|
|
|
}
|
2018-02-01 04:12:08 +08:00
|
|
|
|
|
|
|
if (this.notFlowingTimeout) {
|
|
|
|
clearTimeout(this.notFlowingTimeout);
|
|
|
|
this.notFlowingTimeout = null;
|
|
|
|
}
|
|
|
|
|
2017-11-18 02:57:39 +08:00
|
|
|
this._candidatesQueue = null;
|
2017-12-21 02:01:27 +08:00
|
|
|
Promise.resolve();
|
2017-11-18 02:57:39 +08:00
|
|
|
}
|
|
|
|
catch (err) {
|
2017-11-25 02:59:40 +08:00
|
|
|
// TODO error handling
|
2017-12-21 02:01:27 +08:00
|
|
|
Promise.reject();
|
2017-11-18 02:57:39 +08:00
|
|
|
}
|
|
|
|
return;
|
2017-10-13 21:50:17 +08:00
|
|
|
};
|
|
|
|
};
|