-
-
Notifications
You must be signed in to change notification settings - Fork 45
[EN] 3.Custom PlayerService
The PlayerService
is designed to be extensible. You can implement the following functions by overriding its corresponding methods:
- Custom MusicPlayer
- Custom Notification Controller
- Custom Audio Effect
- Record playback history
- Sound quality/dynamic URL
- Only WiFi network
- Headset clicks
- Custom MediaSessionCompat.Callback
Note: PlayerService
is not thread-safe, please do not call its methods in an asynchronous thread, otherwise the result is unknown.
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);
}
}
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();
}
}
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();
}
}
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.
};
}
}
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
.
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
.
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
}
}
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 {
// ...
}
}
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) {
// ...
}
});
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