-
-
Notifications
You must be signed in to change notification settings - Fork 45
[ZH] 3.自定义 PlayerService
PlayerService
类被设计成可扩展的,你可以通过覆盖它的对应方法来实现以下功能:
注意!PlayerService
不是线程安全的,请不要在异步线程中调用它的方法,否则结果是未知的。
如果默认的 MediaMusicPlayer
不能满足你的需求,则可以覆盖 onCreateMusicPlayer(Context, MusicItem, Uri)
方法并提供一个其他的 MusicPlayer
实现(例如:ExoMusicPlayer
,参见:使用 ExoPlayer)。
例:
@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);
}
}
提示:如果你对
MediaMusicPlayer
与ExoMusicPlayer
都不满意,你完全可以通过实现MusicPlayer
接口来创建一个自定义的音乐播放器实现。
如果你对播放器默认的通知栏控制器不满意,则可以覆盖 onCreateNotificationView()
方法并提供一个其他的 PlayerService.NotificationView
实现。
关于如何实现一个自定义的 NotificationView
,参见:自定义 Notification
例:
@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
...
@Nullable
@Override
protected NotificationView onCreateNotificationView() {
return new MyCustomNotificationView();
}
}
如果你的播放器需要支持自定义的音频特效(例如,均衡器),则可以覆盖 onCreateAudioEffectManager()
方法,并返回一个 AudioEffectManager
实现,该实现用于管理和同步音频特效的配置(另请参见:PlayerClient#setAudioEffectConfig(Bundle)
)。
本项目已经提供了一个音频特效引擎实现,具体请查看:EqualizerActivity
例:
@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
...
@Nullable
@Override
protected AudioEffectManager onCreateAudioEffectManager() {
return new AndroidAudioEffectManager();
}
}
如果你需要记录播放器的播放历史,则可以覆盖 onCreateHistoryRecorder()
方法并返回一个 HistoryRecorder
实现。
例:
@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
...
@Nullable
@Override
protected HistoryRecorder onCreateHistoryRecorder() {
return new HistoryRecorder() {
void recordHistory(MusicItem musicItem) {
// ...记录播放历史
// 该方法会在主线程中调用,如果你需要访问网络、数据库或者本地文件,请在后台线程中执行
};
}
}
默认情况下,onRetrieveMusicItemUri(MusicItem musicItem, SoundQuality soundQuality, AsyncResult<Uri> result)
方法仅仅只是调用 Uri.parse(String)
方法将 musicItem.getUri()
方法的返回值转换成一个 Uri
对象。如果你的播放器需要支持不同的音质或者动态 URL
,则可以覆盖 onRetrieveMusicItemUri(MusicItem musicItem, SoundQuality soundQuality, AsyncResult<Uri> result)
方法来根据不同的 SoundQuality
枚举值返回歌曲对应音质的播放链接。该方法会在异步线程中执行,因此可以在该方法中执行耗时操作,例如访问网络或者本地数据库。
例:
@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
...
@Override
protected void onRetrieveMusicItemUri(@NonNull MusicItem musicItem,
@NonNull SoundQuality soundQuality
@NonNull AsyncResult<Uri> result) throws Exception {
// ...访问服务器获取 URL
// 需要调用 result 参数的 onSuccess(T) 方法来返回异步任务的执行结果
// 如果异步任务出错,则需要调用 result 参数的 onError(Throwable) 方法发出错误通知
result.onSuccess(uri);
}
}
这个异步任务可能会被取消,如果你需要监听它的取消事件,可以调用 AsyncResult#setOnCancelListener(AsyncResult.OnCancelListener listener)
方法向 AsyncResult<T>
注册一个 AsyncResult.OnCancelListener
监听器。
isCached(MusicItem, SoundQuality, AsyncResult<Boolean>)
方法用于判断具有特定 SoundQuality
的 MusicItem
是否已缓存,该方法的默认结果为 false
。如果你打算播放来自网络的音乐,并且打算支持 “仅 Wifi 网络播放” 功能,那么请务必覆盖该方法,因为 “仅 Wifi 网络播放” 功能需要使用该方法来判断歌曲是否已缓存。该方法会在异步线程中执行,因此可以在该方法中执行耗时操作,例如访问本地数据库。
例:
@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
...
@Override
protected void isCached(MusicItem musicItem, SoundQuality soundQuality, AsyncResult<Boolean> result) {
// ...查询具有指定 soundQuality 的 musicItem 是否已缓存
// 需要调用 result 参数的 onSuccess(T) 方法来返回异步任务的执行结果
// 如果异步任务出错,则需要调用 result 参数的 onError(Throwable) 方法发出错误通知
result.onSuccess(false);
}
}
这个异步任务可能会被取消,如果你需要监听它的取消事件,可以调用 AsyncResult#setOnCancelListener(AsyncResult.OnCancelListener listener)
方法向 AsyncResult<T>
注册一个 AsyncResult.OnCancelListener
监听器。
onHeadsetHookClicked(int clickCount)
方法会在耳机按钮被点击时调用,用来实现线控播放,该方法的 clickCount
参数是连续点击的次数。默认情况下,点击 1
次是 “播放/暂停”;点击 2
次是 “下一曲”;点击 3
下是 “上一曲”。如果你对默认行为不满意,则可以覆盖该方法来自定义耳机按钮的点击行为。
例:
@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
...
@Override
protected void onHeadsetHookClicked(int clickCount) {
// ...自定义线控播放
}
}
如果你需要对 MediaSessionCompat.Callback
进行自定义,则可以覆盖 onCreateMediaSessionCallback()
方法。该方法的返回值类型为 PlayerService.MediaSessionCallback
,它是 MediaSessionCompat.Callback
的子类型。建议在覆盖 PlayerService.MediaSessionCallback
的方法时使用 super.xxx
回调超类对应的方法,否则本项目的部分功能将无法正常使用。
例:
@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
...
@NonNull
@Override
protected MediaSessionCallback onCreateMediaSessionCallback() {
// 返回自定义的 MediaSessionCallback
return new MyMediaSessionCallback();
}
private static class MyMediaSessionCallback extends MediaSessionCallback {
// ...
}
}
可以使用 addCustomAction(String, PlayerService.CustomAction)
方法添加自定义动作:
addCustomAction(
String action, // 一个符合 Android 的 Action 命名规则的字符串,不能为 null
PlayerService.CustomAction customAction // action 被触发时要执行的动作,不能为 null
)
其中,action
参数是一个符合 Android
的 Action
命名规则的字符串(如 "snow.player.debug.action.HELLO"
);customAction
是在 action
被触发时要执行的动作(这两个参数都不能为 null
)。
例:
// 自定义动作的 action 字符串
public static final String ACTION_HELLO = "snow.player.debug.action.HELLO";
...
// 添加一个自定义动作
addCustomAction(ACTION_HELLO, new PlayerService.CustomAction() {
@Override
public void doAction(Player player, Bundle extras) {
// ...
}
});
共有 2 种触发自定义动作的方法:
- 第 1 种:通过
sendBroadcast(Intent)
方法触发。 - 第 2 种:通过调用
MediaControllerCompat.TransportControls
的sendCustomAction (String action, Bundle args)
触发。
例 1:
// 创建一个广播 Intent
Intent broadcastIntent = PlayerService.buildCustomActionIntent(ACTION_HELLO, PlayerService.class/*或者是 PlayerService 的任意一个子类型*/);
// 触发自定义动作
context.sendBroadcast(intent);
例 2:
// 通过 PlayerClient 对象获取到一个 MediaControllerCompat.TransportControls 对象
MediaControllerCompat.TransportControls controls = playerClient.getMediaController().getTransportControls();
// 要携带的额外参数
Bundle extras = new Bundle();
// 触发自定义动作
controls.sendCustomAction(ACTION_HELLO, extras);
End