Skip to content

[EN] 2.Advanced

jrfeng edited this page Feb 19, 2023 · 11 revisions

Directory:

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

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

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

Downloading media & Cache

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

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() (milliseconds)
  • getSleepTimerStartedTime() (milliseconds, It's the SystemClock.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.

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, this 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.

Warning!

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

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.

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;
        }

        // ...
    }
}

Update AppWidget with ACTION_PLAYER_STATE_CHANGED

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:


End