Android MediaRouter Volume events - android

I want to implement volume change as seen in Youtube app while casting, like if app is in background or on lock screen
Like this
private void createSession() {
ComponentName receiver = new ComponentName(getPackageName(), RemoteReceiver.class.getName());
mediaSession = new MediaSessionCompat(this, "PlayerService", receiver, null);
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 1f)
.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE)
.build());
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.requestAudioFocus(new AudioManager.OnAudioFocusChangeListener() {
#Override
public void onAudioFocusChange(int focusChange) {
// Ignore
}
}, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
mediaSession.setActive(true);
mediaRouter = MediaRouter.getInstance(this);
mediaRouter.setMediaSessionCompat(mediaSession);
}
Now I get the slider just like Image above and it responds to volume buttons, but I dont receive change in my broadcast receiver.

All you have to do is create a MediaSessionCompat with a VolumeProviderCompat.
// Here is a volume provider which sets the volume of a remote route.
// Extend VolumeProviderCompat with your own implementation.
public static class RemoteVolume extends VolumeProviderCompat {
public RemoteVolume(MediaRouter.RouteInfo routeInfo) {
super(VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE, STEPS, 0);
this.stepSize = routeInfo.getVolumeMax() / STEPS;
this.routeInfo = routeInfo;
}
#Override
public void onSetVolumeTo(int volume) {
routeInfo.requestSetVolume(volume * stepSize);
setCurrentVolume(volume);
}
#Override
public void onAdjustVolume(int delta) {
int newVolume = getCurrentVolume() + delta;
routeInfo.requestSetVolume(newVolume * stepSize);
setCurrentVolume(newVolume);
}
}
Then, connect the VolumeProviderCompat to your MediaSessionCompat:
MediaSessionCompat session = new MediaSessionCompat(context, TAG);
// might need some of these flags
session.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS |
MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS);
// the volume buttons are routed to this session when it is
// active and currently playing
session.setPlaybackState(new PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 1.0f)
.build());
session.setActive(true);
// The media router tries to bind its own VolumeProvider which kinda
// works. We need to unbind the one provided and put ours in.
router.setMediaSessionCompat(session);
session.setPlaybackToRemote(new RemoteVolume(myRemoteRoute));

Related

Android vibration app doesn't work anymore after Android 10, API 29 update

so I made an app a couple months back that helped me improve my sleep. I have sleeping issues and, it sounds odd, but I use the app to vibrate my phone when I go to bed to give myself something to focus on to go to sleep, it is currently a vital aspect of my sleep routine.
However, I updated my phone to Android 10 yesterday and it completely broke the app. Before, the app would vibrate when I click the start button and continue to vibrate even after I lock the phone by using a background service, broadcast receiver, and wake lock. Now though, the app stops vibrating after I lock the phone and nothing in the console gives any reason as to why it is doing this.
If anyone could give advice on what I could change in the code or something, it would be much appreciated as I'm completely lost as to what to do and I have to get this to work somehow.
Here is the code:
Function in MainActivity that handles the beginning of the service Vibrate:
// Event for when the VIBRATE button is pressed
public void beginVibration(View view) {
// Given either of the bars are not 0
if (durationBar.getProgress() != 0 || delayBar.getProgress() != 0) {
// Get the values for each bar and set them accordingly in the vibration value array
long[] pattern = {0, durationBar.getProgress(), delayBar.getProgress()};
// Setup the ServiceConnection to monitor the Vibrate service
c = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
m_service = ((Vibrate.MyBinder)service).getService();
}
#Override
public void onServiceDisconnected(ComponentName name) {
m_service = null;
}
};
// Bind the service to the connection
bindService(i, c, BIND_AUTO_CREATE);
// Insert the pattern into the intent itself
i.putExtra("pattern", pattern);
// Start the vibrate service
this.startService(i);
}
}
Vibrate Service class:
public class Vibrate extends Service {
// Vibration object
private Vibrator v;
/*
THESE THREE ARE FOR PREVENTING THE VIBRATION FROM STOPPING AFTER THE PHONE IS PUT TO SLEEP
*/
// Wake Lock object
private PowerManager.WakeLock wl;
// Manager for the notifications
private NotificationManagerCompat m_notificationManager;
// BroadcastReceiver object
public BroadcastReceiver re;
public AudioAttributes audioAttributes;
// Not gonna lie, Idk what this does just know it is part of the binding process within MainActivity
public class MyBinder extends Binder {
public Vibrate getService() {
return Vibrate.this;
}
}
#Override
public void onCreate() {
super.onCreate();
// Acquire the Wake Lock
PowerManager pw = (PowerManager) getSystemService(POWER_SERVICE);
wl = pw.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WL:");
wl.acquire();
// Get the vibration service
v = (Vibrator) getSystemService(VIBRATOR_SERVICE);
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ALARM)
.build();
}
#Override
public int onStartCommand(Intent i, int flags, int startId) {
// Ensure that an intent with a long array has been passed
if (i != null && i.getExtras() != null) {
// Get the array
final long[] pattern = i.getExtras().getLongArray("pattern");
// Begin the vibration
v.vibrate(pattern, 0);
// Intialize the BroadcastReceiver and set it to trigger when the screen is turned off,
// thus triggering the vibrations
re = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
Log.i("VIZZY: ", "VIBRATION STARTED");
v.vibrate(pattern, 0, audioAttributes);
Log.i("VIZZY: ", "VIBRATION BEGUn");
}
}
};
// Add a listener for when the screen turns off and register the receiver
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
registerReceiver(re, filter);
}
return Service.START_STICKY;
}
#Override
public void onDestroy() {
// If the phone was put to sleep, cancel the notification keeping the vibration going
if (m_notificationManager != null) {
m_notificationManager.cancel(001);
}
// Release the Wake Lock, unregister the BroadcastReceiver, and stop the vibrations
wl.release();
unregisterReceiver(re);
v.cancel();
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
private void addNotification() {
// create the notification
Notification.Builder m_notificationBuilder = new Notification.Builder(this)
.setContentTitle("VIZZY")
.setContentText("VIBRATING")
.setSmallIcon(R.mipmap.ic_launcher);
// create the pending intent and add to the notification
Intent intent = new Intent(this, Vibrate.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
m_notificationBuilder.setContentIntent(pendingIntent);
m_notificationManager = NotificationManagerCompat.from(this);
// send the notification
m_notificationManager.notify(001, m_notificationBuilder.build());
}
}
Thank you for anyone's help in advance.
I faced the same issue. And I found out that starting from target 29 vibration will work in background only with the proper audio attributes passed to the method:
public void vibrate(VibrationEffect vibe, AudioAttributes attributes);
You can try to use it in that way:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createWaveform(pattern, 0),
new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ALARM)
.build());
} else {
vibrator.vibrate(pattern, 0);
}
It might not care about these instructions:
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
registerReceiver(re, filter);
So it probably should be something alike:
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q){
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
registerReceiver(re, filter);
} else {
/* whatever it takes to make it work >= API level 29 */
}
Intent.ACTION_SCREEN_ON and Intent.ACTION_SCREEN_OFF generally require the app to be running. But the Android 10 behavior changes do not mention something alike that, therefore it is difficult to tell. Also receiving broadcasts does not tell anything about a recent change.

How to link my existing MediaPlayer with lockscreen notification

Maybe the title isn't explicit enough, let me explain.
I am working on an already existing code, Java for Android app. The actual app have a mediaplayer playing audio stream, with only a play/pause button in the layout.
The played is initialized and used in an activity.
When the phone is locked, the stream continues to play but there is no notification displayed with the next previous etc buttons.
Using this tutorial i managed to dispay the notification on lock screen and on notification area when i call this service from the activity:
Intent intent = new Intent(getApplicationContext(), MediaPlayerService.class);
intent.setAction(MediaPlayerService.ACTION_PAUSE);
startService(intent);
But it's obviously not working well as the already existing session is not linked to the new created controller on the notification.
So i'm wondering if it's better to:
Use the new service i created and try to link it to existing session
Dish the previous mediaplayer from the activity and handle all the media things in service
Keep all the media handling in the activity
I'm not very familiar with Medias handling in Android.
Here is some preview of the actual code:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_exercices);
setFinishOnTouchOutside(false);
mContext = this;
// Init of MediaSession
//mSession = new MediaSessionCompat(this, "MusicService");
//mSession.setCallback(new MediaSessionCallback());
//mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
//setSessionToken(mSession.getSessionToken());
mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
mRemoteControlResponder = new ComponentName(getPackageName(),
RemoteControlReceiver.class.getName());
mStateBuilder = new PlaybackStateCompat.Builder()
.setActions(
PlaybackStateCompat.ACTION_PLAY |
PlaybackStateCompat.ACTION_PLAY_PAUSE);
initializeViews();
initializeActions();
}
public void initializeActions() {
recyclerExercisesView.setLayoutManager(new LinearLayoutManager(mContext));
pAdapterExercise = new ExercisesPlayerAdapter(mContext, allExercisesList, isGuest, new ExercisesPlayerAdapter.RecyclerItemClickListener() {
#Override
public void onClickListener(Exercise exercice, int position) {
//Toast.makeText(DetailsSceanceActivity.this, exercice.getPath(), Toast.LENGTH_SHORT).show();
iv_play.setEnabled(true);
seekBar_progress.setEnabled(true);
if (!chronoLaunch && firstLaunch) {
startChrono();
startSensors();
}
firstLaunch = false;
changeSelectedExercice(position);
prepareExercise(exercice);
}
});
recyclerExercisesView.setAdapter(pAdapterExercise);
mMediaController = new MediaController(this);
Intent intent = new Intent(getApplicationContext(), MediaPlayerService.class);
intent.setAction(MediaPlayerService.ACTION_PAUSE);
startService(intent);
// inint mediaplayer
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
// play song
togglePlay(mp);
//mMediaController.setMediaPlayer(this);
}
});
}
I also saw few samples with MediaSessionCompat. Better use MediaSession or MediaSessionCompat?
Thanks

Receive callback on all Android media button events all the time (even when another app is playing audio)

Background Info: I need to detect whenever a user presses the play/pause button found on most headsets (KEYCODE_MEDIA_PLAY_PAUSE).
I have it all mostly working using MediaSessions, but when another app starts playing audio, I stop getting callbacks.
It seems like this is because the app that's playing audio created its own MediaSession and Android sends KeyEvents only to the newest MediaSession. To prevent this I create an OnActiveSessionsChangedListener and create a new MediaSession every time it fires.
This does work, but every time I create a new MediaSession, the listener fires again, so I find myself stuck in an inf loop.
My Question: does anyone know how I can do any of the following??:
Prevent other apps from stealing my media button focus
Detect when I've lost media button focus to another app, so I can create a new MediaSession only then, rather then whenever the active
sessions change
Check if I currently already have media button focus so I needlessly create a new MediaSession
What didn't work:
BroadcastReceiver on
AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION didn't work
because apps have to manually trigger that Broadcast, and many apps,
like NPR One do not
AudioManager.OnAudioFocusChangeListener didn't work because it requires I have
audio focus
BroadcastReceiver with max priority on android.intent.action.MEDIA_BUTTON & calling abortBroadcast(), but when other apps were playing audio, my receiver wasn't triggered. Also, other apps can set max priority as well.
My Code:
mMediaSessionManager.addOnActiveSessionsChangedListener(controllers -> {
boolean updateButtonReceiver = false;
// recreate MediaSession if another app handles media buttons
for (MediaController mediaController : controllers) {
if (!TextUtils.equals(getPackageName(), mediaController.getPackageName())) {
if ((mediaController.getFlags() & (MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)) != 0L) {
updateButtonReceiver = true;
}
}
}
if (updateButtonReceiver) {
// using a handler with a delay of about 2 seconds because this listener fires very often.
mAudioFocusHandler.removeCallbacksAndMessages(null);
mAudioFocusHandler.sendEmptyMessageDelayed(0, AUDIO_FOCUS_DELAY_MS);
}
}, ClickAppNotificationListener.getComponentName(this));
Here is the handler that gets triggered:
private final Handler mAudioFocusHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
if (mShouldBeEnabled) {
updateButtonReceiverEnabled(true);
}
}
};
And finally here is the method that the Handler triggers:
private void updateButtonReceiverEnabled(boolean shouldBeEnabled) {
// clear old session (not sure if this is necessary)
if (mMediaSession != null) {
mMediaSession.setActive(false);
mMediaSession.setFlags(0);
mMediaSession.setCallback(null);
mMediaSession.release();
mMediaSession = null;
}
mMediaSession = new MediaSessionCompat(this, MEDIA_SESSION_TAG);
mMediaSession.setCallback(mMediaButtonCallback);
mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mMediaSession.setPlaybackToLocal(AudioManager.STREAM_MUSIC);
mMediaSession.setActive(true);
mMediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE)
.setState(PlaybackStateCompat.STATE_CONNECTING, 0, 0f)
.build());
if (shouldBeEnabled != mShouldBeEnabled) {
getPackageManager().setComponentEnabledSetting(mMediaButtonComponent,
shouldBeEnabled
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
mShouldBeEnabled = shouldBeEnabled;
}
Thanks!
if you just want to capture MediaButton you can register a BroadcastReceiver to get Media Button action all the time .
MediaButtonIntentReceiver class :
public class MediaButtonIntentReceiver extends BroadcastReceiver {
public MediaButtonIntentReceiver() {
super();
}
#Override
public void onReceive(Context context, Intent intent) {
String intentAction = intent.getAction();
if (!Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
return;
}
KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (event == null) {
return;
}
int action = event.getAction();
if (action == KeyEvent.ACTION_DOWN) {
// do something
Toast.makeText(context, "BUTTON PRESSED!", Toast.LENGTH_SHORT).show();
}
abortBroadcast();
}
}
add this to manifest.xml:
<receiver android:name=".MediaButtonIntentReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
and register your BroadcastReceiver like this ( in main activity)
IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON);
MediaButtonIntentReceiver r = new MediaButtonIntentReceiver();
filter.setPriority(1000);
registerReceiver(r, filter);
also look at :
How to capture key events from bluetooth headset with android
How do I intercept button presses on the headset in Android?
The controllers you get in OnActiveSessionsChangedListener is ordered by priority. You only have to create a new MediaSession if you see that your MediaSessionis not the first one in the list.
Note that you might still run into an infinite loop if there is another app contending the media key events using the same approach.

PhoneStateListener is not working in android lollipop

I've developed a radio app where i need to pause radio when incoming/outgoing phone calls comes. I've used PhoneStateListener which is working fine in all android version except in lollipop. Here is the code that i have used to handle phone calls. I've tried both setStreamVolume and setStreamMute methods but both seems not working in lollipop. Please some body help me on this.
Thanks in advance.
PhoneStateListener phoneStateListener = new PhoneStateListener() {
#Override
public void onCallStateChanged(int state,
String incomingNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_RINGING: // Incoming
// call:
// Pause
// music
if (Globals.isPlayerRunning) {
curVolume = audioManager
.getStreamVolume(AudioManager.STREAM_MUSIC);
audioManager.setStreamVolume(
AudioManager.STREAM_MUSIC, 0, 0);
audioManager.setStreamMute(
AudioManager.STREAM_MUSIC, true);
isServiceMuteByCall = true;
}
break;
case TelephonyManager.CALL_STATE_IDLE: // Not in
// call:
// Play
// music
if (isServiceMuteByCall) {
isServiceMuteByCall = false;
audioManager.setStreamMute(
AudioManager.STREAM_MUSIC, false);
audioManager.setStreamVolume(
AudioManager.STREAM_MUSIC, curVolume,
AudioManager.FLAG_PLAY_SOUND);
}
break;
case TelephonyManager.CALL_STATE_OFFHOOK:// A call
// is
// dialing,
// active
// or
// on
// hold
if (Globals.isPlayerRunning) {
curVolume = audioManager
.getStreamVolume(AudioManager.STREAM_MUSIC);
audioManager.setStreamMute(
AudioManager.STREAM_MUSIC, true);
audioManager.setStreamVolume(
AudioManager.STREAM_MUSIC, 0, 0);
isServiceMuteByCall = true;
}
break;
}
super.onCallStateChanged(state, incomingNumber);
}
};
if (mgr != null) {
mgr.listen(phoneStateListener,
PhoneStateListener.LISTEN_CALL_STATE);
}
Here is the solution to above issue. This can be solved using BroadcastReceiver.
Create a class which extends BroadcastReceiver and inside onreceive method check for phone states like below.
public class TelephonyManagerReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(intent.getAction())) {
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) {
//stop the player or mute the audio here
} else if (TelephonyManager.EXTRA_STATE_IDLE.equals(state)) {
//start the player or unmute the audio here
} else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(state)) {
//stop the player or mute the audio here
}
}
}
}
after this add above broadcast receiver in manifest file.
<receiver android:name="com.radioapp.util.TelephonyManagerReceiver" >
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>
and ensure that you request this permission in the manifest:
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>

Play alarm sound just once

I have a working alarm app, but wanted to add a feature where the user gets the choice between "Play alarm continuously till acknowledged" and "play alarm sound once".
I then looked at my alrm ringing code expecting to see some kind of "repeat" flag which I could optionally remove - but there was none. So how do I play the alarm sound just once?
My existing code looks like this:
private void playSound(Context context, Uri alert)
{
mMediaPlayer = new MediaPlayer();
try
{
mMediaPlayer.setDataSource(context, alert);
final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0)
{
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
mMediaPlayer.prepare();
mMediaPlayer.start();
}
}
catch (IOException e)
{
// oops!
}
}
Actually in each alarm sound there is a FLAG named ANDROID_LOOP which force your sound to loop. Unfortunatly you can't change that flag even using MediaPlayer.setLooping(false).
But you still can manually stop your player after a certain time. For example getDuration will give you the length of your sound.
int duration = mMediaPlayer.getDuration();
Runnable stopSoundRunnable = new Runnable() {
#Override
public void run() {
mMediaPlayer.stop();
}
};
mSoundHanlder.postDelayed(stopSoundRunnable, duration);

Categories

Resources