2017-03-31 01:58:05 +02:00
|
|
|
/*
|
2019-01-23 18:03:33 +01:00
|
|
|
* This is the source code of Telegram for Android v. 5.x.x.
|
2017-03-31 01:58:05 +02:00
|
|
|
* It is licensed under GNU GPL v. 2 or later.
|
|
|
|
* You should have received a copy of the license in this archive (see LICENSE).
|
|
|
|
*
|
2019-01-23 18:03:33 +01:00
|
|
|
* Copyright Nikolai Kudashov, 2013-2018.
|
2017-03-31 01:58:05 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
package org.telegram.ui.Components;
|
|
|
|
|
|
|
|
import android.animation.Animator;
|
|
|
|
import android.animation.AnimatorListenerAdapter;
|
|
|
|
import android.animation.AnimatorSet;
|
|
|
|
import android.animation.ObjectAnimator;
|
2020-03-30 14:00:09 +02:00
|
|
|
import android.animation.ValueAnimator;
|
2021-07-15 16:24:57 +02:00
|
|
|
import android.annotation.SuppressLint;
|
2017-03-31 01:58:05 +02:00
|
|
|
import android.annotation.TargetApi;
|
2017-07-08 18:32:04 +02:00
|
|
|
import android.app.Activity;
|
2022-09-16 20:48:21 +02:00
|
|
|
import android.bluetooth.BluetoothAdapter;
|
|
|
|
import android.bluetooth.BluetoothProfile;
|
2017-03-31 01:58:05 +02:00
|
|
|
import android.content.Context;
|
2019-01-23 18:03:33 +01:00
|
|
|
import android.graphics.Bitmap;
|
|
|
|
import android.graphics.BitmapFactory;
|
2017-03-31 01:58:05 +02:00
|
|
|
import android.graphics.Canvas;
|
2021-07-30 16:49:55 +02:00
|
|
|
import android.graphics.Color;
|
2017-07-08 18:32:04 +02:00
|
|
|
import android.graphics.ImageFormat;
|
2017-03-31 01:58:05 +02:00
|
|
|
import android.graphics.Outline;
|
|
|
|
import android.graphics.Paint;
|
|
|
|
import android.graphics.Path;
|
|
|
|
import android.graphics.PorterDuff;
|
|
|
|
import android.graphics.PorterDuffXfermode;
|
2017-07-08 18:32:04 +02:00
|
|
|
import android.graphics.RectF;
|
|
|
|
import android.graphics.SurfaceTexture;
|
2022-04-16 16:43:17 +02:00
|
|
|
import android.graphics.drawable.AnimatedVectorDrawable;
|
2022-08-12 17:23:51 +02:00
|
|
|
import android.hardware.Camera;
|
2017-07-08 18:32:04 +02:00
|
|
|
import android.media.AudioFormat;
|
2022-09-16 20:48:21 +02:00
|
|
|
import android.media.AudioManager;
|
2017-07-08 18:32:04 +02:00
|
|
|
import android.media.AudioRecord;
|
2023-03-08 08:27:18 +01:00
|
|
|
import android.media.AudioTimestamp;
|
2017-07-08 18:32:04 +02:00
|
|
|
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;
|
2017-03-31 01:58:05 +02:00
|
|
|
import android.os.Build;
|
2017-07-08 18:32:04 +02:00
|
|
|
import android.os.Handler;
|
|
|
|
import android.os.Looper;
|
|
|
|
import android.os.Message;
|
2017-03-31 01:58:05 +02:00
|
|
|
import android.view.Gravity;
|
2018-07-30 04:07:02 +02:00
|
|
|
import android.view.HapticFeedbackConstants;
|
2017-07-08 18:32:04 +02:00
|
|
|
import android.view.MotionEvent;
|
|
|
|
import android.view.Surface;
|
|
|
|
import android.view.TextureView;
|
2017-03-31 01:58:05 +02:00
|
|
|
import android.view.View;
|
2020-03-30 14:00:09 +02:00
|
|
|
import android.view.ViewGroup;
|
2017-03-31 01:58:05 +02:00
|
|
|
import android.view.ViewOutlineProvider;
|
2017-07-08 18:32:04 +02:00
|
|
|
import android.view.WindowManager;
|
2017-03-31 01:58:05 +02:00
|
|
|
import android.view.animation.DecelerateInterpolator;
|
|
|
|
import android.widget.FrameLayout;
|
2017-07-08 18:32:04 +02:00
|
|
|
import android.widget.ImageView;
|
2017-03-31 01:58:05 +02:00
|
|
|
|
2023-03-08 08:27:18 +01:00
|
|
|
import androidx.annotation.RequiresApi;
|
2022-04-16 16:43:17 +02:00
|
|
|
import androidx.core.content.ContextCompat;
|
2021-07-30 16:49:55 +02:00
|
|
|
import androidx.core.graphics.ColorUtils;
|
|
|
|
|
2018-08-27 10:33:11 +02:00
|
|
|
import com.google.android.exoplayer2.ExoPlayer;
|
|
|
|
|
2017-03-31 01:58:05 +02:00
|
|
|
import org.telegram.messenger.AndroidUtilities;
|
2019-01-23 18:03:33 +01:00
|
|
|
import org.telegram.messenger.ApplicationLoader;
|
2023-02-25 09:01:39 +01:00
|
|
|
import org.telegram.messenger.AutoDeleteMediaTask;
|
2018-07-30 04:07:02 +02:00
|
|
|
import org.telegram.messenger.BuildVars;
|
2017-07-08 18:32:04 +02:00
|
|
|
import org.telegram.messenger.DispatchQueue;
|
|
|
|
import org.telegram.messenger.FileLoader;
|
2017-03-31 01:58:05 +02:00
|
|
|
import org.telegram.messenger.FileLog;
|
2020-06-04 18:47:15 +02:00
|
|
|
import org.telegram.messenger.ImageReceiver;
|
2019-05-14 14:08:05 +02:00
|
|
|
import org.telegram.messenger.LocaleController;
|
2017-03-31 01:58:05 +02:00
|
|
|
import org.telegram.messenger.MediaController;
|
2021-07-30 16:49:55 +02:00
|
|
|
import org.telegram.messenger.MessagesController;
|
2017-03-31 01:58:05 +02:00
|
|
|
import org.telegram.messenger.NotificationCenter;
|
2017-07-08 18:32:04 +02:00
|
|
|
import org.telegram.messenger.R;
|
2018-07-30 04:07:02 +02:00
|
|
|
import org.telegram.messenger.SharedConfig;
|
2017-07-08 18:32:04 +02:00
|
|
|
import org.telegram.messenger.UserConfig;
|
2019-01-23 18:03:33 +01:00
|
|
|
import org.telegram.messenger.Utilities;
|
2017-03-31 01:58:05 +02:00
|
|
|
import org.telegram.messenger.VideoEditedInfo;
|
|
|
|
import org.telegram.messenger.camera.CameraController;
|
2017-07-08 18:32:04 +02:00
|
|
|
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;
|
2017-03-31 01:58:05 +02:00
|
|
|
import org.telegram.ui.ChatActivity;
|
2021-07-30 16:49:55 +02:00
|
|
|
import org.telegram.ui.Components.voip.CellFlickerDrawable;
|
2017-03-31 01:58:05 +02:00
|
|
|
|
|
|
|
import java.io.File;
|
2019-01-23 18:03:33 +01:00
|
|
|
import java.io.FileOutputStream;
|
2017-07-08 18:32:04 +02:00
|
|
|
import java.lang.ref.WeakReference;
|
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
import java.nio.ByteOrder;
|
|
|
|
import java.nio.FloatBuffer;
|
|
|
|
import java.util.ArrayList;
|
2022-06-21 04:51:00 +02:00
|
|
|
import java.util.Collections;
|
2017-07-08 18:32:04 +02:00
|
|
|
import java.util.Timer;
|
|
|
|
import java.util.TimerTask;
|
|
|
|
import java.util.concurrent.ArrayBlockingQueue;
|
2018-07-30 04:07:02 +02:00
|
|
|
import java.util.concurrent.CountDownLatch;
|
2017-03-31 01:58:05 +02:00
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
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;
|
|
|
|
|
|
|
|
@TargetApi(18)
|
|
|
|
public class InstantCameraView extends FrameLayout implements NotificationCenter.NotificationCenterDelegate {
|
2017-03-31 01:58:05 +02:00
|
|
|
|
2018-07-30 04:07:02 +02:00
|
|
|
private int currentAccount = UserConfig.selectedAccount;
|
2020-06-04 18:47:15 +02:00
|
|
|
private InstantViewCameraContainer cameraContainer;
|
2017-03-31 01:58:05 +02:00
|
|
|
private ChatActivity baseFragment;
|
2017-07-08 18:32:04 +02:00
|
|
|
private Paint paint;
|
|
|
|
private RectF rect;
|
|
|
|
private ImageView switchCameraButton;
|
2022-04-16 16:43:17 +02:00
|
|
|
AnimatedVectorDrawable switchCameraDrawable = null;
|
2017-07-08 18:32:04 +02:00
|
|
|
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;
|
2018-07-30 04:07:02 +02:00
|
|
|
private boolean isSecretChat;
|
2017-07-08 18:32:04 +02:00
|
|
|
private VideoEditedInfo videoEditedInfo;
|
|
|
|
private VideoPlayer videoPlayer;
|
2019-01-23 18:03:33 +01:00
|
|
|
private Bitmap lastBitmap;
|
2019-09-10 12:56:11 +02:00
|
|
|
private int recordingGuid;
|
2017-03-31 01:58:05 +02:00
|
|
|
|
|
|
|
private int[] position = new int[2];
|
2019-07-18 15:01:39 +02:00
|
|
|
private int[] cameraTexture = new int[1];
|
|
|
|
private int[] oldCameraTexture = new int[1];
|
2017-07-08 18:32:04 +02:00
|
|
|
private float cameraTextureAlpha = 1.0f;
|
2017-03-31 01:58:05 +02:00
|
|
|
|
|
|
|
private AnimatorSet animatorSet;
|
|
|
|
|
|
|
|
private boolean deviceHasGoodCamera;
|
|
|
|
private boolean requestingPermissions;
|
|
|
|
private File cameraFile;
|
|
|
|
private long recordStartTime;
|
|
|
|
private boolean recording;
|
2017-07-08 18:32:04 +02:00
|
|
|
private long recordedTime;
|
|
|
|
private boolean cancelled;
|
2017-03-31 01:58:05 +02:00
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
private CameraGLThread cameraThread;
|
|
|
|
private Size previewSize;
|
|
|
|
private Size pictureSize;
|
2022-08-12 17:23:51 +02:00
|
|
|
private Size aspectRatio = SharedConfig.roundCamera16to9 ? new Size(16, 9) : new Size(4, 3);
|
2017-07-08 18:32:04 +02:00
|
|
|
private TextureView textureView;
|
2019-01-23 18:03:33 +01:00
|
|
|
private BackupImageView textureOverlayView;
|
2017-07-08 18:32:04 +02:00
|
|
|
private CameraSession cameraSession;
|
2021-07-30 16:49:55 +02:00
|
|
|
private boolean needDrawFlickerStub;
|
2017-07-08 18:32:04 +02:00
|
|
|
|
2020-03-30 14:00:09 +02:00
|
|
|
private float panTranslationY;
|
|
|
|
private float animationTranslationY;
|
|
|
|
|
2023-03-18 14:33:38 +01:00
|
|
|
private final float[] mMVPMatrix = new float[16];
|
|
|
|
private final float[] mSTMatrix = new float[16];
|
|
|
|
private final float[] moldSTMatrix = new float[16];
|
2017-07-08 18:32:04 +02:00
|
|
|
private static final String VERTEX_SHADER =
|
|
|
|
"uniform mat4 uMVPMatrix;\n" +
|
2020-12-23 08:48:30 +01:00
|
|
|
"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";
|
2017-07-08 18:32:04 +02:00
|
|
|
|
|
|
|
private static final String FRAGMENT_SCREEN_SHADER =
|
|
|
|
"#extension GL_OES_EGL_image_external : require\n" +
|
2020-12-23 08:48:30 +01:00
|
|
|
"precision lowp float;\n" +
|
|
|
|
"varying vec2 vTextureCoord;\n" +
|
|
|
|
"uniform samplerExternalOES sTexture;\n" +
|
|
|
|
"void main() {\n" +
|
|
|
|
" gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
|
|
|
|
"}\n";
|
2017-07-08 18:32:04 +02:00
|
|
|
|
|
|
|
private FloatBuffer vertexBuffer;
|
|
|
|
private FloatBuffer textureBuffer;
|
2022-06-28 12:00:33 +02:00
|
|
|
private FloatBuffer oldTextureTextureBuffer;
|
2017-07-08 18:32:04 +02:00
|
|
|
private float scaleX;
|
|
|
|
private float scaleY;
|
2022-06-28 12:00:33 +02:00
|
|
|
|
|
|
|
private Size oldTexturePreviewSize;
|
|
|
|
|
2022-04-16 16:43:17 +02:00
|
|
|
private boolean flipAnimationInProgress;
|
2017-07-08 18:32:04 +02:00
|
|
|
|
2020-03-30 14:00:09 +02:00
|
|
|
private View parentView;
|
|
|
|
public boolean opened;
|
|
|
|
|
|
|
|
private BlurBehindDrawable blurBehindDrawable;
|
|
|
|
|
2021-07-15 16:24:57 +02:00
|
|
|
float pinchStartDistance;
|
|
|
|
|
|
|
|
float pinchScale;
|
|
|
|
|
|
|
|
boolean isInPinchToZoomTouchMode;
|
2021-07-19 17:56:43 +02:00
|
|
|
boolean maybePinchToZoomTouchMode;
|
2021-07-15 16:24:57 +02:00
|
|
|
|
|
|
|
private int pointerId1, pointerId2;
|
2021-07-30 16:49:55 +02:00
|
|
|
private int textureViewSize;
|
|
|
|
private boolean isMessageTransition;
|
2021-07-31 01:39:13 +02:00
|
|
|
private boolean updateTextureViewSize;
|
2021-09-20 07:54:41 +02:00
|
|
|
private final Theme.ResourcesProvider resourcesProvider;
|
2021-07-15 16:24:57 +02:00
|
|
|
|
2022-06-21 04:51:00 +02:00
|
|
|
private final static int audioSampleRate = 48000;
|
|
|
|
|
2023-03-13 23:39:50 +01:00
|
|
|
private static final int[] ALLOW_BIG_CAMERA_WHITELIST = {
|
|
|
|
285904780, // XIAOMI (Redmi Note 7)
|
2023-03-18 14:33:38 +01:00
|
|
|
-1394191079 // samsung a31
|
2023-03-13 23:39:50 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2021-07-15 16:24:57 +02:00
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
2021-09-20 07:54:41 +02:00
|
|
|
public InstantCameraView(Context context, ChatActivity parentFragment, Theme.ResourcesProvider resourcesProvider) {
|
2017-03-31 01:58:05 +02:00
|
|
|
super(context);
|
2021-09-20 07:54:41 +02:00
|
|
|
this.resourcesProvider = resourcesProvider;
|
2020-03-30 14:00:09 +02:00
|
|
|
parentView = parentFragment.getFragmentView();
|
2017-07-08 18:32:04 +02:00
|
|
|
setWillNotDraw(false);
|
2020-03-30 14:00:09 +02:00
|
|
|
|
2017-03-31 01:58:05 +02:00
|
|
|
baseFragment = parentFragment;
|
2019-09-10 12:56:11 +02:00
|
|
|
recordingGuid = baseFragment.getClassGuid();
|
2018-07-30 04:07:02 +02:00
|
|
|
isSecretChat = baseFragment.getCurrentEncryptedChat() != null;
|
2017-07-08 18:32:04 +02:00
|
|
|
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();
|
2017-03-31 01:58:05 +02:00
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= 21) {
|
2020-06-04 18:47:15 +02:00
|
|
|
cameraContainer = new InstantViewCameraContainer(context) {
|
2017-07-08 18:32:04 +02:00
|
|
|
@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) {
|
2021-07-30 16:49:55 +02:00
|
|
|
outline.setOval(0, 0, textureViewSize, textureViewSize);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
cameraContainer.setClipToOutline(true);
|
|
|
|
cameraContainer.setWillNotDraw(false);
|
2017-03-31 01:58:05 +02:00
|
|
|
} 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));
|
2020-06-04 18:47:15 +02:00
|
|
|
cameraContainer = new InstantViewCameraContainer(context) {
|
2017-03-31 01:58:05 +02:00
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
@Override
|
|
|
|
public void setScaleX(float scaleX) {
|
|
|
|
super.setScaleX(scaleX);
|
|
|
|
InstantCameraView.this.invalidate();
|
|
|
|
}
|
|
|
|
|
2017-03-31 01:58:05 +02:00
|
|
|
@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) {
|
2017-07-08 18:32:04 +02:00
|
|
|
try {
|
|
|
|
super.dispatchDraw(canvas);
|
|
|
|
canvas.drawPath(path, paint);
|
|
|
|
} catch (Exception ignore) {
|
|
|
|
|
|
|
|
}
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|
|
|
|
};
|
2017-07-08 18:32:04 +02:00
|
|
|
cameraContainer.setWillNotDraw(false);
|
2017-03-31 01:58:05 +02:00
|
|
|
cameraContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
|
2022-04-16 16:43:17 +02:00
|
|
|
addView(cameraContainer, new LayoutParams(AndroidUtilities.roundPlayingMessageSize, AndroidUtilities.roundPlayingMessageSize, Gravity.CENTER));
|
2017-07-08 18:32:04 +02:00
|
|
|
|
|
|
|
switchCameraButton = new ImageView(context);
|
|
|
|
switchCameraButton.setScaleType(ImageView.ScaleType.CENTER);
|
2019-05-14 14:08:05 +02:00
|
|
|
switchCameraButton.setContentDescription(LocaleController.getString("AccDescrSwitchCamera", R.string.AccDescrSwitchCamera));
|
2020-03-30 14:00:09 +02:00
|
|
|
addView(switchCameraButton, LayoutHelper.createFrame(62, 62, Gravity.LEFT | Gravity.BOTTOM, 8, 0, 0, 0));
|
2019-01-23 18:03:33 +01:00
|
|
|
switchCameraButton.setOnClickListener(v -> {
|
|
|
|
if (!cameraReady || cameraSession == null || !cameraSession.isInitied() || cameraThread == null) {
|
|
|
|
return;
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
2019-01-23 18:03:33 +01:00
|
|
|
switchCamera();
|
2022-04-16 16:43:17 +02:00
|
|
|
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() {
|
2019-01-23 18:03:33 +01:00
|
|
|
@Override
|
2022-04-16 16:43:17 +02:00
|
|
|
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();
|
2019-01-23 18:03:33 +01:00
|
|
|
}
|
|
|
|
});
|
2022-04-16 16:43:17 +02:00
|
|
|
valueAnimator.start();
|
2017-07-08 18:32:04 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
2021-07-30 16:49:55 +02:00
|
|
|
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));
|
2022-06-21 04:51:00 +02:00
|
|
|
flickerDrawable.draw(canvas, AndroidUtilities.rectTmp, rad, null);
|
2021-07-30 16:49:55 +02:00
|
|
|
invalidate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2022-04-16 16:43:17 +02:00
|
|
|
addView(textureOverlayView, new LayoutParams(AndroidUtilities.roundPlayingMessageSize, AndroidUtilities.roundPlayingMessageSize, Gravity.CENTER));
|
2019-01-23 18:03:33 +01:00
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
setVisibility(INVISIBLE);
|
2022-04-16 16:43:17 +02:00
|
|
|
blurBehindDrawable = new BlurBehindDrawable(parentView, this, 0, resourcesProvider);
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|
|
|
|
|
2021-07-30 16:49:55 +02:00
|
|
|
@Override
|
|
|
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
2021-07-31 01:39:13 +02:00
|
|
|
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();
|
|
|
|
}
|
2021-07-30 16:49:55 +02:00
|
|
|
}
|
2021-07-31 01:39:13 +02:00
|
|
|
updateTextureViewSize = false;
|
2021-07-30 16:49:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
|
|
}
|
|
|
|
|
2021-07-15 16:24:57 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
@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) {
|
2020-03-30 14:00:09 +02:00
|
|
|
animationTranslationY = getMeasuredHeight() / 2;
|
|
|
|
updateTranslationY();
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|
2020-03-30 14:00:09 +02:00
|
|
|
blurBehindDrawable.checkSizes();
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onAttachedToWindow() {
|
|
|
|
super.onAttachedToWindow();
|
2021-06-25 02:43:10 +02:00
|
|
|
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.fileUploaded);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onDetachedFromWindow() {
|
|
|
|
super.onDetachedFromWindow();
|
2021-06-25 02:43:10 +02:00
|
|
|
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.fileUploaded);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2018-07-30 04:07:02 +02:00
|
|
|
public void didReceivedNotification(int id, int account, Object... args) {
|
2021-06-25 02:43:10 +02:00
|
|
|
if (id == NotificationCenter.fileUploaded) {
|
2017-07-08 18:32:04 +02:00
|
|
|
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];
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void destroy(boolean async, final Runnable beforeDestroyRunnable) {
|
|
|
|
if (cameraSession != null) {
|
|
|
|
cameraSession.destroy();
|
2018-07-30 04:07:02 +02:00
|
|
|
CameraController.getInstance().close(cameraSession, !async ? new CountDownLatch(1) : null, beforeDestroyRunnable);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onDraw(Canvas canvas) {
|
2020-03-30 14:00:09 +02:00
|
|
|
blurBehindDrawable.draw(canvas);
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
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));
|
2020-03-30 14:00:09 +02:00
|
|
|
if (recording) {
|
|
|
|
recordedTime = System.currentTimeMillis() - recordStartTime;
|
|
|
|
progress = Math.min(1f, recordedTime / 60000.0f);
|
|
|
|
invalidate();
|
|
|
|
}
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
if (progress != 0) {
|
2020-03-30 14:00:09 +02:00
|
|
|
canvas.save();
|
2022-04-16 16:43:17 +02:00
|
|
|
if (!flipAnimationInProgress) {
|
|
|
|
canvas.scale(cameraContainer.getScaleX(), cameraContainer.getScaleY(), rect.centerX(), rect.centerY());
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
canvas.drawArc(rect, -90, 360 * progress, false, paint);
|
2020-03-30 14:00:09 +02:00
|
|
|
canvas.restore();
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
if (Theme.chat_roundVideoShadow != null) {
|
|
|
|
int x1 = (int) x - AndroidUtilities.dp(3);
|
|
|
|
int y1 = (int) y - AndroidUtilities.dp(2);
|
|
|
|
canvas.save();
|
2021-07-30 16:49:55 +02:00
|
|
|
if (isMessageTransition) {
|
|
|
|
canvas.scale(cameraContainer.getScaleX(), cameraContainer.getScaleY(), x, y);
|
|
|
|
} else {
|
|
|
|
canvas.scale(cameraContainer.getScaleX(), cameraContainer.getScaleY(), x + textureViewSize / 2f, y + textureViewSize / 2f);
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
Theme.chat_roundVideoShadow.setAlpha((int) (cameraContainer.getAlpha() * 255));
|
2021-07-30 16:49:55 +02:00
|
|
|
Theme.chat_roundVideoShadow.setBounds(x1, y1, x1 + textureViewSize + AndroidUtilities.dp(6), y1 + textureViewSize + AndroidUtilities.dp(6));
|
2017-07-08 18:32:04 +02:00
|
|
|
Theme.chat_roundVideoShadow.draw(canvas);
|
|
|
|
canvas.restore();
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setVisibility(int visibility) {
|
|
|
|
super.setVisibility(visibility);
|
2020-03-30 14:00:09 +02:00
|
|
|
if (visibility != View.VISIBLE && blurBehindDrawable != null) {
|
|
|
|
blurBehindDrawable.clear();
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
switchCameraButton.setAlpha(0.0f);
|
2017-03-31 01:58:05 +02:00
|
|
|
cameraContainer.setAlpha(0.0f);
|
2019-01-23 18:03:33 +01:00
|
|
|
textureOverlayView.setAlpha(0.0f);
|
2017-07-08 18:32:04 +02:00
|
|
|
muteImageView.setAlpha(0.0f);
|
|
|
|
muteImageView.setScaleX(1.0f);
|
|
|
|
muteImageView.setScaleY(1.0f);
|
2017-03-31 01:58:05 +02:00
|
|
|
cameraContainer.setScaleX(0.1f);
|
|
|
|
cameraContainer.setScaleY(0.1f);
|
2019-01-23 18:03:33 +01:00
|
|
|
textureOverlayView.setScaleX(0.1f);
|
|
|
|
textureOverlayView.setScaleY(0.1f);
|
2017-03-31 01:58:05 +02:00
|
|
|
if (cameraContainer.getMeasuredWidth() != 0) {
|
|
|
|
cameraContainer.setPivotX(cameraContainer.getMeasuredWidth() / 2);
|
|
|
|
cameraContainer.setPivotY(cameraContainer.getMeasuredHeight() / 2);
|
2019-01-23 18:03:33 +01:00
|
|
|
textureOverlayView.setPivotX(textureOverlayView.getMeasuredWidth() / 2);
|
|
|
|
textureOverlayView.setPivotY(textureOverlayView.getMeasuredHeight() / 2);
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
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);
|
|
|
|
}
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void showCamera() {
|
2017-07-08 18:32:04 +02:00
|
|
|
if (textureView != null) {
|
2017-03-31 01:58:05 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-04-16 16:43:17 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-01-23 18:03:33 +01:00
|
|
|
textureOverlayView.setAlpha(1.0f);
|
2021-07-30 16:49:55 +02:00
|
|
|
textureOverlayView.invalidate();
|
2019-01-23 18:03:33 +01:00
|
|
|
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;
|
2017-07-08 18:32:04 +02:00
|
|
|
isFrontface = true;
|
|
|
|
selectedCamera = null;
|
|
|
|
recordedTime = 0;
|
|
|
|
progress = 0;
|
|
|
|
cancelled = false;
|
|
|
|
file = null;
|
|
|
|
encryptedFile = null;
|
|
|
|
key = null;
|
|
|
|
iv = null;
|
2021-07-30 16:49:55 +02:00
|
|
|
needDrawFlickerStub = true;
|
2017-03-31 01:58:05 +02:00
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
if (!initCamera()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
MediaController.getInstance().pauseMessage(MediaController.getInstance().getPlayingMessageObject());
|
|
|
|
|
2023-02-25 09:01:39 +01:00
|
|
|
cameraFile = new File(FileLoader.getDirectory(FileLoader.MEDIA_DIR_DOCUMENT), System.currentTimeMillis() + "_" + SharedConfig.getLastLocalId() + ".mp4") {
|
2023-02-18 22:24:25 +01:00
|
|
|
@Override
|
|
|
|
public boolean delete() {
|
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("delete camera file");
|
|
|
|
}
|
|
|
|
return super.delete();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-07-30 04:07:02 +02:00
|
|
|
SharedConfig.saveConfig();
|
2023-02-25 09:01:39 +01:00
|
|
|
AutoDeleteMediaTask.lockFile(cameraFile);
|
2017-07-08 18:32:04 +02:00
|
|
|
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
2023-02-18 22:24:25 +01:00
|
|
|
FileLog.d("show round camera " + cameraFile.getAbsolutePath());
|
2018-07-30 04:07:02 +02:00
|
|
|
}
|
2017-03-31 01:58:05 +02:00
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
textureView = new TextureView(getContext());
|
|
|
|
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
|
2017-03-31 01:58:05 +02:00
|
|
|
@Override
|
2017-07-08 18:32:04 +02:00
|
|
|
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.d("camera surface available");
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
if (cameraThread == null && surface != null) {
|
|
|
|
if (cancelled) {
|
2017-03-31 01:58:05 +02:00
|
|
|
return;
|
|
|
|
}
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.d("start create thread");
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
cameraThread = new CameraGLThread(surface, width, height);
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, final int width, final int height) {
|
2022-06-28 12:00:33 +02:00
|
|
|
if (cameraThread != null) {
|
|
|
|
cameraThread.surfaceWidth = width;
|
|
|
|
cameraThread.surfaceHeight = height;
|
|
|
|
cameraThread.updateScale();
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
|
|
|
|
if (cameraThread != null) {
|
|
|
|
cameraThread.shutdown(0);
|
|
|
|
cameraThread = null;
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
if (cameraSession != null) {
|
|
|
|
CameraController.getInstance().close(cameraSession, null, null);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
|
2017-03-31 01:58:05 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
});
|
2017-07-08 18:32:04 +02:00
|
|
|
cameraContainer.addView(textureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
|
|
|
|
|
2021-07-31 01:39:13 +02:00
|
|
|
updateTextureViewSize = true;
|
2017-07-08 18:32:04 +02:00
|
|
|
setVisibility(VISIBLE);
|
|
|
|
|
|
|
|
startAnimation(true);
|
2020-01-03 16:45:22 +01:00
|
|
|
MediaController.getInstance().requestAudioFocus(true);
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|
|
|
|
|
2020-06-04 18:47:15 +02:00
|
|
|
public InstantViewCameraContainer getCameraContainer() {
|
2017-03-31 01:58:05 +02:00
|
|
|
return cameraContainer;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void startAnimation(boolean open) {
|
|
|
|
if (animatorSet != null) {
|
2020-03-30 14:00:09 +02:00
|
|
|
animatorSet.removeAllListeners();
|
2017-03-31 01:58:05 +02:00
|
|
|
animatorSet.cancel();
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
PipRoundVideoView pipRoundVideoView = PipRoundVideoView.getInstance();
|
|
|
|
if (pipRoundVideoView != null) {
|
|
|
|
pipRoundVideoView.showTemporary(!open);
|
|
|
|
}
|
2020-03-30 14:00:09 +02:00
|
|
|
if (open && !opened) {
|
|
|
|
cameraContainer.setTranslationX(0);
|
|
|
|
textureOverlayView.setTranslationX(0);
|
|
|
|
|
|
|
|
animationTranslationY = getMeasuredHeight() / 2f;
|
|
|
|
updateTranslationY();
|
|
|
|
}
|
|
|
|
opened = open;
|
|
|
|
if (parentView != null) {
|
|
|
|
parentView.invalidate();
|
|
|
|
}
|
|
|
|
blurBehindDrawable.show(open);
|
2017-03-31 01:58:05 +02:00
|
|
|
animatorSet = new AnimatorSet();
|
2020-03-30 14:00:09 +02:00
|
|
|
float toX = 0;
|
|
|
|
if (!open) {
|
2020-12-23 08:48:30 +01:00
|
|
|
toX = recordedTime > 300 ? AndroidUtilities.dp(24) - getMeasuredWidth() / 2f : 0;
|
2020-03-30 14:00:09 +02:00
|
|
|
}
|
|
|
|
ValueAnimator translationYAnimator = ValueAnimator.ofFloat(open ? 1f : 0f, open ? 0 : 1f);
|
|
|
|
translationYAnimator.addUpdateListener(animation -> {
|
|
|
|
animationTranslationY = (getMeasuredHeight() / 2f) * (float) animation.getAnimatedValue();
|
|
|
|
updateTranslationY();
|
|
|
|
});
|
2017-03-31 01:58:05 +02:00
|
|
|
animatorSet.playTogether(
|
2020-01-03 16:45:22 +01:00
|
|
|
ObjectAnimator.ofFloat(switchCameraButton, View.ALPHA, open ? 1.0f : 0.0f),
|
|
|
|
ObjectAnimator.ofFloat(muteImageView, View.ALPHA, 0.0f),
|
2020-03-30 14:00:09 +02:00
|
|
|
ObjectAnimator.ofInt(paint, AnimationProperties.PAINT_ALPHA, open ? 255 : 0),
|
2020-01-03 16:45:22 +01:00
|
|
|
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),
|
2020-03-30 14:00:09 +02:00
|
|
|
ObjectAnimator.ofFloat(cameraContainer, View.TRANSLATION_X, toX),
|
2020-01-03 16:45:22 +01:00
|
|
|
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),
|
2020-03-30 14:00:09 +02:00
|
|
|
ObjectAnimator.ofFloat(textureOverlayView, View.TRANSLATION_X, toX),
|
|
|
|
translationYAnimator
|
2017-03-31 01:58:05 +02:00
|
|
|
);
|
|
|
|
if (!open) {
|
|
|
|
animatorSet.addListener(new AnimatorListenerAdapter() {
|
|
|
|
@Override
|
|
|
|
public void onAnimationEnd(Animator animation) {
|
|
|
|
if (animation.equals(animatorSet)) {
|
|
|
|
hideCamera(true);
|
2017-07-08 18:32:04 +02:00
|
|
|
setVisibility(INVISIBLE);
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2020-03-30 14:00:09 +02:00
|
|
|
} else {
|
|
|
|
setTranslationX(0);
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|
|
|
|
animatorSet.setDuration(180);
|
|
|
|
animatorSet.setInterpolator(new DecelerateInterpolator());
|
|
|
|
animatorSet.start();
|
|
|
|
}
|
|
|
|
|
2020-03-30 14:00:09 +02:00
|
|
|
private void updateTranslationY() {
|
|
|
|
textureOverlayView.setTranslationY(animationTranslationY + panTranslationY);
|
|
|
|
cameraContainer.setTranslationY(animationTranslationY + panTranslationY);
|
|
|
|
}
|
|
|
|
|
2017-03-31 01:58:05 +02:00
|
|
|
public Rect getCameraRect() {
|
|
|
|
cameraContainer.getLocationOnScreen(position);
|
|
|
|
return new Rect(position[0], position[1], cameraContainer.getWidth(), cameraContainer.getHeight());
|
|
|
|
}
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
public void changeVideoPreviewState(int state, float progress) {
|
|
|
|
if (videoPlayer == null) {
|
2017-03-31 01:58:05 +02:00
|
|
|
return;
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
if (state == 0) {
|
|
|
|
startProgressTimer();
|
|
|
|
videoPlayer.play();
|
|
|
|
} else if (state == 1) {
|
|
|
|
stopProgressTimer();
|
|
|
|
videoPlayer.pause();
|
|
|
|
} else if (state == 2) {
|
|
|
|
videoPlayer.seekTo((long) (progress * videoPlayer.getDuration()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-10 12:56:11 +02:00
|
|
|
public void send(int state, boolean notify, int scheduleDate) {
|
2017-07-08 18:32:04 +02:00
|
|
|
if (textureView == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
stopProgressTimer();
|
|
|
|
if (videoPlayer != null) {
|
2019-03-03 21:40:48 +01:00
|
|
|
videoPlayer.releasePlayer(true);
|
2017-07-08 18:32:04 +02:00
|
|
|
videoPlayer = null;
|
|
|
|
}
|
|
|
|
if (state == 4) {
|
2023-02-25 09:01:39 +01:00
|
|
|
if (BuildVars.DEBUG_VERSION && !cameraFile.exists()) {
|
|
|
|
FileLog.e(new RuntimeException("file not found :( round video"));
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
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;
|
2018-07-30 04:07:02 +02:00
|
|
|
videoEditedInfo.estimatedSize = Math.max(1, (long) (size * (videoEditedInfo.estimatedDuration / totalDuration)));
|
2021-07-30 16:49:55 +02:00
|
|
|
videoEditedInfo.bitrate = 1000000;
|
2017-07-08 18:32:04 +02:00
|
|
|
if (videoEditedInfo.startTime > 0) {
|
|
|
|
videoEditedInfo.startTime *= 1000;
|
|
|
|
}
|
|
|
|
if (videoEditedInfo.endTime > 0) {
|
|
|
|
videoEditedInfo.endTime *= 1000;
|
|
|
|
}
|
2021-06-25 02:43:10 +02:00
|
|
|
FileLoader.getInstance(currentAccount).cancelFileUpload(cameraFile.getAbsolutePath(), false);
|
2017-07-08 18:32:04 +02:00
|
|
|
} else {
|
2018-07-30 04:07:02 +02:00
|
|
|
videoEditedInfo.estimatedSize = Math.max(1, size);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
videoEditedInfo.file = file;
|
|
|
|
videoEditedInfo.encryptedFile = encryptedFile;
|
|
|
|
videoEditedInfo.key = key;
|
|
|
|
videoEditedInfo.iv = iv;
|
2021-06-25 02:43:10 +02:00
|
|
|
baseFragment.sendMedia(new MediaController.PhotoEntry(0, 0, 0, cameraFile.getAbsolutePath(), 0, true, 0, 0, 0), videoEditedInfo, notify, scheduleDate, false);
|
2019-09-10 12:56:11 +02:00
|
|
|
if (scheduleDate != 0) {
|
|
|
|
startAnimation(false);
|
|
|
|
}
|
2020-01-03 16:45:22 +01:00
|
|
|
MediaController.getInstance().requestAudioFocus(false);
|
2017-07-08 18:32:04 +02:00
|
|
|
} else {
|
|
|
|
cancelled = recordedTime < 800;
|
|
|
|
recording = false;
|
2020-03-30 14:00:09 +02:00
|
|
|
int reason;
|
|
|
|
if (cancelled) {
|
|
|
|
reason = 4;
|
|
|
|
} else {
|
|
|
|
reason = state == 3 ? 2 : 5;
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
if (cameraThread != null) {
|
2020-03-30 14:00:09 +02:00
|
|
|
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.recordStopped, recordingGuid, reason);
|
2017-07-08 18:32:04 +02:00
|
|
|
int send;
|
|
|
|
if (cancelled) {
|
|
|
|
send = 0;
|
|
|
|
} else if (state == 3) {
|
|
|
|
send = 2;
|
|
|
|
} else {
|
|
|
|
send = 1;
|
|
|
|
}
|
2019-01-23 18:03:33 +01:00
|
|
|
saveLastCameraBitmap();
|
2017-07-08 18:32:04 +02:00
|
|
|
cameraThread.shutdown(send);
|
|
|
|
cameraThread = null;
|
|
|
|
}
|
|
|
|
if (cancelled) {
|
2020-03-30 14:00:09 +02:00
|
|
|
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.audioRecordTooShort, recordingGuid, true, (int) recordedTime);
|
2017-07-08 18:32:04 +02:00
|
|
|
startAnimation(false);
|
2020-01-03 16:45:22 +01:00
|
|
|
MediaController.getInstance().requestAudioFocus(false);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
}
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|
|
|
|
|
2019-01-23 18:03:33 +01:00
|
|
|
private void saveLastCameraBitmap() {
|
|
|
|
Bitmap bitmap = textureView.getBitmap();
|
2020-03-30 14:00:09 +02:00
|
|
|
if (bitmap != null && bitmap.getPixel(0, 0) != 0) {
|
2021-07-30 16:49:55 +02:00
|
|
|
lastBitmap = Bitmap.createScaledBitmap(textureView.getBitmap(), 50, 50, true);
|
2019-01-23 18:03:33 +01:00
|
|
|
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);
|
2020-08-14 18:58:22 +02:00
|
|
|
stream.close();
|
2019-01-23 18:03:33 +01:00
|
|
|
} catch (Throwable ignore) {
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-30 14:00:09 +02:00
|
|
|
public void cancel(boolean byGesture) {
|
2017-07-08 18:32:04 +02:00
|
|
|
stopProgressTimer();
|
|
|
|
if (videoPlayer != null) {
|
2019-03-03 21:40:48 +01:00
|
|
|
videoPlayer.releasePlayer(true);
|
2017-07-08 18:32:04 +02:00
|
|
|
videoPlayer = null;
|
|
|
|
}
|
|
|
|
if (textureView == null) {
|
2017-03-31 01:58:05 +02:00
|
|
|
return;
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
cancelled = true;
|
2017-03-31 01:58:05 +02:00
|
|
|
recording = false;
|
2020-03-30 14:00:09 +02:00
|
|
|
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.recordStopped, recordingGuid, byGesture ? 0 : 6);
|
2017-07-08 18:32:04 +02:00
|
|
|
if (cameraThread != null) {
|
2019-01-23 18:03:33 +01:00
|
|
|
saveLastCameraBitmap();
|
2017-07-08 18:32:04 +02:00
|
|
|
cameraThread.shutdown(0);
|
|
|
|
cameraThread = null;
|
|
|
|
}
|
|
|
|
if (cameraFile != null) {
|
2023-02-18 22:24:25 +01:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("delete camera file by cancel");
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
cameraFile.delete();
|
2023-02-25 09:01:39 +01:00
|
|
|
AutoDeleteMediaTask.unlockFile(cameraFile);
|
2017-07-08 18:32:04 +02:00
|
|
|
cameraFile = null;
|
|
|
|
}
|
2020-01-03 16:45:22 +01:00
|
|
|
MediaController.getInstance().requestAudioFocus(false);
|
2017-03-31 01:58:05 +02:00
|
|
|
startAnimation(false);
|
2020-03-30 14:00:09 +02:00
|
|
|
blurBehindDrawable.show(false);
|
2017-07-08 18:32:04 +02:00
|
|
|
invalidate();
|
|
|
|
}
|
|
|
|
|
|
|
|
public View getSwitchButtonView() {
|
|
|
|
return switchCameraButton;
|
|
|
|
}
|
|
|
|
|
|
|
|
public View getMuteImageView() {
|
|
|
|
return muteImageView;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Paint getPaint() {
|
|
|
|
return paint;
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void hideCamera(boolean async) {
|
2017-07-08 18:32:04 +02:00
|
|
|
destroy(async, null);
|
|
|
|
cameraContainer.setTranslationX(0);
|
2019-01-23 18:03:33 +01:00
|
|
|
textureOverlayView.setTranslationX(0);
|
2020-03-30 14:00:09 +02:00
|
|
|
animationTranslationY = 0;
|
|
|
|
updateTranslationY();
|
|
|
|
MediaController.getInstance().playMessage(MediaController.getInstance().getPlayingMessageObject());
|
|
|
|
|
|
|
|
if (textureView != null) {
|
|
|
|
ViewGroup parent = (ViewGroup) textureView.getParent();
|
|
|
|
if (parent != null) {
|
|
|
|
parent.removeView(textureView);
|
|
|
|
}
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
textureView = null;
|
2020-06-04 18:47:15 +02:00
|
|
|
cameraContainer.setImageReceiver(null);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private void switchCamera() {
|
2019-01-23 18:03:33 +01:00
|
|
|
saveLastCameraBitmap();
|
|
|
|
if (lastBitmap != null) {
|
2021-07-30 16:49:55 +02:00
|
|
|
needDrawFlickerStub = false;
|
2019-01-23 18:03:33 +01:00
|
|
|
textureOverlayView.setImageBitmap(lastBitmap);
|
2020-06-04 18:47:15 +02:00
|
|
|
textureOverlayView.setAlpha(1f);
|
2019-01-23 18:03:33 +01:00
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
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();
|
2022-06-21 04:51:00 +02:00
|
|
|
previewSize = chooseOptimalSize(previewSizes);
|
|
|
|
pictureSize = chooseOptimalSize(pictureSizes);
|
2017-07-08 18:32:04 +02:00
|
|
|
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);
|
2021-07-30 16:49:55 +02:00
|
|
|
if (preview.mWidth >= 360 && preview.mHeight >= 360 && preview.mWidth == picture.mWidth && preview.mHeight == picture.mHeight) {
|
2017-07-08 18:32:04 +02:00
|
|
|
previewSize = preview;
|
|
|
|
pictureSize = picture;
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (found) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.d("preview w = " + previewSize.mWidth + " h = " + previewSize.mHeight);
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-21 04:51:00 +02:00
|
|
|
private Size chooseOptimalSize(ArrayList<Size> previewSizes) {
|
|
|
|
ArrayList<Size> sortedSizes = new ArrayList<>();
|
2023-03-24 12:38:14 +01:00
|
|
|
boolean allowBigSizeCamera = allowBigSizeCamera();
|
|
|
|
int maxVideoSize = allowBigSizeCamera ? 1440 : 1200;
|
|
|
|
if (Build.MANUFACTURER.equalsIgnoreCase("Samsung")) {
|
|
|
|
//1440 lead to gl crashes on samsung s9
|
|
|
|
maxVideoSize = 1200;
|
|
|
|
}
|
2022-06-21 04:51:00 +02:00
|
|
|
for (int i = 0; i < previewSizes.size(); i++) {
|
2023-03-24 12:38:14 +01:00
|
|
|
if (Math.max(previewSizes.get(i).mHeight, previewSizes.get(i).mWidth) <= maxVideoSize && Math.min(previewSizes.get(i).mHeight, previewSizes.get(i).mWidth) >= 320) {
|
2022-06-21 04:51:00 +02:00
|
|
|
sortedSizes.add(previewSizes.get(i));
|
|
|
|
}
|
|
|
|
}
|
2023-03-13 23:39:50 +01:00
|
|
|
if (sortedSizes.isEmpty() || !allowBigSizeCamera()) {
|
2022-09-21 16:20:30 +02:00
|
|
|
ArrayList<Size> sizes = sortedSizes;
|
2022-07-04 13:54:30 +02:00
|
|
|
if (!sortedSizes.isEmpty()) {
|
2022-09-21 16:20:30 +02:00
|
|
|
sizes = sortedSizes;
|
2022-07-04 13:54:30 +02:00
|
|
|
} else {
|
2022-09-21 16:20:30 +02:00
|
|
|
sizes = previewSizes;
|
|
|
|
}
|
|
|
|
if (Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")) {
|
|
|
|
return CameraController.chooseOptimalSize(sizes, 640, 480, aspectRatio);
|
|
|
|
} else {
|
|
|
|
return CameraController.chooseOptimalSize(sizes, 480, 270, aspectRatio);
|
2022-07-04 13:54:30 +02:00
|
|
|
}
|
2022-06-21 04:51:00 +02:00
|
|
|
}
|
|
|
|
Collections.sort(sortedSizes, (o1, o2) -> {
|
2022-06-28 12:00:33 +02:00
|
|
|
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));
|
2022-06-21 04:51:00 +02:00
|
|
|
|
|
|
|
if (a1 < a2) {
|
|
|
|
return -1;
|
2022-06-28 12:00:33 +02:00
|
|
|
} else if (a1 > a2) {
|
|
|
|
return 1;
|
2022-06-21 04:51:00 +02:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
});
|
|
|
|
return sortedSizes.get(0);
|
|
|
|
}
|
|
|
|
|
2023-03-13 23:39:50 +01:00
|
|
|
private boolean allowBigSizeCamera() {
|
2023-03-18 14:33:38 +01:00
|
|
|
if (SharedConfig.bigCameraForRound) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (SharedConfig.deviceIsAboveAverage()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
int devicePerformanceClass = Math.max(SharedConfig.getDevicePerformanceClass(), SharedConfig.getLegacyDevicePerformanceClass());
|
|
|
|
if (devicePerformanceClass == SharedConfig.PERFORMANCE_CLASS_HIGH) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
int hash = (Build.MANUFACTURER + " " + Build.DEVICE).toUpperCase().hashCode();
|
|
|
|
for (int i = 0; i < ALLOW_BIG_CAMERA_WHITELIST.length; ++i) {
|
|
|
|
if (ALLOW_BIG_CAMERA_WHITELIST[i] == hash) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean allowBigSizeCameraDebug() {
|
2023-03-13 23:39:50 +01:00
|
|
|
int devicePerformanceClass = Math.max(SharedConfig.getDevicePerformanceClass(), SharedConfig.getLegacyDevicePerformanceClass());
|
|
|
|
if (devicePerformanceClass == SharedConfig.PERFORMANCE_CLASS_HIGH) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
int hash = (Build.MANUFACTURER + " " + Build.DEVICE).toUpperCase().hashCode();
|
|
|
|
for (int i = 0; i < ALLOW_BIG_CAMERA_WHITELIST.length; ++i) {
|
|
|
|
if (ALLOW_BIG_CAMERA_WHITELIST[i] == hash) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
private void createCamera(final SurfaceTexture surfaceTexture) {
|
2019-01-23 18:03:33 +01:00
|
|
|
AndroidUtilities.runOnUIThread(() -> {
|
|
|
|
if (cameraThread == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.d("create camera session");
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
|
2019-01-23 18:03:33 +01:00
|
|
|
surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
|
2021-07-15 16:24:57 +02:00
|
|
|
cameraSession = new CameraSession(selectedCamera, previewSize, pictureSize, ImageFormat.JPEG, true);
|
2019-01-23 18:03:33 +01:00
|
|
|
cameraThread.setCurrentSession(cameraSession);
|
|
|
|
CameraController.getInstance().openRound(cameraSession, surfaceTexture, () -> {
|
|
|
|
if (cameraSession != null) {
|
2022-08-12 17:23:51 +02:00
|
|
|
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);
|
|
|
|
}
|
2019-01-23 18:03:33 +01:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.d("camera initied");
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
2019-01-23 18:03:33 +01:00
|
|
|
cameraSession.setInitied();
|
2022-08-12 17:23:51 +02:00
|
|
|
if (updateScale) {
|
2022-11-05 13:34:47 +01:00
|
|
|
if (cameraThread != null) {
|
|
|
|
cameraThread.reinitForNewCamera();
|
|
|
|
}
|
2022-08-12 17:23:51 +02:00
|
|
|
}
|
2019-01-23 18:03:33 +01:00
|
|
|
}
|
2023-02-18 22:24:25 +01:00
|
|
|
}, () -> {
|
|
|
|
if (cameraThread != null) {
|
|
|
|
cameraThread.setCurrentSession(cameraSession);
|
|
|
|
}
|
|
|
|
});
|
2017-07-08 18:32:04 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e(GLES20.glGetShaderInfoLog(shader));
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
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() {
|
2019-01-23 18:03:33 +01:00
|
|
|
AndroidUtilities.runOnUIThread(() -> {
|
|
|
|
try {
|
|
|
|
if (videoPlayer != null && videoEditedInfo != null && videoEditedInfo.endTime > 0 && videoPlayer.getCurrentPosition() >= videoEditedInfo.endTime) {
|
|
|
|
videoPlayer.seekTo(videoEditedInfo.startTime > 0 ? videoEditedInfo.startTime : 0);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
2019-01-23 18:03:33 +01:00
|
|
|
} catch (Exception e) {
|
|
|
|
FileLog.e(e);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}, 0, 17);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void stopProgressTimer() {
|
|
|
|
if (progressTimer != null) {
|
|
|
|
try {
|
|
|
|
progressTimer.cancel();
|
|
|
|
progressTimer = null;
|
|
|
|
} catch (Exception e) {
|
|
|
|
FileLog.e(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-30 14:00:09 +02:00
|
|
|
public boolean blurFullyDrawing() {
|
|
|
|
return blurBehindDrawable != null && blurBehindDrawable.isFullyDrawing() && opened;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void invalidateBlur() {
|
|
|
|
if (blurBehindDrawable != null) {
|
|
|
|
blurBehindDrawable.invalidate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void cancelBlur() {
|
|
|
|
blurBehindDrawable.show(false);
|
|
|
|
invalidate();
|
|
|
|
}
|
|
|
|
|
2020-09-30 15:48:47 +02:00
|
|
|
public void onPanTranslationUpdate(float y) {
|
2020-03-30 14:00:09 +02:00
|
|
|
panTranslationY = y / 2f;
|
|
|
|
updateTranslationY();
|
|
|
|
blurBehindDrawable.onPanTranslationUpdate(y);
|
|
|
|
}
|
|
|
|
|
2020-06-04 18:47:15 +02:00
|
|
|
public TextureView getTextureView() {
|
|
|
|
return textureView;
|
|
|
|
}
|
|
|
|
|
2021-07-30 16:49:55 +02:00
|
|
|
public void setIsMessageTransition(boolean isMessageTransition) {
|
|
|
|
this.isMessageTransition = isMessageTransition;
|
|
|
|
}
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
public class CameraGLThread extends DispatchQueue {
|
|
|
|
|
2020-01-03 16:45:22 +01:00
|
|
|
private final static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
|
|
|
|
private final static int EGL_OPENGL_ES2_BIT = 4;
|
2017-07-08 18:32:04 +02:00
|
|
|
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;
|
|
|
|
|
2022-06-28 12:00:33 +02:00
|
|
|
private int surfaceWidth;
|
|
|
|
private int surfaceHeight;
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
public CameraGLThread(SurfaceTexture surface, int surfaceWidth, int surfaceHeight) {
|
|
|
|
super("CameraGLThread");
|
|
|
|
surfaceTexture = surface;
|
|
|
|
|
2022-06-28 12:00:33 +02:00
|
|
|
this.surfaceWidth = surfaceWidth;
|
|
|
|
this.surfaceHeight = surfaceHeight;
|
|
|
|
|
|
|
|
updateScale();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void updateScale() {
|
2017-07-08 18:32:04 +02:00
|
|
|
int width = previewSize.getWidth();
|
|
|
|
int height = previewSize.getHeight();
|
|
|
|
|
2018-07-30 04:07:02 +02:00
|
|
|
float scale = surfaceWidth / (float) Math.min(width, height);
|
2017-07-08 18:32:04 +02:00
|
|
|
|
|
|
|
width *= scale;
|
|
|
|
height *= scale;
|
2022-06-28 12:00:33 +02:00
|
|
|
|
|
|
|
if (width == height) {
|
|
|
|
scaleX = 1f;
|
|
|
|
scaleY = 1f;
|
|
|
|
} else if (width > height) {
|
2017-07-08 18:32:04 +02:00
|
|
|
scaleX = 1.0f;
|
|
|
|
scaleY = width / (float) surfaceHeight;
|
|
|
|
} else {
|
|
|
|
scaleX = height / (float) surfaceWidth;
|
|
|
|
scaleY = 1.0f;
|
|
|
|
}
|
2022-06-28 12:00:33 +02:00
|
|
|
FileLog.d("camera scaleX = " + scaleX + " scaleY = " + scaleY);
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private boolean initGL() {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.d("start init gl");
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
egl10 = (EGL10) EGLContext.getEGL();
|
|
|
|
|
|
|
|
eglDisplay = egl10.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
|
|
|
|
if (eglDisplay == EGL10.EGL_NO_DISPLAY) {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("eglGetDisplay failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
finish();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int[] version = new int[2];
|
|
|
|
if (!egl10.eglInitialize(eglDisplay, version)) {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("eglInitialize failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
finish();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int[] configsCount = new int[1];
|
|
|
|
EGLConfig[] configs = new EGLConfig[1];
|
2020-03-30 14:00:09 +02:00
|
|
|
int[] configSpec = new int[]{
|
2017-07-08 18:32:04 +02:00
|
|
|
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
|
|
|
|
};
|
2020-01-03 16:45:22 +01:00
|
|
|
EGLConfig eglConfig;
|
2017-07-08 18:32:04 +02:00
|
|
|
if (!egl10.eglChooseConfig(eglDisplay, configSpec, configs, 1, configsCount)) {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("eglChooseConfig failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
finish();
|
|
|
|
return false;
|
|
|
|
} else if (configsCount[0] > 0) {
|
|
|
|
eglConfig = configs[0];
|
|
|
|
} else {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("eglConfig not initialized");
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
finish();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-12-23 08:48:30 +01:00
|
|
|
int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE};
|
2017-07-08 18:32:04 +02:00
|
|
|
eglContext = egl10.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
|
|
|
|
if (eglContext == null) {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("eglCreateContext failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
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) {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("createWindowSurface failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
finish();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
finish();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("failed link shader");
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
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 {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("failed creating shader");
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
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]);
|
2019-01-23 18:03:33 +01:00
|
|
|
cameraSurface.setOnFrameAvailableListener(surfaceTexture -> requestRender());
|
2017-07-08 18:32:04 +02:00
|
|
|
createCamera(cameraSurface);
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("gl initied");
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void reinitForNewCamera() {
|
|
|
|
Handler handler = getHandler();
|
|
|
|
if (handler != null) {
|
|
|
|
sendMessage(handler.obtainMessage(DO_REINIT_MESSAGE), 0);
|
|
|
|
}
|
2022-06-28 12:00:33 +02:00
|
|
|
updateScale();
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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)) {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cameraSurface.updateTexImage();
|
|
|
|
|
|
|
|
if (!recording) {
|
2018-07-30 04:07:02 +02:00
|
|
|
videoEncoder.startRecording(cameraFile, EGL14.eglGetCurrentContext());
|
2017-07-08 18:32:04 +02:00
|
|
|
recording = true;
|
|
|
|
int orientation = currentSession.getCurrentOrientation();
|
|
|
|
if (orientation == 90 || orientation == 270) {
|
|
|
|
float temp = scaleX;
|
|
|
|
scaleX = scaleY;
|
|
|
|
scaleY = temp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-08 18:35:59 +01:00
|
|
|
videoEncoder.frameAvailable(cameraSurface, cameraId, System.nanoTime());
|
2017-07-08 18:32:04 +02:00
|
|
|
|
|
|
|
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)) {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.d("eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError()));
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cameraSurface != null) {
|
|
|
|
cameraSurface.getTransformMatrix(moldSTMatrix);
|
|
|
|
cameraSurface.setOnFrameAvailableListener(null);
|
|
|
|
cameraSurface.release();
|
|
|
|
oldCameraTexture[0] = cameraTexture[0];
|
|
|
|
cameraTextureAlpha = 0.0f;
|
|
|
|
cameraTexture[0] = 0;
|
2022-06-28 12:00:33 +02:00
|
|
|
oldTextureTextureBuffer = textureBuffer.duplicate();
|
|
|
|
oldTexturePreviewSize = previewSize;
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
cameraId++;
|
|
|
|
cameraReady = false;
|
|
|
|
|
|
|
|
GLES20.glGenTextures(1, cameraTexture, 0);
|
|
|
|
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, cameraTexture[0]);
|
2022-06-21 04:51:00 +02:00
|
|
|
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);
|
2017-07-08 18:32:04 +02:00
|
|
|
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]);
|
2019-01-23 18:03:33 +01:00
|
|
|
cameraSurface.setOnFrameAvailableListener(surfaceTexture -> requestRender());
|
2017-07-08 18:32:04 +02:00
|
|
|
createCamera(cameraSurface);
|
2022-06-28 12:00:33 +02:00
|
|
|
|
|
|
|
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);
|
2017-07-08 18:32:04 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case DO_SETSESSION_MESSAGE: {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.d("set gl rednderer session");
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
CameraSession newSession = (CameraSession) inputMessage.obj;
|
|
|
|
if (currentSession == newSession) {
|
2020-01-03 16:45:22 +01:00
|
|
|
int rotationAngle = currentSession.getWorldAngle();
|
2017-07-08 18:32:04 +02:00
|
|
|
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 {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("start encoder");
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
encoder.prepareEncoder();
|
|
|
|
} catch (Exception e) {
|
|
|
|
FileLog.e(e);
|
|
|
|
encoder.handleStopRecording(0);
|
|
|
|
Looper.myLooper().quit();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MSG_STOP_RECORDING: {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("stop encoder");
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-23 08:48:30 +01:00
|
|
|
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;
|
2020-03-30 14:00:09 +02:00
|
|
|
|
|
|
|
public AudioBufferInfo() {
|
|
|
|
for (int i = 0; i < MAX_SAMPLES; i++) {
|
|
|
|
buffer[i] = ByteBuffer.allocateDirect(2048);
|
|
|
|
buffer[i].order(ByteOrder.nativeOrder());
|
|
|
|
}
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2019-12-31 14:08:08 +01:00
|
|
|
private int prependHeaderSize;
|
|
|
|
private boolean firstEncode;
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
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;
|
|
|
|
|
2018-07-30 04:07:02 +02:00
|
|
|
private long desyncTime;
|
|
|
|
private long videoFirst = -1;
|
|
|
|
private long videoLast;
|
|
|
|
private long audioFirst = -1;
|
|
|
|
private boolean audioStopedByTime;
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
private int drawProgram;
|
|
|
|
private int vertexMatrixHandle;
|
|
|
|
private int textureMatrixHandle;
|
|
|
|
private int positionHandle;
|
|
|
|
private int textureHandle;
|
2022-06-28 12:00:33 +02:00
|
|
|
private int resolutionHandle;
|
|
|
|
private int previewSizeHandle;
|
2017-07-08 18:32:04 +02:00
|
|
|
private int alphaHandle;
|
2017-12-08 18:35:59 +01:00
|
|
|
private int zeroTimeStamps;
|
2017-07-08 18:32:04 +02:00
|
|
|
private Integer lastCameraId = 0;
|
|
|
|
|
|
|
|
private AudioRecord audioRecorder;
|
|
|
|
|
|
|
|
private ArrayBlockingQueue<AudioBufferInfo> buffers = new ArrayBlockingQueue<>(10);
|
2020-03-30 14:00:09 +02:00
|
|
|
private ArrayList<Bitmap> keyframeThumbs = new ArrayList<>();
|
2020-07-26 10:03:38 +02:00
|
|
|
private DispatchQueue generateKeyframeThumbsQueue;
|
2020-03-30 14:00:09 +02:00
|
|
|
private int frameCount;
|
2017-07-08 18:32:04 +02:00
|
|
|
|
|
|
|
private Runnable recorderRunnable = new Runnable() {
|
|
|
|
|
2023-03-08 08:27:18 +01:00
|
|
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
2017-07-08 18:32:04 +02:00
|
|
|
@Override
|
|
|
|
public void run() {
|
2018-07-30 04:07:02 +02:00
|
|
|
long audioPresentationTimeUs = -1;
|
2017-07-08 18:32:04 +02:00
|
|
|
int readResult;
|
|
|
|
boolean done = false;
|
2023-03-08 08:27:18 +01:00
|
|
|
AudioTimestamp audioTimestamp = new AudioTimestamp();
|
|
|
|
boolean shouldUseTimestamp = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
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;
|
2020-03-30 14:00:09 +02:00
|
|
|
buffer.results = AudioBufferInfo.MAX_SAMPLES;
|
|
|
|
for (int a = 0; a < AudioBufferInfo.MAX_SAMPLES; a++) {
|
2023-03-08 08:27:18 +01:00
|
|
|
if (audioPresentationTimeUs == -1 && !shouldUseTimestamp) {
|
2018-07-30 04:07:02 +02:00
|
|
|
audioPresentationTimeUs = System.nanoTime() / 1000;
|
|
|
|
}
|
2020-03-30 14:00:09 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
if (readResult <= 0) {
|
|
|
|
buffer.results = a;
|
|
|
|
if (!running) {
|
|
|
|
buffer.last = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2023-03-08 08:27:18 +01:00
|
|
|
if (shouldUseTimestamp) {
|
|
|
|
audioRecorder.getTimestamp(audioTimestamp, AudioTimestamp.TIMEBASE_MONOTONIC);
|
|
|
|
buffer.offset[a] = audioTimestamp.nanoTime / 1000;
|
|
|
|
} else {
|
|
|
|
buffer.offset[a] = audioPresentationTimeUs;
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
buffer.read[a] = readResult;
|
2022-06-21 04:51:00 +02:00
|
|
|
int bufferDurationUs = 1000000 * readResult / audioSampleRate / 2;
|
2023-03-08 08:27:18 +01:00
|
|
|
if (!shouldUseTimestamp) {
|
|
|
|
audioPresentationTimeUs += bufferDurationUs;
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
if (buffer.results >= 0 || buffer.last) {
|
2020-03-30 14:00:09 +02:00
|
|
|
if (!running && buffer.results < AudioBufferInfo.MAX_SAMPLES) {
|
2017-07-08 18:32:04 +02:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-07-30 04:07:02 +02:00
|
|
|
public void startRecording(File outputFile, android.opengl.EGLContext sharedContext) {
|
2021-07-30 16:49:55 +02:00
|
|
|
int resolution = MessagesController.getInstance(currentAccount).roundVideoSize;
|
|
|
|
int bitrate = MessagesController.getInstance(currentAccount).roundVideoBitrate * 1024;
|
2018-07-30 04:07:02 +02:00
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
videoFile = outputFile;
|
2018-07-30 04:07:02 +02:00
|
|
|
videoWidth = resolution;
|
|
|
|
videoHeight = resolution;
|
|
|
|
videoBitrate = bitrate;
|
2017-07-08 18:32:04 +02:00
|
|
|
sharedEglContext = sharedContext;
|
|
|
|
|
|
|
|
synchronized (sync) {
|
|
|
|
if (running) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
running = true;
|
2018-07-30 04:07:02 +02:00
|
|
|
Thread thread = new Thread(this, "TextureMovieEncoder");
|
|
|
|
thread.setPriority(Thread.MAX_PRIORITY);
|
|
|
|
thread.start();
|
2017-07-08 18:32:04 +02:00
|
|
|
while (!ready) {
|
|
|
|
try {
|
|
|
|
sync.wait();
|
|
|
|
} catch (InterruptedException ie) {
|
|
|
|
// ignore
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-03-30 14:00:09 +02:00
|
|
|
keyframeThumbs.clear();
|
|
|
|
frameCount = 0;
|
2020-07-26 10:03:38 +02:00
|
|
|
if (generateKeyframeThumbsQueue != null) {
|
|
|
|
generateKeyframeThumbsQueue.cleanupQueue();
|
|
|
|
generateKeyframeThumbsQueue.recycle();
|
|
|
|
}
|
|
|
|
generateKeyframeThumbsQueue = new DispatchQueue("keyframes_thumb_queque");
|
2017-07-08 18:32:04 +02:00
|
|
|
handler.sendMessage(handler.obtainMessage(MSG_START_RECORDING));
|
|
|
|
}
|
|
|
|
|
|
|
|
public void stopRecording(int send) {
|
|
|
|
handler.sendMessage(handler.obtainMessage(MSG_STOP_RECORDING, send, 0));
|
|
|
|
}
|
|
|
|
|
2017-12-08 18:35:59 +01:00
|
|
|
public void frameAvailable(SurfaceTexture st, Integer cameraId, long timestampInternal) {
|
2017-07-08 18:32:04 +02:00
|
|
|
synchronized (sync) {
|
|
|
|
if (!ready) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
long timestamp = st.getTimestamp();
|
|
|
|
if (timestamp == 0) {
|
2017-12-08 18:35:59 +01:00
|
|
|
zeroTimeStamps++;
|
|
|
|
if (zeroTimeStamps > 1) {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.d("fix timestamp enabled");
|
|
|
|
}
|
2017-12-08 18:35:59 +01:00
|
|
|
timestamp = timestampInternal;
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
zeroTimeStamps = 0;
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (audioStopedByTime) {
|
|
|
|
return;
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
buffersToWrite.add(input);
|
2018-07-30 04:07:02 +02:00
|
|
|
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++) {
|
2019-01-23 18:03:33 +01:00
|
|
|
if (a == 0 && Math.abs(videoFirst - input.offset[a]) > 10000000L) {
|
2018-07-30 04:07:02 +02:00
|
|
|
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];
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
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) {
|
2018-07-30 04:07:02 +02:00
|
|
|
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;
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
if (inputBuffer.remaining() < input.read[a]) {
|
|
|
|
input.lastWroteBuffer = a;
|
|
|
|
input = null;
|
|
|
|
break;
|
|
|
|
}
|
2020-03-30 14:00:09 +02:00
|
|
|
inputBuffer.put(input.buffer[a]);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-07-30 04:07:02 +02:00
|
|
|
audioEncoder.queueInputBuffer(inputBufferIndex, 0, inputBuffer.position(), startWriteTime == 0 ? 0 : startWriteTime - audioStartTime, isLast ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} 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;
|
2018-07-30 04:07:02 +02:00
|
|
|
if (videoFirst == -1) {
|
|
|
|
videoFirst = timestampNanos / 1000;
|
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.d("first video frame was at " + videoFirst);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
videoLast = timestampNanos;
|
2017-07-08 18:32:04 +02:00
|
|
|
|
2023-03-18 14:33:38 +01:00
|
|
|
FloatBuffer textureBuffer = InstantCameraView.this.textureBuffer;
|
|
|
|
FloatBuffer vertexBuffer = InstantCameraView.this.vertexBuffer;
|
|
|
|
FloatBuffer oldTextureBuffer = oldTextureTextureBuffer;
|
|
|
|
if (textureBuffer == null || vertexBuffer == null) {
|
|
|
|
FileLog.d("handleVideoFrameAvailable skip frame " + textureBuffer + " " + vertexBuffer);
|
|
|
|
return;
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
|
2023-03-18 14:33:38 +01:00
|
|
|
GLES20.glUseProgram(drawProgram);
|
2017-07-08 18:32:04 +02:00
|
|
|
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
|
2023-03-18 14:33:38 +01:00
|
|
|
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer);
|
2022-06-28 12:00:33 +02:00
|
|
|
GLES20.glEnableVertexAttribArray(positionHandle);
|
2023-03-18 14:33:38 +01:00
|
|
|
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 8, textureBuffer);
|
2022-06-28 12:00:33 +02:00
|
|
|
GLES20.glEnableVertexAttribArray(textureHandle);
|
2023-03-18 14:33:38 +01:00
|
|
|
GLES20.glUniformMatrix4fv(vertexMatrixHandle, 1, false, mMVPMatrix, 0);
|
2022-06-28 12:00:33 +02:00
|
|
|
|
|
|
|
GLES20.glUniform2f(resolutionHandle, videoWidth, videoHeight);
|
|
|
|
|
2023-03-18 14:33:38 +01:00
|
|
|
if (oldCameraTexture[0] != 0 && oldTextureBuffer != null) {
|
2017-07-08 18:32:04 +02:00
|
|
|
if (!blendEnabled) {
|
|
|
|
GLES20.glEnable(GLES20.GL_BLEND);
|
|
|
|
blendEnabled = true;
|
|
|
|
}
|
2022-06-28 12:00:33 +02:00
|
|
|
if (oldTexturePreviewSize != null) {
|
|
|
|
GLES20.glUniform2f(previewSizeHandle, oldTexturePreviewSize.getWidth(), oldTexturePreviewSize.getHeight());
|
|
|
|
}
|
2023-03-18 14:33:38 +01:00
|
|
|
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 8, oldTextureBuffer);
|
2022-06-28 12:00:33 +02:00
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
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);
|
|
|
|
}
|
2022-06-28 12:00:33 +02:00
|
|
|
|
|
|
|
if (previewSize != null) {
|
|
|
|
GLES20.glUniform2f(previewSizeHandle, previewSize.getWidth(), previewSize.getHeight());
|
|
|
|
}
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
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);
|
|
|
|
|
2020-07-26 10:03:38 +02:00
|
|
|
createKeyframeThumb();
|
2020-03-30 14:00:09 +02:00
|
|
|
frameCount++;
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
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;
|
2019-01-23 18:03:33 +01:00
|
|
|
AndroidUtilities.runOnUIThread(() -> textureOverlayView.animate().setDuration(120).alpha(0.0f).setInterpolator(new DecelerateInterpolator()).start());
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (!cameraReady) {
|
|
|
|
cameraReady = true;
|
2019-01-23 18:03:33 +01:00
|
|
|
AndroidUtilities.runOnUIThread(() -> textureOverlayView.animate().setDuration(120).alpha(0.0f).setInterpolator(new DecelerateInterpolator()).start());
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-26 10:03:38 +02:00
|
|
|
private void createKeyframeThumb() {
|
2022-04-16 16:43:17 +02:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && SharedConfig.getDevicePerformanceClass() == SharedConfig.PERFORMANCE_CLASS_HIGH && frameCount % 33 == 0) {
|
2020-07-26 10:03:38 +02:00
|
|
|
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) {
|
2022-06-28 12:00:33 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-07-26 10:03:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
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;
|
2022-09-16 20:48:21 +02:00
|
|
|
|
|
|
|
setBluetoothScoOn(false);
|
2017-07-08 18:32:04 +02:00
|
|
|
} catch (Exception e) {
|
|
|
|
FileLog.e(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (mediaMuxer != null) {
|
|
|
|
try {
|
|
|
|
mediaMuxer.finishMovie();
|
|
|
|
} catch (Exception e) {
|
|
|
|
FileLog.e(e);
|
|
|
|
}
|
|
|
|
}
|
2020-07-26 10:03:38 +02:00
|
|
|
if (generateKeyframeThumbsQueue != null) {
|
|
|
|
generateKeyframeThumbsQueue.cleanupQueue();
|
|
|
|
generateKeyframeThumbsQueue.recycle();
|
|
|
|
generateKeyframeThumbsQueue = null;
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
if (send != 0) {
|
2019-01-23 18:03:33 +01:00
|
|
|
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;
|
2021-07-30 16:49:55 +02:00
|
|
|
videoEditedInfo.resultWidth = videoEditedInfo.originalWidth = 360;
|
|
|
|
videoEditedInfo.resultHeight = videoEditedInfo.originalHeight = 360;
|
2019-01-23 18:03:33 +01:00
|
|
|
videoEditedInfo.originalPath = videoFile.getAbsolutePath();
|
|
|
|
if (send == 1) {
|
2019-09-10 12:56:11 +02:00
|
|
|
if (baseFragment.isInScheduleMode()) {
|
2019-12-31 14:08:08 +01:00
|
|
|
AlertsCreator.createScheduleDatePickerDialog(baseFragment.getParentActivity(), baseFragment.getDialogId(), (notify, scheduleDate) -> {
|
2021-06-25 02:43:10 +02:00
|
|
|
baseFragment.sendMedia(new MediaController.PhotoEntry(0, 0, 0, videoFile.getAbsolutePath(), 0, true, 0, 0, 0), videoEditedInfo, notify, scheduleDate, false);
|
2019-09-10 12:56:11 +02:00
|
|
|
startAnimation(false);
|
2020-06-04 18:47:15 +02:00
|
|
|
}, () -> {
|
|
|
|
startAnimation(false);
|
2021-09-20 07:54:41 +02:00
|
|
|
}, resourcesProvider);
|
2019-09-10 12:56:11 +02:00
|
|
|
} else {
|
2021-06-25 02:43:10 +02:00
|
|
|
baseFragment.sendMedia(new MediaController.PhotoEntry(0, 0, 0, videoFile.getAbsolutePath(), 0, true, 0, 0, 0), videoEditedInfo, true, 0, false);
|
2019-09-10 12:56:11 +02:00
|
|
|
}
|
2019-01-23 18:03:33 +01:00
|
|
|
} else {
|
|
|
|
videoPlayer = new VideoPlayer();
|
|
|
|
videoPlayer.setDelegate(new VideoPlayer.VideoPlayerDelegate() {
|
|
|
|
@Override
|
|
|
|
public void onStateChanged(boolean playWhenReady, int playbackState) {
|
|
|
|
if (videoPlayer == null) {
|
|
|
|
return;
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
2019-01-23 18:03:33 +01:00
|
|
|
if (videoPlayer.isPlaying() && playbackState == ExoPlayer.STATE_ENDED) {
|
|
|
|
videoPlayer.seekTo(videoEditedInfo.startTime > 0 ? videoEditedInfo.startTime : 0);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
2019-01-23 18:03:33 +01:00
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
|
2019-01-23 18:03:33 +01:00
|
|
|
@Override
|
2020-06-04 18:47:15 +02:00
|
|
|
public void onError(VideoPlayer player, Exception e) {
|
2019-01-23 18:03:33 +01:00
|
|
|
FileLog.e(e);
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
|
2019-01-23 18:03:33 +01:00
|
|
|
@Override
|
|
|
|
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
|
2017-07-08 18:32:04 +02:00
|
|
|
|
2019-01-23 18:03:33 +01:00
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
|
2019-01-23 18:03:33 +01:00
|
|
|
@Override
|
|
|
|
public void onRenderedFirstFrame() {
|
2017-07-08 18:32:04 +02:00
|
|
|
|
2019-01-23 18:03:33 +01:00
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
|
2019-01-23 18:03:33 +01:00
|
|
|
@Override
|
|
|
|
public boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
|
2019-01-23 18:03:33 +01:00
|
|
|
@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(
|
2020-01-03 16:45:22 +01:00
|
|
|
ObjectAnimator.ofFloat(switchCameraButton, View.ALPHA, 0.0f),
|
2020-03-30 14:00:09 +02:00
|
|
|
ObjectAnimator.ofInt(paint, AnimationProperties.PAINT_ALPHA, 0),
|
2020-01-03 16:45:22 +01:00
|
|
|
ObjectAnimator.ofFloat(muteImageView, View.ALPHA, 1.0f));
|
2019-01-23 18:03:33 +01:00
|
|
|
animatorSet.setDuration(180);
|
|
|
|
animatorSet.setInterpolator(new DecelerateInterpolator());
|
|
|
|
animatorSet.start();
|
2020-03-30 14:00:09 +02:00
|
|
|
videoEditedInfo.estimatedDuration = recordedTime;
|
|
|
|
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.audioDidSent, recordingGuid, videoEditedInfo, videoFile.getAbsolutePath(), keyframeThumbs);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
2019-03-03 21:40:48 +01:00
|
|
|
didWriteData(videoFile, 0, true);
|
2020-01-03 16:45:22 +01:00
|
|
|
MediaController.getInstance().requestAudioFocus(false);
|
2017-07-08 18:32:04 +02:00
|
|
|
});
|
|
|
|
} else {
|
2021-06-25 02:43:10 +02:00
|
|
|
FileLoader.getInstance(currentAccount).cancelFileUpload(videoFile.getAbsolutePath(), false);
|
2017-07-08 18:32:04 +02:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2022-09-16 20:48:21 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
private void prepareEncoder() {
|
2022-09-16 20:48:21 +02:00
|
|
|
setBluetoothScoOn(true);
|
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
try {
|
2022-06-21 04:51:00 +02:00
|
|
|
int recordBufferSize = AudioRecord.getMinBufferSize(audioSampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
|
2017-07-08 18:32:04 +02:00
|
|
|
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());
|
|
|
|
}
|
2022-06-21 04:51:00 +02:00
|
|
|
audioRecorder = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, audioSampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
|
2017-07-08 18:32:04 +02:00
|
|
|
audioRecorder.startRecording();
|
2018-07-30 04:07:02 +02:00
|
|
|
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();
|
2017-07-08 18:32:04 +02:00
|
|
|
|
|
|
|
audioBufferInfo = new MediaCodec.BufferInfo();
|
|
|
|
videoBufferInfo = new MediaCodec.BufferInfo();
|
|
|
|
|
|
|
|
MediaFormat audioFormat = new MediaFormat();
|
|
|
|
audioFormat.setString(MediaFormat.KEY_MIME, AUDIO_MIME_TYPE);
|
2022-06-21 04:51:00 +02:00
|
|
|
audioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, audioSampleRate);
|
2017-07-08 18:32:04 +02:00
|
|
|
audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
|
2021-07-30 16:49:55 +02:00
|
|
|
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, MessagesController.getInstance(currentAccount).roundAudioBitrate * 1024);
|
2020-03-30 14:00:09 +02:00
|
|
|
audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 2048 * AudioBufferInfo.MAX_SAMPLES);
|
2017-07-08 18:32:04 +02:00
|
|
|
|
|
|
|
audioEncoder = MediaCodec.createEncoderByType(AUDIO_MIME_TYPE);
|
|
|
|
audioEncoder.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
|
|
|
audioEncoder.start();
|
|
|
|
|
|
|
|
videoEncoder = MediaCodec.createEncoderByType(VIDEO_MIME_TYPE);
|
2019-12-31 14:08:08 +01:00
|
|
|
firstEncode = true;
|
2017-07-08 18:32:04 +02:00
|
|
|
|
|
|
|
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);
|
2018-08-27 20:45:56 +02:00
|
|
|
/*if (Build.VERSION.SDK_INT >= 21) {
|
2018-08-27 10:33:11 +02:00
|
|
|
format.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh);
|
|
|
|
if (Build.VERSION.SDK_INT >= 23) {
|
|
|
|
format.setInteger(MediaFormat.KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.AVCLevel5);
|
|
|
|
}
|
2018-08-27 20:45:56 +02:00
|
|
|
}*/
|
2017-07-08 18:32:04 +02:00
|
|
|
|
|
|
|
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);
|
2018-07-30 04:07:02 +02:00
|
|
|
mediaMuxer = new MP4Builder().createMovie(movie, isSecretChat);
|
2017-07-08 18:32:04 +02:00
|
|
|
|
2019-01-23 18:03:33 +01:00
|
|
|
AndroidUtilities.runOnUIThread(() -> {
|
|
|
|
if (cancelled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
|
|
|
|
} catch (Exception ignore) {
|
2018-07-30 04:07:02 +02:00
|
|
|
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
2019-01-23 18:03:33 +01:00
|
|
|
AndroidUtilities.lockOrientation(baseFragment.getParentActivity());
|
|
|
|
recording = true;
|
|
|
|
recordStartTime = System.currentTimeMillis();
|
2020-03-30 14:00:09 +02:00
|
|
|
invalidate();
|
|
|
|
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.recordStarted, recordingGuid, false);
|
2017-07-08 18:32:04 +02:00
|
|
|
});
|
|
|
|
} 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)) {
|
2018-07-30 04:07:02 +02:00
|
|
|
if (BuildVars.LOGS_ENABLED) {
|
|
|
|
FileLog.e("eglMakeCurrent failed " + GLUtils.getEGLErrorString(EGL14.eglGetError()));
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
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);
|
2022-06-21 04:51:00 +02:00
|
|
|
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, createFragmentShader(previewSize));
|
2017-07-08 18:32:04 +02:00
|
|
|
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");
|
2022-06-28 12:00:33 +02:00
|
|
|
previewSizeHandle = GLES20.glGetUniformLocation(drawProgram, "preview");
|
|
|
|
resolutionHandle = GLES20.glGetUniformLocation(drawProgram, "resolution");
|
2017-07-08 18:32:04 +02:00
|
|
|
alphaHandle = GLES20.glGetUniformLocation(drawProgram, "alpha");
|
|
|
|
vertexMatrixHandle = GLES20.glGetUniformLocation(drawProgram, "uMVPMatrix");
|
|
|
|
textureMatrixHandle = GLES20.glGetUniformLocation(drawProgram, "uSTMatrix");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public Surface getInputSurface() {
|
|
|
|
return surface;
|
|
|
|
}
|
|
|
|
|
2019-03-03 21:40:48 +01:00
|
|
|
private void didWriteData(File file, long availableSize, boolean last) {
|
2017-07-08 18:32:04 +02:00
|
|
|
if (videoConvertFirstWrite) {
|
2021-01-28 15:15:51 +01:00
|
|
|
FileLoader.getInstance(currentAccount).uploadFile(file.toString(), isSecretChat, false, 1, ConnectionsManager.FileTypeVideo, false);
|
2017-07-08 18:32:04 +02:00
|
|
|
videoConvertFirstWrite = false;
|
2018-07-30 04:07:02 +02:00
|
|
|
if (last) {
|
2019-03-03 21:40:48 +01:00
|
|
|
FileLoader.getInstance(currentAccount).checkUploadNewDataAvailable(file.toString(), isSecretChat, availableSize, last ? file.length() : 0);
|
2018-07-30 04:07:02 +02:00
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
} else {
|
2019-03-03 21:40:48 +01:00
|
|
|
FileLoader.getInstance(currentAccount).checkUploadNewDataAvailable(file.toString(), isSecretChat, availableSize, last ? file.length() : 0);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void drainEncoder(boolean endOfStream) throws Exception {
|
|
|
|
if (endOfStream) {
|
|
|
|
videoEncoder.signalEndOfInputStream();
|
|
|
|
}
|
|
|
|
|
2017-12-08 18:35:59 +01:00
|
|
|
ByteBuffer[] encoderOutputBuffers = null;
|
|
|
|
if (Build.VERSION.SDK_INT < 21) {
|
|
|
|
encoderOutputBuffers = videoEncoder.getOutputBuffers();
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
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);
|
2019-12-31 14:08:08 +01:00
|
|
|
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();
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
} 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) {
|
2019-12-31 14:08:08 +01:00
|
|
|
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;
|
|
|
|
}
|
2019-03-03 21:40:48 +01:00
|
|
|
long availableSize = mediaMuxer.writeSampleData(videoTrackIndex, encodedData, videoBufferInfo, true);
|
|
|
|
if (availableSize != 0) {
|
|
|
|
didWriteData(videoFile, availableSize, false);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
} 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-08 18:35:59 +01:00
|
|
|
if (Build.VERSION.SDK_INT < 21) {
|
|
|
|
encoderOutputBuffers = audioEncoder.getOutputBuffers();
|
|
|
|
}
|
2017-07-08 18:32:04 +02:00
|
|
|
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) {
|
2019-03-03 21:40:48 +01:00
|
|
|
long availableSize = mediaMuxer.writeSampleData(audioTrackIndex, encodedData, audioBufferInfo, false);
|
|
|
|
if (availableSize != 0) {
|
|
|
|
didWriteData(videoFile, availableSize, false);
|
2017-07-08 18:32:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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();
|
|
|
|
}
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|
|
|
|
}
|
2020-06-04 18:47:15 +02:00
|
|
|
|
2022-06-21 04:51:00 +02:00
|
|
|
private String createFragmentShader(Size previewSize) {
|
2023-03-13 23:39:50 +01:00
|
|
|
if (!allowBigSizeCamera() || Math.max(previewSize.getHeight(), previewSize.getWidth()) * 0.7f < MessagesController.getInstance(currentAccount).roundVideoSize) {
|
2022-06-21 04:51:00 +02:00
|
|
|
return "#extension GL_OES_EGL_image_external : require\n" +
|
|
|
|
"precision highp float;\n" +
|
|
|
|
"varying vec2 vTextureCoord;\n" +
|
|
|
|
"uniform float alpha;\n" +
|
2022-06-28 12:00:33 +02:00
|
|
|
"uniform vec2 preview;\n" +
|
|
|
|
"uniform vec2 resolution;\n" +
|
2022-06-21 04:51:00 +02:00
|
|
|
"uniform samplerExternalOES sTexture;\n" +
|
|
|
|
"void main() {\n" +
|
2022-06-28 12:00:33 +02:00
|
|
|
" 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" +
|
2022-06-21 04:51:00 +02:00
|
|
|
" 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" +
|
2022-06-28 12:00:33 +02:00
|
|
|
"uniform vec2 resolution;\n" +
|
|
|
|
"uniform vec2 preview;\n" +
|
2022-06-21 04:51:00 +02:00
|
|
|
"uniform float alpha;\n" +
|
|
|
|
|
|
|
|
"uniform samplerExternalOES sTexture;\n" +
|
|
|
|
"void main() {\n" +
|
2022-06-28 12:00:33 +02:00
|
|
|
" 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" +
|
2023-03-13 23:39:50 +01:00
|
|
|
" if (t == 0.0) {\n" +
|
|
|
|
" float pixelSizeX = 1.0 / preview.x;\n" +
|
|
|
|
" float pixelSizeY = 1.0 / preview.y;\n" +
|
|
|
|
" vec3 accumulation = vec3(0);\n" +
|
|
|
|
" for (float x = 0.0; x < 2.0; x++){\n" +
|
|
|
|
" for (float y = 0.0; y < 2.0; y++){\n" +
|
|
|
|
" accumulation += texture2D(sTexture, vTextureCoord + vec2(x * pixelSizeX, y * pixelSizeY)).xyz;\n" +
|
|
|
|
" }\n" +
|
|
|
|
" }\n" +
|
|
|
|
" vec4 textColor = vec4(accumulation / vec3(4, 4, 4), 1);\n" +
|
|
|
|
" gl_FragColor = textColor * alpha;\n" +
|
|
|
|
" } else {\n" +
|
|
|
|
" gl_FragColor = vec4(1, 1, 1, alpha);\n" +
|
|
|
|
" }\n" +
|
2022-06-21 04:51:00 +02:00
|
|
|
"}\n";
|
|
|
|
}
|
|
|
|
|
2020-06-04 18:47:15 +02:00
|
|
|
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();
|
2021-07-30 16:49:55 +02:00
|
|
|
if (imageReceiver.getImageWidth() != textureViewSize) {
|
|
|
|
float s = textureViewSize / imageReceiver.getImageWidth();
|
|
|
|
canvas.scale(s, s);
|
|
|
|
}
|
2020-06-04 18:47:15 +02:00
|
|
|
canvas.translate(-imageReceiver.getImageX(), -imageReceiver.getImageY());
|
|
|
|
float oldAlpha = imageReceiver.getAlpha();
|
|
|
|
imageReceiver.setAlpha(imageProgress);
|
|
|
|
imageReceiver.draw(canvas);
|
|
|
|
imageReceiver.setAlpha(oldAlpha);
|
|
|
|
canvas.restore();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-15 16:24:57 +02:00
|
|
|
|
|
|
|
|
|
|
|
@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) {
|
2021-07-19 17:56:43 +02:00
|
|
|
if (maybePinchToZoomTouchMode && !isInPinchToZoomTouchMode && ev.getPointerCount() == 2 && finishZoomTransition == null && recording) {
|
2021-07-15 16:24:57 +02:00
|
|
|
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());
|
2021-07-19 17:56:43 +02:00
|
|
|
maybePinchToZoomTouchMode = AndroidUtilities.rectTmp.contains(ev.getX(), ev.getY());
|
2021-07-15 16:24:57 +02:00
|
|
|
}
|
|
|
|
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();
|
|
|
|
}
|
2021-07-19 17:56:43 +02:00
|
|
|
return true;
|
2021-07-15 16:24:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ValueAnimator finishZoomTransition;
|
2022-06-28 12:00:33 +02:00
|
|
|
|
2021-07-15 16:24:57 +02:00
|
|
|
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 -> {
|
2021-12-07 14:02:02 +01:00
|
|
|
if (cameraSession != null) {
|
|
|
|
cameraSession.setZoom((float) valueAnimator.getAnimatedValue());
|
|
|
|
}
|
2021-07-15 16:24:57 +02:00
|
|
|
});
|
|
|
|
finishZoomTransition.addListener(new AnimatorListenerAdapter() {
|
|
|
|
@Override
|
|
|
|
public void onAnimationEnd(Animator animation) {
|
|
|
|
if (finishZoomTransition != null) {
|
|
|
|
finishZoomTransition = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
finishZoomTransition.setDuration(350);
|
|
|
|
finishZoomTransition.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
|
|
|
finishZoomTransition.start();
|
|
|
|
}
|
|
|
|
}
|
2017-03-31 01:58:05 +02:00
|
|
|
}
|