您的位置:首页 > 移动开发

写一个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就可以了,我这里不贴出代码。所以概括下这两种方案:第一种基本能获取信息,但需要授权;第二种不需要授权,但很多播放器获取不到。所以就根据自己需要作出选择吧!目前我找不到更好的实现方法了,如果各位有好的方案的,欢迎提出探讨。如有问题的,在下方评论处留言,我看到就回复。

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐