Skip to content

Commit 2523b12

Browse files
committed
[APT-10727] Fix seek handling
Previously, the progress position was not updating after a seek until the next playback poll. This caused problems with consumers attempting to track listening because it was not possible to accurately detect a seek. This now updates the position when the seek finishes, rather than waiting for the next poll for the position to catch up. Also updates Reducer tests to ensure that we are chaining events properly.
1 parent ccb133e commit 2523b12

File tree

6 files changed

+39
-19
lines changed

6 files changed

+39
-19
lines changed

Armadillo/src/main/java/com/scribd/armadillo/Reducer.kt

+5-2
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,12 @@ internal object Reducer {
195195
seekTarget = action.seekPositionTarget)
196196
} else MediaControlState()
197197

198+
// When we finish a seek, update our progress to the target
199+
val newProgress = if (action.isSeeking) { playbackInfo.progress.positionInDuration } else { action.seekPositionTarget }
198200
oldState.copy(playbackInfo = playbackInfo.copy(
199-
controlState = controlState))
200-
.apply { debugState = newDebug }
201+
controlState = controlState,
202+
progress = playbackInfo.progress.copy(positionInDuration = newProgress)
203+
)).apply { debugState = newDebug }
201204
}
202205

203206
is FastForwardAction -> {

Armadillo/src/main/java/com/scribd/armadillo/actions/Action.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ internal data class UpdateProgressAction(val isUpdated: Boolean, val currentChap
5858
// Media Control State updates - updates control info in ArmadilloState / exposes player intent to users
5959

6060
/** For a beginning or end of a seek (discontinuity). when isSeeking is becoming false, the seek action is resolved. */
61-
internal data class SeekAction(val isSeeking: Boolean, val seekPositionTarget: Milliseconds?) : Action {
61+
internal data class SeekAction(val isSeeking: Boolean, val seekPositionTarget: Milliseconds) : Action {
6262
override val name = "Seeking: $isSeeking"
6363
}
6464

Armadillo/src/main/java/com/scribd/armadillo/playback/PlayerEventListener.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.scribd.armadillo.actions.SeekAction
1616
import com.scribd.armadillo.actions.UpdateProgressAction
1717
import com.scribd.armadillo.di.Injector
1818
import com.scribd.armadillo.models.PlaybackState
19+
import com.scribd.armadillo.time.milliseconds
1920
import javax.inject.Inject
2021

2122
/**
@@ -46,9 +47,9 @@ internal class PlayerEventListener @Inject constructor(private val context: Cont
4647
stateModifier.dispatch(LoadingAction(isLoading))
4748
}
4849

49-
override fun onPositionDiscontinuity(reason: Int) {
50+
override fun onPositionDiscontinuity(oldPosition: Player.PositionInfo, newPosition: Player.PositionInfo, reason: Int) {
5051
Log.v(TAG, "onPositionDiscontinuity --- reason: $reason")
51-
stateModifier.dispatch(SeekAction(false, null))
52+
stateModifier.dispatch(SeekAction(false, newPosition.contentPositionMs.milliseconds))
5253
}
5354

5455
override fun onRepeatModeChanged(repeatMode: Int) = Unit

Armadillo/src/test/java/com/scribd/armadillo/ReducerTest.kt

+25-13
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import com.scribd.armadillo.models.DownloadState
3030
import com.scribd.armadillo.models.DrmState
3131
import com.scribd.armadillo.models.DrmType
3232
import com.scribd.armadillo.models.InternalState
33+
import com.scribd.armadillo.models.MediaControlState
3334
import com.scribd.armadillo.models.PlaybackProgress
3435
import com.scribd.armadillo.models.PlaybackState
3536
import com.scribd.armadillo.time.milliseconds
@@ -157,7 +158,7 @@ class ReducerTest {
157158
val identicalActions = listOf(MockModels.progressAction(), MockModels.progressAction())
158159
var appState2 = MockModels.appState()
159160
identicalActions.forEach {
160-
appState2 = Reducer.reduce(appState2, it)
161+
appState2 = Reducer.reduce(appState1, it)
161162
}
162163

163164
assertThat(appState1).isEqualTo(appState2)
@@ -200,8 +201,8 @@ class ReducerTest {
200201

201202
@Test
202203
fun reduce_skipNext_clearsOtherSeekControls() {
203-
Reducer.reduce(MockModels.appState(), SkipPrevAction(100.milliseconds))
204-
val secondSeek = Reducer.reduce(MockModels.appState(), SkipNextAction(4000.milliseconds))
204+
val firstState = Reducer.reduce(MockModels.appState(), SkipPrevAction(100.milliseconds))
205+
val secondSeek = Reducer.reduce(firstState, SkipNextAction(4000.milliseconds))
205206

206207
assertThat(secondSeek.playbackInfo?.controlState?.isFastForwarding ?: true).isFalse
207208
assertThat(secondSeek.playbackInfo?.controlState?.isRewinding ?: true).isFalse
@@ -212,8 +213,8 @@ class ReducerTest {
212213

213214
@Test
214215
fun reduce_skipPrev_clearsOtherSeekControls() {
215-
Reducer.reduce(MockModels.appState(), SkipNextAction(100.milliseconds))
216-
val secondSeek = Reducer.reduce(MockModels.appState(), SkipPrevAction(4000.milliseconds))
216+
val firstState = Reducer.reduce(MockModels.appState(), SkipNextAction(100.milliseconds))
217+
val secondSeek = Reducer.reduce(firstState, SkipPrevAction(4000.milliseconds))
217218

218219
assertThat(secondSeek.playbackInfo?.controlState?.isFastForwarding ?: true).isFalse
219220
assertThat(secondSeek.playbackInfo?.controlState?.isRewinding ?: true).isFalse
@@ -224,8 +225,8 @@ class ReducerTest {
224225

225226
@Test
226227
fun reduce_fastForward_clearsOtherSeekControls() {
227-
Reducer.reduce(MockModels.appState(), RewindAction(100.milliseconds))
228-
val secondSeek = Reducer.reduce(MockModels.appState(), FastForwardAction(4000.milliseconds))
228+
val firstState = Reducer.reduce(MockModels.appState(), RewindAction(100.milliseconds))
229+
val secondSeek = Reducer.reduce(firstState, FastForwardAction(4000.milliseconds))
229230

230231
assertThat(secondSeek.playbackInfo?.controlState?.isPrevChapter ?: true).isFalse
231232
assertThat(secondSeek.playbackInfo?.controlState?.isRewinding ?: true).isFalse
@@ -236,8 +237,8 @@ class ReducerTest {
236237

237238
@Test
238239
fun reduce_rewind_clearsOtherSeekControls() {
239-
Reducer.reduce(MockModels.appState(), FastForwardAction(100.milliseconds))
240-
val secondSeek = Reducer.reduce(MockModels.appState(), RewindAction(4000.milliseconds))
240+
val firstState = Reducer.reduce(MockModels.appState(), FastForwardAction(100.milliseconds))
241+
val secondSeek = Reducer.reduce(firstState, RewindAction(4000.milliseconds))
241242

242243
assertThat(secondSeek.playbackInfo?.controlState?.isPrevChapter ?: true).isFalse
243244
assertThat(secondSeek.playbackInfo?.controlState?.isFastForwarding ?: true).isFalse
@@ -246,22 +247,33 @@ class ReducerTest {
246247
assertThat(secondSeek.playbackInfo?.controlState?.isSeeking).isTrue
247248
}
248249

250+
@Test
251+
fun reduce_seekStart_setsSeekControls() {
252+
val initialState = MockModels.appState()
253+
val seekState = Reducer.reduce(initialState, SeekAction(true, 300.milliseconds))
254+
255+
assertThat(seekState.playbackInfo?.progress).isEqualTo(initialState.playbackInfo?.progress)
256+
assertThat(seekState.playbackInfo?.controlState?.isSeeking).isTrue
257+
assertThat(seekState.playbackInfo?.controlState?.seekTarget).isEqualTo(300.milliseconds)
258+
}
259+
249260
@Test
250261
fun reduce_seekFinish_allSeekControlsFalse() {
251-
Reducer.reduce(MockModels.appState(), FastForwardAction(100.milliseconds))
252-
val secondSeek = Reducer.reduce(MockModels.appState(), SeekAction(false, null))
262+
val firstState = Reducer.reduce(MockModels.appState(), FastForwardAction(100.milliseconds))
263+
val secondSeek = Reducer.reduce(firstState, SeekAction(false, 300.milliseconds))
253264

254265
assertThat(secondSeek.playbackInfo?.controlState?.isFastForwarding ?: true).isFalse
255266
assertThat(secondSeek.playbackInfo?.controlState?.isRewinding ?: true).isFalse
256267
assertThat(secondSeek.playbackInfo?.controlState?.isPrevChapter ?: true).isFalse
257268
assertThat(secondSeek.playbackInfo?.controlState?.isNextChapter ?: true).isFalse
258269
assertThat(secondSeek.playbackInfo?.controlState?.isSeeking).isFalse
270+
assertThat(secondSeek.playbackInfo?.progress?.positionInDuration).isEqualTo(300.milliseconds)
259271
}
260272

261273
@Test
262274
fun reduce_playbackStopDuringSeek_clearSeekControls() {
263-
Reducer.reduce(MockModels.appState(), FastForwardAction(100000.milliseconds))
264-
val state = Reducer.reduce(MockModels.appState(), PlayerStateAction(PlaybackState.NONE))
275+
val firstState = Reducer.reduce(MockModels.appState(), FastForwardAction(100000.milliseconds))
276+
val state = Reducer.reduce(firstState, PlayerStateAction(PlaybackState.NONE))
265277

266278
assertThat(state.playbackInfo?.controlState?.isStopping).isTrue
267279
assertThat(state.playbackInfo?.controlState?.isFastForwarding ?: true).isFalse

RELEASE.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Project Armadillo Release Notes
22

3+
## 1.7.0
4+
- Fixes the playback progress being incorrect between a seek finishing and the next playback update. The progress position now updates
5+
immediately to the seek target when the seek finishes
6+
37
## 1.6.9
48
- Improves the crash fix from 1.6.8, so that even retries on EncryptedSharedPreferences does not crash.
59

gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ org.gradle.jvmargs=-Xmx1536m
1313
# org.gradle.parallel=true
1414
PACKAGE_NAME=com.scribd.armadillo
1515
GRADLE_PLUGIN_VERSION=7.2.0
16-
LIBRARY_VERSION=1.6.9
16+
LIBRARY_VERSION=1.7.0
1717
EXOPLAYER_VERSION=2.19.1
1818
RXJAVA_VERSION=2.2.4
1919
RXANDROID_VERSION=2.0.1

0 commit comments

Comments
 (0)