|
@@ -1,50 +1,55 @@
|
|
<!-- 单词区 && 音标区:最多16位,超过换行 -->
|
|
<!-- 单词区 && 音标区:最多16位,超过换行 -->
|
|
<!-- 单音节最长:swimming 多音节最长:transportation -->
|
|
<!-- 单音节最长:swimming 多音节最长:transportation -->
|
|
<template>
|
|
<template>
|
|
- <selectTypesVue activeSelect="3"></selectTypesVue>
|
|
|
|
- <view class="ezy-border-body">
|
|
|
|
- <view class="words-du-box">
|
|
|
|
-
|
|
|
|
|
|
+ <selectTypesVue activeSelect="3"></selectTypesVue>
|
|
|
|
+ <view class="ezy-border-body">
|
|
|
|
+ <view class="words-du-box">
|
|
|
|
+
|
|
<view class="du-body-box">
|
|
<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 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>
|
|
</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 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> -->
|
|
<view v-if="isPlaying" class="disabled-mask">播放中不可录音</view> -->
|
|
- </button>
|
|
|
|
- </view>
|
|
|
|
|
|
+ </button>
|
|
|
|
+ </view>
|
|
|
|
|
|
- <!-- <button class="play-btn" :class="{ disabled: isRecording || !voicePath }" @click="playVoice"
|
|
|
|
|
|
+ <!-- <button class="play-btn" :class="{ disabled: isRecording || !voicePath }" @click="playVoice"
|
|
:disabled="isRecording || !voicePath">
|
|
:disabled="isRecording || !voicePath">
|
|
{{ isPlaying ? '播放中...' : '播放录音' }}
|
|
{{ isPlaying ? '播放中...' : '播放录音' }}
|
|
<view v-if="isRecording" class="disabled-mask">录音中不可播放</view>
|
|
<view v-if="isRecording" class="disabled-mask">录音中不可播放</view>
|
|
</button> -->
|
|
</button> -->
|
|
|
|
+ </view>
|
|
</view>
|
|
</view>
|
|
- </view>
|
|
|
|
</view>
|
|
</view>
|
|
|
|
+ <uni-popup ref="statusPopup" :animation="false" :is-mask-click="false"
|
|
|
|
+ mask-background-color="rgba(51, 137, 217, 0.95);">
|
|
|
|
+ <view class="ezy-image-dialog luyin">
|
|
|
|
+ </view>
|
|
|
|
+ </uni-popup>
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
@@ -95,7 +100,9 @@
|
|
// 获取录音和音频播放管理器
|
|
// 获取录音和音频播放管理器
|
|
const recorderManager = uni.getRecorderManager()
|
|
const recorderManager = uni.getRecorderManager()
|
|
const innerAudioContext = uni.createInnerAudioContext()
|
|
const innerAudioContext = uni.createInnerAudioContext()
|
|
-
|
|
|
|
|
|
+ const statusPopup = ref(null)
|
|
|
|
+ const popupImage = ref('')
|
|
|
|
+ const statusAudio = uni.createInnerAudioContext()
|
|
// 初始化录音器
|
|
// 初始化录音器
|
|
const initRecorder = () => {
|
|
const initRecorder = () => {
|
|
recorderManager.onStart(() => {
|
|
recorderManager.onStart(() => {
|
|
@@ -124,6 +131,7 @@
|
|
title: '录音成功',
|
|
title: '录音成功',
|
|
icon: 'success'
|
|
icon: 'success'
|
|
})
|
|
})
|
|
|
|
+ showStatusPopup('', '录音成功!', '/static/mp3/newYingyu/right-tip.mp3')
|
|
} else {
|
|
} else {
|
|
uni.showToast({
|
|
uni.showToast({
|
|
title: '录音失败',
|
|
title: '录音失败',
|
|
@@ -166,15 +174,98 @@
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ const showStatusPopup = (imagePath, text, audioPath) => {
|
|
|
|
+ console.log('audioPath', audioPath);
|
|
|
|
+ // 播放音效
|
|
|
|
+ statusAudio.stop()
|
|
|
|
+ statusAudio.src = audioPath
|
|
|
|
+ statusAudio.play()
|
|
|
|
+
|
|
|
|
+ // 显示弹窗
|
|
|
|
+ statusPopup.value.open()
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const checkMicrophonePermission = async () => {
|
|
|
|
+ try {
|
|
|
|
+ // 1. 检查平台
|
|
|
|
+ const {
|
|
|
|
+ platform
|
|
|
|
+ } = uni.getSystemInfoSync()
|
|
|
|
+
|
|
|
|
+ // 2. Android权限处理
|
|
|
|
+ if (platform === 'android') {
|
|
|
|
+ console.log('1111');
|
|
|
|
+ const status = await new Promise(resolve => {
|
|
|
|
+ plus.android.requestPermissions(
|
|
|
|
+ ['android.permission.RECORD_AUDIO'],
|
|
|
|
+ (result) => {
|
|
|
|
+ console.log('22222', result);
|
|
|
|
+ if (result.deniedAlways?.length) {
|
|
|
|
+ resolve(false) // 永久拒绝
|
|
|
|
+ } else if (result.denied?.length) {
|
|
|
|
+ resolve(false) // 拒绝
|
|
|
|
+ } else {
|
|
|
|
+ resolve(true) // 已授权
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ () => resolve(false) // 出错
|
|
|
|
+ )
|
|
|
|
+ })
|
|
|
|
+ console.log('status', status);
|
|
|
|
+ if (!status) {
|
|
|
|
+ uni.showToast({
|
|
|
|
+ title: '需要麦克风权限',
|
|
|
|
+ icon: 'none'
|
|
|
|
+ })
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // 3. iOS权限处理
|
|
|
|
+ else if (platform === 'ios') {
|
|
|
|
+ const granted = await new Promise(resolve => {
|
|
|
|
+ plus.ios.import('AVAudioSession').then(AVAudioSession => {
|
|
|
|
+ const session = plus.ios.invoke(AVAudioSession, 'sharedInstance')
|
|
|
|
+ plus.ios.invoke(session, 'requestRecordPermission:', resolve)
|
|
|
|
+ plus.ios.deleteObject(session)
|
|
|
|
+ }).catch(() => resolve(false))
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ if (!granted) {
|
|
|
|
+ uni.showToast({
|
|
|
|
+ title: '请在设置中允许麦克风权限',
|
|
|
|
+ icon: 'none'
|
|
|
|
+ })
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('权限检查错误:', error)
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
async function handlePlay(opt) {
|
|
async function handlePlay(opt) {
|
|
emits('play-audio', opt)
|
|
emits('play-audio', opt)
|
|
}
|
|
}
|
|
|
|
|
|
// 处理触摸开始(移动端)
|
|
// 处理触摸开始(移动端)
|
|
- const handleTouchStart = (e) => {
|
|
|
|
|
|
+ const handleTouchStart = async(e) => {
|
|
e.preventDefault()
|
|
e.preventDefault()
|
|
if (isPlaying.value) return
|
|
if (isPlaying.value) return
|
|
|
|
|
|
|
|
+ // 先检查权限
|
|
|
|
+ const hasPermission = await checkMicrophonePermission()
|
|
|
|
+ if (!hasPermission) {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
// 设置长按计时器,500ms后才开始录音
|
|
// 设置长按计时器,500ms后才开始录音
|
|
longPressTimer.value = setTimeout(() => {
|
|
longPressTimer.value = setTimeout(() => {
|
|
startRecording()
|
|
startRecording()
|
|
@@ -195,9 +286,16 @@
|
|
handleTouchEnd(e) // 与touchend同样处理
|
|
handleTouchEnd(e) // 与touchend同样处理
|
|
}
|
|
}
|
|
// 开始录音
|
|
// 开始录音
|
|
- const startRecording = () => {
|
|
|
|
|
|
+ const startRecording = async () => {
|
|
if (isRecording.value || !isRecorderReady.value) return
|
|
if (isRecording.value || !isRecorderReady.value) return
|
|
|
|
|
|
|
|
+ const hasPermission = await checkMicrophonePermission()
|
|
|
|
+ if (!hasPermission) {
|
|
|
|
+ // 权限被拒绝后重置状态
|
|
|
|
+ isRecording.value = false
|
|
|
|
+ isRecorderReady.value = true
|
|
|
|
+ return
|
|
|
|
+ }
|
|
console.log('开始录音')
|
|
console.log('开始录音')
|
|
//recordingStatus.value = '准备录音...'
|
|
//recordingStatus.value = '准备录音...'
|
|
isRecording.value = true // 提前设置状态,避免延迟
|
|
isRecording.value = true // 提前设置状态,避免延迟
|
|
@@ -215,7 +313,7 @@
|
|
}
|
|
}
|
|
|
|
|
|
try {
|
|
try {
|
|
- recorderManager.start(options)
|
|
|
|
|
|
+ await recorderManager.start(options)
|
|
} catch (e) {
|
|
} catch (e) {
|
|
console.error('启动录音失败:', e)
|
|
console.error('启动录音失败:', e)
|
|
isRecording.value = false
|
|
isRecording.value = false
|
|
@@ -283,6 +381,10 @@
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
initItem()
|
|
initItem()
|
|
initRecorder()
|
|
initRecorder()
|
|
|
|
+ statusAudio.autoplay = false
|
|
|
|
+ statusAudio.onEnded(() => {
|
|
|
|
+ statusPopup.value.close()
|
|
|
|
+ })
|
|
})
|
|
})
|
|
|
|
|
|
onUnmounted(() => {
|
|
onUnmounted(() => {
|
|
@@ -293,6 +395,7 @@
|
|
recorderManager.stop()
|
|
recorderManager.stop()
|
|
innerAudioContext.stop()
|
|
innerAudioContext.stop()
|
|
innerAudioContext.destroy()
|
|
innerAudioContext.destroy()
|
|
|
|
+ statusAudio.destroy()
|
|
} catch (e) {
|
|
} catch (e) {
|
|
console.error('清理资源时出错:', e)
|
|
console.error('清理资源时出错:', e)
|
|
}
|
|
}
|