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