How can we implement a chat like mechanism with audio recorder and a player [closed] - android

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 days ago.
Improve this question
I want to create a whatsapp like chat interface where I can record audio, send them and play them. My question is how should be my approach implementing it, should all the messages have their own instance of media player or a single media player and all the chat items on a queue, or something else. Really confused about it.
Want to use in Jetpack Compose, and every message composable has an instance of AudioPlayerHandler attached to it.
Currently I have a wrapper class AudioPlayerHandler around the Mediaplayer, that has just two functions : play() and stop(). And I have the single instance of mediaplayer to all the Handlers. So when one instance of Handler calls mediaPlayer.seekTo(dur), mediaplayer reaches to stop, reset and the setDataSource() is called with the new file to play. But I'm not much convinced it's a good implementation and want to improve it. But still it works 😅.
class AudioPlayerHandler(
private val mediaPlayer: MediaPlayer,
private val scope: CoroutineScope
) {
private val _isPlaying = MutableStateFlow(false)
val isPlaying = _isPlaying.asStateFlow()
private val _currentPositionMillis = MutableStateFlow(0)
val currentPositionMillis = _currentPositionMillis.asStateFlow()
private val _durationMillis = MutableStateFlow(0)
val durationMillis = _durationMillis.asStateFlow()
private var publishProgressJob: Job? = null
fun play(mediaPath: String) {
scope.launch(Dispatchers.IO) {
if (mediaPlayer.isPlaying) {
val dur = mediaPlayer.duration
mediaPlayer.seekTo(dur)
}
mediaPlayer.reset()
mediaPlayer.setDataSource(mediaPath)
mediaPlayer.prepare()
mediaPlayer.start()
mediaPlayer.setOnCompletionListener { stopPlayer() }
_durationMillis.value = mediaPlayer.duration
_currentPositionMillis.value = 0
publishProgressJob = launch {
while (isActive && mediaPlayer.isPlaying) {
delay(1000)
_currentPositionMillis.value = mediaPlayer.currentPosition
}
}
_isPlaying.value = true
}
}
fun stopPlayer() {
Timber.d("Audio recorder stopping ${isPlaying.value}")
mediaPlayer.stop()
publishProgressJob?.cancel()
_currentPositionMillis.value = 0
_isPlaying.value = false
}
}

Related

Creating OnCompletionListener in Compose

I am a Java/Andriod programmer new to Kotlin and Jetpack Compose. I am creating a simple sound-board app with three buttons that will play a unique sound when pressed. All is going well, but I am struggling with creating the OnCompletionListener for the Mediaplayer instances (so I can release resources and change the button on the UI)
Within my button Composable I create the instance of the Mediaplayer
`val mediaPlayer:MediaPlayer by remember {
mutableStateOf(MediaPlayer.create(context,soundID))
}`
which works great in the OnClick of the Image composable:
Image (
painter = painterResource(id = (imageID)),
contentDescription = null,
contentScale = ContentScale.FillBounds,
modifier = Modifier
.size(250.dp)
.aspectRatio(16f / 9f)
.clip(RoundedCornerShape(cornerDiameter.dp))
.border(
BorderStroke(4.dp, Color.Yellow),
RoundedCornerShape(cornerDiameter.dp)
)
.clickable(
enabled = true,
onClick = {
if (isPlaying) {
println("STOPPING player")
mediaPlayer.pause()
isPlaying = false
} else {
println("starting player")
mediaPlayer.start()
isPlaying = true
}
}
)
)
When the respective audio is done playing I want to call a routine to clean up and update the UI. When I create the onCompletionLIstener I send it the instance of the MediaPlayer:
val onCompletionListener =
MediaPlayer.OnCompletionListener(trackDone(mediaPlayer))
mediaPlayer.setOnCompletionListener(onCompletionListener)
which expects the function trackDone to be (MediaPlayer!) → Unit, which it auto creates:
fun trackDone(mediaPlayer: MediaPlayer): (MediaPlayer) -> Unit {
if(mediaPlayer != null)
{
mediaPlayer!!.stop()
mediaPlayer!!.release()
}
}
However, I now get an error for trackDone saying "A 'return' expression required in a function with a block body ('{...}')". But I can't figure out what type of return I can provide to satisfy this. Returning mediaPlayer does not work.
Any help is appreciated. I hope I have given enough information.
The compiler expects trackDone to return a lambda that accepts a MediaPlayer and returns nothing. I'm not sure what you want to accomplish with this function signature but the code below will fix the compile error.
fun trackDone(mediaPlayer: MediaPlayer): (MediaPlayer) -> Unit {
if(mediaPlayer != null)
{
mediaPlayer!!.stop()
mediaPlayer!!.release()
}
return {}
}
I assume this is not exactly your intention and instead you want some callback from this function.
First, please clean it up, you don't need to check for nullability and you don't need to shout !! in your code, because the function already expects a non-nullable MediaPlayer argument.
Next, simply add an additional function type parameter that will be used as a callback inside trackDone.
Putting them all together, your trackDone function should look like this
fun trackDone(mediaPlayer: MediaPlayer, onComplete : (MediaPlayer) -> Unit) {
// remove unnecessary nullability checking
mediaPlayer.stop()
mediaPlayer.release()
// on complete callback
onComplete(mediaPlayer)
}
or if you don't want to return the MediaPlayer instance then this,
fun trackDone(mediaPlayer: MediaPlayer, onComplete : () -> Unit) {
// remove unnecessary nullability checking
mediaPlayer.stop()
mediaPlayer.release()
// on complete callback
onComplete()
}
and this is how it would be used 'ideally'.
fun someFunction() {
val mediaPlayer: MediaPlayer
...
...
...
trackDone(mediaPlayer) { stoppedMediaPlayer ->
// DONE
}
// or
trackDone(mediaPlayer) {
// DONE
}
}
Now I have no idea if this would "work" for a "completeListener" callback like you want to achieve, but the "fix" will surely compile.

Android Kotlin - ExoPlayer keeps playing when app goes to background

So far I only found questions about how to make ExoPlayer keep playing when app goes to background. Why the hell is that the case by me without coding this bs??
This is what I have so far and it's inside RecyclerView OnBingViewHolder:
val player = ExoPlayer.Builder(context).build()
val mediaItem: MediaItem = MediaItem.fromUri(fileUrl)
player.setMediaItem(mediaItem)
player.repeatMode = Player.REPEAT_MODE_ONE
holder.vidPlayer.player = player
player.prepare()
player.seekTo(100)
// player.play()
holder.vidPlayer.setTag(mpTag, player)
holder.vidPlayer.setTag(manuelPlayTag, false)
holder.vidPlayer.setTag(manuelPauseTag, false)
player.addListener(object : Player.Listener { // player listener
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
if (playWhenReady && playbackState == Player.STATE_READY) {
Log.d(tagg, "state: plays")
holder.vidPlayer.hideController()
} else if (playWhenReady) {
// might be idle (plays after prepare()),
// buffering (plays when data available)
// or ended (plays when seek away from end)
} else {
Log.d(tagg, "state: pause")
holder.vidPlayer.showController()
}
}
})
how I prevent the play when app goes to background?
When your app goes to the background the active Fragment/Activity's Lifecycle method onPause (and onStop) is called. In the onPause method you can cycle through your bound ViewHolders and stop the video player(s).
You can simply stop the ExoPlayer when the app goes to the background.
override fun onStop() {
super.onStop()
simpleExoPlayer.stop()
}
And in onStart just prepare() the ExoPlayer again:
override fun onStart() {
super.onStart()
simpleExoPlayer.prepare()
}
In order to automatically play the media, you need to set playWhenReady = true.
simpleExoPlayer.playWhenReady = true
By setting playWhenReady = true it will automcatically play the content and we don't need to explicitly call simpleExoPlayer.play().

How to put an audio before a stream connection in Android Studio (Kotlin)

I am new to programming and I am making a basic app for radio in which an introduction audio sounds when you press a button, then a second audio should appear until the network connection of the online radio is established.
I have managed to make the intro audio sound complete when I click, then silence is generated until the online radio plays, but I don't know how to put a second audio that detects the charging status before the radio plays. This is my code:
fun MediaPlayerRadio(){
mediaPlayer = MediaPlayer.create(
this#MainActivity,
Uri.parse("https://radiolink.com")
)
mediaPlayer?.start()
}
........................................................................
fun MediaPlayerIntroSound(){
mediaPlayer = MediaPlayer.create(this, R.raw.SoundIntro)
mediaPlayer?.start()
}
........................................................................
fun click_Button_Radio(){
btn.setOnClickListener(){
if (btn.isSelected){
btn.isSelected = false
mediaPlayer?.stop()
}else{
btn.isSelected = !btn.isSelected
MediaPlayerIntroSound()
mediaPlayer!!.setOnCompletionListener(object : MediaPlayer.OnCompletionListener {
override fun onCompletion(mp: MediaPlayer?) {
MediaPlayerRadio()
}
})
}
}
}
I hope you can support me with this.
For this, you will have to use 2 media players, one for intro sound and another for actual sound.
While the time actual sound media player gets ready, you can play different audios on the intro sound media player.
To stop the intro audio player once the other player is ready, you can make use of a view model.
//Play Media player
mediaPlayer = MediaPlayer.create(this#MainActivity, R.raw.first_audio)
mediaPlayer.start()
// Add completion listener, so that we can change the audio
// once the first one is finished
mediaPlayer.setOnCompletionListener {
mediaPlayer.release()
mediaPlayer = MediaPlayer.create(this#MainActivity, R.raw.second_audio)
mediaPlayer.start()
}
// We are observing a MutableLiveData of Boolean type,
// once it is true we will stop the intro media player
// and start the radio player
viewModel.isPlayerReady.observe(this) { isReady ->
if(isReady) {
mediaPlayer.stop()
// Start radio media player
}
}
// This is how I am changing the value of MutableLiveData
demoBtn.setOnClickListener {
viewModel.isPlayerReady.value = true
}
I have tested the above approach and it works fine as expected.
Steps to link a view model to the activity:
Add the dependency: implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
Make a view model class and a mutable live variable of type boolean in it.
class MainViewModel: ViewModel() {
val isPlayerReady = MutableLiveData<Boolean>()
}
Link view model with the activity.
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
....
}

Transition delay in exoplayer playlist

Working with playlist on the ExoPlayer, I managed the setting of the playlist by following the documentation.
val firstSource = ProgressiveMediaSource.Factory(...).createMediaSource(firstVideoUri);
val secondSource = ProgressiveMediaSource.Factory(...).createMediaSource(secondVideoUri);
val playlist = ConcatenatingMediaSource(firstSource, secondSource);
player.prepare(playlist)
But I noticed that the transition between video is instantaneous. However, I would like to add a waiting stage between each video, exactly like youtube does (let's say 5 sec). Something like:
Is it already handled by the Exoplayer itself or not? If not what is a clean way to solve the problem ?
You can use following code.
object : CountDownTimer(1500, 500) {
override fun onTick(millisUntilFinished: Long) {
//Update your Ui.
}
override fun onFinish() {
//Thats it 5 seconds gone.
}
}.start()
For more details on CountDownTimer

Kotlin Handler class: How to stop a thread - from inside of run() - when a condition is met?

I am currently learning how to develop android applications. For my first app, I wanted to build a simple clicker with a primitive combat sistem.
In my code, I have created a Handler instance and a Runnable object. Inside of this "delayed loop" the enemy attacks the player instance every 3 seconds, dealing damage. Inside the loop I also test when the player's hp is less or equals 0; when the condition is met I want to stop the runnable object.
How do i do this? I tried calling a function stopCombat() which contains Handler.removeCallbacks, but i can't call a function before declaration. I tried putting .removeCallbacks(this) inside run(), but it also doesn't work.
val mainHandler = Handler(Looper.getMainLooper())
fun playerDied() {
ongoingFight = false
combatLog.append("${myPlayer.name} has been defeated!\n")
myPlayer.currentHitPoints = myPlayer.maxHitPoints / 2
myPlayer.gold = 0
gold.text = "${myPlayer.gold} gold"
}
val performTask = object : Runnable {
override fun run() {
val enemyHit = enemyAttack(myPlayer, myEnemy)
// Call a function to stop this thread if the condition is met:
if (myPlayer.takeDamage(enemyHit)) { //myPlayer.takeDamage returns true if hp <= 0
playerDied()
stopCombat() // called before declaration
mainHandler.removeCallbacks(this) // tried, doesn't work
}
playerHP.text = "${myPlayer.currentHitPoints} / ${myPlayer.maxHitPoints}"
combatLog.append("${myEnemy.name} deals $enemyHit damage!\n")
mainHandler.postDelayed(this, 3000)
}
}
fun stopCombat(){
mainHandler.removeCallbacks(performTask)
}
Set a boolean to determine if you’re going to post the runnable again
override fun run() {
val enemyHit = enemyAttack(myPlayer, myEnemy)
val died = myPlayer.takeDamage(enemyHit))
if (died)
playerDied()
playerHP.text = "${myPlayer.currentHitPoints} / ${myPlayer.maxHitPoints}"
combatLog.append("${myEnemy.name} deals $enemyHit damage!\n")
if (!died)
mainHandler.postDelayed(this, 3000)
}
Sorry for formatting—on my phone.
I notice that you are using kotlin. I think you can use coroutines. It's more concise and lightweight. Cancellation and Timeouts
If you still want to use thread. Try the following code.
val executorService = Executors.newSingleThreadExecutor()
val runnables = Runnable {
}
val task = executorService.submit(runnables)
task.cancel(true)

Categories

Resources