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:
@@ -50,26 +50,55 @@ internal fun decideSync(
|
||||
return SyncDecision(seekToMs = seekToMs, playing = playing)
|
||||
}
|
||||
|
||||
/**
|
||||
* Результат выбора аудио-источника: [source] (локальный путь или presigned URL)
|
||||
* либо null с человекочитаемой [reason] — чтобы лог показал точную причину отсутствия звука.
|
||||
*/
|
||||
internal data class AudioSourceResult(
|
||||
val source: String?,
|
||||
val reason: String,
|
||||
)
|
||||
|
||||
/**
|
||||
* Источник аудио для просмотра на очках: локальный скачанный `audio-N.ogg`
|
||||
* (если есть — быстрее, без сети), иначе presigned URL дорожки [audioIndex]
|
||||
* (стриминг, без скачивания). null — дорожки нет.
|
||||
* (стриминг, без скачивания). null — дорожки нет, причина в [AudioSourceResult.reason].
|
||||
* Чистая функция (без Android) — покрыта unit-тестами.
|
||||
*/
|
||||
internal fun resolveAudioSourceDetailed(
|
||||
mirrorFiles: MirrorFiles?,
|
||||
audioIndex: Int,
|
||||
localAudioFile: (index: Int) -> File,
|
||||
presign: (url: String) -> String = { S3Presigner.presignGet(it, PhoneConfig.S3_ACCESS_KEY, PhoneConfig.S3_SECRET_KEY, PhoneConfig.S3_REGION) },
|
||||
): AudioSourceResult {
|
||||
if (mirrorFiles == null) {
|
||||
return AudioSourceResult(null, "files=null (зеркало не получено)")
|
||||
}
|
||||
val audio = mirrorFiles.audios.firstOrNull { it.index == audioIndex } ?: mirrorFiles.audios.firstOrNull()
|
||||
if (audio == null) {
|
||||
return AudioSourceResult(null, "audios пусто (искал дорожку #$audioIndex)")
|
||||
}
|
||||
val local = localAudioFile(audio.index)
|
||||
return if (local.exists()) {
|
||||
AudioSourceResult(local.absolutePath, "локальный файл ${local.absolutePath}")
|
||||
} else {
|
||||
AudioSourceResult(presign(audio.url), "стриминг (локального audio-${audio.index}.ogg на телефоне нет)")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public-обёртка для UI: источник аудио по [DownloadManager] (S3-presign по умолчанию).
|
||||
*/
|
||||
fun resolveAudioSource(
|
||||
downloadManager: DownloadManager,
|
||||
itemId: String,
|
||||
mirrorFiles: MirrorFiles?,
|
||||
audioIndex: Int,
|
||||
): String? {
|
||||
val files = mirrorFiles ?: return null
|
||||
val audio = files.audios.firstOrNull { it.index == audioIndex } ?: files.audios.firstOrNull() ?: return null
|
||||
val local = downloadManager.audioFile(itemId, audio.index)
|
||||
return if (local.exists()) {
|
||||
local.absolutePath
|
||||
} else {
|
||||
S3Presigner.presignGet(audio.url, PhoneConfig.S3_ACCESS_KEY, PhoneConfig.S3_SECRET_KEY, PhoneConfig.S3_REGION)
|
||||
}
|
||||
}
|
||||
): String? = resolveAudioSourceDetailed(
|
||||
mirrorFiles = mirrorFiles,
|
||||
audioIndex = audioIndex,
|
||||
localAudioFile = { downloadManager.audioFile(itemId, it) },
|
||||
).source
|
||||
|
||||
/**
|
||||
* Аудио-плеер телефона, синхронизированный с мастер-таймкодом очков.
|
||||
|
||||
Reference in New Issue
Block a user