How to create a live broadcasting app in Kotlin using Android Studio

This guide is based on Android Studio 3.4.1.

This guide focuses on the bare minimum required to start broadcasting. We're using Android Studio, which you can find on developer.android.com/studio. Make sure to familiarize yourself with the example apps in the SDK bundles for a more complete overview.

Create a new Application

  • Open Android Studio
  • Choose File -> New -> New project...
  • Choose the Empty Activity template.
  • Enter a suitable application name and your company domain.
  • Set the Minimum SDK to at least API 16: Android 4.1, which is the oldest API supported by the Broadcaster.
  • Choose Kotlin as programming language (See our Java guide if you prefer Java)

Add the broadcast SDK

  • 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 + button in the modules drawer

  • Choose Import .JAR/.AAR Package.
  • Navigate to the SDK files you extracted and import the .aar file.

  • Optionally remove the version number in the suggested subproject name in case you upgrade the module in place later, etc. Below we will assume you used libbambuser

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

  • Once more, in the project tree on the left-hand-side, right click on your app module and choose Open Module Settings again.

  • Choose Dependencies on the lefthand side, keep the app module selected in the second column, click the + button in the third column and choose Module Dependency.

  • In the second popup window, check libbambuser and click OK.

  • Click OK again to return from the Project Structure screen.

Configure Gradle to build for supported architectures

The Bambuser SDK contains native code built for the armeabi-v7a, arm64-v8a, x86 and x86_64 ABIs. If you don't want to bundle all of them, or if other libraries contain native code for other architectures, an ABI filter is needed to ensure that the generated APK contains the greatest common denominator.

The armeabi-v7a and arm64-v8a ABIs are compatible with all modern devices and ARM emulator images. The x86 and x86_64 ABIs are in practice only necessary when developing on x86 emulator images, as real x86 devices can translate ARM machine code.

  • Open the build.gradle file for the app module.

  • Add an NDK ABI filter to the android defaultConfig block. For example:

android {
    defaultConfig {
        // ...
        ndk {
            abiFilters 'armeabi-v7a', 'arm64-v8a'
        }
    }
}

Add required Android app permissions and features

The Bambuser broadcasting library for Android requires at least the following permissions for basic functionality: CAMERA, RECORD_AUDIO and INTERNET. We also recommend adding the ACCESS_NETWORK_STATE and WAKE_LOCK permissions at this point.

Additionally, to achieve suitable filtering of your app in the Google Play store, you should declare <uses-feature /> tags relevant for apps that rely on the camera.

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

  • Add the following tags for permissions and features, before the <application /> tag:

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-feature android:name="android.hardware.camera.any" android:required="true" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.camera.flash" android:required="false" />
<uses-feature android:name="android.hardware.camera.front" android:required="false" />

Since Android 6.0, the above is not enough. Certain permissions must be approved by the end-user at runtime. In the generated MainActivity.kt, check for and request any missing permissions:

import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.support.v4.app.ActivityCompat
import android.support.v7.app.AppCompatActivity

// ...

class MainActivity : AppCompatActivity() {
    public override fun onResume() {
        super.onResume()
        if (!hasPermission(Manifest.permission.CAMERA) && !hasPermission(Manifest.permission.RECORD_AUDIO))
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO), 1)
        else if (!hasPermission(Manifest.permission.RECORD_AUDIO))
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO), 1)
        else if (!hasPermission(Manifest.permission.CAMERA))
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 1)
    }

    // ...

    private fun hasPermission(permission: String): Boolean {
        return ActivityCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
    }
}

This is enough for our minimal example. In practice, you should also at least implement onRequestPermissionsResult() in case the user rejects any of the requested permissions.

Add the viewfinder

In res/layout/activity_main.xml, replace the auto-generated TextView with a SurfaceViewWithAutoAR.

<com.bambuser.broadcaster.SurfaceViewWithAutoAR
    android:id="@+id/previewSurface"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

Android Studio presents a visualization of the component tree by default. By switching from the Design tab to the Text tab near the bottom, you can access the XML representation and paste the snippet above.

Authentication

To be able to broadcast, your app needs to identify itself to Bambuser. Head over to the Developer page on the Bambuser site again and get the Sandbox applicationId, which we'll use when constructing the Broadcaster.

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

Bootstrap the SDK

Prepare MainActivity.kt for integration of the Broadcaster by importing the related classes and adding an implementation of the Broadcaster.Observer interface:

import android.util.Log
// ...
import com.bambuser.broadcaster.BroadcastStatus;
import com.bambuser.broadcaster.Broadcaster;
import com.bambuser.broadcaster.CameraError;
import com.bambuser.broadcaster.ConnectionError;
// ...
class MainActivity : AppCompatActivity() {
    // ...
    private val broadcasterObserver = object : Broadcaster.Observer {
        override fun onConnectionStatusChange(broadcastStatus: BroadcastStatus) {
            Log.i("Mybroadcastingapp", "Received status change: $broadcastStatus")
        }
        override fun onStreamHealthUpdate(i: Int) {}
        override fun onConnectionError(connectionError: ConnectionError, s: String?) {
            Log.i("Mybroadcastingapp","Received connection error: $connectionError, $s")
        }
        override fun onCameraError(cameraError: CameraError) {
            Log.i("Mybroadcastingapp","Received camera error: $cameraError")
        }
        override fun onChatMessage(s: String) {
            Log.i("Mybroadcastingapp","Received chat messsage: $s")
        }
        override fun onResolutionsScanned() {}
        override fun onCameraPreviewStateChanged() {}
        override fun onBroadcastInfoAvailable(s: String, s1: String) {
            Log.i("Mybroadcastingapp","Received broadcast info: $s, $s1")
        }
        override fun onBroadcastIdAvailable(id: String) {
            Log.i("Mybroadcastingapp","Received broadcast id: $id")
        }
    }
    // ...
}

In the main activity, lazily create an instance of the Broadcaster class. provide a reference to the activity as well as the the applicationId. Also forward the Activity lifecycle events necessary for init and release of the camera.

Also import the viewfinder we set up previously, and attach it in onResume().

import kotlinx.android.synthetic.main.activity_main.previewSurface
// ...
class MainActivity : AppCompatActivity() {
    // ...
    private val APPLICATION_ID = "GFZalqkR5iyZcIgaolQmA"

    private val broadcaster by lazy {
        Broadcaster(this, APPLICATION_ID, broadcasterObserver)
    }

    override fun onPause() {
        super.onPause()
        broadcaster.onActivityPause()
    }

    public override fun onResume() {
        super.onResume()
        // ... permission checks, see above
        broadcaster.setCameraSurface(previewSurface)
        broadcaster.onActivityResume()
    }

    override fun onDestroy() {
        super.onDestroy()
        broadcaster.onActivityDestroy()
    }
}

Screen rotation and keep-awake

To rotate the camera preview and live video according to the rotation of the device, forward the display rotation to the Broadcaster after creation and in onResume().

broadcaster.setRotation(this.windowManager.defaultDisplay.rotation)

To prevent the screen from going to sleep, set FLAG_KEEP_SCREEN_ON when a broadcast starts and clear it when the broadcast ends.

Import the WindowManager class where the flag is found, then add an implementation to the empty onConnectionStatusChange(BroadcastStatus broadcastStatus) callback in the Broadcaster.Observer you created earlier:

import android.view.WindowManager;
// ...
class MainActivity : AppCompatActivity() {
    // ...
    private val activity = this

    private val broadcasterObserver = object : Broadcaster.Observer {
        override fun onConnectionStatusChange(broadcastStatus: BroadcastStatus) {
            Log.i("Mybroadcastingapp", "Received status change: $broadcastStatus")
            if (broadcastStatus == BroadcastStatus.STARTING) {
                activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
            }
            if (broadcastStatus == BroadcastStatus.IDLE) {
                activity.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
            }
        }
        // ...

Add a broadcast button

Add a simple Button in the activity_main.xml layout:

<Button android:id="@+id/BroadcastButton"
    android:text="Broadcast"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

...and make it respond to clicks in MainActivity.kt:

import kotlinx.android.synthetic.main.activity_main.broadcastButton
// ...
class MainActivity : AppCompatActivity() {
    // ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        broadcastButton.setOnClickListener {
            if (broadcaster.canStartBroadcasting()) {
                broadcaster.startBroadcast()
            } else {
                broadcaster.stopBroadcast()
            }
        }
    }
// ...

Update the button label depending on broadcast state:


override fun onConnectionStatusChange(broadcastStatus: BroadcastStatus) {
    // ...
    broadcastButton.setText(if (broadcastStatus == BroadcastStatus.IDLE) "Broadcast" else "Disconnect")
}

Running the app

Connect your mobile device to your PC and follow the Android Developers guide for running your app.

If everything is set up correctly, you should be able to start your first broadcast from your app by tapping the button we added earlier.

Check the Content page when logged in on the Bambuser site to view the broadcast when you go live in Sandbox mode.

What's next

  • Be sure to experience a few of your live broadcasts via the Sandbox player on the Bambuser website.

  • Consider whether you also need a player in your app and / or on your website.

  • Study the REST APIs and Webhooks, which can be used to make your backend broadcast-aware.