├── .env
├── .browserslistrc
├── vue.config.js
├── babel.config.js
├── postcss.config.js
├── public
├── favicon.ico
└── index.html
├── src
├── assets
│ ├── bck.jpg
│ ├── logo.png
│ ├── msg_bck.png
│ ├── scaling.png
│ └── local_env.png
├── utils
│ ├── logging.js
│ ├── config.js
│ └── ICEServers.js
├── styles
│ ├── variables.scss
│ └── app.scss
├── App.vue
├── main.js
├── router
│ └── index.js
├── components
│ ├── MessageArea.vue
│ ├── video
│ │ ├── Video.vue
│ │ └── AudioVideoControls.vue
│ ├── ChatArea.vue
│ ├── UserList.vue
│ ├── VideoArea.vue
│ ├── conference
│ │ └── Conference.vue
│ └── ChatDialog.vue
├── views
│ ├── Home.vue
│ └── Chat.vue
├── store
│ └── index.js
└── mixins
│ └── WebRTC.js
├── aws
├── Dockerfile
└── default.conf
├── .gitignore
├── server
├── config
│ └── index.js
├── routes
│ ├── room.js
│ └── user.js
├── index.js
├── app.js
├── redis
│ └── index.js
└── chat_namespace
│ ├── index.js
│ └── events.js
├── Dockerfile
├── .eslintrc.js
├── docker-compose.aws.yml
├── package.json
├── docker-compose.yml
└── README.md
/.env:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not ie <= 8
4 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | productionSourceMap: false
3 | };
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrigardi90/video-chat/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/bck.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrigardi90/video-chat/HEAD/src/assets/bck.jpg
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrigardi90/video-chat/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/msg_bck.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrigardi90/video-chat/HEAD/src/assets/msg_bck.png
--------------------------------------------------------------------------------
/src/assets/scaling.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrigardi90/video-chat/HEAD/src/assets/scaling.png
--------------------------------------------------------------------------------
/src/assets/local_env.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrigardi90/video-chat/HEAD/src/assets/local_env.png
--------------------------------------------------------------------------------
/src/utils/logging.js:
--------------------------------------------------------------------------------
1 | export const log = (arg) => {
2 | var now = (window.performance.now() / 1000).toFixed(3)
3 | console.log(`${now}: ${arg}`)
4 | }
--------------------------------------------------------------------------------
/src/styles/variables.scss:
--------------------------------------------------------------------------------
1 | $main_blue: #3961a5;
2 | $secondary_blue: #486ca9;
3 |
4 | $available_status: #05b105;
5 | $absent_status: #f7bb04;
6 | $unavailable_status: #bb0000;
--------------------------------------------------------------------------------
/aws/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:8.6 as build
2 |
3 | WORKDIR /videochat
4 | COPY package.json /videochat/
5 | COPY /aws/default.conf /etc/nginx/conf.d
6 | RUN npm install
7 |
8 | COPY /server /videochat/server
9 |
10 | CMD ["npm", "run", "run:server"]
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw*
22 |
--------------------------------------------------------------------------------
/server/config/index.js:
--------------------------------------------------------------------------------
1 |
2 | const CONFIG = {
3 | PORT: process.env.PORT || 3000,
4 | CHAT_NAMESPACE: '/video-chat',
5 | REDIS_HOST: process.env.REDIS_HOST || 'localhost',
6 | REDIS_PORT: process.env.REDIS_PORT || 6379,
7 | ORIGINS: process.env.ORIGINS || '*:*',
8 | KEY: 'unique'
9 | }
10 |
11 | module.exports = CONFIG
--------------------------------------------------------------------------------
/aws/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 |
4 | add_header Access-Control-Allow-Origin *;
5 |
6 | location / {
7 | proxy_pass http://localhost:4000;
8 | proxy_http_version 1.1;
9 | proxy_set_header Upgrade $http_upgrade;
10 | proxy_set_header Host $host;
11 | proxy_cache_bypass $http_upgrade;
12 | }
13 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:8.6 as build
2 |
3 | WORKDIR /videochat
4 | COPY package.json /videochat/
5 | RUN npm install
6 |
7 | COPY ./ /videochat
8 |
9 | ARG VUE_APP_SOCKET_HOST=NOT_SET
10 | ARG VUE_APP_SOCKET_PORT=NOT_SET
11 |
12 | RUN export VUE_APP_SOCKET_HOST=${VUE_APP_SOCKET_HOST} VUE_APP_SOCKET_PORT=${VUE_APP_SOCKET_PORT} && npm run build
13 |
14 | CMD ["npm", "run", "run:server"]
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | 'extends': [
7 | 'plugin:vue/essential',
8 | 'eslint:recommended'
9 | ],
10 | rules: {
11 | 'no-console': process.env.NODE_ENV === 'production' ? 'off' : 'off',
12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
13 | },
14 | parserOptions: {
15 | parser: 'babel-eslint'
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/server/routes/room.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const roomRouter = express.Router();
3 |
4 | const rooms = [
5 | {
6 | id: 1,
7 | name: 'GENERAL'
8 | },
9 | {
10 | id: 2,
11 | name: 'SPORTS'
12 | },
13 | {
14 | id: 3,
15 | name: 'GAMES'
16 | },
17 | ]
18 |
19 | // route for get rooms
20 | roomRouter.get('/', (req,res) => {
21 | res.send(rooms)
22 | })
23 |
24 | module.exports = roomRouter
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
26 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const http = require('http')
2 | const redis = require('socket.io-redis')
3 |
4 | const app = require('./app')
5 | const config = require('./config')
6 |
7 | // Server
8 | const server = http.createServer(app)
9 |
10 | // Atach server to the socket
11 | app.io.attach(server)
12 |
13 | // Origin socket configuration
14 | app.io.origins([config.ORIGINS])
15 |
16 | // Using the adapter to pass event between nodes
17 | app.io.adapter(redis({
18 | host: config.REDIS_HOST,
19 | port: config.REDIS_PORT
20 | }))
21 |
22 | server.listen(config.PORT, () => {
23 | console.log(`Server Listening on port ${config.PORT}`)
24 | })
25 |
--------------------------------------------------------------------------------
/docker-compose.aws.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 |
5 | redis:
6 | image: redis:4.0.5-alpine
7 | networks:
8 | - video-chat
9 | ports:
10 | - 6379:6379
11 | expose:
12 | - "6379"
13 | restart: always
14 | command: ["redis-server", "--appendonly", "yes"]
15 |
16 | chat-service:
17 | build:
18 | context: .
19 | dockerfile: ./aws/Dockerfile
20 | ports:
21 | - 4000:4000
22 | networks:
23 | - video-chat
24 | depends_on:
25 | - redis
26 | environment:
27 | PORT: 4000
28 | REDIS_HOST: redis
29 | REDIS_PORT: 6379
30 |
31 | networks:
32 | video-chat:
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | video
12 |
13 |
14 |
15 | We're sorry but video doesn't work properly without JavaScript enabled. Please enable it to continue.
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 |
2 | const express = require('express')
3 | const app = express()
4 | const io = app.io = require('socket.io')()
5 | const cors = require('cors')
6 | const bodyParser = require('body-parser')
7 | const path = require('path')
8 |
9 | const users = require('./routes/user')
10 | const rooms = require('./routes/room')
11 | const chat = require('./chat_namespace')
12 |
13 | app.use(cors())
14 | app.use(bodyParser.json())
15 |
16 | /**
17 | * Middleware
18 | */
19 | app.use((req, res, next) => {
20 | console.log('Time: ', Date.now())
21 | next()
22 | })
23 |
24 | /**
25 | * Routing
26 | */
27 | app.use('/auth', users)
28 | app.use('/rooms', rooms)
29 |
30 | // Static routing
31 | app.use(express.static(path.join(__dirname, '../dist')))
32 |
33 | /**
34 | * Chat socket namespace
35 | */
36 | chat.createNameSpace(io)
37 |
38 |
39 | module.exports = app
40 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import store from './store'
5 | import VueSocketIO from 'vue-socket.io'
6 | import io from 'socket.io-client'
7 | import VueResource from 'vue-resource'
8 | import './styles/app.scss'
9 | import { url } from './utils/config'
10 | import adapter from 'webrtc-adapter'
11 |
12 | console.log(`Browser ${adapter.browserDetails.browser} - version ${adapter.browserDetails.version}`)
13 |
14 | // Socket config
15 | Vue.use(new VueSocketIO({
16 | debug: true,
17 | connection: io(`${url}/video-chat`, { autoConnect: false }),
18 | vuex: {
19 | store,
20 | actionPrefix: 'SOCKET_',
21 | mutationPrefix: 'SOCKET_'
22 | },
23 | }))
24 |
25 | // Vue resource for http
26 | Vue.use(VueResource)
27 |
28 | Vue.config.productionTip = false
29 |
30 | new Vue({
31 | router,
32 | store,
33 | render: h => h(App)
34 | }).$mount('#app')
35 |
--------------------------------------------------------------------------------
/src/styles/app.scss:
--------------------------------------------------------------------------------
1 | body {
2 | min-height: unset;
3 | overflow: hidden;
4 | }
5 |
6 | textarea {
7 | resize: none;
8 | &:disabled {
9 | cursor: not-allowed;
10 | background: #80808021;
11 | }
12 | }
13 |
14 | // Overwrite material styles
15 | .md-overlay {
16 | position: unset;
17 | top: unset;
18 | right: unset;
19 | }
20 |
21 | .md-dialog {
22 | height: 350px;
23 | bottom: 0;
24 | position: fixed;
25 | right: 0;
26 | top: 100vh;
27 | transform: translate(0%, -50%);
28 | left: unset;
29 | &-container {
30 | border: 1px solid gainsboro;
31 | flex-flow: row;
32 | }
33 | &-actions {
34 | padding: 0;
35 | }
36 | &-content {
37 | padding: 1rem;
38 | background: url('./../assets/msg_bck.png');
39 | background-size: 100% 100%;
40 | }
41 | }
42 |
43 | .md-button.md-theme-default[disabled] .md-icon-font {
44 | color: rgba(0, 0, 0, 0.38) !important;
45 | color: var(--md-theme-default-icon-disabled-on-background, rgba(0, 0, 0, 0.38)) !important;
46 | }
--------------------------------------------------------------------------------
/src/utils/config.js:
--------------------------------------------------------------------------------
1 | export const url = `${process.env.VUE_APP_SOCKET_HOST || 'http://localhost'}:${process.env.VUE_APP_SOCKET_PORT || '3000'}`
2 |
3 | export const STORE_ACTIONS = {
4 | joinRoom: 'joinRoom',
5 | setRooms: 'setRooms',
6 | changeRoom: 'changeRoom',
7 | leaveChat:'leaveChat',
8 | changeStatus: 'changeStatus'
9 | }
10 | export const WS_EVENTS = {
11 | joinPrivateRoom: 'joinPrivateRoom',
12 | joinRoom: 'joinRoom',
13 | leaveRoom: 'leaveRoom',
14 | publicMessage: 'publicMessage',
15 | leavePrivateRoom: 'leavePrivateRoom',
16 | leaveChat: 'leaveChat',
17 | changeStatus: 'changeStatus',
18 | privateMessage: 'privateMessage',
19 | privateMessagePCSignaling: 'privateMessagePCSignaling',
20 | PCSignalingConference: 'PCSignalingConference',
21 | conferenceInvitation: 'conferenceInvitation',
22 | joinConference: 'joinConference',
23 | leaveConference: 'leaveConference'
24 | }
25 |
26 | export const STATUS_OPTIONS = {
27 | available: 'available',
28 | absent: 'absent',
29 | unavailable: 'unavailable'
30 | }
31 |
32 | export const DESCRIPTION_TYPE = {
33 | offer: 'offer',
34 | answer: 'answer'
35 | }
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import VueMaterial from 'vue-material'
4 | import 'vue-material/dist/vue-material.min.css'
5 | import 'vue-material/dist/theme/default.css' // This line here
6 | import VueToastr from "vue-toastr"
7 |
8 | import Home from './../views/Home.vue'
9 | import store from '../store'
10 |
11 | Vue.use(VueMaterial)
12 | Vue.use(VueToastr, {
13 | defaultPosition: "toast-top-left",
14 | defaultTimeout: 3000,
15 | defaultProgressBar: false,
16 | defaultProgressBarValue: 0,
17 | })
18 | Vue.use(Router)
19 |
20 | export default new Router({
21 | routes: [
22 | {
23 | path: '/',
24 | name: 'home',
25 | component: Home,
26 | beforeEnter: (to, from, next) => {
27 | store.state.room && store.state.username ? next('/chat') : next()
28 | }
29 | },
30 | {
31 | path: '/chat',
32 | name: 'chat',
33 | // route level code-splitting
34 | // this generates a separate chunk (about.[hash].js) for this route
35 | // which is lazy-loaded when the route is visited.
36 | component: () => import(/* webpackChunkName: "about" */ './../views/Chat.vue'),
37 | beforeEnter: (to, from, next) => {
38 | !store.state.room && !store.state.username ? next('/') : next()
39 | }
40 | }
41 | ]
42 | })
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "video-chat",
3 | "description" : "Chat with 1 to 1 and many to many video functionality",
4 | "version": "0.1.0",
5 | "private": true,
6 | "scripts": {
7 | "serve": "vue-cli-service serve",
8 | "run:server": "node ./server/index.js",
9 | "debug:server": "node --inspect=0.0.0.0:9229 ./server/index.js",
10 | "build": "vue-cli-service build",
11 | "lint": "vue-cli-service lint"
12 | },
13 | "dependencies": {
14 | "body-parser": "^1.18.3",
15 | "cors": "^2.8.5",
16 | "express": "^4.16.4",
17 | "http": "0.0.0",
18 | "path": "^0.12.7",
19 | "socket.io": "^2.2.0",
20 | "socket.io-client": "^2.3.0",
21 | "socket.io-redis": "^5.2.0",
22 | "vue": "^2.5.21",
23 | "vue-material": "^1.0.0-beta-10.2",
24 | "vue-resource": "^1.5.1",
25 | "vue-router": "^3.0.1",
26 | "vue-socket.io": "^3.0.5",
27 | "vue-toastr": "^2.1.2",
28 | "vuex": "^3.0.1",
29 | "webrtc-adapter": "^7.5.1"
30 | },
31 | "devDependencies": {
32 | "@vue/cli-plugin-babel": "^3.3.0",
33 | "@vue/cli-plugin-eslint": "^3.3.0",
34 | "@vue/cli-service": "^3.3.0",
35 | "babel-eslint": "^10.0.1",
36 | "eslint": "^5.8.0",
37 | "eslint-plugin-vue": "^5.0.0",
38 | "node-sass": "^4.9.0",
39 | "sass-loader": "^7.0.1",
40 | "vue-template-compiler": "^2.5.21"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/MessageArea.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 | Send
14 |
15 |
16 |
17 |
18 |
19 |
37 |
38 |
39 |
65 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 |
5 | redis:
6 | image: redis:4.0.5-alpine
7 | networks:
8 | - video-chat
9 | ports:
10 | - 6379:6379
11 | expose:
12 | - "6379"
13 | restart: always
14 | command: ["redis-server", "--appendonly", "yes"]
15 |
16 | # Copy 1
17 | chat1:
18 | build:
19 | context: .
20 | args:
21 | VUE_APP_SOCKET_HOST: http://localhost
22 | VUE_APP_SOCKET_PORT: 3000
23 | ports:
24 | - 3000:3000
25 | networks:
26 | - video-chat
27 | depends_on:
28 | - redis
29 | environment:
30 | PORT: 3000
31 | REDIS_HOST: redis
32 | REDIS_PORT: 6379
33 |
34 | # Copy 2
35 | chat2:
36 | build:
37 | context: .
38 | args:
39 | VUE_APP_SOCKET_HOST: http://localhost
40 | VUE_APP_SOCKET_PORT: 3001
41 | ports:
42 | - 3001:3001
43 | networks:
44 | - video-chat
45 | depends_on:
46 | - redis
47 | environment:
48 | PORT: 3001
49 | REDIS_HOST: redis
50 | REDIS_PORT: 6379
51 |
52 | # Copy 3
53 | chat3:
54 | build:
55 | context: .
56 | args:
57 | VUE_APP_SOCKET_HOST: http://localhost
58 | VUE_APP_SOCKET_PORT: 3002
59 | ports:
60 | - 3002:3002
61 | networks:
62 | - video-chat
63 | depends_on:
64 | - redis
65 | environment:
66 | PORT: 3002
67 | REDIS_HOST: redis
68 | REDIS_PORT: 6379
69 |
70 | networks:
71 | video-chat:
--------------------------------------------------------------------------------
/src/components/video/Video.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
15 |
16 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
48 |
49 |
--------------------------------------------------------------------------------
/src/components/video/AudioVideoControls.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ videoOn ? 'videocam' : 'videocam_off'}}
8 | {{ audioOn ? 'mic' : 'mic_off'}}
13 |
14 |
15 |
16 |
39 |
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # video-chat
2 | > Chat application with **1 to 1** and **many to many** video functionality using [VueJS](https://vuejs.org), [Vuex](https://vuex.vuejs.org), [WebRTC](https://webrtc.org/start/), [SocketIO](https://socket.io),NodeJS and [Redis](https://github.com/NodeRedis/node_redis)
3 |
4 | ## Quick start
5 | First of all, you need to install and run the redis in your PC. Here there is an [article](https://medium.com/@petehouston/install-and-config-redis-on-mac-os-x-via-homebrew-eb8df9a4f298) for Mac OS X. Once Redis is up and running:
6 |
7 | ```bash
8 | # Clone the repo
9 | git clone https://github.com/adrigardi90/video-chat
10 |
11 | # Change into the repo directory
12 | cd video-chat
13 |
14 | # install
15 | npm install
16 |
17 | # Start the FE in dev mode
18 | npm run serve
19 |
20 | # Start the server
21 | npm run run:server
22 |
23 | ```
24 | Then visit http://localhost:8080 in your browser
25 |
26 | ## Horizontal scaling
27 | To test out the horizontal scaling we'll create 3 different instances. Each one running a unique nodeJS process serving the FE and exposing the API
28 |
29 |
30 |
31 |
32 |
33 |
34 | ```bash
35 | # Build the images
36 | docker-compose -f docker-compose.yml build
37 |
38 | # Create and run the three instances
39 | docker-compose -f docker-compose.yml up
40 |
41 | ```
42 |
43 | The webapp will be exposed on http://localhost:3000, http://localhost:3001 and http://localhost:3002, each one with a different socket connection
44 |
45 |
--------------------------------------------------------------------------------
/server/routes/user.js:
--------------------------------------------------------------------------------
1 |
2 | const express = require('express');
3 | const userRouter = express.Router()
4 |
5 | const ChatRedis = require('../redis')
6 | const config = require('../config')
7 |
8 | // config.KEY: We just want to store the logged users (username has to be unique)
9 | // so we always use the same key to adapt it to our Redis implementation
10 |
11 | // Login
12 | userRouter.post('/login', (req, res) => {
13 | const newUser = req.body
14 | if (!newUser.username) return res.send({ code: 400, message: 'Data is required' })
15 | console.log(`Login user ${newUser.username}`)
16 |
17 | ChatRedis
18 | .getUser(newUser.username, config.KEY)
19 | .then(user => {
20 | if (!user) {
21 | ChatRedis.addUser(newUser.username, config.KEY, newUser)
22 | console.log(`User ${newUser.username} logged`)
23 | return res.send({ code: 200, message: 'Logged in succesfully' })
24 | }
25 |
26 | console.log(`User ${newUser.username} already exists`)
27 | return res.send({ code: 401, message: 'Username already exists' })
28 | })
29 | })
30 |
31 | // Logout
32 | userRouter.post('/logout', (req, res) => {
33 | const user = req.body
34 | console.log(`Logout user ${user.username}`)
35 |
36 | ChatRedis
37 | .delUser(user.username, config.KEY)
38 | .then(data => {
39 | if (!data)
40 | return res.send({ code: 400, message: 'User not found' })
41 |
42 | return res.send({ code: 200, message: 'Logged in succesfully' })
43 | })
44 | })
45 |
46 |
47 | module.exports = userRouter
--------------------------------------------------------------------------------
/src/components/ChatArea.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
13 | {{msg.msg}}
14 |
15 |
16 |
17 |
18 |
19 |
64 |
65 |
100 |
101 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
37 |
38 |
39 |
81 |
82 |
107 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import { STATUS_OPTIONS, url, STORE_ACTIONS } from '../utils/config'
4 |
5 | Vue.use(Vuex)
6 |
7 | export default new Vuex.Store({
8 | state: {
9 | room: undefined,
10 | username: undefined,
11 | status: STATUS_OPTIONS.available,
12 | rooms: []
13 | },
14 | mutations: {
15 | joinRoom(state, { room, username }) {
16 | state.room = room
17 | state.username = username
18 | },
19 | changeRoom(state, room) {
20 | state.room = room
21 | },
22 | setRooms(state, rooms) {
23 | state.rooms = rooms
24 | },
25 | leaveChat(state) {
26 | state.room = undefined
27 | state.username = undefined
28 | },
29 | changeStatus(state) {
30 | let nextStatus
31 | if (state.status === STATUS_OPTIONS.available) nextStatus = STATUS_OPTIONS.absent
32 | if (state.status === STATUS_OPTIONS.absent) nextStatus = STATUS_OPTIONS.unavailable
33 | if (state.status === STATUS_OPTIONS.unavailable) nextStatus = STATUS_OPTIONS.available
34 |
35 | state.status = nextStatus
36 | }
37 | },
38 | actions: {
39 | joinRoom({ commit }, data) {
40 | return new Promise(async (resolve, reject) => {
41 | try {
42 | const { body } = await Vue.http.post(`${url}/auth/login`, data)
43 | if (body.code === 400 || body.code === 401 || body.code === 500) {
44 | reject({ message: body.message })
45 | }
46 | commit(STORE_ACTIONS.joinRoom, data)
47 | resolve()
48 | } catch (error) {
49 | reject(error)
50 | }
51 | })
52 | },
53 | changeRoom({ commit }, room) {
54 | commit(STORE_ACTIONS.changeRoom, room)
55 | },
56 | setRooms({ commit }) {
57 | return new Promise(async (resolve, reject) => {
58 | try {
59 | // const rooms = await Vue.http.get(`http://${url}/rooms`)
60 | const rooms = [{
61 | id: 1,
62 | name: 'GENERAL'
63 | }, {
64 | id: 2,
65 | name: 'SPORTS'
66 | },{
67 | id: 3,
68 | name: 'GAMES'
69 | },
70 | ]
71 | commit(STORE_ACTIONS.setRooms, rooms)
72 | resolve(rooms)
73 | } catch (error) {
74 | reject(error)
75 | }
76 | })
77 | },
78 | leaveChat({ commit }, username) {
79 | return new Promise(async (resolve, reject) => {
80 | try {
81 | const { body : { code } } = await Vue.http.post(`${url}/auth/logout`, { username })
82 | if (code !== 200) reject()
83 | commit(STORE_ACTIONS.leaveChat)
84 | resolve()
85 | } catch (error) {
86 | reject(error)
87 | }
88 | })
89 | },
90 | changeStatus({ commit }) {
91 | commit(STORE_ACTIONS.changeStatus)
92 | }
93 | }
94 | })
95 |
--------------------------------------------------------------------------------
/server/redis/index.js:
--------------------------------------------------------------------------------
1 | const redis = require('redis')
2 | const bluebird = require('bluebird')
3 |
4 | const config = require('./../config/')
5 |
6 | // Using promises
7 | bluebird.promisifyAll(redis)
8 |
9 | function ChatRedis() {
10 | this.client = redis.createClient({
11 | host: config.REDIS_HOST
12 | })
13 | }
14 |
15 | /**
16 | * Add user with hash
17 | * @param {} room
18 | * @param {} socketId
19 | * @param {} userObject
20 | */
21 | ChatRedis.prototype.addUser = function (room, socketId, userObject) {
22 | this.client
23 | .hsetAsync(room, socketId, JSON.stringify(userObject))
24 | .then(
25 | () => console.debug('addUser ', userObject.username + ' added to the room ' + room),
26 | err => console.log('addUser', err)
27 | )
28 | }
29 |
30 | /**
31 | * Get all users by room
32 | * @param {} room
33 | */
34 | ChatRedis.prototype.getUsers = function (room) {
35 | return this.client
36 | .hgetallAsync(room)
37 | .then(users => {
38 | const userList = []
39 | for (let user in users) {
40 | userList.push(JSON.parse(users[user]))
41 | }
42 | return userList
43 | }, error => {
44 | console.log('getUsers ', error)
45 | })
46 | }
47 |
48 | /**
49 | * Delete a user in a room with socketId
50 | * @param {} room
51 | * @param {} socketId
52 | */
53 | ChatRedis.prototype.delUser = function (room, socketId) {
54 | return this.client
55 | .hdelAsync(room, socketId)
56 | .then(
57 | res => (res),
58 | err => { console.log('delUser ', err) }
59 | )
60 | }
61 |
62 | /**
63 | * Get user by room and socketId
64 | * @param {} room
65 | * @param {} socketId
66 | */
67 | ChatRedis.prototype.getUser = function (room, socketId) {
68 | return this.client
69 | .hgetAsync(room, socketId)
70 | .then(
71 | res => JSON.parse(res),
72 | err => { console.log('getUser ', err) }
73 | )
74 | }
75 |
76 | /**
77 | * Get number of clients connected in a room
78 | */
79 | ChatRedis.prototype.getClientsInRoom = function (io, namespace, room) {
80 | return new Promise((resolve, reject) => {
81 | io.of(namespace).adapter.clients([room], (err, clients) => {
82 | if (err) reject(err)
83 | resolve(clients.length)
84 | })
85 | })
86 | }
87 |
88 | /**
89 | * Set user
90 | * @param {} room
91 | * @param {} socketId
92 | * @param {} newValue
93 | */
94 | ChatRedis.prototype.setUser = function (room, socketId, newValue) {
95 | return this.client
96 | .hsetAsync(room, socketId, JSON.stringify(newValue))
97 | .then(
98 | res => res,
99 | err => { console.log('setUser', err) }
100 | )
101 | }
102 |
103 | module.exports = new ChatRedis()
104 |
--------------------------------------------------------------------------------
/server/chat_namespace/index.js:
--------------------------------------------------------------------------------
1 | const events = require('./events.js')
2 | const config = require('./../config')
3 | const ChatRedis = require('../redis')
4 |
5 | // Socket namespace
6 | let namespace
7 |
8 | // When connecting
9 | const onConnection = (socket) => {
10 | console.log(`Socket connected to port ${config.PORT}`)
11 | let userRoom, userName
12 |
13 | // Listening for joining a room
14 | socket.on('joinRoom', ({ username, room, status }) => {
15 | console.log(`User ${username} wants to join the room ${room}`)
16 |
17 | // Join the room
18 | socket.join(room, async () => {
19 | console.log(`User ${username} joined the room ${room}`)
20 | // We implement here the listener to save the room where the user is
21 | userRoom = room
22 | userName = username
23 |
24 | try {
25 | // add user for the suitable ROOM
26 | await ChatRedis.addUser(room, userName, { username, status, privateChat: false, conference: false })
27 | const users = await ChatRedis.getUsers(room)
28 | // Notify all the users in the same room
29 | namespace.in(room).emit('newUser', { users, username })
30 | } catch (error) {
31 | console.log(error)
32 | }
33 | })
34 |
35 | })
36 |
37 | // Listening for new public messages
38 | socket.on('publicMessage', events.publicMessage(namespace))
39 | socket.on('conferenceInvitation', events.conferenceInvitation(namespace))
40 | // Leave room
41 | socket.on('leaveRoom', events.leaveRoom(socket, namespace))
42 | // Leave room
43 | socket.on('leaveChat', events.leaveChat(socket, namespace))
44 | // Listening for private chats
45 | socket.on('joinPrivateRoom', events.joinPrivateRoom(socket, namespace))
46 | socket.on('joinConference', events.joinConference(socket, namespace))
47 | // Leave private chat
48 | socket.on('leavePrivateRoom', events.leavePrivateRoom(socket, namespace))
49 | socket.on('leaveConference', events.leaveConference(socket, namespace))
50 | // Private message listener
51 | socket.on('privateMessage', events.privateMessage(namespace))
52 | // Private message for Signaling PeerConnection
53 | socket.on('privateMessagePCSignaling', events.privateMessagePCSignaling(namespace))
54 | socket.on('PCSignalingConference', events.PCSignalingConference(namespace))
55 | // Set status
56 | socket.on('changeStatus', events.changeStatus(socket, namespace))
57 |
58 | // Disconnect
59 | socket.on('disconnect', async () => {
60 | console.log(`User "${userName}" with socket ${socket.id} disconnected`)
61 | try {
62 | await ChatRedis.delUser(userName, config.KEY)
63 | events.leaveChat(socket, namespace)({
64 | room: userRoom,
65 | username: userName
66 | })
67 | } catch (error) {
68 | console.log(error)
69 | }
70 | })
71 |
72 | }
73 |
74 | exports.createNameSpace = (io) => {
75 | exports.io = io
76 | namespace = io
77 | .of(config.CHAT_NAMESPACE)
78 | .on('connection', onConnection)
79 | }
--------------------------------------------------------------------------------
/src/components/UserList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 | fiber_manual_record
15 | {{user.username}}
16 |
21 | chat_bubble
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
67 |
68 |
121 |
122 |
--------------------------------------------------------------------------------
/src/components/VideoArea.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
100 |
101 |
125 |
126 |
--------------------------------------------------------------------------------
/src/components/conference/Conference.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
25 |
26 |
27 |
28 |
35 |
36 |
37 |
47 |
48 |
49 |
50 |
51 |
149 |
150 |
--------------------------------------------------------------------------------
/src/components/ChatDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
13 |
14 |
15 |
16 |
17 |
21 | {{videoCall ? 'videocam_off' : 'video_call' }}
22 |
23 |
24 | close
25 |
26 |
27 |
28 | Private chat with {{showDialog.user}}
29 |
33 |
34 |
35 |
36 |
37 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
155 |
156 |
157 |
158 |
203 |
204 |
--------------------------------------------------------------------------------
/src/mixins/WebRTC.js:
--------------------------------------------------------------------------------
1 | import { log } from "../utils/logging"
2 | import { servers } from '../utils/ICEServers'
3 | import { WS_EVENTS, DESCRIPTION_TYPE } from '../utils/config'
4 |
5 | export const videoConfiguration = {
6 | data() {
7 | return {
8 | // Media config
9 | constraints: {
10 | audio: {
11 | echoCancellation: true,
12 | noiseSuppression: true,
13 | autoGainControl: false
14 | },
15 | video: {
16 | width: 400,
17 | height: 250
18 | },
19 | },
20 | // TURN/STUN ice servers
21 | configuration: servers,
22 | // Offer config
23 | offerOptions: {
24 | offerToReceiveAudio: 1,
25 | offerToReceiveVideo: 1
26 | },
27 |
28 | // Local video
29 | myVideo: undefined,
30 | localStream: undefined,
31 | username: ""
32 | }
33 | },
34 |
35 | created() {
36 | this.username = this.$store.state.username
37 | },
38 | beforeDestroy() {
39 | this.localStream.getTracks().forEach(track => track.stop())
40 | },
41 |
42 | methods: {
43 | async getUserMedia() {
44 | log(`Requesting ${this.username} video stream`)
45 |
46 | if ("mediaDevices" in navigator) {
47 | try {
48 | const stream = await navigator.mediaDevices.getUserMedia(this.constraints)
49 | this.myVideo.srcObject = stream
50 | this.myVideo.volume = 0
51 | this.localStream = stream
52 | } catch (error) {
53 | log(`getUserMedia error: ${error}`)
54 | }
55 | }
56 | },
57 | getAudioVideo() {
58 | const video = this.localStream.getVideoTracks()
59 | const audio = this.localStream.getAudioTracks()
60 |
61 | if (video.length > 0) log(`Using video device: ${video[0].label}`)
62 | if (audio.length > 0) log(`Using audio device: ${audio[0].label}`)
63 | },
64 | async setRemoteDescription(remoteDesc, pc) {
65 | try {
66 | log(`${this.username} setRemoteDescription: start`)
67 | await pc.setRemoteDescription(remoteDesc)
68 | log(`${this.username} setRemoteDescription: finished`)
69 | } catch (error) {
70 | log(`Error setting the RemoteDescription in ${this.username}. Error: ${error}`)
71 | }
72 | },
73 | async createOffer(pc, to, room, conference = false) {
74 | log(`${this.username} wants to start a call with ${to}`)
75 | try {
76 | const offer = await pc.createOffer(this.offerOptions)
77 | log(`${this.username} setLocalDescription: start`)
78 | await pc.setLocalDescription(offer)
79 | log(`${this.username} setLocalDescription: finished`)
80 | this.sendSignalingMessage(pc.localDescription, true, to, room, conference)
81 | } catch (error) {
82 | log(`Error creating the offer from ${this.username}. Error: ${error}`)
83 | }
84 | },
85 | async createAnswer(pc, to, room, conference) {
86 | log(`${this.username} create an answer: start`)
87 | try {
88 | const answer = await pc.createAnswer()
89 | log(`${this.username} setLocalDescription: start`)
90 | await pc.setLocalDescription(answer)
91 | log(`${this.username} setLocalDescription: finished`)
92 | this.sendSignalingMessage(pc.localDescription, false, to, room, conference)
93 | } catch (error) {
94 | log(`Error creating the answer from ${this.username}. Error: ${error}`)
95 | }
96 | },
97 | async handleAnswer(desc, pc, from, room, conference = false) {
98 | log(`${this.username} gets an offer from ${from}`)
99 | await this.setRemoteDescription(desc, pc)
100 | this.createAnswer(pc, from, room, conference)
101 | },
102 | sendSignalingMessage(desc, offer, to, room, conference) {
103 | const isOffer = offer ? DESCRIPTION_TYPE.offer : DESCRIPTION_TYPE.answer
104 | log(`${this.username} sends the ${isOffer} through the signal channel to ${to} in room ${room}`)
105 |
106 | // send the offer to the other peer
107 | this.$socket.emit(conference ? WS_EVENTS.PCSignalingConference : WS_EVENTS.privateMessagePCSignaling, {
108 | desc: desc,
109 | to: to,
110 | from: this.username,
111 | room: room,
112 | })
113 | },
114 | addLocalStream(pc) {
115 | pc.addStream(this.localStream)
116 | },
117 | addCandidate(pc, candidate) {
118 | try {
119 | log(`${this.username} added a candidate`)
120 | pc.addIceCandidate(candidate)
121 | } catch (error) {
122 | log(`Error adding a candidate in ${this.username}. Error: ${error}`)
123 | }
124 | },
125 | onIceCandidates(pc, to, room, conference = false) {
126 | pc.onicecandidate = ({ candidate }) => {
127 | if (!candidate) return
128 | setTimeout(() => {
129 | this.$socket.emit(conference ? WS_EVENTS.PCSignalingConference : WS_EVENTS.privateMessagePCSignaling, {
130 | candidate,
131 | to: to,
132 | from: this.username,
133 | room: room,
134 | })
135 | }, 500)
136 | }
137 | },
138 | onAddStream(user, video) {
139 | user.pc.onaddstream = event => {
140 | user.peerVideo = user.peerVideo || document.getElementById(video)
141 | if (!user.peerVideo.srcObject && event.stream) {
142 | user.peerStream = event.stream
143 | user.peerVideo.srcObject = user.peerStream
144 | }
145 | }
146 | },
147 | pauseVideo() {
148 | this.localStream.getVideoTracks().forEach(t => (t.enabled = !t.enabled))
149 | },
150 | pauseAudio() {
151 | this.localStream.getAudioTracks().forEach(t => (t.enabled = !t.enabled))
152 | },
153 | },
154 | }
--------------------------------------------------------------------------------
/server/chat_namespace/events.js:
--------------------------------------------------------------------------------
1 |
2 | const ChatRedis = require('../redis')
3 |
4 | const changeStatus = (socket, namespace) => async ({ username, status, room }) => {
5 | console.log(`User "${username}" wants to change his status to ${status}`)
6 |
7 | try {
8 | const user = await ChatRedis.getUser(room, username)
9 | await ChatRedis.setUser(room, username, { ...user, status })
10 | const users = await ChatRedis.getUsers(room)
11 | // Notify all the users in the same room
12 | namespace.in(room).emit('newUser', { users, username })
13 | } catch (error) {
14 | console.log(error)
15 | }
16 | }
17 |
18 | const publicMessage = (namespace) => ({ room, message, username }) => {
19 | namespace.in(room).emit('newMessage', { message, username })
20 | }
21 |
22 | const conferenceInvitation = (namespace) => async ({ room, to, from }) => {
23 | console.log(`Conference - Invitation from "${from}" to "${to}" in room ${room}`)
24 | try {
25 | const { privateChat, conference } = await ChatRedis.getUser(room, to)
26 | // User already talking
27 | if (privateChat || conference) {
28 | console.log(`Conference - User "${to}" is already talking. PrivateChat: ${privateChat} - Conference: ${conference}`)
29 | return namespace.to(from).emit('conferenceInvitation', { message: `User ${to} is already talking`, from })
30 | }
31 | namespace.in(room).emit('conferenceInvitation', { room, to, from })
32 | } catch (error) {
33 | console.log(error)
34 | }
35 | }
36 |
37 | const joinConference = (socket, namespace) => ({ username, room, to, from }) => {
38 | const admin = username === to
39 | console.log(admin
40 | ? `Conference - User "${username}" wants to open a conference room`
41 | : `Conference - User "${username}" wants to join the "${to}" conference`)
42 |
43 | // Join the room
44 | socket.join(to, async () => {
45 | if (!room) return
46 |
47 | try {
48 | const user = await ChatRedis.getUser(room, username)
49 | await ChatRedis.setUser(room, username, { ...user, conference: to })
50 | console.log(admin
51 | ? `Conference - User "${username}" opened a conference`
52 | : `Conference - User "${username}" joined the "${to}" conference`)
53 | namespace.in(to).emit('joinConference', { username, to, room, from })
54 | } catch (error) {
55 | console.log(error)
56 | }
57 | })
58 | }
59 |
60 | const leaveConference = (socket, namespace) => async ({ room, from, conferenceRoom }) => {
61 | console.log(`Conference - User "${from}" wants to leave the conference room ${room}`)
62 |
63 | try {
64 | const user = await ChatRedis.getUser(room, from)
65 | await ChatRedis.setUser(room, from, { ...user, conference: false })
66 | socket.leave(conferenceRoom, () => {
67 | namespace.to(conferenceRoom).emit('leaveConference', { room, from })
68 | console.log(`Conference - User ${from} left the conference room ${room}`)
69 | })
70 | } catch (error) {
71 | console.log(error)
72 | }
73 | }
74 |
75 | const PCSignalingConference = (namespace) => ({ desc, to, from, room, candidate }) => {
76 | candidate
77 | ? console.log(`Conference - User "${from}" sends a candidate to "${to}"`)
78 | : console.log(`Conference - User "${from}" sends a ${from === room ? 'offer' : 'answer'} to "${to}"`)
79 | namespace.to(room).emit('PCSignalingConference', { desc, to, from, candidate })
80 | }
81 |
82 |
83 | const leaveRoom = (socket, namespace) => ({ room, username }) => {
84 | console.log(`Room - User "${username}" wants to leave the room ${room}`)
85 |
86 | socket.leave(room, async () => {
87 | console.log(`Room - User "${username}" left the room ${room}`)
88 |
89 | try {
90 | await ChatRedis.delUser(room, username)
91 | const users = await ChatRedis.getUsers(room)
92 | // Notify all the users in the same room
93 | namespace.in(room).emit('newUser', { users, username })
94 | } catch (error) {
95 | console.log(error)
96 | }
97 | })
98 | }
99 |
100 | const leaveChat = (socket, namespace) => async ({ room, username }) => {
101 | console.log(`User "${username}" wants to leave the chat`)
102 |
103 | try {
104 | await ChatRedis.delUser(room, username)
105 | const users = await ChatRedis.getUsers(room)
106 | // Leave the socket
107 | socket.leave(room, () => {
108 | console.log(`User "${username}" left the room ${room}`)
109 | // Notify all the users in the same room
110 | namespace.in(room).emit('leaveChat', { users, message: `${username} left the room`})
111 | })
112 | } catch (error) {
113 | console.log(error)
114 | }
115 | }
116 |
117 | const joinPrivateRoom = (socket, namespace) => ({ username, room, to, from, joinConfirmation }) => {
118 | console.log(`Private chat - User "${username}" ${!joinConfirmation
119 | ? 'wants to have a' : 'accept the private'} chat with with "${to}"`)
120 |
121 | // Join the room
122 | socket.join(to, async () => {
123 | if (!room) return
124 |
125 | try {
126 | const { privateChat } = await ChatRedis.getUser(room, to)
127 | if (!!privateChat && privateChat !== username) {
128 | namespace.to(to).emit('leavePrivateRoom', {
129 | to, room,
130 | privateMessage: `${to} is already talking`,
131 | from: username,
132 | })
133 | // Leave the room
134 | socket.leave(to, () => console.log(`Private chat - User "${username}" forced to leave the room "${to}"`))
135 | return
136 | }
137 |
138 | const user = await ChatRedis.getUser(room, username)
139 | await ChatRedis.setUser(room, username, { ...user, privateChat: to })
140 | if (!joinConfirmation) namespace.in(room).emit('privateChat', { username, to, room, from })
141 | } catch (error) {
142 | console.log(error)
143 | }
144 | })
145 | }
146 |
147 | const leavePrivateRoom = (socket, namespace) => async ({ room, from, to }) => {
148 | console.log(`Private chat - User "${from}" wants to leave the private chat with "${to}"`)
149 |
150 | try {
151 | const user = await ChatRedis.getUser(room, from)
152 | await ChatRedis.setUser(room, from, { ...user, privateChat: false })
153 | socket.leave(to, () => {
154 | console.log(`Private chat - User "${from}" left the private chat with "${to}"`)
155 | namespace.to(to).emit('leavePrivateRoom', {
156 | to, from,
157 | privateMessage: `${from} has closed the chat`,
158 | })
159 | })
160 | } catch (error) {
161 | console.log(error)
162 | }
163 | }
164 |
165 | const privateMessage = (namespace) => ({ privateMessage, to, from, room }) => {
166 | console.log(`Private chat - User "${from}" sends a private message to "${to}"`)
167 | // Private message to the user
168 | namespace.to(room).emit('privateMessage', { to, privateMessage, from, room })
169 | }
170 |
171 | const privateMessagePCSignaling = (namespace) => ({ desc, to, from, room, candidate }) => {
172 | candidate
173 | ? console.log(`Private chat - User "${from}" sends a candidate to "${to}"`)
174 | : console.log(`Private chat - User "${from}" sends a ${from !== room ? 'offer' : 'answer'} to "${to}"`)
175 | // Private signaling to the user
176 | namespace.to(room).emit('privateMessagePCSignaling', { desc, to, from, candidate })
177 | }
178 |
179 | module.exports = {
180 | publicMessage,
181 | leaveRoom,
182 | joinPrivateRoom,
183 | leavePrivateRoom,
184 | privateMessage,
185 | privateMessagePCSignaling,
186 | leaveChat,
187 | changeStatus,
188 | conferenceInvitation,
189 | joinConference,
190 | leaveConference,
191 | PCSignalingConference
192 | }
--------------------------------------------------------------------------------
/src/views/Chat.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Room
8 |
9 | {{room.name}}
14 |
15 |
16 |
17 |
18 |
19 |
20 | {{room}}
21 |
25 | {{!conference.open ? 'group' : 'close'}}
26 |
27 |
28 | power_settings_new
29 |
30 |
31 |
32 |
33 |
38 |
39 |
40 |
41 |
45 |
46 |
47 |
48 |
49 |
51 |
52 |
53 |
57 |
58 |
59 |
60 |
64 |
65 |
66 |
67 |
68 |
69 |
245 |
246 |
247 |
248 |
358 |
359 |
360 |
--------------------------------------------------------------------------------
/src/utils/ICEServers.js:
--------------------------------------------------------------------------------
1 | export const servers = {
2 | iceServers: [
3 | { url: 'stun:stun.services.mozilla.org' },
4 | { url: "stun:stun01.sipphone.com" },
5 | { url: "stun:stun.ekiga.net" },
6 | { url: "stun:stun.fwdnet.net" },
7 | { url: "stun:stun.ideasip.com" },
8 | { url: "stun:stun.iptel.org" },
9 | { url: "stun:stun.rixtelecom.se" },
10 | { url: "stun:stun.schlund.de" },
11 | { url: "stun:stun.l.google.com:19302" },
12 | { url: "stun:stun1.l.google.com:19302" },
13 | { url: "stun:stun2.l.google.com:19302" },
14 | { url: "stun:stun3.l.google.com:19302" },
15 | { url: "stun:stun4.l.google.com:19302" },
16 | { url: "stun:stunserver.org" },
17 | { url: "stun:stun.softjoys.com" },
18 | { url: "stun:stun.voiparound.com" },
19 | { url: "stun:stun.voipbuster.com" },
20 | { url: "stun:stun.voipstunt.com" },
21 | { url: "stun:stun.voxgratia.org" },
22 | { url: "stun:stun.xten.com" },
23 | { url: "stun:stun.wtfismyip.com" },
24 | { url: "stun:stun.1und1.de" },
25 | { url: "stun:stun.gmx.net" },
26 | { url: "stun:stun.l.google.com:19305" },
27 | { url: "stun:stun1.l.google.com:19305" },
28 | { url: "stun:stun2.l.google.com:19305" },
29 | { url: "stun:stun3.l.google.com:19305" },
30 | { url: "stun:stun4.l.google.com:19305" },
31 | { url: "stun:stun.jappix.com:3478" },
32 | { url: "stun:stun.services.mozilla.com" },
33 | { url: "stun:stun.counterpath.com" },
34 | { url: "stun:stun.stunprotocol.prg" },
35 | { url: "stun:s1.taraba.net" },
36 | { url: "stun:s2.taraba.net" },
37 | { url: "stun:s1.voipstation.jp" },
38 | { url: "stun:s2.voipstation.jp" },
39 | { url: "stun:stun.sipnet.net:3478" },
40 | { url: "stun:stun.sipnet.ru:3478" },
41 | { url: "stun:stun.stunprotocol.org:3478" },
42 | { url: 'stun:stun.1und1.de:3478' },
43 | { url: 'stun:stun.gmx.net:3478' },
44 | { url: 'stun:stun.l.google.com:19302' },
45 | { url: 'stun:stun1.l.google.com:19302' },
46 | { url: 'stun:stun2.l.google.com:19302' },
47 | { url: 'stun:stun3.l.google.com:19302' },
48 | { url: 'stun:stun4.l.google.com:19302' },
49 | { url: 'stun:23.21.150.121:3478' },
50 | { url: 'stun:iphone-stun.strato-iphone.de:3478' },
51 | { url: 'stun:numb.viagenie.ca:3478' },
52 | { url: 'stun:stun.12connect.com:3478' },
53 | { url: 'stun:stun.12voip.com:3478' },
54 | { url: 'stun:stun.1und1.de:3478' },
55 | { url: 'stun:stun.2talk.co.nz:3478' },
56 | { url: 'stun:stun.2talk.com:3478' },
57 | { url: 'stun:stun.3clogic.com:3478' },
58 | { url: 'stun:stun.3cx.com:3478' },
59 | { url: 'stun:stun.a-mm.tv:3478' },
60 | { url: 'stun:stun.aa.net.uk:3478' },
61 | { url: 'stun:stun.acrobits.cz:3478' },
62 | { url: 'stun:stun.actionvoip.com:3478' },
63 | { url: 'stun:stun.advfn.com:3478' },
64 | { url: 'stun:stun.aeta-audio.com:3478' },
65 | { url: 'stun:stun.aeta.com:3478' },
66 | { url: 'stun:stun.altar.com.pl:3478' },
67 | { url: 'stun:stun.annatel.net:3478' },
68 | { url: 'stun:stun.antisip.com:3478' },
69 | { url: 'stun:stun.arbuz.ru:3478' },
70 | { url: 'stun:stun.avigora.fr:3478' },
71 | { url: 'stun:stun.awa-shima.com:3478' },
72 | { url: 'stun:stun.b2b2c.ca:3478' },
73 | { url: 'stun:stun.bahnhof.net:3478' },
74 | { url: 'stun:stun.barracuda.com:3478' },
75 | { url: 'stun:stun.bluesip.net:3478' },
76 | { url: 'stun:stun.bmwgs.cz:3478' },
77 | { url: 'stun:stun.botonakis.com:3478' },
78 | { url: 'stun:stun.budgetsip.com:3478' },
79 | { url: 'stun:stun.cablenet-as.net:3478' },
80 | { url: 'stun:stun.callromania.ro:3478' },
81 | { url: 'stun:stun.callwithus.com:3478' },
82 | { url: 'stun:stun.chathelp.ru:3478' },
83 | { url: 'stun:stun.cheapvoip.com:3478' },
84 | { url: 'stun:stun.ciktel.com:3478' },
85 | { url: 'stun:stun.cloopen.com:3478' },
86 | { url: 'stun:stun.comfi.com:3478' },
87 | { url: 'stun:stun.commpeak.com:3478' },
88 | { url: 'stun:stun.comtube.com:3478' },
89 | { url: 'stun:stun.comtube.ru:3478' },
90 | { url: 'stun:stun.cope.es:3478' },
91 | { url: 'stun:stun.counterpath.com:3478' },
92 | { url: 'stun:stun.counterpath.net:3478' },
93 | { url: 'stun:stun.datamanagement.it:3478' },
94 | { url: 'stun:stun.dcalling.de:3478' },
95 | { url: 'stun:stun.demos.ru:3478' },
96 | { url: 'stun:stun.develz.org:3478' },
97 | { url: 'stun:stun.dingaling.ca:3478' },
98 | { url: 'stun:stun.doublerobotics.com:3478' },
99 | { url: 'stun:stun.dus.net:3478' },
100 | { url: 'stun:stun.easycall.pl:3478' },
101 | { url: 'stun:stun.easyvoip.com:3478' },
102 | { url: 'stun:stun.ekiga.net:3478' },
103 | { url: 'stun:stun.epygi.com:3478' },
104 | { url: 'stun:stun.etoilediese.fr:3478' },
105 | { url: 'stun:stun.faktortel.com.au:3478' },
106 | { url: 'stun:stun.freecall.com:3478' },
107 | { url: 'stun:stun.freeswitch.org:3478' },
108 | { url: 'stun:stun.freevoipdeal.com:3478' },
109 | { url: 'stun:stun.gmx.de:3478' },
110 | { url: 'stun:stun.gmx.net:3478' },
111 | { url: 'stun:stun.gradwell.com:3478' },
112 | { url: 'stun:stun.halonet.pl:3478' },
113 | { url: 'stun:stun.hellonanu.com:3478' },
114 | { url: 'stun:stun.hoiio.com:3478' },
115 | { url: 'stun:stun.hosteurope.de:3478' },
116 | { url: 'stun:stun.ideasip.com:3478' },
117 | { url: 'stun:stun.infra.net:3478' },
118 | { url: 'stun:stun.internetcalls.com:3478' },
119 | { url: 'stun:stun.intervoip.com:3478' },
120 | { url: 'stun:stun.ipcomms.net:3478' },
121 | { url: 'stun:stun.ipfire.org:3478' },
122 | { url: 'stun:stun.ippi.fr:3478' },
123 | { url: 'stun:stun.ipshka.com:3478' },
124 | { url: 'stun:stun.irian.at:3478' },
125 | { url: 'stun:stun.it1.hr:3478' },
126 | { url: 'stun:stun.ivao.aero:3478' },
127 | { url: 'stun:stun.jumblo.com:3478' },
128 | { url: 'stun:stun.justvoip.com:3478' },
129 | { url: 'stun:stun.kanet.ru:3478' },
130 | { url: 'stun:stun.kiwilink.co.nz:3478' },
131 | { url: 'stun:stun.l.google.com:19302' },
132 | { url: 'stun:stun.linea7.net:3478' },
133 | { url: 'stun:stun.linphone.org:3478' },
134 | { url: 'stun:stun.liveo.fr:3478' },
135 | { url: 'stun:stun.lowratevoip.com:3478' },
136 | { url: 'stun:stun.lugosoft.com:3478' },
137 | { url: 'stun:stun.lundimatin.fr:3478' },
138 | { url: 'stun:stun.magnet.ie:3478' },
139 | { url: 'stun:stun.mgn.ru:3478' },
140 | { url: 'stun:stun.mit.de:3478' },
141 | { url: 'stun:stun.mitake.com.tw:3478' },
142 | { url: 'stun:stun.miwifi.com:3478' },
143 | { url: 'stun:stun.modulus.gr:3478' },
144 | { url: 'stun:stun.myvoiptraffic.com:3478' },
145 | { url: 'stun:stun.mywatson.it:3478' },
146 | { url: 'stun:stun.nas.net:3478' },
147 | { url: 'stun:stun.neotel.co.za:3478' },
148 | { url: 'stun:stun.netappel.com:3478' },
149 | { url: 'stun:stun.netgsm.com.tr:3478' },
150 | { url: 'stun:stun.nfon.net:3478' },
151 | { url: 'stun:stun.noblogs.org:3478' },
152 | { url: 'stun:stun.noc.ams-ix.net:3478' },
153 | { url: 'stun:stun.nonoh.net:3478' },
154 | { url: 'stun:stun.nottingham.ac.uk:3478' },
155 | { url: 'stun:stun.nova.is:3478' },
156 | { url: 'stun:stun.on.net.mk:3478' },
157 | { url: 'stun:stun.ooma.com:3478' },
158 | { url: 'stun:stun.ooonet.ru:3478' },
159 | { url: 'stun:stun.oriontelekom.rs:3478' },
160 | { url: 'stun:stun.outland-net.de:3478' },
161 | { url: 'stun:stun.ozekiphone.com:3478' },
162 | { url: 'stun:stun.personal-voip.de:3478' },
163 | { url: 'stun:stun.phone.com:3478' },
164 | { url: 'stun:stun.pjsip.org:3478' },
165 | { url: 'stun:stun.poivy.com:3478' },
166 | { url: 'stun:stun.powerpbx.org:3478' },
167 | { url: 'stun:stun.powervoip.com:3478' },
168 | { url: 'stun:stun.ppdi.com:3478' },
169 | { url: 'stun:stun.qq.com:3478' },
170 | { url: 'stun:stun.rackco.com:3478' },
171 | { url: 'stun:stun.rapidnet.de:3478' },
172 | { url: 'stun:stun.rb-net.com:3478' },
173 | { url: 'stun:stun.rixtelecom.se:3478' },
174 | { url: 'stun:stun.rockenstein.de:3478' },
175 | { url: 'stun:stun.rolmail.net:3478' },
176 | { url: 'stun:stun.rynga.com:3478' },
177 | { url: 'stun:stun.schlund.de:3478' },
178 | { url: 'stun:stun.services.mozilla.com:3478' },
179 | { url: 'stun:stun.sigmavoip.com:3478' },
180 | { url: 'stun:stun.sip.us:3478' },
181 | { url: 'stun:stun.sipdiscount.com:3478' },
182 | { url: 'stun:stun.sipgate.net:10000' },
183 | { url: 'stun:stun.sipgate.net:3478' },
184 | { url: 'stun:stun.siplogin.de:3478' },
185 | { url: 'stun:stun.sipnet.net:3478' },
186 | { url: 'stun:stun.sipnet.ru:3478' },
187 | { url: 'stun:stun.siportal.it:3478' },
188 | { url: 'stun:stun.sippeer.dk:3478' },
189 | { url: 'stun:stun.siptraffic.com:3478' },
190 | { url: 'stun:stun.skylink.ru:3478' },
191 | { url: 'stun:stun.sma.de:3478' },
192 | { url: 'stun:stun.smartvoip.com:3478' },
193 | { url: 'stun:stun.smsdiscount.com:3478' },
194 | { url: 'stun:stun.snafu.de:3478' },
195 | { url: 'stun:stun.softjoys.com:3478' },
196 | { url: 'stun:stun.solcon.nl:3478' },
197 | { url: 'stun:stun.solnet.ch:3478' },
198 | { url: 'stun:stun.sonetel.com:3478' },
199 | { url: 'stun:stun.sonetel.net:3478' },
200 | { url: 'stun:stun.sovtest.ru:3478' },
201 | { url: 'stun:stun.speedy.com.ar:3478' },
202 | { url: 'stun:stun.spokn.com:3478' },
203 | { url: 'stun:stun.srce.hr:3478' },
204 | { url: 'stun:stun.ssl7.net:3478' },
205 | { url: 'stun:stun.stunprotocol.org:3478' },
206 | { url: 'stun:stun.symform.com:3478' },
207 | { url: 'stun:stun.symplicity.com:3478' },
208 | { url: 'stun:stun.t-online.de:3478' },
209 | { url: 'stun:stun.tagan.ru:3478' },
210 | { url: 'stun:stun.teachercreated.com:3478' },
211 | { url: 'stun:stun.tel.lu:3478' },
212 | { url: 'stun:stun.telbo.com:3478' },
213 | { url: 'stun:stun.telefacil.com:3478' },
214 | { url: 'stun:stun.tng.de:3478' },
215 | { url: 'stun:stun.twt.it:3478' },
216 | { url: 'stun:stun.u-blox.com:3478' },
217 | { url: 'stun:stun.ucsb.edu:3478' },
218 | { url: 'stun:stun.ucw.cz:3478' },
219 | { url: 'stun:stun.uls.co.za:3478' },
220 | { url: 'stun:stun.unseen.is:3478' },
221 | { url: 'stun:stun.usfamily.net:3478' },
222 | { url: 'stun:stun.veoh.com:3478' },
223 | { url: 'stun:stun.vidyo.com:3478' },
224 | { url: 'stun:stun.vipgroup.net:3478' },
225 | { url: 'stun:stun.viva.gr:3478' },
226 | { url: 'stun:stun.vivox.com:3478' },
227 | { url: 'stun:stun.vline.com:3478' },
228 | { url: 'stun:stun.vo.lu:3478' },
229 | { url: 'stun:stun.vodafone.ro:3478' },
230 | { url: 'stun:stun.voicetrading.com:3478' },
231 | { url: 'stun:stun.voip.aebc.com:3478' },
232 | { url: 'stun:stun.voip.blackberry.com:3478' },
233 | { url: 'stun:stun.voip.eutelia.it:3478' },
234 | { url: 'stun:stun.voiparound.com:3478' },
235 | { url: 'stun:stun.voipblast.com:3478' },
236 | { url: 'stun:stun.voipbuster.com:3478' },
237 | { url: 'stun:stun.voipbusterpro.com:3478' },
238 | { url: 'stun:stun.voipcheap.co.uk:3478' },
239 | { url: 'stun:stun.voipcheap.com:3478' },
240 | { url: 'stun:stun.voipfibre.com:3478' },
241 | { url: 'stun:stun.voipgain.com:3478' },
242 | { url: 'stun:stun.voipgate.com:3478' },
243 | { url: 'stun:stun.voipinfocenter.com:3478' },
244 | { url: 'stun:stun.voipplanet.nl:3478' },
245 | { url: 'stun:stun.voippro.com:3478' },
246 | { url: 'stun:stun.voipraider.com:3478' },
247 | { url: 'stun:stun.voipstunt.com:3478' },
248 | { url: 'stun:stun.voipwise.com:3478' },
249 | { url: 'stun:stun.voipzoom.com:3478' },
250 | { url: 'stun:stun.vopium.com:3478' },
251 | { url: 'stun:stun.voxox.com:3478' },
252 | { url: 'stun:stun.voys.nl:3478' },
253 | { url: 'stun:stun.voztele.com:3478' },
254 | { url: 'stun:stun.vyke.com:3478' },
255 | { url: 'stun:stun.webcalldirect.com:3478' },
256 | { url: 'stun:stun.whoi.edu:3478' },
257 | { url: 'stun:stun.wifirst.net:3478' },
258 | { url: 'stun:stun.wwdl.net:3478' },
259 | { url: 'stun:stun.xs4all.nl:3478' },
260 | { url: 'stun:stun.xtratelecom.es:3478' },
261 | { url: 'stun:stun.yesss.at:3478' },
262 | { url: 'stun:stun.zadarma.com:3478' },
263 | { url: 'stun:stun.zadv.com:3478' },
264 | { url: 'stun:stun.zoiper.com:3478' },
265 | { url: 'stun:stun1.faktortel.com.au:3478' },
266 | { url: 'stun:stun1.l.google.com:19302' },
267 | { url: 'stun:stun1.voiceeclipse.net:3478' },
268 | { url: 'stun:stun2.l.google.com:19302' },
269 | { url: 'stun:stun3.l.google.com:19302' },
270 | { url: 'stun:stun4.l.google.com:19302' },
271 | { url: 'stun:stunserver.org:3478' },
272 | {
273 | url: "turn:numb.viagenie.ca",
274 | credential: "muazkh",
275 | username: "webrtc@live.com"
276 | },
277 | {
278 | url: "turn:192.158.29.39:3478?transport=udp",
279 | credential: "JZEOEt2V3Qb0y27GRntt2u2PAYA=",
280 | username: "28224511:1379330808"
281 | },
282 | {
283 | url: "turn:192.158.29.39:3478?transport=tcp",
284 | credential: "JZEOEt2V3Qb0y27GRntt2u2PAYA=",
285 | username: "28224511:1379330808"
286 | },
287 | ],
288 | iceTransportPolicy: "all"
289 | }
--------------------------------------------------------------------------------