首页 > 解决方案 > 视频捕获 (MediaStore) 并上传到 FireBase - Android Kotlin

问题描述

这是我的第一个 kotlin 项目,从我开始到现在已经 3 个月了。我正在制作一个社交应用程序。现在我很难编写用于在 Firebase 存储和数据库上录制和上传视频文件的代码。下面是我处理上传和发布的视频帖子创建活动的代码。我尝试了许多修改,但现在每当我点击创建新帖子或上传视频时,应用程序都会在此代码上崩溃。你能检查一下这里有什么问题吗?因为我的意图只是录制视频,写下食物名称、地点等,然后发布帖子。我实际上对我的代码感到困惑。

import android.app.Activity
import android.app.ProgressDialog
import android.content.Intent
import android.database.Cursor
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.text.TextUtils
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.tasks.Continuation
import com.google.android.gms.tasks.OnCompleteListener
import com.google.android.gms.tasks.Task
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.storage.FirebaseStorage
import com.google.firebase.storage.StorageReference
import com.google.firebase.storage.StorageTask
import com.google.firebase.storage.UploadTask
import kotlinx.android.synthetic.main.activity_add_post.*

class AddPostActivity : AppCompatActivity() {

    private var myUrl = ""
    private var videoUri: Uri? = null
    private var storagePostPicRef: StorageReference? = null


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

        storagePostPicRef = FirebaseStorage.getInstance().reference.child("Posts Videos")

        save_new_post_btn.setOnClickListener { uploadImage() }

        intent.action = MediaStore.ACTION_VIDEO_CAPTURE
        startActivityForResult(intent, 101)

        //   var intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
        //   startActivityForResult(intent, VIDEO_CAPTURE)

    }


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

        if (requestCode == 101 && resultCode == Activity.RESULT_OK && data != null) {
            val result = videoUri
            video_new_post.setVideoURI(videoUri)
        }
    }

    private fun getPath(uri: Uri): String {
        var projectionArray = arrayOf(MediaStore.Video.Media.DATA)
        var cursor: Cursor? =
            applicationContext.contentResolver.query(uri, projectionArray, null, null, null)
        if (cursor != null) {
            val columnIndex: Int = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
            cursor.moveToFirst()
            return cursor.getString(columnIndex)
        } else {
            return ""
        }
    }

    private fun uploadImage() {
        when {
            videoUri == null -> Toast.makeText(this, "Please select a Video!", Toast.LENGTH_LONG)
                .show()
            TextUtils.isEmpty(name_new_post.text.toString()) -> Toast.makeText(
                this,
                "Please fill all the fields!",
                Toast.LENGTH_LONG
            ).show()
            TextUtils.isEmpty(place_new_post.text.toString()) -> Toast.makeText(
                this,
                "Please fill all the fields!",
                Toast.LENGTH_LONG
            ).show()
            TextUtils.isEmpty(city_new_post.text.toString()) -> Toast.makeText(
                this,
                "Please fill all the fields!",
                Toast.LENGTH_LONG
            ).show()
            TextUtils.isEmpty(state_new_post.text.toString()) -> Toast.makeText(
                this,
                "Please fill all the fields!",
                Toast.LENGTH_LONG
            ).show()

            else -> {

                val progressDialog = ProgressDialog(this)
                progressDialog.setTitle("Upload Started...")
                progressDialog.setMessage("Great! Please wait until we bake it...")
                progressDialog.show()

                val fileRef =
                    storagePostPicRef!!.child(System.currentTimeMillis().toString() + ".mp4")

                var uploadTask: StorageTask<*>
                uploadTask = fileRef.putFile(videoUri!!)

                uploadTask.continueWithTask(Continuation<UploadTask.TaskSnapshot, Task<Uri>> { task ->

                    if (!task.isSuccessful) {
                        task.exception?.let {
                            throw it
                            progressDialog.dismiss()
                        }
                    }

                    return@Continuation fileRef.downloadUrl

                })

                    .addOnCompleteListener(OnCompleteListener<Uri> { task ->

                        if (task.isSuccessful) {
                            val downloadUrl = task.result
                            myUrl = downloadUrl.toString()

                            val ref = FirebaseDatabase.getInstance().reference.child("Posts")
                            val postId = ref.push().key

                            val postMap = HashMap<String, Any>()
                            postMap["postid"] = postId!!
                            postMap["foodname"] = name_new_post.text.toString().toLowerCase()
                            postMap["placename"] = place_new_post.text.toString().toLowerCase()
                            postMap["cityname"] = city_new_post.text.toString().toLowerCase()
                            postMap["statename"] = state_new_post.text.toString().toLowerCase()
                            postMap["publisher"] = FirebaseAuth.getInstance().currentUser!!.uid
                            postMap["postimage"] = myUrl

                            ref.child(postId).updateChildren(postMap)

                            Toast.makeText(this, "Upload Finished Successfully!", Toast.LENGTH_LONG)
                                .show()

                            val intent = Intent(this@AddPostActivity, MainActivity::class.java)
                            startActivity(intent)
                            finish()

                            progressDialog.dismiss()

                        } else {
                            progressDialog.dismiss()
                        }

                    })

            }
        }
    }
}

所以在调整我的代码并重组它之后,这就是我想出的,它仍然没有打开任何东西,只是在尝试创建帖子时关闭了应用程序。它应该打开相机进行录制。

import android.app.ProgressDialog
import android.content.Intent
import android.database.Cursor
import android.icu.text.SimpleDateFormat
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.text.TextUtils
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import com.google.android.gms.tasks.Continuation
import com.google.android.gms.tasks.OnCompleteListener
import com.google.android.gms.tasks.Task
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.storage.FirebaseStorage
import com.google.firebase.storage.StorageReference
import com.google.firebase.storage.StorageTask
import com.google.firebase.storage.UploadTask
import kotlinx.android.synthetic.main.activity_add_post.*
import java.io.File
import java.io.IOException

class AddPostActivity : AppCompatActivity() {

    private var myUrl = ""
    private var videoUri: Uri? = null
    private var storagePostPicRef: StorageReference? = null


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


        storagePostPicRef = FirebaseStorage.getInstance().reference.child("Posts Videos")

        save_new_post_btn.setOnClickListener { uploadImage() }

        dispatchTakeVideoIntent()


    }

    fun createVideoFile(): File {
            val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(java.util.Date())
            val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_MOVIES)
            return File.createTempFile(
                "Video_${timeStamp}_", /* prefix */
                ".mp4", /* suffix */
                storageDir /* directory */
            )

        }

    val REQUEST_VIDEO_CAPTURE = 101
    private fun dispatchTakeVideoIntent() {
        Intent(MediaStore.ACTION_VIDEO_CAPTURE).also { takeVideoIntent ->
    takeVideoIntent.resolveActivity(packageManager)?.also {
        val videoFile: File? = try {
            createVideoFile()
        } catch (ex: IOException) {

            null
        }
        // Continue only if the File was successfully created
        videoFile?.also {
             videoUri  = FileProvider.getUriForFile(
                this,
                "your_app_id.provider", // add your application id , copy from build.gradle file paste here
                it
            )
            takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, videoUri)
            startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE)
        }
    }
  }
}

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 101 && resultCode == RESULT_OK) {
            Log.d("VideoPath", videoUri?.path) // you can see video path in log
            uploadImage()
        }
    }

    private fun uploadImage() {
        when {
            videoUri == null -> Toast.makeText(this, "Please select a Video!", Toast.LENGTH_LONG)
                .show()
            TextUtils.isEmpty(name_new_post.text.toString()) -> Toast.makeText(
                this,
                "Please fill all the fields!",
                Toast.LENGTH_LONG
            ).show()
            TextUtils.isEmpty(place_new_post.text.toString()) -> Toast.makeText(
                this,
                "Please fill all the fields!",
                Toast.LENGTH_LONG
            ).show()
            TextUtils.isEmpty(city_new_post.text.toString()) -> Toast.makeText(
                this,
                "Please fill all the fields!",
                Toast.LENGTH_LONG
            ).show()
            TextUtils.isEmpty(state_new_post.text.toString()) -> Toast.makeText(
                this,
                "Please fill all the fields!",
                Toast.LENGTH_LONG
            ).show()

            else -> {

                val progressDialog = ProgressDialog(this)
                progressDialog.setTitle("Upload Started...")
                progressDialog.setMessage("Great! Please wait until we bake it...")
                progressDialog.show()

                val fileRef =
                    storagePostPicRef!!.child(System.currentTimeMillis().toString() + ".mp4")



                fileRef.putFile(videoUri!!).addOnSuccessListener {
                    fileRef.downloadUrl.addOnSuccessListener {
                        myUrl = it.toString()

                        val ref = FirebaseDatabase.getInstance().reference.child("Posts")
                        val postId = ref.push().key

                        val postMap = HashMap<String, Any>()
                        postMap["postid"] = postId!!
                        postMap["foodname"] = name_new_post.text.toString().toLowerCase()
                        postMap["placename"] = place_new_post.text.toString().toLowerCase()
                        postMap["cityname"] = city_new_post.text.toString().toLowerCase()
                        postMap["statename"] = state_new_post.text.toString().toLowerCase()
                        postMap["publisher"] = FirebaseAuth.getInstance().currentUser!!.uid
                        postMap["postimage"] = myUrl

                        ref.child(postId).setValue(postMap)

                        Toast.makeText(this, "Upload Finished Successfully!", Toast.LENGTH_LONG)
                .show()

                        progressDialog.dismiss()
                       /* val intent = Intent(this@AddPostActivity, MainActivity::class.java)
                        startActivity(intent)
                        finish()*/

                        // if you want to return MainActivity just use finish()
                        finish()
                }
            }.addOnFailureListener {
                   progressDialog.dismiss()

            }

            }
        }
    }
}

标签: androidfirebasekotlinfirebase-realtime-databasefirebase-storage

解决方案


我们正在使用 getUriForFile(Context, String, File) ,它返回一个 content:// URI。对于针对 Android 7.0(API 级别 24)及更高版本的最新应用,跨包边界传递 file:// URI 会导致 FileUriExposedException。因此,我们现在提出一种使用FileProvider存储图像的更通用的方法。

我们创建这样的路径文件: res/xml/file_paths.xml

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path name="external_files" path="." />
</paths>

代表应用程序外部存储区域根目录中的文件。

在 manifest.xml 中添加提供者

<manifest>
   ...
   <application>
       ...
        <provider
           android:name="androidx.core.content.FileProvider"
           android:authorities="${applicationId}.provider"
           android:exported="false"
           android:grantUriPermissions="true">
           <meta-data
              android:name="android.support.FILE_PROVIDER_PATHS"
              android:resource="@xml/file_paths" />
    </provider>
    ...
</application>
</manifest>

这是一个调用意图来捕获视频的函数。

val REQUEST_VIDEO_CAPTURE = 101
private fun dispatchTakeVideoIntent() {
    Intent(MediaStore.ACTION_VIDEO_CAPTURE).also { takeVideoIntent ->
        takeVideoIntent.resolveActivity(packageManager)?.also {
            val videoFile: File? = try {
                createVideoFile()
            } catch (ex: IOException) {

                null
            }
            // Continue only if the File was successfully created
            videoFile?.also {
                 videoUri  = FileProvider.getUriForFile(
                    this,
                    "${your_app_id}.provider",
                    it
                )
                takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, videoUri)
                startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE)
            }
        }
    }
}

这是使用日期时间戳为新视频返回唯一文件名的方法中的示例解决方案:

@Throws(IOException::class)
private fun createVideoFile(): File {
    val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(java.util.Date())
    val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_MOVIES)
    return File.createTempFile(
        "Video_${timeStamp}_", /* prefix */
        ".mp4", /* suffix */
        storageDir /* directory */
    )
}

如果过程成功则继续

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == REQUEST_VIDEO_CAPTURE && resultCode == RESULT_OK) {
        Log.d("VideoPath",videoURI?.path) // you can see video path in log
        uploadImage()
    }
}

D/视频路径:/external_files/Movies/Video_20200510_143935_5711184798783973477.mp4

最后,我们对图片上传功能进行了改动。我们需要进行此更改以获取图像下载 url:

if (task.isSuccessful) {

    fileRef.downloadUrl().addOnSuccessListener {
         myUrl = it.toString()

         val ref = FirebaseDatabase.getInstance().reference.child("Posts")
         val postId = ref.push().key

         val postMap = HashMap<String, Any>()
         postMap["postid"] = postId!!
         postMap["foodname"] = name_new_post.text.toString().toLowerCase()
         postMap["placename"] = place_new_post.text.toString().toLowerCase()
         postMap["cityname"] = city_new_post.text.toString().toLowerCase()
         postMap["statename"] = state_new_post.text.toString().toLowerCase()
         postMap["publisher"] = FirebaseAuth.getInstance().currentUser!!.uid
         postMap["postimage"] = myUrl

         ref.child(postId).updateChildren(postMap)

         Toast.makeText(this, "Upload Finished Successfully!", Toast.LENGTH_LONG)
                    .show()

         progressDialog.dismiss()
        /* val intent = Intent(this@AddPostActivity, MainActivity::class.java)
         startActivity(intent)
         finish()*/

         // if you want to return MainActivity just use finish()
         finish()

        }
    } else {
        progressDialog.dismiss()
  }

推荐阅读