Player Basics
Player Basics¶
This chapter explains the steps required to create a custom player
Activity
and start playback manually or using an Intent. The chapter
follows closely the examples demonstrated in the demos
application
(see Demos).
Activity and Layout¶
The SDK comes with a custom view component: the PlayerView
.
You will need to add this view to your Activities layout. The Activity
that hosts the view can be any Android Activity or Fragment
implementation. However, if you want to support orientation changes, where
the user can turn the device, consider adding orientation
to the
Activities configChanges
setup in your Manifest. With that setting in
place, you need to handle the orientation change manually. The Activity
will not be recreated by the system. However, this will allow you to
continue playback without any interruptions.
In addition to the orientation
configuration, you should also consider
using a custom theme for your player Activity in order to be able to
properly customize the behavior of the application’s toolbar and the
systems status bar.
We can start implementing a custom player Activity using the following entry in the Manifest:
<activity
android:name=".SimplePlaybackDemo"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/title_activity_simple_playback_demo"
android:theme="@style/PlayerTheme"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.castlabs.sdk.demos.MainActivity"/>
</activity>
Here, we are implementing a custom Activity SimplePlaybackDemo
in our
application package and setting the configChanges
accordingly to support
orientation changes while playback continues.
Our demo application leverages Googles Material Design theme and the extended theme of our player activity consists of only one entry:
<style name="PlayerTheme" parent="AppTheme">
<item name="colorPrimaryDark">@color/black</item>
</style>
We extend the application’s theme and change the primary color to black. This ensures that the status bar is rendered with a black background in Android M.
The layout of the demo Activity contains only one component: the
PlayerView
. The layout file looks like this:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
tools:context="com.castlabs.sdk.demos.SimplePlaybackDemo">
<com.castlabs.android.player.PlayerView
android:id="@+id/player_view"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.castlabs.android.player.PlayerView>
</FrameLayout>
We are using a simple FrameLayout
and adding the PlayerView
as its
primary centered component.
Starting Playback¶
Now that we have the Activity in place and a PlayerView
added to its
layout, we can already start playback. All interactions with the player
happen through a PlayerController
instance. The PlayerView
exposes a controller through its
PlayerView.getPlayerController()
method. You can use the player
controller for example, in the Activity’s onCreate()
callback:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple_playback_demo);
// Get the view components from the layout
playerView = (PlayerView) findViewById(R.id.player_view);
// Get the player controller
PlayerController controller = playerView.getPlayerController();
// Start playback of a clear stream at position 0
controller.open(new PlayerConfig.Builder("http://sample.url/manifest.mpd").get());
}
Here, we first get access to the PlayerView component. Then we use the
component to access the PlayerController and send
a PlayerConfig
to the controller’s
open(PlayerConfig)
method. Please note that it might be more convenient to use the Intent extra data to send
a Bundle
to the PlayerController
to configure it and start
playback (see Starting Playback from an Intent).
The example above will already be enough to start playback, but we are missing a few mandatory callbacks to ensure that the Activity behaves correctly when the user leaves it through the back button or switches applications and comes back to the activity.
Starting Playback from a PlayerConfig
¶
As demonstrated before, one way of starting playback is through a PlayerConfig
object.
An instance of this object can be attained through its Builder
subclass.
In order to instantiate a Builder, the content url is required. This is the only mandatory setting to start playback.
You can explore the Builder
to get a complete overview of
all the possible configuration options.
The PlayerConfig
object can be also converted to a Bundle
and it implements the Parcelable
interface.
At the same time, you can populate a PlayerConfig
Builder
with an existing PlayerConfig
object or with a Bundle
with the fromConfig(PlayerConfig)
and fromBundle(Bundle)
methods respectively.
Starting Playback from an Intent
¶
Instead of configuring the PlayerController
programatically and
passing a PlayerConfig
to its open
method, it can be more
convenient to pack all required parameters into a Bundle
. The bundle
can then be sent the through an Intent
and passed to the
PlayerController
open(Bundle)
method to
initialize and configure the controller and start playback. See the
API documentation
of the
open
method to get a list of all the supported bundle keys. The most
important parameters are the following:
Key |
Description |
---|---|
Expects a |
|
Expects a |
|
Expects a |
|
Expects a |
The API documentation
contains
a complete list of supported intent parameters, including support for
additional query parameters and side-loaded
subtitle tracks.
With the support for bundles, you can now prepare the Intent, for example, in your main Activity and then start your player Activity already configured. Your main Activity might contain code like this to prepare the Intent:
Intent intent = new Intent(this, PlayerActivity.class);
intent.putExtra(SdkConsts.INTENT_URL, "...");
intent.putExtra(SdkConsts.INTENT_POSITION_TO_PLAY, 0);
intent.putExtra(SdkConsts.INTENT_START_PLAYING, false);
startActivity(intent);
In the PlayerActivity
, you could then take the intent parameters and pass
them to the PlayerController
to configure it and start playback:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
// Get the view components from the layout
playerView = (PlayerView) findViewById(R.id.player_view);
if (getIntent() != null) {
try {
playerView.getPlayerController().open(getIntent().getExtras());
} catch (Exception e) {
Log.e(TAG, "Error while opening player: " + e.getMessage(), e);
...
}
}
}
Please note the try/catch
block around the call to open()
. This is
required because the open
method will check for mandatory parameters and
their types and throw an exception if either a mandatory parameter is
missing or a parameter type does not match. The player will then delegate all
further data loading to a dedicated thread, so you should always setup the
error handlers appropriately (see Error Handling) before the
call to open
.
Playback Local Content¶
The player can be use to playback content that is stored on the device. For that, you need to specify the content URL with a file:// or content:// scheme prefix. The same applies also for side-loaded tracks.
Mandatory Callbacks¶
In order to ensure that the player activity behaves correctly when it’s finished or the user leaves the application and comes back to the Activity, we need to overwrite a few callbacks to delegate the Activity’s life cycle events and shutdown or resume playback accordingly. This will also ensure that the Activity acquires and releases locks on the devices screen saver to ensure that the display stays on while a video is played.
The PlayerView
exposes a PlayerViewLifecycleDelegate
through its PlayerView.getLifecycleDelegate()
method. The
delegate instance can be used to ensure the correct behavior for basic
Activity life cycle changes. We need to overwrite the following methods in
the Activity implementation:
@Override
protected void onStart() {
super.onStart();
playerView.getLifecycleDelegate().start(this);
}
@Override
protected void onResume() {
super.onResume();
playerView.getLifecycleDelegate().resume();
}
@Override
protected void onStop() {
super.onStop();
playerView.getLifecycleDelegate().releasePlayer(false);
}
Overwriting onStart
and delegating to start()
ensures that the required
locks are acquired. The call to resume()
ensures that playback will be
resumed when the user comes back to the Activity after leaving the app.
Finally, releasePlayer()
is called in
onStop()
to save the current playback state and shutdown the player.
This needs to be called to release any resources occupied.
Please note that up until now, no errors are handled. You need to setup the required error handlers appropriately (see Error Handling) to catch any runtime issues during download or playback.
Background Playback¶
The SDK comes with a PlayerService
implementation. The service can
be used to send the player to the background continuing with audio-only
playback and recover the player when coming back to the Activity.
Because a Bound-Service is used, the
usage pattern slightly differs. Essentially, you will first need to connect to
the service and postpone all usage of the PlayerController
until the
service is connected. The demos
application contains a working example of
the PlayerService
integration. The main concepts are as follows.
The Activity layout and setup is the same. You still add the PlayerView
to
your layout and use it to access the PlayerController
, but you have to
wait until the PlayerService
is connected and initialized.
public class PlayServiceDemo extends Activity {
private PlayerView playerView;
private PlayerService.Binder playerServiceBinder;
private final ServiceConnection playerServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
playerServiceBinder = (PlayerService.Binder) service;
boolean backgrounded = playerServiceBinder.init(playerView);
if (!backgrounded) {
playerView.getPlayerController().open(...);
}
playbackBundle = null;
}
public void onServiceDisconnected(ComponentName name) {
playerServiceBinder.release(playerView, true);
playerServiceBinder = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple_playback_demo);
playerView = (PlayerView) findViewById(R.id.player_view);
}
@Override
protected void onStart() {
super.onStart();
playerView.getLifecycleDelegate().start(this);
}
@Override
protected void onResume() {
super.onResume();
Intent serviceIntent = new Intent(this, PlayerService.class);
bindService(serviceIntent, playerServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
if (playerServiceBinder == null) {
playerView.getLifecycleDelegate().releasePlayer(false);
} else {
if (!isFinishing()) {
playerServiceBinder.releaseToBackground(
playerView,
NOTIFICATION_ID,
createBackgroundNotification(),
true);
} else {
playerServiceBinder.release(playerView, true);
}
unbindService(playerServiceConnection);
playerServiceBinder = null;
}
}
...
}
The differences in contrast to the non-service implementation are in the work
we can do in onCreate()
and how the life cycle callbacks are implemented.
In onCreate()
we are not allowed to use the PlayerController
or start
playback. We need to wait for a service connection before we can get access
to a valid and initialized PlayerController
.
Because we are using a service, we are not delegating directly to the player
view’s life cycle event in onResume()
. Instead, we make sure that a
connection to the player service is established. The binder implementation will
then take care of initializing playback or to recovering from a background
playback session.
The call to playerServiceBinder.init(playerView)
in the service connection
is mandatory to initialize the player view and its controller. After that
call, the controller is ready to be used and we can start playback if we are
not already recovering from a background session. This is also the place where
you can add listeners to the PlayerController
.
With the player service, we have to cover a few use cases on the onStop()
callback implementation:
If we have no service connection, we delegate the view to release the player and any locks acquired when the activity started.
If
isFinishing()
returnstrue
, we assume the user wants to actually leave the Activity and not start background playback. In that case we release the player through the binder implementation.If the activity is not finishing, we send the player to the background using the service binder implementation.
In both cases, we unbind from the service and reset the binder in the onStop()
callback.
Audio Focus¶
By default, the PRESTOplay SDK for Android complies to the Audio Focus guidelines. This implies that the player will request audio focus on playback start, and thus other compliant apps playing media in the background will be paused.
This mechanism is bidirectional, so if the PRESTOplay SDK for Android is playing content and another app requests audio focus, the player will be paused.
This behaviour is encouraged by Google and it’s strongly recommended. Nevertheless, if you consider that this does not apply to your app’s use case, you can either disable this behaviour completely, or provide a callback to perform custom operations whenever the audio focus is lost.
Disabling Audio Focus Management¶
In order to disable the audio focus management, you can set the SdkConsts.INTENT_AUDIO_ATTRIBUTES
key to SdkConsts.AUDIO_ATTRIBUTES_FOCUS_DISABLED
:
Bundle bundle = new Bundle();
bundle.putExtra(SdkConsts.INTENT_URL, "http://sample.url/manifest.mpd");
// Disable Audio Focus logic
intent.putExtra(SdkConsts.INTENT_AUDIO_ATTRIBUTES, SdkConsts.AUDIO_ATTRIBUTES_FOCUS_DISABLED);
playerController.open(bundle);
Or set the audioAttributes
in
the PlayerConfig
to the same value:
PlayerConfig config = new PlayerConfig.Builder("http://sample.url/manifest.mpd")
.audioAttributes(SdkConsts.AUDIO_ATTRIBUTES_FOCUS_DISABLED)
.get();
playerController.open(config);
Note that you can also customize the AudioAttributes
object.
PlayerConfig config = new PlayerConfig.Builder("http://sample.url/manifest.mpd")
.audioAttributes(new AudioAttributes.Builder()
.setContentType(C.CONTENT_TYPE_MUSIC)
.setFlags(C.FLAG_AUDIBILITY_ENFORCED)
.setUsage(C.USAGE_MEDIA)
.get())
.get();
playerController.open(config);
Callback for Audio Focus loss¶
The PRESTOplay SDK for Android also offers the possibility to fine-tune the app’s behaviour whenever audio focus is lost
through a callback set in the PlayerController
.
Note that setting this callback will override the default behaviour of the Player, meaning that it won’t automatically pause anymore on its own.
playerController.setAudioFocusCallback(new AudioFocusCallback() {
@Override
public void onAudioFocusLost() {
// Custom Audio Focus loss logic
playerController.pause(); // default behaviour
}
});
// ...
playerController.open(config);