diff --git a/app/src/main/java/com/xiaoqu/watch/ui/task/TaskListFragment.kt b/app/src/main/java/com/xiaoqu/watch/ui/task/TaskListFragment.kt index 6681f66..294d689 100644 --- a/app/src/main/java/com/xiaoqu/watch/ui/task/TaskListFragment.kt +++ b/app/src/main/java/com/xiaoqu/watch/ui/task/TaskListFragment.kt @@ -406,29 +406,71 @@ class TaskListFragment : BaseFragment() { stopVoice() // 清理上一个 updateVoiceUI(true) Timber.d("语音播放: $url") - mediaPlayer = MediaPlayer().apply { - setAudioAttributes( - android.media.AudioAttributes.Builder() - .setContentType(android.media.AudioAttributes.CONTENT_TYPE_SPEECH) - .setUsage(android.media.AudioAttributes.USAGE_MEDIA) - .build() - ) - setDataSource(url) - setOnPreparedListener { - Timber.d("语音准备完成,开始播放") - start() - } - setOnCompletionListener { stopVoice() } - setOnErrorListener { _, what, extra -> - Timber.w("语音播放失败: what=$what, extra=$extra, url=$url") + viewLifecycleOwner.lifecycleScope.launch { + try { + // 先用 OkHttp 下载到缓存(MediaPlayer 内置 HTTP 在部分设备 HTTPS 有问题) + val cacheFile = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) { + downloadToCache(url) + } + if (cacheFile == null) { + Timber.w("语音下载失败: $url") + stopVoice() + return@launch + } + // 从本地文件播放 + val player = MediaPlayer() + player.setAudioAttributes( + android.media.AudioAttributes.Builder() + .setContentType(android.media.AudioAttributes.CONTENT_TYPE_SPEECH) + .setUsage(android.media.AudioAttributes.USAGE_MEDIA) + .build() + ) + player.setDataSource(cacheFile.absolutePath) + player.setVolume(1.0f, 1.0f) + player.setOnCompletionListener { stopVoice() } + player.setOnErrorListener { _, what, extra -> + Timber.w("语音播放错误: what=$what, extra=$extra") + stopVoice() + true + } + player.prepare() + mediaPlayer = player + player.start() + Timber.d("语音开始播放, duration=${player.duration}ms") + } catch (e: Exception) { + Timber.e(e, "语音播放失败: $url") stopVoice() - true } - prepareAsync() } } } + /** 用 OkHttp 下载音频文件到缓存目录 */ + private fun downloadToCache(url: String): java.io.File? { + return try { + val client = okhttp3.OkHttpClient() + val request = okhttp3.Request.Builder().url(url).build() + val response = client.newCall(request).execute() + if (!response.isSuccessful) { + Timber.w("语音下载HTTP错误: ${response.code}") + return null + } + // 写入缓存文件 + val fileName = "voice_${url.hashCode()}.mp3" + val cacheFile = java.io.File(requireContext().cacheDir, fileName) + response.body?.byteStream()?.use { input -> + cacheFile.outputStream().use { output -> + input.copyTo(output) + } + } + Timber.d("语音下载完成: ${cacheFile.length()} bytes") + cacheFile + } catch (e: Exception) { + Timber.e(e, "语音下载异常") + null + } + } + /** 停止并释放 MediaPlayer,恢复按钮状态 */ private fun stopVoice() { mediaPlayer?.let {