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

183 lines
4.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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/talk/startChart/proto/talk_data.pbserver.dart';
import 'package:star_lock/tools/titleAppBar.dart';
import 'package:webview_flutter/webview_flutter.dart';
class H264WebView extends StatefulWidget {
@override
_H264WebViewState createState() => _H264WebViewState();
}
class _H264WebViewState extends State<H264WebView> {
late final WebViewController _controller;
Timer? timer;
Timer? _sendTimer;
// 私有缓冲区,外部无法直接访问
final List<int> _buffer = [];
// 发送数据至html文件间隔时间
final int sendDataToHtmlIntervalTime = 820;
// 通话数据流的单例流数据处理类
final TalkDataRepository talkDataRepository = TalkDataRepository.instance;
@override
void initState() {
super.initState();
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..enableZoom(false)
..addJavaScriptChannel(
'Flutter',
onMessageReceived: (message) {
print("来自 HTML 的消息: ${message.message}");
},
);
// 加载本地 HTML
_loadLocalHtml();
simulateStreamFromAsset();
_sendFramesToHtml();
}
void simulateStreamFromAsset() async {
// 读取 assets 文件
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;
final List<int> chunks = extractChunks(byteData);
// 定时器控制发送数据块的节奏
timer ??= Timer.periodic(Duration(milliseconds: 10), (timer) {
if (current >= chunks.length) {
print('数据已经发完,重新进行发送');
start = 0;
end = 0;
current = 0;
timer.cancel();
return;
}
// 提取 NALU 边界并生成 chunks
end = chunks[current];
current++;
List<int> frameData = byteData.sublist(start, end);
if (frameData.length == 0) timer.cancel();
talkDataRepository.addTalkData(TalkData(contentType: TalkData_ContentTypeE.H264,content: frameData));
start = end;
});
}
void _sendFramesToHtml() async {
// 接收到流数据,保存到缓冲区
talkDataRepository.talkDataStream.listen((TalkData event) async {
_buffer.addAll(event.content);
});
// 缓冲800ms的数据定时发送
_sendTimer ??= Timer.periodic(
Duration(milliseconds: sendDataToHtmlIntervalTime), (timer) async {
// 发送累积的数据
if (_buffer.isNotEmpty) {
await _sendBufferedData(_buffer);
_buffer.clear(); // 清空缓冲区
}
});
}
// 提取 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 = [];
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);
}
return result;
}
/// 加载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);
}
@override
void dispose() {
timer?.cancel();
timer = null;
_sendTimer?.cancel();
timer = null;
// talkDataRepository.dispose();
super.dispose();
}
}