183 lines
4.9 KiB
Dart
183 lines
4.9 KiB
Dart
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();
|
||
}
|
||
}
|