diff --git a/composeApp/src/desktopMain/kotlin/com/nuvio/app/core/ui/PlatformImageLoader.desktop.kt b/composeApp/src/desktopMain/kotlin/com/nuvio/app/core/ui/PlatformImageLoader.desktop.kt index a1eed447a..1a2a53914 100644 --- a/composeApp/src/desktopMain/kotlin/com/nuvio/app/core/ui/PlatformImageLoader.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/nuvio/app/core/ui/PlatformImageLoader.desktop.kt @@ -1,9 +1,41 @@ package com.nuvio.app.core.ui import coil3.ImageLoader +import coil3.disk.DiskCache +import coil3.memory.MemoryCache +import okio.Path.Companion.toOkioPath +import java.io.File -internal actual fun ImageLoader.Builder.configurePlatformImageLoader(): ImageLoader.Builder { - return components { - add(SkiaGifDecoder.Factory()) +private fun resolveDesktopCacheDir(): File { + val userHome = System.getProperty("user.home").orEmpty() + val os = System.getProperty("os.name").orEmpty().lowercase() + val baseDir = when { + os.contains("win") -> System.getenv("LOCALAPPDATA")?.takeIf(String::isNotBlank)?.let { File(it, "Nuvio") } + ?: File(userHome, ".nuvio") + os.contains("mac") -> File(userHome, "Library/Caches/com.nuvio.app") + else -> System.getenv("XDG_CACHE_HOME")?.takeIf(String::isNotBlank)?.let { File(it, "nuvio") } + ?: File(userHome, ".cache/nuvio") } + return File(baseDir, "image_cache").apply { mkdirs() } +} + +internal actual fun ImageLoader.Builder.configurePlatformImageLoader(): ImageLoader.Builder { + val maxMemory = Runtime.getRuntime().maxMemory() + val memoryCacheBytes = if (maxMemory > 0L) (maxMemory * 0.25).toLong() else 256L * 1024L * 1024L + + return this + .memoryCache { + MemoryCache.Builder() + .maxSizeBytes(memoryCacheBytes) + .build() + } + .diskCache { + DiskCache.Builder() + .directory(resolveDesktopCacheDir().toOkioPath()) + .maxSizeBytes(200L * 1024L * 1024L) // 200MB (matching TV & Mobile) + .build() + } + .components { + add(SkiaGifDecoder.Factory()) + } } diff --git a/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.desktop.kt b/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.desktop.kt index 93e6e0e5c..ee88f5c07 100644 --- a/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.desktop.kt @@ -44,6 +44,11 @@ internal actual object AddonStorage { } private val desktopHttpClient = OkHttpClient.Builder() + .dispatcher(okhttp3.Dispatcher().apply { + maxRequests = 64 + maxRequestsPerHost = 16 + }) + .connectionPool(okhttp3.ConnectionPool(16, 5, TimeUnit.MINUTES)) .dns(DesktopIPv4FirstDns()) .connectTimeout(60, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) @@ -52,6 +57,7 @@ private val desktopHttpClient = OkHttpClient.Builder() .followSslRedirects(true) .build() + private const val truncationSuffix = "\n...[truncated]" actual suspend fun httpGetText(url: String): String = diff --git a/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/trailer/TrailerExtractionPlatform.desktop.kt b/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/trailer/TrailerExtractionPlatform.desktop.kt index 368c79c0f..e2b225be9 100644 --- a/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/trailer/TrailerExtractionPlatform.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/trailer/TrailerExtractionPlatform.desktop.kt @@ -28,6 +28,8 @@ internal object TrailerExtractionPlatform { ) private val httpClient = OkHttpClient.Builder() + .dns(com.nuvio.app.core.network.DesktopIPv4FirstDns()) + .connectionPool(okhttp3.ConnectionPool(8, 5, TimeUnit.MINUTES)) .connectTimeout(TRAILER_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) .readTimeout(TRAILER_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) .writeTimeout(TRAILER_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) @@ -36,12 +38,15 @@ internal object TrailerExtractionPlatform { .build() private val probeClient = OkHttpClient.Builder() + .dns(com.nuvio.app.core.network.DesktopIPv4FirstDns()) + .connectionPool(okhttp3.ConnectionPool(8, 5, TimeUnit.MINUTES)) .connectTimeout(2, TimeUnit.SECONDS) .readTimeout(2, TimeUnit.SECONDS) .followRedirects(true) .followSslRedirects(true) .build() + fun supportsSeparateVideo(candidate: StreamCandidate): Boolean = candidate.ext == "mp4" fun supportsSeparateAudio(candidate: StreamCandidate): Boolean = candidate.ext == "m4a"