123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- <!-- 单词区 && 音标区:最多16位,超过换行 -->
- <!-- 单音节最长:swimming 多音节最长:transportation -->
- <template>
- <view class="words-du-box">
- <selectTypesVue activeSelect="3"></selectTypesVue>
- <view class="du-body-box">
- <!-- 单词区 -->
- <view class="word-circle-box">{{data.name}}</view>
- <!-- 音标区 -->
- <view class="yb-play-box du-yb-play-box">
- <yinbiaoTxtVue :yinbiao="activeWord.yinbiao"></yinbiaoTxtVue>
- <!-- 音频播放 -->
- <audioTwoVue :active-word="activeWord" @play-audio="handlePlay"></audioTwoVue>
- </view>
- <view class="pin-words-explain-box du-words-explain-box">
- <view class="words-explain-item" v-if="data.jianyi&&data.jianyi.length>0" v-for="(item,index) in data.jianyi" :key="index">
- {{item}}
- </view>
- </view>
- </view>
-
- <view class="mike-play-box">
- <view class="mike-play-tip" v-if="isRecording">录音中...</view>
- <view class="mike-play-tip" v-else>长按 读一读</view>
- <!-- <view class="status">{{ recordingStatus }}</view> -->
- <!-- <view class="duration" v-if="isRecording">录音时长: {{ Math.floor(duration) }}秒</view> -->
- <view class="du-btn-box">
- <button class="play-btn" :class="{ 'playing-btn': isPlaying}" @click="playVoice" v-if="voicePath"></button>
- <button class="mike-btn" :class="{ 'mike-az-btn': isRecording}"
- @touchstart="handleTouchStart" @touchend="handleTouchEnd" @touchcancel="handleTouchEnd"
- :disabled="isPlaying">
- <!--{{ isRecording ? '松开结束' : '按住录音' }}
- <view v-if="isPlaying" class="disabled-mask">播放中不可录音</view> -->
- </button>
- </view>
-
- <!-- <button class="play-btn" :class="{ disabled: isRecording || !voicePath }" @click="playVoice"
- :disabled="isRecording || !voicePath">
- {{ isPlaying ? '播放中...' : '播放录音' }}
- <view v-if="isRecording" class="disabled-mask">录音中不可播放</view>
- </button> -->
- </view>
-
- </view>
- </template>
- <script setup>
- import selectTypesVue from './selectTypes.vue';
- import audioTwoVue from './audioTwo.vue';
- import yinbiaoTxtVue from "./yinbiaoTxt.vue"
- import {
- onLoad
- } from "@dcloudio/uni-app"
- import {
- reactive,
- ref,
- onMounted,
- onUnmounted
- } from 'vue';
- const props = defineProps({
- activeWord: {
- type: Object,
- },
- pageData: {
- type: Object,
- },
- activeWords: {
- type: Array
- },
- })
- const emits = defineEmits(['play-audio'])
- let tabFlag = ref(1)
- const audioInfo = ref(null)
- const data = reactive({
- name: '',
- yinbiao: '',
- jianyi: []
- })
- // 录音相关状态
- const isRecording = ref(false)
- const isPlaying = ref(false)
- const voicePath = ref('')
- const duration = ref(0)
- const timer = ref(null)
- const recordingStatus = ref('准备就绪')
- const startTime = ref(0)
- const longPressTimer = ref(null) // 长按计时器
- // 获取录音和音频播放管理器
- const recorderManager = uni.getRecorderManager()
- const innerAudioContext = uni.createInnerAudioContext()
- // 初始化录音器
- const initRecorder = () => {
- recorderManager.onStart(() => {
- console.log('recorder start')
- isRecording.value = true
- // recordingStatus.value = '录音中...'
- // startTime.value = Date.now()
- // startTimer()
- })
- recorderManager.onStop((res) => {
- // const recordTime = (Date.now() - startTime.value) / 1000 // 计算实际录音时间
- isRecording.value = false
- // if (recordTime < 3) {
- // uni.showToast({
- // title: '录音时间太短,需3秒以上',
- // icon: 'none'
- // })
- // return
- // }
- if (res.tempFilePath) {
- voicePath.value = res.tempFilePath
- uni.showToast({
- title: '录音成功',
- icon: 'success'
- })
- } else {
- uni.showToast({
- title: '录音失败',
- icon: 'none'
- })
- }
- })
- recorderManager.onError((res) => {
- console.error('recorder error', res)
- //stopTimer()
- isRecording.value = false
- //recordingStatus.value = `录音错误: ${res.errMsg}`
- uni.showToast({
- title: `录音出错: ${res.errMsg}`,
- icon: 'none'
- })
- })
- innerAudioContext.onPlay(() => {
- isPlaying.value = true
- //recordingStatus.value = '播放中...'
- })
- innerAudioContext.onEnded(() => {
- isPlaying.value = false
- //recordingStatus.value = '播放完成'
- })
- innerAudioContext.onError((res) => {
- isPlaying.value = false
- console.error('play error', res)
- uni.showToast({
- title: '播放失败',
- icon: 'none'
- })
- })
- }
- async function handlePlay(opt) {
- emits('play-audio', opt)
- }
- // 检查权限
- const checkPermission = () => {
- uni.authorize({
- scope: 'scope.record',
- success: () => {
- console.log('已授权录音权限')
- },
- fail: (err) => {
- console.log('未授权录音权限', err)
- uni.showModal({
- title: '提示',
- content: '需要录音权限才能使用此功能',
- confirmText: '去设置',
- success: (res) => {
- if (res.confirm) {
- uni.openSetting()
- }
- }
- })
- }
- })
- }
- // 处理触摸开始(移动端)
- const handleTouchStart = (e) => {
- e.preventDefault()
- if (isPlaying.value) return
- // 设置长按计时器,500ms后才开始录音
- longPressTimer.value = setTimeout(() => {
- startRecording()
- }, 400)
- }
- // 处理触摸结束(移动端)
- const handleTouchEnd = (e) => {
- e.preventDefault()
- clearTimeout(longPressTimer.value)
- if (isRecording.value) {
- endRecording()
- }
- }
- // 处理触摸取消(移动端,如被系统中断)
- const handleTouchCancel = (e) => {
- handleTouchEnd(e) // 与touchend同样处理
- }
- // 开始录音
- const startRecording = () => {
- if (isRecording.value) return
- console.log('开始录音')
- //recordingStatus.value = '准备录音...'
- isRecording.value = true // 提前设置状态,避免延迟
- // startTime.value = Date.now()
- // startTimer()
- const options = {
- duration: 60000,
- sampleRate: 44100,
- numberOfChannels: 1,
- encodeBitRate: 192000,
- format: 'mp3',
- frameSize: 50
- }
- recorderManager.start(options)
- }
- // 结束录音
- const endRecording = () => {
- if (!isRecording.value) return
- console.log('停止录音')
- isRecording.value = false
- //stopTimer()
- recorderManager.stop()
- }
- const getRecordingDuration = () => {
- if (!isRecording.value) return 0
- return Math.floor((Date.now() - startTime.value) / 1000)
- }
- // 播放录音
- const playVoice = () => {
- if (isRecording.value || !voicePath.value) return
- console.log('播放录音:', voicePath.value)
- innerAudioContext.src = voicePath.value
- innerAudioContext.play()
- }
- // // 计时器相关
- // const startTimer = () => {
- // duration.value = 0
- // const start = Date.now()
- // timer.value = setInterval(() => {
- // // 计算实际经过的时间(毫秒),然后转换为秒
- // const elapsed = Math.floor((Date.now() - start) / 1000)
- // duration.value = elapsed
- // }, 200) // 缩短检查间隔,但计算基于实际时间差
- // }
- // const stopTimer = () => {
- // if (timer.value) {
- // clearInterval(timer.value)
- // timer.value = null
- // }
- // duration.value = 0
- // }
- // 初始化单词数据
- const initItem = () => {
- data.name = props.activeWord.name;
- data.yinbiao = props.activeWord.yinbiao;
- data.jianyi = props.activeWord.jianyi;
- console.log('data',data);
- }
- onMounted(() => {
- initItem()
- initRecorder()
- checkPermission()
-
- })
- onUnmounted(() => {
- //stopTimer()
- clearTimeout(longPressTimer.value)
- recorderManager.stop()
- innerAudioContext.stop()
- })
- </script>
|