NekoX/TMessagesProj/src/main/java/org/telegram/ui/Components/InstantCameraView.java

2856 lines
120 KiB
Java

/*
* This is the source code of Telegram for Android v. 5.x.x.
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2013-2018.
*/
package org.telegram.ui.Components;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.hardware.Camera;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaRecorder;
import android.net.Uri;
import android.opengl.EGL14;
import android.opengl.EGLExt;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.util.Log;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.BuildVars;
import org.telegram.messenger.DispatchQueue;
import org.telegram.messenger.FileLoader;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.ImageReceiver;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MediaController;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.R;
import org.telegram.messenger.SharedConfig;
import org.telegram.messenger.UserConfig;
import org.telegram.messenger.Utilities;
import org.telegram.messenger.VideoEditedInfo;
import org.telegram.messenger.camera.CameraController;
import org.telegram.messenger.camera.CameraInfo;
import org.telegram.messenger.camera.CameraSession;
import org.telegram.messenger.camera.Size;
import org.telegram.messenger.video.MP4Builder;
import org.telegram.messenger.video.Mp4Movie;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.ChatActivity;
import org.telegram.ui.Components.voip.CellFlickerDrawable;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL;
@TargetApi(18)
public class InstantCameraView extends FrameLayout implements NotificationCenter.NotificationCenterDelegate {
private int currentAccount = UserConfig.selectedAccount;
private InstantViewCameraContainer cameraContainer;
private ChatActivity baseFragment;
private Paint paint;
private RectF rect;
private ImageView switchCameraButton;
AnimatedVectorDrawable switchCameraDrawable = null;
private ImageView muteImageView;
private float progress;
private CameraInfo selectedCamera;
private boolean isFrontface = true;
private volatile boolean cameraReady;
private AnimatorSet muteAnimation;
private TLRPC.InputFile file;
private TLRPC.InputEncryptedFile encryptedFile;
private byte[] key;
private byte[] iv;
private long size;
private boolean isSecretChat;
private VideoEditedInfo videoEditedInfo;
private VideoPlayer videoPlayer;
private Bitmap lastBitmap;
private int recordingGuid;
private int[] position = new int[2];
private int[] cameraTexture = new int[1];
private int[] oldCameraTexture = new int[1];
private float cameraTextureAlpha = 1.0f;
private AnimatorSet animatorSet;
private boolean deviceHasGoodCamera;
private boolean requestingPermissions;
private File cameraFile;
private long recordStartTime;
private boolean recording;
private long recordedTime;
private boolean cancelled;
private CameraGLThread cameraThread;
private Size previewSize;
private Size pictureSize;
private Size aspectRatio = SharedConfig.roundCamera16to9 ? new Size(16, 9) : new Size(4, 3);
private TextureView textureView;
private BackupImageView textureOverlayView;
private CameraSession cameraSession;
private boolean needDrawFlickerStub;
private float panTranslationY;
private float animationTranslationY;
private float[] mMVPMatrix = new float[16];
private float[] mSTMatrix = new float[16];
private float[] moldSTMatrix = new float[16];
private static final String VERTEX_SHADER =
"uniform mat4 uMVPMatrix;\n" +
"uniform mat4 uSTMatrix;\n" +
"attribute vec4 aPosition;\n" +
"attribute vec4 aTextureCoord;\n" +
"varying vec2 vTextureCoord;\n" +
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" +
" vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
"}\n";
private static final String FRAGMENT_SCREEN_SHADER =
"#extension GL_OES_EGL_image_external : require\n" +
"precision lowp float;\n" +
"varying vec2 vTextureCoord;\n" +
"uniform samplerExternalOES sTexture;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
"}\n";
private FloatBuffer vertexBuffer;
private FloatBuffer textureBuffer;
private FloatBuffer oldTextureTextureBuffer;
private float scaleX;
private float scaleY;
private Size oldTexturePreviewSize;
private boolean flipAnimationInProgress;
private View parentView;
public boolean opened;
private BlurBehindDrawable blurBehindDrawable;
float pinchStartDistance;
float pinchScale;
boolean isInPinchToZoomTouchMode;
boolean maybePinchToZoomTouchMode;
private int pointerId1, pointerId2;
private int textureViewSize;
private boolean isMessageTransition;
private boolean updateTextureViewSize;
private final Theme.ResourcesProvider resourcesProvider;
private final static int audioSampleRate = 48000;
@SuppressLint("ClickableViewAccessibility")
public InstantCameraView(Context context, ChatActivity parentFragment, Theme.ResourcesProvider resourcesProvider) {
super(context);
this.resourcesProvider = resourcesProvider;
parentView = parentFragment.getFragmentView();
setWillNotDraw(false);
baseFragment = parentFragment;
recordingGuid = baseFragment.getClassGuid();
isSecretChat = baseFragment.getCurrentEncryptedChat() != null;
paint = new Paint(Paint.ANTI_ALIAS_FLAG) {
@Override
public void setAlpha(int a) {
super.setAlpha(a);
invalidate();
}
};
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeWidth(AndroidUtilities.dp(3));
paint.setColor(0xffffffff);
rect = new RectF();
if (Build.VERSION.SDK_INT >= 21) {
cameraContainer = new InstantViewCameraContainer(context) {
@Override
public void setScaleX(float scaleX) {
super.setScaleX(scaleX);
InstantCameraView.this.invalidate();
}
@Override
public void setAlpha(float alpha) {
super.setAlpha(alpha);
InstantCameraView.this.invalidate();
}
};
cameraContainer.setOutlineProvider(new ViewOutlineProvider() {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void getOutline(View view, Outline outline) {
outline.setOval(0, 0, textureViewSize, textureViewSize);
}
});
cameraContainer.setClipToOutline(true);
cameraContainer.setWillNotDraw(false);
} else {
final Path path = new Path();
final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(0xff000000);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
cameraContainer = new InstantViewCameraContainer(context) {
@Override
public void setScaleX(float scaleX) {
super.setScaleX(scaleX);
InstantCameraView.this.invalidate();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
path.reset();
path.addCircle(w / 2, h / 2, w / 2, Path.Direction.CW);
path.toggleInverseFillType();
}
@Override
protected void dispatchDraw(Canvas canvas) {
try {
super.dispatchDraw(canvas);
canvas.drawPath(path, paint);
} catch (Exception ignore) {
}
}
};
cameraContainer.setWillNotDraw(false);
cameraContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
addView(cameraContainer, new LayoutParams(AndroidUtilities.roundPlayingMessageSize, AndroidUtilities.roundPlayingMessageSize, Gravity.CENTER));
switchCameraButton = new ImageView(context);
switchCameraButton.setScaleType(ImageView.ScaleType.CENTER);
switchCameraButton.setContentDescription(LocaleController.getString("AccDescrSwitchCamera", R.string.AccDescrSwitchCamera));
addView(switchCameraButton, LayoutHelper.createFrame(62, 62, Gravity.LEFT | Gravity.BOTTOM, 8, 0, 0, 0));
switchCameraButton.setOnClickListener(v -> {
if (!cameraReady || cameraSession == null || !cameraSession.isInitied() || cameraThread == null) {
return;
}
switchCamera();
if (switchCameraDrawable != null) {
switchCameraDrawable.start();
}
flipAnimationInProgress = true;
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1f);
valueAnimator.setDuration(300);
valueAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float p = (float) valueAnimator.getAnimatedValue();
if (p < 0.5f) {
p = (1f - p / 0.5f);
} else {
p = (p - 0.5f) / 0.5f;
}
float scaleDown = 0.9f + 0.1f * p;
cameraContainer.setScaleX(p * scaleDown);
cameraContainer.setScaleY(scaleDown);
textureOverlayView.setScaleX(p * scaleDown);
textureOverlayView.setScaleY(scaleDown);
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
cameraContainer.setScaleX(1f);
cameraContainer.setScaleY(1f);
textureOverlayView.setScaleY(1f);
textureOverlayView.setScaleX(1f);
flipAnimationInProgress = false;
invalidate();
}
});
valueAnimator.start();
});
muteImageView = new ImageView(context);
muteImageView.setScaleType(ImageView.ScaleType.CENTER);
muteImageView.setImageResource(R.drawable.video_mute);
muteImageView.setAlpha(0.0f);
addView(muteImageView, LayoutHelper.createFrame(48, 48, Gravity.CENTER));
Paint blackoutPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
blackoutPaint.setColor(ColorUtils.setAlphaComponent(Color.BLACK, 40));
textureOverlayView = new BackupImageView(getContext()) {
CellFlickerDrawable flickerDrawable = new CellFlickerDrawable();
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (needDrawFlickerStub) {
flickerDrawable.setParentWidth(textureViewSize);
AndroidUtilities.rectTmp.set(0, 0, textureViewSize, textureViewSize);
float rad = AndroidUtilities.rectTmp.width() / 2f;
canvas.drawRoundRect(AndroidUtilities.rectTmp, rad, rad, blackoutPaint);
AndroidUtilities.rectTmp.inset(AndroidUtilities.dp(1), AndroidUtilities.dp(1));
flickerDrawable.draw(canvas, AndroidUtilities.rectTmp, rad, null);
invalidate();
}
}
};
addView(textureOverlayView, new LayoutParams(AndroidUtilities.roundPlayingMessageSize, AndroidUtilities.roundPlayingMessageSize, Gravity.CENTER));
setVisibility(INVISIBLE);
blurBehindDrawable = new BlurBehindDrawable(parentView, this, 0, resourcesProvider);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (updateTextureViewSize) {
int newSize;
if (MeasureSpec.getSize(heightMeasureSpec) > MeasureSpec.getSize(widthMeasureSpec) * 1.3f) {
newSize = AndroidUtilities.roundPlayingMessageSize;
} else {
newSize = AndroidUtilities.roundMessageSize;
}
if (newSize != textureViewSize) {
textureViewSize = newSize;
textureOverlayView.getLayoutParams().width = textureOverlayView.getLayoutParams().height = textureViewSize;
cameraContainer.getLayoutParams().width = cameraContainer.getLayoutParams().height = textureViewSize;
((LayoutParams) muteImageView.getLayoutParams()).topMargin = textureViewSize / 2 - AndroidUtilities.dp(24);
textureOverlayView.setRoundRadius(textureViewSize / 2);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cameraContainer.invalidateOutline();
}
}
updateTextureViewSize = false;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private boolean checkPointerIds(MotionEvent ev) {
if (ev.getPointerCount() < 2) {
return false;
}
if (pointerId1 == ev.getPointerId(0) && pointerId2 == ev.getPointerId(1)) {
return true;
}
if (pointerId1 == ev.getPointerId(1) && pointerId2 == ev.getPointerId(0)) {
return true;
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.onInterceptTouchEvent(ev);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (getVisibility() != VISIBLE) {
animationTranslationY = getMeasuredHeight() / 2;
updateTranslationY();
}
blurBehindDrawable.checkSizes();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.fileUploaded);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.fileUploaded);
}
@Override
public void didReceivedNotification(int id, int account, Object... args) {
if (id == NotificationCenter.fileUploaded) {
final String location = (String) args[0];
if (cameraFile != null && cameraFile.getAbsolutePath().equals(location)) {
file = (TLRPC.InputFile) args[1];
encryptedFile = (TLRPC.InputEncryptedFile) args[2];
size = (Long) args[5];
if (encryptedFile != null) {
key = (byte[]) args[3];
iv = (byte[]) args[4];
}
}
}
}
public void destroy(boolean async, final Runnable beforeDestroyRunnable) {
if (cameraSession != null) {
cameraSession.destroy();
CameraController.getInstance().close(cameraSession, !async ? new CountDownLatch(1) : null, beforeDestroyRunnable);
}
}
@Override
protected void onDraw(Canvas canvas) {
blurBehindDrawable.draw(canvas);
float x = cameraContainer.getX();
float y = cameraContainer.getY();
rect.set(x - AndroidUtilities.dp(8), y - AndroidUtilities.dp(8), x + cameraContainer.getMeasuredWidth() + AndroidUtilities.dp(8), y + cameraContainer.getMeasuredHeight() + AndroidUtilities.dp(8));
if (recording) {
recordedTime = System.currentTimeMillis() - recordStartTime;
progress = Math.min(1f, recordedTime / 60000.0f);
invalidate();
}
if (progress != 0) {
canvas.save();
if (!flipAnimationInProgress) {
canvas.scale(cameraContainer.getScaleX(), cameraContainer.getScaleY(), rect.centerX(), rect.centerY());
}
canvas.drawArc(rect, -90, 360 * progress, false, paint);
canvas.restore();
}
if (Theme.chat_roundVideoShadow != null) {
int x1 = (int) x - AndroidUtilities.dp(3);
int y1 = (int) y - AndroidUtilities.dp(2);
canvas.save();
if (isMessageTransition) {
canvas.scale(cameraContainer.getScaleX(), cameraContainer.getScaleY(), x, y);
} else {
canvas.scale(cameraContainer.getScaleX(), cameraContainer.getScaleY(), x + textureViewSize / 2f, y + textureViewSize / 2f);
}
Theme.chat_roundVideoShadow.setAlpha((int) (cameraContainer.getAlpha() * 255));
Theme.chat_roundVideoShadow.setBounds(x1, y1, x1 + textureViewSize + AndroidUtilities.dp(6), y1 + textureViewSize + AndroidUtilities.dp(6));
Theme.chat_roundVideoShadow.draw(canvas);
canvas.restore();
}
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if (visibility != View.VISIBLE && blurBehindDrawable != null) {
blurBehindDrawable.clear();
}
switchCameraButton.setAlpha(0.0f);
cameraContainer.setAlpha(0.0f);
textureOverlayView.setAlpha(0.0f);
muteImageView.setAlpha(0.0f);
muteImageView.setScaleX(1.0f);
muteImageView.setScaleY(1.0f);
cameraContainer.setScaleX(0.1f);
cameraContainer.setScaleY(0.1f);
textureOverlayView.setScaleX(0.1f);
textureOverlayView.setScaleY(0.1f);
if (cameraContainer.getMeasuredWidth() != 0) {
cameraContainer.setPivotX(cameraContainer.getMeasuredWidth() / 2);
cameraContainer.setPivotY(cameraContainer.getMeasuredHeight() / 2);
textureOverlayView.setPivotX(textureOverlayView.getMeasuredWidth() / 2);
textureOverlayView.setPivotY(textureOverlayView.getMeasuredHeight() / 2);
}
try {
if (visibility == VISIBLE) {
((Activity) getContext()).getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} else {
((Activity) getContext()).getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
} catch (Exception e) {
FileLog.e(e);
}
}
public void showCamera() {
if (textureView != null) {
return;
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
switchCameraDrawable = (AnimatedVectorDrawable) ContextCompat.getDrawable(getContext(), R.drawable.avd_flip);
switchCameraButton.setImageDrawable(switchCameraDrawable);
} else {
switchCameraButton.setImageResource(R.drawable.vd_flip);
}
textureOverlayView.setAlpha(1.0f);
textureOverlayView.invalidate();
if (lastBitmap == null) {
try {
File file = new File(ApplicationLoader.getFilesDirFixed(), "icthumb.jpg");
lastBitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
} catch (Throwable ignore) {
}
}
if (lastBitmap != null) {
textureOverlayView.setImageBitmap(lastBitmap);
} else {
textureOverlayView.setImageResource(R.drawable.icplaceholder);
}
cameraReady = false;
isFrontface = true;
selectedCamera = null;
recordedTime = 0;
progress = 0;
cancelled = false;
file = null;
encryptedFile = null;
key = null;
iv = null;
needDrawFlickerStub = true;
if (!initCamera()) {
return;
}
MediaController.getInstance().pauseMessage(MediaController.getInstance().getPlayingMessageObject());
cameraFile = new File(FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE), SharedConfig.getLastLocalId() + ".mp4");
SharedConfig.saveConfig();
if (BuildVars.LOGS_ENABLED) {
FileLog.d("show round camera");
}
textureView = new TextureView(getContext());
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("camera surface available");
}
if (cameraThread == null && surface != null) {
if (cancelled) {
return;
}
if (BuildVars.LOGS_ENABLED) {
FileLog.d("start create thread");
}
cameraThread = new CameraGLThread(surface, width, height);
}
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, final int width, final int height) {
if (cameraThread != null) {
cameraThread.surfaceWidth = width;
cameraThread.surfaceHeight = height;
cameraThread.updateScale();
}
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
if (cameraThread != null) {
cameraThread.shutdown(0);
cameraThread = null;
}
if (cameraSession != null) {
CameraController.getInstance().close(cameraSession, null, null);
}
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
});
cameraContainer.addView(textureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
updateTextureViewSize = true;
setVisibility(VISIBLE);
startAnimation(true);
MediaController.getInstance().requestAudioFocus(true);
}
public InstantViewCameraContainer getCameraContainer() {
return cameraContainer;
}
public void startAnimation(boolean open) {
if (animatorSet != null) {
animatorSet.removeAllListeners();
animatorSet.cancel();
}
PipRoundVideoView pipRoundVideoView = PipRoundVideoView.getInstance();
if (pipRoundVideoView != null) {
pipRoundVideoView.showTemporary(!open);
}
if (open && !opened) {
cameraContainer.setTranslationX(0);
textureOverlayView.setTranslationX(0);
animationTranslationY = getMeasuredHeight() / 2f;
updateTranslationY();
}
opened = open;
if (parentView != null) {
parentView.invalidate();
}
blurBehindDrawable.show(open);
animatorSet = new AnimatorSet();
float toX = 0;
if (!open) {
toX = recordedTime > 300 ? AndroidUtilities.dp(24) - getMeasuredWidth() / 2f : 0;
}
ValueAnimator translationYAnimator = ValueAnimator.ofFloat(open ? 1f : 0f, open ? 0 : 1f);
translationYAnimator.addUpdateListener(animation -> {
animationTranslationY = (getMeasuredHeight() / 2f) * (float) animation.getAnimatedValue();
updateTranslationY();
});
animatorSet.playTogether(
ObjectAnimator.ofFloat(switchCameraButton, View.ALPHA, open ? 1.0f : 0.0f),
ObjectAnimator.ofFloat(muteImageView, View.ALPHA, 0.0f),
ObjectAnimator.ofInt(paint, AnimationProperties.PAINT_ALPHA, open ? 255 : 0),
ObjectAnimator.ofFloat(cameraContainer, View.ALPHA, open ? 1.0f : 0.0f),
ObjectAnimator.ofFloat(cameraContainer, View.SCALE_X, open ? 1.0f : 0.1f),
ObjectAnimator.ofFloat(cameraContainer, View.SCALE_Y, open ? 1.0f : 0.1f),
ObjectAnimator.ofFloat(cameraContainer, View.TRANSLATION_X, toX),
ObjectAnimator.ofFloat(textureOverlayView, View.ALPHA, open ? 1.0f : 0.0f),
ObjectAnimator.ofFloat(textureOverlayView, View.SCALE_X, open ? 1.0f : 0.1f),
ObjectAnimator.ofFloat(textureOverlayView, View.SCALE_Y, open ? 1.0f : 0.1f),
ObjectAnimator.ofFloat(textureOverlayView, View.TRANSLATION_X, toX),
translationYAnimator
);
if (!open) {
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (animation.equals(animatorSet)) {
hideCamera(true);
setVisibility(INVISIBLE);
}
}
});
} else {
setTranslationX(0);
}
animatorSet.setDuration(180);
animatorSet.setInterpolator(new DecelerateInterpolator());
animatorSet.start();
}
private void updateTranslationY() {
textureOverlayView.setTranslationY(animationTranslationY + panTranslationY);
cameraContainer.setTranslationY(animationTranslationY + panTranslationY);
}
public Rect getCameraRect() {
cameraContainer.getLocationOnScreen(position);
return new Rect(position[0], position[1], cameraContainer.getWidth(), cameraContainer.getHeight());
}
public void changeVideoPreviewState(int state, float progress) {
if (videoPlayer == null) {
return;
}
if (state == 0) {
startProgressTimer();
videoPlayer.play();
} else if (state == 1) {
stopProgressTimer();
videoPlayer.pause();
} else if (state == 2) {
videoPlayer.seekTo((long) (progress * videoPlayer.getDuration()));
}
}
public void send(int state, boolean notify, int scheduleDate) {
if (textureView == null) {
return;
}
stopProgressTimer();
if (videoPlayer != null) {
videoPlayer.releasePlayer(true);
videoPlayer = null;
}
if (state == 4) {
if (videoEditedInfo.needConvert()) {
file = null;
encryptedFile = null;
key = null;
iv = null;
double totalDuration = videoEditedInfo.estimatedDuration;
long startTime = videoEditedInfo.startTime >= 0 ? videoEditedInfo.startTime : 0;
long endTime = videoEditedInfo.endTime >= 0 ? videoEditedInfo.endTime : videoEditedInfo.estimatedDuration;
videoEditedInfo.estimatedDuration = endTime - startTime;
videoEditedInfo.estimatedSize = Math.max(1, (long) (size * (videoEditedInfo.estimatedDuration / totalDuration)));
videoEditedInfo.bitrate = 1000000;
if (videoEditedInfo.startTime > 0) {
videoEditedInfo.startTime *= 1000;
}
if (videoEditedInfo.endTime > 0) {
videoEditedInfo.endTime *= 1000;
}
FileLoader.getInstance(currentAccount).cancelFileUpload(cameraFile.getAbsolutePath(), false);
} else {
videoEditedInfo.estimatedSize = Math.max(1, size);
}
videoEditedInfo.file = file;
videoEditedInfo.encryptedFile = encryptedFile;
videoEditedInfo.key = key;
videoEditedInfo.iv = iv;
baseFragment.sendMedia(new MediaController.PhotoEntry(0, 0, 0, cameraFile.getAbsolutePath(), 0, true, 0, 0, 0), videoEditedInfo, notify, scheduleDate, false);
if (scheduleDate != 0) {
startAnimation(false);
}
MediaController.getInstance().requestAudioFocus(false);
} else {
cancelled = recordedTime < 800;
recording = false;
int reason;
if (cancelled) {
reason = 4;
} else {
reason = state == 3 ? 2 : 5;
}
if (cameraThread != null) {
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.recordStopped, recordingGuid, reason);
int send;
if (cancelled) {
send = 0;
} else if (state == 3) {
send = 2;
} else {
send = 1;
}
saveLastCameraBitmap();
cameraThread.shutdown(send);
cameraThread = null;
}
if (cancelled) {
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.audioRecordTooShort, recordingGuid, true, (int) recordedTime);
startAnimation(false);
MediaController.getInstance().requestAudioFocus(false);
}
}
}
private void saveLastCameraBitmap() {
Bitmap bitmap = textureView.getBitmap();
if (bitmap != null && bitmap.getPixel(0, 0) != 0) {
lastBitmap = Bitmap.createScaledBitmap(textureView.getBitmap(), 50, 50, true);
if (lastBitmap != null) {
Utilities.blurBitmap(lastBitmap, 7, 1, lastBitmap.getWidth(), lastBitmap.getHeight(), lastBitmap.getRowBytes());
try {
File file = new File(ApplicationLoader.getFilesDirFixed(), "icthumb.jpg");
FileOutputStream stream = new FileOutputStream(file);
lastBitmap.compress(Bitmap.CompressFormat.JPEG, 87, stream);
stream.close();
} catch (Throwable ignore) {
}
}
}
}
public void cancel(boolean byGesture) {
stopProgressTimer();
if (videoPlayer != null) {
videoPlayer.releasePlayer(true);
videoPlayer = null;
}
if (textureView == null) {
return;
}
cancelled = true;
recording = false;
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.recordStopped, recordingGuid, byGesture ? 0 : 6);
if (cameraThread != null) {
saveLastCameraBitmap();
cameraThread.shutdown(0);
cameraThread = null;
}
if (cameraFile != null) {
cameraFile.delete();
cameraFile = null;
}
MediaController.getInstance().requestAudioFocus(false);
startAnimation(false);
blurBehindDrawable.show(false);
invalidate();
}
public View getSwitchButtonView() {
return switchCameraButton;
}
public View getMuteImageView() {
return muteImageView;
}
public Paint getPaint() {
return paint;
}
public void hideCamera(boolean async) {
destroy(async, null);
cameraContainer.setTranslationX(0);
textureOverlayView.setTranslationX(0);
animationTranslationY = 0;
updateTranslationY();
MediaController.getInstance().playMessage(MediaController.getInstance().getPlayingMessageObject());
if (textureView != null) {
ViewGroup parent = (ViewGroup) textureView.getParent();
if (parent != null) {
parent.removeView(textureView);
}
}
textureView = null;
cameraContainer.setImageReceiver(null);
}
private void switchCamera() {
saveLastCameraBitmap();
if (lastBitmap != null) {
needDrawFlickerStub = false;
textureOverlayView.setImageBitmap(lastBitmap);
textureOverlayView.setAlpha(1f);
}
if (cameraSession != null) {
cameraSession.destroy();
CameraController.getInstance().close(cameraSession, null, null);
cameraSession = null;
}
isFrontface = !isFrontface;
initCamera();
cameraReady = false;
cameraThread.reinitForNewCamera();
}
private boolean initCamera() {
ArrayList<CameraInfo> cameraInfos = CameraController.getInstance().getCameras();
if (cameraInfos == null) {
return false;
}
CameraInfo notFrontface = null;
for (int a = 0; a < cameraInfos.size(); a++) {
CameraInfo cameraInfo = cameraInfos.get(a);
if (!cameraInfo.isFrontface()) {
notFrontface = cameraInfo;
}
if (isFrontface && cameraInfo.isFrontface() || !isFrontface && !cameraInfo.isFrontface()) {
selectedCamera = cameraInfo;
break;
} else {
notFrontface = cameraInfo;
}
}
if (selectedCamera == null) {
selectedCamera = notFrontface;
}
if (selectedCamera == null) {
return false;
}
ArrayList<Size> previewSizes = selectedCamera.getPreviewSizes();
ArrayList<Size> pictureSizes = selectedCamera.getPictureSizes();
previewSize = chooseOptimalSize(previewSizes);
pictureSize = chooseOptimalSize(pictureSizes);
if (previewSize.mWidth != pictureSize.mWidth) {
boolean found = false;
for (int a = previewSizes.size() - 1; a >= 0; a--) {
Size preview = previewSizes.get(a);
for (int b = pictureSizes.size() - 1; b >= 0; b--) {
Size picture = pictureSizes.get(b);
if (preview.mWidth >= pictureSize.mWidth && preview.mHeight >= pictureSize.mHeight && preview.mWidth == picture.mWidth && preview.mHeight == picture.mHeight) {
previewSize = preview;
pictureSize = picture;
found = true;
break;
}
}
if (found) {
break;
}
}
if (!found) {
for (int a = previewSizes.size() - 1; a >= 0; a--) {
Size preview = previewSizes.get(a);
for (int b = pictureSizes.size() - 1; b >= 0; b--) {
Size picture = pictureSizes.get(b);
if (preview.mWidth >= 360 && preview.mHeight >= 360 && preview.mWidth == picture.mWidth && preview.mHeight == picture.mHeight) {
previewSize = preview;
pictureSize = picture;
found = true;
break;
}
}
if (found) {
break;
}
}
}
}
if (BuildVars.LOGS_ENABLED) {
FileLog.d("preview w = " + previewSize.mWidth + " h = " + previewSize.mHeight);
}
return true;
}
private Size chooseOptimalSize(ArrayList<Size> previewSizes) {
ArrayList<Size> sortedSizes = new ArrayList<>();
for (int i = 0; i < previewSizes.size(); i++) {
if (Math.max(previewSizes.get(i).mHeight, previewSizes.get(i).mWidth) <= 1200 && Math.min(previewSizes.get(i).mHeight, previewSizes.get(i).mWidth) >= 320) {
sortedSizes.add(previewSizes.get(i));
}
}
if (sortedSizes.isEmpty() || SharedConfig.getDevicePerformanceClass() == SharedConfig.PERFORMANCE_CLASS_LOW || SharedConfig.getDevicePerformanceClass() == SharedConfig.PERFORMANCE_CLASS_AVERAGE) {
ArrayList<Size> sizes = sortedSizes;
if (!sortedSizes.isEmpty()) {
sizes = sortedSizes;
} else {
sizes = previewSizes;
}
if (Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")) {
return CameraController.chooseOptimalSize(sizes, 640, 480, aspectRatio);
} else {
return CameraController.chooseOptimalSize(sizes, 480, 270, aspectRatio);
}
}
Collections.sort(sortedSizes, (o1, o2) -> {
float a1 = Math.abs(1f - Math.min(o1.mHeight, o1.mWidth) / (float) Math.max(o1.mHeight, o1.mWidth));
float a2 = Math.abs(1f - Math.min(o2.mHeight, o2.mWidth) / (float) Math.max(o2.mHeight, o2.mWidth));
if (a1 < a2) {
return -1;
} else if (a1 > a2) {
return 1;
}
return 0;
});
return sortedSizes.get(0);
}
private void createCamera(final SurfaceTexture surfaceTexture) {
AndroidUtilities.runOnUIThread(() -> {
if (cameraThread == null) {
return;
}
if (BuildVars.LOGS_ENABLED) {
FileLog.d("create camera session");
}
surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
cameraSession = new CameraSession(selectedCamera, previewSize, pictureSize, ImageFormat.JPEG, true);
cameraThread.setCurrentSession(cameraSession);
CameraController.getInstance().openRound(cameraSession, surfaceTexture, () -> {
if (cameraSession != null) {
boolean updateScale = false;
try {
Camera.Size size = cameraSession.getCurrentPreviewSize();
if (size.width != previewSize.getWidth() || size.height != previewSize.getHeight()) {
previewSize = new Size(size.width, size.height);
FileLog.d("change preview size to w = " + previewSize.getWidth() + " h = " + previewSize.getHeight());
}
} catch (Exception e) {
FileLog.e(e);
}
try {
Camera.Size size = cameraSession.getCurrentPictureSize();
if (size.width != pictureSize.getWidth() || size.height != pictureSize.getHeight()) {
pictureSize = new Size(size.width, size.height);
FileLog.d("change picture size to w = " + pictureSize.getWidth() + " h = " + pictureSize.getHeight());
updateScale = true;
}
} catch (Exception e) {
FileLog.e(e);
}
if (BuildVars.LOGS_ENABLED) {
FileLog.d("camera initied");
}
cameraSession.setInitied();
if (updateScale) {
cameraThread.reinitForNewCamera();
}
}
}, () -> cameraThread.setCurrentSession(cameraSession));
});
}
private int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
int[] compileStatus = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
if (compileStatus[0] == 0) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e(GLES20.glGetShaderInfoLog(shader));
}
GLES20.glDeleteShader(shader);
shader = 0;
}
return shader;
}
private Timer progressTimer;
private void startProgressTimer() {
if (progressTimer != null) {
try {
progressTimer.cancel();
progressTimer = null;
} catch (Exception e) {
FileLog.e(e);
}
}
progressTimer = new Timer();
progressTimer.schedule(new TimerTask() {
@Override
public void run() {
AndroidUtilities.runOnUIThread(() -> {
try {
if (videoPlayer != null && videoEditedInfo != null && videoEditedInfo.endTime > 0 && videoPlayer.getCurrentPosition() >= videoEditedInfo.endTime) {
videoPlayer.seekTo(videoEditedInfo.startTime > 0 ? videoEditedInfo.startTime : 0);
}
} catch (Exception e) {
FileLog.e(e);
}
});
}
}, 0, 17);
}
private void stopProgressTimer() {
if (progressTimer != null) {
try {
progressTimer.cancel();
progressTimer = null;
} catch (Exception e) {
FileLog.e(e);
}
}
}
public boolean blurFullyDrawing() {
return blurBehindDrawable != null && blurBehindDrawable.isFullyDrawing() && opened;
}
public void invalidateBlur() {
if (blurBehindDrawable != null) {
blurBehindDrawable.invalidate();
}
}
public void cancelBlur() {
blurBehindDrawable.show(false);
invalidate();
}
public void onPanTranslationUpdate(float y) {
panTranslationY = y / 2f;
updateTranslationY();
blurBehindDrawable.onPanTranslationUpdate(y);
}
public TextureView getTextureView() {
return textureView;
}
public void setIsMessageTransition(boolean isMessageTransition) {
this.isMessageTransition = isMessageTransition;
}
public class CameraGLThread extends DispatchQueue {
private final static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
private final static int EGL_OPENGL_ES2_BIT = 4;
private SurfaceTexture surfaceTexture;
private EGL10 egl10;
private EGLDisplay eglDisplay;
private EGLContext eglContext;
private EGLSurface eglSurface;
private boolean initied;
private CameraSession currentSession;
private SurfaceTexture cameraSurface;
private final int DO_RENDER_MESSAGE = 0;
private final int DO_SHUTDOWN_MESSAGE = 1;
private final int DO_REINIT_MESSAGE = 2;
private final int DO_SETSESSION_MESSAGE = 3;
private int drawProgram;
private int vertexMatrixHandle;
private int textureMatrixHandle;
private int positionHandle;
private int textureHandle;
private boolean recording;
private Integer cameraId = 0;
private VideoRecorder videoEncoder;
private int surfaceWidth;
private int surfaceHeight;
public CameraGLThread(SurfaceTexture surface, int surfaceWidth, int surfaceHeight) {
super("CameraGLThread");
surfaceTexture = surface;
this.surfaceWidth = surfaceWidth;
this.surfaceHeight = surfaceHeight;
updateScale();
}
private void updateScale() {
int width = previewSize.getWidth();
int height = previewSize.getHeight();
float scale = surfaceWidth / (float) Math.min(width, height);
width *= scale;
height *= scale;
if (width == height) {
scaleX = 1f;
scaleY = 1f;
} else if (width > height) {
scaleX = 1.0f;
scaleY = width / (float) surfaceHeight;
} else {
scaleX = height / (float) surfaceWidth;
scaleY = 1.0f;
}
FileLog.d("camera scaleX = " + scaleX + " scaleY = " + scaleY);
}
private boolean initGL() {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("start init gl");
}
egl10 = (EGL10) EGLContext.getEGL();
eglDisplay = egl10.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (eglDisplay == EGL10.EGL_NO_DISPLAY) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("eglGetDisplay failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
}
finish();
return false;
}
int[] version = new int[2];
if (!egl10.eglInitialize(eglDisplay, version)) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("eglInitialize failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
}
finish();
return false;
}
int[] configsCount = new int[1];
EGLConfig[] configs = new EGLConfig[1];
int[] configSpec = new int[]{
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL10.EGL_RED_SIZE, 8,
EGL10.EGL_GREEN_SIZE, 8,
EGL10.EGL_BLUE_SIZE, 8,
EGL10.EGL_ALPHA_SIZE, 0,
EGL10.EGL_DEPTH_SIZE, 0,
EGL10.EGL_STENCIL_SIZE, 0,
EGL10.EGL_NONE
};
EGLConfig eglConfig;
if (!egl10.eglChooseConfig(eglDisplay, configSpec, configs, 1, configsCount)) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("eglChooseConfig failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
}
finish();
return false;
} else if (configsCount[0] > 0) {
eglConfig = configs[0];
} else {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("eglConfig not initialized");
}
finish();
return false;
}
int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE};
eglContext = egl10.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
if (eglContext == null) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("eglCreateContext failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
}
finish();
return false;
}
if (surfaceTexture instanceof SurfaceTexture) {
eglSurface = egl10.eglCreateWindowSurface(eglDisplay, eglConfig, surfaceTexture, null);
} else {
finish();
return false;
}
if (eglSurface == null || eglSurface == EGL10.EGL_NO_SURFACE) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("createWindowSurface failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
}
finish();
return false;
}
if (!egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
}
finish();
return false;
}
GL gl = eglContext.getGL();
float tX = 1.0f / scaleX / 2.0f;
float tY = 1.0f / scaleY / 2.0f;
float[] verticesData = {
-1.0f, -1.0f, 0,
1.0f, -1.0f, 0,
-1.0f, 1.0f, 0,
1.0f, 1.0f, 0
};
float[] texData = {
0.5f - tX, 0.5f - tY,
0.5f + tX, 0.5f - tY,
0.5f - tX, 0.5f + tY,
0.5f + tX, 0.5f + tY
};
videoEncoder = new VideoRecorder();
vertexBuffer = ByteBuffer.allocateDirect(verticesData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
vertexBuffer.put(verticesData).position(0);
textureBuffer = ByteBuffer.allocateDirect(texData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
textureBuffer.put(texData).position(0);
android.opengl.Matrix.setIdentityM(mSTMatrix, 0);
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SCREEN_SHADER);
if (vertexShader != 0 && fragmentShader != 0) {
drawProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(drawProgram, vertexShader);
GLES20.glAttachShader(drawProgram, fragmentShader);
GLES20.glLinkProgram(drawProgram);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(drawProgram, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] == 0) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("failed link shader");
}
GLES20.glDeleteProgram(drawProgram);
drawProgram = 0;
} else {
positionHandle = GLES20.glGetAttribLocation(drawProgram, "aPosition");
textureHandle = GLES20.glGetAttribLocation(drawProgram, "aTextureCoord");
vertexMatrixHandle = GLES20.glGetUniformLocation(drawProgram, "uMVPMatrix");
textureMatrixHandle = GLES20.glGetUniformLocation(drawProgram, "uSTMatrix");
}
} else {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("failed creating shader");
}
finish();
return false;
}
GLES20.glGenTextures(1, cameraTexture, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, cameraTexture[0]);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
android.opengl.Matrix.setIdentityM(mMVPMatrix, 0);
cameraSurface = new SurfaceTexture(cameraTexture[0]);
cameraSurface.setOnFrameAvailableListener(surfaceTexture -> requestRender());
createCamera(cameraSurface);
if (BuildVars.LOGS_ENABLED) {
FileLog.e("gl initied");
}
return true;
}
public void reinitForNewCamera() {
Handler handler = getHandler();
if (handler != null) {
sendMessage(handler.obtainMessage(DO_REINIT_MESSAGE), 0);
}
updateScale();
}
public void finish() {
if (eglSurface != null) {
egl10.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
egl10.eglDestroySurface(eglDisplay, eglSurface);
eglSurface = null;
}
if (eglContext != null) {
egl10.eglDestroyContext(eglDisplay, eglContext);
eglContext = null;
}
if (eglDisplay != null) {
egl10.eglTerminate(eglDisplay);
eglDisplay = null;
}
}
public void setCurrentSession(CameraSession session) {
Handler handler = getHandler();
if (handler != null) {
sendMessage(handler.obtainMessage(DO_SETSESSION_MESSAGE, session), 0);
}
}
private void onDraw(Integer cameraId) {
if (!initied) {
return;
}
if (!eglContext.equals(egl10.eglGetCurrentContext()) || !eglSurface.equals(egl10.eglGetCurrentSurface(EGL10.EGL_DRAW))) {
if (!egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
}
return;
}
}
cameraSurface.updateTexImage();
if (!recording) {
videoEncoder.startRecording(cameraFile, EGL14.eglGetCurrentContext());
recording = true;
int orientation = currentSession.getCurrentOrientation();
if (orientation == 90 || orientation == 270) {
float temp = scaleX;
scaleX = scaleY;
scaleY = temp;
}
}
videoEncoder.frameAvailable(cameraSurface, cameraId, System.nanoTime());
cameraSurface.getTransformMatrix(mSTMatrix);
GLES20.glUseProgram(drawProgram);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, cameraTexture[0]);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer);
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 8, textureBuffer);
GLES20.glEnableVertexAttribArray(textureHandle);
GLES20.glUniformMatrix4fv(textureMatrixHandle, 1, false, mSTMatrix, 0);
GLES20.glUniformMatrix4fv(vertexMatrixHandle, 1, false, mMVPMatrix, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glDisableVertexAttribArray(positionHandle);
GLES20.glDisableVertexAttribArray(textureHandle);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
GLES20.glUseProgram(0);
egl10.eglSwapBuffers(eglDisplay, eglSurface);
}
@Override
public void run() {
initied = initGL();
super.run();
}
@Override
public void handleMessage(Message inputMessage) {
int what = inputMessage.what;
switch (what) {
case DO_RENDER_MESSAGE:
onDraw((Integer) inputMessage.obj);
break;
case DO_SHUTDOWN_MESSAGE:
finish();
if (recording) {
videoEncoder.stopRecording(inputMessage.arg1);
}
Looper looper = Looper.myLooper();
if (looper != null) {
looper.quit();
}
break;
case DO_REINIT_MESSAGE: {
if (!egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
}
return;
}
if (cameraSurface != null) {
cameraSurface.getTransformMatrix(moldSTMatrix);
cameraSurface.setOnFrameAvailableListener(null);
cameraSurface.release();
oldCameraTexture[0] = cameraTexture[0];
cameraTextureAlpha = 0.0f;
cameraTexture[0] = 0;
oldTextureTextureBuffer = textureBuffer.duplicate();
oldTexturePreviewSize = previewSize;
}
cameraId++;
cameraReady = false;
GLES20.glGenTextures(1, cameraTexture, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, cameraTexture[0]);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
cameraSurface = new SurfaceTexture(cameraTexture[0]);
cameraSurface.setOnFrameAvailableListener(surfaceTexture -> requestRender());
createCamera(cameraSurface);
cameraThread.updateScale();
float tX = 1.0f / scaleX / 2.0f;
float tY = 1.0f / scaleY / 2.0f;
float[] texData = {
0.5f - tX, 0.5f - tY,
0.5f + tX, 0.5f - tY,
0.5f - tX, 0.5f + tY,
0.5f + tX, 0.5f + tY
};
textureBuffer = ByteBuffer.allocateDirect(texData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
textureBuffer.put(texData).position(0);
break;
}
case DO_SETSESSION_MESSAGE: {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("set gl rednderer session");
}
CameraSession newSession = (CameraSession) inputMessage.obj;
if (currentSession == newSession) {
int rotationAngle = currentSession.getWorldAngle();
android.opengl.Matrix.setIdentityM(mMVPMatrix, 0);
if (rotationAngle != 0) {
android.opengl.Matrix.rotateM(mMVPMatrix, 0, rotationAngle, 0, 0, 1);
}
} else {
currentSession = newSession;
}
break;
}
}
}
public void shutdown(int send) {
Handler handler = getHandler();
if (handler != null) {
sendMessage(handler.obtainMessage(DO_SHUTDOWN_MESSAGE, send, 0), 0);
}
}
public void requestRender() {
Handler handler = getHandler();
if (handler != null) {
sendMessage(handler.obtainMessage(DO_RENDER_MESSAGE, cameraId), 0);
}
}
}
private static final int MSG_START_RECORDING = 0;
private static final int MSG_STOP_RECORDING = 1;
private static final int MSG_VIDEOFRAME_AVAILABLE = 2;
private static final int MSG_AUDIOFRAME_AVAILABLE = 3;
private static class EncoderHandler extends Handler {
private WeakReference<VideoRecorder> mWeakEncoder;
public EncoderHandler(VideoRecorder encoder) {
mWeakEncoder = new WeakReference<>(encoder);
}
@Override
public void handleMessage(Message inputMessage) {
int what = inputMessage.what;
Object obj = inputMessage.obj;
VideoRecorder encoder = mWeakEncoder.get();
if (encoder == null) {
return;
}
switch (what) {
case MSG_START_RECORDING: {
try {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("start encoder");
}
encoder.prepareEncoder();
} catch (Exception e) {
FileLog.e(e);
encoder.handleStopRecording(0);
Looper.myLooper().quit();
}
break;
}
case MSG_STOP_RECORDING: {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("stop encoder");
}
encoder.handleStopRecording(inputMessage.arg1);
break;
}
case MSG_VIDEOFRAME_AVAILABLE: {
long timestamp = (((long) inputMessage.arg1) << 32) | (((long) inputMessage.arg2) & 0xffffffffL);
Integer cameraId = (Integer) inputMessage.obj;
encoder.handleVideoFrameAvailable(timestamp, cameraId);
break;
}
case MSG_AUDIOFRAME_AVAILABLE: {
encoder.handleAudioFrameAvailable((AudioBufferInfo) inputMessage.obj);
break;
}
}
}
public void exit() {
Looper.myLooper().quit();
}
}
public static class AudioBufferInfo {
public final static int MAX_SAMPLES = 10;
public ByteBuffer[] buffer = new ByteBuffer[MAX_SAMPLES];
public long[] offset = new long[MAX_SAMPLES];
public int[] read = new int[MAX_SAMPLES];
public int results;
public int lastWroteBuffer;
public boolean last;
public AudioBufferInfo() {
for (int i = 0; i < MAX_SAMPLES; i++) {
buffer[i] = ByteBuffer.allocateDirect(2048);
buffer[i].order(ByteOrder.nativeOrder());
}
}
}
private class VideoRecorder implements Runnable {
private static final String VIDEO_MIME_TYPE = "video/avc";
private static final String AUDIO_MIME_TYPE = "audio/mp4a-latm";
private static final int FRAME_RATE = 30;
private static final int IFRAME_INTERVAL = 1;
private File videoFile;
private int videoWidth;
private int videoHeight;
private int videoBitrate;
private boolean videoConvertFirstWrite = true;
private boolean blendEnabled;
private Surface surface;
private android.opengl.EGLDisplay eglDisplay = EGL14.EGL_NO_DISPLAY;
private android.opengl.EGLContext eglContext = EGL14.EGL_NO_CONTEXT;
private android.opengl.EGLContext sharedEglContext;
private android.opengl.EGLConfig eglConfig;
private android.opengl.EGLSurface eglSurface = EGL14.EGL_NO_SURFACE;
private MediaCodec videoEncoder;
private MediaCodec audioEncoder;
private int prependHeaderSize;
private boolean firstEncode;
private MediaCodec.BufferInfo videoBufferInfo;
private MediaCodec.BufferInfo audioBufferInfo;
private MP4Builder mediaMuxer;
private ArrayList<AudioBufferInfo> buffersToWrite = new ArrayList<>();
private int videoTrackIndex = -5;
private int audioTrackIndex = -5;
private long lastCommitedFrameTime;
private long audioStartTime = -1;
private long currentTimestamp = 0;
private long lastTimestamp = -1;
private volatile EncoderHandler handler;
private final Object sync = new Object();
private boolean ready;
private volatile boolean running;
private volatile int sendWhenDone;
private long skippedTime;
private boolean skippedFirst;
private long desyncTime;
private long videoFirst = -1;
private long videoLast;
private long audioFirst = -1;
private boolean audioStopedByTime;
private int drawProgram;
private int vertexMatrixHandle;
private int textureMatrixHandle;
private int positionHandle;
private int textureHandle;
private int resolutionHandle;
private int previewSizeHandle;
private int alphaHandle;
private int zeroTimeStamps;
private Integer lastCameraId = 0;
private AudioRecord audioRecorder;
private ArrayBlockingQueue<AudioBufferInfo> buffers = new ArrayBlockingQueue<>(10);
private ArrayList<Bitmap> keyframeThumbs = new ArrayList<>();
private DispatchQueue generateKeyframeThumbsQueue;
private int frameCount;
private Runnable recorderRunnable = new Runnable() {
@Override
public void run() {
long audioPresentationTimeUs = -1;
int readResult;
boolean done = false;
while (!done) {
if (!running && audioRecorder.getRecordingState() != AudioRecord.RECORDSTATE_STOPPED) {
try {
audioRecorder.stop();
} catch (Exception e) {
done = true;
}
if (sendWhenDone == 0) {
break;
}
}
AudioBufferInfo buffer;
if (buffers.isEmpty()) {
buffer = new AudioBufferInfo();
} else {
buffer = buffers.poll();
}
buffer.lastWroteBuffer = 0;
buffer.results = AudioBufferInfo.MAX_SAMPLES;
for (int a = 0; a < AudioBufferInfo.MAX_SAMPLES; a++) {
if (audioPresentationTimeUs == -1) {
audioPresentationTimeUs = System.nanoTime() / 1000;
}
ByteBuffer byteBuffer = buffer.buffer[a];
byteBuffer.rewind();
readResult = audioRecorder.read(byteBuffer, 2048);
if (readResult > 0 && a % 2 == 0) {
byteBuffer.limit(readResult);
double s = 0;
for (int i = 0; i < readResult / 2; i++) {
short p = byteBuffer.getShort();
s += p * p;
}
double amplitude = Math.sqrt(s / readResult / 2);
AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.recordProgressChanged, recordingGuid, amplitude));
byteBuffer.position(0);
}
if (readResult <= 0) {
buffer.results = a;
if (!running) {
buffer.last = true;
}
break;
}
buffer.offset[a] = audioPresentationTimeUs;
buffer.read[a] = readResult;
int bufferDurationUs = 1000000 * readResult / audioSampleRate / 2;
audioPresentationTimeUs += bufferDurationUs;
}
if (buffer.results >= 0 || buffer.last) {
if (!running && buffer.results < AudioBufferInfo.MAX_SAMPLES) {
done = true;
}
handler.sendMessage(handler.obtainMessage(MSG_AUDIOFRAME_AVAILABLE, buffer));
} else {
if (!running) {
done = true;
} else {
try {
buffers.put(buffer);
} catch (Exception ignore) {
}
}
}
}
try {
audioRecorder.release();
} catch (Exception e) {
FileLog.e(e);
}
handler.sendMessage(handler.obtainMessage(MSG_STOP_RECORDING, sendWhenDone, 0));
}
};
public void startRecording(File outputFile, android.opengl.EGLContext sharedContext) {
int resolution = MessagesController.getInstance(currentAccount).roundVideoSize;
int bitrate = MessagesController.getInstance(currentAccount).roundVideoBitrate * 1024;
videoFile = outputFile;
videoWidth = resolution;
videoHeight = resolution;
videoBitrate = bitrate;
sharedEglContext = sharedContext;
synchronized (sync) {
if (running) {
return;
}
running = true;
Thread thread = new Thread(this, "TextureMovieEncoder");
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
while (!ready) {
try {
sync.wait();
} catch (InterruptedException ie) {
// ignore
}
}
}
keyframeThumbs.clear();
frameCount = 0;
if (generateKeyframeThumbsQueue != null) {
generateKeyframeThumbsQueue.cleanupQueue();
generateKeyframeThumbsQueue.recycle();
}
generateKeyframeThumbsQueue = new DispatchQueue("keyframes_thumb_queque");
handler.sendMessage(handler.obtainMessage(MSG_START_RECORDING));
}
public void stopRecording(int send) {
handler.sendMessage(handler.obtainMessage(MSG_STOP_RECORDING, send, 0));
}
public void frameAvailable(SurfaceTexture st, Integer cameraId, long timestampInternal) {
synchronized (sync) {
if (!ready) {
return;
}
}
long timestamp = st.getTimestamp();
if (timestamp == 0) {
zeroTimeStamps++;
if (zeroTimeStamps > 1) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("fix timestamp enabled");
}
timestamp = timestampInternal;
} else {
return;
}
} else {
zeroTimeStamps = 0;
}
handler.sendMessage(handler.obtainMessage(MSG_VIDEOFRAME_AVAILABLE, (int) (timestamp >> 32), (int) timestamp, cameraId));
}
@Override
public void run() {
Looper.prepare();
synchronized (sync) {
handler = new EncoderHandler(this);
ready = true;
sync.notify();
}
Looper.loop();
synchronized (sync) {
ready = false;
}
}
private void handleAudioFrameAvailable(AudioBufferInfo input) {
if (audioStopedByTime) {
return;
}
buffersToWrite.add(input);
if (audioFirst == -1) {
if (videoFirst == -1) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("video record not yet started");
}
return;
}
while (true) {
boolean ok = false;
for (int a = 0; a < input.results; a++) {
if (a == 0 && Math.abs(videoFirst - input.offset[a]) > 10000000L) {
desyncTime = videoFirst - input.offset[a];
audioFirst = input.offset[a];
ok = true;
if (BuildVars.LOGS_ENABLED) {
FileLog.d("detected desync between audio and video " + desyncTime);
}
break;
}
if (input.offset[a] >= videoFirst) {
input.lastWroteBuffer = a;
audioFirst = input.offset[a];
ok = true;
if (BuildVars.LOGS_ENABLED) {
FileLog.d("found first audio frame at " + a + " timestamp = " + input.offset[a]);
}
break;
} else {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("ignore first audio frame at " + a + " timestamp = " + input.offset[a]);
}
}
}
if (!ok) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("first audio frame not found, removing buffers " + input.results);
}
buffersToWrite.remove(input);
} else {
break;
}
if (!buffersToWrite.isEmpty()) {
input = buffersToWrite.get(0);
} else {
return;
}
}
}
if (audioStartTime == -1) {
audioStartTime = input.offset[input.lastWroteBuffer];
}
if (buffersToWrite.size() > 1) {
input = buffersToWrite.get(0);
}
try {
drainEncoder(false);
} catch (Exception e) {
FileLog.e(e);
}
try {
boolean isLast = false;
while (input != null) {
int inputBufferIndex = audioEncoder.dequeueInputBuffer(0);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer;
if (Build.VERSION.SDK_INT >= 21) {
inputBuffer = audioEncoder.getInputBuffer(inputBufferIndex);
} else {
ByteBuffer[] inputBuffers = audioEncoder.getInputBuffers();
inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
}
long startWriteTime = input.offset[input.lastWroteBuffer];
for (int a = input.lastWroteBuffer; a <= input.results; a++) {
if (a < input.results) {
if (!running && input.offset[a] >= videoLast - desyncTime) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("stop audio encoding because of stoped video recording at " + input.offset[a] + " last video " + videoLast);
}
audioStopedByTime = true;
isLast = true;
input = null;
buffersToWrite.clear();
break;
}
if (inputBuffer.remaining() < input.read[a]) {
input.lastWroteBuffer = a;
input = null;
break;
}
inputBuffer.put(input.buffer[a]);
}
if (a >= input.results - 1) {
buffersToWrite.remove(input);
if (running) {
buffers.put(input);
}
if (!buffersToWrite.isEmpty()) {
input = buffersToWrite.get(0);
} else {
isLast = input.last;
input = null;
break;
}
}
}
audioEncoder.queueInputBuffer(inputBufferIndex, 0, inputBuffer.position(), startWriteTime == 0 ? 0 : startWriteTime - audioStartTime, isLast ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
}
}
} catch (Throwable e) {
FileLog.e(e);
}
}
private void handleVideoFrameAvailable(long timestampNanos, Integer cameraId) {
try {
drainEncoder(false);
} catch (Exception e) {
FileLog.e(e);
}
long dt, alphaDt;
if (!lastCameraId.equals(cameraId)) {
lastTimestamp = -1;
lastCameraId = cameraId;
}
if (lastTimestamp == -1) {
lastTimestamp = timestampNanos;
if (currentTimestamp != 0) {
dt = (System.currentTimeMillis() - lastCommitedFrameTime) * 1000000;
alphaDt = 0;
} else {
alphaDt = dt = 0;
}
} else {
alphaDt = dt = (timestampNanos - lastTimestamp);
lastTimestamp = timestampNanos;
}
lastCommitedFrameTime = System.currentTimeMillis();
if (!skippedFirst) {
skippedTime += dt;
if (skippedTime < 200000000) {
return;
}
skippedFirst = true;
}
currentTimestamp += dt;
if (videoFirst == -1) {
videoFirst = timestampNanos / 1000;
if (BuildVars.LOGS_ENABLED) {
FileLog.d("first video frame was at " + videoFirst);
}
}
videoLast = timestampNanos;
GLES20.glUseProgram(drawProgram);
GLES20.glUniformMatrix4fv(vertexMatrixHandle, 1, false, mMVPMatrix, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glEnableVertexAttribArray(textureHandle);
GLES20.glUniform2f(resolutionHandle, videoWidth, videoHeight);
if (oldCameraTexture[0] != 0 && oldTextureTextureBuffer != null) {
if (!blendEnabled) {
GLES20.glEnable(GLES20.GL_BLEND);
blendEnabled = true;
}
if (oldTexturePreviewSize != null) {
GLES20.glUniform2f(previewSizeHandle, oldTexturePreviewSize.getWidth(), oldTexturePreviewSize.getHeight());
}
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 8, oldTextureTextureBuffer);
GLES20.glUniformMatrix4fv(textureMatrixHandle, 1, false, moldSTMatrix, 0);
GLES20.glUniform1f(alphaHandle, 1.0f);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, oldCameraTexture[0]);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
}
if (previewSize != null) {
GLES20.glUniform2f(previewSizeHandle, previewSize.getWidth(), previewSize.getHeight());
}
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer);
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 8, textureBuffer);
GLES20.glUniformMatrix4fv(textureMatrixHandle, 1, false, mSTMatrix, 0);
GLES20.glUniform1f(alphaHandle, cameraTextureAlpha);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, cameraTexture[0]);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glDisableVertexAttribArray(positionHandle);
GLES20.glDisableVertexAttribArray(textureHandle);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
GLES20.glUseProgram(0);
EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, currentTimestamp);
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
createKeyframeThumb();
frameCount++;
if (oldCameraTexture[0] != 0 && cameraTextureAlpha < 1.0f) {
cameraTextureAlpha += alphaDt / 200000000.0f;
if (cameraTextureAlpha > 1) {
GLES20.glDisable(GLES20.GL_BLEND);
blendEnabled = false;
cameraTextureAlpha = 1;
GLES20.glDeleteTextures(1, oldCameraTexture, 0);
oldCameraTexture[0] = 0;
if (!cameraReady) {
cameraReady = true;
AndroidUtilities.runOnUIThread(() -> textureOverlayView.animate().setDuration(120).alpha(0.0f).setInterpolator(new DecelerateInterpolator()).start());
}
}
} else if (!cameraReady) {
cameraReady = true;
AndroidUtilities.runOnUIThread(() -> textureOverlayView.animate().setDuration(120).alpha(0.0f).setInterpolator(new DecelerateInterpolator()).start());
}
}
private void createKeyframeThumb() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && SharedConfig.getDevicePerformanceClass() == SharedConfig.PERFORMANCE_CLASS_HIGH && frameCount % 33 == 0) {
GenerateKeyframeThumbTask task = new GenerateKeyframeThumbTask();
generateKeyframeThumbsQueue.postRunnable(task);
}
}
private class GenerateKeyframeThumbTask implements Runnable {
@Override
public void run() {
final TextureView textureView = InstantCameraView.this.textureView;
if (textureView != null) {
try {
final Bitmap bitmap = textureView.getBitmap(AndroidUtilities.dp(56), AndroidUtilities.dp(56));
AndroidUtilities.runOnUIThread(() -> {
if ((bitmap == null || bitmap.getPixel(0, 0) == 0) && keyframeThumbs.size() > 1) {
keyframeThumbs.add(keyframeThumbs.get(keyframeThumbs.size() - 1));
} else {
keyframeThumbs.add(bitmap);
}
});
} catch (Exception e) {
FileLog.e(e);
}
}
}
}
private void handleStopRecording(final int send) {
if (running) {
sendWhenDone = send;
running = false;
return;
}
try {
drainEncoder(true);
} catch (Exception e) {
FileLog.e(e);
}
if (videoEncoder != null) {
try {
videoEncoder.stop();
videoEncoder.release();
videoEncoder = null;
} catch (Exception e) {
FileLog.e(e);
}
}
if (audioEncoder != null) {
try {
audioEncoder.stop();
audioEncoder.release();
audioEncoder = null;
setBluetoothScoOn(false);
} catch (Exception e) {
FileLog.e(e);
}
}
if (mediaMuxer != null) {
try {
mediaMuxer.finishMovie();
} catch (Exception e) {
FileLog.e(e);
}
}
if (generateKeyframeThumbsQueue != null) {
generateKeyframeThumbsQueue.cleanupQueue();
generateKeyframeThumbsQueue.recycle();
generateKeyframeThumbsQueue = null;
}
if (send != 0) {
AndroidUtilities.runOnUIThread(() -> {
videoEditedInfo = new VideoEditedInfo();
videoEditedInfo.roundVideo = true;
videoEditedInfo.startTime = -1;
videoEditedInfo.endTime = -1;
videoEditedInfo.file = file;
videoEditedInfo.encryptedFile = encryptedFile;
videoEditedInfo.key = key;
videoEditedInfo.iv = iv;
videoEditedInfo.estimatedSize = Math.max(1, size);
videoEditedInfo.framerate = 25;
videoEditedInfo.resultWidth = videoEditedInfo.originalWidth = 360;
videoEditedInfo.resultHeight = videoEditedInfo.originalHeight = 360;
videoEditedInfo.originalPath = videoFile.getAbsolutePath();
if (send == 1) {
if (baseFragment.isInScheduleMode()) {
AlertsCreator.createScheduleDatePickerDialog(baseFragment.getParentActivity(), baseFragment.getDialogId(), (notify, scheduleDate) -> {
baseFragment.sendMedia(new MediaController.PhotoEntry(0, 0, 0, videoFile.getAbsolutePath(), 0, true, 0, 0, 0), videoEditedInfo, notify, scheduleDate, false);
startAnimation(false);
}, () -> {
startAnimation(false);
}, resourcesProvider);
} else {
baseFragment.sendMedia(new MediaController.PhotoEntry(0, 0, 0, videoFile.getAbsolutePath(), 0, true, 0, 0, 0), videoEditedInfo, true, 0, false);
}
} else {
videoPlayer = new VideoPlayer();
videoPlayer.setDelegate(new VideoPlayer.VideoPlayerDelegate() {
@Override
public void onStateChanged(boolean playWhenReady, int playbackState) {
if (videoPlayer == null) {
return;
}
if (videoPlayer.isPlaying() && playbackState == ExoPlayer.STATE_ENDED) {
videoPlayer.seekTo(videoEditedInfo.startTime > 0 ? videoEditedInfo.startTime : 0);
}
}
@Override
public void onError(VideoPlayer player, Exception e) {
FileLog.e(e);
}
@Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
}
@Override
public void onRenderedFirstFrame() {
}
@Override
public boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
});
videoPlayer.setTextureView(textureView);
videoPlayer.preparePlayer(Uri.fromFile(videoFile), "other");
videoPlayer.play();
videoPlayer.setMute(true);
startProgressTimer();
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(
ObjectAnimator.ofFloat(switchCameraButton, View.ALPHA, 0.0f),
ObjectAnimator.ofInt(paint, AnimationProperties.PAINT_ALPHA, 0),
ObjectAnimator.ofFloat(muteImageView, View.ALPHA, 1.0f));
animatorSet.setDuration(180);
animatorSet.setInterpolator(new DecelerateInterpolator());
animatorSet.start();
videoEditedInfo.estimatedDuration = recordedTime;
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.audioDidSent, recordingGuid, videoEditedInfo, videoFile.getAbsolutePath(), keyframeThumbs);
}
didWriteData(videoFile, 0, true);
MediaController.getInstance().requestAudioFocus(false);
});
} else {
FileLoader.getInstance(currentAccount).cancelFileUpload(videoFile.getAbsolutePath(), false);
videoFile.delete();
}
EGL14.eglDestroySurface(eglDisplay, eglSurface);
eglSurface = EGL14.EGL_NO_SURFACE;
if (surface != null) {
surface.release();
surface = null;
}
if (eglDisplay != EGL14.EGL_NO_DISPLAY) {
EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
EGL14.eglDestroyContext(eglDisplay, eglContext);
EGL14.eglReleaseThread();
EGL14.eglTerminate(eglDisplay);
}
eglDisplay = EGL14.EGL_NO_DISPLAY;
eglContext = EGL14.EGL_NO_CONTEXT;
eglConfig = null;
handler.exit();
}
private void setBluetoothScoOn(boolean scoOn) {
AudioManager am = (AudioManager) ApplicationLoader.applicationContext.getSystemService(Context.AUDIO_SERVICE);
if (am.isBluetoothScoAvailableOffCall() && SharedConfig.recordViaSco || !scoOn) {
BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
try {
if (btAdapter != null && btAdapter.getProfileConnectionState(BluetoothProfile.HEADSET) == BluetoothProfile.STATE_CONNECTED || !scoOn) {
if (scoOn && !am.isBluetoothScoOn()) {
am.startBluetoothSco();
} else if (!scoOn && am.isBluetoothScoOn()) {
am.stopBluetoothSco();
}
}
} catch (SecurityException ignored) {
} catch (Throwable e) {
FileLog.e(e);
}
}
}
private void prepareEncoder() {
setBluetoothScoOn(true);
try {
int recordBufferSize = AudioRecord.getMinBufferSize(audioSampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
if (recordBufferSize <= 0) {
recordBufferSize = 3584;
}
int bufferSize = 2048 * 24;
if (bufferSize < recordBufferSize) {
bufferSize = ((recordBufferSize / 2048) + 1) * 2048 * 2;
}
for (int a = 0; a < 3; a++) {
buffers.add(new AudioBufferInfo());
}
audioRecorder = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, audioSampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
audioRecorder.startRecording();
if (BuildVars.LOGS_ENABLED) {
FileLog.d("initied audio record with channels " + audioRecorder.getChannelCount() + " sample rate = " + audioRecorder.getSampleRate() + " bufferSize = " + bufferSize);
}
Thread thread = new Thread(recorderRunnable);
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
audioBufferInfo = new MediaCodec.BufferInfo();
videoBufferInfo = new MediaCodec.BufferInfo();
MediaFormat audioFormat = new MediaFormat();
audioFormat.setString(MediaFormat.KEY_MIME, AUDIO_MIME_TYPE);
audioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, audioSampleRate);
audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, MessagesController.getInstance(currentAccount).roundAudioBitrate * 1024);
audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 2048 * AudioBufferInfo.MAX_SAMPLES);
audioEncoder = MediaCodec.createEncoderByType(AUDIO_MIME_TYPE);
audioEncoder.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
audioEncoder.start();
videoEncoder = MediaCodec.createEncoderByType(VIDEO_MIME_TYPE);
firstEncode = true;
MediaFormat format = MediaFormat.createVideoFormat(VIDEO_MIME_TYPE, videoWidth, videoHeight);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, videoBitrate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
/*if (Build.VERSION.SDK_INT >= 21) {
format.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh);
if (Build.VERSION.SDK_INT >= 23) {
format.setInteger(MediaFormat.KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.AVCLevel5);
}
}*/
videoEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
surface = videoEncoder.createInputSurface();
videoEncoder.start();
Mp4Movie movie = new Mp4Movie();
movie.setCacheFile(videoFile);
movie.setRotation(0);
movie.setSize(videoWidth, videoHeight);
mediaMuxer = new MP4Builder().createMovie(movie, isSecretChat);
AndroidUtilities.runOnUIThread(() -> {
if (cancelled) {
return;
}
try {
performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
} catch (Exception ignore) {
}
AndroidUtilities.lockOrientation(baseFragment.getParentActivity());
recording = true;
recordStartTime = System.currentTimeMillis();
invalidate();
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.recordStarted, recordingGuid, false);
});
} catch (Exception ioe) {
throw new RuntimeException(ioe);
}
if (eglDisplay != EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("EGL already set up");
}
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("unable to get EGL14 display");
}
int[] version = new int[2];
if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
eglDisplay = null;
throw new RuntimeException("unable to initialize EGL14");
}
if (eglContext == EGL14.EGL_NO_CONTEXT) {
int renderableType = EGL14.EGL_OPENGL_ES2_BIT;
int[] attribList = {
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, renderableType,
0x3142, 1,
EGL14.EGL_NONE
};
android.opengl.EGLConfig[] configs = new android.opengl.EGLConfig[1];
int[] numConfigs = new int[1];
if (!EGL14.eglChooseConfig(eglDisplay, attribList, 0, configs, 0, configs.length, numConfigs, 0)) {
throw new RuntimeException("Unable to find a suitable EGLConfig");
}
int[] attrib2_list = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE
};
eglContext = EGL14.eglCreateContext(eglDisplay, configs[0], sharedEglContext, attrib2_list, 0);
eglConfig = configs[0];
}
int[] values = new int[1];
EGL14.eglQueryContext(eglDisplay, eglContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, values, 0);
if (eglSurface != EGL14.EGL_NO_SURFACE) {
throw new IllegalStateException("surface already created");
}
int[] surfaceAttribs = {
EGL14.EGL_NONE
};
eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, surface, surfaceAttribs, 0);
if (eglSurface == null) {
throw new RuntimeException("surface was null");
}
if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("eglMakeCurrent failed " + GLUtils.getEGLErrorString(EGL14.eglGetError()));
}
throw new RuntimeException("eglMakeCurrent failed");
}
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, createFragmentShader(previewSize));
if (vertexShader != 0 && fragmentShader != 0) {
drawProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(drawProgram, vertexShader);
GLES20.glAttachShader(drawProgram, fragmentShader);
GLES20.glLinkProgram(drawProgram);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(drawProgram, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] == 0) {
GLES20.glDeleteProgram(drawProgram);
drawProgram = 0;
} else {
positionHandle = GLES20.glGetAttribLocation(drawProgram, "aPosition");
textureHandle = GLES20.glGetAttribLocation(drawProgram, "aTextureCoord");
previewSizeHandle = GLES20.glGetUniformLocation(drawProgram, "preview");
resolutionHandle = GLES20.glGetUniformLocation(drawProgram, "resolution");
alphaHandle = GLES20.glGetUniformLocation(drawProgram, "alpha");
vertexMatrixHandle = GLES20.glGetUniformLocation(drawProgram, "uMVPMatrix");
textureMatrixHandle = GLES20.glGetUniformLocation(drawProgram, "uSTMatrix");
}
}
}
public Surface getInputSurface() {
return surface;
}
private void didWriteData(File file, long availableSize, boolean last) {
if (videoConvertFirstWrite) {
FileLoader.getInstance(currentAccount).uploadFile(file.toString(), isSecretChat, false, 1, ConnectionsManager.FileTypeVideo, false);
videoConvertFirstWrite = false;
if (last) {
FileLoader.getInstance(currentAccount).checkUploadNewDataAvailable(file.toString(), isSecretChat, availableSize, last ? file.length() : 0);
}
} else {
FileLoader.getInstance(currentAccount).checkUploadNewDataAvailable(file.toString(), isSecretChat, availableSize, last ? file.length() : 0);
}
}
public void drainEncoder(boolean endOfStream) throws Exception {
if (endOfStream) {
videoEncoder.signalEndOfInputStream();
}
ByteBuffer[] encoderOutputBuffers = null;
if (Build.VERSION.SDK_INT < 21) {
encoderOutputBuffers = videoEncoder.getOutputBuffers();
}
while (true) {
int encoderStatus = videoEncoder.dequeueOutputBuffer(videoBufferInfo, 10000);
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
if (!endOfStream) {
break;
}
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
if (Build.VERSION.SDK_INT < 21) {
encoderOutputBuffers = videoEncoder.getOutputBuffers();
}
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = videoEncoder.getOutputFormat();
if (videoTrackIndex == -5) {
videoTrackIndex = mediaMuxer.addTrack(newFormat, false);
if (newFormat.containsKey(MediaFormat.KEY_PREPEND_HEADER_TO_SYNC_FRAMES) && newFormat.getInteger(MediaFormat.KEY_PREPEND_HEADER_TO_SYNC_FRAMES) == 1) {
ByteBuffer spsBuff = newFormat.getByteBuffer("csd-0");
ByteBuffer ppsBuff = newFormat.getByteBuffer("csd-1");
prependHeaderSize = spsBuff.limit() + ppsBuff.limit();
}
}
} else if (encoderStatus >= 0) {
ByteBuffer encodedData;
if (Build.VERSION.SDK_INT < 21) {
encodedData = encoderOutputBuffers[encoderStatus];
} else {
encodedData = videoEncoder.getOutputBuffer(encoderStatus);
}
if (encodedData == null) {
throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null");
}
if (videoBufferInfo.size > 1) {
if ((videoBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
if (prependHeaderSize != 0 && (videoBufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
videoBufferInfo.offset += prependHeaderSize;
videoBufferInfo.size -= prependHeaderSize;
}
if (firstEncode && (videoBufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
if (videoBufferInfo.size > 100) {
encodedData.position(videoBufferInfo.offset);
byte[] temp = new byte[100];
encodedData.get(temp);
int nalCount = 0;
for (int a = 0; a < temp.length - 4; a++) {
if (temp[a] == 0 && temp[a + 1] == 0 && temp[a + 2] == 0 && temp[a + 3] == 1) {
nalCount++;
if (nalCount > 1) {
videoBufferInfo.offset += a;
videoBufferInfo.size -= a;
break;
}
}
}
}
firstEncode = false;
}
long availableSize = mediaMuxer.writeSampleData(videoTrackIndex, encodedData, videoBufferInfo, true);
if (availableSize != 0) {
didWriteData(videoFile, availableSize, false);
}
} else if (videoTrackIndex == -5) {
byte[] csd = new byte[videoBufferInfo.size];
encodedData.limit(videoBufferInfo.offset + videoBufferInfo.size);
encodedData.position(videoBufferInfo.offset);
encodedData.get(csd);
ByteBuffer sps = null;
ByteBuffer pps = null;
for (int a = videoBufferInfo.size - 1; a >= 0; a--) {
if (a > 3) {
if (csd[a] == 1 && csd[a - 1] == 0 && csd[a - 2] == 0 && csd[a - 3] == 0) {
sps = ByteBuffer.allocate(a - 3);
pps = ByteBuffer.allocate(videoBufferInfo.size - (a - 3));
sps.put(csd, 0, a - 3).position(0);
pps.put(csd, a - 3, videoBufferInfo.size - (a - 3)).position(0);
break;
}
} else {
break;
}
}
MediaFormat newFormat = MediaFormat.createVideoFormat("video/avc", videoWidth, videoHeight);
if (sps != null && pps != null) {
newFormat.setByteBuffer("csd-0", sps);
newFormat.setByteBuffer("csd-1", pps);
}
videoTrackIndex = mediaMuxer.addTrack(newFormat, false);
}
}
videoEncoder.releaseOutputBuffer(encoderStatus, false);
if ((videoBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
break;
}
}
}
if (Build.VERSION.SDK_INT < 21) {
encoderOutputBuffers = audioEncoder.getOutputBuffers();
}
boolean encoderOutputAvailable = true;
while (true) {
int encoderStatus = audioEncoder.dequeueOutputBuffer(audioBufferInfo, 0);
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
if (!endOfStream || !running && sendWhenDone == 0) {
break;
}
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
if (Build.VERSION.SDK_INT < 21) {
encoderOutputBuffers = audioEncoder.getOutputBuffers();
}
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = audioEncoder.getOutputFormat();
if (audioTrackIndex == -5) {
audioTrackIndex = mediaMuxer.addTrack(newFormat, true);
}
} else if (encoderStatus >= 0) {
ByteBuffer encodedData;
if (Build.VERSION.SDK_INT < 21) {
encodedData = encoderOutputBuffers[encoderStatus];
} else {
encodedData = audioEncoder.getOutputBuffer(encoderStatus);
}
if (encodedData == null) {
throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null");
}
if ((audioBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
audioBufferInfo.size = 0;
}
if (audioBufferInfo.size != 0) {
long availableSize = mediaMuxer.writeSampleData(audioTrackIndex, encodedData, audioBufferInfo, false);
if (availableSize != 0) {
didWriteData(videoFile, availableSize, false);
}
}
audioEncoder.releaseOutputBuffer(encoderStatus, false);
if ((audioBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
break;
}
}
}
}
@Override
protected void finalize() throws Throwable {
try {
if (eglDisplay != EGL14.EGL_NO_DISPLAY) {
EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
EGL14.eglDestroyContext(eglDisplay, eglContext);
EGL14.eglReleaseThread();
EGL14.eglTerminate(eglDisplay);
eglDisplay = EGL14.EGL_NO_DISPLAY;
eglContext = EGL14.EGL_NO_CONTEXT;
eglConfig = null;
}
} finally {
super.finalize();
}
}
}
private String createFragmentShader(Size previewSize) {
if (SharedConfig.getDevicePerformanceClass() == SharedConfig.PERFORMANCE_CLASS_LOW || SharedConfig.getDevicePerformanceClass() == SharedConfig.PERFORMANCE_CLASS_AVERAGE || Math.max(previewSize.getHeight(), previewSize.getWidth()) * 0.7f < MessagesController.getInstance(currentAccount).roundVideoSize) {
return "#extension GL_OES_EGL_image_external : require\n" +
"precision highp float;\n" +
"varying vec2 vTextureCoord;\n" +
"uniform float alpha;\n" +
"uniform vec2 preview;\n" +
"uniform vec2 resolution;\n" +
"uniform samplerExternalOES sTexture;\n" +
"void main() {\n" +
" vec4 textColor = texture2D(sTexture, vTextureCoord);\n" +
" vec2 coord = resolution * 0.5;\n" +
" float radius = 0.51 * resolution.x;\n" +
" float d = length(coord - gl_FragCoord.xy) - radius;\n" +
" float t = clamp(d, 0.0, 1.0);\n" +
" vec3 color = mix(textColor.rgb, vec3(1, 1, 1), t);\n" +
" gl_FragColor = vec4(color * alpha, alpha);\n" +
"}\n";
}
//apply box blur
return "#extension GL_OES_EGL_image_external : require\n" +
"precision highp float;\n" +
"varying vec2 vTextureCoord;\n" +
"uniform vec2 resolution;\n" +
"uniform vec2 preview;\n" +
"uniform float alpha;\n" +
"const float kernel = 1.0;\n" +
"uniform samplerExternalOES sTexture;\n" +
"void main() {\n" +
" float pixelSizeX = 1.0 / preview.x;\n" +
" float pixelSizeY = 1.0 / preview.y;\n" +
" vec3 accumulation = vec3(0);\n" +
" vec3 weightsum = vec3(0);\n" +
" for (float x = -kernel; x < kernel; x++){\n" +
" for (float y = -kernel; y < kernel; y++){\n" +
" accumulation += texture2D(sTexture, vTextureCoord + vec2(x * pixelSizeX, y * pixelSizeY)).xyz;\n" +
" weightsum += 1.0;\n" +
" }\n" +
" }\n" +
" vec4 textColor = vec4(accumulation / weightsum, 1.0);\n" +
" vec2 coord = resolution * 0.5;\n" +
" float radius = 0.51 * resolution.x;\n" +
" float d = length(coord - gl_FragCoord.xy) - radius;\n" +
" float t = clamp(d, 0.0, 1.0);\n" +
" vec3 color = mix(textColor.rgb, vec3(1, 1, 1), t);\n" +
" gl_FragColor = vec4(color * alpha, alpha);\n" +
"}\n";
}
public class InstantViewCameraContainer extends FrameLayout {
ImageReceiver imageReceiver;
float imageProgress;
public InstantViewCameraContainer(Context context) {
super(context);
InstantCameraView.this.setWillNotDraw(false);
}
public void setImageReceiver(ImageReceiver imageReceiver) {
if (this.imageReceiver == null) {
imageProgress = 0;
}
this.imageReceiver = imageReceiver;
invalidate();
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (imageProgress != 1f) {
imageProgress += 16 / 250.0f;
if (imageProgress > 1f) {
imageProgress = 1f;
}
invalidate();
}
if (imageReceiver != null) {
canvas.save();
if (imageReceiver.getImageWidth() != textureViewSize) {
float s = textureViewSize / imageReceiver.getImageWidth();
canvas.scale(s, s);
}
canvas.translate(-imageReceiver.getImageX(), -imageReceiver.getImageY());
float oldAlpha = imageReceiver.getAlpha();
imageReceiver.setAlpha(imageProgress);
imageReceiver.draw(canvas);
imageReceiver.setAlpha(oldAlpha);
canvas.restore();
}
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN && baseFragment != null) {
if (videoPlayer != null) {
boolean mute = !videoPlayer.isMuted();
videoPlayer.setMute(mute);
if (muteAnimation != null) {
muteAnimation.cancel();
}
muteAnimation = new AnimatorSet();
muteAnimation.playTogether(
ObjectAnimator.ofFloat(muteImageView, View.ALPHA, mute ? 1.0f : 0.0f),
ObjectAnimator.ofFloat(muteImageView, View.SCALE_X, mute ? 1.0f : 0.5f),
ObjectAnimator.ofFloat(muteImageView, View.SCALE_Y, mute ? 1.0f : 0.5f));
muteAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (animation.equals(muteAnimation)) {
muteAnimation = null;
}
}
});
muteAnimation.setDuration(180);
muteAnimation.setInterpolator(new DecelerateInterpolator());
muteAnimation.start();
} else {
//baseFragment.checkRecordLocked(false);
}
}
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN || ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
if (maybePinchToZoomTouchMode && !isInPinchToZoomTouchMode && ev.getPointerCount() == 2 && finishZoomTransition == null && recording) {
pinchStartDistance = (float) Math.hypot(ev.getX(1) - ev.getX(0), ev.getY(1) - ev.getY(0));
pinchScale = 1f;
pointerId1 = ev.getPointerId(0);
pointerId2 = ev.getPointerId(1);
isInPinchToZoomTouchMode = true;
}
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
AndroidUtilities.rectTmp.set(cameraContainer.getX(), cameraContainer.getY(), cameraContainer.getX() + cameraContainer.getMeasuredWidth(), cameraContainer.getY() + cameraContainer.getMeasuredHeight());
maybePinchToZoomTouchMode = AndroidUtilities.rectTmp.contains(ev.getX(), ev.getY());
}
return true;
} else if (ev.getActionMasked() == MotionEvent.ACTION_MOVE && isInPinchToZoomTouchMode) {
int index1 = -1;
int index2 = -1;
for (int i = 0; i < ev.getPointerCount(); i++) {
if (pointerId1 == ev.getPointerId(i)) {
index1 = i;
}
if (pointerId2 == ev.getPointerId(i)) {
index2 = i;
}
}
if (index1 == -1 || index2 == -1) {
isInPinchToZoomTouchMode = false;
finishZoom();
return false;
}
pinchScale = (float) Math.hypot(ev.getX(index2) - ev.getX(index1), ev.getY(index2) - ev.getY(index1)) / pinchStartDistance;
float zoom = Math.min(1f, Math.max(0, pinchScale - 1f));
cameraSession.setZoom(zoom);
} else if ((ev.getActionMasked() == MotionEvent.ACTION_UP || (ev.getActionMasked() == MotionEvent.ACTION_POINTER_UP && checkPointerIds(ev)) || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) && isInPinchToZoomTouchMode) {
isInPinchToZoomTouchMode = false;
finishZoom();
}
return true;
}
ValueAnimator finishZoomTransition;
public void finishZoom() {
if (finishZoomTransition != null) {
return;
}
float zoom = Math.min(1f, Math.max(0, pinchScale - 1f));
if (zoom > 0f) {
finishZoomTransition = ValueAnimator.ofFloat(zoom, 0);
finishZoomTransition.addUpdateListener(valueAnimator -> {
if (cameraSession != null) {
cameraSession.setZoom((float) valueAnimator.getAnimatedValue());
}
});
finishZoomTransition.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (finishZoomTransition != null) {
finishZoomTransition = null;
}
}
});
finishZoomTransition.setDuration(350);
finishZoomTransition.setInterpolator(CubicBezierInterpolator.DEFAULT);
finishZoomTransition.start();
}
}
}