-
-
Notifications
You must be signed in to change notification settings - Fork 45
[EN] 1.Getting Started
Directory:
- Add dependency
- Getting started
- Connect to PlayerService
- Playlist
- Control player
- Config player
- Observe player state
- Get player state
- PlayerViewModel
- Disconnect
- Shutdown PlayerService
- Others
- Make sure you have the jitpack repositories included in the
build.gradle
file in the root of your project.
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
implementation 'com.github.jrfeng.snow:player:1.0.1'
}
- Request permission.
<!-- for start foreground service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- for play in the background -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- for play local music -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- for play network music -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Note: Android 6.0 (API level 23) need request android.permission.READ_EXTERNAL_STORAGE
permission at runtime.
- Create PlayerService
Create a class and let it extends the snow.player.PlayerService
, and annotate it with @PersistenceId
annotation. You don't need to override any methods of this class.
@PersistenId("MyPlayerService")
public MyPlayerService extends PlayerService {
}
The @PersistenceId
annotation is used to set a persistent ID for the your PlayerService
, which will be used for the persistence of the PlayerService
state. If you do not use the @PersistenceId
annotation to set the persistent ID, the persistent ID defaults to the full class name of your PlayerService
(such as snow.demo.MyPlayerService
). It is recommended to set a persistent ID for your PlayerService
so that even if the PlayerService
is renamed, the state will not be lost.
- Register PlayerService on your
AndroidManifest.xml
file.
<service android:name="snow.demo.MyPlayerService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
- Connect to
PlayerService
.
// create a PlayerClient instance
PlayerClient playerClient = PlayerClient.newInstance(context, MyPlayerService.class);
// connect to MyPlayerService
playerClient.connect(new PlayerClient.OnConnectCallback() {
@Override
public void onConnected(boolean success) {
// DEBUG
Log.d("App", "connect: " + success);
}
});
- Create a Playlist.
private Playlist createPlaylist() {
MusicItem song1 = new MusicItem.Builder()
.setTitle("逍遥叹")
.setArtist("胡歌")
.setDuration(313520)
.setUri("http://music.163.com/song/media/outer/url?id=4875306")
.setIconUri("http://p1.music.126.net/4tTN8CnR7wG4E1cauIPCvQ==/109951163240682406.jpg")
.build();
MusicItem song2 = new MusicItem.Builder()
.setTitle("终于明白")
.setArtist("动力火车")
.setDuration(267786)
.setUri("http://music.163.com/song/media/outer/url?id=4875305")
.build();
MusicItem song3 = new MusicItem.Builder()
.setTitle("千年泪")
.setArtist("Tank")
.setDuration(260946)
.setUri("http://music.163.com/song/media/outer/url?id=150371")
.setIconUri("http://p2.music.126.net/0543F-ln2Apdiopez_jbsA==/109951163244853571.jpg")
.build();
MusicItem song4 = new MusicItem.Builder()
.setTitle("此生不换")
.setArtist("青鸟飞鱼")
.setDuration(265000)
.setUri("http://music.163.com/song/media/outer/url?id=25638340")
.setIconUri("http://p2.music.126.net/UyDVlWWgOn8p8U8uQ_I1xQ==/7934075907687518.jpg")
.build();
return new Playlist.Builder()
.append(song1)
.append(song2)
.append(song3)
.append(song4)
.build();
}
- Set playlist and start playing music.
// create a Playlist instance.
Playlist playlist = createPlaylist();
// set playlist and start playing music
playerClient.setPlaylist(playlist, true);
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 the indexposition
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 addOnPlaylistChangeListener(Player.OnPlaylistChangeListener Listener)
method of Playerclient
register a Player.OnPlaylistChangeListener
listener to listen for changes to the playlist. When the playlist changes, the onPlaylistChanged(PlaylistManager playlistManager, int position)
method of the listener 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 (counting from 0
).
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) {
// ...
}
});
// ...
}
});
Alternatively, you can use PlaylistLiveData
to listen and get the latest playlists. 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 because 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();
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()
-
getSleepTimerStartedTime()
: base onSystem.currentTimeMillis()
getSleepTimerElapsedTime()
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
, the 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.
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.
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.
Example:
<receiver android:name=".MyAppWidgetProvider">
...
<intent-filter>
<action android:name="snow.player.appwidget.action.PLAYER_STATE_CHANGED" />
<category android:name="snow.demo.MyPlayerService"/>
</intent-filter>
</receiver>
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