Android录音功能、播放、录制、圆形图形

创建一个AudioManger类(录音类)此类参考过其他大佬的代码修改进行修改的

在这里插入图片描述

open class AudioManger {

    private var mMediaRecorder: MediaRecorder? = null
    private var mDir: String? = null
    private var mCurrentFilePath: String? = null


    private var isPrepared = false

    //Recorder准备好录音后的回调
    interface AudioStateListener {
        fun wellPrepared()
    }


    var mListener: AudioStateListener? = null

    fun setOnAudioStateListener(Listener: AudioStateListener?) {
        mListener = Listener
    }

    //构造器 参数:  录音文件夹
    constructor(dir: String) {

        mDir = dir
    }

    companion object{
        private var mInstance: AudioManger? = null
        //单例模式
        fun getInstance(dir: String): AudioManger? {
            if (mInstance == null) {
                synchronized(AudioManger::class.java) {
                    if (mInstance == null) {
                        mInstance = AudioManger(dir)
                    }
                }

            }
            return mInstance
        }

    }



    //准备录音
    fun prepareAudio() {
        try {
            isPrepared = false
            //在录音目录下建立录音文件
            val dir = File(mDir)
            if (!dir.exists()) {
                dir.mkdirs()
            }
            val fileName = generateFileName()
            val file = File(dir, fileName)
            mCurrentFilePath = file.absolutePath
            mMediaRecorder = MediaRecorder()
            //设置输出文件
            mMediaRecorder!!.setOutputFile(mCurrentFilePath)
            //设置音频源为麦克风
            mMediaRecorder!!.setAudioSource(MediaRecorder.AudioSource.MIC)
            //设置音频格式api>=10使用amr_nb 小于10使用raw_amr
            mMediaRecorder!!.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB)
            //设置音频的编码为amr
            mMediaRecorder!!.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
            //准备录制
            mMediaRecorder!!.prepare()

            //准备好录制
            isPrepared = true
            if (mListener != null) {
                mListener!!.wellPrepared()
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    fun start() {
        if (isPrepared) {
            //开始录制
            mMediaRecorder!!.start()
        }
    }

    //随机生成文件名称
    private fun generateFileName(): String {
        return System.currentTimeMillis().toString() + ".amr"
    }

    //获取音量大小  maxLevel为最大等级
    fun getVoiceLevel(maxLevel: Int): Int {
        if (isPrepared) {
            try {
                //mMediaRecorder.getMaxAmplitude() 1-32767
                return maxLevel * mMediaRecorder!!.maxAmplitude / 32768 + 1
            } catch (e: Exception) {
            }
        }
        return 1
    }

    fun release() {
        try {
            if (mMediaRecorder != null) {
                mMediaRecorder!!.stop()
                mMediaRecorder!!.release()
                mMediaRecorder = null
            }
        }catch (e:java.lang.Exception){

        }

    }

    fun cancel() {
        release()
        //如果取消就删除生成的录音文件
        if (mCurrentFilePath != null) {
            val file = File(mCurrentFilePath)
            file.delete()
            mCurrentFilePath = null
        }
    }

    fun getCurrentFilePath(): String? {
        return mCurrentFilePath
    }
}

创建一个MediaManager 类(播放音频类)

class MediaManager : MediaPlayer.OnSeekCompleteListener {
    private var isPause = false
     var isFinish = false
    private var mMediaPlayer: MediaPlayer? = null
    protected var mOnPlayerListener :onPlayerListener ?=null
    companion object{
        private var mediaManager: MediaManager? = null

        fun getInstance():MediaManager{
            if (mediaManager == null) {
                synchronized(MediaManager::class.java) {
                    if (mediaManager == null) {
                        mediaManager = MediaManager()
                    }
                }
            }
            return mediaManager!!

        }
    }

    interface onPlayerListener : MediaPlayer.OnCompletionListener{
        /**
         * @param duration 总时间长
         * @param currentPosition 当前市场
         */
        fun updateSeekBar(duration:Int,currentPosition:Int)
    }
    fun playSound(soundPath: String?, onCompletionListener: onPlayerListener?) {
        if (mMediaPlayer == null) {
            mMediaPlayer = MediaPlayer()
            mMediaPlayer!!.setOnErrorListener { _, what, _ ->
                mMediaPlayer!!.reset()
                LogUtils.wtf("what = $what")

                false

            }
        } else {
            mMediaPlayer!!.reset()
        }
        this.mOnPlayerListener = onCompletionListener
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mMediaPlayer!!.setAudioAttributes(AudioAttributes.Builder()
                    .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
                    .setLegacyStreamType(AudioManager.STREAM_ALARM)
                    .setUsage(AudioAttributes.USAGE_ALARM)
                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                    .build())
        }else {
            mMediaPlayer!!.setAudioStreamType(AudioManager.STREAM_MUSIC)
        }
        mMediaPlayer!!.setOnCompletionListener(onCompletionListener)
        mMediaPlayer!!.setOnSeekCompleteListener(this);

        try {
            mMediaPlayer!!.setDataSource(soundPath)
            mMediaPlayer!!.prepare()
            mMediaPlayer!!.start()

            updateSeekBar()
        } catch (e: IOException) {
            e.printStackTrace()
            LogUtils.wtf(e.message)
        }
    }

    fun pause() {
        if (mMediaPlayer != null && mMediaPlayer!!.isPlaying) {
            mMediaPlayer!!.pause()
            isPause = true
        }
    }

    fun resume() {
        if (mMediaPlayer != null && isPause) {
            mMediaPlayer!!.start()
            isPause = false
        }
    }

    fun release() {
        if (mMediaPlayer != null) {
            mMediaPlayer!!.release()
            mMediaPlayer = null
        }
    }

    fun stop() {
        isFinish =false
        if (mMediaPlayer != null) {
            mMediaPlayer!!.stop()
            mMediaPlayer = null
        }
    }
    fun isPlaying(): Boolean {
        return mMediaPlayer != null && mMediaPlayer!!.isPlaying
    }


    /**
     * 更新SeekBar
     */
    fun updateSeekBar() {

        //获取总时长
        val duration = mMediaPlayer!!.duration
        var handle = Handler()
        //开启线程发送数据
        object : Thread() {
            override fun run() {

                while (!isFinish) {
                    try {
                        sleep(100)
                    } catch (e: InterruptedException) {
                        e.printStackTrace()
                    }
                    var currentPosition =0
                    if (mMediaPlayer !=null) {
                        currentPosition = mMediaPlayer!!.currentPosition
                    }else {
                        currentPosition = duration
                    }
                    handle.post(Runnable {
                        mOnPlayerListener?.updateSeekBar(duration,currentPosition)
                    })
                }
            }
        }.start()
    }

    override fun onSeekComplete(mp: MediaPlayer?) {
       LogUtils.wtf("${mp!!.currentPosition}")
       LogUtils.wtf("${mp!!.duration}")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            LogUtils.wtf("${mp!!.timestamp}")
        }

    }


}

自定义圆形View HDCircleProgressView

class HDCircleProgressView : View ,View.OnClickListener, AudioManger.AudioStateListener {
    private var mMaxProgress = 90 * 10f //90个100ms
    private var mProgress = 0f
    private val mCircleLineStrokeWidth = 3.toFloat()
    private val mRectF: RectF
    private val mPaint: Paint
    private val mPaintHas: Paint
    private var hasMax = 0f
    private val colorBg = "#00000000"
    private val colorHas = "#ffffff"
    private val colorCurtProgress = "#00000000"
    val dir: String = context.cacheDir.toString() + "/audios"
    var isFinish = false
    var isPlayer = false
    var total =  90 * 10f
    var mOnProgressTouchListener : onProgressTouchListener?=null
    var audioManger: AudioManger?=null
    var state = 0
    interface onProgressTouchListener{
        fun start()
        fun finish(total:Float,path:String)
        fun less() //小于三秒钟,提示
    }

    fun setOnProgressTouchListener(mOnProgressTouchListener : onProgressTouchListener){
        this.mOnProgressTouchListener = mOnProgressTouchListener
    }
    constructor(context: Context?) : super(context, null) {
        mRectF = RectF()
        mPaint = Paint()
        mPaintHas = Paint()
        setOnClickListener(this)
        audioManger = AudioManger.getInstance(dir)
        audioManger!!.setOnAudioStateListener(this)

    }

    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs, 0) {
        mRectF = RectF()
        mPaint = Paint()
        mPaintHas = Paint()
        setOnClickListener(this)
        audioManger = AudioManger.getInstance(dir)
        audioManger!!.setOnAudioStateListener(this)

    }

    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        mRectF = RectF()
        mPaint = Paint()
        mPaintHas = Paint()
        setOnClickListener(this)
        audioManger = AudioManger.getInstance(dir)
        audioManger!!.setOnAudioStateListener(this)


    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (state ==1) {
            var width = this.width
            var height = this.height
            if (width != height) {
                val min = Math.min(width, height)
                width = min
                height = min
            }
            canvas.drawColor(Color.TRANSPARENT)

            //设置  当前进度画笔
            mPaint.setAntiAlias(true)
            mPaint.setColor(Color.parseColor(colorBg))
            mPaint.setStrokeWidth(mCircleLineStrokeWidth)
            mPaint.setStyle(Paint.Style.STROKE)
            mPaintHas.setAntiAlias(true)
            mPaintHas.setColor(Color.parseColor(colorBg))
            mPaintHas.setStrokeWidth(mCircleLineStrokeWidth)
            mPaintHas.setStyle(Paint.Style.STROKE)

            //位置
            mRectF.left = mCircleLineStrokeWidth / 2.toFloat()
            mRectF.top = mCircleLineStrokeWidth / 2.toFloat()
            mRectF.right = width - mCircleLineStrokeWidth / 2.toFloat()
            mRectF.bottom = height - mCircleLineStrokeWidth / 2.toFloat()

            //绘制圆圈,进度条背景
            //todo 绘制整个圆圈灰色背景
//        canvas.drawArc(mRectF, (-90).toFloat(), 360.toFloat(), false, mPaint)

            //todo 绘制进度背景
            mPaint.color = Color.parseColor(colorCurtProgress)
            mPaintHas.color = Color.parseColor(colorHas)

            //绘制当前进度1
            if (hasMax <= mProgress) {
                canvas.drawArc(mRectF, (-90).toFloat(), mProgress.toFloat() / mMaxProgress * 360, false, mPaintHas)
                hasMax = mProgress
            } else {
                canvas.drawArc(mRectF, (-90).toFloat(), hasMax.toFloat() / mMaxProgress * 360, false, mPaintHas)
            }

            //绘制当前进度2
//        canvas.drawArc(mRectF, (-90).toFloat(), mProgress.toFloat() / mMaxProgress * 360, false, mPaint)
            canvas.save()

            //画布移动到中心点
            canvas.translate(width / 2.toFloat(), height / 2.toFloat())
            mPaint.color = Color.parseColor(colorCurtProgress)
            //计算圆的位置
            val radius = width / 2 - mCircleLineStrokeWidth / 2.toFloat() //绘制圆的半径等于矩形的宽度 - 线的一半宽度
            val angle = 1.0f * mProgress / mMaxProgress * Math.PI * 2
            //当前的进度/总进度*360=当前的角度   当前的进度/总进度*2PI=当前的弧度
            val circleX = (radius * Math.cos(angle - Math.PI / 2)).toFloat()
            val circleY = (radius * Math.sin(angle - Math.PI / 2)).toFloat() //从-90°开始计算角度
            //计算圆的半径
            val circleRadius = mCircleLineStrokeWidth / 2.toFloat()
            val angle2 = 1.0f * hasMax / mMaxProgress * Math.PI * 2
            //当前的进度/总进度*360=当前的角度   当前的进度/总进度*2PI=当前的弧度
            val circleY2 = (radius * Math.sin(angle2 - Math.PI / 2)).toFloat() //从-90°开始计算角度
            val circleX2 = (radius * Math.cos(angle2 - Math.PI / 2)).toFloat()

            //绘制末点的圆
            mPaintHas.style = Paint.Style.FILL
            canvas.drawCircle(circleX2, circleY2, circleRadius, mPaintHas)
            mPaint.style = Paint.Style.FILL
            //绘制末点的圆
            canvas.drawCircle(circleX, circleY, circleRadius, mPaint)
            //绘制初始点的圆
            canvas.drawCircle(0.toFloat(), -radius, circleRadius, mPaint)
            canvas.restore()
        }
    }

    fun setProgressNotInUiThread(progress: Float) {
        mProgress = progress
        this.postInvalidate()
    }

    fun getmMaxProgress(): Float {
        return mMaxProgress
    }

    fun setmMaxProgress(mMaxProgress: Float) {
        this.mMaxProgress = mMaxProgress
    }

    fun getmProgress(): Float {
        return mProgress
    }

    fun setmProgress(mProgress: Float) {
        this.mProgress = mProgress


    }
    fun sendMessageDelayed(){
        if (handlers !=null) {
            handlers.sendEmptyMessageDelayed(1, 100)
        }
    }

    fun clear() {
        MediaManager.getInstance().pause()
        MediaManager.getInstance().isFinish =false
        isFinish =false
        hasMax = 0f
        state = 0
        mProgress = 0f
        this.invalidate()
    }
    var startTime = 0L
    var endTime = 0L

    var handlers = Handler(object :Handler.Callback{


        override fun handleMessage(p0: Message): Boolean {
            when(p0!!.what){
                1->{
                    if (getmProgress() + 1 >= 600) {
                        if (mOnProgressTouchListener !=null){
                            if (audioManger!=null){
                                audioManger!!.release()
                            }
                            setBackgroundResource(R.mipmap.ic_sound_end)
                            isFinish = true
                            total= getmProgress() * 100

                            mOnProgressTouchListener!!.finish(total,audioManger!!.getCurrentFilePath()!!)
                            play(audioManger!!.getCurrentFilePath()!!)

                        }

                    } else if (state == 1) {
                        setProgressNotInUiThread(getmProgress() + 1)
                        sendMessageDelayed()
                    }
                }
            }
            return true
        }

    })

    override fun onClick(v: View?) {

        if (isPlayer) //正在播放
            return

        if (isFinish){ //播放完成
            play(audioManger!!.getCurrentFilePath()!!)
            return
        }
        when(state){
            MotionEvent.ACTION_DOWN->{

                val vb = context. getSystemService(Service.VIBRATOR_SERVICE) as Vibrator?
                vb!!.vibrate(longArrayOf(50, 100), -1)
                setmMaxProgress(600f)
                state = MotionEvent.ACTION_MOVE
                if (audioManger!=null ){
                    audioManger!!.prepareAudio()
                }

            }
            MotionEvent.ACTION_UP->{
                handlers.removeMessages(1)
                handlers.sendEmptyMessage(2)
                setBackgroundResource(R.mipmap.ic_sound_end)

                if (mOnProgressTouchListener !=null){
                    if (getmProgress() * 100 < 3000) {
                        clear()
                        if (audioManger!=null){
                            audioManger!!.cancel()
                        }
                        state = 0
                        mOnProgressTouchListener!!.less()
                        setBackgroundResource(R.mipmap.ic_sound_ready)
                    }else if (!isFinish){
                        if (audioManger!=null){
                            audioManger!!.release()
                        }
                        total= getmProgress() * 100
                        isFinish = true
                        mOnProgressTouchListener!!.finish(total ,audioManger!!.getCurrentFilePath()!!)
                        play(audioManger!!.getCurrentFilePath()!!)
                    }
                }

            }
            MotionEvent.ACTION_MOVE->{
                if (mOnProgressTouchListener !=null){
                    if (getmProgress() * 100 < 3000) {
                        clear()
                        if (audioManger!=null){
                            audioManger!!.cancel()
                        }
                        state =  MotionEvent.ACTION_DOWN
                        mOnProgressTouchListener!!.less()
                        setBackgroundResource(R.mipmap.ic_sound_ready)
                    }else if (!isFinish){

                        handlers.removeMessages(1)
                        handlers.sendEmptyMessage(2)
                        handlers.sendEmptyMessage(3)

                        setBackgroundResource(R.mipmap.ic_sound_end)

                        total= getmProgress() * 100
                        isFinish = true
                        mOnProgressTouchListener!!.finish(total ,audioManger!!.getCurrentFilePath()!!)
                        play(audioManger!!.getCurrentFilePath()!!)

                        if (audioManger!=null){
                            audioManger!!.release()
                        }
                    }
                }
//                setProgressNotInUiThread((System.currentTimeMillis() - startTime / 100).toFloat() )
//                handlers.removeMessages(1)
//                handlers.sendEmptyMessage(2)
//                setBackgroundResource(R.mipmap.ic_sound_end)
            }

        }

    }
//    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
//
//
//
//        return true
//    }

    override fun wellPrepared() {

        if (audioManger!=null){
            audioManger!!.start()
            startTime = System.currentTimeMillis()
            setBackgroundResource(R.mipmap.ic_sound_start)
            handlers.sendEmptyMessageDelayed(1,100)
            state =  MotionEvent.ACTION_UP
            if(mOnProgressTouchListener  != null){
                mOnProgressTouchListener!!.start()
            }
        }
    }



    fun play(path:String){
        setmMaxProgress(total)
        hasMax = 0f
        isPlayer = true
        setProgressNotInUiThread(0f)
        MediaManager.getInstance().isFinish = false
        MediaManager.getInstance().playSound(path,object : MediaManager.onPlayerListener {

            override fun updateSeekBar(duration: Int, currentPosition: Int) {
                setProgressNotInUiThread(currentPosition.toFloat())
                if (currentPosition >= duration){
                    MediaManager.getInstance().release()
                    isPlayer =false
                    MediaManager.getInstance().isFinish = true
                    setProgressNotInUiThread(total)
                }
            }

            override fun onCompletion(mp: MediaPlayer?) {
                if (!mp!!.isPlaying) {
                    MediaManager.getInstance().isFinish = true
                    isPlayer =false
                    setProgressNotInUiThread(total)
                }
                Log.e("TAG","isPlaying  ="+mp!!.isPlaying)
            }

        })

    }


}

显示对话框 SoundRecordingDialog

class SoundRecordingDialog(context: Context) : Dialog(context, R.style.ActionSheetDialogStyle) {
    var mTotal = 0f
    var mPath = ""
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.dialog_sound_recording)
        iv_sound.setmMaxProgress(600f)
        iv_sound.setmProgress(0f)

        setCancelable(false)
        //设置Dialog从窗体底部弹出
        window!!.setGravity(Gravity.BOTTOM)
        //获得窗体的属性
        val lp = window!!.attributes
        lp.width = WindowManager.LayoutParams.MATCH_PARENT
        lp.dimAmount = 0.0f

        //将属性设置给窗体
        window!!.attributes = lp
        tv_close.setOnClickListener {
            dismiss()
        }

        tv_confirm.setOnClickListener {
            dismiss()
        }

        iv_revision.setOnClickListener {
            iv_sound.setmMaxProgress(600f)
            iv_sound.clear()
            iv_sound.setBackgroundResource(R.mipmap.ic_sound_ready)
            iv_revision.visibility = View.GONE
            tv_time.text = "点击录制"
//            tv_time.visibility = View.GONE
            tv_time.setTextColor(context.resources.getColor(android.R.color.black))

            tv_confirm.visibility = View.GONE
        }

        tv_time.setOnClickListener {

        }

        iv_sound.setOnProgressTouchListener(object : HDCircleProgressView.onProgressTouchListener{
            override fun start() {
                tv_time.text= ""
            }

            override fun finish(total: Float,path:String) {
                mTotal = total
                mPath = path
                iv_revision.visibility = View.VISIBLE
                tv_confirm.visibility = View.VISIBLE
                tv_time.text =  String.format("%02d:%02d", (total / 1000 / 60).toInt() , ((total / 1000) % 60).toInt())
                tv_time.setTextColor(context.resources.getColor(android.R.color.white))
            }

            override fun less() {
                iv_revision.visibility = View.GONE

                Toast.makeText(context,"语音时长太短",Toast.LENGTH_LONG).show()
            }

        })
        iv_sound.setmMaxProgress(600f)
        iv_revision.visibility = View.GONE
        tv_confirm.visibility = View.GONE
//        tv_time.visibility = View.GONE
//        tv_time.text = ""
    }



    fun reset(){

    }
    override fun show() {

        super.show()
    }
    override fun dismiss() {
        iv_sound.setBackgroundResource(R.mipmap.ic_sound_ready)
        iv_sound.clear()
        iv_sound.setmMaxProgress(600f)
        iv_revision.visibility = View.GONE
        tv_confirm.visibility = View.GONE
        tv_time.visibility = View.GONE
        tv_time.text = ""
        super.dismiss()
    }



}

xml 布局 dialog_sound_recording.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"

    android:background="@mipmap/ic_sound_bg"

    android:layout_height="wrap_content">

    <RelativeLayout
        android:layout_width="match_parent"

        android:layout_gravity="center_vertical"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_close"
            android:text="取消"
            android:padding="10dp"
            android:layout_marginTop="20dp"
            android:textColor="@android:color/white"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">

        </TextView>

        <TextView
            android:id="@+id/tv_confirm"
            android:text="提交"
            android:layout_marginTop="25dp"

            android:padding="10dp"
            android:textColor="@android:color/white"
            android:layout_alignParentRight="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">

        </TextView>

        <ImageView
            android:id="@+id/iv_revision"
            android:layout_toLeftOf="@+id/iv_sound"
            android:src="@mipmap/ic_sound_again"
            android:layout_centerVertical="true"
            android:layout_width="40dp"
            android:layout_marginRight="60dp"
            android:layout_height="40dp">
        </ImageView>


        <cn.lanru.soundrecording.HDCircleProgressView
            android:id="@+id/iv_sound"
            android:background="@mipmap/ic_sound_ready"
            android:layout_centerInParent="true"
            android:layout_width="90dp"
            android:layout_height="90dp">
        </cn.lanru.soundrecording.HDCircleProgressView>
        <TextView
            android:id="@+id/tv_time"
            android:visibility="visible"
            android:layout_centerHorizontal="true"
            android:layout_below="@+id/iv_sound"
            android:layout_marginTop="15dp"
            android:text="点击录制"
            android:textColor="@android:color/black"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">

        </TextView>


    </RelativeLayout>

</LinearLayout>

个人能力有限,如有写的不对的地方,还望谅解,加油吧,脱发的程序员

git :https://github.com/zqMyself/SoundRecording.git
码云地址 :https://gitee.com/zqmyself/SoundRecording.git

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小曾老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值