mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Refactor PlaybackViewModel to handle generic playlists
This commit is contained in:
parent
3906eb196a
commit
f737818873
5 changed files with 314 additions and 185 deletions
|
|
@ -0,0 +1,74 @@
|
|||
package com.github.damontecres.dolphin.data.model
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import com.github.damontecres.dolphin.ui.DefaultItemFields
|
||||
import com.github.damontecres.dolphin.ui.indexOfFirstOrNull
|
||||
import com.github.damontecres.dolphin.util.GetEpisodesRequestHandler
|
||||
import com.github.damontecres.dolphin.util.GetPlaylistItemsRequestHandler
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.model.api.request.GetEpisodesRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetPlaylistItemsRequest
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
class Playlist(
|
||||
items: List<BaseItem>,
|
||||
startIndex: Int = 0,
|
||||
) {
|
||||
val items = items.subList(startIndex, items.size)
|
||||
|
||||
var index by mutableIntStateOf(0)
|
||||
|
||||
fun hasPrevious(): Boolean = index > 0
|
||||
|
||||
fun hasNext(): Boolean = index < items.size
|
||||
|
||||
fun getPreviousAndReverse(): BaseItem = items[--index]
|
||||
|
||||
fun getAndAdvance(): BaseItem = items[++index]
|
||||
|
||||
fun peek(): BaseItem? = items.getOrNull(index + 1)
|
||||
|
||||
companion object {
|
||||
const val MAX_SIZE = 100
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class PlaylistCreator
|
||||
@Inject
|
||||
constructor(
|
||||
private val api: ApiClient,
|
||||
) {
|
||||
suspend fun createFromEpisode(
|
||||
seriesId: UUID,
|
||||
episodeId: UUID,
|
||||
): Playlist {
|
||||
val request =
|
||||
GetEpisodesRequest(
|
||||
seriesId = seriesId,
|
||||
fields = DefaultItemFields,
|
||||
startItemId = episodeId,
|
||||
limit = Playlist.MAX_SIZE,
|
||||
)
|
||||
val episodes = GetEpisodesRequestHandler.execute(api, request).content.items
|
||||
val startIndex =
|
||||
episodes.indexOfFirstOrNull { it.id == episodeId }
|
||||
?: throw IllegalStateException("Episode $episodeId was not returned")
|
||||
return Playlist(episodes.map { BaseItem.from(it, api) }, startIndex)
|
||||
}
|
||||
|
||||
suspend fun createFromPlaylistId(playlistId: UUID): Playlist {
|
||||
val request =
|
||||
GetPlaylistItemsRequest(
|
||||
playlistId = playlistId,
|
||||
fields = DefaultItemFields,
|
||||
limit = Playlist.MAX_SIZE,
|
||||
)
|
||||
val items = GetPlaylistItemsRequestHandler.execute(api, request).content.items
|
||||
return Playlist(items.map { BaseItem.from(it, api) }, 0)
|
||||
}
|
||||
}
|
||||
|
|
@ -91,6 +91,10 @@ sealed interface PlaybackAction {
|
|||
data class Scale(
|
||||
val scale: ContentScale,
|
||||
) : PlaybackAction
|
||||
|
||||
data object Previous : PlaybackAction
|
||||
|
||||
data object Next : PlaybackAction
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
|
|
@ -174,6 +178,7 @@ fun PlaybackControls(
|
|||
player = playerControls,
|
||||
initialFocusRequester = initialFocusRequester,
|
||||
onControllerInteraction = onControllerInteraction,
|
||||
onPlaybackActionClick = onPlaybackActionClick,
|
||||
showPlay = showPlay,
|
||||
previousEnabled = previousEnabled,
|
||||
nextEnabled = nextEnabled,
|
||||
|
|
@ -443,6 +448,7 @@ fun PlaybackButtons(
|
|||
player: Player,
|
||||
initialFocusRequester: FocusRequester,
|
||||
onControllerInteraction: () -> Unit,
|
||||
onPlaybackActionClick: (PlaybackAction) -> Unit,
|
||||
showPlay: Boolean,
|
||||
previousEnabled: Boolean,
|
||||
nextEnabled: Boolean,
|
||||
|
|
@ -459,7 +465,7 @@ fun PlaybackButtons(
|
|||
iconRes = R.drawable.baseline_skip_previous_24,
|
||||
onClick = {
|
||||
onControllerInteraction.invoke()
|
||||
player.seekToPrevious()
|
||||
onPlaybackActionClick.invoke(PlaybackAction.Previous)
|
||||
},
|
||||
enabled = previousEnabled,
|
||||
onControllerInteraction = onControllerInteraction,
|
||||
|
|
@ -500,7 +506,7 @@ fun PlaybackButtons(
|
|||
iconRes = R.drawable.baseline_skip_next_24,
|
||||
onClick = {
|
||||
onControllerInteraction.invoke()
|
||||
player.seekToNext()
|
||||
onPlaybackActionClick.invoke(PlaybackAction.Next)
|
||||
},
|
||||
enabled = nextEnabled,
|
||||
onControllerInteraction = onControllerInteraction,
|
||||
|
|
|
|||
|
|
@ -50,14 +50,13 @@ import androidx.media3.ui.SubtitleView
|
|||
import androidx.media3.ui.compose.PlayerSurface
|
||||
import androidx.media3.ui.compose.SURFACE_TYPE_SURFACE_VIEW
|
||||
import androidx.media3.ui.compose.modifiers.resizeWithContentScale
|
||||
import androidx.media3.ui.compose.state.rememberNextButtonState
|
||||
import androidx.media3.ui.compose.state.rememberPlayPauseButtonState
|
||||
import androidx.media3.ui.compose.state.rememberPresentationState
|
||||
import androidx.media3.ui.compose.state.rememberPreviousButtonState
|
||||
import androidx.tv.material3.Button
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.dolphin.data.model.Playlist
|
||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||
import com.github.damontecres.dolphin.preferences.skipBackOnResume
|
||||
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
|
||||
|
|
@ -105,7 +104,8 @@ fun PlaybackPage(
|
|||
var cues by remember { mutableStateOf<List<Cue>>(listOf()) }
|
||||
var showDebugInfo by remember { mutableStateOf(prefs.showDebugInfo) }
|
||||
|
||||
val nextUp by viewModel.nextUpEpisode.observeAsState(null)
|
||||
val nextUp by viewModel.nextUp.observeAsState(null)
|
||||
val playlist by viewModel.playlist.observeAsState(Playlist(listOf()))
|
||||
|
||||
// TODO move to viewmodel?
|
||||
val cueListener =
|
||||
|
|
@ -138,8 +138,6 @@ fun PlaybackPage(
|
|||
Modifier.resizeWithContentScale(contentScale, presentationState.videoSizeDp)
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val playPauseState = rememberPlayPauseButtonState(player)
|
||||
val previousState = rememberPreviousButtonState(player)
|
||||
val nextState = rememberNextButtonState(player)
|
||||
val seekBarState = rememberSeekBarState(player, scope)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
|
|
@ -261,8 +259,8 @@ fun PlaybackPage(
|
|||
playerControls = player,
|
||||
controllerViewState = controllerViewState,
|
||||
showPlay = playPauseState.showPlay,
|
||||
previousEnabled = previousState.isEnabled,
|
||||
nextEnabled = nextState.isEnabled,
|
||||
previousEnabled = true,
|
||||
nextEnabled = playlist.hasNext(),
|
||||
seekEnabled = true,
|
||||
seekForward = preferences.appPreferences.playbackPreferences.skipForwardMs.milliseconds,
|
||||
seekBack = preferences.appPreferences.playbackPreferences.skipBackMs.milliseconds,
|
||||
|
|
@ -290,6 +288,20 @@ fun PlaybackPage(
|
|||
is PlaybackAction.ToggleCaptions -> {
|
||||
viewModel.changeSubtitleStream(it.index)
|
||||
}
|
||||
|
||||
PlaybackAction.Next -> {
|
||||
// TODO focus is lost
|
||||
viewModel.playUpNextUp()
|
||||
}
|
||||
|
||||
PlaybackAction.Previous -> {
|
||||
val pos = player.currentPosition
|
||||
if (pos < player.maxSeekToPreviousPosition && playlist.hasPrevious()) {
|
||||
viewModel.playPrevious()
|
||||
} else {
|
||||
player.seekToPrevious()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onSeekBarChange = seekBarState::onValueChange,
|
||||
|
|
@ -381,14 +393,14 @@ fun PlaybackPage(
|
|||
if (autoPlayEnabled) {
|
||||
LaunchedEffect(Unit) {
|
||||
if (timeLeft == 0L) {
|
||||
viewModel.playUpNextEpisode()
|
||||
viewModel.playUpNextUp()
|
||||
} else {
|
||||
while (timeLeft > 0) {
|
||||
delay(1.seconds)
|
||||
timeLeft--
|
||||
}
|
||||
if (timeLeft == 0L && autoPlayEnabled) {
|
||||
viewModel.playUpNextEpisode()
|
||||
viewModel.playUpNextUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -402,7 +414,7 @@ fun PlaybackPage(
|
|||
description = it.data.overview,
|
||||
imageUrl = it.imageUrl,
|
||||
aspectRatio = it.data.primaryImageAspectRatio?.toFloat() ?: (16f / 9),
|
||||
onClick = { viewModel.playUpNextEpisode() },
|
||||
onClick = { viewModel.playUpNextUp() },
|
||||
timeLeft = if (autoPlayEnabled) timeLeft.seconds else null,
|
||||
modifier =
|
||||
Modifier
|
||||
|
|
|
|||
|
|
@ -16,17 +16,15 @@ import androidx.media3.common.util.UnstableApi
|
|||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||
import com.github.damontecres.dolphin.data.model.Chapter
|
||||
import com.github.damontecres.dolphin.data.model.Playlist
|
||||
import com.github.damontecres.dolphin.data.model.PlaylistCreator
|
||||
import com.github.damontecres.dolphin.preferences.AppPreference
|
||||
import com.github.damontecres.dolphin.preferences.SkipSegmentBehavior
|
||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||
import com.github.damontecres.dolphin.ui.DefaultItemFields
|
||||
import com.github.damontecres.dolphin.ui.indexOfFirstOrNull
|
||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.dolphin.util.ApiRequestPager
|
||||
import com.github.damontecres.dolphin.util.EqualityMutableLiveData
|
||||
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||
import com.github.damontecres.dolphin.util.GetEpisodesRequestHandler
|
||||
import com.github.damontecres.dolphin.util.TrackActivityPlaybackListener
|
||||
import com.github.damontecres.dolphin.util.TrackSupport
|
||||
import com.github.damontecres.dolphin.util.checkForSupport
|
||||
|
|
@ -58,7 +56,6 @@ import org.jellyfin.sdk.model.api.PlayMethod
|
|||
import org.jellyfin.sdk.model.api.PlaybackInfoDto
|
||||
import org.jellyfin.sdk.model.api.SubtitlePlaybackMode
|
||||
import org.jellyfin.sdk.model.api.TrickplayInfo
|
||||
import org.jellyfin.sdk.model.api.request.GetEpisodesRequest
|
||||
import org.jellyfin.sdk.model.extensions.inWholeTicks
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
|
|
@ -86,6 +83,7 @@ class PlaybackViewModel
|
|||
constructor(
|
||||
@ApplicationContext context: Context,
|
||||
val api: ApiClient,
|
||||
val playlistCreator: PlaylistCreator,
|
||||
val navigationManager: NavigationManager,
|
||||
) : ViewModel() {
|
||||
val player: ExoPlayer =
|
||||
|
|
@ -114,9 +112,10 @@ class PlaybackViewModel
|
|||
private lateinit var dto: BaseItemDto
|
||||
private var activityListener: TrackActivityPlaybackListener? = null
|
||||
|
||||
private val episodes = MutableLiveData<ApiRequestPager<GetEpisodesRequest>>()
|
||||
private var currentEpisodeIndex = Int.MAX_VALUE
|
||||
val nextUpEpisode = MutableLiveData<BaseItem?>()
|
||||
val nextUp = MutableLiveData<BaseItem?>()
|
||||
private var isPlaylist = false
|
||||
|
||||
val playlist = MutableLiveData<Playlist>(Playlist(listOf()))
|
||||
|
||||
init {
|
||||
addCloseable { this@PlaybackViewModel.activityListener?.release() }
|
||||
|
|
@ -128,14 +127,45 @@ class PlaybackViewModel
|
|||
deviceProfile: DeviceProfile,
|
||||
preferences: UserPreferences,
|
||||
) {
|
||||
nextUpEpisode.value = null
|
||||
nextUp.value = null
|
||||
this.preferences = preferences
|
||||
this.deviceProfile = deviceProfile
|
||||
val itemId = destination.itemId
|
||||
this.itemId = itemId
|
||||
val item = destination.item
|
||||
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
val base = item?.data ?: api.userLibraryApi.getItem(itemId).content
|
||||
val queriedItem = item?.data ?: api.userLibraryApi.getItem(itemId).content
|
||||
val base =
|
||||
if (queriedItem.type == BaseItemKind.PLAYLIST) {
|
||||
isPlaylist = true
|
||||
val playlist = playlistCreator.createFromPlaylistId(queriedItem.id)
|
||||
withContext(Dispatchers.Main) {
|
||||
this@PlaybackViewModel.playlist.value = playlist
|
||||
}
|
||||
// TODO start index
|
||||
playlist.items.first().data
|
||||
} else {
|
||||
queriedItem
|
||||
}
|
||||
|
||||
play(base, destination.positionMs)
|
||||
|
||||
if (!isPlaylist && queriedItem.type == BaseItemKind.EPISODE) {
|
||||
val playlist =
|
||||
playlistCreator.createFromEpisode(queriedItem.seriesId!!, queriedItem.id)
|
||||
withContext(Dispatchers.Main) {
|
||||
this@PlaybackViewModel.playlist.value = playlist
|
||||
}
|
||||
}
|
||||
maybeSetupPlaylistListener()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun play(
|
||||
base: BaseItemDto,
|
||||
positionMs: Long,
|
||||
) = withContext(Dispatchers.IO) {
|
||||
Timber.i("Playing ${base.id}")
|
||||
dto = base
|
||||
val title =
|
||||
if (base.type == BaseItemKind.EPISODE) {
|
||||
|
|
@ -249,30 +279,27 @@ class PlaybackViewModel
|
|||
this@PlaybackViewModel.subtitleStreams.value = subtitleStreams
|
||||
|
||||
changeStreams(
|
||||
itemId,
|
||||
base,
|
||||
audioIndex,
|
||||
subtitleIndex,
|
||||
if (destination.positionMs > 0) destination.positionMs else C.TIME_UNSET,
|
||||
if (positionMs > 0) positionMs else C.TIME_UNSET,
|
||||
)
|
||||
player.prepare()
|
||||
|
||||
this@PlaybackViewModel.chapters.value = Chapter.fromDto(dto, api)
|
||||
Timber.v("chapters=${this@PlaybackViewModel.chapters.value}")
|
||||
}
|
||||
if (base.type == BaseItemKind.EPISODE) {
|
||||
base.seriesId?.let(::getEpisodes)
|
||||
this@PlaybackViewModel.chapters.value = Chapter.fromDto(base, api)
|
||||
Timber.v("chapters=${this@PlaybackViewModel.chapters.value?.size}")
|
||||
}
|
||||
listenForSegments()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
private suspend fun changeStreams(
|
||||
itemId: UUID,
|
||||
item: BaseItemDto,
|
||||
audioIndex: Int?,
|
||||
subtitleIndex: Int?,
|
||||
positionMs: Long = C.TIME_UNSET,
|
||||
) {
|
||||
val itemId = item.id
|
||||
if (currentPlayback.value?.let {
|
||||
it.itemId == itemId &&
|
||||
it.audioIndex == audioIndex &&
|
||||
|
|
@ -416,7 +443,7 @@ class PlaybackViewModel
|
|||
}
|
||||
}
|
||||
val trickPlayInfo =
|
||||
dto.trickplay
|
||||
item.trickplay
|
||||
?.get(source.id)
|
||||
?.values
|
||||
?.firstOrNull()
|
||||
|
|
@ -431,7 +458,7 @@ class PlaybackViewModel
|
|||
val itemId = currentPlayback.value?.itemId ?: return
|
||||
viewModelScope.launch(ExceptionHandler()) {
|
||||
changeStreams(
|
||||
itemId,
|
||||
dto,
|
||||
index,
|
||||
currentPlayback.value?.subtitleIndex,
|
||||
player.currentPosition,
|
||||
|
|
@ -443,7 +470,7 @@ class PlaybackViewModel
|
|||
val itemId = currentPlayback.value?.itemId ?: return
|
||||
viewModelScope.launch(ExceptionHandler()) {
|
||||
changeStreams(
|
||||
itemId,
|
||||
dto,
|
||||
currentPlayback.value?.audioIndex,
|
||||
index,
|
||||
player.currentPosition,
|
||||
|
|
@ -463,56 +490,26 @@ class PlaybackViewModel
|
|||
)
|
||||
}
|
||||
|
||||
fun getEpisodes(seriesId: UUID) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val request =
|
||||
GetEpisodesRequest(
|
||||
seriesId = seriesId,
|
||||
fields = DefaultItemFields,
|
||||
startItemId = itemId,
|
||||
limit = 2,
|
||||
)
|
||||
val episodes = GetEpisodesRequestHandler.execute(api, request).content.items
|
||||
val currentEpisodeIndex = episodes.indexOfFirstOrNull { it.id == itemId }
|
||||
Timber.v("Current episode is $currentEpisodeIndex of ${episodes.size}")
|
||||
if (currentEpisodeIndex != null) {
|
||||
val nextIndex = currentEpisodeIndex + 1
|
||||
if (nextIndex < episodes.size) {
|
||||
private fun maybeSetupPlaylistListener() {
|
||||
playlist.value?.let { playlist ->
|
||||
if (playlist.hasNext()) {
|
||||
Timber.v("Adding lister for playlist with ${playlist.items.size} items")
|
||||
val listener =
|
||||
object : Player.Listener {
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
if (playbackState == Player.STATE_ENDED) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val nextItem = BaseItem.from(episodes[nextIndex], api)
|
||||
Timber.v("Setting next up episode to ${nextItem.id}")
|
||||
viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||
val nextItem = playlist.peek()
|
||||
Timber.v("Setting next up to ${nextItem?.id}")
|
||||
withContext(Dispatchers.Main) {
|
||||
nextUpEpisode.value = nextItem
|
||||
nextUp.value = nextItem
|
||||
}
|
||||
}
|
||||
player.removeListener(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
player.addListener(listener)
|
||||
|
||||
// viewModelScope.launch(Dispatchers.IO) {
|
||||
// while (this.isActive) {
|
||||
// delay(5.seconds)
|
||||
// val remaining =
|
||||
// withContext(Dispatchers.Main) {
|
||||
// (player.duration - player.currentPosition).milliseconds
|
||||
// }
|
||||
// if (remaining < 2.minutes) { // TODO time & preference
|
||||
// val nextItem = episodes.getBlocking(nextIndex)
|
||||
// Timber.v("Setting next up episode to ${nextItem?.id}")
|
||||
// withContext(Dispatchers.Main) {
|
||||
// nextUpEpisode.value = nextItem
|
||||
// }
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
addCloseable { player.removeListener(listener) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -576,14 +573,30 @@ class PlaybackViewModel
|
|||
}
|
||||
}
|
||||
|
||||
fun playUpNextEpisode() {
|
||||
nextUpEpisode.value?.let {
|
||||
init(Destination.Playback(it.id, 0, it), deviceProfile, preferences)
|
||||
fun playUpNextUp() {
|
||||
playlist.value?.let {
|
||||
if (it.hasNext()) {
|
||||
viewModelScope.launch(ExceptionHandler()) {
|
||||
cancelUpNextEpisode()
|
||||
play(it.getAndAdvance().data, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun playPrevious() {
|
||||
playlist.value?.let {
|
||||
if (it.hasPrevious()) {
|
||||
viewModelScope.launch(ExceptionHandler()) {
|
||||
cancelUpNextEpisode()
|
||||
play(it.getPreviousAndReverse().data, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelUpNextEpisode() {
|
||||
nextUpEpisode.value = null
|
||||
nextUp.value = null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -670,3 +683,6 @@ private fun applyTrackSelections(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val singlePlays =
|
||||
setOf(BaseItemKind.EPISODE, BaseItemKind.MOVIE, BaseItemKind.VIDEO, BaseItemKind.MUSIC_VIDEO)
|
||||
|
|
|
|||
|
|
@ -16,12 +16,14 @@ import kotlinx.coroutines.sync.withLock
|
|||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.Response
|
||||
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.playlistsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.suggestionsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
|
||||
import org.jellyfin.sdk.model.api.BaseItemDtoQueryResult
|
||||
import org.jellyfin.sdk.model.api.request.GetEpisodesRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetNextUpRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetPlaylistItemsRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetResumeItemsRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetSuggestionsRequest
|
||||
import timber.log.Timber
|
||||
|
|
@ -271,3 +273,22 @@ val GetSuggestionsRequestHandler =
|
|||
request: GetSuggestionsRequest,
|
||||
): Response<BaseItemDtoQueryResult> = api.suggestionsApi.getSuggestions(request)
|
||||
}
|
||||
|
||||
val GetPlaylistItemsRequestHandler =
|
||||
object : RequestHandler<GetPlaylistItemsRequest> {
|
||||
override fun prepare(
|
||||
request: GetPlaylistItemsRequest,
|
||||
startIndex: Int,
|
||||
limit: Int,
|
||||
enableTotalRecordCount: Boolean,
|
||||
): GetPlaylistItemsRequest =
|
||||
request.copy(
|
||||
startIndex = startIndex,
|
||||
limit = limit,
|
||||
)
|
||||
|
||||
override suspend fun execute(
|
||||
api: ApiClient,
|
||||
request: GetPlaylistItemsRequest,
|
||||
): Response<BaseItemDtoQueryResult> = api.playlistsApi.getPlaylistItems(request)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue