From 6e7858e00f0265d439ebfa72b50ee5f0fea4ab66 Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Tue, 17 Nov 2020 17:13:46 -0400 Subject: [PATCH] Restrict video send duration. --- .../securesms/mediasend/CameraXFragment.java | 9 +++---- .../mediasend/MediaSendActivity.java | 25 +++++++++---------- .../mediasend/MediaSendFragment.java | 15 +++++++---- .../MediaSendFragmentPagerAdapter.java | 13 ++++++++-- .../mediasend/MediaSendVideoFragment.java | 5 +++- .../mediasend/MediaSendViewModel.java | 6 +---- .../securesms/scribbles/VideoEditorHud.java | 6 ++++- .../securesms/video/VideoUtil.java | 23 +++++++++++------ .../VideoThumbnailsRangeSelectorView.java | 23 +++++++++++++++++ 9 files changed, 84 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java index a6542cd973..aff5b5802a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java @@ -26,7 +26,6 @@ import androidx.camera.core.CameraSelector; import androidx.camera.core.ImageCapture; import androidx.camera.core.ImageCaptureException; import androidx.camera.core.ImageProxy; -import androidx.camera.core.impl.utils.executor.CameraXExecutors; import androidx.camera.lifecycle.ProcessCameraProvider; import androidx.camera.view.PreviewView; import androidx.camera.view.SignalCameraView; @@ -47,8 +46,6 @@ import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.util.MemoryFileDescriptor; import org.thoughtcrime.securesms.util.Stopwatch; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.ThemeUtil; -import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.video.VideoUtil; import org.whispersystems.libsignal.util.guava.Optional; @@ -232,7 +229,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { camera.setCaptureMode(SignalCameraView.CaptureMode.MIXED); - int maxDuration = VideoUtil.getMaxVideoDurationInSeconds(requireContext(), viewModel.getMediaConstraints()); + int maxDuration = VideoUtil.getMaxVideoRecordDurationInSeconds(requireContext(), viewModel.getMediaConstraints()); Log.d(TAG, "Max duration: " + maxDuration + " sec"); captureButton.setVideoCaptureListener(new CameraXVideoCaptureHelper( @@ -269,7 +266,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { "API: " + Build.VERSION.SDK_INT + ", " + "MFD: " + MemoryFileDescriptor.supported() + ", " + "Camera: " + CameraXUtil.getLowestSupportedHardwareLevel(requireContext()) + ", " + - "MaxDuration: " + VideoUtil.getMaxVideoDurationInSeconds(requireContext(), viewModel.getMediaConstraints()) + " sec"); + "MaxDuration: " + VideoUtil.getMaxVideoRecordDurationInSeconds(requireContext(), viewModel.getMediaConstraints()) + " sec"); } viewModel.onCameraControlsInitialized(); @@ -280,7 +277,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { requireArguments().getBoolean(IS_VIDEO_ENABLED, true) && MediaConstraints.isVideoTranscodeAvailable() && CameraXUtil.isMixedModeSupported(context) && - VideoUtil.getMaxVideoDurationInSeconds(context, viewModel.getMediaConstraints()) > 0; + VideoUtil.getMaxVideoRecordDurationInSeconds(context, viewModel.getMediaConstraints()) > 0; } private void displayVideoRecordingTooltipIfNecessary(CameraButtonView captureButton) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java index 7713961eab..37027aa3d5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java @@ -21,9 +21,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatDelegate; -import androidx.appcompat.view.ContextThemeWrapper; import androidx.core.util.Pair; import androidx.core.util.Supplier; import androidx.fragment.app.Fragment; @@ -149,7 +147,6 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med private TextView charactersLeft; private RecyclerView mediaRail; private MediaRailAdapter mediaRailAdapter; - private AlertDialog progressDialog; private int visibleHeight; @@ -549,7 +546,11 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med MediaSendFragment fragment = getMediaSendFragment(); if (fragment != null) { + fragment.pausePlayback(); + + SimpleProgressDialog.DismissibleDialog dialog = SimpleProgressDialog.showDelayed(this); viewModel.onSendClicked(buildModelsToTransform(fragment), recipients, composeText.getMentions()).observe(this, result -> { + dialog.dismiss(); finish(); }); } else { @@ -570,7 +571,14 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med sendButton.setEnabled(false); - viewModel.onSendClicked(buildModelsToTransform(fragment), Collections.emptyList(), composeText.getMentions()).observe(this, this::setActivityResultAndFinish); + fragment.pausePlayback(); + + SimpleProgressDialog.DismissibleDialog dialog = SimpleProgressDialog.showDelayed(this); + viewModel.onSendClicked(buildModelsToTransform(fragment), Collections.emptyList(), composeText.getMentions()) + .observe(this, result -> { + dialog.dismiss(); + setActivityResultAndFinish(result); + }); } private static Map buildModelsToTransform(@NonNull MediaSendFragment fragment) { @@ -771,15 +779,6 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med .setOnDismissListener(() -> TextSecurePreferences.setHasSeenViewOnceTooltip(this, true)) .show(TooltipPopup.POSITION_ABOVE); break; - case SHOW_RENDER_PROGRESS: - progressDialog = SimpleProgressDialog.show(new ContextThemeWrapper(MediaSendActivity.this, R.style.TextSecure_MediaSendProgressDialog)); - break; - case HIDE_RENDER_PROGRESS: - if (progressDialog != null) { - progressDialog.dismiss(); - progressDialog = null; - } - break; } }); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java index db84e17e97..738b3c8b8e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java @@ -1,16 +1,17 @@ package org.thoughtcrime.securesms.mediasend; -import androidx.lifecycle.ViewModelProviders; import android.net.Uri; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.viewpager.widget.ViewPager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProviders; +import androidx.viewpager.widget.ViewPager; + import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.ControllableViewPager; import org.thoughtcrime.securesms.util.Util; @@ -147,6 +148,10 @@ public class MediaSendFragment extends Fragment { }); } + void pausePlayback() { + fragmentPagerAdapter.pausePlayback(); + } + private class FragmentPageChangeListener extends ViewPager.SimpleOnPageChangeListener { @Override public void onPageSelected(int position) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragmentPagerAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragmentPagerAdapter.java index aa71e96b86..d5bc186ff8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragmentPagerAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragmentPagerAdapter.java @@ -1,13 +1,14 @@ package org.thoughtcrime.securesms.mediasend; import android.net.Uri; +import android.view.View; +import android.view.ViewGroup; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentStatePagerAdapter; -import android.view.View; -import android.view.ViewGroup; import com.annimon.stream.Stream; @@ -122,6 +123,14 @@ class MediaSendFragmentPagerAdapter extends FragmentStatePagerAdapter { return fragments.containsKey(position) ? fragments.get(position).getPlaybackControls() : null; } + void pausePlayback() { + for (MediaSendPageFragment fragment : fragments.values()) { + if (fragment instanceof MediaSendVideoFragment) { + ((MediaSendVideoFragment)fragment).pausePlayback(); + } + } + } + void notifyHidden() { Stream.of(fragments.values()).forEach(MediaSendPageFragment::notifyHidden); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendVideoFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendVideoFragment.java index b9fd49a42a..0d0faab658 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendVideoFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendVideoFragment.java @@ -18,7 +18,6 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.scribbles.VideoEditorHud; -import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.Throttler; import org.thoughtcrime.securesms.video.VideoPlayer; @@ -203,6 +202,10 @@ public class MediaSendVideoFragment extends Fragment implements VideoEditorHud.E @Override public void notifyHidden() { + pausePlayback(); + } + + public void pausePlayback() { if (player != null) { player.pause(); if (hud != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java index bc56d8d63a..adad53961f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java @@ -460,15 +460,12 @@ class MediaSendViewModel extends ViewModel { } MutableLiveData result = new MutableLiveData<>(); - Runnable dialogRunnable = () -> event.postValue(Event.SHOW_RENDER_PROGRESS); String trimmedBody = isViewOnce() ? "" : body.toString().trim(); List initialMedia = getSelectedMediaOrDefault(); List trimmedMentions = isViewOnce() ? Collections.emptyList() : mentions; Preconditions.checkState(initialMedia.size() > 0, "No media to send!"); - Util.runOnMainDelayed(dialogRunnable, 250); - MediaRepository.transformMedia(application, initialMedia, modelsToTransform, (oldToNew) -> { List updatedMedia = new ArrayList<>(oldToNew.values()); @@ -499,7 +496,6 @@ class MediaSendViewModel extends ViewModel { uploadRepository.deleteAbandonedAttachments(); } - Util.cancelRunnableOnMain(dialogRunnable); result.postValue(MediaSendActivityResult.forPreUpload(uploadResults, splitBody, transport, isViewOnce(), trimmedMentions)); }); }); @@ -681,7 +677,7 @@ class MediaSendViewModel extends ViewModel { } enum Event { - VIEW_ONCE_TOOLTIP, SHOW_RENDER_PROGRESS, HIDE_RENDER_PROGRESS + VIEW_ONCE_TOOLTIP } enum Page { diff --git a/app/src/main/java/org/thoughtcrime/securesms/scribbles/VideoEditorHud.java b/app/src/main/java/org/thoughtcrime/securesms/scribbles/VideoEditorHud.java index 5c1c732de1..4422a82508 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/scribbles/VideoEditorHud.java +++ b/app/src/main/java/org/thoughtcrime/securesms/scribbles/VideoEditorHud.java @@ -14,11 +14,13 @@ import androidx.annotation.RequiresApi; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.media.DecryptableUriMediaInput; +import org.thoughtcrime.securesms.mms.VideoSlide; +import org.thoughtcrime.securesms.video.VideoUtil; import org.thoughtcrime.securesms.video.videoconverter.VideoThumbnailsRangeSelectorView; import java.io.IOException; +import java.util.concurrent.TimeUnit; /** * The HUD (heads-up display) that contains all of the tools for editing video. @@ -69,6 +71,8 @@ public final class VideoEditorHud extends LinearLayout { return; } + videoTimeLine.setTimeLimit(VideoUtil.getMaxVideoUploadDurationInSeconds(), TimeUnit.SECONDS); + videoTimeLine.setInput(DecryptableUriMediaInput.createForUri(getContext(), uri)); videoTimeLine.setOnRangeChangeListener(new VideoThumbnailsRangeSelectorView.OnRangeChangeListener() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/VideoUtil.java b/app/src/main/java/org/thoughtcrime/securesms/video/VideoUtil.java index 96fb912c2f..121e16301a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/VideoUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/VideoUtil.java @@ -14,12 +14,15 @@ import org.thoughtcrime.securesms.util.MediaUtil; public final class VideoUtil { - public static final int AUDIO_BIT_RATE = 192_000; - public static final int VIDEO_FRAME_RATE = 30; - public static final int VIDEO_BIT_RATE = 2_000_000; - public static final int VIDEO_LONG_WIDTH = 1280; - public static final int VIDEO_SHORT_WIDTH = 720; - public static final int VIDEO_MAX_LENGTH_S = 30; + public static final int AUDIO_BIT_RATE = 192_000; + public static final int VIDEO_FRAME_RATE = 30; + public static final int VIDEO_BIT_RATE = 2_000_000; + + static final int VIDEO_SHORT_WIDTH = 720; + + private static final int VIDEO_LONG_WIDTH = 1280; + private static final int VIDEO_MAX_RECORD_LENGTH_S = 30; + private static final int VIDEO_MAX_UPLOAD_LENGTH_S = 120; private static final int TOTAL_BYTES_PER_SECOND = (VIDEO_BIT_RATE / 8) + (AUDIO_BIT_RATE / 8); @@ -38,11 +41,15 @@ public final class VideoUtil { : new Size(VIDEO_LONG_WIDTH, VIDEO_SHORT_WIDTH); } - public static int getMaxVideoDurationInSeconds(@NonNull Context context, @NonNull MediaConstraints mediaConstraints) { + public static int getMaxVideoRecordDurationInSeconds(@NonNull Context context, @NonNull MediaConstraints mediaConstraints) { int allowedSize = mediaConstraints.getCompressedVideoMaxSize(context); int duration = (int) Math.floor((float) allowedSize / TOTAL_BYTES_PER_SECOND); - return Math.min(duration, VIDEO_MAX_LENGTH_S); + return Math.min(duration, VIDEO_MAX_RECORD_LENGTH_S); + } + + public static int getMaxVideoUploadDurationInSeconds() { + return VIDEO_MAX_UPLOAD_LENGTH_S; } @RequiresApi(21) diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/videoconverter/VideoThumbnailsRangeSelectorView.java b/app/src/main/java/org/thoughtcrime/securesms/video/videoconverter/VideoThumbnailsRangeSelectorView.java index 408c41024e..6bdab08eeb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/videoconverter/VideoThumbnailsRangeSelectorView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/videoconverter/VideoThumbnailsRangeSelectorView.java @@ -11,12 +11,14 @@ import android.util.AttributeSet; import android.view.MotionEvent; import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; import androidx.annotation.RequiresApi; import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.logging.Log; import java.util.Locale; import java.util.concurrent.TimeUnit; @@ -24,6 +26,8 @@ import java.util.concurrent.TimeUnit; @RequiresApi(api = 23) public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView { + private static final String TAG = Log.tag(VideoThumbnailsRangeSelectorView.class); + private static final long MINIMUM_SELECTABLE_RANGE = TimeUnit.MILLISECONDS.toMicros(500); private static final int ANIMATION_DURATION_MS = 100; @@ -63,6 +67,7 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView @ColorInt private int thumbHintBackgroundColor; private long dragStartTimeMs; private long dragEndTimeMs; + private long maximumSelectableRangeMicros; public VideoThumbnailsRangeSelectorView(final Context context) { super(context); @@ -137,6 +142,13 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView } } + if (setMinValue(getMinValue())) { + Log.d(TAG, "Clamped video duration to " + getMaxValue()); + if (onRangeChangeListener != null) { + onRangeChangeListener.onRangeDragEnd(getMinValue(), getMaxValue(), getDuration(), Thumb.MAX); + } + } + invalidate(); } @@ -310,11 +322,18 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView final long duration = getDuration(); final long minDiff = Math.max(MINIMUM_SELECTABLE_RANGE, pixelToDuration(thumbSizePixels * 2.5f)); + final long maxDiff = maximumSelectableRangeMicros <= MINIMUM_SELECTABLE_RANGE ? 0 : Math.max(maximumSelectableRangeMicros, pixelToDuration(thumbSizePixels * 2.5f)); if (thumb == Thumb.MIN) { newMin = clamp(newMin, 0, currentMax - minDiff); + if (maxDiff > 0) { + newMax = clamp(newMax, newMin + minDiff, Math.min(newMin + maxDiff, duration)); + } } else { newMax = clamp(newMax, currentMin + minDiff, duration); + if (maxDiff > 0) { + newMin = clamp(newMin, Math.max(0, newMax - maxDiff), newMax - minDiff); + } } if (newMin != currentMin || newMax != currentMax) { @@ -425,6 +444,10 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView } } + public void setTimeLimit(int t, @NonNull TimeUnit timeUnit) { + maximumSelectableRangeMicros = timeUnit.toMicros(t); + } + public enum Thumb { MIN, MAX,