opentok-android-sdk-samples icon indicating copy to clipboard operation
opentok-android-sdk-samples copied to clipboard

Android 14 crash while screen sharing opentok

Open vmeditab opened this issue 1 year ago • 2 comments

My app is getting crashed while screen sharing opentok in Android 14. Below is my code,

 class MediaProjectionService : Service(), ImageReader.OnImageAvailableListener {
    companion object {
    const val TAG = "MediaProjectionService"

    const val FOREGROUND_SERVICE_ID = 1234

    const val NOTIFICATION_CHANNEL_ID = "Notification"
    const val NOTIFICATION_CHANNEL_NAME = "Vonage Video"

    const val SCREEN_CAPTURE_NAME = "screencapture"
    const val MAX_SCREEN_AXIS = 1024
    const val VIRTUAL_DISPLAY_FLAGS =
        DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY or
                DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
}

private var binder: MediaProjectionBinder? = null
private var binderIntent: Intent? = null

private var width = 240
private var height = 320
private var density = 0

private var imageReader: ImageReader? = null
private var virtualDisplay: VirtualDisplay? = null

private lateinit var mediaProjection: MediaProjection

override fun onBind(intent: Intent?): IBinder? {
    Log.d(TAG, "On Bind")
    binder = MediaProjectionBinder()
    binderIntent = intent

    val notification = createNotification()
    startForeground(FOREGROUND_SERVICE_ID, notification)

    // Get Media Projection
    Log.d(TAG, "Getting Media Projection")
    val resultCode = intent?.getIntExtra("resultCode", Activity.RESULT_CANCELED) ?: 0
    val data = intent?.getParcelableExtra("data") ?: Intent()
    val projectionManager =
        getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
    mediaProjection = projectionManager.getMediaProjection(resultCode, data)

    // Get Display
    Log.d(TAG, "Getting Display")
    val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
    val display = windowManager.defaultDisplay

    // Metrics
    Log.d(TAG, "Getting Metrics")
    val metrics = DisplayMetrics()
    display.getMetrics(metrics)

    // Size
    Log.d(TAG, "Getting Size")
    val size = Point()
    display.getRealSize(size)
    Log.d(TAG, "Size: ${size.x} x ${size.y}")
    width = size.x
    height = size.y


    // Density
    Log.d(TAG, "Getting Density")
    density = metrics.densityDpi
    Log.d(TAG, "Density: $density")

    // Create Virtual Display
    createVirtualDisplay()

    return binder
}

override fun onUnbind(intent: Intent?): Boolean {
    Log.d(TAG, "On Unbind")
    binderIntent = null
    binder = null

    this.stopForeground(STOP_FOREGROUND_DETACH)
    this.stopSelf()
    return super.onUnbind(intent)
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    Log.d(TAG, "On Start Command")


    // Do not allow system to restart the service (must be started by user)
    return START_NOT_STICKY
}

private fun createVirtualDisplay() {
    Log.i(TAG, "Creating Virtual Display [$width x $height]")

    // Create Image Reader
    imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2)

    // Create Virtual Display
    virtualDisplay = mediaProjection.createVirtualDisplay(
        SCREEN_CAPTURE_NAME,
        width,
        height,
        density,
        VIRTUAL_DISPLAY_FLAGS,
        imageReader?.surface,
        callback,
        null
    )

    // Setup Image Available Listener
    try {
        Log.d(TAG, "Setting Image Available Listener")
        val handler = Handler(Looper.getMainLooper())
        imageReader?.setOnImageAvailableListener(this, handler)
    } catch (ex: Exception) {
        Log.e(TAG, ex.message, ex)
    }
}

private val callback = object : VirtualDisplay.Callback() {
    override fun onPaused() = Unit
    override fun onResumed() = Unit
    override fun onStopped() = Unit
}

override fun onImageAvailable(reader: ImageReader?) {
    val image = reader?.acquireLatestImage() ?: return
    image.use {
        // Get Image Buffer
        val imagePlane = image.planes[0]
        val imageBuffer = imagePlane.buffer

        // Compute Width (to avoid image distortion on certain devices)
        val rowStride = imagePlane.rowStride
        val pixelStride = imagePlane.pixelStride
        val width = rowStride / pixelStride

        // Send Image Frame Data
        sendFrame(imageBuffer, width, image.height)
    }
}

private fun sendFrame(imageBuffer: ByteBuffer, width: Int, height: Int) {
    binder?.mediaProjectionHandler?.sendFrame(imageBuffer, width, height)
}

private fun createNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channel = NotificationChannel(
            NOTIFICATION_CHANNEL_ID,
            NOTIFICATION_CHANNEL_NAME,
            NotificationManager.IMPORTANCE_DEFAULT
        )
        channel.description = "Vonage Video Media Projection Service"

        val notificationManager =
            getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)
    }
}

private fun createNotification(): Notification {
    createNotificationChannel()

    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
            .setSmallIcon(R.drawable.launch_background)
            .setContentTitle("IMS Care")
            .setContentText("Running screenshare video driver")
            .build()
    } else {
        Notification.Builder(this)
            .setSmallIcon(R.drawable.launch_background)
            .setContentTitle("IMS Care")
            .setContentText("Running screenshare video driver")
            .setPriority(Notification.PRIORITY_DEFAULT)
            .build()
    }
}
 }

Below is the error though I have registered callback for media projection api,

java.lang.IllegalStateException: Must register a callback before starting capture, to manage resources in response to MediaProjection states.

FGS logic changed: [WIU changed] [BFSL changed] OW:ALLOWED (DENIED+PROC_STATE_TOP) NW:DENIED OS:ALLOWED (DENIED+PROC_STATE_TOP) NS:DENIED cmp: {com.medpharm.imscare/com.medpharm.imscare.MediaProjectionService} sdk: 34

Kindly give update regarding this. I am using Google Pixel 6a with Android 14.

vmeditab avatar Oct 04 '23 10:10 vmeditab

@vmeditab, to confirm, you're able to reproduce this issue using one of our sample apps? If so, which one?

v-kpheng avatar Oct 17 '23 18:10 v-kpheng

fun setVirtualDisplay() { mImageReader = ImageReader.newInstance( deviceScreenUtils.getWidth(), deviceScreenUtils.getHeight(), PixelFormat.RGBA_8888, 2 ) if (mMediaProjection != null) { Handler(Looper.getMainLooper()).post { mMediaProjection!!.registerCallback(object : MediaProjection.Callback() { override fun onStop() { // Handle the onStop event, release resources or take necessary actions. // This method is called when the MediaProjection is stopped. releaseResources() } }, null) } } mImageReader?.let { val flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY or DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC

        try {
            mVirtualDisplay = mMediaProjection?.createVirtualDisplay(
                "screen-mirror",
                deviceScreenUtils.getWidth(),
                deviceScreenUtils.getHeight(),
                deviceScreenUtils.getDensity(),
                flags,
                it.surface,
                null,
                null
            )
        } catch (e: Throwable) {
            Log.i(TAG, "Media Projection not longer available...${e.message}")
            LogUtil.writeLog(context, TAG, "Throwable Exception error: ${e.message}")
            mMediaProjectionIntent = null
        }
    }
}

private fun releaseResources() {
    mVirtualDisplay?.release()
    mImageReader?.close()

}

Utilize this functionality to set up a callback for the Media Projection API.

Dinesh-AIT avatar Dec 27 '23 12:12 Dinesh-AIT