增加语音部分业务

This commit is contained in:
qiji4215
2023-04-28 16:16:17 +08:00
parent 977b4b54da
commit defcfb66fb
54 changed files with 1203 additions and 19 deletions

View File

@@ -7,9 +7,11 @@ import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.navigation.NavOptions
import androidx.navigation.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.FragmentEvaluationResultBinding
import com.navinfo.omqs.ui.fragment.BaseFragment
import com.navinfo.omqs.ui.fragment.tasklist.TaskListAdapter
import com.navinfo.omqs.ui.other.shareViewModels
import dagger.hilt.android.AndroidEntryPoint
@@ -17,15 +19,26 @@ import dagger.hilt.android.AndroidEntryPoint
class EvaluationResultFragment : BaseFragment(), View.OnClickListener {
private lateinit var binding: FragmentEvaluationResultBinding
private val viewModel by shareViewModels<EvaluationResultViewModel>("QsRecode")
private val adapter: SoundtListAdapter by lazy {
SoundtListAdapter()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
binding =
DataBindingUtil.inflate(inflater, R.layout.fragment_evaluation_result, container, false)
binding.fragment = this
val layoutManager = LinearLayoutManager(context)
binding.viewModel = viewModel
binding.lifecycleOwner = this
//// 设置 RecyclerView 的固定大小,避免在滚动时重新计算视图大小和布局,提高性能
binding.evaluationVoiceRecyclerview.setHasFixedSize(true)
binding.evaluationVoiceRecyclerview.layoutManager = layoutManager
binding.evaluationVoiceRecyclerview.adapter = adapter
viewModel.listDataChatMsgEntityList.observe(viewLifecycleOwner) {
adapter.refreshData(it)
}
viewModel.getChatMsgEntityList()
return binding.root
}

View File

@@ -1,12 +1,17 @@
package com.navinfo.omqs.ui.fragment.evaluationresult
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.navinfo.collect.library.data.entity.QsRecordBean
import com.navinfo.collect.library.map.NIMapController
import com.navinfo.collect.library.utils.GeometryTools
import com.navinfo.omqs.Constant
import com.navinfo.omqs.bean.Attachment
import com.navinfo.omqs.bean.ChatMsgEntity
import com.navinfo.omqs.db.RealmOperateHelper
import com.navinfo.omqs.db.RoomAppDatabase
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -14,7 +19,6 @@ import io.realm.Realm
import io.realm.kotlin.where
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.locationtech.jts.geom.Point
import java.util.*
import javax.inject.Inject
@@ -47,9 +51,12 @@ class EvaluationResultViewModel @Inject constructor(
*/
val liveDataRightTypeList = MutableLiveData<List<RightBean>>()
var liveDataQsRecordBean = MutableLiveData<QsRecordBean>()
var listDataChatMsgEntityList = MutableLiveData<MutableList<ChatMsgEntity>>()
var listDataAttachmentList = MutableLiveData<MutableList<Attachment>>()
var oldBean: QsRecordBean? = null
init {
@@ -274,4 +281,22 @@ class EvaluationResultViewModel @Inject constructor(
}
}
}
/**
* 查询问题类型列表
*/
@RequiresApi(Build.VERSION_CODES.N)
fun getChatMsgEntityList() {
val chatMsgEntityList: MutableList<ChatMsgEntity> = ArrayList()
liveDataQsRecordBean.value!!.attachments.forEach{
//1 录音
if(it.type==1){
val chatMsgEntity = ChatMsgEntity()
chatMsgEntity.name = it.filename
chatMsgEntity.voiceUri = Constant.USER_DATA_ATTACHEMNT_PATH
chatMsgEntityList.add(chatMsgEntity)
}
}
listDataChatMsgEntityList.postValue(chatMsgEntityList)
}
}

View File

@@ -0,0 +1,241 @@
package com.navinfo.omqs.ui.fragment.evaluationresult
import android.graphics.drawable.AnimationDrawable
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioTrack
import android.media.MediaPlayer
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import com.navinfo.omqs.R
import com.navinfo.omqs.bean.ChatMsgEntity
import com.navinfo.omqs.databinding.AdapterSoundListBinding
import com.navinfo.omqs.ui.other.BaseRecyclerViewAdapter
import com.navinfo.omqs.ui.other.BaseViewHolder
import java.io.*
/**
* 语音 RecyclerView 适配器
*
* 在 RecycleView 的 ViewHolder 中监听 ViewModel 的 LiveData然后此时传递的 lifecycleOwner 是对应的 Fragment。由于 ViewHolder 的生命周期是比 Fragment 短的,所以当 ViewHolder 销毁时,由于 Fragment 的 Lifecycle 还没有结束,此时 ViewHolder 会发生内存泄露(监听的 LiveData 没有解绑)
* 这种场景下有两种解决办法:
*使用 LiveData 的 observeForever 然后在 ViewHolder 销毁前手动调用 removeObserver
*使用 LifecycleRegistry 给 ViewHolder 分发生命周期(这里使用了这个)
*/
class SoundtListAdapter(
) : BaseRecyclerViewAdapter<ChatMsgEntity>() {
//媒体播放器
private val mMediaPlayer = MediaPlayer()
//媒体播放器
private var md: MediaPlayer? = null
private var mAudioTrack: AudioTrack? = null
//录音结束后,右侧显示图片
private var animView: View? = null
//录音时动画效果
private var animaV: AnimationDrawable? = null
private var itemClick: OnItemClickListner? = null
//最大宽度
private val maxWidth = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
val viewBinding =
AdapterSoundListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BaseViewHolder(viewBinding)
}
override fun onViewRecycled(holder: BaseViewHolder) {
super.onViewRecycled(holder)
}
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
val binding: AdapterSoundListBinding =
holder.viewBinding as AdapterSoundListBinding
val entity = data[position]
//tag 方便onclick里拿到数据
holder.tag = entity.name.toString()
holder.viewBinding.tvTime.isSelected = entity.isDelete
holder.viewBinding.rlSoundContent.isSelected = entity.isDelete
holder.viewBinding.ivSoundAnim.setBackgroundResource(R.drawable.icon_sound_03)
if (itemClick != null) {
holder.viewBinding.rlSoundContent.setOnClickListener {
itemClick!!.onItemClick(it.findViewById<View>(R.id.rl_sound_content), position)
}
}
//mixWidth
if (!TextUtils.isEmpty(entity.name)) {
if (entity.name.indexOf(".pcm") > 0) {
val file: File = File(entity.voiceUri + entity.name)
if (file != null) {
val time = (file.length() / 16000).toInt()
val layoutParams: ViewGroup.LayoutParams =
holder.viewBinding.rlSoundContent.getLayoutParams()
layoutParams.width = 115 + time * 10
layoutParams.width =
if (layoutParams.width > layoutParams.width) maxWidth else layoutParams.width
holder.viewBinding.rlSoundContent.setLayoutParams(layoutParams)
holder.viewBinding.tvTime.text = time.toString() + "\""
}
} else {
try {
md = MediaPlayer()
md.reset()
md.setDataSource(entity.getVoiceUri() + entity.getName())
md.prepare()
} catch (e: Exception) {
// TODO Auto-generated catch block
e.printStackTrace()
}
var time =
if (entity.getVoiceTimeLong() == null) md!!.duration.toString() + "" else entity.getVoiceTimeLong()
.toString() + ""
if (!TextUtils.isEmpty(time)) {
val i = md!!.duration / 1000
time = i.toString() + "\""
val layoutParams: ViewGroup.LayoutParams =
holder.viewBinding.rlSoundContent.getLayoutParams()
layoutParams.width = 115 + i * 10
layoutParams.width =
if (layoutParams.width > layoutParams.width) maxWidth else layoutParams.width
holder.viewBinding.rlSoundContent.setLayoutParams(layoutParams)
}
holder.viewBinding.tvTime.text = time
md!!.release()
}
}
override fun getItemViewRes(position: Int): Int {
return R.layout.adapter_sound_list
}
/**
* 播放某段录音
*
* @param view 显示动画
* @param index 录音在集合中索引
*/
fun setPlayerIndex(view: View, index: Int) {
val imageV = view.findViewById<View>(R.id.iv_sound_anim) as ImageView
val width = view.width
if (animView != null) {
animaV?.stop()
animView!!.setBackgroundResource(R.drawable.icon_sound_03)
}
animView = imageV
val entity: ChatMsgEntity = data.get(index)
playMusic(entity.voiceUri + entity.name, imageV)
}
/**
* 播放录音
*
* @param name 录音名称
* @Description
*/
private fun playMusic(name: String, imageV: ImageView) {
imageV.setBackgroundResource(R.drawable.sound_anim)
animaV = imageV.background as AnimationDrawable
animaV!!.start()
if (name.indexOf(".pcm") > 0) {
audioTrackPlay(name, imageV)
} else {
mediaPlayer(name, imageV)
}
}
private fun mediaPlayer(name: String, imageV: ImageView) {
try {
if (mMediaPlayer.isPlaying) {
mMediaPlayer.stop()
}
mMediaPlayer.reset()
mMediaPlayer.setDataSource(name)
mMediaPlayer.prepare()
mMediaPlayer.start()
//播放结束
mMediaPlayer.setOnCompletionListener {
animaV!!.stop()
imageV.setBackgroundResource(R.drawable.icon_sound_03)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun audioTrackPlay(name: String, imageV: ImageView) {
var dis: DataInputStream? = null
try {
//从音频文件中读取声音
dis = DataInputStream(BufferedInputStream(FileInputStream(name)))
} catch (e: FileNotFoundException) {
e.printStackTrace()
}
//最小缓存区
val bufferSizeInBytes = AudioTrack.getMinBufferSize(
16000,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT
)
//创建AudioTrack对象 依次传入 :流类型、采样率与采集的要一致、音频通道采集是IN 播放时OUT、量化位数、最小缓冲区、模式
if (mAudioTrack != null) {
mAudioTrack!!.stop()
mAudioTrack!!.release()
mAudioTrack = null
}
mAudioTrack = AudioTrack(
AudioManager.STREAM_MUSIC,
16000,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT,
bufferSizeInBytes,
AudioTrack.MODE_STREAM
)
val data = ByteArray(bufferSizeInBytes)
mAudioTrack!!.play() //开始播放
while (true) {
var i = 0
try {
while (dis!!.available() > 0 && i < data.size) {
data[i] = dis.readByte() //录音时write Byte 那么读取时就该为readByte要相互对应
i++
}
} catch (e: IOException) {
// TODO Auto-generated catch block
e.printStackTrace()
}
mAudioTrack!!.write(data, 0, data.size)
if (i != bufferSizeInBytes) //表示读取完了
{
break
}
}
mAudioTrack!!.stop() //停止播放
mAudioTrack!!.release() //释放资源
mAudioTrack = null
imageV.post {
animaV?.stop()
imageV.setBackgroundResource(R.drawable.icon_sound_03)
}
}
fun setOnItemClickListener(clickListner: OnItemClickListner) {
itemClick = clickListner
}
interface OnItemClickListner {
fun onItemClick(view: View?, postion: Int)
}
}

View File

@@ -198,7 +198,7 @@ class TaskListAdapter(
}
override fun getItemViewRes(position: Int): Int {
return R.layout.adapter_offline_map_city
return R.layout.adapter_task_list
}
}