From 633ee6b8c42f731240285eff243aa3a9022cccee Mon Sep 17 00:00:00 2001 From: nomadics9 Date: Fri, 19 Jul 2024 05:01:09 +0300 Subject: [PATCH] lint: klint standard --- .../jdtech/jellyfin/utils/DownloaderImpl.kt | 283 +++++--- .../repository/JellyfinRepositoryImpl.kt | 661 ++++++++++-------- .../JellyfinRepositoryOfflineImpl.kt | 130 ++-- 3 files changed, 636 insertions(+), 438 deletions(-) diff --git a/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt b/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt index d04f9110..0a463d73 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt @@ -30,7 +30,6 @@ import org.jellyfin.sdk.model.api.EncodingContext import org.jellyfin.sdk.model.api.MediaStreamType import java.io.File import java.util.UUID -import kotlin.Exception import kotlin.math.ceil import dev.jdtech.jellyfin.core.R as CoreR @@ -48,13 +47,15 @@ class DownloaderImpl( storageIndex: Int, ): Pair { try { - val source = jellyfinRepository.getMediaSources(item.id, true).first { it.id == sourceId } + val source = + jellyfinRepository.getMediaSources(item.id, true).first { it.id == sourceId } val intro = jellyfinRepository.getIntroTimestamps(item.id) - val trickplayInfo = if (item is FindroidSources) { - item.trickplayInfo?.get(sourceId) - } else { - null - } + val trickplayInfo = + if (item is FindroidSources) { + item.trickplayInfo?.get(sourceId) + } else { + null + } val storageLocation = context.getExternalFilesDirs(null)[storageIndex] if (storageLocation == null || Environment.getExternalStorageState(storageLocation) != Environment.MEDIA_MOUNTED) { return Pair(-1, UiText.StringResource(CoreR.string.storage_unavailable)) @@ -85,24 +86,29 @@ class DownloaderImpl( database.insertIntro(intro.toIntroDto(item.id)) } if (appPreferences.downloadQuality != "Original") { - downloadEmbeddedMediaStreams(item, source,storageIndex) - val transcodingUrl =getTranscodedUrl(item.id,appPreferences.downloadQuality!!) - val request = DownloadManager.Request(transcodingUrl) - .setTitle(item.name) - .setAllowedOverMetered(appPreferences.downloadOverMobileData) - .setAllowedOverRoaming(appPreferences.downloadWhenRoaming) - .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) - .setDestinationUri(path) + downloadEmbeddedMediaStreams(item, source, storageIndex) + val transcodingUrl = + getTranscodedUrl(item.id, appPreferences.downloadQuality!!) + val request = + DownloadManager + .Request(transcodingUrl) + .setTitle(item.name) + .setAllowedOverMetered(appPreferences.downloadOverMobileData) + .setAllowedOverRoaming(appPreferences.downloadWhenRoaming) + .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + .setDestinationUri(path) val downloadId = downloadManager.enqueue(request) database.setSourceDownloadId(source.id, downloadId) return Pair(downloadId, null) - }else { - val request = DownloadManager.Request(source.path.toUri()) - .setTitle(item.name) - .setAllowedOverMetered(appPreferences.downloadOverMobileData) - .setAllowedOverRoaming(appPreferences.downloadWhenRoaming) - .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) - .setDestinationUri(path) + } else { + val request = + DownloadManager + .Request(source.path.toUri()) + .setTitle(item.name) + .setAllowedOverMetered(appPreferences.downloadOverMobileData) + .setAllowedOverRoaming(appPreferences.downloadWhenRoaming) + .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + .setDestinationUri(path) val downloadId = downloadManager.enqueue(request) database.setSourceDownloadId(source.id, downloadId) return Pair(downloadId, null) @@ -111,7 +117,8 @@ class DownloaderImpl( is FindroidEpisode -> { database.insertShow( - jellyfinRepository.getShow(item.seriesId) + jellyfinRepository + .getShow(item.seriesId) .toFindroidShowDto(appPreferences.currentServer!!), ) database.insertSeason( @@ -128,24 +135,29 @@ class DownloaderImpl( database.insertIntro(intro.toIntroDto(item.id)) } if (appPreferences.downloadQuality != "Original") { - downloadEmbeddedMediaStreams(item, source,storageIndex) - val transcodingUrl = getTranscodedUrl(item.id, appPreferences.downloadQuality!!) - val request = DownloadManager.Request(transcodingUrl) - .setTitle(item.name) - .setAllowedOverMetered(appPreferences.downloadOverMobileData) - .setAllowedOverRoaming(appPreferences.downloadWhenRoaming) - .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) - .setDestinationUri(path) + downloadEmbeddedMediaStreams(item, source, storageIndex) + val transcodingUrl = + getTranscodedUrl(item.id, appPreferences.downloadQuality!!) + val request = + DownloadManager + .Request(transcodingUrl) + .setTitle(item.name) + .setAllowedOverMetered(appPreferences.downloadOverMobileData) + .setAllowedOverRoaming(appPreferences.downloadWhenRoaming) + .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + .setDestinationUri(path) val downloadId = downloadManager.enqueue(request) database.setSourceDownloadId(source.id, downloadId) return Pair(downloadId, null) - }else { - val request = DownloadManager.Request(source.path.toUri()) - .setTitle(item.name) - .setAllowedOverMetered(appPreferences.downloadOverMobileData) - .setAllowedOverRoaming(appPreferences.downloadWhenRoaming) - .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) - .setDestinationUri(path) + } else { + val request = + DownloadManager + .Request(source.path.toUri()) + .setTitle(item.name) + .setAllowedOverMetered(appPreferences.downloadOverMobileData) + .setAllowedOverRoaming(appPreferences.downloadWhenRoaming) + .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + .setDestinationUri(path) val downloadId = downloadManager.enqueue(request) database.setSourceDownloadId(source.id, downloadId) return Pair(downloadId, null) @@ -157,24 +169,41 @@ class DownloaderImpl( try { val source = jellyfinRepository.getMediaSources(item.id).first { it.id == sourceId } deleteItem(item, source) - } catch (_: Exception) {} + } catch (_: Exception) { + } - return Pair(-1, if (e.message != null) UiText.DynamicString(e.message!!) else UiText.StringResource(CoreR.string.unknown_error)) + return Pair( + -1, + if (e.message != null) { + UiText.DynamicString(e.message!!) + } else { + UiText.StringResource( + CoreR.string.unknown_error, + ) + }, + ) } } - override suspend fun cancelDownload(item: FindroidItem, source: FindroidSource) { + override suspend fun cancelDownload( + item: FindroidItem, + source: FindroidSource, + ) { if (source.downloadId != null) { downloadManager.remove(source.downloadId!!) } deleteItem(item, source) } - override suspend fun deleteItem(item: FindroidItem, source: FindroidSource) { + override suspend fun deleteItem( + item: FindroidItem, + source: FindroidSource, + ) { when (item) { is FindroidMovie -> { database.deleteMovie(item.id) } + is FindroidEpisode -> { database.deleteEpisode(item.id) val remainingEpisodes = database.getEpisodesBySeasonId(item.seasonId) @@ -212,23 +241,29 @@ class DownloaderImpl( if (downloadId == null) { return Pair(downloadStatus, progress) } - val query = DownloadManager.Query() - .setFilterById(downloadId) + val query = + DownloadManager + .Query() + .setFilterById(downloadId) val cursor = downloadManager.query(query) if (cursor.moveToFirst()) { - downloadStatus = cursor.getInt( - cursor.getColumnIndexOrThrow( - DownloadManager.COLUMN_STATUS, - ), - ) + downloadStatus = + cursor.getInt( + cursor.getColumnIndexOrThrow( + DownloadManager.COLUMN_STATUS, + ), + ) when (downloadStatus) { DownloadManager.STATUS_RUNNING -> { - val totalBytes = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)) + val totalBytes = + cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)) if (totalBytes > 0) { - val downloadedBytes = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)) + val downloadedBytes = + cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)) progress = downloadedBytes.times(100).div(totalBytes).toInt() } } + DownloadManager.STATUS_SUCCESSFUL -> { progress = 100 } @@ -247,14 +282,28 @@ class DownloaderImpl( val storageLocation = context.getExternalFilesDirs(null)[storageIndex] for (mediaStream in source.mediaStreams.filter { it.isExternal }) { val id = UUID.randomUUID() - val streamPath = Uri.fromFile(File(storageLocation, "downloads/${item.id}.${source.id}.$id.download")) - database.insertMediaStream(mediaStream.toFindroidMediaStreamDto(id, source.id, streamPath.path.orEmpty())) - val request = DownloadManager.Request(Uri.parse(mediaStream.path)) - .setTitle(mediaStream.title) - .setAllowedOverMetered(appPreferences.downloadOverMobileData) - .setAllowedOverRoaming(appPreferences.downloadWhenRoaming) - .setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN) - .setDestinationUri(streamPath) + val streamPath = + Uri.fromFile( + File( + storageLocation, + "downloads/${item.id}.${source.id}.$id.download", + ), + ) + database.insertMediaStream( + mediaStream.toFindroidMediaStreamDto( + id, + source.id, + streamPath.path.orEmpty(), + ), + ) + val request = + DownloadManager + .Request(Uri.parse(mediaStream.path)) + .setTitle(mediaStream.title) + .setAllowedOverMetered(appPreferences.downloadOverMobileData) + .setAllowedOverRoaming(appPreferences.downloadWhenRoaming) + .setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN) + .setDestinationUri(streamPath) val downloadId = downloadManager.enqueue(request) database.setMediaStreamDownloadId(id, downloadId) } @@ -263,57 +312,66 @@ class DownloaderImpl( private fun downloadEmbeddedMediaStreams( item: FindroidItem, source: FindroidSource, - storageIndex: Int = 0 + storageIndex: Int = 0, ) { val storageLocation = context.getExternalFilesDirs(null)[storageIndex] - val subtitleStreams = source.mediaStreams.filter { !it.isExternal && it.type == MediaStreamType.SUBTITLE && it.path != null } + val subtitleStreams = + source.mediaStreams.filter { !it.isExternal && it.type == MediaStreamType.SUBTITLE && it.path != null } for (mediaStream in subtitleStreams) { var deliveryUrl = mediaStream.path!! if (mediaStream.codec == "webvtt") { deliveryUrl = deliveryUrl.replace("Stream.srt", "Stream.vtt") } val id = UUID.randomUUID() - val streamPath = Uri.fromFile( - File( - storageLocation, - "downloads/${item.id}.${source.id}.$id.download" + val streamPath = + Uri.fromFile( + File( + storageLocation, + "downloads/${item.id}.${source.id}.$id.download", + ), ) - ) database.insertMediaStream( mediaStream.toFindroidMediaStreamDto( id, source.id, - streamPath.path.orEmpty() - ) + streamPath.path.orEmpty(), + ), ) - val request = DownloadManager.Request(Uri.parse(deliveryUrl)) - .setTitle(mediaStream.title) - .setAllowedOverMetered(appPreferences.downloadOverMobileData) - .setAllowedOverRoaming(appPreferences.downloadWhenRoaming) - .setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN) - .setDestinationUri(streamPath) + val request = + DownloadManager + .Request(Uri.parse(deliveryUrl)) + .setTitle(mediaStream.title) + .setAllowedOverMetered(appPreferences.downloadOverMobileData) + .setAllowedOverRoaming(appPreferences.downloadWhenRoaming) + .setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN) + .setDestinationUri(streamPath) val downloadId = downloadManager.enqueue(request) database.setMediaStreamDownloadId(id, downloadId) } } - private suspend fun downloadTrickplayData( itemId: UUID, sourceId: String, trickplayInfo: FindroidTrickplayInfo, ) { - val maxIndex = ceil(trickplayInfo.thumbnailCount.toDouble().div(trickplayInfo.tileWidth * trickplayInfo.tileHeight)).toInt() + val maxIndex = + ceil( + trickplayInfo.thumbnailCount + .toDouble() + .div(trickplayInfo.tileWidth * trickplayInfo.tileHeight), + ).toInt() val byteArrays = mutableListOf() for (i in 0..maxIndex) { - jellyfinRepository.getTrickplayData( - itemId, - trickplayInfo.width, - i, - )?.let { byteArray -> - byteArrays.add(byteArray) - } + jellyfinRepository + .getTrickplayData( + itemId, + trickplayInfo.width, + i, + )?.let { byteArray -> + byteArrays.add(byteArray) + } } saveTrickplayData(itemId, sourceId, trickplayInfo, byteArrays) } @@ -333,22 +391,38 @@ class DownloaderImpl( } } - private suspend fun getTranscodedUrl(itemId: UUID, quality: String): Uri? { - val maxBitrate = when (quality) { - "720p" -> 2000000 // 2 Mbps - "480p" -> 1000000 // 1 Mbps - "360p" -> 800000 // 800Kbps - else -> 2000000 - } + private suspend fun getTranscodedUrl( + itemId: UUID, + quality: String, + ): Uri? { + val maxBitrate = + when (quality) { + "720p" -> 2000000 // 2 Mbps + "480p" -> 1000000 // 1 Mbps + "360p" -> 800000 // 800Kbps + else -> 2000000 + } return try { - - val deviceProfile = jellyfinRepository.buildDeviceProfile(maxBitrate,"mkv", EncodingContext.STATIC) - val playbackInfo = jellyfinRepository.getPostedPlaybackInfo(itemId,false,deviceProfile,maxBitrate) - val mediaSourceId = playbackInfo.content.mediaSources.firstOrNull()?.id!! + val deviceProfile = + jellyfinRepository.buildDeviceProfile(maxBitrate, "mkv", EncodingContext.STATIC) + val playbackInfo = + jellyfinRepository.getPostedPlaybackInfo(itemId, false, deviceProfile, maxBitrate) + val mediaSourceId = + playbackInfo.content.mediaSources + .firstOrNull() + ?.id!! val playSessionId = playbackInfo.content.playSessionId!! val deviceId = jellyfinRepository.getDeviceId() - val downloadUrl = jellyfinRepository.getVideoStreambyContainerUrl(itemId, deviceId, mediaSourceId, playSessionId, maxBitrate, "ts") + val downloadUrl = + jellyfinRepository.getVideoStreambyContainerUrl( + itemId, + deviceId, + mediaSourceId, + playSessionId, + maxBitrate, + "ts", + ) val transcodeUri = buildTranscodeUri(downloadUrl, maxBitrate, quality) transcodeUri @@ -361,15 +435,18 @@ class DownloaderImpl( private fun buildTranscodeUri( transcodingUrl: String, maxBitrate: Int, - quality: String + quality: String, ): Uri { - val resolution = when (quality) { - "720p" -> "720" - "480p" -> "480" - "360p" -> "360" - else -> "720" - } - return Uri.parse(transcodingUrl).buildUpon() + val resolution = + when (quality) { + "720p" -> "720" + "480p" -> "480" + "360p" -> "360" + else -> "720" + } + return Uri + .parse(transcodingUrl) + .buildUpon() .appendQueryParameter("MaxVideoHeight", resolution) .appendQueryParameter("MaxVideoBitRate", maxBitrate.toString()) .appendQueryParameter("subtitleMethod", "External") diff --git a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt index ae178c7b..c8d6d66d 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt @@ -68,55 +68,70 @@ class JellyfinRepositoryImpl( private val database: ServerDatabaseDao, private val appPreferences: AppPreferences, ) : JellyfinRepository { - override suspend fun getPublicSystemInfo(): PublicSystemInfo = withContext(Dispatchers.IO) { - jellyfinApi.systemApi.getPublicSystemInfo().content - } + override suspend fun getPublicSystemInfo(): PublicSystemInfo = + withContext(Dispatchers.IO) { + jellyfinApi.systemApi.getPublicSystemInfo().content + } - override suspend fun getUserViews(): List = withContext(Dispatchers.IO) { - jellyfinApi.viewsApi.getUserViews(jellyfinApi.userId!!).content.items.orEmpty() - } + override suspend fun getUserViews(): List = + withContext(Dispatchers.IO) { + jellyfinApi.viewsApi + .getUserViews(jellyfinApi.userId!!) + .content.items + .orEmpty() + } - override suspend fun getItem(itemId: UUID): BaseItemDto = withContext(Dispatchers.IO) { - jellyfinApi.userLibraryApi.getItem(itemId, jellyfinApi.userId!!).content - } + override suspend fun getItem(itemId: UUID): BaseItemDto = + withContext(Dispatchers.IO) { + jellyfinApi.userLibraryApi.getItem(itemId, jellyfinApi.userId!!).content + } override suspend fun getEpisode(itemId: UUID): FindroidEpisode = withContext(Dispatchers.IO) { - jellyfinApi.userLibraryApi.getItem( - itemId, - jellyfinApi.userId!!, - ).content.toFindroidEpisode(this@JellyfinRepositoryImpl, database)!! + jellyfinApi.userLibraryApi + .getItem( + itemId, + jellyfinApi.userId!!, + ).content + .toFindroidEpisode(this@JellyfinRepositoryImpl, database)!! } override suspend fun getMovie(itemId: UUID): FindroidMovie = withContext(Dispatchers.IO) { - jellyfinApi.userLibraryApi.getItem( - itemId, - jellyfinApi.userId!!, - ).content.toFindroidMovie(this@JellyfinRepositoryImpl, database) + jellyfinApi.userLibraryApi + .getItem( + itemId, + jellyfinApi.userId!!, + ).content + .toFindroidMovie(this@JellyfinRepositoryImpl, database) } override suspend fun getShow(itemId: UUID): FindroidShow = withContext(Dispatchers.IO) { - jellyfinApi.userLibraryApi.getItem( - itemId, - jellyfinApi.userId!!, - ).content.toFindroidShow(this@JellyfinRepositoryImpl) + jellyfinApi.userLibraryApi + .getItem( + itemId, + jellyfinApi.userId!!, + ).content + .toFindroidShow(this@JellyfinRepositoryImpl) } override suspend fun getSeason(itemId: UUID): FindroidSeason = withContext(Dispatchers.IO) { - jellyfinApi.userLibraryApi.getItem( - itemId, - jellyfinApi.userId!!, - ).content.toFindroidSeason(this@JellyfinRepositoryImpl) + jellyfinApi.userLibraryApi + .getItem( + itemId, + jellyfinApi.userId!!, + ).content + .toFindroidSeason(this@JellyfinRepositoryImpl) } override suspend fun getLibraries(): List = withContext(Dispatchers.IO) { - jellyfinApi.itemsApi.getItems( - jellyfinApi.userId!!, - ).content.items + jellyfinApi.itemsApi + .getItems( + jellyfinApi.userId!!, + ).content.items .orEmpty() .mapNotNull { it.toFindroidCollection(this@JellyfinRepositoryImpl) } } @@ -131,16 +146,17 @@ class JellyfinRepositoryImpl( limit: Int?, ): List = withContext(Dispatchers.IO) { - jellyfinApi.itemsApi.getItems( - jellyfinApi.userId!!, - parentId = parentId, - includeItemTypes = includeTypes, - recursive = recursive, - sortBy = listOf(ItemSortBy.fromName(sortBy.sortString)), - sortOrder = listOf(sortOrder), - startIndex = startIndex, - limit = limit, - ).content.items + jellyfinApi.itemsApi + .getItems( + jellyfinApi.userId!!, + parentId = parentId, + includeItemTypes = includeTypes, + recursive = recursive, + sortBy = listOf(ItemSortBy.fromName(sortBy.sortString)), + sortOrder = listOf(sortOrder), + startIndex = startIndex, + limit = limit, + ).content.items .orEmpty() .mapNotNull { it.toFindroidItem(this@JellyfinRepositoryImpl, database) } } @@ -151,13 +167,14 @@ class JellyfinRepositoryImpl( recursive: Boolean, sortBy: SortBy, sortOrder: SortOrder, - ): Flow> { - return Pager( - config = PagingConfig( - pageSize = 10, - maxSize = 100, - enablePlaceholders = false, - ), + ): Flow> = + Pager( + config = + PagingConfig( + pageSize = 10, + maxSize = 100, + enablePlaceholders = false, + ), pagingSourceFactory = { ItemsPagingSource( this, @@ -169,87 +186,102 @@ class JellyfinRepositoryImpl( ) }, ).flow - } override suspend fun getPersonItems( personIds: List, includeTypes: List?, recursive: Boolean, - ): List = withContext(Dispatchers.IO) { - jellyfinApi.itemsApi.getItems( - jellyfinApi.userId!!, - personIds = personIds, - includeItemTypes = includeTypes, - recursive = recursive, - ).content.items - .orEmpty() - .mapNotNull { - it.toFindroidItem(this@JellyfinRepositoryImpl, database) - } - } + ): List = + withContext(Dispatchers.IO) { + jellyfinApi.itemsApi + .getItems( + jellyfinApi.userId!!, + personIds = personIds, + includeItemTypes = includeTypes, + recursive = recursive, + ).content.items + .orEmpty() + .mapNotNull { + it.toFindroidItem(this@JellyfinRepositoryImpl, database) + } + } override suspend fun getFavoriteItems(): List = withContext(Dispatchers.IO) { - jellyfinApi.itemsApi.getItems( - jellyfinApi.userId!!, - filters = listOf(ItemFilter.IS_FAVORITE), - includeItemTypes = listOf( - BaseItemKind.MOVIE, - BaseItemKind.SERIES, - BaseItemKind.EPISODE, - ), - recursive = true, - ).content.items + jellyfinApi.itemsApi + .getItems( + jellyfinApi.userId!!, + filters = listOf(ItemFilter.IS_FAVORITE), + includeItemTypes = + listOf( + BaseItemKind.MOVIE, + BaseItemKind.SERIES, + BaseItemKind.EPISODE, + ), + recursive = true, + ).content.items .orEmpty() .mapNotNull { it.toFindroidItem(this@JellyfinRepositoryImpl, database) } } override suspend fun getSearchItems(searchQuery: String): List = withContext(Dispatchers.IO) { - jellyfinApi.itemsApi.getItems( - jellyfinApi.userId!!, - searchTerm = searchQuery, - includeItemTypes = listOf( - BaseItemKind.MOVIE, - BaseItemKind.SERIES, - BaseItemKind.EPISODE, - ), - recursive = true, - ).content.items + jellyfinApi.itemsApi + .getItems( + jellyfinApi.userId!!, + searchTerm = searchQuery, + includeItemTypes = + listOf( + BaseItemKind.MOVIE, + BaseItemKind.SERIES, + BaseItemKind.EPISODE, + ), + recursive = true, + ).content.items .orEmpty() .mapNotNull { it.toFindroidItem(this@JellyfinRepositoryImpl, database) } } override suspend fun getResumeItems(): List { - val items = withContext(Dispatchers.IO) { - jellyfinApi.itemsApi.getResumeItems( - jellyfinApi.userId!!, - limit = 12, - includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.EPISODE), - ).content.items.orEmpty() - } + val items = + withContext(Dispatchers.IO) { + jellyfinApi.itemsApi + .getResumeItems( + jellyfinApi.userId!!, + limit = 12, + includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.EPISODE), + ).content.items + .orEmpty() + } return items.mapNotNull { it.toFindroidItem(this, database) } } override suspend fun getLatestMedia(parentId: UUID): List { - val items = withContext(Dispatchers.IO) { - jellyfinApi.userLibraryApi.getLatestMedia( - jellyfinApi.userId!!, - parentId = parentId, - limit = 16, - ).content - } + val items = + withContext(Dispatchers.IO) { + jellyfinApi.userLibraryApi + .getLatestMedia( + jellyfinApi.userId!!, + parentId = parentId, + limit = 16, + ).content + } return items.mapNotNull { it.toFindroidItem(this, database) } } - override suspend fun getSeasons(seriesId: UUID, offline: Boolean): List = + override suspend fun getSeasons( + seriesId: UUID, + offline: Boolean, + ): List = withContext(Dispatchers.IO) { if (!offline) { - jellyfinApi.showsApi.getSeasons(seriesId, jellyfinApi.userId!!).content.items + jellyfinApi.showsApi + .getSeasons(seriesId, jellyfinApi.userId!!) + .content.items .orEmpty() .map { it.toFindroidSeason(this@JellyfinRepositoryImpl) } } else { @@ -259,12 +291,13 @@ class JellyfinRepositoryImpl( override suspend fun getNextUp(seriesId: UUID?): List = withContext(Dispatchers.IO) { - jellyfinApi.showsApi.getNextUp( - jellyfinApi.userId!!, - limit = 24, - seriesId = seriesId, - enableResumable = false, - ).content.items + jellyfinApi.showsApi + .getNextUp( + jellyfinApi.userId!!, + limit = 24, + seriesId = seriesId, + enableResumable = false, + ).content.items .orEmpty() .mapNotNull { it.toFindroidEpisode(this@JellyfinRepositoryImpl) } } @@ -279,14 +312,15 @@ class JellyfinRepositoryImpl( ): List = withContext(Dispatchers.IO) { if (!offline) { - jellyfinApi.showsApi.getEpisodes( - seriesId, - jellyfinApi.userId!!, - seasonId = seasonId, - fields = fields, - startItemId = startItemId, - limit = limit, - ).content.items + jellyfinApi.showsApi + .getEpisodes( + seriesId, + jellyfinApi.userId!!, + seasonId = seasonId, + fields = fields, + startItemId = startItemId, + limit = limit, + ).content.items .orEmpty() .mapNotNull { it.toFindroidEpisode(this@JellyfinRepositoryImpl, database) } } else { @@ -294,39 +328,47 @@ class JellyfinRepositoryImpl( } } - override suspend fun getMediaSources(itemId: UUID, includePath: Boolean): List = + override suspend fun getMediaSources( + itemId: UUID, + includePath: Boolean, + ): List = withContext(Dispatchers.IO) { val sources = mutableListOf() sources.addAll( - jellyfinApi.mediaInfoApi.getPostedPlaybackInfo( - itemId, - PlaybackInfoDto( - userId = jellyfinApi.userId!!, - deviceProfile = DeviceProfile( - name = "Direct play all", - maxStaticBitrate = 1_000_000_000, - maxStreamingBitrate = 1_000_000_000, - codecProfiles = emptyList(), - containerProfiles = emptyList(), - directPlayProfiles = listOf( - DirectPlayProfile(type = DlnaProfileType.VIDEO), - DirectPlayProfile(type = DlnaProfileType.AUDIO), - ), - transcodingProfiles = emptyList(), - subtitleProfiles = listOf( - SubtitleProfile("srt", SubtitleDeliveryMethod.EXTERNAL), - SubtitleProfile("ass", SubtitleDeliveryMethod.EXTERNAL), - ), - ), - maxStreamingBitrate = 1_000_000_000, - ), - ).content.mediaSources.map { - it.toFindroidSource( - this@JellyfinRepositoryImpl, + jellyfinApi.mediaInfoApi + .getPostedPlaybackInfo( itemId, - includePath, - ) - }, + PlaybackInfoDto( + userId = jellyfinApi.userId!!, + deviceProfile = + DeviceProfile( + name = "Direct play all", + maxStaticBitrate = 1_000_000_000, + maxStreamingBitrate = 1_000_000_000, + codecProfiles = emptyList(), + containerProfiles = emptyList(), + directPlayProfiles = + listOf( + DirectPlayProfile(type = DlnaProfileType.VIDEO), + DirectPlayProfile(type = DlnaProfileType.AUDIO), + ), + transcodingProfiles = emptyList(), + subtitleProfiles = + listOf( + SubtitleProfile("srt", SubtitleDeliveryMethod.EXTERNAL), + SubtitleProfile("ass", SubtitleDeliveryMethod.EXTERNAL), + ), + ), + maxStreamingBitrate = 1_000_000_000, + ), + ).content.mediaSources + .map { + it.toFindroidSource( + this@JellyfinRepositoryImpl, + itemId, + includePath, + ) + }, ) sources.addAll( database.getSources(itemId).map { it.toFindroidSource(database) }, @@ -334,14 +376,18 @@ class JellyfinRepositoryImpl( sources } - override suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String, playSessionId: String?): String = + override suspend fun getStreamUrl( + itemId: UUID, + mediaSourceId: String, + playSessionId: String?, + ): String = withContext(Dispatchers.IO) { try { jellyfinApi.videosApi.getVideoStreamUrl( itemId, static = true, mediaSourceId = mediaSourceId, - playSessionId = playSessionId + playSessionId = playSessionId, ) } catch (e: Exception) { Timber.e(e) @@ -362,16 +408,21 @@ class JellyfinRepositoryImpl( pathParameters["itemId"] = itemId try { - return@withContext jellyfinApi.api.get( - "/Episode/{itemId}/IntroTimestamps/v1", - pathParameters, - ).content + return@withContext jellyfinApi.api + .get( + "/Episode/{itemId}/IntroTimestamps/v1", + pathParameters, + ).content } catch (e: Exception) { return@withContext null } } - override suspend fun getTrickplayData(itemId: UUID, width: Int, index: Int): ByteArray? = + override suspend fun getTrickplayData( + itemId: UUID, + width: Int, + index: Int, + ): ByteArray? = withContext(Dispatchers.IO) { try { try { @@ -379,9 +430,13 @@ class JellyfinRepositoryImpl( if (sources != null) { return@withContext File(sources.first(), index.toString()).readBytes() } - } catch (_: Exception) { } + } catch (_: Exception) { + } - return@withContext jellyfinApi.trickplayApi.getTrickplayTileImage(itemId, width, index).content.toByteArray() + return@withContext jellyfinApi.trickplayApi + .getTrickplayTileImage(itemId, width, index) + .content + .toByteArray() } catch (e: Exception) { return@withContext null } @@ -392,21 +447,22 @@ class JellyfinRepositoryImpl( withContext(Dispatchers.IO) { jellyfinApi.sessionApi.postCapabilities( playableMediaTypes = listOf(MediaType.VIDEO), - supportedCommands = listOf( - GeneralCommandType.VOLUME_UP, - GeneralCommandType.VOLUME_DOWN, - GeneralCommandType.TOGGLE_MUTE, - GeneralCommandType.SET_AUDIO_STREAM_INDEX, - GeneralCommandType.SET_SUBTITLE_STREAM_INDEX, - GeneralCommandType.MUTE, - GeneralCommandType.UNMUTE, - GeneralCommandType.SET_VOLUME, - GeneralCommandType.DISPLAY_MESSAGE, - GeneralCommandType.PLAY, - GeneralCommandType.PLAY_STATE, - GeneralCommandType.PLAY_NEXT, - GeneralCommandType.PLAY_MEDIA_SOURCE, - ), + supportedCommands = + listOf( + GeneralCommandType.VOLUME_UP, + GeneralCommandType.VOLUME_DOWN, + GeneralCommandType.TOGGLE_MUTE, + GeneralCommandType.SET_AUDIO_STREAM_INDEX, + GeneralCommandType.SET_SUBTITLE_STREAM_INDEX, + GeneralCommandType.MUTE, + GeneralCommandType.UNMUTE, + GeneralCommandType.SET_VOLUME, + GeneralCommandType.DISPLAY_MESSAGE, + GeneralCommandType.PLAY, + GeneralCommandType.PLAY_STATE, + GeneralCommandType.PLAY_NEXT, + GeneralCommandType.PLAY_MEDIA_SOURCE, + ), supportsMediaControl = true, ) } @@ -528,186 +584,215 @@ class JellyfinRepositoryImpl( } } - override suspend fun getUserConfiguration(): UserConfiguration = withContext(Dispatchers.IO) { - jellyfinApi.userApi.getCurrentUser().content.configuration!! - } + override suspend fun getUserConfiguration(): UserConfiguration = + withContext(Dispatchers.IO) { + jellyfinApi.userApi + .getCurrentUser() + .content.configuration!! + } override suspend fun getDownloads(): List = withContext(Dispatchers.IO) { val items = mutableListOf() items.addAll( - database.getMoviesByServerId(appPreferences.currentServer!!) + database + .getMoviesByServerId(appPreferences.currentServer!!) .map { it.toFindroidMovie(database, jellyfinApi.userId!!) }, ) items.addAll( - database.getShowsByServerId(appPreferences.currentServer!!) + database + .getShowsByServerId(appPreferences.currentServer!!) .map { it.toFindroidShow(database, jellyfinApi.userId!!) }, ) items } - override fun getUserId(): UUID { - return jellyfinApi.userId!! - } + override fun getUserId(): UUID = jellyfinApi.userId!! - - override suspend fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair { - return when (transcodeResolution) { + override suspend fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair = + when (transcodeResolution) { 1080 -> 8000000 to 384000 // Adjusted for personal can be other values 720 -> 2000000 to 384000 // 720p 480 -> 1000000 to 384000 // 480p - 360 -> 800000 to 128000 // 360p + 360 -> 800000 to 128000 // 360p else -> 12000000 to 384000 // its adaptive but setting max here } - } - override suspend fun buildDeviceProfile(maxBitrate: Int, container: String, context: EncodingContext): DeviceProfile { - val deviceProfile = ClientCapabilitiesDto( - supportedCommands = emptyList(), - playableMediaTypes = emptyList(), - supportsMediaControl = true, - supportsPersistentIdentifier = true, - deviceProfile = DeviceProfile( - name = "AnanasUser", - id = getUserId().toString(), - maxStaticBitrate = maxBitrate, - maxStreamingBitrate = maxBitrate, - codecProfiles = emptyList(), - containerProfiles = listOf(), - directPlayProfiles = listOf( - DirectPlayProfile(type = DlnaProfileType.VIDEO), - DirectPlayProfile(type = DlnaProfileType.AUDIO), - ), - transcodingProfiles = listOf( - TranscodingProfile( - container = container, - context = context, - protocol = MediaStreamProtocol.HLS, - audioCodec = "aac,ac3,eac3", - videoCodec = "hevc,h264", - type = DlnaProfileType.VIDEO, - conditions = listOf( - ProfileCondition( - condition = ProfileConditionType.LESS_THAN_EQUAL, - property = ProfileConditionValue.VIDEO_BITRATE, - value = "8000000", - isRequired = true, - ) - ), - copyTimestamps = true, - enableSubtitlesInManifest = true, - transcodeSeekInfo = TranscodeSeekInfo.AUTO, + override suspend fun buildDeviceProfile( + maxBitrate: Int, + container: String, + context: EncodingContext, + ): DeviceProfile { + val deviceProfile = + ClientCapabilitiesDto( + supportedCommands = emptyList(), + playableMediaTypes = emptyList(), + supportsMediaControl = true, + supportsPersistentIdentifier = true, + deviceProfile = + DeviceProfile( + name = "AnanasUser", + id = getUserId().toString(), + maxStaticBitrate = maxBitrate, + maxStreamingBitrate = maxBitrate, + codecProfiles = emptyList(), + containerProfiles = listOf(), + directPlayProfiles = + listOf( + DirectPlayProfile(type = DlnaProfileType.VIDEO), + DirectPlayProfile(type = DlnaProfileType.AUDIO), + ), + transcodingProfiles = + listOf( + TranscodingProfile( + container = container, + context = context, + protocol = MediaStreamProtocol.HLS, + audioCodec = "aac,ac3,eac3", + videoCodec = "hevc,h264", + type = DlnaProfileType.VIDEO, + conditions = + listOf( + ProfileCondition( + condition = ProfileConditionType.LESS_THAN_EQUAL, + property = ProfileConditionValue.VIDEO_BITRATE, + value = "8000000", + isRequired = true, + ), + ), + copyTimestamps = true, + enableSubtitlesInManifest = true, + transcodeSeekInfo = TranscodeSeekInfo.AUTO, + ), + ), + subtitleProfiles = + listOf( + SubtitleProfile("srt", SubtitleDeliveryMethod.EXTERNAL), + SubtitleProfile("ass", SubtitleDeliveryMethod.EXTERNAL), + SubtitleProfile("sub", SubtitleDeliveryMethod.EXTERNAL), + SubtitleProfile("vtt", SubtitleDeliveryMethod.EXTERNAL), + SubtitleProfile("ssa", SubtitleDeliveryMethod.EXTERNAL), + SubtitleProfile("pgs", SubtitleDeliveryMethod.EXTERNAL), + SubtitleProfile("dvb_teletext", SubtitleDeliveryMethod.EXTERNAL), + SubtitleProfile("dvd_subtitle", SubtitleDeliveryMethod.EXTERNAL), + ), ), - ), - subtitleProfiles = listOf( - SubtitleProfile("srt", SubtitleDeliveryMethod.EXTERNAL), - SubtitleProfile("ass", SubtitleDeliveryMethod.EXTERNAL), - SubtitleProfile("sub", SubtitleDeliveryMethod.EXTERNAL), - SubtitleProfile("vtt", SubtitleDeliveryMethod.EXTERNAL), - SubtitleProfile("ssa", SubtitleDeliveryMethod.EXTERNAL), - SubtitleProfile("pgs", SubtitleDeliveryMethod.EXTERNAL), - SubtitleProfile("dvb_teletext", SubtitleDeliveryMethod.EXTERNAL), - SubtitleProfile("dvd_subtitle", SubtitleDeliveryMethod.EXTERNAL) - ), ) - ) return deviceProfile.deviceProfile!! } - - override suspend fun getPostedPlaybackInfo(itemId: UUID ,enableDirectStream: Boolean ,deviceProfile: DeviceProfile ,maxBitrate: Int): Response { - val playbackInfo = jellyfinApi.mediaInfoApi.getPostedPlaybackInfo( - itemId = itemId, - PlaybackInfoDto( - userId = jellyfinApi.userId!!, - enableTranscoding = true, - enableDirectPlay = false, - enableDirectStream = enableDirectStream, - autoOpenLiveStream = true, - deviceProfile = deviceProfile, - allowAudioStreamCopy = true, - allowVideoStreamCopy = true, - maxStreamingBitrate = maxBitrate, + override suspend fun getPostedPlaybackInfo( + itemId: UUID, + enableDirectStream: Boolean, + deviceProfile: DeviceProfile, + maxBitrate: Int, + ): Response { + val playbackInfo = + jellyfinApi.mediaInfoApi.getPostedPlaybackInfo( + itemId = itemId, + PlaybackInfoDto( + userId = jellyfinApi.userId!!, + enableTranscoding = true, + enableDirectPlay = false, + enableDirectStream = enableDirectStream, + autoOpenLiveStream = true, + deviceProfile = deviceProfile, + allowAudioStreamCopy = true, + allowVideoStreamCopy = true, + maxStreamingBitrate = maxBitrate, + ), ) - ) return playbackInfo } - override suspend fun getVideoStreambyContainerUrl(itemId: UUID, deviceId: String, mediaSourceId: String, playSessionId: String, videoBitrate: Int, container: String): String { - val url = jellyfinApi.videosApi.getVideoStreamByContainerUrl( - itemId, - static = false, - deviceId = deviceId, - mediaSourceId = mediaSourceId, - playSessionId = playSessionId, - videoBitRate = videoBitrate, - audioBitRate = 384000, - videoCodec = "hevc", - audioCodec = "aac,ac3,eac3", - container = container, - startTimeTicks = 0, - copyTimestamps = true, - subtitleMethod = SubtitleDeliveryMethod.EXTERNAL - ) - return url - } - - override suspend fun getTranscodedVideoStream(itemId: UUID, deviceId: String, mediaSourceId: String, playSessionId: String, videoBitrate: Int): String { - val isAuto = videoBitrate == 12000000 - val url = if (!isAuto) { - jellyfinApi.api.dynamicHlsApi.getMasterHlsVideoPlaylistUrl( + override suspend fun getVideoStreambyContainerUrl( + itemId: UUID, + deviceId: String, + mediaSourceId: String, + playSessionId: String, + videoBitrate: Int, + container: String, + ): String { + val url = + jellyfinApi.videosApi.getVideoStreamByContainerUrl( itemId, static = false, deviceId = deviceId, mediaSourceId = mediaSourceId, playSessionId = playSessionId, videoBitRate = videoBitrate, - enableAdaptiveBitrateStreaming = false, - audioBitRate = 384000, //could also be passed with audioBitrate but i preferred not as its not much data anyways - videoCodec = "hevc,h264", - audioCodec = "aac,ac3,eac3", - startTimeTicks = 0, - copyTimestamps = true, - subtitleMethod = SubtitleDeliveryMethod.EXTERNAL, - context = EncodingContext.STREAMING, - segmentContainer = "ts", - transcodeReasons = "ContainerBitrateExceedsLimit", - ) - } else { - jellyfinApi.api.dynamicHlsApi.getMasterHlsVideoPlaylistUrl( - itemId, - static = false, - deviceId = deviceId, - mediaSourceId = mediaSourceId, - playSessionId = playSessionId, - enableAdaptiveBitrateStreaming = true, + audioBitRate = 384000, videoCodec = "hevc", audioCodec = "aac,ac3,eac3", + container = container, startTimeTicks = 0, copyTimestamps = true, subtitleMethod = SubtitleDeliveryMethod.EXTERNAL, - context = EncodingContext.STREAMING, - segmentContainer = "ts", - transcodeReasons = "ContainerBitrateExceedsLimit", ) - } return url } + override suspend fun getTranscodedVideoStream( + itemId: UUID, + deviceId: String, + mediaSourceId: String, + playSessionId: String, + videoBitrate: Int, + ): String { + val isAuto = videoBitrate == 12000000 + val url = + if (!isAuto) { + jellyfinApi.api.dynamicHlsApi.getMasterHlsVideoPlaylistUrl( + itemId, + static = false, + deviceId = deviceId, + mediaSourceId = mediaSourceId, + playSessionId = playSessionId, + videoBitRate = videoBitrate, + enableAdaptiveBitrateStreaming = false, + audioBitRate = 384000, // could also be passed with audioBitrate but i preferred not as its not much data anyways + videoCodec = "hevc,h264", + audioCodec = "aac,ac3,eac3", + startTimeTicks = 0, + copyTimestamps = true, + subtitleMethod = SubtitleDeliveryMethod.EXTERNAL, + context = EncodingContext.STREAMING, + segmentContainer = "ts", + transcodeReasons = "ContainerBitrateExceedsLimit", + ) + } else { + jellyfinApi.api.dynamicHlsApi.getMasterHlsVideoPlaylistUrl( + itemId, + static = false, + deviceId = deviceId, + mediaSourceId = mediaSourceId, + playSessionId = playSessionId, + enableAdaptiveBitrateStreaming = true, + videoCodec = "hevc", + audioCodec = "aac,ac3,eac3", + startTimeTicks = 0, + copyTimestamps = true, + subtitleMethod = SubtitleDeliveryMethod.EXTERNAL, + context = EncodingContext.STREAMING, + segmentContainer = "ts", + transcodeReasons = "ContainerBitrateExceedsLimit", + ) + } + return url + } override suspend fun getDeviceId(): String { val devices = jellyfinApi.devicesApi.getDevices(getUserId()) - return devices.content.items?.firstOrNull()?.id!! + return devices.content.items + ?.firstOrNull() + ?.id!! } override suspend fun stopEncodingProcess(playSessionId: String) { val deviceId = getDeviceId() jellyfinApi.api.hlsSegmentApi.stopEncodingProcess( deviceId = deviceId, - playSessionId = playSessionId + playSessionId = playSessionId, ) } - } - - diff --git a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt index 6901c09d..9658541f 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt @@ -42,14 +42,9 @@ class JellyfinRepositoryOfflineImpl( private val database: ServerDatabaseDao, private val appPreferences: AppPreferences, ) : JellyfinRepository { + override suspend fun getPublicSystemInfo(): PublicSystemInfo = throw Exception("System info not available in offline mode") - override suspend fun getPublicSystemInfo(): PublicSystemInfo { - throw Exception("System info not available in offline mode") - } - - override suspend fun getUserViews(): List { - return emptyList() - } + override suspend fun getUserViews(): List = emptyList() override suspend fun getItem(itemId: UUID): BaseItemDto { TODO("Not yet implemented") @@ -113,38 +108,69 @@ class JellyfinRepositoryOfflineImpl( TODO("Not yet implemented") } - override suspend fun getSearchItems(searchQuery: String): List { - return withContext(Dispatchers.IO) { - val movies = database.searchMovies(appPreferences.currentServer!!, searchQuery).map { it.toFindroidMovie(database, jellyfinApi.userId!!) } - val shows = database.searchShows(appPreferences.currentServer!!, searchQuery).map { it.toFindroidShow(database, jellyfinApi.userId!!) } - val episodes = database.searchEpisodes(appPreferences.currentServer!!, searchQuery).map { it.toFindroidEpisode(database, jellyfinApi.userId!!) } + override suspend fun getSearchItems(searchQuery: String): List = + withContext(Dispatchers.IO) { + val movies = + database.searchMovies(appPreferences.currentServer!!, searchQuery).map { + it.toFindroidMovie( + database, + @Suppress("ktlint:standard:max-line-length") + jellyfinApi.userId!!, + ) + } + val shows = + database + .searchShows( + appPreferences.currentServer!!, + searchQuery, + ).map { it.toFindroidShow(database, jellyfinApi.userId!!) } + val episodes = + database.searchEpisodes(appPreferences.currentServer!!, searchQuery).map { + it.toFindroidEpisode(database, jellyfinApi.userId!!) + } movies + shows + episodes } - } - override suspend fun getResumeItems(): List { - return withContext(Dispatchers.IO) { - val movies = database.getMoviesByServerId(appPreferences.currentServer!!).map { it.toFindroidMovie(database, jellyfinApi.userId!!) }.filter { it.playbackPositionTicks > 0 } - val episodes = database.getEpisodesByServerId(appPreferences.currentServer!!).map { it.toFindroidEpisode(database, jellyfinApi.userId!!) }.filter { it.playbackPositionTicks > 0 } + override suspend fun getResumeItems(): List = + withContext(Dispatchers.IO) { + val movies = + database + .getMoviesByServerId( + appPreferences.currentServer!!, + ).map { it.toFindroidMovie(database, jellyfinApi.userId!!) } + .filter { + it.playbackPositionTicks > + 0 + } + val episodes = + database + .getEpisodesByServerId( + appPreferences.currentServer!!, + ).map { it.toFindroidEpisode(database, jellyfinApi.userId!!) } + .filter { + it.playbackPositionTicks > + 0 + } movies + episodes } - } - override suspend fun getLatestMedia(parentId: UUID): List { - return emptyList() - } + override suspend fun getLatestMedia(parentId: UUID): List = emptyList() - override suspend fun getSeasons(seriesId: UUID, offline: Boolean): List = + override suspend fun getSeasons( + seriesId: UUID, + offline: Boolean, + ): List = withContext(Dispatchers.IO) { database.getSeasonsByShowId(seriesId).map { it.toFindroidSeason(database, jellyfinApi.userId!!) } } - override suspend fun getNextUp(seriesId: UUID?): List { - return withContext(Dispatchers.IO) { + override suspend fun getNextUp(seriesId: UUID?): List = + withContext(Dispatchers.IO) { val result = mutableListOf() - val shows = database.getShowsByServerId(appPreferences.currentServer!!).filter { - if (seriesId != null) it.id == seriesId else true - } + val shows = + database.getShowsByServerId(appPreferences.currentServer!!).filter { + if (seriesId != null) it.id == seriesId else true + } for (show in shows) { val episodes = database.getEpisodesByShowId(show.id).map { it.toFindroidEpisode(database, jellyfinApi.userId!!) } val indexOfLastPlayed = episodes.indexOfLast { it.played } @@ -156,7 +182,6 @@ class JellyfinRepositoryOfflineImpl( } result.filter { it.playbackPositionTicks == 0L } } - } override suspend fun getEpisodes( seriesId: UUID, @@ -172,12 +197,19 @@ class JellyfinRepositoryOfflineImpl( items } - override suspend fun getMediaSources(itemId: UUID, includePath: Boolean): List = + override suspend fun getMediaSources( + itemId: UUID, + includePath: Boolean, + ): List = withContext(Dispatchers.IO) { database.getSources(itemId).map { it.toFindroidSource(database) } } - override suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String, playSessionId: String?): String { + override suspend fun getStreamUrl( + itemId: UUID, + mediaSourceId: String, + playSessionId: String?, + ): String { TODO("Not yet implemented") } @@ -186,7 +218,11 @@ class JellyfinRepositoryOfflineImpl( database.getIntro(itemId)?.toIntro() } - override suspend fun getTrickplayData(itemId: UUID, width: Int, index: Int): ByteArray? = + override suspend fun getTrickplayData( + itemId: UUID, + width: Int, + index: Int, + ): ByteArray? = withContext(Dispatchers.IO) { try { val sources = File(context.filesDir, "trickplay/$itemId").listFiles() ?: return@withContext null @@ -200,7 +236,11 @@ class JellyfinRepositoryOfflineImpl( override suspend fun postPlaybackStart(itemId: UUID) {} - override suspend fun postPlaybackStop(itemId: UUID, positionTicks: Long, playedPercentage: Int) { + override suspend fun postPlaybackStop( + itemId: UUID, + positionTicks: Long, + playedPercentage: Int, + ) { withContext(Dispatchers.IO) { when { playedPercentage < 10 -> { @@ -260,35 +300,31 @@ class JellyfinRepositoryOfflineImpl( } } - override fun getBaseUrl(): String { - return "" - } + override fun getBaseUrl(): String = "" override suspend fun updateDeviceName(name: String) { TODO("Not yet implemented") } - override suspend fun getUserConfiguration(): UserConfiguration? { - return null - } + override suspend fun getUserConfiguration(): UserConfiguration? = null override suspend fun getDownloads(): List = withContext(Dispatchers.IO) { val items = mutableListOf() items.addAll( - database.getMoviesByServerId(appPreferences.currentServer!!) + database + .getMoviesByServerId(appPreferences.currentServer!!) .map { it.toFindroidMovie(database, jellyfinApi.userId!!) }, ) items.addAll( - database.getShowsByServerId(appPreferences.currentServer!!) + database + .getShowsByServerId(appPreferences.currentServer!!) .map { it.toFindroidShow(database, jellyfinApi.userId!!) }, ) items } - override fun getUserId(): UUID { - return jellyfinApi.userId!! - } + override fun getUserId(): UUID = jellyfinApi.userId!! override suspend fun getDeviceId(): String { TODO("Not yet implemented") @@ -301,7 +337,7 @@ class JellyfinRepositoryOfflineImpl( override suspend fun buildDeviceProfile( maxBitrate: Int, container: String, - context: EncodingContext + context: EncodingContext, ): DeviceProfile { TODO("Not yet implemented") } @@ -312,7 +348,7 @@ class JellyfinRepositoryOfflineImpl( mediaSourceId: String, playSessionId: String, videoBitrate: Int, - container: String + container: String, ): String { TODO("Not yet implemented") } @@ -322,7 +358,7 @@ class JellyfinRepositoryOfflineImpl( deviceId: String, mediaSourceId: String, playSessionId: String, - videoBitrate: Int + videoBitrate: Int, ): String { TODO("Not yet implemented") } @@ -331,7 +367,7 @@ class JellyfinRepositoryOfflineImpl( itemId: UUID, enableDirectStream: Boolean, deviceProfile: DeviceProfile, - maxBitrate: Int + maxBitrate: Int, ): Response { TODO("Not yet implemented") }