Skip to content

Commit 89c86ae

Browse files
committed
fix(demos): camera: fix creation of new streamer when enabling/disabling audio
1 parent 548f3fb commit 89c86ae

File tree

3 files changed

+60
-31
lines changed

3 files changed

+60
-31
lines changed

demos/camera/src/main/java/io/github/thibaultbee/streampack/app/ui/main/PreviewFragment.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import kotlinx.coroutines.launch
4343

4444
class PreviewFragment : Fragment(R.layout.main_fragment) {
4545
private lateinit var binding: MainFragmentBinding
46+
private var previousStreamerLifecycleObserver: StreamerViewModelLifeCycleObserver? = null
4647

4748
private val previewViewModel: PreviewViewModel by viewModels {
4849
PreviewViewModelFactory(requireActivity().application)
@@ -107,8 +108,14 @@ class PreviewFragment : Fragment(R.layout.main_fragment) {
107108

108109
previewViewModel.streamerLiveData.observe(viewLifecycleOwner) { streamer ->
109110
if (streamer is IStreamer) {
110-
// TODO: Remove this observer when streamer is released
111-
lifecycle.addObserver(StreamerViewModelLifeCycleObserver(streamer))
111+
if (previousStreamerLifecycleObserver != null) {
112+
lifecycle.removeObserver(previousStreamerLifecycleObserver!!)
113+
}
114+
val newObserver = StreamerViewModelLifeCycleObserver(streamer).apply {
115+
previousStreamerLifecycleObserver = this
116+
}
117+
lifecycle.addObserver(newObserver)
118+
112119
} else {
113120
Log.e(TAG, "Streamer is not a ICoroutineStreamer")
114121
}

demos/camera/src/main/java/io/github/thibaultbee/streampack/app/ui/main/PreviewViewModel.kt

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import kotlinx.coroutines.flow.filterNotNull
6565
import kotlinx.coroutines.flow.first
6666
import kotlinx.coroutines.flow.map
6767
import kotlinx.coroutines.launch
68+
import kotlinx.coroutines.runBlocking
6869
import kotlinx.coroutines.sync.Mutex
6970
import kotlinx.coroutines.sync.withLock
7071

@@ -74,7 +75,12 @@ class PreviewViewModel(private val application: Application) : ObservableViewMod
7475

7576
private val buildStreamerUseCase = BuildStreamerUseCase(application, storageRepository)
7677

77-
private val streamerFlow = MutableStateFlow(buildStreamerUseCase())
78+
private val streamerFlow =
79+
MutableStateFlow(
80+
SingleStreamer(
81+
application,
82+
runBlocking { storageRepository.isAudioEnableFlow.first() }) // TODO avoid runBlocking
83+
)
7884
private val streamer: SingleStreamer
7985
get() = streamerFlow.value
8086
val streamerLiveData = streamerFlow.asLiveData()
@@ -119,8 +125,9 @@ class PreviewViewModel(private val application: Application) : ObservableViewMod
119125
val endpointErrorLiveData: LiveData<String> = _endpointErrorLiveData
120126

121127
// Streamer states
128+
private val _isStreamingFlow = MutableStateFlow(false)
122129
val isStreamingLiveData: LiveData<Boolean>
123-
get() = streamer.isStreamingFlow.asLiveData()
130+
get() = _isStreamingFlow.asLiveData()
124131
private val _isTryingConnectionLiveData = MutableLiveData<Boolean>()
125132
val isTryingConnectionLiveData: LiveData<Boolean> = _isTryingConnectionLiveData
126133

@@ -148,6 +155,7 @@ class PreviewViewModel(private val application: Application) : ObservableViewMod
148155
Log.i(TAG, "Video source is disabled")
149156
}
150157

158+
// TODO: cancel jobs linked to previous streamer
151159
viewModelScope.launch {
152160
streamer.videoInput?.sourceFlow?.collect {
153161
notifySourceChanged()
@@ -174,8 +182,9 @@ class PreviewViewModel(private val application: Application) : ObservableViewMod
174182
}
175183
viewModelScope.launch {
176184
streamer.isStreamingFlow
177-
.collect {
178-
Log.i(TAG, "Streamer is streaming: $it")
185+
.collect { isStreaming ->
186+
_isStreamingFlow.emit(isStreaming)
187+
Log.i(TAG, "Streamer is streaming: $isStreaming")
179188
}
180189
}
181190
}
@@ -190,34 +199,30 @@ class PreviewViewModel(private val application: Application) : ObservableViewMod
190199
viewModelScope.launch {
191200
storageRepository.isAudioEnableFlow.combine(storageRepository.isVideoEnableFlow) { isAudioEnable, isVideoEnable ->
192201
Pair(isAudioEnable, isVideoEnable)
193-
}.drop(1).collect { (_, _) ->
194-
val previousStreamer = streamer
195-
streamerFlow.emit(buildStreamerUseCase(previousStreamer))
196-
if (previousStreamer != streamer) {
197-
previousStreamer.release()
198-
}
202+
}.drop(1).collect { (isAudioEnable, _) ->
203+
streamerFlow.emit(buildStreamerUseCase(streamer, isAudioEnable))
199204
}
200205
}
201206
viewModelScope.launch {
202-
storageRepository.audioConfigFlow
207+
storageRepository.audioConfigFlow.filterNotNull()
203208
.collect { config ->
209+
if (!streamer.withAudio) {
210+
Log.i(TAG, "Audio is disabled. Skip setting audio config")
211+
return@collect
212+
}
204213
if (ActivityCompat.checkSelfPermission(
205214
application,
206215
Manifest.permission.RECORD_AUDIO
207216
) == PackageManager.PERMISSION_GRANTED
208217
) {
209-
config?.let {
210-
streamer.setAudioConfig(it)
211-
} ?: Log.i(TAG, "Audio is disabled")
218+
streamer.setAudioConfig(config)
212219
}
213220
}
214221
}
215222
viewModelScope.launch {
216-
storageRepository.videoConfigFlow
223+
storageRepository.videoConfigFlow.filterNotNull()
217224
.collect { config ->
218-
config?.let {
219-
streamer.setVideoConfig(it)
220-
} ?: Log.i(TAG, "Video is disabled")
225+
streamer.setVideoConfig(config)
221226
}
222227
}
223228
}

demos/camera/src/main/java/io/github/thibaultbee/streampack/app/ui/main/usecases/BuildStreamerUseCase.kt

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package io.github.thibaultbee.streampack.app.ui.main.usecases
22

3+
import android.Manifest
34
import android.content.Context
5+
import android.content.pm.PackageManager
6+
import androidx.core.app.ActivityCompat
47
import io.github.thibaultbee.streampack.app.data.storage.DataStoreRepository
58
import io.github.thibaultbee.streampack.core.streamers.single.SingleStreamer
6-
import kotlinx.coroutines.flow.first
7-
import kotlinx.coroutines.runBlocking
89

910
class BuildStreamerUseCase(
1011
private val context: Context,
@@ -15,17 +16,33 @@ class BuildStreamerUseCase(
1516
*
1617
* Only create a new streamer if the previous one is not the same type.
1718
*
19+
* It releases the previous streamer if a new one is created.
20+
*
1821
* @param previousStreamer Previous streamer to check if we need to create a new one.
1922
*/
20-
operator fun invoke(previousStreamer: SingleStreamer? = null): SingleStreamer {
21-
val isAudioEnable = runBlocking {
22-
dataStoreRepository.isAudioEnableFlow.first()
23-
}
24-
25-
if (previousStreamer == null) {
26-
return SingleStreamer(context, isAudioEnable)
27-
} else if ((previousStreamer.audioInput?.sourceFlow?.value == null) != !isAudioEnable) {
28-
return SingleStreamer(context, isAudioEnable)
23+
suspend operator fun invoke(
24+
previousStreamer: SingleStreamer,
25+
isAudioEnable: Boolean
26+
): SingleStreamer {
27+
if (previousStreamer.withAudio != isAudioEnable) {
28+
return SingleStreamer(context, isAudioEnable).apply {
29+
// Get previous streamer config if any
30+
val audioConfig = previousStreamer.audioConfigFlow.value
31+
val videoConfig = previousStreamer.videoConfigFlow.value
32+
if ((audioConfig != null && isAudioEnable)) {
33+
if (ActivityCompat.checkSelfPermission(
34+
context,
35+
Manifest.permission.RECORD_AUDIO
36+
) == PackageManager.PERMISSION_GRANTED
37+
) {
38+
setAudioConfig(audioConfig)
39+
}
40+
}
41+
if (videoConfig != null) {
42+
setVideoConfig(videoConfig)
43+
}
44+
previousStreamer.release()
45+
}
2946
}
3047
return previousStreamer
3148
}

0 commit comments

Comments
 (0)