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:
@@ -0,0 +1,168 @@
|
||||
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 pw.binom.viewmate.core.phone.buildDownloadFiles
|
||||
import pw.binom.viewmate.core.phone.buildGlassesFileSpecs
|
||||
import pw.binom.viewmate.core.protocol.DownloadPhase
|
||||
import pw.binom.viewmate.core.protocol.DownloadProgress
|
||||
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 GlassesFilesTest {
|
||||
|
||||
private fun tempDir(): File = Files.createTempDirectory("glasses-files-test").toFile()
|
||||
|
||||
private fun mirrorFiles() = MirrorFiles(
|
||||
video = MirrorVideo(key = "v", url = "https://s3/mirror/video.mkv"),
|
||||
audios = listOf(
|
||||
MirrorAudio(index = 0, key = "a0", title = "Русский", url = "https://s3/mirror/audio-0.ogg"),
|
||||
MirrorAudio(index = 1, key = "a1", url = "https://s3/mirror/audio-1.ogg"),
|
||||
),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun buildDownloadFilesUsesPresignedUrlsAndNames() {
|
||||
val msg = buildDownloadFiles(
|
||||
itemId = "movie-1",
|
||||
title = "Interstellar",
|
||||
files = mirrorFiles(),
|
||||
sizes = emptyMap(),
|
||||
presign = { "$it?sig" },
|
||||
)
|
||||
|
||||
assertEquals("movie-1", msg.itemId)
|
||||
assertEquals("Interstellar", msg.title)
|
||||
assertEquals(3, msg.files.size)
|
||||
assertEquals("video.mkv", msg.files[0].name)
|
||||
assertEquals("https://s3/mirror/video.mkv?sig", msg.files[0].url)
|
||||
assertEquals("audio-0.ogg", msg.files[1].name)
|
||||
assertEquals("audio-1.ogg", msg.files[2].name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun buildDownloadFilesFillsKnownSizes() {
|
||||
val msg = buildDownloadFiles(
|
||||
itemId = "movie-1",
|
||||
title = "T",
|
||||
files = mirrorFiles(),
|
||||
sizes = mapOf("video" to 1_863_946_240L, "audio-0" to 68_157_440L),
|
||||
presign = { it },
|
||||
)
|
||||
|
||||
assertEquals(1_863_946_240L, msg.files[0].sizeBytes)
|
||||
assertEquals(68_157_440L, msg.files[1].sizeBytes)
|
||||
assertEquals(0L, msg.files[2].sizeBytes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun buildGlassesFileSpecsPresignsEveryFile() {
|
||||
val specs = buildGlassesFileSpecs(
|
||||
files = mirrorFiles(),
|
||||
sizes = emptyMap(),
|
||||
presign = { "signed-$it" },
|
||||
)
|
||||
assertEquals(3, specs.size)
|
||||
assertTrue(specs.all { it.url.startsWith("signed-") })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun glassesFileStateNullIsNone() {
|
||||
assertEquals(FileDownloadState.NONE to 0, glassesFileState(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun glassesFileStateMapsPhases() {
|
||||
val done = DownloadProgress("i", "video.mkv", 100, 10, 10, DownloadPhase.DONE)
|
||||
assertEquals(FileDownloadState.DONE to 100, glassesFileState(done))
|
||||
|
||||
val downloading = DownloadProgress("i", "video.mkv", 45, 450, 1000, DownloadPhase.DOWNLOADING)
|
||||
assertEquals(FileDownloadState.DOWNLOADING to 45, glassesFileState(downloading))
|
||||
|
||||
val failed = DownloadProgress("i", "audio-0.ogg", 0, 5, 10, DownloadPhase.FAILED)
|
||||
assertEquals(FileDownloadState.FAILED to 0, glassesFileState(failed))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun glassesFileStateClampsDownloadingPercent() {
|
||||
val p = DownloadProgress("i", "video.mkv", 150, 150, 100, DownloadPhase.DOWNLOADING)
|
||||
assertEquals(FileDownloadState.DOWNLOADING to 99, glassesFileState(p))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun phoneFileStateFinalExistsIsDone() {
|
||||
val dir = tempDir()
|
||||
val final = File(dir, "video.mkv").apply { writeBytes(ByteArray(10)) }
|
||||
val part = File(dir, "video.mkv.part")
|
||||
|
||||
assertEquals(FileDownloadState.DONE to 100, phoneFileState(final, part))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun phoneFileStatePartIsDownloading() {
|
||||
val dir = tempDir()
|
||||
val final = File(dir, "video.mkv")
|
||||
val part = File(dir, "video.mkv.part").apply { writeBytes(ByteArray(10)) }
|
||||
|
||||
assertEquals(FileDownloadState.DOWNLOADING to 0, phoneFileState(final, part))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun phoneFileStateNoneWhenNothing() {
|
||||
val dir = tempDir()
|
||||
assertEquals(FileDownloadState.NONE to 0, phoneFileState(File(dir, "video.mkv"), File(dir, "video.mkv.part")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun glassesOverallPercentEmptyIsNull() {
|
||||
assertNull(glassesOverallPercent(listOf("video.mkv", "audio-0.ogg"), emptyMap(), emptyMap()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun glassesOverallPercentAllDoneIsHundred() {
|
||||
val names = listOf("video.mkv", "audio-0.ogg")
|
||||
val perFile = mapOf(
|
||||
"video.mkv" to DownloadProgress("i", "video.mkv", 100, 100, 100, DownloadPhase.DONE),
|
||||
"audio-0.ogg" to DownloadProgress("i", "audio-0.ogg", 100, 50, 50, DownloadPhase.DONE),
|
||||
)
|
||||
assertEquals(100, glassesOverallPercent(names, emptyMap(), perFile))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun glassesOverallPercentUsesKnownSizes() {
|
||||
val names = listOf("video.mkv", "audio-0.ogg")
|
||||
val known = mapOf("video.mkv" to 100L, "audio-0.ogg" to 100L)
|
||||
val perFile = mapOf(
|
||||
"video.mkv" to DownloadProgress("i", "video.mkv", 100, 100, 100, DownloadPhase.DONE),
|
||||
"audio-0.ogg" to DownloadProgress("i", "audio-0.ogg", 25, 25, 100, DownloadPhase.DOWNLOADING),
|
||||
)
|
||||
assertEquals(62, glassesOverallPercent(names, known, perFile)) // 125/200
|
||||
}
|
||||
|
||||
@Test
|
||||
fun glassesOverallPercentFallsBackToStartedTotals() {
|
||||
val names = listOf("video.mkv", "audio-0.ogg")
|
||||
val perFile = mapOf(
|
||||
"video.mkv" to DownloadProgress("i", "video.mkv", 50, 50, 100, DownloadPhase.DOWNLOADING),
|
||||
)
|
||||
assertEquals(50, glassesOverallPercent(names, emptyMap(), perFile))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun glassesLivePercentRejectsEmpty() {
|
||||
assertNull(glassesLivePercent(emptyMap()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun glassesLivePercentAllDoneIsHundred() {
|
||||
val perFile = mapOf(
|
||||
"video.mkv" to DownloadProgress("i", "video.mkv", 100, 100, 100, DownloadPhase.DONE),
|
||||
)
|
||||
assertEquals(100, glassesLivePercent(perFile))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user