mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-15 00:42:54 +01:00
playback: show notification progress for unknown-length streams
Media3 marks a progressive stream live when the data source reports no length and the extractor no duration, and a live item makes the media session publish PLAYBACK_POSITION_UNKNOWN, so the notification progress never advances even though the item carries the library duration. The app only declares a stream length for a transcode of a track that has one in a length-estimable format (`declaredStreamLength`), so a chunked response stays live for an Opus/Ogg/Vorbis transcode (a length estimate breaks the end-seek they rely on), for a transcode of a track whose library duration is missing, and for a direct play the server does not give a Content-Length. The now playing screen shows progress in all of those because it falls back to the metadata duration; the notification had nothing to advance. Wrap the ExoPlayer in a ForwardingPlayer that reports an item with a declared duration as finite. The session then publishes the real position and the metadata duration, so the notification shows progress for every unknown-length stream, not just Opus. The stream is still not seekable; only the reported liveness changes.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
package com.subtracks.playback
|
||||
|
||||
import android.os.Looper
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.SimpleBasePlayer
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class KnownDurationPlayerTest {
|
||||
private fun player(
|
||||
declaredDurationMs: Long?,
|
||||
live: Boolean = true,
|
||||
): Player =
|
||||
object : SimpleBasePlayer(Looper.getMainLooper()) {
|
||||
override fun getState(): State {
|
||||
val metadata =
|
||||
MediaMetadata
|
||||
.Builder()
|
||||
.setTitle("Track")
|
||||
.setDurationMs(declaredDurationMs)
|
||||
.build()
|
||||
val item =
|
||||
MediaItemData
|
||||
.Builder("track")
|
||||
.setMediaItem(
|
||||
MediaItem
|
||||
.Builder()
|
||||
.setMediaId("s1")
|
||||
.setMediaMetadata(metadata)
|
||||
.build(),
|
||||
).setDurationUs(PLAYER_DURATION_US)
|
||||
.setLiveConfiguration(if (live) MediaItem.LiveConfiguration.UNSET else null)
|
||||
.build()
|
||||
return State
|
||||
.Builder()
|
||||
.setPlaylist(listOf(item))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aLiveTrackWithADeclaredDurationIsReportedFinite() {
|
||||
val player = player(declaredDurationMs = 100_000L)
|
||||
|
||||
assertFalse(KnownDurationPlayer(player).isCurrentMediaItemLive())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aLiveTrackWithoutADeclaredDurationStaysLive() {
|
||||
val player = player(declaredDurationMs = null)
|
||||
|
||||
assertTrue(KnownDurationPlayer(player).isCurrentMediaItemLive())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aFiniteTrackWithADeclaredDurationStaysFinite() {
|
||||
val player = player(declaredDurationMs = 100_000L, live = false)
|
||||
|
||||
assertFalse(KnownDurationPlayer(player).isCurrentMediaItemLive())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun theWrappedDurationIsThePlayersNotTheMetadata() {
|
||||
val player = player(declaredDurationMs = 100_000L)
|
||||
|
||||
assertEquals(PLAYER_DURATION_MS, KnownDurationPlayer(player).getDuration())
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val PLAYER_DURATION_MS = 200_000L
|
||||
const val PLAYER_DURATION_US = PLAYER_DURATION_MS * 1000
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user