diff --git a/app/src/main/java/com/nextcloud/client/player/media3/datasource/PlaybackDataSource.kt b/app/src/main/java/com/nextcloud/client/player/media3/datasource/PlaybackDataSource.kt index 6a54455b05ad..e83f5c4b7eb1 100644 --- a/app/src/main/java/com/nextcloud/client/player/media3/datasource/PlaybackDataSource.kt +++ b/app/src/main/java/com/nextcloud/client/player/media3/datasource/PlaybackDataSource.kt @@ -12,46 +12,90 @@ import androidx.core.net.toUri import androidx.media3.common.util.UnstableApi import androidx.media3.datasource.DataSource import androidx.media3.datasource.DataSpec +import androidx.media3.datasource.TransferListener import com.nextcloud.client.player.util.PlayerUtil.getRemoteFileId +import com.nextcloud.common.NextcloudClient +import com.nextcloud.repository.ClientRepository import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile import com.owncloud.android.files.StreamMediaFileOperation import com.owncloud.android.lib.common.OwnCloudClient +import kotlinx.coroutines.runBlocking import java.io.IOException @UnstableApi class PlaybackDataSource( - private val delegate: DataSource, + private val createDelegate: (NextcloudClient) -> DataSource, private val fileDataStorageManager: FileDataStorageManager, - private val ownCloudClient: OwnCloudClient -) : DataSource by delegate { + private val clientRepository: ClientRepository +) : DataSource { - override fun getResponseHeaders() = delegate.responseHeaders + private val transferListeners = mutableListOf() + private var delegate: DataSource? = null + private var ownCloudClient: OwnCloudClient? = null + + override fun addTransferListener(transferListener: TransferListener) { + transferListeners.add(transferListener) + delegate?.addTransferListener(transferListener) + } override fun open(dataSpec: DataSpec): Long { + val delegate = getOrCreateDelegate() val fileId = dataSpec.uri.getRemoteFileId() ?: return delegate.open(dataSpec) val file = fileDataStorageManager.getFileByLocalId(fileId) return if (file != null && file.isDown) { - openStoredFile(dataSpec, file) + openStoredFile(delegate, dataSpec, file) } else { - openRemoteFile(dataSpec, fileId) + openRemoteFile(delegate, dataSpec, fileId) } } - private fun openStoredFile(dataSpec: DataSpec, file: OCFile): Long { + override fun read(buffer: ByteArray, offset: Int, length: Int): Int = + checkNotNull(delegate) { "Data source is not opened" }.read(buffer, offset, length) + + override fun getUri(): Uri? = delegate?.uri + + override fun getResponseHeaders(): Map> = delegate?.responseHeaders ?: emptyMap() + + override fun close() { + delegate?.close() + } + + private fun getOrCreateDelegate(): DataSource { + delegate?.let { return it } + + val nextcloudClient = runBlocking { clientRepository.getNextcloudClient() } + ?: throw IOException("Nextcloud client is not available, cannot play media") + + return createDelegate(nextcloudClient).also { created -> + transferListeners.forEach(created::addTransferListener) + delegate = created + } + } + + private fun openStoredFile(delegate: DataSource, dataSpec: DataSpec, file: OCFile): Long { val uri = file.storageUri return delegate.open(dataSpec.buildUpon(uri)) } - private fun openRemoteFile(dataSpec: DataSpec, fileId: Long): Long { - val result = StreamMediaFileOperation(fileId).execute(ownCloudClient) - return if (result.isSuccess) { - val uri = (result.data[0] as? String)?.toUri() - ?: throw IllegalStateException("url is not valid, cannot stream") - delegate.open(dataSpec.buildUpon(uri)) - } else { + private fun getOrCreateOwnCloudClient(): OwnCloudClient { + ownCloudClient?.let { return it } + + val client = runBlocking { clientRepository.getOwncloudClient() } + ?: throw IOException("OwnCloud client is not available, cannot stream") + + return client.also { ownCloudClient = it } + } + + private fun openRemoteFile(delegate: DataSource, dataSpec: DataSpec, fileId: Long): Long { + val result = StreamMediaFileOperation(fileId).execute(getOrCreateOwnCloudClient()) + if (!result.isSuccess) { throw IOException("Failed to retrieve streaming uri", result.exception) } + + val uri = (result.data[0] as? String)?.toUri() + ?: throw IllegalStateException("url is not valid, cannot stream") + return delegate.open(dataSpec.buildUpon(uri)) } private fun DataSpec.buildUpon(uri: Uri) = buildUpon().setUri(uri).build() diff --git a/app/src/main/java/com/nextcloud/client/player/media3/datasource/PlaybackDataSourceFactory.kt b/app/src/main/java/com/nextcloud/client/player/media3/datasource/PlaybackDataSourceFactory.kt index f0998a79343e..d24a2ef64c30 100644 --- a/app/src/main/java/com/nextcloud/client/player/media3/datasource/PlaybackDataSourceFactory.kt +++ b/app/src/main/java/com/nextcloud/client/player/media3/datasource/PlaybackDataSourceFactory.kt @@ -4,7 +4,6 @@ * SPDX-FileCopyrightText: 2025 STRATO GmbH. * SPDX-License-Identifier: AGPL-3.0-or-later */ - package com.nextcloud.client.player.media3.datasource import android.content.Context @@ -15,8 +14,8 @@ import androidx.media3.datasource.HttpDataSource import androidx.media3.datasource.cache.Cache import androidx.media3.datasource.cache.CacheDataSource import androidx.media3.datasource.okhttp.OkHttpDataSource -import com.nextcloud.client.account.UserAccountManager -import com.nextcloud.client.network.ClientFactory +import com.nextcloud.common.NextcloudClient +import com.nextcloud.repository.ClientRepository import com.owncloud.android.MainApp import com.owncloud.android.datamodel.FileDataStorageManager import dagger.Lazy @@ -27,8 +26,7 @@ class PlaybackDataSourceFactory @Inject constructor( private val context: Context, private val cache: Lazy, private val fileDataStorageManager: FileDataStorageManager, - private val clientFactory: ClientFactory, - private val accountManager: UserAccountManager + private val clientRepository: ClientRepository ) : DataSource.Factory { override fun createDataSource(): DataSource = CacheDataSource.Factory() @@ -38,15 +36,15 @@ class PlaybackDataSourceFactory @Inject constructor( private fun createUpstreamDataSourceFactory() = DataSource.Factory { PlaybackDataSource( - delegate = DefaultDataSource.Factory(context, createHttpDataSourceFactory()).createDataSource(), + createDelegate = { nextcloudClient -> + DefaultDataSource.Factory(context, createHttpDataSourceFactory(nextcloudClient)).createDataSource() + }, fileDataStorageManager = fileDataStorageManager, - ownCloudClient = clientFactory.create(accountManager.user) + clientRepository = clientRepository ) } - private fun createHttpDataSourceFactory(): HttpDataSource.Factory { - val client = clientFactory.createNextcloudClient(accountManager.user).client - return OkHttpDataSource.Factory(client) + private fun createHttpDataSourceFactory(nextcloudClient: NextcloudClient): HttpDataSource.Factory = + OkHttpDataSource.Factory(nextcloudClient.client) .setUserAgent(MainApp.getUserAgent()) - } }