davidliu
Committed by GitHub

Implement LocalAudioTrack.addSink (#516)

* Implement LocalAudioTrack.addSink

* test fix

* fix tests

* fix tests
  1 +---
  2 +"client-sdk-android": minor
  3 +---
  4 +
  5 +Implement LocalAudioTrack.addSink to receive audio data from local mic
  1 +/*
  2 + * Copyright 2024 LiveKit, Inc.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package io.livekit.android.audio
  18 +
  19 +import android.media.AudioFormat
  20 +import android.os.SystemClock
  21 +import livekit.org.webrtc.AudioTrackSink
  22 +import livekit.org.webrtc.audio.JavaAudioDeviceModule
  23 +import livekit.org.webrtc.audio.JavaAudioDeviceModule.SamplesReadyCallback
  24 +import java.nio.ByteBuffer
  25 +
  26 +class AudioRecordSamplesDispatcher : SamplesReadyCallback {
  27 +
  28 + private val sinks = mutableSetOf<AudioTrackSink>()
  29 +
  30 + @Synchronized
  31 + fun registerSink(sink: AudioTrackSink) {
  32 + sinks.add(sink)
  33 + }
  34 +
  35 + @Synchronized
  36 + fun unregisterSink(sink: AudioTrackSink) {
  37 + sinks.remove(sink)
  38 + }
  39 +
  40 + // Reference from Android code, AudioFormat.getBytesPerSample. BitPerSample / 8
  41 + // Default audio data format is PCM 16 bits per sample.
  42 + // Guaranteed to be supported by all devices
  43 + private fun getBytesPerSample(audioFormat: Int): Int {
  44 + return when (audioFormat) {
  45 + AudioFormat.ENCODING_PCM_8BIT -> 1
  46 + AudioFormat.ENCODING_PCM_16BIT, AudioFormat.ENCODING_IEC61937, AudioFormat.ENCODING_DEFAULT -> 2
  47 + AudioFormat.ENCODING_PCM_FLOAT -> 4
  48 + AudioFormat.ENCODING_INVALID -> throw IllegalArgumentException("Bad audio format $audioFormat")
  49 + else -> throw IllegalArgumentException("Bad audio format $audioFormat")
  50 + }
  51 + }
  52 +
  53 + @Synchronized
  54 + override fun onWebRtcAudioRecordSamplesReady(samples: JavaAudioDeviceModule.AudioSamples) {
  55 + val bitsPerSample = getBytesPerSample(samples.audioFormat) * 8
  56 + val numFrames = samples.sampleRate / 100 // 10ms worth of samples.
  57 + val timestamp = SystemClock.elapsedRealtime()
  58 + for (sink in sinks) {
  59 + val byteBuffer = ByteBuffer.wrap(samples.data)
  60 + sink.onData(
  61 + byteBuffer,
  62 + bitsPerSample,
  63 + samples.sampleRate,
  64 + samples.channelCount,
  65 + numFrames,
  66 + timestamp,
  67 + )
  68 + }
  69 + }
  70 +}
@@ -47,6 +47,8 @@ object InjectionNames { @@ -47,6 +47,8 @@ object InjectionNames {
47 47
48 const val LIB_WEBRTC_INITIALIZATION = "lib_webrtc_initialization" 48 const val LIB_WEBRTC_INITIALIZATION = "lib_webrtc_initialization"
49 49
  50 + const val LOCAL_AUDIO_RECORD_SAMPLES_DISPATCHER = "local_audio_record_samples_dispatcher"
  51 +
50 // Overrides 52 // Overrides
51 const val OVERRIDE_OKHTTP = "override_okhttp" 53 const val OVERRIDE_OKHTTP = "override_okhttp"
52 const val OVERRIDE_AUDIO_DEVICE_MODULE = "override_audio_device_module" 54 const val OVERRIDE_AUDIO_DEVICE_MODULE = "override_audio_device_module"
@@ -27,6 +27,7 @@ import dagger.Provides @@ -27,6 +27,7 @@ import dagger.Provides
27 import io.livekit.android.LiveKit 27 import io.livekit.android.LiveKit
28 import io.livekit.android.audio.AudioProcessingController 28 import io.livekit.android.audio.AudioProcessingController
29 import io.livekit.android.audio.AudioProcessorOptions 29 import io.livekit.android.audio.AudioProcessorOptions
  30 +import io.livekit.android.audio.AudioRecordSamplesDispatcher
30 import io.livekit.android.audio.CommunicationWorkaround 31 import io.livekit.android.audio.CommunicationWorkaround
31 import io.livekit.android.memory.CloseableManager 32 import io.livekit.android.memory.CloseableManager
32 import io.livekit.android.util.LKLog 33 import io.livekit.android.util.LKLog
@@ -128,6 +129,13 @@ internal object RTCModule { @@ -128,6 +129,13 @@ internal object RTCModule {
128 } 129 }
129 130
130 @Provides 131 @Provides
  132 + @Named(InjectionNames.LOCAL_AUDIO_RECORD_SAMPLES_DISPATCHER)
  133 + @Singleton
  134 + fun localAudioSamplesDispatcher(): AudioRecordSamplesDispatcher {
  135 + return AudioRecordSamplesDispatcher()
  136 + }
  137 +
  138 + @Provides
131 @Singleton 139 @Singleton
132 @JvmSuppressWildcards 140 @JvmSuppressWildcards
133 fun audioModule( 141 fun audioModule(
@@ -141,6 +149,8 @@ internal object RTCModule { @@ -141,6 +149,8 @@ internal object RTCModule {
141 appContext: Context, 149 appContext: Context,
142 closeableManager: CloseableManager, 150 closeableManager: CloseableManager,
143 communicationWorkaround: CommunicationWorkaround, 151 communicationWorkaround: CommunicationWorkaround,
  152 + @Named(InjectionNames.LOCAL_AUDIO_RECORD_SAMPLES_DISPATCHER)
  153 + audioRecordSamplesDispatcher: AudioRecordSamplesDispatcher,
144 ): AudioDeviceModule { 154 ): AudioDeviceModule {
145 if (audioDeviceModuleOverride != null) { 155 if (audioDeviceModuleOverride != null) {
146 return audioDeviceModuleOverride 156 return audioDeviceModuleOverride
@@ -215,6 +225,7 @@ internal object RTCModule { @@ -215,6 +225,7 @@ internal object RTCModule {
215 .setAudioTrackErrorCallback(audioTrackErrorCallback) 225 .setAudioTrackErrorCallback(audioTrackErrorCallback)
216 .setAudioRecordStateCallback(audioRecordStateCallback) 226 .setAudioRecordStateCallback(audioRecordStateCallback)
217 .setAudioTrackStateCallback(audioTrackStateCallback) 227 .setAudioTrackStateCallback(audioTrackStateCallback)
  228 + .setSamplesReadyCallback(audioRecordSamplesDispatcher)
218 // VOICE_COMMUNICATION needs to be used for echo cancelling. 229 // VOICE_COMMUNICATION needs to be used for echo cancelling.
219 .setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION) 230 .setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
220 .setAudioAttributes(audioOutputAttributes) 231 .setAudioAttributes(audioOutputAttributes)
@@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
17 package io.livekit.android.room.track 17 package io.livekit.android.room.track
18 18
19 import livekit.org.webrtc.AudioTrack 19 import livekit.org.webrtc.AudioTrack
  20 +import livekit.org.webrtc.AudioTrackSink
20 21
21 /** 22 /**
22 * A class representing an audio track. 23 * A class representing an audio track.
@@ -27,4 +28,22 @@ abstract class AudioTrack( @@ -27,4 +28,22 @@ abstract class AudioTrack(
27 * The underlying WebRTC audio track. 28 * The underlying WebRTC audio track.
28 */ 29 */
29 override val rtcTrack: AudioTrack, 30 override val rtcTrack: AudioTrack,
30 -) : Track(name, Kind.AUDIO, rtcTrack) 31 +) : Track(name, Kind.AUDIO, rtcTrack) {
  32 +
  33 + /**
  34 + * Adds a sink that receives the audio bytes and related information
  35 + * for this audio track. Repeated calls using the same sink will
  36 + * only add the sink once.
  37 + *
  38 + * Implementations should copy the audio data into a local copy if they wish
  39 + * to use the data after the [AudioTrackSink.onData] callback returns.
  40 + * Long running processing of the received audio data should be done in a separate
  41 + * thread, as doing so inline may block the audio thread.
  42 + */
  43 + abstract fun addSink(sink: AudioTrackSink)
  44 +
  45 + /**
  46 + * Removes a previously added sink.
  47 + */
  48 + abstract fun removeSink(sink: AudioTrackSink)
  49 +}
@@ -24,6 +24,7 @@ import dagger.assisted.Assisted @@ -24,6 +24,7 @@ import dagger.assisted.Assisted
24 import dagger.assisted.AssistedFactory 24 import dagger.assisted.AssistedFactory
25 import dagger.assisted.AssistedInject 25 import dagger.assisted.AssistedInject
26 import io.livekit.android.audio.AudioProcessingController 26 import io.livekit.android.audio.AudioProcessingController
  27 +import io.livekit.android.audio.AudioRecordSamplesDispatcher
27 import io.livekit.android.dagger.InjectionNames 28 import io.livekit.android.dagger.InjectionNames
28 import io.livekit.android.room.participant.LocalParticipant 29 import io.livekit.android.room.participant.LocalParticipant
29 import io.livekit.android.util.FlowObservable 30 import io.livekit.android.util.FlowObservable
@@ -37,10 +38,13 @@ import kotlinx.coroutines.flow.combine @@ -37,10 +38,13 @@ import kotlinx.coroutines.flow.combine
37 import kotlinx.coroutines.flow.map 38 import kotlinx.coroutines.flow.map
38 import kotlinx.coroutines.flow.stateIn 39 import kotlinx.coroutines.flow.stateIn
39 import livekit.LivekitModels.AudioTrackFeature 40 import livekit.LivekitModels.AudioTrackFeature
  41 +import livekit.org.webrtc.AudioTrackSink
40 import livekit.org.webrtc.MediaConstraints 42 import livekit.org.webrtc.MediaConstraints
41 import livekit.org.webrtc.PeerConnectionFactory 43 import livekit.org.webrtc.PeerConnectionFactory
42 import livekit.org.webrtc.RtpSender 44 import livekit.org.webrtc.RtpSender
43 import livekit.org.webrtc.RtpTransceiver 45 import livekit.org.webrtc.RtpTransceiver
  46 +import livekit.org.webrtc.audio.AudioDeviceModule
  47 +import livekit.org.webrtc.audio.JavaAudioDeviceModule
44 import java.util.UUID 48 import java.util.UUID
45 import javax.inject.Named 49 import javax.inject.Named
46 50
@@ -58,6 +62,8 @@ constructor( @@ -58,6 +62,8 @@ constructor(
58 private val audioProcessingController: AudioProcessingController, 62 private val audioProcessingController: AudioProcessingController,
59 @Named(InjectionNames.DISPATCHER_DEFAULT) 63 @Named(InjectionNames.DISPATCHER_DEFAULT)
60 private val dispatcher: CoroutineDispatcher, 64 private val dispatcher: CoroutineDispatcher,
  65 + @Named(InjectionNames.LOCAL_AUDIO_RECORD_SAMPLES_DISPATCHER)
  66 + private val audioRecordSamplesDispatcher: AudioRecordSamplesDispatcher,
61 ) : AudioTrack(name, mediaTrack) { 67 ) : AudioTrack(name, mediaTrack) {
62 /** 68 /**
63 * To only be used for flow delegate scoping, and should not be cancelled. 69 * To only be used for flow delegate scoping, and should not be cancelled.
@@ -68,6 +74,30 @@ constructor( @@ -68,6 +74,30 @@ constructor(
68 internal val sender: RtpSender? 74 internal val sender: RtpSender?
69 get() = transceiver?.sender 75 get() = transceiver?.sender
70 76
  77 + private val trackSinks = mutableSetOf<AudioTrackSink>()
  78 +
  79 + /**
  80 + * Note: This function relies on us setting
  81 + * [JavaAudioDeviceModule.Builder.setSamplesReadyCallback].
  82 + * If you provide your own [AudioDeviceModule], or set your own
  83 + * callback, your sink will not receive any audio data.
  84 + *
  85 + * @see AudioTrack.addSink
  86 + */
  87 + override fun addSink(sink: AudioTrackSink) {
  88 + synchronized(trackSinks) {
  89 + trackSinks.add(sink)
  90 + audioRecordSamplesDispatcher.registerSink(sink)
  91 + }
  92 + }
  93 +
  94 + override fun removeSink(sink: AudioTrackSink) {
  95 + synchronized(trackSinks) {
  96 + trackSinks.remove(sink)
  97 + audioRecordSamplesDispatcher.unregisterSink(sink)
  98 + }
  99 + }
  100 +
71 /** 101 /**
72 * Changes can be observed by using [io.livekit.android.util.flow] 102 * Changes can be observed by using [io.livekit.android.util.flow]
73 */ 103 */
@@ -107,6 +137,16 @@ constructor( @@ -107,6 +137,16 @@ constructor(
107 return features 137 return features
108 } 138 }
109 139
  140 + override fun dispose() {
  141 + synchronized(trackSinks) {
  142 + for (sink in trackSinks) {
  143 + trackSinks.remove(sink)
  144 + audioRecordSamplesDispatcher.unregisterSink(sink)
  145 + }
  146 + }
  147 + super.dispose()
  148 + }
  149 +
110 companion object { 150 companion object {
111 internal fun createTrack( 151 internal fun createTrack(
112 context: Context, 152 context: Context,
@@ -29,24 +29,13 @@ class RemoteAudioTrack( @@ -29,24 +29,13 @@ class RemoteAudioTrack(
29 internal val receiver: RtpReceiver, 29 internal val receiver: RtpReceiver,
30 ) : io.livekit.android.room.track.AudioTrack(name, rtcTrack) { 30 ) : io.livekit.android.room.track.AudioTrack(name, rtcTrack) {
31 31
32 - /**  
33 - * Adds a sink that receives the audio bytes and related information  
34 - * for this audio track. Repeated calls using the same sink will  
35 - * only add the sink once.  
36 - *  
37 - * Implementations should copy the audio data into a local copy if they wish  
38 - * to use the data after this function returns.  
39 - */  
40 - fun addSink(sink: AudioTrackSink) { 32 + override fun addSink(sink: AudioTrackSink) {
41 withRTCTrack { 33 withRTCTrack {
42 rtcTrack.addSink(sink) 34 rtcTrack.addSink(sink)
43 } 35 }
44 } 36 }
45 37
46 - /**  
47 - * Removes a previously added sink.  
48 - */  
49 - fun removeSink(sink: AudioTrackSink) { 38 + override fun removeSink(sink: AudioTrackSink) {
50 withRTCTrack { 39 withRTCTrack {
51 rtcTrack.removeSink(sink) 40 rtcTrack.removeSink(sink)
52 } 41 }
@@ -21,6 +21,7 @@ import android.javax.sdp.SdpFactory @@ -21,6 +21,7 @@ import android.javax.sdp.SdpFactory
21 import dagger.Module 21 import dagger.Module
22 import dagger.Provides 22 import dagger.Provides
23 import io.livekit.android.audio.AudioProcessingController 23 import io.livekit.android.audio.AudioProcessingController
  24 +import io.livekit.android.audio.AudioRecordSamplesDispatcher
24 import io.livekit.android.dagger.CapabilitiesGetter 25 import io.livekit.android.dagger.CapabilitiesGetter
25 import io.livekit.android.dagger.InjectionNames 26 import io.livekit.android.dagger.InjectionNames
26 import io.livekit.android.test.mock.MockAudioDeviceModule 27 import io.livekit.android.test.mock.MockAudioDeviceModule
@@ -59,6 +60,13 @@ object TestRTCModule { @@ -59,6 +60,13 @@ object TestRTCModule {
59 } 60 }
60 61
61 @Provides 62 @Provides
  63 + @Named(InjectionNames.LOCAL_AUDIO_RECORD_SAMPLES_DISPATCHER)
  64 + @Singleton
  65 + fun localAudioSamplesDispatcher(): AudioRecordSamplesDispatcher {
  66 + return AudioRecordSamplesDispatcher()
  67 + }
  68 +
  69 + @Provides
62 @Singleton 70 @Singleton
63 fun peerConnectionFactory( 71 fun peerConnectionFactory(
64 appContext: Context, 72 appContext: Context,
  1 +/*
  2 + * Copyright 2024 LiveKit, Inc.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package io.livekit.android.test.mock.room.track
  18 +
  19 +import io.livekit.android.audio.AudioProcessingController
  20 +import io.livekit.android.audio.AudioRecordSamplesDispatcher
  21 +import io.livekit.android.room.track.LocalAudioTrack
  22 +import io.livekit.android.room.track.LocalAudioTrackOptions
  23 +import io.livekit.android.test.MockE2ETest
  24 +import io.livekit.android.test.mock.MockAudioProcessingController
  25 +import io.livekit.android.test.mock.MockAudioStreamTrack
  26 +import io.livekit.android.test.mock.TestData
  27 +import kotlinx.coroutines.CoroutineDispatcher
  28 +import kotlinx.coroutines.ExperimentalCoroutinesApi
  29 +import livekit.org.webrtc.AudioTrack
  30 +
  31 +@OptIn(ExperimentalCoroutinesApi::class)
  32 +fun MockE2ETest.createMockLocalAudioTrack(
  33 + name: String = "",
  34 + mediaTrack: AudioTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid),
  35 + options: LocalAudioTrackOptions = LocalAudioTrackOptions(),
  36 + audioProcessingController: AudioProcessingController = MockAudioProcessingController(),
  37 + dispatcher: CoroutineDispatcher = coroutineRule.dispatcher,
  38 + audioRecordSamplesDispatcher: AudioRecordSamplesDispatcher = AudioRecordSamplesDispatcher(),
  39 +): LocalAudioTrack {
  40 + return LocalAudioTrack(
  41 + name = name,
  42 + mediaTrack = mediaTrack,
  43 + options = options,
  44 + audioProcessingController = audioProcessingController,
  45 + dispatcher = dispatcher,
  46 + audioRecordSamplesDispatcher = audioRecordSamplesDispatcher,
  47 + )
  48 +}
@@ -22,19 +22,17 @@ import io.livekit.android.events.ParticipantEvent @@ -22,19 +22,17 @@ import io.livekit.android.events.ParticipantEvent
22 import io.livekit.android.events.RoomEvent 22 import io.livekit.android.events.RoomEvent
23 import io.livekit.android.events.convert 23 import io.livekit.android.events.convert
24 import io.livekit.android.room.participant.ConnectionQuality 24 import io.livekit.android.room.participant.ConnectionQuality
25 -import io.livekit.android.room.track.LocalAudioTrack  
26 -import io.livekit.android.room.track.LocalAudioTrackOptions  
27 import io.livekit.android.room.track.Track 25 import io.livekit.android.room.track.Track
28 import io.livekit.android.test.MockE2ETest 26 import io.livekit.android.test.MockE2ETest
29 import io.livekit.android.test.assert.assertIsClassList 27 import io.livekit.android.test.assert.assertIsClassList
30 import io.livekit.android.test.events.EventCollector 28 import io.livekit.android.test.events.EventCollector
31 import io.livekit.android.test.events.FlowCollector 29 import io.livekit.android.test.events.FlowCollector
32 -import io.livekit.android.test.mock.MockAudioProcessingController  
33 import io.livekit.android.test.mock.MockAudioStreamTrack 30 import io.livekit.android.test.mock.MockAudioStreamTrack
34 import io.livekit.android.test.mock.MockMediaStream 31 import io.livekit.android.test.mock.MockMediaStream
35 import io.livekit.android.test.mock.MockRtpReceiver 32 import io.livekit.android.test.mock.MockRtpReceiver
36 import io.livekit.android.test.mock.TestData 33 import io.livekit.android.test.mock.TestData
37 import io.livekit.android.test.mock.createMediaStreamId 34 import io.livekit.android.test.mock.createMediaStreamId
  35 +import io.livekit.android.test.mock.room.track.createMockLocalAudioTrack
38 import io.livekit.android.util.flow 36 import io.livekit.android.util.flow
39 import io.livekit.android.util.toOkioByteString 37 import io.livekit.android.util.toOkioByteString
40 import junit.framework.Assert.assertEquals 38 import junit.framework.Assert.assertEquals
@@ -378,13 +376,7 @@ class RoomMockE2ETest : MockE2ETest() { @@ -378,13 +376,7 @@ class RoomMockE2ETest : MockE2ETest() {
378 connect() 376 connect()
379 377
380 room.localParticipant.publishAudioTrack( 378 room.localParticipant.publishAudioTrack(
381 - LocalAudioTrack(  
382 - name = "",  
383 - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid),  
384 - options = LocalAudioTrackOptions(),  
385 - audioProcessingController = MockAudioProcessingController(),  
386 - dispatcher = coroutineRule.dispatcher,  
387 - ), 379 + track = createMockLocalAudioTrack(),
388 ) 380 )
389 381
390 val eventCollector = EventCollector(room.events, coroutineRule.scope) 382 val eventCollector = EventCollector(room.events, coroutineRule.scope)
@@ -427,13 +419,7 @@ class RoomMockE2ETest : MockE2ETest() { @@ -427,13 +419,7 @@ class RoomMockE2ETest : MockE2ETest() {
427 return@registerSignalRequestHandler false 419 return@registerSignalRequestHandler false
428 } 420 }
429 room.localParticipant.publishAudioTrack( 421 room.localParticipant.publishAudioTrack(
430 - LocalAudioTrack(  
431 - name = "",  
432 - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid),  
433 - options = LocalAudioTrackOptions(),  
434 - audioProcessingController = MockAudioProcessingController(),  
435 - dispatcher = coroutineRule.dispatcher,  
436 - ), 422 + track = createMockLocalAudioTrack(),
437 ) 423 )
438 424
439 val eventCollector = EventCollector(room.events, coroutineRule.scope) 425 val eventCollector = EventCollector(room.events, coroutineRule.scope)
@@ -19,15 +19,12 @@ package io.livekit.android.room @@ -19,15 +19,12 @@ package io.livekit.android.room
19 import io.livekit.android.events.ParticipantEvent 19 import io.livekit.android.events.ParticipantEvent
20 import io.livekit.android.events.RoomEvent 20 import io.livekit.android.events.RoomEvent
21 import io.livekit.android.room.participant.AudioTrackPublishOptions 21 import io.livekit.android.room.participant.AudioTrackPublishOptions
22 -import io.livekit.android.room.track.LocalAudioTrack  
23 -import io.livekit.android.room.track.LocalAudioTrackOptions  
24 import io.livekit.android.room.track.Track 22 import io.livekit.android.room.track.Track
25 import io.livekit.android.test.MockE2ETest 23 import io.livekit.android.test.MockE2ETest
26 import io.livekit.android.test.assert.assertIsClass 24 import io.livekit.android.test.assert.assertIsClass
27 import io.livekit.android.test.events.EventCollector 25 import io.livekit.android.test.events.EventCollector
28 -import io.livekit.android.test.mock.MockAudioProcessingController  
29 -import io.livekit.android.test.mock.MockAudioStreamTrack  
30 import io.livekit.android.test.mock.TestData 26 import io.livekit.android.test.mock.TestData
  27 +import io.livekit.android.test.mock.room.track.createMockLocalAudioTrack
31 import kotlinx.coroutines.ExperimentalCoroutinesApi 28 import kotlinx.coroutines.ExperimentalCoroutinesApi
32 import livekit.LivekitRtc 29 import livekit.LivekitRtc
33 import livekit.LivekitRtc.ParticipantUpdate 30 import livekit.LivekitRtc.ParticipantUpdate
@@ -80,13 +77,7 @@ class RoomParticipantEventMockE2ETest : MockE2ETest() { @@ -80,13 +77,7 @@ class RoomParticipantEventMockE2ETest : MockE2ETest() {
80 fun localTrackSubscribed() = runTest { 77 fun localTrackSubscribed() = runTest {
81 connect() 78 connect()
82 room.localParticipant.publishAudioTrack( 79 room.localParticipant.publishAudioTrack(
83 - LocalAudioTrack(  
84 - name = "",  
85 - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid),  
86 - options = LocalAudioTrackOptions(),  
87 - audioProcessingController = MockAudioProcessingController(),  
88 - dispatcher = coroutineRule.dispatcher,  
89 - ), 80 + track = createMockLocalAudioTrack(),
90 options = AudioTrackPublishOptions( 81 options = AudioTrackPublishOptions(
91 source = Track.Source.MICROPHONE, 82 source = Track.Source.MICROPHONE,
92 ), 83 ),
@@ -16,12 +16,9 @@ @@ -16,12 +16,9 @@
16 16
17 package io.livekit.android.room 17 package io.livekit.android.room
18 18
19 -import io.livekit.android.room.track.LocalAudioTrack  
20 -import io.livekit.android.room.track.LocalAudioTrackOptions  
21 import io.livekit.android.test.MockE2ETest 19 import io.livekit.android.test.MockE2ETest
22 -import io.livekit.android.test.mock.MockAudioProcessingController  
23 -import io.livekit.android.test.mock.MockAudioStreamTrack  
24 import io.livekit.android.test.mock.TestData 20 import io.livekit.android.test.mock.TestData
  21 +import io.livekit.android.test.mock.room.track.createMockLocalAudioTrack
25 import io.livekit.android.test.util.toPBByteString 22 import io.livekit.android.test.util.toPBByteString
26 import kotlinx.coroutines.ExperimentalCoroutinesApi 23 import kotlinx.coroutines.ExperimentalCoroutinesApi
27 import livekit.LivekitRtc 24 import livekit.LivekitRtc
@@ -104,13 +101,7 @@ class RoomReconnectionMockE2ETest : MockE2ETest() { @@ -104,13 +101,7 @@ class RoomReconnectionMockE2ETest : MockE2ETest() {
104 101
105 // publish track 102 // publish track
106 room.localParticipant.publishAudioTrack( 103 room.localParticipant.publishAudioTrack(
107 - LocalAudioTrack(  
108 - name = "",  
109 - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid),  
110 - options = LocalAudioTrackOptions(),  
111 - audioProcessingController = MockAudioProcessingController(),  
112 - dispatcher = coroutineRule.dispatcher,  
113 - ), 104 + track = createMockLocalAudioTrack(),
114 ) 105 )
115 106
116 disconnectPeerConnection() 107 disconnectPeerConnection()
@@ -20,17 +20,14 @@ import io.livekit.android.events.ParticipantEvent @@ -20,17 +20,14 @@ import io.livekit.android.events.ParticipantEvent
20 import io.livekit.android.events.RoomEvent 20 import io.livekit.android.events.RoomEvent
21 import io.livekit.android.events.TrackPublicationEvent 21 import io.livekit.android.events.TrackPublicationEvent
22 import io.livekit.android.room.participant.AudioTrackPublishOptions 22 import io.livekit.android.room.participant.AudioTrackPublishOptions
23 -import io.livekit.android.room.track.LocalAudioTrack  
24 -import io.livekit.android.room.track.LocalAudioTrackOptions  
25 import io.livekit.android.room.track.Track 23 import io.livekit.android.room.track.Track
26 import io.livekit.android.test.MockE2ETest 24 import io.livekit.android.test.MockE2ETest
27 import io.livekit.android.test.assert.assertIsClass 25 import io.livekit.android.test.assert.assertIsClass
28 import io.livekit.android.test.events.EventCollector 26 import io.livekit.android.test.events.EventCollector
29 -import io.livekit.android.test.mock.MockAudioProcessingController  
30 -import io.livekit.android.test.mock.MockAudioStreamTrack  
31 import io.livekit.android.test.mock.MockDataChannel 27 import io.livekit.android.test.mock.MockDataChannel
32 import io.livekit.android.test.mock.MockPeerConnection 28 import io.livekit.android.test.mock.MockPeerConnection
33 import io.livekit.android.test.mock.TestData 29 import io.livekit.android.test.mock.TestData
  30 +import io.livekit.android.test.mock.room.track.createMockLocalAudioTrack
34 import io.livekit.android.test.util.toDataChannelBuffer 31 import io.livekit.android.test.util.toDataChannelBuffer
35 import kotlinx.coroutines.ExperimentalCoroutinesApi 32 import kotlinx.coroutines.ExperimentalCoroutinesApi
36 import kotlinx.coroutines.delay 33 import kotlinx.coroutines.delay
@@ -45,13 +42,7 @@ class RoomTranscriptionMockE2ETest : MockE2ETest() { @@ -45,13 +42,7 @@ class RoomTranscriptionMockE2ETest : MockE2ETest() {
45 fun transcriptionReceived() = runTest { 42 fun transcriptionReceived() = runTest {
46 connect() 43 connect()
47 room.localParticipant.publishAudioTrack( 44 room.localParticipant.publishAudioTrack(
48 - LocalAudioTrack(  
49 - name = "",  
50 - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid),  
51 - options = LocalAudioTrackOptions(),  
52 - audioProcessingController = MockAudioProcessingController(),  
53 - dispatcher = coroutineRule.dispatcher,  
54 - ), 45 + track = createMockLocalAudioTrack(),
55 options = AudioTrackPublishOptions( 46 options = AudioTrackPublishOptions(
56 source = Track.Source.MICROPHONE, 47 source = Track.Source.MICROPHONE,
57 ), 48 ),
@@ -105,13 +96,7 @@ class RoomTranscriptionMockE2ETest : MockE2ETest() { @@ -105,13 +96,7 @@ class RoomTranscriptionMockE2ETest : MockE2ETest() {
105 fun transcriptionFirstReceivedStaysSame() = runTest { 96 fun transcriptionFirstReceivedStaysSame() = runTest {
106 connect() 97 connect()
107 room.localParticipant.publishAudioTrack( 98 room.localParticipant.publishAudioTrack(
108 - LocalAudioTrack(  
109 - name = "",  
110 - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid),  
111 - options = LocalAudioTrackOptions(),  
112 - audioProcessingController = MockAudioProcessingController(),  
113 - dispatcher = coroutineRule.dispatcher,  
114 - ), 99 + track = createMockLocalAudioTrack(),
115 options = AudioTrackPublishOptions( 100 options = AudioTrackPublishOptions(
116 source = Track.Source.MICROPHONE, 101 source = Track.Source.MICROPHONE,
117 ), 102 ),
@@ -24,8 +24,6 @@ import io.livekit.android.audio.AudioProcessorInterface @@ -24,8 +24,6 @@ import io.livekit.android.audio.AudioProcessorInterface
24 import io.livekit.android.events.ParticipantEvent 24 import io.livekit.android.events.ParticipantEvent
25 import io.livekit.android.events.RoomEvent 25 import io.livekit.android.events.RoomEvent
26 import io.livekit.android.room.DefaultsManager 26 import io.livekit.android.room.DefaultsManager
27 -import io.livekit.android.room.track.LocalAudioTrack  
28 -import io.livekit.android.room.track.LocalAudioTrackOptions  
29 import io.livekit.android.room.track.LocalVideoTrack 27 import io.livekit.android.room.track.LocalVideoTrack
30 import io.livekit.android.room.track.LocalVideoTrackOptions 28 import io.livekit.android.room.track.LocalVideoTrackOptions
31 import io.livekit.android.room.track.Track 29 import io.livekit.android.room.track.Track
@@ -35,12 +33,12 @@ import io.livekit.android.test.MockE2ETest @@ -35,12 +33,12 @@ import io.livekit.android.test.MockE2ETest
35 import io.livekit.android.test.assert.assertIsClassList 33 import io.livekit.android.test.assert.assertIsClassList
36 import io.livekit.android.test.events.EventCollector 34 import io.livekit.android.test.events.EventCollector
37 import io.livekit.android.test.mock.MockAudioProcessingController 35 import io.livekit.android.test.mock.MockAudioProcessingController
38 -import io.livekit.android.test.mock.MockAudioStreamTrack  
39 import io.livekit.android.test.mock.MockEglBase 36 import io.livekit.android.test.mock.MockEglBase
40 import io.livekit.android.test.mock.MockVideoCapturer 37 import io.livekit.android.test.mock.MockVideoCapturer
41 import io.livekit.android.test.mock.MockVideoStreamTrack 38 import io.livekit.android.test.mock.MockVideoStreamTrack
42 import io.livekit.android.test.mock.TestData 39 import io.livekit.android.test.mock.TestData
43 import io.livekit.android.test.mock.camera.MockCameraProvider 40 import io.livekit.android.test.mock.camera.MockCameraProvider
  41 +import io.livekit.android.test.mock.room.track.createMockLocalAudioTrack
44 import io.livekit.android.test.util.toPBByteString 42 import io.livekit.android.test.util.toPBByteString
45 import io.livekit.android.util.toOkioByteString 43 import io.livekit.android.util.toOkioByteString
46 import kotlinx.coroutines.CoroutineScope 44 import kotlinx.coroutines.CoroutineScope
@@ -78,13 +76,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() { @@ -78,13 +76,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
78 connect() 76 connect()
79 77
80 room.localParticipant.publishAudioTrack( 78 room.localParticipant.publishAudioTrack(
81 - LocalAudioTrack(  
82 - name = "",  
83 - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid),  
84 - options = LocalAudioTrackOptions(),  
85 - audioProcessingController = MockAudioProcessingController(),  
86 - dispatcher = coroutineRule.dispatcher,  
87 - ), 79 + track = createMockLocalAudioTrack(),
88 ) 80 )
89 81
90 room.disconnect() 82 room.disconnect()
@@ -439,13 +431,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() { @@ -439,13 +431,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
439 431
440 wsFactory.ws.clearRequests() 432 wsFactory.ws.clearRequests()
441 room.localParticipant.publishAudioTrack( 433 room.localParticipant.publishAudioTrack(
442 - LocalAudioTrack(  
443 - name = "",  
444 - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid),  
445 - options = LocalAudioTrackOptions(),  
446 - audioProcessingController = MockAudioProcessingController(),  
447 - dispatcher = coroutineRule.dispatcher,  
448 - ), 434 + track = createMockLocalAudioTrack(),
449 ) 435 )
450 436
451 advanceUntilIdle() 437 advanceUntilIdle()
@@ -471,13 +457,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() { @@ -471,13 +457,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
471 457
472 val audioProcessingController = MockAudioProcessingController() 458 val audioProcessingController = MockAudioProcessingController()
473 room.localParticipant.publishAudioTrack( 459 room.localParticipant.publishAudioTrack(
474 - LocalAudioTrack(  
475 - name = "",  
476 - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid),  
477 - options = LocalAudioTrackOptions(),  
478 - audioProcessingController = audioProcessingController,  
479 - dispatcher = coroutineRule.dispatcher,  
480 - ), 460 + track = createMockLocalAudioTrack(audioProcessingController = audioProcessingController),
481 ) 461 )
482 462
483 advanceUntilIdle() 463 advanceUntilIdle()
@@ -517,13 +497,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() { @@ -517,13 +497,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
517 497
518 val audioProcessingController = MockAudioProcessingController() 498 val audioProcessingController = MockAudioProcessingController()
519 room.localParticipant.publishAudioTrack( 499 room.localParticipant.publishAudioTrack(
520 - LocalAudioTrack(  
521 - name = "",  
522 - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid),  
523 - options = LocalAudioTrackOptions(),  
524 - audioProcessingController = audioProcessingController,  
525 - dispatcher = coroutineRule.dispatcher,  
526 - ), 500 + track = createMockLocalAudioTrack(audioProcessingController = audioProcessingController),
527 ) 501 )
528 502
529 advanceUntilIdle() 503 advanceUntilIdle()
@@ -18,14 +18,11 @@ package io.livekit.android.room.participant @@ -18,14 +18,11 @@ package io.livekit.android.room.participant
18 18
19 import io.livekit.android.events.ParticipantEvent 19 import io.livekit.android.events.ParticipantEvent
20 import io.livekit.android.events.RoomEvent 20 import io.livekit.android.events.RoomEvent
21 -import io.livekit.android.room.track.LocalAudioTrack  
22 -import io.livekit.android.room.track.LocalAudioTrackOptions  
23 import io.livekit.android.test.MockE2ETest 21 import io.livekit.android.test.MockE2ETest
24 import io.livekit.android.test.assert.assertIsClassList 22 import io.livekit.android.test.assert.assertIsClassList
25 import io.livekit.android.test.events.EventCollector 23 import io.livekit.android.test.events.EventCollector
26 -import io.livekit.android.test.mock.MockAudioProcessingController  
27 -import io.livekit.android.test.mock.MockAudioStreamTrack  
28 import io.livekit.android.test.mock.TestData 24 import io.livekit.android.test.mock.TestData
  25 +import io.livekit.android.test.mock.room.track.createMockLocalAudioTrack
29 import io.livekit.android.util.toOkioByteString 26 import io.livekit.android.util.toOkioByteString
30 import kotlinx.coroutines.ExperimentalCoroutinesApi 27 import kotlinx.coroutines.ExperimentalCoroutinesApi
31 import org.junit.Assert.assertEquals 28 import org.junit.Assert.assertEquals
@@ -43,13 +40,7 @@ class ParticipantMockE2ETest : MockE2ETest() { @@ -43,13 +40,7 @@ class ParticipantMockE2ETest : MockE2ETest() {
43 40
44 // publish track 41 // publish track
45 room.localParticipant.publishAudioTrack( 42 room.localParticipant.publishAudioTrack(
46 - LocalAudioTrack(  
47 - name = "",  
48 - mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid),  
49 - options = LocalAudioTrackOptions(),  
50 - audioProcessingController = MockAudioProcessingController(),  
51 - dispatcher = coroutineRule.dispatcher,  
52 - ), 43 + track = createMockLocalAudioTrack(),
53 ) 44 )
54 45
55 val eventCollector = EventCollector(room.events, coroutineRule.scope) 46 val eventCollector = EventCollector(room.events, coroutineRule.scope)