From 87ff9b294f095b18f57e3c98396d8c6dcf96a6ef Mon Sep 17 00:00:00 2001 From: Mostafa Ashraf Date: Wed, 22 May 2024 15:43:09 +0300 Subject: [PATCH] DIOS-4818 Integrate Millicast v1.9.0 + fixing bugs --- .../rtsviewer/ui/streaming/StreamingScreen.kt | 4 - gradle/libs.versions.toml | 2 +- .../streaming/multiview/GridViewScreen.kt | 9 +- .../streaming/multiview/ListViewScreen.kt | 50 +++++------ .../multiview/MultiStreamingViewModel.kt | 2 +- .../multiview/SingleStreamingScreen.kt | 6 +- .../io/dolby/interactiveplayer/utils/Utils.kt | 2 + .../data/multistream/MultiStreamListener.kt | 12 +-- .../multistream/MultiStreamingRepository.kt | 85 +++++++++++++++---- .../domain/MultiStreamingData.kt | 8 +- 10 files changed, 114 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/io/dolby/rtsviewer/ui/streaming/StreamingScreen.kt b/app/src/main/java/io/dolby/rtsviewer/ui/streaming/StreamingScreen.kt index e3d2a96d..2615f0b2 100644 --- a/app/src/main/java/io/dolby/rtsviewer/ui/streaming/StreamingScreen.kt +++ b/app/src/main/java/io/dolby/rtsviewer/ui/streaming/StreamingScreen.kt @@ -61,10 +61,6 @@ fun StreamingScreen(viewModel: StreamingViewModel = hiltViewModel(), onBack: () isMain = true, view = view ) - }, - onRelease = { view -> - uiState.videoTrack?.disableSync(videoSink = view) - view.release() } ) SetupVolumeControlAudioStream() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 663c2ad7..30d20d7b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ androidx-test-ext-junit = "1.1.5" espresso-core = "3.5.1" appcompat = "1.6.1" material = "1.8.0" -millicast-sdk = "1.9.0-SNAPSHOT-2684-63f6cfd2" +millicast-sdk = "1.9.0" [libraries] androidx-appcompat = "androidx.appcompat:appcompat:1.6.1" diff --git a/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/GridViewScreen.kt b/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/GridViewScreen.kt index d26ed4d3..0e21c746 100644 --- a/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/GridViewScreen.kt +++ b/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/GridViewScreen.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView -import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.millicast.Media import com.millicast.subscribers.remote.RemoteVideoTrack import com.millicast.video.TextureViewRenderer @@ -44,7 +43,7 @@ fun GridViewScreen( onMainClick: (String?) -> Unit, onSettingsClick: () -> Unit ) { - val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val uiState by viewModel.uiState.collectAsState() val screenContentDescription = stringResource(id = R.string.streaming_screen_contentDescription) @@ -57,8 +56,8 @@ fun GridViewScreen( TopAppBar( title = uiState.streamName ?: screenContentDescription, onBack = { - onBack() viewModel.disconnect() + onBack() }, onAction = onSettingsClick ) @@ -170,9 +169,9 @@ private fun VideoView( // video.play(view, viewModel, videoQuality) }, onRelease = { - video.disableSync(it) + video.disableAsync() // viewModel.stopVideo(video) - it.release() + // it.release() } ) if (displayLabel) { diff --git a/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/ListViewScreen.kt b/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/ListViewScreen.kt index 9eef14a9..62d502c8 100644 --- a/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/ListViewScreen.kt +++ b/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/ListViewScreen.kt @@ -1,6 +1,7 @@ package io.dolby.interactiveplayer.streaming.multiview import android.content.res.Configuration +import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -24,10 +25,12 @@ import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription @@ -55,8 +58,7 @@ fun ListViewScreen( onMainClick: (String?) -> Unit, onSettingsClick: () -> Unit ) { - val uiState by viewModel.uiState.collectAsStateWithLifecycle() - + val uiState by viewModel.uiState.collectAsState() val screenContentDescription = stringResource(id = R.string.streaming_screen_contentDescription) val focusManager = LocalFocusManager.current @@ -69,8 +71,8 @@ fun ListViewScreen( TopAppBar( title = uiState.streamName ?: screenContentDescription, onBack = { - onBack() viewModel.disconnect() + onBack() }, onAction = onSettingsClick ) @@ -152,22 +154,14 @@ fun HorizontalEndListView( factory = { context -> val view = TextureViewRenderer(context) view.init(Media.eglBaseContext, null) -// view.setZOrderOnTop(true) -// view.setZOrderMediaOverlay(true) view }, update = { view -> view.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT) mainVideo?.enableAsync(videoSink = view) -// mainVideo?.play( -// view = view, -// viewModel = viewModel, -// videoQuality = uiState.connectOptions?.primaryVideoQuality -// ?: VideoQuality.AUTO -// ) }, onRelease = { view -> - mainVideo?.disableSync(videoSink = view) + mainVideo?.disableAsync() view.release() } ) @@ -187,6 +181,7 @@ fun HorizontalEndListView( } val otherTracks = uiState.videoTracks.filter { it.sourceId != mainVideo?.sourceId } + LazyColumn( modifier = Modifier .fillMaxHeight() @@ -241,23 +236,15 @@ fun VerticalTopListView( factory = { context -> val view = TextureViewRenderer(context) view.init(Media.eglBaseContext, null) -// view.setZOrderOnTop(true) -// view.setZOrderMediaOverlay(true) view }, update = { view -> view.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT) mainVideo?.enableAsync(videoSink = view) -// mainVideo?.play( -// view = view, -// viewModel = viewModel, -// videoQuality = uiState.connectOptions?.primaryVideoQuality -// ?: VideoQuality.AUTO -// ) }, onRelease = { view -> - mainVideo?.disableSync(view) - view.release() + mainVideo?.disableAsync() + // view.release() } ) if (displayLabel) { @@ -274,7 +261,9 @@ fun VerticalTopListView( } val otherTracks = uiState.videoTracks.filter { it.sourceId != mainVideo?.sourceId } + val lazyVerticalGridState = rememberLazyGridState() + LazyVerticalGrid( state = lazyVerticalGridState, columns = GridCells.Fixed(count = 2), @@ -318,22 +307,23 @@ fun VideoView( val updatedModifier = onClick?.let { modifier.clickable { onClick(video) } } ?: modifier + val context = LocalContext.current + val videoRenderer = remember(context) { + TextureViewRenderer(context).apply { + init(Media.eglBaseContext, null) + } + } Box { AndroidView( modifier = updatedModifier, - factory = { context -> - val view = TextureViewRenderer(context) - view.init(Media.eglBaseContext, null) - view - }, + factory = { videoRenderer }, update = { view -> view.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT) video.enableAsync(videoSink = view) -// video.play(view, viewModel, videoQuality) }, onRelease = { - video.disableSync(it) -// viewModel.stopVideo(video) + Log.d("VideoView", "disable sync for video id ${video.currentMid}") + video.disableAsync() it.release() } ) diff --git a/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/MultiStreamingViewModel.kt b/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/MultiStreamingViewModel.kt index 24bb5e0d..b089c828 100644 --- a/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/MultiStreamingViewModel.kt +++ b/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/MultiStreamingViewModel.kt @@ -164,7 +164,7 @@ class MultiStreamingViewModel @Inject constructor( } else { Error.NO_INTERNET_CONNECTION } - it.copy(error = error) + it.copy(error = error, videoTracks = emptyList()) } } diff --git a/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/SingleStreamingScreen.kt b/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/SingleStreamingScreen.kt index 077974da..65b6f0ef 100644 --- a/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/SingleStreamingScreen.kt +++ b/interactiveplayer/src/main/java/io/dolby/interactiveplayer/streaming/multiview/SingleStreamingScreen.kt @@ -45,7 +45,7 @@ fun SingleStreamingScreen( onBack: () -> Unit, onSettingsClick: (StreamingData?) -> Unit ) { - val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val uiState by viewModel.uiState.collectAsState() val screenContentDescription = stringResource(id = R.string.streaming_screen_contentDescription) val selectedItem = @@ -190,7 +190,7 @@ private fun VideoView( }, update = { view -> view.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT) - uiState.videoTracks[page].disableSync() + uiState.videoTracks[page].disableAsync() val isSelected = uiState.selectedVideoTrackId == uiState.videoTracks[page].sourceId @@ -201,7 +201,7 @@ private fun VideoView( ) }, onRelease = { view -> - uiState.videoTracks[page].disableSync(videoSink = view) + uiState.videoTracks[page].disableAsync() view.release() } ) diff --git a/interactiveplayer/src/main/java/io/dolby/interactiveplayer/utils/Utils.kt b/interactiveplayer/src/main/java/io/dolby/interactiveplayer/utils/Utils.kt index a2dabffe..2e49ec30 100644 --- a/interactiveplayer/src/main/java/io/dolby/interactiveplayer/utils/Utils.kt +++ b/interactiveplayer/src/main/java/io/dolby/interactiveplayer/utils/Utils.kt @@ -1,6 +1,8 @@ package io.dolby.interactiveplayer.utils import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp diff --git a/rtscomponentkit/src/main/java/io/dolby/rtscomponentkit/data/multistream/MultiStreamListener.kt b/rtscomponentkit/src/main/java/io/dolby/rtscomponentkit/data/multistream/MultiStreamListener.kt index 64416834..473b5729 100644 --- a/rtscomponentkit/src/main/java/io/dolby/rtscomponentkit/data/multistream/MultiStreamListener.kt +++ b/rtscomponentkit/src/main/java/io/dolby/rtscomponentkit/data/multistream/MultiStreamListener.kt @@ -111,11 +111,10 @@ class MultiStreamListener( fun connected(): Boolean = subscriber.isSubscribed fun disconnect() { - Log.d(TAG, "Cancelling coroutines...") + subscriber.disconnect() coroutineScope.coroutineContext.cancelChildren() coroutineScope.coroutineContext.cancel() Log.d(TAG, "Disconnecting subscriber...") - subscriber.release() } private fun onConnected() { @@ -123,6 +122,9 @@ class MultiStreamListener( TAG, "onConnected, this: $this, thread: ${Thread.currentThread().id}" ) + data.update { + it.copy(isConnected = true, isSubscribed = false) + } } private fun onDisconnected() { @@ -165,7 +167,7 @@ class MultiStreamListener( private fun onSubscribed() { Log.d(TAG, "onSubscribed") - data.update { data -> data.copy(isSubscribed = true, error = null) } + data.update { data -> data.copy(isSubscribed = true, isSubscribing = false, error = null) } } private fun onSubscribedError(p0: String?) { @@ -180,7 +182,7 @@ class MultiStreamListener( data.update { data -> data.copy( videoTracks = subscriber.currentState.tracks - .filter { it is RemoteVideoTrack && it.isActive } + .filter { it is RemoteVideoTrack } .map { it as RemoteVideoTrack } ) } @@ -191,7 +193,7 @@ class MultiStreamListener( data.update { data -> data.copy( audioTracks = subscriber.currentState.tracks - .filter { it is RemoteAudioTrack && it.isActive } + .filter { it is RemoteAudioTrack } .map { it as RemoteAudioTrack } ) } diff --git a/rtscomponentkit/src/main/java/io/dolby/rtscomponentkit/data/multistream/MultiStreamingRepository.kt b/rtscomponentkit/src/main/java/io/dolby/rtscomponentkit/data/multistream/MultiStreamingRepository.kt index 2caa88ce..38e9508a 100644 --- a/rtscomponentkit/src/main/java/io/dolby/rtscomponentkit/data/multistream/MultiStreamingRepository.kt +++ b/rtscomponentkit/src/main/java/io/dolby/rtscomponentkit/data/multistream/MultiStreamingRepository.kt @@ -10,10 +10,12 @@ import android.os.HandlerThread import android.provider.Settings import android.util.Log import com.millicast.Core +import com.millicast.Subscriber import com.millicast.clients.ConnectionOptions import com.millicast.subscribers.Credential import com.millicast.subscribers.Option import com.millicast.subscribers.remote.RemoteAudioTrack +import com.millicast.subscribers.state.SubscriberConnectionState import io.dolby.rtscomponentkit.data.multistream.MultiStreamListener.Companion.TAG import io.dolby.rtscomponentkit.data.multistream.prefs.AudioSelection import io.dolby.rtscomponentkit.data.multistream.prefs.MultiStreamPrefsStore @@ -49,6 +51,9 @@ class MultiStreamingRepository( private val _audioSelection = MutableStateFlow(AudioSelection.default) private var audioSelectionListenerJob: Job? = null + private var connectionStateJob: Job? = null + private var connectionJob: Job? = null + private var subscriptionJob: Job? = null private var volumeObserver: RemoteVolumeObserver? = null private val audioManager = context.getSystemService(AudioManager::class.java) as AudioManager @@ -77,7 +82,6 @@ class MultiStreamingRepository( } init { - listenForAudioSelection() handlerThread.start() } @@ -114,17 +118,35 @@ class MultiStreamingRepository( audioManager.isSpeakerphoneOn = true } + private fun listenForConnectionState(subscriber: Subscriber, option: Option) { + connectionStateJob?.cancel() + connectionStateJob = CoroutineScope(dispatcherProvider.main).launch { + subscriber.state.collect { + when { + it.connectionState == SubscriberConnectionState.Connected -> { + if (!_data.value.isSubscribed && !_data.value.isSubscribing) { + _data.update { + it.copy(isSubscribing = true) + } + subscribe(subscriber, option) + } + } + } + } + } + } private fun listenForAudioSelection() { audioSelectionListenerJob?.cancel() audioSelectionListenerJob = CoroutineScope(dispatcherProvider.main).launch { combine( - _data, + data, prefsStore.audioSelection(data.value.streamingData) ) { data, audioSelection -> Pair(data, audioSelection) }.collect { val data = it.first val audioSelection = it.second var audioSourceIdToSelect: RemoteAudioTrack? = null _audioSelection.update { audioSelection } + Log.d(TAG, "ListenForAudio Selection Audio audioTracks ${data.audioTracks}") when (audioSelection) { AudioSelection.MainSource -> { data.audioTracks.firstOrNull { it.sourceId == null } @@ -159,11 +181,14 @@ class MultiStreamingRepository( } } audioSourceIdToSelect?.let { audio -> - if (audioSourceIdToSelect?.sourceId != data.selectedAudioTrackId) { - audio.enable() + if ((audio.sourceId != data.selectedAudioTrackId)) { + Log.d(TAG, "Audio enable") + audio.disableAsync() + audio.enableAsync() + updateSelectedAudioTrackId(audio.sourceId) + adjustTrackVolume(context, audio) + addVolumeObserver(audio) } - adjustTrackVolume(context, audio) - addVolumeObserver(audio) } } } @@ -174,7 +199,6 @@ class MultiStreamingRepository( return } val subscriber = Core.createSubscriber() - listener = MultiStreamListener(_data, subscriber).apply { start() } @@ -213,25 +237,52 @@ class MultiStreamingRepository( Log.d(TAG, "Connecting ...") try { - subscriber.connect(ConnectionOptions(true)) - subscriber.subscribe(options = options) + connectionJob?.cancel() + connectionJob = CoroutineScope(dispatcherProvider.io).launch { + Log.d(TAG, "Connect") + subscriber.connect(ConnectionOptions(true)) + } } catch (e: Throwable) { e.printStackTrace() } _data.update { data -> data.copy(streamingData = streamingData) } + listenForConnectionState(subscriber, options) listenForAudioSelection() listenForAudioDevices() } + fun subscribe(subscriber: Subscriber, option: Option) { + subscriptionJob?.cancel() + subscriptionJob = CoroutineScope(dispatcherProvider.io).launch { + Log.d(TAG, "Start Subscribing") + subscriber.subscribe(option) + } + } + fun disconnect() { - val listener = listener - this.listener = null + Log.d(TAG, "Disconnect") + cancelAllJobs() listener?.disconnect() - _data.update { MultiStreamingData() } + this.listener = null + clearData() unregisterVolumeObserver() unregisterAudioDeviceListener() } + private fun cancelAllJobs() { + subscriptionJob?.cancel() + connectionJob?.cancel() + connectionStateJob?.cancel() + audioSelectionListenerJob?.cancel() + audioSelectionListenerJob = null + subscriptionJob = null + connectionJob = null + connectionStateJob = null + } + private fun clearData() { + _data.update { MultiStreamingData() } + } + private fun credential( credentials: Credential, streamingData: StreamingData, @@ -248,13 +299,17 @@ class MultiStreamingRepository( fun updateSelectedVideoTrackId(sourceId: String?) { _data.update { data -> -// data.videoTracks.forEach { -// it.videoTrack.removeVideoSink() -// } data.copy(selectedVideoTrackId = sourceId) } } + fun updateSelectedAudioTrackId(sourceId: String?) { + Log.d(TAG, "update SelectedAudio TrackId for sourceId $sourceId") + _data.update { data -> + data.copy(selectedAudioTrackId = sourceId) + } + } + private fun addVolumeObserver(audioTrack: RemoteAudioTrack) { unregisterVolumeObserver() val volumeObserver = RemoteVolumeObserver( diff --git a/rtscomponentkit/src/main/java/io/dolby/rtscomponentkit/domain/MultiStreamingData.kt b/rtscomponentkit/src/main/java/io/dolby/rtscomponentkit/domain/MultiStreamingData.kt index cfd58f89..fff89bcc 100644 --- a/rtscomponentkit/src/main/java/io/dolby/rtscomponentkit/domain/MultiStreamingData.kt +++ b/rtscomponentkit/src/main/java/io/dolby/rtscomponentkit/domain/MultiStreamingData.kt @@ -9,10 +9,12 @@ data class MultiStreamingData( val audioTracks: List = emptyList(), val allAudioTrackIds: List = arrayListOf(), val selectedVideoTrackId: String? = null, - val selectedAudioTrackId: String? = null, + val selectedAudioTrackId: String? = "-1", // In order to differentiate between main source id which is null and initial state val viewerCount: Int = 0, val error: String? = null, val isSubscribed: Boolean = false, + val isSubscribing: Boolean = false, + val isConnected: Boolean = false, val streamingData: StreamingData? = null, val statisticsData: MultiStreamStatisticsData? = null, val trackLayerData: Map> = emptyMap() @@ -22,7 +24,9 @@ data class MultiStreamingData( audioTracks = emptyList(), selectedAudioTrackId = null, error = error, - isSubscribed = false + isSubscribed = false, + isSubscribing = false, + isConnected = false ) companion object {