[6.3.0]
[xiaozhi]
@@ -137,7 +137,7 @@
|
||||
android:layout_height="@dimen/dp_30"/>
|
||||
|
||||
|
||||
<com.mogo.och.common.module.wigets.ZhiView
|
||||
<com.mogo.och.common.module.manager.xiaozhimanager.ZhiView
|
||||
android:id="@+id/zv_msg_pop_bottom"
|
||||
android:layout_width="@dimen/dp_240"
|
||||
android:layout_height="@dimen/dp_240"
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
app:layout_constraintEnd_toEndOf="@+id/mapBizView"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.mogo.och.common.module.wigets.ZhiView
|
||||
<com.mogo.och.common.module.manager.xiaozhimanager.ZhiView
|
||||
android:id="@+id/zv_msg_pop_bottom"
|
||||
android:layout_width="@dimen/dp_240"
|
||||
android:layout_height="@dimen/dp_240"
|
||||
|
||||
@@ -40,6 +40,14 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
res.srcDirs = [
|
||||
'src/main/res',
|
||||
'src/main/res/xiaozhi',
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
package com.mogo.och.common.module.manager.xiaozhimanager
|
||||
|
||||
import android.Manifest
|
||||
import androidx.core.app.ActivityCompat
|
||||
import com.mogo.commons.AbsMogoApplication
|
||||
import com.mogo.eagle.core.data.config.FunctionBuildConfig
|
||||
import com.mogo.eagle.core.data.msgbox.MsgBoxBean
|
||||
import com.mogo.eagle.core.data.msgbox.MsgBoxType
|
||||
import com.mogo.eagle.core.data.msgbox.VoiceMsg
|
||||
import com.mogo.eagle.core.function.call.msgbox.CallerMsgBoxManager
|
||||
import com.mogo.eagle.core.function.main.MainPresenter
|
||||
import com.mogo.eagle.core.utilcode.mogo.AppIdentityModeUtils
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
|
||||
import com.mogo.eagle.core.utilcode.mogo.permissions.PermissionsDialogUtils
|
||||
import com.mogo.eagle.core.utilcode.util.ActivityUtils
|
||||
import com.mogo.och.common.module.utils.PermissionUtil
|
||||
import com.mogo.och.common.module.wigets.toast.ToastCharterUtils
|
||||
import com.mogo.tts.base.zhi.AsrTextBean
|
||||
import com.mogo.tts.base.zhi.AvatarManager
|
||||
import com.mogo.tts.base.zhi.CallbackWidget
|
||||
import com.mogo.tts.base.zhi.ZhiRecordWinUi
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
object ZhiStateManager : ZhiRecordWinUi {
|
||||
private const val TAG = "ZhiStateManager"
|
||||
|
||||
@Volatile
|
||||
private var status = ZhiRecordWinUi.RecordStatus.STATUS_SILENCE
|
||||
private val enableZhi = AtomicBoolean(true)
|
||||
|
||||
|
||||
|
||||
init {
|
||||
AvatarManager.addDistanceListener(TAG,this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始对话
|
||||
*/
|
||||
override fun start(reason: String?) {
|
||||
CallerLogger.d(TAG,"-----start 开始对话 $reason")
|
||||
}
|
||||
|
||||
/**
|
||||
* 语音卡状态变化
|
||||
*
|
||||
* @param status
|
||||
*/
|
||||
override fun onStatusChange(status: ZhiRecordWinUi.RecordStatus?) {
|
||||
CallerLogger.d(TAG,"-----onStatusChange $status")
|
||||
this.status = status?:ZhiRecordWinUi.RecordStatus.STATUS_SILENCE
|
||||
when (status) {
|
||||
ZhiRecordWinUi.RecordStatus.STATUS_SILENCE -> {// 进入默认状态
|
||||
CallerLogger.d(TAG,"正在静默状态")
|
||||
ZhiViewmanager.showListeningAni(ZhiViewmanager.normalAni)
|
||||
}
|
||||
ZhiRecordWinUi.RecordStatus.STATUS_LISTENING -> {// 正在监听唤醒人说话
|
||||
CallerLogger.d(TAG,"正在监听唤醒人说话")
|
||||
ZhiViewmanager.showListeningAni(ZhiViewmanager.listenerAni)
|
||||
}
|
||||
ZhiRecordWinUi.RecordStatus.STATUS_UNDERSTANDING -> {// 小智正在理解中
|
||||
CallerLogger.d(TAG,"正在理解")
|
||||
}
|
||||
ZhiRecordWinUi.RecordStatus.STATUS_UNDERSTAND_END -> {// 理解完毕
|
||||
CallerLogger.d(TAG,"理解结束")
|
||||
}
|
||||
ZhiRecordWinUi.RecordStatus.STATUS_SPEAKING -> {// 小智正在说话
|
||||
CallerLogger.d(TAG,"小智正在说话")
|
||||
ZhiViewmanager.showListeningAni(ZhiViewmanager.normalAni)
|
||||
}
|
||||
else -> {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭对话
|
||||
* @param trigger 是否手动
|
||||
*/
|
||||
override fun close(trigger: Boolean) {
|
||||
CallerLogger.d(TAG,"-----close $trigger")
|
||||
onStatusChange(ZhiRecordWinUi.RecordStatus.STATUS_SILENCE)
|
||||
val msg = VoiceMsg(
|
||||
isWakeUp = false,
|
||||
isWakeUpEnd = true,
|
||||
msg = null,
|
||||
isLastMsg = true,
|
||||
isResp = true
|
||||
)
|
||||
pushMsgBox(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* 语音输入音量变化
|
||||
*
|
||||
* @param volume
|
||||
*/
|
||||
override fun onVolumeChange(volume: Int) {
|
||||
CallerLogger.d(TAG,"-----onVolumeChange $volume")
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示用户输入的内容
|
||||
*
|
||||
* @param asrTextBean
|
||||
*/
|
||||
override fun showInputText(asrTextBean: AsrTextBean?) {
|
||||
CallerLogger.d(TAG,"-----showInputText $asrTextBean")
|
||||
asrTextBean.let {
|
||||
val msg = VoiceMsg(
|
||||
isWakeUp = false,
|
||||
isWakeUpEnd = false,
|
||||
msg = it?.text,
|
||||
isLastMsg = it?.isLast == true,
|
||||
isResp = false
|
||||
)
|
||||
pushMsgBox(msg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示控件 暂时没有需求
|
||||
*
|
||||
* @param callbackWidget
|
||||
*/
|
||||
override fun showOutPutWidget(callbackWidget: CallbackWidget?) {
|
||||
// todo 咱不支持定制显示 包括天气
|
||||
CallerLogger.d(TAG,"-----showOutPutWidget $callbackWidget")
|
||||
}
|
||||
/**
|
||||
* 显示系统输出内容
|
||||
*
|
||||
* @param outPutText
|
||||
*/
|
||||
override fun showOutputText(outPutText: String?) {
|
||||
CallerLogger.d(TAG,"-----showOutputText $outPutText")
|
||||
outPutText?.let {
|
||||
val msg = VoiceMsg(
|
||||
isWakeUp = false,
|
||||
isWakeUpEnd = false,
|
||||
msg = it,
|
||||
isLastMsg = false,
|
||||
isResp = true
|
||||
)
|
||||
pushMsgBox(msg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun setZhiEnable(enable:Boolean){
|
||||
if(enable){// 可用
|
||||
enableZhi.set(true)
|
||||
AvatarManager.enableXiaoZhi(false)
|
||||
}else{// 不可用
|
||||
enableZhi.set(false)
|
||||
AvatarManager.enableXiaoZhi(true)
|
||||
}
|
||||
}
|
||||
|
||||
fun weakUpXiaoZhi(){
|
||||
if (!AppIdentityModeUtils.isTaxiPassenger(FunctionBuildConfig.appIdentityMode)) {
|
||||
return
|
||||
}
|
||||
if (!isFastClick()) {
|
||||
ToastCharterUtils.showToastShort("请稍后唤醒")
|
||||
return
|
||||
}
|
||||
if (PermissionUtil.checkPermission(AbsMogoApplication.getApp(), Manifest.permission.RECORD_AUDIO)) {
|
||||
if(enableZhi.get()) {
|
||||
AvatarManager.wakeupXiaoZhi()
|
||||
}
|
||||
}else{
|
||||
//申请悬浮窗权限
|
||||
val shouldShowRequestPermissionRationale = ActivityUtils.getTopActivity()
|
||||
.shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO)
|
||||
if(shouldShowRequestPermissionRationale){// 可以弹窗系统权限框
|
||||
ActivityCompat.requestPermissions(
|
||||
ActivityUtils.getTopActivity(),
|
||||
arrayOf(
|
||||
Manifest.permission.RECORD_AUDIO,
|
||||
), MainPresenter.MOGO_PERMISSION_REQUEST_CODE
|
||||
)
|
||||
}else{// 不会弹系统弹窗
|
||||
PermissionsDialogUtils.openAppDetails(ActivityUtils.getTopActivity(), "录音机", 100)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 两次点击按钮之间的点击间隔不能少于1000毫秒
|
||||
private const val MIN_CLICK_DELAY_TIME = 3000
|
||||
private var lastClickTime: Long = 0
|
||||
|
||||
private fun isFastClick(): Boolean {
|
||||
var flag = false
|
||||
val curClickTime = System.currentTimeMillis()
|
||||
if (curClickTime - lastClickTime >= MIN_CLICK_DELAY_TIME) {
|
||||
flag = true
|
||||
}
|
||||
lastClickTime = curClickTime
|
||||
return flag
|
||||
}
|
||||
|
||||
|
||||
private fun pushMsgBox(msg: VoiceMsg){
|
||||
CallerMsgBoxManager.saveMsgBox(MsgBoxBean(MsgBoxType.VOICE, msg))
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.mogo.och.common.module.manager.xiaozhimanager
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
|
||||
import com.mogo.och.common.module.utils.FrameAnimatorContainer
|
||||
import com.mogo.tts.base.zhi.CallbackWidget
|
||||
import com.mogo.tts.base.zhi.ZhiRecordWinUi
|
||||
import java.util.concurrent.ArrayBlockingQueue
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class ZhiView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : AppCompatImageView(context, attrs, defStyleAttr), ZhiViewmanager.IViewCallback {
|
||||
companion object {
|
||||
const val TAG = "ZhiView"
|
||||
}
|
||||
|
||||
// 播放默认小智
|
||||
// 传递播放一次的动画
|
||||
// 传递开始播放
|
||||
|
||||
private var currentAnim:FrameAnimatorContainer? = null
|
||||
|
||||
init {
|
||||
setOnClickListener {
|
||||
ZhiStateManager.weakUpXiaoZhi()
|
||||
}
|
||||
}
|
||||
|
||||
override fun setFirstPlayAni(initAni: ZhiViewmanager.AniData,aniListener:FrameAnimatorContainer.OnAnimationStoppedListener){
|
||||
currentAnim = FrameAnimatorContainer(initAni.aniArrayId, 12,this)
|
||||
currentAnim?.isOnce = initAni.isOnce
|
||||
currentAnim?.sequence = initAni.sequence
|
||||
currentAnim?.setFtp(initAni.fps)
|
||||
currentAnim?.setOnAnimStopListener(aniListener)
|
||||
currentAnim?.reStart()
|
||||
}
|
||||
|
||||
override fun setNewPlayData(currentPalyingAni: ZhiViewmanager.AniData) {
|
||||
currentAnim?.setData(currentPalyingAni.aniList)
|
||||
currentAnim?.isOnce = currentPalyingAni.isOnce
|
||||
currentAnim?.sequence = currentPalyingAni.sequence
|
||||
currentAnim?.setFtp(currentPalyingAni.fps)
|
||||
}
|
||||
|
||||
override fun changeAniImmediately(currentPalyingAni: ZhiViewmanager.AniData) {
|
||||
currentAnim?.setData(currentPalyingAni.aniList)
|
||||
currentAnim?.isOnce = currentPalyingAni.isOnce
|
||||
currentAnim?.sequence = currentPalyingAni.sequence
|
||||
currentAnim?.setFtp(currentPalyingAni.fps)
|
||||
currentAnim?.reStart()
|
||||
}
|
||||
|
||||
|
||||
override fun restartAni() {
|
||||
currentAnim?.reStart()
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
ZhiViewmanager.addDistanceListener(this)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
ZhiViewmanager.removeListener()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.mogo.och.common.module.manager.xiaozhimanager
|
||||
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
|
||||
import com.mogo.och.common.module.R
|
||||
import com.mogo.och.common.module.utils.FrameAnimatorContainer
|
||||
import java.util.concurrent.ArrayBlockingQueue
|
||||
|
||||
object ZhiViewmanager {
|
||||
|
||||
private const val TAG = "ZhiViewmanager"
|
||||
|
||||
private var iViewCallbacks: IViewCallback? = null
|
||||
|
||||
val normalAni = AniData(1, R.array.xiaozhi_normal, false, true,true,12)
|
||||
val listenerAni = AniData(2,R.array.xiaozhi_think,false,true,true,12)
|
||||
|
||||
val listener2Normal = AniData(3, R.array.xiaozhi_think_normal, true, false,true,24)
|
||||
val normal2Listener = AniData(4, R.array.xiaozhi_think_normal, true, true,true,24)
|
||||
|
||||
val beltAni = AniData(5, R.array.xiaozhi_belt, false, true,true,12)
|
||||
val loveAni = AniData(6, R.array.xiaozhi_love, false, true,true,12)
|
||||
|
||||
private var initAni = normalAni
|
||||
private var currentAni: AniData? = null
|
||||
|
||||
private val readQueue = ArrayBlockingQueue<AniData>(8, true)
|
||||
|
||||
|
||||
fun addDistanceListener(listener: IViewCallback) {
|
||||
this.iViewCallbacks = listener
|
||||
this.currentAni = initAni
|
||||
listener.setFirstPlayAni(initAni,
|
||||
object : FrameAnimatorContainer.OnAnimationStoppedListener {
|
||||
override fun playOnce() {
|
||||
CallerLogger.d(TAG, "播放完一遍动画")
|
||||
var nextPlay = readQueue.poll()
|
||||
CallerLogger.d(TAG, "获取下一个动画${Thread.currentThread().name}--${nextPlay?.id}-${readQueue.size}")
|
||||
if(nextPlay == currentAni){
|
||||
return
|
||||
}
|
||||
if(nextPlay==null){
|
||||
if(currentAni?.isOnce==true) {
|
||||
// 没有设置新的动画 且上一个动画是直播一次的动画
|
||||
currentAni = initAni
|
||||
iViewCallbacks?.setNewPlayData(initAni)
|
||||
}else{
|
||||
// 没有设置新的动画 且上一个动画时循环动画
|
||||
return
|
||||
}
|
||||
}else{
|
||||
// 有设置新的动画
|
||||
currentAni = nextPlay
|
||||
CallerLogger.d(TAG, "切换动画${nextPlay.id}")
|
||||
iViewCallbacks?.setNewPlayData(nextPlay)
|
||||
}
|
||||
}
|
||||
|
||||
override fun AnimationStopped() {
|
||||
CallerLogger.d(TAG, "动画停止")
|
||||
listener.restartAni()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun removeListener() {
|
||||
iViewCallbacks = null
|
||||
}
|
||||
|
||||
fun showListeningAni(aniData: AniData) {
|
||||
var lastAni = readQueue.peek()
|
||||
if(lastAni==null){
|
||||
lastAni = currentAni
|
||||
}
|
||||
if(lastAni?.id==aniData.id){
|
||||
return
|
||||
}
|
||||
when (lastAni?.id) {
|
||||
1 -> {//正在播放默认动画
|
||||
if(aniData.id==2){
|
||||
iViewCallbacks?.changeAniImmediately(normal2Listener)
|
||||
currentAni = normal2Listener
|
||||
CallerLogger.d(TAG, "立刻播放${Thread.currentThread().name}--${normal2Listener.id}-${readQueue.size}")
|
||||
readQueue.offer(aniData)
|
||||
CallerLogger.d(TAG, "排队播放${Thread.currentThread().name}--${aniData.id}-${readQueue.size}")
|
||||
}else{
|
||||
iViewCallbacks?.changeAniImmediately(aniData)
|
||||
currentAni = aniData
|
||||
CallerLogger.d(TAG, "立刻播放${Thread.currentThread().name}--${aniData.id}-${readQueue.size}")
|
||||
}
|
||||
}
|
||||
2 -> {// 正在播放倾听中动画
|
||||
if(aniData.id==1){
|
||||
iViewCallbacks?.changeAniImmediately(listener2Normal)
|
||||
currentAni = listener2Normal
|
||||
CallerLogger.d(TAG, "立刻播放${Thread.currentThread().name}--${listener2Normal.id}-${readQueue.size}")
|
||||
readQueue.offer(aniData)
|
||||
CallerLogger.d(TAG, "排队播放${Thread.currentThread().name}--${aniData.id}-${readQueue.size}")
|
||||
}else{
|
||||
iViewCallbacks?.changeAniImmediately(aniData)
|
||||
currentAni = aniData
|
||||
CallerLogger.d(TAG, "立刻播放${Thread.currentThread().name}--${aniData.id}-${readQueue.size}")
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
iViewCallbacks?.changeAniImmediately(aniData)
|
||||
currentAni = aniData
|
||||
CallerLogger.d(TAG, "立刻播放${Thread.currentThread().name}--${aniData.id}-${readQueue.size}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
interface IViewCallback {
|
||||
fun setFirstPlayAni(
|
||||
currentPalyingAni: AniData,
|
||||
aniListener: FrameAnimatorContainer.OnAnimationStoppedListener
|
||||
)
|
||||
|
||||
fun setNewPlayData(currentPalyingAni: AniData)
|
||||
fun changeAniImmediately(currentPalyingAni: AniData)
|
||||
fun restartAni()
|
||||
}
|
||||
|
||||
data class AniData(
|
||||
val id: Int,// 动画ID
|
||||
val aniArrayId: Int,// 动画序列号
|
||||
val isOnce: Boolean,// true 只播一次 false 循环播放
|
||||
val sequence: Boolean,// true 正向播放 false 倒着播放
|
||||
val immediately:Boolean,
|
||||
val fps:Int
|
||||
) {
|
||||
var aniList = FrameAnimatorContainer.getData(aniArrayId)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,24 @@ class FrameAnimatorContainer (resId: Int,
|
||||
var isOnce:Boolean = false
|
||||
var sequence:Boolean = true
|
||||
|
||||
fun setFtp(fps:Int){
|
||||
mDelayMillis = 1000 / fps
|
||||
}
|
||||
|
||||
companion object{
|
||||
fun getData(resId: Int): IntArray {
|
||||
val array = AbsMogoApplication.getApp().resources.obtainTypedArray(resId)
|
||||
val len = array.length()
|
||||
val intArray = IntArray(array.length())
|
||||
for (i in 0 until len) {
|
||||
intArray[i] = array.getResourceId(i, 0)
|
||||
}
|
||||
array.recycle()
|
||||
return intArray
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
init {
|
||||
createAnimation(imageView, getData(resId), fps,initFirstFrame,width,height)
|
||||
this.isOnce = isOnce
|
||||
@@ -87,16 +105,24 @@ class FrameAnimatorContainer (resId: Int,
|
||||
private val next: Int
|
||||
get() {
|
||||
mIndex++
|
||||
var isPlayOnce = false
|
||||
if (mIndex >= mFrames.size){
|
||||
mIndex = 0
|
||||
if(isOnce){// 一次性动画 播放完毕后直接结束
|
||||
stop()
|
||||
}
|
||||
isPlayOnce = true
|
||||
|
||||
}
|
||||
if(!sequence){// 倒叙
|
||||
return mFrames[mFrames.size-1-mIndex]
|
||||
val nextInfo= if(sequence){// 倒叙
|
||||
mFrames[mIndex]
|
||||
}else{
|
||||
mFrames[mFrames.size-1-mIndex]
|
||||
}
|
||||
return mFrames[mIndex]
|
||||
if(isPlayOnce){// 锁定nextInfo 在回调中可能会修改mFrames值
|
||||
mOnAnimationStoppedListener?.playOnce()
|
||||
}
|
||||
return nextInfo
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@@ -120,9 +146,8 @@ class FrameAnimatorContainer (resId: Int,
|
||||
val imageView = mSoftReferenceImageView!!.get()
|
||||
if (!mShouldRun || imageView == null) {
|
||||
mIsRunning = false
|
||||
if (mOnAnimationStoppedListener != null) {
|
||||
mOnAnimationStoppedListener!!.AnimationStopped()
|
||||
}
|
||||
mOnAnimationStoppedListener?.AnimationStopped()
|
||||
Logger.d(TAG, "结束动画1")
|
||||
return
|
||||
}
|
||||
mIsRunning = true
|
||||
@@ -131,9 +156,8 @@ class FrameAnimatorContainer (resId: Int,
|
||||
val imageRes: Int = next
|
||||
if (!mShouldRun || imageView == null) {
|
||||
mIsRunning = false
|
||||
if (mOnAnimationStoppedListener != null) {
|
||||
mOnAnimationStoppedListener!!.AnimationStopped()
|
||||
}
|
||||
mOnAnimationStoppedListener?.AnimationStopped()
|
||||
Logger.d(TAG, "结束动画2")
|
||||
return
|
||||
}
|
||||
mHandler?.postDelayed(this, mDelayMillis.toLong())
|
||||
@@ -195,16 +219,6 @@ class FrameAnimatorContainer (resId: Int,
|
||||
* @param resId
|
||||
* @return
|
||||
*/
|
||||
fun getData(resId: Int): IntArray {
|
||||
val array = AbsMogoApplication.getApp().resources.obtainTypedArray(resId)
|
||||
val len = array.length()
|
||||
val intArray = IntArray(array.length())
|
||||
for (i in 0 until len) {
|
||||
intArray[i] = array.getResourceId(i, 0)
|
||||
}
|
||||
array.recycle()
|
||||
return intArray
|
||||
}
|
||||
|
||||
fun setData(mFrames: IntArray){
|
||||
this.mFrames = mFrames
|
||||
@@ -215,5 +229,6 @@ class FrameAnimatorContainer (resId: Int,
|
||||
*/
|
||||
interface OnAnimationStoppedListener {
|
||||
fun AnimationStopped()
|
||||
fun playOnce(){}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,13 @@ object VoiceNotice {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun showNotice(notice: String?, level: Int, delayed: Long = 0) {
|
||||
fun showNotice(notice: String?, delayed: Long,callback:IMogoTTSCallback ) {
|
||||
showNotice(notice, AIAssist.LEVEL0, delayed,callback)
|
||||
}
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun showNotice(notice: String?, level: Int, delayed: Long = 0,callback:IMogoTTSCallback?=null) {
|
||||
notice?.let {
|
||||
if (delayed == 0L) {
|
||||
UiThreadHandler.post {
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
package com.mogo.och.common.module.wigets
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.app.ActivityCompat
|
||||
import com.mogo.eagle.core.data.config.FunctionBuildConfig
|
||||
import com.mogo.eagle.core.data.msgbox.MsgBoxBean
|
||||
import com.mogo.eagle.core.data.msgbox.MsgBoxType
|
||||
import com.mogo.eagle.core.data.msgbox.VoiceMsg
|
||||
import com.mogo.eagle.core.function.call.msgbox.CallerMsgBoxManager
|
||||
import com.mogo.eagle.core.function.main.MainPresenter
|
||||
import com.mogo.eagle.core.utilcode.mogo.AppIdentityModeUtils
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
|
||||
import com.mogo.eagle.core.utilcode.mogo.permissions.PermissionsDialogUtils
|
||||
import com.mogo.eagle.core.utilcode.util.ActivityUtils
|
||||
import com.mogo.eagle.core.utilcode.util.ClickUtils
|
||||
import com.mogo.och.common.module.R
|
||||
import com.mogo.och.common.module.utils.FrameAnimatorContainer
|
||||
import com.mogo.och.common.module.utils.PermissionUtil
|
||||
import com.mogo.och.common.module.wigets.toast.ToastCharterUtils
|
||||
import com.mogo.tts.base.zhi.AsrTextBean
|
||||
import com.mogo.tts.base.zhi.AvatarManager
|
||||
import com.mogo.tts.base.zhi.CallbackWidget
|
||||
import com.mogo.tts.base.zhi.ZhiRecordWinUi
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class ZhiView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : AppCompatImageView(context, attrs, defStyleAttr),
|
||||
ZhiRecordWinUi {
|
||||
companion object {
|
||||
const val TAG = "ZhiView"
|
||||
}
|
||||
|
||||
private var currentAnim = FrameAnimatorContainer(R.array.xiaozhi_normal, 12,this)
|
||||
|
||||
@Volatile
|
||||
private var status = ZhiRecordWinUi.RecordStatus.STATUS_SILENCE
|
||||
private var animalState = AnimalState.Normal
|
||||
private val enableZhi = AtomicBoolean(true)
|
||||
init {
|
||||
val xiaozhiNormal = currentAnim.getData(R.array.xiaozhi_normal)
|
||||
val xiaozhiThink = currentAnim.getData(R.array.xiaozhi_think)
|
||||
val xiaozhiThinkNormal = currentAnim.getData(R.array.xiaozhi_think_normal)
|
||||
currentAnim.setOnAnimStopListener(object : FrameAnimatorContainer.OnAnimationStoppedListener{
|
||||
override fun AnimationStopped() {
|
||||
when (status) {
|
||||
ZhiRecordWinUi.RecordStatus.STATUS_SILENCE -> {
|
||||
if(animalState==AnimalState.SPEAK){
|
||||
currentAnim.setData(xiaozhiThinkNormal)
|
||||
currentAnim.isOnce = true
|
||||
currentAnim.sequence = false
|
||||
currentAnim.reStart()
|
||||
animalState = AnimalState.NORMAL_SPEAK
|
||||
CallerLogger.d(SceneConstant.M_OCHCOMMON + TAG, "动画$status speak to normal")
|
||||
}else{
|
||||
currentAnim.setData(xiaozhiNormal)
|
||||
currentAnim.isOnce = false
|
||||
currentAnim.sequence = true
|
||||
currentAnim.reStart()
|
||||
animalState = AnimalState.Normal
|
||||
CallerLogger.d(SceneConstant.M_OCHCOMMON + TAG, "动画$status normal")
|
||||
}
|
||||
}
|
||||
ZhiRecordWinUi.RecordStatus.STATUS_LISTENING -> {
|
||||
if(animalState==AnimalState.Normal){
|
||||
currentAnim.setData(xiaozhiThinkNormal)
|
||||
currentAnim.isOnce = true
|
||||
currentAnim.sequence = true
|
||||
currentAnim.reStart()
|
||||
animalState = AnimalState.NORMAL_SPEAK
|
||||
CallerLogger.d(SceneConstant.M_OCHCOMMON + TAG, "动画$status normal to speak")
|
||||
}else{
|
||||
currentAnim.setData(xiaozhiThink)
|
||||
currentAnim.isOnce = false
|
||||
currentAnim.sequence = true
|
||||
currentAnim.reStart()
|
||||
animalState = AnimalState.SPEAK
|
||||
CallerLogger.d(SceneConstant.M_OCHCOMMON + TAG, "动画$status speak")
|
||||
}
|
||||
}
|
||||
ZhiRecordWinUi.RecordStatus.STATUS_UNDERSTANDING -> {}
|
||||
ZhiRecordWinUi.RecordStatus.STATUS_UNDERSTAND_END -> {}
|
||||
ZhiRecordWinUi.RecordStatus.STATUS_SPEAKING -> {
|
||||
if(animalState==AnimalState.SPEAK){
|
||||
currentAnim.setData(xiaozhiThinkNormal)
|
||||
currentAnim.isOnce = true
|
||||
currentAnim.sequence = false
|
||||
currentAnim.reStart()
|
||||
animalState = AnimalState.NORMAL_SPEAK
|
||||
CallerLogger.d(SceneConstant.M_OCHCOMMON + TAG, "动画$status speak to normal")
|
||||
}else{
|
||||
currentAnim.setData(xiaozhiNormal)
|
||||
currentAnim.isOnce = false
|
||||
currentAnim.sequence = true
|
||||
currentAnim.reStart()
|
||||
animalState = AnimalState.Normal
|
||||
CallerLogger.d(SceneConstant.M_OCHCOMMON + TAG, "动画$status normal")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
setOnClickListener {
|
||||
if (!AppIdentityModeUtils.isTaxiPassenger(FunctionBuildConfig.appIdentityMode)) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
if (ClickUtils.isClickTooFrequent(this,3000)) {
|
||||
ToastCharterUtils.showToastShort("请稍后唤醒")
|
||||
return@setOnClickListener
|
||||
}
|
||||
if (PermissionUtil.checkPermission(context,Manifest.permission.RECORD_AUDIO)) {
|
||||
if(enableZhi.get()) {
|
||||
AvatarManager.wakeupXiaoZhi()
|
||||
}
|
||||
}else{
|
||||
//申请悬浮窗权限
|
||||
val shouldShowRequestPermissionRationale = ActivityUtils.getTopActivity()
|
||||
.shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO)
|
||||
if(shouldShowRequestPermissionRationale){// 可以弹窗系统权限框
|
||||
ActivityCompat.requestPermissions(ActivityUtils.getTopActivity(),
|
||||
arrayOf(
|
||||
Manifest.permission.RECORD_AUDIO,
|
||||
), MainPresenter.MOGO_PERMISSION_REQUEST_CODE
|
||||
)
|
||||
}else{// 不会弹系统弹窗
|
||||
PermissionsDialogUtils.openAppDetails(ActivityUtils.getTopActivity(), "录音机", 100)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setZhiEnable(enable:Boolean){
|
||||
if(enable){// 可用
|
||||
enableZhi.set(true)
|
||||
AvatarManager.enableXiaoZhi(false)
|
||||
}else{// 不可用
|
||||
enableZhi.set(false)
|
||||
AvatarManager.enableXiaoZhi(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasWindowFocus)
|
||||
CallerLogger.d(SceneConstant.M_OCHCOMMON + TAG, "焦点与否:${hasWindowFocus}")
|
||||
AvatarManager.enableXiaoZhi(!hasWindowFocus)
|
||||
if(hasWindowFocus){
|
||||
currentAnim.reStart()
|
||||
}else{
|
||||
currentAnim.stop()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
AvatarManager.addDistanceListener(TAG,this)
|
||||
currentAnim.reStart()
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
AvatarManager.removeListener(TAG)
|
||||
}
|
||||
|
||||
override fun start(reason: String?) {
|
||||
CallerLogger.d(TAG,"-----start $reason")
|
||||
}
|
||||
|
||||
override fun onStatusChange(status: ZhiRecordWinUi.RecordStatus?) {
|
||||
CallerLogger.d(TAG,"-----onStatusChange $status")
|
||||
this.status = status?:ZhiRecordWinUi.RecordStatus.STATUS_SILENCE
|
||||
when (status) {
|
||||
ZhiRecordWinUi.RecordStatus.STATUS_SILENCE -> {
|
||||
currentAnim.stop()
|
||||
}
|
||||
ZhiRecordWinUi.RecordStatus.STATUS_LISTENING -> {// 监听中
|
||||
currentAnim.stop()
|
||||
}
|
||||
ZhiRecordWinUi.RecordStatus.STATUS_UNDERSTANDING -> {
|
||||
// 正在理解
|
||||
CallerLogger.d(TAG,"正在理解")
|
||||
}
|
||||
ZhiRecordWinUi.RecordStatus.STATUS_UNDERSTAND_END -> {
|
||||
CallerLogger.d(TAG,"理解结束")
|
||||
}
|
||||
ZhiRecordWinUi.RecordStatus.STATUS_SPEAKING -> {
|
||||
// 正在说话
|
||||
currentAnim.stop()
|
||||
}
|
||||
else -> {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun close(trigger: Boolean) {
|
||||
CallerLogger.d(TAG,"-----close $trigger")
|
||||
onStatusChange(ZhiRecordWinUi.RecordStatus.STATUS_SILENCE)
|
||||
val msg = VoiceMsg(
|
||||
isWakeUp = false,
|
||||
isWakeUpEnd = true,
|
||||
msg = null,
|
||||
isLastMsg = true,
|
||||
isResp = true
|
||||
)
|
||||
pushMsgBox(msg)
|
||||
}
|
||||
|
||||
|
||||
override fun onVolumeChange(volume: Int) {
|
||||
CallerLogger.d(TAG,"-----onVolumeChange $volume")
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户输入的
|
||||
*/
|
||||
override fun showInputText(asrTextBean: AsrTextBean?) {
|
||||
CallerLogger.d(TAG,"-----showInputText $asrTextBean")
|
||||
asrTextBean.let {
|
||||
val msg = VoiceMsg(
|
||||
isWakeUp = false,
|
||||
isWakeUpEnd = false,
|
||||
msg = it?.text,
|
||||
isLastMsg = it?.isLast == true,
|
||||
isResp = false
|
||||
)
|
||||
pushMsgBox(msg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 特定View
|
||||
*/
|
||||
override fun showOutPutWidget(callbackWidget: CallbackWidget?) {
|
||||
// todo 咱不支持定制显示 包括天气
|
||||
CallerLogger.d(TAG,"-----showOutPutWidget $callbackWidget")
|
||||
}
|
||||
|
||||
/**
|
||||
* 小智说的
|
||||
*/
|
||||
override fun showOutputText(outPutText: String?) {
|
||||
CallerLogger.d(TAG,"-----showOutputText $outPutText")
|
||||
outPutText?.let {
|
||||
val msg = VoiceMsg(
|
||||
isWakeUp = false,
|
||||
isWakeUpEnd = false,
|
||||
msg = it,
|
||||
isLastMsg = false,
|
||||
isResp = true
|
||||
)
|
||||
pushMsgBox(msg)
|
||||
}
|
||||
}
|
||||
|
||||
private fun pushMsgBox(msg:VoiceMsg){
|
||||
CallerMsgBoxManager.saveMsgBox(MsgBoxBean(MsgBoxType.VOICE, msg))
|
||||
}
|
||||
|
||||
enum class AnimalState {
|
||||
Normal,
|
||||
NORMAL_SPEAK,
|
||||
SPEAK_NORMAL,
|
||||
SPEAK,
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |