feat(app): add a view model for a better architecture
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
package io.github.thibaultbee.streampack.example
|
||||||
|
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application configuration.
|
||||||
|
*/
|
||||||
|
object ApplicationConstants {
|
||||||
|
/**
|
||||||
|
* Default application orientation.
|
||||||
|
* Also set in `AndroidManifest.xml` `android:screenOrientation` attribute.
|
||||||
|
*/
|
||||||
|
const val supportedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||||
|
}
|
||||||
@@ -3,40 +3,35 @@ package io.github.thibaultbee.streampack.example
|
|||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.media.AudioFormat
|
|
||||||
import android.media.MediaFormat
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Size
|
import androidx.activity.viewModels
|
||||||
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.app.data.rotation.RotationRepository
|
|
||||||
import io.github.thibaultbee.streampack.core.elements.sources.audio.audiorecord.MicrophoneSourceFactory
|
|
||||||
import io.github.thibaultbee.streampack.core.elements.sources.video.camera.extensions.defaultCameraId
|
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.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 kotlinx.coroutines.flow.filter
|
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
|
private val viewModel: MainViewModel by viewModels {
|
||||||
|
MainViewModelFactory(this.application)
|
||||||
|
}
|
||||||
|
|
||||||
private val streamerRequiredPermissions =
|
private val streamerRequiredPermissions =
|
||||||
listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
|
listOf(
|
||||||
|
Manifest.permission.CAMERA,
|
||||||
|
Manifest.permission.RECORD_AUDIO
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A minimalist permission manager
|
||||||
|
*/
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
private val permissionsManager = PermissionsManager(
|
private val permissionsManager = PermissionsManager(
|
||||||
this,
|
this,
|
||||||
@@ -61,42 +56,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* The streamer is the central object of StreamPack.
|
|
||||||
* It is responsible for the capture audio and video and the streaming process.
|
|
||||||
*
|
|
||||||
* 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 {
|
|
||||||
// 1 output
|
|
||||||
SingleStreamer(
|
|
||||||
this, withAudio = true, withVideo = true
|
|
||||||
)
|
|
||||||
// 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 { StreamerActivityLifeCycleObserver(streamer) }
|
private val streamerLifeCycleObserver by lazy { StreamerActivityLifeCycleObserver(viewModel.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)
|
||||||
@@ -116,47 +79,37 @@ class MainActivity : AppCompatActivity() {
|
|||||||
*/
|
*/
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
isTryingConnectionLiveData.postValue(true)
|
viewModel.startStream()
|
||||||
/**
|
|
||||||
* For SRT, use srt://my.server.url:9998?streamid=myStreamId&passphrase=myPassphrase
|
|
||||||
*/
|
|
||||||
streamer.startStream("rtmp://my.server.url:1935/app/streamKey")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
binding.liveButton.isChecked = false
|
binding.liveButton.isChecked = false
|
||||||
Log.e(TAG, "Failed to connect", e)
|
Log.e(TAG, "Failed to connect", e)
|
||||||
toast("Connection failed: ${e.message}")
|
toast("Connection failed: ${e.message}")
|
||||||
} finally {
|
|
||||||
isTryingConnectionLiveData.postValue(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
streamer.stopStream()
|
viewModel.stopStream()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bindAndPrepareStreamer()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bindAndPrepareStreamer() {
|
|
||||||
// Register the lifecycle observer
|
// Register the lifecycle observer
|
||||||
lifecycle.addObserver(streamerLifeCycleObserver)
|
lifecycle.addObserver(streamerLifeCycleObserver)
|
||||||
|
|
||||||
// Configure the streamer
|
// Configure the streamer
|
||||||
configureStreamer()
|
configureStreamer()
|
||||||
|
|
||||||
// Listen to rotation
|
// Bind events
|
||||||
lifecycleScope.launch {
|
viewModel.closedThrowableLiveData.observe(this) {
|
||||||
rotationRepository.rotationFlow.collect {
|
toast("Connection error: ${it.message}")
|
||||||
streamer.setTargetRotation(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock and unlock orientation on isStreaming state.
|
viewModel.throwableLiveData.observe(this) {
|
||||||
lifecycleScope.launch {
|
toast("Error: ${it.message}")
|
||||||
streamer.isStreamingFlow.collect { isStreaming ->
|
}
|
||||||
|
|
||||||
|
viewModel.isStreamingLiveData.observe(this) { isStreaming ->
|
||||||
if (isStreaming) {
|
if (isStreaming) {
|
||||||
lockOrientation()
|
lockOrientation()
|
||||||
} else {
|
} else {
|
||||||
@@ -164,29 +117,20 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
if (isStreaming) {
|
if (isStreaming) {
|
||||||
binding.liveButton.isChecked = true
|
binding.liveButton.isChecked = true
|
||||||
} else if (isTryingConnectionLiveData.value == true) {
|
} else if (viewModel.isTryingConnectionLiveData.value == true) {
|
||||||
binding.liveButton.isChecked = true
|
binding.liveButton.isChecked = true
|
||||||
} else {
|
} else {
|
||||||
binding.liveButton.isChecked = false
|
binding.liveButton.isChecked = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// General error handling
|
viewModel.isTryingConnectionLiveData.observe(this) { isWaitingForConnection ->
|
||||||
lifecycleScope.launch {
|
if (isWaitingForConnection) {
|
||||||
streamer.throwableFlow.filterNotNull().filter { !it.isClosedException }
|
binding.liveButton.isChecked = true
|
||||||
.collect { throwable ->
|
} else if (viewModel.isStreamingLiveData.value == true) {
|
||||||
Log.e(TAG, "Error: ${throwable.message}", throwable)
|
binding.liveButton.isChecked = true
|
||||||
toast("Error: ${throwable.message}")
|
} else {
|
||||||
}
|
binding.liveButton.isChecked = false
|
||||||
}
|
|
||||||
|
|
||||||
// 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}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,7 +146,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun unlockOrientation() {
|
private fun unlockOrientation() {
|
||||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
requestedOrientation = ApplicationConstants.supportedOrientation
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
@@ -220,54 +164,32 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private fun setAVSource() {
|
private fun setAVSource() {
|
||||||
// Set audio and video sources.
|
// Set audio and video sources.
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
streamer.setAudioSource(MicrophoneSourceFactory())
|
viewModel.setAudioSource()
|
||||||
streamer.setCameraId(this@MainActivity.defaultCameraId)
|
viewModel.setCameraId(this@MainActivity.defaultCameraId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setStreamerView() {
|
private fun setStreamerView() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
binding.preview.setVideoSourceProvider(streamer) // Bind the streamer to the preview
|
binding.preview.setVideoSourceProvider(viewModel.streamer) // Bind the streamer to the preview
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
private fun configureStreamer() {
|
private fun configureStreamer() {
|
||||||
/**
|
|
||||||
* To get the parameters supported by the device, the [SingleStreamer] have a
|
|
||||||
* [SingleStreamer.getInfo] method.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
)
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
streamer.setConfig(audioConfig, videoConfig)
|
viewModel.setAudioConfig()
|
||||||
|
viewModel.setVideoConfig()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
|
||||||
|
// Unregister the lifecycle observer
|
||||||
|
lifecycle.removeObserver(streamerLifeCycleObserver)
|
||||||
|
}
|
||||||
|
|
||||||
private fun toast(message: String) {
|
private fun toast(message: String) {
|
||||||
runOnUiThread { applicationContext.toast(message) }
|
runOnUiThread { applicationContext.toast(message) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package io.github.thibaultbee.streampack.example
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.media.AudioFormat
|
||||||
|
import android.media.MediaFormat
|
||||||
|
import android.util.Size
|
||||||
|
import androidx.annotation.RequiresPermission
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.asLiveData
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import io.github.thibaultbee.streampack.app.data.rotation.RotationRepository
|
||||||
|
import io.github.thibaultbee.streampack.core.elements.sources.audio.audiorecord.MicrophoneSourceFactory
|
||||||
|
import io.github.thibaultbee.streampack.core.interfaces.setCameraId
|
||||||
|
import io.github.thibaultbee.streampack.core.interfaces.startStream
|
||||||
|
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 kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class MainViewModel(
|
||||||
|
private val rotationRepository: RotationRepository,
|
||||||
|
val streamer: SingleStreamer
|
||||||
|
) : ViewModel() {
|
||||||
|
private val defaultDispatcher = Dispatchers.Default
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A LiveData to observe the stream state.
|
||||||
|
*/
|
||||||
|
val isStreamingLiveData: LiveData<Boolean>
|
||||||
|
get() = streamer.isStreamingFlow.asLiveData()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A LiveData to observe the pending connection state.
|
||||||
|
*/
|
||||||
|
private val _isTryingConnectionLiveData = MutableLiveData<Boolean>()
|
||||||
|
val isTryingConnectionLiveData: LiveData<Boolean> = _isTryingConnectionLiveData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A LiveData to observe async disconnection errors.
|
||||||
|
*/
|
||||||
|
val closedThrowableLiveData: LiveData<Throwable> =
|
||||||
|
streamer.throwableFlow.filterNotNull().filter { it.isClosedException }.asLiveData()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A LiveData to observe streamer errors.
|
||||||
|
*/
|
||||||
|
val throwableLiveData: LiveData<Throwable> =
|
||||||
|
streamer.throwableFlow.filterNotNull().filter { !it.isClosedException }.asLiveData()
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
/**
|
||||||
|
* Listens to device rotation.
|
||||||
|
*/
|
||||||
|
viewModelScope.launch(defaultDispatcher) {
|
||||||
|
rotationRepository.rotationFlow.collect {
|
||||||
|
streamer.setTargetRotation(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the stream.
|
||||||
|
*
|
||||||
|
* Replace with a valid URL.
|
||||||
|
*/
|
||||||
|
suspend fun startStream() {
|
||||||
|
_isTryingConnectionLiveData.postValue(true)
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* For SRT, use srt://my.server.url:9998?streamid=myStreamId&passphrase=myPassphrase
|
||||||
|
*/
|
||||||
|
streamer.startStream("rtmp://my.server.url:1935/app/streamKey")
|
||||||
|
} finally {
|
||||||
|
_isTryingConnectionLiveData.postValue(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the stream.
|
||||||
|
*/
|
||||||
|
suspend fun stopStream() {
|
||||||
|
streamer.stopStream()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the audio configuration.
|
||||||
|
*
|
||||||
|
* You can verify the device supported configuration with [SingleStreamer.getInfo].
|
||||||
|
*/
|
||||||
|
@RequiresPermission(Manifest.permission.RECORD_AUDIO)
|
||||||
|
suspend fun setAudioConfig() {
|
||||||
|
/**
|
||||||
|
* 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.setAudioConfig(audioConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the video configuration.
|
||||||
|
*
|
||||||
|
* You can verify the device supported configuration with [SingleStreamer.getInfo].
|
||||||
|
*/
|
||||||
|
suspend fun setVideoConfig() {
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
)
|
||||||
|
|
||||||
|
streamer.setVideoConfig(videoConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the microphone as the audio source.
|
||||||
|
*/
|
||||||
|
suspend fun setAudioSource() {
|
||||||
|
streamer.setAudioSource(MicrophoneSourceFactory())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the camera with the given id as the video source.
|
||||||
|
*
|
||||||
|
* @param cameraId The camera id.
|
||||||
|
*/
|
||||||
|
@RequiresPermission(Manifest.permission.CAMERA)
|
||||||
|
suspend fun setCameraId(cameraId: String) {
|
||||||
|
streamer.setCameraId(cameraId)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "MainViewModel"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package io.github.thibaultbee.streampack.example
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import io.github.thibaultbee.streampack.app.data.rotation.RotationRepository
|
||||||
|
import io.github.thibaultbee.streampack.core.streamers.dual.DualStreamer
|
||||||
|
import io.github.thibaultbee.streampack.core.streamers.single.SingleStreamer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for constructing [MainViewModelFactory] from the [Application].
|
||||||
|
*/
|
||||||
|
class MainViewModelFactory(private val application: Application) :
|
||||||
|
ViewModelProvider.Factory {
|
||||||
|
private val rotationRepository = RotationRepository.getInstance(application)
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
|
||||||
|
val streamer = createStreamer(application)
|
||||||
|
return MainViewModel(rotationRepository, streamer) as T
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Creates a streamer instance.
|
||||||
|
*
|
||||||
|
* The streamer is the central object of StreamPack.
|
||||||
|
* It is responsible for the capture audio and video and the streaming process.
|
||||||
|
*
|
||||||
|
* If you need only 1 output (live only or record only), use [SingleStreamer].
|
||||||
|
* If you need 2 outputs (live and record), use [DualStreamer].
|
||||||
|
*/
|
||||||
|
private fun createStreamer(application: Application): SingleStreamer {
|
||||||
|
// 1 output
|
||||||
|
return SingleStreamer(
|
||||||
|
application, withAudio = true, withVideo = true
|
||||||
|
)
|
||||||
|
// 2 outputs: uncomment the line below
|
||||||
|
/*
|
||||||
|
DualStreamer(
|
||||||
|
this,
|
||||||
|
withAudio = true,
|
||||||
|
withVideo = true
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user