app-starlock/lib/talk/starChart/webView/h264_web_logic.dart

447 lines
15 KiB
Dart
Raw Normal View History

2025-02-22 17:15:33 +08:00
import 'dart:async';
2025-04-18 10:33:51 +08:00
import 'dart:collection';
2025-02-22 17:15:33 +08:00
import 'dart:io';
import 'dart:ui' as ui;
import 'dart:math'; // Import the math package to use sqrt
2025-02-21 15:55:35 +08:00
2025-02-22 17:15:33 +08:00
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
2025-02-21 15:55:35 +08:00
import 'package:flutter/services.dart';
2025-02-22 17:15:33 +08:00
import 'package:flutter_pcm_sound/flutter_pcm_sound.dart';
import 'package:flutter_voice_processor/flutter_voice_processor.dart';
import 'package:gallery_saver/gallery_saver.dart';
import 'package:get/get.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
2025-02-21 15:55:35 +08:00
import 'package:star_lock/app_settings/app_settings.dart';
2025-02-22 17:15:33 +08:00
import 'package:star_lock/login/login/entity/LoginEntity.dart';
import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_logic.dart';
import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_state.dart';
import 'package:star_lock/main/lockDetail/lockDetail/lockNetToken_entity.dart';
import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart';
import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart';
import 'package:star_lock/network/api_repository.dart';
import 'package:star_lock/talk/call/g711.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
2025-04-18 10:33:51 +08:00
import 'package:star_lock/talk/starChart/handle/other/packet_loss_statistics.dart';
2025-02-21 15:55:35 +08:00
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
2025-02-22 17:15:33 +08:00
import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart';
2025-02-21 15:55:35 +08:00
import 'package:star_lock/talk/starChart/star_chart_manage.dart';
2025-02-22 17:15:33 +08:00
import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart';
2025-02-21 15:55:35 +08:00
import 'package:star_lock/talk/starChart/webView/h264_web_view_state.dart';
2025-04-18 10:33:51 +08:00
import 'package:star_lock/tools/G711Tool.dart';
2025-02-22 17:15:33 +08:00
import 'package:star_lock/tools/bugly/bugly_tool.dart';
2025-02-21 15:55:35 +08:00
import 'package:webview_flutter/webview_flutter.dart';
2025-02-22 17:15:33 +08:00
import '../../../../tools/baseGetXController.dart';
2025-02-21 15:55:35 +08:00
class H264WebViewLogic extends BaseGetXController {
final H264WebViewState state = H264WebViewState();
2025-02-22 17:15:33 +08:00
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
2025-04-18 10:33:51 +08:00
// 添加模拟数据相关变量
static const int CHUNK_SIZE = 4096;
Timer? _mockDataTimer;
// 定义音频帧缓冲和发送函数
final List<int> _bufferedAudioFrames = <int>[];
final Queue<List<int>> _frameBuffer = Queue<List<int>>();
static const int FRAME_BUFFER_SIZE = 25;
2025-02-21 15:55:35 +08:00
@override
void onInit() {
super.onInit();
// 初始化 WebView 控制器
state.webViewController = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..enableZoom(false)
..addJavaScriptChannel(
'Flutter',
onMessageReceived: (message) {
print("来自 HTML 的消息: ${message.message}");
},
);
2025-02-22 17:15:33 +08:00
state.isShowLoading.value = true;
2025-02-21 15:55:35 +08:00
// 加载本地 HTML
_loadLocalHtml();
// 创建流数据监听
_createFramesStreamListen();
2025-04-18 10:33:51 +08:00
// playLocalTestVideo();
2025-02-22 17:15:33 +08:00
_startListenTalkStatus();
state.talkStatus.value = state.startChartTalkStatus.status;
// 初始化音频播放器
_initFlutterPcmSound();
// 初始化录音控制器
_initAudioRecorder();
}
/// 初始化音频录制器
void _initAudioRecorder() {
state.voiceProcessor = VoiceProcessor.instance;
}
/// 初始化音频播放器
void _initFlutterPcmSound() {
const int sampleRate = 8000;
FlutterPcmSound.setLogLevel(LogLevel.none);
FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 1);
// 设置 feed 阈值
if (Platform.isAndroid) {
FlutterPcmSound.setFeedThreshold(1024); // Android 平台的特殊处理
} else {
FlutterPcmSound.setFeedThreshold(2000); // 非 Android 平台的处理
}
2025-02-21 15:55:35 +08:00
}
void _createFramesStreamListen() async {
state.talkDataRepository.talkDataStream.listen((TalkData event) async {
2025-04-18 10:33:51 +08:00
// 添加新帧到缓冲区
_frameBuffer.add(event.content);
// 当缓冲区超过最大容量时,发送最早的帧并移除
while (_frameBuffer.length > FRAME_BUFFER_SIZE) {
if (_frameBuffer.isNotEmpty) {
final frame = _frameBuffer.removeFirst();
await _sendBufferedData(frame);
}
}
2025-02-21 15:55:35 +08:00
});
}
2025-04-18 10:33:51 +08:00
/// 播放本地测试视频文件
// Future<void> playLocalTestVideo() async {
// try {
// ByteData data = await rootBundle.load('assets/html/demo.h264');
// List<int> bytes = data.buffer.asUint8List();
//
// int offset = 0;
// _mockDataTimer = Timer.periodic(Duration(milliseconds: 40), (timer) {
// if (offset >= bytes.length) {
// timer.cancel();
// return;
// }
//
// int end = min(offset + CHUNK_SIZE, bytes.length);
// List<int> chunk = bytes.sublist(offset, end);
// _sendBufferedData(chunk);
//
// offset += CHUNK_SIZE;
// });
// } catch (e) {
// AppLog.log('加载测试视频文件失败: $e');
// }
// }
2025-02-21 15:55:35 +08:00
/// 加载html文件
Future<void> _loadLocalHtml() async {
// 加载 HTML 文件内容
final String fileHtmlContent =
await rootBundle.loadString('assets/html/h264.html');
// 加载 JS 文件内容
final String jsContent =
await rootBundle.loadString('assets/html/jmuxer.min.js');
// 将 JS 文件内容嵌入到 HTML 中
final String htmlWithJs = fileHtmlContent.replaceAll(
'<script src="jmuxer.min.js"></script>', // 替换掉引用外部 JS 的标签
'<script>$jsContent</script>' // 使用内联方式嵌入 JS 内容
);
// 加载最终的 HTML 字符串到 WebView 中
if (state.webViewController != null) {
state.webViewController.loadHtmlString(htmlWithJs); // 设置 baseUrl 避免资源加载问题
}
}
// 修改后的发送方法
_sendBufferedData(List<int> buffer) async {
// 原始发送逻辑
String jsCode = "feedDataFromFlutter($buffer);";
await state.webViewController.runJavaScript(jsCode);
2025-02-22 17:15:33 +08:00
if (state.isShowLoading.isTrue) {
await Future.delayed(Duration(seconds: 1));
state.isShowLoading.value = false;
}
}
/// 监听对讲状态
void _startListenTalkStatus() {
state.startChartTalkStatus.statusStream.listen((talkStatus) {
state.talkStatus.value = talkStatus;
switch (talkStatus) {
case TalkStatus.rejected:
case TalkStatus.hangingUpDuring:
case TalkStatus.notTalkData:
case TalkStatus.notTalkPing:
case TalkStatus.end:
_handleInvalidTalkStatus();
break;
case TalkStatus.answeredSuccessfully:
state.oneMinuteTimeTimer?.cancel(); // 取消旧定时器
state.oneMinuteTimeTimer ??=
Timer.periodic(const Duration(seconds: 1), (Timer t) {
if (state.isShowLoading.isFalse) {
state.oneMinuteTime.value++;
if (state.oneMinuteTime.value >= 60) {
t.cancel(); // 取消定时器
state.oneMinuteTime.value = 0;
}
}
});
break;
default:
// 其他状态的处理
break;
}
});
}
/// 更新发送预期数据
void updateTalkExpect() {
TalkExpectReq talkExpectReq = TalkExpectReq();
state.isOpenVoice.value = !state.isOpenVoice.value;
if (!state.isOpenVoice.value) {
talkExpectReq = TalkExpectReq(
videoType: [VideoTypeE.IMAGE],
audioType: [],
);
showToast('已静音'.tr);
} else {
talkExpectReq = TalkExpectReq(
videoType: [VideoTypeE.IMAGE],
audioType: [AudioTypeE.G711],
);
}
/// 修改发送预期数据
StartChartManage().changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
talkExpect: talkExpectReq);
}
/// 处理无效通话状态
void _handleInvalidTalkStatus() {}
/// 截图并保存到相册
Future<void> captureAndSavePng() async {
try {
if (state.globalKey.currentContext == null) {
AppLog.log('截图失败: 未找到当前上下文');
return;
}
final RenderRepaintBoundary boundary = state.globalKey.currentContext!
.findRenderObject()! as RenderRepaintBoundary;
final ui.Image image = await boundary.toImage();
final ByteData? byteData =
await image.toByteData(format: ui.ImageByteFormat.png);
if (byteData == null) {
AppLog.log('截图失败: 图像数据为空');
return;
}
final Uint8List pngBytes = byteData.buffer.asUint8List();
// 获取应用程序的文档目录
final Directory directory = await getApplicationDocumentsDirectory();
final String imagePath = '${directory.path}/screenshot.png';
// 将截图保存为文件
final File imgFile = File(imagePath);
await imgFile.writeAsBytes(pngBytes);
// 将截图保存到相册
await ImageGallerySaver.saveFile(imagePath);
AppLog.log('截图保存路径: $imagePath');
showToast('截图已保存到相册'.tr);
} catch (e) {
AppLog.log('截图失败: $e');
}
}
// 发起接听命令
void initiateAnswerCommand() {
StartChartManage().startTalkAcceptTimer();
}
//开始录音
Future<void> startProcessingAudio() async {
try {
if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) {
await state.voiceProcessor?.start(state.frameLength, state.sampleRate);
final bool? isRecording = await state.voiceProcessor?.isRecording();
state.isRecordingAudio.value = isRecording!;
state.startRecordingAudioTime.value = DateTime.now();
2025-04-18 10:33:51 +08:00
// 增加录音帧监听器和错误监听器
state.voiceProcessor
?.addFrameListeners(<VoiceProcessorFrameListener>[_onFrame]);
state.voiceProcessor?.addErrorListener(_onError);
2025-02-22 17:15:33 +08:00
} else {
// state.errorMessage.value = 'Recording permission not granted';
}
} on PlatformException catch (ex) {
// state.errorMessage.value = 'Failed to start recorder: $ex';
}
state.isOpenVoice.value = false;
}
/// 停止录音
Future<void> stopProcessingAudio() async {
try {
await state.voiceProcessor?.stop();
state.voiceProcessor?.removeFrameListener(_onFrame);
state.udpSendDataFrameNumber = 0;
// 记录结束时间
state.endRecordingAudioTime.value = DateTime.now();
// 计算录音的持续时间
2025-04-18 10:33:51 +08:00
final Duration duration = state.endRecordingAudioTime.value
.difference(state.startRecordingAudioTime.value);
2025-02-22 17:15:33 +08:00
state.recordingAudioTime.value = duration.inSeconds;
} on PlatformException catch (ex) {
// state.errorMessage.value = 'Failed to stop recorder: $ex';
} finally {
final bool? isRecording = await state.voiceProcessor?.isRecording();
state.isRecordingAudio.value = isRecording!;
state.isOpenVoice.value = true;
}
}
2025-04-18 10:33:51 +08:00
// 音频帧处理
2025-02-22 17:15:33 +08:00
Future<void> _onFrame(List<int> frame) async {
2025-04-18 10:33:51 +08:00
// 添加最大缓冲限制
if (_bufferedAudioFrames.length > state.frameLength * 3) {
_bufferedAudioFrames.clear(); // 清空过多积累的数据
return;
}
// 首先应用固定增益提升基础音量
List<int> amplifiedFrame = _applyGain(frame, 1.6);
// 编码为G711数据
List<int> encodedData = G711Tool.encode(amplifiedFrame, 0); // 0表示A-law
_bufferedAudioFrames.addAll(encodedData);
// 使用相对时间戳
final int ms = DateTime.now().millisecondsSinceEpoch % 1000000; // 使用循环时间戳
int getFrameLength = state.frameLength;
if (Platform.isIOS) {
getFrameLength = state.frameLength * 2;
}
// 添加发送间隔控制
if (_bufferedAudioFrames.length >= state.frameLength) {
try {
await StartChartManage().sendTalkDataMessage(
talkData: TalkData(
content: _bufferedAudioFrames,
contentType: TalkData_ContentTypeE.G711,
durationMs: ms,
),
);
} finally {
_bufferedAudioFrames.clear(); // 确保清理缓冲区
}
} else {
_bufferedAudioFrames.addAll(encodedData);
}
}
// 错误监听
void _onError(VoiceProcessorException error) {
AppLog.log(error.message!);
}
// 添加音频增益处理方法
List<int> _applyGain(List<int> pcmData, double gainFactor) {
List<int> result = List<int>.filled(pcmData.length, 0);
for (int i = 0; i < pcmData.length; i++) {
// PCM数据通常是有符号的16位整数
int sample = pcmData[i];
// 应用增益
double amplified = sample * gainFactor;
// 限制在有效范围内,防止溢出
if (amplified > 32767) {
amplified = 32767;
} else if (amplified < -32768) {
amplified = -32768;
}
result[i] = amplified.toInt();
}
return result;
2025-02-22 17:15:33 +08:00
}
/// 挂断
void udpHangUpAction() async {
if (state.talkStatus.value == TalkStatus.answeredSuccessfully) {
// 如果是通话中就挂断
StartChartManage().startTalkHangupMessageTimer();
} else {
// 拒绝
StartChartManage().startTalkRejectMessageTimer();
}
2025-04-18 10:33:51 +08:00
// _mockDataTimer?.cancel();
// _mockDataTimer = null;
PacketLossStatistics().reset();
2025-02-22 17:15:33 +08:00
Get.back();
}
// 远程开锁
Future<void> remoteOpenLock() async {
final lockPeerId = StartChartManage().lockPeerId;
final lockListPeerId = StartChartManage().lockListPeerId;
int lockId = lockDetailState.keyInfos.value.lockId ?? 0;
// 如果锁列表获取到peerId代表有多个锁使用锁列表的peerId
// 从列表中遍历出对应的peerId
lockListPeerId.forEach((element) {
if (element.network?.peerId == lockPeerId) {
lockId = element.lockId ?? 0;
}
});
final LockSetInfoEntity lockSetInfoEntity =
2025-04-18 10:33:51 +08:00
await ApiRepository.to.getLockSettingInfoData(
2025-02-22 17:15:33 +08:00
lockId: lockId.toString(),
);
if (lockSetInfoEntity.errorCode!.codeIsSuccessful) {
if (lockSetInfoEntity.data?.lockFeature?.remoteUnlock == 1 &&
lockSetInfoEntity.data?.lockSettingInfo?.remoteUnlock == 1) {
final LoginEntity entity = await ApiRepository.to
.remoteOpenLock(lockId: lockId.toString(), timeOut: 60);
if (entity.errorCode!.codeIsSuccessful) {
showToast('已开锁'.tr);
StartChartManage().lockListPeerId = [];
}
} else {
showToast('该锁的远程开锁功能未启用'.tr);
}
}
}
2025-02-21 15:55:35 +08:00
@override
2025-02-22 17:15:33 +08:00
void dispose() {
2025-04-18 10:33:51 +08:00
// _mockDataTimer?.cancel();
// _mockDataTimer = null;
2025-02-22 17:15:33 +08:00
super.dispose();
2025-02-21 15:55:35 +08:00
StartChartManage().startTalkHangupMessageTimer();
2025-02-22 17:15:33 +08:00
state.animationController.dispose();
state.webViewController.clearCache();
state.webViewController.reload();
state.oneMinuteTimeTimer?.cancel();
state.oneMinuteTimeTimer = null;
stopProcessingAudio();
StartChartManage().reSetDefaultTalkExpect();
2025-04-18 10:33:51 +08:00
_frameBuffer.clear();
2025-02-21 15:55:35 +08:00
}
}