• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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