melon.yin 2 жил өмнө
parent
commit
ea26ed2c06

+ 6 - 0
lib/interfaces/process/player/play_controller.dart

@@ -24,6 +24,9 @@ abstract class IPlayerController {
   /// 加载资源
   Future<bool> load();
 
+  /// 定位到指定帧
+  Future<bool> locateTo(int index);
+
   /// 播放
   void play();
 
@@ -36,6 +39,9 @@ abstract class IPlayerController {
   /// 事件处理
   late final FEventHandler<VidPlayerEvent> eventHandler;
 
+  /// 帧更新事件
+  late final FEventHandler<VidUsImage> frameUpdated;
+
   /// 首帧事件处理
   late final FEventHandler<VidUsImage> firstFrameLoaded;
 

+ 1 - 1
lib/interfaces/process/workspace/application.dart

@@ -72,7 +72,7 @@ abstract class IApplication {
   IAnnotationItem? get activeAnnotationItem;
 
   /// 测量项集合
-  Set<IMeasureItem> get measureItems;
+  List<IMeasureItem> get measureItems;
 
   /// 注释项集合
   Set<IAnnotationItem> get annotationItems;

+ 66 - 1
lib/measure_page_test.dart

@@ -332,7 +332,72 @@ class _MeasureRightBoardState extends State<MeasureRightBoard> {
   Widget build(BuildContext context) {
     return Container(
       padding: const EdgeInsets.all(8).copyWith(left: 0),
-      child: const MeasureMainView(),
+      child: Stack(
+        children: const [
+          MeasureMainView(),
+          _PlayerTips(),
+        ],
+      ),
+    );
+  }
+}
+
+class _PlayerTips extends StatefulWidget {
+  const _PlayerTips({Key? key}) : super(key: key);
+  @override
+  State<StatefulWidget> createState() => _PlayerTipsState();
+}
+
+class _PlayerTipsState extends State<_PlayerTips> {
+  final playerController = Get.find<IPlayerController>();
+  bool loading = false;
+  String content = 'xxxxxxxxxxxxxxxxxxxx';
+
+  @override
+  void initState() {
+    playerController.frameLoadStateChanged.addListener(_onLoadStateChanged);
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    playerController.frameLoadStateChanged.removeListener(_onLoadStateChanged);
+    super.dispose();
+  }
+
+  void _onLoadStateChanged(sender, bool e) {
+    loading = e;
+    if (loading) {
+      Future.delayed(
+        const Duration(milliseconds: 100),
+        () {
+          setState(() {
+            content = loading ? "Loading。。。" : "";
+          });
+        },
+      );
+    } else {
+      setState(() {
+        content = "";
+      });
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Positioned(
+      child: Center(
+        child: Text(
+          content,
+          style: const TextStyle(
+            color: Colors.white,
+            fontSize: 28,
+          ),
+        ),
+      ),
+      top: Get.size.height * 0.44,
+      left: 0,
+      right: 0,
     );
   }
 }

+ 4 - 11
lib/process/workspace/application.dart

@@ -45,7 +45,7 @@ class Application implements IApplication {
   bool _isSingleFrame = true;
   Size _carotid2DSize = Size.zero;
   MeasureOperateType _currOpType = MeasureOperateType.measure;
-  final Set<IMeasureItem> _measureItems = {};
+  final List<IMeasureItem> _measureItems = [];
   final Set<IAnnotationItem> _annotationItems = {};
   late final _recorder = MeasureRecorder(this);
 
@@ -106,7 +106,7 @@ class Application implements IApplication {
   List<IVisual> get visuals => _visuals!;
 
   @override
-  Set<IMeasureItem> get measureItems => _measureItems;
+  List<IMeasureItem> get measureItems => _measureItems;
 
   @override
   Set<IAnnotationItem> get annotationItems => _annotationItems;
@@ -521,8 +521,8 @@ class Application implements IApplication {
 
   void _clearFrameCache() {
     _recorder.clear();
-    _measureItems.clear();
     _annotationItems.clear();
+    _clearVisuals();
   }
 
   void _updateOperateType(MeasureOperateType type) {
@@ -542,7 +542,7 @@ class Application implements IApplication {
   void _doCanMeasureChanged() {
     canMeasureChanged.emit(this, canMeasure);
 
-    _clear();
+    _clearFrameCache();
     if (canMeasure) {
       if (frameData != null) {
         loadVisuals();
@@ -573,13 +573,6 @@ class Application implements IApplication {
     visualsLoaded.emit(this, null);
   }
 
-  void _clear() {
-    for (var item in measureItems) {
-      item.clear();
-    }
-    _clearVisuals();
-  }
-
   void _clearVisuals() {
     _visuals = [];
   }

+ 1 - 1
lib/view/mobile_view/mobile_control_board/play_btn.dart

@@ -40,7 +40,7 @@ class SinglePlayButtonState extends State<SinglePlayButton> {
       onPressed: () {
         if (playing) {
           playerController.pause();
-          playerController.gotoFrame(playerController.currentFrameIndex);
+          // playerController.gotoFrame(playerController.currentFrameIndex);
         } else {
           playerController.play();
         }

+ 1 - 2
lib/view/mobile_view/mobile_control_board/progress_bar.dart

@@ -136,8 +136,7 @@ class _ProgressBarState extends State<_ProgressBar> {
                         constraints: insideConstraints,
                         currentIndex: i,
                         onChanged: () {
-                          controller.pause();
-                          controller.gotoFrame(i.toInt());
+                          controller.locateTo(i.toInt());
                         },
                         aiDiagnosticOrganName: _buildAIDiagnosticOrgans(),
                         curCursorIndex: curCursorIndex,

+ 1 - 1
lib/view/player/control_board/play_btn.dart

@@ -34,7 +34,7 @@ class _PlayButtonState extends State<_PlayButton> {
       onPressed: () {
         if (playing) {
           playerController.pause();
-          playerController.gotoFrame(playerController.currentFrameIndex);
+          // playerController.gotoFrame(playerController.currentFrameIndex);
         } else {
           playerController.play();
         }

+ 5 - 6
lib/view/player/control_board/progress_bar.dart

@@ -151,10 +151,10 @@ class _ProgressBarState extends State<_ProgressBar> {
           alignment: Alignment.center,
           children: [
             Container(
-              margin: const EdgeInsets.only(
-                left: marginSpace,
-                right: marginSpace,
-              ),
+              // margin: const EdgeInsets.only(
+              //   left: marginSpace,
+              //   right: marginSpace,
+              // ),
               child: const StreamingProgressBar(),
               // child: Slider(
               //   max: max,
@@ -181,8 +181,7 @@ class _ProgressBarState extends State<_ProgressBar> {
                           constraints: insideConstraints,
                           currentIndex: i.index,
                           onChanged: () {
-                            controller.pause();
-                            controller.gotoFrame(i.index.toInt());
+                            controller.locateTo(i.index.toInt());
                           },
                           aiDiagnosticOrganName: i.name,
                           curCursorIndex: curCursorIndex,

+ 3 - 3
lib/view/player/control_board/streaming_progress_bar.dart

@@ -45,7 +45,7 @@ class _StreamingProgressBarState extends State<StreamingProgressBar> {
   @override
   Widget build(BuildContext context) {
     return ProgressBar(
-      progress: Duration(milliseconds: currentIndex),
+      progress: Duration(milliseconds: currentIndex + 1),
       total: Duration(milliseconds: totalCount),
       buffered: Duration(milliseconds: bufferedCount),
       // baseBarColor: Colors.blueGrey,
@@ -58,14 +58,14 @@ class _StreamingProgressBarState extends State<StreamingProgressBar> {
       onSeek: (timeStamp) {
         setState(() {
           currentIndex = timeStamp.inMilliseconds;
-          controller.gotoFrame(currentIndex);
+          controller.locateTo(currentIndex);
         });
       },
       onDragUpdate: (details) {
         final timeStamp = details.timeStamp;
         setState(() {
           currentIndex = timeStamp.inMilliseconds;
-          controller.gotoFrame(currentIndex);
+          controller.locateTo(currentIndex);
         });
       },
     );

+ 106 - 26
lib/view/player/controller.dart

@@ -1,7 +1,8 @@
+// ignore_for_file: constant_identifier_names
+
 import 'dart:async';
 
 import 'package:fis_common/event/event_type.dart';
-import 'package:fis_common/func/func_proxy.dart';
 import 'package:fis_measure/interfaces/process/player/play_controller.dart';
 import 'package:fis_ui/index.dart';
 import 'package:fis_vid/data_channel/channel.dart';
@@ -22,22 +23,27 @@ class VidPlayerController extends ChangeNotifier implements IPlayerController {
     _dataChannel = VidDataChannel.create(url);
     _isFirstFrame = true;
     eventHandler = FEventHandler<VidPlayerEvent>();
+    frameUpdated = FEventHandler<VidUsImage>();
     firstFrameLoaded = FEventHandler<VidUsImage>();
     frameLoadStateChanged = FEventHandler<bool>();
     errorOccured = FEventHandler<String?>();
   }
-  // ignore: constant_identifier_names
+
   static const _CAN_PLAY_STATUS_ARR = [
     VidPlayStatus.ready,
     VidPlayStatus.pause
   ];
 
-  // ignore: constant_identifier_names
   static const _HAS_VIEW_STATUS_ARR = [VidPlayStatus.play, VidPlayStatus.pause];
 
+  static const _FRAME_LOAD_RETRY_LIMIT = 120; // 500ms一次,等一分钟
+
   @override
   late final FEventHandler<VidPlayerEvent> eventHandler;
 
+  @override
+  late final FEventHandler<VidUsImage> frameUpdated;
+
   @override
   late final FEventHandler<VidUsImage> firstFrameLoaded;
 
@@ -58,6 +64,8 @@ class VidPlayerController extends ChangeNotifier implements IPlayerController {
   bool _disposed = false;
   bool _isFirstFrame = false;
   bool _loading = false;
+  int _frameLoadRetryCount = 0;
+  Timer? _bufferWaitingTimer;
 
   @override
   String get url => _url;
@@ -135,7 +143,7 @@ class VidPlayerController extends ChangeNotifier implements IPlayerController {
       _frameIndex = -1;
     }
     if (isSingleFrame) {
-      gotoFrame(0);
+      locateTo(0);
       _setStatus(VidPlayStatus.pause);
     } else {
       _playAssistant ??= _PlayAssistant(this);
@@ -155,14 +163,22 @@ class VidPlayerController extends ChangeNotifier implements IPlayerController {
 
   /// Pause and view next frame
   Future<bool> gotoNextFrame() {
-    pause();
-    return gotoFrame(currentFrameIndex + 1);
+    return locateTo(currentFrameIndex + 1);
   }
 
   /// Pause and view prev frame
   Future<bool> gotoPrevFrame() {
+    return locateTo(currentFrameIndex - 1);
+  }
+
+  @override
+  Future<bool> locateTo(int index) async {
+    if (index < 0 || index >= totalFramesCount) return false;
+
     pause();
-    return gotoFrame(currentFrameIndex - 1);
+    _loading = false;
+    gotoFrame(index);
+    return true;
   }
 
   /// View target indexed frame
@@ -255,16 +271,23 @@ class VidPlayerController extends ChangeNotifier implements IPlayerController {
   }
 
   /// 等待更新帧
-  Future<bool> _waitUpdateFrame() async {
+  Future<bool> _waitUpdateFrame([int? timeout]) async {
     if (_disposed) return false;
 
     try {
-      _frame = await _dataChannel.getImage(currentFrameIndex, 500);
+      final t = timeout ?? _playAssistant?._playIntervalMillSeconds ?? 500;
+      _frame = await _dataChannel.getImage(currentFrameIndex, t);
       emitFrameUpdate();
+      _frameLoadRetryCount = 0;
       return true;
     } catch (e) {
       if (e is ReadTimeoutException) {
-        _emitErrorOccured("FrameLoadTimeout");
+        try {
+          await _waitFrameBufferFluently();
+          return await _waitUpdateFrame(500);
+        } catch (e) {
+          _emitErrorOccured("FrameLoadTimeout");
+        }
       } else {
         _emitErrorOccured("FrameLoadError");
       }
@@ -272,17 +295,72 @@ class VidPlayerController extends ChangeNotifier implements IPlayerController {
     return false;
   }
 
-  /// [Carotid] ✅用于设置颈动脉单帧展示
-  void set2DMeasureFrame(VidUsImage _frame) {
-    eventHandler.emit(
-      this,
-      VidPlayerFrameIndexChangeEvent(
-        currentFrameIndex,
-        _frame.imageData,
-        _frame.width,
-        _frame.height,
-      ),
+  Future<void> _waitFrameBufferFluently() async {
+    const duration = Duration(milliseconds: 500);
+    if (isSingleFrame) {
+      await _waitSingleFrameBuffer(duration);
+    } else {
+      await _waitVideoBuffer(duration);
+    }
+    _frameLoadRetryCount = 0;
+  }
+
+  Future<void> _waitSingleFrameBuffer(Duration duration) async {
+    final completer = Completer();
+    Timer.periodic(
+      duration,
+      (timer) {
+        if (_dataChannel.isBufferedDone) {
+          timer.cancel();
+          completer.complete();
+          return;
+        }
+
+        _frameLoadRetryCount++;
+        if (_frameLoadRetryCount >= _FRAME_LOAD_RETRY_LIMIT) {
+          completer.completeError(Exception());
+        }
+      },
+    );
+    return completer.future;
+  }
+
+  Future<void> _waitVideoBuffer(Duration duration) async {
+    final singleSize = currentFrame?.imageData.length ?? 2048;
+    int fluntFrameCount = (_playAssistant!._frameRate * 1).toInt(); //1s缓冲
+    final leftCount = totalFramesCount - (currentFrameIndex + 1);
+    if (fluntFrameCount > leftCount) {
+      fluntFrameCount = leftCount;
+    }
+    final bufferedSize = _dataChannel.getBufferSize();
+    int fluentSize = bufferedSize + fluntFrameCount * singleSize;
+    final totalSize = _dataChannel.getFileSize();
+    if (fluentSize > totalSize) {
+      fluentSize = totalSize;
+    }
+    final completer = Completer();
+    Timer.periodic(
+      duration,
+      (timer) {
+        final bufferedSize = _dataChannel.getBufferSize();
+        if (bufferedSize >= fluentSize) {
+          timer.cancel();
+          completer.complete();
+          return;
+        }
+
+        _frameLoadRetryCount++;
+        if (_frameLoadRetryCount >= _FRAME_LOAD_RETRY_LIMIT) {
+          completer.completeError(Exception());
+        }
+      },
     );
+    return completer.future;
+  }
+
+  /// [Carotid] ✅用于设置颈动脉单帧展示
+  void set2DMeasureFrame(VidUsImage frame) {
+    _emitFrameUpdated(frame);
   }
 
   /// [Carotid] ✅用于重置播放器
@@ -297,14 +375,15 @@ class VidPlayerController extends ChangeNotifier implements IPlayerController {
       _isFirstFrame = false;
     }
 
+    _emitFrameUpdated();
+  }
+
+  void _emitFrameUpdated([VidUsImage? frame]) {
+    // frameUpdated.emit(this, frame ?? _frame!);
+    final f = frame ?? _frame!;
     eventHandler.emit(
       this,
-      VidPlayerFrameIndexChangeEvent(
-        currentFrameIndex,
-        _frame!.imageData,
-        _frame!.width,
-        _frame!.height,
-      ),
+      VidPlayerFrameIndexChangeEvent(f.index, f.imageData, f.width, f.height),
     );
   }
 
@@ -327,6 +406,7 @@ class VidPlayerController extends ChangeNotifier implements IPlayerController {
   @override
   void dispose() {
     _disposed = true;
+    _bufferWaitingTimer?.cancel();
     _stop(needNotify: false);
     eventHandler.dispose();
     _dataChannel.close();

+ 2 - 2
pubspec.lock

@@ -194,8 +194,8 @@ packages:
     dependency: "direct main"
     description:
       path: "."
-      ref: "1.0.4"
-      resolved-ref: "25fb4f44307b61d123476d0298020b2ccd1a38d0"
+      ref: c71ca8880b
+      resolved-ref: c71ca8880beb43c2fadd0727d3e7d5266244c394
       url: "http://git.ius.plus:88/melon.yin/fis_lib_vid.git"
     source: git
     version: "0.0.1"

+ 1 - 1
pubspec.yaml

@@ -89,7 +89,7 @@ dependency_overrides:
   fis_vid:
     git:
       url: http://git.ius.plus:88/melon.yin/fis_lib_vid.git
-      ref: 1.0.4
+      ref: c71ca8880b
     # path: ../fis_lib_vid
   fis_i18n:
     git: