2024-12-12 15:48:15 +08:00
|
|
|
|
import 'dart:async';
|
2024-12-12 10:28:03 +08:00
|
|
|
|
import 'dart:convert';
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
import 'package:flutter/services.dart' show ByteData, Uint8List, rootBundle;
|
2024-12-13 14:29:33 +08:00
|
|
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
2024-12-12 15:48:15 +08:00
|
|
|
|
import 'package:star_lock/app_settings/app_colors.dart';
|
2024-12-13 14:29:33 +08:00
|
|
|
|
import 'package:star_lock/talk/startChart/handle/other/talk_data_repository.dart';
|
2024-12-17 09:17:50 +08:00
|
|
|
|
import 'package:star_lock/talk/startChart/proto/talk_data.pbserver.dart';
|
2024-12-12 15:48:15 +08:00
|
|
|
|
import 'package:star_lock/tools/titleAppBar.dart';
|
2024-12-12 10:28:03 +08:00
|
|
|
|
import 'package:webview_flutter/webview_flutter.dart';
|
|
|
|
|
|
|
2024-12-13 14:29:33 +08:00
|
|
|
|
class H264WebView extends StatefulWidget {
|
2024-12-12 10:28:03 +08:00
|
|
|
|
@override
|
2024-12-13 14:29:33 +08:00
|
|
|
|
_H264WebViewState createState() => _H264WebViewState();
|
2024-12-12 10:28:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-13 14:29:33 +08:00
|
|
|
|
class _H264WebViewState extends State<H264WebView> {
|
2024-12-12 10:28:03 +08:00
|
|
|
|
late final WebViewController _controller;
|
2024-12-12 16:05:53 +08:00
|
|
|
|
Timer? timer;
|
2024-12-13 14:29:33 +08:00
|
|
|
|
Timer? _sendTimer;
|
|
|
|
|
|
|
|
|
|
|
|
// 私有缓冲区,外部无法直接访问
|
|
|
|
|
|
final List<int> _buffer = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 发送数据至html文件间隔时间
|
|
|
|
|
|
final int sendDataToHtmlIntervalTime = 820;
|
|
|
|
|
|
|
|
|
|
|
|
// 通话数据流的单例流数据处理类
|
|
|
|
|
|
final TalkDataRepository talkDataRepository = TalkDataRepository.instance;
|
2024-12-12 10:28:03 +08:00
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
void initState() {
|
|
|
|
|
|
super.initState();
|
2024-12-13 14:29:33 +08:00
|
|
|
|
|
2024-12-12 10:28:03 +08:00
|
|
|
|
_controller = WebViewController()
|
|
|
|
|
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
|
|
|
|
|
..enableZoom(false)
|
|
|
|
|
|
..addJavaScriptChannel(
|
|
|
|
|
|
'Flutter',
|
|
|
|
|
|
onMessageReceived: (message) {
|
|
|
|
|
|
print("来自 HTML 的消息: ${message.message}");
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 加载本地 HTML
|
|
|
|
|
|
_loadLocalHtml();
|
2024-12-13 14:29:33 +08:00
|
|
|
|
simulateStreamFromAsset();
|
2024-12-12 10:28:03 +08:00
|
|
|
|
_sendFramesToHtml();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-13 14:29:33 +08:00
|
|
|
|
void simulateStreamFromAsset() async {
|
|
|
|
|
|
// 读取 assets 文件
|
2024-12-12 10:28:03 +08:00
|
|
|
|
final ByteData data = await rootBundle.load('assets/talk.h264');
|
|
|
|
|
|
final List<int> byteData = data.buffer.asUint8List();
|
2024-12-12 15:48:15 +08:00
|
|
|
|
int current = 0;
|
|
|
|
|
|
int start = 0;
|
|
|
|
|
|
int end = 0;
|
2024-12-13 14:29:33 +08:00
|
|
|
|
final List<int> chunks = extractChunks(byteData);
|
|
|
|
|
|
// 定时器控制发送数据块的节奏
|
|
|
|
|
|
timer ??= Timer.periodic(Duration(milliseconds: 10), (timer) {
|
|
|
|
|
|
if (current >= chunks.length) {
|
|
|
|
|
|
print('数据已经发完,重新进行发送');
|
2024-12-12 15:48:15 +08:00
|
|
|
|
start = 0;
|
2024-12-13 14:29:33 +08:00
|
|
|
|
end = 0;
|
|
|
|
|
|
current = 0;
|
|
|
|
|
|
timer.cancel();
|
|
|
|
|
|
return;
|
2024-12-12 10:28:03 +08:00
|
|
|
|
}
|
2024-12-13 14:29:33 +08:00
|
|
|
|
// 提取 NALU 边界并生成 chunks
|
2024-12-12 15:48:15 +08:00
|
|
|
|
end = chunks[current];
|
|
|
|
|
|
current++;
|
|
|
|
|
|
List<int> frameData = byteData.sublist(start, end);
|
2024-12-13 14:29:33 +08:00
|
|
|
|
if (frameData.length == 0) timer.cancel();
|
2024-12-17 09:17:50 +08:00
|
|
|
|
|
|
|
|
|
|
talkDataRepository.addTalkData(TalkData(contentType: TalkData_ContentTypeE.H264,content: frameData));
|
2024-12-12 15:48:15 +08:00
|
|
|
|
start = end;
|
|
|
|
|
|
});
|
2024-12-12 10:28:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-13 14:29:33 +08:00
|
|
|
|
void _sendFramesToHtml() async {
|
|
|
|
|
|
// 接收到流数据,保存到缓冲区
|
2024-12-17 09:17:50 +08:00
|
|
|
|
talkDataRepository.talkDataStream.listen((TalkData event) async {
|
|
|
|
|
|
_buffer.addAll(event.content);
|
2024-12-13 14:29:33 +08:00
|
|
|
|
});
|
|
|
|
|
|
// 缓冲800ms的数据,定时发送
|
|
|
|
|
|
_sendTimer ??= Timer.periodic(
|
|
|
|
|
|
Duration(milliseconds: sendDataToHtmlIntervalTime), (timer) async {
|
|
|
|
|
|
// 发送累积的数据
|
|
|
|
|
|
if (_buffer.isNotEmpty) {
|
|
|
|
|
|
await _sendBufferedData(_buffer);
|
|
|
|
|
|
_buffer.clear(); // 清空缓冲区
|
2024-12-12 10:28:03 +08:00
|
|
|
|
}
|
2024-12-13 14:29:33 +08:00
|
|
|
|
});
|
2024-12-12 15:48:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提取 NALU 边界并生成 chunks
|
|
|
|
|
|
List<int> extractChunks(List<int> byteData) {
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
int length = byteData.length;
|
|
|
|
|
|
int naluCount = 0;
|
|
|
|
|
|
int value;
|
|
|
|
|
|
int state = 0;
|
|
|
|
|
|
int lastIndex = 0;
|
|
|
|
|
|
List<int> result = [];
|
2024-12-13 14:29:33 +08:00
|
|
|
|
const minNaluPerChunk = 22; // 每个数据块包含的最小NALU数量
|
2024-12-12 15:48:15 +08:00
|
|
|
|
|
|
|
|
|
|
while (i < length) {
|
|
|
|
|
|
value = byteData[i++];
|
|
|
|
|
|
// finding 3 or 4-byte start codes (00 00 01 OR 00 00 00 01)
|
|
|
|
|
|
switch (state) {
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
if (value == 0) {
|
|
|
|
|
|
state = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
if (value == 0) {
|
|
|
|
|
|
state = 2;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
state = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 2:
|
|
|
|
|
|
case 3:
|
|
|
|
|
|
if (value == 0) {
|
|
|
|
|
|
state = 3;
|
|
|
|
|
|
} else if (value == 1 && i < length) {
|
|
|
|
|
|
if (lastIndex > 0) {
|
|
|
|
|
|
naluCount++;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (naluCount >= minNaluPerChunk) {
|
|
|
|
|
|
result.add(lastIndex - state - 1);
|
|
|
|
|
|
naluCount = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
state = 0;
|
|
|
|
|
|
lastIndex = i;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
state = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (naluCount > 0) {
|
|
|
|
|
|
result.add(lastIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-13 14:29:33 +08:00
|
|
|
|
|
|
|
|
|
|
|
2024-12-12 15:48:15 +08:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
2024-12-12 16:05:53 +08:00
|
|
|
|
|
2024-12-13 14:29:33 +08:00
|
|
|
|
/// 加载html文件
|
|
|
|
|
|
Future<void> _loadLocalHtml() async {
|
|
|
|
|
|
final String fileHtmlContent =
|
|
|
|
|
|
await rootBundle.loadString('assets/html/h264.html');
|
|
|
|
|
|
_controller.loadHtmlString(fileHtmlContent);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 发送数据给js处理
|
|
|
|
|
|
_sendBufferedData(List<int> buffer) async {
|
|
|
|
|
|
String jsCode = "feedDataFromFlutter(${buffer});";
|
|
|
|
|
|
await _controller.runJavaScript(jsCode);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
return WebViewWidget(controller: _controller);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-12 16:05:53 +08:00
|
|
|
|
@override
|
|
|
|
|
|
void dispose() {
|
|
|
|
|
|
timer?.cancel();
|
2024-12-13 14:29:33 +08:00
|
|
|
|
timer = null;
|
|
|
|
|
|
_sendTimer?.cancel();
|
|
|
|
|
|
timer = null;
|
|
|
|
|
|
// talkDataRepository.dispose();
|
2024-12-12 16:05:53 +08:00
|
|
|
|
super.dispose();
|
|
|
|
|
|
}
|
2024-12-12 10:28:03 +08:00
|
|
|
|
}
|