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 'cross_frame.dart'; import 'measure_data_controller.dart'; import 'package:universal_html/html.dart' as html; typedef MeasureImagesFetchFunc = Future<List<ExamImageInfo>> Function( String code); class MeasureController implements IMeasureController { late final ExamInfo _examInfo; IPlayerController? _playerController; IApplication? _application; int _recursive = 0; CrossFrameContext? _crossFrameContext; final List<int> _crossFrameIndexList = []; late bool _enableLoopPlayback; final ValueCallback<bool>? onLoopPlaybackEnableChanged; late final MeasureImagesFetchFunc _imagesFetchFunc; final measureData = Get.find<MeasureDataController>(); final measure3DViewController = Get.find<Measure3DViewController>(); 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<ExamImageInfo?>(); @override Future<void> 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<IApplication>(); Get.delete<IPlayerController>(); } /// 轮询下载接口 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<IPlayerController>(); _windowSetState(index); _playerController = Get.put<IPlayerController>( 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) { if (_crossFrameContext != null && _crossFrameContext!.isOver) { // 跨图恢复状态,切回原图时,无需重新load首帧;等待延迟load初始帧 _crossFrameContext = null; return; } 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; } workingApplication.updateRenderReady.emit(this, null); } void _onFrameUpdated(Object sender, VidUsImage frame) { if (frame.index != 0) { workingApplication.loadFrame(frame); } workingApplication.updateRenderReady.emit(this, null); } void _onPlayerEnvent(Object sender, VidPlayerEvent e) { if (e is VidPlayerStatusChangeEvent) { _application?.canMeasure = e.status == VidPlayStatus.pause; } } Future<void> _buildApplication() async { if (_application != null) { _application!.imageUrl = currentVidUrl; // 跨图时更新 final isPass = _checkAndHandleCrossImage(); // onCrossFramesChanged.call(_crossFrameIndexList); // 更新跨帧指示器 // _updateCrossFrameAnchors(); //TODO 暂不支持跨图,不保留锚点 if (!isPass) { // 仅切图 return; } _disposeCurrentApp(); } 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); } app.imageUrl = currentVidUrl; Get.delete<IApplication>(); _application = Get.put<IApplication>(app); app.isSingleFrame = dataChannel.imageCount == 1; app.crossFrameTriggered.addListener(_onCrossFrameTriggered); app.crossFrameAdded.addListener(_onCrossFrameAdded); if (_crossFrameContext != null) { // 传递跨帧上下文 app.crossFrameContext = _crossFrameContext; } } bool _checkAndHandleCrossImage() { if (_crossFrameContext == null) { return true; } final switchRst = _crossFrameContext!.switchImage(currentVidUrl); if (switchRst) { _application!.updateRenderReady.emit(this, null); // // 切换到指定图,恢复跨帧指示器记录 // _crossFrameIndexList.addAll(_crossFrameContext!.recordIndexList); // _updateCrossFrameAnchors(); return false; } else { // 超过跨图限制,结束之前的测量 return true; } } void _disposeCurrentApp() { _application?.crossFrameTriggered.removeListener(_onCrossFrameTriggered); _application?.crossFrameAdded.removeListener(_onCrossFrameAdded); _application = null; } void _onCrossFrameTriggered(_, e) { final crossContext = CrossFrameContext( currentVidUrl, _application!.frameData!, _application!.activeMeasureItem!, ); // crossContext.imageCount = _imageCount; // crossContext.probe = _probe; crossContext.measureAutoOver.addListener(_onCrossMeasureAutoOver); _crossFrameContext = crossContext; _application!.crossFrameContext = crossContext; } void _onCrossMeasureAutoOver(_, e) { _crossFrameContext!.finish(); _crossFrameContext = null; _clearCrossFrameAnchors(); } void _onCrossFrameAdded(_, int e) { if (!_crossFrameIndexList.contains(e)) { _crossFrameIndexList.add(e); _updateCrossFrameAnchors(); } } void _updateCrossFrameAnchors() { final controller = playerController as VidPlayerController; controller.crossFrameAnchors = _crossFrameIndexList; } void _clearCrossFrameAnchors() { _crossFrameIndexList.clear(); _updateCrossFrameAnchors(); } void _windowSetState(int index) { try { final measureData = Get.find<MeasureDataController>(); 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) {} } }