Difference media controller vs. transport controls Android - android

I am trying to understand what each of these two Android constructs actually are and of course how they work, especially the transport controls, when dealing with the MediaPlayer and the MediaSession classes.
From the official documentation about the MediaSession, it
Allows interaction with media controllers, volume keys, media buttons, and transport controls.
As far as I understood, the media buttons refers to actual physical buttons on a device (if existent) or for ex. on a Bluetooth headset. Volume keys are obvious.
When comes to the media controllers I think I managed to figure out that it refers to a particular View, the FrameView that has buttons for play/pause as well as skip next/previous etc. But what exactly are the transport controls then, where can/should they be used and how are they implemented in code?
Thank you in advance for any answers!

For future reference for myself or others, the MediaPlayer is essentially like the internal mechanism of a stereo player, and directly controls the sound playback, "internally". You do have functions on it, like play(), pause(), skipToNext(), etc.
When you want to create a music application, you'd normally implement a client-service model, with the app interface being your client (in the main UI thread), and the service will play the music (also in the background). What Google did was implement a client-service model specifically for music and video, the MediaSession(Compat). It has its own type of service, session states (when you want to have Notifications, Android Wear or Auto, so they know if the app is playing or paused, can get the track information and album image, etc. so that everything is well synchronized on all these platforms), and the MediaController and its callbacks that need to be implemented for different functionality.
One of its options is to getTransportControls(), which essentially are a high-level representation of what happens when you'd press the play(), pause(), etc. buttons on that stereo player mentioned earlier. For each of these you #Override their respective onPlay(), onPause() callback methods to do the exact functionality you want on that MediaPlayer object that controls your music. In here is where you'd then call the MediaPlayer.play() functions, as well as setting the playback state of MediaSession, possibly update the notification, etc.
As for the others, volume keys are indeed the volume keys, and the Media Buttons are indeed physical buttons you might have (Bluetooth headset, earphones with playback buttons, etc.) that you can set up your app so it recognizes and makes use of them.
For whoever is interested to see this better in action, below is the repo of my app project. Although far from a fully-featured, bug-free app, it does implement the MediaSession and the Service, plus notifications and MediaButton controls pretty much perfectly.
https://github.com/RockBoyEmy/GESMediaPlayer
thanks also to #pantos27 for his help

Related

Use MediaButton to control foreground app (no audio) with audio in background

I want to use the media buttons to control a foreground app, but not for audio use.
So the goal is to detect button clicks to do certain things in the app.
I can achieve that by using MediaSession and the MediaButtonReceiver. see here
The problem is that when the app is used, often users play music in the background, so the audio focus of the background app takes over the MediaSession and i cannot control my app anymore.
Is there a way to achieve that? Directly listening for button clicks with onKeyDown does not seem to work.
sadly there is no way for two active MediaSessions at the same time. if another app is playing music and run MediaSession then yours doesn't have focus and isn't active... this is made for purpose - better UX - only one "player" app can play music (or video). if this wouldn't work like that and you could play music by few apps at once then how should work media button on e.g. headphones? pasuing/resuming all players? this is just not user-friendly, so Android team introduced MediaSession pattern with option for calling "focus on me now" by any app, but then another app/MediaSession pauses and doesn't get any inputs (this active session does)
if you need physical buttons presses then onKeyDown should work (inside Activity or eventually using AccessibilityService, which would work "globally" in whole system). if you need some on-screen notification buttons presses then just make custom layout for your notification with as much buttons as you like, even styled as a player
note that in Android 11 active MediaSessions notification is stickied to top of notification section when you drop down status bar. your custom notification will be somewhere below between all others (you can manipulate position a bit using priority param for notification/channel)

How to get rid of "media playing" notification shown by Chrome on Android?

My HTML5 game has some background music that uses Howler.js in "html5" mode, which apparently triggers Chrome for Android's media playback notifications. This means a notification appears while the user has my game open in any tab:
The game is a good citizen and pauses the music while the tab is not in focus, so there is no need for this notification. It's even actively confusing, because the user can pause and resume the game's background music without being in the game. But I can't find a way to get rid of the notification.
I tried calling stop() instead of pause() or mute() on the music object, but this doesn't remove the notification.
Looking a bit deeper, I discovered the experimental MediaSession API (W3C draft) which supposedly can be used to control the notification. But, if I understand correctly, it offers no way to disable it outright!
I tried this at the start of my application:
if (typeof navigator.mediaSession == 'object') {
navigator.mediaSession.playbackState = 'none'
}
However, this only sets the declared playback state (in spec terminology). And setting that to 'none' has no effect:
The actual playback state is computed in the following way:
If the declared playback state is "playing", return "playing".
Otherwise, return the guessed playback state.
And the guessed playback state is something I have no control over; it's derived by the browser based on the state of <audio> elements on the page.
Is there a possibility that I'm overlooking, or is this just an oversight in the current MediaSession specification?
This may be what you are looking for.
If you can make the music loop within 5 second (like in NES days), notification won't show it said.
Else, use Web Audio API without "audio" element. So, use a stream.
Otherwise, "Dismiss media notifications with audio.src = ''." reference

Cast icon when changing volume via HW buttons

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

Release AudioRecord android when other app request for recording

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

Any guidelines for handling the Headset and Bluetooth AVRC transport controls in Android 2.2

I am trying to figure out what is the correct (new) approach for handling the Intent.ACTION_MEDIA_BUTTON in Froyo. In pre 2.2 days we had to register a BroadcastReceiver (either permanently or at run-time) and the Media Button events would arrive, as long as no other application intercepts them and aborts the broadcast.
Froyo seems to still somewhat support that model (at least for the wired headset), but it also introduces the registerMediaButtonEventReceiver, and unregisterMediaButtonEventReceiver methods that seem to control the "transport focus" between applications.
During my experiments, using registerMediaButtonEventReceiver does cause both the bluetooth and the wired headset button presses to be routed to the application's broadcast receiver (the app gets the "transport focus"), but it looks like any change in the audio routing (for example unplugging the headset) shits the focus back to the default media player.
What is the logic behind the implementation in Android 2.2? What is correct way to handle transport controls? Do we have to detect the change in the audio routing and try to re-gain the focus?
This is an issue that any 3rd party media player on the Android platform has to deal with, so I hope that somebody (probably a Google Engineer) can provide some guidelines that we can all follow. Having a standard approach may make headset button controls a bit more predictable for the end users.
Stefan
Google has a detailed blog post on implementing the newer 2.2 AudioManager media button event receiver while maintaining backwards compatibility with older devices.
http://android-developers.blogspot.com/2010/06/allowing-applications-to-play-nicer.html
After some experiments, I was able to get a working solution with the new transport and audio focus infrastructure in Android 2.2.
What I end up doing is requesting both the Audio Focus (using AudioManager.requestAudioFocus) and the Trasport Focus (using AudioManagter.registerMediaButtonEventReceiver) every time my application starts playback.
requestAudioFocus takes a callback that is called when the audio focus is taken away from you (for example the internal player starts a playback). In my case I just pause the playback in my application if the focus is taken permanently. Same callback also now tells you that the focus is taken only temporary (for example the Nav system is talking) so you can "duck" your playback - lower the volume or pause and resume after it is done talking.
The only issue remaining is that the built in Music Player takes the transport focus every time you connect a Bluetooth headset. This has the effect where the first press of the Play button on the headset after connecting it, always starts the playback in the default Music Player.
There is probably a way to detect the headset connection and "hijack" the transport focus. In my case, I decided to not "fight" the default player, and get the transport focus back when the user manually starts the playback in my application.
If somebody has more insight or knows of a better way of handling the transport/audio focus, please share it.
I also have this same issue with the media button registration.
Periodically the Android returns the media button registration to the default music player. I have not been able to figure out why. This can happen while may application is actively playing as well as while my application playback is paused.
After a number of users complained that their Bluetooth pause and play control buttons would periodically stop working to control my application, I implemented code that re-registers my application by calling registerMediaButtonEventReceiver every 2 seconds. This allows me to get the button registration back and for the most part avoids the time window where where the user presses a Bluetooth media button and the default media player ends up responding.
My application is holding the audio focus during this entire time period, but still loses the Bluetooth button events periodically while it has audio focus. My application always unregisters the media button event receiver if it is called with a notification that it is losing the audio focus, and then registers again if it is later called when a temporary audio focus loss returns the audio focus.
The work around to keep the 2 second timer running and re-registering has been working, but I would like to get rid of this 2 second timer if someone has found a work around for the media button registration periodically switching back to the default media player.

Categories

Resources