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