preface

How to implement a music player App, and then make it can be opened by a third party Android App, and get the playlist, track list, and control its playback? There are already implementations on the market. For example, Baidu CarLife calls QQ music and Ximalaya.

In Baidu’s Carlife App, we can see that as long as our local QQ Music App is installed, it can be called up, and then obtain the song data, and then play it. How is this achieved?

demand

  • You can get a list of songs for your music player
  • Can control the music player playback
  • You can synchronize the status of your music player to a third-party App
  • It can communicate with third-party apps

It is similar to CarLife’s awakening of music App. First, after the third-party App is opened, you can pull up the music App and then obtain the playlist. After opening the playlist, you can obtain the list of songs in the playlist and click to play.

The technical implementation

Google officially offers MediaBroswerService to help us fulfill this requirement.

MediaBroswerService

  • Android Multimedia Architecture

Android multimedia playback adopts client and Server architecture. A server can correspond to multiple clients. The clients need to connect to the server before using it, and the two sides synchronize status through some callback.

Play using MediaBrowserService

MediaBrowser needs to be created on the client and MediaBrowserService needs to be implemented on the server. After the connection is established, the interaction between the two ends is mediated by MediaController and MediaSession. The two classes interact with each other through a predefined callback. MediaSession controls the playback of the player and MediaController controls the changes of the UI.

  • Media session

A session holds the state of the player and some information about what is being played, and a Seesion can receive callbacks from one or more media players. This makes it possible to control through other devices.

  • Media controller

Our UI is only interacting with the Media Controller, not the Player itself, and the Media Controller will pass some control information to the Media Session, and it will also get a callback from the Session when the seesion changes, A media Controller can connect to only one session at a time. When using a Media Contoller and Session, we can deploy multiple players at run time and modify the appearance of the app according to the device as it executes.

Using MediaBrowserService makes it very easy for Android Wear and Auto to find our App, connect to it, browse its content, and control its playback without touching our UI Activity at all.

Server-side implementation

  • Basic configuration of the server

Mainfeat configuration

<service android:name=".MediaPlaybackService">
  <intent-filter>
    <action android:name="android.media.browse.MediaBrowserService" />
  </intent-filter>
</service>
Copy the code

Initialization of MediaPlaybackService

public class MediaPlaybackService extends MediaBrowserServiceCompat {

  @Override

  public void onCreate() { super.onCreate(); Initializing mediasessionmsession = new MediaSessionCompat(this,"MusicService"); // 2. Set MedisSessionCallback mSession. SetCallback (mSessionCallback); / / 3. Open MediaButton and support TransportControls mSession. SetFlags (MediaSessionCompat. FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); / / 4. Initialization PlaybackState mStateBuilder = new PlaybackStateCompat. Builder (). SetActions (PlaybackStateCompat. ACTION_PLAY |  PlaybackStateCompat.ACTION_PLAY_PAUSE); mSession.setPlaybackState(mStateBuilder.build()); // 5. Associate SessionTokensetSessionToken(mSession.getSessionToken()); }}Copy the code

After determining permissions based on the package name, return to the root path

@Override public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {// Do some checking on each access end based on the package name, return null, connection will be disconnected}Copy the code

Used to return the media data required by the third App according to mediaID

@Override public void onLoadChildren(final String parentMediaId, Final Result<List<MediaItem>> Result) {// parentMediaId returns playlist information}Copy the code

Client connection

private void initMediaBrowser() {//1. ComponentName = new ComponentName()"com.example.android.uamp"."com.example.android.uamp.MusicService"); MMediaBrowser = new MediaBrowserCompat(this, componentName, mConnectionCallbacks, null); Mmediabrowser.connect (); }Copy the code

Set the corresponding callback, connect callback, data change callback

Connection state synchronization

Data changes Callback Settings

private final MediaBrowserCompat.ConnectionCallback mConnectionCallbacks =
        new MediaBrowserCompat.ConnectionCallback() {

      @Override
      public void onConnected() {// Callback for connection success} @override public voidonConnectionSuspended() {// Connection disconnection callback} @override public voidonConnectionFailed() {// Connection failure callback}};Copy the code
MediaControllerCompat.Callback controllerCallback =

    new MediaControllerCompat.Callback() {
          public void onSessionDestroyed{Override public void onRepeatModeChanged(int repeatMode) {Override public void onRepeatModeChanged(int repeatMode } Override public void onMetadataChanged(MediaMetadataCompat Metadata) {// Data changes} @override public void onPlaybackStateChanged(PlaybackStateCompat state) {// Play state changes}};Copy the code

Data interaction between client and server

MediaBrowser calls SUBSCRIBE, calls back to MediaService onLoadChildren, makes a judgment there and constructs the corresponding list and returns the list data. After the data is returned.

  • Get data according to MediaID

The client calls the SUBSCRIBE method, passing MediaID, which is processed in the SubscriptionCallback method.

mMediaBrowser.subscribe("ID", new MediaBrowserCompat.SubscriptionCallback() { @Override public void onChildrenLoaded(@NonNull String parentId, @ NonNull List < MediaBrowserCompat. MediaItem > children) {/ / the children for the data from the List of Service}});Copy the code

The data passed between the server and client is the MediaItem list. MediaItem: MediaId, Title, SubTitle, Description, Icon, IconUri, MediaUri, etc. It can help us carry some data to show and play songs.

@Override public void onLoadChildren(@NonNull final String parentMediaId, @NonNull final Result<List<MediaItem>> result) { List<MediaItem> items = new ArrayList<>(); Switch (parentMediaId) {case:
         default: break;
     }
     result.sendResult(items);
 }
Copy the code
  • Send custom data to retrieve content

By calling sendCustomAction, the client establishes the corresponding action type according to the negotiation with the server and carries out data transfer interaction.

mMediaBrowser.sendCustomAction(action, extras, new MediaBrowserCompat.CustomActionCallback() { @Override public void onProgressUpdate(String action, Bundle extras, Bundle data) { super.onProgressUpdate(action, extras, data); } @Override public void onResult(String action, Bundle extras, Bundle resultData) { super.onResult(action, extras, resultData); } @Override public void onError(String action, Bundle extras, Bundle data) { super.onError(action, extras, data); }});Copy the code

The server implements onCustomAction and returns data based on the action type

@override public void onCustomAction(@nonnull String action, Bundle extras, @nonNULL Result<Bundle> Result) {// Branch judgmentif(GET_LIST.equals(action)) { Bundle bundle = new Bundle(); ArrayList<String> list = new ArrayList<>(); / / fill data bundle. PutStringArrayList (LIST_NAMES, a list); result.sendResult(bundle); }}Copy the code

Playback controls

  • The client

The client controls play, pause, previous, next via getMediaController getTransportControls().

. / / to get playing int pbState = MediaControllerCompat getMediaController (MainActivity. This). GetPlaybackState (). The getState (); // Perform playback control according to the playback stateif (pbState == PlaybackStateCompat.STATE_PLAYING) {
    MediaControllerCompat.getMediaController(MainActivity.this).getTransportControls().pause();
} else {
    MediaControllerCompat.getMediaController(MainActivity.this).getTransportControls().play();
}
Copy the code
  • The service side

Set SessionCallback for MediaSession on the server to implement the corresponding playback function.

mSession.setCallback(mSessionCallback);
Copy the code

The client can play, pause, play the next music according to the MediaID, play the music fast forward, and so on. All operations will be called back to the server MediaSessionCallback play, seekTo, etc. We need to implement our own, in which we control the play queue, and then dynamically change the queue according to the list play situation.

Synchronization of Playback status

Synchronization of playback status, such as which song is currently playing, whether it is currently paused or playing. The client gets the corresponding change through the Controller callback, but how does the server send the change status?

setMetadata(android.media.MediaMetadata));
setPlaybackState(android.media.session.PlaybackState));
Copy the code

Set the current song information, set the current playing state. After setting, the client will be updated.

Access media services on your phone

private void discoverBrowseableMediaApps(Context context) { PackageManager packageManager = context.getPackageManager();  Intent intent = new Intent(MediaBrowserService.SERVICE_INTERFACE); List<ResolveInfo> services = packageManager.queryIntentServices(intent, 0);for (ResolveInfo resolveInfo : services) {
        if (resolveInfo.serviceInfo != null && resolveInfo.serviceInfo.applicationInfo != null) {

            ApplicationInfo applicationInfo = resolveInfo.serviceInfo.applicationInfo;
            String label = (String) packageManager.getApplicationLabel(applicationInfo);
            Drawable icon = packageManager.getApplicationIcon(applicationInfo);
            String className = resolveInfo.serviceInfo.name;
            String packageName = resolveInfo.serviceInfo.packageName;

            MusicService service = new MusicService();
            service.icon = icon;
            service.lable = label;
            service.className = className;
            service.packageName = packageName;
            musicServiceList.add(service);
        }
    }

}
Copy the code

Some models cannot be obtained, so the App can be obtained only after it is opened.

conclusion

In this article, we gave a brief introduction to MediaBroswerService, but the implementation of the player, especially on the server side, is more complex, requiring the maintenance of song queues, playback, and state updates.