|
|
|
@ -1,9 +1,6 @@ |
|
|
|
package com.deep_voice.bytedance_speech |
|
|
|
|
|
|
|
import android.content.BroadcastReceiver |
|
|
|
import android.content.Context |
|
|
|
import android.content.Intent |
|
|
|
import android.content.IntentFilter |
|
|
|
import android.media.AudioAttributes |
|
|
|
import android.media.AudioDeviceCallback |
|
|
|
import android.media.AudioDeviceInfo |
|
|
|
@ -11,38 +8,46 @@ import android.media.AudioFormat |
|
|
|
import android.media.AudioManager |
|
|
|
import android.media.AudioTrack |
|
|
|
import android.os.Build |
|
|
|
import android.os.Handler |
|
|
|
import android.os.Looper |
|
|
|
import android.util.Log |
|
|
|
import com.deep_voice.speech.tts.AudioDataListener |
|
|
|
import com.deep_voice.speech.tts.AudioOutputDevice |
|
|
|
|
|
|
|
/** |
|
|
|
* 简化的音频流播放器 |
|
|
|
* 基于位置标记触发播放完成回调 |
|
|
|
* 高性能音频播放器 - 16kHz采样率 |
|
|
|
* 设计要点: |
|
|
|
* • 永远使用 USAGE_MEDIA + MODE_NORMAL 媒体播放通路 |
|
|
|
* • 利用 AudioDeviceCallback 监听设备热插拔 |
|
|
|
* • API 31+ 使用 setPreferredDevice() 精准路由 |
|
|
|
* • 有耳机自动从耳机播放,无耳机自动切到扬声器 |
|
|
|
*/ |
|
|
|
class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
|
class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, AudioDeviceCallback() { |
|
|
|
companion object { |
|
|
|
private const val TAG = "BytedanceAudioPlayer" |
|
|
|
private const val SAMPLE_RATE = 24000 |
|
|
|
private const val SAMPLE_RATE = 16000 // 固定16kHz采样率 |
|
|
|
private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_OUT_MONO |
|
|
|
private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT |
|
|
|
private const val VOLUME_REDUCTION = 0.7f // 降低音量以减少回音 |
|
|
|
} |
|
|
|
|
|
|
|
private val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager |
|
|
|
private var audioTrack: AudioTrack? = null |
|
|
|
private var isFirstData = true // 是否是第一次接收数据 |
|
|
|
private var totalBytesWritten = 0 // 总共写入的字节数 |
|
|
|
private var sessionActive = true // 会话是否活跃 |
|
|
|
private var audioOutputDevice = AudioOutputDevice.DEFAULT // 音频输出设备 |
|
|
|
private var audioManager: AudioManager? = null |
|
|
|
|
|
|
|
// 设备监听相关 |
|
|
|
private var deviceCallback: AudioDeviceCallback? = null |
|
|
|
private var noisyReceiver: BroadcastReceiver? = null |
|
|
|
private var isMonitoringDevices = false |
|
|
|
private var isFirstData = true |
|
|
|
private var totalBytesWritten = 0 |
|
|
|
private var sessionActive = true |
|
|
|
|
|
|
|
// 回调 |
|
|
|
private var onPlayStarted: (() -> Unit)? = null |
|
|
|
private var onPlayCompleted: (() -> Unit)? = null |
|
|
|
|
|
|
|
// 耳机设备类型(优先级顺序) |
|
|
|
private val headsetTypes = intArrayOf( |
|
|
|
AudioDeviceInfo.TYPE_WIRED_HEADSET, |
|
|
|
AudioDeviceInfo.TYPE_WIRED_HEADPHONES, |
|
|
|
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP |
|
|
|
// TYPE_BLUETOOTH_LE 在较新的 API 中才有,这里先不包含 |
|
|
|
) |
|
|
|
|
|
|
|
// 播放位置监听器 |
|
|
|
private val playbackListener = object : AudioTrack.OnPlaybackPositionUpdateListener { |
|
|
|
override fun onMarkerReached(track: AudioTrack) { |
|
|
|
@ -55,10 +60,113 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
|
} |
|
|
|
|
|
|
|
init { |
|
|
|
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager |
|
|
|
initAudioTrack() |
|
|
|
// 注册设备热插拔监听 |
|
|
|
audioManager.registerAudioDeviceCallback(this, Handler(Looper.getMainLooper())) |
|
|
|
buildTrack(selectOutputDevice()) // 首次创建 |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 监听输出设备变化 |
|
|
|
*/ |
|
|
|
override fun onAudioDevicesAdded(addedDevices: Array<AudioDeviceInfo>) { |
|
|
|
Log.d(TAG, "音频设备连接: ${addedDevices.joinToString { "${it.productName}(${it.type})" }}") |
|
|
|
rebuildIfNeeded() |
|
|
|
} |
|
|
|
|
|
|
|
override fun onAudioDevicesRemoved(removedDevices: Array<AudioDeviceInfo>) { |
|
|
|
Log.d(TAG, "音频设备断开: ${removedDevices.joinToString { "${it.productName}(${it.type})" }}") |
|
|
|
rebuildIfNeeded() |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 检查设备变化,必要时重建AudioTrack |
|
|
|
*/ |
|
|
|
private fun rebuildIfNeeded() { |
|
|
|
val newDevice = selectOutputDevice() |
|
|
|
val currentDevice = audioTrack?.preferredDevice |
|
|
|
|
|
|
|
// 设备发生变化时重建 |
|
|
|
if (newDevice?.id != currentDevice?.id) { |
|
|
|
Log.d(TAG, "设备切换: ${currentDevice?.productName} -> ${newDevice?.productName}") |
|
|
|
|
|
|
|
// 保存当前状态 |
|
|
|
val wasPlaying = audioTrack?.playState == AudioTrack.PLAYSTATE_PLAYING |
|
|
|
val savedFirstData = isFirstData |
|
|
|
val savedTotalBytes = totalBytesWritten |
|
|
|
val savedSessionActive = sessionActive |
|
|
|
|
|
|
|
// 重建AudioTrack |
|
|
|
buildTrack(newDevice) |
|
|
|
|
|
|
|
// 恢复状态 |
|
|
|
if (wasPlaying) { |
|
|
|
isFirstData = savedFirstData |
|
|
|
totalBytesWritten = savedTotalBytes |
|
|
|
sessionActive = savedSessionActive |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 选择当前优先输出设备(耳机优先) |
|
|
|
*/ |
|
|
|
private fun selectOutputDevice(): AudioDeviceInfo? { |
|
|
|
return audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS) |
|
|
|
.firstOrNull { it.type in headsetTypes } |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 构建16kHz单声道AudioTrack |
|
|
|
*/ |
|
|
|
private fun buildTrack(device: AudioDeviceInfo?) { |
|
|
|
audioTrack?.release() |
|
|
|
|
|
|
|
val minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT) |
|
|
|
|
|
|
|
audioTrack = AudioTrack.Builder() |
|
|
|
.setAudioAttributes( |
|
|
|
AudioAttributes.Builder() |
|
|
|
.setUsage(AudioAttributes.USAGE_MEDIA) // 统一媒体通路 |
|
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) |
|
|
|
.build() |
|
|
|
) |
|
|
|
.setAudioFormat( |
|
|
|
AudioFormat.Builder() |
|
|
|
.setSampleRate(SAMPLE_RATE) |
|
|
|
.setEncoding(AUDIO_FORMAT) |
|
|
|
.setChannelMask(CHANNEL_CONFIG) |
|
|
|
.build() |
|
|
|
) |
|
|
|
.setTransferMode(AudioTrack.MODE_STREAM) |
|
|
|
.setBufferSizeInBytes(minBufferSize * 2) // 小缓冲区保证低延迟 |
|
|
|
.apply { |
|
|
|
// 低延迟模式 |
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|
|
|
setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY) |
|
|
|
} |
|
|
|
} |
|
|
|
.build().also { track -> |
|
|
|
// API 31+ 精准路由到指定设备 |
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && device != null) { |
|
|
|
track.preferredDevice = device |
|
|
|
Log.d(TAG, "设置首选设备: ${device.productName} (${device.type})") |
|
|
|
} |
|
|
|
|
|
|
|
// 设置播放监听器 |
|
|
|
track.setPlaybackPositionUpdateListener(playbackListener) |
|
|
|
|
|
|
|
// 开始播放 |
|
|
|
track.play() |
|
|
|
} |
|
|
|
|
|
|
|
// 配置音频管理器 - 统一使用 MODE_NORMAL |
|
|
|
audioManager.mode = AudioManager.MODE_NORMAL |
|
|
|
// 切换扬声器开关仅在无耳机时打开 |
|
|
|
audioManager.isSpeakerphoneOn = (device == null) |
|
|
|
|
|
|
|
val deviceName = device?.productName ?: "扬声器" |
|
|
|
Log.d(TAG, "AudioTrack已创建 - 输出设备: $deviceName, 采样率: ${SAMPLE_RATE}Hz") |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 开始新的播放会话 |
|
|
|
@ -74,9 +182,7 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
|
track.flush() |
|
|
|
track.play() |
|
|
|
} |
|
|
|
} ?: initAudioTrack() |
|
|
|
|
|
|
|
startDeviceMonitoring() |
|
|
|
} ?: buildTrack(selectOutputDevice()) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -94,9 +200,9 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
|
track.setNotificationMarkerPosition(totalFrames) |
|
|
|
} catch (e: Exception) { |
|
|
|
val durationMs = (totalFrames * 1000L) / SAMPLE_RATE |
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({ |
|
|
|
Handler(Looper.getMainLooper()).postDelayed({ |
|
|
|
onPlayCompleted?.invoke() |
|
|
|
}, durationMs + 500) |
|
|
|
}, durationMs + 100) // 更短的延迟 |
|
|
|
} |
|
|
|
} else { |
|
|
|
onPlayCompleted?.invoke() |
|
|
|
@ -127,7 +233,7 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
// 忽略写入失败 |
|
|
|
Log.w(TAG, "音频数据写入失败: ${e.message}") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -144,8 +250,6 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
stopDeviceMonitoring() |
|
|
|
|
|
|
|
if (!isFirstData) { |
|
|
|
onPlayCompleted?.invoke() |
|
|
|
} |
|
|
|
@ -158,7 +262,7 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
|
stop() |
|
|
|
audioTrack?.release() |
|
|
|
audioTrack = null |
|
|
|
stopDeviceMonitoring() |
|
|
|
audioManager.unregisterAudioDeviceCallback(this) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -190,283 +294,31 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 设置音频输出设备 |
|
|
|
* 设置音频输出设备(兼容接口) |
|
|
|
* 注意:此实现依赖自动设备切换,手动设置可能被热插拔覆盖 |
|
|
|
*/ |
|
|
|
fun setAudioOutputDevice(device: AudioOutputDevice) { |
|
|
|
if (audioOutputDevice != device) { |
|
|
|
audioOutputDevice = device |
|
|
|
|
|
|
|
// 应用新的音频路由设置 |
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
|
|
|
// API 31+ 可以动态更改设备 |
|
|
|
setPreferredDeviceForTrack() |
|
|
|
} else { |
|
|
|
// API 30 及以下需要重新配置 |
|
|
|
configureAudioRouting() |
|
|
|
} |
|
|
|
} |
|
|
|
// 本实现专注于自动设备切换 |
|
|
|
// 手动设置会在下次设备变化时被自动逻辑覆盖 |
|
|
|
Log.d(TAG, "setAudioOutputDevice被调用,但本实现依赖自动设备切换") |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 初始化 AudioTrack |
|
|
|
* 获取当前路由设备信息(用于调试) |
|
|
|
*/ |
|
|
|
private fun initAudioTrack() { |
|
|
|
audioTrack?.release() |
|
|
|
|
|
|
|
val minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT) |
|
|
|
val bufferSize = minBufferSize * 2 // 使用较小的缓冲区以降低延迟 |
|
|
|
|
|
|
|
audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|
|
|
val builder = AudioTrack.Builder() |
|
|
|
.setAudioAttributes( |
|
|
|
AudioAttributes.Builder() |
|
|
|
.setUsage( |
|
|
|
// 扬声器模式下默认使用语音通信模式以启用回声消除 |
|
|
|
if (audioOutputDevice == AudioOutputDevice.SPEAKER) |
|
|
|
AudioAttributes.USAGE_VOICE_COMMUNICATION |
|
|
|
else AudioAttributes.USAGE_MEDIA |
|
|
|
) |
|
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) |
|
|
|
.build() |
|
|
|
) |
|
|
|
.setAudioFormat( |
|
|
|
AudioFormat.Builder() |
|
|
|
.setEncoding(AUDIO_FORMAT) |
|
|
|
.setSampleRate(SAMPLE_RATE) |
|
|
|
.setChannelMask(CHANNEL_CONFIG) |
|
|
|
.build() |
|
|
|
) |
|
|
|
.setBufferSizeInBytes(bufferSize) |
|
|
|
.setTransferMode(AudioTrack.MODE_STREAM) |
|
|
|
|
|
|
|
// API 26+ 设置低延迟模式 |
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|
|
|
builder.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY) |
|
|
|
} |
|
|
|
|
|
|
|
builder.build() |
|
|
|
fun getCurrentDevice(): String { |
|
|
|
val routedDevice = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
|
|
|
audioTrack?.routedDevice |
|
|
|
} else { |
|
|
|
@Suppress("DEPRECATION") |
|
|
|
AudioTrack( |
|
|
|
AudioManager.STREAM_MUSIC, |
|
|
|
SAMPLE_RATE, |
|
|
|
CHANNEL_CONFIG, |
|
|
|
AUDIO_FORMAT, |
|
|
|
bufferSize, |
|
|
|
AudioTrack.MODE_STREAM |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
audioTrack?.setPlaybackPositionUpdateListener(playbackListener) |
|
|
|
|
|
|
|
// 设置音量以减少回音 |
|
|
|
audioTrack?.setVolume(VOLUME_REDUCTION) |
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
|
|
|
setPreferredDeviceForTrack() |
|
|
|
} else { |
|
|
|
configureAudioRouting() |
|
|
|
} |
|
|
|
|
|
|
|
audioTrack?.play() |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* API 31+ 使用 setPreferredDevice 设置音频输出设备 |
|
|
|
*/ |
|
|
|
private fun setPreferredDeviceForTrack() { |
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return |
|
|
|
|
|
|
|
audioManager?.let { manager -> |
|
|
|
audioTrack?.let { track -> |
|
|
|
when (audioOutputDevice) { |
|
|
|
AudioOutputDevice.DEFAULT -> { |
|
|
|
track.setPreferredDevice(null) |
|
|
|
manager.isSpeakerphoneOn = false |
|
|
|
// 默认模式下使用通信模式以获得更好的音频处理 |
|
|
|
manager.mode = AudioManager.MODE_IN_COMMUNICATION |
|
|
|
} |
|
|
|
|
|
|
|
AudioOutputDevice.SPEAKER -> { |
|
|
|
val speaker = manager.getDevices(AudioManager.GET_DEVICES_OUTPUTS) |
|
|
|
.firstOrNull { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER } |
|
|
|
speaker?.let { track.setPreferredDevice(it) } |
|
|
|
// 扬声器模式下始终启用通信模式以获得回声消除 |
|
|
|
manager.mode = AudioManager.MODE_IN_COMMUNICATION |
|
|
|
manager.isSpeakerphoneOn = true |
|
|
|
} |
|
|
|
|
|
|
|
AudioOutputDevice.HEADPHONES -> { |
|
|
|
val headphones = manager.getDevices(AudioManager.GET_DEVICES_OUTPUTS) |
|
|
|
.firstOrNull { device -> |
|
|
|
device.type == AudioDeviceInfo.TYPE_WIRED_HEADSET || |
|
|
|
device.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES || |
|
|
|
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP || |
|
|
|
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO |
|
|
|
} |
|
|
|
headphones?.let { track.setPreferredDevice(it) } |
|
|
|
?: track.setPreferredDevice(null) |
|
|
|
// 耳机模式下可以使用普通模式 |
|
|
|
manager.mode = AudioManager.MODE_NORMAL |
|
|
|
manager.isSpeakerphoneOn = false |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 配置音频路由(API 30 及以下) |
|
|
|
*/ |
|
|
|
private fun configureAudioRouting() { |
|
|
|
audioManager?.let { manager -> |
|
|
|
when (audioOutputDevice) { |
|
|
|
AudioOutputDevice.DEFAULT -> { |
|
|
|
// 默认模式下使用通信模式以获得更好的音频处理 |
|
|
|
manager.mode = AudioManager.MODE_IN_COMMUNICATION |
|
|
|
manager.isSpeakerphoneOn = false |
|
|
|
} |
|
|
|
|
|
|
|
AudioOutputDevice.SPEAKER -> { |
|
|
|
// 扬声器模式下始终启用通信模式以获得回声消除 |
|
|
|
manager.mode = AudioManager.MODE_IN_COMMUNICATION |
|
|
|
manager.isSpeakerphoneOn = true |
|
|
|
} |
|
|
|
|
|
|
|
AudioOutputDevice.HEADPHONES -> { |
|
|
|
// 耳机模式下可以使用普通模式 |
|
|
|
manager.mode = AudioManager.MODE_NORMAL |
|
|
|
manager.isSpeakerphoneOn = false |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 开始监听设备变化 |
|
|
|
*/ |
|
|
|
private fun startDeviceMonitoring() { |
|
|
|
if (isMonitoringDevices) return |
|
|
|
|
|
|
|
// API 23+ 使用 AudioDeviceCallback |
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|
|
|
deviceCallback = object : AudioDeviceCallback() { |
|
|
|
override fun onAudioDevicesAdded(addedDevices: Array<AudioDeviceInfo>) { |
|
|
|
super.onAudioDevicesAdded(addedDevices) |
|
|
|
handleDeviceAdded(addedDevices) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onAudioDevicesRemoved(removedDevices: Array<AudioDeviceInfo>) { |
|
|
|
super.onAudioDevicesRemoved(removedDevices) |
|
|
|
handleDeviceRemoved(removedDevices) |
|
|
|
} |
|
|
|
} |
|
|
|
audioManager?.registerAudioDeviceCallback(deviceCallback, null) |
|
|
|
null |
|
|
|
} |
|
|
|
|
|
|
|
// 所有版本都监听 NOISY 广播(耳机拔出) |
|
|
|
noisyReceiver = object : BroadcastReceiver() { |
|
|
|
override fun onReceive(context: Context, intent: Intent) { |
|
|
|
if (intent.action == AudioManager.ACTION_AUDIO_BECOMING_NOISY) { |
|
|
|
handleNoisyAudioEvent() |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
context.registerReceiver(noisyReceiver, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) |
|
|
|
|
|
|
|
isMonitoringDevices = true |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 停止监听设备变化 |
|
|
|
*/ |
|
|
|
private fun stopDeviceMonitoring() { |
|
|
|
if (!isMonitoringDevices) return |
|
|
|
|
|
|
|
// 注销 AudioDeviceCallback |
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|
|
|
deviceCallback?.let { |
|
|
|
audioManager?.unregisterAudioDeviceCallback(it) |
|
|
|
} |
|
|
|
deviceCallback = null |
|
|
|
} |
|
|
|
|
|
|
|
// 注销广播接收器 |
|
|
|
noisyReceiver?.let { |
|
|
|
try { |
|
|
|
context.unregisterReceiver(it) |
|
|
|
} catch (e: Exception) { |
|
|
|
// 忽略已注销的异常 |
|
|
|
} |
|
|
|
} |
|
|
|
noisyReceiver = null |
|
|
|
|
|
|
|
isMonitoringDevices = false |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 处理设备添加 |
|
|
|
*/ |
|
|
|
private fun handleDeviceAdded(devices: Array<AudioDeviceInfo>) { |
|
|
|
val hasHeadphones = devices.any { device -> |
|
|
|
device.type == AudioDeviceInfo.TYPE_WIRED_HEADSET || |
|
|
|
device.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES || |
|
|
|
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP || |
|
|
|
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO |
|
|
|
} |
|
|
|
|
|
|
|
if (hasHeadphones) { |
|
|
|
switchToDefaultMode() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 处理设备移除 |
|
|
|
*/ |
|
|
|
private fun handleDeviceRemoved(devices: Array<AudioDeviceInfo>) { |
|
|
|
val hasHeadphones = devices.any { device -> |
|
|
|
device.type == AudioDeviceInfo.TYPE_WIRED_HEADSET || |
|
|
|
device.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES || |
|
|
|
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP || |
|
|
|
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO |
|
|
|
} |
|
|
|
|
|
|
|
if (hasHeadphones) { |
|
|
|
switchToSpeakerMode() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 处理音频变得嘈杂事件(通常是耳机拔出) |
|
|
|
*/ |
|
|
|
private fun handleNoisyAudioEvent() { |
|
|
|
switchToSpeakerMode() |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 切换到 DEFAULT 模式 |
|
|
|
*/ |
|
|
|
private fun switchToDefaultMode() { |
|
|
|
audioOutputDevice = AudioOutputDevice.DEFAULT |
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
|
|
|
setPreferredDeviceForTrack() |
|
|
|
val preferredDevice = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
|
|
|
audioTrack?.preferredDevice |
|
|
|
} else { |
|
|
|
configureAudioRouting() |
|
|
|
null |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 切换到扬声器模式 |
|
|
|
*/ |
|
|
|
private fun switchToSpeakerMode() { |
|
|
|
audioOutputDevice = AudioOutputDevice.SPEAKER |
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
|
|
|
setPreferredDeviceForTrack() |
|
|
|
} else { |
|
|
|
configureAudioRouting() |
|
|
|
} |
|
|
|
return "路由设备: ${routedDevice?.productName}, 首选设备: ${preferredDevice?.productName}" |
|
|
|
} |
|
|
|
} |