Explorar el Código

Merge branch 'feature_movie_edit_mvvm'

liweihao hace 6 años
padre
commit
c97f61cc5c

+ 3 - 0
build.gradle

@@ -35,6 +35,9 @@ dependencies {
     implementation 'com.android.support:support-v4:28.0.0-rc02'
     testImplementation 'junit:junit:4.12'
 
+    implementation "android.arch.lifecycle:extensions:1.1.0"
+    implementation "android.arch.lifecycle:viewmodel:1.1.0"
+
     androidTestImplementation 'com.android.support.test:rules:1.0.2'
     androidTestImplementation 'com.android.support.test:runner:1.0.2'
     androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

+ 20 - 4
src/main/java/com/bomostory/sceneeditmodule/SuperMovieMaker.kt

@@ -2,12 +2,15 @@ package com.bomostory.sceneeditmodule
 
 import android.content.Context
 import android.graphics.Bitmap
+import android.media.MediaMetadataRetriever
+import android.os.Environment
 import android.util.Log
 import com.bomostory.sceneeditmodule.screen.movie.MovieEditActivity.Companion.FPS
 import com.bomostory.sceneeditmodule.basicdata.Music
 import com.bomostory.sceneeditmodule.basicdata.Project
 import com.bomostory.sceneeditmodule.basicdata.Scene
 import com.example.exportmedia.MediaHelper
+import com.example.exportmedia.audio.AudioLooper
 import com.example.exportmedia.data.AudioSource
 import com.example.exportmedia.vedio.MovieMaker
 import io.reactivex.Completable
@@ -33,7 +36,7 @@ class SuperMovieMaker {
         return Observable.create<String> { emitter ->
             val movieBuilder = MovieMaker.Builder(mediaHelper)
             movieBuilder.fps = FPS
-            movieBuilder.audioSources = generateAudioSource(musics)
+            movieBuilder.audioSources = generateAudioSource(musics,mediaHelper)
 
             val inputSceneStatus = LinkedHashMap<Int, Int>()
             val frameDataList = generateFrameDataList(project)
@@ -73,13 +76,26 @@ class SuperMovieMaker {
         }
     }
 
-    private fun generateAudioSource(musics: List<Music>): ArrayList<AudioSource> {
+    private fun generateAudioSource(musics: List<Music>,mediaHelper: MediaHelper): ArrayList<AudioSource> {
         val audioSources = ArrayList<AudioSource>()
         for (music in musics) {
+            val fileName = "test${System.currentTimeMillis()}.mp3"
+            val outputFile = File(Environment.getExternalStorageDirectory(), fileName)
+
+            val audioLooperBuilder = AudioLooper.Builder(mediaHelper)
+            audioLooperBuilder.loopAudioFile = File(music.path)
+            audioLooperBuilder.loopDuration = music.loopDuration.toInt()
+
+            val audioLooper = audioLooperBuilder.build()
+             audioLooper.output(outputFile).blockingFirst()
+
+            val mediaMetadataRetriever = MediaMetadataRetriever()
+            mediaMetadataRetriever.setDataSource(outputFile.path)
+
             val audioSource = AudioSource()
-            audioSource.duration = music.duration
+            audioSource.path = outputFile.path
+            audioSource.duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong()
             audioSource.offsetSec = music.offsetTime.toFloat() / 1000
-            audioSource.path = music.path
             audioSources.add(audioSource)
         }
         return audioSources

+ 2 - 28
src/main/java/com/bomostory/sceneeditmodule/basicdata/DataParser.kt

@@ -1,39 +1,12 @@
 package com.bomostory.sceneeditmodule.basicdata
 
 import android.content.ContentResolver
-import android.media.MediaMetadataRetriever
-import android.os.Environment
 import android.provider.MediaStore
-import org.json.JSONObject
 import java.io.File
 
 object DataParser {
 
-    fun parseMusicSource(contentResolver: ContentResolver,libraryPaths:List<String>):MusicSource{
-        val musicSource = MusicSource()
-        musicSource.library = parseMusic(libraryPaths)
-        musicSource.device = parseMusic(contentResolver,libraryPaths)
-        return musicSource
-    }
-
-    private fun parseMusic(paths: List<String>): ArrayList<Music> {
-        val mediaMetadataRetriever = MediaMetadataRetriever()
-        val musics = ArrayList<Music>()
-
-        for (path in paths) {
-            val file = File(path)
-            mediaMetadataRetriever.setDataSource(path)
-
-            val music = Music()
-            music.name = file.name
-            music.path = path
-            music.duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong()
-            musics.add(music)
-        }
-        return musics
-    }
-
-    private fun parseMusic(contentResolver: ContentResolver,paths: List<String>): ArrayList<Music>{
+     fun parseMusic(contentResolver: ContentResolver, paths: ArrayList<String?>): ArrayList<Music>{
         val selection = MediaStore.Audio.Media.IS_MUSIC + " != 0"
         val projection = arrayOf(
                 MediaStore.Audio.Media.TITLE,
@@ -53,6 +26,7 @@ object DataParser {
             music.name = cursor.getString(0)
             music.path = cursor.getString(1)
             music.duration = cursor.getString(2).toLong()
+            music.endTime = cursor.getString(2).toLong()
             val file = File(music.path)
             if (!paths.contains(file.path)) {
                 musics.add(music)

+ 3 - 2
src/main/java/com/bomostory/sceneeditmodule/basicdata/Music.kt

@@ -2,11 +2,12 @@ package com.bomostory.sceneeditmodule.basicdata
 
 class Music {
     var name: String? = null
-    var originPath:String? = null
     var path: String? = null
     var offsetTime: Long = 0
     var startTime: Long = 0
-    var endTime:Long = 0
+    var endTime: Long = 0
     var duration: Long = 0
     var volume: Float = 0.5f
+    var isLoop: Boolean = false
+    var loopDuration: Long = 0
 }

+ 137 - 130
src/main/java/com/bomostory/sceneeditmodule/screen/movie/MovieEditActivity.kt

@@ -2,6 +2,8 @@ package com.bomostory.sceneeditmodule.screen.movie
 
 import android.app.AlertDialog
 import android.app.ProgressDialog
+import android.arch.lifecycle.Observer
+import android.arch.lifecycle.ViewModelProviders
 import android.content.DialogInterface
 import android.content.Intent
 import android.net.Uri
@@ -14,20 +16,20 @@ import android.view.View
 import android.widget.CompoundButton
 import android.widget.SeekBar
 import android.widget.Toast
-import com.bomostory.sceneeditmodule.*
+import com.bomostory.sceneeditmodule.Config
+import com.bomostory.sceneeditmodule.PdfMaker
+import com.bomostory.sceneeditmodule.SuperMovieMaker
 import com.bomostory.sceneeditmodule.basicdata.*
 import com.bomostory.sceneeditmodule.screen.movie.music.MusicEditDialog
 import com.bomostory.sceneeditmodule.screen.movie.music.MusicSelectDialog
 import com.bomostory.sceneeditmodule.screen.movie.music.MusicSelectFragment
-import com.bomostory.sceneeditmodule.share.ExportPdfDialog
 import com.bomostory.sceneeditmodule.share.ShareDialog
-import com.bomostory.sceneeditmodule.utils.MoviePlayer
-import com.bomostory.sceneeditmodule.utils.MusicPlayer
-import com.bomostory.sceneeditmodule.utils.TimeUtils
 import com.bomostory.sceneeditmodule.screen.view.AudioTrackGroupView
 import com.bomostory.sceneeditmodule.screen.view.MovieSelectView
+import com.bomostory.sceneeditmodule.share.ExportPdfDialog
+import com.bomostory.sceneeditmodule.utils.MoviePlayerV2
+import com.bomostory.sceneeditmodule.utils.MusicPlayerV2
 import com.example.exportmedia.MediaHelper
-import com.example.exportmedia.audio.AudioEditor
 import com.example.tfat.myapplication.R
 import com.google.gson.Gson
 import io.reactivex.Observable
@@ -43,35 +45,38 @@ class MovieEditActivity : AppCompatActivity(),
         MusicEditDialog.OnFragmentInteractionListener {
 
     override fun onMusicImportClick(music: Music) {
+        val musics: ArrayList<Music> = viewModel.musicListLiveData.value as ArrayList<Music>
         musics.add(music)
-        moviePlayer.init()
-        moviePlayer.addMusic(music)
-        movieEditView.musics = musics
+        viewModel.musicListLiveData.value = musics
+        viewModel.musicLiveData.value = music
 
         val dialogFragment = supportFragmentManager.findFragmentByTag(MUSIC_SELECT_DIALOG_TAG) as DialogFragment
         dialogFragment.dismiss()
     }
 
     override fun onMusicPreCastClick(music: Music) {
-        musicPlayer.stop()
-        musicPlayer.clear()
-        musicPlayer.add(music)
-        musicPlayer.play()
+        musicPlayer.apply {
+            this.music = music
+            stop()
+            init()
+            start()
+        }
     }
 
     override fun onMusicPlayClick(music: Music, startPosition: Int) {
-        musicPlayer.clear()
-        musicPlayer.add(music)
-        musicPlayer.playChangedListener = object : MusicPlayer.OnPlayChangedListener {
-            override fun onPlayChanged(music: Music, position: Int) {
-                runOnUiThread {
-                    musicEditDialog?.currentPosition = position
+        musicPlayer.apply {
+            this.music = music
+            playChangedListener = object : MusicPlayerV2.OnPlayChangedListener {
+                override fun OnPlayChanged(position: Int) {
+                    runOnUiThread {
+                        musicEditDialog?.currentPosition = position
+                    }
                 }
-
-
             }
+            init()
+            seekTo(startPosition)
+            start()
         }
-        musicPlayer.play(startPosition)
     }
 
     override fun onMusicVolumeChanged(volume: Float) {
@@ -87,34 +92,42 @@ class MovieEditActivity : AppCompatActivity(),
     }
 
     override fun onMusicEditFinish(music: Music) {
-        if (music.endTime - music.startTime > moviePlayer.totalPeriod) {
-            music.endTime = music.startTime + moviePlayer.totalPeriod
+        //TODO change path to correct path
+        val outputFile = File(Environment.getExternalStorageDirectory(), "test${System.currentTimeMillis()}.mp3")
+
+        val pd = ProgressDialog(this).apply {
+            setMessage("progressing")
+            setCancelable(false)
         }
 
-        val builder = AudioEditor.Builder(mediaHelper)
-        builder.editAudioFile = File(music.path)
-        builder.startTime = TimeUtils.getEncodeTimeFormt(music.startTime)
-        builder.endTime = TimeUtils.getEncodeTimeFormt(music.endTime)
-        builder.volume = music.volume
+        viewModel.exportEditAudio(music, outputFile)
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .doOnSubscribe {
+                    pd.show()
+                }
+                .doFinally {
+                    pd.dismiss()
+                }
+                .subscribe({
+                    music.path = outputFile.path
 
-        val fileName = "test${System.currentTimeMillis()}.mp3"
-        val outputFile = File(Environment.getExternalStorageDirectory(), fileName)
-        val audioEditor = builder.build()
-        audioEditor.output(outputFile).subscribe({
-        }, {
-        })
+                    val musics: ArrayList<Music> = viewModel.musicListLiveData.value as ArrayList<Music>
+                    musics[musics.indexOf(viewModel.musicLiveData.value)] = music
+                    viewModel.musicListLiveData.value = musics
+                    viewModel.musicLiveData.value = music
+                }, {
 
+                })
     }
 
     override fun onDismiss(dialog: DialogInterface?) {
         musicPlayer.stop()
-        musicPlayer.release()
-
         delayedHide(100)
     }
 
-    private val shareDialog = ShareDialog()
     private var musicEditDialog: MusicEditDialog? = null
+    private val shareDialog = ShareDialog()
 
     private val mHideHandler = Handler()
     private val mHidePart2Runnable = Runnable {
@@ -133,50 +146,9 @@ class MovieEditActivity : AppCompatActivity(),
         hide()
     }
 
-    private var project: Project? = null
-        set(value) {
-            field = value
-            value?.let {
-                story = it.story
-            }
-        }
-
-    private var story: Story? = null
-        set(value) {
-            field = value
-            moviePlayer.story = value
-            movieEditView.story = value
-            value?.let {
-                scene = it.scenes[0]
-            }
-        }
-
-    private var scene: Scene? = null
-        set(value) {
-            field = value
-            value?.let {
-                movieView.scene = value
-            }
-        }
-
-    private var isLoop = false
-        set(value) {
-            field = value
-            movieEditView.isLoop = value
-        }
-
-    private var currentAudioPosition = 0
-        set(value) {
-            field = value
-            movieEditView.currentAudioPosition = value
-        }
-
+    private val moviePlayer = MoviePlayerV2()
 
-    private val moviePlayer = MoviePlayer()
-
-    private val musicPlayer = MusicPlayer()
-
-    private val musics = ArrayList<Music>()
+    private val musicPlayer = MusicPlayerV2()
 
     private val mediaHelper = MediaHelper()
 
@@ -188,13 +160,42 @@ class MovieEditActivity : AppCompatActivity(),
         private const val UI_ANIMATION_DELAY = 300
     }
 
+    private lateinit var viewModel: MovieEditViewModel
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_movie_edit)
 
-        supportActionBar?.setDisplayHomeAsUpEnabled(true)
+        mediaHelper.init(this)
+
+        viewModel = ViewModelProviders.of(this).get(MovieEditViewModel::class.java)
+        viewModel.mediaHelper = mediaHelper
+        viewModel.project = Gson().fromJson<Project>(intent.extras.getString(PROJECT_KEY), Project::class.java)
+        viewModel.storyLiveData.observe(this, Observer {
+            movieEditView.story = it
+            moviePlayer.story = it
+        })
+        viewModel.storyPeriodLiveData.observe(this, Observer {
+            movieEditView.period = it ?: 0
+            moviePlayer.period = it ?: 0
+        })
+        viewModel.sceneLiveData.observe(this, Observer {
+            movieView.scene = it
+        })
+        viewModel.musicListLiveData.observe(this, Observer {
+            movieEditView.musics = it as ArrayList<Music>
+            movieEditView.isAudioTrackGroupViewVisible = it.isNotEmpty()
+            moviePlayer.musicList = it
+            moviePlayer.init()
 
-        project = Gson().fromJson<Project>(intent.extras.getString(PROJECT_KEY), Project::class.java)
+        })
+        viewModel.musicLiveData.observe(this, Observer {
+            viewModel.musicListLiveData.value?.apply {
+                movieEditView.currentAudioPosition = indexOf(it)
+            }
+        })
+
+        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 
         movieEditView.onPlayClickListener = View.OnClickListener(this::playMovie)
         movieEditView.onPauseClickListener = View.OnClickListener(this::pauseMovie)
@@ -205,7 +206,7 @@ class MovieEditActivity : AppCompatActivity(),
         movieEditView.onSeekBarChangeListener = object : SeekBar.OnSeekBarChangeListener {
             override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                 if (fromUser) {
-                    moviePlayer.seekTo(progress)
+                    moviePlayer.seekTo(progress.toLong())
                 }
             }
 
@@ -217,51 +218,71 @@ class MovieEditActivity : AppCompatActivity(),
         }
         movieEditView.onMovieSelectListener = object : MovieSelectView.OnMovieSelectListener {
             override fun onMoveSelect(scene: Scene) {
-                this@MovieEditActivity.scene = scene
+                viewModel.sceneLiveData.value = scene
             }
         }
         movieEditView.onAudioTrackSelectListener = object : AudioTrackGroupView.OnAudioTrackSelectListener {
             override fun onAudioTrackSelect(position: Int) {
-                currentAudioPosition = position
+                viewModel.musicListLiveData.value?.let {
+                    viewModel.musicLiveData.value = it[position]
+                }
             }
         }
         movieEditView.onAudioTrackScrollListener = object : AudioTrackGroupView.OnAudioTrackScrollListener {
             override fun onAudioTrackScroll(position: Int, scrollRatio: Double) {
-                scene?.record?.period?.let {
-                    musics[position].offsetTime = (it * scrollRatio).toLong()
-                }
+                viewModel.musicListLiveData.value?.get(position)?.offsetTime = ((viewModel.storyPeriodLiveData.value
+                        ?: 0) * scrollRatio).toLong()
+            }
+        }
+        movieEditView.onAudioTrackLoopScrollListener = object : AudioTrackGroupView.OnAudioTrackScrollListener {
+            override fun onAudioTrackScroll(position: Int, scrollRatio: Double) {
+                viewModel.musicListLiveData.value?.get(position)?.loopDuration = ((viewModel.storyPeriodLiveData.value
+                        ?: 0) * scrollRatio).toLong()
             }
         }
         movieEditView.onLoopSwitchChangedListener = CompoundButton.OnCheckedChangeListener { _, isChecked ->
-            isLoop = isChecked
+            val musics: ArrayList<Music> = viewModel.musicListLiveData.value as ArrayList<Music>
+            for (music in musics) {
+                music.isLoop = isChecked
+            }
+            viewModel.musicListLiveData.value = musics
         }
 
-        moviePlayer.moviePlayListener = object : MoviePlayer.OnMoviePlayListener {
+        moviePlayer.moviePlayListener = object : MoviePlayerV2.OnMoviePlayListener {
             override fun onMovieSceneUpdate(scene: Scene) {
-                if (scene != this@MovieEditActivity.scene) {
-                    this@MovieEditActivity.scene = scene
+                runOnUiThread {
+                    if (viewModel.sceneLiveData.value != scene) {
+                        viewModel.sceneLiveData.value = scene
+                    }
                 }
             }
 
             override fun onMoviePlayComplete() {
-                story?.scenes?.let {
-                    this@MovieEditActivity.scene = it[0]
+                runOnUiThread {
+                    viewModel.project?.story?.let {
+                        viewModel.sceneLiveData.value = it.scenes[0]
+                    }
+                    movieEditView.isBtnPanelEnable = true
                 }
-                movieEditView.isBtnPanelEnable = true
             }
 
             override fun onMoviePlayViewUpdate(x: Int) {
-                movieView.x = x
+                runOnUiThread {
+                    movieView.x = x
+                }
             }
 
             override fun onMoviePlayTimeUpdate(time: Long) {
-                movieEditView.playTime = time
+                runOnUiThread {
+                    movieEditView.playTime = time
+                }
             }
         }
 
-        mediaHelper.init(this)
+
     }
 
+
     override fun onPostResume() {
         super.onPostResume()
         delayedHide(100)
@@ -279,7 +300,7 @@ class MovieEditActivity : AppCompatActivity(),
     }
 
     private fun playMovie(view: View) {
-        moviePlayer.play()
+        moviePlayer.start()
         movieEditView.isBtnPanelEnable = false
     }
 
@@ -289,39 +310,26 @@ class MovieEditActivity : AppCompatActivity(),
     }
 
     private fun addMusic(view: View) {
-        val libraryPaths = ArrayList<String>()
-
-        project?.let {
-            val assetFolder = it.assetFolder.path
-            it.themeAssetIndex.contains.music.values.forEach {
-                libraryPaths.add("$assetFolder/$it")
-            }
-        }
+        viewModel.parseDeviceMusic(contentResolver)
 
-        val musicSource = DataParser.parseMusicSource(contentResolver, libraryPaths)
-        val musicSelectDialog = MusicSelectDialog.newInstance(Gson().toJson(musicSource))
+        val musicSelectDialog = MusicSelectDialog.newInstance(Gson().toJson(viewModel.musicSource))
         musicSelectDialog.show(supportFragmentManager, MUSIC_SELECT_DIALOG_TAG)
     }
 
     private fun editMusic(view: View) {
-        musicEditDialog = MusicEditDialog.newInstance(Gson().toJson(musics[currentAudioPosition]))
+        musicEditDialog = MusicEditDialog.newInstance(Gson().toJson(viewModel.musicLiveData.value))
         musicEditDialog?.show(supportFragmentManager, MUSIC_EDIT_DIALOG_TAG)
     }
 
     private fun deleteMusic(view: View) {
-        if (currentAudioPosition in 0 until musics.size) {
-            moviePlayer.init()
-            moviePlayer.removeMusic(musics[currentAudioPosition])
-
-            musics.removeAt(currentAudioPosition)
-
-            movieEditView.musics = musics
-        }
+        val musics: ArrayList<Music> = viewModel.musicListLiveData.value as ArrayList<Music>
+        musics.remove(viewModel.musicLiveData.value)
+        viewModel.musicListLiveData.value = musics
     }
 
     private fun onClickSaveAndShareBtn(view: View) {
         shareDialog.apply {
-            project = this@MovieEditActivity.project ?: return@apply
+            project = viewModel.project ?: return@apply
             onClickBomo = Runnable { saveMovie() }
             onClickExportPdf = Runnable { onClickExportPdf() }
         }.show(supportFragmentManager)
@@ -329,28 +337,28 @@ class MovieEditActivity : AppCompatActivity(),
 
     private fun onClickExportPdf() {
         ExportPdfDialog().also { exportPdfDialog ->
-            exportPdfDialog.image1Path = project?.scene1File?.path ?: ""
-            exportPdfDialog.image2Path = project?.scene2File?.path ?: ""
+            exportPdfDialog.image1Path = viewModel.project?.scene1File?.path ?: ""
+            exportPdfDialog.image2Path = viewModel.project?.scene2File?.path ?: ""
             exportPdfDialog.onClickPrint = Runnable {
                 when (exportPdfDialog.type) {
                     ExportPdfDialog.Type.Standard -> {
-                        val name = "standard_${project?.name}_${System.currentTimeMillis()}.pdf"
+                        val name = "standard_${viewModel.project?.name}_${System.currentTimeMillis()}.pdf"
                         if (!Config.PDF_FOLDER.exists()) {
                             Config.PDF_FOLDER.mkdirs()
                         }
                         val file = File(Config.PDF_FOLDER, name)
                         file.createNewFile()
-                        val observable = PdfMaker.makeStandard(this@MovieEditActivity, project!!, file)
+                        val observable = PdfMaker.makeStandard(this@MovieEditActivity, viewModel.project!!, file)
                         Pair(file, observable)
                     }
                     ExportPdfDialog.Type.Booklet -> {
-                        val name = "booklet_${project?.name}_${System.currentTimeMillis()}.pdf"
+                        val name = "booklet_${viewModel.project?.name}_${System.currentTimeMillis()}.pdf"
                         if (!Config.PDF_FOLDER.exists()) {
                             Config.PDF_FOLDER.mkdirs()
                         }
                         val file = File(Config.PDF_FOLDER, name)
                         file.createNewFile()
-                        val observable = PdfMaker.makeBooklet(this@MovieEditActivity, project!!, file)
+                        val observable = PdfMaker.makeBooklet(this@MovieEditActivity, viewModel.project!!, file)
                         Pair(file, observable)
                     }
                 }.apply {
@@ -369,7 +377,7 @@ class MovieEditActivity : AppCompatActivity(),
         val scaleHeight = resources.getDimensionPixelSize(R.dimen.movie_height)
 
         val pd = ProgressDialog(this).apply {
-            val t = scene?.record?.period ?: 0
+            val t = viewModel.storyPeriodLiveData.value ?: 0
             max = (t / 33).toInt()
             setMessage("progressing")
             setCancelable(false)
@@ -377,13 +385,13 @@ class MovieEditActivity : AppCompatActivity(),
 
         val fileName = "test${System.currentTimeMillis()}.mp4"
         val outputFile = File(Environment.getExternalStorageDirectory(), fileName)
-        val project = project ?: return
+        val project = viewModel.project ?: return
 
         SuperMovieMaker().make(
                 this,
                 outputFile,
                 project,
-                musics,
+                viewModel.musicListLiveData.value?:ArrayList(),
                 scaledWidth,
                 scaleHeight,
                 mediaHelper)
@@ -465,5 +473,4 @@ class MovieEditActivity : AppCompatActivity(),
                     }
                 }.show()
     }
-
 }

+ 101 - 0
src/main/java/com/bomostory/sceneeditmodule/screen/movie/MovieEditViewModel.kt

@@ -0,0 +1,101 @@
+package com.bomostory.sceneeditmodule.screen.movie
+
+import android.arch.lifecycle.MutableLiveData
+import android.arch.lifecycle.ViewModel
+import android.content.ContentResolver
+import android.media.MediaMetadataRetriever
+import com.bomostory.sceneeditmodule.basicdata.*
+import com.bomostory.sceneeditmodule.utils.TimeUtils
+import com.example.exportmedia.MediaHelper
+import com.example.exportmedia.audio.AudioEditor
+import io.reactivex.Observable
+import java.io.File
+
+class MovieEditViewModel : ViewModel() {
+
+    val storyLiveData = MutableLiveData<Story>()
+
+    val storyPeriodLiveData = MutableLiveData<Long>()
+
+    val sceneLiveData = MutableLiveData<Scene>()
+
+    val musicLiveData = MutableLiveData<Music>()
+
+    val musicListLiveData = MutableLiveData<List<Music>>().apply {
+        value = ArrayList()
+    }
+
+    lateinit var mediaHelper: MediaHelper
+
+    var project: Project? = null
+        set(value) {
+            field = value
+            value?.let {
+                musicSource.library = parseThemeMusic(it)
+
+                it.story?.let {
+                    storyLiveData.value = it
+                    storyPeriodLiveData.value = parseStoryPeriod(it)
+                    sceneLiveData.value = it.scenes[0]
+                }
+            }
+        }
+
+    var musicSource = MusicSource()
+
+
+    fun parseDeviceMusic(contentResolver: ContentResolver) {
+        val libMusicPaths = ArrayList<String?>()
+        for (libMusic in musicSource.library) {
+            libMusicPaths.add(libMusic.path)
+        }
+
+        musicSource.device = DataParser.parseMusic(contentResolver, libMusicPaths)
+    }
+
+    fun exportEditAudio(music: Music, outputFile: File): Observable<String> {
+        if (music.endTime - music.startTime > storyPeriodLiveData.value ?: 0) {
+            music.endTime = music.startTime + (storyPeriodLiveData.value ?: 0)
+        }
+
+        val audioEditorBuilder = AudioEditor.Builder(mediaHelper)
+        audioEditorBuilder.editAudioFile = File(music.path)
+        audioEditorBuilder.startTime = TimeUtils.getEncodeTimeFormt(music.startTime)
+        audioEditorBuilder.endTime = TimeUtils.getEncodeTimeFormt(music.endTime)
+        audioEditorBuilder.volume = music.volume
+
+        val audioEditor = audioEditorBuilder.build()
+        return audioEditor.output(outputFile)
+    }
+
+    private fun parseThemeMusic(project: Project): ArrayList<Music> {
+        val musics = ArrayList<Music>()
+        val mediaMetadataRetriever = MediaMetadataRetriever()
+        val assetFolder = project.assetFolder
+
+        project.themeAssetIndex.contains.music.values.forEach {
+            val musicPath = "$assetFolder/$it"
+            val file = File(musicPath)
+
+            mediaMetadataRetriever.setDataSource(musicPath)
+
+            val music = Music()
+            music.name = file.name
+            music.path = file.path
+            music.duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong()
+            music.endTime = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong()
+            musics.add(music)
+        }
+        return musics
+    }
+
+    private fun parseStoryPeriod(story: Story): Long {
+        var totalPeriod: Long = 0
+        for (scene in story.scenes) {
+            val period = scene.record?.period ?: 0
+            totalPeriod += period
+        }
+        return totalPeriod
+    }
+
+}

+ 4 - 2
src/main/java/com/bomostory/sceneeditmodule/screen/movie/music/MusicEditDialog.kt

@@ -83,9 +83,9 @@ class MusicEditDialog : DialogFragment() {
         return inflater.inflate(R.layout.fragment_music_edit_dialog, container, false).apply {
             editMusic?.let {
                 musicName.text = it.name
-                musicLength.text = getString(R.string.music_length, TimeUtils.getPlayMovieTimeFormat(it.duration))
+                musicLength.text = getString(R.string.music_length, TimeUtils.getPlayMovieTimeFormat((it.endTime - it.startTime)))
                 startTime.text = TimeUtils.getPlayMovieTimeFormat(0)
-                endTime.text = TimeUtils.getPlayMovieTimeFormat(it.duration)
+                endTime.text = TimeUtils.getPlayMovieTimeFormat(it.endTime)
                 volumeSeekBar.progress = (it.volume * volumeSeekBar.max).toInt()
                 volumeText.text = "${volumeSeekBar.progress} %"
 
@@ -148,10 +148,12 @@ class MusicEditDialog : DialogFragment() {
                     editMusic?.apply {
                         startTime = (headScrollRatio * duration).toLong()
                         endTime = (endScrollRatio * duration).toLong()
+                        duration = endTime - startTime
                         volume = volumeSeekBar.progress.toFloat() / volumeSeekBar.max.toFloat()
 
                         listener?.onMusicEditFinish(this)
                     }
+                    dismiss()
                 }
             }
         }

+ 11 - 3
src/main/java/com/bomostory/sceneeditmodule/screen/view/AudioTrackGroupView.kt

@@ -52,6 +52,8 @@ class AudioTrackGroupView : ConstraintLayout {
 
     var onAudioTrackScrollListener: OnAudioTrackScrollListener? = null
 
+    var onAudioTrackLoopScrollListener: OnAudioTrackScrollListener? = null
+
     constructor(context: Context) : super(context) {
         initView()
     }
@@ -136,15 +138,16 @@ class AudioTrackGroupView : ConstraintLayout {
         inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
             fun bind(p1: Int) {
                 itemView.apply {
+
                     loopAudioTrackView.innerWidthRatio =
                             if (musics[p1].duration >= scenePeriod) 1f
-                            else (musics[p1].duration).toFloat() / scenePeriod.toFloat()
+                            else musics[p1].duration.toFloat() / scenePeriod.toFloat()
 
                     musics[p1].name?.let {
                         loopAudioTrackView.innerText = it
                     }
-                    
-                    loopAudioTrackView.isLoop = isLoop
+
+                    loopAudioTrackView.isLoop = musics[p1].isLoop
                     loopAudioTrackView.isSelect = (p1 == currentPosition)
                     loopAudioTrackView.onSelectListener = object : LoopAudioTrackView.OnSelectListener {
                         override fun onSelect() {
@@ -156,6 +159,11 @@ class AudioTrackGroupView : ConstraintLayout {
                             onAudioTrackScrollListener?.onAudioTrackScroll(p1, scrollRatio)
                         }
                     }
+                    loopAudioTrackView.onLoopScrollListener = object : LoopAudioTrackView.OnLoopScrollListener {
+                        override fun onScroll(scrollRatio: Double) {
+                            onAudioTrackLoopScrollListener?.onAudioTrackScroll(p1, scrollRatio)
+                        }
+                    }
                 }
             }
         }

+ 10 - 14
src/main/java/com/bomostory/sceneeditmodule/screen/view/MovieEditView.kt

@@ -17,14 +17,6 @@ class MovieEditView : ConstraintLayout {
     var story: Story? = null
         set(value) {
             movieSelectView.story = value
-
-            value?.apply {
-                for (scene in scenes) {
-                    scene.record?.let {
-                        period += it.period
-                    }
-                }
-            }
         }
 
     var period: Long = 0
@@ -89,6 +81,11 @@ class MovieEditView : ConstraintLayout {
             audioTrackGroupView.onAudioTrackScrollListener = value
         }
 
+    var onAudioTrackLoopScrollListener: AudioTrackGroupView.OnAudioTrackScrollListener? = null
+        set(value) {
+            audioTrackGroupView.onAudioTrackLoopScrollListener = value
+        }
+
     var onDeleteMusicClickListener: OnClickListener? = null
         set(value) {
             movieEditBtnPanel.onDeleteMusicClickListener = value
@@ -103,18 +100,17 @@ class MovieEditView : ConstraintLayout {
         set(value) {
             movieEditBtnPanel.isMusicEmpty = value.isEmpty()
             audioTrackGroupView.musics = value
-            if (value.isNotEmpty()) {
+        }
+
+    var isAudioTrackGroupViewVisible = false
+        set(value) {
+            if (value) {
                 audioTrackGroupView.visibility = View.VISIBLE
             } else {
                 audioTrackGroupView.visibility = View.GONE
             }
         }
 
-    var isLoop = false
-        set(value) {
-            audioTrackGroupView.isLoop = value
-        }
-
     var isBtnPanelEnable = true
         set(value) {
             movieEditBtnPanel.isEnable = value

+ 0 - 1
src/main/java/com/bomostory/sceneeditmodule/screen/view/MovieSelectView.kt

@@ -33,7 +33,6 @@ class MovieSelectView : ConstraintLayout {
             movieList?.apply {
                 adapter = MovieAdapter()
             }
-
         }
 
     constructor(context: Context) : super(context) {

+ 0 - 148
src/main/java/com/bomostory/sceneeditmodule/utils/MoviePlayer.kt

@@ -1,148 +0,0 @@
-package com.bomostory.sceneeditmodule.utils
-
-import com.bomostory.sceneeditmodule.basicdata.Music
-import com.bomostory.sceneeditmodule.basicdata.Scene
-import com.bomostory.sceneeditmodule.basicdata.Story
-import io.reactivex.Observable
-import io.reactivex.Observer
-import io.reactivex.android.schedulers.AndroidSchedulers
-import io.reactivex.disposables.Disposable
-import io.reactivex.schedulers.Schedulers
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.atomic.AtomicLong
-
-class MoviePlayer {
-    interface OnMoviePlayListener {
-        fun onMovieSceneUpdate(scene: Scene)
-        fun onMoviePlayViewUpdate(x: Int)
-        fun onMoviePlayTimeUpdate(time: Long)
-        fun onMoviePlayComplete()
-    }
-
-    companion object {
-        const val INTERVAL_PERIOD: Long = 1
-    }
-
-    var story: Story? = null
-        set(value) {
-            field = value
-            value?.apply {
-                for (scene in scenes) {
-                    scene.record?.period?.let {
-                        totalPeriod += it
-                    }
-                }
-            }
-        }
-
-    var totalPeriod = 0L
-
-    var musicPlayer = MusicPlayer()
-
-    var moviePlayListener: OnMoviePlayListener? = null
-    private val lastTick = AtomicLong(0L)
-    private var disposable: Disposable? = null
-
-    fun addMusic(music: Music) {
-        musicPlayer.add(music)
-    }
-
-    fun removeMusic(music: Music) {
-        musicPlayer.remove(music)
-    }
-
-    fun init() {
-        disposable?.apply {
-            if (!isDisposed) {
-                dispose()
-            }
-        }
-        moviePlayListener?.onMoviePlayViewUpdate(0)
-        moviePlayListener?.onMoviePlayTimeUpdate(0)
-        lastTick.set(0)
-        musicPlayer.stop()
-    }
-
-    fun play() {
-        Observable.interval(INTERVAL_PERIOD, TimeUnit.MILLISECONDS)
-                .map {
-                    lastTick.getAndIncrement()
-                }
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(object : Observer<Long> {
-                    override fun onComplete() {
-                        init()
-                        moviePlayListener?.onMoviePlayComplete()
-                    }
-
-                    override fun onError(e: Throwable) {
-                    }
-
-                    override fun onSubscribe(d: Disposable) {
-                        disposable = d
-                        musicPlayer.play()
-                    }
-
-                    override fun onNext(t: Long) {
-                        story?.apply {
-
-                            updateMovie(t)
-
-                            if (t > (totalPeriod / INTERVAL_PERIOD)) {
-                                disposable?.dispose()
-                                onComplete()
-                            }
-                        }
-                    }
-                })
-    }
-
-    fun pause() {
-        disposable?.dispose()
-        musicPlayer.pause()
-    }
-
-    fun seekTo(mSec: Int?) {
-        mSec?.let {
-            lastTick.set(it.toLong())
-            updateMovie(mSec.toLong())
-            musicPlayer.seekTo(it)
-        }
-    }
-
-    private fun updateMovie(t: Long) {
-        val scene = getScene(t)
-        scene?.apply {
-            moviePlayListener?.onMovieSceneUpdate(this)
-            record?.let {
-                for (track in it.tracks) {
-                    if (track.time == t) {
-                        moviePlayListener?.onMoviePlayViewUpdate(track.positionX)
-                    }
-                }
-            }
-        }
-        moviePlayListener?.onMoviePlayTimeUpdate(t)
-    }
-
-    private fun getScene(t: Long): Scene? {
-        var currentScene: Scene? = null
-        var startPeriod = 0L
-        var endPeriod = 0L
-
-        story?.apply {
-            for (scene in scenes) {
-                scene.record?.apply {
-                    endPeriod += period
-                    if (t in startPeriod..endPeriod) {
-                        currentScene = scene
-                        startPeriod = endPeriod
-                    }
-                }
-            }
-        }
-
-        return currentScene
-    }
-}

+ 176 - 0
src/main/java/com/bomostory/sceneeditmodule/utils/MoviePlayerV2.kt

@@ -0,0 +1,176 @@
+package com.bomostory.sceneeditmodule.utils
+
+import android.media.MediaPlayer
+import com.bomostory.sceneeditmodule.basicdata.Music
+import com.bomostory.sceneeditmodule.basicdata.Scene
+import com.bomostory.sceneeditmodule.basicdata.Story
+import java.util.*
+import kotlin.collections.ArrayList
+
+class MoviePlayerV2 {
+    interface OnMoviePlayListener {
+        fun onMovieSceneUpdate(scene: Scene)
+        fun onMoviePlayViewUpdate(x: Int)
+        fun onMoviePlayTimeUpdate(time: Long)
+        fun onMoviePlayComplete()
+    }
+
+    var moviePlayListener: OnMoviePlayListener? = null
+
+    var story: Story? = null
+
+    var period: Long = 0
+
+    var timer: Timer? = null
+
+    var currentTime: Long = 0
+
+    var isPause = false
+
+    var musicList: List<Music> = ArrayList()
+
+    var mediaPlayers = ArrayList<MediaPlayer>()
+
+    fun init() {
+        timer?.cancel()
+        timer?.purge()
+        timer = null
+
+        currentTime = 0
+
+        updateMovie()
+
+        initMediaPlayer()
+    }
+
+    fun start() {
+        if (timer == null) {
+            timer = Timer()
+            timer?.scheduleAtFixedRate(object : TimerTask() {
+                override fun run() {
+                    if (!isPause) {
+
+                        currentTime++
+
+                        updateMovie()
+
+                        tryPlayMusic()
+
+                        if (currentTime >= period) {
+                            moviePlayListener?.onMoviePlayComplete()
+                            tryStopMusic()
+                            init()
+                        }
+                    }
+                }
+            }, 0, 1)
+        } else {
+            isPause = false
+        }
+    }
+
+    fun pause() {
+        isPause = true
+
+        tryPauseMusic()
+    }
+
+    fun seekTo(currentTime: Long) {
+        this.currentTime = currentTime
+
+        updateMovie()
+
+        trySeekMusic()
+    }
+
+    private fun updateMovie() {
+        val scene = getScene(currentTime)
+        scene?.apply {
+            moviePlayListener?.onMovieSceneUpdate(this)
+            record?.let {
+                for (track in it.tracks) {
+                    if (track.time == currentTime) {
+                        moviePlayListener?.onMoviePlayViewUpdate(track.positionX)
+                    }
+                }
+            }
+        }
+        moviePlayListener?.onMoviePlayTimeUpdate(currentTime)
+    }
+
+    private fun getScene(t: Long): Scene? {
+        var currentScene: Scene? = null
+        var startPeriod = 0L
+        var endPeriod = 0L
+
+        story?.apply {
+            for (scene in scenes) {
+                scene.record?.apply {
+                    endPeriod += period
+                    if (t in startPeriod..endPeriod) {
+                        currentScene = scene
+                        startPeriod = endPeriod
+                    }
+                }
+            }
+        }
+        return currentScene
+    }
+
+    private fun initMediaPlayer() {
+        mediaPlayers.clear()
+        for (music in musicList) {
+            val mediaPlayer = MediaPlayer()
+            mediaPlayer.apply {
+                reset()
+                setDataSource(music.path)
+                isLooping = music.isLoop
+                prepare()
+            }
+            mediaPlayers.add(mediaPlayer)
+        }
+    }
+
+    private fun tryPlayMusic() {
+        for (music in musicList) {
+            val musicEndTime  = if(music.isLoop) (music.offsetTime + music.endTime - music.startTime) + music.loopDuration else (music.offsetTime + music.endTime - music.startTime)
+
+            if (currentTime in music.offsetTime..musicEndTime) {
+                if (!mediaPlayers[musicList.indexOf(music)].isPlaying) {
+                    mediaPlayers[musicList.indexOf(music)].start()
+                } else if (currentTime >= musicEndTime) {
+                    mediaPlayers[musicList.indexOf(music)].stop()
+                    mediaPlayers[musicList.indexOf(music)].prepare()
+                }
+            }
+        }
+    }
+
+    private fun tryPauseMusic() {
+        for (mediaPlayer in mediaPlayers) {
+            mediaPlayer.pause()
+        }
+    }
+
+    private fun trySeekMusic() {
+        for (music in musicList) {
+            val musicEndTime  = if(music.isLoop) (music.offsetTime + music.endTime - music.startTime) + music.loopDuration else (music.offsetTime + music.endTime - music.startTime)
+
+            if (currentTime in music.offsetTime..musicEndTime) {
+                val actualTime = if (currentTime - music.offsetTime >= music.endTime - music.startTime)((currentTime - music.offsetTime).toDouble() / (music.endTime - music.startTime)).toInt()
+                else (currentTime - music.offsetTime).toInt()
+                mediaPlayers[musicList.indexOf(music)].seekTo(actualTime)
+            } else {
+                mediaPlayers[musicList.indexOf(music)].stop()
+                mediaPlayers[musicList.indexOf(music)].prepare()
+            }
+        }
+    }
+
+    private fun tryStopMusic(){
+        for (mediaPlayer in mediaPlayers) {
+            mediaPlayer.stop()
+        }
+    }
+
+}

+ 0 - 162
src/main/java/com/bomostory/sceneeditmodule/utils/MusicPlayer.kt

@@ -1,162 +0,0 @@
-package com.bomostory.sceneeditmodule.utils
-
-import android.media.MediaPlayer
-import com.bomostory.sceneeditmodule.basicdata.Music
-import java.util.*
-import kotlin.collections.ArrayList
-
-class MusicPlayer {
-    interface OnPlayChangedListener {
-        fun onPlayChanged(music: Music, position: Int)
-    }
-
-    private var musics = ArrayList<Music>()
-    private var mediaPlayers = ArrayList<MediaPlayer>()
-
-    private var isPause = false
-
-    private var timer: Timer? = null
-    var playChangedListener: OnPlayChangedListener? = null
-
-    fun add(music: Music) {
-        musics.add(music)
-    }
-
-    fun remove(music: Music) {
-        musics.remove(music)
-    }
-
-    fun clear() {
-        musics.clear()
-    }
-
-    fun play() {
-        if (!isPause) {
-            prepare()
-            for (mediaPlayer in mediaPlayers) {
-                mediaPlayer.apply {
-                    prepareAsync()
-                }
-            }
-        } else {
-            isPause = false
-            start()
-        }
-        startTimer()
-    }
-
-    fun play(position: Int) {
-        if (!isPause) {
-            prepare(position)
-
-            for (mediaPlayer in mediaPlayers) {
-                mediaPlayer.apply {
-                    prepareAsync()
-                }
-            }
-        } else {
-            isPause = false
-            seekTo(position)
-            start()
-        }
-        startTimer()
-    }
-
-    private fun prepare() {
-        mediaPlayers.clear()
-
-        for (music in musics) {
-            val mediaPlayer = MediaPlayer()
-            mediaPlayer.apply {
-                reset()
-                setDataSource(music.path)
-                setOnPreparedListener {
-                    it.start()
-                }
-                setOnCompletionListener {
-                    mediaPlayers.remove(it)
-                }
-                mediaPlayers.add(mediaPlayer)
-            }
-        }
-    }
-
-    private fun prepare(position: Int) {
-        mediaPlayers.clear()
-
-        for (music in musics) {
-            val mediaPlayer = MediaPlayer()
-            mediaPlayer.apply {
-                reset()
-                setDataSource(music.path)
-                setOnPreparedListener {
-                    it.seekTo(position)
-                    it.start()
-                }
-                setOnCompletionListener {
-                    mediaPlayers.remove(it)
-                }
-                mediaPlayers.add(mediaPlayer)
-            }
-        }
-    }
-
-
-    private fun start() {
-        for (mediaPlayer in mediaPlayers) {
-            mediaPlayer.apply {
-                start()
-            }
-        }
-    }
-
-    fun pause() {
-        isPause = true
-        for (mediaPlayer in mediaPlayers) {
-            mediaPlayer.pause()
-        }
-        timer?.cancel()
-    }
-
-    fun seekTo(mSec: Int) {
-        for (mediaPlayer in mediaPlayers) {
-            mediaPlayer.seekTo(mSec)
-        }
-    }
-
-    fun setVolume(volume: Float) {
-        for (mediaPlayer in mediaPlayers) {
-            mediaPlayer.setVolume(volume, volume)
-        }
-    }
-
-    fun stop() {
-        isPause = false
-        for (mediaPlayer in mediaPlayers) {
-            mediaPlayer.apply {
-                stop()
-            }
-        }
-        timer?.cancel()
-    }
-
-    fun release() {
-        for (mediaPlayer in mediaPlayers) {
-            mediaPlayer.apply {
-                release()
-            }
-        }
-        mediaPlayers.clear()
-    }
-
-    private fun startTimer() {
-        timer = Timer()
-        timer?.schedule(object : TimerTask() {
-            override fun run() {
-                for (mediaPlayer in mediaPlayers) {
-                    playChangedListener?.onPlayChanged(musics[mediaPlayers.indexOf(mediaPlayer)], mediaPlayer.currentPosition)
-                }
-            }
-        }, 0, 1000)
-    }
-}

+ 78 - 0
src/main/java/com/bomostory/sceneeditmodule/utils/MusicPlayerV2.kt

@@ -0,0 +1,78 @@
+package com.bomostory.sceneeditmodule.utils
+
+import android.media.MediaPlayer
+import com.bomostory.sceneeditmodule.basicdata.Music
+import java.util.*
+
+class MusicPlayerV2 {
+    interface OnPlayChangedListener {
+        fun OnPlayChanged(position: Int)
+    }
+
+    lateinit var music: Music
+
+    val mediaPlayer = MediaPlayer()
+
+    var timer: Timer? = null
+
+    var isPause = false
+
+    var playChangedListener: OnPlayChangedListener? = null
+
+    fun init() {
+        mediaPlayer.apply {
+            if (!isPause) {
+                reset()
+                setDataSource(music.path)
+                prepare()
+            }
+        }
+    }
+
+    fun start() {
+        if (!isPause) {
+            startTimer()
+        }
+        isPause = false
+        mediaPlayer.start()
+    }
+
+    fun pause() {
+        isPause = true
+        mediaPlayer.pause()
+    }
+
+    fun seekTo(mSec: Int) {
+        mediaPlayer.seekTo(mSec)
+    }
+
+    fun setVolume(volume: Float){
+        mediaPlayer.setVolume(volume, volume)
+    }
+
+    fun stop() {
+        isPause = false
+
+        timer?.cancel()
+        timer?.purge()
+        timer = null
+
+        mediaPlayer.stop()
+    }
+
+    fun release(){
+        mediaPlayer.release()
+    }
+
+    private fun startTimer() {
+        timer = Timer()
+        timer?.scheduleAtFixedRate(object : TimerTask() {
+            override fun run() {
+                if (!isPause) {
+                    playChangedListener?.OnPlayChanged(mediaPlayer.currentPosition)
+                }
+            }
+        }, 0, 1)
+    }
+
+}

+ 1 - 1
src/main/res/layout/movie_edit_btn_panel_view.xml

@@ -45,7 +45,7 @@
     <!--TODO : Remove enabled and drawableTint after implement this function-->
     <Button
         android:drawableTint="@color/brown_grey"
-        android:enabled="false"
+        android:enabled="true"
         android:id="@+id/edit"
         android:layout_width="96dp"
         android:layout_height="36dp"