diff --git a/build.gradle b/build.gradle
index 469b3eb1..f0056dc6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -20,7 +20,7 @@ allprojects {
ext {
compileSdkVersion = 29
- minSdkVersion = 15
+ minSdkVersion = 18
targetSdkVersion = 29
}
diff --git a/demo/build.gradle b/demo/build.gradle
index fe41eebd..90142b58 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 e7e1162e..cf23792f 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 00000000..7cb44e91
--- /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 00000000..58340b3a
--- /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 a60e8d43..1ef9460b 100644
--- a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
+++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
@@ -1,26 +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;
@@ -33,16 +40,21 @@ import com.otaliastudios.cameraview.controls.Mode;
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 {
@@ -54,16 +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);
@@ -77,6 +98,9 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
mImgPlayPauseVideo = findViewById(R.id.imgPlayPauseVideo);
mImgStopVideo = findViewById(R.id.imgStopVideo);
+ mTvTimer = findViewById(R.id.tvTimer);
+ mTvTimer.setText("");
+
mImgStopVideo.setVisibility(View.GONE);
@@ -86,14 +110,52 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
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);
- camera.takeVideo(new File(getFilesDir(), "video.mp4"), MAX_RECORDING_TIME);
+ 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);
}
}
@@ -103,6 +165,7 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
mImgStopVideo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
+ killVideoDurationElapsedTimeHandler(); // make sure not to init more then one instance
camera.stopVideo();
}
});
@@ -230,6 +293,42 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
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);
@@ -285,6 +384,30 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
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
@@ -300,6 +423,7 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
LOG.w("onVideoRecordingEnd!");
mImgPlayPauseVideo.setImageResource(R.drawable.ic_play_circle_outline_black_24dp);
mImgStopVideo.setVisibility(View.GONE);
+ mTvTimer.setText("");
}
@@ -331,6 +455,64 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
}
}
+ 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()) {
@@ -440,6 +622,37 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
// 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)) {
@@ -473,4 +686,18 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
}
//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 a37192cc..72f58161 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.CameraUtils;
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 class VideoPreviewActivity extends AppCompatActivity {
}
}
});
+
+
+
+ transcodeVideo();
}
void playVideo() {
@@ -101,6 +118,84 @@ public class VideoPreviewActivity extends AppCompatActivity {
}
}
+
+ 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/layout/activity_camera.xml b/demo/src/main/res/layout/activity_camera.xml
index 3d99aea1..3ba7cabc 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