Allow to record a voice message (#1926)

This commit is contained in:
Nicolas Mauri
2023-10-23 17:47:36 +02:00
committed by GitHub
parent 65b7c1df20
commit 2f57fbc77d
62 changed files with 1805 additions and 342 deletions
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:050b2abdd29e0dea7b8aa3db65eb34491334675de3bffe8695d2b0b6cce51c15
size 244042
@@ -47,7 +47,7 @@ class AudioPlayerStateTests: XCTestCase {
override func setUp() async throws {
audioPlayerActionsSubject = .init()
audioPlayerSeekCallsSubject = .init()
audioPlayerState = AudioPlayerState(duration: 10.0)
audioPlayerState = AudioPlayerState(id: .timelineItemIdentifier(.random), duration: 10.0)
audioPlayerMock = buildAudioPlayerMock()
}
@@ -31,8 +31,10 @@ class AudioRecorderStateTests: XCTestCase {
private func buildAudioRecorderMock() -> AudioRecorderMock {
let audioRecorderMock = AudioRecorderMock()
audioRecorderMock.isRecording = false
audioRecorderMock.underlyingActions = audioRecorderActions
audioRecorderMock.currentTime = 0.0
audioRecorderMock.averagePowerForChannelNumberReturnValue = 0
return audioRecorderMock
}
@@ -44,14 +46,13 @@ class AudioRecorderStateTests: XCTestCase {
func testAttach() async throws {
audioRecorderState.attachAudioRecorder(audioRecorderMock)
XCTAssertEqual(audioRecorderState.recordingState, .stopped)
}
func testDetach() async throws {
audioRecorderState.attachAudioRecorder(audioRecorderMock)
audioRecorderState.detachAudioRecorder()
audioRecorderMock.isRecording = true
await audioRecorderState.detachAudioRecorder()
XCTAssert(audioRecorderMock.stopRecordingCalled)
XCTAssertEqual(audioRecorderState.recordingState, .stopped)
}
@@ -0,0 +1,125 @@
//
// Copyright 2023 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 Combine
@testable import ElementX
import Foundation
import XCTest
class MediaPlayerProviderTests: XCTestCase {
private var mediaPlayerProvider: MediaPlayerProvider!
private let oggMimeType = "audio/ogg"
private let someURL = URL("/some/url")
private let someOtherURL = URL("/some/other/url")
override func setUp() async throws {
mediaPlayerProvider = MediaPlayerProvider()
}
func testPlayerForWrongMediaType() async throws {
let mediaSourceWithoutMimeType = MediaSourceProxy(url: someURL, mimeType: nil)
switch mediaPlayerProvider.player(for: mediaSourceWithoutMimeType) {
case .failure(.unsupportedMediaType):
// Ok
break
default:
XCTFail("An error is expected")
}
let mediaSourceVideo = MediaSourceProxy(url: someURL, mimeType: "video/mp4")
switch mediaPlayerProvider.player(for: mediaSourceWithoutMimeType) {
case .failure(.unsupportedMediaType):
// Ok
break
default:
XCTFail("An error is expected")
}
}
func testPlayerFor() async throws {
let mediaSource = MediaSourceProxy(url: someURL, mimeType: oggMimeType)
guard case .success(let playerA) = mediaPlayerProvider.player(for: mediaSource) else {
XCTFail("A valid player is expected")
return
}
// calling it again with another mediasource must returns the same player
let otherMediaSource = MediaSourceProxy(url: someOtherURL, mimeType: oggMimeType)
guard case .success(let playerB) = mediaPlayerProvider.player(for: otherMediaSource) else {
XCTFail("A valid player is expected")
return
}
XCTAssert(playerA === playerB)
}
func testPlayerStates() async throws {
let audioPlayerStateId = AudioPlayerStateIdentifier.timelineItemIdentifier(.random)
// By default, there should be no player state
XCTAssertNil(mediaPlayerProvider.playerState(for: audioPlayerStateId))
let audioPlayerState = await AudioPlayerState(id: audioPlayerStateId, duration: 10.0)
await mediaPlayerProvider.register(audioPlayerState: audioPlayerState)
XCTAssertEqual(audioPlayerState, mediaPlayerProvider.playerState(for: audioPlayerStateId))
await mediaPlayerProvider.unregister(audioPlayerState: audioPlayerState)
XCTAssertNil(mediaPlayerProvider.playerState(for: audioPlayerStateId))
}
func testDetachAllStates() async throws {
let audioPlayer = AudioPlayerMock()
audioPlayer.actions = PassthroughSubject<AudioPlayerAction, Never>().eraseToAnyPublisher()
let audioPlayerStates = await Array(repeating: AudioPlayerState(id: .timelineItemIdentifier(.random), duration: 0), count: 10)
for audioPlayerState in audioPlayerStates {
await mediaPlayerProvider.register(audioPlayerState: audioPlayerState)
await audioPlayerState.attachAudioPlayer(audioPlayer)
let isAttached = await audioPlayerState.isAttached
XCTAssertTrue(isAttached)
}
await mediaPlayerProvider.detachAllStates(except: nil)
for audioPlayerState in audioPlayerStates {
let isAttached = await audioPlayerState.isAttached
XCTAssertFalse(isAttached)
}
}
func testDetachAllStatesWithException() async throws {
let audioPlayer = AudioPlayerMock()
audioPlayer.actions = PassthroughSubject<AudioPlayerAction, Never>().eraseToAnyPublisher()
let audioPlayerStates = await Array(repeating: AudioPlayerState(id: .timelineItemIdentifier(.random), duration: 0), count: 10)
for audioPlayerState in audioPlayerStates {
await mediaPlayerProvider.register(audioPlayerState: audioPlayerState)
await audioPlayerState.attachAudioPlayer(audioPlayer)
let isAttached = await audioPlayerState.isAttached
XCTAssertTrue(isAttached)
}
let exception = audioPlayerStates[1]
await mediaPlayerProvider.detachAllStates(except: exception)
for audioPlayerState in audioPlayerStates {
let isAttached = await audioPlayerState.isAttached
if audioPlayerState == exception {
XCTAssertTrue(isAttached)
} else {
XCTAssertFalse(isAttached)
}
}
}
}
+3
View File
@@ -28,6 +28,7 @@ class PillContextTests: XCTestCase {
proxyMock.members = subject.asCurrentValuePublisher()
let mock = RoomScreenViewModel(timelineController: MockRoomTimelineController(),
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: proxyMock,
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
@@ -59,6 +60,7 @@ class PillContextTests: XCTestCase {
proxyMock.members = subject.asCurrentValuePublisher()
let mock = RoomScreenViewModel(timelineController: MockRoomTimelineController(),
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: proxyMock,
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
@@ -77,6 +79,7 @@ class PillContextTests: XCTestCase {
mockController.roomProxy = proxyMock
let mock = RoomScreenViewModel(timelineController: mockController,
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: proxyMock,
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
@@ -51,6 +51,7 @@ class RoomScreenViewModelTests: XCTestCase {
timelineController.timelineItems = items
let viewModel = RoomScreenViewModel(timelineController: timelineController,
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: RoomProxyMock(with: .init(displayName: "")),
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
@@ -84,6 +85,7 @@ class RoomScreenViewModelTests: XCTestCase {
timelineController.timelineItems = items
let viewModel = RoomScreenViewModel(timelineController: timelineController,
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: RoomProxyMock(with: .init(displayName: "")),
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
@@ -115,6 +117,7 @@ class RoomScreenViewModelTests: XCTestCase {
timelineController.timelineItems = items
let viewModel = RoomScreenViewModel(timelineController: timelineController,
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: RoomProxyMock(with: .init(displayName: "")),
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
@@ -143,6 +146,7 @@ class RoomScreenViewModelTests: XCTestCase {
timelineController.timelineItems = items
let viewModel = RoomScreenViewModel(timelineController: timelineController,
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: RoomProxyMock(with: .init(displayName: "")),
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
@@ -171,6 +175,7 @@ class RoomScreenViewModelTests: XCTestCase {
timelineController.timelineItems = items
let viewModel = RoomScreenViewModel(timelineController: timelineController,
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: RoomProxyMock(with: .init(displayName: "")),
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
@@ -195,6 +200,7 @@ class RoomScreenViewModelTests: XCTestCase {
let viewModel = RoomScreenViewModel(timelineController: timelineController,
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: roomProxyMock,
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
@@ -233,6 +239,7 @@ class RoomScreenViewModelTests: XCTestCase {
let viewModel = RoomScreenViewModel(timelineController: timelineController,
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: roomProxyMock,
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
@@ -272,6 +279,7 @@ class RoomScreenViewModelTests: XCTestCase {
let viewModel = RoomScreenViewModel(timelineController: timelineController,
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: roomProxyMock,
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
@@ -303,6 +311,7 @@ class RoomScreenViewModelTests: XCTestCase {
let viewModel = RoomScreenViewModel(timelineController: timelineController,
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: roomProxyMock,
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
@@ -322,6 +331,7 @@ class RoomScreenViewModelTests: XCTestCase {
let viewModel = RoomScreenViewModel(timelineController: timelineController,
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: roomProxyMock,
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
@@ -340,6 +350,7 @@ class RoomScreenViewModelTests: XCTestCase {
let viewModel = RoomScreenViewModel(timelineController: timelineController,
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: roomProxyMock,
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
@@ -359,6 +370,7 @@ class RoomScreenViewModelTests: XCTestCase {
let viewModel = RoomScreenViewModel(timelineController: timelineController,
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: roomProxyMock,
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
@@ -495,6 +507,7 @@ class RoomScreenViewModelTests: XCTestCase {
let viewModel = RoomScreenViewModel(timelineController: timelineController,
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
roomProxy: roomProxy,
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
+16 -12
View File
@@ -56,7 +56,10 @@ class VoiceMessageCacheTests: XCTestCase {
// If the file is present in the cache, its URL must be returned
let temporaryFileURL = try createTemporaryFile(named: testFilename, withExtension: mpeg4aacFileExtension)
let cachedURL = try voiceMessageCache.cache(mediaSource: mediaSource, using: temporaryFileURL, move: true)
guard case .success(let cachedURL) = voiceMessageCache.cache(mediaSource: mediaSource, using: temporaryFileURL, move: true) else {
XCTFail("A success is expected")
return
}
XCTAssertEqual(cachedURL, voiceMessageCache.fileURL(for: mediaSource))
}
@@ -64,22 +67,20 @@ class VoiceMessageCacheTests: XCTestCase {
func testCacheInvalidFileExtension() async throws {
// An error should be raised if the file extension is not "m4a"
let mpegFileURL = try createTemporaryFile(named: testFilename, withExtension: "mpg")
do {
_ = try voiceMessageCache.cache(mediaSource: mediaSource, using: mpegFileURL, move: true)
guard case .failure(let error) = voiceMessageCache.cache(mediaSource: mediaSource, using: mpegFileURL, move: true) else {
XCTFail("An error is expected")
} catch {
switch error as? VoiceMessageCacheError {
case .invalidFileExtension:
break
default:
XCTFail("A VoiceMessageCacheError.invalidFileExtension is expected")
}
return
}
XCTAssertEqual(error, .invalidFileExtension)
}
func testCacheCopy() async throws {
let fileURL = try createTemporaryFile(named: testFilename, withExtension: mpeg4aacFileExtension)
let cacheURL = try voiceMessageCache.cache(mediaSource: mediaSource, using: fileURL, move: false)
guard case .success(let cacheURL) = voiceMessageCache.cache(mediaSource: mediaSource, using: fileURL, move: false) else {
XCTFail("A success is expected")
return
}
// The source file must remain in its original location
XCTAssertTrue(fileManager.fileExists(atPath: fileURL.path()))
@@ -89,7 +90,10 @@ class VoiceMessageCacheTests: XCTestCase {
func testCacheMove() async throws {
let fileURL = try createTemporaryFile(named: testFilename, withExtension: mpeg4aacFileExtension)
let cacheURL = try voiceMessageCache.cache(mediaSource: mediaSource, using: fileURL, move: true)
guard case .success(let cacheURL) = voiceMessageCache.cache(mediaSource: mediaSource, using: fileURL, move: true) else {
XCTFail("A success is expected")
return
}
// The file must have been moved
XCTAssertFalse(fileManager.fileExists(atPath: fileURL.path()))
@@ -93,7 +93,7 @@ class VoiceMessageMediaManagerTests: XCTestCase {
let mediaSource = MediaSourceProxy(url: someURL, mimeType: audioOGGMimeType)
mediaProvider.loadFileFromSourceReturnValue = MediaFileHandleProxy.unmanaged(url: loadedFile)
let audioConverter = AudioConverterMock()
voiceMessageCache.cacheMediaSourceUsingMoveReturnValue = cachedConvertedFileURL
voiceMessageCache.cacheMediaSourceUsingMoveReturnValue = .success(cachedConvertedFileURL)
voiceMessageMediaManager = VoiceMessageMediaManager(mediaProvider: mediaProvider,
voiceMessageCache: voiceMessageCache,
audioConverter: audioConverter,
@@ -124,7 +124,7 @@ class VoiceMessageMediaManagerTests: XCTestCase {
}
voiceMessageCache.cacheMediaSourceUsingMoveClosure = { _, _, _ in
cachedURL = cachedConvertedFileURL
return cachedConvertedFileURL
return .success(cachedConvertedFileURL)
}
let audioConverter = AudioConverterMock()
@@ -0,0 +1,237 @@
//
// Copyright 2023 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 Combine
@testable import ElementX
import Foundation
import XCTest
@MainActor
class VoiceMessageRecorderTests: XCTestCase {
private var voiceMessageRecorder: VoiceMessageRecorder!
private var audioRecorder: AudioRecorderMock!
private var mediaPlayerProvider: MediaPlayerProviderMock!
private var audioConverter: AudioConverterMock!
private var voiceMessageCache: VoiceMessageCacheMock!
private var audioPlayer: AudioPlayerMock!
private var audioPlayerActionsSubject: PassthroughSubject<AudioPlayerAction, Never> = .init()
private var audioPlayerActions: AnyPublisher<AudioPlayerAction, Never> {
audioPlayerActionsSubject.eraseToAnyPublisher()
}
private let recordingURL = URL("/some/url")
override func setUp() async throws {
audioRecorder = AudioRecorderMock()
audioRecorder.recordWithReturnValue = .success(())
audioRecorder.underlyingCurrentTime = 0
audioRecorder.averagePowerForChannelNumberReturnValue = 0
audioPlayer = AudioPlayerMock()
audioPlayer.actions = audioPlayerActions
mediaPlayerProvider = MediaPlayerProviderMock()
mediaPlayerProvider.playerForClosure = { _ in
.success(self.audioPlayer)
}
audioConverter = AudioConverterMock()
voiceMessageCache = VoiceMessageCacheMock()
voiceMessageRecorder = VoiceMessageRecorder(audioRecorder: audioRecorder,
mediaPlayerProvider: mediaPlayerProvider,
audioConverter: audioConverter,
voiceMessageCache: voiceMessageCache)
}
func testStartRecording() async throws {
audioRecorder.url = recordingURL
_ = await voiceMessageRecorder.startRecording()
XCTAssert(audioRecorder.recordWithCalled)
XCTAssertEqual(voiceMessageRecorder.recordingURL, audioRecorder.url)
}
func testStopRecording() async throws {
audioRecorder.isRecording = true
audioRecorder.currentTime = 14.0
audioRecorder.url = recordingURL
_ = await voiceMessageRecorder.startRecording()
_ = await voiceMessageRecorder.stopRecording()
// Internal audio recorder must have been stopped
XCTAssert(audioRecorder.stopRecordingCalled)
// A preview player state must be available
let previewPlayerState = voiceMessageRecorder.previewAudioPlayerState
XCTAssertNotNil(previewPlayerState)
XCTAssertEqual(previewPlayerState?.duration, audioRecorder.currentTime)
}
func testCancelRecording() async throws {
audioRecorder.isRecording = true
await voiceMessageRecorder.cancelRecording()
// The recording audio file must have been deleted
XCTAssert(audioRecorder.deleteRecordingCalled)
}
func testDeleteRecording() async throws {
await voiceMessageRecorder.deleteRecording()
XCTAssert(audioRecorder.deleteRecordingCalled)
}
func testStartPlayback() async throws {
audioRecorder.url = recordingURL
_ = await voiceMessageRecorder.startRecording()
_ = await voiceMessageRecorder.stopRecording()
// if the player url doesn't match the recording url
guard case .success = await voiceMessageRecorder.startPlayback() else {
XCTFail("Playback should start")
return
}
XCTAssert(audioPlayer.loadMediaSourceUsingAutoplayCalled)
XCTAssertEqual(audioPlayer.loadMediaSourceUsingAutoplayReceivedArguments?.url, recordingURL)
XCTAssertEqual(audioPlayer.loadMediaSourceUsingAutoplayReceivedArguments?.mediaSource.mimeType, "audio/m4a")
XCTAssertEqual(audioPlayer.loadMediaSourceUsingAutoplayReceivedArguments?.mediaSource.url, recordingURL)
XCTAssertEqual(audioPlayer.loadMediaSourceUsingAutoplayReceivedArguments?.autoplay, true)
XCTAssertFalse(audioPlayer.playCalled)
}
func testResumePlayback() async throws {
audioRecorder.url = recordingURL
_ = await voiceMessageRecorder.startRecording()
_ = await voiceMessageRecorder.stopRecording()
// if the player url matches the recording url
audioPlayer.url = recordingURL
guard case .success = await voiceMessageRecorder.startPlayback() else {
XCTFail("Playback should start")
return
}
XCTAssertFalse(audioPlayer.loadMediaSourceUsingAutoplayCalled)
XCTAssert(audioPlayer.playCalled)
}
func testPausePlayback() async throws {
audioRecorder.url = recordingURL
switch await voiceMessageRecorder.startRecording() {
case .failure(let error):
XCTFail("Recording should start. \(error)")
case .success:
break
}
_ = await voiceMessageRecorder.stopRecording()
voiceMessageRecorder.pausePlayback()
XCTAssert(audioPlayer.pauseCalled)
}
func testStopPlayback() async throws {
audioRecorder.url = recordingURL
_ = await voiceMessageRecorder.startRecording()
_ = await voiceMessageRecorder.stopRecording()
await voiceMessageRecorder.stopPlayback()
XCTAssertEqual(voiceMessageRecorder.previewAudioPlayerState?.isAttached, false)
XCTAssert(audioPlayer.stopCalled)
}
func testSeekPlayback() async throws {
audioRecorder.url = recordingURL
// Calling stop will generate the preview player state needed to have an audio player
_ = await voiceMessageRecorder.startRecording()
_ = await voiceMessageRecorder.stopRecording()
voiceMessageRecorder.previewAudioPlayerState?.attachAudioPlayer(audioPlayer)
await voiceMessageRecorder.seekPlayback(to: 0.4)
XCTAssert(audioPlayer.seekToCalled)
XCTAssertEqual(audioPlayer.seekToReceivedProgress, 0.4)
}
func testBuildRecordedWaveform() async throws {
guard let audioFileUrl = Bundle(for: Self.self).url(forResource: "test_audio", withExtension: "mp3") else {
XCTFail("Test audio file is missing")
return
}
audioRecorder.url = audioFileUrl
_ = await voiceMessageRecorder.startRecording()
_ = await voiceMessageRecorder.stopRecording()
guard case .success(let data) = await voiceMessageRecorder.buildRecordingWaveform() else {
XCTFail("A waveform is expected")
return
}
XCTAssert(!data.isEmpty)
}
func testSendVoiceMessage() async throws {
guard let audioFileUrl = Bundle(for: Self.self).url(forResource: "test_voice_message", withExtension: "m4a") else {
XCTFail("Test audio file is missing")
return
}
audioRecorder.currentTime = 42
audioRecorder.url = audioFileUrl
_ = await voiceMessageRecorder.startRecording()
_ = await voiceMessageRecorder.stopRecording()
let roomProxy = RoomProxyMock()
let audioConverter = AudioConverterMock()
var convertedFileUrl: URL?
var convertedFileSize: UInt64?
audioConverter.convertToOpusOggSourceURLDestinationURLClosure = { source, destination in
convertedFileUrl = destination
try? FileManager.default.removeItem(at: destination)
let internalConverter = AudioConverter()
try internalConverter.convertToOpusOgg(sourceURL: source, destinationURL: destination)
convertedFileSize = try? UInt64(FileManager.default.sizeForItem(at: destination))
// the source URL must be the recorded file
XCTAssertEqual(source, audioFileUrl)
// check the converted file extension
XCTAssertEqual(destination.pathExtension, "ogg")
}
roomProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleClosure = { url, audioInfo, waveform, _, _ in
XCTAssertEqual(url, convertedFileUrl)
XCTAssertEqual(audioInfo.duration, self.audioRecorder.currentTime)
XCTAssertEqual(audioInfo.size, convertedFileSize)
XCTAssertEqual(audioInfo.mimetype, "audio/ogg")
XCTAssertFalse(waveform.isEmpty)
return .success(())
}
guard case .success = await voiceMessageRecorder.sendVoiceMessage(inRoom: roomProxy, audioConverter: audioConverter) else {
XCTFail("A success is expected")
return
}
XCTAssert(audioConverter.convertToOpusOggSourceURLDestinationURLCalled)
XCTAssert(roomProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleCalled)
// the converted file must have been deleted
if let convertedFileUrl {
XCTAssertFalse(FileManager.default.fileExists(atPath: convertedFileUrl.path()))
} else {
XCTFail("converted file URL is missing")
}
}
}
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:26fdd567b146251130beb6f662ec227ad48d734561c759b072a0e37d8f804789
size 96929
oid sha256:4847895e8d01132c5fbef0cd2a5b3ac8c394ae197a15b37b7d9fff53cfa0bab9
size 95668
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1a14094723fbe08e8ebf64f6a7f567e57e8835533fcb3c2f03f806acded2af54
size 65817
oid sha256:efc7fa1ab541e5dd8bb1757ed782065e7c7e84d7354fe90702f1091134a24467
size 63847
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ee69c3ef27115aeee58aacd362b3d382480b47bf1e53ffd1ce72880319d11e67
size 60199
oid sha256:af1d1d6fcf418f97cc9aae336b349b3630a1e2995c26ab8565421c97a823935a
size 59818
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:76d5359eca0ee480fc6197652baf9b282d342cfd1e84893afb4a469c572f24a9
size 57410
oid sha256:73435a7b6a9a06ee58de540c559cbe5a2492bb9c9617260b303957d54dcf195c
size 56863
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8dfd0301b8fd911e44b1f5f1caf9b74f575eb57e74d39a7765bde7966e5872d0
size 63481