Skip to content

[EN] 1.Getting Started

jrfeng edited this page Dec 20, 2020 · 118 revisions

Directory:

Add dependency

  1. 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' }
    }
}
  1. Add a dependency in the build.gradle file of your app module
dependencies {
    implementation 'com.github.jrfeng.snow:player:1.0.1'
}
  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.

  1. 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.

  1. 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>

Getting started

  1. 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);
    }
});
  1. 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();
}
  1. Set playlist and start playing music.
// create a Playlist instance.
Playlist playlist = createPlaylist();

// set playlist and start playing music
playerClient.setPlaylist(playlist, true);

Connect to PlayerService

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.

Playlist

Set playlist

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 index 0 music of the playlist.
  • setPlaylist(Playlist playlist, int position, boolean play): set a new playlist and whether to play the index position music of the playlist.

Note: Do not set a huge playlist (max size is 1000), a huge playlist maybe will cause Binder to crash!

Get playlist

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) {
        // ...
    }
});

Edit playlist

After connected, you can use the following methods of PlayerClient to edit the player's playlist:

  • setNextPlay(MusicItem musicItem): set the next MusicItem to play. If the MusicItem is already contains in the playlist, it will be moved to the next top play position, otherwise MusicItem will be inserted to the next top playback position.
  • insertMusicItem(int position, MusicItem musicItem): Insert a MusicItem into the position in the playlist. If the MusicItem is already contains in the playlist, it will be moved to the position of playlist.
  • appendMusicItem(MusicItem musicItem): append a MusicItem to the end of the playlist. If the MusicItem is already contains in the playlist, it will be moved to the end of the playlist.
  • moveMusicItem(int fromPosition, int toPosition): move the MusicItem at fromPosition in the playlist to toPosition.
  • removeMusicItem(MusicItem musicItem): remove MusicItem from playlist. If the MusicItem is not contains in the playlist, just ignored.

Observe playlist

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) {
        // ...
    }
});

Control player

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

Config player

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 is false)
  • 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 is false)
  • 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

Observe player state

You can use the following methods of PlayerClient to observe the status of player:

  1. addOnPlaybackStateChangeListener(PlayerClient.OnPlaybackStateChangeListener listener)
  2. addOnPlaybackStateChangeListener(Player.OnPlaybackStateChangeListener listener)
  3. addOnPrepareListener(Player.OnPrepareListener listener)
  4. addOnBufferedProgressChangeListener(Player.OnBufferedProgressChangeListener listener)
  5. addOnAudioSessionChangeListener(PlayerClient.OnAudioSessionChangeListener listener)
  6. addOnPlayingMusicItemChangeListener(Player.OnPlayingMusicItemChangeListener listener)
  7. addOnPlaylistChangeListener(Player.OnPlaylistChangeListener listener)
  8. addOnPlayModeChangeListener(Player.OnPlayModeChangeListener listener)
  9. addOnSeekCompleteListener(Player.OnSeekCompleteListener listener)
  10. addOnConnectStateChangeListener(PlayerClient.OnConnectStateChangeListener listener)
  11. addOnStalledChangeListener(Player.OnStalledChangeListener listener)
  12. 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);

Get player state

You can use the following methods of PlayerClient to get the status of player:

  • getPlayingMusicItem()
  • getPlaybackState()
  • getPlayPosition()
  • getPlayProgress() (milliseconds)
  • getPlayProgressUpdateTime() (milliseconds, It's the SystemClock.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 the skipToNext(), there is a high probability that you will not get the MusicItem that is currently playing. This is because methods like skipToNext() 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 the skipToNext() 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

PlayerViewModel

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

Disconnect

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();

Shutdown PlayerService

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();

Others

Sleep timer

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, the action will be performed. There are 3 actions:
    • SleepTimer.TimeoutAction.PAUSE: pause when timeout.
    • SleepTimer.TimeoutAction.STOP: stop when timeout.
    • SleepTimer.TimeoutAction.SHUTDOWN: shutdown PlayerService when timeout.
  • cancelSleepTimer()

You can use the following methods of PlayerClient to get the status of sleep timer:

  • isSleepTimerStarted()
  • getSleepTimerTime()
  • getSleepTimerStartedTime(): base on System.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.

Max IDLE time

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.

Forbid seek action

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();

Ignore audio focus

You can use the following methods to set whether to ignore the audio focus:

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.

MediaSession

This library is base on MediaSession framework, and provides compatibility with the MediaSession framework.

More details, please check the PlayerService Doc

AppWidget

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:


End