RTMP Examples
Real-Time Messaging Protocol (RTMP) is a communication protocol for streaming audio, video, and data over the Internet.
It was originally developed as a proprietary protocol by Macromedia for streaming between Flash Player and a 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.[1]
- 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).
While the primary motivation for RTMP was to be a protocol for playing Flash video, it is also used in some other applications, such as the Adobe LiveCycle Data Services ES.
Here are its examples in Android.
Android RTMP RTSP Stream-client Example
This is an Android RTMP RTSP Stream-client Example.
This example will comprise the following files:
BackgroundActivity.kt
ConnectCheckerRtp.kt
RtpService.kt
RtmpActivity.java
RtspActivity.java
ExampleRtmpActivity.java
ExampleRtspActivity.java
DisplayActivity.java
DisplayService.kt
RtmpFromFileActivity.java
RtspFromFileActivity.java
MainActivity.java
OpenGlRtmpActivity.java
OpenGlRtspActivity.java
RotationExampleActivity.kt
StreamService.kt
SurfaceModeRtmpActivity.java
SurfaceModeRtspActivity.java
TextureModeRtmpActivity.java
TextureModeRtspActivity.java
ActivityLink.java
ImageAdapter.java
PathUtils.java
Step 1: Create Project
- Open your
AndroidStudio
IDE. - Go to
File-->New-->Project
to create a new project.
Step 2: Add Dependencies
In your app/build.gradle
add dependencies as shown below:
Enable Viewbinding:
Import the project library:
Step 3: Design Layouts
*(a). activity_background.xml
Create a file named activity_background.xml
and design it as follows:
<?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"
android:id="@+id/activity_example_rtmp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.pedro.rtplibrary.view.OpenGlView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<EditText
android:textColor="@color/appColor"
android:textColorHint="@color/appColor"
android:inputType="textUri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="20dp"
android:id="@+id/et_rtp_url"
app:layout_constraintTop_toTopOf="parent"
/>
<Button
android:text="@string/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp"
android:id="@+id/b_start_stop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
activity_custom.xml
Create a file named activity_custom.xml
and design it as follows:
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:openDrawer="start"
android:id="@+id/activity_custom"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<SurfaceView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/surfaceView"
/>
<EditText
android:textColor="@color/appColor"
android:textColorHint="@color/appColor"
android:inputType="textUri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="20dp"
android:id="@+id/et_rtp_url"
app:layout_constraintTop_toTopOf="parent"
/>
<Button
android:text="@string/start_record"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_record"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/b_start_stop"
app:layout_constraintHorizontal_chainStyle="spread"
android:layout_marginBottom="20dp"
/>
<Button
android:text="@string/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_start_stop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/b_record"
app:layout_constraintEnd_toStartOf="@id/switch_camera"
android:layout_marginBottom="20dp"
/>
<Button
android:text="@string/switch_camera_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/switch_camera"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/b_start_stop"
android:layout_marginBottom="20dp"
/>
<TextView
android:textColor="@color/appColor"
android:id="@+id/tv_bitrate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
app:layout_constraintTop_toBottomOf="@id/et_rtp_url"
app:layout_constraintEnd_toEndOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.navigation.NavigationView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:paddingBottom="30dp"
android:fitsSystemWindows="true"
app:headerLayout="@xml/options_header"
android:id="@+id/nv_rtp"
>
</com.google.android.material.navigation.NavigationView>
</androidx.drawerlayout.widget.DrawerLayout>
activity_display.xml
Create a file named activity_display.xml
and design it as follows:
<?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"
android:id="@+id/activity_example_rtmp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/surfaceView"
/>
<EditText
android:textColor="@color/appColor"
android:textColorHint="@color/appColor"
android:inputType="textUri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="20dp"
android:id="@+id/et_rtp_url"
app:layout_constraintTop_toTopOf="parent"
/>
<Button
android:text="@string/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_start_stop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginBottom="20dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
activity_example.xml
Create a file named activity_example.xml
and design it as follows:
<?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"
android:id="@+id/activity_example_rtmp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/surfaceView"
/>
<EditText
android:textColor="@color/appColor"
android:textColorHint="@color/appColor"
android:inputType="textUri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="20dp"
android:id="@+id/et_rtp_url"
app:layout_constraintTop_toTopOf="parent"
/>
<Button
android:text="@string/start_record"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_record"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/b_start_stop"
app:layout_constraintHorizontal_chainStyle="spread"
android:layout_marginBottom="20dp"
/>
<Button
android:text="@string/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_start_stop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/b_record"
app:layout_constraintEnd_toStartOf="@id/switch_camera"
android:layout_marginBottom="20dp"
/>
<Button
android:text="@string/switch_camera_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/switch_camera"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/b_start_stop"
android:layout_marginBottom="20dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
activity_from_file.xml
Create a file named activity_from_file.xml
and design it as follows:
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
>
<EditText
android:hint="@string/hint_rtmp"
android:textColor="@color/appColor"
android:textColorHint="@color/appColor"
android:inputType="textUri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="20dp"
android:id="@+id/et_rtp_url"
app:layout_constraintTop_toTopOf="parent"
/>
<Button
android:text="@string/start_record"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_record"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/b_start_stop"
android:layout_marginBottom="20dp"
/>
<Button
android:text="@string/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_start_stop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/b_record"
android:layout_marginBottom="20dp"
/>
<Button
android:text="@string/select_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_select_file"
android:layout_above="@+id/seek_bar"
app:layout_constraintStart_toEndOf="@id/b_re_sync"
app:layout_constraintBottom_toTopOf="@id/seek_bar"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginBottom="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp" />
<Button
android:text="@string/resync_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/seek_bar"
android:id="@+id/b_re_sync"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/seek_bar"
app:layout_constraintEnd_toStartOf="@id/b_select_file"
app:layout_constraintHorizontal_chainStyle="spread_inside"
android:layout_marginStart="20dp"
android:layout_marginBottom="20dp"
android:layout_marginLeft="20dp" />
<SeekBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:id="@+id/seek_bar"
app:layout_constraintBottom_toTopOf="@id/b_record"
android:layout_marginBottom="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_file"
android:layout_above="@+id/b_select_file"
android:layout_centerHorizontal="true"
android:textColor="@color/appColor"
android:layout_margin="10dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
activity_main.xml
Create a file named activity_main.xml
and design it as follows:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
>
<TextView
android:id="@+id/tv_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/appColor"
android:textSize="12sp"
android:layout_marginTop="@dimen/grid_2"
android:layout_marginLeft="@dimen/grid_2"
android:layout_marginRight="@dimen/grid_2"
/>
<TextView
android:id="@+id/tv_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/grid_2"
android:layout_marginLeft="@dimen/grid_2"
android:layout_marginRight="@dimen/grid_2"
android:text="@string/tv_select"
android:textColor="@color/appColor"
android:textSize="22sp"
/>
<GridView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/grid_1"
android:columnWidth="40dp"
android:gravity="center"
android:horizontalSpacing="@dimen/grid_2"
android:numColumns="2"
android:padding="@dimen/grid_2"
android:stretchMode="columnWidth"
android:verticalSpacing="@dimen/grid_2"
/>
</LinearLayout>
activity_open_gl.xml
Create a file named activity_open_gl.xml
and design it as follows:
<?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"
android:id="@+id/activity_example_rtmp"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.pedro.rtplibrary.view.OpenGlView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/surfaceView"
app:keepAspectRatio="true"
app:aspectRatioMode="adjust"
app:AAEnabled="false"
app:numFilters="1"
app:isFlipHorizontal="false"
app:isFlipVertical="false"
/>
<EditText
android:textColor="@color/appColor"
android:textColorHint="@color/appColor"
android:inputType="textUri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="20dp"
android:id="@+id/et_rtp_url"
app:layout_constraintTop_toTopOf="parent"
/>
<Button
android:text="@string/start_record"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_record"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/b_start_stop"
app:layout_constraintHorizontal_chainStyle="spread"
android:layout_marginBottom="20dp"
/>
<Button
android:text="@string/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_start_stop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/b_record"
app:layout_constraintEnd_toStartOf="@id/switch_camera"
android:layout_marginBottom="20dp"
/>
<Button
android:text="@string/switch_camera_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/switch_camera"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/b_start_stop"
android:layout_marginBottom="20dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
activity_texture_mode.xml
Create a file named activity_texture_mode.xml
and design it as follows:
<?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"
android:id="@+id/activity_example_rtmp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black">
<!-- necessary LinearLayout to work with AutoFitTextureView -->
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.pedro.rtplibrary.view.AutoFitTextureView
android:id="@+id/textureView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
<EditText
android:textColor="@color/appColor"
android:textColorHint="@color/appColor"
android:inputType="textUri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="20dp"
android:id="@+id/et_rtp_url"
app:layout_constraintTop_toTopOf="parent"
/>
<Button
android:text="@string/start_record"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_record"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/b_start_stop"
app:layout_constraintHorizontal_chainStyle="spread"
android:layout_marginBottom="20dp"
/>
<Button
android:text="@string/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_start_stop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/b_record"
app:layout_constraintEnd_toStartOf="@id/switch_camera"
android:layout_marginBottom="20dp"
/>
<Button
android:text="@string/switch_camera_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/switch_camera"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/b_start_stop"
android:layout_marginBottom="20dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
Step 4: Write Code
Write Code as follows:
(a). BackgroundActivity.kt
Create a file named BackgroundActivity.kt
Here is the full code
package com.pedro.rtpstreamer.backgroundexample
import android.app.ActivityManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.SurfaceHolder
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.pedro.rtpstreamer.R
import com.pedro.rtpstreamer.databinding.ActivityBackgroundBinding
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
class BackgroundActivity : AppCompatActivity(), SurfaceHolder.Callback {
private lateinit var binding: ActivityBackgroundBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityBackgroundBinding.inflate(layoutInflater)
setContentView(binding.root)
RtpService.init(this)
binding.bStartStop.setOnClickListener {
if (isMyServiceRunning(RtpService::class.java)) {
stopService(Intent(applicationContext, RtpService::class.java))
binding.bStartStop.setText(R.string.start_button)
} else {
val intent = Intent(applicationContext, RtpService::class.java)
intent.putExtra("endpoint", binding.etRtpUrl.text.toString())
startService(intent)
binding.bStartStop.setText(R.string.stop_button)
}
}
binding.surfaceView.holder.addCallback(this)
}
override fun surfaceChanged(holder: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
RtpService.setView(binding.surfaceView)
RtpService.startPreview()
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
RtpService.setView(applicationContext)
RtpService.stopPreview()
}
override fun surfaceCreated(holder: SurfaceHolder) {
}
override fun onResume() {
super.onResume()
if (isMyServiceRunning(RtpService::class.java)) {
binding.bStartStop.setText(R.string.stop_button)
} else {
binding.bStartStop.setText(R.string.start_button)
}
}
@Suppress("DEPRECATION")
private fun isMyServiceRunning(serviceClass: Class<*>): Boolean {
val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
for (service in manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.name == service.service.className) {
return true
}
}
return false
}
}
(b). ConnectCheckerRtp.kt
Create a file named ConnectCheckerRtp.kt
Here is the full code
package com.pedro.rtpstreamer.backgroundexample
import com.pedro.rtmp.utils.ConnectCheckerRtmp
import com.pedro.rtsp.utils.ConnectCheckerRtsp
/**
* (Only working in kotlin)
* Mix both connect interfaces to support RTMP and RTSP in service with same code.
*/
interface ConnectCheckerRtp: ConnectCheckerRtmp, ConnectCheckerRtsp {
/**
* Commons
*/
fun onConnectionStartedRtp(rtpUrl: String)
fun onConnectionSuccessRtp()
fun onConnectionFailedRtp(reason: String)
fun onNewBitrateRtp(bitrate: Long)
fun onDisconnectRtp()
fun onAuthErrorRtp()
fun onAuthSuccessRtp()
/**
* RTMP
*/
override fun onConnectionStartedRtmp(rtmpUrl: String) {
onConnectionStartedRtp(rtmpUrl)
}
override fun onConnectionSuccessRtmp() {
onConnectionSuccessRtp()
}
override fun onConnectionFailedRtmp(reason: String) {
onConnectionFailedRtp(reason)
}
override fun onNewBitrateRtmp(bitrate: Long) {
onNewBitrateRtp(bitrate)
}
override fun onDisconnectRtmp() {
onDisconnectRtp()
}
override fun onAuthErrorRtmp() {
onAuthErrorRtp()
}
override fun onAuthSuccessRtmp() {
onAuthSuccessRtp()
}
/**
* RTSP
*/
override fun onConnectionStartedRtsp(rtspUrl: String) {
onConnectionStartedRtp(rtspUrl)
}
override fun onConnectionSuccessRtsp() {
onConnectionSuccessRtp()
}
override fun onConnectionFailedRtsp(reason: String) {
onConnectionFailedRtp(reason)
}
override fun onNewBitrateRtsp(bitrate: Long) {
onNewBitrateRtp(bitrate)
}
override fun onDisconnectRtsp() {
onDisconnectRtp()
}
override fun onAuthErrorRtsp() {
onAuthErrorRtp()
}
override fun onAuthSuccessRtsp() {
onAuthSuccessRtp()
}
}
(c). RtpService.kt
Create a file named RtpService.kt
Here is the full code
package com.pedro.rtpstreamer.backgroundexample
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import com.pedro.rtplibrary.base.Camera2Base
import com.pedro.rtplibrary.rtmp.RtmpCamera2
import com.pedro.rtplibrary.rtsp.RtspCamera2
import com.pedro.rtplibrary.view.OpenGlView
import com.pedro.rtpstreamer.R
/**
* Basic RTMP/RTSP service streaming implementation with camera2
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
class RtpService : Service() {
private var endpoint: String? = null
override fun onCreate() {
super.onCreate()
Log.e(TAG, "RTP service create")
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_HIGH)
notificationManager?.createNotificationChannel(channel)
}
keepAliveTrick()
}
private fun keepAliveTrick() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
val notification = NotificationCompat.Builder(this, channelId)
.setOngoing(true)
.setContentTitle("")
.setContentText("").build()
startForeground(1, notification)
} else {
startForeground(1, Notification())
}
}
override fun onBind(p0: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.e(TAG, "RTP service started")
endpoint = intent?.extras?.getString("endpoint")
if (endpoint != null) {
prepareStreamRtp()
startStreamRtp(endpoint!!)
}
return START_STICKY
}
companion object {
private const val TAG = "RtpService"
private const val channelId = "rtpStreamChannel"
private const val notifyId = 123456
private var notificationManager: NotificationManager? = null
private var camera2Base: Camera2Base? = null
private var openGlView: OpenGlView? = null
private var contextApp: Context? = null
fun setView(openGlView: OpenGlView) {
this.openGlView = openGlView
camera2Base?.replaceView(openGlView)
}
fun setView(context: Context) {
contextApp = context
this.openGlView = null
camera2Base?.replaceView(context)
}
fun startPreview() {
camera2Base?.startPreview()
}
fun init(context: Context) {
contextApp = context
if (camera2Base == null) camera2Base = RtmpCamera2(context, true, connectCheckerRtp)
}
fun stopStream() {
if (camera2Base != null) {
if (camera2Base!!.isStreaming) camera2Base!!.stopStream()
}
}
fun stopPreview() {
if (camera2Base != null) {
if (camera2Base!!.isOnPreview) camera2Base!!.stopPreview()
}
}
private val connectCheckerRtp = object : ConnectCheckerRtp {
override fun onConnectionStartedRtp(rtpUrl: String) {
showNotification("Stream connection started")
}
override fun onConnectionSuccessRtp() {
showNotification("Stream started")
Log.e(TAG, "RTP service destroy")
}
override fun onNewBitrateRtp(bitrate: Long) {
}
override fun onConnectionFailedRtp(reason: String) {
showNotification("Stream connection failed")
Log.e(TAG, "RTP service destroy")
}
override fun onDisconnectRtp() {
showNotification("Stream stopped")
}
override fun onAuthErrorRtp() {
showNotification("Stream auth error")
}
override fun onAuthSuccessRtp() {
showNotification("Stream auth success")
}
}
private fun showNotification(text: String) {
contextApp?.let {
val notification = NotificationCompat.Builder(it, channelId)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("RTP Stream")
.setContentText(text).build()
notificationManager?.notify(notifyId, notification)
}
}
}
override fun onDestroy() {
super.onDestroy()
Log.e(TAG, "RTP service destroy")
stopStream()
}
private fun prepareStreamRtp() {
stopStream()
stopPreview()
if (endpoint!!.startsWith("rtmp")) {
camera2Base = if (openGlView == null) {
RtmpCamera2(baseContext, true, connectCheckerRtp)
} else {
RtmpCamera2(openGlView, connectCheckerRtp)
}
} else {
camera2Base = if (openGlView == null) {
RtspCamera2(baseContext, true, connectCheckerRtp)
} else {
RtspCamera2(openGlView, connectCheckerRtp)
}
}
}
private fun startStreamRtp(endpoint: String) {
if (!camera2Base!!.isStreaming) {
if (camera2Base!!.prepareVideo() && camera2Base!!.prepareAudio()) {
camera2Base!!.startStream(endpoint)
}
} else {
showNotification("You are already streaming :(")
}
}
}
(d). RtmpActivity.java
Create a file named RtmpActivity.java
Here is the full code
package com.pedro.rtpstreamer.customexample;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import com.google.android.material.navigation.NavigationView;
import com.pedro.encoder.input.video.CameraHelper;
import com.pedro.encoder.input.video.CameraOpenException;
import com.pedro.rtmp.utils.ConnectCheckerRtmp;
import com.pedro.rtplibrary.rtmp.RtmpCamera1;
import com.pedro.rtpstreamer.R;
import com.pedro.rtpstreamer.utils.PathUtils;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
* More documentation see:
* {@link com.pedro.rtplibrary.base.Camera1Base}
* {@link com.pedro.rtplibrary.rtmp.RtmpCamera1}
*/
public class RtmpActivity extends AppCompatActivity
implements Button.OnClickListener, ConnectCheckerRtmp, SurfaceHolder.Callback,
View.OnTouchListener {
private Integer[] orientations = new Integer[] { 0, 90, 180, 270 };
private RtmpCamera1 rtmpCamera1;
private Button bStartStop, bRecord;
private EditText etUrl;
private String currentDateAndTime = "";
private File folder;
//options menu
private DrawerLayout drawerLayout;
private NavigationView navigationView;
private ActionBarDrawerToggle actionBarDrawerToggle;
private RadioGroup rgChannel;
private Spinner spResolution;
private CheckBox cbEchoCanceler, cbNoiseSuppressor;
private EditText etVideoBitrate, etFps, etAudioBitrate, etSampleRate, etWowzaUser,
etWowzaPassword;
private String lastVideoBitrate;
private TextView tvBitrate;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_custom);
folder = PathUtils.getRecordPath();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
SurfaceView surfaceView = findViewById(R.id.surfaceView);
surfaceView.getHolder().addCallback(this);
surfaceView.setOnTouchListener(this);
rtmpCamera1 = new RtmpCamera1(surfaceView, this);
prepareOptionsMenuViews();
tvBitrate = findViewById(R.id.tv_bitrate);
etUrl = findViewById(R.id.et_rtp_url);
etUrl.setHint(R.string.hint_rtmp);
bStartStop = findViewById(R.id.b_start_stop);
bStartStop.setOnClickListener(this);
bRecord = findViewById(R.id.b_record);
bRecord.setOnClickListener(this);
Button switchCamera = findViewById(R.id.switch_camera);
switchCamera.setOnClickListener(this);
}
private void prepareOptionsMenuViews() {
drawerLayout = findViewById(R.id.activity_custom);
navigationView = findViewById(R.id.nv_rtp);
navigationView.inflateMenu(R.menu.options_rtmp);
actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.rtmp_streamer,
R.string.rtmp_streamer) {
public void onDrawerOpened(View drawerView) {
actionBarDrawerToggle.syncState();
lastVideoBitrate = etVideoBitrate.getText().toString();
}
public void onDrawerClosed(View view) {
actionBarDrawerToggle.syncState();
if (lastVideoBitrate != null && !lastVideoBitrate.equals(
etVideoBitrate.getText().toString()) && rtmpCamera1.isStreaming()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
int bitrate = Integer.parseInt(etVideoBitrate.getText().toString()) * 1024;
rtmpCamera1.setVideoBitrateOnFly(bitrate);
Toast.makeText(RtmpActivity.this, "New bitrate: " + bitrate, Toast.LENGTH_SHORT).
show();
} else {
Toast.makeText(RtmpActivity.this, "Bitrate on fly ignored, Required min API 19",
Toast.LENGTH_SHORT).show();
}
}
}
};
drawerLayout.addDrawerListener(actionBarDrawerToggle);
//checkboxs
cbEchoCanceler =
(CheckBox) navigationView.getMenu().findItem(R.id.cb_echo_canceler).getActionView();
cbNoiseSuppressor =
(CheckBox) navigationView.getMenu().findItem(R.id.cb_noise_suppressor).getActionView();
//radiobuttons
RadioButton rbTcp =
(RadioButton) navigationView.getMenu().findItem(R.id.rb_tcp).getActionView();
rgChannel = (RadioGroup) navigationView.getMenu().findItem(R.id.channel).getActionView();
rbTcp.setChecked(true);
//spinners
spResolution = (Spinner) navigationView.getMenu().findItem(R.id.sp_resolution).getActionView();
ArrayAdapter<Integer> orientationAdapter =
new ArrayAdapter<>(this, R.layout.support_simple_spinner_dropdown_item);
orientationAdapter.addAll(orientations);
ArrayAdapter<String> resolutionAdapter =
new ArrayAdapter<>(this, R.layout.support_simple_spinner_dropdown_item);
List<String> list = new ArrayList<>();
for (Camera.Size size : rtmpCamera1.getResolutionsBack()) {
list.add(size.width + "X" + size.height);
}
resolutionAdapter.addAll(list);
spResolution.setAdapter(resolutionAdapter);
//edittexts
etVideoBitrate =
(EditText) navigationView.getMenu().findItem(R.id.et_video_bitrate).getActionView();
etFps = (EditText) navigationView.getMenu().findItem(R.id.et_fps).getActionView();
etAudioBitrate =
(EditText) navigationView.getMenu().findItem(R.id.et_audio_bitrate).getActionView();
etSampleRate = (EditText) navigationView.getMenu().findItem(R.id.et_samplerate).getActionView();
etVideoBitrate.setText("2500");
etFps.setText("30");
etAudioBitrate.setText("128");
etSampleRate.setText("44100");
etWowzaUser = (EditText) navigationView.getMenu().findItem(R.id.et_user).getActionView();
etWowzaPassword =
(EditText) navigationView.getMenu().findItem(R.id.et_password).getActionView();
}
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
actionBarDrawerToggle.syncState();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (!drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.openDrawer(GravityCompat.START);
} else {
drawerLayout.closeDrawer(GravityCompat.START);
}
return true;
case R.id.microphone:
if (!rtmpCamera1.isAudioMuted()) {
item.setIcon(getResources().getDrawable(R.drawable.icon_microphone_off));
rtmpCamera1.disableAudio();
} else {
item.setIcon(getResources().getDrawable(R.drawable.icon_microphone));
rtmpCamera1.enableAudio();
}
return true;
default:
return false;
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.b_start_stop:
Log.d("TAG_R", "b_start_stop: ");
if (!rtmpCamera1.isStreaming()) {
bStartStop.setText(getResources().getString(R.string.stop_button));
String user = etWowzaUser.getText().toString();
String password = etWowzaPassword.getText().toString();
if (!user.isEmpty() && !password.isEmpty()) {
rtmpCamera1.setAuthorization(user, password);
}
if (rtmpCamera1.isRecording() || prepareEncoders()) {
rtmpCamera1.startStream(etUrl.getText().toString());
} else {
//If you see this all time when you start stream,
//it is because your encoder device dont support the configuration
//in video encoder maybe color format.
//If you have more encoder go to VideoEncoder or AudioEncoder class,
//change encoder and try
Toast.makeText(this, "Error preparing stream, This device cant do it",
Toast.LENGTH_SHORT).show();
bStartStop.setText(getResources().getString(R.string.start_button));
}
} else {
bStartStop.setText(getResources().getString(R.string.start_button));
rtmpCamera1.stopStream();
}
break;
case R.id.b_record:
Log.d("TAG_R", "b_start_stop: ");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
if (!rtmpCamera1.isRecording()) {
try {
if (!folder.exists()) {
folder.mkdir();
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
currentDateAndTime = sdf.format(new Date());
if (!rtmpCamera1.isStreaming()) {
if (prepareEncoders()) {
rtmpCamera1.startRecord(
folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.stop_record);
Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Error preparing stream, This device cant do it",
Toast.LENGTH_SHORT).show();
}
} else {
rtmpCamera1.startRecord(
folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.stop_record);
Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
rtmpCamera1.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
} else {
rtmpCamera1.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
currentDateAndTime = "";
}
} else {
Toast.makeText(this, "You need min JELLY_BEAN_MR2(API 18) for do it...",
Toast.LENGTH_SHORT).show();
}
break;
case R.id.switch_camera:
try {
rtmpCamera1.switchCamera();
} catch (final CameraOpenException e) {
Toast.makeText(RtmpActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
private boolean prepareEncoders() {
Camera.Size resolution =
rtmpCamera1.getResolutionsBack().get(spResolution.getSelectedItemPosition());
int width = resolution.width;
int height = resolution.height;
return rtmpCamera1.prepareVideo(width, height, Integer.parseInt(etFps.getText().toString()),
Integer.parseInt(etVideoBitrate.getText().toString()) * 1024,
CameraHelper.getCameraOrientation(this)) && rtmpCamera1.prepareAudio(
Integer.parseInt(etAudioBitrate.getText().toString()) * 1024,
Integer.parseInt(etSampleRate.getText().toString()),
rgChannel.getCheckedRadioButtonId() == R.id.rb_stereo, cbEchoCanceler.isChecked(),
cbNoiseSuppressor.isChecked());
}
@Override
public void onConnectionStartedRtmp(String rtmpUrl) {
}
@Override
public void onConnectionSuccessRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtmpActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onConnectionFailedRtmp(final String reason) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtmpActivity.this, "Connection failed. " + reason, Toast.LENGTH_SHORT)
.show();
rtmpCamera1.stopStream();
bStartStop.setText(getResources().getString(R.string.start_button));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
&& rtmpCamera1.isRecording()) {
rtmpCamera1.stopRecord();
PathUtils.updateGallery(getApplicationContext(), folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(RtmpActivity.this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
currentDateAndTime = "";
}
}
});
}
@Override
public void onNewBitrateRtmp(final long bitrate) {
runOnUiThread(new Runnable() {
@Override
public void run() {
tvBitrate.setText(bitrate + " bps");
}
});
}
@Override
public void onDisconnectRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtmpActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
&& rtmpCamera1.isRecording()) {
rtmpCamera1.stopRecord();
PathUtils.updateGallery(getApplicationContext(), folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(RtmpActivity.this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
currentDateAndTime = "";
}
}
});
}
@Override
public void onAuthErrorRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtmpActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onAuthSuccessRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtmpActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
drawerLayout.openDrawer(GravityCompat.START);
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
rtmpCamera1.startPreview();
// optionally:
//rtmpCamera1.startPreview(CameraHelper.Facing.BACK);
//or
//rtmpCamera1.startPreview(CameraHelper.Facing.FRONT);
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && rtmpCamera1.isRecording()) {
rtmpCamera1.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
currentDateAndTime = "";
}
if (rtmpCamera1.isStreaming()) {
rtmpCamera1.stopStream();
bStartStop.setText(getResources().getString(R.string.start_button));
}
rtmpCamera1.stopPreview();
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
int action = motionEvent.getAction();
if (motionEvent.getPointerCount() > 1) {
if (action == MotionEvent.ACTION_MOVE) {
rtmpCamera1.setZoom(motionEvent);
}
} else if (action == MotionEvent.ACTION_DOWN) {
rtmpCamera1.tapToFocus(view, motionEvent);
}
return true;
}
}
(e). RtspActivity.java
Create a file named RtspActivity.java
Here is the full code
package com.pedro.rtpstreamer.customexample;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import com.google.android.material.navigation.NavigationView;
import com.pedro.encoder.input.video.CameraHelper;
import com.pedro.encoder.input.video.CameraOpenException;
import com.pedro.rtplibrary.rtsp.RtspCamera1;
import com.pedro.rtpstreamer.R;
import com.pedro.rtpstreamer.utils.PathUtils;
import com.pedro.rtsp.rtsp.Protocol;
import com.pedro.rtsp.utils.ConnectCheckerRtsp;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
* More documentation see:
* {@link com.pedro.rtplibrary.base.Camera1Base}
* {@link com.pedro.rtplibrary.rtsp.RtspCamera1}
*/
public class RtspActivity extends AppCompatActivity
implements Button.OnClickListener, ConnectCheckerRtsp, SurfaceHolder.Callback,
View.OnTouchListener {
private Integer[] orientations = new Integer[] { 0, 90, 180, 270 };
private RtspCamera1 rtspCamera1;
private SurfaceView surfaceView;
private Button bStartStop, bRecord;
private EditText etUrl;
private String currentDateAndTime = "";
private File folder;
//options menu
private DrawerLayout drawerLayout;
private NavigationView navigationView;
private ActionBarDrawerToggle actionBarDrawerToggle;
private RadioGroup rgChannel;
private RadioButton rbTcp, rbUdp;
private Spinner spResolution;
private CheckBox cbEchoCanceler, cbNoiseSuppressor;
private EditText etVideoBitrate, etFps, etAudioBitrate, etSampleRate, etWowzaUser,
etWowzaPassword;
private String lastVideoBitrate;
private TextView tvBitrate;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_custom);
folder = PathUtils.getRecordPath();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
surfaceView = findViewById(R.id.surfaceView);
surfaceView.getHolder().addCallback(this);
surfaceView.setOnTouchListener(this);
rtspCamera1 = new RtspCamera1(surfaceView, this);
prepareOptionsMenuViews();
tvBitrate = findViewById(R.id.tv_bitrate);
etUrl = findViewById(R.id.et_rtp_url);
etUrl.setHint(R.string.hint_rtsp);
bStartStop = findViewById(R.id.b_start_stop);
bStartStop.setOnClickListener(this);
bRecord = findViewById(R.id.b_record);
bRecord.setOnClickListener(this);
Button switchCamera = findViewById(R.id.switch_camera);
switchCamera.setOnClickListener(this);
}
private void prepareOptionsMenuViews() {
drawerLayout = findViewById(R.id.activity_custom);
navigationView = findViewById(R.id.nv_rtp);
navigationView.inflateMenu(R.menu.options_rtsp);
actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.rtsp_streamer,
R.string.rtsp_streamer) {
public void onDrawerOpened(View drawerView) {
actionBarDrawerToggle.syncState();
lastVideoBitrate = etVideoBitrate.getText().toString();
}
public void onDrawerClosed(View view) {
actionBarDrawerToggle.syncState();
if (lastVideoBitrate != null && !lastVideoBitrate.equals(
etVideoBitrate.getText().toString()) && rtspCamera1.isStreaming()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
int bitrate = Integer.parseInt(etVideoBitrate.getText().toString()) * 1024;
rtspCamera1.setVideoBitrateOnFly(bitrate);
Toast.makeText(RtspActivity.this, "New bitrate: " + bitrate, Toast.LENGTH_SHORT).
show();
} else {
Toast.makeText(RtspActivity.this, "Bitrate on fly ignored, Required min API 19",
Toast.LENGTH_SHORT).show();
}
}
}
};
drawerLayout.addDrawerListener(actionBarDrawerToggle);
//checkboxs
cbEchoCanceler =
(CheckBox) navigationView.getMenu().findItem(R.id.cb_echo_canceler).getActionView();
cbNoiseSuppressor =
(CheckBox) navigationView.getMenu().findItem(R.id.cb_noise_suppressor).getActionView();
//radiobuttons
rbTcp = (RadioButton) navigationView.getMenu().findItem(R.id.rb_tcp).getActionView();
rbUdp = (RadioButton) navigationView.getMenu().findItem(R.id.rb_udp).getActionView();
rgChannel = (RadioGroup) navigationView.getMenu().findItem(R.id.channel).getActionView();
rbTcp.setChecked(true);
rbTcp.setOnClickListener(this);
rbUdp.setOnClickListener(this);
//spinners
spResolution = (Spinner) navigationView.getMenu().findItem(R.id.sp_resolution).getActionView();
ArrayAdapter<Integer> orientationAdapter =
new ArrayAdapter<>(this, R.layout.support_simple_spinner_dropdown_item);
orientationAdapter.addAll(orientations);
ArrayAdapter<String> resolutionAdapter =
new ArrayAdapter<>(this, R.layout.support_simple_spinner_dropdown_item);
List<String> list = new ArrayList<>();
for (Camera.Size size : rtspCamera1.getResolutionsBack()) {
list.add(size.width + "X" + size.height);
}
resolutionAdapter.addAll(list);
spResolution.setAdapter(resolutionAdapter);
//edittexts
etVideoBitrate =
(EditText) navigationView.getMenu().findItem(R.id.et_video_bitrate).getActionView();
etFps = (EditText) navigationView.getMenu().findItem(R.id.et_fps).getActionView();
etAudioBitrate =
(EditText) navigationView.getMenu().findItem(R.id.et_audio_bitrate).getActionView();
etSampleRate = (EditText) navigationView.getMenu().findItem(R.id.et_samplerate).getActionView();
etVideoBitrate.setText("2500");
etFps.setText("30");
etAudioBitrate.setText("128");
etSampleRate.setText("44100");
etWowzaUser = (EditText) navigationView.getMenu().findItem(R.id.et_user).getActionView();
etWowzaPassword =
(EditText) navigationView.getMenu().findItem(R.id.et_password).getActionView();
}
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
actionBarDrawerToggle.syncState();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (!drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.openDrawer(GravityCompat.START);
} else {
drawerLayout.closeDrawer(GravityCompat.START);
}
return true;
case R.id.microphone:
if (!rtspCamera1.isAudioMuted()) {
item.setIcon(getResources().getDrawable(R.drawable.icon_microphone_off));
rtspCamera1.disableAudio();
} else {
item.setIcon(getResources().getDrawable(R.drawable.icon_microphone));
rtspCamera1.enableAudio();
}
return true;
default:
return false;
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.b_start_stop:
if (!rtspCamera1.isStreaming()) {
bStartStop.setText(getResources().getString(R.string.stop_button));
if (rbTcp.isChecked()) {
rtspCamera1.setProtocol(Protocol.TCP);
} else {
rtspCamera1.setProtocol(Protocol.UDP);
}
String user = etWowzaUser.getText().toString();
String password = etWowzaPassword.getText().toString();
if (!user.isEmpty() && !password.isEmpty()) {
rtspCamera1.setAuthorization(user, password);
}
if (rtspCamera1.isRecording() || prepareEncoders()) {
rtspCamera1.startStream(etUrl.getText().toString());
} else {
//If you see this all time when you start stream,
//it is because your encoder device dont support the configuration
//in video encoder maybe color format.
//If you have more encoder go to VideoEncoder or AudioEncoder class,
//change encoder and try
Toast.makeText(this, "Error preparing stream, This device cant do it",
Toast.LENGTH_SHORT).show();
bStartStop.setText(getResources().getString(R.string.start_button));
}
} else {
bStartStop.setText(getResources().getString(R.string.start_button));
rtspCamera1.stopStream();
}
break;
case R.id.b_record:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
if (!rtspCamera1.isRecording()) {
try {
if (!folder.exists()) {
folder.mkdir();
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
currentDateAndTime = sdf.format(new Date());
if (!rtspCamera1.isStreaming()) {
if (prepareEncoders()) {
rtspCamera1.startRecord(
folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.stop_record);
Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Error preparing stream, This device cant do it",
Toast.LENGTH_SHORT).show();
}
} else {
rtspCamera1.startRecord(
folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.stop_record);
Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
rtspCamera1.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
} else {
rtspCamera1.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "You need min JELLY_BEAN_MR2(API 18) for do it...",
Toast.LENGTH_SHORT).show();
}
break;
case R.id.switch_camera:
try {
rtspCamera1.switchCamera();
} catch (CameraOpenException e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
break;
//options menu
case R.id.rb_tcp:
if (rbUdp.isChecked()) {
rbUdp.setChecked(false);
rbTcp.setChecked(true);
}
break;
case R.id.rb_udp:
if (rbTcp.isChecked()) {
rbTcp.setChecked(false);
rbUdp.setChecked(true);
}
break;
default:
break;
}
}
private boolean prepareEncoders() {
Camera.Size resolution =
rtspCamera1.getResolutionsBack().get(spResolution.getSelectedItemPosition());
int width = resolution.width;
int height = resolution.height;
return rtspCamera1.prepareVideo(width, height, Integer.parseInt(etFps.getText().toString()),
Integer.parseInt(etVideoBitrate.getText().toString()) * 1024,
CameraHelper.getCameraOrientation(this)) && rtspCamera1.prepareAudio(
Integer.parseInt(etAudioBitrate.getText().toString()) * 1024,
Integer.parseInt(etSampleRate.getText().toString()),
rgChannel.getCheckedRadioButtonId() == R.id.rb_stereo, cbEchoCanceler.isChecked(),
cbNoiseSuppressor.isChecked());
}
@Override
public void onConnectionStartedRtsp(@NotNull String rtspUrl) {
}
@Override
public void onConnectionSuccessRtsp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtspActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onConnectionFailedRtsp(final String reason) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtspActivity.this, "Connection failed. " + reason, Toast.LENGTH_SHORT)
.show();
rtspCamera1.stopStream();
bStartStop.setText(getResources().getString(R.string.start_button));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
&& rtspCamera1.isRecording()) {
rtspCamera1.stopRecord();
PathUtils.updateGallery(getApplicationContext(), folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(RtspActivity.this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
currentDateAndTime = "";
}
}
});
}
@Override
public void onNewBitrateRtsp(final long bitrate) {
runOnUiThread(new Runnable() {
@Override
public void run() {
tvBitrate.setText(bitrate + " bps");
}
});
}
@Override
public void onDisconnectRtsp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtspActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
&& rtspCamera1.isRecording()) {
rtspCamera1.stopRecord();
PathUtils.updateGallery(getApplicationContext(), folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(RtspActivity.this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
currentDateAndTime = "";
}
}
});
}
@Override
public void onAuthErrorRtsp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
bStartStop.setText(getResources().getString(R.string.start_button));
rtspCamera1.stopStream();
Toast.makeText(RtspActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
&& rtspCamera1.isRecording()) {
rtspCamera1.stopRecord();
PathUtils.updateGallery(getApplicationContext(), folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(RtspActivity.this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
currentDateAndTime = "";
}
}
});
}
@Override
public void onAuthSuccessRtsp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtspActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
drawerLayout.openDrawer(GravityCompat.START);
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
rtspCamera1.startPreview();
// optionally:
//rtspCamera1.startPreview(CameraHelper.Facing.BACK);
//or
//rtspCamera1.startPreview(CameraHelper.Facing.FRONT);
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && rtspCamera1.isRecording()) {
rtspCamera1.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
currentDateAndTime = "";
}
if (rtspCamera1.isStreaming()) {
rtspCamera1.stopStream();
bStartStop.setText(getResources().getString(R.string.start_button));
}
rtspCamera1.stopPreview();
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
int action = motionEvent.getAction();
if (motionEvent.getPointerCount() > 1) {
if (action == MotionEvent.ACTION_MOVE) {
rtspCamera1.setZoom(motionEvent);
}
} else if (action == MotionEvent.ACTION_DOWN) {
rtspCamera1.tapToFocus(view, motionEvent);
}
return true;
}
}
(f). ExampleRtmpActivity.java
Create a file named ExampleRtmpActivity.java
Here is the full code
package com.pedro.rtpstreamer.defaultexample;
import android.os.Build;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.pedro.encoder.input.video.CameraOpenException;
import com.pedro.rtmp.utils.ConnectCheckerRtmp;
import com.pedro.rtplibrary.rtmp.RtmpCamera1;
import com.pedro.rtpstreamer.R;
import com.pedro.rtpstreamer.utils.PathUtils;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* More documentation see:
* {@link com.pedro.rtplibrary.base.Camera1Base}
* {@link com.pedro.rtplibrary.rtmp.RtmpCamera1}
*/
public class ExampleRtmpActivity extends AppCompatActivity
implements ConnectCheckerRtmp, View.OnClickListener, SurfaceHolder.Callback {
private RtmpCamera1 rtmpCamera1;
private Button button;
private Button bRecord;
private EditText etUrl;
private String currentDateAndTime = "";
private File folder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_example);
folder = PathUtils.getRecordPath();
SurfaceView surfaceView = findViewById(R.id.surfaceView);
button = findViewById(R.id.b_start_stop);
button.setOnClickListener(this);
bRecord = findViewById(R.id.b_record);
bRecord.setOnClickListener(this);
Button switchCamera = findViewById(R.id.switch_camera);
switchCamera.setOnClickListener(this);
etUrl = findViewById(R.id.et_rtp_url);
etUrl.setHint(R.string.hint_rtmp);
rtmpCamera1 = new RtmpCamera1(surfaceView, this);
rtmpCamera1.setReTries(10);
surfaceView.getHolder().addCallback(this);
}
@Override
public void onConnectionStartedRtmp(String rtmpUrl) {
}
@Override
public void onConnectionSuccessRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(ExampleRtmpActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onConnectionFailedRtmp(final String reason) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (rtmpCamera1.reTry(5000, reason, null)) {
Toast.makeText(ExampleRtmpActivity.this, "Retry", Toast.LENGTH_SHORT)
.show();
} else {
Toast.makeText(ExampleRtmpActivity.this, "Connection failed. " + reason, Toast.LENGTH_SHORT)
.show();
rtmpCamera1.stopStream();
button.setText(R.string.start_button);
}
}
});
}
@Override
public void onNewBitrateRtmp(final long bitrate) {
}
@Override
public void onDisconnectRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(ExampleRtmpActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onAuthErrorRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(ExampleRtmpActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
rtmpCamera1.stopStream();
button.setText(R.string.start_button);
}
});
}
@Override
public void onAuthSuccessRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(ExampleRtmpActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.b_start_stop:
if (!rtmpCamera1.isStreaming()) {
if (rtmpCamera1.isRecording()
|| rtmpCamera1.prepareAudio() && rtmpCamera1.prepareVideo()) {
button.setText(R.string.stop_button);
rtmpCamera1.startStream(etUrl.getText().toString());
} else {
Toast.makeText(this, "Error preparing stream, This device cant do it",
Toast.LENGTH_SHORT).show();
}
} else {
button.setText(R.string.start_button);
rtmpCamera1.stopStream();
}
break;
case R.id.switch_camera:
try {
rtmpCamera1.switchCamera();
} catch (CameraOpenException e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
break;
case R.id.b_record:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
if (!rtmpCamera1.isRecording()) {
try {
if (!folder.exists()) {
folder.mkdir();
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
currentDateAndTime = sdf.format(new Date());
if (!rtmpCamera1.isStreaming()) {
if (rtmpCamera1.prepareAudio() && rtmpCamera1.prepareVideo()) {
rtmpCamera1.startRecord(
folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.stop_record);
Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Error preparing stream, This device cant do it",
Toast.LENGTH_SHORT).show();
}
} else {
rtmpCamera1.startRecord(
folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.stop_record);
Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
rtmpCamera1.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
} else {
rtmpCamera1.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "You need min JELLY_BEAN_MR2(API 18) for do it...",
Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
rtmpCamera1.startPreview();
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && rtmpCamera1.isRecording()) {
rtmpCamera1.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
currentDateAndTime = "";
}
if (rtmpCamera1.isStreaming()) {
rtmpCamera1.stopStream();
button.setText(getResources().getString(R.string.start_button));
}
rtmpCamera1.stopPreview();
}
}
(g). ExampleRtspActivity.java
Create a file named ExampleRtspActivity.java
Here is the full code
package com.pedro.rtpstreamer.defaultexample;
import android.os.Build;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.pedro.encoder.input.video.CameraOpenException;
import com.pedro.rtplibrary.rtsp.RtspCamera1;
import com.pedro.rtpstreamer.R;
import com.pedro.rtpstreamer.utils.PathUtils;
import com.pedro.rtsp.utils.ConnectCheckerRtsp;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* More documentation see:
* {@link com.pedro.rtplibrary.base.Camera1Base}
* {@link com.pedro.rtplibrary.rtsp.RtspCamera1}
*/
public class ExampleRtspActivity extends AppCompatActivity
implements ConnectCheckerRtsp, View.OnClickListener, SurfaceHolder.Callback {
private RtspCamera1 rtspCamera1;
private Button button;
private Button bRecord;
private EditText etUrl;
private String currentDateAndTime = "";
private File folder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_example);
folder = PathUtils.getRecordPath();
SurfaceView surfaceView = findViewById(R.id.surfaceView);
button = findViewById(R.id.b_start_stop);
button.setOnClickListener(this);
bRecord = findViewById(R.id.b_record);
bRecord.setOnClickListener(this);
Button switchCamera = findViewById(R.id.switch_camera);
switchCamera.setOnClickListener(this);
etUrl = findViewById(R.id.et_rtp_url);
etUrl.setHint(R.string.hint_rtsp);
rtspCamera1 = new RtspCamera1(surfaceView, this);
rtspCamera1.setReTries(10);
surfaceView.getHolder().addCallback(this);
}
@Override
public void onConnectionStartedRtsp(@NotNull String rtspUrl) {
}
@Override
public void onConnectionSuccessRtsp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(ExampleRtspActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onConnectionFailedRtsp(final String reason) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (rtspCamera1.reTry(5000, reason, null)) {
Toast.makeText(ExampleRtspActivity.this, "Retry", Toast.LENGTH_SHORT)
.show();
} else {
Toast.makeText(ExampleRtspActivity.this, "Connection failed. " + reason, Toast.LENGTH_SHORT)
.show();
rtspCamera1.stopStream();
button.setText(R.string.start_button);
}
}
});
}
@Override
public void onNewBitrateRtsp(final long bitrate) {
}
@Override
public void onDisconnectRtsp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(ExampleRtspActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onAuthErrorRtsp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(ExampleRtspActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
rtspCamera1.stopStream();
button.setText(R.string.start_button);
}
});
}
@Override
public void onAuthSuccessRtsp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(ExampleRtspActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.b_start_stop:
if (!rtspCamera1.isStreaming()) {
if (rtspCamera1.isRecording()
|| rtspCamera1.prepareAudio() && rtspCamera1.prepareVideo()) {
button.setText(R.string.stop_button);
rtspCamera1.startStream(etUrl.getText().toString());
} else {
Toast.makeText(this, "Error preparing stream, This device cant do it",
Toast.LENGTH_SHORT).show();
}
} else {
button.setText(R.string.start_button);
rtspCamera1.stopStream();
}
break;
case R.id.switch_camera:
try {
rtspCamera1.switchCamera();
} catch (CameraOpenException e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
break;
case R.id.b_record:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
if (!rtspCamera1.isRecording()) {
try {
if (!folder.exists()) {
folder.mkdir();
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
currentDateAndTime = sdf.format(new Date());
if (!rtspCamera1.isStreaming()) {
if (rtspCamera1.prepareAudio() && rtspCamera1.prepareVideo()) {
rtspCamera1.startRecord(
folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.stop_record);
Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Error preparing stream, This device cant do it",
Toast.LENGTH_SHORT).show();
}
} else {
rtspCamera1.startRecord(
folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.stop_record);
Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
rtspCamera1.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
} else {
rtspCamera1.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "You need min JELLY_BEAN_MR2(API 18) for do it...",
Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
rtspCamera1.startPreview();
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && rtspCamera1.isRecording()) {
rtspCamera1.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
currentDateAndTime = "";
}
if (rtspCamera1.isStreaming()) {
rtspCamera1.stopStream();
button.setText(getResources().getString(R.string.start_button));
}
rtspCamera1.stopPreview();
}
}
(h). DisplayActivity.java
Create a file named DisplayActivity.java
Here is the full code
package com.pedro.rtpstreamer.displayexample;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.pedro.rtmp.utils.ConnectCheckerRtmp;
import com.pedro.rtpstreamer.R;
/**
* More documentation see:
* {@link com.pedro.rtplibrary.base.DisplayBase}
* {@link com.pedro.rtplibrary.rtmp.RtmpDisplay}
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class DisplayActivity extends AppCompatActivity
implements ConnectCheckerRtmp, View.OnClickListener {
private Button button;
private EditText etUrl;
private final int REQUEST_CODE_STREAM = 179; //random num
private final int REQUEST_CODE_RECORD = 180; //random num
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_display);
button = findViewById(R.id.b_start_stop);
button.setOnClickListener(this);
etUrl = findViewById(R.id.et_rtp_url);
etUrl.setHint(R.string.hint_rtmp);
DisplayService displayService = DisplayService.Companion.getINSTANCE();
//No streaming/recording start service
if (displayService == null) {
startService(new Intent(this, DisplayService.class));
}
if (displayService != null && displayService.isStreaming()) {
button.setText(R.string.stop_button);
} else {
button.setText(R.string.start_button);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
DisplayService displayService = DisplayService.Companion.getINSTANCE();
if (displayService != null && !displayService.isStreaming() && !displayService.isRecording()) {
//stop service only if no streaming or recording
stopService(new Intent(this, DisplayService.class));
}
}
@Override
public void onConnectionStartedRtmp(String rtmpUrl) {
}
@Override
public void onConnectionSuccessRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(DisplayActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onConnectionFailedRtmp(final String reason) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(DisplayActivity.this, "Connection failed. " + reason, Toast.LENGTH_SHORT)
.show();
DisplayService displayService = DisplayService.Companion.getINSTANCE();
if (displayService != null) {
displayService.stopStream();
}
button.setText(R.string.start_button);
}
});
}
@Override
public void onNewBitrateRtmp(long bitrate) {
}
@Override
public void onDisconnectRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(DisplayActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onAuthErrorRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(DisplayActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onAuthSuccessRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(DisplayActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (data != null && (requestCode == REQUEST_CODE_STREAM
|| requestCode == REQUEST_CODE_RECORD && resultCode == Activity.RESULT_OK)) {
DisplayService displayService = DisplayService.Companion.getINSTANCE();
if (displayService != null) {
String endpoint = etUrl.getText().toString();
displayService.prepareStreamRtp(endpoint, resultCode, data);
displayService.startStreamRtp(endpoint);
}
} else {
Toast.makeText(this, "No permissions available", Toast.LENGTH_SHORT).show();
button.setText(R.string.start_button);
}
}
@Override
public void onClick(View view) {
DisplayService displayService = DisplayService.Companion.getINSTANCE();
if (displayService != null) {
switch (view.getId()) {
case R.id.b_start_stop:
if (!displayService.isStreaming()) {
button.setText(R.string.stop_button);
startActivityForResult(displayService.sendIntent(), REQUEST_CODE_STREAM);
} else {
button.setText(R.string.start_button);
displayService.stopStream();
}
break;
default:
break;
}
}
}
}
(i). DisplayService.kt
Create a file named DisplayService.kt
Here is the full code
package com.pedro.rtpstreamer.displayexample
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import com.pedro.rtplibrary.base.DisplayBase
import com.pedro.rtplibrary.rtmp.RtmpDisplay
import com.pedro.rtplibrary.rtsp.RtspDisplay
import com.pedro.rtpstreamer.R
import com.pedro.rtpstreamer.backgroundexample.ConnectCheckerRtp
/**
* Basic RTMP/RTSP service streaming implementation with camera2
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
class DisplayService : Service() {
override fun onCreate() {
super.onCreate()
INSTANCE = this
Log.i(TAG, "RTP Display service create")
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_HIGH)
notificationManager?.createNotificationChannel(channel)
}
keepAliveTrick()
}
private fun keepAliveTrick() {
val notification = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.notification_icon)
.setSilent(true)
.setOngoing(false)
.build()
startForeground(1, notification)
}
override fun onBind(p0: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
INSTANCE = this
Log.i(TAG, "RTP Display service started")
displayBase = RtmpDisplay(baseContext, true, connectCheckerRtp)
displayBase?.glInterface?.setForceRender(true)
return START_STICKY
}
companion object {
private const val TAG = "DisplayService"
private const val channelId = "rtpDisplayStreamChannel"
const val notifyId = 123456
var INSTANCE: DisplayService? = null
}
private var notificationManager: NotificationManager? = null
private var displayBase: DisplayBase? = null
fun sendIntent(): Intent? {
return displayBase?.sendIntent()
}
fun isStreaming(): Boolean {
return displayBase?.isStreaming ?: false
}
fun isRecording(): Boolean {
return displayBase?.isRecording ?: false
}
fun stopStream() {
if (displayBase?.isStreaming == true) {
displayBase?.stopStream()
notificationManager?.cancel(notifyId)
}
}
private val connectCheckerRtp = object : ConnectCheckerRtp {
override fun onConnectionStartedRtp(rtpUrl: String) {
}
override fun onConnectionSuccessRtp() {
showNotification("Stream started")
Log.i(TAG, "RTP service destroy")
}
override fun onNewBitrateRtp(bitrate: Long) {
}
override fun onConnectionFailedRtp(reason: String) {
showNotification("Stream connection failed")
Log.i(TAG, "RTP service destroy")
}
override fun onDisconnectRtp() {
showNotification("Stream stopped")
}
override fun onAuthErrorRtp() {
showNotification("Stream auth error")
}
override fun onAuthSuccessRtp() {
showNotification("Stream auth success")
}
}
private fun showNotification(text: String) {
val notification = NotificationCompat.Builder(baseContext, channelId)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("RTP Display Stream")
.setContentText(text)
.setOngoing(false)
.build()
notificationManager?.notify(notifyId, notification)
}
override fun onDestroy() {
super.onDestroy()
Log.i(TAG, "RTP Display service destroy")
stopStream()
INSTANCE = null
}
fun prepareStreamRtp(endpoint: String, resultCode: Int, data: Intent) {
stopStream()
if (endpoint.startsWith("rtmp")) {
displayBase = RtmpDisplay(baseContext, true, connectCheckerRtp)
displayBase?.setIntentResult(resultCode, data)
} else {
displayBase = RtspDisplay(baseContext, true, connectCheckerRtp)
displayBase?.setIntentResult(resultCode, data)
}
displayBase?.glInterface?.setForceRender(true)
}
fun startStreamRtp(endpoint: String) {
if (displayBase?.isStreaming != true) {
if (displayBase?.prepareVideo() == true && displayBase?.prepareAudio() == true) {
displayBase?.startStream(endpoint)
}
} else {
showNotification("You are already streaming :(")
}
}
}
(j). RtmpFromFileActivity.java
Create a file named RtmpFromFileActivity.java
Here is the full code
package com.pedro.rtpstreamer.filestreamexample;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.pedro.encoder.input.decoder.AudioDecoderInterface;
import com.pedro.encoder.input.decoder.VideoDecoderInterface;
import com.pedro.rtmp.utils.ConnectCheckerRtmp;
import com.pedro.rtplibrary.rtmp.RtmpFromFile;
import com.pedro.rtpstreamer.R;
import com.pedro.rtpstreamer.utils.PathUtils;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* More documentation see:
* {@link com.pedro.rtplibrary.base.FromFileBase}
* {@link com.pedro.rtplibrary.rtmp.RtmpFromFile}
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class RtmpFromFileActivity extends AppCompatActivity
implements ConnectCheckerRtmp, View.OnClickListener, VideoDecoderInterface,
AudioDecoderInterface, SeekBar.OnSeekBarChangeListener {
private RtmpFromFile rtmpFromFile;
private Button button, bSelectFile, bReSync, bRecord;
private SeekBar seekBar;
private EditText etUrl;
private TextView tvFile;
private String filePath = "";
private boolean touching = false;
private String currentDateAndTime = "";
private File folder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_from_file);
folder = PathUtils.getRecordPath();
button = findViewById(R.id.b_start_stop);
bSelectFile = findViewById(R.id.b_select_file);
button.setOnClickListener(this);
bSelectFile.setOnClickListener(this);
bReSync = findViewById(R.id.b_re_sync);
bReSync.setOnClickListener(this);
bRecord = findViewById(R.id.b_record);
bRecord.setOnClickListener(this);
etUrl = findViewById(R.id.et_rtp_url);
etUrl.setHint(R.string.hint_rtmp);
seekBar = findViewById(R.id.seek_bar);
seekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
tvFile = findViewById(R.id.tv_file);
rtmpFromFile = new RtmpFromFile(this, this, this);
seekBar.setOnSeekBarChangeListener(this);
}
@Override
protected void onPause() {
super.onPause();
if (rtmpFromFile.isRecording()) {
rtmpFromFile.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
}
if (rtmpFromFile.isStreaming()) {
rtmpFromFile.stopStream();
button.setText(getResources().getString(R.string.start_button));
}
}
@Override
public void onConnectionStartedRtmp(String rtmpUrl) {
}
@Override
public void onConnectionSuccessRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtmpFromFileActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onConnectionFailedRtmp(final String reason) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtmpFromFileActivity.this, "Connection failed. " + reason,
Toast.LENGTH_SHORT).show();
rtmpFromFile.stopStream();
button.setText(R.string.start_button);
}
});
}
@Override
public void onNewBitrateRtmp(long bitrate) {
}
@Override
public void onDisconnectRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtmpFromFileActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onAuthErrorRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtmpFromFileActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onAuthSuccessRtmp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtmpFromFileActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 5 && data != null) {
filePath = PathUtils.getPath(this, data.getData());
Toast.makeText(this, filePath, Toast.LENGTH_SHORT).show();
tvFile.setText(filePath);
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.b_start_stop:
if (!rtmpFromFile.isStreaming()) {
try {
if (!rtmpFromFile.isRecording()) {
if (prepare()) {
button.setText(R.string.stop_button);
rtmpFromFile.startStream(etUrl.getText().toString());
seekBar.setMax(Math.max((int) rtmpFromFile.getVideoDuration(),
(int) rtmpFromFile.getAudioDuration()));
updateProgress();
} else {
button.setText(R.string.start_button);
rtmpFromFile.stopStream();
/*This error could be 2 things.
Your device cant decode or encode this file or
the file is not supported for the library.
The file need has h264 video codec and acc audio codec*/
Toast.makeText(this, "Error: unsupported file", Toast.LENGTH_SHORT).show();
}
} else {
button.setText(R.string.stop_button);
rtmpFromFile.startStream(etUrl.getText().toString());
}
} catch (IOException e) {
//Normally this error is for file not found or read permissions
Toast.makeText(this, "Error: file not found", Toast.LENGTH_SHORT).show();
}
} else {
button.setText(R.string.start_button);
rtmpFromFile.stopStream();
}
break;
case R.id.b_select_file:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
startActivityForResult(intent, 5);
break;
//sometimes async is produced when you move in file several times
case R.id.b_re_sync:
rtmpFromFile.reSyncFile();
break;
case R.id.b_record:
if (!rtmpFromFile.isRecording()) {
try {
if (!folder.exists()) {
folder.mkdir();
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
currentDateAndTime = sdf.format(new Date());
if (!rtmpFromFile.isStreaming()) {
if (prepare()) {
rtmpFromFile.startRecord(
folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
seekBar.setMax(Math.max((int) rtmpFromFile.getVideoDuration(),
(int) rtmpFromFile.getAudioDuration()));
updateProgress();
bRecord.setText(R.string.stop_record);
Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Error preparing stream, This device cant do it",
Toast.LENGTH_SHORT).show();
}
} else {
rtmpFromFile.startRecord(
folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.stop_record);
Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
rtmpFromFile.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
} else {
rtmpFromFile.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
currentDateAndTime = "";
}
break;
default:
break;
}
}
private boolean prepare() throws IOException {
boolean result = rtmpFromFile.prepareVideo(filePath);
result |= rtmpFromFile.prepareAudio(filePath);
return result;
}
private void updateProgress() {
new Thread(new Runnable() {
@Override
public void run() {
while (rtmpFromFile.isStreaming() || rtmpFromFile.isRecording()) {
try {
Thread.sleep(1000);
if (!touching) {
runOnUiThread(new Runnable() {
@Override
public void run() {
seekBar.setProgress(Math.max((int) rtmpFromFile.getVideoTime(),
(int) rtmpFromFile.getAudioTime()));
}
});
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
@Override
public void onVideoDecoderFinished() {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (rtmpFromFile.isRecording()) {
rtmpFromFile.stopRecord();
PathUtils.updateGallery(getApplicationContext(), folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(RtmpFromFileActivity.this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
currentDateAndTime = "";
}
if (rtmpFromFile.isStreaming()) {
button.setText(R.string.start_button);
Toast.makeText(RtmpFromFileActivity.this, "Video stream finished", Toast.LENGTH_SHORT)
.show();
rtmpFromFile.stopStream();
}
}
});
}
@Override
public void onAudioDecoderFinished() {
}
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
touching = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (rtmpFromFile.isStreaming()) rtmpFromFile.moveTo(seekBar.getProgress());
touching = false;
}
}
(k). RtspFromFileActivity.java
Create a file named RtspFromFileActivity.java
Here is the full code
package com.pedro.rtpstreamer.filestreamexample;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.pedro.encoder.input.decoder.AudioDecoderInterface;
import com.pedro.encoder.input.decoder.VideoDecoderInterface;
import com.pedro.rtplibrary.rtsp.RtspFromFile;
import com.pedro.rtpstreamer.R;
import com.pedro.rtpstreamer.utils.PathUtils;
import com.pedro.rtsp.utils.ConnectCheckerRtsp;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* More documentation see:
* {@link com.pedro.rtplibrary.base.FromFileBase}
* {@link com.pedro.rtplibrary.rtsp.RtspFromFile}
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class RtspFromFileActivity extends AppCompatActivity
implements ConnectCheckerRtsp, View.OnClickListener, VideoDecoderInterface,
AudioDecoderInterface, SeekBar.OnSeekBarChangeListener {
private RtspFromFile rtspFromFile;
private Button button, bSelectFile, bReSync, bRecord;
private SeekBar seekBar;
private EditText etUrl;
private TextView tvFile;
private String filePath = "";
private boolean touching = false;
private String currentDateAndTime = "";
private File folder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_from_file);
folder = PathUtils.getRecordPath();
button = findViewById(R.id.b_start_stop);
bSelectFile = findViewById(R.id.b_select_file);
button.setOnClickListener(this);
bSelectFile.setOnClickListener(this);
bReSync = findViewById(R.id.b_re_sync);
bReSync.setOnClickListener(this);
bRecord = findViewById(R.id.b_record);
bRecord.setOnClickListener(this);
etUrl = findViewById(R.id.et_rtp_url);
etUrl.setHint(R.string.hint_rtsp);
seekBar = findViewById(R.id.seek_bar);
seekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
seekBar.setOnSeekBarChangeListener(this);
tvFile = findViewById(R.id.tv_file);
rtspFromFile = new RtspFromFile(this, this, this);
}
@Override
protected void onPause() {
super.onPause();
if (rtspFromFile.isRecording()) {
rtspFromFile.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
}
if (rtspFromFile.isStreaming()) {
rtspFromFile.stopStream();
button.setText(getResources().getString(R.string.start_button));
}
}
@Override
public void onConnectionStartedRtsp(@NotNull String rtspUrl) {
}
@Override
public void onConnectionSuccessRtsp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtspFromFileActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onConnectionFailedRtsp(final String reason) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtspFromFileActivity.this, "Connection failed. " + reason,
Toast.LENGTH_SHORT).show();
rtspFromFile.stopStream();
button.setText(R.string.start_button);
}
});
}
@Override
public void onNewBitrateRtsp(long bitrate) {
}
@Override
public void onDisconnectRtsp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtspFromFileActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onAuthErrorRtsp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtspFromFileActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onAuthSuccessRtsp() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RtspFromFileActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 5 && data != null) {
filePath = PathUtils.getPath(this, data.getData());
Toast.makeText(this, filePath, Toast.LENGTH_SHORT).show();
tvFile.setText(filePath);
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.b_start_stop:
if (!rtspFromFile.isStreaming()) {
try {
if (!rtspFromFile.isRecording()) {
if (prepare()) {
button.setText(R.string.stop_button);
rtspFromFile.startStream(etUrl.getText().toString());
seekBar.setMax(Math.max((int) rtspFromFile.getVideoDuration(),
(int) rtspFromFile.getAudioDuration()));
updateProgress();
} else {
button.setText(R.string.start_button);
rtspFromFile.stopStream();
/*This error could be 2 things.
Your device cant decode or encode this file or
the file is not supported for the library.
The file need has h264 video codec and acc audio codec*/
Toast.makeText(this, "Error: unsupported file", Toast.LENGTH_SHORT).show();
}
} else {
button.setText(R.string.stop_button);
rtspFromFile.startStream(etUrl.getText().toString());
}
} catch (IOException e) {
//Normally this error is for file not found or read permissions
Toast.makeText(this, "Error: file not found", Toast.LENGTH_SHORT).show();
}
} else {
button.setText(R.string.start_button);
rtspFromFile.stopStream();
}
break;
case R.id.b_select_file:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
startActivityForResult(intent, 5);
break;
//sometimes async is produced when you move in file several times
case R.id.b_re_sync:
rtspFromFile.reSyncFile();
break;
case R.id.b_record:
if (!rtspFromFile.isRecording()) {
try {
if (!folder.exists()) {
folder.mkdir();
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
currentDateAndTime = sdf.format(new Date());
if (!rtspFromFile.isStreaming()) {
if (prepare()) {
rtspFromFile.startRecord(
folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
seekBar.setMax(Math.max((int) rtspFromFile.getVideoDuration(),
(int) rtspFromFile.getAudioDuration()));
updateProgress();
bRecord.setText(R.string.stop_record);
Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Error preparing stream, This device cant do it",
Toast.LENGTH_SHORT).show();
}
} else {
rtspFromFile.startRecord(
folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.stop_record);
Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
rtspFromFile.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
} else {
rtspFromFile.stopRecord();
PathUtils.updateGallery(this, folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
currentDateAndTime = "";
}
break;
default:
break;
}
}
private boolean prepare() throws IOException {
boolean result = rtspFromFile.prepareVideo(filePath);
result |= rtspFromFile.prepareAudio(filePath);
return result;
}
private void updateProgress() {
new Thread(new Runnable() {
@Override
public void run() {
while (rtspFromFile.isStreaming() || rtspFromFile.isRecording()) {
try {
Thread.sleep(1000);
if (!touching) {
runOnUiThread(new Runnable() {
@Override
public void run() {
seekBar.setProgress(Math.max((int) rtspFromFile.getVideoTime(),
(int) rtspFromFile.getAudioTime()));
}
});
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
@Override
public void onVideoDecoderFinished() {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (rtspFromFile.isRecording()) {
rtspFromFile.stopRecord();
PathUtils.updateGallery(getApplicationContext(), folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
bRecord.setText(R.string.start_record);
Toast.makeText(RtspFromFileActivity.this,
"file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
Toast.LENGTH_SHORT).show();
currentDateAndTime = "";
}
if (rtspFromFile.isStreaming()) {
button.setText(R.string.start_button);
Toast.makeText(RtspFromFileActivity.this, "Video stream finished", Toast.LENGTH_SHORT)
.show();
rtspFromFile.stopStream();
}
}
});
}
@Override
public void onAudioDecoderFinished() {
}
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
touching = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (rtspFromFile.isStreaming()) rtspFromFile.moveTo(seekBar.getProgress());
touching = false;
}
}
(m). MainActivity.java
Create a file named MainActivity.java
Here is the full code
package com.pedro.rtpstreamer;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import androidx.core.app.ActivityCompat;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.TextView;
import android.widget.Toast;
import com.pedro.rtpstreamer.backgroundexample.BackgroundActivity;
import com.pedro.rtpstreamer.customexample.RtmpActivity;
import com.pedro.rtpstreamer.customexample.RtspActivity;
import com.pedro.rtpstreamer.defaultexample.ExampleRtmpActivity;
import com.pedro.rtpstreamer.defaultexample.ExampleRtspActivity;
import com.pedro.rtpstreamer.displayexample.DisplayActivity;
import com.pedro.rtpstreamer.filestreamexample.RtmpFromFileActivity;
import com.pedro.rtpstreamer.filestreamexample.RtspFromFileActivity;
import com.pedro.rtpstreamer.openglexample.OpenGlRtmpActivity;
import com.pedro.rtpstreamer.openglexample.OpenGlRtspActivity;
import com.pedro.rtpstreamer.rotation.RotationExampleActivity;
import com.pedro.rtpstreamer.surfacemodeexample.SurfaceModeRtmpActivity;
import com.pedro.rtpstreamer.surfacemodeexample.SurfaceModeRtspActivity;
import com.pedro.rtpstreamer.texturemodeexample.TextureModeRtmpActivity;
import com.pedro.rtpstreamer.texturemodeexample.TextureModeRtspActivity;
import com.pedro.rtpstreamer.utils.ActivityLink;
import com.pedro.rtpstreamer.utils.ImageAdapter;
import java.util.ArrayList;
import java.util.List;
import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
private GridView list;
private List<ActivityLink> activities;
private final String[] PERMISSIONS = {
Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
overridePendingTransition(R.transition.slide_in, R.transition.slide_out);
TextView tvVersion = findViewById(R.id.tv_version);
tvVersion.setText(getString(R.string.version, BuildConfig.VERSION_NAME));
list = findViewById(R.id.list);
createList();
setListAdapter(activities);
if (!hasPermissions(this, PERMISSIONS)) {
ActivityCompat.requestPermissions(this, PERMISSIONS, 1);
}
}
@SuppressLint("NewApi")
private void createList() {
activities = new ArrayList<>();
activities.add(new ActivityLink(new Intent(this, RtmpActivity.class),
getString(R.string.rtmp_streamer), JELLY_BEAN));
activities.add(new ActivityLink(new Intent(this, RtspActivity.class),
getString(R.string.rtsp_streamer), JELLY_BEAN));
activities.add(new ActivityLink(new Intent(this, ExampleRtmpActivity.class),
getString(R.string.default_rtmp), JELLY_BEAN));
activities.add(new ActivityLink(new Intent(this, ExampleRtspActivity.class),
getString(R.string.default_rtsp), JELLY_BEAN));
activities.add(new ActivityLink(new Intent(this, RtmpFromFileActivity.class),
getString(R.string.from_file_rtmp), JELLY_BEAN_MR2));
activities.add(new ActivityLink(new Intent(this, RtspFromFileActivity.class),
getString(R.string.from_file_rtsp), JELLY_BEAN_MR2));
activities.add(new ActivityLink(new Intent(this, SurfaceModeRtmpActivity.class),
getString(R.string.surface_mode_rtmp), LOLLIPOP));
activities.add(new ActivityLink(new Intent(this, SurfaceModeRtspActivity.class),
getString(R.string.surface_mode_rtsp), LOLLIPOP));
activities.add(new ActivityLink(new Intent(this, TextureModeRtmpActivity.class),
getString(R.string.texture_mode_rtmp), LOLLIPOP));
activities.add(new ActivityLink(new Intent(this, TextureModeRtspActivity.class),
getString(R.string.texture_mode_rtsp), LOLLIPOP));
activities.add(new ActivityLink(new Intent(this, OpenGlRtmpActivity.class),
getString(R.string.opengl_rtmp), JELLY_BEAN_MR2));
activities.add(new ActivityLink(new Intent(this, OpenGlRtspActivity.class),
getString(R.string.opengl_rtsp), JELLY_BEAN_MR2));
activities.add(new ActivityLink(new Intent(this, DisplayActivity.class),
getString(R.string.display_rtmp), LOLLIPOP));
activities.add(new ActivityLink(new Intent(this, BackgroundActivity.class),
getString(R.string.service_rtp), LOLLIPOP));
activities.add(new ActivityLink(new Intent(this, RotationExampleActivity.class),
getString(R.string.rotation_rtmp), LOLLIPOP));
}
private void setListAdapter(List<ActivityLink> activities) {
list.setAdapter(new ImageAdapter(activities));
list.setOnItemClickListener(this);
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (hasPermissions(this, PERMISSIONS)) {
ActivityLink link = activities.get(i);
int minSdk = link.getMinSdk();
if (Build.VERSION.SDK_INT >= minSdk) {
startActivity(link.getIntent());
overridePendingTransition(R.transition.slide_in, R.transition.slide_out);
} else {
showMinSdkError(minSdk);
}
} else {
showPermissionsErrorAndRequest();
}
}
private void showMinSdkError(int minSdk) {
String named;
switch (minSdk) {
case JELLY_BEAN_MR2:
named = "JELLY_BEAN_MR2";
break;
case LOLLIPOP:
named = "LOLLIPOP";
break;
default:
named = "JELLY_BEAN";
break;
}
Toast.makeText(this, "You need min Android " + named + " (API " + minSdk + " )",
Toast.LENGTH_SHORT).show();
}
private void showPermissionsErrorAndRequest() {
Toast.makeText(this, "You need permissions before", Toast.LENGTH_SHORT).show();
ActivityCompat.requestPermissions(this, PERMISSIONS, 1);
}
private boolean hasPermissions(Context context, String... permissions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context != null && permissions != null) {
for (String permission : permissions) {
if (ActivityCompat.checkSelfPermission(context, permission)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
}
return true;
}
}
(n). OpenGlRtmpActivity.java
Create a file named OpenGlRtmpActivity.java
Here is the full code
package com.pedro.rtpstreamer.openglexample;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.SurfaceTexture;
import android.media.MediaPlayer;
import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import com.pedro.encoder.input.gl.SpriteGestureController;
import com.pedro.encoder.input.gl.render.filters.AnalogTVFilterRender;
import com.pedro.encoder.input.gl.render.filters.AndroidViewFilterRender;
import com.pedro.encoder.input.gl.render.filters.BasicDeformationFilterRender;
import com.pedro.encoder.input.gl.render.filters.BeautyFilterRender;
import com.pedro.encoder.input.gl.render.filters.BlackFilterRender;
import com.pedro.encoder.input.gl.render.filters.BlurFilterRender;
import com.pedro.encoder.input.gl.render.filters.BrightnessFilterRender;
import com.pedro.encoder.input.gl.render.filters.CartoonFilterRender;
import com.pedro.encoder.input.gl.render.filters.ChromaFilterRender;
import com.pedro.encoder.input.gl.render.filters.CircleFilterRender;
import com.pedro.encoder.input.gl.render.filters.ColorFilterRender;
import com.pedro.encoder.input.gl.render.filters.ContrastFilterRender;
import com.pedro.encoder.input.gl.render.filters.DuotoneFilterRender;
import com.pedro.encoder.input.gl.render.filters.EarlyBirdFilterRender;
import com.pedro.encoder.input.gl.render.filters.EdgeDetectionFilterRender;
import com.pedro.encoder.input.gl.render.filters.ExposureFilterRender;
import com.pedro.encoder.input.gl.render.filters.FireFilterRender;
import com.pedro.encoder.input.gl.render.filters.GammaFilterRender;
import com.pedro.encoder.input.gl.render.filters.GlitchFilterRender;
import com.pedro.encoder.input.gl.render.filters.GreyScaleFilterRender;
import com.pedro.encoder.input.gl.render.filters.HalftoneLinesFilterRender;
import com.pedro.encoder.input.gl.render.filters.Image70sFilterRender;
import com.pedro.encoder.input.gl.render.filters.LamoishFilterRender;
import com.pedro.encoder.input.gl.render.filters.MoneyFilterRender;
import com.pedro.encoder.input.gl.render.filters.NegativeFilterRender;
import com.pedro.encoder.input.gl.render.filters.NoFilterRender;
import com.pedro.encoder.input.gl.render.filters.PixelatedFilterRender;
import com.pedro.encoder.input.gl.render.filters.PolygonizationFilterRender;
import com.pedro.encoder.input.gl.render.filters.RGBSaturationFilterRender;
import com.pedro.encoder.input.gl.render.filters.RainbowFilterRender;
import com.pedro.encoder.input.gl.render.filters.RippleFilterRender;
import com.pedro.encoder.input.gl.render.filters.RotationFilterRender;
import com.pedro.encoder.input.gl.render.filters.SaturationFilterRender;
import com.pedro.encoder.input.gl.render.filters.SepiaFilterRender;
import com.pedro.encoder.input.gl.render.filters.SharpnessFilterRender;
import com.pedro.encoder.input.gl.render.filters.SnowFilterRender;
import com.pedro.encoder.input.gl.render.filters.SwirlFilterRender;
import com.pedro.encoder.input.gl.render.filters.TemperatureFilterRender;
import com.pedro.encoder.input.gl.render.filters.ZebraFilterRender;
import com.pedro.encoder.input.gl.render.filters.object.GifObjectFilterRender;
import com.pedro.encoder.input.gl.render.filters.object.ImageObjectFilterRender;
import com.pedro.encoder.input.gl.render.filters.object.SurfaceFilterRender;
import com.pedro.encoder.input.gl.render.filters.object.TextObjectFilterRender;
import com.pedro.encoder.input.video.CameraOpenException;
import com.pedro.encoder.utils.gl.TranslateTo;
import com.pedro.rtmp.utils.ConnectCheckerRtmp;
import com.pedro.rtplibrary.rtmp.RtmpCamera1;
import com.pedro.rtplibrary.view.OpenGlView;
import com.pedro.rtpstreamer.R;
import com.pedro.rtpstreamer.utils.PathUtils;
import java.io.File;
import java.io.IOException;