From 4d978688f4373daa797ecfa15ca87d17201b49b5 Mon Sep 17 00:00:00 2001 From: davidliu Date: Wed, 2 Oct 2024 14:00:18 +0900 Subject: [PATCH] Implement LocalAudioTrack.addSink (#516) * Implement LocalAudioTrack.addSink * test fix * fix tests * fix tests --- .changeset/bright-pillows-pay.md | 5 ++ .../audio/AudioRecordSamplesDispatcher.kt | 70 +++++++++++++++++++ .../livekit/android/dagger/InjectionNames.kt | 2 + .../io/livekit/android/dagger/RTCModule.kt | 11 +++ .../livekit/android/room/track/AudioTrack.kt | 21 +++++- .../android/room/track/LocalAudioTrack.kt | 40 +++++++++++ .../android/room/track/RemoteAudioTrack.kt | 15 +--- .../android/test/mock/dagger/TestRTCModule.kt | 8 +++ .../mock/room/track/MockLocalAudioTrack.kt | 48 +++++++++++++ .../livekit/android/room/RoomMockE2ETest.kt | 20 +----- .../room/RoomParticipantEventMockE2ETest.kt | 13 +--- .../room/RoomReconnectionMockE2ETest.kt | 13 +--- .../room/RoomTranscriptionMockE2ETest.kt | 21 +----- .../LocalParticipantMockE2ETest.kt | 36 ++-------- .../participant/ParticipantMockE2ETest.kt | 13 +--- 15 files changed, 223 insertions(+), 113 deletions(-) create mode 100644 .changeset/bright-pillows-pay.md create mode 100644 livekit-android-sdk/src/main/java/io/livekit/android/audio/AudioRecordSamplesDispatcher.kt create mode 100644 livekit-android-test/src/main/java/io/livekit/android/test/mock/room/track/MockLocalAudioTrack.kt diff --git a/.changeset/bright-pillows-pay.md b/.changeset/bright-pillows-pay.md new file mode 100644 index 00000000..fbf15e85 --- /dev/null +++ b/.changeset/bright-pillows-pay.md @@ -0,0 +1,5 @@ +--- +"client-sdk-android": minor +--- + +Implement LocalAudioTrack.addSink to receive audio data from local mic diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/audio/AudioRecordSamplesDispatcher.kt b/livekit-android-sdk/src/main/java/io/livekit/android/audio/AudioRecordSamplesDispatcher.kt new file mode 100644 index 00000000..1d519c06 --- /dev/null +++ b/livekit-android-sdk/src/main/java/io/livekit/android/audio/AudioRecordSamplesDispatcher.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2024 LiveKit, Inc. + * + * 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 io.livekit.android.audio + +import android.media.AudioFormat +import android.os.SystemClock +import livekit.org.webrtc.AudioTrackSink +import livekit.org.webrtc.audio.JavaAudioDeviceModule +import livekit.org.webrtc.audio.JavaAudioDeviceModule.SamplesReadyCallback +import java.nio.ByteBuffer + +class AudioRecordSamplesDispatcher : SamplesReadyCallback { + + private val sinks = mutableSetOf() + + @Synchronized + fun registerSink(sink: AudioTrackSink) { + sinks.add(sink) + } + + @Synchronized + fun unregisterSink(sink: AudioTrackSink) { + sinks.remove(sink) + } + + // Reference from Android code, AudioFormat.getBytesPerSample. BitPerSample / 8 + // Default audio data format is PCM 16 bits per sample. + // Guaranteed to be supported by all devices + private fun getBytesPerSample(audioFormat: Int): Int { + return when (audioFormat) { + AudioFormat.ENCODING_PCM_8BIT -> 1 + AudioFormat.ENCODING_PCM_16BIT, AudioFormat.ENCODING_IEC61937, AudioFormat.ENCODING_DEFAULT -> 2 + AudioFormat.ENCODING_PCM_FLOAT -> 4 + AudioFormat.ENCODING_INVALID -> throw IllegalArgumentException("Bad audio format $audioFormat") + else -> throw IllegalArgumentException("Bad audio format $audioFormat") + } + } + + @Synchronized + override fun onWebRtcAudioRecordSamplesReady(samples: JavaAudioDeviceModule.AudioSamples) { + val bitsPerSample = getBytesPerSample(samples.audioFormat) * 8 + val numFrames = samples.sampleRate / 100 // 10ms worth of samples. + val timestamp = SystemClock.elapsedRealtime() + for (sink in sinks) { + val byteBuffer = ByteBuffer.wrap(samples.data) + sink.onData( + byteBuffer, + bitsPerSample, + samples.sampleRate, + samples.channelCount, + numFrames, + timestamp, + ) + } + } +} diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/dagger/InjectionNames.kt b/livekit-android-sdk/src/main/java/io/livekit/android/dagger/InjectionNames.kt index b5a72c63..0cebcf8a 100644 --- a/livekit-android-sdk/src/main/java/io/livekit/android/dagger/InjectionNames.kt +++ b/livekit-android-sdk/src/main/java/io/livekit/android/dagger/InjectionNames.kt @@ -47,6 +47,8 @@ object InjectionNames { const val LIB_WEBRTC_INITIALIZATION = "lib_webrtc_initialization" + const val LOCAL_AUDIO_RECORD_SAMPLES_DISPATCHER = "local_audio_record_samples_dispatcher" + // Overrides const val OVERRIDE_OKHTTP = "override_okhttp" const val OVERRIDE_AUDIO_DEVICE_MODULE = "override_audio_device_module" diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/dagger/RTCModule.kt b/livekit-android-sdk/src/main/java/io/livekit/android/dagger/RTCModule.kt index 726a6680..5b0123eb 100644 --- a/livekit-android-sdk/src/main/java/io/livekit/android/dagger/RTCModule.kt +++ b/livekit-android-sdk/src/main/java/io/livekit/android/dagger/RTCModule.kt @@ -27,6 +27,7 @@ import dagger.Provides import io.livekit.android.LiveKit import io.livekit.android.audio.AudioProcessingController import io.livekit.android.audio.AudioProcessorOptions +import io.livekit.android.audio.AudioRecordSamplesDispatcher import io.livekit.android.audio.CommunicationWorkaround import io.livekit.android.memory.CloseableManager import io.livekit.android.util.LKLog @@ -127,6 +128,13 @@ internal object RTCModule { return LibWebrtcInitialization } + @Provides + @Named(InjectionNames.LOCAL_AUDIO_RECORD_SAMPLES_DISPATCHER) + @Singleton + fun localAudioSamplesDispatcher(): AudioRecordSamplesDispatcher { + return AudioRecordSamplesDispatcher() + } + @Provides @Singleton @JvmSuppressWildcards @@ -141,6 +149,8 @@ internal object RTCModule { appContext: Context, closeableManager: CloseableManager, communicationWorkaround: CommunicationWorkaround, + @Named(InjectionNames.LOCAL_AUDIO_RECORD_SAMPLES_DISPATCHER) + audioRecordSamplesDispatcher: AudioRecordSamplesDispatcher, ): AudioDeviceModule { if (audioDeviceModuleOverride != null) { return audioDeviceModuleOverride @@ -215,6 +225,7 @@ internal object RTCModule { .setAudioTrackErrorCallback(audioTrackErrorCallback) .setAudioRecordStateCallback(audioRecordStateCallback) .setAudioTrackStateCallback(audioTrackStateCallback) + .setSamplesReadyCallback(audioRecordSamplesDispatcher) // VOICE_COMMUNICATION needs to be used for echo cancelling. .setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION) .setAudioAttributes(audioOutputAttributes) diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/room/track/AudioTrack.kt b/livekit-android-sdk/src/main/java/io/livekit/android/room/track/AudioTrack.kt index e979c67c..143274c6 100644 --- a/livekit-android-sdk/src/main/java/io/livekit/android/room/track/AudioTrack.kt +++ b/livekit-android-sdk/src/main/java/io/livekit/android/room/track/AudioTrack.kt @@ -17,6 +17,7 @@ package io.livekit.android.room.track import livekit.org.webrtc.AudioTrack +import livekit.org.webrtc.AudioTrackSink /** * A class representing an audio track. @@ -27,4 +28,22 @@ abstract class AudioTrack( * The underlying WebRTC audio track. */ override val rtcTrack: AudioTrack, -) : Track(name, Kind.AUDIO, rtcTrack) +) : Track(name, Kind.AUDIO, rtcTrack) { + + /** + * Adds a sink that receives the audio bytes and related information + * for this audio track. Repeated calls using the same sink will + * only add the sink once. + * + * Implementations should copy the audio data into a local copy if they wish + * to use the data after the [AudioTrackSink.onData] callback returns. + * Long running processing of the received audio data should be done in a separate + * thread, as doing so inline may block the audio thread. + */ + abstract fun addSink(sink: AudioTrackSink) + + /** + * Removes a previously added sink. + */ + abstract fun removeSink(sink: AudioTrackSink) +} diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/room/track/LocalAudioTrack.kt b/livekit-android-sdk/src/main/java/io/livekit/android/room/track/LocalAudioTrack.kt index 590228bd..00fddc25 100644 --- a/livekit-android-sdk/src/main/java/io/livekit/android/room/track/LocalAudioTrack.kt +++ b/livekit-android-sdk/src/main/java/io/livekit/android/room/track/LocalAudioTrack.kt @@ -24,6 +24,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.livekit.android.audio.AudioProcessingController +import io.livekit.android.audio.AudioRecordSamplesDispatcher import io.livekit.android.dagger.InjectionNames import io.livekit.android.room.participant.LocalParticipant import io.livekit.android.util.FlowObservable @@ -37,10 +38,13 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import livekit.LivekitModels.AudioTrackFeature +import livekit.org.webrtc.AudioTrackSink import livekit.org.webrtc.MediaConstraints import livekit.org.webrtc.PeerConnectionFactory import livekit.org.webrtc.RtpSender import livekit.org.webrtc.RtpTransceiver +import livekit.org.webrtc.audio.AudioDeviceModule +import livekit.org.webrtc.audio.JavaAudioDeviceModule import java.util.UUID import javax.inject.Named @@ -58,6 +62,8 @@ constructor( private val audioProcessingController: AudioProcessingController, @Named(InjectionNames.DISPATCHER_DEFAULT) private val dispatcher: CoroutineDispatcher, + @Named(InjectionNames.LOCAL_AUDIO_RECORD_SAMPLES_DISPATCHER) + private val audioRecordSamplesDispatcher: AudioRecordSamplesDispatcher, ) : AudioTrack(name, mediaTrack) { /** * To only be used for flow delegate scoping, and should not be cancelled. @@ -68,6 +74,30 @@ constructor( internal val sender: RtpSender? get() = transceiver?.sender + private val trackSinks = mutableSetOf() + + /** + * Note: This function relies on us setting + * [JavaAudioDeviceModule.Builder.setSamplesReadyCallback]. + * If you provide your own [AudioDeviceModule], or set your own + * callback, your sink will not receive any audio data. + * + * @see AudioTrack.addSink + */ + override fun addSink(sink: AudioTrackSink) { + synchronized(trackSinks) { + trackSinks.add(sink) + audioRecordSamplesDispatcher.registerSink(sink) + } + } + + override fun removeSink(sink: AudioTrackSink) { + synchronized(trackSinks) { + trackSinks.remove(sink) + audioRecordSamplesDispatcher.unregisterSink(sink) + } + } + /** * Changes can be observed by using [io.livekit.android.util.flow] */ @@ -107,6 +137,16 @@ constructor( return features } + override fun dispose() { + synchronized(trackSinks) { + for (sink in trackSinks) { + trackSinks.remove(sink) + audioRecordSamplesDispatcher.unregisterSink(sink) + } + } + super.dispose() + } + companion object { internal fun createTrack( context: Context, diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/room/track/RemoteAudioTrack.kt b/livekit-android-sdk/src/main/java/io/livekit/android/room/track/RemoteAudioTrack.kt index 9862691c..c8f92de3 100644 --- a/livekit-android-sdk/src/main/java/io/livekit/android/room/track/RemoteAudioTrack.kt +++ b/livekit-android-sdk/src/main/java/io/livekit/android/room/track/RemoteAudioTrack.kt @@ -29,24 +29,13 @@ class RemoteAudioTrack( internal val receiver: RtpReceiver, ) : io.livekit.android.room.track.AudioTrack(name, rtcTrack) { - /** - * Adds a sink that receives the audio bytes and related information - * for this audio track. Repeated calls using the same sink will - * only add the sink once. - * - * Implementations should copy the audio data into a local copy if they wish - * to use the data after this function returns. - */ - fun addSink(sink: AudioTrackSink) { + override fun addSink(sink: AudioTrackSink) { withRTCTrack { rtcTrack.addSink(sink) } } - /** - * Removes a previously added sink. - */ - fun removeSink(sink: AudioTrackSink) { + override fun removeSink(sink: AudioTrackSink) { withRTCTrack { rtcTrack.removeSink(sink) } diff --git a/livekit-android-test/src/main/java/io/livekit/android/test/mock/dagger/TestRTCModule.kt b/livekit-android-test/src/main/java/io/livekit/android/test/mock/dagger/TestRTCModule.kt index 156d3fd0..3e9312ab 100644 --- a/livekit-android-test/src/main/java/io/livekit/android/test/mock/dagger/TestRTCModule.kt +++ b/livekit-android-test/src/main/java/io/livekit/android/test/mock/dagger/TestRTCModule.kt @@ -21,6 +21,7 @@ import android.javax.sdp.SdpFactory import dagger.Module import dagger.Provides import io.livekit.android.audio.AudioProcessingController +import io.livekit.android.audio.AudioRecordSamplesDispatcher import io.livekit.android.dagger.CapabilitiesGetter import io.livekit.android.dagger.InjectionNames import io.livekit.android.test.mock.MockAudioDeviceModule @@ -58,6 +59,13 @@ object TestRTCModule { return MockAudioDeviceModule() } + @Provides + @Named(InjectionNames.LOCAL_AUDIO_RECORD_SAMPLES_DISPATCHER) + @Singleton + fun localAudioSamplesDispatcher(): AudioRecordSamplesDispatcher { + return AudioRecordSamplesDispatcher() + } + @Provides @Singleton fun peerConnectionFactory( diff --git a/livekit-android-test/src/main/java/io/livekit/android/test/mock/room/track/MockLocalAudioTrack.kt b/livekit-android-test/src/main/java/io/livekit/android/test/mock/room/track/MockLocalAudioTrack.kt new file mode 100644 index 00000000..1f9d0891 --- /dev/null +++ b/livekit-android-test/src/main/java/io/livekit/android/test/mock/room/track/MockLocalAudioTrack.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2024 LiveKit, Inc. + * + * 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 io.livekit.android.test.mock.room.track + +import io.livekit.android.audio.AudioProcessingController +import io.livekit.android.audio.AudioRecordSamplesDispatcher +import io.livekit.android.room.track.LocalAudioTrack +import io.livekit.android.room.track.LocalAudioTrackOptions +import io.livekit.android.test.MockE2ETest +import io.livekit.android.test.mock.MockAudioProcessingController +import io.livekit.android.test.mock.MockAudioStreamTrack +import io.livekit.android.test.mock.TestData +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import livekit.org.webrtc.AudioTrack + +@OptIn(ExperimentalCoroutinesApi::class) +fun MockE2ETest.createMockLocalAudioTrack( + name: String = "", + mediaTrack: AudioTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid), + options: LocalAudioTrackOptions = LocalAudioTrackOptions(), + audioProcessingController: AudioProcessingController = MockAudioProcessingController(), + dispatcher: CoroutineDispatcher = coroutineRule.dispatcher, + audioRecordSamplesDispatcher: AudioRecordSamplesDispatcher = AudioRecordSamplesDispatcher(), +): LocalAudioTrack { + return LocalAudioTrack( + name = name, + mediaTrack = mediaTrack, + options = options, + audioProcessingController = audioProcessingController, + dispatcher = dispatcher, + audioRecordSamplesDispatcher = audioRecordSamplesDispatcher, + ) +} diff --git a/livekit-android-test/src/test/java/io/livekit/android/room/RoomMockE2ETest.kt b/livekit-android-test/src/test/java/io/livekit/android/room/RoomMockE2ETest.kt index c5f4f81c..a42f9ba2 100644 --- a/livekit-android-test/src/test/java/io/livekit/android/room/RoomMockE2ETest.kt +++ b/livekit-android-test/src/test/java/io/livekit/android/room/RoomMockE2ETest.kt @@ -22,19 +22,17 @@ import io.livekit.android.events.ParticipantEvent import io.livekit.android.events.RoomEvent import io.livekit.android.events.convert import io.livekit.android.room.participant.ConnectionQuality -import io.livekit.android.room.track.LocalAudioTrack -import io.livekit.android.room.track.LocalAudioTrackOptions import io.livekit.android.room.track.Track import io.livekit.android.test.MockE2ETest import io.livekit.android.test.assert.assertIsClassList import io.livekit.android.test.events.EventCollector import io.livekit.android.test.events.FlowCollector -import io.livekit.android.test.mock.MockAudioProcessingController import io.livekit.android.test.mock.MockAudioStreamTrack import io.livekit.android.test.mock.MockMediaStream import io.livekit.android.test.mock.MockRtpReceiver import io.livekit.android.test.mock.TestData import io.livekit.android.test.mock.createMediaStreamId +import io.livekit.android.test.mock.room.track.createMockLocalAudioTrack import io.livekit.android.util.flow import io.livekit.android.util.toOkioByteString import junit.framework.Assert.assertEquals @@ -378,13 +376,7 @@ class RoomMockE2ETest : MockE2ETest() { connect() room.localParticipant.publishAudioTrack( - LocalAudioTrack( - name = "", - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid), - options = LocalAudioTrackOptions(), - audioProcessingController = MockAudioProcessingController(), - dispatcher = coroutineRule.dispatcher, - ), + track = createMockLocalAudioTrack(), ) val eventCollector = EventCollector(room.events, coroutineRule.scope) @@ -427,13 +419,7 @@ class RoomMockE2ETest : MockE2ETest() { return@registerSignalRequestHandler false } room.localParticipant.publishAudioTrack( - LocalAudioTrack( - name = "", - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid), - options = LocalAudioTrackOptions(), - audioProcessingController = MockAudioProcessingController(), - dispatcher = coroutineRule.dispatcher, - ), + track = createMockLocalAudioTrack(), ) val eventCollector = EventCollector(room.events, coroutineRule.scope) diff --git a/livekit-android-test/src/test/java/io/livekit/android/room/RoomParticipantEventMockE2ETest.kt b/livekit-android-test/src/test/java/io/livekit/android/room/RoomParticipantEventMockE2ETest.kt index 0d159a9e..2019a594 100644 --- a/livekit-android-test/src/test/java/io/livekit/android/room/RoomParticipantEventMockE2ETest.kt +++ b/livekit-android-test/src/test/java/io/livekit/android/room/RoomParticipantEventMockE2ETest.kt @@ -19,15 +19,12 @@ package io.livekit.android.room import io.livekit.android.events.ParticipantEvent import io.livekit.android.events.RoomEvent import io.livekit.android.room.participant.AudioTrackPublishOptions -import io.livekit.android.room.track.LocalAudioTrack -import io.livekit.android.room.track.LocalAudioTrackOptions import io.livekit.android.room.track.Track import io.livekit.android.test.MockE2ETest import io.livekit.android.test.assert.assertIsClass import io.livekit.android.test.events.EventCollector -import io.livekit.android.test.mock.MockAudioProcessingController -import io.livekit.android.test.mock.MockAudioStreamTrack import io.livekit.android.test.mock.TestData +import io.livekit.android.test.mock.room.track.createMockLocalAudioTrack import kotlinx.coroutines.ExperimentalCoroutinesApi import livekit.LivekitRtc import livekit.LivekitRtc.ParticipantUpdate @@ -80,13 +77,7 @@ class RoomParticipantEventMockE2ETest : MockE2ETest() { fun localTrackSubscribed() = runTest { connect() room.localParticipant.publishAudioTrack( - LocalAudioTrack( - name = "", - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid), - options = LocalAudioTrackOptions(), - audioProcessingController = MockAudioProcessingController(), - dispatcher = coroutineRule.dispatcher, - ), + track = createMockLocalAudioTrack(), options = AudioTrackPublishOptions( source = Track.Source.MICROPHONE, ), diff --git a/livekit-android-test/src/test/java/io/livekit/android/room/RoomReconnectionMockE2ETest.kt b/livekit-android-test/src/test/java/io/livekit/android/room/RoomReconnectionMockE2ETest.kt index 4d43ceed..af527278 100644 --- a/livekit-android-test/src/test/java/io/livekit/android/room/RoomReconnectionMockE2ETest.kt +++ b/livekit-android-test/src/test/java/io/livekit/android/room/RoomReconnectionMockE2ETest.kt @@ -16,12 +16,9 @@ package io.livekit.android.room -import io.livekit.android.room.track.LocalAudioTrack -import io.livekit.android.room.track.LocalAudioTrackOptions import io.livekit.android.test.MockE2ETest -import io.livekit.android.test.mock.MockAudioProcessingController -import io.livekit.android.test.mock.MockAudioStreamTrack import io.livekit.android.test.mock.TestData +import io.livekit.android.test.mock.room.track.createMockLocalAudioTrack import io.livekit.android.test.util.toPBByteString import kotlinx.coroutines.ExperimentalCoroutinesApi import livekit.LivekitRtc @@ -104,13 +101,7 @@ class RoomReconnectionMockE2ETest : MockE2ETest() { // publish track room.localParticipant.publishAudioTrack( - LocalAudioTrack( - name = "", - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid), - options = LocalAudioTrackOptions(), - audioProcessingController = MockAudioProcessingController(), - dispatcher = coroutineRule.dispatcher, - ), + track = createMockLocalAudioTrack(), ) disconnectPeerConnection() diff --git a/livekit-android-test/src/test/java/io/livekit/android/room/RoomTranscriptionMockE2ETest.kt b/livekit-android-test/src/test/java/io/livekit/android/room/RoomTranscriptionMockE2ETest.kt index d380db17..85095ede 100644 --- a/livekit-android-test/src/test/java/io/livekit/android/room/RoomTranscriptionMockE2ETest.kt +++ b/livekit-android-test/src/test/java/io/livekit/android/room/RoomTranscriptionMockE2ETest.kt @@ -20,17 +20,14 @@ import io.livekit.android.events.ParticipantEvent import io.livekit.android.events.RoomEvent import io.livekit.android.events.TrackPublicationEvent import io.livekit.android.room.participant.AudioTrackPublishOptions -import io.livekit.android.room.track.LocalAudioTrack -import io.livekit.android.room.track.LocalAudioTrackOptions import io.livekit.android.room.track.Track import io.livekit.android.test.MockE2ETest import io.livekit.android.test.assert.assertIsClass import io.livekit.android.test.events.EventCollector -import io.livekit.android.test.mock.MockAudioProcessingController -import io.livekit.android.test.mock.MockAudioStreamTrack import io.livekit.android.test.mock.MockDataChannel import io.livekit.android.test.mock.MockPeerConnection import io.livekit.android.test.mock.TestData +import io.livekit.android.test.mock.room.track.createMockLocalAudioTrack import io.livekit.android.test.util.toDataChannelBuffer import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay @@ -45,13 +42,7 @@ class RoomTranscriptionMockE2ETest : MockE2ETest() { fun transcriptionReceived() = runTest { connect() room.localParticipant.publishAudioTrack( - LocalAudioTrack( - name = "", - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid), - options = LocalAudioTrackOptions(), - audioProcessingController = MockAudioProcessingController(), - dispatcher = coroutineRule.dispatcher, - ), + track = createMockLocalAudioTrack(), options = AudioTrackPublishOptions( source = Track.Source.MICROPHONE, ), @@ -105,13 +96,7 @@ class RoomTranscriptionMockE2ETest : MockE2ETest() { fun transcriptionFirstReceivedStaysSame() = runTest { connect() room.localParticipant.publishAudioTrack( - LocalAudioTrack( - name = "", - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid), - options = LocalAudioTrackOptions(), - audioProcessingController = MockAudioProcessingController(), - dispatcher = coroutineRule.dispatcher, - ), + track = createMockLocalAudioTrack(), options = AudioTrackPublishOptions( source = Track.Source.MICROPHONE, ), diff --git a/livekit-android-test/src/test/java/io/livekit/android/room/participant/LocalParticipantMockE2ETest.kt b/livekit-android-test/src/test/java/io/livekit/android/room/participant/LocalParticipantMockE2ETest.kt index caacb961..99b928c8 100644 --- a/livekit-android-test/src/test/java/io/livekit/android/room/participant/LocalParticipantMockE2ETest.kt +++ b/livekit-android-test/src/test/java/io/livekit/android/room/participant/LocalParticipantMockE2ETest.kt @@ -24,8 +24,6 @@ import io.livekit.android.audio.AudioProcessorInterface import io.livekit.android.events.ParticipantEvent import io.livekit.android.events.RoomEvent import io.livekit.android.room.DefaultsManager -import io.livekit.android.room.track.LocalAudioTrack -import io.livekit.android.room.track.LocalAudioTrackOptions import io.livekit.android.room.track.LocalVideoTrack import io.livekit.android.room.track.LocalVideoTrackOptions import io.livekit.android.room.track.Track @@ -35,12 +33,12 @@ import io.livekit.android.test.MockE2ETest import io.livekit.android.test.assert.assertIsClassList import io.livekit.android.test.events.EventCollector import io.livekit.android.test.mock.MockAudioProcessingController -import io.livekit.android.test.mock.MockAudioStreamTrack import io.livekit.android.test.mock.MockEglBase import io.livekit.android.test.mock.MockVideoCapturer import io.livekit.android.test.mock.MockVideoStreamTrack import io.livekit.android.test.mock.TestData import io.livekit.android.test.mock.camera.MockCameraProvider +import io.livekit.android.test.mock.room.track.createMockLocalAudioTrack import io.livekit.android.test.util.toPBByteString import io.livekit.android.util.toOkioByteString import kotlinx.coroutines.CoroutineScope @@ -78,13 +76,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() { connect() room.localParticipant.publishAudioTrack( - LocalAudioTrack( - name = "", - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid), - options = LocalAudioTrackOptions(), - audioProcessingController = MockAudioProcessingController(), - dispatcher = coroutineRule.dispatcher, - ), + track = createMockLocalAudioTrack(), ) room.disconnect() @@ -439,13 +431,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() { wsFactory.ws.clearRequests() room.localParticipant.publishAudioTrack( - LocalAudioTrack( - name = "", - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid), - options = LocalAudioTrackOptions(), - audioProcessingController = MockAudioProcessingController(), - dispatcher = coroutineRule.dispatcher, - ), + track = createMockLocalAudioTrack(), ) advanceUntilIdle() @@ -471,13 +457,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() { val audioProcessingController = MockAudioProcessingController() room.localParticipant.publishAudioTrack( - LocalAudioTrack( - name = "", - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid), - options = LocalAudioTrackOptions(), - audioProcessingController = audioProcessingController, - dispatcher = coroutineRule.dispatcher, - ), + track = createMockLocalAudioTrack(audioProcessingController = audioProcessingController), ) advanceUntilIdle() @@ -517,13 +497,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() { val audioProcessingController = MockAudioProcessingController() room.localParticipant.publishAudioTrack( - LocalAudioTrack( - name = "", - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid), - options = LocalAudioTrackOptions(), - audioProcessingController = audioProcessingController, - dispatcher = coroutineRule.dispatcher, - ), + track = createMockLocalAudioTrack(audioProcessingController = audioProcessingController), ) advanceUntilIdle() diff --git a/livekit-android-test/src/test/java/io/livekit/android/room/participant/ParticipantMockE2ETest.kt b/livekit-android-test/src/test/java/io/livekit/android/room/participant/ParticipantMockE2ETest.kt index bb3ce861..ea392f56 100644 --- a/livekit-android-test/src/test/java/io/livekit/android/room/participant/ParticipantMockE2ETest.kt +++ b/livekit-android-test/src/test/java/io/livekit/android/room/participant/ParticipantMockE2ETest.kt @@ -18,14 +18,11 @@ package io.livekit.android.room.participant import io.livekit.android.events.ParticipantEvent import io.livekit.android.events.RoomEvent -import io.livekit.android.room.track.LocalAudioTrack -import io.livekit.android.room.track.LocalAudioTrackOptions import io.livekit.android.test.MockE2ETest import io.livekit.android.test.assert.assertIsClassList import io.livekit.android.test.events.EventCollector -import io.livekit.android.test.mock.MockAudioProcessingController -import io.livekit.android.test.mock.MockAudioStreamTrack import io.livekit.android.test.mock.TestData +import io.livekit.android.test.mock.room.track.createMockLocalAudioTrack import io.livekit.android.util.toOkioByteString import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Assert.assertEquals @@ -43,13 +40,7 @@ class ParticipantMockE2ETest : MockE2ETest() { // publish track room.localParticipant.publishAudioTrack( - LocalAudioTrack( - name = "", - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid), - options = LocalAudioTrackOptions(), - audioProcessingController = MockAudioProcessingController(), - dispatcher = coroutineRule.dispatcher, - ), + track = createMockLocalAudioTrack(), ) val eventCollector = EventCollector(room.events, coroutineRule.scope)