KaraokePlayerActivity:

Ui creation

Audio recording implementation
Recorded audio implementation
This commit is contained in:
2024-07-10 21:00:36 +05:30
parent 64626f7730
commit fbb253d612
6 changed files with 342 additions and 5 deletions

View File

@@ -108,6 +108,9 @@ dependencies {
// For building media playback UIs
implementation "androidx.media3:media3-ui:$media3_version"
// audio mixer for karaoke
implementation("com.github.ZeroOneZeroR:android_audio_mixer:v1.1")
implementation libs.androidx.core.ktx
implementation libs.androidx.appcompat
implementation libs.material

View File

@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<application
android:name=".WokaApp"

View File

@@ -1,17 +1,37 @@
package com.woka.karaoke.player
import android.Manifest
import android.content.pm.PackageManager
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.Bundle
import android.util.Log
import android.view.WindowManager
import android.widget.ImageView
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import com.woka.R
import com.woka.databinding.ActivityKaraokePlayerrBinding
import com.woka.players.models.PlayBackState
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
class KaraokePlayerActivity : WokaBaseActivity() {
@@ -19,11 +39,30 @@ class KaraokePlayerActivity : WokaBaseActivity() {
const val EXTRA_KARAOKE_DATA = "extra_karaoke_data"
}
enum class RecordingState{
RECORDING, NOT_RECORDING,
PLAYING_AUDIO;
}
private lateinit var binding: ActivityKaraokePlayerrBinding
private var karaokePlayerData: KaraokePlayerData? = null
// video player
private var player: ExoPlayer? = null
private var playbackState: PlayBackState? = null
private lateinit var networkCallback: ConnectivityManager.NetworkCallback
// audio recorder
private var recorder: MediaRecorder? = null
private lateinit var recordingState: RecordingState
private var recordingOutputPath: String = ""
// audio player
private var audioPlayer: MediaPlayer? = null
private lateinit var permissionLauncher: ActivityResultLauncher<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -36,27 +75,74 @@ class KaraokePlayerActivity : WokaBaseActivity() {
insets
}
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
val windowInsetsController =
WindowCompat.getInsetsController(window, window.decorView)
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
karaokePlayerData = intent.getParcelableExtra(EXTRA_KARAOKE_DATA)
networkCallback = object : ConnectivityManager.NetworkCallback(){
override fun onAvailable(network: Network) {
super.onAvailable(network)
runOnUiThread {
if (playbackState == PlayBackState.STOPPED){
binding.playerView.show()
binding.errorView.hide()
playVideo()
}
}
}
}
player = ExoPlayer.Builder(this).build()
binding.playerView.player = player
recordingState = RecordingState.NOT_RECORDING
recordingOutputPath = "${externalCacheDir?.absolutePath}/karaoke.3gp"
addListeners()
playVideo()
initViews()
clickEvents()
registerLaunchers()
}
override fun onStart() {
super.onStart()
if (player?.isPlaying == false){
player?.play()
}
}
override fun onStop() {
super.onStop()
if (player?.isPlaying == true){
player?.pause()
}
}
override fun onDestroy() {
super.onDestroy()
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
player?.stop()
player?.release()
recorder?.release()
audioPlayer?.release()
audioPlayer = null
(getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager).unregisterNetworkCallback(
networkCallback
)
}
private fun initViews(){
@@ -70,6 +156,77 @@ class KaraokePlayerActivity : WokaBaseActivity() {
playerView.findViewById<ImageView>(R.id.player_controller_close_btn).setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
retryBtn.setOnClickListener {
binding.playerView.show()
binding.errorView.hide()
playVideo()
}
closeBtn.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
recordBtn.setOnClickListener {
startStopRecording()
}
audioBtn.setOnClickListener {
playStopPlayingAudio()
}
}
}
private fun registerLaunchers(){
permissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
startStopRecording()
}else{
toast(getString(R.string.permission_deniedd))
}
}
}
private fun playStopPlayingAudio(){
when (recordingState) {
RecordingState.NOT_RECORDING -> {
if (audioPlayer != null){
audioPlayer?.release()
audioPlayer = null
}
audioPlayer = MediaPlayer().apply {
try {
setDataSource(this@KaraokePlayerActivity, Uri.parse(recordingOutputPath))
prepare()
start()
recordingState = RecordingState.PLAYING_AUDIO
setOnCompletionListener {
audioPlayer?.release()
audioPlayer = null
recordingState = RecordingState.NOT_RECORDING
}
}catch (e: Exception) {
Log.d(TAG, "setupAudioPlayer: $e")
toast(getString(R.string.something_went_wrong))
}
}
}
RecordingState.RECORDING -> {
toast(getString(R.string.recording_audio))
}
RecordingState.PLAYING_AUDIO -> {
if (audioPlayer?.isPlaying == true){
audioPlayer?.pause()
}else{
audioPlayer?.start()
}
}
}
}
@@ -81,4 +238,72 @@ class KaraokePlayerActivity : WokaBaseActivity() {
player?.play()
}
private fun addListeners(){
player?.addListener(object : Player.Listener{
override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying)
playbackState = if (isPlaying){
binding.playerView.show()
binding.errorView.hide()
PlayBackState.PLAY
}else{
PlayBackState.PAUSED
}
}
override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error)
playbackState = PlayBackState.STOPPED
binding.playerView.hide()
binding.errorView.show()
}
})
(getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager).registerNetworkCallback(
NetworkRequest.Builder()
.build(),
networkCallback
)
}
private fun startStopRecording() {
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.RECORD_AUDIO
) != PackageManager.PERMISSION_GRANTED
) {
permissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
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)
try {
prepare()
start()
toast(getString(R.string.recording_started))
recordingState = RecordingState.RECORDING
} catch (e: Exception) {
toast(getString(R.string.something_went_wrong))
}
}
}else if (recordingState == RecordingState.RECORDING){
stopRecording()
}
}
private fun stopRecording() {
recorder?.apply {
stop()
release()
recordingState = RecordingState.NOT_RECORDING
toast(getString(R.string.recording_stopped))
}
recorder = null
}
}

View File

@@ -7,13 +7,110 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".karaoke.player.KaraokePlayerActivity">
<androidx.media3.ui.PlayerView
android:id="@+id/player_view"
<RelativeLayout
android:id="@+id/main_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:controller_layout_id="@layout/exo_player_control_view"
/>
>
<androidx.media3.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:controller_layout_id="@layout/exo_player_control_view"
/>
<LinearLayout
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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_horizontal"
>
<Button
android:id="@+id/record_btn"
android:layout_width="wrap_content"
android:layout_height="38dp"
android:text="@string/start_recording"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="11sp"
android:maxLines="1"
android:ellipsize="end"
android:textAllCaps="false"
android:tooltipText="@string/start_recording"
android:drawableStart="@drawable/ic_mic"
android:drawablePadding="15dp"
android:paddingStart="15dp"
android:paddingEnd="25dp"
android:paddingVertical="5dp"
android:layout_marginVertical="10dp"
android:layout_marginEnd="5dp"
android:background="@drawable/round_25"
android:backgroundTint="@android:color/holo_red_dark"
tools:targetApi="o" />
<Button
android:id="@+id/audio_btn"
android:layout_width="wrap_content"
android:layout_height="38dp"
android:text="@string/play_recording"
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:drawableStart="@drawable/ic_play_filled"
android:drawablePadding="15dp"
android:paddingStart="15dp"
android:paddingEnd="25dp"
android:paddingVertical="5dp"
android:layout_marginVertical="10dp"
android:layout_marginStart="@dimen/_5sdp"
android:background="@drawable/round_25"
android:backgroundTint="@android:color/holo_blue_dark"
tools:targetApi="o" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/download_recording"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="16sp"
android:layout_marginTop="10dp"
/>
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/error_view"

View File

@@ -202,4 +202,12 @@
<string name="sing_again">SING AGAIN</string>
<string name="sing">SING</string>
<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>
</resources>