move SkottieAnimation out of SkottieRunner to top level
This refactor adds a LOG_TAG and SkottieRunner member variable to SkottieAnimation so that it still has access to the EGL member variables. The private keyword was removed from the SkottieRunner's EGL variables. Methods in SkottieRunner that were made from private to protected: getNativeProxy() runOnGLThread() Methods in SkottieAnimation that were made from private to protected: SurfaceView constructor setSurfaceTexture() Change-Id: I9ddb167238fbc0e05f4d1cdcee67f6b288019e95 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/335667 Commit-Queue: Jorge Betancourt <jmbetancourt@google.com> Reviewed-by: Florin Malita <fmalita@google.com>
This commit is contained in:
parent
641ff3b7fc
commit
4867834b85
@ -114,10 +114,10 @@ struct SkottieAnimation {
|
||||
|
||||
extern "C" JNIEXPORT jlong
|
||||
JNICALL
|
||||
Java_org_skia_skottie_SkottieRunner_00024SkottieAnimation_nCreateProxy(JNIEnv *env,
|
||||
jobject clazz,
|
||||
jlong runner,
|
||||
jobject bufferObj) {
|
||||
Java_org_skia_skottie_SkottieAnimation_nCreateProxy(JNIEnv *env,
|
||||
jobject clazz,
|
||||
jlong runner,
|
||||
jobject bufferObj) {
|
||||
|
||||
if (!runner) {
|
||||
return 0;
|
||||
@ -163,8 +163,8 @@ Java_org_skia_skottie_SkottieRunner_00024SkottieAnimation_nCreateProxy(JNIEnv *e
|
||||
|
||||
extern "C" JNIEXPORT void
|
||||
JNICALL
|
||||
Java_org_skia_skottie_SkottieRunner_00024SkottieAnimation_nDeleteProxy(JNIEnv *env, jclass clazz,
|
||||
jlong nativeProxy) {
|
||||
Java_org_skia_skottie_SkottieAnimation_nDeleteProxy(JNIEnv *env, jclass clazz,
|
||||
jlong nativeProxy) {
|
||||
if (!nativeProxy) {
|
||||
return;
|
||||
}
|
||||
@ -174,13 +174,13 @@ Java_org_skia_skottie_SkottieRunner_00024SkottieAnimation_nDeleteProxy(JNIEnv *e
|
||||
|
||||
extern "C" JNIEXPORT bool
|
||||
JNICALL
|
||||
Java_org_skia_skottie_SkottieRunner_00024SkottieAnimation_nDrawFrame(JNIEnv *env, jclass clazz,
|
||||
jlong nativeProxy, jint width,
|
||||
jint height,
|
||||
jboolean wideColorGamut,
|
||||
jfloat progress,
|
||||
jint backgroundColor,
|
||||
jboolean forceDraw) {
|
||||
Java_org_skia_skottie_SkottieAnimation_nDrawFrame(JNIEnv *env, jclass clazz,
|
||||
jlong nativeProxy, jint width,
|
||||
jint height,
|
||||
jboolean wideColorGamut,
|
||||
jfloat progress,
|
||||
jint backgroundColor,
|
||||
jboolean forceDraw) {
|
||||
ATRACE_NAME("SkottieDrawFrame");
|
||||
if (!nativeProxy) {
|
||||
return false;
|
||||
@ -234,9 +234,9 @@ Java_org_skia_skottie_SkottieRunner_00024SkottieAnimation_nDrawFrame(JNIEnv *env
|
||||
|
||||
extern "C" JNIEXPORT jlong
|
||||
JNICALL
|
||||
Java_org_skia_skottie_SkottieRunner_00024SkottieAnimation_nGetDuration(JNIEnv *env,
|
||||
jclass clazz,
|
||||
jlong nativeProxy) {
|
||||
Java_org_skia_skottie_SkottieAnimation_nGetDuration(JNIEnv *env,
|
||||
jclass clazz,
|
||||
jlong nativeProxy) {
|
||||
if (!nativeProxy) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -0,0 +1,455 @@
|
||||
package org.skia.skottie;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.opengl.GLUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Choreographer;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.TextureView;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
import javax.microedition.khronos.egl.EGL10;
|
||||
import javax.microedition.khronos.egl.EGLSurface;
|
||||
|
||||
public class SkottieAnimation extends Animator implements Choreographer.FrameCallback,
|
||||
TextureView.SurfaceTextureListener, SurfaceHolder.Callback {
|
||||
private final SkottieRunner mRunner = SkottieRunner.getInstance();
|
||||
private static final String LOG_TAG = "SkottiePlayer";
|
||||
|
||||
private boolean mIsRunning = false;
|
||||
private SurfaceTexture mSurfaceTexture;
|
||||
private EGLSurface mEglSurface;
|
||||
private boolean mNewSurface = false;
|
||||
private SurfaceHolder mSurfaceHolder;
|
||||
boolean mValidSurface = false;
|
||||
private int mRepeatCount;
|
||||
private int mRepeatCounter;
|
||||
private int mSurfaceWidth = 0;
|
||||
private int mSurfaceHeight = 0;
|
||||
private int mBackgroundColor;
|
||||
private long mNativeProxy;
|
||||
private long mDuration; // duration in ms of the animation
|
||||
private float mProgress; // animation progress in the range of 0.0f to 1.0f
|
||||
private long mAnimationStartTime; // time in System.nanoTime units, when started
|
||||
|
||||
SkottieAnimation(SurfaceTexture surfaceTexture, InputStream is) {
|
||||
if (init(is)) {
|
||||
mSurfaceTexture = surfaceTexture;
|
||||
}
|
||||
}
|
||||
SkottieAnimation(TextureView view, InputStream is, int backgroundColor, int repeatCount) {
|
||||
if (init(is)) {
|
||||
mSurfaceTexture = view.getSurfaceTexture();
|
||||
}
|
||||
view.setSurfaceTextureListener(this);
|
||||
mBackgroundColor = backgroundColor;
|
||||
mRepeatCount = repeatCount;
|
||||
mRepeatCounter = mRepeatCount;
|
||||
}
|
||||
|
||||
SkottieAnimation(SurfaceView view, InputStream is, int backgroundColor, int repeatCount) {
|
||||
if (init(is)) {
|
||||
mSurfaceHolder = view.getHolder();
|
||||
}
|
||||
mSurfaceHolder.addCallback(this);
|
||||
mBackgroundColor = backgroundColor;
|
||||
mRepeatCount = repeatCount;
|
||||
mRepeatCounter = mRepeatCount;
|
||||
}
|
||||
|
||||
void setSurfaceTexture(SurfaceTexture s) {
|
||||
mSurfaceTexture = s;
|
||||
}
|
||||
|
||||
private ByteBuffer convertToByteBuffer(InputStream is) throws IOException {
|
||||
if (is instanceof FileInputStream) {
|
||||
FileChannel fileChannel = ((FileInputStream)is).getChannel();
|
||||
return fileChannel.map(FileChannel.MapMode.READ_ONLY,
|
||||
fileChannel.position(), fileChannel.size());
|
||||
}
|
||||
|
||||
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
|
||||
byte[] tmpStorage = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = is.read(tmpStorage, 0, tmpStorage.length)) != -1) {
|
||||
byteStream.write(tmpStorage, 0, bytesRead);
|
||||
}
|
||||
|
||||
byteStream.flush();
|
||||
tmpStorage = byteStream.toByteArray();
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(tmpStorage.length);
|
||||
buffer.order(ByteOrder.nativeOrder());
|
||||
buffer.put(tmpStorage, 0, tmpStorage.length);
|
||||
return buffer.asReadOnlyBuffer();
|
||||
}
|
||||
|
||||
private boolean init(InputStream is) {
|
||||
|
||||
ByteBuffer byteBuffer;
|
||||
try {
|
||||
byteBuffer = convertToByteBuffer(is);
|
||||
} catch (IOException e) {
|
||||
Log.e(LOG_TAG, "failed to read input stream", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
long proxy = mRunner.getNativeProxy();
|
||||
mNativeProxy = nCreateProxy(proxy, byteBuffer);
|
||||
mDuration = nGetDuration(mNativeProxy);
|
||||
mProgress = 0f;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
end();
|
||||
nDeleteProxy(mNativeProxy);
|
||||
mNativeProxy = 0;
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
// Always call this on GL thread
|
||||
public void updateSurface(int width, int height) {
|
||||
mSurfaceWidth = width;
|
||||
mSurfaceHeight = height;
|
||||
mNewSurface = true;
|
||||
drawFrame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
try {
|
||||
mRunner.runOnGLThread(() -> {
|
||||
if (!mIsRunning) {
|
||||
long currentTime = System.nanoTime();
|
||||
mAnimationStartTime = currentTime - (long)(1000000 * mDuration * mProgress);
|
||||
mIsRunning = true;
|
||||
mNewSurface = true;
|
||||
mRepeatCounter = mRepeatCount;
|
||||
doFrame(currentTime);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "start failed", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
for (AnimatorListener l : this.getListeners()) {
|
||||
l.onAnimationStart(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
try {
|
||||
mRunner.runOnGLThread(() -> {
|
||||
mIsRunning = false;
|
||||
if (mEglSurface != null) {
|
||||
// Ensure we always have a valid surface & context.
|
||||
mRunner.mEgl.eglMakeCurrent(mRunner.mEglDisplay, mRunner.mPBufferSurface,
|
||||
mRunner.mPBufferSurface, mRunner.mEglContext);
|
||||
mRunner.mEgl.eglDestroySurface(mRunner.mEglDisplay, mEglSurface);
|
||||
mEglSurface = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "stop failed", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
for (AnimatorListener l : this.getListeners()) {
|
||||
l.onAnimationEnd(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
try {
|
||||
mRunner.runOnGLThread(() -> {
|
||||
mIsRunning = false;
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "pause failed", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
try {
|
||||
mRunner.runOnGLThread(() -> {
|
||||
if (!mIsRunning) {
|
||||
long currentTime = System.nanoTime();
|
||||
mAnimationStartTime = currentTime - (long)(1000000 * mDuration * mProgress);
|
||||
mIsRunning = true;
|
||||
mNewSurface = true;
|
||||
doFrame(currentTime);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "resume failed", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add support for start delay
|
||||
@Override
|
||||
public long getStartDelay() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: add support for start delay
|
||||
@Override
|
||||
public void setStartDelay(long startDelay) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Animator setDuration(long duration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mIsRunning;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
return mDuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalDuration() {
|
||||
if (mRepeatCount == -1) {
|
||||
return DURATION_INFINITE;
|
||||
}
|
||||
// TODO: add start delay when implemented
|
||||
return mDuration * (1 + mRepeatCount);
|
||||
}
|
||||
|
||||
// TODO: support TimeInterpolators
|
||||
@Override
|
||||
public void setInterpolator(TimeInterpolator value) {
|
||||
|
||||
}
|
||||
|
||||
public void setProgress(float progress) {
|
||||
try {
|
||||
mRunner.runOnGLThread(() -> {
|
||||
mProgress = progress;
|
||||
if (mIsRunning) {
|
||||
mAnimationStartTime = System.nanoTime()
|
||||
- (long)(1000000 * mDuration * mProgress);
|
||||
}
|
||||
drawFrame();
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "setProgress failed", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
public float getProgress() {
|
||||
return mProgress;
|
||||
}
|
||||
|
||||
private void drawFrame() {
|
||||
try {
|
||||
boolean forceDraw = false;
|
||||
if (mNewSurface) {
|
||||
forceDraw = true;
|
||||
// if there is a new SurfaceTexture, we need to recreate the EGL surface.
|
||||
if (mEglSurface != null) {
|
||||
mRunner.mEgl.eglDestroySurface(mRunner.mEglDisplay, mEglSurface);
|
||||
mEglSurface = null;
|
||||
}
|
||||
mNewSurface = false;
|
||||
}
|
||||
|
||||
if (mEglSurface == null) {
|
||||
// block for Texture Views
|
||||
if (mSurfaceTexture != null) {
|
||||
mEglSurface = mRunner.mEgl.eglCreateWindowSurface(mRunner.mEglDisplay,
|
||||
mRunner.mEglConfig, mSurfaceTexture, null);
|
||||
checkSurface();
|
||||
// block for Surface Views
|
||||
} else if (mSurfaceHolder != null) {
|
||||
mEglSurface = mRunner.mEgl.eglCreateWindowSurface(mRunner.mEglDisplay,
|
||||
mRunner.mEglConfig, mSurfaceHolder, null);
|
||||
checkSurface();
|
||||
}
|
||||
}
|
||||
|
||||
if (mEglSurface != null) {
|
||||
if (!mRunner.mEgl.eglMakeCurrent(mRunner.mEglDisplay, mEglSurface, mEglSurface,
|
||||
mRunner.mEglContext)) {
|
||||
// If eglMakeCurrent failed, recreate EGL surface on next frame.
|
||||
Log.w(LOG_TAG, "eglMakeCurrent failed "
|
||||
+ GLUtils.getEGLErrorString(mRunner.mEgl.eglGetError()));
|
||||
mNewSurface = true;
|
||||
return;
|
||||
}
|
||||
// only if nDrawFrames() returns true do we need to swap buffers
|
||||
if(nDrawFrame(mNativeProxy, mSurfaceWidth, mSurfaceHeight, false,
|
||||
mProgress, mBackgroundColor, forceDraw)) {
|
||||
if (!mRunner.mEgl.eglSwapBuffers(mRunner.mEglDisplay, mEglSurface)) {
|
||||
int error = mRunner.mEgl.eglGetError();
|
||||
if (error == EGL10.EGL_BAD_SURFACE
|
||||
|| error == EGL10.EGL_BAD_NATIVE_WINDOW) {
|
||||
// For some reason our surface was destroyed. Recreate EGL surface
|
||||
// on next frame.
|
||||
mNewSurface = true;
|
||||
// This really shouldn't happen, but if it does we can recover
|
||||
// easily by just not trying to use the surface anymore
|
||||
Log.w(LOG_TAG, "swapBuffers failed "
|
||||
+ GLUtils.getEGLErrorString(error));
|
||||
return;
|
||||
}
|
||||
|
||||
// Some other fatal EGL error happened, log an error and stop the
|
||||
// animation.
|
||||
throw new RuntimeException("Cannot swap buffers "
|
||||
+ GLUtils.getEGLErrorString(error));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If animation stopped, release EGL surface.
|
||||
if (!mIsRunning) {
|
||||
// Ensure we always have a valid surface & context.
|
||||
mRunner.mEgl.eglMakeCurrent(mRunner.mEglDisplay, mRunner.mPBufferSurface,
|
||||
mRunner.mPBufferSurface, mRunner.mEglContext);
|
||||
mRunner.mEgl.eglDestroySurface(mRunner.mEglDisplay, mEglSurface);
|
||||
mEglSurface = null;
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "drawFrame failed", t);
|
||||
mIsRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkSurface() throws RuntimeException {
|
||||
// ensure eglSurface was created
|
||||
if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
|
||||
// If failed to create a surface, log an error and stop the animation
|
||||
int error = mRunner.mEgl.eglGetError();
|
||||
throw new RuntimeException("createWindowSurface failed "
|
||||
+ GLUtils.getEGLErrorString(error));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFrame(long frameTimeNanos) {
|
||||
if (mIsRunning) {
|
||||
// Schedule next frame.
|
||||
Choreographer.getInstance().postFrameCallback(this);
|
||||
|
||||
// Advance animation.
|
||||
long durationNS = mDuration * 1000000;
|
||||
long timeSinceAnimationStartNS = frameTimeNanos - mAnimationStartTime;
|
||||
long animationProgressNS = timeSinceAnimationStartNS % durationNS;
|
||||
mProgress = animationProgressNS / (float)durationNS;
|
||||
if (timeSinceAnimationStartNS > durationNS) {
|
||||
mAnimationStartTime += durationNS; // prevents overflow
|
||||
}
|
||||
if (timeSinceAnimationStartNS > durationNS) {
|
||||
if (mRepeatCounter > 0) {
|
||||
mRepeatCounter--;
|
||||
} else if (mRepeatCounter == 0) {
|
||||
mIsRunning = false;
|
||||
mProgress = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mValidSurface) {
|
||||
drawFrame();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
|
||||
// will be called on UI thread
|
||||
try {
|
||||
mRunner.runOnGLThread(() -> {
|
||||
mSurfaceTexture = surface;
|
||||
updateSurface(width, height);
|
||||
mValidSurface = true;
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "updateSurface failed", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
|
||||
// will be called on UI thread
|
||||
onSurfaceTextureAvailable(surface, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
|
||||
// will be called on UI thread
|
||||
onSurfaceTextureAvailable(null, 0, 0);
|
||||
mValidSurface = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
|
||||
|
||||
}
|
||||
|
||||
// Inherited from SurfaceHolder
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
try {
|
||||
mRunner.runOnGLThread(() -> {
|
||||
mSurfaceHolder = holder;
|
||||
updateSurface(width, height);
|
||||
mValidSurface = true;
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "updateSurface failed", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
mValidSurface = false;
|
||||
surfaceChanged(null, 0, 0, 0);
|
||||
}
|
||||
|
||||
private native long nCreateProxy(long runner, ByteBuffer data);
|
||||
private native void nDeleteProxy(long nativeProxy);
|
||||
private native boolean nDrawFrame(long nativeProxy, int width, int height,
|
||||
boolean wideColorGamut, float progress,
|
||||
int backgroundColor, boolean forceDraw);
|
||||
private native long nGetDuration(long nativeProxy);
|
||||
}
|
@ -7,26 +7,16 @@
|
||||
|
||||
package org.skia.skottie;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.opengl.GLUtils;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.util.Log;
|
||||
import android.view.Choreographer;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.TextureView;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
@ -48,11 +38,11 @@ class SkottieRunner {
|
||||
|
||||
private HandlerThread mGLThreadLooper;
|
||||
private Handler mGLThread;
|
||||
private EGL10 mEgl;
|
||||
private EGLDisplay mEglDisplay;
|
||||
private EGLConfig mEglConfig;
|
||||
private EGLContext mEglContext;
|
||||
private EGLSurface mPBufferSurface;
|
||||
EGL10 mEgl;
|
||||
EGLDisplay mEglDisplay;
|
||||
EGLConfig mEglConfig;
|
||||
EGLContext mEglContext;
|
||||
EGLSurface mPBufferSurface;
|
||||
private long mNativeProxy;
|
||||
|
||||
static {
|
||||
@ -128,7 +118,7 @@ class SkottieRunner {
|
||||
}
|
||||
}
|
||||
|
||||
private long getNativeProxy() { return mNativeProxy; }
|
||||
long getNativeProxy() { return mNativeProxy; }
|
||||
|
||||
private class RunSignalAndCatch implements Runnable {
|
||||
public Throwable error;
|
||||
@ -152,7 +142,7 @@ class SkottieRunner {
|
||||
}
|
||||
}
|
||||
|
||||
private void runOnGLThread(Runnable r) throws Throwable {
|
||||
void runOnGLThread(Runnable r) throws Throwable {
|
||||
runOnGLThread(r, false);
|
||||
}
|
||||
|
||||
@ -282,437 +272,6 @@ class SkottieRunner {
|
||||
}
|
||||
}
|
||||
|
||||
public class SkottieAnimation extends Animator implements Choreographer.FrameCallback,
|
||||
TextureView.SurfaceTextureListener, SurfaceHolder.Callback {
|
||||
boolean mIsRunning = false;
|
||||
SurfaceTexture mSurfaceTexture;
|
||||
EGLSurface mEglSurface;
|
||||
boolean mNewSurface = false;
|
||||
SurfaceHolder mSurfaceHolder;
|
||||
boolean mValidSurface = false;
|
||||
|
||||
private int mRepeatCount;
|
||||
private int mRepeatCounter;
|
||||
private int mSurfaceWidth = 0;
|
||||
private int mSurfaceHeight = 0;
|
||||
private int mBackgroundColor;
|
||||
private long mNativeProxy;
|
||||
private long mDuration; // duration in ms of the animation
|
||||
private float mProgress; // animation progress in the range of 0.0f to 1.0f
|
||||
private long mAnimationStartTime; // time in System.nanoTime units, when started
|
||||
|
||||
private SkottieAnimation(SurfaceTexture surfaceTexture, InputStream is) {
|
||||
if (init(is)) {
|
||||
mSurfaceTexture = surfaceTexture;
|
||||
}
|
||||
}
|
||||
private SkottieAnimation(TextureView view, InputStream is, int backgroundColor, int repeatCount) {
|
||||
if (init(is)) {
|
||||
mSurfaceTexture = view.getSurfaceTexture();
|
||||
}
|
||||
view.setSurfaceTextureListener(this);
|
||||
mBackgroundColor = backgroundColor;
|
||||
mRepeatCount = repeatCount;
|
||||
mRepeatCounter = mRepeatCount;
|
||||
}
|
||||
|
||||
private SkottieAnimation(SurfaceView view, InputStream is, int backgroundColor, int repeatCount) {
|
||||
if (init(is)) {
|
||||
mSurfaceHolder = view.getHolder();
|
||||
}
|
||||
mSurfaceHolder.addCallback(this);
|
||||
mBackgroundColor = backgroundColor;
|
||||
mRepeatCount = repeatCount;
|
||||
mRepeatCounter = mRepeatCount;
|
||||
}
|
||||
|
||||
private void setSurfaceTexture(SurfaceTexture s) {
|
||||
mSurfaceTexture = s;
|
||||
}
|
||||
|
||||
private ByteBuffer convertToByteBuffer(InputStream is) throws IOException {
|
||||
if (is instanceof FileInputStream) {
|
||||
FileChannel fileChannel = ((FileInputStream)is).getChannel();
|
||||
return fileChannel.map(FileChannel.MapMode.READ_ONLY,
|
||||
fileChannel.position(), fileChannel.size());
|
||||
}
|
||||
|
||||
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
|
||||
byte[] tmpStorage = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = is.read(tmpStorage, 0, tmpStorage.length)) != -1) {
|
||||
byteStream.write(tmpStorage, 0, bytesRead);
|
||||
}
|
||||
|
||||
byteStream.flush();
|
||||
tmpStorage = byteStream.toByteArray();
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(tmpStorage.length);
|
||||
buffer.order(ByteOrder.nativeOrder());
|
||||
buffer.put(tmpStorage, 0, tmpStorage.length);
|
||||
return buffer.asReadOnlyBuffer();
|
||||
}
|
||||
|
||||
private boolean init(InputStream is) {
|
||||
|
||||
ByteBuffer byteBuffer;
|
||||
try {
|
||||
byteBuffer = convertToByteBuffer(is);
|
||||
} catch (IOException e) {
|
||||
Log.e(LOG_TAG, "failed to read input stream", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
long proxy = SkottieRunner.getInstance().getNativeProxy();
|
||||
mNativeProxy = nCreateProxy(proxy, byteBuffer);
|
||||
mDuration = nGetDuration(mNativeProxy);
|
||||
mProgress = 0f;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
end();
|
||||
nDeleteProxy(mNativeProxy);
|
||||
mNativeProxy = 0;
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
// Always call this on GL thread
|
||||
public void updateSurface(int width, int height) {
|
||||
mSurfaceWidth = width;
|
||||
mSurfaceHeight = height;
|
||||
mNewSurface = true;
|
||||
drawFrame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
try {
|
||||
runOnGLThread(() -> {
|
||||
if (!mIsRunning) {
|
||||
long currentTime = System.nanoTime();
|
||||
mAnimationStartTime = currentTime - (long)(1000000 * mDuration * mProgress);
|
||||
mIsRunning = true;
|
||||
mNewSurface = true;
|
||||
mRepeatCounter = mRepeatCount;
|
||||
doFrame(currentTime);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "start failed", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
for (AnimatorListener l : this.getListeners()) {
|
||||
l.onAnimationStart(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
try {
|
||||
runOnGLThread(() -> {
|
||||
mIsRunning = false;
|
||||
if (mEglSurface != null) {
|
||||
// Ensure we always have a valid surface & context.
|
||||
mEgl.eglMakeCurrent(mEglDisplay, mPBufferSurface, mPBufferSurface,
|
||||
mEglContext);
|
||||
mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
|
||||
mEglSurface = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "stop failed", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
for (AnimatorListener l : this.getListeners()) {
|
||||
l.onAnimationEnd(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
try {
|
||||
runOnGLThread(() -> {
|
||||
mIsRunning = false;
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "pause failed", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
try {
|
||||
runOnGLThread(() -> {
|
||||
if (!mIsRunning) {
|
||||
long currentTime = System.nanoTime();
|
||||
mAnimationStartTime = currentTime - (long)(1000000 * mDuration * mProgress);
|
||||
mIsRunning = true;
|
||||
mNewSurface = true;
|
||||
doFrame(currentTime);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "resume failed", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add support for start delay
|
||||
@Override
|
||||
public long getStartDelay() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: add support for start delay
|
||||
@Override
|
||||
public void setStartDelay(long startDelay) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Animator setDuration(long duration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mIsRunning;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
return mDuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalDuration() {
|
||||
if (mRepeatCount == -1) {
|
||||
return DURATION_INFINITE;
|
||||
}
|
||||
// TODO: add start delay when implemented
|
||||
return mDuration * (1 + mRepeatCount);
|
||||
}
|
||||
|
||||
// TODO: support TimeInterpolators
|
||||
@Override
|
||||
public void setInterpolator(TimeInterpolator value) {
|
||||
|
||||
}
|
||||
|
||||
public void setProgress(float progress) {
|
||||
try {
|
||||
runOnGLThread(() -> {
|
||||
mProgress = progress;
|
||||
if (mIsRunning) {
|
||||
mAnimationStartTime = System.nanoTime()
|
||||
- (long)(1000000 * mDuration * mProgress);
|
||||
}
|
||||
drawFrame();
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "setProgress failed", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
public float getProgress() {
|
||||
return mProgress;
|
||||
}
|
||||
|
||||
private void drawFrame() {
|
||||
try {
|
||||
boolean forceDraw = false;
|
||||
if (mNewSurface) {
|
||||
forceDraw = true;
|
||||
// if there is a new SurfaceTexture, we need to recreate the EGL surface.
|
||||
if (mEglSurface != null) {
|
||||
mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
|
||||
mEglSurface = null;
|
||||
}
|
||||
mNewSurface = false;
|
||||
}
|
||||
|
||||
if (mEglSurface == null) {
|
||||
// block for Texture Views
|
||||
if (mSurfaceTexture != null) {
|
||||
mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig,
|
||||
mSurfaceTexture, null);
|
||||
checkSurface();
|
||||
// block for Surface Views
|
||||
} else if (mSurfaceHolder != null) {
|
||||
mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig,
|
||||
mSurfaceHolder, null);
|
||||
checkSurface();
|
||||
}
|
||||
}
|
||||
|
||||
if (mEglSurface != null) {
|
||||
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
|
||||
// If eglMakeCurrent failed, recreate EGL surface on next frame.
|
||||
Log.w(LOG_TAG, "eglMakeCurrent failed "
|
||||
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
|
||||
mNewSurface = true;
|
||||
return;
|
||||
}
|
||||
// only if nDrawFrames() returns true do we need to swap buffers
|
||||
if(nDrawFrame(mNativeProxy, mSurfaceWidth, mSurfaceHeight, false,
|
||||
mProgress, mBackgroundColor, forceDraw)) {
|
||||
if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
|
||||
int error = mEgl.eglGetError();
|
||||
if (error == EGL10.EGL_BAD_SURFACE
|
||||
|| error == EGL10.EGL_BAD_NATIVE_WINDOW) {
|
||||
// For some reason our surface was destroyed. Recreate EGL surface
|
||||
// on next frame.
|
||||
mNewSurface = true;
|
||||
// This really shouldn't happen, but if it does we can recover
|
||||
// easily by just not trying to use the surface anymore
|
||||
Log.w(LOG_TAG, "swapBuffers failed "
|
||||
+ GLUtils.getEGLErrorString(error));
|
||||
return;
|
||||
}
|
||||
|
||||
// Some other fatal EGL error happened, log an error and stop the
|
||||
// animation.
|
||||
throw new RuntimeException("Cannot swap buffers "
|
||||
+ GLUtils.getEGLErrorString(error));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If animation stopped, release EGL surface.
|
||||
if (!mIsRunning) {
|
||||
// Ensure we always have a valid surface & context.
|
||||
mEgl.eglMakeCurrent(mEglDisplay, mPBufferSurface, mPBufferSurface,
|
||||
mEglContext);
|
||||
mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
|
||||
mEglSurface = null;
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "drawFrame failed", t);
|
||||
mIsRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkSurface() throws RuntimeException {
|
||||
// ensure eglSurface was created
|
||||
if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
|
||||
// If failed to create a surface, log an error and stop the animation
|
||||
int error = mEgl.eglGetError();
|
||||
throw new RuntimeException("createWindowSurface failed "
|
||||
+ GLUtils.getEGLErrorString(error));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFrame(long frameTimeNanos) {
|
||||
if (mIsRunning) {
|
||||
// Schedule next frame.
|
||||
Choreographer.getInstance().postFrameCallback(this);
|
||||
|
||||
// Advance animation.
|
||||
long durationNS = mDuration * 1000000;
|
||||
long timeSinceAnimationStartNS = frameTimeNanos - mAnimationStartTime;
|
||||
long animationProgressNS = timeSinceAnimationStartNS % durationNS;
|
||||
mProgress = animationProgressNS / (float)durationNS;
|
||||
if (timeSinceAnimationStartNS > durationNS) {
|
||||
mAnimationStartTime += durationNS; // prevents overflow
|
||||
}
|
||||
if (timeSinceAnimationStartNS > durationNS) {
|
||||
if (mRepeatCounter > 0) {
|
||||
mRepeatCounter--;
|
||||
} else if (mRepeatCounter == 0) {
|
||||
mIsRunning = false;
|
||||
mProgress = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mValidSurface) {
|
||||
drawFrame();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
|
||||
// will be called on UI thread
|
||||
mValidSurface = true;
|
||||
try {
|
||||
runOnGLThread(() -> {
|
||||
mSurfaceTexture = surface;
|
||||
updateSurface(width, height);
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "updateSurface failed", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
|
||||
// will be called on UI thread
|
||||
onSurfaceTextureAvailable(surface, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
|
||||
// will be called on UI thread
|
||||
onSurfaceTextureAvailable(null, 0, 0);
|
||||
mValidSurface = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
|
||||
|
||||
}
|
||||
|
||||
// Inherited from SurfaceHolder
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
mValidSurface = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
mValidSurface = true;
|
||||
try {
|
||||
runOnGLThread(() -> {
|
||||
mSurfaceHolder = holder;
|
||||
updateSurface(width, height);
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Log.e(LOG_TAG, "updateSurface failed", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
mValidSurface = false;
|
||||
surfaceChanged(null, 0, 0, 0);
|
||||
}
|
||||
|
||||
private native long nCreateProxy(long runner, ByteBuffer data);
|
||||
private native void nDeleteProxy(long nativeProxy);
|
||||
private native boolean nDrawFrame(long nativeProxy, int width, int height,
|
||||
boolean wideColorGamut, float progress,
|
||||
int backgroundColor, boolean forceDraw);
|
||||
private native long nGetDuration(long nativeProxy);
|
||||
}
|
||||
|
||||
private static native long nCreateProxy();
|
||||
private static native void nDeleteProxy(long nativeProxy);
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import org.skia.skottie.SkottieRunner.SkottieAnimation;
|
||||
import org.skia.skottielib.R;
|
||||
|
||||
public class SkottieView extends FrameLayout {
|
||||
|
Loading…
Reference in New Issue
Block a user