Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to customize seek time #3794

Merged
merged 12 commits into from
Nov 10, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,16 @@ class CustomSeekProvider(
private val api: ApiClient,
private val context: Context,
private val trickPlayEnabled: Boolean,
private val forwardTime: Long
) : PlaybackSeekDataProvider() {
companion object {
private const val SEEK_LENGTH = 10000L
}

private val imageRequests = mutableMapOf<Int, Disposable>()

override fun getSeekPositions(): LongArray {
if (!videoPlayerAdapter.canSeek()) return LongArray(0)

val duration = videoPlayerAdapter.duration
val size = ceil(duration.toDouble() / SEEK_LENGTH.toDouble()).toInt() + 1
return LongArray(size) { i -> min(i * SEEK_LENGTH, duration) }
val size = ceil(duration.toDouble() / forwardTime.toDouble()).toInt() + 1
return LongArray(size) { i -> min(i * forwardTime, duration) }
}

override fun getThumbnail(index: Int, callback: ResultCallback) {
Expand All @@ -51,7 +48,7 @@ class CustomSeekProvider(
val trickPlayInfo = trickPlayResolutions?.values?.firstOrNull()
if (trickPlayInfo == null) return

val currentTimeMs = (index * SEEK_LENGTH).coerceIn(0, videoPlayerAdapter.duration)
val currentTimeMs = (index * forwardTime).coerceIn(0, videoPlayerAdapter.duration)
val currentTile = currentTimeMs.floorDiv(trickPlayInfo.interval).toInt()

val tileSize = trickPlayInfo.tileWidth * trickPlayInfo.tileHeight
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import androidx.leanback.app.PlaybackSupportFragment;

import org.jellyfin.androidtv.preference.UserPreferences;
import org.jellyfin.androidtv.preference.UserSettingPreferences;
import org.jellyfin.androidtv.ui.playback.CustomPlaybackOverlayFragment;
import org.jellyfin.androidtv.ui.playback.PlaybackController;
import org.jellyfin.androidtv.ui.playback.PlaybackControllerContainer;
Expand All @@ -24,6 +25,7 @@ public class LeanbackOverlayFragment extends PlaybackSupportFragment {
private VideoPlayerAdapter playerAdapter;
private boolean shouldShowOverlay = true;
private Lazy<PlaybackControllerContainer> playbackControllerContainer = inject(PlaybackControllerContainer.class);
private final Lazy<UserSettingPreferences> userSettingPreferences = inject(UserSettingPreferences.class);
private Lazy<ImageLoader> imageLoader = inject(ImageLoader.class);
private Lazy<ApiClient> api = inject(ApiClient.class);
private Lazy<UserPreferences> userPreferences = inject(UserPreferences.class);
Expand Down Expand Up @@ -99,8 +101,10 @@ public void mediaInfoChanged() {

playerGlue.invalidatePlaybackControls();
playerGlue.setSeekEnabled(playerAdapter.canSeek());

long skipForwardLength = userSettingPreferences.getValue().get(UserSettingPreferences.Companion.getSkipForwardLength()).longValue();
boolean enableTrickPlay = userPreferences.getValue().get(UserPreferences.Companion.getTrickPlayEnabled());
playerGlue.setSeekProvider(playerAdapter.canSeek() ? new CustomSeekProvider(playerAdapter, imageLoader.getValue(), api.getValue(), requireContext(), enableTrickPlay) : null);
playerGlue.setSeekProvider(playerAdapter.canSeek() ? new CustomSeekProvider(playerAdapter, imageLoader.getValue(), api.getValue(), requireContext(), enableTrickPlay, skipForwardLength) : null);
recordingStateChanged();
playerAdapter.updateDuration();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ class OptionsItemSeekbar(
it.isEnabled = dependencyCheckFun() && enabled
it.isVisible = visible
it.title = title
it.min = min
// Max must be set before min because the setter of min checks if the value is below the current value of max
it.max = max
it.min = min
it.seekBarIncrement = increment
it.value = binder.get()
it.showSeekBarValue = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.jellyfin.androidtv.ui.preference.screen

import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.preference.UserPreferences
import org.jellyfin.androidtv.preference.UserSettingPreferences
import org.jellyfin.androidtv.preference.constant.AudioBehavior
import org.jellyfin.androidtv.preference.constant.NEXTUP_TIMER_DISABLED
import org.jellyfin.androidtv.preference.constant.NextUpBehavior
Expand All @@ -15,14 +16,19 @@ import org.jellyfin.androidtv.ui.preference.dsl.enum
import org.jellyfin.androidtv.ui.preference.dsl.link
import org.jellyfin.androidtv.ui.preference.dsl.optionsScreen
import org.jellyfin.androidtv.ui.preference.dsl.seekbar
import org.jellyfin.preference.store.PreferenceStore
import org.jellyfin.sdk.model.api.MediaSegmentType
import org.koin.android.ext.android.inject
import kotlin.math.roundToInt

class PlaybackPreferencesScreen : OptionsFragment() {
private val userPreferences: UserPreferences by inject()
private val userSettingPreferences: UserSettingPreferences by inject()
private val mediaSegmentRepository: MediaSegmentRepository by inject()

override val stores: Array<PreferenceStore<*, *>>
get() = arrayOf(userSettingPreferences)

override val screen by optionsScreen {
setTitle(R.string.pref_playback)

Expand Down Expand Up @@ -60,6 +66,19 @@ class PlaybackPreferencesScreen : OptionsFragment() {
setContent(R.string.sum_enable_cinema_mode)
bind(userPreferences, UserPreferences.cinemaModeEnabled)
}

@Suppress("MagicNumber")
seekbar {
setTitle(R.string.skip_forward_length)
setContent(R.string.skip_forward_length)
min = 5_000
max = 30_000
increment = 5_000
valueFormatter = object : DurationSeekBarPreference.ValueFormatter() {
override fun display(value: Int) = "${value / 1000}s"
}
bind(userSettingPreferences, UserSettingPreferences.skipForwardLength)
}
}

category {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@
<string name="segment_type_preview">Previews</string>
<string name="segment_type_recap">Recaps</string>
<string name="segment_type_unknown">Unknown segments</string>
<string name="skip_forward_length">Skip forward length</string>
<string name="preference_enable_trickplay">Enable trickplay in video player</string>
<plurals name="seconds">
<item quantity="one">%1$s second</item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class CustomSeekProviderTests : FunSpec({
every { canSeek() } returns true
every { duration } returns 30000L
}
val customSeekProvider = CustomSeekProvider(videoPlayerAdapter, mockk(), mockk(), mockk(), false)
val customSeekProvider = CustomSeekProvider(videoPlayerAdapter, mockk(), mockk(), mockk(), false, 10_000)

customSeekProvider.seekPositions shouldBe arrayOf(0L, 10000L, 20000L, 30000L)
}
Expand All @@ -21,7 +21,7 @@ class CustomSeekProviderTests : FunSpec({
every { canSeek() } returns true
every { duration } returns 45000L
}
val customSeekProvider = CustomSeekProvider(videoPlayerAdapter, mockk(), mockk(), mockk(), false)
val customSeekProvider = CustomSeekProvider(videoPlayerAdapter, mockk(), mockk(), mockk(), false, 10_000)

customSeekProvider.seekPositions shouldBe arrayOf(0L, 10000, 20000, 30000, 40000, 45000)
}
Expand All @@ -30,7 +30,7 @@ class CustomSeekProviderTests : FunSpec({
val videoPlayerAdapter = mockk<VideoPlayerAdapter> {
every { canSeek() } returns false
}
val customSeekProvider = CustomSeekProvider(videoPlayerAdapter, mockk(), mockk(), mockk(), false)
val customSeekProvider = CustomSeekProvider(videoPlayerAdapter, mockk(), mockk(), mockk(), false, 10_000)

customSeekProvider.seekPositions.size shouldBe 0
}
Expand Down
Loading