구현 이유

게시글은 여행 기록이기 때문에, 사진뿐만 아니라 영상 또한 업로드 할 수 있어야 한다고 생각했습니다.

여행하며 찍은 영상은 재생시간이 길 가능성이 높아, 원하는 구간만 선택해 업로드를 할 수 있으면 좋을 것 같다 생각했고, 컷 편집을 포함한 영상 업로드를 구현하게 되었습니다.

구현 방식

영상과 관련한 부분은 Jetpack의 Media3 라이브러리를 사용했습니다.

Untitled

편집할 영상을 Exoplayer를 통해 로드하고,

영상의 구간을 선택할 수 있는 타임라인은 커스텀뷰로 구현하였습니다.

Untitled

영상의 구간을 설정한 후, 확인 버튼을 누르면 transformer를 통해 인코딩을 수행합니다.

private fun initTransFormer() {

        trans = Transformer.Builder(this@VideoEditActivity)
            .setVideoMimeType(MimeTypes.VIDEO_H265)
            .setAudioMimeType(MimeTypes.AUDIO_AAC)
            .build()

        trans.addListener(object : Listener {
            override fun onCompleted(composition: Composition, exportResult: ExportResult) {
                super.onCompleted(composition, exportResult)
                viewModel.finishLoading()
                intent.putExtra("path", file.path)
                intent.putExtra("original", viewModel.uri.value)
                setResult(RESULT_OK, intent)
                finish()

            }
            override fun onError(
                composition: Composition,
                exportResult: ExportResult,
                exportException: ExportException
            ) {
                super.onError(composition, exportResult, exportException)
                viewModel.finishLoading()
                showToastMessage(exportException.message?:"error")
            }
        })
    }

영상은 VIDEO_H265, 음성은 AUDIO_AAC형식으로 인코딩을 수행했습니다.

object CacheManager {
    private var names: MutableSet<String> = mutableSetOf()

    private fun getRandomName(): String{
        val random = Random
        var temp = ""
        while(true){
            temp = ""
            repeat(10){
                temp += random.nextInt(0, 10)
            }
            if(names.contains(temp).not()) break
        }
        names.add(temp)
        return temp
    }

    fun createExternalCacheFile(context: Context): File {

        Log.d("TAG", "createExternalCacheFile: ${context.cacheDir.path}/video_cache/${getRandomName()}.mp4}")
        val cacheDir = File(context.cacheDir,"video_cache")
        cacheDir.mkdir()
        val file = File(cacheDir, "${getRandomName()}.mp4")

        try{
            if (file.exists() && !file.delete()) {
                throw IllegalStateException("Could not delete the previous export output file")
            }
            if (!file.createNewFile()) {
                throw IllegalStateException("Could not create the export output file")
            }
        } catch (e:Exception){
            Log.d("TAG", "createExternalCacheFile: ${e.message}")
        }
        return file
    }

    fun clearVideoCache(context: Context){
        val cacheDir = File(context.cacheDir,"video_cache")
        val caches = cacheDir.list() ?: return
        caches.forEach {
            File(cacheDir, it).delete()
        }
        names.clear()
        cacheDir.delete()
    }
}

결과물은 업로드를 위해 캐시 디렉토리 내부에 임시로 저장되며, 게시물 생성 후 바로 삭제되도록 구현했습니다.

Untitled