Cannot play audio with ExoPlayer during an ongoing call - android

I have a basic ExoPlayer project which plays and pauses audio. I'm passing handleAudioFocus as true in setAudioAttributes while initializing ExoPlayer to let it handle the audio focus by itself. But it does not behave correctly in case of ongoing call.
The project code is available on GitHub. The main logic is present in the MainActivity.
It works fine in the below scenarios:
CASE 1: if there is no ongoing call (play, pause works normally)
CASE 2: if we pick up a call and then hang up (paused when the call is picked up, resumed after hanging up the call)
But it does not work in this case:
CASE 3: if we press the play button during an ongoing call (does not play after hanging up the call also)
I added an event listener to the player and added logs to all callback methods. I'm getting the below logs for the above-mentioned cases:
Logs in cases 1 and 2 (except picking up call in case 1):
// init
11:36:20.711 PlayerListener com.mohit.exoplayer I onAvailableCommandsChanged:
11:36:20.782 PlayerListener com.mohit.exoplayer I onPlayerStateChanged: playWhenReady = false, playbackState = 3
11:36:20.782 PlayerListener com.mohit.exoplayer I onPlaybackStateChanged: playbackState = 3
// click play
11:36:23.427 MainActivity com.mohit.exoplayer I onCreate: is already playing = false
11:36:23.431 PlayerListener com.mohit.exoplayer I onPlayerStateChanged: playWhenReady = true, playbackState = 3
11:36:23.432 PlayerListener com.mohit.exoplayer I onPlayWhenReadyChanged: playWhenReady = true
11:36:23.432 PlayerListener com.mohit.exoplayer I onIsPlayingChanged: true
// incoming call
11:36:30.541 PlayerListener com.mohit.exoplayer I onPlaybackSuppressionReasonChanged: playbackSuppressionReason = 1
11:36:30.541 PlayerListener com.mohit.exoplayer I onIsPlayingChanged: false
// pick up call
// hang up
11:36:45.400 PlayerListener com.mohit.exoplayer I onPlaybackSuppressionReasonChanged: playbackSuppressionReason = 0
11:36:45.401 PlayerListener com.mohit.exoplayer I onIsPlayingChanged: true
Logs in Case 3:
// init
11:39:27.922 PlayerListener com.mohit.exoplayer I onAvailableCommandsChanged:
11:39:27.988 PlayerListener com.mohit.exoplayer I onPlayerStateChanged: playWhenReady = false, playbackState = 3
11:39:27.988 PlayerListener com.mohit.exoplayer I onPlaybackStateChanged: playbackState = 3
// click play
11:39:33.858 MainActivity com.mohit.exoplayer I onCreate: is already playing = false
11:39:33.862 PlayerListener com.mohit.exoplayer I onPlayerStateChanged: playWhenReady = true, playbackState = 3
11:39:33.862 PlayerListener com.mohit.exoplayer I onPlayWhenReadyChanged: playWhenReady = true
11:39:33.862 PlayerListener com.mohit.exoplayer I onIsPlayingChanged: true
// incoming call
11:39:38.060 PlayerListener com.mohit.exoplayer I onPlaybackSuppressionReasonChanged: playbackSuppressionReason = 1
11:39:38.060 PlayerListener com.mohit.exoplayer I onIsPlayingChanged: false
// click play
11:39:45.363 MainActivity com.mohit.exoplayer I onCreate: is already playing = false
11:39:45.364 PlayerListener com.mohit.exoplayer I onPlayerStateChanged: playWhenReady = false, playbackState = 3
11:39:45.364 PlayerListener com.mohit.exoplayer I onPlayWhenReadyChanged: playWhenReady = false
11:39:45.364 PlayerListener com.mohit.exoplayer I onPlaybackSuppressionReasonChanged: playbackSuppressionReason = 0
// click play again
11:39:50.482 MainActivity com.mohit.exoplayer I onCreate: is already playing = false
// hang up
In case 1 and 2, playWhenReady stays true. But in case 3, player does not work and playWhenReady is set to false, so it does not even resume after call is disconnected. To me, it seems to be a bug of ExoPlayer.
The question is, is there any way to play music during an ongoing call? Or is the only way for it to work is to handle the audio focus manually instead of letting ExoPlayer handle the audio focus?
Note: Spotify and YT Music can play audio during call with low volume (ducked).

Related

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

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
}
}

Why does setSpeakerphoneOn() inconsistently not set audio to speakerphone on Android 12 devices and higher

I have an android app that supports calling capabilities. We've been experiencing speakerphone issues on android 12 and higher devices in the past few months.
When the speakerphone button is pressed, there is sometimes no effect on changing the audio path, even though the logs show the speakerphone route is being applied in all scenarios. The problem reproduces in around half of the tests. This is the code I have for the audio manager:
enum class AudioOutput {
MAIN, SPEAKER, FRONT
/// Main: Can be headphones, front ear speaker, bluetooth. (NOTE: Headphones override bluetooth) (double check on android)
/// Speaker: Used for the bottom device speaker.
/// Front: Can be the front ear speaker or headphones. Only used while bluetooth is connected and main is dedicated to bluetooth.
}
enum class AudioMode {
NORMAL, CALL, RINGER
/// Normal: Default audio mode.
/// Call: When user is on softphone or in a call.
/// Ringer: When user is receiving a call to play ring tone.
}
class AudioManager {
companion object {
val instance = AudioManager()
}
var audioMode: AudioMode = AudioMode.NORMAL; private set
var audioOutput: AudioOutput = AudioOutput.MAIN; private set
fun setAudioMode(mode: AudioMode) {
audioMode = mode
applyAudioRoute()
}
fun setAudioOutput(output: AudioOutput) {
audioOutput = output
if (audioMode != AudioMode.NORMAL) {
applyAudioRoute()
}
}
private fun applyAudioRoute() {
Timber.d("[AudioManager] ApplyRoute -> Mode: $audioMode, Output: $audioOutput")
val context = MyApplication.getAppContext()
val audioManager = context?.getSystemService(Context.AUDIO_SERVICE) as android.media.AudioManager
when (audioMode) {
AudioMode.NORMAL -> {
audioManager.setMode(android.media.AudioManager.MODE_NORMAL)
audioManager.setSpeakerphoneOn(true)
}
AudioMode.CALL -> {
when (audioOutput) {
AudioOutput.MAIN -> {
audioManager.setMode(android.media.AudioManager.MODE_IN_COMMUNICATION)
audioManager.setSpeakerphoneOn(false)
audioManager.setBluetoothScoOn(true)
audioManager.startBluetoothSco()
}
AudioOutput.SPEAKER -> {
audioManager.setMode(android.media.AudioManager.MODE_IN_COMMUNICATION)
audioManager.setSpeakerphoneOn(true)
audioManager.setBluetoothScoOn(false)
audioManager.stopBluetoothSco()
}
AudioOutput.FRONT -> {
audioManager.setMode(android.media.AudioManager.MODE_IN_COMMUNICATION)
audioManager.setSpeakerphoneOn(false)
audioManager.setBluetoothScoOn(false)
audioManager.stopBluetoothSco()
}
}
}
AudioMode.RINGER -> {
if (!MyCallStateManager.instance.isCallActive()) {
audioManager.setMode(android.media.AudioManager.MODE_RINGTONE)
audioManager.setSpeakerphoneOn(true)
}
}
}
}
}
I would appreciate any direction or assistance in tracking down this issue. In addition, this is my first stack overflow post, so I apologize if I there's something missed in the post.
I've Changed the AudioManager variables that were set statically to function calls:
audioManager.isSpeakerphoneOn = true -> setSpeakerphoneOn()
audioManager.mode = android.media.AudioManager.MODE_IN_COMMUNICATION -> audioManager.setMode(android.media.AudioManager.MODE_IN_COMMUNICATION)

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)
....
}

When will onCompletion be launched in MediaPlayer?

I try to use the following code to play music.
I know I need to release mediaPlayer as soon as possible if I don't use it again, so I place the release code in onCompletion.
1: Will onCompletion be launched after a music have finished play?
2: Will onCompletion be launched after I invoke mediaPlayer?.stop()?
3: Will onCompletion be launched if the Activity which invoke PlayHelper is destroied?
Code
class PlayHelper private constructor(): MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener {
private var mediaPlayer: MediaPlayer? = null
fun play(path: String){
mediaPlayer= MediaPlayer()
mediaPlayer?.setAudioAttributes(
AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
)
mediaPlayer?.setDataSource(path)
mediaPlayer?.setOnPreparedListener(this#PlayHelper)
mediaPlayer?.prepareAsync()
}
fun pause(){
mediaPlayer?.pause()
}
fun stop(){
mediaPlayer?.stop()
}
/** Called when MediaPlayer is ready */
override fun onPrepared(mediaPlayer: MediaPlayer) {
mediaPlayer.start()
}
override fun onCompletion(mediaPlayer: MediaPlayer) {
if (mediaPlayer.isPlaying) {
mediaPlayer.stop();
mediaPlayer.release()
}
}
companion object {
// For Singleton instantiation
#Volatile private var instance: PlayHelper? = null
fun getInstance() = instance?: synchronized(this) {
instance?: PlayHelper().also { instance = it }
}
}
}
Regarding the official documents, onCompletion is called only when the end of a media source is reached during playback. So, in other cases, like calling mediaPlayer.stop(), it won't be called.
1: Will onCompletion be launched after a music have finished play?
It will.
2: Will onCompletion be launched after I invoke mediaPlayer?.stop()?
It will not.
3: Will onCompletion be launched if the Activity which invoke PlayHelper is destroied?
It will not. The PlayHelper will be removed from memory when the Activity is destroyed.
This link: onCompletionListener from the docs contains the following line:
Called when the end of a media source is reached during playback.

Categories

Resources