Skip to content

Fix #279: Add adaptive playback health monitoring and dynamic buffer …#284

Open
AnantSinghTanwar-ux wants to merge 1 commit into
ProdigyV21:mainfrom
AnantSinghTanwar-ux:fix/issue-279-playback-adaptive
Open

Fix #279: Add adaptive playback health monitoring and dynamic buffer …#284
AnantSinghTanwar-ux wants to merge 1 commit into
ProdigyV21:mainfrom
AnantSinghTanwar-ux:fix/issue-279-playback-adaptive

Conversation

@AnantSinghTanwar-ux
Copy link
Copy Markdown

Issue

Fixes #279

Changes Made

  • Added adaptive playback health monitoring
  • Implemented dynamic buffer management
  • Improved IPTV stream stability handling
  • Added playback recovery logic for unstable streams

Testing

  • Tested with IPTV streams
  • Verified buffer adjustment behavior
  • Confirmed no regressions in normal playback

Copilot AI review requested due to automatic review settings May 31, 2026 10:57
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds playback health monitoring and adaptive playback behavior around ExoPlayer, surfacing a lightweight UI indicator when network/playback quality degrades.

Changes:

  • Injects a DefaultTrackSelector and a custom LoadControl into TV ExoPlayer creation.
  • Introduces periodic playback metrics collection and adaptive bitrate/buffer tuning (PlaybackMetricsAnalyzer).
  • Displays an on-screen “Network health” indicator in TV playback UI.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
app/src/main/kotlin/com/arflix/tv/ui/screens/tv/TvScreen.kt Wires in track selector + adaptive load control, creates/releases metrics analyzer, and renders UI health indicator.
app/src/main/kotlin/com/arflix/tv/ui/screens/player/PlayerScreen.kt Adds imports for new playback health/adaptation components (usage not shown in diff).
app/src/main/kotlin/com/arflix/tv/playback/PlaybackMetricsAnalyzer.kt New analyzer collecting metrics and adapting track selection/buffer strategy based on computed “health”.
app/src/main/kotlin/com/arflix/tv/playback/PlaybackHealthIndicator.kt New Compose UI overlay to display degraded playback/network health states.
app/src/main/kotlin/com/arflix/tv/playback/NetworkAdaptiveLoadControl.kt New LoadControl implementation with dynamic max-buffer adjustments.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +75 to +87
override fun onPlaybackStateChanged(
eventTime: AnalyticsListener.EventTime,
state: Int
) {
if (state == Player.STATE_BUFFERING && isStartup && startupStartTime == 0L) {
startupStartTime = System.currentTimeMillis()
} else if (state == Player.STATE_READY && isStartup) {
val latency = System.currentTimeMillis() - startupStartTime
_metrics.value = _metrics.value.copy(startupLatencyMs = latency)
isStartup = false
}
evaluateHealthAndAdapt()
}
Comment on lines +92 to +111
val currentPos = player.currentPosition
val bufferedPos = player.bufferedPosition

val playedDelta = currentPos - lastCurrentPosition
val bufferedDelta = bufferedPos - lastBufferedPosition

// Calculate depletion as a ratio: how much playhead advanced vs how much buffer advanced
val depletion = if (playedDelta > 0) {
(playedDelta - bufferedDelta).toFloat() / playedDelta.toFloat()
} else 0f

val oldAvg = _metrics.value.bufferDepletionRateAvg
val newAvg = oldAvg * 0.7f + depletion * 0.3f

_metrics.value = _metrics.value.copy(bufferDepletionRateAvg = newAvg)

lastCurrentPosition = currentPos
lastBufferedPosition = bufferedPos

evaluateHealthAndAdapt()
Comment on lines +114 to +132
private fun evaluateHealthAndAdapt() {
val curr = _metrics.value
var newHealth = PlaybackHealth.EXCELLENT

if (curr.droppedFrames > 30 || curr.bufferDepletionRateAvg > 0.8f) {
newHealth = PlaybackHealth.CRITICAL
} else if (curr.droppedFrames > 10 || curr.bufferDepletionRateAvg > 0.5f) {
newHealth = PlaybackHealth.POOR
} else if (curr.bufferDepletionRateAvg > 0.2f) {
newHealth = PlaybackHealth.FAIR
} else if (curr.bufferDepletionRateAvg > 0f) {
newHealth = PlaybackHealth.GOOD
}

if (curr.health != newHealth) {
_metrics.value = curr.copy(health = newHealth)
adaptPlayback(newHealth)
}
}
Comment on lines +30 to +36
override fun onTracksSelected(
timeline: Timeline,
mediaPeriodId: MediaPeriodId,
renderers: Array<Renderer>,
trackGroups: TrackGroupArray,
trackSelections: Array<ExoTrackSelection>
) {
Comment on lines +62 to +77
override fun shouldContinueLoading(
playbackPositionUs: Long,
bufferedDurationUs: Long,
playbackSpeed: Float
): Boolean {
val targetBufferSizeReached = allocator.totalBytesAllocated >= targetBufferSize
val bufferedMs = bufferedDurationUs / 1000

if (bufferedMs < minBufferMs) {
isBuffering = true
} else if (bufferedMs >= maxBufferMs || targetBufferSizeReached) {
isBuffering = false
}

return isBuffering
}
Comment on lines 201 to 205
return ExoPlayer.Builder(context)
.setMediaSourceFactory(mediaSourceFactory)
.setLoadControl(loadControl)
.setTrackSelector(trackSelector)
.build()
@@ -0,0 +1,168 @@
package com.arflix.tv.playback

import androidx.media3.common.C
@ProdigyV21
Copy link
Copy Markdown
Owner

Good idea, but this is not ready to merge yet.

I tested a clean merge into current main and both compile tasks fail:

  • :app:compilePlayDebugKotlin
  • :app:compileSideloadDebugKotlin

Main compile issues:

  • NetworkAdaptiveLoadControl is using the wrong Media3 LoadControl method signatures for our Media3 version.
  • MediaPeriodId import/path is wrong.
  • LoadControl import is missing in TvScreen.kt.
  • rememberCoroutineScope import is missing in TvScreen.kt.
  • C.DEFAULT_VIDEO_BUFFER_SIZE does not exist in this project’s Media3 version.

Also, the playback health logic needs some cleanup before merge:

  • startupLatencyMs can become invalid if READY happens before BUFFERING
  • buffer depletion can become negative/too high after seeks or live window jumps
  • droppedFrames is treated as a lifetime total, so long playback can eventually mark the stream as POOR/CRITICAL forever even if it becomes stable again
  • dynamic buffer increase may not actually help if the byte buffer limit is already reached

The feature itself is useful, especially for IPTV stability, but please fix compile first and then clean up the health logic so it reflects current playback conditions, not old/invalid data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adaptive Source Reliability Scoring

3 participants