1

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" />
  • Show your code. Tell what not works. It can work on SDK 33 too using the right public directory. – blackapps May 22 '23 at 11:21
  • @blackapps I followed to this source code https://github.com/Truiton/ScreenCapture – WilliamVietnam May 23 '23 at 01:56
  • Much too much code. All irrelevant. To demonstrate that you cannot create a file five code lines would be enough. – blackapps May 23 '23 at 04:18
  • @blackapps I mean, I'm in need of screen recording. And I did exactly the same code in this repository https://github.com/Truiton/ScreenCapture. And currently it only works on API 29 and below. As for API 29 and above, it doesn't work. – WilliamVietnam May 23 '23 at 04:45
  • It does not work becouse you cannot store a file. So only five code lines please to demonstrate that you cannot create a file. Come to the point. – blackapps May 23 '23 at 04:47
  • I checked and found that WRITE_EXTERNAL_STORAGE was not granted. So I'm looking forward to getting people to come up with a solution. Or the steps to solve my above problem. @blackapps – WilliamVietnam May 23 '23 at 04:49
  • On an Android sdk 33 device you will not ask for write exteral storage permission to begin with. – blackapps May 23 '23 at 09:23

3 Answers3

2

From Api level 29 accessing external storage has been restricted in order to improve user privacy and security so if your app is working with api level 32 then I am guessing that you have added the storage permissions in manifest and also requested them at the runtime.

I have code snippet in my app that does the same function that you want .

val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
    addCategory(Intent.CATEGORY_OPENABLE)
    type = "video/mp4"
    putExtra(Intent.EXTRA_TITLE, "my_video.mp4")
}

startActivityForResult(intent, CREATE_DOCUMENT_REQUEST_CODE)

intent with the action ACTION_CREATE_DOCUMENT to create a new document.Specify the category CATEGORY_OPENABLE to allow the user to choose a location. The type specifies the MIME type of the file. In this case, we use "video/mp4". The putExtra method is used to specify the default file name.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    if (requestCode == CREATE_DOCUMENT_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        val uri: Uri? = data?.data
        if (uri != null) {
            // Save the video file to the chosen location
            saveVideoToUri(uri)
        }
    }
}

The result is RESULT_OK. If so, we retrieve the Uri of the chosen location and pass it to the saveVideoToUri function.

fun saveVideoToUri(uri: Uri,context:Context) {
    try {
        context.contentResolver.openOutputStream(uri)?.use { outputStream ->
            // Write the video data to the output stream
            outputStream.write(videoData)
        }
    } catch (e: IOException) {
        e.printStackTrace()
    }
}
zaid khan
  • 825
  • 3
  • 11
  • `I am guessing that you have added the storage permissions in manifest and also requested them at the runtime.` Unclear. All is not needed using ACTION_CREATE_DOCUMENT. – blackapps May 22 '23 at 11:23
  • Sorry, after checked, my this src just active on api 29 and below. On api 29 and above not working. I followed to https://github.com/Truiton/ScreenCapture. Please help me complete this function. – WilliamVietnam May 23 '23 at 02:11
0
  1. You need these two permissions in your manifest and ask the user for permissions programmatically

    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    
  2. In Android R+ you can not access storage without MANAGE_EXTERNAL_STORAGE see documentation here but you can access shared storage Like Downloads and your app folder.

  3. now you can create your folder directory using the below code

     fun createAppDirectoryInDownloads(context: Context): File? {
         val downloadsDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
         val appDirectory = File(downloadsDirectory, "YourAppDirectoryName")
    
         if (!appDirectory.exists()) {
             val directoryCreated = appDirectory.mkdir()
             if (!directoryCreated) {
                 // Failed to create the directory
                 return null
             }
         }
    
         return appDirectory
     }
    
  4. now create your video file in your directory using below code

     companion object {
     @JvmStatic
     fun createFileInAppDirectory(context: Context, fileName: String): File? {
         val appDirectory = createAppDirectoryInDownloads(context)
         if (appDirectory != null) {
             val file = File(appDirectory, fileName)
             try {
                 if (!file.exists()) {
                     val fileCreated = file.createNewFile()
                     if (!fileCreated) {
                         // Failed to create the file
                         return null
                     }
                 }
                 return file
             } catch (e: IOException) {
                 e.printStackTrace()
             }
         }
         return null
     }
    

It's Working For me,I just hope your problem solve with this.

jcredking
  • 314
  • 1
  • 10
0

On an Android sdk 33 device you will not ask for write exteral storage permission to begin with.

You have write permission by default to all public directories on external storage.

blackapps
  • 8,011
  • 2
  • 11
  • 25