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'; import 'package:fis_vid/data_host/data_host.dart'; import 'package:flutter/foundation.dart'; import 'package:vid/us/vid_us_image.dart'; import 'package:vid/us/vid_us_probe.dart'; import 'enums.dart'; import 'events.dart'; /// Vid播放器控制器 class VidPlayerControllerNoSharing extends ChangeNotifier implements IPlayerController { /// Vid播放器控制器 /// /// [dataHost] Vid文件宿主 VidPlayerControllerNoSharing({ required VidDataHost dataHost, }) { _dataHost = dataHost; eventHandler = FEventHandler(); frameUpdated = FEventHandler(); firstFrameLoaded = FEventHandler(); frameLoadStateChanged = FEventHandler(); errorOccured = FEventHandler(); } // 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]; @override var eventHandler = FEventHandler(); var currentFrameHandler = FEventHandler(); @override late final FEventHandler frameUpdated; @override late final FEventHandler firstFrameLoaded; @override late final FEventHandler frameLoadStateChanged; @override late final FEventHandler errorOccured; late final VidDataHost _dataHost; _PlayAssistant? _playAssistant; double _speed = 1.0; VidPlayStatus _status = VidPlayStatus.init; int _frameIndex = -1; VidUsImage? _frame; bool _disposed = false; ///该图像是否需要获取测量项和注释项 bool ifNeedInit = true; @override bool get disposed => _disposed; /// Current play speed double get currentSpeed => _speed; @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 => _dataHost.frameCount; double get frameRate => _dataHost.probe.frameRate; 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; /// Set play speed [0.5~2] /// /// [speed] speed value [0.5~2] void setSpeed(double speed) { if (speed < 0.5 || speed > 2) return; _speed = speed; eventHandler.emit(this, VidPlayerSpeedChangeEvent(currentSpeed)); } @override Future load() async { ifNeedInit = true; final info = await _dataHost.load(); final result = info != null; if (result) { _setStatus(VidPlayStatus.ready); } else { _setStatus(VidPlayStatus.loadFail); } return result; } @override void play() { if (playing) return; if (!canPlay) return; if (isEndOfPlay) { _frameIndex = -1; } if (isSingleFrame) { gotoFrame(0); _setStatus(VidPlayStatus.pause); } else { _playAssistant ??= _PlayAssistant(this); _playAssistant!.play(); _setStatus(VidPlayStatus.play); } } @override void pause() { if (!playing) { if (_status != VidPlayStatus.pause) { _setStatus(VidPlayStatus.pause); } return; } _playAssistant?.pause(); _setStatus(VidPlayStatus.pause); } /// 主动播放结束 void playOver() { pause(); eventHandler.emit(this, VidPlayerPlayOverEvent()); } /// Pause and view next frame Future gotoNextFrame() { pause(); return gotoFrame(currentFrameIndex + 1); } /// Pause and view prev frame Future gotoPrevFrame() { pause(); return gotoFrame(currentFrameIndex - 1); } /// View target indexed frame /// /// [index] frame index Future gotoFrame(int index) async { if (index < 0 || index >= totalFramesCount) return false; if (index == currentFrameIndex) return true; _frameIndex = index; _updateFrame(); return true; } /// Set frame brightness /// /// [value] brightness value void setBrightness(int value) { final brightnessCount = value / 100; if (brightnessCount < -1 || brightnessCount > 1) { return; } if (kIsMobile) { _brightness = brightnessCount * 255; } else { _brightness = brightnessCount; } 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)); _updateFrame(); } /// 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)); _updateFrame(); } void setFilterMatrix(List matrix) { eventHandler.emit(this, VidPlayerFilterChangeEvent(matrix)); } /// 重置图像增益 void resetTone() { setBrightness(0); setContrast(0); eventHandler.emit(this, VidPlayResetToneEvent()); } void _updateFrame() { _handleUpdateFrame .throttle( timeout: _playAssistant?._playIntervalMillSeconds ?? 200, ) .call(); } /// [Carotid] ✅用于设置颈动脉单帧展示 void set2DMeasureFrame(VidUsImage _frame) { eventHandler.emit( this, VidPlayerFrameIndexChangeEvent( currentFrameIndex, _frame.imageData, _frame.width, _frame.height, ), ); } /// [Carotid] ✅用于重置播放器 void resetCurrentFrame() { _frameIndex = -1; play(); } Future _handleUpdateFrame() async { if (_disposed) return; _frame = await _dataHost.getFrame(currentFrameIndex); emitFrameUpdate(); } void emitFrameUpdate() { if (ifNeedInit) { currentFrameHandler.emit(this, _frame!); ifNeedInit = false; } eventHandler.emit( this, VidPlayerFrameIndexChangeEvent( currentFrameIndex, _frame!.imageData, _frame!.width, _frame!.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() { _dataHost.release(); _disposed = true; _stop(needNotify: false); eventHandler.dispose(); 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`."); } @override VidDataChannel get dataChannel => throw UnimplementedError(); @override Future locateTo(int index) { pause(); return gotoFrame(index); } @override String get url => _dataHost.url; VidUsProbe get probe => _dataHost.probe; } class _PlayAssistant { _PlayAssistant(this.owner); final VidPlayerControllerNoSharing 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(); } final duration = Duration(microseconds: _playInterval); // final duration = const Duration(milliseconds: 1000 ~/ 10); _timer = Timer.periodic(duration, (timer) { // print('play at ${DateTime.now()}'); owner.gotoFrame(owner.currentFrameIndex + 1); if (owner.currentFrameIndex == owner.totalFramesCount - 1) { owner.playOver(); } }); } void pause() { _timer?.cancel(); _timer = null; print('stop at ${DateTime.now()}'); } void _prepare() { _frameRate = owner._dataHost.probe.frameRate; _ready = true; } }