@ -0,0 +1,21 @@ |
||||
MIT License |
||||
|
||||
Copyright (c) 2016 Florent37 |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -1 +1,118 @@ |
||||
# CameraFragment |
||||
# CameraFragment |
||||
|
||||
[![gif](https://raw.githubusercontent.com/florent37/CameraFragment/master/media/cameraFragment.gif)](https://github.com/florent37/CameraFragment) |
||||
|
||||
```java |
||||
//you can configure the fragment by the configuration builder |
||||
CameraFragment cameraFragment = CameraFragment.newInstance(new Configuration.Builder().build()); |
||||
|
||||
getSupportFragmentManager().beginTransaction() |
||||
.replace(R.id.content, cameraFragment, FRAGMENT_TAG) |
||||
.commit(); |
||||
``` |
||||
|
||||
## Actions |
||||
|
||||
You can directly take a photo / video with |
||||
```java |
||||
cameraFragment.takePhotoOrCaptureVideo(); |
||||
``` |
||||
|
||||
[![gif](https://raw.githubusercontent.com/florent37/CameraFragment/master/media/take_photo.gif)](https://github.com/florent37/CameraFragment) |
||||
|
||||
Flash can be enable / disabled ( `AUTO` / `OFF` / `ON` ) with |
||||
|
||||
```java |
||||
cameraFragment.toggleFlashMode(); |
||||
``` |
||||
|
||||
[![gif](https://raw.githubusercontent.com/florent37/CameraFragment/master/media/switch_flash.gif)](https://github.com/florent37/CameraFragment) |
||||
|
||||
Camera Type can be modified ( `BACK` / `FRONT` ) with |
||||
|
||||
```java |
||||
cameraFragment.switchCameraType(); |
||||
``` |
||||
|
||||
[![gif](https://raw.githubusercontent.com/florent37/CameraFragment/master/media/switch_camera.gif)](https://github.com/florent37/CameraFragment) |
||||
|
||||
Camera action ( `PHOTO` / `VIDEO` ) can be modified with |
||||
|
||||
```java |
||||
cameraFragment.switchAction(); |
||||
``` |
||||
|
||||
[![gif](https://raw.githubusercontent.com/florent37/CameraFragment/master/media/switch_action.gif)](https://github.com/florent37/CameraFragment) |
||||
|
||||
And you can change the captured photo / video size with |
||||
|
||||
```java |
||||
cameraFragment.openSettingDialog(); |
||||
``` |
||||
|
||||
[![gif](https://raw.githubusercontent.com/florent37/CameraFragment/master/media/settings.gif)](https://github.com/florent37/CameraFragment) |
||||
|
||||
# Listeners |
||||
|
||||
## Result |
||||
|
||||
Get back the result of the camera record / photo in the `CameraFragmentResultListener` |
||||
|
||||
```java |
||||
cameraFragment.setResultListener(new CameraFragmentResultListener() { |
||||
@Override |
||||
public void onVideoRecorded(byte[] bytes, String filePath) { |
||||
//called when the video record is finished and saved |
||||
|
||||
startActivityForResult(PreviewActivity.newIntentVideo(MainActivity.this, filePath)); |
||||
} |
||||
|
||||
@Override |
||||
public void onPhotoTaken(byte[] bytes, String filePath) { |
||||
//called when the photo is taken and saved |
||||
|
||||
startActivity(PreviewActivity.newIntentPhoto(MainActivity.this, filePath)); |
||||
} |
||||
}); |
||||
``` |
||||
|
||||
## Camera Listener |
||||
|
||||
```java |
||||
cameraFragment.setStateListener(new CameraFragmentStateListener() { |
||||
|
||||
//when the current displayed camera is the back |
||||
void onCurrentCameraBack(); |
||||
//when the current displayed camera is the front |
||||
void onCurrentCameraFront(); |
||||
|
||||
//when the flash is at mode auto |
||||
void onFlashAuto(); |
||||
//when the flash is at on |
||||
void onFlashOn(); |
||||
//when the flash is off |
||||
void onFlashOff(); |
||||
|
||||
//if the camera is ready to take a photo |
||||
void onCameraSetupForPhoto(); |
||||
//if the camera is ready to take a video |
||||
void onCameraSetupForVideo(); |
||||
|
||||
//when the camera state is "ready to record a video" |
||||
void onRecordStateVideoReadyForRecord(); |
||||
//when the camera state is "recording a video" |
||||
void onRecordStateVideoInProgress(); |
||||
//when the camera state is "ready to take a photo" |
||||
void onRecordStatePhoto(); |
||||
|
||||
//after the rotation of the screen / camera |
||||
void shouldRotateControls(int degrees); |
||||
|
||||
void onStartVideoRecord(File outputFile); |
||||
void onStopVideoRecord(); |
||||
}); |
||||
``` |
||||
|
||||
## Text |
||||
|
||||
CameraFragment can ping you with the current record duration with `CameraFragmentTextListener` |
@ -0,0 +1 @@ |
||||
/build |
@ -0,0 +1,28 @@ |
||||
apply plugin: 'com.android.application' |
||||
|
||||
ext { |
||||
PUBLISH_VERSION = '1.2.0' |
||||
PUBLISH_VERSION_CODE = 3 |
||||
SUPPORT_VERSION = '25.1.0' |
||||
TARGET_SDK = 24 |
||||
MIN_SDK = 10 |
||||
BUILD_TOOLS = "24.0.3" |
||||
} |
||||
|
||||
android { |
||||
compileSdkVersion TARGET_SDK |
||||
buildToolsVersion BUILD_TOOLS |
||||
|
||||
defaultConfig { |
||||
minSdkVersion MIN_SDK |
||||
targetSdkVersion TARGET_SDK |
||||
versionCode PUBLISH_VERSION_CODE |
||||
versionName PUBLISH_VERSION |
||||
} |
||||
} |
||||
|
||||
dependencies { |
||||
compile "com.android.support:appcompat-v7:$SUPPORT_VERSION" |
||||
compile 'com.jakewharton:butterknife:7.0.1' |
||||
compile project(':camerafragment') |
||||
} |
@ -0,0 +1,17 @@ |
||||
# Add project specific ProGuard rules here. |
||||
# By default, the flags in this file are appended to flags specified |
||||
# in /Users/memfis/Library/Android/sdk/tools/proguard/proguard-android.txt |
||||
# You can edit the include path and order by changing the proguardFiles |
||||
# directive in build.gradle. |
||||
# |
||||
# For more details, see |
||||
# http://developer.android.com/guide/developing/tools/proguard.html |
||||
|
||||
# Add any project specific keep options here: |
||||
|
||||
# If your project uses WebView with JS, uncomment the following |
||||
# and specify the fully qualified class name to the JavaScript interface |
||||
# class: |
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { |
||||
# public *; |
||||
#} |
@ -0,0 +1,37 @@ |
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||
package="com.github.florent37.camerafragment.sample"> |
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" /> |
||||
<uses-permission android:name="android.permission.FLASHLIGHT" /> |
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" /> |
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> |
||||
|
||||
|
||||
<application |
||||
android:allowBackup="true" |
||||
android:icon="@mipmap/ic_launcher" |
||||
android:label="@string/app_name" |
||||
android:supportsRtl="true" |
||||
android:theme="@style/AppTheme"> |
||||
|
||||
<activity |
||||
android:name="com.github.florent37.camerafragment.PreviewActivity" |
||||
android:screenOrientation="portrait" |
||||
android:theme="@style/ThemeFullscreen" /> |
||||
|
||||
<activity |
||||
android:name="com.github.florent37.camerafragment.sample.MainActivity" |
||||
android:screenOrientation="portrait" |
||||
android:theme="@style/ThemeFullscreen" > |
||||
<intent-filter> |
||||
<action android:name="android.intent.action.MAIN" /> |
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" /> |
||||
</intent-filter> |
||||
</activity> |
||||
|
||||
</application> |
||||
|
||||
|
||||
</manifest> |
@ -0,0 +1,333 @@ |
||||
package com.github.florent37.camerafragment.sample; |
||||
|
||||
import android.Manifest; |
||||
import android.content.Intent; |
||||
import android.content.pm.PackageManager; |
||||
import android.os.Build; |
||||
import android.os.Bundle; |
||||
import android.support.annotation.NonNull; |
||||
import android.support.annotation.RequiresPermission; |
||||
import android.support.v4.app.ActivityCompat; |
||||
import android.support.v4.view.ViewCompat; |
||||
import android.support.v7.app.AppCompatActivity; |
||||
import android.view.View; |
||||
import android.widget.TextView; |
||||
|
||||
import java.io.File; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import butterknife.Bind; |
||||
import butterknife.ButterKnife; |
||||
import butterknife.OnClick; |
||||
import com.github.florent37.camerafragment.CameraFragment; |
||||
import com.github.florent37.camerafragment.configuration.Configuration; |
||||
import com.github.florent37.camerafragment.PreviewActivity; |
||||
import com.github.florent37.camerafragment.listeners.CameraFragmentStateListener; |
||||
import com.github.florent37.camerafragment.listeners.CameraFragmentControlsListener; |
||||
import com.github.florent37.camerafragment.listeners.CameraFragmentResultListener; |
||||
import com.github.florent37.camerafragment.listeners.CameraFragmentVideoRecordTextListener; |
||||
import com.github.florent37.camerafragment.widgets.CameraSettingsView; |
||||
import com.github.florent37.camerafragment.widgets.CameraSwitchView; |
||||
import com.github.florent37.camerafragment.widgets.FlashSwitchView; |
||||
import com.github.florent37.camerafragment.widgets.MediaActionSwitchView; |
||||
import com.github.florent37.camerafragment.widgets.RecordButton; |
||||
|
||||
public class MainActivity extends AppCompatActivity { |
||||
|
||||
private static final int REQUEST_CAMERA_PERMISSIONS = 931; |
||||
private static final int REQUEST_PREVIEW_CODE = 1001; |
||||
|
||||
public static final String FRAGMENT_TAG = "camera"; |
||||
|
||||
@Bind(R.id.settings_view) CameraSettingsView settingsView; |
||||
@Bind(R.id.flash_switch_view) FlashSwitchView flashSwitchView; |
||||
@Bind(R.id.front_back_camera_switcher) CameraSwitchView cameraSwitchView; |
||||
@Bind(R.id.record_button) RecordButton recordButton; |
||||
@Bind(R.id.photo_video_camera_switcher) MediaActionSwitchView mediaActionSwitchView; |
||||
|
||||
@Bind(R.id.record_duration_text) TextView recordDurationText; |
||||
@Bind(R.id.record_size_mb_text) TextView recordSizeText; |
||||
|
||||
@Bind(R.id.cameraLayout) View cameraLayout; |
||||
@Bind(R.id.addCameraButton) View addCameraButton; |
||||
|
||||
@Override |
||||
protected void onCreate(Bundle savedInstanceState) { |
||||
super.onCreate(savedInstanceState); |
||||
setContentView(R.layout.activity_fragment); |
||||
ButterKnife.bind(this); |
||||
|
||||
recordButton.setVisibility(View.GONE); |
||||
settingsView.setVisibility(View.GONE); |
||||
flashSwitchView.setVisibility(View.GONE); |
||||
cameraSwitchView.setVisibility(View.GONE); |
||||
mediaActionSwitchView.setVisibility(View.GONE); |
||||
|
||||
setupViewListeners(); |
||||
} |
||||
|
||||
@OnClick(R.id.take_photo) |
||||
public void takePhotoClicked(){ |
||||
final CameraFragment cameraFragment = getCameraFragment(); |
||||
if (cameraFragment != null) { |
||||
cameraFragment.takePhotoOrCaptureVideo(); |
||||
} |
||||
} |
||||
|
||||
private void setupViewListeners() { |
||||
flashSwitchView.setFlashSwitchListener(new FlashSwitchView.FlashModeSwitchListener() { |
||||
@Override |
||||
public void toggleFlashMode() { |
||||
final CameraFragment cameraFragment = getCameraFragment(); |
||||
if (cameraFragment != null) { |
||||
cameraFragment.toggleFlashMode(); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
cameraSwitchView.setOnCameraTypeChangeListener(new CameraSwitchView.OnCameraTypeChangeListener() { |
||||
@Override |
||||
public void switchCameraType() { |
||||
final CameraFragment cameraFragment = getCameraFragment(); |
||||
if (cameraFragment != null) { |
||||
cameraFragment.switchCameraType(); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
recordButton.setRecordButtonListener(new RecordButton.RecordButtonListener() { |
||||
@Override |
||||
public void onRecordButtonClicked() { |
||||
final CameraFragment cameraFragment = getCameraFragment(); |
||||
if (cameraFragment != null) { |
||||
cameraFragment.takePhotoOrCaptureVideo(); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
mediaActionSwitchView.setOnMediaActionStateChangeListener(new MediaActionSwitchView.OnMediaActionStateChangeListener() { |
||||
@Override |
||||
public void switchAction() { |
||||
final CameraFragment cameraFragment = getCameraFragment(); |
||||
if (cameraFragment != null) { |
||||
cameraFragment.switchAction(); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
settingsView.setOnClickListener(new View.OnClickListener() { |
||||
@Override |
||||
public void onClick(View view) { |
||||
final CameraFragment cameraFragment = getCameraFragment(); |
||||
if (cameraFragment != null) { |
||||
cameraFragment.openSettingDialog(); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@OnClick(R.id.addCameraButton) |
||||
public void onAddCameraClicked(){ |
||||
if (Build.VERSION.SDK_INT > 15) { |
||||
final String[] permissions = { |
||||
Manifest.permission.CAMERA, |
||||
Manifest.permission.RECORD_AUDIO, |
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE, |
||||
Manifest.permission.READ_EXTERNAL_STORAGE}; |
||||
|
||||
final List<String> permissionsToRequest = new ArrayList<>(); |
||||
for (String permission : permissions) { |
||||
if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { |
||||
permissionsToRequest.add(permission); |
||||
} |
||||
} |
||||
if (!permissionsToRequest.isEmpty()) { |
||||
ActivityCompat.requestPermissions(this, permissionsToRequest.toArray(new String[permissionsToRequest.size()]), REQUEST_CAMERA_PERMISSIONS); |
||||
} else addCamera(); |
||||
} else { |
||||
addCamera(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { |
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults); |
||||
if (grantResults.length != 0) { |
||||
addCamera(); |
||||
} |
||||
} |
||||
|
||||
@RequiresPermission(Manifest.permission.CAMERA) |
||||
public void addCamera() { |
||||
addCameraButton.setVisibility(View.GONE); |
||||
cameraLayout.setVisibility(View.VISIBLE); |
||||
|
||||
final CameraFragment cameraFragment = CameraFragment.newInstance(new Configuration.Builder().build()); |
||||
getSupportFragmentManager().beginTransaction() |
||||
.replace(R.id.content, cameraFragment, FRAGMENT_TAG) |
||||
.commit(); |
||||
|
||||
if (cameraFragment != null) { |
||||
|
||||
cameraFragment.setResultListener(new CameraFragmentResultListener() { |
||||
@Override |
||||
public void onVideoRecorded(String filePath) { |
||||
Intent intent = PreviewActivity.newIntentVideo(MainActivity.this, filePath); |
||||
startActivityForResult(intent, REQUEST_PREVIEW_CODE); |
||||
} |
||||
|
||||
@Override |
||||
public void onPhotoTaken(byte[] bytes, String filePath) { |
||||
Intent intent = PreviewActivity.newIntentPhoto(MainActivity.this, filePath); |
||||
startActivityForResult(intent, REQUEST_PREVIEW_CODE); |
||||
} |
||||
}); |
||||
|
||||
cameraFragment.setStateListener(new CameraFragmentStateListener() { |
||||
|
||||
@Override |
||||
public void onCurrentCameraBack() { |
||||
cameraSwitchView.displayBackCamera(); |
||||
} |
||||
|
||||
@Override |
||||
public void onCurrentCameraFront() { |
||||
cameraSwitchView.displayFrontCamera(); |
||||
} |
||||
|
||||
@Override |
||||
public void onFlashAuto() { |
||||
flashSwitchView.displayFlashAuto(); |
||||
} |
||||
|
||||
@Override |
||||
public void onFlashOn() { |
||||
flashSwitchView.displayFlashOn(); |
||||
} |
||||
|
||||
@Override |
||||
public void onFlashOff() { |
||||
flashSwitchView.displayFlashOff(); |
||||
} |
||||
|
||||
@Override |
||||
public void onCameraSetupForPhoto() { |
||||
mediaActionSwitchView.displayActionWillSwitchVideo(); |
||||
|
||||
recordButton.displayPhotoState(); |
||||
flashSwitchView.setVisibility(View.VISIBLE); |
||||
} |
||||
|
||||
@Override |
||||
public void onCameraSetupForVideo() { |
||||
mediaActionSwitchView.displayActionWillSwitchPhoto(); |
||||
|
||||
recordButton.displayVideoRecordStateReady(); |
||||
flashSwitchView.setVisibility(View.GONE); |
||||
} |
||||
|
||||
@Override |
||||
public void shouldRotateControls(int degrees) { |
||||
ViewCompat.setRotation(cameraSwitchView, degrees); |
||||
ViewCompat.setRotation(mediaActionSwitchView, degrees); |
||||
ViewCompat.setRotation(flashSwitchView, degrees); |
||||
ViewCompat.setRotation(recordDurationText, degrees); |
||||
ViewCompat.setRotation(recordSizeText, degrees); |
||||
} |
||||
|
||||
@Override |
||||
public void onRecordStateVideoReadyForRecord() { |
||||
recordButton.displayVideoRecordStateReady(); |
||||
} |
||||
|
||||
@Override |
||||
public void onRecordStateVideoInProgress() { |
||||
recordButton.displayVideoRecordStateInProgress(); |
||||
} |
||||
|
||||
@Override |
||||
public void onRecordStatePhoto() { |
||||
recordButton.displayPhotoState(); |
||||
} |
||||
|
||||
@Override |
||||
public void onStopVideoRecord() { |
||||
recordSizeText.setVisibility(View.GONE); |
||||
//cameraSwitchView.setVisibility(View.VISIBLE);
|
||||
settingsView.setVisibility(View.VISIBLE); |
||||
} |
||||
|
||||
@Override |
||||
public void onStartVideoRecord(File outputFile) { |
||||
} |
||||
}); |
||||
|
||||
cameraFragment.setControlsListener(new CameraFragmentControlsListener() { |
||||
@Override |
||||
public void lockControls() { |
||||
cameraSwitchView.setEnabled(false); |
||||
recordButton.setEnabled(false); |
||||
settingsView.setEnabled(false); |
||||
flashSwitchView.setEnabled(false); |
||||
} |
||||
|
||||
@Override |
||||
public void unLockControls() { |
||||
cameraSwitchView.setEnabled(true); |
||||
recordButton.setEnabled(true); |
||||
settingsView.setEnabled(true); |
||||
flashSwitchView.setEnabled(true); |
||||
} |
||||
|
||||
@Override |
||||
public void allowCameraSwitching(boolean allow) { |
||||
//cameraSwitchView.setVisibility(allow ? View.VISIBLE : View.GONE);
|
||||
} |
||||
|
||||
@Override |
||||
public void allowRecord(boolean allow) { |
||||
recordButton.setEnabled(allow); |
||||
} |
||||
|
||||
@Override |
||||
public void setMediaActionSwitchVisible(boolean visible) { |
||||
//mediaActionSwitchView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
|
||||
} |
||||
}); |
||||
|
||||
cameraFragment.setTextListener(new CameraFragmentVideoRecordTextListener() { |
||||
@Override |
||||
public void setRecordSizeText(long size, String text) { |
||||
recordSizeText.setText(text); |
||||
} |
||||
|
||||
@Override |
||||
public void setRecordSizeTextVisible(boolean visible) { |
||||
recordSizeText.setVisibility(visible ? View.VISIBLE : View.GONE); |
||||
} |
||||
|
||||
@Override |
||||
public void setRecordDurationText(String text) { |
||||
recordDurationText.setText(text); |
||||
} |
||||
|
||||
@Override |
||||
public void setRecordDurationTextVisible(boolean visible) { |
||||
recordDurationText.setVisibility(visible ? View.VISIBLE : View.GONE); |
||||
} |
||||
}); |
||||
|
||||
|
||||
} |
||||
|
||||
cameraSwitchView.displayBackCamera(); |
||||
flashSwitchView.displayFlashAuto(); |
||||
recordButton.displayPhotoState(); |
||||
mediaActionSwitchView.displayActionWillSwitchVideo(); |
||||
} |
||||
|
||||
private CameraFragment getCameraFragment() { |
||||
return (CameraFragment) getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG); |
||||
} |
||||
} |
@ -0,0 +1,122 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:tools="http://schemas.android.com/tools" |
||||
android:orientation="vertical" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent"> |
||||
|
||||
<FrameLayout |
||||
android:id="@+id/content" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent"/> |
||||
|
||||
<Button |
||||
android:id="@+id/addCameraButton" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:layout_margin="15dp" |
||||
android:text="Add camera" /> |
||||
|
||||
<RelativeLayout |
||||
android:id="@+id/cameraLayout" |
||||
android:visibility="gone" |
||||
tools:visibility="visible" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
> |
||||
|
||||
<RelativeLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="60dp" |
||||
android:layout_alignParentEnd="true" |
||||
android:layout_alignParentRight="true" |
||||
android:layout_alignParentTop="true" |
||||
android:paddingTop="10dp"> |
||||
|
||||
<com.github.florent37.camerafragment.widgets.CameraSettingsView |
||||
android:id="@+id/settings_view" |
||||
android:layout_width="40dp" |
||||
android:layout_height="40dp" |
||||
android:layout_alignParentLeft="true" |
||||
android:layout_centerVertical="true" |
||||
android:layout_marginLeft="15dp" |
||||
android:background="@drawable/circle_frame_background_dark" |
||||
android:scaleType="center" |
||||
android:src="@drawable/ic_settings_white_24dp" /> |
||||
|
||||
<com.github.florent37.camerafragment.widgets.FlashSwitchView |
||||
android:id="@+id/flash_switch_view" |
||||
android:layout_width="40dp" |
||||
android:layout_height="40dp" |
||||
android:layout_centerInParent="true" /> |
||||
|
||||
<com.github.florent37.camerafragment.widgets.CameraSwitchView |
||||
android:id="@+id/front_back_camera_switcher" |
||||
android:layout_width="40dp" |
||||
android:layout_height="40dp" |
||||
android:layout_alignParentRight="true" |
||||
android:layout_centerVertical="true" |
||||
android:layout_marginRight="15dp" |
||||
/> |
||||
|
||||
</RelativeLayout> |
||||
|
||||
<!--android:background="#82000000"--> |
||||
<RelativeLayout |
||||
android:id="@+id/record_panel" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="150dp" |
||||
android:layout_alignParentBottom="true" |
||||
android:background="@android:color/transparent"> |
||||
|
||||
<com.github.florent37.camerafragment.widgets.RecordButton |
||||
android:id="@+id/record_button" |
||||
android:layout_width="75dp" |
||||
android:layout_height="75dp" |
||||
android:visibility="gone" |
||||
android:layout_centerInParent="true" |
||||
android:layout_marginLeft="50dp" |
||||
android:layout_marginRight="50dp" /> |
||||
|
||||
<Button |
||||
android:id="@+id/take_photo" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_centerInParent="true" |
||||
android:text="take photo"/> |
||||
|
||||
<com.github.florent37.camerafragment.widgets.MediaActionSwitchView |
||||
android:id="@+id/photo_video_camera_switcher" |
||||
android:layout_width="40dp" |
||||
android:layout_height="40dp" |
||||
android:layout_alignParentRight="true" |
||||
android:layout_centerVertical="true" |
||||
android:layout_marginRight="40dp" |
||||
android:checked="false" |
||||
android:gravity="center" /> |
||||
|
||||
<TextView |
||||
android:id="@+id/record_duration_text" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_centerVertical="true" |
||||
android:layout_toLeftOf="@id/record_button" |
||||
android:textColor="@android:color/white" |
||||
android:textSize="14dp" |
||||
android:visibility="invisible" /> |
||||
|
||||
<TextView |
||||
android:id="@+id/record_size_mb_text" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_centerVertical="true" |
||||
android:layout_toRightOf="@id/record_button" |
||||
android:textColor="@android:color/white" |
||||
android:textSize="14dp" |
||||
android:visibility="invisible" /> |
||||
|
||||
</RelativeLayout> |
||||
|
||||
</RelativeLayout> |
||||
|
||||
</FrameLayout> |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 10 KiB |
@ -0,0 +1,6 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
<color name="colorPrimary">#3F51B5</color> |
||||
<color name="colorPrimaryDark">#303F9F</color> |
||||
<color name="colorAccent">#FF4081</color> |
||||
</resources> |
@ -0,0 +1,3 @@ |
||||
<resources> |
||||
<string name="app_name">CameraFragment</string> |
||||
</resources> |
@ -0,0 +1,15 @@ |
||||
<resources> |
||||
|
||||
<!-- Base application theme. --> |
||||
<style name="AppTheme" parent="Theme.AppCompat.NoActionBar"> |
||||
<!-- Customize your theme here. --> |
||||
<item name="colorPrimary">@color/colorPrimary</item> |
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> |
||||
<item name="colorAccent">@color/colorAccent</item> |
||||
</style> |
||||
|
||||
<style name="ThemeFullscreen" parent="AppTheme"> |
||||
<item name="android:windowFullscreen">true</item> |
||||
</style> |
||||
|
||||
</resources> |
After Width: | Height: | Size: 4.1 MiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 3.9 MiB |
After Width: | Height: | Size: 2.7 MiB |
@ -0,0 +1,22 @@ |
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules. |
||||
|
||||
buildscript { |
||||
repositories { |
||||
jcenter() |
||||
} |
||||
dependencies { |
||||
classpath 'com.android.tools.build:gradle:2.2.3' |
||||
// NOTE: Do not place your application dependencies here; they belong |
||||
// in the individual module build.gradle files |
||||
} |
||||
} |
||||
|
||||
allprojects { |
||||
repositories { |
||||
jcenter() |
||||
} |
||||
} |
||||
|
||||
task clean(type: Delete) { |
||||
delete rootProject.buildDir |
||||
} |
@ -0,0 +1 @@ |
||||
/build |
@ -0,0 +1,32 @@ |
||||
apply plugin: 'com.android.library' |
||||
|
||||
|
||||
ext { |
||||
PUBLISH_VERSION = '0.3.2' |
||||
PUBLISH_VERSION_CODE = 6 |
||||
SUPPORT_VERSION = '25.1.0' |
||||
TARGET_SDK = 24 |
||||
MIN_SDK = 10 |
||||
BUILD_TOOLS = "24.0.3" |
||||
} |
||||
|
||||
android { |
||||
compileSdkVersion TARGET_SDK |
||||
buildToolsVersion BUILD_TOOLS |
||||
|
||||
defaultConfig { |
||||
minSdkVersion MIN_SDK |
||||
targetSdkVersion TARGET_SDK |
||||
versionCode PUBLISH_VERSION_CODE |
||||
versionName PUBLISH_VERSION |
||||
} |
||||
} |
||||
|
||||
dependencies { |
||||
compile "com.android.support:support-v4:$SUPPORT_VERSION" |
||||
compile "com.android.support:appcompat-v7:$SUPPORT_VERSION" |
||||
} |
||||
|
||||
task wrapper(type: Wrapper) { |
||||
gradleVersion = '2.4' |
||||
} |
@ -0,0 +1,17 @@ |
||||
# Add project specific ProGuard rules here. |
||||
# By default, the flags in this file are appended to flags specified |
||||
# in /Users/memfis/Library/Android/sdk/tools/proguard/proguard-android.txt |
||||
# You can edit the include path and order by changing the proguardFiles |
||||
# directive in build.gradle. |
||||
# |
||||
# For more details, see |
||||
# http://developer.android.com/guide/developing/tools/proguard.html |
||||
|
||||
# Add any project specific keep options here: |
||||
|
||||
# If your project uses WebView with JS, uncomment the following |
||||
# and specify the fully qualified class name to the JavaScript interface |
||||
# class: |
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { |
||||
# public *; |
||||
#} |
@ -0,0 +1,5 @@ |
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||
package="com.github.florent37.camerafragment"> |
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" /> |
||||
</manifest> |
@ -0,0 +1,15 @@ |
||||
package com.github.florent37.camerafragment; |
||||
|
||||
import android.Manifest; |
||||
import android.support.annotation.RequiresPermission; |
||||
|
||||
import com.github.florent37.camerafragment.configuration.Configuration; |
||||
import com.github.florent37.camerafragment.internal.ui.BaseAnncaFragment; |
||||
|
||||
public class CameraFragment extends BaseAnncaFragment { |
||||
|
||||
@RequiresPermission(Manifest.permission.CAMERA) |
||||
public static CameraFragment newInstance(Configuration configuration) { |
||||
return (CameraFragment) BaseAnncaFragment.newInstance(new CameraFragment(), configuration); |
||||
} |
||||
} |
@ -0,0 +1,386 @@ |
||||
package com.github.florent37.camerafragment; |
||||
|
||||
import android.content.Context; |
||||
import android.content.Intent; |
||||
import android.media.AudioManager; |
||||
import android.media.MediaPlayer; |
||||
import android.os.Bundle; |
||||
import android.support.annotation.NonNull; |
||||
import android.support.annotation.Nullable; |
||||
import android.support.v7.app.AppCompatActivity; |
||||
import android.util.Log; |
||||
import android.view.MotionEvent; |
||||
import android.view.SurfaceHolder; |
||||
import android.view.SurfaceView; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
import android.widget.FrameLayout; |
||||
import android.widget.ImageView; |
||||
import android.widget.MediaController; |
||||
import android.widget.TextView; |
||||
|
||||
import java.io.File; |
||||
|
||||
import com.github.florent37.camerafragment.configuration.Configuration; |
||||
import com.github.florent37.camerafragment.internal.enums.MediaAction; |
||||
import com.github.florent37.camerafragment.internal.ui.view.AspectFrameLayout; |
||||
import com.github.florent37.camerafragment.internal.utils.ImageLoader; |
||||
import com.github.florent37.camerafragment.internal.utils.Utils; |
||||
|
||||
/** |
||||
* Created by memfis on 7/6/16. |
||||
*/ |
||||
public class PreviewActivity extends AppCompatActivity implements View.OnClickListener { |
||||
|
||||
private static final String TAG = "PreviewActivity"; |
||||
|
||||
public static final int ACTION_CONFIRM = 900; |
||||
public static final int ACTION_RETAKE = 901; |
||||
public static final int ACTION_CANCEL = 902; |
||||
|
||||
private final static String MEDIA_ACTION_ARG = "media_action_arg"; |
||||
private final static String FILE_PATH_ARG = "file_path_arg"; |
||||
private final static String RESPONSE_CODE_ARG = "response_code_arg"; |
||||
private final static String VIDEO_POSITION_ARG = "current_video_position"; |
||||
private final static String VIDEO_IS_PLAYED_ARG = "is_played"; |
||||
private final static String MIME_TYPE_VIDEO = "video"; |
||||
private final static String MIME_TYPE_IMAGE = "image"; |
||||
|
||||
private int mediaAction; |
||||
private String previewFilePath; |
||||
|
||||
private SurfaceView surfaceView; |
||||
private FrameLayout photoPreviewContainer; |
||||
private ImageView imagePreview; |
||||
private ViewGroup buttonPanel; |
||||
private AspectFrameLayout videoPreviewContainer; |
||||
private View cropMediaAction; |
||||
private TextView ratioChanger; |
||||
|
||||
private MediaController mediaController; |
||||
private MediaPlayer mediaPlayer; |
||||
|
||||
private int currentPlaybackPosition = 0; |
||||
private boolean isVideoPlaying = true; |
||||
|
||||
private int currentRatioIndex = 0; |
||||
private float[] ratios; |
||||
private String[] ratioLabels; |
||||
|
||||
private SurfaceHolder.Callback surfaceCallbacks = new SurfaceHolder.Callback() { |
||||
@Override |
||||
public void surfaceCreated(SurfaceHolder holder) { |
||||
showVideoPreview(holder); |
||||
} |
||||
|
||||
@Override |
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void surfaceDestroyed(SurfaceHolder holder) { |
||||
|
||||
} |
||||
}; |
||||
|
||||
private MediaController.MediaPlayerControl MediaPlayerControlImpl = new MediaController.MediaPlayerControl() { |
||||
@Override |
||||
public void start() { |
||||
mediaPlayer.start(); |
||||
} |
||||
|
||||
@Override |
||||
public void pause() { |
||||
mediaPlayer.pause(); |
||||
} |
||||
|
||||
@Override |
||||
public int getDuration() { |
||||
return mediaPlayer.getDuration(); |
||||
} |
||||
|
||||
@Override |
||||
public int getCurrentPosition() { |
||||
return mediaPlayer.getCurrentPosition(); |
||||
} |
||||
|
||||
@Override |
||||
public void seekTo(int pos) { |
||||
mediaPlayer.seekTo(pos); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isPlaying() { |
||||
return mediaPlayer.isPlaying(); |
||||
} |
||||
|
||||
@Override |
||||
public int getBufferPercentage() { |
||||
return 0; |
||||
} |
||||
|
||||
@Override |
||||
public boolean canPause() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean canSeekBackward() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean canSeekForward() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public int getAudioSessionId() { |
||||
return mediaPlayer.getAudioSessionId(); |
||||
} |
||||
}; |
||||
|
||||
public static Intent newIntentPhoto(Context context, |
||||
String filePath) { |
||||
|
||||
return new Intent(context, PreviewActivity.class) |
||||
.putExtra(MEDIA_ACTION_ARG, MediaAction.ACTION_PHOTO) |
||||
.putExtra(FILE_PATH_ARG, filePath); |
||||
} |
||||
|
||||
public static Intent newIntentVideo(Context context, |
||||
String filePath) { |
||||
|
||||
return new Intent(context, PreviewActivity.class) |
||||
.putExtra(MEDIA_ACTION_ARG, MediaAction.ACTION_VIDEO) |
||||
.putExtra(FILE_PATH_ARG, filePath); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
protected void onCreate(@Nullable Bundle savedInstanceState) { |
||||
super.onCreate(savedInstanceState); |
||||
setContentView(R.layout.activity_preview); |
||||
|
||||
String originalRatioLabel = getString(R.string.preview_controls_original_ratio_label); |
||||
ratioLabels = new String[]{originalRatioLabel, "1:1", "4:3", "16:9"}; |
||||
ratios = new float[]{0f, 1f, 4f / 3f, 16f / 9f}; |
||||
|
||||
surfaceView = (SurfaceView) findViewById(R.id.video_preview); |
||||
surfaceView.setOnTouchListener(new View.OnTouchListener() { |
||||
@Override |
||||
public boolean onTouch(View v, MotionEvent event) { |
||||
if (mediaController == null) return false; |
||||
if (mediaController.isShowing()) { |
||||
mediaController.hide(); |
||||
showButtonPanel(true); |
||||
} else { |
||||
showButtonPanel(false); |
||||
mediaController.show(); |
||||
} |
||||
return false; |
||||
} |
||||
}); |
||||
|
||||
videoPreviewContainer = (AspectFrameLayout) findViewById(R.id.previewAspectFrameLayout); |
||||
photoPreviewContainer = (FrameLayout) findViewById(R.id.photo_preview_container); |
||||
buttonPanel = (ViewGroup) findViewById(R.id.preview_control_panel); |
||||
View confirmMediaResult = findViewById(R.id.confirm_media_result); |
||||
View reTakeMedia = findViewById(R.id.re_take_media); |
||||
View cancelMediaAction = findViewById(R.id.cancel_media_action); |
||||
cropMediaAction = findViewById(R.id.crop_image); |
||||
ratioChanger = (TextView) findViewById(R.id.ratio_image); |
||||
ratioChanger.setOnClickListener(new View.OnClickListener() { |
||||
@Override |
||||
public void onClick(View v) { |
||||
currentRatioIndex = (currentRatioIndex + 1) % ratios.length; |
||||
ratioChanger.setText(ratioLabels[currentRatioIndex]); |
||||
} |
||||
}); |
||||
|
||||
cropMediaAction.setVisibility(View.GONE); |
||||
ratioChanger.setVisibility(View.GONE); |
||||
|
||||
if (cropMediaAction != null) |
||||
cropMediaAction.setOnClickListener(new View.OnClickListener() { |
||||
@Override |
||||
public void onClick(View view) { |
||||
} |
||||
}); |
||||
|
||||
if (confirmMediaResult != null) |
||||
confirmMediaResult.setOnClickListener(this); |
||||
|
||||
if (reTakeMedia != null) |
||||
reTakeMedia.setOnClickListener(this); |
||||
|
||||
if (cancelMediaAction != null) |
||||
cancelMediaAction.setOnClickListener(this); |
||||
|
||||
Bundle args = getIntent().getExtras(); |
||||
|
||||
mediaAction = args.getInt(MEDIA_ACTION_ARG); |
||||
previewFilePath = args.getString(FILE_PATH_ARG); |
||||
|
||||
if (mediaAction == Configuration.MEDIA_ACTION_VIDEO) { |
||||
displayVideo(savedInstanceState); |
||||
} else if (mediaAction == Configuration.MEDIA_ACTION_PHOTO) { |
||||
displayImage(); |
||||
} else { |
||||
String mimeType = Utils.getMimeType(previewFilePath); |
||||
if (mimeType.contains(MIME_TYPE_VIDEO)) { |
||||
displayVideo(savedInstanceState); |
||||
} else if (mimeType.contains(MIME_TYPE_IMAGE)) { |
||||
displayImage(); |
||||
} else finish(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void onSaveInstanceState(Bundle outState) { |
||||
super.onSaveInstanceState(outState); |
||||
saveVideoParams(outState); |
||||
} |
||||
|
||||
@Override |
||||
protected void onDestroy() { |
||||
super.onDestroy(); |
||||
if (mediaPlayer != null) { |
||||
mediaPlayer.release(); |
||||
mediaPlayer = null; |
||||
} |
||||
if (mediaController != null) { |
||||
mediaController.hide(); |
||||
mediaController = null; |
||||
} |
||||
} |
||||
|
||||
private void displayImage() { |
||||
videoPreviewContainer.setVisibility(View.GONE); |
||||
surfaceView.setVisibility(View.GONE); |
||||
showImagePreview(); |
||||
ratioChanger.setText(ratioLabels[currentRatioIndex]); |
||||
} |
||||
|
||||
private void showImagePreview() { |
||||
imagePreview = new ImageView(this); |
||||
ImageLoader.Builder builder = new ImageLoader.Builder(this); |
||||
builder.load(previewFilePath).build().into(imagePreview); |
||||
photoPreviewContainer.removeAllViews(); |
||||
photoPreviewContainer.addView(imagePreview); |
||||
} |
||||
|
||||
private void displayVideo(Bundle savedInstanceState) { |
||||
cropMediaAction.setVisibility(View.GONE); |
||||
ratioChanger.setVisibility(View.GONE); |
||||
if (savedInstanceState != null) { |
||||
loadVideoParams(savedInstanceState); |
||||
} |
||||
photoPreviewContainer.setVisibility(View.GONE); |
||||
surfaceView.getHolder().addCallback(surfaceCallbacks); |
||||
} |
||||
|
||||
private void showVideoPreview(SurfaceHolder holder) { |
||||
try { |
||||
mediaPlayer = new MediaPlayer(); |
||||
mediaPlayer.setDataSource(previewFilePath); |
||||
mediaPlayer.setDisplay(holder); |
||||
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); |
||||
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { |
||||
@Override |
||||
public void onPrepared(MediaPlayer mp) { |
||||
mediaController = new MediaController(PreviewActivity.this); |
||||
mediaController.setAnchorView(surfaceView); |
||||
mediaController.setMediaPlayer(MediaPlayerControlImpl); |
||||
|
||||
int videoWidth = mp.getVideoWidth(); |
||||
int videoHeight = mp.getVideoHeight(); |
||||
|
||||
videoPreviewContainer.setAspectRatio((double) videoWidth / videoHeight); |
||||
|
||||
mediaPlayer.start(); |
||||
mediaPlayer.seekTo(currentPlaybackPosition); |
||||
|
||||
if (!isVideoPlaying) |
||||
mediaPlayer.pause(); |
||||
} |
||||
}); |
||||
mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { |
||||
@Override |
||||
public boolean onError(MediaPlayer mp, int what, int extra) { |
||||
finish(); |
||||
return true; |
||||
} |
||||
}); |
||||
mediaPlayer.prepareAsync(); |
||||
} catch (Exception e) { |
||||
Log.e(TAG, "Error media player playing video."); |
||||
finish(); |
||||
} |
||||
} |
||||
|
||||
private void saveVideoParams(Bundle outState) { |
||||
if (mediaPlayer != null) { |
||||
outState.putInt(VIDEO_POSITION_ARG, mediaPlayer.getCurrentPosition()); |
||||
outState.putBoolean(VIDEO_IS_PLAYED_ARG, mediaPlayer.isPlaying()); |
||||
} |
||||
} |
||||
|
||||
private void loadVideoParams(Bundle savedInstanceState) { |
||||
currentPlaybackPosition = savedInstanceState.getInt(VIDEO_POSITION_ARG, 0); |
||||
isVideoPlaying = savedInstanceState.getBoolean(VIDEO_IS_PLAYED_ARG, true); |
||||
} |
||||
|
||||
private void showButtonPanel(boolean show) { |
||||
if (show) { |
||||
buttonPanel.setVisibility(View.VISIBLE); |
||||
} else { |
||||
buttonPanel.setVisibility(View.GONE); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onClick(View view) { |
||||
Intent resultIntent = new Intent(); |
||||
if (view.getId() == R.id.confirm_media_result) { |
||||
resultIntent.putExtra(RESPONSE_CODE_ARG, ACTION_CONFIRM).putExtra(FILE_PATH_ARG, previewFilePath); |
||||
} else if (view.getId() == R.id.re_take_media) { |
||||
deleteMediaFile(); |
||||
resultIntent.putExtra(RESPONSE_CODE_ARG, ACTION_RETAKE); |
||||
} else if (view.getId() == R.id.cancel_media_action) { |
||||
deleteMediaFile(); |
||||
resultIntent.putExtra(RESPONSE_CODE_ARG, ACTION_CANCEL); |
||||
} |
||||
setResult(RESULT_OK, resultIntent); |
||||
finish(); |
||||
} |
||||
|
||||
@Override |
||||
public void onBackPressed() { |
||||
super.onBackPressed(); |
||||
deleteMediaFile(); |
||||
} |
||||
|
||||
private boolean deleteMediaFile() { |
||||
File mediaFile = new File(previewFilePath); |
||||
return mediaFile.delete(); |
||||
} |
||||
|
||||
public static String getMediaFilePatch(@NonNull Intent resultIntent) { |
||||
return resultIntent.getStringExtra(FILE_PATH_ARG); |
||||
} |
||||
|
||||
public static boolean isResultConfirm(@NonNull Intent resultIntent) { |
||||
return ACTION_CONFIRM == resultIntent.getIntExtra(RESPONSE_CODE_ARG, -1); |
||||
} |
||||
|
||||
public static boolean isResultRetake(@NonNull Intent resultIntent) { |
||||
return ACTION_RETAKE == resultIntent.getIntExtra(RESPONSE_CODE_ARG, -1); |
||||
} |
||||
|
||||
public static boolean isResultCancel(@NonNull Intent resultIntent) { |
||||
return ACTION_CANCEL == resultIntent.getIntExtra(RESPONSE_CODE_ARG, -1); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,200 @@ |
||||
package com.github.florent37.camerafragment.configuration; |
||||
|
||||
import android.support.annotation.IntDef; |
||||
import android.support.annotation.IntRange; |
||||
|
||||
import java.io.Serializable; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
|
||||
/** |
||||
* Created by memfis on 7/6/16. |
||||
* Updated by Florent37 |
||||
*/ |
||||
public final class Configuration implements Serializable { |
||||
|
||||
public static final int MEDIA_QUALITY_AUTO = 10; |
||||
public static final int MEDIA_QUALITY_LOWEST = 15; |
||||
public static final int MEDIA_QUALITY_LOW = 11; |
||||
public static final int MEDIA_QUALITY_MEDIUM = 12; |
||||
public static final int MEDIA_QUALITY_HIGH = 13; |
||||
public static final int MEDIA_QUALITY_HIGHEST = 14; |
||||
|
||||
public static final int MEDIA_ACTION_VIDEO = 100; |
||||
public static final int MEDIA_ACTION_PHOTO = 101; |
||||
public static final int MEDIA_ACTION_UNSPECIFIED = 102; |
||||
|
||||
public static final int CAMERA_FACE_FRONT = 0x6; |
||||
public static final int CAMERA_FACE_REAR = 0x7; |
||||
|
||||
public static final int SENSOR_POSITION_UP = 90; |
||||
public static final int SENSOR_POSITION_UP_SIDE_DOWN = 270; |
||||
public static final int SENSOR_POSITION_LEFT = 0; |
||||
public static final int SENSOR_POSITION_RIGHT = 180; |
||||
public static final int SENSOR_POSITION_UNSPECIFIED = -1; |
||||
|
||||
public static final int DISPLAY_ROTATION_0 = 0; |
||||
public static final int DISPLAY_ROTATION_90 = 90; |
||||
public static final int DISPLAY_ROTATION_180 = 180; |
||||
public static final int DISPLAY_ROTATION_270 = 270; |
||||
|
||||
public static final int ORIENTATION_PORTRAIT = 0x111; |
||||
public static final int ORIENTATION_LANDSCAPE = 0x222; |
||||
|
||||
public static final int FLASH_MODE_ON = 1; |
||||
public static final int FLASH_MODE_OFF = 2; |
||||
public static final int FLASH_MODE_AUTO = 3; |
||||
|
||||
public interface Arguments { |
||||
String REQUEST_CODE = "io.memfis19.annca.request_code"; |
||||
String MEDIA_ACTION = "io.memfis19.annca.media_action"; |
||||
String MEDIA_QUALITY = "io.memfis19.annca.camera_media_quality"; |
||||
String VIDEO_DURATION = "io.memfis19.annca.video_duration"; |
||||
String MINIMUM_VIDEO_DURATION = "io.memfis19.annca.minimum.video_duration"; |
||||
String VIDEO_FILE_SIZE = "io.memfis19.annca.camera_video_file_size"; |
||||
String FLASH_MODE = "io.memfis19.annca.camera_flash_mode"; |
||||
String FILE_PATH = "io.memfis19.annca.camera_video_file_path"; |
||||
} |
||||
|
||||
@IntDef({MEDIA_QUALITY_AUTO, MEDIA_QUALITY_LOWEST, MEDIA_QUALITY_LOW, MEDIA_QUALITY_MEDIUM, MEDIA_QUALITY_HIGH, MEDIA_QUALITY_HIGHEST}) |
||||
@Retention(RetentionPolicy.SOURCE) |
||||
public @interface MediaQuality { |
||||
} |
||||
|
||||
@IntDef({MEDIA_ACTION_VIDEO, MEDIA_ACTION_PHOTO, MEDIA_ACTION_UNSPECIFIED}) |
||||
@Retention(RetentionPolicy.SOURCE) |
||||
public @interface MediaAction { |
||||
} |
||||
|
||||
@IntDef({FLASH_MODE_ON, FLASH_MODE_OFF, FLASH_MODE_AUTO}) |
||||
@Retention(RetentionPolicy.SOURCE) |
||||
public @interface FlashMode { |
||||
} |
||||
|
||||
@IntDef({CAMERA_FACE_FRONT, CAMERA_FACE_REAR}) |
||||
@Retention(RetentionPolicy.SOURCE) |
||||
public @interface CameraFace { |
||||
} |
||||
|
||||
@IntDef({SENSOR_POSITION_UP, SENSOR_POSITION_UP_SIDE_DOWN, SENSOR_POSITION_LEFT, SENSOR_POSITION_RIGHT, SENSOR_POSITION_UNSPECIFIED}) |
||||
@Retention(RetentionPolicy.SOURCE) |
||||
public @interface SensorPosition { |
||||
} |
||||
|
||||
@IntDef({DISPLAY_ROTATION_0, DISPLAY_ROTATION_90, DISPLAY_ROTATION_180, DISPLAY_ROTATION_270}) |
||||
@Retention(RetentionPolicy.SOURCE) |
||||
public @interface DisplayRotation { |
||||
} |
||||
|
||||
@IntDef({ORIENTATION_PORTRAIT, ORIENTATION_LANDSCAPE}) |
||||
@Retention(RetentionPolicy.SOURCE) |
||||
public @interface DeviceDefaultOrientation { |
||||
} |
||||
|
||||
@MediaAction |
||||
private int mediaAction = -1; |
||||
|
||||
@MediaQuality |
||||
private int mediaQuality = -1; |
||||
|
||||
@CameraFace |
||||
private int cameraFace = -1; |
||||
|
||||
private int videoDuration = -1; |
||||
|
||||
private long videoFileSize = -1; |
||||
|
||||
private int minimumVideoDuration = -1; |
||||
|
||||
@FlashMode |
||||
private int flashMode = FLASH_MODE_AUTO; |
||||
|
||||
public static class Builder { |
||||
|
||||
private Configuration configuration; |
||||
|
||||
public Builder() { |
||||
configuration = new Configuration(); |
||||
} |
||||
|
||||
public Builder setMediaAction(@MediaAction int mediaAction) { |
||||
configuration.mediaAction = mediaAction; |
||||
return this; |
||||
} |
||||
|
||||
public Builder setMediaQuality(@MediaQuality int mediaQuality) { |
||||
configuration.mediaQuality = mediaQuality; |
||||
return this; |
||||
} |
||||
|
||||
/*** |
||||
* @param videoDurationInMilliseconds - video duration in milliseconds |
||||
* @return |
||||
*/ |
||||
public Builder setVideoDuration(@IntRange(from = 1000, to = Integer.MAX_VALUE) int videoDurationInMilliseconds) { |
||||
configuration.videoDuration = videoDurationInMilliseconds; |
||||
return this; |
||||
} |
||||
|
||||
/*** |
||||
* @param minimumVideoDurationInMilliseconds - minimum video duration in milliseconds, used only in video mode |
||||
* for auto quality. |
||||
* @return |
||||
*/ |
||||
public Builder setMinimumVideoDuration(@IntRange(from = 1000, to = Integer.MAX_VALUE) int minimumVideoDurationInMilliseconds) { |
||||
configuration.minimumVideoDuration = minimumVideoDurationInMilliseconds; |
||||
return this; |
||||
} |
||||
|
||||
/*** |
||||
* @param videoSizeInBytes - file size in bytes |
||||
* @return |
||||
*/ |
||||
public Builder setVideoFileSize(@IntRange(from = 1048576, to = Long.MAX_VALUE) long videoSizeInBytes) { |
||||
configuration.videoFileSize = videoSizeInBytes; |
||||
return this; |
||||
} |
||||
|
||||
public Builder setFlashMode(@FlashMode int flashMode) { |
||||
configuration.flashMode = flashMode; |
||||
return this; |
||||
} |
||||
|
||||
public Configuration build() throws IllegalArgumentException { |
||||
if (configuration.mediaQuality == MEDIA_QUALITY_AUTO && configuration.minimumVideoDuration < 0) { |
||||
throw new IllegalStateException("Please provide minimum video duration in milliseconds to use auto quality."); |
||||
} |
||||
|
||||
return configuration; |
||||
} |
||||
|
||||
} |
||||
|
||||
public int getMediaAction() { |
||||
return mediaAction; |
||||
} |
||||
|
||||
public int getMediaQuality() { |
||||
return mediaQuality; |
||||
} |
||||
|
||||
public int getCameraFace() { |
||||
return cameraFace; |
||||
} |
||||
|
||||
public int getVideoDuration() { |
||||
return videoDuration; |
||||
} |
||||
|
||||
public long getVideoFileSize() { |
||||
return videoFileSize; |
||||
} |
||||
|
||||
public int getMinimumVideoDuration() { |
||||
return minimumVideoDuration; |
||||
} |
||||
|
||||
public int getFlashMode() { |
||||
return flashMode; |
||||
} |
||||
} |
@ -0,0 +1,53 @@ |
||||
package com.github.florent37.camerafragment.configuration; |
||||
|
||||
/** |
||||
* Created by memfis on 7/6/16. |
||||
*/ |
||||
public interface ConfigurationProvider { |
||||
|
||||
@Configuration.MediaAction |
||||
int getMediaAction(); |
||||
|
||||
@Configuration.MediaQuality |
||||
int getMediaQuality(); |
||||
|
||||
int getVideoDuration(); |
||||
|
||||
long getVideoFileSize(); |
||||
|
||||
@Configuration.SensorPosition |
||||
int getSensorPosition(); |
||||
|
||||
int getDegrees(); |
||||
|
||||
int getMinimumVideoDuration(); |
||||
|
||||
@Configuration.FlashMode |
||||
int getFlashMode(); |
||||
|
||||
void setMediaQuality(int mediaQuality); |
||||
|
||||
void setPassedMediaQuality(int mediaQuality); |
||||
|
||||
void setVideoDuration(int videoDuration); |
||||
|
||||
void setVideoFileSize(long videoFileSize); |
||||
|
||||
void setMinimumVideoDuration(int minimumVideoDuration); |
||||
|
||||
void setFlashMode(int flashMode); |
||||
|
||||
void setSensorPosition(int sensorPosition); |
||||
|
||||
int getDeviceDefaultOrientation(); |
||||
|
||||
void setDegrees(int degrees); |
||||
|
||||
void setMediaAction(int mediaAction); |
||||
|
||||
void setDeviceDefaultOrientation(int deviceDefaultOrientation); |
||||
|
||||
int getPassedMediaQuality(); |
||||
|
||||
void setupWithAnnaConfiguration(Configuration configuration); |
||||
} |
@ -0,0 +1,207 @@ |
||||
package com.github.florent37.camerafragment.configuration; |
||||
|
||||
/** |
||||
* Created by florentchampigny on 12/01/2017. |
||||
*/ |
||||
|
||||
public class ConfigurationProviderImpl implements ConfigurationProvider { |
||||
|
||||
protected long videoFileSize = -1; |
||||
protected int videoDuration = -1; |
||||
protected int minimumVideoDuration = -1; |
||||
|
||||
@Configuration.MediaAction |
||||
protected int mediaAction = Configuration.MEDIA_ACTION_UNSPECIFIED; |
||||
|
||||
@Configuration.MediaQuality |
||||
protected int mediaQuality = Configuration.MEDIA_QUALITY_MEDIUM; |
||||
|
||||
@Configuration.MediaQuality |
||||
protected int passedMediaQuality = Configuration.MEDIA_QUALITY_MEDIUM; |
||||
|
||||
@Configuration.FlashMode |
||||
protected int flashMode = Configuration.FLASH_MODE_AUTO; |
||||
|
||||
@Configuration.SensorPosition |
||||
protected int sensorPosition = Configuration.SENSOR_POSITION_UNSPECIFIED; |
||||
|
||||
@Configuration.DeviceDefaultOrientation |
||||
protected int deviceDefaultOrientation; |
||||
|
||||
private int degrees = -1; |
||||
|
||||
@Override |
||||
public int getMediaAction() { |
||||
return mediaAction; |
||||
} |
||||
|
||||
@Override |
||||
public int getMediaQuality() { |
||||
return mediaQuality; |
||||
} |
||||
|
||||
@Override |
||||
public int getVideoDuration() { |
||||
return videoDuration; |
||||
} |
||||
|
||||
@Override |
||||
public long getVideoFileSize() { |
||||
return videoFileSize; |
||||
} |
||||
|
||||
@Override |
||||
public int getMinimumVideoDuration() { |
||||
return minimumVideoDuration / 1000; |
||||
} |
||||
|
||||
@Override |
||||
public final int getSensorPosition() { |
||||
return sensorPosition; |
||||
} |
||||
|
||||
@Override |
||||
public final int getDegrees() { |
||||
return degrees; |
||||
} |
||||
|
||||
@Override |
||||
public int getFlashMode() { |
||||
return flashMode; |
||||
} |
||||
|
||||
@Override |
||||
public void setMediaQuality(int mediaQuality) { |
||||
this.mediaQuality = mediaQuality; |
||||
} |
||||
|
||||
@Override |
||||
public void setPassedMediaQuality(int mediaQuality) { |
||||
this.passedMediaQuality = mediaQuality; |
||||
} |
||||
|
||||
@Override |
||||
public void setVideoDuration(int videoDuration) { |
||||
this.videoDuration = videoDuration; |
||||
} |
||||
|
||||
@Override |
||||
public void setVideoFileSize(long videoFileSize) { |
||||
this.videoFileSize = videoFileSize; |
||||
} |
||||
|
||||
@Override |
||||
public void setMinimumVideoDuration(int minimumVideoDuration) { |
||||
this.minimumVideoDuration = minimumVideoDuration; |
||||
} |
||||
|
||||
@Override |
||||
public void setFlashMode(int flashMode) { |
||||
this.flashMode = flashMode; |
||||
} |
||||
|
||||
public void setMediaAction(int mediaAction) { |
||||
this.mediaAction = mediaAction; |
||||
} |
||||
|
||||
public int getPassedMediaQuality() { |
||||
return passedMediaQuality; |
||||
} |
||||
|
||||
@Override |
||||
public void setupWithAnnaConfiguration(Configuration configuration) { |
||||
if (configuration != null) { |
||||
|
||||
final int mediaAction = configuration.getMediaAction(); |
||||
if (mediaAction != -1) { |
||||
switch (mediaAction) { |
||||
case Configuration.MEDIA_ACTION_PHOTO: |
||||
setMediaAction(Configuration.MEDIA_ACTION_PHOTO); |
||||
break; |
||||
case Configuration.MEDIA_ACTION_VIDEO: |
||||
setMediaAction(Configuration.MEDIA_ACTION_VIDEO); |
||||
break; |
||||
default: |
||||
setMediaAction(Configuration.MEDIA_ACTION_UNSPECIFIED); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
final int mediaQuality = configuration.getMediaQuality(); |
||||
if (mediaQuality != -1) { |
||||
switch (mediaQuality) { |
||||
case Configuration.MEDIA_QUALITY_AUTO: |
||||
setMediaQuality(Configuration.MEDIA_QUALITY_AUTO); |
||||
break; |
||||
case Configuration.MEDIA_QUALITY_HIGHEST: |
||||
setMediaQuality(Configuration.MEDIA_QUALITY_HIGHEST); |
||||
break; |
||||
case Configuration.MEDIA_QUALITY_HIGH: |
||||
setMediaQuality(Configuration.MEDIA_QUALITY_HIGH); |
||||
break; |
||||
case Configuration.MEDIA_QUALITY_MEDIUM: |
||||
setMediaQuality(Configuration.MEDIA_QUALITY_MEDIUM); |
||||
break; |
||||
case Configuration.MEDIA_QUALITY_LOW: |
||||
setMediaQuality(Configuration.MEDIA_QUALITY_LOW); |
||||
break; |
||||
case Configuration.MEDIA_QUALITY_LOWEST: |
||||
setMediaQuality(Configuration.MEDIA_QUALITY_LOWEST); |
||||
break; |
||||
default: |
||||
setMediaQuality(Configuration.MEDIA_QUALITY_MEDIUM); |
||||
break; |
||||
} |
||||
setPassedMediaQuality(getMediaQuality()); |
||||
} |
||||
|
||||
final int videoDuration = configuration.getVideoDuration(); |
||||
if (videoDuration != -1) { |
||||
setVideoDuration(videoDuration); |
||||
} |
||||
|
||||
final long videoFileSize = configuration.getVideoFileSize(); |
||||
if (videoFileSize != -1) { |
||||
setVideoFileSize(videoFileSize); |
||||
} |
||||
|
||||
final int minimumVideoDuration = configuration.getMinimumVideoDuration(); |
||||
if (minimumVideoDuration != -1) { |
||||
setMinimumVideoDuration(minimumVideoDuration); |
||||
} |
||||
|
||||
final int flashMode = configuration.getFlashMode(); |
||||
if (flashMode != -1) |
||||
switch (flashMode) { |
||||
case Configuration.FLASH_MODE_AUTO: |
||||
setFlashMode(Configuration.FLASH_MODE_AUTO); |
||||
break; |
||||
case Configuration.FLASH_MODE_ON: |
||||
setFlashMode(Configuration.FLASH_MODE_ON); |
||||
break; |
||||
case Configuration.FLASH_MODE_OFF: |
||||
setFlashMode(Configuration.FLASH_MODE_OFF); |
||||
break; |
||||
default: |
||||
setFlashMode(Configuration.FLASH_MODE_AUTO); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void setSensorPosition(int sensorPosition) { |
||||
this.sensorPosition = sensorPosition; |
||||
} |
||||
|
||||
public int getDeviceDefaultOrientation() { |
||||
return deviceDefaultOrientation; |
||||
} |
||||
|
||||
public void setDeviceDefaultOrientation(int deviceDefaultOrientation) { |
||||
this.deviceDefaultOrientation = deviceDefaultOrientation; |
||||
} |
||||
|
||||
public void setDegrees(int degrees) { |
||||
this.degrees = degrees; |
||||
} |
||||
} |
@ -0,0 +1,51 @@ |
||||
package com.github.florent37.camerafragment.internal.controller; |
||||
|
||||
import android.os.Bundle; |
||||
|
||||
import java.io.File; |
||||
|
||||
import com.github.florent37.camerafragment.configuration.Configuration; |
||||
import com.github.florent37.camerafragment.internal.manager.CameraManager; |
||||
|
||||
/** |
||||
* Created by memfis on 7/6/16. |
||||
*/ |
||||
public interface CameraController<CameraId> { |
||||
|
||||
void onCreate(Bundle savedInstanceState); |
||||
|
||||
void onResume(); |
||||
|
||||
void onPause(); |
||||
|
||||
void onDestroy(); |
||||
|
||||
void takePhoto(); |
||||
|
||||
void startVideoRecord(); |
||||
|
||||
void stopVideoRecord(); |
||||
|
||||
boolean isVideoRecording(); |
||||
|
||||
void switchCamera(@Configuration.CameraFace int cameraFace); |
||||
|
||||
void switchQuality(); |
||||
|
||||
void setFlashMode(@Configuration.FlashMode int flashMode); |
||||
|
||||
int getNumberOfCameras(); |
||||
|
||||
@Configuration.MediaAction |
||||
int getMediaAction(); |
||||
|
||||
CameraId getCurrentCameraId(); |
||||
|
||||
File getOutputFile(); |
||||
|
||||
CameraManager getCameraManager(); |
||||
|
||||
CharSequence[] getVideoQualityOptions(); |
||||
|
||||
CharSequence[] getPhotoQualityOptions(); |
||||
} |
@ -0,0 +1,194 @@ |
||||
package com.github.florent37.camerafragment.internal.controller.impl; |
||||
|
||||
import android.content.Context; |
||||
import android.os.Bundle; |
||||
import android.util.Log; |
||||
import android.view.SurfaceHolder; |
||||
|
||||
import java.io.File; |
||||
|
||||
import com.github.florent37.camerafragment.configuration.Configuration; |
||||
import com.github.florent37.camerafragment.configuration.ConfigurationProvider; |
||||
import com.github.florent37.camerafragment.internal.controller.CameraController; |
||||
import com.github.florent37.camerafragment.internal.controller.view.CameraView; |
||||
import com.github.florent37.camerafragment.internal.manager.CameraManager; |
||||
import com.github.florent37.camerafragment.internal.manager.impl.Camera1Manager; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraCloseListener; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraOpenListener; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraPhotoListener; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraVideoListener; |
||||
import com.github.florent37.camerafragment.internal.ui.view.AutoFitSurfaceView; |
||||
import com.github.florent37.camerafragment.internal.utils.CameraHelper; |
||||
import com.github.florent37.camerafragment.internal.utils.Size; |
||||
|
||||
/** |
||||
* Created by memfis on 7/7/16. |
||||
*/ |
||||
|
||||
@SuppressWarnings("deprecation") |
||||
public class Camera1Controller implements CameraController<Integer>, |
||||
CameraOpenListener<Integer, SurfaceHolder.Callback>, CameraPhotoListener, CameraCloseListener<Integer>, CameraVideoListener { |
||||
|
||||
private final static String TAG = "Camera1Controller"; |
||||
|
||||
private final Context context; |
||||
|
||||
private Integer currentCameraId; |
||||
private ConfigurationProvider configurationProvider; |
||||
private CameraManager<Integer, SurfaceHolder.Callback> cameraManager; |
||||
private CameraView cameraView; |
||||
|
||||
private File outputFile; |
||||
|
||||
public Camera1Controller(Context context, CameraView cameraView, ConfigurationProvider configurationProvider) { |
||||
this.context = context; |
||||
this.cameraView = cameraView; |
||||
this.configurationProvider = configurationProvider; |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate(Bundle savedInstanceState) { |
||||
cameraManager = Camera1Manager.getInstance(); |
||||
cameraManager.initializeCameraManager(configurationProvider, context); |
||||
setCurrentCameraId(cameraManager.getFaceBackCameraId()); |
||||
} |
||||
|
||||
private void setCurrentCameraId(Integer cameraId) { |
||||
this.currentCameraId = cameraId; |
||||
cameraManager.setCameraId(cameraId); |
||||
} |
||||
|
||||
@Override |
||||
public void onResume() { |
||||
cameraManager.openCamera(currentCameraId, this); |
||||
} |
||||
|
||||
@Override |
||||
public void onPause() { |
||||
cameraManager.closeCamera(null); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
cameraManager.releaseCameraManager(); |
||||
} |
||||
|
||||
@Override |
||||
public void takePhoto() { |
||||
outputFile = CameraHelper.getOutputMediaFile(context, Configuration.MEDIA_ACTION_PHOTO); |
||||
cameraManager.takePhoto(outputFile, this); |
||||
} |
||||
|
||||
@Override |
||||
public void startVideoRecord() { |
||||
outputFile = CameraHelper.getOutputMediaFile(context, Configuration.MEDIA_ACTION_VIDEO); |
||||
cameraManager.startVideoRecord(outputFile, this); |
||||
} |
||||
|
||||
@Override |
||||
public void stopVideoRecord() { |
||||
cameraManager.stopVideoRecord(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isVideoRecording() { |
||||
return cameraManager.isVideoRecording(); |
||||
} |
||||
|
||||
@Override |
||||
public void switchCamera(@Configuration.CameraFace final int cameraFace) { |
||||
setCurrentCameraId(cameraManager.getCurrentCameraId().equals(cameraManager.getFaceFrontCameraId()) ? |
||||
cameraManager.getFaceBackCameraId() : cameraManager.getFaceFrontCameraId()); |
||||
|
||||
cameraManager.closeCamera(this); |
||||
} |
||||
|
||||
@Override |
||||
public void setFlashMode(@Configuration.FlashMode int flashMode) { |
||||
cameraManager.setFlashMode(flashMode); |
||||
} |
||||
|
||||
@Override |
||||
public void switchQuality() { |
||||
cameraManager.closeCamera(this); |
||||
} |
||||
|
||||
@Override |
||||
public int getNumberOfCameras() { |
||||
return cameraManager.getNumberOfCameras(); |
||||
} |
||||
|
||||
@Override |
||||
public int getMediaAction() { |
||||
return configurationProvider.getMediaAction(); |
||||
} |
||||
|
||||
@Override |
||||
public File getOutputFile() { |
||||
return outputFile; |
||||
} |
||||
|
||||
@Override |
||||
public Integer getCurrentCameraId() { |
||||
return currentCameraId; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void onCameraOpened(Integer cameraId, Size previewSize, SurfaceHolder.Callback surfaceCallback) { |
||||
cameraView.updateUiForMediaAction(configurationProvider.getMediaAction()); |
||||
cameraView.updateCameraPreview(previewSize, new AutoFitSurfaceView(context, surfaceCallback)); |
||||
cameraView.updateCameraSwitcher(getNumberOfCameras()); |
||||
} |
||||
|
||||
@Override |
||||
public void onCameraOpenError() { |
||||
Log.e(TAG, "onCameraOpenError"); |
||||
} |
||||
|
||||
@Override |
||||
public void onCameraClosed(Integer closedCameraId) { |
||||
cameraView.releaseCameraPreview(); |
||||
|
||||
cameraManager.openCamera(currentCameraId, this); |
||||
} |
||||
|
||||
@Override |
||||
public void onPhotoTaken(byte[] bytes, File photoFile) { |
||||
cameraView.onPhotoTaken(bytes); |
||||
} |
||||
|
||||
@Override |
||||
public void onPhotoTakeError() { |
||||
} |
||||
|
||||
@Override |
||||
public void onVideoRecordStarted(Size videoSize) { |
||||
cameraView.onVideoRecordStart(videoSize.getWidth(), videoSize.getHeight()); |
||||
} |
||||
|
||||
@Override |
||||
public void onVideoRecordStopped(File videoFile) { |
||||
cameraView.onVideoRecordStop(); |
||||
} |
||||
|
||||
@Override |
||||
public void onVideoRecordError() { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public CameraManager getCameraManager() { |
||||
return cameraManager; |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence[] getVideoQualityOptions() { |
||||
return cameraManager.getVideoQualityOptions(); |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence[] getPhotoQualityOptions() { |
||||
return cameraManager.getPhotoQualityOptions(); |
||||
} |
||||
} |
@ -0,0 +1,195 @@ |
||||
package com.github.florent37.camerafragment.internal.controller.impl; |
||||
|
||||
import android.annotation.TargetApi; |
||||
import android.content.Context; |
||||
import android.os.Build; |
||||
import android.os.Bundle; |
||||
import android.util.Log; |
||||
import android.view.TextureView; |
||||
|
||||
import java.io.File; |
||||
|
||||
import com.github.florent37.camerafragment.configuration.Configuration; |
||||
import com.github.florent37.camerafragment.configuration.ConfigurationProvider; |
||||
import com.github.florent37.camerafragment.internal.controller.CameraController; |
||||
import com.github.florent37.camerafragment.internal.controller.view.CameraView; |
||||
import com.github.florent37.camerafragment.internal.manager.CameraManager; |
||||
import com.github.florent37.camerafragment.internal.manager.impl.Camera2Manager; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraCloseListener; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraOpenListener; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraPhotoListener; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraVideoListener; |
||||
import com.github.florent37.camerafragment.internal.ui.view.AutoFitTextureView; |
||||
import com.github.florent37.camerafragment.internal.utils.CameraHelper; |
||||
import com.github.florent37.camerafragment.internal.utils.Size; |
||||
|
||||
/** |
||||
* Created by memfis on 7/6/16. |
||||
*/ |
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public class Camera2Controller implements CameraController<String>, |
||||
CameraOpenListener<String, TextureView.SurfaceTextureListener>, |
||||
CameraPhotoListener, CameraVideoListener, CameraCloseListener<String> { |
||||
|
||||
private final static String TAG = "Camera2Controller"; |
||||
|
||||
private final Context context; |
||||
private String currentCameraId; |
||||
private ConfigurationProvider configurationProvider; |
||||
private CameraManager<String, TextureView.SurfaceTextureListener> camera2Manager; |
||||
private CameraView cameraView; |
||||
|
||||
private File outputFile; |
||||
|
||||
public Camera2Controller(Context context, CameraView cameraView, ConfigurationProvider configurationProvider) { |
||||
this.context = context; |
||||
this.cameraView = cameraView; |
||||
this.configurationProvider = configurationProvider; |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate(Bundle savedInstanceState) { |
||||
camera2Manager = Camera2Manager.getInstance(); |
||||
camera2Manager.initializeCameraManager(configurationProvider, context); |
||||
setCurrentCameraId(camera2Manager.getFaceBackCameraId()); |
||||
} |
||||
|
||||
@Override |
||||
public void onResume() { |
||||
camera2Manager.openCamera(currentCameraId, this); |
||||
} |
||||
|
||||
@Override |
||||
public void onPause() { |
||||
camera2Manager.closeCamera(null); |
||||
cameraView.releaseCameraPreview(); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
camera2Manager.releaseCameraManager(); |
||||
} |
||||
|
||||
@Override |
||||
public void takePhoto() { |
||||
outputFile = CameraHelper.getOutputMediaFile(context, Configuration.MEDIA_ACTION_PHOTO); |
||||
camera2Manager.takePhoto(outputFile, this); |
||||
} |
||||
|
||||
@Override |
||||
public void startVideoRecord() { |
||||
outputFile = CameraHelper.getOutputMediaFile(context, Configuration.MEDIA_ACTION_VIDEO); |
||||
camera2Manager.startVideoRecord(outputFile, this); |
||||
} |
||||
|
||||
@Override |
||||
public void stopVideoRecord() { |
||||
camera2Manager.stopVideoRecord(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isVideoRecording() { |
||||
return camera2Manager.isVideoRecording(); |
||||
} |
||||
|
||||
@Override |
||||
public void switchCamera(final @Configuration.CameraFace int cameraFace) { |
||||
setCurrentCameraId(camera2Manager.getCurrentCameraId().equals(camera2Manager.getFaceFrontCameraId()) ? |
||||
camera2Manager.getFaceBackCameraId() : camera2Manager.getFaceFrontCameraId()); |
||||
|
||||
camera2Manager.closeCamera(this); |
||||
} |
||||
|
||||
private void setCurrentCameraId(String currentCameraId){ |
||||
this.currentCameraId = currentCameraId; |
||||
camera2Manager.setCameraId(currentCameraId); |
||||
} |
||||
|
||||
@Override |
||||
public void setFlashMode(@Configuration.FlashMode int flashMode) { |
||||
camera2Manager.setFlashMode(flashMode); |
||||
} |
||||
|
||||
@Override |
||||
public void switchQuality() { |
||||
camera2Manager.closeCamera(this); |
||||
} |
||||
|
||||
@Override |
||||
public int getNumberOfCameras() { |
||||
return camera2Manager.getNumberOfCameras(); |
||||
} |
||||
|
||||
@Override |
||||
public int getMediaAction() { |
||||
return configurationProvider.getMediaAction(); |
||||
} |
||||
|
||||
@Override |
||||
public File getOutputFile() { |
||||
return outputFile; |
||||
} |
||||
|
||||
@Override |
||||
public String getCurrentCameraId() { |
||||
return currentCameraId; |
||||
} |
||||
|
||||
@Override |
||||
public void onCameraOpened(String openedCameraId, Size previewSize, TextureView.SurfaceTextureListener surfaceTextureListener) { |
||||
cameraView.updateUiForMediaAction(Configuration.MEDIA_ACTION_UNSPECIFIED); |
||||
cameraView.updateCameraPreview(previewSize, new AutoFitTextureView(context, surfaceTextureListener)); |
||||
cameraView.updateCameraSwitcher(camera2Manager.getNumberOfCameras()); |
||||
} |
||||
|
||||
@Override |
||||
public void onCameraOpenError() { |
||||
Log.e(TAG, "onCameraOpenError"); |
||||
} |
||||
|
||||
@Override |
||||
public void onCameraClosed(String closedCameraId) { |
||||
cameraView.releaseCameraPreview(); |
||||
|
||||
camera2Manager.openCamera(currentCameraId, this); |
||||
} |
||||
|
||||
@Override |
||||
public void onPhotoTaken(byte[] bytes, File photoFile) { |
||||
cameraView.onPhotoTaken(bytes); |
||||
} |
||||
|
||||
@Override |
||||
public void onPhotoTakeError() { |
||||
} |
||||
|
||||
@Override |
||||
public void onVideoRecordStarted(Size videoSize) { |
||||
cameraView.onVideoRecordStart(videoSize.getWidth(), videoSize.getHeight()); |
||||
} |
||||
|
||||
@Override |
||||
public void onVideoRecordStopped(File videoFile) { |
||||
cameraView.onVideoRecordStop(); |
||||
} |
||||
|
||||
@Override |
||||
public void onVideoRecordError() { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public CameraManager getCameraManager() { |
||||
return camera2Manager; |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence[] getVideoQualityOptions() { |
||||
return camera2Manager.getVideoQualityOptions(); |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence[] getPhotoQualityOptions() { |
||||
return camera2Manager.getPhotoQualityOptions(); |
||||
} |
||||
} |
@ -0,0 +1,27 @@ |
||||
package com.github.florent37.camerafragment.internal.controller.view; |
||||
|
||||
import android.view.View; |
||||
|
||||
import com.github.florent37.camerafragment.configuration.Configuration; |
||||
import com.github.florent37.camerafragment.internal.utils.Size; |
||||
|
||||
/** |
||||
* Created by memfis on 7/6/16. |
||||
*/ |
||||
public interface CameraView { |
||||
|
||||
void updateCameraPreview(Size size, View cameraPreview); |
||||
|
||||
void updateUiForMediaAction(@Configuration.MediaAction int mediaAction); |
||||
|
||||
void updateCameraSwitcher(int numberOfCameras); |
||||
|
||||
void onPhotoTaken(byte[] bytes); |
||||
|
||||
void onVideoRecordStart(int width, int height); |
||||
|
||||
void onVideoRecordStop(); |
||||
|
||||
void releaseCameraPreview(); |
||||
|
||||
} |
@ -0,0 +1,17 @@ |
||||
package com.github.florent37.camerafragment.internal.enums; |
||||
|
||||
import android.support.annotation.IntDef; |
||||
|
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
|
||||
public class Camera { |
||||
|
||||
public static final int CAMERA_TYPE_FRONT = 0; |
||||
public static final int CAMERA_TYPE_REAR = 1; |
||||
|
||||
@IntDef({CAMERA_TYPE_FRONT, CAMERA_TYPE_REAR}) |
||||
@Retention(RetentionPolicy.SOURCE) |
||||
public @interface CameraType { |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
package com.github.florent37.camerafragment.internal.enums; |
||||
|
||||
import android.support.annotation.IntDef; |
||||
|
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
|
||||
public class Flash { |
||||
public static final int FLASH_ON = 0; |
||||
public static final int FLASH_OFF = 1; |
||||
public static final int FLASH_AUTO = 2; |
||||
|
||||
@IntDef({FLASH_ON, FLASH_OFF, FLASH_AUTO}) |
||||
@Retention(RetentionPolicy.SOURCE) |
||||
public @interface FlashMode { |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
package com.github.florent37.camerafragment.internal.enums; |
||||
|
||||
import android.support.annotation.IntDef; |
||||
|
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
|
||||
public class MediaAction { |
||||
|
||||
public final static int ACTION_PHOTO = 0; |
||||
public final static int ACTION_VIDEO = 1; |
||||
|
||||
@IntDef({ACTION_PHOTO, ACTION_VIDEO}) |
||||
@Retention(RetentionPolicy.SOURCE) |
||||
public @interface MediaActionState { |
||||
} |
||||
} |
@ -0,0 +1,19 @@ |
||||
package com.github.florent37.camerafragment.internal.enums; |
||||
|
||||
import android.support.annotation.IntDef; |
||||
|
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
|
||||
public class Record { |
||||
|
||||
public static final int TAKE_PHOTO_STATE = 0; |
||||
public static final int READY_FOR_RECORD_STATE = 1; |
||||
public static final int RECORD_IN_PROGRESS_STATE = 2; |
||||
|
||||
@IntDef({TAKE_PHOTO_STATE, READY_FOR_RECORD_STATE, RECORD_IN_PROGRESS_STATE}) |
||||
@Retention(RetentionPolicy.SOURCE) |
||||
public @interface RecordState { |
||||
} |
||||
|
||||
} |
@ -0,0 +1,57 @@ |
||||
package com.github.florent37.camerafragment.internal.manager; |
||||
|
||||
import android.content.Context; |
||||
|
||||
import java.io.File; |
||||
|
||||
import com.github.florent37.camerafragment.configuration.Configuration; |
||||
import com.github.florent37.camerafragment.configuration.ConfigurationProvider; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraCloseListener; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraOpenListener; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraPhotoListener; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraVideoListener; |
||||
import com.github.florent37.camerafragment.internal.utils.Size; |
||||
|
||||
/** |
||||
* Created by memfis on 8/14/16. |
||||
*/ |
||||
public interface CameraManager<CameraId, SurfaceListener> { |
||||
|
||||
void initializeCameraManager(ConfigurationProvider configurationProvider, Context context); |
||||
|
||||
void openCamera(CameraId cameraId, CameraOpenListener<CameraId, SurfaceListener> cameraOpenListener); |
||||
|
||||
void closeCamera(CameraCloseListener<CameraId> cameraCloseListener); |
||||
|
||||
void setFlashMode(@Configuration.FlashMode int flashMode); |
||||
|
||||
void takePhoto(File photoFile, CameraPhotoListener cameraPhotoListener); |
||||
|
||||
void startVideoRecord(File videoFile, CameraVideoListener cameraVideoListener); |
||||
|
||||
Size getPhotoSizeForQuality(@Configuration.MediaQuality int mediaQuality); |
||||
|
||||
void stopVideoRecord(); |
||||
|
||||
void releaseCameraManager(); |
||||
|
||||
CameraId getCurrentCameraId(); |
||||
|
||||
CameraId getFaceFrontCameraId(); |
||||
|
||||
CameraId getFaceBackCameraId(); |
||||
|
||||
int getNumberOfCameras(); |
||||
|
||||
int getFaceFrontCameraOrientation(); |
||||
|
||||
int getFaceBackCameraOrientation(); |
||||
|
||||
boolean isVideoRecording(); |
||||
|
||||
CharSequence[] getVideoQualityOptions(); |
||||
|
||||
CharSequence[] getPhotoQualityOptions(); |
||||
|
||||
void setCameraId(CameraId currentCameraId); |
||||
} |
@ -0,0 +1,148 @@ |
||||
package com.github.florent37.camerafragment.internal.manager.impl; |
||||
|
||||
import android.content.Context; |
||||
import android.media.CamcorderProfile; |
||||
import android.media.MediaRecorder; |
||||
import android.os.Build; |
||||
import android.os.Handler; |
||||
import android.os.HandlerThread; |
||||
import android.os.Looper; |
||||
import android.os.Process; |
||||
import android.util.Log; |
||||
|
||||
import com.github.florent37.camerafragment.configuration.Configuration; |
||||
import com.github.florent37.camerafragment.configuration.ConfigurationProvider; |
||||
import com.github.florent37.camerafragment.internal.manager.CameraManager; |
||||
import com.github.florent37.camerafragment.internal.utils.Size; |
||||
|
||||
/** |
||||
* Created by memfis on 8/14/16. |
||||
*/ |
||||
abstract class BaseCameraManager<CameraId, SurfaceListener> |
||||
implements CameraManager<CameraId, SurfaceListener>, MediaRecorder.OnInfoListener { |
||||
|
||||
private static final String TAG = "BaseCameraManager"; |
||||
|
||||
protected Context context; |
||||
ConfigurationProvider configurationProvider; |
||||
|
||||
MediaRecorder videoRecorder; |
||||
boolean isVideoRecording = false; |
||||
|
||||
CameraId currentCameraId = null; |
||||
CameraId faceFrontCameraId = null; |
||||
CameraId faceBackCameraId = null; |
||||
int numberOfCameras = 0; |
||||
int faceFrontCameraOrientation; |
||||
int faceBackCameraOrientation; |
||||
|
||||
CamcorderProfile camcorderProfile; |
||||
Size photoSize; |
||||
Size videoSize; |
||||
Size previewSize; |
||||
Size windowSize; |
||||
|
||||
HandlerThread backgroundThread; |
||||
Handler backgroundHandler; |
||||
Handler uiHandler = new Handler(Looper.getMainLooper()); |
||||
|
||||
@Override |
||||
public void initializeCameraManager(ConfigurationProvider configurationProvider, Context context) { |
||||
this.context = context; |
||||
this.configurationProvider = configurationProvider; |
||||
startBackgroundThread(); |
||||
} |
||||
|
||||
@Override |
||||
public void releaseCameraManager() { |
||||
this.context = null; |
||||
stopBackgroundThread(); |
||||
} |
||||
|
||||
protected abstract void prepareCameraOutputs(); |
||||
|
||||
protected abstract boolean prepareVideoRecorder(); |
||||
|
||||
protected abstract void onMaxDurationReached(); |
||||
|
||||
protected abstract void onMaxFileSizeReached(); |
||||
|
||||
protected abstract int getPhotoOrientation(@Configuration.SensorPosition int sensorPosition); |
||||
|
||||
protected abstract int getVideoOrientation(@Configuration.SensorPosition int sensorPosition); |
||||
|
||||
protected void releaseVideoRecorder() { |
||||
try { |
||||
if (videoRecorder != null) { |
||||
videoRecorder.reset(); |
||||
videoRecorder.release(); |
||||
} |
||||
} catch (Exception ignore) { |
||||
|
||||
} finally { |
||||
videoRecorder = null; |
||||
} |
||||
} |
||||
|
||||
private void startBackgroundThread() { |
||||
backgroundThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); |
||||
backgroundThread.start(); |
||||
backgroundHandler = new Handler(backgroundThread.getLooper()); |
||||
} |
||||
|
||||
private void stopBackgroundThread() { |
||||
if (Build.VERSION.SDK_INT > 17) { |
||||
backgroundThread.quitSafely(); |
||||
} else backgroundThread.quit(); |
||||
|
||||
try { |
||||
backgroundThread.join(); |
||||
} catch (InterruptedException e) { |
||||
Log.e(TAG, "stopBackgroundThread: ", e); |
||||
} finally { |
||||
backgroundThread = null; |
||||
backgroundHandler = null; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onInfo(MediaRecorder mediaRecorder, int what, int extra) { |
||||
if (MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED == what) { |
||||
onMaxDurationReached(); |
||||
} else if (MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED == what) { |
||||
onMaxFileSizeReached(); |
||||
} |
||||
} |
||||
|
||||
public boolean isVideoRecording() { |
||||
return isVideoRecording; |
||||
} |
||||
|
||||
public CameraId getCurrentCameraId() { |
||||
return currentCameraId; |
||||
} |
||||
|
||||
public CameraId getFaceFrontCameraId() { |
||||
return faceFrontCameraId; |
||||
} |
||||
|
||||
public CameraId getFaceBackCameraId() { |
||||
return faceBackCameraId; |
||||
} |
||||
|
||||
public int getNumberOfCameras() { |
||||
return numberOfCameras; |
||||
} |
||||
|
||||
public int getFaceFrontCameraOrientation() { |
||||
return faceFrontCameraOrientation; |
||||
} |
||||
|
||||
public int getFaceBackCameraOrientation() { |
||||
return faceBackCameraOrientation; |
||||
} |
||||
|
||||
public void setCameraId(CameraId currentCameraId) { |
||||
this.currentCameraId = currentCameraId; |
||||
} |
||||
} |
@ -0,0 +1,627 @@ |
||||
package com.github.florent37.camerafragment.internal.manager.impl; |
||||
|
||||
import android.content.Context; |
||||
import android.graphics.PixelFormat; |
||||
import android.hardware.Camera; |
||||
import android.media.CamcorderProfile; |
||||
import android.media.ExifInterface; |
||||
import android.media.MediaRecorder; |
||||
import android.os.Build; |
||||
import android.util.Log; |
||||
import android.view.Surface; |
||||
import android.view.SurfaceHolder; |
||||
import android.view.WindowManager; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileNotFoundException; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import com.github.florent37.camerafragment.configuration.Configuration; |
||||
import com.github.florent37.camerafragment.configuration.ConfigurationProvider; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraCloseListener; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraOpenListener; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraPhotoListener; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraVideoListener; |
||||
import com.github.florent37.camerafragment.internal.ui.model.PhotoQualityOption; |
||||
import com.github.florent37.camerafragment.internal.ui.model.VideoQualityOption; |
||||
import com.github.florent37.camerafragment.internal.utils.CameraHelper; |
||||
import com.github.florent37.camerafragment.internal.utils.Size; |
||||
|
||||
/** |
||||
* Created by memfis on 8/14/16. |
||||
*/ |
||||
@SuppressWarnings("deprecation") |
||||
public class Camera1Manager extends BaseCameraManager<Integer, SurfaceHolder.Callback> |
||||
implements SurfaceHolder.Callback, Camera.PictureCallback { |
||||
|
||||
private static final String TAG = "Camera1Manager"; |
||||
|
||||
private Camera camera; |
||||
private Surface surface; |
||||
|
||||
private static Camera1Manager currentInstance; |
||||
|
||||
private int orientation; |
||||
private int displayRotation = 0; |
||||
|
||||
private File outputPath; |
||||
private CameraVideoListener videoListener; |
||||
private CameraPhotoListener photoListener; |
||||
|
||||
private Camera1Manager() { |
||||
|
||||
} |
||||
|
||||
public static Camera1Manager getInstance() { |
||||
if (currentInstance == null) currentInstance = new Camera1Manager(); |
||||
return currentInstance; |
||||
} |
||||
|
||||
@Override |
||||
public void openCamera(final Integer cameraId, |
||||
final CameraOpenListener<Integer, SurfaceHolder.Callback> cameraOpenListener) { |
||||
this.currentCameraId = cameraId; |
||||
backgroundHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
try { |
||||
camera = Camera.open(cameraId); |
||||
prepareCameraOutputs(); |
||||
if (cameraOpenListener != null) { |
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
cameraOpenListener.onCameraOpened(cameraId, previewSize, currentInstance); |
||||
} |
||||
}); |
||||
} |
||||
} catch (Exception error) { |
||||
Log.d(TAG, "Can't open camera: " + error.getMessage()); |
||||
if (cameraOpenListener != null) { |
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
cameraOpenListener.onCameraOpenError(); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void closeCamera(final CameraCloseListener<Integer> cameraCloseListener) { |
||||
backgroundHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
if (camera != null) { |
||||
camera.release(); |
||||
camera = null; |
||||
if (cameraCloseListener != null) { |
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
cameraCloseListener.onCameraClosed(currentCameraId); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void setFlashMode(@Configuration.FlashMode int flashMode) { |
||||
setFlashMode(camera, camera.getParameters(), flashMode); |
||||
} |
||||
|
||||
@Override |
||||
public void takePhoto(File photoFile, CameraPhotoListener cameraPhotoListener) { |
||||
this.outputPath = photoFile; |
||||
this.photoListener = cameraPhotoListener; |
||||
backgroundHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
setCameraPhotoQuality(camera); |
||||
camera.takePicture(null, null, currentInstance); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void startVideoRecord(final File videoFile, CameraVideoListener cameraVideoListener) { |
||||
if (isVideoRecording) return; |
||||
|
||||
this.outputPath = videoFile; |
||||
this.videoListener = cameraVideoListener; |
||||
|
||||
if (videoListener != null) |
||||
backgroundHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
if (prepareVideoRecorder()) { |
||||
videoRecorder.start(); |
||||
isVideoRecording = true; |
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
videoListener.onVideoRecordStarted(videoSize); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void stopVideoRecord() { |
||||
if (isVideoRecording) |
||||
backgroundHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
|
||||
try { |
||||
if (videoRecorder != null) videoRecorder.stop(); |
||||
} catch (Exception ignore) { |
||||
// ignore illegal state.
|
||||
// appear in case time or file size reach limit and stop already called.
|
||||
} |
||||
|
||||
isVideoRecording = false; |
||||
releaseVideoRecorder(); |
||||
|
||||
if (videoListener != null) { |
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
videoListener.onVideoRecordStopped(outputPath); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void releaseCameraManager() { |
||||
super.releaseCameraManager(); |
||||
} |
||||
|
||||
@Override |
||||
public void initializeCameraManager(ConfigurationProvider configurationProvider, Context context) { |
||||
super.initializeCameraManager(configurationProvider, context); |
||||
|
||||
numberOfCameras = Camera.getNumberOfCameras(); |
||||
|
||||
for (int i = 0; i < numberOfCameras; ++i) { |
||||
Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); |
||||
|
||||
Camera.getCameraInfo(i, cameraInfo); |
||||
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { |
||||
faceBackCameraId = i; |
||||
faceBackCameraOrientation = cameraInfo.orientation; |
||||
} else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { |
||||
faceFrontCameraId = i; |
||||
faceFrontCameraOrientation = cameraInfo.orientation; |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Size getPhotoSizeForQuality(@Configuration.MediaQuality int mediaQuality) { |
||||
return CameraHelper.getPictureSize(Size.fromList(camera.getParameters().getSupportedPictureSizes()), mediaQuality); |
||||
} |
||||
|
||||
@Override |
||||
protected void prepareCameraOutputs() { |
||||
try { |
||||
if (configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_AUTO) { |
||||
camcorderProfile = CameraHelper.getCamcorderProfile(currentCameraId, configurationProvider.getVideoFileSize(), configurationProvider.getMinimumVideoDuration()); |
||||
} else |
||||
camcorderProfile = CameraHelper.getCamcorderProfile(configurationProvider.getMediaQuality(), currentCameraId); |
||||
|
||||
List<Size> previewSizes = Size.fromList(camera.getParameters().getSupportedPreviewSizes()); |
||||
List<Size> pictureSizes = Size.fromList(camera.getParameters().getSupportedPictureSizes()); |
||||
List<Size> videoSizes; |
||||
if (Build.VERSION.SDK_INT > 10) |
||||
videoSizes = Size.fromList(camera.getParameters().getSupportedVideoSizes()); |
||||
else videoSizes = previewSizes; |
||||
|
||||
videoSize = CameraHelper.getSizeWithClosestRatio( |
||||
(videoSizes == null || videoSizes.isEmpty()) ? previewSizes : videoSizes, |
||||
camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight); |
||||
|
||||
photoSize = CameraHelper.getPictureSize( |
||||
(pictureSizes == null || pictureSizes.isEmpty()) ? previewSizes : pictureSizes, |
||||
configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_AUTO |
||||
? Configuration.MEDIA_QUALITY_HIGHEST : configurationProvider.getMediaQuality()); |
||||
|
||||
if (configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_PHOTO |
||||
|| configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_UNSPECIFIED) { |
||||
previewSize = CameraHelper.getSizeWithClosestRatio(previewSizes, photoSize.getWidth(), photoSize.getHeight()); |
||||
} else { |
||||
previewSize = CameraHelper.getSizeWithClosestRatio(previewSizes, videoSize.getWidth(), videoSize.getHeight()); |
||||
} |
||||
} catch (Exception e) { |
||||
Log.e(TAG, "Error while setup camera sizes."); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected boolean prepareVideoRecorder() { |
||||
videoRecorder = new MediaRecorder(); |
||||
try { |
||||
camera.lock(); |
||||
camera.unlock(); |
||||
videoRecorder.setCamera(camera); |
||||
|
||||
videoRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); |
||||
videoRecorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT); |
||||
|
||||
videoRecorder.setOutputFormat(camcorderProfile.fileFormat); |
||||
videoRecorder.setVideoFrameRate(camcorderProfile.videoFrameRate); |
||||
videoRecorder.setVideoSize(videoSize.getWidth(), videoSize.getHeight()); |
||||
videoRecorder.setVideoEncodingBitRate(camcorderProfile.videoBitRate); |
||||
videoRecorder.setVideoEncoder(camcorderProfile.videoCodec); |
||||
|
||||
videoRecorder.setAudioEncodingBitRate(camcorderProfile.audioBitRate); |
||||
videoRecorder.setAudioChannels(camcorderProfile.audioChannels); |
||||
videoRecorder.setAudioSamplingRate(camcorderProfile.audioSampleRate); |
||||
videoRecorder.setAudioEncoder(camcorderProfile.audioCodec); |
||||
|
||||
videoRecorder.setOutputFile(outputPath.toString()); |
||||
|
||||
if (configurationProvider.getVideoFileSize() > 0) { |
||||
videoRecorder.setMaxFileSize(configurationProvider.getVideoFileSize()); |
||||
|
||||
videoRecorder.setOnInfoListener(this); |
||||
} |
||||
if (configurationProvider.getVideoDuration() > 0) { |
||||
videoRecorder.setMaxDuration(configurationProvider.getVideoDuration()); |
||||
|
||||
videoRecorder.setOnInfoListener(this); |
||||
} |
||||
|
||||
videoRecorder.setOrientationHint(getVideoOrientation(configurationProvider.getSensorPosition())); |
||||
videoRecorder.setPreviewDisplay(surface); |
||||
|
||||
videoRecorder.prepare(); |
||||
|
||||
return true; |
||||
} catch (IllegalStateException error) { |
||||
Log.e(TAG, "IllegalStateException preparing MediaRecorder: " + error.getMessage()); |
||||
} catch (IOException error) { |
||||
Log.e(TAG, "IOException preparing MediaRecorder: " + error.getMessage()); |
||||
} catch (Throwable error) { |
||||
Log.e(TAG, "Error during preparing MediaRecorder: " + error.getMessage()); |
||||
} |
||||
|
||||
releaseVideoRecorder(); |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
protected void onMaxDurationReached() { |
||||
stopVideoRecord(); |
||||
} |
||||
|
||||
@Override |
||||
protected void onMaxFileSizeReached() { |
||||
stopVideoRecord(); |
||||
} |
||||
|
||||
@Override |
||||
protected void releaseVideoRecorder() { |
||||
super.releaseVideoRecorder(); |
||||
|
||||
try { |
||||
camera.lock(); // lock camera for later use
|
||||
} catch (Exception ignore) { |
||||
} |
||||
} |
||||
|
||||
//------------------------Implementation------------------
|
||||
|
||||
private void startPreview(SurfaceHolder surfaceHolder) { |
||||
try { |
||||
Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); |
||||
Camera.getCameraInfo(currentCameraId, cameraInfo); |
||||
int cameraRotationOffset = cameraInfo.orientation; |
||||
|
||||
Camera.Parameters parameters = camera.getParameters(); |
||||
setAutoFocus(camera, parameters); |
||||
setFlashMode(configurationProvider.getFlashMode()); |
||||
|
||||
if (configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_PHOTO |
||||
|| configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_UNSPECIFIED) |
||||
turnPhotoCameraFeaturesOn(camera, parameters); |
||||
else if (configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_PHOTO) |
||||
turnVideoCameraFeaturesOn(camera, parameters); |
||||
|
||||
int rotation = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation(); |
||||
int degrees = 0; |
||||
switch (rotation) { |
||||
case Surface.ROTATION_0: |
||||
degrees = 0; |
||||
break; // Natural orientation
|
||||
case Surface.ROTATION_90: |
||||
degrees = 90; |
||||
break; // Landscape left
|
||||
case Surface.ROTATION_180: |
||||
degrees = 180; |
||||
break;// Upside down
|
||||
case Surface.ROTATION_270: |
||||
degrees = 270; |
||||
break;// Landscape right
|
||||
} |
||||
|
||||
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { |
||||
displayRotation = (cameraRotationOffset + degrees) % 360; |
||||
displayRotation = (360 - displayRotation) % 360; // compensate
|
||||
} else { |
||||
displayRotation = (cameraRotationOffset - degrees + 360) % 360; |
||||
} |
||||
|
||||
this.camera.setDisplayOrientation(displayRotation); |
||||
|
||||
if (Build.VERSION.SDK_INT > 13 |
||||
&& (configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_VIDEO |
||||
|| configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_UNSPECIFIED)) { |
||||
// parameters.setRecordingHint(true);
|
||||
} |
||||
|
||||
if (Build.VERSION.SDK_INT > 14 |
||||
&& parameters.isVideoStabilizationSupported() |
||||
&& (configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_VIDEO |
||||
|| configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_UNSPECIFIED)) { |
||||
parameters.setVideoStabilization(true); |
||||
} |
||||
|
||||
parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight()); |
||||
parameters.setPictureSize(photoSize.getWidth(), photoSize.getHeight()); |
||||
|
||||
camera.setParameters(parameters); |
||||
camera.setPreviewDisplay(surfaceHolder); |
||||
camera.startPreview(); |
||||
|
||||
} catch (IOException error) { |
||||
Log.d(TAG, "Error setting camera preview: " + error.getMessage()); |
||||
} catch (Exception ignore) { |
||||
Log.d(TAG, "Error starting camera preview: " + ignore.getMessage()); |
||||
} |
||||
} |
||||
|
||||
private void turnPhotoCameraFeaturesOn(Camera camera, Camera.Parameters parameters) { |
||||
if (parameters.getSupportedFocusModes().contains( |
||||
Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { |
||||
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); |
||||
} |
||||
camera.setParameters(parameters); |
||||
} |
||||
|
||||
private void turnVideoCameraFeaturesOn(Camera camera, Camera.Parameters parameters) { |
||||
if (parameters.getSupportedFocusModes().contains( |
||||
Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { |
||||
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); |
||||
} |
||||
camera.setParameters(parameters); |
||||
} |
||||
|
||||
private void setAutoFocus(Camera camera, Camera.Parameters parameters) { |
||||
try { |
||||
if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) { |
||||
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); |
||||
camera.setParameters(parameters); |
||||
} |
||||
} catch (Exception ignore) { |
||||
} |
||||
} |
||||
|
||||
private void setFlashMode(Camera camera, Camera.Parameters parameters, @Configuration.FlashMode int flashMode) { |
||||
try { |
||||
switch (flashMode) { |
||||
case Configuration.FLASH_MODE_AUTO: |
||||
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO); |
||||
break; |
||||
case Configuration.FLASH_MODE_ON: |
||||
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_ON); |
||||
break; |
||||
case Configuration.FLASH_MODE_OFF: |
||||
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); |
||||
break; |
||||
default: |
||||
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO); |
||||
break; |
||||
} |
||||
camera.setParameters(parameters); |
||||
} catch (Exception ignore) { |
||||
} |
||||
} |
||||
|
||||
private void setCameraPhotoQuality(Camera camera) { |
||||
Camera.Parameters parameters = camera.getParameters(); |
||||
|
||||
parameters.setPictureFormat(PixelFormat.JPEG); |
||||
|
||||
if (configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_LOW) { |
||||
parameters.setJpegQuality(50); |
||||
} else if (configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_MEDIUM) { |
||||
parameters.setJpegQuality(75); |
||||
} else if (configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_HIGH) { |
||||
parameters.setJpegQuality(100); |
||||
} else if (configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_HIGHEST) { |
||||
parameters.setJpegQuality(100); |
||||
} |
||||
parameters.setPictureSize(photoSize.getWidth(), photoSize.getHeight()); |
||||
|
||||
camera.setParameters(parameters); |
||||
} |
||||
|
||||
@Override |
||||
protected int getPhotoOrientation(@Configuration.SensorPosition int sensorPosition) { |
||||
int rotate; |
||||
if (currentCameraId.equals(faceFrontCameraId)) { |
||||
rotate = (360 + faceFrontCameraOrientation + configurationProvider.getDegrees()) % 360; |
||||
} else { |
||||
rotate = (360 + faceBackCameraOrientation - configurationProvider.getDegrees()) % 360; |
||||
} |
||||
|
||||
if (rotate == 0) { |
||||
orientation = ExifInterface.ORIENTATION_NORMAL; |
||||
} else if (rotate == 90) { |
||||
orientation = ExifInterface.ORIENTATION_ROTATE_90; |
||||
} else if (rotate == 180) { |
||||
orientation = ExifInterface.ORIENTATION_ROTATE_180; |
||||
} else if (rotate == 270) { |
||||
orientation = ExifInterface.ORIENTATION_ROTATE_270; |
||||
} |
||||
|
||||
return orientation; |
||||
|
||||
} |
||||
|
||||
@Override |
||||
protected int getVideoOrientation(@Configuration.SensorPosition int sensorPosition) { |
||||
int degrees = 0; |
||||
switch (sensorPosition) { |
||||
case Configuration.SENSOR_POSITION_UP: |
||||
degrees = 0; |
||||
break; // Natural orientation
|
||||
case Configuration.SENSOR_POSITION_LEFT: |
||||
degrees = 90; |
||||
break; // Landscape left
|
||||
case Configuration.SENSOR_POSITION_UP_SIDE_DOWN: |
||||
degrees = 180; |
||||
break;// Upside down
|
||||
case Configuration.SENSOR_POSITION_RIGHT: |
||||
degrees = 270; |
||||
break;// Landscape right
|
||||
} |
||||
|
||||
int rotate; |
||||
if (currentCameraId.equals(faceFrontCameraId)) { |
||||
rotate = (360 + faceFrontCameraOrientation + degrees) % 360; |
||||
} else { |
||||
rotate = (360 + faceBackCameraOrientation - degrees) % 360; |
||||
} |
||||
return rotate; |
||||
} |
||||
|
||||
@Override |
||||
public void surfaceCreated(SurfaceHolder surfaceHolder) { |
||||
if (surfaceHolder.getSurface() == null) { |
||||
return; |
||||
} |
||||
|
||||
surface = surfaceHolder.getSurface(); |
||||
|
||||
try { |
||||
camera.stopPreview(); |
||||
} catch (Exception ignore) { |
||||
} |
||||
|
||||
startPreview(surfaceHolder); |
||||
} |
||||
|
||||
@Override |
||||
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { |
||||
if (surfaceHolder.getSurface() == null) { |
||||
return; |
||||
} |
||||
|
||||
surface = surfaceHolder.getSurface(); |
||||
|
||||
try { |
||||
camera.stopPreview(); |
||||
} catch (Exception ignore) { |
||||
} |
||||
|
||||
startPreview(surfaceHolder); |
||||
} |
||||
|
||||
@Override |
||||
public void surfaceDestroyed(SurfaceHolder surfaceHolder) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void onPictureTaken(final byte[] bytes, Camera camera) { |
||||
File pictureFile = outputPath; |
||||
if (pictureFile == null) { |
||||
Log.d(TAG, "Error creating media file, check storage permissions."); |
||||
return; |
||||
} |
||||
|
||||
try { |
||||
FileOutputStream fileOutputStream = new FileOutputStream(pictureFile); |
||||
fileOutputStream.write(bytes); |
||||
fileOutputStream.close(); |
||||
} catch (FileNotFoundException error) { |
||||
Log.e(TAG, "File not found: " + error.getMessage()); |
||||
} catch (IOException error) { |
||||
Log.e(TAG, "Error accessing file: " + error.getMessage()); |
||||
} catch (Throwable error) { |
||||
Log.e(TAG, "Error saving file: " + error.getMessage()); |
||||
} |
||||
|
||||
try { |
||||
ExifInterface exif = new ExifInterface(pictureFile.getAbsolutePath()); |
||||
exif.setAttribute(ExifInterface.TAG_ORIENTATION, "" + getPhotoOrientation(configurationProvider.getSensorPosition())); |
||||
exif.saveAttributes(); |
||||
|
||||
if (photoListener != null) { |
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
photoListener.onPhotoTaken(bytes, outputPath); |
||||
} |
||||
}); |
||||
} |
||||
} catch (Throwable error) { |
||||
Log.e(TAG, "Can't save exif info: " + error.getMessage()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence[] getVideoQualityOptions() { |
||||
List<CharSequence> videoQualities = new ArrayList<>(); |
||||
|
||||
if (configurationProvider.getMinimumVideoDuration() > 0) |
||||
videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_AUTO, CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_AUTO, getCurrentCameraId()), configurationProvider.getMinimumVideoDuration())); |
||||
|
||||
CamcorderProfile camcorderProfile = CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_HIGH, getCurrentCameraId()); |
||||
double videoDuration = CameraHelper.calculateApproximateVideoDuration(camcorderProfile, configurationProvider.getVideoFileSize()); |
||||
videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_HIGH, camcorderProfile, videoDuration)); |
||||
|
||||
camcorderProfile = CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_MEDIUM, getCurrentCameraId()); |
||||
videoDuration = CameraHelper.calculateApproximateVideoDuration(camcorderProfile, configurationProvider.getVideoFileSize()); |
||||
videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_MEDIUM, camcorderProfile, videoDuration)); |
||||
|
||||
camcorderProfile = CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_LOW, getCurrentCameraId()); |
||||
videoDuration = CameraHelper.calculateApproximateVideoDuration(camcorderProfile, configurationProvider.getVideoFileSize()); |
||||
videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_LOW, camcorderProfile, videoDuration)); |
||||
|
||||
CharSequence[] array = new CharSequence[videoQualities.size()]; |
||||
videoQualities.toArray(array); |
||||
|
||||
return array; |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence[] getPhotoQualityOptions() { |
||||
List<CharSequence> photoQualities = new ArrayList<>(); |
||||
photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_HIGHEST, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_HIGHEST))); |
||||
photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_HIGH, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_HIGH))); |
||||
photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_MEDIUM, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_MEDIUM))); |
||||
photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_LOWEST, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_LOWEST))); |
||||
|
||||
CharSequence[] array = new CharSequence[photoQualities.size()]; |
||||
photoQualities.toArray(array); |
||||
|
||||
return array; |
||||
} |
||||
} |
@ -0,0 +1,844 @@ |
||||
package com.github.florent37.camerafragment.internal.manager.impl; |
||||
|
||||
import android.annotation.TargetApi; |
||||
import android.content.Context; |
||||
import android.graphics.ImageFormat; |
||||
import android.graphics.Point; |
||||
import android.graphics.SurfaceTexture; |
||||
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.CaptureRequest; |
||||
import android.hardware.camera2.CaptureResult; |
||||
import android.hardware.camera2.TotalCaptureResult; |
||||
import android.hardware.camera2.params.StreamConfigurationMap; |
||||
import android.media.CamcorderProfile; |
||||
import android.media.ImageReader; |
||||
import android.media.MediaRecorder; |
||||
import android.os.Build; |
||||
import android.support.annotation.IntDef; |
||||
import android.support.annotation.NonNull; |
||||
import android.text.TextUtils; |
||||
import android.util.Log; |
||||
import android.view.Display; |
||||
import android.view.Surface; |
||||
import android.view.TextureView; |
||||
import android.view.WindowManager; |
||||
|
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.Objects; |
||||
|
||||
import com.github.florent37.camerafragment.configuration.Configuration; |
||||
import com.github.florent37.camerafragment.configuration.ConfigurationProvider; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraCloseListener; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraOpenListener; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraPhotoListener; |
||||
import com.github.florent37.camerafragment.internal.manager.listener.CameraVideoListener; |
||||
import com.github.florent37.camerafragment.internal.ui.model.PhotoQualityOption; |
||||
import com.github.florent37.camerafragment.internal.ui.model.VideoQualityOption; |
||||
import com.github.florent37.camerafragment.internal.utils.CameraHelper; |
||||
import com.github.florent37.camerafragment.internal.utils.ImageSaver; |
||||
import com.github.florent37.camerafragment.internal.utils.Size; |
||||
|
||||
/** |
||||
* Created by memfis on 8/9/16. |
||||
*/ |
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public final class Camera2Manager extends BaseCameraManager<String, TextureView.SurfaceTextureListener> |
||||
implements ImageReader.OnImageAvailableListener, TextureView.SurfaceTextureListener { |
||||
|
||||
private final static String TAG = "Camera2Manager"; |
||||
|
||||
private static Camera2Manager currentInstance; |
||||
|
||||
private CameraOpenListener<String, TextureView.SurfaceTextureListener> cameraOpenListener; |
||||
private CameraPhotoListener cameraPhotoListener; |
||||
private CameraVideoListener cameraVideoListener; |
||||
|
||||
private File outputPath; |
||||
|
||||
@CameraPreviewState |
||||
private int previewState = STATE_PREVIEW; |
||||
private static final int STATE_PREVIEW = 0; |
||||
private static final int STATE_WAITING_LOCK = 1; |
||||
private static final int STATE_WAITING_PRE_CAPTURE = 2; |
||||
private static final int STATE_WAITING_NON_PRE_CAPTURE = 3; |
||||
private static final int STATE_PICTURE_TAKEN = 4; |
||||
|
||||
@IntDef({STATE_PREVIEW, STATE_WAITING_LOCK, STATE_WAITING_PRE_CAPTURE, STATE_WAITING_NON_PRE_CAPTURE, STATE_PICTURE_TAKEN}) |
||||
@Retention(RetentionPolicy.SOURCE) |
||||
@interface CameraPreviewState { |
||||
} |
||||
|
||||
private CameraManager manager; |
||||
private CameraDevice cameraDevice; |
||||
private CaptureRequest previewRequest; |
||||
private CaptureRequest.Builder previewRequestBuilder; |
||||
private CameraCaptureSession captureSession; |
||||
|
||||
private CameraCharacteristics frontCameraCharacteristics; |
||||
private CameraCharacteristics backCameraCharacteristics; |
||||
private StreamConfigurationMap frontCameraStreamConfigurationMap; |
||||
private StreamConfigurationMap backCameraStreamConfigurationMap; |
||||
|
||||
private SurfaceTexture texture; |
||||
|
||||
private Surface workingSurface; |
||||
private ImageReader imageReader; |
||||
|
||||
private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { |
||||
@Override |
||||
public void onOpened(CameraDevice cameraDevice) { |
||||
currentInstance.cameraDevice = cameraDevice; |
||||
if (cameraOpenListener != null) { |
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
if (!TextUtils.isEmpty(currentCameraId) && previewSize != null && currentInstance != null) |
||||
cameraOpenListener.onCameraOpened(currentCameraId, previewSize, currentInstance); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onDisconnected(CameraDevice cameraDevice) { |
||||
cameraDevice.close(); |
||||
currentInstance.cameraDevice = null; |
||||
|
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
cameraOpenListener.onCameraOpenError(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void onError(CameraDevice cameraDevice, int error) { |
||||
cameraDevice.close(); |
||||
currentInstance.cameraDevice = null; |
||||
|
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
cameraOpenListener.onCameraOpenError(); |
||||
} |
||||
}); |
||||
} |
||||
}; |
||||
|
||||
private CameraCaptureSession.CaptureCallback captureCallback |
||||
= new CameraCaptureSession.CaptureCallback() { |
||||
|
||||
@Override |
||||
public void onCaptureProgressed(@NonNull CameraCaptureSession session, |
||||
@NonNull CaptureRequest request, |
||||
@NonNull CaptureResult partialResult) { |
||||
processCaptureResult(partialResult); |
||||
} |
||||
|
||||
@Override |
||||
public void onCaptureCompleted(@NonNull CameraCaptureSession session, |
||||
@NonNull CaptureRequest request, |
||||
@NonNull TotalCaptureResult result) { |
||||
processCaptureResult(result); |
||||
} |
||||
|
||||
}; |
||||
|
||||
private Camera2Manager() { |
||||
} |
||||
|
||||
public static Camera2Manager getInstance() { |
||||
if (currentInstance == null) currentInstance = new Camera2Manager(); |
||||
return currentInstance; |
||||
} |
||||
|
||||
@Override |
||||
public void initializeCameraManager(ConfigurationProvider configurationProvider, Context context) { |
||||
super.initializeCameraManager(configurationProvider, context); |
||||
|
||||
this.manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); |
||||
|
||||
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); |
||||
Display display = windowManager.getDefaultDisplay(); |
||||
Point size = new Point(); |
||||
display.getSize(size); |
||||
windowSize = new Size(size.x, size.y); |
||||
|
||||
try { |
||||
String[] ids = manager.getCameraIdList(); |
||||
numberOfCameras = ids.length; |
||||
for (String id : ids) { |
||||
CameraCharacteristics characteristics = manager.getCameraCharacteristics(id); |
||||
|
||||
int orientation = characteristics.get(CameraCharacteristics.LENS_FACING); |
||||
if (orientation == CameraCharacteristics.LENS_FACING_FRONT) { |
||||
faceFrontCameraId = id; |
||||
faceFrontCameraOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); |
||||
frontCameraCharacteristics = characteristics; |
||||
} else { |
||||
faceBackCameraId = id; |
||||
faceBackCameraOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); |
||||
backCameraCharacteristics = characteristics; |
||||
} |
||||
} |
||||
} catch (Exception e) { |
||||
Log.e(TAG, "Error during camera init"); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void openCamera(String cameraId, final CameraOpenListener<String, TextureView.SurfaceTextureListener> cameraOpenListener) { |
||||
this.currentCameraId = cameraId; |
||||
this.cameraOpenListener = cameraOpenListener; |
||||
backgroundHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
if (context == null || configurationProvider == null) { |
||||
Log.e(TAG, "openCamera: "); |
||||
if (cameraOpenListener != null) { |
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
cameraOpenListener.onCameraOpenError(); |
||||
} |
||||
}); |
||||
} |
||||
return; |
||||
} |
||||
prepareCameraOutputs(); |
||||
try { |
||||
manager.openCamera(currentCameraId, stateCallback, backgroundHandler); |
||||
} catch (Exception e) { |
||||
Log.e(TAG, "openCamera: ", e); |
||||
if (cameraOpenListener != null) { |
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
cameraOpenListener.onCameraOpenError(); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void closeCamera(final CameraCloseListener<String> cameraCloseListener) { |
||||
backgroundHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
closeCamera(); |
||||
if (cameraCloseListener != null) { |
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
cameraCloseListener.onCameraClosed(currentCameraId); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void setFlashMode(@Configuration.FlashMode int flashMode) { |
||||
setFlashModeAndBuildPreviewRequest(flashMode); |
||||
} |
||||
|
||||
@Override |
||||
public void takePhoto(File photoFile, CameraPhotoListener cameraPhotoListener) { |
||||
this.outputPath = photoFile; |
||||
this.cameraPhotoListener = cameraPhotoListener; |
||||
|
||||
backgroundHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
lockFocus(); |
||||
} |
||||
}); |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public Size getPhotoSizeForQuality(@Configuration.MediaQuality int mediaQuality) { |
||||
StreamConfigurationMap map = currentCameraId.equals(faceBackCameraId) ? backCameraStreamConfigurationMap : frontCameraStreamConfigurationMap; |
||||
return CameraHelper.getPictureSize(Size.fromArray2(map.getOutputSizes(ImageFormat.JPEG)), mediaQuality); |
||||
} |
||||
|
||||
@Override |
||||
public void startVideoRecord(File videoFile, final CameraVideoListener cameraVideoListener) { |
||||
if (isVideoRecording || texture == null) return; |
||||
|
||||
this.outputPath = videoFile; |
||||
this.cameraVideoListener = cameraVideoListener; |
||||
|
||||
if (cameraVideoListener != null) |
||||
backgroundHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
closePreviewSession(); |
||||
if (prepareVideoRecorder()) { |
||||
|
||||
SurfaceTexture texture = currentInstance.texture; |
||||
texture.setDefaultBufferSize(videoSize.getWidth(), videoSize.getHeight()); |
||||
|
||||
try { |
||||
previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); |
||||
List<Surface> surfaces = new ArrayList<>(); |
||||
|
||||
Surface previewSurface = workingSurface; |
||||
surfaces.add(previewSurface); |
||||
previewRequestBuilder.addTarget(previewSurface); |
||||
|
||||
workingSurface = videoRecorder.getSurface(); |
||||
surfaces.add(workingSurface); |
||||
previewRequestBuilder.addTarget(workingSurface); |
||||
|
||||
cameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() { |
||||
@Override |
||||
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { |
||||
captureSession = cameraCaptureSession; |
||||
|
||||
previewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); |
||||
try { |
||||
captureSession.setRepeatingRequest(previewRequestBuilder.build(), null, backgroundHandler); |
||||
} catch (Exception e) { |
||||
} |
||||
|
||||
try { |
||||
videoRecorder.start(); |
||||
} catch (Exception ignore) { |
||||
Log.e(TAG, "videoRecorder.start(): ", ignore); |
||||
} |
||||
|
||||
isVideoRecording = true; |
||||
|
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
cameraVideoListener.onVideoRecordStarted(videoSize); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { |
||||
Log.d(TAG, "onConfigureFailed"); |
||||
} |
||||
}, backgroundHandler); |
||||
} catch (Exception e) { |
||||
Log.e(TAG, "startVideoRecord: ", e); |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void stopVideoRecord() { |
||||
if (isVideoRecording) |
||||
backgroundHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
closePreviewSession(); |
||||
|
||||
if (videoRecorder != null) { |
||||
try { |
||||
videoRecorder.stop(); |
||||
} catch (Exception ignore) { |
||||
} |
||||
} |
||||
isVideoRecording = false; |
||||
releaseVideoRecorder(); |
||||
|
||||
if (cameraVideoListener != null) { |
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
cameraVideoListener.onVideoRecordStopped(outputPath); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
//--------------------Internal methods------------------
|
||||
|
||||
private void startPreview(SurfaceTexture texture) { |
||||
try { |
||||
if (texture == null) return; |
||||
|
||||
this.texture = texture; |
||||
|
||||
texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); |
||||
|
||||
workingSurface = new Surface(texture); |
||||
|
||||
previewRequestBuilder |
||||
= cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); |
||||
previewRequestBuilder.addTarget(workingSurface); |
||||
|
||||
cameraDevice.createCaptureSession(Arrays.asList(workingSurface, imageReader.getSurface()), |
||||
new CameraCaptureSession.StateCallback() { |
||||
@Override |
||||
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { |
||||
updatePreview(cameraCaptureSession); |
||||
} |
||||
|
||||
@Override |
||||
public void onConfigureFailed( |
||||
@NonNull CameraCaptureSession cameraCaptureSession) { |
||||
Log.d(TAG, "Fail while starting preview: "); |
||||
} |
||||
}, null); |
||||
} catch (Exception e) { |
||||
Log.e(TAG, "Error while preparing surface for preview: ", e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void onMaxDurationReached() { |
||||
stopVideoRecord(); |
||||
} |
||||
|
||||
@Override |
||||
protected void onMaxFileSizeReached() { |
||||
stopVideoRecord(); |
||||
} |
||||
|
||||
@Override |
||||
protected int getPhotoOrientation(@Configuration.SensorPosition int sensorPosition) { |
||||
return getVideoOrientation(sensorPosition); |
||||
} |
||||
|
||||
@Override |
||||
protected int getVideoOrientation(@Configuration.SensorPosition int sensorPosition) { |
||||
int degrees = 0; |
||||
switch (sensorPosition) { |
||||
case Configuration.SENSOR_POSITION_UP: |
||||
degrees = 0; |
||||
break; // Natural orientation
|
||||
case Configuration.SENSOR_POSITION_LEFT: |
||||
degrees = 90; |
||||
break; // Landscape left
|
||||
case Configuration.SENSOR_POSITION_UP_SIDE_DOWN: |
||||
degrees = 180; |
||||
break;// Upside down
|
||||
case Configuration.SENSOR_POSITION_RIGHT: |
||||
degrees = 270; |
||||
break;// Landscape right
|
||||
case Configuration.SENSOR_POSITION_UNSPECIFIED: |
||||
break; |
||||
} |
||||
|
||||
int rotate; |
||||
if (Objects.equals(currentCameraId, faceFrontCameraId)) { |
||||
rotate = (360 + faceFrontCameraOrientation + degrees) % 360; |
||||
} else { |
||||
rotate = (360 + faceBackCameraOrientation - degrees) % 360; |
||||
} |
||||
return rotate; |
||||
} |
||||
|
||||
private void closeCamera() { |
||||
closePreviewSession(); |
||||
releaseTexture(); |
||||
closeCameraDevice(); |
||||
closeImageReader(); |
||||
releaseVideoRecorder(); |
||||
} |
||||
|
||||
private void releaseTexture() { |
||||
if (null != texture) { |
||||
texture.release(); |
||||
texture = null; |
||||
} |
||||
} |
||||
|
||||
private void closeImageReader() { |
||||
if (null != imageReader) { |
||||
imageReader.close(); |
||||
imageReader = null; |
||||
} |
||||
} |
||||
|
||||
private void closeCameraDevice() { |
||||
if (null != cameraDevice) { |
||||
cameraDevice.close(); |
||||
cameraDevice = null; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void prepareCameraOutputs() { |
||||
try { |
||||
CameraCharacteristics characteristics = currentCameraId.equals(faceBackCameraId) ? backCameraCharacteristics : frontCameraCharacteristics; |
||||
|
||||
if (currentCameraId.equals(faceFrontCameraId) && frontCameraStreamConfigurationMap == null) |
||||
frontCameraStreamConfigurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); |
||||
else if (currentCameraId.equals(faceBackCameraId) && backCameraStreamConfigurationMap == null) |
||||
backCameraStreamConfigurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); |
||||
|
||||
StreamConfigurationMap map = currentCameraId.equals(faceBackCameraId) ? backCameraStreamConfigurationMap : frontCameraStreamConfigurationMap; |
||||
if (configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_AUTO) { |
||||
camcorderProfile = CameraHelper.getCamcorderProfile(currentCameraId, configurationProvider.getVideoFileSize(), configurationProvider.getMinimumVideoDuration()); |
||||
} else |
||||
camcorderProfile = CameraHelper.getCamcorderProfile(configurationProvider.getMediaQuality(), currentCameraId); |
||||
|
||||
videoSize = CameraHelper.chooseOptimalSize(Size.fromArray2(map.getOutputSizes(MediaRecorder.class)), |
||||
windowSize.getWidth(), windowSize.getHeight(), new Size(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight)); |
||||
|
||||
if (videoSize == null || videoSize.getWidth() > camcorderProfile.videoFrameWidth |
||||
|| videoSize.getHeight() > camcorderProfile.videoFrameHeight) |
||||
videoSize = CameraHelper.getSizeWithClosestRatio(Size.fromArray2(map.getOutputSizes(MediaRecorder.class)), camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight); |
||||
else if (videoSize == null || videoSize.getWidth() > camcorderProfile.videoFrameWidth |
||||
|| videoSize.getHeight() > camcorderProfile.videoFrameHeight) |
||||
videoSize = CameraHelper.getSizeWithClosestRatio(Size.fromArray2(map.getOutputSizes(MediaRecorder.class)), camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight); |
||||
|
||||
photoSize = CameraHelper.getPictureSize(Size.fromArray2(map.getOutputSizes(ImageFormat.JPEG)), |
||||
configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_AUTO |
||||
? Configuration.MEDIA_QUALITY_HIGHEST : configurationProvider.getMediaQuality()); |
||||
|
||||
imageReader = ImageReader.newInstance(photoSize.getWidth(), photoSize.getHeight(), |
||||
ImageFormat.JPEG, 2); |
||||
imageReader.setOnImageAvailableListener(this, backgroundHandler); |
||||
|
||||
if (configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_PHOTO |
||||
|| configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_UNSPECIFIED) { |
||||
|
||||
if (windowSize.getHeight() * windowSize.getWidth() > photoSize.getWidth() * photoSize.getHeight()) { |
||||
previewSize = CameraHelper.getOptimalPreviewSize(Size.fromArray2(map.getOutputSizes(SurfaceTexture.class)), photoSize.getWidth(), photoSize.getHeight()); |
||||
} else { |
||||
previewSize = CameraHelper.getOptimalPreviewSize(Size.fromArray2(map.getOutputSizes(SurfaceTexture.class)), windowSize.getWidth(), windowSize.getHeight()); |
||||
} |
||||
|
||||
if (previewSize == null) |
||||
previewSize = CameraHelper.chooseOptimalSize(Size.fromArray2(map.getOutputSizes(SurfaceTexture.class)), windowSize.getWidth(), windowSize.getHeight(), photoSize); |
||||
|
||||
} else { |
||||
if (windowSize.getHeight() * windowSize.getWidth() > videoSize.getWidth() * videoSize.getHeight()) { |
||||
previewSize = CameraHelper.getOptimalPreviewSize(Size.fromArray2(map.getOutputSizes(SurfaceTexture.class)), videoSize.getWidth(), videoSize.getHeight()); |
||||
} else { |
||||
previewSize = CameraHelper.getOptimalPreviewSize(Size.fromArray2(map.getOutputSizes(SurfaceTexture.class)), windowSize.getWidth(), windowSize.getHeight()); |
||||
} |
||||
|
||||
if (previewSize == null) |
||||
previewSize = CameraHelper.getSizeWithClosestRatio(Size.fromArray2(map.getOutputSizes(SurfaceTexture.class)), videoSize.getWidth(), videoSize.getHeight()); |
||||
} |
||||
} catch (Exception e) { |
||||
Log.e(TAG, "Error while setup camera sizes.", e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected boolean prepareVideoRecorder() { |
||||
videoRecorder = new MediaRecorder(); |
||||
try { |
||||
videoRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); |
||||
videoRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); |
||||
|
||||
videoRecorder.setOutputFormat(camcorderProfile.fileFormat); |
||||
videoRecorder.setVideoFrameRate(camcorderProfile.videoFrameRate); |
||||
videoRecorder.setVideoSize(videoSize.getWidth(), videoSize.getHeight()); |
||||
videoRecorder.setVideoEncodingBitRate(camcorderProfile.videoBitRate); |
||||
videoRecorder.setVideoEncoder(camcorderProfile.videoCodec); |
||||
|
||||
videoRecorder.setAudioEncodingBitRate(camcorderProfile.audioBitRate); |
||||
videoRecorder.setAudioChannels(camcorderProfile.audioChannels); |
||||
videoRecorder.setAudioSamplingRate(camcorderProfile.audioSampleRate); |
||||
videoRecorder.setAudioEncoder(camcorderProfile.audioCodec); |
||||
|
||||
File outputFile = outputPath; |
||||
String outputFilePath = outputFile.toString(); |
||||
videoRecorder.setOutputFile(outputFilePath); |
||||
|
||||
if (configurationProvider.getVideoFileSize() > 0) { |
||||
videoRecorder.setMaxFileSize(configurationProvider.getVideoFileSize()); |
||||
videoRecorder.setOnInfoListener(this); |
||||
} |
||||
if (configurationProvider.getVideoDuration() > 0) { |
||||
videoRecorder.setMaxDuration(configurationProvider.getVideoDuration()); |
||||
videoRecorder.setOnInfoListener(this); |
||||
} |
||||
videoRecorder.setOrientationHint(getVideoOrientation(configurationProvider.getSensorPosition())); |
||||
|
||||
videoRecorder.prepare(); |
||||
|
||||
return true; |
||||
} catch (IllegalStateException error) { |
||||
Log.e(TAG, "IllegalStateException preparing MediaRecorder: " + error.getMessage()); |
||||
} catch (IOException error) { |
||||
Log.e(TAG, "IOException preparing MediaRecorder: " + error.getMessage()); |
||||
} catch (Throwable error) { |
||||
Log.e(TAG, "Error during preparing MediaRecorder: " + error.getMessage()); |
||||
} |
||||
|
||||
releaseVideoRecorder(); |
||||
return false; |
||||
} |
||||
|
||||
private void updatePreview(CameraCaptureSession cameraCaptureSession) { |
||||
if (null == cameraDevice) { |
||||
return; |
||||
} |
||||
captureSession = cameraCaptureSession; |
||||
|
||||
setFlashModeAndBuildPreviewRequest(configurationProvider.getFlashMode()); |
||||
} |
||||
|
||||
private void closePreviewSession() { |
||||
if (captureSession != null) { |
||||
captureSession.close(); |
||||
try { |
||||
captureSession.abortCaptures(); |
||||
} catch (Exception ignore) { |
||||
} finally { |
||||
captureSession = null; |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void lockFocus() { |
||||
try { |
||||
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START); |
||||
|
||||
previewState = STATE_WAITING_LOCK; |
||||
captureSession.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler); |
||||
} catch (Exception ignore) { |
||||
} |
||||
} |
||||
|
||||
private void processCaptureResult(CaptureResult result) { |
||||
switch (previewState) { |
||||
case STATE_PREVIEW: { |
||||
break; |
||||
} |
||||
case STATE_WAITING_LOCK: { |
||||
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); |
||||
if (afState == null) { |
||||
captureStillPicture(); |
||||
} else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState |
||||
|| CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState |
||||
|| CaptureResult.CONTROL_AF_STATE_INACTIVE == afState |
||||
|| CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN == afState) { |
||||
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); |
||||
if (aeState == null || |
||||
aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { |
||||
previewState = STATE_PICTURE_TAKEN; |
||||
captureStillPicture(); |
||||
} else { |
||||
runPreCaptureSequence(); |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
case STATE_WAITING_PRE_CAPTURE: { |
||||
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); |
||||
if (aeState == null || |
||||
aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || |
||||
aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { |
||||
previewState = STATE_WAITING_NON_PRE_CAPTURE; |
||||
} |
||||
break; |
||||
} |
||||
case STATE_WAITING_NON_PRE_CAPTURE: { |
||||
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); |
||||
if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { |
||||
previewState = STATE_PICTURE_TAKEN; |
||||
captureStillPicture(); |
||||
} |
||||
break; |
||||
} |
||||
case STATE_PICTURE_TAKEN: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
private void runPreCaptureSequence() { |
||||
try { |
||||
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); |
||||
previewState = STATE_WAITING_PRE_CAPTURE; |
||||
captureSession.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler); |
||||
} catch (CameraAccessException e) { |
||||
} |
||||
} |
||||
|
||||
private void captureStillPicture() { |
||||
try { |
||||
if (null == cameraDevice) { |
||||
return; |
||||
} |
||||
final CaptureRequest.Builder captureBuilder = |
||||
cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); |
||||
captureBuilder.addTarget(imageReader.getSurface()); |
||||
|
||||
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); |
||||
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getPhotoOrientation(configurationProvider.getSensorPosition())); |
||||
|
||||
CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() { |
||||
@Override |
||||
public void onCaptureCompleted(@NonNull CameraCaptureSession session, |
||||
@NonNull CaptureRequest request, |
||||
@NonNull TotalCaptureResult result) { |
||||
Log.d(TAG, "onCaptureCompleted: "); |
||||
} |
||||
}; |
||||
|
||||
captureSession.stopRepeating(); |
||||
captureSession.capture(captureBuilder.build(), CaptureCallback, null); |
||||
|
||||
} catch (CameraAccessException e) { |
||||
Log.e(TAG, "Error during capturing picture"); |
||||
} |
||||
} |
||||
|
||||
private void unlockFocus() { |
||||
try { |
||||
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); |
||||
captureSession.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler); |
||||
previewState = STATE_PREVIEW; |
||||
captureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler); |
||||
} catch (Exception e) { |
||||
Log.e(TAG, "Error during focus unlocking"); |
||||
} |
||||
} |
||||
|
||||
private void setFlashModeAndBuildPreviewRequest(@Configuration.FlashMode int flashMode) { |
||||
try { |
||||
|
||||
switch (flashMode) { |
||||
case Configuration.FLASH_MODE_AUTO: |
||||
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); |
||||
previewRequestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_SINGLE); |
||||
break; |
||||
case Configuration.FLASH_MODE_ON: |
||||
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); |
||||
previewRequestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_SINGLE); |
||||
break; |
||||
case Configuration.FLASH_MODE_OFF: |
||||
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); |
||||
previewRequestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF); |
||||
break; |
||||
default: |
||||
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); |
||||
previewRequestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_SINGLE); |
||||
break; |
||||
} |
||||
|
||||
previewRequest = previewRequestBuilder.build(); |
||||
|
||||
try { |
||||
captureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler); |
||||
} catch (Exception e) { |
||||
Log.e(TAG, "Error updating preview: ", e); |
||||
} |
||||
} catch (Exception ignore) { |
||||
Log.e(TAG, "Error setting flash: ", ignore); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onImageAvailable(ImageReader imageReader) { |
||||
File outputFile = outputPath; |
||||
backgroundHandler.post(new ImageSaver(imageReader.acquireNextImage(), outputFile, new ImageSaver.ImageSaverCallback() { |
||||
@Override |
||||
public void onSuccessFinish(final byte[] bytes) { |
||||
Log.d(TAG, "onPhotoSuccessFinish: "); |
||||
if (cameraPhotoListener != null) { |
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
cameraPhotoListener.onPhotoTaken(bytes, outputPath); |
||||
} |
||||
}); |
||||
} |
||||
unlockFocus(); |
||||
} |
||||
|
||||
@Override |
||||
public void onError() { |
||||
Log.d(TAG, "onPhotoError: "); |
||||
uiHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
cameraPhotoListener.onPhotoTakeError(); |
||||
} |
||||
}); |
||||
} |
||||
})); |
||||
} |
||||
|
||||
@Override |
||||
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { |
||||
if (surfaceTexture != null) startPreview(surfaceTexture); |
||||
} |
||||
|
||||
@Override |
||||
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { |
||||
if (surfaceTexture != null) startPreview(surfaceTexture); |
||||
} |
||||
|
||||
@Override |
||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence[] getVideoQualityOptions() { |
||||
List<CharSequence> videoQualities = new ArrayList<>(); |
||||
|
||||
if (configurationProvider.getMinimumVideoDuration() > 0) |
||||
videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_AUTO, CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_AUTO, getCurrentCameraId()), configurationProvider.getMinimumVideoDuration())); |
||||
|
||||
|
||||
CamcorderProfile camcorderProfile = CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_HIGH, currentCameraId); |
||||
double videoDuration = CameraHelper.calculateApproximateVideoDuration(camcorderProfile, configurationProvider.getVideoFileSize()); |
||||
videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_HIGH, camcorderProfile, videoDuration)); |
||||
|
||||
camcorderProfile = CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_MEDIUM, currentCameraId); |
||||
videoDuration = CameraHelper.calculateApproximateVideoDuration(camcorderProfile, configurationProvider.getVideoFileSize()); |
||||
videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_MEDIUM, camcorderProfile, videoDuration)); |
||||
|
||||
camcorderProfile = CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_LOW, currentCameraId); |
||||
videoDuration = CameraHelper.calculateApproximateVideoDuration(camcorderProfile, configurationProvider.getVideoFileSize()); |
||||
videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_LOW, camcorderProfile, videoDuration)); |
||||
|
||||
CharSequence[] array = new CharSequence[videoQualities.size()]; |
||||
videoQualities.toArray(array); |
||||
|
||||
return array; |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence[] getPhotoQualityOptions() { |
||||
List<CharSequence> photoQualities = new ArrayList<>(); |
||||
photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_HIGHEST, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_HIGHEST))); |
||||
photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_HIGH, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_HIGH))); |
||||
photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_MEDIUM, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_MEDIUM))); |
||||
photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_LOWEST, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_LOWEST))); |
||||
|
||||
CharSequence[] array = new CharSequence[photoQualities.size()]; |
||||
photoQualities.toArray(array); |
||||
|
||||
return array; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,8 @@ |
||||
package com.github.florent37.camerafragment.internal.manager.listener; |
||||
|
||||
/** |
||||
* Created by memfis on 8/14/16. |
||||
*/ |
||||
public interface CameraCloseListener<CameraId> { |
||||
void onCameraClosed(CameraId closedCameraId); |
||||
} |
@ -0,0 +1,12 @@ |
||||
package com.github.florent37.camerafragment.internal.manager.listener; |
||||
|
||||
import com.github.florent37.camerafragment.internal.utils.Size; |
||||
|
||||
/** |
||||
* Created by memfis on 8/14/16. |
||||
*/ |
||||
public interface CameraOpenListener<CameraId, SurfaceListener> { |
||||
void onCameraOpened(CameraId openedCameraId, Size previewSize, SurfaceListener surfaceListener); |
||||
|
||||
void onCameraOpenError(); |
||||
} |
@ -0,0 +1,12 @@ |
||||
package com.github.florent37.camerafragment.internal.manager.listener; |
||||
|
||||
import java.io.File; |
||||
|
||||
/** |
||||
* Created by memfis on 8/14/16. |
||||
*/ |
||||
public interface CameraPhotoListener { |
||||
void onPhotoTaken(byte[] bytes, File photoFile); |
||||
|
||||
void onPhotoTakeError(); |
||||
} |
@ -0,0 +1,16 @@ |
||||
package com.github.florent37.camerafragment.internal.manager.listener; |
||||
|
||||
import java.io.File; |
||||
|
||||
import com.github.florent37.camerafragment.internal.utils.Size; |
||||
|
||||
/** |
||||
* Created by memfis on 8/14/16. |
||||
*/ |
||||
public interface CameraVideoListener { |
||||
void onVideoRecordStarted(Size videoSize); |
||||
|
||||
void onVideoRecordStopped(File videoFile); |
||||
|
||||
void onVideoRecordError(); |
||||
} |
@ -0,0 +1,64 @@ |
||||
package com.github.florent37.camerafragment.internal.timer; |
||||
|
||||
import android.graphics.Color; |
||||
|
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
import com.github.florent37.camerafragment.internal.utils.DateTimeUtils; |
||||
|
||||
/** |
||||
* Created by florentchampigny on 13/01/2017. |
||||
*/ |
||||
|
||||
public class CountdownTask extends TimerTaskBase implements Runnable { |
||||
|
||||
private int maxDurationMilliseconds = 0; |
||||
|
||||
public CountdownTask(Callback callback, int maxDurationMilliseconds) { |
||||
super(callback); |
||||
this.maxDurationMilliseconds = maxDurationMilliseconds; |
||||
} |
||||
|
||||
@Override |
||||
public void run() { |
||||
|
||||
recordingTimeSeconds--; |
||||
|
||||
int millis = (int) recordingTimeSeconds * 1000; |
||||
|
||||
if (callback != null) { |
||||
callback.setText( |
||||
String.format("%02d:%02d", |
||||
TimeUnit.MILLISECONDS.toMinutes(millis), |
||||
TimeUnit.MILLISECONDS.toSeconds(millis) - |
||||
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)) |
||||
)); |
||||
} |
||||
|
||||
if (alive && recordingTimeSeconds > 0) handler.postDelayed(this, DateTimeUtils.SECOND); |
||||
} |
||||
|
||||
@Override |
||||
public void stop() { |
||||
if (callback != null) { |
||||
callback.setTextVisible(false); |
||||
} |
||||
alive = false; |
||||
} |
||||
|
||||
@Override |
||||
public void start() { |
||||
alive = true; |
||||
recordingTimeSeconds = maxDurationMilliseconds / 1000; |
||||
if (callback != null) { |
||||
callback.setText( |
||||
String.format("%02d:%02d", |
||||
TimeUnit.MILLISECONDS.toMinutes(maxDurationMilliseconds), |
||||
TimeUnit.MILLISECONDS.toSeconds(maxDurationMilliseconds) - |
||||
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(maxDurationMilliseconds)) |
||||
)); |
||||
callback.setTextVisible(true); |
||||
} |
||||
handler.postDelayed(this, DateTimeUtils.SECOND); |
||||
} |
||||
} |
@ -0,0 +1,48 @@ |
||||
package com.github.florent37.camerafragment.internal.timer; |
||||
|
||||
import com.github.florent37.camerafragment.internal.utils.DateTimeUtils; |
||||
|
||||
/** |
||||
* Created by florentchampigny on 13/01/2017. |
||||
*/ |
||||
|
||||
public class TimerTask extends TimerTaskBase implements Runnable { |
||||
|
||||
public TimerTask(TimerTaskBase.Callback callback) { |
||||
super(callback); |
||||
} |
||||
|
||||
@Override |
||||
public void run() { |
||||
recordingTimeSeconds++; |
||||
|
||||
if (recordingTimeSeconds == 60) { |
||||
recordingTimeSeconds = 0; |
||||
recordingTimeMinutes++; |
||||
} |
||||
if(callback != null) { |
||||
callback.setText( |
||||
String.format("%02d:%02d", recordingTimeMinutes, recordingTimeSeconds)); |
||||
} |
||||
if (alive) handler.postDelayed(this, DateTimeUtils.SECOND); |
||||
} |
||||
|
||||
public void start() { |
||||
alive = true; |
||||
recordingTimeMinutes = 0; |
||||
recordingTimeSeconds = 0; |
||||
if(callback != null) { |
||||
callback.setText( |
||||
String.format("%02d:%02d", recordingTimeMinutes, recordingTimeSeconds)); |
||||
callback.setTextVisible(true); |
||||
} |
||||
handler.postDelayed(this, DateTimeUtils.SECOND); |
||||
} |
||||
|
||||
public void stop() { |
||||
if(callback != null){ |
||||
callback.setTextVisible(false); |
||||
} |
||||
alive = false; |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
package com.github.florent37.camerafragment.internal.timer; |
||||
|
||||
import android.os.Handler; |
||||
import android.os.Looper; |
||||
|
||||
/** |
||||
* Created by florentchampigny on 13/01/2017. |
||||
*/ |
||||
|
||||
public abstract class TimerTaskBase { |
||||
|
||||
public interface Callback { |
||||
void setText(String text); |
||||
void setTextVisible(boolean visible); |
||||
} |
||||
|
||||
protected Handler handler = new Handler(Looper.getMainLooper()); |
||||
protected boolean alive = false; |
||||
protected long recordingTimeSeconds = 0; |
||||
protected long recordingTimeMinutes = 0; |
||||
|
||||
protected Callback callback; |
||||
|
||||
protected TimerTaskBase(Callback callback) { |
||||
this.callback = callback; |
||||
} |
||||
|
||||
public abstract void stop(); |
||||
|
||||
public abstract void start(); |
||||
} |
@ -0,0 +1,703 @@ |
||||
package com.github.florent37.camerafragment.internal.ui; |
||||
|
||||
import android.app.Activity; |
||||
import android.app.AlertDialog; |
||||
import android.content.Context; |
||||
import android.content.DialogInterface; |
||||
import android.hardware.Sensor; |
||||
import android.hardware.SensorEvent; |
||||
import android.hardware.SensorEventListener; |
||||
import android.hardware.SensorManager; |
||||
import android.media.MediaActionSound; |
||||
import android.os.Build; |
||||
import android.os.Bundle; |
||||
import android.os.FileObserver; |
||||
import android.os.Handler; |
||||
import android.os.Looper; |
||||
import android.support.annotation.Nullable; |
||||
import android.support.v4.app.Fragment; |
||||
import android.util.Log; |
||||
import android.view.LayoutInflater; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
import android.view.WindowManager; |
||||
|
||||
import com.github.florent37.camerafragment.R; |
||||
import com.github.florent37.camerafragment.configuration.Configuration; |
||||
import com.github.florent37.camerafragment.configuration.ConfigurationProvider; |
||||
import com.github.florent37.camerafragment.configuration.ConfigurationProviderImpl; |
||||
import com.github.florent37.camerafragment.internal.controller.CameraController; |
||||
import com.github.florent37.camerafragment.internal.controller.impl.Camera1Controller; |
||||
import com.github.florent37.camerafragment.internal.controller.impl.Camera2Controller; |
||||
import com.github.florent37.camerafragment.internal.controller.view.CameraView; |
||||
import com.github.florent37.camerafragment.internal.enums.Camera; |
||||
import com.github.florent37.camerafragment.internal.enums.Flash; |
||||
import com.github.florent37.camerafragment.internal.enums.MediaAction; |
||||
import com.github.florent37.camerafragment.internal.enums.Record; |
||||
import com.github.florent37.camerafragment.internal.timer.CountdownTask; |
||||
import com.github.florent37.camerafragment.internal.timer.TimerTask; |
||||
import com.github.florent37.camerafragment.internal.timer.TimerTaskBase; |
||||
import com.github.florent37.camerafragment.internal.ui.model.PhotoQualityOption; |
||||
import com.github.florent37.camerafragment.internal.ui.model.VideoQualityOption; |
||||
import com.github.florent37.camerafragment.internal.ui.view.AspectFrameLayout; |
||||
import com.github.florent37.camerafragment.internal.utils.CameraHelper; |
||||
import com.github.florent37.camerafragment.internal.utils.Size; |
||||
import com.github.florent37.camerafragment.internal.utils.Utils; |
||||
import com.github.florent37.camerafragment.listeners.CameraFragmentControlsListener; |
||||
import com.github.florent37.camerafragment.listeners.CameraFragmentResultListener; |
||||
import com.github.florent37.camerafragment.listeners.CameraFragmentStateListener; |
||||
import com.github.florent37.camerafragment.listeners.CameraFragmentVideoRecordTextListener; |
||||
|
||||
import java.io.File; |
||||
|
||||
/** |
||||
* Created by memfis on 12/1/16. |
||||
* Updated by Florent37 |
||||
*/ |
||||
|
||||
public abstract class BaseAnncaFragment<CameraId> extends Fragment { |
||||
|
||||
public static final String ARG_CONFIGURATION = "configuration"; |
||||
public static final int MIN_VERSION_ICECREAM = Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1; |
||||
|
||||
protected CharSequence[] videoQualities; |
||||
protected CharSequence[] photoQualities; |
||||
protected AspectFrameLayout previewContainer; |
||||
|
||||
protected ConfigurationProvider configurationProvider; |
||||
@Configuration.MediaQuality |
||||
protected int newQuality = -1; |
||||
private Configuration configuration; |
||||
private SensorManager sensorManager = null; |
||||
private CameraController cameraController; |
||||
private AlertDialog settingsDialog; |
||||
private CameraFragmentControlsListener cameraFragmentControlsListener; |
||||
private CameraFragmentVideoRecordTextListener cameraFragmentVideoRecordTextListener; |
||||
private CameraFragmentStateListener cameraFragmentStateListener; |
||||
@Flash.FlashMode |
||||
private int currentFlashMode = Flash.FLASH_AUTO; |
||||
|
||||
@Camera.CameraType |
||||
private int currentCameraType = Camera.CAMERA_TYPE_REAR; |
||||
|
||||
@MediaAction.MediaActionState |
||||
private int currentMediaActionState = MediaAction.ACTION_PHOTO; |
||||
|
||||
@Record.RecordState |
||||
private int currentRecordState = Record.TAKE_PHOTO_STATE; |
||||
private String mediaFilePath; |
||||
private FileObserver fileObserver; |
||||
private long maxVideoFileSize = 0; |
||||
private TimerTaskBase countDownTimer; |
||||
private SensorEventListener sensorEventListener = new SensorEventListener() { |
||||
@Override |
||||
public void onSensorChanged(SensorEvent sensorEvent) { |
||||
synchronized (this) { |
||||
if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { |
||||
if (sensorEvent.values[0] < 4 && sensorEvent.values[0] > -4) { |
||||
if (sensorEvent.values[1] > 0) { |
||||
// UP
|
||||
configurationProvider.setSensorPosition(Configuration.SENSOR_POSITION_UP); |
||||
configurationProvider.setDegrees(configurationProvider.getDeviceDefaultOrientation() == Configuration.ORIENTATION_PORTRAIT ? 0 : 90); |
||||
} else if (sensorEvent.values[1] < 0) { |
||||
// UP SIDE DOWN
|
||||
configurationProvider.setSensorPosition(Configuration.SENSOR_POSITION_UP_SIDE_DOWN); |
||||
configurationProvider.setDegrees(configurationProvider.getDeviceDefaultOrientation() == Configuration.ORIENTATION_PORTRAIT ? 180 : 270); |
||||
} |
||||
} else if (sensorEvent.values[1] < 4 && sensorEvent.values[1] > -4) { |
||||
if (sensorEvent.values[0] > 0) { |
||||
// LEFT
|
||||
configurationProvider.setSensorPosition(Configuration.SENSOR_POSITION_LEFT); |
||||
configurationProvider.setDegrees(configurationProvider.getDeviceDefaultOrientation() == Configuration.ORIENTATION_PORTRAIT ? 90 : 180); |
||||
} else if (sensorEvent.values[0] < 0) { |
||||
// RIGHT
|
||||
configurationProvider.setSensorPosition(Configuration.SENSOR_POSITION_RIGHT); |
||||
configurationProvider.setDegrees(configurationProvider.getDeviceDefaultOrientation() == Configuration.ORIENTATION_PORTRAIT ? 270 : 0); |
||||
} |
||||
} |
||||
onScreenRotation(configurationProvider.getDegrees()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onAccuracyChanged(Sensor sensor, int i) { |
||||
|
||||
} |
||||
}; |
||||
private CameraFragmentResultListener cameraFragmentResultListener; |
||||
|
||||
protected static Fragment newInstance(Fragment fragment, Configuration configuration) { |
||||
Bundle args = new Bundle(); |
||||
args.putSerializable(ARG_CONFIGURATION, configuration); |
||||
fragment.setArguments(args); |
||||
return fragment; |
||||
} |
||||
|
||||
@Nullable |
||||
@Override |
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { |
||||
View decorView = ((Activity) container.getContext()).getWindow().getDecorView(); |
||||
if (Build.VERSION.SDK_INT > MIN_VERSION_ICECREAM) { |
||||
int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; |
||||
decorView.setSystemUiVisibility(uiOptions); |
||||
} |
||||
|
||||
return inflater.inflate(R.layout.generic_camera_layout, container, false); |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate(@Nullable Bundle savedInstanceState) { |
||||
super.onCreate(savedInstanceState); |
||||
final Bundle arguments = getArguments(); |
||||
if (arguments != null) { |
||||
configuration = (Configuration) arguments.getSerializable(ARG_CONFIGURATION); |
||||
} |
||||
this.configurationProvider = new ConfigurationProviderImpl(); |
||||
this.configurationProvider.setupWithAnnaConfiguration(configuration); |
||||
|
||||
//onProcessBundle
|
||||
currentMediaActionState = configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_VIDEO ? |
||||
MediaAction.ACTION_VIDEO : MediaAction.ACTION_PHOTO; |
||||
} |
||||
|
||||
@Override |
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { |
||||
super.onViewCreated(view, savedInstanceState); |
||||
previewContainer = (AspectFrameLayout) view.findViewById(R.id.previewContainer); |
||||
|
||||
final CameraView cameraView = new CameraView() { |
||||
|
||||
@Override |
||||
public void updateCameraPreview(Size size, View cameraPreview) { |
||||
if (cameraFragmentControlsListener != null) { |
||||
cameraFragmentControlsListener.unLockControls(); |
||||
cameraFragmentControlsListener.allowRecord(true); |
||||
} |
||||
|
||||
setCameraPreview(cameraPreview, size); |
||||
} |
||||
|
||||
@Override |
||||
public void updateUiForMediaAction(@Configuration.MediaAction int mediaAction) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void updateCameraSwitcher(int numberOfCameras) { |
||||
if (cameraFragmentControlsListener != null) { |
||||
cameraFragmentControlsListener.allowCameraSwitching(numberOfCameras > 1); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onPhotoTaken(byte[] bytes) { |
||||
if (cameraFragmentResultListener != null) { |
||||
final String filePath = getCameraController().getOutputFile().toString(); |
||||
cameraFragmentResultListener.onPhotoTaken(bytes, filePath); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onVideoRecordStart(int width, int height) { |
||||
final File outputFile = getCameraController().getOutputFile(); |
||||
onStartVideoRecord(outputFile); |
||||
} |
||||
|
||||
@Override |
||||
public void onVideoRecordStop() { |
||||
BaseAnncaFragment.this.onStopVideoRecord(); |
||||
} |
||||
|
||||
@Override |
||||
public void releaseCameraPreview() { |
||||
clearCameraPreview(); |
||||
} |
||||
}; |
||||
|
||||
if (CameraHelper.hasCamera2(getContext())) { |
||||
cameraController = new Camera2Controller(getActivity(), cameraView, configurationProvider); |
||||
} else { |
||||
cameraController = new Camera1Controller(getActivity(), cameraView, configurationProvider); |
||||
} |
||||
cameraController.onCreate(savedInstanceState); |
||||
|
||||
sensorManager = (SensorManager) getContext().getSystemService(Activity.SENSOR_SERVICE); |
||||
final int defaultOrientation = Utils.getDeviceDefaultOrientation(getContext()); |
||||
switch (defaultOrientation) { |
||||
case android.content.res.Configuration.ORIENTATION_LANDSCAPE: |
||||
configurationProvider.setDeviceDefaultOrientation(Configuration.ORIENTATION_LANDSCAPE); |
||||
break; |
||||
default: |
||||
configurationProvider.setDeviceDefaultOrientation(Configuration.ORIENTATION_PORTRAIT); |
||||
break; |
||||
} |
||||
|
||||
switch (configurationProvider.getFlashMode()) { |
||||
case Configuration.FLASH_MODE_AUTO: |
||||
setFlashMode(Flash.FLASH_AUTO); |
||||
break; |
||||
case Configuration.FLASH_MODE_ON: |
||||
setFlashMode(Flash.FLASH_ON); |
||||
break; |
||||
case Configuration.FLASH_MODE_OFF: |
||||
setFlashMode(Flash.FLASH_OFF); |
||||
break; |
||||
} |
||||
|
||||
if (cameraFragmentControlsListener != null) { |
||||
setMaxVideoDuration(configurationProvider.getVideoDuration()); |
||||
setMaxVideoFileSize(configurationProvider.getVideoFileSize()); |
||||
} |
||||
|
||||
} |
||||
|
||||
public void takePhotoOrCaptureVideo() { |
||||
switch (currentMediaActionState) { |
||||
case MediaAction.ACTION_PHOTO: |
||||
takePhoto(); |
||||
break; |
||||
case MediaAction.ACTION_VIDEO: |
||||
switch (currentRecordState) { |
||||
case Record.RECORD_IN_PROGRESS_STATE: |
||||
stopRecording(); |
||||
break; |
||||
default: |
||||
startRecording(); |
||||
break; |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onResume() { |
||||
super.onResume(); |
||||
|
||||
cameraController.onResume(); |
||||
sensorManager.registerListener(sensorEventListener, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL); |
||||
|
||||
if (cameraFragmentControlsListener != null) { |
||||
cameraFragmentControlsListener.lockControls(); |
||||
cameraFragmentControlsListener.allowRecord(false); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onPause() { |
||||
super.onPause(); |
||||
|
||||
cameraController.onPause(); |
||||
sensorManager.unregisterListener(sensorEventListener); |
||||
|
||||
if (cameraFragmentControlsListener != null) { |
||||
cameraFragmentControlsListener.lockControls(); |
||||
cameraFragmentControlsListener.allowRecord(false); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
|
||||
cameraController.onDestroy(); |
||||
} |
||||
|
||||
public void setMaxVideoFileSize(long maxVideoFileSize) { |
||||
this.maxVideoFileSize = maxVideoFileSize; |
||||
} |
||||
|
||||
public void setMaxVideoDuration(int maxVideoDurationInMillis) { |
||||
final TimerTaskBase.Callback callback = new TimerTaskBase.Callback() { |
||||
@Override |
||||
public void setText(String text) { |
||||
if (cameraFragmentVideoRecordTextListener != null) { |
||||
cameraFragmentVideoRecordTextListener.setRecordDurationText(text); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void setTextVisible(boolean visible) { |
||||
if (cameraFragmentVideoRecordTextListener != null) { |
||||
cameraFragmentVideoRecordTextListener.setRecordDurationTextVisible(visible); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
if (maxVideoDurationInMillis > 0) { |
||||
this.countDownTimer = new CountdownTask(callback, maxVideoDurationInMillis); |
||||
} else { |
||||
this.countDownTimer = new TimerTask(callback); |
||||
} |
||||
} |
||||
|
||||
public void openSettingDialog() { |
||||
final Context context = getContext(); |
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context); |
||||
|
||||
if (currentMediaActionState == MediaAction.ACTION_VIDEO) { |
||||
builder.setSingleChoiceItems(videoQualities, getVideoOptionCheckedIndex(), getVideoOptionSelectedListener()); |
||||
if (configurationProvider.getVideoFileSize() > 0) |
||||
builder.setTitle(String.format(getString(R.string.settings_video_quality_title), |
||||
"(Max " + String.valueOf(configurationProvider.getVideoFileSize() / (1024 * 1024) + " MB)"))); |
||||
else |
||||
builder.setTitle(String.format(getString(R.string.settings_video_quality_title), "")); |
||||
} else { |
||||
builder.setSingleChoiceItems(photoQualities, getPhotoOptionCheckedIndex(), getPhotoOptionSelectedListener()); |
||||
builder.setTitle(R.string.settings_photo_quality_title); |
||||
} |
||||
|
||||
builder.setPositiveButton(R.string.ok_label, new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialogInterface, int i) { |
||||
if (newQuality > 0 && newQuality != configurationProvider.getMediaQuality()) { |
||||
configurationProvider.setMediaQuality(newQuality); |
||||
dialogInterface.dismiss(); |
||||
if (cameraFragmentControlsListener != null) { |
||||
cameraFragmentControlsListener.lockControls(); |
||||
} |
||||
getCameraController().switchQuality(); |
||||
} |
||||
} |
||||
}); |
||||
builder.setNegativeButton(R.string.cancel_label, new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialogInterface, int i) { |
||||
dialogInterface.dismiss(); |
||||
} |
||||
}); |
||||
settingsDialog = builder.create(); |
||||
settingsDialog.show(); |
||||
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); |
||||
layoutParams.copyFrom(settingsDialog.getWindow().getAttributes()); |
||||
layoutParams.width = Utils.convertDipToPixels(context, 350); |
||||
layoutParams.height = Utils.convertDipToPixels(context, 350); |
||||
settingsDialog.getWindow().setAttributes(layoutParams); |
||||
} |
||||
|
||||
public void switchCameraType() { |
||||
if (cameraFragmentControlsListener != null) { |
||||
cameraFragmentControlsListener.lockControls(); |
||||
cameraFragmentControlsListener.allowRecord(false); |
||||
} |
||||
|
||||
int cameraFace = Configuration.CAMERA_FACE_REAR; |
||||
switch (currentCameraType) { |
||||
case Camera.CAMERA_TYPE_FRONT: |
||||
currentCameraType = Camera.CAMERA_TYPE_REAR; |
||||
cameraFace = Configuration.CAMERA_FACE_REAR; |
||||
if (cameraFragmentStateListener != null) { |
||||
cameraFragmentStateListener.onCurrentCameraBack(); |
||||
} |
||||
break; |
||||
case Camera.CAMERA_TYPE_REAR: |
||||
currentCameraType = Camera.CAMERA_TYPE_FRONT; |
||||
cameraFace = Configuration.CAMERA_FACE_FRONT; |
||||
if (cameraFragmentStateListener != null) { |
||||
cameraFragmentStateListener.onCurrentCameraFront(); |
||||
} |
||||
break; |
||||
} |
||||
|
||||
getCameraController().switchCamera(cameraFace); |
||||
} |
||||
|
||||
public void switchAction() { |
||||
switch (currentMediaActionState) { |
||||
case MediaAction.ACTION_PHOTO: |
||||
currentMediaActionState = MediaAction.ACTION_VIDEO; |
||||
if (cameraFragmentStateListener != null) { |
||||
cameraFragmentStateListener.onCameraSetupForVideo(); |
||||
} |
||||
break; |
||||
case MediaAction.ACTION_VIDEO: |
||||
currentMediaActionState = MediaAction.ACTION_PHOTO; |
||||
if (cameraFragmentStateListener != null) { |
||||
cameraFragmentStateListener.onCameraSetupForPhoto(); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
|
||||
public void toggleFlashMode() { |
||||
switch (currentFlashMode) { |
||||
case Flash.FLASH_AUTO: |
||||
currentFlashMode = Flash.FLASH_OFF; |
||||
break; |
||||
case Flash.FLASH_OFF: |
||||
currentFlashMode = Flash.FLASH_ON; |
||||
break; |
||||
case Flash.FLASH_ON: |
||||
currentFlashMode = Flash.FLASH_AUTO; |
||||
break; |
||||
} |
||||
onFlashModeChanged(); |
||||
} |
||||
|
||||
private void onFlashModeChanged() { |
||||
switch (currentFlashMode) { |
||||
case Flash.FLASH_AUTO: |
||||
if (cameraFragmentStateListener != null) cameraFragmentStateListener.onFlashAuto(); |
||||
configurationProvider.setFlashMode(Configuration.FLASH_MODE_AUTO); |
||||
getCameraController().setFlashMode(Configuration.FLASH_MODE_AUTO); |
||||
break; |
||||
case Flash.FLASH_ON: |
||||
if (cameraFragmentStateListener != null) cameraFragmentStateListener.onFlashOn(); |
||||
configurationProvider.setFlashMode(Configuration.FLASH_MODE_ON); |
||||
getCameraController().setFlashMode(Configuration.FLASH_MODE_ON); |
||||
break; |
||||
case Flash.FLASH_OFF: |
||||
if (cameraFragmentStateListener != null) cameraFragmentStateListener.onFlashOff(); |
||||
configurationProvider.setFlashMode(Configuration.FLASH_MODE_OFF); |
||||
getCameraController().setFlashMode(Configuration.FLASH_MODE_OFF); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
protected void onScreenRotation(int degrees) { |
||||
if (cameraFragmentStateListener != null) { |
||||
cameraFragmentStateListener.shouldRotateControls(degrees); |
||||
} |
||||
rotateSettingsDialog(degrees); |
||||
} |
||||
|
||||
@Record.RecordState |
||||
public int getRecordState() { |
||||
return currentRecordState; |
||||
} |
||||
|
||||
public void setRecordState(@Record.RecordState int recordState) { |
||||
this.currentRecordState = recordState; |
||||
} |
||||
|
||||
//@Override
|
||||
//public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// if (resultCode == Activity.RESULT_OK) {
|
||||
// if (requestCode == REQUEST_PREVIEW_CODE) {
|
||||
// final FragmentActivity activity = getActivity();
|
||||
// if (activity != null) {
|
||||
// if (PreviewActivity.isResultConfirm(data)) {
|
||||
// Intent resultIntent = new Intent();
|
||||
// resultIntent.putExtra(Configuration.Arguments.FILE_PATH,
|
||||
// PreviewActivity.getMediaFilePatch(data));
|
||||
// activity.setResult(Activity.RESULT_OK, resultIntent);
|
||||
// activity.finish();
|
||||
// } else if (PreviewActivity.isResultCancel(data)) {
|
||||
// activity.setResult(Activity.RESULT_CANCELED);
|
||||
// activity.finish();
|
||||
// } else if (PreviewActivity.isResultRetake(data)) {
|
||||
// //ignore, just proceed the camera
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
public void setFlashMode(@Flash.FlashMode int mode) { |
||||
this.currentFlashMode = mode; |
||||
onFlashModeChanged(); |
||||
} |
||||
|
||||
private void rotateSettingsDialog(int degrees) { |
||||
if (settingsDialog != null && settingsDialog.isShowing() && Build.VERSION.SDK_INT > 10) { |
||||
ViewGroup dialogView = (ViewGroup) settingsDialog.getWindow().getDecorView(); |
||||
for (int i = 0; i < dialogView.getChildCount(); i++) { |
||||
dialogView.getChildAt(i).setRotation(degrees); |
||||
} |
||||
} |
||||
} |
||||
|
||||
protected int getVideoOptionCheckedIndex() { |
||||
int checkedIndex = -1; |
||||
|
||||
final int mediaQuality = configurationProvider.getMediaQuality(); |
||||
final int passedMediaQuality = configurationProvider.getPassedMediaQuality(); |
||||
|
||||
if (mediaQuality == Configuration.MEDIA_QUALITY_AUTO) checkedIndex = 0; |
||||
else if (mediaQuality == Configuration.MEDIA_QUALITY_HIGH) checkedIndex = 1; |
||||
else if (mediaQuality == Configuration.MEDIA_QUALITY_MEDIUM) checkedIndex = 2; |
||||
else if (mediaQuality == Configuration.MEDIA_QUALITY_LOW) checkedIndex = 3; |
||||
|
||||
if (passedMediaQuality != Configuration.MEDIA_QUALITY_AUTO) checkedIndex--; |
||||
|
||||
return checkedIndex; |
||||
} |
||||
|
||||
protected int getPhotoOptionCheckedIndex() { |
||||
int checkedIndex = -1; |
||||
|
||||
final int mediaQuality = configurationProvider.getMediaQuality(); |
||||
|
||||
if (mediaQuality == Configuration.MEDIA_QUALITY_HIGHEST) checkedIndex = 0; |
||||
else if (mediaQuality == Configuration.MEDIA_QUALITY_HIGH) checkedIndex = 1; |
||||
else if (mediaQuality == Configuration.MEDIA_QUALITY_MEDIUM) checkedIndex = 2; |
||||
else if (mediaQuality == Configuration.MEDIA_QUALITY_LOWEST) checkedIndex = 3; |
||||
return checkedIndex; |
||||
} |
||||
|
||||
protected DialogInterface.OnClickListener getVideoOptionSelectedListener() { |
||||
return new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialogInterface, int index) { |
||||
newQuality = ((VideoQualityOption) videoQualities[index]).getMediaQuality(); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
protected DialogInterface.OnClickListener getPhotoOptionSelectedListener() { |
||||
return new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialogInterface, int index) { |
||||
newQuality = ((PhotoQualityOption) photoQualities[index]).getMediaQuality(); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
public void takePhoto() { |
||||
if (Build.VERSION.SDK_INT > MIN_VERSION_ICECREAM) { |
||||
new MediaActionSound().play(MediaActionSound.SHUTTER_CLICK); |
||||
} |
||||
setRecordState(Record.TAKE_PHOTO_STATE); |
||||
getCameraController().takePhoto(); |
||||
if (cameraFragmentStateListener != null) { |
||||
cameraFragmentStateListener.onRecordStatePhoto(); |
||||
} |
||||
} |
||||
|
||||
public void startRecording() { |
||||
if (Build.VERSION.SDK_INT > MIN_VERSION_ICECREAM) { |
||||
new MediaActionSound().play(MediaActionSound.START_VIDEO_RECORDING); |
||||
} |
||||
|
||||
setRecordState(Record.RECORD_IN_PROGRESS_STATE); |
||||
getCameraController().startVideoRecord(); |
||||
|
||||
if (cameraFragmentStateListener != null) { |
||||
cameraFragmentStateListener.onRecordStateVideoInProgress(); |
||||
} |
||||
} |
||||
|
||||
public void stopRecording() { |
||||
if (Build.VERSION.SDK_INT > MIN_VERSION_ICECREAM) { |
||||
new MediaActionSound().play(MediaActionSound.STOP_VIDEO_RECORDING); |
||||
} |
||||
|
||||
setRecordState(Record.READY_FOR_RECORD_STATE); |
||||
getCameraController().stopVideoRecord(); |
||||
|
||||
this.onStopVideoRecord(); |
||||
|
||||
if (cameraFragmentStateListener != null) { |
||||
cameraFragmentStateListener.onRecordStateVideoReadyForRecord(); |
||||
} |
||||
} |
||||
|
||||
public CameraController<CameraId> getCameraController() { |
||||
return cameraController; |
||||
} |
||||
|
||||
public void clearCameraPreview() { |
||||
if (previewContainer != null) |
||||
previewContainer.removeAllViews(); |
||||
} |
||||
|
||||
public void setCameraPreview(View preview, Size previewSize) { |
||||
//onCameraControllerReady()
|
||||
videoQualities = cameraController.getVideoQualityOptions(); |
||||
photoQualities = cameraController.getPhotoQualityOptions(); |
||||
|
||||
if (previewContainer == null || preview == null) return; |
||||
previewContainer.removeAllViews(); |
||||
previewContainer.addView(preview); |
||||
|
||||
previewContainer.setAspectRatio(previewSize.getHeight() / (double) previewSize.getWidth()); |
||||
} |
||||
|
||||
public void setMediaFilePath(final File mediaFile) { |
||||
this.mediaFilePath = mediaFile.toString(); |
||||
} |
||||
|
||||
public void onStartVideoRecord(final File mediaFile) { |
||||
setMediaFilePath(mediaFile); |
||||
if (maxVideoFileSize > 0) { |
||||
|
||||
if (cameraFragmentVideoRecordTextListener != null) { |
||||
cameraFragmentVideoRecordTextListener.setRecordSizeText(maxVideoFileSize, "1Mb" + " / " + maxVideoFileSize / (1024 * 1024) + "Mb"); |
||||
cameraFragmentVideoRecordTextListener.setRecordSizeTextVisible(true); |
||||
} |
||||
try { |
||||
fileObserver = new FileObserver(this.mediaFilePath) { |
||||
private long lastUpdateSize = 0; |
||||
|
||||
@Override |
||||
public void onEvent(int event, String path) { |
||||
final long fileSize = mediaFile.length() / (1024 * 1024); |
||||
if ((fileSize - lastUpdateSize) >= 1) { |
||||
lastUpdateSize = fileSize; |
||||
new Handler(Looper.getMainLooper()).post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
if (cameraFragmentVideoRecordTextListener != null) { |
||||
cameraFragmentVideoRecordTextListener.setRecordSizeText(maxVideoFileSize, fileSize + "Mb" + " / " + maxVideoFileSize / (1024 * 1024) + "Mb"); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
fileObserver.startWatching(); |
||||
} catch (Exception e) { |
||||
Log.e("FileObserver", "setMediaFilePath: ", e); |
||||
} |
||||
} |
||||
countDownTimer.start(); |
||||
|
||||
if (cameraFragmentStateListener != null) { |
||||
cameraFragmentStateListener.onStartVideoRecord(mediaFile); |
||||
} |
||||
} |
||||
|
||||
public void onStopVideoRecord() { |
||||
if (cameraFragmentControlsListener != null) { |
||||
cameraFragmentControlsListener.allowRecord(false); |
||||
} |
||||
if (cameraFragmentStateListener != null) { |
||||
cameraFragmentStateListener.onStopVideoRecord(); |
||||
} |
||||
setRecordState(Record.READY_FOR_RECORD_STATE); |
||||
|
||||
if (fileObserver != null) |
||||
fileObserver.stopWatching(); |
||||
countDownTimer.stop(); |
||||
|
||||
final int mediaAction = configurationProvider.getMediaAction(); |
||||
if (cameraFragmentControlsListener != null) { |
||||
if (mediaAction != Configuration.MEDIA_ACTION_UNSPECIFIED) { |
||||
cameraFragmentControlsListener.setMediaActionSwitchVisible(false); |
||||
} else { |
||||
cameraFragmentControlsListener.setMediaActionSwitchVisible(true); |
||||
} |
||||
} |
||||
|
||||
if (cameraFragmentResultListener != null) { |
||||
final String filePath = getCameraController().getOutputFile().toString(); |
||||
cameraFragmentResultListener.onVideoRecorded(filePath); |
||||
} |
||||
} |
||||
|
||||
public void setStateListener(CameraFragmentStateListener cameraFragmentStateListener) { |
||||
this.cameraFragmentStateListener = cameraFragmentStateListener; |
||||
} |
||||
|
||||
public void setTextListener(CameraFragmentVideoRecordTextListener cameraFragmentVideoRecordTextListener) { |
||||
this.cameraFragmentVideoRecordTextListener = cameraFragmentVideoRecordTextListener; |
||||
} |
||||
|
||||
public void setControlsListener(CameraFragmentControlsListener cameraFragmentControlsListener) { |
||||
this.cameraFragmentControlsListener = cameraFragmentControlsListener; |
||||
} |
||||
|
||||
public void setResultListener(CameraFragmentResultListener cameraFragmentResultListener) { |
||||
this.cameraFragmentResultListener = cameraFragmentResultListener; |
||||
} |
||||
} |
@ -0,0 +1,46 @@ |
||||
package com.github.florent37.camerafragment.internal.ui.model; |
||||
|
||||
import com.github.florent37.camerafragment.configuration.Configuration; |
||||
import com.github.florent37.camerafragment.internal.utils.Size; |
||||
|
||||
/** |
||||
* Created by memfis on 12/1/16. |
||||
*/ |
||||
|
||||
public class PhotoQualityOption implements CharSequence { |
||||
|
||||
@Configuration.MediaQuality |
||||
private int mediaQuality; |
||||
private String title; |
||||
|
||||
public PhotoQualityOption(@Configuration.MediaQuality int mediaQuality, Size size) { |
||||
this.mediaQuality = mediaQuality; |
||||
|
||||
title = String.valueOf(size.getWidth()) + " x " + String.valueOf(size.getHeight()); |
||||
} |
||||
|
||||
@Configuration.MediaQuality |
||||
public int getMediaQuality() { |
||||
return mediaQuality; |
||||
} |
||||
|
||||
@Override |
||||
public int length() { |
||||
return title.length(); |
||||
} |
||||
|
||||
@Override |
||||
public char charAt(int index) { |
||||
return title.charAt(index); |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence subSequence(int start, int end) { |
||||
return title.subSequence(start, end); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return title; |
||||
} |
||||
} |
@ -0,0 +1,59 @@ |
||||
package com.github.florent37.camerafragment.internal.ui.model; |
||||
|
||||
import android.media.CamcorderProfile; |
||||
|
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
import com.github.florent37.camerafragment.configuration.Configuration; |
||||
|
||||
/** |
||||
* Created by memfis on 12/1/16. |
||||
*/ |
||||
|
||||
public class VideoQualityOption implements CharSequence { |
||||
|
||||
private String title; |
||||
|
||||
@Configuration.MediaQuality |
||||
private int mediaQuality; |
||||
|
||||
public VideoQualityOption(@Configuration.MediaQuality int mediaQuality, CamcorderProfile camcorderProfile, double videoDuration) { |
||||
this.mediaQuality = mediaQuality; |
||||
|
||||
long minutes = TimeUnit.SECONDS.toMinutes((long) videoDuration); |
||||
long seconds = ((long) videoDuration) - minutes * 60; |
||||
|
||||
if (mediaQuality == Configuration.MEDIA_QUALITY_AUTO) { |
||||
title = "Auto " + ", (" + (minutes > 10 ? minutes : ("0" + minutes)) + ":" + (seconds > 10 ? seconds : ("0" + seconds)) + " min)"; |
||||
} else { |
||||
title = String.valueOf(camcorderProfile.videoFrameWidth) |
||||
+ " x " + String.valueOf(camcorderProfile.videoFrameHeight) |
||||
+ (videoDuration <= 0 ? "" : ", (" + (minutes > 10 ? minutes : ("0" + minutes)) + ":" + (seconds > 10 ? seconds : ("0" + seconds)) + " min)"); |
||||
} |
||||
} |
||||
|
||||
@Configuration.MediaQuality |
||||
public int getMediaQuality() { |
||||
return mediaQuality; |
||||
} |
||||
|
||||
@Override |
||||
public int length() { |
||||
return title.length(); |
||||
} |
||||
|
||||
@Override |
||||
public char charAt(int index) { |
||||
return title.charAt(index); |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence subSequence(int start, int end) { |
||||
return title.subSequence(start, end); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return title; |
||||
} |
||||
} |
@ -0,0 +1,67 @@ |
||||
package com.github.florent37.camerafragment.internal.ui.view; |
||||
|
||||
import android.content.Context; |
||||
import android.util.AttributeSet; |
||||
import android.widget.FrameLayout; |
||||
|
||||
/** |
||||
* Layout that adjusts to maintain a specific aspect ratio. |
||||
*/ |
||||
public class AspectFrameLayout extends FrameLayout { |
||||
|
||||
private static final String TAG = "AspectFrameLayout"; |
||||
|
||||
private double targetAspectRatio = -1.0; // initially use default window size
|
||||
|
||||
public AspectFrameLayout(Context context) { |
||||
super(context); |
||||
} |
||||
|
||||
public AspectFrameLayout(Context context, AttributeSet attrs) { |
||||
super(context, attrs); |
||||
} |
||||
|
||||
public void setAspectRatio(double aspectRatio) { |
||||
if (aspectRatio < 0) { |
||||
throw new IllegalArgumentException(); |
||||
} |
||||
|
||||
if (targetAspectRatio != aspectRatio) { |
||||
targetAspectRatio = aspectRatio; |
||||
requestLayout(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
||||
|
||||
if (targetAspectRatio > 0) { |
||||
int initialWidth = MeasureSpec.getSize(widthMeasureSpec); |
||||
int initialHeight = MeasureSpec.getSize(heightMeasureSpec); |
||||
|
||||
// padding
|
||||
int horizontalPadding = getPaddingLeft() + getPaddingRight(); |
||||
int verticalPadding = getPaddingTop() + getPaddingBottom(); |
||||
initialWidth -= horizontalPadding; |
||||
initialHeight -= verticalPadding; |
||||
|
||||
double viewAspectRatio = (double) initialWidth / initialHeight; |
||||
double aspectDifference = targetAspectRatio / viewAspectRatio - 1; |
||||
|
||||
if (Math.abs(aspectDifference) < 0.01) { |
||||
//no changes
|
||||
} else { |
||||
if (aspectDifference > 0) { |
||||
initialHeight = (int) (initialWidth / targetAspectRatio); |
||||
} else { |
||||
initialWidth = (int) (initialHeight * targetAspectRatio); |
||||
} |
||||
initialWidth += horizontalPadding; |
||||
initialHeight += verticalPadding; |
||||
widthMeasureSpec = MeasureSpec.makeMeasureSpec(initialWidth, MeasureSpec.EXACTLY); |
||||
heightMeasureSpec = MeasureSpec.makeMeasureSpec(initialHeight, MeasureSpec.EXACTLY); |
||||
} |
||||
} |
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
||||
} |
||||
} |
@ -0,0 +1,64 @@ |
||||
package com.github.florent37.camerafragment.internal.ui.view; |
||||
|
||||
import android.annotation.SuppressLint; |
||||
import android.content.Context; |
||||
import android.support.annotation.NonNull; |
||||
import android.view.SurfaceHolder; |
||||
import android.view.SurfaceView; |
||||
|
||||
/** |
||||
* Created by memfis on 7/6/16. |
||||
*/ |
||||
@SuppressWarnings("deprecation") |
||||
@SuppressLint("ViewConstructor") |
||||
public class AutoFitSurfaceView extends SurfaceView { |
||||
|
||||
private final static String TAG = "AutoFitSurfaceView"; |
||||
|
||||
private final SurfaceHolder surfaceHolder; |
||||
|
||||
private int ratioWidth; |
||||
private int ratioHeight; |
||||
|
||||
public AutoFitSurfaceView(@NonNull Context context, SurfaceHolder.Callback callback) { |
||||
super(context); |
||||
|
||||
this.surfaceHolder = getHolder(); |
||||
|
||||
this.surfaceHolder.addCallback(callback); |
||||
this.surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); |
||||
} |
||||
|
||||
/** |
||||
* Sets the aspect ratio for this view. The size of the view will be measured based on the ratio |
||||
* calculated fromList the parameters. Note that the actual sizes of parameters don't matter, that |
||||
* is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result. |
||||
* |
||||
* @param width Relative horizontal size |
||||
* @param height Relative vertical size |
||||
*/ |
||||
public void setAspectRatio(int width, int height) { |
||||
if (width < 0 || height < 0) { |
||||
throw new IllegalArgumentException("Size cannot be negative."); |
||||
} |
||||
ratioWidth = width; |
||||
ratioHeight = height; |
||||
requestLayout(); |
||||
} |
||||
|
||||
@Override |
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
||||
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec); |
||||
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec); |
||||
|
||||
if (0 == ratioWidth || 0 == ratioHeight) { |
||||
setMeasuredDimension(width, height); |
||||
} else { |
||||
if (width < height * (ratioWidth / (float) ratioHeight)) { |
||||
setMeasuredDimension(width, (int) (width * (ratioWidth / (float) ratioHeight))); |
||||
} else { |
||||
setMeasuredDimension((int) (height * (ratioWidth / (float) ratioHeight)), height); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,59 @@ |
||||
package com.github.florent37.camerafragment.internal.ui.view; |
||||
|
||||
import android.annotation.SuppressLint; |
||||
import android.annotation.TargetApi; |
||||
import android.content.Context; |
||||
import android.os.Build; |
||||
import android.view.TextureView; |
||||
|
||||
/** |
||||
* Created by memfis on 7/6/16. |
||||
*/ |
||||
@SuppressLint("ViewConstructor") |
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public class AutoFitTextureView extends TextureView { |
||||
|
||||
private final static String TAG = "AutoFitTextureView"; |
||||
|
||||
private int ratioWidth = 0; |
||||
private int ratioHeight = 0; |
||||
|
||||
public AutoFitTextureView(Context context, TextureView.SurfaceTextureListener surfaceTextureListener) { |
||||
super(context, null); |
||||
setSurfaceTextureListener(surfaceTextureListener); |
||||
} |
||||
|
||||
/** |
||||
* Sets the aspect ratio for this view. The size of the view will be measured based on the ratio |
||||
* calculated fromList the parameters. Note that the actual sizes of parameters don't matter, that |
||||
* is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result. |
||||
* |
||||
* @param width Relative horizontal size |
||||
* @param height Relative vertical size |
||||
*/ |
||||
public void setAspectRatio(int width, int height) { |
||||
if (width < 0 || height < 0) { |
||||
throw new IllegalArgumentException("Size cannot be negative."); |
||||
} |
||||
ratioWidth = width; |
||||
ratioHeight = height; |
||||
|
||||
requestLayout(); |
||||
} |
||||
|
||||
@Override |
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
||||
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec); |
||||
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec); |
||||
|
||||
if (0 == ratioWidth || 0 == ratioHeight) { |
||||
setMeasuredDimension(width, height); |
||||
} else { |
||||
if (width < height * (ratioWidth / (float) ratioHeight)) { |
||||
setMeasuredDimension(width, (int) (width * (ratioWidth / (float) ratioHeight))); |
||||
} else { |
||||
setMeasuredDimension((int) (height * (ratioWidth / (float) ratioHeight)), height); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,465 @@ |
||||
package com.github.florent37.camerafragment.internal.utils; |
||||
|
||||
import android.annotation.TargetApi; |
||||
import android.content.Context; |
||||
import android.content.pm.PackageManager; |
||||
import android.hardware.camera2.CameraCharacteristics; |
||||
import android.hardware.camera2.CameraManager; |
||||
import android.media.CamcorderProfile; |
||||
import android.os.Build; |
||||
import android.os.Environment; |
||||
import android.text.TextUtils; |
||||
import android.util.Log; |
||||
|
||||
import java.io.File; |
||||
import java.text.SimpleDateFormat; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.Comparator; |
||||
import java.util.Date; |
||||
import java.util.List; |
||||
|
||||
import com.github.florent37.camerafragment.configuration.Configuration; |
||||
|
||||
/** |
||||
* Created by memfis on 7/6/16. |
||||
* <p/> |
||||
* Class with some common methods to work with camera. |
||||
*/ |
||||
public final class CameraHelper { |
||||
|
||||
public final static String TAG = "CameraHelper"; |
||||
|
||||
|
||||
private CameraHelper() { |
||||
|
||||
} |
||||
|
||||
public static boolean hasCamera(Context context) { |
||||
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA) || |
||||
context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT); |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public static boolean hasCamera2(Context context) { |
||||
if (context == null) return false; |
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return false; |
||||
try { |
||||
CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); |
||||
String[] idList = manager.getCameraIdList(); |
||||
boolean notNull = true; |
||||
if (idList.length == 0) { |
||||
notNull = false; |
||||
} else { |
||||
for (final String str : idList) { |
||||
if (str == null || str.trim().isEmpty()) { |
||||
notNull = false; |
||||
break; |
||||
} |
||||
final CameraCharacteristics characteristics = manager.getCameraCharacteristics(str); |
||||
|
||||
final int supportLevel = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); |
||||
if (supportLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { |
||||
notNull = false; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
return notNull; |
||||
} catch (Throwable ignore) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public static File getOutputMediaFile(Context context, @Configuration.MediaAction int mediaAction) { |
||||
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory( |
||||
Environment.DIRECTORY_PICTURES), context.getPackageName()); |
||||
|
||||
if (!mediaStorageDir.exists()) { |
||||
if (!mediaStorageDir.mkdirs()) { |
||||
Log.d(TAG, "Failed to create directory."); |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
String timeStamp = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss").format(new Date()); |
||||
File mediaFile; |
||||
if (mediaAction == Configuration.MEDIA_ACTION_PHOTO) { |
||||
mediaFile = new File(mediaStorageDir.getPath() + File.separator + |
||||
"IMG_" + timeStamp + ".jpg"); |
||||
} else if (mediaAction == Configuration.MEDIA_ACTION_VIDEO) { |
||||
mediaFile = new File(mediaStorageDir.getPath() + File.separator + |
||||
"VID_" + timeStamp + ".mp4"); |
||||
} else { |
||||
return null; |
||||
} |
||||
|
||||
return mediaFile; |
||||
} |
||||
|
||||
@SuppressWarnings("deprecation") |
||||
public static Size getPictureSize(List<Size> choices, @Configuration.MediaQuality int mediaQuality) { |
||||
if (choices == null || choices.isEmpty()) return null; |
||||
if (choices.size() == 1) return choices.get(0); |
||||
|
||||
Size result = null; |
||||
Size maxPictureSize = Collections.max(choices, new CompareSizesByArea2()); |
||||
Size minPictureSize = Collections.min(choices, new CompareSizesByArea2()); |
||||
|
||||
Collections.sort(choices, new CompareSizesByArea2()); |
||||
|
||||
if (mediaQuality == Configuration.MEDIA_QUALITY_HIGHEST) { |
||||
result = maxPictureSize; |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_LOW) { |
||||
if (choices.size() == 2) result = minPictureSize; |
||||
else { |
||||
int half = choices.size() / 2; |
||||
int lowQualityIndex = (choices.size() - half) / 2; |
||||
result = choices.get(lowQualityIndex + 1); |
||||
} |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_HIGH) { |
||||
if (choices.size() == 2) result = maxPictureSize; |
||||
else { |
||||
int half = choices.size() / 2; |
||||
int highQualityIndex = (choices.size() - half) / 2; |
||||
result = choices.get(choices.size() - highQualityIndex - 1); |
||||
} |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_MEDIUM) { |
||||
if (choices.size() == 2) result = minPictureSize; |
||||
else { |
||||
int mediumQualityIndex = choices.size() / 2; |
||||
result = choices.get(mediumQualityIndex); |
||||
} |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_LOWEST) { |
||||
result = minPictureSize; |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public static Size getPictureSize(Size[] sizes, @Configuration.MediaQuality int mediaQuality) { |
||||
if (sizes == null || sizes.length == 0) return null; |
||||
|
||||
List<Size> choices = Arrays.asList(sizes); |
||||
|
||||
if (choices.size() == 1) return choices.get(0); |
||||
|
||||
Size result = null; |
||||
Size maxPictureSize = Collections.max(choices, new CompareSizesByArea2()); |
||||
Size minPictureSize = Collections.min(choices, new CompareSizesByArea2()); |
||||
|
||||
Collections.sort(choices, new CompareSizesByArea2()); |
||||
|
||||
if (mediaQuality == Configuration.MEDIA_QUALITY_HIGHEST) { |
||||
result = maxPictureSize; |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_LOW) { |
||||
if (choices.size() == 2) result = minPictureSize; |
||||
else { |
||||
int half = choices.size() / 2; |
||||
int lowQualityIndex = (choices.size() - half) / 2; |
||||
result = choices.get(lowQualityIndex + 1); |
||||
} |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_HIGH) { |
||||
if (choices.size() == 2) result = maxPictureSize; |
||||
else { |
||||
int half = choices.size() / 2; |
||||
int highQualityIndex = (choices.size() - half) / 2; |
||||
result = choices.get(choices.size() - highQualityIndex - 1); |
||||
} |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_MEDIUM) { |
||||
if (choices.size() == 2) result = minPictureSize; |
||||
else { |
||||
int mediumQualityIndex = choices.size() / 2; |
||||
result = choices.get(mediumQualityIndex); |
||||
} |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_LOWEST) { |
||||
result = minPictureSize; |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
@SuppressWarnings("deprecation") |
||||
public static Size getOptimalPreviewSize(List<Size> sizes, int width, int height) { |
||||
final double ASPECT_TOLERANCE = 0.1; |
||||
double targetRatio = (double) height / width; |
||||
|
||||
if (sizes == null) return null; |
||||
|
||||
Size optimalSize = null; |
||||
double minDiff = Double.MAX_VALUE; |
||||
|
||||
int targetHeight = height; |
||||
|
||||
for (Size size : sizes) { |
||||
double ratio = (double) size.getWidth() / size.getHeight(); |
||||
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue; |
||||
if (Math.abs(size.getHeight() - targetHeight) < minDiff) { |
||||
optimalSize = size; |
||||
minDiff = Math.abs(size.getHeight() - targetHeight); |
||||
} |
||||
} |
||||
|
||||
if (optimalSize == null) { |
||||
minDiff = Double.MAX_VALUE; |
||||
for (Size size : sizes) { |
||||
if (Math.abs(size.getHeight() - targetHeight) < minDiff) { |
||||
optimalSize = size; |
||||
minDiff = Math.abs(size.getHeight() - targetHeight); |
||||
} |
||||
} |
||||
} |
||||
return optimalSize; |
||||
} |
||||
|
||||
@SuppressWarnings("deprecation") |
||||
public static Size getSizeWithClosestRatio(List<Size> sizes, int width, int height) { |
||||
|
||||
if (sizes == null) return null; |
||||
|
||||
double MIN_TOLERANCE = 100; |
||||
double targetRatio = (double) height / width; |
||||
Size optimalSize = null; |
||||
double minDiff = Double.MAX_VALUE; |
||||
|
||||
int targetHeight = height; |
||||
|
||||
for (Size size : sizes) { |
||||
if (size.getWidth() == width && size.getHeight() == height) |
||||
return size; |
||||
|
||||
double ratio = (double) size.getHeight() / size.getWidth(); |
||||
|
||||
if (Math.abs(ratio - targetRatio) < MIN_TOLERANCE) MIN_TOLERANCE = ratio; |
||||
else continue; |
||||
|
||||
if (Math.abs(size.getHeight() - targetHeight) < minDiff) { |
||||
optimalSize = size; |
||||
minDiff = Math.abs(size.getHeight() - targetHeight); |
||||
} |
||||
} |
||||
|
||||
if (optimalSize == null) { |
||||
minDiff = Double.MAX_VALUE; |
||||
for (Size size : sizes) { |
||||
if (Math.abs(size.getHeight() - targetHeight) < minDiff) { |
||||
optimalSize = size; |
||||
minDiff = Math.abs(size.getHeight() - targetHeight); |
||||
} |
||||
} |
||||
} |
||||
return optimalSize; |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public static Size getOptimalPreviewSize(Size[] sizes, int width, int height) { |
||||
|
||||
if (sizes == null) return null; |
||||
|
||||
final double ASPECT_TOLERANCE = 0.1; |
||||
double targetRatio = (double) height / width; |
||||
Size optimalSize = null; |
||||
double minDiff = Double.MAX_VALUE; |
||||
|
||||
int targetHeight = height; |
||||
|
||||
for (Size size : sizes) { |
||||
// if (size.getWidth() == width && size.getHeight() == height)
|
||||
// return size;
|
||||
double ratio = (double) size.getWidth() / size.getHeight(); |
||||
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue; |
||||
if (Math.abs(size.getHeight() - targetHeight) < minDiff) { |
||||
optimalSize = size; |
||||
minDiff = Math.abs(size.getHeight() - targetHeight); |
||||
} |
||||
} |
||||
|
||||
if (optimalSize == null) { |
||||
minDiff = Double.MAX_VALUE; |
||||
for (Size size : sizes) { |
||||
if (Math.abs(size.getHeight() - targetHeight) < minDiff) { |
||||
optimalSize = size; |
||||
minDiff = Math.abs(size.getHeight() - targetHeight); |
||||
} |
||||
} |
||||
} |
||||
return optimalSize; |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public static Size getSizeWithClosestRatio(Size[] sizes, int width, int height) { |
||||
|
||||
if (sizes == null) return null; |
||||
|
||||
double MIN_TOLERANCE = 100; |
||||
double targetRatio = (double) height / width; |
||||
Size optimalSize = null; |
||||
double minDiff = Double.MAX_VALUE; |
||||
|
||||
int targetHeight = height; |
||||
|
||||
for (Size size : sizes) { |
||||
// if (size.getWidth() == width && size.getHeight() == height)
|
||||
// return size;
|
||||
|
||||
double ratio = (double) size.getHeight() / size.getWidth(); |
||||
|
||||
if (Math.abs(ratio - targetRatio) < MIN_TOLERANCE) MIN_TOLERANCE = ratio; |
||||
else continue; |
||||
|
||||
if (Math.abs(size.getHeight() - targetHeight) < minDiff) { |
||||
optimalSize = size; |
||||
minDiff = Math.abs(size.getHeight() - targetHeight); |
||||
} |
||||
} |
||||
|
||||
if (optimalSize == null) { |
||||
minDiff = Double.MAX_VALUE; |
||||
for (Size size : sizes) { |
||||
if (Math.abs(size.getHeight() - targetHeight) < minDiff) { |
||||
optimalSize = size; |
||||
minDiff = Math.abs(size.getHeight() - targetHeight); |
||||
} |
||||
} |
||||
} |
||||
return optimalSize; |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) { |
||||
// Collect the supported resolutions that are at least as big as the preview Surface
|
||||
List<Size> bigEnough = new ArrayList<>(); |
||||
int w = aspectRatio.getWidth(); |
||||
int h = aspectRatio.getHeight(); |
||||
for (Size option : choices) { |
||||
if (option.getHeight() == option.getWidth() * h / w && |
||||
option.getWidth() >= width && option.getHeight() >= height) { |
||||
bigEnough.add(option); |
||||
} |
||||
} |
||||
|
||||
// Pick the smallest of those, assuming we found any
|
||||
if (bigEnough.size() > 0) { |
||||
return Collections.min(bigEnough, new CompareSizesByArea2()); |
||||
} else { |
||||
Log.e(TAG, "Couldn't find any suitable preview size"); |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
private static double calculateApproximateVideoSize(CamcorderProfile camcorderProfile, int seconds) { |
||||
return ((camcorderProfile.videoBitRate / (float) 1 + camcorderProfile.audioBitRate / (float) 1) * seconds) / (float) 8; |
||||
} |
||||
|
||||
public static double calculateApproximateVideoDuration(CamcorderProfile camcorderProfile, long maxFileSize) { |
||||
return 8 * maxFileSize / (camcorderProfile.videoBitRate + camcorderProfile.audioBitRate); |
||||
} |
||||
|
||||
private static long calculateMinimumRequiredBitRate(CamcorderProfile camcorderProfile, long maxFileSize, int seconds) { |
||||
return 8 * maxFileSize / seconds - camcorderProfile.audioBitRate; |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public static CamcorderProfile getCamcorderProfile(String cameraId, long maximumFileSize, int minimumDurationInSeconds) { |
||||
if (TextUtils.isEmpty(cameraId)) { |
||||
return null; |
||||
} |
||||
int cameraIdInt = Integer.parseInt(cameraId); |
||||
return getCamcorderProfile(cameraIdInt, maximumFileSize, minimumDurationInSeconds); |
||||
} |
||||
|
||||
public static CamcorderProfile getCamcorderProfile(int currentCameraId, long maximumFileSize, int minimumDurationInSeconds) { |
||||
if (maximumFileSize <= 0) |
||||
return CamcorderProfile.get(currentCameraId, Configuration.MEDIA_QUALITY_HIGHEST); |
||||
|
||||
int[] qualities = new int[]{Configuration.MEDIA_QUALITY_HIGHEST, |
||||
Configuration.MEDIA_QUALITY_HIGH, Configuration.MEDIA_QUALITY_MEDIUM, |
||||
Configuration.MEDIA_QUALITY_LOW, Configuration.MEDIA_QUALITY_LOWEST}; |
||||
|
||||
CamcorderProfile camcorderProfile; |
||||
for (int i = 0; i < qualities.length; ++i) { |
||||
camcorderProfile = CameraHelper.getCamcorderProfile(qualities[i], currentCameraId); |
||||
double fileSize = CameraHelper.calculateApproximateVideoSize(camcorderProfile, minimumDurationInSeconds); |
||||
|
||||
if (fileSize > maximumFileSize) { |
||||
long minimumRequiredBitRate = calculateMinimumRequiredBitRate(camcorderProfile, maximumFileSize, minimumDurationInSeconds); |
||||
|
||||
if (minimumRequiredBitRate >= camcorderProfile.videoBitRate / 4 && minimumRequiredBitRate <= camcorderProfile.videoBitRate) { |
||||
camcorderProfile.videoBitRate = (int) minimumRequiredBitRate; |
||||
return camcorderProfile; |
||||
} |
||||
} else return camcorderProfile; |
||||
} |
||||
return CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_LOWEST, currentCameraId); |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public static CamcorderProfile getCamcorderProfile(@Configuration.MediaQuality int mediaQuality, String cameraId) { |
||||
if (TextUtils.isEmpty(cameraId)) { |
||||
return null; |
||||
} |
||||
int cameraIdInt = Integer.parseInt(cameraId); |
||||
return getCamcorderProfile(mediaQuality, cameraIdInt); |
||||
} |
||||
|
||||
public static CamcorderProfile getCamcorderProfile(@Configuration.MediaQuality int mediaQuality, int cameraId) { |
||||
if (Build.VERSION.SDK_INT > 10) { |
||||
if (mediaQuality == Configuration.MEDIA_QUALITY_HIGHEST) { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH); |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_HIGH) { |
||||
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_1080P); |
||||
} else if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_720P); |
||||
} else { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH); |
||||
} |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_MEDIUM) { |
||||
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_720P); |
||||
} else if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_480P); |
||||
} else { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW); |
||||
} |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_LOW) { |
||||
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_480P); |
||||
} else { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW); |
||||
} |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_LOWEST) { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW); |
||||
} else { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH); |
||||
} |
||||
} else { |
||||
if (mediaQuality == Configuration.MEDIA_QUALITY_HIGHEST) { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH); |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_HIGH) { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH); |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_MEDIUM) { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW); |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_LOW) { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW); |
||||
} else if (mediaQuality == Configuration.MEDIA_QUALITY_LOWEST) { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW); |
||||
} else { |
||||
return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
private static class CompareSizesByArea2 implements Comparator<Size> { |
||||
@Override |
||||
public int compare(Size lhs, Size rhs) { |
||||
// We cast here to ensure the multiplications won't overflow
|
||||
return Long.signum((long) lhs.getWidth() * lhs.getHeight() - |
||||
(long) rhs.getWidth() * rhs.getHeight()); |
||||
} |
||||
|
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
package com.github.florent37.camerafragment.internal.utils; |
||||
|
||||
/** |
||||
* Created by memfis on 6/27/16. |
||||
*/ |
||||
public final class DateTimeUtils { |
||||
|
||||
public static final long SECOND = 1000; |
||||
public static final long MINUTE = 60 * SECOND; |
||||
public static final long HOUR = 60 * MINUTE; |
||||
public static final long DAY = 24 * HOUR; |
||||
} |
@ -0,0 +1,196 @@ |
||||
package com.github.florent37.camerafragment.internal.utils; |
||||
|
||||
import android.content.Context; |
||||
import android.graphics.Bitmap; |
||||
import android.graphics.BitmapFactory; |
||||
import android.graphics.Matrix; |
||||
import android.graphics.Point; |
||||
import android.media.ExifInterface; |
||||
import android.os.Build; |
||||
import android.os.Handler; |
||||
import android.os.Looper; |
||||
import android.os.Process; |
||||
import android.support.annotation.NonNull; |
||||
import android.view.Display; |
||||
import android.view.ViewTreeObserver; |
||||
import android.view.WindowManager; |
||||
import android.widget.ImageView; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
/** |
||||
* Created by memfis on 7/18/16. |
||||
*/ |
||||
public final class ImageLoader { |
||||
|
||||
private Context context; |
||||
private String url; |
||||
|
||||
private ImageLoader(Context context) { |
||||
this.context = context; |
||||
} |
||||
|
||||
public static class Builder { |
||||
|
||||
private ImageLoader imageLoader; |
||||
|
||||
public Builder(@NonNull Context context) { |
||||
imageLoader = new ImageLoader(context); |
||||
} |
||||
|
||||
public Builder load(String url) { |
||||
imageLoader.url = url; |
||||
return this; |
||||
} |
||||
|
||||
public ImageLoader build() { |
||||
return imageLoader; |
||||
} |
||||
} |
||||
|
||||
public void into(final ImageView target) { |
||||
ViewTreeObserver viewTreeObserver = target.getViewTreeObserver(); |
||||
viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { |
||||
public boolean onPreDraw() { |
||||
target.getViewTreeObserver().removeOnPreDrawListener(this); |
||||
|
||||
new ImageLoaderThread(target, url).start(); |
||||
|
||||
return true; |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private class ImageLoaderThread extends Thread { |
||||
|
||||
private ImageView target; |
||||
private String url; |
||||
private Handler mainHandler = new Handler(Looper.getMainLooper()); |
||||
|
||||
private ImageLoaderThread(ImageView target, String url) { |
||||
this.target = target; |
||||
this.url = url; |
||||
} |
||||
|
||||
@Override |
||||
public void run() { |
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); |
||||
|
||||
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); |
||||
Display display = windowManager.getDefaultDisplay(); |
||||
|
||||
int imageViewHeight; |
||||
int imageViewWidth; |
||||
|
||||
if (Build.VERSION.SDK_INT < 13) { |
||||
imageViewHeight = display.getHeight(); |
||||
imageViewWidth = display.getWidth(); |
||||
} else { |
||||
Point size = new Point(); |
||||
display.getSize(size); |
||||
imageViewHeight = size.y; |
||||
imageViewWidth = size.x; |
||||
} |
||||
|
||||
// int imageViewHeight = target.getMeasuredHeight();
|
||||
// int imageViewWidth = target.getMeasuredWidth();
|
||||
|
||||
Bitmap decodedBitmap = decodeSampledBitmapFromResource(url, imageViewWidth, imageViewHeight); |
||||
final Bitmap resultBitmap = rotateBitmap(decodedBitmap, getExifOrientation()); |
||||
|
||||
mainHandler.post(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
target.setImageBitmap(resultBitmap); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private Bitmap rotateBitmap(Bitmap bitmap, int orientation) { |
||||
Matrix matrix = new Matrix(); |
||||
switch (orientation) { |
||||
case ExifInterface.ORIENTATION_NORMAL: |
||||
return bitmap; |
||||
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: |
||||
matrix.setScale(-1, 1); |
||||
break; |
||||
case ExifInterface.ORIENTATION_ROTATE_180: |
||||
matrix.setRotate(180); |
||||
break; |
||||
case ExifInterface.ORIENTATION_FLIP_VERTICAL: |
||||
matrix.setRotate(180); |
||||
matrix.postScale(-1, 1); |
||||
break; |
||||
case ExifInterface.ORIENTATION_TRANSPOSE: |
||||
matrix.setRotate(90); |
||||
matrix.postScale(-1, 1); |
||||
break; |
||||
case ExifInterface.ORIENTATION_ROTATE_90: |
||||
matrix.setRotate(90); |
||||
break; |
||||
case ExifInterface.ORIENTATION_TRANSVERSE: |
||||
matrix.setRotate(-90); |
||||
matrix.postScale(-1, 1); |
||||
break; |
||||
case ExifInterface.ORIENTATION_ROTATE_270: |
||||
matrix.setRotate(-90); |
||||
break; |
||||
default: |
||||
return bitmap; |
||||
} |
||||
|
||||
try { |
||||
Bitmap bmRotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); |
||||
bitmap.recycle(); |
||||
return bmRotated; |
||||
} catch (OutOfMemoryError ignore) { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
private int getExifOrientation() { |
||||
ExifInterface exif = null; |
||||
try { |
||||
exif = new ExifInterface(url); |
||||
} catch (IOException ignore) { |
||||
} |
||||
return exif == null ? ExifInterface.ORIENTATION_UNDEFINED : |
||||
exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED); |
||||
} |
||||
|
||||
private Bitmap decodeSampledBitmapFromResource(String url, |
||||
int requestedWidth, int requestedHeight) { |
||||
|
||||
final BitmapFactory.Options options = new BitmapFactory.Options(); |
||||
options.inJustDecodeBounds = true; |
||||
BitmapFactory.decodeFile(url, options); |
||||
|
||||
options.inSampleSize = calculateInSampleSize(options, requestedWidth, requestedHeight); |
||||
options.inJustDecodeBounds = false; |
||||
options.inPreferredConfig = Bitmap.Config.RGB_565; |
||||
return BitmapFactory.decodeFile(url, options); |
||||
} |
||||
|
||||
private int calculateInSampleSize(BitmapFactory.Options options, |
||||
int requestedWidth, int requestedHeight) { |
||||
|
||||
final int height = options.outHeight; |
||||
final int width = options.outWidth; |
||||
int inSampleSize = 1; |
||||
|
||||
if (height > requestedHeight || width > requestedWidth) { |
||||
|
||||
final int halfHeight = height / inSampleSize; |
||||
final int halfWidth = width / inSampleSize; |
||||
|
||||
while ((halfHeight / inSampleSize) > requestedHeight |
||||
&& (halfWidth / inSampleSize) > requestedWidth) { |
||||
inSampleSize *= 2; |
||||
} |
||||
} |
||||
return inSampleSize; |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
@ -0,0 +1,62 @@ |
||||
package com.github.florent37.camerafragment.internal.utils; |
||||
|
||||
import android.annotation.TargetApi; |
||||
import android.media.Image; |
||||
import android.os.Build; |
||||
import android.util.Log; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
/** |
||||
* Created by memfis on 7/6/16. |
||||
*/ |
||||
public class ImageSaver implements Runnable { |
||||
|
||||
private final static String TAG = "ImageSaver"; |
||||
|
||||
private final Image image; |
||||
private final File file; |
||||
private ImageSaverCallback imageSaverCallback; |
||||
|
||||
public interface ImageSaverCallback { |
||||
void onSuccessFinish(byte[] bytes); |
||||
|
||||
void onError(); |
||||
} |
||||
|
||||
public ImageSaver(Image image, File file, ImageSaverCallback imageSaverCallback) { |
||||
this.image = image; |
||||
this.file = file; |
||||
this.imageSaverCallback = imageSaverCallback; |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT) |
||||
@Override |
||||
public void run() { |
||||
ByteBuffer buffer = image.getPlanes()[0].getBuffer(); |
||||
byte[] bytes = new byte[buffer.remaining()]; |
||||
buffer.get(bytes); |
||||
FileOutputStream output = null; |
||||
try { |
||||
output = new FileOutputStream(file); |
||||
output.write(bytes); |
||||
imageSaverCallback.onSuccessFinish(bytes); |
||||
} catch (IOException ignore) { |
||||
Log.e(TAG, "Can't save the image file."); |
||||
imageSaverCallback.onError(); |
||||
} finally { |
||||
image.close(); |
||||
if (null != output) { |
||||
try { |
||||
output.close(); |
||||
} catch (IOException e) { |
||||
Log.e(TAG, "Can't release image or close the output stream."); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,104 @@ |
||||
package com.github.florent37.camerafragment.internal.utils; |
||||
|
||||
import android.annotation.TargetApi; |
||||
import android.hardware.Camera; |
||||
import android.os.Build; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* Created by memfis on 12/1/16. |
||||
*/ |
||||
|
||||
public class Size { |
||||
|
||||
private int width; |
||||
private int height; |
||||
|
||||
public Size() { |
||||
width = 0; |
||||
height = 0; |
||||
} |
||||
|
||||
public Size(int width, int height) { |
||||
this.width = width; |
||||
this.height = height; |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public Size(android.util.Size size) { |
||||
this.width = size.getWidth(); |
||||
this.height = size.getHeight(); |
||||
} |
||||
|
||||
@SuppressWarnings("deprecation") |
||||
public Size(Camera.Size size) { |
||||
this.width = size.width; |
||||
this.height = size.height; |
||||
} |
||||
|
||||
public int getWidth() { |
||||
return width; |
||||
} |
||||
|
||||
public void setWidth(int width) { |
||||
this.width = width; |
||||
} |
||||
|
||||
public int getHeight() { |
||||
return height; |
||||
} |
||||
|
||||
public void setHeight(int height) { |
||||
this.height = height; |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public static List<Size> fromList2(List<android.util.Size> sizes) { |
||||
if (sizes == null) return null; |
||||
List<Size> result = new ArrayList<>(sizes.size()); |
||||
|
||||
for (android.util.Size size : sizes) { |
||||
result.add(new Size(size)); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
@SuppressWarnings("deprecation") |
||||
public static List<Size> fromList(List<Camera.Size> sizes) { |
||||
if (sizes == null) return null; |
||||
List<Size> result = new ArrayList<>(sizes.size()); |
||||
|
||||
for (Camera.Size size : sizes) { |
||||
result.add(new Size(size)); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public static Size[] fromArray2(android.util.Size[] sizes) { |
||||
if (sizes == null) return null; |
||||
Size[] result = new Size[sizes.length]; |
||||
|
||||
for (int i = 0; i < sizes.length; ++i) { |
||||
result[i] = new Size(sizes[i]); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
@SuppressWarnings("deprecation") |
||||
public static Size[] fromArray(Camera.Size[] sizes) { |
||||
if (sizes == null) return null; |
||||
Size[] result = new Size[sizes.length]; |
||||
|
||||
for (int i = 0; i < sizes.length; ++i) { |
||||
result[i] = new Size(sizes[i]); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
} |
@ -0,0 +1,53 @@ |
||||
package com.github.florent37.camerafragment.internal.utils; |
||||
|
||||
import android.content.Context; |
||||
import android.content.res.Configuration; |
||||
import android.content.res.Resources; |
||||
import android.text.TextUtils; |
||||
import android.util.TypedValue; |
||||
import android.view.Surface; |
||||
import android.view.WindowManager; |
||||
import android.webkit.MimeTypeMap; |
||||
|
||||
/** |
||||
* Created by memfis on 7/18/16. |
||||
*/ |
||||
public class Utils { |
||||
|
||||
public static int getDeviceDefaultOrientation(Context context) { |
||||
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); |
||||
Configuration config = context.getResources().getConfiguration(); |
||||
|
||||
int rotation = windowManager.getDefaultDisplay().getRotation(); |
||||
|
||||
if (((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) && |
||||
config.orientation == Configuration.ORIENTATION_LANDSCAPE) |
||||
|| ((rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) && |
||||
config.orientation == Configuration.ORIENTATION_PORTRAIT)) { |
||||
return Configuration.ORIENTATION_LANDSCAPE; |
||||
} else { |
||||
return Configuration.ORIENTATION_PORTRAIT; |
||||
} |
||||
} |
||||
|
||||
public static String getMimeType(String url) { |
||||
String type = ""; |
||||
String extension = MimeTypeMap.getFileExtensionFromUrl(url); |
||||
if (!TextUtils.isEmpty(extension)) { |
||||
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); |
||||
} else { |
||||
String reCheckExtension = MimeTypeMap.getFileExtensionFromUrl(url.replaceAll("\\s+", "")); |
||||
if (!TextUtils.isEmpty(reCheckExtension)) { |
||||
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(reCheckExtension); |
||||
} |
||||
} |
||||
return type; |
||||
} |
||||
|
||||
public static int convertDipToPixels(Context context, int dip) { |
||||
Resources resources = context.getResources(); |
||||
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, resources.getDisplayMetrics()); |
||||
return (int) px; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,13 @@ |
||||
package com.github.florent37.camerafragment.listeners; |
||||
|
||||
/** |
||||
* Created by florentchampigny on 13/01/2017. |
||||
*/ |
||||
|
||||
public interface CameraFragmentControlsListener { |
||||
void lockControls(); |
||||
void unLockControls(); |
||||
void allowCameraSwitching(boolean allow); |
||||
void allowRecord(boolean allow); |
||||
void setMediaActionSwitchVisible(boolean visible); |
||||
} |
@ -0,0 +1,14 @@ |
||||
package com.github.florent37.camerafragment.listeners; |
||||
|
||||
/** |
||||
* Created by florentchampigny on 13/01/2017. |
||||
*/ |
||||
|
||||
public interface CameraFragmentResultListener { |
||||
|
||||
//Called when the video record is finished and saved
|
||||
void onVideoRecorded(String filePath); |
||||
|
||||
//called when the photo is taken and saved
|
||||
void onPhotoTaken(byte[] bytes, String filePath); |
||||
} |
@ -0,0 +1,42 @@ |
||||
package com.github.florent37.camerafragment.listeners; |
||||
|
||||
import java.io.File; |
||||
|
||||
/** |
||||
* Created by florentchampigny on 13/01/2017. |
||||
*/ |
||||
|
||||
public interface CameraFragmentStateListener { |
||||
|
||||
//when the current displayed camera is the back
|
||||
void onCurrentCameraBack(); |
||||
|
||||
//when the current displayed camera is the front
|
||||
void onCurrentCameraFront(); |
||||
|
||||
//when the flash is at mode auto
|
||||
void onFlashAuto(); |
||||
//when the flash is at on
|
||||
void onFlashOn(); |
||||
//when the flash is off
|
||||
void onFlashOff(); |
||||
|
||||
//if the camera is ready to take a photo
|
||||
void onCameraSetupForPhoto(); |
||||
|
||||
//if the camera is ready to take a video
|
||||
void onCameraSetupForVideo(); |
||||
|
||||
//when the camera state is "ready to record a video"
|
||||
void onRecordStateVideoReadyForRecord(); |
||||
//when the camera state is "recording a video"
|
||||
void onRecordStateVideoInProgress(); |
||||
//when the camera state is "ready to take a photo"
|
||||
void onRecordStatePhoto(); |
||||
|
||||
//after the rotation of the screen / camera
|
||||
void shouldRotateControls(int degrees); |
||||
|
||||
void onStartVideoRecord(File outputFile); |
||||
void onStopVideoRecord(); |
||||
} |
@ -0,0 +1,13 @@ |
||||
package com.github.florent37.camerafragment.listeners; |
||||
|
||||
/** |
||||
* Created by florentchampigny on 13/01/2017. |
||||
*/ |
||||
|
||||
public interface CameraFragmentVideoRecordTextListener { |
||||
void setRecordSizeText(long size, String text); |
||||
void setRecordSizeTextVisible(boolean visible); |
||||
|
||||
void setRecordDurationText(String text); |
||||
void setRecordDurationTextVisible(boolean visible); |
||||
} |
@ -0,0 +1,42 @@ |
||||
package com.github.florent37.camerafragment.widgets; |
||||
|
||||
import android.annotation.TargetApi; |
||||
import android.content.Context; |
||||
import android.os.Build; |
||||
import android.util.AttributeSet; |
||||
import android.widget.ImageButton; |
||||
|
||||
/** |
||||
* Created by memfis on 8/23/16. |
||||
*/ |
||||
public class CameraSettingsView extends ImageButton { |
||||
public CameraSettingsView(Context context) { |
||||
super(context); |
||||
} |
||||
|
||||
public CameraSettingsView(Context context, AttributeSet attrs) { |
||||
super(context, attrs); |
||||
} |
||||
|
||||
public CameraSettingsView(Context context, AttributeSet attrs, int defStyleAttr) { |
||||
super(context, attrs, defStyleAttr); |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public CameraSettingsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { |
||||
super(context, attrs, defStyleAttr, defStyleRes); |
||||
} |
||||
|
||||
@Override |
||||
public void setEnabled(boolean enabled) { |
||||
super.setEnabled(enabled); |
||||
|
||||
if (Build.VERSION.SDK_INT > 10) { |
||||
if (enabled) { |
||||
setAlpha(1f); |
||||
} else { |
||||
setAlpha(0.5f); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,93 @@ |
||||
package com.github.florent37.camerafragment.widgets; |
||||
|
||||
import android.content.Context; |
||||
import android.graphics.drawable.Drawable; |
||||
import android.os.Build; |
||||
import android.support.annotation.Nullable; |
||||
import android.support.v4.content.ContextCompat; |
||||
import android.support.v4.graphics.drawable.DrawableCompat; |
||||
import android.support.v7.widget.AppCompatImageButton; |
||||
import android.util.AttributeSet; |
||||
import android.view.View; |
||||
|
||||
import com.github.florent37.camerafragment.R; |
||||
import com.github.florent37.camerafragment.internal.utils.Utils; |
||||
|
||||
/** |
||||
* Created by memfis on 6/24/16. |
||||
*/ |
||||
public class CameraSwitchView extends AppCompatImageButton { |
||||
|
||||
@Nullable |
||||
private OnCameraTypeChangeListener onCameraTypeChangeListener; |
||||
|
||||
public interface OnCameraTypeChangeListener { |
||||
void switchCameraType(); |
||||
} |
||||
|
||||
private Drawable frontCameraDrawable; |
||||
private Drawable rearCameraDrawable; |
||||
private int padding = 5; |
||||
|
||||
public CameraSwitchView(Context context) { |
||||
this(context, null); |
||||
} |
||||
|
||||
public CameraSwitchView(Context context, AttributeSet attrs) { |
||||
super(context, attrs); |
||||
initializeView(); |
||||
} |
||||
|
||||
public CameraSwitchView(Context context, AttributeSet attrs, int defStyleAttr) { |
||||
this(context, attrs); |
||||
} |
||||
|
||||
private void initializeView() { |
||||
Context context = getContext(); |
||||
frontCameraDrawable = ContextCompat.getDrawable(context, R.drawable.ic_camera_front_white_24dp); |
||||
frontCameraDrawable = DrawableCompat.wrap(frontCameraDrawable); |
||||
DrawableCompat.setTintList(frontCameraDrawable.mutate(), ContextCompat.getColorStateList(context, R.drawable.switch_camera_mode_selector)); |
||||
|
||||
rearCameraDrawable = ContextCompat.getDrawable(context, R.drawable.ic_camera_rear_white_24dp); |
||||
rearCameraDrawable = DrawableCompat.wrap(rearCameraDrawable); |
||||
DrawableCompat.setTintList(rearCameraDrawable.mutate(), ContextCompat.getColorStateList(context, R.drawable.switch_camera_mode_selector)); |
||||
|
||||
setBackgroundResource(R.drawable.circle_frame_background_dark); |
||||
setOnClickListener(new OnClickListener() { |
||||
@Override |
||||
public void onClick(View view) { |
||||
if (onCameraTypeChangeListener != null) { |
||||
onCameraTypeChangeListener.switchCameraType(); |
||||
} |
||||
} |
||||
}); |
||||
displayBackCamera(); |
||||
|
||||
padding = Utils.convertDipToPixels(context, padding); |
||||
setPadding(padding, padding, padding, padding); |
||||
} |
||||
|
||||
public void displayFrontCamera() { |
||||
setImageDrawable(frontCameraDrawable); |
||||
} |
||||
|
||||
public void displayBackCamera() { |
||||
setImageDrawable(rearCameraDrawable); |
||||
} |
||||
|
||||
@Override |
||||
public void setEnabled(boolean enabled) { |
||||
super.setEnabled(enabled); |
||||
if (Build.VERSION.SDK_INT > 10) { |
||||
if (enabled) { |
||||
setAlpha(1f); |
||||
} else { |
||||
setAlpha(0.5f); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void setOnCameraTypeChangeListener(OnCameraTypeChangeListener listener) { |
||||
this.onCameraTypeChangeListener = listener; |
||||
} |
||||
} |
@ -0,0 +1,85 @@ |
||||
package com.github.florent37.camerafragment.widgets; |
||||
|
||||
import android.content.Context; |
||||
import android.graphics.Color; |
||||
import android.graphics.drawable.Drawable; |
||||
import android.os.Build; |
||||
import android.support.annotation.NonNull; |
||||
import android.support.annotation.Nullable; |
||||
import android.support.v4.content.ContextCompat; |
||||
import android.util.AttributeSet; |
||||
import android.view.View; |
||||
import android.widget.ImageButton; |
||||
|
||||
import com.github.florent37.camerafragment.R; |
||||
|
||||
|
||||
/** |
||||
* Created by memfis on 7/6/16. |
||||
*/ |
||||
public class FlashSwitchView extends ImageButton { |
||||
|
||||
@Nullable |
||||
private FlashModeSwitchListener switchListener; |
||||
|
||||
private Drawable flashOnDrawable; |
||||
private Drawable flashOffDrawable; |
||||
private Drawable flashAutoDrawable; |
||||
|
||||
public interface FlashModeSwitchListener { |
||||
void toggleFlashMode(); |
||||
} |
||||
|
||||
public FlashSwitchView(@NonNull Context context) { |
||||
this(context, null); |
||||
} |
||||
|
||||
public FlashSwitchView(@NonNull Context context, AttributeSet attrs) { |
||||
super(context, attrs); |
||||
flashOnDrawable = ContextCompat.getDrawable(context, R.drawable.ic_flash_on_white_24dp); |
||||
flashOffDrawable = ContextCompat.getDrawable(context, R.drawable.ic_flash_off_white_24dp); |
||||
flashAutoDrawable = ContextCompat.getDrawable(context, R.drawable.ic_flash_auto_white_24dp); |
||||
init(); |
||||
} |
||||
|
||||
private void init() { |
||||
setBackgroundColor(Color.TRANSPARENT); |
||||
setOnClickListener(new OnClickListener() { |
||||
@Override |
||||
public void onClick(View view) { |
||||
if (switchListener != null) { |
||||
switchListener.toggleFlashMode(); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public void displayFlashOff() { |
||||
setImageDrawable(flashOffDrawable); |
||||
} |
||||
|
||||
public void displayFlashOn() { |
||||
setImageDrawable(flashOnDrawable); |
||||
} |
||||
|
||||
public void displayFlashAuto() { |
||||
setImageDrawable(flashAutoDrawable); |
||||
} |
||||
|
||||
public void setFlashSwitchListener(@NonNull FlashModeSwitchListener switchListener) { |
||||
this.switchListener = switchListener; |
||||
} |
||||
|
||||
@Override |
||||
public void setEnabled(boolean enabled) { |
||||
super.setEnabled(enabled); |
||||
if (Build.VERSION.SDK_INT > 10) { |
||||
if (enabled) { |
||||
setAlpha(1f); |
||||
} else { |
||||
setAlpha(0.5f); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,96 @@ |
||||
package com.github.florent37.camerafragment.widgets; |
||||
|
||||
import android.content.Context; |
||||
import android.graphics.drawable.Drawable; |
||||
import android.os.Build; |
||||
import android.support.annotation.Nullable; |
||||
import android.support.v4.content.ContextCompat; |
||||
import android.support.v4.graphics.drawable.DrawableCompat; |
||||
import android.util.AttributeSet; |
||||
import android.view.View; |
||||
import android.widget.ImageButton; |
||||
|
||||
import com.github.florent37.camerafragment.R; |
||||
import com.github.florent37.camerafragment.internal.utils.Utils; |
||||
|
||||
/** |
||||
* Created by memfis on 6/24/16. |
||||
*/ |
||||
public class MediaActionSwitchView extends ImageButton { |
||||
|
||||
@Nullable |
||||
private OnMediaActionStateChangeListener onMediaActionStateChangeListener; |
||||
|
||||
public interface OnMediaActionStateChangeListener { |
||||
void switchAction(); |
||||
} |
||||
|
||||
private Drawable photoDrawable; |
||||
private Drawable videoDrawable; |
||||
private int padding = 5; |
||||
|
||||
public MediaActionSwitchView(Context context) { |
||||
this(context, null); |
||||
} |
||||
|
||||
public MediaActionSwitchView(Context context, AttributeSet attrs) { |
||||
super(context, attrs); |
||||
initializeView(); |
||||
} |
||||
|
||||
public MediaActionSwitchView(Context context, AttributeSet attrs, int defStyleAttr) { |
||||
this(context, attrs); |
||||
} |
||||
|
||||
private void initializeView() { |
||||
Context context = getContext(); |
||||
|
||||
photoDrawable = ContextCompat.getDrawable(context, R.drawable.ic_photo_camera_white_24dp); |
||||
photoDrawable = DrawableCompat.wrap(photoDrawable); |
||||
DrawableCompat.setTintList(photoDrawable.mutate(), ContextCompat.getColorStateList(context, R.drawable.switch_camera_mode_selector)); |
||||
|
||||
videoDrawable = ContextCompat.getDrawable(context, R.drawable.ic_videocam_white_24dp); |
||||
videoDrawable = DrawableCompat.wrap(videoDrawable); |
||||
DrawableCompat.setTintList(videoDrawable.mutate(), ContextCompat.getColorStateList(context, R.drawable.switch_camera_mode_selector)); |
||||
|
||||
setBackgroundResource(R.drawable.circle_frame_background_dark); |
||||
// setBackgroundResource(R.drawable.circle_frame_background);
|
||||
|
||||
setOnClickListener(new OnClickListener() { |
||||
@Override |
||||
public void onClick(View view) { |
||||
if(onMediaActionStateChangeListener != null) { |
||||
onMediaActionStateChangeListener.switchAction(); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
padding = Utils.convertDipToPixels(context, padding); |
||||
setPadding(padding, padding, padding, padding); |
||||
} |
||||
|
||||
public void displayActionWillSwitchPhoto(){ |
||||
setImageDrawable(photoDrawable); |
||||
} |
||||
|
||||
public void displayActionWillSwitchVideo(){ |
||||
setImageDrawable(videoDrawable); |
||||
} |
||||
|
||||
public void setOnMediaActionStateChangeListener(OnMediaActionStateChangeListener onMediaActionStateChangeListener) { |
||||
this.onMediaActionStateChangeListener = onMediaActionStateChangeListener; |
||||
} |
||||
|
||||
@Override |
||||
public void setEnabled(boolean enabled) { |
||||
super.setEnabled(enabled); |
||||
if (Build.VERSION.SDK_INT > 10) { |
||||
if (enabled) { |
||||
setAlpha(1f); |
||||
} else { |
||||
setAlpha(0.5f); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,112 @@ |
||||
package com.github.florent37.camerafragment.widgets; |
||||
|
||||
import android.content.Context; |
||||
import android.graphics.drawable.Drawable; |
||||
import android.os.Build; |
||||
import android.support.annotation.NonNull; |
||||
import android.support.annotation.Nullable; |
||||
import android.support.v4.content.ContextCompat; |
||||
import android.util.AttributeSet; |
||||
import android.view.View; |
||||
import android.widget.ImageButton; |
||||
|
||||
import com.github.florent37.camerafragment.R; |
||||
import com.github.florent37.camerafragment.internal.utils.Utils; |
||||
|
||||
/** |
||||
* Created by memfis on 7/6/16. |
||||
*/ |
||||
public class RecordButton extends ImageButton { |
||||
|
||||
public interface RecordButtonListener { |
||||
void onRecordButtonClicked(); |
||||
} |
||||
|
||||
private Drawable takePhotoDrawable; |
||||
private Drawable startRecordDrawable; |
||||
private Drawable stopRecordDrawable; |
||||
private int iconPadding = 8; |
||||
private int iconPaddingStop = 18; |
||||
|
||||
@Nullable |
||||
private RecordButtonListener listener; |
||||
|
||||
public RecordButton(@NonNull Context context) { |
||||
this(context, null, 0); |
||||
} |
||||
|
||||
public RecordButton(@NonNull Context context, AttributeSet attrs) { |
||||
this(context, attrs, 0); |
||||
} |
||||
|
||||
public RecordButton(@NonNull Context context, AttributeSet attrs, int defStyleAttr) { |
||||
super(context, attrs, defStyleAttr); |
||||
takePhotoDrawable = ContextCompat.getDrawable(context, R.drawable.take_photo_button); |
||||
startRecordDrawable = ContextCompat.getDrawable(context, R.drawable.start_video_record_button); |
||||
stopRecordDrawable = ContextCompat.getDrawable(context, R.drawable.stop_button_background); |
||||
|
||||
if (Build.VERSION.SDK_INT > 15) |
||||
setBackground(ContextCompat.getDrawable(context, R.drawable.circle_frame_background)); |
||||
else |
||||
setBackgroundDrawable(ContextCompat.getDrawable(context, R.drawable.circle_frame_background)); |
||||
|
||||
setOnClickListener(new OnClickListener() { |
||||
private final static int CLICK_DELAY = 1000; |
||||
|
||||
private long lastClickTime = 0; |
||||
|
||||
@Override |
||||
public void onClick(View view) { |
||||
if (System.currentTimeMillis() - lastClickTime < CLICK_DELAY) { |
||||
return; |
||||
} else lastClickTime = System.currentTimeMillis(); |
||||
|
||||
if(listener != null) { |
||||
listener.onRecordButtonClicked(); |
||||
} |
||||
} |
||||
}); |
||||
setSoundEffectsEnabled(false); |
||||
setIconPadding(iconPadding); |
||||
} |
||||
|
||||
//public void setup(@Configuration.MediaAction int mediaAction) {
|
||||
// setMediaAction(mediaAction);
|
||||
//}
|
||||
|
||||
private void setIconPadding(int paddingDP) { |
||||
int padding = Utils.convertDipToPixels(getContext(), paddingDP); |
||||
setPadding(padding, padding, padding, padding); |
||||
} |
||||
|
||||
//public void setMediaAction(@Configuration.MediaAction int mediaAction) {
|
||||
// this.mediaAction = mediaAction;
|
||||
// if (listener != null) {
|
||||
// if (Configuration.MEDIA_ACTION_PHOTO == mediaAction) {
|
||||
// listener.setRecordState(Record.TAKE_PHOTO_STATE);
|
||||
// } else {
|
||||
// listener.setRecordState(Record.READY_FOR_RECORD_STATE);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
public void setRecordButtonListener(@NonNull RecordButtonListener listener) { |
||||
this.listener = listener; |
||||
} |
||||
|
||||
public void displayVideoRecordStateReady(){ |
||||
setImageDrawable(startRecordDrawable); |
||||
setIconPadding(iconPadding); |
||||
} |
||||
|
||||
public void displayVideoRecordStateInProgress(){ |
||||
setImageDrawable(stopRecordDrawable); |
||||
setIconPadding(iconPaddingStop); |
||||
} |
||||
|
||||
public void displayPhotoState(){ |
||||
setImageDrawable(takePhotoDrawable); |
||||
setIconPadding(iconPadding); |
||||
} |
||||
|
||||
} |
After Width: | Height: | Size: 274 B |
After Width: | Height: | Size: 224 B |
After Width: | Height: | Size: 221 B |
After Width: | Height: | Size: 406 B |
After Width: | Height: | Size: 188 B |
After Width: | Height: | Size: 188 B |
After Width: | Height: | Size: 379 B |
After Width: | Height: | Size: 306 B |
After Width: | Height: | Size: 217 B |
After Width: | Height: | Size: 150 B |
After Width: | Height: | Size: 364 B |
After Width: | Height: | Size: 391 B |
After Width: | Height: | Size: 460 B |
After Width: | Height: | Size: 173 B |
After Width: | Height: | Size: 196 B |
After Width: | Height: | Size: 193 B |
After Width: | Height: | Size: 164 B |
After Width: | Height: | Size: 175 B |
After Width: | Height: | Size: 269 B |
After Width: | Height: | Size: 119 B |
After Width: | Height: | Size: 139 B |
After Width: | Height: | Size: 263 B |
After Width: | Height: | Size: 223 B |
After Width: | Height: | Size: 158 B |
After Width: | Height: | Size: 127 B |
After Width: | Height: | Size: 240 B |
After Width: | Height: | Size: 244 B |
After Width: | Height: | Size: 326 B |
After Width: | Height: | Size: 131 B |