liveconsultationcontroller.dart 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. import 'dart:async';
  2. import 'package:fis_common/event/event_type.dart';
  3. import 'package:fis_common/index.dart';
  4. import 'package:fis_i18n/i18n.dart';
  5. import 'package:fis_jsonrpc/rpc.dart';
  6. import 'package:fis_lib_media_rt/fis_lib_media_rt.dart';
  7. import 'package:flutter/services.dart';
  8. import 'package:flyinsonolite/consultation/liveconsultation/controllers/livememberscontroller.dart';
  9. import 'package:flyinsonolite/consultation/liveconsultation/controllers/mediacontroller.dart';
  10. import 'package:flyinsonolite/consultation/records/controllers/consultationdetailcontroller.dart';
  11. import 'package:flyinsonolite/consultation/records/controllers/consultationlistcontroller.dart';
  12. import 'package:flyinsonolite/consultation/liveconsultation/models/liveconsultationstate.dart';
  13. import 'package:flyinsonolite/consultation/liveconsultation/controllers/liveloadingcontroller.dart';
  14. import 'package:flyinsonolite/consultation/liveconsultation/controllers/memberlisteners.dart';
  15. import 'package:flyinsonolite/consultation/liveconsultation/controllers/whiteboardcontroller.dart';
  16. import 'package:flyinsonolite/controls/prompt.dart';
  17. import 'package:flyinsonolite/jsonrpc/fisLib/services/consultation.m.dart';
  18. import 'package:flyinsonolite/jsonrpc/fisLib/services/log.m.dart';
  19. import 'package:flyinsonolite/helpers/dialoghelper.dart';
  20. import 'package:flyinsonolite/jsonrpc/jsonrpcclient.dart';
  21. import 'package:flyinsonolite/jsonrpc/jsonrpcclientforFISLib.dart';
  22. import 'package:flyinsonolite/infrastructure/logger.dart';
  23. import 'package:flyinsonolite/managers/interfaces/iappointmentmanager.dart';
  24. import 'package:flyinsonolite/managers/interfaces/idevicemanager.dart';
  25. import 'package:flyinsonolite/managers/interfaces/iliveconsultationmanager.dart';
  26. import 'package:flyinsonolite/consultation/records/models/consultationmember.dart';
  27. import 'package:flyinsonolite/consultation/records/models/liveconsultation.dart';
  28. import 'package:flyinsonolite/infrastructure/storage.dart';
  29. import 'package:flyinsonolite/helpers/parameterhelper.dart';
  30. import 'package:get/get.dart';
  31. import 'recordingcontroller.dart';
  32. class LiveConsultationController extends GetxController {
  33. final ILiveConsultationManager _liveConsultationManager =
  34. Get.find<ILiveConsultationManager>();
  35. final IAppointmentManager _appointmentManager =
  36. Get.find<IAppointmentManager>();
  37. bool _isJoin = false;
  38. //String? _consultationCode;
  39. List<ConsultationMember>? _oldMemberList;
  40. late RecordingController recordingController;
  41. late MediaController mediaController;
  42. late LiveLoadingController loadingController;
  43. late LiveMembersController membersController;
  44. late WhiteboardController whiteboardController;
  45. late MemberListeners memberListeners;
  46. ConsultationDetailDTO? consultationDetail;
  47. final state = LiveConsultationState();
  48. bool get isEmergency =>
  49. consultationDetail == null ? false : consultationDetail!.isEmergency;
  50. bool get isRtmpMode =>
  51. FRTMediaConfig.serviceType == FRealTimeMediaServiceType.ntRtmp;
  52. ///设备频道加入房间事件
  53. //final FEventHandler<IPlayChannel> onDeviceChannelAdd = FEventHandler<IPlayChannel>();
  54. //FEventHandler<bool> onSwitchPosition = FEventHandler<bool>();
  55. /// 获取到清除事件通知
  56. FEventHandler<String> onClearCanavsHandler = FEventHandler<String>();
  57. /// 白板数据接收
  58. FEventHandler<String> get onWhiteboardDataReceive =>
  59. _liveConsultationManager.onWhiteboardDataReceive;
  60. LiveConsultationController({bool isJoin = false}) {
  61. _isJoin = isJoin;
  62. mediaController = MediaController(this);
  63. loadingController = LiveLoadingController(this);
  64. membersController = LiveMembersController(this);
  65. memberListeners = MemberListeners(this);
  66. recordingController = RecordingController(this);
  67. whiteboardController = WhiteboardController(this);
  68. consultationDetail = _liveConsultationManager.consultationDetail;
  69. state.consultationDetail = consultationDetail;
  70. }
  71. @override
  72. void onReady() async {
  73. try {
  74. super.onReady();
  75. await _initDataAsync();
  76. ///初始化截图列表
  77. await recordingController.initCaptureImageListAsync();
  78. } catch (ex) {
  79. await handlerErrorAsync(
  80. ex, 'LiveConsultationController onReady error:$ex');
  81. }
  82. }
  83. @override
  84. void onClose() async {
  85. recordingController.dispose();
  86. await _destroyMemoryAsync();
  87. super.onClose();
  88. }
  89. ///获取成员播放频道
  90. IChannel? getUserMemberPreviewChannel(ConsultationMember member) {
  91. return mediaController.getUserMemberPreviewChannel(member);
  92. }
  93. ///初始化数据
  94. Future _initDataAsync() async {
  95. var parameters = ParameterHelper.parameterMap;
  96. if (parameters.isNotEmpty) {
  97. var isJoinStr = parameters["isJoin"] ?? '';
  98. _isJoin = (isJoinStr == 'true' || isJoinStr == 'True');
  99. }
  100. await _initConsultationInfo();
  101. if (state.liveConsultation.value == null) {
  102. return;
  103. }
  104. if (!isRtmpMode) {
  105. await membersController.startHeartbeatAsync();
  106. }
  107. var deviceMember = state.liveConsultation.value!.memberLiveDatas
  108. .firstWhereOrNull(
  109. (element) => element.memberType == LiveMemberEnum.Device);
  110. if (deviceMember != null && deviceMember.isOnline) {
  111. loadingController.startLoading();
  112. } else if (deviceMember?.isOnline == false) {
  113. loadingController.showRetryButton();
  114. Future.delayed(const Duration(milliseconds: 1000), () {
  115. PromptBox.toast(i18nBook.errorCodes.errorCode806.t);
  116. });
  117. }
  118. if (deviceMember != null) {
  119. var deviceCode = deviceMember.id ?? '';
  120. final deviceManager = Get.find<IDeviceManager>();
  121. var deviceInfo = await deviceManager.getDeviceInfoAsync(deviceCode);
  122. if (deviceInfo != null && deviceInfo.deviceType != 'US') {
  123. state.isUSDevice = false;
  124. }
  125. }
  126. memberListeners.addListeners();
  127. await membersController.initWaitInLineForConsultationPatientsAsync();
  128. await mediaController.initAsync();
  129. }
  130. ///初始化会诊信息
  131. Future<void> _initConsultationInfo({bool isReload = false}) async {
  132. try {
  133. if (consultationDetail == null) {
  134. return;
  135. }
  136. _liveConsultationManager.currentConsultationCode =
  137. consultationDetail!.consultationCode!;
  138. if (!isReload) {
  139. Storage.isConsultating.value = true;
  140. state.liveConsultation.value = await _liveConsultationManager
  141. .startConsultationAsync(consultationDetail!, isJoin: _isJoin);
  142. if (isRtmpMode) {
  143. await startRtmpPushing();
  144. }
  145. var interactiveBoardDatas =
  146. state.liveConsultation.value?.interactiveBoardDatas ?? [];
  147. List<String> initBoardDatas = [];
  148. for (var interactiveBoardData in interactiveBoardDatas) {
  149. initBoardDatas.add(interactiveBoardData.boardData ?? '');
  150. }
  151. state.initBoardDatas.value = initBoardDatas;
  152. var memberLiveDatas = state.liveConsultation.value?.memberLiveDatas;
  153. if (memberLiveDatas != null) {
  154. for (var member in memberLiveDatas) {
  155. if (!member.isOnline) {
  156. member.status.value = MemberStatus.Offline;
  157. if (member.memberType == LiveMemberEnum.Device) {
  158. loadingController.showRetryButton();
  159. }
  160. } else if (member.isBusy) {
  161. member.status.value = MemberStatus.Busyline;
  162. } else if (member.memberType == LiveMemberEnum.User &&
  163. member.status.value == MemberStatus.Default) {
  164. member.status.value = MemberStatus.Calling;
  165. }
  166. }
  167. }
  168. } else {
  169. var interactiveBoardDatas =
  170. state.liveConsultation.value?.interactiveBoardDatas ?? [];
  171. List<String> initBoardDatas = [];
  172. for (var interactiveBoardData in interactiveBoardDatas) {
  173. initBoardDatas.add(interactiveBoardData.boardData ?? '');
  174. }
  175. state.initBoardDatas.value = initBoardDatas;
  176. var memberLiveDatas = state.liveConsultation.value?.memberLiveDatas;
  177. if (memberLiveDatas != null) {
  178. for (var member in memberLiveDatas) {
  179. if (!member.isOnline) {
  180. member.status.value = MemberStatus.Offline;
  181. if (member.memberType == LiveMemberEnum.Device) {
  182. loadingController.showRetryButton();
  183. }
  184. } else if (member.isBusy) {
  185. member.status.value = MemberStatus.Busyline;
  186. } else if (member.memberType == LiveMemberEnum.User &&
  187. member.status.value == MemberStatus.Default) {
  188. member.status.value = MemberStatus.Calling;
  189. }
  190. }
  191. }
  192. }
  193. } on JsonRpcException {
  194. await Future.delayed(const Duration(milliseconds: 1500));
  195. await postProcessAsync();
  196. } catch (e) {
  197. await loggerAsyn(FISDeviceLogCategory.Error,
  198. 'LiveConsultationController _initConsultationInfo ex:$e');
  199. }
  200. }
  201. Future<void> startRtmpPushing() async {
  202. try {
  203. var fisLiveConsultationRequestDTO = FISStartOnlyForRtmpPushingDTO(
  204. consultationCode: consultationDetail!.consultationCode!,
  205. token: Storage.user.token,
  206. rtmpPushingUrl: state.liveConsultation.value?.memberLiveDatas
  207. .firstWhereOrNull(
  208. (element) => element.id == Storage.user.userCode)
  209. ?.liveData
  210. ?.rtmpPushUrl);
  211. await jsonRpcProxyForFISLib.consultation
  212. .startOnlyForRtmpPushingFromFlutter(fisLiveConsultationRequestDTO);
  213. } catch (ex) {
  214. await handlerErrorAsync(ex, "Push RTMP Error:$ex");
  215. }
  216. }
  217. Future _destroyMemoryAsync() async {
  218. //stopFullRecording();
  219. await _unsetMobilePreferredOrientationsAsync();
  220. membersController.destroyMemory();
  221. memberListeners.dispose();
  222. await mediaController.exit();
  223. //onSwitchPosition.dispose();
  224. }
  225. Future _unsetMobilePreferredOrientationsAsync() async {
  226. if (FPlatform.isAndroid || FPlatform.isIOS) {
  227. // 恢复竖屏
  228. await SystemChrome.setPreferredOrientations(
  229. [
  230. DeviceOrientation.portraitUp, // 竖屏 Portrait 模式
  231. DeviceOrientation.portraitDown,
  232. ],
  233. );
  234. SystemChrome.setPreferredOrientations([
  235. DeviceOrientation.portraitUp,
  236. ]);
  237. SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
  238. overlays: SystemUiOverlay.values);
  239. SystemChrome.setSystemUIChangeCallback((systemOverlaysAreVisible) async {
  240. // do nothing
  241. });
  242. }
  243. }
  244. ///离开房间
  245. void exitConsultation() {
  246. DialogHelper.showConfirmDialog(
  247. Get.context!, i18nBook.realTimeConsultation.confirmLeaveConsultation.t,
  248. () async {
  249. try {
  250. Get.close(1);
  251. var result = await _liveConsultationManager.leaveLiveConsultationAsync(
  252. consultationDetail!.consultationCode ?? '');
  253. var consultationCode = result.consultationCode ?? '';
  254. if (consultationCode.isNotEmpty) {
  255. await loggerAsyn(FISDeviceLogCategory.Info,
  256. '$consultationCode endConsultation success');
  257. } else {
  258. await loggerAsyn(FISDeviceLogCategory.Info,
  259. '$consultationCode endConsultation failed');
  260. }
  261. await jsonRpcProxy.user
  262. .removeUserSingleTokenAsync(RemoveUserSingleTokenRequest(
  263. token: Storage.user.token,
  264. wSToken: "${Storage.user.token}_1",
  265. ));
  266. } finally {
  267. await postProcessAsync();
  268. }
  269. });
  270. }
  271. ///对方离开通知到自己时调用
  272. Future<void> leaveConsultationAsync() async {
  273. try {
  274. if (isRtmpMode) {
  275. await stopRtmpPushing();
  276. } else {
  277. var result = await _liveConsultationManager.leaveLiveConsultationAsync(
  278. consultationDetail!.consultationCode ?? '');
  279. var consultationCode = result.consultationCode ?? '';
  280. if (consultationCode.isNotEmpty) {
  281. await loggerAsyn(FISDeviceLogCategory.Info,
  282. '$consultationCode endConsultation success');
  283. } else {
  284. await loggerAsyn(FISDeviceLogCategory.Info,
  285. '$consultationCode endConsultation failed');
  286. }
  287. }
  288. await jsonRpcProxy.user
  289. .removeUserSingleTokenAsync(RemoveUserSingleTokenRequest(
  290. token: Storage.user.token,
  291. wSToken: "${Storage.user.token}_1",
  292. ));
  293. } finally {
  294. await postProcessAsync();
  295. }
  296. }
  297. Future<void> sendFlutterLiveConsultationInfo() async {
  298. try {
  299. await jsonRpcProxyForFISLib.consultation.sendFlutterLiveConsultationInfo(
  300. FISFlutterLiveConsultationInfo(
  301. isStartLiveConsultation: false,
  302. token: Storage.user.token,
  303. consultationCode: "",
  304. liveConsultationFlutterUrl: ""));
  305. } catch (ex) {
  306. await loggerAsyn(FISDeviceLogCategory.Error,
  307. "LiveConsultationController sendFlutterLiveConsultationInfo Error:$ex");
  308. }
  309. }
  310. Future<void> stopRtmpPushing() async {
  311. try {
  312. await jsonRpcProxyForFISLib.consultation
  313. .stopOnlyForRtmpPushingFromFlutter();
  314. } catch (ex) {
  315. await handlerErrorAsync(ex, "StopRtmpPushing Error:$ex");
  316. }
  317. }
  318. Future<void> addMember() async {
  319. return await membersController.addMember();
  320. }
  321. ///取消呼叫
  322. Future<void> cancelCall(String targetId) async {
  323. return await membersController.cancelCall(targetId);
  324. }
  325. ///再次呼叫
  326. Future<void> callAgain(String targetId) async {
  327. return await membersController.callAgain(targetId);
  328. }
  329. ///重新加载病人信息,房间不变
  330. Future<void> reload(LiveConsultation consultation) async {
  331. await sendInteractiveBoardDataAsync('', true);
  332. //图像列表置空
  333. for (ConsultationMember m
  334. in state.liveConsultation.value?.memberLiveDatas ?? []) {
  335. if (m.memberType == LiveMemberEnum.User) {
  336. onClearCanavsHandler.emit('', m.id ?? '');
  337. }
  338. }
  339. state.captureImageList.value = [];
  340. state.liveConsultation.value = consultation;
  341. _liveConsultationManager.liveConsultation = consultation;
  342. var newConsultationDetail = await _appointmentManager
  343. .findConsultationDetailAsync(consultation.consultationCode!);
  344. _liveConsultationManager.consultationDetail = newConsultationDetail!;
  345. consultationDetail = newConsultationDetail;
  346. try {
  347. if (isRtmpMode || Storage.isSecondView) {
  348. //第二窗口或者RTMP推流时需要通知FIS,告知当前的ConsultationCode,退出会诊时以及RTMP心跳包都要用到
  349. await jsonRpcProxyForFISLib.consultation
  350. .changeConsultationCodeOnlyForRtmpPushingFromFlutter(
  351. FISChangeLiveConsultationDTO(
  352. token: Storage.user.token,
  353. consultationCode: consultation.consultationCode,
  354. members: []));
  355. }
  356. } catch (ex) {
  357. await handlerErrorAsync(
  358. ex, "changeConsultationCodeOnlyForRtmpPushingFromFlutter Error:$ex");
  359. }
  360. //_consultationCode = consultation.consultationCode;
  361. _oldMemberList = state.liveConsultation.value!.memberLiveDatas.toList();
  362. ConsultationMember? oldDeviceInfo = _oldMemberList?.firstWhereOrNull(
  363. (element) => element.memberType == LiveMemberEnum.Device);
  364. ConsultationMember? newDeviceInfo = consultation.memberLiveDatas
  365. .firstWhereOrNull(
  366. (element) => element.memberType == LiveMemberEnum.Device);
  367. if (newDeviceInfo != null) {
  368. consultation.memberLiveDatas.remove(
  369. newDeviceInfo); //之所以移除新的DeviceInfo是因为设备是不变的,且通知给的少了VideoDeviceInfo的信息
  370. }
  371. if (oldDeviceInfo != null) {
  372. consultation.memberLiveDatas.add(oldDeviceInfo);
  373. }
  374. state.liveConsultation.value = consultation;
  375. _initConsultationInfo(isReload: true);
  376. }
  377. ///会诊设备重试进入房间
  378. Future<void> retryLoadDevice() async {
  379. try {
  380. loadingController.startLoading();
  381. await _liveConsultationManager
  382. .retryLoadDeviceAsync(consultationDetail!.consultationCode!);
  383. } catch (e) {
  384. await loggerAsyn(FISDeviceLogCategory.Error,
  385. 'LiveConsultationController retryLoadDevice ex:$e');
  386. }
  387. }
  388. void updateSelectedPatient(ConsultationPageDTO? consultationPageDTO) {
  389. if (consultationDetail!.consultationCode ==
  390. consultationPageDTO!.consultationCode) {
  391. return;
  392. }
  393. DialogHelper.showConfirmDialog(
  394. Get.context!, i18nBook.remedical.confirmCloseCurrentConsultation.t,
  395. () async {
  396. Get.close(1);
  397. await onSelectedPatientChangeAsync(consultationPageDTO);
  398. });
  399. }
  400. ///更换病人
  401. Future<void> onSelectedPatientChangeAsync(
  402. ConsultationPageDTO? consultationPageDTO) async {
  403. if (consultationPageDTO == null) {
  404. return;
  405. }
  406. try {
  407. await sendInteractiveBoardDataAsync('', true);
  408. //图像列表置空
  409. for (ConsultationMember m
  410. in state.liveConsultation.value?.memberLiveDatas ?? []) {
  411. if (m.memberType == LiveMemberEnum.User) {
  412. onClearCanavsHandler.emit('', m.id ?? '');
  413. }
  414. }
  415. _liveConsultationManager.currentConsultationCode =
  416. consultationPageDTO.consultationCode!;
  417. final originalCode = consultationDetail!.consultationCode!;
  418. var result = await _liveConsultationManager.changeConsultationAsync(
  419. originalCode, consultationPageDTO.consultationCode!);
  420. var liveConsultation = LiveConsultation(
  421. consultationCode: result.consultationCode,
  422. roomNo: result.roomNo,
  423. initiatorCode: result.initiatorCode,
  424. );
  425. liveConsultation.consultationMemberConvert(result.memberLiveDatas ?? []);
  426. state.liveConsultation.value = liveConsultation;
  427. _liveConsultationManager.liveConsultation = liveConsultation;
  428. var newConsultationDetail = await _appointmentManager
  429. .findConsultationDetailAsync(consultationPageDTO.consultationCode!);
  430. _liveConsultationManager.consultationDetail = newConsultationDetail!;
  431. consultationDetail = newConsultationDetail;
  432. await _initConsultationInfo(isReload: true);
  433. await membersController.initWaitInLineForConsultationPatientsAsync();
  434. } catch (ex) {
  435. await handlerErrorAsync(ex,
  436. "LiveConsultationController onSelectedPatientChangeAsync error: $ex");
  437. }
  438. }
  439. /// 发送白板数据
  440. Future<void> sendInteractiveBoardDataAsync(String boardData,
  441. [bool isClear = false]) async {
  442. await _liveConsultationManager.sendInteractiveBoardDataAsync(
  443. consultationDetail!.consultationCode!, boardData, isClear);
  444. }
  445. /// 当前颜色
  446. Color currectColor() {
  447. return whiteboardController.currectColor();
  448. }
  449. /// 截取一帧 vid
  450. Future captureOneFrameAsync() async {
  451. recordingController.captureOneFrameAsync();
  452. }
  453. ///开启录制
  454. Future startRecordingAsync() async {
  455. await recordingController.startRecordingAsync();
  456. }
  457. ///结束录制
  458. Future endRecordingAsync() async {
  459. await recordingController.endRecordingAsync();
  460. }
  461. //后处理
  462. Future postProcessAsync() async {
  463. if (Storage.isSecondView) {
  464. await sendFlutterLiveConsultationInfo();
  465. }
  466. _liveConsultationManager.currentConsultationCode = "";
  467. Storage.isConsultating.value = false;
  468. if (Storage.isSecondView) {
  469. Get.close(1);
  470. } else {
  471. if (Storage.isLogouting) {
  472. Get.close(1);
  473. } else {
  474. await Future.delayed(const Duration(milliseconds: 500));
  475. Get.close(1);
  476. var consultationListController = Get.find<ConsultationListController>();
  477. await consultationListController.refreshAsync();
  478. var consultationDetailController =
  479. Get.find<ConsultationDetailController>();
  480. await consultationDetailController.onTabChangedAsync(0);
  481. }
  482. }
  483. }
  484. }