|
@@ -6,8 +6,13 @@
|
|
|
|
|
|
<!-- 语音波形动画 -->
|
|
|
<view class="wave-container" v-if="isRecording">
|
|
|
- <view class="wave-bar" v-for="(item, index) in waveData" :key="index"
|
|
|
- :style="{ height: item + 'rpx' }"></view>
|
|
|
+ <view
|
|
|
+ class="wave-bar"
|
|
|
+ v-for="(item, index) in waveData"
|
|
|
+ :key="index"
|
|
|
+ :style="{ height: item + 'rpx' }"
|
|
|
+ ></view>
|
|
|
+ <!-- <view class="timer">{{ formatTime(recordingTime) }}</view> -->
|
|
|
</view>
|
|
|
|
|
|
<!-- 待朗读文字 -->
|
|
@@ -15,8 +20,12 @@
|
|
|
|
|
|
<!-- 按钮 -->
|
|
|
<button class="confirm-btn" @tap="handleConfirm">
|
|
|
- {{ isRecording ? '读完了' : '开始' }}
|
|
|
+ {{ isRecording ? '停止录制' : '开始录制' }}
|
|
|
</button>
|
|
|
+
|
|
|
+ <view class="status-message">
|
|
|
+ {{ statusMessage }}
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</template>
|
|
@@ -35,48 +44,546 @@ export default {
|
|
|
data() {
|
|
|
return {
|
|
|
isRecording: false,
|
|
|
- waveData: Array(20).fill(20), // 初始波形数据
|
|
|
- waveTimer: null
|
|
|
+ waveData: Array(30).fill(20),
|
|
|
+ recordingTime: 0,
|
|
|
+ statusMessage: '点击"开始录制"按钮朗读文字',
|
|
|
+ silenceCounter: 0,
|
|
|
+ recordingTimer: null,
|
|
|
+ volumeLevel: 0,
|
|
|
+ animationTimer: null,
|
|
|
+ lastVolume: 0,
|
|
|
+ hasPermission: false, // 添加录音权限状态
|
|
|
+ noSoundTimeout: null, // 添加无声音超时计时器
|
|
|
+ recordingStarted: false, // 添加录音开始标志
|
|
|
+ audioPath: null, // 添加录音文件路径
|
|
|
+ platform: '', // 添加平台标识
|
|
|
+ isRetrying: false // 添加重试标记
|
|
|
}
|
|
|
},
|
|
|
methods: {
|
|
|
- // 开始录音
|
|
|
- startRecording() {
|
|
|
+ // 检查录音权限 - 跨平台兼容版本
|
|
|
+ async checkPermission() {
|
|
|
+ // 获取系统信息
|
|
|
+ const systemInfo = uni.getSystemInfoSync();
|
|
|
+ const platform = systemInfo.platform;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // iOS平台处理
|
|
|
+ if (platform === 'ios') {
|
|
|
+ // iOS使用getSystemSetting检查权限状态
|
|
|
+ const res = await new Promise((resolve, reject) => {
|
|
|
+ uni.getSetting({
|
|
|
+ success: (result) => {
|
|
|
+ if (result.authSetting['scope.record']) {
|
|
|
+ resolve(true);
|
|
|
+ } else {
|
|
|
+ // 请求权限
|
|
|
+ uni.authorize({
|
|
|
+ scope: 'scope.record',
|
|
|
+ success: () => resolve(true),
|
|
|
+ fail: (err) => reject(err)
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: (err) => reject(err)
|
|
|
+ });
|
|
|
+ });
|
|
|
+ this.hasPermission = true;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ // 安卓和鸿蒙平台处理
|
|
|
+ else {
|
|
|
+ const res = await uni.authorize({
|
|
|
+ scope: 'scope.record'
|
|
|
+ });
|
|
|
+ this.hasPermission = true;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('权限请求失败:', error);
|
|
|
+ uni.showModal({
|
|
|
+ title: '提示',
|
|
|
+ content: '需要录音权限才能进行录音,请在设置中开启录音权限',
|
|
|
+ confirmText: '去设置',
|
|
|
+ success: (res) => {
|
|
|
+ if (res.confirm) {
|
|
|
+ uni.openSetting();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.hasPermission = false;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ async startRecording() {
|
|
|
+ // 检查权限
|
|
|
+ if (!await this.checkPermission()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
this.isRecording = true;
|
|
|
- recorderManager.start({
|
|
|
- duration: 10000,
|
|
|
- sampleRate: 16000,
|
|
|
- numberOfChannels: 1,
|
|
|
- encodeBitRate: 48000,
|
|
|
- format: 'wav'
|
|
|
- });
|
|
|
+ this.recordingTime = 0;
|
|
|
+ this.silenceCounter = 0;
|
|
|
+ this.statusMessage = '正在检测声音...';
|
|
|
+ this.waveData = this.waveData.map(() => 20);
|
|
|
+ this.lastVolume = 0;
|
|
|
+ this.recordingStarted = true;
|
|
|
+
|
|
|
+ // 获取系统信息以适配不同平台
|
|
|
+ const systemInfo = uni.getSystemInfoSync();
|
|
|
+ const platform = systemInfo.platform;
|
|
|
+
|
|
|
+ // 根据不同平台配置录音参数
|
|
|
+ const recorderOptions = {
|
|
|
+ duration: 60000, // 最长录音时间
|
|
|
+ format: 'mp3', // 使用mp3格式提高兼容性
|
|
|
+ frameSize: 5
|
|
|
+ };
|
|
|
+
|
|
|
+ // iOS平台特殊处理
|
|
|
+ if (platform === 'ios') {
|
|
|
+ // iOS推荐参数
|
|
|
+ recorderOptions.sampleRate = 44100;
|
|
|
+ recorderOptions.numberOfChannels = 1;
|
|
|
+ recorderOptions.encodeBitRate = 96000;
|
|
|
+ } else {
|
|
|
+ // 安卓和鸿蒙平台参数
|
|
|
+ recorderOptions.sampleRate = 16000;
|
|
|
+ recorderOptions.numberOfChannels = 1;
|
|
|
+ recorderOptions.encodeBitRate = 48000;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 开始录音
|
|
|
+ recorderManager.start(recorderOptions);
|
|
|
|
|
|
- // 模拟波形动画
|
|
|
- this.waveTimer = setInterval(() => {
|
|
|
- this.waveData = this.waveData.map(() => Math.random() * 40 + 20);
|
|
|
- }, 100);
|
|
|
+ // 计时器
|
|
|
+ this.recordingTimer = setInterval(() => {
|
|
|
+ this.recordingTime++;
|
|
|
+ }, 1000);
|
|
|
+
|
|
|
+ // 动画定时器
|
|
|
+ this.animationTimer = setInterval(() => {
|
|
|
+ if (this.isRecording) {
|
|
|
+ this.updateWaveform();
|
|
|
+ }
|
|
|
+ }, 50);
|
|
|
+
|
|
|
+ // 设置无声音检测超时
|
|
|
+ this.noSoundTimeout = setTimeout(() => {
|
|
|
+ if (this.silenceCounter > 20) {
|
|
|
+ this.showNoSoundTip();
|
|
|
+ }
|
|
|
+ }, 3000);
|
|
|
},
|
|
|
|
|
|
- // 停止录音
|
|
|
stopRecording() {
|
|
|
this.isRecording = false;
|
|
|
+ this.recordingStarted = false;
|
|
|
+ recorderManager.stop();
|
|
|
+ clearInterval(this.recordingTimer);
|
|
|
+ clearInterval(this.animationTimer);
|
|
|
+ clearTimeout(this.noSoundTimeout);
|
|
|
+ this.statusMessage = '录制完成!';
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ this.waveData = Array(30).fill(20);
|
|
|
+ }, 500);
|
|
|
+
|
|
|
+ // 延迟触发完成事件,确保audioPath已经设置
|
|
|
+ setTimeout(() => {
|
|
|
+ // 处理音频文件
|
|
|
+ this.processAudioFile();
|
|
|
+
|
|
|
+ // 触发完成事件
|
|
|
+ this.$emit('complete', {
|
|
|
+ success: true,
|
|
|
+ audioPath: this.audioPath,
|
|
|
+ platform: this.platform
|
|
|
+ });
|
|
|
+ }, 300);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 添加音频文件处理方法
|
|
|
+ processAudioFile() {
|
|
|
+ if (!this.audioPath) return;
|
|
|
+
|
|
|
+ // 获取系统信息
|
|
|
+ const systemInfo = uni.getSystemInfoSync();
|
|
|
+ this.platform = systemInfo.platform;
|
|
|
+
|
|
|
+ console.log('处理音频文件:', this.audioPath, '平台:', this.platform);
|
|
|
+
|
|
|
+ // 针对不同平台的音频文件处理
|
|
|
+ if (this.platform === 'ios') {
|
|
|
+ // iOS平台特殊处理
|
|
|
+ if (!this.audioPath.startsWith('file://')) {
|
|
|
+ this.audioPath = 'file://' + this.audioPath;
|
|
|
+ }
|
|
|
+ } else if (this.platform === 'android') {
|
|
|
+ // 安卓平台特殊处理
|
|
|
+ // 通常不需要特殊处理,但如果有特殊需求可以在这里添加
|
|
|
+ } else if (this.platform === 'harmony') {
|
|
|
+ // 鸿蒙平台特殊处理
|
|
|
+ // 根据鸿蒙系统的特性进行处理
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证音频文件是否存在
|
|
|
+ uni.getFileInfo({
|
|
|
+ filePath: this.audioPath,
|
|
|
+ success: (res) => {
|
|
|
+ console.log('音频文件信息:', res);
|
|
|
+ if (res.size === 0) {
|
|
|
+ console.warn('警告: 音频文件大小为0');
|
|
|
+ // 可以选择重新录制或提示用户
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ console.error('获取音频文件信息失败:', err);
|
|
|
+ // 处理文件不存在的情况
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示无声音提示
|
|
|
+ showNoSoundTip() {
|
|
|
+ if (!this.isRecording) return;
|
|
|
+
|
|
|
+ uni.showModal({
|
|
|
+ title: '未检测到声音',
|
|
|
+ content: '请检查以下问题:\n1. 麦克风是否正常工作\n2. 是否允许使用麦克风\n3. 是否靠近麦克风说话\n4. 说话音量是否足够',
|
|
|
+ showCancel: true,
|
|
|
+ cancelText: '重新录制',
|
|
|
+ confirmText: '继续录制',
|
|
|
+ success: (res) => {
|
|
|
+ if (res.cancel) {
|
|
|
+ // 重新录制:停止当前录音并重新开始
|
|
|
+ this.restartRecording();
|
|
|
+ } else {
|
|
|
+ // 继续录制:重置计数器继续当前录音
|
|
|
+ this.continueRecording();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 重新录制方法
|
|
|
+ restartRecording() {
|
|
|
+ // 先停止当前录音
|
|
|
recorderManager.stop();
|
|
|
- clearInterval(this.waveTimer);
|
|
|
- this.waveData = Array(20).fill(20);
|
|
|
+ clearInterval(this.recordingTimer);
|
|
|
+ clearInterval(this.animationTimer);
|
|
|
+ clearTimeout(this.noSoundTimeout);
|
|
|
+
|
|
|
+ // 重置所有状态
|
|
|
+ this.isRecording = false;
|
|
|
+ this.recordingStarted = false;
|
|
|
+ this.recordingTime = 0;
|
|
|
+ this.silenceCounter = 0;
|
|
|
+ this.volumeLevel = 0;
|
|
|
+ this.lastVolume = 0;
|
|
|
+ this.waveData = Array(30).fill(20);
|
|
|
+
|
|
|
+ // 短暂延迟后开始新的录音
|
|
|
+ setTimeout(() => {
|
|
|
+ this.startRecording();
|
|
|
+ }, 500);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 继续录制方法
|
|
|
+ continueRecording() {
|
|
|
+ // 重置计数器和状态,但保持录音继续
|
|
|
+ this.silenceCounter = 0;
|
|
|
+ this.statusMessage = '正在检测声音...';
|
|
|
+
|
|
|
+ // 重新设置无声音检测超时
|
|
|
+ clearTimeout(this.noSoundTimeout);
|
|
|
+ this.noSoundTimeout = setTimeout(() => {
|
|
|
+ if (this.silenceCounter > 20) {
|
|
|
+ this.showNoSoundTip();
|
|
|
+ }
|
|
|
+ }, 3000);
|
|
|
+ },
|
|
|
+
|
|
|
+ detectAudio(int16Array) {
|
|
|
+ // 获取系统信息
|
|
|
+ const systemInfo = uni.getSystemInfoSync();
|
|
|
+ const platform = systemInfo.platform;
|
|
|
+
|
|
|
+ // 计算平均音量 - 优化算法以提高准确性
|
|
|
+ let sum = 0;
|
|
|
+ let peak = 0;
|
|
|
+
|
|
|
+ // 使用分块处理以提高性能
|
|
|
+ const blockSize = 128;
|
|
|
+ for (let i = 0; i < int16Array.length; i += blockSize) {
|
|
|
+ const end = Math.min(i + blockSize, int16Array.length);
|
|
|
+ for (let j = i; j < end; j++) {
|
|
|
+ const absValue = Math.abs(int16Array[j]);
|
|
|
+ sum += absValue;
|
|
|
+ peak = Math.max(peak, absValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const avg = sum / int16Array.length;
|
|
|
+
|
|
|
+ // 根据不同平台调整阈值
|
|
|
+ let SPEAK_THRESHOLD = 500; // 默认阈值
|
|
|
+ let volumeScale = 2000;
|
|
|
+
|
|
|
+ if (platform === 'ios') {
|
|
|
+ SPEAK_THRESHOLD = 300; // iOS通常需要更低的阈值
|
|
|
+ volumeScale = 1500;
|
|
|
+ } else if (platform === 'android') {
|
|
|
+ SPEAK_THRESHOLD = 500; // 安卓标准阈值
|
|
|
+ volumeScale = 2000;
|
|
|
+ } else {
|
|
|
+ // 鸿蒙或其他平台
|
|
|
+ SPEAK_THRESHOLD = 400;
|
|
|
+ volumeScale = 1800;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用峰值和平均值的组合来计算音量级别
|
|
|
+ const combinedLevel = (avg * 0.7 + peak * 0.3) / volumeScale;
|
|
|
+ this.volumeLevel = this.smoothVolume(combinedLevel);
|
|
|
+
|
|
|
+ // 更新状态
|
|
|
+ if (avg > SPEAK_THRESHOLD || peak > SPEAK_THRESHOLD * 3) {
|
|
|
+ this.statusMessage = '检测到声音:录音中...';
|
|
|
+ this.silenceCounter = 0;
|
|
|
+ clearTimeout(this.noSoundTimeout); // 清除无声音超时
|
|
|
+
|
|
|
+ // 立即更新波形以反映实际声音
|
|
|
+ this.updateWaveform(false);
|
|
|
+ } else {
|
|
|
+ this.silenceCounter++;
|
|
|
+ if (this.silenceCounter > 10) {
|
|
|
+ this.statusMessage = '未检测到声音,请靠近麦克风并说话...';
|
|
|
+ // 如果持续30次都没有声音,显示提示
|
|
|
+ if (this.silenceCounter === 30 && this.isRecording) {
|
|
|
+ this.showNoSoundTip();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 静音时逐渐降低波形
|
|
|
+ this.updateWaveform(true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 添加音量平滑处理函数
|
|
|
+ smoothVolume(newVolume) {
|
|
|
+ const smoothFactor = 0.3; // 平滑系数
|
|
|
+ const smoothedVolume = this.lastVolume + (newVolume - this.lastVolume) * smoothFactor;
|
|
|
+ this.lastVolume = smoothedVolume;
|
|
|
+ return Math.min(1, Math.max(0, smoothedVolume));
|
|
|
},
|
|
|
|
|
|
- // 处理确认按钮点击
|
|
|
+ updateWaveform(isSilent = false) {
|
|
|
+ // 获取系统信息以优化动画性能
|
|
|
+ const systemInfo = uni.getSystemInfoSync();
|
|
|
+ const platform = systemInfo.platform;
|
|
|
+
|
|
|
+ // 根据平台调整动画参数
|
|
|
+ let decayFactor = 0.95; // 静音时波形衰减因子
|
|
|
+ let animationScale = 1.0; // 动画缩放因子
|
|
|
+
|
|
|
+ if (platform === 'ios') {
|
|
|
+ // iOS上动画通常需要更平滑
|
|
|
+ decayFactor = 0.97;
|
|
|
+ animationScale = 0.9;
|
|
|
+ } else if (platform === 'android') {
|
|
|
+ // 安卓标准参数
|
|
|
+ decayFactor = 0.95;
|
|
|
+ animationScale = 1.0;
|
|
|
+ } else {
|
|
|
+ // 鸿蒙或其他平台
|
|
|
+ decayFactor = 0.96;
|
|
|
+ animationScale = 0.95;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isSilent) {
|
|
|
+ // 静音时波形逐渐降低
|
|
|
+ this.waveData = this.waveData.map(height => Math.max(20, height * decayFactor));
|
|
|
+ } else {
|
|
|
+ const now = Date.now() * 0.002;
|
|
|
+
|
|
|
+ // 使用当前音量级别动态调整波形
|
|
|
+ const volumeEffect = this.volumeLevel * animationScale;
|
|
|
+ const baseHeight = 20 + volumeEffect * 60;
|
|
|
+ const waveAmplitude = volumeEffect * 40;
|
|
|
+
|
|
|
+ // 更新波形数据
|
|
|
+ this.waveData = this.waveData.map((currentHeight, i) => {
|
|
|
+ // 使用余弦和正弦的组合来创建更流畅的波形
|
|
|
+ const phase = i * 0.2 + now;
|
|
|
+ const wave1 = Math.cos(phase) * 0.5;
|
|
|
+ const wave2 = Math.sin(phase * 1.5) * 0.3;
|
|
|
+
|
|
|
+ // 添加随机因素使波形更自然
|
|
|
+ const randomFactor = Math.random() * 0.1;
|
|
|
+ const combinedWave = (wave1 + wave2 + randomFactor) * volumeEffect;
|
|
|
+
|
|
|
+ // 计算目标高度
|
|
|
+ const targetHeight = baseHeight + combinedWave * waveAmplitude;
|
|
|
+
|
|
|
+ // 平滑过渡到新高度 (LERP - 线性插值)
|
|
|
+ const smoothFactor = platform === 'ios' ? 0.3 : 0.4; // iOS上更平滑
|
|
|
+ const newHeight = currentHeight + (targetHeight - currentHeight) * smoothFactor;
|
|
|
+
|
|
|
+ // 确保高度在合理范围内
|
|
|
+ return Math.max(20, Math.min(120, newHeight));
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
handleConfirm() {
|
|
|
if (!this.isRecording) {
|
|
|
this.startRecording();
|
|
|
} else {
|
|
|
this.stopRecording();
|
|
|
- this.$emit('complete');
|
|
|
}
|
|
|
+ },
|
|
|
+
|
|
|
+ formatTime(seconds) {
|
|
|
+ const mins = Math.floor(seconds / 60);
|
|
|
+ const secs = seconds % 60;
|
|
|
+ return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
|
}
|
|
|
},
|
|
|
+ mounted() {
|
|
|
+ // 获取系统信息
|
|
|
+ const systemInfo = uni.getSystemInfoSync();
|
|
|
+ const platform = systemInfo.platform;
|
|
|
+
|
|
|
+ // 监听录音错误 - 增强错误处理
|
|
|
+ recorderManager.onError((error) => {
|
|
|
+ console.error('录音错误:', error);
|
|
|
+
|
|
|
+ // 根据不同平台处理错误
|
|
|
+ let errorMessage = '录音出现错误,请检查麦克风权限或重试';
|
|
|
+
|
|
|
+ // 针对iOS的特定错误处理
|
|
|
+ if (platform === 'ios') {
|
|
|
+ if (error.errMsg && error.errMsg.includes('authorize')) {
|
|
|
+ errorMessage = 'iOS需要麦克风权限,请在设置中允许访问麦克风';
|
|
|
+ } else if (error.errMsg && error.errMsg.includes('busy')) {
|
|
|
+ errorMessage = '麦克风正被其他应用使用,请关闭其他使用麦克风的应用';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 针对安卓的特定错误处理
|
|
|
+ else if (platform === 'android') {
|
|
|
+ if (error.errMsg && error.errMsg.includes('permission')) {
|
|
|
+ errorMessage = '安卓系统需要麦克风权限,请在设置中允许访问麦克风';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 针对鸿蒙的特定错误处理
|
|
|
+ else if (platform === 'harmony') {
|
|
|
+ errorMessage = '请确保已授予麦克风权限并重试';
|
|
|
+ }
|
|
|
+
|
|
|
+ uni.showModal({
|
|
|
+ title: '录音错误',
|
|
|
+ content: errorMessage,
|
|
|
+ showCancel: false,
|
|
|
+ success: () => {
|
|
|
+ this.stopRecording();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 监听录音开始
|
|
|
+ recorderManager.onStart(() => {
|
|
|
+ console.log('录音开始');
|
|
|
+ this.recordingStarted = true;
|
|
|
+
|
|
|
+ // 平台特定处理
|
|
|
+ if (platform === 'ios') {
|
|
|
+ // iOS上可能需要额外延迟来确保录音真正开始
|
|
|
+ setTimeout(() => {
|
|
|
+ // 如果没有检测到声音,开始模拟波形动画
|
|
|
+ if (this.silenceCounter > 5) {
|
|
|
+ this.updateWaveform(false);
|
|
|
+ }
|
|
|
+ }, 500);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 监听录音帧数据
|
|
|
+ recorderManager.onFrameRecorded(res => {
|
|
|
+ if (!res.frameBuffer || !this.recordingStarted) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const int16Array = new Int16Array(res.frameBuffer);
|
|
|
+ this.detectAudio(int16Array);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('处理音频帧数据错误:', error);
|
|
|
+ // 出错时使用默认波形动画
|
|
|
+ this.updateWaveform(false);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 录音结束回调
|
|
|
+ recorderManager.onStop(res => {
|
|
|
+ this.audioPath = res.tempFilePath;
|
|
|
+ this.isRecording = false;
|
|
|
+ this.recordingStarted = false;
|
|
|
+ clearInterval(this.recordingTimer);
|
|
|
+ clearInterval(this.animationTimer);
|
|
|
+ clearTimeout(this.noSoundTimeout);
|
|
|
+ this.statusMessage = '录制完成!';
|
|
|
+
|
|
|
+ // 确保在所有平台上都能正确获取录音文件
|
|
|
+ if (res.tempFilePath) {
|
|
|
+ console.log('录音文件路径:', res.tempFilePath);
|
|
|
+
|
|
|
+ // 针对iOS的特殊处理
|
|
|
+ if (platform === 'ios' && !res.tempFilePath.startsWith('file://')) {
|
|
|
+ this.audioPath = 'file://' + res.tempFilePath;
|
|
|
+ } else {
|
|
|
+ this.audioPath = res.tempFilePath;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ console.error('未获取到录音文件路径');
|
|
|
+ uni.showToast({
|
|
|
+ title: '录音保存失败',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 添加中断处理 - 主要针对iOS
|
|
|
+ recorderManager.onInterruptionBegin && recorderManager.onInterruptionBegin(() => {
|
|
|
+ console.log('录音被中断');
|
|
|
+ this.statusMessage = '录音被中断,请重试';
|
|
|
+ this.stopRecording();
|
|
|
+ });
|
|
|
+
|
|
|
+ recorderManager.onInterruptionEnd && recorderManager.onInterruptionEnd(() => {
|
|
|
+ console.log('录音中断结束');
|
|
|
+ // 可以选择自动重新开始录音
|
|
|
+ // this.startRecording();
|
|
|
+ });
|
|
|
+ },
|
|
|
beforeDestroy() {
|
|
|
- clearInterval(this.waveTimer);
|
|
|
+ // 停止所有计时器
|
|
|
+ clearInterval(this.recordingTimer);
|
|
|
+ clearInterval(this.animationTimer);
|
|
|
+ clearTimeout(this.noSoundTimeout);
|
|
|
+
|
|
|
+ // 确保录音停止
|
|
|
+ if (this.isRecording) {
|
|
|
+ recorderManager.stop();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 移除事件监听器
|
|
|
+ try {
|
|
|
+ // 注意:uni-app不支持直接移除事件监听器
|
|
|
+ // 但我们可以确保组件销毁时停止所有活动
|
|
|
+ this.isRecording = false;
|
|
|
+ this.recordingStarted = false;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('清理资源时出错:', error);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
@@ -101,51 +608,98 @@ export default {
|
|
|
border-radius: 20rpx;
|
|
|
padding: 40rpx;
|
|
|
text-align: center;
|
|
|
+ box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.2);
|
|
|
}
|
|
|
|
|
|
.modal-title {
|
|
|
- font-size: 32rpx;
|
|
|
+ font-size: 36rpx;
|
|
|
font-weight: bold;
|
|
|
margin-bottom: 20rpx;
|
|
|
+ color: #0039b3;
|
|
|
}
|
|
|
|
|
|
.modal-text {
|
|
|
- font-size: 28rpx;
|
|
|
+ font-size: 30rpx;
|
|
|
color: #666;
|
|
|
margin-bottom: 30rpx;
|
|
|
}
|
|
|
|
|
|
.wave-container {
|
|
|
- height: 160rpx;
|
|
|
+ height: 200rpx;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
- gap: 6rpx;
|
|
|
+ gap: 4rpx; /* 减小波形条之间的间距 */
|
|
|
margin: 30rpx 0;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border-radius: 15rpx;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ border: 1rpx solid #eee;
|
|
|
}
|
|
|
|
|
|
.wave-bar {
|
|
|
- width: 8rpx;
|
|
|
- background: #0039b3;
|
|
|
- border-radius: 4rpx;
|
|
|
- transition: height 0.1s ease;
|
|
|
+ width: 6rpx; /* 减小波形条的宽度 */
|
|
|
+ background: linear-gradient(to top, #1a73e8, #0039b3);
|
|
|
+ border-radius: 3rpx;
|
|
|
+ transition: height 0.1s ease-out; /* 加快过渡动画速度 */
|
|
|
+}
|
|
|
+
|
|
|
+.timer {
|
|
|
+ position: absolute;
|
|
|
+ top: 15rpx;
|
|
|
+ right: 15rpx;
|
|
|
+ background: rgba(0, 57, 179, 0.1);
|
|
|
+ color: #0039b3;
|
|
|
+ padding: 5rpx 15rpx;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ font-size: 26rpx;
|
|
|
+ font-weight: 500;
|
|
|
}
|
|
|
|
|
|
.read-text {
|
|
|
- background: #f5f5f5;
|
|
|
+ background: #f8f9fa;
|
|
|
padding: 30rpx;
|
|
|
- border-radius: 10rpx;
|
|
|
- font-size: 30rpx;
|
|
|
+ border-radius: 15rpx;
|
|
|
+ font-size: 32rpx;
|
|
|
margin: 30rpx 0;
|
|
|
+ color: #333;
|
|
|
+ line-height: 1.6;
|
|
|
+ border: 1rpx solid #eee;
|
|
|
}
|
|
|
|
|
|
.confirm-btn {
|
|
|
width: 80%;
|
|
|
height: 80rpx;
|
|
|
line-height: 80rpx;
|
|
|
- background: #0039b3;
|
|
|
+ background: linear-gradient(90deg, #0039b3, #1a73e8);
|
|
|
color: #fff;
|
|
|
border-radius: 40rpx;
|
|
|
margin-top: 30rpx;
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: 500;
|
|
|
+ box-shadow: 0 5rpx 15rpx rgba(0, 57, 179, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
+.status-message {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #666;
|
|
|
+ margin-top: 30rpx;
|
|
|
+ height: 40rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+/* 添加状态消息的动画效果 */
|
|
|
+@keyframes blink {
|
|
|
+ 0% { opacity: 1; }
|
|
|
+ 50% { opacity: 0.5; }
|
|
|
+ 100% { opacity: 1; }
|
|
|
+}
|
|
|
+
|
|
|
+.status-message.warning {
|
|
|
+ color: #ff9800;
|
|
|
+ animation: blink 1.5s infinite;
|
|
|
}
|
|
|
</style>
|