diff --git a/build.gradle b/build.gradle index 469b3eb10..f0056dc6b 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ allprojects { ext { compileSdkVersion = 29 - minSdkVersion = 15 + minSdkVersion = 18 targetSdkVersion = 29 } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java index 9f8a71de4..d9187fd80 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java @@ -161,4 +161,14 @@ public void onVideoRecordingEnd() { } + @UiThread + public void onVideoRecordingPause() { + + } + + @UiThread + public void onVideoRecordingResume() { + + } + } \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 5dbd656b5..189acab3a 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -1834,6 +1834,27 @@ public void run() { }); } + public void pauseVideoRecording() { + mCameraEngine.pauseVideoRecording(); + mUiHandler.post(new Runnable() { + @Override + public void run() { + if (getKeepScreenOn() != mKeepScreenOn) setKeepScreenOn(mKeepScreenOn); + } + }); + } + + + public void resumeVideoRecording() { + mCameraEngine.resumeVideoRecording(); + mUiHandler.post(new Runnable() { + @Override + public void run() { + if (getKeepScreenOn() != mKeepScreenOn) setKeepScreenOn(mKeepScreenOn); + } + }); + } + /** * Sets the max width for snapshots taken with {@link #takePictureSnapshot()} or * {@link #takeVideoSnapshot(File)}. If the snapshot width exceeds this value, the snapshot @@ -2082,6 +2103,11 @@ public boolean isTakingVideo() { return mCameraEngine.isTakingVideo(); } + + public boolean isRecordingPaused() { + return mCameraEngine.isRecordingPaused(); + } + /** * Returns true if the camera is currently capturing a picture * @return boolean indicating if the camera is capturing a picture @@ -2380,6 +2406,30 @@ public void run() { } }); } + + @Override + public void dispatchOnVideoRecordingPause() { + mUiHandler.post(new Runnable() { + @Override + public void run() { + for (CameraListener listener : mListeners) { + listener.onVideoRecordingPause(); + } + } + }); + } + + @Override + public void dispatchOnVideoRecordingResume() { + mUiHandler.post(new Runnable() { + @Override + public void run() { + for (CameraListener listener : mListeners) { + listener.onVideoRecordingResume(); + } + } + }); + } } //endregion diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java index 95549c648..8bc6f7682 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java @@ -439,6 +439,16 @@ public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception } } + @Override + public void onVideoRecordingPause() { + setVideoRecordingPauseCallback(); + } + + @Override + public void onVideoRecordingResume() { + setVideoRecordingResumeCallback(); + } + //endregion //region Parameters diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java index 7e09f5884..5fbaa96a1 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java @@ -19,6 +19,7 @@ import android.media.Image; import android.media.ImageReader; import android.os.Build; +import android.util.Log; import android.util.Pair; import android.util.Range; import android.util.Rational; @@ -966,6 +967,22 @@ public void onVideoRecordingEnd() { } } + @Override + public void onVideoRecordingPause() { + Log.d(TAG, "onVideoRecordingPause: "); + setVideoRecordingPauseCallback(); + + + } + + @Override + public void onVideoRecordingResume() { + Log.d(TAG, "onVideoRecordingResume: "); + setVideoRecordingResumeCallback(); + } + + + @Override public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) { super.onVideoResult(result, exception); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java index df95098f3..3c15c2c29 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java @@ -560,6 +560,13 @@ public final boolean isTakingVideo() { return mVideoRecorder != null && mVideoRecorder.isRecording(); } + + @Override + public final boolean isRecordingPaused() { + return mVideoRecorder != null && mVideoRecorder.isRecordingPaused(); + } + + @Override public final void takeVideo(final @NonNull VideoResult.Stub stub, final @Nullable File file, @@ -644,6 +651,57 @@ protected void onStopVideo() { } } + @Override + public final void pauseVideoRecording() { + getOrchestrator().schedule("pause video", true, new Runnable() { + @Override + public void run() { +// LOG.i("pauseVideoRecording", "running. isTakingVideo?", isTakingVideo()); + onPauseVideoRecording(); + } + }); + } + + @EngineThread + @SuppressWarnings("WeakerAccess") + protected void onPauseVideoRecording() { + if (mVideoRecorder != null) { + mVideoRecorder.pauseVideoRecording(); + } + } + + + protected void setVideoRecordingPauseCallback() { + getCallback().dispatchOnVideoRecordingPause(); + } + + + @Override + public final void resumeVideoRecording() { + getOrchestrator().schedule("resume video", true, new Runnable() { + @Override + public void run() { +// LOG.i("pauseVideoRecording", "running. isTakingVideo?", isTakingVideo()); + onResumeVideoRecording(); + } + }); + } + + + @EngineThread + @SuppressWarnings("WeakerAccess") + protected void onResumeVideoRecording() { + if (mVideoRecorder != null) { + mVideoRecorder.resumeVideoRecording(); + } + } + + protected void setVideoRecordingResumeCallback() { + getCallback().dispatchOnVideoRecordingResume(); + } + + + @CallSuper @Override public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) { diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java index 2817bbdb4..c32466f1f 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java @@ -127,6 +127,8 @@ void dispatchOnExposureCorrectionChanged(float newValue, @NonNull float[] bounds void dispatchError(CameraException exception); void dispatchOnVideoRecordingStart(); void dispatchOnVideoRecordingEnd(); + void dispatchOnVideoRecordingPause(); + void dispatchOnVideoRecordingResume(); } protected static final String TAG = CameraEngine.class.getSimpleName(); @@ -714,11 +716,14 @@ public abstract void startAutoFocus(@Nullable Gesture gesture, public abstract void takePictureSnapshot(final @NonNull PictureResult.Stub stub); public abstract boolean isTakingVideo(); + public abstract boolean isRecordingPaused(); public abstract void takeVideo(@NonNull VideoResult.Stub stub, @Nullable File file, @Nullable FileDescriptor fileDescriptor); public abstract void takeVideoSnapshot(@NonNull VideoResult.Stub stub, @NonNull File file); public abstract void stopVideo(); + public abstract void pauseVideoRecording(); + public abstract void resumeVideoRecording(); //endregion } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java index c5a4a4e32..77e15319e 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java @@ -2,6 +2,8 @@ import android.media.CamcorderProfile; import android.media.MediaRecorder; +import android.os.Build; +import android.util.Log; import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.VideoResult; @@ -346,4 +348,29 @@ protected void onStop(boolean isCameraShutdown) { dispatchResult(); } + + @Override + protected void onPauseVideoRecording() { + Log.d(TAG, "onPauseVideoRecording: "); + if (mMediaRecorder != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){ + mMediaRecorder.pause(); + dispatchVideoRecordingPause(); + } + + } + } + + + @Override + protected void onResumeVideoRecording() { + Log.d(TAG, "onPauseVideoRecording: "); + if (mMediaRecorder != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){ + mMediaRecorder.resume(); + dispatchVideoRecordingResume(); + } + } + } + } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java index b702dbf8b..11e4b7037 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java @@ -3,6 +3,7 @@ import android.graphics.SurfaceTexture; import android.opengl.EGL14; import android.os.Build; +import android.util.Log; import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.internal.DeviceEncoders; @@ -103,6 +104,7 @@ protected void onStop(boolean isCameraShutdown) { } } + @RendererThread @Override public void onRendererTextureCreated(int textureId) { @@ -339,4 +341,16 @@ public void onEncodingEnd(int stopReason, @Nullable Exception e) { } dispatchResult(); } + + + @Override + protected void onPauseVideoRecording() { + // not supported + } + + @Override + protected void onResumeVideoRecording() { + // not supported + } + } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java index d2a2bddb1..53b98cb91 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java @@ -39,11 +39,17 @@ public interface VideoResultListener { * and soon {@link #onVideoResult(VideoResult.Stub, Exception)} will be called. */ void onVideoRecordingEnd(); + + void onVideoRecordingPause(); + + void onVideoRecordingResume(); + } private final static int STATE_IDLE = 0; private final static int STATE_RECORDING = 1; private final static int STATE_STOPPING = 2; + private final static int STATE_PAUSING = 3; @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) VideoResult.Stub mResult; private final VideoResultListener mListener; @@ -105,7 +111,15 @@ public final void stop(boolean isCameraShutdown) { public boolean isRecording() { // true if not idle. synchronized (mStateLock) { - return mState != STATE_IDLE; + return mState != STATE_IDLE && mState!=STATE_PAUSING; + } + } + + + public boolean isRecordingPaused() { + // true if not idle. + synchronized (mStateLock) { + return mState ==STATE_PAUSING; } } @@ -173,4 +187,50 @@ protected void dispatchVideoRecordingEnd() { mListener.onVideoRecordingEnd(); } } + + + public final void pauseVideoRecording() { + synchronized (mStateLock) { + if (mState == STATE_IDLE) { + return; + } + LOG.i("pause:", "Changed state to STATE_PAUSING"); + mState = STATE_PAUSING; + } + onPauseVideoRecording(); + } + + protected abstract void onPauseVideoRecording(); + + public final void resumeVideoRecording() { + synchronized (mStateLock) { + if (mState == STATE_IDLE) { + return; + } + LOG.i("resume:", "Changed state to STATE_RECORDING"); + mState = STATE_RECORDING; + } + onResumeVideoRecording(); + } + + protected abstract void onResumeVideoRecording(); + + @SuppressWarnings("WeakerAccess") + @CallSuper + protected void dispatchVideoRecordingPause() { + LOG.i("dispatchVideoRecordingPause:", "pause"); + if (mListener != null) { + mListener.onVideoRecordingPause(); + } + } + + @SuppressWarnings("WeakerAccess") + @CallSuper + protected void dispatchVideoRecordingResume() { + LOG.i("dispatchVideoRecordingResume:", "resume"); + if (mListener != null) { + mListener.onVideoRecordingResume(); + } + } + } diff --git a/demo/build.gradle b/demo/build.gradle index fe41eebd0..90142b58d 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -23,5 +23,8 @@ android { dependencies { implementation project(':cameraview') implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'com.google.android.material:material:1.1.0-beta01' + implementation 'com.google.android.material:material:1.1.0-alpha09' + implementation 'com.otaliastudios:transcoder:0.8.0' + implementation 'com.otaliastudios.opengl:egloo:0.4.0' + } diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index e7e1162ed..cf23792fe 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -4,6 +4,10 @@ package="com.otaliastudios.cameraview.demo"> + + + + + + diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/AlbumStorageDirFactory.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/AlbumStorageDirFactory.java new file mode 100755 index 000000000..7cb44e919 --- /dev/null +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/AlbumStorageDirFactory.java @@ -0,0 +1,7 @@ +package com.otaliastudios.cameraview.demo; + +import java.io.File; + +public abstract class AlbumStorageDirFactory { + public abstract File getAlbumStorageDir(String albumName); +} diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/BaseAlbumDirFactory.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/BaseAlbumDirFactory.java new file mode 100755 index 000000000..58340b3a3 --- /dev/null +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/BaseAlbumDirFactory.java @@ -0,0 +1,21 @@ +package com.otaliastudios.cameraview.demo; + +import android.os.Environment; + +import java.io.File; + +public final class BaseAlbumDirFactory extends AlbumStorageDirFactory { + + // Standard storage location for digital camera files + private static final String CAMERA_DIR = "/Pictures/"; + + + @Override + public File getAlbumStorageDir(String albumName) { + return new File( + Environment.getExternalStorageDirectory() + + CAMERA_DIR + + albumName + ); + } +} diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java index bf5a4c521..1ef9460b5 100644 --- a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java @@ -1,25 +1,33 @@ package com.otaliastudios.cameraview.demo; +import android.Manifest; import android.animation.ValueAnimator; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Color; import android.graphics.ImageFormat; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.YuvImage; +import android.net.Uri; import android.os.Bundle; import androidx.annotation.NonNull; import com.google.android.material.bottomsheet.BottomSheetBehavior; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import android.os.Environment; +import android.os.Handler; +import android.text.format.DateUtils; +import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.widget.ImageButton; +import android.widget.TextView; import android.widget.Toast; import com.otaliastudios.cameraview.CameraException; @@ -32,16 +40,21 @@ import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.controls.Preview; import com.otaliastudios.cameraview.filter.Filters; -import com.otaliastudios.cameraview.filter.MultiFilter; -import com.otaliastudios.cameraview.filters.BrightnessFilter; -import com.otaliastudios.cameraview.filters.DuotoneFilter; import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.FrameProcessor; +import com.otaliastudios.transcoder.Transcoder; +import com.otaliastudios.transcoder.TranscoderListener; +import com.otaliastudios.transcoder.TranscoderOptions; +import com.otaliastudios.transcoder.sink.DataSink; +import com.otaliastudios.transcoder.sink.DefaultDataSink; import java.io.ByteArrayOutputStream; import java.io.File; +import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Date; import java.util.List; +import java.util.Locale; public class CameraActivity extends AppCompatActivity implements View.OnClickListener, OptionView.Callback { @@ -53,10 +66,25 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis private CameraView camera; private ViewGroup controlPanel; private long mCaptureTime; - + private long mVideoCaptureTime; private int mCurrentFilter = 0; private final Filters[] mAllFilters = Filters.values(); + + private TextView mTvTimer; + + private ImageButton mImgPlayPauseVideo; + private ImageButton mImgStopVideo; + + private String videoFilePath; + + private static final int MAX_RECORDING_TIME = 2 * 60 * 1000; // 2 MINUTES! + + + private Handler mUpdateDurationHandler; + + private static final String TAG = "CameraActivity_"; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -67,6 +95,82 @@ protected void onCreate(Bundle savedInstanceState) { camera.setLifecycleOwner(this); camera.addCameraListener(new Listener()); + mImgPlayPauseVideo = findViewById(R.id.imgPlayPauseVideo); + mImgStopVideo = findViewById(R.id.imgStopVideo); + + mTvTimer = findViewById(R.id.tvTimer); + mTvTimer.setText(""); + + mImgStopVideo.setVisibility(View.GONE); + + + mImgPlayPauseVideo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + camera.setMode(Mode.VIDEO); + mImgStopVideo.setVisibility(View.VISIBLE); + if (camera.isTakingVideo()) { + + // PAUSE THE VIDEO + + mImgPlayPauseVideo.setImageResource(R.drawable.ic_play_circle_outline_black_24dp); + camera.pauseVideoRecording(); + + killVideoDurationElapsedTimeHandler(); + mVideoCaptureTime = System.currentTimeMillis(); // saves the pause time! + long secondsElapsed = (System.currentTimeMillis() - mVideoCaptureTime)/1000; + + } else if (camera.isRecordingPaused()) { + + // RESUME VIDEO RECORDING + + + mImgPlayPauseVideo.setImageResource(R.drawable.ic_pause_circle_outline_black_24dp); + camera.resumeVideoRecording(); + + long pauseTime = System.currentTimeMillis() - mVideoCaptureTime; + mVideoCaptureTime = mVideoCaptureTime+pauseTime; + +// mVideoCaptureTime = System.currentTimeMillis() + mVideoCaptureTime; +// mVideoCaptureTime = System.currentTimeMillis() -(System.currentTimeMillis() - mVideoCaptureTime); +// mVideoCaptureTime = System.currentTimeMillis() - mVideoCaptureTime; + long secondsElapsed = (System.currentTimeMillis() - mVideoCaptureTime)/1000; + updateVideoDurationElapsedTime(); + + + } else { + + mVideoCaptureTime = System.currentTimeMillis(); + + updateVideoDurationElapsedTime(); + mImgPlayPauseVideo.setImageResource(R.drawable.ic_pause_circle_outline_black_24dp); + File albumF = getAlbumDir(); + String videoFileName = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ENGLISH).format(new Date()); + videoFileName = videoFileName+".mp4"; + + File videoFile = new File(albumF,videoFileName); + videoFilePath = videoFile.getAbsolutePath(); + camera.takeVideo(videoFile, MAX_RECORDING_TIME); +// camera.takeVideoSnapshot(videoFile, MAX_RECORDING_TIME); + + + +// camera.takeVideo(new File(getFilesDir(), "video.mp4"), MAX_RECORDING_TIME); + } + + } + }); + + + mImgStopVideo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + killVideoDurationElapsedTimeHandler(); // make sure not to init more then one instance + camera.stopVideo(); + } + }); + + if (USE_FRAME_PROCESSOR) { camera.addFrameProcessor(new FrameProcessor() { private long lastTime = System.currentTimeMillis(); @@ -189,6 +293,42 @@ public void onAnimationUpdate(ValueAnimator animation) { animator.start(); } + + + private final String TEMP_FILE_DIR = android.os.Environment.getExternalStorageDirectory().getAbsolutePath() + "/" +getPackageName() + "/temp"; + + public File getTempMediaFileDirectory() throws NullPointerException { + File result = new File(TEMP_FILE_DIR); + if (!result.exists()){ + result.mkdirs(); + } + if (result != null){ + return result; + } else { + throw new NullPointerException(); + } + } + + private File getAlbumDir() { + File storageDir = null; + AlbumStorageDirFactory mAlbumStorageDirFactory = new BaseAlbumDirFactory(); + if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { + storageDir = mAlbumStorageDirFactory.getAlbumStorageDir(getString(R.string.album_name)); + if (!storageDir.mkdirs()) { + if (!storageDir.exists()) { +// Logger.Log("failed to create directory"); +// return storageDir; + return null; + } + } + } else { +// Logger.Log("External storage is not mounted READ/WRITE."); + } + return storageDir; + } + + + private void message(@NonNull String content, boolean important) { if (important) { LOG.w(content); @@ -244,6 +384,30 @@ public void onVideoTaken(@NonNull VideoResult result) { Intent intent = new Intent(CameraActivity.this, VideoPreviewActivity.class); startActivity(intent); LOG.w("onVideoTaken called! Launched activity."); + + + + + // notify yhe gallery after a new video added + if (videoFilePath != null) { + // check if android M has access to write external storage + if (ActivityCompat.checkSelfPermission(CameraActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + return; + } + Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + File f = new File(videoFilePath); + Uri contentUri; + + contentUri = Uri.fromFile(f); + + mediaScanIntent.setData(contentUri); + sendBroadcast(mediaScanIntent); + + +// transcodeVideo(); + + } + } @Override @@ -257,8 +421,27 @@ public void onVideoRecordingEnd() { super.onVideoRecordingEnd(); message("Video taken. Processing...", false); LOG.w("onVideoRecordingEnd!"); + mImgPlayPauseVideo.setImageResource(R.drawable.ic_play_circle_outline_black_24dp); + mImgStopVideo.setVisibility(View.GONE); + mTvTimer.setText(""); + } + + + @Override + public void onVideoRecordingPause() { + super.onVideoRecordingPause(); + message("Video pause", true); + mImgPlayPauseVideo.setImageResource(R.drawable.ic_play_circle_outline_black_24dp); + } + + @Override + public void onVideoRecordingResume() { + super.onVideoRecordingResume(); + message("Video resume",true); + mImgPlayPauseVideo.setImageResource(R.drawable.ic_pause_circle_outline_black_24dp); } + @Override public void onExposureCorrectionChanged(float newValue, @NonNull float[] bounds, @Nullable PointF[] fingers) { super.onExposureCorrectionChanged(newValue, bounds, fingers); @@ -272,6 +455,64 @@ public void onZoomChanged(float newValue, @NonNull float[] bounds, @Nullable Poi } } + private void transcodeVideo() { + +// File f = new File(videoFilePath); +// Uri contentUri; +// +// contentUri = Uri.fromFile(f); + + File albumF = getAlbumDir(); + String videoFileName = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ENGLISH).format(new Date()); + videoFileName = videoFileName+".mp4"; + + File videoFile = new File(albumF,videoFileName); + final String videoFilePathCoded = videoFile.getAbsolutePath(); + Transcoder.into(videoFilePathCoded).addDataSource(videoFilePath).setListener(new TranscoderListener() { + @Override + public void onTranscodeProgress(double progress) { + Log.d(TAG, "onTranscodeProgress: "+progress); + } + + @Override + public void onTranscodeCompleted(int successCode) { + if (successCode == Transcoder.SUCCESS_TRANSCODED) { + + + // notify yhe gallery after a new video added + // check if android M has access to write external storage + if (ActivityCompat.checkSelfPermission(CameraActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + return; + } + Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + File f = new File(videoFilePathCoded); + Uri contentUri; + + contentUri = Uri.fromFile(f); + + mediaScanIntent.setData(contentUri); + sendBroadcast(mediaScanIntent); + + + }else{ + // error + } + } + + @Override + public void onTranscodeCanceled() { + Log.d(TAG, "onTranscodeCanceled: "); + } + + @Override + public void onTranscodeFailed(@NonNull Throwable exception) { + // error! + Log.d(TAG, "onTranscodeFailed: "+exception.getMessage()); + + } + }).transcode(); + } + @Override public void onClick(View view) { switch (view.getId()) { @@ -381,6 +622,37 @@ private void changeCurrentFilter() { // camera.setFilter(new MultiFilter(duotone, filter.newInstance())); } + + + + private void updateVideoDurationElapsedTime(){ + + killVideoDurationElapsedTimeHandler(); // make sure not to init more then one instance + + mUpdateDurationHandler = new Handler(); + mUpdateDurationHandler.postDelayed(mVideoDurationRunnable,1000); + + long secondsElapsed = (System.currentTimeMillis() - mVideoCaptureTime)/1000; + mTvTimer.setText(DateUtils.formatElapsedTime(secondsElapsed)); + + + } + + private Runnable mVideoDurationRunnable = new Runnable() { + @Override + public void run() { + updateVideoDurationElapsedTime(); + } + }; + + + private void killVideoDurationElapsedTimeHandler(){ + if (mUpdateDurationHandler!=null){ + mUpdateDurationHandler.removeCallbacks(mVideoDurationRunnable); + } + } + + @Override public boolean onValueChanged(@NonNull Option option, @NonNull T value, @NonNull String name) { if ((option instanceof Option.Width || option instanceof Option.Height)) { @@ -414,4 +686,18 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis } //endregion + + + @Override + protected void onPause() { + super.onPause(); + killVideoDurationElapsedTimeHandler(); + } + + + @Override + protected void onDestroy() { + super.onDestroy(); + killVideoDurationElapsedTimeHandler(); + } } diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/VideoPreviewActivity.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/VideoPreviewActivity.java index a37192ccc..72f58161c 100644 --- a/demo/src/main/java/com/otaliastudios/cameraview/demo/VideoPreviewActivity.java +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/VideoPreviewActivity.java @@ -1,15 +1,21 @@ package com.otaliastudios.cameraview.demo; +import android.Manifest; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; import androidx.core.content.FileProvider; +import android.os.Environment; import android.util.Log; import android.view.Menu; import android.view.MenuItem; @@ -23,8 +29,13 @@ import com.otaliastudios.cameraview.FileCallback; import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.size.AspectRatio; +import com.otaliastudios.transcoder.Transcoder; +import com.otaliastudios.transcoder.TranscoderListener; import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; public class VideoPreviewActivity extends AppCompatActivity { @@ -33,6 +44,8 @@ public class VideoPreviewActivity extends AppCompatActivity { private static VideoResult videoResult; + private static final String TAG = "CameraActivity_"; + public static void setVideoResult(@Nullable VideoResult result) { videoResult = result; } @@ -93,6 +106,10 @@ public void onPrepared(MediaPlayer mp) { } } }); + + + + transcodeVideo(); } void playVideo() { @@ -101,6 +118,84 @@ void playVideo() { } } + + private void transcodeVideo() { + +// File f = new File(videoFilePath); +// Uri contentUri; +// +// contentUri = Uri.fromFile(f); + + File albumF = getAlbumDir(); + String videoFileName = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ENGLISH).format(new Date()); + videoFileName = videoFileName+".mp4"; + + File videoFile = new File(albumF,videoFileName); + final String videoFilePathCoded = videoFile.getAbsolutePath(); + Transcoder.into(videoFilePathCoded).addDataSource(videoResult.getFile().getAbsolutePath()).setListener(new TranscoderListener() { + @Override + public void onTranscodeProgress(double progress) { + Log.d(TAG, "onTranscodeProgress: "+progress); + } + + @Override + public void onTranscodeCompleted(int successCode) { + if (successCode == Transcoder.SUCCESS_TRANSCODED) { + + + // notify yhe gallery after a new video added + // check if android M has access to write external storage + if (ActivityCompat.checkSelfPermission(VideoPreviewActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + return; + } + Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + File f = new File(videoFilePathCoded); + Uri contentUri; + + contentUri = Uri.fromFile(f); + + mediaScanIntent.setData(contentUri); + sendBroadcast(mediaScanIntent); + + + }else{ + // error + } + } + + @Override + public void onTranscodeCanceled() { + Log.d(TAG, "onTranscodeCanceled: "); + } + + @Override + public void onTranscodeFailed(@NonNull Throwable exception) { + // error! + Log.d(TAG, "onTranscodeFailed: "+exception.getMessage()); + + } + }).transcode(); + } + + + private File getAlbumDir() { + File storageDir = null; + AlbumStorageDirFactory mAlbumStorageDirFactory = new BaseAlbumDirFactory(); + if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { + storageDir = mAlbumStorageDirFactory.getAlbumStorageDir(getString(R.string.album_name)); + if (!storageDir.mkdirs()) { + if (!storageDir.exists()) { +// Logger.Log("failed to create directory"); +// return storageDir; + return null; + } + } + } else { +// Logger.Log("External storage is not mounted READ/WRITE."); + } + return storageDir; + } + @Override protected void onDestroy() { super.onDestroy(); diff --git a/demo/src/main/res/drawable/ic_pause_circle_outline_black_24dp.xml b/demo/src/main/res/drawable/ic_pause_circle_outline_black_24dp.xml new file mode 100644 index 000000000..83e5e1c2b --- /dev/null +++ b/demo/src/main/res/drawable/ic_pause_circle_outline_black_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/demo/src/main/res/drawable/ic_play_circle_outline_black_24dp.xml b/demo/src/main/res/drawable/ic_play_circle_outline_black_24dp.xml new file mode 100644 index 000000000..41bec11bd --- /dev/null +++ b/demo/src/main/res/drawable/ic_play_circle_outline_black_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/demo/src/main/res/drawable/ic_stop_white_24dp.xml b/demo/src/main/res/drawable/ic_stop_white_24dp.xml new file mode 100644 index 000000000..081c346d5 --- /dev/null +++ b/demo/src/main/res/drawable/ic_stop_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/demo/src/main/res/layout/activity_camera.xml b/demo/src/main/res/layout/activity_camera.xml index 2cc1d8a5a..3ba7cabc5 100644 --- a/demo/src/main/res/layout/activity_camera.xml +++ b/demo/src/main/res/layout/activity_camera.xml @@ -7,13 +7,31 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + + + - + CameraView + CameraView Album