Skip to content

Commit 8c6c292

Browse files
authored
Fix current time range reset (#581)
1 parent 4226254 commit 8c6c292

File tree

2 files changed

+86
-22
lines changed

2 files changed

+86
-22
lines changed

pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/TimeRangeTracker.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,15 @@ internal class TimeRangeTracker(
8686

8787
listTrackers.forEach {
8888
pillarboxPlayer.removeListener(it)
89+
it.clear()
8990
}
9091
listTrackers.clear()
9192
}
9293
}
9394

9495
private sealed interface PlayerTimeRangeTracker<T : TimeRange> : PillarboxPlayer.Listener {
9596
fun createMessages(player: PillarboxExoPlayer): List<PlayerMessage>
97+
fun clear() {}
9698
}
9799

98100
private class ChapterCreditsTracker<T : TimeRange>(
@@ -118,15 +120,13 @@ private class ChapterCreditsTracker<T : TimeRange>(
118120
newPosition: Player.PositionInfo,
119121
@DiscontinuityReason reason: Int,
120122
) {
121-
if (
122-
(reason == Player.DISCONTINUITY_REASON_SEEK || reason == Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT) &&
123-
oldPosition.mediaItemIndex == newPosition.mediaItemIndex
124-
) {
125-
val currentPosition = oldPosition.positionMs
123+
if (oldPosition.mediaItemIndex != newPosition.mediaItemIndex) {
124+
clear()
125+
} else {
126+
val currentPosition = newPosition.positionMs
126127
val currentTimeRange = currentTimeRange
127128
?.takeIf { timeRange -> currentPosition in timeRange }
128129
?: timeRanges.firstOrNullAtPosition(currentPosition)
129-
130130
this.currentTimeRange = currentTimeRange
131131
}
132132
}
@@ -164,6 +164,10 @@ private class ChapterCreditsTracker<T : TimeRange>(
164164
return playerMessages
165165
}
166166

167+
override fun clear() {
168+
currentTimeRange = null
169+
}
170+
167171
private companion object {
168172
private const val TYPE_ENTER = 1
169173
private const val TYPE_EXIT = 2

pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/ChapterTrackerTest.kt

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import ch.srgssr.pillarbox.player.asset.timeRange.Chapter
2323
import ch.srgssr.pillarbox.player.source.PillarboxMediaSourceFactory
2424
import io.mockk.clearAllMocks
2525
import io.mockk.spyk
26+
import io.mockk.verify
2627
import io.mockk.verifyOrder
2728
import org.junit.runner.RunWith
2829
import kotlin.test.AfterTest
@@ -67,15 +68,13 @@ class ChapterTrackerTest {
6768
player.addMediaItem(ChapterAssetLoader.MEDIA_ITEM)
6869
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED)
6970

70-
val expectedChapters = listOf(ChapterAssetLoader.CHAPTER_1, ChapterAssetLoader.CHAPTER_2)
71-
val receivedChapters = mutableListOf<Chapter>()
72-
verifyOrder {
73-
listener.onChapterChanged(capture(receivedChapters))
74-
listener.onChapterChanged(null)
75-
listener.onChapterChanged(capture(receivedChapters))
76-
listener.onChapterChanged(null)
71+
val expectedChapters = listOf(ChapterAssetLoader.CHAPTER_1, null, ChapterAssetLoader.CHAPTER_2, null)
72+
val receivedChapters = mutableListOf<Chapter?>()
73+
verify {
74+
listener.onChapterChanged(captureNullable(receivedChapters))
7775
}
78-
assertEquals(expectedChapters, receivedChapters.reversed())
76+
assertEquals(expectedChapters.size, receivedChapters.size)
77+
assertEquals(expectedChapters, receivedChapters)
7978
}
8079

8180
@Test
@@ -97,6 +96,38 @@ class ChapterTrackerTest {
9796
}
9897
assertEquals(expectedChapters, receivedChapters.reversed())
9998
}
99+
100+
@Test
101+
fun `chapter transition after seek back`() {
102+
player.addMediaItem(ChapterAssetLoader.MEDIA_ITEM_WITH_CHAPTER)
103+
TestPlayerRunHelper.playUntilPosition(player, 0, ChapterAssetLoader.CHAPTER_3.start + 1_000L)
104+
player.seekBack()
105+
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED)
106+
107+
val expectedChapters = listOf(ChapterAssetLoader.CHAPTER_3, null, ChapterAssetLoader.CHAPTER_3, null, ChapterAssetLoader.CHAPTER_4, null)
108+
val receivedChapters = mutableListOf<Chapter?>()
109+
110+
verify {
111+
listener.onChapterChanged(captureNullable(receivedChapters))
112+
}
113+
assertEquals(expectedChapters, receivedChapters)
114+
}
115+
116+
@Test
117+
fun `chapter transition skip next`() {
118+
player.addMediaItems(listOf(ChapterAssetLoader.MEDIA_ITEM_WITH_CHAPTER, ChapterAssetLoader.NO_CHAPTER_MEDIA_ITEM))
119+
TestPlayerRunHelper.playUntilPosition(player, 0, ChapterAssetLoader.CHAPTER_3.start + 1_000L)
120+
player.seekToNext()
121+
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED)
122+
123+
val expectedChapters = listOf(ChapterAssetLoader.CHAPTER_3, null)
124+
val receivedChapters = mutableListOf<Chapter?>()
125+
126+
verify {
127+
listener.onChapterChanged(captureNullable(receivedChapters))
128+
}
129+
assertEquals(expectedChapters, receivedChapters)
130+
}
100131
}
101132

102133
private class ChapterAssetLoader(context: Context) : AssetLoader(DefaultMediaSourceFactory(context)) {
@@ -107,20 +138,49 @@ private class ChapterAssetLoader(context: Context) : AssetLoader(DefaultMediaSou
107138

108139
override suspend fun loadAsset(mediaItem: MediaItem): Asset {
109140
val itemBuilder = mediaItem.buildUpon()
110-
return Asset(
111-
mediaSource = mediaSourceFactory.createMediaSource(itemBuilder.build()),
112-
mediaMetadata = mediaItem.mediaMetadata,
113-
chapters = listOf(CHAPTER_1, CHAPTER_2)
114-
)
141+
val mediaSource = mediaSourceFactory.createMediaSource(itemBuilder.build())
142+
return when (mediaItem.mediaId) {
143+
ID_START_WITH_CHAPTER -> {
144+
Asset(
145+
mediaSource = mediaSource,
146+
mediaMetadata = mediaItem.mediaMetadata,
147+
chapters = listOf(CHAPTER_1, CHAPTER_2)
148+
)
149+
}
150+
151+
ID_WITH_CHAPTER -> {
152+
Asset(
153+
mediaSource = mediaSource,
154+
mediaMetadata = mediaItem.mediaMetadata,
155+
chapters = listOf(CHAPTER_3, CHAPTER_4)
156+
)
157+
}
158+
159+
else -> {
160+
Asset(
161+
mediaSource = mediaSource,
162+
mediaMetadata = mediaItem.mediaMetadata,
163+
chapters = emptyList()
164+
)
165+
}
166+
}
115167
}
116168

117169
companion object {
118170
private const val URL = "https://rts-vod-amd.akamaized.net/ww/13317145/f1d49f18-f302-37ce-866c-1c1c9b76a824/master.m3u8"
119-
val MEDIA_ITEM = MediaItem.fromUri(URL)
171+
const val ID_START_WITH_CHAPTER = "ID_START_WITH_CHAPTER"
172+
const val ID_WITH_CHAPTER = "ID_WITH_CHAPTER"
173+
174+
val MEDIA_ITEM = MediaItem.Builder().setMediaId(ID_START_WITH_CHAPTER).setUri(URL).build()
175+
val MEDIA_ITEM_WITH_CHAPTER = MediaItem.Builder().setMediaId(ID_WITH_CHAPTER).setUri(URL).build()
176+
val NO_CHAPTER_MEDIA_ITEM = MediaItem.Builder().setMediaId("NoChapter").setUri(URL).build()
120177

121178
const val NEAR_END_POSITION_MS = 15_000L // the video has 17 sec duration
122179

123-
val CHAPTER_1 = Chapter(id = "Chapter1", 0, 5, MediaMetadata.EMPTY)
124-
val CHAPTER_2 = Chapter(id = "Chapter1", 5, NEAR_END_POSITION_MS, MediaMetadata.EMPTY)
180+
val CHAPTER_1 = Chapter(id = "Chapter1", 0, 5_000L, MediaMetadata.EMPTY)
181+
val CHAPTER_2 = Chapter(id = "Chapter2", 5_000L, NEAR_END_POSITION_MS, MediaMetadata.EMPTY)
182+
183+
val CHAPTER_3 = Chapter(id = "Chapter3", 2_000L, 5_000L, MediaMetadata.EMPTY)
184+
val CHAPTER_4 = Chapter(id = "Chapter4", 10_000L, NEAR_END_POSITION_MS, MediaMetadata.EMPTY)
125185
}
126186
}

0 commit comments

Comments
 (0)