Code Example

Kotlin Android RTMP Client Snippets

A step by step Android RTMP Client example.

What is RTMP?

Real-Time Messaging Protocol (RTMP) is a communication protocol for streaming audio, video, and data over the Internet. Originally developed as a proprietary protocol by Macromedia for streaming between Flash Player and the Flash Communication Server, Adobe (which acquired Macromedia) has released an incomplete version of the specification of the protocol for public use.

The RTMP protocol has multiple variations:

RTMP proper, the "plain" protocol which works on top of Transmission Control Protocol (TCP) and uses port number 1935 by default.

RTMPS, which is RTMP over a Transport Layer Security (TLS/SSL) connection.
RTMPE, which is RTMP encrypted using Adobe’s own security mechanism. While the details of the implementation are proprietary, the mechanism uses industry standard cryptographic primitives.
RTMPT, which is encapsulated within HTTP requests to traverse firewalls.

RTMPT is frequently found utilizing cleartext requests on TCP ports 80 and 443 to bypass most corporate traffic filtering. The encapsulated session may carry plain RTMP, RTMPS, or RTMPE packets within.

RTMFP, which is RTMP over User Datagram Protocol (UDP) instead of TCP, replacing RTMP Chunk Stream. The Secure Real-Time Media Flow Protocol suite has been developed by Adobe Systems and enables end‐users to connect and communicate directly with each other (P2P).

1. TakuSemba/RtmpPublisher

Rtmp client on Android. Live Video Streaming..

RtmpPublisher Example Tutorial

Here is the demo of the sample project:

Step 1: Gradle

dependencies {
    implementation 'com.github.takusemba:rtmppublisher:x.x.x'
}

Step 2: Usage

usage is simple. RtmpPublisher does everything.

Create a Publisher

val publisher: Publisher = Publisher.Builder(this)
  .setGlView(glView)
  .setUrl(rtmpUrl)
  .setSize(Publisher.Builder.DEFAULT_WIDTH, Publisher.Builder.DEFAULT_HEIGHT)
  .setAudioBitrate(Publisher.Builder.DEFAULT_AUDIO_BITRATE)
  .setVideoBitrate(Publisher.Builder.DEFAULT_VIDEO_BITRATE)
  .setCameraMode(Publisher.Builder.DEFAULT_MODE)
  .setListener(this)
  .build()

start RTMP Streaming

// start publishing!
publisher.startPublishing()

// switch camera between front and back
publisher.switchCamera()

// stop publishing!
publisher.stopPublishing()

publisher.setOnPublisherListener(object: PublisherListener {
  override fun onStarted() {
    // do something
  }
    override fun onStopped() {
    // do something
  }
    override fun onFailedToConnect() {
    // do something
  }
    override fun onDisconnected() {
    // do something
  }
})

Quick Start

RtmpPublisher Example Tutorial

Try this sample here, but this is just a RTMP android client. You also need RTMP server and player to do publish and play them.
You can either make the server and player yourself or use the ones below that i prepared.

Server

https://github.com/TakuSemba/docker-nginx-rtmp

RTMP Player

https://github.com/TakuSemba/RtmpPlayer

Full Example

Let us look at a full Example below.

Step 1. Add Permissions

We will need to add some permissions in our AndroidManifest.xml as shown below:

(a). AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.takusemba.rtmppublishersample"
  >

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

  <uses-feature
    android:glEsVersion="0x00020000"
    android:required="true"
    />

  <application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme"
    >
    <activity android:name=".MainActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>

        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>
  </application>

</manifest>

Step 2. Design Layouts

We need to design our XML layouts as follows:

(a). activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.opengl.GLSurfaceView
        android:id="@+id/surface_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center" />

    <Button
        android:id="@+id/toggle_publish"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="32dp"
        android:background="@drawable/round_corner_white"
        android:paddingBottom="8dp"
        android:paddingEnd="16dp"
        android:paddingStart="16dp"
        android:paddingTop="8dp"
        android:text="@string/start_publishing" />

    <ImageView
        android:id="@+id/toggle_camera"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:background="@drawable/ic_switch" />

    <TextView
        android:id="@+id/live_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_marginRight="12dp"
        android:layout_marginTop="12dp"
        android:background="@drawable/round_corner_red"
        android:gravity="center"
        android:paddingBottom="10dp"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:paddingTop="10dp"
        android:textAlignment="center"
        android:textColor="@android:color/white"
        android:textSize="16sp"
        android:textStyle="bold"
        android:visibility="gone"
        tools:text="LIVE  00:00"
        tools:visibility="visible" />

</RelativeLayout>

Step 3. Write Code

Finally we need to write our code as follows:

(a). MainActivity.kt

package com.takusemba.rtmppublishersample

import android.opengl.GLSurfaceView
import android.os.Bundle
import android.os.Handler
import android.support.v7.app.AppCompatActivity
import android.view.Gravity
import android.view.View
import android.widget.*
import com.takusemba.rtmppublisher.Publisher
import com.takusemba.rtmppublisher.PublisherListener

class MainActivity : AppCompatActivity(), PublisherListener {

    private lateinit var publisher: Publisher
    private lateinit var glView: GLSurfaceView
    private lateinit var container: RelativeLayout
    private lateinit var publishButton: Button
    private lateinit var cameraButton: ImageView
    private lateinit var label: TextView

    private val url = BuildConfig.STREAMING_URL
    private val handler = Handler()
    private var thread: Thread? = null
    private var isCounting = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        glView = findViewById(R.id.surface_view)
        container = findViewById(R.id.container)
        publishButton = findViewById(R.id.toggle_publish)
        cameraButton = findViewById(R.id.toggle_camera)
        label = findViewById(R.id.live_label)

        if (url.isBlank()) {
            Toast.makeText(this, R.string.error_empty_url, Toast.LENGTH_SHORT)
                    .apply { setGravity(Gravity.CENTER, 0, 0) }
                    .run { show() }
        } else {
            publisher = Publisher.Builder(this)
                    .setGlView(glView)
                    .setUrl(url)
                    .setSize(Publisher.Builder.DEFAULT_WIDTH, Publisher.Builder.DEFAULT_HEIGHT)
                    .setAudioBitrate(Publisher.Builder.DEFAULT_AUDIO_BITRATE)
                    .setVideoBitrate(Publisher.Builder.DEFAULT_VIDEO_BITRATE)
                    .setCameraMode(Publisher.Builder.DEFAULT_MODE)
                    .setListener(this)
                    .build()

            publishButton.setOnClickListener {
                if (publisher.isPublishing) {
                    publisher.stopPublishing()
                } else {
                    publisher.startPublishing()
                }
            }

            cameraButton.setOnClickListener {
                publisher.switchCamera()
            }
        }
    }

    override fun onResume() {
        super.onResume()
        if (url.isNotBlank()) {
            updateControls()
        }
    }

    override fun onStarted() {
        Toast.makeText(this, R.string.started_publishing, Toast.LENGTH_SHORT)
                .apply { setGravity(Gravity.CENTER, 0, 0) }
                .run { show() }
        updateControls()
        startCounting()
    }

    override fun onStopped() {
        Toast.makeText(this, R.string.stopped_publishing, Toast.LENGTH_SHORT)
                .apply { setGravity(Gravity.CENTER, 0, 0) }
                .run { show() }
        updateControls()
        stopCounting()
    }

    override fun onDisconnected() {
        Toast.makeText(this, R.string.disconnected_publishing, Toast.LENGTH_SHORT)
                .apply { setGravity(Gravity.CENTER, 0, 0) }
                .run { show() }
        updateControls()
        stopCounting()
    }

    override fun onFailedToConnect() {
        Toast.makeText(this, R.string.failed_publishing, Toast.LENGTH_SHORT)
                .apply { setGravity(Gravity.CENTER, 0, 0) }
                .run { show() }
        updateControls()
        stopCounting()
    }

    private fun updateControls() {
        publishButton.text = getString(if (publisher.isPublishing) R.string.stop_publishing else R.string.start_publishing)
    }

    private fun startCounting() {
        isCounting = true
        label.text = getString(R.string.publishing_label, 0L.format(), 0L.format())
        label.visibility = View.VISIBLE
        val startedAt = System.currentTimeMillis()
        var updatedAt = System.currentTimeMillis()
        thread = Thread {
            while (isCounting) {
                if (System.currentTimeMillis() - updatedAt > 1000) {
                    updatedAt = System.currentTimeMillis()
                    handler.post {
                        val diff = System.currentTimeMillis() - startedAt
                        val second = diff / 1000 % 60
                        val min = diff / 1000 / 60
                        label.text = getString(R.string.publishing_label, min.format(), second.format())
                    }
                }
            }
        }
        thread?.start()
    }

    private fun stopCounting() {
        isCounting = false
        label.text = ""
        label.visibility = View.GONE
        thread?.interrupt()
    }

    private fun Long.format(): String {
        return String.format("%02d", this)
    }
}

Reference

You can DOWNLOAD FULL CODE.
You can also browse code or read more here.
Follow code author here.


Read More.