鸿蒙新特性实战:AVSession 媒体会话 — 锁屏控制/元数据/播放状态系统级集成

鸿蒙新特性实战:AVSession 媒体会话 — 锁屏控制/元数据/播放状态系统级集成
引言你有没有注意过在 HarmonyOS 手机上播放音乐时锁屏界面会自动显示专辑封面、歌曲名称和播放控制按钮下拉控制中心也能看到正在播放的媒体信息并控制暂停/切歌这背后驱动一切的就是AVSessionAudio Video SessionKit。AVSession 是 HarmonyOS 系统级的媒体会话管理框架。应用创建一个 AVSession 并设置元数据歌名、歌手、专辑封面和播放状态播放中/已暂停/进度位置系统会自动将信息展示在锁屏卡片、控制中心媒体面板等位置。更强大的是用户在这些系统界面上的操作点击播放/暂停/上一首/下一首/拖动进度条会以事件回调的方式回传给应用——你不需要自己绘制锁屏 UI系统已经替你做好了。本文构建一个媒体会话实验室Demo模拟一个本地音乐播放器的 AVSession 集成创建媒体会话、设置专辑/歌手/时长元数据、同步播放状态、监听系统控制指令play/pause/seek/playNext/playPrevious、切换曲目并自动更新锁屏展示。读完本文你将掌握创建与生命周期createAVSession()→activate()→deactivate()→destroy()完整流程元数据管理setAVMetadata()设置歌名/歌手/专辑/时长播放状态同步setAVPlaybackState()同步播放/暂停状态与进度位置系统指令监听on(play/pause/seek/playNext/playPrevious)接收锁屏/控制中心操作与音频播放器的解耦关系AVSession 只管理会话状态不管理音频流一、AVSession 体系总览1.1 是什么 vs 不是什么AVSession 是什么一个会话管理器在应用和系统之间建立关于当前正在播放什么的通信通道负责将媒体元数据和播放状态同步到系统 UI锁屏、控制中心、通知栏负责将从系统 UI 接收到的控制指令回传给应用AVSession 不是什么它不播放音频——实际的音频播放需要ohos.multimedia.media(AVPlayer) 或ohos.multimedia.sound(SoundPool)它不渲染 UI——锁屏和控制中心的 UI 是系统渲染的你只提供数据它不管理音频焦点——音频焦点管理是 AVPlayer / AudioManager 的职责1.2 系统级集成架构┌──────────────────┐ 同步元数据/状态 ┌──────────────┐ │ 应用 (你的App) │ ──────────────────→ │ AVSession │ │ │ ←────────────────── │ 系统服务 │ │ 媒体会话实验室 │ 接收控制指令 └──────┬───────┘ └──────────────────┘ │ │ 展示到系统UI ┌──────┴───────┐ │ 锁屏卡片 │ │ 控制中心面板 │ │ 通知栏播放器 │ └──────────────┘1.3 核心 API 速查API说明返回值avSession.createAVSession(context, tag, type)创建会话实例PromiseAVSessionsession.activate()激活会话系统 UI 开始展示Promisevoidsession.deactivate()停用会话从系统 UI 移除Promisevoidsession.destroy()销毁会话释放资源Promisevoidsession.setAVMetadata(metadata)设置歌曲名/歌手/专辑等Promisevoidsession.setAVPlaybackState(state)同步播放状态播放/暂停/进度Promisevoidsession.on(play, callback)监听系统发来的播放指令voidsession.on(pause, callback)监听系统发来的暂停指令voidsession.on(stop, callback)监听系统发来的停止指令voidsession.on(playNext, callback)监听系统发来的下一首指令voidsession.on(playPrevious, callback)监听系统发来的上一首指令voidsession.on(seek, callback)监听系统发来的跳转指令void关键限制同一时间一个应用只能有一个激活状态的 AVSession可以创建多个但只能激活一个activate()必须在createAVSession()之后调用否则系统 UI 不会展示监听回调必须在createAVSession()成功之后、activate()之前注册以确保不丢失任何事件二、创建与生命周期管理2.1 createAVSession() — 创建会话import{avSession}fromkit.AVSessionKit;avSession.createAVSession(getContext(this),media_lab,audio).then((session){this.sessionsession;// 此时会话已创建但尚未激活}).catch((err:Error){console.error(创建失败: err.message);});三个参数context应用上下文通过getContext(this)获取tag会话标识字符串用于调试和多会话管理type会话类型audio音频或video视频注意createAVSession是异步操作必须等待 Promise resolve 后拿到AVSession实例才能进行后续操作。2.2 activate() / deactivate() — 激活与停用// 激活 — 系统 UI 开始展示该会话session.activate().then((){// 锁屏/控制中心现在能看到当前播放的媒体信息}).catch((err:Error){...});// 停用 — 从系统 UI 移除session.deactivate().then((){// 锁屏/控制中心不再展示该会话}).catch((err:Error){...});关键行为activate()是让 AVSession对用户可见的操作。元数据和状态必须在activate()之前或之后通过setAVMetadata()/setAVPlaybackState()设置deactivate()后可以再次调用activate()重新激活不需要重新创建应用切换到后台时AVSession 会保持激活状态这正是它的核心价值——锁屏可见2.3 destroy() — 销毁session.destroy().then((){this.sessionnull;}).catch((err:Error){...});调用destroy()后AVSession 实例不可再用系统 UI 自动移除。通常在应用退出或用户停止播放时调用。完整生命周期流程createAVSession() → 注册事件监听器 → setAVMetadata() → activate() ↓ 用户操作 ← setAVPlaybackState() ← 系统事件回调 → setAVMetadata(新曲目) ↓ 停止播放 → deactivate() → destroy()三、元数据管理 — AVMetadata3.1 设置元数据letmetadata:avSession.AVMetadata{assetId:track_1,// 媒体资源 ID唯一标识title:月光曲,// 歌曲名锁屏主标题artist:贝多芬,// 歌手名锁屏副标题album:古典精选集,// 专辑名duration:240000// 总时长毫秒用于进度条展示};session.setAVMetadata(metadata).then((){...});3.2 AVMetadata 主要字段字段类型必填说明assetIdstring是唯一标识当前媒体资源titlestring推荐歌名/视频标题锁屏第一行artiststring推荐歌手/作者锁屏第二行albumstring否专辑名durationnumber推荐总时长毫秒影响进度条范围authorstring否作者与 artist 可不同descriptionstring否媒体描述subtitlestring否副标题avQueueArrayAVQueueItem否播放列表信息最佳实践至少设置assetId、title、artist、duration这样系统 UI 才能呈现完整的播放信息。3.3 更新元数据——切歌时调用当用户切换到下一首时应立即调用setAVMetadata()更新新歌曲的信息nextTrack():void{this.currentTrackIndex(this.currentTrackIndex1)%this.tracks.length;this.currentPosition0;// 新歌曲从头开始this.updateMetadata();// 调用 setAVMetadata() 更新锁屏展示this.syncState();// 同步播放状态进度归零}四、播放状态同步 — AVPlaybackState4.1 设置播放状态letstate:avSession.AVPlaybackState{state:avSession.PlaybackState.PLAYBACK_STATE_PLAY,// 播放中position:{elapsedTime:45000,// 当前已播放 45 秒updateTime:Date.now()// 更新时间戳用于系统推算进度条},speed:1.0// 播放速度1.0 正常};session.setAVPlaybackState(state);4.2 AVPlaybackState 核心字段字段类型说明stateavSession.PlaybackState播放状态枚举position{ elapsedTime, updateTime }播放进度信息speednumber播放速度1.0 正常2.0 两倍速4.3 PlaybackState 枚举值枚举值含义系统 UI 表现PLAYBACK_STATE_INITIAL初始状态刚创建不显示PLAYBACK_STATE_PREPARE准备中加载数据显示缓冲指示PLAYBACK_STATE_PLAY播放中显示暂停按钮PLAYBACK_STATE_PAUSE已暂停显示播放按钮PLAYBACK_STATE_STOP已停止从系统 UI 移除播放控制PLAYBACK_STATE_FAST_FORWARD快进中显示快进状态PLAYBACK_STATE_REWIND快退中显示快退状态4.4 position 字段详解position决定了系统 UI尤其是锁屏中进度条的显示位置position:{elapsedTime:this.currentPosition,// 当前播放到的位置毫秒updateTime:Date.now()// 此位置的记录时间戳}为什么需要updateTime系统用elapsedTime (当前时间 - updateTime) × speed来计算实时的进度条位置。这样即使应用不频繁更新进度条也能平滑前进。但在本 Demo 中为了简化我们直接用秒级定时器更新elapsedTime。五、系统控制指令监听这是 AVSession 最强大的特性——用户无需打开你的应用直接在锁屏界面或控制中心操作系统会将指令转发给你的会话// 在 createAVSession() 成功后注册session.on(play,(){this.isPlayingtrue;this.startTimer();this.syncState();});session.on(pause,(){this.isPlayingfalse;this.stopTimer();this.syncState();});session.on(stop,(){this.isPlayingfalse;this.stopTimer();this.currentPosition0;this.syncState();});session.on(playNext,(){this.nextTrack();});session.on(playPrevious,(){this.prevTrack();});session.on(seek,(seekTime:number){this.currentPositionseekTime;// seekTime 是系统传来的目标位置毫秒this.syncState();});关键说明所有事件回调都在主线程执行不需要手动切换线程seek事件的回调参数是目标时间毫秒而非偏移量收到playNext/playPrevious后必须更新元数据setAVMetadata()否则锁屏仍显示旧歌曲信息如果应用不支持某些指令如不支持快进可以不注册对应事件——系统会隐藏相关按钮六、实战 Demo媒体会话实验室页面结构媒体会话实验室 ├── 标题栏 — 媒体会话实验室 ohos.multimedia.avsession ├── 会话状态卡片 │ ├── 状态指示器未创建 / 已创建 / 已激活 / 已停用 / 已销毁 │ └── 四个生命周期按钮创建 / 激活 / 停用 / 销毁 ├── 正在播放卡片模拟音乐播放器 │ ├── 专辑图占位块彩色方块颜色随曲目变化 │ ├── 歌曲名 歌手 专辑名 │ ├── 进度条Slider可拖动 seek │ ├── 进度时间显示当前 / 总时长 │ └── 三按钮控制上一首 / 播放暂停 / 下一首 ├── 播放列表5 首模拟曲目 │ ├── 每行显示封面色块 歌名 歌手 时长 │ ├── 当前播放项高亮紫色加粗 │ └── 点击可切换曲目 ├── 事件日志时间 来源 详情 │ ├── 蓝色 系统锁屏/控制中心指令 │ ├── 绿色 App应用内操作 │ └── 红色 错误 └── 核心 API 参考4 个交互点会话生命周期管理— 四个按钮控制完整的 create → activate → deactivate → destroy 流程状态指示器实时反馈当前阶段播放控制双通道— 应用内点击播放/暂停/上一首/下一首按钮以及从锁屏/控制中心接收指令两个通道的操作都被记录到事件日志中以颜色区分来源曲目切换与元数据同步— 点击播放列表中的曲目切换当前歌曲AVMetadata 自动更新到系统 UI播放到末尾自动切到下一首进度拖动与实时同步— 拖动进度条滑块 seek 到指定位置播放状态同步到系统 UI秒级定时器自动推进播放进度并每 10 秒全量同步核心代码实现创建会话并注册事件createSession():void{avSession.createAVSession(getContext(this),media_lab,audio).then((session){this.sessionsession;this.addLog(系统,AVSession 创建成功);session.on(play,(){this.isPlayingtrue;this.startTimer();this.syncState();this.addLog(系统,收到 Play 指令 (锁屏/控制中心));});session.on(pause,(){this.isPlayingfalse;this.stopTimer();this.syncState();this.addLog(系统,收到 Pause 指令 (锁屏/控制中心));});session.on(playNext,(){this.nextTrack();this.addLog(系统,收到 PlayNext 指令 (锁屏/控制中心));});session.on(playPrevious,(){this.prevTrack();this.addLog(系统,收到 PlayPrevious 指令 (锁屏/控制中心));});session.on(seek,(seekTime:number){this.currentPositionseekTime;this.syncState();this.addLog(系统,收到 Seek 指令 → this.formatTime(seekTime));});this.updateMetadata();this.syncState();}).catch((err:Error){this.addLog(错误,创建失败: err.message);});}事件注册必须在createAVSession()成功后、activate()之前完成。播放状态同步syncState():void{if(this.sessionnull)return;letstate:avSession.AVPlaybackState{state:this.isPlaying?avSession.PlaybackState.PLAYBACK_STATE_PLAY:avSession.PlaybackState.PLAYBACK_STATE_PAUSE};if(this.currentPosition0||this.isPlaying){state.position{elapsedTime:this.currentPosition,updateTime:Date.now()};state.speed1.0;}this.session.setAVPlaybackState(state);}根据isPlaying决定状态是 PLAY 还是 PAUSE。只有在有播放进度时才设置position字段避免误导系统。曲目切换nextTrack():void{this.stopTimer();this.isPlayingfalse;this.currentTrackIndex(this.currentTrackIndex1)%this.tracks.length;this.currentPosition0;this.updateMetadata();// 更新锁屏展示的歌曲信息this.syncState();// 状态改为暂停、进度归零}updateMetadata():void{lettrackthis.tracks[this.currentTrackIndex];letmetadata:avSession.AVMetadata{assetId:track_this.currentTrackIndex,title:track.title,artist:track.artist,album:track.album,duration:track.duration};this.session!.setAVMetadata(metadata);}模拟播放进度startTimer():void{this.stopTimer();this.timersetInterval((){lettrackthis.tracks[this.currentTrackIndex];this.currentPositionthis.currentPosition1000;// 每秒1秒if(this.currentPositiontrack.duration){// 播放完毕自动切到下一首this.currentPosition0;this.currentTrackIndex(this.currentTrackIndex1)%this.tracks.length;this.updateMetadata();this.syncState();}// 每 10 秒全量同步一次减少不必要的系统调用if(this.currentPosition%100000){this.syncState();}},1000);}预览效果预期创建会话→ 状态灯变黄“已创建”事件日志显示AVSession 创建成功激活会话→ 状态灯变绿“已激活”此时下拉控制中心可看到媒体播放面板显示当前歌曲信息点击播放→ 状态变为播放中控制中心出现暂停按钮应用内进度条开始每秒推进锁屏操作→ 锁屏界面显示歌曲名和播放控制点击下一首后应用自动切歌并更新锁屏信息事件日志以蓝色记录收到 PlayNext 指令拖动进度→ 拖动应用内进度条调用seekTo()播放位置跳转到对应时间点并同步到系统 UI七、实战技巧与常见问题7.1 为什么锁屏没有显示我的媒体信息按顺序排查是否已调用activate()仅createAVSession不会展示是否设置了title和artist系统和锁屏至少需要这两项是否设置了duration没有总时长无法显示进度条是否设置了有效的playbackState至少要是PLAY或PAUSE状态是否有其他应用也在使用 AVSession系统一次只展示一个会话7.2 AVSession 与音频播放器的关系AVSession不播放音频它只管理播放状态信息。典型的架构是应用层UI 按钮 → 播放状态管理AVSession State 中间层AVSession → 同步状态到系统 UI AVSession ← 接收系统 UI 操作 底层层AVPlayer / SoundPool → 实际解码和输出音频在这种架构下AVSession 和 AVPlayer 是解耦的——你可以在不改变音频流的情况下更新锁屏展示也可以在不改变锁屏的情况下调整音频参数。7.3 销毁时机的选择deactivate()用户停止播放但应用仍在运行如退出播放页面。方便用户下次打开时快速恢复destroy()用户彻底退出应用或退出登录时调用。释放资源八、与 Android MediaSession 的对比维度HarmonyOS AVSessionAndroid MediaSession创建方式createAVSession(ctx, tag, type)MediaSession(context, tag)播放状态avSession.PlaybackState枚举PlaybackStateCompat.Builder元数据AVMetadata对象字段直接赋值MediaMetadataCompat.Builder控制回调session.on(play/pause/...)MediaSessionCompat.Callback激活session.activate()session.setActive(true)事件监听字符串事件名 回调函数重写 Callback 抽象方法锁屏集成自动系统 UI 层需要 MediaStyle 通知自己构造HarmonyOS 的 AVSession API 设计更简洁——事件回调使用字符串事件名类似 Node.js EventEmitter而非重写抽象类。元数据和播放状态的设置也是直接赋值对象无需 Builder 模式。九、总结ohos.multimedia.avsession是 HarmonyOS 应用中实现系统级媒体集成的核心 API创建 → 激活 → 停用 → 销毁四步生命周期清晰简洁AVMetadata设置歌曲名/歌手/专辑/时长自动展示在锁屏和控制中心AVPlaybackState同步播放/暂停状态和进度位置系统 UI 实时响应事件监听让应用从锁屏/控制中心接收用户指令无需自建锁屏 UI与音频播放器解耦AVSession 只管理会话状态不管理音频流零额外权限的基础操作——创建、激活、元数据设置、状态同步均无需声明任何权限对于音乐播放器、播客、视频应用等任何需要媒体播放的场景AVSession 都是通往系统级用户体验的必经之路。