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 {
Log.d(TAG, "Running for: " + attachmentId);
AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
DatabaseAttachment databaseAttachment = database.getAttachment(attachmentId);
AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
DatabaseAttachment databaseAttachment = database.getAttachment(attachmentId);
if (databaseAttachment == null) {
throw new UndeliverableMessageException("Cannot find the specified attachment.");
@ -141,7 +141,7 @@ public final class AttachmentCompressionJob extends BaseJob {
{
try {
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)) {
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,
@NonNull AttachmentDatabase attachmentDatabase,
@NonNull DatabaseAttachment attachment,
@NonNull MediaConstraints constraints,
@NonNull EventBus eventBus,
@NonNull InMemoryTranscoder.CancelationSignal cancelationSignal)
private static @NonNull DatabaseAttachment transcodeVideoIfNeededToDatabase(@NonNull Context context,
@NonNull AttachmentDatabase attachmentDatabase,
@NonNull DatabaseAttachment attachment,
@NonNull MediaConstraints constraints,
@NonNull EventBus eventBus,
@NonNull InMemoryTranscoder.CancelationSignal cancelationSignal)
throws UndeliverableMessageException
{
AttachmentDatabase.TransformProperties transformProperties = attachment.getTransformProperties();
@ -179,7 +179,7 @@ public final class AttachmentCompressionJob extends BaseJob {
if (transformProperties.isVideoEdited()) {
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))) {
@ -210,6 +210,11 @@ public final class AttachmentCompressionJob extends BaseJob {
attachmentDatabase.updateAttachmentData(attachment, mediaStream, transformProperties.isVideoEdited());
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) {
throw new UndeliverableMessageException("Failed to transcode", e);
}
return attachment;
}
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.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
@ -17,16 +18,21 @@ import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import org.thoughtcrime.securesms.R;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
@RequiresApi(api = 23)
public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView {
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 paintGrey = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Rect tempDrawRect = new Rect();
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint paintGrey = new Paint(Paint.ANTI_ALIAS_FLAG);
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 chevronRight;
@ -42,8 +48,8 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
private long downMin;
private long downMax;
private Thumb dragThumb;
private Thumb lastDragThumb;
private OnRangeChangeListener onRangeChangeListener;
@Px private int thumbSizePixels;
@Px private int thumbTouchRadius;
@Px private int cursorPixels;
@ -52,6 +58,11 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
@ColorInt private int thumbColorEdited;
private long actualPosition;
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) {
super(context);
@ -71,13 +82,19 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
private void init(final @Nullable AttributeSet attrs) {
if (attrs != null) {
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.VideoThumbnailsRangeSelectorView, 0, 0);
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);
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);
thumbHintBackgroundColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbHintBackgroundColor, 0xff00ff00);
} finally {
typedArray.recycle();
}
}
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);
paint.setStrokeWidth(2);
thumbTimeTextPaint.setTextSize(thumbHintTextSize);
thumbTimeTextPaint.setColor(thumbHintTextColor);
thumbTimeBackgroundPaint.setStyle(Paint.Style.FILL_AND_STROKE);
thumbTimeBackgroundPaint.setColor(thumbHintBackgroundColor);
}
@Override
@ -194,6 +217,16 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
chevronRight.draw(canvas);
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
if (left <= cursor && cursor <= right && dragThumb != Thumb.MIN && dragThumb != Thumb.MAX) {
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() {
return minValue == null ? 0 : minValue;
}
@ -257,11 +334,13 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
public boolean onTouchEvent(MotionEvent event) {
int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
xDown = event.getX();
downCursor = actualPosition;
downMin = getMinValue();
downMax = getMaxValue();
dragThumb = closestThumb(event.getX());
xDown = event.getX();
downCursor = actualPosition;
downMin = getMinValue();
downMax = getMaxValue();
dragThumb = closestThumb(event.getX());
dragStartTimeMs = System.currentTimeMillis();
invalidate();
return dragThumb != null;
}
@ -297,7 +376,9 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView
} else {
onRangeChangeListener.onRangeDragEnd(getMinValue(), getMaxValue(), getDuration(), dragThumb);
}
dragThumb = null;
lastDragThumb = dragThumb;
dragEndTimeMs = System.currentTimeMillis();
dragThumb = null;
invalidate();
}
return true;

View file

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

View file

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