Cleaning up deprecated kurento video and screensharing apps
This commit is contained in:
parent
4dc8085648
commit
f048d30ad6
1
labs/kurento-html5-video/.gitignore
vendored
1
labs/kurento-html5-video/.gitignore
vendored
@ -1 +0,0 @@
|
||||
node_modules/
|
@ -1,2 +0,0 @@
|
||||
kurentoUrl: "wss://HOST/kurento"
|
||||
acceptSelfSignedCertificate: false
|
@ -1,2 +0,0 @@
|
||||
This folder contains a dummy self-signed certificate only for demo purposses,
|
||||
**DON'T USE IT IN PRODUCTION**.
|
@ -1,19 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDBjCCAe4CCQCuf5QfyX2oDDANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
|
||||
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
|
||||
cyBQdHkgTHRkMB4XDTE0MDkyOTA5NDczNVoXDTE1MDkyOTA5NDczNVowRTELMAkG
|
||||
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
|
||||
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AMJOyOHJ+rJWJEQ7P7kKoWa31ff7hKNZxF6sYE5lFi3pBYWIY6kTN/iUaxJLROFo
|
||||
FhoC/M/STY76rIryix474v/6cRoG8N+GQBEn4IAP1UitWzVO6pVvBaIt5IKlhhfm
|
||||
YA1IMweCd03vLcaHTddNmFDBTks7QDwfenTaR5VjKYc3OtEhcG8dgLAnOjbbk2Hr
|
||||
8wter2IeNgkhya3zyoXnTLT8m8IMg2mQaJs62Xlo9gs56urvVDWG4rhdGybj1uwU
|
||||
ZiDYyP4CFCUHS6UVt12vADP8vjbwmss2ScGsIf0NjaU+MpSdEbB82z4b2NiN8Wq+
|
||||
rFA/JbvyeoWWHMoa7wkVs1MCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAYLRwV9fo
|
||||
AOhJfeK199Tv6oXoNSSSe10pVLnYxPcczCVQ4b9SomKFJFbmwtPVGi6w3m+8mV7F
|
||||
9I2WKyeBHzmzfW2utZNupVybxgzEjuFLOVytSPdsB+DcJomOi8W/Cf2Vk8Wykb/t
|
||||
Ctr1gfOcI8rwEGKxm279spBs0u1snzoLyoimbMbiXbC82j1IiN3Jus08U07m/j7N
|
||||
hRBCpeHjUHT3CRpvYyTRnt+AyBd8BiyJB7nWmcNI1DksXPfehd62MAFS9e1ZE+dH
|
||||
Aavg/U8VpS7pcCQcPJvIJ2hehrt8L6kUk3YUYqZ0OeRZK27f2R5+wFlDF33esm3N
|
||||
dCSsLJlXyqAQFg==
|
||||
-----END CERTIFICATE-----
|
@ -1,16 +0,0 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICijCCAXICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
|
||||
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBAMJOyOHJ+rJWJEQ7P7kKoWa31ff7hKNZxF6sYE5l
|
||||
Fi3pBYWIY6kTN/iUaxJLROFoFhoC/M/STY76rIryix474v/6cRoG8N+GQBEn4IAP
|
||||
1UitWzVO6pVvBaIt5IKlhhfmYA1IMweCd03vLcaHTddNmFDBTks7QDwfenTaR5Vj
|
||||
KYc3OtEhcG8dgLAnOjbbk2Hr8wter2IeNgkhya3zyoXnTLT8m8IMg2mQaJs62Xlo
|
||||
9gs56urvVDWG4rhdGybj1uwUZiDYyP4CFCUHS6UVt12vADP8vjbwmss2ScGsIf0N
|
||||
jaU+MpSdEbB82z4b2NiN8Wq+rFA/JbvyeoWWHMoa7wkVs1MCAwEAAaAAMA0GCSqG
|
||||
SIb3DQEBCwUAA4IBAQBMszYHMpklgTF/3h1zAzKXUD9NrtZp8eWhL06nwVjQX8Ai
|
||||
EaCUiW0ypstokWcH9+30chd2OD++67NbxYUEucH8HrKpOoy6gs5L/mqgQ9Npz3OT
|
||||
TB1HI4kGtpVuUQ5D7L0596tKzMX/CgW/hRcHWl+PDkwGhQs1qZcJ8QN+YP6AkRrO
|
||||
5sDdDB/BLrB9PtBQbPrYIQcHQ7ooYWz/G+goqRxzZ6rt0aU2uAB6l7c82ADLAqFJ
|
||||
qlw+xqVzEETVfqM5TXKK/wV3hgm4oSX5Q4SHLKF94ODOkWcnV4nfIKz7y+5XcQ3p
|
||||
PrGimI1br07okC5rO9cgLCR0Ks20PPFcM0FvInW/
|
||||
-----END CERTIFICATE REQUEST-----
|
@ -1,27 +0,0 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAwk7I4cn6slYkRDs/uQqhZrfV9/uEo1nEXqxgTmUWLekFhYhj
|
||||
qRM3+JRrEktE4WgWGgL8z9JNjvqsivKLHjvi//pxGgbw34ZAESfggA/VSK1bNU7q
|
||||
lW8Foi3kgqWGF+ZgDUgzB4J3Te8txodN102YUMFOSztAPB96dNpHlWMphzc60SFw
|
||||
bx2AsCc6NtuTYevzC16vYh42CSHJrfPKhedMtPybwgyDaZBomzrZeWj2Cznq6u9U
|
||||
NYbiuF0bJuPW7BRmINjI/gIUJQdLpRW3Xa8AM/y+NvCayzZJwawh/Q2NpT4ylJ0R
|
||||
sHzbPhvY2I3xar6sUD8lu/J6hZYcyhrvCRWzUwIDAQABAoIBACwt56TW3MZxqZtN
|
||||
8WYsUZheUispJ/ZQMcLo5JjOiSV1Jwk+gpJtyTse291z+bxagzP02/CQu4u32UVa
|
||||
cmE0cp+LHO4zB8964dREwdm8P91fdS6Au/uwG5LNZniCFCQZAFvkv52Ef4XbzQen
|
||||
uf4rKWerHBck6K0C5z/sZXxE6KtScE2ZLUmkhO0nkHM6MA6gFk2OMnB+oDTOWWPt
|
||||
1mlreQlzuMYG/D4axviRYrOSYCE5Qu1SOw/DEOLQqqeBjQrKtAyOlFHZsIR6lBfe
|
||||
KHMChPUcYIwaowt2DcqH/A+AFXRtaifa6DvH8Yul+2vAp47UEpaenVfM5bpN33XV
|
||||
EzerjtECgYEA+xiXzblek67iQgRpc9eHSoqs4iRLhae8s8kpAG51Jz46Je+Dmium
|
||||
XV769oiUGUxBeoUb7ryW+4MOzHJaA1BfGejQSvwLIB9e4cnikqnAArcqbcAcOCL1
|
||||
aYYDiSmSmN/AokNZlPKEBFXP9bzXrU9smQJWNTHlcRl7JXfnwF+jwNsCgYEAxhpE
|
||||
SBr9vlUVHNh/S6C5i80NIYg6jCy2FgsmuzEqmcqV0pTyzegmq8bru+QmuvoUj2o4
|
||||
nVv4J9d1fLF6ECUVk9aK8UdJOOB6hAfurOdJCArgrsY/9t4uDzXfbPCdfSNQITE0
|
||||
XgeNGQX1EzvwwkBmyZKk0kLIr3syP8ZCWfXDROkCgYBR+dF1pJMv++R6UR5sZ20P
|
||||
9P5ERj0xwXVl7MKqFWXCDhrFz9BTQPTrftrIKgbPy4mFCnf4FTHlov/t11dzxYWG
|
||||
2+9Ey8yGDDfZ1yNVZn39ZPdBJXsRCLi+XrZAzYXCyyoEz6ArdJGNKMbgH2r6dfeq
|
||||
bIzgiQ2zQvJlZSQQNiksCQKBgCgwzAmU8EXdHRttEOZXBU3HnBJhgP9PUuHGAWWY
|
||||
4/uvjhXbAiekIbRX9xt3fiQQ+HrgIfxK3F246K0TlKAR5f7IWAf7Xm+bmz+OHG4X
|
||||
vklTa6IJtpBvIwkS9PE1H75zm54gTW+GOKoK+12bm4zNZA0hIy9FPVHcvKUTpAJ8
|
||||
SdGBAoGAHLtJnB1NO4EgO6WtLQMXt7HrIbup8eZi8/82gC3422C+ooKIrYQ07qSw
|
||||
nBOO/G0OB4yd6vCE2x5+TWSSCYGgG5A8aIv5qP76RP4hovGHxG/y2tfotw5UuOrh
|
||||
nFWlTP4Urs8PeykvK9ao8r/T8BnPIC16U6ENYvAc0mRlFA2j1GA=
|
||||
-----END RSA PRIVATE KEY-----
|
@ -1,253 +0,0 @@
|
||||
// Global stuff
|
||||
var mediaPipelines = {};
|
||||
var sharedWebcams = {};
|
||||
|
||||
// TODO Later
|
||||
// var loadBalancer = require('')
|
||||
const kurento = require('kurento-client');
|
||||
const config = require('config');
|
||||
const kurentoUrl = config.get('kurentoUrl');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const inherits = require('util').inherits;
|
||||
|
||||
if (config.get('acceptSelfSignedCertificate')) {
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED=0;
|
||||
}
|
||||
|
||||
var kurentoClient = null;
|
||||
|
||||
function getKurentoClient(callback) {
|
||||
|
||||
if (kurentoClient !== null) {
|
||||
return callback(null, kurentoClient);
|
||||
}
|
||||
|
||||
kurento(kurentoUrl, function(error, _kurentoClient) {
|
||||
if (error) {
|
||||
console.log("Could not find media server at address " + kurentoUrl);
|
||||
return callback("Could not find media server at address" + kurentoUrl + ". Exiting with error " + error);
|
||||
}
|
||||
|
||||
console.log(" [server] Initiating kurento client. Connecting to: " + kurentoUrl);
|
||||
|
||||
kurentoClient = _kurentoClient;
|
||||
callback(null, kurentoClient);
|
||||
});
|
||||
}
|
||||
|
||||
function getMediaPipeline(id, callback) {
|
||||
|
||||
console.log(' [media] Creating media pipeline for ' + id);
|
||||
|
||||
if (mediaPipelines[id]) {
|
||||
|
||||
console.log(' [media] Pipeline already exists.');
|
||||
|
||||
callback(null, mediaPipelines[id]);
|
||||
|
||||
} else {
|
||||
|
||||
kurentoClient.create('MediaPipeline', function(err, pipeline) {
|
||||
|
||||
mediaPipelines[id] = pipeline;
|
||||
|
||||
return callback(err, pipeline);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function Video(_ws, _id, _shared) {
|
||||
|
||||
var ws = _ws;
|
||||
var id = _id;
|
||||
var shared = _shared;
|
||||
var webRtcEndpoint = null;
|
||||
var notFlowingTimeout = null;
|
||||
var notFlowingTimer = 15000;
|
||||
EventEmitter.call(this);
|
||||
|
||||
var candidatesQueue = [];
|
||||
|
||||
this.onIceCandidate = function(_candidate) {
|
||||
var candidate = kurento.getComplexType('IceCandidate')(_candidate);
|
||||
|
||||
if (webRtcEndpoint) {
|
||||
webRtcEndpoint.addIceCandidate(candidate);
|
||||
}
|
||||
else {
|
||||
candidatesQueue.push(candidate);
|
||||
}
|
||||
};
|
||||
|
||||
this.start = function(sdpOffer, callback) {
|
||||
var self = this;
|
||||
|
||||
getKurentoClient(function(error, kurentoClient) {
|
||||
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
getMediaPipeline(id, function(error, pipeline) {
|
||||
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
createMediaElements(pipeline, function(error, _webRtcEndpoint) {
|
||||
|
||||
if (error) {
|
||||
pipeline.release();
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
while(candidatesQueue.length) {
|
||||
var candidate = candidatesQueue.shift();
|
||||
_webRtcEndpoint.addIceCandidate(candidate);
|
||||
}
|
||||
|
||||
var flowInOut = function(event) {
|
||||
console.log(' [=] ' + event.type + ' for endpoint ' + id);
|
||||
|
||||
if (event.state === 'NOT_FLOWING' && event.type === 'MediaFlowInStateChange') {
|
||||
console.log(" [-] Media not flowing ");
|
||||
notFlowingTimeout = setTimeout(function() {
|
||||
console.log(" Timeout! sending playStop for id " + id);
|
||||
ws.sendMessage({ id : 'playStop', cameraId : id });
|
||||
}, notFlowingTimer);
|
||||
} else if (event.state === 'FLOWING' && event.type === 'MediaFlowInStateChange') {
|
||||
|
||||
console.log(" [o] Media flowing ");
|
||||
self.emit("READY");
|
||||
if (notFlowingTimeout) {
|
||||
clearTimeout(notFlowingTimeout);
|
||||
notFlowingTimeout = null;
|
||||
} else{
|
||||
ws.sendMessage({ id : 'playStart', cameraId : id });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_webRtcEndpoint.on('MediaFlowInStateChange', flowInOut);
|
||||
_webRtcEndpoint.on('MediaFlowOutStateChange', flowInOut);
|
||||
|
||||
_webRtcEndpoint.on('MediaStateChanged', (e) => { self.emit("READY"); console.log(id); console.log(e)} );
|
||||
|
||||
connectMediaElements(_webRtcEndpoint, function(error) {
|
||||
|
||||
if (error) {
|
||||
pipeline.release();
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
// It's a user sharing a webcam
|
||||
if (shared) {
|
||||
sharedWebcams[id] = _webRtcEndpoint;
|
||||
}
|
||||
|
||||
// Store our endpoint
|
||||
webRtcEndpoint = _webRtcEndpoint;
|
||||
|
||||
_webRtcEndpoint.on('OnIceCandidate', function(event) {
|
||||
var candidate = kurento.getComplexType('IceCandidate')(event.candidate);
|
||||
ws.sendMessage({ id : 'iceCandidate', cameraId: id, candidate : candidate });
|
||||
});
|
||||
|
||||
_webRtcEndpoint.processOffer(sdpOffer, function(error, sdpAnswer) {
|
||||
if (error) {
|
||||
pipeline.release();
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
return callback(null, sdpAnswer);
|
||||
});
|
||||
|
||||
_webRtcEndpoint.gatherCandidates(function(error) {
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var createMediaElements = function(pipeline, callback) {
|
||||
|
||||
console.log(" [webrtc] Creating webrtc endpoint");
|
||||
|
||||
pipeline.create('WebRtcEndpoint', function(error, _webRtcEndpoint) {
|
||||
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
webRtcEndpoint = _webRtcEndpoint;
|
||||
console.log(" [webrtc] Created webRtcEndpoint => " + webRtcEndpoint.id);
|
||||
|
||||
return callback(null, _webRtcEndpoint);
|
||||
});
|
||||
};
|
||||
|
||||
var connectMediaElements = function(webRtcEndpoint, callback) {
|
||||
|
||||
// User is sharing webcam (sendOnly connection from the client)
|
||||
if (shared) {
|
||||
console.log(" [webrtc] User has shared the webcam, no connection needed");
|
||||
// Dont connect this, just create the webrtcEndpoint
|
||||
// webRtcEndpoint.connect(webRtcEndpoint, callback);
|
||||
|
||||
return callback(null);
|
||||
} else {
|
||||
|
||||
console.log(" [webrtc] User wants to receive webcam ");
|
||||
|
||||
if (sharedWebcams[id]) {
|
||||
var wRtc = sharedWebcams[id];
|
||||
wRtc.connect(webRtcEndpoint, function(error) {
|
||||
console.log(" [webrtc] Connected " + wRtc.id + " => " + webRtcEndpoint.id);
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
return callback(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.stop = function() {
|
||||
|
||||
console.log(' [stop] Releasing webrtc endpoint for ' + id);
|
||||
|
||||
if (webRtcEndpoint) {
|
||||
webRtcEndpoint.release();
|
||||
webRtcEndpoint = null;
|
||||
} else {
|
||||
console.log(" [webRtcEndpoint] PLEASE DONT TRY STOPPING THINGS TWICE");
|
||||
}
|
||||
|
||||
if (shared) {
|
||||
console.log(' [stop] Webcam is shared, releasing ' + id);
|
||||
|
||||
if (mediaPipelines[id]) {
|
||||
console.log( '[stop] Releasing pipeline ' + id);
|
||||
mediaPipelines[id].release();
|
||||
} else {
|
||||
console.log(" [mediaPipeline] PLEASE DONT TRY STOPPING THINGS TWICE");
|
||||
}
|
||||
|
||||
delete mediaPipelines[id];
|
||||
delete sharedWebcams[id];
|
||||
}
|
||||
|
||||
delete candidatesQueue;
|
||||
};
|
||||
|
||||
return this;
|
||||
};
|
||||
inherits(Video, EventEmitter);
|
||||
|
||||
module.exports = Video;
|
@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Simple wrapper around the ws library
|
||||
*
|
||||
*/
|
||||
|
||||
var ws = require('ws');
|
||||
|
||||
ws.prototype.sendMessage = function(json) {
|
||||
|
||||
return this.send(JSON.stringify(json), function(error) {
|
||||
if(error)
|
||||
console.log(' [server] Websocket error "' + error + '" on message "' + json.id + '"');
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
module.exports = ws;
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "bbb-html5-video-kurento-bridge",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "nodejs server.js",
|
||||
"postinstall": "npm start"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-parser": "^1.3.5",
|
||||
"express": "~4.12.4",
|
||||
"express-session": "~1.10.3",
|
||||
"ws": "~1.0.1",
|
||||
"kurento-client": "6.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"config": "^1.26.1",
|
||||
"js-yaml": "^3.8.3"
|
||||
}
|
||||
}
|
@ -1,194 +0,0 @@
|
||||
/*
|
||||
* Lucas Fialho Zawacki
|
||||
* (C) Copyright 2017 Bigbluebutton
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var cookieParser = require('cookie-parser')
|
||||
var express = require('express');
|
||||
var session = require('express-session')
|
||||
var ws = require('./lib/websocket');
|
||||
var http = require('http');
|
||||
var fs = require('fs');
|
||||
|
||||
var Video = require('./lib/video');
|
||||
|
||||
// Global variables
|
||||
var app = express();
|
||||
var sessions = {};
|
||||
|
||||
/*
|
||||
* Management of sessions
|
||||
*/
|
||||
app.use(cookieParser());
|
||||
|
||||
var sessionHandler = session({
|
||||
secret : 'Shawarma', rolling : true, resave : true, saveUninitialized : true
|
||||
});
|
||||
|
||||
app.use(sessionHandler);
|
||||
|
||||
/*
|
||||
* Server startup
|
||||
*/
|
||||
var server = http.createServer(app).listen(3002, function() {
|
||||
console.log(' [*] Running bbb-html5 kurento video service.');
|
||||
});
|
||||
|
||||
var wss = new ws.Server({
|
||||
server : server,
|
||||
path : '/html5video'
|
||||
});
|
||||
|
||||
var clientId = 0;
|
||||
|
||||
wss.on('connection', function(ws) {
|
||||
var sessionId;
|
||||
var request = ws.upgradeReq;
|
||||
var response = {
|
||||
writeHead : {}
|
||||
};
|
||||
|
||||
sessionHandler(request, response, function(err) {
|
||||
sessionId = request.session.id + "_" + clientId++;
|
||||
|
||||
if (!sessions[sessionId]) {
|
||||
sessions[sessionId] = {videos: {}, iceQueue: {}};
|
||||
}
|
||||
|
||||
console.log('Connection received with sessionId ' + sessionId);
|
||||
});
|
||||
|
||||
ws.on('error', function(error) {
|
||||
console.log('Connection ' + sessionId + ' error');
|
||||
// stop(sessionId);
|
||||
});
|
||||
|
||||
ws.on('close', function() {
|
||||
console.log('Connection ' + sessionId + ' closed');
|
||||
stopSession(sessionId);
|
||||
});
|
||||
|
||||
ws.on('message', function(_message) {
|
||||
var message = JSON.parse(_message);
|
||||
|
||||
var video;
|
||||
if (message.cameraId && sessions[sessionId].videos[message.cameraId]) {
|
||||
video = sessions[sessionId].videos[message.cameraId];
|
||||
}
|
||||
|
||||
switch (message.id) {
|
||||
|
||||
case 'start':
|
||||
|
||||
console.log('[' + message.id + '] connection ' + sessionId);
|
||||
if (video) {
|
||||
video.once('READY', function() {
|
||||
console.log("Video is ready");
|
||||
startVideo(message, ws, sessionId);
|
||||
});
|
||||
}
|
||||
else {
|
||||
startVideo(message, ws, sessionId);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'stop':
|
||||
|
||||
console.log('[' + message.id + '] connection ' + sessionId);
|
||||
|
||||
if (video) {
|
||||
video.stop(sessionId);
|
||||
delete sessions[sessionId].videos[message.cameraId];
|
||||
} else {
|
||||
console.log(" [stop] Why is there no video on STOP?");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'onIceCandidate':
|
||||
onIceCandidate(sessionId, message.cameraId, message.candidate);
|
||||
break;
|
||||
|
||||
default:
|
||||
ws.sendMessage({ id : 'error', message : 'Invalid message ' + message });
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
var stopSession = function(sessionId) {
|
||||
|
||||
if(typeof sessions[sessionId] === 'undefined') {
|
||||
console.log(' [>] Session ' + sessionId + ' was already terminated');
|
||||
return;
|
||||
}
|
||||
console.log(' [>] Stopping session ' + sessionId);
|
||||
|
||||
var videoIds = Object.keys(sessions[sessionId].videos);
|
||||
|
||||
for (var i = 0; i < videoIds.length; i++) {
|
||||
var video = sessions[sessionId].videos[videoIds[i]];
|
||||
if (video){
|
||||
console.log(video);
|
||||
console.log(videoIds[i]);
|
||||
video.stop();
|
||||
} else {
|
||||
console.log("Stop session but video was null");
|
||||
}
|
||||
|
||||
delete sessions[sessionId].videos[videoIds[i]];
|
||||
}
|
||||
|
||||
delete sessions[sessionId];
|
||||
}
|
||||
|
||||
var stopAll = function() {
|
||||
|
||||
console.log('\n [x] Stopping everything! ');
|
||||
|
||||
var sessionIds = Object.keys(sessions);
|
||||
|
||||
for (var i = 0; i < sessionIds.length; i++) {
|
||||
|
||||
stopSession(sessionIds[i]);
|
||||
}
|
||||
|
||||
setTimeout(process.exit, 1000);
|
||||
}
|
||||
|
||||
function onIceCandidate(sessionId, id, candidate) {
|
||||
if (sessions[sessionId][id]) {
|
||||
sessions[sessionId][id].onIceCandidate(candidate);
|
||||
} else {
|
||||
sessions[sessionId].iceQueue = sessions[sessionId].iceQueue || {};
|
||||
sessions[sessionId].iceQueue[id] = sessions[sessionId].iceQueue[id] || [];
|
||||
sessions[sessionId].iceQueue[id].push(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
function startVideo(message, ws, sessionId) {
|
||||
console.log('[' + message.id + '] connection ' + sessionId);
|
||||
let video = new Video(ws, message.cameraId, message.cameraShared);
|
||||
sessions[sessionId].videos[message.cameraId] = video;
|
||||
|
||||
video.start(message.sdpOffer, function(error, sdpAnswer) {
|
||||
if (error) {
|
||||
return ws.sendMessage({id : 'error', message : error });
|
||||
}
|
||||
|
||||
// Get ice candidates that arrived before video was created
|
||||
if (sessions[sessionId].iceQueue) {
|
||||
var queue = sessions[sessionId].iceQueue[message.cameraId];
|
||||
while (queue && queue.length > 0) {
|
||||
video.onIceCandidate(queue.pop());
|
||||
}
|
||||
}
|
||||
ws.sendMessage({id : 'startResponse', cameraId: message.cameraId, sdpAnswer : sdpAnswer});
|
||||
});
|
||||
}
|
||||
|
||||
process.on('SIGTERM', stopAll);
|
||||
process.on('SIGINT', stopAll);
|
1
labs/kurento-screenshare/.gitignore
vendored
1
labs/kurento-screenshare/.gitignore
vendored
@ -1 +0,0 @@
|
||||
node_modules/
|
@ -1,8 +0,0 @@
|
||||
kurentoUrl: "KURENTOURL"
|
||||
kurentoIp: "KURENTOIP"
|
||||
localIpAddress: "HOST"
|
||||
acceptSelfSignedCertificate: false
|
||||
redisHost : "127.0.0.1"
|
||||
redisPort : "6379"
|
||||
minVideoPort: 30000
|
||||
maxVideoPort: 33000
|
@ -1 +0,0 @@
|
||||
node --inspect --debug-brk server.js
|
@ -1,2 +0,0 @@
|
||||
This folder contains a dummy self-signed certificate only for demo purposses,
|
||||
**DON'T USE IT IN PRODUCTION**.
|
@ -1,19 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDBjCCAe4CCQCuf5QfyX2oDDANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
|
||||
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
|
||||
cyBQdHkgTHRkMB4XDTE0MDkyOTA5NDczNVoXDTE1MDkyOTA5NDczNVowRTELMAkG
|
||||
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
|
||||
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AMJOyOHJ+rJWJEQ7P7kKoWa31ff7hKNZxF6sYE5lFi3pBYWIY6kTN/iUaxJLROFo
|
||||
FhoC/M/STY76rIryix474v/6cRoG8N+GQBEn4IAP1UitWzVO6pVvBaIt5IKlhhfm
|
||||
YA1IMweCd03vLcaHTddNmFDBTks7QDwfenTaR5VjKYc3OtEhcG8dgLAnOjbbk2Hr
|
||||
8wter2IeNgkhya3zyoXnTLT8m8IMg2mQaJs62Xlo9gs56urvVDWG4rhdGybj1uwU
|
||||
ZiDYyP4CFCUHS6UVt12vADP8vjbwmss2ScGsIf0NjaU+MpSdEbB82z4b2NiN8Wq+
|
||||
rFA/JbvyeoWWHMoa7wkVs1MCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAYLRwV9fo
|
||||
AOhJfeK199Tv6oXoNSSSe10pVLnYxPcczCVQ4b9SomKFJFbmwtPVGi6w3m+8mV7F
|
||||
9I2WKyeBHzmzfW2utZNupVybxgzEjuFLOVytSPdsB+DcJomOi8W/Cf2Vk8Wykb/t
|
||||
Ctr1gfOcI8rwEGKxm279spBs0u1snzoLyoimbMbiXbC82j1IiN3Jus08U07m/j7N
|
||||
hRBCpeHjUHT3CRpvYyTRnt+AyBd8BiyJB7nWmcNI1DksXPfehd62MAFS9e1ZE+dH
|
||||
Aavg/U8VpS7pcCQcPJvIJ2hehrt8L6kUk3YUYqZ0OeRZK27f2R5+wFlDF33esm3N
|
||||
dCSsLJlXyqAQFg==
|
||||
-----END CERTIFICATE-----
|
@ -1,16 +0,0 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICijCCAXICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
|
||||
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBAMJOyOHJ+rJWJEQ7P7kKoWa31ff7hKNZxF6sYE5l
|
||||
Fi3pBYWIY6kTN/iUaxJLROFoFhoC/M/STY76rIryix474v/6cRoG8N+GQBEn4IAP
|
||||
1UitWzVO6pVvBaIt5IKlhhfmYA1IMweCd03vLcaHTddNmFDBTks7QDwfenTaR5Vj
|
||||
KYc3OtEhcG8dgLAnOjbbk2Hr8wter2IeNgkhya3zyoXnTLT8m8IMg2mQaJs62Xlo
|
||||
9gs56urvVDWG4rhdGybj1uwUZiDYyP4CFCUHS6UVt12vADP8vjbwmss2ScGsIf0N
|
||||
jaU+MpSdEbB82z4b2NiN8Wq+rFA/JbvyeoWWHMoa7wkVs1MCAwEAAaAAMA0GCSqG
|
||||
SIb3DQEBCwUAA4IBAQBMszYHMpklgTF/3h1zAzKXUD9NrtZp8eWhL06nwVjQX8Ai
|
||||
EaCUiW0ypstokWcH9+30chd2OD++67NbxYUEucH8HrKpOoy6gs5L/mqgQ9Npz3OT
|
||||
TB1HI4kGtpVuUQ5D7L0596tKzMX/CgW/hRcHWl+PDkwGhQs1qZcJ8QN+YP6AkRrO
|
||||
5sDdDB/BLrB9PtBQbPrYIQcHQ7ooYWz/G+goqRxzZ6rt0aU2uAB6l7c82ADLAqFJ
|
||||
qlw+xqVzEETVfqM5TXKK/wV3hgm4oSX5Q4SHLKF94ODOkWcnV4nfIKz7y+5XcQ3p
|
||||
PrGimI1br07okC5rO9cgLCR0Ks20PPFcM0FvInW/
|
||||
-----END CERTIFICATE REQUEST-----
|
@ -1,27 +0,0 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAwk7I4cn6slYkRDs/uQqhZrfV9/uEo1nEXqxgTmUWLekFhYhj
|
||||
qRM3+JRrEktE4WgWGgL8z9JNjvqsivKLHjvi//pxGgbw34ZAESfggA/VSK1bNU7q
|
||||
lW8Foi3kgqWGF+ZgDUgzB4J3Te8txodN102YUMFOSztAPB96dNpHlWMphzc60SFw
|
||||
bx2AsCc6NtuTYevzC16vYh42CSHJrfPKhedMtPybwgyDaZBomzrZeWj2Cznq6u9U
|
||||
NYbiuF0bJuPW7BRmINjI/gIUJQdLpRW3Xa8AM/y+NvCayzZJwawh/Q2NpT4ylJ0R
|
||||
sHzbPhvY2I3xar6sUD8lu/J6hZYcyhrvCRWzUwIDAQABAoIBACwt56TW3MZxqZtN
|
||||
8WYsUZheUispJ/ZQMcLo5JjOiSV1Jwk+gpJtyTse291z+bxagzP02/CQu4u32UVa
|
||||
cmE0cp+LHO4zB8964dREwdm8P91fdS6Au/uwG5LNZniCFCQZAFvkv52Ef4XbzQen
|
||||
uf4rKWerHBck6K0C5z/sZXxE6KtScE2ZLUmkhO0nkHM6MA6gFk2OMnB+oDTOWWPt
|
||||
1mlreQlzuMYG/D4axviRYrOSYCE5Qu1SOw/DEOLQqqeBjQrKtAyOlFHZsIR6lBfe
|
||||
KHMChPUcYIwaowt2DcqH/A+AFXRtaifa6DvH8Yul+2vAp47UEpaenVfM5bpN33XV
|
||||
EzerjtECgYEA+xiXzblek67iQgRpc9eHSoqs4iRLhae8s8kpAG51Jz46Je+Dmium
|
||||
XV769oiUGUxBeoUb7ryW+4MOzHJaA1BfGejQSvwLIB9e4cnikqnAArcqbcAcOCL1
|
||||
aYYDiSmSmN/AokNZlPKEBFXP9bzXrU9smQJWNTHlcRl7JXfnwF+jwNsCgYEAxhpE
|
||||
SBr9vlUVHNh/S6C5i80NIYg6jCy2FgsmuzEqmcqV0pTyzegmq8bru+QmuvoUj2o4
|
||||
nVv4J9d1fLF6ECUVk9aK8UdJOOB6hAfurOdJCArgrsY/9t4uDzXfbPCdfSNQITE0
|
||||
XgeNGQX1EzvwwkBmyZKk0kLIr3syP8ZCWfXDROkCgYBR+dF1pJMv++R6UR5sZ20P
|
||||
9P5ERj0xwXVl7MKqFWXCDhrFz9BTQPTrftrIKgbPy4mFCnf4FTHlov/t11dzxYWG
|
||||
2+9Ey8yGDDfZ1yNVZn39ZPdBJXsRCLi+XrZAzYXCyyoEz6ArdJGNKMbgH2r6dfeq
|
||||
bIzgiQ2zQvJlZSQQNiksCQKBgCgwzAmU8EXdHRttEOZXBU3HnBJhgP9PUuHGAWWY
|
||||
4/uvjhXbAiekIbRX9xt3fiQQ+HrgIfxK3F246K0TlKAR5f7IWAf7Xm+bmz+OHG4X
|
||||
vklTa6IJtpBvIwkS9PE1H75zm54gTW+GOKoK+12bm4zNZA0hIy9FPVHcvKUTpAJ8
|
||||
SdGBAoGAHLtJnB1NO4EgO6WtLQMXt7HrIbup8eZi8/82gC3422C+ooKIrYQ07qSw
|
||||
nBOO/G0OB4yd6vCE2x5+TWSSCYGgG5A8aIv5qP76RP4hovGHxG/y2tfotw5UuOrh
|
||||
nFWlTP4Urs8PeykvK9ao8r/T8BnPIC16U6ENYvAc0mRlFA2j1GA=
|
||||
-----END RSA PRIVATE KEY-----
|
@ -1,249 +0,0 @@
|
||||
/*
|
||||
* Lucas Fialho Zawacki
|
||||
* Paulo Renato Lanzarin
|
||||
* (C) Copyright 2017 Bigbluebutton
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const cookieParser = require('cookie-parser')
|
||||
const express = require('express');
|
||||
const session = require('express-session')
|
||||
const wsModule = require('./websocket');
|
||||
const http = require('http');
|
||||
const fs = require('fs');
|
||||
const BigBlueButtonGW = require('./bbb/pubsub/bbb-gw');
|
||||
const MediaController = require('./media-controller');
|
||||
var Screenshare = require('./screenshare');
|
||||
var C = require('./bbb/messages/Constants');
|
||||
|
||||
// Global variables
|
||||
|
||||
module.exports = class ConnectionManager {
|
||||
|
||||
constructor (settings, logger) {
|
||||
this._logger = logger;
|
||||
this._clientId = 0;
|
||||
this._app = express();
|
||||
|
||||
this._sessions = {};
|
||||
this._screenshareSessions = {};
|
||||
|
||||
this._setupExpressSession();
|
||||
this._setupHttpServer();
|
||||
}
|
||||
|
||||
_setupExpressSession() {
|
||||
this._app.use(cookieParser());
|
||||
|
||||
this._sessionHandler = session({
|
||||
secret : 'Shawarma', rolling : true, resave : true, saveUninitialized : true
|
||||
});
|
||||
|
||||
this._app.use(this._sessionHandler);
|
||||
}
|
||||
|
||||
_setupHttpServer() {
|
||||
let self = this;
|
||||
/*
|
||||
* Server startup
|
||||
*/
|
||||
this._httpServer = http.createServer(this._app).listen(3008, function() {
|
||||
console.log(' [*] Running node-apps connection manager.');
|
||||
});
|
||||
|
||||
/*
|
||||
* Management of sessions
|
||||
*/
|
||||
this._wss = new wsModule.Server({
|
||||
server : this._httpServer,
|
||||
path : '/kurento-screenshare'
|
||||
});
|
||||
|
||||
|
||||
// TODO isolate this
|
||||
this._bbbGW = new BigBlueButtonGW();
|
||||
|
||||
this._bbbGW.addSubscribeChannel(C.FROM_BBB_TRANSCODE_SYSTEM_CHAN, function(error, redisWrapper) {
|
||||
if(error) {
|
||||
console.log(' Could not connect to transcoder redis channel, finishing app...');
|
||||
self._stopAll();
|
||||
}
|
||||
console.log(' [server] Successfully subscribed to redis channel');
|
||||
});
|
||||
|
||||
|
||||
this._wss.on('connection', self._onNewConnection.bind(self));
|
||||
}
|
||||
|
||||
_onNewConnection(webSocket) {
|
||||
let self = this;
|
||||
let connectionId;
|
||||
let request = webSocket.upgradeReq;
|
||||
let sessionId;
|
||||
let callerName;
|
||||
let response = {
|
||||
writeHead : {}
|
||||
};
|
||||
|
||||
this._sessionHandler(request, response, function(err) {
|
||||
connectionId = request.session.id + "_" + self._clientId++;
|
||||
console.log('Connection received with connectionId ' + connectionId);
|
||||
});
|
||||
|
||||
webSocket.on('error', function(error) {
|
||||
console.log('Connection ' + connectionId + ' error');
|
||||
self._stopSession(sessionId);
|
||||
});
|
||||
|
||||
webSocket.on('close', function() {
|
||||
console.log('Connection ' + connectionId + ' closed');
|
||||
console.log(webSocket.presenter);
|
||||
|
||||
if (webSocket.presenter && self._screenshareSessions[sessionId]) { // if presenter // FIXME (this conditional was added to prevent screenshare stop when an iOS user quits)
|
||||
console.log(" [CM] Stopping presenter " + sessionId);
|
||||
self._stopSession(sessionId);
|
||||
}
|
||||
if (webSocket.viewer && typeof webSocket.session !== 'undefined') {
|
||||
console.log(" [CM] Stopping viewer " + webSocket.viewerId);
|
||||
webSocket.session._stopViewer(webSocket.viewerId);
|
||||
}
|
||||
});
|
||||
|
||||
webSocket.on('message', function(_message) {
|
||||
let message = JSON.parse(_message);
|
||||
let session;
|
||||
// The sessionId is voiceBridge for screensharing sessions
|
||||
sessionId = message.voiceBridge;
|
||||
if(self._screenshareSessions[sessionId]) {
|
||||
session = self._screenshareSessions[sessionId];
|
||||
webSocket.session = session;
|
||||
}
|
||||
|
||||
switch (message.id) {
|
||||
|
||||
case 'presenter':
|
||||
|
||||
// Checking if there's already a Screenshare session started
|
||||
// because we shouldn't overwrite it
|
||||
webSocket.presenter = true;
|
||||
|
||||
if (!self._screenshareSessions[message.voiceBridge]) {
|
||||
self._screenshareSessions[message.voiceBridge] = {}
|
||||
self._screenshareSessions[message.voiceBridge] = session;
|
||||
}
|
||||
|
||||
//session.on('message', self._assembleSessionMessage.bind(self));
|
||||
if(session) {
|
||||
break;
|
||||
}
|
||||
|
||||
session = new Screenshare(webSocket, connectionId, self._bbbGW,
|
||||
sessionId, message.callerName, message.vh, message.vw,
|
||||
message.internalMeetingId);
|
||||
|
||||
self._screenshareSessions[sessionId] = {}
|
||||
self._screenshareSessions[sessionId] = session;
|
||||
|
||||
// starts presenter by sending sessionID, websocket and sdpoffer
|
||||
session._startPresenter(connectionId, webSocket, message.sdpOffer, function(error, sdpAnswer) {
|
||||
console.log(" Started presenter " + connectionId);
|
||||
if (error) {
|
||||
return webSocket.send(JSON.stringify({
|
||||
id : 'presenterResponse',
|
||||
response : 'rejected',
|
||||
message : error
|
||||
}));
|
||||
}
|
||||
|
||||
webSocket.send(JSON.stringify({
|
||||
id : 'presenterResponse',
|
||||
response : 'accepted',
|
||||
sdpAnswer : sdpAnswer
|
||||
}));
|
||||
console.log(" [websocket] Sending presenterResponse \n" + sdpAnswer);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'viewer':
|
||||
console.log("[viewer] Session output \n " + session);
|
||||
|
||||
webSocket.viewer = true;
|
||||
webSocket.viewerId = message.callerName;
|
||||
|
||||
if (message.sdpOffer && message.voiceBridge) {
|
||||
if (session) {
|
||||
session._startViewer(webSocket, message.voiceBridge, message.sdpOffer, message.callerName, self._screenshareSessions[message.voiceBridge]._presenterEndpoint);
|
||||
} else {
|
||||
webSocket.sendMessage("voiceBridge not recognized");
|
||||
webSocket.sendMessage(Object.keys(self._screenshareSessions));
|
||||
webSocket.sendMessage(message.voiceBridge);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'stop':
|
||||
console.log('[' + message.id + '] connection ' + connectionId);
|
||||
|
||||
if (session) {
|
||||
session._stop(sessionId);
|
||||
} else {
|
||||
console.log(" [stop] Why is there no session on STOP?");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'onIceCandidate':
|
||||
if (session) {
|
||||
console.log(" [CM] What the fluff is happening");
|
||||
session._onIceCandidate(message.candidate);
|
||||
} else {
|
||||
console.log(" [iceCandidate] Why is there no session on ICE CANDIDATE?");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ping':
|
||||
webSocket.send(JSON.stringify({
|
||||
id : 'pong',
|
||||
response : 'accepted'
|
||||
}));
|
||||
break;
|
||||
|
||||
|
||||
case 'viewerIceCandidate':
|
||||
console.log("[viewerIceCandidate] Session output => " + session);
|
||||
if (session) {
|
||||
session._onViewerIceCandidate(message.candidate, message.callerName);
|
||||
} else {
|
||||
console.log("[iceCandidate] Why is there no session on ICE CANDIDATE?");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
webSocket.sendMessage({ id : 'error', message : 'Invalid message ' + message });
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_stopSession(sessionId) {
|
||||
console.log(' [>] Stopping session ' + sessionId);
|
||||
let session = this._screenshareSessions[sessionId];
|
||||
if(typeof session !== 'undefined' && typeof session._stop === 'function') {
|
||||
session._stop();
|
||||
}
|
||||
|
||||
delete this._screenshareSessions[sessionId];
|
||||
}
|
||||
|
||||
_stopAll() {
|
||||
console.log('\n [x] Stopping everything! ');
|
||||
let sessionIds = Object.keys(this._screenshareSessions);
|
||||
|
||||
for (let i = 0; i < sessionIds.length; i++) {
|
||||
this._stopSession(sessionIds[i]);
|
||||
}
|
||||
|
||||
setTimeout(process.exit, 1000);
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
"use strict";
|
||||
/**
|
||||
* @classdesc
|
||||
* Message constants for the communication with BigBlueButton
|
||||
* @constructor
|
||||
*/
|
||||
function Constants () {
|
||||
return {
|
||||
// Media elements
|
||||
WebRTC: "WebRtcEndpoint",
|
||||
RTP: "RtpEndpoint",
|
||||
AUDIO: "AUDIO",
|
||||
VIDEO: "VIDEO",
|
||||
ALL: "ALL",
|
||||
|
||||
// Redis channels
|
||||
FROM_BBB_TRANSCODE_SYSTEM_CHAN : "bigbluebutton:from-bbb-transcode:system",
|
||||
FROM_VOICE_CONF_SYSTEM_CHAN: "from-voice-conf-redis-channel",
|
||||
TO_BBB_TRANSCODE_SYSTEM_CHAN: "bigbluebutton:to-bbb-transcode:system",
|
||||
|
||||
// RedisWrapper events
|
||||
REDIS_MESSAGE : "redis_message",
|
||||
|
||||
// Message identifiers 1x
|
||||
START_TRANSCODER_REQUEST: "start_transcoder_request_message",
|
||||
START_TRANSCODER_REPLY: "start_transcoder_reply_message",
|
||||
STOP_TRANSCODER_REQUEST: "stop_transcoder_request_message",
|
||||
STOP_TRANSCODER_REPLY: "stop_transcoder_reply_message",
|
||||
DESKSHARE_RTMP_BROADCAST_STARTED: "deskshare_rtmp_broadcast_started_message",
|
||||
DESKSHARE_RTMP_BROADCAST_STOPPED: "deskshare_rtmp_broadcast_stopped_message",
|
||||
|
||||
//Message identifiers 2x
|
||||
SCREENSHARE_RTMP_BROADCAST_STARTED_2x: "ScreenshareRtmpBroadcastStartedVoiceConfEvtMsg",
|
||||
SCREENSHARE_RTMP_BROADCAST_STOPPED_2x: "ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsg",
|
||||
START_TRANSCODER_REQ_2x: "StartTranscoderSysReqMsg",
|
||||
START_TRANSCODER_RESP_2x: "StartTranscoderSysRespMsg",
|
||||
STOP_TRANSCODER_REQ_2x: "StopTranscoderSysReqMsg",
|
||||
STOP_TRANSCODER_RESP_2x: "StopTranscoderSysRespMsg",
|
||||
|
||||
// Redis messages fields
|
||||
// Transcoder 1x
|
||||
USER_ID : "user_id",
|
||||
OPTIONS: "options",
|
||||
VOICE_CONF_ID : "voice_conf_id",
|
||||
TRANSCODER_ID : "transcoder_id",
|
||||
|
||||
// Transcoder 2x
|
||||
USER_ID_2x : "userId",
|
||||
TRANSCODER_ID_2x : "transcoderId",
|
||||
MEETING_ID_2x: "meetingId",
|
||||
|
||||
// Screenshare 2x
|
||||
CONFERENCE_NAME: "voiceConf",
|
||||
SCREENSHARE_CONF: "screenshareConf",
|
||||
STREAM_URL: "stream",
|
||||
TIMESTAMP: "timestamp",
|
||||
VIDEO_WIDTH: "vidWidth",
|
||||
VIDEO_HEIGHT: "vidHeight",
|
||||
|
||||
// RTP params
|
||||
MEETING_ID : "meeting_id",
|
||||
VOICE_CONF : "voice_conf",
|
||||
KURENTO_ENDPOINT_ID : "kurento_endpoint_id",
|
||||
PARAMS : "params",
|
||||
MEDIA_DESCRIPTION: "media_description",
|
||||
LOCAL_IP_ADDRESS: "local_ip_address",
|
||||
LOCAL_VIDEO_PORT: "local_video_port",
|
||||
DESTINATION_IP_ADDRESS : "destination_ip_address",
|
||||
DESTINATION_VIDEO_PORT : "destination_video_port",
|
||||
REMOTE_VIDEO_PORT : "remote_video_port",
|
||||
CODEC_NAME: "codec_name",
|
||||
CODEC_ID: "codec_id",
|
||||
CODEC_RATE: "codec_rate",
|
||||
RTP_PROFILE: "rtp_profile",
|
||||
SEND_RECEIVE: "send_receive",
|
||||
FRAME_RATE: "frame_rate",
|
||||
INPUT: "input",
|
||||
KURENTO_TOKEN : "kurento_token",
|
||||
SCREENSHARE: "deskShare",
|
||||
STREAM_TYPE: "stream_type",
|
||||
STREAM_TYPE_SCREENSHARE: "stream_type_deskshare",
|
||||
STREAM_TYPE_VIDEO: "stream_type_video",
|
||||
RTP_TO_RTMP: "transcode_rtp_to_rtmp",
|
||||
TRANSCODER_CODEC: "codec",
|
||||
TRANSCODER_TYPE: "transcoder_type",
|
||||
CALLERNAME: "callername"
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Constants();
|
||||
|
@ -1,69 +0,0 @@
|
||||
var Constants = require('./Constants.js');
|
||||
|
||||
// Messages
|
||||
|
||||
var OutMessage = require('./OutMessage.js');
|
||||
|
||||
var StartTranscoderRequestMessage =
|
||||
require('./transcode/StartTranscoderRequestMessage.js')(Constants);
|
||||
var StopTranscoderRequestMessage =
|
||||
require('./transcode/StopTranscoderRequestMessage.js')(Constants);
|
||||
var StartTranscoderSysReqMsg =
|
||||
require('./transcode/StartTranscoderSysReqMsg.js')();
|
||||
var StopTranscoderSysReqMsg =
|
||||
require('./transcode/StopTranscoderSysReqMsg.js')();
|
||||
var DeskShareRTMPBroadcastStartedEventMessage =
|
||||
require('./screenshare/DeskShareRTMPBroadcastStartedEventMessage.js')(Constants);
|
||||
var DeskShareRTMPBroadcastStoppedEventMessage =
|
||||
require('./screenshare/DeskShareRTMPBroadcastStoppedEventMessage.js')(Constants);
|
||||
var ScreenshareRTMPBroadcastStartedEventMessage2x =
|
||||
require('./screenshare/ScreenshareRTMPBroadcastStartedEventMessage2x.js')(Constants);
|
||||
var ScreenshareRTMPBroadcastStoppedEventMessage2x =
|
||||
require('./screenshare/ScreenshareRTMPBroadcastStoppedEventMessage2x.js')(Constants);
|
||||
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* Messaging utils to assemble JSON/Redis BigBlueButton messages
|
||||
* @constructor
|
||||
*/
|
||||
function Messaging() {}
|
||||
|
||||
Messaging.prototype.generateStartTranscoderRequestMessage =
|
||||
function(meetingId, transcoderId, params) {
|
||||
var statrm = new StartTranscoderSysReqMsg(meetingId, transcoderId, params);
|
||||
return statrm.toJson();
|
||||
}
|
||||
|
||||
Messaging.prototype.generateStopTranscoderRequestMessage =
|
||||
function(meetingId, transcoderId) {
|
||||
var stotrm = new StopTranscoderSysReqMsg(meetingId, transcoderId);
|
||||
return stotrm.toJson();
|
||||
}
|
||||
|
||||
Messaging.prototype.generateDeskShareRTMPBroadcastStartedEvent =
|
||||
function(conferenceName, streamUrl, vw, vh, timestamp) {
|
||||
var stadrbem = new DeskShareRTMPBroadcastStartedEventMessage(conferenceName, streamUrl, vw, vh, timestamp);
|
||||
return stadrbem.toJson();
|
||||
}
|
||||
|
||||
Messaging.prototype.generateDeskShareRTMPBroadcastStoppedEvent =
|
||||
function(conferenceName, streamUrl, vw, vh, timestamp) {
|
||||
var stodrbem = new DeskShareRTMPBroadcastStoppedEventMessage(conferenceName, streamUrl, vw, vh, timestamp);
|
||||
return stodrbem.toJson();
|
||||
}
|
||||
|
||||
Messaging.prototype.generateScreenshareRTMPBroadcastStartedEvent2x =
|
||||
function(conferenceName, screenshareConf, streamUrl, vw, vh, timestamp) {
|
||||
var stadrbem = new ScreenshareRTMPBroadcastStartedEventMessage2x(conferenceName, screenshareConf, streamUrl, vw, vh, timestamp);
|
||||
return stadrbem.toJson();
|
||||
}
|
||||
|
||||
Messaging.prototype.generateScreenshareRTMPBroadcastStoppedEvent2x =
|
||||
function(conferenceName, screenshareConf, streamUrl, vw, vh, timestamp) {
|
||||
var stodrbem = new ScreenshareRTMPBroadcastStoppedEventMessage2x(conferenceName, screenshareConf, streamUrl, vw, vh, timestamp);
|
||||
return stodrbem.toJson();
|
||||
}
|
||||
|
||||
module.exports = new Messaging();
|
||||
module.exports.Constants = Constants;
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* (C) Copyright 2016 Mconf Tecnologia (http://mconf.com/)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* Base class for output messages sent to BBB
|
||||
* @constructor
|
||||
*/
|
||||
function OutMessage(messageName) {
|
||||
/**
|
||||
* The header template of the message
|
||||
* @type {Object}
|
||||
*/
|
||||
this.header = {
|
||||
version: "0.0.1",
|
||||
name: messageName
|
||||
};
|
||||
|
||||
/**
|
||||
* The payload of the message
|
||||
* @type {Object}
|
||||
*/
|
||||
this.payload = null;
|
||||
|
||||
/**
|
||||
* Generates the JSON representation of the message
|
||||
* @return {String} The JSON string of this message
|
||||
*/
|
||||
this.toJson = function () {
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = OutMessage;
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* (C) Copyright 2016 Mconf Tecnologia (http://mconf.com/)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* Base class for output messages sent to BBB
|
||||
* 2x model
|
||||
* @constructor
|
||||
*/
|
||||
function OutMessage2x(messageName, routing, headerFields) {
|
||||
|
||||
|
||||
this.envelope = {
|
||||
name: messageName,
|
||||
routing: routing
|
||||
}
|
||||
/**
|
||||
* The header template of the message
|
||||
* @type {Object}
|
||||
*/
|
||||
this.core = {
|
||||
header : {
|
||||
name: messageName
|
||||
}
|
||||
}
|
||||
|
||||
// Copy header fiels to the header object
|
||||
var keys1 = Object.keys(headerFields);
|
||||
for (var k=0; k < keys1.length; k++) {
|
||||
var key = keys1[k];
|
||||
if (typeof this.core.header[key] === 'undefined') {
|
||||
this.core.header[key] = headerFields[key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The body of the message
|
||||
* @type {Object}
|
||||
*/
|
||||
this.core.body = null;
|
||||
|
||||
/**
|
||||
* Generates the JSON representation of the message
|
||||
* @return {String} The JSON string of this message
|
||||
*/
|
||||
this.toJson = function () {
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = OutMessage2x;
|
@ -1,22 +0,0 @@
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
var inherits = require('inherits');
|
||||
var OutMessage = require('../OutMessage');
|
||||
|
||||
module.exports = function (Constants) {
|
||||
function DeskShareRTMPBroadcastStartedEventMessage (conferenceName, streamUrl, vw, vh, timestamp) {
|
||||
DeskShareRTMPBroadcastStartedEventMessage.super_.call(this, Constants.DESKSHARE_RTMP_BROADCAST_STARTED);
|
||||
|
||||
this.payload = {};
|
||||
this.payload[Constants.CONFERENCE_NAME] = conferenceName;
|
||||
this.payload[Constants.STREAM_URL] = streamUrl;
|
||||
this.payload[Constants.TIMESTAMP] = timestamp;
|
||||
this.payload[Constants.VIDEO_WIDTH] = vw;
|
||||
this.payload[Constants.VIDEO_HEIGHT] = vh;
|
||||
};
|
||||
|
||||
inherits(DeskShareRTMPBroadcastStartedEventMessage, OutMessage);
|
||||
return DeskShareRTMPBroadcastStartedEventMessage;
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
var inherits = require('inherits');
|
||||
var OutMessage = require('../OutMessage');
|
||||
|
||||
module.exports = function (Constants) {
|
||||
function DeskShareRTMPBroadcastStoppedEventMessage (conferenceName, streamUrl, vw, vh, timestamp) {
|
||||
DeskShareRTMPBroadcastStoppedEventMessage.super_.call(this, Constants.DESKSHARE_RTMP_BROADCAST_STOPPED);
|
||||
|
||||
this.payload = {};
|
||||
this.payload[Constants.CONFERENCE_NAME] = conferenceName;
|
||||
this.payload[Constants.STREAM_URL] = streamUrl;
|
||||
this.payload[Constants.TIMESTAMP] = timestamp;
|
||||
this.payload[Constants.VIDEO_WIDTH] = vw;
|
||||
this.payload[Constants.VIDEO_HEIGHT] = vh;
|
||||
};
|
||||
|
||||
inherits(DeskShareRTMPBroadcastStoppedEventMessage, OutMessage);
|
||||
return DeskShareRTMPBroadcastStoppedEventMessage;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
var inherits = require('inherits');
|
||||
var OutMessage2x = require('../OutMessage2x');
|
||||
|
||||
module.exports = function (C) {
|
||||
function ScreenshareRTMPBroadcastStartedEventMessage2x (conferenceName, screenshareConf,
|
||||
streamUrl, vw, vh, timestamp) {
|
||||
ScreenshareRTMPBroadcastStartedEventMessage2x.super_.call(this, C.SCREENSHARE_RTMP_BROADCAST_STARTED_2x,
|
||||
{voiceConf: conferenceName}, {voiceConf: conferenceName});
|
||||
|
||||
this.core.body = {};
|
||||
this.core.body[C.CONFERENCE_NAME] = conferenceName;
|
||||
this.core.body[C.SCREENSHARE_CONF] = screenshareConf;
|
||||
this.core.body[C.STREAM_URL] = streamUrl;
|
||||
this.core.body[C.VIDEO_WIDTH] = vw;
|
||||
this.core.body[C.VIDEO_HEIGHT] = vh;
|
||||
this.core.body[C.TIMESTAMP] = timestamp;
|
||||
};
|
||||
|
||||
inherits(ScreenshareRTMPBroadcastStartedEventMessage2x, OutMessage2x);
|
||||
return ScreenshareRTMPBroadcastStartedEventMessage2x;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
var inherits = require('inherits');
|
||||
var OutMessage2x = require('../OutMessage2x');
|
||||
|
||||
module.exports = function (C) {
|
||||
function ScreenshareRTMPBroadcastStoppedEventMessage2x (conferenceName, screenshareConf,
|
||||
streamUrl, vw, vh, timestamp) {
|
||||
ScreenshareRTMPBroadcastStoppedEventMessage2x.super_.call(this, C.SCREENSHARE_RTMP_BROADCAST_STOPPED_2x,
|
||||
{voiceConf: conferenceName}, {voiceConf: conferenceName});
|
||||
|
||||
this.core.body = {};
|
||||
this.core.body[C.CONFERENCE_NAME] = conferenceName;
|
||||
this.core.body[C.SCREENSHARE_CONF] = screenshareConf;
|
||||
this.core.body[C.STREAM_URL] = streamUrl;
|
||||
this.core.body[C.VIDEO_WIDTH] = vw;
|
||||
this.core.body[C.VIDEO_HEIGHT] = vh;
|
||||
this.core.body[C.TIMESTAMP] = timestamp;
|
||||
};
|
||||
|
||||
inherits(ScreenshareRTMPBroadcastStoppedEventMessage2x, OutMessage2x);
|
||||
return ScreenshareRTMPBroadcastStoppedEventMessage2x;
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
var inherits = require('inherits');
|
||||
var OutMessage = require('../OutMessage');
|
||||
|
||||
module.exports = function (Constants) {
|
||||
function StartTranscoderRequestMessage (meetingId, transcoderId, params) {
|
||||
StartTranscoderRequestMessage.super_.call(this, Constants.START_TRANSCODER_REQUEST);
|
||||
|
||||
this.payload = {};
|
||||
this.payload[Constants.MEETING_ID] = meetingId;
|
||||
this.payload[Constants.TRANSCODER_ID] = transcoderId;
|
||||
this.payload[Constants.PARAMS] = params;
|
||||
};
|
||||
|
||||
inherits(StartTranscoderRequestMessage, OutMessage);
|
||||
return StartTranscoderRequestMessage;
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
var inherits = require('inherits');
|
||||
var OutMessage2x = require('../OutMessage2x');
|
||||
var C = require('../Constants');
|
||||
|
||||
module.exports = function() {
|
||||
function StartTranscoderSysReqMsg(meetingId, transcoderId, params) {
|
||||
StartTranscoderSysReqMsg.super_.call(this, C.START_TRANSCODER_REQ_2x,
|
||||
{sender: "kurento-screenshare"},
|
||||
{meetingId: meetingId});
|
||||
|
||||
this.core.body = {};
|
||||
this.core.body[C.TRANSCODER_ID_2x] = transcoderId;
|
||||
this.core.body[C.PARAMS] = params;
|
||||
};
|
||||
|
||||
inherits(StartTranscoderSysReqMsg, OutMessage2x);
|
||||
return StartTranscoderSysReqMsg;
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
var inherits = require('inherits');
|
||||
var OutMessage = require('../OutMessage');
|
||||
|
||||
module.exports = function (Constants) {
|
||||
function StopTranscoderRequestMessage (meetingId, transcoderId) {
|
||||
StopTranscoderRequestMessage.super_.call(this, Constants.STOP_TRANSCODER_REQUEST);
|
||||
|
||||
this.payload = {};
|
||||
this.payload[Constants.MEETING_ID] = meetingId;
|
||||
this.payload[Constants.TRANSCODER_ID] = transcoderId;
|
||||
};
|
||||
|
||||
inherits(StopTranscoderRequestMessage, OutMessage);
|
||||
return StopTranscoderRequestMessage;
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
var inherits = require('inherits');
|
||||
var OutMessage2x = require('../OutMessage2x');
|
||||
var C = require('../Constants');
|
||||
|
||||
module.exports = function() {
|
||||
function StopTranscoderSysReqMsg(meetingId, transcoderId) {
|
||||
StopTranscoderSysReqMsg.super_.call(this, C.STOP_TRANSCODER_REQ_2x,
|
||||
{sender: "kurento-screenshare"},
|
||||
{meetingId: meetingId});
|
||||
|
||||
this.core.body = {};
|
||||
this.core.body[C.TRANSCODER_ID_2x] = transcoderId;
|
||||
};
|
||||
|
||||
inherits(StopTranscoderSysReqMsg, OutMessage2x);
|
||||
return StopTranscoderSysReqMsg;
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
/**
|
||||
* @classdesc
|
||||
* Redis wrapper class for connecting to Redis channels
|
||||
*/
|
||||
|
||||
/* Modules */
|
||||
|
||||
var redis = require('redis');
|
||||
var config = require('config');
|
||||
var Constants = require('../messages/Constants.js');
|
||||
var util = require('util');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const _retryThreshold = 1000 * 60 * 60;
|
||||
const _maxRetries = 10;
|
||||
|
||||
|
||||
/* Public members */
|
||||
|
||||
var RedisWrapper = function(subpattern) {
|
||||
// Redis PubSub client holders
|
||||
this.redisCli = null;
|
||||
this.redisPub = null;
|
||||
// Pub and Sub channels/patterns
|
||||
this.subpattern = subpattern;
|
||||
EventEmitter.call(this);
|
||||
}
|
||||
|
||||
util.inherits(RedisWrapper, EventEmitter);
|
||||
|
||||
RedisWrapper.prototype.startRedis = function(callback) {
|
||||
var self = this;
|
||||
if (this.redisCli) {
|
||||
console.log(" [RedisWrapper] Redis Client already exists");
|
||||
callback(false, this);
|
||||
}
|
||||
|
||||
var options = {
|
||||
host : config.get('redisHost'),
|
||||
port : config.get('redisPort'),
|
||||
//password: config.get('redis.password')
|
||||
retry_strategy: redisRetry
|
||||
};
|
||||
|
||||
this.redisCli = redis.createClient(options);
|
||||
this.redisPub = redis.createClient(options);
|
||||
|
||||
console.log(" [RedisWrapper] Trying to subscribe to redis channel");
|
||||
|
||||
this.redisCli.on("psubscribe", function (channel, count) {
|
||||
console.log(" [RedisWrapper] Successfully subscribed to pattern [" + channel + "]");
|
||||
});
|
||||
|
||||
this.redisCli.on("pmessage", self.onMessage.bind(self));
|
||||
this.redisCli.psubscribe(this.subpattern);
|
||||
|
||||
console.log(" [RedisWrapper] Started Redis client at " + options.host + ":" + options.port +
|
||||
" for subscription pattern: " + this.subpattern);
|
||||
|
||||
callback(false, this);
|
||||
};
|
||||
|
||||
RedisWrapper.prototype.stopRedis = function(callback) {
|
||||
if (this.redisCli){
|
||||
this.redisCli.quit();
|
||||
}
|
||||
callback(false);
|
||||
};
|
||||
|
||||
RedisWrapper.prototype.publishToChannel = function(message, channel) {
|
||||
if(this.redisPub) {
|
||||
console.log(" [RedisWrapper] Sending message to channel [" + channel + "]: " + message);
|
||||
this.redisPub.publish(channel, message);
|
||||
}
|
||||
};
|
||||
|
||||
RedisWrapper.prototype.onMessage = function(pattern, channel, message) {
|
||||
console.log(" [RedisWrapper] Message received from channel [" + channel + "] : " + message);
|
||||
// use event emitter to throw new message
|
||||
this.emit(Constants.REDIS_MESSAGE, message);
|
||||
}
|
||||
|
||||
/* Private members */
|
||||
|
||||
function redisRetry(options) {
|
||||
if (options.error && options.error.code === 'ECONNREFUSED') {
|
||||
return new Error('The server refused the connection');
|
||||
}
|
||||
if (options.total_retry_time > _retryThreshold) {
|
||||
return new Error('Retry time exhausted');
|
||||
}
|
||||
if (options.times_connected > _maxRetries) {
|
||||
return undefined;
|
||||
}
|
||||
return Math.max(options.attempt * 100, 3000);
|
||||
};
|
||||
|
||||
module.exports = RedisWrapper;
|
@ -1,105 +0,0 @@
|
||||
/**
|
||||
* @classdesc
|
||||
* BigBlueButton redis gateway for bbb-screenshare node app
|
||||
*/
|
||||
|
||||
/* Modules */
|
||||
|
||||
var C = require('../messages/Constants.js');
|
||||
var RedisWrapper = require('./RedisWrapper.js');
|
||||
var config = require('config');
|
||||
var util = require('util');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
/* Public members */
|
||||
|
||||
var BigBlueButtonGW = function () {
|
||||
this.redisClients = null
|
||||
EventEmitter.call(this);
|
||||
};
|
||||
|
||||
util.inherits(BigBlueButtonGW, EventEmitter);
|
||||
|
||||
BigBlueButtonGW.prototype.addSubscribeChannel = function (channel, callback) {
|
||||
var self = this;
|
||||
|
||||
if (this.redisClients === null) {
|
||||
this.redisClients = {};
|
||||
}
|
||||
|
||||
if (this.redisClients[channel]) {
|
||||
return callback(null, this.redisClients[channel]);
|
||||
}
|
||||
|
||||
var wrobj = new RedisWrapper(channel);
|
||||
this.redisClients[channel] = {};
|
||||
this.redisClients[channel] = wrobj;
|
||||
wrobj.startRedis(function(error, redisCli) {
|
||||
if(error) {
|
||||
console.log(" [BigBlueButtonGW] Could not start redis client for channel " + channel);
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
console.log(" [BigBlueButtonGW] Added redis client to this.redisClients[" + channel + "]");
|
||||
wrobj.on(C.REDIS_MESSAGE, self.incomingMessage.bind(self));
|
||||
|
||||
return callback(null, wrobj);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Capture messages from subscribed channels and emit an event with it's
|
||||
* identifier and payload. Check Constants.js for the identifiers.
|
||||
*
|
||||
* @param {Object} message Redis message
|
||||
*/
|
||||
BigBlueButtonGW.prototype.incomingMessage = function (message) {
|
||||
var msg = JSON.parse(message);
|
||||
|
||||
// Trying to parse both message types, 1x and 2x
|
||||
if (msg.header) {
|
||||
var header = msg.header;
|
||||
var payload = msg.payload;
|
||||
}
|
||||
else if (msg.core) {
|
||||
var header = msg.core.header;
|
||||
var payload = msg.core.body;
|
||||
}
|
||||
|
||||
if (header){
|
||||
switch (header.name) {
|
||||
// interoperability with 1.1
|
||||
case C.START_TRANSCODER_REPLY:
|
||||
this.emit(C.START_TRANSCODER_REPLY, payload);
|
||||
break;
|
||||
case C.STOP_TRANSCODER_REPLY:
|
||||
this.emit(C.STOP_TRANSCODER_REPLY, payload);
|
||||
break;
|
||||
// 2x messages
|
||||
case C.START_TRANSCODER_RESP_2x:
|
||||
payload[C.MEETING_ID_2x] = header[C.MEETING_ID_2x];
|
||||
|
||||
this.emit(C.START_TRANSCODER_RESP_2x, payload);
|
||||
break;
|
||||
case C.STOP_TRANSCODER_RESP_2x:
|
||||
payload[C.MEETING_ID_2x] = header[C.MEETING_ID_2x];
|
||||
this.emit(C.STOP_TRANSCODER_RESP_2x, payload);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(" [BigBlueButtonGW] Unknown Redis message with ID =>" + header.name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BigBlueButtonGW.prototype.publish = function (message, channel, callback) {
|
||||
for(var client in this.redisClients) {
|
||||
if(typeof this.redisClients[client].publishToChannel === 'function') {
|
||||
this.redisClients[client].publishToChannel(message, channel);
|
||||
return callback(null);
|
||||
}
|
||||
}
|
||||
return callback("Client not found");
|
||||
};
|
||||
|
||||
module.exports = BigBlueButtonGW;
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* A module with the sole purpose of removing all non h264 options from an sdpOffer
|
||||
*
|
||||
* We use this to prevent any transcoding from the Kurento side if Firefox or Chrome offer VP8/VP9 as
|
||||
* the default format.
|
||||
*/
|
||||
|
||||
var sdpTransform = require('sdp-transform');
|
||||
|
||||
exports.transform = function(sdp) {
|
||||
|
||||
var mediaIndex = 0;
|
||||
var res = sdpTransform.parse(sdp);
|
||||
var validPayloads;
|
||||
|
||||
if (res.media[0].type === 'audio') {
|
||||
// Audio
|
||||
res.media[mediaIndex].rtp = res.media[mediaIndex].rtp.filter(function(elem) {
|
||||
return elem.codec === 'opus';
|
||||
});
|
||||
|
||||
validPayloads = res.media[mediaIndex].rtp.map(function(elem) {
|
||||
return elem.payload;
|
||||
});
|
||||
|
||||
res.media[mediaIndex].fmtp = res.media[mediaIndex].fmtp.filter(function(elem) {
|
||||
return validPayloads.indexOf(elem.payload) >= 0;
|
||||
});
|
||||
|
||||
res.media[mediaIndex].payloads = validPayloads.join(' ');
|
||||
|
||||
mediaIndex += 1;
|
||||
}
|
||||
|
||||
// Video
|
||||
res.media[mediaIndex].rtp = res.media[mediaIndex].rtp.filter(function(elem) {
|
||||
return elem.codec === 'H264';
|
||||
});
|
||||
|
||||
validPayloads = res.media[mediaIndex].rtp.map(function(elem) {
|
||||
return elem.payload;
|
||||
});
|
||||
|
||||
res.media[mediaIndex].fmtp = res.media[mediaIndex].fmtp.filter(function(elem) {
|
||||
return validPayloads.indexOf(elem.payload) >= 0;
|
||||
});
|
||||
|
||||
res.media[mediaIndex].rtcpFb = res.media[mediaIndex].rtcpFb.filter(function(elem) {
|
||||
return validPayloads.indexOf(elem.payload) >= 0;
|
||||
});
|
||||
|
||||
res.media[mediaIndex].payloads = validPayloads.join(' ');
|
||||
|
||||
return sdpTransform.write(res);
|
||||
};
|
||||
|
@ -1,141 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const Constants = require('./bbb/messages/Constants.js');
|
||||
const config = require('config');
|
||||
const kurento = require('kurento-client');
|
||||
const mediaServerClient = null;
|
||||
|
||||
var _mediaPipelines = {};
|
||||
var _mediaElements= {};
|
||||
|
||||
function createMediaPipeline(id, callback) {
|
||||
console.log(' [media] Creating media pipeline for ' + id);
|
||||
getMediaServerClient(function (error, mediaServerClient) {
|
||||
mediaServerClient.create('MediaPipeline', function(err, pipeline) {
|
||||
if (error) {
|
||||
console.log("Could not find media server at address " + kurentoUrl);
|
||||
return callback(error);
|
||||
}
|
||||
return callback(null , pipeline);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function getMediaServerClient (callback) {
|
||||
let kurentoUrl = config.get('kurentoUrl');
|
||||
if (mediaServerClient) {
|
||||
callback(null, mediaServerClient);
|
||||
}
|
||||
else {
|
||||
kurento(kurentoUrl, function(error, _mediaServerClient) {
|
||||
if (error) {
|
||||
console.log("Could not find media server at address " + kurentoUrl);
|
||||
return callback(error, null);
|
||||
}
|
||||
|
||||
console.log(" [server] Initiating kurento client. Connecting to: " + kurentoUrl);
|
||||
return callback(null, _mediaServerClient);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/* Public members */
|
||||
module.exports = {
|
||||
|
||||
createMediaElement : function (conference, type, callback) {
|
||||
let self = this;
|
||||
self.getMediaPipeline(conference, function(error, pipeline) {
|
||||
|
||||
pipeline.create(type, function(error, mediaElement) {
|
||||
if (error) {
|
||||
return callback(error, null);
|
||||
}
|
||||
console.log(" [MediaController] Created [" + type + "] media element: " + mediaElement.id);
|
||||
_mediaElements[mediaElement.id] = mediaElement;
|
||||
return callback(null, mediaElement);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
connectMediaElements : function (sourceId, sinkId, type, callback) {
|
||||
let source = _mediaElements[sourceId];
|
||||
let sink = _mediaElements[sinkId];
|
||||
|
||||
if (source && sink) {
|
||||
if (type === 'ALL') {
|
||||
source.connect(sink, function (error) {
|
||||
return callback (error);
|
||||
});
|
||||
} else {
|
||||
console.log(typeof source.connect);
|
||||
source.connect(sink, type, function (error) {
|
||||
return callback (error);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return callback ("Failed to connect " + type + ": " + sourceId + " to " + sinkId);
|
||||
}
|
||||
},
|
||||
|
||||
releaseMediaElement : function (elementId) {
|
||||
let mediaElement = _mediaElements[elementId];
|
||||
|
||||
if (typeof mediaElement !== 'undefined' && typeof mediaElement.release === 'function') {
|
||||
mediaElement.release();
|
||||
}
|
||||
},
|
||||
|
||||
releasePipeline: function (pipelineId) {
|
||||
let MediaPipeline = _mediaPipelines[pipelineId];
|
||||
|
||||
if (typeof mediaElement !== 'undefined' && typeof mediaElement.release === 'function') {
|
||||
mediaElement.release();
|
||||
}
|
||||
},
|
||||
|
||||
processOffer : function (elementId, sdpOffer, callback) {
|
||||
let mediaElement = _mediaElements[elementId];
|
||||
|
||||
if (typeof mediaElement !== 'undefined' && typeof mediaElement.processOffer === 'function') {
|
||||
mediaElement.processOffer (sdpOffer, function (error, sdpAnswer) {
|
||||
return callback (error, sdpAnswer);
|
||||
});
|
||||
} else {
|
||||
return callback (" [MediaController/processOffer] There is no element " + elementId, null);
|
||||
}
|
||||
},
|
||||
|
||||
getMediaPipeline : function(conference, callback) {
|
||||
let self = this;
|
||||
|
||||
if (_mediaPipelines[conference]) {
|
||||
console.log(' [media] Pipeline already exists. ' + JSON.stringify(_mediaPipelines, null, 2));
|
||||
return callback(null, _mediaPipelines[conference]);
|
||||
} else {
|
||||
createMediaPipeline(conference, function(error, pipeline) {
|
||||
_mediaPipelines[conference] = pipeline;
|
||||
return callback(error, pipeline);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
addIceCandidate : function (elementId, candidate) {
|
||||
let mediaElement = _mediaElements[elementId];
|
||||
|
||||
if (typeof mediaElement !== 'undefined' && typeof mediaElement.addIceCandidate === 'function') {
|
||||
mediaElement.addIceCandidate(candidate);
|
||||
}
|
||||
},
|
||||
|
||||
gatherCandidates : function (elementId, callback) {
|
||||
let mediaElement = _mediaElements[elementId];
|
||||
|
||||
if (typeof mediaElement !== 'undefined' && typeof mediaElement.gatherCandidates === 'function') {
|
||||
mediaElement.gatherCandidates(function (error) {
|
||||
return callback(error);
|
||||
});
|
||||
} else {
|
||||
return callback (" [MediaController/gatherCandidates] There is no element " + elementId, null);
|
||||
}
|
||||
},
|
||||
};
|
@ -1,99 +0,0 @@
|
||||
var config = require('config');
|
||||
var kurento = require('kurento-client');
|
||||
var Constants = require('./bbb/messages/Constants');
|
||||
|
||||
var kurentoClient = null;
|
||||
var mediaPipelines = {};
|
||||
|
||||
module.exports.getKurentoClient = function(kurentoUrl, callback) {
|
||||
if (kurentoClient !== null) {
|
||||
return callback(null, kurentoClient);
|
||||
}
|
||||
|
||||
kurento(kurentoUrl, function(error, _kurentoClient) {
|
||||
if (error) {
|
||||
console.log("Could not find media server at address " + kurentoUrl);
|
||||
return callback("Could not find media server at address" + kurentoUrl + ". Exiting with error " + error);
|
||||
}
|
||||
|
||||
console.log(" [MediaHandler] Initiating kurento client. Connecting to: " + kurentoUrl);
|
||||
|
||||
kurentoClient = _kurentoClient;
|
||||
callback(null, kurentoClient);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.getMediaPipeline = function(id, callback) {
|
||||
console.log(' [MediaHandler] Creating media pipeline for ' + id);
|
||||
|
||||
if (mediaPipelines[id]) {
|
||||
console.log(' [media] Pipeline already exists.');
|
||||
callback(null, mediaPipelines[id]);
|
||||
} else {
|
||||
kurentoClient.create('MediaPipeline', function(err, pipeline) {
|
||||
mediaPipelines[id] = pipeline;
|
||||
return callback(err, pipeline);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.generateSdp = function(remote_ip_address, remote_video_port) {
|
||||
return "v=0\r\n"
|
||||
+ "o=- 0 0 IN IP4 " + remote_ip_address + "\r\n"
|
||||
+ "s=Kurento-SCREENSHARE\r\n"
|
||||
+ "c=IN IP4 " + remote_ip_address + "\r\n"
|
||||
+ "t=0 0\r\n"
|
||||
+ "m=video " + remote_video_port + " RTP/AVPF 96\r\n"
|
||||
+ "a=rtpmap:96 H264/90000\r\n"
|
||||
+ "a=ftmp:96\r\n";
|
||||
}
|
||||
|
||||
module.exports.generateVideoSdp = function (sourceIpAddress, sourceVideoPort) {
|
||||
return "v=0\r\n"
|
||||
+ "o=- 0 0 IN IP4 " + sourceIpAddress + "\r\n"
|
||||
+ "s=Kurento-SCREENSHARE\r\n"
|
||||
+ 'm=video ' + sourceVideoPort + ' ' + this.videoConfiguration.rtpProfile + ' ' + this.videoConfiguration.codecId + '\r\n'
|
||||
+ 'a=' + this.videoConfiguration.sendReceive + '\r\n'
|
||||
+ 'c=IN IP4 ' + sourceIpAddress + '\r\n'
|
||||
+ 'a=rtpmap:' + this.videoConfiguration.codecId + ' ' + this.videoConfiguration.codecName + '/' + this.videoConfiguration.codecRate + '\r\n'
|
||||
+ 'a=fmtp:' + this.videoConfiguration.codecId + '\r\n'
|
||||
+ 'a=rtcp-fb:' + this.videoConfiguration.codecId + ' ccm fir \r\n'
|
||||
+ 'a=rtcp-fb:' + this.videoConfiguration.codecId + ' nack \r\n'
|
||||
+ 'a=rtcp-fb:' + this.videoConfiguration.codecId + ' nack pli \r\n'
|
||||
+ 'a=rtcp-fb:' + this.videoConfiguration.codecId + ' goog-remb \r\n';
|
||||
};
|
||||
|
||||
module.exports.videoConfiguration = {
|
||||
codecId: '96',
|
||||
sendReceive: 'sendrecv',
|
||||
rtpProfile: 'RTP/AVPF',
|
||||
codecName: 'H264',
|
||||
frameRate: '30.000000',
|
||||
codecRate: '90000'
|
||||
};
|
||||
|
||||
module.exports.generateStreamUrl = function (address, meeting, path) {
|
||||
return "rtmp://" + address + "/video-broadcast/" + meeting + "/" + path;
|
||||
}
|
||||
|
||||
module.exports.generateTranscoderParams = function (localIp, destIp, sendPort, recvPort, input, streamType, transcoderType, codec, callername) {
|
||||
var rtpParams = {};
|
||||
rtpParams[Constants.LOCAL_IP_ADDRESS] = localIp;
|
||||
rtpParams[Constants.LOCAL_VIDEO_PORT] = sendPort;
|
||||
rtpParams[Constants.DESTINATION_IP_ADDRESS] = destIp;
|
||||
rtpParams[Constants.REMOTE_VIDEO_PORT] = recvPort;
|
||||
rtpParams[Constants.INPUT] = input;
|
||||
rtpParams[Constants.STREAM_TYPE] = streamType;
|
||||
rtpParams[Constants.TRANSCODER_TYPE] = transcoderType;
|
||||
rtpParams[Constants.TRANSCODER_CODEC] = codec;
|
||||
rtpParams[Constants.CALLERNAME] = callername;
|
||||
return rtpParams;
|
||||
}
|
||||
|
||||
module.exports.getPort = function (min_port, max_port) {
|
||||
return Math.floor((Math.random() * (max_port - min_port + 1) + min_port));
|
||||
}
|
||||
|
||||
module.exports.getVideoPort = function () {
|
||||
return this.getPort(config.get('minVideoPort'), config.get('maxVideoPort'));
|
||||
}
|
@ -1,347 +0,0 @@
|
||||
/*
|
||||
* Lucas Fialho Zawacki
|
||||
* Paulo Renato Lanzarin
|
||||
* (C) Copyright 2017 Bigbluebutton
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
// Imports
|
||||
const C = require('./bbb/messages/Constants');
|
||||
const MediaHandler = require('./media-handler');
|
||||
const Messaging = require('./bbb/messages/Messaging');
|
||||
const moment = require('moment');
|
||||
const h264_sdp = require('./h264-sdp');
|
||||
const now = moment();
|
||||
const MediaController = require('./media-controller');
|
||||
|
||||
// Global stuff
|
||||
var sharedScreens = {};
|
||||
var rtpEndpoints = {};
|
||||
|
||||
const kurento = require('kurento-client');
|
||||
const config = require('config');
|
||||
const kurentoUrl = config.get('kurentoUrl');
|
||||
const kurentoIp = config.get('kurentoIp');
|
||||
const localIpAddress = config.get('localIpAddress');
|
||||
|
||||
if (config.get('acceptSelfSignedCertificate')) {
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED=0;
|
||||
}
|
||||
|
||||
module.exports = class Screenshare {
|
||||
constructor(ws, id, bbbgw, voiceBridge, caller, vh, vw, meetingId) {
|
||||
this._ws = ws;
|
||||
this._id = id;
|
||||
this._BigBlueButtonGW = bbbgw;
|
||||
this._presenterEndpoint = null;
|
||||
this._ffmpegRtpEndpoint = null;
|
||||
this._voiceBridge = voiceBridge;
|
||||
this._meetingId = meetingId;
|
||||
this._caller = caller;
|
||||
this._streamUrl = "";
|
||||
this._vw = vw;
|
||||
this._vh = vh;
|
||||
this._candidatesQueue = [];
|
||||
this._viewersEndpoint = [];
|
||||
this._viewersCandidatesQueue = [];
|
||||
}
|
||||
|
||||
// TODO isolate ICE
|
||||
_onIceCandidate(_candidate) {
|
||||
let candidate = kurento.getComplexType('IceCandidate')(_candidate);
|
||||
|
||||
if (this._presenterEndpoint) {
|
||||
console.log(" [screenshare] Adding ICE candidate to presenter");
|
||||
this._presenterEndpoint.addIceCandidate(candidate);
|
||||
}
|
||||
else {
|
||||
this._candidatesQueue.push(candidate);
|
||||
}
|
||||
};
|
||||
|
||||
_onViewerIceCandidate(_candidate, callerName) {
|
||||
console.log("onviewericecandidate callerName = " + callerName);
|
||||
let candidate = kurento.getComplexType('IceCandidate')(_candidate);
|
||||
|
||||
if (this._viewersEndpoint[callerName]) {
|
||||
this._viewersEndpoint[callerName].addIceCandidate(candidate);
|
||||
}
|
||||
else {
|
||||
if (!this._viewersCandidatesQueue[callerName]) {
|
||||
this._viewersCandidatesQueue[callerName] = [];
|
||||
}
|
||||
this._viewersCandidatesQueue[callerName].push(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
_startViewer(ws, voiceBridge, sdp, callerName, presenterEndpoint, callback) {
|
||||
let self = this;
|
||||
let _callback = function(){};
|
||||
console.log("startviewer callerName = " + callerName);
|
||||
self._viewersCandidatesQueue[callerName] = [];
|
||||
|
||||
console.log("VIEWER VOICEBRIDGE: "+self._voiceBridge);
|
||||
|
||||
MediaController.createMediaElement(voiceBridge, C.WebRTC, function(error, webRtcEndpoint) {
|
||||
if (error) {
|
||||
console.log("Media elements error" + error);
|
||||
return _callback(error);
|
||||
}
|
||||
|
||||
self._viewersEndpoint[callerName] = webRtcEndpoint;
|
||||
|
||||
// QUEUES UP ICE CANDIDATES IF NEGOTIATION IS NOT YET READY
|
||||
while(self._viewersCandidatesQueue[callerName].length) {
|
||||
let candidate = self._viewersCandidatesQueue[callerName].shift();
|
||||
MediaController.addIceCandidate(self._viewersEndpoint[callerName].id, candidate);
|
||||
}
|
||||
// CONNECTS TWO MEDIA ELEMENTS
|
||||
MediaController.connectMediaElements(presenterEndpoint.id, self._viewersEndpoint[callerName].id, C.VIDEO, function(error) {
|
||||
if (error) {
|
||||
console.log("Media elements CONNECT error " + error);
|
||||
//pipeline.release();
|
||||
return _callback(error);
|
||||
}
|
||||
});
|
||||
|
||||
// ICE NEGOTIATION WITH THE ENDPOINT
|
||||
self._viewersEndpoint[callerName].on('OnIceCandidate', function(event) {
|
||||
let candidate = kurento.getComplexType('IceCandidate')(event.candidate); ws.sendMessage({ id : 'iceCandidate', candidate : candidate });
|
||||
});
|
||||
|
||||
sdp = h264_sdp.transform(sdp);
|
||||
// PROCESS A SDP OFFER
|
||||
MediaController.processOffer(webRtcEndpoint.id, sdp, function(error, webRtcSdpAnswer) {
|
||||
if (error) {
|
||||
console.log(" [webrtc] processOffer error => " + error + " for SDP " + sdp);
|
||||
//pipeline.release();
|
||||
return _callback(error);
|
||||
}
|
||||
ws.sendMessage({id: "viewerResponse", sdpAnswer: webRtcSdpAnswer, response: "accepted"});
|
||||
console.log(" Sent sdp message to client with callerName:" + callerName);
|
||||
|
||||
MediaController.gatherCandidates(webRtcEndpoint.id, function(error) {
|
||||
if (error) {
|
||||
return _callback(error);
|
||||
}
|
||||
|
||||
self._viewersEndpoint[callerName].on('MediaFlowInStateChange', function(event) {
|
||||
if (event.state === 'NOT_FLOWING') {
|
||||
console.log(" NOT FLOWING ");
|
||||
}
|
||||
else if (event.state === 'FLOWING') {
|
||||
console.log(" FLOWING ");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
_startPresenter(id, ws, sdpOffer, callback) {
|
||||
let self = this;
|
||||
let _callback = callback;
|
||||
|
||||
// Force H264 on Firefox and Chrome
|
||||
sdpOffer = h264_sdp.transform(sdpOffer);
|
||||
console.log("Starting presenter for " + sdpOffer);
|
||||
console.log("PRESENTER VOICEBRIDGE: " + self._voiceBridge);
|
||||
MediaController.createMediaElement(self._voiceBridge, C.WebRTC, function(error, webRtcEndpoint) {
|
||||
if (error) {
|
||||
console.log("Media elements error" + error);
|
||||
return _callback(error);
|
||||
}
|
||||
MediaController.createMediaElement(self._voiceBridge, C.RTP, function(error, rtpEndpoint) {
|
||||
if (error) {
|
||||
console.log("Media elements error" + error);
|
||||
return _callback(error);
|
||||
}
|
||||
|
||||
|
||||
while(self._candidatesQueue.length) {
|
||||
let candidate = self._candidatesQueue.shift();
|
||||
MediaController.addIceCandidate(webRtcEndpoint.id, candidate);
|
||||
}
|
||||
|
||||
MediaController.connectMediaElements(webRtcEndpoint.id, rtpEndpoint.id, C.VIDEO, function(error) {
|
||||
if (error) {
|
||||
console.log("Media elements CONNECT error " + error);
|
||||
//pipeline.release();
|
||||
return _callback(error);
|
||||
}
|
||||
|
||||
// It's a user sharing a Screen
|
||||
sharedScreens[id] = webRtcEndpoint;
|
||||
rtpEndpoints[id] = rtpEndpoint;
|
||||
|
||||
// Store our endpoint
|
||||
self._presenterEndpoint = webRtcEndpoint;
|
||||
self._ffmpegRtpEndpoint = rtpEndpoint;
|
||||
|
||||
self._presenterEndpoint.on('OnIceCandidate', function(event) {
|
||||
let candidate = kurento.getComplexType('IceCandidate')(event.candidate);
|
||||
ws.sendMessage({ id : 'iceCandidate', cameraId: id, candidate : candidate });
|
||||
});
|
||||
|
||||
MediaController.processOffer(webRtcEndpoint.id, sdpOffer, function(error, webRtcSdpAnswer) {
|
||||
if (error) {
|
||||
console.log(" [webrtc] processOffer error => " + error + " for SDP " + sdpOffer);
|
||||
//pipeline.release();
|
||||
return _callback(error);
|
||||
}
|
||||
|
||||
let sendVideoPort = MediaHandler.getVideoPort();
|
||||
|
||||
let rtpSdpOffer = MediaHandler.generateVideoSdp(localIpAddress, sendVideoPort);
|
||||
console.log(" [rtpendpoint] RtpEndpoint processing => " + rtpSdpOffer);
|
||||
|
||||
MediaController.gatherCandidates(webRtcEndpoint.id, function(error) {
|
||||
if (error) {
|
||||
return _callback(error);
|
||||
}
|
||||
|
||||
MediaController.processOffer(rtpEndpoint.id, rtpSdpOffer, function(error, rtpSdpAnswer) {
|
||||
if (error) {
|
||||
console.log(" [rtpendpoint] processOffer error => " + error + " for SDP " + rtpSdpOffer);
|
||||
//pipeline.release();
|
||||
return _callback(error);
|
||||
}
|
||||
|
||||
console.log(" [rtpendpoint] KMS answer SDP => " + rtpSdpAnswer);
|
||||
let recvVideoPort = rtpSdpAnswer.match(/m=video\s(\d*)/)[1];
|
||||
let rtpParams = MediaHandler.generateTranscoderParams(kurentoIp, localIpAddress,
|
||||
sendVideoPort, recvVideoPort, self._meetingId, "stream_type_video", C.RTP_TO_RTMP, "copy", "caller");
|
||||
|
||||
self._ffmpegRtpEndpoint.on('MediaFlowInStateChange', function(event) {
|
||||
if (event.state === 'NOT_FLOWING') {
|
||||
self._onRtpMediaNotFlowing();
|
||||
}
|
||||
else if (event.state === 'FLOWING') {
|
||||
self._onRtpMediaFlowing(self._meetingId, rtpParams);
|
||||
}
|
||||
});
|
||||
return _callback(null, webRtcSdpAnswer);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
_stop() {
|
||||
|
||||
console.log(' [stop] Releasing endpoints for ' + this._id);
|
||||
|
||||
this._stopScreensharing();
|
||||
|
||||
if (this._presenterEndpoint) {
|
||||
MediaController.releaseMediaElement(this._presenterEndpoint.id);
|
||||
this._presenterEndpoint = null;
|
||||
} else {
|
||||
console.log(" [webRtcEndpoint] PLEASE DONT TRY STOPPING THINGS TWICE");
|
||||
}
|
||||
if (this._ffmpegRtpEndpoint) {
|
||||
MediaController.releaseMediaElement(this._ffmpegRtpEndpoint.id);
|
||||
this._ffmpegRtpEndpoint = null;
|
||||
} else {
|
||||
console.log(" [rtpEndpoint] PLEASE DONT TRY STOPPING THINGS TWICE");
|
||||
}
|
||||
|
||||
console.log(' [stop] Screen is shared, releasing ' + this._id);
|
||||
|
||||
delete sharedScreens[this._id];
|
||||
|
||||
delete this._candidatesQueue;
|
||||
};
|
||||
|
||||
_stopScreensharing() {
|
||||
let self = this;
|
||||
let strm = Messaging.generateStopTranscoderRequestMessage(this._meetingId, this._meetingId);
|
||||
|
||||
self._BigBlueButtonGW.publish(strm, C.TO_BBB_TRANSCODE_SYSTEM_CHAN, function(error) {});
|
||||
|
||||
// Interoperability: capturing 1.1 stop_transcoder_reply messages
|
||||
self._BigBlueButtonGW.once(C.STOP_TRANSCODER_REPLY, function(payload) {
|
||||
let meetingId = payload[C.MEETING_ID];
|
||||
self._stopRtmpBroadcast(meetingId);
|
||||
});
|
||||
|
||||
// Capturing stop transcoder responses from the 2x model
|
||||
self._BigBlueButtonGW.once(C.STOP_TRANSCODER_RESP_2x, function(payload) {
|
||||
let meetingId = payload[C.MEETING_ID_2x];
|
||||
self._stopRtmpBroadcast(meetingId);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
_onRtpMediaFlowing(meetingId, rtpParams) {
|
||||
console.log(" [screenshare] Media FLOWING for meeting => " + meetingId);
|
||||
let self = this;
|
||||
let strm = Messaging.generateStartTranscoderRequestMessage(meetingId, meetingId, rtpParams);
|
||||
|
||||
// Interoperability: capturing 1.1 start_transcoder_reply messages
|
||||
self._BigBlueButtonGW.once(C.START_TRANSCODER_REPLY, function(payload) {
|
||||
let meetingId = payload[C.MEETING_ID];
|
||||
let output = payload["params"].output;
|
||||
self._startRtmpBroadcast(meetingId, output);
|
||||
});
|
||||
|
||||
// Capturing stop transcoder responses from the 2x model
|
||||
self._BigBlueButtonGW.once(C.START_TRANSCODER_RESP_2x, function(payload) {
|
||||
let meetingId = payload[C.MEETING_ID_2x];
|
||||
let output = payload["params"].output;
|
||||
self._startRtmpBroadcast(meetingId, output);
|
||||
});
|
||||
|
||||
|
||||
self._BigBlueButtonGW.publish(strm, C.TO_BBB_TRANSCODE_SYSTEM_CHAN, function(error) {});
|
||||
};
|
||||
|
||||
_stopRtmpBroadcast (meetingId) {
|
||||
console.log(" [screenshare] _stopRtmpBroadcast for meeting => " + meetingId);
|
||||
let self = this;
|
||||
if(self._meetingId === meetingId) {
|
||||
// TODO correctly assemble this timestamp
|
||||
let timestamp = now.format('hhmmss');
|
||||
let dsrstom = Messaging.generateScreenshareRTMPBroadcastStoppedEvent2x(self._voiceBridge,
|
||||
self._voiceBridge, self._streamUrl, self._vw, self._vh, timestamp);
|
||||
self._BigBlueButtonGW.publish(dsrstom, C.FROM_VOICE_CONF_SYSTEM_CHAN, function(error) {});
|
||||
}
|
||||
}
|
||||
|
||||
_startRtmpBroadcast (meetingId, output) {
|
||||
console.log(" [screenshare] _startRtmpBroadcast for meeting => " + meetingId);
|
||||
var self = this;
|
||||
if(self._meetingId === meetingId) {
|
||||
// TODO correctly assemble this timestamp
|
||||
let timestamp = now.format('hhmmss');
|
||||
self._streamUrl = MediaHandler.generateStreamUrl(localIpAddress, meetingId, output);
|
||||
let dsrbstam = Messaging.generateScreenshareRTMPBroadcastStartedEvent2x(self._voiceBridge,
|
||||
self._voiceBridge, self._streamUrl, self._vw, self._vh, timestamp);
|
||||
|
||||
self._BigBlueButtonGW.publish(dsrbstam, C.FROM_VOICE_CONF_SYSTEM_CHAN, function(error) {});
|
||||
}
|
||||
}
|
||||
|
||||
_onRtpMediaNotFlowing() {
|
||||
console.log(" [screenshare] TODO RTP NOT_FLOWING");
|
||||
};
|
||||
|
||||
_stopViewer(id) {
|
||||
let viewer = this._viewersEndpoint[id];
|
||||
console.log(' [stop] Releasing endpoints for ' + id);
|
||||
|
||||
if (viewer) {
|
||||
MediaController.releaseMediaElement(viewer.id);
|
||||
this._viewersEndpoint[viewer.id] = null;
|
||||
} else {
|
||||
console.log(" [webRtcEndpoint] PLEASE DONT TRY STOPPING THINGS TWICE");
|
||||
}
|
||||
|
||||
delete this._viewersCandidatesQueue[id];
|
||||
};
|
||||
};
|
@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Simple wrapper around the ws library
|
||||
*
|
||||
*/
|
||||
|
||||
var ws = require('ws');
|
||||
|
||||
ws.prototype.sendMessage = function(json) {
|
||||
|
||||
return this.send(JSON.stringify(json), function(error) {
|
||||
if(error)
|
||||
console.log(' [server] Websocket error "' + error + '" on message "' + json.id + '"');
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
module.exports = ws;
|
@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "bbb-screenshare-video-kurento-bridge",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "nodejs server.js",
|
||||
"postinstall": "npm start"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-parser": "^1.3.5",
|
||||
"express": "~4.12.4",
|
||||
"express-session": "~1.10.3",
|
||||
"kurento-client": "6.6.0",
|
||||
"moment": "*",
|
||||
"redis": "^2.6.2",
|
||||
"sdp-transform": "*",
|
||||
"uuid": "^3.1.0",
|
||||
"ws": "~1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"config": "^1.26.1",
|
||||
"js-yaml": "^3.8.3"
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
/*
|
||||
* Paulo Renato Lanzarin
|
||||
* (C) Copyright 2017 Bigbluebutton
|
||||
*
|
||||
*/
|
||||
|
||||
const ConnectionManager = require('./lib/ConnectionManager');
|
||||
const CM = new ConnectionManager();
|
||||
|
||||
process.on('SIGTERM', CM._stopAll.bind(CM));
|
||||
process.on('SIGINT', CM._stopAll.bind(CM));
|
Loading…
Reference in New Issue
Block a user