AnimatedImageCapture.java 8.38 KB
package com.cnlive.demo.stream;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.net.Uri;
import android.util.Log;

import com.cnlive.libs.stream.Streamer;
import com.cnlive.libs.stream.capture.CNImgTexSrcPin;
import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.BaseDataSubscriber;
import com.facebook.datasource.DataSource;
import com.facebook.datasource.DataSubscriber;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.animated.base.AnimatedDrawableFrameInfo;
import com.facebook.imagepipeline.animated.base.AnimatedImage;
import com.facebook.imagepipeline.animated.base.AnimatedImageFrame;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.image.CloseableAnimatedImage;
import com.facebook.imagepipeline.image.CloseableBitmap;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.stream.Stream;

/**
 * Created by Lynn on 2017/9/19.
 */

public class AnimatedImageCapture {
    private static final String TAG = "AnimatedImageCapture";
    private static final boolean VERBOSE = false;

    private int mIndex;
    private Date mLastDate;
    private int mRepeatCount;   // TODO: handle gif/webp repeat count
    private Bitmap mBitmap;
    private Canvas mCanvas;
    private Bitmap mTempBitmap;
    private Timer mTimer;
    private CloseableReference<? extends CloseableImage> mCloseableReference;

    private Streamer mStreamer;

    private CNImgTexSrcPin cnImgTexSrcPin;
    private Context context;

    public AnimatedImageCapture(Context context, Streamer streamer) {
        this.context = context;
        this.mStreamer = streamer;
        cnImgTexSrcPin = new CNImgTexSrcPin(streamer.getGLRender());
    }

    public void showAnimatedWaterMard(String url, float x, float y, float w, float h, float a) {
        cnImgTexSrcPin.connect(mStreamer.getImgTexPreviewMixer().getSinkPin(3));
        cnImgTexSrcPin.connect(mStreamer.getImgTexMixer().getSinkPin(3));
        mStreamer.getImgTexPreviewMixer().setRenderRect(3, x, y, w, h, a);
        mStreamer.getImgTexMixer().setRenderRect(3, x, y, w, h, a);
        start(context, url);
    }

    public void hideAnimatedWaterMark() {
        stop();
        cnImgTexSrcPin.disconnect(false);
    }

    public void start(Context context, String url) {
        if (url == null) {
            return;
        }

        Uri uri = Uri.parse(assets2Asset(url));
        ImagePipeline imagePipeline = Fresco.getImagePipeline();
        ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri).build();
        DataSource<CloseableReference<CloseableImage>> dataSource =
                imagePipeline.fetchDecodedImage(request, context);
        dataSource.subscribe(mDataSubscriber, CallerThreadExecutor.getInstance());
    }

    private String assets2Asset(String url) {
        if (url.startsWith("assets://")) {
            url = url.replaceFirst("assets://", "asset:///");
        }
        return url;
    }

    public void stop() {
        if (mTimer != null) {
            mTimer.cancel();
            mTimer = null;
        }
        if (mCloseableReference != null) {
            CloseableReference.closeSafely(mCloseableReference);
            mCloseableReference = null;
        }
        cnImgTexSrcPin.updateFrame(null, false);
    }

    private void updateFrame() {
        if (mCloseableReference == null) {
            return;
        }
        try {
            CloseableImage closeableImage = mCloseableReference.get();
            if (closeableImage instanceof CloseableBitmap) {
                Bitmap bitmap = ((CloseableBitmap) closeableImage).getUnderlyingBitmap();
                if (bitmap != null && !bitmap.isRecycled()) {
                    mBitmap = Bitmap.createBitmap(bitmap);
                    cnImgTexSrcPin.updateFrame(mBitmap, false);
                }
                return;
            }

            if (!(closeableImage instanceof CloseableAnimatedImage)) {
                return;
            }
            AnimatedImage animatedImage = ((CloseableAnimatedImage) closeableImage).getImage();
            int w = animatedImage.getWidth();
            int h = animatedImage.getHeight();
            if (mBitmap == null || mBitmap.isRecycled()) {
                Log.d(TAG, "Got animated image " + w + "x" + h);
                mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
                mTempBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
                mCanvas = new Canvas(mBitmap);
            }
            if (mIndex >= animatedImage.getFrameCount()) {
                mRepeatCount++;
                mIndex = 0;
                int loopCount = animatedImage.getLoopCount();
                if (loopCount > 0 && mRepeatCount >= loopCount) {
                    Log.d(TAG, "repeat ended, repeat times: " + mRepeatCount);
                    return;
                }
            }

            AnimatedImageFrame imageFrame = animatedImage.getFrame(mIndex);
            int duration = imageFrame.getDurationMs();
            AnimatedDrawableFrameInfo frameInfo = animatedImage.getFrameInfo(mIndex);
            imageFrame.renderFrame(frameInfo.width, frameInfo.height, mTempBitmap);
            if (VERBOSE) {
                Log.d(TAG, "blend: " + frameInfo.blendOperation.name() +
                        " dispose: " + frameInfo.disposalMethod.name() +
                        " x=" + frameInfo.xOffset + " y=" + frameInfo.yOffset +
                        " " + frameInfo.width + "x" + frameInfo.height);
            }
            if (frameInfo.blendOperation == AnimatedDrawableFrameInfo.BlendOperation.NO_BLEND) {
                mBitmap.eraseColor(Color.TRANSPARENT);
            }
            Rect srcRect = new Rect(0, 0, frameInfo.width, frameInfo.height);
            Rect dstRect = new Rect(frameInfo.xOffset, frameInfo.yOffset,
                    frameInfo.xOffset + frameInfo.width, frameInfo.yOffset + frameInfo.height);
            mCanvas.drawBitmap(mTempBitmap, srcRect, dstRect, null);
            if (VERBOSE) {
                Log.d(TAG, "frame " + mIndex + " got, duration=" + duration);
            }
            cnImgTexSrcPin.updateFrame(mBitmap, false);
            imageFrame.dispose();

            mIndex++;
            if (mLastDate == null) {
                mLastDate = new Date();
            }
            mLastDate = new Date(mLastDate.getTime() + duration);
            mTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    updateFrame();
                }
            }, mLastDate);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private DataSubscriber mDataSubscriber =
            new BaseDataSubscriber<CloseableReference<? extends CloseableImage>>() {

                @Override
                public void onNewResultImpl(
                        DataSource<CloseableReference<? extends CloseableImage>> dataSource) {
                    if (!dataSource.isFinished()) {
                        return;
                    }

                    // get ref
                    mCloseableReference = dataSource.getResult();

                    // reset
                    mIndex = 0;
                    mRepeatCount = 0;
                    mLastDate = null;
                    mBitmap = null;
                    mTempBitmap = null;
                    mCanvas = null;

                    // create timer
                    mTimer = new Timer("AnimatedImage");
                    mTimer.schedule(new TimerTask() {
                        @Override
                        public void run() {
                            updateFrame();
                        }
                    }, 0);
                }

                @Override
                public void onFailureImpl(DataSource dataSource) {
                    Throwable throwable = dataSource.getFailureCause();
                    // handle failure
                    if (throwable != null) {
                        throwable.printStackTrace();
                    }
                }
            };
}