Browse Source

超声工作站图像预览逻辑提交

finlay 11 months ago
parent
commit
f894efe315

+ 163 - 0
lib/helper/sonopost_preview_helper.dart

@@ -0,0 +1,163 @@
+import 'dart:convert';
+import 'dart:math' as math;
+import 'dart:typed_data';
+
+import 'package:fis_common/event/event_type.dart';
+import 'package:fis_common/logger/logger.dart';
+import 'package:flutter/material.dart';
+
+class ShellSonopostPlayController {
+  static bool _playing = false;
+
+  final int width;
+  final int height;
+
+  static final bufferFragmentReceived = FEventHandler<Uint8List>();
+
+  ShellSonopostPlayController({
+    this.width = 1280,
+    this.height = 720,
+  });
+
+  Future<void> init() {
+    return Future(() => null);
+  }
+
+  void play() {
+    if (_playing) return;
+    _playing = true;
+
+    logger.i("ShellSonopost start");
+  }
+
+  static void setPreviewImageData(String imageData) {
+    try {
+      if (_playing) {
+        var data = base64Decode(imageData);
+        bufferFragmentReceived.emit("shell_sonopost", data);
+      } else {
+        logger.i(
+            "refresh sonopost preview failed since publish already stopped!");
+      }
+    } catch (e) {}
+  }
+
+  void stop() {
+    _playing = false;
+    logger.i("ShellSonopost stop");
+  }
+}
+
+class ShellSonopostPlayer extends StatefulWidget {
+  final ShellSonopostPlayController controller;
+
+  const ShellSonopostPlayer({super.key, required this.controller});
+
+  @override
+  State<StatefulWidget> createState() => _ShellSonopostPlayer();
+}
+
+class _ShellSonopostPlayer extends State<ShellSonopostPlayer> {
+  static const int _RETRY_LIMIT = 10;
+  Uint8List? _frameBuffer;
+  // late Uint8List _bufferArea;
+  // int _bufferPosition = 0;
+  bool _hasError = false;
+  int _rgbaSize = 0;
+  int _retryCount = 0;
+
+  @override
+  void initState() {
+    super.initState();
+
+    _rgbaSize = 4 * widget.controller.width * widget.controller.height;
+    // _bufferArea = Uint8List(_rgbaSize);
+    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+      ShellSonopostPlayController.bufferFragmentReceived
+          .addListener(_onBufferOnce);
+    });
+  }
+
+  @override
+  dispose() {
+    ShellSonopostPlayController.bufferFragmentReceived
+        .removeListener(_onBufferOnce);
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    Widget child;
+    if (_hasError) {
+      child = const Center(
+        child: Text("Error", style: TextStyle(color: Colors.white)),
+      );
+    } else if (_frameBuffer != null) {
+      child = Image.memory(
+        _frameBuffer!,
+        fit: BoxFit.contain,
+        gaplessPlayback: true,
+      );
+      // 水平翻转
+      child = Transform(
+        alignment: Alignment.center,
+        transform: Matrix4.rotationY(math.pi),
+        child: child,
+      );
+    } else {
+      child = const SizedBox();
+    }
+
+    return Container(
+      color: Colors.black,
+      child: child,
+    );
+  }
+
+  void _onBufferOnce(Object sender, Uint8List e) {
+    setState(() {
+      _frameBuffer = e;
+    });
+  }
+
+  void _onBuffer(Object sender, Uint8List e) {
+    try {
+      _onBufferOnce(sender, e);
+    } catch (e) {
+      _handleError();
+    }
+  }
+
+  void _handleError() {
+    widget.controller.stop();
+
+    if (_retryCount > _RETRY_LIMIT) {
+      setState(() {
+        _hasError = true;
+      });
+    }
+
+    Future.delayed(const Duration(milliseconds: 800), () {
+      widget.controller.play();
+    });
+    _retryCount++;
+  }
+
+  static int readInt64(Uint8List data, [int index = 0]) {
+    var intData = data.buffer.asByteData(index, 8);
+    return intData.getInt64(0, Endian.little);
+  }
+
+  static int readInt64V2(Uint8List data, [int index = 0]) {
+    int low = readInt(data, index);
+    int high = readInt(data, index + 4);
+    int value = high >> 32;
+    value |= low;
+    return value;
+  }
+
+  static int readInt(Uint8List data, [int index = 0]) {
+    var intData = data.buffer.asByteData(index, 4);
+    return intData.getInt32(0, Endian.little);
+  }
+}

+ 13 - 0
lib/js_platform/handlers/update_sonopost_preview.dart

@@ -0,0 +1,13 @@
+import 'package:vitalapp/helper/sonopost_preview_helper.dart';
+import 'package:vitalapp/js_platform/handlers/base.dart';
+import 'package:vitalapp/js_platform/methods.dart';
+
+class UpdateSonopostPreviewHandler extends MethodHandlerBase {
+  UpdateSonopostPreviewHandler()
+      : super(TargetMethodName.UpdateSonopostPreview);
+
+  @override
+  void execute(List<String> args) {
+    ShellSonopostPlayController.setPreviewImageData(args[0]);
+  }
+}

+ 2 - 0
lib/js_platform/listener.dart

@@ -3,6 +3,7 @@ import 'package:fis_common/logger/logger.dart';
 import 'package:fis_ui/index.dart';
 import 'package:fis_ui/interface/interactive_container.dart';
 import 'package:flutter/material.dart';
+import 'package:vitalapp/js_platform/handlers/update_sonopost_preview.dart';
 import 'handlers/base.dart';
 import 'handlers/idcard_handler.dart';
 import 'methods.dart';
@@ -21,6 +22,7 @@ class JsPlatformListener {
   void _init() {
     //IDCardReader
     _register(OpenIDCardPageHandler());
+    _register(UpdateSonopostPreviewHandler());
   }
 
   void _handleMessage(String method, String arguments) {

+ 3 - 0
lib/js_platform/methods.dart

@@ -1,4 +1,7 @@
 enum TargetMethodName {
   //身份证读卡器
   OnIDCardRead,
+
+  ///更新魔盒图像预览
+  UpdateSonopostPreview,
 }

+ 52 - 24
lib/pages/consultation_record_view/widgets/capture_page.dart

@@ -1,5 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:vitalapp/components/button.dart';
+import 'package:vitalapp/helper/sonopost_preview_helper.dart';
 import 'package:vitalapp/rpc.dart';
 
 class CapturePage extends StatefulWidget {
@@ -10,40 +11,67 @@ class CapturePage extends StatefulWidget {
 class _CapturePageState extends State<CapturePage> {
   // 状态变量,true表示“开始录制”,false表示“结束录制”
   bool isRecording = false;
+  ShellSonopostPlayController controller = ShellSonopostPlayController();
+  @override
+  void initState() {
+    super.initState();
+    rpc.platform.startPreviewSonopost();
+    controller.play();
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    controller.stop();
+    rpc.platform.stopPreviewSonopost();
+  }
 
   @override
   Widget build(BuildContext context) {
     return Container(
       height: 300,
       alignment: Alignment.center,
-      child: Row(
-        mainAxisAlignment: MainAxisAlignment.center,
+      child: Column(
         children: [
-          SizedBox(
-            width: 120,
-            height: 36,
-            child: VButton(
-              child: Text("截图"),
-              onTap: () {
-                rpc.platform.keypadPressF1();
-              },
+          Expanded(
+              child: Container(
+            child: ShellSonopostPlayer(
+              controller: controller,
             ),
-          ),
+          )),
           SizedBox(
-            width: 30,
+            height: 10,
           ),
-          SizedBox(
-            width: 120,
-            height: 36,
-            child: VButton(
-              child: Text(isRecording ? "结束录制" : "开始录制"),
-              onTap: () {
-                rpc.platform.keypadPressF2();
-                setState(() {
-                  isRecording = !isRecording;
-                });
-              },
-            ),
+          Row(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              SizedBox(
+                width: 120,
+                height: 36,
+                child: VButton(
+                  child: Text("截图"),
+                  onTap: () {
+                    rpc.platform.keypadPressF1();
+                  },
+                ),
+              ),
+              SizedBox(
+                width: 30,
+              ),
+              SizedBox(
+                width: 120,
+                height: 36,
+                child: VButton(
+                  child: Text(isRecording ? "结束录制" : "开始录制"),
+                  onTap: () {
+                    rpc.platform.keypadPressF2();
+                    setState(() {
+                      isRecording = !isRecording;
+                    });
+                  },
+                ),
+              ),
+            ],
           ),
         ],
       ),

+ 2 - 2
pubspec.lock

@@ -407,8 +407,8 @@ packages:
     dependency: "direct main"
     description:
       path: "."
-      ref: "1cfb365"
-      resolved-ref: "1cfb365a7e779cc6a0b814796f9265389a2b789b"
+      ref: "9165199dff"
+      resolved-ref: "9165199dff5dc380bc6179de3e5de8612f318f38"
       url: "http://git.ius.plus:88/Project-Wing/fis_lib_jsonrpc.git"
     source: git
     version: "0.0.1"

+ 2 - 2
pubspec.yaml

@@ -48,7 +48,7 @@ dependencies:
   fis_jsonrpc:
     git:
       url: http://git.ius.plus:88/Project-Wing/fis_lib_jsonrpc.git
-      ref: b3dc4b3
+      ref: 9165199dff
   vnote_device_plugin:
     git:
       url: http://git.ius.plus/Project-Vital/FlutterDevicePlugin.git
@@ -162,7 +162,7 @@ dependency_overrides:
   fis_jsonrpc:
     git:
       url: http://git.ius.plus:88/Project-Wing/fis_lib_jsonrpc.git
-      ref: 1cfb365
+      ref: 9165199dff
     #path: ../fis_lib_jsonrpc
   fis_theme:
     git: