Open badboy-tian opened 6 months ago
我使用自己编写的录音方案,用于记录
1.创建AudioViewModel.kt
`package com.yb.app.webview.ui.audio
import android.media.MediaRecorder import android.net.Uri import android.os.Build import android.os.Environment import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.yb.app.webview.App import com.yb.app.webview.R import com.yb.app.webview.expand.R_string import io.reactivex.Observable import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import java.io.File import java.util.concurrent.TimeUnit
class AudioViewModel : ViewModel() {
companion object {
private const val DIRECTORY_MOVIES = "Recordings"
}
val timerText = MutableLiveData(R.string.view_audio_time_default.R_string())
private var disposable: Disposable? = null
private var startNumber: Long = 0L
private var mediaRecorder: MediaRecorder? = null
private val folder = File(Environment.getExternalStorageDirectory(), DIRECTORY_MOVIES).apply {
if (!exists()) {
mkdirs()
}
}
private var outputFile: File? = null
/**
* 开始或暂停录音
* @return true 开始录音 false 暂停录音
*/
fun startOrPause(): Boolean {
val startOrPauseTimer = startOrPauseTimer()
if (startOrPauseTimer) {
startRecording()
} else {
pauseRecording()
}
return startOrPauseTimer
}
/**
* 重置
*/
fun reset() {
resetTimer()
resetRecording()
}
/**
* 停止录音
*/
fun stop(): Uri {
resetTimer()
return stopRecording()
}
/**
* 开始或者暂停计时器
*/
private fun startOrPauseTimer(): Boolean {
return if (disposable == null) {
disposable = Observable.intervalRange(
startNumber,
Long.MAX_VALUE - startNumber,
0L,
1L,
TimeUnit.MILLISECONDS
)
.subscribeOn(Schedulers.io())
.subscribe {
startNumber = it
val milliseconds = "%02d".format(it/10 % 100)
val seconds = "%02d".format(it / 1000 % 60)
val minute = "%02d".format((it / 1000 / 60) % 60)
val hour = "%02d".format(it / 1000 / 3600)
timerText.postValue("$hour:$minute:$seconds:$milliseconds")
}
true
} else {
disposable?.let {
if (!it.isDisposed) {
it.dispose()
disposable = null
}
}
false
}
}
/**
* 重置计时器
*/
private fun resetTimer() {
disposable?.let {
if (!it.isDisposed) {
it.dispose()
disposable = null
}
}
startNumber = 0L
timerText.postValue(R.string.view_audio_time_default.R_string())
}
/**
* 开始录音
*/
private fun startRecording() {
if (mediaRecorder == null) {
mediaRecorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
MediaRecorder(App.getApplication())
} else {
MediaRecorder()
}
mediaRecorder?.let {
outputFile =
File(folder.absoluteFile, "灵云-${System.currentTimeMillis()}.wav").apply {
if (!exists()) {
createNewFile()
}
}
it.setAudioSource(MediaRecorder.AudioSource.MIC)
it.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
it.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
it.setOutputFile(outputFile)
it.prepare()
it.start()
}
} else {
mediaRecorder?.resume()
}
}
/**
* 暂停录音
*/
private fun pauseRecording() {
mediaRecorder?.pause()
}
/**
* 停止录音
*/
private fun stopRecording(): Uri {
mediaRecorder?.let {
it.stop()
it.reset()
it.release()
}
mediaRecorder = null
val uri = Uri.parse(outputFile!!.absolutePath)
outputFile = null
return uri
}
/**
* 重置录音
*/
private fun resetRecording() {
mediaRecorder?.let {
it.stop()
it.reset()
it.release()
}
mediaRecorder = null
outputFile?.delete()
outputFile = null
}
}
2.创建AudioViewModelFactory.kt文件
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class AudioViewModelFactory : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AudioViewModel::class.java)) {
return AudioViewModel() as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
} `
3.创建AudioActivity.kt
` package com.yb.app.webview.ui.audio
import android.app.Activity import android.content.Intent import android.os.Bundle import android.provider.MediaStore import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.ViewModelProvider import com.yb.app.webview.R import com.yb.app.webview.databinding.ActivityAudioBinding
class AudioActivity : AppCompatActivity() {
private lateinit var binding:ActivityAudioBinding
private lateinit var audioViewModel:AudioViewModel
private val backPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
setResult(Activity.RESULT_CANCELED)
finish()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAudioBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.lifecycleOwner = this
audioViewModel = ViewModelProvider(this,AudioViewModelFactory())[AudioViewModel::class.java]
binding.audioData = audioViewModel
binding.recording.setOnClickListener {
val startOrStopTimer = audioViewModel.startOrPause()
if(startOrStopTimer){
it.setBackgroundResource(R.drawable.ic_audio_stop_recording)
}else{
it.setBackgroundResource(R.drawable.ic_audio_recording)
}
}
binding.reset.setOnClickListener {
audioViewModel.reset()
binding.recording.setBackgroundResource(R.drawable.ic_audio_recording)
}
binding.confirm.setOnClickListener {
val uri = audioViewModel.stop()
binding.recording.setBackgroundResource(R.drawable.ic_audio_recording)
val resultIntent = Intent()
resultIntent.data = uri
resultIntent.putExtra(MediaStore.EXTRA_OUTPUT,uri)
setResult(Activity.RESULT_OK, resultIntent)
finish()
}
onBackPressedDispatcher.addCallback(this, backPressedCallback)
}
} ` 4.创建activity_audio.xml文件和AudioActivity对应
`
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="audioData"
type="com.yb.app.webview.ui.audio.AudioViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.audio.AudioActivity">
<TextView
android:id="@+id/timer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{audioData.timerText}"
android:textSize="40sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.4" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHeight_percent="0.3"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1">
<ImageButton
android:id="@+id/reset"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/ic_audio_reset"
android:contentDescription="@string/view_audio_reset"
android:text="@string/view_audio_reset"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/recording"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5" />
<ImageButton
android:id="@+id/recording"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/ic_audio_recording"
android:contentDescription="@string/view_audio_recording"
android:text="@string/view_audio_recording"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/reset"
app:layout_constraintRight_toLeftOf="@+id/confirm"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5" />
<ImageButton
android:id="@+id/confirm"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/ic_audio_confirm"
android:contentDescription="@string/view_audio_recording"
android:text="@string/view_audio_confirm"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/recording"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
` 6.在你的values-> strings.xml中加入
`
<string name="view_audio_reset">重新录音</string>
<string name="view_audio_recording">录音</string>
<string name="view_audio_confirm">确认</string>
`
5.在AndroidManifest.xml文件中的application标签中写入上面创建的Activity
<activity android:name=".ui.audio.AudioActivity" android:exported="false" />
6.写完上述后你会发现缺少如下文件,这些都是图标,你自己去找相应的图标代替即可 (ic_audio_confirm.xml 确认图标 ic_audio_recording.xml 录音图标 ic_audio_reset.xml 重新录音 ic_audio_stop_recording.xml 停止录音)
7.在你的setRecordAudioInterceptListener 监听中如下调用
fragment.startActivityForResult(Intent(fragment.requireActivity(), AudioActivity::class.java),requestCode)
8.完整调用如下
9.录音界面
Current use version?
当前使用的版本是多少?
Will this problem occur in demo?
Demo能否复现这问题?
Describe the problem or provide an error log?
描述问题或提供错误log?
然后发现没有列出系统录音机文件列表, 用系统语音录音后 也没有显示