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:
@@ -20,6 +20,7 @@ import kotlinx.serialization.encodeToString
|
||||
import pw.binom.viewmate.core.GlassesMode
|
||||
import pw.binom.viewmate.core.phone.GlassesSender
|
||||
import pw.binom.viewmate.core.phone.PhoneState
|
||||
import pw.binom.viewmate.core.protocol.DownloadProgress
|
||||
import pw.binom.viewmate.core.protocol.GlassesOff
|
||||
import pw.binom.viewmate.core.protocol.GlassesStatus
|
||||
import pw.binom.viewmate.core.protocol.GlassesToHost
|
||||
@@ -36,7 +37,8 @@ const val GLASSES_WS_PATH = "/ws/glasses"
|
||||
/**
|
||||
* Менеджер подключённых очков (телефон). Потокобезопасен (ConcurrentHashMap).
|
||||
* Раздаёт исходящие сообщения всем очкам и обрабатывает входящие
|
||||
* (Hello → Welcome, жесты/статус/позицию → лог, GlassesOff → пауза).
|
||||
* (Hello → Welcome, жесты/статус/позицию → лог, GlassesOff → пауза,
|
||||
* DownloadProgress → карта скачивания на очки).
|
||||
*/
|
||||
class GlassesHub(
|
||||
private val state: PhoneState,
|
||||
@@ -58,6 +60,29 @@ class GlassesHub(
|
||||
private val _storageTotalGb = MutableStateFlow<Double?>(null)
|
||||
val storageTotalGb: StateFlow<Double?> = _storageTotalGb.asStateFlow()
|
||||
|
||||
private val _storageTotalBytes = MutableStateFlow<Long?>(null)
|
||||
val storageTotalBytes: StateFlow<Long?> = _storageTotalBytes.asStateFlow()
|
||||
|
||||
private val _storageFreeBytes = MutableStateFlow<Long?>(null)
|
||||
val storageFreeBytes: StateFlow<Long?> = _storageFreeBytes.asStateFlow()
|
||||
|
||||
private val _mediaBytes = MutableStateFlow<Long?>(null)
|
||||
val mediaBytes: StateFlow<Long?> = _mediaBytes.asStateFlow()
|
||||
|
||||
private val _appVersion = MutableStateFlow<String?>(null)
|
||||
val appVersion: StateFlow<String?> = _appVersion.asStateFlow()
|
||||
|
||||
private val _mediaDir = MutableStateFlow<String?>(null)
|
||||
val mediaDir: StateFlow<String?> = _mediaDir.asStateFlow()
|
||||
|
||||
private val _downloadedItemIds = MutableStateFlow<Set<String>>(emptySet())
|
||||
/** itemId полностью скачанных на очки (из периодического GlassesStatus). */
|
||||
val downloadedItemIds: StateFlow<Set<String>> = _downloadedItemIds.asStateFlow()
|
||||
|
||||
private val _glassesDownloads = MutableStateFlow<Map<String, Map<String, DownloadProgress>>>(emptyMap())
|
||||
/** Прогресс скачивания на очки: itemId → имя файла → последний DownloadProgress от очков. */
|
||||
val glassesDownloads: StateFlow<Map<String, Map<String, DownloadProgress>>> = _glassesDownloads.asStateFlow()
|
||||
|
||||
private val _positionMs = MutableStateFlow<Long?>(null)
|
||||
val positionMs: StateFlow<Long?> = _positionMs.asStateFlow()
|
||||
|
||||
@@ -106,7 +131,24 @@ class GlassesHub(
|
||||
_batteryPercent.value = msg.batteryPercent
|
||||
_storageUsedGb.value = msg.storageUsedGb
|
||||
_storageTotalGb.value = msg.storageTotalGb
|
||||
log("glasses", "статус: батарея ${msg.batteryPercent}%, память ${msg.storageUsedGb}/${msg.storageTotalGb} ГБ")
|
||||
if (msg.storageTotalBytes > 0) _storageTotalBytes.value = msg.storageTotalBytes
|
||||
if (msg.storageFreeBytes > 0) _storageFreeBytes.value = msg.storageFreeBytes
|
||||
if (msg.mediaBytes > 0) _mediaBytes.value = msg.mediaBytes
|
||||
if (msg.mediaDir.isNotBlank()) _mediaDir.value = msg.mediaDir
|
||||
if (msg.appVersion.isNotBlank()) _appVersion.value = msg.appVersion
|
||||
_downloadedItemIds.value = msg.downloadedItemIds.toSet()
|
||||
log(
|
||||
"glasses",
|
||||
"статус: батарея ${msg.batteryPercent}%, память ${msg.storageUsedGb}/${msg.storageTotalGb} ГБ, " +
|
||||
"медиа ${msg.mediaBytes} байт, скачано: ${msg.downloadedItemIds.size}, app=${msg.appVersion}",
|
||||
)
|
||||
}
|
||||
|
||||
is DownloadProgress -> {
|
||||
val prev = _glassesDownloads.value[msg.itemId] ?: emptyMap()
|
||||
val updated = _glassesDownloads.value + (msg.itemId to (prev + (msg.fileName to msg)))
|
||||
_glassesDownloads.value = updated
|
||||
log("glasses", "скачивание на очки: ${msg.itemId} ${msg.fileName} ${msg.percent}% (${msg.phase})")
|
||||
}
|
||||
|
||||
is PlaybackPosition -> {
|
||||
|
||||
Reference in New Issue
Block a user