走进HarmonyOS开发中的声音之谜
HarmonyOS APP开发中的声音设置:@ohos.multimedia.audio 全解析——从音量控制到音频焦点,让声音井井有条
📌 核心要点:audio 模块提供音量控制、音频路由、静音模式和音频焦点管理,是构建音频应用和系统级声音设置的核心基础设施
一、背景与动机
想象一下这个场景——你正在用手机听音乐,突然来了个电话,音乐自动淡出,铃声响起;通话结束后,音乐又自动淡入继续播放。这种丝滑的体验背后,是音频焦点管理在默默工作。
再比如,你正在看视频,想调大音量,结果把铃声音量调大了——因为系统默认调节的是铃声音量,而不是媒体音量。这种"调错音量"的尴尬,是因为音频流类型和音量控制没有正确关联。
HarmonyOS 的 @ohos.multimedia.audio 模块,就是为了解决这些问题而存在的。它就像一个"交通警察",管理着各种声音的通行权——谁该响、谁该静、谁该让路,都安排得明明白白。
典型使用场景:
- 音乐播放器:音频焦点申请与释放、音量控制
- 视频通话:通话音量独立控制、静音切换
- 游戏应用:游戏音效与背景音乐混合
- 系统设置:音量面板、静音模式、音频路由切换
二、核心原理
2.1 音频系统架构

2.2 音频流类型(AudioVolumeType)
HarmonyOS 将声音分为不同的流类型,每种类型有独立的音量控制:
| 流类型 | 枚举值 | 说明 | 典型场景 |
|---|---|---|---|
| RINGTONE | 2 | 铃声 | 来电铃声、通知提示音 |
| MEDIA | 3 | 媒体 | 音乐、视频、游戏音效 |
| VOICE_CALL | 0 | 语音通话 | 电话通话 |
| SYSTEM | 6 | 系统 | 系统按键音、开关机音 |
| ALARM | 4 | 闹钟 | 闹钟铃声 |
| ACCESSIBILITY | 5 | 辅助功能 | 屏幕朗读 |
| VOICE_ASSISTANT | 9 | 语音助手 | 小艺语音 |
2.3 音频焦点机制
sequenceDiagram
participant Music as 音乐播放器
participant System as 音频焦点管理器
participant Phone as 电话应用
Music->>System: requestAudioFocus(MEDIA)
System-->>Music: 焦点授予 ✓
Note over Music: 播放音乐中...
Phone->>System: requestAudioFocus(VOICE_CALL)
System->>Music: onAudioFocusChange(LOST)
Note over Music: 音乐暂停
System-->>Phone: 焦点授予 ✓
Note over Phone: 通话进行中...
Phone->>System: abandonAudioFocus()
System->>Music: onAudioFocusChange(GAINED)
Note over Music: 音乐恢复播放
音频焦点就像会议室的"发言权"——同一时间只有一个人能发言,新来的人要发言,之前的人就得让座。但"让座"的方式有多种:完全暂停、降低音量(duck)、或者只是被通知一下。
三、代码实战
3.1 音量控制:多流类型音量管理
不同流类型的音量是独立的。调节媒体音量不应该影响铃声音量,反之亦然。
import { audio } from '@kit.MultimediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 音量管理器
* 封装各音频流类型的音量读写操作
*/
class VolumeManager {
private audioManager: audio.AudioManager | null = null;
/**
* 初始化音频管理器
*/
async init(): Promise<void> {
this.audioManager = audio.getAudioManager();
console.info('[Volume] 音频管理器初始化成功');
}
/**
* 获取指定流类型的音量
* @param volumeType 音频流类型
*/
async getVolume(volumeType: audio.AudioVolumeType): Promise<number> {
if (!this.audioManager) return 0;
try {
const volume = await this.audioManager.getVolume(volumeType);
console.info(`[Volume] ${this.getTypeName(volumeType)} 音量: ${volume}`);
return volume;
} catch (err) {
console.error(`[Volume] 获取音量失败: ${JSON.stringify(err)}`);
return 0;
}
}
/**
* 设置指定流类型的音量
* @param volumeType 音频流类型
* @param volume 音量值
*/
async setVolume(volumeType: audio.AudioVolumeType, volume: number): Promise<void> {
if (!this.audioManager) return;
try {
// 先获取最大音量,做范围校验
const maxVolume = await this.audioManager.getMaxVolume(volumeType);
const minVolume = await this.audioManager.getMinVolume(volumeType);
const clampedVolume = Math.max(minVolume, Math.min(maxVolume, volume));
await this.audioManager.setVolume(volumeType, clampedVolume);
console.info(`[Volume] ${this.getTypeName(volumeType)} 音量设置: ${clampedVolume}`);
} catch (err) {
console.error(`[Volume] 设置音量失败: ${JSON.stringify(err)}`);
}
}
/**
* 获取最大音量
*/
async getMaxVolume(volumeType: audio.AudioVolumeType): Promise<number> {
if (!this.audioManager) return 15;
try {
return await this.audioManager.getMaxVolume(volumeType);
} catch (err) {
return 15;
}
}
/**
* 获取最小音量
*/
async getMinVolume(volumeType: audio.AudioVolumeType): Promise<number> {
if (!this.audioManager) return 0;
try {
return await this.audioManager.getMinVolume(volumeType);
} catch (err) {
return 0;
}
}
/**
* 逐步调节音量(模拟物理按键效果)
* @param volumeType 流类型
* @param direction 方向: 1=增大, -1=减小
*/
async stepVolume(volumeType: audio.AudioVolumeType, direction: number): Promise<void> {
const current = await this.getVolume(volumeType);
const newVolume = current + direction;
await this.setVolume(volumeType, newVolume);
}
/**
* 判断指定流类型是否静音
*/
async isMute(volumeType: audio.AudioVolumeType): Promise<boolean> {
if (!this.audioManager) return false;
try {
return await this.audioManager.isMute(volumeType);
} catch (err) {
return false;
}
}
/**
* 设置指定流类型静音
*/
async setMute(volumeType: audio.AudioVolumeType, mute: boolean): Promise<void> {
if (!this.audioManager) return;
try {
await this.audioManager.mute(volumeType, mute);
console.info(`[Volume] ${this.getTypeName(volumeType)} ${mute ? '静音' : '取消静音'}`);
} catch (err) {
console.error(`[Volume] 设置静音失败: ${JSON.stringify(err)}`);
}
}
/**
* 监听音量变化
*/
onVolumeChange(callback: (volumeType: audio.AudioVolumeType, volume: number) => void): void {
if (!this.audioManager) return;
this.audioManager.on('volumeChange', (volumeEvent: audio.VolumeEvent) => {
callback(volumeEvent.volumeType, volumeEvent.volume);
});
}
/**
* 获取流类型名称(用于日志)
*/
private getTypeName(type: audio.AudioVolumeType): string {
const nameMap: Record<number, string> = {
0: '语音通话',
2: '铃声',
3: '媒体',
4: '闹钟',
5: '辅助功能',
6: '系统',
9: '语音助手',
};
return nameMap[type] || `未知(${type})`;
}
}
export { VolumeManager };
3.2 音频路由与静音模式:蓝牙/扬声器切换
音频路由决定了声音从哪里出来——扬声器、耳机还是听筒。静音模式则控制是否允许声音播放。
import { audio } from '@kit.MultimediaKit';
/**
* 音频路由管理器
* 管理音频输出设备切换和静音模式
*/
class AudioRouteManager {
private audioManager: audio.AudioManager | null = null;
private audioRoutingManager: audio.AudioRoutingManager | null = null;
/**
* 初始化
*/
async init(): Promise<void> {
this.audioManager = audio.getAudioManager();
this.audioRoutingManager = this.audioManager.getRoutingManager();
console.info('[Route] 音频路由管理器初始化成功');
}
/**
* 获取当前输出设备列表
*/
async getOutputDevices(): Promise<audio.AudioDeviceDescriptors> {
if (!this.audioRoutingManager) return [];
try {
const devices = await this.audioRoutingManager.getDevices(audio.DeviceFlag.OUTPUT_DEVICES_FLAG);
console.info(`[Route] 输出设备数量: ${devices.length}`);
devices.forEach((device, index) => {
console.info(`[Route] 设备${index}: ${this.getDeviceName(device.deviceType)}`);
});
return devices;
} catch (err) {
console.error(`[Route] 获取设备失败: ${JSON.stringify(err)}`);
return [];
}
}
/**
* 判断是否连接了蓝牙音频设备
*/
async hasBluetoothDevice(): Promise<boolean> {
const devices = await this.getOutputDevices();
return devices.some(d =>
d.deviceType === audio.DeviceType.BLUETOOTH_A2DP ||
d.deviceType === audio.DeviceType.BLUETOOTH_SCO
);
}
/**
* 判断是否连接了有线耳机
*/
async hasWiredHeadset(): Promise<boolean> {
const devices = await this.getOutputDevices();
return devices.some(d => d.deviceType === audio.DeviceType.WIRED_HEADSET);
}
/**
* 获取当前活跃的输出设备类型
*/
async getActiveOutputType(): Promise<string> {
const devices = await this.getOutputDevices();
if (devices.length === 0) return '无设备';
// 优先级:蓝牙 > 有线 > 扬声器
if (devices.some(d => d.deviceType === audio.DeviceType.BLUETOOTH_A2DP)) {
return '蓝牙耳机';
}
if (devices.some(d => d.deviceType === audio.DeviceType.WIRED_HEADSET)) {
return '有线耳机';
}
if (devices.some(d => d.deviceType === audio.DeviceType.EARPIECE)) {
return '听筒';
}
return '扬声器';
}
/**
* 监听音频设备变化
* 蓝牙连接/断开、耳机插拔等
*/
onDeviceChange(callback: (deviceChanged: audio.AudioDeviceDescriptors) => void): void {
if (!this.audioRoutingManager) return;
this.audioRoutingManager.on('deviceChange', audio.DeviceFlag.OUTPUT_DEVICES_FLAG,
(deviceChanged: audio.AudioDeviceDescriptors) => {
console.info('[Route] 音频设备变更');
callback(deviceChanged);
}
);
}
/**
* 获取设备名称
*/
private getDeviceName(type: audio.DeviceType): string {
const nameMap: Record<number, string> = {
1: '无效',
2: '扬声器',
3: '有线耳机',
4: '蓝牙A2DP',
7: '蓝牙SCO',
8: '听筒',
14: 'USB',
17: '蓝牙HFP',
};
return nameMap[type] || `未知(${type})`;
}
}
/**
* 静音模式管理器
*/
class MuteModeManager {
private audioManager: audio.AudioManager | null = null;
async init(): Promise<void> {
this.audioManager = audio.getAudioManager();
}
/**
* 获取静音状态
*/
async getMuteStatus(): Promise<Record<string, boolean>> {
if (!this.audioManager) return {};
try {
return {
media: await this.audioManager.isMute(audio.AudioVolumeType.MEDIA),
ringtone: await this.audioManager.isMute(audio.AudioVolumeType.RINGTONE),
system: await this.audioManager.isMute(audio.AudioVolumeType.SYSTEM),
alarm: await this.audioManager.isMute(audio.AudioVolumeType.ALARM),
};
} catch (err) {
console.error(`[Mute] 获取静音状态失败: ${JSON.stringify(err)}`);
return {};
}
}
/**
* 全部静音(勿扰模式)
*/
async muteAll(): Promise<void> {
if (!this.audioManager) return;
try {
await this.audioManager.mute(audio.AudioVolumeType.MEDIA, true);
await this.audioManager.mute(audio.AudioVolumeType.RINGTONE, true);
await this.audioManager.mute(audio.AudioVolumeType.SYSTEM, true);
// 注意:闹钟通常不静音,避免用户错过重要提醒
console.info('[Mute] 已进入勿扰模式(闹钟除外)');
} catch (err) {
console.error(`[Mute] 全部静音失败: ${JSON.stringify(err)}`);
}
}
/**
* 取消全部静音
*/
async unmuteAll(): Promise<void> {
if (!this.audioManager) return;
try {
await this.audioManager.mute(audio.AudioVolumeType.MEDIA, false);
await this.audioManager.mute(audio.AudioVolumeType.RINGTONE, false);
await this.audioManager.mute(audio.AudioVolumeType.SYSTEM, false);
console.info('[Mute] 已退出勿扰模式');
} catch (err) {
console.error(`[Mute] 取消静音失败: ${JSON.stringify(err)}`);
}
}
}
/**
* 音量与路由控制页面
*/
@Entry
@Component
struct AudioSettingPage {
@State mediaVolume: number = 0;
@State ringVolume: number = 0;
@State maxVolume: number = 15;
@State isMediaMute: boolean = false;
@State activeOutput: string = '扬声器';
private volumeManager: VolumeManager = new VolumeManager();
private routeManager: AudioRouteManager = new AudioRouteManager();
async aboutToAppear(): Promise<void> {
await this.volumeManager.init();
await this.routeManager.init();
// 读取当前音量
this.mediaVolume = await this.volumeManager.getVolume(audio.AudioVolumeType.MEDIA);
this.ringVolume = await this.volumeManager.getVolume(audio.AudioVolumeType.RINGTONE);
this.maxVolume = await this.volumeManager.getMaxVolume(audio.AudioVolumeType.MEDIA);
this.isMediaMute = await this.volumeManager.isMute(audio.AudioVolumeType.MEDIA);
this.activeOutput = await this.routeManager.getActiveOutputType();
// 监听音量变化
this.volumeManager.onVolumeChange((type, volume) => {
if (type === audio.AudioVolumeType.MEDIA) {
this.mediaVolume = volume;
} else if (type === audio.AudioVolumeType.RINGTONE) {
this.ringVolume = volume;
}
});
// 监听设备变化
this.routeManager.onDeviceChange(async () => {
this.activeOutput = await this.routeManager.getActiveOutputType();
});
}
build() {
Scroll() {
Column() {
Text('🔊 声音设置')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 24 })
// 当前输出设备
Row() {
Text('当前输出')
.fontSize(16)
.layoutWeight(1)
Text(this.activeOutput)
.fontSize(16)
.fontColor('#4CAF50')
.fontWeight(FontWeight.Bold)
}
.width('100%')
.padding(16)
.backgroundColor('#E8F5E9')
.borderRadius(12)
.margin({ bottom: 20 })
// 媒体音量
Column() {
Row() {
Text('🎵 媒体音量')
.fontSize(16)
.layoutWeight(1)
Text(`${this.mediaVolume}/${this.maxVolume}`)
.fontSize(14)
.fontColor('#666')
}
.width('100%')
Slider({
value: this.mediaVolume,
min: 0,
max: this.maxVolume,
step: 1,
style: SliderStyle.OutSet
})
.width('100%')
.onChange(async (value: number) => {
await this.volumeManager.setVolume(audio.AudioVolumeType.MEDIA, value);
})
}
.width('100%')
.padding(16)
.backgroundColor('#f5f5f5')
.borderRadius(12)
.margin({ bottom: 12 })
// 铃声音量
Column() {
Row() {
Text('🔔 铃声音量')
.fontSize(16)
.layoutWeight(1)
Text(`${this.ringVolume}/${this.maxVolume}`)
.fontSize(14)
.fontColor('#666')
}
.width('100%')
Slider({
value: this.ringVolume,
min: 0,
max: this.maxVolume,
step: 1,
style: SliderStyle.OutSet
})
.width('100%')
.onChange(async (value: number) => {
await this.volumeManager.setVolume(audio.AudioVolumeType.RINGTONE, value);
})
}
.width('100%')
.padding(16)
.backgroundColor('#f5f5f5')
.borderRadius(12)
.margin({ bottom: 12 })
// 静音开关
Row() {
Text('🔇 媒体静音')
.fontSize(16)
.layoutWeight(1)
Toggle({ type: ToggleType.Switch, isOn: this.isMediaMute })
.onChange(async (isOn: boolean) => {
this.isMediaMute = isOn;
await this.volumeManager.setMute(audio.AudioVolumeType.MEDIA, isOn);
})
}
.width('100%')
.padding(16)
.backgroundColor('#f5f5f5')
.borderRadius(12)
}
.padding(20)
}
.width('100%')
.height('100%')
}
}
export { AudioRouteManager, MuteModeManager };
3.3 音频焦点管理:多应用音频协调
音频焦点是音频系统中最容易被忽视、却又最关键的能力。没有焦点管理,多个应用同时播放声音就是一场灾难。
import { audio } from '@kit.MultimediaKit';
/**
* 音频焦点类型说明:
* AUDIOFOCUS_DEFAULT = 0 默认焦点
* AUDIOFOCUS_GAIN = 1 获得焦点(可正常播放)
* AUDIOFOCUS_LOSS = 2 永久失去焦点(需停止播放)
* AUDIOFOCUS_LOSS_TRANSIENT = 3 暂时失去焦点(如通知提示音,稍后可恢复)
* AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK = 4 暂时失去焦点但可降低音量播放
*/
/**
* 音频焦点管理器
* 封装焦点申请、释放和变更监听
*/
class AudioFocusManager {
private audioManager: audio.AudioManager | null = null;
private audioSessionManager: audio.AudioSessionManager | null = null;
private currentFocusType: audio.AudioFocusType = audio.AudioFocusType.AUDIOFOCUS_GAIN;
private isFocused: boolean = false;
/**
* 初始化
*/
async init(): Promise<void> {
this.audioManager = audio.getAudioManager();
this.audioSessionManager = this.audioManager.getSessionManager();
console.info('[Focus] 音频焦点管理器初始化成功');
}
/**
* 申请音频焦点
* @param focusType 焦点类型
* @param streamUsage 音频流用途
*/
async requestFocus(
focusType: audio.AudioFocusType,
streamUsage: audio.StreamUsage = audio.StreamUsage.STREAM_USAGE_MUSIC
): Promise<boolean> {
if (!this.audioSessionManager) return false;
try {
// 构建焦点请求参数
const focusRequest: audio.AudioFocusRequest = {
focusType: focusType,
streamUsage: streamUsage,
};
// 申请焦点
await this.audioSessionManager.requestAudioFocus(focusRequest);
this.isFocused = true;
this.currentFocusType = focusType;
console.info(`[Focus] 焦点申请成功: type=${focusType}`);
return true;
} catch (err) {
console.error(`[Focus] 焦点申请失败: ${JSON.stringify(err)}`);
this.isFocused = false;
return false;
}
}
/**
* 释放音频焦点
*/
async abandonFocus(): Promise<void> {
if (!this.audioSessionManager) return;
try {
await this.audioSessionManager.abandonAudioFocus();
this.isFocused = false;
console.info('[Focus] 焦点已释放');
} catch (err) {
console.error(`[Focus] 焦点释放失败: ${JSON.stringify(err)}`);
}
}
/**
* 监听音频焦点变更
* 当其他应用申请焦点时,当前应用会收到通知
*/
onFocusChange(callback: (focusType: audio.AudioFocusType) => void): void {
if (!this.audioSessionManager) return;
this.audioSessionManager.on('audioFocusChange', (focusChangeInfo: audio.AudioFocusChangeInfo) => {
const focusType = focusChangeInfo.focusType;
console.info(`[Focus] 焦点变更: ${this.getFocusName(focusType)}`);
switch (focusType) {
case audio.AudioFocusType.AUDIOFOCUS_GAIN:
// 重新获得焦点,恢复播放
this.isFocused = true;
break;
case audio.AudioFocusType.AUDIOFOCUS_LOSS:
// 永久失去焦点,停止播放
this.isFocused = false;
break;
case audio.AudioFocusType.AUDIOFOCUS_LOSS_TRANSIENT:
// 暂时失去焦点,暂停播放
this.isFocused = false;
break;
case audio.AudioFocusType.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 可降低音量继续播放(duck 模式)
this.isFocused = true;
break;
}
callback(focusType);
});
}
/**
* 是否持有焦点
*/
hasFocus(): boolean {
return this.isFocused;
}
/**
* 获取焦点名称
*/
private getFocusName(type: audio.AudioFocusType): string {
const nameMap: Record<number, string> = {
0: '默认',
1: '获得焦点',
2: '永久失去',
3: '暂时失去',
4: '可降低音量',
};
return nameMap[type] || `未知(${type})`;
}
}
/**
* 音乐播放器示例:集成音频焦点
*/
@Entry
@Component
struct MusicPlayerWithFocus {
@State isPlaying: boolean = false;
@State focusStatus: string = '未申请';
@State volumeDucked: boolean = false;
private focusManager: AudioFocusManager = new AudioFocusManager();
async aboutToAppear(): Promise<void> {
await this.focusManager.init();
// 监听焦点变更
this.focusManager.onFocusChange((focusType) => {
switch (focusType) {
case audio.AudioFocusType.AUDIOFOCUS_GAIN:
this.focusStatus = '已获得焦点';
this.volumeDucked = false;
// 恢复正常播放
break;
case audio.AudioFocusType.AUDIOFOCUS_LOSS:
this.focusStatus = '焦点被抢占';
this.isPlaying = false;
// 停止播放
break;
case audio.AudioFocusType.AUDIOFOCUS_LOSS_TRANSIENT:
this.focusStatus = '暂时失去焦点';
this.isPlaying = false;
// 暂停播放,等待恢复
break;
case audio.AudioFocusType.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
this.focusStatus = '降低音量播放';
this.volumeDucked = true;
// 降低音量继续播放
break;
}
});
}
/**
* 开始播放
*/
async startPlay(): Promise<void> {
// 先申请焦点
const granted = await this.focusManager.requestFocus(
audio.AudioFocusType.AUDIOFOCUS_GAIN,
audio.StreamUsage.STREAM_USAGE_MUSIC
);
if (granted) {
this.isPlaying = true;
this.focusStatus = '已获得焦点';
console.info('[Player] 开始播放');
} else {
this.focusStatus = '焦点申请被拒';
console.warn('[Player] 无法获取焦点,播放失败');
}
}
/**
* 停止播放
*/
async stopPlay(): Promise<void> {
this.isPlaying = false;
// 释放焦点
await this.focusManager.abandonFocus();
this.focusStatus = '已释放焦点';
}
build() {
Column() {
Text('🎵 音乐播放器')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 24 })
// 焦点状态
Row() {
Text('焦点状态:')
.fontSize(14)
.fontColor('#666')
Text(this.focusStatus)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(this.focusManager.hasFocus() ? '#4CAF50' : '#F44336')
}
.width('100%')
.padding(12)
.backgroundColor('#f5f5f5')
.borderRadius(8)
.margin({ bottom: 16 })
// Duck 提示
if (this.volumeDucked) {
Row() {
Text('⚠️ 音频被降低(有更高优先级的声音正在播放)')
.fontSize(12)
.fontColor('#FF9800')
}
.width('100%')
.padding(8)
.backgroundColor('#FFF3E0')
.borderRadius(8)
.margin({ bottom: 16 })
}
// 播放控制
Row() {
Button(this.isPlaying ? '⏸ 暂停' : '▶ 播放')
.fontSize(16)
.backgroundColor(this.isPlaying ? '#FF9800' : '#4CAF50')
.onClick(async () => {
if (this.isPlaying) {
await this.stopPlay();
} else {
await this.startPlay();
}
})
}
.width('100%')
.justifyContent(FlexAlign.Center)
}
.width('100%')
.height('100%')
.padding(20)
}
async aboutToDisappear(): Promise<void> {
if (this.isPlaying) {
await this.stopPlay();
}
}
}
export { AudioFocusManager };
四、踩坑与注意事项
4.1 音量设置需要系统权限
坑:应用内调用 setVolume() 报权限错误。
解:setVolume() 需要 ohos.permission.ACCESS_NOTIFICATION_POLICY 系统权限。普通应用只能读取音量,不能修改。如果需要调节音量,建议引导用户通过系统音量面板操作。
4.2 静音不等于音量为 0
坑:把音量设为 0 和静音是两个不同的状态。取消静音后恢复的是静音前的音量,而不是 0。
解:使用 mute() 方法管理静音状态,不要通过设置音量为 0 来模拟静音。
4.3 音频焦点必须主动申请
坑:直接播放音频而不申请焦点,导致与其他应用声音冲突。
解:播放前务必申请焦点,播放结束或暂停时释放焦点。
// ✅ 正确流程
async play() {
const granted = await this.focusManager.requestFocus(
audio.AudioFocusType.AUDIOFOCUS_GAIN
);
if (granted) {
// 开始播放
}
}
async pause() {
// 暂停时可以保持焦点,也可以释放
// 取决于业务需求
}
async stop() {
// 停止时务必释放焦点
await this.focusManager.abandonFocus();
}
4.4 蓝牙设备切换延迟
坑:蓝牙耳机连接后,音频路由切换有 1-2 秒延迟,这段时间声音可能从扬声器漏出。
解:监听 deviceChange 事件,在检测到蓝牙设备后延迟 500ms 再开始播放。
4.5 Duck 模式的音量控制
坑:收到 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 后,需要自行降低音量,系统不会自动处理。
解:在 duck 回调中手动降低播放音量(如降低到 20%),焦点恢复后再调回。
五、HarmonyOS 6 适配
5.1 API 变化一览
| 变化项 | HarmonyOS 5 | HarmonyOS 6 |
|---|---|---|
| AudioSessionManager | 基础焦点管理 | 新增 Session 分组,支持多流协同 |
| 空间音频 | 不支持 | 新增 SpatialAudioManager 空间音频 |
| 音频Offload | 不支持 | 新增音频 Offload 低功耗播放 |
| 多声道 | 立体声 | 新增 5.1/7.1 多声道支持 |
| 音频特效 | 基础均衡器 | 新增 AudioEffectManager 音频特效管理 |
5.2 迁移指南
// HarmonyOS 5:基础焦点申请
const focusRequest: audio.AudioFocusRequest = {
focusType: audio.AudioFocusType.AUDIOFOCUS_GAIN,
streamUsage: audio.StreamUsage.STREAM_USAGE_MUSIC,
};
await audioSessionManager.requestAudioFocus(focusRequest);
// HarmonyOS 6:新增 Session 分组
const session = await audioSessionManager.createSession({
sessionType: audio.AudioSessionType.AUDIO_SESSION_TYPE_MEDIA,
concurrencyType: audio.AudioConcurrencyType.CONCURRENCY_TYPE_EXCLUSIVE,
});
await session.requestFocus(focusRequest);
5.3 注意事项
- Session 分组:HarmonyOS 6 的 Session 机制允许将多个音频流归入同一组,统一管理焦点。比如将背景音乐和音效归入同一 Session,它们会一起获得或失去焦点。
- 空间音频:需要设备硬件支持,使用前检查
isSpatialAudioSupported()。 - 音频 Offload:将音频解码交给 DSP 处理,降低 CPU 功耗。适合长时间播放场景(如音乐、播客)。
六、总结
mindmap
root((声音设置))
音量控制
多流类型独立控制
最大/最小音量
音量变化监听
静音与勿扰
音频路由
扬声器/听筒
有线耳机
蓝牙A2DP/SCO
设备变更监听
静音模式
按流类型静音
勿扰模式
闹钟例外
音频焦点
焦点申请/释放
永久失去/暂时失去
Duck 降低音量
多应用协调
注意事项
setVolume 需系统权限
静音≠音量为0
焦点必须主动申请
蓝牙切换延迟
Duck 需自行降音量
HarmonyOS 6
Session 分组
空间音频
音频 Offload
多声道支持
音频特效管理
| 知识点 | 要点 |
|---|---|
| 音量控制 | 按流类型独立控制,媒体/铃声/通话各有各的音量 |
| 音频路由 | 声音从哪里出来由路由决定,监听设备变化做适配 |
| 静音模式 | 使用 mute() 而非设置音量为 0,勿扰模式保留闹钟 |
| 音频焦点 | 播放前申请、停止时释放,处理四种焦点变更类型 |
| Duck 模式 | 收到 CAN_DUCK 时自行降低音量,恢复时调回 |
| HarmonyOS 6 | Session 分组、空间音频、Offload 低功耗播放 |
声音管理就像指挥一个交响乐团——每种乐器(流类型)有自己的音量,每个声部(路由)有自己的位置,而音频焦点就是指挥棒,决定谁该演奏、谁该暂停。掌握了这些,你的应用就能奏出和谐的乐章。
- 点赞
- 收藏
- 关注作者
评论(0)