Skip to content

Instantly share code, notes, and snippets.

@GeorgCantor
Created March 14, 2021 18:00
Show Gist options
  • Save GeorgCantor/bfa539242c52e186a8923a36bed45d27 to your computer and use it in GitHub Desktop.
Save GeorgCantor/bfa539242c52e186a8923a36bed45d27 to your computer and use it in GitHub Desktop.
class CameraSourcePreview(
private val mContext: Context,
attrs: AttributeSet?
) : ViewGroup(mContext, attrs) {
private val mSurfaceView: SurfaceView = SurfaceView(mContext)
private var mStartRequested = false
private var mSurfaceAvailable = false
private var mCameraSource: CameraSource? = null
private var mOverlay: GraphicOverlay<*>? = null
@RequiresPermission(Manifest.permission.CAMERA)
@Throws(IOException::class, SecurityException::class)
fun start(cameraSource: CameraSource?) {
if (cameraSource == null) {
stop()
}
mCameraSource = cameraSource
if (mCameraSource != null) {
mStartRequested = true
startIfReady()
}
}
@RequiresPermission(Manifest.permission.CAMERA)
@Throws(IOException::class, SecurityException::class)
fun start(cameraSource: CameraSource?, overlay: GraphicOverlay<*>?) {
mOverlay = overlay
start(cameraSource)
}
fun stop() {
if (mCameraSource != null) {
mCameraSource!!.stop()
}
}
fun release() {
if (mCameraSource != null) {
mCameraSource!!.release()
mCameraSource = null
}
}
@RequiresPermission(Manifest.permission.CAMERA)
@Throws(IOException::class, SecurityException::class)
private fun startIfReady() {
if (mStartRequested && mSurfaceAvailable) {
mCameraSource!!.start(mSurfaceView.holder)
if (mOverlay != null) {
val size: Size? = mCameraSource!!.previewSize
val min = size!!.width.coerceAtMost(size.height)
val max = size.width.coerceAtLeast(size.height)
if (isPortraitMode) {
// Swap width and height sizes when in portrait, since it will be rotated by
// 90 degrees
mOverlay!!.setCameraInfo(min, max, mCameraSource!!.cameraFacing)
} else {
mOverlay!!.setCameraInfo(max, min, mCameraSource!!.cameraFacing)
}
mOverlay!!.clear()
}
mStartRequested = false
}
}
private inner class SurfaceCallback : SurfaceHolder.Callback {
override fun surfaceCreated(surface: SurfaceHolder) {
mSurfaceAvailable = true
try {
startIfReady()
} catch (se: SecurityException) {
Log.e(TAG, "Do not have permission to start the camera", se)
} catch (e: IOException) {
Log.e(TAG, "Could not start camera source.", e)
}
}
override fun surfaceDestroyed(surface: SurfaceHolder) {
mSurfaceAvailable = false
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
var previewWidth = 320
var previewHeight = 240
if (mCameraSource != null) {
val size: Size? = mCameraSource!!.previewSize
if (size != null) {
previewWidth = size.width
previewHeight = size.height
}
}
// Swap width and height sizes when in portrait, since it will be rotated 90 degrees
if (isPortraitMode) {
val tmp = previewWidth
previewWidth = previewHeight
previewHeight = tmp
}
val viewWidth = right - left
val viewHeight = bottom - top
val childWidth: Int
val childHeight: Int
var childXOffset = 0
var childYOffset = 0
val widthRatio = viewWidth.toFloat() / previewWidth.toFloat()
val heightRatio = viewHeight.toFloat() / previewHeight.toFloat()
// To fill the view with the camera preview, while also preserving the correct aspect ratio,
// it is usually necessary to slightly oversize the child and to crop off portions along one
// of the dimensions. We scale up based on the dimension requiring the most correction, and
// compute a crop offset for the other dimension.
if (widthRatio > heightRatio) {
childWidth = viewWidth
childHeight = (previewHeight.toFloat() * widthRatio).toInt()
childYOffset = (childHeight - viewHeight) / 2
} else {
childWidth = (previewWidth.toFloat() * heightRatio).toInt()
childHeight = viewHeight
childXOffset = (childWidth - viewWidth) / 2
}
for (i in 0 until childCount) {
// One dimension will be cropped. We shift child over or up by this offset and adjust
// the size to maintain the proper aspect ratio.
getChildAt(i).layout(
-1 * childXOffset, -1 * childYOffset,
childWidth - childXOffset, childHeight - childYOffset
)
}
try {
startIfReady()
} catch (se: SecurityException) {
Log.e(TAG, "Do not have permission to start the camera", se)
} catch (e: IOException) {
Log.e(TAG, "Could not start camera source.", e)
}
}
private val isPortraitMode: Boolean
get() {
val orientation = mContext.resources.configuration.orientation
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
return false
}
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
return true
}
Log.d(TAG, "isPortraitMode returning false by default")
return false
}
companion object {
private const val TAG = "CameraSourcePreview"
}
init {
mSurfaceView.holder.addCallback(SurfaceCallback())
addView(mSurfaceView)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment