|
@@ -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);
|
|
|
+ }
|
|
|
+}
|