feat(servicing): 添加客户语音通知功能
- 新增 CustomerSpeechManager 对象,用于处理文本转语音功能 - 添加 AppForegroundListener 接口和 BaseActivityLifecycleCallbacks 类,用于监听应用前后台切换- 更新 BaseActivity,使其支持推送消息 - 新增 ServicePeopleConfirmActivity 活动 - 优化订单处理逻辑,过滤掉已接受的订单 - 更新版本号至 1.0.1.9.9.12
This commit is contained in:
@ -117,6 +117,7 @@ object GlobalData : GlobalLocalData() {
|
||||
currentLocation = null
|
||||
driverInfoBean = null
|
||||
loginTime = null
|
||||
isLoginRecognition = null
|
||||
}
|
||||
|
||||
fun clearAllOrderCache() {
|
||||
|
@ -5,6 +5,7 @@ import com.tencent.bugly.Bugly
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.za.base.AppConfig
|
||||
import com.za.common.log.LogUtil
|
||||
import com.za.common.speech.SpeechManager
|
||||
import com.za.room.RoomHelper
|
||||
import com.za.service.location.ZdLocationManager
|
||||
|
||||
@ -28,5 +29,6 @@ object ZDManager {
|
||||
LogUtil.init(application)
|
||||
RoomHelper.init(application)
|
||||
ZdLocationManager.init(application)
|
||||
SpeechManager.init(application)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,122 @@
|
||||
package com.za.common.speech
|
||||
|
||||
import android.media.MediaPlayer
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import com.za.common.log.LogUtil
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.ResponseBody
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Call
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
|
||||
interface SpeechApiService {
|
||||
@POST("v1/audio/speech")
|
||||
fun textToSpeech(@Body request: JsonObject): Call<ResponseBody>
|
||||
}
|
||||
|
||||
object CustomerSpeechManager {
|
||||
|
||||
private const val BASE_URL = "http://192.168.3.129:8880/"
|
||||
|
||||
private val gson: Gson by lazy {
|
||||
Gson().newBuilder()
|
||||
.setLenient()
|
||||
.create()
|
||||
}
|
||||
|
||||
private val retrofit: Retrofit by lazy {
|
||||
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}
|
||||
|
||||
val client = OkHttpClient.Builder()
|
||||
.addInterceptor(loggingInterceptor)
|
||||
.build()
|
||||
|
||||
Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.client(client)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
|
||||
.build()
|
||||
}
|
||||
|
||||
private val speechApiService: SpeechApiService by lazy {
|
||||
retrofit.create(SpeechApiService::class.java)
|
||||
}
|
||||
|
||||
fun textToSpeech(input: String, destinationFile: File): Boolean {
|
||||
val requestBody = JsonObject().apply {
|
||||
addProperty("input", input)
|
||||
addProperty("voice", "zf_xiaoxiao")
|
||||
addProperty("response_format", "mp3")
|
||||
addProperty("stream", true)
|
||||
addProperty("speed", 1)
|
||||
addProperty("return_download_link", false) // Set to false to get the stream directly
|
||||
addProperty("lang_code", "z")
|
||||
}
|
||||
|
||||
val call = speechApiService.textToSpeech(requestBody)
|
||||
try {
|
||||
val response = call.execute()
|
||||
LogUtil.print("CustomerSpeechManager", "response: $response")
|
||||
if (response.isSuccessful) {
|
||||
val responseBody = response.body()
|
||||
if (responseBody != null) {
|
||||
saveToFile(responseBody, destinationFile)
|
||||
playAudio(destinationFile)
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
LogUtil.print("CustomerSpeechManager", "Request failed: ${response.code()}")
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun saveToFile(body: ResponseBody, destinationFile: File) {
|
||||
body.byteStream().use {
|
||||
FileOutputStream(destinationFile).buffered().use { outputStream ->
|
||||
val buffer = ByteArray(4096)
|
||||
var bytesRead: Int
|
||||
while (it.read(buffer).also { bytesRead = it } != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun playAudio(file: File) {
|
||||
val mediaPlayer = MediaPlayer().apply {
|
||||
try {
|
||||
setDataSource(file.absolutePath)
|
||||
prepare()
|
||||
start()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
release()
|
||||
}
|
||||
}
|
||||
|
||||
mediaPlayer.setOnCompletionListener {
|
||||
it.release()
|
||||
}
|
||||
|
||||
mediaPlayer.setOnErrorListener { mp, what, extra ->
|
||||
mp.release()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
221
servicing/src/main/java/com/za/common/speech/SpeechManager.kt
Normal file
221
servicing/src/main/java/com/za/common/speech/SpeechManager.kt
Normal file
@ -0,0 +1,221 @@
|
||||
package com.za.common.speech
|
||||
|
||||
import android.app.Application
|
||||
import android.media.AudioManager
|
||||
import android.media.MediaPlayer
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.blankj.utilcode.util.ThreadUtils
|
||||
import com.za.base.AppConfig
|
||||
import com.za.bean.request.AppNewOrderVoiceRequest
|
||||
import com.za.common.GlobalData
|
||||
import com.za.common.log.LogUtil
|
||||
import com.za.net.BaseObserver
|
||||
import com.za.net.RetrofitHelper
|
||||
import com.za.room.RoomHelper
|
||||
import com.za.room.db.user.LocalResourceBean
|
||||
import com.za.servicing.R
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object SpeechManager {
|
||||
private var mContext : Application? = null
|
||||
|
||||
|
||||
fun init(context : Application?) {
|
||||
mContext = context //语音合成初始化|
|
||||
|
||||
TTSManager.init(context, object : OnTTSListener {
|
||||
override fun onTTSInitialized() { // TTS初始化成功,可以开始播放语音
|
||||
LogUtil.print("TTS", "TTS initialized successfully")
|
||||
}
|
||||
|
||||
override fun onTTSSuccess() {
|
||||
LogUtil.print("TTS", "TTS speech completed successfully")
|
||||
resetAudioVolume()
|
||||
}
|
||||
|
||||
override fun onTTSFailed(errorMessage : String?) {
|
||||
resetAudioVolume() // TTS初始化失败或语音播放失败,使用科大讯飞进行播放
|
||||
LogUtil.print("TTS", "TTS failed: $errorMessage")
|
||||
if (lastFailedText != null && ! lastFailedText.isNullOrBlank()) {
|
||||
speechKDXF(lastFailedText)
|
||||
lastFailedText = null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private var lastFailedText : String? = null
|
||||
fun speech(msg : String?) {
|
||||
setMaxAudioVolume()
|
||||
lastFailedText = msg
|
||||
TTSManager.speak(msg)
|
||||
}
|
||||
|
||||
private fun speechKDXF(msg : String?) {
|
||||
|
||||
}
|
||||
|
||||
private var originVolume = 0
|
||||
private fun setMaxAudioVolume() {
|
||||
try {
|
||||
if (! AppConfig.isRelease) {
|
||||
return
|
||||
}
|
||||
val audioManager =
|
||||
ContextCompat.getSystemService(GlobalData.application, AudioManager::class.java)
|
||||
val maxVolume = audioManager?.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
|
||||
audioManager?.setStreamVolume(AudioManager.STREAM_MUSIC,
|
||||
maxVolume ?: 1,
|
||||
AudioManager.FLAG_PLAY_SOUND)
|
||||
} catch (e : Exception) {
|
||||
LogUtil.print("setMaxAudioVolume", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetAudioVolume() {
|
||||
try {
|
||||
if (! AppConfig.isRelease) {
|
||||
return
|
||||
}
|
||||
val audioManager =
|
||||
ContextCompat.getSystemService(GlobalData.application, AudioManager::class.java)
|
||||
audioManager?.setStreamVolume(AudioManager.STREAM_MUSIC,
|
||||
originVolume,
|
||||
AudioManager.FLAG_PLAY_SOUND)
|
||||
} catch (e : Exception) {
|
||||
LogUtil.print("resetAudioVolume", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun stopSpeech() {
|
||||
TTSManager.stop()
|
||||
lastFailedText = null
|
||||
}
|
||||
|
||||
private var mediaPlayer : MediaPlayer? = null
|
||||
|
||||
// 当前订单被取消
|
||||
fun playCurrentOrderCanceled() {
|
||||
mediaPlayer = MediaPlayer.create(mContext, R.raw.current_order_cancel)
|
||||
mediaPlayer?.start()
|
||||
}
|
||||
|
||||
// 订单被取消
|
||||
fun playOrderCanceled() {
|
||||
mediaPlayer = MediaPlayer.create(mContext, R.raw.cancel_order)
|
||||
mediaPlayer?.start()
|
||||
}
|
||||
|
||||
// 面部居中
|
||||
fun playFaceCenter() {
|
||||
mediaPlayer = MediaPlayer.create(mContext, R.raw.face_center)
|
||||
mediaPlayer?.start()
|
||||
}
|
||||
|
||||
// 面部居中
|
||||
fun playFaceLeft() {
|
||||
mediaPlayer = MediaPlayer.create(mContext, R.raw.face_left)
|
||||
mediaPlayer?.start()
|
||||
}
|
||||
|
||||
// 面部居中
|
||||
fun playFaceRight() {
|
||||
mediaPlayer = MediaPlayer.create(mContext, R.raw.face_right)
|
||||
mediaPlayer?.start()
|
||||
}
|
||||
|
||||
private fun playNewOrder() {
|
||||
stopPlayMedia()
|
||||
setMaxAudioVolume()
|
||||
mediaPlayer = MediaPlayer.create(mContext, R.raw.neworder)
|
||||
mediaPlayer?.start()
|
||||
mediaPlayer?.setOnCompletionListener {
|
||||
resetAudioVolume()
|
||||
}
|
||||
}
|
||||
|
||||
private fun playNewOrderFromNet(url : String) {
|
||||
try {
|
||||
stopPlayMedia()
|
||||
setMaxAudioVolume()
|
||||
mediaPlayer = MediaPlayer()
|
||||
mediaPlayer?.setAudioStreamType(AudioManager.STREAM_MUSIC) // 设置音频流类型
|
||||
mediaPlayer?.setDataSource(url) // 设置音频文件的 URL
|
||||
mediaPlayer?.prepareAsync() // 异步准备音频
|
||||
// 准备完成后开始播放
|
||||
mediaPlayer?.setOnPreparedListener { obj : MediaPlayer -> obj.start() }
|
||||
mediaPlayer?.setOnErrorListener { mp : MediaPlayer?, what : Int, extra : Int ->
|
||||
playNewOrder()
|
||||
false
|
||||
}
|
||||
mediaPlayer?.setOnCompletionListener {
|
||||
resetAudioVolume()
|
||||
}
|
||||
} catch (e : Exception) {
|
||||
playNewOrder()
|
||||
LogUtil.print("播放新订单失败", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun stopPlayMedia() {
|
||||
ThreadUtils.runOnUiThread {
|
||||
if (null != mediaPlayer) {
|
||||
mediaPlayer?.stop()
|
||||
mediaPlayer?.release()
|
||||
mediaPlayer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun speechNewOrderSound(content : String?) {
|
||||
if (content.isNullOrBlank()) {
|
||||
speechNewOrderLooper("您有新的中道救援订单,请尽快接单!") { playNewOrder() }
|
||||
return
|
||||
}
|
||||
|
||||
val localResourceDao = RoomHelper.db?.localResourceDao()
|
||||
val localUrlResource = localResourceDao?.getLocalResourceByName(content)
|
||||
if (localUrlResource != null && ! localUrlResource.resourceUrl.isNullOrBlank()) {
|
||||
speechNewOrderLooper(content) {
|
||||
playNewOrderFromNet(localUrlResource.resourceUrl ?: "")
|
||||
}
|
||||
LogUtil.print("handlerNewOrderVoice", "播放本地语音");
|
||||
return
|
||||
}
|
||||
|
||||
val appNewOrderVoiceRequest = AppNewOrderVoiceRequest(content)
|
||||
RetrofitHelper.getDefaultService().getVoiceUrl(appNewOrderVoiceRequest)
|
||||
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : BaseObserver<String>() {
|
||||
override fun doSuccess(it : String?) {
|
||||
if (it == null) {
|
||||
speechNewOrderLooper(content) { playNewOrder() }
|
||||
return
|
||||
}
|
||||
localResourceDao?.insert(LocalResourceBean(resourceName = content,
|
||||
resourceType = 1,
|
||||
resourceUrl = it))
|
||||
speechNewOrderLooper(content) { playNewOrderFromNet(it) }
|
||||
}
|
||||
|
||||
override fun doFailure(code : Int, msg : String?) {
|
||||
speechNewOrderLooper("您有新的中道救援订单,请尽快接单!") { playNewOrder() }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun speechNewOrderLooper(content : String, play : () -> Unit) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val startTime = System.currentTimeMillis()
|
||||
while (System.currentTimeMillis() - startTime < 1000 * 60 * 3 && GlobalData.isHandlerNewOrder == false) {
|
||||
play()
|
||||
delay(250L * content.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
81
servicing/src/main/java/com/za/common/speech/TTSManager.kt
Normal file
81
servicing/src/main/java/com/za/common/speech/TTSManager.kt
Normal file
@ -0,0 +1,81 @@
|
||||
package com.za.common.speech
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.speech.tts.TextToSpeech
|
||||
import android.speech.tts.UtteranceProgressListener
|
||||
import com.za.common.log.LogUtil
|
||||
import java.util.Locale
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
object TTSManager {
|
||||
private var tts: TextToSpeech? = null
|
||||
private var context: Context? = null
|
||||
private var listener: OnTTSListener? = null
|
||||
|
||||
|
||||
fun init(context: Context?, onTTSListener: OnTTSListener) {
|
||||
this.context = context
|
||||
this.listener = onTTSListener
|
||||
initTTS()
|
||||
}
|
||||
|
||||
private fun initTTS() {
|
||||
tts = TextToSpeech(context) { status ->
|
||||
if (status == TextToSpeech.SUCCESS) {
|
||||
val result = tts?.setLanguage(Locale.getDefault())
|
||||
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
|
||||
LogUtil.print("TTS", "Language not supported")
|
||||
listener?.onTTSFailed("Language not supported")
|
||||
} else {
|
||||
LogUtil.print("TTS", "TTS initialized successfully")
|
||||
listener?.onTTSInitialized()
|
||||
}
|
||||
} else {
|
||||
LogUtil.print("TTS", "Initialization failed")
|
||||
listener?.onTTSFailed("Initialization failed")
|
||||
}
|
||||
}
|
||||
|
||||
tts?.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
|
||||
override fun onStart(utteranceId: String) {
|
||||
LogUtil.print("TTS", "Speech started")
|
||||
}
|
||||
|
||||
override fun onDone(utteranceId: String) {
|
||||
LogUtil.print("TTS", "Speech completed")
|
||||
listener?.onTTSSuccess()
|
||||
}
|
||||
|
||||
override fun onError(utteranceId: String) {
|
||||
LogUtil.print("TTS", "Speech error")
|
||||
listener?.onTTSFailed("Speech error")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun speak(text: String?) {
|
||||
if (tts != null && tts?.isSpeaking == false) {
|
||||
tts?.speak(text, TextToSpeech.QUEUE_FLUSH, null, "uniqueId")
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
tts?.takeIf { it.isSpeaking }?.stop()
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
tts?.apply {
|
||||
stop()
|
||||
shutdown()
|
||||
}
|
||||
tts = null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface OnTTSListener {
|
||||
fun onTTSInitialized()
|
||||
fun onTTSSuccess()
|
||||
fun onTTSFailed(errorMessage: String?)
|
||||
}
|
Reference in New Issue
Block a user