-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
App foundation: introducing CameraX #23
Changes from 10 commits
c4d56ed
a2673b6
49286a8
ff17c99
2e4b24d
30c248e
14dee59
9358e8b
8cdd629
4944f99
2f4c6aa
78054d2
bf3b76a
26767fc
e0f25fa
bbcf4df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,3 +42,40 @@ dependencies { | |
androidTestImplementation 'androidx.test:runner:1.2.0' | ||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' | ||
} | ||
|
||
android.buildTypes.all { buildType -> | ||
// Add properties named "portkey.xxx" to our BuildConfig | ||
def inputFile = checkGradlePropertiesFile() | ||
def properties = loadPropertiesFromFile(inputFile) | ||
properties.any { property -> | ||
if (property.key.toLowerCase().startsWith("portkey.use.")) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting - in WPAndroid we don't have this handling and instead just use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TBH that was a TIL for me as well when I introduced / borrowed these scripts :D |
||
buildType.buildConfigField "boolean", property.key.replace("portkey.", "").replace(".", "_").toUpperCase(), | ||
"${property.value}" | ||
} | ||
else if (property.key.toLowerCase().startsWith("portkey.")) { | ||
buildType.buildConfigField "String", property.key.replace("portkey.", "").replace(".", "_").toUpperCase(), | ||
"\"${property.value}\"" | ||
} | ||
else if (property.key.toLowerCase().startsWith("portkey.res.")) { | ||
buildType.resValue "string", property.key.replace("portkey.res.", "").replace(".", "_").toLowerCase(), | ||
"${property.value}" | ||
} | ||
} | ||
} | ||
|
||
def checkGradlePropertiesFile() { | ||
def inputFile = file("${rootDir}/gradle.properties") | ||
if (!inputFile.exists()) { | ||
throw new StopActionException("Build configuration file gradle.properties doesn't exist, follow README instructions") | ||
} | ||
return inputFile | ||
} | ||
|
||
static def loadPropertiesFromFile(inputFile) { | ||
def properties = new Properties() | ||
inputFile.withInputStream { stream -> | ||
properties.load(stream) | ||
} | ||
return properties | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,19 @@ dependencies { | |
|
||
implementation 'androidx.appcompat:appcompat:1.1.0-beta01' | ||
implementation "androidx.core:core-ktx:+" | ||
|
||
// jetpack camera library versions | ||
// check https://developer.android.com/jetpack/androidx/releases/camera for updates | ||
|
||
// CameraX core library | ||
def camerax_version = "1.0.0-alpha04" | ||
// CameraX view library | ||
def camerax_view_version = "1.0.0-alpha01" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one and the one below seems to be unused. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. addressed in 26767fc |
||
// CameraX extensions library | ||
def camerax_ext_version = "1.0.0-alpha01" | ||
implementation "androidx.camera:camera-core:${camerax_version}" | ||
implementation "androidx.camera:camera-camera2:${camerax_version}" | ||
|
||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" | ||
implementation 'com.github.bumptech.glide:glide:4.9.0' | ||
kapt 'com.github.bumptech.glide:compiler:4.9.0' | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package com.automattic.photoeditor.camera | ||
|
||
import android.annotation.SuppressLint | ||
import android.os.Bundle | ||
import android.util.Log | ||
import androidx.camera.core.CameraX | ||
import androidx.camera.core.Preview | ||
import androidx.camera.core.PreviewConfig | ||
import androidx.camera.core.VideoCapture | ||
import androidx.camera.core.VideoCaptureConfig | ||
import androidx.core.app.ActivityCompat | ||
import com.automattic.photoeditor.R | ||
import com.automattic.photoeditor.camera.interfaces.VideoRecorderFragment | ||
import com.automattic.photoeditor.util.FileUtils | ||
import com.automattic.photoeditor.util.PermissionUtils | ||
import com.automattic.photoeditor.views.background.video.AutoFitTextureView | ||
import java.io.File | ||
|
||
class CameraXBasicHandling : VideoRecorderFragment(), | ||
ActivityCompat.OnRequestPermissionsResultCallback { | ||
private lateinit var videoCapture: VideoCapture | ||
|
||
private var active: Boolean = false | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
retainInstance = true | ||
} | ||
|
||
override fun activate() { | ||
active = true | ||
startUp() | ||
} | ||
|
||
override fun deactivate() { | ||
if (active) { | ||
active = false | ||
windDown() | ||
} | ||
} | ||
|
||
private fun startUp() { | ||
if (active) { | ||
startCamera() | ||
} | ||
} | ||
|
||
private fun windDown() { | ||
if (CameraX.isBound(videoCapture)) { | ||
CameraX.unbind(videoCapture) | ||
} | ||
} | ||
|
||
override fun onRequestPermissionsResult( | ||
requestCode: Int, | ||
permissions: Array<String>, | ||
grantResults: IntArray | ||
) { | ||
if (!PermissionUtils.allRequiredPermissionsGranted(activity!!)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can remove the activity?.let { activity ->
...
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ended up removing this entirely in bbcf4df, to match the same thing we did in e2fe8ee , related comment here: #22 (comment) |
||
ErrorDialog.newInstance(getString(R.string.request_permissions)) | ||
.show(childFragmentManager, | ||
FRAGMENT_DIALOG | ||
) | ||
} else { | ||
super.onRequestPermissionsResult(requestCode, permissions, grantResults) | ||
} | ||
} | ||
|
||
// TODO remove this RestrictedApi annotation once androidx.camera:camera moves out of alpha | ||
@SuppressLint("RestrictedApi") | ||
private fun startCamera() { | ||
// Create configuration object for the preview use case | ||
val previewConfig = PreviewConfig.Builder().build() | ||
val preview = Preview(previewConfig) | ||
|
||
// Create a configuration object for the video capture use case | ||
val videoCaptureConfig = VideoCaptureConfig.Builder().apply { | ||
setTargetRotation(textureView.display.rotation) | ||
}.build() | ||
videoCapture = VideoCapture(videoCaptureConfig) | ||
|
||
preview.setOnPreviewOutputUpdateListener { | ||
textureView.surfaceTexture = it.surfaceTexture | ||
} | ||
|
||
// Bind use cases to lifecycle | ||
CameraX.bindToLifecycle(activity, preview, videoCapture) | ||
} | ||
|
||
@SuppressLint("RestrictedApi") | ||
override fun startRecordingVideo() { | ||
currentFile = FileUtils.getLoopFrameFile(true, "orig_") | ||
currentFile?.createNewFile() | ||
|
||
videoCapture.startRecording(currentFile, object : VideoCapture.OnVideoSavedListener { | ||
override fun onVideoSaved(file: File?) { | ||
Log.i(tag, "Video File : $file") | ||
} | ||
override fun onError(useCaseError: VideoCapture.UseCaseError?, message: String?, cause: Throwable?) { | ||
Log.i(tag, "Video Error: $message") | ||
} | ||
}) | ||
} | ||
|
||
@SuppressLint("RestrictedApi") | ||
override fun stopRecordingVideo() { | ||
videoCapture.stopRecording() | ||
} | ||
|
||
companion object { | ||
private val instance = CameraXBasicHandling() | ||
|
||
private val FRAGMENT_DIALOG = "dialog" | ||
/** | ||
* Tag for the [Log]. | ||
*/ | ||
private val TAG = "CameraXBasicHandling" | ||
|
||
@JvmStatic fun getInstance(textureView: AutoFitTextureView): CameraXBasicHandling { | ||
instance.textureView = textureView | ||
return instance | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,10 +26,15 @@ import java.io.File | |
import android.media.AudioManager | ||
import android.media.MediaPlayer | ||
import androidx.fragment.app.Fragment | ||
import com.automattic.photoeditor.camera.interfaces.SurfaceFragmentHandler | ||
import com.automattic.photoeditor.views.background.video.AutoFitTextureView | ||
import java.io.FileInputStream | ||
import java.io.IOException | ||
|
||
class VideoPlayingBasicHandling : Fragment(), SurfaceFragmentHandler { | ||
// holds the File handle to the current video file to be played | ||
var currentFile: File? = null | ||
|
||
/** | ||
* [TextureView.SurfaceTextureListener] handles several lifecycle events on a | ||
* [TextureView]. | ||
|
@@ -131,22 +136,26 @@ class VideoPlayingBasicHandling : Fragment(), SurfaceFragmentHandler { | |
stopVideoPlay() | ||
} | ||
|
||
val assetManager = context?.assets | ||
val descriptor = assetManager!!.openFd("small.mp4") | ||
mediaPlayer = MediaPlayer().apply { | ||
// setDataSource("http://techslides.com/demos/sample-videos/small.mp4") | ||
setDataSource(descriptor?.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength()) | ||
setSurface(s) | ||
prepare() | ||
// TODO check whether we want fine grained error handling by setting these listeners | ||
// setOnBufferingUpdateListener(this) | ||
// setOnCompletionListener(this) | ||
// setOnPreparedListener(this) | ||
// setOnVideoSizeChangedListener(this) | ||
setAudioStreamType(AudioManager.STREAM_MUSIC) | ||
start() | ||
if (currentFile != null && currentFile!!.exists()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can Kotlinify this a bit and fix a nullability warning in the code (the call is actually safe but the IDE doesn't know that): currentFile?.takeIf { it.exists() }?.let { file ->
...
val inputStream = FileInputStream(file)
...
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice! addressed in e0f25fa (plus removed some commented code) Also TIL:
|
||
// val assetManager = context?.assets | ||
// val descriptor = assetManager!!.openFd("small.mp4") | ||
val inputStream = FileInputStream(currentFile) | ||
mediaPlayer = MediaPlayer().apply { | ||
setDataSource(inputStream.getFD()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Kotlinification note, not a blocker) This can use property access syntax: |
||
// setDataSource("http://techslides.com/demos/sample-videos/small.mp4") | ||
// setDataSource(descriptor?.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength()) | ||
setSurface(s) | ||
prepare() | ||
// TODO check whether we want fine grained error handling by setting these listeners | ||
// setOnBufferingUpdateListener(this) | ||
// setOnCompletionListener(this) | ||
// setOnPreparedListener(this) | ||
// setOnVideoSizeChangedListener(this) | ||
setAudioStreamType(AudioManager.STREAM_MUSIC) | ||
start() | ||
} | ||
// descriptor?.close() | ||
} | ||
descriptor?.close() | ||
} catch (e: IllegalArgumentException) { | ||
// TODO Auto-generated catch block | ||
e.printStackTrace() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
|
||
package com.automattic.photoeditor.camera.interfaces | ||
|
||
import androidx.fragment.app.Fragment | ||
import com.automattic.photoeditor.views.background.video.AutoFitTextureView | ||
import java.io.File | ||
|
||
abstract class VideoRecorderFragment : Fragment(), VideoRecorderHandler, SurfaceFragmentHandler { | ||
/** | ||
* An [AutoFitTextureView] for camera preview. | ||
*/ | ||
lateinit var textureView: AutoFitTextureView | ||
var currentFile: File? = null | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
|
||
package com.automattic.photoeditor.camera.interfaces | ||
|
||
interface VideoRecorderHandler { | ||
fun startRecordingVideo() | ||
fun stopRecordingVideo() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of this looks good, though I think we should take care of adding
gradle.properties
to.gitignore
sooner than later and add agradle.properties-example
file. I'll open an issue to track that.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opened: #27.