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(); ShellSonopostPlayController({ this.width = 1280, this.height = 720, }); Future 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 createState() => _ShellSonopostPlayer(); } class _ShellSonopostPlayer extends State { 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); } }