Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<TransferListener>()
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<String, List<String>> = 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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -27,8 +26,7 @@ class PlaybackDataSourceFactory @Inject constructor(
private val context: Context,
private val cache: Lazy<Cache>,
private val fileDataStorageManager: FileDataStorageManager,
private val clientFactory: ClientFactory,
private val accountManager: UserAccountManager
private val clientRepository: ClientRepository
) : DataSource.Factory {

override fun createDataSource(): DataSource = CacheDataSource.Factory()
Expand All @@ -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())
}
}
Loading