5,606,297 Members 32,688 Now Online
XDA Developers Android and Mobile Development Forum

[LIBRARY / GUIDE] Remote Metadata Provider

Tip us?
 
Dr.Alexander_Breen
Old
(Last edited by Dr.Alexander_Breen; 9th September 2013 at 10:32 AM.)
#1  
Dr.Alexander_Breen's Avatar
Senior Member - OP
Thanks Meter 493
Posts: 333
Join Date: Jun 2012
Location: Moscow

 
DONATE TO ME
Default [LIBRARY / GUIDE] Remote Metadata Provider

Hello, my fellow XDAers.

Recentlly I've created a guide about how to implement your all lockscreen-like music controls.

However, the problem was that this way of implementing your remote music controls(let's call it this way, okay?) was extremely counter-intuitive.
Starting from methods which have bunch of setSomething but no getSomething and ending with weird Handler.Callback required.

So, in order to ease this process, I've created small library.
For the sake of simplicity, it's distributed as a JAR file, not as a library project.

Why?
The main reason is:
Quote:
The library relies heavily on hidden interface IRemoteControlDisplay and hidden methods of AudioManager.
Accessing IRemoteControlDisplay even with reflection is IMPOSSIBLE, because that will require some tool to modify classes at runtime.
This is way too complex, so instead I use a modified android.jar with hidden classes exposed.
So, in order to use this library, you would have to download modified android.jar(or create it yourself), and create modified android platform for Eclipse...
Doesn't sound like fun, does it?
So, to prevent this, I've packaged my project as a library JAR.
That doesn't mean that my library is closed-source. It's completely open-sourced, and is available in form of ZIP archive or GitHub repository.
Also, it is licensed under Apache 2.0, so that means you can use it in any of your projects, commercial or not.
Just remember that instead of buying alcohol and junk food you can send me some little donation

Okay, end of introduction.

Now, off to the guide and download links.

The name of this library is:
Remote Metadata Provider


This library allows you to create your own remote media controls, which will act the same as lockscreen controls.
This is achieved by usage of some Listeners and class called RemoteMetadataProvider.

1. Reference:

Class: RemoteMetadataProvider

Code:
static synchronized RemoteMetadataProvider getInstance(Context context)

This method returns an instance of RemoteMetadataProvider.

Context is required to fetch instance of AudioManager and to send media button commands.
Also, it is used to retrieve launch Intent for current active client(player).

Please note that calling to this method from incorrect API version will result in RuntimeException. 
remote-metadata-provider.jar is for API 17 and lower.
remote-metadata-provider-v18.jar is for API 18.
Code:
void acquireRemoteControls()

This method makes current RemoteMetadataProvider active, so you will receive metadata updates.
You MUST call this method when View displaying your metadata becomes visible to the user(the sentence above explains, why).
Please note that only one RemoteMetadataProvider can be active at time.
Code:
void dropRemoteControls(boolean destroyRemoteControls)

This method makes current RemoteMetadataProvider inactive.
You won't be receiving further metadata updates, and most likely you will lose control of current client.
This method MUST be called whenever your view displaying metadata becomes invisible to the user.
If you won't do so, then system can lose it's remote media controls or it can interfere with other apps.

Boolean defines, whenever remote media controls should be destroyed or not. If you have some problems with artwork not displaying in your app after dropping remote controls/acquiring it, when set this parameter to true, otherwise use false(it will save memory and time).

My tests shown that it will not interfere, but it's better to be safe and call this method.
Code:
Intent getCurrentClientIntent()

This will return launch Intent for current player or null if there is no active clients.
It throws NameNotFoundException in case client package wasn't found. 
This exception should not happen under normal circumstances - if it was thrown, then probably something bad happened with the system itself.
Code:
PendingIntent getCurrentClientPendingIntent()

Returns PendingIntent of current client or null if there is no current client.
This method isn't used generally, but you can(for example) save PendingIntents of multiple players to re-use them in a future or to prevent fast client loss.
Code:
Looper getLooper()

Returns user-defined Looper which is used if you are processing metadata in some special thread or null if default Looper is used.
Code:
OnArtworkChangeListener getOnArtworkChangeListener()

Returns callback to be invoked when artwork has to be updated or null if there is no such callback..
Code:
OnMetadataChangeListener getOnMetadataChangeListener()

Returns callback to be invoked when metadata has to be updated or null if there is no such callback.
Code:
OnPlaybackStateChangeListener getOnPlaybackStateChangeListener()

Returns callback to be invoked when playback state has changed.
Code:
OnRemoteControlFeaturesChangeListener getOnRemoteControlFlagsChangeListener()

Returns callback to be invoked when remote control features has changed.

Code:
boolean isClientActive()

Returns true if there is active client which can send metadata and receive media commands and false otherwise.
Code:
void removeLooper()

Tells the RemoteMetadataProvider to recreate Handler without Looper(if there was one) on next acquireRemoteControls() call.
Code:
void sendBroadcastMediaCommand(int keyCode)

Sends media button input event with specified keycode(use KeyEvent class to get keycodes from there) in form of Broadcast message. 
In most cases it will fail, but some players like Neutron Player and Poweramp are able to receive this broadcast message.
Do not use this method if you have active clients.
Use this method only if you don't have any active clients and you want to try to start client with broadcast message.
Code:
void sendBroadcastMediaCommand(MediaCommand command) 

The same as the method above, but you specify command as a MediaCommand enum.
Code:
boolean sendMediaCommand(int keyCode)

This command will send media button input event to current active client.
It will return true if command was successfully delivery or false if it failed.
For example, if there is no active clients, it will return false.
Code:
boolean sendMediaCommand(MediaCommand command)

The same as the method above, but instead it uses MediaCommand enum to specify media button input event.
Code:
void setCurrentClientPendingIntent(PendingIntent pintent)

Sets current client PendingIntent to one specified by you. Do not use it unless you received PendingIntent via getCurrentClientPendingIntent().
Code:
void setLooper(Looper looper)

Sets Looper for processing the metadata receiving in another thread. Call acquireRemoteControls() to start using this Looper.
Code:
void setOnArtworkChangeListener(OnArtworkChangeListener l)

Register a callback to be invoked when artwork should be updated.
Code:
void setOnMetadataChangeListener(OnMetadataChangeListener l)

Register a callback to be invoked when metadata should be updated.
Code:
setOnPlaybackStateChangeListener(OnPlaybackStateChangeListener l)

Register a callback to be invoked when playback state should be updated.
Code:
setOnRemoteControlFeaturesChangeListener(OnRemoteControlFeaturesChangeListener l)

Register a callback to be invoked when remote control features should be updated.
Interface: OnArtworkChangeListener
Code:
void onArtworkChanged(Bitmap artwork)

Called when artwork of current song album was updated.
artwork parameter is a Bitmap containing current artwork. May be null if it wasn't specified by player.
Please note that previous Bitmap is recycled after artwork update! So don't forget to use Bitmap#isRecycled() if you're saving album arts somehow.
Interface: OnMetadataChangeListener

Code:
void onMetadataChanged(String artist, String title, String album, String albumArtist, long duration)

Called when remote metadata was updated.

Parameter artist is the artist of current song. May be null if wasn't specified by player. Some players use albumArtist parameter instead.
Parameter title is the title of current song.  May be null if wasn't specified by player.
Parameter album is the current song album title.  May be null if wasn't specified by player.
Parameter albumArtist is the current song album artist.  May be null if wasn't specified by player. Some players(for example, PowerAmp) use this parameter instead of artist parameter.
Parameter duration is the song duration in milliseconds.
Interface: OnPlaybackStateChangeListener
Code:
void onPlaybackStateChanged(PlayState playbackState)

Called when playback state was changed. For example, this method will be called with parameter PlayState.PAUSED
when playback is paused.
Possible values of playbackState parameters are listed in enum class PlayState.
Interface: OnRemoteControlFeaturesChangeListener
Code:
void onFeaturesChanged(List<RemoteControlFeature> usesFeatures)

Called when information about player was changed.
usesFeatures is a list containing different RemoteControlFeature enums. This list describes, to which
buttons the player will respond correctly.
For example, if this list contains RemoteControlFeature.USES_REWIND, then the player would respond to Rewind command.
Enum: MediaCommand

Code:
NEXT - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to jump to next track.

PREVIOUS  - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to jump to previous track.

PLAY - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to start playback. Usually it is treated the same as PLAY_PAUSE.

PAUSE - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to pause playback. Usually it is treated the same as PLAY_PAUSE.

PLAY_PAUSE - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to pause the track if it was playing and to start it if it was paused.

REWIND - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to rewind the song.

FAST_FORWARD - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to fast forward the song.

STOP - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to stop playback.
Enum: RemoteControlFeature

Code:
USES_FAST_FORWARD - indicates that player makes use of Fast Forward button.
USES_NEXT - indicates that player makes use of Next button.
USES_PAUSE - indicates that player makes use of Pause button.
USES_PLAY - indicates that player makes use of Play button.
USES_PLAY_PAUSE - indicates that player makes use of Play button.
USES_PREVIOUS - indicates that player makes use of Previous button.
USES_REWIND - indicates that player makes use of Rewind button.
USES_STOP - indicates that player makes use of Stop button.
Enum: PlayState
Code:
BUFFERING - the music is buffering right now and should start playing soon.
ERROR - some error occured. We should not except the music to start playing.
FAST_FORWARDING - the music is being fast forwarded.
PAUSED - the music is paused.
PLAYING - the music is playing.
REWINDING - the music is being rewinded.
SKIPPING_BACKWARDS - the music is being skipped backwards.
SKIPPING_FORWARDS - the music is being skipped forwards.
STOPPED - the music is stopped.
1a. Reference for API 18 version of library:

Only the differences will be listed here. If method/interface/enum is not listed here, then it's the same as in pre-API 18 version of library.

Class: RemoteMetadataProvider

Code:
void acquireRemoteControls()

This method makes current RemoteMetadataProvider active, so you will receive metadata updates.
You MUST call this method when View displaying your metadata becomes visible to the user(the sentence above explains, why).
Multiple RemoteMetadataProviders can be active at the time.
Use this method if you DO NOT NEED artwork updates.
Code:
void acquireRemoteControls(int maxWidth, int maxHeight)

This method makes current RemoteMetadataProvider active, so you will receive metadata updates.
You MUST call this method when View displaying your metadata becomes visible to the user(the sentence above explains, why).
Multiple RemoteMetadataProviders can be active at the time.
Use this method if you NEED artwork updates. 
Parameter maxWidth is maximum width of Bitmap artwork which you will receive.
Parameter maxHeight is maximum height of Bitmap artwork which you will receive.
Code:
void setPlaybackPositionSyncEnabled(boolean isEnabled)

Sets state of playback position update. If you pass true to this method, then you will receive position updates by OnPlaybackStateChangeListener callback. If you set false, you will not receive position updates.
Please note that this method have to be called ONLY after calling acquireRemoteControls() method, or it will fail.
Interface: OnPlaybackStateChangeListener

Code:
onPlaybackStateChanged(PlayState playbackState, long playbackPosition, float speed)

Called when playback state, playback speed or playback position was changed.
For example, this method will be called with parameter PlayState.PAUSED
when playback is paused.

Possible values of playbackState parameters are listed in enum class PlayState.

Parameter playbackPosition is current playbackPosition is playback position in ms.
Please note that for this parameter to be updated you have to call RemoteMetadataProvider#setPlaybackPositionSyncEnabled(true) first.

Parameter speed is current playback speed. Normal playback speed is 1.0f, 2x is 2.0f etc.
Enum: RemoteMetadataFeature
Code:
Everything is same, except for this three new parameters:
USES_POSITIONING - it indicates that current player will send you position updates.
USES_WRITABLE_POSITIONING - currently not used, added for future. Indicates that player should respond to remote position update.
USES_READABLE_POSITIONING - probably not used too. Indicates that player should tell you it's playback position.
2. How to use:

Usage of my library is extremely simple. First of all, add my library to your project as an external JAR.

Then do the following steps:
1) Get the instance of RemoteMetadataProvider with RemoteMetadataProvider.getInstance(context). Remember, this is a singleton, so all getInstance calls will return the same instance. To save time, save it in field variable, let's call it mProvider.

2) Implement necessary listeners and register them. For example, you want to receive metadata updates and you want to show them in TextViews. Then you can use following code.
Code:
mProvider.setOnMetadataChangeListener(new OnMetadataChangeListener() {
              @override
	public void onMetadataChanged(String artist, String title, String album, String albumArtist, long duration) {
		mArtistTextView.setText("ARTIST: "+artist);
		mTitleTextView.setText("TITLE: "+title);
		mAlbumTextView.setText("ALBUM: "+album);
		mAlbumArtistTextView.setText("ALBUM ARTIST: "+albumArtist);
		mDurationTextView.setText("DURATION: "+(duration/1000)+"s");
	}
});
Other listeners are registered in same manner. Read reference to get the details.

3) When your view with current metadata is displayed, call mProvider.acquireRemoteControls(). For example, if you're displaying metadata in Activity, you should call this method in your onResume() method.

3) When your view is hidden, you should call mProvider.dropRemoteControls(boolean). Generally, the parameter should be false, but there are some cases when artwork isn't being displayed after calling dropRemoteControls() and then acquireRemoteControls(). So, if there are problems with artwork, use true as a parameter.

4) To tell player to do something with playback, use mProvider.sendMediaCommand(MediaCommand).
This method will return true if the command was delivered successfully and false otherwise.
For example, you can use following code to make button with id "next" work as a Next button:
Code:
findViewById(R.id.next).setOnClickListener(new OnClickListener() {
              @override
	public void onClick(View v) {
		if(!mProvider.sendMediaCommand(MediaCommand.NEXT)) {
			Toast.makeText(getApplicationContext(), "Failed to send NEXT_EVENT", Toast.LENGTH_SHORT).show();
		}
	}	
});
This will send NEXT command to player, or will display toast message with error text if there is no active player.

5) If there is no current player, but you know that there is some player which can receive Broadcast media event, you can use
mProvider.sendBroadcastMediaCommand(MediaCommand).

For example, following code can be used to make button with id "next" send Broadcast command on long click.
Code:
findViewById(R.id.next).setOnLongClickListener(new OnLongClickListener() {
              @override
	public boolean onLongClick(View v) {
		mProvider.sendBroadcastMediaCommand(MediaCommand.NEXT);
		return true;
	}
});

Basically, that's all you need.

3. Licensing
This project is licensed under Apache 2.0 license.

4. Source code
GitHub link: https://github.com/DrBreen/RemoteMetadataProvider
Source codes for library and test application are available at the end of the post.

5. Bugs
-On HTC Sense the system will lose lockscreen controls after calling RemoteMetadataProvider#acquireRemoteControls().
This is due to how the lockscreen is being initialized on HTC Sense, and it can't be fixed unless I'll do deep investigation of decompiled source code and write Xposed module which will somehow re-register original system metadata receiver.


Files with "v18" suffix are for Android 4.3. Without this suffix - for Android 4.2.2 and lower.
Please do not cross-use this libraries, or you will get RuntimeException(in getInstance() method) or AbstractMethodError(if you somehow acquire instance of the RemoteMetadataProvider without calling getInstance()).
The Following 13 Users Say Thank You to Dr.Alexander_Breen For This Useful Post: [ Click to Expand ]
 
Dr.Alexander_Breen
Old
#3  
Dr.Alexander_Breen's Avatar
Senior Member - OP
Thanks Meter 493
Posts: 333
Join Date: Jun 2012
Location: Moscow

 
DONATE TO ME
Added library and source codes for Android 4.3.
The Following 3 Users Say Thank You to Dr.Alexander_Breen For This Useful Post: [ Click to Expand ]
 
spadival
Old
#4  
Senior Member
Thanks Meter 67
Posts: 207
Join Date: May 2005
Location: Sydney/Melbourne

 
DONATE TO ME
I an getting the following error in the "exported" version of the apk, but not in the debug compiled version.

Quote:
09-25 07:25:56.112: E/JavaBinder(24862): java.lang.AbstractMethodError: abstract method not implemented
09-25 07:25:56.112: E/JavaBinder(24862): at android.media.IRemoteControlDisplay$Stub.setCurren tClientId(IRemoteControlDisplay.java)
09-25 07:25:56.112: E/JavaBinder(24862): at android.media.IRemoteControlDisplay$Stub.onTransac t(IRemoteControlDisplay.java:65)
09-25 07:25:56.112: E/JavaBinder(24862): at android.os.Binder.execTransact(Binder.java:367)
09-25 07:25:56.112: E/JavaBinder(24862): at dalvik.system.NativeStart.run(Native Method)
09-25 07:25:56.112: W/dalvikvm(24862): threadid=8: thread exiting with uncaught exception (group=0x41d3b2a0)
09-25 07:25:56.112: E/android.os.Debug(2243): !@Dumpstate > dumpstate -k -t -z -d -o /data/log/dumpstate_app_error
09-25 07:25:56.112: E/AndroidRuntime(24862): FATAL EXCEPTION: Binder_1
09-25 07:25:56.112: E/AndroidRuntime(24862): java.lang.AbstractMethodError: abstract method not implemented
09-25 07:25:56.112: E/AndroidRuntime(24862): at android.media.IRemoteControlDisplay$Stub.setCurren tClientId(IRemoteControlDisplay.java)
09-25 07:25:56.112: E/AndroidRuntime(24862): at android.media.IRemoteControlDisplay$Stub.onTransac t(IRemoteControlDisplay.java:65)
09-25 07:25:56.112: E/AndroidRuntime(24862): at android.os.Binder.execTransact(Binder.java:367)
09-25 07:25:56.112: E/AndroidRuntime(24862): at dalvik.system.NativeStart.run(Native Method)
I have added the following lines in proguard to make the warnings go away.

Quote:
-dontwarn android.media.IRemoteControlDisplay$Stub
-dontwarn android.media.IRemoteControlDisplay
-dontwarn android.media.AudioManager
Any idea why this is happening?
 
Dr.Alexander_Breen
Old
#5  
Dr.Alexander_Breen's Avatar
Senior Member - OP
Thanks Meter 493
Posts: 333
Join Date: Jun 2012
Location: Moscow

 
DONATE TO ME
Quote:
Originally Posted by spadival View Post
I an getting the following error in the "exported" version of the apk, but not in the debug compiled version.



I have added the following lines in proguard to make the warnings go away.



Any idea why this is happening?
I think that's because of wrong versioning. It looks like you somehow use the 4.2.2 version for 4.3 or the other way.
 
WisdomWolf
Old
#6  
Senior Member
Thanks Meter 43
Posts: 251
Join Date: Sep 2006
Quote:
Originally Posted by Dr.Alexander_Breen View Post

Files with "v18" suffix are for Android 4.3. Without this suffix - for Android 4.2.2 and lower.
Please do not cross-use this libraries, or you will get RuntimeException(in getInstance() method) or AbstractMethodError(if you somehow acquire instance of the RemoteMetadataProvider without calling getInstance()).
So I take it this means that if I wanted to implement this in an application designed to run on API 14+ (including KitKat and beyond) I'll have to use the guide and do it manually. Am I correct?
 
kvnx1337
Old
(Last edited by kvnx1337; 23rd November 2013 at 11:26 PM.)
#7  
Junior Member
Thanks Meter 2
Posts: 7
Join Date: Aug 2012
At first, thank you for this awesome Lib!

It is working very well on 4.2.2 CM.

But on 4.4 (Nexus 7 2013) and 4.1.2 (S3 stock) I am not able to read the playback state. Any idea?

Edit: The next and previous button on 4.4 Nex and 4.1.2 s3 is working well. It is just the playback state..

Edit2: On your floating music widget it is not working, too :/

Edit3: setOnPlaybackStateChangeListener and setOnMetadataChangeListener seem not to work with 4.4

Thanks
The Following User Says Thank You to kvnx1337 For This Useful Post: [ Click to Expand ]
 
Dr.Alexander_Breen
Old
#8  
Dr.Alexander_Breen's Avatar
Senior Member - OP
Thanks Meter 493
Posts: 333
Join Date: Jun 2012
Location: Moscow

 
DONATE TO ME
Quote:
Originally Posted by WisdomWolf View Post
So I take it this means that if I wanted to implement this in an application designed to run on API 14+ (including KitKat and beyond) I'll have to use the guide and do it manually. Am I correct?
Well, on KitKat there is a class called RemoteController, which has the same functionality, so you does not need to use this library on API 19+.

v18 library is designed to run only on API 18.
"Normal" version should run fine of API 14, 15, 16, 17.
 
kvnx1337
Old
(Last edited by kvnx1337; 24th November 2013 at 04:50 PM.)
#9  
Junior Member
Thanks Meter 2
Posts: 7
Join Date: Aug 2012
Okay. Thanks for the reply.

I used now intentfilter with the ending "playstatechanged". With this action the music apps sends the extra "playing" ture/false.

I tested it with s3 music player and google play music on nexus 7. Worked fine. Just the huawei music player is not working.

Edit: Your apps just need an update right now to make them working with 4.3 / 4.4?
On 4.3 your apps should work but on samsung s3 with 4.3 it is not working well with the stock music player. Just as information

Anyway good library
 
alexandr1us
Old
#10  
Junior Member
Thanks Meter 0
Posts: 4
Join Date: Dec 2011
Default KitKat

Anyone please give tutorial how does RemoteController works on kitkat :/

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes