1 package org.skia.skottie; 2 3 import android.animation.Animator; 4 import android.animation.TimeInterpolator; 5 import android.graphics.SurfaceTexture; 6 import android.opengl.GLUtils; 7 import android.util.Log; 8 import android.view.Choreographer; 9 import android.view.SurfaceHolder; 10 import android.view.SurfaceView; 11 import android.view.TextureView; 12 13 import java.io.ByteArrayOutputStream; 14 import java.io.FileInputStream; 15 import java.io.IOException; 16 import java.io.InputStream; 17 import java.nio.ByteBuffer; 18 import java.nio.ByteOrder; 19 import java.nio.channels.FileChannel; 20 21 import javax.microedition.khronos.egl.EGL10; 22 import javax.microedition.khronos.egl.EGLSurface; 23 24 public class SkottieAnimation extends Animator implements Choreographer.FrameCallback, 25 TextureView.SurfaceTextureListener, SurfaceHolder.Callback { 26 class Config { 27 int mSurfaceWidth; 28 int mSurfaceHeight; 29 boolean mValidSurface; 30 } 31 32 private final SkottieRunner mRunner = SkottieRunner.getInstance(); 33 private static final String LOG_TAG = "SkottiePlayer"; 34 35 private boolean mIsRunning = false; 36 private SurfaceTexture mSurfaceTexture; 37 private EGLSurface mEglSurface; 38 private boolean mNewSurface = false; 39 private SurfaceHolder mSurfaceHolder; 40 private int mRepeatCount; 41 private int mRepeatCounter; 42 private Config config = new Config(); 43 private int mBackgroundColor; 44 private long mNativeProxy; 45 private long mDuration; // duration in ms of the animation 46 private float mProgress; // animation progress in the range of 0.0f to 1.0f 47 private long mAnimationStartTime; // time in System.nanoTime units, when started 48 SkottieAnimation(SurfaceTexture surfaceTexture, InputStream is)49 SkottieAnimation(SurfaceTexture surfaceTexture, InputStream is) { 50 if (init(is)) { 51 mSurfaceTexture = surfaceTexture; 52 } 53 } SkottieAnimation(TextureView view, InputStream is, int backgroundColor, int repeatCount)54 SkottieAnimation(TextureView view, InputStream is, int backgroundColor, int repeatCount) { 55 if (init(is)) { 56 mSurfaceTexture = view.getSurfaceTexture(); 57 } 58 view.setSurfaceTextureListener(this); 59 mBackgroundColor = backgroundColor; 60 mRepeatCount = repeatCount; 61 mRepeatCounter = mRepeatCount; 62 } 63 SkottieAnimation(SurfaceView view, InputStream is, int backgroundColor, int repeatCount)64 SkottieAnimation(SurfaceView view, InputStream is, int backgroundColor, int repeatCount) { 65 if (init(is)) { 66 mSurfaceHolder = view.getHolder(); 67 } 68 mSurfaceHolder.addCallback(this); 69 mBackgroundColor = backgroundColor; 70 mRepeatCount = repeatCount; 71 mRepeatCounter = mRepeatCount; 72 } 73 setSurfaceTexture(SurfaceTexture s)74 void setSurfaceTexture(SurfaceTexture s) { 75 mSurfaceTexture = s; 76 } 77 convertToByteBuffer(InputStream is)78 private ByteBuffer convertToByteBuffer(InputStream is) throws IOException { 79 if (is instanceof FileInputStream) { 80 FileChannel fileChannel = ((FileInputStream)is).getChannel(); 81 return fileChannel.map(FileChannel.MapMode.READ_ONLY, 82 fileChannel.position(), fileChannel.size()); 83 } 84 85 ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 86 byte[] tmpStorage = new byte[4096]; 87 int bytesRead; 88 while ((bytesRead = is.read(tmpStorage, 0, tmpStorage.length)) != -1) { 89 byteStream.write(tmpStorage, 0, bytesRead); 90 } 91 92 byteStream.flush(); 93 tmpStorage = byteStream.toByteArray(); 94 95 ByteBuffer buffer = ByteBuffer.allocateDirect(tmpStorage.length); 96 buffer.order(ByteOrder.nativeOrder()); 97 buffer.put(tmpStorage, 0, tmpStorage.length); 98 return buffer.asReadOnlyBuffer(); 99 } 100 init(InputStream is)101 private boolean init(InputStream is) { 102 103 ByteBuffer byteBuffer; 104 try { 105 byteBuffer = convertToByteBuffer(is); 106 } catch (IOException e) { 107 Log.e(LOG_TAG, "failed to read input stream", e); 108 return false; 109 } 110 111 long proxy = mRunner.getNativeProxy(); 112 mNativeProxy = nCreateProxy(proxy, byteBuffer); 113 mDuration = nGetDuration(mNativeProxy); 114 mProgress = 0f; 115 return true; 116 } 117 notifyAnimationEnd()118 private void notifyAnimationEnd() { 119 if (this.getListeners() != null) { 120 for (AnimatorListener l : this.getListeners()) { 121 l.onAnimationEnd(this); 122 } 123 } 124 } 125 126 @Override finalize()127 protected void finalize() throws Throwable { 128 try { 129 end(); 130 nDeleteProxy(mNativeProxy); 131 mNativeProxy = 0; 132 } finally { 133 super.finalize(); 134 } 135 } 136 137 // Always call this on GL thread updateSurface(int width, int height)138 public void updateSurface(int width, int height) { 139 config.mSurfaceWidth = width; 140 config.mSurfaceHeight = height; 141 mNewSurface = true; 142 drawFrame(); 143 } 144 145 @Override start()146 public void start() { 147 try { 148 mRunner.runOnGLThread(() -> { 149 if (!mIsRunning) { 150 long currentTime = System.nanoTime(); 151 mAnimationStartTime = currentTime - (long)(1000000 * mDuration * mProgress); 152 mIsRunning = true; 153 mNewSurface = true; 154 mRepeatCounter = mRepeatCount; 155 doFrame(currentTime); 156 } 157 }); 158 } 159 catch (Throwable t) { 160 Log.e(LOG_TAG, "start failed", t); 161 throw new RuntimeException(t); 162 } 163 if (this.getListeners() != null) { 164 for (AnimatorListener l : this.getListeners()) { 165 l.onAnimationStart(this); 166 } 167 } 168 } 169 170 @Override end()171 public void end() { 172 try { 173 mRunner.runOnGLThread(() -> { 174 mIsRunning = false; 175 if (mEglSurface != null) { 176 // Ensure we always have a valid surface & context. 177 mRunner.mEgl.eglMakeCurrent(mRunner.mEglDisplay, mRunner.mPBufferSurface, 178 mRunner.mPBufferSurface, mRunner.mEglContext); 179 mRunner.mEgl.eglDestroySurface(mRunner.mEglDisplay, mEglSurface); 180 mEglSurface = null; 181 } 182 }); 183 } 184 catch (Throwable t) { 185 Log.e(LOG_TAG, "stop failed", t); 186 throw new RuntimeException(t); 187 } 188 notifyAnimationEnd(); 189 } 190 191 @Override pause()192 public void pause() { 193 try { 194 mRunner.runOnGLThread(() -> { 195 mIsRunning = false; 196 }); 197 } 198 catch (Throwable t) { 199 Log.e(LOG_TAG, "pause failed", t); 200 throw new RuntimeException(t); 201 } 202 } 203 204 @Override resume()205 public void resume() { 206 try { 207 mRunner.runOnGLThread(() -> { 208 if (!mIsRunning) { 209 long currentTime = System.nanoTime(); 210 mAnimationStartTime = currentTime - (long)(1000000 * mDuration * mProgress); 211 mIsRunning = true; 212 mNewSurface = true; 213 doFrame(currentTime); 214 } 215 }); 216 } 217 catch (Throwable t) { 218 Log.e(LOG_TAG, "resume failed", t); 219 throw new RuntimeException(t); 220 } 221 } 222 223 // TODO: add support for start delay 224 @Override getStartDelay()225 public long getStartDelay() { 226 return 0; 227 } 228 229 // TODO: add support for start delay 230 @Override setStartDelay(long startDelay)231 public void setStartDelay(long startDelay) { 232 233 } 234 235 @Override setDuration(long duration)236 public Animator setDuration(long duration) { 237 return null; 238 } 239 240 @Override isRunning()241 public boolean isRunning() { 242 return mIsRunning; 243 } 244 245 @Override getDuration()246 public long getDuration() { 247 return mDuration; 248 } 249 250 @Override getTotalDuration()251 public long getTotalDuration() { 252 if (mRepeatCount == -1) { 253 return DURATION_INFINITE; 254 } 255 // TODO: add start delay when implemented 256 return mDuration * (1 + mRepeatCount); 257 } 258 259 // TODO: support TimeInterpolators 260 @Override setInterpolator(TimeInterpolator value)261 public void setInterpolator(TimeInterpolator value) { 262 263 } 264 setProgress(float progress)265 public void setProgress(float progress) { 266 try { 267 mRunner.runOnGLThread(() -> { 268 mProgress = progress; 269 if (mIsRunning) { 270 mAnimationStartTime = System.nanoTime() 271 - (long)(1000000 * mDuration * mProgress); 272 } 273 drawFrame(); 274 }); 275 } 276 catch (Throwable t) { 277 Log.e(LOG_TAG, "setProgress failed", t); 278 throw new RuntimeException(t); 279 } 280 } 281 getProgress()282 public float getProgress() { 283 return mProgress; 284 } 285 drawFrame()286 private void drawFrame() { 287 try { 288 boolean forceDraw = false; 289 if (mNewSurface) { 290 forceDraw = true; 291 // if there is a new SurfaceTexture, we need to recreate the EGL surface. 292 if (mEglSurface != null) { 293 mRunner.mEgl.eglDestroySurface(mRunner.mEglDisplay, mEglSurface); 294 mEglSurface = null; 295 } 296 mNewSurface = false; 297 } 298 299 if (mEglSurface == null) { 300 // block for Texture Views 301 if (mSurfaceTexture != null) { 302 mEglSurface = mRunner.mEgl.eglCreateWindowSurface(mRunner.mEglDisplay, 303 mRunner.mEglConfig, mSurfaceTexture, null); 304 checkSurface(); 305 // block for Surface Views 306 } else if (mSurfaceHolder != null) { 307 mEglSurface = mRunner.mEgl.eglCreateWindowSurface(mRunner.mEglDisplay, 308 mRunner.mEglConfig, mSurfaceHolder, null); 309 checkSurface(); 310 } 311 } 312 313 if (mEglSurface != null) { 314 if (!mRunner.mEgl.eglMakeCurrent(mRunner.mEglDisplay, mEglSurface, mEglSurface, 315 mRunner.mEglContext)) { 316 // If eglMakeCurrent failed, recreate EGL surface on next frame. 317 Log.w(LOG_TAG, "eglMakeCurrent failed " 318 + GLUtils.getEGLErrorString(mRunner.mEgl.eglGetError())); 319 mNewSurface = true; 320 return; 321 } 322 // only if nDrawFrames() returns true do we need to swap buffers 323 if(nDrawFrame(mNativeProxy, config.mSurfaceWidth, config.mSurfaceHeight, false, 324 mProgress, mBackgroundColor, forceDraw)) { 325 if (!mRunner.mEgl.eglSwapBuffers(mRunner.mEglDisplay, mEglSurface)) { 326 int error = mRunner.mEgl.eglGetError(); 327 if (error == EGL10.EGL_BAD_SURFACE 328 || error == EGL10.EGL_BAD_NATIVE_WINDOW) { 329 // For some reason our surface was destroyed. Recreate EGL surface 330 // on next frame. 331 mNewSurface = true; 332 // This really shouldn't happen, but if it does we can recover 333 // easily by just not trying to use the surface anymore 334 Log.w(LOG_TAG, "swapBuffers failed " 335 + GLUtils.getEGLErrorString(error)); 336 return; 337 } 338 339 // Some other fatal EGL error happened, log an error and stop the 340 // animation. 341 throw new RuntimeException("Cannot swap buffers " 342 + GLUtils.getEGLErrorString(error)); 343 } 344 } 345 346 347 // If animation stopped, release EGL surface. 348 if (!mIsRunning) { 349 // Ensure we always have a valid surface & context. 350 mRunner.mEgl.eglMakeCurrent(mRunner.mEglDisplay, mRunner.mPBufferSurface, 351 mRunner.mPBufferSurface, mRunner.mEglContext); 352 mRunner.mEgl.eglDestroySurface(mRunner.mEglDisplay, mEglSurface); 353 mEglSurface = null; 354 } 355 } 356 } catch (Throwable t) { 357 Log.e(LOG_TAG, "drawFrame failed", t); 358 mIsRunning = false; 359 } 360 } 361 checkSurface()362 private void checkSurface() throws RuntimeException { 363 // ensure eglSurface was created 364 if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { 365 // If failed to create a surface, log an error and stop the animation 366 int error = mRunner.mEgl.eglGetError(); 367 throw new RuntimeException("createWindowSurface failed " 368 + GLUtils.getEGLErrorString(error)); 369 } 370 } 371 372 @Override doFrame(long frameTimeNanos)373 public void doFrame(long frameTimeNanos) { 374 if (mIsRunning) { 375 // Schedule next frame. 376 Choreographer.getInstance().postFrameCallback(this); 377 378 // Advance animation. 379 long durationNS = mDuration * 1000000; 380 long timeSinceAnimationStartNS = frameTimeNanos - mAnimationStartTime; 381 long animationProgressNS = timeSinceAnimationStartNS % durationNS; 382 mProgress = animationProgressNS / (float)durationNS; 383 if (timeSinceAnimationStartNS > durationNS) { 384 mAnimationStartTime += durationNS; // prevents overflow 385 } 386 if (timeSinceAnimationStartNS > durationNS) { 387 if (mRepeatCounter > 0) { 388 mRepeatCounter--; 389 } else if (mRepeatCounter == 0) { 390 mIsRunning = false; 391 mProgress = 1; 392 notifyAnimationEnd(); 393 } 394 } 395 } 396 if (config.mValidSurface) { 397 drawFrame(); 398 } 399 } 400 401 @Override onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)402 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 403 // will be called on UI thread 404 try { 405 mRunner.runOnGLThread(() -> { 406 mSurfaceTexture = surface; 407 updateSurface(width, height); 408 config.mValidSurface = true; 409 }); 410 } 411 catch (Throwable t) { 412 Log.e(LOG_TAG, "updateSurface failed", t); 413 throw new RuntimeException(t); 414 } 415 } 416 417 @Override onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)418 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 419 // will be called on UI thread 420 onSurfaceTextureAvailable(surface, width, height); 421 } 422 423 @Override onSurfaceTextureDestroyed(SurfaceTexture surface)424 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 425 // will be called on UI thread 426 onSurfaceTextureAvailable(null, 0, 0); 427 config.mValidSurface = false; 428 return true; 429 } 430 431 @Override onSurfaceTextureUpdated(SurfaceTexture surfaceTexture)432 public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { 433 434 } 435 436 // Inherited from SurfaceHolder 437 @Override surfaceCreated(SurfaceHolder holder)438 public void surfaceCreated(SurfaceHolder holder) { 439 } 440 441 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)442 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 443 try { 444 mRunner.runOnGLThread(() -> { 445 mSurfaceHolder = holder; 446 updateSurface(width, height); 447 config.mValidSurface = true; 448 }); 449 } 450 catch (Throwable t) { 451 Log.e(LOG_TAG, "updateSurface failed", t); 452 throw new RuntimeException(t); 453 } 454 } 455 456 @Override surfaceDestroyed(SurfaceHolder holder)457 public void surfaceDestroyed(SurfaceHolder holder) { 458 config.mValidSurface = false; 459 surfaceChanged(null, 0, 0, 0); 460 } 461 getBackingViewConfig()462 Config getBackingViewConfig() { 463 return config; 464 } 465 setBackingViewConfig(Config config)466 void setBackingViewConfig(Config config) { 467 this.config.mSurfaceHeight = config.mSurfaceHeight; 468 this.config.mSurfaceWidth = config.mSurfaceWidth; 469 this.config.mValidSurface = config.mValidSurface; 470 } 471 nCreateProxy(long runner, ByteBuffer data)472 private native long nCreateProxy(long runner, ByteBuffer data); nDeleteProxy(long nativeProxy)473 private native void nDeleteProxy(long nativeProxy); nDrawFrame(long nativeProxy, int width, int height, boolean wideColorGamut, float progress, int backgroundColor, boolean forceDraw)474 private native boolean nDrawFrame(long nativeProxy, int width, int height, 475 boolean wideColorGamut, float progress, 476 int backgroundColor, boolean forceDraw); nGetDuration(long nativeProxy)477 private native long nGetDuration(long nativeProxy); 478 } 479