Improve series overview loading speed (#800)

## Description
Improves loading speed for the series overview page (one with list of
episodes). This is mostly accomplished by querying for the cast/crew in
the episodes on demand instead of during the initial query.

Also stops using the date as a replacement for the episode index. Using
this caused an extra unnecessary (long) query.

Going from home to the 109th of a 230 episode season is ~2x faster with
this change on my test server.


### Related issues
Closes #599
This commit is contained in:
Ray 2026-01-30 11:34:12 -05:00 committed by GitHub
parent e7ed173692
commit 003fe8348c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 28 additions and 10 deletions

View file

@ -72,8 +72,7 @@ data class BaseItem(
@Transient
val aspectRatio: Float? = data.primaryImageAspectRatio?.toFloat()?.takeIf { it > 0 }
@Transient
val indexNumber = data.indexNumber ?: dateAsIndex()
val indexNumber get() = data.indexNumber
val playbackPosition get() = data.userData?.playbackPositionTicks?.ticks ?: Duration.ZERO

View file

@ -39,6 +39,7 @@ import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState
import com.google.common.cache.CacheBuilder
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -57,6 +58,7 @@ import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.libraryApi
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ItemFields
import org.jellyfin.sdk.model.api.ItemSortBy
@ -293,14 +295,16 @@ class SeriesViewModel
listOf(
ItemFields.MEDIA_SOURCES,
ItemFields.MEDIA_SOURCE_COUNT,
ItemFields.MEDIA_STREAMS,
ItemFields.OVERVIEW,
ItemFields.CUSTOM_RATING,
ItemFields.TRICKPLAY,
ItemFields.PRIMARY_IMAGE_ASPECT_RATIO,
ItemFields.PEOPLE,
),
)
Timber.v(
"loadEpisodesInternal: episodeId=%s, episodeNumber=%s",
episodeId,
episodeNumber,
)
val pager = ApiRequestPager(api, request, GetEpisodesRequestHandler, viewModelScope)
pager.init(episodeNumber ?: 0)
val initialIndex =
@ -504,19 +508,34 @@ class SeriesViewModel
}
private var peopleInEpisodeJob: Job? = null
private val peopleInEpisodeCache =
CacheBuilder
.newBuilder()
.maximumSize(25)
.build<UUID, Deferred<PeopleInItem>>()
suspend fun lookupPeopleInEpisode(item: BaseItem) {
peopleInEpisodeJob?.cancel()
if (peopleInEpisode.value?.itemId != item.id) {
peopleInEpisode.setValueOnMain(PeopleInItem())
val result =
peopleInEpisodeCache
.get(item.id) {
viewModelScope.async(Dispatchers.IO) {
val list =
api.userLibraryApi
.getItem(item.id)
.content.people
?.map { Person.fromDto(it, api) }
.orEmpty()
PeopleInItem(item.id, list)
}
}
peopleInEpisodeJob =
viewModelScope.launch(ExceptionHandler()) {
delay(250)
val people =
item.data.people
?.letNotEmpty { it.map { Person.fromDto(it, api) } }
.orEmpty()
peopleInEpisode.setValueOnMain(PeopleInItem(item.id, people))
peopleInEpisode.setValueOnMain(result.await())
}
}
}