// ignore_for_file: constant_identifier_names import 'dart:async'; import 'package:fis_common/event/event_type.dart'; import 'package:fis_common/logger/logger.dart'; import 'package:fis_i18n/i18n.dart'; import 'package:fis_measure/interfaces/process/player/play_controller.dart'; import 'package:fis_measure/view/player/buffer_waiter.dart'; import 'package:fis_vid/data_channel/channel.dart'; import 'package:flutter/foundation.dart'; import 'package:vid/us/vid_us_image.dart'; import 'package:vid/us/vid_us_image_data.dart'; import 'enums.dart'; import 'events.dart'; /// Vid播放器控制器 class VidPlayerController extends ChangeNotifier implements IPlayerController { /// Vid播放器控制器 /// /// [url] Vid文件链接 VidPlayerController(String url) { _url = url; _dataChannel = VidDataChannel.create(url); _bufferWaiter = VidBufferWaiter(_dataChannel); _isFirstFrame = true; eventHandler = FEventHandler(); frameUpdated = FEventHandler(); firstFrameLoaded = FEventHandler(); frameLoadStateChanged = FEventHandler(); errorOccured = FEventHandler(); } static const _CAN_PLAY_STATUS_ARR = [ VidPlayStatus.ready, VidPlayStatus.pause ]; static const _HAS_VIEW_STATUS_ARR = [VidPlayStatus.play, VidPlayStatus.pause]; @override late final FEventHandler eventHandler; @override late final FEventHandler frameUpdated; @override late final FEventHandler firstFrameLoaded; @override late final FEventHandler frameLoadStateChanged; @override late final FEventHandler errorOccured; final enableLoopChanged = FEventHandler(); late final String _url; late final VidDataChannel _dataChannel; late final VidBufferWaiter _bufferWaiter; _PlayAssistant? _playAssistant; VidPlayStatus _status = VidPlayStatus.init; int _frameIndex = -1; VidUsImage? _frame; bool _disposed = false; bool _isFirstFrame = false; bool _loading = false; bool _enableLoop = false; /// 是否开启循环播放 bool get enableLoop => _enableLoop; set enableLoop(bool val) { if (val != _enableLoop) { _enableLoop = val; enableLoopChanged.emit(this, val); } } @override String get url => _url; @override VidDataChannel get dataChannel => _dataChannel; @override bool get disposed => _disposed; @override VidPlayStatus get status => _status; @override VidUsImage? get currentFrame => _frame; /// Whether the player is playing bool get playing => status == VidPlayStatus.play; /// Whether the player can play bool get canPlay => _CAN_PLAY_STATUS_ARR.contains(status); /// Whether the player should has view @override bool get hasView => _HAS_VIEW_STATUS_ARR.contains(status); /// Current viewed frame index int get currentFrameIndex => _frameIndex; /// Total frames count of current vid int get totalFramesCount => _dataChannel.imageCount; bool get isSingleFrame => totalFramesCount == 1; /// 是否播放结束 bool get isEndOfPlay => currentFrameIndex == totalFramesCount - 1; /// 当前播放器亮度 初始值为 0 double get brightness => _brightness; double _brightness = 0.0; /// 当前播放器对比度 初始值为 1 double get contrast => _contrast; double _contrast = 1.0; /// vid头信息尺寸(含扩展) int get vidHeaderSize => _bufferWaiter.vidHeaderSize; @override Future load() async { final loaded = await _dataChannel.load(10 * 1000); if (loaded) { _bufferWaiter.init(); _setStatus(VidPlayStatus.ready); logger.i( "Vid load successed. ImageCount:${_dataChannel.imageCount}, FrameRate:${_dataChannel.probe.frameRate} Url: $url"); } else { _setStatus(VidPlayStatus.loadFail); logger.i("Vid load failed. Url: $url"); } return loaded; } @override void play() { if (playing) return; if (!canPlay) return; if (isEndOfPlay) { _frameIndex = -1; } if (isSingleFrame) { locateTo(0); _setStatus(VidPlayStatus.pause); } else { _playAssistant ??= _PlayAssistant(this); _playAssistant!.play(); _setStatus(VidPlayStatus.play); } } @override void pause() { if (!playing) return; _playAssistant?.pause(); _setStatus(VidPlayStatus.pause); } /// Pause and view next frame Future gotoNextFrame() { return locateTo(currentFrameIndex + 1); } /// Pause and view prev frame Future gotoPrevFrame() { return locateTo(currentFrameIndex - 1); } @override Future locateTo(int index) async { if (index < 0 || index >= totalFramesCount) return false; pause(); _loading = false; gotoFrame(index); return true; } /// View target indexed frame /// /// [index] frame index Future gotoFrame(int index) async { if (index < 0 || index >= totalFramesCount) return false; if (_loading) return false; _frameIndex = index; _loading = true; _updateFrameLoadState(true); final start = DateTime.now(); final result = await _waitUpdateFrame(); final end = DateTime.now(); if (result) { _updateFrameLoadState(false); final spendTime = end.difference(start).inMilliseconds; _bufferWaiter.recordFrameSpendTime(spendTime); } return result; } /// Set frame brightness /// /// [value] brightness value void setBrightness(int value) { final brightnessCount = value / 100; if (brightnessCount < -1 || brightnessCount > 1) { return; } _brightness = brightnessCount * 255; final fliterMatrix = [ contrast, 0, 0, 0, brightness, // red 0, contrast, 0, 0, brightness, // green 0, 0, contrast, 0, brightness, // blue 0, 0, 0, 1, 0, // alpha // alpha ]; eventHandler.emit(this, VidPlayerFilterChangeEvent(fliterMatrix)); _reloadFrame(); } /// Set frame contrast /// /// [value] contrast value void setContrast(int value) { double contrastCount = 1; if (value < 0) { contrastCount = (value + 100) / 100; } else if (value >= 0) { contrastCount = value / 100 * 9 + 1; } if (contrastCount < 0 || contrastCount > 10) { return; } _contrast = contrastCount; final fliterMatrix = [ contrast, 0, 0, 0, brightness, // red 0, contrast, 0, 0, brightness, // green 0, 0, contrast, 0, brightness, // blue 0, 0, 0, 1, 0, // alpha ]; eventHandler.emit(this, VidPlayerFilterChangeEvent(fliterMatrix)); _reloadFrame(); } void setFilterMatrix(List matrix) { eventHandler.emit(this, VidPlayerFilterChangeEvent(matrix)); } /// 重置图像增益 void resetTone() { setBrightness(0); setContrast(0); eventHandler.emit(this, VidPlayResetToneEvent()); } void _reloadFrame() { gotoFrame(currentFrameIndex); } void _updateFrameLoadState(bool val) { _loading = val; frameLoadStateChanged.emit(this, _loading); } void _emitErrorOccured([String? msg]) { errorOccured.emit(this, msg); } /// 等待更新帧 Future _waitUpdateFrame() async { if (_disposed) return false; Future _fetchOnce() async { int timeout = 500; if (!isSingleFrame) { // 视频:一帧刷新时长,buffer 10ms 处理渲染 timeout = _playAssistant!._playIntervalMillSeconds - 10; } _frame = await _dataChannel.getImage(currentFrameIndex, timeout); emitFrameUpdate(); return true; } try { return await _fetchOnce(); } catch (e) { if (e is ReadTimeoutException) { try { // 等待一次缓存 await _waitFrameBufferFluently(); return await _fetchOnce(); } catch (e) { _emitErrorOccured(i18nBook.measure.frameLoadTimeout.t); } } else { _emitErrorOccured(i18nBook.measure.frameLoadError.t); } } return false; } Future _waitFrameBufferFluently() async { if (isSingleFrame) { await _bufferWaiter.waitSingleVid(); } else { await _bufferWaiter.waitBuffer(_frameIndex); } } /// [Carotid] ✅用于设置颈动脉单帧展示 void set2DMeasureFrame(VidUsImage frame) { _emitFrameUpdated(frame); } /// [Carotid] ✅用于重置播放器 void resetCurrentFrame() { _frameIndex = -1; play(); } void emitFrameUpdate() { if (_isFirstFrame) { firstFrameLoaded.emit(this, _frame!); _isFirstFrame = false; resetTone(); } _emitFrameUpdated(); } void _emitFrameUpdated([VidUsImage? frame]) { frameUpdated.emit(this, frame ?? _frame!); final f = frame ?? _frame!; eventHandler.emit( this, VidPlayerFrameIndexChangeEvent(f.index, f.imageData, f.width, f.height), ); } void _setStatus(VidPlayStatus value) { _status = value; _notifyStatus(); } void _notifyStatus() { eventHandler.emit(this, VidPlayerStatusChangeEvent(status)); } void _stop({bool needNotify = true}) { _playAssistant?.pause(); if (needNotify) { _setStatus(VidPlayStatus.stop); } } @override void dispose() { _disposed = true; _stop(needNotify: false); eventHandler.dispose(); _dataChannel.close(); super.dispose(); } /// 已禁用,请通过eventHandler监听事件 @override void addListener(VoidCallback listener) { throw UnsupportedError( "method `addListener` has been limited.Pls use `eventHandler.addListener`."); } /// 已禁用,请通过eventHandler监听事件 @override void removeListener(VoidCallback listener) { throw UnsupportedError( "method `removeListener` has been limited.Pls use `eventHandler.removeListener`."); } } class _PlayAssistant { _PlayAssistant(this.owner); final VidPlayerController owner; bool _ready = false; late double _frameRate; int get _playInterval => 1000 * 1000 ~/ _frameRate; int get _playIntervalMillSeconds => _playInterval ~/ 1000.0; Timer? _timer; void play() { if (!_ready) { _prepare(); } if (_timer != null) { pause(); } bool waiting = false; final duration = Duration(microseconds: _playInterval); // final duration = const Duration(milliseconds: 1000 ~/ 10); _timer = Timer.periodic(duration, (timer) async { if (waiting) return; waiting = true; final result = await owner.gotoFrame(owner.currentFrameIndex + 1); waiting = false; if (result) { if (owner.currentFrameIndex == owner.totalFramesCount - 1) { // 播放到最后一帧 if (owner.enableLoop) { pause(); // 循环播放 owner._frameIndex = -1; play(); } else { owner.pause(); } } } }); } void pause() { _timer?.cancel(); _timer = null; } void _prepare() { _frameRate = owner._dataChannel.probe.frameRate; _ready = true; } }