First commit
This commit is contained in:
@@ -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) }
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user