First commit

This commit is contained in:
ThibaultBee
2022-11-11 18:58:47 +01:00
commit cc5984b6c1
39 changed files with 1031 additions and 0 deletions

View File

@@ -0,0 +1,171 @@
package io.github.thibaultbee.streampack.example
import android.Manifest
import android.annotation.SuppressLint
import android.content.pm.ActivityInfo
import android.media.AudioFormat
import android.media.MediaFormat
import android.os.Bundle
import android.util.Log
import android.util.Size
import androidx.annotation.RequiresPermission
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import io.github.thibaultbee.streampack.data.AudioConfig
import io.github.thibaultbee.streampack.data.VideoConfig
import io.github.thibaultbee.streampack.error.StreamPackError
import io.github.thibaultbee.streampack.example.databinding.ActivityMainBinding
import io.github.thibaultbee.streampack.example.utils.PermissionsManager
import io.github.thibaultbee.streampack.example.utils.toast
import io.github.thibaultbee.streampack.ext.rtmp.streamers.CameraRtmpLiveStreamer
import io.github.thibaultbee.streampack.ext.srt.streamers.CameraSrtLiveStreamer
import io.github.thibaultbee.streampack.listeners.OnConnectionListener
import io.github.thibaultbee.streampack.listeners.OnErrorListener
import io.github.thibaultbee.streampack.streamers.StreamerLifeCycleObserver
import io.github.thibaultbee.streampack.streamers.helpers.CameraStreamerConfigurationHelper
import io.github.thibaultbee.streampack.utils.TAG
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val streamerRequiredPermissions =
listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
@SuppressLint("MissingPermission")
private val permissionsManager = PermissionsManager(this,
streamerRequiredPermissions,
onAllGranted = { inflateStreamer() },
onShowPermissionRationale = { true },
onDenied = {})
// Reports and manages error with [OnErrorListener]
private val errorListener = object : OnErrorListener {
override fun onError(error: StreamPackError) {
toast("An error occured: $error")
}
}
// Reports and manages connection events with [OnConnectionListener]
private val connectionListener = object : OnConnectionListener {
override fun onFailed(message: String) {
toast("Connection failed: $message")
}
override fun onLost(message: String) {
toast("Connection lost: $message")
}
override fun onSuccess() {
toast("Connected")
}
}
/**
* The streamer is the central object of StreamPack.
* It is responsible for the capture audio and video and the streaming process.
*
* Use a [CameraRtmpLiveStreamer] For RTMP. Or a [CameraSrtLiveStreamer] for SRT.
*/
private val streamer by lazy {
CameraRtmpLiveStreamer(
this,
initialOnErrorListener = errorListener,
initialOnConnectionListener = connectionListener
)
}
/**
* Listen to lifecycle events. So we don't have to stop the streamer manually in `onPause` and release in `onDestroy
*/
private val streamerLifeCycleObserver by lazy { StreamerLifeCycleObserver(streamer) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.liveButton.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
/**
* Dispatch from main thread is forced to avoid making network call on main thread
* with coroutines.
*/
lifecycleScope.launch {
/**
* For SRT, use srt://my.server.url:9998?streamid=myStreamId&passphrase=myPassphrase
*/
try {
/**
* Always lock the device orientation during a live streaming to avoid
* to recreate the Activity.
*/
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
streamer.startStream("rtmp://192.168.1.12/app/streamKey")
//streamer.startStream("rtmp://my.server.url:1234/app/streamKey")
} catch (e: Exception) {
binding.liveButton.isChecked = false
Log.e(TAG, "Failed to connect", e)
}
}
} else {
streamer.stopStream()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
}
}
override fun onResume() {
super.onResume()
permissionsManager.requestPermissions()
}
@RequiresPermission(allOf = [Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO])
private fun inflateStreamer() {
lifecycle.addObserver(streamerLifeCycleObserver) // Register the lifecycle observer
/**
* Configure the streamer before calling view.streamer. Because it will start camera preview
* which required a configuration
*/
configureStreamer()
binding.preview.streamer = streamer // Bind the streamer to the preview
}
private fun configureStreamer() {
/**
* To check the parameters supported by the device, you can check parameter against:
* - [CameraStreamerConfigurationHelper.flvHelper] for RTMP live streaming or
* - [CameraStreamerConfigurationHelper.tsHelper] for SRT communication.
*/
/**
* There are other parameters in the [VideoConfig] such as:
* - bitrate
* - profile
* - level
* - gopSize
* They will be initialized with an appropriate default value.
*/
val videoConfig = VideoConfig(
mimeType = MediaFormat.MIMETYPE_VIDEO_AVC, resolution = Size(1280, 720), fps = 25
)
/**
* There are other parameters in the [AudioConfig] such as:
* - byteFormat
* - enableEchoCanceler
* - enableNoiseSuppressor
* They will be initialized with an appropriate default value.
*/
val audioConfig = AudioConfig(
mimeType = MediaFormat.MIMETYPE_AUDIO_AAC,
sampleRate = 44100,
channelConfig = AudioFormat.CHANNEL_IN_STEREO
)
streamer.configure(audioConfig, videoConfig)
}
private fun toast(message: String) {
runOnUiThread { applicationContext.toast(message) }
}
}

View File

@@ -0,0 +1,7 @@
package io.github.thibaultbee.streampack.example.utils
import android.content.Context
import android.widget.Toast
fun Context.toast(message: String) =
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()

View File

@@ -0,0 +1,50 @@
package io.github.thibaultbee.streampack.example.utils
import android.content.pm.PackageManager
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
class PermissionsManager(
private val activity: ComponentActivity,
private val requiredPermissions: List<String>,
private val onAllGranted: () -> Unit,
private val onShowPermissionRationale: (List<String>) -> Boolean,
private val onDenied: (List<String>) -> Unit
) {
private fun hasPermissions(): Boolean {
return requiredPermissions.all {
activity.checkSelfPermission(it) == PackageManager.PERMISSION_GRANTED
}
}
fun requestPermissions() {
if (hasPermissions()) {
onAllGranted()
} else {
var hasShownRationale = false
requiredPermissions.forEach {
if (activity.shouldShowRequestPermissionRationale(it)) {
hasShownRationale = true
// Last chance to show rationale
if (onShowPermissionRationale(requiredPermissions)) {
requestMultiplePermissions.launch(arrayOf(it))
}
}
}
if (!hasShownRationale) {
requestMultiplePermissions.launch(requiredPermissions.toTypedArray())
}
}
}
private val requestMultiplePermissions =
activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
permissions.filter { !it.value }.keys.toList().let {
if (it.isNotEmpty()) {
onDenied(it)
} else {
onAllGranted()
}
}
}
}