diff --git a/kurento-recorder/README.md b/kurento-recorder/README.md new file mode 100644 index 00000000..60751a8b --- /dev/null +++ b/kurento-recorder/README.md @@ -0,0 +1,122 @@ +[![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0) +[![Documentation badge](https://readthedocs.org/projects/fiware-orion/badge/?version=latest)](http://doc-kurento.readthedocs.org/en/latest/) +[![Docker badge](https://img.shields.io/docker/pulls/fiware/orion.svg)](https://hub.docker.com/r/fiware/stream-oriented-kurento/) +[![Support badge]( https://img.shields.io/badge/support-sof-yellowgreen.svg)](http://stackoverflow.com/questions/tagged/kurento) + +[![][KurentoImage]][Kurento] + +Copyright © 2013-2016 [Kurento]. Licensed under [Apache 2.0 License]. + +kurento-recorder +================ + +Kurento JavaScript Tutorial: WebRTC in loopback with recorder. + +Running this tutorial +--------------------- + +In order to run this tutorial, please read the following [instructions]. + +What is Kurento +--------------- + +Kurento is an open source software project providing a platform suitable +for creating modular applications with advanced real-time communication +capabilities. For knowing more about Kurento, please visit the Kurento +project website: http://www.kurento.org. + +Kurento is part of [FIWARE]. For further information on the relationship of +FIWARE and Kurento check the [Kurento FIWARE Catalog Entry] + +Kurento is part of the [NUBOMEDIA] research initiative. + +Documentation +------------- + +The Kurento project provides detailed [documentation] including tutorials, +installation and development guides. A simplified version of the documentation +can be found on [readthedocs.org]. The [Open API specification] a.k.a. Kurento +Protocol is also available on [apiary.io]. + +Source +------ + +Code for other Kurento projects can be found in the [GitHub Kurento Group]. + +News and Website +---------------- + +Check the [Kurento blog] +Follow us on Twitter @[kurentoms]. + +Issue tracker +------------- + +Issues and bug reports should be posted to the [GitHub Kurento bugtracker] + +Licensing and distribution +-------------------------- + +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. + +Contribution policy +------------------- + +You can contribute to the Kurento community through bug-reports, bug-fixes, new +code or new documentation. For contributing to the Kurento community, drop a +post to the [Kurento Public Mailing List] providing full information about your +contribution and its value. In your contributions, you must comply with the +following guidelines + +* You must specify the specific contents of your contribution either through a + detailed bug description, through a pull-request or through a patch. +* You must specify the licensing restrictions of the code you contribute. +* For newly created code to be incorporated in the Kurento code-base, you must + accept Kurento to own the code copyright, so that its open source nature is + guaranteed. +* You must justify appropriately the need and value of your contribution. The + Kurento project has no obligations in relation to accepting contributions + from third parties. +* The Kurento project leaders have the right of asking for further + explanations, tests or validations of any code contributed to the community + before it being incorporated into the Kurento code-base. You must be ready to + addressing all these kind of concerns before having your code approved. + +Support +------- + +The Kurento project provides community support through the [Kurento Public +Mailing List] and through [StackOverflow] using the tags *kurento* and +*fiware-kurento*. + +Before asking for support, please read first the [Kurento Netiquette Guidelines] + +[documentation]: http://www.kurento.org/documentation +[FIWARE]: http://www.fiware.org +[GitHub Kurento bugtracker]: https://github.com/Kurento/bugtracker/issues +[GitHub Kurento Group]: https://github.com/kurento +[kurentoms]: http://twitter.com/kurentoms +[Kurento]: http://kurento.org +[Kurento Blog]: http://www.kurento.org/blog +[Kurento FIWARE Catalog Entry]: http://catalogue.fiware.org/enablers/stream-oriented-kurento +[Kurento Netiquette Guidelines]: http://www.kurento.org/blog/kurento-netiquette-guidelines +[Kurento Public Mailing list]: https://groups.google.com/forum/#!forum/kurento +[KurentoImage]: https://secure.gravatar.com/avatar/21a2a12c56b2a91c8918d5779f1778bf?s=120 +[Apache 2.0 License]: http://www.apache.org/licenses/LICENSE-2.0 +[NUBOMEDIA]: http://www.nubomedia.eu +[StackOverflow]: http://stackoverflow.com/search?q=kurento +[Read-the-docs]: http://read-the-docs.readthedocs.org/ +[readthedocs.org]: http://kurento.readthedocs.org/ +[Open API specification]: http://kurento.github.io/doc-kurento/ +[apiary.io]: http://docs.streamoriented.apiary.io/ +[instructions]: http://www.kurento.org/docs/current/tutorials/js/tutorial-recorder.html diff --git a/kurento-recorder/keys/README.md b/kurento-recorder/keys/README.md new file mode 100644 index 00000000..9603ced3 --- /dev/null +++ b/kurento-recorder/keys/README.md @@ -0,0 +1,7 @@ +[![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0) +[![Documentation badge](https://readthedocs.org/projects/fiware-orion/badge/?version=latest)](http://doc-kurento.readthedocs.org/en/latest/) +[![Docker badge](https://img.shields.io/docker/pulls/fiware/orion.svg)](https://hub.docker.com/r/fiware/stream-oriented-kurento/) +[![Support badge]( https://img.shields.io/badge/support-sof-yellowgreen.svg)](http://stackoverflow.com/questions/tagged/kurento) + +This folder contains a dummy self-signed certificate only for demo purposses, +**DON'T USE IT IN PRODUCTION**. diff --git a/kurento-recorder/keys/server.crt b/kurento-recorder/keys/server.crt new file mode 100644 index 00000000..65e608da --- /dev/null +++ b/kurento-recorder/keys/server.crt @@ -0,0 +1,19 @@ +-----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----- diff --git a/kurento-recorder/keys/server.csr b/kurento-recorder/keys/server.csr new file mode 100644 index 00000000..6615b130 --- /dev/null +++ b/kurento-recorder/keys/server.csr @@ -0,0 +1,16 @@ +-----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----- diff --git a/kurento-recorder/keys/server.key b/kurento-recorder/keys/server.key new file mode 100644 index 00000000..a69a0a27 --- /dev/null +++ b/kurento-recorder/keys/server.key @@ -0,0 +1,27 @@ +-----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----- diff --git a/kurento-recorder/package.json b/kurento-recorder/package.json new file mode 100644 index 00000000..7e283485 --- /dev/null +++ b/kurento-recorder/package.json @@ -0,0 +1,19 @@ +{ + "name": "kurento-recorder", + "version": "6.6.1", + "private": true, + "scripts": { + "postinstall": "cd static && bower install" + }, + "dependencies": { + "cookie-parser": "^1.3.5", + "express": "~4.12.4", + "express-session": "~1.10.3", + "minimist": "^1.1.1", + "ws": "~1.0.1", + "kurento-client": "6.6.0" + }, + "devDependencies": { + "bower": "^1.4.1" + } +} diff --git a/kurento-recorder/server.js b/kurento-recorder/server.js new file mode 100755 index 00000000..859e205e --- /dev/null +++ b/kurento-recorder/server.js @@ -0,0 +1,427 @@ +/* + * (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 path = require('path'); +var url = require('url'); +var cookieParser = require('cookie-parser') +var express = require('express'); +var session = require('express-session') +var minimist = require('minimist'); +var ws = require('ws'); +var kurento = require('kurento-client'); +var fs = require('fs'); +var https = require('https'); + +var argv = minimist(process.argv.slice(2), { + default: { + as_uri: 'https://localhost:8443/', + ws_uri: 'ws://localhost:8888/kurento', + file_uri: 'file:///tmp/recorder_demo.webm', // file to be stored in media server + } +}); + +var options = +{ + key: fs.readFileSync('keys/server.key'), + cert: fs.readFileSync('keys/server.crt') +}; + +var app = express(); + +/* + * Management of sessions + */ +app.use(cookieParser()); + +var sessionHandler = session({ + secret : 'none', + rolling : true, + resave : true, + saveUninitialized : true +}); + +app.use(sessionHandler); + +/* + * Definition of global variables. + */ +var sessions = {}; +var candidatesQueue = {}; +var kurentoClient = null; + +/* + * Server startup + */ +var asUrl = url.parse(argv.as_uri); +var port = asUrl.port; +var server = https.createServer(options, app).listen(port, function() { + console.log('Kurento Tutorial started'); + console.log('Open ' + url.format(asUrl) + ' with a WebRTC capable browser'); +}); + +var wss = new ws.Server({ + server : server, + path : '/helloworld' +}); + +/* + * Management of WebSocket messages + */ +wss.on('connection', function(ws) { + var sessionId = null; + var request = ws.upgradeReq; + var response = { + writeHead : {} + }; + + sessionHandler(request, response, function(err) { + sessionId = request.session.id; + 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'); + stop(sessionId); + }); + + ws.on('message', function(_message) { + var message = JSON.parse(_message); + console.log('Connection ' + sessionId + ' received message ', message); + + switch (message.id) { + case 'start': + sessionId = request.session.id; + start(sessionId, ws, message.sdpOffer, function(error, sdpAnswer) { + if (error) { + return ws.send(JSON.stringify({ + id : 'error', + message : error + })); + } + ws.send(JSON.stringify({ + id : 'startResponse', + sdpAnswer : sdpAnswer + })); + }); + break; + + case 'play': + sessionId = request.session.id; + play(sessionId, ws, message.sdpOffer, function(error, sdpAnswer) { + if (error) { + return ws.send(JSON.stringify({ + id : 'error', + message : error + })); + } + ws.send(JSON.stringify({ + id : 'playResponse', + sdpAnswer : sdpAnswer + })); + }); + break; + + case 'stop': + stop(sessionId); + break; + + case 'onIceCandidate': + onIceCandidate(sessionId, message.candidate); + break; + + default: + ws.send(JSON.stringify({ + id : 'error', + message : 'Invalid message ' + message + })); + break; + } + + }); +}); + +/* + * Definition of functions + */ + +// Recover kurentoClient for the first time. +function getKurentoClient(callback) { + if (kurentoClient !== null) { + return callback(null, kurentoClient); + } + + kurento(argv.ws_uri, function(error, _kurentoClient) { + if (error) { + console.log("Could not find media server at address " + argv.ws_uri); + return callback("Could not find media server at address" + argv.ws_uri + + ". Exiting with error " + error); + } + + kurentoClient = _kurentoClient; + callback(null, kurentoClient); + }); +} + +function start(sessionId, ws, sdpOffer, callback) { + if (!sessionId) { + return callback('Cannot use undefined sessionId'); + } + + getKurentoClient(function(error, kurentoClient) { + if (error) { + return callback(error); + } + + kurentoClient.create('MediaPipeline', function(error, pipeline) { + if (error) { + return callback(error); + } + + var elements = + [ + {type: 'RecorderEndpoint', params: {uri : argv.file_uri}}, + {type: 'WebRtcEndpoint', params: {}} + ]; + + createMediaElements(elements, pipeline, function(error, elements) { + + var recorder = elements[0]; + var webRtc = elements[1]; + + if (error) { + pipeline.release(); + return callback(error); + } + + if (candidatesQueue[sessionId]) { + while(candidatesQueue[sessionId].length) { + var candidate = candidatesQueue[sessionId].shift(); + webRtc.addIceCandidate(candidate); + } + } + + connectMediaElementsWithRecorder(kurentoClient, webRtc, webRtc, recorder, function(error) { + if (error) { + pipeline.release(); + return callback(error); + } + + webRtc.on('OnIceCandidate', function(event) { + var candidate = kurento.getComplexType('IceCandidate')(event.candidate); + ws.send(JSON.stringify({ + id : 'iceCandidate', + candidate : candidate + })); + }); + + webRtc.processOffer(sdpOffer, function(error, sdpAnswer) { + if (error) { + pipeline.release(); + return callback(error); + } + + sessions[sessionId] = { + 'pipeline' : pipeline, + 'webRtcEndpoint' : webRtc, + 'recorder' : recorder + } + return callback(null, sdpAnswer); + }); + + webRtc.gatherCandidates(function(error) { + if (error) { + return callback(error); + } + }); + + recorder.record(function(error) { + if (error) return onError(error); + console.log("record"); + }); + + }); + }); + }); + }); +} + +function play(sessionId, ws, sdpOffer, callback) { + if (!sessionId) { + return callback('Cannot use undefined sessionId'); + } + + getKurentoClient(function(error, kurentoClient) { + if (error) { + return callback(error); + } + + kurentoClient.create('MediaPipeline', function(error, pipeline) { + if (error) { + return callback(error); + } + + var options = {uri : argv.file_uri} + createMediaElementsWithOption('PlayerEndpoint', options, pipeline, function(error, player) { + + if (error) return onError(error); + + + player.on('EndOfStream', function(event){ + console.log('END OF STREAM'); + pipeline.release(); + }); + + + player.play(function(error) { + if (error) return onError(error); + console.log("Playing ..."); + + createMediaElements('WebRtcEndpoint', pipeline, function(error, webRtcEndpoint) { + if (error) { + pipeline.release(); + return callback(error); + } + + webRtcEndpoint.on('OnIceCandidate', function(event) { + var candidate = kurento.getComplexType('IceCandidate')(event.candidate); + console.log('OnIceCandidate called'); + ws.send(JSON.stringify({ + id : 'iceCandidate', + candidate : candidate + })); + }); + + if (candidatesQueue[sessionId]) { + console.log('candidatesQueue'); + while(candidatesQueue[sessionId].length) { + var candidate = candidatesQueue[sessionId].shift(); + webRtcEndpoint.addIceCandidate(candidate); + } + } + + webRtcEndpoint.processOffer(sdpOffer, function(error, sdpAnswer) { + console.log('processOffer'); + if (error) { + pipeline.release(); + return callback(error); + } + + sessions[sessionId] = { + 'pipeline' : pipeline, + 'webRtcEndpoint' : webRtcEndpoint, + } + + connectMediaElements(player, webRtcEndpoint,function(error) { + if (error) return onError(error); + + console.log('connectMediaElements'); + return callback(null, sdpAnswer); + }); + }); + + webRtcEndpoint.gatherCandidates(function(error) { + if (error) { + return callback(error); + } + }); + }); + }); + }); + }); + }); +} + +function createMediaElements(elements, pipeline, callback) { + pipeline.create(elements, function(error, elements) { + if (error) { + return callback(error); + } + + return callback(null, elements); + }); +} + +function createMediaElementsWithOption(elements, option, pipeline, callback) { + pipeline.create(elements, option,function(error, elements) { + if (error) { + return callback(error); + } + + return callback(null, elements); + }); +} + +function connectMediaElements(target_1, target2, callback) { + target_1.connect(target2, function(error) { + if (error) { + return callback(error); + } + return callback(null); + }); +} + +function connectMediaElementsWithRecorder(client, webRtc1, webRtc2, recorder, callback) { + client.connect(webRtc1, webRtc2, recorder, function(error) { + if (error) { + return callback(error); + } + return callback(null); + }); +} + +function stop(sessionId) { + if (sessions[sessionId]) { + var pipeline = sessions[sessionId].pipeline; + var webRtcEndpoint = sessions[sessionId].webRtcEndpoint; + var recorder = sessions[sessionId].recorder; + + console.info('Releasing pipeline'); + + delete sessions[sessionId]; + delete candidatesQueue[sessionId]; + + if(recorder) + recorder.stop(); + pipeline.release(); + + } +} + +function onIceCandidate(sessionId, _candidate) { + var candidate = kurento.getComplexType('IceCandidate')(_candidate); + + if (sessions[sessionId]) { + console.info('Sending candidate'); + var webRtcEndpoint = sessions[sessionId].webRtcEndpoint; + webRtcEndpoint.addIceCandidate(candidate); + } + else { + console.info('Queueing candidate'); + if (!candidatesQueue[sessionId]) { + candidatesQueue[sessionId] = []; + } + candidatesQueue[sessionId].push(candidate); + } +} + +app.use(express.static(path.join(__dirname, 'static'))); + diff --git a/kurento-recorder/static/bower.json b/kurento-recorder/static/bower.json new file mode 100644 index 00000000..97cf4853 --- /dev/null +++ b/kurento-recorder/static/bower.json @@ -0,0 +1,30 @@ +{ + "name": "kurento-recorder", + "version": "6.6.2-dev", + "description": "Kurento demo with Recorder", + "authors": [ + "Kurento " + ], + "main": "index.html", + "moduleType": [ + "globals" + ], + "license": "ALv2", + "homepage": "http://www.kurento.org/", + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "adapter.js": "v0.2.9", + "bootstrap": "~3.3.0", + "ekko-lightbox": "~3.3.0", + "demo-console": "1.5.1", + "kurento-client": "master", + "kurento-utils": "master" + } +} diff --git a/kurento-recorder/static/css/kurento.css b/kurento-recorder/static/css/kurento.css new file mode 100644 index 00000000..376484ef --- /dev/null +++ b/kurento-recorder/static/css/kurento.css @@ -0,0 +1,53 @@ +/* + * (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. + * + */ +@CHARSET "UTF-8"; + +html { + position: relative; + min-height: 100%; +} + +body { + padding-top: 40px; + body +} + +video,#console { + display: block; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, box-shadow + ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} + +#console { + min-height: 120px; + max-height: 360px; +} + +.col-md-2 { + width: 80px; + padding-top: 190px; +} diff --git a/kurento-recorder/static/img/kurento.png b/kurento-recorder/static/img/kurento.png new file mode 100644 index 00000000..6f1a4ad3 Binary files /dev/null and b/kurento-recorder/static/img/kurento.png differ diff --git a/kurento-recorder/static/img/naevatec.png b/kurento-recorder/static/img/naevatec.png new file mode 100644 index 00000000..05ee7041 Binary files /dev/null and b/kurento-recorder/static/img/naevatec.png differ diff --git a/kurento-recorder/static/img/pipeline.png b/kurento-recorder/static/img/pipeline.png new file mode 100644 index 00000000..fad16bba Binary files /dev/null and b/kurento-recorder/static/img/pipeline.png differ diff --git a/kurento-recorder/static/img/spinner.gif b/kurento-recorder/static/img/spinner.gif new file mode 100644 index 00000000..8be8ba33 Binary files /dev/null and b/kurento-recorder/static/img/spinner.gif differ diff --git a/kurento-recorder/static/img/transparent-1px.png b/kurento-recorder/static/img/transparent-1px.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/kurento-recorder/static/img/transparent-1px.png differ diff --git a/kurento-recorder/static/img/urjc.gif b/kurento-recorder/static/img/urjc.gif new file mode 100644 index 00000000..cd8a7703 Binary files /dev/null and b/kurento-recorder/static/img/urjc.gif differ diff --git a/kurento-recorder/static/img/webrtc.png b/kurento-recorder/static/img/webrtc.png new file mode 100644 index 00000000..d47e2e4c Binary files /dev/null and b/kurento-recorder/static/img/webrtc.png differ diff --git a/kurento-recorder/static/index.html b/kurento-recorder/static/index.html new file mode 100644 index 00000000..39278bb8 --- /dev/null +++ b/kurento-recorder/static/index.html @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Kurento Tutoria: Recorder + + + +
+ +
+ +
+ +
+
+

Local stream

+ +
+
+ + Start +
+
+ + Stop +
+
+ Play +
+
+

Remote stream

+ +
+
+
+
+

+
+
    +
    +
    +
    +
    + + + + + diff --git a/kurento-recorder/static/js/index.js b/kurento-recorder/static/js/index.js new file mode 100644 index 00000000..04f586b9 --- /dev/null +++ b/kurento-recorder/static/js/index.js @@ -0,0 +1,253 @@ +/* + * (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 ws = new WebSocket('wss://' + location.host + '/helloworld'); +var videoInput; +var videoOutput; +var webRtcPeer; +var webRtcPeerPlay; +var state = null; + +const I_CAN_START = 0; +const I_CAN_STOP = 1; +const I_AM_STARTING = 2; +const I_AM_PLAYING = 3; +const I_CAN_STOP_PLAYING = 4; + +window.onload = function() { + console = new Console(); + console.log('Page loaded ...'); + videoInput = document.getElementById('videoInput'); + videoOutput = document.getElementById('videoOutput'); + setState(I_CAN_START); +} + +window.onbeforeunload = function() { + ws.close(); +} + +ws.onmessage = function(message) { + var parsedMessage = JSON.parse(message.data); + console.info('Received message: ' + message.data); + + switch (parsedMessage.id) { + case 'startResponse': + startResponse(parsedMessage); + break; + case 'playResponse': + playResponse(parsedMessage); + break; + case 'error': + if (state == I_AM_STARTING) { + setState(I_CAN_START); + } + onError('Error message from server: ' + parsedMessage.message); + break; + case 'iceCandidate': + + if(webRtcPeer) + webRtcPeer.addIceCandidate(parsedMessage.candidate) + else + webRtcPeerPlay.addIceCandidate(parsedMessage.candidate) + break; + default: + if (state == I_AM_STARTING) { + setState(I_CAN_START); + } + onError('Unrecognized message', parsedMessage); + } +} + +function start() { + console.log('Starting video call ...') + + // Disable start button + setState(I_AM_STARTING); + showSpinner(videoInput, videoOutput); + + console.log('Creating WebRtcPeer and generating local sdp offer ...'); + + var options = { + localVideo: videoInput, + remoteVideo: videoOutput, + onicecandidate : onIceCandidate + } + + webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv(options, function(error) { + if(error) return onError(error); + this.generateOffer(onOffer); + }); +} + +function onIceCandidate(candidate) { + console.log('Local candidate' + JSON.stringify(candidate)); + + var message = { + id : 'onIceCandidate', + candidate : candidate + }; + sendMessage(message); +} + +function onOffer(error, offerSdp) { + if(error) return onError(error); + + console.info('Invoking SDP offer callback function ' + location.host); + var message = { + id : 'start', + sdpOffer : offerSdp + } + sendMessage(message); +} + +function onError(error) { + console.error(error); +} + +function startResponse(message) { + setState(I_CAN_STOP); + console.log('SDP answer received from server. Processing ...'); + webRtcPeer.processAnswer(message.sdpAnswer); +} + +function playResponse(message) { + setState(I_CAN_STOP_PLAYING); + console.log('SDP answer received from server. Processing ...'); + webRtcPeerPlay.processAnswer(message.sdpAnswer); +} + +function stop() { + console.log('Stopping video call ...'); + setState(I_CAN_START); + if (webRtcPeer) { + webRtcPeer.dispose(); + webRtcPeer = null; + videoInput.src = ""; + videoOutput.src = ""; + + var message = { + id : 'stop' + } + sendMessage(message); + } + hideSpinner(videoInput, videoOutput); + var playButton = document.getElementById('play'); + playButton.addEventListener('click', startPlaying); +} + +function onPlayOffer(error, offerSdp) { + if(error) return onError(error); + + console.info('Invoking play offer callback function ' + location.host); + var message = { + id : 'play', + sdpOffer : offerSdp + } + sendMessage(message); +} + +function startPlaying() { + console.log('Playing video call ...') + + // Disable start button + setState(I_AM_PLAYING); + showSpinner(videoOutput); + + var options = { + remoteVideo: videoOutput, + onicecandidate : onIceCandidate + } + + webRtcPeerPlay = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options, function(error) { + if(error) return onError(error); + this.generateOffer(onPlayOffer); + }); +} +function setState(nextState) { + switch (nextState) { + case I_CAN_START: + $('#start').attr('disabled', false); + $('#start').attr('onclick', 'start()'); + $('#stop').attr('disabled', true); + $('#stop').removeAttr('onclick'); + break; + + case I_CAN_STOP: + $('#start').attr('disabled', true); + $('#stop').attr('disabled', false); + $('#stop').attr('onclick', 'stop()'); + break; + + case I_AM_STARTING: + $('#start').attr('disabled', true); + $('#start').removeAttr('onclick'); + $('#stop').attr('disabled', true); + $('#stop').removeAttr('onclick'); + break; + case I_AM_PLAYING: + $('#start').attr('disabled', true); + $('#start').removeAttr('onclick'); + $('#stop').attr('disabled', true); + $('#stop').removeAttr('onclick'); + $('#play').attr('disabled', true); + $('#play').removeAttr('onclick'); + + break; + case I_CAN_STOP_PLAYING: + $('#start').attr('disabled', false); + $('#start').attr('onclick', 'start()'); + $('#stop').attr('disabled', true); + $('#stop').removeAttr('onclick'); + $('#play').attr('disabled', false); + $('#play').attr('onclick', 'startPlaying()'); + + break; + default: + onError('Unknown state ' + nextState); + return; + } + state = nextState; +} + +function sendMessage(message) { + var jsonMessage = JSON.stringify(message); + console.log('Sending message: ' + jsonMessage); + ws.send(jsonMessage); +} + +function showSpinner() { + for (var i = 0; i < arguments.length; i++) { + arguments[i].poster = './img/transparent-1px.png'; + arguments[i].style.background = 'center transparent url("./img/spinner.gif") no-repeat'; + } +} + +function hideSpinner() { + for (var i = 0; i < arguments.length; i++) { + arguments[i].src = ''; + arguments[i].poster = './img/webrtc.png'; + arguments[i].style.background = ''; + } +} + +/** + * Lightbox utility (to display media pipeline image in a modal dialog) + */ +$(document).delegate('*[data-toggle="lightbox"]', 'click', function(event) { + event.preventDefault(); + $(this).ekkoLightbox(); +});