fix:增加页面播放逻辑、调整proto文件
This commit is contained in:
parent
133f863448
commit
c865db7a9f
@ -1011,6 +1011,7 @@
|
|||||||
"请在锁设置中开启远程开锁": "Please enable remote unlocking in the lock settings",
|
"请在锁设置中开启远程开锁": "Please enable remote unlocking in the lock settings",
|
||||||
"接听": "Answer",
|
"接听": "Answer",
|
||||||
"截图已保存到相册": "Screenshot saved to album",
|
"截图已保存到相册": "Screenshot saved to album",
|
||||||
|
"录屏已保存到相册": "Screen recording file saved to album",
|
||||||
"添加遥控": "Add remote control",
|
"添加遥控": "Add remote control",
|
||||||
"已连接到锁,请按遥控": "Connected to the lock, please press the remote control",
|
"已连接到锁,请按遥控": "Connected to the lock, please press the remote control",
|
||||||
"遥控号": "Remote control number",
|
"遥控号": "Remote control number",
|
||||||
|
|||||||
@ -1014,6 +1014,7 @@
|
|||||||
"请在锁设置中开启远程开锁": "请在锁设置中开启远程开锁",
|
"请在锁设置中开启远程开锁": "请在锁设置中开启远程开锁",
|
||||||
"接听": "接听",
|
"接听": "接听",
|
||||||
"截图已保存到相册": "截图已保存到相册",
|
"截图已保存到相册": "截图已保存到相册",
|
||||||
|
"录屏已保存到相册": "录屏已保存到相册",
|
||||||
"添加遥控": "添加遥控",
|
"添加遥控": "添加遥控",
|
||||||
"已连接到锁,请按遥控": "已连接到锁,请按遥控",
|
"已连接到锁,请按遥控": "已连接到锁,请按遥控",
|
||||||
"遥控号": "遥控号",
|
"遥控号": "遥控号",
|
||||||
|
|||||||
@ -1013,6 +1013,7 @@
|
|||||||
"请在锁设置中开启远程开锁": "请在锁设置中开启远程开锁",
|
"请在锁设置中开启远程开锁": "请在锁设置中开启远程开锁",
|
||||||
"接听": "接听",
|
"接听": "接听",
|
||||||
"截图已保存到相册": "截图已保存到相册",
|
"截图已保存到相册": "截图已保存到相册",
|
||||||
|
"录屏已保存到相册": "录屏已保存到相册",
|
||||||
"添加遥控": "添加遥控",
|
"添加遥控": "添加遥控",
|
||||||
"已连接到锁,请按遥控": "已连接到锁,请按遥控",
|
"已连接到锁,请按遥控": "已连接到锁,请按遥控",
|
||||||
"遥控号": "遥控号",
|
"遥控号": "遥控号",
|
||||||
|
|||||||
@ -39,6 +39,9 @@ class UdpTalkAcceptHandler extends ScpMessageBaseHandle
|
|||||||
stopRingtone();
|
stopRingtone();
|
||||||
// 设置状态为接听成功
|
// 设置状态为接听成功
|
||||||
talkStatus.setAnsweredSuccessfully();
|
talkStatus.setAnsweredSuccessfully();
|
||||||
|
// 同意接听之后,停止对讲请求超时监听定时器
|
||||||
|
talkeRequestOverTimeTimerManager.receiveMessage();
|
||||||
|
talkeRequestOverTimeTimerManager.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,10 @@ class UdpTalkHangUpHandler extends ScpMessageBaseHandle
|
|||||||
startChartManage.stopTalkExpectMessageTimer();
|
startChartManage.stopTalkExpectMessageTimer();
|
||||||
talkStatus.setHangingUpDuring();
|
talkStatus.setHangingUpDuring();
|
||||||
stopRingtone();
|
stopRingtone();
|
||||||
|
|
||||||
|
// 拒绝接听之后,停止对讲请求超时监听定时器
|
||||||
|
talkeRequestOverTimeTimerManager.receiveMessage();
|
||||||
|
talkeRequestOverTimeTimerManager.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -18,16 +18,13 @@ class UdpTalkRejectHandler extends ScpMessageBaseHandle
|
|||||||
@override
|
@override
|
||||||
void handleReq(ScpMessage scpMessage) {
|
void handleReq(ScpMessage scpMessage) {
|
||||||
// 收到接听拒绝请求
|
// 收到接听拒绝请求
|
||||||
startChartManage.sendGenericRespSuccessMessage(
|
// 回复成功消息
|
||||||
ToPeerId: scpMessage.FromPeerId!,
|
replySuccessMessage(scpMessage);
|
||||||
FromPeerId: scpMessage.ToPeerId!,
|
// 停止铃声
|
||||||
PayloadType: scpMessage.PayloadType!,
|
|
||||||
);
|
|
||||||
startChartManage.stopTalkPingMessageTimer();
|
|
||||||
startChartManage.stopTalkExpectMessageTimer();
|
|
||||||
stopRingtone();
|
stopRingtone();
|
||||||
// 收到接听拒绝回复
|
// 收到接听拒绝回复
|
||||||
talkStatus.setRejected();
|
talkStatus.setRejected();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -35,6 +32,11 @@ class UdpTalkRejectHandler extends ScpMessageBaseHandle
|
|||||||
startChartManage.stopTalkPingMessageTimer();
|
startChartManage.stopTalkPingMessageTimer();
|
||||||
startChartManage.stopTalkExpectMessageTimer();
|
startChartManage.stopTalkExpectMessageTimer();
|
||||||
stopRingtone();
|
stopRingtone();
|
||||||
|
|
||||||
|
|
||||||
|
// 拒绝接听之后,停止对讲请求超时监听定时器
|
||||||
|
talkeRequestOverTimeTimerManager.receiveMessage();
|
||||||
|
talkeRequestOverTimeTimerManager.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -34,6 +34,16 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
|
|||||||
startChartManage.ToPeerId = scpMessage.FromPeerId!;
|
startChartManage.ToPeerId = scpMessage.FromPeerId!;
|
||||||
// 处理收到接听请求后的事件
|
// 处理收到接听请求后的事件
|
||||||
_talkRequestEvent(talkObjectName: talkReq.callerName);
|
_talkRequestEvent(talkObjectName: talkReq.callerName);
|
||||||
|
|
||||||
|
// 启动对讲请求超时定时器
|
||||||
|
talkeRequestOverTimeTimerManager.startTimer();
|
||||||
|
talkeRequestOverTimeTimerManager.setOnTimeout(() {
|
||||||
|
if (talkStatus.status == TalkStatus.waitingAnswer) {
|
||||||
|
// 超时未接听,发送挂断请求
|
||||||
|
startChartManage.sendTalkRejectMessage();
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -36,6 +36,11 @@ class ScpMessageBaseHandle {
|
|||||||
|
|
||||||
final audioManager = AudioPlayerManager();
|
final audioManager = AudioPlayerManager();
|
||||||
|
|
||||||
|
// 通话请求超时未处理监听定时器管理
|
||||||
|
final talkeRequestOverTimeTimerManager = OverTimeTimerManager(
|
||||||
|
timeoutInSeconds: 30,
|
||||||
|
);
|
||||||
|
|
||||||
// 通话保持超时监听定时器管理
|
// 通话保持超时监听定时器管理
|
||||||
final talkePingOverTimeTimerManager = OverTimeTimerManager(
|
final talkePingOverTimeTimerManager = OverTimeTimerManager(
|
||||||
timeoutInSeconds: 260,
|
timeoutInSeconds: 260,
|
||||||
|
|||||||
@ -86,7 +86,7 @@ class StartChartManage {
|
|||||||
final int _maxPayloadSize = 8 * 1024; // 分包大小
|
final int _maxPayloadSize = 8 * 1024; // 分包大小
|
||||||
|
|
||||||
// 默认通话的期望数据格式
|
// 默认通话的期望数据格式
|
||||||
TalkExpectReq defaultTalkExpect = TalkExpectReq(
|
TalkExpectReq _defaultTalkExpect = TalkExpectReq(
|
||||||
videoType: [VideoTypeE.IMAGE],
|
videoType: [VideoTypeE.IMAGE],
|
||||||
audioType: [AudioTypeE.G711],
|
audioType: [AudioTypeE.G711],
|
||||||
);
|
);
|
||||||
@ -857,7 +857,7 @@ class StartChartManage {
|
|||||||
(Timer timer) {
|
(Timer timer) {
|
||||||
// 发送期望接受消息
|
// 发送期望接受消息
|
||||||
sendTalkExpectMessage(
|
sendTalkExpectMessage(
|
||||||
talkExpect: defaultTalkExpect,
|
talkExpect: _defaultTalkExpect,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -971,9 +971,16 @@ class StartChartManage {
|
|||||||
talkExpectTimer = null; // 清除定时器引用
|
talkExpectTimer = null; // 清除定时器引用
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重新发送预期数据
|
||||||
|
void reStartTalkExpectMessageTimer() {
|
||||||
|
stopTalkExpectMessageTimer();
|
||||||
|
startTalkExpectTimer();
|
||||||
|
}
|
||||||
|
|
||||||
/// 修改预期接收到的数据
|
/// 修改预期接收到的数据
|
||||||
void changeTalkExpectDataType({required TalkExpectReq talkExpect}) {
|
void changeTalkExpectDataType({required TalkExpectReq talkExpect}) {
|
||||||
defaultTalkExpect = talkExpect;
|
_defaultTalkExpect = talkExpect;
|
||||||
|
reStartTalkExpectMessageTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 销毁资源
|
/// 销毁资源
|
||||||
|
|||||||
@ -1,34 +1,36 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
import 'package:flutter_pcm_sound/flutter_pcm_sound.dart';
|
import 'package:flutter_pcm_sound/flutter_pcm_sound.dart';
|
||||||
import 'package:flutter_voice_processor/flutter_voice_processor.dart';
|
import 'package:flutter_screen_recording/flutter_screen_recording.dart';
|
||||||
|
import 'package:gallery_saver/gallery_saver.dart';
|
||||||
|
|
||||||
import 'package:get/get.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';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:star_lock/app_settings/app_settings.dart';
|
import 'package:star_lock/app_settings/app_settings.dart';
|
||||||
import 'package:star_lock/blue/io_tool/manager_event_bus.dart';
|
|
||||||
import 'package:star_lock/talk/call/callTalk.dart';
|
|
||||||
import 'package:star_lock/talk/startChart/constant/talk_status.dart';
|
import 'package:star_lock/talk/startChart/constant/talk_status.dart';
|
||||||
import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart';
|
import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart';
|
||||||
import 'package:star_lock/talk/startChart/proto/talk_data.pbenum.dart';
|
import 'package:star_lock/talk/startChart/proto/talk_data.pbenum.dart';
|
||||||
|
import 'package:star_lock/talk/startChart/proto/talk_expect.pb.dart';
|
||||||
import 'package:star_lock/talk/startChart/start_chart_manage.dart';
|
import 'package:star_lock/talk/startChart/start_chart_manage.dart';
|
||||||
import 'package:star_lock/talk/startChart/start_chart_talk_status.dart';
|
|
||||||
import 'package:star_lock/talk/startChart/views/talkView/talk_view_state.dart';
|
import 'package:star_lock/talk/startChart/views/talkView/talk_view_state.dart';
|
||||||
|
|
||||||
import '../../../../talk/call/g711.dart';
|
|
||||||
import '../../../../talk/udp/udp_manage.dart';
|
|
||||||
import '../../../../talk/udp/udp_senderManage.dart';
|
|
||||||
import '../../../../tools/baseGetXController.dart';
|
import '../../../../tools/baseGetXController.dart';
|
||||||
import '../../../../tools/eventBusEventManage.dart';
|
|
||||||
|
|
||||||
class TalkViewLogic extends BaseGetXController {
|
class TalkViewLogic extends BaseGetXController {
|
||||||
final TalkViewState state = TalkViewState();
|
final TalkViewState state = TalkViewState();
|
||||||
|
|
||||||
Timer? _syncTimer;
|
Timer? _syncTimer;
|
||||||
int _startTime = 0;
|
int _startTime = 0;
|
||||||
final int bufferSize = 22; // 缓冲区大小(以帧为单位)
|
final int bufferSize = 20; // 缓冲区大小(以帧为单位)
|
||||||
final List<int> frameTimestamps = [];
|
final List<int> frameTimestamps = [];
|
||||||
int frameIntervalMs = 45; // 初始帧间隔设置为45毫秒(约22FPS)
|
int frameIntervalMs = 45; // 初始帧间隔设置为45毫秒(约22FPS)
|
||||||
int minFrameIntervalMs = 30; // 最小帧间隔(约33 FPS)
|
int minFrameIntervalMs = 30; // 最小帧间隔(约33 FPS)
|
||||||
@ -82,22 +84,27 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
void _startListenTalkData() {
|
void _startListenTalkData() {
|
||||||
state.talkDataRepository.talkDataStream.listen((talkData) {
|
state.talkDataRepository.talkDataStream.listen((talkData) {
|
||||||
final contentType = talkData.contentType;
|
final contentType = talkData.contentType;
|
||||||
|
final currentTimestamp = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|
||||||
|
/// 如果不是通话中的状态不处理对讲数据
|
||||||
|
if (state.startChartTalkStatus.status != TalkStatus.duringCall) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 判断数据类型,进行分发处理
|
// 判断数据类型,进行分发处理
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
case TalkData_ContentTypeE.G711:
|
case TalkData_ContentTypeE.G711:
|
||||||
// state.audioBuffer.add(talkData);
|
if (state.audioBuffer.length < bufferSize) {
|
||||||
if (state.audioBuffer.length < 60) {
|
|
||||||
// 假设缓冲区大小为60帧
|
|
||||||
state.audioBuffer.add(talkData);
|
state.audioBuffer.add(talkData);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TalkData_ContentTypeE.Image:
|
case TalkData_ContentTypeE.Image:
|
||||||
// state.videoBuffer.add(talkData);
|
if (state.videoBuffer.length < bufferSize) {
|
||||||
// 增加视频缓冲区大小
|
|
||||||
if (state.videoBuffer.length < 60) {
|
|
||||||
// 假设缓冲区大小为60帧
|
|
||||||
state.videoBuffer.add(talkData);
|
state.videoBuffer.add(talkData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 更新网络状态
|
||||||
|
updateNetworkStatus(currentTimestamp);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -136,6 +143,7 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
state.listData.value = Uint8List.fromList(talkData.content);
|
state.listData.value = Uint8List.fromList(talkData.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 启动播放
|
||||||
void _startPlayback() {
|
void _startPlayback() {
|
||||||
int frameIntervalMs = 45; // 初始帧间隔设置为45毫秒(约22FPS)
|
int frameIntervalMs = 45; // 初始帧间隔设置为45毫秒(约22FPS)
|
||||||
|
|
||||||
@ -154,7 +162,15 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
// 播放合适的音频帧
|
// 播放合适的音频帧
|
||||||
if (state.audioBuffer.isNotEmpty &&
|
if (state.audioBuffer.isNotEmpty &&
|
||||||
state.audioBuffer.first.durationMs <= elapsedTime) {
|
state.audioBuffer.first.durationMs <= elapsedTime) {
|
||||||
_playAudioData(state.audioBuffer.removeAt(0));
|
// 判断音频开关是否打开
|
||||||
|
if (state.isOpenVoice.value) {
|
||||||
|
_playAudioData(state.audioBuffer.removeAt(0));
|
||||||
|
} else {
|
||||||
|
// 如果不播放音频,只从缓冲区中读取数据,但不移除
|
||||||
|
// 你可以根据需要调整此处逻辑,例如保留缓冲区的最大长度,防止无限增长
|
||||||
|
// 仅移除缓冲区数据但不播放音频,确保音频也是实时更新的
|
||||||
|
state.audioBuffer.removeAt(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 播放合适的视频帧
|
// 播放合适的视频帧
|
||||||
@ -165,10 +181,10 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
if (state.videoBuffer.length > 1) {
|
if (state.videoBuffer.length > 1) {
|
||||||
state.videoBuffer.removeAt(0);
|
state.videoBuffer.removeAt(0);
|
||||||
} else {
|
} else {
|
||||||
// 记录当前时间戳
|
// // 记录当前时间戳
|
||||||
frameTimestamps.add(DateTime.now().millisecondsSinceEpoch);
|
// frameTimestamps.add(DateTime.now().millisecondsSinceEpoch);
|
||||||
// 计算并更新 FPS
|
// // 计算并更新 FPS
|
||||||
_updateFps(frameTimestamps);
|
// _updateFps(frameTimestamps);
|
||||||
_playVideoData(state.videoBuffer.removeAt(0));
|
_playVideoData(state.videoBuffer.removeAt(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,7 +211,15 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
// 播放合适的音频帧
|
// 播放合适的音频帧
|
||||||
if (state.audioBuffer.isNotEmpty &&
|
if (state.audioBuffer.isNotEmpty &&
|
||||||
state.audioBuffer.first.durationMs <= elapsedTime) {
|
state.audioBuffer.first.durationMs <= elapsedTime) {
|
||||||
_playAudioData(state.audioBuffer.removeAt(0));
|
// 判断音频开关是否打开
|
||||||
|
if (state.isOpenVoice.value) {
|
||||||
|
_playAudioData(state.audioBuffer.removeAt(0));
|
||||||
|
} else {
|
||||||
|
// 如果不播放音频,只从缓冲区中读取数据,但不移除
|
||||||
|
// 你可以根据需要调整此处逻辑,例如保留缓冲区的最大长度,防止无限增长
|
||||||
|
// 仅移除缓冲区数据但不播放音频,确保音频也是实时更新的
|
||||||
|
state.audioBuffer.removeAt(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 播放合适的视频帧
|
// 播放合适的视频帧
|
||||||
@ -206,19 +230,47 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
if (state.videoBuffer.length > 1) {
|
if (state.videoBuffer.length > 1) {
|
||||||
state.videoBuffer.removeAt(0);
|
state.videoBuffer.removeAt(0);
|
||||||
} else {
|
} else {
|
||||||
// 记录当前时间戳
|
// // 记录当前时间戳
|
||||||
frameTimestamps.add(DateTime.now().millisecondsSinceEpoch);
|
// frameTimestamps.add(DateTime.now().millisecondsSinceEpoch);
|
||||||
// 计算并更新 FPS
|
// // 计算并更新 FPS
|
||||||
_updateFps(frameTimestamps);
|
// _updateFps(frameTimestamps);
|
||||||
_playVideoData(state.videoBuffer.removeAt(0));
|
_playVideoData(state.videoBuffer.removeAt(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 修改网络状态
|
||||||
|
void updateNetworkStatus(int currentTimestamp) {
|
||||||
|
if (state.lastFrameTimestamp.value != 0) {
|
||||||
|
final frameInterval = currentTimestamp - state.lastFrameTimestamp.value;
|
||||||
|
if (frameInterval > 500 && frameInterval <= 1000) {
|
||||||
|
// 判断帧间隔是否在500毫秒到1秒之间
|
||||||
|
state.networkStatus.value = NetworkStatus.lagging;
|
||||||
|
showNetworkStatus("Network is lagging");
|
||||||
|
} else if (frameInterval > 1000) {
|
||||||
|
// 判断帧间隔是否超过1秒
|
||||||
|
state.networkStatus.value = NetworkStatus.delayed;
|
||||||
|
showNetworkStatus("Network is delayed");
|
||||||
|
} else {
|
||||||
|
state.networkStatus.value = NetworkStatus.normal;
|
||||||
|
state.alertCount.value = 0; // 重置计数器
|
||||||
|
EasyLoading.dismiss(); // 网络恢复正常时关闭提示
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.lastFrameTimestamp.value = currentTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 提示网络状态
|
||||||
|
void showNetworkStatus(String message) {
|
||||||
|
if (state.alertCount.value < 3 && !EasyLoading.isShow) {
|
||||||
|
showToast(message);
|
||||||
|
state.alertCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 停止播放音频
|
/// 停止播放音频
|
||||||
void _stopPlayG711Data() async {
|
void _stopPlayG711Data() async {
|
||||||
print('停止播放');
|
|
||||||
await FlutterPcmSound.pause();
|
await FlutterPcmSound.pause();
|
||||||
await FlutterPcmSound.stop();
|
await FlutterPcmSound.stop();
|
||||||
await FlutterPcmSound.clear();
|
await FlutterPcmSound.clear();
|
||||||
@ -288,4 +340,85 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
// 状态错误,返回页面
|
// 状态错误,返回页面
|
||||||
Get.back();
|
Get.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 更新发送预期数据
|
||||||
|
void updateTalkExpect() {
|
||||||
|
TalkExpectReq talkExpectReq = TalkExpectReq();
|
||||||
|
if (state.isOpenVoice.value) {
|
||||||
|
talkExpectReq = TalkExpectReq(
|
||||||
|
videoType: [VideoTypeE.IMAGE],
|
||||||
|
audioType: [AudioTypeE.G711],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
talkExpectReq = TalkExpectReq(
|
||||||
|
videoType: [VideoTypeE.IMAGE],
|
||||||
|
audioType: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 修改发送预期数据
|
||||||
|
StartChartManage().changeTalkExpectDataType(talkExpect: talkExpectReq);
|
||||||
|
state.isOpenVoice.value = !state.isOpenVoice.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 截图并保存到相册
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 开始录屏
|
||||||
|
Future<void> startRecording() async {
|
||||||
|
getPermissionStatus();
|
||||||
|
bool started =
|
||||||
|
await FlutterScreenRecording.startRecordScreenAndAudio("Recording");
|
||||||
|
if (started) {
|
||||||
|
state.isRecording.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 停止录屏
|
||||||
|
Future<void> stopRecording() async {
|
||||||
|
String path = await FlutterScreenRecording.stopRecordScreen;
|
||||||
|
if (path != null) {
|
||||||
|
state.isRecording.value = false;
|
||||||
|
// 保存录制的视频到相册
|
||||||
|
await GallerySaver.saveVideo(path).then((bool? success) {});
|
||||||
|
showToast('录屏已保存到相册'.tr);
|
||||||
|
} else {
|
||||||
|
state.isRecording.value = false;
|
||||||
|
print("Recording failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_screen_recording/flutter_screen_recording.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:gallery_saver/gallery_saver.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:star_lock/app_settings/app_settings.dart';
|
import 'package:star_lock/app_settings/app_settings.dart';
|
||||||
import 'package:star_lock/main/lockDetail/realTimePicture/realTimePicture_state.dart';
|
import 'package:star_lock/main/lockDetail/realTimePicture/realTimePicture_state.dart';
|
||||||
import 'package:star_lock/talk/call/callTalk.dart';
|
import 'package:star_lock/talk/call/callTalk.dart';
|
||||||
@ -43,7 +49,6 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
state.animationController.repeat();
|
state.animationController.repeat();
|
||||||
//动画开始、结束、向前移动或向后移动时会调用StatusListener
|
//动画开始、结束、向前移动或向后移动时会调用StatusListener
|
||||||
state.animationController.addStatusListener((AnimationStatus status) {
|
state.animationController.addStatusListener((AnimationStatus status) {
|
||||||
// AppLog.log("AnimationStatus:$status");
|
|
||||||
if (status == AnimationStatus.completed) {
|
if (status == AnimationStatus.completed) {
|
||||||
state.animationController.reset();
|
state.animationController.reset();
|
||||||
state.animationController.forward();
|
state.animationController.forward();
|
||||||
@ -70,23 +75,29 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
height: ScreenUtil().screenHeight,
|
height: ScreenUtil().screenHeight,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
)
|
)
|
||||||
: Image.memory(
|
: PopScope(
|
||||||
state.listData.value,
|
canPop: false,
|
||||||
gaplessPlayback: true,
|
child: RepaintBoundary(
|
||||||
width: 1.sw,
|
key: state.globalKey,
|
||||||
height: 1.sh,
|
child: Image.memory(
|
||||||
fit: BoxFit.cover,
|
state.listData.value,
|
||||||
filterQuality: FilterQuality.high,
|
gaplessPlayback: true,
|
||||||
errorBuilder: (
|
width: 1.sw,
|
||||||
BuildContext context,
|
height: 1.sh,
|
||||||
Object error,
|
fit: BoxFit.cover,
|
||||||
StackTrace? stackTrace,
|
filterQuality: FilterQuality.high,
|
||||||
) {
|
errorBuilder: (
|
||||||
return Container(color: Colors.transparent);
|
BuildContext context,
|
||||||
},
|
Object error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
) {
|
||||||
|
return Container(color: Colors.transparent);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Obx(() => state.listData.value.isEmpty
|
Obx(() => state.talkStatus.value == TalkStatus.answeredSuccessfully
|
||||||
? Positioned(
|
? Positioned(
|
||||||
bottom: 300.h,
|
bottom: 300.h,
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -114,17 +125,20 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
// Positioned(
|
||||||
top: 100.h,
|
// top: 100.h,
|
||||||
left: 10.w,
|
// left: 10.w,
|
||||||
child: Obx(
|
// child: Obx(
|
||||||
() => Text(
|
// () => Text(
|
||||||
'FPS:${state.fps.value}',
|
// 'FPS:${state.fps.value}',
|
||||||
style: TextStyle(fontSize: 30.sp, color: Colors.orange,fontWeight: FontWeight.bold),
|
// style: TextStyle(
|
||||||
),
|
// fontSize: 30.sp,
|
||||||
),
|
// color: Colors.orange,
|
||||||
),
|
// fontWeight: FontWeight.bold),
|
||||||
Obx(() => state.listData.value.isEmpty
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
Obx(() => state.talkStatus.value == TalkStatus.answeredSuccessfully
|
||||||
? buildRotationTransition()
|
? buildRotationTransition()
|
||||||
: Container())
|
: Container())
|
||||||
],
|
],
|
||||||
@ -137,7 +151,7 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
// 打开关闭声音
|
// 打开关闭声音
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
state.isOpenVoice.value = !state.isOpenVoice.value;
|
logic.updateTalkExpect();
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 50.w,
|
width: 50.w,
|
||||||
@ -148,16 +162,16 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
height: 40.w,
|
height: 40.w,
|
||||||
image: state.isOpenVoice.value
|
image: state.isOpenVoice.value
|
||||||
? const AssetImage(
|
? const AssetImage(
|
||||||
'images/main/icon_lockDetail_monitoringCloseVoice.png')
|
'images/main/icon_lockDetail_monitoringOpenVoice.png')
|
||||||
: const AssetImage(
|
: const AssetImage(
|
||||||
'images/main/icon_lockDetail_monitoringOpenVoice.png'))),
|
'images/main/icon_lockDetail_monitoringCloseVoice.png'))),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 50.w),
|
SizedBox(width: 50.w),
|
||||||
// 截图
|
// 截图
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
// Get.toNamed(Routers.monitoringRealTimeScreenPage);
|
await logic.captureAndSavePng();
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 50.w,
|
width: 50.w,
|
||||||
@ -173,32 +187,38 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
SizedBox(width: 50.w),
|
SizedBox(width: 50.w),
|
||||||
// 录制
|
// 录制
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
// Get.toNamed(Routers.monitoringRealTimeScreenPage);
|
if (state.isRecording.value) {
|
||||||
|
await logic.stopRecording();
|
||||||
|
} else {
|
||||||
|
await logic.startRecording();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 50.w,
|
width: 50.w,
|
||||||
height: 50.w,
|
height: 50.w,
|
||||||
padding: EdgeInsets.all(5.w),
|
padding: EdgeInsets.all(5.w),
|
||||||
child: Image(
|
child: Image(
|
||||||
width: 40.w,
|
width: 40.w,
|
||||||
height: 40.w,
|
height: 40.w,
|
||||||
fit: BoxFit.fill,
|
fit: BoxFit.fill,
|
||||||
image: const AssetImage(
|
image: const AssetImage(
|
||||||
'images/main/icon_lockDetail_monitoringScreenRecording.png')),
|
'images/main/icon_lockDetail_monitoringScreenRecording.png'),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 50.w),
|
SizedBox(width: 50.w),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
logic.showToast('功能暂未开放'.tr);
|
logic.showToast('功能暂未开放'.tr);
|
||||||
},
|
},
|
||||||
child: Image(
|
child: Image(
|
||||||
width: 28.w,
|
width: 28.w,
|
||||||
height: 28.w,
|
height: 28.w,
|
||||||
fit: BoxFit.fill,
|
fit: BoxFit.fill,
|
||||||
image: const AssetImage(
|
image: const AssetImage('images/main/icon_lockDetail_rectangle.png'),
|
||||||
'images/main/icon_lockDetail_rectangle.png')))
|
),
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +232,10 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
getAnswerBtnImg(),
|
getAnswerBtnImg(),
|
||||||
getAnswerBtnName(),
|
getAnswerBtnName(),
|
||||||
Colors.white,
|
Colors.white,
|
||||||
longPress: () async {},
|
longPress: () async {
|
||||||
|
if (state.talkStatus.value == TalkStatus.answeredSuccessfully ||
|
||||||
|
state.talkStatus.value == TalkStatus.duringCall) {}
|
||||||
|
},
|
||||||
longPressUp: () async {},
|
longPressUp: () async {},
|
||||||
onClick: () async {
|
onClick: () async {
|
||||||
if (state.talkStatus.value == TalkStatus.waitingAnswer) {
|
if (state.talkStatus.value == TalkStatus.waitingAnswer) {
|
||||||
|
|||||||
@ -13,8 +13,15 @@ import 'package:star_lock/talk/startChart/start_chart_talk_status.dart';
|
|||||||
|
|
||||||
import '../../../../tools/storage.dart';
|
import '../../../../tools/storage.dart';
|
||||||
|
|
||||||
|
enum NetworkStatus {
|
||||||
|
normal, // 0
|
||||||
|
lagging, // 1
|
||||||
|
delayed, // 2
|
||||||
|
packetLoss // 3
|
||||||
|
}
|
||||||
|
|
||||||
class TalkViewState {
|
class TalkViewState {
|
||||||
RxBool isOpenVoice = false.obs;
|
|
||||||
int udpSendDataFrameNumber = 0; // 帧序号
|
int udpSendDataFrameNumber = 0; // 帧序号
|
||||||
// var isSenderAudioData = false.obs;// 是否要发送音频数据
|
// var isSenderAudioData = false.obs;// 是否要发送音频数据
|
||||||
|
|
||||||
@ -27,7 +34,7 @@ class TalkViewState {
|
|||||||
|
|
||||||
Rx<Uint8List> listData = Uint8List(0).obs; //得到的视频流字节数据
|
Rx<Uint8List> listData = Uint8List(0).obs; //得到的视频流字节数据
|
||||||
RxList<int> listAudioData = <int>[].obs; //得到的音频流字节数据
|
RxList<int> listAudioData = <int>[].obs; //得到的音频流字节数据
|
||||||
|
GlobalKey globalKey = GlobalKey();
|
||||||
late final VoiceProcessor? voiceProcessor;
|
late final VoiceProcessor? voiceProcessor;
|
||||||
|
|
||||||
late Timer oneMinuteTimeTimer =
|
late Timer oneMinuteTimeTimer =
|
||||||
@ -40,8 +47,6 @@ class TalkViewState {
|
|||||||
late Timer openDoorTimer;
|
late Timer openDoorTimer;
|
||||||
late AnimationController animationController;
|
late AnimationController animationController;
|
||||||
|
|
||||||
RxDouble fps = 0.0.obs; // 添加 FPS 计数
|
|
||||||
|
|
||||||
|
|
||||||
late Timer autoBackTimer =
|
late Timer autoBackTimer =
|
||||||
Timer(const Duration(seconds: 1), () {}); //发送30秒监视后自动返回
|
Timer(const Duration(seconds: 1), () {}); //发送30秒监视后自动返回
|
||||||
@ -50,19 +55,25 @@ class TalkViewState {
|
|||||||
RxInt elapsedSeconds = 0.obs;
|
RxInt elapsedSeconds = 0.obs;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 星图对讲相关状态
|
||||||
List<TalkData> audioBuffer = <TalkData>[].obs;
|
List<TalkData> audioBuffer = <TalkData>[].obs;
|
||||||
List<TalkData> videoBuffer = <TalkData>[].obs;
|
List<TalkData> videoBuffer = <TalkData>[].obs;
|
||||||
|
|
||||||
|
|
||||||
RxBool isPlaying = false.obs; // 是否开始播放
|
RxBool isPlaying = false.obs; // 是否开始播放
|
||||||
|
|
||||||
|
|
||||||
Rx<TalkStatus> talkStatus = TalkStatus.none.obs; //星图对讲状态
|
Rx<TalkStatus> talkStatus = TalkStatus.none.obs; //星图对讲状态
|
||||||
|
|
||||||
// 获取 startChartTalkStatus 的唯一实例
|
// 获取 startChartTalkStatus 的唯一实例
|
||||||
final StartChartTalkStatus startChartTalkStatus =
|
final StartChartTalkStatus startChartTalkStatus =
|
||||||
StartChartTalkStatus.instance;
|
StartChartTalkStatus.instance;
|
||||||
|
|
||||||
// 通话数据流的单例流数据处理类
|
// 通话数据流的单例流数据处理类
|
||||||
final TalkDataRepository talkDataRepository = TalkDataRepository.instance;
|
final TalkDataRepository talkDataRepository = TalkDataRepository.instance;
|
||||||
|
RxInt lastFrameTimestamp = 0.obs; // 上一帧的时间戳,用来判断网络环境
|
||||||
|
Rx<NetworkStatus> networkStatus =
|
||||||
|
NetworkStatus.normal.obs; // 网络状态:0-正常 1-网络卡顿 2-网络延迟 3-网络丢包
|
||||||
|
RxInt alertCount = 0.obs; // 网络状态提示计数器
|
||||||
|
RxInt maxAlertNumber = 3.obs; // 网络状态提示最大提示次数
|
||||||
|
RxBool isOpenVoice = true.obs; // 是否打开声音
|
||||||
|
RxBool isRecording = true.obs; // 是否录屏中
|
||||||
|
RxDouble fps = 0.0.obs; // 添加 FPS 计数
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -256,6 +256,10 @@ dependencies:
|
|||||||
asn1lib: ^1.0.0
|
asn1lib: ^1.0.0
|
||||||
fast_rsa: ^3.6.6
|
fast_rsa: ^3.6.6
|
||||||
protobuf: ^3.1.0
|
protobuf: ^3.1.0
|
||||||
|
#录屏
|
||||||
|
flutter_screen_recording: 2.0.16
|
||||||
|
#图库保存
|
||||||
|
gallery_saver: ^2.3.2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user