From 4081ac2a831eaa6b4a09d6cfe8e3989a08f9d69a Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Mon, 19 Dec 2022 14:30:37 -0500 Subject: [PATCH] Fix video controls becoming unresponsive after quickly paging. --- .../mediapreview/MediaPreviewFragment.java | 3 +- .../mediapreview/MediaPreviewV2Adapter.kt | 16 +++++- .../mediapreview/MediaPreviewV2Fragment.kt | 53 ++++++++++++------- .../mediapreview/MediaPreviewV2ViewModel.kt | 5 +- .../VideoMediaPreviewFragment.java | 2 +- .../securesms/video/VideoPlayer.java | 5 +- 6 files changed, 59 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java index c199f8f0e..9945af693 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.mediapreview; import android.content.Context; import android.net.Uri; import android.os.Bundle; -import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -99,7 +98,7 @@ public abstract class MediaPreviewFragment extends Fragment { void unableToPlayMedia(); void onMediaReady(); void onPlaying(); - void onStopped(); + void onStopped(@Nullable String tag); default @Nullable VideoControlsDelegate getVideoControlsDelegate() { return null; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Adapter.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Adapter.kt index 8fb3f7407..e690be6e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Adapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Adapter.kt @@ -6,14 +6,20 @@ import androidx.viewpager2.adapter.FragmentStateAdapter import org.thoughtcrime.securesms.attachments.Attachment import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.util.MediaUtil +import org.thoughtcrime.securesms.util.adapter.StableIdGenerator -class MediaPreviewV2Adapter(val fragment: Fragment) : FragmentStateAdapter(fragment) { +class MediaPreviewV2Adapter(fragment: Fragment) : FragmentStateAdapter(fragment) { private var items: List = listOf() + private val stableIdGenerator = StableIdGenerator() override fun getItemCount(): Int { return items.count() } + override fun getItemId(position: Int): Long { + return stableIdGenerator.getId(items[position]) + } + override fun createFragment(position: Int): Fragment { val attachment: Attachment = items[position] @@ -38,6 +44,14 @@ class MediaPreviewV2Adapter(val fragment: Fragment) : FragmentStateAdapter(fragm return fragment } + fun getFragmentTag(position: Int): String? { + if (position < 0 || position > itemCount) { + return null + } + + return "f${getItemId(position)}" + } + fun findItemPosition(media: Media): Int { return items.indexOfFirst { it.uri == media.uri } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt index e102cd90a..a06bfbb55 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt @@ -75,15 +75,15 @@ import java.util.concurrent.TimeUnit import kotlin.math.roundToInt class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v2), MediaPreviewFragment.Events { - private val TAG = Log.tag(MediaPreviewV2Fragment::class.java) private val lifecycleDisposable = LifecycleDisposable() private val binding by ViewBinderDelegate(FragmentMediaPreviewV2Binding::bind) private val viewModel: MediaPreviewV2ViewModel by viewModels() private val debouncer = Debouncer(2, TimeUnit.SECONDS) - private lateinit var fullscreenHelper: FullscreenHelper + private lateinit var pagerAdapter: MediaPreviewV2Adapter private lateinit var albumRailAdapter: MediaRailAdapter + private lateinit var fullscreenHelper: FullscreenHelper private var individualItemWidth: Int = 0 @@ -108,9 +108,14 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v initializeAlbumRail() initializeFullScreenUi() anchorMarginsToBottomInsets(binding.mediaPreviewDetailsContainer) - lifecycleDisposable += viewModel.state.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread()).subscribe { - bindCurrentState(it) - } + lifecycleDisposable += + viewModel + .state + .distinctUntilChanged() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + bindCurrentState(it) + } } private fun initializeViewModel(args: MediaIntentFactory.MediaPreviewArgs) { @@ -141,8 +146,8 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v private fun initializeViewPager() { binding.mediaPager.offscreenPageLimit = OFFSCREEN_PAGE_LIMIT_DEFAULT binding.mediaPager.setPageTransformer(MarginPageTransformer(ViewUtil.dpToPx(24))) - val adapter = MediaPreviewV2Adapter(this) - binding.mediaPager.adapter = adapter + pagerAdapter = MediaPreviewV2Adapter(this) + binding.mediaPager.adapter = pagerAdapter binding.mediaPager.registerOnPageChangeCallback(object : OnPageChangeCallback() { override fun onPageSelected(position: Int) { super.onPageSelected(position) @@ -187,14 +192,13 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v private fun bindDataLoadedState(currentState: MediaPreviewV2State) { val currentPosition = currentState.position - val fragmentAdapter = binding.mediaPager.adapter as MediaPreviewV2Adapter val backingItems = currentState.mediaRecords.mapNotNull { it.attachment } if (backingItems.isEmpty()) { onMediaNotAvailable() return } - fragmentAdapter.updateBackingItems(backingItems) + pagerAdapter.updateBackingItems(backingItems) if (binding.mediaPager.currentItem != currentPosition) { binding.mediaPager.setCurrentItem(currentPosition, false) @@ -212,12 +216,12 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v return } - val currentPosition = currentState.position + val currentPosition: Int = currentState.position val currentItem: MediaTable.MediaRecord = currentState.mediaRecords[currentPosition] + val currentItemTag: String? = pagerAdapter.getFragmentTag(currentPosition) - // pause all other fragments - childFragmentManager.fragments.map { fragment -> - if (fragment.tag != "f$currentPosition") { + childFragmentManager.fragments.forEach { fragment -> + if (fragment.tag != currentItemTag) { (fragment as? MediaPreviewFragment)?.pause() } } @@ -232,6 +236,8 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v currentState.albums[currentItem.attachment?.mmsId] ?: emptyList() } bindAlbumRail(albumThumbnailMedia, currentItem) + + fullscreenHelper.showSystemUI() crossfadeViewIn(binding.mediaPreviewDetailsContainer) } @@ -349,11 +355,12 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v } } - private fun getMediaPreviewFragmentFromChildFragmentManager(currentPosition: Int) = childFragmentManager.findFragmentByTag("f$currentPosition") as? MediaPreviewFragment + private fun getMediaPreviewFragmentFromChildFragmentManager(currentPosition: Int): MediaPreviewFragment? { + return childFragmentManager.findFragmentByTag(pagerAdapter.getFragmentTag(currentPosition)) as? MediaPreviewFragment + } private fun jumpViewPagerToMedia(media: Media) { - val viewPagerAdapter = binding.mediaPager.adapter as MediaPreviewV2Adapter - val position = viewPagerAdapter.findItemPosition(media) + val position = pagerAdapter.findItemPosition(media) binding.mediaPager.setCurrentItem(position, true) } @@ -432,8 +439,15 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v debouncer.publish { fullscreenHelper.hideSystemUI() } } - override fun onStopped() { - debouncer.clear() + override fun onStopped(tag: String?) { + if (tag == null) { + return + } + + if (pagerAdapter.getFragmentTag(viewModel.currentPosition) == tag) { + debouncer.clear() + fullscreenHelper.showSystemUI() + } } override fun unableToPlayMedia() { @@ -493,6 +507,7 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v } } + @Suppress("DEPRECATION") fun performSaveToDisk(mediaItem: MediaTable.MediaRecord) { val saveTask = SaveAttachmentTask(requireContext()) val saveDate = if (mediaItem.date > 0) mediaItem.date else System.currentTimeMillis() @@ -587,6 +602,8 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v } companion object { + private val TAG = Log.tag(MediaPreviewV2Fragment::class.java) + const val ARGS_KEY: String = "args" @JvmStatic diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt index 34db6625a..1c58ef058 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt @@ -10,7 +10,6 @@ import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.schedulers.Schedulers -import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.attachments.AttachmentId import org.thoughtcrime.securesms.attachments.DatabaseAttachment import org.thoughtcrime.securesms.database.MediaTable @@ -19,12 +18,14 @@ import org.thoughtcrime.securesms.util.rx.RxStore import java.util.Optional class MediaPreviewV2ViewModel : ViewModel() { - private val TAG = Log.tag(MediaPreviewV2ViewModel::class.java) + private val store = RxStore(MediaPreviewV2State()) private val disposables = CompositeDisposable() private val repository: MediaPreviewRepository = MediaPreviewRepository() val state: Flowable = store.stateFlowable.observeOn(AndroidSchedulers.mainThread()) + val currentPosition: Int + get() = store.state.position fun fetchAttachments(startingAttachmentId: AttachmentId, threadId: Long, sorting: MediaTable.Sorting, forceRefresh: Boolean = false) { if (store.state.loadState == MediaPreviewV2State.LoadState.INIT || forceRefresh) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java index 5a6782a9c..d03ff9978 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java @@ -79,7 +79,7 @@ public final class VideoMediaPreviewFragment extends MediaPreviewFragment { @Override public void onStopped() { - events.onStopped(); + events.onStopped(getTag()); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java b/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java index 80066f13b..2bcbdd97b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java @@ -252,7 +252,10 @@ public class VideoPlayer extends FrameLayout { if (this.exoPlayer != null) { exoView.setPlayer(null); - exoControls.setPlayer(null); + + if (exoPlayer.equals(exoControls.getPlayer())) { + exoControls.setPlayer(null); + } exoPlayer.removeListener(playerListener); exoPlayer.removeListener(exoPlayerListener);