I need to call setVolumeControlStream from a service that plays some sound via STREAM_SYSTEM.
Obviously in an Activity that is no problem, but how can I do this with a service?
From the looks of it, this isn't possible the way you are trying to do it.
As said in the android reference
The suggested audio stream will be tied to the window of this Activity
Hence with a service that has no activity, there is no window to tie the audio stream to.
However, It looks like you should be able to receive media key events (including volume keys) from a broadcaster as shown here which would allow you to change your service volume without an activity being visible.
I think this library can provide you what you need:
http://code.google.com/p/media-volume-control/
Related
I'm trying to be able to start music playback from my app when the headset buttons are clicked while my app is stopped.
I can use MediaSession.Callback onMediaButtonEvent() or the now deprecated registerMediaButtonEventReceiver() to listen for media button click WHILE my app is playing music, but if I pause the music for a minute, with my Activity and playback Service still running, and then I press the headset button, I see that I have lost the ability to receive the media button broadcast. Instead, Google Now opens.
What I'm trying to do is something like Google Play Music. It is able to start music playback even if the app is completely stopped...no services in the background.
I feel that setMediaButtonReceiver() is the one to use for this, but I've not been able to get it to work.
setMediaButtonReceiver(PendingIntent mbr)
Set a pending intent for your media button receiver to allow restarting playback after the session has been stopped. If your app is started in this way an ACTION_MEDIA_BUTTON intent will be sent via the pending intent.
I have the following snippet in my Service.
PendingIntent pi = PendingIntent.getBroadcast(HeadsetService.this, 0, new Intent(HeadsetService.this, RemoteControlReceiver.class), 0);
mMediaSession.setMediaButtonReceiver(pi);
My RemoteControlReceiver BroadcastReceiver is registered in the Manifest but I receive no broadcast when I press the button.
I've also seen that other music player apps lose the ability to receive media button broadcasts once they've stopped playback for about minute.
Any ideas how I can have a more robust media button controls?
Thanks in advance!
First, it is important to distinct MediaSessionCompat from any service such as MediaBrowserServiceCompat.
MediaSessionCompat is communicating with the external MediaSessionStack that dictates which app will get media key commands from external MediaSessionService. On API 26+ the key will be sent to the last playing app. Devices with API < 26 will first look for active playing/buffering session, then for active session and then for last playing session. The latter allows apps to 'steal' the focus by keeping the mMediaSesssion.isActive tag on when they shouldn't. More details about priority can be found in the official guide.
So as long as your app set mMediaSesssion.isActive = true at some point and was last playing, it will get media keys unless mMediaSession.release() was called. The latter removes your session from MediaSessionStack and thus will your session no longer receives media keys. That's why it is important to call release() once you no longer expect user to continue playing video or music. There is one more caveat: If system thinks your app was killed as opposed to ended gracefully, then the app is removed from MediaSessionStack as well, which makes sense, because in service onDestroy() is not necessarily called in such scenario and thus the system releases your session for you. This might happen when you swipe away the app. A workaround I use is to keep the service in foreground while the main app is in use and then end the service after a short delay upon receiving a call to onTaskRemoved() in service.
The call to mMediaSession.release() will happen at some point if you put it in onDestroy() of your service. The service is expected to end when it is not being used, as otherwise it is taking system resources. Thus, it is recommended to end it in onStop() command or when swiping away app or notification. In your case it may happen that power manager killed your service after being inactive for some time. Depending on implementation the some parts of the player might still be working even if the service is destroyed. Also the notification might still be there, as the service is no longer in foreground. That might've fool you into thinking that the service was running. Though without more details, I cannot really say what exactly went wrong in your case.
One additional cause that might prevent your app from getting media keys is if your manifest is not properly configured. Make sure that your BroadcaseReceiver entry includes android.intent.action.MEDIA_BUTTON intent filter.
Another possible mistake is initializing your media key callback MediaSessionCompat.Callback() in the service or any other lifecycle component. Thus, if that component gets destroyed, it can quickly lead to unexpected behavior.
TLDR:
Filter your Logcat for MediaSessionStack|MediaSessionService to ensure that your app gets the media keys. If it doesn't, then:
ensure that mMusicService.isActive = true is set (in e.g. in onPlay())
ensure that mMusicService.release() is not called
ensure that your manifest is properly set
ensure that the system doesn't think your service was killed (e.g. by swiping away the app) as opposed to being ended gracefully
Then make sure your app is handling media keys properly.
In case of custom receiver, they should be there.
In case of androidx.media.session.MediaButtonReceiver they should be in MediaControllerCompat.Callback() whose state should not depend on any service or lifecycle component
I wrote the answer a little bit more general, since it is an old question and others might benefit from it more than the OP.
Basing on this example: https://www.binpress.com/tutorial/using-android-media-style-notifications-with-media-session-controls/165
I've created a service which playing audio stream from URL with control buttons in notification - I can pause, resume and turn off streaming (by stopping service).
But how can I control this from activity, just like from notification? Of course I can send intents with proper action, but what I want is (for example):
I have opened activity from where I start service, music is playing and then I pull down a notification drawer, kicked pause for example, music is paused but activity is not aware about this
So there is my question - is there any possibility to access running Media Session or something like that from activity and control it just like from notification?
You need to bind your activity to your service, then add activity as listener to the player, and every time you start, pause ...etc report to your listener (activity). You will need to create your Interface to report such details to whoever wants to track the player's events.
Alternatively, you can use the new MediaSession compat libraries in the android support libraries found here:
https://developer.android.com/guide/topics/media-apps/index.html
They simplify the process of creating media controllers and establishing the transport control channels for wiring user interface components to media control service classes.
I am developing an application which supports GoogleCast. I am using a CastCompanionLibrary and everything works fine, but there is one little problem there in my application.
I need to change the layout, which appears in case that user changes the volume via HW buttons. In my application, there the layout looks like this:
There is a Cast icon in this layout, but it is white and visibility of them is very bad. For example, in YouTube application, there the same layout looks like this:
So, my question is simple: how can I change the layout, or how can I change the Cast icon in layout? I do not see it in CastCompanionLibrary.
Thanks for any advice.
As far as I know, the UI that you see there when you change the volume is provided by the system through MediaSession or RemoteControlClient, etc and not the application itself. As a result, I am not aware of any way to change that. The UI that you see in YT is a custom UI, and handling of the volume is done through app; in fact if you send the YT app to background and change the volume, you see a different UI so they seem to have decided to capture hardware volume themselves and provide their own custom UI. This is doable as long as your app is in front; as soon as it loses focus (for example when it goes to background), your app doesn't receive the volume key events and your app cannot do much (that is what happens to YT as well). Try Google Play Music and see how their UI looks like when you change volume while casting.
In order to achieve this, the player service must maintain an "active" MediaSessionCompat. The service should proactively sync media player's playback state with the MediaSessionCompat. In addition to this, service must call MediaRouter#setMediaSessionCompat() with the media session on its initialization.
If all of these conditions are met, then whilst casting, the service can call MediaSessionCompat#setPlaybackToRemote(VolumeProviderCompat). Then whenever the player is in "playing" state, volume events will be delivered to MediaSession which calls the VolumeProviderCompat. To switch back to regular media controls, i.e. on cast session end, the service can call MediaSessionCompat#setPlaybackToLocal(AudioManager.STREAM_*).
Sample implementation - Android MediaRouter Volume events
I have built an application that uses Cast Companion Library (CCL) to remotely play video to cast comparable devices. Every thing is working find but I need to be able to change video files and not kill and restart the activity and fragment.
I have implemented my own custom version of the VideoCastControllerActivity that implements the same interface but I am re-using the VideoCastControllerFragment that CCL comes with. One problem is that the VideoCastControllerFragment does not really give specific notice when the end of a video file is reached. It somewhat does by calling closeActivity() from the IVideoCastController interface so I tried to use that event to know when to load the next video file. But loading the next video file by calling
getCastManager().loadMedia( mSelectedMedia, autoPlay, position );
but doing that results in another call to close activity and a loop until the end of the playlist is reached without playing any video.
Is there another way to go about this without re-writing my own VideoCastControllerFragment?
To answer your first question (or observation), you can listen to various callback events directly in your implementation of VideoCastControllerActivity; you have access to the VideoCastManager so you know when your media status goes to IDLE with the reason FINISHED. Outside of that, you seem to be trying to implement some sort of playlist functionality in your sender while the right place for that is on the receiver (i.e. you need to write a custom receiver); if your playlist knowledge/logic lives on your phone, then the whole thing becomes dependent on your phone so if it goes to sleep, you chromecast doesn't know what to do. In addition, if a second device connects to the same cast device, it cannot correctly reflect the playlist, etc. So a phone device can let user form a playlist and then it has to send the information about that playlist to your custom receiver and your custom receiver should handle playing them in queue and your sender(s) should be able to send custom messages to move to the next/prev, etc and your receiver should do the right thing accordingly. We have a very rudimentary sample on our GitHub repo that shows how you can do video playlist on the receiver.
I have an audio recording service in my app which will record the sound continuously. So, it will always occupy the AudioRecord. It means no other app can use audio recorder as it is already occupied by the service. Is there any way to notify that other app is requesting for audio recorder(so that I can release it) and also when the app releases it(so that I can assign it back to the service)?
Maybe a possible way is to create a BroadcastReceiver which receives an event from the app which is requesting the control over the mic source. The onReceive() method should interact with the service and release the resource. When the other app is finishing it can revert the process to start the service again. If you can't get control over the behavior of the requesting app I think there's a slightly different problem. Anyway:
The problem is all about knowing when the resource is being requested, this can be done through AudioManager intent types.
Be sure to check Managing audio focus which talks about audio focus loss in TRANSIENT way!
As #Rekire mentioned, there is possibly no way to achieve this. Also, AudioManager provide no such broadcasts, so it is not possible for different apps. Maybe rooting the device is the only option.
This can be done with AudioManager.OnAudioFocusChangeListener callback. Just stop recording on AUDIOFOCUS_LOSS_TRANSIENT event and start again on AUDIOFOCUS_GAIN event.
This solution works well for Google Voice Search (Google Search widget, Google Chrome, etc).
But unfortunately it works poorly for other ordinary applications (for example HTC M7 Voice Recorder app is not able to start recording on first click on "Record" button, second click do the trick - it seems app should be ready to retry recording on failure several times).