worked on karaoke ui syncing with different recording states
saving karaoke audio file to download folder
This commit is contained in:
@@ -1,26 +1,29 @@
|
||||
package com.woka.karaoke.player
|
||||
|
||||
import android.Manifest
|
||||
import android.R.attr.mimeType
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.media.MediaCodec
|
||||
import android.media.MediaExtractor
|
||||
import android.media.MediaFormat
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.media.MediaMuxer
|
||||
import android.media.MediaPlayer
|
||||
import android.media.MediaRecorder
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkRequest
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
@@ -33,7 +36,6 @@ import androidx.media3.exoplayer.ExoPlayer
|
||||
import com.woka.R
|
||||
import com.woka.databinding.ActivityKaraokePlayerrBinding
|
||||
import com.woka.players.models.PlayBackState
|
||||
import com.woka.utils.ProgressView
|
||||
import com.woka.utils.TAG
|
||||
import com.woka.utils.WokaBaseActivity
|
||||
import com.woka.utils.hide
|
||||
@@ -42,14 +44,17 @@ import com.woka.utils.toast
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import okio.FileNotFoundException
|
||||
import zeroonezero.android.audio_mixer.AudioMixer
|
||||
import zeroonezero.android.audio_mixer.input.GeneralAudioInput
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.net.URL
|
||||
import java.nio.ByteBuffer
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
@@ -88,8 +93,6 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
|
||||
private lateinit var permissionLauncher: ActivityResultLauncher<String>
|
||||
|
||||
private lateinit var progressView: ProgressView
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
@@ -107,9 +110,6 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
WindowCompat.getInsetsController(window, window.decorView)
|
||||
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
||||
|
||||
progressView = ProgressView(this, getString(R.string.please_wait))
|
||||
progressView.show()
|
||||
|
||||
karaokePlayerData = intent.getParcelableExtra(EXTRA_KARAOKE_DATA)
|
||||
|
||||
networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
@@ -141,6 +141,8 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
|
||||
clickEvents()
|
||||
|
||||
playVideo()
|
||||
|
||||
registerLaunchers()
|
||||
|
||||
loadAudioFromUrl()
|
||||
@@ -207,6 +209,26 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
audioBtn.setOnClickListener {
|
||||
playStopPlayingAudio()
|
||||
}
|
||||
|
||||
downloadBtn.setOnClickListener {
|
||||
try {
|
||||
val cal = Calendar.getInstance()
|
||||
val fileName = "${karaokePlayerData?.title}_${
|
||||
SimpleDateFormat(
|
||||
"dd MMM yyyy hh:mm a",
|
||||
Locale.getDefault()
|
||||
).format(cal.time)
|
||||
}.mp3"
|
||||
|
||||
saveFileUsingMediaStore(fileName)
|
||||
|
||||
} catch (e: FileNotFoundException) {
|
||||
toast(getString(R.string.no_recorded_audio_found))
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "clickEvents: $e")
|
||||
toast(getString(R.string.something_went_wrong))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,6 +248,7 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
binding.apply {
|
||||
when (recordingState) {
|
||||
RecordingState.RECORDING -> {
|
||||
|
||||
recordBtn.text = getString(R.string.stop_recording)
|
||||
recordBtn.setCompoundDrawablesWithIntrinsicBounds(
|
||||
R.drawable.ic_rec_mic_cross,
|
||||
@@ -244,6 +267,7 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
}
|
||||
|
||||
RecordingState.NOT_RECORDING -> {
|
||||
|
||||
recordBtn.text = getString(R.string.stop_recording)
|
||||
recordBtn.setCompoundDrawablesWithIntrinsicBounds(
|
||||
R.drawable.ic_rec_mic,
|
||||
@@ -262,6 +286,7 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
}
|
||||
|
||||
RecordingState.PLAYING_AUDIO -> {
|
||||
|
||||
recordBtn.text = getString(R.string.stop_recording)
|
||||
recordBtn.setCompoundDrawablesWithIntrinsicBounds(
|
||||
R.drawable.ic_rec_mic,
|
||||
@@ -331,9 +356,8 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
|
||||
private fun loadAudioFromUrl() {
|
||||
if (karaokePlayerData?.karaokeVideoUrl == null) {
|
||||
progressView.hide()
|
||||
binding.progressView.hide()
|
||||
toast(getString(R.string.canoot_load_karaoke))
|
||||
playVideo()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -351,8 +375,7 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
}
|
||||
} finally {
|
||||
runOnUiThread {
|
||||
playVideo()
|
||||
progressView.hide()
|
||||
binding.progressView.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,6 +394,14 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
setDataSource(this@KaraokePlayerActivity, Uri.parse(karaokeFinalPath))
|
||||
prepare()
|
||||
|
||||
if (player?.isPlaying == true) {
|
||||
player?.pause()
|
||||
}
|
||||
audioPlayer?.start()
|
||||
|
||||
recordingState = RecordingState.PLAYING_AUDIO
|
||||
updateRecordingUI()
|
||||
|
||||
setOnCompletionListener {
|
||||
audioPlayer?.release()
|
||||
audioPlayer = null
|
||||
@@ -378,26 +409,18 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
recordingState = RecordingState.NOT_RECORDING
|
||||
updateRecordingUI()
|
||||
}
|
||||
} catch (e: FileNotFoundException) {
|
||||
toast(getString(R.string.no_recorded_audio_found))
|
||||
recordingState = RecordingState.NOT_RECORDING
|
||||
audioPlayer = null
|
||||
updateRecordingUI()
|
||||
} catch (e: Exception) {
|
||||
toast(getString(R.string.something_went_wrong))
|
||||
recordingState = RecordingState.NOT_RECORDING
|
||||
audioPlayer = null
|
||||
updateRecordingUI()
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (player?.isPlaying == true) {
|
||||
player?.pause()
|
||||
}
|
||||
audioPlayer?.start()
|
||||
|
||||
recordingState = RecordingState.PLAYING_AUDIO
|
||||
updateRecordingUI()
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "playStopPlayingAudio: $e")
|
||||
toast(getString(R.string.something_went_wrong))
|
||||
recordingState = RecordingState.NOT_RECORDING
|
||||
audioPlayer?.release()
|
||||
audioPlayer = null
|
||||
}
|
||||
}
|
||||
|
||||
RecordingState.RECORDING -> {
|
||||
@@ -447,14 +470,17 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
}
|
||||
|
||||
RecordingState.RECORDING -> {
|
||||
binding.recorderView.hide()
|
||||
binding.mixingProgressView.show()
|
||||
try {
|
||||
stopRecording()
|
||||
} catch (e: Exception) {
|
||||
// do nothing
|
||||
} finally {
|
||||
binding.playerView.showController()
|
||||
if (player?.isPlaying == true) player?.pause()
|
||||
updateRecordingUI()
|
||||
binding.playerView.showController()
|
||||
mixAudios()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,6 +512,8 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
updateRecordingUI()
|
||||
} catch (e: Exception) {
|
||||
toast(getString(R.string.something_went_wrong))
|
||||
recordingState = RecordingState.NOT_RECORDING
|
||||
updateRecordingUI()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -497,21 +525,20 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
toast(getString(R.string.recording_stopped))
|
||||
|
||||
release()
|
||||
|
||||
mixAudios()
|
||||
|
||||
}
|
||||
recorder = null
|
||||
}
|
||||
|
||||
private fun mixAudios() {
|
||||
try {
|
||||
if (startRecordingPos != null){
|
||||
binding.recorderView.hide()
|
||||
binding.mixingProgressView.show()
|
||||
if (startRecordingPos != null) {
|
||||
val input1 = GeneralAudioInput(karaokeMusicPath)
|
||||
input1.startTimeUs = startRecordingPos!! * 1000
|
||||
input1.startOffsetUs = 100_000
|
||||
getAudioDuration(recordingOutputPath)?.let {
|
||||
input1.endTimeUs = input1.startTimeUs + (it * 1000)
|
||||
input1.endTimeUs = input1.startTimeUs + (it * 1000)
|
||||
}
|
||||
val input2 = GeneralAudioInput(recordingOutputPath)
|
||||
|
||||
@@ -520,33 +547,43 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
audioMixer.addDataSource(input2)
|
||||
audioMixer.isLoopingEnabled = false
|
||||
audioMixer.mixingType = AudioMixer.MixingType.PARALLEL
|
||||
audioMixer.setProcessingListener(object : AudioMixer.ProcessingListener{
|
||||
audioMixer.setProcessingListener(object : AudioMixer.ProcessingListener {
|
||||
override fun onProgress(progress: Double) {
|
||||
runOnUiThread {
|
||||
Log.d("aditya_test", "onProgress: ${(progress * 100).toInt()}")
|
||||
val progressPercent = (progress * 100).toInt()
|
||||
binding.mixingProgressBar.progress = progressPercent
|
||||
val txt = "$progressPercent %"
|
||||
binding.mixingProgressPercent.text = txt
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEnd() {
|
||||
runOnUiThread {
|
||||
Log.d(TAG, "onEnd: ")
|
||||
binding.recorderView.show()
|
||||
binding.mixingProgressView.hide()
|
||||
binding.mixingProgressBar.progress = 0
|
||||
toast(getString(R.string.audio_is_ready_to_play))
|
||||
|
||||
audioMixer.release()
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
audioMixer.start()
|
||||
|
||||
audioMixer.processAsync()
|
||||
}else{
|
||||
} else {
|
||||
throw Exception()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
binding.recorderView.show()
|
||||
binding.mixingProgressView.hide()
|
||||
toast(getString(R.string.karaoke_process_failed))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAudioDuration(audio: String):Long? {
|
||||
private fun getAudioDuration(audio: String): Long? {
|
||||
val mmr = MediaMetadataRetriever()
|
||||
mmr.setDataSource(this, Uri.parse(audio))
|
||||
val durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
|
||||
@@ -568,4 +605,49 @@ class KaraokePlayerActivity : WokaBaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveFileUsingMediaStore(fileName: String) {
|
||||
val resolver = applicationContext.contentResolver
|
||||
|
||||
val audioCollection =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
MediaStore.Audio.Media.getContentUri(
|
||||
MediaStore.VOLUME_EXTERNAL_PRIMARY
|
||||
)
|
||||
} else {
|
||||
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||
}
|
||||
|
||||
val songDetails = ContentValues().apply {
|
||||
put(MediaStore.Audio.Media.DISPLAY_NAME, fileName)
|
||||
put(MediaStore.Audio.Media.IS_PENDING, 1)
|
||||
}
|
||||
|
||||
resolver.insert(audioCollection, songDetails)?.let {songContentUri ->
|
||||
try {
|
||||
resolver.openFileDescriptor(songContentUri, "w", null)?.use { pfd ->
|
||||
FileOutputStream(pfd.fileDescriptor).use {outputStream ->
|
||||
FileInputStream(File(karaokeFinalPath)).use { input ->
|
||||
outputStream.use { output ->
|
||||
val buffer = ByteArray(4 * 1024) // buffer size
|
||||
while (true) {
|
||||
val byteCount = input.read(buffer)
|
||||
if (byteCount < 0) break
|
||||
output.write(buffer, 0, byteCount)
|
||||
}
|
||||
output.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch (e: Exception){
|
||||
resolver.delete(songContentUri, null, null)
|
||||
}
|
||||
|
||||
songDetails.clear()
|
||||
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0)
|
||||
resolver.update(songContentUri, songDetails, null, null)
|
||||
toast("Done")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -71,8 +71,107 @@
|
||||
android:layout_height="wrap_content"
|
||||
app:controller_layout_id="@layout/exo_player_control_view"
|
||||
android:layout_centerInParent="true"
|
||||
android:visibility="gone"
|
||||
android:visibility="visible"
|
||||
/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/progress_view"
|
||||
android:visibility="visible"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="15dp"
|
||||
android:layout_below="@id/player_view"
|
||||
android:layout_marginTop="60dp">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/white"
|
||||
android:layout_centerHorizontal="true"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
android:text="@string/preparing_karaoke"
|
||||
android:fontFamily="@font/exo_2_bold"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/_11ssp"
|
||||
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_below="@id/progress_bar"
|
||||
|
||||
android:layout_marginTop="10dp"
|
||||
|
||||
/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/mixing_progress_view"
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="15dp"
|
||||
android:layout_below="@id/player_view"
|
||||
android:layout_marginTop="60dp">
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/mixing_progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
app:indicatorSize="80dp"
|
||||
app:indicatorColor="@android:color/holo_blue_dark"
|
||||
app:trackColor="@color/white_60"
|
||||
app:trackThickness="6dp"
|
||||
|
||||
android:layout_centerHorizontal="true"
|
||||
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mixing_progress_percent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
android:fontFamily="@font/exo_2_bold"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/_11ssp"
|
||||
|
||||
app:layout_constraintStart_toStartOf="@id/mixing_progress_bar"
|
||||
app:layout_constraintEnd_toEndOf="@id/mixing_progress_bar"
|
||||
app:layout_constraintTop_toTopOf="@id/mixing_progress_bar"
|
||||
app:layout_constraintBottom_toBottomOf="@id/mixing_progress_bar"
|
||||
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
android:text="@string/creating_karaoke_music"
|
||||
android:fontFamily="@font/exo_2_bold"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/_11ssp"
|
||||
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_below="@id/mixing_progress_bar"
|
||||
|
||||
android:layout_marginTop="10dp"
|
||||
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/mixing_progress_bar"
|
||||
/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/recorder_view"
|
||||
@@ -162,6 +261,8 @@
|
||||
android:textSize="16sp"
|
||||
|
||||
android:layout_marginTop="10dp"
|
||||
|
||||
android:padding="5dp"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -213,4 +213,10 @@
|
||||
<string name="pause">PAUSE</string>
|
||||
<string name="canoot_load_karaoke">Cannot load karaoke</string>
|
||||
<string name="karaoke_process_failed">Karaoke process failed.</string>
|
||||
<string name="preparing_karaoke">Preparing karaoke...</string>
|
||||
<string name="audio_is_ready_to_play">Audio is ready to play</string>
|
||||
<string name="creating_karaoke_music">Creating karaoke music...</string>
|
||||
<string name="no_recorded_audio_found">No recorded audio found</string>
|
||||
<string name="download_complete">Download complete</string>
|
||||
<string name="file_saved_to_your_download_folder">File saved to your download folder.</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user