🔊 前言:为什么不直接用蓝牙?
| 维度 | 蓝牙 (Bluetooth A2DP) | 鸿蒙流转 (Distributed Audio) |
|---|---|---|
| 传输介质 | 蓝牙 (带宽低,易受干扰) | Wi-Fi / 软总线 (高带宽,无损音质) |
| 手机状态 | 必须做解码和传输,耗电 | 仅做控制,省电 |
| 多设备控制 | 一对一连接 | 全屋设备组网,随意切换 |
| 延迟 | 100ms - 300ms | < 40ms (唇音同步) |
🏗️ 一、 架构原理:AVSession 是指挥官
要实现流转,你的 App 必须接入AVSession (音视频会话)。
系统通过 AVSession 知道你的 App 正在放什么歌、进度是多少。当用户点击“流转”或手机靠近音箱时,系统接管 AVSession,将其“投影”到远端设备。
流转原理图 (Mermaid):
🛠️ 二、 实战步骤:从发现到连接
在 HarmonyOS Next 中,流转主要依赖AVCastController或AudioRoutingManager。这里我们演示最通用的AVSession 投射模式。
1. 基础准备:创建 AVSession
这是所有播放类 App 的起手式。不写这个,系统根本不知道你在放歌。
import{avSession}from'@kit.AVSessionKit';letsession:avSession.AVSession;asyncfunctioninitSession(context:Context){// 1. 创建会话,类型为音频 (AUDIO)session=awaitavSession.createAVSession(context,'MyMusicApp','audio');// 2. 激活会话awaitsession.activate();// 3. 设置元数据 (歌名、歌手、封面)// 这一步很重要,流转到音箱后,如果音箱有屏幕,会显示这些信息letmetadata:avSession.AVMetadata={assetId:'123',title:'海阔天空',artist:'Beyond',mediaImage:'https://example.com/cover.jpg'};awaitsession.setAVMetadata(metadata);console.info('AVSession 初始化完成');}2. 核心逻辑:发现并流转到音箱
我们要调用AVCastController来扫描局域网内的鸿蒙设备(音箱、智慧屏),并指定输出。
import{avSession}from'@kit.AVSessionKit';import{BusinessError}from'@kit.BasicServicesKit';asyncfunctioncastToSpeaker(){if(!session)return;// 1. 获取投射控制器letcastController=awaitsession.getAVCastController();// 2. 监听设备发现 (类似于蓝牙扫描,但是是在 Wi-Fi/软总线上扫)castController.on('castDeviceChange',(state:avSession.AVCastDeviceState,device:avSession.AVCastDeviceInfo)=>{console.info(`发现设备:${device.deviceId}, 状态:${state}`);// 3. 简单的自动流转逻辑:发现是“音箱”,就直接切过去// 实际业务中,应该弹窗让用户选择if(state===avSession.AVCastDeviceState.DEVICE_STATE_CONNECTED){// 设备已连接,准备就绪}});// 4. 发起流转指令// startCast 会触发系统的设备选择弹窗 (AirPlay 风格)// 如果你想自己写 UI,需要使用 getOutputDevices() 手动切换try{// 这里的 'audio' 表示我们只投射音频awaitcastController.startCast('audio');console.info('已拉起系统流转弹窗');}catch(err){leterror=errasBusinessError;console.error(`流转失败:${error.message}`);}}3. 处理流转后的状态 (同步播放进度)
当流转成功后,音频实际上是从音箱发出的。你的 App 需要监听来自音箱的指令(比如用户对着音箱喊“下一首”)。
// 监听远端指令session.on('play',()=>{// 音箱发来“播放”指令player.play();});session.on('pause',()=>{// 音箱发来“暂停”指令player.pause();});session.on('playNext',()=>{// 音箱发来“下一首”指令playNextSong();});📡 三、 进阶:如何实现“碰一碰”自动流转?
上面的代码是“手动点击”触发流转。如果要实现华为音乐那种“手机碰一下音箱就流转”,需要结合NFC和Want机制。
逻辑极其简单:
- NFC 标签写入:在音箱的 NFC 贴纸里写入你的 App 的
Ability信息。 - 手机读取:手机碰到 NFC,系统读取到 Ability 信息,自动拉起你的 App。
- 触发流转:
在 App 的onCreate或onNewWant生命周期中,判断启动来源。如果是NFC触发,直接执行上面的castToSpeaker()逻辑,并指定 deviceId(NFC数据里通常包含音箱的 MAC/ID)。
// EntryAbility.tsonNewWant(want:Want,launchParam:AbilityConstant.LaunchParam){// 判断是否由 NFC 碰一碰触发if(want.parameters&&want.parameters['ohos.extra.param.deviceType']==='speaker'){// 获取音箱 IDletspeakerId=want.parameters['deviceId'];// 执行静默流转AudioManager.transferTo(speakerId);}}🚧 四、 避坑指南
- 权限问题:跨设备发现和连接需要
ohos.permission.DISTRIBUTED_DATASYNC权限,且这是个 ACL 权限(需要签名证书授权)。 - 网络环境:流转的前提是设备必须在同一 Wi-Fi下,或者登录了同一个华为账号(利用超级终端能力)。
- 流转模式:
- 本地解码 (Remote Audio):手机下载解码,传 PCM 给音箱。适合实时性要求高的(游戏)。
- 远端解码 (Cast):手机传 URL 给音箱,音箱自己下载。适合听歌,音质最好,手机最省电。
- AVSession 默认倾向于智能选择模式。
🎯 总结
鸿蒙的音频流转,把**“连接”变成了“服务”**。
通过AVSession,你开发的不是一个孤立的播放器,而是一个可以在全屋设备间自由穿梭的“音乐精灵”。
Next Step:
如果你手里没有华为音箱,可以找两台鸿蒙手机。一台装你的 App,另一台作为“接收端”(在设置里打开“超级终端 -> 允许被发现”)。尝试用你的 App 搜索另一台手机,你会发现它也会出现在设备列表里,点击它,音乐就会神奇地从另一台手机里飘出来!