opentok-android-sdk-samples
opentok-android-sdk-samples copied to clipboard
Android 14 crash while screen sharing opentok
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, to confirm, you're able to reproduce this issue using one of our sample apps? If so, which one?
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.