Browse Source

Merge branches 'master' and 'master' of http://git.ids.center/Project-Wing/fis_lib_vid

gavin.chen 2 months ago
parent
commit
3da9a74317

+ 7 - 0
lib/async_vid/cached/reader.dart

@@ -23,4 +23,11 @@ class AsyncCachedVidDataReader extends AsyncVidDataReaderBase {
 
   @override
   void startDownload() {}
+
+  @override
+  void skipToFrame(int index) {}
+
+  @override
+  Future<void> fetchFrames(int startIndex, int size,
+      {bool isNeedReload = false}) async {}
 }

+ 7 - 0
lib/async_vid/http/reader.dart

@@ -38,9 +38,16 @@ class AsyncHttpVidDataReader extends AsyncVidDataReaderBase {
   @override
   void startDownload() {}
 
+  @override
+  void skipToFrame(int index) {}
+
   void _onDownloadCallback(double progress, DownloadErrorException? error) {
     downloadCallback?.call(progress, error);
     final bufferedSize = (progress * totalSize).toInt();
     setBufferSize(bufferedSize);
   }
+
+  @override
+  Future<void> fetchFrames(int startIndex, int size,
+      {bool isNeedReload = false}) async {}
 }

+ 48 - 0
lib/async_vid/reader.dart

@@ -13,11 +13,16 @@ abstract class AsyncVidDataReaderBase {
   String _errorMsg = '';
   double _downloadProgress = 0;
   String url;
+  //科研版内靠这个获取缓存
+  Map<int, Uint8List> frames = {};
+
+  bool get isVrd => url.endsWith(".0");
 
   DownloadCallback? downloadCallback;
   AsyncVidDataReaderBytesProxy? _bytesProxy;
 
   AsyncVidDataReaderBase(this.url, {this.downloadCallback}) {
+    frames = {};
     startDownload();
   }
 
@@ -33,17 +38,47 @@ abstract class AsyncVidDataReaderBase {
 
   void startDownload();
 
+  //跳帧
+  void skipToFrame(int index);
+
+  ///批量获取帧数
+  Future<void> fetchFrames(int startIndex, int size,
+      {bool isNeedReload = false});
+
   void initChunk(int size) {
     _chunkedData = Uint8List(size);
   }
 
   void appendChunk(Uint8List chunk) {
+    print('appendChunk: $_bufferSize ${chunk.length}');
+    if (isVrd && _chunkedData.length < _bufferSize + chunk.length) {
+      _chunkedData = extendUint8List(
+          _chunkedData, _bufferSize + chunk.length - _chunkedData.length);
+    }
     _chunkedData.setAll(_bufferSize, chunk);
     setBufferSize(_bufferSize + chunk.length);
   }
 
+  void appendFrame(Uint8List frame, int index) {
+    if (frames.containsKey(index) && frames[index]! == frame) {
+      return;
+    }
+    frames[index] = frame;
+  }
+
+  Uint8List extendUint8List(Uint8List originalData, int additionalSize) {
+    // 创建新的 Uint8List,其长度等于原始长度 + 额外需要的长度
+    Uint8List newData = Uint8List(originalData.length + additionalSize);
+
+    // 将原始数据复制到新的 Uint8List 中
+    newData.setRange(0, originalData.length, originalData);
+
+    return newData; // 返回扩展后的新数据
+  }
+
   @protected
   void setBufferSize(int size) {
+    print('setBufferSize size:$size');
     _bufferSize = size;
   }
 
@@ -57,6 +92,7 @@ abstract class AsyncVidDataReaderBase {
 
   void updateProgress(double progress, [DownloadErrorException? err]) {
     _downloadProgress = progress;
+    print('updateProgress : $progress');
     downloadCallback?.call(_downloadProgress, err);
   }
 
@@ -295,4 +331,16 @@ abstract class AsyncVidDataReaderBase {
     _index += dataLength;
     return bytes;
   }
+
+  Uint8List getImageBytes(int index) {
+    print("getImageBytes by index: $index,frames count:${frames.length}");
+    Uint8List result;
+    if (frames.containsKey(index)) {
+      result = frames[index] ?? Uint8List(0);
+    } else {
+      result = Uint8List(0);
+    }
+
+    return result;
+  }
 }

+ 134 - 3
lib/async_vid/shell/reader.dart

@@ -1,6 +1,9 @@
+import 'dart:async';
+import 'dart:convert';
 import 'dart:typed_data';
 
 import 'package:fis_common/extensions/date.dart';
+import 'package:fis_common/index.dart';
 import 'package:fis_common/web/shell_api_hepler.dart';
 import 'package:vid/us/vid_us_data_http_reader.dart';
 
@@ -8,6 +11,12 @@ import '../reader.dart';
 
 class AsyncShellVidDataReader extends AsyncVidDataReaderBase {
   final int minChunkSize;
+  int _imageCount = 0;
+  bool _isBreak = false;
+  bool _isStop = false;
+  int _skipFrameIndex = 0;
+  Completer<void> _completer = Completer<void>();
+
   late final String id =
       DateTime.now().format('MMddHHmmssSSS') + hashCode.toString();
 
@@ -19,7 +28,8 @@ class AsyncShellVidDataReader extends AsyncVidDataReaderBase {
 
   @override
   void close() {
-    ShellApiHelper.call('cancelFetchVid', id);
+    _isStop = true;
+    cancelFetchVid();
   }
 
   @override
@@ -28,19 +38,48 @@ class AsyncShellVidDataReader extends AsyncVidDataReaderBase {
     // 获取到size即开始下载
     if (size == null) {
       setError(true, 'Get file size fail.');
+    } else if (isVrd) {
+      _imageCount = size;
+      fetchFrames(0, size, isNeedReload: true);
+    }
+  }
+
+  @override
+  Future<void> skipToFrame(int index) async {
+    if (_skipFrameIndex == index || index == 0) {
+      return;
+    }
+    //停用上个不断获取帧的请求
+    if (!_completer.isCompleted) {
+      _isStop = true;
+      _completer.complete();
     }
+    _skipFrameIndex = index;
+    print(
+        "_skipFrameIndex change:$_skipFrameIndex _completer:${_completer.isCompleted}");
+    await Future.delayed(const Duration(milliseconds: 2000));
+    _isStop = false;
+    print("fetchFrames : $_skipFrameIndex $_imageCount");
+    //从跳帧的位置开始不断获取数据
+    fetchFrames(_skipFrameIndex, _imageCount, isNeedReload: true);
+  }
+
+  void cancelFetchVid() {
+    ShellApiHelper.call('cancelFetchVid', id);
   }
 
   /// 接收分片数据
   void receiveChunk(Uint8List chunk) {
     appendChunk(chunk);
     final progress = downloadedSize / totalSize;
+    print('updateProgress : $progress');
     updateProgress(progress);
   }
 
+  //注意:此处VRD返回的是总帧数,非VRD返回的是数据大小
   Future<int?> getFileSize() async {
     final result = await ShellApiHelper.call(
-      'fetchVid',
+      (isVrd) ? 'dynamicFetchVidAsync' : 'fetchVid',
       {
         'Id': id,
         'Url': url,
@@ -48,8 +87,100 @@ class AsyncShellVidDataReader extends AsyncVidDataReaderBase {
       },
     );
     if (result != null) {
-      initChunk(result!);
+      if (isVrd) {
+        _imageCount = result!;
+      }
+      initChunk(result);
     }
+    await fetchFrames(0, 1, isNeedReload: true);
+    print("getFileSize result:$result");
     return result;
   }
+
+  @override
+  Future<void> fetchFrames(int startIndex, int size,
+      {bool isNeedReload = false}) async {
+    _completer = Completer<void>();
+    int batchSize = 5; // 每次获取的帧数
+
+    try {
+      for (int start = startIndex; start < size; start += batchSize) {
+        if (_isStop) {
+          break;
+        }
+        int end = start + batchSize;
+        if (end > size) {
+          end = size; // 确保最后一次获取不会超出范围
+        }
+        print("batchFetchFrames: start:$start end:$end");
+        bool result = await batchFetchFrames(
+          start,
+          end,
+          isNeedReload: isNeedReload,
+        );
+        if (_isStop) {
+          break;
+        }
+        if (result == false) {
+          continue;
+        }
+      }
+    } finally {
+      _completer.complete();
+    }
+    return _completer.future;
+  }
+
+  Future<bool> batchFetchFrames(
+    int start,
+    int end, {
+    bool isNeedReload = false,
+  }) async {
+    print("batchFetchFrames : $start $end");
+    final result = await ShellApiHelper.call(
+      'batchFetchFrames',
+      {
+        'Id': id,
+        'Url': url,
+        'Start': start,
+        'End': end,
+      },
+    );
+    if (_isStop) {
+      return false;
+    }
+    if (result.toString().isEmpty) {
+      print("batchFetchFrames result is empty");
+      _isBreak = true;
+      if (isNeedReload) {
+        await Future.delayed(const Duration(milliseconds: 1000));
+        return await batchFetchFrames(start, end, isNeedReload: isNeedReload);
+      }
+      return false;
+    }
+    List<String> base64Frames = result.toString().split("&");
+    int index = start;
+    for (var f in base64Frames) {
+      var frame = base64Decode(f);
+      appendFrame(frame, index);
+      print("appendFrame ${frame.length} index: $index");
+      //非空,且之前没有中断,则允许更新进度
+      if (frame.isNotEmpty) {
+        if (!_isBreak) {
+          if (_isStop) {
+            return false;
+          }
+          updateProgress(index / _imageCount);
+        }
+      } else {
+        //如果遇到获取不到帧,则中断更新进度
+        print('updateProgress isBreak true');
+        _isBreak = true;
+      }
+      index++;
+    }
+    //base64Decode
+    print("batchFetchFrames result:${base64Frames.length}");
+    return true;
+  }
 }

+ 98 - 13
lib/async_vid/vid_data.dart

@@ -1,5 +1,8 @@
+import 'dart:convert';
 import 'dart:typed_data';
 
+import 'package:fis_common/env/env.dart';
+import 'package:fis_common/web/shell_api_hepler.dart';
 import 'package:flutter/foundation.dart';
 import 'package:vid/us/vid_us_data_http_reader.dart';
 import 'package:vid/us/vid_us_data_reader.dart';
@@ -8,6 +11,7 @@ import 'package:vid/us/vid_us_image_data.dart';
 import 'package:vid/us/vid_us_probe.dart';
 
 import 'reader.dart';
+import 'shell/reader.dart';
 
 abstract class AsyncVidImageDataBase {
   late AsyncVidDataReaderBase _reader;
@@ -76,21 +80,35 @@ abstract class AsyncVidImageDataBase {
   ///Initialize the HttpVidUsImageData, must be called firstly.
   Future<bool> initialize() async {
     if (!_initialized) {
-      var header = await _readHeader();
+      String header;
+      if (_reader.isVrd) {
+        header = vid_header;
+      } else {
+        header = await _readHeader();
+      }
       if (header != vid_header) {
         throw Exception("The input data is not a VID data.");
       }
-      _version = await _readVersion();
-      //Get probe info
-      _probe = await _readProbe();
-      _imageFormat = await _readImageFormat();
-      _extendedData = await _readExtendedData();
-      _imagePositionList = [];
-      var imagePositionListData = await _readImagePositionListData();
-      _imageCount = imagePositionListData.length ~/ 8;
-      var imagePositionReader = VidUsDataReader(imagePositionListData);
-      for (var i = 0; i < _imageCount; i++) {
-        _imagePositionList.add(imagePositionReader.readInt64V2());
+      if (_reader.isVrd) {
+        _version = await _getVersion();
+        _probe = await _getProbe();
+        _imageFormat = await _getImageFormat();
+        _extendedData = await _getExtendedData();
+        _imageCount = await _getImageCount();
+        print('_imageCount:$_imageCount');
+      } else {
+        _version = await _readVersion();
+        //Get probe info
+        _probe = await _readProbe();
+        _imageFormat = await _readImageFormat();
+        _extendedData = await _readExtendedData();
+        _imagePositionList = [];
+        var imagePositionListData = await _readImagePositionListData();
+        _imageCount = imagePositionListData.length ~/ 8;
+        var imagePositionReader = VidUsDataReader(imagePositionListData);
+        for (var i = 0; i < _imageCount; i++) {
+          _imagePositionList.add(imagePositionReader.readInt64V2());
+        }
       }
       _initialized = true;
     }
@@ -245,9 +263,21 @@ abstract class AsyncVidImageDataBase {
     var timeout = 0;
     while (!_closed) {
       try {
-        var imageData = _reader.readBytes(_imagePositionList[index]);
+        print('_reader.readBytes $index');
+        Uint8List imageData;
+        if (_reader.isVrd) {
+          imageData = _reader.getImageBytes(index);
+          if (imageData.isEmpty) {
+            _reader.skipToFrame(index);
+            throw Exception("Can not find image Data");
+          }
+        } else {
+          imageData = _reader.readBytes(_imagePositionList[index]);
+        }
+        print('_reader.readBytes $index done');
         return VidUsImage.fromBytes(imageData);
       } catch (ex) {
+        print('_reader.readBytes $index ex:$ex');
         if (ex is NotReadyException) {
           if (timeout >= _readImageTimeout) {
             throw ReadTimeoutException();
@@ -261,4 +291,59 @@ abstract class AsyncVidImageDataBase {
     }
     throw AlreadyClosedException();
   }
+
+  Future<int> _getVersion() async {
+    final result = await ShellApiHelper.call(
+      'getVersion',
+      {
+        'Url': _reader.url,
+      },
+    );
+    print('_getVersion:$result');
+    return result;
+  }
+
+  Future<VidUsProbe> _getProbe() async {
+    final result = await ShellApiHelper.call(
+      'getVinnoProbe',
+      {
+        'Url': _reader.url,
+      },
+    );
+    var probe = base64Decode(result);
+    var probeResult = VidUsProbe.fromBytes(probe);
+    print("probe : ${probeResult.name} ${probeResult.type}");
+    return probeResult;
+  }
+
+  Future<VidUsImageFormat> _getImageFormat() async {
+    final result = await ShellApiHelper.call(
+      'getImageFormat',
+      {
+        'Url': _reader.url,
+      },
+    );
+    return VidUsImageFormat.values
+        .firstWhere((element) => element.index == result);
+  }
+
+  Future<Uint8List> _getExtendedData() async {
+    final result = await ShellApiHelper.call(
+      'getExtendedData',
+      {
+        'Url': _reader.url,
+      },
+    );
+    return base64Decode(result);
+  }
+
+  Future<int> _getImageCount() async {
+    final result = await ShellApiHelper.call(
+      'getImageCount',
+      {
+        'Url': _reader.url,
+      },
+    );
+    return result;
+  }
 }

+ 20 - 1
lib/data_channel/channel.dart

@@ -1,6 +1,7 @@
 import 'dart:async';
 
 import 'package:fis_common/event/event_type.dart';
+import 'package:fis_common/index.dart';
 import 'package:fis_vid/async_vid/vid_data.dart';
 import 'package:flutter/foundation.dart';
 import 'package:vid/us/vid_us_data_http_reader.dart';
@@ -17,6 +18,7 @@ import 'progress_info.dart';
 abstract class VidDataChannel {
   @protected
   AsyncVidImageDataBase? source;
+  bool get _isVrdPlayer => url.endsWith(".0");
 
   final String url;
 
@@ -62,6 +64,7 @@ abstract class VidDataChannel {
       source = await buildSource();
       source!.setReadHeaderTimeout(timeout + 50); // 50ms buffer 4 timer
       await source!.initialize();
+      print("_innerLoad initialize end");
       return true;
     } catch (e) {
       return false;
@@ -80,7 +83,11 @@ abstract class VidDataChannel {
 
   /// 是否缓冲完成
   bool get isBufferedDone {
-    return getBufferSize() >= getFileSize();
+    var bufferSize = getBufferSize();
+    var fileSized = getFileSize();
+    var result = bufferSize >= fileSized;
+    print("是否缓冲完成:$result $bufferSize $fileSized");
+    return result;
   }
 
   VidUsProbe get probe => source!.probe;
@@ -98,6 +105,18 @@ abstract class VidDataChannel {
     return source!.getImage(index);
   }
 
+  bool isBufferedDoneForFrames(int index) {
+    if (_isVrdPlayer) {
+      bool result = source!.getReader().frames.containsKey(index);
+      if (result) {
+        result = source!.getReader().frames[index]!.isNotEmpty;
+      }
+      return result;
+    } else {
+      return false;
+    }
+  }
+
   /// 关闭通道
   void close() {
     _initialized = false;

+ 1 - 1
lib/data_channel/channel_web.dart

@@ -18,7 +18,7 @@ class VidDataChannelImpl extends VidDataChannel {
 
   @override
   Future<AsyncVidImageDataBase> buildSource() async {
-    if (FPlatform.isPureWeb || FPlatform.isVStationLab || FPlatform.isWinLab) {
+    if (FPlatform.isPureWeb) {
       return AsyncHttpVidImageData(url, downloadCallback: onDownloadCallback);
     } else {
       return AsyncShellVidImageData(url, downloadCallback: onDownloadCallback);