diff --git a/app/build.gradle b/app/build.gradle index 00628cd..f2ef0ad 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,11 +1,13 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-android' android { compileSdkVersion COMPILE_SDK_VERSION as int buildToolsVersion BUILD_TOOLS_VERSION as String defaultConfig { applicationId "co.fitcom.videorecorder" - minSdkVersion 17 + minSdkVersion 21 targetSdkVersion COMPILE_SDK_VERSION as int versionCode 1 versionName "1.0" @@ -17,6 +19,10 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } buildToolsVersion '29.0.2' } @@ -28,4 +34,9 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' implementation project(':fancycamera') + implementation "androidx.core:core-ktx:1.1.0" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} +repositories { + mavenCentral() } diff --git a/app/src/androidTest/java/co/fitcom/videorecorder/ExampleInstrumentedTest.java b/app/src/androidTest/java/co/fitcom/videorecorder/ExampleInstrumentedTest.java deleted file mode 100644 index 575bc1e..0000000 --- a/app/src/androidTest/java/co/fitcom/videorecorder/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package co.fitcom.videorecorder; - -import android.content.Context; -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() throws Exception { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); - - assertEquals("co.fitcom.videorecorder", appContext.getPackageName()); - } -} diff --git a/app/src/androidTest/java/co/fitcom/videorecorder/ExampleInstrumentedTest.kt b/app/src/androidTest/java/co/fitcom/videorecorder/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..0cbe873 --- /dev/null +++ b/app/src/androidTest/java/co/fitcom/videorecorder/ExampleInstrumentedTest.kt @@ -0,0 +1,27 @@ +package co.fitcom.videorecorder + +import android.content.Context +import androidx.test.InstrumentationRegistry +import androidx.test.runner.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * @see [Testing documentation](http://d.android.com/tools/testing) + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + @Throws(Exception::class) + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + + assertEquals("co.fitcom.videorecorder", appContext.packageName) + } +} diff --git a/app/src/main/java/co/fitcom/videorecorder/Home.java b/app/src/main/java/co/fitcom/videorecorder/Home.java deleted file mode 100644 index 79ae73f..0000000 --- a/app/src/main/java/co/fitcom/videorecorder/Home.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Created By Osei Fortune on 4/19/18 10:38 AM - * Copyright (c) 2018 - * Last modified 4/19/18 10:38 AM - * - */ - -package co.fitcom.videorecorder; - -import androidx.appcompat.app.AppCompatActivity; -import android.os.Bundle; - -public class Home extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_home); - } -} diff --git a/app/src/main/java/co/fitcom/videorecorder/Home.kt b/app/src/main/java/co/fitcom/videorecorder/Home.kt new file mode 100644 index 0000000..9b5b9ca --- /dev/null +++ b/app/src/main/java/co/fitcom/videorecorder/Home.kt @@ -0,0 +1,19 @@ +/* + * Created By Osei Fortune on 4/19/18 10:38 AM + * Copyright (c) 2018 + * Last modified 4/19/18 10:38 AM + * + */ + +package co.fitcom.videorecorder + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle + +class Home : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_home) + } +} diff --git a/app/src/main/java/co/fitcom/videorecorder/MainActivity.java b/app/src/main/java/co/fitcom/videorecorder/MainActivity.java deleted file mode 100644 index 5606320..0000000 --- a/app/src/main/java/co/fitcom/videorecorder/MainActivity.java +++ /dev/null @@ -1,172 +0,0 @@ -package co.fitcom.videorecorder; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; - -import androidx.appcompat.app.AppCompatActivity; - -import android.util.Log; -import android.view.View; -import android.widget.TextView; -import android.widget.VideoView; - -import java.util.Timer; -import java.util.TimerTask; - -import co.fitcom.fancycamera.CameraEventListenerUI; -import co.fitcom.fancycamera.EventType; -import co.fitcom.fancycamera.FancyCamera; -import co.fitcom.fancycamera.PhotoEvent; -import co.fitcom.fancycamera.VideoEvent; - -public class MainActivity extends AppCompatActivity { - FancyCamera cameraView; - VideoView videoPlayer; - TextView durationView; - Timer timer; - TimerTask timerTask; - Timer levelsTask; - double level = 0; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - videoPlayer = findViewById(R.id.videoPlayer); - durationView = findViewById(R.id.durationView); - cameraView = findViewById(R.id.cameraView); - cameraView.setCameraPosition(FancyCamera.CameraPosition.BACK); - cameraView.setQuality(FancyCamera.Quality.HIGHEST.getValue()); - cameraView.setListener(new CameraEventListenerUI() { - @Override - public void onCameraOpenUI() { - Log.d("co.fitcom.test", "Camera Opened"); - } - - @Override - public void onCameraCloseUI() { - Log.d("co.fitcom.test", "Camera Close"); - } - - @Override - public void onPhotoEventUI(PhotoEvent event) { - - } - - @Override - public void onVideoEventUI(VideoEvent event) { - if (event.getType() == EventType.INFO && event.getMessage().equals(VideoEvent.EventInfo.RECORDING_FINISHED.toString())) { - timerTask.cancel(); - timer.cancel(); - videoPlayer.setVideoURI(Uri.fromFile(event.getFile())); - videoPlayer.start(); - } else if (event.getType() == EventType.INFO && event.getMessage().equals(VideoEvent.EventInfo.RECORDING_STARTED.toString())) { - System.out.println("Recording Started"); - timer = new Timer(); - timerTask = new TimerTask() { - @Override - public void run() { - runOnUiThread(new Runnable() { - @Override - public void run() { - durationView.setText(String.valueOf(cameraView.getDuration())); - } - }); - } - }; - timer.schedule(timerTask, 0, 1000); - - } else { - System.out.println(event.getMessage()); - } - } - - }); - cameraView.setSaveToGallery(true); - } - - public void startRecording(View view) { - cameraView.setQuality(FancyCamera.Quality.MAX_720P.getValue()); - cameraView.startRecording(); - } - - public void stopRecording(View view) { - cameraView.stopRecording(); - } - - public void toggleCamera(View view) { - cameraView.toggleCamera(); - } - - - public void goToVideo(View view) { - Intent i = new Intent(this, MainActivity.class); - startActivity(i); - } - - public void goToPhoto(View view) { - Intent i = new Intent(this, Photo.class); - startActivity(i); - } - - public void toggleFlash(View view) { - cameraView.toggleFlash(); - } - - @Override - protected void onPause() { - super.onPause(); - cameraView.release(); - if (levelsTask != null) { - levelsTask.cancel(); - } - if (timerTask != null) { - timerTask.cancel(); - } - timerTask = null; - levelsTask = null; - } - - void start() { - if (cameraView.isAudioLevelsEnabled()) { - if (levelsTask == null) { - levelsTask = new Timer(); - levelsTask.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - runOnUiThread(new Runnable() { - @Override - public void run() { - level = Math.pow(10, (0.02 * cameraView.getDB())); - Log.d("co.test", "Audio Levels" + level); - } - }); - } - }, 0, 1000); - } - } - cameraView.start(); - } - - @Override - protected void onResume() { - super.onResume(); - boolean b = cameraView.hasPermission(); - Log.d("hasPermission", Boolean.toString(b)); - if (cameraView.hasPermission()) { - start(); - } else { - cameraView.requestPermission(); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (cameraView.hasPermission()) { - start(); - } - } - -} diff --git a/app/src/main/java/co/fitcom/videorecorder/MainActivity.kt b/app/src/main/java/co/fitcom/videorecorder/MainActivity.kt new file mode 100644 index 0000000..767f743 --- /dev/null +++ b/app/src/main/java/co/fitcom/videorecorder/MainActivity.kt @@ -0,0 +1,134 @@ +package co.fitcom.videorecorder + +import android.content.Intent +import android.net.Uri +import android.os.Bundle + +import androidx.appcompat.app.AppCompatActivity + +import android.util.Log +import android.view.View +import android.widget.TextView +import android.widget.VideoView + +import java.util.Timer +import java.util.TimerTask + +import co.fitcom.fancycamera.CameraEventListenerUI +import co.fitcom.fancycamera.EventType +import co.fitcom.fancycamera.FancyCamera +import co.fitcom.fancycamera.PhotoEvent +import co.fitcom.fancycamera.VideoEvent + +class MainActivity : AppCompatActivity() { + internal lateinit var cameraView: FancyCamera + internal lateinit var videoPlayer: VideoView + internal lateinit var durationView: TextView + internal lateinit var timer: Timer + internal var timerTask: TimerTask? = null + internal var levelsTask: Timer? = null + internal var level = 0.0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + videoPlayer = findViewById(R.id.videoPlayer) + durationView = findViewById(R.id.durationView) + cameraView = findViewById(R.id.cameraView) + cameraView.setCameraPosition(FancyCamera.CameraPosition.BACK) + cameraView.setQuality(FancyCamera.Quality.HIGHEST.value) + cameraView.setListener(object : CameraEventListenerUI() { + override fun onCameraOpenUI() { + Log.d("co.fitcom.test", "Camera Opened") + } + + override fun onCameraCloseUI() { + Log.d("co.fitcom.test", "Camera Close") + } + + override fun onPhotoEventUI(event: PhotoEvent) { + + } + + override fun onVideoEventUI(event: VideoEvent) { + if (event.type === EventType.INFO && event.message == VideoEvent.EventInfo.RECORDING_FINISHED.toString()) { + timerTask!!.cancel() + timer.cancel() + videoPlayer.setVideoURI(Uri.fromFile(event.file)) + videoPlayer.start() + } else if (event.type === EventType.INFO && event.message == VideoEvent.EventInfo.RECORDING_STARTED.toString()) { + println("Recording Started") + timer = Timer() + timerTask = object : TimerTask() { + override fun run() { + runOnUiThread { durationView.text = cameraView.duration.toString() } + } + } + timer.schedule(timerTask, 0, 1000) + + } else { + println(event.message) + } + } + + }) + cameraView.saveToGallery = true + } + + fun startRecording(view: View) { + cameraView.setQuality(FancyCamera.Quality.MAX_720P.value) + cameraView.startRecording() + } + + fun stopRecording(view: View) { + cameraView.stopRecording() + } + + fun toggleCamera(view: View) { + cameraView.toggleCamera() + } + + + fun goToVideo(view: View) { + val i = Intent(this, MainActivity::class.java) + startActivity(i) + } + + fun goToPhoto(view: View) { + val i = Intent(this, Photo::class.java) + startActivity(i) + } + + fun toggleFlash(view: View) { + cameraView.toggleFlash() + } + + override fun onPause() { + super.onPause() + if (levelsTask != null) { + levelsTask!!.cancel() + } + if (timerTask != null) { + timerTask!!.cancel() + } + timerTask = null + levelsTask = null + } + + internal fun start() { + if (cameraView.isAudioLevelsEnabled) { + if (levelsTask == null) { + levelsTask = Timer() + levelsTask!!.scheduleAtFixedRate(object : TimerTask() { + override fun run() { + runOnUiThread { + level = Math.pow(10.0, 0.02 * cameraView.db) + Log.d("co.test", "Audio Levels$level") + } + } + }, 0, 1000) + } + } + } + +} diff --git a/app/src/main/java/co/fitcom/videorecorder/Photo.java b/app/src/main/java/co/fitcom/videorecorder/Photo.java deleted file mode 100644 index b0416f5..0000000 --- a/app/src/main/java/co/fitcom/videorecorder/Photo.java +++ /dev/null @@ -1,113 +0,0 @@ -package co.fitcom.videorecorder; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; - -import androidx.appcompat.app.AppCompatActivity; - -import android.os.Bundle; -import android.view.View; -import android.widget.ImageView; - -import co.fitcom.fancycamera.CameraEventListenerUI; -import co.fitcom.fancycamera.EventType; -import co.fitcom.fancycamera.FancyCamera; -import co.fitcom.fancycamera.PhotoEvent; -import co.fitcom.fancycamera.VideoEvent; - -public class Photo extends AppCompatActivity { - FancyCamera cameraView; - ImageView imageView; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_photo); - - imageView = findViewById(R.id.imageView); - cameraView = findViewById(R.id.PhotoView); - cameraView.setQuality(FancyCamera.Quality.HIGHEST.getValue()); - cameraView.setListener(new CameraEventListenerUI() { - @Override - public void onCameraOpenUI() { - - } - - @Override - public void onCameraCloseUI() { - - } - - @Override - public void onPhotoEventUI(PhotoEvent event) { - if (event.getType() == EventType.INFO && event.getMessage().equals(PhotoEvent.EventInfo.PHOTO_TAKEN.toString())) { - Bitmap myBitmap = BitmapFactory.decodeFile(event.getFile().getAbsolutePath()); - imageView.setImageBitmap(myBitmap); - } else { - System.out.println(event.getMessage()); - } - } - - @Override - public void onVideoEventUI(VideoEvent event) { - - } - - }); - cameraView.setSaveToGallery(true); - } - - public void takePhoto(View view) { - if(cameraView.hasStoragePermission()){ - cameraView.takePhoto(); - }else { - cameraView.requestStoragePermission(); - } - } - - public void toggleFlash(View view) { - cameraView.toggleFlash(); - } - - public void toggleCamera(View view) { - cameraView.toggleCamera(); - } - - public void goToHome(View view) { - Intent i = new Intent(this, Home.class); - startActivity(i); - } - - - @Override - protected void onPause() { - super.onPause(); - cameraView.release(); - } - - @Override - protected void onResume() { - super.onResume(); - cameraView.start(); - } - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - int count = 0; - for (int grant : grantResults) { - if (permissions[count].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE) && grant == PackageManager.PERMISSION_GRANTED) { - cameraView.takePhoto(); - break; - } - count++; - } - if (cameraView.hasPermission()) { - cameraView.start(); - } - } - -} diff --git a/app/src/main/java/co/fitcom/videorecorder/Photo.kt b/app/src/main/java/co/fitcom/videorecorder/Photo.kt new file mode 100644 index 0000000..4210079 --- /dev/null +++ b/app/src/main/java/co/fitcom/videorecorder/Photo.kt @@ -0,0 +1,96 @@ +package co.fitcom.videorecorder + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri + +import androidx.appcompat.app.AppCompatActivity + +import android.os.Bundle +import android.view.View +import android.widget.ImageView + +import co.fitcom.fancycamera.CameraEventListenerUI +import co.fitcom.fancycamera.EventType +import co.fitcom.fancycamera.FancyCamera +import co.fitcom.fancycamera.PhotoEvent +import co.fitcom.fancycamera.VideoEvent + +class Photo : AppCompatActivity() { + internal lateinit var cameraView: FancyCamera + internal lateinit var imageView: ImageView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_photo) + + imageView = findViewById(R.id.imageView) + cameraView = findViewById(R.id.PhotoView) + cameraView.setQuality(FancyCamera.Quality.HIGHEST.value) + cameraView.setListener(object : CameraEventListenerUI() { + override fun onCameraOpenUI() { + + } + + override fun onCameraCloseUI() { + + } + + override fun onPhotoEventUI(event: PhotoEvent) { + if (event.type === EventType.INFO && event.message == PhotoEvent.EventInfo.PHOTO_TAKEN.toString()) { + imageView.setImageURI(Uri.fromFile(event.file!!)) + } else { + println(event.message) + } + } + + override fun onVideoEventUI(event: VideoEvent) { + + } + + }) + cameraView.saveToGallery = true + } + + fun takePhoto(view: View) { + if (cameraView.hasStoragePermission()) { + cameraView.takePhoto() + } else { + cameraView.requestStoragePermission() + } + } + + fun toggleFlash(view: View) { + cameraView.toggleFlash() + } + + fun toggleCamera(view: View) { + cameraView.toggleCamera() + } + + fun goToHome(view: View) { + val i = Intent(this, Home::class.java) + startActivity(i) + } + + override fun onPause() { + super.onPause() + } + + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + var count = 0 + for (grant in grantResults) { + if (permissions[count] == Manifest.permission.WRITE_EXTERNAL_STORAGE && grant == PackageManager.PERMISSION_GRANTED) { + cameraView.takePhoto() + break + } + count++ + } + } + +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 76bd48e..c96d8ac 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -119,8 +119,8 @@ Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/app/src/test/java/co/fitcom/videorecorder/ExampleUnitTest.kt b/app/src/test/java/co/fitcom/videorecorder/ExampleUnitTest.kt new file mode 100644 index 0000000..9d50438 --- /dev/null +++ b/app/src/test/java/co/fitcom/videorecorder/ExampleUnitTest.kt @@ -0,0 +1,18 @@ +package co.fitcom.videorecorder + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see [Testing documentation](http://d.android.com/tools/testing) + */ +class ExampleUnitTest { + @Test + @Throws(Exception::class) + fun addition_isCorrect() { + assertEquals(4, (2 + 2).toLong()) + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 577ec19..5d2b31b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - + ext.kotlin_version = '1.3.60' + repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.android.tools.build:gradle:3.6.0-beta04' classpath 'com.novoda:bintray-release:0.9.1' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' // Add this line for jitpack @@ -20,6 +21,7 @@ buildscript { classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.0' } } + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/fancycamera/build.gradle b/fancycamera/build.gradle index f3f7da8..964fd0b 100644 --- a/fancycamera/build.gradle +++ b/fancycamera/build.gradle @@ -1,13 +1,15 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-android' apply plugin: 'com.novoda.bintray-release' apply plugin: 'com.github.dcendents.android-maven' // for jitpack -group='co.fitcom.fancycamera' +group = 'co.fitcom.fancycamera' ext { PUBLISH_GROUP_ID = 'co.fitcom' PUBLISH_ARTIFACT_ID = 'fancycamera' - PUBLISH_VERSION = '1.2.2' + PUBLISH_VERSION = '2.0.0-alpha0' } android { @@ -15,7 +17,7 @@ android { buildToolsVersion BUILD_TOOLS_VERSION as String defaultConfig { - minSdkVersion 17 + minSdkVersion 21 versionCode 1 versionName "1.0" @@ -31,14 +33,27 @@ android { } buildToolsVersion '29.0.2' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "androidx.appcompat:appcompat:1.1.0" + implementation "androidx.appcompat:appcompat:1.0.0" testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + implementation "androidx.camera:camera-core:$camerax_version" + implementation "androidx.camera:camera-camera2:$camerax_version" + implementation "androidx.camera:camera-view:$camerax_view_version" + implementation "androidx.camera:camera-extensions:$camerax_ext_version" + implementation "androidx.core:core-ktx:1.1.0" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "androidx.exifinterface:exifinterface:1.1.0" } @@ -46,7 +61,10 @@ publish { userOrg = 'triniwiz' groupId = 'co.fitcom' artifactId = 'fancycamera' - publishVersion = '1.2.2' + publishVersion = '2.0.0-alpha0' desc = '' website = '' } +repositories { + mavenCentral() +} diff --git a/fancycamera/src/androidTest/java/co/fitcom/fancycamera/ExampleInstrumentedTest.java b/fancycamera/src/androidTest/java/co/fitcom/fancycamera/ExampleInstrumentedTest.java deleted file mode 100644 index 7b5da4f..0000000 --- a/fancycamera/src/androidTest/java/co/fitcom/fancycamera/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package co.fitcom.fancycamera; - -import android.content.Context; -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() throws Exception { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); - - assertEquals("co.fitcom.fancycamera.test", appContext.getPackageName()); - } -} diff --git a/fancycamera/src/androidTest/java/co/fitcom/fancycamera/ExampleInstrumentedTest.kt b/fancycamera/src/androidTest/java/co/fitcom/fancycamera/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..d9cf175 --- /dev/null +++ b/fancycamera/src/androidTest/java/co/fitcom/fancycamera/ExampleInstrumentedTest.kt @@ -0,0 +1,27 @@ +package co.fitcom.fancycamera + +import android.content.Context +import androidx.test.InstrumentationRegistry +import androidx.test.runner.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * @see [Testing documentation](http://d.android.com/tools/testing) + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + @Throws(Exception::class) + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + + assertEquals("co.fitcom.fancycamera.test", appContext.packageName) + } +} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/AutoFitPreviewBuilder.kt b/fancycamera/src/main/java/co/fitcom/fancycamera/AutoFitPreviewBuilder.kt new file mode 100644 index 0000000..1c251c5 --- /dev/null +++ b/fancycamera/src/main/java/co/fitcom/fancycamera/AutoFitPreviewBuilder.kt @@ -0,0 +1,250 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package co.fitcom.fancycamera + + +import android.content.Context +import android.graphics.Matrix +import android.hardware.display.DisplayManager +import android.util.Log +import android.util.Size +import android.view.Display +import android.view.Surface +import android.view.TextureView +import android.view.View +import android.view.ViewGroup +import androidx.camera.core.Preview +import androidx.camera.core.PreviewConfig +import java.lang.IllegalArgumentException +import java.lang.ref.WeakReference +import kotlin.math.roundToInt + +/** + * Builder for [Preview] that takes in a [WeakReference] of the view finder and [PreviewConfig], + * then instantiates a [Preview] which automatically resizes and rotates reacting to config changes. + */ +class AutoFitPreviewBuilder private constructor( + config: PreviewConfig, viewFinderRef: WeakReference, listener: CameraEventListener?) { + + /** Public instance of preview use-case which can be used by consumers of this adapter */ + var useCase: Preview + + /** Internal variable used to keep track of the use case's output rotation */ + private var bufferRotation: Int = 0 + + /** Internal variable used to keep track of the view's rotation */ + private var viewFinderRotation: Int? = null + + /** Internal variable used to keep track of the use-case's output dimension */ + private var bufferDimens: Size = Size(0, 0) + + /** Internal variable used to keep track of the view's dimension */ + private var viewFinderDimens: Size = Size(0, 0) + + /** Internal variable used to keep track of the view's display */ + private var viewFinderDisplay: Int = -1 + + /** Internal reference of the [DisplayManager] */ + private lateinit var displayManager: DisplayManager + + + /** + * We need a display listener for orientation changes that do not trigger a configuration + * change, for example if we choose to override config change in manifest or for 180-degree + * orientation changes. + */ + private val displayListener = object : DisplayManager.DisplayListener { + override fun onDisplayAdded(displayId: Int) = Unit + override fun onDisplayRemoved(displayId: Int) = Unit + override fun onDisplayChanged(displayId: Int) { + val viewFinder = viewFinderRef.get() ?: return + if (displayId == viewFinderDisplay) { + val display = displayManager.getDisplay(displayId) + val rotation = getDisplaySurfaceRotation(display) + updateTransform(viewFinder, rotation, bufferDimens, viewFinderDimens) + } + } + } + + init { + // Make sure that the view finder reference is valid + val viewFinder = viewFinderRef.get() ?: + throw IllegalArgumentException("Invalid reference to view finder used") + + // Initialize the display and rotation from texture view information + viewFinderDisplay = viewFinder.display.displayId + viewFinderRotation = getDisplaySurfaceRotation(viewFinder.display) ?: 0 + + // Initialize public use-case with the given config + useCase = Preview(config) + + // Every time the view finder is updated, recompute layout + useCase.setOnPreviewOutputUpdateListener{ + val viewFinder = + viewFinderRef.get() ?: return@setOnPreviewOutputUpdateListener + Log.d(TAG, "Preview output changed. " + + "Size: ${it.textureSize}. Rotation: ${it.rotationDegrees}") + + // To update the SurfaceTexture, we have to remove it and re-add it + val parent = viewFinder.parent as ViewGroup + parent.removeView(viewFinder) + parent.addView(viewFinder, 0) + + // Update internal texture + viewFinder.surfaceTexture = it.surfaceTexture + + // Apply relevant transformations + bufferRotation = it.rotationDegrees + val rotation = getDisplaySurfaceRotation(viewFinder.display) + updateTransform(viewFinder, rotation, it.textureSize, viewFinderDimens) + listener?.onCameraOpen() + } + + // Every time the provided texture view changes, recompute layout + viewFinder.addOnLayoutChangeListener { view, left, top, right, bottom, _, _, _, _ -> + val viewFinder = view as TextureView + val newViewFinderDimens = Size(right - left, bottom - top) + Log.d(TAG, "View finder layout changed. Size: $newViewFinderDimens") + val rotation = getDisplaySurfaceRotation(viewFinder.display) + updateTransform(viewFinder, rotation, bufferDimens, newViewFinderDimens) + } + + // Every time the orientation of device changes, recompute layout + // NOTE: This is unnecessary if we listen to display orientation changes in the camera + // fragment and call [Preview.setTargetRotation()] (like we do in this sample), which will + // trigger [Preview.OnPreviewOutputUpdateListener] with a new + // [PreviewOutput.rotationDegrees]. CameraX Preview use case will not rotate the frames for + // us, it will just tell us about the buffer rotation with respect to sensor orientation. + // In this sample, we ignore the buffer rotation and instead look at the view finder's + // rotation every time [updateTransform] is called, which gets triggered by + // [CameraFragment] display listener -- but the approach taken in this sample is not the + // only valid one. + displayManager = viewFinder.context + .getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + displayManager.registerDisplayListener(displayListener, null) + + // Remove the display listeners when the view is detached to avoid holding a reference to + // it outside of the Fragment that owns the view. + // NOTE: Even though using a weak reference should take care of this, we still try to avoid + // unnecessary calls to the listener this way. + viewFinder.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(view: View?) = + displayManager.registerDisplayListener(displayListener, null) + override fun onViewDetachedFromWindow(view: View?) = + displayManager.unregisterDisplayListener(displayListener) + }) + } + + /** Helper function that fits a camera preview into the given [TextureView] */ + private fun updateTransform(textureView: TextureView?, rotation: Int?, newBufferDimens: Size, + newViewFinderDimens: Size) { + // This should not happen anyway, but now the linter knows + textureView ?: return + + if (rotation == viewFinderRotation && + newBufferDimens == bufferDimens && + newViewFinderDimens == viewFinderDimens) { + // Nothing has changed, no need to transform output again + return + } + + if (rotation == null) { + // Invalid rotation - wait for valid inputs before setting matrix + return + } else { + // Update internal field with new inputs + viewFinderRotation = rotation + } + + if (newBufferDimens.width == 0 || newBufferDimens.height == 0) { + // Invalid buffer dimens - wait for valid inputs before setting matrix + return + } else { + // Update internal field with new inputs + bufferDimens = newBufferDimens + } + + if (newViewFinderDimens.width == 0 || newViewFinderDimens.height == 0) { + // Invalid view finder dimens - wait for valid inputs before setting matrix + return + } else { + // Update internal field with new inputs + viewFinderDimens = newViewFinderDimens + } + + val matrix = Matrix() + Log.d(TAG, "Applying output transformation.\n" + + "View finder size: $viewFinderDimens.\n" + + "Preview output size: $bufferDimens\n" + + "View finder rotation: $viewFinderRotation\n" + + "Preview output rotation: $bufferRotation") + + // Compute the center of the view finder + val centerX = viewFinderDimens.width / 2f + val centerY = viewFinderDimens.height / 2f + + // Correct preview output to account for display rotation + matrix.postRotate(-viewFinderRotation!!.toFloat(), centerX, centerY) + + // Buffers are rotated relative to the device's 'natural' orientation: swap width and height + val bufferRatio = bufferDimens.height / bufferDimens.width.toFloat() + + val scaledWidth: Int + val scaledHeight: Int + // Match longest sides together -- i.e. apply center-crop transformation + if (viewFinderDimens.width > viewFinderDimens.height) { + scaledHeight = viewFinderDimens.width + scaledWidth = (viewFinderDimens.width * bufferRatio).roundToInt() + } else { + scaledHeight = viewFinderDimens.height + scaledWidth = (viewFinderDimens.height * bufferRatio).roundToInt() + } + + // Compute the relative scale value + val xScale = scaledWidth / viewFinderDimens.width.toFloat() + val yScale = scaledHeight / viewFinderDimens.height.toFloat() + + // Scale input buffers to fill the view finder + matrix.preScale(xScale, yScale, centerX, centerY) + + // Finally, apply transformations to our TextureView + textureView.setTransform(matrix) + + + } + + companion object { + private val TAG = AutoFitPreviewBuilder::class.java.simpleName + + /** Helper function that gets the rotation of a [Display] in degrees */ + fun getDisplaySurfaceRotation(display: Display?) = when(display?.rotation) { + Surface.ROTATION_0 -> 0 + Surface.ROTATION_90 -> 90 + Surface.ROTATION_180 -> 180 + Surface.ROTATION_270 -> 270 + else -> null + } + + /** + * Main entrypoint for users of this class: instantiates the adapter and returns an instance + * of [Preview] which automatically adjusts in size and rotation to compensate for + * config changes. + */ + fun build(config: PreviewConfig, viewFinder: TextureView, listener: CameraEventListener?) = + AutoFitPreviewBuilder(config, WeakReference(viewFinder), listener) + } +} \ No newline at end of file diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/Camera1.java b/fancycamera/src/main/java/co/fitcom/fancycamera/Camera1.java deleted file mode 100644 index ff8c4b6..0000000 --- a/fancycamera/src/main/java/co/fitcom/fancycamera/Camera1.java +++ /dev/null @@ -1,857 +0,0 @@ -/* - * Created By Osei Fortune on 2/16/18 8:42 PM - * Copyright (c) 2018 - * Last modified 2/16/18 7:39 PM - * - */ - -package co.fitcom.fancycamera; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.graphics.SurfaceTexture; -import android.hardware.Camera; -import android.media.CamcorderProfile; -import android.media.MediaRecorder; -import android.net.Uri; -import android.os.Environment; -import android.os.Handler; -import android.os.HandlerThread; -import android.view.Surface; -import android.view.TextureView; - -import androidx.annotation.Nullable; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.Timer; -import java.util.TimerTask; - - -public class Camera1 extends CameraBase { - private final Object lock = new Object(); - private Camera mCamera; - private Context mContext; - private FancyCamera.CameraPosition mPosition; - private FancyCamera.CameraOrientation mOrientation; - private Handler backgroundHandler; - private HandlerThread backgroundHandlerThread; - private boolean isRecording; - private MediaRecorder mMediaRecorder; - private boolean isStarted; - private boolean autoStart; - private CamcorderProfile mProfile; - private Timer mTimer; - private TimerTask mTimerTask; - private int mDuration = 0; - private boolean isFlashEnabled = false; - private boolean mAutoFocus = true; - private boolean disableHEVC = false; - private int maxVideoBitrate = -1; - private int maxAudioBitRate = -1; - private int maxVideoFrameRate = -1; - private boolean saveToGallery = false; - private boolean autoSquareCrop = false; - private boolean isAudioLevelsEnabled = false; - - Camera1(Context context, TextureView textureView, @Nullable FancyCamera.CameraPosition position) { - super(textureView); - mContext = context; - if (position == null) { - mPosition = FancyCamera.CameraPosition.BACK; - } else { - mPosition = position; - } - startBackgroundThread(); - setTextViewListener(new TextViewListener() { - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - - } - - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - - } - - @Override - public void onSurfaceTextureDestroyed(SurfaceTexture surface) { - - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - - } - }); - } - - @Override - MediaRecorder getRecorder() { - return mMediaRecorder; - } - - @Override - public void setEnableAudioLevels(boolean enable) { - synchronized (lock) { - isAudioLevelsEnabled = enable; - } - } - - @Override - public boolean isAudioLevelsEnabled() { - return isAudioLevelsEnabled; - } - - @Override - boolean getAutoSquareCrop() { - return autoSquareCrop; - } - - @Override - public void setAutoSquareCrop(boolean autoSquareCrop) { - synchronized (lock) { - this.autoSquareCrop = autoSquareCrop; - } - } - - @Override - public boolean getAutoFocus() { - return mAutoFocus; - } - - @Override - public void setAutoFocus(boolean focus) { - synchronized (lock) { - mAutoFocus = focus; - } - } - - @Override - public boolean getSaveToGallery() { - return saveToGallery; - } - - @Override - public void setSaveToGallery(boolean saveToGallery) { - synchronized (lock) { - this.saveToGallery = saveToGallery; - } - } - - @Override - public int getMaxAudioBitRate() { - return maxAudioBitRate; - } - - @Override - public int getMaxVideoBitrate() { - return maxVideoBitrate; - } - - @Override - public int getMaxVideoFrameRate() { - return maxVideoFrameRate; - } - - @Override - public boolean getDisableHEVC() { - return disableHEVC; - } - - @Override - public void setDisableHEVC(boolean disableHEVC) { - synchronized (lock) { - this.disableHEVC = disableHEVC; - } - } - - @Override - public void setMaxAudioBitRate(int maxAudioBitRate) { - synchronized (lock) { - this.maxAudioBitRate = maxAudioBitRate; - } - } - - @Override - public void setMaxVideoBitrate(int maxVideoBitrate) { - synchronized (lock) { - this.maxVideoBitrate = maxVideoBitrate; - } - } - - @Override - public void setMaxVideoFrameRate(int maxVideoFrameRate) { - synchronized (lock) { - this.maxVideoFrameRate = maxVideoFrameRate; - } - } - - @Override - public int getNumberOfCameras() { - return Camera.getNumberOfCameras(); - } - - @Override - boolean hasCamera() { - return Camera.getNumberOfCameras() > 0; - } - - @Override - boolean cameraStarted() { - return isStarted; - } - - @Override - boolean cameraRecording() { - return isRecording; - } - - private void startBackgroundThread() { - synchronized (lock) { - backgroundHandlerThread = new HandlerThread(CameraThread); - backgroundHandlerThread.start(); - backgroundHandler = new Handler(backgroundHandlerThread.getLooper()); - } - } - - private void stopBackgroundThread() { - synchronized (lock) { - backgroundHandlerThread.interrupt(); - try { - backgroundHandlerThread.join(); - backgroundHandlerThread = null; - backgroundHandler = null; - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - - @Override - void openCamera(int width, int height) { - try { - backgroundHandler.post(new Runnable() { - @Override - public void run() { - synchronized (lock) { - mCamera = Camera.open(mPosition.getValue()); - if (listener != null) { - listener.onCameraOpen(); - } - updatePreview(); - } - } - }); - - } catch (Exception e) { - e.printStackTrace(); - } - - } - - private void setupPreview() { - if (mCamera == null) return; - backgroundHandler.post(new Runnable() { - @Override - public void run() { - synchronized (lock) { - try { - mCamera.reconnect(); - mCamera.setPreviewTexture(getHolder().getSurfaceTexture()); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - }); - } - - @Override - void start() { - backgroundHandler.post(new Runnable() { - @Override - public void run() { - synchronized (lock) { - if (getHolder().isAvailable()) { - updatePreview(); - } - } - } - }); - } - - @Override - void stop() { - backgroundHandler.post(new Runnable() { - @Override - public void run() { - synchronized (lock) { - if (mCamera == null) return; - mCamera.stopPreview(); - try { - mCamera.setPreviewTexture(null); - } catch (IOException e) { - e.printStackTrace(); - } - mCamera.release(); - mCamera = null; - isStarted = false; - if (listener != null) { - listener.onCameraClose(); - } - } - } - }); - } - - @Override - void startRecording() { - synchronized (lock) { - if (isRecording) { - return; - } - Camera.Parameters params = mCamera.getParameters(); - CamcorderProfile profile = getCamcorderProfile(FancyCamera.Quality.values()[getQuality()]); - List mSupportedPreviewSizes = params.getSupportedPreviewSizes(); - List mSupportedVideoSizes = params.getSupportedVideoSizes(); - Camera.Size optimalSize = getOptimalVideoSize(mSupportedVideoSizes, - mSupportedPreviewSizes, getHolder().getWidth(), getHolder().getHeight()); - - profile.videoFrameWidth = optimalSize.width; - profile.videoFrameHeight = optimalSize.height; - params.setPreviewSize(profile.videoFrameWidth, profile.videoFrameHeight); - if (getAutoFocus()) { - List supportedFocusModes = params.getSupportedFocusModes(); - if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { - params.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); - } else if (supportedFocusModes.contains(android.hardware.Camera.Parameters.FOCUS_MODE_AUTO)) { - params.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_AUTO); - } - } else { - List supportedFocusModes = params.getSupportedFocusModes(); - if (supportedFocusModes.contains(android.hardware.Camera.Parameters.FOCUS_MODE_FIXED)) { - params.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_FIXED); - } - } - - setProfile(profile); - mCamera.setParameters(params); - if (mMediaRecorder == null) { - mMediaRecorder = new MediaRecorder(); - } else { - mMediaRecorder.reset(); - } - mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() { - @Override - public void onInfo(MediaRecorder mr, int what, int extra) { - if (listener != null) { - switch (what) { - case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED: - listener.onVideoEvent(new VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.MAX_DURATION_REACHED.toString())); - break; - case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING: - listener.onVideoEvent(new VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.MAX_FILESIZE_APPROACHING.toString())); - break; - case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED: - listener.onVideoEvent(new VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.MAX_FILESIZE_REACHED.toString())); - break; - case MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED: - listener.onVideoEvent(new VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.NEXT_OUTPUT_FILE_STARTED.toString())); - break; - case MediaRecorder.MEDIA_RECORDER_INFO_UNKNOWN: - listener.onVideoEvent(new VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.UNKNOWN.toString())); - break; - } - } - } - }); - - mMediaRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() { - @Override - public void onError(MediaRecorder mr, int what, int extra) { - if (listener != null) { - switch (what) { - case MediaRecorder.MEDIA_ERROR_SERVER_DIED: - listener.onVideoEvent(new VideoEvent(EventType.ERROR, null, VideoEvent.EventError.SERVER_DIED.toString())); - break; - case MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN: - listener.onVideoEvent(new VideoEvent(EventType.ERROR, null, VideoEvent.EventError.UNKNOWN.toString())); - break; - } - } - } - - }); - - DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US); - Date today = Calendar.getInstance().getTime(); - if (getSaveToGallery()) { - File cameraDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera"); - if (!cameraDir.exists()) { - final boolean mkdirs = cameraDir.mkdirs(); - } - setFile(new File(cameraDir, "VID_" + df.format(today) + ".mp4")); - } else { - setFile(new File(mContext.getExternalFilesDir(null), "VID_" + df.format(today) + ".mp4")); - } - mCamera.unlock(); - try { - CamcorderProfile camcorderProfile = getProfile(); - mMediaRecorder.setCamera(mCamera); - mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); - mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); - mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); - mMediaRecorder.setVideoSize(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight); - mMediaRecorder.setAudioChannels(camcorderProfile.audioChannels); - int videoBitRate = camcorderProfile.videoBitRate; - int maxVideoBitrate = camcorderProfile.videoBitRate; - if (this.maxVideoBitrate > -1) { - maxVideoBitrate = this.maxVideoBitrate; - } - int maxVideoFrameRate = camcorderProfile.videoFrameRate; - if (this.maxVideoFrameRate > -1) { - maxVideoFrameRate = this.maxVideoFrameRate; - } - int maxAudioBitRate = camcorderProfile.audioBitRate; - if (this.maxAudioBitRate > -1) { - maxAudioBitRate = this.maxAudioBitRate; - } - mMediaRecorder.setVideoFrameRate(Math.min(camcorderProfile.videoFrameRate, maxVideoFrameRate)); - mMediaRecorder.setVideoEncodingBitRate(Math.min(camcorderProfile.videoBitRate, maxVideoBitrate)); - mMediaRecorder.setAudioEncodingBitRate(Math.min(camcorderProfile.audioBitRate, maxAudioBitRate)); - mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); - mMediaRecorder.setAudioEncoder(camcorderProfile.audioCodec); - mMediaRecorder.setOutputFile(getFile().getPath()); - mMediaRecorder.prepare(); - mMediaRecorder.start(); - isRecording = true; - startDurationTimer(); - } catch (Exception e) { - e.printStackTrace(); - } - - if (listener != null) { - listener.onVideoEvent(new VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.RECORDING_STARTED.toString())); - } - } - } - - @Override - void takePhoto() { - synchronized (lock) { - if (isRecording) { - return; - } - Camera.Parameters params = mCamera.getParameters(); - CamcorderProfile profile = getCamcorderProfile(FancyCamera.Quality.values()[getQuality()]); - List mSupportedPreviewSizes = params.getSupportedPreviewSizes(); - List mSupportedVideoSizes = params.getSupportedVideoSizes(); - Camera.Size optimalSize = getOptimalVideoSize(mSupportedVideoSizes, - mSupportedPreviewSizes, getHolder().getWidth(), getHolder().getHeight()); - - int width = optimalSize.width; - int height = optimalSize.height; - if (getAutoSquareCrop()) { - int offsetWidth; - int offsetHeight; - if (width < height) { - offsetHeight = (height - width) / 2; - height = width - offsetHeight; - } else { - offsetWidth = (width - height) / 2; - width = height - offsetWidth; - } - } - - profile.videoFrameWidth = width; - profile.videoFrameHeight = height; - - - params.setPreviewSize(profile.videoFrameWidth, profile.videoFrameHeight); - setProfile(profile); - mCamera.setParameters(params); - DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US); - Date today = Calendar.getInstance().getTime(); - if (getSaveToGallery()) { - File cameraDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera"); - if (!cameraDir.exists()) { - final boolean mkdirs = cameraDir.mkdirs(); - } - setFile(new File(cameraDir, "PIC_" + df.format(today) + ".jpg")); - } else { - setFile(new File(mContext.getExternalFilesDir(null), "PIC_" + df.format(today) + ".jpg")); - } - mCamera.takePicture(null, null, new Camera.PictureCallback() { - @Override - public void onPictureTaken(byte[] data, Camera camera) { - FileOutputStream fos = null; - try { - fos = new FileOutputStream(getFile()); - fos.write(data); - Uri contentUri = Uri.fromFile(getFile()); - Intent mediaScanIntent = new android.content.Intent( - "android.intent.action.MEDIA_SCANNER_SCAN_FILE", - contentUri - ); - mContext.sendBroadcast(mediaScanIntent); - if (getListener() != null) { - PhotoEvent event = new PhotoEvent(EventType.INFO, getFile(), PhotoEvent.EventInfo.PHOTO_TAKEN.toString()); - getListener().onPhotoEvent(event); - } - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (fos != null) { - try { - fos.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } - }); - } - } - - @Override - void stopRecording() { - synchronized (lock) { - if (!isRecording) { - return; - } - try { - mMediaRecorder.stop(); - stopDurationTimer(); - mMediaRecorder.reset(); - mMediaRecorder.release(); - mMediaRecorder = null; - Uri contentUri = Uri.fromFile(getFile()); - Intent mediaScanIntent = new android.content.Intent( - "android.intent.action.MEDIA_SCANNER_SCAN_FILE", - contentUri - ); - mContext.sendBroadcast(mediaScanIntent); - if (listener != null) { - listener.onVideoEvent(new VideoEvent(EventType.INFO, getFile(), VideoEvent.EventInfo.RECORDING_FINISHED.toString())); - } - } catch (Exception e) { - e.printStackTrace(); - final boolean delete = getFile().delete(); - stopDurationTimer(); - } finally { - isRecording = false; - mCamera.lock(); - } - } - } - - @Override - void toggleCamera() { - synchronized (lock) { - stop(); - if (mPosition == FancyCamera.CameraPosition.BACK) { - setCameraPosition(FancyCamera.CameraPosition.FRONT); - } else { - setCameraPosition(FancyCamera.CameraPosition.BACK); - } - openCamera(getHolder().getWidth(), getHolder().getHeight()); - // updatePreview(); - } - } - - @Override - void updatePreview() { - backgroundHandler.post(new Runnable() { - @Override - public void run() { - synchronized (lock) { - setupPreview(); - if (mCamera != null) { - if (isStarted) { - mCamera.stopPreview(); - isStarted = false; - } - updatePreviewSize(); - updateCameraDisplayOrientation((Activity) mContext, mPosition.getValue(), mCamera); - setupPreview(); - if (!isStarted) { - mCamera.startPreview(); - isStarted = true; - } - isStarted = true; - } - } - } - }); - } - - @Override - void release() { - synchronized (lock) { - if (isRecording) { - stopRecording(); - } - stop(); - } - } - - @Override - void setCameraPosition(FancyCamera.CameraPosition position) { - synchronized (lock) { - stop(); - - if (Camera.getNumberOfCameras() < 2) { - mPosition = FancyCamera.CameraPosition.BACK; - } else { - mPosition = position; - } - if (isStarted) { - start(); - } - } - } - - @Override - void setCameraOrientation(FancyCamera.CameraOrientation orientation) { - synchronized (lock) { - mOrientation = orientation; - } - } - - - @Override - boolean hasFlash() { - if (mCamera != null) { - Camera.Parameters parameters = mCamera.getParameters(); - boolean hasFlash = false; - for (String mode : parameters.getSupportedFlashModes()) { - if (mode.equals("on") || mode.equals("auto")) { - hasFlash = true; - break; - } - } - return hasFlash; - } - - return false; - } - - - @Override - void toggleFlash() { - synchronized (lock) { - if (!hasFlash()) { - return; - } - isFlashEnabled = !isFlashEnabled; - if (mCamera != null) { - Camera.Parameters parameters = mCamera.getParameters(); - parameters.setFlashMode(isFlashEnabled ? Camera.Parameters.FLASH_MODE_ON : Camera.Parameters.FLASH_MODE_OFF); - mCamera.setParameters(parameters); - } - } - } - - @Override - void enableFlash() { - synchronized (lock) { - if (!hasFlash()) { - return; - } - isFlashEnabled = true; - if (mCamera != null) { - Camera.Parameters parameters = mCamera.getParameters(); - parameters.setFlashMode(Camera.Parameters.FLASH_MODE_ON); - mCamera.setParameters(parameters); - } - } - } - - @Override - void disableFlash() { - synchronized (lock) { - if (!hasFlash()) { - return; - } - isFlashEnabled = false; - if (mCamera != null) { - Camera.Parameters parameters = mCamera.getParameters(); - parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); - mCamera.setParameters(parameters); - } - } - } - - @Override - boolean flashEnabled() { - return isFlashEnabled; - } - - private void setProfile(CamcorderProfile profile) { - synchronized (lock) { - mProfile = profile; - } - } - - private CamcorderProfile getProfile() { - return mProfile; - } - - private void updatePreviewSize() { - synchronized (lock) { - Camera.Parameters params = mCamera.getParameters(); - List mSupportedPreviewSizes = params.getSupportedPreviewSizes(); - List mSupportedVideoSizes = params.getSupportedVideoSizes(); - Camera.Size optimalSize = getOptimalVideoSize(mSupportedVideoSizes, - mSupportedPreviewSizes, getHolder().getWidth(), getHolder().getHeight()); - params.setPreviewSize(optimalSize.width, optimalSize.height); - mCamera.setParameters(params); - } - } - - private void updateCameraDisplayOrientation(Activity activity, - int cameraId, Camera camera) { - synchronized (lock) { - Camera.CameraInfo info = new Camera.CameraInfo(); - Camera.getCameraInfo(cameraId, info); - int rotation = activity.getWindowManager().getDefaultDisplay() - .getRotation(); - int degrees = 0; - switch (rotation) { - case Surface.ROTATION_0: - degrees = 0; - break; - case Surface.ROTATION_90: - degrees = 90; - break; - case Surface.ROTATION_180: - degrees = 180; - break; - case Surface.ROTATION_270: - degrees = 270; - break; - } - - int result; - if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { - result = (info.orientation + degrees) % 360; - result = (360 - result) % 360; - } else { - result = (info.orientation - degrees + 360) % 360; - } - camera.setDisplayOrientation(result); - } - } - - private CamcorderProfile getCamcorderProfile(FancyCamera.Quality quality) { - CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_LOW); - switch (quality) { - case MAX_480P: - if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) { - profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P); - } else { - profile = getCamcorderProfile(FancyCamera.Quality.QVGA); - } - break; - case MAX_720P: - if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) { - profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P); - } else { - profile = getCamcorderProfile(FancyCamera.Quality.MAX_480P); - } - break; - case MAX_1080P: - if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) { - profile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P); - } else { - profile = getCamcorderProfile(FancyCamera.Quality.MAX_720P); - } - - break; - case MAX_2160P: - try { - profile = CamcorderProfile.get(CamcorderProfile.QUALITY_2160P); - } catch (Exception e) { - profile = getCamcorderProfile(FancyCamera.Quality.HIGHEST); - } - break; - case HIGHEST: - profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH); - break; - case LOWEST: - profile = CamcorderProfile.get(CamcorderProfile.QUALITY_LOW); - break; - case QVGA: - if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QVGA)) { - profile = CamcorderProfile.get(CamcorderProfile.QUALITY_QVGA); - } else { - profile = getCamcorderProfile(FancyCamera.Quality.LOWEST); - } - break; - - } - return profile; - } - - private Camera.Size getOptimalVideoSize(List supportedVideoSizes, - List previewSizes, int w, int h) { - // Use a very small tolerance because we want an exact match. - final double ASPECT_TOLERANCE = 0.1; - double targetRatio = (double) w / h; - - // Supported video sizes list might be null, it means that we are allowed to use the preview - // sizes - List videoSizes; - if (supportedVideoSizes != null) { - videoSizes = supportedVideoSizes; - } else { - videoSizes = previewSizes; - } - Camera.Size optimalSize = null; - - // Start with max value and refine as we iterate over available video sizes. This is the - // minimum difference between view and camera height. - double minDiff = Double.MAX_VALUE; - - // Target view height - int targetHeight = h; - - // Try to find a video size that matches aspect ratio and the target view size. - // Iterate over all available sizes and pick the largest size that can fit in the view and - // still maintain the aspect ratio. - for (Camera.Size size : videoSizes) { - double ratio = (double) size.width / size.height; - if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) - continue; - if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) { - optimalSize = size; - minDiff = Math.abs(size.height - targetHeight); - } - } - - // Cannot find video size that matches the aspect ratio, ignore the requirement - if (optimalSize == null) { - minDiff = Double.MAX_VALUE; - for (Camera.Size size : videoSizes) { - if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) { - optimalSize = size; - minDiff = Math.abs(size.height - targetHeight); - } - } - } - return optimalSize; - } -} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/Camera1.kt b/fancycamera/src/main/java/co/fitcom/fancycamera/Camera1.kt new file mode 100644 index 0000000..68b0831 --- /dev/null +++ b/fancycamera/src/main/java/co/fitcom/fancycamera/Camera1.kt @@ -0,0 +1,698 @@ +/* + * Created By Osei Fortune on 2/16/18 8:42 PM + * Copyright (c) 2018 + * Last modified 2/16/18 7:39 PM + * + */ + +package co.fitcom.fancycamera + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.graphics.SurfaceTexture +import android.hardware.Camera +import android.media.CamcorderProfile +import android.media.MediaRecorder +import android.net.Uri +import android.os.Environment +import android.os.Handler +import android.os.HandlerThread +import android.view.Surface +import android.view.TextureView + +import java.io.File +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date +import java.util.Locale +import java.util.Timer +import java.util.TimerTask + + +class Camera1 internal constructor(private val mContext: Context, textureView: TextureView, position: FancyCamera.CameraPosition?) : CameraBase(textureView) { + override var didPauseForPermission: Boolean = false + override var quality: Int = 0 + private val lock = Any() + private var mCamera: Camera? = null + private var mPosition: FancyCamera.CameraPosition? = null + private var mOrientation: FancyCamera.CameraOrientation? = null + private var backgroundHandler: Handler? = null + private var backgroundHandlerThread: HandlerThread? = null + private var isRecording: Boolean = false + internal override var recorder: MediaRecorder? = null + private set + private var isStarted: Boolean = false + private val autoStart: Boolean = false + private var profile: CamcorderProfile? = null + set(profile) { + synchronized(lock) { + field = profile + } + } + private val mTimer: Timer? = null + private val mTimerTask: TimerTask? = null + private val mDuration = 0 + private var isFlashEnabled = false + override var autoFocus = true + set(focus) { + synchronized(lock) { + field = focus + } + } + override var disableHEVC = false + set(disableHEVC) { + synchronized(lock) { + field = disableHEVC + } + } + override var maxVideoBitrate = -1 + set(maxVideoBitrate) { + synchronized(lock) { + field = maxVideoBitrate + } + } + override var maxAudioBitRate = -1 + set(maxAudioBitRate) { + synchronized(lock) { + field = maxAudioBitRate + } + } + override var maxVideoFrameRate = -1 + set(maxVideoFrameRate) { + synchronized(lock) { + field = maxVideoFrameRate + } + } + override var saveToGallery = false + set(saveToGallery) { + synchronized(lock) { + field = saveToGallery + } + } + internal override var autoSquareCrop = false + set(autoSquareCrop) { + synchronized(lock) { + field = autoSquareCrop + } + } + override var isAudioLevelsEnabled = false + private set + + override val numberOfCameras: Int + get() = Camera.getNumberOfCameras() + + init { + if (position == null) { + mPosition = FancyCamera.CameraPosition.BACK + } else { + mPosition = position + } + startBackgroundThread() + textViewListener = object : TextViewListener { + override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { + + } + + override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) { + + } + + override fun onSurfaceTextureDestroyed(surface: SurfaceTexture) { + + } + + override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { + + } + } + } + + override fun setEnableAudioLevels(enable: Boolean) { + synchronized(lock) { + isAudioLevelsEnabled = enable + } + } + + internal override fun hasCamera(): Boolean { + return Camera.getNumberOfCameras() > 0 + } + + internal override fun cameraStarted(): Boolean { + return isStarted + } + + internal override fun cameraRecording(): Boolean { + return isRecording + } + + private fun startBackgroundThread() { + synchronized(lock) { + backgroundHandlerThread = HandlerThread(CameraBase.CameraThread) + backgroundHandlerThread!!.start() + backgroundHandler = Handler(backgroundHandlerThread!!.looper) + } + } + + private fun stopBackgroundThread() { + synchronized(lock) { + backgroundHandlerThread!!.interrupt() + try { + backgroundHandlerThread!!.join() + backgroundHandlerThread = null + backgroundHandler = null + } catch (e: InterruptedException) { + e.printStackTrace() + } + + } + } + + internal override fun openCamera(width: Int, height: Int) { + try { + backgroundHandler!!.post { + synchronized(lock) { + mCamera = Camera.open(mPosition!!.value) + listener?.onCameraOpen() + updatePreview() + } + } + + } catch (e: Exception) { + e.printStackTrace() + } + + } + + private fun setupPreview() { + if (mCamera == null) return + backgroundHandler!!.post { + synchronized(lock) { + try { + mCamera!!.reconnect() + mCamera!!.setPreviewTexture(holder.surfaceTexture) + } catch (e: IOException) { + e.printStackTrace() + } + + } + } + } + + internal override fun start() { + backgroundHandler!!.post { + synchronized(lock) { + if (holder.isAvailable) { + updatePreview() + } + } + } + } + + internal override fun stop() { + backgroundHandler!!.post(Runnable { + synchronized(lock) { + if (mCamera == null) return@Runnable + mCamera!!.stopPreview() + try { + mCamera!!.setPreviewTexture(null) + } catch (e: IOException) { + e.printStackTrace() + } + + mCamera!!.release() + mCamera = null + isStarted = false + listener?.onCameraClose() + } + }) + } + + internal override fun startRecording() { + synchronized(lock) { + if (isRecording) { + return + } + val params = mCamera!!.parameters + var profile = getCamcorderProfile(FancyCamera.Quality.values()[quality]) + val mSupportedPreviewSizes = params.supportedPreviewSizes + val mSupportedVideoSizes = params.supportedVideoSizes + val optimalSize = getOptimalVideoSize(mSupportedVideoSizes, + mSupportedPreviewSizes, holder.width, holder.height) + + profile.videoFrameWidth = optimalSize!!.width + profile.videoFrameHeight = optimalSize.height + params.setPreviewSize(profile.videoFrameWidth, profile.videoFrameHeight) + if (autoFocus) { + val supportedFocusModes = params.supportedFocusModes + if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { + params.focusMode = android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE + } else if (supportedFocusModes.contains(android.hardware.Camera.Parameters.FOCUS_MODE_AUTO)) { + params.focusMode = android.hardware.Camera.Parameters.FOCUS_MODE_AUTO + } + } else { + val supportedFocusModes = params.supportedFocusModes + if (supportedFocusModes.contains(android.hardware.Camera.Parameters.FOCUS_MODE_FIXED)) { + params.focusMode = android.hardware.Camera.Parameters.FOCUS_MODE_FIXED + } + } + + profile = profile + mCamera!!.parameters = params + if (recorder == null) { + recorder = MediaRecorder() + } else { + recorder!!.reset() + } + recorder!!.setOnInfoListener { mr, what, extra -> + if (listener != null) { + when (what) { + MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED -> listener?.onVideoEvent(VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.MAX_DURATION_REACHED.toString())) + MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING -> listener?.onVideoEvent(VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.MAX_FILESIZE_APPROACHING.toString())) + MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED -> listener?.onVideoEvent(VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.MAX_FILESIZE_REACHED.toString())) + MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED -> listener?.onVideoEvent(VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.NEXT_OUTPUT_FILE_STARTED.toString())) + MediaRecorder.MEDIA_RECORDER_INFO_UNKNOWN -> listener?.onVideoEvent(VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.UNKNOWN.toString())) + } + } + } + + recorder!!.setOnErrorListener { mr, what, extra -> + if (listener != null) { + when (what) { + MediaRecorder.MEDIA_ERROR_SERVER_DIED -> listener?.onVideoEvent(VideoEvent(EventType.ERROR, null, VideoEvent.EventError.SERVER_DIED.toString())) + MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN -> listener?.onVideoEvent(VideoEvent(EventType.ERROR, null, VideoEvent.EventError.UNKNOWN.toString())) + } + } + } + + val df = SimpleDateFormat("yyyyMMddHHmmss", Locale.US) + val today = Calendar.getInstance().time + if (saveToGallery) { + val cameraDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera") + if (!cameraDir.exists()) { + val mkdirs = cameraDir.mkdirs() + } + file = File(cameraDir, "VID_" + df.format(today) + ".mp4") + } else { + file = File(mContext.getExternalFilesDir(null), "VID_" + df.format(today) + ".mp4") + } + mCamera!!.unlock() + try { + val camcorderProfile = profile + recorder!!.setCamera(mCamera) + recorder!!.setAudioSource(MediaRecorder.AudioSource.MIC) + recorder!!.setVideoSource(MediaRecorder.VideoSource.CAMERA) + recorder!!.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) + recorder!!.setVideoSize(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight) + recorder!!.setAudioChannels(camcorderProfile.audioChannels) + val videoBitRate = camcorderProfile.videoBitRate + var maxVideoBitrate = camcorderProfile.videoBitRate + if (this.maxVideoBitrate > -1) { + maxVideoBitrate = this.maxVideoBitrate + } + var maxVideoFrameRate = camcorderProfile.videoFrameRate + if (this.maxVideoFrameRate > -1) { + maxVideoFrameRate = this.maxVideoFrameRate + } + var maxAudioBitRate = camcorderProfile.audioBitRate + if (this.maxAudioBitRate > -1) { + maxAudioBitRate = this.maxAudioBitRate + } + recorder!!.setVideoFrameRate(Math.min(camcorderProfile.videoFrameRate, maxVideoFrameRate)) + recorder!!.setVideoEncodingBitRate(Math.min(camcorderProfile.videoBitRate, maxVideoBitrate)) + recorder!!.setAudioEncodingBitRate(Math.min(camcorderProfile.audioBitRate, maxAudioBitRate)) + recorder!!.setVideoEncoder(MediaRecorder.VideoEncoder.H264) + recorder!!.setAudioEncoder(camcorderProfile.audioCodec) + recorder!!.setOutputFile(file!!.path) + recorder!!.prepare() + recorder!!.start() + isRecording = true + startDurationTimer() + } catch (e: Exception) { + e.printStackTrace() + } + + if (listener != null) { + listener?.onVideoEvent(VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.RECORDING_STARTED.toString())) + } + } + } + + internal override fun takePhoto() { + synchronized(lock) { + if (isRecording) { + return + } + val params = mCamera!!.parameters + var profile = getCamcorderProfile(FancyCamera.Quality.values()[quality]) + val mSupportedPreviewSizes = params.supportedPreviewSizes + val mSupportedVideoSizes = params.supportedVideoSizes + val optimalSize = getOptimalVideoSize(mSupportedVideoSizes, + mSupportedPreviewSizes, holder.width, holder.height) + + var width = optimalSize!!.width + var height = optimalSize.height + if (autoSquareCrop) { + val offsetWidth: Int + val offsetHeight: Int + if (width < height) { + offsetHeight = (height - width) / 2 + height = width - offsetHeight + } else { + offsetWidth = (width - height) / 2 + width = height - offsetWidth + } + } + + profile.videoFrameWidth = width + profile.videoFrameHeight = height + + + params.setPreviewSize(profile.videoFrameWidth, profile.videoFrameHeight) + profile = profile + mCamera!!.parameters = params + val df = SimpleDateFormat("yyyyMMddHHmmss", Locale.US) + val today = Calendar.getInstance().time + if (saveToGallery) { + val cameraDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera") + if (!cameraDir.exists()) { + val mkdirs = cameraDir.mkdirs() + } + file = File(cameraDir, "PIC_" + df.format(today) + ".jpg") + } else { + file = File(mContext.getExternalFilesDir(null), "PIC_" + df.format(today) + ".jpg") + } + mCamera!!.takePicture(null, null, Camera.PictureCallback { data, camera -> + var fos: FileOutputStream? = null + try { + fos = FileOutputStream(file!!) + fos.write(data) + val contentUri = Uri.fromFile(file) + val mediaScanIntent = android.content.Intent( + "android.intent.action.MEDIA_SCANNER_SCAN_FILE", + contentUri + ) + mContext.sendBroadcast(mediaScanIntent) + val event = PhotoEvent(EventType.INFO, file, PhotoEvent.EventInfo.PHOTO_TAKEN.toString()) + listener?.onPhotoEvent(event) + } catch (e: FileNotFoundException) { + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } finally { + if (fos != null) { + try { + fos.close() + } catch (e: IOException) { + e.printStackTrace() + } + + } + } + }) + } + } + + internal override fun stopRecording() { + synchronized(lock) { + if (!isRecording) { + return + } + try { + recorder!!.stop() + stopDurationTimer() + recorder!!.reset() + recorder!!.release() + recorder = null + val contentUri = Uri.fromFile(file) + val mediaScanIntent = android.content.Intent( + "android.intent.action.MEDIA_SCANNER_SCAN_FILE", + contentUri + ) + mContext.sendBroadcast(mediaScanIntent) + listener?.onVideoEvent(VideoEvent(EventType.INFO, file, VideoEvent.EventInfo.RECORDING_FINISHED.toString())) + } catch (e: Exception) { + e.printStackTrace() + val delete = file!!.delete() + stopDurationTimer() + } finally { + isRecording = false + mCamera!!.lock() + } + } + } + + internal override fun toggleCamera() { + synchronized(lock) { + stop() + if (mPosition == FancyCamera.CameraPosition.BACK) { + setCameraPosition(FancyCamera.CameraPosition.FRONT) + } else { + setCameraPosition(FancyCamera.CameraPosition.BACK) + } + openCamera(holder.width, holder.height) + // updatePreview(); + } + } + + internal override fun updatePreview() { + backgroundHandler!!.post { + synchronized(lock) { + setupPreview() + if (mCamera != null) { + if (isStarted) { + mCamera!!.stopPreview() + isStarted = false + } + updatePreviewSize() + updateCameraDisplayOrientation(mContext as Activity, mPosition!!.value, mCamera) + setupPreview() + if (!isStarted) { + mCamera!!.startPreview() + isStarted = true + } + isStarted = true + } + } + } + } + + internal override fun release() { + synchronized(lock) { + if (isRecording) { + stopRecording() + } + stop() + } + } + + internal override fun setCameraPosition(position: FancyCamera.CameraPosition) { + synchronized(lock) { + stop() + + if (Camera.getNumberOfCameras() < 2) { + mPosition = FancyCamera.CameraPosition.BACK + } else { + mPosition = position + } + if (isStarted) { + start() + } + } + } + + internal override fun setCameraOrientation(orientation: FancyCamera.CameraOrientation) { + synchronized(lock) { + mOrientation = orientation + } + } + + + internal override fun hasFlash(): Boolean { + if (mCamera != null) { + val parameters = mCamera!!.parameters + var hasFlash = false + for (mode in parameters.supportedFlashModes) { + if (mode == "on" || mode == "auto") { + hasFlash = true + break + } + } + return hasFlash + } + + return false + } + + + internal override fun toggleFlash() { + synchronized(lock) { + if (!hasFlash()) { + return + } + isFlashEnabled = !isFlashEnabled + if (mCamera != null) { + val parameters = mCamera!!.parameters + parameters.flashMode = if (isFlashEnabled) Camera.Parameters.FLASH_MODE_ON else Camera.Parameters.FLASH_MODE_OFF + mCamera!!.parameters = parameters + } + } + } + + internal override fun enableFlash() { + synchronized(lock) { + if (!hasFlash()) { + return + } + isFlashEnabled = true + if (mCamera != null) { + val parameters = mCamera!!.parameters + parameters.flashMode = Camera.Parameters.FLASH_MODE_ON + mCamera!!.parameters = parameters + } + } + } + + internal override fun disableFlash() { + synchronized(lock) { + if (!hasFlash()) { + return + } + isFlashEnabled = false + if (mCamera != null) { + val parameters = mCamera!!.parameters + parameters.flashMode = Camera.Parameters.FLASH_MODE_OFF + mCamera!!.parameters = parameters + } + } + } + + internal override fun flashEnabled(): Boolean { + return isFlashEnabled + } + + private fun updatePreviewSize() { + synchronized(lock) { + val params = mCamera!!.parameters + val mSupportedPreviewSizes = params.supportedPreviewSizes + val mSupportedVideoSizes = params.supportedVideoSizes + val optimalSize = getOptimalVideoSize(mSupportedVideoSizes, + mSupportedPreviewSizes, holder.width, holder.height) + params.setPreviewSize(optimalSize!!.width, optimalSize.height) + mCamera!!.parameters = params + } + } + + private fun updateCameraDisplayOrientation(activity: Activity, + cameraId: Int, camera: Camera?) { + synchronized(lock) { + val info = Camera.CameraInfo() + Camera.getCameraInfo(cameraId, info) + val rotation = activity.windowManager.defaultDisplay + .rotation + var degrees = 0 + when (rotation) { + Surface.ROTATION_0 -> degrees = 0 + Surface.ROTATION_90 -> degrees = 90 + Surface.ROTATION_180 -> degrees = 180 + Surface.ROTATION_270 -> degrees = 270 + } + + var result: Int + if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + result = (info.orientation + degrees) % 360 + result = (360 - result) % 360 + } else { + result = (info.orientation - degrees + 360) % 360 + } + camera!!.setDisplayOrientation(result) + } + } + + private fun getCamcorderProfile(quality: FancyCamera.Quality): CamcorderProfile { + var profile = CamcorderProfile.get(CamcorderProfile.QUALITY_LOW) + when (quality) { + FancyCamera.Quality.MAX_480P -> if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) { + profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P) + } else { + profile = getCamcorderProfile(FancyCamera.Quality.QVGA) + } + FancyCamera.Quality.MAX_720P -> if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) { + profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P) + } else { + profile = getCamcorderProfile(FancyCamera.Quality.MAX_480P) + } + FancyCamera.Quality.MAX_1080P -> if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) { + profile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P) + } else { + profile = getCamcorderProfile(FancyCamera.Quality.MAX_720P) + } + FancyCamera.Quality.MAX_2160P -> try { + profile = CamcorderProfile.get(CamcorderProfile.QUALITY_2160P) + } catch (e: Exception) { + profile = getCamcorderProfile(FancyCamera.Quality.HIGHEST) + } + + FancyCamera.Quality.HIGHEST -> profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH) + FancyCamera.Quality.LOWEST -> profile = CamcorderProfile.get(CamcorderProfile.QUALITY_LOW) + FancyCamera.Quality.QVGA -> if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QVGA)) { + profile = CamcorderProfile.get(CamcorderProfile.QUALITY_QVGA) + } else { + profile = getCamcorderProfile(FancyCamera.Quality.LOWEST) + } + } + return profile + } + + private fun getOptimalVideoSize(supportedVideoSizes: List?, + previewSizes: List, w: Int, h: Int): Camera.Size? { + // Use a very small tolerance because we want an exact match. + val ASPECT_TOLERANCE = 0.1 + val targetRatio = w.toDouble() / h + + // Supported video sizes list might be null, it means that we are allowed to use the preview + // sizes + val videoSizes: List + if (supportedVideoSizes != null) { + videoSizes = supportedVideoSizes + } else { + videoSizes = previewSizes + } + var optimalSize: Camera.Size? = null + + // Start with max value and refine as we iterate over available video sizes. This is the + // minimum difference between view and camera height. + var minDiff = java.lang.Double.MAX_VALUE + + // Target view height + + // Try to find a video size that matches aspect ratio and the target view size. + // Iterate over all available sizes and pick the largest size that can fit in the view and + // still maintain the aspect ratio. + for (size in videoSizes) { + val ratio = size.width.toDouble() / size.height + if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) + continue + if (Math.abs(size.height - h) < minDiff && previewSizes.contains(size)) { + optimalSize = size + minDiff = Math.abs(size.height - h).toDouble() + } + } + + // Cannot find video size that matches the aspect ratio, ignore the requirement + if (optimalSize == null) { + minDiff = java.lang.Double.MAX_VALUE + for (size in videoSizes) { + if (Math.abs(size.height - h) < minDiff && previewSizes.contains(size)) { + optimalSize = size + minDiff = Math.abs(size.height - h).toDouble() + } + } + } + return optimalSize + } +} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/Camera2.java b/fancycamera/src/main/java/co/fitcom/fancycamera/Camera2.java deleted file mode 100644 index 3843dbb..0000000 --- a/fancycamera/src/main/java/co/fitcom/fancycamera/Camera2.java +++ /dev/null @@ -1,1372 +0,0 @@ -/* - * Created By Osei Fortune on 2/16/18 8:42 PM - * Copyright (c) 2018 - * Last modified 2/16/18 8:42 PM - * - */ - -package co.fitcom.fancycamera; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.ImageFormat; -import android.graphics.Matrix; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.SurfaceTexture; -import android.graphics.YuvImage; -import android.hardware.camera2.CameraAccessException; -import android.hardware.camera2.CameraCaptureSession; -import android.hardware.camera2.CameraCharacteristics; -import android.hardware.camera2.CameraDevice; -import android.hardware.camera2.CameraManager; -import android.hardware.camera2.CameraMetadata; -import android.hardware.camera2.CaptureFailure; -import android.hardware.camera2.CaptureRequest; -import android.hardware.camera2.TotalCaptureResult; -import android.hardware.camera2.params.StreamConfigurationMap; -import android.media.CamcorderProfile; -import android.media.Image; -import android.media.ImageReader; -import android.media.MediaRecorder; -import android.net.Uri; -import android.os.Build; -import android.os.Environment; -import android.os.Handler; -import android.os.HandlerThread; -import android.util.Log; -import android.util.Size; -import android.util.SparseIntArray; -import android.view.Display; -import android.view.Surface; -import android.view.TextureView; -import android.view.WindowManager; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.ActivityCompat; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.Field; -import java.nio.ByteBuffer; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Locale; - -@androidx.annotation.RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) -class Camera2 extends CameraBase { - private static final Object lock = new Object(); - private static final String TAG = "Camera2.Fancy"; - private CameraManager mManager; - MediaRecorder mMediaRecorder; - private FancyCamera.CameraPosition mPosition; - private FancyCamera.CameraOrientation mOrientation; - private Context mContext; - private Handler backgroundHandler; - private HandlerThread backgroundHandlerThread; - private CameraCharacteristics characteristics; - private Size previewSize; - private Size videoSize; - private CaptureRequest.Builder mPreviewBuilder; - private CameraDevice mCameraDevice; - private CameraCaptureSession mPreviewSession; - private boolean isRecording = false; - private boolean isStarted = false; - private boolean isFlashEnabled = false; - private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90; - private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270; - private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray(); - private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray(); - private Integer mSensorOrientation; - private String cameraIdToOpen = "0"; - private boolean mAutoFocus = true; - private boolean disableHEVC = false; - private int maxVideoBitrate = -1; - private int maxAudioBitRate = -1; - private int maxVideoFrameRate = -1; - private boolean saveToGallery = false; - private boolean autoSquareCrop = false; - private boolean isAudioLevelsEnabled = false; - private ImageReader reader; - - private ImageReader.OnImageAvailableListener readOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { - @Override - public void onImageAvailable(ImageReader imageReader) { - Image image = imageReader.acquireLatestImage(); - try { - Bitmap bitmap = imageToBitmap(image); - save(bitmap); - } catch (Throwable t) { - // IOException and java.nio.BufferOverflowException are known possibilities, - // but likely OutOfMemoryError too - // Buffers and Bitmaps are crash prone - t.printStackTrace(); - } finally { - reader = null; - Uri contentUri = Uri.fromFile(getFile()); - Intent mediaScanIntent = new android.content.Intent( - "android.intent.action.MEDIA_SCANNER_SCAN_FILE", - contentUri - ); - mContext.sendBroadcast(mediaScanIntent); - if (getListener() != null) { - PhotoEvent event = new PhotoEvent(EventType.INFO, getFile(), PhotoEvent.EventInfo.PHOTO_TAKEN.toString()); - getListener().onPhotoEvent(event); - } else { - Log.w(TAG, "No listener found"); - } - } - } - - private Bitmap imageToBitmap(Image image) { - // NV21 is a plane of 8 bit Y values followed by interleaved Cb Cr - ByteBuffer ib = ByteBuffer.allocate(image.getHeight() * image.getWidth() * 2); - - ByteBuffer y = image.getPlanes()[0].getBuffer(); - ByteBuffer cr = image.getPlanes()[1].getBuffer(); - ByteBuffer cb = image.getPlanes()[2].getBuffer(); - ib.put(y); - ib.put(cb); - ib.put(cr); - - YuvImage yuvImage = new YuvImage(ib.array(), - ImageFormat.NV21, image.getWidth(), image.getHeight(), null); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - yuvImage.compressToJpeg(new Rect(0, 0, - image.getWidth(), image.getHeight()), 50, out); - byte[] imageBytes = out.toByteArray(); - return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length); - } - - private void save(Bitmap bm) throws IOException { - int originalWidth = bm.getWidth(); - int originalHeight = bm.getHeight(); - int offsetWidth = 0; - int offsetHeight = 0; - if (getAutoSquareCrop()) { - if (originalWidth < originalHeight) { - offsetHeight = (originalHeight - originalWidth) / 2; - originalHeight = originalWidth; - } else { - offsetWidth = (originalWidth - originalHeight) / 2; - originalWidth = originalHeight; - } - } - - // this flips the front camera image to not be 'mirrored effect' for selfies - // does not flip if using the back camera - Matrix matrix = new Matrix(); - if (mPosition == FancyCamera.CameraPosition.FRONT) { - float[] mirrorY = {-1, 0, 0, 0, 1, 0, 0, 0, 1}; - Matrix matrixMirrorY = new Matrix(); - matrixMirrorY.setValues(mirrorY); - matrix.postConcat(matrixMirrorY); - matrix.postRotate(90); - } else { - matrix.postRotate(mSensorOrientation); - } - - Bitmap rotated = Bitmap.createBitmap(bm, offsetWidth, offsetHeight, originalWidth, originalHeight, matrix, false); - - File cameraDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera"); - if (!cameraDir.exists()) { - final boolean mkdirs = cameraDir.mkdirs(); - } - try (OutputStream outputStream = new FileOutputStream(getFile())) { - rotated.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); - } - bm.recycle(); - rotated.recycle(); - } - }; - - Camera2(Context context, TextureView textureView, @Nullable FancyCamera.CameraPosition position, @Nullable FancyCamera.CameraOrientation orientation) { - super(textureView); - - if (position == null) { - mPosition = FancyCamera.CameraPosition.BACK; - } else { - mPosition = position; - } - - if (orientation == null) { - mOrientation = FancyCamera.CameraOrientation.UNKNOWN; - } else { - mOrientation = orientation; - } - - mContext = context; - startBackgroundThread(); - - setTextViewListener(new TextViewListener() { - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - - } - - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - if (getHolder() != null) { - Handler mainHandler = new Handler(mContext.getMainLooper()); - mainHandler.post(new Runnable() { - @Override - public void run() { - configureTransform(getHolder().getWidth(), getHolder().getHeight()); - } - }); - } - } - - @Override - public void onSurfaceTextureDestroyed(SurfaceTexture surface) { - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - } - }); - - } - - @Override - public void setEnableAudioLevels(boolean enable) { - synchronized (lock) { - isAudioLevelsEnabled = enable; - } - } - - @Override - public boolean isAudioLevelsEnabled() { - return isAudioLevelsEnabled; - } - - @Override - boolean getAutoSquareCrop() { - return autoSquareCrop; - } - - @Override - public void setAutoSquareCrop(boolean autoSquareCrop) { - synchronized (lock) { - this.autoSquareCrop = autoSquareCrop; - } - } - - @Override - public boolean getAutoFocus() { - return mAutoFocus; - } - - @Override - public void setAutoFocus(boolean focus) { - synchronized (lock) { - mAutoFocus = focus; - } - } - - @Override - public boolean getSaveToGallery() { - return saveToGallery; - } - - @Override - public void setSaveToGallery(boolean saveToGallery) { - synchronized (lock) { - this.saveToGallery = saveToGallery; - } - } - - @Override - public int getMaxAudioBitRate() { - return maxAudioBitRate; - } - - @Override - public int getMaxVideoBitrate() { - return maxVideoBitrate; - } - - @Override - public int getMaxVideoFrameRate() { - return maxVideoFrameRate; - } - - @Override - public boolean getDisableHEVC() { - return disableHEVC; - } - - @Override - public void setDisableHEVC(boolean disableHEVC) { - synchronized (lock) { - this.disableHEVC = disableHEVC; - } - } - - @Override - public void setMaxAudioBitRate(int maxAudioBitRate) { - synchronized (lock) { - this.maxAudioBitRate = maxAudioBitRate; - } - } - - @Override - public void setMaxVideoBitrate(int maxVideoBitrate) { - synchronized (lock) { - this.maxVideoBitrate = maxVideoBitrate; - } - } - - @Override - public void setMaxVideoFrameRate(int maxVideoFrameRate) { - synchronized (lock) { - this.maxVideoFrameRate = maxVideoFrameRate; - } - } - - @Override - public int getNumberOfCameras() { - if (mManager != null) { - try { - return mManager.getCameraIdList().length; - } catch (CameraAccessException e) { - e.printStackTrace(); - return 0; - } - } - return 0; - } - - private void startBackgroundThread() { - synchronized (lock) { - backgroundHandlerThread = new HandlerThread(CameraThread); - backgroundHandlerThread.start(); - backgroundHandler = new Handler(backgroundHandlerThread.getLooper()); - } - } - - - private void stopBackgroundThread() { - synchronized (lock) { - backgroundHandlerThread.interrupt(); - try { - backgroundHandlerThread.join(); - backgroundHandlerThread = null; - backgroundHandler = null; - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - - - @Override - boolean hasCamera() { - if (mManager == null) return false; - try { - return mManager.getCameraIdList().length > 0; - } catch (CameraAccessException e) { - e.printStackTrace(); - } - return false; - } - - @Override - boolean cameraStarted() { - return isStarted; - } - - @Override - boolean cameraRecording() { - return isRecording; - } - - - static { - DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90); - DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0); - DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270); - DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180); - } - - static { - INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270); - INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180); - INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90); - INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0); - } - - - @Override - void openCamera(int width, int height) { - synchronized (lock) { - if (mManager == null) { - mManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); - } - if (mManager == null) { - // emit error - return; - } - try { - String[] cameraList = mManager.getCameraIdList(); - int cameraOrientation; - StreamConfigurationMap map = null; - for (String cameraId : cameraList) { - if (mPosition == FancyCamera.CameraPosition.FRONT) { - characteristics = mManager.getCameraCharacteristics(cameraId); - cameraOrientation = characteristics.get(CameraCharacteristics.LENS_FACING); - if (cameraOrientation == CameraCharacteristics.LENS_FACING_FRONT) { - cameraIdToOpen = cameraId; - map = characteristics - .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); - break; - } - - } else { - characteristics = mManager.getCameraCharacteristics(cameraId); - cameraOrientation = characteristics.get(CameraCharacteristics.LENS_FACING); - if (cameraOrientation == CameraCharacteristics.LENS_FACING_BACK) { - cameraIdToOpen = cameraId; - map = characteristics - .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); - break; - } - } - } - - if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { - return; - } - - - if (map == null) { - throw new RuntimeException("Cannot get available preview/video sizes"); - } - - - previewSize = getPreviewSize(map.getOutputSizes(SurfaceTexture.class)); - videoSize = getPreviewSize(map.getOutputSizes(MediaRecorder.class)); - - mMediaRecorder = new MediaRecorder(); - mManager.openCamera(cameraIdToOpen, new CameraDevice.StateCallback() { - @Override - public void onOpened(@NonNull CameraDevice camera) { - synchronized (lock) { - mCameraDevice = camera; - isStarted = true; - if (getHolder() != null) { - Handler mainHandler = new Handler(mContext.getMainLooper()); - mainHandler.post(new Runnable() { - @Override - public void run() { - configureTransform(getHolder().getWidth(), getHolder().getHeight()); - } - }); - } - startPreview(); - if (listener != null) { - listener.onCameraOpen(); - } - } - - } - - @Override - public void onDisconnected(@NonNull CameraDevice camera) { - synchronized (lock) { - camera.close(); - isStarted = false; - mCameraDevice = null; - if (listener != null) { - listener.onCameraClose(); - } - } - } - - @Override - public void onError(@NonNull CameraDevice camera, int error) { - synchronized (lock) { - camera.close(); - mCameraDevice = null; - isStarted = false; - if (listener != null) { - listener.onCameraClose(); - } - } - } - }, backgroundHandler); - - } catch (Exception e) { - e.printStackTrace(); - } - } - - - } - - - @Override - MediaRecorder getRecorder() { - return mMediaRecorder; - } - - private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) { - builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); - } - - private static int getIntFieldIfExists(Class klass, String fieldName, - Class obj, int defaultVal) { - try { - Field f = klass.getDeclaredField(fieldName); - return f.getInt(obj); - } catch (Exception e) { - return defaultVal; - } - } - - private static boolean isEmulator() { - return Build.FINGERPRINT.startsWith("generic") - || Build.FINGERPRINT.startsWith("unknown") - || Build.MODEL.contains("google_sdk") - || Build.MODEL.contains("Emulator") - || Build.MODEL.contains("Android SDK built for x86") - || Build.MANUFACTURER.contains("Genymotion") - || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) - || "google_sdk".equals(Build.PRODUCT); - } - - @SuppressLint("InlinedApi") - private void setUpMediaRecorder() throws IOException { - final Activity activity = (Activity) mContext; - if (null == activity) { - return; - } - - synchronized (lock) { - mMediaRecorder = new MediaRecorder(); - mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); - mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US); - Date today = Calendar.getInstance().getTime(); - if (getSaveToGallery() && hasStoragePermission()) { - File cameraDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera"); - if (!cameraDir.exists()) { - final boolean mkdirs = cameraDir.mkdirs(); - } - setFile(new File(cameraDir, "VID_" + df.format(today) + ".mp4")); - } else { - setFile(new File(mContext.getExternalFilesDir(null), "VID_" + df.format(today) + ".mp4")); - } - CamcorderProfile profile = getCamcorderProfile(FancyCamera.Quality.values()[quality]); - mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); - mMediaRecorder.setVideoSize(videoSize.getWidth(), videoSize.getHeight()); - mMediaRecorder.setAudioChannels(profile.audioChannels); - - if (mOrientation == null || mOrientation == FancyCamera.CameraOrientation.UNKNOWN) { - int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); - - int orientation = activity.getResources().getConfiguration().orientation; - - if (orientation == Configuration.ORIENTATION_PORTRAIT) { - if (mSensorOrientation != null) { - switch (mSensorOrientation) { - case SENSOR_ORIENTATION_DEFAULT_DEGREES: - mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation)); - break; - case SENSOR_ORIENTATION_INVERSE_DEGREES: - mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation)); - break; - } - } - } else if (orientation == Configuration.ORIENTATION_LANDSCAPE && Surface.ROTATION_90 == rotation) { - mMediaRecorder.setOrientationHint(0); - } else if (orientation == Configuration.ORIENTATION_LANDSCAPE && Surface.ROTATION_270 == rotation) { - mMediaRecorder.setOrientationHint(0); - } - } else { - switch (mOrientation) { - case PORTRAIT_UPSIDE_DOWN: - mMediaRecorder.setOrientationHint(270); - break; - case LANDSCAPE_LEFT: - mMediaRecorder.setOrientationHint(0); - break; - case LANDSCAPE_RIGHT: - mMediaRecorder.setOrientationHint(180); - break; - default: - mMediaRecorder.setOrientationHint(90); - break; - } - } - - boolean isHEVCSupported = !disableHEVC && android.os.Build.VERSION.SDK_INT >= 24; - - // Use half bit rate for HEVC - int videoBitRate = isHEVCSupported ? profile.videoBitRate / 2 : profile.videoBitRate; - int maxVideoBitrate = profile.videoBitRate; - if (this.maxVideoBitrate > -1) { - maxVideoBitrate = this.maxVideoBitrate; - } - int maxVideoFrameRate = profile.videoFrameRate; - if (this.maxVideoFrameRate > -1) { - maxVideoFrameRate = this.maxVideoFrameRate; - } - int maxAudioBitRate = profile.audioBitRate; - if (this.maxAudioBitRate > -1) { - maxAudioBitRate = this.maxAudioBitRate; - } - - mMediaRecorder.setVideoFrameRate(Math.min(profile.videoFrameRate, maxVideoFrameRate)); - mMediaRecorder.setVideoEncodingBitRate(Math.min(videoBitRate, maxVideoBitrate)); - mMediaRecorder.setAudioEncodingBitRate(Math.min(profile.audioBitRate, maxAudioBitRate)); - - if (isHEVCSupported) { - int h265 = Camera2.getIntFieldIfExists(MediaRecorder.VideoEncoder.class, - "HEVC", null, MediaRecorder.VideoEncoder.DEFAULT); - if (h265 == MediaRecorder.VideoEncoder.DEFAULT) { - h265 = Camera2.getIntFieldIfExists(MediaRecorder.VideoEncoder.class, - "H265", null, MediaRecorder.VideoEncoder.DEFAULT); - } - // Emulator seems to dislike H264/HEVC - if (isEmulator()) { - mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); - } else { - mMediaRecorder.setVideoEncoder(h265); - } - } else { - mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); - } - mMediaRecorder.setAudioEncoder(profile.audioCodec); - mMediaRecorder.setOutputFile(getFile().getPath()); - mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() { - @Override - public void onInfo(MediaRecorder mr, int what, int extra) { - if (listener != null) { - switch (what) { - case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED: - listener.onVideoEvent(new VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.MAX_DURATION_REACHED.toString())); - break; - case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING: - listener.onVideoEvent(new VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.MAX_FILESIZE_APPROACHING.toString())); - break; - case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED: - listener.onVideoEvent(new VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.MAX_FILESIZE_REACHED.toString())); - break; - case MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED: - listener.onVideoEvent(new VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.NEXT_OUTPUT_FILE_STARTED.toString())); - break; - case MediaRecorder.MEDIA_RECORDER_INFO_UNKNOWN: - listener.onVideoEvent(new VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.UNKNOWN.toString())); - break; - } - } - } - }); - - mMediaRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() { - @Override - public void onError(MediaRecorder mr, int what, int extra) { - if (listener != null) { - switch (what) { - case MediaRecorder.MEDIA_ERROR_SERVER_DIED: - listener.onVideoEvent(new VideoEvent(EventType.ERROR, null, VideoEvent.EventError.SERVER_DIED.toString())); - break; - case MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN: - listener.onVideoEvent(new VideoEvent(EventType.ERROR, null, VideoEvent.EventError.UNKNOWN.toString())); - break; - } - } - } - - }); - - mMediaRecorder.prepare(); - } - } - - - private void updateAutoFocus(boolean isVideo) { - if (mAutoFocus) { - int[] modes = characteristics.get( - CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); - // Auto focus is not supported - if (modes == null || modes.length == 0 || - (modes.length == 1 && modes[0] == CameraCharacteristics.CONTROL_AF_MODE_OFF)) { - mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, - CaptureRequest.CONTROL_AF_MODE_OFF); - } else { - boolean hasVideoSupport = false; - boolean hasPhotoSupport = false; - boolean hasAutoSupport = false; - for (int mode : modes) { - if (mode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO) { - hasVideoSupport = true; - } - if (mode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) { - hasPhotoSupport = true; - } - if (mode == CaptureRequest.CONTROL_AF_MODE_AUTO) { - hasAutoSupport = true; - } - } - if (isVideo) { - if (hasVideoSupport) { - mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, - CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO); - } else if (hasAutoSupport) { - mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, - CaptureRequest.CONTROL_AF_MODE_AUTO); - } - } else { - if (hasPhotoSupport) { - mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, - CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); - } else if (hasAutoSupport) { - mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, - CaptureRequest.CONTROL_AF_MODE_AUTO); - } - - } - } - } else { - mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, - CaptureRequest.CONTROL_AF_MODE_OFF); - } - } - - private Size getPreviewSize(Size[] sizes) { - return getSupportedSize(sizes, quality); - } - - private Size getSupportedSize(Size[] sizes, int quality) { - WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - Display display = wm.getDefaultDisplay(); - Point deviceSize = new Point(); - display.getSize(deviceSize); - int width = deviceSize.x; - int height = deviceSize.y; - - Size optimalSize = null; - int count = 0; - for (Size size : sizes) { - count++; - if (quality == FancyCamera.Quality.LOWEST.getValue()) { - return sizes[sizes.length - 1]; - } else if (quality == FancyCamera.Quality.HIGHEST.getValue()) { - if (size.getHeight() <= height && size.getWidth() <= width) { - optimalSize = size; - break; - } else { - if (count == sizes.length - 1) { - optimalSize = sizes[sizes.length - 1]; - break; - } - } - } else if (quality == FancyCamera.Quality.MAX_480P.getValue()) { - if (size.getHeight() == 480 && size.getWidth() <= width) { - optimalSize = size; - break; - } else { - if (count == sizes.length - 1) { - optimalSize = getSupportedSize(sizes, FancyCamera.Quality.QVGA.getValue()); - break; - } - } - } else if (quality == FancyCamera.Quality.MAX_720P.getValue()) { - if (size.getHeight() == 720 && size.getWidth() <= width) { - optimalSize = size; - break; - } else { - if (count == sizes.length - 1) { - optimalSize = getSupportedSize(sizes, FancyCamera.Quality.MAX_480P.getValue()); - break; - } - } - } else if (quality == FancyCamera.Quality.MAX_1080P.getValue()) { - if (size.getHeight() == 1080 && size.getWidth() <= width) { - optimalSize = size; - break; - } else { - if (count == sizes.length - 1) { - optimalSize = getSupportedSize(sizes, FancyCamera.Quality.MAX_720P.getValue()); - break; - } - } - } else if (quality == FancyCamera.Quality.MAX_2160P.getValue()) { - if (size.getHeight() == 2160 && size.getWidth() <= width) { - optimalSize = size; - break; - } else { - if (count == sizes.length - 1) { - optimalSize = getSupportedSize(sizes, FancyCamera.Quality.MAX_1080P.getValue()); - break; - } - } - } else if (quality == FancyCamera.Quality.QVGA.getValue()) { - if (size.getHeight() == 240 && size.getWidth() <= width) { - optimalSize = size; - break; - } else { - if (count == sizes.length - 1) { - optimalSize = sizes[sizes.length - 1]; - break; - } - } - } - } - return optimalSize; - } - - - private void startPreview() { - synchronized (lock) { - if (mCameraDevice == null || !getHolder().isAvailable()) { - return; - } - SurfaceTexture texture = getHolder().getSurfaceTexture(); - assert texture != null; - texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); - try { - if (mCameraDevice == null) return; - mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); - - Surface previewSurface = new Surface(texture); - mPreviewBuilder.addTarget(previewSurface); - updateAutoFocus(true); - // tempOffFlash(mPreviewBuilder); - mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface), - new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(@NonNull CameraCaptureSession session) { - synchronized (lock) { - mPreviewSession = session; - updatePreview(); - } - } - - @Override - public void onConfigureFailed(@NonNull CameraCaptureSession session) { - Log.e(TAG, "configure fail " + session.toString()); - } - }, backgroundHandler); - } catch (CameraAccessException e) { - Log.e(TAG, "CameraAccessException " + e.toString()); - e.printStackTrace(); - } - } - } - - private void closePreviewSession() { - synchronized (lock) { - if (mPreviewSession != null) { - mPreviewSession.close(); - mPreviewSession = null; - } - - if (null != mMediaRecorder) { - mMediaRecorder.reset(); - mMediaRecorder.release(); - mMediaRecorder = null; - } - } - } - - @Override - void start() { - synchronized (lock) { - if (isStarted && mCameraDevice != null) { - return; - } - stop(); - openCamera(getHolder().getWidth(), getHolder().getHeight()); - } - } - - - @Override - void stop() { - synchronized (lock) { - if (!isStarted && mCameraDevice == null) { - return; - } - if (mCameraDevice != null) { - closePreviewSession(); - isStarted = false; - if (mCameraDevice == null) { - return; - } - mCameraDevice.close(); - mCameraDevice = null; - } - } - } - - - @Override - void startRecording() { - synchronized (lock) { - if (null == mCameraDevice || !getHolder().isAvailable() || null == previewSize || isRecording) { - return; - } - closePreviewSession(); - try { - setUpMediaRecorder(); - } catch (IOException e) { - e.printStackTrace(); - return; - } - SurfaceTexture texture = getHolder().getSurfaceTexture(); - assert texture != null; - texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); - try { - mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); - } catch (CameraAccessException e) { - e.printStackTrace(); - return; - } - updateAutoFocus(true); - setupFlash(mPreviewBuilder); - List surfaces = new ArrayList<>(); - Surface previewSurface = new Surface(texture); - surfaces.add(previewSurface); - mPreviewBuilder.addTarget(previewSurface); - Surface recorderSurface = mMediaRecorder.getSurface(); - surfaces.add(recorderSurface); - mPreviewBuilder.addTarget(recorderSurface); - try { - mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(@NonNull final CameraCaptureSession cameraCaptureSession) { - synchronized (lock) { - mPreviewSession = cameraCaptureSession; - updatePreview(); - Handler mainHandler = new Handler(mContext.getMainLooper()); - mainHandler.post(new Runnable() { - @Override - public void run() { - synchronized (lock) { - try { - mMediaRecorder.start(); - } catch (IllegalStateException e) { - try { - setUpMediaRecorder(); - } catch (IOException ex) { - ex.printStackTrace(); - } - } - isRecording = true; - startDurationTimer(); - if (listener != null) { - listener.onVideoEvent(new VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.RECORDING_STARTED.toString())); - } - } - } - }); - } - } - - @Override - public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { - Log.e(TAG, "onConfigureFailed"); - } - }, backgroundHandler); - } catch (CameraAccessException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - } - } - } - - private boolean isCapturingPhoto; - - @Override - void takePhoto() { - synchronized (lock) { - if (null == mCameraDevice || !getHolder().isAvailable() || null == previewSize || isRecording || isCapturingPhoto) { - return; - } - - try { - isCapturingPhoto = true; - Size[] jpegSizes = null; - - int width = 640; - int height = 480; - if (characteristics != null) { - jpegSizes = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(ImageFormat.JPEG); - } - - if (jpegSizes != null && jpegSizes.length > 0) { - Size size = jpegSizes[0]; - width = size.getHeight(); - height = size.getHeight(); - } - - reader = ImageReader.newInstance(width, height, ImageFormat.YUV_420_888, 2); - - SurfaceTexture texture = getHolder().getSurfaceTexture(); - assert texture != null; - texture.setDefaultBufferSize(width, height); - - List surfaces = new ArrayList<>(); - surfaces.add(reader.getSurface()); - Surface previewSurface = new Surface(texture); - surfaces.add(previewSurface); - - final CaptureRequest.Builder photoPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); - setupFlash(photoPreviewBuilder); - photoPreviewBuilder.addTarget(reader.getSurface()); - photoPreviewBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); - - - DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US); - Date today = Calendar.getInstance().getTime(); - if (getSaveToGallery() && hasStoragePermission()) { - File cameraDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera"); - if (!cameraDir.exists()) { - final boolean mkdirs = cameraDir.mkdirs(); - } - setFile(new File(cameraDir, "PIC_" + df.format(today) + ".jpg")); - } else { - setFile(new File(mContext.getExternalFilesDir(null), "PIC_" + df.format(today) + ".jpg")); - } - - reader.setOnImageAvailableListener(readOnImageAvailableListener, backgroundHandler); - - final CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() { - @Override - public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { - super.onCaptureCompleted(session, request, result); - synchronized (lock) { - stop(); - start(); //TODO allow user to choose - isCapturingPhoto = false; - } - } - - @Override - public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { - super.onCaptureFailed(session, request, failure); - Log.e(TAG, "onCaptureFailed " + session + " " + request); - } - }; - - mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(@NonNull CameraCaptureSession session) { - synchronized (lock) { - if (isStarted) { - try { - session.capture(photoPreviewBuilder.build(), captureCallback, backgroundHandler); - } catch (CameraAccessException e) { - e.printStackTrace(); - isCapturingPhoto = false; - } - } - } - } - - @Override - public void onConfigureFailed(@NonNull CameraCaptureSession session) { - synchronized (lock) { - isCapturingPhoto = false; - } - } - }, backgroundHandler); - } catch (CameraAccessException e) { - e.printStackTrace(); - isCapturingPhoto = false; - } - } - - } - - private void stopRecord() { - synchronized (lock) { - if (!isRecording) { - return; - } - - try { - if (mPreviewSession != null) { - mPreviewSession.stopRepeating(); - mPreviewSession.abortCaptures(); - } - } catch (CameraAccessException e) { - e.printStackTrace(); - } - - - try { - if (mMediaRecorder != null) { - mMediaRecorder.stop(); - } - } catch (RuntimeException e) { - //handle the exception - } - - isStarted = false; - isRecording = false; - stopDurationTimer(); - - if (mMediaRecorder != null) { - mMediaRecorder.reset(); - mMediaRecorder.release(); - } - - mMediaRecorder = null; - - if (getFile() != null) { - Uri contentUri = Uri.fromFile(getFile()); - Intent mediaScanIntent = new android.content.Intent( - "android.intent.action.MEDIA_SCANNER_SCAN_FILE", - contentUri - ); - mContext.sendBroadcast(mediaScanIntent); - } - - if (listener != null) { - listener.onVideoEvent(new VideoEvent(EventType.INFO, getFile(), VideoEvent.EventInfo.RECORDING_FINISHED.toString())); - } - setFile(null); - } - } - - @Override - void stopRecording() { - synchronized (lock) { - stopRecord(); - start(); - } - } - - - private void configureTransform(int viewWidth, int viewHeight) { - synchronized (lock) { - Activity activity = (Activity) mContext; - if (null == getHolder() || null == previewSize || null == activity) { - return; - } - int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); - Matrix matrix = new Matrix(); - RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); - RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); - float centerX = viewRect.centerX(); - float centerY = viewRect.centerY(); - - bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); - matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); - float scale = Math.max( - (float) viewHeight / previewSize.getHeight(), - (float) viewWidth / previewSize.getWidth()); - matrix.postScale(scale, scale, centerX, centerY); - - if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { - matrix.postRotate(90 * (rotation - 2), centerX, centerY); - } else if (Surface.ROTATION_180 == rotation) { - matrix.postRotate(180, centerX, centerY); - } else { - matrix.postRotate(0, centerX, centerY); - } - getHolder().setTransform(matrix); - } - } - - - @Override - void toggleCamera() { - synchronized (lock) { - if (isRecording) { - stopRecord(); - } - stop(); - if (mPosition == FancyCamera.CameraPosition.BACK) { - mPosition = FancyCamera.CameraPosition.FRONT; - } else { - mPosition = FancyCamera.CameraPosition.BACK; - } - start(); - - } - } - - - private void tempOffFlash(CaptureRequest.Builder builder) { - builder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); - } - - private void setupFlash(CaptureRequest.Builder builder) { - builder.set(CaptureRequest.FLASH_MODE, isFlashEnabled ? CaptureRequest.FLASH_MODE_TORCH : CaptureRequest.FLASH_MODE_OFF); - } - - @Override - void updatePreview() { - synchronized (lock) { - if (null == mCameraDevice || null == mPreviewSession) { - return; - } - setUpCaptureRequestBuilder(mPreviewBuilder); - try { - mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, backgroundHandler); - } catch (CameraAccessException e) { - e.printStackTrace(); - } catch (IllegalStateException e) { - e.printStackTrace(); - } - } - } - - @Override - void release() { - synchronized (lock) { - if (isRecording) { - stopRecord(); - } - stop(); - } - } - - - @Override - void setCameraPosition(FancyCamera.CameraPosition position) { - synchronized (lock) { - if (position != mPosition) { - mPosition = position; - if (null == mCameraDevice) { - return; - } - stop(); - start(); - } - } - } - - @Override - void setCameraOrientation(FancyCamera.CameraOrientation orientation) { - synchronized (lock) { - mOrientation = orientation; - } - } - - @Override - boolean hasFlash() { - if (characteristics != null) { - Boolean info = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); - if (info != null) { - return info; - } - } - return false; - } - - @Override - void toggleFlash() { - if (!hasFlash()) { - return; - } - isFlashEnabled = !isFlashEnabled; - if (mCameraDevice != null) { - start(); - } - } - - @Override - void enableFlash() { - if (!hasFlash()) { - return; - } - isFlashEnabled = true; - if (mCameraDevice != null) { - start(); - } - } - - @Override - void disableFlash() { - if (!hasFlash()) { - return; - } - isFlashEnabled = false; - if (mCameraDevice != null) { - start(); - } - - } - - @Override - boolean flashEnabled() { - return isFlashEnabled; - } - - private CamcorderProfile getCamcorderProfile(FancyCamera.Quality quality) { - CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_LOW); - switch (quality) { - case MAX_480P: - if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) { - profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P); - } else { - profile = getCamcorderProfile(FancyCamera.Quality.QVGA); - } - break; - case MAX_720P: - if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) { - profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P); - } else { - profile = getCamcorderProfile(FancyCamera.Quality.MAX_480P); - } - break; - case MAX_1080P: - if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) { - profile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P); - } else { - profile = getCamcorderProfile(FancyCamera.Quality.MAX_720P); - } - - break; - case MAX_2160P: - try { - profile = CamcorderProfile.get(CamcorderProfile.QUALITY_2160P); - } catch (Exception e) { - profile = getCamcorderProfile(FancyCamera.Quality.HIGHEST); - } - break; - case HIGHEST: - profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH); - break; - case LOWEST: - profile = CamcorderProfile.get(CamcorderProfile.QUALITY_LOW); - break; - case QVGA: - if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QVGA)) { - profile = CamcorderProfile.get(CamcorderProfile.QUALITY_QVGA); - } else { - profile = getCamcorderProfile(FancyCamera.Quality.LOWEST); - } - break; - - } - return profile; - } - -} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/Camera2.kt b/fancycamera/src/main/java/co/fitcom/fancycamera/Camera2.kt new file mode 100644 index 0000000..f868a28 --- /dev/null +++ b/fancycamera/src/main/java/co/fitcom/fancycamera/Camera2.kt @@ -0,0 +1,1158 @@ +/* + * Created By Osei Fortune on 2/16/18 8:42 PM + * Copyright (c) 2018 + * Last modified 2/16/18 8:42 PM + * + */ + +package co.fitcom.fancycamera + +import android.Manifest +import android.annotation.SuppressLint +import android.app.Activity +import android.app.Application +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.content.res.Configuration +import android.graphics.* +import android.hardware.camera2.CameraAccessException +import android.hardware.camera2.CameraCaptureSession +import android.hardware.camera2.CameraCharacteristics +import android.hardware.camera2.CameraDevice +import android.hardware.camera2.CameraManager +import android.hardware.camera2.CameraMetadata +import android.hardware.camera2.CaptureFailure +import android.hardware.camera2.CaptureRequest +import android.hardware.camera2.TotalCaptureResult +import android.hardware.camera2.params.StreamConfigurationMap +import android.hardware.display.DisplayManager +import android.media.* +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.os.Handler +import android.os.HandlerThread +import android.util.* +import androidx.camera.view.CameraView +import androidx.core.app.ActivityCompat + +import java.lang.reflect.Field +import java.nio.ByteBuffer +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.ArrayList +import java.util.Calendar +import java.util.Collections +import java.util.Date +import java.util.Locale +import android.view.* +import androidx.camera.core.* +import androidx.camera.core.impl.utils.executor.CameraXExecutors +import androidx.camera.view.TextureViewMeteringPointFactory +import androidx.exifinterface.media.ExifInterface +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.OnLifecycleEvent +import com.google.common.util.concurrent.ListenableFuture +import java.io.* +import java.lang.ref.WeakReference +import java.nio.Buffer +import java.nio.file.FileSystem +import java.nio.file.Files +import java.nio.file.Path +import java.text.ParseException +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import kotlin.math.max +import kotlin.math.min + +@androidx.annotation.RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +@SuppressLint("RestrictedApi") +internal class Camera2(private val mContext: Context, private val textureView: TextureView, private val position: FancyCamera.CameraPosition?, private val orientation: FancyCamera.CameraOrientation?) : CameraBase(textureView) { + override fun updatePreview() { + + } + + private var mManager: CameraManager? = null + internal override var recorder: MediaRecorder? = null + private var mPosition: FancyCamera.CameraPosition? = null + private var mOrientation: FancyCamera.CameraOrientation? = null + private var backgroundHandler: Handler? = null + private var backgroundHandlerThread: HandlerThread? = null + private var characteristics: CameraCharacteristics? = null + private var previewSize: Size? = null + private var videoSize: Size? = null + private var mPreviewBuilder: CaptureRequest.Builder? = null + private var mCameraDevice: CameraDevice? = null + private var mPreviewSession: CameraCaptureSession? = null + private var isRecording = false + private var isStarted = false + private var isFlashEnabled = false + private var mSensorOrientation: Int? = null + private var cameraIdToOpen = "0" + override var autoFocus = true + set(focus) { + synchronized(lock) { + field = focus + } + } + override var disableHEVC = false + override var maxVideoBitrate = -1 + override var maxAudioBitRate = -1 + override var maxVideoFrameRate = -1 + override var saveToGallery = false + override var autoSquareCrop = false + override var isAudioLevelsEnabled = false + private set + private var reader: ImageReader? = null + + private val readOnImageAvailableListener = object : ImageReader.OnImageAvailableListener { + override fun onImageAvailable(imageReader: ImageReader) { + val image = imageReader.acquireLatestImage() + val bitmap = imageToBitmap(image) + try { + save(bitmap) + } catch (e: IOException) { + e.printStackTrace() + } finally { + reader = null + val contentUri = Uri.fromFile(file) + val mediaScanIntent = android.content.Intent( + "android.intent.action.MEDIA_SCANNER_SCAN_FILE", + contentUri + ) + mContext.sendBroadcast(mediaScanIntent) + if (listener != null) { + val event = PhotoEvent(EventType.INFO, file, PhotoEvent.EventInfo.PHOTO_TAKEN.toString()) + listener?.onPhotoEvent(event) + } else { + Log.w(TAG, "No listener found") + } + } + } + + private fun imageToBitmap(image: Image): Bitmap { + // NV21 is a plane of 8 bit Y values followed by interleaved Cb Cr + val ib = ByteBuffer.allocate(image.height * image.width * 2) + + val y = image.planes[0].buffer + val cr = image.planes[1].buffer + val cb = image.planes[2].buffer + ib.put(y) + ib.put(cb) + ib.put(cr) + + val yuvImage = YuvImage(ib.array(), + ImageFormat.NV21, image.width, image.height, null) + + val out = ByteArrayOutputStream() + yuvImage.compressToJpeg(Rect(0, 0, + image.width, image.height), 50, out) + val imageBytes = out.toByteArray() + return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + } + + @Throws(IOException::class) + private fun save(bm: Bitmap) { + var originalWidth = bm.width + var originalHeight = bm.height + var offsetWidth = 0 + var offsetHeight = 0 + if (autoSquareCrop) { + if (originalWidth < originalHeight) { + offsetHeight = (originalHeight - originalWidth) / 2 + originalHeight = originalWidth + } else { + offsetWidth = (originalWidth - originalHeight) / 2 + originalWidth = originalHeight + } + } + + // this flips the front camera image to not be 'mirrored effect' for selfies + // does not flip if using the back camera + val matrix = Matrix() + if (mPosition == FancyCamera.CameraPosition.FRONT) { + val mirrorY = floatArrayOf(-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f) + val matrixMirrorY = Matrix() + matrixMirrorY.setValues(mirrorY) + matrix.postConcat(matrixMirrorY) + matrix.postRotate(90f) + } else { + matrix.postRotate(mSensorOrientation!!.toFloat()) + } + + val rotated = Bitmap.createBitmap(bm, offsetWidth, offsetHeight, originalWidth, originalHeight, matrix, false) + + val cameraDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera") + if (!cameraDir.exists()) { + val mkdirs = cameraDir.mkdirs() + } + FileOutputStream(file!!).use { outputStream -> rotated.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) } + bm.recycle() + rotated.recycle() + } + } + + override val numberOfCameras: Int + get() { + return CameraX.getCameraFactory().availableCameraIds.size + } + + private var isCapturingPhoto: Boolean = false + private lateinit var previewConfig: PreviewConfig + private lateinit var videoCaptureConfig: VideoCaptureConfig + private lateinit var imageCaptureConfig: ImageCaptureConfig + private var preview: Preview? = null + private var imageCapture: ImageCapture? = null + private var videoCapture: VideoCapture? = null + private lateinit var displayManager: DisplayManager + + private var textureViewDisplay: Int = -1 + + + private val displayListener = object : DisplayManager.DisplayListener { + override fun onDisplayAdded(displayId: Int) = Unit + override fun onDisplayRemoved(displayId: Int) = Unit + override fun onDisplayChanged(displayId: Int) { + if (displayId == textureViewDisplay) { + val display = displayManager.getDisplay(displayId) + preview?.setTargetRotation(display.rotation) + imageCapture?.setTargetRotation(display.rotation) + videoCapture?.setTargetRotation(display.rotation) + } + } + } + + + private var mQuality = 0 + override var quality: Int + set(value) { + mQuality = value + refreshCamera() + } + get() { + return mQuality + } + + override var didPauseForPermission = false + var didCreateBefore = false + + init { + mManager = mContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager + + displayManager = mContext + .getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + + + (mContext as LifecycleOwner).lifecycle.addObserver(object : LifecycleObserver { + + @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) + fun onCreate() { + } + + @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) + fun onPause() { + displayManager.unregisterDisplayListener(displayListener) + didCreateBefore = true + } + + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + fun onStopped() { + } + + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) + fun onResume() { + displayManager.registerDisplayListener(displayListener, null) + if (didPauseForPermission) { + refreshCamera() + didPauseForPermission = false + } + + if (!didPauseForPermission && didCreateBefore) { + // refreshCamera() + } + } + }) + + val lensFacing = when (position) { + FancyCamera.CameraPosition.FRONT -> { + mPosition = FancyCamera.CameraPosition.FRONT + CameraX.LensFacing.FRONT + } + FancyCamera.CameraPosition.BACK -> { + mPosition = FancyCamera.CameraPosition.BACK + CameraX.LensFacing.BACK + } + else -> { + mPosition = FancyCamera.CameraPosition.BACK + CameraX.LensFacing.BACK + } + } + + + previewConfig = PreviewConfig.Builder() + .apply { + setBackgroundExecutor(executorService) + setLensFacing(lensFacing) + }.build() + videoCaptureConfig = VideoCaptureConfig.Builder() + .apply { + setBackgroundExecutor(executorService) + setLensFacing(lensFacing) + }.build() + imageCaptureConfig = ImageCaptureConfig.Builder() + .apply { + setBackgroundExecutor(executorService) + setLensFacing(lensFacing) + }.build() + + textureView.post { + textureViewDisplay = textureView.display.displayId + refreshCamera() + } + } + + private fun currentLens(): CameraX.LensFacing { + return when (mPosition) { + FancyCamera.CameraPosition.FRONT -> CameraX.LensFacing.FRONT + FancyCamera.CameraPosition.BACK -> CameraX.LensFacing.BACK + else -> CameraX.LensFacing.BACK + } + } + + private fun rotationToSurfaceRotation(rotation: Int): Int { + return when (rotation) { + 270 -> Surface.ROTATION_270 + 180 -> Surface.ROTATION_180 + 90 -> Surface.ROTATION_90 + else -> Surface.ROTATION_0 + } + } + + private var didOpen = false + + private lateinit var autoFitPreview: AutoFitPreviewBuilder + + @SuppressLint("ClickableViewAccessibility") + private fun refreshCamera() { + CameraX.unbindAll() + if (!textureView.isAvailable || !hasPermission()) { + return + } + + try { + val cameraOrientation = when (orientation) { + FancyCamera.CameraOrientation.PORTRAIT_UPSIDE_DOWN -> Surface.ROTATION_270 + FancyCamera.CameraOrientation.PORTRAIT -> Surface.ROTATION_90 + FancyCamera.CameraOrientation.LANDSCAPE_LEFT -> Surface.ROTATION_0 + FancyCamera.CameraOrientation.LANDSCAPE_RIGHT -> Surface.ROTATION_180 + else -> textureView.display.rotation + } + + + val currentLens = currentLens() + + + val config = PreviewConfig.Builder.fromConfig(previewConfig) + .apply { + setTargetRotation(cameraOrientation) + setLensFacing(currentLens) + } + val imageConfig = ImageCaptureConfig.Builder.fromConfig(imageCaptureConfig) + .apply { + setTargetRotation(cameraOrientation) + setLensFacing(currentLens) + setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY) + } + + val profile = getCamcorderProfile(FancyCamera.Quality.values()[quality]) + + config.setTargetResolution(Size(profile.videoFrameWidth, profile.videoFrameHeight)) + + videoCaptureConfig = VideoCaptureConfig.Builder.fromConfig(videoCaptureConfig) + .apply { + setTargetRotation(cameraOrientation) + setLensFacing(currentLens) + setTargetResolution(Size(profile.videoFrameWidth, profile.videoFrameHeight)) + setMaxResolution(Size(profile.videoFrameWidth, profile.videoFrameHeight)) + + var _maxVideoBitrate = profile.videoBitRate + if (maxVideoBitrate > -1) { + _maxVideoBitrate = maxVideoBitrate + } + var _maxVideoFrameRate = profile.videoFrameRate + if (maxVideoFrameRate > -1) { + _maxVideoFrameRate = maxVideoFrameRate + } + var _maxAudioBitRate = profile.audioBitRate + if (maxAudioBitRate > -1) { + _maxAudioBitRate = maxAudioBitRate + } + + setAudioBitRate(min(profile.audioBitRate, _maxAudioBitRate)) + setBitRate(min(profile.videoBitRate, _maxVideoBitrate)) + setVideoFrameRate(min(profile.videoFrameRate, _maxVideoFrameRate)) + }.build() + + previewConfig = config.build() + autoFitPreview = AutoFitPreviewBuilder.build(previewConfig, textureView, listener) //Preview(previewConfig) + preview = autoFitPreview.useCase + val cameraControl = CameraX.getCameraControl(currentLens) + + textureView.setOnTouchListener { _, event -> + if (event.action != MotionEvent.ACTION_UP) { + return@setOnTouchListener false + } + + val factory = TextureViewMeteringPointFactory(textureView) + val point = factory.createPoint(event.x, event.y) + val action = FocusMeteringAction.Builder.from(point).build() + cameraControl.startFocusAndMetering(action) + return@setOnTouchListener true + } + videoCapture = VideoCapture(videoCaptureConfig) + imageCaptureConfig = imageConfig.build() + imageCapture = ImageCapture(imageCaptureConfig) + + CameraX.bindToLifecycle(mContext as LifecycleOwner, preview!!) + } catch (e: IllegalStateException) { + CameraX.unbindAll() + e.printStackTrace() + } + + } + + override fun setEnableAudioLevels(enable: Boolean) { + isAudioLevelsEnabled = enable + } + + override fun hasCamera(): Boolean { + if (mManager == null) return false + try { + return mManager!!.cameraIdList.isNotEmpty() + } catch (e: CameraAccessException) { + e.printStackTrace() + } + return false + } + + override fun cameraStarted(): Boolean { + return isStarted + } + + override fun cameraRecording(): Boolean { + return isRecording + } + + + override fun openCamera(width: Int, height: Int) { + + } + + + @SuppressLint("InlinedApi") + @Throws(IOException::class) + private fun setUpMediaRecorder() { + val activity = mContext as Activity ?: return + + synchronized(lock) { + recorder = MediaRecorder() + recorder!!.setAudioSource(MediaRecorder.AudioSource.MIC) + recorder!!.setVideoSource(MediaRecorder.VideoSource.SURFACE) + val df = SimpleDateFormat("yyyyMMddHHmmss", Locale.US) + val today = Calendar.getInstance().time + if (saveToGallery && hasStoragePermission()) { + val cameraDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera") + if (!cameraDir.exists()) { + val mkdirs = cameraDir.mkdirs() + } + file = File(cameraDir, "VID_" + df.format(today) + ".mp4") + } else { + file = File(mContext.getExternalFilesDir(null), "VID_" + df.format(today) + ".mp4") + } + val profile = getCamcorderProfile(FancyCamera.Quality.values()[quality]) + recorder!!.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) + recorder!!.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight) + recorder!!.setAudioChannels(profile.audioChannels) + + if (mOrientation == null || mOrientation == FancyCamera.CameraOrientation.UNKNOWN) { + val rotation = activity.windowManager.defaultDisplay.rotation + + val orientation = activity.resources.configuration.orientation + + if (orientation == Configuration.ORIENTATION_PORTRAIT) { + if (mSensorOrientation != null) { + when (mSensorOrientation) { + SENSOR_ORIENTATION_DEFAULT_DEGREES -> recorder!!.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation)) + SENSOR_ORIENTATION_INVERSE_DEGREES -> recorder!!.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation)) + } + } + } else if (orientation == Configuration.ORIENTATION_LANDSCAPE && Surface.ROTATION_90 == rotation) { + recorder!!.setOrientationHint(0) + } else if (orientation == Configuration.ORIENTATION_LANDSCAPE && Surface.ROTATION_270 == rotation) { + recorder!!.setOrientationHint(0) + } + } else { + when (mOrientation) { + FancyCamera.CameraOrientation.PORTRAIT_UPSIDE_DOWN -> recorder!!.setOrientationHint(270) + FancyCamera.CameraOrientation.LANDSCAPE_LEFT -> recorder!!.setOrientationHint(0) + FancyCamera.CameraOrientation.LANDSCAPE_RIGHT -> recorder!!.setOrientationHint(180) + else -> recorder!!.setOrientationHint(90) + } + } + + val isHEVCSupported = !this.disableHEVC && android.os.Build.VERSION.SDK_INT >= 24 + + // Use half bit rate for HEVC + val videoBitRate = if (isHEVCSupported) profile.videoBitRate / 2 else profile.videoBitRate + var maxVideoBitrate = profile.videoBitRate + if (this.maxVideoBitrate > -1) { + maxVideoBitrate = this.maxVideoBitrate + } + var maxVideoFrameRate = profile.videoFrameRate + if (this.maxVideoFrameRate > -1) { + maxVideoFrameRate = this.maxVideoFrameRate + } + var maxAudioBitRate = profile.audioBitRate + if (this.maxAudioBitRate > -1) { + maxAudioBitRate = this.maxAudioBitRate + } + + recorder!!.setVideoFrameRate(Math.min(profile.videoFrameRate, maxVideoFrameRate)) + recorder!!.setVideoEncodingBitRate(Math.min(videoBitRate, maxVideoBitrate)) + recorder!!.setAudioEncodingBitRate(Math.min(profile.audioBitRate, maxAudioBitRate)) + + if (isHEVCSupported) { + var h265 = Camera2.getIntFieldIfExists(MediaRecorder.VideoEncoder::class.java, + "HEVC", null, MediaRecorder.VideoEncoder.DEFAULT) + if (h265 == MediaRecorder.VideoEncoder.DEFAULT) { + h265 = Camera2.getIntFieldIfExists(MediaRecorder.VideoEncoder::class.java, + "H265", null, MediaRecorder.VideoEncoder.DEFAULT) + } + // Emulator seems to dislike H264/HEVC + if (isEmulator) { + recorder!!.setVideoEncoder(MediaRecorder.VideoEncoder.H264) + } else { + recorder!!.setVideoEncoder(h265) + } + } else { + recorder!!.setVideoEncoder(MediaRecorder.VideoEncoder.H264) + } + recorder!!.setAudioEncoder(profile.audioCodec) + recorder!!.setOutputFile(file!!.path) + recorder!!.setOnInfoListener { mr, what, extra -> + if (listener != null) { + when (what) { + MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED -> listener?.onVideoEvent(VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.MAX_DURATION_REACHED.toString())) + MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING -> listener?.onVideoEvent(VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.MAX_FILESIZE_APPROACHING.toString())) + MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED -> listener?.onVideoEvent(VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.MAX_FILESIZE_REACHED.toString())) + MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED -> listener?.onVideoEvent(VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.NEXT_OUTPUT_FILE_STARTED.toString())) + MediaRecorder.MEDIA_RECORDER_INFO_UNKNOWN -> listener?.onVideoEvent(VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.UNKNOWN.toString())) + } + } + } + + recorder!!.setOnErrorListener { mr, what, extra -> + if (listener != null) { + when (what) { + MediaRecorder.MEDIA_ERROR_SERVER_DIED -> listener?.onVideoEvent(VideoEvent(EventType.ERROR, null, VideoEvent.EventError.SERVER_DIED.toString())) + MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN -> listener?.onVideoEvent(VideoEvent(EventType.ERROR, null, VideoEvent.EventError.UNKNOWN.toString())) + } + } + } + + recorder!!.prepare() + } + } + + + private fun updateAutoFocus(isVideo: Boolean) { + if (autoFocus) { + val modes = characteristics!!.get( + CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES) + // Auto focus is not supported + if (modes == null || modes.size == 0 || + modes.size == 1 && modes[0] == CameraCharacteristics.CONTROL_AF_MODE_OFF) { + mPreviewBuilder!!.set(CaptureRequest.CONTROL_AF_MODE, + CaptureRequest.CONTROL_AF_MODE_OFF) + } else { + var hasVideoSupport = false + var hasPhotoSupport = false + var hasAutoSupport = false + for (mode in modes) { + if (mode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO) { + hasVideoSupport = true + } + if (mode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) { + hasPhotoSupport = true + } + if (mode == CaptureRequest.CONTROL_AF_MODE_AUTO) { + hasAutoSupport = true + } + } + if (isVideo) { + if (hasVideoSupport) { + mPreviewBuilder!!.set(CaptureRequest.CONTROL_AF_MODE, + CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO) + } else if (hasAutoSupport) { + mPreviewBuilder!!.set(CaptureRequest.CONTROL_AF_MODE, + CaptureRequest.CONTROL_AF_MODE_AUTO) + } + } else { + if (hasPhotoSupport) { + mPreviewBuilder!!.set(CaptureRequest.CONTROL_AF_MODE, + CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) + } else if (hasAutoSupport) { + mPreviewBuilder!!.set(CaptureRequest.CONTROL_AF_MODE, + CaptureRequest.CONTROL_AF_MODE_AUTO) + } + + } + } + } else { + mPreviewBuilder!!.set(CaptureRequest.CONTROL_AF_MODE, + CaptureRequest.CONTROL_AF_MODE_OFF) + } + } + + private fun getPreviewSize(sizes: Array): Size? { + return getSupportedSize(sizes, quality) + } + + private fun getSupportedSize(sizes: Array, quality: Int): Size? { + val wm = mContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager + val display = wm.defaultDisplay + val deviceSize = Point() + display.getSize(deviceSize) + val width = deviceSize.x + val height = deviceSize.y + + var optimalSize: Size? = null + var count = 0 + for (size in sizes) { + count++ + if (quality == FancyCamera.Quality.LOWEST.value) { + return sizes[sizes.size - 1] + } else if (quality == FancyCamera.Quality.HIGHEST.value) { + if (size.height <= height && size.width <= width) { + optimalSize = size + break + } else { + if (count == sizes.size - 1) { + optimalSize = sizes[sizes.size - 1] + break + } + } + } else if (quality == FancyCamera.Quality.MAX_480P.value) { + if (size.height == 480 && size.width <= width) { + optimalSize = size + break + } else { + if (count == sizes.size - 1) { + optimalSize = getSupportedSize(sizes, FancyCamera.Quality.QVGA.value) + break + } + } + } else if (quality == FancyCamera.Quality.MAX_720P.value) { + if (size.height == 720 && size.width <= width) { + optimalSize = size + break + } else { + if (count == sizes.size - 1) { + optimalSize = getSupportedSize(sizes, FancyCamera.Quality.MAX_480P.value) + break + } + } + } else if (quality == FancyCamera.Quality.MAX_1080P.value) { + if (size.height == 1080 && size.width <= width) { + optimalSize = size + break + } else { + if (count == sizes.size - 1) { + optimalSize = getSupportedSize(sizes, FancyCamera.Quality.MAX_720P.value) + break + } + } + } else if (quality == FancyCamera.Quality.MAX_2160P.value) { + if (size.height == 2160 && size.width <= width) { + optimalSize = size + break + } else { + if (count == sizes.size - 1) { + optimalSize = getSupportedSize(sizes, FancyCamera.Quality.MAX_1080P.value) + break + } + } + } else if (quality == FancyCamera.Quality.QVGA.value) { + if (size.height == 240 && size.width <= width) { + optimalSize = size + break + } else { + if (count == sizes.size - 1) { + optimalSize = sizes[sizes.size - 1] + break + } + } + } + } + return optimalSize + } + + + private fun startPreview() { + + } + + private fun closePreviewSession() { + + } + + override fun start() { + refreshCamera() + } + + + override fun stop() { + CameraX.unbindAll() + } + + + override fun startRecording() { + CameraX.unbind(imageCapture!!) + if (!CameraX.isBound(videoCapture)) { + try { + CameraX.bindToLifecycle(mContext as LifecycleOwner, videoCapture!!) + } catch (e: IllegalStateException) { + // Try refreshing camera + e.printStackTrace() + refreshCamera() + try { + CameraX.bindToLifecycle(mContext as LifecycleOwner, videoCapture!!) + } catch (e: IllegalStateException) { + e.printStackTrace() + return + } + } + } + + val df = SimpleDateFormat("yyyyMMddHHmmss", Locale.US) + val today = Calendar.getInstance().time + if (saveToGallery && hasStoragePermission()) { + val cameraDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera") + if (!cameraDir.exists()) { + cameraDir.mkdirs() + } + file = File(cameraDir, "VID_" + df.format(today) + ".mp4") + } else { + file = File(mContext.getExternalFilesDir(null), "VID_" + df.format(today) + ".mp4") + } + + videoCapture?.startRecording(file!!, executorService, object : VideoCapture.OnVideoSavedListener { + override fun onVideoSaved(videoFile: File) { + isStarted = false + isRecording = false + stopDurationTimer() + + if (file != null && saveToGallery && hasStoragePermission()) { + val contentUri = Uri.fromFile(file) + val mediaScanIntent = Intent( + "android.intent.action.MEDIA_SCANNER_SCAN_FILE", + contentUri + ) + mContext.sendBroadcast(mediaScanIntent) + } + + if (listener != null) { + listener?.onVideoEvent(VideoEvent(EventType.INFO, videoFile, VideoEvent.EventInfo.RECORDING_FINISHED.toString())) + } + file = null + } + + override fun onError(videoCaptureError: VideoCapture.VideoCaptureError, message: String, cause: Throwable?) { + isStarted = false + isRecording = false + stopDurationTimer() + file = null + listener?.onVideoEvent(VideoEvent(EventType.ERROR, null, VideoEvent.EventError.UNKNOWN.toString())) + } + }) + isRecording = true + startDurationTimer() + listener?.onVideoEvent(VideoEvent(EventType.INFO, null, VideoEvent.EventInfo.RECORDING_STARTED.toString())) + } + + + fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int { + // Raw height and width of image + val (height: Int, width: Int) = options.run { outHeight to outWidth } + var inSampleSize = 1 + + if (height > reqHeight || width > reqWidth) { + + val halfHeight: Int = height / 2 + val halfWidth: Int = width / 2 + + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) { + inSampleSize *= 2 + } + } + + return inSampleSize + } + + override fun takePhoto() { + CameraX.unbind(videoCapture!!) + + if (!CameraX.isBound(imageCapture!!)) { + try { + CameraX.bindToLifecycle(mContext as LifecycleOwner, imageCapture!!) + } catch (e: IllegalStateException) { + // Try refreshing + e.printStackTrace() + refreshCamera() + try { + CameraX.bindToLifecycle(mContext as LifecycleOwner, videoCapture!!) + } catch (e: IllegalStateException) { + e.printStackTrace() + return + } + } + } + + + val df = SimpleDateFormat("yyyyMMddHHmmss", Locale.US) + val today = Calendar.getInstance().time + if (saveToGallery && hasStoragePermission()) { + val cameraDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera") + if (!cameraDir.exists()) { + cameraDir.mkdirs() + } + file = File(cameraDir, "PIC_" + df.format(today) + ".jpg") + } else { + file = File(mContext.getExternalFilesDir(null), "PIC_" + df.format(today) + ".jpg") + } + + val meta = ImageCapture.Metadata().apply { + isReversedHorizontal = imageCaptureConfig.lensFacing == CameraX.LensFacing.FRONT + } + + if (autoSquareCrop) { + imageCapture?.takePicture(executorService, object : ImageCapture.OnImageCapturedListener() { + override fun onCaptureSuccess(image: ImageProxy, rotationDegrees: Int) { + CameraXExecutors.ioExecutor().execute { + var isError = false + try { + val buffer = image.planes.first().buffer + val bytes = ByteArray(buffer!!.remaining()) + buffer.get(bytes) + + val bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + val matrix = Matrix() + + matrix.postRotate(rotationDegrees.toFloat()) + + if (imageCaptureConfig.lensFacing == CameraX.LensFacing.FRONT) { + matrix.postScale(-1f, 1f); + } + + var originalWidth = bm.width + var originalHeight = bm.height + var offsetWidth = 0 + var offsetHeight = 0 + if (autoSquareCrop) { + if (originalWidth < originalHeight) { + offsetHeight = (originalHeight - originalWidth) / 2; + originalHeight = originalWidth; + } else { + offsetWidth = (originalWidth - originalHeight) / 2; + originalWidth = originalHeight; + } + } + val rotated = Bitmap.createBitmap(bm, offsetWidth, offsetHeight, originalWidth, originalHeight, matrix, false); + + val cameraDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera"); + if (!cameraDir.exists()) { + cameraDir.mkdirs(); + } + val outputStream = FileOutputStream(file!!, false) + rotated.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) + + + /* + val exif = ExifInterface(file!!.absolutePath) + + val now = System.currentTimeMillis() + val datetime = convertToExifDateTime(now) + + exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, datetime) + exif.setAttribute(ExifInterface.TAG_DATETIME_DIGITIZED, datetime) + + try { + val subsec = java.lang.Long.toString(now - convertFromExifDateTime(datetime).getTime()) + exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, subsec) + exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, subsec) + } catch (e: ParseException) { + } + + exif.rotate(rotationDegrees) + if (meta.isReversedHorizontal) { + exif.flipHorizontally() + } + if (meta.isReversedVertical) { + exif.flipVertically() + } + if (meta.location != null) { + + exif.setGpsInfo(meta.location!!) + } + exif.saveAttributes() + + */ + + bm.recycle() + rotated.recycle() + } catch (e: Exception) { + isError = true + val event = PhotoEvent(EventType.ERROR, null, PhotoEvent.EventError.UNKNOWN.toString()) + listener?.onPhotoEvent(event) + } finally { + try { + image.close() + } catch (e: Exception) { + + } + if (!isError) { + if (saveToGallery && hasStoragePermission()) { + val contentUri = Uri.fromFile(file) + val mediaScanIntent = Intent( + "android.intent.action.MEDIA_SCANNER_SCAN_FILE", + contentUri + ) + mContext.sendBroadcast(mediaScanIntent) + } + val event = PhotoEvent(EventType.INFO, file, PhotoEvent.EventInfo.PHOTO_TAKEN.toString()) + listener?.onPhotoEvent(event) + } + } + } + + } + + override fun onError(imageCaptureError: ImageCapture.ImageCaptureError, message: String, cause: Throwable?) { + val event = PhotoEvent(EventType.ERROR, null, PhotoEvent.EventError.UNKNOWN.toString()) + listener?.onPhotoEvent(event) + } + }) + } else { + imageCapture?.takePicture(file!!, meta, executorService, object : ImageCapture.OnImageSavedListener { + override fun onImageSaved(file: File) { + val event = PhotoEvent(EventType.INFO, file, PhotoEvent.EventInfo.PHOTO_TAKEN.toString()) + listener?.onPhotoEvent(event) + } + + override fun onError(imageCaptureError: ImageCapture.ImageCaptureError, message: String, cause: Throwable?) { + val event = PhotoEvent(EventType.ERROR, null, PhotoEvent.EventError.UNKNOWN.toString()) + listener?.onPhotoEvent(event) + } + }) + } + } + + + private val DATE_FORMAT = object : ThreadLocal() { + public override fun initialValue(): SimpleDateFormat { + return SimpleDateFormat("yyyy:MM:dd", Locale.US) + } + } + private val TIME_FORMAT = object : ThreadLocal() { + public override fun initialValue(): SimpleDateFormat { + return SimpleDateFormat("HH:mm:ss", Locale.US) + } + } + private val DATETIME_FORMAT = object : ThreadLocal() { + public override fun initialValue(): SimpleDateFormat { + return SimpleDateFormat("yyyy:MM:dd HH:mm:ss", Locale.US) + } + } + + private fun convertToExifDateTime(timestamp: Long): String { + return DATETIME_FORMAT.get()!!.format(Date(timestamp)) + } + + @Throws(ParseException::class) + private fun convertFromExifDateTime(dateTime: String): Date { + return DATETIME_FORMAT.get()!!.parse(dateTime) + } + + private fun stopRecord() { + videoCapture?.stopRecording() + } + + override fun stopRecording() { + stopRecord() + } + + + private fun configureTransform(viewWidth: Int, viewHeight: Int) { + + } + + + override fun toggleCamera() { + if (mPosition == FancyCamera.CameraPosition.BACK) { + mPosition = FancyCamera.CameraPosition.FRONT + } else { + mPosition = FancyCamera.CameraPosition.BACK + } + refreshCamera() + } + + + override fun release() { + CameraX.unbindAll() + } + + + override fun setCameraPosition(position: FancyCamera.CameraPosition) { + if (position != mPosition) { + mPosition = position + refreshCamera() + } + } + + override fun setCameraOrientation(orientation: FancyCamera.CameraOrientation) { + mOrientation = orientation + refreshCamera() + } + + override fun hasFlash(): Boolean { + return try { + val cameraInfo = CameraX.getCameraInfo(currentLens()) + cameraInfo.isFlashAvailable.value ?: false + } catch (e: Exception) { + false + } + } + + override fun toggleFlash() { + if (!hasFlash()) { + return + } + isFlashEnabled = !isFlashEnabled + + imageCapture?.flashMode = when (isFlashEnabled) { + true -> FlashMode.ON + else -> FlashMode.OFF + } + + } + + override fun enableFlash() { + if (!hasFlash()) { + return + } + isFlashEnabled = true + imageCapture?.flashMode = FlashMode.ON + } + + override fun disableFlash() { + if (!hasFlash()) { + return + } + isFlashEnabled = false + imageCapture?.flashMode = FlashMode.OFF + + } + + override fun flashEnabled(): Boolean { + return isFlashEnabled + } + + private fun getCamcorderProfile(quality: FancyCamera.Quality): CamcorderProfile { + var profile = CamcorderProfile.get(CamcorderProfile.QUALITY_LOW) + when (quality) { + FancyCamera.Quality.MAX_480P -> if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) { + profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P) + } else { + profile = getCamcorderProfile(FancyCamera.Quality.QVGA) + } + FancyCamera.Quality.MAX_720P -> if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) { + profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P) + } else { + profile = getCamcorderProfile(FancyCamera.Quality.MAX_480P) + } + FancyCamera.Quality.MAX_1080P -> if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) { + profile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P) + } else { + profile = getCamcorderProfile(FancyCamera.Quality.MAX_720P) + } + FancyCamera.Quality.MAX_2160P -> try { + profile = CamcorderProfile.get(CamcorderProfile.QUALITY_2160P) + } catch (e: Exception) { + profile = getCamcorderProfile(FancyCamera.Quality.HIGHEST) + } + + FancyCamera.Quality.HIGHEST -> profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH) + FancyCamera.Quality.LOWEST -> profile = CamcorderProfile.get(CamcorderProfile.QUALITY_LOW) + FancyCamera.Quality.QVGA -> if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QVGA)) { + profile = CamcorderProfile.get(CamcorderProfile.QUALITY_QVGA) + } else { + profile = getCamcorderProfile(FancyCamera.Quality.LOWEST) + } + } + return profile + } + + companion object { + private val lock = Any() + private val TAG = "Camera2.Fancy" + private val SENSOR_ORIENTATION_DEFAULT_DEGREES = 90 + private val SENSOR_ORIENTATION_INVERSE_DEGREES = 270 + private val DEFAULT_ORIENTATIONS = SparseIntArray() + private val INVERSE_ORIENTATIONS = SparseIntArray() + + @JvmStatic + val executorService = Executors.newSingleThreadExecutor() + + + init { + DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90) + DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0) + DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270) + DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180) + } + + init { + INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270) + INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180) + INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90) + INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0) + } + + private fun getIntFieldIfExists(klass: Class<*>, fieldName: String, + obj: Class<*>?, defaultVal: Int): Int { + try { + val f = klass.getDeclaredField(fieldName) + return f.getInt(obj) + } catch (e: Exception) { + return defaultVal + } + + } + + private val isEmulator: Boolean + get() = (Build.FINGERPRINT.startsWith("generic") + || Build.FINGERPRINT.startsWith("unknown") + || Build.MODEL.contains("google_sdk") + || Build.MODEL.contains("Emulator") + || Build.MODEL.contains("Android SDK built for x86") + || Build.MANUFACTURER.contains("Genymotion") + || Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic") + || "google_sdk" == Build.PRODUCT) + } + +} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/CameraBase.java b/fancycamera/src/main/java/co/fitcom/fancycamera/CameraBase.java deleted file mode 100644 index f3337f0..0000000 --- a/fancycamera/src/main/java/co/fitcom/fancycamera/CameraBase.java +++ /dev/null @@ -1,185 +0,0 @@ - -/* - * Created By Osei Fortune on 2/16/18 8:42 PM - * Copyright (c) 2018 - * Last modified 2/16/18 8:42 PM - * - */ - -package co.fitcom.fancycamera; - -import android.Manifest; -import android.app.Activity; -import android.content.Context; -import android.content.pm.PackageManager; -import android.media.MediaRecorder; -import android.os.Build; -import android.view.TextureView; - -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; - -import java.io.File; -import java.util.Timer; -import java.util.TimerTask; - -public abstract class CameraBase { - private Timer mTimer; - private TimerTask mTimerTask; - private int mDuration = 0; - private TextureView holder; - private File mFile; - CameraEventListener listener; - int quality; - - CameraBase(TextureView holder) { - this.holder = holder; - } - - public final static String CameraThread = "CameraThread"; - - private TextViewListener textViewListener; - - public TextureView getHolder() { - return holder; - } - - void setFile(File file) { - mFile = file; - } - - File getFile() { - return mFile; - } - - void setQuality(int quality) { - this.quality = quality; - } - - int getQuality() { - return quality; - } - - abstract public void setEnableAudioLevels(boolean enable); - - abstract public boolean isAudioLevelsEnabled(); - - abstract boolean getAutoSquareCrop(); - - abstract void setAutoSquareCrop(boolean crop); - - abstract MediaRecorder getRecorder(); - - abstract boolean getSaveToGallery(); - - abstract public void setSaveToGallery(boolean saveToGallery); - - abstract public boolean getAutoFocus(); - - abstract public void setAutoFocus(boolean focus); - - abstract int getMaxAudioBitRate(); - - abstract int getMaxVideoBitrate(); - - abstract int getMaxVideoFrameRate(); - - abstract public boolean getDisableHEVC(); - - abstract public void setDisableHEVC(boolean disableHEVC); - - abstract public void setMaxAudioBitRate(int maxAudioBitRate); - - abstract public void setMaxVideoBitrate(int maxVideoBitrate); - - abstract public void setMaxVideoFrameRate(int maxVideoFrameRate); - - abstract public int getNumberOfCameras(); - - abstract boolean hasCamera(); - - abstract boolean hasFlash(); - - abstract boolean cameraStarted(); - - abstract boolean cameraRecording(); - - abstract void openCamera(int width, int height); - - abstract void start(); - - abstract void stop(); - - abstract void startRecording(); - - abstract void takePhoto(); - - abstract void stopRecording(); - - abstract void toggleCamera(); - - abstract void updatePreview(); - - abstract void release(); - - abstract void setCameraPosition(FancyCamera.CameraPosition position); - - abstract void setCameraOrientation(FancyCamera.CameraOrientation orientation); - - abstract void toggleFlash(); - - abstract void enableFlash(); - - abstract void disableFlash(); - - abstract boolean flashEnabled(); - - public void setTextViewListener(TextViewListener listener) { - textViewListener = listener; - } - - public TextViewListener getTextViewListener() { - return textViewListener; - } - - public CameraEventListener getListener() { - return listener; - } - - public void setListener(CameraEventListener listener) { - this.listener = listener; - } - - void startDurationTimer() { - mTimer = new Timer(); - mTimerTask = new TimerTask() { - @Override - public void run() { - mDuration += 1; - } - }; - mTimer.schedule(mTimerTask, 0, 1000); - } - - void stopDurationTimer() { - mTimerTask.cancel(); - mTimer.cancel(); - mDuration = 0; - } - - int getDuration() { - return mDuration; - } - - public boolean hasStoragePermission() { - if (Build.VERSION.SDK_INT < 23) { - return true; - } - return ContextCompat.checkSelfPermission(holder.getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == (PackageManager.PERMISSION_GRANTED); - } - - public void requestStoragePermission() { - ActivityCompat.requestPermissions((Activity) holder.getContext(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 868); - } - -} \ No newline at end of file diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/CameraBase.kt b/fancycamera/src/main/java/co/fitcom/fancycamera/CameraBase.kt new file mode 100644 index 0000000..ae5aab1 --- /dev/null +++ b/fancycamera/src/main/java/co/fitcom/fancycamera/CameraBase.kt @@ -0,0 +1,142 @@ +/* + * Created By Osei Fortune on 2/16/18 8:42 PM + * Copyright (c) 2018 + * Last modified 2/16/18 8:42 PM + * + */ + +package co.fitcom.fancycamera + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import android.media.MediaRecorder +import android.os.Build +import android.view.TextureView + +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat + +import java.io.File +import java.util.Timer +import java.util.TimerTask + +abstract class CameraBase internal constructor(val holder: TextureView) { + private var mTimer: Timer? = null + private var mTimerTask: TimerTask? = null + internal var duration = 0 + private set + internal var file: File? = null + var listener: CameraEventListener? = null + internal abstract var quality: Int + + private val VIDEO_RECORDER_PERMISSIONS_REQUEST = 868 + private val VIDEO_RECORDER_PERMISSIONS = arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA) + + var textViewListener: TextViewListener? = null + + abstract val isAudioLevelsEnabled: Boolean + + internal abstract var autoSquareCrop: Boolean + + internal abstract val recorder: MediaRecorder? + + internal abstract var saveToGallery: Boolean + + abstract var autoFocus: Boolean + + internal abstract var maxAudioBitRate: Int + + internal abstract var maxVideoBitrate: Int + + internal abstract var maxVideoFrameRate: Int + internal abstract var didPauseForPermission: Boolean + + abstract var disableHEVC: Boolean + + abstract val numberOfCameras: Int + + abstract fun setEnableAudioLevels(enable: Boolean) + + internal abstract fun hasCamera(): Boolean + + internal abstract fun hasFlash(): Boolean + + internal abstract fun cameraStarted(): Boolean + + internal abstract fun cameraRecording(): Boolean + + internal abstract fun openCamera(width: Int, height: Int) + + internal abstract fun start() + + internal abstract fun stop() + + internal abstract fun startRecording() + + internal abstract fun takePhoto() + + internal abstract fun stopRecording() + + internal abstract fun toggleCamera() + + internal abstract fun updatePreview() + + internal abstract fun release() + + internal abstract fun setCameraPosition(position: FancyCamera.CameraPosition) + + internal abstract fun setCameraOrientation(orientation: FancyCamera.CameraOrientation) + + internal abstract fun toggleFlash() + + internal abstract fun enableFlash() + + internal abstract fun disableFlash() + + internal abstract fun flashEnabled(): Boolean + + internal fun startDurationTimer() { + mTimer = Timer() + mTimerTask = object : TimerTask() { + override fun run() { + duration += 1 + } + } + mTimer?.schedule(mTimerTask, 0, 1000) + } + + internal fun stopDurationTimer() { + mTimerTask?.cancel() + mTimer?.cancel() + duration = 0 + } + + fun hasStoragePermission(): Boolean { + return if (Build.VERSION.SDK_INT < 23) { + true + } else ContextCompat.checkSelfPermission(holder.context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED + } + + fun requestStoragePermission() { + ActivityCompat.requestPermissions(holder.context as Activity, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 868) + } + + fun requestPermission() { + didPauseForPermission = true + ActivityCompat.requestPermissions(holder.context as Activity, VIDEO_RECORDER_PERMISSIONS, VIDEO_RECORDER_PERMISSIONS_REQUEST) + } + + fun hasPermission(): Boolean { + return if (Build.VERSION.SDK_INT < 23) { + true + } else ContextCompat.checkSelfPermission(holder.context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(holder.context, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED + } + + companion object { + + val CameraThread = "CameraThread" + } + +} \ No newline at end of file diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/CameraEventListener.java b/fancycamera/src/main/java/co/fitcom/fancycamera/CameraEventListener.java deleted file mode 100644 index 5cb534a..0000000 --- a/fancycamera/src/main/java/co/fitcom/fancycamera/CameraEventListener.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Created By Osei Fortune on 2/16/18 8:43 PM - * Copyright (c) 2018 - * Last modified 2/16/18 7:16 PM - * - */ - -package co.fitcom.fancycamera; - -public interface CameraEventListener { - void onCameraOpen(); - void onCameraClose(); - void onPhotoEvent(PhotoEvent event); - void onVideoEvent(VideoEvent event); -} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/CameraEventListener.kt b/fancycamera/src/main/java/co/fitcom/fancycamera/CameraEventListener.kt new file mode 100644 index 0000000..1984406 --- /dev/null +++ b/fancycamera/src/main/java/co/fitcom/fancycamera/CameraEventListener.kt @@ -0,0 +1,15 @@ +/* + * Created By Osei Fortune on 2/16/18 8:43 PM + * Copyright (c) 2018 + * Last modified 2/16/18 7:16 PM + * + */ + +package co.fitcom.fancycamera + +interface CameraEventListener { + fun onCameraOpen() + fun onCameraClose() + fun onPhotoEvent(event: PhotoEvent) + fun onVideoEvent(event: VideoEvent) +} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/CameraEventListenerUI.java b/fancycamera/src/main/java/co/fitcom/fancycamera/CameraEventListenerUI.java deleted file mode 100644 index 597a867..0000000 --- a/fancycamera/src/main/java/co/fitcom/fancycamera/CameraEventListenerUI.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Created By Osei Fortune on 2/16/18 8:43 PM - * Copyright (c) 2018 - * Last modified 2/16/18 7:51 PM - * - */ - -package co.fitcom.fancycamera; - -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.util.Log; - -import java.io.File; - - -public abstract class CameraEventListenerUI implements CameraEventListener { - private Handler handler; - private static final int WHAT_PHOTO_EVENT = 0x01; - private static final int WHAT_VIDEO_EVENT = 0x02; - private static final int WHAT_CAMERA_CLOSE_EVENT = 0x03; - private static final int WHAT_CAMERA_OPEN_EVENT = 0x04; - private static final String MESSAGE = "message"; - private static final String TYPE = "type"; - private static final String FILE = "file"; - - private void ensureHandler() { - if (handler != null) { - return; - } - synchronized (CameraEventListenerUI.class) { - if (handler == null) { - handler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(Message msg) { - Bundle eventData = msg.getData(); - if (eventData == null && (msg.what != WHAT_CAMERA_CLOSE_EVENT || msg.what != WHAT_CAMERA_OPEN_EVENT)) { - return; - } - EventType type = (EventType) eventData.getSerializable(TYPE); - String message = eventData.getString(MESSAGE); - File file = null; - switch (msg.what) { - case WHAT_PHOTO_EVENT: - if (eventData.getString(FILE) != null) { - file = new File(eventData.getString(FILE)); - } - onPhotoEventUI(new PhotoEvent(type, file, message)); - break; - case WHAT_VIDEO_EVENT: - if (eventData.getString(FILE) != null) { - file = new File(eventData.getString(FILE)); - } - onVideoEventUI(new VideoEvent(type, file, message)); - break; - case WHAT_CAMERA_CLOSE_EVENT: - onCameraCloseUI(); - break; - case WHAT_CAMERA_OPEN_EVENT: - onCameraOpenUI(); - break; - } - } - }; - } - } - } - - - @Override - public void onPhotoEvent(PhotoEvent event) { - if (Looper.myLooper() == Looper.getMainLooper()) { - onPhotoEventUI(event); - return; - } - ensureHandler(); - Message message = handler.obtainMessage(); - message.what = WHAT_PHOTO_EVENT; - Bundle bundle = new Bundle(); - bundle.putString(MESSAGE, event.getMessage()); - bundle.putSerializable(TYPE, event.getType()); - if (event.getFile() != null) { - bundle.putString(FILE, event.getFile().getPath()); - } - message.setData(bundle); - handler.sendMessage(message); - } - - @Override - public void onVideoEvent(VideoEvent event) { - if (Looper.myLooper() == Looper.getMainLooper()) { - onVideoEventUI(event); - return; - } - ensureHandler(); - Message message = handler.obtainMessage(); - message.what = WHAT_VIDEO_EVENT; - Bundle bundle = new Bundle(); - bundle.putString(MESSAGE, event.getMessage()); - bundle.putSerializable(TYPE, event.getType()); - if (event.getFile() != null) { - bundle.putString(FILE, event.getFile().getPath()); - } - message.setData(bundle); - handler.sendMessage(message); - } - - @Override - public void onCameraClose() { - if (Looper.myLooper() == Looper.getMainLooper()) { - onCameraCloseUI(); - return; - } - ensureHandler(); - Message message = handler.obtainMessage(); - message.what = WHAT_CAMERA_CLOSE_EVENT; - Bundle bundle = new Bundle(); - message.setData(bundle); - handler.sendMessage(message); - } - - @Override - public void onCameraOpen() { - if (Looper.myLooper() == Looper.getMainLooper()) { - onCameraOpenUI(); - return; - } - ensureHandler(); - Message message = handler.obtainMessage(); - message.what = WHAT_CAMERA_OPEN_EVENT; - Bundle bundle = new Bundle(); - message.setData(bundle); - handler.sendMessage(message); - } - - public abstract void onCameraOpenUI(); - - public abstract void onCameraCloseUI(); - - public abstract void onPhotoEventUI(PhotoEvent event); - - public abstract void onVideoEventUI(VideoEvent event); -} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/CameraEventListenerUI.kt b/fancycamera/src/main/java/co/fitcom/fancycamera/CameraEventListenerUI.kt new file mode 100644 index 0000000..a56d551 --- /dev/null +++ b/fancycamera/src/main/java/co/fitcom/fancycamera/CameraEventListenerUI.kt @@ -0,0 +1,139 @@ +/* + * Created By Osei Fortune on 2/16/18 8:43 PM + * Copyright (c) 2018 + * Last modified 2/16/18 7:51 PM + * + */ + +package co.fitcom.fancycamera + +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.os.Message +import android.util.Log + +import java.io.File + + +abstract class CameraEventListenerUI : CameraEventListener { + private var handler: Handler? = null + + private fun ensureHandler() { + if (handler != null) { + return + } + synchronized(CameraEventListenerUI::class.java) { + if (handler == null) { + handler = object : Handler(Looper.getMainLooper()) { + override fun handleMessage(msg: Message) { + val eventData = msg.data + if (eventData == null && (msg.what != WHAT_CAMERA_CLOSE_EVENT || msg.what != WHAT_CAMERA_OPEN_EVENT)) { + return + } + val type = eventData!!.getSerializable(TYPE) as EventType + val message = eventData.getString(MESSAGE) + var file: File? = null + when (msg.what) { + WHAT_PHOTO_EVENT -> { + if (eventData.getString(FILE) != null) { + file = File(eventData.getString(FILE)!!) + } + onPhotoEventUI(PhotoEvent(type, file, message)) + } + WHAT_VIDEO_EVENT -> { + if (eventData.getString(FILE) != null) { + file = File(eventData.getString(FILE)!!) + } + onVideoEventUI(VideoEvent(type, file, message)) + } + WHAT_CAMERA_CLOSE_EVENT -> onCameraCloseUI() + WHAT_CAMERA_OPEN_EVENT -> onCameraOpenUI() + } + } + } + } + } + } + + + override fun onPhotoEvent(event: PhotoEvent) { + if (Looper.myLooper() == Looper.getMainLooper()) { + onPhotoEventUI(event) + return + } + ensureHandler() + val message = handler!!.obtainMessage() + message.what = WHAT_PHOTO_EVENT + val bundle = Bundle() + bundle.putString(MESSAGE, event.message) + bundle.putSerializable(TYPE, event.type) + if (event.file != null) { + bundle.putString(FILE, event.file!!.path) + } + message.data = bundle + handler!!.sendMessage(message) + } + + override fun onVideoEvent(event: VideoEvent) { + if (Looper.myLooper() == Looper.getMainLooper()) { + onVideoEventUI(event) + return + } + ensureHandler() + val message = handler!!.obtainMessage() + message.what = WHAT_VIDEO_EVENT + val bundle = Bundle() + bundle.putString(MESSAGE, event.message) + bundle.putSerializable(TYPE, event.type) + if (event.file != null) { + bundle.putString(FILE, event.file!!.path) + } + message.data = bundle + handler!!.sendMessage(message) + } + + override fun onCameraClose() { + if (Looper.myLooper() == Looper.getMainLooper()) { + onCameraCloseUI() + return + } + ensureHandler() + val message = handler!!.obtainMessage() + message.what = WHAT_CAMERA_CLOSE_EVENT + val bundle = Bundle() + message.data = bundle + handler!!.sendMessage(message) + } + + override fun onCameraOpen() { + if (Looper.myLooper() == Looper.getMainLooper()) { + onCameraOpenUI() + return + } + ensureHandler() + val message = handler!!.obtainMessage() + message.what = WHAT_CAMERA_OPEN_EVENT + val bundle = Bundle() + message.data = bundle + handler!!.sendMessage(message) + } + + abstract fun onCameraOpenUI() + + abstract fun onCameraCloseUI() + + abstract fun onPhotoEventUI(event: PhotoEvent) + + abstract fun onVideoEventUI(event: VideoEvent) + + companion object { + private val WHAT_PHOTO_EVENT = 0x01 + private val WHAT_VIDEO_EVENT = 0x02 + private val WHAT_CAMERA_CLOSE_EVENT = 0x03 + private val WHAT_CAMERA_OPEN_EVENT = 0x04 + private val MESSAGE = "message" + private val TYPE = "type" + private val FILE = "file" + } +} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/EventType.java b/fancycamera/src/main/java/co/fitcom/fancycamera/EventType.kt similarity index 71% rename from fancycamera/src/main/java/co/fitcom/fancycamera/EventType.java rename to fancycamera/src/main/java/co/fitcom/fancycamera/EventType.kt index dc41254..159cc7c 100644 --- a/fancycamera/src/main/java/co/fitcom/fancycamera/EventType.java +++ b/fancycamera/src/main/java/co/fitcom/fancycamera/EventType.kt @@ -5,10 +5,10 @@ * */ -package co.fitcom.fancycamera; +package co.fitcom.fancycamera -public enum EventType { +enum class EventType { ERROR, INFO } diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/FancyCamera.java b/fancycamera/src/main/java/co/fitcom/fancycamera/FancyCamera.java deleted file mode 100644 index 3604aba..0000000 --- a/fancycamera/src/main/java/co/fitcom/fancycamera/FancyCamera.java +++ /dev/null @@ -1,493 +0,0 @@ -/* - * Created By Osei Fortune on 2/16/18 8:43 PM - * Copyright (c) 2018 - * Last modified 2/16/18 7:58 PM - * - */ - -package co.fitcom.fancycamera; - -import android.Manifest; -import android.app.Activity; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.TypedArray; -import android.graphics.SurfaceTexture; -import android.media.MediaRecorder; -import android.os.Build; - -import androidx.annotation.Nullable; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; - -import android.util.AttributeSet; -import android.view.TextureView; -import android.view.ViewGroup; - -import java.io.File; -import java.io.IOException; - - -public class FancyCamera extends TextureView implements TextureView.SurfaceTextureListener { - private boolean mFlashEnabled = false; - private boolean isStarted = false; - private int mCameraPosition = 0; - private int mCameraOrientation = 0; - private int mQuality = Quality.MAX_480P.getValue(); - private final Object mLock = new Object(); - private CameraEventListener listener; - private final int VIDEO_RECORDER_PERMISSIONS_REQUEST = 868; - private String[] VIDEO_RECORDER_PERMISSIONS = new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA}; - private boolean isReady = false; - private CameraBase cameraBase; - private MediaRecorder recorder; - private boolean isGettingAudioLvls = false; - static final private double EMA_FILTER = 0.6; - private double mEMA = 0.0; - private CameraEventListener internalListener; - - public FancyCamera(Context context) { - super(context); - init(context, null); - } - - public FancyCamera(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs); - } - - private void initListener() { - if (isAudioLevelsEnabled()) { - if (!hasPermission()) { - return; - } - if (recorder != null) deInitListener(); - recorder = new MediaRecorder(); - recorder.setAudioSource(MediaRecorder.AudioSource.MIC); - recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); - recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); - recorder.setOutputFile("/dev/null"); - try { - recorder.prepare(); - recorder.start(); - isGettingAudioLvls = true; - mEMA = 0.0; - } catch (IOException e) { - // Need this ??? - e.printStackTrace(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - public void deInitListener() { - if (isAudioLevelsEnabled() && isGettingAudioLvls) { - try { - recorder.stop(); - recorder.release(); - recorder = null; - isGettingAudioLvls = false; - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - private void init(Context context, @Nullable AttributeSet attrs) { - if (Build.VERSION.SDK_INT >= 21) { - cameraBase = new Camera2(getContext(), this, CameraPosition.values()[getCameraPosition()], CameraOrientation.values()[getCameraOrientation()]); - } else { - cameraBase = new Camera1(getContext(), this, CameraPosition.values()[getCameraPosition()]); - } - - internalListener = new CameraEventListener() { - @Override - public void onCameraOpen() { - initListener(); - if (listener != null) { - listener.onCameraOpen(); - } - } - - @Override - public void onCameraClose() { - deInitListener(); - if (listener != null) { - listener.onCameraClose(); - } - } - - @Override - public void onPhotoEvent(PhotoEvent event) { - if (listener != null) { - listener.onPhotoEvent(event); - } - } - - @Override - public void onVideoEvent(VideoEvent event) { - if (listener != null) { - listener.onVideoEvent(event); - } - } - }; - - cameraBase.setListener(internalListener); - - if (attrs != null) { - TypedArray a = context.obtainStyledAttributes( - attrs, - R.styleable.FancyCamera); - - try { - mFlashEnabled = a.getBoolean(R.styleable.FancyCamera_enableFlash, false); - setSaveToGallery(a.getBoolean(R.styleable.FancyCamera_saveToGallery, false)); - mQuality = a.getInteger(R.styleable.FancyCamera_quality, Quality.MAX_480P.getValue()); - setQuality(mQuality); - mCameraPosition = a.getInteger(R.styleable.FancyCamera_cameraPosition, 0); - setCameraPosition(mCameraPosition); - mCameraOrientation = a.getInteger(R.styleable.FancyCamera_cameraOrientation, 0); - setCameraOrientation(mCameraOrientation); - setDisableHEVC(a.getBoolean(R.styleable.FancyCamera_disableHEVC, false)); - setMaxAudioBitRate(a.getInteger(R.styleable.FancyCamera_maxAudioBitRate, -1)); - setMaxVideoBitrate(a.getInteger(R.styleable.FancyCamera_maxVideoBitrate, -1)); - setMaxVideoFrameRate(a.getInteger(R.styleable.FancyCamera_maxVideoFrameRate, -1)); - setEnableAudioLevels(a.getBoolean(R.styleable.FancyCamera_audioLevels, false)); - - } finally { - a.recycle(); - } - } - this.setSurfaceTextureListener(this); - } - - public int getNumberOfCameras() { - return cameraBase.getNumberOfCameras(); - } - - boolean getAutoSquareCrop() { - return cameraBase.getAutoSquareCrop(); - } - - public void setAutoSquareCrop(boolean autoSquareCrop) { - cameraBase.setAutoSquareCrop(autoSquareCrop); - } - - public boolean getAutoFocus() { - return cameraBase.getAutoFocus(); - } - - public void setAutoFocus(boolean focus) { - cameraBase.setAutoFocus(focus); - } - - public boolean getSaveToGallery() { - return cameraBase.getSaveToGallery(); - } - - public void setSaveToGallery(boolean saveToGallery) { - cameraBase.setSaveToGallery(saveToGallery); - } - - public void setFile(File file) { - cameraBase.setFile(file); - } - - File getFile() { - return cameraBase.getFile(); - } - - public boolean hasFlash() { - return cameraBase.hasFlash(); - } - - public void toggleFlash() { - cameraBase.toggleFlash(); - } - - public void enableFlash() { - cameraBase.enableFlash(); - } - - public void disableFlash() { - cameraBase.disableFlash(); - } - - public boolean flashEnabled() { - return cameraBase.flashEnabled(); - } - - public boolean cameraStarted() { - return cameraBase.cameraStarted(); - } - - public boolean cameraRecording() { - return cameraBase.cameraRecording(); - } - - public void takePhoto() { - cameraBase.takePhoto(); - } - - public int getCameraPosition() { - return mCameraPosition; - } - - public int getCameraOrientation() { - return mCameraOrientation; - } - - public int getDuration() { - return cameraBase.getDuration(); - } - - public void setQuality(int quality) { - mQuality = Quality.MAX_480P.getValue(); - switch (quality) { - case 0: - mQuality = 0; - break; - case 1: - mQuality = 1; - break; - case 2: - mQuality = 2; - break; - case 3: - mQuality = 3; - break; - case 4: - mQuality = 4; - break; - case 5: - mQuality = 5; - break; - case 6: - mQuality = 6; - break; - - } - cameraBase.setQuality(mQuality); - } - - public void setListener(CameraEventListener listener) { - this.listener = listener; - } - - public void setCameraPosition(int position) { - cameraBase.setCameraPosition(CameraPosition.values()[position]); - } - - public void setCameraPosition(FancyCamera.CameraPosition position) { - cameraBase.setCameraPosition(position); - } - - public void setCameraOrientation(int orientation) { - cameraBase.setCameraOrientation(CameraOrientation.values()[orientation]); - } - - public void setCameraOrientation(FancyCamera.CameraOrientation orientation) { - cameraBase.setCameraOrientation(orientation); - } - - public void requestPermission() { - ActivityCompat.requestPermissions((Activity) getContext(), VIDEO_RECORDER_PERMISSIONS, VIDEO_RECORDER_PERMISSIONS_REQUEST); - } - - public boolean hasPermission() { - if (Build.VERSION.SDK_INT < 23) { - return true; - } - return ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) == (PackageManager.PERMISSION_GRANTED) && ContextCompat.checkSelfPermission(getContext(), Manifest.permission.RECORD_AUDIO) == (PackageManager.PERMISSION_GRANTED); - } - - public boolean hasStoragePermission() { - return cameraBase.hasStoragePermission(); - } - - public void requestStoragePermission() { - cameraBase.requestStoragePermission(); - } - - public void start() { - cameraBase.start(); - } - - public void stopRecording() { - cameraBase.stopRecording(); - } - - public void startRecording() { - deInitListener(); - cameraBase.startRecording(); - } - - public void stop() { - cameraBase.stop(); - } - - public void release() { - cameraBase.release(); - deInitListener(); - } - - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - if (cameraBase.getTextViewListener() != null) { - cameraBase.getTextViewListener().onSurfaceTextureAvailable(surface, width, height); - } - if (!hasPermission()) { - requestPermission(); - return; - } - cameraBase.openCamera(width, height); - } - - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - if (cameraBase.getTextViewListener() != null) { - cameraBase.getTextViewListener().onSurfaceTextureSizeChanged(surface, width, height); - } - cameraBase.updatePreview(); - } - - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - if (cameraBase.getTextViewListener() != null) { - cameraBase.getTextViewListener().onSurfaceTextureDestroyed(surface); - } - return true; - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - if (cameraBase.getTextViewListener() != null) { - cameraBase.getTextViewListener().onSurfaceTextureUpdated(surface); - } - } - - public enum Quality { - MAX_480P(0), - MAX_720P(1), - MAX_1080P(2), - MAX_2160P(3), - HIGHEST(4), - LOWEST(5), - QVGA(6); - private int value; - - private Quality(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - } - - public enum CameraOrientation { - UNKNOWN(0), - PORTRAIT(1), - PORTRAIT_UPSIDE_DOWN(2), - LANDSCAPE_LEFT(3), - LANDSCAPE_RIGHT(4); - private int value; - - CameraOrientation(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - } - - public enum CameraPosition { - BACK(0), - FRONT(1); - private int value; - - CameraPosition(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - } - - public void toggleCamera() { - cameraBase.toggleCamera(); - } - - public void setEnableAudioLevels(boolean enableAudioLevels) { - cameraBase.setEnableAudioLevels(enableAudioLevels); - } - - public boolean isAudioLevelsEnabled() { - return cameraBase.isAudioLevelsEnabled(); - } - - public double getAmplitude() { - double amp = 0; - if (isAudioLevelsEnabled()) { - if (cameraRecording()) { - amp = cameraBase.getRecorder() != null ? cameraBase.getRecorder().getMaxAmplitude() : 0; - return amp; - } - try { - amp = recorder != null ? recorder.getMaxAmplitude() : 0; - } catch (Exception ignored) { - amp = 0; - } - } - return amp; - } - - public double getDB() { - return 20 * Math.log10(getAmplitude() / 32767.0); - } - - public double getAmplitudeEMA() { - double amp = getAmplitude(); - mEMA = EMA_FILTER * amp + (1.0 - EMA_FILTER) * mEMA; - return mEMA; - } - - public int getMaxAudioBitRate() { - return cameraBase.getMaxAudioBitRate(); - } - - - public int getMaxVideoBitrate() { - return cameraBase.getMaxVideoBitrate(); - } - - - public int getMaxVideoFrameRate() { - return cameraBase.getMaxVideoFrameRate(); - } - - - public boolean getDisableHEVC() { - return cameraBase.getDisableHEVC(); - } - - - public void setDisableHEVC(boolean disableHEVC) { - cameraBase.setDisableHEVC(disableHEVC); - } - - public void setMaxAudioBitRate(int maxAudioBitRate) { - cameraBase.setMaxAudioBitRate(maxAudioBitRate); - } - - public void setMaxVideoBitrate(int maxVideoBitrate) { - cameraBase.setMaxVideoBitrate(maxVideoBitrate); - } - - public void setMaxVideoFrameRate(int maxVideoFrameRate) { - cameraBase.setMaxVideoFrameRate(maxVideoFrameRate); - } -} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/FancyCamera.kt b/fancycamera/src/main/java/co/fitcom/fancycamera/FancyCamera.kt new file mode 100644 index 0000000..6fb86cd --- /dev/null +++ b/fancycamera/src/main/java/co/fitcom/fancycamera/FancyCamera.kt @@ -0,0 +1,413 @@ +/* + * Created By Osei Fortune on 2/16/18 8:43 PM + * Copyright (c) 2018 + * Last modified 2/16/18 7:58 PM + * + */ + +package co.fitcom.fancycamera + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import android.content.res.TypedArray +import android.graphics.SurfaceTexture +import android.media.MediaRecorder +import android.os.Build +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat + +import android.util.AttributeSet +import android.view.TextureView +import android.view.ViewGroup + +import java.io.File +import java.io.IOException + + +class FancyCamera : TextureView, TextureView.SurfaceTextureListener { + private var mFlashEnabled = false + private val isStarted = false + private var mCameraPosition = 0 + private var mCameraOrientation = 0 + private var mQuality = Quality.MAX_480P.value + private val mLock = Any() + private var listener: CameraEventListener? = null + private val isReady = false + private var cameraBase: CameraBase? = null + private var recorder: MediaRecorder? = null + private var isGettingAudioLvls = false + private var mEMA = 0.0 + private var internalListener: CameraEventListener? = null + + val numberOfCameras: Int + get() = cameraBase!!.numberOfCameras + + var autoSquareCrop: Boolean + get() = cameraBase!!.autoSquareCrop + set(autoSquareCrop) { + cameraBase!!.autoSquareCrop = autoSquareCrop + } + + var autoFocus: Boolean + get() = cameraBase!!.autoFocus + set(focus) { + cameraBase!!.autoFocus = focus + } + + var saveToGallery: Boolean + get() = cameraBase!!.saveToGallery + set(saveToGallery) { + cameraBase!!.saveToGallery = saveToGallery + } + + internal var file: File? + get() = cameraBase!!.file + set(file) { + cameraBase!!.file = file + } + + var cameraPosition: Int + get() = mCameraPosition + set(position) = cameraBase!!.setCameraPosition(CameraPosition.values()[position]) + + var cameraOrientation: Int + get() = mCameraOrientation + set(orientation) = cameraBase!!.setCameraOrientation(CameraOrientation.values()[orientation]) + + val duration: Int + get() = cameraBase!!.duration + + val isAudioLevelsEnabled: Boolean + get() = cameraBase!!.isAudioLevelsEnabled + + val amplitude: Double + get() { + var amp = 0.0 + if (isAudioLevelsEnabled) { + if (cameraRecording()) { + amp = (if (cameraBase!!.recorder != null) cameraBase!!.recorder?.maxAmplitude else 0)!!.toDouble() + return amp + } + try { + amp = (if (recorder != null) recorder!!.maxAmplitude else 0).toDouble() + } catch (ignored: Exception) { + amp = 0.0 + } + + } + return amp + } + + val db: Double + get() = 20 * Math.log10(amplitude / 32767.0) + + val amplitudeEMA: Double + get() { + val amp = amplitude + mEMA = EMA_FILTER * amp + (1.0 - EMA_FILTER) * mEMA + return mEMA + } + + var maxAudioBitRate: Int + get() = cameraBase!!.maxAudioBitRate + set(maxAudioBitRate) { + cameraBase!!.maxAudioBitRate = maxAudioBitRate + } + + + var maxVideoBitrate: Int + get() = cameraBase!!.maxVideoBitrate + set(maxVideoBitrate) { + cameraBase!!.maxVideoBitrate = maxVideoBitrate + } + + + var maxVideoFrameRate: Int + get() = cameraBase!!.maxVideoFrameRate + set(maxVideoFrameRate) { + cameraBase!!.maxVideoFrameRate = maxVideoFrameRate + } + + + var disableHEVC: Boolean + get() = cameraBase!!.disableHEVC + set(disableHEVC) { + cameraBase!!.disableHEVC = disableHEVC + } + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + private fun initListener() { + if (isAudioLevelsEnabled) { + if (!hasPermission()) { + return + } + if (recorder != null) deInitListener() + recorder = MediaRecorder() + recorder!!.setAudioSource(MediaRecorder.AudioSource.MIC) + recorder!!.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP) + recorder!!.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) + recorder!!.setOutputFile("/dev/null") + try { + recorder!!.prepare() + recorder!!.start() + isGettingAudioLvls = true + mEMA = 0.0 + } catch (e: IOException) { + // Need this ??? + e.printStackTrace() + } catch (e: Exception) { + e.printStackTrace() + } + + } + } + + fun deInitListener() { + if (isAudioLevelsEnabled && isGettingAudioLvls) { + try { + recorder!!.stop() + recorder!!.release() + recorder = null + isGettingAudioLvls = false + } catch (e: Exception) { + e.printStackTrace() + } + + } + } + + private fun init(context: Context, attrs: AttributeSet?) { + if (Build.VERSION.SDK_INT >= 21) { + cameraBase = Camera2(getContext(), this, CameraPosition.values()[cameraPosition], CameraOrientation.values()[cameraOrientation]) + } else { + cameraBase = Camera1(getContext(), this, CameraPosition.values()[cameraPosition]) + } + + internalListener = object : CameraEventListener { + override fun onCameraOpen() { + initListener() + if (listener != null) { + listener!!.onCameraOpen() + } + } + + override fun onCameraClose() { + deInitListener() + if (listener != null) { + listener!!.onCameraClose() + } + } + + override fun onPhotoEvent(event: PhotoEvent) { + if (listener != null) { + listener!!.onPhotoEvent(event) + } + } + + override fun onVideoEvent(event: VideoEvent) { + if (listener != null) { + listener!!.onVideoEvent(event) + } + } + } + + cameraBase!!.listener = internalListener + + if (attrs != null) { + val a = context.obtainStyledAttributes( + attrs, + R.styleable.FancyCamera) + + try { + mFlashEnabled = a.getBoolean(R.styleable.FancyCamera_enableFlash, false) + saveToGallery = a.getBoolean(R.styleable.FancyCamera_saveToGallery, false) + mQuality = a.getInteger(R.styleable.FancyCamera_quality, Quality.MAX_480P.value) + setQuality(mQuality) + mCameraPosition = a.getInteger(R.styleable.FancyCamera_cameraPosition, 0) + cameraPosition = mCameraPosition + mCameraOrientation = a.getInteger(R.styleable.FancyCamera_cameraOrientation, 0) + cameraOrientation = mCameraOrientation + disableHEVC = a.getBoolean(R.styleable.FancyCamera_disableHEVC, false) + maxAudioBitRate = a.getInteger(R.styleable.FancyCamera_maxAudioBitRate, -1) + maxVideoBitrate = a.getInteger(R.styleable.FancyCamera_maxVideoBitrate, -1) + maxVideoFrameRate = a.getInteger(R.styleable.FancyCamera_maxVideoFrameRate, -1) + setEnableAudioLevels(a.getBoolean(R.styleable.FancyCamera_audioLevels, false)) + + } finally { + a.recycle() + } + } + this.surfaceTextureListener = this + } + + fun hasFlash(): Boolean { + return cameraBase!!.hasFlash() + } + + fun toggleFlash() { + cameraBase!!.toggleFlash() + } + + fun enableFlash() { + cameraBase!!.enableFlash() + } + + fun disableFlash() { + cameraBase!!.disableFlash() + } + + fun flashEnabled(): Boolean { + return cameraBase!!.flashEnabled() + } + + fun cameraStarted(): Boolean { + return cameraBase!!.cameraStarted() + } + + fun cameraRecording(): Boolean { + return cameraBase!!.cameraRecording() + } + + fun takePhoto() { + cameraBase!!.takePhoto() + } + + fun setQuality(quality: Int) { + mQuality = Quality.MAX_480P.value + when (quality) { + 0 -> mQuality = 0 + 1 -> mQuality = 1 + 2 -> mQuality = 2 + 3 -> mQuality = 3 + 4 -> mQuality = 4 + 5 -> mQuality = 5 + 6 -> mQuality = 6 + } + cameraBase!!.quality = mQuality + } + + fun setListener(listener: CameraEventListener) { + this.listener = listener + } + + fun setCameraPosition(position: FancyCamera.CameraPosition) { + cameraBase!!.setCameraPosition(position) + } + + fun setCameraOrientation(orientation: FancyCamera.CameraOrientation) { + cameraBase!!.setCameraOrientation(orientation) + } + + fun requestPermission() { + cameraBase!!.requestPermission() + } + + fun hasPermission(): Boolean { + return cameraBase!!.hasPermission() + } + + fun hasStoragePermission(): Boolean { + return cameraBase!!.hasStoragePermission() + } + + fun requestStoragePermission() { + cameraBase!!.requestStoragePermission() + } + + fun start() { + cameraBase!!.start() + } + + fun stopRecording() { + cameraBase!!.stopRecording() + } + + fun startRecording() { + deInitListener() + cameraBase!!.startRecording() + } + + fun stop() { + cameraBase!!.stop() + } + + fun release() { + cameraBase!!.release() + deInitListener() + } + + override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { + if (cameraBase!!.textViewListener != null) { + cameraBase!!.textViewListener!!.onSurfaceTextureAvailable(surface, width, height) + } + if (!hasPermission()) { + requestPermission() + return + } + cameraBase!!.openCamera(width, height) + } + + override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) { + if (cameraBase!!.textViewListener != null) { + cameraBase!!.textViewListener!!.onSurfaceTextureSizeChanged(surface, width, height) + } + cameraBase!!.updatePreview() + } + + override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { + if (cameraBase!!.textViewListener != null) { + cameraBase!!.textViewListener!!.onSurfaceTextureDestroyed(surface) + } + return true + } + + override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { + if (cameraBase!!.textViewListener != null) { + cameraBase!!.textViewListener!!.onSurfaceTextureUpdated(surface) + } + } + + enum class Quality private constructor(val value: Int) { + MAX_480P(0), + MAX_720P(1), + MAX_1080P(2), + MAX_2160P(3), + HIGHEST(4), + LOWEST(5), + QVGA(6) + } + + enum class CameraOrientation private constructor(val value: Int) { + UNKNOWN(0), + PORTRAIT(1), + PORTRAIT_UPSIDE_DOWN(2), + LANDSCAPE_LEFT(3), + LANDSCAPE_RIGHT(4) + } + + enum class CameraPosition private constructor(val value: Int) { + BACK(0), + FRONT(1) + } + + fun toggleCamera() { + cameraBase!!.toggleCamera() + } + + fun setEnableAudioLevels(enableAudioLevels: Boolean) { + cameraBase!!.setEnableAudioLevels(enableAudioLevels) + } + + companion object { + private val EMA_FILTER = 0.6 + } +} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/PhotoEvent.java b/fancycamera/src/main/java/co/fitcom/fancycamera/PhotoEvent.java deleted file mode 100644 index ba83595..0000000 --- a/fancycamera/src/main/java/co/fitcom/fancycamera/PhotoEvent.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Created By Osei Fortune on 2/16/18 8:43 PM - * Copyright (c) 2018 - * Last modified 2/16/18 7:39 PM - * - */ - -package co.fitcom.fancycamera; - -import androidx.annotation.Nullable; - -import java.io.File; - - -public class PhotoEvent { - private EventType mType; - private File mFile; - private String mMessage; - - PhotoEvent(EventType type, @Nullable File file, @Nullable String message) { - mType = type; - mFile = file; - mMessage = message; - } - - public EventType getType() { - return mType; - } - - public File getFile() { - return mFile; - } - - public String getMessage() { - return mMessage; - } - - public enum EventError { - UNKNOWN { - @Override - public String toString() { - return "Unknown"; - } - } - } - - public enum EventInfo { - PHOTO_TAKEN { - @Override - public String toString() { - return "Photo taken"; - } - }, - UNKNOWN { - @Override - public String toString() { - return "Unknown"; - } - } - } - -} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/PhotoEvent.kt b/fancycamera/src/main/java/co/fitcom/fancycamera/PhotoEvent.kt new file mode 100644 index 0000000..132ce14 --- /dev/null +++ b/fancycamera/src/main/java/co/fitcom/fancycamera/PhotoEvent.kt @@ -0,0 +1,36 @@ +/* + * Created By Osei Fortune on 2/16/18 8:43 PM + * Copyright (c) 2018 + * Last modified 2/16/18 7:39 PM + * + */ + +package co.fitcom.fancycamera + +import java.io.File + + +class PhotoEvent internal constructor(val type: EventType, val file: File?, val message: String?) { + + enum class EventError { + UNKNOWN { + override fun toString(): String { + return "Unknown" + } + } + } + + enum class EventInfo { + PHOTO_TAKEN { + override fun toString(): String { + return "Photo taken" + } + }, + UNKNOWN { + override fun toString(): String { + return "Unknown" + } + } + } + +} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/TextViewListener.java b/fancycamera/src/main/java/co/fitcom/fancycamera/TextViewListener.java deleted file mode 100644 index 42d57d8..0000000 --- a/fancycamera/src/main/java/co/fitcom/fancycamera/TextViewListener.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Created By Osei Fortune on 3/24/18 5:18 PM - * Copyright (c) 2018 - * Last modified 3/24/18 5:18 PM - * - */ - -package co.fitcom.fancycamera; - -import android.graphics.SurfaceTexture; - -/** - * Created by triniwiz on 3/24/18 - */ -public interface TextViewListener { - void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height); - void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height); - void onSurfaceTextureDestroyed(SurfaceTexture surface); - void onSurfaceTextureUpdated(SurfaceTexture surface); -} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/TextViewListener.kt b/fancycamera/src/main/java/co/fitcom/fancycamera/TextViewListener.kt new file mode 100644 index 0000000..3ee52b7 --- /dev/null +++ b/fancycamera/src/main/java/co/fitcom/fancycamera/TextViewListener.kt @@ -0,0 +1,20 @@ +/* + * Created By Osei Fortune on 3/24/18 5:18 PM + * Copyright (c) 2018 + * Last modified 3/24/18 5:18 PM + * + */ + +package co.fitcom.fancycamera + +import android.graphics.SurfaceTexture + +/** + * Created by triniwiz on 3/24/18 + */ +interface TextViewListener { + fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) + fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) + fun onSurfaceTextureDestroyed(surface: SurfaceTexture) + fun onSurfaceTextureUpdated(surface: SurfaceTexture) +} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/VideoEvent.java b/fancycamera/src/main/java/co/fitcom/fancycamera/VideoEvent.java deleted file mode 100644 index 8d77fe2..0000000 --- a/fancycamera/src/main/java/co/fitcom/fancycamera/VideoEvent.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Created By Osei Fortune on 2/16/18 8:44 PM - * Copyright (c) 2018 - * Last modified 2/16/18 7:39 PM - * - */ - -package co.fitcom.fancycamera; - -import androidx.annotation.Nullable; - -import java.io.File; - -public class VideoEvent { - private EventType mType; - private File mFile; - private String mMessage; - VideoEvent(EventType type, @Nullable File file, @Nullable String message){ - mType = type; - mFile = file; - mMessage = message; - } - public EventType getType(){ - return mType; - } - public File getFile(){ - return mFile; - } - - public String getMessage(){ - return mMessage; - } - - public enum EventError{ - SERVER_DIED{ - @Override - public String toString() { - return "Server died"; - } - }, - UNKNOWN{ - @Override - public String toString() { - return "Unknown"; - } - } - } - - public enum EventInfo{ - RECORDING_STARTED{ - @Override - public String toString() { - return "Recording started"; - } - }, - RECORDING_FINISHED{ - @Override - public String toString() { - return "Recording finished"; - } - }, - MAX_DURATION_REACHED { - @Override - public String toString() { - return "Max duration reached"; - } - }, - MAX_FILESIZE_APPROACHING{ - @Override - public String toString() { - return "Max filesize approaching"; - } - }, - MAX_FILESIZE_REACHED{ - @Override - public String toString() { - return "Max filesize reached"; - } - }, - NEXT_OUTPUT_FILE_STARTED{ - @Override - public String toString() { - return "Next output file started"; - } - }, - UNKNOWN{ - @Override - public String toString() { - return "Unknown"; - } - } - } -} diff --git a/fancycamera/src/main/java/co/fitcom/fancycamera/VideoEvent.kt b/fancycamera/src/main/java/co/fitcom/fancycamera/VideoEvent.kt new file mode 100644 index 0000000..e5e38cc --- /dev/null +++ b/fancycamera/src/main/java/co/fitcom/fancycamera/VideoEvent.kt @@ -0,0 +1,64 @@ +/* + * Created By Osei Fortune on 2/16/18 8:44 PM + * Copyright (c) 2018 + * Last modified 2/16/18 7:39 PM + * + */ + +package co.fitcom.fancycamera + +import java.io.File + +class VideoEvent internal constructor(val type: EventType, val file: File?, val message: String?) { + + enum class EventError { + SERVER_DIED { + override fun toString(): String { + return "Server died" + } + }, + UNKNOWN { + override fun toString(): String { + return "Unknown" + } + } + } + + enum class EventInfo { + RECORDING_STARTED { + override fun toString(): String { + return "Recording started" + } + }, + RECORDING_FINISHED { + override fun toString(): String { + return "Recording finished" + } + }, + MAX_DURATION_REACHED { + override fun toString(): String { + return "Max duration reached" + } + }, + MAX_FILESIZE_APPROACHING { + override fun toString(): String { + return "Max filesize approaching" + } + }, + MAX_FILESIZE_REACHED { + override fun toString(): String { + return "Max filesize reached" + } + }, + NEXT_OUTPUT_FILE_STARTED { + override fun toString(): String { + return "Next output file started" + } + }, + UNKNOWN { + override fun toString(): String { + return "Unknown" + } + } + } +} diff --git a/fancycamera/src/test/java/co/fitcom/fancycamera/ExampleUnitTest.java b/fancycamera/src/test/java/co/fitcom/fancycamera/ExampleUnitTest.java deleted file mode 100644 index b84bee9..0000000 --- a/fancycamera/src/test/java/co/fitcom/fancycamera/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package co.fitcom.fancycamera; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/fancycamera/src/test/java/co/fitcom/fancycamera/ExampleUnitTest.kt b/fancycamera/src/test/java/co/fitcom/fancycamera/ExampleUnitTest.kt new file mode 100644 index 0000000..41d6350 --- /dev/null +++ b/fancycamera/src/test/java/co/fitcom/fancycamera/ExampleUnitTest.kt @@ -0,0 +1,18 @@ +package co.fitcom.fancycamera + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see [Testing documentation](http://d.android.com/tools/testing) + */ +class ExampleUnitTest { + @Test + @Throws(Exception::class) + fun addition_isCorrect() { + assertEquals(4, (2 + 2).toLong()) + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 40b1fd6..70a40c5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,4 +18,7 @@ org.gradle.jvmargs=-Xmx1536m BUILD_TOOLS_VERSION=28.0.3 COMPILE_SDK_VERSION=28 android.useAndroidX=true -android.enableJetifier=true \ No newline at end of file +android.enableJetifier=true +camerax_version = 1.0.0-alpha06 +camerax_view_version = 1.0.0-alpha03 +camerax_ext_version = 1.0.0-alpha03 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9945ebb..70a9e44 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Aug 08 15:52:29 AST 2019 +#Sun Nov 17 18:15:25 AST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip