Jetpack Compose periodically update and draw in another thread as SurfaceView - android

How can i implement counterpart of SurfaceView used with another thread to draw and update in a specific interval in Jetpack Compose?
And with coroutines i use it like this
abstract class CoroutineSurfaceView : SurfaceView, SurfaceHolder.Callback,
DefaultLifecycleObserver {
// Handle works in thread that exception is caught that are
private val handler = CoroutineExceptionHandler { coroutineContext, throwable ->
}
internal lateinit var canvas: Canvas
var framePerSecond = 60
private var renderTime = 100L / framePerSecond
private val coroutineScope = CoroutineScope(handler + SupervisorJob() + Dispatchers.Default)
private lateinit var job: Job
private lateinit var surfaceHolder: SurfaceHolder
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context)
}
constructor(context: Context) : super(context) {
init(context)
}
open fun init(context: Context) {
surfaceHolder = this.holder
surfaceHolder.addCallback(this)
setZOrderOnTop(true)
}
override fun surfaceCreated(holder: SurfaceHolder) {
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
}
private fun startCoroutineRendering() {
job = coroutineScope.launch {
while (isActive) {
if (!surfaceHolder.surface.isValid) {
continue
}
canvas = surfaceHolder.lockCanvas()
update()
render(canvas)
holder.unlockCanvasAndPost(canvas)
delay(renderTime)
}
}
}
internal abstract fun update()
internal abstract fun render(canvas: Canvas)
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
startCoroutineRendering()
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
coroutineScope.launch(Dispatchers.Main.immediate) {
job.cancelAndJoin()
}
}
}

#Composable
fun EachFrameUpdatingCanvas(modifier: Modifier, onDraw: DrawScope.(Long) -> Unit) {
var frameTime by remember { mutableStateOf(0L) }
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(Unit) {
lifecycleOwner.whenStarted {
while (true) {
// this will be called for each frame
// by updating `remember` value we initiating EachFrameUpdatingCanvas redraw
frameTime = withFrameMillis { it }
}
}
}
Canvas(modifier = modifier) {
// you had to use frameTime somewhere in EachFrameUpdatingCanvas
// otherwise it won't be redrawn. But you don't have to pass it to `onDraw` if you don't want
onDraw(frameTime)
}
}
Use it like this:
EachFrameUpdatingCanvas(Modifier.fillMaxSize()) { frameTime ->
drawCircle(
Color.Black,
radius = size.minDimension / 2.0f * (frameTime % 100) / 100f,
)
}
Drawing in compose is done on the background, but if you have some heavy calculations during drawing(inside DrawScope), you probably have to wrap your CoroutineSurfaceView with AndroidView

Related

MapBox speedLimit return null value in android

I would like to use Mapbox to get the road speed limit value but returned the value from LocationObserver always is null while speed limit value in the official map box app has value In the same direction. how can i fix this problem?
setup mapBoxNavigation:
if (!MapboxNavigationApp.isSetup()) {
MapboxNavigationApp.setup {
NavigationOptions.Builder(this)
.accessToken(MAPBOX_ACCESS_TOKEN)
.build()
}
}
lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
MapboxNavigationApp.attach(owner)
}
override fun onPause(owner: LifecycleOwner) {
MapboxNavigationApp.detach(owner)
}
})
MapboxNavigationApp.current()?.startTripSession()
This is observer dataSource:
class MapBoxLocationObserverDataSource #Inject constructor(context: Context) :
MapboxNavigationObserver, MapBoxLocationObserver {
private val speedLimitCallback = MutableStateFlow<NavigationInfo?>(null)
override val speedLimit: Flow<NavigationInfo?>
get() = speedLimitCallback
private val locationObserver = object : LocationObserver {
override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
MapboxSpeedInfoApi().updatePostedAndCurrentSpeed(
locationMatcherResult,
DistanceFormatterOptions.Builder(context).build(),
).apply {
speedLimitCallback.value =
NavigationInfo(speedInfo = this, locationMatcherResult = locationMatcherResult)
}
}
override fun onNewRawLocation(rawLocation: Location) = Unit
}
override fun onAttached(mapboxNavigation: MapboxNavigation) {
mapboxNavigation.registerLocationObserver(locationObserver)
}
override fun onDetached(mapboxNavigation: MapboxNavigation) {
mapboxNavigation.unregisterLocationObserver(locationObserver)
}
data class NavigationInfo(
val speedInfo: SpeedInfoValue,
val locationMatcherResult: LocationMatcherResult
)
}
The speed limit always is null in the LocationMatcherResult but some value are not, like current speed or enhancedLocation

How to extract detected faces to bitmaps?

I have following code in place
GraphicOverlay.kt
open class GraphicOverlay(context: Context?, attrs: AttributeSet?) :
View(context, attrs) {
private val lock = Any()
private val graphics: MutableList<Graphic> = ArrayList()
var mScale: Float? = null
var mOffsetX: Float? = null
var mOffsetY: Float? = null
var cameraSelector: Int = CameraSelector.LENS_FACING_FRONT
abstract class Graphic(private val overlay: GraphicOverlay) {
abstract fun draw(canvas: Canvas?)
fun calculateRect(height: Float, width: Float, boundingBoxT: Rect): RectF {
// for land scape
fun isLandScapeMode(): Boolean {
return overlay.context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
}
fun whenLandScapeModeWidth(): Float {
return when (isLandScapeMode()) {
true -> width
false -> height
}
}
fun whenLandScapeModeHeight(): Float {
return when (isLandScapeMode()) {
true -> height
false -> width
}
}
val scaleX = overlay.width.toFloat() / whenLandScapeModeWidth()
val scaleY = overlay.height.toFloat() / whenLandScapeModeHeight()
val scale = scaleX.coerceAtLeast(scaleY)
overlay.mScale = scale
// Calculate offset (we need to center the overlay on the target)
val offsetX = (overlay.width.toFloat() - ceil(whenLandScapeModeWidth() * scale)) / 2.0f
val offsetY =
(overlay.height.toFloat() - ceil(whenLandScapeModeHeight() * scale)) / 2.0f
overlay.mOffsetX = offsetX
overlay.mOffsetY = offsetY
val mappedBox = RectF().apply {
left = boundingBoxT.right * scale + offsetX
top = boundingBoxT.top * scale + offsetY
right = boundingBoxT.left * scale + offsetX
bottom = boundingBoxT.bottom * scale + offsetY
}
// for front mode
if (overlay.isFrontMode()) {
val centerX = overlay.width.toFloat() / 2
mappedBox.apply {
left = centerX + (centerX - left)
right = centerX - (right - centerX)
}
}
return mappedBox
}
}
fun isFrontMode() = cameraSelector == CameraSelector.LENS_FACING_FRONT
fun toggleSelector() {
cameraSelector =
if (cameraSelector == CameraSelector.LENS_FACING_BACK) CameraSelector.LENS_FACING_FRONT
else CameraSelector.LENS_FACING_BACK
}
fun clear() {
synchronized(lock) { graphics.clear() }
postInvalidate()
}
fun add(graphic: Graphic) {
synchronized(lock) { graphics.add(graphic) }
}
fun remove(graphic: Graphic) {
synchronized(lock) { graphics.remove(graphic) }
postInvalidate()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
synchronized(lock) {
for (graphic in graphics) {
graphic.draw(canvas)
}
}
}
}
FaceContourGraphic.kt
class FaceContourGraphic(
overlay: GraphicOverlay,
private val face: Face,
private val imageRect: Rect
) : GraphicOverlay.Graphic(overlay) {
private val facePositionPaint: Paint
private val idPaint: Paint
private val boxPaint: Paint
init {
val selectedColor = Color.WHITE
facePositionPaint = Paint()
facePositionPaint.color = selectedColor
idPaint = Paint()
idPaint.color = selectedColor
boxPaint = Paint()
boxPaint.color = selectedColor
boxPaint.style = Paint.Style.STROKE
boxPaint.strokeWidth = BOX_STROKE_WIDTH
}
override fun draw(canvas: Canvas?) {
val rect = calculateRect(
imageRect.height().toFloat(),
imageRect.width().toFloat(),
face.boundingBox
)
canvas?.drawRect(rect, boxPaint)
}
companion object {
private const val BOX_STROKE_WIDTH = 5.0f
}
}
SelfieAnalyzer.kt
abstract class SelfieAnalyzer<T> : ImageAnalysis.Analyzer {
abstract val graphicOverlay: GraphicOverlay
#SuppressLint("UnsafeExperimentalUsageError")
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image
mediaImage?.let { image ->
detectInImage(InputImage.fromMediaImage(image, imageProxy.imageInfo.rotationDegrees))
.addOnSuccessListener { results ->
onSuccess(
results,
graphicOverlay,
image.cropRect
)
imageProxy.close()
}
.addOnFailureListener {
onFailure(it)
imageProxy.close()
}
}
}
protected abstract fun detectInImage(image: InputImage): Task<T>
abstract fun stop()
protected abstract fun onSuccess(
results: T,
graphicOverlay: GraphicOverlay,
rect: Rect
)
protected abstract fun onFailure(e: Exception)
companion object {
const val TAG: String = "SelfieAnalyzer"
}
}
FaceContourDetectionProcessor.kt
class FaceContourDetectionProcessor(private val view: GraphicOverlay) :
SelfieAnalyzer<List<Face>>() {
private val realTimeOpts = FaceDetectorOptions.Builder()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
.setContourMode(FaceDetectorOptions.CONTOUR_MODE_NONE)
.build()
private val detector = FaceDetection.getClient(realTimeOpts)
override val graphicOverlay: GraphicOverlay
get() = view
override fun detectInImage(image: InputImage): Task<List<Face>> {
return detector.process(image)
}
override fun stop() {
try {
detector.close()
} catch (e: IOException) {
Log.e(TAG, "Exception thrown while trying to close Face Detector: $e")
}
}
override fun onSuccess(
results: List<Face>,
graphicOverlay: GraphicOverlay,
rect: Rect
) {
graphicOverlay.clear()
results.forEach { face ->
val faceGraphic = FaceContourGraphic(graphicOverlay, face, rect)
graphicOverlay.add(faceGraphic)
}
graphicOverlay.postInvalidate()
}
override fun onFailure(e: Exception) {
Log.w(TAG, "Face Detector failed.$e")
}
companion object {
private const val TAG = "FaceDetectionProcessor"
}
}
SelfieFragment.kt
class SelfieFragment : Fragment() {
// runs on camera permission grant success
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener({
// Used to bind the lifecycle of cameras to the lifecycle owner
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
// Preview
val preview = Preview.Builder().build()
preview.setSurfaceProvider(binding.viewFinder.surfaceProvider)
imageCapture = ImageCapture.Builder().build()
val imageAnalyzer = ImageAnalysis.Builder()
.setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST)
.build()
.also {
it.setAnalyzer(
cameraExecutor,
selectAnalyzer()
)
}
// Select front camera as a default
val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
// Bind use cases to camera
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, imageAnalyzer
)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(context))
}
}
}
fragment_selfie.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ui.fragments.SelfieFragment">
<androidx.camera.view.PreviewView
android:id="#+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.example.appname.customviews.GraphicOverlay
android:id="#+id/graphic_overlay"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="#id/viewFinder"
app:layout_constraintLeft_toLeftOf="#id/viewFinder"
app:layout_constraintRight_toRightOf="#id/viewFinder"
app:layout_constraintTop_toTopOf="#id/viewFinder" />
<com.google.android.material.button.MaterialButton
android:id="#+id/btnCamera"
style="#style/Widget.MaterialComponents.Button.OutlinedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="56dp"
android:layout_marginBottom="16dp"
android:background="#android:color/transparent"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
app:icon="#drawable/ic_photo_camera_48"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
I want to create bitmap of detected faces but I do not know how, because Face api does not provide an way.
GraphicOverlay extends View and View can be rendered to Bitmap... here's an example. When wanting to render individual FaceContourGraphic, one may have to use a temporary View - so that one can add one FaceContourGraphic, render the output to Bitmap and then proceed with the next one FaceContourGraphic.

Kotlin " surfaceChanged", "surfaceDestroyed",SurfaceCreated", overrides nothing

I am using surfaceview inside a fragment, however the methods of the surfaceHolder.Callback(surfaceCreated,surfaceChanged, and surfaceDestroyed) seems to overrides nothing.and I also get the following error " object is not abstract and does not implement abstract member".
Below is the error in the image.
e: /Users/malorimorow/AndroidStudioProjects/CapstoneProject/app/src/main/java/com/example/capstoneproject/main/ScannerFragment.kt: (158, 32): Object is not abstract and does not implement abstract member public abstract fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int): Unit defined in android.view.SurfaceHolder.Callback
Below is the source code and you can find the error in the overlay.apply method.I am new to kotlin and would greatly appreciate the help.
class ScannerFragment : Fragment() {
companion object {
fun newInstance() = ScannerFragment()
// We only need to analyze the part of the image that has text, so we set crop percentages
// to avoid analyze the entire image from the live camera feed.
const val DESIRED_WIDTH_CROP_PERCENT = 8
const val DESIRED_HEIGHT_CROP_PERCENT = 74
// This is an arbitrary number we are using to keep tab of the permission
// request. Where an app has multiple context for requesting permission,
// this can help differentiate the different contexts
private const val REQUEST_CODE_PERMISSIONS = 10
// This is an array of all the permission specified in the manifest
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
private const val RATIO_4_3_VALUE = 4.0 / 3.0
private const val RATIO_16_9_VALUE = 16.0 / 9.0
private const val TAG = "ScannerFragment"
}
private var displayId: Int = -1
private val viewModel: MainViewModel by viewModels()
private var cameraProvider: ProcessCameraProvider? = null
private var camera: Camera? = null
private var imageAnalyzer: ImageAnalysis? = null
private lateinit var container: ConstraintLayout
private lateinit var viewFinder: PreviewView
/** Blocking camera operations are performed using this executor */
private lateinit var cameraExecutor: ExecutorService
private lateinit var scopedExecutor: ScopedExecutor
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_scanner, container, false)
}
override fun onDestroyView() {
super.onDestroyView()
// Shut down our background executor
cameraExecutor.shutdown()
scopedExecutor.shutdown()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
container = view as ConstraintLayout
viewFinder = container.findViewById(R.id.viewfinder)
// Initialize our background executor
cameraExecutor = Executors.newSingleThreadExecutor()
scopedExecutor = ScopedExecutor(cameraExecutor)
// Request camera permissions
if (allPermissionsGranted()) {
// Wait for the views to be properly laid out
viewFinder.post {
// Keep track of the display in which this view is attached
displayId = viewFinder.display.displayId
// Set up the camera and its use cases
setUpCamera()
}
} else {
requestPermissions(
REQUIRED_PERMISSIONS,
REQUEST_CODE_PERMISSIONS
)
}
// Get available language list and set up the target language spinner
// with default selections.
val adapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_dropdown_item, viewModel.availableLanguages
)
targetLangSelector.adapter = adapter
targetLangSelector.setSelection(adapter.getPosition(Language("en")))
targetLangSelector.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>,
view: View?,
position: Int,
id: Long
) {
viewModel.targetLang.value = adapter.getItem(position)
}
override fun onNothingSelected(parent: AdapterView<*>) {}
}
viewModel.sourceLang.observe(viewLifecycleOwner, Observer { srcLang.text = it.displayName })
viewModel.translatedText.observe(viewLifecycleOwner, Observer { resultOrError ->
resultOrError?.let {
if (it.error != null) {
translatedText.error = resultOrError.error?.localizedMessage
} else {
translatedText.text = resultOrError.result
}
}
})
viewModel.modelDownloading.observe(viewLifecycleOwner, Observer { isDownloading ->
progressBar.visibility = if (isDownloading) {
View.VISIBLE
} else {
View.INVISIBLE
}
progressText.visibility = progressBar.visibility
})
overlay.apply {
setZOrderOnTop(true)
holder.setFormat(PixelFormat.TRANSPARENT)
holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceChanged(
holder: SurfaceHolder?,
format: Int,
width: Int,
height: Int
) {
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
}
override fun surfaceCreated(holder: SurfaceHolder?) {
holder?.let { drawOverlay(it,
DESIRED_HEIGHT_CROP_PERCENT,
DESIRED_WIDTH_CROP_PERCENT
) }
}
})
}
}
/** Initialize CameraX, and prepare to bind the camera use cases */
private fun setUpCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener(Runnable {
// CameraProvider
cameraProvider = cameraProviderFuture.get()
// Build and bind the camera use cases
bindCameraUseCases()
}, ContextCompat.getMainExecutor(requireContext()))
}
private fun bindCameraUseCases() {
val cameraProvider = cameraProvider
?: throw IllegalStateException("Camera initialization failed.")
// Get screen metrics used to setup camera for full screen resolution
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
Log.d(TAG, "Screen metrics: ${metrics.widthPixels} x ${metrics.heightPixels}")
val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
Log.d(TAG, "Preview aspect ratio: $screenAspectRatio")
val rotation = viewFinder.display.rotation
val preview = Preview.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(rotation)
.build()
// Build the image analysis use case and instantiate our analyzer
imageAnalyzer = ImageAnalysis.Builder()
// We request aspect ratio but no resolution
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(rotation)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also {
it.setAnalyzer(
cameraExecutor
, TextAnalyzer(
requireContext(),
lifecycle,
viewModel.sourceText,
viewModel.imageCropPercentages
)
)
}
viewModel.sourceText.observe(viewLifecycleOwner, Observer { srcText.text = it })
viewModel.imageCropPercentages.observe(viewLifecycleOwner,
Observer { drawOverlay(overlay.holder, it.first, it.second) })
// Select back camera since text detection does not work with front camera
val cameraSelector =
CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
// Bind use cases to camera
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageAnalyzer
)
preview.setSurfaceProvider(viewFinder.createSurfaceProvider())
} catch (exc: IllegalStateException) {
Log.e(TAG, "Use case binding failed. This must be running on main thread.", exc)
}
}
private fun drawOverlay(
holder: SurfaceHolder,
heightCropPercent: Int,
widthCropPercent: Int
) {
val canvas = holder.lockCanvas()
val bgPaint = Paint().apply {
alpha = 140
}
canvas.drawPaint(bgPaint)
val rectPaint = Paint()
rectPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
rectPaint.style = Paint.Style.FILL
rectPaint.color = Color.WHITE
val outlinePaint = Paint()
outlinePaint.style = Paint.Style.STROKE
outlinePaint.color = Color.WHITE
outlinePaint.strokeWidth = 4f
val surfaceWidth = holder.surfaceFrame.width()
val surfaceHeight = holder.surfaceFrame.height()
val cornerRadius = 25f
// Set rect centered in frame
val rectTop = surfaceHeight * heightCropPercent / 2 / 100f
val rectLeft = surfaceWidth * widthCropPercent / 2 / 100f
val rectRight = surfaceWidth * (1 - widthCropPercent / 2 / 100f)
val rectBottom = surfaceHeight * (1 - heightCropPercent / 2 / 100f)
val rect = RectF(rectLeft, rectTop, rectRight, rectBottom)
canvas.drawRoundRect(
rect, cornerRadius, cornerRadius, rectPaint
)
canvas.drawRoundRect(
rect, cornerRadius, cornerRadius, outlinePaint
)
val textPaint = Paint()
textPaint.color = Color.WHITE
textPaint.textSize = 50F
val overlayText = getString(R.string.overlay_help)
val textBounds = Rect()
textPaint.getTextBounds(overlayText, 0, overlayText.length, textBounds)
val textX = (surfaceWidth - textBounds.width()) / 2f
val textY = rectBottom + textBounds.height() + 15f // put text below rect and 15f padding
canvas.drawText(getString(R.string.overlay_help), textX, textY, textPaint)
holder.unlockCanvasAndPost(canvas)
}
/**
* [androidx.camera.core.ImageAnalysisConfig] requires enum value of
* [androidx.camera.core.AspectRatio]. Currently it has values of 4:3 & 16:9.
*
* Detecting the most suitable ratio for dimensions provided in #params by comparing absolute
* of preview ratio to one of the provided values.
*
* #param width - preview width
* #param height - preview height
* #return suitable aspect ratio
*/
private fun aspectRatio(width: Int, height: Int): Int {
val previewRatio = ln(max(width, height).toDouble() / min(width, height))
if (abs(previewRatio - ln(RATIO_4_3_VALUE))
<= abs(previewRatio - ln(RATIO_16_9_VALUE))
) {
return AspectRatio.RATIO_4_3
}
return AspectRatio.RATIO_16_9
}
/**
* Process result from permission request dialog box, has the request
* been granted? If yes, start Camera. Otherwise display a toast
*/
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray
) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
viewFinder.post {
// Keep track of the display in which this view is attached
displayId = viewFinder.display.displayId
// Set up the camera and its use cases
setUpCamera()
}
} else {
Toast.makeText(
context,
"Permissions not granted by the user.",
Toast.LENGTH_SHORT
).show()
}
}
}
/**
* Check if all permission specified in the manifest have been granted
*/
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(
requireContext(), it
) == PackageManager.PERMISSION_GRANTED
}
}
Its probably because of Version confict . Just Remove all 3 methods and implement them with ALT+Enter . again .
As the error says surfaceChanged defined as follows
fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int): Unit
But when you are implementing it you are making SurfaceHolder parameter null-able.
holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceChanged(
holder: SurfaceHolder,
format: Int,
width: Int,
height: Int
) {
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
}
override fun surfaceCreated(holder: SurfaceHolder) {
holder?.let { drawOverlay(it,
DESIRED_HEIGHT_CROP_PERCENT,
DESIRED_WIDTH_CROP_PERCENT
) }
}
})

Exoplayer is removing preview with PageTransformer

I have a viewPager with CubeTransformer, which is transforming every fragment. And inside every fragment is Image or Video view (with Exoplayer). And when you are trying to change a framgnet with transformation, exoplayer losses preview (I've got a black screen), even it's not playing. But after you changing condition to normal, preview is coming back
Ohterwise, if you will remove pageTransformer, review is not dissapears. How to keep preview always on screen?
CubeTransformer
class CubeTransformer : ViewPager.PageTransformer {
override fun transformPage(view: View, position: Float) {
if (view.visibility != View.VISIBLE) return
view.apply {
cameraDistance = (view.width * distanceMultiplier).toFloat()
pivotX = if (position < 0f) view.width.toFloat() else 0f
pivotY = view.height * 0.5f
rotationY = 90f * position
if (position < -1 || position > 1) {
alpha = 0f // item not visible
} else {
alpha = 1f
}
}
}
private companion object {
private const val distanceMultiplier: Int = 20
}
}
VideoView
class VideoView(context: Context) : ConstraintLayout(context, null) {
private val player = ExoPlayerFactory.newSimpleInstance(context, DefaultTrackSelector(), DefaultLoadControl())
private val dataSourceFactory = DefaultDataSourceFactory(context, "android")
private lateinit var model: Model
init {
inflate(context, R.layout.story_item_video, this)
video_view.player = player
video_view.keepScreenOn = true
video_view.setBackgroundColor(Color.TRANSPARENT)
video_view.setShutterBackgroundColor(Color.TRANSPARENT)
}
fun setData(model: Model?) {
if (model== null) return
this.model = model
val mediaSource = HlsMediaSource
.Factory(dataSourceFactory)
.setExtractorFactory(DefaultHlsExtractorFactory())
.createMediaSource(Uri.parse(model.streamLink))
player.playWhenReady = true
player.prepare(mediaSource)
player.addListener(object: Player.EventListener {
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters?) {
}
override fun onSeekProcessed() {}
override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) {
}
override fun onPlayerError(error: ExoPlaybackException?) {
}
override fun onLoadingChanged(isLoading: Boolean) {
}
override fun onPositionDiscontinuity(reason: Int) {
}
override fun onRepeatModeChanged(repeatMode: Int) {
}
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
}
override fun onTimelineChanged(timeline: Timeline?, manifest: Any?, reason: Int) {
}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
}
})
}
}
After a day of searching, I've found an answer to my question. You just need to add app:surface_type="texture_view" to your PlayerView

Android, passing data to the viewModel and then do work on the view

In hopes to better understand MVVM I decided to redo one of my Android projects implementing the architecture.
One of the activities uses the Zxing library to scan a QR code. Using Zxing's event handler I would like to pass the scanned result to my viewModel to check for example if the result is a "product"(this is a QR code I generate following a product class) or not. If the the result is a product it will show a dialogue with the result asking the user if they want to continue scanning or save the product, if it is not a product then it shows a dialogue with the result and a cancel button.
Now the issue here is that I don't know how to properly pass the data from Zxing's handler to the viewModel. Below attached my activity.
class QRreader_Activity : AppCompatActivity(), ZXingScannerView.ResultHandler, IQRreader{
override fun isProduct(ProductDetail: String) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun isNotProduct(ScannedText: String) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
lateinit var mScannerView: ZXingScannerView
var data: String="test"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_qrreader)
val bReturn: ImageButton = findViewById(R.id.ic_return)
bReturn.setOnClickListener {
onBackPressed() }
ActivityCompat.requestPermissions(this#QRreader_Activity,
arrayOf(Manifest.permission.CAMERA),
1)
val flashlightCheckBox = findViewById<CheckBox>(R.id.flashlight_checkbox)
val contentFrame = findViewById<ViewGroup>(R.id.content_frame)
mScannerView = object : ZXingScannerView(this) {
override fun createViewFinderView(context: Context): IViewFinder {
return CustomViewFinderView(context)
}
}
contentFrame.addView(mScannerView)
flashlightCheckBox.setOnCheckedChangeListener { compoundButton, isChecked -> mScannerView.flash = isChecked }
}
public override fun onResume() {
super.onResume()
mScannerView.setResultHandler(this) // Register ourselves as a handler for scan results.
mScannerView.startCamera() // Start camera on resume
}
public override fun onPause() {
super.onPause()
mScannerView.stopCamera() // Stop camera on pause
}
override fun onBackPressed() {
super.onBackPressed()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
//This is where ZXing provides the result of the scan
override fun handleResult(rawResult: Result) {
Toast.makeText(this,""+rawResult.text,Toast.LENGTH_LONG).show()
}
private class CustomViewFinderView : ViewFinderView {
val PAINT = Paint()
constructor(context: Context) : super(context) {
init()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init()
}
private fun init() {
PAINT.color = Color.WHITE
PAINT.isAntiAlias = true
val textPixelSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
TRADE_MARK_TEXT_SIZE_SP.toFloat(), resources.displayMetrics)
PAINT.textSize = textPixelSize
setSquareViewFinder(true)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawTradeMark(canvas)
}
private fun drawTradeMark(canvas: Canvas) {
val framingRect = framingRect
val tradeMarkTop: Float
val tradeMarkLeft: Float
if (framingRect != null) {
tradeMarkTop = framingRect.bottom.toFloat() + PAINT.textSize + 10f
tradeMarkLeft = framingRect.left.toFloat()
} else {
tradeMarkTop = 10f
tradeMarkLeft = canvas.height.toFloat() - PAINT.textSize - 10f
}
canvas.drawText(TRADE_MARK_TEXT, tradeMarkLeft, tradeMarkTop, PAINT)
}
companion object {
val TRADE_MARK_TEXT = ""
val TRADE_MARK_TEXT_SIZE_SP = 40
}
}
}
The solution I found was to have the view model implement "ZXingScannerView.ResultHandler" interface. I don't know if this is the optimal solution though.

Categories

Resources