Browse Source

修复语音

ios
Jolie 3 months ago
parent
commit
35a2af642d
3 changed files with 249 additions and 75 deletions
  1. 88
      lib/controller/message/voice_player_manager.dart
  2. 43
      lib/widget/message/voice_input_view.dart
  3. 193
      lib/widget/message/voice_item.dart

88
lib/controller/message/voice_player_manager.dart

@ -1,5 +1,4 @@
import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers/audioplayers.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
/// ///
@ -32,43 +31,98 @@ class VoicePlayerManager extends GetxController {
/// [filePath] /// [filePath]
Future<void> play(String audioId, String filePath) async { Future<void> play(String audioId, String filePath) async {
try { try {
print('🎵 [VoicePlayerManager] 准备播放音频: audioId=$audioId, filePath=$filePath');
// /
if (_currentPlayingId == audioId) {
print('⏯️ [VoicePlayerManager] 切换播放/暂停状态');
try {
if (playerState.value == PlayerState.playing) {
await _audioPlayer.pause();
} else {
await _audioPlayer.resume();
}
} catch (e) {
print('⚠️ [VoicePlayerManager] 暂停/恢复失败,尝试重新播放: $e');
// /
}
// /
if (_currentPlayingId == audioId &&
(playerState.value == PlayerState.playing ||
playerState.value == PlayerState.paused)) {
return;
}
}
// //
if (_currentPlayingId != null && _currentPlayingId != audioId) { if (_currentPlayingId != null && _currentPlayingId != audioId) {
await stop();
print('🛑 [VoicePlayerManager] 停止当前播放的音频: $_currentPlayingId');
try {
// stopped stop
if (playerState.value != PlayerState.stopped) {
await _audioPlayer.stop();
}
} catch (e) {
print('⚠️ [VoicePlayerManager] 停止播放器时出错(可忽略): $e');
}
// ID
_currentPlayingId = null;
currentPlayingId.value = null;
//
await Future.delayed(Duration(milliseconds: 200));
} }
// /
if (_currentPlayingId == audioId) {
if (playerState.value == PlayerState.playing) {
await _audioPlayer.pause();
} else {
await _audioPlayer.resume();
//
print('🔄 [VoicePlayerManager] 准备播放新音频,当前状态: ${playerState.value}');
if (playerState.value == PlayerState.playing || playerState.value == PlayerState.paused) {
try {
await _audioPlayer.stop();
await Future.delayed(Duration(milliseconds: 150));
} catch (e) {
// stop
print('⚠️ [VoicePlayerManager] 停止播放器时出错(可忽略): $e');
} }
return;
} }
//
// ID
_currentPlayingId = audioId; _currentPlayingId = audioId;
currentPlayingId.value = audioId; currentPlayingId.value = audioId;
if(filePath.startsWith('https://')){
//
if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
print('🌐 [VoicePlayerManager] 使用网络URL播放: $filePath');
await _audioPlayer.play(UrlSource(filePath)); await _audioPlayer.play(UrlSource(filePath));
}else{
} else {
print('📁 [VoicePlayerManager] 使用本地文件播放: $filePath');
await _audioPlayer.play(DeviceFileSource(filePath)); await _audioPlayer.play(DeviceFileSource(filePath));
} }
} catch (e) {
print('播放音频失败: $e');
print('✅ [VoicePlayerManager] 音频播放请求已发送');
} catch (e, stackTrace) {
print('❌ [VoicePlayerManager] 播放音频失败: $e');
print('📚 [VoicePlayerManager] 堆栈跟踪: $stackTrace');
//
_currentPlayingId = null; _currentPlayingId = null;
currentPlayingId.value = null; currentPlayingId.value = null;
//
rethrow;
} }
} }
/// ///
Future<void> stop() async { Future<void> stop() async {
try { try {
await _audioPlayer.stop();
// stopped stop
if (playerState.value != PlayerState.stopped) {
await _audioPlayer.stop();
}
_currentPlayingId = null; _currentPlayingId = null;
currentPlayingId.value = null; currentPlayingId.value = null;
} catch (e) { } catch (e) {
print('停止播放失败: $e');
print('⚠️ [VoicePlayerManager] 停止播放失败(可忽略): $e');
// 使
_currentPlayingId = null;
currentPlayingId.value = null;
} }
} }

43
lib/widget/message/voice_input_view.dart

@ -30,7 +30,18 @@ class _VoiceInputViewState extends State<VoiceInputView> {
@override @override
void dispose() { void dispose() {
//
_timer?.cancel(); _timer?.cancel();
_timer = null;
//
if (_isRecording) {
_audioRecorder.stop().catchError((e) {
//
print('停止录音时出错(可忽略): $e');
return null; // null catchError
});
}
//
_audioRecorder.dispose(); _audioRecorder.dispose();
super.dispose(); super.dispose();
} }
@ -59,6 +70,8 @@ class _VoiceInputViewState extends State<VoiceInputView> {
await _audioRecorder.start(const RecordConfig(), path: path); await _audioRecorder.start(const RecordConfig(), path: path);
// mounted UI
if (!mounted) return;
setState(() { setState(() {
_isRecording = true; _isRecording = true;
_seconds = 0; _seconds = 0;
@ -69,6 +82,11 @@ class _VoiceInputViewState extends State<VoiceInputView> {
// //
_timer = Timer.periodic(const Duration(seconds: 1), (timer) { _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
// mounted dispose setState
if (!mounted) {
timer.cancel();
return;
}
setState(() { setState(() {
_seconds++; _seconds++;
}); });
@ -91,19 +109,26 @@ class _VoiceInputViewState extends State<VoiceInputView> {
final finalPath = _recordingPath; final finalPath = _recordingPath;
await _audioRecorder.stop(); await _audioRecorder.stop();
setState(() {
_isRecording = false;
_seconds = 0;
_isCanceling = false;
_panStartPosition = Offset.zero;
_recordingPath = null;
});
// mounted UI
if (mounted) {
setState(() {
_isRecording = false;
_seconds = 0;
_isCanceling = false;
_panStartPosition = Offset.zero;
_recordingPath = null;
});
}
// //
if (!cancel && finalPath != null && finalSeconds > 0) { if (!cancel && finalPath != null && finalSeconds > 0) {
// //
Future.microtask(() { Future.microtask(() {
widget.onVoiceRecorded?.call(finalPath, finalSeconds);
// mounted dispose
if (mounted) {
widget.onVoiceRecorded?.call(finalPath, finalSeconds);
}
}); });
} }
} catch (e) { } catch (e) {
@ -326,7 +351,7 @@ class _VoiceInputViewState extends State<VoiceInputView> {
// 60 // 60
final shouldCancel = deltaY > 60; final shouldCancel = deltaY > 60;
if (_isCanceling != shouldCancel) {
if (_isCanceling != shouldCancel && mounted) {
setState(() { setState(() {
_isCanceling = shouldCancel; _isCanceling = shouldCancel;
}); });

193
lib/widget/message/voice_item.dart

@ -108,40 +108,95 @@ class _VoiceItemState extends State<VoiceItem> with TickerProviderStateMixin {
print('🔍 语音消息路径检查: localPath=$localPath, remotePath=$remotePath'); print('🔍 语音消息路径检查: localPath=$localPath, remotePath=$remotePath');
// 0
//
if (!widget.isSentByMe && widget.message != null) { if (!widget.isSentByMe && widget.message != null) {
final localFile = localPath.isNotEmpty ? File(localPath) : null;
bool needDownload = false; bool needDownload = false;
bool fileValid = false;
if (localPath.isEmpty || localFile == null || !await localFile.exists()) {
needDownload = true;
print('📥 本地文件不存在,需要下载');
} else {
final fileSize = await localFile.length();
if (fileSize == 0) {
//
if (localPath.isNotEmpty) {
final localFile = File(localPath);
if (await localFile.exists()) {
final fileSize = await localFile.length();
// 01KB
if (fileSize > 1024) {
fileValid = true;
print('✅ 本地文件存在且有效: $localPath, 大小: $fileSize bytes');
} else {
print('⚠️ 本地文件大小异常: $localPath, 大小: $fileSize bytes');
needDownload = true;
}
} else {
print('📥 本地文件不存在: $localPath');
needDownload = true; needDownload = true;
print('📥 本地文件大小为0,需要下载');
} }
} else {
print('📥 本地路径为空,需要下载');
needDownload = true;
} }
// //
if (needDownload) { if (needDownload) {
if (remotePath == null || remotePath.isEmpty) {
SmartDialog.showToast('无法获取语音文件');
print('❌ 远程路径为空,无法下载');
return;
}
try { try {
print('📥 开始下载语音文件...');
print('📥 开始下载语音文件,远程路径: $remotePath');
SmartDialog.showToast('正在下载语音...');
//
await EMClient.getInstance.chatManager await EMClient.getInstance.chatManager
.downloadAttachment(widget.message!); .downloadAttachment(widget.message!);
// //
await Future.delayed(Duration(milliseconds: 300));
await Future.delayed(Duration(milliseconds: 500));
// body // body
// voiceBody
if (widget.message!.body is EMVoiceMessageBody) {
final updatedVoiceBody = widget.message!.body as EMVoiceMessageBody;
localPath = updatedVoiceBody.localPath;
print('✅ 语音文件下载完成,新路径: $localPath');
} else {
print('⚠️ 消息 body 类型不是 EMVoiceMessageBody');
// SDK可能需要一些时间来更新路径
String? downloadedPath;
for (int i = 0; i < 3; i++) {
if (widget.message!.body is EMVoiceMessageBody) {
final updatedVoiceBody = widget.message!.body as EMVoiceMessageBody;
final newPath = updatedVoiceBody.localPath;
if (newPath.isNotEmpty) {
downloadedPath = newPath;
final downloadedFile = File(newPath);
if (await downloadedFile.exists()) {
final fileSize = await downloadedFile.length();
if (fileSize > 1024) {
print('✅ 语音文件下载完成,新路径: $newPath, 大小: $fileSize bytes');
localPath = newPath; //
fileValid = true;
break; // 退
} else {
print('⚠️ 下载的文件大小异常: $newPath, 大小: $fileSize bytes');
}
} else {
print('⚠️ 下载后文件不存在: $newPath');
}
}
}
//
if (!fileValid && i < 2) {
await Future.delayed(Duration(milliseconds: 300));
}
}
//
if (!fileValid) {
if (downloadedPath == null || downloadedPath.isEmpty) {
print('❌ 下载后本地路径为空');
SmartDialog.showToast('下载失败,无法获取文件路径');
} else {
print('❌ 下载的文件无效: $downloadedPath');
SmartDialog.showToast('下载的文件无效,请重试');
}
return;
} }
// //
@ -152,75 +207,115 @@ class _VoiceItemState extends State<VoiceItem> with TickerProviderStateMixin {
return; return;
} }
} }
//
if (!fileValid) {
print('❌ 文件验证失败,无法播放');
SmartDialog.showToast('语音文件无效,请重试');
return;
}
} }
// 使
// 使
// 使URL
// localPath
if (localPath.isNotEmpty) { if (localPath.isNotEmpty) {
print('🔍 检查本地文件: $localPath');
final localFile = File(localPath); final localFile = File(localPath);
if (await localFile.exists()) { if (await localFile.exists()) {
// //
final fileSize = await localFile.length(); final fileSize = await localFile.length();
if (fileSize > 0) {
print('📊 本地文件大小: $fileSize bytes');
if (fileSize > 1024) { // 1KB
filePath = localPath; filePath = localPath;
print('✅ 使用本地音频文件: $localPath, 文件大小: $fileSize bytes'); print('✅ 使用本地音频文件: $localPath, 文件大小: $fileSize bytes');
} else { } else {
print('⚠️ 本地音频文件大小为0: $localPath');
// 0使
if (remotePath != null && remotePath.isNotEmpty) {
if (remotePath.startsWith('http://') ||
remotePath.startsWith('https://')) {
filePath = remotePath;
print('✅ 使用远程音频文件: $remotePath');
print('⚠️ 本地音频文件大小异常: $localPath, 大小: $fileSize bytes');
// 退URL
if (widget.isSentByMe) {
// 使
if (remotePath != null && remotePath.isNotEmpty) {
if (remotePath.startsWith('http://') ||
remotePath.startsWith('https://')) {
filePath = remotePath;
print('✅ 使用远程音频文件: $remotePath');
} else {
SmartDialog.showToast('音频文件无效');
return;
}
} else { } else {
SmartDialog.showToast('音频文件无效'); SmartDialog.showToast('音频文件无效');
print('⚠️ 本地文件大小为0,远程路径不是URL: $remotePath');
return; return;
} }
} else { } else {
SmartDialog.showToast('音频文件无效');
print('⚠️ 本地文件大小为0,且没有远程路径');
//
SmartDialog.showToast('语音文件无效,请重试');
return; return;
} }
} }
} else { } else {
print('⚠️ 本地音频文件不存在: $localPath'); print('⚠️ 本地音频文件不存在: $localPath');
// 使
if (remotePath != null && remotePath.isNotEmpty) {
if (remotePath.startsWith('http://') ||
remotePath.startsWith('https://')) {
filePath = remotePath;
print('✅ 使用远程音频文件: $remotePath');
//
if (widget.isSentByMe) {
// 使
if (remotePath != null && remotePath.isNotEmpty) {
if (remotePath.startsWith('http://') ||
remotePath.startsWith('https://')) {
filePath = remotePath;
print('✅ 使用远程音频文件: $remotePath');
} else {
SmartDialog.showToast('音频文件不存在');
return;
}
} else { } else {
SmartDialog.showToast('音频文件不存在'); SmartDialog.showToast('音频文件不存在');
print('⚠️ 本地文件不存在,远程路径不是URL: $remotePath');
return; return;
} }
} else { } else {
SmartDialog.showToast('音频文件不存在');
print('⚠️ 本地和远程路径都无效');
//
SmartDialog.showToast('语音文件不存在,请重试');
return; return;
} }
} }
} else if (remotePath != null && remotePath.isNotEmpty) {
// 使
} else if (widget.isSentByMe && remotePath != null && remotePath.isNotEmpty) {
// 使URL
if (remotePath.startsWith('http://') || if (remotePath.startsWith('http://') ||
remotePath.startsWith('https://')) { remotePath.startsWith('https://')) {
// HTTP URL
filePath = remotePath; filePath = remotePath;
print('✅ 使用远程音频文件(无本地路径): $remotePath');
print('✅ 使用远程音频文件(发送的消息): $remotePath');
} else { } else {
SmartDialog.showToast('音频文件不存在'); SmartDialog.showToast('音频文件不存在');
print('⚠️ 远程路径不是URL: $remotePath');
return; return;
} }
} else {
SmartDialog.showToast('无法获取音频文件');
print('❌ 本地和远程路径都无效');
return;
} }
if (filePath != null && filePath.isNotEmpty) {
print('🎵 开始播放音频: $filePath');
// filePath
print('🎵 开始播放音频: $filePath');
try {
await _playerManager.play(widget.messageId, filePath); await _playerManager.play(widget.messageId, filePath);
} else {
SmartDialog.showToast('无法获取音频文件');
print('❌ 音频文件路径为空');
} catch (e) {
print('❌ 播放音频失败: $e');
// 使URL
// 退URL
if (widget.isSentByMe && !filePath.startsWith('http://') && !filePath.startsWith('https://')) {
final remotePath = widget.voiceBody.remotePath;
if (remotePath != null && remotePath.isNotEmpty &&
(remotePath.startsWith('http://') || remotePath.startsWith('https://'))) {
print('🔄 本地文件播放失败,尝试使用远程URL: $remotePath');
try {
await _playerManager.play(widget.messageId, remotePath);
SmartDialog.showToast('正在播放语音');
return;
} catch (e2) {
print('❌ 远程URL播放也失败: $e2');
}
}
}
SmartDialog.showToast('播放失败,请重试');
} }
} catch (e) { } catch (e) {
SmartDialog.showToast('播放失败: $e'); SmartDialog.showToast('播放失败: $e');

Loading…
Cancel
Save