Skip to content

[EN] 3.Custom PlayerService

jrfeng edited this page Mar 7, 2021 · 10 revisions

The PlayerService is designed to be extensible. You can implement the following functions by overriding its corresponding methods:

  1. Custom MusicPlayer
  2. Custom Notification Controller
  3. Custom Audio Effect
  4. Record playback history
  5. Sound quality/dynamic URL
  6. Only WiFi network
  7. Headset clicks
  8. Custom MediaSessionCompat.Callback

Other

Note: PlayerService is not thread-safe, please do not call its methods in an asynchronous thread, otherwise the result is unknown.

1. Custom MusicPlayer

If the default MediaMusicPlayer cannot meet your needs, you can override onCreateMusicPlayer(Context, MusicItem, Uri) and provide an alternative MusicPlayer(e.g. ExoMusicPlayer, see: Using ExoPlayer).

Example:

@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
    ...

    @NonNull
    @Override
    protected MusicPlayer onCreateMusicPlayer(@NonNull Context context, @NonNull MusicItem musicItem, @NonNull Uri uri) {
        return new ExoMusicPlayer(context, uri);
    }
}

2. Custom Notification Controller

If you are not satisfied with the player's default notification controller, you can override onCreateNotificationView() and provide another PlayerService.NotificationView.

About how to implement a custom NotificationView, see: Custom Notification

Example:

@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
    ...
    
    @Nullable
    @Override
    protected NotificationView onCreateNotificationView() {
        return new MyCustomNotificationView();
    }
}

3. Custom Audio Effect

If your player needs to support audio effects, you can override onCreateAudioEffectManager() and return an AudioEffectManager, which is used to manage and synchronize the configuration of audio effects (see: PlayerClient#setAudioEffectConfig(Bundle)).

This library has provided an implementation of AudioEffectManager, see: EqualizerActivity

Example:

@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
    ...
    
    @Nullable
    @Override
    protected AudioEffectManager onCreateAudioEffectManager() {
        return new AndroidAudioEffectManager();
    }
}

4. Record playback history

If you need to record the playback history of the player, you can override onCreateHistoryRecorder() and return a HistoryRecorder instance.

Example:

@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
    ...
    
    @Nullable
    @Override
    protected HistoryRecorder onCreateHistoryRecorder() {
        return new HistoryRecorder() {
            void recordHistory(MusicItem musicItem) {
                // ...Record playback history
                // This method will called in the main thread. 
                // If you need to access the network, database or local file,
                // please execute it in the background thread.
            };
    }
}

5. Sound quality/dynamic URL

If your player want to support different sound quality or dynamic URL, you can override onRetrieveMusicItemUri(MusicItem musicItem, SoundQuality soundQuality, AsyncResult<Uri> result) to return the corresponding sound quality URL of the song according to different SoundQuality. This method will be executed in a background thread, so you can perform some I/O operations in this method, such as access network or database.

Example:

@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
    ...

    @Override
    protected void onRetrieveMusicItemUri(@NonNull MusicItem musicItem, 
                                     @NonNull SoundQuality soundQuality
                                     @NonNull AsyncResult<Uri> result) throws Exception {
        // ...Access the server to get the URL
        // The onSuccess(T) of the result parameter needs to be called to return the execution result of the asynchronous task
        // If there is an error in the asynchronous task, the onError(Throwable) of the result parameter needs to be called 
        // to send an error message
        result.onSuccess(uri);
    }
}

This asynchronous task may be cancelled, If you need to listen for its cancel event, you can use AsyncResult#setOnCancelListener(AsyncResult.OnCancelListener listener) set a AsyncResult.OnCancelListener.

6. Only WiFi network

The isCached(MusicItem, SoundQuality, AsyncResult<Boolean>) is used to determine whether the MusicItem with a specific SoundQuality has been cached, this method return false by default. If you plan to play music from the Internet and you want to support the "Only WiFi Network", then you must override this method, because the "Only WiFi Network" requires this method to determine whether the song is cached. This method will be executed in a background thread, so you can perform some I/O operations in this method, such as access database.

Example:

@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
    ...
    
    @Override
    protected void isCached(MusicItem musicItem, SoundQuality soundQuality, AsyncResult<Boolean> result) {
        // ...Queries whether musictItem with specified soundQuality is cached
        // The onSuccess(T) of the result parameter needs to be called to return the execution result of the asynchronous task
        // If there is an error in the asynchronous task, the onError(Throwable) of the result parameter needs to be called 
        // to send an error message
        result.onSuccess(false);
    }
}

This asynchronous task may be cancelled, If you need to listen for its cancel event, you can use AsyncResult#setOnCancelListener(AsyncResult.OnCancelListener listener) set a AsyncResult.OnCancelListener.

7. Headset clicks

The onHeadsetHookClicked(int clickCount) will be called when the headset is clicked, the clickCount parameter of this method is the number of consecutive clicks. By default, one click is "play/pause"; two clicks is "skip to next"; three clicks is "skip to previous". If you are not satisfied with the default behavior, you can override this method to customize the click behavior of the headset.

Example:

@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
    ...

    @Override
    protected void onHeadsetHookClicked(int clickCount) {
        // ...custom headset clicks
    }
}

8. Custom MediaSessionCompat.Callback

If you want to customize MediaSessionCompat.Callback, you can override onCreateMediaSessionCallback(). The return type of this method is PlayerService.MediaSessionCallback, which is a subtype of MediaSessionCompat.Callback. You must to use super.xxx invoke the method of PlayerService.MediaSessionCallback, otherwise some functions of PlayerService will not working.

Example:

@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
    ...

    @NonNull
    @Override
    protected MediaSessionCallback onCreateMediaSessionCallback() {
        // Return your custom MediaSessionCallback
        return new MyMediaSessionCallback();
    }

    private static class MyMediaSessionCallback extends MediaSessionCallback {
        // ...
    }
}

Other

Add custom action

You can add custom actions using the addCustomAction(String, PlayerService.CustomAction) method:

addCustomAction(
        String action,                             // A string conforming to Android's Action naming rules, not null
        PlayerService.CustomAction customAction    // The task to be executed when action is triggered, not null
)

The action parameter is a string that conforms to Android's Action naming rules (such as "snow.player.debug.action.HELLO"); customAction is the task to be executed when the action is triggered (both parameters cannot be null).

// the name of custom action
public static final String ACTION_HELLO = "snow.player.debug.action.HELLO";

...

// add a custom action
addCustomAction(ACTION_HELLO, new PlayerService.CustomAction() {
    @Override
    public void doAction(Player player, Bundle extras) {
        // ...
    }
});

Trigger a custom action

There are two ways to trigger a custom action:

  • Triggered by Context#sendBroadcast(Intent)
  • Triggered by MediaControllerCompat.TransportControls#sendCustomAction (String action, Bundle args)

Example 1:

// Create an broadcast Intent
Intent broadcastIntent = PlayerService.buildCustomActionIntent(ACTION_HELLO, PlayerService.class/*Or any subclass of PlayerService*/);

// Optional, put some extras:
// broadcastIntent.putExtra(xxx, xxx);
// ...

// Trigger custom action
context.sendBroadcast(intent);

Example 2:

// Get a MediaControllerCompat.TransportControls instance by PlayerClient
MediaControllerCompat.TransportControls controls = playerClient.getMediaController().getTransportControls();

// Optional, put some extras:
// Bundle extras = new Bundle();
// extras.putString(xxx, xxx);
// ...

// Trigger custom action (no extras)
controls.sendCustomAction(ACTION_HELLO, null);
// Or put some extras:
// controls.sendCustomAction(ACTION_HELLO, extras);

End