├── .eslintrc.json
├── .gitignore
├── Dockerfile
├── README.md
├── deploy.sh
├── docker-compose.yml
├── images
├── VideoTest.gif
└── WebcamTest.gif
├── package.json
├── src
├── index.js
├── kurento-pipeline.js
└── server.js
├── webapp
├── Dockerfile
├── build_and_run.sh
├── nginx.conf
├── package.json
└── static
│ ├── assets
│ ├── WebRTC.png
│ ├── spinner.gif
│ └── transparent-1px.png
│ ├── bower.json
│ ├── index.html
│ ├── src
│ └── index_tmpl.js
│ └── style
│ └── webrtcctv.css
└── yarn.lock
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es6": true,
6 | "node": true
7 | },
8 | "parser": "babel-eslint",
9 | "rules": {
10 | "no-const-assign": "warn",
11 | "no-this-before-super": "warn",
12 | "no-undef": "warn",
13 | "no-unreachable": "warn",
14 | "no-unused-vars": "warn",
15 | "constructor-super": "warn",
16 | "valid-typeof": "warn"
17 | }
18 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node
2 | node_modules/
3 |
4 | # Bower
5 | webapp/static/bower_components/
6 |
7 | # npm
8 | npm-debug.log
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:7.10
2 |
3 | RUN mkdir -p /usr/src/app
4 | WORKDIR /usr/src/app
5 |
6 |
7 | EXPOSE 7000
8 | CMD [ "npm", "start" ]
9 |
10 | COPY package.json /usr/src/app/
11 | RUN npm install
12 | COPY src /usr/src/app/src
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WebRTCCTV
2 |
3 | ## Introduction
4 |
5 | WebRTCCTV is a signaling server able to stream RTSP streams from cameras using WebRTC. It uses [Kurento](http://www.kurento.org/) as a signaling server.
6 |
7 |
8 | 
9 |
10 |
11 | ## Explanations
12 |
13 | This repository contains a signaling server as well as a simple webapp example that use WebRTC to read RTSP streams. To use it in a production environment, you will also need a TURN server and you will need to run the services manually instead of with the provided `docker-compose.yml` file. See the [deployment script](deploy.sh) for an idea of how it can be deployed on a production environment.
14 |
15 | By using the `docker-compose.yml` file like explained below, you will have a local test environment with four containers running:
16 |
17 | - `kurento`: The WebRTC media server
18 | - `signaling`: The WebRTC signaling server (communication between client and media server)
19 | - `webapp`: The example webapp to start, pause and stop streams
20 | - `fake_camera`: An RTSP stream using [RTSPATT](https://github.com/EtixLabs/RTSPAllTheThings)
21 |
22 | The way all of this works is that the signaling server establishes a WebRTC connection between your web browser and the Kurento Media server, as well as creates the media pipeline that will be used for streaming video, and then your browser communicates directly via WebRTC with Kurento to get the stream. The signaling server is no longer needed once the connection is established.
23 |
24 | The reason why a TURN server is needed in case you want to deploy this system on a cloud is that your users will need to communicate with a media server that is behind a NAT or a firewall. The TURN server will help initiating connections by acting as a relay. Since the Kurento media server should NOT be accessible from the outside (it would be a security issue), you need a TURN server to be accessible publicly and to relay data between the user and the media server.
25 |
26 | ## How to build
27 |
28 | Just run `docker-compose build` in the root of this repository.
29 |
30 | ## How to run
31 |
32 | Just run `docker-compose up` in the root of this repository.
33 |
34 | ## How to test
35 |
36 | If you ran the previous command, you should now be able to access the webapp at [localhost:4242](http://localhost:4242). Just click the start button to launch the streaming.
37 |
38 | ## How to make it use your own camera
39 |
40 | For that, you'll have to change the RTSP_URL environment variable that is set in the `docker-compose.yml` file to put your RTSP URL instead. I might add features to the webapp later, but keep in mind that this web application is just a demonstration of the capabilities of the signaling server with Kurento.
41 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # VARIABLE INITIALIZATION
4 |
5 | set -e
6 | set -x
7 |
8 | usage() {
9 | echo "Usage: $(basename "$0") [-h] -c CLOUD_SSH_ADDR -u CLOUD_SSH_USER -a MEDIA_SERV_SSH_ADDR -m MEDIA_SERV_SSH_USER
10 | This script is used to deploy the WebRTCCTV in a cloud environment, using the first server 'MEDIA_SERV' as a media server and a the 'CLOUD' as the server that serves the web application, the signaling server and the TURN server.
11 |
12 | -h shows this help text
13 | -c set cloud SSH address
14 | -u set cloud SSH user
15 | -a set media server SSH address
16 | -m set media server SSH user" >&2
17 | }
18 |
19 | # COMMAND LINE PARSING
20 |
21 | while getopts ':hb:u:c:v:' option; do
22 | case "$option" in
23 | h) usage
24 | exit
25 | ;;
26 | c) CLOUD_SSH_ADDR=${OPTARG}
27 | ;;
28 | u) CLOUD_SSH_USER=${OPTARG}
29 | ;;
30 | a) MEDIA_SERV_SSH_ADDR=${OPTARG}
31 | ;;
32 | m) MEDIA_SERV_SSH_USER=${OPTARG}
33 | ;;
34 | :) printf "missing argument for -%s\n" "$OPTARG" >&2
35 | usage
36 | exit 1
37 | ;;
38 | \?) printf "illegal option: -%s\n" "$OPTARG" >&2
39 | usage
40 | exit 1
41 | ;;
42 | esac
43 | done
44 | shift $((OPTIND - 1))
45 |
46 | ESC_SEQ="\x1b["
47 | COL_RESET=$ESC_SEQ"39;49;00m"
48 | COL_RED=$ESC_SEQ"31;01m"
49 | COL_GREEN=$ESC_SEQ"32;01m"
50 | COL_YELLOW=$ESC_SEQ"33;01m"
51 |
52 | # INFO
53 |
54 | echo -e $COL_YELLOW"Cloud address "$CLOUD_SSH_ADDR$COL_RESET
55 | echo -e $COL_YELLOW"Cloud user "$CLOUD_SSH_USER$COL_RESET
56 | echo -e $COL_YELLOW"Media server address "$MEDIA_SERV_SSH_ADDR$COL_RESET
57 | echo -e $COL_YELLOW"Media server user "$MEDIA_SERV_SSH_USER$COL_RESET
58 |
59 | ####################################
60 | ###### SIGNALING SERVER DEPLOYMENT #
61 | ####################################
62 |
63 | echo "Starting signaling server..."
64 | ssh -o StrictHostKeyChecking=no -A $CLOUD_SSH_USER@$CLOUD_SSH_ADDR "
65 | git clone git@github.com:Ullaakut/WebRTCCTV.git
66 | cd WebRTCCTV
67 | ./docker-compose build
68 | ./docker-compose run -d -p 8443:8443 --name signaling --entrypoint bash signaling -c \"node server.js -k ws://" $MEDIA_SERV_SSH_ADDR ":8888/kurento -c " $CCTV_API_URL "\" signaling"
69 |
70 | echo "Starting webapp..."
71 | ssh -o StrictHostKeyChecking=no -A $CLOUD_SSH_USER@$CLOUD_SSH_ADDR "
72 | git clone git@github.com:Ullaakut/WebRTCCTV.git
73 | cd WebRTCCTV
74 | ./docker-compose build ;
75 | ./docker-compose run -d -p 80:80 -e SIGNALING_URI="$CLOUD_SSH_ADDR":8443 --name webapp webapp"
76 |
77 | echo "Starting TURN server"
78 | ssh -o StrictHostKeyChecking=no -A $CLOUD_SSH_USER@$CLOUD_SSH_ADDR "docker stop coturn; docker rm -f coturn ; docker run -d --net=host --name coturn ullaakut/dockurn ./turnserver -L0.0.0.0 --no-stun -v -f -a -r kurento.org -u kurento:kurento"
79 |
80 | ret=$?
81 | if [ "$ret" -ne "0" ]; then
82 | echo -e $COL_RED"The machine " $CLOUD_SSH_ADDR " was not accessible. The signaling server could not be deployed."$COL_RESET;
83 | exit 1;
84 | fi
85 |
86 | echo -e $COL_GREEN"The machine " $CLOUD_SSH_ADDR " was accessible and the signaling server should be started."$COL_RESET
87 |
88 | ###################################
89 | ######### MEDIA SERVER DEPLOYMENT #
90 | ###################################
91 |
92 | echo "Starting Kurento Media Server on $MEDIA_SERV_SSH_ADDR..."
93 |
94 | ssh -o StrictHostKeyChecking=no $MEDIA_SERV_SSH_USER@$MEDIA_SERV_SSH_ADDR "
95 | docker rm -f kurento;
96 | docker run -d --name kurento --cap-add=SETPCAP -e KMS_TURN_URL=kurento:kurento@"$CLOUD_SSH_ADDR":3478 -e OUTPUT_BITRATE=2048000 -p 8888:8888 ullaakut/kurento-custom-bitrate -e GST_DEBUG=Kurento*:5
97 | "
98 |
99 | ret=$?
100 | if [ "$ret" -ne "0" ]; then
101 | echo -e $COL_RED"The test server was not acessible. Kurento could not be deployed."$COL_RESET;
102 | exit 1;
103 | fi
104 |
105 | echo -e $COL_GREEN"The test server was acessible and Kurento should be started."$COL_RESET
106 |
107 | ssh $CLOUD_SSH_ADDR -l $CLOUD_SSH_USER "docker logs -ft signaling"
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "2.1"
2 |
3 | services:
4 |
5 | kurento:
6 | image: ullaakut/kurento-custom-bitrate
7 | environment:
8 | - OUTPUT_BITRATE=2048000
9 | ports:
10 | - 8888:8888
11 | depends_on:
12 | - fake_camera
13 |
14 | signaling:
15 | build: ./
16 | ports:
17 | - 7000:7000
18 | depends_on:
19 | kurento: { condition: service_started }
20 |
21 | webapp:
22 | build:
23 | context: ./webapp
24 | dockerfile: Dockerfile
25 | ports:
26 | - 4242:80
27 | environment:
28 | - SIGNALING_URI=ws://localhost:7000
29 | depends_on:
30 | - signaling
31 |
32 | fake_camera:
33 | image: ullaakut/rtspatt
34 | # volumes:
35 | # - ./testvideo.mp4:/testvideo.mp4
36 | # environment:
37 | # - INPUT=/testvideo.mp4
38 | ports:
39 | - 8554:8554
--------------------------------------------------------------------------------
/images/VideoTest.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ullaakut/WebRTCCTV/b2005a1ed4bfe85639259f068ab84847229f4456/images/VideoTest.gif
--------------------------------------------------------------------------------
/images/WebcamTest.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ullaakut/WebRTCCTV/b2005a1ed4bfe85639259f068ab84847229f4456/images/WebcamTest.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webrtcctv",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "node src/index.js",
7 | "lint": "eslint ."
8 | },
9 | "dependencies": {
10 | "babel-eslint": "^7.2.3",
11 | "eslint": "^3.19.0",
12 | "kurento-client": "Kurento/kurento-client-js",
13 | "node-fetch": "^1.7.0",
14 | "ws": "~0.7.2"
15 | },
16 | "devDependencies": {
17 | "eslint": "^3.19.0",
18 | "eslint-config-airbnb-base": "^11.2.0",
19 | "eslint-plugin-import": "^2.2.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const ws = require('ws').Server;
4 | const Server = require('./server')(ws);
5 | const kurento = require('kurento-client');
6 | const kurentoPipeline = require('./kurento-pipeline');
7 |
8 | const kmsUrl = 'ws://kurento:8888/kurento';
9 |
10 | console.log(`Using KMS WebSocket server at ${kmsUrl}`);
11 |
12 | // Establish connection with Kurento Media Server using its URL
13 | kurento(kmsUrl)
14 | .catch(error => {
15 | console.error(
16 | `Could not find media server at address ${kmsUrl}. Exiting:`,
17 | error
18 | );
19 | process.exit(1);
20 | })
21 | .then(kurentoClient => {
22 | const createPipeline = kurentoPipeline(
23 | kurentoClient,
24 | // We need to manually give this constructor to pipeline so it doesn't
25 | // have to import Kurento.
26 | kurento.register.complexTypes.IceCandidate
27 | );
28 |
29 | const server = Server({ port: 7000 });
30 | console.log('WS Server listening on port 7000');
31 |
32 | server.onConnection(client => {
33 | const pipeline = createPipeline(client);
34 |
35 | client.onMessage(handleMessages(client, pipeline));
36 | client.onError(handleError(pipeline));
37 | client.onClose(handleClose(pipeline));
38 | });
39 | });
40 |
41 | function handleMessages(client, pipeline) {
42 | return message => {
43 | switch (message.id) {
44 | case 'start':
45 | const { rtspUri, sdpOffer } = message;
46 |
47 | handleStart(client, pipeline)(rtspUri, sdpOffer);
48 | break;
49 | case 'stop':
50 | console.log('Client stopped stream');
51 | pipeline.stop();
52 | break;
53 | case 'onIceCandidate':
54 | pipeline.handleIceCandidate(message.candidate);
55 | break;
56 | default:
57 | client.send({
58 | id: 'error',
59 | message: `Invalid message ID: ${message.id}`,
60 | });
61 | break;
62 | }
63 | };
64 | }
65 |
66 | function handleStart(client, pipeline) {
67 | return async (rtspUri, sdpOffer) => {
68 | console.log('START', rtspUri);
69 |
70 | try {
71 | console.log('Launching pipeline for RTSP URL:', rtspUri);
72 | pipeline.start(rtspUri, sdpOffer);
73 | } catch (error) {
74 | const [message, reason] = error;
75 | console.error(message);
76 | client.send({
77 | id: 'error',
78 | error: reason,
79 | });
80 | }
81 | };
82 | }
83 |
84 | function handleError(pipeline) {
85 | return error => {
86 | console.error('WebSocket error:', error);
87 | pipeline.stop();
88 | };
89 | }
90 |
91 | function handleClose(pipeline) {
92 | return (code, reason) => {
93 | console.info(`WebSocket closed with code ${code}: ${reason}`);
94 | pipeline.stop();
95 | };
96 | }
97 |
--------------------------------------------------------------------------------
/src/kurento-pipeline.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = (kurentoClient, IceCandidate) => client => {
4 | let pipeline;
5 | let webRtcEndpoint;
6 |
7 | // Used to buffer ice candidates until webRtcEndpoint is ready to process them
8 | let iceCandidatesQueue = [];
9 |
10 | // Start an RTSP stream using client's offer
11 | function start(rtspUri, sdpOffer) {
12 | return startStream(rtspUri, sdpOffer).catch(pipelineError);
13 | }
14 |
15 | // Start the RTSP stream
16 | async function startStream(rtspUri, sdpOffer) {
17 | // Create the media pipeline
18 | const { playerEndpoint } = await createPipeline(rtspUri, sdpOffer);
19 |
20 | // Start the pipeline
21 | await playerEndpoint.play();
22 | }
23 |
24 | // Create the kurento pipeline composed of a WebRTCEndpoint and a PlayerEndpoint
25 | // The PlayerEndpoint sends the stream into the pipeline
26 | // The WebRtcEndpoint forwards it to the browser
27 | async function createPipeline(rtspUri, sdpOffer) {
28 | console.log(`Creating KMS pipeline with RTSP stream: ${rtspUri}`);
29 |
30 | pipeline = await kurentoClient.create('MediaPipeline');
31 |
32 | pipeline.on('Error', pipelineError);
33 |
34 | // Create the 2 endpoints in parallel
35 | const [playerEndpoint, webRtcEndpoint] = await Promise.all([
36 | createPlayerEndpoint(rtspUri),
37 | createWebRtcEndpoint(sdpOffer),
38 | ]);
39 |
40 | // Connect the playerEndpoint to the webRtcEndpoint
41 | await playerEndpoint.connect(webRtcEndpoint, 'VIDEO');
42 |
43 | return {
44 | playerEndpoint,
45 | webRtcEndpoint,
46 | pipeline,
47 | };
48 | }
49 |
50 | // Create and start the player endpoint
51 | async function createPlayerEndpoint(rtspUri) {
52 | const playerOptions = {
53 | uri: rtspUri,
54 | useEncodedMedia: false,
55 |
56 | // Reduce the buffering in order to decrease latency to the minimum
57 | // Using 0 as the networkCache value could cause stability problems
58 | networkCache: 100,
59 | };
60 |
61 | const playerEndpoint = await pipeline.create(
62 | 'PlayerEndpoint',
63 | playerOptions
64 | );
65 |
66 | playerEndpoint.on('Error', pipelineError);
67 |
68 | return playerEndpoint;
69 | }
70 |
71 | // Create and setup the WebRTC endpoint
72 | async function createWebRtcEndpoint(sdpOffer) {
73 | webRtcEndpoint = await pipeline.create('WebRtcEndpoint');
74 |
75 | webRtcEndpoint.on('Error', pipelineError);
76 |
77 | // If we already had ICE candidates queued, we add them to the WebRTC endpoint
78 | // We can safely assume there won't be candidates added to the queue while we empty it
79 | // since `webRtcEndpoint` has been set, so handleIceCandidate will directly send them to it
80 | await Promise.all(
81 | iceCandidatesQueue.map(candidate =>
82 | webRtcEndpoint.addIceCandidate(candidate)
83 | )
84 | );
85 |
86 | // Ask Kurento to process the SDP offer in order to get an SDP answer
87 | const sdpAnswer = await webRtcEndpoint.processOffer(sdpOffer);
88 |
89 | // Send sdp answer to client
90 | client.send({
91 | id: 'startResponse',
92 | sdpAnswer,
93 | });
94 |
95 | // Start gathering local ICE candidates and send them to the client
96 | webRtcEndpoint.on('OnIceCandidate', event => {
97 | const candidate = IceCandidate(event.candidate);
98 | client.send({
99 | id: 'iceCandidate',
100 | candidate,
101 | });
102 | });
103 | await webRtcEndpoint.gatherCandidates();
104 |
105 | return webRtcEndpoint;
106 | }
107 |
108 | function handleIceCandidate(candidate) {
109 | const kurentoCandidate = IceCandidate(candidate);
110 | if (webRtcEndpoint) {
111 | console.info('Candidate received, forwarding to WebRTC endpoint');
112 | webRtcEndpoint.addIceCandidate(kurentoCandidate);
113 | } else {
114 | console.info('Candidate received, queuing...');
115 | // Push this IceCandidate into the queue
116 | iceCandidatesQueue.push(kurentoCandidate);
117 | }
118 | }
119 |
120 | // Release pipeline for this camera after a stream could not start
121 | function pipelineError(error) {
122 | console.error('Pipeline error:', error);
123 | client.send({
124 | id: 'error',
125 | error: 'Pipeline error',
126 | });
127 | stop();
128 | }
129 |
130 | // Release pipeline
131 | function stop() {
132 | if (!pipeline) {
133 | return;
134 | }
135 | console.info('Releasing pipeline');
136 | pipeline.release();
137 | pipeline = null;
138 | webRtcEndpoint = null;
139 | iceCandidatesQueue = [];
140 | }
141 |
142 | return {
143 | start,
144 | stop,
145 | handleIceCandidate,
146 | };
147 | };
148 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = Server => options => {
4 | const { pingInterval = 10000, port = 8443 } = options;
5 | let connectionHandler;
6 |
7 | // Start the ws server
8 | const server = new Server({ port });
9 |
10 | server.on('connection', ws => {
11 | if (!connectionHandler) {
12 | return;
13 | }
14 |
15 | console.log('New connection');
16 | const client = createClient(ws);
17 | connectionHandler(client);
18 | });
19 |
20 | function createClient(ws) {
21 | let messageHandler, errorHandler, closeHandler;
22 |
23 | // Starts pinging the client every `pingInterval`ms
24 | const stopKeepAlive = startKeepAlive(ws);
25 |
26 | ws.on('error', error => {
27 | if (errorHandler) {
28 | errorHandler(error);
29 | }
30 | });
31 |
32 | ws.on('close', (code, reason) => {
33 | if (closeHandler) {
34 | closeHandler(code, reason);
35 | stopKeepAlive();
36 | }
37 | });
38 |
39 | ws.on('message', message => {
40 | if (messageHandler) {
41 | messageHandler(JSON.parse(message));
42 | }
43 | });
44 |
45 | return {
46 | onMessage(handler) {
47 | messageHandler = handler;
48 | },
49 | onError(handler) {
50 | errorHandler = handler;
51 | },
52 | onClose(handler) {
53 | closeHandler = handler;
54 | },
55 | send(message) {
56 | ws.send(JSON.stringify(message));
57 | },
58 | };
59 | }
60 |
61 | // Starts pinging a ws connection to make sure it stays alive
62 | function startKeepAlive(ws) {
63 | let isAlive = true;
64 |
65 | ws.on('pong', () => {
66 | isAlive = true;
67 | });
68 |
69 | // Every `pingInterval` ms, check if socket is still alive and send a new ping
70 | const timer = setInterval(() => {
71 | if (!isAlive) {
72 | ws.terminate();
73 | return;
74 | }
75 | isAlive = false;
76 | ws.ping();
77 | }, pingInterval);
78 |
79 | // Return a function that stops the ping timer
80 | return () => {
81 | clearInterval(timer);
82 | };
83 | }
84 |
85 | return {
86 | onConnection(handler) {
87 | connectionHandler = handler;
88 | },
89 | };
90 | };
--------------------------------------------------------------------------------
/webapp/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:alpine
2 |
3 | COPY nginx.conf /etc/nginx/nginx.conf
4 | COPY static /usr/share/nginx/html
5 | COPY build_and_run.sh /build_and_run.sh
6 |
7 | EXPOSE 80
8 |
9 | ENTRYPOINT ["/build_and_run.sh"]
10 | CMD ['/usr/sbin/nginx']
11 |
--------------------------------------------------------------------------------
/webapp/build_and_run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -x
4 | set -eo pipefail
5 |
6 | # Set default value of signaling URI if not in the environment
7 | if [ -z "$SIGNALING_URI" ]; then
8 | echo "No signaling URI defined. Using 0.0.0.0 by default."
9 | export SIGNALING_URI=0.0.0.0
10 | fi
11 |
12 | envsubst < /usr/share/nginx/html/src/index_tmpl.js > /usr/share/nginx/html/src/index.js
13 |
14 | /usr/sbin/nginx
15 |
--------------------------------------------------------------------------------
/webapp/nginx.conf:
--------------------------------------------------------------------------------
1 | daemon off;
2 |
3 | events {}
4 |
5 | http {
6 | server {
7 | listen 80;
8 | server_name www.mysite.com mysite.com;
9 | error_log /var/log/nginx/error.log;
10 |
11 | root /usr/share/nginx/html;
12 |
13 | location / {
14 | include /etc/nginx/mime.types;
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/webapp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webrtcctv",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "postinstall": "cd static; bower install"
7 | },
8 | "devDependencies": {
9 | "bower": "^1.4.1"
10 | }
11 | }
--------------------------------------------------------------------------------
/webapp/static/assets/WebRTC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ullaakut/WebRTCCTV/b2005a1ed4bfe85639259f068ab84847229f4456/webapp/static/assets/WebRTC.png
--------------------------------------------------------------------------------
/webapp/static/assets/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ullaakut/WebRTCCTV/b2005a1ed4bfe85639259f068ab84847229f4456/webapp/static/assets/spinner.gif
--------------------------------------------------------------------------------
/webapp/static/assets/transparent-1px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ullaakut/WebRTCCTV/b2005a1ed4bfe85639259f068ab84847229f4456/webapp/static/assets/transparent-1px.png
--------------------------------------------------------------------------------
/webapp/static/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webrtcctv",
3 | "description": "WebRTC signaling server and webapp that can stream RTSP cameras",
4 | "authors": [
5 | "Brendan LE GLAUNEC - brendan.le-glaunec@epitech.eu"
6 | ],
7 | "main": "index.html",
8 | "moduleType": [
9 | "globals"
10 | ],
11 | "private": true,
12 | "ignore": [
13 | "**/.*",
14 | "node_modules",
15 | "bower_components",
16 | "test",
17 | "tests"
18 | ],
19 | "dependencies": {
20 | "adapter.js": "*",
21 | "bootstrap": "~3.3.0",
22 | "ekko-lightbox": "~3.3.0",
23 | "demo-console": "master",
24 | "kurento-utils": "master"
25 | }
26 | }
--------------------------------------------------------------------------------
/webapp/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | WebRTCCTV
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
36 |
38 |
43 |
44 |
45 |
46 |
47 |
48 | Exit
49 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
61 |
62 |
63 |
64 |