import 'dart:async'; import 'package:fis_i18n/i18n.dart'; import 'package:fis_jsonrpc/rpc.dart'; import 'package:fis_measure/interfaces/process/workspace/exam_info.dart'; import 'package:fis_measure/interfaces/process/workspace/measure_3d_view_controller.dart'; import 'package:fis_measure/interfaces/process/workspace/measure_controller.dart'; import 'package:fis_measure/process/workspace/application.dart'; import 'package:fis_measure/process/workspace/measure_3d_view_controller.dart'; import 'package:fis_measure/process/workspace/third_part/application.dart'; import 'package:fis_measure/utils/prompt_box.dart'; import 'package:fis_measure/view/player/controller.dart'; import 'package:fis_measure/view/player/enums.dart'; import 'package:fis_measure/view/player/events.dart'; import 'package:fis_measure/interfaces/process/workspace/application.dart'; import 'package:fis_measure/interfaces/process/player/play_controller.dart'; import 'package:fis_measure/interfaces/process/config/style_config.dart'; import 'package:fis_common/event/event_type.dart'; import 'package:fis_ui/index.dart'; import 'package:get/get.dart'; import 'package:vid/us/vid_us_image.dart'; import 'measure_data_controller.dart'; import 'package:universal_html/html.dart' as html; typedef MeasureImagesFetchFunc = Future> Function( String code); class MeasureController implements IMeasureController { late final ExamInfo _examInfo; IPlayerController? _playerController; IApplication? _application; int _recursive = 0; late bool _enableLoopPlayback; final ValueCallback? onLoopPlaybackEnableChanged; late final MeasureImagesFetchFunc _imagesFetchFunc; final measureData = Get.find(); final measure3DViewController = Get.find(); MeasureController( String code, { required MeasureImagesFetchFunc imagesFetchFunc, bool enableLoopPlayback = false, this.onLoopPlaybackEnableChanged, }) { _enableLoopPlayback = enableLoopPlayback; _examInfo = ExamInfo(code); _imagesFetchFunc = imagesFetchFunc; } @override ExamInfo get examInfo => _examInfo; @override String get currentVidUrl => examInfo.selectedImage.url; @override bool get canDoMeasure => playerController.status == VidPlayStatus.pause; @override IPlayerController get playerController => _playerController!; @override // TODO: implement styleConfiguration IStyeConfiguration get styleConfiguration => throw UnimplementedError(); @override IApplication get workingApplication => _application!; @override var imageLoaded = FEventHandler(); @override Future load() async { final images = await _imagesFetchFunc.call(examInfo.code); examInfo.images.addAll(images); examInfo.selectedImageChanged.addListener(_onExamImageSelectedChanged); // // 加载第一张图像 // examInfo.selectedImageIndex = 0; } @override void dispose() { examInfo.selectedImageChanged.removeListener(_onExamImageSelectedChanged); _playerController?.dispose(); Get.delete(); Get.delete(); } /// 轮询下载接口 void _recursivePlayer(Object sender) { playerController.load().then((_) async { if (playerController.status != VidPlayStatus.ready) { if (_recursive <= 2) { _recursive += 1; _recursivePlayer(sender); } else { PromptBox.toast(i18nBook.common.networkUnstable.t); _recursive = 0; _recursivePlayer(sender); // throw FlutterError("Vid file load fail."); } } else { playerController.eventHandler.addListener(_onPlayerEnvent); } }); } void _onExamImageSelectedChanged(Object sender, int index) async { final url = currentVidUrl; _playerController?.firstFrameLoaded.removeListener(_onFirstFrameLoaded); _playerController?.frameUpdated.removeListener(_onFrameUpdated); _playerController?.eventHandler.removeListener(_onPlayerEnvent); _playerController?.dispose(); Get.delete(); _windowSetState(index); _playerController = Get.put( VidPlayerController(url) ..enableLoop = _enableLoopPlayback ..enableLoopChanged.addListener(_onEnableLoopPlaybackChanged), ); playerController.firstFrameLoaded.addListener(_onFirstFrameLoaded); playerController.frameUpdated.addListener(_onFrameUpdated); playerController.eventHandler.addListener(_onPlayerEnvent); _tryLoadPlayer(); } void _onEnableLoopPlaybackChanged(Object _, bool e) { _enableLoopPlayback = e; onLoopPlaybackEnableChanged?.call(e); } void _tryLoadPlayer() async { int tryCount = 0; const limitCount = 5; bool loaded = false; while (loaded == false) { if (limitCount <= tryCount) break; if (playerController.disposed) break; loaded = await playerController.load(); if (loaded == false) { if (tryCount == 0) { PromptBox.toast(i18nBook.common.networkUnstable.t); } } tryCount++; } if (playerController.disposed) return; if (loaded) { _onPlayerLoaded(); } } void _onPlayerLoaded() { _buildApplication(); // 通知图像加载完成 imageLoaded.emit(this, examInfo.selectedImage); } void _onFirstFrameLoaded(Object sender, VidUsImage frame) { workingApplication.loadFrame(frame); if (workingApplication.canMeasure) { if (workingApplication.frameData!.index != 0) { // 非首帧,切换第一个测量项 final items = measureData.curItemMetaList; if (items.isNotEmpty) { workingApplication.switchItem(items.first); } } } /// 非颈动脉2D模式下需要将 isAdaptiveCarotid2D 置为 false if (measure3DViewController.curMeasureMode != MeasureMode.carotid2DMode) { workingApplication.isAdaptiveCarotid2D = false; } } void _onFrameUpdated(Object sender, VidUsImage frame) { if (frame.index != 0) workingApplication.loadFrame(frame); } void _onPlayerEnvent(Object sender, VidPlayerEvent e) { if (e is VidPlayerStatusChangeEvent) { _application?.canMeasure = e.status == VidPlayStatus.pause; } } Future _buildApplication() async { final dataChannel = playerController.dataChannel; final probe = dataChannel.probe; final is3rd = probe.name == Application.C_VID_THIRDPART_NAME; IApplication app; if (is3rd) { app = ThirdPartApplication(probe, dataChannel.extendedData); } else { app = Application(probe); } Get.delete(); _application = Get.put(app); _application!.isSingleFrame = dataChannel.imageCount == 1; } void _windowSetState(int index) { try { final measureData = Get.find(); var remedicalList = measureData.remedicalList; var examCode = examInfo.code; if (index > remedicalList.length - 1) { return; } var remedicalInfo = remedicalList[index]; if (remedicalInfo != null) { var searchUrl = html.window.location.search ?? ''; var remedicalCode = remedicalInfo.remedicalCode; var parameters = searchUrl.split('&'); if (parameters.isNotEmpty) { var target = parameters .firstWhereOrNull((element) => element.contains("remedicalCode")); if (target != null) { var paraneter = "remedicalCode=$remedicalCode"; var newSearchUrl = searchUrl.replaceAll(target, paraneter); html.window.history.replaceState('', '', newSearchUrl); } } } } catch (e) {} } }