Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,16 @@ internal fun shouldDisableAudioOffloadOnEarlyBuffering(
lastPlayingAtMs: Long,
timeSincePlayingMs: Long,
isPostSeekBuffering: Boolean,
isPostTransitionBuffering: Boolean
isPostTransitionBuffering: Boolean,
isPostMediaItemTransition: Boolean
): Boolean {
return audioOffloadEnabled &&
!transitionRunning &&
lastPlayingAtMs > 0L &&
timeSincePlayingMs < 500L &&
!isPostSeekBuffering &&
!isPostTransitionBuffering
!isPostTransitionBuffering &&
!isPostMediaItemTransition
}

/** ExoPlayer [DefaultLoadControl] buffer durations (ms) for a build of the player. */
Expand All @@ -178,14 +180,14 @@ internal fun loadControlBufferProfileFor(isLowRamDevice: Boolean): LoadControlBu
LoadControlBufferProfile(
minBufferMs = 15_000,
maxBufferMs = 30_000,
bufferForPlaybackMs = 2_000,
bufferForPlaybackMs = 1_000,
bufferForPlaybackAfterRebufferMs = 5_000
)
} else {
LoadControlBufferProfile(
minBufferMs = 30_000,
maxBufferMs = 60_000,
bufferForPlaybackMs = 2_000,
bufferForPlaybackMs = 1_000,
bufferForPlaybackAfterRebufferMs = 5_000
)
}
Expand Down Expand Up @@ -279,6 +281,8 @@ class DualPlayerEngine @Inject constructor(
// player rebuild, which leaves the MediaSession briefly pointing at the released player
// and silently drops any subsequent seeks.
private var lastSeekAtMs: Long = 0L
// Used to distinguish a STATE_BUFFERING caused by a song transition from a real HAL offload reset.
private var lastMediaItemTransitionAtMs: Long = 0L
// Diagnostics: timestamp when the master player entered STATE_BUFFERING, used to
// measure buffering->ready (playback prepare) durations for the performance report.
private var bufferingStartedAtMs: Long = 0L
Expand Down Expand Up @@ -466,6 +470,7 @@ class DualPlayerEngine @Inject constructor(
}

override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
lastMediaItemTransitionAtMs = SystemClock.elapsedRealtime()
cancelAudioOffloadFallback()

// If the transition was not automatic (e.g. user skip or playlist change),
Expand Down Expand Up @@ -540,16 +545,20 @@ class DualPlayerEngine @Inject constructor(
val timeSincePlayingMs = now - lastPlayingAtMs
val timeSinceSeekMs = now - lastSeekAtMs
val timeSinceTransitionMs = now - lastTransitionFinishedAtMs
val timeSinceMediaItemTransitionMs = now - lastMediaItemTransitionAtMs
val isPostSeekBuffering = lastSeekAtMs > 0L && timeSinceSeekMs < 1_500L
val isPostTransitionBuffering = lastTransitionFinishedAtMs > 0L &&
timeSinceTransitionMs < POST_TRANSITION_OFFLOAD_GUARD_MS
val isPostMediaItemTransition = lastMediaItemTransitionAtMs > 0L &&
timeSinceMediaItemTransitionMs < 2_000L
if (shouldDisableAudioOffloadOnEarlyBuffering(
audioOffloadEnabled = audioOffloadEnabled,
transitionRunning = transitionRunning,
lastPlayingAtMs = lastPlayingAtMs,
timeSincePlayingMs = timeSincePlayingMs,
isPostSeekBuffering = isPostSeekBuffering,
isPostTransitionBuffering = isPostTransitionBuffering
isPostTransitionBuffering = isPostTransitionBuffering,
isPostMediaItemTransition = isPostMediaItemTransition
)
) {
disableAudioOffloadForSession(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ class TransitionController @Inject constructor(
engine.cancelNext()
}

// Debounce preparing the next track during rapid skips
delay(1500)

val player = engine.masterPlayer
val repeatMode = player.repeatMode
val transitionTarget = engine.getNextTransitionTarget(currentMediaItem, repeatMode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2990,7 +2990,7 @@ class PlayerViewModel @Inject constructor(
bufferingDebounceJob?.cancel()
if (playbackState == Player.STATE_BUFFERING) {
bufferingDebounceJob = viewModelScope.launch {
delay(150) // Wait 150ms before showing buffering indicator
delay(500) // Wait 500ms before showing buffering indicator
playbackStateHolder.updateStablePlayerState { state ->
state.copy(isBuffering = true)
}
Expand Down Expand Up @@ -3113,7 +3113,7 @@ class PlayerViewModel @Inject constructor(
)

song?.let { currentSongValue ->
viewModelScope.launch {
launch {
val uri = currentSongValue.albumArtUriString?.toUri()
val currentUri = playbackStateHolder.stablePlayerState.value.currentSong?.albumArtUriString
themeStateHolder.extractAndGenerateColorScheme(uri, currentUri)
Expand Down
Loading