From 85484ff4ffbd3283c1009cf8c69be80e9a0f97fc Mon Sep 17 00:00:00 2001 From: Daisy <> Date: Wed, 3 Jan 2024 15:24:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=9B=91=E6=8E=A7=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=8F=8A=E8=BF=9E=E6=8E=A5=E4=B8=AD=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- star_lock/images/main/realTime_connecting.png | Bin 0 -> 8755 bytes star_lock/lib/appRouters.dart | 22 +- .../lockDetail/lockDetail_page.dart | 8 +- .../realTimePicture_logic.dart | 368 ++++++++++++++++++ .../realTimePicture/realTimePicture_page.dart | 301 ++++++++++++++ .../realTimePicture_state.dart | 35 ++ 6 files changed, 719 insertions(+), 15 deletions(-) create mode 100644 star_lock/images/main/realTime_connecting.png create mode 100644 star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_logic.dart create mode 100644 star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_page.dart create mode 100644 star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_state.dart diff --git a/star_lock/images/main/realTime_connecting.png b/star_lock/images/main/realTime_connecting.png new file mode 100644 index 0000000000000000000000000000000000000000..62c50bbc5fb932081af2df86c4fb16bd78edee3c GIT binary patch literal 8755 zcmXAv1ymc&*T#cuArzM&1qv1130|~7@ZxU83GPy8vEuGp+}$bePH-#1r4)Dg=l#B% zv)OZYGCR9>GtZspcQ;HKBJ&EH0viATypofZR7LD35Ze?cI^tWY*MJSN!*Z0>b_M`Q z82{UlDQK4^YBcvk-e2qz^4v@Oh&`@f}0&r?kRk->1_)2c@lfb{( z$7Y0KTyuLFrvmJ=`d2QlqH3@PphDnPrHoQ7i&!K?6M=-$5Jhco`ZCY0{l*m_ITlNrPkoLQnuk75Vv}yygYP5xoJO z@pXS^80CeMS)crnn5RC@PS5|v0QgVYr%-&Nk_ifnkk?x5^6z@iUHL}(lZ?Y8H}7Eo z@9uk8*E!K!0qP6U2dbGdYSBFet@Hk9cj0%*Hq$s$(4VJ>B!=4URW>#44@Sb#8 zT^M+4%oh+!-O}1}v@kc9Au zVgiHEJ50xTc+mjGm6er?L}bd$N`hebziA_IfqOg}kyYU0AMP#8zv~_8@fmSmCQpK* zk@h_VgSn|1WnajDQe0(){n{xw@bU_}48f3gzFgDp7L{g)2YN!qR2SzXNSMk{cyE=kA;nWUU$0TAM?;$ ze)-%OV`o0H+h+IRcJzJ2ukAPpUxXdw$|(hJZca$l_3z_%$;fd1fHu*Lah;<*OQ(z7 zaasmX+h7Sy6&MUQ=;Vx+Tc%mXUBGoZYdO*E7GKpWp+ZFbP0P{n0O*FY$lySeJzc}W zfkPweM-{09LUH}>1)EE#6&3QqBjiOzMNeB;Sjawo>);xKIAf7gwws$96!z(3^IkZ1 z8qC3gt zRK25c_Z@R{9c9htQjE;vE9FF1!}iTEGY{I0PTzC_YHAM3rjDkI3-?R2GIVhm;wJGi zak*tadVDb>a6?I%|I-^{mo4P!gnXskiz$^?AnbjkXxLy)T+v}V@>O@=%|_hbUKYR6 z2Dibr!E$zX_9q^*E(+*L2~&*{GSewY5c>XE5hu zqfZ^!vHAVuFg`lN~>iA&@^6a9poRNZD)6)tzD@&o=ZVV$=34n zuUEP6n^Q8wG5Qpv!zoWb7Z=w8J-KyCYwL0yDVu>_gG&kMC>=Gso&PqyzH=jq2|bY}V@^9htF21XWEZ#{|-GshG}_<|#mkgP9|m*f9ajrEDi zjcFYmtM}^jS4YpGXge&dh;Hxk*{Wg{6%{qjSRRkd%V}46xfnH@cJAH(t}aD#H=*a* z?{3@PL4&&9nTUJR7ZgU(o5thP1uHu{J1_Dog;ZmNQMWsD01N>i6d&EiK!eg~@emXEa$;sKcka z>&B>Sxb|yFCdhK3QlGafElt(z-KwUd;yVHiys?@l_qlF6Fr3j||RGvcmt8R4~Opq*>gock`_-}Yq z@=+4vk9UKO(@U%v@wRw=XKhV-n z39+!ORkgP2Bxw_$czAiadsh9sJf|k7q-fW1kzI?xd70EKFDC>w*~0 zW)}a0Ci!Y>VjbG>AMUfTun5PMig(yJD}3_H1&hMo*N|jBMZ+%FxBmsf=ed2?QqF1y z*Y$M$spEXPxVUtYrzrWx0E6EHhajS0|j~Z`TdrYJKn)TggcMzs0 z_8~$$25=o-U+8$ywtIX1im!$uj=< zthtPNnjAGyobZSPT?!RlU2nIb7iq566LsytMKJ&2X9WV?%b@ONc5Wxn@G%cl$rNBT_#xb zfgW$7q{K7VWl~pBaomb}Bdjs?;-_JAf*oh;&KZ1gTK?o1#)3Q`wr@Qc?&dz6`=j3R+-&0R+5C-KzZO|0>j{twh$ize^ z_Bqc=xt*=;?uP_A(E5b#u8b!Vf(P45d57q&uCy?yN{3bXgWU2 z0Euvdw>X6aW-fd%8axv#N)X$8c(@&L=C(6JtmJv2+*;CT5y^Jbi$&x2@zm2{qf@cV z^I})S{DKK6SjYF#2Q)NoR##h_W`8;~8~cIKTnNm<1FO1gcr@k3q?@CNB7UeMjJ0@A zcNVIbiB{1qX9h7Wp|Bj8E2+acJQz>9Y5z~@_LS9IIXww6XB=IsMKzR_W1e(P;b_`X z*%n-il@R3bASOn}CGXAOf#-NG$bjD+-tNxVCu-dfrF773wj$OT2F7 zVX3D&qLt-VACCE*_xu_#0p&ZwRjsA>MPrv^&f&ZAv_rjo8$Q0YY^T?*x z=vw7^m+m6GZK6OfY1R`m>f)`SjC86|+-POn*LFok5L;BVAj{Bukq)bMD4na{E`@Gw z!AVA(+xd~j?F;kvUSqmcyPswi4ZnL=loMjmh(8#ey=ZFUj-jSA4ripM-+l7})Mv0n8qW5(j|D<(RM1nxJm{FXo z%>rg=S=k2$UrWSl&fI-Z&XtPBA$$7mTliQ)wY;*nwsv0M9V#;1%+kFp$H|J%d7(gX zVvvvtg2tF;)kQ)|EPcFTShjzM`t7}}yppH}LKz@En)7N!kkk(cR3%Xtca~~6kt;f0Kxjbp$Z{9c8Aq%Q4EenGF_D1Zs1x3jVLlfEV zNBr?jzHe&6A;!b8-4$cg)#Yu8Dxfwuw-Gvd)FOX4a_Qy$f#xhcy%prL35B#6Vkr zYFa-z+XV-94<#Zpd8q$FkCr;^b=6hzbUyg!o(q&} zG@5CdIsKpmeB+d$7$o*vZvLtqhjw0%Qe^eF^5X+*l&OXNb4{ElcCdzqnF*74g?5XH zRO#&t<`eAC16~w-yWSQ6N|lz51I=R|D*1_vyRB^82Vvk7zElRm$tax_#fNVwk;T=T zo$d~yU+pSikqdbg-@2bf!FL<={CCGvanx-n0)HDIBR{T~X51WYbbYcZUxO+r{2W&h zy`>dP{6T#mk#Oz3UxH-iE0Pb&2H|R#x+TdT0zS@*6 zGb0C{WPsg8r~$uMS65Yi$Xy?3=wH2RAv=Bp)Kat=!$ksU9UUHC+dMpWDK-$G>ysQI z&XpM)d@G8LgX4e#*he%vJhZPt*L`o+a^oNjKrb+(x%qbhz}MAfd11#9phHp_Nx`GD zwQP-?+jMbnu0{_K>v~3624nIA0Q5ibX|T*THZGZ|Fz_xO%<)wVFnvO=5m7+*$mn}?ypbMuQkT8?G_-km%Jk* zqY)-~`0g%LsQ)x?ih!8G$Tg`mfQl$CS%*!a&@K@3uzR*>Qn1{57x3TZzXw?ch)tQL zrKhL&z3c%1mnSPt+1tHXsK@}Zi=&0A|37%Tp)~y@%(%6?Tc~8|g^Fzdnvn3B=%uS- zAh)i>qN1uwsqhrYO@Rjph-!o+FE)=DolVP_ksyobTag1`GkLB;d)YKE@%(s*15h#+ zFG3L4fNTk{6%}~}@dK%Ue3@Z~M@R8cx^L3GNPyePjh9>)6act|K+J4Ib870_;g8?< zAq&g97n7ZZU=`#*7DoWU?*(pHbpW|76L2P(&U_n~V~qeQ0@EBE`qvTG(2R|ZXNdcc z(?|-tdbbmu+R}uaMpRpDPJFEOG=BTlW~|JvS#-ed(~Unzjd$AB?U@B3Qh@E9xH9O)lH<*6|I1Dbx3=V5@>51Q2U_ya!FFKQ~< zu(wbTWYUTK2nBYI~q0suf_;!eK;xw-9XgRd-Ybsz$1LC^preT$2@+qlsKNPw@l z*4BWUC%l8ckr8V_!h_eC;jfA6m$sH0j*fhNeceAr^j_|B`@2Vt1_G@Kzo!K1+`zrm zYJ5UNtF87b*LYV85}K0e@o%kjyI5R)@LHug``z@Rq< zGqAxeXZ<%`S=tJysIl?--K$19vn^2;5!3f}$hl0%$DSE^d3m912&F+{COh+v$s0okf^gHSbGbv?9Hs-Z6zQ=MHKvi{eJ?Ln@OH1I#mm}~BXaJu z5?F!uPeurBXpNOxO#eJfI^p0?ayty}lb-G!C&d#gsy2Ibt$Uk;ju0t)-r3kNhJk1r zhuB2d@$8aIEL2ibzK)TRkzmDiC{3#DKgF$AkHif z(_J6*S*A-9x07`{LJ%m?!q)Z}6Zhnb+w5PA@ySc*jUxCw(}nl|o`mxL<40Q(Rh4Yv z*j*ch%VM?mv`F=_U&&cMPc*~LN1vPT)Q79Oq}|T8dUs|z`N(fNQ|nK$(ju!> zQ>vZ%vDJRVkfw-_YTyH@C}M$qDJa9k!%P8pH&n>tZzfn-z4&VDP?`tycm-Yo04aX| z0@1|yx&{3%Z@R`^nz#dLv8er1E6XAxUbrdKum367nrzYv`vTsn*xRp-$f;xspi1Ci zoz?g&WC?k$ozr}Ijz)wEHL1bp{^;-;BW8=An0QB_0+SKwwLR4xD$SZaxos^Mr zi1HCiYieq$28dZjOjBh8l{28uPjh`&xqV?sh-q~P@ z!=wr-qV1wBYiSYC&=>$>)*9K4^;2)Fmcps{$)^7`7hL9+{-gtXy*b#Q?r*jO#L(?~ z@k;4ye5YHR7Wgcb>u%~`xrdbkEUD`v7rFC;sHjA&ej+#Vg zB`=PvbpwNert;x^{R8caigHg<#3XW(5$gP?0dX@k%%qlBJ3cB+_jMWpbrkn;Kh3LqrQ0olf_JQfSALk*poeI@3toU7!XifU0sd*6Wzwf z#vLpxzYqFHbu1jfh~VE-nwpwT5pYyAH0^}s#D3o{7dhP_<#P0Rshp1V%S#tgId8|U zzP`T2@~4H2!M&r2OfGj7BcmKu4!46Jy$8KR&jqO0b#v>1&L0nkkd3A0kP$Q+}r{uNeZnz!t zsSSS>PtF2RgL+~RDOG^w_Z!v49S~iowi*jxF zsMN>iRLQZ3(`|0CnB)b!Ir<;DdU|V@<_#K$$zZqR6de_nqyToosaDsc0lB(ZS!b87 zYIdqWfy<<uL_)}A^Qy8}l0DWuHL&riOtt`4Qm zWs;*WCij%0_zev*^%qYM3ReAEGh!o(DgocalE(`THVcmg)W!9PXYFZAoZORt{_^s0N{D2Ho!w>oLzYTSe&wa3@`!UB)!6ZX%$oFRJH&t-S zil@K)`e%KV3bY=C^eANs4a;zo$lBVjN(kC9^ptIG0zbsc2HTkyBMeey+~E4(KG{FF z4-VYks`|dj)0<)i1JhJzw-YFmZ)|nll%r{qryDn3=cAeL4b7I6mn(>U<_DHQxj#K4 z1@6LNN>Q#Y-S6|R1PV^P$TDlQ6M!#F$)h_`($e;R*T=(h$~h=VNRRyiR;a|a9FFmK zoFWe6#VYj4MneX)MB%UDa6-4~wD*|?i}6Ao?J|W$mbb#5=a<3dT|@;Xg`GOPTctM_ z2eT8H@PrBkSeDhcP1MxFo(mjYVEOPN;u9wkvsGzH(HCzIiZ|`?!6o!EQLK&b+XpU| z`J6qv&1J&4Wo2bibnnmL~y^oxrnjlN00mmfZU^jLMZujqQ4E)bmsgsBQ9SbsjUyFlcF z@^BvsKyz#+=--=q)bIKm&7O#IO(|aVAKph8 z-e?468yg$DT50^Q18)QjrVy^nABz6lzM>K^7#a;jh&VT!T+9%l8kX+vBm};neS3_~Uhzx*L_kVPDz+H@Nh~ojagY&F`)oHWLBVK>Y;Kra z&mu%nOjl3s7KIB84a`I+i}RBXg6kr>Ga%@H@naWGY z&BHU^RGybt;byG%DhMc{{!2EY&jG7Fs-wL%on^uJr%qzejB%;r@Tf{>?Yq5;H|pPP@T zcVmDzk_tOT%pL2{Wq!ZM;5Ex^#Z0ip7#SE1LK-TWS9=~FzV+k&N7_JPA-Wxe1_VIO zd#@s=0gE3zgK}UClU?IQQDt_vKDpdmA3?IXJzGg~M4k}#S9E)g4jrzj$YSQ=(MZS4 zR^{PxczMu!^f+J2kifYH93~rqG~N%Y-dt{Q@eUK=Q-B)IvZ7pAD95@JUH9u!>!B)Y<-1KJ+2BS9E8 zge6cS23A(T+~MXL3oae68ZVny3;+NS`#U2Mqb%Dy7M4{+l{j>TSwoCUM(!8{a{b7{ zwB%!BGs?F`5oz;%KIg`ovWxEcB}D>96iy>THpXjv~YV z1@2(xS3Wg$85fi?h;oY`x7*oi2H|M;egCF#6w=FdT5==GUs4r@B@tX$m4;n@ExY{@ zhg*o~i1^-@zu9$E=d#aVM21ok*Loh!hh-~d+}?pt_wiFLE(CN3te4Xb5HpdHm^d*y zoQ9Zmdw18IT2b(v?`^skK9q{2`W^F758~tXZ?pT8YrU(B?emeki=f&=gpa4W`Ed5j z^9@bER6p9yU(#JFxt}lb13q48eNe4%`sk|s{8>%+L!zS=e;Xu6<j`ae9IwlB0;{SXX;znbad0nAWpA$`j3=ETG?6IDP_cePoq~Q-(Jma%#dVhWW TsBVXN3j`o11(B?jF#7gCt9=0@ literal 0 HcmV?d00001 diff --git a/star_lock/lib/appRouters.dart b/star_lock/lib/appRouters.dart index ecf2e56c..f4b0beb6 100644 --- a/star_lock/lib/appRouters.dart +++ b/star_lock/lib/appRouters.dart @@ -15,6 +15,7 @@ import 'package:star_lock/main/lockDetail/lcokSet/msgNotification/msgNotificatio import 'package:star_lock/main/lockDetail/lcokSet/notificationMode/notificationMode_page.dart'; import 'package:star_lock/main/lockDetail/lcokSet/openDoorDirection/openDoorDirection_page.dart'; import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_main_page.dart'; +import 'package:star_lock/main/lockDetail/lockDetail/realTimePicture/realTimePicture_page.dart'; import 'package:star_lock/main/lockDetail/passwordKey/passwordKeyDetailChangeDate/passwordKeyDetailChangeDate_page.dart'; import 'package:star_lock/mine/about/webviewShow_page.dart'; import 'package:star_lock/mine/mine/safeVerify/safeVerify_page.dart'; @@ -406,6 +407,7 @@ abstract class Routers { static const addFaceTypeManagePage = '/AddFaceTypeManagePage'; // 添加人脸 static const passwordKeyDetailChangeDatePage = '/passwordKeyDetailChangeDatePage'; //密码更改时间 + static const realTimePicturePage = '/realTimePicturePage'; //实时监控画面 } abstract class AppRouters { @@ -953,12 +955,9 @@ abstract class AppRouters { GetPage( name: Routers.monitoringRealTimeScreenPage, page: () => const MonitoringRealTimeScreenPage()), + GetPage(name: Routers.videoLogPage, page: () => const VideoLogPage()), GetPage( - name: Routers.videoLogPage, - page: () => const VideoLogPage()), - GetPage( - name: Routers.editVideoLogPage, - page: () => const EditVideoLogPage()), + name: Routers.editVideoLogPage, page: () => const EditVideoLogPage()), GetPage( name: Routers.videoLogDetailPage, page: () => const VideoLogDetailPage()), @@ -971,15 +970,11 @@ abstract class AppRouters { GetPage( name: Routers.addRemoteControlManagePage, page: () => const AddRemoteControlManagePage()), - GetPage( - name: Routers.cardListPage, - page: () => const CardListPage()), + GetPage(name: Routers.cardListPage, page: () => const CardListPage()), GetPage( name: Routers.addCardTypeManagePage, page: () => const AddCardTypeManagePage()), - GetPage( - name: Routers.cardDetailPage, - page: () => const CardDetailPage()), + GetPage(name: Routers.cardDetailPage, page: () => const CardDetailPage()), GetPage( name: Routers.fingerprintListPage, page: () => const FingerprintListPage()), @@ -995,6 +990,9 @@ abstract class AppRouters { page: () => const AddFaceTypeManagePage()), GetPage( name: Routers.passwordKeyDetailChangeDatePage, - page: () => const PasswordKeyDetailChangeDatePage()) + page: () => const PasswordKeyDetailChangeDatePage()), + GetPage( + name: Routers.realTimePicturePage, + page: () => const RealTimePicturePage()) ]; } diff --git a/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart b/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart index 988bceab..fa3de7f1 100644 --- a/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart +++ b/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart @@ -373,9 +373,11 @@ class _LockDetailPageState extends State with TickerProviderStat //可视对讲门锁新增->监控 if (state.keyInfos.value.lockFeature!.videoIntercom == 1) { showWidgetArr.add( - bottomItem('images/main/icon_catEyes.png', TranslationLoader.lanKeys!.monitoring!.tr, () { - Get.toNamed(Routers.lockMonitoringPage, arguments: { - "lockId": widget.lockListInfoItemEntity.lockId + bottomItem('images/main/icon_catEyes.png', + TranslationLoader.lanKeys!.monitoring!.tr, () { + Get.toNamed(Routers.realTimePicturePage, arguments: { + "lockId": widget.lockListInfoItemEntity.lockId, + "isMonitoring": true }); }), ); diff --git a/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_logic.dart b/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_logic.dart new file mode 100644 index 00000000..013d18d3 --- /dev/null +++ b/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_logic.dart @@ -0,0 +1,368 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/services.dart'; +import 'package:flutter_voice_processor/flutter_voice_processor.dart'; +import 'package:get/get.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:star_lock/main/lockDetail/lockDetail/realTimePicture/realTimePicture_state.dart'; + +import '../../../../talk/call/g711.dart'; +import '../../../../talk/udp/udp_manage.dart'; +import '../../../../talk/udp/udp_senderManage.dart'; +import '../../../../tools/baseGetXController.dart'; +import '../../../../tools/eventBusEventManage.dart'; + +class RealTimePictureLogic extends BaseGetXController { + final RealTimePictureState state = RealTimePictureState(); + + /// 初始化发送声音 + initRecorder() { + state.voiceProcessor = VoiceProcessor.instance; + } + + /// 收到视频流数据 + StreamSubscription? _getTVDataRefreshUIEvent; + void _getTVDataRefreshUIAction() { + // 蓝牙协议通知传输跟蓝牙之外的数据传输类不一样 eventBus + _getTVDataRefreshUIEvent = + eventBus.on().listen((event) { + if (event.tvList.isNotEmpty) { + // 预加载图片数据 + Uint8List imageData = Uint8List.fromList(event.tvList); + // 更新状态 + state.listData.value = imageData; + } + }); + } + + /// 接听 + udpAnswerAction() async { + UDPSenderManage.sendMainProtocol( + command: 150, + commandTypeIsCalling: 1, + subCommand: 6, + lockID: UDPManage().lockId, + lockIP: UDPManage().host, + userMobile: await state.userMobile, + userMobileIP: await state.userMobileIP, + endData: []); + } + + /// 挂断 + udpHangUpAction() async { + UDPSenderManage.sendMainProtocol( + command: 150, + commandTypeIsCalling: 1, + subCommand: 30, + lockID: UDPManage().lockId, + lockIP: UDPManage().host, + userMobile: await state.userMobile, + userMobileIP: await state.userMobileIP, + endData: []); + } + + /// 开门 + udpOpenDoorAction() async { + UDPSenderManage.sendMainProtocol( + command: 150, + commandTypeIsCalling: 1, + subCommand: 10, + lockID: UDPManage().lockId, + lockIP: UDPManage().host, + userMobile: await state.userMobile, + userMobileIP: await state.userMobileIP, + endData: []); + Get.back(); + } + + Future _readG711Data() async { + String filePath = 'assets/s10-g711.bin'; + List audioData = await G711().readAssetFile(filePath); + // Get.log('发送读取711文件数据为:$audioData');// 数据为:$audioData + // return; + // print('发送读取711文件数据长度为:${audioData.length}');// 数据为:$audioData + if (audioData.isNotEmpty) { + // 在这里处理你的音频数据 + // pcmBytes = G711().convertList(audioData); + // print('发送转换pcmBytes数据长度为:${pcmBytes.length}'); + + int start = 0; + int length = 320; + while (start < audioData.length) { + // await Future.delayed(const Duration(milliseconds: 50)); + + int end = (start + length > audioData.length) + ? audioData.length + : start + length; + List sublist = audioData.sublist(start, end); + sendRecordData({ + "bytes": sublist, + // "udpSendDataFrameNumber": 0, + "lockID": UDPManage().lockId, + "lockIP": UDPManage().host, + "userMobile": await state.userMobile, + "userMobileIP": await state.userMobileIP, + }); + print(sublist); + start += length; + } + print('G711数据发送完成'); + } else { + print('Failed to read audio data.'); + } + } + + Future startProcessing() async { + frameListener(List frame) async { + // Get.log('Get data.length:${frame.length} Received data:$frame'); + for (int i = 0; i < frame.length; i++) { + frame[i] = linearToULaw(frame[i]); + } + // Get.log('change Get data.length:${frame.length} change Received data:$frame'); + await Future.delayed(const Duration(milliseconds: 50)); + sendRecordData({ + "bytes": frame, + // "udpSendDataFrameNumber": 0, + "lockID": UDPManage().lockId, + "lockIP": UDPManage().host, + "userMobile": await state.userMobile, + "userMobileIP": await state.userMobileIP, + }); + } + + errorListener(VoiceProcessorException error) { + print("VoiceProcessorException: $error"); + } + + ; + state.voiceProcessor?.addFrameListener(frameListener); + state.voiceProcessor?.addErrorListener(errorListener); + + try { + if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) { + await state.voiceProcessor?.start(320, 8000); + bool? isRecording = await state.voiceProcessor?.isRecording(); + } else {} + } on PlatformException catch (ex) { + Get.log("PlatformException: $ex"); + } finally {} + } + + Future stopProcessing() async { + try { + await state.voiceProcessor?.stop(); + } on PlatformException catch (ex) { + Get.log("PlatformException: $ex"); + } finally {} + } + + void onError(Object e) { + print(e); + } + + sendRecordData(Map args) async { + List bytes = args["bytes"]; + // int udpSendDataFrameNumber = args["udpSendDataFrameNumber"]; + String? lockID = args["lockID"]; + String? lockIP = args["lockIP"]; + String? userMobile = args["userMobile"]; + String? userMobileIP = args["userMobileIP"]; + + // int length = 320; // 每个子List的长度 + // List list = state.listAudioData.value.sublist(0, 320); + // for (int i = 0; i < bytes.length; i += length) { + // int end = (i + length < bytes.length) ? i + length : bytes.length; + // bytes.sublist(i, end); + // // _sendRecordData(bytes.sublist(i, end)); + // // // 刚进来是接听状态,然后改为长按对讲 + // } + + // while(list.isNotEmpty) { + state.udpSendDataFrameNumber++; + if (state.udpSendDataFrameNumber >= 65536) state.udpSendDataFrameNumber = 1; + // 57 + List topBytes = []; + + // var cID = "XXXCID"; + // List cIDData = utf8.encode(cID!); + // topBytes.addAll(cIDData); + // // topBytes = getFixedLengthList(cIDData, 20 - cIDData.length); + // for (int i = 0; i < 6 - cIDData.length; i++) { + // topBytes.add(0); + // } + // + // // 命令 + // topBytes.add(150); + // + // // 命令类型 + // topBytes.add(1); + // + // // 子命令 + // topBytes.add(8); + // + // // lockID + // List lockIDData = utf8.encode(lockID!); + // topBytes.addAll(lockIDData); + // // topBytes = getFixedLengthList(lockIDData, 20 - lockIDData.length); + // for (int i = 0; i < 20 - lockIDData.length; i++) { + // topBytes.add(0); + // } + // + // // lockIP + // var lockIPList = lockIP!.split("."); + // lockIPList.forEach((element) { + // topBytes.add(int.parse(element)); + // }); + // + // // userMobile + // List userMobileData = utf8.encode(userMobile!); + // topBytes.addAll(userMobileData); + // // topBytes = getFixedLengthList(topBytes, 20 - userMobileData.length); + // for (int i = 0; i < 20 - userMobileData.length; i++) { + // topBytes.add(0); + // } + // + // // userMobileIP + // var userMobileIPList = userMobileIP!.split("."); + // userMobileIPList.forEach((element) { + // topBytes.add(int.parse(element)); + // }); + + topBytes.addAll([ + 1, 1, 1, 1, // 时间戳 + 1, 0, // 音频 + 1, 0, // 帧序号 + 64, 0, 0, 0, // 帧长度 + 1, 0, // 总包数 + 1, 0, // 当前包号 + 64, 1, // 数据长度 + 176, 4, // 保留 + ]); + + topBytes[6] = (state.udpSendDataFrameNumber & 0x000000FF); + topBytes[7] = ((state.udpSendDataFrameNumber & 0x0000FF00) >> 8); + + print( + "udpSendDataFrameNumber:${state.udpSendDataFrameNumber} topBytes[63]:${topBytes[6]} topBytes[64]:${topBytes[7]}"); + topBytes.addAll(bytes); + Get.log("setVoiceBytes:$topBytes"); + + UDPSenderManage.sendMainProtocol( + command: 150, + commandTypeIsCalling: 1, + subCommand: 8, + lockID: lockID, + lockIP: lockIP, + userMobile: userMobile, + userMobileIP: userMobileIP, + endData: topBytes); + + // UDPManage().sendData(topBytes); + } + + // 拿到的音频转化成pcm + int linearToULaw(int pcmVal) { + int mask; + int seg; + int uval; + + if (pcmVal < 0) { + pcmVal = 0x84 - pcmVal; + mask = 0x7F; + } else { + pcmVal += 0x84; + mask = 0xFF; + } + + seg = search(pcmVal); + if (seg >= 8) { + return 0x7F ^ mask; + } else { + uval = (seg << 4); + uval |= ((pcmVal >> (seg + 3)) & 0xF); + return uval ^ mask; + } + } + + int search(int val) { + List table = [ + 0xFF, + 0x1FF, + 0x3FF, + 0x7FF, + 0xFFF, + 0x1FFF, + 0x3FFF, + 0x7FFF + ]; + int size = 8; + for (int i = 0; i < size; i++) { + if (val <= table[i]) { + return i; + } + } + return size; + } + + double _calculateVolumeLevel(List frame) { + double rms = 0.0; + for (int sample in frame) { + rms += pow(sample, 2); + } + rms = sqrt(rms / frame.length); + + double dbfs = 20 * log(rms / 32767.0) / log(10); + double normalizedValue = (dbfs + 50) / 50; + return normalizedValue.clamp(0.0, 1.0); + } + + Future getPermissionStatus() async { + Permission permission = Permission.microphone; + //granted 通过,denied 被拒绝,permanentlyDenied 拒绝且不在提示 + PermissionStatus status = await permission.status; + if (status.isGranted) { + return true; + } else if (status.isDenied) { + requestPermission(permission); + } else if (status.isPermanentlyDenied) { + openAppSettings(); + } else if (status.isRestricted) { + requestPermission(permission); + } else {} + return false; + } + + ///申请权限 + void requestPermission(Permission permission) async { + PermissionStatus status = await permission.request(); + if (status.isPermanentlyDenied) { + openAppSettings(); + } + } + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + print("onReady()"); + + _getTVDataRefreshUIAction(); + + initRecorder(); + } + + @override + void onInit() { + // TODO: implement onInit + super.onInit(); + } + + @override + void onClose() { + // TODO: implement onClose + print("锁详情界面销毁了"); + _getTVDataRefreshUIEvent!.cancel(); + stopProcessing(); + } +} diff --git a/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_page.dart b/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_page.dart new file mode 100644 index 00000000..fa067dab --- /dev/null +++ b/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_page.dart @@ -0,0 +1,301 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:star_lock/main/lockDetail/lockDetail/realTimePicture/realTimePicture_logic.dart'; + +import '../../../../app_settings/app_colors.dart'; +import '../../../../tools/showTFView.dart'; +import '../../../../tools/toast.dart'; + +class RealTimePicturePage extends StatefulWidget { + const RealTimePicturePage({Key? key}) : super(key: key); + + @override + State createState() => _RealTimePicturePageState(); +} + +class _RealTimePicturePageState extends State + with TickerProviderStateMixin { + final logic = Get.put(RealTimePictureLogic()); + final state = Get.find().state; + + @override + void initState() { + super.initState(); + + // listeningAnimations(); + state.animationController = + AnimationController(duration: const Duration(seconds: 2), vsync: this); + state.animationController.repeat(); + //动画开始、结束、向前移动或向后移动时会调用StatusListener + state.animationController.addStatusListener((status) { + // print("AnimationStatus:$status"); + if (status == AnimationStatus.completed) { + state.animationController.reset(); + state.animationController.forward(); + } else if (status == AnimationStatus.dismissed) { + state.animationController.reset(); + state.animationController.forward(); + } + }); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 1.sw, + height: 1.sh, + child: Stack( + children: [ + Obx(() => state.listData.value.isEmpty + ? Container(color: Colors.black) + : Image.memory( + state.listData.value, + // key: ValueKey(state.listData.value.hashCode), + gaplessPlayback: true, + width: 1.sw, + height: 1.sh, + fit: BoxFit.cover, + )), + Positioned( + bottom: 10.w, + child: Container( + width: 1.sw - 30.w * 2, + // height: 300.h, + margin: EdgeInsets.all(30.w), + decoration: BoxDecoration( + color: const Color(0xC83C3F41), + borderRadius: BorderRadius.circular(20.h)), + child: Column( + children: [ + SizedBox(height: 20.h), + bottomTopBtnWidget(), + SizedBox(height: 20.h), + bottomBottomBtnWidget(), + SizedBox(height: 20.h), + ], + ), + )), + buildRotationTransition() + ], + ), + ); + } + + Widget bottomTopBtnWidget() { + return Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + // 打开关闭声音 + GestureDetector( + onTap: () { + state.isOpenVoice.value = !state.isOpenVoice.value; + }, + child: Container( + width: 50.w, + height: 50.w, + padding: EdgeInsets.all(5.w), + child: Obx(() => Image( + width: 40.w, + height: 40.w, + image: state.isOpenVoice.value + ? const AssetImage( + "images/main/icon_lockDetail_monitoringCloseVoice.png") + : const AssetImage( + "images/main/icon_lockDetail_monitoringOpenVoice.png"))), + ), + ), + SizedBox(width: 60.w), + // 截图 + GestureDetector( + onTap: () { + // Get.toNamed(Routers.monitoringRealTimeScreenPage); + }, + child: Container( + width: 50.w, + height: 50.w, + padding: EdgeInsets.all(5.w), + child: Image( + width: 40.w, + height: 40.w, + image: const AssetImage( + "images/main/icon_lockDetail_monitoringScreenshot.png")), + ), + ), + SizedBox(width: 60.w), + // 录制 + GestureDetector( + onTap: () { + // Get.toNamed(Routers.monitoringRealTimeScreenPage); + }, + child: Container( + width: 50.w, + height: 50.w, + padding: EdgeInsets.all(5.w), + child: Image( + width: 40.w, + height: 40.w, + image: const AssetImage( + "images/main/icon_lockDetail_monitoringScreenRecording.png")), + ), + ), + ]); + } + + Widget bottomBottomBtnWidget() { + return Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + // 接听 + Obx(() => bottomBtnItemWidget( + getAnswerBtnImg(), getAnswerBtnName(), Colors.white, () async { + //获取麦克风权限 + await logic.getPermissionStatus().then((value) async { + if (!value) { + return; + } + + // state.isSenderAudioData.value = false; + print("发送接听了"); + // 刚进来是接听状态,然后改为长按对讲 + logic.udpAnswerAction(); + }); + }, longPress: () { + // 开始长按 + print("onLongPress"); + state.listAudioData.value = []; + if (state.udpStatus.value == 8) { + state.udpStatus.value = 9; + } + // logic.readG711Data(); + logic.startProcessing(); + }, longPressUp: () async { + // 长按结束 + print("onLongPressUp"); + if (state.udpStatus.value == 9) { + state.udpStatus.value = 8; + } + })), + bottomBtnItemWidget( + "images/main/icon_lockDetail_hangUp.png", "挂断", Colors.red, () async { + logic.stopProcessing(); + + // 挂断 + logic.udpHangUpAction(); + }), + bottomBtnItemWidget("images/main/icon_lockDetail_monitoringUnlock.png", + "开锁", AppColors.mainColor, () { + showDeletPasswordAlertDialog(context); + }) + ]); + } + + String getAnswerBtnImg() { + switch (state.udpStatus.value) { + case 8: + return "images/main/icon_lockDetail_monitoringUnTalkback.png"; + case 9: + return "images/main/icon_lockDetail_monitoringTalkback.png"; + default: + return "images/main/icon_lockDetail_monitoringAnswerCalls.png"; + } + } + + String getAnswerBtnName() { + switch (state.udpStatus.value) { + case 8: + return "长按说话"; + case 9: + return "松开发送"; + default: + return "接听"; + } + } + + Widget bottomBtnItemWidget( + String iconUrl, String name, Color backgroundColor, Function() onClick, + {Function()? longPress, Function()? longPressUp}) { + var wh = 80.w; + return GestureDetector( + onTap: onClick, + onLongPress: longPress, + onLongPressUp: longPressUp, + child: SizedBox( + height: 140.h, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: wh, + height: wh, + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular((wh + 10.w * 2) / 2)), + padding: EdgeInsets.all(20.w), + child: Image.asset(iconUrl, fit: BoxFit.fitWidth), + ), + SizedBox(height: 20.w), + Expanded( + child: Text(name, + style: TextStyle(fontSize: 20.sp, color: Colors.white), + textAlign: TextAlign.center)) + ], + )), + ); + } + + void showDeletPasswordAlertDialog(BuildContext context) { + showDialog( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return ShowTFView( + title: "请输入六位数字开锁密码", + tipTitle: "", + controller: state.passwordTF, + inputFormatters: [ + LengthLimitingTextInputFormatter(6), //限制长度 + FilteringTextInputFormatter.allow(RegExp("[0-9]")), + ], + sureClick: () async { + //发送删除锁请求 + if (state.passwordTF.text.isEmpty) { + Toast.show(msg: "请输入开锁密码"); + return; + } + + // 开锁 + logic.udpOpenDoorAction(); + }, + cancelClick: () { + Get.back(); + }, + ); + }, + ); + } + + //旋转动画 + Widget buildRotationTransition() { + return Positioned( + left: ScreenUtil().screenWidth / 2 - 220.w / 2, + top: ScreenUtil().screenHeight / 2 - 220.w / 2 - 150.h, + child: RotationTransition( + //设置动画的旋转中心 + alignment: Alignment.center, + //动画控制器 + turns: state.animationController, + //将要执行动画的子view + child: Image.asset( + 'images/main/realTime_connecting.png', + width: 220.w, + height: 220.w, + ), + ), + ); + } + + @override + void dispose() { + state.animationController.dispose(); + + super.dispose(); + } +} diff --git a/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_state.dart b/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_state.dart new file mode 100644 index 00000000..fe01bd43 --- /dev/null +++ b/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_state.dart @@ -0,0 +1,35 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_voice_processor/flutter_voice_processor.dart'; +import 'package:get/get.dart'; +import 'package:network_info_plus/network_info_plus.dart'; + +import '../../../../tools/storage.dart'; + +class RealTimePictureState { + var isOpenVoice = false.obs; + var udpSendDataFrameNumber = 0; // 帧序号 + // var isSenderAudioData = false.obs;// 是否要发送音频数据 + + var userMobileIP = NetworkInfo().getWifiIP(); + var userMobile = Storage.getMobile(); + + var udpStatus = + 0.obs; //0:初始状态 1:等待监视 2: 3:监视中 4:呼叫成功 5:主角通话中 6:被叫通话 8:被叫通话中 9:长按说话 + var passwordTF = TextEditingController(); + + var listData = Uint8List(0).obs; //得到的视频流字节数据 + var listAudioData = [].obs; //得到的音频流字节数据 + + late final VoiceProcessor? voiceProcessor; + + var oneMinuteTime = 0.obs; // 定时器秒数 + + // 定时器如果发送了接听的命令 而没收到回复就每秒重复发送10次 + late Timer answerTimer; + late Timer hangUpTimer; + late Timer openDoorTimer; + late AnimationController animationController; +}