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