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

@@ -320,12 +320,6 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
return imageUri;
}
@Nullable
@Override
public View getPlaybackControls() {
return null;
}
@Override
public Object saveState() {
Data data = new Data();

View File

@@ -1,226 +0,0 @@
package org.thoughtcrime.securesms.scribbles;
import android.animation.Animator;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Rect;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.OvershootInterpolator;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.view.ViewCompat;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.media.DecryptableUriMediaInput;
import org.thoughtcrime.securesms.mms.VideoSlide;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.video.TranscodingQuality;
import org.thoughtcrime.securesms.video.VideoBitRateCalculator;
import org.thoughtcrime.securesms.video.VideoUtil;
import org.thoughtcrime.securesms.video.videoconverter.VideoThumbnailsRangeSelectorView;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* The HUD (heads-up display) that contains all of the tools for editing video.
*/
public final class VideoEditorHud extends LinearLayout {
@SuppressWarnings("unused")
private static final String TAG = Log.tag(VideoEditorHud.class);
private final List<Rect> exclusionZone = List.of(new Rect());
private VideoThumbnailsRangeSelectorView videoTimeLine;
private EventListener eventListener;
private View playOverlay;
public VideoEditorHud(@NonNull Context context) {
super(context);
initialize();
}
public VideoEditorHud(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initialize();
}
public VideoEditorHud(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
private void initialize() {
View root = inflate(getContext(), R.layout.video_editor_hud, this);
setOrientation(VERTICAL);
videoTimeLine = root.findViewById(R.id.video_timeline);
playOverlay = root.findViewById(R.id.play_overlay);
playOverlay.setOnClickListener(v -> eventListener.onPlay());
}
public void setEventListener(EventListener eventListener) {
this.eventListener = eventListener;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final Rect outRect = exclusionZone.get(0);
videoTimeLine.getHitRect(outRect);
outRect.left = l;
outRect.right = r;
ViewCompat.setSystemGestureExclusionRects(this, exclusionZone);
super.onLayout(changed, l, t, r, b);
}
@RequiresApi(api = 23)
public void setVideoSource(@NonNull VideoSlide slide, @NonNull VideoBitRateCalculator videoBitRateCalculator, long maxSendSize)
throws IOException
{
Uri uri = slide.getUri();
if (uri == null || !slide.hasVideo()) {
return;
}
videoTimeLine.setInput(DecryptableUriMediaInput.createForUri(getContext(), uri));
long size = tryGetUriSize(getContext(), uri, Long.MAX_VALUE);
if (size > maxSendSize) {
videoTimeLine.setTimeLimit(videoBitRateCalculator.getMaxVideoUploadDurationInSeconds(), TimeUnit.SECONDS);
}
videoTimeLine.setOnRangeChangeListener(new VideoThumbnailsRangeSelectorView.OnRangeChangeListener() {
@Override
public void onPositionDrag(long position) {
if (eventListener != null) {
eventListener.onSeek(position, false);
}
}
@Override
public void onEndPositionDrag(long position) {
if (eventListener != null) {
eventListener.onSeek(position, true);
}
}
@Override
public void onRangeDrag(long minValueUs, long maxValueUs, long durationUs, VideoThumbnailsRangeSelectorView.Thumb thumb) {
if (eventListener != null) {
eventListener.onEditVideoDuration(durationUs, minValueUs, maxValueUs, thumb == VideoThumbnailsRangeSelectorView.Thumb.MIN, false);
}
}
@Override
public void onRangeDragEnd(long minValueUs, long maxValueUs, long durationUs, VideoThumbnailsRangeSelectorView.Thumb thumb) {
if (eventListener != null) {
eventListener.onEditVideoDuration(durationUs, minValueUs, maxValueUs, thumb == VideoThumbnailsRangeSelectorView.Thumb.MIN, true);
}
}
@Override
public VideoThumbnailsRangeSelectorView.Quality getQuality(long clipDurationUs, long totalDurationUs) {
int inputBitRate = VideoBitRateCalculator.bitRate(size, TimeUnit.MICROSECONDS.toMillis(totalDurationUs));
TranscodingQuality targetQuality = videoBitRateCalculator.getTargetQuality(TimeUnit.MICROSECONDS.toMillis(clipDurationUs), inputBitRate);
return new VideoThumbnailsRangeSelectorView.Quality(targetQuality.getFileSizeEstimate(), (int) (100 * targetQuality.getQuality()));
}
});
}
public void showPlayButton() {
playOverlay.setVisibility(VISIBLE);
playOverlay.animate()
.setListener(null)
.alpha(1)
.scaleX(1).scaleY(1)
.setInterpolator(new OvershootInterpolator())
.start();
}
public void fadePlayButton() {
playOverlay.animate()
.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
playOverlay.setVisibility(GONE);
}
@Override
public void onAnimationStart(Animator animation) {}
@Override
public void onAnimationCancel(Animator animation) {}
@Override
public void onAnimationRepeat(Animator animation) {}
})
.alpha(0)
.scaleX(0.8f).scaleY(0.8f)
.start();
}
public void hidePlayButton() {
playOverlay.setVisibility(GONE);
playOverlay.setAlpha(0);
playOverlay.setScaleX(0.8f);
playOverlay.setScaleY(0.8f);
}
@RequiresApi(api = 23)
public void setDurationRange(long totalDuration, long fromDuration, long toDuration) {
videoTimeLine.setRange(fromDuration, toDuration);
}
@RequiresApi(api = 23)
public void setPosition(long playbackPositionUs) {
videoTimeLine.setActualPosition(playbackPositionUs);
}
public interface EventListener {
void onEditVideoDuration(long totalDurationUs, long startTimeUs, long endTimeUs, boolean fromEdited, boolean editingComplete);
void onPlay();
void onSeek(long position, boolean dragComplete);
}
private long tryGetUriSize(@NonNull Context context, @NonNull Uri uri, long defaultValue) {
try {
return getSize(context, uri);
} catch (IOException e) {
Log.w(TAG, e);
return defaultValue;
}
}
private static long getSize(@NonNull Context context, @NonNull Uri uri) throws IOException {
long size = 0;
try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
if (cursor != null && cursor.moveToFirst() && cursor.getColumnIndex(OpenableColumns.SIZE) >= 0) {
size = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE));
}
}
if (size <= 0) {
size = MediaUtil.getMediaSize(context, uri);
}
return size;
}
}

View File

@@ -0,0 +1,55 @@
package org.thoughtcrime.securesms.scribbles
import android.animation.Animator
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import org.signal.core.util.logging.Log.tag
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.util.createDefaultCubicBezierInterpolator
/**
* The play button overlay for controlling playback in the video editor.
*/
class VideoEditorPlayButtonLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr) {
private val playOverlay: View = inflate(this.context, R.layout.video_editor_hud, this).findViewById(R.id.play_overlay)
fun setPlayClickListener(listener: OnClickListener?) {
playOverlay.setOnClickListener(listener)
}
fun showPlayButton() {
playOverlay.visibility = VISIBLE
playOverlay.animate()
.setListener(null)
.alpha(1f)
.setInterpolator(createDefaultCubicBezierInterpolator())
.setDuration(500)
.start()
}
fun fadePlayButton() {
playOverlay.animate()
.setListener(object : Animator.AnimatorListener {
override fun onAnimationEnd(animation: Animator) { playOverlay.visibility = GONE }
override fun onAnimationStart(animation: Animator) = Unit
override fun onAnimationCancel(animation: Animator) = Unit
override fun onAnimationRepeat(animation: Animator) = Unit
})
.alpha(0f)
.setInterpolator(createDefaultCubicBezierInterpolator())
.setDuration(200)
.start()
}
fun hidePlayButton() {
playOverlay.visibility = GONE
playOverlay.setAlpha(0f)
}
companion object {
@Suppress("unused")
private val TAG = tag(VideoEditorPlayButtonLayout::class.java)
}
}