Skip to content
Snippets Groups Projects
Commit 08e91c1e authored by Taras's avatar Taras
Browse files

Add video prefetch

parent b6230f27
No related branches found
No related tags found
No related merge requests found
package org.futo.circles.feature.timeline
import android.content.Context
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.asLiveData
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import org.futo.circles.core.base.SingleEventLiveData
......@@ -32,6 +34,7 @@ import javax.inject.Inject
@HiltViewModel
class TimelineViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
@ApplicationContext context: Context,
roomNotificationsDataSource: RoomNotificationsDataSource,
timelineDataSourceFactory: BaseTimelineDataSource.Factory,
accessLevelDataSource: AccessLevelDataSource,
......@@ -43,6 +46,7 @@ class TimelineViewModel @Inject constructor(
circleFilterAccountDataManager: CircleFilterAccountDataManager
) : BaseTimelineViewModel(
savedStateHandle,
context,
timelineDataSourceFactory.create(savedStateHandle.get<String>("timelineId") != null),
circleFilterAccountDataManager
) {
......
......@@ -6,7 +6,6 @@ import org.futo.circles.core.extensions.visible
import org.futo.circles.core.model.LoadingData
import org.futo.circles.core.view.LoadingView
import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
object MediaProgressHelper {
......@@ -52,31 +51,6 @@ object MediaProgressHelper {
)
}
else -> loadingView.gone()
}
}
}
fun getDownloadListener(loadingView: LoadingView): ContentDownloadStateTracker.UpdateListener =
object : ContentDownloadStateTracker.UpdateListener {
override fun onDownloadStateUpdate(state: ContentDownloadStateTracker.State) {
when (state) {
ContentDownloadStateTracker.State.Decrypting -> {
loadingView.visible()
loadingView.setProgress(LoadingData(R.string.decrypting))
}
is ContentDownloadStateTracker.State.Downloading -> {
loadingView.visible()
loadingView.setProgress(
LoadingData(
messageId = R.string.downloading,
progress = state.current.toInt(),
total = state.total.toInt()
)
)
}
else -> loadingView.gone()
}
}
......
......@@ -32,8 +32,6 @@ class TimelineAdapter(
private val uploadMediaTracker =
MatrixSessionProvider.getSessionOrThrow().contentUploadProgressTracker()
private val downloadMediaTracker =
MatrixSessionProvider.getSessionOrThrow().contentDownloadProgressTracker()
override fun getItemId(position: Int): Long = getItem(position).id.hashCode().toLong()
......@@ -54,7 +52,6 @@ class TimelineAdapter(
parent,
postOptionsListener,
isThread,
downloadMediaTracker,
uploadMediaTracker,
videoPlayer
)
......
package org.futo.circles.core.feature.picker.gallery.media
import android.content.Context
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.asLiveData
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
......@@ -18,9 +20,15 @@ import javax.inject.Inject
@HiltViewModel
class PickMediaItemViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
@ApplicationContext context: Context,
timelineDataSource: SingleTimelineDataSource,
circleFilterAccountDataManager: CircleFilterAccountDataManager
) : BaseTimelineViewModel(savedStateHandle, timelineDataSource, circleFilterAccountDataManager) {
) : BaseTimelineViewModel(
savedStateHandle,
context,
timelineDataSource,
circleFilterAccountDataManager
) {
private val isVideoAvailable: Boolean = savedStateHandle[IS_VIDEO_AVAILABLE] ?: true
......
package org.futo.circles.core.feature.timeline
import android.content.Context
import android.net.Uri
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asFlow
import androidx.lifecycle.asLiveData
import androidx.lifecycle.map
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.update
import org.futo.circles.core.extensions.getOrThrow
import org.futo.circles.core.extensions.launchBg
import org.futo.circles.core.feature.circles.filter.CircleFilterAccountDataManager
import org.futo.circles.core.feature.timeline.data_source.BaseTimelineDataSource
import org.futo.circles.core.mapping.nameOrId
import org.futo.circles.core.model.MediaContent
import org.futo.circles.core.model.MediaFileData
import org.futo.circles.core.model.Post
import org.futo.circles.core.model.PostContentType
import org.futo.circles.core.utils.FileUtils
abstract class BaseTimelineViewModel(
savedStateHandle: SavedStateHandle,
@ApplicationContext context: Context,
private val baseTimelineDataSource: BaseTimelineDataSource,
private val filterAccountDataManager: CircleFilterAccountDataManager
) : ViewModel() {
......@@ -29,15 +41,15 @@ abstract class BaseTimelineViewModel(
baseTimelineDataSource.room.getRoomSummaryLive().map { it.getOrNull()?.nameOrId() ?: "" }
val isFilterActiveLiveData = MutableLiveData(false)
private val prefetchedVideoUriFlow = MutableStateFlow<Map<String, Uri>>(emptyMap())
val timelineEventsLiveData = combine(
baseTimelineDataSource.getTimelineEventFlow(),
getFilterFlow()
) { events, selectedRoomIds ->
val isActive = isFilterActive(selectedRoomIds)
isFilterActiveLiveData.postValue(isActive)
if (isActive) events.filter { selectedRoomIds.contains(it.postInfo.roomId) }
else events
getFilterFlow(),
prefetchedVideoUriFlow
) { events, selectedRoomIds, videoUris ->
val filteredEvents = applyTimelinesFilter(events, selectedRoomIds)
mapEventsWithVideoUri(context, filteredEvents, videoUris)
}.flowOn(Dispatchers.IO).asLiveData()
private fun getFilterFlow(): Flow<Set<String>> {
......@@ -51,12 +63,48 @@ abstract class BaseTimelineViewModel(
}?.asFlow() ?: MutableStateFlow(emptySet())
}
private fun applyTimelinesFilter(events: List<Post>, selectedRoomIds: Set<String>): List<Post> {
val isActive = isFilterActive(selectedRoomIds)
isFilterActiveLiveData.postValue(isActive)
return if (isActive) events.filter { selectedRoomIds.contains(it.postInfo.roomId) }
else events
}
private fun isFilterActive(selectedRoomIds: Set<String>): Boolean {
timelineId ?: return false
if (selectedRoomIds.isEmpty()) return false
return selectedRoomIds.size != filterAccountDataManager.getAllTimelinesIds(roomId).size
}
private fun mapEventsWithVideoUri(
context: Context,
events: List<Post>,
uriMap: Map<String, Uri>
): List<Post> = events.map { post ->
if (post.content.type != PostContentType.VIDEO_CONTENT) return@map post
val mediaContent = (post.content as? MediaContent) ?: return@map post
val uri = uriMap[post.id] ?: run {
prefetchVideo(context, post.id, mediaContent.mediaFileData)
return@map post
}
post.copy(
content = mediaContent.copy(
mediaFileData = mediaContent.mediaFileData.copy(videoUri = uri)
)
)
}
private fun prefetchVideo(context: Context, postId: String, data: MediaFileData) {
launchBg {
async {
val uri =
FileUtils.downloadEncryptedFileToContentUri(context, data) ?: return@async
prefetchedVideoUriFlow.update {
it.toMutableMap().apply { put(postId, uri) }
}
}
}
}
override fun onCleared() {
baseTimelineDataSource.clearTimeline()
......
package org.futo.circles.core.model
import android.net.Uri
import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt
data class MediaFileData(
......@@ -9,5 +10,6 @@ data class MediaFileData(
val elementToDecrypt: ElementToDecrypt?,
val width: Int,
val height: Int,
val duration: String
val duration: String,
val videoUri: Uri? = null
)
\ No newline at end of file
package org.futo.circles.gallery.feature.gallery.grid
import android.content.Context
import android.net.Uri
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.asLiveData
import androidx.lifecycle.map
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import org.futo.circles.core.base.SingleEventLiveData
import org.futo.circles.core.extensions.launchBg
import org.futo.circles.core.feature.circles.filter.CircleFilterAccountDataManager
......@@ -23,13 +25,19 @@ import javax.inject.Inject
@HiltViewModel
class GalleryViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
@ApplicationContext context: Context,
timelineDataSource: SingleTimelineDataSource,
private val sendMessageDataSource: SendMessageDataSource,
private val mediaDataSource: PostContentDataSource,
private val postOptionsDataSource: PostOptionsDataSource,
accessLevelDataSource: AccessLevelDataSource,
circleFilterAccountDataManager: CircleFilterAccountDataManager
) : BaseTimelineViewModel(savedStateHandle, timelineDataSource, circleFilterAccountDataManager) {
) : BaseTimelineViewModel(
savedStateHandle,
context,
timelineDataSource,
circleFilterAccountDataManager
) {
val accessLevelLiveData = accessLevelDataSource.accessLevelFlow.asLiveData()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment