• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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&lt;AVSession&gt;): void<sup>10+<sup> | 创建媒体会话。<br/>一个UIAbility只能存在一个媒体会话,重复创建会失败。 |
26| setAVMetadata(data: AVMetadata, callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 设置媒体会话元数据。 |
27| setAVPlaybackState(state: AVPlaybackState, callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 设置媒体会话播放状态。 |
28| setLaunchAbility(ability: WantAgent, callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 设置启动UIAbility。 |
29| getController(callback: AsyncCallback&lt;AVSessionController&gt;): void<sup>10+<sup> | 获取当前会话自身控制器。 |
30| getOutputDevice(callback: AsyncCallback&lt;OutputDeviceInfo&gt;): void<sup>10+<sup> | 获取播放设备相关信息。 |
31| activate(callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 激活媒体会话。 |
32| deactivate(callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 禁用当前会话。 |
33| destroy(callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 销毁媒体会话。 |
34| setAVQueueItems(items: Array&lt;AVQueueItem&gt;, callback: AsyncCallback&lt;void&gt;): void <sup>10+<sup> | 设置媒体播放列表。 |
35| setAVQueueTitle(title: string, callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 设置媒体播放列表名称。 |
36| dispatchSessionEvent(event: string, args: {[key: string]: Object}, callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 设置会话内自定义事件。 |
37| setExtras(extras: {[key: string]: Object}, callback: AsyncCallback&lt;void&gt;): 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)