1# 媒体会话提供方 2 3音视频应用在实现音视频功能的同时,需要作为媒体会话提供方接入媒体会话,在媒体会话控制方(例如播控中心)中展示媒体相关信息,及响应媒体会话控制方下发的播控命令。 4 5## 基本概念 6 7- 媒体会话元数据(AVMetadata): 用于描述媒体数据相关属性,包含标识当前媒体的ID(assetId),上一首媒体的ID(previousAssetId),下一首媒体的ID(nextAssetId),标题(title),专辑作者(author),专辑名称(album),词作者(writer),媒体时长(duration)等属性。 8 9- 媒体播放状态(AVPlaybackState):用于描述媒体播放状态的相关属性,包含当前媒体的播放状态(state)、播放位置(position)、播放倍速(speed)、缓冲时间(bufferedTime)、循环模式(loopMode)、是否收藏(isFavorite)、正在播放的媒体Id(activeItemId)、自定义媒体数据(extras)等属性。 10 11## 接口说明 12 13媒体会话提供方使用的关键接口如下表所示。接口返回值有两种返回形式:callback和promise,下表中为callback形式接口,promise和callback只是返回值方式不一样,功能相同。 14 15更多API说明请参见[API文档](../../reference/apis-avsession-kit/js-apis-avsession.md)。 16 17| 接口名 | 说明 | 18| -------- | -------- | 19| createAVSession(context: Context, tag: string, type: AVSessionType, callback: AsyncCallback<AVSession>): void<sup>10+<sup> | 创建媒体会话。<br/>一个UIAbility只能存在一个媒体会话,重复创建会失败。 | 20| setAVMetadata(data: AVMetadata, callback: AsyncCallback<void>): void<sup>10+<sup> | 设置媒体会话元数据。 | 21| setAVPlaybackState(state: AVPlaybackState, callback: AsyncCallback<void>): void<sup>10+<sup> | 设置媒体会话播放状态。 | 22| setLaunchAbility(ability: WantAgent, callback: AsyncCallback<void>): void<sup>10+<sup> | 设置启动UIAbility。 | 23| getController(callback: AsyncCallback<AVSessionController>): void<sup>10+<sup> | 获取当前会话自身控制器。 | 24| getOutputDevice(callback: AsyncCallback<OutputDeviceInfo>): void<sup>10+<sup> | 获取播放设备相关信息。 | 25| activate(callback: AsyncCallback<void>): void<sup>10+<sup> | 激活媒体会话。 | 26| deactivate(callback: AsyncCallback<void>): void<sup>10+<sup> | 禁用当前会话。 | 27| destroy(callback: AsyncCallback<void>): void<sup>10+<sup> | 销毁媒体会话。 | 28| setAVQueueItems(items: Array<AVQueueItem>, callback: AsyncCallback<void>): void <sup>10+<sup> | 设置媒体播放列表。 | 29| setAVQueueTitle(title: string, callback: AsyncCallback<void>): void<sup>10+<sup> | 设置媒体播放列表名称。 | 30| dispatchSessionEvent(event: string, args: {[key: string]: Object}, callback: AsyncCallback<void>): void<sup>10+<sup> | 设置会话内自定义事件。 | 31| setExtras(extras: {[key: string]: Object}, callback: AsyncCallback<void>): void<sup>10+<sup> | 设置键值对形式的自定义媒体数据包。| 32| getOutputDeviceSync(): OutputDeviceInfo<sup>10+<sup> | 使用同步方法获取当前输出设备信息。 | 33 34## 开发步骤 35 36音视频应用作为媒体会话提供方接入媒体会话的基本步骤如下所示: 37 381. 通过AVSessionManager的方法创建并激活媒体会话。 39 40 ```ts 41 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 42 @Entry 43 @Component 44 struct Index { 45 @State message: string = 'hello world'; 46 47 build() { 48 Column() { 49 Text(this.message) 50 .onClick(()=>{ 51 // 开始创建并激活媒体会话。 52 // 创建session。 53 let context = this.getUIContext().getHostContext() as Context; 54 async function createSession() { 55 let type: AVSessionManager.AVSessionType = 'audio'; 56 let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type); 57 await session.activate(); 58 console.info(`session create done : sessionId : ${session.sessionId}`); 59 } 60 }) 61 } 62 .width('100%') 63 .height('100%') 64 } 65 } 66 ``` 67 682. 跟随媒体信息的变化,及时设置媒体会话信息。需要设置的媒体会话信息主要包括: 69 - 媒体会话元数据AVMetadata。 70 - 媒体播放状态AVPlaybackState。 71 72 音视频应用设置的媒体会话信息,会被媒体会话控制方通过AVSessionController相关方法获取后进行显示或处理。 73 74 ```ts 75 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 76 import { BusinessError } from '@kit.BasicServicesKit'; 77 @Entry 78 @Component 79 struct Index { 80 @State message: string = 'hello world'; 81 82 build() { 83 Column() { 84 Text(this.message) 85 .onClick(()=>{ 86 let context = this.getUIContext().getHostContext() as Context; 87 async function setSessionInfo() { 88 // 假设已经创建了一个session,如何创建session可以参考之前的案例。 89 let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', 'audio'); 90 // 播放器逻辑··· 引发媒体信息与播放状态的变更。 91 // 设置必要的媒体信息 92 let metadata: AVSessionManager.AVMetadata = { 93 assetId: '0', // 由应用指定,用于标识应用媒体库里的媒体。 94 title: 'TITLE', 95 mediaImage: 'IMAGE', 96 artist: 'ARTIST' 97 }; 98 session.setAVMetadata(metadata).then(() => { 99 console.info(`SetAVMetadata successfully`); 100 }).catch((err: BusinessError) => { 101 console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`); 102 }); 103 // 简单设置一个播放状态 - 暂停 未收藏。 104 let playbackState: AVSessionManager.AVPlaybackState = { 105 state:AVSessionManager.PlaybackState.PLAYBACK_STATE_PAUSE, 106 isFavorite:false 107 }; 108 session.setAVPlaybackState(playbackState, (err) => { 109 if (err) { 110 console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`); 111 } else { 112 console.info(`SetAVPlaybackState successfully`); 113 } 114 }); 115 // 设置一个播放列表。 116 let queueItemDescription_1: AVSessionManager.AVMediaDescription = { 117 assetId: '001', 118 title: 'music_name', 119 subtitle: 'music_sub_name', 120 description: 'music_description', 121 mediaImage: "PIXELMAP_OBJECT", 122 extras: {'extras':'any'} 123 }; 124 let queueItem_1: AVSessionManager.AVQueueItem = { 125 itemId: 1, 126 description: queueItemDescription_1 127 }; 128 let queueItemDescription_2: AVSessionManager.AVMediaDescription = { 129 assetId: '002', 130 title: 'music_name', 131 subtitle: 'music_sub_name', 132 description: 'music_description', 133 mediaImage: "PIXELMAP_OBJECT", 134 extras: {'extras':'any'} 135 }; 136 let queueItem_2: AVSessionManager.AVQueueItem = { 137 itemId: 2, 138 description: queueItemDescription_2 139 }; 140 let queueItemsArray = [queueItem_1, queueItem_2]; 141 session.setAVQueueItems(queueItemsArray).then(() => { 142 console.info(`SetAVQueueItems successfully`); 143 }).catch((err: BusinessError) => { 144 console.error(`Failed to set AVQueueItem, error code: ${err.code}, error message: ${err.message}`); 145 }); 146 // 设置媒体播放列表名称。 147 let queueTitle = 'QUEUE_TITLE'; 148 session.setAVQueueTitle(queueTitle).then(() => { 149 console.info(`SetAVQueueTitle successfully`); 150 }).catch((err: BusinessError) => { 151 console.info(`Failed to set AVQueueTitle, error code: ${err.code}, error message: ${err.message}`); 152 }); 153 } 154 }) 155 } 156 .width('100%') 157 .height('100%') 158 } 159 } 160 ``` 161 1623. 设置用于被媒体会话控制方拉起的UIAbility。当用户操作媒体会话控制方的界面时,例如点击播控中心的卡片,可以拉起此处配置的UIAbility。 163 设置UIAbility时通过WantAgent接口实现,更多关于WantAgent的信息请参考[WantAgent](../../reference/apis-ability-kit/js-apis-app-ability-wantAgent.md)。 164 165 ```ts 166 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 167 import { wantAgent } from '@kit.AbilityKit'; 168 @Entry 169 @Component 170 struct Index { 171 @State message: string = 'hello world'; 172 173 build() { 174 Column() { 175 Text(this.message) 176 .onClick(()=>{ 177 let context = this.getUIContext().getHostContext() as Context; 178 async function getWantAgent() { 179 let type: AVSessionManager.AVSessionType = 'audio'; 180 // 假设已经创建了一个session,如何创建session可以参考之前的案例。 181 let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type); 182 let wantAgentInfo: wantAgent.WantAgentInfo = { 183 wants: [ 184 { 185 bundleName: 'com.example.musicdemo', 186 abilityName: 'MainAbility' 187 } 188 ], 189 // OperationType.START_ABILITIES 190 operationType: 2, 191 requestCode: 0, 192 wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG] 193 } 194 wantAgent.getWantAgent(wantAgentInfo).then((agent) => { 195 session.setLaunchAbility(agent); 196 }) 197 } 198 }) 199 } 200 .width('100%') 201 .height('100%') 202 } 203 } 204 ``` 205 2064. 设置一个即时的自定义会话事件,以供媒体控制方接收到事件后进行相应的操作。 207 208 > **说明:**<br> 209 > 通过dispatchSessionEvent方法设置的数据不会保存在会话对象或AVSession服务中。 210 211 ```ts 212 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 213 import { BusinessError } from '@kit.BasicServicesKit'; 214 @Entry 215 @Component 216 struct Index { 217 @State message: string = 'hello world'; 218 219 build() { 220 Column() { 221 Text(this.message) 222 .onClick(()=>{ 223 let context = this.getUIContext().getHostContext() as Context; 224 async function dispatchSessionEvent() { 225 // 假设已经创建了一个session,如何创建session可以参考之前的案例。 226 let type: AVSessionManager.AVSessionType = 'audio'; 227 let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type); 228 let eventName = 'dynamic_lyric'; 229 await session.dispatchSessionEvent(eventName, {lyric : 'This is my lyric'}).then(() => { 230 console.info(`Dispatch session event successfully`); 231 }).catch((err: BusinessError) => { 232 console.error(`Failed to dispatch session event. Code: ${err.code}, message: ${err.message}`); 233 }) 234 } 235 }) 236 } 237 .width('100%') 238 .height('100%') 239 } 240 } 241 ``` 242 2435. 设置与当前会话相关的自定义媒体数据包,以供媒体控制方接收到事件后进行相应的操作。 244 245 > **说明:**<br> 246 > 通过setExtras方法设置的数据包会被存储在AVSession服务中,数据的生命周期与会话一致;会话对应的Controller可以使用getExtras来获取该数据。 247 248 ```ts 249 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 250 import { BusinessError } from '@kit.BasicServicesKit'; 251 @Entry 252 @Component 253 struct Index { 254 @State message: string = 'hello world'; 255 256 build() { 257 Column() { 258 Text(this.message) 259 .onClick(()=>{ 260 let context = this.getUIContext().getHostContext() as Context; 261 async function setExtras() { 262 // 假设已经创建了一个session,如何创建session可以参考之前的案例。 263 let type: AVSessionManager.AVSessionType = 'audio'; 264 let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type); 265 await session.setExtras({extra : 'This is my custom meida packet'}).then(() => { 266 console.info(`Set extras successfully`); 267 }).catch((err: BusinessError) => { 268 console.error(`Failed to set extras. Code: ${err.code}, message: ${err.message}`); 269 }) 270 } 271 }) 272 } 273 .width('100%') 274 .height('100%') 275 } 276 } 277 ``` 278 2796. 注册播控命令事件监听,便于响应用户通过媒体会话控制方,例如播控中心,下发的播控命令。 280 281 在Session侧注册的监听分为`固定播控命令`和`高级播控事件`两种。 282 283 6.1 固定控制命令的监听 284 285 > **说明:** 286 > 287 > 媒体会话提供方在注册相关固定播控命令事件监听时,监听的事件会在媒体会话控制方的getValidCommands()方法中体现,即媒体会话控制方会认为对应的方法有效,进而根据需要触发相应暂不使用时的事件。为了保证媒体会话控制方下发的播控命令可以被正常执行,媒体会话提供方请勿进行无逻辑的空实现监听。 288 289 Session侧的固定播控命令主要包括播放、暂停、上一首、下一首等基础操作命令,详细介绍请参见[AVControlCommand](../../reference/apis-avsession-kit/js-apis-avsession.md#avcontrolcommand10)。 290 291 ```ts 292 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 293 294 @Entry 295 @Component 296 struct Index { 297 @State message: string = 'hello world'; 298 299 build() { 300 Column() { 301 Text(this.message) 302 .onClick(()=>{ 303 let context = this.getUIContext().getHostContext() as Context; 304 async function setListenerForMesFromController() { 305 // 假设已经创建了一个session,如何创建session可以参考之前的案例。 306 let type: AVSessionManager.AVSessionType = 'audio'; 307 let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type); 308 // 一般在监听器中会对播放器做相应逻辑处理。 309 // 不要忘记处理完后需要通过set接口同步播放相关信息,参考上面的用例。 310 session.on('play', () => { 311 console.info(`on play , do play task`); 312 // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('play')取消监听。 313 // 处理完毕后,请使用SetAVPlayState上报播放状态。 314 }); 315 session.on('pause', () => { 316 console.info(`on pause , do pause task`); 317 // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('pause')取消监听。 318 // 处理完毕后,请使用SetAVPlayState上报播放状态。 319 }); 320 session.on('stop', () => { 321 console.info(`on stop , do stop task`); 322 // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('stop')取消监听。 323 // 处理完毕后,请使用SetAVPlayState上报播放状态。 324 }); 325 session.on('playNext', () => { 326 console.info(`on playNext , do playNext task`); 327 // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('playNext')取消监听。 328 // 处理完毕后,请使用SetAVPlayState上报播放状态,使用SetAVMetadata上报媒体信息。 329 }); 330 session.on('playPrevious', () => { 331 console.info(`on playPrevious , do playPrevious task`); 332 // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('playPrevious')取消监听。 333 // 处理完毕后,请使用SetAVPlayState上报播放状态,使用SetAVMetadata上报媒体信息。 334 }); 335 session.on('fastForward', () => { 336 console.info(`on fastForward , do fastForward task`); 337 // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('fastForward')取消监听。 338 // 处理完毕后,请使用SetAVPlayState上报播放状态和播放position。 339 }); 340 session.on('rewind', () => { 341 console.info(`on rewind , do rewind task`); 342 // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('rewind')取消监听。 343 // 处理完毕后,请使用SetAVPlayState上报播放状态和播放position。 344 }); 345 session.on('seek', (time) => { 346 console.info(`on seek , the seek time is ${time}`); 347 // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('seek')取消监听。 348 // 处理完毕后,请使用SetAVPlayState上报播放状态和播放position。 349 }); 350 session.on('setSpeed', (speed) => { 351 console.info(`on setSpeed , the speed is ${speed}`); 352 // do some tasks ··· 353 }); 354 session.on('setLoopMode', (mode) => { 355 console.info(`on setLoopMode , the loop mode is ${mode}`); 356 // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('setLoopMode')取消监听。 357 // 应用自定下一个模式,处理完毕后,请使用SetAVPlayState上报切换后的LoopMode。 358 }); 359 session.on('toggleFavorite', (assetId) => { 360 console.info(`on toggleFavorite , the target asset Id is ${assetId}`); 361 // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('toggleFavorite')取消监听。 362 // 处理完毕后,请使用SetAVPlayState上报收藏结果isFavorite。 363 }); 364 } 365 }) 366 } 367 .width('100%') 368 .height('100%') 369 } 370 } 371 ``` 372 373 6.2 高级播控事件的监听 374 375 Session侧的可以注册的高级播控事件主要包括: 376 377 - skipToQueueItem: 播放列表其中某项被选中的事件。 378 - handleKeyEvent: 按键事件。 379 - outputDeviceChange: 播放设备变化的事件。 380 - commonCommand: 自定义控制命令变化的事件。 381 382 ```ts 383 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 384 @Entry 385 @Component 386 struct Index { 387 @State message: string = 'hello world'; 388 389 build() { 390 Column() { 391 Text(this.message) 392 .onClick(()=>{ 393 let context = this.getUIContext().getHostContext() as Context; 394 async function setListenerForMesFromController() { 395 // 假设已经创建了一个session,如何创建session可以参考之前的案例。 396 let type: AVSessionManager.AVSessionType = 'audio'; 397 let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type); 398 // 一般在监听器中会对播放器做相应逻辑处理。 399 // 不要忘记处理完后需要通过set接口同步播放相关信息,参考上面的用例。 400 session.on('skipToQueueItem', (itemId) => { 401 console.info(`on skipToQueueItem , do skip task`); 402 // do some tasks ··· 403 }); 404 session.on('handleKeyEvent', (event) => { 405 console.info(`on handleKeyEvent , the event is ${JSON.stringify(event)}`); 406 // do some tasks ··· 407 }); 408 session.on('outputDeviceChange', (device) => { 409 console.info(`on outputDeviceChange , the device info is ${JSON.stringify(device)}`); 410 // do some tasks ··· 411 }); 412 session.on('commonCommand', (commandString, args) => { 413 console.info(`on commonCommand , command is ${commandString}, args are ${JSON.stringify(args)}`); 414 // do some tasks ··· 415 }); 416 } 417 }) 418 } 419 .width('100%') 420 .height('100%') 421 } 422 } 423 ``` 424 4257. 获取当前媒体会话自身的控制器,与媒体会话对应进行通信交互。 426 427 ```ts 428 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 429 430 @Entry 431 @Component 432 struct Index { 433 @State message: string = 'hello world'; 434 435 build() { 436 Column() { 437 Text(this.message) 438 .onClick(()=>{ 439 let context = this.getUIContext().getHostContext() as Context; 440 async function createControllerFromSession() { 441 // 假设已经创建了一个session,如何创建session可以参考之前的案例。 442 let type: AVSessionManager.AVSessionType = 'audio'; 443 let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type); 444 445 // 通过已有session获取一个controller对象。 446 let controller = await session.getController(); 447 448 // controller可以与原session对象进行基本的通信交互,比如下发播放命令。 449 let avCommand: AVSessionManager.AVControlCommand = {command:'play'}; 450 controller.sendControlCommand(avCommand); 451 452 // 或者做状态变更监听。 453 controller.on('playbackStateChange', 'all', (state) => { 454 455 // do some things. 456 }); 457 458 // controller可以做的操作还有很多,具体可以参考媒体会话控制方相关的说明。 459 } 460 }) 461 } 462 .width('100%') 463 .height('100%') 464 } 465 } 466 ``` 467 4688. 音视频应用在退出,并且不需要继续播放时,及时取消监听以及销毁媒体会话释放资源。 469 取消播控命令监听的示例代码如下所示 : 470 471 ```ts 472 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 473 @Entry 474 @Component 475 struct Index { 476 @State message: string = 'hello world'; 477 478 build() { 479 Column() { 480 Text(this.message) 481 .onClick(()=>{ 482 let context = this.getUIContext().getHostContext() as Context; 483 async function unregisterSessionListener() { 484 // 假设已经创建了一个session,如何创建session可以参考之前的案例。 485 let type: AVSessionManager.AVSessionType = 'audio'; 486 let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type); 487 488 // 取消指定session下的相关监听。 489 session.off('play'); 490 session.off('pause'); 491 session.off('stop'); 492 session.off('playNext'); 493 session.off('playPrevious'); 494 session.off('skipToQueueItem'); 495 session.off('handleKeyEvent'); 496 session.off('outputDeviceChange'); 497 session.off('commonCommand'); 498 } 499 }) 500 } 501 .width('100%') 502 .height('100%') 503 } 504 } 505 ``` 506 507 销毁媒体会话示例代码如下所示: 508 509 ```ts 510 ```ts 511 import { avSession as AVSessionManager } from '@kit.AVSessionKit'; 512 @Entry 513 @Component 514 struct Index { 515 @State message: string = 'hello world'; 516 517 build() { 518 Column() { 519 Text(this.message) 520 .onClick(()=>{ 521 let context = this.getUIContext().getHostContext() as Context; 522 async function destroySession() { 523 // 假设已经创建了一个session,如何创建session可以参考之前的案例。 524 let type: AVSessionManager.AVSessionType = 'audio'; 525 let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type); 526 // 主动销毁已创建的session。 527 session.destroy((err) => { 528 if (err) { 529 console.error(`Failed to destroy session. Code: ${err.code}, message: ${err.message}`); 530 } else { 531 console.info(`Destroy : SUCCESS `); 532 } 533 }); 534 } 535 }) 536 } 537 .width('100%') 538 .height('100%') 539 } 540 } 541 ``` 542 543## 相关实例 544 545针对媒体会话提供方开发,有以下相关实例可供参考: 546 547- [媒体会话——提供方(ArkTS)(Full SDK)(API10)](https://gitee.com/openharmony/applications_app_samples/tree/master/code/BasicFeature/Media/AVSession/MediaProvider)