diff --git a/README.md b/README.md index ca92b41..cca33ac 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # THETA Plug-in: CameraAPI Capture Plugin -Version: 2.0.0 +Version: 2.1.0 This sample plug-in shows the way of capturing image with Camera API for RICOH THETA V. diff --git a/app/build.gradle b/app/build.gradle index 52b5716..e264950 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,7 +8,7 @@ android { minSdkVersion 25 targetSdkVersion 25 versionCode 2 - versionName "2.0.0" + versionName "2.1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } @@ -32,5 +32,5 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' - implementation 'com.theta360:pluginlibrary:2.0.0' + implementation 'com.theta360:pluginlibrary:2.1.0' } diff --git a/app/src/main/java/com/theta360/pluginsample/CameraFragment.java b/app/src/main/java/com/theta360/pluginsample/CameraFragment.java index 4ff05f4..06bb1c2 100644 --- a/app/src/main/java/com/theta360/pluginsample/CameraFragment.java +++ b/app/src/main/java/com/theta360/pluginsample/CameraFragment.java @@ -32,12 +32,30 @@ import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; + +import com.theta360.pluginlibrary.activity.ThetaInfo; +import com.theta360.pluginlibrary.exif.CameraAttitude; +import com.theta360.pluginlibrary.values.ThetaModel; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; +import com.theta360.pluginlibrary.exif.CameraSettings; +import com.theta360.pluginlibrary.exif.Box; +import com.theta360.pluginlibrary.exif.DngExif; +import com.theta360.pluginlibrary.exif.Exif; +import com.theta360.pluginlibrary.exif.GpsInfo; +import com.theta360.pluginlibrary.exif.SensorValues; +import com.theta360.pluginlibrary.exif.Xmp; +import com.theta360.pluginlibrary.exif.values.SphereType; + +import java.util.List; +import org.apache.sanselan.util.IOUtils; + /** * CameraFragment */ @@ -50,13 +68,18 @@ public class CameraFragment extends Fragment { private Camera.Parameters mParameters; private Camera.CameraInfo mCameraInfo; private CFCallback mCallback; + private Box.Callback mBoxCallback; private AudioManager mAudioManager;//for video private MediaRecorder mMediaRecorder;//for video - private boolean isSurface = false; - private boolean isCapturing = false; - private boolean isShutter = false; - private File instanceRecordMP4; - private File instanceRecordWAV; + private boolean mIsSurface = false; + private boolean mIsCapturing = false; + private boolean mIsDuringExposure = false; + private File mInstanceRecordMP4; + private File mInstanceRecordWAV; + private String mMp4filePath; + private String mWavfilePath; + private CameraAttitude mCameraAttitude = null; + private MediaRecorder.OnInfoListener onInfoListener = new MediaRecorder.OnInfoListener() { @Override public void onInfo(MediaRecorder mr, int what, int extra) { @@ -76,7 +99,7 @@ public void onError(int error, Camera camera) { private SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { - isSurface = true; + mIsSurface = true; open(); } @@ -87,17 +110,37 @@ public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, i @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { - isSurface = false; + mIsSurface = false; close(); } }; private Camera.ShutterCallback onShutterCallback = new Camera.ShutterCallback() { @Override public void onShutter() { - // ShutterCallback is called twice. - if (!isShutter) { - mCallback.onShutter(); - isShutter = true; + if (mIsCapturing && !mIsDuringExposure) { + mIsDuringExposure = true; + mIsCapturing = false; + + /* + * Hold the current value of the attitude sensor + * - It will be used later as setting value for Metadata. + */ + mCameraAttitude.snapshot(); + + if (mCallback != null) { + mCallback.onShutter(); + } + } else if (!mIsCapturing && mIsDuringExposure) { + mIsDuringExposure = false; + + /* + * Acquire the camera parameters for metadata at the completion of exposure + */ + mParameters = mCamera.getParameters(); + CameraSettings.setCameraParameters(mParameters); + } else { + mIsCapturing = false; + mIsDuringExposure = false; } } }; @@ -108,17 +151,175 @@ public void onPictureTaken(byte[] data, Camera camera) { mCamera.setParameters(mParameters); mCamera.stopPreview(); - String fileUrl = String.format("%s/plugin_%s.jpg", DCIM, getDateTime()); + /* + * Create Exif object with image data holding incomplete Metadeta + */ + Exif exif = new Exif(data, true); + + /* + * Set Sphere type + */ + CameraSettings.setSphereType(SphereType.EQUIRECTANGULAR); + + /* + * Set Date, Time, and TimeZone to CameraSettings + * - In fact, please use `camera.getOptions` of `RICOH THETA API v2.1` to obtain `dateTimeZone` + * from 'THETA shooting application' beforehand. + * The operating system's time zone is always UTC (+00:00). + * + * See. https://developers.theta360.com/ja/docs/v2.1/api_reference/ + */ + long sysTime = System.currentTimeMillis(); + int timeZoneOffset = 32400000; // TimeZone offset JST (+09:00) + CameraSettings.setDateTime(sysTime); + CameraSettings.setTimeZone(timeZoneOffset); + + /* + * Set attitude sensor value + * - Get the sensor value already held and sets it to the camera settings. + */ + SensorValues sensorValues = new SensorValues(); + sensorValues.setAttitudeRadian(mCameraAttitude.getAttitudeRadianSnapshot()); + sensorValues.setCompassAccuracy(mCameraAttitude.getAccuracySnapshot()); + CameraSettings.setSensorValues(sensorValues); + + /* + * Confirm setting value (for debug) + */ + Log.d("CameraSettings", "Confirm CameraSettings"); + Log.d("CameraSettings", " ExposureProgram: " + CameraSettings.getExposureProgram()); + Log.d("CameraSettings", " Aperture: " + CameraSettings.getAperture()); + Log.d("CameraSettings", " ShutterSpeed: " + CameraSettings.getShutterSpeed()); + Log.d("CameraSettings", " Iso: " + CameraSettings.getIso()); + Log.d("CameraSettings", " ExposureCompensation: " + CameraSettings.getExposureCompensation()); + Log.d("CameraSettings", " WhiteBalance: " + CameraSettings.getWhiteBalance()); + Log.d("CameraSettings", " ColorTemperature: " + CameraSettings.getColorTemperature()); + Log.d("CameraSettings", " Filter: " + CameraSettings.getFilter()); + Log.d("CameraSettings", " SphereType: " + CameraSettings.getSphereType()); + Log.d("CameraSettings", " DateTimzeZone: " + CameraSettings.getDateTimeZone()); + Log.d("CameraSettings", " DateTimze: " + CameraSettings.getDateTime()); + if (!CameraSettings.isEmptyTimeZone()) { + Log.d("CameraSettings", " TimeZone: " + CameraSettings.getTimeZone()); + } else { + Log.d("CameraSettings", " TimeZone: (Not set yet)"); + } + + /* + * Confirm sensor value (for debug) + */ + Log.d("SensorValues", "Confirm SensorValues:"); + Log.d("SensorValues", " AttitudeRadian: " + + sensorValues.getAttitudeRadian()[0] + ", " + + sensorValues.getAttitudeRadian()[1] + ", " + + sensorValues.getAttitudeRadian()[2]); + Log.d("SensorValues", " CompassAccuracy: " + sensorValues.getCompassAccuracy()); + + /* + * Set correct value for Receptor-IFD in MakerNote. + */ + exif.setExifSphere(); + + /* + * Set GPS information + * - In fact, please use `camera.getOptions` of `RICOH THETA API v2.1` to obtain `gpsInfo` + * from 'THETA shooting application' beforehand. + * + * See. https://developers.theta360.com/ja/docs/v2.1/api_reference/ + */ + GpsInfo gpsInfo = new GpsInfo(); + // Sample: Asia/Tokyo + gpsInfo.setLat(35.6580); // North latitude 35 degree 39 minutes 29 seconds + gpsInfo.setLng(139.7377); // East longitude 139 degree 44 minutes 28 seconds + gpsInfo.setAltitude(40.0); // Altitude 40.0m + gpsInfo.setDatum(GpsInfo.DATUM); + gpsInfo.setDateTimeZone(CameraSettings.getDateTimeZone()); + CameraSettings.setGpsInfo(gpsInfo); + + /* + * Confirm GPS Infomation (for debug) + */ + Log.d("GpsInfo", "Confirm GpsInfo:"); + Log.d("GpsInfo", " Latitude: " + gpsInfo.getLat()); + Log.d("GpsInfo", " Longitude: " + gpsInfo.getLng()); + Log.d("GpsInfo", " Altitude: " + gpsInfo.getAltitude()); + Log.d("GpsInfo", " Datum: " + gpsInfo.getDatum()); + Log.d("GpsInfo", " DateTimeZone: " + gpsInfo.getDateTimeZone()); + + /* + * Set correct value for GPS-IFD + */ + exif.setExifGPS(); + + /* + * Set correct value for MakerNote. + */ + exif.setExifMaker(); + + /* + * Get Exif data + * - Acquires the image data in which the correct Metadata has been set. + */ + byte[] exifData = exif.getExif(); + + String fileUrl = String.format("%s/plugin_%s.JPG", DCIM, getDateTime()); + List fileUrls = new ArrayList(); + fileUrls.add(fileUrl); try (FileOutputStream fileOutputStream = new FileOutputStream(fileUrl)) { - fileOutputStream.write(data); + // Get Image size + Camera.Size picSize = mParameters.getPictureSize(); + + int pitch = 0; + int roll = 0; + if (!CameraSettings.isZenith()) { + pitch = exif.calcPitch(); + roll = exif.calcRoll(); + } + + /* + * Add XMP and write image data to a file. + */ + Xmp.setXmp(exifData, fileOutputStream, picSize.width, picSize.height, pitch, roll); } catch (IOException e) { e.printStackTrace(); } - mCallback.onPictureTaken(); + /** + * If DNG output is enabled, a DNG file is output by the following process. + */ + if (CameraSettings.isDngOutput()) { + /** + * The following DNG file is output by enabling DNG output and executing still image shooting. + */ + File dngTempFile = new File( + Environment.getExternalStorageDirectory().getPath() + "/temp.dng"); + String dngFileUrl = fileUrl.replace(".JPG", ".DNG"); + fileUrls.add(dngFileUrl); + /** + * Copy temp.dng and give the same metadata as the jpeg file. + */ + try (FileInputStream dngInputStream = new FileInputStream(dngTempFile); + FileOutputStream dngOutputStream = new FileOutputStream(dngFileUrl)) { + byte[] dngData = IOUtils.getInputStreamBytes(dngInputStream); + DngExif dngExif = new DngExif(dngData, false); + dngExif.setExifGPS(); + dngExif.setExifSphere(); + dngExif.setExifMaker(); + dngExif.replaceJpeg(exifData); + + dngOutputStream.write(dngExif.getExif()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + mCallback.onPictureTaken(fileUrls.toArray(new String[fileUrls.size()])); mCamera.startPreview(); - isCapturing = false; + mIsCapturing = false; + + // After shooting, set the shooting mode to monitoring mode. + mParameters.set("RIC_SHOOTING_MODE", "RicMonitoring"); + mCamera.setParameters(mParameters); } }; private FileObserver fileObserver = new FileObserver(DCIM) { @@ -176,6 +377,12 @@ public void onAttach(Context context) { if (context instanceof CFCallback) { mCallback = (CFCallback) context; } + + /* + * Attitude sensor start + */ + mCameraAttitude = new CameraAttitude(context); + mCameraAttitude.register(); } @Override @@ -183,7 +390,7 @@ public void onStart() { super.onStart(); // startWatching(); // for debug - if (isSurface) { + if (mIsSurface) { open(); setSurface(mSurfaceHolder); } @@ -202,6 +409,11 @@ public void onDetach() { super.onDetach(); mCallback = null; + + /* + * Attitude sensor stop + */ + mCameraAttitude.unregister(); } public void startWatching() { @@ -213,14 +425,42 @@ public void stopWatching() { } public void takePicture() { - if (!isCapturing) { - isCapturing = true; - isShutter = false; + if (!mIsCapturing) { + mIsCapturing = true; + + ThetaModel thetaModel = ThetaModel.getValue(ThetaInfo.getThetaModelName()); + + /** + * Set Manufacturer name, Serial number, Version name, Model name + */ + CameraSettings.setManufacturer("RICOH"); + CameraSettings.setThetaSerialNumber(ThetaInfo.getThetaSerialNumber()); + CameraSettings.setThetaFirmwareVersion(ThetaInfo.getThetaFirmwareVersion(getContext())); + CameraSettings.setThetaModel(thetaModel); - mParameters.setPictureSize(5376, 2688); + if (thetaModel == ThetaModel.THETA_Z1) { + mParameters.setPictureSize(6720, 3360); + } + else { + mParameters.setPictureSize(5376, 2688); + } mParameters.set("RIC_SHOOTING_MODE", "RicStillCaptureStd"); mParameters.set("RIC_EXPOSURE_MODE", "RicAutoExposureP"); + mParameters.set("RIC_MANUAL_EXPOSURE_TIME_REAR", -1); + mParameters.set("RIC_MANUAL_EXPOSURE_TIME_FRONT", -1); + mParameters.set("RIC_MANUAL_EXPOSURE_ISO_REAR", -1); + mParameters.set("RIC_MANUAL_EXPOSURE_ISO_FRONT", -1); + mParameters.set("exposure-compensation-step", "0.333333333"); // 1/3 step + mParameters.setExposureCompensation(0); + if(thetaModel == ThetaModel.THETA_Z1) { + mParameters.set("RIC_MANUAL_EXPOSURE_AV_REAR", 0); + mParameters.set("RIC_MANUAL_EXPOSURE_AV_FRONT", 0); + } + mParameters.set("RIC_WB_MODE", "RicWbAuto"); + mParameters.set("RIC_WB_TEMPERATURE", 2500); mParameters.set("RIC_PROC_STITCHING", "RicDynamicStitchingAuto"); + mParameters.set("RIC_PROC_ZENITH_CORRECTION", "RicZenithCorrectionOnAuto"); + mParameters.set("RIC_DNG_OUTPUT_ENABLED", 1); //DNG output is enabled only on Z1 mParameters.set("recording-hint", "false"); mParameters.setJpegThumbnailSize(320, 160); mCamera.setParameters(mParameters); @@ -237,6 +477,7 @@ public boolean isMediaRecorderNull() { public boolean takeVideo() { boolean result = true; if (mMediaRecorder == null) { + // Audio Manager settings mMediaRecorder = new MediaRecorder(); mAudioManager.setParameters("RicUseBFormat=true"); @@ -244,10 +485,10 @@ public boolean takeVideo() { mAudioManager .setParameters("RicMicSurroundVolumeLevel=RicMicSurroundVolumeLevelNormal"); + // Sample:Set up 4K Equi videos mParameters.set("RIC_PROC_STITCHING", "RicStaticStitching"); mParameters.set("RIC_SHOOTING_MODE", "RicMovieRecording4kEqui"); - // for 4K video CamcorderProfile camcorderProfile = CamcorderProfile.get(mCameraId, 10013); mParameters.set("video-size", "3840x1920"); @@ -271,21 +512,89 @@ public boolean takeVideo() { mMediaRecorder.setMaxDuration(1500000); // max: 25 min mMediaRecorder.setMaxFileSize(20401094656L); // max: 19 GB - String videoFile = String.format("%s/plugin_%s.mp4", DCIM, getDateTime()); - String wavFile = String.format("%s/plugin_%s.wav", DCIM, getDateTime()); - String videoWavFile = String.format("%s,%s", videoFile, wavFile); + String dateTime = getDateTime(); + mMp4filePath = String.format("%s/plugin_%s.MP4", DCIM, dateTime); + mWavfilePath = String.format("%s/plugin_%s.WAV", DCIM, dateTime); + String videoWavFile = String.format("%s,%s", mMp4filePath, mWavfilePath); mMediaRecorder.setOutputFile(videoWavFile); mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); mMediaRecorder.setOnErrorListener(onErrorListener); mMediaRecorder.setOnInfoListener(onInfoListener); try { + /** + * Set parameters to be given to metadata before starting recording of video + */ + CameraSettings.setCameraParameters(mParameters); + + /* + * Set Sphere type + */ + CameraSettings.setSphereType(SphereType.EQUIRECTANGULAR); + + /* + * Set Date, Time, and TimeZone to CameraSettings + * - In fact, please use `camera.getOptions` of `RICOH THETA API v2.1` to obtain `dateTimeZone` + * from 'THETA shooting application' beforehand. + * The operating system's time zone is always UTC (+00:00). + * + * See. https://developers.theta360.com/ja/docs/v2.1/api_reference/ + */ + long sysTime = System.currentTimeMillis(); + int timeZoneOffset = 32400000; // TimeZone offset JST (+09:00) + CameraSettings.setDateTime(sysTime); + CameraSettings.setTimeZone(timeZoneOffset); + + /** + * Set Microphone Gain + */ + CameraSettings.setGain("RicMicSurroundVolumeLevel=RicMicSurroundVolumeLevelNormal"); + + /** + * Set the parameter to be set to movie metadata to MovieSettings + * If you want to add metadata to the video file, + * please set the parameter to MovieSettings as below + */ + + /** + * Set Manufacturer name, Serial number, Version name, Model name + */ + CameraSettings.setManufacturer("RICOH"); + CameraSettings.setThetaSerialNumber(ThetaInfo.getThetaSerialNumber()); + CameraSettings.setThetaFirmwareVersion(ThetaInfo.getThetaFirmwareVersion(getContext())); + CameraSettings.setThetaModel(ThetaModel.getValue(ThetaInfo.getThetaModelName())); + + /* + * Set GPS information + * - In fact, please use `camera.getOptions` of `RICOH THETA API v2.1` to obtain `gpsInfo` + * from 'THETA shooting application' beforehand. + * + * See. https://developers.theta360.com/ja/docs/v2.1/api_reference/ + */ + GpsInfo gpsInfo = new GpsInfo(); + // Sample: Asia/Tokyo + gpsInfo.setLat(35.6580); // North latitude 35 degree 39 minutes 29 seconds + gpsInfo.setLng(139.7377); // East longitude 139 degree 44 minutes 28 seconds + gpsInfo.setAltitude(40.0); // Altitude 40.0m + gpsInfo.setDatum(GpsInfo.DATUM); + gpsInfo.setDateTimeZone(CameraSettings.getDateTimeZone()); + CameraSettings.setGpsInfo(gpsInfo); + + /* + * Set attitude sensor value + */ + mCameraAttitude.snapshot(); + SensorValues sensorValues = new SensorValues(); + sensorValues.setAttitudeRadian(mCameraAttitude.getAttitudeRadianSnapshot()); + sensorValues.setCompassAccuracy(mCameraAttitude.getAccuracySnapshot()); + CameraSettings.setSensorValues(sensorValues); + mMediaRecorder.prepare(); mMediaRecorder.start(); Log.d("debug", "mMediaRecorder.start()"); - instanceRecordMP4 = new File(videoFile); - instanceRecordWAV = new File(wavFile); + mInstanceRecordMP4 = new File(mMp4filePath); + mInstanceRecordWAV = new File(mWavfilePath); } catch (IOException | RuntimeException e) { e.printStackTrace(); stopMediaRecorder(); @@ -295,10 +604,20 @@ public boolean takeVideo() { try { mMediaRecorder.stop(); Log.d("debug", "mMediaRecorder.stop()"); + /** + * Metadata is written to the movie file + * by specifying mp4 file path and wav file path in form box of BoxClass + */ + Box box = new Box(); + box.formBox(mMp4filePath, mWavfilePath, mBoxCallback); + + // After shooting, set the shooting mode to monitoring mode. + mParameters.set("RIC_SHOOTING_MODE", "RicMonitoring"); + mCamera.setParameters(mParameters); } catch (RuntimeException e) { // cancel recording - instanceRecordMP4.delete(); - instanceRecordWAV.delete(); + mInstanceRecordMP4.delete(); + mInstanceRecordWAV.delete(); result = false; } finally { stopMediaRecorder(); @@ -307,8 +626,25 @@ public boolean takeVideo() { return result; } + /** + * CallBack allows you to configure the processing + * when metadata write succeeds and fails. + */ + public void setBoxCallback(@NonNull Box.Callback callback) { + mBoxCallback = callback; + } + + /** + * Sample: Returns the MP4 file and WAV file that it holds + * @return recordFiles [0]:MP4 file [1]:WAV file + */ + public File[] getRecordFiles() { + File[] recordFiles = {mInstanceRecordMP4, mInstanceRecordWAV}; + return recordFiles; + } + public boolean isCapturing() { - return isCapturing; + return mIsCapturing; } private void open() { @@ -329,12 +665,17 @@ private void open() { mCamera.setErrorCallback(mErrorCallback); mParameters = mCamera.getParameters(); + /** + * Initialize CameraSettings for Metadata + */ + CameraSettings.initialize(); + mParameters.set("RIC_SHOOTING_MODE", "RicMonitoring"); mCamera.setParameters(mParameters); } } - private void close() { + protected void close() { stopMediaRecorder(); if (mCamera != null) { mCamera.stopPreview(); @@ -342,6 +683,8 @@ private void close() { mCamera.setErrorCallback(null); mCamera.release(); mCamera = null; + mIsCapturing = false; + mIsDuringExposure = false; } } @@ -387,6 +730,6 @@ private String getDateTime() { public interface CFCallback { void onShutter(); - void onPictureTaken(); + void onPictureTaken(String[] fileUrls); } } diff --git a/app/src/main/java/com/theta360/pluginsample/MainActivity.java b/app/src/main/java/com/theta360/pluginsample/MainActivity.java index 1f6d118..68733ee 100644 --- a/app/src/main/java/com/theta360/pluginsample/MainActivity.java +++ b/app/src/main/java/com/theta360/pluginsample/MainActivity.java @@ -17,19 +17,30 @@ package com.theta360.pluginsample; import android.os.Bundle; +import android.os.Environment; import android.support.v4.app.Fragment; import android.support.v7.app.ActionBar; +import android.util.Log; import android.view.KeyEvent; import android.view.WindowManager; import com.theta360.pluginlibrary.activity.PluginActivity; +import com.theta360.pluginlibrary.activity.ThetaInfo; import com.theta360.pluginlibrary.callback.KeyCallback; import com.theta360.pluginlibrary.receiver.KeyReceiver; import com.theta360.pluginlibrary.values.LedColor; import com.theta360.pluginlibrary.values.LedTarget; +import com.theta360.pluginlibrary.exif.Box; +import com.theta360.pluginlibrary.values.TextArea; +import com.theta360.pluginlibrary.values.ThetaModel; +import java.io.File; +import java.util.HashMap; +import java.util.Map; public class MainActivity extends PluginActivity implements CameraFragment.CFCallback { - private boolean isVideo = false; - private boolean isEnded = false; + private boolean mIsVideo = false; + private boolean mIsEnded = false; + private File mRecordMp4File; + private File mRecordWavFile; @Override protected void onCreate(Bundle savedInstanceState) { @@ -48,7 +59,7 @@ protected void onCreate(Bundle savedInstanceState) { @Override public void onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyReceiver.KEYCODE_CAMERA) { - if (isVideo) { + if (mIsVideo) { if (!takeVideo()) { // Cancel recording notificationAudioWarning(); @@ -64,9 +75,10 @@ public void onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyReceiver.KEYCODE_MEDIA_RECORD) { Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment); if (fragment != null && fragment instanceof CameraFragment) { - if (((CameraFragment) fragment).isMediaRecorderNull() && !(((CameraFragment) fragment).isCapturing())) { + if (((CameraFragment) fragment).isMediaRecorderNull() + && !(((CameraFragment) fragment).isCapturing())) { // not recording video or capturing still - isVideo = !isVideo; + mIsVideo = !mIsVideo; updateLED(); } } @@ -105,14 +117,25 @@ public void onShutter() { } @Override - public void onPictureTaken() { - + public void onPictureTaken(String[] fileUrls) { + notificationSensorStop(); + /** + * The file path specified in "notificationDatabaseUpdate" + * specifies the file path or directory path under the DCIM directory. + * Replace file path because fileUrls has full path set + */ + String storagePath = Environment.getExternalStorageDirectory().getPath(); + for(int i = 0; i < fileUrls.length; i++){ + fileUrls[i] = fileUrls[i].replace(storagePath, ""); + } + notificationDatabaseUpdate(fileUrls); } private void takePicture() { Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment); if (fragment != null && fragment instanceof CameraFragment) { if (!(((CameraFragment) fragment).isCapturing())) { + notificationSensorStart(); ((CameraFragment) fragment).takePicture(); } } @@ -129,8 +152,16 @@ private boolean takeVideo() { notificationAudioMovStart(); notificationLedBlink(LedTarget.LED7, LedColor.RED, 2000); } + // Sample: Register callback to CameraFragment + // to acquire the result of Box data writing + ((CameraFragment) fragment).setBoxCallback(mBoxCallBack); + notificationSensorStart(); result = ((CameraFragment) fragment).takeVideo(); } else { + // Sample: Get recorded file + File[] recordFiles = ((CameraFragment) fragment).getRecordFiles(); + mRecordMp4File = recordFiles[0]; + mRecordWavFile = recordFiles[1]; // stop recording result = ((CameraFragment) fragment).takeVideo(); if (result) { @@ -142,26 +173,82 @@ private boolean takeVideo() { return result; } + /** + * CallBack allows you to configure the processing when metadata write succeeds and fails. + */ + private Box.Callback mBoxCallBack = new Box.Callback() { + @Override + /** + * fileUrls contains full path of mp4 and wav files + */ + public void onCompleted(String[] fileUrls) { + /** + * Sample: If writing of Box data is successful, + * registration of recorded file to database will be executed + */ + Log.d("Sample", "Success in writing metadata"); + // Delete Wav file if unnecessary + mRecordWavFile.delete(); + notificationSensorStop(); + /** + * The file path specified in "notificationDatabaseUpdate" + * specifies the file path or directory path under the DCIM directory. + * Replace file path because fileUrls has full path set + */ + String storagePath = Environment.getExternalStorageDirectory().getPath(); + for(int i = 0; i < fileUrls.length; i++){ + fileUrls[i] = fileUrls[i].replace(storagePath, ""); + } + notificationDatabaseUpdate(fileUrls); + } + + @Override + public void onError() { + /** + * Sample: If writing of Box data fails, + * operation will be performed when an error occurs + */ + Log.d("Sample", "Failed to write metadata"); + // Delete file if unnecessary + mRecordMp4File.delete(); + mRecordWavFile.delete(); + notificationSensorStop(); + notificationErrorOccured(); + } + }; + private void updateLED() { - if (isVideo) { - notificationLedHide(LedTarget.LED4); - notificationLedShow(LedTarget.LED5); - } else { - notificationLedHide(LedTarget.LED5); - notificationLedShow(LedTarget.LED4); + if(ThetaModel.getValue(ThetaInfo.getThetaModelName()) == ThetaModel.THETA_Z1) { + Map textMap = new HashMap<>(); + if (mIsVideo) { + textMap.put(TextArea.BOTTOM, "video"); + } else { + textMap.put(TextArea.BOTTOM, "image"); + } + notificationOledTextShow(textMap); + } + else { + if (mIsVideo) { + notificationLedHide(LedTarget.LED4); + notificationLedShow(LedTarget.LED5); + } else { + notificationLedHide(LedTarget.LED5); + notificationLedShow(LedTarget.LED4); + } } } private void endProcess() { - if (!isEnded) { + if (!mIsEnded) { Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment); if (fragment != null && fragment instanceof CameraFragment) { if (!((CameraFragment) fragment).isMediaRecorderNull()) { takeVideo(); // stop recording } + ((CameraFragment) fragment).close(); } close(); - isEnded = true; + mIsEnded = true; } } }