|
|
@ -100,6 +100,9 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
// 内部音频播放器 |
|
|
// 内部音频播放器 |
|
|
private val audioPlayer = BytedanceAudioPlayer() |
|
|
private val audioPlayer = BytedanceAudioPlayer() |
|
|
|
|
|
|
|
|
|
|
|
// 是否使用内部播放器 |
|
|
|
|
|
private var useInternalPlayer = true |
|
|
|
|
|
|
|
|
// WebSocket连接 |
|
|
// WebSocket连接 |
|
|
private var webSocket: WebSocket? = null |
|
|
private var webSocket: WebSocket? = null |
|
|
private val client: OkHttpClient |
|
|
private val client: OkHttpClient |
|
|
@ -119,9 +122,7 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
private var language: String = "zh-CN" |
|
|
private var language: String = "zh-CN" |
|
|
|
|
|
|
|
|
// 流式处理状态 |
|
|
// 流式处理状态 |
|
|
private var isStreamMode = false |
|
|
|
|
|
private var currentStatus = STATUS_STOPPED |
|
|
private var currentStatus = STATUS_STOPPED |
|
|
private var isSpeaking = false |
|
|
|
|
|
|
|
|
|
|
|
// 连接相关状态 |
|
|
// 连接相关状态 |
|
|
private var connectionAttempts = 0 |
|
|
private var connectionAttempts = 0 |
|
|
@ -155,12 +156,8 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
* 初始化音频播放器回调 |
|
|
* 初始化音频播放器回调 |
|
|
*/ |
|
|
*/ |
|
|
private fun initAudioPlayerCallbacks() { |
|
|
private fun initAudioPlayerCallbacks() { |
|
|
// 添加播放器作为音频数据监听器 |
|
|
|
|
|
addAudioDataListener(audioPlayer) |
|
|
|
|
|
|
|
|
|
|
|
audioPlayer.setOnPlayStarted { |
|
|
audioPlayer.setOnPlayStarted { |
|
|
Log.d(TAG, "播放开始") |
|
|
Log.d(TAG, "播放开始") |
|
|
isSpeaking = true |
|
|
|
|
|
updateStatus(STATUS_SPEAKING) |
|
|
updateStatus(STATUS_SPEAKING) |
|
|
notifyEvent(TtsEventType.SYNTHESIS_STARTED) |
|
|
notifyEvent(TtsEventType.SYNTHESIS_STARTED) |
|
|
} |
|
|
} |
|
|
@ -168,17 +165,18 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
// 设置播放完成回调 |
|
|
// 设置播放完成回调 |
|
|
audioPlayer.setOnPlayCompleted { |
|
|
audioPlayer.setOnPlayCompleted { |
|
|
Log.d(TAG, "播放结束") |
|
|
Log.d(TAG, "播放结束") |
|
|
isSpeaking = false |
|
|
updateStatus(STATUS_READY) |
|
|
updateStatus(if (isStreamMode) STATUS_READY else STATUS_STOPPED) |
|
|
|
|
|
notifyEvent(TtsEventType.SYNTHESIS_COMPLETED) |
|
|
notifyEvent(TtsEventType.SYNTHESIS_COMPLETED) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 设置错误回调 |
|
|
// 设置错误回调 |
|
|
audioPlayer.setOnError { errorMsg -> |
|
|
audioPlayer.setOnError { errorMsg -> |
|
|
isSpeaking = false |
|
|
|
|
|
updateStatus(STATUS_ERROR) |
|
|
updateStatus(STATUS_ERROR) |
|
|
notifyEvent(TtsEventType.ERROR, mapOf("errorCode" to "PLAYER_ERROR", "errorMessage" to errorMsg)) |
|
|
notifyEvent(TtsEventType.ERROR, mapOf("errorCode" to "PLAYER_ERROR", "errorMessage" to errorMsg)) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 根据设置决定是否使用内部播放器 |
|
|
|
|
|
updateInternalPlayerUsage() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -208,16 +206,19 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
*/ |
|
|
*/ |
|
|
override fun stop(): Boolean { |
|
|
override fun stop(): Boolean { |
|
|
try { |
|
|
try { |
|
|
audioPlayer.stop() |
|
|
Log.i(TAG, ">stop()") |
|
|
|
|
|
|
|
|
|
|
|
if (useInternalPlayer) { |
|
|
|
|
|
audioPlayer.stop() |
|
|
|
|
|
} |
|
|
textProcessingJob?.cancel() |
|
|
textProcessingJob?.cancel() |
|
|
|
|
|
|
|
|
launch { |
|
|
|
|
|
if (isSessionStarted) { |
|
|
if (isSessionStarted) { |
|
|
finishSession() |
|
|
finishSession() |
|
|
} |
|
|
|
|
|
isSpeaking = false // 显式设置,因为这是强制停止 |
|
|
|
|
|
updateStatus(STATUS_STOPPED) |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
updateStatus(STATUS_STOPPED) |
|
|
|
|
|
|
|
|
return true |
|
|
return true |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e(TAG, "停止合成失败: ${e.message}") |
|
|
Log.e(TAG, "停止合成失败: ${e.message}") |
|
|
@ -277,7 +278,6 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
|
|
|
|
|
|
isConnected = false |
|
|
isConnected = false |
|
|
isSessionStarted = false |
|
|
isSessionStarted = false |
|
|
isSpeaking = false |
|
|
|
|
|
|
|
|
|
|
|
updateStatus(STATUS_STOPPED) |
|
|
updateStatus(STATUS_STOPPED) |
|
|
startTextProcessing() |
|
|
startTextProcessing() |
|
|
@ -304,29 +304,9 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
* 单次播放(非流式) |
|
|
* 单次播放(非流式) |
|
|
*/ |
|
|
*/ |
|
|
override fun speakOnce(text: String): Boolean { |
|
|
override fun speakOnce(text: String): Boolean { |
|
|
try { |
|
|
// 未实现:只支持流式模式 |
|
|
if (text.isEmpty()) { |
|
|
Log.w(TAG, "speakOnce未实现,请使用speakStream") |
|
|
return false |
|
|
return false |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
isStreamMode = false |
|
|
|
|
|
|
|
|
|
|
|
launch { |
|
|
|
|
|
if (!ensureSessionReady()) { |
|
|
|
|
|
notifyEvent(TtsEventType.ERROR, mapOf("errorCode" to "SESSION_ERROR", |
|
|
|
|
|
"errorMessage" to "无法建立会话")) |
|
|
|
|
|
return@launch |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
sendTextRequest(text) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return true |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(TAG, "语音合成失败: ${e.message}") |
|
|
|
|
|
updateStatus(STATUS_ERROR) |
|
|
|
|
|
return false |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -337,7 +317,6 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
if (text.isEmpty()) { |
|
|
if (text.isEmpty()) { |
|
|
return true |
|
|
return true |
|
|
} |
|
|
} |
|
|
isStreamMode = true |
|
|
|
|
|
|
|
|
|
|
|
// 确保会话已就绪 |
|
|
// 确保会话已就绪 |
|
|
if (!isSessionStarted && !ensureSessionReady()) { |
|
|
if (!isSessionStarted && !ensureSessionReady()) { |
|
|
@ -365,17 +344,11 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
*/ |
|
|
*/ |
|
|
override fun flushStream(): Boolean { |
|
|
override fun flushStream(): Boolean { |
|
|
try { |
|
|
try { |
|
|
if (!isStreamMode) return true |
|
|
|
|
|
// Log.d(TAG, "flushStream: $sessionId") |
|
|
// Log.d(TAG, "flushStream: $sessionId") |
|
|
launch { |
|
|
|
|
|
withContext(Dispatchers.IO) { |
|
|
if (isSessionStarted) { |
|
|
delay(300) // 确保现有文本处理完成 |
|
|
isSessionStarted = false |
|
|
} |
|
|
finishSession() |
|
|
|
|
|
|
|
|
if (isSessionStarted) { |
|
|
|
|
|
isSessionStarted = false |
|
|
|
|
|
finishSession() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return true |
|
|
return true |
|
|
@ -440,6 +413,29 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
audioDataListeners.remove(listener) |
|
|
audioDataListeners.remove(listener) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 设置是否使用内部播放器 |
|
|
|
|
|
*/ |
|
|
|
|
|
override fun setUseInternalPlayer(useInternalPlayer: Boolean) { |
|
|
|
|
|
this.useInternalPlayer = useInternalPlayer |
|
|
|
|
|
updateInternalPlayerUsage() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 更新内部播放器的使用状态 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun updateInternalPlayerUsage() { |
|
|
|
|
|
if (useInternalPlayer) { |
|
|
|
|
|
// 使用内部播放器 |
|
|
|
|
|
if (!audioDataListeners.contains(audioPlayer)) { |
|
|
|
|
|
audioDataListeners.add(audioPlayer) |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
// 不使用内部播放器 |
|
|
|
|
|
audioDataListeners.remove(audioPlayer) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 通知事件处理 |
|
|
* 通知事件处理 |
|
|
*/ |
|
|
*/ |
|
|
@ -461,15 +457,14 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
* 通知音频数据 |
|
|
* 通知音频数据 |
|
|
*/ |
|
|
*/ |
|
|
private fun notifyAudioData(data: ByteArray) { |
|
|
private fun notifyAudioData(data: ByteArray) { |
|
|
launch(Dispatchers.Main) { |
|
|
for (listener in audioDataListeners) { |
|
|
for (listener in audioDataListeners) { |
|
|
try { |
|
|
try { |
|
|
listener.onAudioData(data) |
|
|
listener.onAudioData(data) |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e(TAG, "音频数据回调异常: ${e.message}") |
|
|
Log.e(TAG, "音频数据回调异常: ${e.message}") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -483,7 +478,7 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
|
|
|
|
|
|
// 如果已连接但会话未开始,只需要开始会话 |
|
|
// 如果已连接但会话未开始,只需要开始会话 |
|
|
if (isConnected && !isSessionStarted) { |
|
|
if (isConnected && !isSessionStarted) { |
|
|
Log.d(TAG, "WebSocket已连接,正在启动新会话...") |
|
|
// Log.d(TAG, "WebSocket已连接,正在启动新会话...") |
|
|
return startSessionOnly() |
|
|
return startSessionOnly() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -648,7 +643,6 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
"errorMessage" to errorMsg)) |
|
|
"errorMessage" to errorMsg)) |
|
|
webSocket.close(1000, "Error") |
|
|
webSocket.close(1000, "Error") |
|
|
updateStatus(STATUS_ERROR) |
|
|
updateStatus(STATUS_ERROR) |
|
|
isSpeaking = false // 错误情况下显式设置 |
|
|
|
|
|
isSessionStarted = false |
|
|
isSessionStarted = false |
|
|
isConnected = false |
|
|
isConnected = false |
|
|
isConnecting.set(false) |
|
|
isConnecting.set(false) |
|
|
@ -656,6 +650,10 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
|
|
|
|
|
|
EVENT_SESSION_STARTED -> { |
|
|
EVENT_SESSION_STARTED -> { |
|
|
connectionAttempts = 0 |
|
|
connectionAttempts = 0 |
|
|
|
|
|
// 会话开始时,如果使用内部播放器则启动播放器会话 |
|
|
|
|
|
if (useInternalPlayer) { |
|
|
|
|
|
audioPlayer.startSession() |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
EVENT_TTS_RESPONSE -> { |
|
|
EVENT_TTS_RESPONSE -> { |
|
|
@ -673,16 +671,18 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
|
|
|
|
|
|
EVENT_SESSION_FINISHED -> { |
|
|
EVENT_SESSION_FINISHED -> { |
|
|
isSessionStarted = false |
|
|
isSessionStarted = false |
|
|
|
|
|
Log.i(TAG, "EVENT_SESSION_FINISHED, ${sessionId}") |
|
|
if (!isStreamMode) { |
|
|
// 如果使用内部播放器则结束播放器会话 |
|
|
finishConnection(webSocket) |
|
|
if (useInternalPlayer) { |
|
|
|
|
|
audioPlayer.endSession() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 流式模式下不自动关闭连接 |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e(TAG, "解析响应失败: ${e.message}") |
|
|
Log.e(TAG, "解析响应失败: ${e.message}") |
|
|
updateStatus(STATUS_ERROR) |
|
|
updateStatus(STATUS_ERROR) |
|
|
isSpeaking = false // 错误情况下显式设置 |
|
|
|
|
|
isConnecting.set(false) |
|
|
isConnecting.set(false) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -690,7 +690,6 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { |
|
|
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { |
|
|
isConnected = false |
|
|
isConnected = false |
|
|
isSessionStarted = false |
|
|
isSessionStarted = false |
|
|
isSpeaking = false |
|
|
|
|
|
isConnecting.set(false) |
|
|
isConnecting.set(false) |
|
|
|
|
|
|
|
|
Log.e(TAG, "WebSocket连接失败: ${t.message}") |
|
|
Log.e(TAG, "WebSocket连接失败: ${t.message}") |
|
|
@ -702,7 +701,6 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { |
|
|
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { |
|
|
isConnected = false |
|
|
isConnected = false |
|
|
isSessionStarted = false |
|
|
isSessionStarted = false |
|
|
isSpeaking = false |
|
|
|
|
|
isConnecting.set(false) |
|
|
isConnecting.set(false) |
|
|
updateStatus(STATUS_STOPPED) |
|
|
updateStatus(STATUS_STOPPED) |
|
|
notifyEvent(TtsEventType.SYNTHESIS_CANCELED) |
|
|
notifyEvent(TtsEventType.SYNTHESIS_CANCELED) |
|
|
@ -798,53 +796,6 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { |
|
|
sendEvent(webSocket, header, optional, payload) |
|
|
sendEvent(webSocket, header, optional, payload) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 发送文本进行合成 (非流式) |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun sendTextRequest(text: String) { |
|
|
|
|
|
webSocket?.let { ws -> |
|
|
|
|
|
val header = Header( |
|
|
|
|
|
protocolVersion = PROTOCOL_VERSION, |
|
|
|
|
|
headerSize = DEFAULT_HEADER_SIZE, |
|
|
|
|
|
messageType = FULL_CLIENT_REQUEST, |
|
|
|
|
|
messageTypeSpecificFlags = MSG_TYPE_FLAG_WITH_EVENT, |
|
|
|
|
|
serializationMethod = JSON, |
|
|
|
|
|
messageCompression = COMPRESSION_NO, |
|
|
|
|
|
reserved = 0 |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
val optional = Optional( |
|
|
|
|
|
event = EVENT_TASK_REQUEST, |
|
|
|
|
|
sessionId = sessionId |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
val jsonObject = JSONObject() |
|
|
|
|
|
val user = JSONObject() |
|
|
|
|
|
user.put("uid", "123456") |
|
|
|
|
|
jsonObject.put("user", user) |
|
|
|
|
|
jsonObject.put("event", EVENT_TASK_REQUEST) |
|
|
|
|
|
jsonObject.put("namespace", "BidirectionalTTS") |
|
|
|
|
|
|
|
|
|
|
|
val reqParams = JSONObject() |
|
|
|
|
|
// 将#和*替换为空格 |
|
|
|
|
|
val processedText = text.replace("#", " ").replace("*", " ") |
|
|
|
|
|
reqParams.put("text", processedText) |
|
|
|
|
|
reqParams.put("speaker", speaker) |
|
|
|
|
|
|
|
|
|
|
|
val audioParams = JSONObject() |
|
|
|
|
|
audioParams.put("format", format) |
|
|
|
|
|
audioParams.put("sample_rate", sampleRate) |
|
|
|
|
|
audioParams.put("emotion", "happy") |
|
|
|
|
|
|
|
|
|
|
|
reqParams.put("audio_params", audioParams) |
|
|
|
|
|
jsonObject.put("req_params", reqParams) |
|
|
|
|
|
|
|
|
|
|
|
val payload = jsonObject.toString().toByteArray() |
|
|
|
|
|
Log.d(TAG, "发送文本合成请求: ${jsonObject.toString()}") |
|
|
|
|
|
|
|
|
|
|
|
sendEvent(ws, header, optional, payload) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 发送文本进行合成 (流式) |
|
|
* 发送文本进行合成 (流式) |
|
|
|