写一个APP控制第三方播放器播放,以及获取正在播放的歌曲信息
2017-11-30 09:51
786 查看
最近遇到这么一个需求,就是在自己的应用中控制第三方播放器播放,以及获取正在播放的歌曲信息,包括名字,歌手,专辑,显示出来。一开始觉得很简单,但实际上遇到了不少的麻烦,最终实现了两种方案,读者可根据自己需要选择。 先讲如何控制第三方播放器播放的问题,可以使用RemoteControlService,里面有相关的方法,但究其原理,还是模拟耳机信号来控制。我们知道当耳机按下一曲时,无论是系统自带播放器还是第三方播放器都会做出相应的反应,所以这里我直接模拟发出耳机的keycode,就可以控制播放、暂停、上一曲、下一曲了,这个实现得比较轻松,代码如下:
public static void sendKeyEvent(final int KeyCode) { new Thread() { //不可在主线程中调用 public void run() { try { Instrumentation inst = new Instrumentation(); inst.sendKeyDownUpSync(KeyCode); } catch (Exception e) { e.printStackTrace(); } } }.start(); }然后调用这个方法:sendKeyEvent(85); //暂停sendKeyEvent(87); //下一曲sendKeyEvent(88); //上一曲 接着说如何获取正在播放的歌曲信息的问题,第一个方案是使用RemoteControlService,继承NotificationListenerService,实现RemoteController.OnClientUpdateListener接口,原理就是播放器在切换歌曲的时候,会给系统发一个Notification,告诉系统歌曲切换了,而其中就包含了歌曲的信息,可以拦截Notification来获取到里面的信息,但是有个弊端,你需要开启一个Service来监听,而且需要系统授权,这是最大的问题,如果用户不授权,你将无法获取信息,非常被动,但这个方法几乎能获取到所有第三方播放器的歌曲信息(酷狗不行,不知道为何,可能他发出的Notification里没有包含相关信息。而网易云我是监听不到Notification的,但却获取到了,非常奇怪)。代码如下:import android.annotation.TargetApi;import android.content.Intent;import android.media.AudioManager;import android.media.RemoteController;import android.os.Binder;import android.os.Build;import android.os.IBinder;import android.os.SystemClock;import android.service.notification.NotificationListenerService;import android.service.notification.StatusBarNotification;import android.util.Log;import android.view.KeyEvent;/*** Created by wangzhaopeng on 2017/11/20.*/@TargetApi(Build.VERSION_CODES.KITKAT)public class RemoteControlService extends NotificationListenerService implements RemoteController.OnClientUpdateListener {public RemoteController remoteController;private RemoteController.OnClientUpdateListener mExternalClientUpdateListener;private IBinder mBinder = new RCBinder();@Overridepublic void onCreate() {registerRemoteController();4000}@Overridepublic IBinder onBind(Intent intent) {if (intent.getAction().equals("com.example.wangzhaopeng.music.BIND_RC_CONTROL_SERVICE")) {//这里要根据实际进行替换return mBinder;} else {return super.onBind(intent);}}@Overridepublic void onNotificationPosted(StatusBarNotification sbn) {// Log.e(TAG, "onNotificationPosted...");if (sbn.getPackageName().contains("music")){Log.e(TAG, "音乐软件正在播放...");Log.e(TAG, sbn.getPackageName());Log.e(TAG, sbn.getNotification().toString());}Log.e("正在播放", sbn.getPackageName());}@Overridepublic void onNotificationRemoved(StatusBarNotification sbn) {Log.e(TAG, "onNotificationRemoved...");}public void registerRemoteController() {remoteController = new RemoteController(this, this);boolean registered;try {registered = ((AudioManager) getSystemService(AUDIO_SERVICE)).registerRemoteController(remoteController);} catch (NullPointerException e) {registered = false;}if (registered) {try {remoteController.setArtworkConfiguration(100,100);remoteController.setSynchronizationMode(RemoteController.POSITION_SYNCHRONIZATION_CHECK);} catch (IllegalArgumentException e) {e.printStackTrace();}}}public void setClientUpdateListener(RemoteController.OnClientUpdateListener listener) {mExternalClientUpdateListener = listener;}@Overridepublic void onClientChange(boolean clearing) {if (mExternalClientUpdateListener != null) {mExternalClientUpdateListener.onClientChange(clearing);}}@Overridepublic void onClientPlaybackStateUpdate(int state) {if (mExternalClientUpdateListener != null) {mExternalClientUpdateListener.onClientPlaybackStateUpdate(state);}}@Overridepublic void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed) {if (mExternalClientUpdateListener != null) {mExternalClientUpdateListener.onClientPlaybackStateUpdate(state, stateChangeTimeMs, currentPosMs, speed);}}@Overridepublic void onClientTransportControlUpdate(int transportControlFlags) {if (mExternalClientUpdateListener != null) {mExternalClientUpdateListener.onClientTransportControlUpdate(transportControlFlags);}}@Overridepublic void onClientMetadataUpdate(RemoteController.MetadataEditor metadataEditor) {if (mExternalClientUpdateListener != null) {mExternalClientUpdateListener.onClientMetadataUpdate(metadataEditor);}}public boolean sendMusicKeyEvent(int keyCode) {if (remoteController != null) {KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);boolean down = remoteController.sendMediaKeyEvent(keyEvent);keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);boolean up = remoteController.sendMediaKeyEvent(keyEvent);return down && up;} else {long eventTime = SystemClock.uptimeMillis();KeyEvent key = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, keyCode, 0);dispatchMediaKeyToAudioService(key);dispatchMediaKeyToAudioService(KeyEvent.changeAction(key, KeyEvent.ACTION_UP));}return false;}private void dispatchMediaKeyToAudioService(KeyEvent event) {AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);if (audioManager != null) {try {audioManager.dispatchMediaKeyEvent(event);} catch (Exception e) {e.printStackTrace();}}}public class RCBinder extends Binder {public RemoteControlService getService() {return RemoteControlService.this;}}} 关于歌曲的信息就在上面的metadataEditor中,接着在Activity中实现OnClientUpdateListener方法来获取:
RemoteController.OnClientUpdateListener mExternalClientUpdateListener = new RemoteController.OnClientUpdateListener() { @Override public void onClientChange(boolean clearing) { // Log.e(TAG, "onClientChange()..."); } @Override public void onClientPlaybackStateUpdate(int state) { // Log.e(TAG, "onClientPlaybackStateUpdate()..."); } @Override public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed) { // Log.e(TAG, "onClientPlaybackStateUpdate()..."); } @Override public void onClientTransportControlUpdate(int transportControlFlags) { // Log.e(TAG, "onClientTransportControlUpdate()..."); } @Override public void onClientMetadataUpdate(RemoteController.MetadataEditor metadataEditor) { String artist = metadataEditor. getString(MediaMetadataRetriever.METADATA_KEY_ARTIST, "null"); String album = metadataEditor. getString(MediaMetadataRetriever.METADATA_KEY_ALBUM, "null"); String title = metadataEditor. getString(MediaMetadataRetriever.METADATA_KEY_TITLE, "null"); Long duration = metadataEditor. getLong(MediaMetadataRetriever.METADATA_KEY_DURATION, -1); Bitmap defaultCover = BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_menu_compass); Bitmap bitmap = metadataEditor. getBitmap(RemoteController.MetadataEditor.BITMAP_KEY_ARTWORK, defaultCover);
//这里便获取到信息了,下面只是我设定给组件的方法,可以自己调用自己的设定方法,这里只要获取到结果就可以了 setCoverImage(bitmap); setContentString(artist); setTitleString(title); malbum.setText(album); Log.e("结果为:", "artist:" + artist + "album:" + album + "title:" + title + "duration:" + duration); } };不要忘记在Androidmanifest中声明service:
<service android:name=".RemoteControlService" android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" > <intent-filter> <action android:name="com.example.wangzhaopeng.music.BIND_RC_CONTROL_SERVICE" /> <action android:name="android.service.notification.NotificationListenerService" /> </intent-filter> </service>至此,该方案就能够获取到正在播放的音乐信息了,但要注意到,只有在歌曲状态发生改变时才能获取到Notification,并且需要用户授权。那有没有不需要用户授权的方法呢?答案是肯定的,这里提出第二种方案。我注意到播放器在播放音乐的时候,会发出一个广播(可能是让蓝牙设备接收歌曲信息的),只要我们找到这个广播的Action,写一个广播接收者,就可以收到播放器发出的广播了,也就能获取到广播中的歌曲信息。但是怎么找这个Action呢?答案是反编译APK,事实证明这种方法实在是费尽,我反编译了数十款播放器,有的做了代码混淆,有的根本找不到发出广播的位置,有的是发出的广播中没有歌曲信息,也就是说这种方法也有着它自身很大的局限性,但也可以尝试下吧!系统自带的播放器发出的是com.android.music.metachanged,QQ音乐也是这个,所以这两个很容易就获取到了,kugou的是com.kugou.android.music.metachanged,但很遗憾里面没信息,其他的国内的基本都找不到发广播的位置了,但我要做的主要是国外的播放器,所以尝试了国外几款主流播放器,类似Spotify、Googleplay music、SoundClound、poweramp,都成功了, 部分失败,这也没办法。具体代码如下:
import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.widget.ImageView; import android.widget.TextView; /** * Created by wangzhaopeng on 2017/11/22. */ public class MyReceiver extends BroadcastReceiver { private ImageView mCover; private TextView mTitle ; private TextView singer; private TextView malbum; private DataCallBack mDataCallBack; public MyReceiver(DataCallBack callBack) { mDataCallBack = callBack; } @Override public void onReceive(Context context, Intent intent) { String albumName = intent.getStringExtra("album"); String artist = intent.getStringExtra("artist"); String trackName = intent.getStringExtra("track"); String xiaMiName=intent.getStringExtra("widget_song_name"); System.out.println("最新的结果是!: " + albumName + " artist: " + artist + " Track:" + trackName+" xiaMiName:"+xiaMiName); if(albumName!=null || artist!=null || trackName!= null) { mDataCallBack.onDataChanged(trackName, artist, albumName); } } public interface DataCallBack { void onDataChanged(String trackName,String artist,String albumName ); } }先写一个广播,再去Activity中动态注册:private void receive(){IntentFilter iF = new IntentFilter();//这么多Action是我尝试出来的,有的播放器可以,有的不行,但基本可以的都在这iF.addAction("com.android.music.metachanged");iF.addAction("com.android.music.playstatechanged");iF.addAction("com.android.music.queuechanged");iF.addAction("com.htc.music.metachanged");iF.addAction("fm.last.android.metachanged");iF.addAction("com.sec.android.app.music.metachanged");iF.addAction("com.nullsoft.winamp.metachanged");iF.addAction("com.amazon.mp3.metachanged");iF.addAction("com.miui.player.metachanged");iF.addAction("com.real.IMP.metachanged");iF.addAction("com.sonyericsson.music.metachanged");iF.addAction("com.rdio.android.metachanged");iF.addAction("com.samsung.sec.android.MusicPlayer.metachanged");iF.addAction("com.andrew.apollo.metachanged");iF.addAction("com.kugou.android.music.metachanged");iF.addAction("com.ting.mp3.playinfo_changed");iF.addAction("com.spotify.music.playbackstatechanged");iF.addAction("com.spotify.music.metadatachanged");iF.addAction("com.rhapsody.playstatechanged");iF.addAction("com.spotify.music.metadatachanged");iF.addAction("com.xiami.music.horizontalplayer");iF.addAction("com.android.music.musicservicecommand");iF.addAction("com.jiubang.go.music");iF.addAction("com.google.android.gms.measurement.UPLOAD");iF.addAction("com.google.android.gms.measurement.AppMeasurementService");iF.addAction("com.google.android.gms");iF.addAction("com.google.android.gms.common.account.CHOOSE_ACCOUNT");iF.addAction("com.Project100Pi.themusicplayer");iF.addAction("com.tutorialsface.audioplayer.next");iF.addAction("com.sec.android.automotive.drivelink");iF.addAction("com.apsalar.sdk.INITIALIZE");registerReceiver(new MyReceiver(new MyReceiver.DataCallBack() {@Overridepublic void onDataChanged(String albumName,String singer,String zhuanji) {mTitle.setText(albumName);msinger.setText(singer);malbum.setText(zhuanji);}}), iF);} 至此,基本就实现了第二种方案,布局的话自己写几个按钮,放三个Textview和一个Imageview就可以了,我这里不贴出代码。所以概括下这两种方案:第一种基本能获取信息,但需要授权;第二种不需要授权,但很多播放器获取不到。所以就根据自己需要作出选择吧!目前我找不到更好的实现方法了,如果各位有好的方案的,欢迎提出探讨。如有问题的,在下方评论处留言,我看到就回复。
相关文章推荐
- angularjs获取前一个网页以及现网页网址的路由信息
- 获取MP3和M4A音乐文件的歌曲信息以及专辑图片
- 获取MP3和M4A音乐文件的歌曲信息以及专辑图片
- 获取MP3和M4A音乐文件的歌曲信息以及专辑图片--备用
- 微信公众号第三方开发之四回调url中获取授权方的授权信息以及基本信息
- Android如何判断当前手机是否正在播放音乐,并获取到正在播放的音乐的信息
- Android如何判断当前手机是否正在播放音乐,并获取到正在播放的音乐的信息
- Android如何判断当前手机是否正在播放音乐,并获取到正在播放的音乐的信息
- Android如何判断当前手机是否正在播放音乐,并获取到正在播放的音乐的信息
- Android如何判断当前手机是否正在播放音乐,并获取到正在播放的音乐的信息
- 实现php获取mp3文件元信息如播放时间歌曲作者等
- 一个视频播放第三方(JCvideoPlayer)以及部分调整
- Android如何判断当前手机是否正在播放音乐,并获取到正在播放的音乐的信息
- Android如何判断当前手机是否正在播放音乐,并获取到正在播放的音乐的信息
- Android如何判断当前手机是否正在播放音乐,并获取到正在播放的音乐的信息
- 获取与一个磁盘的组织以及剩余空间容量有关的信息以及代码测试
- android 在锁屏或者休眠时 获取定位以及上传定位信息(用的百度地图定位包)和afinal包(主要用afinalHttp)
- 播放器常见问题的解决(锁屏/后台连续播放;锁屏播放控制;锁屏信息显示)
- 怎样获取一个表中所有字段的信息
- 获取手机第三方应用的信息(名称,包名,版本号,版本名,安装包路径,安装包大小)