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 // For building media playback UIs
implementation "androidx.media3:media3-ui:$media3_version" 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.core.ktx
implementation libs.androidx.appcompat implementation libs.androidx.appcompat
implementation libs.material implementation libs.material

View File

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

View File

@@ -1,17 +1,37 @@
package com.woka.karaoke.player 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.os.Bundle
import android.util.Log
import android.view.WindowManager
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.activity.enableEdgeToEdge 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.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import com.woka.R import com.woka.R
import com.woka.databinding.ActivityKaraokePlayerrBinding 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.WokaBaseActivity
import com.woka.utils.hide
import com.woka.utils.show
import com.woka.utils.toast
class KaraokePlayerActivity : WokaBaseActivity() { class KaraokePlayerActivity : WokaBaseActivity() {
@@ -19,11 +39,30 @@ class KaraokePlayerActivity : WokaBaseActivity() {
const val EXTRA_KARAOKE_DATA = "extra_karaoke_data" const val EXTRA_KARAOKE_DATA = "extra_karaoke_data"
} }
enum class RecordingState{
RECORDING, NOT_RECORDING,
PLAYING_AUDIO;
}
private lateinit var binding: ActivityKaraokePlayerrBinding private lateinit var binding: ActivityKaraokePlayerrBinding
private var karaokePlayerData: KaraokePlayerData? = null private var karaokePlayerData: KaraokePlayerData? = null
// video player
private var player: ExoPlayer? = null 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -36,27 +75,74 @@ class KaraokePlayerActivity : WokaBaseActivity() {
insets insets
} }
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
val windowInsetsController = val windowInsetsController =
WindowCompat.getInsetsController(window, window.decorView) WindowCompat.getInsetsController(window, window.decorView)
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
karaokePlayerData = intent.getParcelableExtra(EXTRA_KARAOKE_DATA) 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() player = ExoPlayer.Builder(this).build()
binding.playerView.player = player binding.playerView.player = player
recordingState = RecordingState.NOT_RECORDING
recordingOutputPath = "${externalCacheDir?.absolutePath}/karaoke.3gp"
addListeners()
playVideo() playVideo()
initViews() initViews()
clickEvents() 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() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
player?.stop() player?.stop()
player?.release() player?.release()
recorder?.release()
audioPlayer?.release()
audioPlayer = null
(getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager).unregisterNetworkCallback(
networkCallback
)
} }
private fun initViews(){ private fun initViews(){
@@ -70,6 +156,77 @@ class KaraokePlayerActivity : WokaBaseActivity() {
playerView.findViewById<ImageView>(R.id.player_controller_close_btn).setOnClickListener { playerView.findViewById<ImageView>(R.id.player_controller_close_btn).setOnClickListener {
onBackPressedDispatcher.onBackPressed() 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() 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

@@ -8,12 +8,109 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".karaoke.player.KaraokePlayerActivity"> tools:context=".karaoke.player.KaraokePlayerActivity">
<androidx.media3.ui.PlayerView <RelativeLayout
android:id="@+id/player_view" android:id="@+id/main_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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 <RelativeLayout
android:id="@+id/error_view" android:id="@+id/error_view"

View File

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

View File

@@ -19,6 +19,9 @@ dependencyResolutionManagement {
maven { maven {
url 'https://mvn.jwplayer.com/content/repositories/releases/' url 'https://mvn.jwplayer.com/content/repositories/releases/'
} }
maven{
url = uri("https://jitpack.io")
}
} }
} }