写一个APP控制第三方播放器播放,以及获取正在播放的歌曲信息

来源:互联网 发布:淘宝妈妈网站推广 编辑:程序博客网 时间:2024/05/01 01:55

      最近遇到这么一个需求,就是在自己的应用中控制第三方播放器播放,以及获取正在播放的歌曲信息,包括名字,歌手,专辑,显示出来。一开始觉得很简单,但实际上遇到了不少的麻烦,最终实现了两种方案,读者可根据自己需要选择。

      先讲如何控制第三方播放器播放的问题,可以使用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();    @Override    public void onCreate() {        registerRemoteController();    }    @Override    public IBinder onBind(Intent intent) {        if (intent.getAction().equals("com.example.wangzhaopeng.music.BIND_RC_CONTROL_SERVICE")) {//这里要根据实际进行替换            return mBinder;        } else {            return super.onBind(intent);        }    }    @Override    public 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());    }    @Override    public 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;    }    @Override    public void onClientChange(boolean clearing) {        if (mExternalClientUpdateListener != null) {            mExternalClientUpdateListener.onClientChange(clearing);        }    }    @Override    public void onClientPlaybackStateUpdate(int state) {        if (mExternalClientUpdateListener != null) {            mExternalClientUpdateListener.onClientPlaybackStateUpdate(state);        }    }    @Override    public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed) {        if (mExternalClientUpdateListener != null) {            mExternalClientUpdateListener.onClientPlaybackStateUpdate(state, stateChangeTimeMs, currentPosMs, speed);        }    }    @Override    public void onClientTransportControlUpdate(int transportControlFlags) {        if (mExternalClientUpdateListener != null) {            mExternalClientUpdateListener.onClientTransportControlUpdate(transportControlFlags);        }    }    @Override    public 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、Google play 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() {            @Override            public void onDataChanged(String albumName,String singer,String zhuanji) {                mTitle.setText(albumName);                msinger.setText(singer);                malbum.setText(zhuanji);            }        }), iF);    }
      至此,基本就实现了第二种方案,布局的话自己写几个按钮,放三个Textview和一个Imageview就可以了,我这里不贴出代码。所以概括下这两种方案:第一种基本能获取信息,但需要授权;第二种不需要授权,但很多播放器获取不到。所以就根据自己需要作出选择吧!目前我找不到更好的实现方法了,如果各位有好的方案的,欢迎提出探讨。如有问题的,在下方评论处留言,我看到就回复。







阅读全文
0 0
原创粉丝点击