openvidu-browser refactoring: kurento deps integrated

pull/20/head
pabloFuente 2017-09-22 15:57:59 +02:00
parent 8d0aefb959
commit 3f25170615
30 changed files with 15105 additions and 17583 deletions

View File

@ -15,13 +15,18 @@
"author": "",
"license": "Apache-2.0",
"dependencies": {
"kurento-jsonrpc": "5.1.3",
"wolfy87-eventemitter": "4.2.9",
"@types/wolfy87-eventemitter": "4.2.31",
"webrtc-adapter": "3.3.2",
"kurento-utils": "6.6.2",
"webrtc-adapter": "5.0.4",
"uuid": "~2.0.1",
"sdp-translator": "^0.1.15"
"freeice": "2.2.0",
"inherits": "^2.0.3",
"merge": "^1.2.0",
"ua-parser-js": "^0.7.7",
"hark": "1.1.3",
"bufferutil": "3.0.2",
"utf-8-validate": "3.0.3",
"sdp-translator": "0.1.24"
},
"devDependencies": {
"typescript": "2.5.2",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,66 @@
function Mapper()
{
var sources = {};
this.forEach = function(callback)
{
for(var key in sources)
{
var source = sources[key];
for(var key2 in source)
callback(source[key2]);
};
};
this.get = function(id, source)
{
var ids = sources[source];
if(ids == undefined)
return undefined;
return ids[id];
};
this.remove = function(id, source)
{
var ids = sources[source];
if(ids == undefined)
return;
delete ids[id];
// Check it's empty
for(var i in ids){return false}
delete sources[source];
};
this.set = function(value, id, source)
{
if(value == undefined)
return this.remove(id, source);
var ids = sources[source];
if(ids == undefined)
sources[source] = ids = {};
ids[id] = value;
};
};
Mapper.prototype.pop = function(id, source)
{
var value = this.get(id, source);
if(value == undefined)
return undefined;
this.remove(id, source);
return value;
};
module.exports = Mapper;

View File

@ -0,0 +1,21 @@
/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var JsonRpcClient = require('./jsonrpcclient');
exports.JsonRpcClient = JsonRpcClient;

View File

@ -0,0 +1,276 @@
/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var RpcBuilder = require('../');
var WebSocketWithReconnection = require('./transports/webSocketWithReconnection');
Date.now = Date.now || function() {
return +new Date;
};
var PING_INTERVAL = 5000;
var RECONNECTING = 'RECONNECTING';
var CONNECTED = 'CONNECTED';
var DISCONNECTED = 'DISCONNECTED';
var Logger = console;
/**
*
* heartbeat: interval in ms for each heartbeat message,
* sendCloseMessage : true / false, before closing the connection, it sends a closeSession message
* <pre>
* ws : {
* uri : URI to conntect to,
* useSockJS : true (use SockJS) / false (use WebSocket) by default,
* onconnected : callback method to invoke when connection is successful,
* ondisconnect : callback method to invoke when the connection is lost,
* onreconnecting : callback method to invoke when the client is reconnecting,
* onreconnected : callback method to invoke when the client succesfully reconnects,
* onerror : callback method to invoke when there is an error
* },
* rpc : {
* requestTimeout : timeout for a request,
* sessionStatusChanged: callback method for changes in session status,
* mediaRenegotiation: mediaRenegotiation
* }
* </pre>
*/
function JsonRpcClient(configuration) {
var self = this;
var wsConfig = configuration.ws;
var notReconnectIfNumLessThan = -1;
var pingNextNum = 0;
var enabledPings = true;
var pingPongStarted = false;
var pingInterval;
var status = DISCONNECTED;
var onreconnecting = wsConfig.onreconnecting;
var onreconnected = wsConfig.onreconnected;
var onconnected = wsConfig.onconnected;
var onerror = wsConfig.onerror;
configuration.rpc.pull = function(params, request) {
request.reply(null, "push");
}
wsConfig.onreconnecting = function() {
Logger.debug("--------- ONRECONNECTING -----------");
if (status === RECONNECTING) {
Logger.error("Websocket already in RECONNECTING state when receiving a new ONRECONNECTING message. Ignoring it");
return;
}
status = RECONNECTING;
if (onreconnecting) {
onreconnecting();
}
}
wsConfig.onreconnected = function() {
Logger.debug("--------- ONRECONNECTED -----------");
if (status === CONNECTED) {
Logger.error("Websocket already in CONNECTED state when receiving a new ONRECONNECTED message. Ignoring it");
return;
}
status = CONNECTED;
enabledPings = true;
updateNotReconnectIfLessThan();
usePing();
if (onreconnected) {
onreconnected();
}
}
wsConfig.onconnected = function() {
Logger.debug("--------- ONCONNECTED -----------");
if (status === CONNECTED) {
Logger.error("Websocket already in CONNECTED state when receiving a new ONCONNECTED message. Ignoring it");
return;
}
status = CONNECTED;
enabledPings = true;
usePing();
if (onconnected) {
onconnected();
}
}
wsConfig.onerror = function(error) {
Logger.debug("--------- ONERROR -----------");
status = DISCONNECTED;
if (onerror) {
onerror(error);
}
}
var ws = new WebSocketWithReconnection(wsConfig);
Logger.debug('Connecting websocket to URI: ' + wsConfig.uri);
var rpcBuilderOptions = {
request_timeout: configuration.rpc.requestTimeout,
ping_request_timeout: configuration.rpc.heartbeatRequestTimeout
};
var rpc = new RpcBuilder(RpcBuilder.packers.JsonRPC, rpcBuilderOptions, ws,
function(request) {
Logger.debug('Received request: ' + JSON.stringify(request));
try {
var func = configuration.rpc[request.method];
if (func === undefined) {
Logger.error("Method " + request.method + " not registered in client");
} else {
func(request.params, request);
}
} catch (err) {
Logger.error('Exception processing request: ' + JSON.stringify(request));
Logger.error(err);
}
});
this.send = function(method, params, callback) {
if (method !== 'ping') {
Logger.debug('Request: method:' + method + " params:" + JSON.stringify(params));
}
var requestTime = Date.now();
rpc.encode(method, params, function(error, result) {
if (error) {
try {
Logger.error("ERROR:" + error.message + " in Request: method:" +
method + " params:" + JSON.stringify(params) + " request:" +
error.request);
if (error.data) {
Logger.error("ERROR DATA:" + JSON.stringify(error.data));
}
} catch (e) {}
error.requestTime = requestTime;
}
if (callback) {
if (result != undefined && result.value !== 'pong') {
Logger.debug('Response: ' + JSON.stringify(result));
}
callback(error, result);
}
});
}
function updateNotReconnectIfLessThan() {
Logger.debug("notReconnectIfNumLessThan = " + pingNextNum + ' (old=' +
notReconnectIfNumLessThan + ')');
notReconnectIfNumLessThan = pingNextNum;
}
function sendPing() {
if (enabledPings) {
var params = null;
if (pingNextNum == 0 || pingNextNum == notReconnectIfNumLessThan) {
params = {
interval: configuration.heartbeat || PING_INTERVAL
};
}
pingNextNum++;
self.send('ping', params, (function(pingNum) {
return function(error, result) {
if (error) {
Logger.debug("Error in ping request #" + pingNum + " (" +
error.message + ")");
if (pingNum > notReconnectIfNumLessThan) {
enabledPings = false;
updateNotReconnectIfLessThan();
Logger.debug("Server did not respond to ping message #" +
pingNum + ". Reconnecting... ");
ws.reconnectWs();
}
}
}
})(pingNextNum));
} else {
Logger.debug("Trying to send ping, but ping is not enabled");
}
}
/*
* If configuration.hearbeat has any value, the ping-pong will work with the interval
* of configuration.hearbeat
*/
function usePing() {
if (!pingPongStarted) {
Logger.debug("Starting ping (if configured)")
pingPongStarted = true;
if (configuration.heartbeat != undefined) {
pingInterval = setInterval(sendPing, configuration.heartbeat);
sendPing();
}
}
}
this.close = function() {
Logger.debug("Closing jsonRpcClient explicitly by client");
if (pingInterval != undefined) {
Logger.debug("Clearing ping interval");
clearInterval(pingInterval);
}
pingPongStarted = false;
enabledPings = false;
if (configuration.sendCloseMessage) {
Logger.debug("Sending close message")
this.send('closeSession', null, function(error, result) {
if (error) {
Logger.error("Error sending close message: " + JSON.stringify(error));
}
ws.close();
});
} else {
ws.close();
}
}
// This method is only for testing
this.forceClose = function(millis) {
ws.forceClose(millis);
}
this.reconnect = function() {
ws.reconnectWs();
}
}
module.exports = JsonRpcClient;

View File

@ -0,0 +1,21 @@
/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var WebSocketWithReconnection = require('./webSocketWithReconnection');
exports.WebSocketWithReconnection = WebSocketWithReconnection;

View File

@ -0,0 +1,242 @@
/*
* (C) Copyright 2013-2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
"use strict";
var BrowserWebSocket = global.WebSocket || global.MozWebSocket;
var Logger = console;
/**
* Get either the `WebSocket` or `MozWebSocket` globals
* in the browser or try to resolve WebSocket-compatible
* interface exposed by `ws` for Node-like environment.
*/
/*var WebSocket = BrowserWebSocket;
if (!WebSocket && typeof window === 'undefined') {
try {
WebSocket = require('ws');
} catch (e) { }
}*/
//var SockJS = require('sockjs-client');
var MAX_RETRIES = 2000; // Forever...
var RETRY_TIME_MS = 3000; // FIXME: Implement exponential wait times...
var CONNECTING = 0;
var OPEN = 1;
var CLOSING = 2;
var CLOSED = 3;
/*
config = {
uri : wsUri,
useSockJS : true (use SockJS) / false (use WebSocket) by default,
onconnected : callback method to invoke when connection is successful,
ondisconnect : callback method to invoke when the connection is lost,
onreconnecting : callback method to invoke when the client is reconnecting,
onreconnected : callback method to invoke when the client succesfully reconnects,
};
*/
function WebSocketWithReconnection(config) {
var closing = false;
var registerMessageHandler;
var wsUri = config.uri;
var useSockJS = config.useSockJS;
var reconnecting = false;
var forcingDisconnection = false;
var ws;
if (useSockJS) {
ws = new SockJS(wsUri);
} else {
ws = new WebSocket(wsUri);
}
ws.onopen = function() {
logConnected(ws, wsUri);
if (config.onconnected) {
config.onconnected();
}
};
ws.onerror = function(error) {
Logger.error("Could not connect to " + wsUri + " (invoking onerror if defined)", error);
if (config.onerror) {
config.onerror(error);
}
};
function logConnected(ws, wsUri) {
try {
Logger.debug("WebSocket connected to " + wsUri);
} catch (e) {
Logger.error(e);
}
}
var reconnectionOnClose = function() {
if (ws.readyState === CLOSED) {
if (closing) {
Logger.debug("Connection closed by user");
} else {
Logger.debug("Connection closed unexpectecly. Reconnecting...");
reconnectToSameUri(MAX_RETRIES, 1);
}
} else {
Logger.debug("Close callback from previous websocket. Ignoring it");
}
};
ws.onclose = reconnectionOnClose;
function reconnectToSameUri(maxRetries, numRetries) {
Logger.debug("reconnectToSameUri (attempt #" + numRetries + ", max=" + maxRetries + ")");
if (numRetries === 1) {
if (reconnecting) {
Logger.warn("Trying to reconnectToNewUri when reconnecting... Ignoring this reconnection.")
return;
} else {
reconnecting = true;
}
if (config.onreconnecting) {
config.onreconnecting();
}
}
if (forcingDisconnection) {
reconnectToNewUri(maxRetries, numRetries, wsUri);
} else {
if (config.newWsUriOnReconnection) {
config.newWsUriOnReconnection(function(error, newWsUri) {
if (error) {
Logger.debug(error);
setTimeout(function() {
reconnectToSameUri(maxRetries, numRetries + 1);
}, RETRY_TIME_MS);
} else {
reconnectToNewUri(maxRetries, numRetries, newWsUri);
}
})
} else {
reconnectToNewUri(maxRetries, numRetries, wsUri);
}
}
}
// TODO Test retries. How to force not connection?
function reconnectToNewUri(maxRetries, numRetries, reconnectWsUri) {
Logger.debug("Reconnection attempt #" + numRetries);
ws.close();
wsUri = reconnectWsUri || wsUri;
var newWs;
if (useSockJS) {
newWs = new SockJS(wsUri);
} else {
newWs = new WebSocket(wsUri);
}
newWs.onopen = function() {
Logger.debug("Reconnected after " + numRetries + " attempts...");
logConnected(newWs, wsUri);
reconnecting = false;
registerMessageHandler();
if (config.onreconnected()) {
config.onreconnected();
}
newWs.onclose = reconnectionOnClose;
};
var onErrorOrClose = function(error) {
Logger.warn("Reconnection error: ", error);
if (numRetries === maxRetries) {
if (config.ondisconnect) {
config.ondisconnect();
}
} else {
setTimeout(function() {
reconnectToSameUri(maxRetries, numRetries + 1);
}, RETRY_TIME_MS);
}
};
newWs.onerror = onErrorOrClose;
ws = newWs;
}
this.close = function() {
closing = true;
ws.close();
};
// This method is only for testing
this.forceClose = function(millis) {
Logger.debug("Testing: Force WebSocket close");
if (millis) {
Logger.debug("Testing: Change wsUri for " + millis + " millis to simulate net failure");
var goodWsUri = wsUri;
wsUri = "wss://21.234.12.34.4:443/";
forcingDisconnection = true;
setTimeout(function() {
Logger.debug("Testing: Recover good wsUri " + goodWsUri);
wsUri = goodWsUri;
forcingDisconnection = false;
}, millis);
}
ws.close();
};
this.reconnectWs = function() {
Logger.debug("reconnectWs");
reconnectToSameUri(MAX_RETRIES, 1, wsUri);
};
this.send = function(message) {
ws.send(message);
};
this.addEventListener = function(type, callback) {
registerMessageHandler = function() {
ws.addEventListener(type, callback);
};
registerMessageHandler();
};
}
module.exports = WebSocketWithReconnection;

View File

@ -0,0 +1,822 @@
/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var defineProperty_IE8 = false
if(Object.defineProperty)
{
try
{
Object.defineProperty({}, "x", {});
}
catch(e)
{
defineProperty_IE8 = true
}
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
var EventEmitter = require('events').EventEmitter;
var inherits = require('inherits');
var packers = require('./packers');
var Mapper = require('./Mapper');
var BASE_TIMEOUT = 5000;
function unifyResponseMethods(responseMethods)
{
if(!responseMethods) return {};
for(var key in responseMethods)
{
var value = responseMethods[key];
if(typeof value == 'string')
responseMethods[key] =
{
response: value
}
};
return responseMethods;
};
function unifyTransport(transport)
{
if(!transport) return;
// Transport as a function
if(transport instanceof Function)
return {send: transport};
// WebSocket & DataChannel
if(transport.send instanceof Function)
return transport;
// Message API (Inter-window & WebWorker)
if(transport.postMessage instanceof Function)
{
transport.send = transport.postMessage;
return transport;
}
// Stream API
if(transport.write instanceof Function)
{
transport.send = transport.write;
return transport;
}
// Transports that only can receive messages, but not send
if(transport.onmessage !== undefined) return;
if(transport.pause instanceof Function) return;
throw new SyntaxError("Transport is not a function nor a valid object");
};
/**
* Representation of a RPC notification
*
* @class
*
* @constructor
*
* @param {String} method -method of the notification
* @param params - parameters of the notification
*/
function RpcNotification(method, params)
{
if(defineProperty_IE8)
{
this.method = method
this.params = params
}
else
{
Object.defineProperty(this, 'method', {value: method, enumerable: true});
Object.defineProperty(this, 'params', {value: params, enumerable: true});
}
};
/**
* @class
*
* @constructor
*
* @param {object} packer
*
* @param {object} [options]
*
* @param {object} [transport]
*
* @param {Function} [onRequest]
*/
function RpcBuilder(packer, options, transport, onRequest)
{
var self = this;
if(!packer)
throw new SyntaxError('Packer is not defined');
if(!packer.pack || !packer.unpack)
throw new SyntaxError('Packer is invalid');
var responseMethods = unifyResponseMethods(packer.responseMethods);
if(options instanceof Function)
{
if(transport != undefined)
throw new SyntaxError("There can't be parameters after onRequest");
onRequest = options;
transport = undefined;
options = undefined;
};
if(options && options.send instanceof Function)
{
if(transport && !(transport instanceof Function))
throw new SyntaxError("Only a function can be after transport");
onRequest = transport;
transport = options;
options = undefined;
};
if(transport instanceof Function)
{
if(onRequest != undefined)
throw new SyntaxError("There can't be parameters after onRequest");
onRequest = transport;
transport = undefined;
};
if(transport && transport.send instanceof Function)
if(onRequest && !(onRequest instanceof Function))
throw new SyntaxError("Only a function can be after transport");
options = options || {};
EventEmitter.call(this);
if(onRequest)
this.on('request', onRequest);
if(defineProperty_IE8)
this.peerID = options.peerID
else
Object.defineProperty(this, 'peerID', {value: options.peerID});
var max_retries = options.max_retries || 0;
function transportMessage(event)
{
self.decode(event.data || event);
};
this.getTransport = function()
{
return transport;
}
this.setTransport = function(value)
{
// Remove listener from old transport
if(transport)
{
// W3C transports
if(transport.removeEventListener)
transport.removeEventListener('message', transportMessage);
// Node.js Streams API
else if(transport.removeListener)
transport.removeListener('data', transportMessage);
};
// Set listener on new transport
if(value)
{
// W3C transports
if(value.addEventListener)
value.addEventListener('message', transportMessage);
// Node.js Streams API
else if(value.addListener)
value.addListener('data', transportMessage);
};
transport = unifyTransport(value);
}
if(!defineProperty_IE8)
Object.defineProperty(this, 'transport',
{
get: this.getTransport.bind(this),
set: this.setTransport.bind(this)
})
this.setTransport(transport);
var request_timeout = options.request_timeout || BASE_TIMEOUT;
var ping_request_timeout = options.ping_request_timeout || request_timeout;
var response_timeout = options.response_timeout || BASE_TIMEOUT;
var duplicates_timeout = options.duplicates_timeout || BASE_TIMEOUT;
var requestID = 0;
var requests = new Mapper();
var responses = new Mapper();
var processedResponses = new Mapper();
var message2Key = {};
/**
* Store the response to prevent to process duplicate request later
*/
function storeResponse(message, id, dest)
{
var response =
{
message: message,
/** Timeout to auto-clean old responses */
timeout: setTimeout(function()
{
responses.remove(id, dest);
},
response_timeout)
};
responses.set(response, id, dest);
};
/**
* Store the response to ignore duplicated messages later
*/
function storeProcessedResponse(ack, from)
{
var timeout = setTimeout(function()
{
processedResponses.remove(ack, from);
},
duplicates_timeout);
processedResponses.set(timeout, ack, from);
};
/**
* Representation of a RPC request
*
* @class
* @extends RpcNotification
*
* @constructor
*
* @param {String} method -method of the notification
* @param params - parameters of the notification
* @param {Integer} id - identifier of the request
* @param [from] - source of the notification
*/
function RpcRequest(method, params, id, from, transport)
{
RpcNotification.call(this, method, params);
this.getTransport = function()
{
return transport;
}
this.setTransport = function(value)
{
transport = unifyTransport(value);
}
if(!defineProperty_IE8)
Object.defineProperty(this, 'transport',
{
get: this.getTransport.bind(this),
set: this.setTransport.bind(this)
})
var response = responses.get(id, from);
/**
* @constant {Boolean} duplicated
*/
if(!(transport || self.getTransport()))
{
if(defineProperty_IE8)
this.duplicated = Boolean(response)
else
Object.defineProperty(this, 'duplicated',
{
value: Boolean(response)
});
}
var responseMethod = responseMethods[method];
this.pack = packer.pack.bind(packer, this, id)
/**
* Generate a response to this request
*
* @param {Error} [error]
* @param {*} [result]
*
* @returns {string}
*/
this.reply = function(error, result, transport)
{
// Fix optional parameters
if(error instanceof Function || error && error.send instanceof Function)
{
if(result != undefined)
throw new SyntaxError("There can't be parameters after callback");
transport = error;
result = null;
error = undefined;
}
else if(result instanceof Function
|| result && result.send instanceof Function)
{
if(transport != undefined)
throw new SyntaxError("There can't be parameters after callback");
transport = result;
result = null;
};
transport = unifyTransport(transport);
// Duplicated request, remove old response timeout
if(response)
clearTimeout(response.timeout);
if(from != undefined)
{
if(error)
error.dest = from;
if(result)
result.dest = from;
};
var message;
// New request or overriden one, create new response with provided data
if(error || result != undefined)
{
if(self.peerID != undefined)
{
if(error)
error.from = self.peerID;
else
result.from = self.peerID;
}
// Protocol indicates that responses has own request methods
if(responseMethod)
{
if(responseMethod.error == undefined && error)
message =
{
error: error
};
else
{
var method = error
? responseMethod.error
: responseMethod.response;
message =
{
method: method,
params: error || result
};
}
}
else
message =
{
error: error,
result: result
};
message = packer.pack(message, id);
}
// Duplicate & not-overriden request, re-send old response
else if(response)
message = response.message;
// New empty reply, response null value
else
message = packer.pack({result: null}, id);
// Store the response to prevent to process a duplicated request later
storeResponse(message, id, from);
// Return the stored response so it can be directly send back
transport = transport || this.getTransport() || self.getTransport();
if(transport)
return transport.send(message);
return message;
}
};
inherits(RpcRequest, RpcNotification);
function cancel(message)
{
var key = message2Key[message];
if(!key) return;
delete message2Key[message];
var request = requests.pop(key.id, key.dest);
if(!request) return;
clearTimeout(request.timeout);
// Start duplicated responses timeout
storeProcessedResponse(key.id, key.dest);
};
/**
* Allow to cancel a request and don't wait for a response
*
* If `message` is not given, cancel all the request
*/
this.cancel = function(message)
{
if(message) return cancel(message);
for(var message in message2Key)
cancel(message);
};
this.close = function()
{
// Prevent to receive new messages
var transport = this.getTransport();
if(transport && transport.close)
transport.close();
// Request & processed responses
this.cancel();
processedResponses.forEach(clearTimeout);
// Responses
responses.forEach(function(response)
{
clearTimeout(response.timeout);
});
};
/**
* Generates and encode a JsonRPC 2.0 message
*
* @param {String} method -method of the notification
* @param params - parameters of the notification
* @param [dest] - destination of the notification
* @param {object} [transport] - transport where to send the message
* @param [callback] - function called when a response to this request is
* received. If not defined, a notification will be send instead
*
* @returns {string} A raw JsonRPC 2.0 request or notification string
*/
this.encode = function(method, params, dest, transport, callback)
{
// Fix optional parameters
if(params instanceof Function)
{
if(dest != undefined)
throw new SyntaxError("There can't be parameters after callback");
callback = params;
transport = undefined;
dest = undefined;
params = undefined;
}
else if(dest instanceof Function)
{
if(transport != undefined)
throw new SyntaxError("There can't be parameters after callback");
callback = dest;
transport = undefined;
dest = undefined;
}
else if(transport instanceof Function)
{
if(callback != undefined)
throw new SyntaxError("There can't be parameters after callback");
callback = transport;
transport = undefined;
};
if(self.peerID != undefined)
{
params = params || {};
params.from = self.peerID;
};
if(dest != undefined)
{
params = params || {};
params.dest = dest;
};
// Encode message
var message =
{
method: method,
params: params
};
if(callback)
{
var id = requestID++;
var retried = 0;
message = packer.pack(message, id);
function dispatchCallback(error, result)
{
self.cancel(message);
callback(error, result);
};
var request =
{
message: message,
callback: dispatchCallback,
responseMethods: responseMethods[method] || {}
};
var encode_transport = unifyTransport(transport);
function sendRequest(transport)
{
var rt = (method === 'ping' ? ping_request_timeout : request_timeout);
request.timeout = setTimeout(timeout, rt*Math.pow(2, retried++));
message2Key[message] = {id: id, dest: dest};
requests.set(request, id, dest);
transport = transport || encode_transport || self.getTransport();
if(transport)
return transport.send(message);
return message;
};
function retry(transport)
{
transport = unifyTransport(transport);
console.warn(retried+' retry for request message:',message);
var timeout = processedResponses.pop(id, dest);
clearTimeout(timeout);
return sendRequest(transport);
};
function timeout()
{
if(retried < max_retries)
return retry(transport);
var error = new Error('Request has timed out');
error.request = message;
error.retry = retry;
dispatchCallback(error)
};
return sendRequest(transport);
};
// Return the packed message
message = packer.pack(message);
transport = transport || this.getTransport();
if(transport)
return transport.send(message);
return message;
};
/**
* Decode and process a JsonRPC 2.0 message
*
* @param {string} message - string with the content of the message
*
* @returns {RpcNotification|RpcRequest|undefined} - the representation of the
* notification or the request. If a response was processed, it will return
* `undefined` to notify that it was processed
*
* @throws {TypeError} - Message is not defined
*/
this.decode = function(message, transport)
{
if(!message)
throw new TypeError("Message is not defined");
try
{
message = packer.unpack(message);
}
catch(e)
{
// Ignore invalid messages
return console.debug(e, message);
};
var id = message.id;
var ack = message.ack;
var method = message.method;
var params = message.params || {};
var from = params.from;
var dest = params.dest;
// Ignore messages send by us
if(self.peerID != undefined && from == self.peerID) return;
// Notification
if(id == undefined && ack == undefined)
{
var notification = new RpcNotification(method, params);
if(self.emit('request', notification)) return;
return notification;
};
function processRequest()
{
// If we have a transport and it's a duplicated request, reply inmediatly
transport = unifyTransport(transport) || self.getTransport();
if(transport)
{
var response = responses.get(id, from);
if(response)
return transport.send(response.message);
};
var idAck = (id != undefined) ? id : ack;
var request = new RpcRequest(method, params, idAck, from, transport);
if(self.emit('request', request)) return;
return request;
};
function processResponse(request, error, result)
{
request.callback(error, result);
};
function duplicatedResponse(timeout)
{
console.warn("Response already processed", message);
// Update duplicated responses timeout
clearTimeout(timeout);
storeProcessedResponse(ack, from);
};
// Request, or response with own method
if(method)
{
// Check if it's a response with own method
if(dest == undefined || dest == self.peerID)
{
var request = requests.get(ack, from);
if(request)
{
var responseMethods = request.responseMethods;
if(method == responseMethods.error)
return processResponse(request, params);
if(method == responseMethods.response)
return processResponse(request, null, params);
return processRequest();
}
var processed = processedResponses.get(ack, from);
if(processed)
return duplicatedResponse(processed);
}
// Request
return processRequest();
};
var error = message.error;
var result = message.result;
// Ignore responses not send to us
if(error && error.dest && error.dest != self.peerID) return;
if(result && result.dest && result.dest != self.peerID) return;
// Response
var request = requests.get(ack, from);
if(!request)
{
var processed = processedResponses.get(ack, from);
if(processed)
return duplicatedResponse(processed);
return console.warn("No callback was defined for this message", message);
};
// Process response
processResponse(request, error, result);
};
};
inherits(RpcBuilder, EventEmitter);
RpcBuilder.RpcNotification = RpcNotification;
module.exports = RpcBuilder;
var clients = require('./clients');
var transports = require('./clients/transports');
RpcBuilder.clients = clients;
RpcBuilder.clients.transports = transports;
RpcBuilder.packers = packers;

View File

@ -0,0 +1,103 @@
/**
* JsonRPC 2.0 packer
*/
/**
* Pack a JsonRPC 2.0 message
*
* @param {Object} message - object to be packaged. It requires to have all the
* fields needed by the JsonRPC 2.0 message that it's going to be generated
*
* @return {String} - the stringified JsonRPC 2.0 message
*/
function pack(message, id)
{
var result =
{
jsonrpc: "2.0"
};
// Request
if(message.method)
{
result.method = message.method;
if(message.params)
result.params = message.params;
// Request is a notification
if(id != undefined)
result.id = id;
}
// Response
else if(id != undefined)
{
if(message.error)
{
if(message.result !== undefined)
throw new TypeError("Both result and error are defined");
result.error = message.error;
}
else if(message.result !== undefined)
result.result = message.result;
else
throw new TypeError("No result or error is defined");
result.id = id;
};
return JSON.stringify(result);
};
/**
* Unpack a JsonRPC 2.0 message
*
* @param {String} message - string with the content of the JsonRPC 2.0 message
*
* @throws {TypeError} - Invalid JsonRPC version
*
* @return {Object} - object filled with the JsonRPC 2.0 message content
*/
function unpack(message)
{
var result = message;
if(typeof message === 'string' || message instanceof String) {
result = JSON.parse(message);
}
// Check if it's a valid message
var version = result.jsonrpc;
if(version !== '2.0')
throw new TypeError("Invalid JsonRPC version '" + version + "': " + message);
// Response
if(result.method == undefined)
{
if(result.id == undefined)
throw new TypeError("Invalid message: "+message);
var result_defined = result.result !== undefined;
var error_defined = result.error !== undefined;
// Check only result or error is defined, not both or none
if(result_defined && error_defined)
throw new TypeError("Both result and error are defined: "+message);
if(!result_defined && !error_defined)
throw new TypeError("No result or error is defined: "+message);
result.ack = result.id;
delete result.id;
}
// Return unpacked message
return result;
};
exports.pack = pack;
exports.unpack = unpack;

View File

@ -0,0 +1,13 @@
function pack(message)
{
throw new TypeError("Not yet implemented");
};
function unpack(message)
{
throw new TypeError("Not yet implemented");
};
exports.pack = pack;
exports.unpack = unpack;

View File

@ -0,0 +1,6 @@
var JsonRPC = require('./JsonRPC');
var XmlRPC = require('./XmlRPC');
exports.JsonRPC = JsonRPC;
exports.XmlRPC = XmlRPC;

View File

@ -0,0 +1,777 @@
/*
* (C) Copyright 2014-2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var freeice = require('freeice')
var inherits = require('inherits')
var UAParser = require('ua-parser-js')
var uuid = require('uuid')
var hark = require('hark')
var EventEmitter = require('events').EventEmitter
var recursive = require('merge').recursive.bind(undefined, true)
var sdpTranslator = require('sdp-translator')
var logger = window.Logger || console
// var gUM = navigator.mediaDevices.getUserMedia || function (constraints) {
// return new Promise(navigator.getUserMedia(constraints, function (stream) {
// videoStream = stream
// start()
// }).eror(callback));
// }
try {
require('kurento-browser-extensions')
} catch (error) {
if (typeof getScreenConstraints === 'undefined') {
logger.warn('screen sharing is not available')
getScreenConstraints = function getScreenConstraints(sendSource, callback) {
callback(new Error("This library is not enabled for screen sharing"))
}
}
}
var MEDIA_CONSTRAINTS = {
audio: true,
video: {
width: 640,
framerate: 15
}
}
// Somehow, the UAParser constructor gets an empty window object.
// We need to pass the user agent string in order to get information
var ua = (window && window.navigator) ? window.navigator.userAgent : ''
var parser = new UAParser(ua)
var browser = parser.getBrowser()
var usePlanB = false
if (browser.name === 'Chrome' || browser.name === 'Chromium') {
logger.debug(browser.name + ": using SDP PlanB")
usePlanB = true
}
function noop(error) {
if (error) logger.error(error)
}
function trackStop(track) {
track.stop && track.stop()
}
function streamStop(stream) {
stream.getTracks().forEach(trackStop)
}
/**
* Returns a string representation of a SessionDescription object.
*/
var dumpSDP = function (description) {
if (typeof description === 'undefined' || description === null) {
return ''
}
return 'type: ' + description.type + '\r\n' + description.sdp
}
function bufferizeCandidates(pc, onerror) {
var candidatesQueue = []
pc.addEventListener('signalingstatechange', function () {
if (this.signalingState === 'stable') {
while (candidatesQueue.length) {
var entry = candidatesQueue.shift()
this.addIceCandidate(entry.candidate, entry.callback, entry.callback)
}
}
})
return function (candidate, callback) {
callback = callback || onerror
switch (pc.signalingState) {
case 'closed':
callback(new Error('PeerConnection object is closed'))
break
case 'stable':
if (pc.remoteDescription) {
pc.addIceCandidate(candidate, callback, callback)
break
}
default:
candidatesQueue.push({
candidate: candidate,
callback: callback
})
}
}
}
/* Simulcast utilities */
function removeFIDFromOffer(sdp) {
var n = sdp.indexOf("a=ssrc-group:FID");
if (n > 0) {
return sdp.slice(0, n);
} else {
return sdp;
}
}
function getSimulcastInfo(videoStream) {
var videoTracks = videoStream.getVideoTracks();
if (!videoTracks.length) {
logger.warn('No video tracks available in the video stream')
return ''
}
var lines = [
'a=x-google-flag:conference',
'a=ssrc-group:SIM 1 2 3',
'a=ssrc:1 cname:localVideo',
'a=ssrc:1 msid:' + videoStream.id + ' ' + videoTracks[0].id,
'a=ssrc:1 mslabel:' + videoStream.id,
'a=ssrc:1 label:' + videoTracks[0].id,
'a=ssrc:2 cname:localVideo',
'a=ssrc:2 msid:' + videoStream.id + ' ' + videoTracks[0].id,
'a=ssrc:2 mslabel:' + videoStream.id,
'a=ssrc:2 label:' + videoTracks[0].id,
'a=ssrc:3 cname:localVideo',
'a=ssrc:3 msid:' + videoStream.id + ' ' + videoTracks[0].id,
'a=ssrc:3 mslabel:' + videoStream.id,
'a=ssrc:3 label:' + videoTracks[0].id
];
lines.push('');
return lines.join('\n');
}
/**
* Wrapper object of an RTCPeerConnection. This object is aimed to simplify the
* development of WebRTC-based applications.
*
* @constructor module:kurentoUtils.WebRtcPeer
*
* @param {String} mode Mode in which the PeerConnection will be configured.
* Valid values are: 'recv', 'send', and 'sendRecv'
* @param localVideo Video tag for the local stream
* @param remoteVideo Video tag for the remote stream
* @param {MediaStream} videoStream Stream to be used as primary source
* (typically video and audio, or only video if combined with audioStream) for
* localVideo and to be added as stream to the RTCPeerConnection
* @param {MediaStream} audioStream Stream to be used as second source
* (typically for audio) for localVideo and to be added as stream to the
* RTCPeerConnection
*/
function WebRtcPeer(mode, options, callback) {
if (!(this instanceof WebRtcPeer)) {
return new WebRtcPeer(mode, options, callback)
}
WebRtcPeer.super_.call(this)
if (options instanceof Function) {
callback = options
options = undefined
}
options = options || {}
callback = (callback || noop).bind(this)
var self = this
var localVideo = options.localVideo
var remoteVideo = options.remoteVideo
var videoStream = options.videoStream
var audioStream = options.audioStream
var mediaConstraints = options.mediaConstraints
var connectionConstraints = options.connectionConstraints
var pc = options.peerConnection
var sendSource = options.sendSource || 'webcam'
var dataChannelConfig = options.dataChannelConfig
var useDataChannels = options.dataChannels || false
var dataChannel
var guid = uuid.v4()
var configuration = recursive({
iceServers: freeice()
},
options.configuration)
var onicecandidate = options.onicecandidate
if (onicecandidate) this.on('icecandidate', onicecandidate)
var oncandidategatheringdone = options.oncandidategatheringdone
if (oncandidategatheringdone) {
this.on('candidategatheringdone', oncandidategatheringdone)
}
var simulcast = options.simulcast
var multistream = options.multistream
var interop = new sdpTranslator.Interop()
var candidatesQueueOut = []
var candidategatheringdone = false
Object.defineProperties(this, {
'peerConnection': {
get: function () {
return pc
}
},
'id': {
value: options.id || guid,
writable: false
},
'remoteVideo': {
get: function () {
return remoteVideo
}
},
'localVideo': {
get: function () {
return localVideo
}
},
'dataChannel': {
get: function () {
return dataChannel
}
},
/**
* @member {(external:ImageData|undefined)} currentFrame
*/
'currentFrame': {
get: function () {
// [ToDo] Find solution when we have a remote stream but we didn't set
// a remoteVideo tag
if (!remoteVideo) return;
if (remoteVideo.readyState < remoteVideo.HAVE_CURRENT_DATA)
throw new Error('No video stream data available')
var canvas = document.createElement('canvas')
canvas.width = remoteVideo.videoWidth
canvas.height = remoteVideo.videoHeight
canvas.getContext('2d').drawImage(remoteVideo, 0, 0)
return canvas
}
}
})
// Init PeerConnection
if (!pc) {
pc = new RTCPeerConnection(configuration);
if (useDataChannels && !dataChannel) {
var dcId = 'WebRtcPeer-' + self.id
var dcOptions = undefined
if (dataChannelConfig) {
dcId = dataChannelConfig.id || dcId
dcOptions = dataChannelConfig.options
}
dataChannel = pc.createDataChannel(dcId, dcOptions);
if (dataChannelConfig) {
dataChannel.onopen = dataChannelConfig.onopen;
dataChannel.onclose = dataChannelConfig.onclose;
dataChannel.onmessage = dataChannelConfig.onmessage;
dataChannel.onbufferedamountlow = dataChannelConfig.onbufferedamountlow;
dataChannel.onerror = dataChannelConfig.onerror || noop;
}
}
}
pc.addEventListener('icecandidate', function (event) {
var candidate = event.candidate
if (EventEmitter.listenerCount(self, 'icecandidate') ||
EventEmitter.listenerCount(
self, 'candidategatheringdone')) {
if (candidate) {
var cand
if (multistream && usePlanB) {
cand = interop.candidateToUnifiedPlan(candidate)
} else {
cand = candidate
}
self.emit('icecandidate', cand)
candidategatheringdone = false
} else if (!candidategatheringdone) {
self.emit('candidategatheringdone')
candidategatheringdone = true
}
} else if (!candidategatheringdone) {
// Not listening to 'icecandidate' or 'candidategatheringdone' events, queue
// the candidate until one of them is listened
candidatesQueueOut.push(candidate)
if (!candidate) candidategatheringdone = true
}
})
pc.ontrack = options.onaddstream
pc.onnegotiationneeded = options.onnegotiationneeded
this.on('newListener', function (event, listener) {
if (event === 'icecandidate' || event === 'candidategatheringdone') {
while (candidatesQueueOut.length) {
var candidate = candidatesQueueOut.shift()
if (!candidate === (event === 'candidategatheringdone')) {
listener(candidate)
}
}
}
})
var addIceCandidate = bufferizeCandidates(pc)
/**
* Callback function invoked when an ICE candidate is received. Developers are
* expected to invoke this function in order to complete the SDP negotiation.
*
* @function module:kurentoUtils.WebRtcPeer.prototype.addIceCandidate
*
* @param iceCandidate - Literal object with the ICE candidate description
* @param callback - Called when the ICE candidate has been added.
*/
this.addIceCandidate = function (iceCandidate, callback) {
var candidate
if (multistream && usePlanB) {
candidate = interop.candidateToPlanB(iceCandidate)
} else {
candidate = new RTCIceCandidate(iceCandidate)
}
logger.debug('Remote ICE candidate received', iceCandidate)
callback = (callback || noop).bind(this)
addIceCandidate(candidate, callback)
}
this.generateOffer = function (callback) {
callback = callback.bind(this)
var offerAudio = true
var offerVideo = true
// Constraints must have both blocks
if (mediaConstraints) {
offerAudio = (typeof mediaConstraints.audio === 'boolean') ?
mediaConstraints.audio : true
offerVideo = (typeof mediaConstraints.video === 'boolean') ?
mediaConstraints.video : true
}
var browserDependantConstraints = {
offerToReceiveAudio: (mode !== 'sendonly' && offerAudio),
offerToReceiveVideo: (mode !== 'sendonly' && offerVideo)
}
//FIXME: clarify possible constraints passed to createOffer()
/*var constraints = recursive(browserDependantConstraints,
connectionConstraints)*/
var constraints = browserDependantConstraints;
logger.debug('constraints: ' + JSON.stringify(constraints))
pc.createOffer(constraints).then(function (offer) {
logger.debug('Created SDP offer')
offer = mangleSdpToAddSimulcast(offer)
return pc.setLocalDescription(offer)
}).then(function () {
var localDescription = pc.localDescription
logger.debug('Local description set', localDescription.sdp)
if (multistream && usePlanB) {
localDescription = interop.toUnifiedPlan(localDescription)
logger.debug('offer::origPlanB->UnifiedPlan', dumpSDP(
localDescription))
}
callback(null, localDescription.sdp, self.processAnswer.bind(
self))
}).catch(callback)
}
this.getLocalSessionDescriptor = function () {
return pc.localDescription
}
this.getRemoteSessionDescriptor = function () {
return pc.remoteDescription
}
function setRemoteVideo() {
if (remoteVideo) {
var stream = pc.getRemoteStreams()[0]
var url = stream ? URL.createObjectURL(stream) : ''
remoteVideo.pause()
remoteVideo.src = url
remoteVideo.load()
logger.debug('Remote URL:', url)
}
}
this.showLocalVideo = function () {
localVideo.src = URL.createObjectURL(videoStream)
localVideo.muted = true
}
this.send = function (data) {
if (dataChannel && dataChannel.readyState === 'open') {
dataChannel.send(data)
} else {
logger.warn(
'Trying to send data over a non-existing or closed data channel')
}
}
/**
* Callback function invoked when a SDP answer is received. Developers are
* expected to invoke this function in order to complete the SDP negotiation.
*
* @function module:kurentoUtils.WebRtcPeer.prototype.processAnswer
*
* @param sdpAnswer - Description of sdpAnswer
* @param callback -
* Invoked after the SDP answer is processed, or there is an error.
*/
this.processAnswer = function (sdpAnswer, callback) {
callback = (callback || noop).bind(this)
var answer = new RTCSessionDescription({
type: 'answer',
sdp: sdpAnswer
})
if (multistream && usePlanB) {
var planBAnswer = interop.toPlanB(answer)
logger.debug('asnwer::planB', dumpSDP(planBAnswer))
answer = planBAnswer
}
logger.debug('SDP answer received, setting remote description')
if (pc.signalingState === 'closed') {
return callback('PeerConnection is closed')
}
pc.setRemoteDescription(answer, function () {
setRemoteVideo()
callback()
},
callback)
}
/**
* Callback function invoked when a SDP offer is received. Developers are
* expected to invoke this function in order to complete the SDP negotiation.
*
* @function module:kurentoUtils.WebRtcPeer.prototype.processOffer
*
* @param sdpOffer - Description of sdpOffer
* @param callback - Called when the remote description has been set
* successfully.
*/
this.processOffer = function (sdpOffer, callback) {
callback = callback.bind(this)
var offer = new RTCSessionDescription({
type: 'offer',
sdp: sdpOffer
})
if (multistream && usePlanB) {
var planBOffer = interop.toPlanB(offer)
logger.debug('offer::planB', dumpSDP(planBOffer))
offer = planBOffer
}
logger.debug('SDP offer received, setting remote description')
if (pc.signalingState === 'closed') {
return callback('PeerConnection is closed')
}
pc.setRemoteDescription(offer).then(function () {
return setRemoteVideo()
}).then(function () {
return pc.createAnswer()
}).then(function (answer) {
answer = mangleSdpToAddSimulcast(answer)
logger.debug('Created SDP answer')
return pc.setLocalDescription(answer)
}).then(function () {
var localDescription = pc.localDescription
if (multistream && usePlanB) {
localDescription = interop.toUnifiedPlan(localDescription)
logger.debug('answer::origPlanB->UnifiedPlan', dumpSDP(
localDescription))
}
logger.debug('Local description set', localDescription.sdp)
callback(null, localDescription.sdp)
}).catch(callback)
}
function mangleSdpToAddSimulcast(answer) {
if (simulcast) {
if (browser.name === 'Chrome' || browser.name === 'Chromium') {
logger.debug('Adding multicast info')
answer = new RTCSessionDescription({
'type': answer.type,
'sdp': removeFIDFromOffer(answer.sdp) + getSimulcastInfo(
videoStream)
})
} else {
logger.warn('Simulcast is only available in Chrome browser.')
}
}
return answer
}
/**
* This function creates the RTCPeerConnection object taking into account the
* properties received in the constructor. It starts the SDP negotiation
* process: generates the SDP offer and invokes the onsdpoffer callback. This
* callback is expected to send the SDP offer, in order to obtain an SDP
* answer from another peer.
*/
function start() {
if (pc.signalingState === 'closed') {
callback(
'The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue'
)
}
if (videoStream && localVideo) {
self.showLocalVideo()
}
if (videoStream) {
pc.addStream(videoStream)
}
if (audioStream) {
pc.addStream(audioStream)
}
// [Hack] https://code.google.com/p/chromium/issues/detail?id=443558
var browser = parser.getBrowser()
if (mode === 'sendonly' &&
(browser.name === 'Chrome' || browser.name === 'Chromium') &&
browser.major === 39) {
mode = 'sendrecv'
}
callback()
}
if (mode !== 'recvonly' && !videoStream && !audioStream) {
function getMedia(constraints) {
if (constraints === undefined) {
constraints = MEDIA_CONSTRAINTS
}
navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
videoStream = stream
start()
}).catch(callback);
}
if (sendSource === 'webcam') {
getMedia(mediaConstraints)
} else {
getScreenConstraints(sendSource, function (error, constraints_) {
if (error)
return callback(error)
constraints = [mediaConstraints]
constraints.unshift(constraints_)
getMedia(recursive.apply(undefined, constraints))
}, guid)
}
} else {
setTimeout(start, 0)
}
this.on('_dispose', function () {
if (localVideo) {
localVideo.pause()
localVideo.src = ''
localVideo.load()
//Unmute local video in case the video tag is later used for remote video
localVideo.muted = false
}
if (remoteVideo) {
remoteVideo.pause()
remoteVideo.src = ''
remoteVideo.load()
}
self.removeAllListeners()
if (window.cancelChooseDesktopMedia !== undefined) {
window.cancelChooseDesktopMedia(guid)
}
})
}
inherits(WebRtcPeer, EventEmitter)
function createEnableDescriptor(type) {
var method = 'get' + type + 'Tracks'
return {
enumerable: true,
get: function () {
// [ToDo] Should return undefined if not all tracks have the same value?
if (!this.peerConnection) return
var streams = this.peerConnection.getLocalStreams()
if (!streams.length) return
for (var i = 0, stream; stream = streams[i]; i++) {
var tracks = stream[method]()
for (var j = 0, track; track = tracks[j]; j++)
if (!track.enabled) return false
}
return true
},
set: function (value) {
function trackSetEnable(track) {
track.enabled = value
}
this.peerConnection.getLocalStreams().forEach(function (stream) {
stream[method]().forEach(trackSetEnable)
})
}
}
}
Object.defineProperties(WebRtcPeer.prototype, {
'enabled': {
enumerable: true,
get: function () {
return this.audioEnabled && this.videoEnabled
},
set: function (value) {
this.audioEnabled = this.videoEnabled = value
}
},
'audioEnabled': createEnableDescriptor('Audio'),
'videoEnabled': createEnableDescriptor('Video')
})
WebRtcPeer.prototype.getLocalStream = function (index) {
if (this.peerConnection) {
return this.peerConnection.getLocalStreams()[index || 0]
}
}
WebRtcPeer.prototype.getRemoteStream = function (index) {
if (this.peerConnection) {
return this.peerConnection.getRemoteStreams()[index || 0]
}
}
/**
* @description This method frees the resources used by WebRtcPeer.
*
* @function module:kurentoUtils.WebRtcPeer.prototype.dispose
*/
WebRtcPeer.prototype.dispose = function () {
logger.debug('Disposing WebRtcPeer')
var pc = this.peerConnection
var dc = this.dataChannel
try {
if (dc) {
if (dc.signalingState === 'closed') return
dc.close()
}
if (pc) {
if (pc.signalingState === 'closed') return
pc.getLocalStreams().forEach(streamStop)
// FIXME This is not yet implemented in firefox
// if(videoStream) pc.removeStream(videoStream);
// if(audioStream) pc.removeStream(audioStream);
pc.close()
}
} catch (err) {
logger.warn('Exception disposing webrtc peer ' + err)
}
this.emit('_dispose')
}
//
// Specialized child classes
//
function WebRtcPeerRecvonly(options, callback) {
if (!(this instanceof WebRtcPeerRecvonly)) {
return new WebRtcPeerRecvonly(options, callback)
}
WebRtcPeerRecvonly.super_.call(this, 'recvonly', options, callback)
}
inherits(WebRtcPeerRecvonly, WebRtcPeer)
function WebRtcPeerSendonly(options, callback) {
if (!(this instanceof WebRtcPeerSendonly)) {
return new WebRtcPeerSendonly(options, callback)
}
WebRtcPeerSendonly.super_.call(this, 'sendonly', options, callback)
}
inherits(WebRtcPeerSendonly, WebRtcPeer)
function WebRtcPeerSendrecv(options, callback) {
if (!(this instanceof WebRtcPeerSendrecv)) {
return new WebRtcPeerSendrecv(options, callback)
}
WebRtcPeerSendrecv.super_.call(this, 'sendrecv', options, callback)
}
inherits(WebRtcPeerSendrecv, WebRtcPeer)
function harkUtils(stream, options) {
return hark(stream, options);
}
exports.bufferizeCandidates = bufferizeCandidates
exports.WebRtcPeerRecvonly = WebRtcPeerRecvonly
exports.WebRtcPeerSendonly = WebRtcPeerSendonly
exports.WebRtcPeerSendrecv = WebRtcPeerSendrecv
exports.hark = harkUtils

View File

@ -0,0 +1,18 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Don't run on Internet Explorer 8, so exit inmediatly
if (window.addEventListener) module.exports = require('./index');

View File

@ -0,0 +1,30 @@
/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* This module contains a set of reusable components that have been found useful
* during the development of the WebRTC applications with Kurento.
*
* @module kurentoUtils
*
* @copyright 2014 Kurento (http://kurento.org/)
* @license ALv2
*/
var WebRtcPeer = require('./WebRtcPeer');
exports.WebRtcPeer = WebRtcPeer;

View File

@ -31,6 +31,7 @@ export class OpenVidu {
constructor() {
this.openVidu = new OpenViduInternal();
console.info("'OpenVidu' initialized");
};
initSession(apiKey: string, sessionId: string): Session;
@ -73,8 +74,10 @@ export class OpenVidu {
}
}
}
return new Publisher(this.openVidu.initPublisherTagged(parentId, cameraOptions, callback), parentId);
var publisher = new Publisher(this.openVidu.initPublisherTagged(parentId, cameraOptions, callback), parentId);
console.info("'Publisher' initialized");
return publisher;
} else {
alert("Browser not supported");
@ -100,9 +103,16 @@ export class OpenVidu {
navigator.mediaDevices.enumerateDevices().then((deviceInfos) => {
callback(null, deviceInfos);
}).catch((error) => {
console.log("Error getting devices: " + error);
console.error("Error getting devices", error);
callback(error, null);
});
}
enableProdMode() {
console.log = function() {};
console.debug = function() {};
console.info = function() {};
console.warn = function() {};
}
}

View File

@ -52,8 +52,17 @@ export class Publisher {
return this;
}
subscribeToRemote() {
this.stream.subscribeToMyRemote();
}
on(eventName: string, callback) {
this.ee.addListener(eventName, event => {
if (event) {
console.info("Event '" + eventName + "' triggered by 'Publisher'", event);
} else {
console.info("Event '" + eventName + "' triggered by 'Publisher'");
}
callback(event);
});
if (eventName == 'videoElementCreated') {
@ -62,8 +71,7 @@ export class Publisher {
element: this.stream.getVideoElement()
}]);
} else {
this.stream.addEventListener('video-element-created-by-stream', (element) => {
console.warn('Publisher emitting videoElementCreated');
this.stream.addOnceEventListener('video-element-created-by-stream', (element) => {
this.id = element.id;
this.ee.emitEvent('videoElementCreated', [{
element: element.element
@ -71,6 +79,42 @@ export class Publisher {
});
}
}
if (eventName == 'videoPlaying') {
var video = this.stream.getVideoElement();
if (!this.stream.displayMyRemote() && video &&
video.currentTime > 0 &&
video.paused == false &&
video.ended == false &&
video.readyState == 4) {
this.ee.emitEvent('videoPlaying', [{
element: this.stream.getVideoElement()
}]);
} else {
this.stream.addOnceEventListener('video-is-playing', (element) => {
this.ee.emitEvent('videoPlaying', [{
element: element.element
}]);
});
}
}
if (eventName == 'remoteVideoPlaying') {
var video = this.stream.getVideoElement();
if (this.stream.displayMyRemote() && video &&
video.currentTime > 0 &&
video.paused == false &&
video.ended == false &&
video.readyState == 4) {
this.ee.emitEvent('remoteVideoPlaying', [{
element: this.stream.getVideoElement()
}]);
} else {
this.stream.addOnceEventListener('remote-video-is-playing', (element) => {
this.ee.emitEvent('remoteVideoPlaying', [{
element: element.element
}]);
});
}
}
if (eventName == 'streamCreated') {
if (this.stream.isReady) {
this.ee.emitEvent('streamCreated', [{ stream: this.stream }]);

View File

@ -86,6 +86,11 @@ export class Session {
on(eventName: string, callback) {
this.session.addEventListener(eventName, event => {
if (event) {
console.info("Event '" + eventName + "' triggered by 'Session'", event);
} else {
console.info("Event '" + eventName + "' triggered by 'Session'");
}
callback(event);
});
}

View File

@ -19,6 +19,11 @@ export class Subscriber {
on(eventName: string, callback) {
this.ee.addListener(eventName, event => {
if (event) {
console.info("Event '" + eventName + "' triggered by 'Subscriber'", event);
} else {
console.info("Event '" + eventName + "' triggered by 'Subscriber'");
}
callback(event);
});
if (eventName == 'videoElementCreated') {
@ -27,7 +32,7 @@ export class Subscriber {
element: this.stream.getVideoElement()
}]);
} else {
this.stream.addEventListener('video-element-created-by-stream', element => {
this.stream.addOnceEventListener('video-element-created-by-stream', element => {
console.warn("Subscriber emitting videoElementCreated");
this.id = element.id;
this.ee.emitEvent('videoElementCreated', [{
@ -36,5 +41,23 @@ export class Subscriber {
});
}
}
if (eventName == 'videoPlaying') {
var video = this.stream.getVideoElement();
if (!this.stream.displayMyRemote() && video &&
video.currentTime > 0 &&
video.paused == false &&
video.ended == false &&
video.readyState == 4) {
this.ee.emitEvent('videoPlaying', [{
element: this.stream.getVideoElement()
}]);
} else {
this.stream.addOnceEventListener('video-is-playing', (element) => {
this.ee.emitEvent('videoPlaying', [{
element: element.element
}]);
});
}
}
}
}

View File

@ -1,6 +1,6 @@
{
"compilerOptions": {
"declaration": true,
"allowJs": true,
"target": "es5",
"module": "commonjs",
//"noImplicitAny": true,

View File

@ -21,36 +21,18 @@ export class Connection {
constructor( private openVidu: OpenViduInternal, private local: boolean, private room: SessionInternal, private options?: ConnectionOptions ) {
console.info( "'Connection' created (" + ( local ? "local" : "remote" ) + ")" + ( local ? "" : ", with 'connectionId' [" + (options ? options.id : '') + "] " ));
if ( options ) {
this.connectionId = options.id;
this.data = options.metadata;
if ( options.streams ) {
for ( let streamOptions of options.streams ) {
let streamOpts = {
id: streamOptions.id,
connection: this,
recvVideo: ( streamOptions.recvVideo == undefined ? true : streamOptions.recvVideo ),
recvAudio: ( streamOptions.recvAudio == undefined ? true : streamOptions.recvAudio ),
audio: streamOptions.audio,
video: streamOptions.video,
data: streamOptions.data,
mediaConstraints: streamOptions.mediaConstraints,
audioOnly: streamOptions.audioOnly
}
let stream = new Stream( openVidu, false, room, streamOpts );
this.addStream( stream );
this.streamsOpts.push( streamOpts );
}
this.initStreams(options);
}
}
console.log( "New " + ( local ? "local " : "remote " ) + "participant " + this.connectionId
+ ", streams opts: ", this.streamsOpts );
}
addStream( stream: Stream ) {
@ -85,4 +67,27 @@ export class Connection {
}
});
}
initStreams(options) {
for ( let streamOptions of options.streams ) {
let streamOpts = {
id: streamOptions.id,
connection: this,
recvVideo: ( streamOptions.recvVideo == undefined ? true : streamOptions.recvVideo ),
recvAudio: ( streamOptions.recvAudio == undefined ? true : streamOptions.recvAudio ),
audio: streamOptions.audio,
video: streamOptions.video,
data: streamOptions.data,
mediaConstraints: streamOptions.mediaConstraints,
audioOnly: streamOptions.audioOnly,
}
let stream = new Stream(this.openVidu, false, this.room, streamOpts );
this.addStream( stream );
this.streamsOpts.push( streamOpts );
}
console.info("Remote 'Connection' with 'connectionId' [" + this.connectionId + "] is now configured for receiving Streams with options: ", this.streamsOpts );
}
}

View File

@ -16,7 +16,7 @@
*/
import { SessionInternal, SessionOptions } from './SessionInternal';
import { Stream } from './Stream';
import * as RpcBuilder from 'kurento-jsonrpc';
import * as RpcBuilder from '../KurentoUtils/kurento-jsonrpc';
export type Callback<T> = (error?: any, openVidu?: T) => void;
@ -37,20 +37,19 @@ export class OpenViduInternal {
/* NEW METHODS */
initSession(sessionId) {
console.log("Session initialized!");
console.info("'Session' initialized with 'sessionId' [" + sessionId + "]");
this.session = new SessionInternal(this, sessionId);
return this.session;
}
initPublisherTagged(parentId: string, cameraOptions: any, callback?) {
console.log("Publisher tagged initialized!");
this.getCamera(cameraOptions);
if (callback == null) {
this.camera.requestCameraAccess((error, camera) => {
if (error) {
console.log("Error accessing the camera");
console.error("Error accessing the camera", error);
}
else {
this.camera.setVideoElement(this.cameraReady(camera!, parentId));
@ -79,8 +78,6 @@ export class OpenViduInternal {
}
initPublisher(cameraOptions: any, callback) {
console.log("Publisher initialized!");
this.getCamera(cameraOptions);
this.camera.requestCameraAccess((error, camera) => {
if (error) callback(error);
@ -184,7 +181,7 @@ export class OpenViduInternal {
}
private disconnectCallback() {
console.log('Websocket connection lost');
console.warn('Websocket connection lost');
if (this.isRoomAvailable()) {
this.session.onLostConnection();
} else {
@ -193,7 +190,7 @@ export class OpenViduInternal {
}
private reconnectingCallback() {
console.log('Websocket connection lost (reconnecting)');
console.warn('Websocket connection lost (reconnecting)');
if (this.isRoomAvailable()) {
this.session.onLostConnection();
} else {
@ -202,7 +199,7 @@ export class OpenViduInternal {
}
private reconnectedCallback() {
console.log('Websocket reconnected');
console.warn('Websocket reconnected');
}
private onParticipantJoined(params) {
@ -271,12 +268,12 @@ export class OpenViduInternal {
for (let index in this.rpcParams) {
if (this.rpcParams.hasOwnProperty(index)) {
params[index] = this.rpcParams[index];
console.log('RPC param added to request {' + index + ': ' + this.rpcParams[index] + '}');
console.debug('RPC param added to request {' + index + ': ' + this.rpcParams[index] + '}');
}
}
}
console.log('Sending request: { method:"' + method + '", params: ' + JSON.stringify(params) + ' }');
console.debug('Sending request: {method:"' + method + '", params: ' + JSON.stringify(params) + '}');
this.jsonRpcClient.send(method, params, callback);
}

View File

@ -237,13 +237,13 @@ export class SessionInternal {
}
unsuscribe(stream) {
console.log("Unsubscribing from " + stream.getId());
console.info("Unsubscribing from " + stream.getId());
this.openVidu.sendRequest('unsubscribeFromVideo', {
sender: stream.getId()
},
function (error, response) {
if (error) {
console.error(error);
console.error("Error unsubscribing from Subscriber", error);
} else {
console.info("Unsubscribed correctly from " + stream.getId());
}
@ -254,15 +254,25 @@ export class SessionInternal {
options.metadata = this.participants[options.id].data;
let connection = new Connection(this.openVidu, false, this, options);
// Get the existing Connection created on 'onParticipantJoined' for
// existing participants or create a new one for new participants
let connection = this.participants[options.id];
if (connection) {
// Update existing Connection
connection.options = options;
connection.initStreams(options);
} else {
// Create new Connection
connection = new Connection(this.openVidu, false, this, options);
}
let pid = connection.connectionId;
if (!(pid in this.participants)) {
console.info("Publisher not found in participants list by its id", pid);
console.debug("Remote Connection not found in connections list by its id [" + pid + "]");
} else {
console.log("Publisher found in participants list by its id", pid);
console.debug("Remote Connection found in connections list by its id [" + pid + "]");
}
//replacing old connection (this one has streams)
connection.creationTime = this.participants[pid].creationTime;
this.participants[pid] = connection;
@ -288,12 +298,11 @@ export class SessionInternal {
let pid = connection.connectionId;
if (!(pid in this.participants)) {
console.log("New participant to participants list with id", pid);
this.participants[pid] = connection;
} else {
//use existing so that we don't lose streams info
console.info("Participant already exists in participants list with " +
"the same id, old:", this.participants[pid], ", joined now:", connection);
console.warn("Connection already exists in connections list with " +
"the same connectionId, old:", this.participants[pid], ", joined now:", connection);
connection = this.participants[pid];
}
@ -354,7 +363,7 @@ export class SessionInternal {
onNewMessage(msg) {
console.log("New message: " + JSON.stringify(msg));
console.info("New message: " + JSON.stringify(msg));
let room = msg.room;
let user = msg.user;
let message = msg.message;
@ -401,7 +410,7 @@ export class SessionInternal {
onRoomClosed(msg) {
console.log("Room closed: " + JSON.stringify(msg));
console.info("Room closed: " + JSON.stringify(msg));
let room = msg.room;
if (room !== undefined) {
this.ee.emitEvent('room-closed', [{
@ -422,7 +431,7 @@ export class SessionInternal {
return;
}
console.log('Lost connection in room ' + this.id);
console.warn('Lost connection in Session ' + this.id);
let room = this.id;
if (room !== undefined) {
this.ee.emitEvent('lost-connection', [{ room }]);
@ -451,7 +460,7 @@ export class SessionInternal {
forced = !!forced;
console.log("Leaving room (forced=" + forced + ")");
console.info("Leaving Session (forced=" + forced + ")");
if (this.connected && !forced) {
this.openVidu.sendRequest('leaveRoom', function (error, response) {
@ -485,7 +494,7 @@ export class SessionInternal {
if (connection === this.localParticipant) {
console.log("Unpublishing my media (I'm " + connection.connectionId + ")");
console.info("Unpublishing my media (I'm " + connection.connectionId + ")");
delete this.localParticipant;
this.openVidu.sendRequest('unpublishVideo', function (error, response) {
if (error) {
@ -513,7 +522,7 @@ export class SessionInternal {
delete this.participants[connection.connectionId];
connection.dispose();
console.log("Unpublishing my media (I'm " + connection.connectionId + ")");
console.info("Unpublishing my media (I'm " + connection.connectionId + ")");
delete this.localParticipant;
this.openVidu.sendRequest('unpublishVideo', function (error, response) {
if (error) {

View File

@ -9,7 +9,7 @@ import { Connection } from './Connection';
import { SessionInternal } from './SessionInternal';
import { OpenViduInternal, Callback } from './OpenViduInternal';
import EventEmitter = require('wolfy87-eventemitter');
import * as kurentoUtils from 'kurento-utils';
import * as kurentoUtils from '../KurentoUtils/kurento-utils-js';
import * as adapter from 'webrtc-adapter';
declare var navigator: any;
@ -73,7 +73,7 @@ export class Stream {
private audioOnly = false;
private videoSrc: string;
private videoSrcObject: MediaStream | null;
private parentId: string;
public isReady: boolean = false;
public isVideoELementCreated: boolean = false;
@ -98,15 +98,15 @@ export class Stream {
this.audioOnly = options.audioOnly || false;
this.addEventListener('src-added', (srcEvent) => {
this.videoSrc = srcEvent.src;
if (this.video) this.video.src = srcEvent.src;
console.warn("Videosrc [" + srcEvent.src + "] added to stream [" + this.getId() + "]");
this.videoSrcObject = srcEvent.srcObject;
if (this.video) this.video.srcObject = srcEvent.srcObject;
console.debug("Video srcObject [" + srcEvent.srcObject + "] added to stream [" + this.getId() + "]");
});
}
emitSrcEvent(wrstream) {
this.ee.emitEvent('src-added', [{
src: URL.createObjectURL(wrstream)
srcObject: wrstream
}]);
}
@ -114,8 +114,8 @@ export class Stream {
this.ee.emitEvent('stream-ready'), [{}];
}
getVideoSrc() {
return this.videoSrc;
getVideoSrcObject() {
return this.videoSrcObject;
}
removeVideo(parentElement: string);
@ -193,12 +193,12 @@ export class Stream {
}
onDataChannelOpen(event) {
console.log('Data channel is opened');
console.debug('Data channel is opened');
this.dataChannelOpened = true;
}
onDataChannelClosed(event) {
console.log('Data channel is closed');
console.debug('Data channel is closed');
this.dataChannelOpened = false;
}
@ -209,7 +209,7 @@ export class Stream {
if (!this.dataChannelOpened) {
throw new Error('Data channel is not opened');
}
console.log("Sending through data channel: " + data);
console.info("Sending through data channel: " + data);
this.wp.send(data);
}
@ -254,18 +254,24 @@ export class Stream {
this.video = document.createElement('video');
this.video.id = 'native-video-' + this.getId();
this.video.id = (this.local ? 'local-' : 'remote-') + 'video-' + this.getId();
this.video.autoplay = true;
this.video.controls = false;
this.video.src = this.videoSrc;
this.video.srcObject = this.videoSrcObject;
this.videoElements.push({
thumb: thumbnailId,
video: this.video
});
if (this.local) {
if (this.local && !this.displayMyRemote()) {
this.video.muted = true;
this.video.onplay = () => {
console.info("Local 'Stream' with id [" + this.getId() + "] video is now playing");
this.ee.emitEvent('video-is-playing', [{
element: this.video
}]);
};
} else {
this.video.title = this.getId();
}
@ -330,11 +336,7 @@ export class Stream {
}
getId() {
if (this.connection) {
return this.connection.connectionId + "_" + this.id;
} else {
return this.id + "_webcam";
}
return this.connection.connectionId + "_" + this.id;
}
getRTCPeerConnection() {
@ -430,7 +432,7 @@ export class Stream {
+ JSON.stringify(error));
}
console.log("Sending SDP offer to publish as "
console.debug("Sending SDP offer to publish as "
+ this.getId(), sdpOfferParam);
this.openVidu.sendRequest("publishVideo", {
@ -441,10 +443,8 @@ export class Stream {
if (error) {
console.error("Error on publishVideo: " + JSON.stringify(error));
} else {
this.room.emitEvent('stream-published', [{
stream: this
}]);
this.processSdpAnswer(response.sdpAnswer);
console.info("'Publisher' succesfully published to session");
}
});
}
@ -454,7 +454,7 @@ export class Stream {
return console.error("(subscribe) SDP offer error: "
+ JSON.stringify(error));
}
console.log("Sending SDP offer to subscribe to "
console.debug("Sending SDP offer to subscribe to "
+ this.getId(), sdpOfferParam);
this.openVidu.sendRequest("receiveVideoFrom", {
sender: this.getId(),
@ -492,14 +492,14 @@ export class Stream {
}
if (this.displayMyRemote()) {
this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv(options, error => {
this.wp = kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv(options, error => {
if (error) {
return console.error(error);
}
this.wp.generateOffer(sdpOfferCallback.bind(this));
});
} else {
this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerSendonly(options, error => {
this.wp = kurentoUtils.WebRtcPeer.WebRtcPeerSendonly(options, error => {
if (error) {
return console.error(error);
}
@ -511,21 +511,21 @@ export class Stream {
audio: this.recvAudio,
video: !this.audioOnly
};
console.log("Constraints of generate SDP offer (subscribing)",
console.debug("'Session.subscribe(Stream)' called. Constraints of generate SDP offer",
offerConstraints);
let options = {
onicecandidate: this.connection.sendIceCandidate.bind(this.connection),
mediaConstraints: offerConstraints
}
this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options, error => {
this.wp = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options, error => {
if (error) {
return console.error(error);
}
this.wp.generateOffer(sdpOfferCallback.bind(this));
});
}
console.log("Waiting for SDP offer to be generated ("
+ (this.local ? "local" : "remote") + " peer: " + this.getId() + ")");
console.debug("Waiting for SDP offer to be generated ("
+ (this.local ? "local" : "remote") + " 'Stream': " + this.getId() + ")");
}
publish() {
@ -560,7 +560,7 @@ export class Stream {
type: 'answer',
sdp: sdpAnswer,
});
console.log(this.getId() + ": set peer connection with recvd SDP answer",
console.debug(this.getId() + ": set peer connection with recvd SDP answer",
sdpAnswer);
let participantId = this.getId();
let pc = this.wp.peerConnection;
@ -569,7 +569,7 @@ export class Stream {
// except when showMyRemote is true
if (!this.local || this.displayMyRemote()) {
this.wrStream = pc.getRemoteStreams()[0];
console.log("Peer remote stream", this.wrStream);
console.debug("Peer remote stream", this.wrStream);
if (this.wrStream != undefined) {
@ -594,9 +594,19 @@ export class Stream {
for (let videoElement of this.videoElements) {
let thumbnailId = videoElement.thumb;
let video = videoElement.video;
video.src = URL.createObjectURL(this.wrStream);
video.srcObject = this.wrStream;
video.onplay = () => {
console.log(this.getId() + ': ' + 'Video playing');
if (this.local && this.displayMyRemote()) {
console.info("Your own remote 'Stream' with id [" + this.getId() + "] video is now playing");
this.ee.emitEvent('remote-video-is-playing', [{
element: this.video
}]);
} else if (!this.local && !this.displayMyRemote()) {
console.info("Remote 'Stream' with id [" + this.getId() + "] video is now playing");
this.ee.emitEvent('video-is-playing', [{
element: this.video
}]);
}
//show(thumbnailId);
//this.hideSpinner(this.getId());
};
@ -629,7 +639,7 @@ export class Stream {
this.speechEvent.stop();
}
console.log(this.getId() + ": Stream '" + this.id + "' unpublished");
console.info(this.getId() + ": Stream '" + this.id + "' unpublished");
}
dispose() {
@ -663,6 +673,6 @@ export class Stream {
this.speechEvent.stop();
}
console.log(this.getId() + ": Stream '" + this.id + "' disposed");
console.info((this.local ? "Local " : "Remote ") + "'Stream' with id [" + this.getId() + "]' has been succesfully disposed");
}
}

View File

@ -1,6 +1,6 @@
{
"compilerOptions": {
"declaration": true,
"allowJs": true,
"target": "es5",
"module": "commonjs",
//"noImplicitAny": true,

View File

@ -1,5 +1,8 @@
VERSION="$1"
# openvidu-hello-world
cp openvidu-browser/src/main/resources/static/js/openvidu-browser-"$1".js ../openvidu-tutorials/openvidu-hello-world/web/openvidu-browser-"$1".js
# openvidu-insecure-js
cp openvidu-browser/src/main/resources/static/js/openvidu-browser-"$1".js ../openvidu-tutorials/openvidu-insecure-js/web/openvidu-browser-"$1".js