Restrict video send duration.
This commit is contained in:
parent
95468c85a8
commit
6e7858e00f
9 changed files with 84 additions and 41 deletions
|
@ -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) {
|
||||
|
|
|
@ -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<Media, MediaTransform> 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -460,15 +460,12 @@ class MediaSendViewModel extends ViewModel {
|
|||
}
|
||||
|
||||
MutableLiveData<MediaSendActivityResult> result = new MutableLiveData<>();
|
||||
Runnable dialogRunnable = () -> event.postValue(Event.SHOW_RENDER_PROGRESS);
|
||||
String trimmedBody = isViewOnce() ? "" : body.toString().trim();
|
||||
List<Media> initialMedia = getSelectedMediaOrDefault();
|
||||
List<Mention> 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<Media> 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 {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Reference in a new issue