fix(downloads): passing resume position

This commit is contained in:
tapframe 2026-04-11 21:30:44 +05:30
parent 7437f54ab8
commit 7c2cad51b9
6 changed files with 33 additions and 5 deletions

View file

@ -1,20 +1,24 @@
package com.nuvio.app.features.player package com.nuvio.app.features.player
import android.content.Context
import androidx.media3.datasource.DataSource import androidx.media3.datasource.DataSource
import androidx.media3.datasource.DefaultDataSource
import androidx.media3.datasource.DefaultHttpDataSource import androidx.media3.datasource.DefaultHttpDataSource
import com.nuvio.app.features.trailer.YoutubeChunkedDataSourceFactory import com.nuvio.app.features.trailer.YoutubeChunkedDataSourceFactory
internal object PlatformPlaybackDataSourceFactory { internal object PlatformPlaybackDataSourceFactory {
fun create( fun create(
context: Context,
defaultRequestHeaders: Map<String, String>, defaultRequestHeaders: Map<String, String>,
defaultResponseHeaders: Map<String, String>, defaultResponseHeaders: Map<String, String>,
useYoutubeChunkedPlayback: Boolean, useYoutubeChunkedPlayback: Boolean,
): DataSource.Factory { ): DataSource.Factory {
val baseFactory: DataSource.Factory = if (useYoutubeChunkedPlayback) { val networkFactory: DataSource.Factory = if (useYoutubeChunkedPlayback) {
YoutubeChunkedDataSourceFactory(defaultRequestHeaders = defaultRequestHeaders) YoutubeChunkedDataSourceFactory(defaultRequestHeaders = defaultRequestHeaders)
} else { } else {
DefaultHttpDataSource.Factory().setDefaultRequestProperties(defaultRequestHeaders) DefaultHttpDataSource.Factory().setDefaultRequestProperties(defaultRequestHeaders)
} }
val baseFactory: DataSource.Factory = DefaultDataSource.Factory(context, networkFactory)
return if (defaultResponseHeaders.isEmpty()) { return if (defaultResponseHeaders.isEmpty()) {
baseFactory baseFactory
} else { } else {

View file

@ -92,7 +92,8 @@ internal actual object DownloadsPlatformDownloader {
tempFile.delete() tempFile.delete()
} }
onSuccess(destination.toURI().toString(), totalBytes) val finalSize = destination.length()
onSuccess(destination.toURI().toString(), totalBytes ?: finalSize)
} }
} catch (_: CancellationException) { } catch (_: CancellationException) {
tempFile.delete() tempFile.delete()

View file

@ -128,6 +128,7 @@ actual fun PlatformPlayerSurface(
.setTsExtractorTimestampSearchBytes(1500 * TsExtractor.TS_PACKET_SIZE) .setTsExtractorTimestampSearchBytes(1500 * TsExtractor.TS_PACKET_SIZE)
val dataSourceFactory = PlatformPlaybackDataSourceFactory.create( val dataSourceFactory = PlatformPlaybackDataSourceFactory.create(
context = context,
defaultRequestHeaders = sanitizedSourceHeaders, defaultRequestHeaders = sanitizedSourceHeaders,
defaultResponseHeaders = sanitizedSourceResponseHeaders, defaultResponseHeaders = sanitizedSourceResponseHeaders,
useYoutubeChunkedPlayback = useYoutubeChunkedPlayback, useYoutubeChunkedPlayback = useYoutubeChunkedPlayback,

View file

@ -1,16 +1,20 @@
package com.nuvio.app.features.player package com.nuvio.app.features.player
import android.content.Context
import androidx.media3.datasource.DataSource import androidx.media3.datasource.DataSource
import androidx.media3.datasource.DefaultDataSource
import androidx.media3.datasource.DefaultHttpDataSource import androidx.media3.datasource.DefaultHttpDataSource
internal object PlatformPlaybackDataSourceFactory { internal object PlatformPlaybackDataSourceFactory {
fun create( fun create(
context: Context,
defaultRequestHeaders: Map<String, String>, defaultRequestHeaders: Map<String, String>,
defaultResponseHeaders: Map<String, String>, defaultResponseHeaders: Map<String, String>,
useYoutubeChunkedPlayback: Boolean, useYoutubeChunkedPlayback: Boolean,
): DataSource.Factory { ): DataSource.Factory {
val baseFactory = DefaultHttpDataSource.Factory() val httpFactory = DefaultHttpDataSource.Factory()
.setDefaultRequestProperties(defaultRequestHeaders) .setDefaultRequestProperties(defaultRequestHeaders)
val baseFactory: DataSource.Factory = DefaultDataSource.Factory(context, httpFactory)
return if (defaultResponseHeaders.isEmpty()) { return if (defaultResponseHeaders.isEmpty()) {
baseFactory baseFactory
} else { } else {

View file

@ -1250,6 +1250,11 @@ private fun MainAppContent(
onBack = onBack, onBack = onBack,
onOpenDownload = { item -> onOpenDownload = { item ->
val sourceUrl = item.localFileUri ?: return@DownloadsScreen val sourceUrl = item.localFileUri ?: return@DownloadsScreen
val resumeEntry = item.videoId
.takeIf { it.isNotBlank() }
?.let(WatchProgressRepository::progressForVideo)
?.takeIf { it.isResumable }
val launchId = PlayerLaunchStore.put( val launchId = PlayerLaunchStore.put(
PlayerLaunch( PlayerLaunch(
title = item.title, title = item.title,
@ -1271,7 +1276,8 @@ private fun MainAppContent(
videoId = item.videoId, videoId = item.videoId,
parentMetaId = item.parentMetaId, parentMetaId = item.parentMetaId,
parentMetaType = item.parentMetaType, parentMetaType = item.parentMetaType,
initialPositionMs = 0L, initialPositionMs = resumeEntry?.lastPositionMs?.takeIf { it > 0L } ?: 0L,
initialProgressFraction = resumeEntry?.progressFraction?.takeIf { it > 0f },
), ),
) )
navController.navigate(PlayerRoute(launchId = launchId)) navController.navigate(PlayerRoute(launchId = launchId))

View file

@ -88,7 +88,8 @@ internal actual object DownloadsPlatformDownloader {
} }
val localFileUri = NSURL.fileURLWithPath(destinationPath).absoluteString ?: "file://$destinationPath" val localFileUri = NSURL.fileURLWithPath(destinationPath).absoluteString ?: "file://$destinationPath"
onSuccess(localFileUri, totalBytes) val finalSize = fileSizeOrNull(destinationPath)
onSuccess(localFileUri, totalBytes ?: finalSize)
} catch (_: CancellationException) { } catch (_: CancellationException) {
removePathIfExists(tempPath) removePathIfExists(tempPath)
} catch (error: Throwable) { } 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? { private fun String.toLocalPath(): String? {
if (startsWith("file://")) { if (startsWith("file://")) {
return removePrefix("file://") return removePrefix("file://")