mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 09:49:30 +01:00
Video Sending Redesign
This commit is contained in:
committed by
Alex Hart
parent
276e253fdf
commit
c53abe0941
@@ -17,8 +17,8 @@ import org.thoughtcrime.securesms.video.exceptions.VideoSizeException;
|
||||
import org.thoughtcrime.securesms.video.exceptions.VideoSourceException;
|
||||
import org.thoughtcrime.securesms.video.interfaces.TranscoderCancelationSignal;
|
||||
import org.thoughtcrime.securesms.video.postprocessing.Mp4FaststartPostProcessor;
|
||||
import org.thoughtcrime.securesms.video.videoconverter.exceptions.EncodingException;
|
||||
import org.thoughtcrime.securesms.video.videoconverter.MediaConverter;
|
||||
import org.thoughtcrime.securesms.video.videoconverter.exceptions.EncodingException;
|
||||
import org.thoughtcrime.securesms.video.videoconverter.mediadatasource.MediaDataSourceMediaInput;
|
||||
|
||||
import java.io.Closeable;
|
||||
@@ -51,7 +51,7 @@ public final class InMemoryTranscoder implements Closeable {
|
||||
/**
|
||||
* @param upperSizeLimit A upper size to transcode to. The actual output size can be up to 10% smaller.
|
||||
*/
|
||||
public InMemoryTranscoder(@NonNull Context context, @NonNull MediaDataSource dataSource, @Nullable TranscoderOptions options, long upperSizeLimit) throws IOException, VideoSourceException {
|
||||
public InMemoryTranscoder(@NonNull Context context, @NonNull MediaDataSource dataSource, @Nullable TranscoderOptions options, @NonNull TranscodingPreset preset, long upperSizeLimit) throws IOException, VideoSourceException {
|
||||
this.context = context;
|
||||
this.dataSource = dataSource;
|
||||
this.options = options;
|
||||
@@ -71,8 +71,8 @@ public final class InMemoryTranscoder implements Closeable {
|
||||
}
|
||||
|
||||
this.inSize = dataSource.getSize();
|
||||
this.inputBitRate = VideoBitRateCalculator.bitRate(inSize, duration);
|
||||
this.targetQuality = new VideoBitRateCalculator(upperSizeLimit).getTargetQuality(duration, inputBitRate);
|
||||
this.inputBitRate = TranscodingQuality.bitRate(inSize, duration);
|
||||
this.targetQuality = TranscodingQuality.createFromPreset(preset, duration);
|
||||
this.upperSizeLimit = upperSizeLimit;
|
||||
|
||||
this.transcodeRequired = inputBitRate >= targetQuality.getTargetTotalBitRate() * 1.2 || inSize > upperSizeLimit || containsLocation(mediaMetadataRetriever) || options != null;
|
||||
@@ -80,7 +80,7 @@ public final class InMemoryTranscoder implements Closeable {
|
||||
Log.i(TAG, "Video is within 20% of target bitrate, below the size limit, contained no location metadata or custom options.");
|
||||
}
|
||||
|
||||
this.fileSizeEstimate = targetQuality.getFileSizeEstimate();
|
||||
this.fileSizeEstimate = targetQuality.getByteCountEstimate();
|
||||
this.memoryFileEstimate = (long) (fileSizeEstimate * 1.1);
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ public final class InMemoryTranscoder implements Closeable {
|
||||
(outSize * 100d) / inSize,
|
||||
(outSize * 100d) / fileSizeEstimate,
|
||||
(outSize * 100d) / memoryFileEstimate,
|
||||
numberFormat.format(VideoBitRateCalculator.bitRate(outSize, duration))));
|
||||
numberFormat.format(TranscodingQuality.bitRate(outSize, duration))));
|
||||
|
||||
if (outSize > upperSizeLimit) {
|
||||
throw new VideoSizeException("Size constraints could not be met!");
|
||||
|
||||
@@ -292,7 +292,7 @@ public class VideoPlayer extends FrameLayout {
|
||||
if (this.exoPlayer != null) {
|
||||
return TimeUnit.MILLISECONDS.toMicros(this.exoPlayer.getCurrentPosition()) + clippedStartUs;
|
||||
}
|
||||
return 0L;
|
||||
return -1L;
|
||||
}
|
||||
|
||||
public void setPlaybackPosition(long positionMs) {
|
||||
|
||||
@@ -10,21 +10,19 @@ import androidx.annotation.NonNull;
|
||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.video.videoconverter.utils.VideoConstants;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class VideoUtil {
|
||||
|
||||
private VideoUtil() { }
|
||||
|
||||
public static Size getVideoRecordingSize() {
|
||||
return isPortrait(screenSize())
|
||||
? new Size(VideoConstants.VIDEO_SHORT_EDGE, VideoConstants.VIDEO_LONG_EDGE)
|
||||
: new Size(VideoConstants.VIDEO_LONG_EDGE, VideoConstants.VIDEO_SHORT_EDGE);
|
||||
? new Size(VideoConstants.VIDEO_SHORT_EDGE_HD, VideoConstants.VIDEO_LONG_EDGE_HD)
|
||||
: new Size(VideoConstants.VIDEO_LONG_EDGE_HD, VideoConstants.VIDEO_SHORT_EDGE_HD);
|
||||
}
|
||||
|
||||
public static int getMaxVideoRecordDurationInSeconds(@NonNull Context context, @NonNull MediaConstraints mediaConstraints) {
|
||||
long allowedSize = mediaConstraints.getCompressedVideoMaxSize(context);
|
||||
int duration = (int) Math.floor((float) allowedSize / VideoConstants.TOTAL_BYTES_PER_SECOND);
|
||||
int duration = (int) Math.floor((float) allowedSize / VideoConstants.MAX_ALLOWED_BYTES_PER_SECOND);
|
||||
|
||||
return Math.min(duration, VideoConstants.VIDEO_MAX_RECORD_LENGTH_S);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
@@ -15,14 +14,12 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.Px;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.MemoryUnitFormat;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RequiresApi(api = 23)
|
||||
@@ -30,8 +27,9 @@ 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;
|
||||
private static final long MINIMUM_SELECTABLE_RANGE = TimeUnit.MILLISECONDS.toMicros(500);
|
||||
private static final int ANIMATION_DURATION_MS = 100;
|
||||
private static final float THUMB_RECT_CORNER_RADIUS = ViewUtil.dpToPx(4);
|
||||
|
||||
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint paintGrey = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
@@ -39,8 +37,6 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
private final Paint thumbTimeBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Rect tempDrawRect = new Rect();
|
||||
private final RectF timePillRect = new RectF();
|
||||
private Drawable chevronLeft;
|
||||
private Drawable chevronRight;
|
||||
|
||||
@Px private int left;
|
||||
@Px private int right;
|
||||
@@ -58,10 +54,7 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
private OnRangeChangeListener onRangeChangeListener;
|
||||
@Px private int thumbSizePixels;
|
||||
@Px private int thumbTouchRadius;
|
||||
@Px private int cursorPixels;
|
||||
@ColorInt private int cursorColor;
|
||||
@ColorInt private int thumbColor;
|
||||
@ColorInt private int thumbColorEdited;
|
||||
private long actualPosition;
|
||||
private long dragPosition;
|
||||
@Px private int thumbHintTextSize;
|
||||
@@ -70,8 +63,6 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
private long dragStartTimeMs;
|
||||
private long dragEndTimeMs;
|
||||
private long maximumSelectableRangeMicros;
|
||||
private Quality outputQuality;
|
||||
private long qualityAvailableTimeMs;
|
||||
|
||||
public VideoThumbnailsRangeSelectorView(final Context context) {
|
||||
super(context);
|
||||
@@ -93,10 +84,7 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.VideoThumbnailsRangeSelectorView, 0, 0);
|
||||
try {
|
||||
thumbSizePixels = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_thumbWidth, 1);
|
||||
cursorPixels = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_cursorWidth, 1);
|
||||
thumbColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbColor, 0xffff0000);
|
||||
thumbColorEdited = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbColorEdited, thumbColor);
|
||||
cursorColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_cursorColor, thumbColor);
|
||||
thumbTouchRadius = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_thumbTouchRadius, 50);
|
||||
thumbHintTextSize = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_thumbHintTextSize, 0);
|
||||
thumbHintTextColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbHintTextColor, 0xffff0000);
|
||||
@@ -106,9 +94,6 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
}
|
||||
}
|
||||
|
||||
chevronLeft = VectorDrawableCompat.create(getResources(), R.drawable.ic_chevron_left_black_8dp, null);
|
||||
chevronRight = VectorDrawableCompat.create(getResources(), R.drawable.ic_chevron_right_black_8dp, null);
|
||||
|
||||
paintGrey.setColor(0x7f000000);
|
||||
paintGrey.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||
paintGrey.setStrokeWidth(1);
|
||||
@@ -135,27 +120,19 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
}
|
||||
|
||||
if (duration > 0) {
|
||||
if (externalMinValue != null) {
|
||||
setMinMax(externalMinValue, getMaxValue(), Thumb.MIN);
|
||||
externalMinValue = null;
|
||||
}
|
||||
|
||||
if (externalMaxValue != null) {
|
||||
setMinMax(getMinValue(), externalMaxValue, Thumb.MAX);
|
||||
externalMaxValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (setMinValue(getMinValue())) {
|
||||
Log.d(TAG, "Clamped video duration to " + getMaxValue());
|
||||
if (onRangeChangeListener != null) {
|
||||
onRangeChangeListener.onRangeDragEnd(getMinValue(), getMaxValue(), getDuration(), Thumb.MAX);
|
||||
if (externalMinValue != null) {
|
||||
setMinMax(externalMinValue, getMaxValue(), Thumb.MIN);
|
||||
externalMinValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (onRangeChangeListener != null) {
|
||||
onRangeChangeListener.onRangeDragEnd(getMinValue(), getMaxValue(), getDuration(), Thumb.MIN);
|
||||
setOutputQuality(onRangeChangeListener.getQuality(getClipDuration(), getDuration()));
|
||||
}
|
||||
|
||||
invalidate();
|
||||
@@ -183,7 +160,6 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
protected void onDraw(final Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
canvas.translate(getPaddingLeft(), getPaddingTop());
|
||||
int drawableWidth = getDrawableWidth();
|
||||
int drawableHeight = getDrawableHeight();
|
||||
|
||||
@@ -192,77 +168,56 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
long min = getMinValue();
|
||||
long max = getMaxValue();
|
||||
|
||||
boolean edited = min != 0 || max != duration;
|
||||
|
||||
long drawPosAt = dragThumb == Thumb.POSITION ? dragPosition : actualPosition;
|
||||
|
||||
left = duration != 0 ? (int) ((min * drawableWidth) / duration) : 0;
|
||||
right = duration != 0 ? (int) ((max * drawableWidth) / duration) : drawableWidth;
|
||||
cursor = duration != 0 ? (int) ((drawPosAt * drawableWidth) / duration) : drawableWidth;
|
||||
|
||||
canvas.save();
|
||||
canvas.clipPath(clippingPath);
|
||||
canvas.translate(getPaddingLeft(), getPaddingTop());
|
||||
|
||||
// draw greyed out areas
|
||||
tempDrawRect.set(0, 0, left - 1, drawableHeight);
|
||||
canvas.drawRect(tempDrawRect, paintGrey);
|
||||
tempDrawRect.set(right + 1, 0, drawableWidth, drawableHeight);
|
||||
canvas.drawRect(tempDrawRect, paintGrey);
|
||||
|
||||
// draw area rectangle
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
tempDrawRect.set(left, 0, right, drawableHeight);
|
||||
paint.setColor(edited ? thumbColorEdited : thumbColor);
|
||||
canvas.drawRect(tempDrawRect, paint);
|
||||
canvas.restore();
|
||||
|
||||
canvas.translate(getPaddingLeft(), getPaddingTop());
|
||||
|
||||
int verticalThumbInset = drawableHeight / 4;
|
||||
int halfThumbWidth = thumbSizePixels / 2;
|
||||
// draw thumb rectangles
|
||||
paint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||
tempDrawRect.set(left, 0, left + thumbSizePixels, drawableHeight);
|
||||
canvas.drawRect(tempDrawRect, paint);
|
||||
tempDrawRect.set(right - thumbSizePixels, 0, right, drawableHeight);
|
||||
canvas.drawRect(tempDrawRect, paint);
|
||||
|
||||
int arrowSize = Math.min(drawableHeight, thumbSizePixels * 2);
|
||||
chevronLeft .setBounds(0, 0, arrowSize, arrowSize);
|
||||
chevronRight.setBounds(0, 0, arrowSize, arrowSize);
|
||||
|
||||
float dy = (drawableHeight - arrowSize) / 2f;
|
||||
float arrowPaddingX = (thumbSizePixels - arrowSize) / 2f;
|
||||
|
||||
// draw left thumb chevron
|
||||
canvas.save();
|
||||
canvas.translate(left + arrowPaddingX, dy);
|
||||
chevronLeft.draw(canvas);
|
||||
canvas.restore();
|
||||
|
||||
// draw right thumb chevron
|
||||
canvas.save();
|
||||
canvas.translate(right - thumbSizePixels + arrowPaddingX, dy);
|
||||
chevronRight.draw(canvas);
|
||||
canvas.restore();
|
||||
paint.setColor(thumbColor);
|
||||
timePillRect.set(left - halfThumbWidth, verticalThumbInset, left + halfThumbWidth, drawableHeight - verticalThumbInset);
|
||||
canvas.drawRoundRect(timePillRect, THUMB_RECT_CORNER_RADIUS, THUMB_RECT_CORNER_RADIUS, paint);
|
||||
timePillRect.set(right - halfThumbWidth, verticalThumbInset, right + halfThumbWidth, drawableHeight - verticalThumbInset);
|
||||
canvas.drawRoundRect(timePillRect, THUMB_RECT_CORNER_RADIUS, THUMB_RECT_CORNER_RADIUS, paint);
|
||||
|
||||
// draw time hint pill
|
||||
if (thumbHintTextSize > 0) {
|
||||
if (dragStartTimeMs > 0 && (dragThumb == Thumb.MIN || dragThumb == Thumb.MAX)) {
|
||||
drawTimeHint(canvas, drawableWidth, drawableHeight, dragThumb, false);
|
||||
drawTimeHint(canvas, drawableWidth, dragThumb, false);
|
||||
}
|
||||
if (dragEndTimeMs > 0 && (lastDragThumb == Thumb.MIN || lastDragThumb == Thumb.MAX)) {
|
||||
drawTimeHint(canvas, drawableWidth, drawableHeight, lastDragThumb, true);
|
||||
drawTimeHint(canvas, drawableWidth, lastDragThumb, true);
|
||||
}
|
||||
|
||||
canvas.save();
|
||||
canvas.translate(0, drawableHeight * 2);
|
||||
drawDurationAndSizeHint(canvas, drawableWidth);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
// draw current position marker
|
||||
if (left <= cursor && cursor <= right && dragThumb != Thumb.MIN && dragThumb != Thumb.MAX) {
|
||||
canvas.translate(cursorPixels / 2, 0);
|
||||
tempDrawRect.set(cursor, 0, cursor + cursorPixels, drawableHeight);
|
||||
paint.setColor(cursorColor);
|
||||
canvas.drawRect(tempDrawRect, paint);
|
||||
timePillRect.set(cursor - halfThumbWidth, 0, cursor + halfThumbWidth, drawableHeight);
|
||||
paint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||
paint.setColor(thumbColor);
|
||||
canvas.drawRoundRect(timePillRect, THUMB_RECT_CORNER_RADIUS, THUMB_RECT_CORNER_RADIUS, paint);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawTimeHint(Canvas canvas, int drawableWidth, int drawableHeight, Thumb dragThumb, boolean fadeOut) {
|
||||
private void drawTimeHint(Canvas canvas, int drawableWidth, Thumb dragThumb, boolean fadeOut) {
|
||||
canvas.save();
|
||||
long microsecondValue = dragThumb == Thumb.MIN ? getMinValue() : getMaxValue();
|
||||
long seconds = TimeUnit.MICROSECONDS.toSeconds(microsecondValue);
|
||||
@@ -274,11 +229,11 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
|
||||
timePillRect.set(tempDrawRect.left - leftRightPadding, tempDrawRect.top - topBottomPadding, tempDrawRect.right + leftRightPadding, tempDrawRect.bottom + topBottomPadding);
|
||||
|
||||
float halfPillWidth = timePillRect.width() / 2f;
|
||||
float halfPillWidth = timePillRect.width() / 2f;
|
||||
float halfPillHeight = timePillRect.height() / 2f;
|
||||
|
||||
long animationTime = fadeOut ? ANIMATION_DURATION_MS - Math.min(ANIMATION_DURATION_MS, System.currentTimeMillis() - dragEndTimeMs)
|
||||
: Math.min(ANIMATION_DURATION_MS, System.currentTimeMillis() - dragStartTimeMs);
|
||||
long animationTime = fadeOut ? ANIMATION_DURATION_MS - Math.min(ANIMATION_DURATION_MS, System.currentTimeMillis() - dragEndTimeMs)
|
||||
: Math.min(ANIMATION_DURATION_MS, System.currentTimeMillis() - dragStartTimeMs);
|
||||
float animationPosition = animationTime / (float) ANIMATION_DURATION_MS;
|
||||
float scaleIn = 0.2f * animationPosition + 0.8f;
|
||||
int alpha = (int) (255 * animationPosition);
|
||||
@@ -288,10 +243,12 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
} else {
|
||||
canvas.translate(Math.max(left, halfPillWidth), 0);
|
||||
}
|
||||
canvas.translate(0, drawableHeight + halfPillHeight);
|
||||
|
||||
float timePillOffset = timePillRect.height() * -1.5f;
|
||||
canvas.translate(0, timePillOffset);
|
||||
canvas.scale(scaleIn, scaleIn);
|
||||
thumbTimeBackgroundPaint.setAlpha(Math.round(alpha * 0.6f));
|
||||
thumbTimeTextPaint.setAlpha(alpha);
|
||||
thumbTimeBackgroundPaint.setAlpha(alpha);
|
||||
canvas.translate(leftRightPadding - halfPillWidth, halfPillHeight);
|
||||
canvas.drawRoundRect(timePillRect, halfPillHeight, halfPillHeight, thumbTimeBackgroundPaint);
|
||||
canvas.drawText(timeString, 0, 0, thumbTimeTextPaint);
|
||||
@@ -306,42 +263,6 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
}
|
||||
}
|
||||
|
||||
private void drawDurationAndSizeHint(Canvas canvas, int drawableWidth) {
|
||||
if (outputQuality == null) return;
|
||||
|
||||
canvas.save();
|
||||
long microsecondValue = getMaxValue() - getMinValue();
|
||||
long seconds = TimeUnit.MICROSECONDS.toSeconds(microsecondValue);
|
||||
String durationAndSize = String.format(Locale.getDefault(), "%d:%02d • %s", seconds / 60, seconds % 60, MemoryUnitFormat.formatBytes(outputQuality.fileSize, MemoryUnitFormat.MEGA_BYTES, true));
|
||||
float topBottomPadding = thumbHintTextSize * 0.5f;
|
||||
float leftRightPadding = thumbHintTextSize * 0.75f;
|
||||
|
||||
thumbTimeTextPaint.getTextBounds(durationAndSize, 0, durationAndSize.length(), tempDrawRect);
|
||||
|
||||
timePillRect.set(tempDrawRect.left - leftRightPadding, tempDrawRect.top - topBottomPadding, tempDrawRect.right + leftRightPadding, tempDrawRect.bottom + topBottomPadding);
|
||||
|
||||
float halfPillWidth = timePillRect.width() / 2f;
|
||||
float halfPillHeight = timePillRect.height() / 2f;
|
||||
|
||||
long animationTime = Math.min(ANIMATION_DURATION_MS, System.currentTimeMillis() - qualityAvailableTimeMs);
|
||||
float animationPosition = animationTime / (float) ANIMATION_DURATION_MS;
|
||||
float scaleIn = 0.2f * animationPosition + 0.8f;
|
||||
int alpha = (int) (255 * animationPosition);
|
||||
|
||||
canvas.translate(Math.max(halfPillWidth, Math.min((right + left) / 2f, drawableWidth - halfPillWidth)), - 2 * halfPillHeight);
|
||||
canvas.scale(scaleIn, scaleIn);
|
||||
thumbTimeBackgroundPaint.setAlpha(Math.round(alpha * 0.6f));
|
||||
thumbTimeTextPaint.setAlpha(alpha);
|
||||
canvas.translate(leftRightPadding - halfPillWidth, halfPillHeight);
|
||||
canvas.drawRoundRect(timePillRect, halfPillHeight, halfPillHeight, thumbTimeBackgroundPaint);
|
||||
canvas.drawText(durationAndSize, 0, 0, thumbTimeTextPaint);
|
||||
canvas.restore();
|
||||
|
||||
if (animationTime < ANIMATION_DURATION_MS) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public long getMinValue() {
|
||||
return minValue == null ? 0 : minValue;
|
||||
}
|
||||
@@ -350,22 +271,18 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
return maxValue == null ? getDuration() : maxValue;
|
||||
}
|
||||
|
||||
public long getClipDuration() {
|
||||
return getMaxValue() - getMinValue();
|
||||
}
|
||||
|
||||
private boolean setMinValue(long minValue) {
|
||||
if (this.minValue == null || this.minValue != minValue) {
|
||||
return setMinMax(minValue, getMaxValue(), Thumb.MIN);
|
||||
} else{
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean setMaxValue(long maxValue) {
|
||||
private boolean setMaxValue(long maxValue) {
|
||||
if (this.maxValue == null || this.maxValue != maxValue) {
|
||||
return setMinMax(getMinValue(), maxValue, Thumb.MAX);
|
||||
} else{
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -419,7 +336,7 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
|
||||
if (actionMasked == MotionEvent.ACTION_MOVE) {
|
||||
boolean changed = false;
|
||||
long delta = pixelToDuration(event.getX() - xDown);
|
||||
long delta = pixelToDuration(event.getX() - xDown);
|
||||
switch (dragThumb) {
|
||||
case POSITION:
|
||||
setDragPosition(downCursor + delta);
|
||||
@@ -437,7 +354,6 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
onRangeChangeListener.onPositionDrag(dragPosition);
|
||||
} else {
|
||||
onRangeChangeListener.onRangeDrag(getMinValue(), getMaxValue(), getDuration(), dragThumb);
|
||||
setOutputQuality(onRangeChangeListener.getQuality(getClipDuration(), getDuration()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -449,7 +365,6 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
onRangeChangeListener.onEndPositionDrag(dragPosition);
|
||||
} else {
|
||||
onRangeChangeListener.onRangeDragEnd(getMinValue(), getMaxValue(), getDuration(), dragThumb);
|
||||
setOutputQuality(onRangeChangeListener.getQuality(getClipDuration(), getDuration()));
|
||||
}
|
||||
lastDragThumb = dragThumb;
|
||||
dragEndTimeMs = System.currentTimeMillis();
|
||||
@@ -466,20 +381,10 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setOutputQuality(@Nullable Quality outputQuality) {
|
||||
if (!Objects.equals(this.outputQuality, outputQuality)) {
|
||||
if (this.outputQuality == null) {
|
||||
qualityAvailableTimeMs = System.currentTimeMillis();
|
||||
}
|
||||
this.outputQuality = outputQuality;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Thumb closestThumb(@Px float x) {
|
||||
float midPoint = (right + left) / 2f;
|
||||
Thumb possibleThumb = x < midPoint ? Thumb.MIN : Thumb.MAX;
|
||||
int possibleThumbX = x < midPoint ? left : right;
|
||||
float midPoint = (right + left) / 2f;
|
||||
Thumb possibleThumb = x < midPoint ? Thumb.MIN : Thumb.MAX;
|
||||
int possibleThumbX = x < midPoint ? left : right;
|
||||
|
||||
if (Math.abs(x - possibleThumbX) < thumbTouchRadius) {
|
||||
return possibleThumb;
|
||||
@@ -503,7 +408,6 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
public void setRange(long minValue, long maxValue) {
|
||||
if (getDuration() > 0) {
|
||||
setMinMax(minValue, maxValue, Thumb.MIN);
|
||||
setMinMax(minValue, maxValue, Thumb.MAX);
|
||||
} else {
|
||||
externalMinValue = minValue;
|
||||
externalMaxValue = maxValue;
|
||||
@@ -529,34 +433,5 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
|
||||
void onRangeDrag(long minValue, long maxValue, long duration, Thumb thumb);
|
||||
|
||||
void onRangeDragEnd(long minValue, long maxValue, long duration, Thumb thumb);
|
||||
|
||||
@Nullable Quality getQuality(long clipDurationUs, long totalDurationUs);
|
||||
}
|
||||
|
||||
public static final class Quality {
|
||||
private final long fileSize;
|
||||
private final int qualityRange;
|
||||
|
||||
public Quality(long fileSize, int qualityRange) {
|
||||
this.fileSize = fileSize;
|
||||
this.qualityRange = qualityRange;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object o) {
|
||||
if (!(o instanceof Quality)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Quality quality = (Quality) o;
|
||||
|
||||
return fileSize == quality.fileSize &&
|
||||
qualityRange == quality.qualityRange;
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
int result = (int) (fileSize ^ (fileSize >>> 32));
|
||||
result = 31 * result + qualityRange;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.AsyncTask;
|
||||
@@ -15,6 +16,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.video.interfaces.MediaInput;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -26,12 +28,12 @@ import java.util.List;
|
||||
@RequiresApi(api = 23)
|
||||
public class VideoThumbnailsView extends View {
|
||||
|
||||
private static final String TAG = Log.tag(VideoThumbnailsView.class);
|
||||
private static final String TAG = Log.tag(VideoThumbnailsView.class);
|
||||
private static final int CORNER_RADIUS = ViewUtil.dpToPx(8);
|
||||
|
||||
private MediaInput input;
|
||||
private volatile ArrayList<Bitmap> thumbnails;
|
||||
private MediaInput input;
|
||||
private volatile ArrayList<Bitmap> thumbnails;
|
||||
private AsyncTask<Void, Bitmap, Void> thumbnailsTask;
|
||||
private OnDurationListener durationListener;
|
||||
|
||||
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final RectF tempRect = new RectF();
|
||||
@@ -39,6 +41,8 @@ public class VideoThumbnailsView extends View {
|
||||
private final Rect tempDrawRect = new Rect();
|
||||
private long duration = 0;
|
||||
|
||||
protected final Path clippingPath = new Path();
|
||||
|
||||
public VideoThumbnailsView(final Context context) {
|
||||
super(context);
|
||||
}
|
||||
@@ -52,6 +56,10 @@ public class VideoThumbnailsView extends View {
|
||||
}
|
||||
|
||||
public void setInput(@NonNull MediaInput input) {
|
||||
if (this.input != null && input.hasSameInput(this.input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.input = input;
|
||||
this.thumbnails = null;
|
||||
if (thumbnailsTask != null) {
|
||||
@@ -88,7 +96,15 @@ public class VideoThumbnailsView extends View {
|
||||
return;
|
||||
}
|
||||
|
||||
tempDrawRect.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
|
||||
final int left = getPaddingLeft();
|
||||
final int top = getPaddingTop();
|
||||
final int right = getWidth() - getPaddingRight();
|
||||
final int bottom = getHeight() - getPaddingBottom();
|
||||
|
||||
clippingPath.reset();
|
||||
clippingPath.addRoundRect(left, top, right, bottom, CORNER_RADIUS, CORNER_RADIUS, Path.Direction.CW);
|
||||
|
||||
tempDrawRect.set(left, top, right, bottom);
|
||||
|
||||
if (!drawRect.equals(tempDrawRect)) {
|
||||
drawRect.set(tempDrawRect);
|
||||
@@ -116,6 +132,9 @@ public class VideoThumbnailsView extends View {
|
||||
|
||||
tempRect.top = drawRect.top;
|
||||
tempRect.bottom = drawRect.bottom;
|
||||
canvas.save();
|
||||
|
||||
canvas.clipPath(clippingPath);
|
||||
|
||||
for (int i = 0; i < thumbnails.size(); i++) {
|
||||
tempRect.left = drawRect.left + i * thumbnailWidth;
|
||||
@@ -139,17 +158,12 @@ public class VideoThumbnailsView extends View {
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
public void setDurationListener(OnDurationListener durationListener) {
|
||||
this.durationListener = durationListener;
|
||||
}
|
||||
|
||||
private void setDuration(long duration) {
|
||||
if (durationListener != null) {
|
||||
durationListener.onDurationKnown(duration);
|
||||
}
|
||||
public void setDuration(long duration) {
|
||||
if (this.duration != duration) {
|
||||
this.duration = duration;
|
||||
afterDurationChange(duration);
|
||||
@@ -159,7 +173,7 @@ public class VideoThumbnailsView extends View {
|
||||
protected void afterDurationChange(long duration) {
|
||||
}
|
||||
|
||||
protected long getDuration() {
|
||||
public long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user