/*
* Copyright 2013 Sony Corporation
*/
package com.example.sony.cameraremote;
import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.example.sony.cameraremote.utils.SimpleLiveviewSlicer;
import com.example.sony.cameraremote.utils.SimpleLiveviewSlicer.Payload;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* SimpleLiveviewSurfaceView.
*/
public class SimpleLiveviewSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
/** Remote APIインスタンス. */
private SimpleRemoteApi mRemoteApi;
/** View生成・破壊フラグ. */
private boolean mWhileFetching;
/** jpegデータ. */
private final BlockingQueue<byte[]> mJpegQueue = new ArrayBlockingQueue<byte[]>(2);
/** ミュート可否フラグ. */
private final boolean mInMutableAvailable = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
/** ビューの内容描画用スレッド. */
private Thread mDrawerThread;
/** 横幅. */
private int mPreviousWidth = 0;
/** 縦幅. */
private int mPreviousHeight = 0;
/** フレームペイントインスタンス. */
private final Paint mFramePaint;
/**
* Contractor.
*
* @param context コンテクスト
*/
public SimpleLiveviewSurfaceView(final Context context) {
super(context);
getHolder().addCallback(this);
mFramePaint = new Paint();
mFramePaint.setDither(true);
}
/**
* Contractor.
*
* @param context コンテクスト
* @param attrs アトリビュート
*/
public SimpleLiveviewSurfaceView(final Context context, final AttributeSet attrs) {
super(context, attrs);
getHolder().addCallback(this);
mFramePaint = new Paint();
mFramePaint.setDither(true);
}
/**
* Contractor.
*
* @param context コンテクスト
* @param attrs アトリビュート
* @param defStyle デフォルトスタイル
*/
public SimpleLiveviewSurfaceView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
getHolder().addCallback(this);
mFramePaint = new Paint();
mFramePaint.setDither(true);
}
@Override
public void surfaceChanged(final SurfaceHolder holder, final int format, final int width, final int height) {
// do nothing.
}
@Override
public void surfaceCreated(final SurfaceHolder holder) {
// do nothing.
}
@Override
public void surfaceDestroyed(final SurfaceHolder holder) {
mWhileFetching = false;
}
/**
* Bind a Remote API object to communicate with Camera device. Need to call
* this method before calling start() method.
*
* @param remoteApi リモートAPIインスタンス
*/
public void bindRemoteApi(final SimpleRemoteApi remoteApi) {
mRemoteApi = remoteApi;
}
/**
* Start retrieving and drawing liveview frame data by new threads.
*
* @return true if the starting is completed successfully, false otherwise.
* @see SimpleLiveviewSurfaceView#bindRemoteApi(SimpleRemoteApi)
*/
public boolean start() {
if (mRemoteApi == null) {
throw new IllegalStateException("RemoteApi is not set.");
}
if (mWhileFetching) {
return false;
}
mWhileFetching = true;
// A thread for retrieving liveview data from server.
Thread mThread = new Thread() {
@Override
public void run() {
SimpleLiveviewSlicer slicer = null;
try {
// Prepare for connecting.
JSONObject replyJson = null;
replyJson = mRemoteApi.startLiveview();
if (!isErrorReply(replyJson)) {
JSONArray resultsObj = replyJson.getJSONArray("result");
String liveviewUrl = null;
if (1 <= resultsObj.length()) {
// Obtain liveview URL from the result.
liveviewUrl = resultsObj.getString(0);
}
if (liveviewUrl != null) {
// Create Slicer to open the stream and parse it.
slicer = new SimpleLiveviewSlicer();
slicer.open(liveviewUrl);
}
}
if (slicer == null) {
mWhileFetching = false;
return;
}
while (mWhileFetching) {
final Payload payload = slicer.nextPayload();
if (payload == null) { // never occurs
continue;
}
if (mJpegQueue.size() == 2) {
mJpegQueue.remove();
}
mJpegQueue.add(payload.getJpegData());
}
} catch (IOException e) {
//Exceptionを受けるだけなので処理は行わない
} catch (JSONException e) {
//Exceptionを受けるだけなので処理は行わない
} finally {
// Finalize
try {
if (slicer != null) {
slicer.close();
}
mRemoteApi.stopLiveview();
} catch (IOException e) {
//Exceptionを受けるだけなので処理は行わない
}
if (mDrawerThread != null) {
mDrawerThread.interrupt();
}
mJpegQueue.clear();
mWhileFetching = false;
}
}
};
mThread.start();
// A thread for drawing liveview frame fetched by above thread.
mDrawerThread = new Thread() {
@Override
public void run() {
Bitmap frameBitmap = null;
BitmapFactory.Options factoryOptions = new BitmapFactory.Options();
factoryOptions.inSampleSize = 1;
if (mInMutableAvailable) {
initInBitmap(factoryOptions);
}
while (mWhileFetching) {
try {
byte[] jpegData = mJpegQueue.take();
frameBitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, factoryOptions);
} catch (IllegalArgumentException e) {
if (mInMutableAvailable) {
clearInBitmap(factoryOptions);
}
continue;
} catch (InterruptedException e) {
break;
}
if (mInMutableAvailable) {
setInBitmap(factoryOptions, frameBitmap);
}
drawFrame(frameBitmap);
}
if (frameBitmap != null) {
frameBitmap.recycle();
}
mWhileFetching = false;
}
};
mDrawerThread.start();
return true;
}
/**
* Request to stop retrieving and drawing liveview data.
*/
public void stop() {
mWhileFetching = false;
}
/**
* Check to see whether start() is already called.
*
* @return true if start() is already called, false otherwise.
*/
public boolean isStarted() {
return mWhileFetching;
}
/**
* Target API version.
*
* @param options オプション
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void initInBitmap(final BitmapFactory.Options options) {
options.inBitmap = null;
options.inMutable = true;
}
/**
* Target API version.
*
* @param options オプション
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void clearInBitmap(final BitmapFactory.Options options) {
if (options.inBitmap != null) {
options.inBitmap.recycle();
options.inBitmap = null;
}
}
/**
* Target API version.
*
* @param options オプション
* @param bitmap ビットマップ
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void setInBitmap(final BitmapFactory.Options options, final Bitmap bitmap) {
options.inBitmap = bitmap;
}
/**
* Draw frame bitmap onto a canvas.
*
* @param frame フレーム
*/
private void drawFrame(final Bitmap frame) {
if (frame.getWidth() != mPreviousWidth || frame.getHeight() != mPreviousHeight) {
onDetectedFrameSizeChanged(frame.getWidth(), frame.getHeight());
return;
}
Canvas canvas = getHolder().lockCanvas();
if (canvas == null) {
return;
}
int w = frame.getWidth();
int h = frame.getHeight();
Rect src = new Rect(0, 0, w, h);
float by = Math.min((float) getWidth() / w, (float) getHeight() / h);
int offsetX = (getWidth() - (int) (w * by)) / 2;
int offsetY = (getHeight() - (int) (h * by)) / 2;
Rect dst = new Rect(offsetX, offsetY, getWidth() - offsetX, getHeight() - offsetY);
canvas.drawBitmap(frame, src, dst, mFramePaint);
getHolder().unlockCanvasAndPost(canvas);
}
/**
* Called when the width or height of liveview frame image is changed.
*
* @param width width
* @param height height
*/
private void onDetectedFrameSizeChanged(final int width, final int height) {
mPreviousWidth = width;
mPreviousHeight = height;
drawBlackFrame();
drawBlackFrame();
drawBlackFrame(); // delete triple buffers
}
/**
* Draw black screen.
*/
private void drawBlackFrame() {
Canvas canvas = getHolder().lockCanvas();
if (canvas == null) {
return;
}
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL);
canvas.drawRect(new Rect(0, 0, getWidth(), getHeight()), paint);
getHolder().unlockCanvasAndPost(canvas);
}
/**
* Parse JSON and returns a error code.
*
* @param replyJson 送信用JSON
* @return hasError
*/
private static boolean isErrorReply(final JSONObject replyJson) {
boolean hasError = (replyJson != null && replyJson.has("error"));
return hasError;
}
}