implementing karaoke

This commit is contained in:
2024-07-11 21:12:02 +05:30
parent fbb253d612
commit 5bfb1c03f5
11 changed files with 458 additions and 149 deletions

View File

@@ -18,7 +18,7 @@
tools:targetApi="31">
<activity
android:name=".karaoke.player.KaraokePlayerActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode|fontScale"
android:exported="false"
android:launchMode="singleTask"
android:supportsPictureInPicture="true"

View File

@@ -51,6 +51,9 @@ object KaraokeRepository {
val response = handleApiCall {
apiService.karaokeListing(
FormBody.Builder()
.add("api_version", "v2")
.add("start", "0")
.add("limit", "10")
.build()
)
}

View File

@@ -2,6 +2,11 @@ package com.woka.karaoke.player
import android.Manifest
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
@@ -11,11 +16,11 @@ import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.WindowManager
import android.widget.ImageView
import android.widget.TextView
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.core.app.ActivityCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
@@ -23,15 +28,29 @@ import androidx.core.view.WindowInsetsCompat
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
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
import com.woka.utils.show
import com.woka.utils.toast
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import zeroonezero.android.audio_mixer.AudioMixer
import zeroonezero.android.audio_mixer.input.GeneralAudioInput
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.net.URL
import java.nio.ByteBuffer
class KaraokePlayerActivity : WokaBaseActivity() {
@@ -39,7 +58,7 @@ class KaraokePlayerActivity : WokaBaseActivity() {
const val EXTRA_KARAOKE_DATA = "extra_karaoke_data"
}
enum class RecordingState{
enum class RecordingState {
RECORDING, NOT_RECORDING,
PLAYING_AUDIO;
}
@@ -57,13 +76,20 @@ class KaraokePlayerActivity : WokaBaseActivity() {
// audio recorder
private var recorder: MediaRecorder? = null
private lateinit var recordingState: RecordingState
private var recordingOutputPath: String = ""
private var karaokeMusicPath: String = ""
private var karaokeFinalPath: String = ""
private var startRecordingPos: Long? = null
// audio player
private var audioPlayer: MediaPlayer? = null
private lateinit var permissionLauncher: ActivityResultLauncher<String>
private lateinit var progressView: ProgressView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
@@ -81,14 +107,17 @@ 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(){
networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
runOnUiThread {
if (playbackState == PlayBackState.STOPPED){
binding.playerView.show()
if (playbackState == PlayBackState.STOPPED) {
binding.mainView.show()
binding.errorView.hide()
playVideo()
}
@@ -100,11 +129,13 @@ class KaraokePlayerActivity : WokaBaseActivity() {
binding.playerView.player = player
recordingState = RecordingState.NOT_RECORDING
recordingOutputPath = "${externalCacheDir?.absolutePath}/karaoke.3gp"
recordingOutputPath = "${externalCacheDir?.absolutePath}/karaoke_recording.3gp"
karaokeMusicPath = "${externalCacheDir?.absolutePath}/karaoke_music"
karaokeFinalPath = "${externalCacheDir?.absolutePath}/karaoke_final_output"
addListeners()
playVideo()
updateRecordingUI()
initViews()
@@ -112,18 +143,20 @@ class KaraokePlayerActivity : WokaBaseActivity() {
registerLaunchers()
loadAudioFromUrl()
}
override fun onStart() {
super.onStart()
if (player?.isPlaying == false){
if (player?.isPlaying == false) {
player?.play()
}
}
override fun onStop() {
super.onStop()
if (player?.isPlaying == true){
if (player?.isPlaying == true) {
player?.pause()
}
}
@@ -145,20 +178,20 @@ class KaraokePlayerActivity : WokaBaseActivity() {
)
}
private fun initViews(){
private fun initViews() {
binding.apply {
playerView.findViewById<TextView>(R.id.player_controller_title).text = karaokePlayerData?.title
title.text = karaokePlayerData?.title
}
}
private fun clickEvents(){
private fun clickEvents() {
binding.apply {
playerView.findViewById<ImageView>(R.id.player_controller_close_btn).setOnClickListener {
backBtn.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
retryBtn.setOnClickListener {
binding.playerView.show()
binding.mainView.show()
binding.errorView.hide()
playVideo()
}
@@ -177,76 +210,106 @@ class KaraokePlayerActivity : WokaBaseActivity() {
}
}
private fun registerLaunchers(){
private fun registerLaunchers() {
permissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
startStopRecording()
}else{
} else {
toast(getString(R.string.permission_deniedd))
}
}
}
private fun playStopPlayingAudio(){
when (recordingState) {
RecordingState.NOT_RECORDING -> {
if (audioPlayer != null){
audioPlayer?.release()
audioPlayer = null
private fun updateRecordingUI() {
binding.apply {
when (recordingState) {
RecordingState.RECORDING -> {
recordBtn.text = getString(R.string.stop_recording)
recordBtn.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_rec_mic_cross,
0,
0,
0
)
playerView.useController = false
audioBtn.isEnabled = false
audioBtn.text = getString(R.string.play)
audioBtn.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_play, 0, 0, 0)
downloadBtn.isEnabled = false
}
audioPlayer = MediaPlayer().apply {
try {
setDataSource(this@KaraokePlayerActivity, Uri.parse(recordingOutputPath))
prepare()
start()
RecordingState.NOT_RECORDING -> {
recordBtn.text = getString(R.string.stop_recording)
recordBtn.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_rec_mic,
0,
0,
0
)
recordingState = RecordingState.PLAYING_AUDIO
playerView.useController = true
setOnCompletionListener {
audioPlayer?.release()
audioPlayer = null
audioBtn.isEnabled = true
audioBtn.text = getString(R.string.play)
audioBtn.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_play, 0, 0, 0)
recordingState = RecordingState.NOT_RECORDING
}
}catch (e: Exception) {
Log.d(TAG, "setupAudioPlayer: $e")
toast(getString(R.string.something_went_wrong))
downloadBtn.isEnabled = true
}
RecordingState.PLAYING_AUDIO -> {
recordBtn.text = getString(R.string.stop_recording)
recordBtn.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_rec_mic,
0,
0,
0
)
playerView.useController = true
audioBtn.isEnabled = true
if (audioPlayer?.isPlaying == true) {
audioBtn.text = getString(R.string.pause)
audioBtn.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_pause,
0,
0,
0
)
} else {
audioBtn.text = getString(R.string.play)
audioBtn.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_play,
0,
0,
0
)
}
}
}
RecordingState.RECORDING -> {
toast(getString(R.string.recording_audio))
}
RecordingState.PLAYING_AUDIO -> {
if (audioPlayer?.isPlaying == true){
audioPlayer?.pause()
}else{
audioPlayer?.start()
downloadBtn.isEnabled = true
}
}
}
}
private fun playVideo() {
if (karaokePlayerData?.karaokeVideoUrl == null) return
player?.setMediaItem(MediaItem.fromUri(karaokePlayerData?.karaokeVideoUrl!!))
player?.prepare()
player?.play()
}
private fun addListeners(){
player?.addListener(object : Player.Listener{
private fun addListeners() {
player?.addListener(object : Player.Listener {
override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying)
playbackState = if (isPlaying){
binding.playerView.show()
playbackState = if (isPlaying) {
binding.mainView.show()
binding.errorView.hide()
if (audioPlayer?.isPlaying == true) {
audioPlayer?.pause()
updateRecordingUI()
}
PlayBackState.PLAY
}else{
} else {
PlayBackState.PAUSED
}
}
@@ -254,7 +317,7 @@ class KaraokePlayerActivity : WokaBaseActivity() {
override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error)
playbackState = PlayBackState.STOPPED
binding.playerView.hide()
binding.mainView.hide()
binding.errorView.show()
}
})
@@ -266,6 +329,108 @@ class KaraokePlayerActivity : WokaBaseActivity() {
)
}
private fun loadAudioFromUrl() {
if (karaokePlayerData?.karaokeVideoUrl == null) {
progressView.hide()
toast(getString(R.string.canoot_load_karaoke))
playVideo()
return
}
CoroutineScope(Dispatchers.IO).launch {
try {
val url = URL(karaokePlayerData?.karaokeVideoUrl)
copyStreamToFile(url.openConnection().getInputStream(), File(karaokeMusicPath))
runOnUiThread {
binding.recorderView.show()
}
} catch (e: Exception) {
runOnUiThread {
toast(getString(R.string.canoot_load_karaoke))
binding.recorderView.hide()
}
} finally {
runOnUiThread {
playVideo()
progressView.hide()
}
}
}
}
private fun playStopPlayingAudio() {
when (recordingState) {
RecordingState.NOT_RECORDING -> {
if (audioPlayer != null) {
audioPlayer?.release()
audioPlayer = null
}
audioPlayer = MediaPlayer().apply {
try {
setDataSource(this@KaraokePlayerActivity, Uri.parse(karaokeFinalPath))
prepare()
setOnCompletionListener {
audioPlayer?.release()
audioPlayer = null
recordingState = RecordingState.NOT_RECORDING
updateRecordingUI()
}
} catch (e: Exception) {
toast(getString(R.string.something_went_wrong))
}
}
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 -> {
toast(getString(R.string.recording_audio))
updateRecordingUI()
}
RecordingState.PLAYING_AUDIO -> {
if (audioPlayer?.isPlaying == true) {
audioPlayer?.pause()
updateRecordingUI()
} else {
if (player?.isPlaying == true) {
player?.pause()
}
audioPlayer?.start()
updateRecordingUI()
}
}
}
}
private fun playVideo() {
if (karaokePlayerData?.karaokeVideoUrl == null) return
binding.playerView.show()
player?.setMediaItem(MediaItem.fromUri(karaokePlayerData?.karaokeVideoUrl!!))
player?.prepare()
player?.play()
}
@OptIn(UnstableApi::class)
private fun startStopRecording() {
if (ActivityCompat.checkSelfPermission(
this,
@@ -276,34 +441,131 @@ class KaraokePlayerActivity : WokaBaseActivity() {
return
}
if (recordingState == RecordingState.NOT_RECORDING){
recorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
setOutputFile(recordingOutputPath)
setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
when (recordingState) {
RecordingState.NOT_RECORDING -> {
startRecording()
}
RecordingState.RECORDING -> {
try {
prepare()
start()
toast(getString(R.string.recording_started))
recordingState = RecordingState.RECORDING
stopRecording()
} catch (e: Exception) {
toast(getString(R.string.something_went_wrong))
// do nothing
} finally {
if (player?.isPlaying == true) player?.pause()
updateRecordingUI()
binding.playerView.showController()
}
}
}else if (recordingState == RecordingState.RECORDING){
stopRecording()
RecordingState.PLAYING_AUDIO -> {
startRecording()
}
}
}
private fun startRecording() {
recorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
setOutputFile(recordingOutputPath)
setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
try {
prepare()
if (audioPlayer?.isPlaying == true) {
audioPlayer?.pause()
}
player?.play()
startRecordingPos = player?.currentPosition
start()
toast(getString(R.string.recording_started))
recordingState = RecordingState.RECORDING
updateRecordingUI()
} catch (e: Exception) {
toast(getString(R.string.something_went_wrong))
}
}
}
private fun stopRecording() {
recorder?.apply {
stop()
release()
recordingState = RecordingState.NOT_RECORDING
toast(getString(R.string.recording_stopped))
release()
mixAudios()
}
recorder = null
}
private fun mixAudios() {
try {
if (startRecordingPos != null){
val input1 = GeneralAudioInput(karaokeMusicPath)
input1.startTimeUs = startRecordingPos!! * 1000
input1.startOffsetUs = 100_000
getAudioDuration(recordingOutputPath)?.let {
input1.endTimeUs = input1.startTimeUs + (it * 1000)
}
val input2 = GeneralAudioInput(recordingOutputPath)
val audioMixer = AudioMixer(karaokeFinalPath)
audioMixer.addDataSource(input1)
audioMixer.addDataSource(input2)
audioMixer.isLoopingEnabled = false
audioMixer.mixingType = AudioMixer.MixingType.PARALLEL
audioMixer.setProcessingListener(object : AudioMixer.ProcessingListener{
override fun onProgress(progress: Double) {
runOnUiThread {
Log.d("aditya_test", "onProgress: ${(progress * 100).toInt()}")
}
}
override fun onEnd() {
runOnUiThread {
Log.d(TAG, "onEnd: ")
audioMixer.release()
}
}
})
audioMixer.start()
audioMixer.processAsync()
}else{
throw Exception()
}
} catch (e: Exception) {
toast(getString(R.string.karaoke_process_failed))
}
}
private fun getAudioDuration(audio: String):Long? {
val mmr = MediaMetadataRetriever()
mmr.setDataSource(this, Uri.parse(audio))
val durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
return durationStr?.toLongOrNull()
}
private fun copyStreamToFile(inputStream: InputStream, outputFile: File) {
inputStream.use { input ->
val outputStream = FileOutputStream(outputFile)
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()
}
}
}
}

View File

@@ -1,5 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="31" android:viewportWidth="31" android:width="24dp">
<path android:fillColor="#ffffff" android:pathData="M15.5,0C11.731,0 8.68,2.967 8.68,6.646V6.82H13.33C13.671,6.82 13.95,7.099 13.95,7.44C13.95,7.781 13.671,8.06 13.33,8.06H8.68V9.3H13.33C13.671,9.3 13.95,9.579 13.95,9.92C13.95,10.262 13.671,10.54 13.33,10.54H8.68V11.78H13.33C13.671,11.78 13.95,12.059 13.95,12.4C13.95,12.741 13.671,13.02 13.33,13.02H8.68V14.434C8.68,18.113 11.731,21.08 15.5,21.08C19.268,21.08 22.32,18.113 22.32,14.434V13.02H17.67C17.326,13.02 17.05,12.741 17.05,12.4C17.05,12.059 17.326,11.78 17.67,11.78H22.32V10.54H17.67C17.326,10.54 17.05,10.262 17.05,9.92C17.05,9.579 17.326,9.3 17.67,9.3H22.32V8.06H17.67C17.326,8.06 17.05,7.781 17.05,7.44C17.05,7.099 17.326,6.82 17.67,6.82H22.32V6.646C22.32,2.967 19.268,0 15.5,0ZM6.084,10.618C5.829,10.671 5.65,10.901 5.657,11.16V14.26C5.657,19.043 9.106,23.054 13.64,23.928V27.28H17.36V23.928C21.894,23.054 25.342,19.043 25.342,14.26V11.16C25.342,10.86 25.1,10.618 24.8,10.618C24.5,10.618 24.257,10.86 24.257,11.16V14.26C24.257,19.097 20.336,23.017 15.5,23.017C10.663,23.017 6.742,19.097 6.742,14.26V11.16C6.745,11.005 6.679,10.857 6.566,10.753C6.449,10.649 6.297,10.598 6.142,10.618C6.122,10.618 6.103,10.618 6.084,10.618ZM9.61,28.52C8.423,28.52 7.459,29.489 7.459,30.671L7.44,31L23.482,30.961L23.502,30.671C23.502,29.489 22.538,28.52 21.351,28.52H9.61Z"/>
</vector>

View File

@@ -1,7 +1,14 @@
<vector android:height="44dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="44dp" xmlns:android="http://schemas.android.com/apk/res/android">
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<clip-path android:fillType="evenOdd" android:pathData="M10.0038,19.5015C10.0195,19.7603 9.8234,19.9839 9.5607,20.0035C9.5411,20.0035 9.5215,20.0035 9.5019,20.0035H6.502C6.2432,20.0191 6.0196,19.8231 6,19.5603C6,19.5407 6,19.5211 6,19.5015V4.502C5.9843,4.2432 6.1804,4.0196 6.4432,4C6.4628,4 6.4824,4 6.502,4H9.5019C9.7607,3.9843 9.9842,4.1804 10.0038,4.4432C10.0038,4.4628 10.0038,4.4824 10.0038,4.502V19.5015ZM18.0036,4.5059C18.0193,4.2471 17.8232,4.0236 17.5605,4.004C17.5408,4.004 17.5212,4.004 17.5016,4.004H14.5017C14.2429,3.9883 14.0194,4.1843 13.9998,4.4471C13.9998,4.4667 13.9998,4.4863 13.9998,4.5059V19.5054C13.9841,19.7642 14.1802,19.9878 14.4429,20.0074C14.4625,20.0074 14.4821,20.0074 14.5017,20.0074H17.5016C17.7604,20.0231 17.984,19.827 18.0036,19.5643C18.0036,19.5446 18.0036,19.525 18.0036,19.5054V4.5059Z"/>
<path android:fillColor="@color/jw_icons_active" android:pathData="M0,0h24v24h-24z"/>
<clip-path
android:fillType="evenOdd"
android:pathData="M10.0038,19.5015C10.0195,19.7603 9.8234,19.9839 9.5607,20.0035C9.5411,20.0035 9.5215,20.0035 9.5019,20.0035H6.502C6.2432,20.0191 6.0196,19.8231 6,19.5603C6,19.5407 6,19.5211 6,19.5015V4.502C5.9843,4.2432 6.1804,4.0196 6.4432,4C6.4628,4 6.4824,4 6.502,4H9.5019C9.7607,3.9843 9.9842,4.1804 10.0038,4.4432C10.0038,4.4628 10.0038,4.4824 10.0038,4.502V19.5015ZM18.0036,4.5059C18.0193,4.2471 17.8232,4.0236 17.5605,4.004C17.5408,4.004 17.5212,4.004 17.5016,4.004H14.5017C14.2429,3.9883 14.0194,4.1843 13.9998,4.4471C13.9998,4.4667 13.9998,4.4863 13.9998,4.5059V19.5054C13.9841,19.7642 14.1802,19.9878 14.4429,20.0074C14.4625,20.0074 14.4821,20.0074 14.5017,20.0074H17.5016C17.7604,20.0231 17.984,19.827 18.0036,19.5643C18.0036,19.5446 18.0036,19.525 18.0036,19.5054V4.5059Z" />
<path
android:fillColor="@color/jw_icons_active"
android:pathData="M0,0h24v24h-24z" />
</group>
</vector>

View File

@@ -1,7 +1,14 @@
<vector android:height="44dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="44dp" xmlns:android="http://schemas.android.com/apk/res/android">
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<clip-path android:fillType="evenOdd" android:pathData="M6.364,19.955C6.265,20.0316 6.1245,20.0125 6.0479,19.9135C6.0095,19.8624 5.9936,19.7985 6.0031,19.7378V4.2651C5.9808,4.1437 6.0638,4.0256 6.1852,4.0032C6.2491,3.9936 6.3129,4.0096 6.3608,4.0479L21.1118,11.8114C21.3098,11.9168 21.3098,12.0861 21.1118,12.1915L6.364,19.955Z"/>
<path android:fillColor="@color/jw_icons_active" android:pathData="M0,0h24v24h-24z"/>
<clip-path
android:fillType="evenOdd"
android:pathData="M6.364,19.955C6.265,20.0316 6.1245,20.0125 6.0479,19.9135C6.0095,19.8624 5.9936,19.7985 6.0031,19.7378V4.2651C5.9808,4.1437 6.0638,4.0256 6.1852,4.0032C6.2491,3.9936 6.3129,4.0096 6.3608,4.0479L21.1118,11.8114C21.3098,11.9168 21.3098,12.0861 21.1118,12.1915L6.364,19.955Z" />
<path
android:fillColor="@color/jw_icons_active"
android:pathData="M0,0h24v24h-24z" />
</group>
</vector>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="20dp"
android:viewportWidth="352"
android:viewportHeight="512">
<path
android:fillColor="#FFFFFF"
android:pathData="M176,352c53,0 96,-43 96,-96L272,96c0,-53 -43,-96 -96,-96S80,43 80,96v160c0,53 43,96 96,96zM336,192h-16c-8.8,0 -16,7.2 -16,16v48c0,74.8 -64.5,134.8 -140.8,127.4C96.7,376.9 48,317.1 48,250.3L48,208c0,-8.8 -7.2,-16 -16,-16L16,192c-8.8,0 -16,7.2 -16,16v40.2c0,89.6 64,169.6 152,181.7L152,464L96,464c-8.8,0 -16,7.2 -16,16v16c0,8.8 7.2,16 16,16h160c8.8,0 16,-7.2 16,-16v-16c0,-8.8 -7.2,-16 -16,-16h-56v-33.8C285.7,418.5 352,344.9 352,256v-48c0,-8.8 -7.2,-16 -16,-16z" />
</vector>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="640"
android:viewportHeight="512">
<path
android:fillColor="#ffffff"
android:pathData="M38.8,5.1C28.4,-3.1 13.3,-1.2 5.1,9.2S-1.2,34.7 9.2,42.9l592,464c10.4,8.2 25.5,6.3 33.7,-4.1s6.3,-25.5 -4.1,-33.7L472.1,344.7c15.2,-26 23.9,-56.3 23.9,-88.7L496,216c0,-13.3 -10.7,-24 -24,-24s-24,10.7 -24,24v40c0,21.2 -5.1,41.1 -14.2,58.7L416,300.8L416,96c0,-53 -43,-96 -96,-96s-96,43 -96,96v54.3L38.8,5.1zM401.3,412.1l-43.1,-33.9C346.1,382 333.3,384 320,384c-70.7,0 -128,-57.3 -128,-128v-8.7L144.7,210c-0.5,1.9 -0.7,3.9 -0.7,6v40c0,89.1 66.2,162.7 152,174.4L296,464L248,464c-13.3,0 -24,10.7 -24,24s10.7,24 24,24h72,72c13.3,0 24,-10.7 24,-24s-10.7,-24 -24,-24L344,464L344,430.4c20.4,-2.8 39.7,-9.1 57.3,-18.2z" />
</vector>

View File

@@ -12,24 +12,79 @@
android:id="@+id/main_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<!-- custom vie-->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/back_btn"
android:layout_width="25dp"
android:layout_height="25dp"
android:contentDescription="@string/image"
android:src="@drawable/ic_close"
android:layout_margin="25dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:padding="5dp"
style="@style/ExoStyledControls.Button.Center.PlayPause"
/>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_14ssp"
android:textAlignment="center"
android:ems="10"
android:maxLines="1"
android:ellipsize="end"
android:layout_gravity="center_horizontal"
android:layout_margin="15dp"
app:layout_constraintTop_toTopOf="@id/back_btn"
app:layout_constraintBottom_toBottomOf="@id/back_btn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/ExoStyledControls.Button.Center.PlayPause"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.media3.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
app:controller_layout_id="@layout/exo_player_control_view"
android:layout_centerInParent="true"
android:visibility="gone"
/>
<LinearLayout
android:id="@+id/recorder_view"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_marginHorizontal="15dp"
android:layout_marginVertical="@dimen/_100sdp"
android:layout_alignParentBottom="true">
android:layout_below="@id/player_view"
android:layout_marginTop="60dp"
>
<LinearLayout
android:layout_width="match_parent"
@@ -52,7 +107,7 @@
android:textAllCaps="false"
android:tooltipText="@string/start_recording"
android:drawableStart="@drawable/ic_mic"
android:drawableStart="@drawable/ic_rec_mic"
android:drawablePadding="15dp"
android:paddingStart="15dp"
android:paddingEnd="25dp"
@@ -71,16 +126,16 @@
android:layout_width="wrap_content"
android:layout_height="38dp"
android:text="@string/play_recording"
android:text="@string/play"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="11sp"
android:textAllCaps="false"
android:maxLines="1"
android:ellipsize="end"
android:tooltipText="@string/play_recording"
android:tooltipText="@string/play"
android:drawableStart="@drawable/ic_play_filled"
android:drawableStart="@drawable/ic_play"
android:drawablePadding="15dp"
android:paddingStart="15dp"
android:paddingEnd="25dp"
@@ -97,6 +152,7 @@
</LinearLayout>
<TextView
android:id="@+id/download_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@@ -28,10 +28,10 @@
<FrameLayout android:id="@id/exo_bottom_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/exo_styled_bottom_bar_height"
android:layout_height="40dp"
android:layout_marginTop="@dimen/exo_styled_bottom_bar_margin_top"
android:layout_gravity="bottom"
android:background="@color/exo_bottom_bar_background"
android:background="@color/black_50"
android:layoutDirection="ltr">
<LinearLayout android:id="@id/exo_time"
@@ -102,7 +102,7 @@
android:layout_width="match_parent"
android:layout_height="@dimen/exo_styled_progress_layout_height"
android:layout_gravity="bottom"
android:layout_marginBottom="@dimen/exo_styled_progress_margin_bottom"/>
android:layout_marginBottom="32dp"/>
<LinearLayout android:id="@id/exo_minimal_controls"
android:layout_width="wrap_content"
@@ -138,55 +138,4 @@
</LinearLayout>
<!-- custom vie-->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/player_controller_close_btn"
android:layout_width="25dp"
android:layout_height="25dp"
android:contentDescription="@string/image"
android:src="@drawable/ic_close"
android:layout_margin="25dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:padding="5dp"
style="@style/ExoStyledControls.Button.Center.PlayPause"
/>
<TextView
android:id="@+id/player_controller_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_14ssp"
android:textAlignment="center"
android:ems="10"
android:maxLines="1"
android:ellipsize="end"
android:layout_gravity="center_horizontal"
android:layout_margin="15dp"
app:layout_constraintTop_toTopOf="@id/player_controller_close_btn"
app:layout_constraintBottom_toBottomOf="@id/player_controller_close_btn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/ExoStyledControls.Button.Center.PlayPause"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</merge>

View File

@@ -204,10 +204,13 @@
<string name="sing_now">SING NOW</string>
<string name="start_recording">Start Recording</string>
<string name="download_recording"><u>Download Recording</u></string>
<string name="play_recording">Play Recording</string>
<string name="permission_deniedd">Permission Denied.</string>
<string name="recording_started">Recording Started</string>
<string name="recording_stopped">Recording Stopped.</string>
<string name="recording_audio">Recording</string>
<string name="no_recordings_found">No recording found</string>
<string name="stop_recording">Stop Recording</string>
<string name="pause">PAUSE</string>
<string name="canoot_load_karaoke">Cannot load karaoke</string>
<string name="karaoke_process_failed">Karaoke process failed.</string>
</resources>