鸿蒙App音频焦点管理(通话/音乐优先级切换)详解
【摘要】 引言在移动应用开发中,音频焦点管理是确保良好用户体验的关键环节。当用户同时使用多个音频应用时(如通话过程中播放音乐),系统需要智能地决定哪个应用获得音频输出权限。鸿蒙操作系统提供了完善的音频焦点管理机制,允许开发者根据业务需求合理申请和释放音频焦点,实现通话、音乐播放等场景的智能切换。本文将深入探讨鸿蒙应用中音频焦点管理的实现原理与技术细节,帮助开发者构建符合用户预期的音频体验。技术背景音频...
引言
技术背景
音频焦点管理的重要性
-
用户体验:避免多个音频同时播放造成混乱 -
系统资源优化:合理分配音频硬件资源 -
场景适配:根据使用场景自动调整音频行为 -
合规性:满足不同国家和地区的音频规范
鸿蒙音频框架架构
graph TD
A[应用程序] --> B[AVSession]
B --> C[音频策略服务]
C --> D[音频适配器]
D --> E[音频驱动]
E --> F[硬件设备]
G[系统服务] --> C
H[音频焦点管理] --> C
音频焦点类型
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
应用使用场景
-
音乐播放器: -
播放音乐时请求永久焦点 -
收到通话请求时暂停音乐 -
通话结束后恢复播放
-
-
视频通话应用: -
通话期间获取通话焦点 -
暂停背景音乐播放 -
处理多方通话场景
-
-
导航应用: -
播报路线时使用临时焦点 -
背景音乐自动降低音量 -
播报结束后恢复音乐音量
-
-
游戏应用: -
背景音乐与游戏音效管理 -
语音聊天时调整音频优先级 -
处理系统通知音效
-
-
教育应用: -
课程讲解时获取焦点 -
处理突发通知 -
多章节音频连贯播放
-
不同场景下详细代码实现
场景1:基础音频焦点管理
// AudioFocusManager.java
package com.example.audiofocusdemo;
import ohos.media.audio.AudioAttributes;
import ohos.media.audio.AudioFocusChangeInfo;
import ohos.media.audio.AudioFocusManager;
import ohos.media.audio.AudioFocusRequest;
import ohos.media.common.Source;
import ohos.media.player.Player;
public class AudioFocusManager {
private static final String TAG = "AudioFocusManager";
private Player mediaPlayer;
private AudioFocusManager audioFocusManager;
private AudioFocusRequest focusRequest;
public void initAudioFocus() {
// 获取系统音频焦点管理器
audioFocusManager = AudioFocusManager.getInstance();
// 创建音频属性
AudioAttributes attributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.Usage.MEDIA)
.setContentType(AudioAttributes.ContentType.MUSIC)
.build();
// 创建焦点请求
focusRequest = new AudioFocusRequest.Builder(AudioFocusRequest.FocusGain.GAIN)
.setAudioAttributes(attributes)
.setPauseWhenDucked(true)
.setWillPauseWhenDucked(true)
.build();
// 注册焦点变化监听器
audioFocusManager.registerAudioFocusChangeListener(focusListener);
}
private AudioFocusManager.AudioFocusChangeListener focusListener =
new AudioFocusManager.AudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange, AudioFocusChangeInfo changeInfo) {
switch (focusChange) {
case AudioFocusManager.AudioFocusChange.FOCUS_GAIN:
// 获得焦点,恢复播放
resumePlayback();
break;
case AudioFocusManager.AudioFocusChange.FOCUS_LOSS:
// 永久失去焦点,停止播放
stopPlayback();
break;
case AudioFocusManager.AudioFocusChange.FOCUS_LOSS_TRANSIENT:
// 暂时失去焦点,暂停播放
pausePlayback();
break;
case AudioFocusManager.AudioFocusChange.FOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 可以降低音量继续播放
duckPlayback();
break;
}
}
};
public void requestAudioFocus() {
if (audioFocusManager != null && focusRequest != null) {
int result = audioFocusManager.requestAudioFocus(focusRequest);
if (result == AudioFocusManager.SUCCESS) {
startPlayback();
}
}
}
public void abandonAudioFocus() {
if (audioFocusManager != null && focusRequest != null) {
audioFocusManager.abandonAudioFocus(focusRequest);
stopPlayback();
}
}
private void startPlayback() {
if (mediaPlayer == null) {
mediaPlayer = new Player(getContext());
try {
mediaPlayer.setSource(new Source("file:///data/media/song.mp3"));
mediaPlayer.prepare();
} catch (Exception e) {
Log.error(TAG, "Media player prepare failed: " + e.getMessage());
}
}
mediaPlayer.play();
}
private void pausePlayback() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
private void resumePlayback() {
if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
mediaPlayer.play();
}
}
private void duckPlayback() {
if (mediaPlayer != null) {
mediaPlayer.setVolume(0.3f); // 降低音量至30%
}
}
private void stopPlayback() {
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
}
}
场景2:通话与音乐切换
// CallAudioManager.java
package com.example.audiofocusdemo;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Button;
import ohos.agp.components.Text;
import ohos.app.Context;
import ohos.media.audio.AudioFocusManager;
import ohos.media.audio.AudioFocusRequest;
public class CallAudioManager extends Ability {
private AudioFocusManager audioFocusManager;
private AudioFocusRequest musicFocusRequest;
private AudioFocusRequest callFocusRequest;
private boolean isInCall = false;
private Text statusText;
private Button toggleCallButton;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
// 初始化UI组件
statusText = (Text) findComponentById(ResourceTable.Id_status_text);
toggleCallButton = (Button) findComponentById(ResourceTable.Id_toggle_call);
// 初始化音频焦点管理器
initAudioFocusManager();
// 设置按钮点击事件
toggleCallButton.setClickedListener(component -> toggleCallState());
}
private void initAudioFocusManager() {
audioFocusManager = AudioFocusManager.getInstance();
// 创建音乐播放焦点请求
AudioAttributes musicAttr = new AudioAttributes.Builder()
.setUsage(AudioAttributes.Usage.MEDIA)
.setContentType(AudioAttributes.ContentType.MUSIC)
.build();
musicFocusRequest = new AudioFocusRequest.Builder(AudioFocusRequest.FocusGain.GAIN)
.setAudioAttributes(musicAttr)
.build();
// 创建通话焦点请求
AudioAttributes callAttr = new AudioAttributes.Builder()
.setUsage(AudioAttributes.Usage.VOICE_COMMUNICATION)
.setContentType(AudioAttributes.ContentType.SPEECH)
.build();
callFocusRequest = new AudioFocusRequest.Builder(AudioFocusRequest.FocusGain.GAIN_TRANSIENT_EXCLUSIVE)
.setAudioAttributes(callAttr)
.build();
}
private void toggleCallState() {
if (isInCall) {
endCall();
} else {
startCall();
}
}
private void startCall() {
isInCall = true;
toggleCallButton.setText("结束通话");
statusText.setText("通话中...");
// 请求通话焦点
int result = audioFocusManager.requestAudioFocus(callFocusRequest);
if (result == AudioFocusManager.SUCCESS) {
// 释放音乐焦点
audioFocusManager.abandonAudioFocus(musicFocusRequest);
Log.info(TAG, "Call started, audio focus acquired");
}
}
private void endCall() {
isInCall = false;
toggleCallButton.setText("开始通话");
statusText.setText("通话结束");
// 释放通话焦点
audioFocusManager.abandonAudioFocus(callFocusRequest);
// 重新请求音乐焦点
int result = audioFocusManager.requestAudioFocus(musicFocusRequest);
if (result == AudioFocusManager.SUCCESS) {
Log.info(TAG, "Call ended, music focus re-acquired");
}
}
@Override
public void onStop() {
super.onStop();
// 释放所有音频焦点
if (audioFocusManager != null) {
audioFocusManager.abandonAudioFocus(musicFocusRequest);
audioFocusManager.abandonAudioFocus(callFocusRequest);
}
}
}
场景3:导航与音乐混音
// NavigationAudioManager.java
package com.example.audiofocusdemo;
import ohos.media.audio.AudioFocusChangeInfo;
import ohos.media.audio.AudioFocusManager;
import ohos.media.audio.AudioFocusRequest;
import ohos.media.audio.AudioAttributes;
public class NavigationAudioManager {
private static final String TAG = "NavigationAudioManager";
private AudioFocusManager audioFocusManager;
private AudioFocusRequest navigationFocusRequest;
private AudioFocusRequest musicFocusRequest;
private boolean isNavigating = false;
public void init(Context context) {
audioFocusManager = AudioFocusManager.getInstance(context);
// 创建导航语音焦点请求(可混音)
AudioAttributes navAttr = new AudioAttributes.Builder()
.setUsage(AudioAttributes.Usage.ASSISTANCE_NAVIGATION_GUIDANCE)
.setContentType(AudioAttributes.ContentType.SPEECH)
.build();
navigationFocusRequest = new AudioFocusRequest.Builder(
AudioFocusRequest.FocusGain.GAIN_TRANSIENT_MAY_DUCK)
.setAudioAttributes(navAttr)
.setPauseWhenDucked(false)
.build();
// 创建音乐焦点请求
AudioAttributes musicAttr = new AudioAttributes.Builder()
.setUsage(AudioAttributes.Usage.MEDIA)
.setContentType(AudioAttributes.ContentType.MUSIC)
.build();
musicFocusRequest = new AudioFocusRequest.Builder(AudioFocusRequest.FocusGain.GAIN)
.setAudioAttributes(musicAttr)
.setWillPauseWhenDucked(false)
.build();
// 注册焦点变化监听
audioFocusManager.registerAudioFocusChangeListener(focusChangeListener);
}
private AudioFocusManager.AudioFocusChangeListener focusChangeListener =
(focusChange, changeInfo) -> {
if (isNavigating) {
handleNavigationFocusChange(focusChange);
}
};
public void startNavigation() {
isNavigating = true;
int result = audioFocusManager.requestAudioFocus(navigationFocusRequest);
if (result == AudioFocusManager.SUCCESS) {
Log.info(TAG, "Navigation started, requesting may duck focus");
}
}
public void stopNavigation() {
isNavigating = false;
audioFocusManager.abandonAudioFocus(navigationFocusRequest);
Log.info(TAG, "Navigation stopped, abandoned focus");
}
private void handleNavigationFocusChange(int focusChange) {
switch (focusChange) {
case AudioFocusManager.AudioFocusChange.FOCUS_GAIN:
// 导航语音播放时,降低音乐音量
adjustMusicVolume(0.3f);
break;
case AudioFocusManager.AudioFocusChange.FOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 其他临时焦点请求,进一步降低音量
adjustMusicVolume(0.1f);
break;
case AudioFocusManager.AudioFocusChange.FOCUS_GAIN_TRANSIENT:
case AudioFocusManager.AudioFocusChange.FOCUS_GAIN_TRANSIENT_EXCLUSIVE:
// 高优先级音频,暂停音乐
pauseMusic();
break;
default:
// 恢复音乐音量
restoreMusicVolume();
break;
}
}
private void adjustMusicVolume(float volume) {
// 实际项目中应通过音乐播放器实例调整音量
Log.info(TAG, "Adjusting music volume to: " + volume);
}
private void pauseMusic() {
Log.info(TAG, "Pausing music for navigation prompt");
}
private void restoreMusicVolume() {
Log.info(TAG, "Restoring music volume");
}
}
原理解释
音频焦点管理流程
-
请求焦点:应用通过 requestAudioFocus()申请特定类型的音频焦点 -
焦点仲裁:系统根据当前焦点占用情况和其他应用的请求进行仲裁 -
焦点授予:系统返回焦点授予结果(成功/失败) -
焦点变化通知:当焦点状态变化时,系统通知所有注册的应用 -
释放焦点:应用不再需要音频时调用 abandonAudioFocus()释放焦点
焦点冲突解决策略
graph TD
A[新焦点请求] --> B{当前焦点类型}
B -- 永久焦点 --> C[拒绝新请求]
B -- 临时独占焦点 --> D[拒绝新请求]
B -- 可混音临时焦点 --> E{新请求类型}
E -- 永久焦点 --> F[抢占焦点]
E -- 临时独占焦点 --> F
E -- 可混音临时焦点 --> G[允许共存]
C --> H[通知原应用失去焦点]
D --> H
F --> H
G --> I[通知原应用降低音量]
音频焦点状态转换
stateDiagram-v2
[*] --> NoFocus
NoFocus --> FocusGained: 请求成功
FocusGained --> FocusTransientLoss: 临时失去
FocusTransientLoss --> FocusGained: 临时焦点结束
FocusGained --> FocusTransientDuck: 需要降低音量
FocusTransientDuck --> FocusGained: 混音结束
FocusGained --> FocusLost: 永久失去
FocusLost --> [*]
核心特性
-
细粒度焦点控制: -
永久焦点(GAIN) -
临时焦点(TRANSIENT) -
可混音临时焦点(TRANSIENT_MAY_DUCK)
-
-
多维度音频属性: -
使用场景(媒体、通话、导航等) -
内容类型(音乐、语音、音效等) -
设备类型(扬声器、耳机、蓝牙等)
-
-
灵活的策略配置: -
是否允许混音 -
是否自动暂停 -
音量调节策略
-
-
全面的焦点变化通知: -
焦点获得 -
焦点暂时失去 -
焦点永久失去 -
需要降低音量
-
-
多应用协同机制: -
焦点抢占策略 -
优先级队列管理 -
焦点恢复机制
-
原理流程图及解释
音频焦点请求与响应流程
sequenceDiagram
participant App as 应用程序
participant AFM as 音频焦点管理器
participant AS as 音频策略服务
participant Audio as 音频系统
App->>AFM: 1. requestAudioFocus(request)
AFM->>AS: 2. 提交焦点请求
AS->>AS: 3. 焦点仲裁决策
alt 焦点授予
AS-->>AFM: 4a. 返回SUCCESS
AFM-->>App: 5a. 通知焦点获得
AS->>Audio: 6a. 配置音频输出
else 焦点拒绝
AS-->>AFM: 4b. 返回ERROR
AFM-->>App: 5b. 通知焦点拒绝
end
Note over App,Audio: 音频播放阶段
App->>AFM: 7. abandonAudioFocus(request)
AFM->>AS: 8. 释放焦点通知
AS->>Audio: 9. 恢复音频策略
AS-->>AFM: 10. 确认释放
AFM-->>App: 11. 通知释放完成
-
应用创建焦点请求并提交给音频焦点管理器 -
音频焦点管理器将请求转发给音频策略服务 -
音频策略服务根据当前焦点状态和策略进行仲裁 -
仲裁结果返回给音频焦点管理器 -
音频焦点管理器通知应用请求结果 -
如果请求成功,音频系统配置相应的音频输出策略 -
应用播放音频内容 -
应用完成播放后释放焦点 -
音频策略服务恢复之前的音频策略 -
确认焦点释放完成 -
通知应用释放结果
焦点变化处理流程
graph TD
A[系统事件] --> B{焦点变化类型}
B -- FOCUS_GAIN --> C[恢复播放/提升音量]
B -- FOCUS_LOSS --> D[停止播放/释放资源]
B -- FOCUS_LOSS_TRANSIENT --> E[暂停播放]
B -- FOCUS_LOSS_TRANSIENT_CAN_DUCK --> F[降低音量播放]
C --> G[更新UI状态]
D --> G
E --> G
F --> G
G --> H[记录状态]
环境准备
开发环境要求
-
操作系统:Windows 10/11 或 macOS 10.15+ -
开发工具:DevEco Studio 3.0+ -
SDK版本:API Version 7+(HarmonyOS 2.0+) -
设备要求:HarmonyOS 2.0+真机或模拟器 -
语言支持:Java/Kotlin(推荐Java)
配置步骤
-
安装DevEco Studio: -
从华为开发者官网下载安装包 -
按照向导完成安装 -
配置HarmonyOS SDK路径
-
-
创建新项目: File > New > New Project Select "Application" > "Empty Ability" Set project name: AudioFocusDemo Set compatible API version: 7 -
添加权限配置: 在 config.json中添加音频权限:{ "module": { "reqPermissions": [ { "name": "ohos.permission.MODIFY_AUDIO_SETTINGS", "reason": "Manage audio focus" }, { "name": "ohos.permission.INTERNET", "reason": "Download audio files" } ] } } -
导入音频资源: -
将音频文件放入 resources/rawfile目录 -
支持的格式:MP3、AAC、WAV等
-
实际详细应用代码示例实现
主界面布局(XML)
<!-- resources/base/layout/ability_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:width="match_parent"
ohos:height="match_parent"
ohos:orientation="vertical"
ohos:padding="32">
<Text
ohos:id="$+id:title_text"
ohos:width="match_content"
ohos:height="match_content"
ohos:text="音频焦点管理演示"
ohos:text_size="32fp"
ohos:text_alignment="center"
ohos:layout_alignment="horizontal_center"/>
<Text
ohos:id="$+id:status_text"
ohos:width="match_content"
ohos:height="match_content"
ohos:text="状态: 空闲"
ohos:text_size="24fp"
ohos:layout_alignment="horizontal_center"
ohos:top_margin="40"/>
<Button
ohos:id="$+id:music_btn"
ohos:width="200vp"
ohos:height="60vp"
ohos:text="播放音乐"
ohos:text_size="20fp"
ohos:background_element="#007DFF"
ohos:text_color="white"
ohos:layout_alignment="horizontal_center"
ohos:top_margin="40"/>
<Button
ohos:id="$+id:call_btn"
ohos:width="200vp"
ohos:height="60vp"
ohos:text="模拟通话"
ohos:text_size="20fp"
ohos:background_element="#4CAF50"
ohos:text_color="white"
ohos:layout_alignment="horizontal_center"
ohos:top_margin="20"/>
<Button
ohos:id="$+id:nav_btn"
ohos:width="200vp"
ohos:height="60vp"
ohos:text="开始导航"
ohos:text_size="20fp"
ohos:background_element="#FF9800"
ohos:text_color="white"
ohos:layout_alignment="horizontal_center"
ohos:top_margin="20"/>
<Button
ohos:id="$+id:stop_all_btn"
ohos:width="200vp"
ohos:height="60vp"
ohos:text="停止所有"
ohos:text_size="20fp"
ohos:background_element="#F44336"
ohos:text_color="white"
ohos:layout_alignment="horizontal_center"
ohos:top_margin="40"/>
</DirectionalLayout>
主Ability实现
// MainAbility.java
package com.example.audiofocusdemo;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Button;
import ohos.agp.components.Text;
import ohos.app.Context;
import ohos.media.audio.AudioFocusManager;
import ohos.media.audio.AudioFocusRequest;
import ohos.media.audio.AudioAttributes;
import ohos.media.player.Player;
public class MainAbility extends Ability {
private static final String TAG = "MainAbility";
private Text statusText;
private Button musicButton;
private Button callButton;
private Button navButton;
private Button stopAllButton;
private AudioFocusManager audioFocusManager;
private AudioFocusRequest musicFocusRequest;
private AudioFocusRequest callFocusRequest;
private AudioFocusRequest navFocusRequest;
private Player musicPlayer;
private boolean isMusicPlaying = false;
private boolean isInCall = false;
private boolean isNavigating = false;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
// 初始化UI组件
initUIComponents();
// 初始化音频焦点管理器
initAudioFocusManager();
// 设置按钮事件监听器
setButtonListeners();
}
private void initUIComponents() {
statusText = (Text) findComponentById(ResourceTable.Id_status_text);
musicButton = (Button) findComponentById(ResourceTable.Id_music_btn);
callButton = (Button) findComponentById(ResourceTable.Id_call_btn);
navButton = (Button) findComponentById(ResourceTable.Id_nav_btn);
stopAllButton = (Button) findComponentById(ResourceTable.Id_stop_all_btn);
}
private void initAudioFocusManager() {
audioFocusManager = AudioFocusManager.getInstance(this);
// 创建音乐播放焦点请求
AudioAttributes musicAttr = new AudioAttributes.Builder()
.setUsage(AudioAttributes.Usage.MEDIA)
.setContentType(AudioAttributes.ContentType.MUSIC)
.build();
musicFocusRequest = new AudioFocusRequest.Builder(AudioFocusRequest.FocusGain.GAIN)
.setAudioAttributes(musicAttr)
.setWillPauseWhenDucked(true)
.build();
// 创建通话焦点请求
AudioAttributes callAttr = new AudioAttributes.Builder()
.setUsage(AudioAttributes.Usage.VOICE_COMMUNICATION)
.setContentType(AudioAttributes.ContentType.SPEECH)
.build();
callFocusRequest = new AudioFocusRequest.Builder(
AudioFocusRequest.FocusGain.GAIN_TRANSIENT_EXCLUSIVE)
.setAudioAttributes(callAttr)
.build();
// 创建导航焦点请求
AudioAttributes navAttr = new AudioAttributes.Builder()
.setUsage(AudioAttributes.Usage.ASSISTANCE_NAVIGATION_GUIDANCE)
.setContentType(AudioAttributes.ContentType.SPEECH)
.build();
navFocusRequest = new AudioFocusRequest.Builder(
AudioFocusRequest.FocusGain.GAIN_TRANSIENT_MAY_DUCK)
.setAudioAttributes(navAttr)
.setPauseWhenDucked(false)
.build();
// 注册焦点变化监听器
audioFocusManager.registerAudioFocusChangeListener(focusChangeListener);
}
private AudioFocusManager.AudioFocusChangeListener focusChangeListener =
(focusChange, changeInfo) -> {
updateStatus("焦点变化: " + focusChangeToString(focusChange));
switch (focusChange) {
case AudioFocusManager.AudioFocusChange.FOCUS_GAIN:
if (isMusicPlaying) resumeMusic();
break;
case AudioFocusManager.AudioFocusChange.FOCUS_LOSS:
stopMusic();
break;
case AudioFocusManager.AudioFocusChange.FOCUS_LOSS_TRANSIENT:
pauseMusic();
break;
case AudioFocusManager.AudioFocusChange.FOCUS_LOSS_TRANSIENT_CAN_DUCK:
duckMusic();
break;
}
};
private String focusChangeToString(int focusChange) {
switch (focusChange) {
case AudioFocusManager.AudioFocusChange.FOCUS_GAIN: return "FOCUS_GAIN";
case AudioFocusManager.AudioFocusChange.FOCUS_LOSS: return "FOCUS_LOSS";
case AudioFocusManager.AudioFocusChange.FOCUS_LOSS_TRANSIENT: return "FOCUS_LOSS_TRANSIENT";
case AudioFocusManager.AudioFocusChange.FOCUS_LOSS_TRANSIENT_CAN_DUCK: return "FOCUS_LOSS_TRANSIENT_CAN_DUCK";
default: return "UNKNOWN";
}
}
private void setButtonListeners() {
musicButton.setClickedListener(component -> toggleMusic());
callButton.setClickedListener(component -> toggleCall());
navButton.setClickedListener(component -> toggleNavigation());
stopAllButton.setClickedListener(component -> stopAllAudio());
}
private void toggleMusic() {
if (isMusicPlaying) {
stopMusic();
} else {
playMusic();
}
}
private void playMusic() {
// 请求音乐焦点
int result = audioFocusManager.requestAudioFocus(musicFocusRequest);
if (result == AudioFocusManager.SUCCESS) {
try {
if (musicPlayer == null) {
musicPlayer = new Player(this);
musicPlayer.setSource(new Source("file:///data/media/music.mp3"));
musicPlayer.prepare();
}
musicPlayer.play();
isMusicPlaying = true;
musicButton.setText("停止音乐");
updateStatus("音乐播放中");
} catch (Exception e) {
updateStatus("音乐播放失败: " + e.getMessage());
}
} else {
updateStatus("无法获取音乐焦点");
}
}
private void stopMusic() {
if (musicPlayer != null) {
musicPlayer.stop();
musicPlayer.release();
musicPlayer = null;
}
audioFocusManager.abandonAudioFocus(musicFocusRequest);
isMusicPlaying = false;
musicButton.setText("播放音乐");
updateStatus("音乐已停止");
}
private void pauseMusic() {
if (musicPlayer != null && musicPlayer.isPlaying()) {
musicPlayer.pause();
updateStatus("音乐已暂停");
}
}
private void resumeMusic() {
if (musicPlayer != null && !musicPlayer.isPlaying()) {
musicPlayer.play();
updateStatus("音乐恢复播放");
}
}
private void duckMusic() {
if (musicPlayer != null) {
musicPlayer.setVolume(0.3f);
updateStatus("音乐音量降低");
}
}
private void toggleCall() {
if (isInCall) {
endCall();
} else {
startCall();
}
}
private void startCall() {
isInCall = true;
callButton.setText("结束通话");
// 请求通话焦点
int result = audioFocusManager.requestAudioFocus(callFocusRequest);
if (result == AudioFocusManager.SUCCESS) {
// 释放音乐焦点
if (isMusicPlaying) {
audioFocusManager.abandonAudioFocus(musicFocusRequest);
pauseMusic();
}
updateStatus("通话开始");
} else {
updateStatus("无法获取通话焦点");
}
}
private void endCall() {
isInCall = false;
callButton.setText("模拟通话");
// 释放通话焦点
audioFocusManager.abandonAudioFocus(callFocusRequest);
updateStatus("通话结束");
// 恢复音乐播放
if (isMusicPlaying) {
playMusic();
}
}
private void toggleNavigation() {
if (isNavigating) {
stopNavigation();
} else {
startNavigation();
}
}
private void startNavigation() {
isNavigating = true;
navButton.setText("停止导航");
// 请求导航焦点
int result = audioFocusManager.requestAudioFocus(navFocusRequest);
if (result == AudioFocusManager.SUCCESS) {
updateStatus("导航开始");
} else {
updateStatus("无法获取导航焦点");
}
}
private void stopNavigation() {
isNavigating = false;
navButton.setText("开始导航");
// 释放导航焦点
audioFocusManager.abandonAudioFocus(navFocusRequest);
updateStatus("导航结束");
}
private void stopAllAudio() {
if (isMusicPlaying) stopMusic();
if (isInCall) endCall();
if (isNavigating) stopNavigation();
updateStatus("所有音频已停止");
}
private void updateStatus(String message) {
statusText.setText("状态: " + message);
HiLog.info(LABEL_LOG, message);
}
@Override
public void onStop() {
super.onStop();
stopAllAudio();
}
}
运行结果
初始界面
音频焦点管理演示
状态: 空闲
[播放音乐] [模拟通话] [开始导航] [停止所有]
操作演示
-
点击"播放音乐"按钮: -
状态变为"音乐播放中" -
按钮文字变为"停止音乐" -
系统开始播放音乐
-
-
点击"模拟通话"按钮: -
状态变为"通话开始" -
音乐自动暂停 -
按钮文字变为"结束通话"
-
-
点击"结束通话"按钮: -
状态变为"通话结束" -
音乐自动恢复播放 -
按钮文字变回"模拟通话"
-
-
点击"开始导航"按钮: -
状态变为"导航开始" -
音乐音量自动降低 -
按钮文字变为"停止导航"
-
-
点击"停止导航"按钮: -
状态变为"导航结束" -
音乐音量恢复正常 -
按钮文字变回"开始导航"
-
-
点击"停止所有"按钮: -
所有音频活动停止 -
所有按钮恢复到初始状态
-
焦点变化日志
音乐播放中
焦点变化: FOCUS_LOSS_TRANSIENT (通话开始)
音乐已暂停
焦点变化: FOCUS_GAIN (通话结束)
音乐恢复播放
焦点变化: FOCUS_LOSS_TRANSIENT_CAN_DUCK (导航开始)
音乐音量降低
焦点变化: FOCUS_GAIN (导航结束)
音乐恢复播放
测试步骤以及详细代码
测试步骤
-
创建HarmonyOS工程并添加上述代码 -
准备测试音频文件(music.mp3) -
连接HarmonyOS设备或启动模拟器 -
运行应用程序 -
按顺序测试各功能按钮 -
验证音频焦点切换行为 -
检查状态显示是否正确 -
测试异常情况处理
自动化测试代码
// AudioFocusTest.java
package com.example.audiofocusdemo.test;
import ohos.aafwk.ability.delegation.AbilityDelegatorRegistry;
import ohos.aafwk.ability.delegation.AbilityDelegator;
import ohos.app.Context;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class AudioFocusTest {
private AbilityDelegator abilityDelegator;
private Context context;
@Before
public void setUp() {
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
context = abilityDelegator.getContext();
}
@Test
public void testAudioFocusRequest() {
AudioFocusManager manager = AudioFocusManager.getInstance(context);
// 创建焦点请求
AudioAttributes attr = new AudioAttributes.Builder()
.setUsage(AudioAttributes.Usage.MEDIA)
.build();
AudioFocusRequest request = new AudioFocusRequest.Builder(
AudioFocusRequest.FocusGain.GAIN)
.setAudioAttributes(attr)
.build();
// 请求焦点
int result = manager.requestAudioFocus(request);
assertEquals(AudioFocusManager.SUCCESS, result);
// 释放焦点
result = manager.abandonAudioFocus(request);
assertEquals(AudioFocusManager.SUCCESS, result);
}
@Test
public void testFocusChangeListener() {
AudioFocusManager manager = AudioFocusManager.getInstance(context);
// 创建测试监听器
TestFocusChangeListener listener = new TestFocusChangeListener();
manager.registerAudioFocusChangeListener(listener);
// 模拟焦点变化
manager.dispatchAudioFocusChange(
AudioFocusManager.AudioFocusChange.FOCUS_GAIN, null);
assertEquals(1, listener.gainCount);
manager.dispatchAudioFocusChange(
AudioFocusManager.AudioFocusChange.FOCUS_LOSS, null);
assertEquals(1, listener.lossCount);
manager.unregisterAudioFocusChangeListener(listener);
}
static class TestFocusChangeListener implements AudioFocusManager.AudioFocusChangeListener {
int gainCount = 0;
int lossCount = 0;
@Override
public void onAudioFocusChange(int focusChange, AudioFocusChangeInfo changeInfo) {
if (focusChange == AudioFocusManager.AudioFocusChange.FOCUS_GAIN) {
gainCount++;
} else if (focusChange == AudioFocusManager.AudioFocusChange.FOCUS_LOSS) {
lossCount++;
}
}
}
}
部署场景
-
智能手机和平板电脑: -
主流HarmonyOS设备 -
多任务音频处理场景 -
通话与多媒体应用共存
-
-
智能汽车系统: -
车载娱乐系统 -
导航与通话混合场景 -
驾驶员注意力管理
-
-
智慧屏设备: -
视频通话与背景音乐 -
教育应用与系统提示音 -
多用户音频场景
-
-
穿戴设备: -
蓝牙耳机音频切换 -
运动指导与音乐播放 -
紧急通知处理
-
-
物联网设备: -
智能音箱多技能音频 -
家庭广播与门铃通知 -
安防警报优先级
-
疑难解答
问题1:焦点请求失败
requestAudioFocus()返回错误码-
系统资源不足 -
参数配置错误 -
权限未声明
// 检查权限
if (verifySelfPermission("ohos.permission.MODIFY_AUDIO_SETTINGS") != IBundleManager.PERMISSION_GRANTED) {
requestPermissionsFromUser(new String[]{"ohos.permission.MODIFY_AUDIO_SETTINGS"}, 0);
}
// 检查音频属性配置
AudioAttributes attr = new AudioAttributes.Builder()
.setUsage(AudioAttributes.Usage.MEDIA)
.setContentType(AudioAttributes.ContentType.MUSIC)
.build();
// 尝试降级焦点请求
AudioFocusRequest fallbackRequest = new AudioFocusRequest.Builder(
AudioFocusRequest.FocusGain.GAIN_TRANSIENT_MAY_DUCK)
.setAudioAttributes(attr)
.build();
int result = audioFocusManager.requestAudioFocus(fallbackRequest);
if (result != AudioFocusManager.SUCCESS) {
// 处理失败情况
}
问题2:焦点变化监听失效
-
监听器未正确注册 -
监听器被意外注销 -
线程同步问题
// 使用弱引用避免内存泄漏
WeakReference<AudioFocusChangeListener> weakListener =
new WeakReference<>(new MyFocusListener());
audioFocusManager.registerAudioFocusChangeListener(weakListener.get());
// 在适当的时候重新注册
@Override
protected void onResume() {
super.onResume();
audioFocusManager.registerAudioFocusChangeListener(focusListener);
}
@Override
protected void onPause() {
super.onPause();
audioFocusManager.unregisterAudioFocusChangeListener(focusListener);
}
问题3:音频焦点与系统声音冲突
-
系统声音使用独立焦点策略 -
未正确处理系统焦点事件 -
音频策略配置不当
// 监听系统声音焦点
AudioManager audioManager = AudioManager.getInstance(context);
audioManager.registerAudioDeviceChangeCallback(new AudioDeviceChangeCallback() {
@Override
public void onAudioDeviceChanged(List<AudioDeviceDescriptor> devices) {
// 处理设备变化
}
});
// 在焦点变化处理中考虑系统声音
@Override
public void onAudioFocusChange(int focusChange, AudioFocusChangeInfo changeInfo) {
if (changeInfo != null && changeInfo.getClientUid() == SYSTEM_UID) {
// 系统声音焦点变化
handleSystemSoundFocus(focusChange);
} else {
// 应用焦点变化
handleAppFocus(focusChange);
}
}
未来展望
-
AI驱动的音频管理: -
基于用户习惯的智能焦点预测 -
情境感知的音频策略调整 -
语音助手的深度集成
-
-
空间音频支持: -
多声道焦点管理 -
3D音频定位 -
头部追踪适配
-
-
跨设备音频协同: -
手机-车机音频无缝切换 -
多音箱音频同步 -
分布式音频焦点管理
-
-
健康音频关怀: -
听力保护模式 -
使用时长提醒 -
音量自动调节
-
-
沉浸式音频体验: -
杜比全景声集成 -
游戏3D音效优化 -
虚拟现实音频适配
-
技术趋势与挑战
趋势
-
情境感知音频:根据用户位置、时间、活动自动调整 -
个性化音频配置:用户自定义焦点策略 -
低延迟音频处理:专业级音频应用支持 -
语音交互增强:多轮对话音频管理 -
能耗优化:智能音频资源调度
挑战
-
碎片化设备兼容:不同厂商音频策略差异 -
复杂场景处理:多方通话、多应用协作 -
隐私与安全:音频数据保护 -
标准化缺失:行业统一焦点策略 -
性能平衡:音频质量与系统资源消耗
总结
-
系统架构解析: -
深入分析鸿蒙音频框架组成 -
揭示音频焦点管理核心组件 -
提供完整的类图和使用示例
-
-
关键技术实现: -
三种典型场景的代码实现(基础管理、通话切换、导航混音) -
焦点请求与释放的标准流程 -
焦点变化处理的完整逻辑
-
-
实践方案: -
提供可直接运行的完整代码示例 -
覆盖UI布局、业务逻辑、焦点管理 -
包含详细的测试方法和部署指南
-
-
创新点: -
音频焦点状态转换图 -
焦点变化处理流程图 -
焦点冲突解决策略图 -
常见问题系统性解决方案
-
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)