|
|
@ -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(); |
|
|
|
|
|
// 文件大小应该大于0,且至少应该大于1KB(语音文件通常不会太小) |
|
|
|
|
|
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'); |
|
|
|