From fe16471ee819e12c75ae7b29b7f406d6cfa7a3a8 Mon Sep 17 00:00:00 2001 From: "lvlin.huang" Date: Tue, 24 Dec 2024 16:14:17 +0800 Subject: [PATCH 01/13] [feat] (1)Modify app icon and name. (2)Modify Camera preview resolution and keep screen on. (3)Add live stream addresses --- app/src/main/AndroidManifest.xml | 27 ++++--- app/src/main/ic_hohem-playstore.png | Bin 0 -> 71946 bytes .../java/com/pedro/streamer/MainActivity.kt | 2 +- .../pedro/streamer/rotation/CameraFragment.kt | 17 +++- .../streamer/rotation/RotationActivity.kt | 15 ++++ .../java/com/pedro/streamer/utils/Logger.kt | 73 ++++++++++++++++++ app/src/main/res/drawable/hohem_icon.png | Bin 0 -> 91054 bytes app/src/main/res/layout/fragment_camera.xml | 3 +- app/src/main/res/menu/rotation_menu.xml | 17 +++- .../main/res/mipmap-anydpi-v26/ic_hohem.xml | 5 ++ .../res/mipmap-anydpi-v26/ic_hohem_round.xml | 5 ++ app/src/main/res/mipmap-hdpi/ic_hohem.webp | Bin 0 -> 2828 bytes .../res/mipmap-hdpi/ic_hohem_foreground.webp | Bin 0 -> 4038 bytes .../main/res/mipmap-hdpi/ic_hohem_round.webp | Bin 0 -> 4902 bytes app/src/main/res/mipmap-mdpi/ic_hohem.webp | Bin 0 -> 1994 bytes .../res/mipmap-mdpi/ic_hohem_foreground.webp | Bin 0 -> 2772 bytes .../main/res/mipmap-mdpi/ic_hohem_round.webp | Bin 0 -> 3124 bytes app/src/main/res/mipmap-xhdpi/ic_hohem.webp | Bin 0 -> 3820 bytes .../res/mipmap-xhdpi/ic_hohem_foreground.webp | Bin 0 -> 5460 bytes .../main/res/mipmap-xhdpi/ic_hohem_round.webp | Bin 0 -> 6794 bytes app/src/main/res/mipmap-xxhdpi/ic_hohem.webp | Bin 0 -> 6160 bytes .../mipmap-xxhdpi/ic_hohem_foreground.webp | Bin 0 -> 8846 bytes .../res/mipmap-xxhdpi/ic_hohem_round.webp | Bin 0 -> 10472 bytes app/src/main/res/mipmap-xxxhdpi/ic_hohem.webp | Bin 0 -> 7860 bytes .../mipmap-xxxhdpi/ic_hohem_foreground.webp | Bin 0 -> 10076 bytes .../res/mipmap-xxxhdpi/ic_hohem_round.webp | Bin 0 -> 13932 bytes app/src/main/res/values/colors.xml | 3 +- .../main/res/values/ic_hohem_background.xml | 4 + app/src/main/res/values/strings.xml | 12 ++- .../encoder/input/gl/render/ScreenRender.java | 11 ++- .../input/sources/video/Camera2Source.kt | 7 ++ .../java/com/pedro/encoder/utils/Logger.kt | 72 +++++++++++++++++ .../encoder/utils/gl/SizeCalculator.java | 28 +++++-- .../java/com/pedro/library/base/StreamBase.kt | 6 ++ .../pedro/library/view/GlStreamInterface.kt | 10 +++ 35 files changed, 287 insertions(+), 30 deletions(-) create mode 100644 app/src/main/ic_hohem-playstore.png create mode 100644 app/src/main/java/com/pedro/streamer/utils/Logger.kt create mode 100644 app/src/main/res/drawable/hohem_icon.png create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_hohem.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_hohem_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_hohem.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_hohem_foreground.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_hohem_round.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_hohem.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_hohem_foreground.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_hohem_round.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_hohem.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_hohem_foreground.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_hohem_round.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_hohem.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_hohem_foreground.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_hohem_round.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_hohem.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_hohem_foreground.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_hohem_round.webp create mode 100644 app/src/main/res/values/ic_hohem_background.xml create mode 100644 encoder/src/main/java/com/pedro/encoder/utils/Logger.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a627981ec8..3bd162ebd1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,7 +32,8 @@ android:requestLegacyExternalStorage="true" android:usesCleartextTraffic="true" android:allowBackup="true" - android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:icon="@drawable/hohem_icon" android:supportsRtl="true" android:theme="@style/AppTheme" > @@ -40,12 +41,12 @@ android:name="com.pedro.streamer.MainActivity" android:label="@string/app_name" android:exported="true"> - - - + + + - - + + + android:exported="true" + tools:ignore="DiscouragedApi"> + + + + + + + ZB1wm^STZ_0bC|b0rIFKQbL{UIRaF#lQ zqGCb8EwU0tg}M-sf-^xtHiS(=NPg!&Nf?g4-}n9fBOu|q_n!GVpL6cBZ_z?`jS=HV zU>K$`Z?5aF7)FF|i5OWGzI24V5@Q$}o98;~w~$qh?}NFfr8!r_K0LYC{qAOA*N4ZH z$osao0_UR#|C!-;?VXRW-e`5zqth0sWuE(I#+jVL1KUe>)Li-Xs^?CoZ;IRBsjK&j zD|@Y@n#bO{)uDd;w_@v3ey*2iH;Xl0OOnWsr0X}Is}%S3xu=Hq^i&(pSiM8m)4C=8 zY`DeItpVHZ3Zw^I2&V&HtX0U;6JYGy1yDIK1<(>3l1DTdHr*nw~D(e9z8e z>GS&|dvijI>OW*=@ZTn=cfYup8rpq>+2T!m*XuQT$InjKC5qKS_Boy2QK}#A^Bm+~ z1?I6?2X+Npbxlyuj=b-s6Fe?5-Abq#6%n&pIQH@TM*?%@|5rAz>Dae5*t%y#9*u;5 zNbzfzJ#29m4%K|LdsAH*Ly| zY~jX{Z==!0s%HfJ%{y=P+y^`dS4k!Imz(p$j4b6N+~5%gu@S@JGs>^{riUC3KU8IN zJ*v``%{y56K#|$w2r?|zWvmmv7d9&2lfl2At%5fC|7(?~Z>_?!SMiw^hk+c+ z6F}&o7Wr3vr`eI$En#6eI_wyiQFEG7C$U*_UE!i${S}?$jmJ{q!MS_jSPG4-rtTj2 z0VMu)^~iM&!a-tF%cHs4;eRe13!B4j1^!ZuR1m%w9ta-_QG-pOU$52xBly|WXQF&n zq2{2~ha3afX?b+5QX!x%`SHG`_$vgk+pTUGz7Pnjb~Jp48`4mNQtS}lxi14O1~i+A zs~C>lJ^T(x*Xw1740l9USst#Bj9ReV||PkoRyr++pN~hzE}h6g((l>w3Ub zc!#wHoPQvSeDP!h^44_v{Xj&AzmntmC6{2n=|f> zxLzuo{{VBx&jdTiV?7>=yN%@mvp>%MQw-X{sDpDr;niH0hC57kB7B=y!~{n*8grvkR|> z{P0NdIxwm4UETINw=Pj*><-IYvUk$ElVz{@HY__N>V^#B(bDnas*f zTUgK@g7OzSPk%5UlmHcZr98Sk;@@p3=pcvtEY8C52V(VnHQr%O!jMc6i;^R*?vMP( zGLBMHf*FR`DTBxXq1SPzZ$8aA<<7;Sfc%VL z#2r&aEuRXHa;4#-3-+(vHGZ{e->y-=n!S}b1iO3|#H;|zDPRKxa~x6t1j83Y$y6Sf z-g%oy8;~E4S0sk3du0z(#5IM#o-Wo1iU^?=naOiF`n$)u-;+KF|Cr^Z*;aPiB0iy5 zqi5tKi_Tvn7yEu)TS{Py5T63&eufBe*(0o%`?yQprc1a0tO9a zWc`US^m@4+g{Ek=*W`D3Y2UVv}W2n~E{K|Rqmq~ZHgJxUd z0ulpE92OaH6coX}h;{vu_A}6%hKD{Ra^YAvuAbDaU4vyO@iu9Tki#-%G(|wKyf15a zGiN@dFtyH~f1Fzrc;b%Mq^i{~owhm4r7{rOltASh-Z2zFE=Qq05t$xGN9tbcBppQ& z(d17IM_65vdW@yR!Yp{)r!0B|Jya+Ah((RW_2Ib1v2DLZ)-YeIN1i0hY=}{~H0~?? z4+-Mi$Q2g0ttW4Z#*}!acCmr%=%@JB8l9?%RZw$O^(yi6kT4udn%l(rhicAxCb^yb z)Qo=mrQ!4t>%CWKe+ZxcAb7u(2c`qEz(FYPstV96PpkM3pJ@p8E5`?RQ>YL_U!t(W zKgeP(VfFqP(dl2$nQdnt7a}Hhb8)=0r;pD2H3}z68jT-lNvE zWLz`tlf8c}sk|DYAqrcNu+B-dxKnU3&}rvZn&!3)U1Z4W*??q~CB!bpcLPSEsKsIm zEGrR)1&2t@Et6FSrn96(nrNj|Uz(;JoS>8h9=it$+9TQ_`FmAXTFF+58h+xln7OmU z#A_VXSIxb?DgXYDg9_fyj;$2H0NjC?kNnbjHsd3Lx9elW(kpAa#c$0-`+7V~d3o|! zhk)B2o`Xf>-ENa}eJ$gj(OJ^vCF_bKOg_H(C9>ocxlCo&9@comQ?P7h)KPqgFFB-g z13X=hqRw|Fj6*FVx-DxM*Kc9`{bcPf<9;i|vCse*Fc!v*?A=RPR4hHy<%<&6rn(zD zr}sGqUF5trubbJrEz>Y4dRCm+hWKP~mXg=s17Qj3Q?M(6fk}@|IQFxtvu|Tv#psm! zR>@CWxtldbcXc90tftO(&DFw_2!4=(x@Z&;)Xs>>*}%v=_qt3flWc9PdCKsYo-MBS zqE4Sn&oBN(C5q(`hsmqpuSZ@$@VF{nJI1N;7;y*j&(p4qZnGmTvYmNuzgw2SL98=qj zu!bVeKs+C$CPkG8dr&~K{w?;v7Hx=*>bW_uNeD1SvWbsxVU z`=I3si_ZEW>8x&<0)e|Njr!}{vV>;)kh+c^KETSjCw*p)Lr@JaFa;B$-OA9%R%{?C z*_wJ6X?`KKMdk?sGBSNnMpnN>@T4-tNL`VT_Gaj<%5q(yr#8;!P0jemh4gf0^Qz&bvTn@DAFzF0wcs3v6moZ?AuLbVdht&2`+?Ll?c7ch7bZfHdG zk-|xf9B^ROQTCBFd%x_Ty0fwot@L_RXOaS{;R^gx7O37{^|zYyJn1x%Z*?Ju+ZBGV z>Zw&V(8Y#N=5@iVH|adHZbn1~7~+5ne`y;EDj^5OOvA@?dWq~h$F7z2yE|Th zkNMaDFjY+oO3ZjcKD(#)7Ss_E$qoqRj8&o=3+OF?`$_|yp5^&?Wv10^J(Tnu z4uYz|$o~rCpg)9x+*?pQiEd?y?HXDUCXq{`pYbym4qm)!fttmNTejM5H81#nnT}nT zo@N=SWtHrRtz_e=K$#)&0#TXW5gh>XkNZUS0c|K~vVzuh#=l+l5^celJUdRr7f z$VK}hw=#I+pb{P1MTTgJZB3)4nbCcSZ~)hUOpdPu7)gTYPu#ZfC^JL|_=l?~+JVxr zh$@b9>5?T)~)>CqVw%{&QzdK)y1| zLSDlI#7lFnzRM|weH%QTM5%!-vJQpM{RIAj8!Rdn6*MfsAw`D4ofixiPygz|OMnRewf+@eQ5>!qAEFoNTm9}@<&rO(T2;wOGF+Q^E@~&y>@-iL@C`COsfV_go z`7%r%a1jD9N~x43b$W?VB_&6%V4js%O67I^kn#!#K%bc>q9b}oMSbY}bi6^fl656W z{{rW^e_c(kr8l+vQiJ$`)%r7@cyTqLIF6Vweceh1n9h%paN)R)$^RhG;g-EWGEW3omn0m@K*^{1Gu`6$^PP{clY z>4~QiE6JZCs{$>vS02tkmz8zRofWK&xG*@K0*4G$$kY%}M`DWM%-j^i-Z|i-%GGOO z_4y>`{nysbmKXFQ#}%HL&*fR385h~x7Eoq=Yfg<`v1M(V=LN<~uU`1)y}8fIDKjod zxcVeVv=itFOu=pY~13?w3#tSkeh_1`J9x*fVwniBFS*3J#L*ozi-aB5E zSUxeY`zgoV`|qG+tZ|;5eHg5h8<=#P{yxMY!@|2R;AgM;s^=?i-(7z9svxMTDa|3g zCGh>zBYf$$V&byM9lE(PF@StO_81u!e z==11F@x_I*yg+ZJ?9Yz&h1~fqt2k)~%9d=k35=R8m4$|1+2B5l_T!N?hpD!g8CinU zt9{vW$HEzs{#qSO0XKlRJ4lYJt2RtYIzO9`DH4`*0@h`>@3?8T0K7 z)rvJ<=-GMhxYn>pN_$U!?%1OHL6)~asH<1dTtUmuZ!a1_TlI+Q#F19f;Ce!Daz4s_-d6ds>iH+)T=>71# zF1RbIBkPn~QG642mZbVVrjOoc>H@ zIUGDdsv_Y`W=SPE7oQqYSNDA1_afrhzDlUoa3nB&kUl`e2q9{(OMDP*P$8}2` z8g}O?xu9E070vT>YJCb}Mv+tMKfEoBe^ler8J7ODS9zd^;Q5M^VQo{}B7RR@4e2Jh z>;+TFrlQmxK4Bmy?GczO5*LaT0J^r`b-ea4T)%4dp?H(3B(=~n*{3#}wdIjT^Y(SR zs7~6`xt)B8Vq}ejNl1D*C5dR-+L-n4 z-V7~ky}0{ckSQG!d=>lz82}9+;C@VHN@7q3A9CGJe3NVablvwr)odVfaX04HTH|^)UW};eW=4B(Q?&U%Y`(&yz4h?MdnwIfL z*ytt7waXD78G5pVGl^}JLb8g-n$mx15lmUo!T+g<)%#*jEKKOi<#1 zlmR);qzAzuMVUap@73Q@Kw5^R?b?&|02AnBa7CGPkFs47*Sz4i(R(aY|2;d6Cxo&b zp{-tRpvY~A`pVOwFI&`!ubJC!9ne=x#4krdUkf*T@CGJb5NcICH3#<<$vsmxo?2| z07*jeU?TB0?wMaj=WwGf+X`*_nyYV)&|I~ov~X-e>sdG67D#Q4E|U$gx1pq}Fq!C# zwC#WtYz<Vni`|{;wO{-^9_Fyl;Q;mRrx)Ph4k|4&8-Pm;xP|s4>#JYGC&qhE8~#I!H5I|!(D?RPnC}sg zdUY1!#Wa%_pTlWCO@we5-b45^>@Bg+zkRj?lzz#~|*td-jS-h33 zy)U`sre<-ue%x3v_jTg7f$|7z(zpw=Bss4!BpEMPm2+yGRSv4#ce~NF1=1iW`4?``t_S^C8 zTie-6S}SU=jvm(bR-b$J{4vJQF}oCuD+o|tJk8mYyEX#bSVj?cQ@u**9tT_LKbi_U zK7~uY6PEm&!`usEN%Ammba|%mVn@7#jQ1eF$eO5H?UuI?P)vr|A2P0_M}(KO$%i}) zo~2J-O4YEpbW;$3gA6Ea-BxVlUCJRX8GYxC+-}wdQ+8`%bRXkqdPj~|GL@NGCi`Td z0R_pH_7PJmp%$bht8KeV+lo;&31eyFwT2J%tcUIOuqw8+V z2if6Rw!W<9#osCK6TCWo`_yH3^a(eim_k1EU-sIc08xWl8->qN6)oT<>MSp5HZ}R} zU!EMJ0=%g0XX_{x-KxALh6KmX!bd2c^)lvq{pI9!k@_2R!W9QGRPj+^Kg-J~R8Inc zY5%WWqsV^piW20kJbO=;)FI*>pwstn%P#al$jQ~hfgNt3@VW3g4_T)P*Os~S_#T&2 zSRY@}$N74A)NJo9(fXagqfRYw#RRQhH1hQMZsc$(2c{6d@Lr`fP^umx4cP9~xJG^5mL-L)eW!l?esMvRqvfE4hs^QGPbxmC2id)> zMV|3zwyjk$ie-m0WhXp4pG>HW4lh{)WW%+G8>i!%DSsW0TX1Fbh;Knk+*mFcsV(adE zEiuGS0SFo=3vGP$s3(0Uu|8i?c#_}Fod6cxF9n1``=wyYk5-`g|ARKb2Nn6mTKro) zdu$vUhPz*+hv`8D87TQ!!CF&wV4I|H<42a@(;rQ*r7rTZ4tWOSImqjqkbFmIyvpHt z+BcCp&ip(?p-*mqNgg79snJ2U=LZ)yj_Sb&5v^<@KA<9q=kHh86JEWy7I;)vutr=T zSKq@p$qcz`(;9%2d#IiG&UcM)65vq03gkI_I?Rar-%JtM3mkW$yqPRH%?=OZy#9%ACw*f zHPs$@H13$hJ^V7v?zc$BWb|kyAsf7}$5NBsWJNTzPYac>Ub|osrh>oqWd~BkztN+h zCS3;$zWxKyH|c}5R^L?EKl*F0Z)O2ScL&KAAl@i)j~YS`DEB;*OCs$|-~WRpKvnYb zt(Rv>m|Dlunb@61a?j@Wde$aMbR1hUn{Ii5`YgBM6xat&M?)~h(VWuY0qZvA!+#>h z?FG^tN`QV=jj}sE2LBZpO4kHsF@(}@Uz&tZpT{jxvrJW`Pug-S4s*id){^jg7G|lm zrT4fI+!RBn37S|isVcbhZP)@&PD3G}4h3-ZK|vVl9#;hzpq6|F@jnwXt_o!V1d6L* z=n(2Ena4I))=yKJxpnZENn_dv!nIO2XQqTx5$w(@@q5OoO%`6Ds|}VsBEI|6@_*nH zWx1?yLOJ3`VvweV|IQ1uyXeEALe%)o6t3Dpu#wH_>2-6a@nzKqQpFRP?Qg~Ze)@&F z9~UUpD00-+oTiY%|5CFnIN-sXkX+#1O^;IqEihDssxL_iTfTRZC#A^rKs6qHA@CR9 zeUzo9>@@byNcU!3%?EF=bsI20RGfdi^iYx;z&#-w8H7#BlIwz-56<_6;cmpnBD8pv z98P6nS7s{0arnBXRv81Pk8Y_LaUaV*+%j>ItZ2Wig(g_=`BFfOs0#P~zeQ)DgMNgv zLbRRv*Jg+4TpG`IJRwh&_qze1TehW7!t$b32Q;l?>PlgDFnY~U3k2D3fg}tB1+;JR zH2*b(so&`^jqY@w*s5@Ov!{ z#a=U^B{4|pkwc(*2b%W%I#gE@TsRf}u^D=9+BhPNW&J6OU``YWE_&YXJ`IE0cq<1N zeN4^`V6`@y3M!7K>>Y>IALUmBbbETra%5=>2U>Q>0snu|ML0g6%o%%#Z!?iI78s9V z`M-5b!r&xIL+tVA|DEFQaaIfSB~|(LaktI$P|nn z4KDxBp!o@W#t`-KhK3}DiHXA1@Gf6JB2(kZ7RZ_p1bRR58&-uFnCZ{4o=kW4G!oUjLF4>^Zu?*l&?m^w(8}`b zf~t9Y-3KEO6A2VcrIanQt&WP67Y90Qo6 z?7=VWj;-}&su`#fZKr6f@yeWtc;cuo!E_gX)T+YadSTPC*V8zxrv1_V$D+18J_m)DGyUouT}#O^#G zUoOOY=BU%0ZYQO5t@K>hEycwx9z>XY0_TRX{C`2|;Eajvr-U-Kx-JK<{YzUa;VCX} zz_$gKC1(lRFNn1tdg)hFR{cEgv6P@#bTCWv>H2>#`rpv8SJ)WA&A|7+xf7Nc5O5%) z6!II&mW|t0ogJ{BOpUlzEzPMZ_rJF0e{~VQzGk=an;K$v*IRq+P7&GuQf4m;NL^SJ zrSqT2w5og=b>`}=*ky`vcIKfd$1cXGHH>129WW7rhfMYEzlJ!{fAI4UR}JCk6)CKI zQ(X)5Po~FD_@U*qL6B4sc-?PmM3>6CkAB2zpQh!t!*APTCz>ocx zx|L}Zl}OOskndr{R=bE37Eim2H*t4b@N9Jn4Jv~5_%$I!#sylXC*mKI4m#@8sF*RPiNZAWQdXGRLUfIBs``G`$Bq*_qptq{UnCs4y zw)=MMQzor<-Z0Z+ikQKr>Eo;gB(VJ}tEg$(<#1P~R z=Z}~Ck420UIU9G(uUbrOAE_dr|5WfeP`n(phj4z8rXqN#BmK9;?-I436O9o$>%x(T zkS6~(Tmf7il9MLlBa-WFr$|lQYbjl)4U#<6<(1dC%FXpu?9^6L6+tNUSktq@()*aj zExocl`ayPeKQ>U<7*s8YKf{B1^Z%h*?zge>`E`X6hnW@uj1qPBCL3Zl;o57!Z949ZY&YLncs(MV$mO-M($Wj#NYP%IT7vFh*L8BsUb_@nUr4QE8xmRcl_+@_W z@zW2oZ%B5qLRO5fgQX;dYZQ03K9Qf6(hCxaswWz~tY%j~{f}-rHtyM(-rOz1jOY z|DOaGLz3P$(pouLaX(q&M_a(vQnOe|#oV`(>=Apq?`49z9(HOqsmhEXZBknxStc`k!Q~I_pxbwDuNmKbF&(6U81xNjs zl#cfMt+dkNyx9@65?0uceJ7F&H@pack|enL_GJ8V?kCSE3??d-<{B&A-NEP1O=x}V z1(p328ZkP;Rq27-C)7|G`g>P+5 zeQqOV^WI?gcTx-`TlKMJ%o=))kZ%3KcliC!14n<S*Zq?XaQSC)_AXDL2zLobvKe4^B>20m%*%1#{eq?lv6Ur>X2g8rs$xo_vWXA zPtMZJ=SVu@3{qE1W{_6hyCpi!kW7z)5mnk{qdgwd<$UMThBSyoEXaH*nFA3HOon9X!#nj}4h}_`f7Jhq;-PTnx zEOS}!SSv}##t}s;?%J8|0rY{-8Ny*H(ljgVh-FE)v{}PivLcXlMT0^1g(r@>WArd4>In-Z48~3>wC%*j1&`iUm>bZBoAS0XqIqj&v>>Vp&-7S`tx0D zkF(Q(P;yw3SG$-tUMSXh3)Dwp=jzO+rva2_LY9FkPnQVO?ZMc_F3KS)F{4Gzv$LSE z%tK{mm_!yESn)nXeqji8B|c+@;5-q^!u7MJV9t?Bj>qe#h4r$UI`GJwzSCoAPCJ&g z--c$> zI~oA5VI+3;9;P|p!dD-i6oufZ3XU7k86OI;$J$ccon*lFzy{7bq^B^QcA9IM4WYrfqTikdla?Z;u7<=W1J@X)JtTw}P!|SdMTws;A z90r}b&lKHZjM3>?*TyLoC4Xo2k&c`9!a9>M_Z=iOoI?sjDUsCE~_J|-{oB8n*s!b4f zQY08TE*}_ek;hG>^FeWQ%z1w6R3~--IbKPfhUH59 zGhbYKdp4)WRsqXnYQt1~ZXRUQspbm_ju&UB`0x+Hqz&E!1jGWxJIwD4lEdJV`0L@U zBA~j(uaIIWP1f!`Vvx6$)R<}0r{A)Qimli|3O|vX-t`+V2-|p{axEO&c#<-GO~S*=Vg9faIkFeB#B?ZYl_titml%uGjo)H!9`IuK(*t+Uh58MyrFB{cnU*| z{Q&7P)a1nO=-Qq3j!KS9bib*Nt&cPwli|=tVS5y}6zC;15U$N}W7}c2V)A-EX`(uNampWM z$E}rD6u%~GbZ2Reg5OdimTH1k*Jy11?8f;)>fKekbwQ2dPho2$cZ+ulJUb;udUyW-hgEXZEAO#CehnTT5HlVLDUX~PnX1-pesG-2z>dq zHmp3xR8WXqj9#ST!(eK#^?c=|zQyDJV+j8WOz2CC&wWfdv=<_1)IadR{cC6^@7juCY{=`;Z(flsr-KNdhNhC5MhS(qv0)c{?QWK4?;0nIaS&oMvSQXTnz^ z_yfKHmd+pJ5@7?2w`Vxi6SgeaE|O0@BH#)Wn6EV1-Nl9GoMFQH6Us}1U`gUFKTq$K zLB7K?mnvhK9wz(6lOP5xN0f0s-d%^G&>}iMk>?gc5?s=$ zOPWGr%&MLLCK_v7m-K=pzX%V-#;77M6+vI1-idguig0b1yYn;*I54?mg-_68hmR0M z&|_LzYo$N3N2(Z&2F!q8KC2lWA!)JbTGJmWJdTDL+d)FQ=c@6 z#HiboTeWY@eHA+!zAyBvgGe#8sRZHurUUp?HAQNV**1_1lCe*AgpXK3y2>GsWy3$) zL3#}M{~$U_3|Ej8e2`+aad4TYrsgaT4!GDLTkL0t)?iDv@j@R~-5mdhaBZwRkbG^a zgl$;(QMB)UJ^6%dy6(<~nAYh;@CRLiw(L*kfJ~*+pD-K@_0XWSj$d9t%^lR=Q`|Q~ z;YsNdNe;wf8OzR_e%65nMDiVqFmV4%hhWtXiP_bR}UZ-&}v{cgTNfJ0CzKVjShPIF0SadBd*K-T}&gk zRi9*>z5*exgd9?_JB2yP=P?ULHh|gfeXApTK9gug>cGmux{9(e17f-P=6~VDj zJ5pUb=E0ejDF-;Up~6Q?s^EwL?<#-3v@Z>7E9-i*W0E*I;05Jc&67`IK0SBekk>QF z6Sdf7%!DEuI#DMru% z&bhxQ94aJ2M853530rBU1;wl4?gc6$(XNJs2mHzc`T&mUz?+Atz*lO;J238XPXKH~ zBB~P~O*_TemoFPDuTvxD#bmjtvsK1*1Gu&)ULjmN42e&J-OJQbb6%pw9$5pn31+8+ z^Dr$0&(9tpg(G0X0RI4xgz`&Fs6neN{OzjJH^?*=uyygV<-ZSvrESjjQvz!I){m(c zyW=+NG}EIED`+9tsAX_xvi_ ztrx<8Ta-h5ReQR~l3}RKmMF`0i>7Lvi7#&PH^kJ^b!;))BJz59+tu*xX<|W7uo^E{ zFGUCD2^5!Vk+E_=tA&PaV_*NZ1-Y^6h-lPjAN`3F)jNv-DixkdNHYt5(b9J+UWN)i zJnFbHPgu6;>IF~#kokKL*7Jiqk8OO7hQ)OOa2c!0{F}vrsBu(>rO8eYQ%op)HD=>R z8j`=fk;Qctw}>Ro(_^9UCOr((Bv5#A=zlSjIgaMQQ`aZ`ttT@poO@Tx&V~_Spp1ri z^nIBSE=@KCJ`=nK%1;|&+jdOa_TC0#F85@~mZ6J^&RjG?9P-5^f{Cz+6=pV{+S_!c zZ+TmHXxNUPt8b62NX-lQ@N2{l;ibo?yzRn_}%rOVqYI`vn`cWQ_^0CKwcyevaow#kY}>-8NiYi#mge5G(7O0)Qjz2>;G zL|FM`XzZvu(}r2)b4$^yv0hq!S+^!@cke3Bx0n3eH0hI_7vo~s21XM%ZRRc6$@-W6 zUY^li-oNzux2!AWcCB1~C&9wMu_4njBHO$;e*v}KSzMmZ@QUbstnqw(h=a!d;Fem) zSfh_n{W7T+p4MC(=_?!?C0_r;^1`3oJK9pT+2>`4v&Ofq3|_0^qY{rsOuCTsMS6(F z@VEwd?GdX`v^cfC`YHaNxG&}E23LkBMbr2P(p^{)Big1Lq-a73V7D6(q6Q1NWM3=bkX?p*Gfvu$0hjB=3uv+D;r0Q_?s6J z)=E~0$h;eJ zaWpqyFEvjTIy_XpD(@-0Gi6Y-6Mhoi8*9mU3;(vEY{`(z7O|{e z8unC`_nj`G587YxDB(zMhH+6tdrTf*>~4s8!N>x-1*e=#{t6n7m7co$Z~S}eo8ao5 zLdJ88%~u|=%wyTjIvF<%RXU4yNnW}!Gk69nL-$9$)YF)g8QK&9P9yZJ_7aD6?*FuX zn)K}^XiFSo3=^)`U~8bxfz?_v5GU~3qsLt4I9`iwGB#D>g07#4p*4m|uK^5JZPrv; z#7!B#O|vgeUvl8nr&fNksn2Fsrn=_$>5LTyJEu`~Gjc*dV8DtEbcT_z{AYyBZ)g4x z&!Obe|0Net9jI-E$)?PO$n;{D-r@g=AqNw{iS~a;_7n_OO!$esY%Wq9_ci!%rsH@`T6S^cxic57S6n$s8VKkSXvXOBlCp398*y@W|!oTJ)(PR9O)#SC>qpj z-HBaDmnHGTR0MOQ(Q#45wPZYMKV-NhMvOLc`mPN{*?5$%>dq|JP)OTG4ACBT3fBws^@Fx}b8yd>KUw_u}+5&gyKe=ba*H(1~ zo@#Bo=-K&d#)5}lx&+bcy$KczsSS@miBv@mfXa+}CJemv=E*qLs(3D-!8-ENO;~sd zP&vbd?$%NJ+X~b$Z;%~CSQCE>XI4E>6EQ_C zdO&FDxXWo){FeX$tB&8@oUX||tJTiPaRl;dsYNWi>oPB#oV{ayyU;n9jMubsp4UqF z=a}U9cAdE#GrH@M__*w&=`2zA4}CAHYc$pdd|1vfYmogZdlnsBNUzB`09>i_F>sPR z2QTO%Z6pDE1RYd2)@m#~E5*>hh^2|)?Ae!&s~ixy&&bHav+V zWClsijg45A5F}USjVvff=NTRgP74bYVCAl+J^-p8Qh3ITxFZGK->I?36C%D`b3wEg zWc1EAx1XPrdi{Euk(>C77CUJMlpA+C$$zCXHd)b^3I7$HYYub#+GWgvF$#N(CXLj= z{PGJB<%9eRH5WSI#>()QS|`h}T1^Z}Cpi;O08!)a-#---!y9)|NKMP*0Dawgx1!~w z8!yC^XGmi zIJQW;Al+2>@AJ}%?&g$wvs}O^Jcs|-g1HvkvbgLxwHwO!;|9P&Dl#y%I$#d_RWfEs z%`2o>t;H3CPnO^`&Oe({Wzlvt10Fyi*lh3sj(IzjVY7|6o zZ&`*s@bgJDhzN>Fw6dByf7zCQu<|u{Vg>dl9yNSf`bkt(-Xxerlm`$ry?{@x4DS}F zr)xINBRq+3569D3_cV3cf@rn#tXG?ps zZ)*LnqiZ_q4v!tY5?%`Z(^}}!mfF(EDBg3r$PA4?n@IT&$r;<2HiD@7)ZoqjtFUNr zUpBmG0~mG%r0E5P(le6q0ZwS%>qLy&)dDYP(wIh+PB}|l`l}ZEsk|fcu~~Zv4WgXS zNbc|h)m|5;w5;Mj5C-h07_DKz*A&VY&mFY~!)<(+PmG|ykHHFHJK;qVlL-Y=l+53N zphGMnlT8JTbjJJa20>P^1?xRFgndS!xBsRmywGAo-#x8%|DWDnwGi^}GYN zk>0gm{RB-NY&sDx+@lxIpp~C&N(F7~keqUnMh7MTl^uA0jRd{PLP$MM*)m5)2Ak?6 z7ibM%S>pKpAwDG@|4WdEaulej<%u5_zNH4JCAh&e&z)yodPau2F`!zw0m*8Jm%T>S zoP&yw`9a0xUH8)2!sDSYz3%h6=do&p(gs-EQfd*!S{%=QfodEZDxuSb81UYPK3{M) zW)SBZpeOOr6oI!9oyiEHFO<)KWo0 zwlyo8PHkgVNOq{h80Vk9J$9Akx%Fl01p$*UEF8P`8}k|f-BwH5D`ic(SjWFm4*)n` zYw80uR21bOIpu?O_gsVFW;r9Gw25s@KW4Tf3U8@!wJc*VtdYazZ5byMnLn<@d zj;iSP6AEA4AA{)pqGj^EwODrePCSR97v=zQ1n41b199L8ok^>XZ?)85Pnk-Tlh9$(q?G2~ zU(_S!0s94Lcc>Mdf?4-aOJZ1uQXDlpzd$+!91hj;{ zh^+E}$N9+*o2-XqhQG6Ntvt|q0fIn9|4krq^*Jj#zNJ=SX^0c|TC0zVR^y@HK|#h#A^X zdc3Mm`8svPqA};$wasrVY~^O}>)k#>I)x(JWCz&5wu$_g79mQuK9*fq!7fEp>gcr$ zl#eV%dkZ}`OBeft)SmOFf8UUoP9;IQ&hH8MNV)bz8zQmy-AgxBmcXTXJV-_BV=TZ{`UL1v zT`(2=RUap#;9U=hsydi#i6_iN?UbCMEE*3mw7eK@@!nxYl+)d1?;fEl6W;P%1S5`q zYB6XeqYQB{Y~X|9@*pmEmCnXc6J z8-h@J##(ySPE8Z30-_nb<`cab;S}~hI9y$JgYpuZT217oTgijk2SV}H(h5miZXw6= z0c(sq?}jmmDW-yd#nh)MN2R$4Wh1`#&@VZ-7d3H01 z&C>Xk%oclVY$`>%m#}3+KR1qUyNpu4)3eoZUi$^^$T=Mw#uMf*F!i}WImFT0;=7|K zXF{PGBywcgfOJ1{WD$La*n44cpR6$_I!nLwczP4JVr2J+dAYgM&gzCaP7D6MIoPjx zZJ|BE_Am9>L}TMcB;#!_ViHf^jNA6z+}X27k+pvyyS|uVc6i$3`ugSkRkyPW|CTm+ zdZuOtmDX*4)#=#cyW-D?6s^Y@IGK9n!KoI)WqN^yLsEM*Z!9BV*H}f4kH*Q0i+9i*rTz&=|m%vcsuFV zD5CfER%}B&aVF)E#)wssroG=AVIi2$Ej8zr;<;|vEnkuih4_4!a~(yP4B>KXo#`^d z%YE3`i4mmgv|$rI{Mf0%T>A2;*66hyS2NIB_AV*7?5w zmbDy`D%L_^7pvVMCTh7mw-XlqP7>PkG^Sz21tZuCNtd1LOkukd+b`+Ps*oI=^cVBi zah8F5*StUzqvH!n8HrUlYk$3>1xp)D4AF>dJbGD;EwXE9Sd1-R%G?%&brg|%G!Kf- znulmi?EQX`aC|*s5r>dC0{g)nTONxYix>reHZgp-d-HPAeZO4~5}fb(Z@rLaw$dhc z_lf)!C-PMiUy-$|SiCvTlQD4^DN!(8_+pp&Gk@QS0h#iyv=v%w3u`+oNc z!WLZuk1e>39xfaYXN!Axn|!2=!j`JES%@#6?~665rik ztLhv8m#gS4Cx|*~b2zTSnEKztVUs!8^Y*PB zByt;FU}E$g81dy_sM_{vqX{UufT^}(0$t1zvogfyQ4Wp8BBm36BfMNe+48Se;}~ak zY!k_b;2h(cGh?!wCDFPGn zDisKb)oFXvr8QdTeBS($CK(yeUW(n)CrmQl!gIy;ICBoNM?1~IEJr&&TR0L_0C8WCBgKDzTJ_|TYe)r#z+lYCB`7fx6n z%l5f0%H)b1$5kACJ89|E8{5sCKRU-^V{#+6jDuKWfpjaf=@H4q)4VI@Z^0(}PsPNG zPeEx6T6D2@0Sc^Ij!jtDMk>%|X>B zJPr1F)5I9;kcko0#1P4M3vSA-6rvLN8wU0PM%y^-yq|3GL3K03KU5N9$P>TEicOpk z$q3^J*D?D+ok3})Z(n61qU!p4jx%u#iAB)%f>5*v?df1ggc7!QsoKa$Re z@yH=1(?d$>*qCz5*+{A@46RW4Z{7rs5@Emy-z+FG;t|nNGNH684+VWXR zJrVBC73eUBRji3eW+(c`yf?+F`THBiU~Z2@|Dq?R+G$Md8_M*navH zV4gOsvzcQEnV3+Qux=QZKKf;#;`u|gW0gG}veRu$@HeX{n}yPS_`VBdp)NMRm}oQw z0yT9DMSCQhWD?Oc~U%(>5kxBRbBF(|P$FaJDhtFXH<5Dq{c4-;o9mTQvu4Eu}5 zUWo@AcVg zJM!<$H5beyih?ZH2?WMpD9;{8|`}F0f6v6C0lhC0V^$1S_z0ix;Bfh-i6X2(kB~#bnsf*!wE+{!Z5>HdY@KrR`M&Mv?@i?x;lcF0 zF{YU`o0wSLZ-{^(AG##^TkTCP+ycgZhj$Y9IRpf_xcr zr!x1!{)!*>v%{PHr&SVT$uUG9``nb1w2fHo{^p5*{vzR%t`w`kQl7eJzzXX)B!p7| zhvCkzeF41Yrhs*mwocV~)O?SrCbaV7iZlwjL3>Ypc(hwY*>NO0*Ix)*yhq|L>MmR! zb=Pe&_CS=ZGed9J!k%>pN0}rZ!fGFp=Z@Tk@Mcfg4C3yKE=S8qhfsp~96UjE^i9)q zzxBrjbCJz0!pk_yAqvqJdlHLr2wNu2WsF7=9Z*{El)O{<_xf1tCu2XwK*Zp=_ovha zbDT#Tvp=?6C9m(Y26tEs0nn9T^{=Y)GzgVec`<=+3RmJuS#GN&k)5jZGK#R4EpZ0B*wZD@k`9FwPbG}cpx>o=R&H0BjWHxzyoBX+5qa>oT(;r4-Z^$uf z&h!K9$Jj(u;(jvZ)Eh+hvOiX-n-|(z?%BR`Df80UQ>L=tU7UwlUbp(6Cpi4^?W*2k zt0DWfWcUZh3x{O}$oMgBmi9z@EW|-@+I<^lqf5A{_Nx!`Ps#K-f7uWJ?#v&q{%dWU zE-xglox>xV7OgX#xO9h`>8HXU+to5Z1|*OL6Lyuw-P~x^l5o{aFRk?E^J~>Vncg1h z4dE$dZBnRKKvq=kzpYLibM~K~v^}o2WB_69ZCTVCcFMAOSJ^PZ6xJ`|4TP8RiiB|) z00l5V{`-DtORR$#CG`5e2^ITEZW%yLx3o%q@VTKjS(Iz0svk(w(tcVUOP($%BI zBr|w#sLRh*jNTbbF2Shn4 zjPX08_kFMLb^ZP?GtW8axzBxnwlT}KQ!p>=Ki;`^!IVCeB2=*`)R9T)P7@>!&pH=v8hzAk$^-M6i?V(oL8RS%h^8t56@8@Xi4?P@T zTwJN>`9sB#7{47TXX(r)p^8;0b4g+?gGaj6|s(kw2rp0iq(eoZk~;mOgF*uwU6s-v2JP`fiHQ{ z&)*9I7Xd_)qVhzRXiHad!sIurFzesfh=EKUx+ix1qwZ(@33;7~Alr?N<>o>EDT$1F zSDBJtx?R$IUC)m(Fe!_b;?R49&8;dsUU5J%`$B9!u+656WPCG9xTB)@z^Ivf=?YC| zgYL4{#9WfkXrzQ?1+VBy2aeX^K5k)Ko1Iu2_wYX&xM40T0Gpql>k+=+5)$AgiiVdc zYtA85#gj{=tg`c}f)=Gkk14zz47?g7aio@sKg&;}efkynfVfsyHl&l+vB;FyZ|ghK zDSmE`rBW1Z$L*o76G5_=E-RRCu`u(bu;b?I!*u+rPfiuu^rT%asFGyUbsjMb;$3LV zSyQER-B+A_Ijwy~=wm4fpZet1@IwK;mE{7AlEqTxDlIC1!fbJ+Uue$162C!7ObhiD zU!QZLd8}NSRZUn@8SS#Q9i%bb=!1c`GRWl6ExWk z|L0@9Cf#w}+D-S?$A0=0>iu_iEnHaEsK*4!cUYNpM+!0zzoM-dme96th|woH)&pKx z(tqNUz0;H>E;>+znl*cluZy%8>3xufD+(`%Z#;wy8Q6&x&7_58s}iH` zDuxu!Djx8APdp@Zw$|%q9*ybB1s--p{)RaD(Dxd&_}iJ zCEgnFw4atHvR|Yt%%8rvCuVZO;!`<$i+{sq#6!h_W1aon@4hUY#&seBG?5wd>yOTt z2OKs}E3dv>PP}e1YNo`-vWh~?lV++<14db!k)9tA!Xlst=1Y*;#?6XHV-wxF{>ujb zcGqvN9J=^goH0z!xt|j?h>w`8CT=GyBg@!GuA)GJse#(l?X%rnvfnOyzs=;c zE?fGqC+GX~8qp0J(b9wkw|b22e9kuGPJdy+!JgRHwH7lSDJ3DXa_kjv+rvJtelI^? z?jda6+%?>{l`4inK)1Jl4T?}36kog}$V;SyBd5Dmy#Ko*l^olav*_0B#0-rOPaQaO zxBfhCG7=8qLl_-4W|4P`l;4fa6})2YfRjBwZLmiZwOES$)3);k&B@~LO$f4V-f1T{ z+ebPV;hWKR-{`NyV^%c$8}q-BdRj-<3`dN3C|alT9rktXrD?Z1gcsgMyT7B_mUA?B zt+jeAc~=b@&E0VwQ%a**d2C@$8fV?XIG0r&pDudwSKElt1}tyyW^-!IoW>=!bWY1J zoqWl2Q^TmuNeRDpGi!h-v%|wQ(bsv+h?F5+Q#f3$F7YN#@)7l0nT2)e^|pO!lvmU8 z{&GbwT;|VtoXT+IeMl{K(iM;9XH+3vO>el4UyJqZa-&n+O#iRJzyU!d{(h^)A6DC~ zU3HDmwX_4@C*F~gGfC5r?eH5Sw>1UVUXxqO{^E_kvEKOXzo66+>cvSU$pJme3v`Z@ zEp6*qe@hzhtJ4wPw|erFKb1R_;{{d4M&fRf<4&|*BsQHF@f5!$(l9~&hk|CqBcgT1 z7PrXIV`O>U4~zG!?JYtIDqBVZka$Bv4p91R5pX9H>$71*_sIIj0*UpsHL}Bej;`}D zsV#Pj;gbB!!A&5d zmi{=2&R16+SzdGys#ua^>)rh*K!G&8EPzNwlKA#Iy?U}=f}!wtq)-ljN0aT3;+J8= zm1RRTHIMlw)6BDtOVCfT=fj+rc5~-l`qGmQy_&`dvkQMyWk{z0b>Gya*@>iz*cC>V z$|dXK!I-SBK3>v?z>5Ah6@iNGuDZcYjlrNLz7Cb%S=&mDhsFP(hM`lK43msvK*ho* zNY4=y+dGKAYSckVxRF-43=vH@{)CUW&`tgORZnO(bM7n=bss#*RnpAqv&C)4Qx)WH z@so%g19vGghUU1kaE{LD&e`3s$CJs8#AZx_dRz#vh}q&)ikG3jjSugRse5Tfs$HRXLHP@#DV#!3>Z=Vh*BXUBSh()AD?iqT?n=u3 zHjdXMWNb&liF^|kmTs`l*lhMWPJ*BO*?$8R6Oq==yZS`2q#s);mu=G)joK{N+AF0O zSJuYSczL!Dt#2Bi;uZ|_gh1wy!_?qIUtYsk{l5DUfjbEcia4Y&-`nZ0e@;iu6y$lV ze#iDdT78*OvQuxz!RhGVSH(!K@I^Cl77%fqyTq3d$f1(v_yxX4#gC&eG|2Bm zMom6Xy{fC&Hs;pV+j-Gw7A>Yz+IwbuvfueXSa&IQAUW>c_G8PdL}a9i=a>x)?l_#7 zRcP{tI-9v_O{n8f-A7%m7IG#A@4a%&!+?Yk0eI@lMC5XKYOg{52)hQ7KE1)*RiLTw zvKprThfH}Q$w@sOY(Ac0lJ`h}i-jv4jjIp^_J*Y3ZSWCA?i9P3yC9^6ePb|szcmx& zH{M(epHp3!IC8-yh}-x!W9bx^O<-I|Qub@OpjMFtlq#Sr$^ML^yw$pU&KH{-H>TTi zCf2#jp{PY{w{4ZWWTtKXy~|j4J@_Px!5)e;U*!eufRdIq`A_WHl13!aBql|+u)7m| zv0HAv3_+IuhYpc7);MkfYUPKa+h{05s6Kz>$ifb>`L0po%;^mO3;ObDiyuOb96iv~ z6v8*6s}7QH6p*Wv&sXQ@yqvDyjU4Cq8l$KnEt!R#V}nK%ofAevh*+~+4;l04$=}bS z#c~lOj>r@v8qwW*_BKF+w#A@ZNdZfPR_BhEi@l5%w|*H%LH~nm;hZWJqz)Rx2qEh?>h@X37OB)l~H{Yv|Mwe}G4-TX{g=Tu_ehqh|y>UhNX;klb{W(FAxbO8Ni{p9c z4GzpO#|8rmP& z_sXOTf`ZVrXSBmi|K~mX{6`R^jU?~!5&H*ge#aM2@q&AHsn%XG`};age#N07jZ)q9&ad}$)@e;c z5yb2cP(Du^39Uz$`AD)g)olxw9BCwe!(g7rq)&%B&F+|eBk_6JJyG`<^Ku#`QdliZ zK{VT1IaeE%&digapQz{H=NmJ4>1BMIf^;sebM>F`SUT>OO7R`ZFcvYrs)BkEMfeNk zWRN}5AcOb3&X*5E^Pd(9)AB4vC>6GDEl{rE@~m)+;XYcj<3O<6wn5;kXj zskKe*68D;iCW#6sGrEQ%C6x4sr))W6y?Ej6QF{gQ)q(Rve_~$yH;RGE1)tF&sMbLsE!uQ9!x@z44Y%Jrn*k8s1B`0}$Z)q< zUHk0gIg2O&2;hYNk@Jr!R>pUGoijc?33HV&x%*^K%RY4hmo&rt-ZHYp!0Erk>uOv& z8zi^j;Y!hOeRo2sa&S1(8VsT>da2iB+-s^DPD5>IS@#_KJ1MLgcYES4O{J`ZF_WF= zoMd}4kgp?Eb$kM!)Grd;B}FzP8U(nEds_uKi<*z~JpckS>4ivwiFH|9rI8_>HNy(_ zzV~a}!u8cS>a8l=oyr$9IZJOi`DVwYwpc8Gd^@e;{o;^MW8H=PQ`eMrtJ{;&p)Fl! z?JqR?Kx{O@Qvr4Wuc>3&wVa{XVqe`H9hK0Fo0^+KcZNB2ZSUo%b>uM{1=L99Au_w<@g6*}wUJe=%y_SNU>`U5` z<$WeDP}%?s(2E%mO6e zm9tlSr>Q^s(uoN|vT>hz+4$5({!sGXAF*$3YY%0;csD#TEj{=$BKwv$k-Y_5aaWh^ zl;Mt!Of9bC5ro=I)5T!(C9eAA${%yhPPw=At^4!7A7|d7tvu)nU>pCrBode4l%P)7 zd+N*^+dl>8RgaFiJULeJWgq#L;z8M^>Gh^}-_4VDBy2%czJOeZO(Z&b5wPu{Jk#R8>O1Gjyqtk(({*@WdC#HE3N9)@#0)_5)P9|e14 zH5#Eb%>R#PY!^k4h)RK#G_^|pMERA1_@1fTa;pcYu_P*OacsAkYnls%i~1~=VwI<0 z$fQRaaUtTiAjI})O{Ym((X}$7wRG}_i5?Zvg4V}~6=h0XHX#O9^)-676@|9m@%=~I z3Tu1@0j=OdGo_IiazRgpK*1BNjjtg$B|(18G4h)5+Gw>x40RV@;c4yfcsJQ|o_~0S zbjsx~b+)Zt*^_$lv(t0zMEYNGv@+6%Jh-pxMT4@1iB1mRD<9acmE{ekc)a^!7lmEr z?3i%svxR7&OpN-lc1M`^<()SJ&kOVmFRko2Xrt)nB<5VwY(15!ucrY^WEVyeT?d5? z7q26kXXO`5=p*U$N4yh)F3KTgUHIfKiG1%Zc*V}9J9xXievF(1P`A0R>+t=VMv%Bo zpe^b?htPmufw+DVQ$c>|0XQB`@WXr*g@6M!#2~F=WTTZs*KdgOgaf@~hwcWD=8fPZ zI2M27pK%GSD9{~Do^eR)!!+uI`=Eawv)!uuE)D-HVfa!8xa>)dOp!b_bJ1aVYKvwmP#3wS20NXXjnNO=nVb*L+81kZ>V8(a^sM#I>~LH2 z*6wWI@x79T0x<@#)R_ue94vCBsV;pzoMT}s5+r5>DF%$~+e87&Ls32-Y$Vjls;`^t zhhn!Y=%!?pf<_FcYX^Iyp>=+zTMRz|pdY3*VTu3>b6{584@L3KE(c3nO3d|caQd|h!=qHO_BXrr; z&y>$PWwgPi`ME2AFBW3S`29Nkv`>Kpem?b{7Z@EeArqb1%!@PIV9#Ls5EUXRTK2VY zY1@w33TCfCkD&!L3~*MT&(w4nmW`9uzxk)?YD?s zJ5K7V^cFr#LF%FPEjGp}K@0l>U;p9{R4Jb?3i#TF;x=2waM{>(ycc=_x`Wn_Xg6=u zt4xW^IO2|9oEx*fS@xwX*}3K0vfJHN zG)pcuYr`Rr(pj)uYtL7|gf5T8rR`q9&|d!$R&^dWt9qkQa+x^8|DeTWq|5-qd?FfNge(i8_xAjVmK$7qw6 z>9#HU7UreiG%vm3eU84Q-E@1T`15BFk1eO&oyk7th)Qr<-nfyvvY~jh?@YqRY|rNq zdQuc2yK=nB?R9^87^17naog^Lq_Ef4ea;%QSi-T9WqtH?sMci zV7q(AO8asvnuK+zXRibO1@)Z!6V1M(&z&xB5<>21YCj=G za8|`521w={(eY_zeCq1?K=(oC-t)aeC}H>#24~0ZK304dU?;s$f$EHqzoJO8KF%7~ z%vQfeyXd>l+4T8`ZAi6Qxt4K?8M`0g^+^w! zT+r0XOOhNmEpAEIYp26>*<){#O9iy#n(HfnxLf=GG;FCF${x)Vcs*FH#O07S$quR? zSFH3LPqrR7k7rw(arLl2+|CXR^~?~Ywjkg19=DR_&XwEBD~!DN2VCr9qTdCi7{A!;;pyU`ds=qLIpL2#L#Q zI=)tx3&7BUgMvsox&od9Y;5rfP3axCoXTS)aRNzfF7wX04E{CjHvXR9r5�q2FVx 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.id.b_record) val bSwitchCamera = view.findViewById(R.id.switch_camera) - val etUrl = view.findViewById(R.id.et_rtp_url) + streamUrl = view.findViewById(R.id.et_rtp_url) txtBitrate = view.findViewById(R.id.txt_bitrate) surfaceView = view.findViewById(R.id.surfaceView) @@ -115,11 +120,14 @@ class CameraFragment: Fragment(), ConnectChecker { } surfaceView.holder.addCallback(object: SurfaceHolder.Callback { override fun surfaceCreated(holder: SurfaceHolder) { + holder.setKeepScreenOn(true) if (!genericStream.isOnPreview) genericStream.startPreview(surfaceView) } override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { + Logger.d(TAG, "surfaceChanged: width = $width; height = $height") genericStream.getGlInterface().setPreviewResolution(width, height) +// genericStream.getGlInterface().setPreviewResolution(height, width) } override fun surfaceDestroyed(holder: SurfaceHolder) { @@ -130,7 +138,7 @@ class CameraFragment: Fragment(), ConnectChecker { bStartStop.setOnClickListener { if (!genericStream.isStreaming) { - genericStream.startStream(etUrl.text.toString()) + genericStream.startStream(streamUrl.text.toString()) bStartStop.setImageResource(R.drawable.stream_stop_icon) } else { genericStream.stopStream() @@ -167,6 +175,7 @@ class CameraFragment: Fragment(), ConnectChecker { fun setOrientationMode(isVertical: Boolean) { val wasOnPreview = genericStream.isOnPreview + Logger.d(TAG, "setOrientationMode: isVertical = $isVertical, wasOnPreview = $wasOnPreview") genericStream.release() rotation = if (isVertical) 90 else 0 prepare() diff --git a/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt b/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt index cc718101b6..2112b50f4e 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt @@ -52,6 +52,7 @@ class RotationActivity : AppCompatActivity(), OnTouchListener { private var currentAudioSource: MenuItem? = null private var currentOrientation: MenuItem? = null private var currentFilter: MenuItem? = null + private var currentPlatform: MenuItem? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -65,10 +66,12 @@ class RotationActivity : AppCompatActivity(), OnTouchListener { val defaultAudioSource = menu.findItem(R.id.audio_source_microphone) val defaultOrientation = menu.findItem(R.id.orientation_horizontal) val defaultFilter = menu.findItem(R.id.no_filter) + val defaultPlatform = menu.findItem(R.id.platform_huya) currentVideoSource = defaultVideoSource.updateMenuColor(this, currentVideoSource) currentAudioSource = defaultAudioSource.updateMenuColor(this, currentAudioSource) currentOrientation = defaultOrientation.updateMenuColor(this, currentOrientation) currentFilter = defaultFilter.updateMenuColor(this, currentFilter) + currentPlatform = defaultPlatform.updateMenuColor(this, currentPlatform) return true } @@ -108,6 +111,18 @@ class RotationActivity : AppCompatActivity(), OnTouchListener { currentOrientation = item.updateMenuColor(this, currentOrientation) cameraFragment.setOrientationMode(true) } + R.id.platform_huya -> { + currentPlatform = item.updateMenuColor(this, currentPlatform) + cameraFragment.streamUrl.setText(R.string.stream_url_huya) + } + R.id.platform_tiktok -> { + currentPlatform = item.updateMenuColor(this, currentPlatform) + cameraFragment.streamUrl.setText(R.string.stream_url_tiktok) + } + R.id.platform_youtube -> { + currentPlatform = item.updateMenuColor(this, currentPlatform) + cameraFragment.streamUrl.setText(R.string.stream_url_youtube) + } else -> { val result = filterMenu.onOptionsItemSelected(item, cameraFragment.genericStream.getGlInterface()) if (result) currentFilter = item.updateMenuColor(this, currentFilter) diff --git a/app/src/main/java/com/pedro/streamer/utils/Logger.kt b/app/src/main/java/com/pedro/streamer/utils/Logger.kt new file mode 100644 index 0000000000..4ad7cea476 --- /dev/null +++ b/app/src/main/java/com/pedro/streamer/utils/Logger.kt @@ -0,0 +1,73 @@ +package com.pedro.streamer.utils + +import android.util.Log +import com.pedro.streamer.BuildConfig + + +object Logger { + private const val TAG = "HUANG" + private const val ERROR = "ANDROID_ERROR:" + private const val WARN = "ANDROID_WARN:" + private const val RUNTIME_EXCEPTION = "ANDROID_RUNTIME_EXCEPTION" +// private val DEBUG = BuildConfig.DEBUG + private val DEBUG = true + + @JvmStatic + fun v(subTag: String, message: String){ + if (DEBUG) { + Log.v(TAG, "[$subTag][Thread: ${Thread.currentThread().name}] $message") + } + } + @JvmStatic + fun d(subTag: String, message: String){ + if (DEBUG) { + Log.d(TAG, "[$subTag][Thread: ${Thread.currentThread().name}] $message") + } + } + @JvmStatic + fun i(subTag: String, message: String){ + if (DEBUG) { + Log.i(TAG, "[$subTag][Thread: ${Thread.currentThread().name}] $message") + } + } + @JvmStatic + fun w(subTag: String, message: String){ + if (DEBUG) { + Log.w(TAG, "[$subTag][Thread: ${Thread.currentThread().name}] $WARN $message") + } + } + @JvmStatic + fun e(subTag: String, message: String){ + if (DEBUG) { + Log.e(TAG, "[$subTag][Thread: ${Thread.currentThread().name}] $ERROR $message") + } + } + + @JvmStatic + fun throwRuntimeException(){ + RuntimeException(TAG).printStackTrace() + } + + @JvmStatic + fun printStackTrace(tag: String){ + //打印堆栈而不退出 + d(tag, Log.getStackTraceString(Throwable())) + + Exception("debug log").printStackTrace() + + for (i in Thread.currentThread().stackTrace){ + i(tag, i.toString()) + } + val runtimeException = RuntimeException() + runtimeException.fillInStackTrace() + + try { + i(tag, "----------------------throw NullPointerException----------------------") + throw NullPointerException() + } catch (nullPointer: NullPointerException) { + i(tag, "----------------------catch NullPointerException----------------------") + e(tag, Log.getStackTraceString(nullPointer)) + } + i(tag, "----------------------end NullPointerException ----------------------") + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/hohem_icon.png b/app/src/main/res/drawable/hohem_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..23e9d3cb28a772c7a7c988f7a73206c53f83c33c GIT binary patch literal 91054 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 b@6-P<|JkCjnVa81;Lj@eb#C{U`5*m1l4kP1 literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/fragment_camera.xml b/app/src/main/res/layout/fragment_camera.xml index 794f128feb..a46980b9f2 100644 --- a/app/src/main/res/layout/fragment_camera.xml +++ b/app/src/main/res/layout/fragment_camera.xml @@ -21,8 +21,9 @@ android:gravity="center" android:layout_margin="20dp" android:id="@+id/et_rtp_url" + android:text="@string/stream_url_huya" app:layout_constraintTop_toTopOf="parent" - /> + android:imeOptions="actionGo"/> - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_hohem.xml b/app/src/main/res/mipmap-anydpi-v26/ic_hohem.xml new file mode 100644 index 0000000000..680e2a0f3c --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_hohem.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_hohem_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_hohem_round.xml new file mode 100644 index 0000000000..680e2a0f3c --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_hohem_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_hohem.webp b/app/src/main/res/mipmap-hdpi/ic_hohem.webp new file mode 100644 index 0000000000000000000000000000000000000000..d2cf076480eb18edcd833cde4be8b107762a1616 GIT binary patch literal 2828 zcmV+n3-k0+Nk&El3jhFDMM6+kP&iEZ3IG5vN5Byf2?cE%IcomKPyT?vAR_ud0Yg5x z4sJ(807Soti#-P*Lc&Glr~gv&W5G6(BqxIi;FXD3^DJuXAF#+jG-5#7NK!`rKPW;0 zFX_KB6hSI#BuS0c;fq1Q^*`Z9mkEO40}m3WBOnMwA|arw+Yv1gkmz*W`i%FEF9N_A zFa5_J@kTTn#ux)PA^|HGLI|TL0e}Vo$x0T!G7z+F8;3vaZ9j#Gm;jnQRGQMK9~Dg= z**AP54$f)KEWc=f3~R zs7MCh3@dm*0}`SN>WcsYe?}N(6y(g^kRsc5t@J)Wy7?pl&jFU6v_B_kzyu@scf+l1 zTeY(GdH!8#f(o>OKZvXl8lnNn++8wh9q0>8=1xS zGPiBpw)uab(f+v)z_xAMHb=!&eAlXs%FLc4cq3Y3+enfmQQ0{Ez5TB{yxO+y+O{NH zxwNrNYeSX{88WQR{+}5$rWKYe)VN1TNXVaZE z_c27jE)2pC-D>RS2O-qkNMuT+js#I9Ljp>oq$K4Iz$G9$IR#auq$E*$CFp+$QN@yc zCUXPZ1D5eR$g(!d2PzPthCzV{5C!9Cz@fb=)LFoOVIs5^$pJXk_s24X4ZBq%1R-*= zK2l2JqG$~Q3IG`lB7o5LId3E)7^#lw7TJqG)XaAQFf`kq9WttX-Um)+vP-ljdInb55w15Cn`Q%*i0i88cF% zBkf#85O)a{7vW-zCa*@7N;O^+kqCyU7n7J^Kt?)bjH7L~0VGC1pb!y= zQIL$1C8dWZf|0_rq=dZ8ARuA_F*j#3PBbF*9@S899ZnM;xeuBV6=RI@)?1TFtxU#@ zV^I(w;M|`GGxlki47w8{&~@P%`DhJ^H=Iu3hWu!!u?)ky0RtifrWq~D)OE~h`Ki0o4kSjPd;_Cqju2Q{hA z5MWt!)_{627v0ALF%20RsziC_K6C1`nYuU6MirUnLsxgCTV7Px2@16>5*zx5m{%|M z-M9Jasn4qej&zevb!stGrLqL?-Ai_k8zCz0zl2anEBfIup1nuX~<1fA^-Zm+iSr_n7Oc`^*E)!VWN8e8$zI*@E*YC!9%$~b^+{^tppTAqznCI-hTle-E>%eb){LFcd zc!u52`z?2gulvO({rq?Sbc?S)z_$loA9gVZ`)#x9$a!ZV{>KPu7Sm^*8rmZ^l+e>3=`=N9q>e9CX3jMX3-h zfyzWzD_16y6|^K(0@cpYJ*vu72e!_o9vXnam0qi+ykMqO3D6TgM&0GR>2K z9BKiX`>FoVgY4zrS9zXy9(wPOz8V0`@3E3~K>w*9_e}Miy&E^lgkRmWa6sk)$vXoU zfuf>X_;YjAg8!;=$)x-0aeSE{`#<@YE?H^+wRgVW{_k$C=RWtn*QeeWa&4s7epx

N-!wg^jaq5f`Deal|H1?_F8RlM z{OntP0|xM36CfKcwC4Qna^sieSLu?IA)w*n%_weZmfBBOTn%mKViFi^RTYBMBn zEB`6{&jXC2)cX?cNXVSYoGEoy)9f)MkuFY0&+^a&OgrYhOh$|`X~1mrrewg(N!m}6 zBuaAL4tWkwRuT;a8c#9?OXeEqol@`Fm9vTH7^h&L7wg)Bx02=`{jo{hKLpO_yY(>mW9m0jTi* z+NXg-yi5V(lEzlBwL+B|bq+Px7XoC|GKseIUt{o)P@%C=>7ukP8`f+TYisbBhIM#X zdSC#yru4}eJY)|6F(|{Up>&0|wn1A})N0f-)!apGM)IsEoDl~q7WT@tMl{wbGcpD5 z8~}>JgnvluArdvVi6)H9iK0<}02&bpE;6Dd z0hqkJRjOzV5QTzO6fd=6M-`G-m9a@mI0!yB2mnR8khRcCW8XhQz||7AghKX_Z*`27&%Ul~T(!n%5XmFh3^ eJD@M{<=%`Y!gPh74nE<3(9HrxeE%%|orVD6Aa>IL literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_hohem_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_hohem_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..2cd7d1eb1f4f87473caedf91909da2ba89da03af GIT binary patch literal 4038 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_hohem_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_hohem_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..65f7bcdcbfe795df7c11a8348b8f56e44cf3b56f GIT binary patch literal 2772 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_hohem_round.webp b/app/src/main/res/mipmap-mdpi/ic_hohem_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..8bb72a660660ed7953aab65757f68999638c8811 GIT binary patch literal 3124 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 O!=DcRyt)}Dwj2P$ivxK8 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_hohem.webp b/app/src/main/res/mipmap-xhdpi/ic_hohem.webp new file mode 100644 index 0000000000000000000000000000000000000000..b2f8e71ecc3787c1f4db761575094ee7741e921e GIT binary patch literal 3820 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 i0A$R>YH!~~>)BuU4<#4?cq`yQ literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_hohem_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_hohem_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..15f49255f7bb4afe0117ef51f741aff3d83d70c3 GIT binary patch literal 5460 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_hohem_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_hohem_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..92f211d5f96384939f002dd16db78edc56b9371f GIT binary patch literal 8846 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 Q`rnBDH=_RyN41dv09X;^CIA2c literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_hohem_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_hohem_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..b9e4e14997fc869bb78227a02ef232e12345c7e3 GIT binary patch literal 10472 zcmVPgKNk&HCC;$LgMM6+kP&iD~C;$L2kH8}k35RXlND}O5XFUkE|AObaD$YL{!akZ2LJ)I4|?aQ(5XKl21T}%e3?T%75U^pvYpF~S00II4@yreI5#9h`r5ykOaki~nA9@G@ zB3uB13d)0WadOeuT~+hQGeU&C`GQD5fB^vL1@ys)kZ=HDlEgEoOqnvx(yWRzW-M3$ z2qJ_?5@+$7n**mzS>6!hi^8dVw|gOsxxsG$-blrLW^5xM2!HtB1pq+7wHr`_@c@8b3^cFkQ9u9y-w{R#VGIBm--T2F=`sNTK!_fNa6oDR zQ@pkVbmYXgd0E>(Q zCfI80Ha1=4*uPL+Xn;Wr%*@QpeDC*2PrH6S zzI)_%W@cXIH8aQpi=}~v%82RAtm^jo;O)DiP1jOh>!NEF=idKPZJxr~iSsO&7LQVG z+pcY;k9`}|a#VvDBp?A@)Tm(q2RY~X?w)>Z+g5G6wzg7g9}C3HiUEU(OA3HInTmo; zrlkPjImeQB$lB>xIssVTL5rE0&smUY&e1C!TicFp&%W;a`E7Q#ZQC|$OyIB5rpgF1 zfUI;WZQHgwSI+a?mqgjN>bAMswm?V1_wo`_(;u)j{r}>~&G*->Zp|#&3l1}L-ooB< zW@b)Kxv%yd&M{}syoOXUGa1)x&+=+W>TUd0jnpk#-@hr`8zYvyH)h@QD{4XmE zs?gQ8ZEYhx-}g&04>L2*REr%mGnTt2Fh{U0mV63REpIo=Jj{&ATtTC33;zG3TW#C( zwz;17{{fLyaL(CP&I5>}sdw)|Jb`kjRvtk)ONNOb|M${?ZQHbMjXBra2N&o1*|yE- z+x|aP%*;Rgu_N2ao^6}|+P?J;@7a5=HKzl&Z5&DQO)}25>-_@?!GF3pGvXSD!A?I8 z$FAf@x&2LtWzCms9prPokS8C2jiDTs*D4GE z@U5=JPkL$mth2FG_C_1qF@~cuRA%r(hr&EwWiC4?y^Vhgx$XU?!}`C;D8j}?#YK~Fp6pCh{5CH^mz_^eca8JmH!cU4n7<{ey z<*cjk6n+8N;>OCiy4FrUaE@H-p|Zu%V$IDh*`{=@;e5@Rpb0b7VC626yhI&p(EM59 z8^iZCUe|o{UQd0g&|s$@*Y$tkxuYltev`~*8u`Bopalk3xdV=vdW zHvbtf&dIGeG_EPE1U=Hophjqv%?SQn?ZK`Y10u!|1{8?^mE6emVQoC5@B=^W-S|z1 z<$dck^;4eRVQ^dFyyiI6bV98~u~?y(#qS1)03s60Cp7;wd?b?x6<%R9S$8Sk#~uE8 z(BPitAuwzIsL~2h0_t+`($p|Nlm#@OGV*}JQ=m}S)sI~3pYcBKalS3XeVWHLDN<{1 zMtw{v3>alhxs@no1X`8ZQ+v8`!4-AVaLBTIqE>h`$NjwTT7*KZ8@t9qRp`0LW^T*4 zt8t5yW*zkGsGSxFl0zdRVyHY6Kl&*4S*^MR8SYG3=W^m2hjpDCU7Px|yS;48Gr38V zo`YE{)E;0=U?Vi6oFtHfC-PX1`hB`o6wdT+E`O^J0-ocASO@l-zFto7i$33`4wn@s zl8;`Gst{IuUZUebQYMi}uB1s3qK}B*jgw~dW_q(eUio#0+cG-5~so^$6J{6v0JI`Q`U?yc2rF^4f z03t9>d^8tLV;}`1#zlbAa^?(*doIB|?&O}>0kWT~1bD{YdV@7$(69YS2vupKOF>Qy zNh3{?0???hLt#yt&l@=1&s7>2FXAeZ;;d0pLp~YANY-#;9T=i7@-W_LMj}aq%jTIg zKutw^O-&GgGa&9G_#ly47VvL|1w;ghNEHxL#g1sMQl zk(QlpwwD}V#AWRpFXnIM^%HLwW83Ev7MKJfGeiRtEs7glhR`{BI*R^I1sG&O{=oki z3cQkNpQv%FT-YmCsyASVi~$jPCQI}ZVW@@m5?zsi8iFiO|H45b_tDxH0A1imLp66Z z8UZ`HBsugZAROePse}8#K~FbUe&2u3@OML<+D-?f)=CIv+^T(1*257!6LL|@8q<>h z2q-i&_5Sydec$>HXE|>@14I1$TW|i5FCU{cm!U_Jt7$??F;p4w0?F^piW6PuKt<*+ z=={y+Kl|aU-|$NAO0U#7HRMm9PbQ@$$f*NbRP-6J`J17Ll@GretKW=^w!`nN5^KR|#D<{pq z2l$@61wkLqzkfZcKw1?mhNI9`PLM(ldWCpuNgWhzNRwGOYE{^&n!|pt;~Z;;I{zT( zp8W&3UaXqonq2kon*4UU6Xiq0&Y;1HYSTd)d1`&&W!k3>!>9tSj+@%1R8(_I{_aEo z#b#y~4fpqsTz&c%!Krl5uRwOkigO=A)ahs`@!l0U0=Y;aLy2F*;9Y4y+(%(d1vHXT zfyZs94*M;m099K&UJ!KN?E3Vxf3<$ueqcbl<&_q*AmoGHAJ@V4%lG7?f<#R%!{9@t z#*?UVR)R#dDz=B(3Lrq@u&q^7fzT2)2xe3vHY8Ga(^uxo7g|Hzkbt&?gG7IbdEdnMSuYx>zHC$d%z?jN^FQ;{Ca zle(+rtJNDlCE_d|Tq%dn)B!2-x|Czh)xV@jgNxPMPh=1dlV~7z+!zi&mtX_>O##KSvRX79d^`48Wf@X`p zD@Q@Z;a*2@7CMt?pZujh>o<$?&wFXNUeppb0AaSN(!u~r4Q{#B%EjB10zZYlARkKN z|MIoC6-8M{!;Yx#ZWd&Z{)_;2Tkd0K?Z<3W%m_&`sP$|d^KV@+2Iq}Rg}>Hh81Z-F32`82>?94I$vFrp?E(MNN{;utN4OAk6 zrtuC42o=Gx4=ap#Zij<6TLanG9s7KyM7KU&og?=|>jn$%nj)_4-_eO%uMOmvG=R+{;QvU8 z8d<&79>Ru2z(-X|?7=`&coPsCJ%I|3t2L@o)&u0zx_dBcmZFLvCl%D>|?-~&=6E)}v&!y>! z8@6-p;xAWCp69iYtuD$OAjp}Umhv4Q^AlD-YV{+gPa_RQBwzgrqoo^c1bF%xzUT3& z?|Xd7_ddSlYaXwewx^$wg7T%PFd3KL<<#IOKZH&d*d4ifw!Sx1a8v+S8kt9RN$~}d zHZ~|<-`$}%BntpSQ`8oUO1Ka*i?Wt00sJ$ctHGT1yb1HvuOfilIMeSjNcs%C1O*;R z^a+4SpFy{f88nr&E+EN!V~E*kjy5W4sx9E?Su&BxGzxxZXc4C1)C#RYE-FYQU;~&I zZ=4d+hGRo#k?3OwxiPI4!;FXzEe$_*#oMh@S04` z!dtb-O%FbTbIY2dIb~ogEJBBlC~v)JrlUc{)nvdNo%ulWeD!AlNYo@4XoN47(S`JE z!&70NS|qD*m~Iq%p)FkMt#qZurD~q0AU$$uc;<QZpKyTRrmy3^~3&~@O2?cSgwly!L zz6#Vp?Ywdi;*s;gxDme45(ujLvPaL%60hM-rvt zMQhJj@v9G`q% zh>7LXGmex9Y+RZ??STaXB&THgQm=@1sw@MNQK_B)X&1dzz55CUa;irHFhvJd<$SRs zNo}cIs4Q5}Zdo)ZAf3EtQ^ZG`Zc}%uqhb~=q^GFu3ivcNWrEA$S*8pihdbcihfPy( zw&AJ`hG2od;0*JW?1b!^ZkqN@oSU8=E=p@|efrUPX+x<6_{ypFwl5kM4p)>hI~~K{5m!g4CKyH~{aEGId6t$WWyIOP; zaG8HtfW1Zsg;Xb}9{p^qslnk|bKtN<_%(?lPgG_Zp*fNto70E7k?U`z)dEn%ysfaRc# z+Y!@FUoX?GJmy5i*6{Y)1JxI^1XKpYj*a zxM$F+A~L7WDJG1&w13(j>_D3$a*#fa_{fKQ6H~h8TIF~JZ?qj8qS%B0)gY_VfIgGx zR-%_xxJd|H1YZajn5Ka^2OoD<-+AMkr|($tPbXd$Iz@24Y$O1oC$7EK&mNv$banf% zs`$vc;ZqJSOd3enT|Mts!a;nbCSZYh8drtPAocQqdj?Nwf#0+pK%W7awCur$a;EQ< zO0drio_o>t@EFvWs4sypGJPg|w)$fD!lrX!7S4yK)6LAS7S=gmiZjoDh6TnK`1^;MuOfA}1 zyM6BF)vHz;P7Q@MEHPj;XFN9i&7D5$#1%AggFCkW_}BYQckCy?&V&lroJF!$;ME1g zNQRAYHhqQ#VS?dkr-=Z82^QY#AS8O?4)vE-AL~I^xl7An*^>I#t3Q014i3p#xJ#(| zqIjA--Z%W^6CW==RxObIEPu;>=oRfEf)Bn8MKfzvSf~2(3r>$| zlNPJ6|KQI3#F70QXi9Z<7WdG!LQ$J=gTBn8w!0S`a)DeCDgzPexO9i_TW~9G@pDHV z>!Xnd5%(%wK0VsZ_6o^!fOtTzJJ!9a8Bm}`0%7vEelH`co)QA|Ii~(b{S`d)H?RWq zi7;sisZ&SIA82BY+lN1Xen?R?rml@B0fNx8`ltJ4fBO6gTJZx{hy7D4`Dp!DQ+HkL*1Ca@RgjFa0&X;JxU-*W5{ZW5!B+Ybkrbn*)WYbZu{&~;m zJ^VZ?RyGb!F6Fe)B8F*AdtDPy0b-F;SFLS^>`uH!1egl*ry%kAwhOT$N?2n}4FBJ8 z0U|C`5vfzCm_0Y3OF$&LOaRFt+C4`)t`IvmdL{(|x@PY`G$(Z~{v`LLyI$#!zWP*5 zUN5d8z(?c0p@M*9fLJa{BH2j@(5H3?EF4THNElUzN`l3jNE1nMBUIp!q)NOtYj8$D zirz;^W3r>8shw-YOe(bhM)S72K)myri(J&GFCc)dbo(;876B!&U37xS3m&qx_&^P* z?t=|M*wCtowLMeqnmN}&BA!9%yDLN=$wEbG|B0p3u9R~`iq_l`c);KM_5OPP!tXlb zor0zW4d7lUWuGm3gJ%*oh1Z+H2iJ6}r}80{@Z44eaju0b0jUB)L~R{ zZ7CWKOcE@iSAOu{Q2P%*7BwK6NcKDy7;x(iCEf;@q0Eble8`ogRN}!p5k}aExMVtz zcvu^1{_$CzyV&XO>;L+q0Y}K5eRR6=^?VHh@tolj%RaSBpwO`h1(g$|M*KvVG{Pc) z!@WWf7O*_bWgy(O^cX+lQ!zzyn*?xsKKGU081$hSq;pTD0f2$TDofGhSXoh}TBJ|8 zS55*52@q6(Bxn`FK^A{#u8&TuN(_rQe3={m(?|WN^BbRt8Yv@OhOlMyrUCy^Wtt*b zl<4$GPFMtSQKgB&O4==7`Mb{z!+u2J^(-7rGjQX3KV6^7pZ)Lu z|1US?%0?s%LFBO~-%#L~rvf$2!gjS4-XIQ896qy`0EP2mE)j0)0X*0ALt}QF+!4TH(SNdSE1C zlbaAg6~w4IX2lZUIPO1yII>+hrFa(rPLny@U9wwSQ=LeR3Q-+2>XVF0W&W&a7(|9b zEe^FHf(k&%W5>zS?*>;MSCp@pN0BMvpP8@YIEQUg{cdEgWBBfl6$niY3`?whjPWD~ zGr*!o1Oy6UmWaR3d=EsPaexD)#23Six-mZ3S;qW&r3I`?R&3M?5a}skXriJr;((|0 zlsmulObUQ}5F$;2qoo7iO`Y9SmcPB)<7Ib;IA5Z;fJ*3&ie#oJ&XLQu8fbc^vcIq{ z$W+W62i*!}Awtl|Si!%yfOWccp=+g-Z5|tNV@ycA zD9VD=UxN7^<-Jn!Z0jZ|j0DepFZ28*weIeJbDVHMm1E3-w{r~NG!0;31>70KXN#19 zu?lQdD&Ztg1L*$)1bZ4MX~5cdg8-1F52Z+LKKb!%b*s{%LZ#JXC;hfmjN5|8ApmAg-XnW2qF^J}bks{o@U7gVM?;Cq~9-4teM z724qbQpUiTB9T8vqqu8+%w^a?W`MD;0FgZuqbxU(E1j8j9J!@oIT^+<3-+rOm|s(mmnVo0J9uNN@X zCsWfzs-aA&8{?gedNE)$Fn}EutTR9XBlKY6FUw4cKp>mEVTwGXF;`uBr-rDhez?&h z10~{QKn7dkoWhXB#x$VKkU;)jt&EwIFk1nbR^2-j)Y-6&tlLPFM1cx0nS$cBkHlIg zP17{7jL5*^XvBsEnNT9-!gUQV?s~w4*-Y~s0)YDh?P#tpn2e;-8T}|DsRlHhEQ%!( zEpbfs@R~Hy)I<*qEXwIzDTPIn98jA2m2e^%z)rLpyS|0b@e7fCDN$kUuKja5d&~(c z0I4qAdms{HHPJL_CUvYManK1lbZtC3f<8%J6I03_$X;dK&c#VvRE|u0-51%3#@Nn$ zupO<@sevQlithnzj=n%fchA%h@33b$`Q)}V^-CBm({XZrAN9nnv_URdNx*_TJm2{c3evI z>8ayt6;7pGB7cFo{?8_$wvQ*;wjyjFqAXWX-=Lua5&vVArwDkBlojIwMezJ+ZMjhQ zaowo3h;;^;m`JR~RRpxjz-ZJ13HCV|0}%@vyg|SYDFXm9M^)i(5sIq_a~CzqKAJFO z*XA>xmLr>bP{9I)0Y^<;vpKmM9cw?X|Smapf1E|w)!1)tj0*Al+$da41}Cv zjn`P=C02Qbh&{$?4M?ki6Ckx@*M`%$RSC7+10eP_(INvt0ZB5WSqbpSkYvmrYdk}p z`^f0T6t(E#I#ZDC$!xDhe1w^iTmkF^Y&>5gaAp7L4zk#SDydZSw-+vOHCEAy{pF9p1Qe= zf%aea#A8SbJ}7mU2Kkv7l7pZOh4-5{X3^z4>+PtcFG&G{LyC zUm}c22+4eN(1zx$mg|4o+=|^0UsuAIGsX;LZb{RMwOdta-gYtWRWa^?2qO>}oEd}~ zuQRa>R!$8d1j3x`6oNU$wc9Q=CMpXk3fig$673ZrQ%O00F*1vXbFw)xu7vZAbc7&O zBg{?F0f?S+i;@F_9&~LgG|5q82G%C0Yq$QgJz@OCGsWo|1HN3xaB#*HFsa9GOClZ) z1h?gPZcR{aP4k>s#?vq~VkLwgBE^Dy?*3(R3I5DKZ$K!d#qnhtDKtCjJ3j5eO&8N1 zvx?8qFrOm8PX{=VBujuNfT_T^j)9-Bd}Hmqe8<$_TV_in>1i>9|1%6hfR}o&N+2OP z?}QQv+YGQ$bpFJ=Bft22I#~MgKvb>{aKp0XfWrV}CzI48cag+nI-cUxr+>aV>ReZ| zMA?c93y}+dqezgG0)5g23Pc@sLYW}IS}r_R_JM19{Y_&~xtLY_WY#E~%7AOt1BYDK zBsJ_W6vTdL(37j!H0akC<8YGG)kqVaUZ)e z^~-gGaWjrrzqL`cR(3QT0v?dE2Znu*n81VUvD0E7SnU{%&|ZxL$I<}nj1z_2C@ezn z;yDQP@Dd_=fPrzKkN^@+#_<0}mfm{q$bn)1ck5uZ#qlazWkM`#h)f8?7l$RFJY~Yt zrqD``-2}xpH0UOKd}_Hm->f2#&Mb8r5;zfZqmV!$5voNux^6}+?W{;tK#*{n2VuX_ z>?$>;)|*F5`+q)DyH7HTalCpzX+^?wNa)(d4o8{H) zxXxuELF!YZB`X?oU04)5N};?!0D(neu(8ppsU|q7HFZ*%8Zi=sI1l1E=zQ|VFBDTGYf^wV+ff(@9 z+{aw&(MnVP1qe1beUWqd z?}~yDh%a7jJ+=Tig~J%Y0Cu#wIh&y(;YR`w91}!o;IScVTlaZ|om*6?`P!OvYh|>8 zDLF~g)d!t~C#Mc&k$6B#8e?J>^&8-^Ma*6z#K=U$ArN1j4oh0YQ(CP@!P#iGJ{zIJ z`o*dQ>2SF2lqFC7uxuHV z(P%Onjn)sqT2%1F2h-9pDoBZp7*m$zbUH29XY0}ajD5#2thKFPi?+zU4r!y!H>SwB e!?f7y+oE?Urrn@1EbiUG79}lNV3UR%@Uj4l1j9K1 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_hohem.webp b/app/src/main/res/mipmap-xxxhdpi/ic_hohem.webp new file mode 100644 index 0000000000000000000000000000000000000000..41e84e9afe3aa6c22690503a390d8faf86d9a3f3 GIT binary patch literal 7860 zcmWNVc|6k(7{|YxjTy3GZZ&h{&V80<<_IyjpCeZ!SB^p`bI;6uR%jME6P2Tg(a_;Y zh;knx$B-Q9`t|(v`Q!QT^Lo9X=ku|-U~F7}5&#^Ha0CZ}wgvT{KGQ`%gJ4e$nlk0t z+!Z_f`d}`kGe(%S_7^hGJ$yRfZ|Z_!#F+<=oZQ;x{HETsy#2;Xlyq_xoswho{Aw&| z&J=tP0^yRVnO+&Nk47>{FjfSovC00bS%sVyK+P#UfaL{PCE4&Wf8blLm<#l> z#ViB_PTiNtPT#$VmqahSSrRSE3B_xYJO=aBco)?l1%NmOp(vh!-sz{GN9Kf>-KI;d zlt=-MP>Ur3)tEnmp)&1q{Vl%H$|nV*b0q=fOWz1 zcan&{i@aLzg<@H%u!?dZK)Jy1K~Ey6jwW`@=GeT&I`{ccO^)Q$$L(vm)8{Z}wR7)1 zXH)w(Sx-ZuOF||~Z}deRiLLYjZ$6DtKZS=%4QB8ISkdLbS4@4S-&DMQrE;6*I@RLu z@-=+!bX&|wsi3~jdl_4rr38yP5k`77Q@O(2*?)QXdoE_UthhBuPyfP)o9E}Y1#*OD zd?cnc9S`CN?Jdo%EzQh=n1O${|s$h5Rc6h*@+jzQyvmr;i>!pyU=DC9V-I2h}H!QCXkKXCL23Z>tY3~!DPjB=`xrpR_<=lmCj&dCN*iep8UJ#BtG& zL(YYFhZ8$oSSUhFBFRce!r{NI^dGvbqjPkvbd;p>CQ!0@b zCKeUJy5s*%pC(+b-tc>=i1~73B_>V?AUVDG8nfj)>k=38^}|=yFb?2FZndNk2MEDt zFf2a6z(4-^Kw$gj{J-V@xb=Ug@dt28BfkELt^?Pa9%5s z^sG~8_vh;H{S0lfMh%)U9fl=+!(11~X7J`@lpLqly)-ADk7I@#ik>@SrV}j=!YaW7 z)xgLtWDX`?kQIysY6u_=IlGLHejZUD#FK7f9%K~B+*)1mVUk2B-vR-O=uf@#XvS!&0IOX=)G37VF zXQU<7;^68%$mQDrBD|;UiRZ&P?BVhX)W!Q-%+QbV(gSGE;klX3Q=E$h-rGSc*S{_& zvWNXu_}kCcmDTrDp%+7$l`&0jV9>M0 zFcXByL>C6bcT%7qYT6ESd9iF{kqrWSr~`*wW(LkOR6c*wy3pse8MExuY#8!6#Fqj| z6gGgl#du&D^It8z716+PyN>O8$oPr@J|@yExd)Sse584x=chOU8hzVG^*Q zF{5X_9?%+KO`_O?%VpBGsIji4Q5^WNF@*l52jwQcPmNa-6K%ljbSF7!tT+j{O!hWR zcI&FL;qf4dfsyRTClzEQD}+80RCDL9r(8s%l7k%?#Fg$w7rmhl`)9U8Q{Px2(TuUy9wY(Dxn@s2Qgka7W!$`(GCP^+kgc4KLp?B3O4i_ zuH6`PHKznOk)*XphAzuaYg83WJO#fl$05^h*-j{X2|C`}>VU21dyq8*0on^*mBs|k zG&{+)C+k9}%gq+3q|uEN$3AG9q0BrUzbEHBEt3Y=Auwt-Ea3SN((T)*b1_z166^(J zPWz^GB8_kB#8~8Q3^9jQh<3m0&oruW?GEl)m^P}yi-tD<2`pDGijq4I0~f3roWy2j z9E8qcC_D6ih|BK2q*pD~P6|v24HX=I1Rqy&BcJS26>{pK3q>gnyjv6u8vU(eK*kAz zc5tEO>GE0b@d{atLgDkg!L4^w*07cC%nuX>UE z{?wMpRxh|l$O7KV8a5h?Ql{`KnDFW_`?Fb~I(%`U5jd{wCK_NU2+q(z9c3WJRN?L$ z`|{%ft-D0TD<{rfZWRF?;b2bR6Y8~uRj?o{dEE_-0V(6*Bj43Mn7V@kU01-hI(&`1 z<{>m8B#lB8p3ZZ^X&e`8`;2BV{K6bb(CF!UKL(79)X@ji?~C(-AgkN=tTxSiie!k0 z@IYZi#)0_V=(*SG&y{h>fFNc`q!A|Gc6ne#dxrziq|wgf0R+|x3nF095X2*${w5H_ zso&n>eQY&9OH>>ij{%Ctw>v_RbDqqXlfeSwI8{pWo0O)4%kK(&)h9XjIc+RQjgxOBIT<*@-nMS6p27N;_-FJE-KyP~xX0k=R#?yQ}&CJSty1Gl)|!qKJm4 z?;(?GdnDjAXp&rpXr;epg&CI&jDt5jj)4(hn{f){rB^Abk#w zsJF78+?%K-C|1miLmfB~oZbO|0W&UZq8t(~12dwec!`(qHJgaoF{E5`37dhV^ayB3 zeYq7SvwTV#_1vJA0Fa9h9|akfOx|rzMkDLq)`mZ5H@qkq3Hs?XJH}nB+&&1;9m=jd?d^32ogd3pO+IyL%LPjJ9**+(5zo6yA~1yh z%g)6M*w&gs$DWz?R#?Tm<0i&dQjcT0_=d9Or!M+pO#RrR$2AcnQ)Oky8hy`b&DT zJU@`uPA}iI)jmuCQI9lt_t#U5k@Zry@XYU^! zkyx-d15>Y^?2y$Cy^!O1vBRjtJx~M;z^N)cE|S}1o$1ryn&LG1#Zrd?v0#^2p$I$h zYKYKERMxR-Q?NSpK_hajQ!nZ1Y*;GwwERJR2bI*cpt%vuUt;0VVR!J&W^0Hn&6GOn zRNf6*Oqux6(CrKRbfIaym<2cDtDxzX`8>g?Khi@QXRPYY9Z*~41<#6jm0|7gWc`kM zy=T(z+g{?*lxBEQ;kYhbZ>V}CWL*B%wJr}O;Z4czltKyHgb&I`#y7aSRUETP(|%F! z$JolKmZgzsOBLIDUiN}O1-acg(`>?8cV$v1d_;s9p1^m{YtWSN)p>Sps;{W>ASt$N ziMoW9oRkykzFIXmK3uF#Xj=IfaH+B^6==4?R>!>it;xqx@2#p6ifc-J=2|l+EdaiynVVg$U&MadUhU=h!W+Kp{0dx5 zZWWv(D6xa+BA1rV&2r&bc5ZJ+_P!ej#l2jL^X>Rx4McfRvNGFBLso)k?{l+r+NAa^ zMGgAP;)QmF(p`G4T1AfcQy%wB?W9DRbu@&5c9F3|i+~*PVNs}Sdm8$CQStBk&$0t6 z2e(u(F+pT8M$rM3ZRIkpLDZ_7dscgyVrVsWs2yMeBH4sSK5Z7c?b|%b4yaY#+HXsB zidq^H&^zhOuS5L2l5*7jO&XN#KFfVq8>oN&rv0a->FoUy^Js$(RSKykYw<60D{B|_KZIp5673v}F_m>f!P)4#NF zbk8T0)f$mGcy9OUQwJ#B?aJm&aW2P>>B1>{=jyJ>;1ZZMBKU8kHx_Wckyv+=i$210 z%BD3r(o@FEkOaIw%~&$)_3O34f|i3U#tLig+SWbY?X{QLSZW3Q#nP;_`P+i;R6l-J z_2EUacY@p4!Y@&?qw}m$4IQpP*diTu=F8>rb+x@i1y>*{JR&lBHgC`KhIWGw){O%r z_hs=Jq*g=ClpLz@m>Tl^ZaA}zao=Nb>+IGAvYB2A3`z}YuXj<3`kZI_aFHx>6G=LL zSeq8JegZ7;jdgBWooJyfz&lpD(G)sNSomz6=n#9A6)T*+KizjHTj$2C2$QAyo1pvV zQptiFrOEA!=JeZ_nlUp*wQJ#P(>?jN-(~#ZpVO zI8Y!*_W(C+^gj;7btm9%(elOxQw!tYC!>KGsM zIXveD?!aI~{-K+~s4JQO$rDRH697B>tlGaUDTE+<)_5ixRkBmSqWCmWbrS|s05|DK zzZCiLSxPg6Y}0JJKytq6j^rMeP7;>zNQR>?<00?m2P4p;p|V5xN*zteJT(Qx*H~c0 zPW(7zeV_G<&H>D#`&7gTQ)j+ZAHM};Kw@zkgFkxNEThJ+MZRLLyH2xU$w#UM^!(A_|TiHwOPy&QH^{B%k zQy0i}FNuwMl4MAZ^qEuVAP&Uzs{J;p?a2F(U~OIHEVY2=7UXDMO!DV(N49bjEp~$( z+$9`5(H>qMK0E1pyS(zZucOYlk{M+th3?<){Lq8I3y)(Osw2Kb=lhK0&y%&bkmEre`^`Oc8e;wBdGeD*wTxSVAoO#K4q>JbaPRr-eu>HrIQHA5 z(fy!m9tpd_k?!fTE>3ajr?V|`>_bl0K-c%9Uyl3iCnPY9Hi!`5b*BuTS}L+2Bmi+} zu!8}(Es@i(YKC|CSyjiM(4EzDd<4YF+s4V*UArZ@&ZnXFu-KXPJbm52pWodoma|3> zo4^S&_$24-!|<|`)=@`Ze!4$507zq>{Ca4*7Q+T>0Pt6>--s>WXK04OfVf$fw;;K* zkq#gg)OJgBWPe!`4G)mR9cR9(l`-R@eg8aGO9hbrSSfKXOR;kh{RcGW&Nu>qqXzT& zM?4dzlzxi*HQ57K>PG^bMIh+cseX7R5lnThiafh@{Y^Ie@UN=pk&}E68-vH7nq6T*<1aJ zJyy>4C28&8>RUU3eIq<_{wME|YR{z+cmDB1;Abj~0BDCl0mSkD3QjEHKM9DkblP>w zi|y5#a672Xqf7hh#Mn1T4)Aq!mfJyfoD|Tx=6NQH|NZHqG6Di_ka$`Kh7y;ciS{LK zIq}mke3Z5y2WC^_g$&qWSO_{K0%gJ05NXdje;ekbqI+Yn>l7S~{dKl?$K)Bz?e>Cc zyq_%%j#}2v<*~qxd}$C@r$j@e4S^`EQRCfLDSq~09+z@CsW(LV_~Avy?$!dRlZXT) z0k8HbElS>AqowW%-OpXfo`3}oQhJQ|AoE}&B2vbvJ{)ZSNUM;&$lidt6l;Yex;a!lVo7l#BeCuE0;ySA!ia;JI5d7_T>3f)c5S&iw~8_Cz~83 z@eb0xBqh@8F}P|Kz&FCh0iA#UYx<^S`J%6=R1e_sU6OY|*mf)!8ibHc`|l18Xq5Md zqFzkwk-B`*j08UHDP>fhm^=oFAPfp>Sli|p05Bjmxa)A;_`77iD$srA+L+i!-$2X4 zG*PKQ3=x)>6|;eT`y|Wm(Xs?Cy#3CMwN}U0>FGz|L`xb`fh0$#cLM>k4#+ zHo|wT#}oAb%v95{BZjkwn;JIOLpf+7qV&wWvkJ#A*8TmNWPF>nottRA81bZoEKwX# zxk10>(3yGTA5!Fu7P95KK!zIIj}kT)mKYdb=JyZMU>5WGg{t$Y+UtyKbbn~$oewkt z>9^F6@Rm@|*}_Ik;LknGar|dpz8v_KgfIaCBL@Y0>NUjzEwArZ3DG_w|GDuM$k@?w zSD`Y_qSdE4A;8T~W(=sc5*%55Gq~TJ8w7&TgDMW(IBQ;eA{}0>hVCgEt9hUT8kR)T zVxS0je)H;`0zJ=iK_32(rj3>8Uk-$8r`F;Q%84-J&1?>3)jM|^OLj73=lDyzpgi^xTK>3@ z?A^RmQs@%n(_mBJtpFV(#)Bb*S};n95&v3jao$v!4fWi~S<%Q}kb{L(jv>^&KZf<= z?9;I_#~1TT#BG6cGZ4whi;||yZ%h)TqU+(~QE9i9Y4*z0V`F9B;KmXYi9V-!QJs1l z6@{87a?0=Xz1$T6C__=GE<;eox*Pf84*_lL<>QfL?L|S{c2{~EFybje!~8PpuRwi2 zt%}0f7#a{0CA%kGT|oaVg?eH?$f!WhiNAe>6IU1$oYA>NHsd6sUdbEhvSK-=B@77p zbQu;2zkC>Iswb20wZR9L1UMK@wStfIR>A`ywNs>kB)(TzJcbhw!!ryC&uwxZ0Oevl%AaBsDNG?Lza?ULDhVUZg$XAbVBgkh zA_xEXtXqQ#ZOTeb{gYCeAzt}lI7ZqsIDVIN6=PCUPe4wh{GVY2R<(QC@jp6I=AvZ7 zWjLJh%otgksWiN`sds(CzOGo@K%_(>EhK!4f(0-XaaN=EH@_RA00?$M&o0w&x@Tju zpNE{;L{K3GdN|h#Ig~d-?a+=x^!+XiFzUn_w;&Mn~&6}aX zfQiUwilWYU6K0WklTdL4h+zOxj+)26uAJ9Tz{nM%->h9gzI?T?^Fo$xCLqm4|Gg&F zSf2kWVvAlnvue*-S%s>8FjwUM?ijl8C&{uvY zNskMQ=)Hxj&6X*YQCeNKio5kqs@f&){HOt5nRM-YY5x>)F?QB?1cdEX*86>Fn04%j zkEPO2SpDpcjh1s;pLNIvFU3LAzxB^dN-0;^9NrHiB`m)&Vv0t`_wMH+A@H`t4GXIN zFWj8A-Y2asX-R3sp-B1K>+8`uuMD?;t!`|LQY~+Hodr?7h)c)6#MxII&qp49g9RDe z8MX~8s9txYM4nh2Ukc}Ta_jWI+FEkg07U8D-K}?QSXFx*74+96OUaUY5hMc`eV=^O z7bXyH-4S{=`2Nl$%l2m3f6t>=GvqhqhQMI;Mkk-8#SWyxBj$_H3D+lKF_L;8-=ft( zq%~EH4PHb7v&cz3{wohiyE-C~bXc;UPu5_YIFES0QBe39j;otD~n@)w-0HpSEvw9ycX^f$nr|o{V%m z<wc`tpYJ3Zg=mh-PRtKcAGJy+ zvTm)bY3e%k1YSHGczmX0SPSzv@oi+}tsvCt_CGfgRKS!1L3T}|)Pcg&0Lmv{ksna# z;%(($M6Z@EcI}84qo|L6*#7EulUlPO91iZbn?g_Bxb#$~Q0Gp21OT#ucAZ+`!4pj_ ztZGh)<7*6wT6g8lB^Yb=xYoXYBdgk)2RMKy-&@m{=R1;*J^L_qqbO9=Ac4$8T_-F# zL0^81%4v;0-J~mAt?$v>>mDy0?=RHfRFs`qJImyq(mojXOUZxnUh(f=-6-V$266k{ literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_hohem_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_hohem_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..438fb00626449ce79d67ea75091df087b9b47d2c GIT binary patch literal 10076 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 GZ#MvB+gon{ literal 0 HcmV?d00001 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index fb67225e38..ecf0be7679 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,6 +1,7 @@ - #e74c3c + + #F48442 #000000 #ffffff diff --git a/app/src/main/res/values/ic_hohem_background.xml b/app/src/main/res/values/ic_hohem_background.xml new file mode 100644 index 0000000000..12160afe7b --- /dev/null +++ b/app/src/main/res/values/ic_hohem_background.xml @@ -0,0 +1,4 @@ + + + #EE8344 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f1e2e330e0..1357839f1b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,13 +1,17 @@ - RootEncoder streamer + Hohem Live Version: %1$s Old API (16 – 20) From file Screen - Rotation\n(include filters) + + Camera Live Select your streamer protocol://yourendpoint + rtmp://tx.direct.huya.com/huyalive/1445553152-1445553152-7010649780141597433-2035368522-10057-A-1632294239-1?seq=1735020136589&type=simple + null + rtmps://a.rtmps.youtube.com/live2/xpjt-yrb3-dtft-asvs-1erd Grey scale Negative @@ -69,5 +73,9 @@ CameraX CameraUVC Bitmap + Platform + Huya + Tiktok + YouTube diff --git a/encoder/src/main/java/com/pedro/encoder/input/gl/render/ScreenRender.java b/encoder/src/main/java/com/pedro/encoder/input/gl/render/ScreenRender.java index e5818601a4..a8c777c5d3 100644 --- a/encoder/src/main/java/com/pedro/encoder/input/gl/render/ScreenRender.java +++ b/encoder/src/main/java/com/pedro/encoder/input/gl/render/ScreenRender.java @@ -24,6 +24,7 @@ import androidx.annotation.RequiresApi; import com.pedro.encoder.R; +import com.pedro.encoder.utils.Logger; import com.pedro.encoder.utils.ViewPort; import com.pedro.encoder.utils.gl.AspectRatioMode; import com.pedro.encoder.utils.gl.GlUtil; @@ -41,6 +42,7 @@ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) public class ScreenRender { + public static final String TAG = "ScreenRender"; //rotation matrix private final float[] squareVertexData = { @@ -116,21 +118,24 @@ public void drawEncoder(int width, int height, boolean isPortrait, int rotation, public void drawPreview(int width, int height, boolean isPortrait, AspectRatioMode mode, int rotation, boolean flipStreamVertical, boolean flipStreamHorizontal) { GlUtil.checkGlError("drawScreen start"); - + Logger.d(TAG, "drawPreview: width = " + width + "; height = " + height + "; isPortrait = " + isPortrait + "; mode = " + mode + "; rotation = " + rotation + "; flipStreamVertical = " + flipStreamVertical + "; flipStreamHorizontal = " + flipStreamHorizontal); updateMatrix(rotation, SizeCalculator.calculateFlip(flipStreamHorizontal, flipStreamVertical), MVPMatrix); float factor = (float) streamWidth / (float) streamHeight; int w; int h; + Logger.d(TAG, "drawPreview: streamWidth = " + streamWidth + "; streamHeight = " + streamHeight + "; factor = " + factor + ";"); if (factor >= 1f) { + Logger.d(TAG, "drawPreview: go here"); w = isPortrait ? streamHeight : streamWidth; h = isPortrait ? streamWidth : streamHeight; } else { w = isPortrait ? streamWidth : streamHeight; h = isPortrait ? streamHeight : streamWidth; } - ViewPort viewport = SizeCalculator.calculateViewPort(mode, width, height, w, h); +// ViewPort viewport = SizeCalculator.calculateViewPort(mode, width, height, w, h); + ViewPort viewport = SizeCalculator.calculateViewPort(mode, width, height, streamWidth, streamHeight); GLES20.glViewport(viewport.getX(), viewport.getY(), viewport.getWidth(), viewport.getHeight()); - + Logger.d(TAG, "drawPreview: x = " + viewport.getX() + "; y = " + viewport.getY() + "; w = " + viewport.getWidth() + "; h = " + viewport.getHeight() + ";"); draw(); } diff --git a/encoder/src/main/java/com/pedro/encoder/input/sources/video/Camera2Source.kt b/encoder/src/main/java/com/pedro/encoder/input/sources/video/Camera2Source.kt index ce741b52f3..078a2c6822 100644 --- a/encoder/src/main/java/com/pedro/encoder/input/sources/video/Camera2Source.kt +++ b/encoder/src/main/java/com/pedro/encoder/input/sources/video/Camera2Source.kt @@ -29,12 +29,16 @@ import com.pedro.encoder.input.video.Camera2ApiManager import com.pedro.encoder.input.video.Camera2ApiManager.ImageCallback import com.pedro.encoder.input.video.CameraHelper import com.pedro.encoder.input.video.facedetector.FaceDetectorCallback +import com.pedro.encoder.utils.Logger /** * Created by pedro on 11/1/24. */ @RequiresApi(Build.VERSION_CODES.LOLLIPOP) class Camera2Source(context: Context): VideoSource() { + companion object { + private const val TAG = "Camera2Source" + } private val camera = Camera2ApiManager(context) private var facing = CameraHelper.Facing.BACK @@ -50,6 +54,7 @@ class Camera2Source(context: Context): VideoSource() { override fun start(surfaceTexture: SurfaceTexture) { this.surfaceTexture = surfaceTexture if (!isRunning()) { + Logger.d(TAG, "start: width = $width, height = $height, fps = $fps, facing = $facing") surfaceTexture.setDefaultBufferSize(width, height) camera.prepareCamera(surfaceTexture, width, height, fps, facing) camera.openCameraFacing(facing) @@ -72,6 +77,8 @@ class Camera2Source(context: Context): VideoSource() { val resolutions = if (facing == CameraHelper.Facing.BACK) { camera.cameraResolutionsBack } else camera.cameraResolutionsFront + Logger.d(TAG, "checkResolutionSupported: size = $size, resolutions = ${resolutions.contentToString()}") + Logger.d(TAG, "checkResolutionSupported: camera.levelSupported = ${camera.levelSupported}") return if (camera.levelSupported == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { //this is a wrapper of camera1 api. Only listed resolutions are supported resolutions.contains(size) diff --git a/encoder/src/main/java/com/pedro/encoder/utils/Logger.kt b/encoder/src/main/java/com/pedro/encoder/utils/Logger.kt new file mode 100644 index 0000000000..2fb9a75ce7 --- /dev/null +++ b/encoder/src/main/java/com/pedro/encoder/utils/Logger.kt @@ -0,0 +1,72 @@ +package com.pedro.encoder.utils + +import android.util.Log + + +object Logger { + private const val TAG = "HUANG" + private const val ERROR = "ANDROID_ERROR:" + private const val WARN = "ANDROID_WARN:" + private const val RUNTIME_EXCEPTION = "ANDROID_RUNTIME_EXCEPTION" +// private val DEBUG = BuildConfig.DEBUG + private val DEBUG = true + + @JvmStatic + fun v(subTag: String, message: String){ + if (DEBUG) { + Log.v(TAG, "[$subTag][Thread: ${Thread.currentThread().name}] $message") + } + } + @JvmStatic + fun d(subTag: String, message: String){ + if (DEBUG) { + Log.d(TAG, "[$subTag][Thread: ${Thread.currentThread().name}] $message") + } + } + @JvmStatic + fun i(subTag: String, message: String){ + if (DEBUG) { + Log.i(TAG, "[$subTag][Thread: ${Thread.currentThread().name}] $message") + } + } + @JvmStatic + fun w(subTag: String, message: String){ + if (DEBUG) { + Log.w(TAG, "[$subTag][Thread: ${Thread.currentThread().name}] $WARN $message") + } + } + @JvmStatic + fun e(subTag: String, message: String){ + if (DEBUG) { + Log.e(TAG, "[$subTag][Thread: ${Thread.currentThread().name}] $ERROR $message") + } + } + + @JvmStatic + fun throwRuntimeException(){ + RuntimeException(TAG).printStackTrace() + } + + @JvmStatic + fun printStackTrace(tag: String){ + //打印堆栈而不退出 + d(tag, Log.getStackTraceString(Throwable())) + + Exception("debug log").printStackTrace() + + for (i in Thread.currentThread().stackTrace){ + i(tag, i.toString()) + } + val runtimeException = RuntimeException() + runtimeException.fillInStackTrace() + + try { + i(tag, "----------------------throw NullPointerException----------------------") + throw NullPointerException() + } catch (nullPointer: NullPointerException) { + i(tag, "----------------------catch NullPointerException----------------------") + e(tag, Log.getStackTraceString(nullPointer)) + } + i(tag, "----------------------end NullPointerException ----------------------") + } +} \ No newline at end of file diff --git a/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java b/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java index 2a13bf2183..306b799df7 100644 --- a/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java +++ b/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java @@ -16,6 +16,7 @@ package com.pedro.encoder.utils.gl; +import com.pedro.encoder.utils.Logger; import com.pedro.encoder.utils.ViewPort; import kotlin.Pair; @@ -26,11 +27,14 @@ public class SizeCalculator { + public static final String TAG = "SizeCalculator"; + public static ViewPort calculateViewPort(AspectRatioMode mode, int previewWidth, int previewHeight, int streamWidth, int streamHeight) { if (mode == AspectRatioMode.NONE) { return new ViewPort(0, 0, previewWidth, previewHeight); } + Logger.d(TAG, "calculateViewPort: mode = " + mode + "; previewWidth = " + previewWidth + "; previewHeight = " + previewHeight + "; streamWidth = " + streamWidth + "; streamHeight = " + streamHeight + ";"); float streamAspectRatio = (float) streamWidth / (float) streamHeight; float previewAspectRatio = (float) previewWidth / (float) previewHeight; int xo = 0; @@ -38,13 +42,24 @@ public static ViewPort calculateViewPort(AspectRatioMode mode, int previewWidth, int xf = previewWidth; int yf = previewHeight; if (mode == AspectRatioMode.Adjust) { - if (streamAspectRatio > previewAspectRatio) { - yf = streamHeight * previewWidth / streamWidth; - yo = (yf - previewHeight) / -2; - } else { - xf = streamWidth * previewHeight / streamHeight; - xo = (xf - previewWidth) / -2; + Logger.d(TAG, "calculateViewPort: streamAspectRatio = " + streamAspectRatio + "; previewAspectRatio = " + previewAspectRatio + ";"); + //640x480是宽x高,在屏幕显示的时候640是竖直方向,480是水平方向 + float wr = (float) streamHeight / previewWidth;//水平方向的占比 + float hr = (float) streamWidth / previewHeight;//竖直方向的占比 + if(wr > hr) { + xo = 0; + yo = (int) ((previewHeight - streamWidth / wr) / 2); + xf = previewWidth; + yf = (int) (streamWidth / wr); } + +// if (streamAspectRatio > previewAspectRatio) { +// yf = streamHeight * previewWidth / streamWidth; +// yo = (yf - previewHeight) / -2; +// } else { +// xf = streamWidth * previewHeight / streamHeight; +// xo = (xf - previewWidth) / -2; +// } } else { //AspectRatioMode.Fill if (streamAspectRatio > previewAspectRatio) { xf = streamWidth * previewHeight / streamHeight; @@ -54,6 +69,7 @@ public static ViewPort calculateViewPort(AspectRatioMode mode, int previewWidth, yo = (yf - previewHeight) / -2; } } + Logger.d(TAG, "calculateViewPort: xo = " + xo + "; yo = " + yo + "; xf = " + xf + "; yf = " + yf + ";"); return new ViewPort(xo, yo, xf, yf); } diff --git a/library/src/main/java/com/pedro/library/base/StreamBase.kt b/library/src/main/java/com/pedro/library/base/StreamBase.kt index dbb0cd8cca..c0f8a24cc1 100644 --- a/library/src/main/java/com/pedro/library/base/StreamBase.kt +++ b/library/src/main/java/com/pedro/library/base/StreamBase.kt @@ -40,6 +40,7 @@ import com.pedro.encoder.input.sources.audio.AudioSource import com.pedro.encoder.input.sources.video.NoVideoSource import com.pedro.encoder.input.sources.video.VideoSource import com.pedro.encoder.utils.CodecUtil +import com.pedro.encoder.utils.Logger import com.pedro.encoder.video.FormatVideoEncoder import com.pedro.encoder.video.GetVideoData import com.pedro.encoder.video.VideoEncoder @@ -67,6 +68,9 @@ abstract class StreamBase( vSource: VideoSource, aSource: AudioSource ) { + companion object { + private const val TAG = "StreamBase" + } private val getMicrophoneData = object: GetMicrophoneData { override fun inputPCMData(frame: Frame) { @@ -122,6 +126,7 @@ abstract class StreamBase( } differentRecordResolution = true } + Logger.d(TAG, "prepareVideo: differentRecordResolution: $differentRecordResolution, width = $width, height = $height, bitrate = $bitrate, fps = $fps, iFrameInterval = $iFrameInterval, recordWidth = $recordWidth, recordHeight = $recordHeight, recordBitrate = $recordBitrate") val videoResult = videoSource.init(max(width, recordWidth), max(height, recordHeight), fps, rotation) if (videoResult) { if (differentRecordResolution) { @@ -132,6 +137,7 @@ abstract class StreamBase( if (rotation == 90 || rotation == 270) glInterface.setEncoderSize(height, width) else glInterface.setEncoderSize(width, height) val isPortrait = rotation == 90 || rotation == 270 + Logger.d(TAG, "prepareVideo: isPortrait = $isPortrait, rotation = $rotation, width = $width, height = $height, recordWidth = $recordWidth, recordHeight = $recordHeight") glInterface.setIsPortrait(isPortrait) glInterface.setCameraOrientation(if (rotation == 0) 270 else rotation - 90) glInterface.forceOrientation(videoSource.getOrientationConfig()) diff --git a/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt b/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt index d3f241a38e..1e6d5a3c65 100644 --- a/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt +++ b/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt @@ -33,6 +33,7 @@ import com.pedro.encoder.input.gl.render.filters.NoFilterRender import com.pedro.encoder.input.sources.OrientationForced import com.pedro.encoder.input.video.CameraHelper import com.pedro.encoder.input.video.FpsLimiter +import com.pedro.encoder.utils.Logger import com.pedro.encoder.utils.gl.AspectRatioMode import com.pedro.encoder.utils.gl.GlUtil import com.pedro.library.util.Filter @@ -48,6 +49,9 @@ import java.util.concurrent.atomic.AtomicBoolean */ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) class GlStreamInterface(private val context: Context): OnFrameAvailableListener, GlInterface { + companion object { + private const val TAG = "GlStreamInterface" + } private var takePhotoCallback: TakePhotoCallback? = null private val running = AtomicBoolean(false) @@ -199,6 +203,7 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, } private fun draw(forced: Boolean) { + Logger.d(TAG, "draw: forced = $forced") if (!isRunning) return val limitFps = fpsLimiter.limitFPS() if (!forced) forceRender.frameAvailable() @@ -227,6 +232,7 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, } // render VideoEncoder (stream and record) if (surfaceManagerEncoder.isReady && mainRender.isReady() && !limitFps) { + Logger.d(TAG, "draw: 1 surfaceManagerEncoder.isReady = ${surfaceManagerEncoder.isReady}, mainRender.isReady() = ${mainRender.isReady()}, limitFps = ${limitFps}") val w = if (muteVideo) 0 else encoderWidth val h = if (muteVideo) 0 else encoderHeight surfaceManagerEncoder.makeCurrent() @@ -236,6 +242,7 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, } // render VideoEncoder (record if the resolution is different than stream) if (surfaceManagerEncoderRecord.isReady && mainRender.isReady() && !limitFps) { + Logger.d(TAG, "draw: 2 surfaceManagerEncoderRecord.isReady = ${surfaceManagerEncoderRecord.isReady}, mainRender.isReady() = ${mainRender.isReady()}, limitFps = ${limitFps}") val w = if (muteVideo) 0 else encoderRecordWidth val h = if (muteVideo) 0 else encoderRecordHeight surfaceManagerEncoderRecord.makeCurrent() @@ -245,6 +252,7 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, } //render surface photo if request photo if (takePhotoCallback != null && surfaceManagerPhoto.isReady && mainRender.isReady()) { + Logger.d(TAG, "draw: 3 takePhotoCallback = ${takePhotoCallback}, surfaceManagerPhoto.isReady = ${surfaceManagerPhoto.isReady}, mainRender.isReady() = ${mainRender.isReady()}") surfaceManagerPhoto.makeCurrent() mainRender.drawScreen(encoderWidth, encoderHeight, AspectRatioMode.NONE, streamOrientation, isStreamVerticalFlip, isStreamHorizontalFlip) @@ -254,6 +262,7 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, } // render preview if (surfaceManagerPreview.isReady && mainRender.isReady() && !limitFps) { + Logger.d(TAG, "draw: 4 surfaceManagerPreview.isReady = ${surfaceManagerPreview.isReady}, mainRender.isReady() = ${mainRender.isReady()}, limitFps = ${limitFps}") val w = if (previewWidth == 0) encoderWidth else previewWidth val h = if (previewHeight == 0) encoderHeight else previewHeight surfaceManagerPreview.makeCurrent() @@ -264,6 +273,7 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, } override fun onFrameAvailable(surfaceTexture: SurfaceTexture?) { + Logger.d(TAG, "onFrameAvailable: isRunning = $isRunning") if (!isRunning) return executor?.execute { draw(false) } } From 653788c5843ab91e09e4c8f52e6d870577dcda3b Mon Sep 17 00:00:00 2001 From: "haliry.huang" Date: Wed, 25 Dec 2024 10:36:12 +0800 Subject: [PATCH 02/13] [feat] Add small screen preview config --- app/build.gradle.kts | 2 +- .../pedro/streamer/rotation/CameraFragment.kt | 8 +++--- app/src/main/res/values/strings.xml | 2 +- .../encoder/utils/gl/SizeCalculator.java | 25 +++++++++++++++---- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9fb2f0f2e0..a01f1bc84b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,7 +9,7 @@ android { defaultConfig { applicationId = "com.pedro.streamer" - minSdk = 16 + minSdk = 21 targetSdk = 35 versionCode = libs.versions.versionCode.get().toInt() versionName = libs.versions.versionName.get() diff --git a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt index 1b7a259df2..08ad3543cc 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt @@ -86,10 +86,10 @@ class CameraFragment: Fragment(), ConnectChecker { private lateinit var bStartStop: ImageView private lateinit var txtBitrate: TextView lateinit var streamUrl: EditText -// private val width = 640 -// private val height = 480 - private val width = 1440 - private val height = 1080 + private val width = 640 + private val height = 480 +// private val width = 1440 +// private val height = 1080 private val vBitrate = 1200 * 1000 private var rotation = 0 private val sampleRate = 32000 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1357839f1b..f82a65dd6e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,7 +9,7 @@ Camera Live Select your streamer protocol://yourendpoint - rtmp://tx.direct.huya.com/huyalive/1445553152-1445553152-7010649780141597433-2035368522-10057-A-1632294239-1?seq=1735020136589&type=simple + rtmp://tx.direct.huya.com/huyalive/1445553152-1445553152-7010649780141597433-2035368522-10057-A-1632294239-1?seq=1735090859329&type=simple null rtmps://a.rtmps.youtube.com/live2/xpjt-yrb3-dtft-asvs-1erd diff --git a/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java b/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java index 306b799df7..c48fcc9129 100644 --- a/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java +++ b/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java @@ -46,13 +46,28 @@ public static ViewPort calculateViewPort(AspectRatioMode mode, int previewWidth, //640x480是宽x高,在屏幕显示的时候640是竖直方向,480是水平方向 float wr = (float) streamHeight / previewWidth;//水平方向的占比 float hr = (float) streamWidth / previewHeight;//竖直方向的占比 - if(wr > hr) { - xo = 0; - yo = (int) ((previewHeight - streamWidth / wr) / 2); - xf = previewWidth; - yf = (int) (streamWidth / wr); + if(wr <= 1.0 && hr <= 1.0){ + Logger.d(TAG, "calculateViewPort: 1 wr = " + wr + "; hr = " + hr); + if(wr > hr) {//以水平方向去适配 + xo = 0; + yo = (int) ((previewHeight - streamWidth / wr) / 2); + xf = previewWidth; + yf = (int) (streamWidth / wr); + } + }else{ + Logger.d(TAG, "calculateViewPort: 2 wr = " + wr + "; hr = " + hr); + wr = (float) previewWidth / streamWidth;//水平方向的占比 + hr = (float) previewHeight / streamHeight;//竖直方向的占比 + Logger.d(TAG, "calculateViewPort: 3 wr = " + wr + "; hr = " + hr); + if(wr < hr) {//将画面倒转过来,以水平方向去适配 + xo = 0; + yo = (int) ((previewHeight - streamHeight * wr) / 2); + xf = previewWidth; + yf = (int) (streamHeight * wr); + } } + // if (streamAspectRatio > previewAspectRatio) { // yf = streamHeight * previewWidth / streamWidth; // yo = (yf - previewHeight) / -2; From 1138e2d0c6a788ff8ae2af861a51498fb5ebad33 Mon Sep 17 00:00:00 2001 From: "lvlin.huang" Date: Wed, 25 Dec 2024 13:52:32 +0800 Subject: [PATCH 03/13] [feat] Modify rtmp stream resolution --- .../com/pedro/encoder/utils/gl/SizeCalculator.java | 2 +- .../java/com/pedro/library/generic/GenericStream.kt | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java b/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java index c48fcc9129..3ed5824089 100644 --- a/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java +++ b/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java @@ -44,7 +44,7 @@ public static ViewPort calculateViewPort(AspectRatioMode mode, int previewWidth, if (mode == AspectRatioMode.Adjust) { Logger.d(TAG, "calculateViewPort: streamAspectRatio = " + streamAspectRatio + "; previewAspectRatio = " + previewAspectRatio + ";"); //640x480是宽x高,在屏幕显示的时候640是竖直方向,480是水平方向 - float wr = (float) streamHeight / previewWidth;//水平方向的占比 + float wr = (float) streamHeight / previewWidth;//水平方向的占比, 一般后者比前者大,比值是小于1的 float hr = (float) streamWidth / previewHeight;//竖直方向的占比 if(wr <= 1.0 && hr <= 1.0){ Logger.d(TAG, "calculateViewPort: 1 wr = " + wr + "; hr = " + hr); diff --git a/library/src/main/java/com/pedro/library/generic/GenericStream.kt b/library/src/main/java/com/pedro/library/generic/GenericStream.kt index 4b70e2e84a..044f7cfbc2 100644 --- a/library/src/main/java/com/pedro/library/generic/GenericStream.kt +++ b/library/src/main/java/com/pedro/library/generic/GenericStream.kt @@ -29,6 +29,7 @@ import com.pedro.encoder.input.sources.audio.AudioSource import com.pedro.encoder.input.sources.audio.MicrophoneSource import com.pedro.encoder.input.sources.video.Camera2Source import com.pedro.encoder.input.sources.video.VideoSource +import com.pedro.encoder.utils.Logger import com.pedro.library.util.streamclient.GenericStreamClient import com.pedro.library.util.streamclient.RtmpStreamClient import com.pedro.library.util.streamclient.RtspStreamClient @@ -54,6 +55,9 @@ class GenericStream( videoSource: VideoSource, audioSource: AudioSource ): StreamBase(context, videoSource, audioSource) { + companion object { + private const val TAG = "GenericStream" + } private val streamClientListener = object: StreamClientListener { override fun onRequestKeyframe() { @@ -109,8 +113,11 @@ class GenericStream( if (endPoint.startsWith("rtmp", ignoreCase = true)) { connectedType = ClientType.RTMP val resolution = super.getVideoResolution() - rtmpClient.setVideoResolution(resolution.width, resolution.height) - rtmpClient.setFps(super.getVideoFps()) + val fps = super.getVideoFps() + Logger.d(TAG, "startStreamImp: resolution = $resolution, fps = $fps") +// rtmpClient.setVideoResolution(resolution.width, resolution.height) + rtmpClient.setVideoResolution(resolution.height, resolution.width) + rtmpClient.setFps(fps) rtmpClient.connect(endPoint) } else if (endPoint.startsWith("rtsp", ignoreCase = true)) { connectedType = ClientType.RTSP From 930f44c679bd89a83f56312a090397dee3e54205 Mon Sep 17 00:00:00 2001 From: "haliry.huang" Date: Wed, 25 Dec 2024 20:13:50 +0800 Subject: [PATCH 04/13] [feat] Force preview orientation to landscape --- .../encoder/input/gl/render/ScreenRender.java | 1 + .../encoder/utils/gl/SizeCalculator.java | 1 + .../com/pedro/encoder/video/VideoEncoder.java | 4 +- .../pedro/library/generic/GenericStream.kt | 4 +- .../pedro/library/view/GlStreamInterface.kt | 15 ++-- .../com/pedro/rtmp/rtmp/CommandsManager.kt | 6 +- .../pedro/rtmp/rtmp/CommandsManagerAmf0.kt | 9 +++ .../pedro/rtmp/rtmp/CommandsManagerAmf3.kt | 6 ++ .../main/java/com/pedro/rtmp/utils/Logger.kt | 72 +++++++++++++++++++ 9 files changed, 108 insertions(+), 10 deletions(-) create mode 100644 rtmp/src/main/java/com/pedro/rtmp/utils/Logger.kt diff --git a/encoder/src/main/java/com/pedro/encoder/input/gl/render/ScreenRender.java b/encoder/src/main/java/com/pedro/encoder/input/gl/render/ScreenRender.java index a8c777c5d3..55766e40fe 100644 --- a/encoder/src/main/java/com/pedro/encoder/input/gl/render/ScreenRender.java +++ b/encoder/src/main/java/com/pedro/encoder/input/gl/render/ScreenRender.java @@ -110,6 +110,7 @@ public void drawEncoder(int width, int height, boolean isPortrait, int rotation, updateMatrix(rotation, SizeCalculator.calculateFlip(flipStreamHorizontal, flipStreamVertical), MVPMatrix); ViewPort viewport = SizeCalculator.calculateViewPortEncoder(width, height, isPortrait); + Logger.d(TAG, "drawEncoder: viewport = " + viewport); GLES20.glViewport(viewport.getX(), viewport.getY(), viewport.getWidth(), viewport.getHeight()); draw(); diff --git a/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java b/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java index 3ed5824089..5f1fc7d7e5 100644 --- a/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java +++ b/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java @@ -90,6 +90,7 @@ public static ViewPort calculateViewPort(AspectRatioMode mode, int previewWidth, public static ViewPort calculateViewPortEncoder(int streamWidth, int streamHeight, boolean isPortrait) { float factor = (float) streamWidth / (float) streamHeight; + Logger.i(TAG, "calculateViewPortEncoder: factor = " + factor + "; isPortrait = " + isPortrait); if (factor >= 1f) { if (isPortrait) { int width = (int) (streamHeight / factor); diff --git a/encoder/src/main/java/com/pedro/encoder/video/VideoEncoder.java b/encoder/src/main/java/com/pedro/encoder/video/VideoEncoder.java index 3768bca7b0..4fa7b43897 100644 --- a/encoder/src/main/java/com/pedro/encoder/video/VideoEncoder.java +++ b/encoder/src/main/java/com/pedro/encoder/video/VideoEncoder.java @@ -38,6 +38,7 @@ import com.pedro.encoder.input.video.FpsLimiter; import com.pedro.encoder.input.video.GetCameraData; import com.pedro.encoder.utils.CodecUtil; +import com.pedro.encoder.utils.Logger; import com.pedro.encoder.utils.yuv.YUVUtil; import java.nio.ByteBuffer; @@ -140,7 +141,8 @@ public boolean prepareVideoEncoder(int width, int height, int fps, int bitRate, resolution = width + "x" + height; videoFormat = MediaFormat.createVideoFormat(type, width, height); } - Log.i(TAG, "Prepare video info: " + this.formatVideoEncoder.name() + ", " + resolution); + Logger.i(TAG, "prepareVideoEncoder: rotation = " + rotation + "; videoFormat = " + videoFormat); + Logger.i(TAG, "Prepare video info: " + this.formatVideoEncoder.name() + ", " + resolution); videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, this.formatVideoEncoder.getFormatCodec()); videoFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0); diff --git a/library/src/main/java/com/pedro/library/generic/GenericStream.kt b/library/src/main/java/com/pedro/library/generic/GenericStream.kt index 044f7cfbc2..36ce71b4bf 100644 --- a/library/src/main/java/com/pedro/library/generic/GenericStream.kt +++ b/library/src/main/java/com/pedro/library/generic/GenericStream.kt @@ -115,8 +115,8 @@ class GenericStream( val resolution = super.getVideoResolution() val fps = super.getVideoFps() Logger.d(TAG, "startStreamImp: resolution = $resolution, fps = $fps") -// rtmpClient.setVideoResolution(resolution.width, resolution.height) - rtmpClient.setVideoResolution(resolution.height, resolution.width) + rtmpClient.setVideoResolution(resolution.width, resolution.height) +// rtmpClient.setVideoResolution(1080, 1920) rtmpClient.setFps(fps) rtmpClient.connect(endPoint) } else if (endPoint.startsWith("rtsp", ignoreCase = true)) { diff --git a/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt b/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt index 1e6d5a3c65..d0a6c06f3a 100644 --- a/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt +++ b/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt @@ -225,11 +225,13 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, } } - val orientation = when (orientationForced) { - OrientationForced.PORTRAIT -> true - OrientationForced.LANDSCAPE -> false - OrientationForced.NONE -> isPortrait - } +// val orientation = when (orientationForced) { +// OrientationForced.PORTRAIT -> true +// OrientationForced.LANDSCAPE -> false +// OrientationForced.NONE -> isPortrait +// } + val orientation = false + Logger.d(TAG, "draw: orientation = $orientation") // render VideoEncoder (stream and record) if (surfaceManagerEncoder.isReady && mainRender.isReady() && !limitFps) { Logger.d(TAG, "draw: 1 surfaceManagerEncoder.isReady = ${surfaceManagerEncoder.isReady}, mainRender.isReady() = ${mainRender.isReady()}, limitFps = ${limitFps}") @@ -279,6 +281,7 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, } fun forceOrientation(forced: OrientationForced) { + Logger.d(TAG, "forceOrientation: forced = $forced") when (forced) { OrientationForced.PORTRAIT -> { setCameraOrientation(90) @@ -290,6 +293,7 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, } OrientationForced.NONE -> { val orientation = CameraHelper.getCameraOrientation(context) + Logger.d(TAG, "forceOrientation: orientation = $orientation") setCameraOrientation(if (orientation == 0) 270 else orientation - 90) shouldHandleOrientation = true } @@ -330,6 +334,7 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, } fun setCameraOrientation(orientation: Int) { + Logger.d(TAG, "setCameraOrientation: orientation = $orientation") mainRender.setCameraRotation(orientation) } diff --git a/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManager.kt b/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManager.kt index 8933d9538f..a800a16c17 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManager.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManager.kt @@ -37,8 +37,10 @@ import java.io.* * Created by pedro on 21/04/21. */ abstract class CommandsManager { - - protected val TAG = "CommandsManager" + companion object { + private const val TAG = "CommandsManager" + } +// protected val TAG = "CommandsManager" val sessionHistory = CommandSessionHistory() var timestamp = 0 diff --git a/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf0.kt b/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf0.kt index 49304e25fe..7bbc473875 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf0.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf0.kt @@ -32,9 +32,14 @@ import com.pedro.rtmp.rtmp.chunk.ChunkType import com.pedro.rtmp.rtmp.message.BasicHeader import com.pedro.rtmp.rtmp.message.command.CommandAmf0 import com.pedro.rtmp.rtmp.message.data.DataAmf0 +import com.pedro.rtmp.utils.Logger import com.pedro.rtmp.utils.socket.RtmpSocket class CommandsManagerAmf0: CommandsManager() { + companion object { + private const val TAG = "CommandsManagerAmf0" + } + override suspend fun sendConnectImp(auth: String, socket: RtmpSocket) { val connect = CommandAmf0("connect", ++commandId, getCurrentTimestamp(), streamId, BasicHeader(ChunkType.TYPE_0, ChunkStreamId.OVER_CONNECTION.mark)) @@ -104,6 +109,10 @@ class CommandsManagerAmf0: CommandsManager() { if (!videoDisabled) { amfEcmaArray.setProperty("width", width.toDouble()) amfEcmaArray.setProperty("height", height.toDouble()) +// amfEcmaArray.setProperty("width", height.toDouble()) +// amfEcmaArray.setProperty("height", width.toDouble()) + Logger.d(TAG, "sendMetadataImp: width = $width, height = $height") + //few servers don't support it even if it is in the standard rtmp enhanced val codecValue = when (videoCodec) { VideoCodec.H264 -> VideoFormat.AVC.value diff --git a/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf3.kt b/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf3.kt index 060c210542..fa9bc88897 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf3.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf3.kt @@ -31,9 +31,14 @@ import com.pedro.rtmp.rtmp.chunk.ChunkType import com.pedro.rtmp.rtmp.message.BasicHeader import com.pedro.rtmp.rtmp.message.command.CommandAmf3 import com.pedro.rtmp.rtmp.message.data.DataAmf3 +import com.pedro.rtmp.utils.Logger import com.pedro.rtmp.utils.socket.RtmpSocket class CommandsManagerAmf3: CommandsManager() { + companion object { + private const val TAG = "CommandsManagerAmf3" + } + override suspend fun sendConnectImp(auth: String, socket: RtmpSocket) { val connect = CommandAmf3("connect", ++commandId, getCurrentTimestamp(), streamId, BasicHeader(ChunkType.TYPE_0, ChunkStreamId.OVER_CONNECTION.mark)) @@ -101,6 +106,7 @@ class CommandsManagerAmf3: CommandsManager() { val amfEcmaArray = Amf3Dictionary() amfEcmaArray.setProperty("duration", 0.0) if (!videoDisabled) { + Logger.d(TAG, "sendMetadataImp: width = $width, height = $height") amfEcmaArray.setProperty("width", width.toDouble()) amfEcmaArray.setProperty("height", height.toDouble()) //few servers don't support it even if it is in the standard rtmp enhanced diff --git a/rtmp/src/main/java/com/pedro/rtmp/utils/Logger.kt b/rtmp/src/main/java/com/pedro/rtmp/utils/Logger.kt new file mode 100644 index 0000000000..1db323e640 --- /dev/null +++ b/rtmp/src/main/java/com/pedro/rtmp/utils/Logger.kt @@ -0,0 +1,72 @@ +package com.pedro.rtmp.utils + +import android.util.Log + + +object Logger { + private const val TAG = "HUANG" + private const val ERROR = "ANDROID_ERROR:" + private const val WARN = "ANDROID_WARN:" + private const val RUNTIME_EXCEPTION = "ANDROID_RUNTIME_EXCEPTION" +// private val DEBUG = BuildConfig.DEBUG + private val DEBUG = true + + @JvmStatic + fun v(subTag: String, message: String){ + if (DEBUG) { + Log.v(TAG, "[$subTag][Thread: ${Thread.currentThread().name}] $message") + } + } + @JvmStatic + fun d(subTag: String, message: String){ + if (DEBUG) { + Log.d(TAG, "[$subTag][Thread: ${Thread.currentThread().name}] $message") + } + } + @JvmStatic + fun i(subTag: String, message: String){ + if (DEBUG) { + Log.i(TAG, "[$subTag][Thread: ${Thread.currentThread().name}] $message") + } + } + @JvmStatic + fun w(subTag: String, message: String){ + if (DEBUG) { + Log.w(TAG, "[$subTag][Thread: ${Thread.currentThread().name}] $WARN $message") + } + } + @JvmStatic + fun e(subTag: String, message: String){ + if (DEBUG) { + Log.e(TAG, "[$subTag][Thread: ${Thread.currentThread().name}] $ERROR $message") + } + } + + @JvmStatic + fun throwRuntimeException(){ + RuntimeException(TAG).printStackTrace() + } + + @JvmStatic + fun printStackTrace(tag: String){ + //打印堆栈而不退出 + d(tag, Log.getStackTraceString(Throwable())) + + Exception("debug log").printStackTrace() + + for (i in Thread.currentThread().stackTrace){ + i(tag, i.toString()) + } + val runtimeException = RuntimeException() + runtimeException.fillInStackTrace() + + try { + i(tag, "----------------------throw NullPointerException----------------------") + throw NullPointerException() + } catch (nullPointer: NullPointerException) { + i(tag, "----------------------catch NullPointerException----------------------") + e(tag, Log.getStackTraceString(nullPointer)) + } + i(tag, "----------------------end NullPointerException ----------------------") + } +} \ No newline at end of file From 38371cee647053193566d78a24379d272b377676 Mon Sep 17 00:00:00 2001 From: "lvlin.huang" Date: Wed, 25 Dec 2024 21:05:53 +0800 Subject: [PATCH 05/13] [feat] Modify layout style --- app/src/main/res/drawable/stream_icon.xml | 4 ++-- app/src/main/res/drawable/switch_icon.xml | 4 ++-- app/src/main/res/layout/fragment_camera.xml | 20 +++++++++---------- app/src/main/res/menu/rotation_menu.xml | 12 +++++++---- app/src/main/res/values/colors.xml | 3 ++- app/src/main/res/values/strings.xml | 2 +- .../pedro/library/generic/GenericFromFile.kt | 5 +++++ .../java/com/pedro/library/rtmp/RtmpStream.kt | 6 ++++++ 8 files changed, 36 insertions(+), 20 deletions(-) diff --git a/app/src/main/res/drawable/stream_icon.xml b/app/src/main/res/drawable/stream_icon.xml index fc5d03ebb9..9ef1313ceb 100644 --- a/app/src/main/res/drawable/stream_icon.xml +++ b/app/src/main/res/drawable/stream_icon.xml @@ -15,8 +15,8 @@ --> + app:layout_constraintBottom_toBottomOf="parent">

- + - + - + - + - #F48442 + #F48442 #000000 + @color/black #ffffff diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f82a65dd6e..ffe51570ef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,7 +6,7 @@ From file Screen - Camera Live + Hohem Live Select your streamer protocol://yourendpoint rtmp://tx.direct.huya.com/huyalive/1445553152-1445553152-7010649780141597433-2035368522-10057-A-1632294239-1?seq=1735090859329&type=simple diff --git a/library/src/main/java/com/pedro/library/generic/GenericFromFile.kt b/library/src/main/java/com/pedro/library/generic/GenericFromFile.kt index 5a16bbabf1..f94052cf53 100644 --- a/library/src/main/java/com/pedro/library/generic/GenericFromFile.kt +++ b/library/src/main/java/com/pedro/library/generic/GenericFromFile.kt @@ -25,6 +25,7 @@ import com.pedro.common.VideoCodec import com.pedro.common.onMainThreadHandler import com.pedro.encoder.input.decoder.AudioDecoderInterface import com.pedro.encoder.input.decoder.VideoDecoderInterface +import com.pedro.encoder.utils.Logger import com.pedro.library.base.FromFileBase import com.pedro.library.util.streamclient.GenericStreamClient import com.pedro.library.util.streamclient.RtmpStreamClient @@ -41,6 +42,9 @@ import java.nio.ByteBuffer @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) class GenericFromFile: FromFileBase { + companion object { + private const val TAG = "GenericFromFile" + } private val streamClientListener = object: StreamClientListener { override fun onRequestKeyframe() { @@ -123,6 +127,7 @@ class GenericFromFile: FromFileBase { streamClient.connecting(url) if (url.startsWith("rtmp", ignoreCase = true)) { connectedType = ClientType.RTMP + Logger.d(TAG, "startStreamImp: videoEncoder.rotation = ${videoEncoder.rotation}, videoEncoder.width = ${videoEncoder.width}, videoEncoder.height = ${videoEncoder.height}") if (videoEncoder.rotation == 90 || videoEncoder.rotation == 270) { rtmpClient.setVideoResolution(videoEncoder.height, videoEncoder.width) } else { diff --git a/library/src/main/java/com/pedro/library/rtmp/RtmpStream.kt b/library/src/main/java/com/pedro/library/rtmp/RtmpStream.kt index 027091bb59..a12c56d995 100644 --- a/library/src/main/java/com/pedro/library/rtmp/RtmpStream.kt +++ b/library/src/main/java/com/pedro/library/rtmp/RtmpStream.kt @@ -28,6 +28,7 @@ import com.pedro.encoder.input.sources.audio.AudioSource import com.pedro.encoder.input.sources.audio.MicrophoneSource import com.pedro.encoder.input.sources.video.Camera2Source import com.pedro.encoder.input.sources.video.VideoSource +import com.pedro.encoder.utils.Logger import com.pedro.library.util.streamclient.RtmpStreamClient import com.pedro.library.util.streamclient.StreamClientListener import com.pedro.rtmp.rtmp.RtmpClient @@ -45,6 +46,9 @@ class RtmpStream( context: Context, connectChecker: ConnectChecker, videoSource: VideoSource, audioSource: AudioSource ): StreamBase(context, videoSource, audioSource) { + companion object { + private const val TAG = "RtmpStream" + } private val rtmpClient = RtmpClient(connectChecker) private val streamClientListener = object: StreamClientListener { @@ -70,6 +74,8 @@ class RtmpStream( } override fun startStreamImp(endPoint: String) { + Logger.d(TAG, "startStreamImp: endPoint = $endPoint") + Logger.throwRuntimeException() val resolution = super.getVideoResolution() rtmpClient.setVideoResolution(resolution.width, resolution.height) rtmpClient.setFps(super.getVideoFps()) From 40837876e9bec257e419d56b8b215ffa113b1778 Mon Sep 17 00:00:00 2001 From: "haliry.huang" Date: Thu, 26 Dec 2024 10:24:16 +0800 Subject: [PATCH 06/13] [feat] Add back event that stop live stream --- app/build.gradle.kts | 2 + .../pedro/streamer/rotation/CameraFragment.kt | 21 ++++++++ .../streamer/rotation/RotationActivity.kt | 50 +++++++++++++++++++ .../eventbus/BroadcastBackPressedEvent.kt | 4 ++ .../com/pedro/streamer/utils/Extensions.kt | 2 +- .../main/res/drawable/stream_stop_icon.xml | 4 +- .../encoder/utils/gl/SizeCalculator.java | 2 +- gradle/libs.versions.toml | 5 ++ 8 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/pedro/streamer/rotation/eventbus/BroadcastBackPressedEvent.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a01f1bc84b..b13cb4396b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -39,4 +39,6 @@ dependencies { implementation(libs.androidx.constraintlayout) implementation(libs.androidx.appcompat) implementation(libs.androidx.multidex) + implementation(libs.firebase.crashlytics.buildtools) + implementation(libs.eventbus) } diff --git a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt index 08ad3543cc..b0a957c086 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt @@ -37,9 +37,13 @@ import com.pedro.library.base.recording.RecordController import com.pedro.library.generic.GenericStream import com.pedro.library.util.BitrateAdapter import com.pedro.streamer.R +import com.pedro.streamer.rotation.eventbus.BroadcastBackPressedEvent import com.pedro.streamer.utils.Logger import com.pedro.streamer.utils.PathUtils import com.pedro.streamer.utils.toast +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -145,6 +149,7 @@ class CameraFragment: Fragment(), ConnectChecker { bStartStop.setImageResource(R.drawable.stream_icon) } } + bRecord.setOnClickListener { if (!genericStream.isRecording) { val folder = PathUtils.getRecordPath() @@ -173,6 +178,16 @@ class CameraFragment: Fragment(), ConnectChecker { return view } + @Subscribe(threadMode = ThreadMode.MAIN) + fun handleMessageEvent(event: BroadcastBackPressedEvent){ + if(genericStream.isStreaming){ + genericStream.stopStream() + bStartStop.setImageResource(R.drawable.stream_icon) + }else{ + (requireActivity() as RotationActivity).handleBackEvent() + } + } + fun setOrientationMode(isVertical: Boolean) { val wasOnPreview = genericStream.isOnPreview Logger.d(TAG, "setOrientationMode: isVertical = $isVertical, wasOnPreview = $wasOnPreview") @@ -184,6 +199,9 @@ class CameraFragment: Fragment(), ConnectChecker { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + if (EventBus.getDefault().isRegistered(this).not()) { + EventBus.getDefault().register(this) + } prepare() genericStream.getStreamClient().setReTries(10) } @@ -204,6 +222,9 @@ class CameraFragment: Fragment(), ConnectChecker { override fun onDestroy() { super.onDestroy() genericStream.release() + if(EventBus.getDefault().isRegistered(this)){ + EventBus.getDefault().unregister(this) + } } override fun onConnectionStarted(url: String) { diff --git a/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt b/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt index 2112b50f4e..a972e501c6 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt @@ -20,11 +20,14 @@ import android.annotation.SuppressLint import android.graphics.BitmapFactory import android.os.Build import android.os.Bundle +import android.view.KeyEvent import android.view.Menu import android.view.MenuItem import android.view.MotionEvent import android.view.View import android.view.View.OnTouchListener +import android.window.OnBackInvokedDispatcher +import androidx.activity.OnBackPressedCallback import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import com.pedro.encoder.input.sources.audio.MicrophoneSource @@ -35,9 +38,12 @@ import com.pedro.extrasources.BitmapSource import com.pedro.extrasources.CameraUvcSource import com.pedro.extrasources.CameraXSource import com.pedro.streamer.R +import com.pedro.streamer.rotation.eventbus.BroadcastBackPressedEvent import com.pedro.streamer.utils.FilterMenu +import com.pedro.streamer.utils.Logger import com.pedro.streamer.utils.toast import com.pedro.streamer.utils.updateMenuColor +import org.greenrobot.eventbus.EventBus /** @@ -45,6 +51,10 @@ import com.pedro.streamer.utils.updateMenuColor */ @RequiresApi(Build.VERSION_CODES.LOLLIPOP) class RotationActivity : AppCompatActivity(), OnTouchListener { + companion object { + private const val TAG = "RotationActivity" + private const val EXIT_TIME_INTERVAL = 2000 + } private val cameraFragment = CameraFragment.getInstance() private val filterMenu: FilterMenu by lazy { FilterMenu(this) } @@ -53,11 +63,30 @@ class RotationActivity : AppCompatActivity(), OnTouchListener { private var currentOrientation: MenuItem? = null private var currentFilter: MenuItem? = null private var currentPlatform: MenuItem? = null + private var mClickTime: Long = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.rotation_activity) supportFragmentManager.beginTransaction().add(R.id.container, cameraFragment).commit() + + initBackEventListener() + } + + private fun initBackEventListener(){ + onBackPressedListener(true){ + EventBus.getDefault().post(BroadcastBackPressedEvent()) + } + } + + fun handleBackEvent(){ + Logger.d(TAG, "handleBackEvent: ") + if((System.currentTimeMillis() - mClickTime) > EXIT_TIME_INTERVAL){ + toast("Press again to exit app") + mClickTime = System.currentTimeMillis() + }else{ + finish() + } } override fun onCreateOptionsMenu(menu: Menu): Boolean { @@ -75,6 +104,19 @@ class RotationActivity : AppCompatActivity(), OnTouchListener { return true } +// override fun onBackPressed() { +// super.onBackPressed() +// Logger.d(TAG, "onBackPressed: ") +// } + +// override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { +// if(keyCode == KeyEvent.KEYCODE_BACK){ +// Logger.d(TAG, "onKeyDown: ") +// +// } +// return super.onKeyDown(keyCode, event) +// } + override fun onOptionsItemSelected(item: MenuItem): Boolean { try { when (item.itemId) { @@ -144,4 +186,12 @@ class RotationActivity : AppCompatActivity(), OnTouchListener { } return false } +} + +fun AppCompatActivity.onBackPressedListener(isEnabled: Boolean, callback: () -> Unit){ + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(isEnabled){ + override fun handleOnBackPressed() { + callback() + } + }) } \ No newline at end of file diff --git a/app/src/main/java/com/pedro/streamer/rotation/eventbus/BroadcastBackPressedEvent.kt b/app/src/main/java/com/pedro/streamer/rotation/eventbus/BroadcastBackPressedEvent.kt new file mode 100644 index 0000000000..d159517264 --- /dev/null +++ b/app/src/main/java/com/pedro/streamer/rotation/eventbus/BroadcastBackPressedEvent.kt @@ -0,0 +1,4 @@ +package com.pedro.streamer.rotation.eventbus + +//用于EventBus事件 +class BroadcastBackPressedEvent {} \ No newline at end of file diff --git a/app/src/main/java/com/pedro/streamer/utils/Extensions.kt b/app/src/main/java/com/pedro/streamer/utils/Extensions.kt index 285c3e419e..5e51ddf6bd 100644 --- a/app/src/main/java/com/pedro/streamer/utils/Extensions.kt +++ b/app/src/main/java/com/pedro/streamer/utils/Extensions.kt @@ -72,6 +72,6 @@ fun Drawable.setColorFilter(@ColorInt color: Int) { fun MenuItem.updateMenuColor(context: Context, currentItem: MenuItem?): MenuItem { currentItem?.setColor(context, R.color.black) - setColor(context, R.color.appColor) + setColor(context, R.color.orange) return this } \ No newline at end of file diff --git a/app/src/main/res/drawable/stream_stop_icon.xml b/app/src/main/res/drawable/stream_stop_icon.xml index 9c64d0cb43..f3dcc1b265 100644 --- a/app/src/main/res/drawable/stream_stop_icon.xml +++ b/app/src/main/res/drawable/stream_stop_icon.xml @@ -15,8 +15,8 @@ --> Date: Thu, 26 Dec 2024 11:14:36 +0800 Subject: [PATCH 07/13] [feat] Replace UI icons --- .../pedro/streamer/rotation/CameraFragment.kt | 44 ++++++++++--------- app/src/main/res/drawable/camera_switch.xml | 7 +++ app/src/main/res/drawable/live_setting.xml | 15 +++++++ app/src/main/res/drawable/live_start.xml | 9 ++++ app/src/main/res/drawable/live_stop.xml | 9 ++++ app/src/main/res/layout/fragment_camera.xml | 15 +++---- app/src/main/res/values/colors.xml | 1 + 7 files changed, 71 insertions(+), 29 deletions(-) create mode 100644 app/src/main/res/drawable/camera_switch.xml create mode 100644 app/src/main/res/drawable/live_setting.xml create mode 100644 app/src/main/res/drawable/live_start.xml create mode 100644 app/src/main/res/drawable/live_stop.xml diff --git a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt index b0a957c086..96ada0ff5d 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt @@ -143,31 +143,33 @@ class CameraFragment: Fragment(), ConnectChecker { bStartStop.setOnClickListener { if (!genericStream.isStreaming) { genericStream.startStream(streamUrl.text.toString()) - bStartStop.setImageResource(R.drawable.stream_stop_icon) + bStartStop.setImageResource(R.drawable.live_stop) } else { genericStream.stopStream() - bStartStop.setImageResource(R.drawable.stream_icon) + bStartStop.setImageResource(R.drawable.live_start) } } bRecord.setOnClickListener { - if (!genericStream.isRecording) { - val folder = PathUtils.getRecordPath() - if (!folder.exists()) folder.mkdir() - val sdf = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()) - recordPath = "${folder.absolutePath}/${sdf.format(Date())}.mp4" - genericStream.startRecord(recordPath) { status -> - if (status == RecordController.Status.RECORDING) { - bRecord.setImageResource(R.drawable.stop_icon) - } - } - bRecord.setImageResource(R.drawable.pause_icon) - } else { - genericStream.stopRecord() - bRecord.setImageResource(R.drawable.record_icon) - PathUtils.updateGallery(requireContext(), recordPath) - } + toast("Feature not available yet!") +// if (!genericStream.isRecording) { +// val folder = PathUtils.getRecordPath() +// if (!folder.exists()) folder.mkdir() +// val sdf = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()) +// recordPath = "${folder.absolutePath}/${sdf.format(Date())}.mp4" +// genericStream.startRecord(recordPath) { status -> +// if (status == RecordController.Status.RECORDING) { +// bRecord.setImageResource(R.drawable.stop_icon) +// } +// } +// bRecord.setImageResource(R.drawable.pause_icon) +// } else { +// genericStream.stopRecord() +// bRecord.setImageResource(R.drawable.record_icon) +// PathUtils.updateGallery(requireContext(), recordPath) +// } } + bSwitchCamera.setOnClickListener { when (val source = genericStream.videoSource) { is Camera1Source -> source.switchCamera() @@ -182,7 +184,7 @@ class CameraFragment: Fragment(), ConnectChecker { fun handleMessageEvent(event: BroadcastBackPressedEvent){ if(genericStream.isStreaming){ genericStream.stopStream() - bStartStop.setImageResource(R.drawable.stream_icon) + bStartStop.setImageResource(R.drawable.live_start) }else{ (requireActivity() as RotationActivity).handleBackEvent() } @@ -239,7 +241,7 @@ class CameraFragment: Fragment(), ConnectChecker { toast("Retry") } else { genericStream.stopStream() - bStartStop.setImageResource(R.drawable.stream_icon) + bStartStop.setImageResource(R.drawable.live_start) toast("Failed: $reason") } } @@ -256,7 +258,7 @@ class CameraFragment: Fragment(), ConnectChecker { override fun onAuthError() { genericStream.stopStream() - bStartStop.setImageResource(R.drawable.stream_icon) + bStartStop.setImageResource(R.drawable.live_start) toast("Auth error") } diff --git a/app/src/main/res/drawable/camera_switch.xml b/app/src/main/res/drawable/camera_switch.xml new file mode 100644 index 0000000000..9ceaff4892 --- /dev/null +++ b/app/src/main/res/drawable/camera_switch.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/main/res/drawable/live_setting.xml b/app/src/main/res/drawable/live_setting.xml new file mode 100644 index 0000000000..bc8cfd7b5d --- /dev/null +++ b/app/src/main/res/drawable/live_setting.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/live_start.xml b/app/src/main/res/drawable/live_start.xml new file mode 100644 index 0000000000..298480b838 --- /dev/null +++ b/app/src/main/res/drawable/live_start.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/live_stop.xml b/app/src/main/res/drawable/live_stop.xml new file mode 100644 index 0000000000..61536e1140 --- /dev/null +++ b/app/src/main/res/drawable/live_stop.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_camera.xml b/app/src/main/res/layout/fragment_camera.xml index eac754ede7..4399a4010f 100644 --- a/app/src/main/res/layout/fragment_camera.xml +++ b/app/src/main/res/layout/fragment_camera.xml @@ -13,8 +13,8 @@ #F48442 + #A69D97 #000000 @color/black #ffffff From adf62f75662265128ab6ad73d59ceef0ebdff581 Mon Sep 17 00:00:00 2001 From: "haliry.huang" Date: Mon, 30 Dec 2024 13:41:36 +0800 Subject: [PATCH 08/13] [feat] Modify defaulted live stream resolution --- .../com/pedro/streamer/rotation/CameraFragment.kt | 11 ++++++----- .../com/pedro/encoder/utils/gl/SizeCalculator.java | 2 +- .../java/com/pedro/encoder/video/VideoEncoder.java | 9 ++++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt index 96ada0ff5d..ef450874e8 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt @@ -90,11 +90,12 @@ class CameraFragment: Fragment(), ConnectChecker { private lateinit var bStartStop: ImageView private lateinit var txtBitrate: TextView lateinit var streamUrl: EditText - private val width = 640 - private val height = 480 -// private val width = 1440 -// private val height = 1080 - private val vBitrate = 1200 * 1000 +// private val width = 640 +// private val height = 480 + private val width = 1440 + private val height = 1080 +// private val vBitrate = 1200 * 1000 + private val vBitrate = 2500 * 1000 private var rotation = 0 private val sampleRate = 32000 private val isStereo = true diff --git a/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java b/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java index 6ea2c762d1..9440f1e54e 100644 --- a/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java +++ b/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java @@ -61,7 +61,7 @@ public static ViewPort calculateViewPort(AspectRatioMode mode, int previewWidth, Logger.d(TAG, "calculateViewPort: 3 wr = " + wr + "; hr = " + hr); if(wr < hr) {//将画面倒转过来,以水平方向去适配 xo = 0; - yo = (int) ((previewHeight - streamHeight * wr) / 2) + 25; + yo = (int) ((previewHeight - streamHeight * wr) / 2) + 20; xf = previewWidth; yf = (int) (streamHeight * wr); } diff --git a/encoder/src/main/java/com/pedro/encoder/video/VideoEncoder.java b/encoder/src/main/java/com/pedro/encoder/video/VideoEncoder.java index 4fa7b43897..b83ce7a2f0 100644 --- a/encoder/src/main/java/com/pedro/encoder/video/VideoEncoder.java +++ b/encoder/src/main/java/com/pedro/encoder/video/VideoEncoder.java @@ -61,10 +61,13 @@ public class VideoEncoder extends BaseEncoder implements GetCameraData { //surface to buffer encoder private Surface inputSurface; - private int width = 640; - private int height = 480; +// private int width = 640; +// private int height = 480; + private int width = 1440; + private int height = 1080; private int fps = 30; - private int bitRate = 1200 * 1024; //in kbps +// private int bitRate = 1200 * 1024; //in kbps + private int bitRate = 2500 * 1024; //in kbps private int rotation = 90; private int iFrameInterval = 2; private long firstTimestamp = 0; From 233a23183da97a9850b406e2bb513869825c52e6 Mon Sep 17 00:00:00 2001 From: "lvlin.huang" Date: Mon, 30 Dec 2024 15:32:41 +0800 Subject: [PATCH 09/13] [feat] Add switch resolution option --- .../pedro/streamer/rotation/CameraFragment.kt | 42 +++++++++++++++++-- .../streamer/rotation/RotationActivity.kt | 2 +- app/src/main/res/drawable/resolution_1080.xml | 7 ++++ app/src/main/res/drawable/resolution_720.xml | 7 ++++ app/src/main/res/layout/fragment_camera.xml | 4 +- .../encoder/input/gl/render/ScreenRender.java | 2 +- .../input/sources/video/Camera2Source.kt | 9 ++++ .../com/pedro/encoder/video/VideoEncoder.java | 12 ++++++ .../java/com/pedro/library/base/StreamBase.kt | 10 +++++ .../pedro/library/view/GlStreamInterface.kt | 4 +- 10 files changed, 91 insertions(+), 8 deletions(-) create mode 100644 app/src/main/res/drawable/resolution_1080.xml create mode 100644 app/src/main/res/drawable/resolution_720.xml diff --git a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt index ef450874e8..020925734f 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt @@ -80,6 +80,10 @@ class CameraFragment: Fragment(), ConnectChecker { private const val TAG = "CameraFragment" } + enum class Resolution { + _1080P, _720P + } + val genericStream: GenericStream by lazy { GenericStream(requireContext(), this).apply { getGlInterface().autoHandleOrientation = true @@ -92,15 +96,17 @@ class CameraFragment: Fragment(), ConnectChecker { lateinit var streamUrl: EditText // private val width = 640 // private val height = 480 - private val width = 1440 - private val height = 1080 + private var width = 1440 + private var height = 1080 // private val vBitrate = 1200 * 1000 - private val vBitrate = 2500 * 1000 + private var vBitrate = 2500 * 1000 private var rotation = 0 private val sampleRate = 32000 private val isStereo = true private val aBitrate = 128 * 1000 private var recordPath = "" + private var mCurResolution = Resolution._1080P + //Bitrate adapter used to change the bitrate on fly depend of the bandwidth. private val bitrateAdapter = BitrateAdapter { genericStream.setVideoBitrateOnFly(it) @@ -153,6 +159,36 @@ class CameraFragment: Fragment(), ConnectChecker { bRecord.setOnClickListener { toast("Feature not available yet!") + + if(mCurResolution == Resolution._1080P){ + mCurResolution = Resolution._720P + width = 960 + height = 720 + vBitrate = 2000 * 1000 + bRecord.setImageResource(R.drawable.resolution_720) + genericStream.setVideoResolution(width, height) + genericStream.setVideoBitRate(vBitrate) + if(genericStream.isStreaming){ + genericStream.stopStream() + bStartStop.setImageResource(R.drawable.live_start) + } + }else{ + mCurResolution = Resolution._1080P + width = 1440 + height = 1080 + vBitrate = 2500 * 1000 + bRecord.setImageResource(R.drawable.resolution_1080) + genericStream.setVideoResolution(width, height) + genericStream.setVideoBitRate(vBitrate) + if(genericStream.isStreaming){ + genericStream.stopStream() + bStartStop.setImageResource(R.drawable.live_start) + } + } + when (val source = genericStream.videoSource) { + is Camera2Source -> source.changeResolution() + } + // if (!genericStream.isRecording) { // val folder = PathUtils.getRecordPath() // if (!folder.exists()) folder.mkdir() diff --git a/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt b/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt index a972e501c6..04600a163a 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt @@ -95,7 +95,7 @@ class RotationActivity : AppCompatActivity(), OnTouchListener { val defaultAudioSource = menu.findItem(R.id.audio_source_microphone) val defaultOrientation = menu.findItem(R.id.orientation_horizontal) val defaultFilter = menu.findItem(R.id.no_filter) - val defaultPlatform = menu.findItem(R.id.platform_huya) + val defaultPlatform = menu.findItem(R.id.platform_youtube) currentVideoSource = defaultVideoSource.updateMenuColor(this, currentVideoSource) currentAudioSource = defaultAudioSource.updateMenuColor(this, currentAudioSource) currentOrientation = defaultOrientation.updateMenuColor(this, currentOrientation) diff --git a/app/src/main/res/drawable/resolution_1080.xml b/app/src/main/res/drawable/resolution_1080.xml new file mode 100644 index 0000000000..f500791f6b --- /dev/null +++ b/app/src/main/res/drawable/resolution_1080.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/main/res/drawable/resolution_720.xml b/app/src/main/res/drawable/resolution_720.xml new file mode 100644 index 0000000000..7eacdac9aa --- /dev/null +++ b/app/src/main/res/drawable/resolution_720.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/main/res/layout/fragment_camera.xml b/app/src/main/res/layout/fragment_camera.xml index 4399a4010f..0e4babd81c 100644 --- a/app/src/main/res/layout/fragment_camera.xml +++ b/app/src/main/res/layout/fragment_camera.xml @@ -22,7 +22,7 @@ android:layout_marginStart="20dp" android:layout_marginEnd="20dp" android:id="@+id/et_rtp_url" - android:text="@string/stream_url_huya" + android:text="@string/stream_url_youtube" app:layout_constraintTop_toTopOf="parent" android:imeOptions="actionGo"/> @@ -39,7 +39,7 @@ android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent"> Date: Mon, 6 Jan 2025 14:07:11 +0800 Subject: [PATCH 10/13] [feat] Remove some toast and set youtube address into defaulted --- .../com/pedro/streamer/rotation/CameraFragment.kt | 13 +++++++------ .../com/pedro/streamer/rotation/RotationActivity.kt | 2 +- app/src/main/res/layout/fragment_camera.xml | 3 ++- .../com/pedro/encoder/utils/gl/SizeCalculator.java | 2 +- .../com/pedro/library/view/GlStreamInterface.kt | 4 ++-- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt index 020925734f..0df9e56643 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt @@ -158,7 +158,7 @@ class CameraFragment: Fragment(), ConnectChecker { } bRecord.setOnClickListener { - toast("Feature not available yet!") +// toast("Resolution changed!") if(mCurResolution == Resolution._1080P){ mCurResolution = Resolution._720P @@ -253,7 +253,8 @@ class CameraFragment: Fragment(), ConnectChecker { false } if (!prepared) { - toast("Audio or Video configuration failed") +// toast("Audio or Video configuration failed") + Logger.d(TAG, "prepare: Audio or Video configuration failed") activity?.finish() } } @@ -275,11 +276,11 @@ class CameraFragment: Fragment(), ConnectChecker { override fun onConnectionFailed(reason: String) { if (genericStream.getStreamClient().reTry(5000, reason, null)) { - toast("Retry") +// toast("Retry") } else { genericStream.stopStream() bStartStop.setImageResource(R.drawable.live_start) - toast("Failed: $reason") +// toast("Failed: $reason") } } @@ -296,10 +297,10 @@ class CameraFragment: Fragment(), ConnectChecker { override fun onAuthError() { genericStream.stopStream() bStartStop.setImageResource(R.drawable.live_start) - toast("Auth error") +// toast("Auth error") } override fun onAuthSuccess() { - toast("Auth success") +// toast("Auth success") } } \ No newline at end of file diff --git a/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt b/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt index 04600a163a..9be91b3ec2 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt @@ -172,7 +172,7 @@ class RotationActivity : AppCompatActivity(), OnTouchListener { } } } catch (e: IllegalArgumentException) { - toast("Change source error: ${e.message}") +// toast("Change source error: ${e.message}") } return super.onOptionsItemSelected(item) } diff --git a/app/src/main/res/layout/fragment_camera.xml b/app/src/main/res/layout/fragment_camera.xml index 0e4babd81c..305d7a67dc 100644 --- a/app/src/main/res/layout/fragment_camera.xml +++ b/app/src/main/res/layout/fragment_camera.xml @@ -19,6 +19,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" + android:layout_marginTop="5dp" android:layout_marginStart="20dp" android:layout_marginEnd="20dp" android:id="@+id/et_rtp_url" @@ -30,7 +31,7 @@ android:id="@+id/txt_bitrate" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="@color/appColor" + android:textColor="@color/gray" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java b/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java index 9440f1e54e..e5510bb03c 100644 --- a/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java +++ b/encoder/src/main/java/com/pedro/encoder/utils/gl/SizeCalculator.java @@ -61,7 +61,7 @@ public static ViewPort calculateViewPort(AspectRatioMode mode, int previewWidth, Logger.d(TAG, "calculateViewPort: 3 wr = " + wr + "; hr = " + hr); if(wr < hr) {//将画面倒转过来,以水平方向去适配 xo = 0; - yo = (int) ((previewHeight - streamHeight * wr) / 2) + 20; + yo = (int) ((previewHeight - streamHeight * wr) / 2) + 15; xf = previewWidth; yf = (int) (streamHeight * wr); } diff --git a/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt b/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt index 90e01d4802..004840ae31 100644 --- a/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt +++ b/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt @@ -231,8 +231,8 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, // OrientationForced.NONE -> isPortrait // } //todo 临时修改:设置给记录仪的是横屏模式; -// val orientation = false - val orientation = true + val orientation = false +// val orientation = true Logger.d(TAG, "draw: orientation = $orientation") // render VideoEncoder (stream and record) if (surfaceManagerEncoder.isReady && mainRender.isReady() && !limitFps) { From bfa28bcc84db8cbdd28524c60dc189a7f6acb443 Mon Sep 17 00:00:00 2001 From: HUANG Date: Thu, 16 Oct 2025 18:45:52 +0800 Subject: [PATCH 11/13] [feat] Add permission check. --- app/src/main/AndroidManifest.xml | 152 ++++--- .../pedro/streamer/rotation/CameraFragment.kt | 356 +++++++-------- .../streamer/rotation/RotationActivity.kt | 422 +++++++++++++----- gradle/wrapper/gradle-wrapper.properties | 2 +- 4 files changed, 561 insertions(+), 371 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3bd162ebd1..18ef3ca305 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,86 +1,94 @@ - + - - - - + + + - - - - - - - - - - + + + + + + + - - + + + + + + + + + + - - - - - + + - - - + + + + + - + + + - + - + - - - - + - - - + + + + - - + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt index 0df9e56643..e32842856d 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt @@ -73,121 +73,128 @@ import java.util.Locale * [com.pedro.library.srt.SrtStream] */ @RequiresApi(Build.VERSION_CODES.LOLLIPOP) -class CameraFragment: Fragment(), ConnectChecker { +class CameraFragment : Fragment(), ConnectChecker { - companion object { - fun getInstance(): CameraFragment = CameraFragment() - private const val TAG = "CameraFragment" - } + companion object { + fun getInstance(): CameraFragment = CameraFragment() + private const val TAG = "CameraFragment" + } - enum class Resolution { - _1080P, _720P - } + enum class Resolution { + _1080P, _720P + } - val genericStream: GenericStream by lazy { - GenericStream(requireContext(), this).apply { - getGlInterface().autoHandleOrientation = true - getStreamClient().setBitrateExponentialFactor(0.5f) + val genericStream: GenericStream by lazy { + GenericStream(requireContext(), this).apply { + getGlInterface().autoHandleOrientation = true + getStreamClient().setBitrateExponentialFactor(0.5f) + } } - } - private lateinit var surfaceView: SurfaceView - private lateinit var bStartStop: ImageView - private lateinit var txtBitrate: TextView - lateinit var streamUrl: EditText -// private val width = 640 -// private val height = 480 - private var width = 1440 - private var height = 1080 -// private val vBitrate = 1200 * 1000 - private var vBitrate = 2500 * 1000 - private var rotation = 0 - private val sampleRate = 32000 - private val isStereo = true - private val aBitrate = 128 * 1000 - private var recordPath = "" - private var mCurResolution = Resolution._1080P + private lateinit var surfaceView: SurfaceView + private lateinit var bStartStop: ImageView + private lateinit var txtBitrate: TextView + lateinit var streamUrl: EditText - //Bitrate adapter used to change the bitrate on fly depend of the bandwidth. - private val bitrateAdapter = BitrateAdapter { - genericStream.setVideoBitrateOnFly(it) - }.apply { - setMaxBitrate(vBitrate + aBitrate) - } + // private val width = 640 +// private val height = 480 + private var width = 1440 + private var height = 1080 - @SuppressLint("ClickableViewAccessibility") - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - val view = inflater.inflate(R.layout.fragment_camera, container, false) - bStartStop = view.findViewById(R.id.b_start_stop) - val bRecord = view.findViewById(R.id.b_record) - val bSwitchCamera = view.findViewById(R.id.switch_camera) - streamUrl = view.findViewById(R.id.et_rtp_url) + // private val vBitrate = 1200 * 1000 + private var vBitrate = 2500 * 1000 + private var rotation = 0 + private val sampleRate = 32000 + private val isStereo = true + private val aBitrate = 128 * 1000 + private var recordPath = "" + private var mCurResolution = Resolution._1080P - txtBitrate = view.findViewById(R.id.txt_bitrate) - surfaceView = view.findViewById(R.id.surfaceView) - (activity as? RotationActivity)?.let { - surfaceView.setOnTouchListener(it) + //Bitrate adapter used to change the bitrate on fly depend of the bandwidth. + private val bitrateAdapter = BitrateAdapter { + genericStream.setVideoBitrateOnFly(it) + }.apply { + setMaxBitrate(vBitrate + aBitrate) } - surfaceView.holder.addCallback(object: SurfaceHolder.Callback { - override fun surfaceCreated(holder: SurfaceHolder) { - holder.setKeepScreenOn(true) - if (!genericStream.isOnPreview) genericStream.startPreview(surfaceView) - } - override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { - Logger.d(TAG, "surfaceChanged: width = $width; height = $height") - genericStream.getGlInterface().setPreviewResolution(width, height) + @SuppressLint("ClickableViewAccessibility") + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + val view = inflater.inflate(R.layout.fragment_camera, container, false) + bStartStop = view.findViewById(R.id.b_start_stop) + val bRecord = view.findViewById(R.id.b_record) + val bSwitchCamera = view.findViewById(R.id.switch_camera) + streamUrl = view.findViewById(R.id.et_rtp_url) + + txtBitrate = view.findViewById(R.id.txt_bitrate) + surfaceView = view.findViewById(R.id.surfaceView) + (activity as? RotationActivity)?.let { + surfaceView.setOnTouchListener(it) + } + surfaceView.holder.addCallback(object : SurfaceHolder.Callback { + override fun surfaceCreated(holder: SurfaceHolder) { + holder.setKeepScreenOn(true) + if (!genericStream.isOnPreview) genericStream.startPreview(surfaceView) + } + + override fun surfaceChanged( + holder: SurfaceHolder, + format: Int, + width: Int, + height: Int + ) { + Logger.d(TAG, "surfaceChanged: width = $width; height = $height") + genericStream.getGlInterface().setPreviewResolution(width, height) // genericStream.getGlInterface().setPreviewResolution(height, width) - } + } - override fun surfaceDestroyed(holder: SurfaceHolder) { - if (genericStream.isOnPreview) genericStream.stopPreview() - } + override fun surfaceDestroyed(holder: SurfaceHolder) { + if (genericStream.isOnPreview) genericStream.stopPreview() + } - }) + }) - bStartStop.setOnClickListener { - if (!genericStream.isStreaming) { - genericStream.startStream(streamUrl.text.toString()) - bStartStop.setImageResource(R.drawable.live_stop) - } else { - genericStream.stopStream() - bStartStop.setImageResource(R.drawable.live_start) - } - } + bStartStop.setOnClickListener { + if (!genericStream.isStreaming) { + genericStream.startStream(streamUrl.text.toString()) + bStartStop.setImageResource(R.drawable.live_stop) + } else { + genericStream.stopStream() + bStartStop.setImageResource(R.drawable.live_start) + } + } - bRecord.setOnClickListener { + bRecord.setOnClickListener { // toast("Resolution changed!") - if(mCurResolution == Resolution._1080P){ - mCurResolution = Resolution._720P - width = 960 - height = 720 - vBitrate = 2000 * 1000 - bRecord.setImageResource(R.drawable.resolution_720) - genericStream.setVideoResolution(width, height) - genericStream.setVideoBitRate(vBitrate) - if(genericStream.isStreaming){ - genericStream.stopStream() - bStartStop.setImageResource(R.drawable.live_start) - } - }else{ - mCurResolution = Resolution._1080P - width = 1440 - height = 1080 - vBitrate = 2500 * 1000 - bRecord.setImageResource(R.drawable.resolution_1080) - genericStream.setVideoResolution(width, height) - genericStream.setVideoBitRate(vBitrate) - if(genericStream.isStreaming){ - genericStream.stopStream() - bStartStop.setImageResource(R.drawable.live_start) - } - } - when (val source = genericStream.videoSource) { - is Camera2Source -> source.changeResolution() - } + if (mCurResolution == Resolution._1080P) { + mCurResolution = Resolution._720P + width = 960 + height = 720 + vBitrate = 2000 * 1000 + bRecord.setImageResource(R.drawable.resolution_720) + genericStream.setVideoResolution(width, height) + genericStream.setVideoBitRate(vBitrate) + if (genericStream.isStreaming) { + genericStream.stopStream() + bStartStop.setImageResource(R.drawable.live_start) + } + } else { + mCurResolution = Resolution._1080P + width = 1440 + height = 1080 + vBitrate = 2500 * 1000 + bRecord.setImageResource(R.drawable.resolution_1080) + genericStream.setVideoResolution(width, height) + genericStream.setVideoBitRate(vBitrate) + if (genericStream.isStreaming) { + genericStream.stopStream() + bStartStop.setImageResource(R.drawable.live_start) + } + } + when (val source = genericStream.videoSource) { + is Camera2Source -> source.changeResolution() + } // if (!genericStream.isRecording) { // val folder = PathUtils.getRecordPath() @@ -205,102 +212,103 @@ class CameraFragment: Fragment(), ConnectChecker { // bRecord.setImageResource(R.drawable.record_icon) // PathUtils.updateGallery(requireContext(), recordPath) // } - } + } - bSwitchCamera.setOnClickListener { - when (val source = genericStream.videoSource) { - is Camera1Source -> source.switchCamera() - is Camera2Source -> source.switchCamera() - is CameraXSource -> source.switchCamera() - } + bSwitchCamera.setOnClickListener { + when (val source = genericStream.videoSource) { + is Camera1Source -> source.switchCamera() + is Camera2Source -> source.switchCamera() + is CameraXSource -> source.switchCamera() + } + } + return view } - return view - } - @Subscribe(threadMode = ThreadMode.MAIN) - fun handleMessageEvent(event: BroadcastBackPressedEvent){ - if(genericStream.isStreaming){ - genericStream.stopStream() - bStartStop.setImageResource(R.drawable.live_start) - }else{ - (requireActivity() as RotationActivity).handleBackEvent() + @Subscribe(threadMode = ThreadMode.MAIN) + fun handleMessageEvent(event: BroadcastBackPressedEvent) { + if (genericStream.isStreaming) { + genericStream.stopStream() + bStartStop.setImageResource(R.drawable.live_start) + } else { + (requireActivity() as RotationActivity).handleBackEvent() + } } - } - - fun setOrientationMode(isVertical: Boolean) { - val wasOnPreview = genericStream.isOnPreview - Logger.d(TAG, "setOrientationMode: isVertical = $isVertical, wasOnPreview = $wasOnPreview") - genericStream.release() - rotation = if (isVertical) 90 else 0 - prepare() - if (wasOnPreview) genericStream.startPreview(surfaceView) - } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - if (EventBus.getDefault().isRegistered(this).not()) { - EventBus.getDefault().register(this) - } - prepare() - genericStream.getStreamClient().setReTries(10) - } + fun setOrientationMode(isVertical: Boolean) { + val wasOnPreview = genericStream.isOnPreview + Logger.d(TAG, "setOrientationMode: isVertical = $isVertical, wasOnPreview = $wasOnPreview") + genericStream.release() + rotation = if (isVertical) 90 else 0 + prepare() + if (wasOnPreview) genericStream.startPreview(surfaceView) + } - private fun prepare() { - val prepared = try { - genericStream.prepareVideo(width, height, vBitrate, rotation = rotation) - && genericStream.prepareAudio(sampleRate, isStereo, aBitrate) - } catch (e: IllegalArgumentException) { - false + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Logger.d(TAG, "onCreate: ") + if (EventBus.getDefault().isRegistered(this).not()) { + EventBus.getDefault().register(this) + } + prepare() + genericStream.getStreamClient().setReTries(10) } - if (!prepared) { + + private fun prepare() { + val prepared = try { + genericStream.prepareVideo(width, height, vBitrate, rotation = rotation) + && genericStream.prepareAudio(sampleRate, isStereo, aBitrate) + } catch (e: IllegalArgumentException) { + false + } + if (!prepared) { // toast("Audio or Video configuration failed") - Logger.d(TAG, "prepare: Audio or Video configuration failed") - activity?.finish() + Logger.d(TAG, "prepare: Audio or Video configuration failed") + activity?.finish() + } } - } - override fun onDestroy() { - super.onDestroy() - genericStream.release() - if(EventBus.getDefault().isRegistered(this)){ - EventBus.getDefault().unregister(this) + override fun onDestroy() { + super.onDestroy() + genericStream.release() + if (EventBus.getDefault().isRegistered(this)) { + EventBus.getDefault().unregister(this) + } } - } - override fun onConnectionStarted(url: String) { - } + override fun onConnectionStarted(url: String) { + } - override fun onConnectionSuccess() { - toast("Connected") - } + override fun onConnectionSuccess() { + toast("Connected") + } - override fun onConnectionFailed(reason: String) { - if (genericStream.getStreamClient().reTry(5000, reason, null)) { + override fun onConnectionFailed(reason: String) { + if (genericStream.getStreamClient().reTry(5000, reason, null)) { // toast("Retry") - } else { - genericStream.stopStream() - bStartStop.setImageResource(R.drawable.live_start) + } else { + genericStream.stopStream() + bStartStop.setImageResource(R.drawable.live_start) // toast("Failed: $reason") + } } - } - override fun onNewBitrate(bitrate: Long) { - bitrateAdapter.adaptBitrate(bitrate, genericStream.getStreamClient().hasCongestion()) - txtBitrate.text = String.format(Locale.getDefault(), "%.1f mb/s", bitrate / 1000_000f) - } + override fun onNewBitrate(bitrate: Long) { + bitrateAdapter.adaptBitrate(bitrate, genericStream.getStreamClient().hasCongestion()) + txtBitrate.text = String.format(Locale.getDefault(), "%.1f mb/s", bitrate / 1000_000f) + } - override fun onDisconnect() { - txtBitrate.text = String() - toast("Disconnected") - } + override fun onDisconnect() { + txtBitrate.text = String() + toast("Disconnected") + } - override fun onAuthError() { - genericStream.stopStream() - bStartStop.setImageResource(R.drawable.live_start) + override fun onAuthError() { + genericStream.stopStream() + bStartStop.setImageResource(R.drawable.live_start) // toast("Auth error") - } + } - override fun onAuthSuccess() { + override fun onAuthSuccess() { // toast("Auth success") - } + } } \ No newline at end of file diff --git a/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt b/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt index 9be91b3ec2..db83956eab 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt @@ -17,9 +17,14 @@ package com.pedro.streamer.rotation import android.annotation.SuppressLint +import android.content.Intent +import android.content.pm.PackageManager import android.graphics.BitmapFactory +import android.media.audiofx.Virtualizer +import android.net.Uri import android.os.Build import android.os.Bundle +import android.provider.Settings import android.view.KeyEvent import android.view.Menu import android.view.MenuItem @@ -28,8 +33,13 @@ import android.view.View import android.view.View.OnTouchListener import android.window.OnBackInvokedDispatcher import androidx.activity.OnBackPressedCallback +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import com.pedro.encoder.input.sources.audio.MicrophoneSource import com.pedro.encoder.input.sources.video.Camera1Source import com.pedro.encoder.input.sources.video.Camera2Source @@ -45,64 +55,214 @@ import com.pedro.streamer.utils.toast import com.pedro.streamer.utils.updateMenuColor import org.greenrobot.eventbus.EventBus - /** * Created by pedro on 22/3/22. */ @RequiresApi(Build.VERSION_CODES.LOLLIPOP) class RotationActivity : AppCompatActivity(), OnTouchListener { - companion object { - private const val TAG = "RotationActivity" - private const val EXIT_TIME_INTERVAL = 2000 - } - - private val cameraFragment = CameraFragment.getInstance() - private val filterMenu: FilterMenu by lazy { FilterMenu(this) } - private var currentVideoSource: MenuItem? = null - private var currentAudioSource: MenuItem? = null - private var currentOrientation: MenuItem? = null - private var currentFilter: MenuItem? = null - private var currentPlatform: MenuItem? = null - private var mClickTime: Long = 0 - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.rotation_activity) - supportFragmentManager.beginTransaction().add(R.id.container, cameraFragment).commit() - - initBackEventListener() - } - - private fun initBackEventListener(){ - onBackPressedListener(true){ - EventBus.getDefault().post(BroadcastBackPressedEvent()) - } - } - - fun handleBackEvent(){ - Logger.d(TAG, "handleBackEvent: ") - if((System.currentTimeMillis() - mClickTime) > EXIT_TIME_INTERVAL){ - toast("Press again to exit app") - mClickTime = System.currentTimeMillis() - }else{ - finish() - } - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.rotation_menu, menu) - val defaultVideoSource = menu.findItem(R.id.video_source_camera2) - val defaultAudioSource = menu.findItem(R.id.audio_source_microphone) - val defaultOrientation = menu.findItem(R.id.orientation_horizontal) - val defaultFilter = menu.findItem(R.id.no_filter) - val defaultPlatform = menu.findItem(R.id.platform_youtube) - currentVideoSource = defaultVideoSource.updateMenuColor(this, currentVideoSource) - currentAudioSource = defaultAudioSource.updateMenuColor(this, currentAudioSource) - currentOrientation = defaultOrientation.updateMenuColor(this, currentOrientation) - currentFilter = defaultFilter.updateMenuColor(this, currentFilter) - currentPlatform = defaultPlatform.updateMenuColor(this, currentPlatform) - return true - } + companion object { + private const val TAG = "RotationActivity" + private const val EXIT_TIME_INTERVAL = 2000 + } + + private val cameraFragment = CameraFragment.getInstance() + private val filterMenu: FilterMenu by lazy { FilterMenu(this) } + private var currentVideoSource: MenuItem? = null + private var currentAudioSource: MenuItem? = null + private var currentOrientation: MenuItem? = null + private var currentFilter: MenuItem? = null + private var currentPlatform: MenuItem? = null + private var mClickTime: Long = 0 + private var requestPermissionLauncher: ActivityResultLauncher>? = null + private var hasCheckPermission: Boolean = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.rotation_activity) + requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()){ result: Map -> + handlePermissionResults(result) + } + checkAndRequestPermissions() + initBackEventListener() + } + + private fun buildRequiredPermissions(): Array { + val perms = mutableListOf() + perms.add(android.Manifest.permission.CAMERA) + perms.add(android.Manifest.permission.RECORD_AUDIO) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + perms.add(android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) + perms.add(android.Manifest.permission.READ_MEDIA_IMAGES) + perms.add(android.Manifest.permission.READ_MEDIA_VIDEO) + } else { + perms.add(android.Manifest.permission.READ_EXTERNAL_STORAGE) + perms.add(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) + } + return perms.toTypedArray() + } + + private fun checkAndRequestPermissions() { + hasCheckPermission = true + val required = buildRequiredPermissions() + val notGranted = required.filter { perm -> + ContextCompat.checkSelfPermission(this, perm) != PackageManager.PERMISSION_GRANTED + } + if(notGranted.isEmpty()){ + onAllPermissionGranted() + }else{ + requestPermissionLauncher?.launch(notGranted.toTypedArray()) + } + } + + private fun handlePermissionResults(results: Map) { + val denied = results.filter { !it.value }.keys + if(denied.isEmpty()){ + onAllPermissionGranted() + return + } + // 检查是否有“永久拒绝”(即用户勾选了 Don't ask again / 不再询问) + val permanentlyDenied = denied.filter { perm -> + isPermissionPermanentlyDenied(perm) + } + if(permanentlyDenied.isNotEmpty()){ + // 有永久拒绝 — 强制引导到设置页 + showPermissionDeniedPermanentlyDialog(permanentlyDenied) + }else{ + // 只是普通拒绝 — 给用户一个明确说明和再次请求的机会或退出 + showPermissionRationaleDialog(denied) + } + } + + /** + * 判断某个权限是否被“永久拒绝”(拒绝并不再询问) + */ + private fun isPermissionPermanentlyDenied(permission: String): Boolean { + val denied = ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_DENIED + // shouldShowRequestPermissionRationale -> false 表示:要么从未请求过,要么永久拒绝 + val shouldShow = ActivityCompat.shouldShowRequestPermissionRationale(this, permission) + return denied && !shouldShow + } + + private fun onAllPermissionGranted(){ + Logger.d(TAG, "onAllPermissionGranted: ") + supportFragmentManager.beginTransaction().add(R.id.container, cameraFragment).commit() + } + + private fun showPermissionRationaleDialog(deniedPermissions: Set) { + Logger.d(TAG, "showPermissionRationaleDialog: deniedPermissions = $deniedPermissions") + val message = buildRationaleMessage(deniedPermissions) + AlertDialog.Builder(this) + .setTitle("需要授权以继续使用") + .setMessage(message) + .setCancelable(false) + .setPositiveButton("重新授权") { _, _ -> + requestPermissionLauncher?.launch(deniedPermissions.toTypedArray()) + } + .setNegativeButton("退出应用") { _, _ -> + finishAffinity() + } + .show() + } + + private fun showPermissionDeniedPermanentlyDialog(permanentlyDenied: List) { + Logger.d(TAG, "showPermissionDeniedPermanentlyDialog: permanentlyDenied = $permanentlyDenied") + val message = buildRationaleMessage(permanentlyDenied.toSet()) + "\n\n请在应用设置中手动开启权限,否则无法使用本应用。" + AlertDialog.Builder(this) + .setTitle("权限被禁用") + .setMessage(message) + .setCancelable(false) + .setPositiveButton("打开设置") { _, _ -> + openAppSettings() + } + .setNegativeButton("退出应用") { _, _ -> + finishAffinity() + } + .show() + } + + private fun openAppSettings() { + hasCheckPermission = false + val intent = Intent( + Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.fromParts("package", packageName, null) + ) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) + } + + private fun buildRationaleMessage(deniedPermissions: Set): String { + val human = deniedPermissions.map { perm -> + when (perm) { + android.Manifest.permission.CAMERA -> "相机(拍摄/扫码)" + android.Manifest.permission.RECORD_AUDIO -> "麦克风(语音/录音)" + android.Manifest.permission.READ_MEDIA_IMAGES, + android.Manifest.permission.READ_MEDIA_VIDEO, + android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED, + android.Manifest.permission.READ_EXTERNAL_STORAGE, + android.Manifest.permission.WRITE_EXTERNAL_STORAGE -> "相册/媒体(读取照片)" + else -> perm + } + } + return "应用需要以下权限:\n• ${human.joinToString("\n• ")}\n以正常运行。" + } + + override fun onResume() { + super.onResume() + // 用户可能从设置页回来,这里再次检查权限 + if(!hasCheckPermission){ + checkAndRequestPermissionsIfNeeded() + } + } + + private fun checkAndRequestPermissionsIfNeeded() { + val required = buildRequiredPermissions() + val notGranted = required.filter { perm -> + ContextCompat.checkSelfPermission(this, perm) != PackageManager.PERMISSION_GRANTED + } + if(notGranted.isEmpty()){ + onAllPermissionGranted() + }else{ + AlertDialog.Builder(this) + .setTitle("权限未就绪") + .setMessage("必要权限仍然未授权,应用无法继续运行。") + .setCancelable(false) + .setPositiveButton("退出应用") { _, _ -> + finishAffinity() + } + .show() + } + } + + private fun initBackEventListener() { + onBackPressedListener(true) { + EventBus.getDefault().post(BroadcastBackPressedEvent()) + } + } + + fun handleBackEvent() { + Logger.d(TAG, "handleBackEvent: ") + if ((System.currentTimeMillis() - mClickTime) > EXIT_TIME_INTERVAL) { + toast("Press again to exit app") + mClickTime = System.currentTimeMillis() + } else { + finish() + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.rotation_menu, menu) + val defaultVideoSource = menu.findItem(R.id.video_source_camera2) + val defaultAudioSource = menu.findItem(R.id.audio_source_microphone) + val defaultOrientation = menu.findItem(R.id.orientation_horizontal) + val defaultFilter = menu.findItem(R.id.no_filter) + val defaultPlatform = menu.findItem(R.id.platform_youtube) + currentVideoSource = defaultVideoSource.updateMenuColor(this, currentVideoSource) + currentAudioSource = defaultAudioSource.updateMenuColor(this, currentAudioSource) + currentOrientation = defaultOrientation.updateMenuColor(this, currentOrientation) + currentFilter = defaultFilter.updateMenuColor(this, currentFilter) + currentPlatform = defaultPlatform.updateMenuColor(this, currentPlatform) + return true + } // override fun onBackPressed() { // super.onBackPressed() @@ -117,81 +277,95 @@ class RotationActivity : AppCompatActivity(), OnTouchListener { // return super.onKeyDown(keyCode, event) // } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - try { - when (item.itemId) { - R.id.video_source_camera1 -> { - currentVideoSource = item.updateMenuColor(this, currentVideoSource) - cameraFragment.genericStream.changeVideoSource(Camera1Source(applicationContext)) - } - R.id.video_source_camera2 -> { - currentVideoSource = item.updateMenuColor(this, currentVideoSource) - cameraFragment.genericStream.changeVideoSource(Camera2Source(applicationContext)) - } - R.id.video_source_camerax -> { - currentVideoSource = item.updateMenuColor(this, currentVideoSource) - cameraFragment.genericStream.changeVideoSource(CameraXSource(applicationContext)) - } - R.id.video_source_camera_uvc -> { - currentVideoSource = item.updateMenuColor(this, currentVideoSource) - cameraFragment.genericStream.changeVideoSource(CameraUvcSource()) - } - R.id.video_source_bitmap -> { - currentVideoSource = item.updateMenuColor(this, currentVideoSource) - val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) - cameraFragment.genericStream.changeVideoSource(BitmapSource(bitmap)) - } - R.id.audio_source_microphone -> { - currentAudioSource = item.updateMenuColor(this, currentAudioSource) - cameraFragment.genericStream.changeAudioSource(MicrophoneSource()) - } - R.id.orientation_horizontal -> { - currentOrientation = item.updateMenuColor(this, currentOrientation) - cameraFragment.setOrientationMode(false) - } - R.id.orientation_vertical -> { - currentOrientation = item.updateMenuColor(this, currentOrientation) - cameraFragment.setOrientationMode(true) - } - R.id.platform_huya -> { - currentPlatform = item.updateMenuColor(this, currentPlatform) - cameraFragment.streamUrl.setText(R.string.stream_url_huya) - } - R.id.platform_tiktok -> { - currentPlatform = item.updateMenuColor(this, currentPlatform) - cameraFragment.streamUrl.setText(R.string.stream_url_tiktok) - } - R.id.platform_youtube -> { - currentPlatform = item.updateMenuColor(this, currentPlatform) - cameraFragment.streamUrl.setText(R.string.stream_url_youtube) - } - else -> { - val result = filterMenu.onOptionsItemSelected(item, cameraFragment.genericStream.getGlInterface()) - if (result) currentFilter = item.updateMenuColor(this, currentFilter) - return result - } - } - } catch (e: IllegalArgumentException) { + override fun onOptionsItemSelected(item: MenuItem): Boolean { + try { + when (item.itemId) { + R.id.video_source_camera1 -> { + currentVideoSource = item.updateMenuColor(this, currentVideoSource) + cameraFragment.genericStream.changeVideoSource(Camera1Source(applicationContext)) + } + + R.id.video_source_camera2 -> { + currentVideoSource = item.updateMenuColor(this, currentVideoSource) + cameraFragment.genericStream.changeVideoSource(Camera2Source(applicationContext)) + } + + R.id.video_source_camerax -> { + currentVideoSource = item.updateMenuColor(this, currentVideoSource) + cameraFragment.genericStream.changeVideoSource(CameraXSource(applicationContext)) + } + + R.id.video_source_camera_uvc -> { + currentVideoSource = item.updateMenuColor(this, currentVideoSource) + cameraFragment.genericStream.changeVideoSource(CameraUvcSource()) + } + + R.id.video_source_bitmap -> { + currentVideoSource = item.updateMenuColor(this, currentVideoSource) + val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) + cameraFragment.genericStream.changeVideoSource(BitmapSource(bitmap)) + } + + R.id.audio_source_microphone -> { + currentAudioSource = item.updateMenuColor(this, currentAudioSource) + cameraFragment.genericStream.changeAudioSource(MicrophoneSource()) + } + + R.id.orientation_horizontal -> { + currentOrientation = item.updateMenuColor(this, currentOrientation) + cameraFragment.setOrientationMode(false) + } + + R.id.orientation_vertical -> { + currentOrientation = item.updateMenuColor(this, currentOrientation) + cameraFragment.setOrientationMode(true) + } + + R.id.platform_huya -> { + currentPlatform = item.updateMenuColor(this, currentPlatform) + cameraFragment.streamUrl.setText(R.string.stream_url_huya) + } + + R.id.platform_tiktok -> { + currentPlatform = item.updateMenuColor(this, currentPlatform) + cameraFragment.streamUrl.setText(R.string.stream_url_tiktok) + } + + R.id.platform_youtube -> { + currentPlatform = item.updateMenuColor(this, currentPlatform) + cameraFragment.streamUrl.setText(R.string.stream_url_youtube) + } + + else -> { + val result = filterMenu.onOptionsItemSelected( + item, + cameraFragment.genericStream.getGlInterface() + ) + if (result) currentFilter = item.updateMenuColor(this, currentFilter) + return result + } + } + } catch (e: IllegalArgumentException) { // toast("Change source error: ${e.message}") + } + return super.onOptionsItemSelected(item) } - return super.onOptionsItemSelected(item) - } - @SuppressLint("ClickableViewAccessibility") - override fun onTouch(view: View, motionEvent: MotionEvent): Boolean { - if (filterMenu.spriteGestureController.spriteTouched(view, motionEvent)) { - filterMenu.spriteGestureController.moveSprite(view, motionEvent) - filterMenu.spriteGestureController.scaleSprite(motionEvent) - return true + @SuppressLint("ClickableViewAccessibility") + override fun onTouch(view: View, motionEvent: MotionEvent): Boolean { + if (filterMenu.spriteGestureController.spriteTouched(view, motionEvent)) { + filterMenu.spriteGestureController.moveSprite(view, motionEvent) + filterMenu.spriteGestureController.scaleSprite(motionEvent) + return true + } + return false } - return false - } } -fun AppCompatActivity.onBackPressedListener(isEnabled: Boolean, callback: () -> Unit){ - onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(isEnabled){ - override fun handleOnBackPressed() { - callback() - } - }) +fun AppCompatActivity.onBackPressedListener(isEnabled: Boolean, callback: () -> Unit) { + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(isEnabled) { + override fun handleOnBackPressed() { + callback() + } + }) } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b24282bd64..826a2388e3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sun Aug 06 22:37:42 CEST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 74c23de7f6e9c7071eac9a43a8265c63156e201f Mon Sep 17 00:00:00 2001 From: HUANG Date: Fri, 17 Oct 2025 10:18:39 +0800 Subject: [PATCH 12/13] [feat] Update huya stream url. --- .../main/java/com/pedro/streamer/rotation/CameraFragment.kt | 2 +- app/src/main/res/layout/fragment_camera.xml | 3 +-- app/src/main/res/layout/rotation_activity.xml | 3 +-- app/src/main/res/values/strings.xml | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt index e32842856d..69de3c700c 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt @@ -124,7 +124,7 @@ class CameraFragment : Fragment(), ConnectChecker { bStartStop = view.findViewById(R.id.b_start_stop) val bRecord = view.findViewById(R.id.b_record) val bSwitchCamera = view.findViewById(R.id.switch_camera) - streamUrl = view.findViewById(R.id.et_rtp_url) + streamUrl = view.findViewById(R.id.et_rtp_url) txtBitrate = view.findViewById(R.id.txt_bitrate) surfaceView = view.findViewById(R.id.surfaceView) diff --git a/app/src/main/res/layout/fragment_camera.xml b/app/src/main/res/layout/fragment_camera.xml index 305d7a67dc..fb9f7b54be 100644 --- a/app/src/main/res/layout/fragment_camera.xml +++ b/app/src/main/res/layout/fragment_camera.xml @@ -8,8 +8,7 @@ + android:id="@+id/surfaceView" /> + android:layout_height="match_parent" /> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ffe51570ef..4509da547a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,7 +9,7 @@ Hohem Live Select your streamer protocol://yourendpoint - rtmp://tx.direct.huya.com/huyalive/1445553152-1445553152-7010649780141597433-2035368522-10057-A-1632294239-1?seq=1735090859329&type=simple + rtmp://tx.direct.huya.com/huyalive/1199649870524-1199649870524-0-2399299864504-10057-A-1760610117-1?seq=1760666707697&type=simple null rtmps://a.rtmps.youtube.com/live2/xpjt-yrb3-dtft-asvs-1erd From 0c46045d4a3679578f6a23935de1f7f6f89d574c Mon Sep 17 00:00:00 2001 From: HUANG Date: Wed, 12 Nov 2025 10:46:50 +0800 Subject: [PATCH 13/13] [feat] Add CameraActivity. --- app/build.gradle.kts | 70 +-- app/src/main/AndroidManifest.xml | 17 +- .../pedro/streamer/file/FromFileActivity.kt | 8 +- .../pedro/streamer/oldapi/OldApiActivity.kt | 8 +- .../pedro/streamer/rotation/CameraActivity.kt | 362 ++++++++++++++ .../pedro/streamer/rotation/CameraFragment.kt | 22 +- .../pedro/streamer/rotation/LiveFragment.kt | 450 ++++++++++++++++++ .../streamer/rotation/RotationActivity.kt | 9 +- .../rotation/annotation/CameraMode.kt | 13 + .../streamer/rotation/annotation/IconState.kt | 16 + .../rotation/annotation/SettingType.kt | 10 + .../pedro/streamer/rotation/bean/IconInfo.kt | 17 + .../rotation/custom/LiveSettingsAdapter.kt | 69 +++ .../rotation/custom/LiveSettingsView.kt | 133 ++++++ .../rotation/custom/OnLiveSettingsListener.kt | 7 + .../streamer/rotation/topmethod/TopMethod.kt | 12 + .../pedro/streamer/screen/ScreenActivity.kt | 10 +- .../res/drawable/bg_b2_black_round_8dp_70.xml | 5 + .../res/drawable/bg_black20_round_2dp.xml | 5 + app/src/main/res/drawable/camera_switch.xml | 2 +- .../main/res/drawable/ic_live_setting_off.xml | 32 ++ .../main/res/drawable/ic_live_setting_on.xml | 32 ++ .../{live_start.xml => ic_live_start.xml} | 2 +- .../{live_stop.xml => ic_live_stop.xml} | 0 ...one_off_icon.xml => ic_microphone_off.xml} | 0 ...crophone_icon.xml => ic_microphone_on.xml} | 0 .../{pause_icon.xml => ic_record_pause.xml} | 0 .../{record_icon.xml => ic_record_start.xml} | 4 +- .../{stop_icon.xml => ic_record_stop.xml} | 0 ...lution_1080.xml => ic_resolution_1080.xml} | 0 ...solution_720.xml => ic_resolution_720.xml} | 0 app/src/main/res/drawable/live_setting.xml | 15 - .../style_item_setting_switcher_thumb.xml | 16 + .../style_item_setting_switcher_track.xml | 17 + app/src/main/res/layout/activity_display.xml | 2 +- .../main/res/layout/activity_from_file.xml | 2 +- app/src/main/res/layout/activity_old_api.xml | 2 +- app/src/main/res/layout/fragment_camera.xml | 10 +- app/src/main/res/layout/fragment_live.xml | 86 ++++ app/src/main/res/layout/item_live_setting.xml | 13 + .../res/layout/view_live_settings_dialog.xml | 174 +++++++ .../main/res/mipmap-hdpi/ic_scan_qrcode.png | Bin 0 -> 368 bytes .../main/res/mipmap-mdpi/ic_scan_qrcode.png | Bin 0 -> 301 bytes .../main/res/mipmap-xhdpi/ic_scan_qrcode.png | Bin 0 -> 461 bytes .../main/res/mipmap-xxhdpi/ic_scan_qrcode.png | Bin 0 -> 670 bytes .../res/mipmap-xxxhdpi/ic_scan_qrcode.png | Bin 0 -> 738 bytes app/src/main/res/values/strings.xml | 3 +- .../input/sources/video/Camera2Source.kt | 354 +++++++------- gradle/libs.versions.toml | 3 +- .../pedro/library/generic/GenericStream.kt | 198 ++++---- 50 files changed, 1839 insertions(+), 371 deletions(-) create mode 100644 app/src/main/java/com/pedro/streamer/rotation/CameraActivity.kt create mode 100644 app/src/main/java/com/pedro/streamer/rotation/LiveFragment.kt create mode 100644 app/src/main/java/com/pedro/streamer/rotation/annotation/CameraMode.kt create mode 100644 app/src/main/java/com/pedro/streamer/rotation/annotation/IconState.kt create mode 100644 app/src/main/java/com/pedro/streamer/rotation/annotation/SettingType.kt create mode 100644 app/src/main/java/com/pedro/streamer/rotation/bean/IconInfo.kt create mode 100644 app/src/main/java/com/pedro/streamer/rotation/custom/LiveSettingsAdapter.kt create mode 100644 app/src/main/java/com/pedro/streamer/rotation/custom/LiveSettingsView.kt create mode 100644 app/src/main/java/com/pedro/streamer/rotation/custom/OnLiveSettingsListener.kt create mode 100644 app/src/main/java/com/pedro/streamer/rotation/topmethod/TopMethod.kt create mode 100644 app/src/main/res/drawable/bg_b2_black_round_8dp_70.xml create mode 100644 app/src/main/res/drawable/bg_black20_round_2dp.xml create mode 100644 app/src/main/res/drawable/ic_live_setting_off.xml create mode 100644 app/src/main/res/drawable/ic_live_setting_on.xml rename app/src/main/res/drawable/{live_start.xml => ic_live_start.xml} (94%) rename app/src/main/res/drawable/{live_stop.xml => ic_live_stop.xml} (100%) rename app/src/main/res/drawable/{microphone_off_icon.xml => ic_microphone_off.xml} (100%) rename app/src/main/res/drawable/{microphone_icon.xml => ic_microphone_on.xml} (100%) rename app/src/main/res/drawable/{pause_icon.xml => ic_record_pause.xml} (100%) rename app/src/main/res/drawable/{record_icon.xml => ic_record_start.xml} (93%) rename app/src/main/res/drawable/{stop_icon.xml => ic_record_stop.xml} (100%) rename app/src/main/res/drawable/{resolution_1080.xml => ic_resolution_1080.xml} (100%) rename app/src/main/res/drawable/{resolution_720.xml => ic_resolution_720.xml} (100%) delete mode 100644 app/src/main/res/drawable/live_setting.xml create mode 100644 app/src/main/res/drawable/style_item_setting_switcher_thumb.xml create mode 100644 app/src/main/res/drawable/style_item_setting_switcher_track.xml create mode 100644 app/src/main/res/layout/fragment_live.xml create mode 100644 app/src/main/res/layout/item_live_setting.xml create mode 100644 app/src/main/res/layout/view_live_settings_dialog.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_scan_qrcode.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_scan_qrcode.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_scan_qrcode.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_scan_qrcode.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_scan_qrcode.png diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b13cb4396b..dc386642d0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,44 +1,46 @@ plugins { - alias(libs.plugins.android.application) - alias(libs.plugins.jetbrains.kotlin) + alias(libs.plugins.android.application) + alias(libs.plugins.jetbrains.kotlin) } android { - namespace = "com.pedro.streamer" - compileSdk = 35 + namespace = "com.pedro.streamer" + compileSdk = 35 - defaultConfig { - applicationId = "com.pedro.streamer" - minSdk = 21 - targetSdk = 35 - versionCode = libs.versions.versionCode.get().toInt() - versionName = libs.versions.versionName.get() - multiDexEnabled = true - } - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + defaultConfig { + applicationId = "com.pedro.streamer" + minSdk = 21 + targetSdk = 35 + versionCode = libs.versions.versionCode.get().toInt() + versionName = libs.versions.versionName.get() + multiDexEnabled = true + } + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } + buildFeatures { + buildConfig = true + viewBinding = true } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } - buildFeatures { - buildConfig = true - } } dependencies { - implementation(project(":library")) - implementation(project(":extra-sources")) - implementation(libs.androidx.constraintlayout) - implementation(libs.androidx.appcompat) - implementation(libs.androidx.multidex) - implementation(libs.firebase.crashlytics.buildtools) - implementation(libs.eventbus) + implementation(project(":library")) + implementation(project(":extra-sources")) + implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.multidex) + implementation(libs.firebase.crashlytics.buildtools) + implementation(libs.eventbus) + implementation(libs.recyclerview) } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 18ef3ca305..855b761fdd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -72,10 +72,25 @@ android:name="com.pedro.streamer.screen.ScreenActivity" android:label="@string/display" /> - + + + + + + + --> + + diff --git a/app/src/main/java/com/pedro/streamer/file/FromFileActivity.kt b/app/src/main/java/com/pedro/streamer/file/FromFileActivity.kt index 371b0a8a5e..be7243af8b 100644 --- a/app/src/main/java/com/pedro/streamer/file/FromFileActivity.kt +++ b/app/src/main/java/com/pedro/streamer/file/FromFileActivity.kt @@ -125,7 +125,7 @@ class FromFileActivity : AppCompatActivity(), ConnectChecker, bRecord.setOnClickListener { if (genericFromFile.isRecording) { genericFromFile.stopRecord() - bRecord.setImageResource(R.drawable.record_icon) + bRecord.setImageResource(R.drawable.ic_record_start) PathUtils.updateGallery(this, recordPath) if (!genericFromFile.isStreaming) ScreenOrientation.unlockScreen(this) } else if (genericFromFile.isStreaming || prepare()) { @@ -134,10 +134,10 @@ class FromFileActivity : AppCompatActivity(), ConnectChecker, if (!folder.exists()) folder.mkdir() val sdf = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()) recordPath = "${folder.absolutePath}/${sdf.format(Date())}.mp4" - bRecord.setImageResource(R.drawable.pause_icon) + bRecord.setImageResource(R.drawable.ic_record_pause) genericFromFile.startRecord(recordPath) { status -> if (status == RecordController.Status.RECORDING) { - bRecord.setImageResource(R.drawable.stop_icon) + bRecord.setImageResource(R.drawable.ic_record_stop) } } ScreenOrientation.lockScreen(this) @@ -164,7 +164,7 @@ class FromFileActivity : AppCompatActivity(), ConnectChecker, genericFromFile.stopAudioDevice() if (genericFromFile.isRecording) { genericFromFile.stopRecord() - bRecord.setImageResource(R.drawable.record_icon) + bRecord.setImageResource(R.drawable.ic_record_start) } if (genericFromFile.isStreaming) { genericFromFile.stopStream() diff --git a/app/src/main/java/com/pedro/streamer/oldapi/OldApiActivity.kt b/app/src/main/java/com/pedro/streamer/oldapi/OldApiActivity.kt index 04ef5c6932..96175c9e89 100644 --- a/app/src/main/java/com/pedro/streamer/oldapi/OldApiActivity.kt +++ b/app/src/main/java/com/pedro/streamer/oldapi/OldApiActivity.kt @@ -89,7 +89,7 @@ class OldApiActivity : AppCompatActivity(), ConnectChecker, TextureView.SurfaceT if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { if (genericCamera1.isRecording) { genericCamera1.stopRecord() - bRecord.setImageResource(R.drawable.record_icon) + bRecord.setImageResource(R.drawable.ic_record_start) PathUtils.updateGallery(this, recordPath) if (!genericCamera1.isStreaming) ScreenOrientation.unlockScreen(this) } else if (genericCamera1.isStreaming || prepare()) { @@ -97,10 +97,10 @@ class OldApiActivity : AppCompatActivity(), ConnectChecker, TextureView.SurfaceT if (!folder.exists()) folder.mkdir() val sdf = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()) recordPath = "${folder.absolutePath}/${sdf.format(Date())}.mp4" - bRecord.setImageResource(R.drawable.pause_icon) + bRecord.setImageResource(R.drawable.ic_record_pause) genericCamera1.startRecord(recordPath) { status -> if (status == RecordController.Status.RECORDING) { - bRecord.setImageResource(R.drawable.stop_icon) + bRecord.setImageResource(R.drawable.ic_record_stop) } } ScreenOrientation.lockScreen(this) @@ -178,7 +178,7 @@ class OldApiActivity : AppCompatActivity(), ConnectChecker, TextureView.SurfaceT override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && genericCamera1.isRecording) { genericCamera1.stopRecord() - bRecord.setBackgroundResource(R.drawable.record_icon) + bRecord.setBackgroundResource(R.drawable.ic_record_start) PathUtils.updateGallery(this, recordPath) } if (genericCamera1.isStreaming) { diff --git a/app/src/main/java/com/pedro/streamer/rotation/CameraActivity.kt b/app/src/main/java/com/pedro/streamer/rotation/CameraActivity.kt new file mode 100644 index 0000000000..61013e1766 --- /dev/null +++ b/app/src/main/java/com/pedro/streamer/rotation/CameraActivity.kt @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2024 pedroSG94. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.pedro.streamer.rotation + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.BitmapFactory +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.Settings +import android.view.Menu +import android.view.MenuItem +import android.view.MotionEvent +import android.view.View +import android.view.View.OnTouchListener +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.RequiresApi +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.pedro.encoder.input.sources.audio.MicrophoneSource +import com.pedro.encoder.input.sources.video.Camera1Source +import com.pedro.encoder.input.sources.video.Camera2Source +import com.pedro.extrasources.BitmapSource +import com.pedro.extrasources.CameraUvcSource +import com.pedro.extrasources.CameraXSource +import com.pedro.streamer.R +import com.pedro.streamer.rotation.eventbus.BroadcastBackPressedEvent +import com.pedro.streamer.rotation.topmethod.onBackPressedListener +import com.pedro.streamer.utils.FilterMenu +import com.pedro.streamer.utils.Logger +import com.pedro.streamer.utils.toast +import com.pedro.streamer.utils.updateMenuColor +import org.greenrobot.eventbus.EventBus + +/** + * Created by pedro on 22/3/22. + */ +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +class CameraActivity : AppCompatActivity(), OnTouchListener { + companion object { + private const val TAG = "CameraActivity" + private const val EXIT_TIME_INTERVAL = 2000 + } + + private val liveFragment = LiveFragment.getInstance() + private val filterMenu: FilterMenu by lazy { FilterMenu(this) } + private var currentVideoSource: MenuItem? = null + private var currentAudioSource: MenuItem? = null + private var currentOrientation: MenuItem? = null + private var currentFilter: MenuItem? = null + private var currentPlatform: MenuItem? = null + private var mClickTime: Long = 0 + private var requestPermissionLauncher: ActivityResultLauncher>? = null + private var hasCheckPermission: Boolean = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.rotation_activity) + requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()){ result: Map -> + handlePermissionResults(result) + } + checkAndRequestPermissions() + initBackEventListener() + } + + private fun buildRequiredPermissions(): Array { + val perms = mutableListOf() + perms.add(Manifest.permission.CAMERA) + perms.add(Manifest.permission.RECORD_AUDIO) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + perms.add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) + perms.add(Manifest.permission.READ_MEDIA_IMAGES) + perms.add(Manifest.permission.READ_MEDIA_VIDEO) + } else { + perms.add(Manifest.permission.READ_EXTERNAL_STORAGE) + perms.add(Manifest.permission.WRITE_EXTERNAL_STORAGE) + } + return perms.toTypedArray() + } + + private fun checkAndRequestPermissions() { + hasCheckPermission = true + val required = buildRequiredPermissions() + val notGranted = required.filter { perm -> + ContextCompat.checkSelfPermission(this, perm) != PackageManager.PERMISSION_GRANTED + } + if(notGranted.isEmpty()){ + onAllPermissionGranted() + }else{ + requestPermissionLauncher?.launch(notGranted.toTypedArray()) + } + } + + private fun handlePermissionResults(results: Map) { + Logger.d(TAG, "handlePermissionResults: results = $results") + val denied: Set = results.filter { !it.value }.keys + if(denied.isEmpty()){ + onAllPermissionGranted() + return + } + // 检查是否有“永久拒绝”(即用户勾选了 Don't ask again / 不再询问) + val permanentlyDenied = denied.filter { perm -> + isPermissionPermanentlyDenied(perm) + } + if(permanentlyDenied.isNotEmpty()){ + // 有永久拒绝 — 强制引导到设置页 + showPermissionDeniedPermanentlyDialog(permanentlyDenied) + }else{ + // 只是普通拒绝 — 给用户一个明确说明和再次请求的机会或退出 + showPermissionRationaleDialog(denied) + } + } + + /** + * 判断某个权限是否被“永久拒绝”(拒绝并不再询问) + */ + private fun isPermissionPermanentlyDenied(permission: String): Boolean { + val denied = ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_DENIED + // shouldShowRequestPermissionRationale -> false 表示:要么从未请求过,要么永久拒绝 + val shouldShow = ActivityCompat.shouldShowRequestPermissionRationale(this, permission) + return denied && !shouldShow + } + + private fun onAllPermissionGranted(){ + Logger.d(TAG, "onAllPermissionGranted: ") + supportFragmentManager.beginTransaction().add(R.id.container, liveFragment).commit() + } + + private fun showPermissionRationaleDialog(deniedPermissions: Set) { + Logger.d(TAG, "showPermissionRationaleDialog: deniedPermissions = $deniedPermissions") + val message = buildRationaleMessage(deniedPermissions) + AlertDialog.Builder(this) + .setTitle("需要授权以继续使用") + .setMessage(message) + .setCancelable(false) + .setPositiveButton("重新授权") { _, _ -> + requestPermissionLauncher?.launch(deniedPermissions.toTypedArray()) + } + .setNegativeButton("退出应用") { _, _ -> + finishAffinity() + } + .show() + } + + private fun showPermissionDeniedPermanentlyDialog(permanentlyDenied: List) { + Logger.d(TAG, "showPermissionDeniedPermanentlyDialog: permanentlyDenied = $permanentlyDenied") + val message = buildRationaleMessage(permanentlyDenied.toSet()) + "\n\n请在应用设置中手动开启权限,否则无法使用本应用。" + AlertDialog.Builder(this) + .setTitle("权限被禁用") + .setMessage(message) + .setCancelable(false) + .setPositiveButton("打开设置") { _, _ -> + openAppSettings() + } + .setNegativeButton("退出应用") { _, _ -> + finishAffinity() + } + .show() + } + + private fun openAppSettings() { + hasCheckPermission = false + val intent = Intent( + Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.fromParts("package", packageName, null) + ) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) + } + + private fun buildRationaleMessage(deniedPermissions: Set): String { + val human = deniedPermissions.map { perm -> + when (perm) { + Manifest.permission.CAMERA -> "相机(拍摄/扫码)" + Manifest.permission.RECORD_AUDIO -> "麦克风(语音/录音)" + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED, + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE -> "相册/媒体(读取照片)" + else -> perm + } + } + return "应用需要以下权限:\n• ${human.joinToString("\n• ")}\n以正常运行。" + } + + override fun onResume() { + super.onResume() + // 用户可能从设置页回来,这里再次检查权限 + if(!hasCheckPermission){ + checkAndRequestPermissionsIfNeeded() + } + } + + private fun checkAndRequestPermissionsIfNeeded() { + val required = buildRequiredPermissions() + val notGranted = required.filter { perm -> + ContextCompat.checkSelfPermission(this, perm) != PackageManager.PERMISSION_GRANTED + } + if(notGranted.isEmpty()){ + onAllPermissionGranted() + }else{ + AlertDialog.Builder(this) + .setTitle("权限未就绪") + .setMessage("必要权限仍然未授权,应用无法继续运行。") + .setCancelable(false) + .setPositiveButton("退出应用") { _, _ -> + finishAffinity() + } + .show() + } + } + + private fun initBackEventListener() { + onBackPressedListener(true) { + EventBus.getDefault().post(BroadcastBackPressedEvent()) + } + } + + fun handleBackEvent() { + Logger.d(TAG, "handleBackEvent: ") + if ((System.currentTimeMillis() - mClickTime) > EXIT_TIME_INTERVAL) { + toast("Press again to exit app") + mClickTime = System.currentTimeMillis() + } else { + finish() + } + } + + /*override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.rotation_menu, menu) + val defaultVideoSource = menu.findItem(R.id.video_source_camera2) + val defaultAudioSource = menu.findItem(R.id.audio_source_microphone) + val defaultOrientation = menu.findItem(R.id.orientation_horizontal) + val defaultFilter = menu.findItem(R.id.no_filter) + val defaultPlatform = menu.findItem(R.id.platform_youtube) + currentVideoSource = defaultVideoSource.updateMenuColor(this, currentVideoSource) + currentAudioSource = defaultAudioSource.updateMenuColor(this, currentAudioSource) + currentOrientation = defaultOrientation.updateMenuColor(this, currentOrientation) + currentFilter = defaultFilter.updateMenuColor(this, currentFilter) + currentPlatform = defaultPlatform.updateMenuColor(this, currentPlatform) + return true + }*/ + +// override fun onBackPressed() { +// super.onBackPressed() +// Logger.d(TAG, "onBackPressed: ") +// } + +// override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { +// if(keyCode == KeyEvent.KEYCODE_BACK){ +// Logger.d(TAG, "onKeyDown: ") +// +// } +// return super.onKeyDown(keyCode, event) +// } + + /*override fun onOptionsItemSelected(item: MenuItem): Boolean { + try { + when (item.itemId) { + R.id.video_source_camera1 -> { + currentVideoSource = item.updateMenuColor(this, currentVideoSource) + liveFragment.genericStream.changeVideoSource(Camera1Source(applicationContext)) + } + + R.id.video_source_camera2 -> { + currentVideoSource = item.updateMenuColor(this, currentVideoSource) + liveFragment.genericStream.changeVideoSource(Camera2Source(applicationContext)) + } + + R.id.video_source_camerax -> { + currentVideoSource = item.updateMenuColor(this, currentVideoSource) + liveFragment.genericStream.changeVideoSource(CameraXSource(applicationContext)) + } + + R.id.video_source_camera_uvc -> { + currentVideoSource = item.updateMenuColor(this, currentVideoSource) + liveFragment.genericStream.changeVideoSource(CameraUvcSource()) + } + + R.id.video_source_bitmap -> { + currentVideoSource = item.updateMenuColor(this, currentVideoSource) + val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) + liveFragment.genericStream.changeVideoSource(BitmapSource(bitmap)) + } + + R.id.audio_source_microphone -> { + currentAudioSource = item.updateMenuColor(this, currentAudioSource) + liveFragment.genericStream.changeAudioSource(MicrophoneSource()) + } + + R.id.orientation_horizontal -> { + currentOrientation = item.updateMenuColor(this, currentOrientation) + liveFragment.setOrientationMode(false) + } + + R.id.orientation_vertical -> { + currentOrientation = item.updateMenuColor(this, currentOrientation) + liveFragment.setOrientationMode(true) + } + + R.id.platform_huya -> { + currentPlatform = item.updateMenuColor(this, currentPlatform) + liveFragment.streamUrl.setText(R.string.stream_url_huya) + } + + R.id.platform_tiktok -> { + currentPlatform = item.updateMenuColor(this, currentPlatform) + liveFragment.streamUrl.setText(R.string.stream_url_tiktok) + } + + R.id.platform_youtube -> { + currentPlatform = item.updateMenuColor(this, currentPlatform) + liveFragment.streamUrl.setText(R.string.stream_url_youtube) + } + + else -> { + val result = filterMenu.onOptionsItemSelected( + item, + liveFragment.genericStream.getGlInterface() + ) + if (result) currentFilter = item.updateMenuColor(this, currentFilter) + return result + } + } + } catch (e: IllegalArgumentException) { +// toast("Change source error: ${e.message}") + } + return super.onOptionsItemSelected(item) + }*/ + + @SuppressLint("ClickableViewAccessibility") + override fun onTouch(view: View, motionEvent: MotionEvent): Boolean { + if (filterMenu.spriteGestureController.spriteTouched(view, motionEvent)) { + filterMenu.spriteGestureController.moveSprite(view, motionEvent) + filterMenu.spriteGestureController.scaleSprite(motionEvent) + return true + } + return false + } +} + diff --git a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt index 69de3c700c..5a884c69ce 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt @@ -33,19 +33,15 @@ import com.pedro.common.ConnectChecker import com.pedro.encoder.input.sources.video.Camera1Source import com.pedro.encoder.input.sources.video.Camera2Source import com.pedro.extrasources.CameraXSource -import com.pedro.library.base.recording.RecordController import com.pedro.library.generic.GenericStream import com.pedro.library.util.BitrateAdapter import com.pedro.streamer.R import com.pedro.streamer.rotation.eventbus.BroadcastBackPressedEvent import com.pedro.streamer.utils.Logger -import com.pedro.streamer.utils.PathUtils import com.pedro.streamer.utils.toast import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode -import java.text.SimpleDateFormat -import java.util.Date import java.util.Locale /** @@ -157,10 +153,10 @@ class CameraFragment : Fragment(), ConnectChecker { bStartStop.setOnClickListener { if (!genericStream.isStreaming) { genericStream.startStream(streamUrl.text.toString()) - bStartStop.setImageResource(R.drawable.live_stop) + bStartStop.setImageResource(R.drawable.ic_live_stop) } else { genericStream.stopStream() - bStartStop.setImageResource(R.drawable.live_start) + bStartStop.setImageResource(R.drawable.ic_live_start) } } @@ -172,24 +168,24 @@ class CameraFragment : Fragment(), ConnectChecker { width = 960 height = 720 vBitrate = 2000 * 1000 - bRecord.setImageResource(R.drawable.resolution_720) + bRecord.setImageResource(R.drawable.ic_resolution_720) genericStream.setVideoResolution(width, height) genericStream.setVideoBitRate(vBitrate) if (genericStream.isStreaming) { genericStream.stopStream() - bStartStop.setImageResource(R.drawable.live_start) + bStartStop.setImageResource(R.drawable.ic_live_start) } } else { mCurResolution = Resolution._1080P width = 1440 height = 1080 vBitrate = 2500 * 1000 - bRecord.setImageResource(R.drawable.resolution_1080) + bRecord.setImageResource(R.drawable.ic_resolution_1080) genericStream.setVideoResolution(width, height) genericStream.setVideoBitRate(vBitrate) if (genericStream.isStreaming) { genericStream.stopStream() - bStartStop.setImageResource(R.drawable.live_start) + bStartStop.setImageResource(R.drawable.ic_live_start) } } when (val source = genericStream.videoSource) { @@ -228,7 +224,7 @@ class CameraFragment : Fragment(), ConnectChecker { fun handleMessageEvent(event: BroadcastBackPressedEvent) { if (genericStream.isStreaming) { genericStream.stopStream() - bStartStop.setImageResource(R.drawable.live_start) + bStartStop.setImageResource(R.drawable.ic_live_start) } else { (requireActivity() as RotationActivity).handleBackEvent() } @@ -287,7 +283,7 @@ class CameraFragment : Fragment(), ConnectChecker { // toast("Retry") } else { genericStream.stopStream() - bStartStop.setImageResource(R.drawable.live_start) + bStartStop.setImageResource(R.drawable.ic_live_start) // toast("Failed: $reason") } } @@ -304,7 +300,7 @@ class CameraFragment : Fragment(), ConnectChecker { override fun onAuthError() { genericStream.stopStream() - bStartStop.setImageResource(R.drawable.live_start) + bStartStop.setImageResource(R.drawable.ic_live_start) // toast("Auth error") } diff --git a/app/src/main/java/com/pedro/streamer/rotation/LiveFragment.kt b/app/src/main/java/com/pedro/streamer/rotation/LiveFragment.kt new file mode 100644 index 0000000000..a7c3d206f6 --- /dev/null +++ b/app/src/main/java/com/pedro/streamer/rotation/LiveFragment.kt @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2024 pedroSG94. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.pedro.streamer.rotation + +import android.annotation.SuppressLint +import android.graphics.drawable.Icon +import android.os.Build +import android.os.Bundle +import android.util.SparseIntArray +import android.view.LayoutInflater +import android.view.SurfaceHolder +import android.view.SurfaceView +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.RequiresApi +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView +import com.pedro.common.ConnectChecker +import com.pedro.encoder.input.sources.video.Camera1Source +import com.pedro.encoder.input.sources.video.Camera2Source +import com.pedro.extrasources.CameraXSource +import com.pedro.library.generic.GenericStream +import com.pedro.library.util.BitrateAdapter +import com.pedro.streamer.R +import com.pedro.streamer.rotation.annotation.CameraMode +import com.pedro.streamer.rotation.annotation.IconState +import com.pedro.streamer.rotation.annotation.SettingType +import com.pedro.streamer.rotation.bean.IconInfo +import com.pedro.streamer.rotation.custom.LiveSettingsAdapter +import com.pedro.streamer.rotation.custom.LiveSettingsView +import com.pedro.streamer.rotation.eventbus.BroadcastBackPressedEvent +import com.pedro.streamer.utils.Logger +import com.pedro.streamer.utils.toast +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import java.util.Locale +import com.pedro.streamer.rotation.custom.OnLiveSettingsListener +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +/** + * Example code to stream using StreamBase. This is the recommend way to use the library. + * Necessary API 21+ + * This mode allow you stream using custom Video/Audio sources, attach a preview or not dynamically, support device rotation, etc. + * + * Check Menu to use filters, video and audio sources, and orientation + * + * Orientation horizontal (by default) means that you want stream with vertical resolution + * (with = 640, height = 480 and rotation = 0) The stream/record result will be 640x480 resolution + * + * Orientation vertical means that you want stream with vertical resolution + * (with = 640, height = 480 and rotation = 90) The stream/record result will be 480x640 resolution + * + * More documentation see: + * [com.pedro.library.base.StreamBase] + * Support RTMP, RTSP and SRT with commons features + * [com.pedro.library.generic.GenericStream] + * Support RTSP with all RTSP features + * [com.pedro.library.rtsp.RtspStream] + * Support RTMP with all RTMP features + * [com.pedro.library.rtmp.RtmpStream] + * Support SRT with all SRT features + * [com.pedro.library.srt.SrtStream] + */ +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +class LiveFragment: Fragment(), ConnectChecker { + companion object { + fun getInstance(): LiveFragment = LiveFragment() + private const val TAG = "CameraFragment" + } + + enum class Resolution { + _1080P, _720P + } + + val genericStream: GenericStream by lazy { + GenericStream(requireContext(), this).apply { + getGlInterface().autoHandleOrientation = true + getStreamClient().setBitrateExponentialFactor(0.5f) + } + } + private lateinit var surfaceView: SurfaceView + private lateinit var liveStartStop: ImageView + private lateinit var bitrateText: TextView + private lateinit var recyclerView: RecyclerView + private var liveSettingView: LiveSettingsView? = null + // private val width = 640 +// private val height = 480 + private var width = 1440 + private var height = 1080 + + // private val vBitrate = 1200 * 1000 + private var vBitrate = 2500 * 1000 + private var rotation = 0 + private val sampleRate = 32000 + private val isStereo = true + private val aBitrate = 128 * 1000 + private var recordPath = "" + private var mCurResolution = Resolution._1080P + private var liveSettings: List? = null + private val liveSettingsAdapter: LiveSettingsAdapter by lazy { LiveSettingsAdapter( + onIconClick = { handleSettingIconClick(it) } + ) } + private var topLayout: FrameLayout? = null + //Bitrate adapter used to change the bitrate on fly depend of the bandwidth. + private val bitrateAdapter = BitrateAdapter { + genericStream.setVideoBitrateOnFly(it) + }.apply { + setMaxBitrate(vBitrate + aBitrate) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Logger.d(TAG, "onCreate: ") + if (EventBus.getDefault().isRegistered(this).not()) { + EventBus.getDefault().register(this) + } + prepare() + genericStream.getStreamClient().setReTries(10) + } + + @SuppressLint("ClickableViewAccessibility") + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + val view = inflater.inflate(R.layout.fragment_live, container, false) + liveStartStop = view.findViewById(R.id.live_start_stop) + val liveRecord = view.findViewById(R.id.live_record) + val liveSwitchLens = view.findViewById(R.id.live_switch_lens) + recyclerView = view.findViewById(R.id.live_recycler_view) + recyclerView.adapter = liveSettingsAdapter.also { it.submitList(liveSettings) } + bitrateText = view.findViewById(R.id.live_bitrate) + surfaceView = view.findViewById(R.id.live_surface_view) + (requireActivity() as? RotationActivity)?.let { + surfaceView.setOnTouchListener(it) + } + topLayout = view.findViewById(R.id.top_layout) + showLiveSettingsDialog() + + surfaceView.holder.addCallback(object : SurfaceHolder.Callback2 { + override fun surfaceCreated(holder: SurfaceHolder) { + Logger.d(TAG, "surfaceCreated: ") + holder.setKeepScreenOn(true) + if (!genericStream.isOnPreview) genericStream.startPreview(surfaceView) + } + + override fun surfaceChanged( + holder: SurfaceHolder, + format: Int, + width: Int, + height: Int + ) { + Logger.d(TAG, "surfaceChanged: width = $width; height = $height") + genericStream.getGlInterface().setPreviewResolution(width, height) +// genericStream.getGlInterface().setPreviewResolution(height, width) + } + + override fun surfaceDestroyed(holder: SurfaceHolder) { + Logger.d(TAG, "surfaceDestroyed: ") + if (genericStream.isOnPreview) genericStream.stopPreview() + } + + override fun surfaceRedrawNeeded(holder: SurfaceHolder) { + Logger.d(TAG, "surfaceRedrawNeeded: ") + } + }) + + liveStartStop.setOnClickListener { + Logger.d(TAG, "onCreateView: liveStartStop clicked") + if (!genericStream.isStreaming) { + genericStream.startStream("streamUrl.text.toString()") + liveStartStop.setImageResource(R.drawable.ic_live_stop) + } else { + genericStream.stopStream() + liveStartStop.setImageResource(R.drawable.ic_live_start) + } + } + + liveRecord.setOnClickListener { + Logger.d(TAG, "onCreateView: liveRecord clicked") + if (mCurResolution == Resolution._1080P) { + mCurResolution = Resolution._720P + width = 960 + height = 720 + vBitrate = 2000 * 1000 + liveRecord.setImageResource(R.drawable.ic_resolution_720) + genericStream.setVideoResolution(width, height) + genericStream.setVideoBitRate(vBitrate) + if (genericStream.isStreaming) { + genericStream.stopStream() + liveStartStop.setImageResource(R.drawable.ic_live_start) + } + } else { + mCurResolution = Resolution._1080P + width = 1440 + height = 1080 + vBitrate = 2500 * 1000 + liveRecord.setImageResource(R.drawable.ic_resolution_1080) + genericStream.setVideoResolution(width, height) + genericStream.setVideoBitRate(vBitrate) + if (genericStream.isStreaming) { + genericStream.stopStream() + liveStartStop.setImageResource(R.drawable.ic_live_start) + } + } + when (val source = genericStream.videoSource) { + is Camera2Source -> source.changeResolution() + } + +// if (!genericStream.isRecording) { +// val folder = PathUtils.getRecordPath() +// if (!folder.exists()) folder.mkdir() +// val sdf = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()) +// recordPath = "${folder.absolutePath}/${sdf.format(Date())}.mp4" +// genericStream.startRecord(recordPath) { status -> +// if (status == RecordController.Status.RECORDING) { +// bRecord.setImageResource(R.drawable.stop_icon) +// } +// } +// bRecord.setImageResource(R.drawable.pause_icon) +// } else { +// genericStream.stopRecord() +// bRecord.setImageResource(R.drawable.record_icon) +// PathUtils.updateGallery(requireContext(), recordPath) +// } + } + + liveSwitchLens.setOnClickListener { + Logger.d(TAG, "onCreateView: liveSwitchLens clicked") + when (val source = genericStream.videoSource) { + is Camera1Source -> source.switchCamera() + is Camera2Source -> source.switchCamera() + is CameraXSource -> source.switchCamera() + } + } + return view + } + + override fun onDestroy() { + super.onDestroy() + liveSettingView?.let { + it.setLiveSettingsListener(null) + topLayout?.removeView(it) + liveSettingView = null + } + genericStream.release() + if (EventBus.getDefault().isRegistered(this)) { + EventBus.getDefault().unregister(this) + } + } + + private fun showLiveSettingsDialog() { + if(liveSettingView != null && liveSettingView?.parent != null){ + liveSettingView?.let { + it.isVisible = true + it.bringToFront() + it.requestLayout() + } + return + } + val v = LiveSettingsView(requireContext()) + v.setLiveSettingsListener(object : OnLiveSettingsListener { + override fun onScanCodeClicked(type: Int) { + showScanCodeView() + } + + override fun onMobileNetworkChecked(isChecked: Boolean) { + + } + }) + val lp = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT + ) + viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { + topLayout?.addView(v, lp) + v.bringToFront() + v.requestLayout() + } + liveSettingView = v + } + + private fun hideLiveSettingsDialog() { + liveSettingView?.let { + it.isVisible = false + it.clearEditTextFocus() + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun handleMessageEvent(event: BroadcastBackPressedEvent) { + if (genericStream.isStreaming) { + genericStream.stopStream() + liveStartStop.setImageResource(R.drawable.ic_live_start) + } else { + (requireActivity() as CameraActivity).handleBackEvent() + } + } + + fun setOrientationMode(isVertical: Boolean) { + val wasOnPreview = genericStream.isOnPreview + Logger.d(TAG, "setOrientationMode: isVertical = $isVertical, wasOnPreview = $wasOnPreview") + genericStream.release() + rotation = if (isVertical) 90 else 0 + prepare() + if (wasOnPreview) genericStream.startPreview(surfaceView) + } + + private fun prepare() { + Logger.d(TAG, "prepare: ") + prepareLiveSettings() + val prepared = try { + genericStream.prepareVideo(width, height, vBitrate, rotation = rotation) + && genericStream.prepareAudio(sampleRate, isStereo, aBitrate) + } catch (e: IllegalArgumentException) { + false + } + if (!prepared) { + Logger.d(TAG, "prepare: Audio or Video configuration failed") + activity?.finish() + } + } + + private fun prepareLiveSettings(){ + @SettingType + val settingTypes = listOf( + SettingType.LIVE, + SettingType.RESOLUTION, + SettingType.MICROPHONE + ) + liveSettings = settingTypes.map { type -> + val icons = getIconsBySettingType(type) + IconInfo(CameraMode.LIVE, type, icons.keyAt(0), icons) + } + } + + private fun getIconsBySettingType(@SettingType settingType: Int): SparseIntArray { + return when (settingType) { + SettingType.LIVE -> SparseIntArray().apply { + put(IconState.OFF, R.drawable.ic_live_setting_off) + put(IconState.ON, R.drawable.ic_live_setting_on) + } + SettingType.RESOLUTION -> SparseIntArray().apply { + put(IconState.ON, R.drawable.ic_resolution_720) + put(IconState.ON, R.drawable.ic_resolution_1080) + } + SettingType.MICROPHONE -> SparseIntArray().apply { + put(IconState.ON, R.drawable.ic_microphone_on) + put(IconState.OFF, R.drawable.ic_microphone_off) + } + else -> SparseIntArray() + } + } + + private fun handleSettingIconClick(iconInfo: IconInfo){ + when (iconInfo.mode) { + CameraMode.LIVE -> { + when (iconInfo.type) { + SettingType.LIVE -> { + when (iconInfo.state) { + IconState.ON -> { + showLiveSettingsDialog() + } + IconState.OFF -> { + hideLiveSettingsDialog() + } + } + } + SettingType.RESOLUTION -> { + + } + SettingType.MICROPHONE -> { + when (iconInfo.state) { + IconState.ON -> { + + } + IconState.OFF -> { + + } + } + } + } + } + } + } + + + override fun onConnectionStarted(url: String) { + } + + override fun onConnectionSuccess() { + toast("Connected") + } + + override fun onConnectionFailed(reason: String) { + if (genericStream.getStreamClient().reTry(5000, reason, null)) { +// toast("Retry") + } else { + genericStream.stopStream() + liveStartStop.setImageResource(R.drawable.ic_live_start) +// toast("Failed: $reason") + } + } + + override fun onNewBitrate(bitrate: Long) { + bitrateAdapter.adaptBitrate(bitrate, genericStream.getStreamClient().hasCongestion()) + bitrateText.text = String.format(Locale.getDefault(), "%.1f mb/s", bitrate / 1000_000f) + } + + override fun onDisconnect() { + bitrateText.text = String() + toast("Disconnected") + } + + override fun onAuthError() { + genericStream.stopStream() + liveStartStop.setImageResource(R.drawable.ic_live_start) +// toast("Auth error") + } + + override fun onAuthSuccess() { +// toast("Auth success") + } + + private fun showScanCodeView() { + viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt b/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt index db83956eab..9b6c7f027a 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt @@ -49,6 +49,7 @@ import com.pedro.extrasources.CameraUvcSource import com.pedro.extrasources.CameraXSource import com.pedro.streamer.R import com.pedro.streamer.rotation.eventbus.BroadcastBackPressedEvent +import com.pedro.streamer.rotation.topmethod.onBackPressedListener import com.pedro.streamer.utils.FilterMenu import com.pedro.streamer.utils.Logger import com.pedro.streamer.utils.toast @@ -360,12 +361,4 @@ class RotationActivity : AppCompatActivity(), OnTouchListener { } return false } -} - -fun AppCompatActivity.onBackPressedListener(isEnabled: Boolean, callback: () -> Unit) { - onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(isEnabled) { - override fun handleOnBackPressed() { - callback() - } - }) } \ No newline at end of file diff --git a/app/src/main/java/com/pedro/streamer/rotation/annotation/CameraMode.kt b/app/src/main/java/com/pedro/streamer/rotation/annotation/CameraMode.kt new file mode 100644 index 0000000000..36c79c596b --- /dev/null +++ b/app/src/main/java/com/pedro/streamer/rotation/annotation/CameraMode.kt @@ -0,0 +1,13 @@ +package com.pedro.streamer.rotation.annotation + +import androidx.annotation.IntDef + +@IntDef(CameraMode.PHOTO, CameraMode.VIDEO, CameraMode.LIVE) +@Retention(AnnotationRetention.SOURCE) +annotation class CameraMode { + companion object { + const val PHOTO = 0 + const val VIDEO = 1 + const val LIVE = 2 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pedro/streamer/rotation/annotation/IconState.kt b/app/src/main/java/com/pedro/streamer/rotation/annotation/IconState.kt new file mode 100644 index 0000000000..b6c02130d8 --- /dev/null +++ b/app/src/main/java/com/pedro/streamer/rotation/annotation/IconState.kt @@ -0,0 +1,16 @@ +package com.pedro.streamer.rotation.annotation + +import androidx.annotation.IntDef + +@IntDef(IconState.SHOW, IconState.GONE, IconState.ON, IconState.OFF, IconState.ENABLE, IconState.DISABLE) +@Retention(AnnotationRetention.SOURCE) +annotation class IconState { + companion object { + const val SHOW = 0x00 + const val GONE = 0x01 + const val ON = 0x02 + const val OFF = 0x03 + const val ENABLE = 0x04 + const val DISABLE = 0x05 + } +} diff --git a/app/src/main/java/com/pedro/streamer/rotation/annotation/SettingType.kt b/app/src/main/java/com/pedro/streamer/rotation/annotation/SettingType.kt new file mode 100644 index 0000000000..d107d09a61 --- /dev/null +++ b/app/src/main/java/com/pedro/streamer/rotation/annotation/SettingType.kt @@ -0,0 +1,10 @@ +package com.pedro.streamer.rotation.annotation + +@Retention(AnnotationRetention.SOURCE) +annotation class SettingType { + companion object { + const val LIVE = 0x00 + const val RESOLUTION = 0x01 + const val MICROPHONE = 0x02 + } +} diff --git a/app/src/main/java/com/pedro/streamer/rotation/bean/IconInfo.kt b/app/src/main/java/com/pedro/streamer/rotation/bean/IconInfo.kt new file mode 100644 index 0000000000..27c5a0c4a0 --- /dev/null +++ b/app/src/main/java/com/pedro/streamer/rotation/bean/IconInfo.kt @@ -0,0 +1,17 @@ +package com.pedro.streamer.rotation.bean + +import android.util.SparseIntArray +import androidx.annotation.IntDef +import com.pedro.streamer.rotation.annotation.CameraMode +import com.pedro.streamer.rotation.annotation.IconState +import com.pedro.streamer.rotation.annotation.SettingType + +data class IconInfo( + @CameraMode + val mode: Int, + @SettingType + val type: Int, + @IconState + var state: Int, + val images: SparseIntArray, +) diff --git a/app/src/main/java/com/pedro/streamer/rotation/custom/LiveSettingsAdapter.kt b/app/src/main/java/com/pedro/streamer/rotation/custom/LiveSettingsAdapter.kt new file mode 100644 index 0000000000..01202e658a --- /dev/null +++ b/app/src/main/java/com/pedro/streamer/rotation/custom/LiveSettingsAdapter.kt @@ -0,0 +1,69 @@ +package com.pedro.streamer.rotation.custom + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.pedro.streamer.R +import com.pedro.streamer.rotation.bean.IconInfo +import com.pedro.streamer.utils.Logger +import android.widget.ImageView +import com.pedro.streamer.rotation.annotation.IconState + +class LiveSettingsAdapter( + private val onIconClick: (IconInfo) -> Unit +): ListAdapter(DIFF) { + companion object { + private const val TAG = "LiveSettingsAdapter" + private val DIFF = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: IconInfo, + newItem: IconInfo + ): Boolean { + return oldItem.type == newItem.type && oldItem.state == newItem.state + } + + override fun areContentsTheSame( + oldItem: IconInfo, + newItem: IconInfo + ): Boolean { + return oldItem == newItem + } + } + } + + inner class IconVH(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val imageView = itemView.findViewById(R.id.item_live_setting_icon) + + fun bind(icon: IconInfo) { + Logger.d(TAG, "bind: icon = $icon") + imageView.setImageResource(icon.images[icon.state]) + imageView.setOnClickListener { + icon.state = if(icon.state == IconState.ON) IconState.OFF else IconState.ON + imageView.setImageResource(icon.images[icon.state]) + onIconClick(icon) + } + } + fun onRecycled() { + Logger.d(TAG, "onRecycled: hashcode = ${hashCode()}") + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IconVH { + val v = LayoutInflater.from(parent.context).inflate(R.layout.item_live_setting, parent, false) + return IconVH(v) + } + + override fun onBindViewHolder(holder: IconVH, position: Int) { + Logger.d(TAG, "onBindViewHolder: position = $position") + holder.bind(getItem(position)) + } + + override fun onViewRecycled(holder: IconVH) { + super.onViewRecycled(holder) + Logger.d(TAG, "onViewRecycled: hashcode = ${holder.hashCode()}") + holder.onRecycled() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pedro/streamer/rotation/custom/LiveSettingsView.kt b/app/src/main/java/com/pedro/streamer/rotation/custom/LiveSettingsView.kt new file mode 100644 index 0000000000..bec3e039a2 --- /dev/null +++ b/app/src/main/java/com/pedro/streamer/rotation/custom/LiveSettingsView.kt @@ -0,0 +1,133 @@ +package com.pedro.streamer.rotation.custom + +import android.app.Activity +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import androidx.annotation.IntDef +import androidx.constraintlayout.widget.ConstraintLayout +import com.pedro.streamer.databinding.ViewLiveSettingsDialogBinding + +class LiveSettingsView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr), View.OnClickListener { + companion object { + private const val TAG = "LiveSettingsView" + } + + private val binding: ViewLiveSettingsDialogBinding = + ViewLiveSettingsDialogBinding.inflate(LayoutInflater.from(context), this, true) + + private var listener: OnLiveSettingsListener? = null + @ScanType + private var scanType: Int = ScanType.PATH + + @IntDef(ScanType.PATH, ScanType.CODE) + @Retention(AnnotationRetention.SOURCE) + annotation class ScanType { + companion object { + const val PATH = 0 + const val CODE = 1 + } + } + + init { + // 初始化 Switch 状态并设置点击监听 +// binding.itemLiveSwitch.isChecked = PreferencesUtils.getBoolean(context, SettingsConstant.KEY_USE_MOBILE_NETWORK) + binding.ivLiveSettingsPathScan.setOnClickListener(this) + binding.ivLiveSettingsCodeScan.setOnClickListener(this) + binding.itemLiveSwitch.setOnClickListener(this) + } + + /** + * 适配竖屏/横屏的位移(保持和你原实现一致) + */ + fun matchPortraitScreen() { + translationY = 80f + translationX = 0f + } + + fun matchLandScreen() { + translationX = 0f + translationY = 0f + } + + override fun onClick(v: View) { + when (v.id) { + binding.ivLiveSettingsPathScan.id -> { + scanType = ScanType.PATH + listener?.onScanCodeClicked(scanType) + } + binding.ivLiveSettingsCodeScan.id -> { + scanType = ScanType.CODE + listener?.onScanCodeClicked(scanType) + } + binding.itemLiveSwitch.id -> { + val isChecked = binding.itemLiveSwitch.isChecked + //PreferencesUtils.putBoolean(context, SettingsConstant.KEY_USE_MOBILE_NETWORK, isChecked) + listener?.onMobileNetworkChecked(isChecked) + } + } + } + + fun setLiveSettingsListener(l: OnLiveSettingsListener?) { + listener = l + } + + /** + * 接收二维码扫描结果并填写到对应 EditText(如果值相同则忽略) + */ + fun receiveQrScanResult(result: String) { + when (scanType) { + ScanType.PATH -> { + val current = binding.etLiveSettingsPath.text?.toString() + if (current != result) { + binding.etLiveSettingsPath.setText(result) + } + } + ScanType.CODE -> { + val current = binding.etLiveSettingsCode.text?.toString() + if (current != result) { + binding.etLiveSettingsCode.setText(result) + } + } + } + } + + /** + * 拼接 live url。会做空值保护与多余斜杠清理 + * + * 例如: + * etPath: "https://example.com/stream" + * etCode: "room1" + * -> "https://example.com/stream/room1" + */ + fun appendLiveUrl(): String { + val path = binding.etLiveSettingsPath.text?.toString()?.trim().orEmpty() + .trimEnd('/') + val code = binding.etLiveSettingsCode.text?.toString()?.trim().orEmpty() + .trimStart('/') + + return when { + path.isBlank() && code.isBlank() -> "" + path.isBlank() -> code + code.isBlank() -> path + else -> "$path/$code" + } + } + + /** + * 清除 EditText 焦点并隐藏键盘(安全地从 context 转换为 Activity) + */ + fun clearEditTextFocus() { + val activity = context as? Activity ?: return + val current = activity.currentFocus + if (current == binding.etLiveSettingsPath || current == binding.etLiveSettingsCode) { + current.clearFocus() + //PubUtils.getInstance().hideKeyboard(context, current) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pedro/streamer/rotation/custom/OnLiveSettingsListener.kt b/app/src/main/java/com/pedro/streamer/rotation/custom/OnLiveSettingsListener.kt new file mode 100644 index 0000000000..0a1e9515aa --- /dev/null +++ b/app/src/main/java/com/pedro/streamer/rotation/custom/OnLiveSettingsListener.kt @@ -0,0 +1,7 @@ +package com.pedro.streamer.rotation.custom + +interface OnLiveSettingsListener { + fun onScanCodeClicked(@LiveSettingsView.ScanType type: Int) + + fun onMobileNetworkChecked(isChecked: Boolean) +} \ No newline at end of file diff --git a/app/src/main/java/com/pedro/streamer/rotation/topmethod/TopMethod.kt b/app/src/main/java/com/pedro/streamer/rotation/topmethod/TopMethod.kt new file mode 100644 index 0000000000..1508b9ae35 --- /dev/null +++ b/app/src/main/java/com/pedro/streamer/rotation/topmethod/TopMethod.kt @@ -0,0 +1,12 @@ +package com.pedro.streamer.rotation.topmethod + +import androidx.activity.OnBackPressedCallback +import androidx.appcompat.app.AppCompatActivity + +fun AppCompatActivity.onBackPressedListener(isEnabled: Boolean, callback: () -> Unit) { + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(isEnabled) { + override fun handleOnBackPressed() { + callback() + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/pedro/streamer/screen/ScreenActivity.kt b/app/src/main/java/com/pedro/streamer/screen/ScreenActivity.kt index f327976203..00b119a32b 100644 --- a/app/src/main/java/com/pedro/streamer/screen/ScreenActivity.kt +++ b/app/src/main/java/com/pedro/streamer/screen/ScreenActivity.kt @@ -93,9 +93,9 @@ class ScreenActivity : AppCompatActivity(), ConnectChecker { button.setImageResource(R.drawable.stream_icon) } if (screenService != null && screenService.isRecording()) { - bRecord.setImageResource(R.drawable.stop_icon) + bRecord.setImageResource(R.drawable.ic_record_stop) } else { - bRecord.setImageResource(R.drawable.record_icon) + bRecord.setImageResource(R.drawable.ic_record_start) } button.setOnClickListener { val service = ScreenService.INSTANCE @@ -113,13 +113,13 @@ class ScreenActivity : AppCompatActivity(), ConnectChecker { ScreenService.INSTANCE?.toggleRecord { state -> when (state) { RecordController.Status.STARTED -> { - bRecord.setImageResource(R.drawable.pause_icon) + bRecord.setImageResource(R.drawable.ic_record_pause) } RecordController.Status.STOPPED -> { - bRecord.setImageResource(R.drawable.record_icon) + bRecord.setImageResource(R.drawable.ic_record_start) } RecordController.Status.RECORDING -> { - bRecord.setImageResource(R.drawable.stop_icon) + bRecord.setImageResource(R.drawable.ic_record_stop) } else -> {} } diff --git a/app/src/main/res/drawable/bg_b2_black_round_8dp_70.xml b/app/src/main/res/drawable/bg_b2_black_round_8dp_70.xml new file mode 100644 index 0000000000..8418f4c2a4 --- /dev/null +++ b/app/src/main/res/drawable/bg_b2_black_round_8dp_70.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_black20_round_2dp.xml b/app/src/main/res/drawable/bg_black20_round_2dp.xml new file mode 100644 index 0000000000..cc736acfe8 --- /dev/null +++ b/app/src/main/res/drawable/bg_black20_round_2dp.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/camera_switch.xml b/app/src/main/res/drawable/camera_switch.xml index 9ceaff4892..5733c725c0 100644 --- a/app/src/main/res/drawable/camera_switch.xml +++ b/app/src/main/res/drawable/camera_switch.xml @@ -1,4 +1,4 @@ - + diff --git a/app/src/main/res/drawable/ic_live_setting_off.xml b/app/src/main/res/drawable/ic_live_setting_off.xml new file mode 100644 index 0000000000..e810ca4f84 --- /dev/null +++ b/app/src/main/res/drawable/ic_live_setting_off.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_live_setting_on.xml b/app/src/main/res/drawable/ic_live_setting_on.xml new file mode 100644 index 0000000000..3e15623d78 --- /dev/null +++ b/app/src/main/res/drawable/ic_live_setting_on.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/live_start.xml b/app/src/main/res/drawable/ic_live_start.xml similarity index 94% rename from app/src/main/res/drawable/live_start.xml rename to app/src/main/res/drawable/ic_live_start.xml index 298480b838..c59ef373ec 100644 --- a/app/src/main/res/drawable/live_start.xml +++ b/app/src/main/res/drawable/ic_live_start.xml @@ -1,4 +1,4 @@ - + diff --git a/app/src/main/res/drawable/live_stop.xml b/app/src/main/res/drawable/ic_live_stop.xml similarity index 100% rename from app/src/main/res/drawable/live_stop.xml rename to app/src/main/res/drawable/ic_live_stop.xml diff --git a/app/src/main/res/drawable/microphone_off_icon.xml b/app/src/main/res/drawable/ic_microphone_off.xml similarity index 100% rename from app/src/main/res/drawable/microphone_off_icon.xml rename to app/src/main/res/drawable/ic_microphone_off.xml diff --git a/app/src/main/res/drawable/microphone_icon.xml b/app/src/main/res/drawable/ic_microphone_on.xml similarity index 100% rename from app/src/main/res/drawable/microphone_icon.xml rename to app/src/main/res/drawable/ic_microphone_on.xml diff --git a/app/src/main/res/drawable/pause_icon.xml b/app/src/main/res/drawable/ic_record_pause.xml similarity index 100% rename from app/src/main/res/drawable/pause_icon.xml rename to app/src/main/res/drawable/ic_record_pause.xml diff --git a/app/src/main/res/drawable/record_icon.xml b/app/src/main/res/drawable/ic_record_start.xml similarity index 93% rename from app/src/main/res/drawable/record_icon.xml rename to app/src/main/res/drawable/ic_record_start.xml index 413b6631c8..bdeed7ccc1 100644 --- a/app/src/main/res/drawable/record_icon.xml +++ b/app/src/main/res/drawable/ic_record_start.xml @@ -1,6 +1,6 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/style_item_setting_switcher_thumb.xml b/app/src/main/res/drawable/style_item_setting_switcher_thumb.xml new file mode 100644 index 0000000000..bc32d0e7a4 --- /dev/null +++ b/app/src/main/res/drawable/style_item_setting_switcher_thumb.xml @@ -0,0 +1,16 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/style_item_setting_switcher_track.xml b/app/src/main/res/drawable/style_item_setting_switcher_track.xml new file mode 100644 index 0000000000..a99e23c8d8 --- /dev/null +++ b/app/src/main/res/drawable/style_item_setting_switcher_track.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_display.xml b/app/src/main/res/layout/activity_display.xml index 9d2e3c5223..e47bf61adf 100644 --- a/app/src/main/res/layout/activity_display.xml +++ b/app/src/main/res/layout/activity_display.xml @@ -32,7 +32,7 @@ > + app:layout_constraintHorizontal_chainStyle="spread" /> + app:layout_constraintEnd_toStartOf="@id/switch_camera" /> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_live_setting.xml b/app/src/main/res/layout/item_live_setting.xml new file mode 100644 index 0000000000..f083fa25d4 --- /dev/null +++ b/app/src/main/res/layout/item_live_setting.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_live_settings_dialog.xml b/app/src/main/res/layout/view_live_settings_dialog.xml new file mode 100644 index 0000000000..aa9e385192 --- /dev/null +++ b/app/src/main/res/layout/view_live_settings_dialog.xml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_scan_qrcode.png b/app/src/main/res/mipmap-hdpi/ic_scan_qrcode.png new file mode 100644 index 0000000000000000000000000000000000000000..e6e79b2933435dc21ae8e28531a37c3c9ac0e68c GIT binary patch literal 368 zcmV-$0gwKPP)Px#1am@3R0s$N2z&@+hyVZq8c9S!R9J=Wm(dY|Fc3ud91E}n?SK+uM|P+kUfQ-?}o8bq@vLSb20UC{U$lMJi4*(9qsX;^ri};$cnwTTmGv?tRG;7A+ zJEbRi%r$dVgtjs?;&Kd)Z{|v}^9ILb*386IIM#VhlI(LtMmqB>>?U2HA5CtjjCm`< ztqlQqCZ3fv^i`@ev{%jz!D}tCWcm8Ou+80+OKJW8Ci<3TZ|iFfh&+=QNnSo|^`*Fk z9;$2mx|ghBrK*iF2v(*bw$O}Sf07nAFYr12>e~J%426-PL}7=^WR4%3gnYz7KR@In z4&qbH-RCEhLTwnehD78B-~`}qo`sHP{0`up`eYRSl~%?t|CMeujJyCJBsKIQBm8v$ O0000_W)T+sJ`ZBt{;hd&P; zH<%?k2qpjc_w`cw^>4SrXS_Vv%IGP%fi;MG>baeZA+*|za&fLi^ld2Pp z6E}IOMXc$`@y+# z-oMM#@;}X2T)836XYuqWsy_u;|q^Kx_c=DX$TTAHTp-<~bu xdiyJr{afUdXI=9HW-~L-X|VZIkXE&mS@BQh`G*yvr9dw-c)I$ztaD0e0sz2yc{u<8 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_scan_qrcode.png b/app/src/main/res/mipmap-xhdpi/ic_scan_qrcode.png new file mode 100644 index 0000000000000000000000000000000000000000..bb86b2d3b0b8fa3a5007ce9099da5fcd18ba968b GIT binary patch literal 461 zcmV;;0W$uHP)Px#1am@3R0s$N2z&@+hyVZqcS%G+RA_?I7Lv3ptM89e2pqGhi5oQ4`rZkz@;?18`P9O(OD@B2MxK zpi#p!fI&p=DQhl?v?L#lb4v++WzCVKPVNtHDY`> zEEd)dNJ1xZ_>JuFEo2x*gxZIxr}UL?Q#^^S`mueW2|Sl#lm6j1GE1N~u>^c0>r@~X zjjy@iRCBKMt5}OcXKu~h^_}VL<}Q{Gj@sOvMC6jXxL<{=_XF5=N5Hi^g6KyqA_IU+ zC0}R&a21iL_MR)I&b3pvD<&+r&Px#1am@3R0s$N2z&@+hyVZrNJ&INRCt{2o9R)*Fc5{G7z%I+rUR!UN+2B&Do9)c z6;J{tBpo;%#2qYulp$kuA5Jv9Z-yTnlYOj?m3FlNA%qY@2q6Yk?gxlS51`XuCT6A7 zDc6p}cJKI;lTvD~VcUs_bRx17kx9bXSJ1Ezv^|N)wzPehus*^ux41?+R>P2Be@fBt z6e+9+aP&-T8@45wLdvq~0US#X)E^ROj@)^PnPyV(U z;b&ErO(jfPgZ81QD7Ikz16VqDanQej4)Q<;d7y(l&_NzT9b}3Etw*u4`WJVf;&-ds ztfF7186N(6VWV~Zgb+eV9Nv^`K<%kz@@jS&iSd?j9vZDPNGHUP$=+>?7h7S$br);t zu9b^5yJ&Tg_UNISq{rdl+J{0mX;V2j(%2FJFCf0KP?J)DM(wN&ag6V$hG=^`URMjIQ;(H^cA7DncHq znFaPX10QAE5DEyoQhYgz$XBF>=xre?% z`!r0tnI&m}N;LI$o{(R)_T;cX>j1&J7a@cYLI^3(51oVHhyJ`MY5)KL07*qoM6N<$ Ef?awmmH+?% literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_scan_qrcode.png b/app/src/main/res/mipmap-xxxhdpi/ic_scan_qrcode.png new file mode 100644 index 0000000000000000000000000000000000000000..5ce13c9ee4263b243fcbd4c24efd442b0c080fd1 GIT binary patch literal 738 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-D$|Sc;uILpXq- zh9jkefq|*V)5S5QV$R#Uw+n9@2(&&-)he(G;PhfIV%Iz<>muu-cW|nPtbweBtcPrX z>>L}GPZ>sS9E=h=`v1S5*tj58&+|jlx-&p?LEudPlHMiao_m)}UgG$e&6B-7f}j6L z`J|F5Tc&@xVs$NW(mXj2jb)L7PHT3yefXIvqLlM>c~gs>P_{(z$+n`edYQper>7rs zF-yBPJF-y0c#G-geOowgvU2Qxu<_BU`qd_Wr{CXdQQBfJBP!>f?^`S6chT&h!OMHx z{}eLKXLhb~I8kaHv|-_~nhzcUzdczlMOZxSsLYf+Q7E)~QL}aXJ-hThcRVYkBmZd^ z@3H(T8#!<5^XFm;WjXfgedg-*N~bqJ+pYK9dD54c39-{n@7=j_X8#K(o~IfO##|42 zm@F1EoC7m;wlhpWlIk6*`~MD8O_}}Pr+;hm|NXpwXI|;f`R{Yz{$vJ)#02%9ibXr_ z{G0guOTDXJ+P7<^v6tR#zjPdH8h z#ce=muKqc1gYH7l`!lR9&1(HEzNKyqOY;`W_Pw@+VdJ%w-Df^Nds3ZJxn^o)?Lz0O zGna0M9jj`8w_NDN*4;nKIE$S!?(C9&DPm)CzVzMN?*FG3Z)q+3{p0Yoz{Rz@*iIwe)&Dge={T4rwy*ZxxY0o?S0wU Q0!p+Dp00i_>zopr0Kh3&Gynhq literal 0 HcmV?d00001 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4509da547a..7b3c99c456 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,7 +6,8 @@ From file Screen - Hohem Live + Hohem Live + Camera Live Select your streamer protocol://yourendpoint rtmp://tx.direct.huya.com/huyalive/1199649870524-1199649870524-0-2399299864504-10057-A-1760610117-1?seq=1760666707697&type=simple diff --git a/encoder/src/main/java/com/pedro/encoder/input/sources/video/Camera2Source.kt b/encoder/src/main/java/com/pedro/encoder/input/sources/video/Camera2Source.kt index 37a09ebef5..989355cc70 100644 --- a/encoder/src/main/java/com/pedro/encoder/input/sources/video/Camera2Source.kt +++ b/encoder/src/main/java/com/pedro/encoder/input/sources/video/Camera2Source.kt @@ -35,205 +35,213 @@ import com.pedro.encoder.utils.Logger * Created by pedro on 11/1/24. */ @RequiresApi(Build.VERSION_CODES.LOLLIPOP) -class Camera2Source(context: Context): VideoSource() { - companion object { - private const val TAG = "Camera2Source" - } - - private val camera = Camera2ApiManager(context) - private var facing = CameraHelper.Facing.BACK - - override fun create(width: Int, height: Int, fps: Int, rotation: Int): Boolean { - val result = checkResolutionSupported(width, height) - if (!result) { - throw IllegalArgumentException("Unsupported resolution: ${width}x$height") - } - return true - } - - override fun start(surfaceTexture: SurfaceTexture) { - this.surfaceTexture = surfaceTexture - if (!isRunning()) { - Logger.d(TAG, "start: width = $width, height = $height, fps = $fps, facing = $facing") - surfaceTexture.setDefaultBufferSize(width, height) - camera.prepareCamera(surfaceTexture, width, height, fps, facing) - camera.openCameraFacing(facing) - } - } - - override fun stop() { - if (isRunning()) camera.closeCamera() - } - - override fun release() {} - - override fun isRunning(): Boolean = camera.isRunning - - private fun checkResolutionSupported(width: Int, height: Int): Boolean { - if (width % 2 != 0 || height % 2 != 0) { - throw IllegalArgumentException("width and height values must be divisible by 2") - } - val size = Size(width, height) - val resolutions = if (facing == CameraHelper.Facing.BACK) { - camera.cameraResolutionsBack - } else camera.cameraResolutionsFront - Logger.d(TAG, "checkResolutionSupported: size = $size, resolutions = ${resolutions.contentToString()}") - Logger.d(TAG, "checkResolutionSupported: camera.levelSupported = ${camera.levelSupported}") - return if (camera.levelSupported == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { - //this is a wrapper of camera1 api. Only listed resolutions are supported - resolutions.contains(size) - } else { - val widthList = resolutions.map { size.width } - val heightList = resolutions.map { size.height } - val maxWidth = widthList.maxOrNull() ?: 0 - val maxHeight = heightList.maxOrNull() ?: 0 - val minWidth = widthList.minOrNull() ?: 0 - val minHeight = heightList.minOrNull() ?: 0 - size.width in minWidth..maxWidth && size.height in minHeight..maxHeight - } - } - - fun switchCamera() { - facing = if (facing == CameraHelper.Facing.BACK) { - CameraHelper.Facing.FRONT - } else { - CameraHelper.Facing.BACK - } - if (isRunning()) { - stop() - surfaceTexture?.let { - start(it) - } - } - } - - fun changeResolution() { - if (isRunning()) { - stop() - surfaceTexture?.let { - start(it) - } +class Camera2Source(context: Context) : VideoSource() { + companion object { + private const val TAG = "Camera2Source" } - } - fun getCameraFacing(): CameraHelper.Facing = facing + private val camera = Camera2ApiManager(context) + private var facing = CameraHelper.Facing.BACK - fun getCameraResolutions(facing: CameraHelper.Facing): List { - val resolutions = if (facing == CameraHelper.Facing.FRONT) { - camera.cameraResolutionsFront - } else { - camera.cameraResolutionsBack + override fun create(width: Int, height: Int, fps: Int, rotation: Int): Boolean { + val result = checkResolutionSupported(width, height) + if (!result) { + throw IllegalArgumentException("Unsupported resolution: ${width}x$height") + } + return true } - return resolutions.toList() - } - fun setExposure(level: Int) { - if (isRunning()) camera.exposure = level - } - - fun getExposure(): Int { - return if (isRunning()) camera.exposure else 0 - } - - fun enableLantern() { - if (isRunning()) camera.enableLantern() - } + override fun start(surfaceTexture: SurfaceTexture) { + this.surfaceTexture = surfaceTexture + if (!isRunning()) { + Logger.d(TAG, "start: width = $width, height = $height, fps = $fps, facing = $facing") + surfaceTexture.setDefaultBufferSize(width, height) + camera.prepareCamera(surfaceTexture, width, height, fps, facing) + camera.openCameraFacing(facing) + } + } + + override fun stop() { + if (isRunning()) camera.closeCamera() + } + + override fun release() {} + + override fun isRunning(): Boolean = camera.isRunning + + private fun checkResolutionSupported(width: Int, height: Int): Boolean { + if (width % 2 != 0 || height % 2 != 0) { + throw IllegalArgumentException("width and height values must be divisible by 2") + } + val size = Size(width, height) + val resolutions = if (facing == CameraHelper.Facing.BACK) { + camera.cameraResolutionsBack + } else camera.cameraResolutionsFront + Logger.d( + TAG, + "checkResolutionSupported: size = $size, resolutions = ${resolutions.contentToString()}" + ) + Logger.d(TAG, "checkResolutionSupported: camera.levelSupported = ${camera.levelSupported}") + return if (camera.levelSupported == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { + //this is a wrapper of camera1 api. Only listed resolutions are supported + resolutions.contains(size) + } else { + val widthList = resolutions.map { size.width } + val heightList = resolutions.map { size.height } + val maxWidth = widthList.maxOrNull() ?: 0 + val maxHeight = heightList.maxOrNull() ?: 0 + val minWidth = widthList.minOrNull() ?: 0 + val minHeight = heightList.minOrNull() ?: 0 + size.width in minWidth..maxWidth && size.height in minHeight..maxHeight + } + } + + fun switchCamera() { + facing = if (facing == CameraHelper.Facing.BACK) { + CameraHelper.Facing.FRONT + } else { + CameraHelper.Facing.BACK + } + if (isRunning()) { + stop() + surfaceTexture?.let { + start(it) + } + } + } + + fun changeResolution() { + if (isRunning()) { + stop() + surfaceTexture?.let { + start(it) + } + } + } + + fun getCameraFacing(): CameraHelper.Facing = facing + + fun getCameraResolutions(facing: CameraHelper.Facing): List { + val resolutions = if (facing == CameraHelper.Facing.FRONT) { + camera.cameraResolutionsFront + } else { + camera.cameraResolutionsBack + } + return resolutions.toList() + } + + fun setExposure(level: Int) { + if (isRunning()) camera.exposure = level + } + + fun getExposure(): Int { + return if (isRunning()) camera.exposure else 0 + } + + fun enableLantern() { + if (isRunning()) camera.enableLantern() + } + + fun disableLantern() { + if (isRunning()) camera.disableLantern() + } - fun disableLantern() { - if (isRunning()) camera.disableLantern() - } + fun isLanternEnabled(): Boolean { + return if (isRunning()) camera.isLanternEnabled else false + } + + fun enableAutoFocus(): Boolean { + if (isRunning()) return camera.enableAutoFocus() + return false + } + + fun disableAutoFocus(): Boolean { + if (isRunning()) return camera.disableAutoFocus() + return false + } - fun isLanternEnabled(): Boolean { - return if (isRunning()) camera.isLanternEnabled else false - } + fun isAutoFocusEnabled(): Boolean { + return if (isRunning()) camera.isAutoFocusEnabled else false + } - fun enableAutoFocus(): Boolean { - if (isRunning()) return camera.enableAutoFocus() - return false - } - - fun disableAutoFocus(): Boolean { - if (isRunning()) return camera.disableAutoFocus() - return false - } + fun tapToFocus(event: MotionEvent): Boolean { + return camera.tapToFocus(event) + } - fun isAutoFocusEnabled(): Boolean { - return if (isRunning()) camera.isAutoFocusEnabled else false - } + @JvmOverloads + fun setZoom(event: MotionEvent, delta: Float = 0.1f) { + if (isRunning()) camera.setZoom(event, delta) + } - fun tapToFocus(event: MotionEvent): Boolean { - return camera.tapToFocus(event) - } + fun setZoom(level: Float) { + if (isRunning()) camera.zoom = level + } - @JvmOverloads - fun setZoom(event: MotionEvent, delta: Float = 0.1f) { - if (isRunning()) camera.setZoom(event, delta) - } - - fun setZoom(level: Float) { - if (isRunning()) camera.zoom = level - } - - fun getZoomRange(): Range = camera.zoomRange - - fun getZoom(): Float = camera.zoom + fun getZoomRange(): Range = camera.zoomRange - fun enableFaceDetection(callback: FaceDetectorCallback): Boolean { - return if (isRunning()) camera.enableFaceDetection(callback) else false - } + fun getZoom(): Float = camera.zoom - fun disableFaceDetection() { - if (isRunning()) camera.disableFaceDetection() - } + fun enableFaceDetection(callback: FaceDetectorCallback): Boolean { + return if (isRunning()) camera.enableFaceDetection(callback) else false + } - fun isFaceDetectionEnabled() = camera.isFaceDetectionEnabled() + fun disableFaceDetection() { + if (isRunning()) camera.disableFaceDetection() + } - fun camerasAvailable(): Array = camera.camerasAvailable + fun isFaceDetectionEnabled() = camera.isFaceDetectionEnabled() - fun getCurrentCameraId() = camera.getCurrentCameraId() + fun camerasAvailable(): Array = camera.camerasAvailable - fun openCameraId(id: String) { - if (isRunning()) camera.reOpenCamera(id) - } + fun getCurrentCameraId() = camera.getCurrentCameraId() - fun enableOpticalVideoStabilization(): Boolean { - return if (isRunning()) camera.enableOpticalVideoStabilization() else false - } + fun openCameraId(id: String) { + if (isRunning()) camera.reOpenCamera(id) + } - fun disableOpticalVideoStabilization() { - if (isRunning()) camera.disableOpticalVideoStabilization() - } + fun enableOpticalVideoStabilization(): Boolean { + return if (isRunning()) camera.enableOpticalVideoStabilization() else false + } - fun isOpticalVideoStabilizationEnabled() = camera.isOpticalStabilizationEnabled + fun disableOpticalVideoStabilization() { + if (isRunning()) camera.disableOpticalVideoStabilization() + } - fun enableVideoStabilization(): Boolean { - return if (isRunning()) camera.enableVideoStabilization() else false - } + fun isOpticalVideoStabilizationEnabled() = camera.isOpticalStabilizationEnabled - fun disableVideoStabilization() { - if (isRunning()) camera.disableVideoStabilization() - } + fun enableVideoStabilization(): Boolean { + return if (isRunning()) camera.enableVideoStabilization() else false + } - fun isVideoStabilizationEnabled() = camera.isVideoStabilizationEnabled + fun disableVideoStabilization() { + if (isRunning()) camera.disableVideoStabilization() + } - fun enableAutoExposure(): Boolean { - return if (isRunning()) camera.enableAutoExposure() else false - } + fun isVideoStabilizationEnabled() = camera.isVideoStabilizationEnabled - fun disableAutoExposure() { - if (isRunning()) camera.disableAutoExposure() - } + fun enableAutoExposure(): Boolean { + return if (isRunning()) camera.enableAutoExposure() else false + } - fun isAutoExposureEnabled() = camera.isAutoExposureEnabled + fun disableAutoExposure() { + if (isRunning()) camera.disableAutoExposure() + } - @JvmOverloads - fun addImageListener(format: Int, maxImages: Int, autoClose: Boolean = true, listener: ImageCallback) { - val w = if (rotation == 90 || rotation == 270) height else width - val h = if (rotation == 90 || rotation == 270) width else height - camera.addImageListener(w, h, format, maxImages, autoClose, listener) - } + fun isAutoExposureEnabled() = camera.isAutoExposureEnabled + + @JvmOverloads + fun addImageListener( + format: Int, + maxImages: Int, + autoClose: Boolean = true, + listener: ImageCallback + ) { + val w = if (rotation == 90 || rotation == 270) height else width + val h = if (rotation == 90 || rotation == 270) width else height + camera.addImageListener(w, h, format, maxImages, autoClose, listener) + } - fun removeImageListener() { - camera.removeImageListener() - } + fun removeImageListener() { + camera.removeImageListener() + } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 101c3d9d02..40622026f4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,6 +24,7 @@ uvcandroid = "1.0.7" media3 = "1.5.0" firebaseCrashlyticsBuildtools = "3.0.2" eventbus = "3.3.1" +recyclerview = "1.4.0" [libraries] androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" } @@ -43,7 +44,7 @@ uvcandroid = { module = "com.herohan:UVCAndroid", version.ref = "uvcandroid" } androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" } firebase-crashlytics-buildtools = { group = "com.google.firebase", name = "firebase-crashlytics-buildtools", version.ref = "firebaseCrashlyticsBuildtools" } eventbus = {module = "org.greenrobot:eventbus", version.ref = "eventbus"} - +recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/library/src/main/java/com/pedro/library/generic/GenericStream.kt b/library/src/main/java/com/pedro/library/generic/GenericStream.kt index 36ce71b4bf..6b4e7f4220 100644 --- a/library/src/main/java/com/pedro/library/generic/GenericStream.kt +++ b/library/src/main/java/com/pedro/library/generic/GenericStream.kt @@ -54,122 +54,122 @@ class GenericStream( private val connectChecker: ConnectChecker, videoSource: VideoSource, audioSource: AudioSource -): StreamBase(context, videoSource, audioSource) { - companion object { - private const val TAG = "GenericStream" - } +) : StreamBase(context, videoSource, audioSource) { + companion object { + private const val TAG = "GenericStream" + } - private val streamClientListener = object: StreamClientListener { - override fun onRequestKeyframe() { - requestKeyframe() + private val streamClientListener = object : StreamClientListener { + override fun onRequestKeyframe() { + requestKeyframe() + } } - } - private val rtmpClient = RtmpClient(connectChecker) - private val rtspClient = RtspClient(connectChecker) - private val srtClient = SrtClient(connectChecker) - private val udpClient = UdpClient(connectChecker) - private val streamClient = GenericStreamClient( - RtmpStreamClient(rtmpClient, streamClientListener), - RtspStreamClient(rtspClient, streamClientListener), - SrtStreamClient(srtClient, streamClientListener), - UdpStreamClient(udpClient, streamClientListener) - ) - private var connectedType = ClientType.NONE + private val rtmpClient = RtmpClient(connectChecker) + private val rtspClient = RtspClient(connectChecker) + private val srtClient = SrtClient(connectChecker) + private val udpClient = UdpClient(connectChecker) + private val streamClient = GenericStreamClient( + RtmpStreamClient(rtmpClient, streamClientListener), + RtspStreamClient(rtspClient, streamClientListener), + SrtStreamClient(srtClient, streamClientListener), + UdpStreamClient(udpClient, streamClientListener) + ) + private var connectedType = ClientType.NONE - constructor(context: Context, connectChecker: ConnectChecker): - this(context, connectChecker, Camera2Source(context), MicrophoneSource()) + constructor(context: Context, connectChecker: ConnectChecker) : + this(context, connectChecker, Camera2Source(context), MicrophoneSource()) - override fun getStreamClient(): GenericStreamClient = streamClient + override fun getStreamClient(): GenericStreamClient = streamClient - override fun setVideoCodecImp(codec: VideoCodec) { - if (codec != VideoCodec.H264 && codec != VideoCodec.H265) { - throw IllegalArgumentException("Unsupported codec: ${codec.name}. Generic only support video ${VideoCodec.H264.name} and ${VideoCodec.H265.name}") + override fun setVideoCodecImp(codec: VideoCodec) { + if (codec != VideoCodec.H264 && codec != VideoCodec.H265) { + throw IllegalArgumentException("Unsupported codec: ${codec.name}. Generic only support video ${VideoCodec.H264.name} and ${VideoCodec.H265.name}") + } + rtmpClient.setVideoCodec(codec) + rtspClient.setVideoCodec(codec) + srtClient.setVideoCodec(codec) + udpClient.setVideoCodec(codec) } - rtmpClient.setVideoCodec(codec) - rtspClient.setVideoCodec(codec) - srtClient.setVideoCodec(codec) - udpClient.setVideoCodec(codec) - } - override fun setAudioCodecImp(codec: AudioCodec) { - if (codec != AudioCodec.AAC) { - throw IllegalArgumentException("Unsupported codec: ${codec.name}. Generic only support audio ${AudioCodec.AAC.name}") + override fun setAudioCodecImp(codec: AudioCodec) { + if (codec != AudioCodec.AAC) { + throw IllegalArgumentException("Unsupported codec: ${codec.name}. Generic only support audio ${AudioCodec.AAC.name}") + } + rtmpClient.setAudioCodec(codec) + rtspClient.setAudioCodec(codec) + srtClient.setAudioCodec(codec) + udpClient.setAudioCodec(codec) } - rtmpClient.setAudioCodec(codec) - rtspClient.setAudioCodec(codec) - srtClient.setAudioCodec(codec) - udpClient.setAudioCodec(codec) - } - override fun onAudioInfoImp(sampleRate: Int, isStereo: Boolean) { - rtmpClient.setAudioInfo(sampleRate, isStereo) - rtspClient.setAudioInfo(sampleRate, isStereo) - srtClient.setAudioInfo(sampleRate, isStereo) - udpClient.setAudioInfo(sampleRate, isStereo) - } + override fun onAudioInfoImp(sampleRate: Int, isStereo: Boolean) { + rtmpClient.setAudioInfo(sampleRate, isStereo) + rtspClient.setAudioInfo(sampleRate, isStereo) + srtClient.setAudioInfo(sampleRate, isStereo) + udpClient.setAudioInfo(sampleRate, isStereo) + } - override fun startStreamImp(endPoint: String) { - streamClient.connecting(endPoint) - if (endPoint.startsWith("rtmp", ignoreCase = true)) { - connectedType = ClientType.RTMP - val resolution = super.getVideoResolution() - val fps = super.getVideoFps() - Logger.d(TAG, "startStreamImp: resolution = $resolution, fps = $fps") - rtmpClient.setVideoResolution(resolution.width, resolution.height) + override fun startStreamImp(endPoint: String) { + streamClient.connecting(endPoint) + if (endPoint.startsWith("rtmp", ignoreCase = true)) { + connectedType = ClientType.RTMP + val resolution = super.getVideoResolution() + val fps = super.getVideoFps() + Logger.d(TAG, "startStreamImp: resolution = $resolution, fps = $fps") + rtmpClient.setVideoResolution(resolution.width, resolution.height) // rtmpClient.setVideoResolution(1080, 1920) - rtmpClient.setFps(fps) - rtmpClient.connect(endPoint) - } else if (endPoint.startsWith("rtsp", ignoreCase = true)) { - connectedType = ClientType.RTSP - rtspClient.connect(endPoint) - } else if (endPoint.startsWith("srt", ignoreCase = true)) { - connectedType = ClientType.SRT - srtClient.connect(endPoint) - } else if (endPoint.startsWith("udp", ignoreCase = true)) { - connectedType = ClientType.UDP - udpClient.connect(endPoint) - } else { - onMainThreadHandler { - connectChecker.onConnectionFailed("Unsupported protocol. Only support rtmp, rtsp and srt") - } + rtmpClient.setFps(fps) + rtmpClient.connect(endPoint) + } else if (endPoint.startsWith("rtsp", ignoreCase = true)) { + connectedType = ClientType.RTSP + rtspClient.connect(endPoint) + } else if (endPoint.startsWith("srt", ignoreCase = true)) { + connectedType = ClientType.SRT + srtClient.connect(endPoint) + } else if (endPoint.startsWith("udp", ignoreCase = true)) { + connectedType = ClientType.UDP + udpClient.connect(endPoint) + } else { + onMainThreadHandler { + connectChecker.onConnectionFailed("Unsupported protocol. Only support rtmp, rtsp and srt") + } + } } - } - override fun stopStreamImp() { - when (connectedType) { - ClientType.RTMP -> rtmpClient.disconnect() - ClientType.RTSP -> rtspClient.disconnect() - ClientType.SRT -> srtClient.disconnect() - ClientType.UDP -> udpClient.disconnect() - else -> {} + override fun stopStreamImp() { + when (connectedType) { + ClientType.RTMP -> rtmpClient.disconnect() + ClientType.RTSP -> rtspClient.disconnect() + ClientType.SRT -> srtClient.disconnect() + ClientType.UDP -> udpClient.disconnect() + else -> {} + } + connectedType = ClientType.NONE } - connectedType = ClientType.NONE - } - override fun getAudioDataImp(audioBuffer: ByteBuffer, info: MediaCodec.BufferInfo) { - when (connectedType) { - ClientType.RTMP -> rtmpClient.sendAudio(audioBuffer, info) - ClientType.RTSP -> rtspClient.sendAudio(audioBuffer, info) - ClientType.SRT -> srtClient.sendAudio(audioBuffer, info) - ClientType.UDP -> udpClient.sendAudio(audioBuffer, info) - else -> {} + override fun getAudioDataImp(audioBuffer: ByteBuffer, info: MediaCodec.BufferInfo) { + when (connectedType) { + ClientType.RTMP -> rtmpClient.sendAudio(audioBuffer, info) + ClientType.RTSP -> rtspClient.sendAudio(audioBuffer, info) + ClientType.SRT -> srtClient.sendAudio(audioBuffer, info) + ClientType.UDP -> udpClient.sendAudio(audioBuffer, info) + else -> {} + } } - } - override fun onVideoInfoImp(sps: ByteBuffer, pps: ByteBuffer?, vps: ByteBuffer?) { - rtmpClient.setVideoInfo(sps, pps, vps) - rtspClient.setVideoInfo(sps, pps, vps) - srtClient.setVideoInfo(sps, pps, vps) - udpClient.setVideoInfo(sps, pps, vps) - } + override fun onVideoInfoImp(sps: ByteBuffer, pps: ByteBuffer?, vps: ByteBuffer?) { + rtmpClient.setVideoInfo(sps, pps, vps) + rtspClient.setVideoInfo(sps, pps, vps) + srtClient.setVideoInfo(sps, pps, vps) + udpClient.setVideoInfo(sps, pps, vps) + } - override fun getVideoDataImp(videoBuffer: ByteBuffer, info: MediaCodec.BufferInfo) { - when (connectedType) { - ClientType.RTMP -> rtmpClient.sendVideo(videoBuffer, info) - ClientType.RTSP -> rtspClient.sendVideo(videoBuffer, info) - ClientType.SRT -> srtClient.sendVideo(videoBuffer, info) - ClientType.UDP -> udpClient.sendVideo(videoBuffer, info) - else -> {} + override fun getVideoDataImp(videoBuffer: ByteBuffer, info: MediaCodec.BufferInfo) { + when (connectedType) { + ClientType.RTMP -> rtmpClient.sendVideo(videoBuffer, info) + ClientType.RTSP -> rtspClient.sendVideo(videoBuffer, info) + ClientType.SRT -> srtClient.sendVideo(videoBuffer, info) + ClientType.UDP -> udpClient.sendVideo(videoBuffer, info) + else -> {} + } } - } } \ No newline at end of file