I am trying to handle the pause/play button from a Bluetooth headset (e.g. Jabra Elite 7 Pro
) by simply logging into Logcat a message whenever it is pressed. The “twist” is that this is during TextToSpeech.speak(...)
, not during ordinary media playback.
To accomplish that, I introduced the following changes into my app’s code (Java):
1. Updated build.gradle
Added the necessary dependencies in my build.gradle
file:
dependencies {
implementation 'androidx.media:media:1.7.0'
implementation 'androidx.appcompat:appcompat:1.7.0'
}
2. Updated AndroidManifest.xml
Added the MediaButtonReceiver
to my AndroidManifest.xml
:
<receiver android:name="androidx.media.session.MediaButtonReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
and necessary Bluetooth permissions:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
3. Modified My Activity
import android.content.ComponentName;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.session.MediaSession;
import androidx.media.session.MediaButtonReceiver;
import android.content.Intent;
import android.media.session.MediaSession;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
along with:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "BluetoothMediaButton";
private MediaSession mediaSession;
private AudioFocusRequest audioFocusRequest;
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener;
and MediaSession.setCallback(..):
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// etc. ...
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.registerMediaButtonEventReceiver(new ComponentName(getPackageName(), MediaButtonReceiver.class.getName()));
audioFocusChangeListener = focusChange -> {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
Log.d(TAG, "Audio focus gained");
// Resume playback or increase volume
break;
case AudioManager.AUDIOFOCUS_LOSS:
Log.d(TAG, "Audio focus lost");
// Stop playback
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
Log.d(TAG, "Audio focus lost temporarily");
// Pause playback or lower volume
break;
}
};
Log.d(TAG, "Setting up MediaSession");
mediaSession = new MediaSession(this, "BluetoothMediaButtonExample");
Log.d(TAG, "MediaSession created");
mediaSession.setCallback(new MediaSession.Callback() {
@Override
public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
Log.d(TAG, "onMediaButtonEvent called");
Log.d(TAG, "Media button pressed: " + mediaButtonIntent.getAction());
return super.onMediaButtonEvent(mediaButtonIntent);
}
});
Log.d(TAG, "MediaSession callback set");
mediaSession.setActive(true);
Log.d(TAG, "MediaSession activated");
// etc. ...
}
Note: I didn't introduce any “audio focus” code initially. But since I didn’t receive the expected events, I suspected that the media button events may be intercepted by another app that has audio focus.
So I added the audio focus logging above, plus code to explicitly request audio focus:
private void requestAudioFocus() {
AudioAttributes playbackAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build();
audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(playbackAttributes)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(audioFocusChangeListener)
.build();
int result = mAudioManager.requestAudioFocus(audioFocusRequest);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(TAG, "Audio focus request granted");
// Start playback or enable media button handling
} else {
Log.d(TAG, "Audio focus request failed");
}
}
private void abandonAudioFocus() {
if (audioFocusRequest != null) {
mAudioManager.abandonAudioFocusRequest(audioFocusRequest);
Log.d(TAG, "Audio focus abandoned");
}
}
and
@Override
public void onResume() {
super.onResume();
requestAudioFocus();
}
and
@Override
public void onPause() {
super.onPause();
abandonAudioFocus();
}
and
@Override
public void onDestroy() {
super.onDestroy();
if (mediaSession != null)
mediaSession.release();
}
Confirmed by the following Logcat relevant messages (filtered by “media”):
---------------------------- PROCESS STARTED (31873) for package com.me.myapp ----------------------------
09:50:34.384 MyActivity D T=31873 Setting up MediaSession | MyActivity.onCreate(MyActivity.java:892) < MyAppActivity.onCreate(MyAppActivity.java:24)
09:50:34.387 D T=31873 MediaSession created | MyActivity.onCreate(MyActivity.java:894) < MyAppActivity.onCreate(MyAppActivity.java:24)
09:50:34.387 D T=31873 MediaSession callback set | MyActivity.onCreate(MyActivity.java:903) < MyAppActivity.onCreate(MyAppActivity.java:24)
09:50:34.388 D T=31873 MediaSession activated | MyActivity.onCreate(MyActivity.java:905) < MyAppActivity.onCreate(MyAppActivity.java:24)
09:50:36.217 cr_media W BLUETOOTH_CONNECT permission is missing.
09:50:36.218 W getBluetoothAdapter() requires BLUETOOTH permission
09:50:36.219 W registerBluetoothIntentsIfNeeded: Requires BLUETOOTH permission
---------------------------- PROCESS ENDED (31873) for package com.me.myapp ----------------------------
and (filtered by “audio”):
---------------------------- PROCESS STARTED (31873) for package com.me.myapp ----------------------------
09:50:34.698 MyActivity D T=31873 Audio focus request granted | MyActivity.requestAudioFocus(MyActivity.java:988) < MyActivity.onResume(MyActivity.java:1099)
09:50:34.800 D T=31873 Audio focus abandoned | MyActivity.abandonAudioFocus(MyActivity.java:998) < MyActivity.onPause(MyActivity.java:1651)
09:50:36.239 D T=31873 Audio focus request granted | MyActivity.requestAudioFocus(MyActivity.java:988) < MyActivity.onResume(MyActivity.java:1099)
---------------------------- PROCESS ENDED (31873) for package com.me.myapp ----------------------------
Still, despite the explicit request for audio focus, I have not been able to see in Logcat the desired "onMediaButtonEvent called" message.
After much research I discovered that there is a documented “bug” that prevents receiving media button events for TTS: https://issuetracker.google.com/issues/249741615
Currently, the only workaround is "I just played a silent sound for less than 1s using MediaPlayer class and when done I start using TextToSpeech."
This workaround, unfortunately, is not suitable for my app, so I am looking for additional ideas to receive Bluetooth headset play/pause events when TextToSpeech.speak(...)
.
Is there a way to overcome this audio focus (for TTS) that doesn’t require playing a 1-second .wav
file before every speak()
?
!intent.hasExtra(Intent.EXTRA_KEY_EVENT)
. You still need the workaround, though. – Tough