448 lines
14 KiB
Vue
448 lines
14 KiB
Vue
<template>
|
||
<view class="test-page">
|
||
<view class="header">🎤 录音测试工具</view>
|
||
|
||
<!-- 设备信息 -->
|
||
<view class="card">
|
||
<view class="card-title">📱 设备信息</view>
|
||
<view class="info-item">系统: {{ deviceInfo }}</view>
|
||
</view>
|
||
|
||
<!-- 权限状态 -->
|
||
<view class="card">
|
||
<view class="card-title">🔐 权限状态</view>
|
||
<view class="info-item">{{ permissionStatus }}</view>
|
||
<button v-if="!hasPermission" @click="requestPermission" class="btn-blue">
|
||
请求录音权限
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 测试1:直接录音 -->
|
||
<view class="card">
|
||
<view class="card-title">测试1: 直接录音(AMR格式)</view>
|
||
<button @click="testRecord" :disabled="testing" class="btn-green">
|
||
{{ testing ? '测试中...' : '开始测试' }}
|
||
</button>
|
||
<view class="result">{{ result1 }}</view>
|
||
</view>
|
||
|
||
<!-- 测试2:系统录音API -->
|
||
<view class="card">
|
||
<view class="card-title">测试2: 系统录音API</view>
|
||
<button @click="testSystemRecord" :disabled="testing" class="btn-orange">
|
||
{{ testing ? '测试中...' : '开始测试' }}
|
||
</button>
|
||
<view class="result">{{ result2 }}</view>
|
||
</view>
|
||
|
||
<!-- 测试3:原生Intent录音 -->
|
||
<view class="card" style="border: 2px solid #667eea;">
|
||
<view class="card-title">🎯 测试3: 原生Intent录音(推荐)</view>
|
||
<view class="info-text">使用系统录音界面,兼容性最好</view>
|
||
<button @click="goNativeTest" class="btn-purple">
|
||
前往测试 →
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 日志显示 -->
|
||
<view class="card">
|
||
<view class="card-title">📋 测试日志</view>
|
||
<view class="log-box">
|
||
<text v-for="(log, index) in logs" :key="index" class="log-line">
|
||
{{ log }}
|
||
</text>
|
||
</view>
|
||
<button @click="clearLogs" class="btn-small">清空日志</button>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
data() {
|
||
return {
|
||
deviceInfo: '',
|
||
permissionStatus: '检查中...',
|
||
hasPermission: false,
|
||
testing: false,
|
||
result1: '未测试',
|
||
result2: '未测试',
|
||
logs: []
|
||
}
|
||
},
|
||
onLoad() {
|
||
this.init()
|
||
},
|
||
methods: {
|
||
// 初始化
|
||
init() {
|
||
this.log('=== 开始初始化 ===')
|
||
this.getDeviceInfo()
|
||
this.checkPermission()
|
||
},
|
||
|
||
// 添加日志
|
||
log(msg) {
|
||
const time = new Date().toLocaleTimeString()
|
||
this.logs.push(`[${time}] ${msg}`)
|
||
console.log(msg)
|
||
|
||
// 最多保留20条日志
|
||
if (this.logs.length > 20) {
|
||
this.logs.shift()
|
||
}
|
||
},
|
||
|
||
// 清空日志
|
||
clearLogs() {
|
||
this.logs = []
|
||
},
|
||
|
||
// 获取设备信息
|
||
getDeviceInfo() {
|
||
// #ifdef APP-PLUS
|
||
try {
|
||
this.deviceInfo = `${plus.os.name} ${plus.os.version} (${plus.device.model})`
|
||
this.log(`设备: ${this.deviceInfo}`)
|
||
} catch (e) {
|
||
this.deviceInfo = '获取失败'
|
||
this.log(`获取设备信息失败: ${e.message}`)
|
||
}
|
||
// #endif
|
||
},
|
||
|
||
// 检查权限
|
||
checkPermission() {
|
||
// #ifdef APP-PLUS
|
||
try {
|
||
const result = plus.android.checkPermission('android.permission.RECORD_AUDIO')
|
||
this.log(`权限检查结果: ${result} (1=已授权, 0=未授权, -1=永久拒绝)`)
|
||
|
||
if (result === 1) {
|
||
this.permissionStatus = '✅ 已授权'
|
||
this.hasPermission = true
|
||
} else if (result === 0) {
|
||
this.permissionStatus = '❌ 未授权'
|
||
this.hasPermission = false
|
||
} else {
|
||
this.permissionStatus = '❌ 永久拒绝,需手动开启'
|
||
this.hasPermission = false
|
||
}
|
||
} catch (e) {
|
||
this.permissionStatus = '检查失败'
|
||
this.log(`权限检查异常: ${e.message}`)
|
||
}
|
||
// #endif
|
||
},
|
||
|
||
// 请求权限
|
||
requestPermission() {
|
||
// #ifdef APP-PLUS
|
||
this.log('请求录音权限...')
|
||
plus.android.requestPermissions(
|
||
['android.permission.RECORD_AUDIO'],
|
||
(result) => {
|
||
this.log(`权限请求结果: ${JSON.stringify(result)}`)
|
||
if (result.granted && result.granted.length > 0) {
|
||
this.permissionStatus = '✅ 已授权'
|
||
this.hasPermission = true
|
||
uni.showToast({ title: '授权成功', icon: 'success' })
|
||
} else {
|
||
uni.showToast({ title: '授权失败', icon: 'none' })
|
||
}
|
||
},
|
||
(error) => {
|
||
this.log(`权限请求失败: ${JSON.stringify(error)}`)
|
||
}
|
||
)
|
||
// #endif
|
||
},
|
||
|
||
// 测试1:直接录音
|
||
testRecord() {
|
||
if (!this.hasPermission) {
|
||
uni.showToast({ title: '请先授予录音权限', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
this.testing = true
|
||
this.result1 = '测试中...'
|
||
this.log('=== 测试1 开始 ===')
|
||
|
||
// #ifdef APP-PLUS
|
||
try {
|
||
const recorder = plus.audio.getRecorder()
|
||
const filePath = '_doc/test1_' + Date.now() + '.amr'
|
||
|
||
this.log(`文件路径: ${filePath}`)
|
||
this.log('开始录音...')
|
||
|
||
// 超时保护
|
||
const timeout = setTimeout(() => {
|
||
this.log('❌ 超时:5秒内未开始录音')
|
||
this.result1 = '❌ 超时失败'
|
||
this.testing = false
|
||
}, 5000)
|
||
|
||
recorder.record(
|
||
{
|
||
filename: filePath,
|
||
format: 'amr',
|
||
samplerate: '8000',
|
||
channels: '1'
|
||
},
|
||
() => {
|
||
clearTimeout(timeout)
|
||
this.log('✅ 录音已开始')
|
||
this.result1 = '录音中...'
|
||
|
||
// 3秒后停止
|
||
setTimeout(() => {
|
||
this.log('停止录音...')
|
||
recorder.stop()
|
||
this.result1 = '检查文件...'
|
||
|
||
// 检查文件
|
||
setTimeout(() => {
|
||
plus.io.resolveLocalFileSystemURL(filePath, (entry) => {
|
||
entry.file((file) => {
|
||
const sizeKB = (file.size / 1024).toFixed(2)
|
||
this.log(`文件大小: ${sizeKB}KB`)
|
||
|
||
if (file.size < 100) {
|
||
this.result1 = `⚠️ 文件过小: ${sizeKB}KB\n录音可能失败`
|
||
this.log('⚠️ 录音文件过小')
|
||
} else {
|
||
this.result1 = `✅ 成功!\n文件大小: ${sizeKB}KB`
|
||
this.log('✅ 测试1 成功')
|
||
}
|
||
this.testing = false
|
||
})
|
||
}, (error) => {
|
||
this.log(`❌ 文件不存在: ${JSON.stringify(error)}`)
|
||
this.result1 = '❌ 文件不存在'
|
||
this.testing = false
|
||
})
|
||
}, 500)
|
||
}, 3000)
|
||
},
|
||
(error) => {
|
||
clearTimeout(timeout)
|
||
this.log(`❌ 录音失败: code=${error.code}, msg=${error.message}`)
|
||
|
||
let msg = '❌ 失败\n'
|
||
if (error.code === 3) msg += '原因: 权限被拒绝'
|
||
else if (error.code === 4) msg += '原因: 麦克风被占用'
|
||
else msg += `错误码: ${error.code}`
|
||
|
||
this.result1 = msg
|
||
this.testing = false
|
||
}
|
||
)
|
||
} catch (e) {
|
||
this.log(`❌ 异常: ${e.message}`)
|
||
this.result1 = `❌ 异常: ${e.message}`
|
||
this.testing = false
|
||
}
|
||
// #endif
|
||
},
|
||
|
||
// 前往原生录音测试
|
||
goNativeTest() {
|
||
uni.navigateTo({
|
||
url: '/pages/native-record-test'
|
||
})
|
||
},
|
||
|
||
// 测试2:系统录音API
|
||
testSystemRecord() {
|
||
if (!this.hasPermission) {
|
||
uni.showToast({ title: '请先授予录音权限', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
this.testing = true
|
||
this.result2 = '测试中...'
|
||
this.log('=== 测试2 开始 ===')
|
||
|
||
try {
|
||
const recorderManager = uni.getRecorderManager()
|
||
|
||
recorderManager.onStart(() => {
|
||
this.log('✅ 系统API录音已开始')
|
||
this.result2 = '录音中...'
|
||
})
|
||
|
||
recorderManager.onStop((res) => {
|
||
this.log(`录音停止: ${res.tempFilePath}`)
|
||
|
||
// 获取文件信息
|
||
uni.getFileInfo({
|
||
filePath: res.tempFilePath,
|
||
success: (info) => {
|
||
const sizeKB = (info.size / 1024).toFixed(2)
|
||
this.log(`文件大小: ${sizeKB}KB`)
|
||
|
||
if (info.size < 100) {
|
||
this.result2 = `⚠️ 文件过小: ${sizeKB}KB\n录音可能失败`
|
||
this.log('⚠️ 录音文件过小')
|
||
} else {
|
||
this.result2 = `✅ 成功!\n文件大小: ${sizeKB}KB`
|
||
this.log('✅ 测试2 成功')
|
||
}
|
||
this.testing = false
|
||
},
|
||
fail: (error) => {
|
||
this.log(`❌ 获取文件信息失败: ${JSON.stringify(error)}`)
|
||
this.result2 = '❌ 获取文件信息失败'
|
||
this.testing = false
|
||
}
|
||
})
|
||
})
|
||
|
||
recorderManager.onError((error) => {
|
||
this.log(`❌ 录音错误: ${JSON.stringify(error)}`)
|
||
this.result2 = `❌ 错误: ${error.errMsg}`
|
||
this.testing = false
|
||
})
|
||
|
||
// 开始录音
|
||
this.log('启动系统API录音...')
|
||
recorderManager.start({
|
||
format: 'wav',
|
||
sampleRate: 8000,
|
||
numberOfChannels: 1
|
||
})
|
||
|
||
// 3秒后停止
|
||
setTimeout(() => {
|
||
this.log('停止系统API录音...')
|
||
recorderManager.stop()
|
||
}, 3000)
|
||
|
||
} catch (e) {
|
||
this.log(`❌ 异常: ${e.message}`)
|
||
this.result2 = `❌ 异常: ${e.message}`
|
||
this.testing = false
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.test-page {
|
||
min-height: 100vh;
|
||
background: #f5f7fa;
|
||
padding: 20rpx;
|
||
}
|
||
|
||
.header {
|
||
text-align: center;
|
||
font-size: 40rpx;
|
||
font-weight: bold;
|
||
padding: 30rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.card {
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
margin-bottom: 20rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.info-item {
|
||
padding: 15rpx 0;
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
button {
|
||
width: 100%;
|
||
padding: 25rpx;
|
||
border: none;
|
||
border-radius: 12rpx;
|
||
font-size: 30rpx;
|
||
margin-top: 15rpx;
|
||
}
|
||
|
||
.btn-blue {
|
||
background: #409eff;
|
||
color: #fff;
|
||
}
|
||
|
||
.btn-green {
|
||
background: #67c23a;
|
||
color: #fff;
|
||
}
|
||
|
||
.btn-orange {
|
||
background: #e6a23c;
|
||
color: #fff;
|
||
}
|
||
|
||
.btn-purple {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: #fff;
|
||
box-shadow: 0 4rpx 15rpx rgba(102, 126, 234, 0.4);
|
||
}
|
||
|
||
.btn-small {
|
||
background: #909399;
|
||
color: #fff;
|
||
padding: 15rpx;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.info-text {
|
||
padding: 15rpx 0;
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
button:disabled {
|
||
background: #ccc;
|
||
color: #999;
|
||
}
|
||
|
||
.result {
|
||
margin-top: 20rpx;
|
||
padding: 20rpx;
|
||
background: #f5f5f5;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
min-height: 80rpx;
|
||
white-space: pre-line;
|
||
color: #333;
|
||
}
|
||
|
||
.log-box {
|
||
max-height: 400rpx;
|
||
overflow-y: auto;
|
||
background: #2d2d2d;
|
||
border-radius: 8rpx;
|
||
padding: 20rpx;
|
||
margin-bottom: 15rpx;
|
||
}
|
||
|
||
.log-line {
|
||
display: block;
|
||
font-size: 24rpx;
|
||
color: #0f0;
|
||
font-family: monospace;
|
||
line-height: 1.8;
|
||
word-break: break-all;
|
||
}
|
||
</style>
|