|
@@ -4,43 +4,55 @@
|
|
<view class="words-xuan-box">
|
|
<view class="words-xuan-box">
|
|
<!-- 单词区 -->
|
|
<!-- 单词区 -->
|
|
<view class="show-words-box"> {{data.name}} </view>
|
|
<view class="show-words-box"> {{data.name}} </view>
|
|
- <!-- 音标区 -->
|
|
|
|
- <view class="yb-play-box">
|
|
|
|
- <text>{{data.yinbiao}}</text>
|
|
|
|
- <!-- active -->
|
|
|
|
-
|
|
|
|
- <icon class="yb-play-btn"></icon>
|
|
|
|
- <icon class="yb-playing-btn" v-if="false"></icon>
|
|
|
|
- </view>
|
|
|
|
|
|
|
|
<view style="text-align: center;">
|
|
<view style="text-align: center;">
|
|
- <text v-if="data.jianyi&&data.jianyi.length>0" v-for="(item,index) in data.jianyi"
|
|
|
|
- :key="index">
|
|
|
|
|
|
+ <text v-if="data.jianyi&&data.jianyi.length>0" v-for="(item,index) in data.jianyi" :key="index">
|
|
{{item}}
|
|
{{item}}
|
|
</text>
|
|
</text>
|
|
</view>
|
|
</view>
|
|
|
|
|
|
- <view>
|
|
|
|
- <view class="audio-play-btn"></view>
|
|
|
|
- <!-- 播放中 -->
|
|
|
|
- <view class="audio-playing-btn" v-if="false"></view>
|
|
|
|
|
|
+ <view class="yb-play-box xue-yb-play-box">
|
|
|
|
+ <yinbiaoTxtVue :yinbiao="activeWord.yinbiao"></yinbiaoTxtVue>
|
|
|
|
+ <!-- 音频播放 -->
|
|
|
|
+ <audioTwoVue :active-word="activeWord" @play-audio="handlePlay"></audioTwoVue>
|
|
</view>
|
|
</view>
|
|
|
|
+ <view class="container">
|
|
|
|
+ <!-- <view class="status">{{ recordingStatus }}</view> -->
|
|
|
|
+ <!-- <view class="duration" v-if="isRecording">录音时长: {{ Math.floor(duration) }}秒</view> -->
|
|
|
|
+ <button class="record-btn" :class="{ recording: isRecording, disabled: isPlaying }"
|
|
|
|
+ @touchstart="handleTouchStart" @touchend="handleTouchEnd" @touchcancel="handleTouchEnd"
|
|
|
|
+ :disabled="isPlaying">
|
|
|
|
+ {{ isRecording ? '松开结束' : '按住录音' }}
|
|
|
|
+ <!-- <view v-if="isPlaying" class="disabled-mask">播放中不可录音</view> -->
|
|
|
|
+ </button>
|
|
|
|
|
|
|
|
+ <button class="play-btn" @click="playVoice" v-if="voicePath">
|
|
|
|
+ 播放小喇叭
|
|
|
|
+ </button>
|
|
|
|
+ <!-- <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>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
- import selectWordsVue from './selectWords.vue';
|
|
|
|
import selectTypesVue from './selectTypes.vue';
|
|
import selectTypesVue from './selectTypes.vue';
|
|
|
|
+ import audioTwoVue from './audioTwo.vue';
|
|
|
|
+ import yinbiaoTxtVue from "./yinbiaoTxt.vue"
|
|
import {
|
|
import {
|
|
onLoad
|
|
onLoad
|
|
} from "@dcloudio/uni-app"
|
|
} from "@dcloudio/uni-app"
|
|
import {
|
|
import {
|
|
reactive,
|
|
reactive,
|
|
ref,
|
|
ref,
|
|
- onMounted
|
|
|
|
|
|
+ onMounted,
|
|
|
|
+ onUnmounted
|
|
} from 'vue';
|
|
} from 'vue';
|
|
|
|
+
|
|
const props = defineProps({
|
|
const props = defineProps({
|
|
activeWord: {
|
|
activeWord: {
|
|
type: Object,
|
|
type: Object,
|
|
@@ -52,26 +64,226 @@
|
|
type: Array
|
|
type: Array
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
+ const emits = defineEmits(['play-audio'])
|
|
let tabFlag = ref(1)
|
|
let tabFlag = ref(1)
|
|
const audioInfo = ref(null)
|
|
const audioInfo = ref(null)
|
|
-
|
|
|
|
const data = reactive({
|
|
const data = reactive({
|
|
name: '',
|
|
name: '',
|
|
yinbiao: '',
|
|
yinbiao: '',
|
|
- jianyi:[]
|
|
|
|
|
|
+ jianyi: []
|
|
})
|
|
})
|
|
|
|
|
|
- onLoad(() => {
|
|
|
|
- initItem()
|
|
|
|
- })
|
|
|
|
|
|
+ // 录音相关状态
|
|
|
|
+ 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) // 长按计时器
|
|
|
|
|
|
- function initItem() {
|
|
|
|
- console.log('data',data);
|
|
|
|
|
|
+
|
|
|
|
+ // 获取录音和音频播放管理器
|
|
|
|
+ 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.name = props.activeWord.name;
|
|
data.yinbiao = props.activeWord.yinbiao;
|
|
data.yinbiao = props.activeWord.yinbiao;
|
|
data.jianyi = props.activeWord.jianyi;
|
|
data.jianyi = props.activeWord.jianyi;
|
|
|
|
+ console.log('data',data);
|
|
}
|
|
}
|
|
-</script>
|
|
|
|
|
|
|
|
-<style>
|
|
|
|
-</style>
|
|
|
|
|
|
+ onMounted(() => {
|
|
|
|
+ initItem()
|
|
|
|
+ initRecorder()
|
|
|
|
+ checkPermission()
|
|
|
|
+
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ onUnmounted(() => {
|
|
|
|
+ //stopTimer()
|
|
|
|
+ clearTimeout(longPressTimer.value)
|
|
|
|
+ recorderManager.stop()
|
|
|
|
+ innerAudioContext.stop()
|
|
|
|
+ })
|
|
|
|
+</script>
|