diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlayer.kt b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlayer.kt index c65740a28..e20a83cf3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlayer.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlayer.kt @@ -5,12 +5,12 @@ import com.google.android.exoplayer2.C import com.google.android.exoplayer2.DefaultLoadControl import com.google.android.exoplayer2.ForwardingPlayer import com.google.android.exoplayer2.SimpleExoPlayer -import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory +import org.thoughtcrime.securesms.video.exo.SignalMediaSourceFactory class VoiceNotePlayer @JvmOverloads constructor( context: Context, val internalPlayer: SimpleExoPlayer = SimpleExoPlayer.Builder(context) - .setMediaSourceFactory(AttachmentMediaSourceFactory(context)) + .setMediaSourceFactory(SignalMediaSourceFactory(context)) .setLoadControl( DefaultLoadControl.Builder() .setBufferDurationsMs(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index b79e16d62..44a4d5c9f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -389,6 +389,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo int defaultTopMargin = readDimen(R.dimen.message_bubble_default_footer_bottom_margin); int defaultBottomMargin = readDimen(R.dimen.message_bubble_bottom_padding); int collapsedBottomMargin = readDimen(R.dimen.message_bubble_collapsed_bottom_padding); + if (!updatingFooter && getActiveFooter(messageRecord) == footer && !hasAudio(messageRecord) && @@ -400,11 +401,13 @@ public final class ConversationItem extends RelativeLayout implements BindableCo int footerWidth = footer.getMeasuredWidth(); int availableWidth = getAvailableMessageBubbleWidth(bodyText); int collapsedTopMargin = -1 * (dateView.getMeasuredHeight() + ViewUtil.dpToPx(4)); + if (bodyText.isSingleLine()) { int maxBubbleWidth = hasBigImageLinkPreview(messageRecord) || hasThumbnail(messageRecord) ? readDimen(R.dimen.media_bubble_max_width) : getMaxBubbleWidth(); int bodyMargins = ViewUtil.getLeftMargin(bodyText) + ViewUtil.getRightMargin(bodyText); int sizeWithMargins = bodyText.getMeasuredWidth() + ViewUtil.dpToPx(6) + footerWidth + bodyMargins; int minSize = Math.min(maxBubbleWidth, Math.max(bodyText.getMeasuredWidth() + ViewUtil.dpToPx(6) + footerWidth + bodyMargins, bodyBubble.getMeasuredWidth())); + if (hasQuote(messageRecord) && sizeWithMargins < availableWidth) { ViewUtil.setTopMargin(footer, collapsedTopMargin); ViewUtil.setBottomMargin(footer, collapsedBottomMargin); @@ -418,6 +421,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo updatingFooter = true; } } + if (!updatingFooter && bodyText.getLastLineWidth() + ViewUtil.dpToPx(6) + footerWidth <= bodyText.getMeasuredWidth()) { ViewUtil.setTopMargin(footer, collapsedTopMargin); ViewUtil.setBottomMargin(footer, collapsedBottomMargin); @@ -1044,9 +1048,9 @@ public final class ConversationItem extends RelativeLayout implements BindableCo mediaThumbnailStub.require().setMinimumThumbnailWidth(readDimen(isCaptionlessMms(messageRecord) ? R.dimen.media_bubble_min_width_solo : R.dimen.media_bubble_min_width_with_content)); mediaThumbnailStub.require().setImageResource(glideRequests, - thumbnailSlides, - showControls, - false); + thumbnailSlides, + showControls, + false); mediaThumbnailStub.require().setThumbnailClickListener(new ThumbnailClickListener()); mediaThumbnailStub.require().setDownloadClickListener(downloadClickListener); mediaThumbnailStub.require().setOnLongClickListener(passthroughClickListener); diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ExoPlayerProvider.java b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ExoPlayerProvider.java index 10488a8fd..0cc28dc62 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ExoPlayerProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ExoPlayerProvider.java @@ -15,7 +15,7 @@ import com.google.android.exoplayer2.upstream.DataSource; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.net.ContentProxySelector; -import org.thoughtcrime.securesms.video.exo.ChunkedDataSourceFactory; +import org.thoughtcrime.securesms.video.exo.SignalDataSource; import okhttp3.OkHttpClient; @@ -26,7 +26,7 @@ final class GiphyMp4ExoPlayerProvider implements DefaultLifecycleObserver { private final Context context; private final OkHttpClient okHttpClient = ApplicationDependencies.getOkHttpClient().newBuilder().proxySelector(new ContentProxySelector()).build(); - private final DataSource.Factory dataSourceFactory = new ChunkedDataSourceFactory(okHttpClient, null); + private final DataSource.Factory dataSourceFactory = new SignalDataSource.Factory(ApplicationDependencies.getApplication(), okHttpClient, null); private final MediaSourceFactory mediaSourceFactory = new ProgressiveMediaSource.Factory(dataSourceFactory); GiphyMp4ExoPlayerProvider(@NonNull Context context) { 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 f490f956f..cbc8b02c8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java @@ -27,7 +27,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; @@ -36,12 +35,13 @@ import com.google.android.exoplayer2.source.MediaSourceFactory; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.PlayerControlView; import com.google.android.exoplayer2.ui.PlayerView; +import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.mms.VideoSlide; -import org.thoughtcrime.securesms.video.exo.AttachmentDataSourceFactory; +import org.thoughtcrime.securesms.video.exo.SignalDataSource; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -86,9 +86,8 @@ public class VideoPlayer extends FrameLayout { Context context = getContext(); if (exoPlayer == null) { - DefaultDataSourceFactory defaultDataSourceFactory = new DefaultDataSourceFactory(context, "GenericUserAgent", null); - AttachmentDataSourceFactory attachmentDataSourceFactory = new AttachmentDataSourceFactory(context, defaultDataSourceFactory, null); - MediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(attachmentDataSourceFactory); + DataSource.Factory attachmentDataSourceFactory = new SignalDataSource.Factory(context, null, null); + MediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(attachmentDataSourceFactory); exoPlayer = new SimpleExoPlayer.Builder(context).setMediaSourceFactory(mediaSourceFactory).build(); exoPlayer.addListener(new ExoPlayerListener(this, window, playerStateCallback, playerPositionDiscontinuityCallback)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSource.java deleted file mode 100644 index d965835d3..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSource.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.thoughtcrime.securesms.video.exo; - - -import android.net.Uri; - -import androidx.annotation.NonNull; - -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.upstream.DefaultDataSource; -import com.google.android.exoplayer2.upstream.TransferListener; - -import org.thoughtcrime.securesms.mms.PartAuthority; -import org.thoughtcrime.securesms.providers.BlobProvider; - -import java.io.IOException; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public class AttachmentDataSource implements DataSource { - - private final DefaultDataSource defaultDataSource; - private final PartDataSource partDataSource; - private final BlobDataSource blobDataSource; - - private DataSource dataSource; - - public AttachmentDataSource(DefaultDataSource defaultDataSource, - PartDataSource partDataSource, - BlobDataSource blobDataSource) - { - this.defaultDataSource = defaultDataSource; - this.partDataSource = partDataSource; - this.blobDataSource = blobDataSource; - } - - @Override - public void addTransferListener(@NonNull TransferListener transferListener) { - } - - @Override - public long open(DataSpec dataSpec) throws IOException { - if (BlobProvider.isAuthority(dataSpec.uri)) dataSource = blobDataSource; - else if (PartAuthority.isLocalUri(dataSpec.uri)) dataSource = partDataSource; - else dataSource = defaultDataSource; - - return dataSource.open(dataSpec); - } - - @Override - public int read(@NonNull byte[] buffer, int offset, int readLength) throws IOException { - return dataSource.read(buffer, offset, readLength); - } - - @Override - public Uri getUri() { - return dataSource.getUri(); - } - - @Override - public @NonNull Map> getResponseHeaders() { - return Collections.emptyMap(); - } - - @Override - public void close() throws IOException { - dataSource.close(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java deleted file mode 100644 index 9bd861b4c..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.thoughtcrime.securesms.video.exo; - - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; -import com.google.android.exoplayer2.upstream.TransferListener; - -public class AttachmentDataSourceFactory implements DataSource.Factory { - - private final Context context; - - private final DefaultDataSourceFactory defaultDataSourceFactory; - private final TransferListener listener; - - public AttachmentDataSourceFactory(@NonNull Context context, - @NonNull DefaultDataSourceFactory defaultDataSourceFactory, - @Nullable TransferListener listener) - { - this.context = context; - this.defaultDataSourceFactory = defaultDataSourceFactory; - this.listener = listener; - } - - @Override - public @NonNull AttachmentDataSource createDataSource() { - return new AttachmentDataSource(defaultDataSourceFactory.createDataSource(), - new PartDataSource(context, listener), - new BlobDataSource(context, listener)); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/BlobDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/BlobDataSource.java index c07ebb6d3..dd58cb904 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/BlobDataSource.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/exo/BlobDataSource.java @@ -20,7 +20,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; -public class BlobDataSource implements DataSource { +class BlobDataSource implements DataSource { private final @NonNull Context context; private final @Nullable TransferListener listener; diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/ChunkedDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/ChunkedDataSource.java index ea76af98a..56558e1c9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/ChunkedDataSource.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/exo/ChunkedDataSource.java @@ -27,7 +27,7 @@ import okhttp3.OkHttpClient; /** * DataSource which utilizes ChunkedDataFetcher to download video content via Signal content proxy. */ -public class ChunkedDataSource implements DataSource { +class ChunkedDataSource implements DataSource { private final OkHttpClient okHttpClient; private final TransferListener transferListener; @@ -138,5 +138,4 @@ public class ChunkedDataSource implements DataSource { } cacheEntry = null; } - } diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/ChunkedDataSourceFactory.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/ChunkedDataSourceFactory.java deleted file mode 100644 index f6a01c9c7..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/ChunkedDataSourceFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.thoughtcrime.securesms.video.exo; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.TransferListener; - -import okhttp3.OkHttpClient; - -public class ChunkedDataSourceFactory implements DataSource.Factory { - - private final OkHttpClient okHttpClient; - private final TransferListener listener; - - public ChunkedDataSourceFactory(@NonNull OkHttpClient okHttpClient, @Nullable TransferListener listener) { - this.okHttpClient = okHttpClient; - this.listener = listener; - } - - - @Override - public @NonNull DataSource createDataSource() { - return new ChunkedDataSource(okHttpClient, listener); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/PartDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/PartDataSource.java index 22bf62a7d..df6f8a8c1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/PartDataSource.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/exo/PartDataSource.java @@ -23,7 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; -public class PartDataSource implements DataSource { +class PartDataSource implements DataSource { private final @NonNull Context context; private final @Nullable TransferListener listener; diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/SignalDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/SignalDataSource.java new file mode 100644 index 000000000..4f8180ec7 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/video/exo/SignalDataSource.java @@ -0,0 +1,119 @@ +package org.thoughtcrime.securesms.video.exo; + +import android.content.Context; +import android.net.Uri; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.DefaultDataSource; +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.upstream.TransferListener; + +import org.thoughtcrime.securesms.mms.PartAuthority; +import org.thoughtcrime.securesms.providers.BlobProvider; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import okhttp3.OkHttpClient; + +/** + * Go-to {@link DataSource} that handles all of our various types of video sources. + * Will defer to other {@link DataSource}s depending on the URI. + */ +public class SignalDataSource implements DataSource { + + private final DefaultDataSource defaultDataSource; + private final PartDataSource partDataSource; + private final BlobDataSource blobDataSource; + private final ChunkedDataSource chunkedDataSource; + + private DataSource dataSource; + + public SignalDataSource(@NonNull DefaultDataSource defaultDataSource, + @NonNull PartDataSource partDataSource, + @NonNull BlobDataSource blobDataSource, + @Nullable ChunkedDataSource chunkedDataSource) + { + this.defaultDataSource = defaultDataSource; + this.partDataSource = partDataSource; + this.blobDataSource = blobDataSource; + this.chunkedDataSource = chunkedDataSource; + } + + @Override + public void addTransferListener(@NonNull TransferListener transferListener) { + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + if (BlobProvider.isAuthority(dataSpec.uri)) { + dataSource = blobDataSource; + } else if (PartAuthority.isLocalUri(dataSpec.uri)) { + dataSource = partDataSource; + } else if (chunkedDataSource != null && isRemoteUri(dataSpec.uri)) { + dataSource = chunkedDataSource; + } else { + dataSource = defaultDataSource; + } + + return dataSource.open(dataSpec); + } + + @Override + public int read(@NonNull byte[] buffer, int offset, int readLength) throws IOException { + return dataSource.read(buffer, offset, readLength); + } + + @Override + public @Nullable Uri getUri() { + return dataSource.getUri(); + } + + @Override + public @NonNull Map> getResponseHeaders() { + return Collections.emptyMap(); + } + + @Override + public void close() throws IOException { + dataSource.close(); + } + + private static boolean isRemoteUri(@Nullable Uri uri) { + if (uri != null) { + String scheme = uri.getScheme(); + return "http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme); + } else { + return false; + } + } + + public static final class Factory implements DataSource.Factory { + private final Context context; + private final OkHttpClient okHttpClient; + private final TransferListener listener; + + public Factory(@NonNull Context context, + @Nullable OkHttpClient okHttpClient, + @Nullable TransferListener listener) + { + this.context = context; + this.okHttpClient = okHttpClient; + this.listener = listener; + } + + @Override + public @NonNull SignalDataSource createDataSource() { + return new SignalDataSource(new DefaultDataSourceFactory(context, "GenericUserAgent", null).createDataSource(), + new PartDataSource(context, listener), + new BlobDataSource(context, listener), + okHttpClient != null ? new ChunkedDataSource(okHttpClient, listener) : null); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentMediaSourceFactory.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/SignalMediaSourceFactory.java similarity index 85% rename from app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentMediaSourceFactory.java rename to app/src/main/java/org/thoughtcrime/securesms/video/exo/SignalMediaSourceFactory.java index 90376d758..3e728392c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentMediaSourceFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/exo/SignalMediaSourceFactory.java @@ -17,6 +17,7 @@ import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSourceFactory; import com.google.android.exoplayer2.source.ProgressiveMediaSource; +import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; @@ -24,17 +25,16 @@ import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import java.util.List; /** - * This class is responsible for creating a MediaSource object for a given Uri, using AttachmentDataSourceFactory + * This class is responsible for creating a MediaSource object for a given Uri, using {@link SignalDataSource.Factory}. */ @SuppressWarnings("deprecation") -public final class AttachmentMediaSourceFactory implements MediaSourceFactory { +public final class SignalMediaSourceFactory implements MediaSourceFactory { private final ProgressiveMediaSource.Factory progressiveMediaSourceFactory; - public AttachmentMediaSourceFactory(@NonNull Context context) { - DefaultDataSourceFactory defaultDataSourceFactory = new DefaultDataSourceFactory(context, "GenericUserAgent", null); - AttachmentDataSourceFactory attachmentDataSourceFactory = new AttachmentDataSourceFactory(context, defaultDataSourceFactory, null); - ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true); + public SignalMediaSourceFactory(@NonNull Context context) { + DataSource.Factory attachmentDataSourceFactory = new SignalDataSource.Factory(context, null, null); + ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true); progressiveMediaSourceFactory = new ProgressiveMediaSource.Factory(attachmentDataSourceFactory, extractorsFactory); }