diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 61a8e5b79e..1e79dc5844 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -42,6 +42,8 @@ import im.vector.app.features.raw.wellknown.isSecureBackupRequired import im.vector.app.features.raw.wellknown.withElementWellKnown import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.lib.core.utils.compat.getParcelableExtraCompat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -60,12 +62,14 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.pushrules.RuleIds import org.matrix.android.sdk.api.session.room.model.Membership @@ -92,6 +96,7 @@ class HomeActivityViewModel @AssistedInject constructor( private val analyticsConfig: AnalyticsConfig, private val releaseNotesPreferencesStore: ReleaseNotesPreferencesStore, private val vectorFeatures: VectorFeatures, + private val voiceBroadcastHelper: VoiceBroadcastHelper, ) : VectorViewModel(initialState) { @AssistedFactory @@ -123,6 +128,7 @@ class HomeActivityViewModel @AssistedInject constructor( observeReleaseNotes() observeLocalNotificationsSilenced() initThreadsMigration() + stopOngoingVoiceBroadcast() } private fun observeReleaseNotes() = withState { state -> @@ -490,6 +496,30 @@ class HomeActivityViewModel @AssistedInject constructor( } } + /** + * Stop ongoing voice broadcast if any. + */ + private fun stopOngoingVoiceBroadcast() { + val session = activeSessionHolder.getSafeActiveSession() ?: return + + // FIXME Iterate only on recent rooms for the moment, improve this + val recentRooms = session.roomService().getBreadcrumbs(roomSummaryQueryParams { + displayName = QueryStringValue.NoCondition + memberships = listOf(Membership.JOIN) + }).mapNotNull { session.getRoom(it.roomId) } + + recentRooms + .forEach { room -> + val ongoingVoiceBroadcasts = voiceBroadcastHelper.getOngoingVoiceBroadcasts(room.roomId) + val myOngoingVoiceBroadcastId = ongoingVoiceBroadcasts.find { it.root.stateKey == session.myUserId }?.reference?.eventId + val initialEvent = myOngoingVoiceBroadcastId?.let { room.timelineService().getTimelineEvent(it)?.root?.asVoiceBroadcastEvent() } + if (myOngoingVoiceBroadcastId != null && initialEvent?.content?.deviceId == session.sessionParams.deviceId) { + viewModelScope.launch { voiceBroadcastHelper.stopVoiceBroadcast(room.roomId) } + return // No need to iterate more as we should not have more than one recording VB + } + } + } + override fun handle(action: HomeActivityViewActions) { when (action) { HomeActivityViewActions.PushPromptHasBeenReviewed -> { diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt index 58e7de7f32..ee9034661c 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt @@ -16,6 +16,7 @@ package im.vector.app.features.voicebroadcast +import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase import im.vector.app.features.voicebroadcast.usecase.PauseVoiceBroadcastUseCase import im.vector.app.features.voicebroadcast.usecase.ResumeVoiceBroadcastUseCase import im.vector.app.features.voicebroadcast.usecase.StartVoiceBroadcastUseCase @@ -30,6 +31,7 @@ class VoiceBroadcastHelper @Inject constructor( private val pauseVoiceBroadcastUseCase: PauseVoiceBroadcastUseCase, private val resumeVoiceBroadcastUseCase: ResumeVoiceBroadcastUseCase, private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase, + private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase, private val voiceBroadcastPlayer: VoiceBroadcastPlayer, ) { suspend fun startVoiceBroadcast(roomId: String) = startVoiceBroadcastUseCase.execute(roomId) @@ -45,4 +47,6 @@ class VoiceBroadcastHelper @Inject constructor( fun pausePlayback() = voiceBroadcastPlayer.pause() fun stopPlayback() = voiceBroadcastPlayer.stop() + + fun getOngoingVoiceBroadcasts(roomId: String) = getOngoingVoiceBroadcastsUseCase.execute(roomId) } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetLastVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetLastVoiceBroadcastUseCase.kt new file mode 100644 index 0000000000..db2c625161 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetLastVoiceBroadcastUseCase.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.usecase + +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import timber.log.Timber +import javax.inject.Inject + +class GetOngoingVoiceBroadcastsUseCase @Inject constructor( + private val session: Session, +) { + + fun execute(roomId: String): List { + val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") + + Timber.d("## GetLastVoiceBroadcastUseCase: get last voice broadcast in $roomId") + + return room.stateService().getStateEvents( + setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO), + QueryStringValue.IsNotEmpty + ) + .mapNotNull { it.asVoiceBroadcastEvent() } + .filter { it.content?.voiceBroadcastState != null && it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } + } +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 7934d18e36..2b7ca7b9f1 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -25,9 +25,7 @@ import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState -import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.lib.multipicker.utils.toMultiPickerAudioType -import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toContent @@ -43,6 +41,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, private val context: Context, private val buildMeta: BuildMeta, + private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -50,12 +49,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( Timber.d("## StartVoiceBroadcastUseCase: Start voice broadcast requested") - val onGoingVoiceBroadcastEvents = room.stateService().getStateEvents( - setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO), - QueryStringValue.IsNotEmpty - ) - .mapNotNull { it.asVoiceBroadcastEvent() } - .filter { it.content?.voiceBroadcastState != null && it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } + val onGoingVoiceBroadcastEvents = getOngoingVoiceBroadcastsUseCase.execute(roomId) if (onGoingVoiceBroadcastEvents.isEmpty()) { startVoiceBroadcast(room)