app-starlock/lib/talk/startChart/webView/h264_web_view.dart

183 lines
4.9 KiB
Dart
Raw Normal View History

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';
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';
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();
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('数据已经发完,重新进行发送');
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
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));
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
});
}
// 提取 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数量
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
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
}