mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
stop estimating a length for Ogg-based transcodes
Streaming Opus from Navidrome failed the first play with a read out of range, then worked. Ogg seeks by binary-searching for the last page from the end of the file, and the length the app estimates for a transcode overshoots the real output, so that search reads past the data and the server answers 416. Opus, Ogg and Vorbis now get no estimate: seeking falls back to what is buffered, but playback is not scuppered by a length we cannot know. Downloads were unaffected because they never carried the estimate.
This commit is contained in:
@@ -10,12 +10,18 @@ private const val LOSSLESS_CEILING_BPS = 9_216_000L
|
|||||||
private const val OVERHEAD_BYTES = 256L * 1024L
|
private const val OVERHEAD_BYTES = 256L * 1024L
|
||||||
private val LOSSLESS_FORMATS = setOf("flac", "wav", "alac")
|
private val LOSSLESS_FORMATS = setOf("flac", "wav", "alac")
|
||||||
|
|
||||||
|
// Ogg-based streams seek by binary-searching for the last page from the end of the file, so a
|
||||||
|
// length estimate that overshoots makes the player read past the real data and the server answers
|
||||||
|
// 416 (POSITION_OUT_OF_RANGE). These get no estimate and seek only within what is buffered.
|
||||||
|
private val END_SEEKING_FORMATS = setOf("opus", "ogg", "vorbis")
|
||||||
|
|
||||||
fun declaredStreamLength(
|
fun declaredStreamLength(
|
||||||
durationMs: Long?,
|
durationMs: Long?,
|
||||||
quality: StreamQuality,
|
quality: StreamQuality,
|
||||||
): Long? {
|
): Long? {
|
||||||
if (durationMs == null || durationMs <= 0) return null
|
if (durationMs == null || durationMs <= 0) return null
|
||||||
if (!quality.transcodes) return null
|
if (!quality.transcodes) return null
|
||||||
|
if (quality.format?.lowercase() in END_SEEKING_FORMATS) return null
|
||||||
val bps =
|
val bps =
|
||||||
when {
|
when {
|
||||||
quality.format?.lowercase() in LOSSLESS_FORMATS -> LOSSLESS_CEILING_BPS
|
quality.format?.lowercase() in LOSSLESS_FORMATS -> LOSSLESS_CEILING_BPS
|
||||||
|
|||||||
@@ -39,6 +39,13 @@ class StreamLengthTest {
|
|||||||
assertTrue(cdQuality >= 320 * 1_411_200 / 8)
|
assertTrue(cdQuality >= 320 * 1_411_200 / 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun oggBasedFormatsGetNoEstimateBecauseSeekingReadsFromTheEnd() {
|
||||||
|
assertNull(declaredStreamLength(180_000, StreamQuality(128, "opus")))
|
||||||
|
assertNull(declaredStreamLength(180_000, StreamQuality(128, "ogg")))
|
||||||
|
assertNull(declaredStreamLength(180_000, StreamQuality(128, "vorbis")))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun theLengthSuffixRoundTripsThroughTheFragment() {
|
fun theLengthSuffixRoundTripsThroughTheFragment() {
|
||||||
val uri = "http://host/rest/stream.view?id=s1" + streamLengthSuffix(1_234_567)
|
val uri = "http://host/rest/stream.view?id=s1" + streamLengthSuffix(1_234_567)
|
||||||
|
|||||||
Reference in New Issue
Block a user