-
Notifications
You must be signed in to change notification settings - Fork 46
fix: stabilize message media loading [WPB-18914] #4687
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,8 @@ import kotlinx.coroutines.flow.map | |
| import kotlinx.coroutines.flow.mapNotNull | ||
| import kotlinx.coroutines.launch | ||
| import kotlinx.serialization.Serializable | ||
| import java.util.concurrent.ConcurrentHashMap | ||
| import java.util.concurrent.ConcurrentMap | ||
| import javax.inject.Inject | ||
|
|
||
| @ViewModelScopedPreview | ||
|
|
@@ -58,7 +60,9 @@ class AudioMessageViewModelImpl @Inject constructor( | |
|
|
||
| private val args: AudioMessageArgs = savedStateHandle.scopedArgs() | ||
|
|
||
| override var state: AudioMessageState by mutableStateOf(AudioMessageState()) | ||
| override var state: AudioMessageState by mutableStateOf( | ||
| AudioMessageState(wavesMask = cachedWavesMasks[args.key]) | ||
| ) | ||
| private set | ||
|
|
||
| init { | ||
|
|
@@ -92,9 +96,15 @@ class AudioMessageViewModelImpl @Inject constructor( | |
| } | ||
|
|
||
| private fun preloadAudioMessage() { | ||
| if (preloadStates.putIfAbsent(args.key, PreloadState.InFlight) != null) return | ||
| viewModelScope.launch { | ||
| // calls preload to initially fetch the audio asset data to be ready and schedule waves mask generation if needed | ||
| audioMessagePlayer.preloadAudioMessage(args.conversationId, args.messageId) | ||
| try { | ||
| // calls preload to initially fetch the audio asset data to be ready and schedule waves mask generation if needed | ||
| audioMessagePlayer.preloadAudioMessage(args.conversationId, args.messageId) | ||
| preloadStates[args.key] = PreloadState.Succeeded | ||
| } catch (_: Throwable) { | ||
| preloadStates.remove(args.key, PreloadState.InFlight) | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -110,7 +120,10 @@ class AudioMessageViewModelImpl @Inject constructor( | |
| } | ||
| .distinctUntilChanged() | ||
| .firstOrNull { it != null } // wait for the first non-null value | ||
| .let { state = state.copy(wavesMask = it) } | ||
| ?.let { | ||
| cachedWavesMasks[args.key] = it | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Waves masks are being generated only once and then are stored in the DB, so there isn't much overhead here. I don't understand this idea of having |
||
| state = state.copy(wavesMask = it) | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -131,6 +144,16 @@ class AudioMessageViewModelImpl @Inject constructor( | |
| audioMessagePlayer.setSpeed(audioSpeed) | ||
| } | ||
| } | ||
|
|
||
| private companion object { | ||
| val cachedWavesMasks = ConcurrentHashMap<String, List<Int>>() | ||
| val preloadStates: ConcurrentMap<String, PreloadState> = ConcurrentHashMap() | ||
| } | ||
|
|
||
| private enum class PreloadState { | ||
| InFlight, | ||
| Succeeded, | ||
| } | ||
| } | ||
|
|
||
| @Serializable | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ConversationAudioMessagePlayeris a singleton, wouldn't be better to have this logic of checking and not preloading the same audio message again inside this singleton player instead of having this not so pretty approach here with keeping a map in companion object?