Skip to content

Commit d5218b1

Browse files
authored
Fix crash when creating audio track for communication mode workaround (#805)
1 parent 4bf585e commit d5218b1

File tree

2 files changed

+56
-26
lines changed

2 files changed

+56
-26
lines changed

.changeset/witty-buttons-lick.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"client-sdk-android": patch
3+
---
4+
5+
Fix crash when creating audio track for communication mode workaround

livekit-android-sdk/src/main/java/io/livekit/android/audio/CommunicationWorkaround.kt

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package io.livekit.android.audio
1818

1919
import android.annotation.SuppressLint
20+
import android.content.Context
21+
import android.content.Context.AUDIO_SERVICE
2022
import android.media.AudioAttributes
2123
import android.media.AudioFormat
2224
import android.media.AudioManager
@@ -83,8 +85,9 @@ constructor() : CommunicationWorkaround {
8385
internal class CommunicationWorkaroundImpl
8486
@Inject
8587
constructor(
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

Comments
 (0)