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

SdkConsts.INTENT_URL

Expects a String and denotes the URL to the content. This is a mandatory parameter

SdkConsts.INTENT_START_PLAYING

Expects a boolean. If true playback will start automatically once enough data is available.

SdkConsts.INTENT_POSITION_TO_PLAY

Expects a long. The playback start position in microseconds. (default: 0)

SdkConsts.INTENT_DRM_CONFIGURATION

Expects a DrmConfiguration. The DRM configuration for encrypted content. See DRMtoday Integration. (default: null)

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:

  1. If we have no service connection, we delegate the view to release the player and any locks acquired when the activity started.

  2. If isFinishing() returns true, 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.

  3. 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);
Previous topic: Project Setup
Next topic: Advanced Setup