From 3a6a85d31f126a03c7266ff0cae7b490f3574bc8 Mon Sep 17 00:00:00 2001 From: Aria Date: Sun, 17 May 2026 18:15:10 +0300 Subject: [PATCH] feat: implement dynamic Agora RTC video room and authentication token view --- base/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 154 bytes base/__pycache__/admin.cpython-313.pyc | Bin 0 -> 198 bytes base/__pycache__/apps.cpython-313.pyc | Bin 0 -> 514 bytes base/__pycache__/models.cpython-313.pyc | Bin 0 -> 195 bytes base/__pycache__/urls.cpython-313.pyc | Bin 0 -> 452 bytes base/__pycache__/views.cpython-313.pyc | Bin 0 -> 1406 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 165 bytes base/templates/base/lobby.html | 37 ++++++- base/templates/base/room.html | 17 +-- base/urls.py | 9 +- base/views.py | 18 ++++ mychart/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 157 bytes mychart/__pycache__/settings.cpython-313.pyc | Bin 0 -> 2596 bytes mychart/__pycache__/urls.cpython-313.pyc | Bin 0 -> 1098 bytes mychart/__pycache__/wsgi.cpython-313.pyc | Bin 0 -> 645 bytes static/js/stream.js | 98 ++++++++++++++++++ 16 files changed, 165 insertions(+), 14 deletions(-) create mode 100644 base/__pycache__/__init__.cpython-313.pyc create mode 100644 base/__pycache__/admin.cpython-313.pyc create mode 100644 base/__pycache__/apps.cpython-313.pyc create mode 100644 base/__pycache__/models.cpython-313.pyc create mode 100644 base/__pycache__/urls.cpython-313.pyc create mode 100644 base/__pycache__/views.cpython-313.pyc create mode 100644 base/migrations/__pycache__/__init__.cpython-313.pyc create mode 100644 mychart/__pycache__/__init__.cpython-313.pyc create mode 100644 mychart/__pycache__/settings.cpython-313.pyc create mode 100644 mychart/__pycache__/urls.cpython-313.pyc create mode 100644 mychart/__pycache__/wsgi.cpython-313.pyc diff --git a/base/__pycache__/__init__.cpython-313.pyc b/base/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aab381969ba5d1af38d34a66c8a1b4398d3d29af GIT binary patch literal 154 zcmey&%ge<81XWi!vq1D?5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iQeoA6VB7{iJ z&($kQEy^ve&`-#SpORRT2qBX5bM;D6i*idV z^wTnPQj7I7@^e%5b1RcG5{pXolM;(l^}%N96;$5hu*uC&Da}c>E8+ke!wAI1AjSt~ OMn=Ya3<^bTKn?(4gfV&m literal 0 HcmV?d00001 diff --git a/base/__pycache__/apps.cpython-313.pyc b/base/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42ce66b6c747ff4873872d38a6b4f3f0c6d38768 GIT binary patch literal 514 zcmXv~Jxc>Y5Z%4I#F!Y=_<<1xwGghks{$4xM8!hEW?Ny| zzp=281uH8%A=3mjrY zg*Z?RD{fWQPnqE5jBiKVN4vo-OT)|ydR~$RJQm(*6m|=lT|_(%l+)AxS9I+zs2Zgy zOE{&fPO0`5vEfUU-W4pKDa(`&A`mi;Ql4g7ZUub6ida&nM`~?4)0ou;r9v_p^(m7Q zqFy1npcHC)sfjsAb^L%yRzg2ZJjo#`?)^a&bKwuOg!{=uf5<@kW=Egpx$yD_Fdv?R zIZre-JfV;F(VMgR(O7*vhn2a0?b@m_yWTW|`FYuHmMD#@nyK1xdF~0U>C(JgtZVqO UF~*;0|F>%4)=T>j=``*72Xd%|TL1t6 literal 0 HcmV?d00001 diff --git a/base/__pycache__/models.cpython-313.pyc b/base/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..97fb24b060a0189a43a583b3f2ffddc823cd83a1 GIT binary patch literal 195 zcmey&%ge<81XWi!vn+x1V-N=hn4pZ$0zk%8hG2$ZMsEf$#v(=qhIA%P=9eI8O~zYn zx%nxnImLdOOt&~wvJ&&s^Yv1aikN|tD;Yk6)Z8-HPf09Egb>O3xq2n3MY*LF`e~Ut zsm1yk`MIh3xs}NoiA5#)Nr}Zk4%j@sg34PQHb8c1PO4oI2hbcwAT9I-2@=#~ zyv0(GSd#IQ5yD|D%S~%LlAoQLr|+l9af>@8D={xUU$3+% zr?`k2sHF(x!6H^5af>x4KPjp577Iw@Ee@c*5RkT8+(5NJ8%k1(@`_h7d&DiYf|KHiR9jhAJvmrL9&qs)0|fhG`f^ts1FB zX%u4NHAG`(1a#btf=+bQbnILtey;Ec#l*%mt?*>U_lSZ0zDImq1IHzUwd>wjnA{jR zJy_FL#Ga{WFalUKSp)SLHRrK?fS8}c-a_e6?ct>Z%g4E%cW`i=?J~DV#XdMXO7YhN z>@#2N-Qb_(Y>QcMRNHg23{!V-lsj~K*w1x67w6pLcGsdTC-XgU@Hc+8f6T)&Gu0tU z6h40mRDe$P)DPE(>pLSoGt@E@@S*>Ue^H)CJ-{@FPh;XJ37m@VaZSYOBA8%ukCna0YxNYHB3b6c>|Xhyq-YP1IO!>^8p5 zPf?M0Gc{Y%_|c4JLuSm3?}dOYi5gFRd1v2f6>i@#3a!H3yRGs~zIMS?EE)Ckt#YwY zFYzcaf)Lz07iBpA2b2?hp#bf8mP^yfXte29pZGHtG7#au68g3=3B(}T%aM0 z4q;psN@I?TsesB;2ZHzV1>AF3ci*vT82sr36?NlbtKTo%d=Z8srp!5X+7`oHpCYPR zkmrh6xPp)R4z-x$kvb$V6BD;RV*7llJ)rRXnDCm+a{GL_Pn`!q_zo_KJ=m`Mf>}LG zFGD6@^k!o&Z-^IzyGV@mBZw}DWk;X_G){gPl*X4o4r=4&)hFAJwx8r4<({QS%R52! zG_mx3_PgxU)YIZ9u@Mx!&MgzwG|H_tW0bpN>|*@$PsveX?2{YFEa3GPwCN zx&Bu&^Jg;i8~&p?yw@Bh`$6fYzVhta$$PuM=TG#)P%B81r**z$-?vD|%lci9vi5-a zRM3;_Q+JoKzN{t{{}1}Uh`nQVJZiOM-&(Tvn95Gbw=Lh6yyJ5u51C&7GuN{RJ^VS{ sh8%(g{uZczRYg(G(A6`v^#U!tK>8WVz0wpV`zoR+nMow6+)!ke*CAR!RR910 literal 0 HcmV?d00001 diff --git a/base/migrations/__pycache__/__init__.cpython-313.pyc b/base/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4280a6516d3526c101fb4d790b92828e4337172c GIT binary patch literal 165 zcmey&%ge<81XWi!vq1D?5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~i0eoA6VB7{iJ z&($kQEy^ve&`-
@@ -9,6 +9,39 @@

Welcome to MyChart

A group calling application for you

+ +
+
+ +
+
+ +
+
-{% endblock content %} \ No newline at end of file + + + +{% endblock content %} diff --git a/base/templates/base/room.html b/base/templates/base/room.html index 2061dec..fbf8a58 100644 --- a/base/templates/base/room.html +++ b/base/templates/base/room.html @@ -1,18 +1,19 @@ {% extends 'base/main.html' %} - {% block content %}
-
-

Room Name:

+
+

Room Name:

-
-
-
My Name
-
-
+
+ +
+ + +
{% endblock content %} + diff --git a/base/urls.py b/base/urls.py index eee9bea..9dbd17b 100644 --- a/base/urls.py +++ b/base/urls.py @@ -1,7 +1,8 @@ -from django.urls import path -from . import views +from django.urls import path +from . import views urlpatterns = [ - path("", views.lobby), - path("room/", views.room), + path('', views.lobby), + path('room/', views.room), + path('get_token/', views.getToken), ] diff --git a/base/views.py b/base/views.py index bf673c6..0ff600a 100644 --- a/base/views.py +++ b/base/views.py @@ -1,7 +1,25 @@ from django.shortcuts import render +from django.http import JsonResponse +import random +import time +from agora_token_builder import RtcTokenBuilder def lobby(request): return render(request, 'base/lobby.html') def room(request): return render(request, 'base/room.html') + +def getToken(request): + appId = 'YOUR_AGORA_APP_ID' + appCertificate = 'YOUR_AGORA_APP_CERTIFICATE' + channelName = request.GET.get('channel') + uid = random.randint(1, 230) + expirationTimeInSeconds = 3600 * 24 + currentTimeStamp = int(time.time()) + privilegeExpiredTs = currentTimeStamp + expirationTimeInSeconds + role = 1 + + token = RtcTokenBuilder.buildTokenWithUid(appId, appCertificate, channelName, uid, role, privilegeExpiredTs) + + return JsonResponse({'token': token, 'uid': uid}, safe=False) diff --git a/mychart/__pycache__/__init__.cpython-313.pyc b/mychart/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..292ca43c4fc14665582c4f063bdd640ff7603839 GIT binary patch literal 157 zcmey&%ge<81XWi!vq1D?5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~i2eoA6VB7{iJ z&($kQEy^ve&`-WHa^HWN5QtgUZ Ufrf!BD+Vz>GBYwV7BK@^0KrryJpcdz literal 0 HcmV?d00001 diff --git a/mychart/__pycache__/settings.cpython-313.pyc b/mychart/__pycache__/settings.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7242c19c5993c9dc42415bf7a61721177bdc2422 GIT binary patch literal 2596 zcmb7GOK;mo5GM70TUL}?uB27a}w4+9r6s;7uK>&gl*D@VGlqD70 zJn2}VmtG3=FZAEUXn`0!8R)4uIYEIQJESCAkPXxoq~+|+eBaE@&MfsQnT%2JvHyr} z|B;}mKi$db6B-Ji{sG|^3Q>eYH1a*Czw;3vL%sHWLA~}b(F3b`bObwjE)B)H=i_AE zmCjltA>=1v6d)0fB2kV)!51`%!7~KUI0};l0u2fvR~LIh-=R^Q4RAC`q6A5yBuc#w zp{e;eMWHn7yF-&HG>tM~HH|KKW7Zol-l4ewNux_-8qJUl_`3kVEXt9KfWPeGf%6i& zLT1`m^Vg)?u*Fz$ThiMGV`9rv^tQ=t8idjIT3Zsam<}NB3W2wHujiV z*~83BvZm;aX@Tj`a+c|uz`_hk@3D8;1wgO3oCd*+q657qS&E@E@U^xSaHy#GHu%S} zEz9bdUvr#nv`lv3V-!Kl&^WAfK*(7}M`@YdC}3_tZ*5^_v#Y9%s+bmIY@TdL(q(Xj z6~ere)Gjvvk1v8Z#~l6To^PIZ0yW9nvX_ThP;?Wwx&+TFQvT+fw_D2M+;;mLyfgP; z{^62IZtThTA1Ttp?!E1&?YlbJk)2zQK z62YvuTgBRAk-KdW){b@dy-CVp5bPKp4c5j``O{9|yfs5-3*O^DOii-M7GQX(;GHqk z2NSVKkizjec$h=>l$KYHW5CHxj1o2uUK=EF40ZpEO{hxi&9>Bf11-URzd|GpS37R) zn`5}du-VuyQB9j0vRGK_sL*fh2Eef{kNSZvdac(Os!b>36~V40LZ`GKD1(^!fc@np z&1oxE#v9$XJ$DXEg5N^lSoT*#+v8GoGkONIn`wJ-!uU9gX8=aJe834W^M&U^rRW5T zr8;sVe61#WQn*@cl&TeE=SB(JG203&bvlq`?+Bf+j5npOY8_X$ym690^R22_c+rl* z;!w`}oRCmiEmZ_3P~q1E0xt^6el`)fS2rEWGTjb>UX5*Py;}Ke~){EjxzFY!j6$&v~<}0gBepL_))uI4|r8Pl( zRfW=tG$G*9!o!Laa;3&Ah`a&J1=qWi9>^=DvVaDKoGA(`e6!pT!B3OVWM?+Iqyp5whRq(!`eT7j^2W7XU~Z|hEwRO_zB4anfv#}%);3B z(=>hLi_w9B+ox&8Ruhxo#Ne4!WVr>In&?#22c zDw^yC57U`m@?$KL@%2(4vyqe!G+8SBc`wnAP+5UKNM{bRxx)nWkqX{O_5I;&>?oQD zhWh?g($|aj87j-Zr_#V0OdVv{gLLlK$NTBoqo6+xHbKwk3KgAtpMKT%`v4d5CbjX& zi}6W)Fu9n#=6g@&`nRa*TrYW;nC?Xn<5RuJAp{-jC#cknw}oKx$KCzF3}CWg?uKz` LX`jlFS;)@68dO&D literal 0 HcmV?d00001 diff --git a/mychart/__pycache__/urls.cpython-313.pyc b/mychart/__pycache__/urls.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fdff78a9d04f2fc31687c1c7ea5b42625dd2b932 GIT binary patch literal 1098 zcmb7DL2J}N6rN<0?rv&Z@#Jw@h222SV6h;fT0|_i6%Sp-gN5xhncYoEGGS(-Z4tp+ zy|oAFZ}9iji@A94dPdLOG&W3OVjVX&8yl9eHf&*Iv~4Y*p0#amU8@1E-dnR*aUCt2VL!(tzpw`(f3g7yxe+*1D+0|VJ@hOc!~#H;N&0}QK4`q_#O+C=QuA$ z4DaM3&ZtsMq;dzxkyKdlTrr6OMPkMAn~1%Y9$wQBTreD^fR>DLvzSrIuw=}4FszYM zne+(>c_6)TkETOjw+nbeSW51BOGNQ33MA3R1lmjDJlUrSU`gLu%hRBwT(&Ji?|S%A z7-E{@D9N}`KE?yV6A;v=Sym;-G|T#>s$MJ50wxr~RuDVtb~=ldt`SezB2H<-9&~iv zah=C8mGaIm2n#1r|Hgu=5fhq-S2C{YJO!-(VWaCkD*GCw>;A!LJq_YKjM5>PQ1;W( z^acgorPYQI{))trhp1bX$+3&;D^!vqZMHKWY5$y`NZm5`^ zU@t7l2wSHu87Wp+T2nDSan7I7CHzLkeG*bd#}61l55_#n_sJlFStI(mNp&lJzcrMX z9TcsVgok;|9*XM#q_3M?fya5nFpLXx&ai%BWXyjZF3ug_-Y&#Z-WyD?{em=^kS{{r)AL;wH) literal 0 HcmV?d00001 diff --git a/mychart/__pycache__/wsgi.cpython-313.pyc b/mychart/__pycache__/wsgi.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6748f49c2f4fe6e2a5616b8d3e84bb6725ac1235 GIT binary patch literal 645 zcmYjPO=}ZD7@paUO`X_K#P&QCF9F@D(t?NxYHLWVDRe7dgw1sK&1S>y%rY}co7{Tp zFYrhBZz>iFds4)MHzj|-*=}MxhxcP%p7(j4VP-2U1lcpaygNL{2>mRZ#i`AU*|9A? zBOfW`W8c}w3ft`NJA1BjDf)yDmlu$meAu|D7nNW0?`%6&XL0TZ+MbWpQtY&rrdQ;n z-|o;*ihdH)zEm`ugafV&&6ONNXgoqXhQetsHE3!EKq~-GQ+}KR<(hJu$!L_q;}ph_ z(lJ-b^+@mxBHHWmJWrF5n?#CU&+dC$vPcCq5tcB!9#ARl@|-;oa_Zb?)BNU`!%n-KrP$Ag~gGpm3xvRx2T1X!U#LTIE`z zq7Gu5C@BhJff4liC^bdn7ULH*D-{*bSi}vlf_32;P}%5|m9Dk*k^!45_@7G>FP{|4 zFRE95shW0?{W(Z)+VIE482@$ZxHh|wF!_a^d`HeXIU`qS?JHWlSobbAUtgj(Kbjlg Mnh(z!>YfGt1Ghc9zyJUM literal 0 HcmV?d00001 diff --git a/static/js/stream.js b/static/js/stream.js index e69de29..4b7ea00 100644 --- a/static/js/stream.js +++ b/static/js/stream.js @@ -0,0 +1,98 @@ +const APP_ID = 'YOUR_AGORA_APP_ID' +const CHANNEL = sessionStorage.getItem('room') +const TOKEN = sessionStorage.getItem('token') +let UID = Number(sessionStorage.getItem('uid')) + +const client = AgoraRTC.createClient({mode:'rtc', codec:'vp8'}) + +let localTracks = [] +let remoteUsers = {} + +let joinAndDisplayLocalStream = async () => { + document.getElementById('room-name-wrapper').innerText = CHANNEL + + client.on('user-published', handleUserJoined) + client.on('user-left', handleUserLeft) + + try{ + await client.join(APP_ID, CHANNEL, TOKEN, UID) + }catch(error){ + console.error(error) + window.open('/', '_self') + } + + localTracks = await AgoraRTC.createMicrophoneAndCameraTracks() + + let player = `
+
My Stream
+
+
` + document.getElementById('video-streams').insertAdjacentHTML('beforeend', player) + + localTracks[1].play(`user-${UID}`) + await client.publish([localTracks[0], localTracks[1]]) +} + +let handleUserJoined = async (user, mediaType) => { + remoteUsers[user.uid] = user + await client.subscribe(user, mediaType) + + if (mediaType === 'video'){ + let player = document.getElementById(`user-container-${user.uid}`) + if (player != null){ + player.remove() + } + + player = `
+
User ${user.uid}
+
+
` + document.getElementById('video-streams').insertAdjacentHTML('beforeend', player) + user.videoTrack.play(`user-${user.uid}`) + } + + if (mediaType === 'audio'){ + user.audioTrack.play() + } +} + +let handleUserLeft = async (user) => { + delete remoteUsers[user.uid] + document.getElementById(`user-container-${user.uid}`).remove() +} + +let leaveAndRemoveLocalStream = async () => { + for(let i = 0; localTracks.length > i; i++){ + localTracks[i].stop() + localTracks[i].close() + } + + await client.leave() + window.open('/', '_self') +} + +let toggleMic = async (e) => { + if (localTracks[0].muted){ + await localTracks[0].setMuted(false) + e.target.innerText = 'Mute Mic' + }else{ + await localTracks[0].setMuted(true) + e.target.innerText = 'Unmute Mic' + } +} + +let toggleCamera = async (e) => { + if (localTracks[1].muted){ + await localTracks[1].setMuted(false) + e.target.innerText = 'Turn Off Camera' + }else{ + await localTracks[1].setMuted(true) + e.target.innerText = 'Turn On Camera' + } +} + +joinAndDisplayLocalStream() + +document.getElementById('leave-btn').addEventListener('click', leaveAndRemoveLocalStream) +document.getElementById('mic-btn').addEventListener('click', toggleMic) +document.getElementById('camera-btn').addEventListener('click', toggleCamera)