Skip to content

Commit

Permalink
Voice Note Beta Feedback fixes.
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-signal committed Oct 16, 2020
1 parent 36da519 commit 082d9e8
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 27 deletions.
Expand Up @@ -14,15 +14,13 @@
import android.support.v4.media.session.PlaybackStateCompat;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;

import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.Util;

import java.util.Objects;

Expand All @@ -38,6 +36,7 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {

public static final String EXTRA_MESSAGE_ID = "voice.note.message_id";
public static final String EXTRA_PLAYHEAD = "voice.note.playhead";
public static final String EXTRA_PLAY_SINGLE = "voice.note.play.single";

private static final String TAG = Log.tag(VoiceNoteMediaController.class);

Expand Down Expand Up @@ -97,22 +96,33 @@ private static boolean isPlayerActive(@NonNull PlaybackStateCompat playbackState
return MediaControllerCompat.getMediaController(activity);
}


public void startConsecutivePlayback(@NonNull Uri audioSlideUri, long messageId, long position) {
startPlayback(audioSlideUri, messageId, position, false);
}

public void startSinglePlayback(@NonNull Uri audioSlideUri, long messageId, long position) {
startPlayback(audioSlideUri, messageId, position, true);
}

/**
* Tells the Media service to begin playback of a given audio slide. If the audio
* slide is currently playing, we jump to the desired position and then begin playback.
*
* @param audioSlideUri The Uri of the desired audio slide
* @param messageId The Message id of the given audio slide
* @param position The desired position in milliseconds at which to start playback.
* @param audioSlideUri The Uri of the desired audio slide
* @param messageId The Message id of the given audio slide
* @param position The desired position in milliseconds at which to start playback.
* @param singlePlayback The player will only play back the specified Uri, and not build a playlist.
*/
public void startPlayback(@NonNull Uri audioSlideUri, long messageId, long position) {
private void startPlayback(@NonNull Uri audioSlideUri, long messageId, long position, boolean singlePlayback) {
if (isCurrentTrack(audioSlideUri)) {
getMediaController().getTransportControls().seekTo(position);
getMediaController().getTransportControls().play();
} else {
Bundle extras = new Bundle();
extras.putLong(EXTRA_MESSAGE_ID, messageId);
extras.putLong(EXTRA_PLAYHEAD, position);
extras.putBoolean(EXTRA_PLAY_SINGLE, singlePlayback);

getMediaController().getTransportControls().playFromUri(audioSlideUri, extras);
}
Expand Down
Expand Up @@ -50,8 +50,8 @@ class VoiceNoteNotificationManager {
}

notificationManager = PlayerNotificationManager.createWithNotificationChannel(context,
NotificationChannels.OTHER,
R.string.NotificationChannel_other,
NotificationChannels.VOICE_NOTES,
R.string.NotificationChannel_voice_notes,
NOW_PLAYING_NOTIFICATION_ID,
new DescriptionAdapter());

Expand Down
Expand Up @@ -88,8 +88,9 @@ public void onPrepareFromSearch(String query, Bundle extras) {

@Override
public void onPrepareFromUri(final Uri uri, Bundle extras) {
long messageId = extras.getLong(VoiceNoteMediaController.EXTRA_MESSAGE_ID);
long position = extras.getLong(VoiceNoteMediaController.EXTRA_PLAYHEAD, 0);
long messageId = extras.getLong(VoiceNoteMediaController.EXTRA_MESSAGE_ID);
long position = extras.getLong(VoiceNoteMediaController.EXTRA_PLAYHEAD, 0);
boolean singlePlayback = extras.getBoolean(VoiceNoteMediaController.EXTRA_PLAY_SINGLE, false);

canLoadMore = false;
latestUri = uri;
Expand All @@ -98,7 +99,13 @@ public void onPrepareFromUri(final Uri uri, Bundle extras) {
dataSource.clear();

SimpleTask.run(EXECUTOR,
() -> loadMediaDescriptions(messageId),
() -> {
if (singlePlayback) {
return loadMediaDescriptionForSinglePlayback(messageId);
} else {
return loadMediaDescriptionsForConsecutivePlayback(messageId);
}
},
descriptions -> {
if (Util.hasItems(descriptions) && Objects.equals(latestUri, uri)) {
applyDescriptionsToQueue(descriptions);
Expand All @@ -116,7 +123,7 @@ public void onTimelineChanged(Timeline timeline, @Nullable Object manifest, int
});

player.prepare(dataSource);
canLoadMore = true;
canLoadMore = !singlePlayback;
}
});
}
Expand Down Expand Up @@ -203,16 +210,32 @@ public void loadMoreVoiceNotes() {
long messageId = mediaDescriptionCompat.getExtras().getLong(VoiceNoteMediaDescriptionCompatFactory.EXTRA_MESSAGE_ID);

SimpleTask.run(EXECUTOR,
() -> loadMediaDescriptions(messageId),
() -> loadMediaDescriptionsForConsecutivePlayback(messageId),
descriptions -> {
if (Util.hasItems(descriptions) && canLoadMore) {
applyDescriptionsToQueue(descriptions);
}
});
}

private @NonNull List<MediaDescriptionCompat> loadMediaDescriptionForSinglePlayback(long messageId) {
try {
MessageRecord messageRecord = DatabaseFactory.getMmsDatabase(context).getMessageRecord(messageId);

if (!MessageRecordUtil.hasAudio(messageRecord)) {
Log.w(TAG, "Message does not contain audio.");
return Collections.emptyList();
}

return Collections.singletonList(VoiceNoteMediaDescriptionCompatFactory.buildMediaDescription(context ,messageRecord));
} catch (NoSuchMessageException e) {
Log.w(TAG, "Could not find message.", e);
return Collections.emptyList();
}
}

@WorkerThread
private @NonNull List<MediaDescriptionCompat> loadMediaDescriptions(long messageId) {
private @NonNull List<MediaDescriptionCompat> loadMediaDescriptionsForConsecutivePlayback(long messageId) {
try {
List<MessageRecord> recordsBefore = DatabaseFactory.getMmsSmsDatabase(context).getMessagesBeforeVoiceNoteExclusive(messageId, LIMIT);
List<MessageRecord> recordsAfter = DatabaseFactory.getMmsSmsDatabase(context).getMessagesAfterVoiceNoteInclusive(messageId, LIMIT);
Expand Down
Expand Up @@ -28,6 +28,7 @@
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.Player;
Expand Down Expand Up @@ -179,6 +180,11 @@ public void onPositionDiscontinuity(int reason) {
voiceNotePlaybackPreparer.loadMoreVoiceNotes();
}
}

@Override
public void onPlayerError(ExoPlaybackException error) {
Log.w(TAG, "ExoPlayer error occurred:", error);
}
}

private class VoiceNoteNotificationManagerListener implements PlayerNotificationManager.NotificationListener {
Expand Down
Expand Up @@ -118,7 +118,6 @@
import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity;
import org.thoughtcrime.securesms.util.CachedInflater;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.HtmlUtil;
import org.thoughtcrime.securesms.util.RemoteDeleteUtil;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
Expand Down Expand Up @@ -353,9 +352,17 @@ public void onNewIntent() {
actionMode.finish();
}

long oldThreadId = threadId;

initializeResources();
messageRequestViewModel.setConversationInfo(recipient.getId(), threadId);
initializeListAdapter();

int startingPosition = getStartPosition();
if (startingPosition != -1 && oldThreadId == threadId) {
list.post(() -> moveToPosition(startingPosition, () -> Log.w(TAG, "Could not scroll to requested message.")));
} else {
initializeListAdapter();
}
}

public void moveToLastSeen() {
Expand All @@ -373,6 +380,10 @@ public void moveToLastSeen() {
snapToTopDataObserver.requestScrollPosition(position);
}

private int getStartPosition() {
return requireActivity().getIntent().getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1);
}

private void initializeMessageRequestViewModel() {
MessageRequestViewModel.Factory factory = new MessageRequestViewModel.Factory(requireContext());

Expand Down Expand Up @@ -460,7 +471,7 @@ private static void presentMessageRequestProfileView(@NonNull Context context, @
private void initializeResources() {
long oldThreadId = threadId;

int startingPosition = this.getActivity().getIntent().getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1);
int startingPosition = getStartPosition();

this.recipient = Recipient.live(getActivity().getIntent().getParcelableExtra(ConversationActivity.RECIPIENT_EXTRA));
this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1);
Expand Down Expand Up @@ -1367,7 +1378,7 @@ public void onVoiceNotePause(@NonNull Uri uri) {

@Override
public void onVoiceNotePlay(@NonNull Uri uri, long messageId, long position) {
voiceNoteMediaController.startPlayback(uri, messageId, position);
voiceNoteMediaController.startConsecutivePlayback(uri, messageId, position);
}

@Override
Expand Down
Expand Up @@ -36,6 +36,7 @@
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.components.AudioView;
import org.thoughtcrime.securesms.components.ThumbnailView;
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
import org.thoughtcrime.securesms.database.loaders.GroupedThreadMediaLoader.GroupedThreadMedia;
Expand All @@ -56,6 +57,7 @@
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter {

Expand All @@ -64,7 +66,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter {
private final GlideRequests glideRequests;
private final ItemClickListener itemClickListener;
private final Map<AttachmentId, MediaRecord> selected = new HashMap<>();
private final AudioView.Callbacks audioViewCallbacks;
private final AudioItemListener audioItemListener;

private GroupedThreadMedia media;
private boolean showFileSizes;
Expand Down Expand Up @@ -94,15 +96,15 @@ private static class HeaderHolder extends HeaderViewHolder {
@NonNull GlideRequests glideRequests,
GroupedThreadMedia media,
ItemClickListener clickListener,
@NonNull AudioView.Callbacks audioViewCallbacks,
@NonNull AudioItemListener audioItemListener,
boolean showFileSizes,
boolean showThread)
{
this.context = context;
this.glideRequests = glideRequests;
this.media = media;
this.itemClickListener = clickListener;
this.audioViewCallbacks = audioViewCallbacks;
this.audioItemListener = audioItemListener;
this.showFileSizes = showFileSizes;
this.showThread = showThread;
}
Expand Down Expand Up @@ -435,11 +437,22 @@ public void bind(@NonNull Context context, @NonNull MediaDatabase.MediaRecord me
throw new AssertionError();
}

audioView.setAudio((AudioSlide) slide, audioViewCallbacks, true);
long mmsId = Objects.requireNonNull(mediaRecord.getAttachment()).getMmsId();

audioItemListener.unregisterPlaybackStateObserver(audioView.getPlaybackStateObserver());
audioView.setAudio((AudioSlide) slide, new AudioViewCallbacksAdapter(audioItemListener, mmsId), true);
audioItemListener.registerPlaybackStateObserver(audioView.getPlaybackStateObserver());

audioView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord));
itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord));
}

@Override
void unbind() {
super.unbind();
audioItemListener.unregisterPlaybackStateObserver(audioView.getPlaybackStateObserver());
}

@Override
protected String getFileTypeDescription(@NonNull Context context, @NonNull Slide slide) {
return context.getString(R.string.MediaOverviewActivity_audio);
Expand Down Expand Up @@ -478,8 +491,48 @@ void unbind() {
}
}

private static final class AudioViewCallbacksAdapter implements AudioView.Callbacks {

private final AudioItemListener audioItemListener;
private final long messageId;

private AudioViewCallbacksAdapter(@NonNull AudioItemListener audioItemListener, long messageId) {
this.audioItemListener = audioItemListener;
this.messageId = messageId;
}

@Override
public void onPlay(@NonNull Uri audioUri, long position) {
audioItemListener.onPlay(audioUri, position, messageId);
}

@Override
public void onPause(@NonNull Uri audioUri) {
audioItemListener.onPause(audioUri);
}

@Override
public void onSeekTo(@NonNull Uri audioUri, long position) {
audioItemListener.onSeekTo(audioUri, position);
}

@Override
public void onStopAndReset(@NonNull Uri audioUri) {
audioItemListener.onStopAndReset(audioUri);
}
}

interface ItemClickListener {
void onMediaClicked(@NonNull MediaDatabase.MediaRecord mediaRecord);
void onMediaLongClicked(MediaDatabase.MediaRecord mediaRecord);
}

interface AudioItemListener {
void onPlay(@NonNull Uri audioUri, long position, long messageId);
void onPause(@NonNull Uri audioUri);
void onSeekTo(@NonNull Uri audioUri, long position);
void onStopAndReset(@NonNull Uri audioUri);
void registerPlaybackStateObserver(@NonNull Observer<VoiceNotePlaybackState> observer);
void unregisterPlaybackStateObserver(@NonNull Observer<VoiceNotePlaybackState> observer);
}
}
Expand Up @@ -22,6 +22,7 @@
import androidx.appcompat.view.ActionMode;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.Observer;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.RecyclerView;
Expand All @@ -31,8 +32,8 @@
import org.thoughtcrime.securesms.MediaPreviewActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.components.AudioView;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController;
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.loaders.GroupedThreadMediaLoader;
import org.thoughtcrime.securesms.database.loaders.MediaLoader;
Expand All @@ -44,7 +45,7 @@

public final class MediaOverviewPageFragment extends Fragment
implements MediaGalleryAllAdapter.ItemClickListener,
AudioView.Callbacks,
MediaGalleryAllAdapter.AudioItemListener,
LoaderManager.LoaderCallbacks<GroupedThreadMediaLoader.GroupedThreadMedia>
{

Expand Down Expand Up @@ -310,8 +311,8 @@ private void enterMultiSelect() {
}

@Override
public void onPlay(@NonNull Uri audioUri, long position) {
voiceNoteMediaController.startPlayback(audioUri, -1, position);
public void onPlay(@NonNull Uri audioUri, long position, long messageId) {
voiceNoteMediaController.startSinglePlayback(audioUri, messageId, position);
}

@Override
Expand All @@ -329,6 +330,16 @@ public void onStopAndReset(@NonNull Uri audioUri) {
voiceNoteMediaController.stopPlaybackAndReset(audioUri);
}

@Override
public void registerPlaybackStateObserver(@NonNull Observer<VoiceNotePlaybackState> observer) {
voiceNoteMediaController.getVoiceNotePlaybackState().observe(getViewLifecycleOwner(), observer);
}

@Override
public void unregisterPlaybackStateObserver(@NonNull Observer<VoiceNotePlaybackState> observer) {
voiceNoteMediaController.getVoiceNotePlaybackState().removeObserver(observer);
}

private class ActionModeCallback implements ActionMode.Callback {

private int originalStatusBarColor;
Expand Down

0 comments on commit 082d9e8

Please sign in to comment.