1717package io.livekit.android.audio
1818
1919import android.annotation.SuppressLint
20+ import android.content.Context
21+ import android.content.Context.AUDIO_SERVICE
2022import android.media.AudioAttributes
2123import android.media.AudioFormat
2224import android.media.AudioManager
@@ -83,8 +85,9 @@ constructor() : CommunicationWorkaround {
8385internal class CommunicationWorkaroundImpl
8486@Inject
8587constructor (
88+ private val context: Context ,
8689 @Named(InjectionNames .DISPATCHER_IO )
87- dispatcher: CoroutineDispatcher ,
90+ private val dispatcher: CoroutineDispatcher ,
8891) : CommunicationWorkaround {
8992
9093 private val coroutineScope = CloseableCoroutineScope (dispatcher)
@@ -141,32 +144,51 @@ constructor(
141144 }
142145 }
143146
147+ private fun String.toIntOrDefault (default : Int ): Int {
148+ return try {
149+ toInt()
150+ } catch (e: NumberFormatException ) {
151+ default
152+ }
153+ }
154+
144155 @SuppressLint(" Range" )
145- private fun buildAudioTrack (): AudioTrack {
146- val audioSample = ByteBuffer .allocateDirect(getBytesPerSample(AUDIO_FORMAT ) * AUDIO_FRAME_PER_BUFFER )
147-
148- return AudioTrack .Builder ()
149- .setAudioFormat(
150- AudioFormat .Builder ()
151- .setEncoding(AUDIO_FORMAT )
152- .setSampleRate(SAMPLE_RATE )
153- .setChannelMask(AudioFormat .CHANNEL_OUT_MONO )
154- .build(),
155- )
156- .setAudioAttributes(
157- AudioAttributes .Builder ()
158- .setUsage(AudioAttributes .USAGE_VOICE_COMMUNICATION )
159- .setContentType(AudioAttributes .CONTENT_TYPE_SPEECH )
160- .build(),
161- )
162- .setBufferSizeInBytes(audioSample.capacity())
163- .setTransferMode(AudioTrack .MODE_STATIC )
164- .setSessionId(AudioManager .AUDIO_SESSION_ID_GENERATE )
165- .build()
166- .apply {
167- write(audioSample, audioSample.remaining(), AudioTrack .WRITE_BLOCKING )
168- setLoopPoints(0 , AUDIO_FRAME_PER_BUFFER - 1 , - 1 )
169- }
156+ private fun buildAudioTrack (): AudioTrack ? {
157+ try {
158+ // Get preferred audio output settings
159+ val audioManager = context.getSystemService(AUDIO_SERVICE ) as AudioManager
160+ val sampleRate = audioManager.getProperty(AudioManager .PROPERTY_OUTPUT_SAMPLE_RATE ).toIntOrDefault(SAMPLE_RATE )
161+ val framesPerBuffer = audioManager.getProperty(AudioManager .PROPERTY_OUTPUT_FRAMES_PER_BUFFER ).toIntOrDefault(AUDIO_FRAME_PER_BUFFER )
162+
163+ // ByteBuffers are zeroed by default on Android
164+ val audioSample = ByteBuffer .allocateDirect(getBytesPerSample(AUDIO_FORMAT ) * framesPerBuffer)
165+
166+ return AudioTrack .Builder ()
167+ .setAudioFormat(
168+ AudioFormat .Builder ()
169+ .setEncoding(AUDIO_FORMAT )
170+ .setSampleRate(sampleRate)
171+ .setChannelMask(AudioFormat .CHANNEL_OUT_MONO )
172+ .build(),
173+ )
174+ .setAudioAttributes(
175+ AudioAttributes .Builder ()
176+ .setUsage(AudioAttributes .USAGE_VOICE_COMMUNICATION )
177+ .setContentType(AudioAttributes .CONTENT_TYPE_SPEECH )
178+ .build(),
179+ )
180+ .setBufferSizeInBytes(audioSample.capacity())
181+ .setTransferMode(AudioTrack .MODE_STATIC )
182+ .setSessionId(AudioManager .AUDIO_SESSION_ID_GENERATE )
183+ .build()
184+ .apply {
185+ write(audioSample, audioSample.remaining(), AudioTrack .WRITE_BLOCKING )
186+ setLoopPoints(0 , AUDIO_FRAME_PER_BUFFER - 1 , - 1 )
187+ }
188+ } catch (e: Exception ) {
189+ LKLog .w(e) { " Failed to build audio track for communication workaround." }
190+ return null
191+ }
170192 }
171193
172194 private fun playAudioTrackIfNeeded () {
@@ -177,6 +199,9 @@ constructor(
177199 }
178200
179201 val audioTrack = audioTrack ? : buildAudioTrack().also { audioTrack = it }
202+ if (audioTrack == null ) {
203+ return
204+ }
180205 synchronized(audioTrack) {
181206 if (audioTrack.state == AudioTrack .STATE_INITIALIZED ) {
182207 audioTrack.play()
0 commit comments