媒体投影:将音视频投射到远端设备(202)

📅 2026/7/19 13:48:44
媒体投影:将音视频投射到远端设备(202)
在鸿蒙HarmonyOS应用开发中媒体投影投播能力允许开发者将应用内的音视频资源无缝流转至远端设备播放。依托系统级的AVSession Kit和AVCastPicker组件开发者无需关注底层的设备发现、连接与认证过程即可快速构建跨设备的媒体播控体验。一、 核心架构与基本概念鸿蒙的投播机制基于分布式媒体会话AVSession体系主要包含以下核心组件媒体会话AVSession作为音视频管控服务负责统一管理应用内的音视频行为。在投播场景下应用通过 AVSession 设置投播能力、获取投播控制器并监听远端设备的连接与状态变化。投播组件AVCastPicker系统级的 UI 组件作为设备切换和投播能力的统一入口。应用将其嵌入界面后系统会自动完成设备的发现、连接和认证流程。投播控制器AVCastController在设备成功连接后由系统返回。应用通过该控制器向远端设备发送播放、暂停、调节音量等播控指令并监听远端播放状态的变更。// AvSessionManager.ets import { avSession } from kit.AVSessionKit; export class AvSessionManager { private session: avSession.AVSession | null null; // 1. 创建并激活媒体会话 public async createSession(context: Context) { try { // 创建视频类型的媒体会话 this.session await avSession.createAVSession(context, video_cast, video); await this.session.activate(); // 核心声明当前应用支持投播能力 await this.session.setExtras({ requireAbilityList: [url-cast] }); console.info(AVSession 创建并激活成功); } catch (err) { console.error(AVSession 创建失败:, err); } } // 2. 获取 Session 实例供外部使用 public getSession(): avSession.AVSession | null { return this.session; } }二、 投播运作机制与交互流程设备发现与连接用户在应用内点击 AVCastPicker 组件系统自动搜索并展示支持投播的远端设备。用户选择后系统自动建立连接。进入远端投播监听到设备连接成功后应用应停止本地播放器避免双端同时发声并通过 AVSession 获取 AVCastController。此时建议将应用界面切换为“遥控器”模式。双向播控与状态同步本端控制用户在手机端或播控中心操作时指令通过 AVCastController 下发系统同步更新远端播放器状态。远端控制用户直接在远端设备如智慧屏上操作时状态变更会触发回调本端应用可实时监听并刷新 UI。// CastPlayerPage.ets import { avSession } from kit.AVSessionKit; import { AvSessionManager } from ./AvSessionManager; Entry Component struct CastPlayerPage { private sessionManager new AvSessionManager(); State isCasting: boolean false; aboutToAppear() { // 初始化会话 this.sessionManager.createSession(getContext(this)); const session this.sessionManager.getSession(); // 监听远端设备连接状态变化 session?.on(outputDeviceChange, (connectState, device) { const currentDevice device?.devices?.[0]; if (currentDevice?.castCategory avSession.AVCastCategory.CATEGORY_REMOTE) { if (connectState avSession.ConnectionState.STATE_CONNECTED) { // 设备连接成功进入投播状态 this.isCasting true; // TODO: 停止本地播放切换为遥控器模式 } else if (connectState avSession.ConnectionState.STATE_DISCONNECTED) { // 设备断开恢复本地播放 this.isCasting false; } } }); } build() { Column() { // 挂载系统级投播组件点击后自动弹出设备选择半模态 avSession.AVCastPicker({ pickerStyle: avSession.AVCastPickerStyle.STYLE_PANEL, sessionType: video }) .width(100%) .height(60) Text(this.isCasting ? 正在投播中... : 本地播放模式) .fontSize(18) .margin({ top: 20 }) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) } }三、 开发约束与前置条件实现投播功能需严格满足以下系统与环境要求设备与系统版本本端设备需为 HarmonyOS 5.0.0 及以上版本的手机或平板远端设备需为 HarmonyOS 5.0.0 及以上的 2in1 设备、HarmonyOS 3.1 及以上版本的华为智慧屏或支持标准 DLNA 协议的设备。网络与权限双端设备必须同时打开蓝牙和 Wi-Fi并可访问网络。若涉及读取媒体库文件还需申请ohos.permission.READ_IMAGEVIDEO受限权限。后台长时任务为防止应用在投播后被系统冻结或清理必须申请后台长时任务Background Task以维持投播会话。// CastController.ets import { avSession } from kit.AVSessionKit; export class CastController { private avCastController: avSession.AVCastController | null null; // 1. 设备连接成功后获取投播控制器 public async initController(session: avSession.AVSession) { try { this.avCastController await session.getAVCastController(); // 监听远端播放状态变化 this.avCastController.on(playbackStateChange, all, (state) { console.info(远端播放状态变更: ${state.state}); // TODO: 同步更新本端 UI 状态 }); } catch (err) { console.error(获取投播控制器失败:, err); } } // 2. 向远端设备发送播放指令 public async startRemotePlay(mediaUri: string, title: string) { if (!this.avCastController) return; // 构建投播媒体队列项 const playItem: avSession.AVQueueItem { itemId: video_001, description: { assetId: VIDEO-001, title: title, mediaUri: mediaUri, mediaType: VIDEO, duration: 3600000 // 毫秒 } }; // 准备资源并启动远端播放 await this.avCastController.prepare(playItem); await this.avCastController.start(playItem); } // 3. 发送暂停/播放等播控指令 public async sendPlayPauseCommand() { const command: avSession.AVCastControlCommand { command: avSession.CastControlCommand.COMMAND_PLAY_PAUSE }; await this.avCastController?.sendControlCommand(command); } }四、 性能优化基础播控前置适配在接入投播组件前应用必须先适配媒体播控中心的基础播控业务如配置媒体元数据、响应暂停/播放命令等否则投播功能无法生效。不支持投播内容的处理当应用切换上下集时若下一集内容不支持投播可通过设置filter参数为 0控制系统播控中心隐藏投播设备列表避免用户误触。DRM 加密资源支持若投播的是在线 DRM 视频资源应用需注册 DRM 许可证请求回调函数keyRequest在获取许可证后调用processMediaKeyResponse接口向远端提供秘钥方可正常播放。// DrmCastSupport.ets import { avSession } from kit.AVSessionKit; export class DrmCastSupport { // 针对 DRM 加密视频的投播支持 public static setupDrmCallback(castController: avSession.AVCastController) { // 注册 DRM 秘钥请求回调 castController.on(keyRequest, async (keyRequest) { console.info(收到 DRM 秘钥请求, assetId:, keyRequest.assetId); try { // TODO: 调用业务后端接口获取 DRM 许可证秘钥 const licenseKey await fetchDrmLicense(keyRequest.assetId); // 将秘钥提供给远端设备进行解密播放 const response new Uint8Array(licenseKey); await castController.processMediaKeyResponse(keyRequest.assetId, response); } catch (err) { console.error(DRM 秘钥获取失败:, err); } }); } }五、 媒体投影实战AVSession 初始化与 AVCastPicker 挂载在鸿蒙 ArkTS 开发中实现音视频投播的核心在于正确配置媒体会话并接入系统级投播组件。创建并激活媒体会话通过avSession.createAVSession创建会话并调用activate()激活。同时必须通过setExtras声明当前应用支持投播能力如设置requireAbilityList: [url-cast]这是触发投播的关键前置条件。配置媒体元数据调用setAVMetadata设置当前播放内容的标题、作者、封面及 DRM 类型等信息。未配置元数据会导致投播组件无法显示或投播后远端黑屏。挂载 AVCastPicker 组件在 UI 层引入AVCastPicker组件。用户点击后系统会自动拉起设备选择弹框完成发现、连接和认证应用无需干预底层流程。// AvSessionCastSetup.ets import { avSession } from kit.AVSessionKit; export class AvSessionCastSetup { private videoSession: avSession.AVSession | null null; // 1. 创建会话、激活并声明投播能力 public async initSession(context: Context): Promisevoid { this.videoSession await avSession.createAVSession(context, video_cast, video); await this.videoSession.activate(); // 核心声明支持投播能力 await this.videoSession.setExtras({ requireAbilityList: [url-cast] }); } // 2. 配置媒体元数据未配置会导致投播组件无法显示或远端黑屏 public async setMediaMetadata(title: string, artist: string, coverUri: string) { const metadata: avSession.AVMetadata { title: title, artist: artist, mediaImage: coverUri, // 实际开发中需传入 PixelMap 或有效 URI duration: 3600000 }; await this.videoSession?.setAVMetadata(metadata); } // 获取 Session 供 UI 层绑定 AVCastPicker public getSession(): avSession.AVSession | null { return this.videoSession; } }六、 进阶场景AVCastController 播控指令与状态同步当设备连接成功后应用需通过投播控制器实现双向播控。获取投播控制器监听outputDeviceChange事件当connectState为已连接且设备类别为CATEGORY_REMOTE时调用getAVCastController获取控制器实例。发送播控指令通过avCastController.sendControlCommand发送播放、暂停、上一首/下一首等指令。同时可调用prepare和start接口主动控制远端资源的加载与播放。监听远端状态回传注册playbackStateChange和mediaItemChange回调实时同步远端的播放进度、播放状态及当前媒体信息确保本端 UI 与远端保持一致。// AvSessionCastSetup.ets import { avSession } from kit.AVSessionKit; export class AvSessionCastSetup { private videoSession: avSession.AVSession | null null; // 1. 创建会话、激活并声明投播能力 public async initSession(context: Context): Promisevoid { this.videoSession await avSession.createAVSession(context, video_cast, video); await this.videoSession.activate(); // 核心声明支持投播能力 await this.videoSession.setExtras({ requireAbilityList: [url-cast] }); } // 2. 配置媒体元数据未配置会导致投播组件无法显示或远端黑屏 public async setMediaMetadata(title: string, artist: string, coverUri: string) { const metadata: avSession.AVMetadata { title: title, artist: artist, mediaImage: coverUri, // 实际开发中需传入 PixelMap 或有效 URI duration: 3600000 }; await this.videoSession?.setAVMetadata(metadata); } // 获取 Session 供 UI 层绑定 AVCastPicker public getSession(): avSession.AVSession | null { return this.videoSession; } }在实际落地媒体投影功能时需特别注意以下工程规范PCM 与 Cast 协议差异PCM 投播模式类似蓝牙连接无playbackStateChange回调远端状态直接跟随本地而 Cast Stream 模式和 DLNA 协议则有独立的状态回调需据此同步状态。直播投播的特殊处理直播场景下不应设置播放初始位置startPosition或应设为 0否则可能导致投播失败。同时带 Referer 防盗链的网络视频目前不支持投播。扩展屏投播模式对于需要双屏协作的场景可通过getAllCastDisplays()获取支持扩展屏的设备在虚拟扩展屏上启动独立的 UIAbility 绘制投屏内容实现主屏与扩展屏的差异化显示。// CastEngineeringUtils.ets import { avSession } from kit.AVSessionKit; export class CastEngineeringUtils { // 1. 直播投播特殊处理startPosition 必须设为 0 public static getLiveStreamPlayItem(liveUrl: string): avSession.AVQueueItem { return { itemId: live_001, description: { assetId: LIVE-001, title: Live Broadcast, mediaUri: liveUrl, mediaType: VIDEO, duration: 0, // 直播无固定时长 startPosition: 0 // 核心不可设置初始位置否则投播失败 } }; } // 2. 扩展屏投播模式获取支持扩展屏的设备列表 public static async getExtendedDisplays(session: avSession.AVSession) { try { // 获取所有支持扩展屏的远端设备 const displays await session.getAllCastDisplays(); if (displays displays.length 0) { console.info(发现 ${displays.length} 个支持扩展屏的设备); // TODO: 在虚拟扩展屏上启动独立的 UIAbility 绘制差异化内容 } } catch (err) { console.error(获取扩展屏设备失败:, err); } } // 3. 协议差异提示PCM 模式无独立状态回调 public static handleProtocolDifferences(protocolType: number) { if (protocolType avSession.ProtocolType.TYPE_PCM) { // PCM 模式远端状态直接跟随本地无需监听 playbackStateChange console.info(当前为 PCM 投播模式状态自动同步); } else { // Cast Stream 或 DLNA 协议需依赖回调同步 console.info(当前为 Cast/DLNA 协议需监听状态回调); } } }