diff --git a/assets/demo.h264 b/assets/demo.h264 new file mode 100644 index 00000000..eb04d7b3 Binary files /dev/null and b/assets/demo.h264 differ diff --git a/assets/html/h264.html b/assets/html/h264.html new file mode 100644 index 00000000..431b5c38 --- /dev/null +++ b/assets/html/h264.html @@ -0,0 +1,2684 @@ + + + + + + play + + + + + + + + + diff --git a/assets/talk.h264 b/assets/talk.h264 new file mode 100644 index 00000000..5a1f2802 Binary files /dev/null and b/assets/talk.h264 differ diff --git a/lib/appRouters.dart b/lib/appRouters.dart index 12fbb302..f4bfb8ef 100755 --- a/lib/appRouters.dart +++ b/lib/appRouters.dart @@ -2,6 +2,7 @@ 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'; @@ -397,6 +398,7 @@ 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'; // 忘记密码 @@ -638,6 +640,9 @@ 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/login/login/JMuxerApp.dart new file mode 100644 index 00000000..61d809ee --- /dev/null +++ b/lib/login/login/JMuxerApp.dart @@ -0,0 +1,137 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show ByteData, Uint8List, rootBundle; +import 'package:webview_flutter/webview_flutter.dart'; + +class LocalHtmlPage extends StatefulWidget { + @override + _LocalHtmlPageState createState() => _LocalHtmlPageState(); +} + +class _LocalHtmlPageState extends State { + late final WebViewController _controller; + + @override + void initState() { + super.initState(); + _controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..enableZoom(false) + ..addJavaScriptChannel( + 'Flutter', + onMessageReceived: (message) { + print("来自 HTML 的消息: ${message.message}"); + }, + ); + + // 加载本地 HTML + _loadLocalHtml(); + _sendFramesToHtml(); + } + + void _sendFramesToHtml() async { + // 读取 assets/demo.h264 文件 + final ByteData data = await rootBundle.load('assets/talk.h264'); + final List byteData = data.buffer.asUint8List(); + + int offset = 0; + int frameSize = 0; + + // 根据 H.264 数据的帧结构来逐帧读取并发送 + while (offset < byteData.length) { + // 获取每一帧的大小 + frameSize = getFrameSize(byteData, offset); + + if (frameSize == 0) { + print("No more frames or error in frame size calculation."); + break; // 如果没有更多的帧,或者无法计算帧的大小,则退出循环 + } + + // 提取当前帧数据 + List frameData = byteData.sublist(offset, offset + frameSize); + + // 将当前帧数据发送到 WebView 中的 feedDataFromFlutter 函数 + String jsCode = "feedDataFromFlutter(${frameData});"; + await _controller.runJavaScript(jsCode); + + // 更新偏移量,继续发送下一个帧 + offset += frameSize; + + // 控制帧率,模拟每秒 22 帧播放(或者根据你的视频帧率调整) + await Future.delayed(Duration(milliseconds: (1000 / 22).toInt())); + } + } + + 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; + } + } + + // 如果找不到有效的 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( + appBar: AppBar( + title: Text("加载本地 HTML"), + ), + body: WebViewWidget(controller: _controller), + ); + } +} diff --git a/lib/login/login/starLock_login_page.dart b/lib/login/login/starLock_login_page.dart index b9e9acaf..d32a0609 100755 --- a/lib/login/login/starLock_login_page.dart +++ b/lib/login/login/starLock_login_page.dart @@ -238,18 +238,23 @@ class _StarLockLoginPageState extends State { SubmitBtn( btnName: '发送回声测试消息', onClick: () { - StartChartManage().sendEchoMessage( - ToPeerId: '3phX8Ng2cZHz5NtP8xAf6nYy2z1BYytoejgjoHrWMGhH'); + StartChartManage().sendEchoMessage(); }, ), SubmitBtn( btnName: '发送对讲请求1', onClick: () { StartChartManage().sendCallRequestMessage( - ToPeerId: 'CqKJUADeUZsHrSD4SEVnPBr73fbsHNUXCEq9kyCtE3wp', + ToPeerId: '3QvLvD3cBnM358bCdccUpGBBYicepEskPXEsh8KvTvY2', ); }, ), + SubmitBtn( + btnName: '跳转至html', + onClick: () { + Get.toNamed(Routers.LocalHtmlPage); + }, + ), SizedBox(height: 50.w), Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/talk/startChart/handle/impl/udp_talk_accept_handler.dart b/lib/talk/startChart/handle/impl/udp_talk_accept_handler.dart index c2f4aad4..434dbb8b 100644 --- a/lib/talk/startChart/handle/impl/udp_talk_accept_handler.dart +++ b/lib/talk/startChart/handle/impl/udp_talk_accept_handler.dart @@ -36,7 +36,6 @@ class UdpTalkAcceptHandler extends ScpMessageBaseHandle stopRingtone(); // 设置状态为接听中 talkStatus.setDuringCall(); - stopRingtone(); } } diff --git a/lib/talk/startChart/start_chart_manage.dart b/lib/talk/startChart/start_chart_manage.dart index fb1090ac..28376433 100644 --- a/lib/talk/startChart/start_chart_manage.dart +++ b/lib/talk/startChart/start_chart_manage.dart @@ -176,6 +176,7 @@ class StartChartManage { // 发送上线消息 Future _sendOnlineMessage() async { + _log(text: '发送上线消息'); if (isOnlineStartChartServer) { _log(text: '星图已上线,请勿重复发送上线消息'); return; @@ -190,6 +191,10 @@ class StartChartManage { // 发送对讲请求消息 Future sendCallRequestMessage({required String ToPeerId}) async { + if (talkStatus.status == TalkStatus.duringCall) { + _log(text: '已经在通话中,请勿重复发送对讲请求'); + return; + } // 组装上线消息 final message = MessageCommand.talkRequestMessage( FromPeerId: FromPeerId, @@ -220,12 +225,13 @@ class StartChartManage { } // 发送回声测试消息 - void sendEchoMessage({required String ToPeerId}) async { + void sendEchoMessage() async { final message = MessageCommand.echoMessage( - ToPeerId: ToPeerId, + ToPeerId: echoPeerId, FromPeerId: FromPeerId, ); await _sendMessage(message: message); + _log(text: '发送回声测试消息'); } // 发送网关初始化消息 @@ -704,7 +710,6 @@ class StartChartManage { seconds: talkPingIntervalTime, ), (Timer timer) async { - // 重新发送上线消息 await sendTalkPingMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, diff --git a/lib/talk/startChart/start_chart_talk_status.dart b/lib/talk/startChart/start_chart_talk_status.dart index 31cda259..504e6aae 100644 --- a/lib/talk/startChart/start_chart_talk_status.dart +++ b/lib/talk/startChart/start_chart_talk_status.dart @@ -4,6 +4,7 @@ import 'package:star_lock/talk/startChart/events/talk_status_change_event.dart'; enum TalkStatus { waitingAnswer, // 等待接听 + waitingData, // 等待数据 duringCall, // 通话中 rejected, // 被拒绝 uninitialized, // 未初始化 diff --git a/pubspec.yaml b/pubspec.yaml index 3109fdae..e844e3e2 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -298,6 +298,9 @@ flutter: - images/mine/ - images/lockType/ - assets/ + - assets/html/h264.html + - assets/demo.h264 + - assets/talk.h264 - lan/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware