diff --git a/Riot/Categories/MXRoom+VoiceBroadcast.swift b/Riot/Categories/MXRoom+VoiceBroadcast.swift new file mode 100644 index 000000000..6d74cfaaf --- /dev/null +++ b/Riot/Categories/MXRoom+VoiceBroadcast.swift @@ -0,0 +1,54 @@ +// +// Copyright 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. +// + +import MatrixSDK + +extension MXRoom { + + func stopUncompletedVoiceBroadcastIfNeeded() { + // Detection of a potential uncompleted VoiceBroadcast + // Check whether a VoiceBroadcast is in progress on the current session for this room whereas no VoiceBroadcast Service is available. + self.lastVoiceBroadcastStateEvent { event in + guard let event = event, + event.stateKey == self.mxSession.myUserId, + let eventDeviceId = event.content[VoiceBroadcastSettings.voiceBroadcastContentKeyDeviceId] as? String, + eventDeviceId == self.mxSession.myDeviceId, + let voiceBroadcastInfo = VoiceBroadcastInfo(fromJSON: event.content), + voiceBroadcastInfo.state != VoiceBroadcastInfoState.stopped.rawValue, + self.mxSession.voiceBroadcastService == nil else { + return + } + + self.mxSession.getOrCreateVoiceBroadcastService(for: self) { service in + guard let service = service else { + return + } + + service.stopVoiceBroadcast(lastChunkSequence: 0, + voiceBroadcastId: voiceBroadcastInfo.voiceBroadcastId ?? event.eventId) { response in + MXLog.debug("[MXRoom] stopUncompletedVoiceBroadcastIfNeeded stopVoiceBroadcast with response : \(response)") + self.mxSession.tearDownVoiceBroadcastService() + } + } + } + } + + func lastVoiceBroadcastStateEvent(completion: @escaping (MXEvent?) -> Void) { + self.state { roomState in + completion(roomState?.stateEvents(with: .custom(VoiceBroadcastSettings.voiceBroadcastInfoContentKeyType))?.last) + } + } +} diff --git a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift index 938361d00..069aff9a8 100644 --- a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift +++ b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift @@ -30,6 +30,7 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { public private(set) var query: String? public private(set) var space: MXSpace? private var fetchersCreated: Bool = false + private var uncompletedVoiceBroadcastCleaningDone: Bool = false // MARK: - Fetchers @@ -757,6 +758,7 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { forSection: section, totalCountsChanged: totalCountsChanged) } } else { + stopUncompletedVoiceBroadcastIfNeeded() multicastDelegate.invoke { $0.recentsListServiceDidChangeData?(self, totalCountsChanged: totalCountsChanged) } } @@ -784,6 +786,31 @@ extension RecentsListService: MXRoomListDataFetcherDelegate { } +// MARK: - VoiceBroadcast +extension RecentsListService { + + private func stopUncompletedVoiceBroadcastIfNeeded() { + guard uncompletedVoiceBroadcastCleaningDone == false, + let breadcrumbsFetcher = breadcrumbsRoomListDataFetcher else { + return + } + // We limit for the moment the uncompleted voice broadcast cleaning to the breadcrumbs rooms list + stopUncompletedVoiceBroadcastIfNeeded(for: breadcrumbsFetcher) + uncompletedVoiceBroadcastCleaningDone = true + } + + private func stopUncompletedVoiceBroadcastIfNeeded(for fetcher: MXRoomListDataFetcher) { + fetcher.data?.rooms.forEach({ roomSummary in + guard let roomSummary = roomSummary as? MXRoomSummary, + let room = roomSummary.room else { + return + } + + room.stopUncompletedVoiceBroadcastIfNeeded() + }) + } +} + // MARK: - FetcherTypes private struct FetcherTypes: OptionSet { diff --git a/Riot/Modules/Common/Recents/Service/Mock/MockRecentsListService.swift b/Riot/Modules/Common/Recents/Service/Mock/MockRecentsListService.swift index 77cecac6f..bc95f1f94 100644 --- a/Riot/Modules/Common/Recents/Service/Mock/MockRecentsListService.swift +++ b/Riot/Modules/Common/Recents/Service/Mock/MockRecentsListService.swift @@ -229,4 +229,7 @@ public class MockRecentsListService: NSObject, RecentsListServiceProtocol { multicastDelegate.invoke({ $0.recentsListServiceDidChangeData?(self, totalCountsChanged: true) }) } + public func stopUncompletedVoiceBroadcastIfNeeded(for listData: MatrixSDK.MXRoomListData?) { + // nothing here + } } diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 25170d023..b244e101e 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -1082,6 +1082,8 @@ static CGSize kThreadListBarButtonItemImageSize; [self setupUserSuggestionViewIfNeeded]; [self updateTopBanners]; + + [self stopUncompletedVoiceBroadcastIfNeeded]; } - (void)onRoomDataSourceReady diff --git a/Riot/Modules/Room/RoomViewController.swift b/Riot/Modules/Room/RoomViewController.swift index d1745dc6b..ad6a1866c 100644 --- a/Riot/Modules/Room/RoomViewController.swift +++ b/Riot/Modules/Room/RoomViewController.swift @@ -316,3 +316,10 @@ extension RoomViewController: ComposerLinkActionBridgePresenterDelegate { composerLinkActionBridgePresenter = nil } } + +// MARK: - VoiceBroadcast +extension RoomViewController { + @objc func stopUncompletedVoiceBroadcastIfNeeded() { + self.roomDataSource.room.stopUncompletedVoiceBroadcastIfNeeded() + } +} diff --git a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastAggregator.swift b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastAggregator.swift index a649a621d..f85945dce 100644 --- a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastAggregator.swift +++ b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastAggregator.swift @@ -163,15 +163,17 @@ public class VoiceBroadcastAggregator { } private func updateState() { - self.room.state { roomState in - guard let event = roomState?.stateEvents(with: .custom(VoiceBroadcastSettings.voiceBroadcastInfoContentKeyType))?.last, + // This update is useful only in case of a live broadcast (The aggregator considers the broadcast stopped by default) + // We will consider here only the most recent voice broadcast state event + self.room.lastVoiceBroadcastStateEvent { event in + guard let event = event, event.stateKey == self.voiceBroadcastSenderId, let voiceBroadcastInfo = VoiceBroadcastInfo(fromJSON: event.content), (event.eventId == self.voiceBroadcastStartEventId || voiceBroadcastInfo.voiceBroadcastId == self.voiceBroadcastStartEventId), let state = VoiceBroadcastInfoState(rawValue: voiceBroadcastInfo.state) else { return } - + self.delegate?.voiceBroadcastAggregator(self, didReceiveState: state) } } diff --git a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastService.swift b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastService.swift index b386f0d4d..8c707eab2 100644 --- a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastService.swift +++ b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastService.swift @@ -78,8 +78,14 @@ public class VoiceBroadcastService: NSObject { /// stop a voice broadcast info. /// - Parameters: /// - lastChunkSequence: The last sent chunk number. + /// - voiceBroadcastId: The VoiceBroadcast identifier to stop. Use it only to force stop a specific VoiceBroadcast. /// - completion: A closure called when the operation completes. Provides the event id of the event generated on the home server on success. - func stopVoiceBroadcast(lastChunkSequence: Int, completion: @escaping (MXResponse) -> Void) { + func stopVoiceBroadcast(lastChunkSequence: Int, + voiceBroadcastId: String? = nil, + completion: @escaping (MXResponse) -> Void) { + if let voiceBroadcastId = voiceBroadcastId { + self.voiceBroadcastId = voiceBroadcastId + } sendVoiceBroadcastInfo(lastChunkSequence: lastChunkSequence, state: VoiceBroadcastInfoState.stopped, completion: completion) } @@ -132,7 +138,7 @@ public class VoiceBroadcastService: NSObject { case .resumed: return [.paused, .stopped] case .stopped: - return [.started] + return [.started, .stopped] } } diff --git a/changelog.d/pr-7188.change b/changelog.d/pr-7188.change new file mode 100644 index 000000000..bd6f7056d --- /dev/null +++ b/changelog.d/pr-7188.change @@ -0,0 +1 @@ +Labs: VoiceBroadcast: Handle potential crash whereas a voice broadcast is in progress