-
-
Notifications
You must be signed in to change notification settings - Fork 45
[EN] 2.Advanced
Directory:
- Connect to PlayerService
- Playlist
- Control player
- Config player
- Observe player state
- Get player state
- PlayerViewModel
- Disconnect
- Shutdown PlayerService
- Downloading media & Cache
- Others
The PlayerClient
is the client of player, and the PlayerService
is the server of player. Before using PlayerClient
, you need to establish a connection with the PlayerService
.
Before that, you need create a PlayerClient
instance. You can use static method PlayerClient.newInstance(Context, Class<? extends PlayerService>)
create a PlayerClient
instance.
public static PlayerClient newInstance(
Context context, // Context instance, not null
Class<? extends PlayerService> playerService // The Class instance of PlayerService which the PlayerClient need connect to, not null
)
The first param is a Context
instance, not null. The second param is a Class
instance, that is the Class
instance of PlayerService
which the PlayerClient
need connect to, not null.
After creating a PlayerClient
instance, you can use its following methods to connect to the PlayerService
:
connect()
connect(PlayerClient.OnConnectCallback callback)
The second connect
method have a PlayerClient.OnConnectCallback
param, this param is a callback interface. This callback interface is called when the connect succeeds or fails. If you want to know the connect result, you can use this method.
Example:
PlayerClient playerClient = PlayerClient.newInstance(context, MyPlayerService.class);
playerClient.connect(new PlayerClient.OnConnectCallback() {
@Override
public void onConnected(boolean success) {
// DEBUG
Log.d("DEBUG", "connect result: " + success);
}
});
In addition, you can invoke the isConnected()
method to check the connect result. If the connection is successful, this method will return true
. If there is no connection, connection failure, or already disconnected, this method will return false
.
Use the following methods of PlayerClient
to set up a new playlist:
-
setPlaylist(Playlist playlist)
: just set a new playlist. -
setPlaylist(Playlist playlist, boolean play)
: set a new playlist and whether to play the index0
music of the playlist. -
setPlaylist(Playlist playlist, int position, boolean play)
: set a new playlist and whether to play theposition
music of the playlist.
Note: Do not set a huge playlist (max size is 1000), a huge playlist maybe will cause Binder
to crash!
After connected, you can use the following methods of PlayerClient
to get the player's playlist:
getPlaylist(PlaylistManager.Callback callback)
getPlaylistSize()
Exmaple:
playerClient.getPlaylist(new PlaylistManager.Callback() {
@Override
public void onFinished(Playlist playlist) {
// ...
}
});
After connected, you can use the following methods of PlayerClient
to edit the player's playlist:
-
setNextPlay(MusicItem musicItem)
: set the nextMusicItem
to play. If theMusicItem
is already contains in the playlist, it will be moved to the next top play position, otherwiseMusicItem
will be inserted to the next top playback position. -
insertMusicItem(int position, MusicItem musicItem)
: Insert aMusicItem
into theposition
in the playlist. If theMusicItem
is already contains in the playlist, it will be moved to theposition
of playlist. -
appendMusicItem(MusicItem musicItem)
: append aMusicItem
to the end of the playlist. If theMusicItem
is already contains in the playlist, it will be moved to the end of the playlist. -
moveMusicItem(int fromPosition, int toPosition)
: move theMusicItem
atfromPosition
in the playlist totoPosition
. -
removeMusicItem(MusicItem musicItem)
: removeMusicItem
from playlist. If theMusicItem
is not contains in the playlist, just ignored.
If you want to listen for changes in the playlist, you can use the PlayerClient#addOnPlaylistChangeListener(Player.OnPlaylistChangeListener Listener)
register a Player.OnPlaylistChangeListener
listener to listen for changes to the playlist. When the playlist changes, the onPlaylistChanged(PlaylistManager playlistManager, int position)
will be called, the parameter PlaylistManager
can be used to get the latest playlist, and the parameter position
is the position of the currently playing song in the playlist.
Example:
playerClient.addOnPlaylistChangeListener(new Player.OnPlaylistChangeListener() {
@Override
public void onPlaylistChanged(PlaylistManager playlistManager, int position) {
// get fresh playlist
playlistManager.getPlaylist(new PlaylistManager.Callback() {
@Override
public void onFinished(Playlist playlist) {
// ...
}
});
// ...
}
});
If you want to get the latest playlist when playlist changed, you can use PlaylistLiveData
. The advantage of using PlaylistLiveData
is that you don't have to worry about memory leaks, and developers can write less code than they do with listeners.
Example:
PlaylistLiveData playlistLiveData = new PlaylistLiveData();
playlistLiveData.init(playerClient);
playlistLiveData.observe(lifecycleOwner, new Observer<Playlist>() {
@Override
public void onChanged(Playlist playlist) {
// ...
}
});
Use the following methods of PlayerClient
to control the player:
play()
pause()
playPause()
playPause(int position)
stop()
seekTo(int progress)
skipToPrevious()
skipToNext()
skipToPosition(int position)
fastForward()
rewind()
More methods, please check the API Doc
You can use the following methods of PlayerClient
to config the player:
-
setPlayMode(PlayMode playMode)
: there are three kinds of play mode:SEQUENTIAL, LOOP, SHUFFLE
-
setOnlyWifiNetwork(boolean onlyWifiNetwork)
: set whether music is allowed only on WiFi networks (default isfalse
) -
setSoundQuality(SoundQuality soundQuality)
: set the preferred sound quality of the player -
setAudioEffectEnabled(boolean enabled)
: set whether to enable audio effects (e.g. equalizer, the default isfalse
) -
setAudioEffectConfig(android.os.Bundle config)
: modify the configuration of audio effects
Note: The functions of "Only WiFi Network", "Sound Quality" and "Audio Effects" need cooperate with PlayerService
to realize. More details, please check the Custom PlayerService.
About audio effects, see: EqualizerActivity
You can use the following methods of PlayerClient
to observe the status of player:
addOnPlaybackStateChangeListener(PlayerClient.OnPlaybackStateChangeListener listener)
addOnPlaybackStateChangeListener(Player.OnPlaybackStateChangeListener listener)
addOnPrepareListener(Player.OnPrepareListener listener)
addOnBufferedProgressChangeListener(Player.OnBufferedProgressChangeListener listener)
addOnAudioSessionChangeListener(PlayerClient.OnAudioSessionChangeListener listener)
addOnPlayingMusicItemChangeListener(Player.OnPlayingMusicItemChangeListener listener)
addOnPlaylistChangeListener(Player.OnPlaylistChangeListener listener)
addOnPlayModeChangeListener(Player.OnPlayModeChangeListener listener)
addOnSeekCompleteListener(Player.OnSeekCompleteListener listener)
addOnConnectStateChangeListener(PlayerClient.OnConnectStateChangeListener listener)
addOnStalledChangeListener(Player.OnStalledChangeListener listener)
addOnRepeatListener(Player.OnRepeatListener listener)
When you no longer need a listener, you must use the corresponding removeXxx
method to remove it.
Exmaple:
Player.OnPlaybackStateChangeListener listener = new Player.OnPlaybackStateChangeListener() {
// ...
};
// add a Player.OnPlaybackStateChangeListener
playerClient.addOnPlaybackStateChangeListener(listener);
// remove a Player.OnPlaybackStateChangeListener
playerClient.removeOnPlaybackStateChangeListener(listener);
Each of the above methods has an overloaded method that receives a LifecycleOwner
instance as the first parameter. Listeners added with these overload methods will be automatically remove when the LifecycleOwner
instance is destroyed to avoid memory leakage.
public void addOnPlaybackStateChangeListener(Player.OnPlaybackStateChangeListener listener)
// receives a LifecycleOwner instance as the first parameter
public void addOnPlaybackStateChangeListener(LifecycleOwner owner,
Player.OnPlaybackStateChangeListener listener)
Exmaple:
Player.OnPlaybackStateChangeListener listener = new Player.OnPlaybackStateChangeListener() {
// ...
};
playerClient.addOnPlaybackStateChangeListener(lifecycleOwner, listener);
You can use the following methods of PlayerClient
to get the status of player:
getPlayingMusicItem()
getPlaybackState()
getPlayPosition()
-
getPlayProgress()
(milliseconds) -
getPlayProgressUpdateTime()
(milliseconds, It's theSystemClock.elapsedRealtime()
) -
getPlayingMusicItemDuration()
(milliseconds) getPlayMode()
getErrorCode()
getErrorMessage()
getAudioSessionId()
getBufferedProgress()
Tips: These methods for getting player status are not "exactly". For example, if you call the
getPlayingMusicItem()
immediately after you call theskipToNext()
, there is a high probability that you will not get theMusicItem
that is currently playing. This is because methods likeskipToNext()
that control the player simply return immediately after a command is send out, and do not wait until the command is executed. Therefore, if you call these methods to get the player status immediately after theskipToNext()
method returns, because the command may not be finished (or not executed at all), the obtained state may not be what you expect. If you want to get the status of the player exactly, you should use the listeners described in the previous section. These listeners will be called immediately when the state of the player changes.
More methods, please check the API Doc
It's a very tedious process to observe the player status and update the UI with a listener. To simplify this process, this library provides a PlayerViewModel
, it works fine with Jetpack DataBinding
framework.
PlayerViewModel
is used in the same way as other ViewModel
. The only difference is that after creating a PlayerViewModel
instance, you need use any of its init
methods to initialize it (example: init(Context, PlayerClient)
).
Example:
@Override
protected void onCreate(Bundle savedInstanceState) {
...
PlayerViewModel playerViewModel = new ViewModelProvider(this).get(PlayerViewModel.class);
// Avoid repeatedly initializing and creating redundant PlayerClient instance
if (playerViewModel.isInitialized()) {
mPlayerClient = playerViewModel.getPlayerClient();
return;
}
// Create a PlayerClient instance
mPlayerClient = PlayerClient.newInstance(this, MyPlayerService.class);
// Init PlayerViewModel instance
playerViewModel.init(this, mPlayerClient);
// Optional: enable automatically disconnect the PlayerClient when the ViewModel is destroyed
playerViewModel.setDisconnectOnCleared(true);
}
More details, please check the PlayerViewModel Doc
When the PlayerClient
instance is no longer needed, its disconnect()
method must be called to disconnect from the PlayerService
. The PlayerService
keep running in the background and does not terminate even if all clients are disconnected. The disconnected PlayerClient
instance can be reused. You can call its connect()
method again to re-establish the connection with the PlayerService
.
Example:
// disconnect
mPlayerClient.disconnect();
...
// re-establish the connection with the PlayerService
mPlayerClient.connect();
If you need to shutdown PlayerService
, you can call the shutdown()
method of PlayerClient
. After calling this method, PlayerService
will be shutdown and all clients will be disconnected automatically. If you want to run the PlayerService
again, you need to invoke the connect()
method again to connect, because the PlayerService
has been shutdown, invoke the connect()
method can restart it.
Example:
// shutdown PlayerService
mPlayerClient.shutdown();
// After shutdown PlayerService, all clients will be disconnected automatically.
// If you want restart PlayerService, you need call the connect() method again to connect.
mPlayerClient.connect();
The ExoPlayer
has provided support for downloading media and caching. You can use ExoPlayer in this framework. If you plan to use ExoPlayer, see wiki Use ExoPlayer
About downloading media and cache, see ExoPlayer document: Downloading media
You can set a sleep timer (in milliseconds) and set the action to be performed when the time is up (pause, stop, or shutdown PlayerService
).
You can use the following methods of PlayerClient
to start/cancel the sleep timer:
-
startSleepTimer(long time)
: start the sleep timer, when timeout, the player will be pause. -
startSleepTimer(long time, SleepTimer.TimeoutAction action)
: start the sleep timer, when timeout, theaction
will be performed. There are 3 actions:-
SleepTimer.TimeoutAction.PAUSE
: pause when timeout. -
SleepTimer.TimeoutAction.STOP
: stop when timeout. -
SleepTimer.TimeoutAction.SHUTDOWN
: shutdownPlayerService
when timeout.
-
cancelSleepTimer()
You can use the following methods of PlayerClient
to get the status of sleep timer:
isSleepTimerStarted()
-
getSleepTimerTime()
(milliseconds) -
getSleepTimerStartedTime()
(milliseconds, It's theSystemClock.elapsedRealtime()
) -
getSleepTimerElapsedTime()
(milliseconds)
You can use the following methods of PlayerClient
to observe the status of sleep timer:
addOnSleepTimerStateChangeListener(SleepTimer.OnStateChangeListener listener)
addOnSleepTimerStateChangeListener(LifecycleOwner owner, SleepTimer.OnStateChangeListener listener)
removeOnSleepTimerStateChangeListener(SleepTimer.OnStateChangeListener listener)
The callback methods of SleepTimer.OnStateChangeListener
:
-
onTimerStart(long time, long startTime, SleepTimer.TimeoutAction action)
: called when the sleep timer is started. -
onTimerEnd()
: called when the sleep timer is timeout or cancelled.
You can override the PlayerService#onCreate
method, and use the setMaxIDLETime(int minutes)
set the max IDLE time in onCreate
method.
When the player is not in PlaybackState.PLAYING
, and both preparing
and stalled
are false, PlayerService
is considered IDLE. When the PlayerService
is IDLE more than the max IDLE time, the PlayerService
will automatically shutdown. This can saves system resources and saves battery power.
Example:
public class MyPlayerService extends PlayerService {
...
@Override
public void onCreate() {
super.onCreate();
// set the PlayerService max IDLE time.
setMaxIDLETime(10); // 10 minutes
...
}
...
}
The default max IDLE time is -1
. When the max IDLE time set is less than or equal to 0
, this function will be turned off, that is, it is not enabled by default.
If your audio source does not support seek action, such as live streams, you should disable the seek action when creating MusicItem
instance.
Example:
MusicItem liveStream = new MusicItem.Builder()
.setTitle("Live Stream")
.setArtist("Live Stream")
.setDuration(0) // Or any value
.setUri("https://www.example.com/some_live_stream")
.setIconUri("https://www.example.com/icon.png")
// Forbid seek action
.setForbidSeek(true)
.build();
You can use the following methods to set whether to ignore the audio focus:
PlayerClient#setIgnoreAudioFocus(boolean ignoreAudioFocus)
PlayerService#setIgnoreAudioFocus(boolean ignoreAudioFocus)
You can use the following methods to determine whether to ignore the audio focus:
Example:
public void toggleIgnoreAudioFocus() {
mPlayerClient.setIgnoreAudioFocus(!mPlayerClient.isIgnoreAudioFocus());
}
If ignore audio focus, the music playback won't be paused by other audio apps.
Warning! If ignore audio focus, the player will not automatically pause playing during an incoming call! If you want to ignore the audio focus and automatically pause the playback during an incoming call, you need to request android.permission.READ_PHONE_STATE
permission at runtime.
Example:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
Request permission at runtime:
boolean noPermission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED;
if (noPermission) {
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.READ_PHONE_STATE}, requestCode);
}
This library is base on MediaSession
framework, and provides compatibility with the MediaSession
framework.
- The
PlayerService
extends theMediaBrowserServiceCompat
class, so you can use thePlayerService
class likeMediaBrowserServiceCompat
. - You can override
PlayerService#onCreateMediaSessionCallback()
to provide your ownMediaSessionCompat.Callback
. The return type of this method isPlayerService.MediaSessionCallback
, which is a subtype ofMediaSessionCompat.Callback
. - You can use
PlayerService#setMediaSessionFlags(int flags)
to set the Flags ofMediaSessionCompat
.
More details, please check the PlayerService Doc
This library provides support for AppWidget. You can use the AppWidgetPlayerState
in AppWidgetProvider
to get the player state.
In order to update the AppWidget
after the player state changes, please override the getAppWidget()
of PlayerService and return the Class object of your AppWidgetProvider
in this method.
public class MyPlayerService extends PlayerService {
// ...
@Nullable
@Override
protected Class<? extends AppWidgetProvider> getAppWidget() {
return ExampleAppWidgetProvider.class;
}
}
If there are multiple AppWidgets in your application that need to be updated, you can override the getAppWidgets()
. If the getAppWidgets()
is overloaded and returns a non-null
value, the getAppWidget()
will no longer work.
public class MyPlayerService extends PlayerService {
// ...
@Nullable
@Override
protected List<Class<? extends AppWidgetProvider>> getAppWidgets() {
List<Class<? extends AppWidgetProvider>> appWidgets = new ArraysList<>();
appWidgets.add(ExampleAppWidgetProviderA.class);
appWidgets.add(ExampleAppWidgetProviderB.class);
appWidgets.add(ExampleAppWidgetProviderC.class);
return appWidgets;
}
}
Get player state:
public class ExampleAppWidgetProvider extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
// get player state
AppWidgetPlayerState state = AppWidgetPlayerState.getPlayerState(context, MyPlayerService.class);
if (state == null) {
return;
}
// ...
}
}
When the player state changes, an ACTION_PLAYER_STATE_CHANGED
("snow.player.appwidget.action.PLAYER_STATE_CHANGED") broadcast will be sent. The Category of the broadcast is the complete class name of your PlayerService
(such as snow.demo.MyPlayerService
). You can add an <intent-filter>
for your AppWidgetProvider
, and update your AppWidget
when the broadcast is received.
Note: Android restricts the sending of implicit broadcasts, so this method may not be effective on high-version Android systems. It is recommended to use the method described above first.
Example:
First, declare and use the following permissions in your app's AndroidManifest.xml
:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="snow.player.debug">
<!-- Declare and use permissions -->
<permission android:name="snow.player.appwidget.permission.UPDATE_APPWIDGET" />
<uses-permission android:name="snow.player.appwidget.permission.UPDATE_APPWIDGET" />
...
</manifest>
Then, add a <intent-filter>
element to your AppWidgetProvider
:
<receiver
android:name=".ExampleAppWidgetProvider"
android:exported="true">
...
<!--add a intent-filter element-->
<intent-filter>
<action android:name="snow.player.appwidget.action.PLAYER_STATE_CHANGED" />
<!-- The android:name of this category is the full class name of your PlayerService -->
<category android:name="snow.player.debug.MyPlayerService" />
</intent-filter>
</receiver>
Finally, update the AppWidget when the broadcast is received:
public class ExampleAppWidgetProvider extends AppWidgetProvider {
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
// Update AppWidget when receiving broadcast
if (AppWidgetPlayerState.ACTION_PLAYER_STATE_CHANGED.equals(intent.getAction())) {
AppWidgetManager am = AppWidgetManager.getInstance(context);
onUpdate(context, am, am.getAppWidgetIds(new ComponentName(context, ExampleAppWidgetProvider.class)));
}
}
// ...
}
You can use the static method AppWidgetPlayerState.getPlayerState(Context, Class<? extends PlayerService>)
in the onUpdate
method of AppWidgetProvider
to get the latest player state.
public static AppWidgetPlayerState getPlayerState(
Context context,
Class<? extends PlayerService> playerService
)
Param:
-
context
: Context object, can't be null. -
playerService
: The Class object of your PlayerService, can't be null.
Reture type:
-
AppWidgetPlayerState
: This type have some getter methods to get the status of the player.
End