fix:增加读取h264文件后使用定时器发送,调整html样式
This commit is contained in:
parent
5b2cd50879
commit
f3e42e3ac8
@ -6,8 +6,19 @@
|
|||||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
<title>play</title>
|
<title>play</title>
|
||||||
</head>
|
</head>
|
||||||
|
<style>
|
||||||
|
html{
|
||||||
|
margin:0 0;
|
||||||
|
padding: 0 0;
|
||||||
|
}
|
||||||
|
body{
|
||||||
|
margin:0 0;
|
||||||
|
padding: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<video controls autoplay muted style="width: 100vw;height: 100vh;" id="player">
|
<video autoplay muted style="width: 100vw;height: 100vh;" poster="images/loader-thumb.jpg" id="player">
|
||||||
</video>
|
</video>
|
||||||
<script>
|
<script>
|
||||||
(function (global, factory) {
|
(function (global, factory) {
|
||||||
@ -2644,7 +2655,7 @@
|
|||||||
jmuxer = new JMuxer({
|
jmuxer = new JMuxer({
|
||||||
node: 'player',
|
node: 'player',
|
||||||
mode: 'video',
|
mode: 'video',
|
||||||
flushingTime: 0,
|
flushingTime: 1000,
|
||||||
fps: 22,
|
fps: 22,
|
||||||
debug: true,
|
debug: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart' show ByteData, Uint8List, rootBundle;
|
import 'package:flutter/services.dart' show ByteData, Uint8List, rootBundle;
|
||||||
|
import 'package:star_lock/app_settings/app_colors.dart';
|
||||||
|
import 'package:star_lock/tools/titleAppBar.dart';
|
||||||
import 'package:webview_flutter/webview_flutter.dart';
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
|
||||||
class LocalHtmlPage extends StatefulWidget {
|
class LocalHtmlPage extends StatefulWidget {
|
||||||
@ -34,32 +37,52 @@ class _LocalHtmlPageState extends State<LocalHtmlPage> {
|
|||||||
final ByteData data = await rootBundle.load('assets/talk.h264');
|
final ByteData data = await rootBundle.load('assets/talk.h264');
|
||||||
final List<int> byteData = data.buffer.asUint8List();
|
final List<int> byteData = data.buffer.asUint8List();
|
||||||
|
|
||||||
int offset = 0;
|
// 提取 NALU 边界并生成 chunks
|
||||||
int frameSize = 0;
|
final List<int> chunks = extractChunks(byteData);
|
||||||
|
final int total = chunks.length;
|
||||||
|
|
||||||
// 根据 H.264 数据的帧结构来逐帧读取并发送
|
int current = 0;
|
||||||
while (offset < byteData.length) {
|
int start = 0;
|
||||||
// 获取每一帧的大小
|
int end = 0;
|
||||||
frameSize = getFrameSize(byteData, offset);
|
List<int> buffer = []; // 用于累积帧数据的缓冲区
|
||||||
|
Timer? timer;
|
||||||
|
|
||||||
if (frameSize == 0) {
|
// 创建一个定时器,每800毫秒触发一次
|
||||||
print("No more frames or error in frame size calculation.");
|
timer ??= Timer.periodic(Duration(milliseconds: 800), (timer) async {
|
||||||
break; // 如果没有更多的帧,或者无法计算帧的大小,则退出循环
|
if (current >= total) {
|
||||||
|
current = 0;
|
||||||
|
start = 0;
|
||||||
|
print("All frames sent. Restarting from the beginning.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提取当前帧数据
|
end = chunks[current];
|
||||||
List<int> frameData = byteData.sublist(offset, offset + frameSize);
|
current++;
|
||||||
|
|
||||||
// 将当前帧数据发送到 WebView 中的 feedDataFromFlutter 函数
|
// 累积当前帧数据到缓冲区
|
||||||
String jsCode = "feedDataFromFlutter(${frameData});";
|
List<int> frameData = byteData.sublist(start, end);
|
||||||
await _controller.runJavaScript(jsCode);
|
buffer.addAll(frameData);
|
||||||
|
start = end;
|
||||||
|
|
||||||
// 更新偏移量,继续发送下一个帧
|
// 发送累积的数据
|
||||||
offset += frameSize;
|
if (buffer.isNotEmpty) {
|
||||||
|
await _sendBufferedData(buffer);
|
||||||
|
buffer.clear(); // 清空缓冲区
|
||||||
|
}
|
||||||
|
|
||||||
// 控制帧率,模拟每秒 22 帧播放(或者根据你的视频帧率调整)
|
// 打印进度信息(可选)
|
||||||
await Future.delayed(Duration(milliseconds: (1000 / 22).toInt()));
|
if (current % 50 == 0) {
|
||||||
}
|
print("I am serving, no problem!");
|
||||||
|
}
|
||||||
|
if (current == 0) {
|
||||||
|
print("Started from first chunk...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果所有帧都已处理完毕,取消定时器
|
||||||
|
if (current >= total) {
|
||||||
|
timer.cancel();
|
||||||
|
print('All frames sent. Stopping timer...');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int getFrameSize(List<int> data, int offset) {
|
int getFrameSize(List<int> data, int offset) {
|
||||||
@ -128,10 +151,69 @@ class _LocalHtmlPageState extends State<LocalHtmlPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
|
||||||
title: Text("加载本地 HTML"),
|
|
||||||
),
|
|
||||||
body: WebViewWidget(controller: _controller),
|
body: WebViewWidget(controller: _controller),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_sendBufferedData(List<int> buffer) async {
|
||||||
|
String jsCode = "feedDataFromFlutter(${buffer});";
|
||||||
|
await _controller.runJavaScript(jsCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取 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 = 30; // 每个数据块包含的最小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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,18 +22,17 @@ class UdpTalkAcceptHandler extends ScpMessageBaseHandle
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void handleResp(ScpMessage scpMessage) async {
|
void handleResp(ScpMessage scpMessage) {
|
||||||
// 收到同意接听回复
|
// 收到同意接听回复
|
||||||
final GenericResp genericResp = scpMessage.Payload;
|
final GenericResp genericResp = scpMessage.Payload;
|
||||||
if (checkGenericRespSuccess(genericResp)) {
|
if (checkGenericRespSuccess(genericResp)) {
|
||||||
// 启动通话保持定时器
|
// 启动通话保持定时器
|
||||||
_handleStartTalkPing();
|
_handleStartTalkPing();
|
||||||
await Future.delayed(Duration(seconds: 2), () {
|
Future.delayed(Duration(seconds: 2), () {
|
||||||
// 启动发送预期数据请求
|
// 启动发送预期数据请求
|
||||||
_handleStartSendTalkExpectDataRequest();
|
_handleStartSendTalkExpectDataRequest();
|
||||||
// 启动通话数据检查的定时器
|
// 启动通话数据检查的定时器
|
||||||
_handleCheckTalkDataTimer();
|
_handleCheckTalkDataTimer();
|
||||||
|
|
||||||
});
|
});
|
||||||
// 停止播放铃声
|
// 停止播放铃声
|
||||||
stopRingtone();
|
stopRingtone();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user