mirror of https://github.com/NekoX-Dev/NekoX.git
1077 lines
38 KiB
Java
1077 lines
38 KiB
Java
/*
|
|
* This is the source code of Telegram for Android v. 5.x.x.
|
|
* It is licensed under GNU GPL v. 2 or later.
|
|
* You should have received a copy of the license in this archive (see LICENSE).
|
|
*
|
|
* Copyright Nikolai Kudashov, 2013-2018.
|
|
*/
|
|
|
|
package org.telegram.ui.Components;
|
|
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.BitmapShader;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Color;
|
|
import android.graphics.Matrix;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Path;
|
|
import android.graphics.PixelFormat;
|
|
import android.graphics.Rect;
|
|
import android.graphics.RectF;
|
|
import android.graphics.Shader;
|
|
import android.graphics.drawable.Animatable;
|
|
import android.graphics.drawable.BitmapDrawable;
|
|
import android.os.Build;
|
|
import android.util.Log;
|
|
import android.view.View;
|
|
|
|
import org.telegram.messenger.AndroidUtilities;
|
|
import org.telegram.messenger.AnimatedFileDrawableStream;
|
|
import org.telegram.messenger.DispatchQueue;
|
|
import org.telegram.messenger.FileLoader;
|
|
import org.telegram.messenger.FileLog;
|
|
import org.telegram.messenger.ImageLocation;
|
|
import org.telegram.messenger.ImageReceiver;
|
|
import org.telegram.messenger.utils.BitmapsCache;
|
|
import org.telegram.tgnet.TLRPC;
|
|
import org.telegram.ui.ActionBar.Theme;
|
|
|
|
import java.io.File;
|
|
import java.util.ArrayList;
|
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
|
import java.util.concurrent.ThreadPoolExecutor;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
public class AnimatedFileDrawable extends BitmapDrawable implements Animatable, BitmapsCache.Cacheable {
|
|
|
|
public boolean skipFrameUpdate;
|
|
public long currentTime;
|
|
|
|
private static native long createDecoder(String src, int[] params, int account, long streamFileSize, Object readCallback, boolean preview);
|
|
|
|
private static native void destroyDecoder(long ptr);
|
|
|
|
private static native void stopDecoder(long ptr);
|
|
|
|
private static native int getVideoFrame(long ptr, Bitmap bitmap, int[] params, int stride, boolean preview, float startTimeSeconds, float endTimeSeconds);
|
|
|
|
private static native void seekToMs(long ptr, long ms, boolean precise);
|
|
|
|
private static native int getFrameAtTime(long ptr, long ms, Bitmap bitmap, int[] data, int stride);
|
|
|
|
private static native void prepareToSeek(long ptr);
|
|
|
|
private static native void getVideoInfo(int sdkVersion, String src, int[] params);
|
|
|
|
public final static int PARAM_NUM_SUPPORTED_VIDEO_CODEC = 0;
|
|
public final static int PARAM_NUM_WIDTH = 1;
|
|
public final static int PARAM_NUM_HEIGHT = 2;
|
|
public final static int PARAM_NUM_BITRATE = 3;
|
|
public final static int PARAM_NUM_DURATION = 4;
|
|
public final static int PARAM_NUM_AUDIO_FRAME_SIZE = 5;
|
|
public final static int PARAM_NUM_VIDEO_FRAME_SIZE = 6;
|
|
public final static int PARAM_NUM_FRAMERATE = 7;
|
|
public final static int PARAM_NUM_ROTATION = 8;
|
|
public final static int PARAM_NUM_SUPPORTED_AUDIO_CODEC = 9;
|
|
public final static int PARAM_NUM_HAS_AUDIO = 10;
|
|
public final static int PARAM_NUM_COUNT = 11;
|
|
|
|
private long lastFrameTime;
|
|
private int lastTimeStamp;
|
|
private int invalidateAfter = 50;
|
|
private final int[] metaData = new int[5];
|
|
private Runnable loadFrameTask;
|
|
private Bitmap renderingBitmap;
|
|
private int renderingBitmapTime;
|
|
private Bitmap nextRenderingBitmap;
|
|
private int nextRenderingBitmapTime;
|
|
private Bitmap backgroundBitmap;
|
|
private int backgroundBitmapTime;
|
|
private boolean destroyWhenDone;
|
|
private boolean decoderCreated;
|
|
private boolean decodeSingleFrame;
|
|
private boolean singleFrameDecoded;
|
|
private boolean forceDecodeAfterNextFrame;
|
|
private File path;
|
|
private long streamFileSize;
|
|
private int currentAccount;
|
|
private boolean recycleWithSecond;
|
|
private volatile long pendingSeekTo = -1;
|
|
private volatile long pendingSeekToUI = -1;
|
|
private boolean pendingRemoveLoading;
|
|
private int pendingRemoveLoadingFramesReset;
|
|
private boolean isRestarted;
|
|
private final Object sync = new Object();
|
|
|
|
private boolean invalidateParentViewWithSecond;
|
|
public boolean ignoreNoParent;
|
|
|
|
private long lastFrameDecodeTime;
|
|
|
|
private RectF actualDrawRect = new RectF();
|
|
|
|
private BitmapShader renderingShader;
|
|
private BitmapShader nextRenderingShader;
|
|
private BitmapShader backgroundShader;
|
|
|
|
private int[] roundRadius = new int[4];
|
|
private int[] roundRadiusBackup;
|
|
private Matrix shaderMatrix = new Matrix();
|
|
private Path roundPath = new Path();
|
|
private static float[] radii = new float[8];
|
|
|
|
private float scaleX = 1.0f;
|
|
private float scaleY = 1.0f;
|
|
private boolean applyTransformation;
|
|
private final RectF dstRect = new RectF();
|
|
private volatile boolean isRunning;
|
|
private volatile boolean isRecycled;
|
|
public volatile long nativePtr;
|
|
private DispatchQueue decodeQueue;
|
|
private float startTime;
|
|
private float endTime;
|
|
private int renderingHeight;
|
|
private int renderingWidth;
|
|
private boolean precache;
|
|
private float scaleFactor = 1f;
|
|
public boolean isWebmSticker;
|
|
private final TLRPC.Document document;
|
|
private RectF dstRectBackground;
|
|
private Paint backgroundPaint;
|
|
|
|
private View parentView;
|
|
private ArrayList<View> secondParentViews = new ArrayList<>();
|
|
|
|
private ArrayList<ImageReceiver> parents = new ArrayList<>();
|
|
|
|
private AnimatedFileDrawableStream stream;
|
|
|
|
private boolean useSharedQueue;
|
|
private boolean invalidatePath = true;
|
|
private boolean invalidateTaskIsRunning;
|
|
private boolean limitFps;
|
|
|
|
public int repeatCount;
|
|
BitmapsCache bitmapsCache;
|
|
BitmapsCache.Metadata cacheMetadata;
|
|
|
|
private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(8, new ThreadPoolExecutor.DiscardPolicy());
|
|
|
|
private Runnable uiRunnableNoFrame = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
chekDestroyDecoder();
|
|
loadFrameTask = null;
|
|
scheduleNextGetFrame();
|
|
invalidateInternal();
|
|
}
|
|
};
|
|
|
|
boolean generatingCache;
|
|
Runnable cacheGenRunnable;
|
|
private Runnable uiRunnableGenerateCache = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (!isRecycled && !destroyWhenDone && !generatingCache) {
|
|
startTime = System.currentTimeMillis();
|
|
if (RLottieDrawable.lottieCacheGenerateQueue == null) {
|
|
RLottieDrawable.createCacheGenQueue();
|
|
}
|
|
generatingCache = true;
|
|
loadFrameTask = null;
|
|
RLottieDrawable.lottieCacheGenerateQueue.postRunnable(cacheGenRunnable = () -> {
|
|
bitmapsCache.createCache();
|
|
AndroidUtilities.runOnUIThread(() -> {
|
|
generatingCache = false;
|
|
scheduleNextGetFrame();
|
|
});
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
private void chekDestroyDecoder() {
|
|
if (loadFrameRunnable == null && destroyWhenDone && nativePtr != 0 && !generatingCache) {
|
|
destroyDecoder(nativePtr);
|
|
nativePtr = 0;
|
|
}
|
|
if (!canLoadFrames()) {
|
|
if (renderingBitmap != null) {
|
|
renderingBitmap.recycle();
|
|
renderingBitmap = null;
|
|
}
|
|
if (backgroundBitmap != null) {
|
|
backgroundBitmap.recycle();
|
|
backgroundBitmap = null;
|
|
}
|
|
if (decodeQueue != null) {
|
|
decodeQueue.recycle();
|
|
decodeQueue = null;
|
|
}
|
|
invalidateInternal();
|
|
}
|
|
}
|
|
|
|
private void invalidateInternal() {
|
|
for (int i = 0; i < parents.size(); i++) {
|
|
parents.get(i).invalidate();
|
|
}
|
|
}
|
|
|
|
private Runnable uiRunnable = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
chekDestroyDecoder();
|
|
if (stream != null && pendingRemoveLoading) {
|
|
FileLoader.getInstance(currentAccount).removeLoadingVideo(stream.getDocument(), false, false);
|
|
}
|
|
if (pendingRemoveLoadingFramesReset <= 0) {
|
|
pendingRemoveLoading = true;
|
|
} else {
|
|
pendingRemoveLoadingFramesReset--;
|
|
}
|
|
if (!forceDecodeAfterNextFrame) {
|
|
singleFrameDecoded = true;
|
|
} else {
|
|
forceDecodeAfterNextFrame = false;
|
|
}
|
|
loadFrameTask = null;
|
|
nextRenderingBitmap = backgroundBitmap;
|
|
nextRenderingBitmapTime = backgroundBitmapTime;
|
|
nextRenderingShader = backgroundShader;
|
|
if (isRestarted) {
|
|
isRestarted = false;
|
|
repeatCount++;
|
|
checkRepeat();
|
|
}
|
|
|
|
if (metaData[3] < lastTimeStamp) {
|
|
lastTimeStamp = startTime > 0 ? (int) (startTime * 1000) : 0;
|
|
}
|
|
if (metaData[3] - lastTimeStamp != 0) {
|
|
invalidateAfter = metaData[3] - lastTimeStamp;
|
|
if (limitFps && invalidateAfter < 32) {
|
|
invalidateAfter = 32;
|
|
}
|
|
}
|
|
if (pendingSeekToUI >= 0 && pendingSeekTo == -1) {
|
|
pendingSeekToUI = -1;
|
|
invalidateAfter = 0;
|
|
}
|
|
lastTimeStamp = metaData[3];
|
|
if (!secondParentViews.isEmpty()) {
|
|
for (int a = 0, N = secondParentViews.size(); a < N; a++) {
|
|
secondParentViews.get(a).invalidate();
|
|
}
|
|
}
|
|
invalidateInternal();
|
|
scheduleNextGetFrame();
|
|
}
|
|
};
|
|
|
|
public void checkRepeat() {
|
|
if (ignoreNoParent) {
|
|
start();
|
|
return;
|
|
}
|
|
int count = 0;
|
|
for (int j = 0; j < parents.size(); j++) {
|
|
ImageReceiver parent = parents.get(j);
|
|
if (!parent.isAttachedToWindow()) {
|
|
parents.remove(j);
|
|
j--;
|
|
}
|
|
if (parent.animatedFileDrawableRepeatMaxCount > 0 && repeatCount >= parent.animatedFileDrawableRepeatMaxCount) {
|
|
count++;
|
|
}
|
|
}
|
|
if (parents.size() == count) {
|
|
stop();
|
|
} else {
|
|
start();
|
|
}
|
|
}
|
|
|
|
private Runnable loadFrameRunnable = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (!isRecycled) {
|
|
if (!decoderCreated && nativePtr == 0) {
|
|
nativePtr = createDecoder(path.getAbsolutePath(), metaData, currentAccount, streamFileSize, stream, false);
|
|
if (nativePtr != 0 && (metaData[0] > 3840 || metaData[1] > 3840)) {
|
|
destroyDecoder(nativePtr);
|
|
nativePtr = 0;
|
|
}
|
|
updateScaleFactor();
|
|
decoderCreated = true;
|
|
}
|
|
try {
|
|
if (bitmapsCache != null) {
|
|
if (backgroundBitmap == null) {
|
|
backgroundBitmap = Bitmap.createBitmap(renderingWidth, renderingHeight, Bitmap.Config.ARGB_8888);
|
|
}
|
|
if (cacheMetadata == null) {
|
|
cacheMetadata = new BitmapsCache.Metadata();
|
|
}
|
|
lastFrameDecodeTime = System.currentTimeMillis();
|
|
int result = bitmapsCache.getFrame(backgroundBitmap, cacheMetadata);
|
|
metaData[3] = backgroundBitmapTime = cacheMetadata.frame * 33;
|
|
|
|
if (bitmapsCache.needGenCache()) {
|
|
AndroidUtilities.runOnUIThread(uiRunnableGenerateCache);
|
|
}
|
|
if (result == -1) {
|
|
AndroidUtilities.runOnUIThread(uiRunnableNoFrame);
|
|
} else {
|
|
AndroidUtilities.runOnUIThread(uiRunnable);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (nativePtr != 0 || metaData[0] == 0 || metaData[1] == 0) {
|
|
if (backgroundBitmap == null && metaData[0] > 0 && metaData[1] > 0) {
|
|
try {
|
|
backgroundBitmap = Bitmap.createBitmap((int) (metaData[0] * scaleFactor), (int) (metaData[1] * scaleFactor), Bitmap.Config.ARGB_8888);
|
|
} catch (Throwable e) {
|
|
FileLog.e(e);
|
|
}
|
|
if (backgroundShader == null && backgroundBitmap != null && hasRoundRadius()) {
|
|
backgroundShader = new BitmapShader(backgroundBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
|
}
|
|
}
|
|
boolean seekWas = false;
|
|
if (pendingSeekTo >= 0) {
|
|
metaData[3] = (int) pendingSeekTo;
|
|
long seekTo = pendingSeekTo;
|
|
synchronized (sync) {
|
|
pendingSeekTo = -1;
|
|
}
|
|
seekWas = true;
|
|
if (stream != null) {
|
|
stream.reset();
|
|
}
|
|
seekToMs(nativePtr, seekTo, true);
|
|
}
|
|
if (backgroundBitmap != null) {
|
|
lastFrameDecodeTime = System.currentTimeMillis();
|
|
|
|
if (getVideoFrame(nativePtr, backgroundBitmap, metaData, backgroundBitmap.getRowBytes(), false, startTime, endTime) == 0) {
|
|
AndroidUtilities.runOnUIThread(uiRunnableNoFrame);
|
|
return;
|
|
}
|
|
if (lastTimeStamp != 0 && metaData[3] == 0) {
|
|
isRestarted = true;
|
|
}
|
|
if (seekWas) {
|
|
lastTimeStamp = metaData[3];
|
|
}
|
|
|
|
backgroundBitmapTime = metaData[3];
|
|
}
|
|
} else {
|
|
AndroidUtilities.runOnUIThread(uiRunnableNoFrame);
|
|
return;
|
|
}
|
|
} catch (Throwable e) {
|
|
FileLog.e(e);
|
|
}
|
|
}
|
|
AndroidUtilities.runOnUIThread(uiRunnable);
|
|
}
|
|
};
|
|
|
|
private void updateScaleFactor() {
|
|
if (!isWebmSticker && renderingHeight > 0 && renderingWidth > 0 && metaData[0] > 0 && metaData[1] > 0) {
|
|
scaleFactor = Math.max(renderingWidth / (float) metaData[0], renderingHeight / (float) metaData[1]);
|
|
if (scaleFactor <= 0 || scaleFactor > 0.7) {
|
|
scaleFactor = 1;
|
|
}
|
|
} else {
|
|
scaleFactor = 1f;
|
|
}
|
|
}
|
|
|
|
private final Runnable mStartTask = () -> {
|
|
if (!secondParentViews.isEmpty()) {
|
|
for (int a = 0, N = secondParentViews.size(); a < N; a++) {
|
|
secondParentViews.get(a).invalidate();
|
|
}
|
|
}
|
|
if ((secondParentViews.isEmpty() || invalidateParentViewWithSecond) && parentView != null) {
|
|
parentView.invalidate();
|
|
}
|
|
};
|
|
|
|
public AnimatedFileDrawable(File file, boolean createDecoder, long streamSize, TLRPC.Document document, ImageLocation location, Object parentObject, long seekTo, int account, boolean preview, BitmapsCache.CacheOptions cacheOptions) {
|
|
this(file, createDecoder, streamSize, document, location, parentObject, seekTo, account, preview, 0, 0, cacheOptions);
|
|
}
|
|
|
|
public AnimatedFileDrawable(File file, boolean createDecoder, long streamSize, TLRPC.Document document, ImageLocation location, Object parentObject, long seekTo, int account, boolean preview, int w, int h, BitmapsCache.CacheOptions cacheOptions) {
|
|
path = file;
|
|
streamFileSize = streamSize;
|
|
currentAccount = account;
|
|
renderingHeight = h;
|
|
renderingWidth = w;
|
|
this.precache = cacheOptions != null && renderingWidth > 0 && renderingHeight > 0;
|
|
this.document = document;
|
|
getPaint().setFlags(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
|
if (streamSize != 0 && (document != null || location != null)) {
|
|
stream = new AnimatedFileDrawableStream(document, location, parentObject, account, preview);
|
|
}
|
|
if (createDecoder && !this.precache) {
|
|
nativePtr = createDecoder(file.getAbsolutePath(), metaData, currentAccount, streamFileSize, stream, preview);
|
|
if (nativePtr != 0 && (metaData[0] > 3840 || metaData[1] > 3840)) {
|
|
destroyDecoder(nativePtr);
|
|
nativePtr = 0;
|
|
}
|
|
updateScaleFactor();
|
|
decoderCreated = true;
|
|
}
|
|
if (this.precache) {
|
|
nativePtr = createDecoder(file.getAbsolutePath(), metaData, currentAccount, streamFileSize, stream, preview);
|
|
if (nativePtr != 0 && (metaData[0] > 3840 || metaData[1] > 3840)) {
|
|
destroyDecoder(nativePtr);
|
|
nativePtr = 0;
|
|
} else {
|
|
bitmapsCache = new BitmapsCache(file, this, cacheOptions, renderingWidth, renderingHeight);
|
|
}
|
|
}
|
|
if (seekTo != 0) {
|
|
seekTo(seekTo, false);
|
|
}
|
|
}
|
|
|
|
public void setIsWebmSticker(boolean b) {
|
|
isWebmSticker = b;
|
|
if (isWebmSticker) {
|
|
useSharedQueue = true;
|
|
}
|
|
}
|
|
|
|
public Bitmap getFrameAtTime(long ms) {
|
|
return getFrameAtTime(ms, false);
|
|
}
|
|
|
|
public Bitmap getFrameAtTime(long ms, boolean precise) {
|
|
if (!decoderCreated || nativePtr == 0) {
|
|
return null;
|
|
}
|
|
if (stream != null) {
|
|
stream.cancel(false);
|
|
stream.reset();
|
|
}
|
|
if (!precise) {
|
|
seekToMs(nativePtr, ms, precise);
|
|
}
|
|
if (backgroundBitmap == null) {
|
|
backgroundBitmap = Bitmap.createBitmap((int) (metaData[0] * scaleFactor), (int) (metaData[1] * scaleFactor), Bitmap.Config.ARGB_8888);
|
|
}
|
|
int result;
|
|
if (precise) {
|
|
result = getFrameAtTime(nativePtr, ms, backgroundBitmap, metaData, backgroundBitmap.getRowBytes());
|
|
} else {
|
|
result = getVideoFrame(nativePtr, backgroundBitmap, metaData, backgroundBitmap.getRowBytes(), true, 0, 0);
|
|
}
|
|
return result != 0 ? backgroundBitmap : null;
|
|
}
|
|
|
|
public void setParentView(View view) {
|
|
if (parentView != null) {
|
|
return;
|
|
}
|
|
parentView = view;
|
|
}
|
|
|
|
public void addParent(ImageReceiver imageReceiver) {
|
|
if (imageReceiver != null && !parents.contains(imageReceiver)) {
|
|
parents.add(imageReceiver);
|
|
if (isRunning) {
|
|
scheduleNextGetFrame();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void removeParent(ImageReceiver imageReceiver) {
|
|
parents.remove(imageReceiver);
|
|
if (parents.size() == 0) {
|
|
repeatCount = 0;
|
|
}
|
|
}
|
|
|
|
public void setInvalidateParentViewWithSecond(boolean value) {
|
|
invalidateParentViewWithSecond = value;
|
|
}
|
|
|
|
public void addSecondParentView(View view) {
|
|
if (view == null || secondParentViews.contains(view)) {
|
|
return;
|
|
}
|
|
secondParentViews.add(view);
|
|
}
|
|
|
|
public void removeSecondParentView(View view) {
|
|
secondParentViews.remove(view);
|
|
if (secondParentViews.isEmpty()) {
|
|
if (recycleWithSecond) {
|
|
recycle();
|
|
} else {
|
|
if (roundRadiusBackup != null) {
|
|
setRoundRadius(roundRadiusBackup);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setAllowDecodeSingleFrame(boolean value) {
|
|
decodeSingleFrame = value;
|
|
if (decodeSingleFrame) {
|
|
scheduleNextGetFrame();
|
|
}
|
|
}
|
|
|
|
public void seekTo(long ms, boolean removeLoading) {
|
|
seekTo(ms, removeLoading, false);
|
|
}
|
|
|
|
public void seekTo(long ms, boolean removeLoading, boolean force) {
|
|
synchronized (sync) {
|
|
pendingSeekTo = ms;
|
|
pendingSeekToUI = ms;
|
|
if (nativePtr != 0) {
|
|
prepareToSeek(nativePtr);
|
|
}
|
|
if (decoderCreated && stream != null) {
|
|
stream.cancel(removeLoading);
|
|
pendingRemoveLoading = removeLoading;
|
|
pendingRemoveLoadingFramesReset = pendingRemoveLoading ? 0 : 10;
|
|
}
|
|
if (force && decodeSingleFrame) {
|
|
singleFrameDecoded = false;
|
|
if (loadFrameTask == null) {
|
|
scheduleNextGetFrame();
|
|
} else {
|
|
forceDecodeAfterNextFrame = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void recycle() {
|
|
if (!secondParentViews.isEmpty()) {
|
|
recycleWithSecond = true;
|
|
return;
|
|
}
|
|
isRunning = false;
|
|
isRecycled = true;
|
|
if (cacheGenRunnable != null) {
|
|
RLottieDrawable.lottieCacheGenerateQueue.cancelRunnable(cacheGenRunnable);
|
|
}
|
|
if (loadFrameTask == null) {
|
|
if (nativePtr != 0) {
|
|
destroyDecoder(nativePtr);
|
|
nativePtr = 0;
|
|
}
|
|
|
|
ArrayList<Bitmap> bitmapToRecycle = new ArrayList<>();
|
|
bitmapToRecycle.add(renderingBitmap);
|
|
bitmapToRecycle.add(nextRenderingBitmap);
|
|
|
|
if (renderingBitmap != null) {
|
|
renderingBitmap = null;
|
|
}
|
|
if (nextRenderingBitmap != null) {
|
|
nextRenderingBitmap = null;
|
|
}
|
|
if (decodeQueue != null) {
|
|
decodeQueue.recycle();
|
|
decodeQueue = null;
|
|
}
|
|
getPaint().setShader(null);
|
|
AndroidUtilities.recycleBitmaps(bitmapToRecycle);
|
|
} else {
|
|
destroyWhenDone = true;
|
|
}
|
|
if (stream != null) {
|
|
stream.cancel(true);
|
|
}
|
|
invalidateInternal();
|
|
}
|
|
|
|
public void resetStream(boolean stop) {
|
|
if (stream != null) {
|
|
stream.cancel(true);
|
|
}
|
|
if (nativePtr != 0) {
|
|
if (stop) {
|
|
stopDecoder(nativePtr);
|
|
} else {
|
|
prepareToSeek(nativePtr);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setUseSharedQueue(boolean value) {
|
|
if (isWebmSticker) {
|
|
return;
|
|
}
|
|
useSharedQueue = value;
|
|
}
|
|
|
|
@Override
|
|
protected void finalize() throws Throwable {
|
|
try {
|
|
recycle();
|
|
} finally {
|
|
super.finalize();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getOpacity() {
|
|
return PixelFormat.TRANSPARENT;
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
if (isRunning || parents.size() == 0 && !ignoreNoParent) {
|
|
return;
|
|
}
|
|
isRunning = true;
|
|
scheduleNextGetFrame();
|
|
AndroidUtilities.runOnUIThread(mStartTask);
|
|
}
|
|
|
|
public float getCurrentProgress() {
|
|
if (metaData[4] == 0) {
|
|
return 0;
|
|
}
|
|
if (pendingSeekToUI >= 0) {
|
|
return pendingSeekToUI / (float) metaData[4];
|
|
}
|
|
return metaData[3] / (float) metaData[4];
|
|
}
|
|
|
|
public int getCurrentProgressMs() {
|
|
if (pendingSeekToUI >= 0) {
|
|
return (int) pendingSeekToUI;
|
|
}
|
|
return nextRenderingBitmapTime != 0 ? nextRenderingBitmapTime : renderingBitmapTime;
|
|
}
|
|
|
|
public int getDurationMs() {
|
|
return metaData[4];
|
|
}
|
|
|
|
private void scheduleNextGetFrame() {
|
|
if (loadFrameTask != null || !canLoadFrames() || destroyWhenDone || !isRunning && (!decodeSingleFrame || decodeSingleFrame && singleFrameDecoded) || parents.size() == 0 && !ignoreNoParent || generatingCache) {
|
|
return;
|
|
}
|
|
long ms = 0;
|
|
if (lastFrameDecodeTime != 0) {
|
|
ms = Math.min(invalidateAfter, Math.max(0, invalidateAfter - (System.currentTimeMillis() - lastFrameDecodeTime)));
|
|
}
|
|
if (useSharedQueue) {
|
|
executor.schedule(loadFrameTask = loadFrameRunnable, ms, TimeUnit.MILLISECONDS);
|
|
} else {
|
|
if (decodeQueue == null) {
|
|
decodeQueue = new DispatchQueue("decodeQueue" + this);
|
|
}
|
|
decodeQueue.postRunnable(loadFrameTask = loadFrameRunnable, ms);
|
|
}
|
|
}
|
|
|
|
public boolean isLoadingStream() {
|
|
return stream != null && stream.isWaitingForLoad();
|
|
}
|
|
|
|
@Override
|
|
public void stop() {
|
|
isRunning = false;
|
|
}
|
|
|
|
@Override
|
|
public boolean isRunning() {
|
|
return isRunning;
|
|
}
|
|
|
|
@Override
|
|
public int getIntrinsicHeight() {
|
|
int height = decoderCreated ? (metaData[2] == 90 || metaData[2] == 270 ? metaData[0] : metaData[1]) : 0;
|
|
if (height == 0) {
|
|
return AndroidUtilities.dp(100);
|
|
} else {
|
|
height *= scaleFactor;
|
|
}
|
|
return height;
|
|
}
|
|
|
|
@Override
|
|
public int getIntrinsicWidth() {
|
|
int width = decoderCreated ? (metaData[2] == 90 || metaData[2] == 270 ? metaData[1] : metaData[0]) : 0;
|
|
if (width == 0) {
|
|
return AndroidUtilities.dp(100);
|
|
} else {
|
|
width *= scaleFactor;
|
|
}
|
|
return width;
|
|
}
|
|
|
|
@Override
|
|
protected void onBoundsChange(Rect bounds) {
|
|
super.onBoundsChange(bounds);
|
|
applyTransformation = true;
|
|
}
|
|
|
|
@Override
|
|
public void draw(Canvas canvas) {
|
|
drawInternal(canvas, false, System.currentTimeMillis());
|
|
}
|
|
|
|
public void drawInBackground(Canvas canvas, float x, float y, float w, float h, int alpha) {
|
|
if (dstRectBackground == null) {
|
|
dstRectBackground = new RectF();
|
|
backgroundPaint = new Paint();
|
|
backgroundPaint.setFilterBitmap(true);
|
|
}
|
|
backgroundPaint.setAlpha(alpha);
|
|
dstRectBackground.set(x, y, x + w, y + h);
|
|
drawInternal(canvas, true, 0);
|
|
}
|
|
|
|
public void drawInternal(Canvas canvas, boolean drawInBackground, long currentTime) {
|
|
if (!canLoadFrames() || destroyWhenDone) {
|
|
return;
|
|
}
|
|
|
|
if (currentTime == 0) {
|
|
currentTime = System.currentTimeMillis();
|
|
}
|
|
|
|
RectF rect = drawInBackground ? dstRectBackground : dstRect;
|
|
Paint paint = drawInBackground ? backgroundPaint : getPaint();
|
|
|
|
if (!drawInBackground) {
|
|
updateCurrentFrame(currentTime, false);
|
|
}
|
|
|
|
if (renderingBitmap != null) {
|
|
float scaleX = this.scaleX;
|
|
float scaleY = this.scaleY;
|
|
if (drawInBackground) {
|
|
int bitmapW = renderingBitmap.getWidth();
|
|
int bitmapH = renderingBitmap.getHeight();
|
|
if (metaData[2] == 90 || metaData[2] == 270) {
|
|
int temp = bitmapW;
|
|
bitmapW = bitmapH;
|
|
bitmapH = temp;
|
|
}
|
|
scaleX = rect.width() / bitmapW;
|
|
scaleY = rect.height() / bitmapH;
|
|
} else if (applyTransformation) {
|
|
int bitmapW = renderingBitmap.getWidth();
|
|
int bitmapH = renderingBitmap.getHeight();
|
|
if (metaData[2] == 90 || metaData[2] == 270) {
|
|
int temp = bitmapW;
|
|
bitmapW = bitmapH;
|
|
bitmapH = temp;
|
|
}
|
|
rect.set(getBounds());
|
|
this.scaleX = scaleX = rect.width() / bitmapW;
|
|
this.scaleY = scaleY = rect.height() / bitmapH;
|
|
applyTransformation = false;
|
|
}
|
|
if (hasRoundRadius()) {
|
|
if (renderingShader == null) {
|
|
renderingShader = new BitmapShader(backgroundBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
|
}
|
|
paint.setShader(renderingShader);
|
|
shaderMatrix.reset();
|
|
shaderMatrix.setTranslate(rect.left, rect.top);
|
|
if (metaData[2] == 90) {
|
|
shaderMatrix.preRotate(90);
|
|
shaderMatrix.preTranslate(0, -rect.width());
|
|
} else if (metaData[2] == 180) {
|
|
shaderMatrix.preRotate(180);
|
|
shaderMatrix.preTranslate(-rect.width(), -rect.height());
|
|
} else if (metaData[2] == 270) {
|
|
shaderMatrix.preRotate(270);
|
|
shaderMatrix.preTranslate(-rect.height(), 0);
|
|
}
|
|
shaderMatrix.preScale(scaleX, scaleY);
|
|
|
|
renderingShader.setLocalMatrix(shaderMatrix);
|
|
if (invalidatePath) {
|
|
invalidatePath = false;
|
|
for (int a = 0; a < roundRadius.length; a++) {
|
|
radii[a * 2] = roundRadius[a];
|
|
radii[a * 2 + 1] = roundRadius[a];
|
|
}
|
|
roundPath.reset();
|
|
roundPath.addRoundRect(actualDrawRect, radii, Path.Direction.CW);
|
|
roundPath.close();
|
|
}
|
|
canvas.drawPath(roundPath, paint);
|
|
} else {
|
|
canvas.translate(rect.left, rect.top);
|
|
if (metaData[2] == 90) {
|
|
canvas.rotate(90);
|
|
canvas.translate(0, -rect.width());
|
|
} else if (metaData[2] == 180) {
|
|
canvas.rotate(180);
|
|
canvas.translate(-rect.width(), -rect.height());
|
|
} else if (metaData[2] == 270) {
|
|
canvas.rotate(270);
|
|
canvas.translate(-rect.height(), 0);
|
|
}
|
|
canvas.scale(scaleX, scaleY);
|
|
canvas.drawBitmap(renderingBitmap, 0, 0, paint);
|
|
}
|
|
}
|
|
}
|
|
|
|
public long getLastFrameTimestamp() {
|
|
return lastTimeStamp;
|
|
}
|
|
|
|
@Override
|
|
public int getMinimumHeight() {
|
|
int height = decoderCreated ? (metaData[2] == 90 || metaData[2] == 270 ? metaData[0] : metaData[1]) : 0;
|
|
if (height == 0) {
|
|
return AndroidUtilities.dp(100);
|
|
}
|
|
return height;
|
|
}
|
|
|
|
@Override
|
|
public int getMinimumWidth() {
|
|
int width = decoderCreated ? (metaData[2] == 90 || metaData[2] == 270 ? metaData[1] : metaData[0]) : 0;
|
|
if (width == 0) {
|
|
return AndroidUtilities.dp(100);
|
|
}
|
|
return width;
|
|
}
|
|
|
|
public Bitmap getRenderingBitmap() {
|
|
return renderingBitmap;
|
|
}
|
|
|
|
public Bitmap getNextRenderingBitmap() {
|
|
return nextRenderingBitmap;
|
|
}
|
|
|
|
public Bitmap getBackgroundBitmap() {
|
|
return backgroundBitmap;
|
|
}
|
|
|
|
public Bitmap getAnimatedBitmap() {
|
|
if (renderingBitmap != null) {
|
|
return renderingBitmap;
|
|
} else if (nextRenderingBitmap != null) {
|
|
return nextRenderingBitmap;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void setActualDrawRect(float x, float y, float width, float height) {
|
|
float bottom = y + height;
|
|
float right = x + width;
|
|
if (actualDrawRect.left != x || actualDrawRect.top != y || actualDrawRect.right != right || actualDrawRect.bottom != bottom) {
|
|
actualDrawRect.set(x, y, right, bottom);
|
|
invalidatePath = true;
|
|
}
|
|
}
|
|
|
|
public void setRoundRadius(int[] value) {
|
|
if (!secondParentViews.isEmpty()) {
|
|
if (roundRadiusBackup == null) {
|
|
roundRadiusBackup = new int[4];
|
|
}
|
|
System.arraycopy(roundRadius, 0, roundRadiusBackup, 0, roundRadiusBackup.length);
|
|
}
|
|
for (int i = 0; i < 4; i++) {
|
|
if (!invalidatePath && value[i] != roundRadius[i]) {
|
|
invalidatePath = true;
|
|
}
|
|
roundRadius[i] = value[i];
|
|
}
|
|
}
|
|
|
|
private boolean hasRoundRadius() {
|
|
for (int a = 0; a < roundRadius.length; a++) {
|
|
if (roundRadius[a] != 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean hasBitmap() {
|
|
return canLoadFrames() && (renderingBitmap != null || nextRenderingBitmap != null);
|
|
}
|
|
|
|
public int getOrientation() {
|
|
return metaData[2];
|
|
}
|
|
|
|
public AnimatedFileDrawable makeCopy() {
|
|
AnimatedFileDrawable drawable;
|
|
if (stream != null) {
|
|
drawable = new AnimatedFileDrawable(path, false, streamFileSize, stream.getDocument(), stream.getLocation(), stream.getParentObject(), pendingSeekToUI, currentAccount, stream != null && stream.isPreview(), null);
|
|
} else {
|
|
drawable = new AnimatedFileDrawable(path, false, streamFileSize, document, null, null, pendingSeekToUI, currentAccount, stream != null && stream.isPreview(), null);
|
|
}
|
|
drawable.metaData[0] = metaData[0];
|
|
drawable.metaData[1] = metaData[1];
|
|
return drawable;
|
|
}
|
|
|
|
public static void getVideoInfo(String src, int[] params) {
|
|
getVideoInfo(Build.VERSION.SDK_INT, src, params);
|
|
}
|
|
|
|
public void setStartEndTime(long startTime, long endTime) {
|
|
this.startTime = startTime / 1000f;
|
|
this.endTime = endTime / 1000f;
|
|
if (getCurrentProgressMs() < startTime) {
|
|
seekTo(startTime, true);
|
|
}
|
|
}
|
|
|
|
public long getStartTime() {
|
|
return (long) (startTime * 1000);
|
|
}
|
|
|
|
public boolean isRecycled() {
|
|
return isRecycled;
|
|
}
|
|
|
|
public Bitmap getNextFrame() {
|
|
if (nativePtr == 0) {
|
|
return backgroundBitmap;
|
|
}
|
|
if (backgroundBitmap == null) {
|
|
backgroundBitmap = Bitmap.createBitmap((int) (metaData[0] * scaleFactor), (int) (metaData[1] * scaleFactor), Bitmap.Config.ARGB_8888);
|
|
}
|
|
getVideoFrame(nativePtr, backgroundBitmap, metaData, backgroundBitmap.getRowBytes(), false, startTime, endTime);
|
|
return backgroundBitmap;
|
|
}
|
|
|
|
public void setLimitFps(boolean limitFps) {
|
|
this.limitFps = limitFps;
|
|
}
|
|
|
|
public ArrayList<ImageReceiver> getParents() {
|
|
return parents;
|
|
}
|
|
|
|
public File getFilePath() {
|
|
return path;
|
|
}
|
|
|
|
long cacheGenerateTimestamp;
|
|
Bitmap generatingCacheBitmap;
|
|
long cacheGenerateNativePtr;
|
|
|
|
@Override
|
|
public void prepareForGenerateCache() {
|
|
cacheGenerateNativePtr = createDecoder(path.getAbsolutePath(), metaData, currentAccount, streamFileSize, stream, false);
|
|
}
|
|
|
|
@Override
|
|
public void releaseForGenerateCache() {
|
|
if (cacheGenerateNativePtr != 0) {
|
|
destroyDecoder(cacheGenerateNativePtr);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getNextFrame(Bitmap bitmap) {
|
|
if (cacheGenerateNativePtr == 0) {
|
|
return -1;
|
|
}
|
|
Canvas canvas = new Canvas(bitmap);
|
|
if (generatingCacheBitmap == null) {
|
|
generatingCacheBitmap = Bitmap.createBitmap(metaData[0], metaData[1], Bitmap.Config.ARGB_8888);
|
|
}
|
|
getVideoFrame(cacheGenerateNativePtr, generatingCacheBitmap, metaData, generatingCacheBitmap.getRowBytes(), false, startTime, endTime);
|
|
if (cacheGenerateTimestamp != 0 && metaData[3] == 0 || cacheGenerateTimestamp > metaData[3]) {
|
|
return 0;
|
|
}
|
|
bitmap.eraseColor(Color.TRANSPARENT);
|
|
canvas.save();
|
|
float s = (float) renderingWidth / generatingCacheBitmap.getWidth();
|
|
canvas.scale(s, s);
|
|
canvas.drawBitmap(generatingCacheBitmap, 0, 0, null);
|
|
canvas.restore();
|
|
cacheGenerateTimestamp = metaData[3];
|
|
return 1;
|
|
}
|
|
|
|
@Override
|
|
public Bitmap getFirstFrame(Bitmap bitmap) {
|
|
Canvas canvas = new Canvas(bitmap);
|
|
if (generatingCacheBitmap == null) {
|
|
generatingCacheBitmap = Bitmap.createBitmap(metaData[0], metaData[1], Bitmap.Config.ARGB_8888);
|
|
}
|
|
|
|
long nativePtr = createDecoder(path.getAbsolutePath(), metaData, currentAccount, streamFileSize, stream, false);
|
|
if (nativePtr == 0) {
|
|
return bitmap;
|
|
}
|
|
getVideoFrame(nativePtr, generatingCacheBitmap, metaData, generatingCacheBitmap.getRowBytes(), false, startTime, endTime);
|
|
destroyDecoder(nativePtr);
|
|
bitmap.eraseColor(Color.TRANSPARENT);
|
|
canvas.save();
|
|
float s = (float) renderingWidth / generatingCacheBitmap.getWidth();
|
|
canvas.scale(s, s);
|
|
canvas.drawBitmap(generatingCacheBitmap, 0, 0, null);
|
|
canvas.restore();
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
public boolean canLoadFrames() {
|
|
if (precache) {
|
|
return bitmapsCache != null;
|
|
} else {
|
|
return nativePtr != 0 || !decoderCreated;
|
|
}
|
|
}
|
|
|
|
public void checkCacheExist() {
|
|
if (precache && bitmapsCache != null) {
|
|
bitmapsCache.cacheExist();
|
|
}
|
|
}
|
|
|
|
public void updateCurrentFrame(long now, boolean b) {
|
|
if (isRunning) {
|
|
if (renderingBitmap == null && nextRenderingBitmap == null) {
|
|
scheduleNextGetFrame();
|
|
} else if (nextRenderingBitmap != null && (renderingBitmap == null || (Math.abs(now - lastFrameTime) >= invalidateAfter && !skipFrameUpdate))) {
|
|
if (precache) {
|
|
backgroundBitmap = renderingBitmap;
|
|
}
|
|
renderingBitmap = nextRenderingBitmap;
|
|
renderingBitmapTime = nextRenderingBitmapTime;
|
|
renderingShader = nextRenderingShader;
|
|
nextRenderingBitmap = null;
|
|
nextRenderingBitmapTime = 0;
|
|
nextRenderingShader = null;
|
|
lastFrameTime = now;
|
|
}
|
|
} else if (!isRunning && decodeSingleFrame && Math.abs(now - lastFrameTime) >= invalidateAfter && nextRenderingBitmap != null) {
|
|
if (precache) {
|
|
backgroundBitmap = renderingBitmap;
|
|
}
|
|
renderingBitmap = nextRenderingBitmap;
|
|
renderingBitmapTime = nextRenderingBitmapTime;
|
|
renderingShader = nextRenderingShader;
|
|
nextRenderingBitmap = null;
|
|
nextRenderingBitmapTime = 0;
|
|
nextRenderingShader = null;
|
|
lastFrameTime = now;
|
|
}
|
|
}
|
|
}
|