package org.deviceconnect.android.deviceplugin.theta.core;
import org.deviceconnect.android.deviceplugin.theta.core.osc.OscClient;
import org.deviceconnect.android.deviceplugin.theta.core.osc.OscCommand;
import org.deviceconnect.android.deviceplugin.theta.core.osc.OscEntry;
import org.deviceconnect.android.deviceplugin.theta.core.osc.OscSession;
import org.deviceconnect.android.deviceplugin.theta.core.osc.OscState;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
class ThetaS extends AbstractThetaDevice {
private static final String ID_PREFIX = "theta-s-";
private static final String PARAM_RESULTS = "results";
private static final String PARAM_ENTRIES = "entries";
private static final String PARAM_ID = "id";
private static final String PARAM_FILE_URI = "fileUri";
private static final String PARAM_EXIF = "exif";
private static final String PARAM_OPTIONS = "options";
private static final String OPTION_CAPTURE_MODE = "captureMode";
private static final String OPTION_FILE_FORMAT = "fileFormat";
private static final String CAPTURE_MODE_IMAGE = "image";
private static final String CAPTURE_MODE_VIDEO = "_video";
private static final String CAPTURE_MODE_LIVE_STREAMING = "_liveStreaming";
private static final SimpleDateFormat BEFORE_FORMAT_WITH_TIMEZONE = new SimpleDateFormat("yyyy:MM:dd HH:mm:ssZ");
private static final SimpleDateFormat BEFORE_FORMAT = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
private static final SimpleDateFormat AFTER_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
private OscClient mOscClient = new OscClient();
ThetaS(final String ssId) {
super(ssId);
}
@Override
public String getId() {
return ID_PREFIX + mSSID;
}
@Override
public ThetaDeviceModel getModel() {
return ThetaDeviceModel.THETA_S;
}
@Override
public List<ThetaObject> fetchAllObjectList() throws ThetaDeviceException {
return fetchObjectList(0, 10000);
}
@Override
public List<ThetaObject> fetchObjectList(final int offset, final int maxLength) throws ThetaDeviceException {
try {
OscCommand.Result result = mOscClient.listAll(offset, maxLength);
throwExceptionIfError(result);
JSONObject json = result.getJSON();
JSONObject results = json.getJSONObject(PARAM_RESULTS);
JSONArray entries = results.getJSONArray(PARAM_ENTRIES);
List<OscEntry> list = OscEntry.parseList(entries, true);
List<ThetaObject> objects = new ArrayList<ThetaObject>();
for (Iterator<OscEntry> it = list.iterator(); it.hasNext(); ) {
ThetaObject object = createThetaObject(it.next());
if (object != null) {
objects.add(object);
}
}
return objects;
} catch (SocketTimeoutException e) {
throw new ThetaDeviceException(ThetaDeviceException.TIMEOUT, e);
} catch (IOException e) {
throw new ThetaDeviceException(ThetaDeviceException.IO_ERROR, e);
} catch (JSONException e) {
throw new ThetaDeviceException(ThetaDeviceException.INVALID_RESPONSE, e);
}
}
private ThetaObject createThetaObject(final OscEntry entry) {
if (!checkExtension(entry.getName())) {
return null;
}
return new ThetaObjectS(entry.getUri(), entry.getName(), entry.getDateTime(),
entry.getWidth(), entry.getHeight());
}
private boolean checkExtension(final String filename) {
return filename.toLowerCase().endsWith(".mp4") || filename.toLowerCase().endsWith(".jpg");
}
@Override
public ThetaObject takePicture() throws ThetaDeviceException {
try {
OscSession session = mOscClient.startSession();
OscCommand.Result result = mOscClient.takePicture(session.getId());
throwExceptionIfError(result);
JSONObject json = result.getJSON();
String id = json.getString(PARAM_ID);
result = mOscClient.waitForDone(id);
json = result.getJSON();
JSONObject results = json.getJSONObject(PARAM_RESULTS);
String fileUri = results.getString(PARAM_FILE_URI);
result = mOscClient.getMetaData(fileUri);
throwExceptionIfError(result);
json = result.getJSON();
results = json.getJSONObject(PARAM_RESULTS);
mOscClient.closeSession(session.getId());
Exif exif = new Exif(results.getJSONObject(PARAM_EXIF));
return new ThetaObjectS(fileUri, parseFileName(fileUri), exif.mDateTime,
exif.mImageWidth, exif.mImageLength);
} catch (SocketTimeoutException e) {
throw new ThetaDeviceException(ThetaDeviceException.TIMEOUT, e);
} catch (IOException e) {
throw new ThetaDeviceException(ThetaDeviceException.IO_ERROR, e);
} catch (JSONException e) {
throw new ThetaDeviceException(ThetaDeviceException.INVALID_RESPONSE, e);
} catch (InterruptedException e) {
throw new ThetaDeviceException(ThetaDeviceException.UNKNOWN);
}
}
private String parseFileName(final String fileUri) {
String[] result = fileUri.split("/");
if (result.length < 2) {
return fileUri;
}
return result[1];
}
@Override
public synchronized void startVideoRecording() throws ThetaDeviceException {
try {
OscSession session = mOscClient.startSession();
String sessionId = session.getId();
OscCommand.Result result = mOscClient.startCapture(sessionId);
throwExceptionIfError(result);
result = mOscClient.closeSession(sessionId);
throwExceptionIfError(result);
} catch (SocketTimeoutException e) {
throw new ThetaDeviceException(ThetaDeviceException.TIMEOUT, e);
} catch (IOException e) {
throw new ThetaDeviceException(ThetaDeviceException.IO_ERROR, e);
} catch (JSONException e) {
throw new ThetaDeviceException(ThetaDeviceException.INVALID_RESPONSE, e);
}
}
@Override
public synchronized void stopVideoRecording() throws ThetaDeviceException {
try {
OscSession session = mOscClient.startSession();
String sessionId = session.getId();
OscCommand.Result result = mOscClient.stopCapture(sessionId);
throwExceptionIfError(result);
result = mOscClient.closeSession(sessionId);
throwExceptionIfError(result);
} catch (SocketTimeoutException e) {
throw new ThetaDeviceException(ThetaDeviceException.TIMEOUT, e);
} catch (IOException e) {
throw new ThetaDeviceException(ThetaDeviceException.IO_ERROR, e);
} catch (JSONException e) {
throw new ThetaDeviceException(ThetaDeviceException.INVALID_RESPONSE, e);
}
}
@Override
public long getMaxVideoLength() {
return 25 * 60 * 1000;
}
@Override
public double getBatteryLevel() throws ThetaDeviceException {
try {
OscState state = mOscClient.state();
return state.getBatteryLevel();
} catch (SocketTimeoutException e) {
throw new ThetaDeviceException(ThetaDeviceException.TIMEOUT, e);
} catch (IOException e) {
throw new ThetaDeviceException(ThetaDeviceException.IO_ERROR, e);
} catch (JSONException e) {
throw new ThetaDeviceException(ThetaDeviceException.INVALID_RESPONSE, e);
}
}
@Override
public ShootingMode getShootingMode() throws ThetaDeviceException {
try {
OscSession session = mOscClient.startSession();
String sessionId = session.getId();
JSONArray optionNames = new JSONArray();
optionNames.put("captureMode");
OscCommand.Result result = mOscClient.getOptions(sessionId, optionNames);
throwExceptionIfError(result);
JSONObject json = result.getJSON();
JSONObject results = json.getJSONObject(PARAM_RESULTS);
JSONObject options = results.getJSONObject(PARAM_OPTIONS);
String captureMode = options.getString(OPTION_CAPTURE_MODE);
ShootingMode mode;
if (CAPTURE_MODE_IMAGE.equals(captureMode)) {
mode = ShootingMode.IMAGE;
} else if (CAPTURE_MODE_VIDEO.equals(captureMode)) {
mode = ShootingMode.VIDEO;
} else if (CAPTURE_MODE_LIVE_STREAMING.equals(captureMode)) {
mode = ShootingMode.LIVE_STREAMING;
} else {
mode = ShootingMode.UNKNOWN;
}
result = mOscClient.closeSession(sessionId);
throwExceptionIfError(result);
return mode;
} catch (SocketTimeoutException e) {
throw new ThetaDeviceException(ThetaDeviceException.TIMEOUT, e);
} catch (IOException e) {
throw new ThetaDeviceException(ThetaDeviceException.IO_ERROR, e);
} catch (JSONException e) {
throw new ThetaDeviceException(ThetaDeviceException.INVALID_RESPONSE, e);
}
}
@Override
public void changeShootingMode(final ShootingMode mode) throws ThetaDeviceException {
try {
String captureMode;
switch (mode) {
case IMAGE:
captureMode = CAPTURE_MODE_IMAGE;
break;
case VIDEO:
captureMode = CAPTURE_MODE_VIDEO;
break;
default:
throw new IllegalArgumentException("mode must be IMAGE or VIDEO.");
}
OscSession session = mOscClient.startSession();
String sessionId = session.getId();
JSONObject options = new JSONObject();
options.put(OPTION_CAPTURE_MODE, captureMode);
OscCommand.Result result = mOscClient.setOptions(sessionId, options);
throwExceptionIfError(result);
mOscClient.closeSession(sessionId);
} catch (SocketTimeoutException e) {
throw new ThetaDeviceException(ThetaDeviceException.TIMEOUT, e);
} catch (IOException e) {
throw new ThetaDeviceException(ThetaDeviceException.IO_ERROR, e);
} catch (JSONException e) {
throw new ThetaDeviceException(ThetaDeviceException.INVALID_RESPONSE, e);
}
}
@Override
public Recorder getRecorder() throws ThetaDeviceException {
try {
OscSession session = mOscClient.startSession();
String sessionId = session.getId();
JSONArray optionNames = new JSONArray();
optionNames.put("fileFormat");
OscCommand.Result result = mOscClient.getOptions(sessionId, optionNames);
throwExceptionIfError(result);
JSONObject json = result.getJSON();
JSONObject results = json.getJSONObject(PARAM_RESULTS);
JSONObject options = results.getJSONObject(PARAM_OPTIONS);
JSONObject format = options.getJSONObject(OPTION_FILE_FORMAT);
String type = format.getString("type");
int width = format.getInt("width");
int height = format.getInt("height");
if ("jpeg".equals(type)) {
return new ThetaImageRecorderS("0", width, height);
} else if ("mp4".equals(type)) {
return new ThetaVideoRecorderS("1", width, height);
} else {
return null;
}
} catch (SocketTimeoutException e) {
throw new ThetaDeviceException(ThetaDeviceException.TIMEOUT, e);
} catch (IOException e) {
throw new ThetaDeviceException(ThetaDeviceException.IO_ERROR, e);
} catch (JSONException e) {
throw new ThetaDeviceException(ThetaDeviceException.INVALID_RESPONSE, e);
}
}
@Override
public InputStream getLiveStream() throws IOException {
try {
OscSession session = mOscClient.startSession();
return mOscClient.getLivePreview(session.getId());
} catch (JSONException e) {
throw new IOException(e);
}
}
@Override
public void destroy() {
}
private void throwExceptionIfError(final OscCommand.Result result) throws ThetaDeviceException {
if (result.isSuccess()) {
return;
}
OscCommand.Error error = result.getError();
String code = error.getCode();
int status = result.getHttpStatusCode();
switch (status) {
case 400:
throw new ThetaDeviceException(ThetaDeviceException.BAD_REQUEST, code);
case 403:
throw new ThetaDeviceException(ThetaDeviceException.FORBIDDEN, code);
case 503:
throw new ThetaDeviceException(ThetaDeviceException.UNAVAILABLE, code);
default:
throw new ThetaDeviceException(ThetaDeviceException.UNKNOWN);
}
}
private class ThetaObjectS implements ThetaObject {
private static final String MIMETYPE_IMAGE = "image/jpeg";
private static final String MIMETYPE_VIDEO = "video/mp4";
private final String mFileUri;
private final String mFileName;
private final int mWidth;
private final int mHeight;
private final String mDateTime;
private final long mDateTimeUnix;
private byte[] mThumbnail;
private byte[] mMain;
public ThetaObjectS(final String fileUri, final String fileName, final String dateTime,
final int width, final int height) {
mFileUri = fileUri;
mFileName = fileName;
mWidth = width;
mHeight = height;
Date date = parseDate(dateTime);
if (date != null) {
mDateTime = AFTER_FORMAT.format(date);
mDateTimeUnix = date.getTime();
} else {
mDateTime = "";
mDateTimeUnix = 0;
}
}
private Date parseDate(final String time) {
Date date = null;
try {
date = BEFORE_FORMAT_WITH_TIMEZONE.parse(time);
} catch (ParseException e) {
// Nothing to do.
}
if (date == null) {
try {
date = BEFORE_FORMAT.parse(time);
} catch (ParseException e) {
// Nothing to do.
}
}
return date;
}
@Override
public void fetch(final DataType type) throws ThetaDeviceException {
try {
switch (type) {
case THUMBNAIL: {
OscCommand.Result result;
if (isImage()) {
result = mOscClient.getImage(mFileUri, true);
} else {
result = mOscClient.getVideo(mFileUri, true);
}
throwExceptionIfError(result);
mThumbnail = result.getBytes();
} break;
case MAIN: {
OscCommand.Result result;
if (isImage()) {
result = mOscClient.getImage(mFileUri, false);
} else {
result = mOscClient.getVideo(mFileUri, false);
}
throwExceptionIfError(result);
mMain = result.getBytes();
} break;
default:
throw new IllegalArgumentException();
}
} catch (SocketTimeoutException e) {
throw new ThetaDeviceException(ThetaDeviceException.TIMEOUT, e);
} catch (IOException e) {
throw new ThetaDeviceException(ThetaDeviceException.IO_ERROR, e);
} catch (JSONException e) {
throw new ThetaDeviceException(ThetaDeviceException.INVALID_RESPONSE, e);
}
}
@Override
public boolean isFetched(final DataType type) {
switch (type) {
case THUMBNAIL:
return mThumbnail != null;
case MAIN:
return mMain != null;
default:
throw new IllegalArgumentException();
}
}
@Override
public void remove() throws ThetaDeviceException {
try {
mOscClient.delete(mFileUri);
} catch (SocketTimeoutException e) {
throw new ThetaDeviceException(ThetaDeviceException.TIMEOUT, e);
} catch (IOException e) {
throw new ThetaDeviceException(ThetaDeviceException.IO_ERROR, e);
} catch (JSONException e) {
throw new ThetaDeviceException(ThetaDeviceException.INVALID_RESPONSE, e);
}
}
@Override
public void clear(final DataType type) {
switch (type) {
case THUMBNAIL:
mThumbnail = null;
break;
case MAIN:
mMain = null;
break;
default:
throw new IllegalArgumentException();
}
}
@Override
public String getMimeType() {
return isImage() ? MIMETYPE_IMAGE : MIMETYPE_VIDEO;
}
@Override
public Boolean isImage() {
return getFileName().toLowerCase().endsWith(".jpg");
}
@Override
public String getCreationTime() {
return mDateTime;
}
@Override
public long getCreationTimeWithUnixTime() {
return mDateTimeUnix;
}
@Override
public String getFileName() {
return mFileName;
}
@Override
public Integer getWidth() {
return mWidth;
}
@Override
public Integer getHeight() {
return mHeight;
}
@Override
public byte[] getThumbnailData() {
return mThumbnail;
}
@Override
public byte[] getMainData() {
return mMain;
}
}
private class ThetaImageRecorderS extends ThetaRecorderS {
private static final String NAME = "THETA S - photo";
private static final String MIME_TYPE = "image/jpeg";
private static final int PREVIEW_WIDTH = 640;
private static final int PREVIEW_HEIGHT = 320;
private static final double PREVIEW_MAX_FRAME_RATE = 10.0d;
public ThetaImageRecorderS(final String id, final int imageWidth, final int imageHeight) {
super(id, NAME, MIME_TYPE, imageWidth, imageHeight);
}
@Override
public int getPreviewWidth() {
return PREVIEW_WIDTH;
}
@Override
public int getPreviewHeight() {
return PREVIEW_HEIGHT;
}
@Override
public double getPreviewMaxFrameRate() {
return PREVIEW_MAX_FRAME_RATE;
}
@Override
public boolean supportsPreview() {
return true;
}
@Override
public boolean supportsVideoRecording() {
return false;
}
@Override
public boolean supportsPhoto() {
return true;
}
@Override
public RecorderState getState() throws ThetaDeviceException {
return RecorderState.INACTIVE;
}
}
private class ThetaVideoRecorderS extends ThetaRecorderS {
private static final String NAME = "THETA S - video";
private static final String MIME_TYPE = "video/mp4";
public ThetaVideoRecorderS(final String id, final int imageWidth, final int imageHeight) {
super(id, NAME, MIME_TYPE, imageWidth, imageHeight);
}
@Override
public int getPreviewWidth() {
return 0;
}
@Override
public int getPreviewHeight() {
return 0;
}
@Override
public double getPreviewMaxFrameRate() {
return 0;
}
@Override
public boolean supportsPreview() {
return false;
}
@Override
public boolean supportsVideoRecording() {
return true;
}
@Override
public boolean supportsPhoto() {
return false;
}
@Override
public RecorderState getState() throws ThetaDeviceException {
try {
OscState state = mOscClient.state();
String captureStatus = state.getCaptureStatus();
if ("shooting".equals(captureStatus)) {
return RecorderState.RECORDING;
} else if ("idle".equals(captureStatus)) {
return RecorderState.INACTIVE;
} else {
return RecorderState.UNKNOWN;
}
} catch (SocketTimeoutException e) {
throw new ThetaDeviceException(ThetaDeviceException.TIMEOUT, e);
} catch (IOException e) {
throw new ThetaDeviceException(ThetaDeviceException.IO_ERROR, e);
} catch (JSONException e) {
throw new ThetaDeviceException(ThetaDeviceException.INVALID_RESPONSE, e);
}
}
}
private abstract class ThetaRecorderS implements Recorder {
private final String mId;
private final String mName;
private final String mMimeType;
private final int mImageWidth;
private final int mImageHeight;
public ThetaRecorderS(final String id, final String name, final String mimeType,
final int imageWidth, final int imageHeight) {
mId = id;
mName = name;
mMimeType = mimeType;
mImageWidth = imageWidth;
mImageHeight = imageHeight;
}
@Override
public String getId() {
return mId;
}
@Override
public String getName() {
return mName;
}
@Override
public String getMimeType() {
return mMimeType;
}
@Override
public int getImageWidth() {
return mImageWidth;
}
@Override
public int getImageHeight() {
return mImageHeight;
}
}
private static class Exif {
private static final String KEY_IMAGE_WIDTH = "ImageWidth";
private static final String KEY_IMAGE_LENGTH = "ImageLength";
private static final String KEY_DATE_TIME = "DateTime";
private final int mImageWidth;
private final int mImageLength;
private final String mDateTime;
public Exif(final JSONObject exif) throws JSONException {
mImageWidth = exif.getInt(KEY_IMAGE_WIDTH);
mImageLength = exif.getInt(KEY_IMAGE_LENGTH);
mDateTime = exif.getString(KEY_DATE_TIME);
}
}
}