From a79ba8813ef011025a3f5a0ef9b4829d2307bde1 Mon Sep 17 00:00:00 2001 From: AdityaGaikwad Date: Fri, 12 Jul 2024 21:07:52 +0530 Subject: [PATCH] worked on karaoke ui syncing with different recording states saving karaoke audio file to download folder --- .../karaoke/player/KaraokePlayerActivity.kt | 170 +++++++++++++----- .../res/layout/activity_karaoke_playerr.xml | 103 ++++++++++- app/src/main/res/values/strings.xml | 6 + 3 files changed, 234 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/woka/karaoke/player/KaraokePlayerActivity.kt b/app/src/main/java/com/woka/karaoke/player/KaraokePlayerActivity.kt index aedd7fb..21ea594 100644 --- a/app/src/main/java/com/woka/karaoke/player/KaraokePlayerActivity.kt +++ b/app/src/main/java/com/woka/karaoke/player/KaraokePlayerActivity.kt @@ -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 - 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") + } + } + } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_karaoke_playerr.xml b/app/src/main/res/layout/activity_karaoke_playerr.xml index f704bf1..478d041 100644 --- a/app/src/main/res/layout/activity_karaoke_playerr.xml +++ b/app/src/main/res/layout/activity_karaoke_playerr.xml @@ -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" /> + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7141644..93e8ecd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -213,4 +213,10 @@ PAUSE Cannot load karaoke Karaoke process failed. + Preparing karaoke... + Audio is ready to play + Creating karaoke music... + No recorded audio found + Download complete + File saved to your download folder. \ No newline at end of file