1. 项目配置与权限设置
1.1 配置module.json5
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.MICROPHONE",
"reason": "录音需要麦克风权限"
},
{
"name": "ohos.permission.WRITE_MEDIA",
"reason": "保存录音文件到媒体库"
},
{
"name": "ohos.permission.READ_MEDIA",
"reason": "读取录音文件"
}
],
"abilities": [
{
"name": "EntryAbility",
"backgroundModes": ["audioRecording"]
}
]
}
}
2. 录音核心功能实现
2.1 录音服务封装
// src/main/ets/service/AudioRecorder.ts
import audio from '@ohos.multimedia.audio';
import fs from '@ohos.file.fs';
import mediaLibrary from '@ohos.multimedia.mediaLibrary';
export class AudioRecorder {
private audioRecorder: audio.AudioRecorder | null = null;
private filePath: string = '';
private audioOptions: audio.AudioRecorderOptions = {
encoder: audio.AudioEncoder.AAC_LC,
sampleRate: audio.AudioSampleRate.SAMPLE_RATE_44100,
numberOfChannels: audio.AudioChannel.CHANNEL_2,
format: audio.AudioOutputFormat.MPEG_4,
uri: '', // 将在startRecording时设置
location: { latitude: 0, longitude: 0 } // 可选地理位置
};
async startRecording(): Promise<boolean> {
try {
// 创建录音保存路径
const context = getContext(this) as Context;
const dir = context.filesDir + '/recordings';
await fs.ensureDir(dir);
this.filePath = `${dir}/recording_${new Date().getTime()}.m4a`;
// 初始化录音器
this.audioRecorder = await audio.createAudioRecorder();
this.audioOptions.uri = `file://${this.filePath}`;
// 配置并启动录音
await this.audioRecorder.prepare(this.audioOptions);
await this.audioRecorder.start();
return true;
} catch (err) {
console.error('启动录音失败:', err);
return false;
}
}
async stopRecording(): Promise<string | null> {
if (!this.audioRecorder) return null;
try {
await this.audioRecorder.stop();
await this.audioRecorder.release();
this.audioRecorder = null;
// 保存到媒体库
const media = mediaLibrary.getMediaLibrary(getContext(this) as Context);
const fileAsset = await media.createAsset(
mediaLibrary.MediaType.AUDIO,
'recording.m4a',
this.filePath
);
return fileAsset.uri;
} catch (err) {
console.error('停止录音失败:', err);
return null;
}
}
async pauseRecording(): Promise<boolean> {
if (!this.audioRecorder) return false;
try {
await this.audioRecorder.pause();
return true;
} catch (err) {
console.error('暂停录音失败:', err);
return false;
}
}
async resumeRecording(): Promise<boolean> {
if (!this.audioRecorder) return false;
try {
await this.audioRecorder.resume();
return true;
} catch (err) {
console.error('恢复录音失败:', err);
return false;
}
}
getRecordingState(): audio.AudioState | null {
return this.audioRecorder?.getState() || null;
}
}
2.2 录音状态管理
// src/main/ets/model/RecordingModel.ts
export class RecordingModel {
@Tracked recordingState: 'idle' | 'recording' | 'paused' = 'idle';
@Tracked currentDuration: number = 0; // 毫秒
@Tracked filePath: string | null = null;
private timer: number | null = null;
startTimer() {
this.stopTimer();
this.timer = setInterval(() => {
this.currentDuration += 1000;
}, 1000);
}
stopTimer() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
}
reset() {
this.stopTimer();
this.recordingState = 'idle';
this.currentDuration = 0;
this.filePath = null;
}
}
3. 用户界面实现
3.1 主录音界面
// src/main/ets/pages/RecordPage.ets
import { AudioRecorder } from '../service/AudioRecorder';
import { RecordingModel } from '../model/RecordingModel';
@Entry
@Component
struct RecordPage {
private audioRecorder: AudioRecorder = new AudioRecorder();
@State recordingModel: RecordingModel = new RecordingModel();
build() {
Column() {
// 录音时间显示
Text(this.formatTime(this.recordingModel.currentDuration))
.fontSize(40)
.margin({ top: 50 })
// 波形图占位
this.buildWaveform()
// 录音控制按钮
this.buildControlButtons()
// 录音文件列表
if (this.recordingModel.filePath) {
this.buildRecordingInfo()
}
}
.width('100%')
.height('100%')
}
@Builder
buildWaveform() {
// 实现波形图显示
Canvas(this.context)
.width('90%')
.height(150)
.margin({ top: 30, bottom: 30 })
.onReady(() => {
// 绘制波形图的逻辑
})
}
@Builder
buildControlButtons() {
Row() {
if (this.recordingModel.recordingState === 'idle') {
Button('开始录音', { type: ButtonType.Circle })
.width(80)
.height(80)
.backgroundColor('#FF5722')
.onClick(() => this.startRecording())
} else {
if (this.recordingModel.recordingState === 'recording') {
Button('暂停', { type: ButtonType.Circle })
.width(60)
.height(60)
.backgroundColor('#4CAF50')
.onClick(() => this.pauseRecording())
} else {
Button('继续', { type: ButtonType.Circle })
.width(60)
.height(60)
.backgroundColor('#2196F3')
.onClick(() => this.resumeRecording())
}
Button('停止', { type: ButtonType.Circle })
.width(60)
.height(60)
.backgroundColor('#F44336')
.margin({ left: 30 })
.onClick(() => this.stopRecording())
}
}
.margin({ top: 50 })
}
@Builder
buildRecordingInfo() {
Column() {
Text('录音文件:')
.fontSize(16)
.margin({ bottom: 10 })
Text(this.recordingModel.filePath || '')
.fontSize(14)
.fontColor('#666666')
Button('播放录音')
.margin({ top: 20 })
.onClick(() => this.playRecording())
}
.margin({ top: 30 })
}
async startRecording() {
const success = await this.audioRecorder.startRecording();
if (success) {
this.recordingModel.recordingState = 'recording';
this.recordingModel.startTimer();
}
}
async pauseRecording() {
const success = await this.audioRecorder.pauseRecording();
if (success) {
this.recordingModel.recordingState = 'paused';
this.recordingModel.stopTimer();
}
}
async resumeRecording() {
const success = await this.audioRecorder.resumeRecording();
if (success) {
this.recordingModel.recordingState = 'recording';
this.recordingModel.startTimer();
}
}
async stopRecording() {
const filePath = await this.audioRecorder.stopRecording();
if (filePath) {
this.recordingModel.filePath = filePath;
}
this.recordingModel.reset();
}
async playRecording() {
// 实现播放录音功能
}
formatTime(milliseconds: number): string {
const totalSeconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
}
3.2 录音列表页面
// src/main/ets/pages/RecordingsListPage.ets
import { mediaLibrary } from '@ohos.multimedia.mediaLibrary';
@Entry
@Component
struct RecordingsListPage {
@State recordings: Array<mediaLibrary.FileAsset> = [];
aboutToAppear() {
this.loadRecordings();
}
async loadRecordings() {
try {
const media = mediaLibrary.getMediaLibrary(getContext(this) as Context);
const fetchOpts: mediaLibrary.MediaFetchOptions = {
selections: `${mediaLibrary.FileKey.MEDIA_TYPE}=?`,
selectionArgs: [mediaLibrary.MediaType.AUDIO.toString()],
order: `${mediaLibrary.FileKey.DATE_ADDED} DESC`
};
const fetchResult = await media.getFileAssets(fetchOpts);
this.recordings = await fetchResult.getAllObject();
} catch (err) {
console.error('加载录音列表失败:', err);
}
}
build() {
List({ space: 10 }) {
ForEach(this.recordings, (recording) => {
ListItem() {
RecordingItem({ recording: recording })
}
})
}
.width('100%')
.height('100%')
}
}
@Component
struct RecordingItem {
private recording: mediaLibrary.FileAsset;
build() {
Row() {
Image($r('app.media.ic_audio'))
.width(40)
.height(40)
.margin({ right: 15 })
Column() {
Text(this.recording.displayName)
.fontSize(16)
Text(this.formatDate(this.recording.dateAdded * 1000))
.fontSize(12)
.fontColor('#888888')
}
.layoutWeight(1)
Text(this.formatDuration(this.recording.duration))
.fontSize(14)
}
.padding(15)
}
formatDate(timestamp: number): string {
const date = new Date(timestamp);
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
}
formatDuration(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
}
4. 音频播放功能实现
4.1 音频播放器封装
// src/main/ets/service/AudioPlayer.ts
import audio from '@ohos.multimedia.audio';
export class AudioPlayer {
private audioPlayer: audio.AudioPlayer | null = null;
private currentUri: string = '';
async play(uri: string): Promise<boolean> {
if (this.audioPlayer && this.currentUri === uri) {
await this.audioPlayer.play();
return true;
}
try {
await this.stop();
this.audioPlayer = await audio.createAudioPlayer();
this.currentUri = uri;
await this.audioPlayer.reset();
await this.audioPlayer.setSource({ source: uri });
await this.audioPlayer.play();
return true;
} catch (err) {
console.error('播放失败:', err);
return false;
}
}
async pause(): Promise<boolean> {
if (!this.audioPlayer) return false;
try {
await this.audioPlayer.pause();
return true;
} catch (err) {
console.error('暂停失败:', err);
return false;
}
}
async stop(): Promise<boolean> {
if (!this.audioPlayer) return true;
try {
await this.audioPlayer.stop();
await this.audioPlayer.release();
this.audioPlayer = null;
this.currentUri = '';
return true;
} catch (err) {
console.error('停止失败:', err);
return false;
}
}
async seek(position: number): Promise<boolean> {
if (!this.audioPlayer) return false;
try {
await this.audioPlayer.seek(position);
return true;
} catch (err) {
console.error('跳转失败:', err);
return false;
}
}
}
4.2 播放控制组件
// src/main/ets/components/PlayerControls.ets
@Component
export struct PlayerControls {
private player: AudioPlayer;
@State isPlaying: boolean = false;
@State currentPosition: number = 0;
@State duration: number = 0;
private updateInterval: number | null = null;
build() {
Column() {
// 进度条
Slider({
value: this.currentPosition,
min: 0,
max: this.duration,
step: 1,
style: SliderStyle.OutSet
})
.onChange((value: number) => {
this.player.seek(value);
})
// 时间显示
Row() {
Text(this.formatTime(this.currentPosition))
.fontSize(12)
Blank()
Text(this.formatTime(this.duration))
.fontSize(12)
}
.width('100%')
// 控制按钮
Row() {
Button(this.isPlaying ? '暂停' : '播放')
.onClick(() => {
if (this.isPlaying) {
this.player.pause();
} else {
this.player.play();
}
})
Button('停止')
.margin({ left: 20 })
.onClick(() => {
this.player.stop();
})
}
.margin({ top: 15 })
}
}
aboutToAppear() {
this.startUpdatingPosition();
}
aboutToDisappear() {
this.stopUpdatingPosition();
}
startUpdatingPosition() {
this.updateInterval = setInterval(() => {
// 更新当前播放位置
}, 500);
}
stopUpdatingPosition() {
if (this.updateInterval) {
clearInterval(this.updateInterval);
this.updateInterval = null;
}
}
formatTime(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
}
5. 功能扩展建议
录音质量设置:
- 添加不同采样率和编码格式选项
- 实现高质量和低质量录音切换
录音编辑功能:
- 实现录音裁剪功能
- 添加淡入淡出效果
录音标签管理:
- 为录音添加标签和备注
- 实现录音分类管理
云同步功能:
- 集成华为云存储
- 实现录音多设备同步
高级音频处理:
- 添加降噪功能
- 实现音频变速播放