HarmonyOS多音频播放并发政策及音频管理解析
音频打断策略
多音频并发,即多个音频流同时播放。此场景下,如果系统不加管控,会造成多个音频流混音播放,容易让用户感到嘈杂,造成不好的用户体验。为了解决这个问题,系统预设了音频打断策略,对多音频播放的并发进行管控,只有持有音频焦点的音频流才可以正常播放,避免多个音频流无序并发播放的现象出现。
当应用开始播放音频时,系统首先为相应的音频流申请音频焦点,获得焦点的音频流可以播放;若焦点申请被拒绝,则不能播放。在音频流播放的过程中,若被其他音频流打断,则会失去音频焦点。当音频流失去音频焦点时,只能暂停播放。在应用播放音频的过程中,这些动作均由系统自行完成,无需应用主动触发。但为了维持应用和系统的状态一致性,保证良好的用户体验,推荐应用监听音频打断事件,并在收到音频打断事件(InterruptEvent)时做出相应处理。
为满足应用对多音频并发策略的不同需求,音频打断策略预设了两种焦点模式,针对同一应用创建的多个音频流,应用可通过设置焦点模式,选择由应用自主管控或由系统统一管控。
音频打断策略决定了应该对音频流采取何种操作,如暂停播放、继续播放、降低音量播放、恢复音量播放等,这些操作可能由系统或应用来执行。音频打断策略预置了两种打断类型,用于区分音频打断事件(InterruptEvent)的执行者。
焦点模式
音频打断策略预设了两种焦点模式(InterruptMode):
● 共享焦点模式(SHARE_MODE):由同一应用创建的多个音频流,共享一个音频焦点。这些音频流之间的并发规则由应用自主决定,音频打断策略不会介入。当其他应用创建的音频流与该应用的音频流并发播放时,才会触发音频打断策略的管控。
● 独立焦点模式(INDEPENDENT_MODE):应用创建的每一个音频流均会独立拥有一个音频焦点,当多个音频流并发播放时,会触发音频打断策略的管控。
应用可以按需选择合适的焦点模式,在创建音频流时,系统默认采用共享焦点模式,应用可主动设置所需的模式。
设置焦点模式的方法:
● 若使用AVPlayer开发音频播放功能,则可以通过修改AVPlayer的audioInterruptMode属性进行设置。
● 若使用AudioRenderer开发音频播放功能,则可以调用AudioRenderer的setInterruptMode函数进行设置。
打断类型
音频打断策略(包括两种焦点模式)决定了应该对各个音频流采取何种操作,如暂停播放、继续播放、降低音量播放、恢复音量播放等。而针对这些操作的执行过程,根据执行者的不同,可以分为两种打断类型(InterruptForceType):
● 强制打断类型(INTERRUPT_FORCE):由系统进行操作,强制打断音频播放。
● 共享打断类型(INTERRUPT_SHARE):由应用进行操作,可以选择打断或忽略。
对于音频打断策略的执行,系统默认采用强制打断类型(INTERRUPT_FORCE),应用无法更改。但对于一些策略(如继续播放等),系统无法强制执行,所以这两种打断类型均可能出现。应用可根据音频打断事件(InterruptEvent)的成员变量forceType的值,获取该事件采用的打断类型。
在应用播放音频的过程中,系统自动为音频流执行申请焦点、持有焦点、释放焦点等动作,当发生音频打断事件时,系统强制对音频流执行暂停、停止、降低音量、恢复音量等操作,并向应用发送音频打断事件(InterruptEvent)回调。由于系统会强制改变音频流状态,为了维持应用和系统的状态一致性,保证良好的用户体验,推荐应用监听音频打断事件,并在收到音频打断事件(InterruptEvent)时做出相应处理。
对于一些系统无法强制执行的操作(例如音频流继续播放的场景),会向应用发送包含了共享打断类型的音频打断事件,由应用自行执行相应操作,此时应用可以选择执行或忽略,系统不会干涉。
监听音频打断事件
在应用播放音频时,推荐应用监听音频打断事件,当音频打断事件发生时,系统会根据预设策略,对音频流做出相应的操作,并针对状态发生改变的音频流,向所属的应用发送音频打断事件。
应用收到音频打断事件后,需根据其内容提示,做出相应的处理,避免出现应用状态与预期效果不一致的问题。
监听音频打断事件的方法:
● 若使用AVPlayer开发音频播放功能,则可以调用AVPlayer的on('audioInterrupt')函数进行监听,当收到音频打断事件(InterruptEvent)时,应用需根据其内容,做出相应的调整。
● 若使用AudioRenderer开发音频播放功能,则可以调用AudioRenderer的on('audioInterrupt')函数进行监听,当收到音频打断事件(InterruptEvent)时,应用需根据其内容,做出相应的调整。
为了带给用户更好的体验,针对不同的音频打断事件内容,应用需要做出相应的处理操作。此处以使用AudioRenderer开发音频播放功能为例,展示推荐应用采取的处理方法,提供伪代码供开发者参考(若使用AVPlayer开发音频播放功能,处理方法类似),具体的代码实现,开发者可结合实际情况编写,处理方法也可自行调整。
let isPlay; // 是否正在播放,实际开发中,对应与音频播放状态相关的模块
let isDucked; //是否降低音量,实际开发中,对应与音频音量相关的模块
let started; // 标识符,记录“开始播放(start)”操作是否成功 async function onAudioInterrupt(){
// 此处以使用AudioRenderer开发音频播放功能举例,变量audioRenderer即为播放时创建的AudioRenderer实例。
audioRenderer.on('audioInterrupt', async(interruptEvent) => {
// 在发生音频打断事件时,audioRenderer收到interruptEvent回调,此处根据其内容做相应处理
// 先读取interruptEvent.forceType的类型,判断系统是否已强制执行相应操作
// 再读取interruptEvent.hintType的类型,做出相应的处理
if (interruptEvent.forceType === audio.InterruptForceType.INTERRUPT_FORCE) {
// 强制打断类型(INTERRUPT_FORCE):音频相关处理已由系统执行,应用需更新自身状态,做相应调整
switch (interruptEvent.hintType) {
case audio.InterruptHint.INTERRUPT_HINT_PAUSE:
// 此分支表示系统已将音频流暂停(临时失去焦点),为保持状态一致,应用需切换至音频暂停状态
// 临时失去焦点:待其他音频流释放音频焦点后,本音频流会收到resume对应的音频打断事件,到时可自行继续播放
isPlay = false; // 此句为简化处理,代表应用切换至音频暂停状态的若干操作
break;
case audio.InterruptHint.INTERRUPT_HINT_STOP:
// 此分支表示系统已将音频流停止(永久失去焦点),为保持状态一致,应用需切换至音频暂停状态
// 永久失去焦点:后续不会再收到任何音频打断事件,若想恢复播放,需要用户主动触发。
isPlay = false; // 此句为简化处理,代表应用切换至音频暂停状态的若干操作
break;
case audio.InterruptHint.INTERRUPT_HINT_DUCK:
// 此分支表示系统已将音频音量降低(默认降到正常音量的20%),为保持状态一致,应用需切换至降低音量播放状态
// 若应用不接受降低音量播放,可在此处选择其他处理方式,如主动暂停等
isDucked = true; // 此句为简化处理,代表应用切换至降低音量播放状态的若干操作
break;
case audio.InterruptHint.INTERRUPT_HINT_UNDUCK:
// 此分支表示系统已将音频音量恢复正常,为保持状态一致,应用需切换至正常音量播放状态
isDucked = false; // 此句为简化处理,代表应用切换至正常音量播放状态的若干操作
break;
default:
break;
}
} else if (interruptEvent.forceType === audio.InterruptForceType.INTERRUPT_SHARE) {
// 共享打断类型(INTERRUPT_SHARE):应用可自主选择执行相关操作或忽略音频打断事件
switch (interruptEvent.hintType) {
case audio.InterruptHint.INTERRUPT_HINT_RESUME:
// 此分支表示临时失去焦点后被暂停的音频流此时可以继续播放,建议应用继续播放,切换至音频播放状态
// 若应用此时不想继续播放,可以忽略此音频打断事件,不进行处理即可
// 继续播放,此处主动执行start(),以标识符变量started记录start()的执行结果
await audioRenderer.start().then(async function () {
started = true; // start()执行成功
}).catch((err) => {
started = false; // start()执行失败
});
// 若start()执行成功,则切换至音频播放状态
if (started) {
isPlay = true; // 此句为简化处理,代表应用切换至音频播放状态的若干操作
} else {
// 音频继续播放执行失败
}
break;
default:
break;
}
}
});
}
播放音量管理
播放音量的管理主要包括对系统音量的管理和对音频流音量的管理。系统音量与音频流音量分别是指HarmonyOS系统的总音量和指定音频流的音量,其中音频流音量的大小受制于系统音量,管理两者的接口不同。
详细的API说明请参考audio API参考。
系统音量
管理系统音量的接口是AudioVolumeManager,在使用之前,需要使用getVolumeManager()获取AudioVolumeManager实例。目前该接口只能获取音量信息及监听音量变化,不能主动调节系统音量。
import audio from '@ohos.multimedia.audio';
let audioManager = audio.getAudioManager();
let audioVolumeManager = audioManager.getVolumeManager();
监听系统音量变化
通过设置监听事件,可以监听系统音量的变化:
audioVolumeManager.on('volumeChange', (volumeEvent) => {
console.info(`VolumeType of stream: ${volumeEvent.volumeType} `);
console.info(`Volume level: ${volumeEvent.volume} `);
console.info(`Whether to updateUI: ${volumeEvent.updateUi} `);
});
音频流音量
管理音频流音量的接口是AVPlayer或AudioRenderer的setVolume()方法,使用AVPlayer设置音频流音量的示例代码如下:
let volume = 1.0 // 指定的音量大小,取值范围为[0.00-1.00],1表示最大音量
avPlayer.setVolume(volume)
使用AudioRenderer设置音频流音量的示例代码如下:
audioRenderer.setVolume(0.5).then(data=>{ // 音量范围为[0.0-1.0]
console.info('Invoke setVolume succeeded.');
}).catch((err) => {
console.error(`Invoke setVolume failed, code is ${err.code}, message is ${err.message}`);
});
音频播放流管理
对于播放音频类的应用,开发者需要关注该应用的音频流的状态以做出相应的操作,比如监听到状态为播放中/暂停时,及时改变播放按钮的UI显示。
读取或监听应用内音频流状态变化
参考使用AudioRenderer开发音频播放功能或audio.createAudioRenderer,完成AudioRenderer的创建,然后可以通过以下两种方式查看音频流状态的变化:
● 方法1:直接查看AudioRenderer的state:
let audioRendererState = audioRenderer.state;
console.info(`Current state is: ${audioRendererState }`)
● 方法2:注册stateChange监听AudioRenderer的状态变化:
audioRenderer.on('stateChange', (rendererState) => {
console.info(`State change to: ${rendererState}`)
});
获取state后可对照AudioState来进行相应的操作,比如更改暂停播放按钮的显示等。
读取或监听所有音频流的变化
如果部分应用需要查询获取所有音频流的变化信息,可以通过AudioStreamManager读取或监听所有音频流的变化。
如下为音频流管理调用关系图:
在进行应用开发的过程中,开发者需要使用getStreamManager()创建一个AudioStreamManager实例,进而通过该实例管理音频流。开发者可通过调用on('audioRendererChange')监听音频流的变化,在音频流状态变化、设备变化时获得通知。同时可通过off('audioRendererChange')取消相关事件的监听。另外,开发者可以主动调用getCurrentAudioRendererInfoArray()来查询播放流的唯一ID、播放流客户端的UID、音频流状态等信息。
详细API含义可参考音频管理API文档AudioStreamManager。
开发步骤及注意事项
1. 创建AudioStreamManager实例。在使用AudioStreamManager的API前,需要使用getStreamManager()创建一个AudioStreamManager实例。
import audio from '@ohos.multimedia.audio';
let audioManager = audio.getAudioManager();
let audioStreamManager = audioManager.getStreamManager();
2. 使用on('audioRendererChange')监听音频播放流的变化。 如果音频流监听应用需要在音频播放流状态变化、设备变化时获取通知,可以订阅该事件。
audioStreamManager.on('audioRendererChange', (AudioRendererChangeInfoArray) => {
for (let i = 0; i < AudioRendererChangeInfoArray.length; i++) {
let AudioRendererChangeInfo = AudioRendererChangeInfoArray[i];
console.info(`## RendererChange on is called for ${i} ##`);
console.info(`StreamId for ${i} is: ${AudioRendererChangeInfo.streamId}`);
console.info(`Content ${i} is: ${AudioRendererChangeInfo.rendererInfo.content}`);
console.info(`Stream ${i} is: ${AudioRendererChangeInfo.rendererInfo.usage}`);
console.info(`Flag ${i} is: ${AudioRendererChangeInfo.rendererInfo.rendererFlags}`);
for (let j = 0;j < AudioRendererChangeInfo.deviceDescriptors.length; j++) {
console.info(`Id: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].id}`);
console.info(`Type: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceType}`);
console.info(`Role: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceRole}`);
console.info(`Name: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].name}`);
console.info(`Address: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].address}`);
console.info(`SampleRates: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].sampleRates[0]}`);
console.info(`ChannelCount ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelCounts[0]}`);
console.info(`ChannelMask: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelMasks}`);
}
}
});
3. (可选)使用off('audioRendererChange')取消监听音频播放流变化。
audioStreamManager.off('audioRendererChange');
console.info('RendererChange Off is called ');
4. (可选)使用getCurrentAudioRendererInfoArray()获取所有音频播放流的信息。该接口可获取音频播放流唯一ID,音频播放客户端的UID,音频状态以及音频播放器的其他信息。
说明
对所有音频流状态进行监听的应用需要申请权限ohos.permission.USE_BLUETOOTH,否则无法获得实际的设备名称和设备地址信息,查询到的设备名称和设备地址(蓝牙设备的相关属性)将为空字符串。
async function getCurrentAudioRendererInfoArray(){
await audioStreamManager.getCurrentAudioRendererInfoArray().then( function (AudioRendererChangeInfoArray) {
console.info(`getCurrentAudioRendererInfoArray Get Promise is called `);
if (AudioRendererChangeInfoArray != null) {s
for (let i = 0; i < AudioRendererChangeInfoArray.length; i++) {
let AudioRendererChangeInfo = AudioRendererChangeInfoArray[i];
console.info(`StreamId for ${i} is: ${AudioRendererChangeInfo.streamId}`);
console.info(`Content ${i} is: ${AudioRendererChangeInfo.rendererInfo.content}`);
console.info(`Stream ${i} is: ${AudioRendererChangeInfo.rendererInfo.usage}`);
console.info(`Flag ${i} is: ${AudioRendererChangeInfo.rendererInfo.rendererFlags}`);
for (let j = 0;j < AudioRendererChangeInfo.deviceDescriptors.length; j++) {
console.info(`Id: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].id}`);
console.info(`Type: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceType}`);
console.info(`Role: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceRole}`);
console.info(`Name: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].name}`);
console.info(`Address: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].address}`);
console.info(`SampleRates: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].sampleRates[0]}`);
console.info(`ChannelCount ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelCounts[0]}`);
console.info(`ChannelMask: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelMasks}`);
}
}
}
}).catch((err) => {
console.error(`Invoke getCurrentAudioRendererInfoArray failed, code is ${err.code}, message is ${err.message}`);
});
}
音频输出设备管理
有时设备同时连接多个音频输出设备,需要指定音频输出设备进行音频播放,此时需要使用AudioRoutingManager接口进行输出设备的管理,API说明可以参考AudioRoutingManager API文档。
创建AudioRoutingManager实例
在使用AudioRoutingManager管理音频设备前,需要先导入模块并创建实例。
import audio from '@ohos.multimedia.audio'; // 导入audio模块 let audioManager = audio.getAudioManager(); // 需要先创建AudioManager实例 let audioRoutingManager = audioManager.getRoutingManager(); // 再调用AudioManager的方法创建AudioRoutingManager实例
支持的音频输出设备类型
目前支持的音频输出设备见下表:
名称 |
值 |
说明 |
EARPIECE |
1 |
听筒。 |
SPEAKER |
2 |
扬声器。 |
WIRED_HEADSET |
3 |
有线耳机,带麦克风。 |
WIRED_HEADPHONES |
4 |
有线耳机,无麦克风。 |
BLUETOOTH_SCO |
7 |
蓝牙设备SCO(Synchronous Connection Oriented)连接。 |
BLUETOOTH_A2DP |
8 |
蓝牙设备A2DP(Advanced Audio Distribution Profile)连接。 |
USB_HEADSET |
22 |
USB耳机,带麦克风。 |
获取输出设备信息
使用getDevices()方法可以获取当前所有输出设备的信息。
audioRoutingManager.getDevices(audio.DeviceFlag.OUTPUT_DEVICES_FLAG).then((data) => {
console.info('Promise returned to indicate that the device list is obtained.');
});
监听设备连接状态变化
可以设置监听事件来监听设备连接状态的变化,当有设备连接或断开时触发回调:
// 监听音频设备状态变化
audioRoutingManager.on('deviceChange', audio.DeviceFlag.OUTPUT_DEVICES_FLAG, (deviceChanged) => {
console.info('device change type : ' + deviceChanged.type); // 设备连接状态变化,0为连接,1为断开连接
console.info('device descriptor size : ' + deviceChanged.deviceDescriptors.length);
console.info('device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceRole); // 设备角色
console.info('device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceType); // 设备类型
}); // 取消监听音频设备状态变化
audioRoutingManager.off('deviceChange', (deviceChanged) => {
console.info('Should be no callback.');
});
HarmonyOS多音频播放并发政策及音频管理解析的更多相关文章
- Wavesurfer.js音频播放器插件的使用教程
Wavesurfer.js是一款基于HTML5 canvas和Web Audio的音频播放器插件,本文主要记录它及其视觉效果插件Regions插件的使用方法. 1.创建实例 引入插件 import W ...
- iOS 9音频应用播放音频之iOS 9音频播放进度
iOS 9音频应用播放音频之iOS 9音频播放进度 iOS 9音频应用开发播放进度 音频文件在播放后经过了多久以及还有多久才可以播放完毕,想必是用户所关注的问题.为了解决这一问题,在很多的音乐播放器中 ...
- 【iOS录音与播放】实现利用音频队列,通过缓存进行对声音的采集与播放
都说iOS最恶心的部分是流媒体,其中恶心的恶心之处更在即时语音. 所以我们先不谈即时语音,研究一下,iOS中声音采集与播放的实现. 要在iOS设备上实现录音和播放功能,苹果提供了简单的做法,那就是利用 ...
- HMS Core音频编辑服务支持7种音频特效,助力一站式音频处理
多媒体时代,音频作为内容传播中的重要形式,因其不受空间限制.认知负担小.声音元素多样化等特点,广泛应用于短视频制作.儿童在线教育.有声阅读.游戏等领域产品,在各种形式的音频呈现过程中,合理添加音效能够 ...
- IOS 音频播放
iOS音频播放 (一):概述 前言 从事音乐相关的app开发也已经有一段时日了,在这过程中app的播放器几经修改我也因此对于iOS下的音频播放实现有了一定的研究.写这个系列的博客目的一方面希望能够抛砖 ...
- iOS开发系列--音频播放、录音、视频播放、拍照、视频录制
--iOS多媒体 概览 随着移动互联网的发展,如今的手机早已不是打电话.发短信那么简单了,播放音乐.视频.录音.拍照等都是很常用的功能.在iOS中对于多媒体的支持是非常强大的,无论是音视频播放.录制, ...
- HTML5的Audio标签打造WEB音频播放器
目前,WEB页面上没有标准的方式来播放音频文件,大多数的音频文件是使用插件来播放,而众多浏览器都使用了不同的插件.而HTML5的到来,给我们提供了一个标准的方式来播放WEB中的音频文件,用户不再为浏览 ...
- HTML5 音频播放器-Javascript代码(短小精悍)
直接上干货咯! //HTML5 音频播放器 lzpong 2015/01/19 var wavPlayer = function () { if(window.parent.wavPlayer) re ...
- IOS开发之简单音频播放器
今天第一次接触IOS开发的UI部分,之前学OC的时候一直在模拟的使用Target-Action回调模式,今天算是真正的用了一次.为了熟悉一下基本控件的使用方法,和UI部分的回调,下面开发了一个特别简易 ...
- WIN32下使用DirectSound接口的简单音频播放器(支持wav和mp3)
刚好最近接触了一些DirectSound,就写了一个小程序练练手,可以用来添加播放基本的wav和mp3音频文件的播放器.界面只是简单的GDI,dxsdk只使用了DirectSound8相关的接口. D ...
随机推荐
- 高性能图计算系统 Plato 在 Nebula Graph 中的实践
本文首发于 Nebula Graph Community 公众号 1.图计算介绍 1.1 图数据库 vs 图计算 图数据库是面向 OLTP 场景,强调增删改查,并且一个查询往往只涉及到全图中的少量数据 ...
- 【Filament】基于物理的光照(PBR)
1 前言 自定义Blinn Phong光照模型中实现了基础的自定义光照,与现实的光照还是有些差别,本文将实现更逼真的光照效果,即基于物理的光照(PBR). 读者如果对 Filament 不太熟 ...
- [Linux] 快速修改hosts访问github
sudo sed -i '/github/d' /etc/hosts sudo bash -c "curl https://gitlab.com/ineo6/hosts/-/raw/mast ...
- Android Handler实现子线程与子线程、主线程之间通信
一.子线程向主线程传值: 首选在主线程里创建一个Handler 1 Handler mHandler = new Handler(){ 2 @Override 3 public void handle ...
- 磁盘 U盘找不到
及磁盘找不到了, 第一步: 看排线是否插好,稳当 第二步:在磁盘管理下面,磁盘是否还在,如果磁盘显示在,只是没有磁盘符 第三步:diskpart ,进入磁盘 其他的命令自己 查询 第四步:进入 CM ...
- ASP.NET Core 移除已注册的过滤器
背景 ABP vNext 默认对异常响应进行了处理,现在某个项目需要自定义异常响应结果. 问题 在 ABP vNext 的 MVC 模块当中,可以看到是通过 AddService(typeof(Abp ...
- 一些Clion使用记录
一些Clion使用记录 编译链设置 在设置"工具链"中可以按需增加不同的编译链 访问越界溢出debug AddressSanitizer介绍:AddressSanitizer 交的 ...
- 关于黑客网络 for linux,这个游戏的启动解决方法
原帖位置https://tieba.baidu.com/p/6200215090
- 什么叫运行时的Java程序?
Java程序的运行包含编写.编译和运行三个主要步骤. 1.在编写阶段: 开发人员在Java开发环境中输入程序代码,形成后缀名为.java的Java源文件. 2.在编译阶段: 使用Java编译器对源文件 ...
- Ubuntu下安装Android Studio
一.系统环境 二.安装源文件 Android Studio 4.2.2:android-studio-ide-202.7486908-linux.tar.gz Java SE Development ...