I'm doing screen recording function for my app. So, I need save video mp4 files to external storage. My function active on API 29 and below, but not working on API 32 and above. Please show me steps to resolve this problem.
I followed to this source code: https://github.com/Truiton/ScreenCapture
MainActivity.kt
class MainActivity : AppCompatActivity() {
private var mScreenDensity = 0
private var mProjectionManager: MediaProjectionManager? = null
private var mMediaProjection: MediaProjection? = null
private var mVirtualDisplay: VirtualDisplay? = null
private var mMediaProjectionCallback: MediaProjectionCallback? = null
private var mMediaRecorder: MediaRecorder? = null
private lateinit var binding: ActivityMainBinding
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val metrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(metrics)
mScreenDensity = metrics.densityDpi
mMediaRecorder = MediaRecorder()
mProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
binding.toggle.setOnClickListener { v: View? ->
if ((ContextCompat.checkSelfPermission(
this@MainActivity,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) + ContextCompat
.checkSelfPermission(
this@MainActivity,
Manifest.permission.RECORD_AUDIO
) + ContextCompat.checkSelfPermission(
this@MainActivity,
MANAGE_EXTERNAL_STORAGE
))
!= PackageManager.PERMISSION_GRANTED
) {
if (ActivityCompat.shouldShowRequestPermissionRationale(
this@MainActivity,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) ||
ActivityCompat.shouldShowRequestPermissionRationale(
this@MainActivity,
Manifest.permission.RECORD_AUDIO
) || ActivityCompat.shouldShowRequestPermissionRationale(
this@MainActivity,
MANAGE_EXTERNAL_STORAGE
)
) {
binding.toggle.isChecked = false
Snackbar.make(
findViewById(android.R.id.content), R.string.label_permissions,
Snackbar.LENGTH_INDEFINITE
).setAction("ENABLE",
View.OnClickListener { v1: View? ->
ActivityCompat.requestPermissions(
this@MainActivity,
arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO,
MANAGE_EXTERNAL_STORAGE
),
REQUEST_PERMISSIONS
)
}).show()
} else {
ActivityCompat.requestPermissions(
this@MainActivity,
arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO,
MANAGE_EXTERNAL_STORAGE
),
REQUEST_PERMISSIONS
)
}
} else {
onToggleScreenShare(v)
}
}
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode != REQUEST_CODE) {
Log.e(TAG, "Unknown request code: $requestCode")
return
}
if (resultCode != RESULT_OK) {
Toast.makeText(
this,
"Screen Cast Permission Denied", Toast.LENGTH_SHORT
).show()
binding.toggle.isChecked = false
return
}
mMediaProjectionCallback = MediaProjectionCallback()
mMediaProjection = mProjectionManager!!.getMediaProjection(resultCode, data!!)
mMediaProjection?.registerCallback(mMediaProjectionCallback, null)
mVirtualDisplay = createVirtualDisplay()
mMediaRecorder!!.start()
}
fun onToggleScreenShare(view: View?) {
if ((view as ToggleButton?)!!.isChecked) {
initRecorder()
shareScreen()
} else {
mMediaRecorder!!.stop()
mMediaRecorder!!.reset()
Log.v(TAG, "Stopping Recording")
stopScreenSharing()
}
}
private fun shareScreen() {
if (mMediaProjection == null) {
startActivityForResult(mProjectionManager!!.createScreenCaptureIntent(), REQUEST_CODE)
return
}
mVirtualDisplay = createVirtualDisplay()
mMediaRecorder!!.start()
}
private fun createVirtualDisplay(): VirtualDisplay {
return mMediaProjection!!.createVirtualDisplay(
"MainActivity",
DISPLAY_WIDTH, DISPLAY_HEIGHT, mScreenDensity,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mMediaRecorder!!.surface, null /*Callbacks*/, null /*Handler*/
)
}
@SuppressLint("SimpleDateFormat")
private fun initRecorder() {
try {
mMediaRecorder!!.setAudioSource(MediaRecorder.AudioSource.MIC)
mMediaRecorder!!.setVideoSource(MediaRecorder.VideoSource.SURFACE)
mMediaRecorder!!.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
mMediaRecorder!!.setOutputFile(
Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.toString() + "/" + "Prank_Record_" + SimpleDateFormat("dd-MM-yyyy-hh_mm_ss").format(
Date()
) + ".mp4"
)
mMediaRecorder!!.setVideoSize(DISPLAY_WIDTH, DISPLAY_HEIGHT)
mMediaRecorder!!.setVideoEncoder(MediaRecorder.VideoEncoder.H264)
mMediaRecorder!!.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
mMediaRecorder!!.setVideoEncodingBitRate(512 * 1000)
mMediaRecorder!!.setVideoFrameRate(30)
val rotation = windowManager.defaultDisplay.rotation
val orientation = ORIENTATIONS[rotation + 90]
mMediaRecorder!!.setOrientationHint(orientation)
mMediaRecorder!!.prepare()
} catch (e: IOException) {
e.printStackTrace()
}
}
private inner class MediaProjectionCallback : MediaProjection.Callback() {
override fun onStop() {
if (binding.toggle.isChecked) {
binding.toggle.isChecked = false
mMediaRecorder!!.stop()
mMediaRecorder!!.reset()
Log.v(TAG, "Recording Stopped")
}
mMediaProjection = null
stopScreenSharing()
}
}
private fun stopScreenSharing() {
if (mVirtualDisplay == null) {
return
}
mVirtualDisplay!!.release()
//mMediaRecorder.release(); //If used: mMediaRecorder object cannot
// be reused again
destroyMediaProjection()
}
public override fun onDestroy() {
super.onDestroy()
destroyMediaProjection()
}
private fun destroyMediaProjection() {
if (mMediaProjection != null) {
mMediaProjection!!.unregisterCallback(mMediaProjectionCallback)
mMediaProjection!!.stop()
mMediaProjection = null
}
Log.i(TAG, "MediaProjection Stopped")
}
@RequiresApi(Build.VERSION_CODES.R)
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
REQUEST_PERMISSIONS -> {
if (true) {
onToggleScreenShare(binding.toggle)
} else {
binding.toggle.isChecked = false
Snackbar.make(
findViewById(android.R.id.content), R.string.label_permissions,
Snackbar.LENGTH_INDEFINITE
).setAction("ENABLE",
View.OnClickListener {
val intent = Intent()
intent.action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
intent.addCategory(Intent.CATEGORY_DEFAULT)
intent.data = Uri.parse("package:$packageName")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
startActivity(intent)
}).show()
}
return
}
}
}
companion object {
private const val TAG = "MainActivity"
private const val REQUEST_CODE = 1000
private const val DISPLAY_WIDTH = 720
private const val DISPLAY_HEIGHT = 1280
private val ORIENTATIONS = SparseIntArray()
private const val REQUEST_PERMISSIONS = 10
init {
ORIENTATIONS.append(Surface.ROTATION_0, 90)
ORIENTATIONS.append(Surface.ROTATION_90, 0)
ORIENTATIONS.append(Surface.ROTATION_180, 270)
ORIENTATIONS.append(Surface.ROTATION_270, 180)
}
}
}
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />