chore(deps): upgrade to StreamPack 3.0.0-RC
This commit is contained in:
@@ -47,11 +47,13 @@ dependencies {
|
|||||||
implementation 'com.google.android.material:material:1.12.0'
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
||||||
|
|
||||||
implementation "io.github.thibaultbee:streampack:${streampackVersion}"
|
implementation "io.github.thibaultbee.streampack:streampack-core:${streampackVersion}"
|
||||||
// Only needed for RTMP live streaming
|
// For the `PreviewView`
|
||||||
implementation "io.github.thibaultbee:streampack-extension-rtmp:${streampackVersion}"
|
implementation "io.github.thibaultbee.streampack:streampack-ui:${streampackVersion}"
|
||||||
// Only needed for SRT live streaming
|
// TODO: Only needed for RTMP live streaming: remove if you don't need it
|
||||||
implementation "io.github.thibaultbee:streampack-extension-srt:${streampackVersion}"
|
implementation "io.github.thibaultbee.streampack:streampack-extension-rtmp:${streampackVersion}"
|
||||||
|
// TODO: Only needed for SRT live streaming: remove if you don't need it
|
||||||
|
implementation "io.github.thibaultbee.streampack:streampack-extension-srt:${streampackVersion}"
|
||||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.7'
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.7'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
|||||||
@@ -10,20 +10,25 @@ import android.util.Log
|
|||||||
import android.util.Size
|
import android.util.Size
|
||||||
import androidx.annotation.RequiresPermission
|
import androidx.annotation.RequiresPermission
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import io.github.thibaultbee.streampack.data.AudioConfig
|
import io.github.thibaultbee.streampack.app.data.rotation.RotationRepository
|
||||||
import io.github.thibaultbee.streampack.data.VideoConfig
|
import io.github.thibaultbee.streampack.core.elements.sources.audio.audiorecord.MicrophoneSourceFactory
|
||||||
import io.github.thibaultbee.streampack.error.StreamPackError
|
import io.github.thibaultbee.streampack.core.elements.sources.video.camera.extensions.defaultCameraId
|
||||||
|
import io.github.thibaultbee.streampack.core.interfaces.setCameraId
|
||||||
|
import io.github.thibaultbee.streampack.core.interfaces.startStream
|
||||||
|
import io.github.thibaultbee.streampack.core.streamers.dual.DualStreamer
|
||||||
|
import io.github.thibaultbee.streampack.core.streamers.lifecycle.StreamerActivityLifeCycleObserver
|
||||||
|
import io.github.thibaultbee.streampack.core.streamers.single.AudioConfig
|
||||||
|
import io.github.thibaultbee.streampack.core.streamers.single.SingleStreamer
|
||||||
|
import io.github.thibaultbee.streampack.core.streamers.single.VideoConfig
|
||||||
|
import io.github.thibaultbee.streampack.core.utils.extensions.isClosedException
|
||||||
import io.github.thibaultbee.streampack.example.databinding.ActivityMainBinding
|
import io.github.thibaultbee.streampack.example.databinding.ActivityMainBinding
|
||||||
import io.github.thibaultbee.streampack.example.utils.PermissionsManager
|
import io.github.thibaultbee.streampack.example.utils.PermissionsManager
|
||||||
import io.github.thibaultbee.streampack.example.utils.showDialog
|
import io.github.thibaultbee.streampack.example.utils.showDialog
|
||||||
import io.github.thibaultbee.streampack.example.utils.toast
|
import io.github.thibaultbee.streampack.example.utils.toast
|
||||||
import io.github.thibaultbee.streampack.ext.rtmp.streamers.CameraRtmpLiveStreamer
|
import kotlinx.coroutines.flow.filter
|
||||||
import io.github.thibaultbee.streampack.ext.srt.streamers.CameraSrtLiveStreamer
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
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 kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
@@ -33,9 +38,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
|
listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
private val permissionsManager = PermissionsManager(this,
|
private val permissionsManager = PermissionsManager(
|
||||||
|
this,
|
||||||
streamerRequiredPermissions,
|
streamerRequiredPermissions,
|
||||||
onAllGranted = { inflateStreamer() },
|
onAllGranted = { onPermissionsGranted() },
|
||||||
onShowPermissionRationale = { permissions, onRequiredPermissionLastTime ->
|
onShowPermissionRationale = { permissions, onRequiredPermissionLastTime ->
|
||||||
// Explain why we need permissions
|
// Explain why we need permissions
|
||||||
showDialog(
|
showDialog(
|
||||||
@@ -53,83 +59,150 @@ class MainActivity : AppCompatActivity() {
|
|||||||
positiveButtonText = 0,
|
positiveButtonText = 0,
|
||||||
negativeButtonText = 0
|
negativeButtonText = 0
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// Reports and manages error with [OnErrorListener]
|
|
||||||
private val errorListener = object : OnErrorListener {
|
|
||||||
override fun onError(error: StreamPackError) {
|
|
||||||
toast("An error occurred: $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.
|
* The streamer is the central object of StreamPack.
|
||||||
* It is responsible for the capture audio and video and the streaming process.
|
* It is responsible for the capture audio and video and the streaming process.
|
||||||
*
|
*
|
||||||
* Use a [CameraRtmpLiveStreamer] For RTMP. Or a [CameraSrtLiveStreamer] for SRT.
|
* If you need only 1 output (live only or record only), use [SingleStreamer].
|
||||||
|
* If you need 2 outputs (live and record), use [DualStreamer].
|
||||||
*/
|
*/
|
||||||
private val streamer by lazy {
|
private val streamer by lazy {
|
||||||
CameraRtmpLiveStreamer(
|
// 1 output
|
||||||
this,
|
SingleStreamer(
|
||||||
initialOnErrorListener = errorListener,
|
this, withAudio = true, withVideo = true
|
||||||
initialOnConnectionListener = connectionListener
|
|
||||||
)
|
)
|
||||||
|
// 2 outputs: uncomment the line below
|
||||||
|
/*
|
||||||
|
DualStreamer(
|
||||||
|
this,
|
||||||
|
withAudio = true,
|
||||||
|
withVideo = true
|
||||||
|
)
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listen to lifecycle events. So we don't have to stop the streamer manually in `onPause` and release in `onDestroy
|
* 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) }
|
private val streamerLifeCycleObserver by lazy { StreamerActivityLifeCycleObserver(streamer) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen to device rotation.
|
||||||
|
*/
|
||||||
|
private val rotationRepository by lazy { RotationRepository.getInstance(applicationContext) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A LiveData to observe the connection state.
|
||||||
|
*/
|
||||||
|
private val isTryingConnectionLiveData = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
binding.liveButton.setOnCheckedChangeListener { _, isChecked ->
|
|
||||||
if (isChecked) {
|
bindProperties()
|
||||||
/**
|
}
|
||||||
* Dispatch from main thread is forced to avoid making network call on main thread
|
|
||||||
* with coroutines.
|
private fun bindProperties() {
|
||||||
*/
|
binding.liveButton.setOnCheckedChangeListener { view, isChecked ->
|
||||||
lifecycleScope.launch {
|
if (view.isPressed) {
|
||||||
try {
|
if (isChecked) {
|
||||||
/**
|
/**
|
||||||
* Always lock the device orientation during a live streaming to avoid
|
* Dispatch from main thread is forced to avoid making network call on main thread
|
||||||
* to recreate the Activity.
|
* with coroutines.
|
||||||
*/
|
*/
|
||||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
lifecycleScope.launch {
|
||||||
/**
|
try {
|
||||||
* For SRT, use srt://my.server.url:9998?streamid=myStreamId&passphrase=myPassphrase
|
isTryingConnectionLiveData.postValue(true)
|
||||||
*/
|
/**
|
||||||
streamer.startStream("rtmp://my.server.url:1234/app/streamKey")
|
* For SRT, use srt://my.server.url:9998?streamid=myStreamId&passphrase=myPassphrase
|
||||||
} catch (e: Exception) {
|
*/
|
||||||
binding.liveButton.isChecked = false
|
streamer.startStream("rtmp://my.server.url:1935/app/streamKey")
|
||||||
Log.e(TAG, "Failed to connect", e)
|
} catch (e: Exception) {
|
||||||
|
binding.liveButton.isChecked = false
|
||||||
|
Log.e(TAG, "Failed to connect", e)
|
||||||
|
toast("Connection failed: ${e.message}")
|
||||||
|
} finally {
|
||||||
|
isTryingConnectionLiveData.postValue(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
streamer.stopStream()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
streamer.stopStream()
|
|
||||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bindAndPrepareStreamer()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindAndPrepareStreamer() {
|
||||||
|
// Register the lifecycle observer
|
||||||
|
lifecycle.addObserver(streamerLifeCycleObserver)
|
||||||
|
|
||||||
|
// Configure the streamer
|
||||||
|
configureStreamer()
|
||||||
|
|
||||||
|
// Listen to rotation
|
||||||
|
lifecycleScope.launch {
|
||||||
|
rotationRepository.rotationFlow.collect {
|
||||||
|
streamer.setTargetRotation(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock and unlock orientation on isStreaming state.
|
||||||
|
lifecycleScope.launch {
|
||||||
|
streamer.isStreamingFlow.collect { isStreaming ->
|
||||||
|
if (isStreaming) {
|
||||||
|
lockOrientation()
|
||||||
|
} else {
|
||||||
|
unlockOrientation()
|
||||||
|
}
|
||||||
|
if (isStreaming) {
|
||||||
|
binding.liveButton.isChecked = true
|
||||||
|
} else if (isTryingConnectionLiveData.value == true) {
|
||||||
|
binding.liveButton.isChecked = true
|
||||||
|
} else {
|
||||||
|
binding.liveButton.isChecked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// General error handling
|
||||||
|
lifecycleScope.launch {
|
||||||
|
streamer.throwableFlow.filterNotNull().filter { !it.isClosedException }
|
||||||
|
.collect { throwable ->
|
||||||
|
Log.e(TAG, "Error: ${throwable.message}", throwable)
|
||||||
|
toast("Error: ${throwable.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection error handling
|
||||||
|
lifecycleScope.launch {
|
||||||
|
streamer.throwableFlow.filterNotNull().filter { it.isClosedException }
|
||||||
|
.collect { throwable ->
|
||||||
|
Log.e(TAG, "Connection lost: ${throwable.message}", throwable)
|
||||||
|
toast("Connection lost: ${throwable.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun lockOrientation() {
|
||||||
|
/**
|
||||||
|
* Lock orientation while stream is running to avoid stream interruption if
|
||||||
|
* user turns the device.
|
||||||
|
* For landscape only mode, set [requireActivity().requestedOrientation] to
|
||||||
|
* [ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE].
|
||||||
|
*/
|
||||||
|
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unlockOrientation() {
|
||||||
|
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
@@ -138,22 +211,32 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequiresPermission(allOf = [Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO])
|
@RequiresPermission(allOf = [Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO])
|
||||||
private fun inflateStreamer() {
|
private fun onPermissionsGranted() {
|
||||||
lifecycle.addObserver(streamerLifeCycleObserver) // Register the lifecycle observer
|
setAVSource()
|
||||||
/**
|
setStreamerView()
|
||||||
* Configure the streamer before calling view.streamer. Because it will start camera preview
|
}
|
||||||
* which required a configuration
|
|
||||||
*/
|
@RequiresPermission(allOf = [Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO])
|
||||||
configureStreamer()
|
private fun setAVSource() {
|
||||||
|
// Set audio and video sources.
|
||||||
|
lifecycleScope.launch {
|
||||||
|
streamer.setAudioSource(MicrophoneSourceFactory())
|
||||||
|
streamer.setCameraId(this@MainActivity.defaultCameraId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setStreamerView() {
|
||||||
binding.preview.streamer = streamer // Bind the streamer to the preview
|
binding.preview.streamer = streamer // Bind the streamer to the preview
|
||||||
|
lifecycleScope.launch {
|
||||||
|
binding.preview.startPreview()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
private fun configureStreamer() {
|
private fun configureStreamer() {
|
||||||
/**
|
/**
|
||||||
* To check the parameters supported by the device, you can check parameter against:
|
* To get the parameters supported by the device, the [SingleStreamer] have a
|
||||||
* - [CameraStreamerConfigurationHelper.flvHelper] for RTMP live streaming or
|
* [SingleStreamer.getInfo] method.
|
||||||
* - [CameraStreamerConfigurationHelper.tsHelper] for SRT communication.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,9 +263,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
sampleRate = 44100,
|
sampleRate = 44100,
|
||||||
channelConfig = AudioFormat.CHANNEL_IN_STEREO
|
channelConfig = AudioFormat.CHANNEL_IN_STEREO
|
||||||
)
|
)
|
||||||
streamer.configure(audioConfig, videoConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
streamer.setConfig(audioConfig, videoConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun toast(message: String) {
|
private fun toast(message: String) {
|
||||||
runOnUiThread { applicationContext.toast(message) }
|
runOnUiThread { applicationContext.toast(message) }
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package io.github.thibaultbee.streampack.app.data.rotation
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import io.github.thibaultbee.streampack.core.streamers.orientation.SensorRotationProvider
|
||||||
|
import io.github.thibaultbee.streampack.core.streamers.orientation.asFlowProvider
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A repository for orientation data.
|
||||||
|
*/
|
||||||
|
class RotationRepository(
|
||||||
|
context: Context,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* A flow of device rotation.
|
||||||
|
* `SensorRotationProvider` follows the orientation of the sensor, so it will change when the
|
||||||
|
* device is rotated.
|
||||||
|
* If the application orientation is locked, you should use `DisplayRotationProvider` instead.
|
||||||
|
*/
|
||||||
|
//private val rotationProvider = DisplayRotationProvider(context).asFlowProvider()
|
||||||
|
private val rotationProvider = SensorRotationProvider(context).asFlowProvider()
|
||||||
|
val rotationFlow: Flow<Int> = rotationProvider.rotationFlow
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Volatile
|
||||||
|
private var INSTANCE: RotationRepository? = null
|
||||||
|
|
||||||
|
fun getInstance(context: Context): RotationRepository {
|
||||||
|
return INSTANCE ?: synchronized(this) {
|
||||||
|
INSTANCE?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
RotationRepository(context).apply {
|
||||||
|
INSTANCE = this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".MainActivity">
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
<io.github.thibaultbee.streampack.views.PreviewView
|
<io.github.thibaultbee.streampack.ui.views.PreviewView
|
||||||
android:id="@+id/preview"
|
android:id="@+id/preview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext {
|
ext {
|
||||||
// Upgrade StreamPack version here
|
// Upgrade StreamPack version here
|
||||||
streampackVersion = '2.6.1'
|
streampackVersion = '3.0.0-RC'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user