HarmonyOS开发中的媒体路由:AVRouter、设备发现、路由选择、路由切换、路由策略
HarmonyOS开发中的媒体路由:AVRouter、设备发现、路由选择、路由切换、路由策略
核心要点:掌握 HarmonyOS 媒体路由框架的核心机制,理解 AVRouter 的设备发现与路由选择流程,实现多设备间的无缝媒体切换,构建智能化的路由策略体系。
一、背景与动机
想象一下这个场景:你正在手机上追一部剧,看到高潮部分突然想投到客厅的大屏电视上看——这时候,你的 App 需要知道"有哪些设备可以接收媒体流",“选哪个设备最合适”,“切换过程中怎么保证不卡顿”。这就是媒体路由要解决的核心问题。
再比如,智能家居场景下,音乐 App 播放的歌曲可以从手机无缝切换到智能音箱,视频通话可以从平板切换到智慧屏。这些看似简单的"切换"操作,背后涉及设备发现、能力协商、路由选择、状态同步等一系列复杂流程。
HarmonyOS 提供了 AVRouter 媒体路由框架,它就像一个"交通指挥官",负责管理媒体数据在不同设备之间的流转路径。理解它,你才能让媒体在多设备生态中自由流动。
二、核心原理
2.1 媒体路由架构总览
媒体路由框架的核心是 AVRouter,它管理着从媒体源到媒体输出的完整链路。整个架构分为三层:
- 应用层:通过 AVRouter API 发起路由请求
- 路由层:负责设备发现、路由选择、状态管理
- 设备层:各类媒体输出设备(音箱、电视、耳机等)
flowchart TB
classDef primary fill:#4A90D9,stroke:#2E6BA6,color:#fff,stroke-width:2px
classDef warning fill:#E6A23C,stroke:#CF8C12,color:#fff,stroke-width:2px
classDef error fill:#F56C6C,stroke:#DD3B3B,color:#fff,stroke-width:2px
classDef info fill:#67C23A,stroke:#4AA82A,color:#fff,stroke-width:2px
classDef purple fill:#9B59B6,stroke:#7D3C98,color:#fff,stroke-width:2px
A[应用层]:::primary --> B[AVRouter 路由管理器]:::warning
B --> C[设备发现模块]:::info
B --> D[路由选择引擎]:::purple
B --> E[路由状态管理]:::error
C --> F[局域网设备]:::primary
C --> G[蓝牙设备]:::primary
C --> H[投屏设备]:::primary
D --> I[能力匹配]:::info
D --> J[优先级排序]:::info
D --> K[策略决策]:::info
E --> L[连接状态]:::warning
E --> M[切换状态]:::warning
E --> N[断开状态]:::warning
2.2 AVRouter 核心概念
| 概念 | 说明 |
|---|---|
| AVRouter | 媒体路由管理器,提供设备发现、路由选择、路由切换等核心能力 |
| AVRoute | 一条具体的媒体路由,包含源设备、目标设备、媒体类型等信息 |
| AVDevice | 媒体输出设备描述,包含设备ID、名称、类型、能力集 |
| AVRoutePolicy | 路由策略,定义路由选择的规则和优先级 |
| AVRouteSession | 路由会话,管理一次完整的路由生命周期 |
2.3 设备发现流程
设备发现是媒体路由的第一步。AVRouter 通过多种协议(mDNS、SSDP、蓝牙等)扫描局域网内的可用媒体设备,然后根据设备的媒体能力进行过滤和分类。
flowchart LR
classDef primary fill:#4A90D9,stroke:#2E6BA6,color:#fff,stroke-width:2px
classDef warning fill:#E6A23C,stroke:#CF8C12,color:#fff,stroke-width:2px
classDef error fill:#F56C6C,stroke:#DD3B3B,color:#fff,stroke-width:2px
classDef info fill:#67C23A,stroke:#4AA82A,color:#fff,stroke-width:2px
classDef purple fill:#9B59B6,stroke:#7D3C98,color:#fff,stroke-width:2px
A[启动发现]:::primary --> B[多协议扫描]:::warning
B --> C[mDNS 局域网]:::info
B --> D[SSDP UPnP]:::info
B --> E[蓝牙 BLE]:::info
C --> F[设备信息收集]:::purple
D --> F
E --> F
F --> G[能力过滤]:::error
G --> H[可用设备列表]:::primary
三、代码实战
3.1 基础设备发现与监听
这是最基础的用法——发现局域网内的媒体设备,并实时监听设备变化。
// MediaRouteDiscovery.ets
// 媒体路由设备发现与监听示例
import { avRouter } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct MediaRouteDiscovery {
// 已发现的设备列表
@State deviceList: avRouter.AVDevice[] = [];
// 发现状态
@State isDiscovering: boolean = false;
// 当前选中设备
@State selectedDevice: avRouter.AVDevice | null = null;
// 设备发现回调
private deviceCallback: avRouter.AVRouterDeviceCallback = {
// 设备上线回调
onDeviceAvailable: (device: avRouter.AVDevice) => {
console.info(`[媒体路由] 发现新设备: ${device.deviceName}`);
// 检查是否已存在,避免重复
const exists = this.deviceList.some(d => d.deviceId === device.deviceId);
if (!exists) {
this.deviceList = [...this.deviceList, device];
}
},
// 设备离线回调
onDeviceUnavailable: (deviceId: string) => {
console.info(`[媒体路由] 设备离线: ${deviceId}`);
this.deviceList = this.deviceList.filter(d => d.deviceId !== deviceId);
// 如果离线的是当前选中设备,清除选中状态
if (this.selectedDevice?.deviceId === deviceId) {
this.selectedDevice = null;
}
}
};
// 启动设备发现
async startDiscovery() {
if (this.isDiscovering) {
console.warn('[媒体路由] 正在发现中,请勿重复启动');
return;
}
try {
// 注册设备发现回调
avRouter.on('deviceAvailable', this.deviceCallback);
// 启动发现,指定媒体类型为音频和视频
await avRouter.startDiscovery({
mediaType: avRouter.MediaType.MEDIA_TYPE_AUDIO | avRouter.MediaType.MEDIA_TYPE_VIDEO,
// 发现模式:主动扫描
discoveryMode: avRouter.DiscoveryMode.DISCOVERY_MODE_ACTIVE
});
this.isDiscovering = true;
console.info('[媒体路由] 设备发现已启动');
} catch (error) {
const err = error as BusinessError;
console.error(`[媒体路由] 启动发现失败: ${err.code} - ${err.message}`);
}
}
// 停止设备发现
async stopDiscovery() {
if (!this.isDiscovering) {
return;
}
try {
await avRouter.stopDiscovery();
avRouter.off('deviceAvailable');
this.isDiscovering = false;
console.info('[媒体路由] 设备发现已停止');
} catch (error) {
const err = error as BusinessError;
console.error(`[媒体路由] 停止发现失败: ${err.code} - ${err.message}`);
}
}
build() {
Column() {
// 标题栏
Text('媒体设备发现')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
// 控制按钮
Row() {
Button(this.isDiscovering ? '停止发现' : '开始发现')
.onClick(() => {
if (this.isDiscovering) {
this.stopDiscovery();
} else {
this.startDiscovery();
}
})
.backgroundColor(this.isDiscovering ? '#F56C6C' : '#4A90D9')
.fontColor(Color.White)
}
.margin({ bottom: 20 })
// 设备列表
List() {
ForEach(this.deviceList, (device: avRouter.AVDevice) => {
ListItem() {
Row() {
// 设备图标
Image(this.getDeviceIcon(device.deviceType))
.width(40)
.height(40)
.margin({ right: 12 })
// 设备信息
Column() {
Text(device.deviceName)
.fontSize(16)
.fontWeight(FontWeight.Medium)
Text(this.getDeviceTypeLabel(device.deviceType))
.fontSize(12)
.fontColor('#999999')
}
.alignItems(HorizontalAlign.Start)
Blank()
// 选中标识
if (this.selectedDevice?.deviceId === device.deviceId) {
Text('已选中')
.fontSize(12)
.fontColor('#4A90D9')
}
}
.width('100%')
.padding(12)
.borderRadius(8)
.backgroundColor(this.selectedDevice?.deviceId === device.deviceId
? '#E8F4FD' : '#FFFFFF')
.onClick(() => {
this.selectedDevice = device;
})
}
.margin({ bottom: 8 })
}, (device: avRouter.AVDevice) => device.deviceId)
}
.layoutWeight(1)
// 空状态提示
if (this.deviceList.length === 0) {
Column() {
Text('暂无可用设备')
.fontSize(16)
.fontColor('#999999')
Text('请确保设备处于同一局域网')
.fontSize(12)
.fontColor('#CCCCCC')
.margin({ top: 8 })
}
.justifyContent(FlexAlign.Center)
.layoutWeight(1)
}
}
.width('100%')
.height('100%')
.padding(16)
}
// 根据设备类型获取图标资源名
private getDeviceIcon(deviceType: avRouter.DeviceType): ResourceStr {
switch (deviceType) {
case avRouter.DeviceType.DEVICE_TYPE_SPEAKER:
return $r('app.media.ic_speaker');
case avRouter.DeviceType.DEVICE_TYPE_TV:
return $r('app.media.ic_tv');
case avRouter.DeviceType.DEVICE_TYPE_HEADPHONE:
return $r('app.media.ic_headphone');
default:
return $r('app.media.ic_device_default');
}
}
// 获取设备类型标签
private getDeviceTypeLabel(deviceType: avRouter.DeviceType): string {
switch (deviceType) {
case avRouter.DeviceType.DEVICE_TYPE_SPEAKER:
return '智能音箱';
case avRouter.DeviceType.DEVICE_TYPE_TV:
return '智能电视';
case avRouter.DeviceType.DEVICE_TYPE_HEADPHONE:
return '蓝牙耳机';
case avRouter.DeviceType.DEVICE_TYPE_TABLET:
return '平板设备';
default:
return '未知设备';
}
}
}
3.2 路由选择与切换
发现设备之后,最关键的操作就是路由选择和切换。下面展示如何将媒体流从当前设备切换到目标设备。
// RouteSwitchManager.ets
// 路由选择与切换管理器
import { avRouter } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
// 路由切换状态枚举
enum RouteSwitchState {
IDLE = 'idle',
SWITCHING = 'switching',
COMPLETED = 'completed',
FAILED = 'failed'
}
@Entry
@Component
struct RouteSwitchManager {
// 当前路由
@State currentRoute: avRouter.AVRoute | null = null;
// 可用路由列表
@State availableRoutes: avRouter.AVRoute[] = [];
// 切换状态
@State switchState: RouteSwitchState = RouteSwitchState.IDLE;
// 状态描述
@State stateMessage: string = '等待操作';
// 进度百分比
@State switchProgress: number = 0;
// 路由状态回调
private routeStateCallback: avRouter.AVRouterStateCallback = {
// 路由状态变更
onRouteStateChanged: (state: avRouter.AVRouteState, route: avRouter.AVRoute) => {
console.info(`[路由切换] 状态变更: ${state}, 路由: ${route.routeId}`);
switch (state) {
case avRouter.AVRouteState.ROUTE_STATE_CONNECTING:
this.switchState = RouteSwitchState.SWITCHING;
this.stateMessage = '正在连接目标设备...';
this.switchProgress = 30;
break;
case avRouter.AVRouteState.ROUTE_STATE_CONNECTED:
this.switchState = RouteSwitchState.COMPLETED;
this.stateMessage = '路由切换成功';
this.switchProgress = 100;
this.currentRoute = route;
break;
case avRouter.AVRouteState.ROUTE_STATE_DISCONNECTED:
if (this.switchState === RouteSwitchState.SWITCHING) {
this.switchState = RouteSwitchState.FAILED;
this.stateMessage = '路由切换失败,设备已断开';
}
this.switchProgress = 0;
break;
default:
break;
}
}
};
aboutToAppear() {
// 注册路由状态监听
avRouter.on('routeStateChanged', this.routeStateCallback);
}
aboutToDisappear() {
// 注销路由状态监听
avRouter.off('routeStateChanged');
}
// 选择并切换到指定路由
async switchToRoute(targetRoute: avRouter.AVRoute) {
if (this.switchState === RouteSwitchState.SWITCHING) {
console.warn('[路由切换] 正在切换中,请稍候');
return;
}
try {
this.switchState = RouteSwitchState.SWITCHING;
this.stateMessage = '正在准备路由切换...';
this.switchProgress = 10;
// 执行路由切换
await avRouter.selectRoute(targetRoute);
this.switchProgress = 50;
console.info(`[路由切换] 已选择路由: ${targetRoute.routeId}`);
} catch (error) {
const err = error as BusinessError;
this.switchState = RouteSwitchState.FAILED;
this.stateMessage = `切换失败: ${err.message}`;
this.switchProgress = 0;
console.error(`[路由切换] 切换失败: ${err.code} - ${err.message}`);
}
}
// 释放当前路由
async releaseCurrentRoute() {
if (!this.currentRoute) {
console.warn('[路由切换] 当前无活跃路由');
return;
}
try {
await avRouter.releaseRoute(this.currentRoute);
this.currentRoute = null;
this.switchState = RouteSwitchState.IDLE;
this.stateMessage = '路由已释放';
this.switchProgress = 0;
console.info('[路由切换] 路由已释放');
} catch (error) {
const err = error as BusinessError;
console.error(`[路由切换] 释放路由失败: ${err.code} - ${err.message}`);
}
}
// 获取可用路由列表
async queryAvailableRoutes() {
try {
const routes = await avRouter.getAvailableRoutes({
mediaType: avRouter.MediaType.MEDIA_TYPE_AUDIO | avRouter.MediaType.MEDIA_TYPE_VIDEO
});
this.availableRoutes = routes;
console.info(`[路由切换] 查询到 ${routes.length} 条可用路由`);
} catch (error) {
const err = error as BusinessError;
console.error(`[路由切换] 查询路由失败: ${err.code} - ${err.message}`);
}
}
build() {
Column() {
// 顶部状态卡片
Column() {
Text('当前路由状态')
.fontSize(14)
.fontColor('#999999')
Text(this.stateMessage)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ top: 4 })
.fontColor(this.getStateColor())
// 切换进度条
if (this.switchState === RouteSwitchState.SWITCHING) {
Progress({ value: this.switchProgress, total: 100, type: ProgressType.Linear })
.width('80%')
.color('#4A90D9')
.margin({ top: 12 })
}
}
.width('100%')
.padding(20)
.borderRadius(12)
.backgroundColor('#F5F7FA')
.margin({ bottom: 16 })
// 操作按钮
Row() {
Button('查询路由')
.onClick(() => this.queryAvailableRoutes())
.backgroundColor('#4A90D9')
.fontColor(Color.White)
.layoutWeight(1)
Button('释放路由')
.onClick(() => this.releaseCurrentRoute())
.backgroundColor('#E6A23C')
.fontColor(Color.White)
.layoutWeight(1)
.margin({ left: 12 })
}
.width('100%')
.margin({ bottom: 16 })
// 可用路由列表
Text('可用路由')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 8 })
List() {
ForEach(this.availableRoutes, (route: avRouter.AVRoute) => {
ListItem() {
Row() {
Column() {
Text(route.routeName || `路由 ${route.routeId}`)
.fontSize(15)
.fontWeight(FontWeight.Medium)
Text(`设备: ${route.deviceInfo?.deviceName || '未知'}`)
.fontSize(12)
.fontColor('#666666')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
Blank()
Button('切换')
.height(32)
.fontSize(13)
.onClick(() => this.switchToRoute(route))
.backgroundColor('#67C23A')
.fontColor(Color.White)
}
.width('100%')
.padding(12)
.borderRadius(8)
.backgroundColor(Color.White)
}
.margin({ bottom: 8 })
}, (route: avRouter.AVRoute) => route.routeId)
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.padding(16)
}
// 获取状态对应颜色
private getStateColor(): string {
switch (this.switchState) {
case RouteSwitchState.IDLE:
return '#909399';
case RouteSwitchState.SWITCHING:
return '#E6A23C';
case RouteSwitchState.COMPLETED:
return '#67C23A';
case RouteSwitchState.FAILED:
return '#F56C6C';
default:
return '#909399';
}
}
}
3.3 自定义路由策略
在实际项目中,我们往往需要根据业务场景自定义路由选择策略,比如优先选择特定类型的设备,或者根据网络状况动态调整。
// CustomRoutePolicy.ets
// 自定义媒体路由策略实现
import { avRouter } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
// 路由策略类型
enum PolicyType {
PRIORITY_BASED = 'priority', // 优先级策略
QUALITY_BASED = 'quality', // 质量策略
POWER_SAVING = 'power_saving', // 省电策略
AUTO = 'auto' // 自动策略
}
// 设备优先级配置
interface DevicePriority {
deviceType: avRouter.DeviceType;
priority: number; // 数值越大优先级越高
label: string;
}
@Entry
@Component
struct CustomRoutePolicy {
// 当前策略
@State currentPolicy: PolicyType = PolicyType.AUTO;
// 排序后的路由列表
@State sortedRoutes: avRouter.AVRoute[] = [];
// 策略描述
@State policyDescription: string = '自动策略:根据设备能力和网络状况智能选择';
// 策略选择弹窗控制
@State showPolicyPicker: boolean = false;
// 设备优先级配置表(可根据业务需求调整)
private devicePriorities: DevicePriority[] = [
{ deviceType: avRouter.DeviceType.DEVICE_TYPE_TV, priority: 100, label: '智能电视' },
{ deviceType: avRouter.DeviceType.DEVICE_TYPE_SPEAKER, priority: 80, label: '智能音箱' },
{ deviceType: avRouter.DeviceType.DEVICE_TYPE_TABLET, priority: 60, label: '平板设备' },
{ deviceType: avRouter.DeviceType.DEVICE_TYPE_HEADPHONE, priority: 40, label: '蓝牙耳机' },
];
// 根据策略对路由排序
sortRoutesByPolicy(routes: avRouter.AVRoute[], policy: PolicyType): avRouter.AVRoute[] {
const sorted = [...routes];
switch (policy) {
case PolicyType.PRIORITY_BASED:
// 按设备优先级排序
sorted.sort((a, b) => {
const priorityA = this.getDevicePriority(a.deviceInfo?.deviceType);
const priorityB = this.getDevicePriority(b.deviceInfo?.deviceType);
return priorityB - priorityA;
});
break;
case PolicyType.QUALITY_BASED:
// 按设备能力排序(支持更多编解码格式的优先)
sorted.sort((a, b) => {
const scoreA = this.calculateQualityScore(a);
const scoreB = this.calculateQualityScore(b);
return scoreB - scoreA;
});
break;
case PolicyType.POWER_SAVING:
// 省电策略:优先选择蓝牙设备,其次局域网设备
sorted.sort((a, b) => {
const powerA = this.getPowerConsumptionLevel(a);
const powerB = this.getPowerConsumptionLevel(b);
return powerA - powerB;
});
break;
case PolicyType.AUTO:
// 自动策略:综合评分
sorted.sort((a, b) => {
const scoreA = this.calculateAutoScore(a);
const scoreB = this.calculateAutoScore(b);
return scoreB - scoreA;
});
break;
}
return sorted;
}
// 获取设备优先级
private getDevicePriority(deviceType?: avRouter.DeviceType): number {
if (!deviceType) return 0;
const config = this.devicePriorities.find(p => p.deviceType === deviceType);
return config ? config.priority : 10;
}
// 计算质量评分
private calculateQualityScore(route: avRouter.AVRoute): number {
let score = 0;
// 支持视频加分
if (route.supportMediaTypes & avRouter.MediaType.MEDIA_TYPE_VIDEO) {
score += 50;
}
// 支持音频加分
if (route.supportMediaTypes & avRouter.MediaType.MEDIA_TYPE_AUDIO) {
score += 30;
}
// 信号强度加分
if (route.deviceInfo?.signalStrength) {
score += route.deviceInfo.signalStrength;
}
return score;
}
// 获取功耗等级(数值越低越省电)
private getPowerConsumptionLevel(route: avRouter.AVRoute): number {
const deviceType = route.deviceInfo?.deviceType;
if (deviceType === avRouter.DeviceType.DEVICE_TYPE_HEADPHONE) return 1;
if (deviceType === avRouter.DeviceType.DEVICE_TYPE_SPEAKER) return 2;
if (deviceType === avRouter.DeviceType.DEVICE_TYPE_TV) return 3;
return 4;
}
// 自动策略综合评分
private calculateAutoScore(route: avRouter.AVRoute): number {
const priorityScore = this.getDevicePriority(route.deviceInfo?.deviceType) * 0.4;
const qualityScore = this.calculateQualityScore(route) * 0.4;
const signalScore = (route.deviceInfo?.signalStrength || 0) * 0.2;
return priorityScore + qualityScore + signalScore;
}
// 应用策略并刷新列表
async applyPolicy(policy: PolicyType) {
this.currentPolicy = policy;
this.updatePolicyDescription(policy);
try {
const routes = await avRouter.getAvailableRoutes({
mediaType: avRouter.MediaType.MEDIA_TYPE_AUDIO | avRouter.MediaType.MEDIA_TYPE_VIDEO
});
this.sortedRoutes = this.sortRoutesByPolicy(routes, policy);
console.info(`[路由策略] 应用 ${policy} 策略,排序后 ${this.sortedRoutes.length} 条路由`);
} catch (error) {
const err = error as BusinessError;
console.error(`[路由策略] 应用策略失败: ${err.code} - ${err.message}`);
}
}
// 更新策略描述
private updatePolicyDescription(policy: PolicyType) {
switch (policy) {
case PolicyType.PRIORITY_BASED:
this.policyDescription = '优先级策略:按设备类型优先级选择(电视 > 音箱 > 平板 > 耳机)';
break;
case PolicyType.QUALITY_BASED:
this.policyDescription = '质量策略:优先选择支持更多媒体格式、信号更强的设备';
break;
case PolicyType.POWER_SAVING:
this.policyDescription = '省电策略:优先选择功耗更低的蓝牙设备';
break;
case PolicyType.AUTO:
this.policyDescription = '自动策略:综合优先级、质量和信号强度智能选择';
break;
}
}
build() {
Column() {
// 策略选择区
Column() {
Text('路由策略')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text(this.policyDescription)
.fontSize(13)
.fontColor('#666666')
.margin({ top: 8 })
// 策略切换按钮组
Row() {
ForEach([PolicyType.AUTO, PolicyType.PRIORITY_BASED, PolicyType.QUALITY_BASED, PolicyType.POWER_SAVING],
(policy: PolicyType) => {
Button(this.getPolicyLabel(policy))
.height(32)
.fontSize(12)
.onClick(() => this.applyPolicy(policy))
.backgroundColor(this.currentPolicy === policy ? '#4A90D9' : '#E8E8E8')
.fontColor(this.currentPolicy === policy ? Color.White : '#333333')
.margin({ right: 8 })
}, (policy: PolicyType) => policy)
}
.margin({ top: 12 })
}
.width('100%')
.padding(16)
.borderRadius(12)
.backgroundColor('#F5F7FA')
.margin({ bottom: 16 })
// 排序后的路由列表
Text('推荐路由(按策略排序)')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 8 })
List() {
ForEach(this.sortedRoutes, (route: avRouter.AVRoute, index?: number) => {
ListItem() {
Row() {
// 排名标识
Text(`${(index ?? 0) + 1}`)
.width(28)
.height(28)
.textAlign(TextAlign.Center)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
.borderRadius(14)
.backgroundColor((index ?? 0) === 0 ? '#4A90D9' : '#C0C4CC')
// 设备信息
Column() {
Text(route.routeName || `路由 ${route.routeId}`)
.fontSize(15)
.fontWeight(FontWeight.Medium)
Text(`评分: ${this.calculateAutoScore(route).toFixed(1)}`)
.fontSize(12)
.fontColor('#999999')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 })
Blank()
// 推荐标签
if ((index ?? 0) === 0) {
Text('推荐')
.fontSize(11)
.fontColor('#4A90D9')
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.borderRadius(4)
.backgroundColor('#E8F4FD')
}
}
.width('100%')
.padding(12)
.borderRadius(8)
.backgroundColor(Color.White)
}
.margin({ bottom: 8 })
}, (route: avRouter.AVRoute) => route.routeId)
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.padding(16)
}
// 获取策略标签
private getPolicyLabel(policy: PolicyType): string {
switch (policy) {
case PolicyType.AUTO: return '自动';
case PolicyType.PRIORITY_BASED: return '优先级';
case PolicyType.QUALITY_BASED: return '质量';
case PolicyType.POWER_SAVING: return '省电';
default: return '未知';
}
}
}
四、踩坑与注意事项
4.1 设备发现常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 发现不到设备 | 设备不在同一局域网或蓝牙未开启 | 检查网络连通性,确保同一 WiFi 下 |
| 设备重复出现 | 回调未做去重处理 | 在 onDeviceAvailable 中按 deviceId 去重 |
| 发现速度慢 | 多协议扫描耗时较长 | 使用 DISCOVERY_MODE_PASSIVE 被动模式减少功耗 |
| 设备信息不完整 | 部分设备未上报完整能力信息 | 对缺失字段做兜底处理,避免空指针 |
4.2 路由切换注意事项
- 切换前检查当前状态:如果正在切换中,不要重复发起切换请求,否则会导致状态混乱
- 超时处理:路由切换可能因为网络问题卡住,务必设置超时机制(建议 10-15 秒)
- 回退策略:切换失败时应该回退到之前的路由,而不是让用户处于"无路可走"的状态
- 资源释放:不再使用的路由必须调用
releaseRoute释放,否则会占用系统资源
4.3 路由策略设计陷阱
- 不要只看设备类型:同类型设备的能力可能差异很大,需要综合评估
- 信号强度会波动:不要只凭一次扫描结果做决策,建议加入历史数据平滑
- 用户偏好很重要:纯算法排序可能不符合用户习惯,应该允许用户手动调整优先级
- 省电策略要谨慎:在低电量时自动切换到省电策略是合理的,但不要牺牲太多体验
五、HarmonyOS 6 适配
5.1 API 变更
| 变更项 | HarmonyOS 5 | HarmonyOS 6 |
|---|---|---|
| 路由发现 API | avRouter.startDiscovery() |
avRouter.startDiscovery()(参数结构优化) |
| 设备能力查询 | 需手动解析 | 新增 getDeviceCapabilities() 便捷方法 |
| 路由状态回调 | onRouteStateChanged |
新增 onRouteSwitchProgress 进度回调 |
| 多路由管理 | 单路由 | 支持多路由并行(如同时投屏+蓝牙音频) |
5.2 迁移指南
// HarmonyOS 5 写法
avRouter.startDiscovery({
mediaType: avRouter.MediaType.MEDIA_TYPE_AUDIO,
discoveryMode: avRouter.DiscoveryMode.DISCOVERY_MODE_ACTIVE
});
// HarmonyOS 6 写法(参数结构优化)
avRouter.startDiscovery({
mediaTypes: [avRouter.MediaType.MEDIA_TYPE_AUDIO, avRouter.MediaType.MEDIA_TYPE_VIDEO],
discoveryMode: avRouter.DiscoveryMode.DISCOVERY_MODE_ACTIVE,
// 新增:过滤条件
filter: {
minSignalStrength: 30,
supportedCodecs: ['AAC', 'SBC']
}
});
5.3 新特性适配
HarmonyOS 6 新增了路由预测能力,系统会根据用户历史行为预测可能的路由切换,提前建立连接:
// HarmonyOS 6 新增:路由预测
avRouter.enableRoutePrediction({
enabled: true,
// 预测模型参数
predictionConfig: {
historyWeight: 0.6, // 历史行为权重
contextWeight: 0.3, // 上下文权重(时间、地点)
realtimeWeight: 0.1 // 实时信号权重
}
});
六、总结
mindmap
root((媒体路由))
AVRouter 核心
路由管理器
设备发现
路由选择
状态监听
设备发现
mDNS 局域网
SSDP UPnP
蓝牙 BLE
去重处理
路由选择
优先级策略
质量策略
省电策略
自动策略
路由切换
状态管理
超时处理
回退机制
资源释放
HarmonyOS 6
参数结构优化
路由预测
多路由并行
进度回调
关键知识点回顾:
- AVRouter 是核心:所有媒体路由操作都通过 AVRouter 进行,理解它的 API 设计是基础
- 设备发现是起点:多协议扫描确保覆盖各类设备,回调中务必做去重和空值保护
- 路由策略要灵活:不同场景需要不同策略,建议提供多种策略供用户选择
- 切换过程要健壮:超时、回退、资源释放一个都不能少
- HarmonyOS 6 新特性:路由预测和多路由并行是重要升级方向
一句话总结:媒体路由的本质是"让媒体流找到最合适的输出路径",AVRouter 就是那个帮你指路的导航仪。
- 点赞
- 收藏
- 关注作者
评论(0)