HarmonyOS开发中的媒体路由:AVRouter、设备发现、路由选择、路由切换、路由策略

举报
Jack20 发表于 2026/06/21 11:45:13 2026/06/21
【摘要】 HarmonyOS开发中的媒体路由:AVRouter、设备发现、路由选择、路由切换、路由策略核心要点:掌握 HarmonyOS 媒体路由框架的核心机制,理解 AVRouter 的设备发现与路由选择流程,实现多设备间的无缝媒体切换,构建智能化的路由策略体系。 一、背景与动机想象一下这个场景:你正在手机上追一部剧,看到高潮部分突然想投到客厅的大屏电视上看——这时候,你的 App 需要知道"有哪...

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 路由切换注意事项

  1. 切换前检查当前状态:如果正在切换中,不要重复发起切换请求,否则会导致状态混乱
  2. 超时处理:路由切换可能因为网络问题卡住,务必设置超时机制(建议 10-15 秒)
  3. 回退策略:切换失败时应该回退到之前的路由,而不是让用户处于"无路可走"的状态
  4. 资源释放:不再使用的路由必须调用 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
      参数结构优化
      路由预测
      多路由并行
      进度回调

关键知识点回顾

  1. AVRouter 是核心:所有媒体路由操作都通过 AVRouter 进行,理解它的 API 设计是基础
  2. 设备发现是起点:多协议扫描确保覆盖各类设备,回调中务必做去重和空值保护
  3. 路由策略要灵活:不同场景需要不同策略,建议提供多种策略供用户选择
  4. 切换过程要健壮:超时、回退、资源释放一个都不能少
  5. HarmonyOS 6 新特性:路由预测和多路由并行是重要升级方向

一句话总结:媒体路由的本质是"让媒体流找到最合适的输出路径",AVRouter 就是那个帮你指路的导航仪。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。