├── 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 |
8 | 9 | 10 | 11 |
12 |

Welcome to MyChat

13 |

A group video calling platform made just for you!

14 |
15 | 16 |
17 |
18 | 19 |
20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 |
28 | 29 |
30 | 31 |
32 |
33 |
34 |
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 |
7 |

Room Name:

8 |
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 |
26 |
27 |
Sam
28 |
29 | 30 |
31 |
32 |
Peter
33 |
34 | 35 |
36 |
37 |
Erik
38 |
39 | 40 |
41 |
42 |
Paul
43 |
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 |
15 | 16 | 17 | 18 |
19 |

Welcome to MyChat

20 |

A group video calling platform made just for you!

21 |
22 | 23 |
24 |
25 | 26 | 27 |
28 | 29 |
30 | 31 | 32 |
33 | 34 |
35 | 36 |
37 |
38 |
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 | --------------------------------------------------------------------------------