diff --git a/.github/nightly-notes/2026-11-07-fix-stats-viewport.md b/.github/nightly-notes/2026-11-07-fix-stats-viewport.md new file mode 100644 index 000000000..d5e76ed92 --- /dev/null +++ b/.github/nightly-notes/2026-11-07-fix-stats-viewport.md @@ -0,0 +1,2 @@ +## Fixed +- Stats for nerds now shows the real size of the video window instead of -1x-1 diff --git a/app/src/main/java/io/github/aedev/flow/player/EnhancedPlayerManager.kt b/app/src/main/java/io/github/aedev/flow/player/EnhancedPlayerManager.kt index dbbc44ad9..e9683dda1 100644 --- a/app/src/main/java/io/github/aedev/flow/player/EnhancedPlayerManager.kt +++ b/app/src/main/java/io/github/aedev/flow/player/EnhancedPlayerManager.kt @@ -2693,6 +2693,7 @@ class EnhancedPlayerManager private constructor() { bandwidthBitsPerSecond = bandwidthMeter?.bitrateEstimate ?: 0L, decoderName = analyticsLogger?.activeDecoderName, gainDb = audioFeaturesManager?.appliedLoudnessDb, + surfaceFrame = surfaceManager?.getSurfaceHolder()?.surfaceFrame, ) fun getCastVideoStreams(): List = availableVideoStreams diff --git a/app/src/main/java/io/github/aedev/flow/player/stats/PlayerStatsReader.kt b/app/src/main/java/io/github/aedev/flow/player/stats/PlayerStatsReader.kt index c8632e8b6..dc5e4e457 100644 --- a/app/src/main/java/io/github/aedev/flow/player/stats/PlayerStatsReader.kt +++ b/app/src/main/java/io/github/aedev/flow/player/stats/PlayerStatsReader.kt @@ -1,7 +1,9 @@ package io.github.aedev.flow.player.stats +import android.graphics.Rect import android.os.Build import androidx.media3.common.Format +import androidx.media3.common.util.Size import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer @@ -10,6 +12,7 @@ internal data class PlayerStatsReadings( val bandwidthBitsPerSecond: Long, val decoderName: String?, val gainDb: Float?, + val surfaceFrame: Rect?, ) @UnstableApi @@ -24,7 +27,8 @@ internal object PlayerStatsReader { client: String?, ): PlayerStatsSnapshot { val counters = player.videoDecoderCounters?.also { it.ensureUpdated() } - val surface = player.surfaceSize + // The player can't measure a bare Surface and reports -1, so a SurfaceView's own frame comes first. + val viewport = readings.surfaceFrame?.let { Size(it.width(), it.height()) } ?: player.surfaceSize return PlayerStatsSnapshot( device = device, videoId = videoId, @@ -43,8 +47,8 @@ internal object PlayerStatsReader { gainDb = readings.gainDb, bandwidthBitsPerSecond = readings.bandwidthBitsPerSecond, bufferedMs = player.totalBufferedDuration, - viewportWidth = surface.width, - viewportHeight = surface.height, + viewportWidth = viewport.width, + viewportHeight = viewport.height, droppedFrames = counters?.droppedBufferCount ?: 0, renderedFrames = counters?.renderedOutputBufferCount ?: 0, ) diff --git a/app/src/main/java/io/github/aedev/flow/player/stats/PlayerStatsSnapshot.kt b/app/src/main/java/io/github/aedev/flow/player/stats/PlayerStatsSnapshot.kt index e7d54881b..a61c514f1 100644 --- a/app/src/main/java/io/github/aedev/flow/player/stats/PlayerStatsSnapshot.kt +++ b/app/src/main/java/io/github/aedev/flow/player/stats/PlayerStatsSnapshot.kt @@ -67,10 +67,17 @@ internal object PlayerStatsFormatter { PlayerStatLine(R.string.stats_volume, volume(snapshot)), PlayerStatLine(R.string.stats_bandwidth, bandwidth(snapshot.bandwidthBitsPerSecond)), PlayerStatLine(R.string.stats_buffered, String.format(Locale.US, "%.1f s", snapshot.bufferedMs / MS_PER_SECOND)), - PlayerStatLine(R.string.stats_viewport, "${snapshot.viewportWidth}x${snapshot.viewportHeight}"), + PlayerStatLine(R.string.stats_viewport, viewport(snapshot)), PlayerStatLine(R.string.stats_dropped_frames, "${snapshot.droppedFrames} / ${snapshot.renderedFrames}"), ) + private fun viewport(snapshot: PlayerStatsSnapshot): String = + if (snapshot.viewportWidth > 0 && snapshot.viewportHeight > 0) { + "${snapshot.viewportWidth}x${snapshot.viewportHeight}" + } else { + UNKNOWN + } + private fun videoId(snapshot: PlayerStatsSnapshot): String = listOfNotNull(snapshot.videoId, "[${snapshot.kind}]", snapshot.client).joinToString(" ") diff --git a/app/src/test/java/io/github/aedev/flow/player/stats/PlayerStatsFormatterTest.kt b/app/src/test/java/io/github/aedev/flow/player/stats/PlayerStatsFormatterTest.kt index 2d83c3680..92c724661 100644 --- a/app/src/test/java/io/github/aedev/flow/player/stats/PlayerStatsFormatterTest.kt +++ b/app/src/test/java/io/github/aedev/flow/player/stats/PlayerStatsFormatterTest.kt @@ -41,6 +41,13 @@ class PlayerStatsFormatterTest { assertThat(value(full, R.string.stats_dropped_frames)).isEqualTo("18 / 4469") } + @Test + fun `a viewport the player could not measure reads as unknown`() { + val unmeasured = full.copy(viewportWidth = -1, viewportHeight = -1) + + assertThat(value(unmeasured, R.string.stats_viewport)).isEqualTo("-") + } + @Test fun `a slow connection reads in kbps`() { assertThat(value(full.copy(bandwidthBitsPerSecond = 640_000L), R.string.stats_bandwidth)).isEqualTo("640 kbps")