公司动态

AVSession:媒体会话控制与锁屏播放(192)

📅 2026/7/17 11:03:41
AVSession:媒体会话控制与锁屏播放(192)
在鸿蒙HarmonyOS应用开发中AVSession Kit音视频播控服务是实现专业级音视频体验的核心框架。它不仅能让应用在锁屏状态下继续播放还能将播放控制权无缝移交给系统播控中心、蓝牙耳机、智能手表等设备。一、 核心机制与强制约束后台播放的“通行证”鸿蒙系统对后台音视频播放有严格的约束。如果应用需要后台播放如音乐、听书、长视频必须同时接入AVSession和BackgroundTasks Kit申请AUDIO_PLAYBACK长时任务。系统级管控若应用有后台播放需求但未接入AVSession当应用退到后台或锁屏时系统会强制暂停音频播放或静音通话声音以防止恶意后台发声。统一管控与解耦AVSession在应用与控制器如播控中心、语音助手之间建立了一个标准化的“会话Session”。应用只需向系统注册播放状态和控制命令无需单独适配各种外设即可实现一次开发、处处可控。// BackgroundTaskManager.ets import { backgroundTaskManager } from kit.BackgroundTasksKit; import { wantAgent } from kit.AbilityKit; export class BackgroundTaskHelper { // 1. 在播放开始前申请 AUDIO_PLAYBACK 长时任务 public static async startAudioPlayback(context: Context) { try { let wantAgentObj await wantAgent.getWantAgent(context, { wants: [{ bundleName: context.abilityInfo.bundleName, abilityName: context.abilityInfo.name }], operationType: wantAgent.OperationType.START_ABILITIES, requestCode: 0, wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG] }); await backgroundTaskManager.startBackgroundRunning( context, AudioPlaybackTask, wantAgentObj, backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK ); } catch (err) { console.error(Failed to start background running:, err); } } // 2. 播放结束或暂停时释放长时任务 public static async stopAudioPlayback(context: Context) { try { await backgroundTaskManager.stopBackgroundRunning(context, AudioPlaybackTask); } catch (err) { console.error(Failed to stop background running:, err); } } }二、 接入流程与状态同步创建并激活会话根据业务场景选择正确的会话类型如audio对应音乐video对应视频voice_call对应通话这决定了播控中心展示的按钮样式。创建后必须调用activate()激活会话。设置元数据Metadata通过setAVMetadata上报当前媒体的标题、歌手、专辑封面、时长等信息这些信息会实时展示在锁屏界面和播控中心。注册控制命令与状态同步应用需注册播放、暂停、上一首、下一首等回调以响应外部控制。同时当应用内部状态改变时必须通过setAVPlaybackState及时同步给系统确保播控中心的进度条和播放状态与 App 完全一致。// AVSessionManager.ets import { avSession } from kit.AVSessionKit; export class AVSessionManager { private session: avSession.AVSession | null null; // 严格遵循初始化顺序创建 - 设置元数据 - 注册命令 - 激活 public async initSession(context: Context) { // 1. 创建并激活会话 this.session await avSession.createAVSession(context, MyMusicSession, audio); // 2. 设置元数据Metadata const metadata: avSession.AVMetadata { title: 鸿蒙星河交响曲, artist: Harmony Band, mediaImage: https://example.com/cover.jpg, duration: 240000 // 毫秒 }; await this.session.setAVMetadata(metadata); // 3. 注册控制命令回调 this.session.on(play, () { console.info(Received play command); // 调用内部 AVPlayer 的 play() 方法 }); this.session.on(pause, () { console.info(Received pause command); // 调用内部 AVPlayer 的 pause() 方法 }); this.session.on(playNext, () { console.info(Received next track command); }); // 4. 激活会话 this.session.activate(); } // 状态同步当内部播放器状态改变时调用 public async syncPlaybackState(isPlaying: boolean, elapsedTime: number) { if (!this.session) return; const playbackState: avSession.AVPlaybackState { state: isPlaying ? avSession.PlaybackState.PLAYBACK_STATE_PLAY : avSession.PlaybackState.PLAYBACK_STATE_PAUSE, position: { elapsedTime: elapsedTime, updateTime: new Date().getTime() } }; await this.session.setAVPlaybackState(playbackState); } }三、 性能优化会话生命周期管理AVSession对象在后台播放期间必须保持存活。严禁将其定义在页面组件内部否则页面销毁会导致 Session 释放后台播放随即被系统掐断。应将其挂载到全局变量或单例中。严格的初始化顺序接入流程必须遵循设置元数据 - 注册控制命令 - 激活 Session。如果先激活再注册可能会导致播控中心界面显示不全或按钮置灰。配置防错在module.json5中声明后台任务模式时backgroundModes必须准确填写为audio写成music或media会导致后台播放静默失效且不会报错。// GlobalMediaService.ets import { Context } from kit.AbilityKit; import { AVSessionManager } from ./AVSessionManager; import { BackgroundTaskHelper } from ./BackgroundTaskManager; // 核心避坑严禁将 AVSession 定义在页面组件内部必须挂载到全局单例 export class GlobalMediaService { private static instance: GlobalMediaService; public avSessionManager: AVSessionManager; private context: Context; private constructor(context: Context) { this.context context; this.avSessionManager new AVSessionManager(); } public static getInstance(context: Context): GlobalMediaService { if (!this.instance) { this.instance new GlobalMediaService(context); } return this.instance; } // 会话销毁与资源释放规范 public async destroySession() { // 1. 取消长时任务 await BackgroundTaskHelper.stopAudioPlayback(this.context); // 2. 销毁 AVSession if (this.avSessionManager) { // 内部需调用 session.off() 取消所有监听然后 session.destroy() await this.avSessionManager.destroy(); } } }四、 多模设备交互与外设按键适配AVSession 的核心价值之一在于屏蔽底层硬件差异让应用能够无缝响应各种外设的控制指令。蓝牙耳机与有线耳机适配应用无需单独监听蓝牙或有线耳机的媒体按键事件。只要正确接入了 AVSession 并注册了相应的控制指令系统会自动将耳机的按键事件如播放/暂停、上下一首、佩戴检测等转换为 AVSession 的控制指令发送给应用。进度条拖拽控制对于支持进度调整的场景应用需注册seek控制命令。当用户在锁屏界面或播控中心拖动进度条时应用会收到目标时间戳的回调。此时建议先将播放状态上报为BUFFERING缓冲中待应用内播放器完成 Seek 操作后再同步更新真实的播放状态与进度。循环模式与收藏状态同步播控中心支持展示循环模式和收藏状态。应用需在收到setLoopMode等指令后更新内部播放器状态并立即通过setAVPlaybackState将最新的loopMode或isFavorite状态上报给系统确保 UI 表现一致。// AVSessionControlHandler.ets import { avSession } from kit.AVSessionKit; export class AVSessionControlHandler { private session: avSession.AVSession | null null; constructor(session: avSession.AVSession) { this.session session; this.registerCommands(); } private registerCommands() { if (!this.session) return; // 1. 进度条拖拽控制 (Seek) this.session.on(seek, async (position: number) { console.info(Received seek command to position: ${position}ms); // 建议先上报缓冲状态防止播控中心进度条跳动 await this.session?.setAVPlaybackState({ state: avSession.PlaybackState.PLAYBACK_STATE_BUFFERING }); // TODO: 调用内部播放器执行 seek 操作 // await internalPlayer.seek(position); // 操作完成后同步真实播放状态 await this.updatePlaybackState(true, position); }); // 2. 循环模式切换 this.session.on(setLoopMode, async (mode: avSession.LoopMode) { console.info(Received setLoopMode: ${mode}); // TODO: 更新内部播放器的循环模式 // internalPlayer.setLoopMode(mode); // 立即同步状态给系统播控中心 await this.session?.setAVPlaybackState({ loopMode: mode }); }); // 3. 收藏状态同步 this.session.on(toggleFavorite, async (isFavorite: boolean) { console.info(Received toggleFavorite: ${isFavorite}); // TODO: 执行数据库收藏/取消收藏操作 // 同步状态 await this.session?.setAVPlaybackState({ isFavorite: isFavorite }); }); } public async updatePlaybackState(isPlaying: boolean, elapsedTime: number) { if (!this.session) return; await this.session.setAVPlaybackState({ state: isPlaying ? avSession.PlaybackState.PLAYBACK_STATE_PLAY : avSession.PlaybackState.PLAYBACK_STATE_PAUSE, position: { elapsedTime: elapsedTime, updateTime: new Date().getTime() } }); } }五、进阶场景投播与虚拟扩展屏在鸿蒙生态中音视频播放不仅限于本地设备还涉及跨设备的协同体验。投播长时任务检测当应用进行音视频投播将本端音视频投至远端设备时即使应用退至后台只要音视频播放或投屏业务其一正常运行系统就不会终止AUDIO_PLAYBACK长时任务。扩展屏设备监听通过 AVSession 提供的getAllCastDisplays()接口应用可以获取当前系统中所有支持扩展屏投播的显示设备。结合castDisplayChange事件监听应用能在检测到远端设备连接或断开时动态启动或停止扩展屏上的 UIAbility 绘制实现双屏联动。// CastDisplayManager.ets import { avSession } from kit.AVSessionKit; import { common } from kit.AbilityKit; export class CastDisplayManager { private session: avSession.AVSession | null null; public async initCastDisplay(context: common.UIAbilityContext) { // 1. 创建视频类型的 AVSession this.session await avSession.createAVSession(context, CastDisplay, video); // 2. 监听扩展屏设备连接/断开状态变化 this.session.on(castDisplayChange, (info: avSession.CastDisplayInfo) { if (info.state avSession.CastDisplayState.STATE_ON) { console.info(扩展屏已连接启动大屏 UIAbility); // 启动扩展屏的 UIAbility传入 displayId context.startAbility({ bundleName: context.abilityInfo.bundleName, abilityName: CastScreenAbility }, { displayId: info.id }); } else if (info.state avSession.CastDisplayState.STATE_OFF) { console.info(扩展屏已断开停止大屏业务); // TODO: 停止扩展屏业务或回退到本地播放 } }); // 3. 主动获取当前可用的扩展屏设备 const displays await this.session.getAllCastDisplays(); if (displays.length 0) { console.info(发现 ${displays.length} 个可用扩展屏); } } }六、 资源释放与异常处理规范健壮的生命周期管理是防止内存泄漏和系统异常的关键。会话销毁规范当应用完全退出或不再有任何音视频播放业务时必须严格按照顺序释放资源先调用deactivate()停用会话再调用destroy()彻底销毁 Session 对象同时取消已申请的后台长时任务。暂停事件与任务解绑应用应设置监听音频暂停事件on(pause)。如果收到系统下发的暂停指令且后续不再需要继续播放推荐主动取消已申请的AUDIO_PLAYBACK长时任务以节省系统资源。防冻结机制即使申请了长时任务如果设备长时间没有有效的音频流输出应用仍可能被系统冻结。因此确保 AVPlayer 的播放状态与 AVSession 的上报状态保持绝对同步是维持后台保活的核心前提。// AVSessionCleanup.ets import { avSession } from kit.AVSessionKit; import { backgroundTaskManager } from kit.BackgroundTasksKit; export class AVSessionCleanup { // 严格的资源释放规范 public static async safeDestroy( session: avSession.AVSession, context: Context, taskId: string ) { try { // 1. 取消所有已注册的控制命令监听器防止内存泄漏 session.off(play); session.off(pause); session.off(seek); session.off(setLoopMode); session.off(toggleFavorite); session.off(castDisplayChange); // 2. 停用并销毁会话 await session.deactivate(); await session.destroy(); // 3. 释放后台长时任务 await backgroundTaskManager.stopBackgroundRunning(context, taskId); console.info(AVSession 资源安全释放完毕); } catch (err) { console.error(AVSession 资源释放失败:, err); } } }