123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- 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) {}
- }
- }
|