Creating a live video playback app in Java using Android Studio

This guide focuses on the minimum required to start playback in an Android app written in Java and built using Android Studio 3.1.3. You can find Android Studio on developer.android.com/studio.

For simplicity, this example loads the latest broadcast available through the broadcast metadata API.

If you want to clone a sample project similar to this guide, see the Player example for Android project on our GitHub page.

Create a new Application

  • Open Android Studio
  • Choose Start a new Android Studio project
  • Enter a suitable application name and your company domain.
  • Select the Phone and Tablet form factor.
  • Set the Minimum SDK to at least API 16: Android 4.1, which is the oldest API supported by the BroadcastPlayer.
  • Choose the Empty Activity template.
  • Enter a suitable Activity Name, make sure Backwards Compatibility (AppCompat) is checked and let Android Studio finish generating the project.

Add the Bambuser SDK for Android

  • Log in to the Bambuser site and download the latest SDK bundle for Android from the Developer page.
  • Open the downloaded zip file and extract it.
  • In Android Studio, in the project tree on the left-hand-side, right click on your app module and choose Open Module Settings.
  • Click the green + in the upper left corner of the Project Structure screen.
  • Choose Import .JAR/.AAR Package.
  • Navigate to the SDK files you extracted and import the .aar file.
  • You may now need to close and reopen the Project Structure screen, before the library module shows up in the list. Some recent versions of Android Studio fail to add new modules to the settings.gradle file. If this happens, add include ':libbambuser' manually at the end of the file and sync the project, then reopen the Project Structure screen.
  • Keep the app module selected in the Project Structure screen and bring up the Dependencies pane.
  • Click the green + in the upper right corner and choose Module dependency.
  • Choose the imported library module, click OK

In order to do http requests in a convenient way, add a dependency on the OkHttp library.

  • Click the green + in the upper right corner again and choose Library dependency.

  • Search for okhttp and choose the latest version of the library, as shown below.

The list of dependencies for the app should now contain both the libbambuser module and the okhttp library.

  • Click OK to return from the Project Structure screen.

Add required Android app permissions

The Bambuser player for Android requires at least the INTERNET permission.

  • Open the manifests/AndroidManifest.xml file in the Project tree on the left.

  • Add the permission tag before the <application /> tag:

<uses-permission android:name="android.permission.INTERNET"/>

Add a SurfaceView for video playback

In activity_player.xml replace the auto-generated TextView with a SurfaceViewWithAutoAR and a TextView for showing player state.

<com.bambuser.broadcaster.SurfaceViewWithAutoAR
    android:id="@+id/VideoSurfaceView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>
<TextView
    android:id="@+id/PlayerStatusTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textColor="@color/colorAccent"
    android:textSize="20sp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"/>

In the generated PlayerActivity.java import and add references to the SurfaceView and TextView from the layout.

import android.view.SurfaceView;
import android.widget.TextView;
// ...

public class PlayerActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        mVideoSurface = (SurfaceView) findViewById(R.id.VideoSurfaceView);
        mPlayerStatusTextView = (TextView) findViewById(R.id.PlayerStatusTextView);
    }

    SurfaceView mVideoSurface;
    TextView mPlayerStatusTextView;
}

Authentication

To access broadcasts, your app needs to identify itself to Bambuser. Head over to the Developer page on the Bambuser site and get the Sandbox applicationId which we'll use when constructing the BroadcastPlayer.

To access the metadata API, your app also needs an API key. Get an API key for your Sandbox environment from the Developer page on the Bambuser site.

Remember to replace the Sandbox id and key with a Production applicationId and a Production API key before you release your app!

Preparations for playback

Prepare PlayerActivity.java for integration of the BroadcastPlayer by importing the related classes, adding an implementation of the BroadcastPlayer.Observer interface, and inserting your Sandbox applicationId and API key.

import com.bambuser.broadcaster.BroadcastPlayer;
import com.bambuser.broadcaster.PlayerState;
// ...
public class PlayerActivity extends AppCompatActivity {
    private static final String APPLICATION_ID = "GFZalqkR5iyZcIgaolQmA";
    private static final String API_KEY = "Fmn10d2ffa4mnxw8ec13z";
    // ...
    BroadcastPlayer.Observer mBroadcastPlayerObserver = new BroadcastPlayer.Observer() {
        @Override
        public void onStateChange(PlayerState playerState) {
            if (mPlayerStatusTextView != null)
                mPlayerStatusTextView.setText("Status: " + playerState);
        }
        @Override
        public void onBroadcastLoaded(boolean live, int width, int height) {
        }
    };
    // ...
}

Get resourceUri for latest broadcast

To demonstrate how the Bambuser metadata API can be used, we fetch information about the latest broadcasts in your Sandbox environment, and choose the latest one for playback.

Import classes needed for OkHttp and JSON parsing, initiate an asynchronous http request to https://api.bambuser.com/broadcasts using your API key in the onResume() method, then parse the latest resourceUri from the results. The network request must be enqueued on a background thread, while anything related to the UI must be done on the main thread of the app.

Remember to cancel any outstanding http request and drop the reference to the SurfaceView in the onPause() method, to prevent a late response from causing playback in the background.

import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
// ...
public class PlayerActivity extends AppCompatActivity {
    // ...

    @Override
    protected void onPause() {
        super.onPause();
        mOkHttpClient.dispatcher().cancelAll();
        mVideoSurface = null;
    }

    @Override
    protected void onResume() {
        super.onResume();
        mVideoSurface = (SurfaceView) findViewById(R.id.VideoSurfaceView);
        mPlayerStatusTextView.setText("Loading latest broadcast");
        getLatestResourceUri();
    }

    void getLatestResourceUri() {
        Request request = new Request.Builder()
        .url("https://api.bambuser.com/broadcasts")
        .addHeader("Accept", "application/vnd.bambuser.v1+json")
        .addHeader("Content-Type", "application/json")
        .addHeader("Authorization", "Bearer " + API_KEY)
        .get()
        .build();
        mOkHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(final Call call, final IOException e) {
                runOnUiThread(new Runnable() { @Override public void run() {
                    if (mPlayerStatusTextView != null)
                        mPlayerStatusTextView.setText("Http exception: " + e);
                }});
            }
            @Override
            public void onResponse(final Call call, final Response response) throws IOException {
                String body = response.body().string();
                String resourceUri = null;
                try {
                    JSONObject json = new JSONObject(body);
                    JSONArray results = json.getJSONArray("results");
                    JSONObject latestBroadcast = results.optJSONObject(0);
                    resourceUri = latestBroadcast.optString("resourceUri");
                } catch (Exception ignored) {}
                final String uri = resourceUri;
                runOnUiThread(new Runnable() { @Override public void run() {
                    initPlayer(uri);
                }});
            }
        });
    }

    void initPlayer(String resourceUri) {
        if (resourceUri == null) {
            if (mPlayerStatusTextView != null)
                mPlayerStatusTextView.setText("Could not get info about latest broadcast");
            return;
        }
        if (mVideoSurface == null) {
            // UI no longer active
            return;
        }
    }
    // ...
    final OkHttpClient mOkHttpClient = new OkHttpClient();
}

Initiate a BroadcastPlayer

Import the BroadcastPlayer class, create an instance of it in the initPlayer(resourceUri) method, using your resourceUri, applicationId, SurfaceView and BroadcastPlayer.Observer.

Call load() to start loading the requested resourceUri.

Make sure to close the BroadcastPlayer when the Activity is no longer active.

import com.bambuser.broadcaster.BroadcastPlayer;
// ...
public class PlayerActivity extends AppCompatActivity {
    @Override
    protected void onPause() {
        // ...
        if (mBroadcastPlayer != null)
            mBroadcastPlayer.close();
        mBroadcastPlayer = null;
    }

    // ...
    void initPlayer(String resourceUri) {
        // ...
        if (mBroadcastPlayer != null)
            mBroadcastPlayer.close();
        mBroadcastPlayer = new BroadcastPlayer(this, resourceUri, APPLICATION_ID, mBroadcastPlayerObserver);
        mBroadcastPlayer.setSurfaceView(mVideoSurface);
        mBroadcastPlayer.load();
    }

    // ...
    BroadcastPlayer mBroadcastPlayer;
}

Running the app

The above should be enough for playback of the latest video. Connect your mobile device to your PC and follow the Android Developers guide for running your app.

Provided that you have broadcast something to your Sandbox environment, the player should automatically find and play the latest broadcast.

Adding player controls

To use Android's standard MediaController widget, start by importing the class. Check the player state in the onStateChange(playerState) callback we added earlier. When an archived broadcast is successfully loaded, construct an instance of the MediaController, attach it to the BroadcastPlayer and anchor it to the SurfaceView. Import the MotionEvent class and show / hide the MediaController when the user taps the player.

import android.widget.MediaController;
import android.view.MotionEvent;
// ...
public class PlayerActivity extends AppCompatActivity {
    @Override
    protected void onPause() {
        // ...
        if (mMediaController != null)
            mMediaController.hide();
        mMediaController = null;
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (ev.getActionMasked() == MotionEvent.ACTION_UP && mBroadcastPlayer != null && mMediaController != null) {
            PlayerState state = mBroadcastPlayer.getState();
            if (state == PlayerState.PLAYING ||
                state == PlayerState.BUFFERING ||
                state == PlayerState.PAUSED ||
                state == PlayerState.COMPLETED) {
                if (mMediaController.isShowing())
                    mMediaController.hide();
                else
                    mMediaController.show();
            } else {
                mMediaController.hide();
            }
        }
        return false;
    }
    // ...
    BroadcastPlayer.Observer mBroadcastPlayerObserver = new BroadcastPlayer.Observer() {
        @Override
        public void onStateChange(PlayerState playerState) {
            // ...
            if (playerState == PlayerState.PLAYING || playerState == PlayerState.PAUSED || playerState == PlayerState.COMPLETED) {
                if (mMediaController == null && mBroadcastPlayer != null && !mBroadcastPlayer.isTypeLive()) {
                    mMediaController = new MediaController(PlayerActivity.this);
                    mMediaController.setAnchorView(mVideoSurface);
                    mMediaController.setMediaPlayer(mBroadcastPlayer);
                }
                if (mMediaController != null) {
                    mMediaController.setEnabled(true);
                    mMediaController.show();
                }
            } else if (playerState == PlayerState.ERROR || playerState == PlayerState.CLOSED) {
                if (mMediaController != null) {
                    mMediaController.setEnabled(false);
                    mMediaController.hide();
                }
                mMediaController = null;
            }
        }
        // ...
    };
    // ...
    MediaController mMediaController = null;
}

When playing an archived broadcast in the app, controls for pause, play and seeking should be available, as in the screenshot below.