How to live broadcast on Android using Amity Video

  • This is some text inside of a div block.
  • This is some text inside of a div block.
  • This is some text inside of a div block.
  • This is some text inside of a div block.

How to live broadcast on Android using Amity Video

I believe that many people who are interested in this article probably have seen many video content on well-known social media apps such as Facebook, Instagram, Twitter, or TikTok.

As you may have noticed that currently, all well-known social media apps have started to develop and release more video features to attract their users. I can only say that it is very effective so, what if.. you can create your own video content platform and control or set up whatever you want by yourself on your platform?

I think you all can guess now that today I’m going to let you all simply learn about the tools which conveniently help you to create your own video content platform. It’s called Amity Video

Amity Video SDK is the easiest way to create your video content platform or live commerce where your community can advertise their products through video live or create an application that users can share their lifestyle, activity, and experience, yeah I’m talking about the story features like Instagram. However, if you already have an application and you don’t want to create the new one, then you can bring the SDK to fill up a part of your current application as well.

Bring in-store magic online

Amity Video supports both live and recording activities. This SDK is available to all platforms (iOS, Android, Web). After you download SDK you will be able to use it easily without a backend concern because we prepared everything for you, definitely secure and scalable enough to grow up in the future.


Creativity is thinking up new things, Amity Video is creating new live.

Let’s have some fun, I’m taking you to make a simple live application now…

(Technical part is about to begin…)

Description

In this project, I aim to show you how our SDK can do with very simple code so I decided to include 2 parts with different roles.

  • A creator who can create their live stream.
  • A viewer who can watch the stream.

Required

  • Android Studio
  • Android 5.0 (API level 21 or higher)
  • Android X
  • Gradle 3.4.0 or higher
  • Java 8
  • Coffee or Tea and Snack

Creating an Android project

→ In the Welcome window, select Start new Project.

→ You can select any Project Templates you want (I chose Empty Activity)

→ Enter your project Name, Package Name

Setting up SDK

→ Add Jitpack in project level {% c-line %}build.gradle{% c-line-end %}

{% c-block language="javascript" %}
allprojects {
   
repositories {
       
google()
       jcenter()
       maven { url 'https://jitpack.io' }
   }
}
{% c-block-end %}

→ Add rxjava, rxandroid, rxkotlin for asynchronous programming with observable streams

{% c-block language="javascript" %}
implementation "io.reactivex.rxjava2:rxjava:2.2.19"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "io.reactivex.rxjava2:rxkotlin:2.4.0"
{% c-block-end %}

→ Add exoplayer for video player

{% c-block language="javascript" %}
implementation "com.google.android.exoplayer:exoplayer:2.13.2"
{% c-block-end %}

→ Add Amity Video SDK the protagonist of this project

{% c-block language="javascript" %}
implementation
"com.github.EkoCommunications.EkoMessagingSDKAndroid:eko-sdk:4.7.1"

implementation
"com.github.EkoCommunications.EkoMessagingSDKAndroid:eko-video-publisher:4.7.1"
{% c-block-end %}

Initialize the SDK

→ Add permissions in your AndroidManifest.xml

{% c-block language="javascript" %}
<uses-permission android:name="android.permission.INTERNET" />

<uses-permission android:name=
"android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />

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

<uses-permission android:name="android.permission.RECORD_AUDIO" />
{% c-block-end %}

→ In your AndroidManifest.xml <application> add android:name and usesCleartextTraffic for using http protocol

{% c-block language="javascript" %}
android:name="App"
android:usesCleartextTraffic="true"
{% c-block-end %}

→ Create the following files like me will help you to follow my guide easier.

project tree

→ Create App.kt in your main package and add the following code

  • apiKey is a key to your “private” environment. The app will show only data of your environment only.
  • Register your account on our website to get your API key

APP.kt

{% c-block language="javascript" %}
import android.app.Application
import android.util.Log
import com.ekoapp.ekosdk.EkoClient
import com.ekoapp.sdk.publisher.EkoStreamPublisherClient
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulersclass App : Application(){
   override fun onCreate() {
       super.onCreate()
       EkoClient.setup(API_KEY)
           .subscribeOn(Schedulers.io())
           .observeOn(AndroidSchedulers.mainThread())
           .doOnComplete {
               
Log.e("sdk setup", "SETUP SUCCESS")
}
           
.doOnError {
               it
.message?.let { it1 -> Log.d("", it1) }
           }
           
.subscribe()

       EkoClient.registerDevice("Mobile User")
               .displayName("Mobile User")
               .build()
               .submit()
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .subscribe()

       val streamRepository = EkoClient.newStreamRepository()
       streamRepository
           .getStreamById("Mobile User")
           .subscribe()

       EkoStreamPublisherClient.setup(EkoClient.getConfiguration())
   }
}
{% c-block-end %}

activity_main.xml

  • FrameLayout prepare for fragmemt

{% c-block language="javascript" %}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

<Button
       android:id="@+id/creator"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="300dp"
       android:backgroundTint="#F0F0F0"
       android:textColor="#000"
       android:text="Creator"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />

<Button
       android:id="@+id/viewer"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="36dp"
       android:backgroundTint="#000"
       android:text="Viewer"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@+id/creator" />

<FrameLayout
       android:id="@+id/frame_content"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       tools:layout_editor_absoluteX="62dp"
       tools:layout_editor_absoluteY="48dp">
</FrameLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
{% c-block-end %}

MainActivity.kt

→ Create MainActivity.kt for main class

  • MainActivity.kt add onCreate method

{% c-block language="javascript" %}
class MainActivity : AppCompatActivity() {
   private val PERMISSION_REQUEST_CODE = 200

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)

       val creatorBtn = findViewById<Button>(R.id.creator)
       val viewerBtn = findViewById<Button>(R.id.viewer)

       creatorBtn.setOnClickListener{
           
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.CAMERA) ==
PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(this,Manifest.permission.
RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED){
               creatorBtn.visibility = View.INVISIBLE
               
viewerBtn.visibility = View.INVISIBLE
               
addFragment(getCreatorPage())
           }else{
               requestPermission()
           }
}        

viewerBtn.setOnClickListener{
           
creatorBtn.visibility = View.INVISIBLE
           
viewerBtn.visibility = View.INVISIBLE
           
addFragment(getViewerPage())
}
   
}
}
{% c-block-end %}

  • MainActivity.kt add getCreatorPage(), getViewerPage(), addFragment(), requestPermission()

{% c-block language="javascript" %}
private fun getCreatorPage(): Fragment {
   return LiveVideoFragment()
}

private fun getViewerPage(): Fragment {
   return MainVideoFragment()
}

private fun addFragment(fragment: Fragment) {
supportFragmentManager
       
.beginTransaction()
       .setCustomAnimations(
               R.anim.design_bottom_sheet_slide_in,
               R.anim.design_bottom_sheet_slide_out
       
)
       .replace(R.id.frame_content,
               fragment,
               fragment.javaClass.getSimpleName())
       .commit()
}

private fun requestPermission() {
   ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO,
Manifest.permission.INTERNET),
PERMISSION_REQUEST_CODE)
}
{% c-block-end %}

Before we go to next class

→ Create 2 drawable file name round_button.xml and default_button.xml

  • default_button.xml we’ll use this to end live stream

{% c-block language="javascript" %}
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
   android:color="#FFF">
<item>
<shape android:shape="rectangle">
<stroke android:color="#F0F0F0" android:width="1dp" />
<solid android:color="#F0F0F0"/>
</shape>
</item>
</ripple>
{% c-block-end %}

  • round_button.xml we’ll use this before live stream

{% c-block language="javascript" %}
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
   android:color="#FFF">
<item>
<shape android:shape="oval">
<stroke android:color="#F0F0F0" android:width="1dp" />
<solid android:color="#F0F0F0"/>
</shape>
</item>
</ripple>
{% c-block-end %}

CREATOR PART

We build this part for creator users who can use our application to create their live stream.

live_record.xml

→ Create live_record.xml for live record layout

{% c-block language="javascript" %}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   xmlns:app="http://schemas.android.com/apk/res-auto">

<androidx.appcompat.widget.AppCompatButton
       android:id="@+id/livebutton"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginBottom="28dp"
       android:background="@drawable/round_button"
       android:textAppearance="@style/TextAppearance.AppCompat.Display3"
       android:textSelectHandleLeft="@drawable/round_button"
       android:textSelectHandleRight="@drawable/round_button"
       app:icon="@drawable/round_button"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent" />

<com.ekoapp.sdk.publisher.EkoCameraView
       android:id="@+id/eko_camera"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintDimensionRatio="H,9:16"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintVertical_bias="0.5">

</com.ekoapp.sdk.publisher.EkoCameraView>

<Button
       android:id="@+id/switch_cam"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginBottom="16dp"
       android:text="FRONT"
       android:backgroundTint="#000"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintHorizontal_bias="0.454"
       app:layout_constraintStart_toEndOf="@+id/livebutton" />


</androidx.constraintlayout.widget.ConstraintLayout>
{% c-block-end %}

LiveVideoFragment.kt

{% c-block language="javascript" %}
class LiveVideoFragment : Fragment()  {    

var islive = false

override fun onCreateView(
           inflater: LayoutInflater,
           container: ViewGroup?,
           savedInstanceState: Bundle?
   ): View? {
       return inflater.inflate(R.layout.live_record, container, false);
   }
}
{% c-block-end %}

  • override onViewCreated and add button controller

{% c-block language="javascript" %}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   super.onViewCreated(view, savedInstanceState)
   val eko_camera = view?.findViewById<EkoCameraView>(R.id.eko_camera)

   val broadcasterConfig = EkoStreamBroadcasterConfiguration.Builder()
           .setOrientation(Configuration.ORIENTATION_PORTRAIT)
           .setResolution(EkoBroadcastResolution.FHD_1080P)
           .build()

   val broadcaster = EkoStreamBroadcaster.Builder(eko_camera)
           .setConfiguration(broadcasterConfig)
           .build()

   broadcaster.startPreview()

   val btn = view?.findViewById<Button>(R.id.switch_cam)
   val publishBtn = view?.findViewById<Button>(R.id.livebutton)

   publishBtn.setOnClickListener {
       
if(!islive) {
           broadcaster.startPublish("Test MY Live",
"Hello My First Live")
           islive = !islive
       }else{
           broadcaster.stopPublish()
           islive = !islive
       }
       publishBtn.setBackgroundResource(if (islive) R.drawable.default_button else R.drawable.round_button)
}

   
btn?.setOnClickListener {
       
if(btn.text == "FRONT"){
           btn.text = "BACK"
       }else{
           btn.text = "FRONT"
       }
       broadcaster.switchCamera()
}
}
{% c-block-end %}

VIEWER PART

We build this part for viewer users who can use our application to watch the live stream.

video_player.xml

  • I use exoplayer for video player you can access their website for more detail.

{% c-block language="javascript" %}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
   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"
   xmlns:app="http://schemas.android.com/apk/res-auto">

<TextView
       android:id="@+id/notfound"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:ems="10"
       android:text="NOT FOUND VIDEO"
       android:visibility="invisible"
       app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent" />

<com.google.android.exoplayer2.ui.StyledPlayerView
       android:id="@+id/player_view"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       app:show_buffering="when_playing"
       app:show_shuffle_button="true" >
</com.google.android.exoplayer2.ui.StyledPlayerView>

</androidx.constraintlayout.widget.ConstraintLayout>
{% c-block-end %}

MainVideoFragment.kt

{% c-block language="javascript" %}
class MainVideoFragment : Fragment()  {
   override fun onCreateView(
           inflater: LayoutInflater,
           container: ViewGroup?,
           savedInstanceState: Bundle?
   ): View? {
       return inflater.inflate(R.layout.video_player, container, false);
   }

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
       super.onViewCreated(view, savedInstanceState)
       getStreamRepo()
   }

   private fun playStreaming(url: String?){
       val exoPlayerView = view?.findViewById<StyledPlayerView>(R.id.player_view);        exoPlayerView?.visibility = View.VISIBLE
       
exoPlayerView?.requestFocus();

       val player =  SimpleExoPlayer.Builder(context!!)
               .setMediaSourceFactory(
DefaultMediaSourceFactory(context!!).
setLiveTargetOffsetMs(5000)).build()

       val mediaItem: MediaItem = MediaItem.Builder()
               .setUri(url)
               .setLiveMaxPlaybackSpeed(1.02f)
               .build()

       player.setMediaItem(mediaItem)
       exoPlayerView?.player = player;
       player.prepare()
       player.play()
   }

   private fun nolive(){
       val exoPlayerView = view?.findViewById<StyledPlayerView>(R.id.player_view);
       exoPlayerView?.visibility = View.INVISIBLE

       
val notfoundText = view?.findViewById<TextView>(R.id.notfound);
       notfoundText?.visibility = View.VISIBLE
   
}

   private fun getStreamRepo(){
       EkoClient.newStreamRepository()
               .getStreamCollection()
               .setStatus(arrayOf(EkoStream.Status.RECORDED))
               .build()
               .query()
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .doOnError{ throwable -> Log.e("Throwable ", throwable.message.toString())
}
               
.subscribe{ stream ->
                   
if (stream.size > 0) {
playStreaming(stream[0]?.getRecordings()?.get(0)?.getUrl(EkoRecordingData.Format.MP4))
} else{
                       nolive()
                   }
}
   
}
}
{% c-block-end %}

Congratulations !!

I recommend you to have 2 android phones. Maybe one is virtual and another one is for the real phone so you can try both roles and see what our magic can do.

If you are interested to learn more about Amity Video, you can contact us at our website. and if you want more details about SDK, you can walk through our docs to know more.

The future is social, thank you, and see you again soon!