Download primary images

This commit is contained in:
Niels van Velzen
2026-06-11 09:05:05 +02:00
committed by Niels van Velzen
parent 641105f08f
commit ee8ce1607e
2 changed files with 61 additions and 20 deletions
@@ -1,6 +1,7 @@
package org.jellyfin.mobile.downloads
import android.content.Context
import android.net.Uri
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import kotlinx.coroutines.CancellationException
@@ -10,8 +11,11 @@ import org.jellyfin.mobile.app.StorageManager
import org.jellyfin.mobile.data.dao.DownloadDao
import org.jellyfin.mobile.data.entity.DownloadEntity
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.imageApi
import org.jellyfin.sdk.api.client.extensions.libraryApi
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.ImageFormat
import org.jellyfin.sdk.model.api.ImageType
class DownloadQueue(
private val context: Context,
@@ -58,9 +62,9 @@ class DownloadQueue(
val api = apiClientController.getApiClient(download.serverId, download.userId)
val storageLocation = storageManager.getStorageLocation()
val itemLocation = storageLocation.findFile(download.path) ?: storageLocation.createDirectory(download.path) ?: error("Unable to find or create folder ${download.path}")
val itemLocation = storageLocation.findFile(download.path) ?: storageLocation.createDirectory(download.path)
?: error("Unable to find or create folder ${download.path}")
// TODO: Download all mediastreams, thumbnail etc.
download(
api = api,
item = download.item,
@@ -84,16 +88,51 @@ class DownloadQueue(
itemLocation: DocumentFile,
progressCallback: FileDownloader.ProgressCallback,
) {
val filename = item.path?.replace(Regex("^.*[\\\\/]"), "") ?: error("Missing item path")
// Main file
download(
api = api,
itemLocation = itemLocation,
fileName = item.path?.replace(Regex("^.*[\\\\/]"), "") ?: error("Missing item path"),
uri = api.libraryApi.getDownloadUrl(item.id).toUri(),
progressCallback = progressCallback,
)
val fileLocation = itemLocation.findFile(filename) ?: itemLocation.createFile("", filename) ?: error("Unable to find or create file $filename")
if (!fileLocation.canRead() || !fileLocation.canWrite()) error("Not allowed to read-write $fileLocation")
// Primary image
item.imageTags?.get(ImageType.PRIMARY)?.let { imageTag ->
download(
api = api,
itemLocation = itemLocation,
fileName = "primary.webp",
uri = api.imageApi.getItemImageUrl(
itemId = item.id,
imageType = ImageType.PRIMARY,
tag = imageTag,
format = ImageFormat.WEBP
).toUri(),
progressCallback = progressCallback,
)
}
}
val fileDescriptor = context.contentResolver.openFileDescriptor(fileLocation.uri, "rw") ?: error("Unable to open file descriptor for $fileLocation")
private suspend fun download(
api: ApiClient,
itemLocation: DocumentFile,
fileName: String,
uri: Uri,
progressCallback: FileDownloader.ProgressCallback,
) {
val documentFile = itemLocation.findFile(fileName)
?: itemLocation.createFile("", fileName)
?: error("Unable to find or create file $fileName")
if (!documentFile.canRead() || !documentFile.canWrite()) error("Not allowed to read-write $fileName")
val fileDescriptor = context.contentResolver.openFileDescriptor(documentFile.uri, "rw")
?: error("Unable to open file descriptor for $fileName")
_downloader.downloadAndSave(
api,
from = api.libraryApi.getDownloadUrl(item.id).toUri(),
api = api,
from = uri,
to = fileDescriptor,
progressCallback = progressCallback,
)
@@ -1,12 +1,14 @@
package org.jellyfin.mobile.ui.screens.downloads
import android.content.Context
import android.net.Uri
import android.text.format.Formatter
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.AlertDialog
@@ -27,7 +29,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
@@ -43,10 +44,8 @@ import org.jellyfin.mobile.downloads.DownloadStatus
import org.jellyfin.mobile.downloads.DownloadsViewModel
import org.jellyfin.mobile.utils.lengthRecursive
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.imageApi
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ImageType
import org.koin.compose.koinInject
@Composable
@@ -158,21 +157,24 @@ fun DownloadItem(
)
},
icon = {
val maxSize = LocalResources.current.getDimensionPixelSize(R.dimen.movie_thumbnail_list_size)
val url = remember(apiClient, download.itemId, maxSize) {
apiClient.imageApi.getItemImageUrl(
itemId = download.itemId,
imageType = ImageType.PRIMARY,
maxWidth = maxSize,
maxHeight = maxSize,
)
val uri by produceState<Uri?>(initialValue = null, download) {
value = withContext(Dispatchers.IO) {
storageManager.getStorageLocation()
.findFile(download.path)
?.findFile("primary.webp")
?.uri
}
}
AsyncImage(
model = url,
model = uri,
placeholder = painterResource(R.drawable.ic_local_movies_white_64),
fallback = painterResource(R.drawable.ic_local_movies_white_64),
contentDescription = null,
modifier = Modifier.sizeIn(
maxWidth = 64.dp,
maxHeight = 64.dp,
)
)
},
secondaryText = {