|
|
@ -1,12 +1,16 @@ |
|
|
package com.deep_voice.bytedance_speech |
|
|
package com.deep_voice.bytedance_speech |
|
|
|
|
|
|
|
|
|
|
|
import android.content.BroadcastReceiver |
|
|
import android.content.Context |
|
|
import android.content.Context |
|
|
|
|
|
import android.content.Intent |
|
|
|
|
|
import android.content.IntentFilter |
|
|
import android.media.AudioAttributes |
|
|
import android.media.AudioAttributes |
|
|
|
|
|
import android.media.AudioDeviceCallback |
|
|
|
|
|
import android.media.AudioDeviceInfo |
|
|
import android.media.AudioFormat |
|
|
import android.media.AudioFormat |
|
|
import android.media.AudioManager |
|
|
import android.media.AudioManager |
|
|
import android.media.AudioTrack |
|
|
import android.media.AudioTrack |
|
|
import android.os.Build |
|
|
import android.os.Build |
|
|
import android.util.Log |
|
|
|
|
|
import com.deep_voice.speech.tts.AudioDataListener |
|
|
import com.deep_voice.speech.tts.AudioDataListener |
|
|
import com.deep_voice.speech.tts.AudioOutputDevice |
|
|
import com.deep_voice.speech.tts.AudioOutputDevice |
|
|
|
|
|
|
|
|
@ -29,6 +33,11 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
private var audioOutputDevice = AudioOutputDevice.DEFAULT // 音频输出设备 |
|
|
private var audioOutputDevice = AudioOutputDevice.DEFAULT // 音频输出设备 |
|
|
private var audioManager: AudioManager? = null |
|
|
private var audioManager: AudioManager? = null |
|
|
|
|
|
|
|
|
|
|
|
// 设备监听相关 |
|
|
|
|
|
private var deviceCallback: AudioDeviceCallback? = null |
|
|
|
|
|
private var noisyReceiver: BroadcastReceiver? = null |
|
|
|
|
|
private var isMonitoringDevices = false |
|
|
|
|
|
|
|
|
// 回调 |
|
|
// 回调 |
|
|
private var onPlayStarted: (() -> Unit)? = null |
|
|
private var onPlayStarted: (() -> Unit)? = null |
|
|
private var onPlayCompleted: (() -> Unit)? = null |
|
|
private var onPlayCompleted: (() -> Unit)? = null |
|
|
@ -36,7 +45,6 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
// 播放位置监听器 |
|
|
// 播放位置监听器 |
|
|
private val playbackListener = object : AudioTrack.OnPlaybackPositionUpdateListener { |
|
|
private val playbackListener = object : AudioTrack.OnPlaybackPositionUpdateListener { |
|
|
override fun onMarkerReached(track: AudioTrack) { |
|
|
override fun onMarkerReached(track: AudioTrack) { |
|
|
Log.d(TAG, "播放到达标记位置: ${track.playbackHeadPosition}") |
|
|
|
|
|
onPlayCompleted?.invoke() |
|
|
onPlayCompleted?.invoke() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -46,7 +54,6 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
init { |
|
|
init { |
|
|
Log.d(TAG, "BytedanceAudioPlayer 初始化") |
|
|
|
|
|
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager |
|
|
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager |
|
|
initAudioTrack() |
|
|
initAudioTrack() |
|
|
} |
|
|
} |
|
|
@ -56,24 +63,19 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
* 开始新的播放会话 |
|
|
* 开始新的播放会话 |
|
|
*/ |
|
|
*/ |
|
|
fun startSession() { |
|
|
fun startSession() { |
|
|
Log.d(TAG, "开始新会话") |
|
|
|
|
|
|
|
|
|
|
|
// 重置状态 |
|
|
|
|
|
isFirstData = true |
|
|
isFirstData = true |
|
|
totalBytesWritten = 0 |
|
|
totalBytesWritten = 0 |
|
|
sessionActive = true |
|
|
sessionActive = true |
|
|
|
|
|
|
|
|
// 重置 AudioTrack |
|
|
|
|
|
audioTrack?.let { track -> |
|
|
audioTrack?.let { track -> |
|
|
if (track.state == AudioTrack.STATE_INITIALIZED) { |
|
|
if (track.state == AudioTrack.STATE_INITIALIZED) { |
|
|
// 停止并清空缓冲区 |
|
|
|
|
|
track.pause() |
|
|
track.pause() |
|
|
track.flush() |
|
|
track.flush() |
|
|
// 重新开始播放 |
|
|
|
|
|
track.play() |
|
|
track.play() |
|
|
Log.d(TAG, "AudioTrack 已重置") |
|
|
|
|
|
} |
|
|
} |
|
|
} ?: initAudioTrack() // 如果没有初始化,则初始化 |
|
|
} ?: initAudioTrack() |
|
|
|
|
|
|
|
|
|
|
|
startDeviceMonitoring() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -82,28 +84,20 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
fun endSession() { |
|
|
fun endSession() { |
|
|
if (!sessionActive) return |
|
|
if (!sessionActive) return |
|
|
|
|
|
|
|
|
Log.d(TAG, "数据流结束,总共写入字节数: $totalBytesWritten") |
|
|
|
|
|
sessionActive = false |
|
|
sessionActive = false |
|
|
|
|
|
|
|
|
audioTrack?.let { track -> |
|
|
audioTrack?.let { track -> |
|
|
if (totalBytesWritten > 0) { |
|
|
if (totalBytesWritten > 0) { |
|
|
// 计算总帧数(16-bit 单声道,每帧2字节) |
|
|
|
|
|
val totalFrames = totalBytesWritten / 2 |
|
|
val totalFrames = totalBytesWritten / 2 |
|
|
|
|
|
|
|
|
// 设置标记位置 |
|
|
|
|
|
try { |
|
|
try { |
|
|
track.setNotificationMarkerPosition(totalFrames) |
|
|
track.setNotificationMarkerPosition(totalFrames) |
|
|
Log.d(TAG, "设置播放完成标记位置: $totalFrames") |
|
|
|
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e(TAG, "设置标记失败: ${e.message}") |
|
|
|
|
|
// 设置失败时,使用延迟触发作为后备 |
|
|
|
|
|
val durationMs = (totalFrames * 1000L) / SAMPLE_RATE |
|
|
val durationMs = (totalFrames * 1000L) / SAMPLE_RATE |
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({ |
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({ |
|
|
onPlayCompleted?.invoke() |
|
|
onPlayCompleted?.invoke() |
|
|
}, durationMs + 500) |
|
|
}, durationMs + 500) |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
// 没有数据,直接触发完成 |
|
|
|
|
|
onPlayCompleted?.invoke() |
|
|
onPlayCompleted?.invoke() |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -113,37 +107,26 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
* 接收音频数据 |
|
|
* 接收音频数据 |
|
|
*/ |
|
|
*/ |
|
|
override fun onAudioData(data: ByteArray) { |
|
|
override fun onAudioData(data: ByteArray) { |
|
|
if (data.isEmpty()) return |
|
|
if (data.isEmpty() || !sessionActive) return |
|
|
if (!sessionActive) { |
|
|
if (audioTrack?.state != AudioTrack.STATE_INITIALIZED) return |
|
|
Log.w(TAG, "收到音频数据但会话未激活,忽略数据") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
if (audioTrack == null || audioTrack?.state != AudioTrack.STATE_INITIALIZED) { |
|
|
|
|
|
Log.w(TAG, "收到音频数据但 AudioTrack 未初始化或状态不正确,忽略数据") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
try { |
|
|
try { |
|
|
audioTrack?.let { track -> |
|
|
audioTrack?.let { track -> |
|
|
if (track.state == AudioTrack.STATE_INITIALIZED) { |
|
|
val bytesWritten = track.write(data, 0, data.size) |
|
|
val bytesWritten = track.write(data, 0, data.size) |
|
|
|
|
|
|
|
|
if (bytesWritten > 0) { |
|
|
|
|
|
if (isFirstData) { |
|
|
|
|
|
isFirstData = false |
|
|
|
|
|
onPlayStarted?.invoke() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (bytesWritten > 0) { |
|
|
if (sessionActive) { |
|
|
// 第一次写入数据时自动触发开始回调 |
|
|
totalBytesWritten += bytesWritten |
|
|
if (isFirstData) { |
|
|
|
|
|
isFirstData = false |
|
|
|
|
|
onPlayStarted?.invoke() |
|
|
|
|
|
Log.d(TAG, "播放开始") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 累计写入字节数 |
|
|
|
|
|
if (sessionActive) { |
|
|
|
|
|
totalBytesWritten += bytesWritten |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e(TAG, "写入音频数据失败: ${e.message}") |
|
|
// 忽略写入失败 |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -151,8 +134,6 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
* 停止播放 |
|
|
* 停止播放 |
|
|
*/ |
|
|
*/ |
|
|
fun stop() { |
|
|
fun stop() { |
|
|
Log.d(TAG, "停止播放") |
|
|
|
|
|
|
|
|
|
|
|
sessionActive = false |
|
|
sessionActive = false |
|
|
|
|
|
|
|
|
audioTrack?.let { track -> |
|
|
audioTrack?.let { track -> |
|
|
@ -162,7 +143,8 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 如果已经开始播放,触发完成回调 |
|
|
stopDeviceMonitoring() |
|
|
|
|
|
|
|
|
if (!isFirstData) { |
|
|
if (!isFirstData) { |
|
|
onPlayCompleted?.invoke() |
|
|
onPlayCompleted?.invoke() |
|
|
} |
|
|
} |
|
|
@ -172,12 +154,10 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
* 释放资源 |
|
|
* 释放资源 |
|
|
*/ |
|
|
*/ |
|
|
fun release() { |
|
|
fun release() { |
|
|
Log.d(TAG, "释放资源") |
|
|
|
|
|
|
|
|
|
|
|
stop() |
|
|
stop() |
|
|
|
|
|
|
|
|
audioTrack?.release() |
|
|
audioTrack?.release() |
|
|
audioTrack = null |
|
|
audioTrack = null |
|
|
|
|
|
stopDeviceMonitoring() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -214,13 +194,14 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
fun setAudioOutputDevice(device: AudioOutputDevice) { |
|
|
fun setAudioOutputDevice(device: AudioOutputDevice) { |
|
|
if (audioOutputDevice != device) { |
|
|
if (audioOutputDevice != device) { |
|
|
audioOutputDevice = device |
|
|
audioOutputDevice = device |
|
|
// 如果AudioTrack已初始化,需要重新创建以应用新的输出设备设置 |
|
|
|
|
|
if (audioTrack != null) { |
|
|
// 应用新的音频路由设置 |
|
|
val wasPlaying = isPlaying() |
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
|
|
initAudioTrack() |
|
|
// API 31+ 可以动态更改设备 |
|
|
if (wasPlaying) { |
|
|
setPreferredDeviceForTrack() |
|
|
audioTrack?.play() |
|
|
} else { |
|
|
} |
|
|
// API 30 及以下需要重新配置 |
|
|
|
|
|
configureAudioRouting() |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -229,43 +210,19 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
* 初始化 AudioTrack |
|
|
* 初始化 AudioTrack |
|
|
*/ |
|
|
*/ |
|
|
private fun initAudioTrack() { |
|
|
private fun initAudioTrack() { |
|
|
// 释放旧实例 |
|
|
|
|
|
audioTrack?.release() |
|
|
audioTrack?.release() |
|
|
|
|
|
|
|
|
// 计算缓冲区大小 |
|
|
|
|
|
val minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT) |
|
|
val minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT) |
|
|
val bufferSize = minBufferSize * 2 |
|
|
val bufferSize = minBufferSize * 2 |
|
|
|
|
|
|
|
|
// 根据输出设备配置AudioAttributes |
|
|
audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|
|
val audioAttributes = when (audioOutputDevice) { |
|
|
AudioTrack.Builder() |
|
|
AudioOutputDevice.EARPIECE -> { |
|
|
.setAudioAttributes( |
|
|
// 听筒模式 |
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|
|
|
|
|
AudioAttributes.Builder() |
|
|
|
|
|
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) |
|
|
|
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) |
|
|
|
|
|
.build() |
|
|
|
|
|
} else { |
|
|
|
|
|
null |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
else -> { |
|
|
|
|
|
// 默认、耳机、扬声器模式 |
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|
|
|
|
|
AudioAttributes.Builder() |
|
|
AudioAttributes.Builder() |
|
|
.setUsage(AudioAttributes.USAGE_MEDIA) |
|
|
.setUsage(AudioAttributes.USAGE_MEDIA) |
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) |
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) |
|
|
.build() |
|
|
.build() |
|
|
} else { |
|
|
) |
|
|
null |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 创建 AudioTrack |
|
|
|
|
|
audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && audioAttributes != null) { |
|
|
|
|
|
AudioTrack.Builder() |
|
|
|
|
|
.setAudioAttributes(audioAttributes) |
|
|
|
|
|
.setAudioFormat( |
|
|
.setAudioFormat( |
|
|
AudioFormat.Builder() |
|
|
AudioFormat.Builder() |
|
|
.setEncoding(AUDIO_FORMAT) |
|
|
.setEncoding(AUDIO_FORMAT) |
|
|
@ -278,12 +235,8 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
.build() |
|
|
.build() |
|
|
} else { |
|
|
} else { |
|
|
@Suppress("DEPRECATION") |
|
|
@Suppress("DEPRECATION") |
|
|
val streamType = when (audioOutputDevice) { |
|
|
|
|
|
AudioOutputDevice.EARPIECE -> AudioManager.STREAM_VOICE_CALL |
|
|
|
|
|
else -> AudioManager.STREAM_MUSIC |
|
|
|
|
|
} |
|
|
|
|
|
AudioTrack( |
|
|
AudioTrack( |
|
|
streamType, |
|
|
AudioManager.STREAM_MUSIC, |
|
|
SAMPLE_RATE, |
|
|
SAMPLE_RATE, |
|
|
CHANNEL_CONFIG, |
|
|
CHANNEL_CONFIG, |
|
|
AUDIO_FORMAT, |
|
|
AUDIO_FORMAT, |
|
|
@ -292,46 +245,202 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener { |
|
|
) |
|
|
) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 设置播放位置监听器 |
|
|
|
|
|
audioTrack?.setPlaybackPositionUpdateListener(playbackListener) |
|
|
audioTrack?.setPlaybackPositionUpdateListener(playbackListener) |
|
|
|
|
|
|
|
|
// 配置音频路由 |
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
|
|
configureAudioRouting() |
|
|
setPreferredDeviceForTrack() |
|
|
|
|
|
} else { |
|
|
|
|
|
configureAudioRouting() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 开始播放 |
|
|
|
|
|
audioTrack?.play() |
|
|
audioTrack?.play() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* API 31+ 使用 setPreferredDevice 设置音频输出设备 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun setPreferredDeviceForTrack() { |
|
|
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return |
|
|
|
|
|
|
|
|
Log.d(TAG, "AudioTrack 初始化成功") |
|
|
audioManager?.let { manager -> |
|
|
|
|
|
audioTrack?.let { track -> |
|
|
|
|
|
when (audioOutputDevice) { |
|
|
|
|
|
AudioOutputDevice.DEFAULT -> { |
|
|
|
|
|
track.setPreferredDevice(null) |
|
|
|
|
|
manager.isSpeakerphoneOn = false |
|
|
|
|
|
manager.mode = AudioManager.MODE_NORMAL |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
AudioOutputDevice.SPEAKER -> { |
|
|
|
|
|
val speaker = manager.getDevices(AudioManager.GET_DEVICES_OUTPUTS) |
|
|
|
|
|
.firstOrNull { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER } |
|
|
|
|
|
speaker?.let { track.setPreferredDevice(it) } |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 配置音频路由 |
|
|
* 配置音频路由(API 30 及以下) |
|
|
*/ |
|
|
*/ |
|
|
private fun configureAudioRouting() { |
|
|
private fun configureAudioRouting() { |
|
|
audioManager?.let { manager -> |
|
|
audioManager?.let { manager -> |
|
|
when (audioOutputDevice) { |
|
|
when (audioOutputDevice) { |
|
|
AudioOutputDevice.DEFAULT -> { |
|
|
AudioOutputDevice.DEFAULT -> { |
|
|
// 默认模式:系统自动选择 |
|
|
|
|
|
manager.mode = AudioManager.MODE_NORMAL |
|
|
manager.mode = AudioManager.MODE_NORMAL |
|
|
manager.isSpeakerphoneOn = false |
|
|
manager.isSpeakerphoneOn = false |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
AudioOutputDevice.SPEAKER -> { |
|
|
AudioOutputDevice.SPEAKER -> { |
|
|
// 强制使用扬声器 |
|
|
|
|
|
manager.mode = AudioManager.MODE_NORMAL |
|
|
manager.mode = AudioManager.MODE_NORMAL |
|
|
manager.isSpeakerphoneOn = true |
|
|
manager.isSpeakerphoneOn = true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
AudioOutputDevice.HEADPHONES -> { |
|
|
AudioOutputDevice.HEADPHONES -> { |
|
|
// 强制使用耳机(如果已连接) |
|
|
|
|
|
manager.mode = AudioManager.MODE_NORMAL |
|
|
manager.mode = AudioManager.MODE_NORMAL |
|
|
manager.isSpeakerphoneOn = false |
|
|
manager.isSpeakerphoneOn = false |
|
|
// 注意:Android不能强制路由到耳机,只能在耳机已连接时使用 |
|
|
|
|
|
} |
|
|
} |
|
|
AudioOutputDevice.EARPIECE -> { |
|
|
} |
|
|
// 强制使用听筒 |
|
|
} |
|
|
manager.mode = AudioManager.MODE_IN_COMMUNICATION |
|
|
} |
|
|
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) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 所有版本都监听 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() |
|
|
|
|
|
} else { |
|
|
|
|
|
configureAudioRouting() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 切换到扬声器模式 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun switchToSpeakerMode() { |
|
|
|
|
|
audioOutputDevice = AudioOutputDevice.SPEAKER |
|
|
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
|
|
|
|
|
setPreferredDeviceForTrack() |
|
|
|
|
|
} else { |
|
|
|
|
|
configureAudioRouting() |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |