diff --git a/composeApp/src/androidFull/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt b/composeApp/src/androidFull/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt index 2a30c309..4dc10c27 100644 --- a/composeApp/src/androidFull/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt +++ b/composeApp/src/androidFull/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt @@ -1,20 +1,24 @@ package com.nuvio.app.features.player +import android.content.Context import androidx.media3.datasource.DataSource +import androidx.media3.datasource.DefaultDataSource import androidx.media3.datasource.DefaultHttpDataSource import com.nuvio.app.features.trailer.YoutubeChunkedDataSourceFactory internal object PlatformPlaybackDataSourceFactory { fun create( + context: Context, defaultRequestHeaders: Map, defaultResponseHeaders: Map, useYoutubeChunkedPlayback: Boolean, ): DataSource.Factory { - val baseFactory: DataSource.Factory = if (useYoutubeChunkedPlayback) { + val networkFactory: DataSource.Factory = if (useYoutubeChunkedPlayback) { YoutubeChunkedDataSourceFactory(defaultRequestHeaders = defaultRequestHeaders) } else { DefaultHttpDataSource.Factory().setDefaultRequestProperties(defaultRequestHeaders) } + val baseFactory: DataSource.Factory = DefaultDataSource.Factory(context, networkFactory) return if (defaultResponseHeaders.isEmpty()) { baseFactory } else { diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.android.kt index 9ad04243..0d3b0724 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.android.kt @@ -92,7 +92,8 @@ internal actual object DownloadsPlatformDownloader { tempFile.delete() } - onSuccess(destination.toURI().toString(), totalBytes) + val finalSize = destination.length() + onSuccess(destination.toURI().toString(), totalBytes ?: finalSize) } } catch (_: CancellationException) { tempFile.delete() diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt index a1421720..cf975fa9 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt @@ -128,6 +128,7 @@ actual fun PlatformPlayerSurface( .setTsExtractorTimestampSearchBytes(1500 * TsExtractor.TS_PACKET_SIZE) val dataSourceFactory = PlatformPlaybackDataSourceFactory.create( + context = context, defaultRequestHeaders = sanitizedSourceHeaders, defaultResponseHeaders = sanitizedSourceResponseHeaders, useYoutubeChunkedPlayback = useYoutubeChunkedPlayback, diff --git a/composeApp/src/androidPlaystore/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt b/composeApp/src/androidPlaystore/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt index 0935a904..d5d312db 100644 --- a/composeApp/src/androidPlaystore/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt +++ b/composeApp/src/androidPlaystore/kotlin/com/nuvio/app/features/player/PlatformPlaybackDataSourceFactory.android.kt @@ -1,16 +1,20 @@ package com.nuvio.app.features.player +import android.content.Context import androidx.media3.datasource.DataSource +import androidx.media3.datasource.DefaultDataSource import androidx.media3.datasource.DefaultHttpDataSource internal object PlatformPlaybackDataSourceFactory { fun create( + context: Context, defaultRequestHeaders: Map, defaultResponseHeaders: Map, useYoutubeChunkedPlayback: Boolean, ): DataSource.Factory { - val baseFactory = DefaultHttpDataSource.Factory() + val httpFactory = DefaultHttpDataSource.Factory() .setDefaultRequestProperties(defaultRequestHeaders) + val baseFactory: DataSource.Factory = DefaultDataSource.Factory(context, httpFactory) return if (defaultResponseHeaders.isEmpty()) { baseFactory } else { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt index 83df5a1a..2122c545 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt @@ -1250,6 +1250,11 @@ private fun MainAppContent( onBack = onBack, onOpenDownload = { item -> val sourceUrl = item.localFileUri ?: return@DownloadsScreen + val resumeEntry = item.videoId + .takeIf { it.isNotBlank() } + ?.let(WatchProgressRepository::progressForVideo) + ?.takeIf { it.isResumable } + val launchId = PlayerLaunchStore.put( PlayerLaunch( title = item.title, @@ -1271,7 +1276,8 @@ private fun MainAppContent( videoId = item.videoId, parentMetaId = item.parentMetaId, parentMetaType = item.parentMetaType, - initialPositionMs = 0L, + initialPositionMs = resumeEntry?.lastPositionMs?.takeIf { it > 0L } ?: 0L, + initialProgressFraction = resumeEntry?.progressFraction?.takeIf { it > 0f }, ), ) navController.navigate(PlayerRoute(launchId = launchId)) diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.ios.kt index 1af2dc5f..746a742e 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.ios.kt @@ -88,7 +88,8 @@ internal actual object DownloadsPlatformDownloader { } val localFileUri = NSURL.fileURLWithPath(destinationPath).absoluteString ?: "file://$destinationPath" - onSuccess(localFileUri, totalBytes) + val finalSize = fileSizeOrNull(destinationPath) + onSuccess(localFileUri, totalBytes ?: finalSize) } catch (_: CancellationException) { removePathIfExists(tempPath) } catch (error: Throwable) { @@ -174,6 +175,17 @@ private suspend fun writeChannelToFile( } } +@OptIn(ExperimentalForeignApi::class) +private fun fileSizeOrNull(path: String): Long? { + val attrs = NSFileManager.defaultManager.attributesOfItemAtPath(path, error = null) + val value = attrs?.get("NSFileSize") + return when (value) { + is Long -> value + is Number -> value.toLong() + else -> null + } +} + private fun String.toLocalPath(): String? { if (startsWith("file://")) { return removePrefix("file://")