diff --git a/assets/demo.h264 b/assets/demo.h264 deleted file mode 100644 index eb04d7b3..00000000 Binary files a/assets/demo.h264 and /dev/null differ diff --git a/lib/appRouters.dart b/lib/appRouters.dart index f4bfb8ef..12fbb302 100755 --- a/lib/appRouters.dart +++ b/lib/appRouters.dart @@ -2,7 +2,6 @@ import 'package:get/get.dart'; import 'package:star_lock/common/safetyVerification/safetyVerification_binding.dart'; import 'package:star_lock/flavors.dart'; import 'package:star_lock/login/forgetPassword/starLock_forgetPassword_xhj_page.dart'; -import 'package:star_lock/login/login/JMuxerApp.dart'; import 'package:star_lock/login/login/starLock_login_xhj_page.dart'; import 'package:star_lock/login/register/starLock_register_binding.dart'; import 'package:star_lock/login/register/starLock_register_xhj_page.dart'; @@ -398,7 +397,6 @@ abstract class Routers { static const String ownedKeyListPage = '/ownedKeyListPage'; //拥有的钥匙 static const String starLockLoginPage = '/StarLockLoginPage'; // 登录 - static const String LocalHtmlPage = '/LocalHtmlPage'; // LocalHtmlPage static const String starLockRegisterPage = '/StarLockRegisterPage'; // 注册 static const String starLockForgetPasswordPage = '/StarLockForgetPasswordPage'; // 忘记密码 @@ -640,9 +638,6 @@ abstract class AppRouters { page: () => F.sw( skyCall: () => const StarLockLoginPage(), xhjCall: () => const StarLockLoginXHJPage()), - ),GetPage( - name: Routers.LocalHtmlPage, - page: () => LocalHtmlPage(), ), GetPage( name: Routers.starLockRegisterPage, diff --git a/lib/login/login/JMuxerApp.dart b/lib/talk/startChart/webView/h264_web_view.dart similarity index 50% rename from lib/login/login/JMuxerApp.dart rename to lib/talk/startChart/webView/h264_web_view.dart index ad481c6e..a38e9bef 100644 --- a/lib/login/login/JMuxerApp.dart +++ b/lib/talk/startChart/webView/h264_web_view.dart @@ -2,22 +2,35 @@ import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show ByteData, Uint8List, rootBundle; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:star_lock/app_settings/app_colors.dart'; +import 'package:star_lock/talk/startChart/handle/other/talk_data_repository.dart'; import 'package:star_lock/tools/titleAppBar.dart'; import 'package:webview_flutter/webview_flutter.dart'; -class LocalHtmlPage extends StatefulWidget { +class H264WebView extends StatefulWidget { @override - _LocalHtmlPageState createState() => _LocalHtmlPageState(); + _H264WebViewState createState() => _H264WebViewState(); } -class _LocalHtmlPageState extends State { +class _H264WebViewState extends State { late final WebViewController _controller; Timer? timer; + Timer? _sendTimer; + + // 私有缓冲区,外部无法直接访问 + final List _buffer = []; + + // 发送数据至html文件间隔时间 + final int sendDataToHtmlIntervalTime = 820; + + // 通话数据流的单例流数据处理类 + final TalkDataRepository talkDataRepository = TalkDataRepository.instance; @override void initState() { super.initState(); + _controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..enableZoom(false) @@ -30,135 +43,52 @@ class _LocalHtmlPageState extends State { // 加载本地 HTML _loadLocalHtml(); + simulateStreamFromAsset(); _sendFramesToHtml(); } - void _sendFramesToHtml() async { - // 读取 assets/demo.h264 文件 + void simulateStreamFromAsset() async { + // 读取 assets 文件 final ByteData data = await rootBundle.load('assets/talk.h264'); final List byteData = data.buffer.asUint8List(); - - // 提取 NALU 边界并生成 chunks - final List chunks = extractChunks(byteData); - final int total = chunks.length; - int current = 0; int start = 0; int end = 0; - List buffer = []; // 用于累积帧数据的缓冲区 - - - // 创建一个定时器,每800毫秒触发一次 - timer ??= Timer.periodic(Duration(milliseconds: 800), (timer) async { - if (current >= total) { - current = 0; + final List chunks = extractChunks(byteData); + // 定时器控制发送数据块的节奏 + timer ??= Timer.periodic(Duration(milliseconds: 10), (timer) { + if (current >= chunks.length) { + print('数据已经发完,重新进行发送'); start = 0; - print("All frames sent. Restarting from the beginning."); + end = 0; + current = 0; + timer.cancel(); + return; } - + // 提取 NALU 边界并生成 chunks end = chunks[current]; current++; - - // 累积当前帧数据到缓冲区 List frameData = byteData.sublist(start, end); - buffer.addAll(frameData); + if (frameData.length == 0) timer.cancel(); + talkDataRepository.addTalkData(frameData); start = end; - - // 发送累积的数据 - if (buffer.isNotEmpty) { - await _sendBufferedData(buffer); - buffer.clear(); // 清空缓冲区 - } - - // 打印进度信息(可选) - if (current % 50 == 0) { - print("I am serving, no problem!"); - } - if (current == 0) { - print("Started from first chunk..."); - } - - // 如果所有帧都已处理完毕,取消定时器 - if (current >= total) { - timer.cancel(); - print('All frames sent. Stopping timer...'); - } }); } - int getFrameSize(List data, int offset) { - // 查找第一个 Start Code (0x000001 或 0x00000001) - const List startCode1 = [0, 0, 0, 1]; // Start Code: 0x000001 - const List startCode2 = [ - 0, - 0, - 1 - ]; // Start Code: 0x000001 (0x00000001 version) - - // 查找下一个 Start Code 的位置 - int startCodeStart = -1; - int startCodeEnd = -1; - for (int i = offset; i < data.length - 3; i++) { - // 判断是否匹配 Start Code (0x000001 或 0x00000001) - if (_matchesStartCode(data, i, startCode1)) { - startCodeStart = i; - startCodeEnd = i + startCode1.length; - break; - } else if (_matchesStartCode(data, i, startCode2)) { - startCodeStart = i; - startCodeEnd = i + startCode2.length; - break; + void _sendFramesToHtml() async { + // 接收到流数据,保存到缓冲区 + talkDataRepository.talkDataStream.listen((event) async { + _buffer.addAll(event); + }); + // 缓冲800ms的数据,定时发送 + _sendTimer ??= Timer.periodic( + Duration(milliseconds: sendDataToHtmlIntervalTime), (timer) async { + // 发送累积的数据 + if (_buffer.isNotEmpty) { + await _sendBufferedData(_buffer); + _buffer.clear(); // 清空缓冲区 } - } - - // 如果找不到有效的 Start Code,返回 0(结束循环) - if (startCodeStart == -1) return 0; - - // 查找下一个 Start Code 的位置 - int nextStartCodeStart = -1; - for (int i = startCodeEnd; i < data.length - 3; i++) { - if (_matchesStartCode(data, i, startCode1) || - _matchesStartCode(data, i, startCode2)) { - nextStartCodeStart = i; - break; - } - } - - // 如果找不到下一个 Start Code,说明当前是最后一个 NAL 单元 - if (nextStartCodeStart == -1) { - return data.length - startCodeStart; - } - - // 计算并返回当前 NAL 单元的大小 - return nextStartCodeStart - startCodeStart; - } - -// 检查数据是否匹配 Start Code - bool _matchesStartCode(List data, int index, List startCode) { - for (int i = 0; i < startCode.length; i++) { - if (data[index + i] != startCode[i]) { - return false; - } - } - return true; - } - - Future _loadLocalHtml() async { - final String fileHtmlContent = - await rootBundle.loadString('assets/html/h264.html'); - _controller.loadHtmlString(fileHtmlContent); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: WebViewWidget(controller: _controller), - ); - } - - _sendBufferedData(List buffer) async { - String jsCode = "feedDataFromFlutter(${buffer});"; - await _controller.runJavaScript(jsCode); + }); } // 提取 NALU 边界并生成 chunks @@ -170,7 +100,7 @@ class _LocalHtmlPageState extends State { int state = 0; int lastIndex = 0; List result = []; - const minNaluPerChunk = 30; // 每个数据块包含的最小NALU数量 + const minNaluPerChunk = 22; // 每个数据块包含的最小NALU数量 while (i < length) { value = byteData[i++]; @@ -215,13 +145,36 @@ class _LocalHtmlPageState extends State { result.add(lastIndex); } + + return result; } + /// 加载html文件 + Future _loadLocalHtml() async { + final String fileHtmlContent = + await rootBundle.loadString('assets/html/h264.html'); + _controller.loadHtmlString(fileHtmlContent); + } + + // 发送数据给js处理 + _sendBufferedData(List buffer) async { + String jsCode = "feedDataFromFlutter(${buffer});"; + await _controller.runJavaScript(jsCode); + } + + @override + Widget build(BuildContext context) { + return WebViewWidget(controller: _controller); + } + @override void dispose() { timer?.cancel(); - timer=null; + timer = null; + _sendTimer?.cancel(); + timer = null; + // talkDataRepository.dispose(); super.dispose(); } }