KaraokePlayerActivity:
Ui creation Audio recording implementation Recorded audio implementation
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user