fix:调整发送g711音频数据、增加回声消除、增大缓冲区
This commit is contained in:
parent
1fb9a5a188
commit
b876f608e4
@ -1599,33 +1599,8 @@ class _LockDetailPageState extends State<LockDetailPage>
|
||||
}
|
||||
|
||||
Future<void> _handleLockMonitor() async {
|
||||
final lockId = state.keyInfos.value.lockId;
|
||||
final LockSetInfoEntity entity =
|
||||
await ApiRepository.to.getLockSettingInfoData(
|
||||
lockId: lockId.toString(),
|
||||
);
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
final LockSetInfoData data = entity.data!;
|
||||
final mac = data.lockBasicInfo?.mac;
|
||||
if (mac != null && mac.isNotEmpty) {
|
||||
final DeviceNetwork deviceNetworkInfo = await ApiRepository.to
|
||||
.getDeviceNetwork(deviceType: 2, deviceMac: mac);
|
||||
if (deviceNetworkInfo.data?.wifiName == null) {
|
||||
// 未找到配网信息
|
||||
logic.showToast('请先进行配网');
|
||||
return;
|
||||
} else {
|
||||
final peerId = deviceNetworkInfo?.data?.peerId;
|
||||
if (peerId == null || peerId.isEmpty) {
|
||||
throw Exception('设备peerId为空');
|
||||
}
|
||||
// 设置锁的peerID
|
||||
StartChartManage().lockPeerId = peerId;
|
||||
// 发送监控id
|
||||
StartChartManage().startCallRequestMessageTimer(
|
||||
ToPeerId: StartChartManage().lockPeerId ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
// 发送监控id
|
||||
StartChartManage().startCallRequestMessageTimer(
|
||||
ToPeerId: StartChartManage().lockPeerId ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +148,6 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
}
|
||||
state.sureBtnState.value = 1;
|
||||
|
||||
showEasyLoading();
|
||||
showBlueConnetctToastTimer(action: () {
|
||||
dismissEasyLoading();
|
||||
state.sureBtnState.value = 0;
|
||||
@ -198,8 +197,6 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
gatewayConfigurationStr: state.getGatewayConfigurationStr,
|
||||
);
|
||||
} else if (connectionState == BluetoothConnectionState.disconnected) {
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
state.sureBtnState.value = 0;
|
||||
if (state.ifCurrentScreen.value == true) {
|
||||
showBlueConnetctToast();
|
||||
|
||||
@ -3,54 +3,12 @@ import 'dart:math';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class G711 {
|
||||
|
||||
List<int> _aLawTable = [
|
||||
1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
|
||||
];
|
||||
|
||||
Future<List<int>> readAssetFile(String assetPath) async {
|
||||
final ByteData data = await rootBundle.load(assetPath);
|
||||
final List<int> bytes = data.buffer.asUint8List();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
List<int> encodeALaw(List<int> pcmSamples) {
|
||||
final List<int> aLawSamples = [];
|
||||
|
||||
for (final sample in pcmSamples) {
|
||||
// 将 16 位 PCM 样本归一化为 13 位有符号整数
|
||||
int normalizedSample = sample >> 3;
|
||||
|
||||
// 获取样本的符号位
|
||||
int sign = (normalizedSample & 0x8000) != 0 ? 0x80 : 0x00;
|
||||
|
||||
// 取绝对值
|
||||
normalizedSample = normalizedSample.abs();
|
||||
|
||||
// 查找编码表中的段
|
||||
int segment = _aLawTable[normalizedSample >> 8];
|
||||
|
||||
// 计算量化值
|
||||
int quantizedValue = (normalizedSample >> (segment + 3)) & 0x0F;
|
||||
|
||||
// 生成 A-law 编码
|
||||
int aLawSample = sign | (segment << 4) | quantizedValue;
|
||||
|
||||
// 添加到结果列表
|
||||
aLawSamples.add(aLawSample);
|
||||
}
|
||||
|
||||
return aLawSamples;
|
||||
}
|
||||
|
||||
|
||||
int ALawToLinear(int aVal) {
|
||||
// 取反
|
||||
aVal = ~aVal;
|
||||
@ -107,6 +65,18 @@ class G711 {
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<int> removeEcho(List<int> audioData, int delaySamples, double decay) {
|
||||
final List<int> processedData = [];
|
||||
for (int i = 0; i < audioData.length; i++) {
|
||||
int sample = audioData[i];
|
||||
if (i >= delaySamples) {
|
||||
sample -= (audioData[i - delaySamples] * decay).round();
|
||||
}
|
||||
processedData.add(sample);
|
||||
}
|
||||
return processedData;
|
||||
}
|
||||
|
||||
/// 解码并降噪
|
||||
List<int> decodeAndDenoise(List<int> encodedData, bool isALaw, int sampleRate,
|
||||
double cutoffFreq, int threshold) {
|
||||
@ -121,7 +91,10 @@ class G711 {
|
||||
// 噪声门
|
||||
List<int> denoisedData = noiseGate(filteredData, threshold);
|
||||
|
||||
return denoisedData;
|
||||
// 回声消除处理
|
||||
final List<int> processedData = removeEcho(denoisedData, 160, 0.5);
|
||||
|
||||
return processedData;
|
||||
}
|
||||
|
||||
//711解码为pcm数据
|
||||
|
||||
@ -390,7 +390,7 @@ class StartChartManage {
|
||||
// 如果已经处于等待接听状态就不发送
|
||||
if (talkStatus.status != TalkStatus.proactivelyCallWaitingAnswer) {
|
||||
// 停止播放铃声
|
||||
AudioPlayerManager().playRingtone();
|
||||
// AudioPlayerManager().playRingtone();
|
||||
Get.toNamed(
|
||||
Routers.starChartTalkView,
|
||||
);
|
||||
|
||||
@ -31,8 +31,7 @@ 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/views/talkView/talk_view_state.dart';
|
||||
import 'package:star_lock/talk/udp/udp_manage.dart';
|
||||
import 'package:star_lock/talk/udp/udp_senderManage.dart';
|
||||
|
||||
import 'package:star_lock/tools/bugly/bugly_tool.dart';
|
||||
import 'package:star_lock/tools/storage.dart';
|
||||
|
||||
@ -44,8 +43,9 @@ class TalkViewLogic extends BaseGetXController {
|
||||
Timer? _syncTimer; // 音视频播放刷新率定时器
|
||||
int _startTime = 0; // 开始播放时间戳,用于判断帧数据中的时间戳位置
|
||||
int bufferSize = 20; // 缓冲区大小(以帧为单位)
|
||||
int audioBufferSize = 640; // 缓冲区大小(以帧为单位)
|
||||
final List<int> frameTimestamps = []; // 帧时间戳用于计算 FPS
|
||||
int frameIntervalMs = 45; // 初始帧间隔设置为45毫秒(约22FPS)
|
||||
int frameIntervalMs = 35; // 初始帧间隔设置为45毫秒(约22FPS)
|
||||
int minFrameIntervalMs = 30; // 最小帧间隔(约33 FPS)
|
||||
int maxFrameIntervalMs = 100; // 最大帧间隔(约1 FPS)
|
||||
// int maxFrameIntervalMs = 100; // 最大帧间隔(约10 FPS)
|
||||
@ -53,11 +53,11 @@ class TalkViewLogic extends BaseGetXController {
|
||||
/// 初始化音频播放器
|
||||
void _initFlutterPcmSound() {
|
||||
const int sampleRate = 8000;
|
||||
FlutterPcmSound.setLogLevel(LogLevel.none);
|
||||
FlutterPcmSound.setLogLevel(LogLevel.verbose);
|
||||
FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 1);
|
||||
// 设置 feed 阈值
|
||||
if (Platform.isAndroid) {
|
||||
FlutterPcmSound.setFeedThreshold(-1); // Android 平台的特殊处理
|
||||
FlutterPcmSound.setFeedThreshold(sampleRate ~/ 2); // Android 平台的特殊处理
|
||||
} else {
|
||||
FlutterPcmSound.setFeedThreshold(sampleRate ~/ 32); // 非 Android 平台的处理
|
||||
}
|
||||
@ -88,10 +88,14 @@ class TalkViewLogic extends BaseGetXController {
|
||||
// 判断数据类型,进行分发处理
|
||||
switch (contentType) {
|
||||
case TalkData_ContentTypeE.G711:
|
||||
if (state.audioBuffer.length >= bufferSize) {
|
||||
if (state.audioBuffer.length >= audioBufferSize) {
|
||||
state.audioBuffer.removeAt(0); // 丢弃最旧的数据
|
||||
// readAudioBufferSize.removeAt(0); // 丢弃最旧的数据
|
||||
}
|
||||
state.audioBuffer.add(talkData); // 添加新数据
|
||||
|
||||
// readAudioBufferSize.add(talkData.content);
|
||||
|
||||
break;
|
||||
case TalkData_ContentTypeE.Image:
|
||||
if (state.videoBuffer.length >= bufferSize) {
|
||||
@ -128,7 +132,6 @@ class TalkViewLogic extends BaseGetXController {
|
||||
void _playAudioData(TalkData talkData) async {
|
||||
// final list = G711().convertList(talkData.content);
|
||||
final list = G711().decodeAndDenoise(talkData.content, true, 8000, 300, 50);
|
||||
//
|
||||
// // 将 PCM 数据转换为 PcmArrayInt16
|
||||
final PcmArrayInt16 fromList = PcmArrayInt16.fromList(list);
|
||||
FlutterPcmSound.feed(fromList);
|
||||
@ -145,44 +148,14 @@ class TalkViewLogic extends BaseGetXController {
|
||||
|
||||
/// 启动播放
|
||||
void _startPlayback() {
|
||||
int frameIntervalMs = 45; // 初始帧间隔设置为45毫秒(约22FPS)
|
||||
|
||||
Future.delayed(Duration(milliseconds: 800), () {
|
||||
_startTime = DateTime.now().millisecondsSinceEpoch;
|
||||
_syncTimer ??=
|
||||
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
|
||||
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||
final elapsedTime = currentTime - _startTime;
|
||||
|
||||
// 动态调整帧间隔
|
||||
_adjustFrameInterval();
|
||||
|
||||
// 播放合适的音频帧
|
||||
if (state.audioBuffer.isNotEmpty &&
|
||||
state.audioBuffer.first.durationMs <= elapsedTime) {
|
||||
// 判断音频开关是否打开
|
||||
if (state.isOpenVoice.value) {
|
||||
AppLog.log('播放音频:${state.audioBuffer[0]}');
|
||||
_playAudioData(state.audioBuffer.removeAt(0));
|
||||
} else {
|
||||
// 如果不播放音频,只从缓冲区中读取数据,但不移除
|
||||
// 你可以根据需要调整此处逻辑,例如保留缓冲区的最大长度,防止无限增长
|
||||
// 仅移除缓冲区数据但不播放音频,确保音频也是实时更新的
|
||||
state.audioBuffer.removeAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 播放合适的视频帧
|
||||
// 跳帧策略:如果缓冲区中有多个帧,且它们的时间戳都在当前时间之前,则播放最新的帧
|
||||
while (state.videoBuffer.isNotEmpty &&
|
||||
state.videoBuffer.first.durationMs <= elapsedTime) {
|
||||
// 如果有多个帧,移除旧的帧,保持最新的帧
|
||||
if (state.videoBuffer.length > 1) {
|
||||
state.videoBuffer.removeAt(0);
|
||||
} else {
|
||||
_playVideoData(state.videoBuffer.removeAt(0));
|
||||
}
|
||||
}
|
||||
_playFrames();
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -211,42 +184,46 @@ class TalkViewLogic extends BaseGetXController {
|
||||
_syncTimer?.cancel();
|
||||
_syncTimer =
|
||||
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
|
||||
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||
final elapsedTime = currentTime - _startTime;
|
||||
|
||||
// 播放合适的音频帧
|
||||
if (state.audioBuffer.isNotEmpty &&
|
||||
state.audioBuffer.first.durationMs <= elapsedTime) {
|
||||
// 判断音频开关是否打开
|
||||
if (state.isOpenVoice.value) {
|
||||
_playAudioData(state.audioBuffer.removeAt(0));
|
||||
} else {
|
||||
// 如果不播放音频,只从缓冲区中读取数据,但不移除
|
||||
// 你可以根据需要调整此处逻辑,例如保留缓冲区的最大长度,防止无限增长
|
||||
// 仅移除缓冲区数据但不播放音频,确保音频也是实时更新的
|
||||
state.audioBuffer.removeAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 播放合适的视频帧
|
||||
// 跳帧策略:如果缓冲区中有多个帧,且它们的时间戳都在当前时间之前,则播放最新的帧
|
||||
int maxFramesToProcess = 5; // 每次最多处理 5 帧
|
||||
int processedFrames = 0;
|
||||
|
||||
while (state.videoBuffer.isNotEmpty &&
|
||||
state.videoBuffer.first.durationMs <= elapsedTime &&
|
||||
processedFrames < maxFramesToProcess) {
|
||||
if (state.videoBuffer.length > 1) {
|
||||
state.videoBuffer.removeAt(0);
|
||||
} else {
|
||||
_playVideoData(state.videoBuffer.removeAt(0));
|
||||
}
|
||||
processedFrames++;
|
||||
}
|
||||
_playFrames();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _playFrames() {
|
||||
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||
final elapsedTime = currentTime - _startTime;
|
||||
|
||||
// 播放合适的音频帧
|
||||
if (state.audioBuffer.isNotEmpty &&
|
||||
state.audioBuffer.first.durationMs <= elapsedTime) {
|
||||
// 判断音频开关是否打开
|
||||
if (state.isOpenVoice.value) {
|
||||
_playAudioData(state.audioBuffer.removeAt(0));
|
||||
} else {
|
||||
// 如果不播放音频,只从缓冲区中读取数据,但不移除
|
||||
// 你可以根据需要调整此处逻辑,例如保留缓冲区的最大长度,防止无限增长
|
||||
// 仅移除缓冲区数据但不播放音频,确保音频也是实时更新的
|
||||
state.audioBuffer.removeAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 播放合适的视频帧
|
||||
// 跳帧策略:如果缓冲区中有多个帧,且它们的时间戳都在当前时间之前,则播放最新的帧
|
||||
int maxFramesToProcess = 5; // 每次最多处理 5 帧
|
||||
int processedFrames = 0;
|
||||
|
||||
while (state.videoBuffer.isNotEmpty &&
|
||||
state.videoBuffer.first.durationMs <= elapsedTime &&
|
||||
processedFrames < maxFramesToProcess) {
|
||||
if (state.videoBuffer.length > 1) {
|
||||
state.videoBuffer.removeAt(0);
|
||||
} else {
|
||||
_playVideoData(state.videoBuffer.removeAt(0));
|
||||
}
|
||||
processedFrames++;
|
||||
}
|
||||
}
|
||||
|
||||
/// 修改网络状态
|
||||
void updateNetworkStatus(int currentTimestamp) {
|
||||
if (state.lastFrameTimestamp.value != 0) {
|
||||
@ -474,8 +451,6 @@ class TalkViewLogic extends BaseGetXController {
|
||||
// 停止播放音频
|
||||
_stopPlayG711Data();
|
||||
stopProcessingAudio();
|
||||
// 状态错误,返回页面
|
||||
Get.back();
|
||||
}
|
||||
|
||||
/// 更新发送预期数据
|
||||
|
||||
@ -62,143 +62,150 @@ class _TalkViewPageState extends State<TalkViewPage>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 1.sw,
|
||||
height: 1.sh,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
Obx(
|
||||
() {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final screenHeight = MediaQuery.of(context).size.height;
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
// 返回 false 表示禁止退出
|
||||
return false;
|
||||
},
|
||||
child: SizedBox(
|
||||
width: 1.sw,
|
||||
height: 1.sh,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
Obx(
|
||||
() {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final screenHeight = MediaQuery.of(context).size.height;
|
||||
|
||||
final logicalWidth = MediaQuery.of(context).size.width;
|
||||
final logicalHeight = MediaQuery.of(context).size.height;
|
||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
final logicalWidth = MediaQuery.of(context).size.width;
|
||||
final logicalHeight = MediaQuery.of(context).size.height;
|
||||
final devicePixelRatio =
|
||||
MediaQuery.of(context).devicePixelRatio;
|
||||
|
||||
// 计算物理像素值
|
||||
final physicalWidth = logicalWidth * devicePixelRatio;
|
||||
final physicalHeight = logicalHeight * devicePixelRatio;
|
||||
// 计算物理像素值
|
||||
final physicalWidth = logicalWidth * devicePixelRatio;
|
||||
final physicalHeight = logicalHeight * devicePixelRatio;
|
||||
|
||||
// 旋转后的图片尺寸
|
||||
final rotatedImageWidth = 480; // 原始高度
|
||||
final rotatedImageHeight = 864; // 原始宽度
|
||||
// 旋转后的图片尺寸
|
||||
final rotatedImageWidth = 480; // 原始高度
|
||||
final rotatedImageHeight = 864; // 原始宽度
|
||||
|
||||
// 计算缩放比例
|
||||
final scaleWidth = physicalWidth / rotatedImageWidth;
|
||||
final scaleHeight = physicalHeight / rotatedImageHeight;
|
||||
final scale = max(scaleWidth, scaleHeight); // 选择较大的缩放比例
|
||||
// 计算缩放比例
|
||||
final scaleWidth = physicalWidth / rotatedImageWidth;
|
||||
final scaleHeight = physicalHeight / rotatedImageHeight;
|
||||
final scale = max(scaleWidth, scaleHeight); // 选择较大的缩放比例
|
||||
|
||||
return state.listData.value.isEmpty
|
||||
? Image.asset(
|
||||
'images/main/monitorBg.png',
|
||||
width: screenWidth,
|
||||
height: screenHeight,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: PopScope(
|
||||
canPop: false,
|
||||
child: RepaintBoundary(
|
||||
key: state.globalKey,
|
||||
child: Transform.rotate(
|
||||
angle:
|
||||
state.rotateAngle.value * (pi / 180), // 旋转 90 度
|
||||
child: Transform.scale(
|
||||
scale: scale, // 动态计算的缩放比例
|
||||
child: Image.memory(
|
||||
state.listData.value,
|
||||
gaplessPlayback: true,
|
||||
fit: BoxFit.cover,
|
||||
filterQuality: FilterQuality.high,
|
||||
errorBuilder: (
|
||||
BuildContext context,
|
||||
Object error,
|
||||
StackTrace? stackTrace,
|
||||
) {
|
||||
return Container(color: Colors.transparent);
|
||||
},
|
||||
return state.listData.value.isEmpty
|
||||
? Image.asset(
|
||||
'images/main/monitorBg.png',
|
||||
width: screenWidth,
|
||||
height: screenHeight,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: PopScope(
|
||||
canPop: false,
|
||||
child: RepaintBoundary(
|
||||
key: state.globalKey,
|
||||
child: Transform.rotate(
|
||||
angle:
|
||||
state.rotateAngle.value * (pi / 180), // 旋转 90 度
|
||||
child: Transform.scale(
|
||||
scale: scale, // 动态计算的缩放比例
|
||||
child: Image.memory(
|
||||
state.listData.value,
|
||||
gaplessPlayback: true,
|
||||
fit: BoxFit.cover,
|
||||
filterQuality: FilterQuality.high,
|
||||
errorBuilder: (
|
||||
BuildContext context,
|
||||
Object error,
|
||||
StackTrace? stackTrace,
|
||||
) {
|
||||
return Container(color: Colors.transparent);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Obx(() => state.listData.value.isEmpty
|
||||
? Positioned(
|
||||
bottom: 300.h,
|
||||
child: Text(
|
||||
'正在创建安全连接...'.tr,
|
||||
style: TextStyle(color: Colors.black, fontSize: 26.sp),
|
||||
))
|
||||
: Container()),
|
||||
Positioned(
|
||||
bottom: 10.w,
|
||||
child: Container(
|
||||
width: 1.sw - 30.w * 2,
|
||||
// height: 300.h,
|
||||
margin: EdgeInsets.all(30.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(20.h)),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
SizedBox(height: 20.h),
|
||||
bottomTopBtnWidget(),
|
||||
SizedBox(height: 20.h),
|
||||
bottomBottomBtnWidget(),
|
||||
SizedBox(height: 20.h),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Obx(() => state.listData.value.isEmpty
|
||||
? Positioned(
|
||||
bottom: 300.h,
|
||||
child: Text(
|
||||
'正在创建安全连接...'.tr,
|
||||
style: TextStyle(color: Colors.black, fontSize: 26.sp),
|
||||
))
|
||||
: Container()),
|
||||
Positioned(
|
||||
bottom: 10.w,
|
||||
child: Container(
|
||||
width: 1.sw - 30.w * 2,
|
||||
// height: 300.h,
|
||||
margin: EdgeInsets.all(30.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(20.h)),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
SizedBox(height: 20.h),
|
||||
bottomTopBtnWidget(),
|
||||
SizedBox(height: 20.h),
|
||||
bottomBottomBtnWidget(),
|
||||
SizedBox(height: 20.h),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Positioned(
|
||||
// top: 100.h,
|
||||
// left: 10.w,
|
||||
// child: Obx(
|
||||
// () => Text(
|
||||
// 'FPS:${state.fps.value}',
|
||||
// style: TextStyle(
|
||||
// fontSize: 30.sp,
|
||||
// color: Colors.orange,
|
||||
// fontWeight: FontWeight.bold),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
Obx(() => state.listData.value.isEmpty
|
||||
? buildRotationTransition()
|
||||
: Container()),
|
||||
// Positioned(
|
||||
// top: 100.h,
|
||||
// left: 10.w,
|
||||
// child: Obx(
|
||||
// () => Text(
|
||||
// 'FPS:${state.fps.value}',
|
||||
// style: TextStyle(
|
||||
// fontSize: 30.sp,
|
||||
// color: Colors.orange,
|
||||
// fontWeight: FontWeight.bold),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
Obx(() => state.listData.value.isEmpty
|
||||
? buildRotationTransition()
|
||||
: Container()),
|
||||
|
||||
Obx(() => state.isLongPressing.value
|
||||
? Positioned(
|
||||
top: 80.h,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(10.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.7),
|
||||
borderRadius: BorderRadius.circular(10.w),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.mic, color: Colors.white, size: 24.w),
|
||||
SizedBox(width: 10.w),
|
||||
Text(
|
||||
'正在说话...',
|
||||
style:
|
||||
TextStyle(fontSize: 20.sp, color: Colors.white),
|
||||
),
|
||||
],
|
||||
Obx(() => state.isLongPressing.value
|
||||
? Positioned(
|
||||
top: 80.h,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(10.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.7),
|
||||
borderRadius: BorderRadius.circular(10.w),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.mic, color: Colors.white, size: 24.w),
|
||||
SizedBox(width: 10.w),
|
||||
Text(
|
||||
'正在说话...',
|
||||
style: TextStyle(
|
||||
fontSize: 20.sp, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container()),
|
||||
],
|
||||
)
|
||||
: Container()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -87,4 +87,6 @@ class TalkViewState {
|
||||
List<List<int>> recordingAudioAllFrames = <List<int>>[]; // 录制音频的所有帧
|
||||
RxInt rotateAngle = 0.obs; // 旋转角度(以弧度为单位)
|
||||
RxBool isLongPressing = false.obs; // 旋转角度(以弧度为单位)
|
||||
RxBool hasAudioData = false.obs; // 是否有音频数据
|
||||
RxInt lastAudioTimestamp = 0.obs; // 最后接收到的音频数据的时间戳
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user