androidkotlinandroid-contentproviderandroid-external-storage

Video file is corrupted while saving mp4 to external storage in android


I am trying to save an mp4 file after trimming with the help of its url. I am using https://github.com/a914-gowtham/android-video-trimmer for trimming my video and the updated uri after trimming is fine as I trying playing it in exoplayer and it works, the problem happens when I try to save it to external storage. I can see my file being saved to external storage as well but when I try to play it after saving via third party apps like vlc, mxplayer etc they throw error. So below is my code to save video after getting the uri

class MainActivity : AppCompatActivity() {

    private lateinit var uri: Uri
    private lateinit var playerView: PlayerView

    private val REQUEST_CODE_PERMISSIONS = 10
    private val REQUIRED_PERMISSIONS =
        mutableListOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE).toTypedArray()

    private var player: ExoPlayer? = null
    private var playWhenReady = true
    private var currentItem = 0
    private var playbackPosition = 0L

    private fun setUpPlayer(url: String){
        player = ExoPlayer.Builder(this)
            .build()
            .also { exoPlayer ->
                playerView.player = exoPlayer
                val mediaItem = MediaItem.fromUri(url)
                exoPlayer.setMediaItem(mediaItem)
                exoPlayer.playWhenReady = playWhenReady
                exoPlayer.seekTo(currentItem, playbackPosition)
                exoPlayer.prepare()
            }
    }


    private val startForResult =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
            if (result.resultCode == Activity.RESULT_OK &&
                result.data != null
            ) {
                uri = Uri.parse(TrimVideo.getTrimmedVideoPath(result.data))
                Log.i("TAG", "Trimmed path:: $uri")
                setUpPlayer(uri.toString())
                if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
                    lifecycleScope.launch {
                        val uri = saveVideoToExternalStorage()
                        Log.i("helloooo", "3487438734784387")
                        Toast.makeText(this@MainActivity, "Uri is $uri", Toast.LENGTH_LONG).show()
                    }
                } else {
                    ActivityCompat.requestPermissions(
                        this,
                        REQUIRED_PERMISSIONS,
                        REQUEST_CODE_PERMISSIONS
                    )
                }



            } else
                Log.i("TAG", "Trimmed path11111:: Errorr")
        }

    private val singleVideoPickerLauncher =
        registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { videoUri ->
            TrimVideo.activity(videoUri.toString())
                .setHideSeekBar(true)
                .start(this, startForResult)
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        playerView = findViewById(R.id.playerview)

        findViewById<Button>(R.id.btn).setOnClickListener {
            singleVideoPickerLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly))
        }


    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (allPermissionsGranted()) {
                lifecycleScope.launch {
                    Log.i("helloooo", "000000")
                    val uri = saveVideoToExternalStorage()
                    Log.i("helloooo", "1111111")
                    Toast.makeText(this@MainActivity, "Uri is $uri", Toast.LENGTH_LONG).show()
                }
            } else {
                Log.i("TAG", "Permission denied")

            }
        }
    }

    private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(
            this, it
        ) == PackageManager.PERMISSION_GRANTED
    }

    private suspend fun saveVideoToExternalStorage(): Uri? {
        return withContext(Dispatchers.Default) {
            Log.i("helloooo", "1")

            val videoCollection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
            } else {
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI
            }

            val file = uri.path?.let { File(it) }

            Log.i("helloooo", "2")
            val contentValues = ContentValues().apply {
                Log.i("helloooo", "3")

                put(MediaStore.Video.Media.DATA, uri.path)
                put(MediaStore.MediaColumns.DISPLAY_NAME, "${System.currentTimeMillis()}${file?.name}.mp4")

                put(MediaStore.Video.Media.TITLE, "${System.currentTimeMillis()}${file?.name}.mp4")
                put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
                put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000)
            }
            Log.i("helloooo", "4")
            val uri =
                contentResolver.insert(videoCollection, contentValues)
            Log.i("helloooo", "5 ${uri}")
            uri
        }
    }
}

Solution

  • You can use this method to save the video in device's having android 10 or above.

    Just pass the file and display name.

    @RequiresApi(api = Build.VERSION_CODES.Q)
            @Throws(FileNotFoundException::class)
            fun Context.addVideoToGalleryAPI29(
                file: File,
                displayName: String,
                exportListener: VideoExportListener
            ) {
                val savePath = (Environment.DIRECTORY_DCIM
                        + File.separator + file.name)
                val cv = ContentValues()
                cv.put(
                    MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM
                            + File.separator + File(savePath).name
                )
                cv.put(MediaStore.Video.Media.TITLE, "${displayName}.mp4")
                cv.put(MediaStore.Video.Media.DISPLAY_NAME, "${displayName}.mp4")
                cv.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
                cv.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000)
                cv.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis())
                cv.put(MediaStore.Video.Media.IS_PENDING, 1)
                val resolver = contentResolver
                val collection =
                    MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
                val uriSavedVideo = resolver.insert(collection, cv)
                val pfd = contentResolver.openFileDescriptor(uriSavedVideo!!, "w")
                if (pfd != null) {
                    try {
                        val out =
                            FileOutputStream(pfd.fileDescriptor)
                        val inputStream = FileInputStream(file)
                        val buf = ByteArray(8192)
                        var len: Int
                        while (inputStream.read(buf).also { len = it } > 0) {
                            out.write(buf, 0, len)
                        }
                        out.close()
                        inputStream.close()
                        pfd.close()
                        cv.clear()
                        cv.put(MediaStore.Video.Media.IS_PENDING, 0)
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                    contentResolver.update(uriSavedVideo, cv, null, null)
                }
                exportListener.onVideoExported()
            }