Committed by
GitHub
Move audio handling to background thread to avoid UI freezes. (#715)
正在显示
3 个修改的文件
包含
38 行增加
和
12 行删除
.changeset/fresh-schools-chew.md
0 → 100644
| @@ -21,8 +21,10 @@ import android.media.AudioAttributes | @@ -21,8 +21,10 @@ import android.media.AudioAttributes | ||
| 21 | import android.media.AudioManager | 21 | import android.media.AudioManager |
| 22 | import android.os.Build | 22 | import android.os.Build |
| 23 | import android.os.Handler | 23 | import android.os.Handler |
| 24 | +import android.os.HandlerThread | ||
| 24 | import android.os.Looper | 25 | import android.os.Looper |
| 25 | import com.twilio.audioswitch.* | 26 | import com.twilio.audioswitch.* |
| 27 | +import io.livekit.android.util.LKLog | ||
| 26 | import javax.inject.Inject | 28 | import javax.inject.Inject |
| 27 | import javax.inject.Singleton | 29 | import javax.inject.Singleton |
| 28 | 30 | ||
| @@ -138,13 +140,26 @@ constructor(private val context: Context) : AudioHandler { | @@ -138,13 +140,26 @@ constructor(private val context: Context) : AudioHandler { | ||
| 138 | 140 | ||
| 139 | private var audioSwitch: AbstractAudioSwitch? = null | 141 | private var audioSwitch: AbstractAudioSwitch? = null |
| 140 | 142 | ||
| 141 | - // AudioSwitch is not threadsafe, so all calls should be done on the main thread. | ||
| 142 | - private val handler = Handler(Looper.getMainLooper()) | 143 | + // AudioSwitch is not threadsafe, so all calls should be done through a single thread. |
| 144 | + private var handler: Handler? = null | ||
| 145 | + private var thread: HandlerThread? = null | ||
| 143 | 146 | ||
| 147 | + @Synchronized | ||
| 144 | override fun start() { | 148 | override fun start() { |
| 149 | + if (handler != null || thread != null) { | ||
| 150 | + LKLog.i { "AudioSwitchHandler called start multiple times?" } | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + if (thread == null) { | ||
| 154 | + thread = HandlerThread("AudioSwitchHandlerThread").also { it.start() } | ||
| 155 | + } | ||
| 156 | + if (handler == null) { | ||
| 157 | + handler = Handler(thread!!.looper) | ||
| 158 | + } | ||
| 159 | + | ||
| 145 | if (audioSwitch == null) { | 160 | if (audioSwitch == null) { |
| 146 | - handler.removeCallbacksAndMessages(null) | ||
| 147 | - handler.postAtFrontOfQueue { | 161 | + handler?.removeCallbacksAndMessages(null) |
| 162 | + handler?.postAtFrontOfQueue { | ||
| 148 | val switch = | 163 | val switch = |
| 149 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | 164 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
| 150 | AudioSwitch( | 165 | AudioSwitch( |
| @@ -176,12 +191,17 @@ constructor(private val context: Context) : AudioHandler { | @@ -176,12 +191,17 @@ constructor(private val context: Context) : AudioHandler { | ||
| 176 | } | 191 | } |
| 177 | } | 192 | } |
| 178 | 193 | ||
| 194 | + @Synchronized | ||
| 179 | override fun stop() { | 195 | override fun stop() { |
| 180 | - handler.removeCallbacksAndMessages(null) | ||
| 181 | - handler.postAtFrontOfQueue { | 196 | + handler?.removeCallbacksAndMessages(null) |
| 197 | + handler?.postAtFrontOfQueue { | ||
| 182 | audioSwitch?.stop() | 198 | audioSwitch?.stop() |
| 183 | audioSwitch = null | 199 | audioSwitch = null |
| 184 | } | 200 | } |
| 201 | + thread?.quitSafely() | ||
| 202 | + | ||
| 203 | + handler = null | ||
| 204 | + thread = null | ||
| 185 | } | 205 | } |
| 186 | 206 | ||
| 187 | /** | 207 | /** |
| @@ -199,11 +219,12 @@ constructor(private val context: Context) : AudioHandler { | @@ -199,11 +219,12 @@ constructor(private val context: Context) : AudioHandler { | ||
| 199 | /** | 219 | /** |
| 200 | * Select a specific audio device. | 220 | * Select a specific audio device. |
| 201 | */ | 221 | */ |
| 222 | + @Synchronized | ||
| 202 | fun selectDevice(audioDevice: AudioDevice?) { | 223 | fun selectDevice(audioDevice: AudioDevice?) { |
| 203 | - if (Looper.myLooper() == Looper.getMainLooper()) { | 224 | + if (Looper.myLooper() == handler?.looper) { |
| 204 | audioSwitch?.selectDevice(audioDevice) | 225 | audioSwitch?.selectDevice(audioDevice) |
| 205 | } else { | 226 | } else { |
| 206 | - handler.post { | 227 | + handler?.post { |
| 207 | audioSwitch?.selectDevice(audioDevice) | 228 | audioSwitch?.selectDevice(audioDevice) |
| 208 | } | 229 | } |
| 209 | } | 230 | } |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2024 LiveKit, Inc. | 2 | + * Copyright 2024-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -26,7 +26,7 @@ import androidx.annotation.RequiresApi | @@ -26,7 +26,7 @@ import androidx.annotation.RequiresApi | ||
| 26 | import io.livekit.android.dagger.InjectionNames | 26 | import io.livekit.android.dagger.InjectionNames |
| 27 | import io.livekit.android.util.CloseableCoroutineScope | 27 | import io.livekit.android.util.CloseableCoroutineScope |
| 28 | import io.livekit.android.util.LKLog | 28 | import io.livekit.android.util.LKLog |
| 29 | -import kotlinx.coroutines.MainCoroutineDispatcher | 29 | +import kotlinx.coroutines.CoroutineDispatcher |
| 30 | import kotlinx.coroutines.flow.MutableStateFlow | 30 | import kotlinx.coroutines.flow.MutableStateFlow |
| 31 | import kotlinx.coroutines.flow.collectLatest | 31 | import kotlinx.coroutines.flow.collectLatest |
| 32 | import kotlinx.coroutines.flow.combine | 32 | import kotlinx.coroutines.flow.combine |
| @@ -83,8 +83,8 @@ constructor() : CommunicationWorkaround { | @@ -83,8 +83,8 @@ constructor() : CommunicationWorkaround { | ||
| 83 | internal class CommunicationWorkaroundImpl | 83 | internal class CommunicationWorkaroundImpl |
| 84 | @Inject | 84 | @Inject |
| 85 | constructor( | 85 | constructor( |
| 86 | - @Named(InjectionNames.DISPATCHER_MAIN) | ||
| 87 | - dispatcher: MainCoroutineDispatcher, | 86 | + @Named(InjectionNames.DISPATCHER_IO) |
| 87 | + dispatcher: CoroutineDispatcher, | ||
| 88 | ) : CommunicationWorkaround { | 88 | ) : CommunicationWorkaround { |
| 89 | 89 | ||
| 90 | private val coroutineScope = CloseableCoroutineScope(dispatcher) | 90 | private val coroutineScope = CloseableCoroutineScope(dispatcher) |
-
请 注册 或 登录 后发表评论