worked on karaoke ui syncing with different recording states

saving karaoke audio file to download folder
This commit is contained in:
2024-07-12 21:07:52 +05:30
parent 5bfb1c03f5
commit a79ba8813e
3 changed files with 234 additions and 45 deletions

View File

@@ -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")
}
}
}

View File

@@ -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>

View File

@@ -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>