mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 16:11:20 +02:00
Play all or shuffle libraries, shows, collections, etc (#278)
Adds ability to play all (or shuffle play) most libraries, collections, folders, TV series, TV seasons, etc This adds buttons to play all or shuffle play at the top of grid pages that support. Additionally, a shuffle button is added to TV Series details. And play all or shuffle play is added when long clicking TV seasons. Note: Until #42 is completed, only the first 100 items will play back-to-back Closes #197
This commit is contained in:
parent
9a3a33fdd3
commit
50dc4b9a35
18 changed files with 467 additions and 88 deletions
|
|
@ -4,9 +4,12 @@ import android.content.Context
|
|||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||
import com.github.damontecres.wholphin.data.model.Playlist
|
||||
import com.github.damontecres.wholphin.data.model.PlaylistInfo
|
||||
import com.github.damontecres.wholphin.ui.DefaultItemFields
|
||||
import com.github.damontecres.wholphin.ui.components.baseItemKinds
|
||||
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
||||
import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
|
|
@ -18,10 +21,14 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.playlistsApi
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.CreatePlaylistDto
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import org.jellyfin.sdk.model.api.PlaylistUserPermissions
|
||||
import org.jellyfin.sdk.model.api.SortOrder
|
||||
import org.jellyfin.sdk.model.api.request.GetEpisodesRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetPlaylistItemsRequest
|
||||
|
|
@ -43,19 +50,22 @@ class PlaylistCreator
|
|||
*/
|
||||
suspend fun createFromEpisode(
|
||||
seriesId: UUID,
|
||||
episodeId: UUID,
|
||||
episodeId: UUID?,
|
||||
seasonId: UUID? = null,
|
||||
shuffled: Boolean = false,
|
||||
): Playlist {
|
||||
val request =
|
||||
GetEpisodesRequest(
|
||||
seriesId = seriesId,
|
||||
seasonId = seasonId,
|
||||
fields = DefaultItemFields,
|
||||
startItemId = episodeId,
|
||||
sortBy = if (shuffled) ItemSortBy.RANDOM else null,
|
||||
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")
|
||||
episodeId?.let { episodes.indexOfFirstOrNull { it.id == episodeId } } ?: 0
|
||||
return Playlist(episodes.map { BaseItem.from(it, api) }, startIndex)
|
||||
}
|
||||
|
||||
|
|
@ -79,6 +89,128 @@ class PlaylistCreator
|
|||
return Playlist(baseItems, 0)
|
||||
}
|
||||
|
||||
private suspend fun createFromCollection(
|
||||
item: BaseItemDto,
|
||||
startIndex: Int = 0,
|
||||
sortAndDirection: SortAndDirection?,
|
||||
recursive: Boolean,
|
||||
filter: GetItemsFilter,
|
||||
): Playlist {
|
||||
val includeItemTypes =
|
||||
item.collectionType?.baseItemKinds?.takeIf { it.isNotEmpty() }
|
||||
?: listOf(BaseItemKind.MOVIE, BaseItemKind.EPISODE, BaseItemKind.VIDEO)
|
||||
val request =
|
||||
filter.applyTo(
|
||||
GetItemsRequest(
|
||||
parentId = item.id,
|
||||
enableImageTypes = listOf(ImageType.PRIMARY, ImageType.THUMB),
|
||||
includeItemTypes = includeItemTypes,
|
||||
recursive = true,
|
||||
excludeItemIds = listOf(item.id),
|
||||
sortBy = sortAndDirection?.let { listOf(sortAndDirection.sort) },
|
||||
sortOrder = sortAndDirection?.let { listOf(sortAndDirection.direction) },
|
||||
fields = DefaultItemFields,
|
||||
startIndex = startIndex,
|
||||
limit = Playlist.MAX_SIZE,
|
||||
),
|
||||
)
|
||||
val items =
|
||||
GetItemsRequestHandler.execute(api, request).content.items.map {
|
||||
BaseItem.from(it, api)
|
||||
}
|
||||
return Playlist(items, 0)
|
||||
}
|
||||
|
||||
suspend fun createFrom(
|
||||
item: BaseItemDto,
|
||||
startIndex: Int = 0,
|
||||
sortAndDirection: SortAndDirection?,
|
||||
shuffled: Boolean,
|
||||
recursive: Boolean,
|
||||
filter: GetItemsFilter,
|
||||
): PlaylistCreationResult =
|
||||
when (item.type) {
|
||||
BaseItemKind.BOX_SET,
|
||||
BaseItemKind.COLLECTION_FOLDER,
|
||||
BaseItemKind.USER_VIEW,
|
||||
->
|
||||
PlaylistCreationResult.Success(
|
||||
createFromCollection(
|
||||
item = item,
|
||||
startIndex = startIndex,
|
||||
sortAndDirection =
|
||||
if (shuffled) {
|
||||
SortAndDirection(ItemSortBy.RANDOM, SortOrder.ASCENDING)
|
||||
} else {
|
||||
sortAndDirection
|
||||
},
|
||||
recursive = recursive,
|
||||
filter = filter,
|
||||
),
|
||||
)
|
||||
|
||||
BaseItemKind.EPISODE -> {
|
||||
val seriesId = item.seriesId
|
||||
if (seriesId != null) {
|
||||
PlaylistCreationResult.Success(
|
||||
createFromEpisode(
|
||||
seriesId = seriesId,
|
||||
seasonId = null,
|
||||
episodeId = item.id,
|
||||
shuffled = shuffled,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
PlaylistCreationResult.Error(null, "Episode has not seriesId")
|
||||
}
|
||||
}
|
||||
|
||||
BaseItemKind.SEASON -> {
|
||||
val seriesId = item.seriesId
|
||||
if (seriesId != null) {
|
||||
PlaylistCreationResult.Success(
|
||||
createFromEpisode(
|
||||
seriesId = seriesId,
|
||||
seasonId = item.id,
|
||||
episodeId = null,
|
||||
shuffled = shuffled,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
PlaylistCreationResult.Error(null, "Episode has not seriesId")
|
||||
}
|
||||
}
|
||||
|
||||
BaseItemKind.SERIES ->
|
||||
PlaylistCreationResult.Success(
|
||||
createFromEpisode(
|
||||
seriesId = item.id,
|
||||
seasonId = null,
|
||||
episodeId = null,
|
||||
shuffled = shuffled,
|
||||
),
|
||||
)
|
||||
|
||||
BaseItemKind.PLAYLIST ->
|
||||
PlaylistCreationResult.Success(
|
||||
createFromPlaylistId(
|
||||
item.id,
|
||||
startIndex,
|
||||
shuffled,
|
||||
),
|
||||
)
|
||||
|
||||
// Not support yet
|
||||
// BaseItemKind.AGGREGATE_FOLDER -> TODO()
|
||||
// BaseItemKind.FOLDER -> TODO()
|
||||
// BaseItemKind.GENRE -> TODO()
|
||||
// BaseItemKind.MANUAL_PLAYLISTS_FOLDER -> TODO()
|
||||
// BaseItemKind.MUSIC_ALBUM -> TODO()
|
||||
// BaseItemKind.MUSIC_ARTIST -> TODO()
|
||||
|
||||
else -> PlaylistCreationResult.Error(null, "Unsupported type: ${item.type}")
|
||||
}
|
||||
|
||||
suspend fun getPlaylists(
|
||||
mediaType: MediaType?,
|
||||
scope: CoroutineScope,
|
||||
|
|
@ -136,3 +268,14 @@ class PlaylistCreator
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface PlaylistCreationResult {
|
||||
data class Success(
|
||||
val playlist: Playlist,
|
||||
) : PlaylistCreationResult
|
||||
|
||||
data class Error(
|
||||
val ex: Exception?,
|
||||
val message: String?,
|
||||
) : PlaylistCreationResult
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue