From 41f2c3e26b140b5ce787f160ee92587c0a065aac Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Sat, 27 Nov 2021 17:41:10 +0100 Subject: [PATCH] Introduce Group Calls (#1520) --- .eslintrc | 3 +- package-lock.json | 14 +- public/voicechat_connecting.mp3 | Bin 0 -> 65496 bytes public/voicechat_join.mp3 | Bin 0 -> 18240 bytes public/voicechat_leave.mp3 | Bin 0 -> 9600 bytes public/voicechat_onallowtalk.mp3 | Bin 0 -> 15744 bytes public/voicechat_recordstart.mp3 | Bin 0 -> 17616 bytes src/@types/global.d.ts | 4 + src/api/gramjs/apiBuilders/calls.ts | 98 +++ src/api/gramjs/apiBuilders/messages.ts | 14 + src/api/gramjs/gramjsBuilders/index.ts | 24 +- src/api/gramjs/helpers.ts | 4 + src/api/gramjs/methods/bots.ts | 6 +- src/api/gramjs/methods/calls.ts | 236 ++++++ src/api/gramjs/methods/chats.ts | 30 +- src/api/gramjs/methods/index.ts | 6 + src/api/gramjs/provider.ts | 2 + src/api/gramjs/updater.ts | 102 ++- src/api/types/calls.ts | 26 + src/api/types/chats.ts | 1 + src/api/types/index.ts | 1 + src/api/types/messages.ts | 3 + src/api/types/updates.ts | 50 +- src/assets/animatedIcons/CallSchedule.tgs | Bin 0 -> 14263 bytes src/assets/animatedIcons/CameraFlip.tgs | Bin 0 -> 1127 bytes src/assets/animatedIcons/HandFilled.tgs | Bin 0 -> 9578 bytes src/assets/animatedIcons/HandOutline.tgs | Bin 0 -> 11147 bytes src/assets/animatedIcons/Speaker.tgs | Bin 0 -> 1321 bytes src/assets/animatedIcons/VoiceAllowTalk.tgs | Bin 0 -> 2687 bytes src/assets/animatedIcons/VoiceMini.tgs | Bin 0 -> 12305 bytes src/assets/animatedIcons/VoiceMuted.tgs | Bin 0 -> 1535 bytes src/assets/animatedIcons/VoiceOutlined.tgs | Bin 0 -> 3668 bytes src/assets/animatedIcons/VoipGroupRemoved.tgs | Bin 0 -> 2264 bytes src/assets/animatedIcons/VoipInvite.tgs | Bin 0 -> 2514 bytes src/assets/animatedIcons/VoipMuted.tgs | Bin 0 -> 3900 bytes src/assets/animatedIcons/VoipRecordSave.tgs | Bin 0 -> 3282 bytes src/assets/animatedIcons/VoipRecordStart.tgs | Bin 0 -> 3077 bytes src/assets/animatedIcons/VoipUnmuted.tgs | Bin 0 -> 1979 bytes src/assets/fonts/icomoon.woff | Bin 37204 -> 40624 bytes src/assets/fonts/icomoon.woff2 | Bin 17156 -> 18992 bytes src/bundles/calls.ts | 2 + .../calls/ActiveCallHeader.async.tsx | 16 + src/components/calls/ActiveCallHeader.scss | 22 + src/components/calls/ActiveCallHeader.tsx | 63 ++ .../calls/group/GroupCall.async.tsx | 14 + src/components/calls/group/GroupCall.scss | 348 ++++++++ src/components/calls/group/GroupCall.tsx | 412 ++++++++++ .../calls/group/GroupCallParticipant.scss | 78 ++ .../calls/group/GroupCallParticipant.tsx | 101 +++ .../calls/group/GroupCallParticipantList.tsx | 95 +++ .../calls/group/GroupCallParticipantMenu.scss | 98 +++ .../calls/group/GroupCallParticipantMenu.tsx | 239 ++++++ .../group/GroupCallParticipantStreams.tsx | 105 +++ .../group/GroupCallParticipantVideo.scss | 125 +++ .../calls/group/GroupCallParticipantVideo.tsx | 86 ++ .../calls/group/GroupCallTopPane.scss | 89 +++ .../calls/group/GroupCallTopPane.tsx | 139 ++++ .../calls/group/MicrophoneButton.scss | 58 ++ .../calls/group/MicrophoneButton.tsx | 187 +++++ .../calls/group/OutlinedMicrophoneIcon.tsx | 68 ++ src/components/common/AnimatedIcon.tsx | 42 + src/components/common/AnimatedSticker.tsx | 18 +- src/components/common/GroupCallLink.tsx | 41 + .../common/helpers/animatedAssets.ts | 24 + .../helpers/renderActionMessageText.tsx | 19 +- src/components/left/main/Chat.scss | 1 + src/components/left/main/Chat.tsx | 4 + src/components/left/main/ChatCallStatus.tsx | 9 +- src/components/main/Main.scss | 8 + src/components/main/Main.tsx | 11 + src/components/middle/HeaderActions.tsx | 15 +- src/components/middle/HeaderMenuContainer.tsx | 33 +- src/components/middle/MiddleHeader.scss | 5 +- src/components/middle/MiddleHeader.tsx | 9 + .../management/ManageGroupAdminRights.tsx | 10 + src/components/ui/FloatingActionButton.scss | 4 +- src/components/ui/Link.scss | 4 + src/components/ui/Modal.tsx | 6 +- src/components/ui/RangeSlider.scss | 40 +- src/config.ts | 6 + src/global/cache.ts | 4 + src/global/initial.ts | 4 + src/global/types.ts | 14 +- src/lib/gramjs/tl/apiTl.js | 11 + src/lib/gramjs/tl/static/api.reduced.tl | 11 + src/lib/rlottie/RLottie.ts | 58 +- src/lib/rlottie/rlottie.worker.ts | 18 + src/lib/secret-sauce/blacksilence.d.ts | 5 + src/lib/secret-sauce/buildSdp.d.ts | 21 + src/lib/secret-sauce/colibriClass.d.ts | 36 + src/lib/secret-sauce/index.d.ts | 3 + src/lib/secret-sauce/index.js | 2 + src/lib/secret-sauce/index.js.LICENSE.txt | 39 + src/lib/secret-sauce/parseSdp.d.ts | 3 + src/lib/secret-sauce/secretsauce.d.ts | 20 + src/lib/secret-sauce/types.d.ts | 100 +++ src/lib/secret-sauce/utils.d.ts | 11 + src/modules/actions/all.ts | 2 + src/modules/actions/api/calls.async.ts | 283 +++++++ src/modules/actions/api/chats.ts | 36 +- src/modules/actions/apiUpdaters/calls.ts | 60 ++ src/modules/actions/calls.ts | 1 + src/modules/actions/ui/calls.ts | 311 ++++++++ src/modules/reducers/calls.ts | 115 +++ src/modules/selectors/calls.ts | 38 + src/styles/Telegram T.json | 752 +++++++++++++----- src/styles/_mixins.scss | 40 + src/styles/icons.scss | 48 +- src/util/deeplink.ts | 22 +- src/util/environment.ts | 2 + src/util/moduleLoader.ts | 5 + src/util/vibrate.ts | 3 + webpack.config.js | 1 + 113 files changed, 5016 insertions(+), 341 deletions(-) create mode 100644 public/voicechat_connecting.mp3 create mode 100644 public/voicechat_join.mp3 create mode 100644 public/voicechat_leave.mp3 create mode 100644 public/voicechat_onallowtalk.mp3 create mode 100644 public/voicechat_recordstart.mp3 create mode 100644 src/api/gramjs/apiBuilders/calls.ts create mode 100644 src/api/gramjs/methods/calls.ts create mode 100644 src/api/types/calls.ts create mode 100644 src/assets/animatedIcons/CallSchedule.tgs create mode 100644 src/assets/animatedIcons/CameraFlip.tgs create mode 100644 src/assets/animatedIcons/HandFilled.tgs create mode 100644 src/assets/animatedIcons/HandOutline.tgs create mode 100644 src/assets/animatedIcons/Speaker.tgs create mode 100644 src/assets/animatedIcons/VoiceAllowTalk.tgs create mode 100644 src/assets/animatedIcons/VoiceMini.tgs create mode 100644 src/assets/animatedIcons/VoiceMuted.tgs create mode 100644 src/assets/animatedIcons/VoiceOutlined.tgs create mode 100644 src/assets/animatedIcons/VoipGroupRemoved.tgs create mode 100644 src/assets/animatedIcons/VoipInvite.tgs create mode 100644 src/assets/animatedIcons/VoipMuted.tgs create mode 100644 src/assets/animatedIcons/VoipRecordSave.tgs create mode 100644 src/assets/animatedIcons/VoipRecordStart.tgs create mode 100644 src/assets/animatedIcons/VoipUnmuted.tgs create mode 100644 src/bundles/calls.ts create mode 100644 src/components/calls/ActiveCallHeader.async.tsx create mode 100644 src/components/calls/ActiveCallHeader.scss create mode 100644 src/components/calls/ActiveCallHeader.tsx create mode 100644 src/components/calls/group/GroupCall.async.tsx create mode 100644 src/components/calls/group/GroupCall.scss create mode 100644 src/components/calls/group/GroupCall.tsx create mode 100644 src/components/calls/group/GroupCallParticipant.scss create mode 100644 src/components/calls/group/GroupCallParticipant.tsx create mode 100644 src/components/calls/group/GroupCallParticipantList.tsx create mode 100644 src/components/calls/group/GroupCallParticipantMenu.scss create mode 100644 src/components/calls/group/GroupCallParticipantMenu.tsx create mode 100644 src/components/calls/group/GroupCallParticipantStreams.tsx create mode 100644 src/components/calls/group/GroupCallParticipantVideo.scss create mode 100644 src/components/calls/group/GroupCallParticipantVideo.tsx create mode 100644 src/components/calls/group/GroupCallTopPane.scss create mode 100644 src/components/calls/group/GroupCallTopPane.tsx create mode 100644 src/components/calls/group/MicrophoneButton.scss create mode 100644 src/components/calls/group/MicrophoneButton.tsx create mode 100644 src/components/calls/group/OutlinedMicrophoneIcon.tsx create mode 100644 src/components/common/AnimatedIcon.tsx create mode 100644 src/components/common/GroupCallLink.tsx create mode 100644 src/lib/secret-sauce/blacksilence.d.ts create mode 100644 src/lib/secret-sauce/buildSdp.d.ts create mode 100644 src/lib/secret-sauce/colibriClass.d.ts create mode 100644 src/lib/secret-sauce/index.d.ts create mode 100644 src/lib/secret-sauce/index.js create mode 100644 src/lib/secret-sauce/index.js.LICENSE.txt create mode 100644 src/lib/secret-sauce/parseSdp.d.ts create mode 100644 src/lib/secret-sauce/secretsauce.d.ts create mode 100644 src/lib/secret-sauce/types.d.ts create mode 100644 src/lib/secret-sauce/utils.d.ts create mode 100644 src/modules/actions/api/calls.async.ts create mode 100644 src/modules/actions/apiUpdaters/calls.ts create mode 100644 src/modules/actions/calls.ts create mode 100644 src/modules/actions/ui/calls.ts create mode 100644 src/modules/reducers/calls.ts create mode 100644 src/modules/selectors/calls.ts create mode 100644 src/util/vibrate.ts diff --git a/.eslintrc b/.eslintrc index ab6dd23bb..28eb23c2f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -74,6 +74,7 @@ }, "ignorePatterns": [ "webpack.config.js", - "jest.config.js" + "jest.config.js", + "src/lib/secret-sauce" ] } diff --git a/package-lock.json b/package-lock.json index 2b17d202d..ea57d1297 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8633,11 +8633,11 @@ } }, "ext": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", - "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.5.0.tgz", + "integrity": "sha512-+ONcYoWj/SoQwUofMr94aGu05Ou4FepKi7N7b+O8T4jVfyIsZQV1/xeS8jpaBzF0csAk0KLXoHCxU7cKYZjo1Q==", "requires": { - "type": "^2.0.0" + "type": "^2.5.0" }, "dependencies": { "type": { @@ -15127,9 +15127,9 @@ } }, "node-gyp-build": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", + "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==" }, "node-int64": { "version": "0.4.0", diff --git a/public/voicechat_connecting.mp3 b/public/voicechat_connecting.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..b5cd7af45ef8f6432084be9b3810df4c72eda5af GIT binary patch literal 65496 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 literal 0 HcmV?d00001 diff --git a/public/voicechat_join.mp3 b/public/voicechat_join.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..2a1bbb464a559cf33d83d41cc702440c1359a6e9 GIT binary patch literal 18240 zcmeF2Rc{19p$p znwF}ise4DKZtGUf=bsZu!2bm~CoAW_kD&a$&;bA$Q2_Ek2>*lrAH4q|^AD~6F#m_^ ze+2y_;XiW!qxv76|1tiL)&DsD$HRYo{^Rd=@hOTaNpUcX7c=nr&tGUu57UXsYYCU83*GNR5pjkAS2##!7{f6_z|~rE zzlFbvL3!swiUTEZY;b{F@r($;ql@dxLL!lYnFhEd`3%RgbQvnp^l$xs`jMwMG#pGS zL(|YiCSkuXpgpS7Kg|_A&e!aavEr12F4Ed4UL`%iS7JPk*q6PBy^z_8$n->0~ z7#<<^f09r87a%IF=|9nXdh&tfZV z-D#52$f`$0fScK8NIXYOnV-YcCJS{@gh;sCd+zglD)#!YS6*af(BEl*MgHY~zxgF7C z8~FlYy}y4r10c=QMSJSVi-!_HwK6WSm*Q~F#VY{hB`Bd#5hs(6NdXq*vNfR^IF6=7 zQsd}a#{c==ThDhDU2~CNTAycCI=ooNf$-HvIa)B;R(vabbSn&A)l8~_Y zHc6>3}q8 zS$I;sREjZCz=UM74s<#5)`TYxp`3QMRkt-R26g`nWtx@?_k|z-m=HPw9hi-*A{Dgc zAq^7M`(PI?Iu@%Yz3(F`DyeO;sHmzw#w8>f`?6k9OPS(jL-w4%c65gh_AEyqcI!@= z=BDx#X$Dp)D3(_;MFzAX-W`}%zGtEidd_giZ}-G?XH#4wlK&fG4JU^LS8X<-iG~SM z$LIW~i}R{hI-E zZBcwJ{jY!i@+Vk>{3Ias6n}i5m-VrStuPN*>uk-uo=_mM0Aic@^ z=ljnlHD|-+lqoU7uW?o7| zKllOeIC(OpY)zj5008@BW0A4^JAdiK88ZDwTa8N-r7R z90G}0i3`@vcEm0$oVr%(*~Oz+KDws`Mh=o%Qa2>7TME1V#w^I73Vf}uzmgn{P!TCY zmx5d&q}f1if$jGK#QBAPW5!u>N#mN$VDS>aSBpR1rcxAHtXnyH?8-d^#fk`V>Yd!q zwxtt1NV5sWp80lc)X38x^k7mqJUcr}@r&qlC?JsJbN_x@=Dz0o3iCJjYZ@sT4Zbo% zZMISp5$@slLorM-mlI~+@r|!OLF7?ItWiE`B9sR%E-n_q)O8;ZUSZ7fUnSsdvX+pt z!8FJ8CUoT!OZ38sA%z7I=~+iRu?frABla-aahIdCGkYBsD$?}UBJ`KC1gT7MAh*52 z5S1p{7~JepcK4*SvQa7s75MP8Q(=f9ix;YSMgSE;YLy#ht&W6}7YUkkj@mrf#|g9Y zr$Edfyy(jn_~}i>l7qr==}}l_D@wPvYE|X*J?JMC($^&@mLFTw1$@clPj>vX3@=@O zICeQWIQYJl*~{zKa_);5oYlwIWcASUZij?M?_&)FJH<;3QFePIu$tq9*jA!VXvKCxzm> zjF%qelwKTB&OB9IcNcL5^4?HjLef*Zh{#Cb=GIMOA{|_4UW`WSjuv-W9$U8`hz+A1 zee%W}M%{Zyp}IOZe0erOzeN_Qzv(+Rh>s8KcE+A@j{azgrnA;9eX`s$A0DBst-di! z$IrL);Grn1`@!sed2ZuI&?a$0mdrj?Ne5>@UWCBZ+{iFH66$sXo#qhPh6@XreC$cv zu^5zuPlHA2`V>IZm4rhOs%=9rN>ZiuTCmVW{iWC5tt;knjLKQpR^8lS7*UjYG}LBy z8d3!H#3F}QIg4tijKXe!^3PvtNEmH@u@+vxgX0DpX#y5zBEB2I4BNXzbYEdvgh(X? z6;hs0L77>28Sgo?LmLMtXPa4Ffo_junT~N%Yfte>t z%uH&{zBf+YJ;gbJZ>2!3ZWGP(8tx<0MZo&kp8Y@byBrseN8EQx#ysgPtG{)bTHF5u|KOmlu(!wA9?I}Ex5_2-epkNXyk|7KrT2%^22~SY;-M|**3C4|mSb3w zj&T=ltXhZi&*GNh{7sDs&f?rSk+Y4cJj~C$F=|H1O&l@&tbx^}3D=4CuIyJ^hF_~v zTG6EAJZHWXDSYi!WL>$oEsByyDCh}8Uez_0speYi;U8b3hAfvTnC z-B!sZSOkbMUn6!O9azZn*v92zXKkS5<>}~Cf56W>JZjXaUSOH9=~>NR=&uu>6MbE| zQEu+s*WR2G%8xyb-VE#sZ1MVI9OEeULcsWgv#IrwvFD4gewNEo$yc|cgl2{*?clYl&11(`r^<{h1~X~AI7TOn0juUFRDsYq|~uw z+ho@D?Lu8nVu4u=w8zYKo96FMHF6?JrQ|DdB&5OSu5S7~DBsq`bnG72?YGc%8Hgy# z<0(qvwV9iy+=JhrB76C!cnIF6l@wB%zl{= zv3q54w+d3}Tn9AHA`#|{zYc`&PU{a+I^ahcH90AY;0Onw+lHys#{cs-Nc!4elmYRp z%N~mjlv&~q;pFveMFq}syikGUuVWX_Z){ACpf;;U3eQmb=9@Z2?W?M*>Fk6l@`Sp^ zX&K!91Xm#4D4&j{zw%d&j4upX1~+Qg0Wn*DR&osN{r7Sl`?Wh%fq^pVBX!jYEBt(F zO}xeFrls>5Cq||l-z75*JlwObUTiG#)DlWoX1T$mnZe5*G^gX*e*BAtf2x|6sO@iSn0 zzQJCpxQ@xE&{PZCKZO;fHX}V$DcXoM&&3$x5x9{qH(^KhF;SF6EA495KdO zuCX1XF~yPQAI8c)LRtOJ;$~uOE0Gr;`Qp^>ZmQTEY2|7uC$Hfz<<4Z|S^iS+4}~cN z!F45Xoj66J9)0Mn=8%Lgu}L&xT`U}U#QePgfN=i}>@K|OAI_=)$VbGI z6^dpSpk^#8N1%!ZYF~i@D|&`n*cUL%30I6~i8`B_NmSxg1-xzyI4iy*mtBI8lW)@3 z$2I>pB-iC2LJpF-kCUTnJVn&X>q^|geB=M|{b%cW&F>@2&K`!MMK7vG|31Axj;E^T zH08e5%R*W|ENNe|d@na9mA#4vwXSv{b6KV)mfj(5=DnewC8jRnP!u$R8*s(1RD~Z#h@FE}!Avvsa8#Alj&6pD z*Ahm7A3?FI`qd`wp8+XvXB5cE(~Oaju(5O-}qDY zojo9AaGJT)TkHZ)XLPXcs)v>x<>Ky4kf$rNTZDAP=Ze^vQP(PMSL#`E#7jT1%I)8n zCPf^CL##1`mRS_e!lTmz*T>80N;pvX0w^IEIpP zjdEtImi{;&yrm@t;bdQ~E4*ndj*F#Y!4H2V5zcPNL%@~9hjMskfLEFoYf z`a>Zo8GNPU(b!E>dJb7W9V0T*)K=itGEvxou>h`#B!8uU*&0!Ln>$p$>73*1v{aWs z?`d_?pqu4F8Jlc0Cv(Dau-@Swcfv`>k=k#%=QLHJ+tA$Jg7mR{p~`*mlm?z&~nq|etp z;YkA9fparBp zA>f)~GovF(mb5JY3QH?DPmz|<7^rnAqGn?}nNhcJHXB1PjA$~Q#qGm1Pj*o4#X(MO zU}0u{xb&o1=Ms*XB^InbxUSIOY0$Dr!I%5Julfdk(Ba#`KrN>tIK9Wz#W<+BO;DB{ zJq&jpd|kx4{c0`bEfL=D4Iiqe22arBDV5GUwA!=^;S?qQO3}HzkdF>++5~|~u0ciK z<-h-+36B_D;mR+{7^{oqN*k$0L1O0z0HDf(bvXJfi0KRuOe4Ad^5(i_S6$02xxl-r zb;~VsNo()){7{ccZMzdfmj04sx4FVJiCgEeXcqL93qJ!!O7h;u5S4(zc)ZdQTDAf~ zuA9HpKv0BNiN;brQR$x0y<`zt3*Nz0*e^N z?B0!W2Wsa0xmk48h2Y!IpD+^W6}OP-9bAn1sOhr5Wb&kEAy}fe4c&L39KDw1ilod= zZDb>N9Z}HmsHSy2l0+$IyMMSxCPbty4XM+mbqPa*Ug-k(sojd9XqmIP}}1<{&< z^f)drjN2?ZiYeow(r?l%p2`HAeu#JlNbFC{KXkl*ei|Iq7_@sW)tIn?vYF=Wuub(r ze<-K3vWgguoz&4c_?W4PNR@-!3d@^phs=;5{kxY%gGo^pVgC6`4}rty-v^i(Z@f|Q zF2s6yCRDn91ZyFJh!xs9;Wpohg&tz%!I-SMPm`c5rOt>Sp_HaF|HW?WQp+j}{Na@; zVM$B^JPxgzX1|DOc`{cybHA$<8?%`gI<5Se+F(54UBYt@nLD6@lwbK!lYCVVBUUbd zVa+>#kXfVuz*lTqkb%0d&mXgkhXY5laPg=6231QAOg}w}ivmx9YGo4DVsdbWwV5-S zm>i*0qNuCY=bEcYYk?6bFD!x7mD?2{3b$cA>cFwKAm>1dV|b2nhlwav48t$RQOdLn!^z=>#?#G=?G;M~ zgfw8Z*y4~D*pS_p6Rp=J`sgdM223qXZiK*V>LX#!W>SSaq@p7{oI%e*G^Pzqa8~)S z${FHy2qs+HSNVG_sTLrTXlq1P$PghI7YC$ikCmwv`~0B~Hu{K0VGyLfymT!m2xW&e zzuG*7*%OCj{tvU|iOJ6k_07Zveg8@a=28Yf9%KqWj&+Pl^01U(!p3G6Q%_pbQsx4X z{Ji{gX_%J2pCF?SP&U80q6m#N%|}aW`~pEeCf{>S!XZ209Qm6Ti2ql0Q+anv zFXO@#vjW{I@4}3{{<6hOSqV4Gc=@fwa?1E6QJKquoMHH(#X^*8<031l zdYmkJ>?1!wRMV3K_u;E$N;*D0otpJVw{|KNf~FKctYUV^P*BPflm>ZFX(@jsswdch z_EY}y@g}ohISjD_x6br3WK*+~y>C<}6>cH}~N)uRd?CU69JwaLpsPar|2aBcu1h z=jxl*Np`weP89C6u|9tjm;QqgSe17g<1)dEbBoE=Wwia9FHo>%$vEcNO9wrK!h;iy7CZfCD08%qjh409>DG1Mja*RM*kvDM z!!uND;gSKBopzTJG;gvhyi*dKcJGZRb$ShLFHLQ>TT0Gzc$R2-~B6oi`Qom8j~3y*-JihhVPgKn?^=$mlLZ+e!C+uYiX+idTgP?NhXCN3aU_g8-9FxfbJSI2^n@}{Q z&>Z>`6R&Zywg6d>$%Pb^#08U(khSdqiW8^jjb{Lvl;gNJ3d8EOkTqgRabO6X^sq+8 z%uwXp3j{-gFNSD+Sb;42Ky##QVAw731Fs@7n0w&cqO7Vt^+j()qHI2Sc*>ziE(;w6 z%HLRDohGZ=Yji$88IvrQspFK8$(@q7d~-@QHO0vQQgz11AQtG_YHIzlnF%{%&FeB~ zMf^dr?UMh;j&a?U*ootQ2$Y3 zNy~0-XhB8pf(bck+6exVX*mDnX}v20ZFNdn1){1hw^*w3jCHO-Q`Y-Z=T^TO9_;UT zn*F1;(wucTT=t6EZ8YZ}!LOMxCJVM_}3Y82fb-u1#1C*GpLqURG{U znKDDl{oIrltE)N7EW=N2Ca>DBc!8GeSt|5jKVP1Y6%_0}ZOz#?0&(Ln7prZ(KU`^N z>)+M`KMD}grj1tn?M2>qZom0_URurX6u!CMeTsZue||iS|9Slz_oy`eYVQ zNe!AfkfBB8d-zE>n+=z|7mOH$I5rq{99j~!7MMbX?lhG2CIef7rB4!$XO>2lBNG#S zhqBf<&Xhm)NrhT^5h(E7PC|FgY*VtEcdb!3XlO*z8o5KqvWCCfK>iP(>+d6cknSq| zW0&WL0Xpwhuq1W_3xvuzp+iMMG?yKpW0dAK=dDSb=<7JJK1^TuRm!N+cd5gvoL9c8 zoaS+J!|y~$z6WSK=u~^ksc=9b67Jjk@~1D|FzQ&6JQ`W;%IZS9N0~ZX5`xi{r{?x! zPqU@;azzjN!@WVS|NGu7+9Dn*TsZn${VW@*N({K^fMekaWhbAiY3Lws8Qmkk?Q%7L zS=ni*PMkZ#B>!6ny~k?Ww5@vesG)0U9q@rmP9f`Ggd!_V8E0h72^_VUsAy;qH9fVD zwN+=U43_n&86@5$7h3V^t=l>A9FxyiO_HQD{m=yooR>>9=t}x3SdG7xdz9P~{DKz~ zM)f6yNIXb&;67}%FD}i)ZeVv5*``&qHCu;9W>>Tfc@E@)9$%jDe228O7aj8vW)BinV%o<(3w%@r~CdX3!H41u{syyB;>?~qL7O%{@flk8oafc4#!Z& ziQ=&$_K_^G>RJWN`epJ&eEzr|HDw}of;cFDFmqPSRo~KOAIitzZK`ieVgI`lyS1Ot zdq=O@{#U1l8DR7Mr{T?>@ts@kDuw)O%Xs%90sGFShI_}uYe!oF>r^Hk)5{0n=jD-7 z7iGlU(=bK+&w!tvYLrN90$=pDy0=-(ZlJdfT?tye2!q%Hzz3k-KK+8Jk%ZX?Qz^c> zvs>b)kC4%Fl%!PFvg@#{LL@a4q_rE=q$2THdSjP`IM2+gO_8Htt{*-T=CPkYp4Q}H z?m(&FdY8>oX&KP}0vCM4t}U8yq)X&mRw;Rh;9YgiEG-YfdZ)-eX?>0>`=w$^6XW%4 z)vewnFXT}AIH+u>f-^m`K4OPlrXRG-Om9Uy!xzbWbsOnqz1 z*W*KMf@WlDNZ1Ca`05n{YM&HOrZ&#>E^-uSu4mkpglQ# zAFcgCetnB)G8*5X`cwW&0O#s9y|x&98vAQ#R~!GrwL-e3#!@FcO$~t8#$=p>5DrMl$PVch4X8HpXzHw;6d;a+TXgfKCVNH&_VE<+ z$0-jjRp)q4%8Re~5egR3*gt=%0Whe3LzB-|g|~OQa^@%rL|_*XG>0DuAfZCtH<1A) zLig{$?v<5a-D?%zfObnQ{booEnSx7iNdoo|@$Z|^^;2Ryt3;}kvX+zx%wa;VunZ6l zVsp)=Rc!V>S=Cqb9%BMXwxM+VuGz-eu8ARhSA7Bn9%R*md6TnqTRI{Qj|m92PXg;L z*@s(C22_n-?A$`U!_MMdI$wuqkK%HxQel>xT|}z5#Zfm%amE`=+_gti@4%|i0N^5@ zJv+QH;1Tjr5vp2^IkQWQ^IMvngEW~4`v$$uGv0ruGrok{YJ5V~M0} z*LTX4S+8(pDfh2+%^4r<-OkD?~a2rZM|#{abc~|Ik-Ztz#ob=nh$StrZG_UF|rfMQ@z(lzm8IL zvadp%DMyIBts=#a8Ohfv&JS}8k8W3AD++)Yg%7ZPftiegNx@IzT9Tu~tWuy{82?#k zweagp#(F`!^!@$UE(=702Cnw1;$WR@iPqr;A4 z&mS-?6EtEl2&*w@_axw|LY(2?PRdy-Z5_}x!Ug#?FNX2pWOG7RqD9o@wQS?c@Pp6+ z5LcJYD5gkF_iRb$13%|0IN8XzQQE+N0ceW){pIjD|nAe~FkRba7 zbVO?K7v@9a$IUpZo<-|6J9|#eZr?J_gi=nvJ{}*yOj_zH209JX<&oyZ&Bcp==4$Jc z-Ci}1h`eE4jeCN2{f@Y+KM3J(39NL1&FVId7FM$)N1Jn>uVeO3r#OD|zf+!fDR>9W zvtP3*x!lPkbd%Hd4M7M9&iI*gnyIUcw+G6(*f<&RmL3+l>Ydv(R4X~`tXbnW7DZ7h z@GMi3yx%d4-*8GHCj^wZzBtkkKuiSl7$2Zxijk5kO1elE2bY?waB_eChEG<&uwI*5 zGTt14Jp;OP;6WuCI1$*D+qC4Dtt?6Ls4a+k)mb5r)cq|IiEuR6ad-j)Afn|w5Qm$ zM8PG4GaCZ>xjG%_vx>)r6sn42@tTaO3OH)dI~U)K-Zq;Y`v}5@MG7Ej8>IVUre&`k zVr)EGxqTF|?L$1pXTD5l#?O`PL9st6eG8?u61pTN2{?>tO@#TIt*c{?$6c0y_O*o? zN?H9<>!)xmE1j-tYHb<^7f?hT_ai$U`Bb!(y)0YRINOIavzDE8^smE(fJ}-v5US{E z1jT9dhb#DDl+zek?wnf8wYaQ;92y+yJUOvKDuluONE1@hcQylHyL1_-(&6${%Y108 zCN1EL?mj{Ov-VBwa0@M;6pP}#d{wA2dL3ctHY!N)IJE$(?L&j+T!mJUra2ar7OT`j zt#2(NDCg7Z^#vCn=UO8*TPHb~w%Rf4<#Qo~N=E-`mjGf(}4@{i7GHQZqlh3Rf($KQ$Ch(B4nHTKp$g%qIiGDlm#)E&xOr3E6~PMf55);CU2 zNiC*PFet-g_SO%HmTY@Fqdm(yi8F+#<|a&W6`Di+El8vjf~=BhV&K@VAt&`}tl%qz zP=!-=0nR?P5*B=j+8uQ&#h&z$jPlhz&ufew#?#*%XHKX&!jD#HePf&GZYmEXedt!@5t4wn3n`=x6~#H- z)b@Y@WN9|#v!|8-Yp`s@V^?ddAVy3CxU|4z+QRF@qyJP-4+co}6e4g>YJK^RhYnW;wAHm*n^_jz*{@MS zVDJ^sAl9S`5Q36Y6_s0XXEC28z+xVl>Gm)*;h@K&--!M_aILRRqKI}lHB{m_9M%yW z9_i>ta&>7j_|IQ@fb^Ze2ph~-(xV2M39{#BgU!pQ;=lc8U|#{Iw)ugZgbrGI0Yma7 z2$+;XIk^N1AGO5X^UUKm^SKZ`MRS`vbzDsE$6UDHF39b*N;Krrs)qFAlQvc46N%Ww z`?I2U6x+467sP{+fWAncMAZ~6UdKyE1K)Vbnp#{bv0Qw%)+soMiNfwsL{Lq3U^24v zNe$iPx$q^_l@l5@IJ^iq66WHgKmyeCBnki{n=+gP*vL}jJK)HN!U)8X?8|?NV>s(8 zbEf6Hn`=0(IuOr+i%c}n{&O8{pXsUYaFNg%HtbvBBb}K@!`N0~(z|tP)Hi5Xiv=2) zC-9wm94xY>AunRxhz&%>%E3WPySYhl0VN8BPRC1k{$}#}J!SeGPBSb}GBS8%*pJx>PJ-*ZqPewMFEkKZR=_ zRok65R_ElaC6iX2v}j^0FHKsA-Km$K=*tGS#fGs`&48gIW3vcmGx4{gQd00a!v%#G zV-7{TYFm42i_wVPHWSLO<;TtP^l@P)2nh%Lxn}tO1FPw~-URBNZ}F0K4qBuO16fxyJ@7$0ID#fpL`+ds0F{X1L5TGeY>+>ccL zG41GJ%o{xh`QrAv`LG^SX3E* z=p-;R4{KOxN~`1$Gw_+HnbfGRJP?~X9&x(2wwD3t{KppgQ8?z;ay7^=R~kGJ=inMz zg6NatbX}YaF(Ly*+{tQ{K7+c|+F0;-%-!|qdM(rNwYjC9wu6(rZ zhq5$WyD;TPBIW33{ZvoubcK-8w_YX8>d+B8P+oPCl9z25@jeaVSEPPrdd(cmS=)xS zrQ5_=CZnTP4GilgLL^Fu0|2G~>L3JmF<_sl54v&*5xYN(U=#4t=#Y)j8qv zcy$qS%V^q_w!H~7b$R+01Vzz7*zfI!k8rWklNcd4MVR#~T4nPySyF{^L=kjCvw;CV z@^(5`zRg28=($pndsR)Ngz|JyRGjmKGy;#C33H79{1pV#D)kqUr3)NrXVJNUV)>5U zcll06Ljt}cw4a7;e((UDc`Q>kSD271Pmx>{=Z}D-kh~Xa6kFIqFDGW^$4IIiNlFW< z8WcvS4B%cj_1@XK9_9 zQQ-?DB)9#|Y3o}NIyyrc7hsu0G2FObMQABIiC28mDUL@6CKx>lEIPDe!w->8+vN6? z$}QD6pVrIN;`%Dw|MggUTc$L6yfflxEJT$d#o9f@4ZI5!bz`TNVNF@>;g9e5fO3-3 zbtG3gM4Td7>VY%Vy%&Ikkx7pKcWH=zwGkbY_9RLS| z*w@JsAh&Pk;#|pQx$XJWkn^ zDoL8LObb<~M-OQcpu4e*rVV=bxXJ?b1vGLyfNA(if7)^bUq6$pD#r*&0Yc8tUa%WI zGAvUh*&IH3+Z`62bM0O0^Lv&+pA6dZYqv6dDYy~Ax);9RB-eZo^qB_{bS}4orR%O| zUi`-N{Z~2D8qHfP^9zH1=q<-T(X)=I=Yv5Qe&^?GG`^-iH=d!xP7vPBC#`L-X9Gz` zTMn~J{86=i3P!89=N#+rnYE;q1Wm5`)j5qLBDvH&z{+OrIqUL3~OrBU}Zq0cu1y^qt=VYYSn z%_r_2K05k#rA*G#j4y>|>Jv)@qDd8S5mDm7N1BwC&=aeqNeRPx zaz$TxOoqvr^RyX|=ba z3IF^p0MqLB7ZFtq9O!bkKKtiy`1L!Wf&~1iuRxUA{J`9!y}b~T~Pb zmit?h!y0@!PmlI+sRhE#(^)#%j&bCc8z94;RDZ`yg_VIjB9^WKN?<&e=~L?UQk5*egSt;wlmW2ZmPVStQd zFe_xYC3*c5FJ_~opbAnc2J7d6_hW15&RmF^z&@$_{@FHsv` z@Izut2f$i7MypY?L4@*(D|&H|GZ`(3NNsBh2=;ME0|9wjjlodjj}t4C3*M@9Tg@3q zl7Sg1a^&KE%WPOfd2|+$R1rk-p=4~Y)T)%W##7t=ZIWOBKoOQY-BItVl^XyvuXxw#z48{~}kO9&sg|CI*6Du#zIMyG|>jnP@-bbCtE(Q9GBkg0E zTeO!2kusf^gpu1so)-Pw`n62gSwsUrwY30;|74zw48D^ccLcUJrnt61$gkPFV~@*= zLz$o_8SL_m1XiNqsQddbTM>mbuB77>;+8yj@M$ z#NamhnrkNBxbhH;U-85OqmNoCPjY^|rYThC81Hr{wUr~oZrVw^WZ76DQ()QT&UC$& zFFzoBgno2s!@OMAs8MAnA<5z2uPmlBwT=zr^FG%Tv~6>l347*^Wn_SH5$wfOnt3gV z?7=3iL3D|#!8nR-D$AR2abJ2gEhMz@J-(jxyC{Of-==+C7#OVbm?EXG%c{oBgw`) z0Q<(ylF)0npC9=7VaKsH!?dn#k^Rr#52$r(e<4=q@qycU?OCsOvA7I~zBqD|K z!??{0d;AgP*P>KNxN3+v*anQMuw3S;&tNCM%M6EtCV_{>U0%XS(5s6l;l+j+7Zh*Z znbDfZWGVe$DN+^Z;NeUtR-z+o%SMH}#PX9hh{QKjpM=@)Vi>RtTZkFydX1*}5519O z11e9v(o~8nAtoZ*5LV#6kg$cnAh5(F58ESQG=ac0bS%)p#l;{?S@7TKk;HWQXaV5r z3Yw?@_atg(c9o`|0I8=)j-&x<+SCwWp{v4_5G|(AQHlOw26A^qDYz3RWaSBishz*1 zsFi{(M8pze1J1swiM^9tcAz>7p7ZG^wJ-5aGf64W`F-TAJ#0)1)6|vln>U*3TYmv@U zG9XF`?ve(NoM@m#iko=(u38iTX{VQspg8z*)xHb{c833i8NFZcQ`izwX+5B#oqp0MUJlX*R!1lw>i_UgTW6jmNwBzT|1TvKsL=h z>|!$skBi~9Ki0X9VFUsoM^0dXF)^S*Moc|256M#pX{cRvYLG9;SkhxaW<_;5FFO|| zP171@XoL&=-8?3a8&@*AA=@%HvMz{dQ*3$=fnzZe142zlS9H3`4+qm$yO^Moqr-~h z0v$N16h&2^XdteFe2=iP0_3p%wc&I!?*CfQj#BMGt0Hm=zCk7BW$<<_QjAbD%yKOQeY( zdS~AV2trN4SQisZv(oZ}_C{3Bl`=Lg!rRi=`84_DHy-@))b>878jlb9=kIT@NHTvR zT8Ht*!@uXRkTSm$vHj2US9yK;h1}MKjPBO zFd4^GSh#pQ`YNMeULDurfEhKC!ZHmdXX=vtTm(%;uFGo^#ZG=%DoSbw z&OahK5@n63&MZm~D>;RpQgrP=ybtkHWNDS!tr@Jsb6)}BM@G<+J^epb@`&2oE|vYxg$*zrZZFI z73+vngXt1ehXNV=JrWk-rY;CUeOl%el8;H=?Esf1qJc5s5xY!rfA+qv$7P*V9_B25P8v8Yx<}UgIqO*H>#iXcC+u)I4dew z0e7Eg(#~nBx?-2X_#Kek$K$YYa9SFXG(fi_@up;(``S)7bt_u*gwSE=`RTGvYAr-D zK8sDr`~a5-$W!E^b|<@ke-Sts4#d!<*I#i_tS2#6N8E0m^& z2-@DboKAh@tN>(D@i9j6vfx&A+AGzqrK}-(zjN@tWaTr%x0GeBd_tuw{F?Qf1V4xR z%sZlO>LRH~pCp%nN>F5UAg@Yx{^=pIjqI4_ZUkJRO@Wjf?hESJPz5W-q$Tha>8tCg z+mxqZto{-keMLlk_2kATNW9FpeZw`bE!gQ)I(WfQi3HT1hg6SBD1vy z=B4HVot)GfN|zK5h?Z`Di|+&T#B`PMtJ)w_2M)Fz?W6*$eCM^pjI!J>0eHzRm&K^P z7h^91F>>xPz$-{T>u1dTfknLMW($F3VQ>=_w~X6;aT`1{*8~#uqGC5H9Xp^L*}hQ= zA02(RG(5-|l@|&#WWWSQ4HD2y+W8~*7i@pHAMAi@s;Zz35AKL4xR($eD)cZUx>5@} zlM=ZM^Y4%N6yp3XU$2*<`Od9gEH834I)gOlH8Z}u($$ncTU`k@BIRz)k#l!ATyy@U z-HwbWSw_2zO8>LtZ*V_LFWEQnbPIFnF0h;<%P=ez?V6euG&b9lG`Y3{e(5|E8g0K& zm$n*8*ekbiR^e^nt3{B}u@nVyqkccq)1PogNF`U~9Cg9S;tG?{wJdK=YV=^h7A&-{ zatcPCFvij4qo*t!D5OQ`?l_^#7V|u7h%*188_xv;sD!0|SL70e4T)u^+M|WgfDIbR z(>>7u!GfV$YEp8UyUPASM_*Eq-;xwm9Tjjw+&GD3gI5??i3aQNz5pdmCv#;q8NA69AkR&C|8_#h zivrjbbrp8Ox*PFlYPXndzoQv(wE+^pB};EAZ%Ifh@f?~ZrDZfn5_MA1>0!~gQ4jD9 zmw=dL1j(R&66#B2jIb7sGcDSyW5L1hOq#^11_1!@E1Pg8WIrk}2aLePSX?2r8=g4$UO6>Tb{1~0Gzw>E7o~(pNOE%B!%A^1!nT3w zKH^#QM#%bH&q*;P&Lb$$gW6>)V0fsaC?k|7%hUz|drpTIAKle8IC(7!si{0Jp(k#e zh)D2BBHiBrQw86FwL4sZ4w{%e%`E?!^-K!_=||Q_{pswoBsF=fQ40AyG+QW9$t`|p>E@IueBAVq*-R)TNV9Z`Dsw){q4@y- z{D6CPb#9E{631nAc3a!TOm^I~md4_wV0HrM%-~4M=D+&;9ONzFFErsiu6j0aU5S+S zMDTKb4#7bJktwtz%57d*y`I9pV#nx8u5SZH$Ay=&9uOctb}9WAbQp)1W?iJl`=*Wahr%5P+7_%w(&-Q`(7|MxE% z4+|jtWsH^GHj7cyGz$v&AodmvtkP7~?`T9aWxEjaW*OA5-sVd$>s!o?|6}VO3C(Y9 zD_t;c)`{8Hyc67#FO_ZP+pR0G<&4k%6H6|g_FB45qsL*Tns<5ugMwSJLyX7l5|&1T zjNQLF)tE#XdJ|WDIMmtT{#(X0R5j?ENY>e}-d($8lDSyEXlGk|`*l4kX7@Rl87I|u zR&7a@y!b_Z<$S}0IdQr+j?7CBs6@#G&fAbGb#-;@rIqGO4a{`;6>e;q;%RGGaLQzn zm*NRd;Ry!?tZG7q90fbLWM>&ANK1$Nd2US;@-$Nlc=IKR>nl^>52XfGeU*I*9YFJX zuf?`{h$WnU%Am(|Y+~BKD+@D@KFWT2*YVn%i7Qv<>|J>wJyEccIZXF@S^SMPUUq+; xX0j-(Xbs^i(iZh9S!F+4YOB~S37H#?#s>~=5YjsA`2&7b32+1YC>TK@0065V$&&y8 literal 0 HcmV?d00001 diff --git a/public/voicechat_leave.mp3 b/public/voicechat_leave.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..942dd0c653629bb148a5a45ce1ca9cb858a62627 GIT binary patch literal 9600 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 w=rt2mQ%@FAtj-dAYA`N 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 literal 0 HcmV?d00001 diff --git a/public/voicechat_recordstart.mp3 b/public/voicechat_recordstart.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..0060e82c3737b9fefb3d3cc59a240cfdcf458f54 GIT binary patch literal 17616 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 literal 0 HcmV?d00001 diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index d6cf98c52..a8f799828 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -14,6 +14,10 @@ declare namespace React { interface ImgHTMLAttributes extends HTMLAttributes { loading?: 'auto' | 'eager' | 'lazy'; } + + interface VideoHTMLAttributes { + srcObject?: MediaStream; + } } type AnyLiteral = Record; diff --git a/src/api/gramjs/apiBuilders/calls.ts b/src/api/gramjs/apiBuilders/calls.ts new file mode 100644 index 000000000..4430a6e0a --- /dev/null +++ b/src/api/gramjs/apiBuilders/calls.ts @@ -0,0 +1,98 @@ +import { GroupCallParticipant, GroupCallParticipantVideo, SsrcGroup } from '../../../lib/secret-sauce'; +import { Api as GramJs } from '../../../lib/gramjs'; +import { ApiGroupCall } from '../../types'; +import { getApiChatIdFromMtpPeer, isPeerUser } from './peers'; + +export function buildApiGroupCallParticipant(participant: GramJs.GroupCallParticipant): GroupCallParticipant { + const { + self, min, about, date, versioned, canSelfUnmute, justJoined, left, muted, mutedByYou, source, volume, + volumeByAdmin, videoJoined, peer, video, presentation, raiseHandRating, + } = participant; + + return { + isSelf: self, + isMin: min, + canSelfUnmute, + isLeft: left, + isMuted: muted, + isMutedByMe: mutedByYou, + hasJustJoined: justJoined, + isVolumeByAdmin: volumeByAdmin, + isVersioned: versioned, + isVideoJoined: videoJoined, + about, + source, + raiseHandRating: raiseHandRating?.toString(), + volume, + date: new Date(date), + isUser: isPeerUser(peer), + id: getApiChatIdFromMtpPeer(peer), + video: video ? buildApiGroupCallParticipantVideo(video) : undefined, + presentation: presentation ? buildApiGroupCallParticipantVideo(presentation) : undefined, + }; +} + +function buildApiGroupCallParticipantVideo( + participantVideo: GramJs.GroupCallParticipantVideo, +): GroupCallParticipantVideo { + const { + audioSource, endpoint, paused, sourceGroups, + } = participantVideo; + return { + audioSource, + endpoint, + isPaused: paused, + sourceGroups: sourceGroups.map(buildApiGroupCallParticipantVideoSourceGroup), + }; +} + +function buildApiGroupCallParticipantVideoSourceGroup( + participantVideoSourceGroup: GramJs.GroupCallParticipantVideoSourceGroup, +): SsrcGroup { + return { + semantics: participantVideoSourceGroup.semantics, + sources: participantVideoSourceGroup.sources, + }; +} + +export function buildApiGroupCall(groupCall: GramJs.TypeGroupCall): ApiGroupCall { + const { + id, accessHash, + } = groupCall; + + if (groupCall instanceof GramJs.GroupCallDiscarded) { + return { + connectionState: 'discarded', + id: id.toString(), + accessHash: accessHash.toString(), + participantsCount: 0, + version: 0, + participants: {}, + }; + } + + const { + version, participantsCount, streamDcId, scheduleDate, canChangeJoinMuted, joinMuted, canStartVideo, + scheduleStartSubscribed, + } = groupCall; + + return { + connectionState: 'disconnected', + isLoaded: true, + id: id.toString(), + accessHash: accessHash.toString(), + version, + participantsCount, + streamDcId, + scheduleDate, + canChangeJoinMuted, + joinMuted, + canStartVideo, + scheduleStartSubscribed, + participants: {}, + }; +} + +export function getGroupCallId(groupCall: GramJs.TypeInputGroupCall) { + return groupCall.id.toString(); +} diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index a5abe5e55..00e382ab2 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -21,6 +21,7 @@ import { ApiChat, ApiThreadInfo, ApiInvoice, + ApiGroupCall, } from '../../types'; import { @@ -593,6 +594,7 @@ function buildAction( return undefined; } + let call: Partial | undefined; let amount: number | undefined; let currency: string | undefined; let text: string; @@ -677,6 +679,13 @@ function buildAction( const mins = Math.max(Math.round(action.duration! / 60), 1); translationValues.push(`${mins} min${mins > 1 ? 's' : ''}`); } + } else if (action instanceof GramJs.MessageActionInviteToGroupCall) { + text = 'Notification.VoiceChatInvitation'; + call = { + id: action.call.id.toString(), + accessHash: action.call.accessHash.toString(), + }; + translationValues.push('%action_origin%', '%target_user%'); } else if (action instanceof GramJs.MessageActionContactSignUp) { text = 'Notification.Joined'; translationValues.push('%action_origin%'); @@ -696,6 +705,10 @@ function buildAction( translationValues.push(`${mins} min${mins > 1 ? 's' : ''}`); } else { text = 'Notification.VoiceChatStartedChannel'; + call = { + id: action.call.id.toString(), + accessHash: action.call.accessHash.toString(), + }; } } else if (action instanceof GramJs.MessageActionBotAllowed) { text = 'Chat.Service.BotPermissionAllowed'; @@ -718,6 +731,7 @@ function buildAction( amount, currency, translationValues, + call, }; } diff --git a/src/api/gramjs/gramjsBuilders/index.ts b/src/api/gramjs/gramjsBuilders/index.ts index 21700fb85..ff48a3685 100644 --- a/src/api/gramjs/gramjsBuilders/index.ts +++ b/src/api/gramjs/gramjsBuilders/index.ts @@ -5,15 +5,16 @@ import { ApiPrivacyKey } from '../../../types'; import { generateRandomBytes, readBigIntFromBuffer } from '../../../lib/gramjs/Helpers'; import { - ApiSticker, - ApiVideo, - ApiNewPoll, + ApiChatAdminRights, + ApiChatBannedRights, + ApiChatFolder, + ApiGroupCall, ApiMessageEntity, ApiMessageEntityTypes, - ApiChatFolder, - ApiChatBannedRights, - ApiChatAdminRights, + ApiNewPoll, ApiReportReason, + ApiSticker, + ApiVideo, } from '../../types'; import localDb from '../localDb'; import { pick } from '../../../util/iteratees'; @@ -237,6 +238,10 @@ export function generateRandomBigInt() { return readBigIntFromBuffer(generateRandomBytes(8), true, true); } +export function generateRandomInt() { + return readBigIntFromBuffer(generateRandomBytes(4), true, true).toJSNumber(); +} + export function buildMessageFromUpdate( id: number, chatId: string, @@ -424,3 +429,10 @@ export function buildMtpPeerId(id: string, type: 'user' | 'chat' | 'channel') { return type === 'user' ? BigInt(id) : BigInt(id.slice(1)); } + +export function buildInputGroupCall(groupCall: Partial) { + return new GramJs.InputGroupCall({ + id: BigInt(groupCall.id!), + accessHash: BigInt(groupCall.accessHash!), + }); +} diff --git a/src/api/gramjs/helpers.ts b/src/api/gramjs/helpers.ts index 9080b6f1c..b2a0cabcd 100644 --- a/src/api/gramjs/helpers.ts +++ b/src/api/gramjs/helpers.ts @@ -47,3 +47,7 @@ export function addChatToLocalDb(chat: GramJs.TypeChat) { localDb.chats[buildApiPeerId(chat.id, chat instanceof GramJs.Chat ? 'chat' : 'channel')] = chat; } } + +export function addUserToLocalDb(user: GramJs.User) { + localDb.users[buildApiPeerId(user.id, 'user')] = user; +} diff --git a/src/api/gramjs/methods/bots.ts b/src/api/gramjs/methods/bots.ts index e1cc37d29..ee95eb326 100644 --- a/src/api/gramjs/methods/bots.ts +++ b/src/api/gramjs/methods/bots.ts @@ -9,7 +9,7 @@ import { buildInputPeer, generateRandomBigInt } from '../gramjsBuilders'; import { buildApiUser } from '../apiBuilders/users'; import { buildApiBotInlineMediaResult, buildApiBotInlineResult, buildBotSwitchPm } from '../apiBuilders/bots'; import { buildApiChatFromPreview } from '../apiBuilders/chats'; -import { buildApiPeerId } from '../apiBuilders/peers'; +import { addUserToLocalDb } from '../helpers'; export function init() { } @@ -158,10 +158,6 @@ function getInlineBotResultsNextOffset(username: string, nextOffset?: string) { return username === 'gif' && nextOffset === '0' ? '' : nextOffset; } -function addUserToLocalDb(user: GramJs.User) { - localDb.users[buildApiPeerId(user.id, 'user')] = user; -} - function addDocumentToLocalDb(document: GramJs.Document) { localDb.documents[String(document.id)] = document; } diff --git a/src/api/gramjs/methods/calls.ts b/src/api/gramjs/methods/calls.ts new file mode 100644 index 000000000..d2d00ab13 --- /dev/null +++ b/src/api/gramjs/methods/calls.ts @@ -0,0 +1,236 @@ +import { JoinGroupCallPayload } from '../../../lib/secret-sauce'; +import { + ApiChat, ApiUser, OnApiUpdate, ApiGroupCall, +} from '../../types'; +import { Api as GramJs } from '../../../lib/gramjs'; + +import { invokeRequest } from './client'; +import { buildInputGroupCall, buildInputPeer, generateRandomInt } from '../gramjsBuilders'; +import { + buildApiGroupCall, + buildApiGroupCallParticipant, + +} from '../apiBuilders/calls'; +import { buildApiUser } from '../apiBuilders/users'; +import { buildApiChatFromPreview } from '../apiBuilders/chats'; +import { addChatToLocalDb, addUserToLocalDb } from '../helpers'; +import { GROUP_CALL_PARTICIPANTS_LIMIT } from '../../../config'; + +let onUpdate: OnApiUpdate; + +export function init(_onUpdate: OnApiUpdate) { + onUpdate = _onUpdate; +} + +export async function getGroupCall({ + call, +}: { + call: Partial; +}) { + const result = await invokeRequest(new GramJs.phone.GetGroupCall({ + call: buildInputGroupCall(call), + })); + + if (!result) { + return undefined; + } + + result.users.map(addUserToLocalDb); + result.chats.map(addChatToLocalDb); + + const users = result.users.map(buildApiUser).filter(Boolean as any); + const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean as any); + + return { + groupCall: buildApiGroupCall(result.call), + users, + chats, + }; +} + +export function discardGroupCall({ + call, +}: { + call: ApiGroupCall; +}) { + return invokeRequest(new GramJs.phone.DiscardGroupCall({ + call: buildInputGroupCall(call), + }), true); +} + +export function editGroupCallParticipant({ + call, participant, muted, presentationPaused, videoStopped, videoPaused, volume, + raiseHand, +}: { + call: ApiGroupCall; participant: ApiUser; muted?: boolean; presentationPaused?: boolean; + videoStopped?: boolean; videoPaused?: boolean; raiseHand?: boolean; volume?: number; +}) { + return invokeRequest(new GramJs.phone.EditGroupCallParticipant({ + call: buildInputGroupCall(call), + participant: buildInputPeer(participant.id, participant.accessHash), + ...(videoStopped !== undefined && { videoStopped }), + ...(videoPaused !== undefined && { videoPaused }), + ...(muted !== undefined && { muted }), + ...(presentationPaused !== undefined && { presentationPaused }), + ...(raiseHand !== undefined && { raiseHand }), + ...(volume !== undefined && { volume }), + }), true); +} + +export function editGroupCallTitle({ + groupCall, title, +}: { + groupCall: ApiGroupCall; title: string; +}) { + return invokeRequest(new GramJs.phone.EditGroupCallTitle({ + title, + call: buildInputGroupCall(groupCall), + }), true); +} + +export async function exportGroupCallInvite({ + call, canSelfUnmute, +}: { + call: ApiGroupCall; canSelfUnmute: boolean; +}) { + const result = await invokeRequest(new GramJs.phone.ExportGroupCallInvite({ + canSelfUnmute: canSelfUnmute || undefined, + call: buildInputGroupCall(call), + })); + + if (!result) { + return undefined; + } + + return result.link; +} + +export async function fetchGroupCallParticipants({ + call, offset, +}: { + call: ApiGroupCall; offset?: string; +}) { + const result = await invokeRequest(new GramJs.phone.GetGroupParticipants({ + call: buildInputGroupCall(call), + ids: [], + sources: [], + offset: offset || '', + limit: GROUP_CALL_PARTICIPANTS_LIMIT, + })); + + if (!result) { + return undefined; + } + + result.users.map(addUserToLocalDb); + result.chats.map(addChatToLocalDb); + + const users = result.users.map(buildApiUser).filter(Boolean as any); + const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean as any); + + onUpdate({ + '@type': 'updateGroupCallParticipants', + groupCallId: call.id, + participants: result.participants.map(buildApiGroupCallParticipant), + nextOffset: result.nextOffset, + }); + + return { + users, chats, + }; +} + +export function leaveGroupCall({ + call, +}: { + call: ApiGroupCall; +}) { + return invokeRequest(new GramJs.phone.LeaveGroupCall({ + call: buildInputGroupCall(call), + }), true); +} + +export async function joinGroupCall({ + call, inviteHash, params, +}: { + call: ApiGroupCall; inviteHash?: string; params: JoinGroupCallPayload; +}) { + const result = await invokeRequest(new GramJs.phone.JoinGroupCall({ + call: buildInputGroupCall(call), + joinAs: new GramJs.InputPeerSelf(), + muted: true, + videoStopped: true, + params: new GramJs.DataJSON({ + data: JSON.stringify(params), + }), + inviteHash, + }), true); + + if (!result) return undefined; + + if (result instanceof GramJs.Updates) { + const update = result.updates.find((u) => u instanceof GramJs.UpdateGroupCall); + if (!(update instanceof GramJs.UpdateGroupCall)) return undefined; + + return buildApiGroupCall(update.call); + } + + return undefined; +} + +export async function createGroupCall({ + peer, +}: { + peer: ApiChat; +}) { + const randomId = generateRandomInt(); + const result = await invokeRequest(new GramJs.phone.CreateGroupCall({ + peer: buildInputPeer(peer.id, peer.accessHash), + randomId, + }), true); + + if (!result) return undefined; + + if (result instanceof GramJs.Updates) { + const update = result.updates[0]; + if (update instanceof GramJs.UpdateGroupCall) { + return buildApiGroupCall(update.call); + } + } + + return undefined; +} + +export function joinGroupCallPresentation({ + call, params, +}: { + call: ApiGroupCall; params: JoinGroupCallPayload; +}) { + return invokeRequest(new GramJs.phone.JoinGroupCallPresentation({ + call: buildInputGroupCall(call), + params: new GramJs.DataJSON({ + data: JSON.stringify(params), + }), + }), true); +} + +export function toggleGroupCallStartSubscription({ + call, subscribed, +}: { + call: ApiGroupCall; subscribed: boolean; +}) { + return invokeRequest(new GramJs.phone.ToggleGroupCallStartSubscription({ + call: buildInputGroupCall(call), + subscribed, + }), true); +} + +export function leaveGroupCallPresentation({ + call, +}: { + call: ApiGroupCall; +}) { + return invokeRequest(new GramJs.phone.LeaveGroupCallPresentation({ + call: buildInputGroupCall(call), + }), true); +} diff --git a/src/api/gramjs/methods/chats.ts b/src/api/gramjs/methods/chats.ts index aae826131..5932da779 100644 --- a/src/api/gramjs/methods/chats.ts +++ b/src/api/gramjs/methods/chats.ts @@ -11,6 +11,7 @@ import { ApiChatFolder, ApiChatBannedRights, ApiChatAdminRights, + ApiGroupCall, } from '../../types'; import { @@ -323,6 +324,7 @@ export function clearDraft(chat: ApiChat) { async function getFullChatInfo(chatId: string): Promise<{ fullInfo: ApiChatFullInfo; users?: ApiUser[]; + groupCall?: Partial; } | undefined> { const result = await invokeRequest(new GramJs.messages.GetFullChat({ chatId: buildInputEntity(chatId) as BigInt.BigInteger, @@ -339,6 +341,7 @@ async function getFullChatInfo(chatId: string): Promise<{ participants, exportedInvite, botInfo, + call, } = result.fullChat; const members = buildChatMembers(participants); @@ -355,8 +358,19 @@ async function getFullChatInfo(chatId: string): Promise<{ ...(exportedInvite && { inviteLink: exportedInvite.link, }), + groupCallId: call?.id.toString(), }, users: result.users.map(buildApiUser).filter(Boolean as any), + groupCall: call ? { + chatId, + isLoaded: false, + id: call.id.toString(), + accessHash: call.accessHash.toString(), + connectionState: 'disconnected', + participantsCount: 0, + version: 0, + participants: {}, + } : undefined, }; } @@ -364,7 +378,11 @@ async function getFullChannelInfo( id: string, accessHash: string, adminRights?: ApiChatAdminRights, -) { +): Promise<{ + fullInfo: ApiChatFullInfo; + users?: ApiUser[]; + groupCall?: Partial; + } | undefined> { const result = await invokeRequest(new GramJs.channels.GetFullChannel({ channel: buildInputEntity(id, accessHash) as GramJs.InputChannel, })); @@ -438,6 +456,16 @@ async function getFullChannelInfo( botCommands, }, users: [...(users || []), ...(bannedUsers || []), ...(adminUsers || [])], + groupCall: call ? { + chatId: id, + isLoaded: false, + id: call.id.toString(), + accessHash: call?.accessHash.toString(), + participants: {}, + version: 0, + participantsCount: 0, + connectionState: 'disconnected', + } : undefined, }; } diff --git a/src/api/gramjs/methods/index.ts b/src/api/gramjs/methods/index.ts index 3f2848f1c..09988c31d 100644 --- a/src/api/gramjs/methods/index.ts +++ b/src/api/gramjs/methods/index.ts @@ -60,3 +60,9 @@ export { export { validateRequestedInfo, sendPaymentForm, getPaymentForm, getReceipt, } from './payments'; + +export { + getGroupCall, joinGroupCall, discardGroupCall, createGroupCall, + editGroupCallTitle, editGroupCallParticipant, exportGroupCallInvite, fetchGroupCallParticipants, + joinGroupCallPresentation, leaveGroupCall, leaveGroupCallPresentation, toggleGroupCallStartSubscription, +} from './calls'; diff --git a/src/api/gramjs/provider.ts b/src/api/gramjs/provider.ts index 07eedacf3..0a509dea6 100644 --- a/src/api/gramjs/provider.ts +++ b/src/api/gramjs/provider.ts @@ -17,6 +17,7 @@ import { init as initClient } from './methods/client'; import { init as initStickers } from './methods/symbols'; import { init as initManagement } from './methods/management'; import { init as initTwoFaSettings } from './methods/twoFaSettings'; +import { init as initCalls } from './methods/calls'; import * as methods from './methods'; let onUpdate: OnApiUpdate; @@ -32,6 +33,7 @@ export async function initApi(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArg initStickers(handleUpdate); initManagement(handleUpdate); initTwoFaSettings(handleUpdate); + initCalls(handleUpdate); await initClient(handleUpdate, initialArgs); } diff --git a/src/api/gramjs/updater.ts b/src/api/gramjs/updater.ts index 713eef467..fabd83f7e 100644 --- a/src/api/gramjs/updater.ts +++ b/src/api/gramjs/updater.ts @@ -1,3 +1,4 @@ +import { GroupCallConnectionData } from '../../lib/secret-sauce'; import { Api as GramJs, connection } from '../../lib/gramjs'; import { ApiMessage, ApiUpdateConnectionStateType, OnApiUpdate } from '../types'; @@ -33,6 +34,11 @@ import { DEBUG } from '../../config'; import { addMessageToLocalDb, addPhotoToLocalDb, resolveMessageApiChatId } from './helpers'; import { buildApiNotifyException, buildPrivacyKey, buildPrivacyRules } from './apiBuilders/misc'; import { buildApiPhoto } from './apiBuilders/common'; +import { + buildApiGroupCall, + buildApiGroupCallParticipant, + getGroupCallId, +} from './apiBuilders/calls'; import { buildApiPeerId, getApiChatIdFromMtpPeer } from './apiBuilders/peers'; type Update = ( @@ -50,6 +56,39 @@ export function init(_onUpdate: OnApiUpdate) { const sentMessageIds = new Set(); let serverTimeOffset = 0; +function addEntities(entities: (GramJs.TypeUser | GramJs.TypeChat)[] | undefined) { + if (entities?.length) { + entities + .filter((e) => e instanceof GramJs.User) + .map(buildApiUser) + .forEach((user) => { + if (!user) { + return; + } + + onUpdate({ + '@type': 'updateUser', + id: user.id, + user, + }); + }); + entities + .filter((e) => e instanceof GramJs.Chat || e instanceof GramJs.Channel) + .map((e) => buildApiChatFromPreview(e)) + .forEach((chat) => { + if (!chat) { + return; + } + + onUpdate({ + '@type': 'updateChat', + id: chat.id, + chat, + }); + }); + } +} + export function updater(update: Update, originRequest?: GramJs.AnyRequest) { if (update instanceof connection.UpdateServerTimeOffset) { serverTimeOffset = update.timeOffset; @@ -111,37 +150,7 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) { } // eslint-disable-next-line no-underscore-dangle - const entities = update._entities; - if (entities?.length) { - entities - .filter((e) => e instanceof GramJs.User) - .map(buildApiUser) - .forEach((user) => { - if (!user) { - return; - } - - onUpdate({ - '@type': 'updateUser', - id: user.id, - user, - }); - }); - entities - .filter((e) => e instanceof GramJs.Chat || e instanceof GramJs.Channel) - .map((e) => buildApiChatFromPreview(e)) - .forEach((chat) => { - if (!chat) { - return; - } - - onUpdate({ - '@type': 'updateChat', - id: chat.id, - chat, - }); - }); - } + addEntities(update._entities); if (update instanceof GramJs.UpdateNewScheduledMessage) { onUpdate({ @@ -232,6 +241,17 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) { id: message.chatId, }); } + } else if (action instanceof GramJs.MessageActionGroupCall) { + if (!action.duration && action.call) { + onUpdate({ + '@type': 'updateGroupCallChatId', + chatId: message.chatId, + call: { + id: action.call.id.toString(), + accessHash: action.call.accessHash.toString(), + }, + }); + } } } } else if ( @@ -785,6 +805,26 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) { onUpdate({ '@type': 'updateResetContactList' }); } else if (update instanceof GramJs.UpdateFavedStickers) { onUpdate({ '@type': 'updateFavoriteStickers' }); + } else if (update instanceof GramJs.UpdateGroupCall) { + onUpdate({ + '@type': 'updateGroupCall', + call: buildApiGroupCall(update.call), + }); + } else if (update instanceof GramJs.UpdateGroupCallConnection) { + onUpdate({ + '@type': 'updateGroupCallConnection', + data: JSON.parse(update.params.data) as GroupCallConnectionData, + presentation: Boolean(update.presentation), + }); + } else if (update instanceof GramJs.UpdateGroupCallParticipants) { + // eslint-disable-next-line no-underscore-dangle + addEntities(update._entities); + + onUpdate({ + '@type': 'updateGroupCallParticipants', + groupCallId: getGroupCallId(update.call), + participants: update.participants.map(buildApiGroupCallParticipant), + }); } else if (DEBUG) { const params = typeof update === 'object' && 'className' in update ? update.className : update; // eslint-disable-next-line no-console diff --git a/src/api/types/calls.ts b/src/api/types/calls.ts new file mode 100644 index 000000000..7f2a6dc3c --- /dev/null +++ b/src/api/types/calls.ts @@ -0,0 +1,26 @@ +import { GroupCallParticipant, GroupCallConnectionState } from '../../lib/secret-sauce'; + +export interface ApiGroupCall { + chatId?: string; + isLoaded?: boolean; + id: string; + accessHash: string; + joinMuted?: true; + canChangeJoinMuted?: true; + canStartVideo?: true; + joinDateAsc?: true; + scheduleStartSubscribed?: true; + participantsCount: number; + params?: any; + title?: string; + streamDcId?: number; + recordStartDate?: number; + scheduleDate?: number; + version: number; + inviteHash?: string; + + nextOffset?: string; + participants: Record; + connectionState: GroupCallConnectionState; + isSpeakerDisabled?: boolean; +} diff --git a/src/api/types/chats.ts b/src/api/types/chats.ts index 0f1b24ed3..0cf8d59d4 100644 --- a/src/api/types/chats.ts +++ b/src/api/types/chats.ts @@ -108,6 +108,7 @@ export interface ApiChatAdminRights { pinMessages?: true; addAdmins?: true; anonymous?: true; + manageCall?: true; } export interface ApiChatBannedRights { diff --git a/src/api/types/index.ts b/src/api/types/index.ts index b7c320395..d0059b8a2 100644 --- a/src/api/types/index.ts +++ b/src/api/types/index.ts @@ -7,3 +7,4 @@ export * from './payments'; export * from './settings'; export * from './bots'; export * from './misc'; +export * from './calls'; diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts index af8c8ac15..4be0c1812 100644 --- a/src/api/types/messages.ts +++ b/src/api/types/messages.ts @@ -1,3 +1,5 @@ +import { ApiGroupCall } from './calls'; + export interface ApiDimensions { width: number; height: number; @@ -155,6 +157,7 @@ export interface ApiAction { amount?: number; currency?: string; translationValues: string[]; + call?: Partial; } export interface ApiWebPage { diff --git a/src/api/types/updates.ts b/src/api/types/updates.ts index 7ca4693af..b051d3bf9 100644 --- a/src/api/types/updates.ts +++ b/src/api/types/updates.ts @@ -1,3 +1,4 @@ +import { GroupCallConnectionData, GroupCallParticipant, GroupCallConnectionState } from '../../lib/secret-sauce'; import { ApiChat, ApiChatFullInfo, @@ -12,6 +13,9 @@ import { ApiUser, ApiUserFullInfo, ApiUserStatus } from './users'; import { ApiError, ApiInviteInfo, ApiNotifyException, ApiSessionData, } from './misc'; +import { + ApiGroupCall, +} from './calls'; export type ApiUpdateReady = { '@type': 'updateApiReady'; @@ -378,6 +382,48 @@ export type ApiUpdateServerTimeOffset = { serverTimeOffset: number; }; +export type ApiUpdateGroupCall = { + '@type': 'updateGroupCall'; + call: ApiGroupCall; +}; + +export type ApiUpdateGroupCallChatId = { + '@type': 'updateGroupCallChatId'; + call: Partial; + chatId: string; +}; + +export type ApiUpdateGroupCallLeavePresentation = { + '@type': 'updateGroupCallLeavePresentation'; +}; + +export type ApiUpdateGroupCallParticipants = { + '@type': 'updateGroupCallParticipants'; + groupCallId: string; + participants: GroupCallParticipant[]; + nextOffset?: string; +}; + +export type ApiUpdateGroupCallConnection = { + '@type': 'updateGroupCallConnection'; + data: GroupCallConnectionData; + presentation: boolean; +}; + +export type ApiUpdateGroupCallStreams = { + '@type': 'updateGroupCallStreams'; + userId: string; + hasAudioStream: boolean; + hasVideoStream: boolean; + hasPresentationStream: boolean; +}; + +export type ApiUpdateGroupCallConnectionState = { + '@type': 'updateGroupCallConnectionState'; + connectionState: GroupCallConnectionState; + isSpeakerDisabled?: boolean; +}; + export type ApiUpdate = ( ApiUpdateReady | ApiUpdateSession | ApiUpdateAuthorizationState | ApiUpdateAuthorizationError | ApiUpdateConnectionState | ApiUpdateCurrentUser | @@ -395,7 +441,9 @@ export type ApiUpdate = ( ApiUpdateDeleteScheduledMessages | ApiUpdateResetMessages | ApiUpdateTwoFaError | ApiUpdateTwoFaStateWaitCode | ApiUpdateNotifySettings | ApiUpdateNotifyExceptions | ApiUpdatePeerBlocked | ApiUpdatePrivacy | - ApiUpdateServerTimeOffset | ApiUpdateShowInvite + ApiUpdateServerTimeOffset | ApiUpdateShowInvite | + ApiUpdateGroupCallParticipants | ApiUpdateGroupCallConnection | ApiUpdateGroupCall | ApiUpdateGroupCallStreams | + ApiUpdateGroupCallConnectionState | ApiUpdateGroupCallLeavePresentation | ApiUpdateGroupCallChatId ); export type OnApiUpdate = (update: ApiUpdate) => void; diff --git a/src/assets/animatedIcons/CallSchedule.tgs b/src/assets/animatedIcons/CallSchedule.tgs new file mode 100644 index 0000000000000000000000000000000000000000..17ab60453ac165ee0da0573575f55fbfb9e5a23e GIT binary patch literal 14263 zcmZvDW0a;bw`G@Y+qR7^+qP}ncD-d+b=kIU+qSK)s_E~}-1}o@@?+?AA6%E`0# zvl4=6$Y1}JK(2i?6zF)Hp^1GD>=e@Z-{!Rl>Un;y`UyqBg zdg$^3UoTI5Umqg9Dn$a{pXc7CF85CbA77UYPYc@R*P8-A=Xu|ExAs5BwLgFD{XIWV zd%q!`?+@bz9wu*ld^~?r`0r2rbN&AC$oqdjP1@U&vAwV* z9Yrr-?%NXOm5+DoCGCkYR^0VRQR><~7H^8J6S~xeIyEWOD*&(yO69i>Cd1u(yJuMj zU~|78S>QfPYdvpzPpBS(8PaEGU3aNpWo;b}e-v4eVySY=ivP?Au(_uo_^2;z*LsHc zEHU(WDpwE!Bt^?DDwG5;?33#@&`lA=vQEu!&f%&-pXFHQh(jb|rATxF)ssZCeh%1I z_A^t1N+e&SIX0vVOeurpguw0ITT5?$zHbiGUUw~W=<&Vh5O`nKa5^zoM6o-Oq}Gj% z)#Wg=7~O1_z>L*{>_CGRi`IqXUmVl;p8LNXQ*$6^2kAQjz7GCH{Q}H=mDWMA$U8Xr zU61$B!K)v+;PF7~om}+&8jSXj{yV;3cR9p*dxP=Mi+>LAdVG8j+&8wuQFpk+c_Uny zE)JfGy=q!d`>-W#X8hf6L^~c6dxTH#M}H=J{oX?J?tlF}JU^D^?Gj)esMm4u@k_@w zs5xb40GOQ>MB_t~#KpdkPk%Qu+=91y?ET!oZ`bO7f1f}8+dsLL+CQIIIOi)V>8*YAmO&jiX@as zBa`4Qh$Hjh0UwBv>u7JlEozFU5Gp7AjcYD4Q07`_jT3c_Fp>p;01Q$V3Hf?VCBLP%Pnej%Th z3BW)P2`9IWlgT8X%W;8SF3JhLNflvU&1^0VW?x9|lHi}+fI>l=<_>J^tLkP%mIan{ zRo@rY_pY=Q>%R-Bay=ucolj>p*W;7N<7tW@)(Q3B9^)_F{PP6&(mwjZ`@%1OP*0F) zmj}1*`{GmFh#z>z?_OIyx6x!KM~J6ueG+zZC}w6C;@H9B4D7a?$yIF#u;hFu3|eAk zplC8~Vi!DInJPpA@V6gvZKpxuJFJi>4Hf>ru7o7elE>i32i&i|4!@;YDD+JgIEruw z@m2mx+F{v3QR`4io>A!S2$b|Ix7ne_gQ~?|$T25@;wG~x$SS9cf_vt%{Pj?^m7TkRTBUeBlPL5x=- zeap3)VHZ*aZLKB_Lmzql%#WmhIr^q~xQ;UKO{J7QI2`YMhrqtcL=iU%G}GCN%}aV{ zcN6WH=96xjk=yut0xOl&)is?9*uGFw(1fMgND@))GFXQSCMo1VIE!>t!icatjzm_7 zpDe!_G^=ODZ~oHs(GOf%QmNGmtUvD#dArVcYYxu{0lBy0{3yny2+a0O`Krc5!oe*- z=RmNlmhkoR)Sq@_e|LUBLj#)!Mp#A?GAFda6mmPXAg6VIXg~%H%px61O6>Q>Su)%|zOFT_w9V@9UqzGZPGGqFiE63vYs_NH$exm1Xo46)D6 zPy;s9ub+w4EmW49q5KOBBL&xN#9%FI`IrQ!Dz6B=c<}=4ea@C+5kxCAAFpG~rN+0Y z+`l*>u?4@lrmpbDLY=Jkk~84C6riwPpvJ4c^fHjKNjTk5#2Oz+#2Y~#l`TfTSzEBO2}=zEc~&0 zOXzOWw}RF^VuN3Ca!iHNR{`U5e?pl-<3LG&yZaE|X{~ob80$GCa)w0BDgy8Hz~}E6 zVG=TVyCKi5!~l_u1D^0&PO;F^$kS{Fc=95ou_N@b}HJ~G{{xm zA`A+ABzPlyA2%AKBl3(S`~o%j*KEWkj!A${x1C!NB&ljmBy)wRI-y%dPJ1TVvs5g% zRK)ku`xJ1OX^}t5erdzugplqhUs!J4`RO-H^j4?dTargvv%dKu2B3O$^ag!T&s}-evkuR$b z@ii&$@6BEA(xUigAKc7Lj~4a@&Cy}@%j+82fkfIO(YY3GM2ZmOi#Olk68=yHYNbpN z6Q2iN!zegEC&Xh*!{*^4XC%e#U~JvE0mvAbB%BFrmPX{j!C_R~L`=L00~-mqNHXCR zmO_&>e&{#Agu(JyiI<<2hnu!>t}X&jo!MRonV)bH->6fLHmL_bu`z33^#^zecQr^) zO8+YQsW?wKr6^+$+Ih&y)xJsO!J?^y2n#Bo1Py7N^(d7zB$edc%GHIB(AP+$R*X;3 z0WGXaIZiwu*G`a&-~Z!!uvTC14*@B72&lX7LDTsYHW~3*}7_KC6tdblVgD|a}er_bUAxl|fc2F6NaIT3E#AA$H%J7b} z67SePM4^(M>dJJ5TDTY*uI4Pzxgzl)r7$jDC}X1dI1o>Cz&yR@4p#!bA;Il0ss?L{ zvbIq59`1o?4j^&M}fvpLh@RKA|#H~Xh=*bSgWk}{w`faI?0E!6Qmj2Q9xbmWEX{- zha18|!^ksWX_K+fJ>h~^{t(C!!(tXUJ-4k#Ap}lh=8LUlWD=0!Lt~gkzANJqs1h1S zsMu_OY3L$6=ZLx6h)7@Z4ziUsFDEg7FU=sm3c1HZuXly+B~jYy78^j_Grd(b1klOp z--JTwN&1kkNl>G=5C=nY&(ZQ(tpnu`&1b>YADM-#t+zxDLEV**lcy+G68%Xwlz=*B zP%PNVExNI}>}K@5o2OVCJ-K&Zc2bXs1irL?L(6=&4 zWdb|vIKfnmkIJ(+Ibc4Sk7mR=2iA$khqV*mQ`%Jni+v?lv~M*V^y`^Mc|aOQUxrEGTE^Njg&JNXp9!a zkuqvSXtgim&_bsnA31=zbgX+!n{u^kAuhHQ;zbk4m%A5fDz#>b^ zp3Q>#TXXu^g+dOswqF(M`1nM=DG%V7^I87UT({bh=Y^so0Y`09>nb3dhgj|W4`Hia z1#JJz*8MMgyYNx?vbY*UM{8<;Ja940tK`Z;u{?!E}wo z{Hf55SSed+7*sCu=bHU?Z&%6fP8%n+#=(*xR9BYsScqbZgCRnChiSR)+*vw||HS+}L z2pO`$4TaIk=0;9TpHIqzWV$AZ{s*o>u@A%$xWBu8lyckF9b;*wU5CW)mBRAbnJMBU?v(xmJ^ zaK8Kpj}iX?rpFOv49)GPp$lY^=01(HCNAdGwI;qojBU+rH*$xJ_4-#B^#4Bc$uR#7A`;E02CsBf;Ux6Quq1kWE9_htoR5)>N{Y_@$c(Y$pvTgBGmKpJ+p8YCblO8jtUaDdj z#z$+}9RUu%;@Rh7{8N9he_SdXy1k(IF*H?N*E;TJRh+3Fr!o=LSgP!Dd1g-z-zrPm zp1Hl_iJ{9Juof2ZArhu%D(dK|;uj3JqwK zu|QfA>q;(C|F(gw2BDB8U8!8~{c~21G;?%e{zmUM-VoLj(jkmSneZ#Lq#pPbmnJx` zeb3?#*Ia&cH8P47L}-+?>8D$6N#Ae~aH)7mMND*+SEM1ae9dF^DVbCWHo~*rL@{bt zF-`~LZ%lgh6J!cCCP=k#U=9yrrKn~eL${f6zv5HhtQ)7Sj~i@;YZO^cC){-RmW9*) z+`v#)Pd_2#^L%~Hbp(=NlBf_R)eV_liVY}IE=N`RnH{l$8AzaPHyz;gMn22{lob{E zopOmB(3iHxwXy^j*+nr8eU0fJw zWQ0J44`w+d@iJ|qOQae=jF8?JmqZR04?5k@0sA}Wwu!rNZ9iR_0{212 zZB!ZnryykcXK4O%Z=0>L&1D+v_1fQEGBp5&zCy|sZsq4|vGxawKy|f6^K!AiFhqu5 zvq(&ezQ<1GEV@17^H1KA7$g40BCzMNb_S`I>Tr&Jrh#2 z{QAl96w>ck{kmB^>*@*qAl561eoLo6FzSd2K4<yk=yr0%wdaCOInc+SXeGDPa#X^YXe*BWuxqCeu%RsI+1Op~BMtRnv+eM`vjm$Y}; ztn;y*#JJAv1H^;Eskbn@T}|`{RHS~q#NX|FmBm^3wc`yHMq;VDkFTo0GMhr>0yr9# zQOuzQB;y-dH`gyW2CpFQ(MCqD4Ck>Aju~0_?LVv#o;{id1sVmU^d zsED}}VLb^Q#6&%@buN+TMiYe#!K!vTTBYktWn?v*u4oc(jJ#^;F=yw2jqHWpFqo6n zi+(jYo*~LcO`P+gi*-)ls^_jdK7`;onRY$%S*F@<+VUS_6#!+(&_fW>ND zDybm2<1>uW*G4X)@iG)`E(57LIZM&-{dF~XCgEVGsTm|^Xkcu(AVvdh;fAQPXVq)m z`0`G!iHotqD!JO0ug~3G|I=B?D6WiJ3lmCl%qo=v07+jw3b7x3Gy`#90B3FA?xWwm zUT$jz|{E^kDy1|<8tcEZg@hLkzd1>ndXB(*%d_C0;O&cL) z1|GNzX+&|}`_^eoyQOc@@y*3f836(AOk@HUoCHt1dl~`3kB9HWK4jD#(|9O95u#=K zH=24>Ksc1Q;h2qego}_U=0}d9KS&ok%*?$rb)LqwS}t0Tmqv%6Iq^lN6Pxf`N843# z!mQJ<@+uKe2d6d60gVrzRb;q@mj<_s`fFH&ykfn6;KXAK^=ocL3g7p=*Lfd2_LcgC zumq*B%wfaw3)t*d*pwVq$_Fn?}cldZIu?e(45*VNH;GJIUBcg z4SHO}x>%!qu8)y8a(fXX#ppA z)EC_1LIuHQZkwF_M#bWGSY>&Hu)}~EykoFYE+TB&`HXVs>Epjmei|pGrhZ#hW94GQ z#d|4vd7*Cu$>99{M);a*B?#Yij!-k&~PqL6xG5sYW0D;7^ z#vx20`N^;htg0 ztOf*!Xv#L~`6>g!I)q(n>j4aBdw^Bx^{eY{X3qQ;Od~mmjYI3BPY>_G5d1ddHkV!c z@)jK4*w`=O(=vyNm{oFlatyd4wB-J{!6-}5R^LwbU3RZrSfPpH>6Oy#uI;u7ub|TB zL8WEMt^2lpU^zBwIp2IUpa?p`Kav6&DNIi|%_KQfwc;u1IKR?@e1sqYX_l7aY*48K z^_2oYW(cmDORp0jW_*{4;=>vgv$3+WjxETn2fdBeXAUg-v{feB<5eF~4Q#;Iq}OX- zx3{QptJtmXQCgSZ?(P69E4IZA`31$C1*wm+b{bqf~AJf2L83iR>eN=$2O zP-b>TId&Iw_REdg_XnTS-N)Dcv}v2mR`Qf})m85E?t44l-v4b)5(P6V={%t@Jm-4T zz%Y^^h8Rm9lT1O_H8N(j{W4SaWx$I*-KgCmhgJb%So|bfS&mdtxUO2tasqj`3a0Ly z`+xJ_WM(*!c6%`6&=2dAtkl!dU{#Ubk7F1!((l59Y=?j@3=9o)SIgK;16HQ)NbU^HTU zrY)ZF#&E1nU#SKW%44w=NFY68S#$`Nse=lCA1*}^vaB6_ksc1T%-;z%0?)k3){hgx7M?yWRn2EKZ zr-v3iei}4AqXdXCB9BQLGtiZ3=L&hhjNsdvs7H;$$2*TR)I`$WMH7~hZ)9IOXowJf zq%oV6aSHi3isMq@rM4Q3k0f)XH7c{J@iQ^aAujJ4Z;>ZOs9IT0#6%zj@t@GxD7p|p z_u7ND>7&*tp@pp5;~Z8!7YMVjzlP zyWkPrcc|2x8Zm8bvR=M{NDU)ep{Rs|Z4>K9|5i3pOqe*KqHz)*LA_C~E|IWY1^yWW zu{$eY8^Xx@A`Tsqu;M_w@eg3b*%kY)+LRCj*!$fw_ zZRCwb!fc#X==i4g5h`ZvGNaDqF;^<2vY`5iA2*%*Aj+ULY@|DCbMB=fJ*Ttw=lT>c zMu&B%AopopBjoX7A(&ZLy+}vaI2hBj;P~cNUUXCkhxPIysA&s2nH1{;$Sn9!5J&EP zS^pJU@17^ZTucTyS_&K7EOPY{hjzK4H`d^U$RslExa{y60kk2G@aPgrf`UV!-hnC0 zmqMGB_KdH&j1)UF0YbB!to8U|y>qh>I)jp%QK(wy)UUl=*PtuHZ<~JGv@WS6tT$=X2}h;o{T#y|He_ z1CZ%+(!BdLKVn(M$^eAO?F;=W)o*%rEQ@tjtiS)FcJ@C}>qakKs=9(bVaBnfVO^tg z1_OssdnA?u1x|*L5kZEcC#=tBG$j?3N+3ef(zrr4FsjhUismiF9~=P~lbY%aDJR8- z0SF_+s7$_PBc&wO#L*bW;s-2DDCkVUtp(jI6u#ORMR z>yb@zrqCWLHN=RIGk=p#z7l z%L<;rkJ27|tj;C04?0eA{?J{gvbW|njZq5#w6&a8@1zz-vHaLf-ZsexJOG;YN8vG&H`8nW_82SK}&ZErc2{;JQJtE^>}&w+wPn(38J>Xs31VXm^n zIMEl@Rq6E`D?PZ0x}pvqXel#!_!gXzm^BbMAO1U0X}jbqYR)HXwbHh&aTbkQdt8+Yoqy=DCcJ_Ps?pj=+CsfJlNYr*w0rl|8fHC zXCHVs)(&A-=zks$_V5>Lhdb-VXT_DbOSpH@36wZbc_(-Ir8m^Ig-f_a(J7ud54J8r z>=SQ^op_f*}pFvo!lT@KWLdB+VG9|A(f69*e}8u zI4_dMi_xygm)0g((i~X&i3UNRsD^zSBWd|PHa*Ak!IZkyMO;POdE|yU{)j2fk38#D zNOn38J4kO=7o&{u$Y*=_)joP-i6g$X`O*}77-#FfF(U2~zrDzwE6e|{bI-*&lq1DACL=LjpzJG->79M;=FJ(3g z1Jcc%!;t;W+d3_K2}0|x{tAxbv*1I&7d1ms>~u;b!1sOqk-n(YCpoW~mV?gv&_0+< zz=tr<;FL#?=1n`-A1PHv3gekWAU!PUpXt(OEJr^d)877M8GK!escd@$mw?Z z%J%xFsCr%e!aGtMHFl>BD-&Get`-xi#ny@xuM00M@h=Ngu^`NtXOxUyIxT5IV&~rv z5^+@;SU$?A=HFaqt_BfC=fkE;ZYw1%CdlLR)PE=5UEnKd(bx@8a_^z>8p#4b&m-e! zD*VEH(Lu{yBorHMlh7zH$PDn?6J(5J*Sv;iFtS69FgnjJofjbJl7|Y$DrAcQO@`jV zl_(;&43hwB;574vRm3!hDPlFCE-Q2Zil-A$TSr-1m{yjXQGV;4kj{XFUqvM27F}9j z=o)lB9Mln@eOzP~u&W-u|1Z^3xgRPWX?811FF{)GR@PBrIun}lKcXkSb~DYSueKW7 zmb~838We!^Vr7dXplQ7207IeL@9fKUu{bfygvJ2ats{zJiQRn|3o-LJYU*D)rQ_q+ zzImT`#bEaV0$3m0WtA~lISPZjam$@e;}LHwQ0>n)RxKlu0GL|iT`+Gr6b&d4 z8!8|FZ?aG$fp!cY|+BWVUhk~FTMuo?=Cxs_o$LYlwU6>ac8 zLIt8a=3ox{KRTsl|35lqTBg=ii!aP0#egRgsb>`q-_UHe!LswhezHOU4{0o0QcOXsgFsEzarQeC2}whbZ2Ny3NPK} zTY>+uQYbcOEU^vgrizBJM)c*%S_u*+~6YBZ>jY zDTh&%j^G8ogY4Mi$cR#&?hUYaz38Bx&Fmx1BT+|5#RLXg%<%Y;fB#r^L^KT-INGA@ zfKtF-?*37?Cf38+_^=g7 zT0<+*;y9Lgd?ThIoYcmQ~%@*&01I5B-Ipz<>^ltS~qnw zGlUj0CT`G|CGz^Ea(^Q#(+4Q{tn|{(4hqCg)#psd?f(OsRo=J_H>ag=A}(GZ>`ita zA}$zr=n9s+)`ax|Lrc<~GIVE1sk-RoP`3L6i7|6K96ROUK)b350EDP8OA!Y+W`>LF zRLcfr1l*QK-)2)jvEaPSjJzjA*liGPq)CzQ?}?faV#Y&EB-5X~35kmgoXvyLSEBWh zs+=(UFK_T9{a7hpB_?Y%oa)Yg{8|ErgvyoW-n?QpMAfN5&E7>=r%|j2)UvD zG#fAD1e3VZ1}Bkm7D>P@@RxJgM@7O{h$d{BIj72oT$xKFL899xg3D1pPv_88Hi7Y9 z{yTVv%f_$CS>V)8dmIu-lI_;yf6pjP`~pt}35RT%ekBR(6I>4ufe0LO{`sm>y%^^AF=eKW)8IIg z1R=px@M)hd|;`BXzAaZ}|)?XBP2ThFx0+2S`Zsbn7+x292TJR^L2Xi(NP+F##f3O{|edHa#U{mhKl zm#+)A*rGy&XycsawWz%gQUI9id}J=sCOHySj%jcNvj|sa_2=FDo**BVcFuib0wEba zx`dL}oE&zNqdFI|boL{3Io`~W>iaddUN2-@Nh7(%IWa|EK3S~D2%k=|#oh`s+??wN=pLsuT zON09bfIqi>S8WmGlDk~d+hfpPns0jN0h1$@`h>g;ps(KHn6NW$G~1rZ=5@RC=uf?- zAu0H(PN1GMtYQ*H6(E%(`{=ku)|@uam_&dvD|JMQeFWIV2DP{B!?##Nu4(9uElcDu z8E+Z*L`=l1KSU>z1VoliMUj(RVrGu?1&%)98mWEfM)?8jsLPKTlKW9LVCF~VKl@G; zu9JWcz4>GU{;xN0b(9C-Yl9tLL_Z{i+ilA0U(<{tIIJ4`xh=+1dZ=7HE{}~X)bS=s zYOu)Dvs#n90E$i*NyOR9Ac^k{tg7znUdaZdegUx_k8fK2Z{iCA+K=%x3^nUNS*O+4 z4SBN9;_uGyi>u#d^%SQYEqbsxw7!Jg+{!}y$=QVPb#+bKxst-jp~m_e5)OVF<(rDeX(1xz z$(u_T63s-VZd6ni=c`v8Xg%_?b!4QsO?lS90lcXk_bR4X4LlOrgveod0#g(90((=A z$@3kCj6^kP(7B@AUHjdZz!W<1xxB9L&bs&ZRL_avcW|;hDQ5e~1TZVOyoL*(gHXG|Z8nmP>^9@p}b@=}K0xA~-N6#?~fc$q0bN{^*8tFE4 z_4RS;>-Shv@50zR2vLie9q>F-_oEX1GwUIYqSKk|gFu=B z$JyJm++ruIFKxyo%^ZhjI;D##H-#D4an7FIP;;$`8{I;NI}ts&s)eCv9YkMvH!oC5 zgFFIIgK`yqqiQGxg4qc4pyyo0zdwYSbP??kTA-mWCfSB0P(~oox6KBuRDr5-*of|B z!XsG{6rzNBlEv#-o}VF)@To&MNRdQ5w2}2g&n;>qj?jyPQGJ?GUYZu7AE2J0G|WP0 zw+5+`e+K-5s-8jHN$VFKy)PuRO5?IL)-66YFFa{Wf$>2@K(Oj#h9A_v2IBw+~obq<9*I_e`1*cbXwBk5iU))!3k3b|g&(M)~JPP$*STvW;h1oSh;P)HUUEUQU z;B0*7b$1Vi+thQ6gs215bBOuxGg(w7dSB?N@$@`#QX*@6;%{6N72nlI#XQ1$9{5LA zM@OGJl$Rcs?-eUoN5re+4~-eVrVTk)Ctw!oeb+jYR4Kssk64FAx|p?9Dse;xTww9G z$5qHRFv|gHM4lFwa{6-AF3t`|6aMb*mB(Gf{^~p`kG$2gmJ1AcR-ISp#I;a9WtMYc z-9FuZbBn%nEPkE&?*A?^EDOqm8w%U>>$w^~_mx7p%-W>iWN81&BQBrR_W#xMG4c^; zFZlixMwQnMbr%|f>rJ;;X;+2+wiNJi4yVlTjfWK<3QXw88O$ zA4wEms%GV~Ra6AZJ&@9P9bNyI0ZJ&z4t&3E)YtnZPIl~`1aLnTq~tcX34_CcvtQsx zm~di$it)srL?RBq(gB_UG{9dUwmN#ld}KB5ltLBNj>P4)+8!dT*8e}P3MmF zf7l|+*Btry66Nan6^Y2|q#E(=ztw$gID4#7=F0Sm$^DeWdnoDJ1IvS-N^U)PHiR@g zv*(6P60K1fk~!-!AMlpfM&3h<&m?)gdkS?^r@{NEyLJP$(7?tewH+TGk-KSz2Vw6E z7FS*(QFk-1;Ut{ZnIik5G*+n&l%z@vx^}yDYM`($1$EOcc4ujrLXCCVdxEO0UWRuA zZ9i(4cuQlNGKBG&whgWH6A|jy;=gPK+`Rv*OLmowF__C6khAq`2(Bz=let-YK+l6| zD8<)F-7a)z9jMz`yTn~uV~;+(hrjS&(PHIcHT<0UwOfHagW*yK?#BnQT{{}qDy&7` zHskdoM#yuhO(h>SzuOu@R_~_c3M%1kWaUo8r9D`i&P^SBZU}S})s2=$S)4qFwv;n|RqqZK}oe-FkP>7qPj=K}c>)Bt7G{IxzbP#fx-jLbZh~=vA0L)a12Fw*6zOtP5zu{_OH2*rY zd)H})*!&7v8wVs!dZAwz8}9eCABuARfzJBZ6S{UyOSUcGna(~G#@EhXcZ@lrV9N=z zxELi>a)|5|JZKIrtq*l67moT-Dz)}d?u0Pn zEpV*DBsZ4aNR$yn8GZogxaA=LMO3LLof9&p2mD;U7pnJOzznId?c`H|#kM1$uf;$Y zw=6yAxx6Gz0*Y0grY76dGz8H~lK_``b$7R^r*hovP^>JKKhe1ANIe}q$#7Lx7WCjD`z?00S> zO-dE)Q<#gqUE0kysqZXUvw#|ycTUI^+m&tF)i!D3tmrbU8o2MjvvI+VYF8HdT}L6$ zNVg6HB3ZupTB%1gN?8Zi=XBLa1M3RzTyKkhTf1m)aJbIh4=>cUUk`JkyUnn}9p!Rl zy%|ak+)KCtgI(t3WdM{z0)98F8!oB4o3S4bFxYA^YoC5|9SUWafZGG-jZ@<7Z5Xr~ z!p9ZBXWk0)AE#0{8&8R+)zGRHgS<`B>J9yA9d5DnUu#*V!PWx%&L(>X8HcQ;JGwKL z&0^nw4b*3d_=YZd(QiVM`0x^#%T`3t;v&z2hkIcN%=UB?pg- z1|-x%|K0F3WO*uX)7|S#tUkQT-nj(s2HKbYH-lCt7oB8MZ`C;rh!6(5v^zQgkvH&@ zF2K!8ca9_9*YV$w6SEgl@^1kmGnGL-jQPHXgH5;WVR4g6B(*Sq!G{=Z(%-g6Rvi5r z_EiIO2^9Ey4JX`jq(@EPy;s5fKqgK(WZuciZhgg~<8S@^f8JqkB`A_=CkVA_*Z)6{ z+X`-d{eE5r6b4IseLs#{TNAwGil3L`Jbr)ttO>Q``dTx8Aa`+^5Pm9r*-eu|j4jAW S?PgB?K;(g0Qqb%G|N38w$lc5U literal 0 HcmV?d00001 diff --git a/src/assets/animatedIcons/CameraFlip.tgs b/src/assets/animatedIcons/CameraFlip.tgs new file mode 100644 index 0000000000000000000000000000000000000000..50f957bb3aade7d2c40a8051746853ebd57455fe GIT binary patch literal 1127 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 literal 0 HcmV?d00001 diff --git a/src/assets/animatedIcons/VoiceAllowTalk.tgs b/src/assets/animatedIcons/VoiceAllowTalk.tgs new file mode 100644 index 0000000000000000000000000000000000000000..f3144b7a265c72bdbb3632feaa4a258a954299a1 GIT binary patch literal 2687 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 literal 0 HcmV?d00001 diff --git a/src/assets/animatedIcons/VoiceMuted.tgs b/src/assets/animatedIcons/VoiceMuted.tgs new file mode 100644 index 0000000000000000000000000000000000000000..db3aa69af317d69eecb7ce3a396fb4bf28b91f75 GIT binary patch literal 1535 zcmVNCR$mMT;Z$nT`e)^W<5Wj{e8V!Ue~h=%)Pj{;7QY}n$=Y@IXx4}VtP9(sr{*A z&sX?rk<_=zq!snsd^LlFs$|XwO!$1P@{Siqb*KBB2)k!28m+qLgR93Hva=N*^Y z;MTk}DWX6=Tb7Qpv^t73lMlsFSJ7#U3GgN` zs*zzZa5@~QgzDI|LN#PVCDHshgZv!{Sw}ptrkh!`E?F~qU&4s_li?-|d#*KwSUc3!W`lecsZPSTS%<@$Pq0aq8T6w#cFs!mw;;A{#0YnJIi?FP`f zW0t++2-Ihp$Z_B-AQt4}A3hPw1xJ;4OCHL4zpcylhuOI1Z_4TBYJLvg8iS1m&=3<+>gGi4Fh@3o9ZBJ zymkb+`?fwCq>aoAvO1A0iO?LRfNYooep)ZzL63`gm>x<7_mDU+C>jB+zRX}4muNgW z4Knz~cd6Mpu{iDZr#(jN?>+W#P`blmMjI6ra6zvwgf8MY60OPnT5E>MO9K$l;kU%3KGckBFo(-yV`y25bwl z&hxrdR0RPvb>0L<$~*{^CKNj`5+vC(AkordZIrXt9qf>(2{p-U%zac$nAA)=zGUXe z`d%0VtZby|hQp$Upp4d^=4tika!K=+d<|q_zt76|vl9|ojbB%PoiFNH386f4ROsbm zUr}oxF5+CQT=cM3#j1;al`4(-U)HHRB_dU<_8AqcI6|p*SgJfANxoQm+)0Q>^s{gM z2=dO^LFw%2(X(e>;9! zU6^aNp>EPgt=p)OIb4Lu!C7L6m94=w^9z9qWu{$=~2_%oSit@g~w<4{ER4C$J=ZF*>-QC5@*ib+dhGZh004_|>U97B literal 0 HcmV?d00001 diff --git a/src/assets/animatedIcons/VoiceOutlined.tgs b/src/assets/animatedIcons/VoiceOutlined.tgs new file mode 100644 index 0000000000000000000000000000000000000000..2ee903eb77f3aee1af0c426cdb82facc2bad8202 GIT binary patch literal 3668 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 literal 0 HcmV?d00001 diff --git a/src/assets/animatedIcons/VoipGroupRemoved.tgs b/src/assets/animatedIcons/VoipGroupRemoved.tgs new file mode 100644 index 0000000000000000000000000000000000000000..c5e834f4ecd59ac6fb90d14d842ecd401c30ff0a GIT binary patch literal 2264 zcmV;}2q*U+iwFP!000001MON{Z`(!_{VN8aNn&U3>$mp1w2uW^^u_RlnmDx)+cG4j z7X8Zc%_Y9p z_#%rRzc!bFY}?${@5cvB`p^2|>4ImyeECv_ueRHp-L|>Bx^A2M)$cbO{{42meD{cN zYMb5f&84q0zFncSZMGY^@){G~Hf#Q~k}0pzZFuT$_(AX5W>c?`T!ZjC?!eV6(|f3e z=qtL$!1cc0)qcbMJCVx^vjOdyfOw_=3M!#X=SuJk3D&EG{9FOf)g{E|O7K=CJX=7Z zx&$i{xF>55MqF2)8H{~8Q$HhNP(roy0VEf!vnq2UGR)OzAa}nKtX^Lu(iu^f#&)T$w_P|9^*q zJlMeE_bPBHh9l z|331lsV#G^t~!#C1v&LL<`!QjWNn97xE&*L+x4*4^)8@cbV{9Sm@SK+TtI#@;Jvk; zo4zm3w@9bxxLNvaagkC`xe3T&jHSH<(;Emdy>(H?l;~R|aG9v(UXPinH=+inCm29j zOR#%MIyUtf$+BQl8M`T>+f2qfx10!zjc;v_#a#=eS68Fi{8wQbP!f>35v38A*%X+? z=EuD*1*jVkViQmuV79kWq%uS!LOR{-ZB%%=ici%AJ~ggoQO>JRtdDK%$<7{NnJ?3cZny%(?z0!40gxh zTHYmHk`OGZ&)6tBn|qg&xG}-GkewNI?g#-xSaj$^kyr`X+cSe#de!COd*U4(ld^^~ zX>eu`eY-3w+?RVBE8V&^F=+1X5vh96AES?b*TX zQj4hMbw%5kthPkUu*A|wa3Fxlg=fSJr?;?!WCbW83gI#$t7uGk?Ffqwm`BnPsAmd_ zNISG(bkH41!9{jV+fmyzZ+Gk#BN!1j<`SZ~Bj%`G2sBPH*kGMypTB4>cbhji@19%0 zEJ+9vpS=`cCNJ|vfJVHM_u!#Y*EF)1Lf*L1nYo{4nV1(bpvXLAT3y^%b-12fc&-jOg<8* zx&bSJsvEJ|6O)fth{**F9^mh-vWJhViv%PI`$Rg*FtJKc(Cw%c&=2DYrXqKWmmGQF z7!s#;p0_@( zkq*?XvNDjd#2qe#z$;^dam1Mp_9L&nVcBA`&|4Vj2yvPB3NaV0gk3EFHrZal(;Q9E zNhA&i9Y{x+bOcvg2#)bXFjMd_8VMxQrN?GAl8w_YoP((P(Zs(x`V8r5fsNo+VH^3&=Y)9Vt?nd6VbhMp#g%d4 zAEc=CIZza;SsR3+oDd4w!gJNkWnv=69Cb$EfMAZYQrjfQl_NV2#Abw*PzObE3rT&8 zc1GC=qij;wa@}H;l>~7toja_Zbin(8>+1ArvD9$)?QjdI0EM<5tp6X(ia*8eu zVaRm?b}Cc$r;#O7!HJyqrAF~*OH@mEDtKK*Zx6F?acFUScQ2_5XRqQ=v)b@aGSz2N zOmP__#gs4=yya^#6@{p9{v%!n;ouk&`v40wS<2r6e+e<+n1*95_TL<|nL#=@fflWm znAtqDQdJs^c7$n1Yy(c1Ie%0%b?kQd{2uJ5T1>G#C`)p3m+!WAFLUL&cb7g53~J$&v+lL$WS;LHkX z$_s!5XTMm9VKF8w@{d-uZb5#fO30cgPp>lpabMJ^k;#m~*r!e&wQ2#00iknH(RJ9xzH$8;t z{*OXZ%BZ0NNcEoKPy=LstLNWN_Q`7)^6uAntKF@r@i`hOQc-n`hYHy7XY zqxFmK;+xI-%@YRPzHBaEt?susLKyr^{ndRVqGvC2wE7l+s_{dCarh=#&=<+5ZGL)? zi?W>m-`(FIU(Pq1)zj_W3oMAo99_?jAO1hh*CQc+>dlk=MVh>BGFVbyJJ11c^4^;c zFvHsjCrwN&^!2exjLUX)>`E9M^TTHKxP7(WJT&Yx>H7M9{o-}^W_!bSKEhB7_{Fpl m#QKKA5%iW$yk5Lu$S+**Hc&&f! literal 0 HcmV?d00001 diff --git a/src/assets/animatedIcons/VoipInvite.tgs b/src/assets/animatedIcons/VoipInvite.tgs new file mode 100644 index 0000000000000000000000000000000000000000..7d93d0c1b9ce9d4b33610727e8593f1820ab96c1 GIT binary patch literal 2514 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 literal 0 HcmV?d00001 diff --git a/src/assets/animatedIcons/VoipRecordSave.tgs b/src/assets/animatedIcons/VoipRecordSave.tgs new file mode 100644 index 0000000000000000000000000000000000000000..362e19fa1b378dbe6a36ba883459bf309dba73fc GIT binary patch literal 3282 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 Q^h9j=7uGvaX^t=e06}PQ2LJ#7 literal 0 HcmV?d00001 diff --git a/src/assets/animatedIcons/VoipRecordStart.tgs b/src/assets/animatedIcons/VoipRecordStart.tgs new file mode 100644 index 0000000000000000000000000000000000000000..9f8fb7e8d465ddb63f48141a9965456d9add3052 GIT binary patch literal 3077 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 N<$p;Oqj91w004br$b0|* literal 0 HcmV?d00001 diff --git a/src/assets/fonts/icomoon.woff b/src/assets/fonts/icomoon.woff index 112b736f3174362fc54d4a448ead328ed06a9303..9cfcfcb6f764b8613b4792db71f7ef36c9b36e3a 100644 GIT binary patch delta 3821 zcmai1d2Afj8GmoyT)VS7J2T$Hj(BXULpzke&&TORR`}VWT^9QGA5jqP~P?eVWxnqBv zhfe5Q5A8H9!dvXF`Gq_0hQ56WF>cv`H@$iI_5(0)>rY;VMZ0M6_gm$K>APX$pMD00 zNw=KCmu{P0IEc^_@FMsDTF#Dn{&VE^V|T*$JaTy+ff^x=MIWx?#}+5>IDYKHB+Fy~710(13*m4ijw9hvP>`{}!_73ZxoovotqtH>)wXhFoXf%unh|2H)#5l5 zykhbIwu$O4V1RjruF;RJ>IT;;juV*pYn`P{s_Llfrc!5(^|HP&C;F69I^8Y%{c?9Y zT~d6am{MBfF+VFwrd;eQ<@tc%Q3Z*S+uEP!gB6VxS))QuAGOEF?V~5T-n>=`@+1+> z=V$wR`}_O)4ixfj4CC`DDKV5xZY!79b(PE8lBu9*D2gOBo#UBJ5m;PieddAn_Dv?o zi-K(W!gT}hY3%o!Z3HLC=`s5qYvLrE=1J7>0+cKgPz#I6D2lqM9Y-j$0uy}swV*`) zCxlMq8n_7sm=6GEd<+M}8!-5zr;j36$7+s%L-;Qv+8vIl%m*Ho_Yr?vEYS{MvkI6H z5R$$SFGwrM%JK@bk^sn34*>qETJP+vaUS*+05spno;pI&JaU?JnM7nMp!_!CWjz{s z@uaHj>d6<_77yzsp9+FMEk)`^dh;rR#%!$$2y(Aqg?FVqnM#I)lmh;2%jXaF_VxGo z_RZ!CZ473dj{spl&7K;!FWTd$8DK`6>1CerHWoOM@i4#8)r*>bo_HCNYdj3dBZ5pE zB%d&QfUtG60|*QBOu9@>;5e=`;DI^@zJ>wXo=6DT#)PZTlg)854ln>7lZ7^gQ{bEc zmT`qCV>?G_AIAduaPM)a`A_Hg-V~E8w@<`}c|{YH_Da`WS0#!C98!n5#CWt4W0Q>( zEHO;HtzwaQ7_TRLLx~lbmph=xJ485+CzYw37fbl#tvR^1#&1kibSAQ%4dlZ|2uBz^ zMF@WfAIix!HpE9O$=$I*!OzwRPhd$Y>JgIb6nu|SWCSeM2g{Z!EjGEgl*Qu|DT{&lJ^>33PzSLlcH<#5YV>e zDnd`HdsQ%a6Cbn7<-s6agXOYqIpy*YZ48ym*6_^C@X*Z65S|uS1NiE=79ASc#9!Um zXUSOdnf_E8Fu~*XWTH`nmxV`EeavdUVvXslrYmZ1E?dH5rEIoOQFRTMR(65yhgyQc z7TD3Y$`0KgpPLz4*}QpNSPkvW=CG9Z%dBM1*6eA6=e{Y3e$!XJZ+OTl!`6OAikaqQ zM@Ol%Se!J|F^>DYu34j_mZs}kF_-P7Vbhz<6*V34Xy5XyWZs6zO=b4-pJuIX>lT?Ql-f`83d zya|RjHLKRvtU)gr#6&^Yn%|;5+bxz_;E_}K zBUN+y3;7D{yHQhBtv_FYdcI#%@h#i?dbh(HY+qW?wbA;(p+Y`pYq|!8J|kkJ_Eu|k zc(c1$0q>%#HeVJ>bXif?>b2^W0dpieTeD+@{Gs~5C>?_FqKGG{RyBXl-bF>j;SgFr z&m!_73c$%(N4wDi1W*dlqBx}}%t0OlwN;3h6a~_Bc7Ot4mF4NU)|a{Qx%l znl(X`^hr%2jH6l{L6faYAvA4z>aQuq-PC!2!)tio2XQ;ml#wU7m_`K4mNKt)$#4~iE!{fK~V6!iXfbjLpt~cwp3U`)}y^Exd>>FjMqfM0eY^W zO0^p58g*gw^5Dkhbwk=-4m9}tuf)ugMR0{Mh3Mkf;7 zkQRi~HriF0uXMF5f~N4p@rib}m}F9W9S6&fvp31c@Cert6~b&y&W40Lc)~Jpo*xP4 z16-|@X=96t8{=E^xmY;2l%c@PYrWd zk^?e97+co@PQh8LR_%^ZQK72-D_I#RH4&}{BLkfb;=Fw01!n1L;`DG5x3nY$ki&B9A;aa%ps6bY}G4(T7GqA3Hu?9)Hr^@(g|-`Q1Gk N`Wt)Umk*!A{{>{0ezgDq delta 369 zcmdn6m+8tPCXsS~H#Y`G1|XOi!oUrtIVOr|*RM&=O)OwwV9Wptg@CYl*k#`I#A1-x z9w46siUrbhD${^sM;I8oG(fnZMx`txH8F*Oq5BO`jTs2b6<-w3017fN^r!&&V9b7q z#U~@Tqyi}B!N9{?oz(o#0sFr{XIa<3Sc~w@lIZ1 zZYof04Ny%R2+#E5{9TY=4AeK#1g?%5$YN&xGnt3cp0RDR52H6@+vXXJrEQxXr#xWR z2KgI^SM8W}Jf7d?D+4zRP=JBq?kcw=1pR+C3j_1&$vQJ7fX-oH04f1#Lk8WOV`hpo jO`bnnbn}kcyo`b#&I!&X&Uai=Tt&cYyEng^bBGZDk&IQI diff --git a/src/assets/fonts/icomoon.woff2 b/src/assets/fonts/icomoon.woff2 index 17707dddaf6a3930eea84db5e854ae91300e33f0..98d10e616194d690ef0b4e8d9c134e29ed69d686 100644 GIT binary patch literal 18992 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 LX_Ny8$_5_*=Z!WL diff --git a/src/bundles/calls.ts b/src/bundles/calls.ts new file mode 100644 index 000000000..cc2f86d7f --- /dev/null +++ b/src/bundles/calls.ts @@ -0,0 +1,2 @@ +export { default as GroupCall } from '../components/calls/group/GroupCall'; +export { default as ActiveCallHeader } from '../components/calls/ActiveCallHeader'; diff --git a/src/components/calls/ActiveCallHeader.async.tsx b/src/components/calls/ActiveCallHeader.async.tsx new file mode 100644 index 000000000..a0f4eaf0a --- /dev/null +++ b/src/components/calls/ActiveCallHeader.async.tsx @@ -0,0 +1,16 @@ +import React, { FC, memo } from '../../lib/teact/teact'; +import useModuleLoader from '../../hooks/useModuleLoader'; +import { Bundles } from '../../util/moduleLoader'; + +type OwnProps = { + groupCallId?: string; +}; + +const ActiveCallHeaderAsync: FC = (props) => { + const { groupCallId } = props; + const ActiveCallHeader = useModuleLoader(Bundles.Calls, 'ActiveCallHeader', !groupCallId); + + return ActiveCallHeader ? : undefined; +}; + +export default memo(ActiveCallHeaderAsync); diff --git a/src/components/calls/ActiveCallHeader.scss b/src/components/calls/ActiveCallHeader.scss new file mode 100644 index 000000000..e17ae83be --- /dev/null +++ b/src/components/calls/ActiveCallHeader.scss @@ -0,0 +1,22 @@ +.ActiveCallHeader { + position: absolute; + top: 0; + left: 0; + height: 2rem; + width: 100%; + z-index: 1; + + display: flex; + justify-content: center; + font-weight: 500; + font-size: 0.875rem; + color: #fff; + align-items: center; + padding: 0 1rem; + background: linear-gradient(90deg, rgb(82, 206, 93), rgb(0, 177, 192)); + transform: translateY(-100%); + + &.open { + transform: translateY(0); + } +} diff --git a/src/components/calls/ActiveCallHeader.tsx b/src/components/calls/ActiveCallHeader.tsx new file mode 100644 index 000000000..d2d912631 --- /dev/null +++ b/src/components/calls/ActiveCallHeader.tsx @@ -0,0 +1,63 @@ +import { GroupCallParticipant } from '../../lib/secret-sauce'; +import React, { + FC, memo, useEffect, +} from '../../lib/teact/teact'; +import { withGlobal } from '../../lib/teact/teactn'; + +import { GlobalActions } from '../../global/types'; +import { ApiGroupCall } from '../../api/types'; + +import { selectActiveGroupCall, selectGroupCallParticipant } from '../../modules/selectors/calls'; +import { pick } from '../../util/iteratees'; +import buildClassName from '../../util/buildClassName'; +import useLang from '../../hooks/useLang'; + +import './ActiveCallHeader.scss'; + +type StateProps = { + isGroupCallPanelHidden?: boolean; + meParticipant: GroupCallParticipant; + groupCall?: ApiGroupCall; +}; + +type DispatchProps = Pick; + +const ActiveCallHeader: FC = ({ + groupCall, + meParticipant, + isGroupCallPanelHidden, + toggleGroupCallPanel, +}) => { + const lang = useLang(); + + useEffect(() => { + document.body.classList.toggle('has-group-call-header', isGroupCallPanelHidden); + }, [isGroupCallPanelHidden]); + + if (!groupCall || !meParticipant) return undefined; + + return ( +

+ ); +}; + +export default memo(withGlobal( + (global): StateProps => { + return { + groupCall: selectActiveGroupCall(global), + isGroupCallPanelHidden: global.groupCalls.isGroupCallPanelHidden, + meParticipant: selectGroupCallParticipant(global, global.groupCalls.activeGroupCallId!, global.currentUserId!), + }; + }, + (setGlobal, actions) => pick(actions, [ + 'toggleGroupCallPanel', + ]), +)(ActiveCallHeader)); diff --git a/src/components/calls/group/GroupCall.async.tsx b/src/components/calls/group/GroupCall.async.tsx new file mode 100644 index 000000000..1317e4201 --- /dev/null +++ b/src/components/calls/group/GroupCall.async.tsx @@ -0,0 +1,14 @@ +import React, { FC, memo } from '../../../lib/teact/teact'; +import useModuleLoader from '../../../hooks/useModuleLoader'; +import { Bundles } from '../../../util/moduleLoader'; +import { OwnProps } from './GroupCall'; + +const GroupCallAsync: FC = (props) => { + const { groupCallId } = props; + const GroupCall = useModuleLoader(Bundles.Calls, 'GroupCall', !groupCallId); + + // eslint-disable-next-line react/jsx-props-no-spreading + return GroupCall ? : undefined; +}; + +export default memo(GroupCallAsync); diff --git a/src/components/calls/group/GroupCall.scss b/src/components/calls/group/GroupCall.scss new file mode 100644 index 000000000..0532036e6 --- /dev/null +++ b/src/components/calls/group/GroupCall.scss @@ -0,0 +1,348 @@ +.GroupCall { + .modal-content { + display: flex; + flex-direction: column; + align-items: center; + height: 37.5rem; + } + + .modal-dialog { + max-height: calc(100% - 4rem); + background: #181F27; + } + + .Menu .bubble { + --color-background: #232A34; + --color-chat-hover: #2F363E; + --color-item-active: #2F363E; + --color-text: #fff; + box-shadow: 0 0.25rem 0.5rem 0.125rem rgba(16, 16, 16, 0.3); + } + + // Compact menu items + .MenuItem { + padding: 0.75rem 1rem !important; + } + + &.single-column { + opacity: 1 !important; + + .modal-dialog { + max-width: 100% !important; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + margin-top: auto; + margin-bottom: 0; + transform: translate3d(0, 100%, 0); + transition: transform .3s ease, opacity .3s ease; + } + + .modal-backdrop { + opacity: 0; + transition: opacity 0.2s ease; + } + + &.open { + .modal-backdrop { + opacity: 1; + } + + .modal-dialog { + transform: translate3d(0, 0, 0); + } + } + } + + .header { + width: 100%; + display: flex; + align-items: center; + color: #fff; + margin-bottom: 0.5rem; + + h3 { + font-size: 1.25rem; + font-weight: 500; + margin: 0 auto 0 0.5rem; + } + } + + .videos { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + } + + .participants { + margin-top: 0.75rem; + background: #222B34; + border-radius: 0.75rem; + + .Loading { + padding: 2rem 0; + } + + .invite-btn { + padding: 0.25rem 0.75rem; + display: flex; + align-items: center; + border-radius: 0.75rem; + transition: .15s ease-out background-color; + cursor: pointer; + color: var(--color-text-secondary); + + &:hover { + background: #2F363E; + } + + .text { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + .icon { + display: flex; + justify-content: center; + align-items: center; + width: 2.75rem; + height: 2.75rem; + font-size: 1.5rem; + margin-right: 1rem; + } + } + } + + .scrollable { + overflow: auto; + padding-bottom: 2rem; + max-width: 37.5rem; + width: 100%; + } + + .buttons { + max-width: 37.5rem; + margin-top: auto; + display: flex; + align-items: center; + justify-content: space-around; + width: 100%; + position: relative; + height: 8.75rem; + + button { + cursor: pointer; + } + + &::before { + position: absolute; + content: ""; + width: 100%; + height: 2rem; + background: linear-gradient(0deg, #181F27, rgba(24, 31, 39, 0)); + z-index: 0; + top: -2rem; + } + + .button-wrapper { + width: 4rem; + display: flex; + flex-direction: column; + align-items: center; + + .button-text { + white-space: nowrap; + font-size: 0.75rem; + margin-top: 0.5rem; + color: #fff; + } + + &.microphone-wrapper { + width: 6rem; + + .button-text { + margin-top: 0.75rem; + font-size: 1rem; + } + } + } + + .Loading { + position: absolute; + transform: translate(0, -1.125rem); + + .Spinner { + --spinner-size: 6.5rem; + } + } + + .video-buttons { + display: flex; + flex-direction: column; + align-items: center; + } + + .small-button, .smaller-button { + outline: none; + border: 0; + background: #15415b; + border-radius: 50%; + width: 3rem; + height: 3rem; + color: #fff; + font-size: 1.375rem; + display: flex; + align-items: center; + justify-content: center; + transition: 0.25s ease-out background-color; + + &:hover { + background: #11364b; + } + } + + .small-button.camera.active { + background: #15415b; + + &:hover { + background: #11364b; + } + } + + .small-button.speaker { + background: #2B3A51; + + &.active { + background: #496092; + } + } + + .small-button.leave { + background: #5A2824; + + &:hover { + background: #49201d; + } + } + + .smaller-button { + width: 2.5rem; + height: 2.5rem; + margin-bottom: 0.5rem; + padding: 0; + } + } + + + &.landscape .scrollable { + display: flex; + flex-direction: row; + flex-grow: 1; + gap: 1rem; + align-items: flex-start; + max-width: 100%; + max-height: 100%; + } + + &.landscape .GroupCallParticipantVideo { + max-height: initial; + + video { + height: 100%; + } + } + + &.landscape .buttons { + position: absolute; + left: calc(50% - 15.625rem / 2); + transform: translateX(-50%); + width: auto; + gap: 1rem; + bottom: 4rem; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(10px); + border-radius: 1rem; + z-index: 5; + padding: 0.75rem 1rem; + height: auto; + + .button-text { + display: none; + } + + .video-buttons { + flex-direction: row; + gap: 1rem; + + .smaller-button { + margin-bottom: 0; + } + } + + .Loading { + transform: none; + .Spinner { + --spinner-size: 3.25rem; + } + } + + .MicrophoneButton { + canvas { + width: 2rem !important; + height: 2rem !important; + } + } + + .MicrophoneButton, .microphone-wrapper { + width: 3rem; + height: 3rem; + + .AnimatedSticker { + display: flex; + align-items: center; + justify-content: center; + } + } + + &::before { + display: none; + } + } + + &.landscape.no-sidebar .buttons { + left: calc(50%); + } + + &.landscape .streams { + width: 100%; + height: 100%; + } + + &.landscape .videos { + width: 100%; + height: 100%; + + display: grid; + --column-count: 1; + grid-template-columns: repeat(var(--column-count), 1fr); + grid-auto-rows: 1fr; + + .GroupCallParticipantVideo { + max-height: 100%; + width: 100%; + + .thumbnail-wrapper { + height: 100%; + } + } + + &.span-last-video .GroupCallParticipantVideo:last-child { + grid-column: span var(--column-count); + } + } + + &.landscape .participants { + width: 15.625rem; + margin-top: 0; + } +} diff --git a/src/components/calls/group/GroupCall.tsx b/src/components/calls/group/GroupCall.tsx new file mode 100644 index 000000000..e9847a844 --- /dev/null +++ b/src/components/calls/group/GroupCall.tsx @@ -0,0 +1,412 @@ +import { + GroupCallConnectionState, GroupCallParticipant as TypeGroupCallParticipant, + IS_SCREENSHARE_SUPPORTED, switchCameraInput, toggleSpeaker, +} from '../../../lib/secret-sauce'; +import React, { + FC, memo, useCallback, useEffect, useMemo, useRef, useState, +} from '../../../lib/teact/teact'; +import { withGlobal } from '../../../lib/teact/teactn'; +import '../../../modules/actions/calls'; + +import { GlobalActions } from '../../../global/types'; +import { IAnchorPosition } from '../../../types'; + +import { + IS_ANDROID, + IS_IOS, + IS_REQUEST_FULLSCREEN_SUPPORTED, + IS_SINGLE_COLUMN_LAYOUT, +} from '../../../util/environment'; +import { pick } from '../../../util/iteratees'; +import buildClassName from '../../../util/buildClassName'; +import { + selectGroupCall, + selectGroupCallParticipant, + selectIsAdminInActiveGroupCall, +} from '../../../modules/selectors/calls'; +import useFlag from '../../../hooks/useFlag'; +import useLang from '../../../hooks/useLang'; + +import Loading from '../../ui/Loading'; +import Button from '../../ui/Button'; +import DropdownMenu from '../../ui/DropdownMenu'; +import MenuItem from '../../ui/MenuItem'; +import Modal from '../../ui/Modal'; +import MicrophoneButton from './MicrophoneButton'; +import AnimatedIcon from '../../common/AnimatedIcon'; +import Checkbox from '../../ui/Checkbox'; +import GroupCallParticipantMenu from './GroupCallParticipantMenu'; +import GroupCallParticipantList from './GroupCallParticipantList'; +import GroupCallParticipantStreams from './GroupCallParticipantStreams'; + +import './GroupCall.scss'; + +const CAMERA_FLIP_PLAY_SEGMENT: [number, number] = [0, 10]; +const PARTICIPANT_HEIGHT = 60; + +export type OwnProps = { + groupCallId: string; +}; + +type StateProps = { + isGroupCallPanelHidden: boolean; + connectionState: GroupCallConnectionState; + title?: string; + meParticipant?: TypeGroupCallParticipant; + participantsCount?: number; + isSpeakerEnabled?: boolean; + isAdmin: boolean; + participants: Record; +}; + +type DispatchProps = Pick; + +const GroupCall: FC = ({ + groupCallId, + isGroupCallPanelHidden, + connectionState, + isSpeakerEnabled, + title, + meParticipant, + isAdmin, + participants, + + toggleGroupCallVideo, + toggleGroupCallPresentation, + leaveGroupCall, + toggleGroupCallPanel, + connectToActiveGroupCall, + playGroupCallSound, +}) => { + const lang = useLang(); + // eslint-disable-next-line no-null/no-null + const containerRef = useRef(null); + + const [isLeaving, setIsLeaving] = useState(false); + const [isFullscreen, openFullscreen, closeFullscreen] = useFlag(); + const [isSidebarOpen, openSidebar, closeSidebar] = useFlag(true); + const hasVideoParticipants = participants && Object.values(participants).some((l) => l.video || l.presentation); + const isLandscape = isFullscreen && !IS_SINGLE_COLUMN_LAYOUT && hasVideoParticipants; + + const [participantMenu, setParticipantMenu] = useState<{ + participant: TypeGroupCallParticipant; + anchor: IAnchorPosition; + } | undefined>(); + const [isParticipantMenuOpen, openParticipantMenu, closeParticipantMenu] = useFlag(); + + const [isConfirmLeaveModalOpen, openConfirmLeaveModal, closeConfirmLeaveModal] = useFlag(); + const [isEndGroupCallModal, setIsEndGroupCallModal] = useState(false); + const [shouldEndGroupCall, setShouldEndGroupCall] = useState(false); + + const hasVideo = meParticipant?.hasVideoStream; + const hasPresentation = meParticipant?.hasPresentationStream; + const isConnecting = connectionState !== 'connected'; + const canSelfUnmute = meParticipant?.canSelfUnmute; + const shouldRaiseHand = !canSelfUnmute && meParticipant?.isMuted; + + const handleOpenParticipantMenu = useCallback((anchor: HTMLDivElement, participant: TypeGroupCallParticipant) => { + const rect = anchor.getBoundingClientRect(); + const container = containerRef.current!; + + setParticipantMenu({ + anchor: { x: rect.left, y: rect.top - container.offsetTop + PARTICIPANT_HEIGHT }, + participant, + }); + + openParticipantMenu(); + }, [openParticipantMenu]); + + useEffect(() => { + if (connectionState === 'connected') { + playGroupCallSound({ sound: 'join' }); + } else if (connectionState === 'reconnecting') { + playGroupCallSound({ sound: 'connecting' }); + } + }, [connectionState, playGroupCallSound]); + + const handleShouldEndGroupCallChange = useCallback(() => { + setShouldEndGroupCall(!shouldEndGroupCall); + }, [shouldEndGroupCall]); + + const handleCloseConfirmLeaveModal = () => { + closeConfirmLeaveModal(); + setIsEndGroupCallModal(false); + }; + + const MainButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => { + return ({ onTrigger, isOpen }) => ( + + ); + }, [lang]); + + const handleToggleFullscreen = useCallback(() => { + if (!containerRef.current) return; + + if (isFullscreen) { + document.exitFullscreen().then(closeFullscreen); + } else { + containerRef.current.requestFullscreen().then(openFullscreen); + } + }, [closeFullscreen, isFullscreen, openFullscreen]); + + const handleToggleSidebar = () => { + if (isSidebarOpen) { + closeSidebar(); + } else { + openSidebar(); + } + }; + + const handleStreamsDoubleClick = useCallback(() => { + if (!IS_REQUEST_FULLSCREEN_SUPPORTED) return; + + if (!isFullscreen) { + closeSidebar(); + handleToggleFullscreen(); + } else { + handleToggleFullscreen(); + } + }, [closeSidebar, handleToggleFullscreen, isFullscreen]); + + const toggleFullscreen = useCallback(() => { + if (isFullscreen) { + closeFullscreen(); + } else { + openFullscreen(); + } + }, [closeFullscreen, isFullscreen, openFullscreen]); + + useEffect(() => { + if (!IS_REQUEST_FULLSCREEN_SUPPORTED) return undefined; + const container = containerRef.current; + if (!container) return undefined; + + container.addEventListener('fullscreenchange', toggleFullscreen); + + return () => { + container.removeEventListener('fullscreenchange', toggleFullscreen); + }; + }, [toggleFullscreen]); + + const handleClickVideoOrSpeaker = () => { + if (shouldRaiseHand) { + toggleSpeaker(); + } else { + toggleGroupCallVideo(); + } + }; + + useEffect(() => { + connectToActiveGroupCall(); + }, [connectToActiveGroupCall, groupCallId]); + + const endGroupCall = () => { + setIsEndGroupCallModal(true); + setShouldEndGroupCall(true); + openConfirmLeaveModal(); + if (isFullscreen) { + handleToggleFullscreen(); + } + }; + + const handleLeaveGroupCall = () => { + if (isAdmin && !isConfirmLeaveModalOpen) { + openConfirmLeaveModal(); + if (isFullscreen) { + handleToggleFullscreen(); + } + return; + } + playGroupCallSound({ sound: 'leave' }); + setIsLeaving(true); + closeConfirmLeaveModal(); + }; + + const handleCloseAnimationEnd = () => { + if (isLeaving) { + leaveGroupCall({ + shouldDiscard: shouldEndGroupCall, + }); + } + }; + + return ( + +
+

{title || lang('VoipGroupVoiceChat')}

+ {IS_REQUEST_FULLSCREEN_SUPPORTED && ( + + )} + {isLandscape && ( + + )} + {((IS_SCREENSHARE_SUPPORTED && !shouldRaiseHand) || isAdmin) && ( + + {IS_SCREENSHARE_SUPPORTED && !shouldRaiseHand && ( + + {lang(hasPresentation ? 'VoipChatStopScreenCapture' : 'VoipChatStartScreenCapture')} + + )} + {isAdmin && ( + + {lang('VoipGroupLeaveAlertEndChat')} + + )} + + )} + +
+ +
+ + + {(!isLandscape || isSidebarOpen) + && } +
+ + + +
+ {isConnecting && } + +
+
+ {hasVideo && (IS_ANDROID || IS_IOS) && ( + + )} + +
+ +
+ {lang(shouldRaiseHand ? 'VoipSpeaker' : 'VoipCamera')} +
+
+ + + +
+ + +
+ {lang('VoipGroupLeave')} +
+
+
+ + +

{lang(isEndGroupCallModal ? 'VoipGroupEndAlertText' : 'VoipGroupLeaveAlertText')}

+ {!isEndGroupCallModal && ( + + )} + + +
+
+ ); +}; + +export default memo(withGlobal( + (global, { groupCallId }): StateProps => { + const { + connectionState, title, isSpeakerDisabled, participants, participantsCount, + } = selectGroupCall(global, groupCallId)! || {}; + + return { + connectionState, + title, + isSpeakerEnabled: !isSpeakerDisabled, + participantsCount, + meParticipant: selectGroupCallParticipant(global, groupCallId, global.currentUserId!), + isGroupCallPanelHidden: !!global.groupCalls.isGroupCallPanelHidden, + isAdmin: selectIsAdminInActiveGroupCall(global), + participants, + }; + }, + (setGlobal, actions): DispatchProps => pick(actions, [ + 'toggleGroupCallVideo', + 'leaveGroupCall', + 'toggleGroupCallPresentation', + 'toggleGroupCallPanel', + 'connectToActiveGroupCall', + 'playGroupCallSound', + ]), +)(GroupCall)); diff --git a/src/components/calls/group/GroupCallParticipant.scss b/src/components/calls/group/GroupCallParticipant.scss new file mode 100644 index 000000000..edefdc39d --- /dev/null +++ b/src/components/calls/group/GroupCallParticipant.scss @@ -0,0 +1,78 @@ +.GroupCallParticipant { + position: relative; + display: flex; + flex-direction: row; + align-items: center; + color: #fff; + padding: 0.5rem 0.75rem; + border-radius: 0.75rem; + transition: .15s ease-out background-color; + cursor: pointer; + + &:hover { + background: #2F363E; + } + + audio { + display: none; + } + + .Avatar { + margin-right: 1rem; + } + + .info { + min-width: 0; + display: flex; + flex-direction: column; + + .name { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + .about { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + color: #848D94; + font-size: 0.75rem; + + &.blue { + color: #4DA6E0; + } + + &.green { + color: #57BC6C; + } + + &.red { + color: #FF706F; + } + } + } + + .microphone { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + width: 2.75rem; + height: 2.75rem; + margin-left: auto; + font-size: 1.5rem; + color: #FF706F; + } + + &.can-self-unmute { + .microphone { + color: #848D94; + } + } + + .streams { + cursor: pointer; + display: flex; + } +} diff --git a/src/components/calls/group/GroupCallParticipant.tsx b/src/components/calls/group/GroupCallParticipant.tsx new file mode 100644 index 000000000..908333a67 --- /dev/null +++ b/src/components/calls/group/GroupCallParticipant.tsx @@ -0,0 +1,101 @@ +import { GroupCallParticipant as TypeGroupCallParticipant, THRESHOLD } from '../../../lib/secret-sauce'; +import React, { + FC, memo, useMemo, useRef, +} from '../../../lib/teact/teact'; +import { withGlobal } from '../../../lib/teact/teactn'; + +import { ApiChat, ApiUser } from '../../../api/types'; + +import buildClassName from '../../../util/buildClassName'; +import { selectChat, selectUser } from '../../../modules/selectors'; +import useLang from '../../../hooks/useLang'; +import { GROUP_CALL_DEFAULT_VOLUME, GROUP_CALL_VOLUME_MULTIPLIER } from '../../../config'; + +import Avatar from '../../common/Avatar'; +import OutlinedMicrophoneIcon from './OutlinedMicrophoneIcon'; + +import './GroupCallParticipant.scss'; + +type OwnProps = { + participant: TypeGroupCallParticipant; + openParticipantMenu: (anchor: HTMLDivElement, participant: TypeGroupCallParticipant) => void; +}; + +type StateProps = { + user?: ApiUser; + chat?: ApiChat; +}; + +const GroupCallParticipant: FC = ({ + openParticipantMenu, + participant, + user, + chat, +}) => { + // eslint-disable-next-line no-null/no-null + const anchorRef = useRef(null); + const lang = useLang(); + + const { isSelf, isMutedByMe, isMuted } = participant; + const isSpeaking = (participant.amplitude || 0) > THRESHOLD; + const isRaiseHand = Boolean(participant.raiseHandRating); + + const handleOnClick = () => { + if (isSelf) return; + openParticipantMenu(anchorRef.current!, participant); + }; + + const [aboutText, aboutColor] = useMemo(() => { + if (isSelf) { + return [lang('ThisIsYou'), 'blue']; + } + if (isMutedByMe) { + return [lang('VoipGroupMutedForMe'), 'red']; + } + return isRaiseHand + ? [lang('WantsToSpeak'), 'blue'] + : (!isMuted && isSpeaking ? [ + participant.volume && participant.volume !== GROUP_CALL_DEFAULT_VOLUME + ? lang('SpeakingWithVolume', + (participant.volume / GROUP_CALL_VOLUME_MULTIPLIER).toString()) + .replace('%%', '%') : lang('Speaking'), + 'green', + ] + : (participant.about ? [participant.about, ''] : [lang('Listening'), 'blue'])); + }, [isSpeaking, participant.volume, lang, isSelf, isMutedByMe, isRaiseHand, isMuted, participant.about]); + + if (!user && !chat) { + return undefined; + } + + const name = user ? `${user.firstName || ''} ${user.lastName || ''}` : chat?.title; + + return ( +
+ +
+ {name} + {aboutText} +
+
+ +
+
+ ); +}; + +export default memo(withGlobal( + (global, { participant }): StateProps => { + return { + user: participant.isUser ? selectUser(global, participant.id) : undefined, + chat: !participant.isUser ? selectChat(global, participant.id) : undefined, + }; + }, +)(GroupCallParticipant)); diff --git a/src/components/calls/group/GroupCallParticipantList.tsx b/src/components/calls/group/GroupCallParticipantList.tsx new file mode 100644 index 000000000..4f46aa37a --- /dev/null +++ b/src/components/calls/group/GroupCallParticipantList.tsx @@ -0,0 +1,95 @@ +import { GroupCallParticipant as TypeGroupCallParticipant } from '../../../lib/secret-sauce'; +import React, { FC, memo, useMemo } from '../../../lib/teact/teact'; +import { withGlobal } from '../../../lib/teact/teactn'; + +import { GlobalActions } from '../../../global/types'; + +import { pick } from '../../../util/iteratees'; +import useLang from '../../../hooks/useLang'; +import { selectActiveGroupCall } from '../../../modules/selectors/calls'; +import useInfiniteScroll from '../../../hooks/useInfiniteScroll'; +import { selectChat } from '../../../modules/selectors'; + +import GroupCallParticipant from './GroupCallParticipant'; +import InfiniteScroll from '../../ui/InfiniteScroll'; + +type OwnProps = { + openParticipantMenu: (anchor: HTMLDivElement, participant: TypeGroupCallParticipant) => void; +}; + +type StateProps = { + participantsCount: number; + participants?: Record; + canInvite?: boolean; +}; + +type DispatchProps = Pick; + +const GroupCallParticipantList: FC = ({ + createGroupCallInviteLink, + loadMoreGroupCallParticipants, + participants, + participantsCount, + openParticipantMenu, + canInvite, +}) => { + const lang = useLang(); + + const participantsIds = useMemo(() => { + return Object.keys(participants || {}); + }, [participants]); + + const [viewportIds, getMore] = useInfiniteScroll( + loadMoreGroupCallParticipants, + participantsIds, + participantsIds.length >= participantsCount, + ); + + return ( +
+ {canInvite && ( +
+
+ +
+
{lang('VoipGroupInviteMember')}
+
+ )} + + + {viewportIds?.map( + (participantId) => ( + participants![participantId] && ( + + ) + ), + )} + + +
+ ); +}; + +export default memo(withGlobal( + (global): StateProps => { + const { participantsCount, participants, chatId } = selectActiveGroupCall(global) || {}; + const chat = chatId && selectChat(global, chatId); + + return { + participants, + participantsCount: participantsCount || 0, + canInvite: !!chat && !!chat.username, + }; + }, + (setGlobal, actions): DispatchProps => pick(actions, [ + 'createGroupCallInviteLink', + 'loadMoreGroupCallParticipants', + ]), +)(GroupCallParticipantList)); diff --git a/src/components/calls/group/GroupCallParticipantMenu.scss b/src/components/calls/group/GroupCallParticipantMenu.scss new file mode 100644 index 000000000..28001713d --- /dev/null +++ b/src/components/calls/group/GroupCallParticipantMenu.scss @@ -0,0 +1,98 @@ +@import '../../../styles/mixins'; + +.participant-menu { + position: absolute; + .bubble { + background: none; + border-radius: 0; + padding: 0; + border: none !important; + box-shadow: none !important; + overflow: visible; + color: #ffffff; + + .group { + box-shadow: 0 0.25rem 0.5rem 0.125rem rgba(16, 16, 16, 0.3); + overflow: hidden; + background: var(--color-background); + border-radius: var(--border-radius-default); + margin-bottom: 0.5rem; + } + } + + .volume-control { + height: 3rem; + + .info { + pointer-events: none; + position: relative; + z-index: 1; + height: 100%; + display: flex; + flex-direction: row; + align-items: center; + padding: 0.75rem 1rem; + + .AnimatedSticker { + margin-right: 2rem; + } + } + + &.high { + --range-color: #4DA6E0; + } + + &.normal { + --range-color: #57BC6C; + } + + &.medium { + --range-color: #CAA53B; + } + + &.low { + --range-color: #CB5757; + } + + position: relative; + overflow: hidden; + cursor: pointer; + + @mixin thumb-styles() { + border: none; + height: 3rem; + width: 1.5rem; + background: var(--range-color); + border-radius: var(--border-radius-default); + box-shadow: -13.5rem 0 0 12.75rem var(--range-color); + transition: 0.25s ease-in-out background-color, 0.25s ease-in-out box-shadow; + } + + @include reset-range(); + + // Apply custom styles + input[type="range"] { + height: 3rem; + position: absolute; + left: -1.5rem; + top: 0; + width: calc(100% + 3rem); + margin: 0; + z-index: 0; + + // Note that while we're repeating code here, that's necessary as you can't comma-separate these type of selectors. + // Browsers will drop the entire selector if it doesn't understand a part of it. + &::-webkit-slider-thumb { + @include thumb-styles(); + } + + &::-moz-range-thumb { + @include thumb-styles(); + } + + &::-ms-thumb { + @include thumb-styles(); + } + } + } +} diff --git a/src/components/calls/group/GroupCallParticipantMenu.tsx b/src/components/calls/group/GroupCallParticipantMenu.tsx new file mode 100644 index 000000000..b75186d26 --- /dev/null +++ b/src/components/calls/group/GroupCallParticipantMenu.tsx @@ -0,0 +1,239 @@ +import { GroupCallParticipant } from '../../../lib/secret-sauce'; +import React, { + FC, memo, useCallback, useEffect, useState, +} from '../../../lib/teact/teact'; +import { withGlobal } from '../../../lib/teact/teactn'; + +import { IAnchorPosition } from '../../../types'; +import { GlobalActions } from '../../../global/types'; + +import buildClassName from '../../../util/buildClassName'; +import useThrottle from '../../../hooks/useThrottle'; +import useFlag from '../../../hooks/useFlag'; +import useLang from '../../../hooks/useLang'; +import { selectIsAdminInActiveGroupCall } from '../../../modules/selectors/calls'; +import { pick } from '../../../util/iteratees'; +import { GROUP_CALL_DEFAULT_VOLUME, GROUP_CALL_VOLUME_MULTIPLIER } from '../../../config'; + +import Menu from '../../ui/Menu'; +import MenuItem from '../../ui/MenuItem'; +import AnimatedIcon from '../../common/AnimatedIcon'; +import DeleteMemberModal from '../../right/DeleteMemberModal'; + +import './GroupCallParticipantMenu.scss'; + +const SPEAKER_ICON_DISABLED_SEGMENT: [number, number] = [0, 17]; +const SPEAKER_ICON_ENABLED_SEGMENT: [number, number] = [17, 34]; + +type OwnProps = { + participant?: GroupCallParticipant; + closeDropdown: VoidFunction; + isDropdownOpen: boolean; + anchor?: IAnchorPosition; +}; + +type StateProps = { + isAdmin: boolean; +}; + +type DispatchProps = Pick; + +const VOLUME_ZERO = 0; +const VOLUME_LOW = 50; +const VOLUME_MEDIUM = 100; +const VOLUME_NORMAL = 150; + +const VOLUME_CHANGE_THROTTLE = 500; + +const SPEAKER_ICON_SIZE = 24; + +const GroupCallParticipantMenu: FC = ({ + participant, + closeDropdown, + isDropdownOpen, + anchor, + + isAdmin, + toggleGroupCallMute, + setGroupCallParticipantVolume, + toggleGroupCallPanel, + openChat, + requestToSpeak, +}) => { + const lang = useLang(); + const [isDeleteUserModalOpen, openDeleteUserModal, closeDeleteUserModal] = useFlag(); + + const id = participant?.id; + const { + isMutedByMe, isMuted, isSelf, canSelfUnmute, + } = participant || {}; + const isRaiseHand = Boolean(participant?.raiseHandRating); + const shouldRaiseHand = !canSelfUnmute && isMuted; + + const [localVolume, setLocalVolume] = useState( + isMutedByMe ? VOLUME_ZERO : ((participant?.volume || GROUP_CALL_DEFAULT_VOLUME) / GROUP_CALL_VOLUME_MULTIPLIER), + ); + + useEffect(() => { + setLocalVolume(isMutedByMe + ? VOLUME_ZERO + : ((participant?.volume || GROUP_CALL_DEFAULT_VOLUME) / GROUP_CALL_VOLUME_MULTIPLIER)); + // We only want to initialize local volume when switching participants and ignore following updates from server + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [id]); + + const runThrottled = useThrottle(VOLUME_CHANGE_THROTTLE); + + const handleRemove = useCallback((e: React.SyntheticEvent) => { + e.stopPropagation(); + openDeleteUserModal(); + closeDropdown(); + }, [openDeleteUserModal, closeDropdown]); + + const handleCancelRequestToSpeak = useCallback((e: React.SyntheticEvent) => { + e.stopPropagation(); + requestToSpeak({ + value: false, + }); + closeDropdown(); + }, [requestToSpeak, closeDropdown]); + + const handleMute = useCallback((e: React.SyntheticEvent) => { + e.stopPropagation(); + closeDropdown(); + + if (!isAdmin) { + setLocalVolume(isMutedByMe ? GROUP_CALL_DEFAULT_VOLUME / GROUP_CALL_VOLUME_MULTIPLIER : VOLUME_ZERO); + } + + toggleGroupCallMute({ + participantId: id, + value: isAdmin ? !shouldRaiseHand : !isMutedByMe, + }); + }, [closeDropdown, toggleGroupCallMute, id, isAdmin, shouldRaiseHand, isMutedByMe]); + + const handleOpenProfile = useCallback((e: React.SyntheticEvent) => { + e.stopPropagation(); + toggleGroupCallPanel(); + openChat({ + id, + }); + closeDropdown(); + }, [toggleGroupCallPanel, closeDropdown, openChat, id]); + + const isLocalVolumeZero = localVolume === VOLUME_ZERO; + const speakerIconPlaySegment = isLocalVolumeZero ? SPEAKER_ICON_DISABLED_SEGMENT : SPEAKER_ICON_ENABLED_SEGMENT; + + const handleChangeVolume = (e: React.ChangeEvent) => { + const value = Number(e.target.value); + setLocalVolume(value); + runThrottled(() => { + if (value === VOLUME_ZERO) { + toggleGroupCallMute({ + participantId: id, + value: true, + }); + } else { + setGroupCallParticipantVolume({ + participantId: id, + volume: Math.floor(value * GROUP_CALL_VOLUME_MULTIPLIER), + }); + } + }); + }; + + return ( +
+ + {!isSelf && !shouldRaiseHand && ( +
+
= VOLUME_LOW && localVolume < VOLUME_MEDIUM && 'medium', + localVolume >= VOLUME_MEDIUM && localVolume < VOLUME_NORMAL && 'normal', + localVolume >= VOLUME_NORMAL && 'high', + )} + > + +
+ + {localVolume}% +
+
+
+ )} +
+ {(isRaiseHand && isSelf) && ( + + {lang('VoipGroupCancelRaiseHand')} + + )} + {!isSelf && {lang('VoipGroupOpenProfile')}} + {!isSelf && ( + // TODO cross mic + + {isAdmin + ? lang(shouldRaiseHand ? 'VoipGroupAllowToSpeak' : 'VoipMute') + : lang(isMutedByMe ? 'VoipGroupUnmuteForMe' : 'VoipGroupMuteForMe')} + + )} + {!isSelf && isAdmin && ( + // TODO replace with hand + + {lang('VoipGroupUserRemove')} + + )} +
+
+ + {!isSelf && isAdmin && ( + + )} +
+ ); +}; + +export default memo(withGlobal( + (global): StateProps => { + return { + isAdmin: selectIsAdminInActiveGroupCall(global), + }; + }, + (setGlobal, actions): DispatchProps => pick(actions, [ + 'setGroupCallParticipantVolume', + 'toggleGroupCallMute', + 'openChat', + 'toggleGroupCallPanel', + 'requestToSpeak', + ]), +)(GroupCallParticipantMenu)); diff --git a/src/components/calls/group/GroupCallParticipantStreams.tsx b/src/components/calls/group/GroupCallParticipantStreams.tsx new file mode 100644 index 000000000..11a4aef49 --- /dev/null +++ b/src/components/calls/group/GroupCallParticipantStreams.tsx @@ -0,0 +1,105 @@ +import { GroupCallParticipant } from '../../../lib/secret-sauce'; +import React, { + FC, memo, useCallback, useMemo, useState, +} from '../../../lib/teact/teact'; +import { withGlobal } from '../../../lib/teact/teactn'; +import GroupCallParticipantVideo from './GroupCallParticipantVideo'; +import { selectActiveGroupCall } from '../../../modules/selectors/calls'; +import buildClassName from '../../../util/buildClassName'; + +type OwnProps = { + onDoubleClick?: VoidFunction; +}; + +type StateProps = { + participants?: Record; +}; + +type SelectedVideo = { + type: 'video' | 'presentation'; + id: string; +}; + +const GroupCallParticipantStreams: FC = ({ + participants, + onDoubleClick, +}) => { + const [selectedVideo, setSelectedVideo] = useState(undefined); + const presentationParticipants = useMemo(() => { + return Object.values(participants || {}).filter((l) => l.hasPresentationStream); + }, [participants]); + const videoParticipants = useMemo(() => { + return Object.values(participants || {}).filter((l) => l.hasVideoStream); + }, [participants]); + + const totalVideoCount = videoParticipants.length + presentationParticipants.length; + // TODO replace with more adequate solution. + // There's a max of 30 videos or so right now + const columnCount = totalVideoCount <= 2 ? 1 : ( + totalVideoCount <= 6 ? 2 : ( + totalVideoCount <= 9 ? 3 : 4 + ) + ); + + const shouldSpanLastVideo = totalVideoCount === 3 || (columnCount === 2 && totalVideoCount % 2 !== 0); + + const handleClickVideo = useCallback((id: string, type: 'video' | 'presentation') => { + if (!selectedVideo || (id !== selectedVideo.id || type !== selectedVideo.type)) { + setSelectedVideo({ + id, + type, + }); + } else { + setSelectedVideo(undefined); + } + }, [selectedVideo]); + + return ( +
+
+ {selectedVideo && ( + + )} + + {!selectedVideo ? presentationParticipants.map((participant) => ( + + )) : undefined} + {!selectedVideo ? videoParticipants.map((participant) => ( + + )) : undefined} +
+
+ ); +}; + +export default memo(withGlobal( + (global): StateProps => { + const { participants } = selectActiveGroupCall(global) || {}; + return { + participants, + }; + }, +)(GroupCallParticipantStreams)); diff --git a/src/components/calls/group/GroupCallParticipantVideo.scss b/src/components/calls/group/GroupCallParticipantVideo.scss new file mode 100644 index 000000000..49fcf1ad7 --- /dev/null +++ b/src/components/calls/group/GroupCallParticipantVideo.scss @@ -0,0 +1,125 @@ +.GroupCallParticipantVideo { + border-radius: 0.75rem; + overflow: hidden; + position: relative; + max-height: 12.875rem; + width: calc(50% - 0.25rem); + transition: 0.25s ease-out width; + cursor: pointer; + + .thumbnail-avatar { + position: absolute; + border-radius: 0; + width: 100%; + height: 100%; + transform: scale(1.1); + + img { + filter: blur(10px); + border-radius: 0; + object-fit: cover; + } + } + + &:last-child:nth-child(odd) { + width: 100%; + } + + &::before { + box-shadow: 0 0 0 3px transparent inset; + width: 100%; + height: 100%; + position: absolute; + display: block; + content: ""; + z-index: 5; + border-radius: 0.75rem; + transition: 0.25s ease-out box-shadow; + } + + &.active::before { + box-shadow: 0px 0px 0px 3px #78ee7e inset; + } + + .back-button { + position: absolute; + z-index: 5; + top: 0.75rem; + left: 0.75rem; + background: rgba(0, 0, 0, 0.3); + border: 0; + color: white; + border-radius: 1rem; + padding: 0.25rem 0.75rem; + display: flex; + align-items: center; + gap: 0.25rem; + transition: 0.25s ease-out opacity, 0.25s ease-out background-color; + opacity: 0; + cursor: pointer; + outline: none !important; + + &:hover { + background: rgba(0, 0, 0, 0.4); + } + } + + video { + display: block; + width: 100%; + } + + .video { + object-fit: contain; + height: 12.5rem; + position: relative; + } + + .thumbnail-wrapper { + position: absolute; + top: 50%; + left: 50%; + z-index: 0; + width: 100%; + transform: translate(-50%, -50%) scale(1.5); + background: black; + } + + .thumbnail { + filter: blur(10px) brightness(0.5); + object-fit: cover; + } + + .info { + position: absolute; + bottom: 0; + color: #fff; + display: flex; + align-items: center; + padding: 0 0.5rem 0.25rem; + width: 100%; + height: 2rem; + background: linear-gradient(0deg, #000, transparent); + transition: 0.25s ease-out opacity; + opacity: 0; + + .name { + margin-left: 0.5rem; + } + + .last-icon { + margin-left: auto; + } + } +} + +.videos:hover .GroupCallParticipantVideo { + + .info { + opacity: 1; + } + + .back-button { + opacity: 1; + } +} diff --git a/src/components/calls/group/GroupCallParticipantVideo.tsx b/src/components/calls/group/GroupCallParticipantVideo.tsx new file mode 100644 index 000000000..b2ab55096 --- /dev/null +++ b/src/components/calls/group/GroupCallParticipantVideo.tsx @@ -0,0 +1,86 @@ +import { getUserStreams, GroupCallParticipant as TypeGroupCallParticipant, THRESHOLD } from '../../../lib/secret-sauce'; +import React, { FC, memo, useCallback } from '../../../lib/teact/teact'; +import { withGlobal } from '../../../lib/teact/teactn'; + +import { ApiChat, ApiUser } from '../../../api/types'; + +import buildClassName from '../../../util/buildClassName'; +import { selectChat, selectUser } from '../../../modules/selectors'; +import useLang from '../../../hooks/useLang'; +import { ENABLE_THUMBNAIL_VIDEO } from '../../../config'; + +import Avatar from '../../common/Avatar'; + +import './GroupCallParticipantVideo.scss'; + +type OwnProps = { + participant: TypeGroupCallParticipant; + type: 'video' | 'presentation'; + onClick?: (id: string, type: 'video' | 'presentation') => void; + isFullscreen?: boolean; +}; + +type StateProps = { + user?: ApiUser; + chat?: ApiChat; + currentUserId?: string; + isActive?: boolean; +}; + +const GroupCallParticipantVideo: FC = ({ + type, + onClick, + user, + chat, + isActive, + isFullscreen, +}) => { + const lang = useLang(); + + const handleClick = useCallback(() => { + if (onClick) { + onClick(user?.id || chat!.id, type); + } + }, [chat, onClick, type, user?.id]); + + if (!user && !chat) return undefined; + + const streams = getUserStreams(user?.id || chat!.id); + + return ( +
+ {isFullscreen && ( + + )} + + {ENABLE_THUMBNAIL_VIDEO && ( +
+
+ )} +
+ ); +}; + +export default memo(withGlobal( + (global, { participant }): StateProps => { + return { + currentUserId: global.currentUserId, + user: participant.isUser ? selectUser(global, participant.id) : undefined, + chat: !participant.isUser ? selectChat(global, participant.id) : undefined, + isActive: (participant.amplitude || 0) > THRESHOLD, + }; + }, +)(GroupCallParticipantVideo)); diff --git a/src/components/calls/group/GroupCallTopPane.scss b/src/components/calls/group/GroupCallTopPane.scss new file mode 100644 index 000000000..ece4000ed --- /dev/null +++ b/src/components/calls/group/GroupCallTopPane.scss @@ -0,0 +1,89 @@ +.GroupCallTopPane { + position: absolute; + top: 100%; + left: 0; + right: 0; + height: 2.875rem; + overflow: hidden; + box-shadow: 0 2px 2px var(--color-light-shadow); + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 0.375rem 0.5rem 0.375rem 0.75rem; + background: var(--color-background); + z-index: -1; + cursor: pointer; + + &::before { + content: ""; + display: block; + position: absolute; + top: -.1875rem; + left: 0; + right: 0; + height: .125rem; + box-shadow: 0 .125rem .125rem var(--color-light-shadow); + } + + &.is-hidden { + display: none; + } + + @media (max-width: 600px) { + &.has-pinned-offset { + top: calc(100% + 2.875rem); + } + } + + .info { + display: flex; + flex-direction: column; + + .title { + font-size: 0.875rem; + color: var(--color-text); + } + + .participants { + font-size: 0.75rem; + color: var(--color-text-secondary); + } + } + + .avatars { + display: flex; + flex-direction: row; + align-items: center; + + .Avatar { + margin: 0 0 0 -0.75rem; + + &:first-child { + width: 2rem; + height: 2rem; + } + + &:not(:first-child) { + width: 2.25rem; + height: 2.25rem; + border: 0.125rem solid var(--color-background); + } + } + } + + .join { + height: 1.5rem; + border-radius: 1.5rem; + font-weight: 500; + padding: 1rem 1rem; + width: auto; + } +} + + +@media (min-width: 1440px) { + #Main.right-column-open .MiddleHeader .GroupCallTopPane { + width: calc(100% - var(--right-column-width)); + } +} diff --git a/src/components/calls/group/GroupCallTopPane.tsx b/src/components/calls/group/GroupCallTopPane.tsx new file mode 100644 index 000000000..e345ca802 --- /dev/null +++ b/src/components/calls/group/GroupCallTopPane.tsx @@ -0,0 +1,139 @@ +import React, { + FC, memo, useCallback, useEffect, useMemo, +} from '../../../lib/teact/teact'; +import { withGlobal } from '../../../lib/teact/teactn'; + +import { GlobalActions } from '../../../global/types'; +import { ApiChat, ApiGroupCall, ApiUser } from '../../../api/types'; + +import { pick } from '../../../util/iteratees'; +import { selectChatGroupCall } from '../../../modules/selectors/calls'; +import buildClassName from '../../../util/buildClassName'; +import { selectChat } from '../../../modules/selectors'; +import useLang from '../../../hooks/useLang'; + +import Button from '../../ui/Button'; +import Avatar from '../../common/Avatar'; + +import './GroupCallTopPane.scss'; + +type OwnProps = { + chatId: string; + hasPinnedOffset: boolean; +}; + +type StateProps = { + groupCall?: ApiGroupCall; + isActive: boolean; + usersById: Record; + chatsById: Record; +}; + +type DispatchProps = Pick; + +const GroupCallTopPane: FC = ({ + chatId, + isActive, + groupCall, + hasPinnedOffset, + joinGroupCall, + subscribeToGroupCallUpdates, + usersById, + chatsById, +}) => { + const lang = useLang(); + + const handleJoinGroupCall = useCallback(() => { + joinGroupCall({ + chatId, + }); + }, [joinGroupCall, chatId]); + + const participants = groupCall?.participants; + + const fetchedParticipants = useMemo(() => { + if (participants) { + return Object.values(participants).filter((_, i) => i < 3).map(({ id, isUser }) => { + if (isUser) { + if (!usersById[id]) { + return undefined; + } + return { user: usersById[id] }; + } else { + if (!chatsById[id]) { + return undefined; + } + return { chat: chatsById[id] }; + } + }).filter(Boolean); + } else return []; + }, [chatsById, participants, usersById]); + + useEffect(() => { + if (!groupCall?.id) return undefined; + if (!isActive && groupCall.isLoaded) return undefined; + + subscribeToGroupCallUpdates({ + id: groupCall.id, + subscribed: true, + }); + + return () => { + subscribeToGroupCallUpdates({ + id: groupCall.id, + subscribed: false, + }); + }; + }, [groupCall?.id, groupCall?.isLoaded, isActive, subscribeToGroupCallUpdates]); + + if (!groupCall) return undefined; + + return ( +
+
+ {lang('VoipGroupVoiceChat')} + {lang('Participants', groupCall.participantsCount || 0, 'i')} +
+
+ {fetchedParticipants.map((p) => { + if (!p) return undefined; + if (p.user) { + return ; + } else { + return ; + } + })} +
+ +
+ ); +}; + +export default memo(withGlobal( + (global, { chatId }) => { + const chat = selectChat(global, chatId)!; + const groupCall = selectChatGroupCall(global, chatId); + return { + groupCall, + usersById: global.users.byId, + chatsById: global.chats.byId, + activeGroupCallId: global.groupCalls.activeGroupCallId, + isActive: ((!groupCall ? (chat && chat.isCallNotEmpty && chat.isCallActive) + : (groupCall.participantsCount > 0 && groupCall.isLoaded))) + && (global.groupCalls.activeGroupCallId !== groupCall?.id), + }; + }, + (setGlobal, actions) => pick(actions, [ + 'joinGroupCall', + 'subscribeToGroupCallUpdates', + ]), +)(GroupCallTopPane)); diff --git a/src/components/calls/group/MicrophoneButton.scss b/src/components/calls/group/MicrophoneButton.scss new file mode 100644 index 000000000..ca1360937 --- /dev/null +++ b/src/components/calls/group/MicrophoneButton.scss @@ -0,0 +1,58 @@ +.MicrophoneButton { + display: flex; + justify-content: center; + align-items: center; + outline: none !important; + position: relative; + width: 6rem; + height: 6rem; + border: 0; + background: radial-gradient(100% 100% at 100% 0%, #00a0b9 0%, #33c659 55%, #33c659 100%); + border-radius: 50%; + font-size: 2rem; + color: #fff; + transition: 0.25s ease-out filter; + + &::before { + content: ""; + display: block; + position: absolute; + width: 8rem; + height: 8rem; + background: #64C166; + border-radius: 50%; + filter: blur(10px); + opacity: 0.2; + pointer-events: none; + + body.is-ios & { + display: none; + } + } + + &:hover { + filter: brightness(0.9); + } + + &.crossed { + background: radial-gradient(100% 100% at 100% 0%, #00AFFE 0%, #00AFFE 55%, #007FFF 100%); + + &::before { + background: #00AFFE; + } + } + + &.muted-by-admin { + background: radial-gradient(85.5% 103.5% at 87.5% 20.65%, #CE4D74 0%, #3D52DF 100%); + &::before { + background: #3D52DF; + } + } + + &.is-connecting, &.is-connecting:hover { + background: #222B34; + &::before { + background: transparent; + } + } +} diff --git a/src/components/calls/group/MicrophoneButton.tsx b/src/components/calls/group/MicrophoneButton.tsx new file mode 100644 index 000000000..c9e6d20c6 --- /dev/null +++ b/src/components/calls/group/MicrophoneButton.tsx @@ -0,0 +1,187 @@ +import { GroupCallConnectionState } from '../../../lib/secret-sauce'; +import React, { + FC, memo, useEffect, useMemo, useRef, useState, +} from '../../../lib/teact/teact'; +import { withGlobal } from '../../../lib/teact/teactn'; + +import { GlobalActions } from '../../../global/types'; + +import buildClassName from '../../../util/buildClassName'; +import { vibrateShort } from '../../../util/vibrate'; +import { pick } from '../../../util/iteratees'; +import usePrevious from '../../../hooks/usePrevious'; +import { selectActiveGroupCall, selectGroupCallParticipant } from '../../../modules/selectors/calls'; +import useLang from '../../../hooks/useLang'; + +import AnimatedIcon from '../../common/AnimatedIcon'; + +import './MicrophoneButton.scss'; + +const CONNECTION_STATE_DEFAULT = 'discarded'; + +type StateProps = { + connectionState?: GroupCallConnectionState; + hasRequestedToSpeak: boolean; + isMuted?: boolean; + canSelfUnmute?: boolean; + noAudioStream: boolean; +}; + +type DispatchProps = Pick; + +const REQUEST_TO_SPEAK_THROTTLE = 3000; +const HOLD_TO_SPEAK_TIME = 200; +const ICON_SIZE = 48; + +const MicrophoneButton: FC = ({ + noAudioStream, + canSelfUnmute, + isMuted, + hasRequestedToSpeak, + connectionState, + toggleGroupCallMute, + requestToSpeak, + playGroupCallSound, +}) => { + const lang = useLang(); + const muteMouseDownState = useRef('up'); + + const [isRequestingToSpeak, setIsRequestingToSpeak] = useState(false); + const isConnecting = connectionState !== 'connected'; + const shouldRaiseHand = !canSelfUnmute && isMuted; + const prevShouldRaiseHand = usePrevious(shouldRaiseHand); + + useEffect(() => { + if (prevShouldRaiseHand && !shouldRaiseHand) { + playGroupCallSound('allowTalk'); + } + }, [playGroupCallSound, prevShouldRaiseHand, shouldRaiseHand]); + + // Voice mini + // unmuted -> muted [69, 99] + // muted -> unmuted [36, 69] + // raise -> muted [0, 36] + // muted -> raise [99, 136] + // unmuted -> raise [136, 172] + // TODO should probably move to other component + const playSegment: [number, number] = useMemo(() => { + if (isRequestingToSpeak) { + const r = Math.floor(Math.random() * 100); + return (r < 32 ? [0, 120] + : (r < 64 ? [120, 240] + : (r < 97 ? [240, 420] + : [420, 540] + ) + ) + ); + } + if (!prevShouldRaiseHand && shouldRaiseHand) { + return noAudioStream ? [99, 135] : [136, 172]; + } + if (prevShouldRaiseHand && !shouldRaiseHand) { + return [0, 36]; + } + if (!shouldRaiseHand) { + return noAudioStream ? [69, 99] : [36, 69]; + } + return [0, 0]; + }, [prevShouldRaiseHand, isRequestingToSpeak, noAudioStream, shouldRaiseHand]); + + const animatedIconName = isRequestingToSpeak ? 'HandFilled' : 'VoiceMini'; + + const toggleMute = () => { + vibrateShort(); + toggleGroupCallMute(); + }; + + const handleMouseDownMute = () => { + if (shouldRaiseHand) { + if (isRequestingToSpeak) return; + vibrateShort(); + requestToSpeak(); + setIsRequestingToSpeak(true); + setTimeout(() => { + setIsRequestingToSpeak(false); + }, REQUEST_TO_SPEAK_THROTTLE); + return; + } + muteMouseDownState.current = 'down'; + if (noAudioStream) { + setTimeout(() => { + if (muteMouseDownState.current === 'down') { + muteMouseDownState.current = 'hold'; + toggleMute(); + } + }, HOLD_TO_SPEAK_TIME); + } + }; + + const handleMouseUpMute = () => { + if (shouldRaiseHand) { + return; + } + toggleMute(); + muteMouseDownState.current = 'up'; + }; + + const buttonText = useMemo(() => { + return lang( + hasRequestedToSpeak ? 'VoipMutedTapedForSpeak' : ( + shouldRaiseHand ? 'VoipMutedByAdmin' : ( + noAudioStream ? 'VoipUnmute' : 'VoipTapToMute' + ) + ), + ); + }, [hasRequestedToSpeak, noAudioStream, lang, shouldRaiseHand]); + + return ( +
+ +
+ {buttonText} +
+
+ ); +}; + +export default memo(withGlobal( + (global): StateProps => { + const groupCall = selectActiveGroupCall(global); + + const { connectionState } = groupCall || {}; + const meParticipant = groupCall && selectGroupCallParticipant(global, groupCall.id, global.currentUserId!); + + const { + raiseHandRating, hasAudioStream, canSelfUnmute, isMuted, + } = meParticipant || {}; + + return { + connectionState: connectionState || CONNECTION_STATE_DEFAULT, + hasRequestedToSpeak: Boolean(raiseHandRating), + noAudioStream: !hasAudioStream, + canSelfUnmute, + isMuted, + }; + }, + (setGlobal, actions): DispatchProps => pick(actions, [ + 'toggleGroupCallMute', + 'requestToSpeak', + 'playGroupCallSound', + ]), +)(MicrophoneButton)); diff --git a/src/components/calls/group/OutlinedMicrophoneIcon.tsx b/src/components/calls/group/OutlinedMicrophoneIcon.tsx new file mode 100644 index 000000000..a9dea3834 --- /dev/null +++ b/src/components/calls/group/OutlinedMicrophoneIcon.tsx @@ -0,0 +1,68 @@ +import { GroupCallParticipant, THRESHOLD } from '../../../lib/secret-sauce'; +import React, { FC, memo, useMemo } from '../../../lib/teact/teact'; +import AnimatedIcon from '../../common/AnimatedIcon'; +import usePrevious from '../../../hooks/usePrevious'; + +type OwnProps = { + participant: GroupCallParticipant; + noColor?: boolean; +}; + +const OutlinedMicrophoneIcon: FC = ({ + participant, + noColor, +}) => { + const { isMuted, isMutedByMe } = participant; + const isSpeaking = (participant.amplitude || 0) > THRESHOLD; + const isRaiseHand = Boolean(participant.raiseHandRating); + const prevIsRaiseHand = usePrevious(isRaiseHand); + const canSelfUnmute = !!participant?.canSelfUnmute; + const shouldRaiseHand = !canSelfUnmute && isMuted; + const prevIsMuted = usePrevious(isMuted); + + const playSegment: [number, number] = useMemo(() => { + if (isMuted && !prevIsMuted) { + return [43, 64]; + } + + if (!isMuted && prevIsMuted) { + return [22, 42]; + } + + if (isRaiseHand && !prevIsRaiseHand) { + return [65, 84]; + } + + if (!shouldRaiseHand && prevIsRaiseHand) { + return [0, 21]; + } + + // TODO cancel request to speak should play in reverse + // if (!isRaiseHand && prevIsRaiseHand) { + // return [84, 65]; + // } + + return isMuted ? [22, 23] : [43, 44]; + // eslint-disable-next-line + }, [isMuted, shouldRaiseHand, isRaiseHand]); + + const microphoneColor: [number, number, number] | undefined = useMemo(() => { + return noColor ? [0xff, 0xff, 0xff] : ( + isRaiseHand ? [0x4d, 0xa6, 0xe0] + : (shouldRaiseHand || isMutedByMe ? [0xFF, 0x70, 0x6F] : ( + isSpeaking ? [0x57, 0xBC, 0x6C] : [0x84, 0x8D, 0x94] + )) + ); + }, [noColor, isRaiseHand, shouldRaiseHand, isMutedByMe, isSpeaking]); + + return ( + + ); +}; + +export default memo(OutlinedMicrophoneIcon); diff --git a/src/components/common/AnimatedIcon.tsx b/src/components/common/AnimatedIcon.tsx new file mode 100644 index 000000000..a8dfa76b2 --- /dev/null +++ b/src/components/common/AnimatedIcon.tsx @@ -0,0 +1,42 @@ +import React, { + FC, memo, useEffect, useState, +} from '../../lib/teact/teact'; + +import getAnimationData, { ANIMATED_STICKERS_PATHS } from './helpers/animatedAssets'; + +import AnimatedSticker from './AnimatedSticker'; + +type OwnProps = { + name: keyof typeof ANIMATED_STICKERS_PATHS; + size: number; + playSegment?: [number, number]; + color?: [number, number, number]; +}; + +const AnimatedIcon: FC = ({ + size, + name, + playSegment, + color, +}) => { + const [iconData, setIconData] = useState>(); + + useEffect(() => { + getAnimationData(name).then(setIconData); + }, [name]); + + return ( + + ); +}; + +export default memo(AnimatedIcon); diff --git a/src/components/common/AnimatedSticker.tsx b/src/components/common/AnimatedSticker.tsx index e7cb68181..e6ffe5e67 100644 --- a/src/components/common/AnimatedSticker.tsx +++ b/src/components/common/AnimatedSticker.tsx @@ -19,6 +19,7 @@ type OwnProps = { quality?: number; isLowPriority?: boolean; onLoad?: NoneToVoidFunction; + color?: [number, number, number]; }; type RLottieClass = typeof import('../../lib/rlottie/RLottie').default; @@ -52,6 +53,7 @@ const AnimatedSticker: FC = ({ quality, isLowPriority, onLoad, + color, }) => { const [animation, setAnimation] = useState(); // eslint-disable-next-line no-null/no-null @@ -85,6 +87,7 @@ const AnimatedSticker: FC = ({ isLowPriority, }, onLoad, + color, ); if (speed) { @@ -105,7 +108,13 @@ const AnimatedSticker: FC = ({ }); }); } - }, [animation, animationData, id, isLowPriority, noLoop, onLoad, quality, size, speed]); + }, [color, animation, animationData, id, isLowPriority, noLoop, onLoad, quality, size, speed]); + + useEffect(() => { + if (!animation) return; + + animation.setColor(color); + }, [color, animation]); useEffect(() => { return () => { @@ -183,6 +192,13 @@ const AnimatedSticker: FC = ({ } }, [animation, play, playSegment, noLoop, playAnimation, pauseAnimation]); + useEffect(() => { + if (animation) { + animation.changeData(animationData); + playAnimation(); + } + }, [playAnimation, animation, animationData]); + useHeavyAnimationCheck(freezeAnimation, unfreezeAnimation); // Pausing frame may not happen in background // so we need to make sure it happens right after focusing, diff --git a/src/components/common/GroupCallLink.tsx b/src/components/common/GroupCallLink.tsx new file mode 100644 index 000000000..9eeac9354 --- /dev/null +++ b/src/components/common/GroupCallLink.tsx @@ -0,0 +1,41 @@ +import React, { FC, useCallback } from '../../lib/teact/teact'; +import { withGlobal } from '../../lib/teact/teactn'; + +import { GlobalActions } from '../../global/types'; +import { ApiGroupCall } from '../../api/types'; + +import { pick } from '../../util/iteratees'; +import buildClassName from '../../util/buildClassName'; + +import Link from '../ui/Link'; + +type OwnProps = { + className?: string; + groupCall?: Partial; + children: any; +}; + +type DispatchProps = Pick; + +const GroupCallLink: FC = ({ + className, groupCall, joinGroupCall, children, +}) => { + const handleClick = useCallback(() => { + if (groupCall) { + joinGroupCall({ id: groupCall.id, accessHash: groupCall.accessHash }); + } + }, [groupCall, joinGroupCall]); + + if (!groupCall) { + return children; + } + + return ( + {children} + ); +}; + +export default withGlobal( + undefined, + (setGlobal, actions): DispatchProps => pick(actions, ['joinGroupCall']), +)(GroupCallLink); diff --git a/src/components/common/helpers/animatedAssets.ts b/src/components/common/helpers/animatedAssets.ts index 34819ec53..b7dc7743f 100644 --- a/src/components/common/helpers/animatedAssets.ts +++ b/src/components/common/helpers/animatedAssets.ts @@ -16,6 +16,22 @@ import FoldersAll from '../../../assets/FoldersAll.tgs'; import FoldersNew from '../../../assets/FoldersNew.tgs'; // @ts-ignore import DiscussionGroups from '../../../assets/DiscussionGroupsDucks.tgs'; +// @ts-ignore +import CameraFlip from '../../../assets/animatedIcons/CameraFlip.tgs'; +// @ts-ignore +import HandFilled from '../../../assets/animatedIcons/HandFilled.tgs'; +// @ts-ignore +import HandOutline from '../../../assets/animatedIcons/HandOutline.tgs'; +// @ts-ignore +import Speaker from '../../../assets/animatedIcons/Speaker.tgs'; +// @ts-ignore +import VoiceAllowTalk from '../../../assets/animatedIcons/VoiceAllowTalk.tgs'; +// @ts-ignore +import VoiceMini from '../../../assets/animatedIcons/VoiceMini.tgs'; +// @ts-ignore +import VoiceMuted from '../../../assets/animatedIcons/VoiceMuted.tgs'; +// @ts-ignore +import VoiceOutlined from '../../../assets/animatedIcons/VoiceOutlined.tgs'; export const ANIMATED_STICKERS_PATHS = { MonkeyIdle, @@ -25,6 +41,14 @@ export const ANIMATED_STICKERS_PATHS = { FoldersAll, FoldersNew, DiscussionGroups, + CameraFlip, + HandFilled, + HandOutline, + Speaker, + VoiceAllowTalk, + VoiceMini, + VoiceMuted, + VoiceOutlined, }; export default function getAnimationData(name: keyof typeof ANIMATED_STICKERS_PATHS) { diff --git a/src/components/common/helpers/renderActionMessageText.tsx b/src/components/common/helpers/renderActionMessageText.tsx index c02adcca0..c2f1ae4f7 100644 --- a/src/components/common/helpers/renderActionMessageText.tsx +++ b/src/components/common/helpers/renderActionMessageText.tsx @@ -1,6 +1,8 @@ import React from '../../../lib/teact/teact'; -import { ApiChat, ApiMessage, ApiUser } from '../../../api/types'; +import { + ApiChat, ApiMessage, ApiUser, ApiGroupCall, +} from '../../../api/types'; import { LangFn } from '../../../hooks/useLang'; import { getChatTitle, @@ -17,6 +19,7 @@ import renderText from './renderText'; import UserLink from '../UserLink'; import MessageLink from '../MessageLink'; import ChatLink from '../ChatLink'; +import GroupCallLink from '../GroupCallLink'; interface ActionMessageTextOptions { maxTextLength?: number; @@ -39,7 +42,7 @@ export function renderActionMessageText( return []; } const { - text, translationValues, amount, currency, + text, translationValues, amount, currency, call, } = message.content.action; const content: TextPart[] = []; const textOptions: ActionMessageTextOptions = { ...options, maxTextLength: 32 }; @@ -115,6 +118,10 @@ export function renderActionMessageText( return content.join('').trim(); } + if (call) { + return renderGroupCallContent(call, content); + } + return content; } @@ -172,6 +179,14 @@ function renderOriginContent(lang: LangFn, origin: ApiUser | ApiChat, asPlain?: : renderChatContent(lang, origin as ApiChat, asPlain); } +function renderGroupCallContent(groupCall: Partial, text: TextPart[]): string | TextPart | undefined { + return ( + + {text} + + ); +} + function renderUserContent(sender: ApiUser, asPlain?: boolean): string | TextPart | undefined { const text = trimText(getUserFullName(sender)); diff --git a/src/components/left/main/Chat.scss b/src/components/left/main/Chat.scss index 2e6d1c824..621e9e3d6 100644 --- a/src/components/left/main/Chat.scss +++ b/src/components/left/main/Chat.scss @@ -76,6 +76,7 @@ } .status { + position: relative; flex-shrink: 0; } diff --git a/src/components/left/main/Chat.tsx b/src/components/left/main/Chat.tsx index 9d33640c4..2ff43451e 100644 --- a/src/components/left/main/Chat.tsx +++ b/src/components/left/main/Chat.tsx @@ -51,6 +51,7 @@ import DeleteChatModal from '../../common/DeleteChatModal'; import ListItem from '../../ui/ListItem'; import Badge from './Badge'; import ChatFolderModal from '../ChatFolderModal.async'; +import ChatCallStatus from './ChatCallStatus'; import './Chat.scss'; @@ -285,6 +286,9 @@ const Chat: FC = ({ isSavedMessages={privateChatUser?.isSelf} lastSyncTime={lastSyncTime} /> + {chat.isCallActive && chat.isCallNotEmpty && ( + + )}
diff --git a/src/components/left/main/ChatCallStatus.tsx b/src/components/left/main/ChatCallStatus.tsx index 6d1478ad3..f6ba47ad0 100644 --- a/src/components/left/main/ChatCallStatus.tsx +++ b/src/components/left/main/ChatCallStatus.tsx @@ -1,6 +1,8 @@ import React, { FC, memo } from '../../../lib/teact/teact'; import buildClassName from '../../../util/buildClassName'; +import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment'; + import './ChatCallStatus.scss'; type OwnProps = { @@ -13,7 +15,12 @@ const ChatCallStatus: FC = ({ isActive, }) => { return ( -
+
diff --git a/src/components/main/Main.scss b/src/components/main/Main.scss index fd9e82448..e8c053f02 100644 --- a/src/components/main/Main.scss +++ b/src/components/main/Main.scss @@ -18,6 +18,14 @@ } } +.has-group-call-header { + --group-call-header-height: 2rem; + #LeftColumn, #MiddleColumn, #RightColumn-wrapper { + height: calc(100% - 2rem); + margin-top: 2rem; + } +} + #LeftColumn { min-width: 12rem; width: 33vw; diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index d1b8ef474..821b70e25 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -44,6 +44,8 @@ import ForwardPicker from './ForwardPicker.async'; import SafeLinkModal from './SafeLinkModal.async'; import HistoryCalendar from './HistoryCalendar.async'; import StickerSetModal from '../common/StickerSetModal.async'; +import GroupCall from '../calls/group/GroupCall.async'; +import ActiveCallHeader from '../calls/ActiveCallHeader.async'; import './Main.scss'; @@ -60,6 +62,7 @@ type StateProps = { isHistoryCalendarOpen: boolean; shouldSkipHistoryAnimations?: boolean; openedStickerSetShortName?: string; + activeGroupCallId?: string; isServiceChatReady?: boolean; animationLevel: number; language?: LangCode; @@ -88,6 +91,7 @@ const Main: FC = ({ hasNotifications, hasDialogs, audioMessage, + activeGroupCallId, safeLinkModalUrl, isHistoryCalendarOpen, shouldSkipHistoryAnimations, @@ -271,6 +275,12 @@ const Main: FC = ({ onClose={handleStickerSetModalClose} stickerSetShortName={openedStickerSetShortName} /> + {activeGroupCallId && ( + <> + + + + )}
); @@ -319,6 +329,7 @@ export default memo(withGlobal( shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations, openedStickerSetShortName: global.openedStickerSetShortName, isServiceChatReady: selectIsServiceChatReady(global), + activeGroupCallId: global.groupCalls.activeGroupCallId, animationLevel, language, wasTimeFormatSetManually, diff --git a/src/components/middle/HeaderActions.tsx b/src/components/middle/HeaderActions.tsx index 7c9812855..2b68ee308 100644 --- a/src/components/middle/HeaderActions.tsx +++ b/src/components/middle/HeaderActions.tsx @@ -11,9 +11,9 @@ import { GlobalActions, MessageListType } from '../../global/types'; import { MAIN_THREAD_ID } from '../../api/types'; import { IAnchorPosition } from '../../types'; -import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment'; +import { ARE_CALLS_SUPPORTED, IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment'; import { pick } from '../../util/iteratees'; -import { isChatChannel, isChatSuperGroup } from '../../modules/helpers'; +import { isChatBasicGroup, isChatChannel, isChatSuperGroup } from '../../modules/helpers'; import { selectChat, selectChatBot, @@ -45,6 +45,8 @@ interface StateProps { canSearch?: boolean; canMute?: boolean; canLeave?: boolean; + canEnterVoiceChat?: boolean; + canCreateVoiceChat?: boolean; } type DispatchProps = Pick; @@ -63,6 +65,8 @@ const HeaderActions: FC = ({ canSearch, canMute, canLeave, + canEnterVoiceChat, + canCreateVoiceChat, isRightColumnShown, canExpandActions, joinChannel, @@ -191,6 +195,8 @@ const HeaderActions: FC = ({ canSearch={canSearch} canMute={canMute} canLeave={canLeave} + canEnterVoiceChat={canEnterVoiceChat} + canCreateVoiceChat={canCreateVoiceChat} onSubscribeChannel={handleSubscribeClick} onSearchClick={handleSearchClick} onClose={handleHeaderMenuClose} @@ -226,6 +232,9 @@ export default memo(withGlobal( const canSearch = isMainThread || isDiscussionThread; const canMute = isMainThread && !isChatWithSelf && !canSubscribe; const canLeave = isMainThread && !canSubscribe; + const canEnterVoiceChat = ARE_CALLS_SUPPORTED && chat && chat.isCallActive; + const canCreateVoiceChat = ARE_CALLS_SUPPORTED && chat && !chat.isCallActive + && (chat.adminRights?.manageCall || (chat.isCreator && isChatBasicGroup(chat))); return { noMenu: false, @@ -237,6 +246,8 @@ export default memo(withGlobal( canSearch, canMute, canLeave, + canEnterVoiceChat, + canCreateVoiceChat, }; }, (setGlobal, actions): DispatchProps => pick(actions, [ diff --git a/src/components/middle/HeaderMenuContainer.tsx b/src/components/middle/HeaderMenuContainer.tsx index 84d6c812e..494f60173 100644 --- a/src/components/middle/HeaderMenuContainer.tsx +++ b/src/components/middle/HeaderMenuContainer.tsx @@ -27,7 +27,8 @@ import DeleteChatModal from '../common/DeleteChatModal'; import './HeaderMenuContainer.scss'; type DispatchProps = Pick; export type OwnProps = { @@ -43,6 +44,8 @@ export type OwnProps = { canSearch?: boolean; canMute?: boolean; canLeave?: boolean; + canEnterVoiceChat?: boolean; + canCreateVoiceChat?: boolean; onSubscribeChannel: () => void; onSearchClick: () => void; onClose: () => void; @@ -70,6 +73,8 @@ const HeaderMenuContainer: FC = ({ canSearch, canMute, canLeave, + canEnterVoiceChat, + canCreateVoiceChat, chat, isPrivate, isMuted, @@ -84,6 +89,8 @@ const HeaderMenuContainer: FC = ({ enterMessageSelectMode, sendBotCommand, restartBot, + joinGroupCall, + createGroupCall, openLinkedChat, addContact, }) => { @@ -121,6 +128,20 @@ const HeaderMenuContainer: FC = ({ closeMenu(); }, [chatId, closeMenu, isMuted, updateChatMutedState]); + const handleEnterVoiceChatClick = useCallback(() => { + if (canCreateVoiceChat) { + // TODO show popup to schedule + createGroupCall({ + chatId, + }); + } else { + joinGroupCall({ + chatId, + }); + } + closeMenu(); + }, [closeMenu, canCreateVoiceChat, chatId, joinGroupCall, createGroupCall]); + const handleLinkedChatClick = useCallback(() => { openLinkedChat({ id: chatId }); closeMenu(); @@ -211,6 +232,14 @@ const HeaderMenuContainer: FC = ({ {lang(isMuted ? 'ChatsUnmute' : 'ChatsMute')} )} + {(canEnterVoiceChat || canCreateVoiceChat) && ( + + {lang(canCreateVoiceChat ? 'StartVoipChat' : 'VoipGroupJoinCall')} + + )} {hasLinkedChat && ( ( 'enterMessageSelectMode', 'sendBotCommand', 'restartBot', + 'joinGroupCall', + 'createGroupCall', 'openLinkedChat', 'addContact', ]), diff --git a/src/components/middle/MiddleHeader.scss b/src/components/middle/MiddleHeader.scss index dfe436a5e..daa2f48e4 100644 --- a/src/components/middle/MiddleHeader.scss +++ b/src/components/middle/MiddleHeader.scss @@ -302,8 +302,9 @@ .Avatar { margin-right: .625rem; - width: 2.5rem; - height: 2.5rem; + // TODO For some reason webpack imports `Audio.scss` second time when loading calls bundle + width: 2.5rem !important; + height: 2.5rem !important; font-size: 1.0625rem; } diff --git a/src/components/middle/MiddleHeader.tsx b/src/components/middle/MiddleHeader.tsx index 8842f412f..11bda9429 100644 --- a/src/components/middle/MiddleHeader.tsx +++ b/src/components/middle/MiddleHeader.tsx @@ -61,6 +61,7 @@ import Button from '../ui/Button'; import HeaderActions from './HeaderActions'; import HeaderPinnedMessage from './HeaderPinnedMessage'; import AudioPlayer from './AudioPlayer'; +import GroupCallTopPane from '../calls/group/GroupCallTopPane'; import './MiddleHeader.scss'; @@ -404,6 +405,14 @@ const MiddleHeader: FC = ({ {renderInfo} + + {shouldRenderPinnedMessage && renderingPinnedMessage && ( = ({ onChange={handlePermissionChange} />
+
+ +
{!isChannel && (
void; onCloseAnimationEnd?: () => void; onEnter?: () => void; + dialogRef?: RefObject; }; type StateProps = { @@ -38,6 +40,7 @@ type StateProps = { }; const Modal: FC = ({ + dialogRef, title, className, isOpen, @@ -78,7 +81,6 @@ const Modal: FC = ({ useEffectWithPrevDeps(([prevIsOpen]) => { document.body.classList.toggle('has-open-dialog', isOpen); - if (isOpen || (!isOpen && prevIsOpen !== undefined)) { dispatchHeavyAnimationEvent(ANIMATION_DURATION); } @@ -138,7 +140,7 @@ const Modal: FC = ({ >
-
+
{renderHeader()}
{children} diff --git a/src/components/ui/RangeSlider.scss b/src/components/ui/RangeSlider.scss index af1d8a56b..6f0ad9ed4 100644 --- a/src/components/ui/RangeSlider.scss +++ b/src/components/ui/RangeSlider.scss @@ -1,3 +1,5 @@ +@import '../../styles/mixins'; + @mixin thumb-styles() { background: var(--slider-color); border: none; @@ -72,43 +74,7 @@ } // Reset range input browser styles - input[type="range"] { - -webkit-appearance: none; - display: block; - width: 100%; - height: 0.75rem; - margin-bottom: 0.5rem; - background: transparent; - - &:focus { - outline: none; - } - - &::-ms-track { - width: 100%; - cursor: pointer; - - background: transparent; - border-color: transparent; - color: transparent; - } - - &::-webkit-slider-thumb { - -webkit-appearance: none; - } - - &::-moz-slider-thumb { - -moz-appearance: none; - } - - &::-webkit-slider-runnable-track { - cursor: pointer; - } - - &::-moz-range-track, &::-moz-range-progress { - cursor: pointer; - } - } + @include reset-range(); // Apply custom styles input[type="range"] { diff --git a/src/config.ts b/src/config.ts index 3d67e975b..cb23399c2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -58,6 +58,7 @@ export const BLOCKED_LIST_LIMIT = 100; export const PROFILE_PHOTOS_LIMIT = 40; export const PROFILE_SENSITIVE_AREA = 500; export const COMMON_CHATS_LIMIT = 100; +export const GROUP_CALL_PARTICIPANTS_LIMIT = 100; export const TOP_CHAT_MESSAGES_PRELOAD_LIMIT = 20; export const ALL_CHATS_PRELOAD_DISABLED = false; @@ -170,3 +171,8 @@ export const LIGHT_THEME_BG_COLOR = '#A2AF8E'; export const DARK_THEME_BG_COLOR = '#0F0F0F'; export const DARK_THEME_PATTERN_COLOR = '#0a0a0a8c'; export const DEFAULT_PATTERN_COLOR = 'rgba(90, 110, 70, 0.6)'; + +// Group calls +export const GROUP_CALL_VOLUME_MULTIPLIER = 100; +export const GROUP_CALL_DEFAULT_VOLUME = 100 * GROUP_CALL_VOLUME_MULTIPLIER; +export const ENABLE_THUMBNAIL_VIDEO = false; diff --git a/src/global/cache.ts b/src/global/cache.ts index 2bc9908e5..61ce53f10 100644 --- a/src/global/cache.ts +++ b/src/global/cache.ts @@ -189,6 +189,10 @@ function migrateCache(cached: GlobalState, initialState: GlobalState) { if (cached.audioPlayer.playbackRate === undefined) { cached.audioPlayer.playbackRate = DEFAULT_PLAYBACK_RATE; } + + if (cached.groupCalls === undefined) { + cached.groupCalls = initialState.groupCalls; + } } function updateCache() { diff --git a/src/global/initial.ts b/src/global/initial.ts index 7abdc61b2..9c5453a72 100644 --- a/src/global/initial.ts +++ b/src/global/initial.ts @@ -44,6 +44,10 @@ export const INITIAL_STATE: GlobalState = { messageLists: [], }, + groupCalls: { + byId: {}, + }, + scheduledMessages: { byChatId: {}, }, diff --git a/src/global/types.ts b/src/global/types.ts index 559cc9a25..4a6126016 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -21,6 +21,7 @@ import { ApiInviteInfo, ApiCountryCode, ApiCountry, + ApiGroupCall, } from '../api/types'; import { FocusDirection, @@ -163,6 +164,12 @@ export type GlobalState = { }; }; + groupCalls: { + byId: Record; + activeGroupCallId?: string; + isGroupCallPanelHidden?: boolean; + }; + scheduledMessages: { byChatId: Record; @@ -539,7 +546,12 @@ export type ActionTypes = ( // payment 'openPaymentModal' | 'closePaymentModal' | 'addPaymentError' | 'validateRequestedInfo' | 'setPaymentStep' | 'sendPaymentForm' | 'getPaymentForm' | 'getReceipt' | - 'sendCredentialsInfo' | 'setInvoiceMessageInfo' | 'clearPaymentError' | 'clearReceipt' + 'sendCredentialsInfo' | 'setInvoiceMessageInfo' | 'clearPaymentError' | 'clearReceipt' | + // calls + 'joinGroupCall' | 'toggleGroupCallMute' | 'toggleGroupCallPresentation' | 'leaveGroupCall' | + 'toggleGroupCallVideo' | 'requestToSpeak' | 'setGroupCallParticipantVolume' | 'toggleGroupCallPanel' | + 'createGroupCall' | 'joinVoiceChatByLink' | 'subscribeToGroupCallUpdates' | 'createGroupCallInviteLink' | + 'loadMoreGroupCallParticipants' | 'connectToActiveGroupCall' | 'playGroupCallSound' ); export type GlobalActions = Record void>; diff --git a/src/lib/gramjs/tl/apiTl.js b/src/lib/gramjs/tl/apiTl.js index 2a225c36e..85d1cbb35 100644 --- a/src/lib/gramjs/tl/apiTl.js +++ b/src/lib/gramjs/tl/apiTl.js @@ -1100,4 +1100,15 @@ langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDiffer langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; langpack.getLanguages#42c6978f lang_pack:string = Vector; folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; +phone.createGroupCall#48cdc6d8 flags:# peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates; +phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates; +phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; +phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; +phone.getGroupCall#41845db call:InputGroupCall limit:int = phone.GroupCall; +phone.getGroupParticipants#c558d8ab call:InputGroupCall ids:Vector sources:Vector offset:string limit:int = phone.GroupParticipants; +phone.editGroupCallParticipant#a5273abf flags:# call:InputGroupCall participant:InputPeer muted:flags.0?Bool volume:flags.1?int raise_hand:flags.2?Bool video_stopped:flags.3?Bool video_paused:flags.4?Bool presentation_paused:flags.5?Bool = Updates; +phone.exportGroupCallInvite#e6aa647f flags:# can_self_unmute:flags.0?true call:InputGroupCall = phone.ExportedGroupCallInvite; +phone.toggleGroupCallStartSubscription#219c34e6 call:InputGroupCall subscribed:Bool = Updates; +phone.joinGroupCallPresentation#cbea6bc4 call:InputGroupCall params:DataJSON = Updates; +phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates; `; \ No newline at end of file diff --git a/src/lib/gramjs/tl/static/api.reduced.tl b/src/lib/gramjs/tl/static/api.reduced.tl index bf5914323..9f3d06c1d 100644 --- a/src/lib/gramjs/tl/static/api.reduced.tl +++ b/src/lib/gramjs/tl/static/api.reduced.tl @@ -1101,4 +1101,15 @@ langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDiffer langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; langpack.getLanguages#42c6978f lang_pack:string = Vector; folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; +phone.createGroupCall#48cdc6d8 flags:# peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates; +phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates; +phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; +phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; +phone.getGroupCall#41845db call:InputGroupCall limit:int = phone.GroupCall; +phone.getGroupParticipants#c558d8ab call:InputGroupCall ids:Vector sources:Vector offset:string limit:int = phone.GroupParticipants; +phone.editGroupCallParticipant#a5273abf flags:# call:InputGroupCall participant:InputPeer muted:flags.0?Bool volume:flags.1?int raise_hand:flags.2?Bool video_stopped:flags.3?Bool video_paused:flags.4?Bool presentation_paused:flags.5?Bool = Updates; +phone.exportGroupCallInvite#e6aa647f flags:# can_self_unmute:flags.0?true call:InputGroupCall = phone.ExportedGroupCallInvite; +phone.toggleGroupCallStartSubscription#219c34e6 call:InputGroupCall subscribed:Bool = Updates; +phone.joinGroupCallPresentation#cbea6bc4 call:InputGroupCall params:DataJSON = Updates; +phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates; // LAYER 133 diff --git a/src/lib/rlottie/RLottie.ts b/src/lib/rlottie/RLottie.ts index 992d0a33d..c264ca7cd 100644 --- a/src/lib/rlottie/RLottie.ts +++ b/src/lib/rlottie/RLottie.ts @@ -89,6 +89,7 @@ class RLottie { private animationData: AnyLiteral, private params: Params = {}, private onLoad?: () => void, + private customColor?: [number, number, number], ) { this.initContainer(); this.initConfig(); @@ -199,6 +200,46 @@ class RLottie { this.canvas.remove(); } + private onChangeData(framesCount: number) { + this.isWaiting = false; + this.framesCount = framesCount; + this.chunksCount = Math.ceil(framesCount / this.chunkSize); + this.isAnimating = false; + + this.doPlay(); + } + + setColor(newColor: [number, number, number] | undefined) { + this.customColor = newColor; + if (this.customColor) { + const imageData = this.ctx.getImageData(0, 0, this.imgSize, this.imgSize); + const arr = imageData.data; + for (let i = 0; i < arr.length; i += 4) { + /* eslint-disable prefer-destructuring */ + arr[i] = this.customColor[0]; + arr[i + 1] = this.customColor[1]; + arr[i + 2] = this.customColor[2]; + /* eslint-enable prefer-destructuring */ + } + this.ctx.putImageData(imageData, 0, 0); + } + } + + changeData(animationData: AnyLiteral) { + this.pause(); + this.animationData = animationData; + this.initConfig(); + + workers[this.workerIndex].request({ + name: 'changeData', + args: [ + this.key, + this.animationData, + this.onChangeData.bind(this), + ], + }); + } + private initRenderer() { this.workerIndex = cycleRestrict(MAX_WORKERS, ++lastWorkerIndex); @@ -264,7 +305,8 @@ class RLottie { const frameIndex = Math.round(this.approxFrameIndex); const chunkIndex = this.getChunkIndex(frameIndex); const chunk = this.chunks[chunkIndex]; - if (!chunk) { + + if (!chunk || chunk.length === 0) { this.requestChunk(chunkIndex); this.isAnimating = false; this.isWaiting = true; @@ -285,7 +327,17 @@ class RLottie { return false; } - const imageData = new ImageData(new Uint8ClampedArray(frame), this.imgSize, this.imgSize); + const arr = new Uint8ClampedArray(frame); + if (this.customColor) { + for (let i = 0; i < arr.length; i += 4) { + /* eslint-disable prefer-destructuring */ + arr[i] = this.customColor[0]; + arr[i + 1] = this.customColor[1]; + arr[i + 2] = this.customColor[2]; + /* eslint-enable prefer-destructuring */ + } + } + const imageData = new ImageData(arr, this.imgSize, this.imgSize); this.ctx.putImageData(imageData, 0, 0); if (this.onLoad && !this.isOnLoadFired) { @@ -373,7 +425,7 @@ class RLottie { } private requestChunk(chunkIndex: number) { - if (this.chunks[chunkIndex]) { + if (this.chunks[chunkIndex] && this.chunks[chunkIndex]?.length !== 0) { return; } diff --git a/src/lib/rlottie/rlottie.worker.ts b/src/lib/rlottie/rlottie.worker.ts index 246063062..a976bec39 100644 --- a/src/lib/rlottie/rlottie.worker.ts +++ b/src/lib/rlottie/rlottie.worker.ts @@ -56,6 +56,23 @@ async function init( onInit(Math.ceil(framesCount / reduceFactor)); } +async function changeData( + key: string, + animationData: AnyLiteral, + onInit: CancellableCallback, +) { + if (!rLottieApi) { + await rLottieApiPromise; + } + + const json = JSON.stringify(animationData); + const { reduceFactor, handle } = renderers.get(key)!; + + const stringOnWasmHeap = allocate(intArrayFromString(json), 'i8', 0); + const framesCount = rLottieApi.loadFromData(handle, stringOnWasmHeap); + onInit(Math.ceil(framesCount / reduceFactor)); +} + async function renderFrames( key: string, fromIndex: number, toIndex: number, onProgress: CancellableCallback, ) { @@ -86,6 +103,7 @@ function destroy(key: string) { createWorkerInterface({ init, + changeData, renderFrames, destroy, }); diff --git a/src/lib/secret-sauce/blacksilence.d.ts b/src/lib/secret-sauce/blacksilence.d.ts new file mode 100644 index 000000000..828b76642 --- /dev/null +++ b/src/lib/secret-sauce/blacksilence.d.ts @@ -0,0 +1,5 @@ +export declare const silence: (ctx: AudioContext) => MediaStream; +export declare const black: ({ width, height }?: { + width?: number | undefined; + height?: number | undefined; +}) => MediaStream; diff --git a/src/lib/secret-sauce/buildSdp.d.ts b/src/lib/secret-sauce/buildSdp.d.ts new file mode 100644 index 000000000..f2e1d31f8 --- /dev/null +++ b/src/lib/secret-sauce/buildSdp.d.ts @@ -0,0 +1,21 @@ +import { GroupCallTransport, PayloadType, RTPExtension, SsrcGroup } from './types'; +export declare type Conference = { + sessionId: number; + audioExtensions: RTPExtension[]; + videoExtensions: RTPExtension[]; + audioPayloadTypes: PayloadType[]; + videoPayloadTypes: PayloadType[]; + ssrcs: Ssrc[]; + transport: GroupCallTransport; +}; +export declare type Ssrc = { + userId: string; + endpoint: string; + isMain: boolean; + isRemoved?: boolean; + isVideo: boolean; + isPresentation?: boolean; + sourceGroups: SsrcGroup[]; +}; +declare const _default: (conference: Conference, isAnswer?: boolean, isPresentation?: boolean) => string; +export default _default; diff --git a/src/lib/secret-sauce/colibriClass.d.ts b/src/lib/secret-sauce/colibriClass.d.ts new file mode 100644 index 000000000..80bcc3aaa --- /dev/null +++ b/src/lib/secret-sauce/colibriClass.d.ts @@ -0,0 +1,36 @@ +export declare type EndpointConnectivityStatusChangeEvent = { + colibriClass: 'EndpointConnectivityStatusChangeEvent'; + endpoint: string; + active: boolean; +}; +export declare type DominantSpeakerEndpointChangeEvent = { + colibriClass: 'DominantSpeakerEndpointChangeEvent'; + dominantSpeakerEndpoint: string; + previousSpeakers: string[]; +}; +export declare type SenderVideoConstraints = { + colibriClass: 'SenderVideoConstraints'; + videoConstraints: { + idealHeight: number; + }; +}; +export declare type DebugMessage = { + colibriClass: 'DebugMessage'; + message: string; +}; +export declare type LastNEndpointsChangeEvent = { + colibriClass: 'LastNEndpointsChangeEvent'; + lastNEndpoints: string[]; +}; +export declare type ReceiverVideoConstraints = { + colibriClass: 'ReceiverVideoConstraints'; + defaultConstraints: { + maxHeight: number; + }; + constraints: Record; + onStageEndpoints: string[]; +}; +export declare type ColibriClass = (LastNEndpointsChangeEvent | DebugMessage | EndpointConnectivityStatusChangeEvent | SenderVideoConstraints | DominantSpeakerEndpointChangeEvent | ReceiverVideoConstraints); diff --git a/src/lib/secret-sauce/index.d.ts b/src/lib/secret-sauce/index.d.ts new file mode 100644 index 000000000..f887e1781 --- /dev/null +++ b/src/lib/secret-sauce/index.d.ts @@ -0,0 +1,3 @@ +export { handleUpdateGroupCallConnection, startSharingScreen, joinGroupCall, getDevices, getUserStreams, setVolume, isStreamEnabled, toggleStream, leaveGroupCall, handleUpdateGroupCallParticipants, switchCameraInput, toggleSpeaker, toggleNoiseSuppression, } from './secretsauce'; +export { IS_SCREENSHARE_SUPPORTED, THRESHOLD, } from './utils'; +export * from './types'; diff --git a/src/lib/secret-sauce/index.js b/src/lib/secret-sauce/index.js new file mode 100644 index 000000000..8f63f62df --- /dev/null +++ b/src/lib/secret-sauce/index.js @@ -0,0 +1,2 @@ +/*! For license information please see index.js.LICENSE.txt */ +(()=>{"use strict";var e={"./src/blacksilence.ts":(e,t,n)=>{n.r(t),n.d(t,{silence:()=>a,black:()=>i});const a=e=>{const t=e.createOscillator(),n=t.connect(e.createMediaStreamDestination());return t.start(),new MediaStream([Object.assign(n.stream.getAudioTracks()[0],{enabled:!1})])},i=({width:e=640,height:t=480}={})=>{const n=Object.assign(document.createElement("canvas"),{width:e,height:t}),a=n.getContext("2d");if(!a)throw Error("Cannot create canvas ctx");a.fillRect(0,0,e,t);const i=n.captureStream();return new MediaStream([Object.assign(i.getVideoTracks()[0],{enabled:!1})])}},"./src/buildSdp.ts":(e,t,n)=>{n.r(t),n.d(t,{default:()=>i});var a=n("./src/utils.ts");const i=(e,t=!1,n=!1)=>{const i=[],r=e=>{i.push(e)},{sessionId:s,ssrcs:o,audioExtensions:c,videoExtensions:d,audioPayloadTypes:p,videoPayloadTypes:u,transport:{ufrag:l,pwd:f,fingerprints:m,candidates:g}}=e;r("v=0"),r(`o=- ${s} 2 IN IP4 0.0.0.0`),r("s=-"),r("t=0 0"),r(`a=group:BUNDLE ${o.map((e=>e.endpoint)).join(" ")}${n?"":" 2"}`),r("a=ice-lite");const v=e=>{let t="";t+="a=candidate:",t+=`${e.foundation} ${e.component} ${e.protocol} ${e.priority} ${e.ip} ${e.port} typ ${e.type}`,"rel-addr"in e&&(t+=` raddr ${e["rel-addr"]} rport ${e["rel-port"]}`),t+=` generation ${e.generation}`,r(t)},S=()=>{r(`a=ice-ufrag:${l}`),r(`a=ice-pwd:${f}`),m.forEach((e=>{r(`a=fingerprint:${e.hash} ${e.fingerprint}`),r("a=setup:passive")})),g.forEach(v)},h=e=>{const{channels:t,id:n,name:a,clockrate:i,parameters:s}=e;var o=t?`/${t}`:"";r(`a=rtpmap:${n} ${a}/${i}${o}`),s&&(o=Object.keys(s).map((e=>`${e}=${s[e]};`)).join(" "),r(`a=fmtp:${n} ${o}`)),e["rtcp-fbs"]?.forEach((e=>{r(`a=rtcp-fb:${n} ${e.type}${e.subtype?` ${e.subtype}`:""}`)}))};return e=e=>{const n=e.isVideo?u:p;var i=e.isVideo?"video":"audio";r(`m=${i} ${e.isMain?1:0} RTP/SAVPF ${n.map((e=>e.id)).join(" ")}`),r("c=IN IP4 0.0.0.0"),r("b=AS:1300"),r(`a=mid:${e.endpoint}`),r("a=rtcp-mux"),n.forEach(h),r("a=rtcp:1 IN IP4 0.0.0.0"),e.isVideo&&r("a=rtcp-rsize"),(e.isVideo?d:c).forEach((({id:e,uri:t})=>{r(`a=extmap:${e} ${t}`)})),e.isRemoved?r("a=inactive"):(S(),t?r("a=recvonly"):(e.isMain?r("a=sendrecv"):(r("a=sendonly"),r("a=bundle-only")),e.sourceGroups.forEach((t=>{r(`a=ssrc-group:${t.semantics} ${t.sources.map(a.fromTelegramSource).join(" ")}`),t.sources.forEach((t=>{t=(0,a.fromTelegramSource)(t),r(`a=ssrc:${t} cname:${e.endpoint}`),r(`a=ssrc:${t} msid:${e.endpoint} ${e.endpoint}`),r(`a=ssrc:${t} mslabel:${e.endpoint}`),r(`a=ssrc:${t} label:${e.endpoint}`)}))}))))},o.filter((e=>"0"===e.endpoint||"1"===e.endpoint)).map(e),n||(r("m=application 1 UDP/DTLS/SCTP webrtc-datachannel"),r("c=IN IP4 0.0.0.0"),S(),r("a=ice-options:trickle"),r("a=mid:"+(n?"1":"2")),r("a=sctp-port:5000"),r("a=max-message-size:262144")),o.filter((e=>"0"!==e.endpoint&&"1"!==e.endpoint)).map(e),`${i.join("\n")}\n`}},"./src/parseSdp.ts":(e,t,n)=>{n.r(t),n.d(t,{default:()=>i});var a=n("./src/utils.ts");const i=e=>{if(!e||!e.sdp)throw Error("Failed parsing SDP: session description is null");const t=e.sdp.split("\r\nm=").map(((e,t)=>0===t?e:`m=${e}`)).reduce(((e,t)=>(e[t.match(/^m=(.+?)\s/)?.[1]||"header"]=t.split("\r\n").filter(Boolean),e)),{});var n=(e,n)=>n?t[n]?.find((t=>t.startsWith(e)))?.substr(e.length):Object.values(t).map((t=>t.find((t=>t.startsWith(e)))?.substr(e.length))).filter(Boolean)[0];const i=n("a=ssrc:","audio");var r=i&&Number(i.split(" ")[0]);const s=n("a=ssrc-group:","video")?.split(" ")||void 0;if(!s)throw Error("Failed parsing SDP: no video ssrc");var[o,c]=n("a=fingerprint:")?.split(" ")||[];if(!o||!c)throw Error("Failed parsing SDP: no fingerprint");if(e=n("a=ice-ufrag:"),n=n("a=ice-pwd:"),!e||!n)throw Error("Failed parsing SDP: no ICE ufrag or pwd");return{fingerprints:[{fingerprint:c,hash:o,setup:"active"}],pwd:n,ufrag:e,...r&&{ssrc:(0,a.toTelegramSource)(r)},...s&&{"ssrc-groups":[{semantics:s[0],sources:s.slice(1,s.length).map(Number).map(a.toTelegramSource)}]}}}},"./src/secretsauce.ts":(e,t,n)=>{n.r(t),n.d(t,{getDevices:()=>async function(e,t=!0){return(await navigator.mediaDevices.enumerateDevices()).filter((n=>n.kind===`${e}${t?"input":"output"}`))},toggleSpeaker:()=>function(){o&&(o.isSpeakerDisabled=!o.isSpeakerDisabled,o?.onUpdate?.({"@type":"updateGroupCallConnectionState",connectionState:"connected",isSpeakerDisabled:o.isSpeakerDisabled}),o.participantFunctions&&Object.values(o.participantFunctions).forEach((e=>{e.toggleMute?.(!!o?.isSpeakerDisabled)})))},toggleNoiseSuppression:()=>function(){if(o&&o.myId&&o.streams){const n=o.streams[o.myId].audio;if(n){const a=n.getTracks()[0];var e,t;a&&(({echoCancellation:e,noiseSuppression:t}=a.getConstraints()),a.applyConstraints({echoCancellation:!e,noiseSuppression:!t}))}}},getUserStreams:()=>d,setVolume:()=>function(e,t){const n=o?.participantFunctions?.[e];n&&n.setVolume?.(t)},isStreamEnabled:()=>p,switchCameraInput:()=>async function(){if(o?.myId&&o.connection&&o.streams&&o.facingMode){const e=d(o.myId)?.video;if(e){const t=e.getTracks()[0];if(t){const e=o.connection.getSenders().find((e=>t.id===e.track?.id));if(e){o.facingMode="environment"===o.facingMode?"user":"environment";try{const t=await l("video",o.facingMode);await e.replaceTrack(t.getTracks()[0]),o.streams[o.myId].video=t}catch(e){}}}}}},toggleStream:()=>f,leaveGroupCall:()=>g,handleUpdateGroupCallParticipants:()=>async function(e){if(o){const{participants:a,conference:r,connection:s,myId:c}=o;if(a&&r&&s&&r.ssrcs&&r.transport&&c)if(e.find((e=>e.isSelf&&e.source!==o?.conference?.ssrcs?.find((e=>e.isMain&&!e.isVideo))?.sourceGroups[0].sources[0])))g();else{const a=[];if(e.forEach((e=>{if(e.isSelf)e.isMuted&&!e.canSelfUnmute&&(f("audio",!1),f("video",!1),f("presentation",!1));else{var t=e.isLeft;const n=e.isMuted||e.isMutedByMe,i=!e.isVideoJoined||!e.video||t,s=!e.presentation||t;let o=!1,c=!1,d=!1;r.ssrcs.filter((t=>t.userId===e.id)).forEach((t=>{t.isVideo||(t.sourceGroups[0].sources[0]===e.source&&(c=!0),t.isRemoved=n),t.isVideo&&(t.isPresentation||(e.video&&t.endpoint===e.video.endpoint&&(o=!0),t.isRemoved=i),t.isPresentation&&(e.presentation&&t.endpoint===e.presentation.endpoint&&(d=!0),t.isRemoved=s))})),n||c||r.ssrcs.push({userId:e.id,isMain:!1,endpoint:`audio${e.source}`,isVideo:!1,sourceGroups:[{semantics:"FID",sources:[e.source]}]}),i||o||!e.video||(a.push(e.video.endpoint),r.ssrcs.push({userId:e.id,isMain:!1,endpoint:e.video.endpoint,isVideo:!0,sourceGroups:e.video.sourceGroups})),s||d||!e.presentation||r.ssrcs.push({isPresentation:!0,userId:e.id,isMain:!1,endpoint:e.presentation.endpoint,isVideo:!0,sourceGroups:e.presentation.sourceGroups})}})),o.updatingParticipantsQueue)o.updatingParticipantsQueue.push(r);else{o.updatingParticipantsQueue=[],e=(0,i.default)(r),await s.setRemoteDescription({type:"offer",sdp:e});try{var t=await s.createAnswer();if(await s.setLocalDescription(t),u(c),0async function(e,t){if(o){var n=t?o.screenshareConference:o.conference;const r=t?o.screenshareConnection:o.connection;if(n&&r&&n.ssrcs){var a=Date.now();e={...n,transport:e.transport,sessionId:a,audioExtensions:e.audio?.["rtp-hdrexts"],audioPayloadTypes:e.audio?.["payload-types"],videoExtensions:e.video?.["rtp-hdrexts"],videoPayloadTypes:e.video?.["payload-types"]};o={...o,...t?{screenshareConference:e}:{conference:e}};try{await r.setRemoteDescription({type:"answer",sdp:(0,i.default)(e,!0,t)})}catch(e){console.error(e)}}}},startSharingScreen:()=>async function(){if(o)try{const e=await l("presentation");return e?(e.getTracks()[0].onended=()=>{o&&o.myId&&(o.streams?.[o.myId].presentation,u(o.myId),c())},new Promise((t=>{var{connection:n,dataChannel:t}=h([e],t,!0);o={...o,screenshareConnection:n,screenshareDataChannel:t}}))):void 0}catch(e){return}},joinGroupCall:()=>function(e,t,n,a){if(o)throw Error("Already in call");m("connecting");var i=t.createMediaStreamDestination();return n.srcObject=i.stream,n.play().catch((e=>console.warn(e))),o={onUpdate:a,participants:[],myId:e,speaking:{},silence:(0,r.silence)(t),black:(0,r.black)({width:640,height:480}),analyserInterval:setInterval(v,1e3),audioElement:n,destination:i,audioContext:t},new Promise((e=>{o={...o,...h([o.silence,o.black],e)}}))}});var a=n("./src/parseSdp.ts"),i=n("./src/buildSdp.ts"),r=n("./src/blacksilence.ts"),s=n("./src/utils.ts");let o;function c(e){o&&(o.screenshareDataChannel?.close(),o.screenshareConnection?.close(),e||o.onUpdate?.({"@type":"updateGroupCallLeavePresentation"}))}function d(e){return o?.streams?.[e]}function p(e,t){const n=(t=t||o?.myId)&&d(t)?.[e];return!!n&&n.getTracks()[0]?.enabled}function u(e){o?.onUpdate?.({"@type":"updateGroupCallStreams",userId:e,hasAudioStream:p("audio",e),hasVideoStream:p("video",e),hasPresentationStream:p("presentation",e),amplitude:o.speaking?.[e]})}function l(e,t="user"){return"presentation"===e?navigator.mediaDevices.getDisplayMedia({audio:!1,video:!0}):navigator.mediaDevices.getUserMedia({audio:"audio"===e&&{...s.IS_ECHO_CANCELLATION_SUPPORTED&&{echoCancellation:!0},...s.IS_NOISE_SUPPRESSION_SUPPORTED&&{noiseSuppression:!0}},video:"video"===e&&{facingMode:t}})}async function f(e,t){if(o&&o.myId&&o.connection&&o.streams){const n=d(o.myId)?.[e];if(n){const a=n.getTracks()[0];if(a){const n=[...o.connection.getSenders(),...o.screenshareConnection?.getSenders()||[]].find((e=>a.id===e.track?.id));if(n){t=void 0===t?!a.enabled:t;try{if(t&&!a.enabled){const t=await l(e);if(await n.replaceTrack(t.getTracks()[0]),o.streams[o.myId][e]=t,"video"===e)o.facingMode="user";else if("audio"===e){const e=o.audioContext;if(!e)return;const n=e.createMediaStreamSource(t),a=e.createAnalyser();a.minDecibels=-100,a.maxDecibels=-30,a.smoothingTimeConstant=.05,a.fftSize=1024,n.connect(a),o={...o,participantFunctions:{...o.participantFunctions,[o.myId]:{...o.participantFunctions?.[o.myId],getCurrentAmplitude:()=>{var e=new Uint8Array(a.frequencyBinCount);return a.getByteFrequencyData(e),(0,s.getAmplitude)(e,1.5)}}}}}}else if(!t&&a.enabled){a.stop();const t="audio"===e?o.silence:o.black;if(!t)return;await n.replaceTrack(t.getTracks()[0]),o.streams[o.myId][e]=t,"video"===e&&(o.facingMode=void 0)}u(o.myId),"presentation"!==e||t||c(!0)}catch(e){}}}}}}function m(e){o?.onUpdate?.({"@type":"updateGroupCallConnectionState",connectionState:e})}function g(){o&&(o.myId&&o.streams?.[o.myId]&&Object.values(o.streams[o.myId]||{}).forEach((e=>{e?.getTracks().forEach((e=>{e.stop()}))})),c(!0),o.dataChannel?.close(),o.connection?.close(),m("disconnected"),o.analyserInterval&&clearInterval(o.analyserInterval),o=void 0)}function v(){o&&o.participantFunctions&&Object.keys(o.participantFunctions).forEach((e=>{const t=o.participantFunctions[Number(e)].getCurrentAmplitude;var n,a;t&&(n=t(),a=o.speaking[e]||0,((o.speaking[e]=n)>s.THRESHOLD&&a<=s.THRESHOLD||n<=s.THRESHOLD&&a>s.THRESHOLD)&&u(e))}))}function S(e){if(o&&o.audioElement&&o.destination&&o.audioContext){var t=o.conference?.ssrcs?.find((t=>t.endpoint===e.track.id));if(t&&t.userId){const{userId:a,isPresentation:i}=t;var n=o.participants?.find((e=>e.id===a));const r="video"===e.track.kind?i?"presentation":"video":"audio";if(e.track.onended=()=>{o?.streams?.[a][r],u(a)},t=e.streams[0],"audio"===e.track.kind){const e=o.audioContext,i=e.createMediaStreamSource(t),r=e.createGain();r.gain.value=(n?.volume||1e4)/1e4;const c=e.createGain();r.gain.value=1;const d=e.createAnalyser();d.minDecibels=-100,d.maxDecibels=-30,d.smoothingTimeConstant=.05,d.fftSize=1024,i.connect(d).connect(c).connect(r).connect(o.destination);const p=new Audio;p.srcObject=i.mediaStream,p.muted=!0,p.remove(),o={...o,participantFunctions:{...o.participantFunctions,[a]:{...o.participantFunctions?.[a],setVolume:e=>{r.gain.value=1{c.gain.value=e?0:1},getCurrentAmplitude:()=>{var e=new Uint8Array(d.frequencyBinCount);return d.getByteFrequencyData(e),(0,s.getAmplitude)(e,1.5)}}}}}o={...o,streams:{...o.streams,[a]:{...o.streams?.[a],[r]:t}}},u(a)}}}function h(e,t,n=!1){const i=new RTCPeerConnection;var r=n?void 0:function(e){const t=e.createDataChannel("data",{id:0});return t.onopen=()=>{},t.onmessage=e=>{JSON.parse(e.data).colibriClass},t.onerror=e=>{console.log("%conerror","background: green; font-size: 5em"),console.error(e)},t}(i);return e.forEach((e=>e.getTracks().forEach((t=>{i.addTrack(t,e)})))),n||(i.oniceconnectionstatechange=()=>{var e=i.iceConnectionState;"connected"===e||"completed"===e?m("connected"):"checking"===e||"new"===e?m("connecting"):"disconnected"===i.iceConnectionState&&m("reconnecting")}),i.ontrack=S,i.onnegotiationneeded=async()=>{if(o){var r=o.myId;if(r){var s=await i.createOffer({offerToReceiveVideo:!0,offerToReceiveAudio:!n});if(await i.setLocalDescription(s),s.sdp){var c=(0,a.default)(s),d=n?void 0:{userId:"",sourceGroups:[{semantics:"FID",sources:[c.ssrc||0]}],isRemoved:n,isMain:!0,isVideo:!1,isPresentation:n,endpoint:n?"1":"0"},p=c["ssrc-groups"]&&{isPresentation:n,userId:"",sourceGroups:c["ssrc-groups"],isMain:!0,isVideo:!0,endpoint:n?"0":"1"};s=n?o.screenshareConference:o.conference;const i=[];n?(p&&i.push(p),d&&i.push(d)):(d&&i.push(d),p&&i.push(p)),d=e.find((e=>"audio"===e.getTracks()[0].kind)),p=e.find((e=>"video"===e.getTracks()[0].kind)),o={...o,...n?{screenshareConference:{...s,ssrcs:i}}:{conference:{...s,ssrcs:i}},streams:{...o.streams,[r]:{...o.streams?.[r],...d&&{audio:d},...!n&&p?{video:p}:{presentation:p}}}},u(r),t(c)}}}},{connection:i,dataChannel:r}}},"./src/types.ts":(e,t,n)=>{n.r(t)},"./src/utils.ts":(e,t,n)=>{function a(){var{userAgent:e,platform:t}=window.navigator;let n;return-1!==["Macintosh","MacIntel","MacPPC","Mac68K"].indexOf(t)?n="macOS":-1!==["iPhone","iPad","iPod"].indexOf(t)?n="iOS":-1!==["Win32","Win64","Windows","WinCE"].indexOf(t)?n="Windows":/Android/.test(e)?n="Android":/Linux/.test(t)&&(n="Linux"),n}n.r(t),n.d(t,{toTelegramSource:()=>function(e){return e<<0},fromTelegramSource:()=>function(e){return e>>>0},getAmplitude:()=>function(e,t=3){if(!e)return 0;var n=e.length;let a=0;for(let t=0;ta,THRESHOLD:()=>i,PLATFORM_ENV:()=>r,IS_MAC_OS:()=>s,IS_IOS:()=>o,IS_SCREENSHARE_SUPPORTED:()=>c,IS_ECHO_CANCELLATION_SUPPORTED:()=>d,IS_NOISE_SUPPRESSION_SUPPORTED:()=>p});const i=.1,r=a(),s="macOS"===r,o="iOS"===r,c="getDisplayMedia"in(navigator?.mediaDevices||{}),d=navigator?.mediaDevices?.getSupportedConstraints().echoCancellation,p=navigator?.mediaDevices?.getSupportedConstraints().noiseSuppression}},t={};function n(a){var i=t[a];return void 0!==i||(i=t[a]={exports:{}},e[a](i,i.exports,n)),i.exports}n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var a={};(()=>{n.r(a),n.d(a,{handleUpdateGroupCallConnection:()=>e.handleUpdateGroupCallConnection,startSharingScreen:()=>e.startSharingScreen,joinGroupCall:()=>e.joinGroupCall,getDevices:()=>e.getDevices,getUserStreams:()=>e.getUserStreams,setVolume:()=>e.setVolume,isStreamEnabled:()=>e.isStreamEnabled,toggleStream:()=>e.toggleStream,leaveGroupCall:()=>e.leaveGroupCall,handleUpdateGroupCallParticipants:()=>e.handleUpdateGroupCallParticipants,switchCameraInput:()=>e.switchCameraInput,toggleSpeaker:()=>e.toggleSpeaker,toggleNoiseSuppression:()=>e.toggleNoiseSuppression,IS_SCREENSHARE_SUPPORTED:()=>t.IS_SCREENSHARE_SUPPORTED,THRESHOLD:()=>t.THRESHOLD});var e=n("./src/secretsauce.ts"),t=n("./src/utils.ts");n("./src/types.ts")})();var i,r=exports;for(i in a)r[i]=a[i];a.__esModule&&Object.defineProperty(r,"__esModule",{value:!0})})(); \ No newline at end of file diff --git a/src/lib/secret-sauce/index.js.LICENSE.txt b/src/lib/secret-sauce/index.js.LICENSE.txt new file mode 100644 index 000000000..1d5cd7da2 --- /dev/null +++ b/src/lib/secret-sauce/index.js.LICENSE.txt @@ -0,0 +1,39 @@ +/*! ./blacksilence */ + +/*! ./buildSdp */ + +/*! ./parseSdp */ + +/*! ./secretsauce */ + +/*! ./types */ + +/*! ./utils */ + +/*!**********************!*\ + !*** ./src/index.ts ***! + \**********************/ + +/*!**********************!*\ + !*** ./src/types.ts ***! + \**********************/ + +/*!**********************!*\ + !*** ./src/utils.ts ***! + \**********************/ + +/*!*************************!*\ + !*** ./src/buildSdp.ts ***! + \*************************/ + +/*!*************************!*\ + !*** ./src/parseSdp.ts ***! + \*************************/ + +/*!****************************!*\ + !*** ./src/secretsauce.ts ***! + \****************************/ + +/*!*****************************!*\ + !*** ./src/blacksilence.ts ***! + \*****************************/ diff --git a/src/lib/secret-sauce/parseSdp.d.ts b/src/lib/secret-sauce/parseSdp.d.ts new file mode 100644 index 000000000..d789bd62d --- /dev/null +++ b/src/lib/secret-sauce/parseSdp.d.ts @@ -0,0 +1,3 @@ +import { JoinGroupCallPayload } from './types'; +declare const _default: (sessionDescription: RTCSessionDescriptionInit) => JoinGroupCallPayload; +export default _default; diff --git a/src/lib/secret-sauce/secretsauce.d.ts b/src/lib/secret-sauce/secretsauce.d.ts new file mode 100644 index 000000000..8d3829af9 --- /dev/null +++ b/src/lib/secret-sauce/secretsauce.d.ts @@ -0,0 +1,20 @@ +import { GroupCallConnectionData, GroupCallParticipant, JoinGroupCallPayload } from './types'; +declare type StreamType = 'audio' | 'video' | 'presentation'; +export declare function getDevices(streamType: StreamType, isInput?: boolean): Promise; +export declare function toggleSpeaker(): void; +export declare function toggleNoiseSuppression(): void; +export declare function getUserStreams(userId: string): { + audio?: MediaStream | undefined; + video?: MediaStream | undefined; + presentation?: MediaStream | undefined; +} | undefined; +export declare function setVolume(userId: string, volume: number): void; +export declare function isStreamEnabled(streamType: StreamType, userId?: string): boolean; +export declare function switchCameraInput(): Promise; +export declare function toggleStream(streamType: StreamType, value?: boolean | undefined): Promise; +export declare function leaveGroupCall(): void; +export declare function handleUpdateGroupCallParticipants(updatedParticipants: GroupCallParticipant[]): Promise; +export declare function handleUpdateGroupCallConnection(data: GroupCallConnectionData, isPresentation: boolean): Promise; +export declare function startSharingScreen(): Promise; +export declare function joinGroupCall(myId: string, audioContext: AudioContext, audioElement: HTMLAudioElement, onUpdate: (...args: any[]) => void): Promise; +export {}; diff --git a/src/lib/secret-sauce/types.d.ts b/src/lib/secret-sauce/types.d.ts new file mode 100644 index 000000000..57029540f --- /dev/null +++ b/src/lib/secret-sauce/types.d.ts @@ -0,0 +1,100 @@ +export interface GroupCallParticipant { + isSelf?: boolean; + isMuted?: boolean; + isLeft?: boolean; + isUser?: boolean; + canSelfUnmute?: boolean; + hasJustJoined?: boolean; + isVideoJoined?: boolean; + isMutedByMe?: boolean; + isVolumeByAdmin?: boolean; + isMin?: boolean; + isVersioned?: boolean; + source: number; + date: Date; + id: string; + volume?: number; + about?: string; + video?: GroupCallParticipantVideo; + presentation?: GroupCallParticipantVideo; + raiseHandRating?: string; + hasAudioStream?: boolean; + hasVideoStream?: boolean; + hasPresentationStream?: boolean; + amplitude?: number; +} +export declare type GroupCallConnectionState = 'connecting' | 'connected' | 'disconnected' | 'reconnecting' | 'discarded'; +export interface GroupCallParticipantVideo { + endpoint: string; + isPaused?: boolean; + sourceGroups: SsrcGroup[]; + audioSource?: number; +} +export declare type Fingerprint = { + hash: string; + setup: string; + fingerprint: string; +}; +export declare type SsrcGroup = { + semantics: string; + sources: number[]; +}; +export declare type Candidate = { + generation: string; + component: string; + protocol: string; + port: string; + ip: string; + foundation: string; + id: string; + priority: string; + type: string; + network: string; + 'rel-addr': string; + 'rel-port': string; +}; +export declare type JoinGroupCallPayload = { + ufrag: string; + pwd: string; + fingerprints: Fingerprint[]; + ssrc?: number; + 'ssrc-groups'?: SsrcGroup[]; +}; +export interface RTPExtension { + id: number; + uri: string; +} +export interface RTCPFeedbackParam { + type: string; + subtype?: string; +} +export interface PayloadType { + id: number; + name: string; + clockrate: number; + channels: number; + parameters?: Record; + 'rtcp-fbs'?: RTCPFeedbackParam[]; +} +export interface GroupCallTransport { + candidates: Candidate[]; + pwd: string; + ufrag: string; + fingerprints: Fingerprint[]; + 'rtcp-mux': boolean; + xmlns: string; +} +export interface GroupCallConnectionData { + transport: GroupCallTransport; + audio: { + 'payload-types': PayloadType[]; + 'rtp-hdrexts': RTPExtension[]; + }; + video: { + endpoint: string; + 'payload-types': PayloadType[]; + 'rtp-hdrexts': RTPExtension[]; + server_sources: number[]; + }; + stream?: boolean; +} diff --git a/src/lib/secret-sauce/utils.d.ts b/src/lib/secret-sauce/utils.d.ts new file mode 100644 index 000000000..2540de229 --- /dev/null +++ b/src/lib/secret-sauce/utils.d.ts @@ -0,0 +1,11 @@ +export declare function toTelegramSource(source: number): number; +export declare function fromTelegramSource(source: number): number; +export declare function getAmplitude(array: Uint8Array, scale?: number): number; +export declare function getPlatform(): "Windows" | "macOS" | "iOS" | "Android" | "Linux" | undefined; +export declare const THRESHOLD = 0.1; +export declare const PLATFORM_ENV: "Windows" | "macOS" | "iOS" | "Android" | "Linux" | undefined; +export declare const IS_MAC_OS: boolean; +export declare const IS_IOS: boolean; +export declare const IS_SCREENSHARE_SUPPORTED: boolean; +export declare const IS_ECHO_CANCELLATION_SUPPORTED: boolean | undefined; +export declare const IS_NOISE_SUPPRESSION_SUPPORTED: any; diff --git a/src/modules/actions/all.ts b/src/modules/actions/all.ts index ad3188e0d..4b2f03d5e 100644 --- a/src/modules/actions/all.ts +++ b/src/modules/actions/all.ts @@ -8,6 +8,7 @@ import './ui/users'; import './ui/settings'; import './ui/misc'; import './ui/payments'; +import './ui/calls'; import './api/initial'; import './api/chats'; @@ -31,3 +32,4 @@ import './apiUpdaters/symbols'; import './apiUpdaters/misc'; import './apiUpdaters/settings'; import './apiUpdaters/twoFaSettings'; +import './apiUpdaters/calls'; diff --git a/src/modules/actions/api/calls.async.ts b/src/modules/actions/api/calls.async.ts new file mode 100644 index 000000000..5b00a7abf --- /dev/null +++ b/src/modules/actions/api/calls.async.ts @@ -0,0 +1,283 @@ +import { + joinGroupCall, + startSharingScreen, + leaveGroupCall, + toggleStream, + isStreamEnabled, + setVolume, + handleUpdateGroupCallParticipants, handleUpdateGroupCallConnection, +} from '../../../lib/secret-sauce'; +import { addReducer, getGlobal, setGlobal } from '../../../lib/teact/teactn'; +import { callApi } from '../../../api/gramjs'; +import { selectChat, selectUser } from '../../selectors'; +import { + selectActiveGroupCall, + selectGroupCallParticipant, +} from '../../selectors/calls'; +import { + removeGroupCall, + updateActiveGroupCall, + updateGroupCall, + updateGroupCallParticipant, +} from '../../reducers/calls'; +import { ApiUpdate } from '../../../api/types'; +import { GROUP_CALL_VOLUME_MULTIPLIER } from '../../../config'; +import { omit } from '../../../util/iteratees'; +import { getGroupCallAudioContext, getGroupCallAudioElement, removeGroupCallAudioElement } from '../ui/calls'; +import { loadFullChat } from './chats'; + +addReducer('apiUpdate', (global, actions, update: ApiUpdate) => { + const { activeGroupCallId } = global.groupCalls; + + switch (update['@type']) { + case 'updateGroupCallLeavePresentation': { + actions.toggleGroupCallPresentation({ value: false }); + break; + } + case 'updateGroupCallStreams': { + if (!update.userId || !activeGroupCallId) break; + if (!selectGroupCallParticipant(global, activeGroupCallId, update.userId)) break; + + return updateGroupCallParticipant(global, activeGroupCallId, update.userId, omit(update, ['@type', 'userId'])); + } + case 'updateGroupCallConnectionState': { + if (!activeGroupCallId) break; + + if (update.connectionState === 'disconnected') { + actions.leaveGroupCall({ isFromLibrary: true }); + break; + } + + return updateGroupCall(global, activeGroupCallId, { + connectionState: update.connectionState, + isSpeakerDisabled: update.isSpeakerDisabled, + }); + } + case 'updateGroupCallParticipants': { + const { groupCallId, participants } = update; + if (activeGroupCallId === groupCallId) { + void handleUpdateGroupCallParticipants(participants); + } + break; + } + case 'updateGroupCallConnection': { + if (update.data.stream) { + actions.showNotification({ message: 'Big live streams are not yet supported' }); + actions.leaveGroupCall(); + break; + } + void handleUpdateGroupCallConnection(update.data, update.presentation); + + const groupCall = selectActiveGroupCall(global); + if (groupCall?.participants && Object.keys(groupCall.participants).length > 0) { + void handleUpdateGroupCallParticipants(Object.values(groupCall.participants)); + } + break; + } + } + + return undefined; +}); + +addReducer('leaveGroupCall', (global, actions, payload) => { + const { + isFromLibrary, shouldDiscard, shouldRemove, rejoin, + } = payload || {}; + const groupCall = selectActiveGroupCall(global); + if (!groupCall) { + return; + } + + global = updateActiveGroupCall(global, { + connectionState: 'disconnected', + }, groupCall.participantsCount - 1); + + (async () => { + await callApi('leaveGroupCall', { + call: groupCall, + }); + + if (shouldDiscard) { + await callApi('discardGroupCall', { + call: groupCall, + }); + } + + global = getGlobal(); + if (shouldRemove) { + global = removeGroupCall(global, groupCall.id); + } + + removeGroupCallAudioElement(); + + setGlobal({ + ...global, + groupCalls: { + ...global.groupCalls, + isGroupCallPanelHidden: true, + activeGroupCallId: undefined, + }, + }); + + if (!isFromLibrary) { + leaveGroupCall(); + } + + if (rejoin) { + actions.joinGroupCall(rejoin); + } + })(); +}); + +addReducer('toggleGroupCallVideo', (global) => { + const groupCall = selectActiveGroupCall(global); + const user = selectUser(global, global.currentUserId!); + if (!user || !groupCall) { + return; + } + + (async () => { + await toggleStream('video'); + + await callApi('editGroupCallParticipant', { + call: groupCall, + videoStopped: !isStreamEnabled('video'), + participant: user, + }); + })(); +}); + +addReducer('requestToSpeak', (global, actions, payload) => { + const { value } = payload || { value: true }; + const groupCall = selectActiveGroupCall(global); + const user = selectUser(global, global.currentUserId!); + if (!user || !groupCall) { + return; + } + + void callApi('editGroupCallParticipant', { + call: groupCall, + raiseHand: value, + participant: user, + }); +}); + +addReducer('setGroupCallParticipantVolume', (global, actions, payload) => { + const { participantId, volume } = payload!; + + const groupCall = selectActiveGroupCall(global); + const user = selectUser(global, participantId); + if (!user || !groupCall) { + return; + } + + setVolume(participantId, Math.floor(volume / GROUP_CALL_VOLUME_MULTIPLIER) / 100); + + void callApi('editGroupCallParticipant', { + call: groupCall, + volume: Number(volume), + participant: user, + }); +}); + +addReducer('toggleGroupCallMute', (global, actions, payload) => { + const { participantId, value } = payload || {}; + const groupCall = selectActiveGroupCall(global); + const user = selectUser(global, participantId || global.currentUserId!); + if (!user || !groupCall) { + return; + } + + (async () => { + const muted = value === undefined ? isStreamEnabled('audio', user.id) : value; + + if (!participantId) { + await toggleStream('audio'); + } else { + setVolume(participantId, muted ? 0 : 1); + } + + await callApi('editGroupCallParticipant', { + call: groupCall, + muted, + participant: user, + }); + })(); +}); + +addReducer('toggleGroupCallPresentation', (global, actions, payload) => { + const groupCall = selectActiveGroupCall(global); + const user = selectUser(global, global.currentUserId!); + if (!user || !groupCall) { + return; + } + + (async () => { + const value = payload?.value !== undefined ? payload?.value : !isStreamEnabled('presentation'); + if (value) { + const params = await startSharingScreen(); + if (!params) { + return; + } + + await callApi('joinGroupCallPresentation', { + call: groupCall, + params, + }); + } else { + await toggleStream('presentation', false); + await callApi('leaveGroupCallPresentation', { + call: groupCall, + }); + } + + await callApi('editGroupCallParticipant', { + call: groupCall, + presentationPaused: !isStreamEnabled('presentation'), + participant: user, + }); + })(); +}); + +addReducer('connectToActiveGroupCall', (global, actions) => { + const groupCall = selectActiveGroupCall(global); + if (!groupCall) return; + + if (groupCall.connectionState === 'discarded') { + actions.showNotification({ message: 'This voice chat is not active' }); + return; + } + + const audioElement = getGroupCallAudioElement(); + const audioContext = getGroupCallAudioContext(); + + if (!audioElement || !audioContext) { + return; + } + + const { + currentUserId, + } = global; + + if (!currentUserId) return; + + (async () => { + const params = await joinGroupCall(currentUserId, audioContext, audioElement, actions.apiUpdate); + + const result = await callApi('joinGroupCall', { + call: groupCall, + params, + inviteHash: groupCall.inviteHash, + }); + + if (!result) return; + + actions.loadMoreGroupCallParticipants(); + + if (groupCall.chatId) { + const chat = selectChat(getGlobal(), groupCall.chatId); + if (!chat) return; + await loadFullChat(chat); + } + })(); +}); diff --git a/src/modules/actions/api/chats.ts b/src/modules/actions/api/chats.ts index 5c359efec..0eec22192 100644 --- a/src/modules/actions/api/chats.ts +++ b/src/modules/actions/api/chats.ts @@ -42,12 +42,14 @@ import { selectCurrentMessageList, selectThreadInfo, selectCurrentChat, selectLastServiceNotification, } from '../../selectors'; -import { buildCollectionByKey } from '../../../util/iteratees'; +import { buildCollectionByKey, omit } from '../../../util/iteratees'; import { debounce, pause, throttle } from '../../../util/schedulers'; import { isChatSummaryOnly, isChatArchived, prepareChatList, isChatBasicGroup, } from '../../helpers'; import { processDeepLink } from '../../../util/deeplink'; +import { updateGroupCall } from '../../reducers/calls'; +import { selectGroupCall } from '../../selectors/calls'; const TOP_CHAT_MESSAGES_PRELOAD_INTERVAL = 100; const CHATS_PRELOAD_INTERVAL = 300; @@ -567,7 +569,13 @@ addReducer('openTelegramLink', (global, actions, payload) => { const chatOrChannelPostId = part2 ? Number(part2) : undefined; const messageId = part3 ? Number(part3) : undefined; const commentId = params.comment ? Number(params.comment) : undefined; - if (part1 === 'c' && chatOrChannelPostId && messageId) { + + if (params.hasOwnProperty('voicechat') || params.hasOwnProperty('livestream')) { + actions.joinVoiceChatByLink({ + username: part1, + inviteHash: params.voicechat || params.livestream, + }); + } else if (part1 === 'c' && chatOrChannelPostId && messageId) { actions.focusMessage({ chatId: -chatOrChannelPostId, messageId, @@ -869,7 +877,7 @@ addReducer('linkDiscussionGroup', (global, actions, payload) => { fullInfo = fullChat.fullInfo; } - if (fullInfo.isPreHistoryHidden) { + if (fullInfo!.isPreHistoryHidden) { await callApi('togglePreHistoryHidden', { chat, isEnabled: false }); } @@ -1031,21 +1039,35 @@ async function loadChats(listType: 'active' | 'archived', offsetId?: string, off setGlobal(global); } -async function loadFullChat(chat: ApiChat) { +export async function loadFullChat(chat: ApiChat) { const result = await callApi('fetchFullChat', chat); if (!result) { - return; + return undefined; } - const { users, fullInfo } = result; + const { users, fullInfo, groupCall } = result; let global = getGlobal(); if (users) { global = addUsers(global, buildCollectionByKey(users, 'id')); } + + if (groupCall) { + const existingGroupCall = selectGroupCall(global, groupCall.id!); + global = updateGroupCall( + global, + groupCall.id!, + omit(groupCall, ['connectionState']), + undefined, + existingGroupCall ? undefined : groupCall.participantsCount, + ); + } + global = updateChat(global, chat.id, { fullInfo }); setGlobal(global); + + return result; } async function createChannel(title: string, users: ApiUser[], about?: string, photo?: File) { @@ -1203,7 +1225,7 @@ async function deleteChatFolder(id: number) { await callApi('deleteChatFolder', id); } -async function fetchChatByUsername( +export async function fetchChatByUsername( username: string, ) { const global = getGlobal(); diff --git a/src/modules/actions/apiUpdaters/calls.ts b/src/modules/actions/apiUpdaters/calls.ts new file mode 100644 index 000000000..7ebcff41f --- /dev/null +++ b/src/modules/actions/apiUpdaters/calls.ts @@ -0,0 +1,60 @@ +import { addReducer, getGlobal } from '../../../lib/teact/teactn'; +import { ApiUpdate } from '../../../api/types'; +import { removeGroupCall, updateGroupCall, updateGroupCallParticipant } from '../../reducers/calls'; +import { omit } from '../../../util/iteratees'; +import { selectChat } from '../../selectors'; +import { updateChat } from '../../reducers'; + +addReducer('apiUpdate', (global, actions, update: ApiUpdate) => { + switch (update['@type']) { + case 'updateGroupCall': { + if (update.call.connectionState === 'discarded') { + if (global.groupCalls.activeGroupCallId) { + actions.leaveGroupCall({ shouldRemove: true }); + return undefined; + } else { + return removeGroupCall(global, update.call.id); + } + } + + return updateGroupCall(global, + update.call.id, + omit(update.call, ['connectionState']), + undefined, + update.call.participantsCount); + } + case 'updateGroupCallChatId': { + const chat = selectChat(global, update.chatId); + if (chat) { + global = updateChat(global, update.chatId, { + fullInfo: { + ...chat.fullInfo, + groupCallId: update.call.id, + }, + }); + } + return global; + } + case 'updateGroupCallParticipants': { + const { groupCallId, participants, nextOffset } = update; + const { currentUserId } = global; + + // `secret-sauce` should disconnect if the participant is us but from another device + global = getGlobal(); + participants.forEach((participant) => { + if (participant.id) { + global = updateGroupCallParticipant(global, groupCallId, participant.id, participant, + !!nextOffset || currentUserId === participant.id); + } + }); + if (nextOffset) { + global = updateGroupCall(global, groupCallId, { + nextOffset, + }); + } + return global; + } + } + + return undefined; +}); diff --git a/src/modules/actions/calls.ts b/src/modules/actions/calls.ts new file mode 100644 index 000000000..5828e6c0a --- /dev/null +++ b/src/modules/actions/calls.ts @@ -0,0 +1 @@ +import './api/calls.async'; diff --git a/src/modules/actions/ui/calls.ts b/src/modules/actions/ui/calls.ts new file mode 100644 index 000000000..2f07fcc0b --- /dev/null +++ b/src/modules/actions/ui/calls.ts @@ -0,0 +1,311 @@ +import { addReducer, getGlobal, setGlobal } from '../../../lib/teact/teactn'; +import { selectActiveGroupCall, selectChatGroupCall, selectGroupCall } from '../../selectors/calls'; +import { callApi } from '../../../api/gramjs'; +import { selectChat } from '../../selectors'; +import { copyTextToClipboard } from '../../../util/clipboard'; +import { ApiGroupCall } from '../../../api/types'; +import { updateGroupCall } from '../../reducers/calls'; +import { buildCollectionByKey, omit } from '../../../util/iteratees'; +import { addChats, addUsers } from '../../reducers'; +import { fetchChatByUsername, loadFullChat } from '../api/chats'; +import safePlay from '../../../util/safePlay'; +import { ARE_CALLS_SUPPORTED } from '../../../util/environment'; +import * as langProvider from '../../../util/langProvider'; + +// Workaround for Safari not playing audio without user interaction +let audioElement: HTMLAudioElement | undefined; +let audioContext: AudioContext | undefined; + +const joinAudio = new Audio('./voicechat_join.mp3'); +const connectingAudio = new Audio('./voicechat_connecting.mp3'); +connectingAudio.loop = true; +const leaveAudio = new Audio('./voicechat_leave.mp3'); +const allowTalkAudio = new Audio('./voicechat_onallowtalk.mp3'); + +const sounds: Record = { + join: joinAudio, + allowTalk: allowTalkAudio, + leave: leaveAudio, + connecting: connectingAudio, +}; + +let initializationPromise: Promise | undefined = Promise.resolve(); + +const initializeSoundsForSafari = () => { + if (!initializationPromise) return Promise.resolve(); + + initializationPromise = Promise.all(Object.values(sounds).map((l) => { + l.muted = true; + l.volume = 0.0001; + return l.play().then(() => { + l.pause(); + l.volume = 1; + l.currentTime = 0; + l.muted = false; + }); + })).then(() => { + initializationPromise = undefined; + }); + + return initializationPromise; +}; + +async function fetchGroupCall(groupCall: Partial) { + const result = await callApi('getGroupCall', { + call: groupCall, + }); + + if (!result) return undefined; + + let global = getGlobal(); + + const existingGroupCall = selectGroupCall(global, groupCall.id!); + + global = updateGroupCall(global, + groupCall.id!, + omit(result.groupCall, ['connectionState']), + undefined, + existingGroupCall?.isLoaded ? undefined : result.groupCall.participantsCount); + global = addUsers(global, buildCollectionByKey(result.users, 'id')); + global = addChats(global, buildCollectionByKey(result.chats, 'id')); + + setGlobal(global); + + return result.groupCall; +} + +async function fetchGroupCallParticipants(groupCall: Partial, nextOffset?: string) { + const result = await callApi('fetchGroupCallParticipants', { + call: groupCall as ApiGroupCall, + offset: nextOffset, + }); + + if (!result) return; + + let global = getGlobal(); + + global = addUsers(global, buildCollectionByKey(result.users, 'id')); + global = addChats(global, buildCollectionByKey(result.chats, 'id')); + + setGlobal(global); +} + +addReducer('toggleGroupCallPanel', (global) => { + return { + ...global, + groupCalls: { + ...global.groupCalls, + isGroupCallPanelHidden: !global.groupCalls.isGroupCallPanelHidden, + }, + }; +}); + +addReducer('subscribeToGroupCallUpdates', (global, actions, payload) => { + const { subscribed, id } = payload!; + const groupCall = selectGroupCall(global, id); + + if (!groupCall) return; + + (async () => { + if (subscribed) { + await fetchGroupCall(groupCall); + await fetchGroupCallParticipants(groupCall); + } + + await callApi('toggleGroupCallStartSubscription', { + subscribed, + call: groupCall, + }); + })(); +}); + +addReducer('createGroupCall', (global, actions, payload) => { + const { chatId } = payload; + + const chat = selectChat(global, chatId); + if (!chat) { + return; + } + + (async () => { + const result = await callApi('createGroupCall', { + peer: chat, + }); + + if (!result) return; + + global = getGlobal(); + setGlobal(updateGroupCall(global, result.id, { + ...result, + chatId, + })); + + actions.joinGroupCall({ id: result.id, accessHash: result.accessHash }); + })(); +}); + +addReducer('createGroupCallInviteLink', (global, actions) => { + const groupCall = selectActiveGroupCall(global); + + if (!groupCall) { + return; + } + + (async () => { + const result = await callApi('exportGroupCallInvite', { + call: groupCall, + canSelfUnmute: false, + }); + + if (!result) return; + + copyTextToClipboard(result); + actions.showNotification({ + message: 'Link copied to clipboard', + }); + })(); +}); + +addReducer('joinVoiceChatByLink', (global, actions, payload) => { + const { username, inviteHash } = payload!; + + (async () => { + const chat = await fetchChatByUsername(username); + + if (!chat) { + actions.showNotification({ message: langProvider.getTranslation('NoUsernameFound') }); + return; + } + + const full = await loadFullChat(chat); + + if (full?.groupCall) { + actions.joinGroupCall({ id: full.groupCall.id, accessHash: full.groupCall.accessHash, inviteHash }); + } + })(); +}); + +addReducer('joinGroupCall', (global, actions, payload) => { + if (!ARE_CALLS_SUPPORTED) return; + + const { + chatId, id, accessHash, inviteHash, + } = payload; + + createAudioElement(); + + (async () => { + await initializeSoundsForSafari(); + const { groupCalls: { activeGroupCallId } } = global; + let groupCall = id ? selectGroupCall(global, id) : selectChatGroupCall(global, chatId); + + if (groupCall?.id === activeGroupCallId) { + actions.toggleGroupCallPanel(); + return; + } + + if (activeGroupCallId) { + actions.leaveGroupCall({ + rejoin: payload, + }); + return; + } + + if (groupCall && activeGroupCallId === groupCall.id) { + actions.toggleGroupCallPanel(); + return; + } + + if (!groupCall && (!id || !accessHash)) { + groupCall = await fetchGroupCall({ + id, + accessHash, + }); + } + + if (!groupCall) return; + + global = getGlobal(); + + global = updateGroupCall( + global, + groupCall.id, + { + ...groupCall, + inviteHash, + }, + undefined, + groupCall.participantsCount + 1, + ); + + setGlobal({ + ...global, + groupCalls: { + ...global.groupCalls, + activeGroupCallId: groupCall.id, + isGroupCallPanelHidden: false, + }, + }); + })(); +}); + +addReducer('playGroupCallSound', (global, actions, payload) => { + const { sound } = payload!; + + if (!sounds[sound]) { + return; + } + + if (initializationPromise) { + initializationPromise.then(() => { + safePlay(sounds[sound]); + }); + } else { + if (sound !== 'connecting') { + sounds.connecting.pause(); + } + safePlay(sounds[sound]); + } +}); + +addReducer('loadMoreGroupCallParticipants', (global) => { + const groupCall = selectActiveGroupCall(global); + if (!groupCall) { + return; + } + + void fetchGroupCallParticipants(groupCall, groupCall.nextOffset); +}); + +function createAudioContext() { + return (new (window.AudioContext || (window as any).webkitAudioContext)()); +} + +const silence = (ctx: AudioContext) => { + const oscillator = ctx.createOscillator(); + const dst = oscillator.connect(ctx.createMediaStreamDestination()); + oscillator.start(); + return new MediaStream([Object.assign((dst as any).stream.getAudioTracks()[0], { enabled: false })]); +}; + +function createAudioElement() { + const ctx = createAudioContext(); + audioElement = new Audio(); + audioContext = ctx; + audioElement.srcObject = silence(ctx); + safePlay(audioElement); +} + +export function getGroupCallAudioElement() { + return audioElement; +} + +export function getGroupCallAudioContext() { + return audioContext; +} + +export function removeGroupCallAudioElement() { + audioElement?.pause(); + audioContext = undefined; + audioElement = undefined; +} diff --git a/src/modules/reducers/calls.ts b/src/modules/reducers/calls.ts new file mode 100644 index 000000000..41250737f --- /dev/null +++ b/src/modules/reducers/calls.ts @@ -0,0 +1,115 @@ +import { GroupCallParticipant } from '../../lib/secret-sauce'; +import { GlobalState } from '../../global/types'; +import { ApiGroupCall } from '../../api/types'; +import { selectGroupCall } from '../selectors/calls'; +import { omit } from '../../util/iteratees'; +import { updateChat } from './chats'; +import { selectChat } from '../selectors'; + +export function updateGroupCall( + global: GlobalState, + groupCallId: string, + groupCallUpdate: Partial, + addToParticipantCount?: number, + resetParticipantCount?: number, +): GlobalState { + const unfiltered = Object.values({ + ...global.groupCalls.byId[groupCallId]?.participants, + ...groupCallUpdate.participants, + }); + const filtered = unfiltered.filter((l) => !l.isLeft); + const participants = filtered.reduce((acc: Record, el) => { + acc[el.id] = el; + return acc; + }, {}); + + return { + ...global, + groupCalls: { + ...global.groupCalls, + byId: { + ...global.groupCalls.byId, + [groupCallId]: { + ...global.groupCalls.byId[groupCallId], + ...omit(groupCallUpdate, ['participantsCount']), + ...(addToParticipantCount && { + participantsCount: global.groupCalls.byId[groupCallId].participantsCount + addToParticipantCount, + }), + ...(resetParticipantCount !== undefined && { + participantsCount: resetParticipantCount, + }), + participants, + }, + }, + }, + }; +} + +export function removeGroupCall( + global: GlobalState, + groupCallId: string, +): GlobalState { + const groupCall = selectGroupCall(global, groupCallId); + if (groupCall && groupCall.chatId) { + const chat = selectChat(global, groupCall.chatId); + if (chat) { + global = updateChat(global, groupCall.chatId, { + fullInfo: { + ...chat.fullInfo, + groupCallId: undefined, + }, + }); + } + } + + return { + ...global, + groupCalls: { + ...global.groupCalls, + byId: { + ...omit(global.groupCalls.byId, [groupCallId.toString()]), + }, + }, + }; +} + +export function updateActiveGroupCall( + global: GlobalState, + groupCallUpdate: Partial, + resetParticipantCount?: number, +): GlobalState { + if (!global.groupCalls.activeGroupCallId) { + return global; + } + + return updateGroupCall(global, + global.groupCalls.activeGroupCallId, + groupCallUpdate, + undefined, + resetParticipantCount); +} + +export function updateGroupCallParticipant( + global: GlobalState, + groupCallId: string, + userId: string, + participantUpdate: Partial, + noUpdateCount = false, +) { + const groupCall = selectGroupCall(global, groupCallId); + if (!groupCall) { + return global; + } + + return updateGroupCall(global, groupCallId, { + participants: { + ...groupCall.participants, + [userId]: { + ...groupCall.participants[userId], + ...participantUpdate, + }, + }, + }, participantUpdate.isLeft + ? (noUpdateCount ? 0 : -1) + : (groupCall.participants[userId] || noUpdateCount ? 0 : 1)); +} diff --git a/src/modules/selectors/calls.ts b/src/modules/selectors/calls.ts new file mode 100644 index 000000000..7061f2136 --- /dev/null +++ b/src/modules/selectors/calls.ts @@ -0,0 +1,38 @@ +import { GlobalState } from '../../global/types'; +import { selectChat } from './chats'; +import { isChatBasicGroup } from '../helpers'; + +export function selectChatGroupCall(global: GlobalState, chatId: string) { + const chat = selectChat(global, chatId); + if (!chat || !chat.fullInfo || !chat.fullInfo.groupCallId) return undefined; + + return selectGroupCall(global, chat.fullInfo.groupCallId); +} + +export function selectGroupCall(global: GlobalState, groupCallId: string) { + return global.groupCalls.byId[groupCallId]; +} + +export function selectGroupCallParticipant(global: GlobalState, groupCallId: string, participantId: string) { + return selectGroupCall(global, groupCallId)?.participants[participantId]; +} + +export function selectIsAdminInActiveGroupCall(global: GlobalState): boolean { + const chatId = selectActiveGroupCall(global)?.chatId; + + if (!chatId) return false; + + const chat = selectChat(global, chatId); + if (!chat) return false; + + return (isChatBasicGroup(chat) && chat.isCreator) || !!chat.adminRights?.manageCall; +} + +export function selectActiveGroupCall(global: GlobalState) { + const { groupCalls: { activeGroupCallId } } = global; + if (!activeGroupCallId) { + return undefined; + } + + return selectGroupCall(global, activeGroupCallId); +} diff --git a/src/styles/Telegram T.json b/src/styles/Telegram T.json index 68ad76f86..e8ffbd808 100644 --- a/src/styles/Telegram T.json +++ b/src/styles/Telegram T.json @@ -2,57 +2,57 @@ "metadata": { "name": "Telegram T", "lastOpened": 0, - "created": 1635725227633 + "created": 1637672665426 }, "iconSets": [ { "selection": [ { "order": 673, - "id": 5, "name": "loop", "prevSize": 32, - "code": 59777, + "id": 0, + "code": 59788, "tempChar": "" }, { "order": 672, - "id": 4, "name": "skip-next", "prevSize": 32, - "code": 59778, + "id": 1, + "code": 59789, "tempChar": "" }, { "order": 671, - "id": 3, "name": "skip-previous", "prevSize": 32, - "code": 59779, + "id": 2, + "code": 59790, "tempChar": "" }, { "order": 674, - "id": 2, "name": "volume-1", "prevSize": 32, - "code": 59780, + "id": 3, + "code": 59791, "tempChar": "" }, { "order": 669, - "id": 1, "name": "volume-2", "prevSize": 32, - "code": 59781, + "id": 4, + "code": 59792, "tempChar": "" }, { "order": 676, - "id": 0, "name": "volume-3", "prevSize": 32, - "code": 59782, + "id": 5, + "code": 59793, "tempChar": "" } ], @@ -68,108 +68,198 @@ "prevSize": 32, "icons": [ { - "id": 5, + "id": 0, "paths": [ "M298.667 298.667h426.667v128l170.667-170.667-170.667-170.667v128h-512v256h85.333v-170.667zM725.333 725.333h-426.667v-128l-170.667 170.667 170.667 170.667v-128h512v-256h-85.333v170.667z" ], - "attrs": [ - {} - ], + "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "grid": 24, "tags": [ "loop" - ] - }, - { - "id": 4, - "paths": [ - "M256 768l362.667-256-362.667-256v512zM682.667 256v512h85.333v-512h-85.333z" ], - "attrs": [ - {} - ], - "isMulticolor": false, - "isMulticolor2": false, - "grid": 24, - "tags": [ - "skip-next" - ] - }, - { - "id": 3, - "paths": [ - "M256 256h85.333v512h-85.333zM405.333 512l362.667 256v-512z" - ], - "attrs": [ - {} - ], - "isMulticolor": false, - "isMulticolor2": false, - "grid": 24, - "tags": [ - "skip-previous" - ] - }, - { - "id": 2, - "paths": [ - "M558.623 229.209c5.383 8.831 8.569 19.512 8.569 30.939v509.203c0 33.212-26.923 60.135-60.135 60.135-11.427 0-22.108-3.187-31.207-8.72l0.267 0.149-160.939-96.567c-29.352-17.832-48.66-49.631-48.66-85.94v-247.323c0-36.309 19.309-68.108 48.217-85.692l0.443-0.251 160.939-96.565c28.48-17.087 65.419-7.853 82.505 20.627v0.005zM146.653 334.345c33.212 0 60.135 26.923 60.135 60.135v240.54c0 33.212-26.923 60.135-60.135 60.135s-60.135-26.923-60.135-60.135v-240.54c0-33.212 26.923-60.135 60.135-60.135v0zM554.485 623.967h-0.040c-3.997 0.005-7.956-0.781-11.648-2.313s-7.045-3.78-9.864-6.613l-0.001-0.001c-11.909-11.909-11.909-187.531 0-199.44 11.911-11.911 31.219-11.911 43.107 0 26.637 26.636 41.295 62.059 41.295 99.697 0 37.688-14.657 73.084-41.293 99.72-2.823 2.841-6.181 5.095-9.88 6.631-3.699 1.537-7.665 2.325-11.671 2.32h-0.004z" - ], - "attrs": [ - {} - ], - "grid": 24, - "tags": [ - "volume-1" - ], - "isMulticolor": false, - "isMulticolor2": false + "defaultCode": 59777, + "grid": 24 }, { "id": 1, + "paths": [ + "M256 768l362.667-256-362.667-256v512zM682.667 256v512h85.333v-512h-85.333z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "skip-next" + ], + "defaultCode": 59778, + "grid": 24 + }, + { + "id": 2, + "paths": [ + "M256 256h85.333v512h-85.333zM405.333 512l362.667 256v-512z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "skip-previous" + ], + "defaultCode": 59779, + "grid": 24 + }, + { + "id": 3, + "paths": [ + "M558.623 229.209c5.383 8.831 8.569 19.512 8.569 30.939v509.203c0 33.212-26.923 60.135-60.135 60.135-11.427 0-22.108-3.187-31.207-8.72l0.267 0.149-160.939-96.567c-29.352-17.832-48.66-49.631-48.66-85.94v-247.323c0-36.309 19.309-68.108 48.217-85.692l0.443-0.251 160.939-96.565c28.48-17.087 65.419-7.853 82.505 20.627v0.005zM146.653 334.345c33.212 0 60.135 26.923 60.135 60.135v240.54c0 33.212-26.923 60.135-60.135 60.135s-60.135-26.923-60.135-60.135v-240.54c0-33.212 26.923-60.135 60.135-60.135v0zM554.485 623.967h-0.040c-3.997 0.005-7.956-0.781-11.648-2.313s-7.045-3.78-9.864-6.613l-0.001-0.001c-11.909-11.909-11.909-187.531 0-199.44 11.911-11.911 31.219-11.911 43.107 0 26.637 26.636 41.295 62.059 41.295 99.697 0 37.688-14.657 73.084-41.293 99.72-2.823 2.841-6.181 5.095-9.88 6.631-3.699 1.537-7.665 2.325-11.671 2.32h-0.004z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "volume-1" + ], + "defaultCode": 59780, + "grid": 24 + }, + { + "id": 4, "paths": [ "M558.623 229.209c5.383 8.831 8.569 19.512 8.569 30.939v509.203c0 33.212-26.923 60.135-60.135 60.135-11.427 0-22.108-3.187-31.207-8.72l0.267 0.149-160.939-96.567c-29.352-17.832-48.66-49.631-48.66-85.94v-247.323c0-36.309 19.309-68.108 48.217-85.692l0.443-0.251 160.939-96.565c28.48-17.087 65.419-7.853 82.505 20.627v0.005zM146.653 334.345c33.212 0 60.135 26.923 60.135 60.135v240.54c0 33.212-26.923 60.135-60.135 60.135s-60.135-26.923-60.135-60.135v-240.54c0-33.212 26.923-60.135 60.135-60.135zM554.485 623.967h-0.040c-0.012 0-0.026 0-0.040 0-8.391 0-15.985-3.413-21.47-8.925l-0.001-0.001-0.001-0.001c-11.909-11.909-11.909-187.531 0-199.44 11.911-11.911 31.219-11.911 43.107 0 26.637 26.636 41.295 62.059 41.295 99.697 0 37.688-14.657 73.084-41.293 99.72-5.493 5.529-13.101 8.951-21.508 8.951-0.015 0-0.030-0-0.045-0l0.002 0h-0.004zM659.088 707.369h-0.024c-0.012 0-0.027 0-0.041 0-9.339 0-17.792-3.794-23.902-9.926l-0.001-0.001c-13.221-13.245-13.221-34.696 0-47.941 74.168-74.14 74.139-194.815 0-268.96-13.247-13.245-13.247-34.695 0-47.94 13.221-13.247 34.695-13.247 47.941 0 100.605 100.607 100.605 264.259 0.024 364.812-6.663 6.637-15.315 9.956-23.997 9.956z" ], - "attrs": [ - {} - ], - "grid": 24, + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, "tags": [ "volume-2" ], - "isMulticolor": false, - "isMulticolor2": false + "defaultCode": 59781, + "grid": 24 }, { - "id": 0, + "id": 5, "paths": [ "M558.623 229.209c5.383 8.831 8.569 19.512 8.569 30.939v509.203c0 33.212-26.923 60.135-60.135 60.135-11.427 0-22.108-3.187-31.207-8.72l0.267 0.149-160.939-96.567c-29.352-17.832-48.66-49.631-48.66-85.94v-247.323c0-36.309 19.309-68.108 48.217-85.692l0.443-0.251 160.939-96.565c28.48-17.087 65.419-7.853 82.505 20.627v0.005zM146.653 334.345c33.212 0 60.135 26.923 60.135 60.135v240.54c0 33.212-26.923 60.135-60.135 60.135s-60.135-26.923-60.135-60.135v-240.54c0-33.212 26.923-60.135 60.135-60.135zM554.485 623.967h-0.040c-0.012 0-0.026 0-0.040 0-8.391 0-15.985-3.413-21.47-8.925l-0.001-0.001-0.001-0.001c-11.909-11.909-11.909-187.531 0-199.44 11.911-11.911 31.219-11.911 43.107 0 26.637 26.636 41.295 62.059 41.295 99.697 0 37.688-14.657 73.084-41.293 99.72-5.493 5.529-13.101 8.951-21.508 8.951-0.015 0-0.030-0-0.045-0l0.002 0h-0.004zM659.088 707.369h-0.024c-0.012 0-0.027 0-0.041 0-9.339 0-17.792-3.794-23.902-9.926l-0.001-0.001c-13.221-13.245-13.221-34.696 0-47.941 74.168-74.14 74.139-194.815 0-268.96-13.247-13.245-13.247-34.695 0-47.94 13.221-13.247 34.695-13.247 47.941 0 100.605 100.607 100.605 264.259 0.024 364.812-6.663 6.637-15.315 9.956-23.997 9.956zM740.708 792.089c-13.341-13.339-13.341-34.941 0-48.283 61.275-61.275 95.013-142.712 95.013-229.413s-33.769-168.164-95.067-229.439c-13.317-13.34-13.317-34.943 0-48.283 13.368-13.34 34.967-13.34 48.307 0 74.187 74.187 115.039 172.789 115.039 277.721s-40.821 203.533-115.013 277.667c-6.167 6.195-14.699 10.029-24.128 10.029h-0.037c-8.715 0-17.429-3.344-24.113-10z" ], - "attrs": [ - {} - ], - "grid": 24, + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, "tags": [ "volume-3" ], - "isMulticolor": false, - "isMulticolor2": false + "defaultCode": 59782, + "grid": 24 } ], - "invisible": false, - "colorThemes": [] + "colorThemes": [], + "colorThemeIdx": 0 }, { "selection": [ + { + "order": 693, + "id": 46, + "name": "sidebar", + "prevSize": 32, + "code": 59794, + "tempChar": "" + }, + { + "order": 690, + "id": 45, + "name": "video-stop", + "prevSize": 32, + "code": 59787, + "tempChar": "" + }, + { + "order": 678, + "id": 44, + "name": "speaker", + "prevSize": 32, + "code": 59777, + "tempChar": "" + }, + { + "order": 679, + "id": 43, + "name": "speaker-outline", + "prevSize": 32, + "code": 59778, + "tempChar": "" + }, + { + "order": 680, + "id": 42, + "name": "phone-discard-outline", + "prevSize": 32, + "code": 59779, + "tempChar": "" + }, + { + "order": 681, + "id": 41, + "name": "allow-speak", + "prevSize": 32, + "code": 59780, + "tempChar": "" + }, + { + "order": 682, + "id": 40, + "name": "stop-raising-hand", + "prevSize": 32, + "code": 59781, + "tempChar": "" + }, + { + "order": 683, + "id": 39, + "name": "share-screen", + "prevSize": 32, + "code": 59782, + "tempChar": "" + }, + { + "order": 684, + "id": 38, + "name": "voice-chat", + "prevSize": 32, + "code": 59783, + "tempChar": "" + }, + { + "order": 689, + "id": 37, + "name": "video", + "prevSize": 32, + "code": 59784, + "tempChar": "" + }, + { + "order": 686, + "id": 36, + "name": "noise-suppression", + "prevSize": 32, + "code": 59785, + "tempChar": "" + }, + { + "order": 688, + "id": 35, + "name": "phone-discard", + "prevSize": 32, + "code": 59786, + "tempChar": "" + }, { "order": 667, "id": 34, "name": "bot-commands-filled", "prevSize": 32, "code": 59775, - "tempChar": "" + "tempChar": "" }, { "order": 664, @@ -177,7 +267,7 @@ "name": "reply-filled", "prevSize": 32, "code": 59776, - "tempChar": "" + "tempChar": "" }, { "order": 656, @@ -185,7 +275,7 @@ "name": "bug", "prevSize": 32, "code": 59774, - "tempChar": "" + "tempChar": "" }, { "order": 619, @@ -193,7 +283,7 @@ "name": "data", "prevSize": 32, "code": 59773, - "tempChar": "" + "tempChar": "" }, { "order": 622, @@ -201,7 +291,7 @@ "name": "darkmode", "prevSize": 32, "code": 59769, - "tempChar": "" + "tempChar": "" }, { "order": 623, @@ -209,7 +299,7 @@ "name": "animations", "prevSize": 32, "code": 59770, - "tempChar": "" + "tempChar": "" }, { "order": 626, @@ -217,7 +307,7 @@ "name": "enter", "prevSize": 32, "code": 59771, - "tempChar": "" + "tempChar": "" }, { "order": 627, @@ -225,7 +315,7 @@ "name": "fontsize", "prevSize": 32, "code": 59772, - "tempChar": "" + "tempChar": "" }, { "order": 630, @@ -233,7 +323,7 @@ "name": "permissions", "prevSize": 32, "code": 59766, - "tempChar": "" + "tempChar": "" }, { "order": 631, @@ -241,7 +331,7 @@ "name": "card", "prevSize": 32, "code": 59767, - "tempChar": "" + "tempChar": "" }, { "order": 634, @@ -249,7 +339,7 @@ "name": "truck", "prevSize": 32, "code": 59768, - "tempChar": "" + "tempChar": "" }, { "order": 663, @@ -257,7 +347,7 @@ "name": "share-filled", "prevSize": 32, "code": 59738, - "tempChar": "" + "tempChar": "" }, { "order": 638, @@ -265,7 +355,7 @@ "name": "bold", "prevSize": 32, "code": 59745, - "tempChar": "" + "tempChar": "" }, { "order": 639, @@ -273,7 +363,7 @@ "name": "bot-command", "prevSize": 32, "code": 59746, - "tempChar": "" + "tempChar": "" }, { "order": 642, @@ -281,7 +371,7 @@ "name": "calendar-filter", "prevSize": 32, "code": 59747, - "tempChar": "" + "tempChar": "" }, { "order": 643, @@ -289,7 +379,7 @@ "name": "comments", "prevSize": 32, "code": 59748, - "tempChar": "" + "tempChar": "" }, { "order": 645, @@ -297,7 +387,7 @@ "name": "comments-sticker", "prevSize": 32, "code": 59749, - "tempChar": "" + "tempChar": "" }, { "order": 646, @@ -305,15 +395,15 @@ "name": "arrow-down", "prevSize": 32, "code": 59750, - "tempChar": "" + "tempChar": "" }, { - "order": 647, + "order": 668, "id": 14, "name": "email", "prevSize": 32, "code": 59751, - "tempChar": "" + "tempChar": "" }, { "order": 648, @@ -321,7 +411,7 @@ "name": "italic", "prevSize": 32, "code": 59752, - "tempChar": "" + "tempChar": "" }, { "order": 620, @@ -329,7 +419,7 @@ "name": "link", "prevSize": 32, "code": 59753, - "tempChar": "" + "tempChar": "" }, { "order": 621, @@ -337,7 +427,7 @@ "name": "mention", "prevSize": 32, "code": 59754, - "tempChar": "" + "tempChar": "" }, { "order": 624, @@ -345,7 +435,7 @@ "name": "monospace", "prevSize": 32, "code": 59755, - "tempChar": "" + "tempChar": "" }, { "order": 625, @@ -353,7 +443,7 @@ "name": "next", "prevSize": 32, "code": 59756, - "tempChar": "" + "tempChar": "" }, { "order": 628, @@ -361,7 +451,7 @@ "name": "password-off", "prevSize": 32, "code": 59757, - "tempChar": "" + "tempChar": "" }, { "order": 629, @@ -369,7 +459,7 @@ "name": "pin-list", "prevSize": 32, "code": 59758, - "tempChar": "" + "tempChar": "" }, { "order": 632, @@ -377,7 +467,7 @@ "name": "previous", "prevSize": 32, "code": 59759, - "tempChar": "" + "tempChar": "" }, { "order": 633, @@ -385,7 +475,7 @@ "name": "replace", "prevSize": 32, "code": 59760, - "tempChar": "" + "tempChar": "" }, { "order": 636, @@ -393,23 +483,23 @@ "name": "schedule", "prevSize": 32, "code": 59761, - "tempChar": "" + "tempChar": "" }, { - "order": 637, + "order": 691, "id": 3, "name": "strikethrough", "prevSize": 32, "code": 59762, - "tempChar": "" + "tempChar": "" }, { - "order": 640, + "order": 692, "id": 2, "name": "underlined", "prevSize": 32, "code": 59763, - "tempChar": "" + "tempChar": "" }, { "order": 641, @@ -417,7 +507,7 @@ "name": "zoom-in", "prevSize": 32, "code": 59764, - "tempChar": "" + "tempChar": "" }, { "order": 649, @@ -425,20 +515,250 @@ "name": "zoom-out", "prevSize": 32, "code": 59765, - "tempChar": "" + "tempChar": "" } ], "id": 2, "metadata": { "name": "Untitled Set", "importSize": { - "width": 22, - "height": 22 + "width": 768, + "height": 768 } }, "height": 1024, "prevSize": 32, "icons": [ + { + "id": 46, + "paths": [ + "M868 886.533h-734.4c-43.867 0-79.467-35.733-79.467-79.467v-569.067c0-43.867 35.733-79.467 79.467-79.467h734.4c43.867 0 79.467 35.733 79.467 79.467v569.067c0 43.733-35.733 79.467-79.467 79.467zM133.6 211.733c-14.4 0-26.133 11.733-26.133 26.133v569.067c0 14.4 11.733 26.133 26.133 26.133h734.4c14.4 0 26.133-11.733 26.133-26.133v-568.933c0-14.4-11.733-26.133-26.133-26.133h-734.4z", + "M346.667 185.067h53.333v674.667h-53.333v-674.667z", + "M272.533 549.2h-109.2c-14.667 0-26.667-12-26.667-26.667s12-26.667 26.667-26.667h109.2c14.667 0 26.667 12 26.667 26.667s-12 26.667-26.667 26.667z", + "M272.533 455.067h-107.867c-14.667 0-26.667-12-26.667-26.667s12-26.667 26.667-26.667h107.867c14.667 0 26.667 12 26.667 26.667s-12 26.667-26.667 26.667z", + "M272.533 356.4h-107.867c-14.667 0-26.667-12-26.667-26.667s12-26.667 26.667-26.667h107.867c14.667 0 26.667 12 26.667 26.667s-12 26.667-26.667 26.667z" + ], + "attrs": [ + {}, + {}, + {}, + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 24, + "tags": [ + "sidebar" + ] + }, + { + "id": 45, + "paths": [ + "M694.667 811.6c18.933-19.2 30.667-45.733 30.667-74.8v-406.933c0-58.933-47.733-106.667-106.667-106.667h-512c-0.133 0-0.4 0-0.533 0l588.533 588.4z", + "M42.667 244.533c-25.867 19.467-42.667 50.4-42.667 85.333v407.067c0 58.933 47.733 106.667 106.667 106.667h512c7.2 0 14.133-0.667 20.933-2.133l-596.933-596.933z", + "M806.8 477.067v104.133c0 40.533 0.8 46.133 27.733 72.933l128.4 128.4c8.933 8.933 34.933 8.933 44.4 0l15.733-15.733v-464.667l-15.733-16.267c-10.267-10.267-36.267-9.333-45.6 0l-130.133 130.933c-18.8 18.4-24.8 26.933-24.8 60.267v0z", + "M858.4 964.933c-7.733 0-15.333-2.933-21.2-8.8l-771.733-771.733c-11.733-11.733-11.733-30.667 0-42.4s30.667-11.733 42.4 0l771.733 771.733c11.733 11.733 11.733 30.667 0 42.4-5.867 5.867-13.467 8.8-21.2 8.8z" + ], + "attrs": [ + {}, + {}, + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 24, + "tags": [ + "video-stop" + ] + }, + { + "id": 44, + "paths": [ + "M781.227 139.947c146.773 69.973 241.493 220.16 241.493 383.147 0 162.56-94.72 312.747-241.92 382.72-5.547 2.987-11.947 4.267-17.92 4.267-16.213 0-31.573-8.96-38.827-24.32-10.24-21.333-0.853-46.507 20.053-56.747 117.333-55.893 193.28-176.213 193.28-305.92 0-130.133-75.947-250.027-193.28-305.92-20.907-10.24-30.293-35.84-20.053-57.173s35.413-30.293 57.173-20.053z", + "M757.333 525.227c0-48.213-21.333-93.013-58.88-123.733-18.347-14.507-21.333-41.387-6.4-59.733s41.813-21.333 60.16-6.4c57.6 46.507 90.453 116.053 90.453 189.867 0 70.827-30.293 137.813-84.053 184.32-8.107 6.827-17.92 10.667-27.733 10.667-11.947 0-23.893-5.12-32.427-14.933-15.36-17.493-13.653-44.8 4.267-60.16 34.987-30.293 54.613-73.813 54.613-119.893z", + "M586.667 182.613v672.853l-5.973 10.24c-26.88 46.080-60.587 60.16-84.48 64-5.12 0.853-10.667 1.28-15.787 1.28-46.080 0-80.64-30.293-84.907-34.133l-149.76-149.76h-112.64c-70.4 0-128-57.173-128-127.573v-200.533c0-70.4 57.6-128 128-128h112.64l148.053-148.053c6.4-5.973 49.067-42.667 102.4-34.133 23.893 3.84 57.6 17.92 84.48 64l5.973 9.813z" + ], + "attrs": [ + {}, + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 24, + "tags": [ + "speaker" + ] + }, + { + "id": 43, + "paths": [ + "M733.013 709.12c-11.947 0-23.893-5.12-32-14.507-15.36-17.92-13.653-44.8 4.267-60.16 34.56-30.293 54.613-73.813 54.613-119.893 0-48.213-21.333-93.44-58.88-123.733-18.347-14.933-21.333-41.813-6.4-60.16s41.813-21.333 60.16-6.4c57.6 46.507 90.88 116.053 90.88 189.867 0 70.827-30.72 137.813-84.053 184.32-8.96 7.253-18.773 10.667-28.587 10.667z", + "M764.587 898.987c-15.787 0-31.147-8.96-38.4-24.32-10.24-21.333-1.28-46.507 20.053-56.747 117.333-55.893 193.28-176.213 193.28-305.92 0-130.133-75.947-250.027-193.28-305.92-21.333-10.24-30.293-35.413-20.053-56.747s35.413-30.293 56.747-20.053c146.773 69.973 241.92 220.587 241.92 383.147s-94.72 313.173-241.92 383.147c-5.973 2.133-11.947 3.413-18.347 3.413z", + "M582.827 161.707c-26.88-46.080-60.587-60.16-84.48-64-53.333-8.107-96.427 28.587-102.4 34.133l-148.48 148.48h-112.213c-70.827 0-128 57.173-128 128v200.107c0 70.4 57.173 128 128 128h112.213l149.76 149.76c4.267 3.84 39.253 33.707 85.333 33.707 5.12 0 10.24-0.427 15.787-1.28 23.893-3.413 57.6-17.92 84.48-64l5.973-9.813v-672.853l-5.973-10.24zM503.467 820.48c-5.12 6.827-11.52 12.8-17.92 13.653-11.093 1.707-26.027-7.253-29.44-9.813l-173.227-173.227h-147.627c-23.467 0-42.667-19.2-42.667-42.667v-200.107c0-23.893 19.2-42.667 42.667-42.667h147.627l171.093-171.52c5.547-4.693 20.48-14.080 31.573-11.947 6.4 1.28 12.8 7.253 17.92 13.653v624.64z" + ], + "attrs": [ + {}, + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 24, + "tags": [ + "speaker-outline" + ] + }, + { + "id": 42, + "paths": [ + "M989.867 456.96c-236.373-208.64-715.52-208.64-951.893 0-44.373 39.253-45.227 108.8-3.413 150.613l46.933 46.933c37.12 33.707 93.867 35.413 133.973 3.84l69.973-55.467 5.12-4.267c21.76-19.627 34.133-47.787 34.133-77.227l7.253-111.36 16.213-3.413c73.813-14.080 273.92-12.8 347.307 3.413h0.427l7.253 113.493v6.4c1.707 27.733 15.787 55.040 39.253 73.387l69.12 55.040 5.12 3.84c41.387 28.587 97.707 24.32 133.547-11.947l46.933-46.933c37.973-42.24 35.84-108.373-7.253-146.347zM254.72 524.8c-1.28 9.813-5.547 17.92-12.8 23.893l-69.547 55.467-3.413 2.133c-13.653 8.533-31.147 6.4-42.24-4.693l-42.667-42.667-2.987-3.413c-11.52-14.080-10.24-34.987 2.987-46.507l8.96-7.68c50.347-42.667 106.24-74.667 165.12-96.853l3.413-1.28-6.827 121.6zM944.213 558.507l-42.667 42.667-2.987 2.56c-12.8 9.813-30.293 10.24-43.52 0l-69.12-54.613-3.413-2.987c-6.4-6.4-9.813-14.933-9.813-24.32l-5.973-118.187 3.413 1.28c62.293 23.040 121.6 58.027 174.080 104.533 13.653 11.947 14.080 34.987 0 49.067z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 24, + "tags": [ + "phone-discard-outline" + ] + }, + { + "id": 41, + "paths": [ + "M370.773 638.293c-139.52 0-253.013-113.493-253.013-253.013s113.493-253.013 253.013-253.013 253.013 113.493 253.013 253.013-113.493 253.013-253.013 253.013zM370.773 217.173c-92.587 0-167.68 75.093-167.68 167.68s75.093 167.68 167.68 167.68 167.68-75.093 167.68-167.68-75.52-167.68-167.68-167.68z", + "M724.48 603.733c-11.947 0-23.893-5.12-32-14.507-15.36-17.92-13.653-44.8 4.267-60.16 34.56-30.293 54.613-73.813 54.613-119.893 0-48.213-21.333-93.44-58.88-123.733-18.347-14.933-21.333-41.813-6.4-60.16s41.813-21.333 60.16-6.4c57.6 46.507 90.88 116.053 90.88 189.867 0 70.827-30.72 137.813-84.053 184.32-8.533 7.253-18.773 10.667-28.587 10.667z", + "M850.773 727.040c-11.947 0-23.893-5.12-32.427-14.933-15.36-17.92-13.227-44.8 4.693-60.16 72.533-61.867 113.92-151.467 113.92-245.333 0-92.16-40.107-180.48-110.080-241.92-17.92-15.787-19.2-42.667-3.84-60.16 15.787-17.493 42.667-19.2 60.16-3.84 88.32 77.653 138.667 189.44 138.667 305.92 0 119.040-52.48 232.107-143.787 310.187-7.68 6.827-17.493 10.24-27.307 10.24z", + "M707.413 965.547c-23.467 0-42.667-19.2-42.667-42.667 0-69.12-56.32-125.44-125.44-125.44h-330.667c-69.12 0-125.44 56.32-125.44 125.44 0 23.467-19.2 42.667-42.667 42.667s-42.667-19.2-42.667-42.667c0-116.48 94.72-210.773 210.773-210.773h330.24c116.48 0 210.773 94.72 210.773 210.773 0.427 23.467-18.773 42.667-42.24 42.667z" + ], + "attrs": [ + {}, + {}, + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 24, + "tags": [ + "allow-speak" + ] + }, + { + "id": 40, + "paths": [ + "M512 457.813c-123.307 0-224-100.267-224-224s100.693-223.573 224-223.573 224 100.267 224 224-100.693 223.573-224 223.573zM512 95.573c-76.373 0-138.667 62.293-138.667 138.667s62.293 138.24 138.667 138.24 138.667-62.293 138.667-138.667-62.293-138.24-138.667-138.24z", + "M134.827 1010.347c-23.467 0-42.667-19.2-42.667-42.667 0-194.987 39.68-333.227 118.187-411.307 70.827-70.827 151.467-70.4 177.493-70.4h135.253c23.467 0 42.667 19.2 42.667 42.667s-19.2 42.667-42.667 42.667h-133.12c-23.467-0.853-73.813-0.427-119.467 45.227-61.013 60.587-93.013 182.187-93.013 351.147 0 23.467-19.2 42.667-42.667 42.667z", + "M523.093 1003.093c-198.4 0-220.16-118.187-221.013-123.307l-0.427-7.253v-340.907c0-23.467 19.2-42.667 42.667-42.667s42.667 19.2 42.667 42.667v335.36c3.413 8.533 26.453 50.347 136.533 50.347 23.467 0 42.667 19.2 42.667 42.667s-19.627 43.093-43.093 43.093z", + "M911.36 1010.347c-23.467 0-42.667-19.2-42.667-42.667 0-168.96-32-290.133-93.013-351.147-45.653-45.653-95.147-45.227-116.907-45.227h-135.68c-23.467 0-42.667-19.2-42.667-42.667s19.2-42.667 42.667-42.667h133.12c28.16 0 108.8-0.427 179.627 70.4 78.507 78.080 118.187 216.747 118.187 411.307 0 23.467-19.2 42.667-42.667 42.667z", + "M523.093 1003.093c-23.467 0-42.667-19.2-42.667-42.667s19.2-42.667 42.667-42.667c107.52 0 131.84-40.107 136.533-51.2v-334.507c0-23.467 19.2-42.667 42.667-42.667s42.667 19.2 42.667 42.667l-0.427 347.733c-1.28 5.12-23.467 123.307-221.44 123.307z" + ], + "attrs": [ + {}, + {}, + {}, + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 24, + "tags": [ + "stop-raising-hand" + ] + }, + { + "id": 39, + "paths": [ + "M896 1023.147h-547.84c-70.827 0-128-57.6-128-128v-769.707c0-70.827 57.6-128 128-128h547.84c70.827 0 128 57.6 128 128v769.707c0 70.4-57.6 128-128 128zM348.16 82.347c-23.467 0-42.667 19.2-42.667 42.667v769.707c0 23.467 19.2 42.667 42.667 42.667h547.84c23.467 0 42.667-19.2 42.667-42.667v-769.28c0-23.467-19.2-42.667-42.667-42.667h-547.84z", + "M148.053 968.96h-20.053c-70.4 0-128-57.173-128-128v-605.867c0-70.4 57.173-128 128-128h24.32c23.467 0 42.667 19.2 42.667 42.667s-19.2 42.667-42.667 42.667h-24.32c-23.467 0-42.667 19.2-42.667 42.667v606.293c0 23.467 19.2 42.667 42.667 42.667h20.48c23.467 0 42.667 19.2 42.667 42.667s-19.627 42.24-43.093 42.24z", + "M622.080 734.293c-23.467 0-42.667-19.2-42.667-42.667v-249.6l-87.893 87.893c-16.64 16.64-43.52 16.64-60.16 0s-16.64-43.52 0-60.16l153.173-153.173c13.227-13.653 33.28-17.92 51.2-10.24 17.493 7.253 29.013 24.32 29.013 43.52v342.187c0 23.467-19.2 42.24-42.667 42.24z", + "M622.080 734.293c-23.467 0-42.667-19.2-42.667-42.667v-341.76c0-19.2 11.52-36.267 29.013-43.52 17.92-7.253 37.973-3.413 51.2 10.24l153.173 152.747c16.64 16.64 16.64 43.52 0 60.16s-43.52 16.64-60.16 0l-87.893-87.893v249.6c0 24.32-19.2 43.093-42.667 43.093z" + ], + "attrs": [ + {}, + {}, + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 24, + "tags": [ + "share-screen" + ] + }, + { + "id": 38, + "paths": [ + "M661.76 570.027c-23.467 0-42.667-19.2-42.667-42.667v-197.547c0-23.467 19.2-42.667 42.667-42.667s42.667 19.2 42.667 42.667v197.12c0 23.893-18.773 43.093-42.667 43.093z", + "M316.587 609.28c-23.467 0-42.667-19.2-42.667-42.667v-251.733c0-23.467 19.2-42.667 42.667-42.667s42.667 19.2 42.667 42.667v251.733c0 23.467-18.773 42.667-42.667 42.667z", + "M488.533 654.507c-23.467 0-42.667-19.2-42.667-42.667v-354.987c0-23.467 19.2-42.667 42.667-42.667s42.667 19.2 42.667 42.667v354.987c0 23.893-18.773 42.667-42.667 42.667z", + "M346.88 1017.6c-50.773 0-79.787-40.96-80.64-79.36l0.427-20.907c0.427-29.44 1.28-59.733-11.947-73.387-10.667-11.093-34.133-16.64-67.413-16.64-96.427 0-174.507-78.080-174.507-174.507v-465.493c0-96.427 78.080-174.507 174.507-174.507h607.573c96 0 174.507 78.080 174.507 174.507v465.067c0 96-78.080 174.507-174.507 174.507h-1.707c-0.853 0-90.453-3.84-161.707-3.84-27.307 0.427-114.347 75.093-155.733 111.36-69.547 59.307-98.987 83.2-128.853 83.2zM338.773 933.547v0 0zM186.88 98.133c-49.067 0-89.173 40.107-89.173 89.173v465.067c0 49.067 40.107 89.173 89.173 89.173 58.453 0 100.267 13.653 128.427 42.24 38.4 39.253 37.547 92.587 36.267 134.827v6.4c17.493-12.8 45.653-37.12 67.84-55.893 92.587-79.787 157.44-131.84 211.627-131.84 69.547 0 153.6 3.413 164.267 3.84 48.64-0.427 88.32-40.107 88.32-89.173v-464.64c0-49.067-40.107-89.173-89.173-89.173h-607.573z" + ], + "attrs": [ + {}, + {}, + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 24, + "tags": [ + "voice-chat" + ] + }, + { + "id": 37, + "paths": [ + "M618.667 843.52h-512c-58.88 0-106.667-47.787-106.667-106.667v-407.040c0-58.88 47.787-106.667 106.667-106.667h512c58.88 0 106.667 47.787 106.667 106.667v407.040c0 58.88-47.787 106.667-106.667 106.667z", + "M806.827 477.013v104.107c0 40.533 0.853 46.080 27.733 72.96l128.427 128.427c8.96 8.96 34.987 8.96 44.373 0l15.787-15.787v-464.64l-15.787-16.213c-10.24-10.24-36.267-9.387-45.653 0l-130.133 130.987c-18.773 18.347-24.747 26.88-24.747 60.16z" + ], + "attrs": [ + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 24, + "tags": [ + "video" + ] + }, + { + "id": 36, + "paths": [ + "M327.253 922.88c-46.080 0-51.627-29.013-97.28-255.147-4.267-21.333-8.96-45.227-13.653-68.267-32.427 66.56-50.347 96.853-84.48 96.853h-87.467c-23.467 0-42.667-19.2-42.667-42.667s19.2-42.667 42.667-42.667h69.547c9.813-16.64 25.6-48.64 35.413-69.547 35.413-73.387 47.36-98.133 81.067-98.133 41.813 0 43.947 11.52 83.2 207.36 2.987 14.080 5.973 29.867 9.387 46.080 5.973-35.84 12.373-73.387 17.92-108.373 75.947-456.533 85.333-474.027 135.68-474.027 50.773 0 58.88 36.267 108.373 321.28 14.080 80.213 32.427 187.307 49.067 258.987 13.227-50.773 26.88-117.333 37.12-164.693 35.84-172.373 44.373-213.333 91.733-213.333 42.667 0 56.747 40.107 94.293 146.347 17.92 50.347 49.92 141.227 69.973 164.693h56.747c23.467 0 42.667 19.2 42.667 42.667s-19.2 42.667-42.667 42.667h-65.707c-60.16 0-91.307-80.213-141.227-221.44-2.133-5.973-4.267-11.947-6.4-18.347-5.547 24.747-10.667 50.773-15.787 73.813-45.653 219.307-64.427 295.253-123.307 295.253-57.173 0-73.387-64.427-130.133-392.107-7.68-43.52-16.213-93.867-24.747-139.52-17.067 87.467-35.84 202.667-50.773 291.84-53.333 320.427-53.333 320.427-98.56 320.427z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 24, + "tags": [ + "noise-suppression" + ] + }, + { + "id": 35, + "paths": [ + "M997.267 610.72l-4.213 4.467-42.747 42.733c-36.16 36.173-92.573 40.787-134.12 11.88l-5.107-3.787-69.44-55.107c-23.307-18.493-37.493-45.733-39.36-73.64l-0.213-6.453-7.36-114-0.4-0.093c-73.907-16.213-274.587-17.333-348.84-3.267l-16.173 3.307-7.307 111.707c0 29.653-12.533 57.693-34.4 77.48l-5.213 4.413-70.040 55.693c-40.267 31.72-97.107 29.733-134.28-3.973l-4.36-4.2-42.747-42.733c-42.2-42.2-41.373-111.893 3.307-151.36 237.133-209.387 718.307-209.427 955.493 0.040 43.147 38.107 45.387 104.387 7.52 146.893z" + ], + "attrs": [ + {} + ], + "grid": 24, + "tags": [ + "phone-discard" + ], + "isMulticolor": false, + "isMulticolor2": false + }, { "id": 34, "paths": [ @@ -2367,7 +2687,7 @@ ], "grid": 24, "tags": [ - "muted" + "muted-chat" ], "isMulticolor": false, "isMulticolor2": false, @@ -2491,7 +2811,7 @@ "name": "select", "prevSize": 32, "code": 59744, - "tempChar": "" + "tempChar": "" }, { "order": 480, @@ -2499,7 +2819,7 @@ "name": "folder", "prevSize": 32, "code": 59667, - "tempChar": "" + "tempChar": "" }, { "order": 481, @@ -2507,7 +2827,7 @@ "name": "bots", "prevSize": 32, "code": 59669, - "tempChar": "" + "tempChar": "" }, { "order": 482, @@ -2515,7 +2835,7 @@ "name": "calendar", "prevSize": 32, "code": 59670, - "tempChar": "" + "tempChar": "" }, { "order": 483, @@ -2523,7 +2843,7 @@ "name": "cloud-download", "prevSize": 32, "code": 59671, - "tempChar": "" + "tempChar": "" }, { "order": 484, @@ -2531,7 +2851,7 @@ "name": "colorize", "prevSize": 32, "code": 59672, - "tempChar": "" + "tempChar": "" }, { "order": 651, @@ -2539,7 +2859,7 @@ "name": "forward", "prevSize": 32, "code": 59687, - "tempChar": "" + "tempChar": "" }, { "order": 650, @@ -2547,7 +2867,7 @@ "name": "reply", "prevSize": 32, "code": 59719, - "tempChar": "" + "tempChar": "" }, { "order": 487, @@ -2555,7 +2875,7 @@ "name": "help", "prevSize": 32, "code": 59690, - "tempChar": "" + "tempChar": "" }, { "order": 488, @@ -2563,7 +2883,7 @@ "name": "info", "prevSize": 32, "code": 59691, - "tempChar": "" + "tempChar": "" }, { "order": 489, @@ -2571,7 +2891,7 @@ "name": "info-filled", "prevSize": 32, "code": 59675, - "tempChar": "" + "tempChar": "" }, { "order": 490, @@ -2579,7 +2899,7 @@ "name": "delete-filled", "prevSize": 32, "code": 59676, - "tempChar": "" + "tempChar": "" }, { "order": 491, @@ -2587,7 +2907,7 @@ "name": "delete", "prevSize": 32, "code": 59677, - "tempChar": "" + "tempChar": "" }, { "order": 492, @@ -2595,7 +2915,7 @@ "name": "edit", "prevSize": 32, "code": 59683, - "tempChar": "" + "tempChar": "" }, { "order": 493, @@ -2603,7 +2923,7 @@ "name": "new-chat-filled", "prevSize": 32, "code": 59705, - "tempChar": "" + "tempChar": "" }, { "order": 494, @@ -2611,7 +2931,7 @@ "name": "send", "prevSize": 32, "code": 59722, - "tempChar": "" + "tempChar": "" }, { "order": 495, @@ -2619,7 +2939,7 @@ "name": "send-outline", "prevSize": 32, "code": 59723, - "tempChar": "" + "tempChar": "" }, { "order": 496, @@ -2627,7 +2947,7 @@ "name": "add-user-filled", "prevSize": 32, "code": 59652, - "tempChar": "" + "tempChar": "" }, { "order": 497, @@ -2635,7 +2955,7 @@ "name": "add-user", "prevSize": 32, "code": 59653, - "tempChar": "" + "tempChar": "" }, { "order": 498, @@ -2643,7 +2963,7 @@ "name": "delete-user", "prevSize": 32, "code": 59678, - "tempChar": "" + "tempChar": "" }, { "order": 499, @@ -2651,7 +2971,7 @@ "name": "microphone", "prevSize": 32, "code": 59701, - "tempChar": "" + "tempChar": "" }, { "order": 500, @@ -2659,7 +2979,7 @@ "name": "microphone-alt", "prevSize": 32, "code": 59707, - "tempChar": "" + "tempChar": "" }, { "order": 501, @@ -2667,7 +2987,7 @@ "name": "poll", "prevSize": 32, "code": 59704, - "tempChar": "" + "tempChar": "" }, { "order": 502, @@ -2675,7 +2995,7 @@ "name": "revote", "prevSize": 32, "code": 59706, - "tempChar": "" + "tempChar": "" }, { "order": 503, @@ -2683,7 +3003,7 @@ "name": "photo", "prevSize": 32, "code": 59712, - "tempChar": "" + "tempChar": "" }, { "order": 504, @@ -2691,7 +3011,7 @@ "name": "document", "prevSize": 32, "code": 59679, - "tempChar": "" + "tempChar": "" }, { "order": 505, @@ -2699,7 +3019,7 @@ "name": "camera", "prevSize": 32, "code": 59662, - "tempChar": "" + "tempChar": "" }, { "order": 506, @@ -2707,7 +3027,7 @@ "name": "camera-add", "prevSize": 32, "code": 59663, - "tempChar": "" + "tempChar": "" }, { "order": 507, @@ -2715,7 +3035,7 @@ "name": "logout", "prevSize": 32, "code": 59698, - "tempChar": "" + "tempChar": "" }, { "order": 508, @@ -2723,7 +3043,7 @@ "name": "saved-messages", "prevSize": 32, "code": 59720, - "tempChar": "" + "tempChar": "" }, { "order": 509, @@ -2731,7 +3051,7 @@ "name": "settings", "prevSize": 32, "code": 59726, - "tempChar": "" + "tempChar": "" }, { "order": 652, @@ -2739,7 +3059,7 @@ "name": "phone", "prevSize": 32, "code": 59711, - "tempChar": "" + "tempChar": "" }, { "order": 653, @@ -2747,7 +3067,7 @@ "name": "attach", "prevSize": 32, "code": 59657, - "tempChar": "" + "tempChar": "" }, { "order": 512, @@ -2755,7 +3075,7 @@ "name": "copy", "prevSize": 32, "code": 59674, - "tempChar": "" + "tempChar": "" }, { "order": 513, @@ -2763,7 +3083,7 @@ "name": "channel", "prevSize": 32, "code": 59665, - "tempChar": "" + "tempChar": "" }, { "order": 514, @@ -2771,7 +3091,7 @@ "name": "group", "prevSize": 32, "code": 59689, - "tempChar": "" + "tempChar": "" }, { "order": 515, @@ -2779,7 +3099,7 @@ "name": "user", "prevSize": 32, "code": 59737, - "tempChar": "" + "tempChar": "" }, { "order": 516, @@ -2787,7 +3107,7 @@ "name": "non-contacts", "prevSize": 32, "code": 59688, - "tempChar": "" + "tempChar": "" }, { "order": 517, @@ -2795,7 +3115,7 @@ "name": "active-sessions", "prevSize": 32, "code": 59650, - "tempChar": "" + "tempChar": "" }, { "order": 518, @@ -2803,7 +3123,7 @@ "name": "admin", "prevSize": 32, "code": 59654, - "tempChar": "" + "tempChar": "" }, { "order": 519, @@ -2811,7 +3131,7 @@ "name": "download", "prevSize": 32, "code": 59681, - "tempChar": "" + "tempChar": "" }, { "order": 520, @@ -2819,7 +3139,7 @@ "name": "location", "prevSize": 32, "code": 59696, - "tempChar": "" + "tempChar": "" }, { "order": 521, @@ -2827,7 +3147,7 @@ "name": "stop", "prevSize": 32, "code": 59730, - "tempChar": "" + "tempChar": "" }, { "order": 523, @@ -2835,7 +3155,7 @@ "name": "archive", "prevSize": 32, "code": 59656, - "tempChar": "" + "tempChar": "" }, { "order": 524, @@ -2843,7 +3163,7 @@ "name": "unarchive", "prevSize": 32, "code": 59731, - "tempChar": "" + "tempChar": "" }, { "order": 525, @@ -2851,7 +3171,7 @@ "name": "readchats", "prevSize": 32, "code": 59699, - "tempChar": "" + "tempChar": "" }, { "order": 526, @@ -2859,7 +3179,7 @@ "name": "unread", "prevSize": 32, "code": 59735, - "tempChar": "" + "tempChar": "" }, { "order": 654, @@ -2867,7 +3187,7 @@ "name": "message", "prevSize": 32, "code": 59700, - "tempChar": "" + "tempChar": "" }, { "order": 659, @@ -2875,7 +3195,7 @@ "name": "lock", "prevSize": 32, "code": 59697, - "tempChar": "" + "tempChar": "" }, { "order": 529, @@ -2883,7 +3203,7 @@ "name": "unlock", "prevSize": 32, "code": 59732, - "tempChar": "" + "tempChar": "" }, { "order": 530, @@ -2891,7 +3211,7 @@ "name": "mute", "prevSize": 32, "code": 59703, - "tempChar": "" + "tempChar": "" }, { "order": 531, @@ -2899,7 +3219,7 @@ "name": "unmute", "prevSize": 32, "code": 59733, - "tempChar": "" + "tempChar": "" }, { "order": 532, @@ -2907,7 +3227,7 @@ "name": "pin", "prevSize": 32, "code": 59713, - "tempChar": "" + "tempChar": "" }, { "order": 533, @@ -2915,7 +3235,7 @@ "name": "unpin", "prevSize": 32, "code": 59734, - "tempChar": "" + "tempChar": "" }, { "order": 534, @@ -2923,7 +3243,7 @@ "name": "smallscreen", "prevSize": 32, "code": 59742, - "tempChar": "" + "tempChar": "" }, { "order": 535, @@ -2931,7 +3251,7 @@ "name": "fullscreen", "prevSize": 32, "code": 59743, - "tempChar": "" + "tempChar": "" }, { "order": 536, @@ -2939,7 +3259,7 @@ "name": "large-pause", "prevSize": 32, "code": 59694, - "tempChar": "" + "tempChar": "" }, { "order": 537, @@ -2947,7 +3267,7 @@ "name": "large-play", "prevSize": 32, "code": 59695, - "tempChar": "" + "tempChar": "" }, { "order": 538, @@ -2955,7 +3275,7 @@ "name": "pause", "prevSize": 32, "code": 59709, - "tempChar": "" + "tempChar": "" }, { "order": 539, @@ -2963,7 +3283,7 @@ "name": "play", "prevSize": 32, "code": 59715, - "tempChar": "" + "tempChar": "" }, { "order": 540, @@ -2971,7 +3291,7 @@ "name": "channelviews", "prevSize": 32, "code": 59666, - "tempChar": "" + "tempChar": "" }, { "order": 541, @@ -2979,7 +3299,7 @@ "name": "message-succeeded", "prevSize": 32, "code": 59648, - "tempChar": "" + "tempChar": "" }, { "order": 657, @@ -2987,7 +3307,7 @@ "name": "message-read", "prevSize": 32, "code": 59649, - "tempChar": "" + "tempChar": "" }, { "order": 543, @@ -2995,7 +3315,7 @@ "name": "message-pending", "prevSize": 32, "code": 59724, - "tempChar": "" + "tempChar": "" }, { "order": 544, @@ -3003,7 +3323,7 @@ "name": "message-failed", "prevSize": 32, "code": 59725, - "tempChar": "" + "tempChar": "" }, { "order": 545, @@ -3011,7 +3331,7 @@ "name": "favorite", "prevSize": 32, "code": 59710, - "tempChar": "" + "tempChar": "" }, { "order": 546, @@ -3019,7 +3339,7 @@ "name": "keyboard", "prevSize": 32, "code": 59716, - "tempChar": "" + "tempChar": "" }, { "order": 547, @@ -3027,7 +3347,7 @@ "name": "delete-left", "prevSize": 32, "code": 59717, - "tempChar": "" + "tempChar": "" }, { "order": 548, @@ -3035,7 +3355,7 @@ "name": "recent", "prevSize": 32, "code": 59718, - "tempChar": "" + "tempChar": "" }, { "order": 549, @@ -3043,7 +3363,7 @@ "name": "gifs", "prevSize": 32, "code": 59727, - "tempChar": "" + "tempChar": "" }, { "order": 550, @@ -3051,7 +3371,7 @@ "name": "stickers", "prevSize": 32, "code": 59739, - "tempChar": "" + "tempChar": "" }, { "order": 551, @@ -3059,7 +3379,7 @@ "name": "smile", "prevSize": 32, "code": 59728, - "tempChar": "" + "tempChar": "" }, { "order": 552, @@ -3067,7 +3387,7 @@ "name": "animals", "prevSize": 32, "code": 59655, - "tempChar": "" + "tempChar": "" }, { "order": 553, @@ -3075,7 +3395,7 @@ "name": "eats", "prevSize": 32, "code": 59682, - "tempChar": "" + "tempChar": "" }, { "order": 554, @@ -3083,7 +3403,7 @@ "name": "sport", "prevSize": 32, "code": 59729, - "tempChar": "" + "tempChar": "" }, { "order": 555, @@ -3091,7 +3411,7 @@ "name": "car", "prevSize": 32, "code": 59664, - "tempChar": "" + "tempChar": "" }, { "order": 556, @@ -3099,7 +3419,7 @@ "name": "lamp", "prevSize": 32, "code": 59692, - "tempChar": "" + "tempChar": "" }, { "order": 557, @@ -3107,7 +3427,7 @@ "name": "language", "prevSize": 32, "code": 59693, - "tempChar": "" + "tempChar": "" }, { "order": 558, @@ -3115,7 +3435,7 @@ "name": "flag", "prevSize": 32, "code": 59686, - "tempChar": "" + "tempChar": "" }, { "order": 559, @@ -3123,7 +3443,7 @@ "name": "more", "prevSize": 32, "code": 59702, - "tempChar": "" + "tempChar": "" }, { "order": 560, @@ -3131,7 +3451,7 @@ "name": "search", "prevSize": 32, "code": 59721, - "tempChar": "" + "tempChar": "" }, { "order": 561, @@ -3139,7 +3459,7 @@ "name": "remove", "prevSize": 32, "code": 59740, - "tempChar": "" + "tempChar": "" }, { "order": 562, @@ -3147,7 +3467,7 @@ "name": "add", "prevSize": 32, "code": 59651, - "tempChar": "" + "tempChar": "" }, { "order": 563, @@ -3155,7 +3475,7 @@ "name": "check", "prevSize": 32, "code": 59668, - "tempChar": "" + "tempChar": "" }, { "order": 564, @@ -3163,7 +3483,7 @@ "name": "close", "prevSize": 32, "code": 59673, - "tempChar": "" + "tempChar": "" }, { "order": 610, @@ -3171,7 +3491,7 @@ "name": "arrow-left", "prevSize": 32, "code": 59661, - "tempChar": "" + "tempChar": "" }, { "order": 566, @@ -3179,7 +3499,7 @@ "name": "arrow-right", "prevSize": 32, "code": 59708, - "tempChar": "" + "tempChar": "" }, { "order": 567, @@ -3187,7 +3507,7 @@ "name": "down", "prevSize": 32, "code": 59680, - "tempChar": "" + "tempChar": "" }, { "order": 568, @@ -3195,7 +3515,7 @@ "name": "up", "prevSize": 32, "code": 59736, - "tempChar": "" + "tempChar": "" }, { "order": 569, @@ -3203,7 +3523,7 @@ "name": "eye-closed", "prevSize": 32, "code": 59685, - "tempChar": "" + "tempChar": "" }, { "order": 570, @@ -3211,15 +3531,15 @@ "name": "eye", "prevSize": 32, "code": 59684, - "tempChar": "" + "tempChar": "" }, { - "order": 675, + "order": 571, "id": 4, - "name": "muted", + "name": "muted-chat", "prevSize": 32, "code": 59741, - "tempChar": "" + "tempChar": "" }, { "order": 572, @@ -3227,7 +3547,7 @@ "name": "avatar-archived-chats", "prevSize": 32, "code": 59658, - "tempChar": "" + "tempChar": "" }, { "order": 573, @@ -3235,7 +3555,7 @@ "name": "avatar-deleted-account", "prevSize": 32, "code": 59659, - "tempChar": "" + "tempChar": "" }, { "order": 574, @@ -3243,7 +3563,7 @@ "name": "avatar-saved-messages", "prevSize": 32, "code": 59660, - "tempChar": "" + "tempChar": "" }, { "order": 575, @@ -3251,7 +3571,7 @@ "name": "pinned-chat", "prevSize": 32, "code": 59714, - "tempChar": "" + "tempChar": "" } ], "prevSize": 32, @@ -3299,4 +3619,4 @@ "showLiga": false }, "uid": -1 -} +} \ No newline at end of file diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss index 1b34ff0a0..afd0416d9 100644 --- a/src/styles/_mixins.scss +++ b/src/styles/_mixins.scss @@ -13,3 +13,43 @@ overflow-x: hidden; } } + +@mixin reset-range() { + input[type="range"] { + -webkit-appearance: none; + display: block; + width: 100%; + height: 0.75rem; + margin-bottom: 0.5rem; + background: transparent; + + &:focus { + outline: none; + } + + &::-ms-track { + width: 100%; + cursor: pointer; + + background: transparent; + border-color: transparent; + color: transparent; + } + + &::-webkit-slider-thumb { + -webkit-appearance: none; + } + + &::-moz-slider-thumb { + -moz-appearance: none; + } + + &::-webkit-slider-runnable-track { + cursor: pointer; + } + + &::-moz-range-track, &::-moz-range-progress { + cursor: pointer; + } + } +} diff --git a/src/styles/icons.scss b/src/styles/icons.scss index e442748c6..7668eb52f 100644 --- a/src/styles/icons.scss +++ b/src/styles/icons.scss @@ -33,23 +33,59 @@ } .icon-loop:before { - content: "\e981"; + content: "\e98c"; } .icon-skip-next:before { - content: "\e982"; + content: "\e98d"; } .icon-skip-previous:before { - content: "\e983"; + content: "\e98e"; } .icon-volume-1:before { - content: "\e984"; + content: "\e98f"; } .icon-volume-2:before { - content: "\e985"; + content: "\e990"; } .icon-volume-3:before { + content: "\e991"; +} +.icon-sidebar:before { + content: "\e992"; +} +.icon-video-stop:before { + content: "\e98b"; +} +.icon-speaker:before { + content: "\e981"; +} +.icon-speaker-outline:before { + content: "\e982"; +} +.icon-phone-discard-outline:before { + content: "\e983"; +} +.icon-allow-speak:before { + content: "\e984"; +} +.icon-stop-raising-hand:before { + content: "\e985"; +} +.icon-share-screen:before { content: "\e986"; } +.icon-voice-chat:before { + content: "\e987"; +} +.icon-video:before { + content: "\e988"; +} +.icon-noise-suppression:before { + content: "\e989"; +} +.icon-phone-discard:before { + content: "\e98a"; +} .icon-bot-commands-filled:before { content: "\e97f"; } @@ -422,7 +458,7 @@ .icon-eye:before { content: "\e924"; } -.icon-muted:before { +.icon-muted-chat:before { content: "\e95d"; } .icon-avatar-archived-chats:before { diff --git a/src/util/deeplink.ts b/src/util/deeplink.ts index f94da80fd..321cccef7 100644 --- a/src/util/deeplink.ts +++ b/src/util/deeplink.ts @@ -13,6 +13,7 @@ export const processDeepLink = (url: string) => { openChatByUsername, openStickerSetShortName, focusMessage, + joinVoiceChatByLink, } = getDispatch(); const method = pathname.replace(/^\/\//, '') as DeepLinkMethod; @@ -23,14 +24,23 @@ export const processDeepLink = (url: string) => { switch (method) { case 'resolve': { - const { domain, post, comment } = params; + const { + domain, post, comment, voicechat, livestream, + } = params; if (domain !== 'telegrampassport') { - openChatByUsername({ - username: domain, - messageId: Number(post), - commentId: Number(comment), - }); + if (params.hasOwnProperty('voicechat') || params.hasOwnProperty('livestream')) { + joinVoiceChatByLink({ + username: domain, + inviteHash: voicechat || livestream, + }); + } else { + openChatByUsername({ + username: domain, + messageId: Number(post), + commentId: Number(comment), + }); + } } break; } diff --git a/src/util/environment.ts b/src/util/environment.ts index 48f0c8eae..84d4651ae 100644 --- a/src/util/environment.ts +++ b/src/util/environment.ts @@ -67,6 +67,8 @@ export const IS_OPUS_SUPPORTED = Boolean((new Audio()).canPlayType('audio/ogg; c export const IS_CANVAS_FILTER_SUPPORTED = ( !IS_TEST && 'filter' in (document.createElement('canvas').getContext('2d') || {}) ); +export const IS_REQUEST_FULLSCREEN_SUPPORTED = 'requestFullscreen' in document.createElement('div'); +export const ARE_CALLS_SUPPORTED = !navigator.userAgent.includes('Firefox'); export const LAYERS_ANIMATION_NAME = IS_ANDROID ? 'slide-fade' : IS_IOS ? 'slide-layers' : 'push-slide'; const TEST_VIDEO = document.createElement('video'); diff --git a/src/util/moduleLoader.ts b/src/util/moduleLoader.ts index 0b8e87968..0a8135155 100644 --- a/src/util/moduleLoader.ts +++ b/src/util/moduleLoader.ts @@ -4,12 +4,14 @@ export enum Bundles { Auth, Main, Extra, + Calls, } interface ImportedBundles { [Bundles.Auth]: typeof import('../bundles/auth'); [Bundles.Main]: typeof import('../bundles/main'); [Bundles.Extra]: typeof import('../bundles/extra'); + [Bundles.Calls]: typeof import('../bundles/calls'); } type BundlePromises = { @@ -38,6 +40,9 @@ export async function loadModule>( case Bundles.Extra: LOAD_PROMISES[Bundles.Extra] = import('../bundles/extra'); break; + case Bundles.Calls: + LOAD_PROMISES[Bundles.Calls] = import('../bundles/calls'); + break; } (LOAD_PROMISES[bundleName] as Promise).then(handleBundleLoad); diff --git a/src/util/vibrate.ts b/src/util/vibrate.ts new file mode 100644 index 000000000..2af6a7bf0 --- /dev/null +++ b/src/util/vibrate.ts @@ -0,0 +1,3 @@ +export const vibrateShort = () => { + navigator.vibrate?.(50); +}; diff --git a/webpack.config.js b/webpack.config.js index b267c859a..d82a0c10d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -25,6 +25,7 @@ module.exports = (env = {}, argv = {}) => { path.resolve(__dirname, 'node_modules/opus-recorder/dist'), path.resolve(__dirname, 'src/lib/webp'), path.resolve(__dirname, 'src/lib/rlottie'), + path.resolve(__dirname, 'src/lib/secret-sauce'), ], port: 1234, host: '0.0.0.0',