听辨训练怎么做?HarmonyOS音频播放与交互反馈
如果你对乐理学习感兴趣,可以去鸿蒙应用市场搜一下**「耳聪记」**,下载下来体验体验。播放音程、和弦、节奏,选择正确答案,训练音乐听辨能力。体验完了再回来看这篇文章,你会更清楚音频播放和训练交互是怎么实现的。
写在前面
大家好,我是一名写了十多年Web前端的老兵。从jQuery时代一路走到React/Vue,Web Audio API是我的看家本领之一。去年开始转战鸿蒙生态,用ArkTS开发App,发现音频播放这块差异不小。
比如:
- 音频播放:Web里
new Audio(url).play()就能播放;鸿蒙里得用media.createAudioPlayer()。 - 状态管理:Web里用
useState管理播放状态;鸿蒙里用@State装饰器。 - 事件监听:Web里
audio.addEventListener('ended', handler);鸿蒙里用player.on('complete', callback)。
别担心,接下来这篇文章,我会用"耳聪记"的听辨训练功能,带你看看HarmonyOS怎么播放音频并实现训练交互。
这篇文章聊什么
耳聪记的核心功能是听辨训练:
- 播放音程、和弦、节奏音频
- 显示题目和选项
- 判断答案正误
- 记录训练成绩
第一步:设计训练数据结构
// Web前端同学看这里:React里我们用interface定义类型
// 鸿蒙里ArkTS也支持interface,用法基本一致
// 音程定义
const INTERVALS = [
{ id: 'unison', name: '纯一度', semitones: 0, sound: 'unison.mp3' },
{ id: 'minor2nd', name: '小二度', semitones: 1, sound: 'minor2nd.mp3' },
{ id: 'major2nd', name: '大二度', semitones: 2, sound: 'major2nd.mp3' },
{ id: 'minor3rd', name: '小三度', semitones: 3, sound: 'minor3rd.mp3' },
{ id: 'major3rd', name: '大三度', semitones: 4, sound: 'major3rd.mp3' },
{ id: 'perfect4th', name: '纯四度', semitones: 5, sound: 'perfect4th.mp3' },
{ id: 'tritone', name: '三全音', semitones: 6, sound: 'tritone.mp3' },
{ id: 'perfect5th', name: '纯五度', semitones: 7, sound: 'perfect5th.mp3' },
];
// 和弦类型
const CHORD_TYPES = [
{ id: 'major', name: '大三和弦', intervals: [0, 4, 7], sound: 'major.mp3' },
{ id: 'minor', name: '小三和弦', intervals: [0, 3, 7], sound: 'minor.mp3' },
{ id: 'dim', name: '减三和弦', intervals: [0, 3, 6], sound: 'dim.mp3' },
{ id: 'aug', name: '增三和弦', intervals: [0, 4, 8], sound: 'aug.mp3' },
{ id: 'maj7', name: '大七和弦', intervals: [0, 4, 7, 11], sound: 'maj7.mp3' },
{ id: 'min7', name: '小七和弦', intervals: [0, 3, 7, 10], sound: 'min7.mp3' },
];
// 训练题目
interface TrainingQuestion {
id: string;
type: string; // 'interval' | 'chord' | 'rhythm'
correctAnswer: string;
options: string[];
audioFile: string;
}
// 训练记录
interface TrainingRecord {
date: string;
type: string;
total: number;
correct: number;
accuracy: number;
}
第二步:实现音频播放
// Web前端同学看这里:React里我们用new Audio()播放
// 鸿蒙里用media.createAudioPlayer(),需要异步创建
import { media } from '@kit.MediaKit';
let audioPlayer: media.AudioPlayer | null = null;
async function initAudioPlayer(): Promise<void> {
audioPlayer = media.createAudioPlayer();
}
async function playAudio(fileName: string): Promise<void> {
if (!audioPlayer) {
await initAudioPlayer();
}
try {
// 设置音频源
audioPlayer!.src = `resources/rawfile/${fileName}`;
audioPlayer!.play();
// 监听播放完成
audioPlayer!.on('complete', () => {
console.log('播放完成');
});
} catch (err) {
console.error(`播放失败: ${err}`);
}
}
// 释放资源
function releaseAudioPlayer(): void {
if (audioPlayer) {
audioPlayer.release();
audioPlayer = null;
}
}
第三步:实现训练页面
// Web前端同学看这里:React里我们用useState管理状态
// 鸿蒙里用@State装饰器,状态变化自动触发UI更新
@Entry
@Component
struct TrainingPage {
@State currentQuestion: TrainingQuestion | null = null
@State selectedAnswer: string = ''
@State isAnswered: boolean = false
@State isCorrect: boolean = false
@State score: number = 0
@State totalQuestions: number = 0
async aboutToAppear() {
await this.loadNextQuestion();
}
async loadNextQuestion() {
// 随机选择题型
const types = ['interval', 'chord'];
const type = types[Math.floor(Math.random() * types.length)];
if (type === 'interval') {
const interval = INTERVALS[Math.floor(Math.random() * INTERVALS.length)];
this.currentQuestion = {
id: `q_${Date.now()}`,
type: 'interval',
correctAnswer: interval.name,
options: this.generateOptions(interval.name, INTERVALS.map(i => i.name)),
audioFile: interval.sound
};
} else {
const chord = CHORD_TYPES[Math.floor(Math.random() * CHORD_TYPES.length)];
this.currentQuestion = {
id: `q_${Date.now()}`,
type: 'chord',
correctAnswer: chord.name,
options: this.generateOptions(chord.name, CHORD_TYPES.map(c => c.name)),
audioFile: chord.sound
};
}
this.selectedAnswer = '';
this.isAnswered = false;
this.isCorrect = false;
}
generateOptions(correct: string, allOptions: string[]): string[] {
const options = [correct];
while (options.length < 4) {
const random = allOptions[Math.floor(Math.random() * allOptions.length)];
if (!options.includes(random)) {
options.push(random);
}
}
return options.sort(() => Math.random() - 0.5);
}
async checkAnswer(answer: string) {
if (this.isAnswered || !this.currentQuestion) return;
this.selectedAnswer = answer;
this.isAnswered = true;
this.isCorrect = answer === this.currentQuestion.correctAnswer;
this.totalQuestions++;
if (this.isCorrect) {
this.score++;
}
}
build() {
Column() {
// 得分显示
Row() {
Text(`正确: ${this.score}`)
.fontSize(14)
.fontColor('#22c55e')
Text(`总数: ${this.totalQuestions}`)
.fontSize(14)
.fontColor('#6b7280')
.margin({ left: 16 })
}
.width('100%')
.justifyContent(FlexAlign.Center)
.margin({ bottom: 20 })
if (this.currentQuestion) {
// 题目类型
Text(this.currentQuestion.type === 'interval' ? '听辨音程' : '听辨和弦')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
// 播放按钮
Button('播放音频')
.width(120)
.height(120)
.backgroundColor('#3b82f6')
.borderRadius(60)
.fontSize(16)
.onClick(() => {
playAudio(this.currentQuestion!.audioFile);
})
// 选项
ForEach(this.currentQuestion.options, (option: string) => {
Button(option)
.width('100%')
.height(48)
.backgroundColor(this.getOptionColor(option))
.borderRadius(12)
.margin({ top: 8 })
.onClick(() => this.checkAnswer(option))
})
// 下一题按钮
if (this.isAnswered) {
Button('下一题')
.width('100%')
.height(48)
.backgroundColor('#3b82f6')
.borderRadius(12)
.margin({ top: 20 })
.onClick(() => this.loadNextQuestion())
}
}
}
.padding(16)
}
private getOptionColor(option: string): string {
if (!this.isAnswered) return '#f3f4f6';
if (option === this.currentQuestion?.correctAnswer) return '#22c55e';
if (option === this.selectedAnswer && !this.isCorrect) return '#ef4444';
return '#f3f4f6';
}
}
第四步:常见问题
4.1 音频加载延迟
问题:首次播放有延迟。
解决:在页面加载时预初始化播放器。
4.2 播放中断
问题:快速切换题目时音频混乱。
解决:播放新音频前先调用stop()停止当前播放。
总结
这篇文章围绕"耳聪记"的听辨训练功能,讲解了:
音频播放
- media.createAudioPlayer()的使用
- 音频源设置和播放控制
- 播放完成事件监听
训练交互
- 随机题目生成
- 选项判断和反馈
- 得分统计
如果你对"耳聪记"感兴趣,欢迎去鸿蒙应用市场搜索下载体验。