implementing karaoke
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -51,6 +51,9 @@ object KaraokeRepository {
|
||||
val response = handleApiCall {
|
||||
apiService.karaokeListing(
|
||||
FormBody.Builder()
|
||||
.add("api_version", "v2")
|
||||
.add("start", "0")
|
||||
.add("limit", "10")
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
11
app/src/main/res/drawable/ic_rec_mic.xml
Normal file
11
app/src/main/res/drawable/ic_rec_mic.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_rec_mic_cross.xml
Normal file
11
app/src/main/res/drawable/ic_rec_mic_cross.xml
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user