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

Follow skip forward/back length on ExoPlayer #441

Merged
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
2 changes: 2 additions & 0 deletions app/src/main/java/org/jellyfin/mobile/api/ApiModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.jellyfin.sdk.Jellyfin
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.KtorClient
import org.jellyfin.sdk.api.operations.ArtistsApi
import org.jellyfin.sdk.api.operations.DisplayPreferencesApi
import org.jellyfin.sdk.api.operations.GenresApi
import org.jellyfin.sdk.api.operations.HlsSegmentApi
import org.jellyfin.sdk.api.operations.ImageApi
Expand Down Expand Up @@ -49,6 +50,7 @@ val apiModule = module {
single { VideosApi(get()) }
single { UniversalAudioApi(get()) }
single { MediaInfoApi(get()) }
single { DisplayPreferencesApi(get()) }
single { PlayStateApi(get()) }
single { HlsSegmentApi(get()) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.jellyfin.mobile.model

import org.jellyfin.mobile.utils.Constants

data class DisplayPreferences(
val skipBackLength: Long = Constants.DEFAULT_SEEK_TIME_MS,
val skipForwardLength: Long = Constants.DEFAULT_SEEK_TIME_MS,
)
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,9 @@ class PlayerFragment : Fragment() {
fun isLandscape(configuration: Configuration = resources.configuration) =
configuration.orientation == Configuration.ORIENTATION_LANDSCAPE

fun onSeek(offsetMs: Long) {
viewModel.seekToOffset(offsetMs)
}
fun onRewind() = viewModel.rewind()

fun onFastForward() = viewModel.fastForward()

/**
* @return true if the audio track was changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,12 @@ class PlayerGestureHelper(
val viewHeight = playerView.measuredHeight
val viewCenterX = viewWidth / 2
val viewCenterY = viewHeight / 2
val fastForward = e.x.toInt() > viewCenterX
val isFastForward = e.x.toInt() > viewCenterX

// Show ripple effect
playerView.foreground?.apply {
val left = if (fastForward) viewCenterX else 0
val right = if (fastForward) viewWidth else viewCenterX
val left = if (isFastForward) viewCenterX else 0
val right = if (isFastForward) viewWidth else viewCenterX
setBounds(left, viewCenterY - viewCenterX / 2, right, viewCenterY + viewCenterX / 2)
setHotspot(e.x, e.y)
state = intArrayOf(android.R.attr.state_enabled, android.R.attr.state_pressed)
Expand All @@ -104,7 +104,7 @@ class PlayerGestureHelper(
}

// Fast-forward/rewind
fragment.onSeek(if (fastForward) Constants.DEFAULT_SEEK_TIME_MS else Constants.DEFAULT_SEEK_TIME_MS.unaryMinus())
with(fragment) { if (isFastForward) onFastForward() else onRewind() }

// Cancel previous runnable to not hide controller while seeking
playerView.removeCallbacks(hidePlayerViewControllerAction)
Expand Down
34 changes: 28 additions & 6 deletions app/src/main/java/org/jellyfin/mobile/player/PlayerViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.jellyfin.mobile.BuildConfig
import org.jellyfin.mobile.PLAYER_EVENT_CHANNEL
import org.jellyfin.mobile.model.DisplayPreferences
import org.jellyfin.mobile.player.source.JellyfinMediaSource
import org.jellyfin.mobile.player.source.MediaQueueManager
import org.jellyfin.mobile.utils.Constants
Expand All @@ -47,6 +48,7 @@ import org.jellyfin.mobile.utils.toMediaMetadata
import org.jellyfin.mobile.utils.width
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.jellyfin.sdk.api.operations.DisplayPreferencesApi
import org.jellyfin.sdk.api.operations.HlsSegmentApi
import org.jellyfin.sdk.api.operations.PlayStateApi
import org.jellyfin.sdk.model.api.PlayMethod
Expand All @@ -62,6 +64,7 @@ import java.util.concurrent.atomic.AtomicBoolean

class PlayerViewModel(application: Application) : AndroidViewModel(application), KoinComponent, Player.Listener {
private val apiClient by inject<ApiClient>()
private val displayPreferencesApi by inject<DisplayPreferencesApi>()
private val playStateApi by inject<PlayStateApi>()
private val hlsSegmentApi by inject<HlsSegmentApi>()

Expand Down Expand Up @@ -104,9 +107,32 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
}
private val mediaSessionCallback = PlayerMediaSessionCallback(this)

private var displayPreferences = DisplayPreferences()

init {
ProcessLifecycleOwner.get().lifecycle.addObserver(lifecycleObserver)

// Load display preferences
viewModelScope.launch {
try {
val displayPreferencesDto by displayPreferencesApi.getDisplayPreferences(
displayPreferencesId = Constants.DISPLAY_PREFERENCES_ID_USER_SETTINGS,
client = Constants.DISPLAY_PREFERENCES_CLIENT_EMBY,
)

val customPrefs = displayPreferencesDto.customPrefs

displayPreferences = DisplayPreferences(
skipBackLength = customPrefs?.get(Constants.DISPLAY_PREFERENCES_SKIP_BACK_LENGTH)?.toLongOrNull()
?: Constants.DEFAULT_SEEK_TIME_MS,
skipForwardLength = customPrefs?.get(Constants.DISPLAY_PREFERENCES_SKIP_FORWARD_LENGTH)?.toLongOrNull()
?: Constants.DEFAULT_SEEK_TIME_MS
)
} catch (e: ApiClientException) {
Timber.e(e, "Failed to load display preferences")
}
}

// Subscribe to player events from webapp
viewModelScope.launch {
for (event in playerEventChannel) {
Expand Down Expand Up @@ -296,16 +322,12 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
playerOrNull?.playWhenReady = false
}

fun seekToOffset(offsetMs: Long) {
playerOrNull?.seekToOffset(offsetMs)
}

fun rewind() {
seekToOffset(Constants.DEFAULT_SEEK_TIME_MS.unaryMinus())
playerOrNull?.seekToOffset(displayPreferences.skipBackLength.unaryMinus())
}

fun fastForward() {
seekToOffset(Constants.DEFAULT_SEEK_TIME_MS)
playerOrNull?.seekToOffset(displayPreferences.skipForwardLength)
}

fun skipToPrevious(force: Boolean = false) {
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/java/org/jellyfin/mobile/utils/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ object Constants {
const val DEFAULT_CONTROLS_TIMEOUT_MS = 2500
const val GESTURE_EXCLUSION_AREA_TOP = 48
const val DEFAULT_CENTER_OVERLAY_TIMEOUT_MS = 250
const val DEFAULT_SEEK_TIME_MS = 5000L
const val DISPLAY_PREFERENCES_ID_USER_SETTINGS = "usersettings"
const val DISPLAY_PREFERENCES_CLIENT_EMBY = "emby"
const val DISPLAY_PREFERENCES_SKIP_BACK_LENGTH = "skipBackLength"
const val DISPLAY_PREFERENCES_SKIP_FORWARD_LENGTH = "skipForwardLength"
const val DEFAULT_SEEK_TIME_MS = 10000L
const val MAX_SKIP_TO_PREV_MS = 3000L
const val DOUBLE_TAP_RIPPLE_DURATION_MS = 100L
const val FULL_SWIPE_RANGE_SCREEN_RATIO = 0.66f
Expand Down