Video Sending Redesign

This commit is contained in:
Nicholas Tinsley
2024-03-01 13:15:08 -05:00
committed by Alex Hart
parent 276e253fdf
commit c53abe0941
65 changed files with 1830 additions and 1621 deletions

View File

@@ -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!");

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}