Attend XDA's Second Annual Developer Conference, XDA:DevCon 2014!
5,738,303 Members 52,191 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 898
Posts: 415
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:
Select 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:
Select 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:
Select 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:
Select 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:
Select 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:
Select 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:
Select Code
OnArtworkChangeListener getOnArtworkChangeListener()

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

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

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

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

Code:
Select Code
boolean isClientActive()

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

Tells the RemoteMetadataProvider to recreate Handler without Looper(if there was one) on next acquireRemoteControls() call.
Code:
Select 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:
Select Code
void sendBroadcastMediaCommand(MediaCommand command) 

The same as the method above, but you specify command as a MediaCommand enum.
Code:
Select 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:
Select Code
boolean sendMediaCommand(MediaCommand command)

The same as the method above, but instead it uses MediaCommand enum to specify media button input event.
Code:
Select 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:
Select Code
void setLooper(Looper looper)

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

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

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

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

Register a callback to be invoked when remote control features should be updated.
Interface: OnArtworkChangeListener
Code:
Select 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:
Select 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:
Select 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:
Select 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:
Select 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:
Select 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:
Select 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:
Select 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:
Select 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:
Select 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:
Select 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:
Select 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:
Select 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:
Select 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:
Select 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 14 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 898
Posts: 415
Join Date: Jun 2012
Location: Moscow

 
DONATE TO ME
Added library and source codes for Android 4.3.
The Following 4 Users Say Thank You to Dr.Alexander_Breen For This Useful Post: [ Click to Expand ]
 
spadival
Old
#4  
Senior Member
Thanks Meter 82
Posts: 222
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 898
Posts: 415
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 51
Posts: 271
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 898
Posts: 415
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: 8
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