├── base
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-39.pyc
│ ├── admin.cpython-39.pyc
│ ├── apps.cpython-39.pyc
│ ├── models.cpython-39.pyc
│ ├── urls.cpython-39.pyc
│ └── views.cpython-39.pyc
├── admin.py
├── apps.py
├── migrations
│ ├── 0001_initial.py
│ ├── 0002_rename_member_id_roommember_uid.py
│ ├── __init__.py
│ └── __pycache__
│ │ ├── 0001_initial.cpython-39.pyc
│ │ ├── 0002_rename_member_id_roommember_uid.cpython-39.pyc
│ │ └── __init__.cpython-39.pyc
├── models.py
├── templates
│ └── base
│ │ ├── lobby.html
│ │ ├── main.html
│ │ └── room.html
├── tests.py
├── urls.py
└── views.py
├── db.sqlite3
├── manage.py
├── mychat
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-39.pyc
│ ├── settings.cpython-39.pyc
│ ├── urls.cpython-39.pyc
│ └── wsgi.cpython-39.pyc
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py
├── ngrok.exe
├── readme.md
├── requirements.txt
├── static
├── assets
│ └── AgoraRTC_N-4.8.0.js
├── images
│ ├── chat-icon.png
│ ├── leave.svg
│ ├── microphone.svg
│ └── video.svg
├── js
│ ├── main.js
│ └── streams.js
└── styles
│ └── main.css
└── template
├── chatroom.html
├── images
├── chat-icon.png
├── leave.svg
├── microphone.svg
└── video.svg
├── lobby.html
└── main.css
/base/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/base/__init__.py
--------------------------------------------------------------------------------
/base/__pycache__/__init__.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/base/__pycache__/__init__.cpython-39.pyc
--------------------------------------------------------------------------------
/base/__pycache__/admin.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/base/__pycache__/admin.cpython-39.pyc
--------------------------------------------------------------------------------
/base/__pycache__/apps.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/base/__pycache__/apps.cpython-39.pyc
--------------------------------------------------------------------------------
/base/__pycache__/models.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/base/__pycache__/models.cpython-39.pyc
--------------------------------------------------------------------------------
/base/__pycache__/urls.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/base/__pycache__/urls.cpython-39.pyc
--------------------------------------------------------------------------------
/base/__pycache__/views.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/base/__pycache__/views.cpython-39.pyc
--------------------------------------------------------------------------------
/base/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | # Register your models here.
4 |
5 | from .models import RoomMember
6 |
7 |
8 | admin.site.register(RoomMember)
9 |
--------------------------------------------------------------------------------
/base/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class BaseConfig(AppConfig):
5 | default_auto_field = 'django.db.models.BigAutoField'
6 | name = 'base'
7 |
--------------------------------------------------------------------------------
/base/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.1 on 2022-01-06 03:14
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | initial = True
9 |
10 | dependencies = [
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='RoomMember',
16 | fields=[
17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('name', models.CharField(max_length=200)),
19 | ('member_id', models.CharField(max_length=1000)),
20 | ('room_name', models.CharField(max_length=200)),
21 | ('insession', models.BooleanField(default=True)),
22 | ],
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/base/migrations/0002_rename_member_id_roommember_uid.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.1 on 2022-01-06 03:16
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('base', '0001_initial'),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(
14 | model_name='roommember',
15 | old_name='member_id',
16 | new_name='uid',
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/base/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/base/migrations/__init__.py
--------------------------------------------------------------------------------
/base/migrations/__pycache__/0001_initial.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/base/migrations/__pycache__/0001_initial.cpython-39.pyc
--------------------------------------------------------------------------------
/base/migrations/__pycache__/0002_rename_member_id_roommember_uid.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/base/migrations/__pycache__/0002_rename_member_id_roommember_uid.cpython-39.pyc
--------------------------------------------------------------------------------
/base/migrations/__pycache__/__init__.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/base/migrations/__pycache__/__init__.cpython-39.pyc
--------------------------------------------------------------------------------
/base/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | # Create your models here.
4 |
5 | class RoomMember(models.Model):
6 | name = models.CharField(max_length=200)
7 | uid = models.CharField(max_length=1000)
8 | room_name = models.CharField(max_length=200)
9 | insession = models.BooleanField(default=True)
10 |
11 | def __str__(self):
12 | return self.name
--------------------------------------------------------------------------------
/base/templates/base/lobby.html:
--------------------------------------------------------------------------------
1 | {% extends 'base/main.html' %}
2 | {% load static %}
3 | {% block content %}
4 |
5 |
6 |
7 |
35 |
36 |
37 |
63 |
64 | {% endblock content %}
65 |
66 |
--------------------------------------------------------------------------------
/base/templates/base/main.html:
--------------------------------------------------------------------------------
1 |
2 | {% load static %}
3 |
4 |
5 |
6 |
7 | MyChat
8 |
9 |
10 |
11 |
12 |
13 | {% block content %}
14 |
15 | {% endblock content %}
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/base/templates/base/room.html:
--------------------------------------------------------------------------------
1 | {% extends 'base/main.html' %}
2 | {% load static %}
3 | {% block content %}
4 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |

15 |
16 |
17 |
18 |

19 |
20 |
21 |
22 |

23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {% endblock content %}
--------------------------------------------------------------------------------
/base/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/base/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from . import views
3 |
4 | urlpatterns = [
5 | path('', views.lobby),
6 | path('room/', views.room),
7 | path('get_token/', views.getToken),
8 |
9 | path('create_member/', views.createMember),
10 | path('get_member/', views.getMember),
11 | path('delete_member/', views.deleteMember),
12 | ]
--------------------------------------------------------------------------------
/base/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 | from django.http import JsonResponse
3 | import random
4 | import time
5 | from agora_token_builder import RtcTokenBuilder
6 | from .models import RoomMember
7 | import json
8 | from django.views.decorators.csrf import csrf_exempt
9 |
10 |
11 |
12 | # Create your views here.
13 |
14 | def lobby(request):
15 | return render(request, 'base/lobby.html')
16 |
17 | def room(request):
18 | return render(request, 'base/room.html')
19 |
20 |
21 | def getToken(request):
22 | appId = "YOUR APP ID"
23 | appCertificate = "YOUR APP CERTIFICATE"
24 | channelName = request.GET.get('channel')
25 | uid = random.randint(1, 230)
26 | expirationTimeInSeconds = 3600
27 | currentTimeStamp = int(time.time())
28 | privilegeExpiredTs = currentTimeStamp + expirationTimeInSeconds
29 | role = 1
30 |
31 | token = RtcTokenBuilder.buildTokenWithUid(appId, appCertificate, channelName, uid, role, privilegeExpiredTs)
32 |
33 | return JsonResponse({'token': token, 'uid': uid}, safe=False)
34 |
35 |
36 | @csrf_exempt
37 | def createMember(request):
38 | data = json.loads(request.body)
39 | member, created = RoomMember.objects.get_or_create(
40 | name=data['name'],
41 | uid=data['UID'],
42 | room_name=data['room_name']
43 | )
44 |
45 | return JsonResponse({'name':data['name']}, safe=False)
46 |
47 |
48 | def getMember(request):
49 | uid = request.GET.get('UID')
50 | room_name = request.GET.get('room_name')
51 |
52 | member = RoomMember.objects.get(
53 | uid=uid,
54 | room_name=room_name,
55 | )
56 | name = member.name
57 | return JsonResponse({'name':member.name}, safe=False)
58 |
59 | @csrf_exempt
60 | def deleteMember(request):
61 | data = json.loads(request.body)
62 | member = RoomMember.objects.get(
63 | name=data['name'],
64 | uid=data['UID'],
65 | room_name=data['room_name']
66 | )
67 | member.delete()
68 | return JsonResponse('Member deleted', safe=False)
--------------------------------------------------------------------------------
/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/db.sqlite3
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | """Run administrative tasks."""
9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mychat.settings')
10 | try:
11 | from django.core.management import execute_from_command_line
12 | except ImportError as exc:
13 | raise ImportError(
14 | "Couldn't import Django. Are you sure it's installed and "
15 | "available on your PYTHONPATH environment variable? Did you "
16 | "forget to activate a virtual environment?"
17 | ) from exc
18 | execute_from_command_line(sys.argv)
19 |
20 |
21 | if __name__ == '__main__':
22 | main()
23 |
--------------------------------------------------------------------------------
/mychat/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/mychat/__init__.py
--------------------------------------------------------------------------------
/mychat/__pycache__/__init__.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/mychat/__pycache__/__init__.cpython-39.pyc
--------------------------------------------------------------------------------
/mychat/__pycache__/settings.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/mychat/__pycache__/settings.cpython-39.pyc
--------------------------------------------------------------------------------
/mychat/__pycache__/urls.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/mychat/__pycache__/urls.cpython-39.pyc
--------------------------------------------------------------------------------
/mychat/__pycache__/wsgi.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/mychat/__pycache__/wsgi.cpython-39.pyc
--------------------------------------------------------------------------------
/mychat/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for mychat project.
3 |
4 | It exposes the ASGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.asgi import get_asgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mychat.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/mychat/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for mychat project.
3 |
4 | Generated by 'django-admin startproject' using Django 4.0.1.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.0/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/4.0/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 |
15 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
16 | BASE_DIR = Path(__file__).resolve().parent.parent
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = 'django-insecure-cly%(5-34f8d1pc_fi*l_p@m3^x%#a$!iq(yu=s&&ez%-_pk$3'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = ['*']
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | 'django.contrib.admin',
35 | 'django.contrib.auth',
36 | 'django.contrib.contenttypes',
37 | 'django.contrib.sessions',
38 | 'django.contrib.messages',
39 | 'django.contrib.staticfiles',
40 |
41 | 'base',
42 | ]
43 |
44 | MIDDLEWARE = [
45 | 'django.middleware.security.SecurityMiddleware',
46 | 'django.contrib.sessions.middleware.SessionMiddleware',
47 | 'django.middleware.common.CommonMiddleware',
48 | 'django.middleware.csrf.CsrfViewMiddleware',
49 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
50 | 'django.contrib.messages.middleware.MessageMiddleware',
51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
52 | ]
53 |
54 | ROOT_URLCONF = 'mychat.urls'
55 |
56 | TEMPLATES = [
57 | {
58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
59 | 'DIRS': [],
60 | 'APP_DIRS': True,
61 | 'OPTIONS': {
62 | 'context_processors': [
63 | 'django.template.context_processors.debug',
64 | 'django.template.context_processors.request',
65 | 'django.contrib.auth.context_processors.auth',
66 | 'django.contrib.messages.context_processors.messages',
67 | ],
68 | },
69 | },
70 | ]
71 |
72 | WSGI_APPLICATION = 'mychat.wsgi.application'
73 |
74 |
75 | # Database
76 | # https://docs.djangoproject.com/en/4.0/ref/settings/#databases
77 |
78 | DATABASES = {
79 | 'default': {
80 | 'ENGINE': 'django.db.backends.sqlite3',
81 | 'NAME': BASE_DIR / 'db.sqlite3',
82 | }
83 | }
84 |
85 |
86 | # Password validation
87 | # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
88 |
89 | AUTH_PASSWORD_VALIDATORS = [
90 | {
91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
92 | },
93 | {
94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
95 | },
96 | {
97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
98 | },
99 | {
100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
101 | },
102 | ]
103 |
104 |
105 | # Internationalization
106 | # https://docs.djangoproject.com/en/4.0/topics/i18n/
107 |
108 | LANGUAGE_CODE = 'en-us'
109 |
110 | TIME_ZONE = 'UTC'
111 |
112 | USE_I18N = True
113 |
114 | USE_TZ = True
115 |
116 |
117 | # Static files (CSS, JavaScript, Images)
118 | # https://docs.djangoproject.com/en/4.0/howto/static-files/
119 |
120 | STATIC_URL = 'static/'
121 |
122 | STATICFILES_DIRS = [
123 | BASE_DIR / 'static'
124 | ]
125 |
126 | # Default primary key field type
127 | # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
128 |
129 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
130 |
--------------------------------------------------------------------------------
/mychat/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import path, include
3 |
4 | urlpatterns = [
5 | path('admin/', admin.site.urls),
6 | path('', include('base.urls'))
7 | ]
8 |
--------------------------------------------------------------------------------
/mychat/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for mychat project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mychat.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/ngrok.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/ngrok.exe
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # MyChat
2 |
3 | ## Description
4 | A Group video calling application using the Agora Web SDK with a Django backend.
5 |
6 | ## How to use this source code
7 |
8 | #### 1 - Clone repo
9 | ```
10 | git clone https://github.com/divanov11/mychat
11 | ```
12 |
13 | #### 2 - Install requirements
14 | ```
15 | cd mychat
16 | pip install -r requirements.txt
17 | ```
18 |
19 | #### 3 - Update Agora credentals
20 | In order to use this project you will need to replace the agora credentials in `views.py` and `streams.js`.
21 |
22 | Create an account at agora.io and create an `app`. Once you create your app, you will want to copy the `appid` & `appCertificate` to update `views.py` and `streams.js`. If you have questions about where to get your app I'd recommend referencing this link `https://youtu.be/HX6AM_1-jNM?t=88`
23 |
24 | ###### views.py
25 | ```
26 | def getToken(request):
27 | appId = "YOUR APP ID"
28 | appCertificate = "YOUR APPS CERTIFICATE"
29 | ......
30 | ```
31 |
32 | ###### streams.js
33 | ```
34 | ....
35 | const APP_ID = 'YOUR APP ID'
36 | ....
37 | ```
38 |
39 |
40 | #### 4 - Start server
41 | ```
42 | python manage.py runserver
43 | ```
44 |
45 |
46 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | agora-token-builder==1.0.0
2 | asgiref==3.4.1
3 | Django==4.0.1
4 | sqlparse==0.4.2
5 | tzdata==2021.5
6 |
--------------------------------------------------------------------------------
/static/images/chat-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/static/images/chat-icon.png
--------------------------------------------------------------------------------
/static/images/leave.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/images/microphone.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/images/video.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/js/main.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/static/js/main.js
--------------------------------------------------------------------------------
/static/js/streams.js:
--------------------------------------------------------------------------------
1 |
2 | const APP_ID = 'YOUR APP ID'
3 | const TOKEN = sessionStorage.getItem('token')
4 | const CHANNEL = sessionStorage.getItem('room')
5 | let UID = sessionStorage.getItem('UID')
6 |
7 | let NAME = sessionStorage.getItem('name')
8 |
9 | const client = AgoraRTC.createClient({mode:'rtc', codec:'vp8'})
10 |
11 | let localTracks = []
12 | let remoteUsers = {}
13 |
14 | let joinAndDisplayLocalStream = async () => {
15 | document.getElementById('room-name').innerText = CHANNEL
16 |
17 | client.on('user-published', handleUserJoined)
18 | client.on('user-left', handleUserLeft)
19 |
20 | try{
21 | UID = await client.join(APP_ID, CHANNEL, TOKEN, UID)
22 | }catch(error){
23 | console.error(error)
24 | window.open('/', '_self')
25 | }
26 |
27 | localTracks = await AgoraRTC.createMicrophoneAndCameraTracks()
28 |
29 | let member = await createMember()
30 |
31 | let player = `
32 |
33 |
${member.name}
34 |
`
35 |
36 | document.getElementById('video-streams').insertAdjacentHTML('beforeend', player)
37 | localTracks[1].play(`user-${UID}`)
38 | await client.publish([localTracks[0], localTracks[1]])
39 | }
40 |
41 | let handleUserJoined = async (user, mediaType) => {
42 | remoteUsers[user.uid] = user
43 | await client.subscribe(user, mediaType)
44 |
45 | if (mediaType === 'video'){
46 | let player = document.getElementById(`user-container-${user.uid}`)
47 | if (player != null){
48 | player.remove()
49 | }
50 |
51 | let member = await getMember(user)
52 |
53 | player = `
54 |
55 |
${member.name}
56 |
`
57 |
58 | document.getElementById('video-streams').insertAdjacentHTML('beforeend', player)
59 | user.videoTrack.play(`user-${user.uid}`)
60 | }
61 |
62 | if (mediaType === 'audio'){
63 | user.audioTrack.play()
64 | }
65 | }
66 |
67 | let handleUserLeft = async (user) => {
68 | delete remoteUsers[user.uid]
69 | document.getElementById(`user-container-${user.uid}`).remove()
70 | }
71 |
72 | let leaveAndRemoveLocalStream = async () => {
73 | for (let i=0; localTracks.length > i; i++){
74 | localTracks[i].stop()
75 | localTracks[i].close()
76 | }
77 |
78 | await client.leave()
79 | //This is somewhat of an issue because if user leaves without actaull pressing leave button, it will not trigger
80 | deleteMember()
81 | window.open('/', '_self')
82 | }
83 |
84 | let toggleCamera = async (e) => {
85 | console.log('TOGGLE CAMERA TRIGGERED')
86 | if(localTracks[1].muted){
87 | await localTracks[1].setMuted(false)
88 | e.target.style.backgroundColor = '#fff'
89 | }else{
90 | await localTracks[1].setMuted(true)
91 | e.target.style.backgroundColor = 'rgb(255, 80, 80, 1)'
92 | }
93 | }
94 |
95 | let toggleMic = async (e) => {
96 | console.log('TOGGLE MIC TRIGGERED')
97 | if(localTracks[0].muted){
98 | await localTracks[0].setMuted(false)
99 | e.target.style.backgroundColor = '#fff'
100 | }else{
101 | await localTracks[0].setMuted(true)
102 | e.target.style.backgroundColor = 'rgb(255, 80, 80, 1)'
103 | }
104 | }
105 |
106 | let createMember = async () => {
107 | let response = await fetch('/create_member/', {
108 | method:'POST',
109 | headers: {
110 | 'Content-Type':'application/json'
111 | },
112 | body:JSON.stringify({'name':NAME, 'room_name':CHANNEL, 'UID':UID})
113 | })
114 | let member = await response.json()
115 | return member
116 | }
117 |
118 |
119 | let getMember = async (user) => {
120 | let response = await fetch(`/get_member/?UID=${user.uid}&room_name=${CHANNEL}`)
121 | let member = await response.json()
122 | return member
123 | }
124 |
125 | let deleteMember = async () => {
126 | let response = await fetch('/delete_member/', {
127 | method:'POST',
128 | headers: {
129 | 'Content-Type':'application/json'
130 | },
131 | body:JSON.stringify({'name':NAME, 'room_name':CHANNEL, 'UID':UID})
132 | })
133 | let member = await response.json()
134 | }
135 |
136 | window.addEventListener("beforeunload",deleteMember);
137 |
138 | joinAndDisplayLocalStream()
139 |
140 | document.getElementById('leave-btn').addEventListener('click', leaveAndRemoveLocalStream)
141 | document.getElementById('camera-btn').addEventListener('click', toggleCamera)
142 | document.getElementById('mic-btn').addEventListener('click', toggleMic)
143 |
144 |
--------------------------------------------------------------------------------
/static/styles/main.css:
--------------------------------------------------------------------------------
1 | /* ------------------ Global Styling ------------------ */
2 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&display=swap');
3 |
4 |
5 | :root {
6 | --shaddow:0 4px 6px -1px rgba(0,0,0,0.1),0 2px 4px -1px rgba(0,0,0,0.06);
7 | }
8 |
9 | body{
10 | background-color: rgba(232,233,239,1);
11 | font-family: 'Roboto', sans-serif;
12 | }
13 |
14 |
15 | /* ------------------ Register Page ------------------ */
16 |
17 | #form-container{
18 | width: 400px;
19 | box-shadow:var(--shaddow);
20 | background-color: #fff;
21 | padding: 30px;
22 | border-radius: 5px;
23 | position: fixed;
24 | top:50%;
25 | left:50%;
26 | transform: translate(-50%, -50%);
27 | }
28 |
29 | #logo{
30 | display: block;
31 | width: 100px;
32 | margin: 0 auto;
33 | }
34 |
35 | #welcome-message{
36 | text-align: center;
37 | margin-bottom: 20px;
38 | }
39 |
40 | #welcome-message h1{
41 | font-size: 36px;
42 | }
43 |
44 |
45 | #welcome-message p{
46 | font-size: 16px;
47 | color: rgb(97, 98, 105);
48 | font-weight: 300;
49 | }
50 |
51 | .form-field{
52 | margin-bottom: 20px;
53 | }
54 |
55 | .form-field label{
56 | font-size: 16px;
57 | line-height: 1.7em;
58 | }
59 |
60 | .form-field input{
61 | width: 100%;
62 | border:2px solid rgba(198,202,219,1);
63 | border-radius: 5px;
64 | padding: 10px;
65 | font-size: 16px;
66 | box-sizing: border-box;
67 | }
68 |
69 | .form-field input[type='submit']{
70 | background-color: rgb(75, 93, 172);
71 | border:none;
72 | color: #fff;
73 | }
74 |
75 |
76 | @media screen and (max-width:450px) {
77 | #form-container{
78 | width: 95%;
79 |
80 | }
81 |
82 | #welcome-message h1{
83 | font-size: 24px;
84 | }
85 |
86 | }
87 |
88 |
89 | /* ----------------- Room Styling ------------------*/
90 | #room-name-wrapper{
91 | text-align: center;
92 | font-size: 18px;
93 | }
94 |
95 | #video-streams{
96 | display: flex;
97 | flex-wrap: wrap;
98 | height: 85vh;
99 | width: 95%;
100 | margin:0 auto;
101 | }
102 |
103 | .video-container{
104 | flex-basis: 500px;
105 | flex-grow: 1;
106 | max-height: 100%;
107 | min-height: 350px;
108 | border: 1px solid rgb(75, 93, 172);
109 | border-radius: 5px;
110 | margin: 2px;
111 | background-color: rgba(198,202,219,1);
112 | position: relative;
113 | }
114 |
115 | .video-player{
116 | height: 100%;
117 | width: 100%;
118 | }
119 |
120 | .video-player > * {
121 | border-radius: 5px;
122 | }
123 |
124 | .username-wrapper{
125 | position: absolute;
126 | top: 10px;
127 | left: 10px;
128 | z-index: 9999;
129 | background-color: rgba(0,0,0,0.3);
130 | width: fit-content;
131 | padding: 10px;
132 | border-radius: 5px;
133 | color: #fff;
134 | font-size: 14px;
135 | }
136 |
137 | @media screen and (max-width:1650px) {
138 | .video-container{
139 | flex-basis: 300px;
140 | min-height: 200px;
141 | }
142 | }
143 |
144 |
145 | /* ----------------- Room Styling | Controls ------------------*/
146 |
147 | #controls-wrapper{
148 | display: flex;
149 | width: 100%;
150 | justify-content: center;
151 | column-gap: 1em;
152 | position: fixed;
153 | bottom: 20px;
154 | }
155 |
156 | .control-icon{
157 | height: 20px;
158 | width: 20px;
159 | box-shadow: var(--shaddow);
160 | background-color: #fff;
161 | cursor: pointer;
162 | padding: 10px;
163 | border-radius: 5px;
164 |
165 | }
166 |
167 | #leave-btn{
168 | background-color: rgb(255, 80, 80, 1);
169 | }
170 |
--------------------------------------------------------------------------------
/template/chatroom.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MyChat
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Room Name: TESTROOM
16 |
17 |
18 |
19 |
20 |
21 |
22 |
Dennis Ivanov
23 |
24 |
25 |
29 |
30 |
34 |
35 |
39 |
40 |
44 |
45 |
46 |
47 |
48 |

49 |
50 |
51 |
52 |

53 |
54 |
55 |
56 |

57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/template/images/chat-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/mychat/60c6d8295d2684c62fbb31756fb6e2778b1590dc/template/images/chat-icon.png
--------------------------------------------------------------------------------
/template/images/leave.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/template/images/microphone.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/template/images/video.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/template/lobby.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MyChat
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/template/main.css:
--------------------------------------------------------------------------------
1 | /* ------------------ Global Styling ------------------ */
2 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&display=swap');
3 |
4 |
5 | :root {
6 | --shaddow:0 4px 6px -1px rgba(0,0,0,0.1),0 2px 4px -1px rgba(0,0,0,0.06);
7 | }
8 |
9 | body{
10 | background-color: rgba(232,233,239,1);
11 | font-family: 'Roboto', sans-serif;
12 | }
13 |
14 |
15 | /* ------------------ Register Page ------------------ */
16 |
17 | #form-container{
18 | width: 400px;
19 | box-shadow:var(--shaddow);
20 | background-color: #fff;
21 | padding: 30px;
22 | border-radius: 5px;
23 | position: fixed;
24 | top:50%;
25 | left:50%;
26 | transform: translate(-50%, -50%);
27 | }
28 |
29 | #logo{
30 | display: block;
31 | width: 100px;
32 | margin: 0 auto;
33 | }
34 |
35 | #welcome-message{
36 | text-align: center;
37 | margin-bottom: 20px;
38 | }
39 |
40 | #welcome-message h1{
41 | font-size: 36px;
42 | }
43 |
44 |
45 | #welcome-message p{
46 | font-size: 16px;
47 | color: rgb(97, 98, 105);
48 | font-weight: 300;
49 | }
50 |
51 | .form-field{
52 | margin-bottom: 20px;
53 | }
54 |
55 | .form-field label{
56 | font-size: 16px;
57 | line-height: 1.7em;
58 | }
59 |
60 | .form-field input{
61 | width: 100%;
62 | border:2px solid rgba(198,202,219,1);
63 | border-radius: 5px;
64 | padding: 10px;
65 | font-size: 16px;
66 | box-sizing: border-box;
67 | }
68 |
69 | .form-field input[type='submit']{
70 | background-color: rgb(75, 93, 172);
71 | border:none;
72 | color: #fff;
73 | }
74 |
75 |
76 | @media screen and (max-width:450px) {
77 | #form-container{
78 | width: 95%;
79 |
80 | }
81 |
82 | #welcome-message h1{
83 | font-size: 24px;
84 | }
85 |
86 | }
87 |
88 |
89 | /* ------------------ Streams Page ------------------ */
90 |
91 | #room-name-wrapper{
92 | text-align: center;
93 | font-size: 18px;
94 | }
95 |
96 | #video-streams{
97 | display: flex;
98 | flex-wrap: wrap;
99 | height: 85vh;
100 | width: 95%;
101 | margin:0 auto;
102 | }
103 |
104 |
105 |
106 |
107 | .video-container{
108 | flex-basis: 500px;
109 | flex-grow: 1;
110 |
111 | max-height: 100%;
112 | min-height: 350px;
113 | border: 1px solid rgb(75, 93, 172);
114 | border-radius: 5px;
115 | margin: 2px;
116 | background-color: rgba(198,202,219,1);
117 | position: relative;
118 | }
119 |
120 | .video-player{
121 | height: 100%;
122 | width: 100%;
123 | }
124 |
125 | .video-player > * {
126 | border-radius: 5px;
127 | }
128 |
129 | .username-wrapper{
130 | position: absolute;
131 | top: 10px;
132 | left: 10px;
133 | z-index: 9999;
134 | background-color: rgba(0,0,0,0.3);
135 | width: fit-content;
136 | padding: 10px;
137 | border-radius: 5px;
138 | color: #fff;
139 | font-size: 14px;
140 | }
141 |
142 | @media screen and (max-width:1650px) {
143 | .video-container{
144 | flex-basis: 300px;
145 | min-height: 200px;
146 | }
147 | }
148 |
149 |
150 | /* ------------------ Streams Page | Controls ------------------ */
151 |
152 |
153 | #controls-wrapper{
154 | display: flex;
155 | width: 100%;
156 | justify-content: center;
157 | column-gap: 1em;
158 | padding: 5px 20px;
159 | position: fixed;
160 | bottom:20px;
161 |
162 |
163 | }
164 |
165 | .control-icon{
166 | height: 20px;
167 | width: 20px;
168 | box-shadow: var(--shaddow);
169 | background-color: #fff;
170 | padding: 10px;
171 | border-radius: 5px;
172 | cursor: pointer;
173 | }
174 |
175 | #leave-btn{
176 | background-color: rgb(255, 80, 80, 1);
177 | }
178 |
--------------------------------------------------------------------------------