美文网首页
仿QQ录音语音消息

仿QQ录音语音消息

作者: 宇智波_佐星 | 来源:发表于2022-03-22 10:33 被阅读0次

没啥用但是又值得记录一下的工具类的封装。



先上一张鸡爱夫看看啥效果



个人觉得语音转文字是最鸡肋的一个功能,能发语音为什么要发文字呢???但是产品设计就是如此,当然得按着产品的思路来(⊙o⊙)…

然后是封装思路。封装的意义在于封装性和复用性。所以这里我想的是通过一个管理类+布局来操作。先看管理类:


class ChatInputManagerHelper(private val c:Context,private val container:RelativeLayout) {
    private lateinit var voiceButton:ImageView
    private lateinit var leftButton:ImageView
    private lateinit var rightButton:ImageView
    private lateinit var recordPlayButton:ImageView
    private lateinit var loVoice:View
    private lateinit var loWord:View
    private lateinit var loRecord:View
    private lateinit var voiceLong:TextView
    private lateinit var tvWord:TextView
    private lateinit var btCancelWord:View
    private lateinit var btSendWord:TextView
    private lateinit var replayTime:TextView
    private lateinit var btCancelVoice:View
    private lateinit var btSendVoice:TextView
    private lateinit var level1:ImageView
    private lateinit var level2:ImageView
    private lateinit var tvProgress:TextView
    private lateinit var tvHint:TextView
    private lateinit var loProgress:View
    private lateinit var bottomHint:TextView
    private lateinit var tvChangeStatus:TextView
     var callback: ShowInputCallBack?=null
    private var leftBtX1=0
    private var leftBtX2=0
    private var leftBtY1=0
    private var leftBtY2=0
    private var centerBtX1=0
    private var centerBtX2=0
    private var centerBtY1=0
    private var centerBtY2=0
    private var rihgtBtX1=0
    private var rightBtX2=0
    private var rightBtY1=0
    private var rightBtY2=0

    private var recordTime=0
    var mAudioRecorderUtils: AudioRecorderUtils? = null
    private var voiceFile:String?=null
    private var tag=-1//-1初始状态 0-按下状态 1-按下语音 2-按下转文字 3-按下取消
    var type=1//1-在线咨询 2随访管理
    private var isDown=false
    private var  aniCom: CompositeDisposable?=null //动画Com

    private var isWordOk=true;//是否有文字
    var messageSendListener: EmotionInputDetector.MessageSendListener? = null
    private var isPlaying=false;
    private var isLock=false
    var isNotChat:Boolean?=false// true -不是聊天 false 是聊天
    init {
        voiceButton = container.findViewById(R.id.voiceButton)
        leftButton = container.findViewById(R.id.buttonLeft)
        rightButton = container.findViewById(R.id.buttonRight)
        recordPlayButton=container.findViewById(R.id.recordButton)
        loVoice=container.findViewById(R.id.loVoice)
        loWord=container.findViewById(R.id.loWord)
        loRecord=container.findViewById(R.id.loRecord)
        voiceLong=container.findViewById(R.id.voiceLong)
        tvWord=container.findViewById(R.id.tvWord)
        btCancelWord=container.findViewById(R.id.btCancelWord)
        btSendWord=container.findViewById(R.id.btSendWord)
        replayTime=container.findViewById(R.id.replayTime)
        btCancelVoice=container.findViewById(R.id.btCancelVoice)
        btSendVoice=container.findViewById(R.id.btSendVoice)
        level1=container.findViewById(R.id.level1)
        level2=container.findViewById(R.id.level2)
        tvProgress=container.findViewById(R.id.tvProgress)
        tvHint=container.findViewById(R.id.tvHintVoice)
        loProgress=container.findViewById(R.id.loProgress)
        bottomHint=container.findViewById(R.id.bottomHint)
        tvChangeStatus=container.findViewById(R.id.tvChangeStatus)

        aniCom=CompositeDisposable()

        enable()
    }

    private fun aning(){
        aniCom?.clear()
        tvChangeStatus.visibility=View.VISIBLE
        Observable.intervalRange(1,100,0,500, TimeUnit.MILLISECONDS)
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(object : SimplerObserver2<Long>(c,false){
                override fun onNext(t: Long) {
                    if (tvChangeStatus.text.toString().equals("正在识别.")){
                        tvChangeStatus.text="正在识别.."

                    }else if (tvChangeStatus.text.toString().equals("正在识别..")){
                        tvChangeStatus.text="正在识别..."

                    }else{
                        tvChangeStatus.text="正在识别."
                    }
                }
                override fun onSubscribe(d: Disposable) {
                    super.onSubscribe(d)
                    aniCom?.add(d)
                }
            })
    }

    fun enable() {
        //初始化视图

        val locationLeft = IntArray(2)
        leftButton.getLocationOnScreen(locationLeft)

        leftBtX1 = locationLeft[0];
        leftBtX2 = locationLeft[0] + leftButton.width
        leftBtY1 = locationLeft[1]
        leftBtY2 = locationLeft[1] + leftButton.height

        voiceButton.getLocationOnScreen(locationLeft)

        centerBtX1 = locationLeft[0];
        centerBtX2 = locationLeft[0] + voiceButton.width
        centerBtY1 = locationLeft[1]
        centerBtY2 = locationLeft[1] + voiceButton.height

        rightButton.getLocationOnScreen(locationLeft)
        rihgtBtX1 = locationLeft[0];
        rightBtX2 = locationLeft[0] + rightButton.width
        rightBtY1 = locationLeft[1]
        rightBtY2 = locationLeft[1] + rightButton.height
        //初始化功能
        mAudioRecorderUtils = AudioRecorderUtils()
        bottomHint.text="为了保障服务质量,请您吐字清晰,尽量说普通话"

        mAudioRecorderUtils?.setOnAudioStatusUpdateListener(object :
            AudioRecorderUtils.OnAudioStatusUpdateListener {
            override fun onUpdate(db: Double, time: Long) {
                if ((time/1000)>59){
                    mAudioRecorderUtils?.stopRecord()
                    return
                }
                if (tag==0){
                    mAudioRecorderUtils?.stopRecord()
                    goBackNormal()
                }else{
                    val toInt = ( 200 * (db-35) ).toInt()
                    level1.drawable.level = (toInt)
                    level2.drawable.level = toInt
                    tvProgress.text = Utils.long2String(time)

                    if (tag!=2&&tag!=3){
                        loProgress.visibility= View.VISIBLE
                        tvHint.visibility=View.GONE
                        tvHint.text=""
                        bottomHint.text="为了保障服务质量,请您吐字清晰,尽量说普通话"
                    }
                }

            }
            override fun onStop(time: Long, filePath: String?) {
                recordTime = (time / 1000).toInt()
                voiceFile=filePath
                if (tag==3){
                    goBackNormal()
                    return
                }
                if (time < 1000) {
                        goBackNormal()
                    return
                }
                when (tag) {
                    1 -> {
                        //发送
                        loVoice.visibility = View.GONE
                        loWord.visibility = View.GONE
                        loRecord.visibility = View.VISIBLE
                        replayTime.text=
                            Utils.long2String(time)

                    }
                    2 -> {
                        //转文字  这里应该开启一个线程去操作转文字的业务 ,操作中先展示转换中。。。并且转换可能还会失败的状态

                        //先将语音部分隐藏,显示文字部分
                        loVoice.visibility = View.GONE
                        loRecord.visibility = View.GONE
                        loWord.visibility = View.VISIBLE
                        val min = recordTime / 60
                        val sec = recordTime % 60
                        if (min>0){
                            voiceLong.text = "${min}′${sec}″"
                        }else{
                            voiceLong.text = "${sec}″"
                        }
                        //然后应该先将转换结果隐藏
                        tvWord.visibility=View.GONE
                        //开始操作耗时业务
                        //开始操作耗时业务
                        // 先上传
                        aning()
                        btSendWord.isClickable=false

                        OssUpHelper.upFile(c,File(filePath?:""),object : OssUpHelper.OssUPCallBack{
                            override fun onError(msg: String) {
                                super.onError(msg)
                                aniCom?.clear()
                                //转换失败
                                isWordOk=false
                                tvChangeStatus.visibility=View.GONE
                                tvWord.visibility=View.VISIBLE
                                btSendWord.text="${if (isNotChat==true) "保存" else "发送"}语音"
                                btSendWord.isClickable=true

                                tvWord.text="未识别出文字"
                            }
                            override fun upResponse(url: String) {
                                Api.changeToWord(url)?.subscribe(object : CustomAction<String>(c){
                                    override fun httpSuccess(t: String?) {
                                        aniCom?.clear()
                                        btSendWord.isClickable=true
                                        if (!t.isNullOrEmpty()){
                                            //成功
                                            //完成之后 显示结果
                                            isWordOk=true
                                            btSendWord.text="${if (isNotChat==true) "保存文字" else "发送"}"
                                            tvChangeStatus.visibility=View.GONE
                                            tvWord.visibility=View.VISIBLE
                                            tvWord.text=t?:""
                                        }else{
                                            //转换失败
                                            isWordOk=false
                                            tvChangeStatus.visibility=View.GONE
                                            tvWord.visibility=View.VISIBLE
                                            btSendWord.text="${if (isNotChat==true) "保存" else "发送"}语音"

                                            tvWord.text="未识别出文字"
                                        }
                                    }

                                    override fun httpErro(
                                        code: Int,
                                        message: String,
                                        data: String?
                                    ): Boolean {
                                        aniCom?.clear()
                                        //转换失败
                                        isWordOk=false
                                        tvChangeStatus.visibility=View.GONE
                                        tvWord.visibility=View.VISIBLE
                                        btSendWord.text="${if (isNotChat==true) "保存" else "发送"}语音"
                                        btSendWord.isClickable=true

                                        tvWord.text="未识别出文字"

                                        return false
                                    }
                                })
                            }
                        })

                    }
                    3 -> {
                        //取消
                        loVoice.visibility = View.VISIBLE
                        loWord.visibility = View.GONE
                        loRecord.visibility = View.GONE
                    }
                }
            }

            override fun onError() {
            }


        })
        //按钮
        recordPlayButton.setDelayClickListener {
            if (isPlaying){


                MediaManager.release()
                recordPlayButton.setImageResource(R.mipmap.icon_chat_play)
                isPlaying=false


            }else{
                recordPlayButton.setImageResource(R.mipmap.icon_chat_stop)
                MediaManager.playSound(voiceFile, MediaPlayer.OnCompletionListener {
                    //播放完毕
                    isPlaying=false
                    replayTime.text= Utils.long2String(recordTime*1000L)

                    recordPlayButton.setImageResource(R.mipmap.icon_chat_play)
                    MediaManager.clearTimer()
                }, object :MediaManager.PlayCallBack{
                    override fun onPro(pro: Int) {
                        (c as Activity).runOnUiThread{
                            isPlaying=true
                            replayTime.text= Utils.long2String((recordTime-pro)*1000L)
                        }
                    }

                    override fun onPro(progress: Float) {
                    }
                })
            }
        }
        btSendVoice.setDelayClickListener {
            //发送语音
            if (type == 1) {
                //在线咨询发送
                val bean = OnlineMessageBean()
                bean.content = "语音"
                bean.messageType = OnlineMessageBean.SendVoice
                bean.url = voiceFile
                bean.voiceLength = recordTime
                bean.showVoiceLength =recordTime
                bean.time =
                    TimeUtil.getYMdTime(System.currentTimeMillis())
                if (bean.voiceLength < 1) {
                    "语音过短了".Toast()
                } else {
                    messageSendListener?.sendOnlineMessage(bean)
                }
            }
            goBackNormal()

        }
        btSendWord.setDelayClickListener {
            isLock=false
            if (type == 1) {
                //在线咨询
                if (isWordOk) {
                    //可以发文字
                    //在线咨询发送
                    val bean = OnlineMessageBean()
                    bean.content = tvWord.text.toString()
                    bean.messageType = OnlineMessageBean.SendText
                    bean.url = ""
                    bean.time = TimeUtil.getYMdTime(System.currentTimeMillis())
                    messageSendListener?.sendOnlineMessage(bean)
                } else {
                    val bean = OnlineMessageBean()
                    bean.content = "语音"
                    bean.messageType = OnlineMessageBean.SendVoice
                    bean.url = voiceFile
                    bean.voiceLength = recordTime
                    bean.showVoiceLength = recordTime
                    bean.time = TimeUtil.getYMdTime(System.currentTimeMillis())
                    if (bean.voiceLength < 1) {
                        "语音过短了".Toast()
                    } else {
                        messageSendListener?.sendOnlineMessage(bean)
                    }
                }
            }
            goBackNormal()
//            hide()
        }
        btCancelVoice.setDelayClickListener {
            goBackNormal()
        }
        btCancelWord.setDelayClickListener {
            goBackNormal()
            isLock=false
        }
        tvWord.setDelayClickListener {
            isLock=true
            callback?.isShow(true)
        }
    }


    @SuppressLint("ClickableViewAccessibility")
    fun initUtil(){
        container.setOnTouchListener { v, event ->
            val rawX = event.rawX
            val rawY = event.rawY
            if (event.action==MotionEvent.ACTION_DOWN){
                //按下时
                //如果按下语音按钮
                if (rawX>=centerBtX1&&rawX<=centerBtX2&&rawY>=centerBtY1&&rawY<=centerBtY2){
                    SystemUtil.vibe(c)
                    voiceButton.scaleAnima {
                        leftButton.slowlyShow()
                        rightButton.slowlyShow()
                        voiceButton.setImageResource(R.mipmap.icon_oline_voice_green)
                        tag=1
                        isDown=true
                        Thread(Runnable {
                            if (!RxpermissionUtil.checkPer(Manifest.permission_group.STORAGE)||
                                !RxpermissionUtil.checkPer(Manifest.permission.RECORD_AUDIO)){
                                RxpermissionUtil.requestPer(arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE))
                                    ?.subscribe {
                                            b->
                                        if (b){
                                            mAudioRecorderUtils?.startRecord(c)

                                        }
                                    }
                            }else{
                                mAudioRecorderUtils?.startRecord(c)

                            }

                        }).start()
                       /* if (tag==0){

                        }else{
                            mAudioRecorderUtils?.stopRecord()
                        }*/

                    }

                }
                false
            }else if (event.action==MotionEvent.ACTION_MOVE){
                if (isDown){
                    //暗中语音 时 当按在左边

                    if (rawX>=leftBtX1&&rawX<=leftBtX2&&rawY>=leftBtY1&&rawY<=leftBtY2){
                        if (tag!=2){
                            loProgress.visibility= View.GONE
                            tvHint.visibility=View.VISIBLE
                            tvHint.text="松手转文字"
                            leftButton.setImageResource(R.mipmap.icon_chat_toword_blue)
                            SystemUtil.vibe(c)
                        }
                        bottomHint.text="为了保障服务质量,请您吐字清晰,尽量说普通话"

                        tag=2

                    }else{
                        leftButton.setImageResource(R.mipmap.icon_chat_toword)
                        //当没按在左边的时候 判断是否按在右边
                            if (rawX>=rihgtBtX1&&rawX<=rightBtX2&&rawY>=rightBtY1&&rawY<=rightBtY2){
                                if (tag!=3){
                                    loProgress.visibility= View.GONE
                                    tvHint.visibility=View.VISIBLE
                                    tvHint.text="松手取消发送"
                                    SystemUtil.vibe(c)
                                    rightButton.setImageResource(R.mipmap.icon_chat_cancel_red)
                                }
                                tag=3

                        }else{
                                rightButton.setImageResource(R.mipmap.icon_chat_cancel)
                                //等于都没按
                                tag=1
                                loProgress.visibility= View.VISIBLE
                                tvHint.visibility=View.GONE
                                tvHint.text=""
                                leftButton.setImageResource(R.mipmap.icon_chat_toword)

                        }
                    }
                }

            }
            else if (event.action==MotionEvent.ACTION_UP){
                isDown=false
                mAudioRecorderUtils?.stopRecord()
                voiceButton.tag=null
                if (tag==3){
                    //取消

                }else if (tag==2){
                    //转文字


                }else if (tag==1){
                    //发送

                }
                //恢复初始状态
                voiceButton.setImageResource(R.mipmap.icon_oline_voice)
                leftButton.setImageResource(R.mipmap.icon_chat_toword)
                rightButton.setImageResource(R.mipmap.icon_chat_cancel)
                tvProgress.text="00:00"
                loProgress.visibility=View.GONE
                tvHint.visibility=View.VISIBLE
                tvHint.text="长按开始说话"
                leftButton.slowlyGone()
                rightButton.slowlyGone()
                false
            }
            true
        }
    }

    fun show(){
        container.visibility=View.VISIBLE
        isLock=false
    }

    fun hide(){
        container.visibility=View.GONE
        if (!isLock)
        goBackNormal()
    }

    //回到初始状态
    private fun goBackNormal() {
        btSendWord.isClickable=true
        loVoice.visibility=View.VISIBLE
        loRecord.visibility=View.GONE
        loWord.visibility=View.GONE
        tvHint.text="长按开始说话"
        tvHint.visibility=View.VISIBLE
        loProgress.visibility=View.GONE
        btSendWord.text= if (isNotChat==true) "确认" else "发送"
        isWordOk=true
    }
    interface ShowInputCallBack{
        fun isShow(b:Boolean)
    }
}

其实代码还是比较简单,主要的思路就是对ontouch的action的追踪和状态的界定,这其实那张纸理一下就很清晰了。其他就不赘述了。对外暴露的东西比较少,需要的自己拿去改吧改吧应该还行。
下面上xml:

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="@drawable/white_5r"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="230dp">
    <RelativeLayout
        android:id="@+id/loVoice"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout

            android:id="@+id/loProgress"
            android:orientation="horizontal"
            android:layout_centerHorizontal="true"
            android:visibility="gone"
            android:layout_marginTop="12dp"
            android:layout_width="wrap_content"
            android:gravity="center_vertical"
            android:layout_height="wrap_content">
            <ImageView

                android:id="@+id/level1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:rotation="180"
                android:src="@drawable/record_online_left" />
            <TextView
                android:id="@+id/tvProgress"
                android:layout_marginLeft="4dp"
                android:layout_marginRight="4dp"
                android:text="00:00"
                android:textSize="15dp"
                android:textColor="@color/text_aaa"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            <ImageView
                android:id="@+id/level2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/record_online_left" />
        </LinearLayout>
        <TextView
            android:id="@+id/tvHintVoice"
            android:text="长按开始说话"
            android:textColor="@color/text_777"
            android:textSize="15dp"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="12dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <ImageView
            android:id="@+id/buttonLeft"
            android:src="@mipmap/icon_chat_toword"
            android:layout_marginTop="56dp"
            android:visibility="invisible"
            android:scaleType="centerInside"
            android:layout_marginLeft="30dp"
            android:layout_width="62dp"
            android:layout_height="62dp"/>
        <ImageView
            android:id="@+id/voiceButton"
            android:src="@mipmap/icon_oline_voice"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="54dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <ImageView
            android:id="@+id/buttonRight"
            android:src="@mipmap/icon_chat_cancel"
            android:visibility="invisible"
            android:layout_alignParentRight="true"
            android:layout_marginRight="40dp"
            android:layout_marginTop="56dp"
            android:scaleType="centerInside"
            android:layout_width="62dp"
            android:layout_height="62dp"/>
        <TextView
            android:id="@+id/bottomHint"
            android:textSize="12dp"
            android:textColor="@color/text_aaa"
            android:layout_centerHorizontal="true"
            android:layout_below="@id/voiceButton"
            android:layout_marginTop="13dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </RelativeLayout>
    <RelativeLayout
        android:id="@+id/loWord"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/voiceLong"
            android:layout_marginTop="12dp"
            android:layout_centerHorizontal="true"
            android:background="@drawable/_f5_125r"
            android:text="10″"
            android:drawablePadding="4dp"
            android:textSize="15dp"
            android:drawableLeft="@mipmap/icon_bl"
            android:gravity="center_vertical"
            android:paddingLeft="8dp"
            android:paddingTop="5dp"
            android:paddingBottom="5dp"
            android:paddingRight="8dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <TextView
            android:id="@+id/tvWord"
            android:text="3213213213213"
            android:textColor="@color/text_333"
            android:textSize="14dp"
            android:layout_below="@id/voiceLong"
            android:layout_above="@id/lobt1"
            android:layout_marginTop="7dp"
            android:layout_marginLeft="15dp"
            android:layout_marginRight="15dp"
            android:layout_width="match_parent"
            android:layout_height="107dp"/>
        <TextView
            android:id="@+id/tvChangeStatus"
            android:visibility="gone"
            android:text="正在识别..."
            android:layout_centerInParent="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <LinearLayout
            android:id="@+id/lobt1"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:paddingLeft="15dp"
            android:paddingRight="15dp"
            android:background="@color/white"
            android:paddingTop="9dp"
            android:paddingBottom="14dp"
            android:layout_alignParentBottom="true"

            android:layout_height="wrap_content">
            <TextView
                android:id="@+id/btCancelWord"
                android:textSize="14dp"
                android:gravity="center"
                android:layout_marginRight="8dp"
                android:textColor="@color/textBlue"
                android:background="@drawable/f5_22r"
                android:text="取消"
                android:layout_weight="1"
                android:layout_width="0dp"
                android:layout_height="36dp"/>
            <TextView
                android:id="@+id/btSendWord"
                android:textSize="14dp"
                android:gravity="center"
                android:textColor="@color/white"
                android:background="@drawable/_009aff_22r"
                android:text="确认"
                android:layout_weight="1"
                android:layout_width="0dp"
                android:layout_height="36dp"/>
        </LinearLayout>
    </RelativeLayout>
    <RelativeLayout
        android:id="@+id/loRecord"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/replayTime"
            android:textColor="@color/text_777"
            android:textSize="15dp"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="12dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <ImageView
            android:id="@+id/recordButton"
            android:src="@mipmap/icon_chat_play"
            android:layout_centerInParent="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <LinearLayout
            android:id="@+id/lobt2"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:paddingLeft="15dp"
            android:paddingRight="15dp"
            android:background="@color/white"
            android:paddingTop="9dp"
            android:paddingBottom="14dp"
            android:layout_alignParentBottom="true"

            android:layout_height="wrap_content">
            <TextView
                android:id="@+id/btCancelVoice"
                android:textSize="14dp"
                android:gravity="center"
                android:layout_marginRight="8dp"
                android:textColor="@color/textBlue"
                android:background="@drawable/f5_22r"
                android:text="取消"
                android:layout_weight="1"
                android:layout_width="0dp"
                android:layout_height="36dp"/>
            <TextView
                android:id="@+id/btSendVoice"
                android:textSize="14dp"
                android:gravity="center"
                android:textColor="@color/white"
                android:background="@drawable/_009aff_22r"
                android:text="确认"
                android:layout_weight="1"
                android:layout_width="0dp"
                android:layout_height="36dp"/>
        </LinearLayout>
    </RelativeLayout>
</RelativeLayout>

当然xml这只是看一下,OK这次就这样了


相关文章

网友评论

      本文标题:仿QQ录音语音消息

      本文链接:https://www.haomeiwen.com/subject/jfqfjrtx.html