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&lt;AVSession&gt;): void<sup>10+<sup> | 创建媒体会话。<br/>一个UIAbility只能存在一个媒体会话,重复创建会失败。 |
20| setAVMetadata(data: AVMetadata, callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 设置媒体会话元数据。 |
21| setAVPlaybackState(state: AVPlaybackState, callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 设置媒体会话播放状态。 |
22| setLaunchAbility(ability: WantAgent, callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 设置启动UIAbility。 |
23| getController(callback: AsyncCallback&lt;AVSessionController&gt;): void<sup>10+<sup> | 获取当前会话自身控制器。 |
24| getOutputDevice(callback: AsyncCallback&lt;OutputDeviceInfo&gt;): void<sup>10+<sup> | 获取播放设备相关信息。 |
25| activate(callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 激活媒体会话。 |
26| deactivate(callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 禁用当前会话。 |
27| destroy(callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 销毁媒体会话。 |
28| setAVQueueItems(items: Array&lt;AVQueueItem&gt;, callback: AsyncCallback&lt;void&gt;): void <sup>10+<sup> | 设置媒体播放列表。 |
29| setAVQueueTitle(title: string, callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 设置媒体播放列表名称。 |
30| dispatchSessionEvent(event: string, args: {[key: string]: Object}, callback: AsyncCallback&lt;void&gt;): void<sup>10+<sup> | 设置会话内自定义事件。 |
31| setExtras(extras: {[key: string]: Object}, callback: AsyncCallback&lt;void&gt;): 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
43   // 开始创建并激活媒体会话
44   // 创建session
45   let context: Context = getContext(this);
46   async function createSession() {
47     let type: AVSessionManager.AVSessionType = 'audio';
48     let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
49     await session.activate();
50     console.info(`session create done : sessionId : ${session.sessionId}`);
51   }
52   ```
53
542. 跟随媒体信息的变化,及时设置媒体会话信息。需要设置的媒体会话信息主要包括:
55   - 媒体会话元数据AVMetadata。
56   - 媒体播放状态AVPlaybackState。
57
58   音视频应用设置的媒体会话信息,会被媒体会话控制方通过AVSessionController相关方法获取后进行显示或处理。
59
60   ```ts
61   import { avSession as AVSessionManager } from '@kit.AVSessionKit';
62   import { BusinessError } from '@kit.BasicServicesKit';
63
64   let context: Context = getContext(this);
65   async function setSessionInfo() {
66     // 假设已经创建了一个session,如何创建session可以参考之前的案例
67     let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', 'audio');
68     // 播放器逻辑··· 引发媒体信息与播放状态的变更
69     // 设置必要的媒体信息
70     let metadata: AVSessionManager.AVMetadata = {
71       assetId: '0', // 由应用指定,用于标识应用媒体库里的媒体
72       title: 'TITLE',
73       mediaImage: 'IMAGE',
74       artist: 'ARTIST'
75     };
76     session.setAVMetadata(metadata).then(() => {
77       console.info(`SetAVMetadata successfully`);
78     }).catch((err: BusinessError) => {
79       console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
80     });
81     // 简单设置一个播放状态 - 暂停 未收藏
82     let playbackState: AVSessionManager.AVPlaybackState = {
83       state:AVSessionManager.PlaybackState.PLAYBACK_STATE_PAUSE,
84       isFavorite:false
85     };
86     session.setAVPlaybackState(playbackState, (err) => {
87       if (err) {
88         console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
89       } else {
90         console.info(`SetAVPlaybackState successfully`);
91       }
92     });
93     // 设置一个播放列表
94     let queueItemDescription_1: AVSessionManager.AVMediaDescription = {
95       assetId: '001',
96       title: 'music_name',
97       subtitle: 'music_sub_name',
98       description: 'music_description',
99       mediaImage: "PIXELMAP_OBJECT",
100       extras: {'extras':'any'}
101     };
102     let queueItem_1: AVSessionManager.AVQueueItem = {
103       itemId: 1,
104       description: queueItemDescription_1
105     };
106     let queueItemDescription_2: AVSessionManager.AVMediaDescription = {
107       assetId: '002',
108       title: 'music_name',
109       subtitle: 'music_sub_name',
110       description: 'music_description',
111       mediaImage: "PIXELMAP_OBJECT",
112       extras: {'extras':'any'}
113     };
114     let queueItem_2: AVSessionManager.AVQueueItem = {
115       itemId: 2,
116       description: queueItemDescription_2
117     };
118     let queueItemsArray = [queueItem_1, queueItem_2];
119     session.setAVQueueItems(queueItemsArray).then(() => {
120       console.info(`SetAVQueueItems successfully`);
121     }).catch((err: BusinessError) => {
122       console.error(`Failed to set AVQueueItem, error code: ${err.code}, error message: ${err.message}`);
123     });
124     // 设置媒体播放列表名称
125     let queueTitle = 'QUEUE_TITLE';
126     session.setAVQueueTitle(queueTitle).then(() => {
127       console.info(`SetAVQueueTitle successfully`);
128     }).catch((err: BusinessError) => {
129       console.info(`Failed to set AVQueueTitle, error code: ${err.code}, error message: ${err.message}`);
130     });
131   }
132   ```
133
1343. 设置用于被媒体会话控制方拉起的UIAbility。当用户操作媒体会话控制方的界面时,例如点击播控中心的卡片,可以拉起此处配置的UIAbility。
135   设置UIAbility时通过WantAgent接口实现,更多关于WantAgent的信息请参考[WantAgent](../../reference/apis-ability-kit/js-apis-app-ability-wantAgent.md)。
136
137   ```ts
138   import { avSession as AVSessionManager } from '@kit.AVSessionKit';
139   import { wantAgent } from '@kit.AbilityKit';
140
141   let context: Context = getContext(this);
142   async function getWantAgent() {
143     let type: AVSessionManager.AVSessionType = 'audio';
144     // 假设已经创建了一个session,如何创建session可以参考之前的案例
145     let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
146     let wantAgentInfo: wantAgent.WantAgentInfo = {
147       wants: [
148         {
149           bundleName: 'com.example.musicdemo',
150           abilityName: 'MainAbility'
151         }
152       ],
153       // OperationType.START_ABILITIES
154       operationType: 2,
155       requestCode: 0,
156       wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
157     }
158     wantAgent.getWantAgent(wantAgentInfo).then((agent) => {
159       session.setLaunchAbility(agent);
160     })
161   }
162   ```
163
1644. 设置一个即时的自定义会话事件,以供媒体控制方接收到事件后进行相应的操作。
165
166   > **说明:**<br>
167   > 通过dispatchSessionEvent方法设置的数据不会保存在会话对象或AVSession服务中。
168
169   ```ts
170
171   import { avSession as AVSessionManager } from '@kit.AVSessionKit';
172   import { BusinessError } from '@kit.BasicServicesKit';
173
174   let context: Context = getContext(this);
175   async function dispatchSessionEvent() {
176     // 假设已经创建了一个session,如何创建session可以参考之前的案例
177     let type: AVSessionManager.AVSessionType = 'audio';
178     let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
179     let eventName = 'dynamic_lyric';
180     await session.dispatchSessionEvent(eventName, {lyric : 'This is my lyric'}).then(() => {
181       console.info(`Dispatch session event successfully`);
182     }).catch((err: BusinessError) => {
183       console.error(`Failed to dispatch session event. Code: ${err.code}, message: ${err.message}`);
184     })
185   }
186
187   ```
188
1895. 设置与当前会话相关的自定义媒体数据包,以供媒体控制方接收到事件后进行相应的操作。
190
191   > **说明:**<br>
192   > 通过setExtras方法设置的数据包会被存储在AVSession服务中,数据的生命周期与会话一致;会话对应的Controller可以使用getExtras来获取该数据。
193
194   ```ts
195   import { avSession as AVSessionManager } from '@kit.AVSessionKit';
196   import { BusinessError } from '@kit.BasicServicesKit';
197
198   let context: Context = getContext(this);
199   async function setExtras() {
200     // 假设已经创建了一个session,如何创建session可以参考之前的案例
201     let type: AVSessionManager.AVSessionType = 'audio';
202     let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
203     await session.setExtras({extra : 'This is my custom meida packet'}).then(() => {
204       console.info(`Set extras successfully`);
205     }).catch((err: BusinessError) => {
206       console.error(`Failed to set extras. Code: ${err.code}, message: ${err.message}`);
207     })
208   }
209   ```
210
2116. 注册播控命令事件监听,便于响应用户通过媒体会话控制方,例如播控中心,下发的播控命令。
212
213   在Session侧注册的监听分为`固定播控命令`和`高级播控事件`两种。
214
215   6.1 固定控制命令的监听
216
217   > **说明:**
218   >
219   > 媒体会话提供方在注册相关固定播控命令事件监听时,监听的事件会在媒体会话控制方的getValidCommands()方法中体现,即媒体会话控制方会认为对应的方法有效,进而根据需要触发相应暂不使用时的事件。为了保证媒体会话控制方下发的播控命令可以被正常执行,媒体会话提供方请勿进行无逻辑的空实现监听。
220
221   Session侧的固定播控命令主要包括播放、暂停、上一首、下一首等基础操作命令,详细介绍请参见[AVControlCommand](../../reference/apis-avsession-kit/js-apis-avsession.md#avcontrolcommand10)
222
223   ```ts
224   import { avSession as AVSessionManager } from '@kit.AVSessionKit';
225
226   let context: Context = getContext(this);
227   async function setListenerForMesFromController() {
228     // 假设已经创建了一个session,如何创建session可以参考之前的案例
229     let type: AVSessionManager.AVSessionType = 'audio';
230     let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
231     // 一般在监听器中会对播放器做相应逻辑处理
232     // 不要忘记处理完后需要通过set接口同步播放相关信息,参考上面的用例
233     session.on('play', () => {
234       console.info(`on play , do play task`);
235       // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('play')取消监听
236       // 处理完毕后,请使用SetAVPlayState上报播放状态
237     });
238     session.on('pause', () => {
239       console.info(`on pause , do pause task`);
240        // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('pause')取消监听
241        // 处理完毕后,请使用SetAVPlayState上报播放状态
242     });
243     session.on('stop', () => {
244       console.info(`on stop , do stop task`);
245        // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('stop')取消监听
246        // 处理完毕后,请使用SetAVPlayState上报播放状态
247     });
248     session.on('playNext', () => {
249       console.info(`on playNext , do playNext task`);
250        // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('playNext')取消监听
251        // 处理完毕后,请使用SetAVPlayState上报播放状态,使用SetAVMetadata上报媒体信息
252     });
253     session.on('playPrevious', () => {
254       console.info(`on playPrevious , do playPrevious task`);
255        // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('playPrevious')取消监听
256        // 处理完毕后,请使用SetAVPlayState上报播放状态,使用SetAVMetadata上报媒体信息
257     });
258     session.on('fastForward', () => {
259       console.info(`on fastForward , do fastForward task`);
260        // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('fastForward')取消监听
261        // 处理完毕后,请使用SetAVPlayState上报播放状态和播放position
262     });
263     session.on('rewind', () => {
264       console.info(`on rewind , do rewind task`);
265        // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('rewind')取消监听
266        // 处理完毕后,请使用SetAVPlayState上报播放状态和播放position
267     });
268     session.on('seek', (time) => {
269       console.info(`on seek , the seek time is ${time}`);
270        // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('seek')取消监听
271        // 处理完毕后,请使用SetAVPlayState上报播放状态和播放position
272     });
273     session.on('setSpeed', (speed) => {
274       console.info(`on setSpeed , the speed is ${speed}`);
275       // do some tasks ···
276     });
277     session.on('setLoopMode', (mode) => {
278       console.info(`on setLoopMode , the loop mode is ${mode}`);
279        // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('setLoopMode')取消监听
280        // 应用自定下一个模式,处理完毕后,请使用SetAVPlayState上报切换后的LoopMode
281     });
282     session.on('toggleFavorite', (assetId) => {
283       console.info(`on toggleFavorite , the target asset Id is ${assetId}`);
284        // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('toggleFavorite')取消监听
285        // 处理完毕后,请使用SetAVPlayState上报收藏结果isFavorite
286     });
287   }
288   ```
289
290   6.2 高级播控事件的监听
291
292   Session侧的可以注册的高级播控事件主要包括:
293
294   - skipToQueueItem: 播放列表其中某项被选中的事件。
295   - handleKeyEvent: 按键事件。
296   - outputDeviceChange: 播放设备变化的事件。
297   - commonCommand: 自定义控制命令变化的事件。
298
299   ```ts
300   import { avSession as AVSessionManager } from '@kit.AVSessionKit';
301
302   let context: Context = getContext(this);
303   async function setListenerForMesFromController() {
304     // 假设已经创建了一个session,如何创建session可以参考之前的案例
305     let type: AVSessionManager.AVSessionType = 'audio';
306     let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
307     // 一般在监听器中会对播放器做相应逻辑处理
308     // 不要忘记处理完后需要通过set接口同步播放相关信息,参考上面的用例
309     session.on('skipToQueueItem', (itemId) => {
310       console.info(`on skipToQueueItem , do skip task`);
311       // do some tasks ···
312     });
313     session.on('handleKeyEvent', (event) => {
314       console.info(`on handleKeyEvent , the event is ${JSON.stringify(event)}`);
315       // do some tasks ···
316     });
317     session.on('outputDeviceChange', (device) => {
318       console.info(`on outputDeviceChange , the device info is ${JSON.stringify(device)}`);
319       // do some tasks ···
320     });
321     session.on('commonCommand', (commandString, args) => {
322       console.info(`on commonCommand , command is ${commandString}, args are ${JSON.stringify(args)}`);
323       // do some tasks ···
324     });
325   }
326   ```
327
3287. 获取当前媒体会话自身的控制器,与媒体会话对应进行通信交互。
329
330   ```ts
331   import { avSession as AVSessionManager } from '@kit.AVSessionKit';
332
333   let context: Context = getContext(this);
334   async function createControllerFromSession() {
335     // 假设已经创建了一个session,如何创建session可以参考之前的案例
336     let type: AVSessionManager.AVSessionType = 'audio';
337     let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
338
339     // 通过已有session获取一个controller对象
340     let controller = await session.getController();
341
342     // controller可以与原session对象进行基本的通信交互,比如下发播放命令
343     let avCommand: AVSessionManager.AVControlCommand = {command:'play'};
344     controller.sendControlCommand(avCommand);
345
346     // 或者做状态变更监听
347     controller.on('playbackStateChange', 'all', (state) => {
348
349       // do some things
350     });
351
352     // controller可以做的操作还有很多,具体可以参考媒体会话控制方相关的说明
353   }
354   ```
355
3568. 音视频应用在退出,并且不需要继续播放时,及时取消监听以及销毁媒体会话释放资源。
357   取消播控命令监听的示例代码如下所示 :
358
359   ```ts
360   import { avSession as AVSessionManager } from '@kit.AVSessionKit';
361
362   let context: Context = getContext(this);
363   async function unregisterSessionListener() {
364     // 假设已经创建了一个session,如何创建session可以参考之前的案例
365     let type: AVSessionManager.AVSessionType = 'audio';
366     let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
367
368     // 取消指定session下的相关监听
369     session.off('play');
370     session.off('pause');
371     session.off('stop');
372     session.off('playNext');
373     session.off('playPrevious');
374     session.off('skipToQueueItem');
375     session.off('handleKeyEvent');
376     session.off('outputDeviceChange');
377     session.off('commonCommand');
378   }
379   ```
380
381     销毁媒体会话示例代码如下所示:
382
383   ```ts
384   import { avSession as AVSessionManager } from '@kit.AVSessionKit';
385
386   let context: Context = getContext(this);
387   async function destroySession() {
388     // 假设已经创建了一个session,如何创建session可以参考之前的案例
389     let type: AVSessionManager.AVSessionType = 'audio';
390     let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
391     // 主动销毁已创建的session
392     session.destroy((err) => {
393       if (err) {
394         console.error(`Failed to destroy session. Code: ${err.code}, message: ${err.message}`);
395       } else {
396         console.info(`Destroy : SUCCESS `);
397       }
398     });
399   }
400   ```
401
402## 相关实例
403
404针对媒体会话提供方开发,有以下相关实例可供参考:
405
406- [媒体会话——提供方(ArkTS)(Full SDK)(API10)](https://gitee.com/openharmony/applications_app_samples/tree/master/code/BasicFeature/Media/AVSession/MediaProvider)