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:
@@ -1,8 +1,14 @@
|
||||
package pw.binom.viewmate.phone
|
||||
|
||||
import pw.binom.viewmate.core.media.MirrorAudio
|
||||
import pw.binom.viewmate.core.media.MirrorFiles
|
||||
import pw.binom.viewmate.core.media.MirrorVideo
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class AudioSyncPlayerTest {
|
||||
|
||||
@@ -59,4 +65,70 @@ class AudioSyncPlayerTest {
|
||||
assertNull(d.seekToMs)
|
||||
assertNull(d.playing)
|
||||
}
|
||||
|
||||
// --- resolveAudioSourceDetailed (чистая функция, ветки причины «нет звука») ---
|
||||
|
||||
private fun mirrorFiles(audios: List<MirrorAudio> = listOf()) = MirrorFiles(
|
||||
video = MirrorVideo(key = "v", url = "https://s3/mirror/video.mkv"),
|
||||
audios = audios,
|
||||
)
|
||||
|
||||
private fun audioDir(): File = Files.createTempDirectory("audio-src-test").toFile()
|
||||
|
||||
@Test
|
||||
fun sourceIsNullWhenMirrorFilesMissing() {
|
||||
val result = resolveAudioSourceDetailed(null, 0, { File("/x", "audio-$it.ogg") }) { "signed:$it" }
|
||||
assertNull(result.source)
|
||||
assertTrue(result.reason.contains("null"), "причина должна указывать на отсутствующее зеркало: ${result.reason}")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sourceIsNullWhenAudiosEmpty() {
|
||||
val result = resolveAudioSourceDetailed(mirrorFiles(), 0, { File("/x", "audio-$it.ogg") }) { "signed:$it" }
|
||||
assertNull(result.source)
|
||||
assertTrue(result.reason.contains("audios пусто"), "причина должна указывать на пустой список дорожек: ${result.reason}")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun usesRequestedAudioIndex() {
|
||||
val dir = audioDir()
|
||||
File(dir, "audio-1.ogg").writeBytes(ByteArray(5))
|
||||
val audios = listOf(
|
||||
MirrorAudio(index = 0, key = "a0", url = "https://s3/audio-0.ogg"),
|
||||
MirrorAudio(index = 1, key = "a1", url = "https://s3/audio-1.ogg"),
|
||||
)
|
||||
val result = resolveAudioSourceDetailed(mirrorFiles(audios), 1, { File(dir, "audio-$it.ogg") }) { "signed:$it" }
|
||||
assertEquals(File(dir, "audio-1.ogg").absolutePath, result.source)
|
||||
assertTrue(result.reason.contains("локальный файл"), "выбрана именно дорожка #1")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fallsBackToFirstAudioWhenIndexMissing() {
|
||||
val dir = audioDir()
|
||||
File(dir, "audio-0.ogg").writeBytes(ByteArray(5))
|
||||
val audios = listOf(
|
||||
MirrorAudio(index = 0, key = "a0", url = "https://s3/audio-0.ogg"),
|
||||
MirrorAudio(index = 2, key = "a2", url = "https://s3/audio-2.ogg"),
|
||||
)
|
||||
val result = resolveAudioSourceDetailed(mirrorFiles(audios), 7, { File(dir, "audio-$it.ogg") }) { "signed:$it" }
|
||||
assertEquals(File(dir, "audio-0.ogg").absolutePath, result.source, "нет #7 — берём первую дорожку")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun usesLocalFileWhenDownloaded() {
|
||||
val dir = audioDir()
|
||||
val local = File(dir, "audio-3.ogg").apply { writeBytes(ByteArray(10)) }
|
||||
val audios = listOf(MirrorAudio(index = 3, key = "a3", url = "https://s3/audio-3.ogg"))
|
||||
val result = resolveAudioSourceDetailed(mirrorFiles(audios), 3, { File(dir, "audio-$it.ogg") }) { "signed:$it" }
|
||||
assertEquals(local.absolutePath, result.source)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun streamsPresignedUrlWhenNoLocalFile() {
|
||||
val dir = audioDir()
|
||||
val audios = listOf(MirrorAudio(index = 0, key = "a0", url = "https://s3/audio-0.ogg"))
|
||||
val result = resolveAudioSourceDetailed(mirrorFiles(audios), 0, { File(dir, "audio-$it.ogg") }) { "signed:$it" }
|
||||
assertEquals("signed:https://s3/audio-0.ogg", result.source)
|
||||
assertTrue(result.reason.contains("стриминг"), "без локального файла — стриминг: ${result.reason}")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user