fix(app-phone): звук при watch (диагностика+подстраховка mirrorByItem) + качка не воскресает после удаления

- onWatch: явные логи в каждой точке отказа (watch result / audio source / уже играет / не скачано / mediaDir пуст), свежий mirrorByItem если files не догрузился, звук стартует с позиции очков при уже идущем видео
- resolveAudioSource → resolveAudioSourceDetailed (AudioSourceResult с причиной null)
- DownloadManager: поколения generations — delete() инвалидирует старую корутину (не воскрешает качку), ensureActive перед .part/rename, jobs.remove(itemId, job)
- Тесты 62/0 (в т.ч. поколения — поймали null != 0L)

Инфраструктурный блокер (не код): SeaweedFS на Server3 76.66 в IO-шторме (load ~184, wa 20%, чужой yt-dlp на хосте) — большие GET с s3.binom.pw висят/рвутся с ~14:00; качка на очки FAILED по этой причине.
This commit is contained in:
Hermes Agent
2026-08-19 17:19:53 +03:00
parent fafdddc121
commit 49dac8b33f
29 changed files with 2237 additions and 173 deletions
@@ -1,6 +1,7 @@
package pw.binom.viewmate.glasses
import android.content.Context
import android.net.Uri
import android.os.Handler
import android.os.Looper
import com.google.android.exoplayer2.MediaItem
@@ -10,6 +11,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import pw.binom.viewmate.core.MediaCommand
import pw.binom.viewmate.core.protocol.PlayVideo
import java.io.File
/**
* Держатель плеера фильма: создаёт/освобождает SimpleExoPlayer,
@@ -30,6 +32,16 @@ class MovieController(private val context: Context) {
fun playVideo(msg: PlayVideo) {
mainHandler.post {
// Стриминг отключён полностью: играем ТОЛЬКО локальный файл file://, который есть на диске.
if (!msg.videoUrl.startsWith("file://")) {
log("glasses", "PlayVideo отклонён: стриминг запрещён, url не локальный (${msg.videoUrl.take(80)})")
return@post
}
val local = Uri.parse(msg.videoUrl).path?.let { File(it) }
if (local == null || !local.isFile || local.length() <= 0) {
log("glasses", "PlayVideo отклонён: локальный файл не найден (${msg.videoUrl})")
return@post
}
val player = _player ?: SimpleExoPlayer.Builder(context).build().also {
_player = it
log("glasses", "SimpleExoPlayer создан")