Add video trimming time indication pill.

This commit is contained in:
Alan Evans
2020-03-05 15:13:22 -04:00
committed by Greyson Parrelli
parent 2152b4a2cd
commit 51603be5ec
4 changed files with 123 additions and 29 deletions

View File

@@ -108,8 +108,8 @@ public final class AttachmentCompressionJob extends BaseJob {
public void onRun() throws Exception { public void onRun() throws Exception {
Log.d(TAG, "Running for: " + attachmentId); Log.d(TAG, "Running for: " + attachmentId);
AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context); AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
DatabaseAttachment databaseAttachment = database.getAttachment(attachmentId); DatabaseAttachment databaseAttachment = database.getAttachment(attachmentId);
if (databaseAttachment == null) { if (databaseAttachment == null) {
throw new UndeliverableMessageException("Cannot find the specified attachment."); throw new UndeliverableMessageException("Cannot find the specified attachment.");
@@ -141,7 +141,7 @@ public final class AttachmentCompressionJob extends BaseJob {
{ {
try { try {
if (MediaUtil.isVideo(attachment)) { if (MediaUtil.isVideo(attachment)) {
transcodeVideoIfNeededToDatabase(context, attachmentDatabase, attachment, constraints, EventBus.getDefault(), this::isCanceled); attachment = transcodeVideoIfNeededToDatabase(context, attachmentDatabase, attachment, constraints, EventBus.getDefault(), this::isCanceled);
if (!constraints.isSatisfied(context, attachment)) { if (!constraints.isSatisfied(context, attachment)) {
throw new UndeliverableMessageException("Size constraints could not be met on video!"); throw new UndeliverableMessageException("Size constraints could not be met on video!");
} }
@@ -163,12 +163,12 @@ public final class AttachmentCompressionJob extends BaseJob {
} }
} }
private static void transcodeVideoIfNeededToDatabase(@NonNull Context context, private static @NonNull DatabaseAttachment transcodeVideoIfNeededToDatabase(@NonNull Context context,
@NonNull AttachmentDatabase attachmentDatabase, @NonNull AttachmentDatabase attachmentDatabase,
@NonNull DatabaseAttachment attachment, @NonNull DatabaseAttachment attachment,
@NonNull MediaConstraints constraints, @NonNull MediaConstraints constraints,
@NonNull EventBus eventBus, @NonNull EventBus eventBus,
@NonNull InMemoryTranscoder.CancelationSignal cancelationSignal) @NonNull InMemoryTranscoder.CancelationSignal cancelationSignal)
throws UndeliverableMessageException throws UndeliverableMessageException
{ {
AttachmentDatabase.TransformProperties transformProperties = attachment.getTransformProperties(); AttachmentDatabase.TransformProperties transformProperties = attachment.getTransformProperties();
@@ -179,7 +179,7 @@ public final class AttachmentCompressionJob extends BaseJob {
if (transformProperties.isVideoEdited()) { if (transformProperties.isVideoEdited()) {
throw new UndeliverableMessageException("Video edited, but transcode is not available"); throw new UndeliverableMessageException("Video edited, but transcode is not available");
} }
return; return attachment;
} }
try (NotificationController notification = GenericForegroundService.startForegroundTask(context, context.getString(R.string.AttachmentUploadJob_compressing_video_start))) { try (NotificationController notification = GenericForegroundService.startForegroundTask(context, context.getString(R.string.AttachmentUploadJob_compressing_video_start))) {
@@ -210,6 +210,11 @@ public final class AttachmentCompressionJob extends BaseJob {
attachmentDatabase.updateAttachmentData(attachment, mediaStream, transformProperties.isVideoEdited()); attachmentDatabase.updateAttachmentData(attachment, mediaStream, transformProperties.isVideoEdited());
attachmentDatabase.markAttachmentAsTransformed(attachment.getAttachmentId()); attachmentDatabase.markAttachmentAsTransformed(attachment.getAttachmentId());
DatabaseAttachment updatedAttachment = attachmentDatabase.getAttachment(attachment.getAttachmentId());
if (updatedAttachment == null) {
throw new AssertionError();
}
return updatedAttachment;
} }
} }
} }
@@ -226,6 +231,7 @@ public final class AttachmentCompressionJob extends BaseJob {
} catch (IOException | MmsException | VideoSizeException e) { } catch (IOException | MmsException | VideoSizeException e) {
throw new UndeliverableMessageException("Failed to transcode", e); throw new UndeliverableMessageException("Failed to transcode", e);
} }
return attachment;
} }
private static MediaStream getResizedMedia(@NonNull Context context, private static MediaStream getResizedMedia(@NonNull Context context,

View File

@@ -5,6 +5,7 @@ import android.content.res.TypedArray;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.MotionEvent; import android.view.MotionEvent;
@@ -17,16 +18,21 @@ import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import java.util.Locale;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@RequiresApi(api = 23) @RequiresApi(api = 23)
public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView { public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView {
private static final long MINIMUM_SELECTABLE_RANGE = TimeUnit.MILLISECONDS.toMicros(500); private static final long MINIMUM_SELECTABLE_RANGE = TimeUnit.MILLISECONDS.toMicros(500);
private static final int ANIMATION_DURATION_MS = 100;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint paintGrey = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint paintGrey = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Rect tempDrawRect = new Rect(); private final Paint thumbTimeTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
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 chevronLeft;
private Drawable chevronRight; private Drawable chevronRight;
@@ -42,8 +48,8 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
private long downMin; private long downMin;
private long downMax; private long downMax;
private Thumb dragThumb; private Thumb dragThumb;
private Thumb lastDragThumb;
private OnRangeChangeListener onRangeChangeListener; private OnRangeChangeListener onRangeChangeListener;
@Px private int thumbSizePixels; @Px private int thumbSizePixels;
@Px private int thumbTouchRadius; @Px private int thumbTouchRadius;
@Px private int cursorPixels; @Px private int cursorPixels;
@@ -52,6 +58,11 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
@ColorInt private int thumbColorEdited; @ColorInt private int thumbColorEdited;
private long actualPosition; private long actualPosition;
private long dragPosition; private long dragPosition;
@Px private int thumbHintTextSize;
@ColorInt private int thumbHintTextColor;
@ColorInt private int thumbHintBackgroundColor;
private long dragStartTimeMs;
private long dragEndTimeMs;
public VideoThumbnailsRangeSelectorView(final Context context) { public VideoThumbnailsRangeSelectorView(final Context context) {
super(context); super(context);
@@ -71,13 +82,19 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
private void init(final @Nullable AttributeSet attrs) { private void init(final @Nullable AttributeSet attrs) {
if (attrs != null) { if (attrs != null) {
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.VideoThumbnailsRangeSelectorView, 0, 0); TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.VideoThumbnailsRangeSelectorView, 0, 0);
try {
thumbSizePixels = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_thumbWidth, 1); thumbSizePixels = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_thumbWidth, 1);
cursorPixels = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_cursorWidth, 1); cursorPixels = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_cursorWidth, 1);
thumbColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbColor, 0xffff0000); thumbColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbColor, 0xffff0000);
thumbColorEdited = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbColorEdited, thumbColor); thumbColorEdited = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbColorEdited, thumbColor);
cursorColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_cursorColor, thumbColor); cursorColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_cursorColor, thumbColor);
thumbTouchRadius = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_thumbTouchRadius, 50); thumbTouchRadius = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_thumbTouchRadius, 50);
thumbHintTextSize = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_thumbHintTextSize, 0);
thumbHintTextColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbHintTextColor, 0xffff0000);
thumbHintBackgroundColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbHintBackgroundColor, 0xff00ff00);
} finally {
typedArray.recycle();
}
} }
chevronLeft = VectorDrawableCompat.create(getResources(), R.drawable.ic_chevron_left_black_8dp, null); chevronLeft = VectorDrawableCompat.create(getResources(), R.drawable.ic_chevron_left_black_8dp, null);
@@ -88,6 +105,12 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
paintGrey.setStrokeWidth(1); paintGrey.setStrokeWidth(1);
paint.setStrokeWidth(2); paint.setStrokeWidth(2);
thumbTimeTextPaint.setTextSize(thumbHintTextSize);
thumbTimeTextPaint.setColor(thumbHintTextColor);
thumbTimeBackgroundPaint.setStyle(Paint.Style.FILL_AND_STROKE);
thumbTimeBackgroundPaint.setColor(thumbHintBackgroundColor);
} }
@Override @Override
@@ -194,6 +217,16 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
chevronRight.draw(canvas); chevronRight.draw(canvas);
canvas.restore(); canvas.restore();
// draw time hint pill
if (thumbHintTextSize > 0) {
if (dragStartTimeMs > 0 && (dragThumb == Thumb.MIN || dragThumb == Thumb.MAX)) {
drawTimeHint(canvas, drawableWidth, drawableHeight, dragThumb, false);
}
if (dragEndTimeMs > 0 && (lastDragThumb == Thumb.MIN || lastDragThumb == Thumb.MAX)) {
drawTimeHint(canvas, drawableWidth, drawableHeight, lastDragThumb, true);
}
}
// draw current position marker // draw current position marker
if (left <= cursor && cursor <= right && dragThumb != Thumb.MIN && dragThumb != Thumb.MAX) { if (left <= cursor && cursor <= right && dragThumb != Thumb.MIN && dragThumb != Thumb.MAX) {
canvas.translate(cursorPixels / 2, 0); canvas.translate(cursorPixels / 2, 0);
@@ -203,6 +236,50 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
} }
} }
private void drawTimeHint(Canvas canvas, int drawableWidth, int drawableHeight, Thumb dragThumb, boolean fadeOut) {
canvas.save();
long microsecondValue = dragThumb == Thumb.MIN ? getMinValue() : getMaxValue();
long seconds = TimeUnit.MICROSECONDS.toSeconds(microsecondValue);
String timeString = String.format(Locale.getDefault(), "%d:%02d", seconds / 60, seconds % 60);
float topBottomPadding = thumbHintTextSize * 0.5f;
float leftRightPadding = thumbHintTextSize * 0.75f;
thumbTimeTextPaint.getTextBounds(timeString, 0, timeString.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 = 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);
if (dragThumb == Thumb.MAX) {
canvas.translate(Math.min(right, drawableWidth - halfPillWidth), 0);
} else {
canvas.translate(Math.max(left, halfPillWidth), 0);
}
canvas.translate(0, drawableHeight + halfPillHeight);
canvas.scale(scaleIn, scaleIn);
thumbTimeBackgroundPaint.setAlpha(alpha);
thumbTimeTextPaint.setAlpha(alpha);
canvas.translate(leftRightPadding - halfPillWidth, halfPillHeight);
canvas.drawRoundRect(timePillRect, halfPillHeight, halfPillHeight, thumbTimeBackgroundPaint);
canvas.drawText(timeString, 0, 0, thumbTimeTextPaint);
canvas.restore();
if (fadeOut && animationTime > 0 || !fadeOut && animationTime < ANIMATION_DURATION_MS) {
invalidate();
} else {
if (fadeOut) {
lastDragThumb = null;
}
}
}
public long getMinValue() { public long getMinValue() {
return minValue == null ? 0 : minValue; return minValue == null ? 0 : minValue;
} }
@@ -257,11 +334,13 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
public boolean onTouchEvent(MotionEvent event) { public boolean onTouchEvent(MotionEvent event) {
int actionMasked = event.getActionMasked(); int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) { if (actionMasked == MotionEvent.ACTION_DOWN) {
xDown = event.getX(); xDown = event.getX();
downCursor = actualPosition; downCursor = actualPosition;
downMin = getMinValue(); downMin = getMinValue();
downMax = getMaxValue(); downMax = getMaxValue();
dragThumb = closestThumb(event.getX()); dragThumb = closestThumb(event.getX());
dragStartTimeMs = System.currentTimeMillis();
invalidate();
return dragThumb != null; return dragThumb != null;
} }
@@ -297,7 +376,9 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
} else { } else {
onRangeChangeListener.onRangeDragEnd(getMinValue(), getMaxValue(), getDuration(), dragThumb); onRangeChangeListener.onRangeDragEnd(getMinValue(), getMaxValue(), getDuration(), dragThumb);
} }
dragThumb = null; lastDragThumb = dragThumb;
dragEndTimeMs = System.currentTimeMillis();
dragThumb = null;
invalidate(); invalidate();
} }
return true; return true;

View File

@@ -10,7 +10,8 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:clipChildren="false">
<org.thoughtcrime.securesms.video.videoconverter.VideoThumbnailsRangeSelectorView <org.thoughtcrime.securesms.video.videoconverter.VideoThumbnailsRangeSelectorView
android:id="@+id/video_timeline" android:id="@+id/video_timeline"
@@ -23,8 +24,11 @@
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:thumbColor="#fff" app:thumbColor="@color/core_white"
app:thumbColorEdited="#ff0" app:thumbColorEdited="#ff0"
app:thumbHintBackgroundColor="@color/core_grey_90"
app:thumbHintTextColor="@color/core_white"
app:thumbHintTextSize="14sp"
app:thumbTouchRadius="24dp" app:thumbTouchRadius="24dp"
app:thumbWidth="16dp" /> app:thumbWidth="16dp" />

View File

@@ -482,6 +482,9 @@
<attr name="cursorWidth" format="dimension" /> <attr name="cursorWidth" format="dimension" />
<attr name="cursorColor" format="color" /> <attr name="cursorColor" format="color" />
<attr name="thumbTouchRadius" format="dimension" /> <attr name="thumbTouchRadius" format="dimension" />
<attr name="thumbHintTextSize" format="dimension" />
<attr name="thumbHintTextColor" format="color" />
<attr name="thumbHintBackgroundColor" format="color" />
</declare-styleable> </declare-styleable>
<declare-styleable name="GroupMemberListView"> <declare-styleable name="GroupMemberListView">