TRTCCallingVideo.dart 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. import 'dart:io';
  2. import 'package:ctjt_flutter/common/trtc/ProfileManager_Mock.dart';
  3. import 'package:ctjt_flutter/common/trtc/TxUtils.dart';
  4. import 'package:ctjt_flutter/common/trtc/calling/model/TRTCCalling.dart';
  5. import 'package:ctjt_flutter/common/trtc/calling/model/TRTCCallingDelegate.dart';
  6. import 'package:ctjt_flutter/common/trtc/calling/ui/base/CallTypes.dart';
  7. import 'package:ctjt_flutter/common/trtc/calling/ui/base/CallingScenes.dart';
  8. import 'package:flutter/cupertino.dart';
  9. import 'package:flutter/material.dart';
  10. import 'package:flutter_screenutil/flutter_screenutil.dart';
  11. import 'package:tencent_trtc_cloud/trtc_cloud_def.dart';
  12. import 'package:tencent_trtc_cloud/trtc_cloud_video_view.dart';
  13. import 'dart:async';
  14. import '../base/ExtendButton.dart';
  15. import '../base/CallStatus.dart';
  16. class TRTCCallingVideo extends StatefulWidget {
  17. @override
  18. _TRTCCallingVideoState createState() => _TRTCCallingVideoState();
  19. }
  20. class _TRTCCallingVideoState extends State<TRTCCallingVideo> {
  21. CallStatus _currentCallStatus = CallStatus.calling;
  22. CallTypes _currentCallType = CallTypes.Type_Call_Someone;
  23. CallingScenes _callingScenes = CallingScenes.AudioOneVOne;
  24. //已经通话时长
  25. String _hadCallingTime = "00:00";
  26. late DateTime _startAnswerTime;
  27. bool _isCameraOff = false;
  28. bool _isHandsFree = true;
  29. bool _isMicrophoneOff = false;
  30. bool _isFrontCamera = true;
  31. int _bigVideoViewId = -1;
  32. Timer? _hadCalledCalcTimer;
  33. late int _smallVideoViewId;
  34. double _smallViewTop = 64.h;
  35. double _smallViewRight = 20.w;
  36. //为false的时候,在已接听状态的时候。小画面显示本地视频,大画面显示远端视频。
  37. bool isChangeBigSmallVideo = false;
  38. UserModel? _remoteUserInfo;
  39. //远端画面可见不可见
  40. bool _remoteUserAvailable = true;
  41. late TRTCCalling _tRTCCallingService;
  42. @override
  43. void initState() {
  44. super.initState();
  45. Future.delayed(Duration.zero, () {
  46. this.initRemoteInfo();
  47. });
  48. initTrtc();
  49. }
  50. initTrtc() async {
  51. _tRTCCallingService = await TRTCCalling.sharedInstance();
  52. _tRTCCallingService.registerListener(onRtcListener);
  53. }
  54. onRtcListener(type, params) {
  55. switch (type) {
  56. case TRTCCallingDelegate.onError:
  57. showMessageTips("发生错误:" + params['errCode'] + "," + params['errMsg'],
  58. stopCameraAndFinish);
  59. break;
  60. case TRTCCallingDelegate.onWarning:
  61. print('onWarning:warning code = ' +
  62. params['warningCode'] +
  63. " ,warning msg = " +
  64. params['warningMsg']);
  65. break;
  66. case TRTCCallingDelegate.onUserEnter:
  67. handleOnUserAnswer();
  68. break;
  69. case TRTCCallingDelegate.onUserLeave:
  70. showMessageTips("用户离开了", stopCameraAndFinish);
  71. break;
  72. case TRTCCallingDelegate.onReject:
  73. showMessageTips("拒绝通话", stopCameraAndFinish);
  74. break;
  75. case TRTCCallingDelegate.onNoResp:
  76. showMessageTips("无响应", stopCameraAndFinish);
  77. break;
  78. case TRTCCallingDelegate.onLineBusy:
  79. showMessageTips("忙线", stopCameraAndFinish);
  80. break;
  81. case TRTCCallingDelegate.onCallingCancel:
  82. showMessageTips("取消了通话", stopCameraAndFinish);
  83. break;
  84. case TRTCCallingDelegate.onCallingTimeout:
  85. showMessageTips("本次通话超时未应答", stopCameraAndFinish);
  86. break;
  87. case TRTCCallingDelegate.onCallEnd:
  88. showMessageTips("结束通话", stopCameraAndFinish);
  89. break;
  90. case TRTCCallingDelegate.onUserVideoAvailable:
  91. handleOnUserVideoAvailable(params);
  92. break;
  93. case TRTCCallingDelegate.onKickedOffline:
  94. showMessageTips("你被踢下线了", stopCameraAndFinish);
  95. break;
  96. }
  97. }
  98. initRemoteInfo() async {
  99. Map arguments = ModalRoute.of(context)!.settings.arguments! as Map;
  100. safeSetState(() {
  101. _remoteUserInfo = arguments['remoteUserInfo'] as UserModel;
  102. _currentCallType = arguments["callType"] as CallTypes;
  103. _callingScenes = arguments['callingScenes'] as CallingScenes;
  104. Future.delayed(Duration(microseconds: 100), () {
  105. if (_currentCallType == CallTypes.Type_Call_Someone) {
  106. _tRTCCallingService.call(
  107. _remoteUserInfo!.userId,
  108. _callingScenes == CallingScenes.VideoOneVOne
  109. ? TRTCCalling.typeVideoCall
  110. : TRTCCalling.typeAudioCall);
  111. }
  112. });
  113. });
  114. }
  115. //用户接听
  116. handleOnUserAnswer() async {
  117. if (_remoteUserInfo != null) {
  118. _startAnswerTime = DateTime.now();
  119. safeSetState(() async {
  120. _currentCallStatus = CallStatus.answer;
  121. _hadCallingTime = "00:00";
  122. if (_bigVideoViewId != -1) {
  123. await _tRTCCallingService.startRemoteView(
  124. _remoteUserInfo!.userId,
  125. TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_SMALL,
  126. _bigVideoViewId,
  127. );
  128. }
  129. });
  130. this._callIngTimeUpdate();
  131. }
  132. }
  133. handleOnUserVideoAvailable(params) async {
  134. if (_remoteUserInfo != null &&
  135. params["userId"].toString() == _remoteUserInfo!.userId) {
  136. safeSetState(() {
  137. _remoteUserAvailable = params["available"] as bool;
  138. });
  139. }
  140. }
  141. showMessageTips(String msg, Function callback) {
  142. TxUtils.showErrorToast(msg, context);
  143. Future.delayed(Duration(seconds: 1), () {
  144. callback();
  145. });
  146. }
  147. stopCameraAndFinish() {
  148. _tRTCCallingService.setMicMute(true);
  149. _tRTCCallingService.closeCamera();
  150. Future.delayed(Duration(seconds: 1), () {
  151. if (mounted) {
  152. Navigator.pushReplacementNamed(
  153. context,
  154. "/index",
  155. );
  156. }
  157. });
  158. }
  159. String _twoDigits(int n) {
  160. if (n >= 10) return "$n";
  161. return "0$n";
  162. }
  163. _getDurationTimeString(Duration duration) {
  164. String line = "";
  165. if (duration.inHours != 0) {
  166. line = _twoDigits(duration.inHours.remainder(24)) + ":";
  167. }
  168. line = line + _twoDigits(duration.inMinutes.remainder(60)) + ":";
  169. line = line + _twoDigits(duration.inSeconds.remainder(60));
  170. return line;
  171. }
  172. _callIngTimeUpdate() {
  173. _hadCalledCalcTimer = Timer.periodic(Duration(seconds: 1), (Timer timer) {
  174. DateTime now = DateTime.now();
  175. Duration duration = now.difference(_startAnswerTime);
  176. safeSetState(() {
  177. _hadCallingTime = _getDurationTimeString(duration);
  178. });
  179. });
  180. }
  181. double _getOpacityByVis(bool vis) {
  182. return vis ? 1.0 : 0;
  183. }
  184. safeSetState(callBack) {
  185. setState(() {
  186. if (mounted) {
  187. callBack();
  188. }
  189. });
  190. }
  191. @override
  192. dispose() {
  193. if (_hadCalledCalcTimer != null) {
  194. _hadCalledCalcTimer!.cancel();
  195. }
  196. _tRTCCallingService.unRegisterListener(onRtcListener);
  197. super.dispose();
  198. }
  199. //前后摄像头切换
  200. onSwitchCamera() {
  201. _tRTCCallingService.switchCamera(!_isFrontCamera);
  202. safeSetState(() {
  203. _isFrontCamera = !_isFrontCamera;
  204. });
  205. }
  206. //麦克风启用禁用
  207. onMicrophoneTap() {
  208. _tRTCCallingService.setMicMute(!_isMicrophoneOff);
  209. setState(() {
  210. _isMicrophoneOff = !_isMicrophoneOff;
  211. });
  212. }
  213. //摄像头启用禁用
  214. onCameraTap() async {
  215. if (!_isCameraOff) {
  216. await _tRTCCallingService.closeCamera();
  217. } else {
  218. //为false的时候,在已接听状态的时候。小画面显示本地视频,大画面显示远端视频。
  219. if (isChangeBigSmallVideo) {
  220. await _tRTCCallingService.openCamera(_isFrontCamera, _bigVideoViewId);
  221. } else {
  222. await _tRTCCallingService.openCamera(_isFrontCamera, _smallVideoViewId);
  223. }
  224. }
  225. safeSetState(() {
  226. _isCameraOff = !_isCameraOff;
  227. });
  228. }
  229. //扬声器是否禁用
  230. onHandsfreeTap() {
  231. _tRTCCallingService.setHandsFree(!_isHandsFree);
  232. setState(() {
  233. _isHandsFree = !_isHandsFree;
  234. });
  235. }
  236. onSwitchAudioTap() {
  237. //先不支持切到语音通话
  238. // _tRTCCallingService.closeCamera();
  239. // safeSetState(() {
  240. // _callingScenes = CallingScenes.AudioOneVOne;
  241. // });
  242. }
  243. //挂断
  244. onHangUpCall() async {
  245. _tRTCCallingService.closeCamera();
  246. if (_currentCallType == CallTypes.Type_Being_Called &&
  247. _currentCallStatus == CallStatus.calling) {
  248. await _tRTCCallingService.reject();
  249. } else {
  250. await _tRTCCallingService.hangup();
  251. }
  252. Navigator.pushReplacementNamed(
  253. context,
  254. "/index",
  255. );
  256. }
  257. //接听
  258. onAcceptCall() async {
  259. await _tRTCCallingService.accept();
  260. safeSetState(() {
  261. _currentCallStatus = CallStatus.answer;
  262. });
  263. }
  264. getTopBarWidget() {
  265. bool isCalling = _currentCallStatus == CallStatus.calling ? true : false;
  266. var topWidget = Positioned(
  267. left: 0,
  268. top: _callingScenes == CallingScenes.VideoOneVOne ? 64.h : 185.h,
  269. width: MediaQuery.of(context).size.width,
  270. child: Column(
  271. mainAxisAlignment: MainAxisAlignment.center,
  272. children: isCalling
  273. ? [
  274. Row(
  275. mainAxisAlignment: MainAxisAlignment.center,
  276. children: [
  277. Text(
  278. _remoteUserInfo != null ? _remoteUserInfo!.name : "--",
  279. style: TextStyle(
  280. fontSize: 24.sp,
  281. color: Colors.white,
  282. fontWeight: FontWeight.bold,
  283. ),
  284. ),
  285. ],
  286. ),
  287. Row(
  288. mainAxisAlignment: MainAxisAlignment.center,
  289. children: [
  290. Text(
  291. '正在等待对方接受邀请…',
  292. style: TextStyle(fontSize: 12.sp, color: Colors.white),
  293. ),
  294. ],
  295. )
  296. ]
  297. : _callingScenes == CallingScenes.VideoOneVOne
  298. ? [
  299. Row(
  300. mainAxisAlignment: MainAxisAlignment.start,
  301. children: [
  302. Container(
  303. margin: EdgeInsets.only(
  304. left: 20.w,
  305. ),
  306. decoration: BoxDecoration(),
  307. child: InkWell(
  308. onTap: () {
  309. onSwitchCamera();
  310. },
  311. child: Image.asset(
  312. 'assets/images/callingDemo/switch-camera.png',
  313. height: 32.h,
  314. color: Color.fromRGBO(125, 123, 123, 1.0),
  315. ),
  316. ),
  317. )
  318. ],
  319. )
  320. ]
  321. : [
  322. //1V1语音通话显示名字
  323. Row(
  324. mainAxisAlignment: MainAxisAlignment.center,
  325. children: [
  326. Text(
  327. _remoteUserInfo != null
  328. ? _remoteUserInfo!.name
  329. : "--",
  330. style: TextStyle(
  331. fontSize: 24.sp,
  332. color: Colors.white,
  333. fontWeight: FontWeight.bold,
  334. ),
  335. ),
  336. ],
  337. ),
  338. ],
  339. ),
  340. );
  341. return topWidget;
  342. }
  343. getButtomWidget() {
  344. var callSomeBtnList = [
  345. _currentCallStatus == CallStatus.answer
  346. ? ExtendButton(
  347. imgUrl: _isMicrophoneOff
  348. ? "assets/images/callingDemo/microphone-off.png"
  349. : "assets/images/callingDemo/microphone-on.png",
  350. tips: "麦克风",
  351. onTap: () {
  352. onMicrophoneTap();
  353. },
  354. )
  355. : SizedBox(
  356. height: 0,
  357. width: 0,
  358. ),
  359. ExtendButton(
  360. imgUrl: "assets/images/callingDemo/hangup.png",
  361. tips: "挂断",
  362. onTap: () {
  363. onHangUpCall();
  364. },
  365. ),
  366. _currentCallStatus == CallStatus.answer
  367. ? ExtendButton(
  368. imgUrl: _callingScenes == CallingScenes.VideoOneVOne
  369. ? _isCameraOff
  370. ? "assets/images/callingDemo/camera-off.png"
  371. : "assets/images/callingDemo/camera-on.png"
  372. : _isHandsFree
  373. ? "assets/images/callingDemo/trtccalling_ic_handsfree_enable.png"
  374. : "assets/images/callingDemo/trtccalling_ic_handsfree_disable.png",
  375. tips:
  376. _callingScenes == CallingScenes.VideoOneVOne ? "摄像头" : "扬声器",
  377. onTap: () {
  378. if (_callingScenes == CallingScenes.VideoOneVOne)
  379. onCameraTap();
  380. else
  381. onHandsfreeTap();
  382. },
  383. )
  384. : SizedBox(
  385. height: 0,
  386. width: 0,
  387. ),
  388. ];
  389. if (_currentCallType == CallTypes.Type_Being_Called &&
  390. _currentCallStatus == CallStatus.calling) {
  391. callSomeBtnList.insert(
  392. 2,
  393. SizedBox(
  394. height: 0,
  395. width: 0,
  396. ),
  397. );
  398. callSomeBtnList.insert(
  399. 3,
  400. ExtendButton(
  401. imgUrl: "assets/images/callingDemo/trtccalling_ic_dialing.png",
  402. tips: "接听",
  403. onTap: () {
  404. onAcceptCall();
  405. },
  406. ),
  407. );
  408. }
  409. var buttomWidget = Positioned(
  410. left: 0,
  411. bottom: 50.h,
  412. width: MediaQuery.of(context).size.width,
  413. child: Column(
  414. mainAxisAlignment: MainAxisAlignment.center,
  415. children: [
  416. Container(
  417. margin: EdgeInsets.only(bottom: 20.h),
  418. child: _currentCallStatus == CallStatus.answer
  419. ? Text(
  420. '$_hadCallingTime',
  421. style: TextStyle(color: Colors.white),
  422. )
  423. :
  424. // _callingScenes == CallingScenes.VideoOneVOne
  425. // ? ExtendButton(
  426. // imgUrl: "assets/images/callingDemo/switchToAudio.png",
  427. // imgHieght: 18,
  428. // imgColor: Color.fromRGBO(125, 123, 123, 1.0),
  429. // tips: "切到语音通话",
  430. // onTap: () {
  431. // onSwitchAudioTap();
  432. // },
  433. // ):
  434. SizedBox(
  435. height: 0,
  436. width: 0,
  437. ),
  438. ),
  439. Row(
  440. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  441. children: callSomeBtnList,
  442. )
  443. ],
  444. ),
  445. );
  446. return buttomWidget;
  447. }
  448. getBigVideo() {
  449. if (_callingScenes == CallingScenes.AudioOneVOne) return Container();
  450. bool nowIsLocalView = true; //判断当前大窗口是否显示本地摄像头
  451. if (_currentCallStatus == CallStatus.calling)
  452. nowIsLocalView = true;
  453. else {
  454. //已经接听
  455. if (isChangeBigSmallVideo) {
  456. nowIsLocalView = true;
  457. } else {
  458. nowIsLocalView = false; //远端画面
  459. }
  460. }
  461. var opacityVal = nowIsLocalView
  462. ? _getOpacityByVis(!_isCameraOff)
  463. : _getOpacityByVis(_remoteUserAvailable);
  464. return _callingScenes == CallingScenes.VideoOneVOne
  465. ? AnimatedOpacity(
  466. duration: Duration(milliseconds: 100),
  467. opacity: opacityVal,
  468. child: TRTCCloudVideoView(
  469. key: ValueKey("_bigVideoViewId"),
  470. viewType: TRTCCloudDef.TRTC_VideoView_SurfaceView,
  471. onViewCreated: (viewId) async {
  472. _bigVideoViewId = viewId;
  473. if (_callingScenes == CallingScenes.VideoOneVOne) {
  474. await _tRTCCallingService.openCamera(
  475. _isFrontCamera, _bigVideoViewId);
  476. }
  477. },
  478. ),
  479. )
  480. : Container();
  481. }
  482. getSmallVideoContainer() {
  483. if (_callingScenes == CallingScenes.AudioOneVOne) {
  484. return Container(
  485. height: 100.h,
  486. width: 100.w,
  487. child: Container(),
  488. decoration: _remoteUserInfo != null
  489. ? BoxDecoration(
  490. image: DecorationImage(
  491. image: NetworkImage(_remoteUserInfo!.avatar),
  492. fit: BoxFit.cover,
  493. ),
  494. )
  495. : BoxDecoration(),
  496. );
  497. }
  498. bool nowIsRemoteView = false; //判断当前小窗口是否显示远端画面
  499. if (_currentCallStatus == CallStatus.calling)
  500. nowIsRemoteView = true;
  501. else {
  502. //已经接听
  503. if (isChangeBigSmallVideo) {
  504. nowIsRemoteView = true;
  505. } else {
  506. nowIsRemoteView = false; //本地摄像头
  507. }
  508. }
  509. return Container(
  510. height: _currentCallStatus == CallStatus.calling ? 100.h : 216.h,
  511. width: 100.w,
  512. child: _currentCallStatus == CallStatus.answer
  513. ? AnimatedOpacity(
  514. duration: Duration(milliseconds: 100),
  515. opacity: nowIsRemoteView
  516. ? _getOpacityByVis(_remoteUserAvailable)
  517. : _getOpacityByVis(!_isCameraOff),
  518. child: TRTCCloudVideoView(
  519. key: ValueKey("_smallVideoViewId"),
  520. viewType: TRTCCloudDef.TRTC_VideoView_SurfaceView,
  521. onViewCreated: (viewId) async {
  522. _smallVideoViewId = viewId;
  523. if (Platform.isIOS) {
  524. await _tRTCCallingService
  525. .updateLocalView(_smallVideoViewId);
  526. } else {
  527. await _tRTCCallingService.openCamera(
  528. _isFrontCamera, _smallVideoViewId);
  529. }
  530. },
  531. ),
  532. )
  533. : Container(),
  534. decoration:
  535. _remoteUserInfo != null && _currentCallStatus == CallStatus.calling
  536. ? BoxDecoration(
  537. image: DecorationImage(
  538. image: NetworkImage(_remoteUserInfo!.avatar),
  539. fit: BoxFit.cover,
  540. ),
  541. )
  542. : BoxDecoration(),
  543. );
  544. }
  545. changeVideoView() {
  546. if (_callingScenes == CallingScenes.AudioOneVOne ||
  547. _currentCallStatus == CallStatus.calling) return;
  548. setState(() async {
  549. isChangeBigSmallVideo = !isChangeBigSmallVideo;
  550. //为false的时候,在已接听状态的时候。小画面显示本地视频,大画面显示远端视频。
  551. if (isChangeBigSmallVideo) {
  552. await _tRTCCallingService.updateLocalView(_bigVideoViewId);
  553. await _tRTCCallingService.updateRemoteView(_remoteUserInfo!.userId,
  554. TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_SMALL, _smallVideoViewId);
  555. } else {
  556. await _tRTCCallingService.updateLocalView(_smallVideoViewId);
  557. await _tRTCCallingService.updateRemoteView(_remoteUserInfo!.userId,
  558. TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_SMALL, _bigVideoViewId);
  559. }
  560. });
  561. }
  562. @override
  563. Widget build(BuildContext context) {
  564. var remotePanel = Positioned(
  565. top: _smallViewTop,
  566. right: _callingScenes == CallingScenes.VideoOneVOne
  567. ? _smallViewRight
  568. : MediaQuery.of(context).size.width / 2 - 100 / 2,
  569. child: GestureDetector(
  570. onDoubleTap: () {
  571. if (Platform.isIOS) changeVideoView();
  572. },
  573. onPanUpdate: (DragUpdateDetails e) {
  574. //用户手指滑动时,更新偏移,重新构建
  575. if (_callingScenes == CallingScenes.VideoOneVOne) {
  576. safeSetState(() {
  577. _smallViewRight -= e.delta.dx;
  578. _smallViewTop += e.delta.dy;
  579. });
  580. }
  581. },
  582. child: getSmallVideoContainer(),
  583. ));
  584. return Scaffold(
  585. body: WillPopScope(
  586. onWillPop: () async {
  587. return true;
  588. },
  589. child: Stack(
  590. alignment: Alignment.topLeft,
  591. fit: StackFit.expand,
  592. children: [
  593. Container(
  594. color: Color.fromRGBO(
  595. 93, 91, 90, 1), //Color.fromRGBO(242, 243, 248, 1),
  596. child: getBigVideo(),
  597. ),
  598. remotePanel,
  599. getTopBarWidget(),
  600. getButtomWidget(),
  601. ],
  602. ),
  603. ),
  604. );
  605. }
  606. }