Improve conversation open speed.

main
Alex Hart 2023-02-14 12:38:21 -04:00
rodzic 60874ba57b
commit ec504af593
5 zmienionych plików z 65 dodań i 17 usunięć

Wyświetl plik

@ -16,6 +16,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@ -33,9 +34,9 @@ import java.util.Optional;
/**
* Encapsulates control of voice note playback from an Activity component.
*
* <p>
* This class assumes that it will be created within the scope of Activity#onCreate
*
* <p>
* The workhorse of this repository is the ProgressEventHandler, which will supply a
* steady stream of update events to the set callback.
*/
@ -54,15 +55,17 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
private MutableLiveData<VoiceNotePlaybackState> voiceNotePlaybackState = new MutableLiveData<>(VoiceNotePlaybackState.NONE);
private LiveData<Optional<VoiceNotePlayerView.State>> voiceNotePlayerViewState;
private VoiceNoteProximityWakeLockManager voiceNoteProximityWakeLockManager;
private boolean isMediaBrowserCreationPostponed;
private final MediaControllerCompatCallback mediaControllerCompatCallback = new MediaControllerCompatCallback();
public VoiceNoteMediaController(@NonNull FragmentActivity activity) {
this.activity = activity;
this.mediaBrowser = new MediaBrowserCompat(activity,
new ComponentName(activity, VoiceNotePlaybackService.class),
new ConnectionCallback(),
null);
this(activity, false);
}
public VoiceNoteMediaController(@NonNull FragmentActivity activity, boolean postponeMediaBrowserCreation) {
this.activity = activity;
this.isMediaBrowserCreationPostponed = postponeMediaBrowserCreation;
activity.getLifecycle().addObserver(this);
@ -71,9 +74,9 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
VoiceNotePlaybackState.ClipType.Message message = (VoiceNotePlaybackState.ClipType.Message) playbackState.getClipType();
LiveRecipient sender = Recipient.live(message.getSenderId());
LiveRecipient threadRecipient = Recipient.live(message.getThreadRecipientId());
LiveData<String> name = LiveDataUtil.combineLatest(sender.getLiveDataResolved(),
threadRecipient.getLiveDataResolved(),
(s, t) -> VoiceNoteMediaItemFactory.getTitle(activity, s, t, null));
LiveData<String> name = LiveDataUtil.combineLatest(sender.getLiveDataResolved(),
threadRecipient.getLiveDataResolved(),
(s, t) -> VoiceNoteMediaItemFactory.getTitle(activity, s, t, null));
return Transformations.map(name, displayName -> Optional.of(
new VoiceNotePlayerView.State(
@ -95,6 +98,17 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
});
}
public void ensureMediaBrowser() {
if (mediaBrowser != null) {
return;
}
mediaBrowser = new MediaBrowserCompat(activity,
new ComponentName(activity, VoiceNotePlaybackService.class),
new ConnectionCallback(),
null);
}
public LiveData<VoiceNotePlaybackState> getVoiceNotePlaybackState() {
return voiceNotePlaybackState;
}
@ -103,8 +117,22 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
return voiceNotePlayerViewState;
}
public void finishPostpone() {
isMediaBrowserCreationPostponed = false;
if (activity != null && mediaBrowser == null && activity.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
ensureMediaBrowser();
mediaBrowser.disconnect();
mediaBrowser.connect();
}
}
@Override
public void onResume(@NonNull LifecycleOwner owner) {
if (mediaBrowser == null && isMediaBrowserCreationPostponed) {
return;
}
ensureMediaBrowser();
mediaBrowser.disconnect();
mediaBrowser.connect();
}
@ -117,7 +145,9 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
MediaControllerCompat.getMediaController(activity).unregisterCallback(mediaControllerCompatCallback);
}
mediaBrowser.disconnect();
if (mediaBrowser != null) {
mediaBrowser.disconnect();
}
}
@Override
@ -201,8 +231,8 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
* Tells the Media service to resume playback of a given audio slide. If the audio slide is not
* currently paused, playback will be started from the beginning.
*
* @param audioSlideUri The Uri of the desired audio slide
* @param messageId The Message id of the given audio slide
* @param audioSlideUri The Uri of the desired audio slide
* @param messageId The Message id of the given audio slide
*/
public void resumePlayback(@NonNull Uri audioSlideUri, long messageId) {
if (getMediaController() == null) {
@ -390,8 +420,8 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
}
private static boolean canExtractPlaybackInformationFromMetadata(@Nullable MediaMetadataCompat mediaMetadataCompat) {
return mediaMetadataCompat != null &&
mediaMetadataCompat.getDescription() != null &&
return mediaMetadataCompat != null &&
mediaMetadataCompat.getDescription() != null &&
mediaMetadataCompat.getDescription().getMediaUri() != null;
}

Wyświetl plik

@ -254,7 +254,7 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
if (extras == null) {
return;
}
long messageId = extras.getLong(VoiceNoteMediaItemFactory.EXTRA_MESSAGE_ID);
long messageId = extras.getLong(VoiceNoteMediaItemFactory.EXTRA_MESSAGE_ID);
RecipientId recipientId = RecipientId.from(extras.getString(VoiceNoteMediaItemFactory.EXTRA_INDIVIDUAL_RECIPIENT_ID));
MessageTable messageDatabase = SignalDatabase.messages();

Wyświetl plik

@ -16,9 +16,11 @@ import org.thoughtcrime.securesms.components.reminder.ReminderView
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
import org.thoughtcrime.securesms.components.settings.app.subscription.StripeRepository
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.Debouncer
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme
import org.thoughtcrime.securesms.util.DynamicTheme
import org.thoughtcrime.securesms.util.views.Stub
import java.util.concurrent.TimeUnit
open class ConversationActivity : PassphraseRequiredActivity(), ConversationParentFragment.Callback, DonationPaymentComponent {
@ -26,6 +28,7 @@ open class ConversationActivity : PassphraseRequiredActivity(), ConversationPare
private const val STATE_WATERMARK = "share_data_watermark"
}
private val transitionDebouncer: Debouncer = Debouncer(150, TimeUnit.MILLISECONDS)
private lateinit var fragment: ConversationParentFragment
private var shareDataTimestamp: Long = -1L
@ -35,6 +38,8 @@ open class ConversationActivity : PassphraseRequiredActivity(), ConversationPare
}
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
supportPostponeEnterTransition()
transitionDebouncer.publish { supportStartPostponedEnterTransition() }
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
if (savedInstanceState != null) {
@ -51,6 +56,11 @@ open class ConversationActivity : PassphraseRequiredActivity(), ConversationPare
}
}
override fun onDestroy() {
super.onDestroy()
transitionDebouncer.clear()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong(STATE_WATERMARK, shareDataTimestamp)

Wyświetl plik

@ -746,6 +746,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
startupStopwatch.split("first-render");
startupStopwatch.stop(TAG);
SignalLocalMetrics.ConversationOpen.onRenderFinished();
listener.onFirstRender();
});
}
});
@ -1480,6 +1481,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
@NonNull ConversationReactionOverlay.OnHideListener onHideListener);
void onCursorChanged();
void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord);
void onFirstRender();
void onVoiceNotePause(@NonNull Uri uri);
void onVoiceNotePlay(@NonNull Uri uri, long messageId, double progress);
void onVoiceNoteResume(@NonNull Uri uri, long messageId);

Wyświetl plik

@ -512,7 +512,7 @@ public class ConversationParentFragment extends Fragment
return;
}
voiceNoteMediaController = new VoiceNoteMediaController(requireActivity());
voiceNoteMediaController = new VoiceNoteMediaController(requireActivity(), true);
voiceRecorderWakeLock = new VoiceRecorderWakeLock(requireActivity());
// TODO [alex] LargeScreenSupport -- Should be removed once we move to multi-pane layout.
@ -4040,6 +4040,12 @@ public class ConversationParentFragment extends Fragment
}
}
@Override
public void onFirstRender() {
requireActivity().supportStartPostponedEnterTransition();
voiceNoteMediaController.finishPostpone();
}
@Override
public void onVoiceNotePause(@NonNull Uri uri) {
voiceNoteMediaController.pausePlayback(uri);