1# 媒体会话控制方(仅对系统应用开放) 2<!--Kit: AVSession Kit--> 3<!--Subsystem: Multimedia--> 4<!--Owner: @ccfriend; @liao_qian--> 5<!--Designer: @ccfriend--> 6<!--Tester: @chenmingxi1_huawei--> 7<!--Adviser: @zengyawen--> 8 9OpenHarmony系统预置的播控中心,作为媒体会话控制方与音视频应用进行交互,包括获取媒体信息进行展示以及下发播控命令等。 10 11系统应用开发者也可以根据需要,按照本章节的内容自行开发一款新的系统应用(例如新开发一款播控中心或语音助手),作为媒体会话控制方的角色,与系统中的音视频应用进行交互。 12 13## 基本概念 14 15- 媒体会话描述符(AVSessionDescriptor):描述媒体会话的相关信息,包含标识媒体会话的ID(sessionId),媒体会话的类型type(音频Audio/视频Video),媒体会话自定义名称(sessionTag),媒体会话所属应用的信息(elementName)、是否为置顶会话(isTopSession)等。 16 17- 置顶会话(TopSession):系统中优先级最高的媒体会话,例如当前处于正在播放状态的会话。一般来说,如果想与媒体会话通信,需要获取会话对应的控制器,而媒体会话控制方可以在不用获取对应控制器的情况下,直接与置顶会话通信,例如直接向置顶会话发送播控命令和按键事件。 18 19## 接口说明 20 21媒体会话控制方使用的关键接口包括两类: 22 231. 直接通过import得到的AVSessionManager来调用,例如接口`AVSessionManager.createController(sessionId)`。 242. 通过AVSessionController对象来调用,例如接口`controller.getAVPlaybackState()`。 25 26异步的JavaScript接口返回值有两种返回形式:callback和promise,本说明仅提供callback形式接口,promise和callback只是返回值方式不一样,功能相同。 27 28更多API说明请参见[API文档](../../reference/apis-avsession-kit/arkts-apis-avsession.md)。 29 30### 直接通过AVSessionManager调用的接口 31 32| 接口名 | 说明 | 33| -------- | -------- | 34| getAllSessionDescriptors(callback: AsyncCallback<Array<Readonly<AVSessionDescriptor>>>): void | 获取系统中所有媒体会话的描述符。 | 35| createController(sessionId: string, callback: AsyncCallback<AVSessionController>): void | 创建媒体会话控制器。 | 36| sendSystemAVKeyEvent(event: KeyEvent, callback: AsyncCallback<void>): void | 向置顶会话发送按键命令。 | 37| sendSystemControlCommand(command: AVControlCommand, callback: AsyncCallback<void>): void | 向置顶会话发送播控命令。 | 38| getHistoricalSessionDescriptors(maxSize: number, callback: AsyncCallback\<Array\<Readonly\<AVSessionDescriptor>>>): void<sup>10+<sup> | 获取历史会话的描述符。 | 39 40### 通过AVSessionController对象调用的接口 41 42| 接口名 | 说明 | 43| -------- | -------- | 44| getAVPlaybackState(callback: AsyncCallback<AVPlaybackState>): void<sup>10+<sup> | 获取当前会话播放状态相关信息。 | 45| getAVMetadata(callback: AsyncCallback<AVMetadata>): void<sup>10+<sup> | 获取会话元数据。 | 46| getOutputDevice(callback: AsyncCallback<OutputDeviceInfo>): void<sup>10+<sup> | 获取播放设备信息。 | 47| sendAVKeyEvent(event: KeyEvent, callback: AsyncCallback<void>): void<sup>10+<sup> | 发送按键事件到会话。| 48| getLaunchAbility(callback: AsyncCallback<WantAgent>): void<sup>10+<sup> | 获取应用在会话中保存的WantAgent对象。 | 49| isActive(callback: AsyncCallback<boolean>): void<sup>10+<sup> | 判断会话是否被激活。 | 50| destroy(callback: AsyncCallback<void>): void<sup>10+<sup> | 销毁当前控制器,销毁后当前控制器不再可用。 | 51| getValidCommands(callback: AsyncCallback<Array<AVControlCommandType>>): void<sup>10+<sup> | 获取会话支持的有效命令。 | 52| sendControlCommand(command: AVControlCommand, callback: AsyncCallback<void>): void<sup>10+<sup> | 通过会话控制器发送命令到其对应的会话。 | 53| sendCommonCommand(command: string, args: {[key: string]: Object}, callback: AsyncCallback<void>): void<sup>10+<sup> | 通过会话控制器发送自定义命令到其对应的会话。 | 54| getAVQueueItems(callback: AsyncCallback<Array<AVQueueItem>>): void<sup>10+<sup> | 获取当前播放列表相关信息。 | 55| getAVQueueTitle(callback: AsyncCallback<string>): void<sup>10+<sup> | 获取当前播放列表的名称。 | 56| skipToQueueItem(itemId: number, callback: AsyncCallback<void>): void<sup>10+<sup> | 设置指定播放列表单项的ID,发送给session端处理,session端可以选择对这个单项歌曲进行播放。 | 57| getExtras(callback: AsyncCallback<{[key: string]: Object}>): void<sup>10+<sup> | 获取媒体提供方设置的自定义媒体数据包。 | 58| getOutputDeviceSync(): OutputDeviceInfo<sup>10+<sup> | 使用同步方法获取当前输出设备信息。 | 59| getAVPlaybackStateSync(): AVPlaybackState<sup>10+<sup> | 使用同步方法获取当前会话播放状态相关信息。 | 60| getAVMetadataSync(): AVMetadata<sup>10+<sup> | 使用同步方法获取当前会话元数据信息。 | 61| getAVQueueTitleSync(): string<sup>10+<sup> | 使用同步方法当前播放列表的名称。 | 62| getAVQueueItemsSync(): Array<AVQueueItem><sup>10+<sup> | 使用同步方法获取当前播放列表相关信息。 | 63| isActiveSync(): boolean<sup>10+<sup> | 使用同步方法判断会话是否被激活。 | 64| getValidCommandsSync(): Array<AVControlCommandType><sup>10+<sup> | 使用同步方法获取会话支持的有效命令。 | 65 66## 开发步骤 67 68系统应用作为媒体会话控制方接入媒体会话的基本步骤如下所示: 69 701. 通过AVSessionManager获取媒体会话描述符AVSessionDescriptor,创建媒体会话控制器AVSessionController。 71 媒体会话控制方可以获取当前系统中所有的AVSessionDescriptor,并创建每个会话对应的AVSessionController,从而对系统中的音视频应用进行统一的播放控制。 72 73 ```ts 74 //导入AVSessionManager模块。 75 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 76 import { BusinessError } from '@kit.BasicServicesKit'; 77 78 // 全局变量定义。 79 let g_controller = new Array<AVSessionManager.AVSessionController>(); 80 let g_centerSupportCmd:Set<AVSessionManager.AVControlCommandType> = new Set(['play', 'pause', 'playNext', 'playPrevious', 'fastForward', 'rewind', 'seek','setSpeed', 'setLoopMode', 'toggleFavorite']); 81 let g_validCmd:Set<AVSessionManager.AVControlCommandType>; 82 // 获取会话描述符,创建控制器。 83 AVSessionManager.getAllSessionDescriptors().then((descriptors) => { 84 descriptors.forEach((descriptor) => { 85 AVSessionManager.createController(descriptor.sessionId).then((controller) => { 86 g_controller.push(controller); 87 }).catch((err: BusinessError) => { 88 console.error(`Failed to create controller. Code: ${err.code}, message: ${err.message}`); 89 }); 90 }); 91 }).catch((err: BusinessError) => { 92 console.error(`Failed to get all session descriptors. Code: ${err.code}, message: ${err.message}`); 93 }); 94 95 // 获取历史会话的描述符。 96 AVSessionManager.getHistoricalSessionDescriptors().then((descriptors) => { 97 console.info(`getHistoricalSessionDescriptors : SUCCESS : descriptors.length : ${descriptors.length}`); 98 if (descriptors.length > 0){ 99 console.info(`getHistoricalSessionDescriptors : SUCCESS : descriptors[0].isActive : ${descriptors[0].isActive}`); 100 console.info(`getHistoricalSessionDescriptors : SUCCESS : descriptors[0].type : ${descriptors[0].type}`); 101 console.info(`getHistoricalSessionDescriptors : SUCCESS : descriptors[0].sessionTag : ${descriptors[0].sessionTag}`); 102 console.info(`getHistoricalSessionDescriptors : SUCCESS : descriptors[0].sessionId : ${descriptors[0].sessionId}`); 103 console.info(`getHistoricalSessionDescriptors : SUCCESS : descriptors[0].elementName.bundleName : ${descriptors[0].elementName.bundleName}`); 104 } 105 }).catch((err: BusinessError) => { 106 console.error(`Failed to get historical session descriptors, error code: ${err.code}, error message: ${err.message}`); 107 }); 108 ``` 109 1102. 监听AVSession会话状态及AVSession服务状态事件。 111 112 AVSession会话状态事件包括: 113 114 - sessionCreate:媒体会话创建事件。 115 - sessionDestroy:媒体会话销毁事件。 116 - topSessionChange:置顶会话发生变化事件。 117 118 AVSession服务状态事件指sessionServiceDie,在AVSession服务异常时产生该事件。 119 120 ```ts 121 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 122 import { BusinessError } from '@kit.BasicServicesKit'; 123 124 let g_controller = new Array<AVSessionManager.AVSessionController>(); 125 // 注册会话创建监听,创建控制器。 126 AVSessionManager.on('sessionCreate', (session) => { 127 // 新增会话,需要创建控制器。 128 AVSessionManager.createController(session.sessionId).then((controller) => { 129 g_controller.push(controller); 130 }).catch((err: BusinessError) => { 131 console.error(`Failed to create controller. Code: ${err.code}, message: ${err.message}`); 132 }); 133 }); 134 135 // 注册系统会话销毁监听。 136 AVSessionManager.on('sessionDestroy', (session) => { 137 let index = g_controller.findIndex((controller) => { 138 return controller.sessionId === session.sessionId; 139 }); 140 if (index !== 0) { 141 g_controller[index].destroy(); 142 g_controller.splice(index, 1); 143 } 144 }); 145 // 注册系统最高优先级会话变更监听。 146 AVSessionManager.on('topSessionChange', (session) => { 147 let index = g_controller.findIndex((controller) => { 148 return controller.sessionId === session.sessionId; 149 }); 150 // 将该会话显示排到第一个。 151 if (index !== 0) { 152 g_controller.sort((a, b) => { 153 return a.sessionId === session.sessionId ? -1 : 0; 154 }); 155 } 156 }); 157 // 注册服务异常监听。 158 AVSessionManager.on('sessionServiceDie', () => { 159 // 服务端异常,应用清理资源。 160 console.info(`服务端异常`); 161 }) 162 ``` 163 1643. 监听媒体信息变化及会话其他事件。 165 166 AVSession媒体信息变化事件主要包括: 167 168 - metadataChange:媒体会话元数据变化事件。 169 - playbackStateChange:媒体播放状态变化事件。 170 - activeStateChange:媒体会话激活状态变化事件。 171 - validCommandChange:媒体会话支持的有效命令变化事件。 172 - outputDeviceChange:播放设备变化事件。 173 - sessionDestroy:媒体会话销毁事件。 174 - sessionEvent:媒体会话自定义事件变化事件。 175 - extrasChange:媒体会话自定义数据包变化事件。 176 - queueItemsChange:媒体会话自定义播放列表变化事件。 177 - queueTitleChange:媒体会话自定义播放列表的名称变化事件。 178 179 媒体会话控制方可以根据实际需要监听对应的事件。 180 181 ```ts 182 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 183 import { BusinessError } from '@kit.BasicServicesKit'; 184 185 let g_controller = new Array<AVSessionManager.AVSessionController>(); 186 if (g_controller && g_controller.length > 0 && g_controller[0]) { 187 let controller = g_controller[0]; 188 let g_validCmd:Set<AVSessionManager.AVControlCommandType>; 189 let g_centerSupportCmd:Set<AVSessionManager.AVControlCommandType> = new Set(['play', 'pause', 'playNext', 'playPrevious', 'fastForward', 'rewind', 'seek','setSpeed', 'setLoopMode', 'toggleFavorite']); 190 // 注册会话激活状态变更监听。 191 controller.on('activeStateChange', (isActive) => { 192 if (isActive) { 193 console.info(`控制器卡片按键高亮`); 194 } else { 195 console.info(`控制器卡片按键变更为无效`); 196 } 197 }); 198 // 注册会话销毁监听。 199 controller.on('sessionDestroy', () => { 200 console.info(`on sessionDestroy : SUCCESS `); 201 controller.destroy().then(() => { 202 console.info(`destroy : SUCCESS`); 203 }).catch((err: BusinessError) => { 204 console.error(`Failed to destroy session. Code: ${err.code}, message: ${err.message}`); 205 }); 206 }); 207 208 // 注册元数据更新监听。 209 controller.on('metadataChange', ['assetId', 'title', 'description'], (metadata: AVSessionManager.AVMetadata) => { 210 console.info(`on metadataChange assetId : ${metadata.assetId}`); 211 }); 212 // 注册播放状态更新监听。 213 controller.on('playbackStateChange', ['state', 'speed', 'loopMode'], (playbackState: AVSessionManager.AVPlaybackState) => { 214 console.info(`on playbackStateChange state : ${playbackState.state}`); 215 }); 216 // 注册会话支持的命令变更监听。 217 controller.on('validCommandChange', (cmds) => { 218 console.info(`validCommandChange : SUCCESS : size : ${cmds.length}`); 219 console.info(`validCommandChange : SUCCESS : cmds : ${Array.from(cmds)}`); 220 g_validCmd.clear(); 221 let centerSupportCmd = Array.from(g_centerSupportCmd.values()) 222 for (let c of centerSupportCmd) { 223 if (cmds.indexOf(c) != -1) { 224 g_validCmd.add(c); 225 } 226 } 227 }); 228 // 注册输出设备变更监听。 229 controller.on('outputDeviceChange', (state, device) => { 230 console.info(`outputDeviceChange device are : ${JSON.stringify(device)}`); 231 }); 232 // 注册会话自定义事件变更监听。 233 controller.on('sessionEvent', (eventName, eventArgs) => { 234 console.info(`Received new session event, event name is ${eventName}, args are ${JSON.stringify(eventArgs)}`); 235 }); 236 // 注册会话自定义媒体数据包变更监听。 237 controller.on('extrasChange', (extras) => { 238 console.info(`Received custom media packet, packet data is ${JSON.stringify(extras)}`); 239 }); 240 // 注册会话自定义播放列表变更监听。 241 controller.on('queueItemsChange', (items) => { 242 console.info(`Caught queue items change, items length is ${items.length}`); 243 }); 244 // 注册会话自定义播放标题变更监听。 245 controller.on('queueTitleChange', (title) => { 246 console.info(`Caught queue title change, title is ${title}`); 247 }); 248 } 249 ``` 250 2514. 获取媒体会话提供方传递的媒体信息,可以用于界面展示,例如在播控中心展示当前播放的曲目及对应的播放状态。 252 253 ```ts 254 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 255 async function getInfoFromSessionByController() { 256 // 假设已经有了一个对应session的controller,如何创建controller可以参考之前的案例。 257 let controller = await AVSessionManager.createController(""); 258 // 获取sessionId。 259 let sessionId = controller.sessionId; 260 console.info(`get sessionId by controller : isActive : ${sessionId}`); 261 // 获取session激活状态。 262 let isActive = await controller.isActive(); 263 console.info(`get activeState by controller : ${isActive}`); 264 // 获取session的媒体信息。 265 let metadata = await controller.getAVMetadata(); 266 console.info(`get media title by controller : ${metadata.title}`); 267 console.info(`get media artist by controller : ${metadata.artist}`); 268 // 获取session的播放信息。 269 let avPlaybackState = await controller.getAVPlaybackState(); 270 console.info(`get playbackState by controller : ${avPlaybackState.state}`); 271 console.info(`get favoriteState by controller : ${avPlaybackState.isFavorite}`); 272 // 获取session的播放列表信息。 273 let queueItems = await controller.getAVQueueItems(); 274 console.info(`get queueItems length by controller : ${queueItems.length}`); 275 // 获取session的播放标题信息。 276 let queueTitle = await controller.getAVQueueTitle(); 277 console.info(`get queueTitle by controller : ${queueTitle}`); 278 // 获取session的自定义媒体数据包。 279 let extras = await controller.getExtras(); 280 console.info(`get custom media packets by controller : ${extras ? JSON.stringify(extras) : ''}`); 281 // 获取session对应应用提供的ability信息。 282 let agent = await controller.getLaunchAbility(); 283 console.info(`get want agent info by controller : ${agent ? JSON.stringify(agent) : ''}`); 284 // 获取session的当前播放位置信息。 285 let currentTime = controller.getRealPlaybackPositionSync(); 286 console.info(`get current playback time by controller : ${currentTime}`); 287 // 获取session支持的有效命令。 288 let validCommands = await controller.getValidCommands(); 289 console.info(`get valid commands by controller : ${validCommands ? JSON.stringify(validCommands) : '[]'}`); 290 } 291 ``` 292 2935. 控制媒体会话行为,例如发送用户在播控中心对当前曲目的操作(播放/暂停/上一首/下一首等)命令。 294 295 作为媒体会话提供方的音视频应用在监听到相关的播控命令事件后,在相应的逻辑中可以完成对应的操作动作。 296 297 ```ts 298 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 299 import { BusinessError } from '@kit.BasicServicesKit'; 300 301 async function sendCommandToSessionByController() { 302 // 假设我们已经有了一个对应session的controller,如何创建controller可以参考之前的案例。 303 let controller = await AVSessionManager.createController(""); 304 // 获取这个session支持的命令种类。 305 let validCommandTypeArray = await controller.getValidCommands(); 306 console.info(`get validCommandArray by controller : length : ${validCommandTypeArray.length}`); 307 // 下发播放命令。 308 // 如果可用命令包含播放,则下发播放命令,正常session都应该提供并实现播放功能。 309 if (validCommandTypeArray.indexOf('play') >= 0) { 310 let avCommand: AVSessionManager.AVControlCommand = {command:'play'}; 311 controller.sendControlCommand(avCommand); 312 } 313 // 下发暂停命令。 314 if (validCommandTypeArray.indexOf('pause') >= 0) { 315 let avCommand: AVSessionManager.AVControlCommand = {command:'pause'}; 316 controller.sendControlCommand(avCommand); 317 } 318 // 下发上一首命令。 319 if (validCommandTypeArray.indexOf('playPrevious') >= 0) { 320 let avCommand: AVSessionManager.AVControlCommand = {command:'playPrevious'}; 321 controller.sendControlCommand(avCommand); 322 } 323 // 下发下一首命令。 324 if (validCommandTypeArray.indexOf('playNext') >= 0) { 325 let avCommand: AVSessionManager.AVControlCommand = {command:'playNext'}; 326 controller.sendControlCommand(avCommand); 327 } 328 // 下发自定义控制命令。 329 let commandName = 'custom command'; 330 await controller.sendCommonCommand(commandName, {command : 'This is my custom command'}).then(() => { 331 console.info(`SendCommonCommand successfully`); 332 }).catch((err: BusinessError) => { 333 console.error(`Failed to send common command. Code: ${err.code}, message: ${err.message}`); 334 }) 335 // 设置指定播放列表单项的ID,供session选择播放。 336 let queueItemId = 0; 337 await controller.skipToQueueItem(queueItemId).then(() => { 338 console.info(`SkipToQueueItem successfully`); 339 }).catch((err: BusinessError) => { 340 console.error(`Failed to skip to queue item. Code: ${err.code}, message: ${err.message}`); 341 }); 342 } 343 ``` 344 3456. 在媒体会话控制方应用退出时及时取消事件监听,并释放资源。 346 347 ```ts 348 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 349 import { BusinessError } from '@kit.BasicServicesKit'; 350 351 async function destroyController() { 352 // 假设我们已经有了一个对应session的controller,如何创建controller可以参考之前的案例。 353 let controller = await AVSessionManager.createController(""); 354 355 // 销毁当前的controller,销毁后这个controller将不在可用。 356 controller.destroy((err: BusinessError) => { 357 if (err) { 358 console.error(`Failed to destroy controller. Code: ${err.code}, message: ${err.message}`); 359 } else { 360 console.info(`Destroy controller SUCCESS`); 361 } 362 }); 363 } 364 ``` 365 366## 相关实例 367 368针对媒体会话控制方开发,有以下相关实例可供参考: 369 370- [媒体会话——控制方(仅对系统应用开放)(ArkTS)(Full SDK)(API10)](https://gitcode.com/openharmony/applications_app_samples/tree/master/code/SystemFeature/Media/AVSession/MediaController)