package com.o3dr.android.client.apis.solo; import android.os.Bundle; import android.view.Surface; import com.MAVLink.enums.GOPRO_COMMAND; import com.o3dr.android.client.Drone; import com.o3dr.android.client.apis.CameraApi; import com.o3dr.android.client.apis.CapabilityApi; import com.o3dr.services.android.lib.drone.attribute.error.CommandExecutionError; import com.o3dr.services.android.lib.drone.companion.solo.tlv.SoloGoproConstants; import com.o3dr.services.android.lib.drone.companion.solo.tlv.SoloGoproRecord; import com.o3dr.services.android.lib.drone.companion.solo.tlv.SoloGoproSetExtendedRequest; import com.o3dr.services.android.lib.drone.companion.solo.tlv.SoloGoproSetRequest; import com.o3dr.services.android.lib.model.AbstractCommandListener; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; /** * Provides access to the solo video specific functionality * Created by Fredia Huya-Kouadio on 7/12/15. * * @since 2.5.0 */ public class SoloCameraApi extends SoloApi { private static final SimpleDateFormat FILE_DATE_FORMAT = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.US); private static final ConcurrentHashMap<Drone, SoloCameraApi> soloCameraApiCache = new ConcurrentHashMap<>(); private static final Builder<SoloCameraApi> apiBuilder = new Builder<SoloCameraApi>() { @Override public SoloCameraApi build(Drone drone) { return new SoloCameraApi(drone); } }; private static final int SOLO_STREAM_UDP_PORT = 5600; /** * Retrieves a sololink api instance. * * @param drone target vehicle * @return a SoloCameraApi instance. */ public static SoloCameraApi getApi(final Drone drone) { return getApi(drone, soloCameraApiCache, apiBuilder); } private final CapabilityApi capabilityChecker; private final CameraApi cameraApi; private SoloCameraApi(Drone drone) { super(drone); this.capabilityChecker = CapabilityApi.getApi(drone); this.cameraApi = CameraApi.getApi(drone); } /** * Take a photo with the connected gopro. * * @param listener Register a callback to receive update of the command execution status. * @since 2.5.0 */ public void takePhoto(final AbstractCommandListener listener) { //Set the gopro to photo mode switchCameraCaptureMode(SoloGoproConstants.CAPTURE_MODE_PHOTO, new AbstractCommandListener() { @Override public void onSuccess() { //Send the command to take a picture. final SoloGoproRecord photoRecord = new SoloGoproRecord(SoloGoproConstants.START_RECORDING); sendMessage(photoRecord, listener); } @Override public void onError(int executionError) { if (listener != null) { listener.onError(executionError); } } @Override public void onTimeout() { if (listener != null) { listener.onTimeout(); } } }); } /** * Toggle video recording on the connected gopro. * * @param listener Register a callback to receive update of the command execution status. * @since 2.5.0 */ public void toggleVideoRecording(final AbstractCommandListener listener) { sendVideoRecordingCommand(SoloGoproConstants.TOGGLE_RECORDING, listener); } /** * Starts video recording on the connected gopro. * * @param listener Register a callback to receive update of the command execution status. * @since 2.5.0 */ public void startVideoRecording(final AbstractCommandListener listener) { sendVideoRecordingCommand(SoloGoproConstants.START_RECORDING, listener); } /** * Stops video recording on the connected gopro. * * @param listener Register a callback to receive update of the command execution status. * @since 2.5.0 */ public void stopVideoRecording(final AbstractCommandListener listener) { sendVideoRecordingCommand(SoloGoproConstants.STOP_RECORDING, listener); } private void sendVideoRecordingCommand(@SoloGoproConstants.RecordCommand final int recordCommand, final AbstractCommandListener listener) { //Set the gopro to video mode switchCameraCaptureMode(SoloGoproConstants.CAPTURE_MODE_VIDEO, new AbstractCommandListener() { @Override public void onSuccess() { //Send the command to toggle video recording final SoloGoproRecord videoToggle = new SoloGoproRecord(recordCommand); sendMessage(videoToggle, listener); } @Override public void onError(int executionError) { if (listener != null) { listener.onError(executionError); } } @Override public void onTimeout() { if (listener != null) { listener.onTimeout(); } } }); } public void startVideoStream(final Surface surface, final String tag, final AbstractCommandListener listener) { startVideoStream(surface, tag, false, listener); } /** * Attempt to grab ownership and start the video stream from the connected drone. Can fail if * the video stream is already owned by another client. * * @param surface Surface object onto which the video is decoded. * @param tag Video tag. * @param enableLocalRecording Set to true to enable local recording, false to disable it. * @param listener Register a callback to receive update of the command execution status. * * @since 2.5.0 */ public void startVideoStream(final Surface surface, final String tag, final boolean enableLocalRecording, final AbstractCommandListener listener) { if (surface == null) { postErrorEvent(CommandExecutionError.COMMAND_FAILED, listener); return; } capabilityChecker.checkFeatureSupport(CapabilityApi.FeatureIds.SOLO_VIDEO_STREAMING, new CapabilityApi.FeatureSupportListener() { @Override public void onFeatureSupportResult(String featureId, int result, Bundle resultInfo) { switch (result) { case CapabilityApi.FEATURE_SUPPORTED: final Bundle videoProps = new Bundle(); videoProps.putInt(CameraApi.VIDEO_PROPS_UDP_PORT, SOLO_STREAM_UDP_PORT); videoProps.putBoolean(CameraApi.VIDEO_ENABLE_LOCAL_RECORDING, enableLocalRecording); if(enableLocalRecording){ String localRecordingFilename = "solo_stream_" + FILE_DATE_FORMAT.format(new Date()); videoProps.putString(CameraApi.VIDEO_LOCAL_RECORDING_FILENAME, localRecordingFilename); } cameraApi.startVideoStream(surface, tag, videoProps, listener); break; case CapabilityApi.FEATURE_UNSUPPORTED: postErrorEvent(CommandExecutionError.COMMAND_UNSUPPORTED, listener); break; default: postErrorEvent(CommandExecutionError.COMMAND_FAILED, listener); break; } } }); } /** * Attempt to grab ownership and start the video stream from the connected drone. Can fail if * the video stream is already owned by another client. * * @param surface Surface object onto which the video is decoded. * @param listener Register a callback to receive update of the command execution status. * * @since 2.5.0 */ public void startVideoStream(final Surface surface, final AbstractCommandListener listener) { startVideoStream(surface, "", listener); } /** * Stop the video stream from the connected drone, and release ownership. * * @param listener Register a callback to receive update of the command execution status. * * @since 2.5.0 */ public void stopVideoStream(final AbstractCommandListener listener) { stopVideoStream("", listener); } /** * Stop the video stream from the connected drone, and release ownership. * * @param tag Video tag. * @param listener Register a callback to receive update of the command execution status. * * @since 2.5.0 */ public void stopVideoStream(final String tag, final AbstractCommandListener listener) { capabilityChecker.checkFeatureSupport(CapabilityApi.FeatureIds.SOLO_VIDEO_STREAMING, new CapabilityApi.FeatureSupportListener() { @Override public void onFeatureSupportResult(String featureId, int result, Bundle resultInfo) { switch (result) { case CapabilityApi.FEATURE_SUPPORTED: cameraApi.stopVideoStream(tag, listener); break; case CapabilityApi.FEATURE_UNSUPPORTED: postErrorEvent(CommandExecutionError.COMMAND_UNSUPPORTED, listener); break; default: postErrorEvent(CommandExecutionError.COMMAND_FAILED, listener); break; } } }); } /** * Switches the camera capture mode. * * @param captureMode One of {@link SoloGoproConstants#CAPTURE_MODE_VIDEO}, * {@link SoloGoproConstants#CAPTURE_MODE_PHOTO}, * {@link SoloGoproConstants#CAPTURE_MODE_BURST}, * {@link SoloGoproConstants#CAPTURE_MODE_TIME_LAPSE} * @param listener Register a callback to receive update of the command execution status. * @since 2.6.8 */ public void switchCameraCaptureMode(@SoloGoproConstants.CaptureMode byte captureMode, final AbstractCommandListener listener) { final SoloGoproSetRequest captureModeRequest = new SoloGoproSetRequest((short) GOPRO_COMMAND.GOPRO_COMMAND_CAPTURE_MODE, captureMode); sendMessage(captureModeRequest, listener); } private void sendExtendedRequest(AbstractCommandListener listener, int command, byte value1, byte value2, byte value3, byte value4){ byte[] values = {value1, value2, value3, value4}; SoloGoproSetExtendedRequest extendedRequest = new SoloGoproSetExtendedRequest((short) command, values); sendMessage(extendedRequest, listener); } private void sendExtendedRequest(AbstractCommandListener listener, int command, byte value) { sendExtendedRequest(listener, command, value, (byte) 0, (byte) 0, (byte) 0); } /** * Updates the camera video settings. * @since 2.7.0 * @param resolution * @param frameRate * @param fieldOfView * @param flags */ public void updateVideoSettings(byte resolution, byte frameRate, byte fieldOfView, byte flags, AbstractCommandListener listener){ sendExtendedRequest(listener, GOPRO_COMMAND.GOPRO_COMMAND_VIDEO_SETTINGS, resolution, frameRate, fieldOfView, flags); } public void setCameraPhotoResolution(byte photoResolution, AbstractCommandListener listener){ sendExtendedRequest(listener, GOPRO_COMMAND.GOPRO_COMMAND_PHOTO_RESOLUTION, photoResolution); } public void enableCameraLowLight(boolean enable, AbstractCommandListener listener){ sendExtendedRequest(listener, GOPRO_COMMAND.GOPRO_COMMAND_LOW_LIGHT, enable ? (byte) 1 : (byte) 0); } public void setCameraPhotoBurstRate(byte burstRate, AbstractCommandListener listener){ sendExtendedRequest(listener, GOPRO_COMMAND.GOPRO_COMMAND_PHOTO_BURST_RATE, burstRate); } public void enableCameraProtune(boolean enable, AbstractCommandListener listener){ sendExtendedRequest(listener, GOPRO_COMMAND.GOPRO_COMMAND_PROTUNE, enable ? (byte)1 : (byte) 0); } public void setCameraProtuneWhiteBalance(byte whiteBalance, AbstractCommandListener listener) { sendExtendedRequest(listener, GOPRO_COMMAND.GOPRO_COMMAND_PROTUNE_WHITE_BALANCE, whiteBalance); } public void setCameraProtuneColour(byte colour, AbstractCommandListener listener) { sendExtendedRequest(listener, GOPRO_COMMAND.GOPRO_COMMAND_PROTUNE_COLOUR, colour); } public void setCameraProtuneGain(byte gain, AbstractCommandListener listener) { sendExtendedRequest(listener, GOPRO_COMMAND.GOPRO_COMMAND_PROTUNE_GAIN, gain); } public void setCameraProtuneSharpness(byte sharpness, AbstractCommandListener listener) { sendExtendedRequest(listener, GOPRO_COMMAND.GOPRO_COMMAND_PROTUNE_SHARPNESS, sharpness); } public void setCameraProtuneExposure(byte exposure, AbstractCommandListener listener) { sendExtendedRequest(listener, GOPRO_COMMAND.GOPRO_COMMAND_PROTUNE_EXPOSURE, exposure); } }