|
@@ -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();
|