Android Kotlin : 기본 카메라 앱 호출해서 동영상 촬영, 저장, 미리보기
Intent의 암시적 호출 방법으로 카메라 앱으로 촬영한 동영상을 미리보기 및 저장하는 방법
입니다. 비슷한 방법으로 촬영할 영상을 내부 저장소 및 공용 저장소에 저장 할 수 있습니다. 필요하시면 하단의 링크를 참조해주세요.
프로젝트 생성 및 환경설정
프로젝트 생성
아래와 같이 프로젝트를 생성합니다.
- 프로젝트명 : RecodeVideo
- 사용언어 : Kotlin
※ 이 포스트는 Android Studio 4.1.2, Kotlin 1.4.31 버전으로 작성하였습니다.
Gradle 설정 : ViewBinding
App > Gradle Scripts > Build.gradle에 아래의 ViewBinding 설정 코드를 추가 후 Sync Now를 클릭하여 Gradle 변경 사항을 적용합니다.
android {
...
viewBinding {
enabled = true
}
}
※ ViewBInding은 Layout(xml)에 있는 레이아웃과, View의 Id를 코틀린 코드에서 직접 접근 할 수 있도록 해주는 도구 입니다. ViewBinding과 관련된 설명은 아래의 링크를 참조해주세요.
(Manifest) 권한 및 FileProvider 설정
촬영한 동영상을 저장하기 위해 아래의 권한이 필요합니다. 권한 코드는 아래 코드에서 7~9줄에 해당됩니다.
- 카메라 권한
- 저장소 쓰기/저장
본 포스트는 제작된 앱에서 카메라 앱을 호출하여 지정된 URI에 파일을 저장하는 방식을 사용합니다. FileProvider는 지정된 파일의 콘텐츠 URI를 생성하는 기능을 가지고 있습니다. 자세한 내용은 안드로이드 개발자 가이드를 참고해주세요. FileProvider 설정 추가 코드는 16~24번 줄에 해당됩니다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.blacklog.recordvideo">
...
<!--Permissions -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application>
...
<!--File Provider Start-->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.blacklog.takepicture.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
<!--File Provider End-->
...
</application>
</manifest>
위 코드를 작성하면 @xml/filepaths에 붉은 색이 표시되는데, 참조할 리소스 파일이 생성되지 않았기 때문입니다. 아래와 같이 app > res > xml 폴더 생성 후 filepaths.xml 파일을 추가합니다.
생성된 filepaths.xml 파일에 아래와 같이 코드를 작성합니다.
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_external_video" path="video/" />
<root-path name="root" path="./"/>
</paths>
레이아웃 작성
activity_main.xml 코드 작성
아래와 같이 activity_main.xml 코드를 작성합니다. 각 뷰의 기능은 아래와 같습니다.
- VideoView : 촬영한 비디오를 볼 수 있는 뷰
- Button(ID : btnRecord) : 동영상 촬영 할 수 있는 앱을 호출
- Button(ID : btnPlay)
- 동영상을 재생 또는 정지하는 버튼
- 디폴트 비활성화로 설정
- 동영상 촬영이 정상적으로 완료되면 버튼 활성화
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btnPlay"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Play / Stop"
android:layout_margin="10dp"
android:enabled="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnRecord"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/btnRecord"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Record"
android:layout_margin="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/btnPlay" />
<VideoView
android:id="@+id/videoView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="10dp"
app:layout_constraintBottom_toTopOf="@+id/btnRecord"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt 코드 작성
권한 코드 작성
Manifest에 설정한 권한을 요청하는 코드를 작성합니다. 예제용으로 간단하게만 구현하였습니다. 권한 설정 코드에 대한 설명은 아래의 링크를 확인해주세요.
package com.blacklog.cameraxvideo
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.blacklog.cameraxvideo.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
lateinit var binding : ActivityMainBinding
private val CAMERA_PERMISSION = arrayOf(Manifest.permission.CAMERA)
private val CAMERA_PERMISSION_FLAG = 100
private val STORAGE_PERMISSION = arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
private val STORAGE_PERMISSION_FLAG = 200
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
if(checkPermission(CAMERA_PERMISSION, CAMERA_PERMISSION_FLAG)){
checkPermission(STORAGE_PERMISSION, STORAGE_PERMISSION_FLAG)
}
}
private fun checkPermission(permissions : Array<out String>, flag : Int):Boolean{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
for (permission in permissions) {
if (ContextCompat.checkSelfPermission(
this,
permission
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(this, permissions, flag)
return false
}
}
}
return true
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
when(requestCode){
CAMERA_PERMISSION_FLAG -> {
for(grant in grantResults) {
if(grant != PackageManager.PERMISSION_GRANTED){
Toast.makeText(this, "카메라 권한을 승인해야지만 앱을 사용 할 수 있습니다.", Toast.LENGTH_LONG).show()
finish()
}else{
checkPermission(STORAGE_PERMISSION, STORAGE_PERMISSION_FLAG)
}
}
}
STORAGE_PERMISSION_FLAG -> {
for(grant in grantResults) {
if(grant != PackageManager.PERMISSION_GRANTED){
Toast.makeText(this, "저장소 권한을 승인해야지만 앱을 사용 할 수 있습니다.", Toast.LENGTH_LONG).show()
finish()
}
}
}
}
}
}
위 코드를 작성 후 실행하면 앱 실행시 아래와 같이 앱 권한 요청 다이얼로그가 표시됩니다.
동영상 촬영 및 저장 코드 작성하기
위 UI에서 RECORD 버튼을 누르면 암시적 인텐트를 사용하여 동영상 앱을 호출합니다. 인텐트 인스턴스에는 동영상 파일이 저장될 URI를 포함합니다. 현재 앱 디렉토리의 파일을 카메라 앱으로 파일을 안전하게 전송하기 위해 FileProvider를 사용하여 파일을 Content Uri로 변환하여 카메라 앱에서 전송함과 동시에 파일을 사용할 수 있도록 권한을 부여합니다. 아래는 관련 코드만 표시하였습니다.
class MainActivity : AppCompatActivity() {
...
private val REQUEST_VIDEO_CAPTURE_CODE = 1
private var videoUri : Uri? = null // video 저장될 Uri
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
...
binding.btnRecord.setOnClickListener {
val recordVideoIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
val videoFile = File(
File("${filesDir}/video").apply {
if(!this.exists()){
this.mkdirs()
}
},
newVideoFileName()
)
videoUri = FileProvider.getUriForFile(
this,
"com.blacklog.cameraxvideo.fileprovider",
videoFile
)
recordVideoIntent.resolveActivity(packageManager)?.also{
recordVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, videoUri)
startActivityForResult(recordVideoIntent, REQUEST_VIDEO_CAPTURE_CODE)
}
}
...
}
....
private fun newVideoFileName() : String {
val sdf = SimpleDateFormat("yyyyMMdd_HHmmss")
val filename = sdf.format(System.currentTimeMillis())
return "${filename}.mp4"
}
....
}
카메라 앱에서 동영상 촬영이 정상적으로 완료되면 VideoView와 저장된 동영상 파일을 연결하고, Play/Stop 버튼을 활성화 합니다. onActivityResult는 코드창에서 Ctrl + O 를 눌러 검색 후 override 할 수 있습니다.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(resultCode == RESULT_OK){
when(requestCode){
REQUEST_VIDEO_CAPTURE_CODE -> {
binding.videoView.setVideoURI(videoUri)
binding.videoView.requestFocus()
binding.btnPlay.isEnabled = true
}
}
}
}
마지막으로, Play/Stop 버튼 동작을 처리하는 코드를 추가합니다. VideoView에 동영상이 재생되고 있는지를 확인하는 Boolean타입의 변수 isVideoPlaying을 전역 변수로 선언합니다. Play / Stop 버튼 동작을 눌렀을 떄 isVideoPlaying 의 상태에 따라 영상을 재생하거나, 종료합니다. videoView.setOnCompletionListener 는 VideoView의 영상이 재생 완료되었을 때 실행되는 코드입니다. 영상 재생이 완료되면 버튼의 텍스트를 변경하고, isVideoPlaying 의 값을 업데이트 합니다.
class MainActivity : AppCompatActivity() {
...
private var isVideoPlaying = false // VideoView에 영상이 재생되고 있는지 상태를 확인
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
...
binding.btnPlay.setOnClickListener {
when(isVideoPlaying){
true -> {
isVideoPlaying = false
binding.videoView.stopPlayback()
binding.btnPlay.text = "Play"
binding.videoView.setVideoURI(videoUri)
}
false -> {
isVideoPlaying = true
binding.btnPlay.text = "Stop"
binding.videoView.start()
}
}
}
binding.videoView.setOnCompletionListener {
binding.btnPlay.text = "Play"
isVideoPlaying = false
}
}
...
}
실행 결과
위 코드를 실행하면 아래와 같이 촬영한 영상을 VideoView에서 재생 할 수 있습니다. 촬영한 영상은 외장메모리의 앱 디렉토리에 저장되기 떄문에 겔러리앱에서 보이지 않습니다.
추가로, 내부저장소 또는 공용저장소에 저장하는 방법이 필요하면 아래의 링크를 참조해주세요.
카메라 앱으로 찍은 사진 내부저장소, 외부저장소, 공용저장소 저장 방법
끝까지 읽어 주셔서 감사합니다.^^
'Programming > Android App(Kotlin)' 카테고리의 다른 글
안드로이드 코틀린 : Android Studio 에서 코틀린 언어 연습 하는 방법 (0) | 2021.05.05 |
---|---|
웹에서 코틀린 프로그래밍 언어 연습 및 학습 하는 방법 - try.kotlinlang.org (1) | 2021.05.05 |
안드로이드 코틀린 : LocalBroadcast로 프래그먼트에서 볼륨 업/다운 키 이벤트 사용 (0) | 2021.04.28 |
안드로이드 코틀린 : 액티비티에서 볼륨 업/다운 키 이벤트 등록 - onKeyDown() 콜백 함수 (0) | 2021.04.27 |
안드로이드 코틀린 : Pair, Triple, Data Class 여러 개의 반환 값을 가지는 메서드 또는 함수 만들기 (0) | 2021.04.21 |