chore(playback): add diagnostic logs for cache + quality selection
Three debug-level log lines for verifying the HLS caching and resolution-policy plumbing on a real device. Easy to revert as a single commit before merging if the noise is unwanted. - VideoCache: one line at app start with computed size, available, total disk - CustomMediaSourceFactory: one line per MediaSource creation, CACHE or BYPASS - InitialVideoQualitySelector: one line per applied policy, with selected resolution and the rendition list Tail with: adb logcat -s VideoCache CustomMediaSourceFactory VideoQuality Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+18
@@ -28,6 +28,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.Tracks
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
|
||||
/**
|
||||
* Which HLS rendition to pick automatically the first time tracks become available for a media
|
||||
@@ -96,13 +97,26 @@ private fun applyInitialQuality(
|
||||
// No point forcing a choice when there's only one rendition, and no future update will
|
||||
// change that for this media id, so mark it as settled.
|
||||
if (videoGroup.length <= 1) {
|
||||
Log.d("VideoQuality") {
|
||||
val f = videoGroup.getTrackFormat(0)
|
||||
"policy=$policy mediaId=$mediaId SINGLE rendition ${f.width}x${f.height} (no choice)"
|
||||
}
|
||||
appliedForMediaId.value = mediaId
|
||||
return
|
||||
}
|
||||
|
||||
val renditions =
|
||||
(0 until videoGroup.length).joinToString(", ") {
|
||||
val f = videoGroup.getTrackFormat(it)
|
||||
"${f.width}x${f.height}"
|
||||
}
|
||||
|
||||
when (policy) {
|
||||
VideoQualityPolicy.AUTO -> {
|
||||
if (hasVideoOverride(player)) clearVideoOverride(player)
|
||||
Log.d("VideoQuality") {
|
||||
"policy=AUTO mediaId=$mediaId cleared override (from ${videoGroup.length} renditions: [$renditions])"
|
||||
}
|
||||
appliedForMediaId.value = mediaId
|
||||
}
|
||||
|
||||
@@ -111,6 +125,10 @@ private fun applyInitialQuality(
|
||||
// retry on the next onTracksChanged when real video dimensions arrive.
|
||||
val lowestIndex = findLowestResolutionTrackIndex(videoGroup) ?: return
|
||||
selectVideoTrack(player, videoGroup, lowestIndex)
|
||||
Log.d("VideoQuality") {
|
||||
val f = videoGroup.getTrackFormat(lowestIndex)
|
||||
"policy=LOWEST mediaId=$mediaId selected=${f.width}x${f.height} (from ${videoGroup.length} renditions: [$renditions])"
|
||||
}
|
||||
appliedForMediaId.value = mediaId
|
||||
}
|
||||
}
|
||||
|
||||
+12
-5
@@ -28,6 +28,7 @@ import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.datasource.cache.CacheDataSource
|
||||
import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor
|
||||
import androidx.media3.datasource.cache.SimpleCache
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import java.io.File
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
@@ -35,8 +36,10 @@ class VideoCache {
|
||||
companion object {
|
||||
// Target fraction of currently-available disk space.
|
||||
private const val CACHE_SIZE_PERCENT = 0.20
|
||||
|
||||
// Hard cap so we never consume more than this even on large devices.
|
||||
private const val CACHE_SIZE_MAX_BYTES = 4L * 1024 * 1024 * 1024 // 4 GB
|
||||
|
||||
// Floor so the cache is still useful on low-storage devices.
|
||||
private const val CACHE_SIZE_MIN_BYTES = 256L * 1024 * 1024 // 256 MB
|
||||
}
|
||||
@@ -76,12 +79,16 @@ class VideoCache {
|
||||
*/
|
||||
private fun calculateCacheSize(cachePath: File): Long {
|
||||
cachePath.mkdirs()
|
||||
val availableBytes =
|
||||
runCatching {
|
||||
StatFs(cachePath.absolutePath).availableBytes
|
||||
}.getOrDefault(0L)
|
||||
val statFs = runCatching { StatFs(cachePath.absolutePath) }.getOrNull()
|
||||
val availableBytes = statFs?.availableBytes ?: 0L
|
||||
val target = (availableBytes * CACHE_SIZE_PERCENT).toLong()
|
||||
return target.coerceIn(CACHE_SIZE_MIN_BYTES, CACHE_SIZE_MAX_BYTES)
|
||||
val cacheSize = target.coerceIn(CACHE_SIZE_MIN_BYTES, CACHE_SIZE_MAX_BYTES)
|
||||
Log.d("VideoCache") {
|
||||
"VideoCache size: ${cacheSize / (1024 * 1024)} MB " +
|
||||
"(available: ${availableBytes / (1024 * 1024)} MB / " +
|
||||
"total: ${(statFs?.totalBytes ?: 0L) / (1024 * 1024)} MB)"
|
||||
}
|
||||
return cacheSize
|
||||
}
|
||||
|
||||
// This method should be called when proxy setting changes.
|
||||
|
||||
+9
-3
@@ -30,6 +30,7 @@ import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.MediaItemCache
|
||||
import com.vitorpamplona.amethyst.service.playback.diskCache.VideoCache
|
||||
import com.vitorpamplona.amethyst.service.playback.diskCache.isLiveStreaming
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
|
||||
/**
|
||||
* True live streams (kind 30311) must not be cached. On-demand HLS
|
||||
@@ -66,10 +67,15 @@ class CustomMediaSourceFactory(
|
||||
override fun getSupportedTypes(): IntArray = nonCachingFactory.supportedTypes
|
||||
|
||||
override fun createMediaSource(mediaItem: MediaItem): MediaSource {
|
||||
if (isLiveStream(mediaItem)) {
|
||||
return nonCachingFactory.createMediaSource(mediaItem)
|
||||
val live = isLiveStream(mediaItem)
|
||||
Log.d("CustomMediaSourceFactory") {
|
||||
"createMediaSource(${if (live) "BYPASS" else "CACHE"}): ${mediaItem.mediaId}"
|
||||
}
|
||||
return if (live) {
|
||||
nonCachingFactory.createMediaSource(mediaItem)
|
||||
} else {
|
||||
cachingFactory.createMediaSource(mediaItem)
|
||||
}
|
||||
return cachingFactory.createMediaSource(mediaItem)
|
||||
}
|
||||
|
||||
private fun isLiveStream(mediaItem: MediaItem): Boolean {
|
||||
|
||||
Reference in New Issue
Block a user