• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 应用接入AVSession场景介绍
2<!--Kit: AVSession Kit-->
3<!--Subsystem: Multimedia-->
4<!--Owner: @ccfriend; @liao_qian-->
5<!--Designer: @ccfriend-->
6<!--Tester: @chenmingxi1_huawei-->
7<!--Adviser: @zengyawen-->
8
9音视频应用在实现音视频功能的同时,需要接入媒体会话即AVSession Kit,下文将提供一些典型的接入AVSession的展示和控制场景,方便开发者根据场景进行适配。
10
11对于不同的场景,将会在系统的播控中心看到不同的UI呈现。同时,在不同的场景下,应用的接入处理也需要遵循不同的规范约束。
12
13## 哪些场景下需要接入AVSession
14
15AVSession会对后台的音频播放、VOIP通话做约束,所以通常来说,长音频应用、听书类应用、长视频应用、VOIP类应用等都需要接入AVSession。当应用在没有创建接入AVSession的情况下进行了上述业务,那么系统会在检测到应用后台时,停止对应的音频播放,静音通话声音,以达到约束应用行为的目的。这种约束,应用上架前在本地就可以验证。
16
17对于其他使用到音频播放的应用,比如游戏,直播等场景,接入AVSession不是必选项,只是可选,取决于应用是否有后台播放的使用诉求。若应用需要后台播放,那么接入AVSession仍然是必须的,否则业务的正常功能会受到限制。
18
19当应用需要实现后台播放等功能时,需要使用[BackgroundTasks Kit](../../task-management/background-task-overview.md)(后台任务管理)的能力,申请对应的长时任务,避免进入挂起(Suspend)状态。
20
21## 接入流程
22
23应用接入AVSession流程分为如下几个步骤:
24
251. 确定应用需要创建的会话类型,[创建对应的会话](#创建不同类型的会话),不同类型决定了播控中心展示的控制模板样式。
262. 按需[创建后台任务](#创建后台任务)。
273. [设置必要的元数据(Metadata)](#设置元数据),以在播控中心展示相应的信息,包括不限于:当前媒体的ID(assetId),上一首媒体的ID(previousAssetId),下一首媒体的ID(nextAssetId),标题(title),专辑作者(author),专辑名称(album),词作者(writer),媒体时长(duration)等属性。
284. [设置播放相关的状态](#设置播放状态),包括不限于:当前媒体的播放状态(state)、播放位置(position)、播放倍速(speed)、缓冲时间(bufferedTime)、循环模式(loopMode)、是否收藏(isFavorite)、正在播放的媒体Id(activeItemId)、自定义媒体数据(extras)等属性。
295. 按需[注册不同的控制命令](#注册控制命令),包括不限于:播放/暂停、上下一首、快进快退、收藏、循环模式、进度条。
306. 应用退出或者无对应业务时,注销会话。
31
32## 创建不同类型的会话
33
34AVSession在构造方法中支持不同的类型参数,由 [AVSessionType](../../reference/apis-avsession-kit/arkts-apis-avsession-t.md#avsessiontype10) 定义,不同的类型代表了不同场景的控制能力,对于播控中心来说,会展示不同的控制模版。
35
36- audio类型,播控中心的控制样式为:收藏,上一首,播放/暂停,下一首,循环模式。
37
38- video类型,播控中心的控制样式为:快退,上一首,播放/暂停,下一首,快进。
39
40- voice_call类型,通话类型。
41
42使用代码示例:
43
44> **说明:**
45>
46> 以下示例代码仅展示创建AVSession对象的接口调用,应用在真正使用时,需要确保AVSession对象实例在应用后台播放业务活动期间一直存在,避免被系统回收、释放,导致后台发声时被系统管控。
47
48```ts
49import { avSession as AVSessionManager } from '@kit.AVSessionKit';
50
51@Entry
52@Component
53struct Index {
54  @State message: string = 'hello world';
55
56  build() {
57    Column() {
58      Text(this.message)
59        .onClick(async () => {
60          // 开始创建并激活媒体会话。
61          // 创建session。
62          let context = this.getUIContext().getHostContext() as Context;
63          let type: AVSessionManager.AVSessionType = 'audio';
64          let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
65          // 激活接口要在元数据、控制命令注册完成之后再执行。
66          await session.activate();
67          console.info(`session create done : sessionId : ${session.sessionId}`);
68        })
69    }
70    .width('100%')
71    .height('100%')
72  }
73}
74```
75
76## 创建后台任务
77
78当应用需要实现后台播放等功能时,需要使用[BackgroundTasks Kit](../../task-management/background-task-overview.md)(后台任务管理)的能力,申请对应的长时任务,避免进入挂起(Suspend)状态。
79
80对媒体类播放来说,需要申请[AUDIO_PLAYBACK BackgroundMode](../../reference/apis-backgroundtasks-kit/js-apis-resourceschedule-backgroundTaskManager.md#backgroundmode)的长时任务。
81
82
83## 设置元数据
84
85### 通用元数据
86
87应用可以通过setAVMetadata把会话的一些元数据信息设置给系统,从而在播控中心界面进行展示,包括不限制:当前媒体的ID(assetId),上一首媒体的ID(previousAssetId),下一首媒体的ID(nextAssetId),标题(title),专辑作者(author),专辑名称(album),词作者(writer),媒体时长(duration)等。
88
89```ts
90import { avSession as AVSessionManager } from '@kit.AVSessionKit';
91import { BusinessError } from '@kit.BasicServicesKit';
92
93@Entry
94@Component
95struct Index {
96  @State message: string = 'hello world';
97
98  build() {
99    Column() {
100      Text(this.message)
101        .onClick(async () => {
102          let context = this.getUIContext().getHostContext() as Context;
103          // 假设已经创建了一个session,如何创建session可以参考之前的案例。
104          let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', 'audio');
105          // 设置必要的媒体信息。
106          let metadata: AVSessionManager.AVMetadata = {
107            assetId: '0', // 由应用指定,用于标识应用媒体库里的媒体。
108            title: 'TITLE',
109            mediaImage: 'IMAGE',
110            artist: 'ARTIST',
111          };
112          session.setAVMetadata(metadata).then(() => {
113            console.info(`SetAVMetadata successfully`);
114          }).catch((err: BusinessError) => {
115            console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
116          });
117        })
118    }
119    .width('100%')
120    .height('100%')
121  }
122}
123```
124
125### 歌词
126
127对于长音频来说,播控中心提供了歌词的展示页面,对于应用来说,接入也比较简单,只需要把歌词内容设置给系统。播控中心会解析歌词内容,并根据播放进度进行同步的刷新。
128
129```ts
130import { avSession as AVSessionManager } from '@kit.AVSessionKit';
131import { BusinessError } from '@kit.BasicServicesKit';
132
133@Entry
134@Component
135struct Index {
136  @State message: string = 'hello world';
137
138  build() {
139    Column() {
140      Text(this.message)
141        .onClick(async () => {
142          let context = this.getUIContext().getHostContext() as Context;
143          // 假设已经创建了一个session,如何创建session可以参考之前的案例。
144          let type: AVSessionManager.AVSessionType = 'audio';
145          let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
146
147          // 把歌词信息设置给AVSession。
148          let metadata: AVSessionManager.AVMetadata = {
149            assetId: '0',
150            title: 'TITLE',
151            mediaImage: 'IMAGE',
152            // LRC中有两类元素:一种是时间标签+歌词,一种是ID标签。
153            // 例如:[00:25.44]xxx\r\n[00:26.44]xxx\r\n。
154            lyric: "lrc格式歌词内容",
155            // singleLyricText字段存储单条歌词文本,不包含时间戳。
156            // 例如:"单条歌词内容"。
157            singleLyricText: "单条歌词内容",
158          };
159          session.setAVMetadata(metadata).then(() => {
160            console.info(`SetAVMetadata successfully`);
161          }).catch((err: BusinessError) => {
162            console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
163          });
164        })
165    }
166    .width('100%')
167    .height('100%')
168  }
169}
170```
171
172<!--RP1-->
173<!--RP1End-->
174
175### 媒体资源金标
176
177对于长音频,播控中心提供了媒体资源金标的展示,媒体资源金标又可称为应用媒体音频音源的标识,目前暂时只支持展示Audio Vivid标识。
178对于应用来说,接入只需要在AVMetadata中通知系统,当前播放音频的音源标识,播控就会同步展示。
179
180```ts
181import { avSession as AVSessionManager } from '@kit.AVSessionKit';
182import { BusinessError } from '@kit.BasicServicesKit';
183
184@Entry
185@Component
186struct Index {
187  @State message: string = 'hello world';
188
189  build() {
190    Column() {
191      Text(this.message)
192        .onClick(async () => {
193          let context = this.getUIContext().getHostContext() as Context;
194          // 假设已经创建了一个session,如何创建session可以参考之前的案例。
195          let type: AVSessionManager.AVSessionType = 'audio';
196          let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
197
198          // 把媒体音源信息设置给AVSession。
199          let metadata: AVSessionManager.AVMetadata = {
200            assetId: '0',
201            title: 'TITLE',
202            mediaImage: 'IMAGE',
203            // 标识该媒体音源是Audio Vivid。
204            displayTags: AVSessionManager.DisplayTag.TAG_AUDIO_VIVID,
205          };
206          session.setAVMetadata(metadata).then(() => {
207            console.info(`SetAVMetadata successfully`);
208          }).catch((err: BusinessError) => {
209            console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
210          });
211        })
212    }
213    .width('100%')
214    .height('100%')
215  }
216}
217```
218
219## 设置播放状态
220
221### 通用播放状态
222
223应用可以通过[setAVPlaybackState](../../reference/apis-avsession-kit/arkts-apis-avsession-AVSession.md#setavplaybackstate10)。把当前的播放状态设置给系统,以在播控中心界面进行展示。
224播放状态一般是在资源播放后会进行变化的内容,包括:当前媒体的播放状态(state)、播放位置(position)、播放倍速(speed)、缓冲时间(bufferedTime)、循环模式(loopMode)、是否收藏(isFavorite)、正在播放的媒体Id(activeItemId)、自定义媒体数据(extras)等。
225
226```ts
227import { avSession as AVSessionManager } from '@kit.AVSessionKit';
228import { BusinessError } from '@kit.BasicServicesKit';
229
230@Entry
231@Component
232struct Index {
233  @State message: string = 'hello world';
234
235  build() {
236    Column() {
237      Text(this.message)
238        .onClick(async () => {
239          let context = this.getUIContext().getHostContext() as Context;
240          // 假设已经创建了一个session,如何创建session可以参考之前的案例。
241          let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', 'audio');
242
243          // 播放器逻辑··· 引发媒体信息与播放状态的变更。
244          // 简单设置一个播放状态 - 暂停 未收藏。
245          let playbackState: AVSessionManager.AVPlaybackState = {
246            state: AVSessionManager.PlaybackState.PLAYBACK_STATE_PAUSE,
247            isFavorite: false
248          };
249          session.setAVPlaybackState(playbackState, (err: BusinessError) => {
250            if (err) {
251              console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
252            } else {
253              console.info(`SetAVPlaybackState successfully`);
254            }
255          });
256        })
257    }
258    .width('100%')
259    .height('100%')
260  }
261}
262```
263
264### 进度条
265
266应用如果支持在播控中心展示进度,那么在媒体资源播放中,需要设置资源的时长、播放状态(暂停、播放)、播放位置、倍速,播控中心会使用这些信息进行进度的展示:
267
268```ts
269import { avSession as AVSessionManager } from '@kit.AVSessionKit';
270import { BusinessError } from '@kit.BasicServicesKit';
271
272@Entry
273@Component
274struct Index {
275  @State message: string = 'hello world';
276
277  build() {
278    Column() {
279      Text(this.message)
280        .onClick(async () => {
281          let context = this.getUIContext().getHostContext() as Context;
282          // 假设已经创建了一个session,如何创建session可以参考之前的案例。
283          let type: AVSessionManager.AVSessionType = 'audio';
284          let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
285
286          // 设置媒体资源时长。
287          let metadata: AVSessionManager.AVMetadata = {
288            assetId: '0',
289            title: 'TITLE',
290            mediaImage: 'IMAGE',
291            duration: 23000, // 资源的时长,以ms为单位。
292          };
293          session.setAVMetadata(metadata).then(() => {
294            console.info(`SetAVMetadata successfully`);
295          }).catch((err: BusinessError) => {
296            console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
297          });
298
299          // 设置状态: 播放状态,进度位置,播放倍速,缓存的时间。
300          let playbackState: AVSessionManager.AVPlaybackState = {
301            state: AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY, // 播放状态。
302            position: {
303              elapsedTime: 1000, // 已经播放的位置,以ms为单位。
304              updateTime: new Date().getTime(), // 应用更新当前位置时的时间戳,以ms为单位。
305            },
306            speed: 1.0, // 可选,默认是1.0,播放的倍速,按照应用内支持的speed进行设置,系统不做校验。
307            bufferedTime: 14000, // 可选,资源缓存的时间,以ms为单位。
308          };
309          session.setAVPlaybackState(playbackState, (err) => {
310            if (err) {
311              console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
312            } else {
313              console.info(`SetAVPlaybackState successfully`);
314            }
315          });
316        })
317    }
318    .width('100%')
319    .height('100%')
320  }
321}
322```
323
324系统的播控中心会根据应用设置的信息自行进行播放进度的计算,而不需要应用实时更新播放进度;
325但是应用需要如下状态发生变化的时候,再更新AVPlaybackState,否则系统会发生计算错误:
326
327- state
328- position
329- speed
330
331应用在真实播放开始时,再上报进度起始position;若播放存在buffer状态,可以先上报播放状态为AVSessionManager.PlaybackState.PLAYBACK_STATE_BUFFERING,来通知系统不刷新进度。
332
333关于进度条有一些特殊情况需要处理:
334
3351. 歌曲支持试听
336
337    (1)应用不需要设置完整的歌曲时长,则只需要设置歌曲的试听时长。当应用仅设置歌曲的试听时长而不是完整时长,用户在播控中心触发进度控制时,应用收到的时长也是VIP试听时长内的相对时间戳位置,而不是完整歌曲的绝对时间戳位置,应用需要重新计算歌曲从零开始的绝对时间戳进行实际响应处理。
338
339    (2)如果应用设置完整歌曲时长,但需要系统支持试听片段,也可以在播放时上报起始进度position,当收到的seek指令超过试听片段时,上报试听截止position,系统播控的进度会跟随回弹。
340
3412. 歌曲不支持试听
342
343    如果歌曲不支持试听,那么理论上应用内也不支持播放,这时可以把 duration 设置为 -1,以通知系统不显示实际的时长。
344
3453. 广告等内容的时长设置
346
347    对于有前贴广告、后贴广告的资源来说,建议这么处理:
348    - 播放广告时,单独设置广告的时长 duration。
349    - 当进入到正片播放的时候,则重新设置一次新的时长,以与广告进行区分。
350
351## 注册控制命令
352
353应用接入AVSession,可以通过注册不同的控制命令来实现播控中心界面上的控制操作,即通过on接口注册不同的控制命令参数,即可实现对应的功能。
354具体的接口参考[接口注册](../../reference/apis-avsession-kit/arkts-apis-avsession-AVSession.md#onplay10)。
355> **说明:**
356>
357> 创建AVSession后,请先注册应用支持的控制命令,再激活 Session。
358
359媒体资源支持的控制命令列表:
360
361| 控制命令 | 功能说明   |
362| ------  | -------------------------|
363| play    | 播放命令。 |
364| pause    | 暂停命令。 |
365| stop    | 停止命令。 |
366| playNext    | 播放下一首命令。 |
367| playPrevious    | 播放上一首命令。 |
368| fastForward    | 快进命令。 |
369| rewind    | 快退命令。 |
370| playFromAssetId    | 根据某个资源id进行播放命令。 |
371| seek    | 跳转命令。 |
372| setSpeed    | 设置播放速率命令。 |
373| setLoopMode    | 设置循环模式命令。 |
374| toggleFavorite    | 设置是否收藏命令。 |
375| skipToQueueItem    | 设置播放列表其中某项被选中播放的命令。 |
376| handleKeyEvent    | 设置按键事件的命令。 |
377| commonCommand    | 设置自定义控制命令。 |
378
379通话类应用支持的控制:
380
381| 控制命令 | 功能说明   |
382| ------  | -------------------------|
383| answer    | 接听电话的命令。 |
384| hangUp    | 通话挂断的命令。 |
385| toggleCallMute    | 通话静音或解除静音的命令。 |
386
387### 不支持命令的处理
388
389系统支持的控制命令对于不支持的控制,比如应用不支持“上一首”的命令处理,只需要使用off 接口注销对应的控制命令,系统的播控中心会相应的对该控制界面进行置灰处理,以明确告知用户此控制命令不支持。
390
391```ts
392import { avSession as AVSessionManager } from '@kit.AVSessionKit';
393
394@Entry
395@Component
396struct Index {
397  @State message: string = 'hello world';
398
399  build() {
400    Column() {
401      Text(this.message)
402        .onClick(async () => {
403          let context = this.getUIContext().getHostContext() as Context;
404          // 假设已经创建了一个session,如何创建session可以参考之前的案例。
405          let type: AVSessionManager.AVSessionType = 'audio';
406          let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
407
408          // 取消指定session下的相关监听。
409          session.off('play');
410          session.off('pause');
411          session.off('stop');
412          session.off('playNext');
413          session.off('playPrevious');
414        })
415    }
416    .width('100%')
417    .height('100%')
418  }
419}
420```
421
422### 快进快退
423
424系统支持三种快进/快退的时长,应用可以通过接口进行设置;同时注册快进/快退的回调命令,以响应控制。
425
426> **说明:**
427>
428> 应用注册快进/快退及上/下一首资源切换的命令时,在播控中心的显示存在实际差异。
429
430- **当AVSessionType是audio时:**
431
432    | 应用注册的事件组合 | 播放中心显示按钮 | 按钮是否可用 |
433    | ------------ | ------------ | ------------ |
434    | 未注册任何事件 | “上一首”、“下一首” | 所有按钮置灰,无法点击。 |
435    | 注册上一首/下一首事件 | “上一首”、“下一首” | 注册上一首事件 →“上一首”按钮可用。<br>注册下一首事件 →“下一首”按钮可用。<br>未注册对应事件的按钮不可用。  |
436    | 注册快进/快退事件 | “上一首”、“下一首”|  所有按钮置灰,无法点击。 |
437    | 注册上一首/下一首及快进/快退事件 | “上一首”、“下一首” | 注册上一首事件 →“上一首”按钮可用。<br>注册下一首事件 →“下一首”按钮可用。<br>未注册对应事件的按钮不可用。  |
438
439- **当AVSessionType是video时:**
440
441    | 应用注册的事件组合 | 播放中心显示按钮 | 按钮是否可用 |
442    | ------------ | ------------ | ------------ |
443    | 未注册任何事件 | “快进”、“快退” | 所有按钮置灰,无法点击。 |
444    | 注册上一首/下一首事件 | “上一首”、“下一首” | 注册上一首事件 →“上一首”按钮可用。<br>注册下一首事件 →“下一首”按钮可用。<br>未注册对应事件的按钮不可用。  |
445    | 注册快进/快退事件 | “快进”、“快退”|  注册快进事件 →“快进”按钮可用。<br>注册快退事件 →“快退”按钮可用。<br>未注册对应事件的按钮不可用。 |
446    | 注册上一首/下一首及快进/快退事件 | “快进”、“快退”|  注册快进事件 →“快进”按钮可用。<br>注册快退事件 →“快退”按钮可用。<br>未注册对应事件的按钮不可用。 |
447
448  ```ts
449  import { avSession as AVSessionManager } from '@kit.AVSessionKit';
450  import { BusinessError } from '@kit.BasicServicesKit';
451
452  @Entry
453  @Component
454  struct Index {
455    @State message: string = 'hello world';
456
457    build() {
458      Column() {
459        Text(this.message)
460          .onClick(async () => {
461            let context = this.getUIContext().getHostContext() as Context;
462            // 假设已经创建了一个session,如何创建session可以参考之前的案例。
463            let type: AVSessionManager.AVSessionType = 'audio';
464            let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
465
466            // 设置支持的快进快退的时长设置给AVSession。
467            let metadata: AVSessionManager.AVMetadata = {
468              assetId: '0', // 由应用指定,用于标识应用媒体库里的媒体。
469              title: 'TITLE',
470              mediaImage: 'IMAGE',
471              skipIntervals: AVSessionManager.SkipIntervals.SECONDS_10,
472            };
473            session.setAVMetadata(metadata).then(() => {
474              console.info(`SetAVMetadata successfully`);
475            }).catch((err: BusinessError) => {
476              console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
477            });
478
479            session.on('fastForward', (time ?: number) => {
480              console.info(`on fastForward , do fastForward task`);
481              // do some tasks ···
482            });
483            session.on('rewind', (time ?: number) => {
484              console.info(`on rewind , do rewind task`);
485              // do some tasks ···
486            });
487          })
488      }
489      .width('100%')
490      .height('100%')
491    }
492  }
493  ```
494
495### 收藏
496
497音乐类应用实现收藏功能,那么需要注册收藏的控制响应[on('toggleFavorite')](../../reference/apis-avsession-kit/arkts-apis-avsession-AVSession.md#ontogglefavorite10)。
498
499```ts
500import { avSession as AVSessionManager } from '@kit.AVSessionKit';
501import { BusinessError } from '@kit.BasicServicesKit';
502
503@Entry
504@Component
505struct Index {
506  @State message: string = 'hello world';
507
508  build() {
509    Column() {
510      Text(this.message)
511        .onClick(async () => {
512          let context = this.getUIContext().getHostContext() as Context;
513          // 假设已经创建了一个session,如何创建session可以参考之前的案例。
514          let type: AVSessionManager.AVSessionType = 'audio';
515          let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
516          session.on('toggleFavorite', (assetId) => {
517            console.info(`on toggleFavorite `);
518            // 应用收到收藏命令,进行收藏处理。
519
520            // 应用内完成或者取消收藏,把新的收藏状态设置给AVSession。
521            let playbackState: AVSessionManager.AVPlaybackState = {
522              isFavorite: true,
523            };
524            session.setAVPlaybackState(playbackState).then(() => {
525              console.info(`SetAVPlaybackState successfully`);
526            }).catch((err: BusinessError) => {
527              console.error(`SetAVPlaybackState BusinessError: code: ${err.code}, message: ${err.message}`);
528            });
529          });
530        })
531    }
532    .width('100%')
533    .height('100%')
534  }
535}
536```
537
538### 循环模式
539
540针对音乐类应用,系统的播控中心界面会默认展示循环模式的控制操作,目前系统支持四种固定的循环模式控制,参考: [LoopMode](../../reference/apis-avsession-kit/arkts-apis-avsession-e.md#loopmode10)。
541
542播控中心支持固定的四种循环模式的切换,即: 随机播放、顺序播放、单曲循环、列表循环。应用收到循环模式切换的指令并切换后,需要向系统上报切换后的LoopMode。
543若应用内支持的LoopMode不在系统固定的四个循环模式内,需要选择四个固定循环模式其一向系统上报,由应用自定。
544
545实现参考:
546
547```ts
548import { avSession as AVSessionManager } from '@kit.AVSessionKit';
549import { BusinessError } from '@kit.BasicServicesKit';
550
551@Entry
552@Component
553struct Index {
554  @State message: string = 'hello world';
555
556  build() {
557    Column() {
558      Text(this.message)
559        .onClick(async () => {
560          let context = this.getUIContext().getHostContext() as Context;
561          // 假设已经创建了一个session,如何创建session可以参考之前的案例。
562          let type: AVSessionManager.AVSessionType = 'audio';
563          let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
564
565          // 应用启动时/内部切换循环模式,需要把应用内的当前的循环模式设置给AVSession。
566          let playBackState: AVSessionManager.AVPlaybackState = {
567            loopMode: AVSessionManager.LoopMode.LOOP_MODE_SINGLE,
568          };
569          session.setAVPlaybackState(playBackState).then(() => {
570            console.info(`set AVPlaybackState successfully`);
571          }).catch((err: BusinessError) => {
572            console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
573          });
574
575          // 应用注册循环模式的控制监听。
576          session.on('setLoopMode', (mode) => {
577            console.info(`on setLoopMode ${mode}`);
578            // 应用收到设置循环模式的指令后,应用自定下一个模式,切换完毕后通过AVPlaybackState上报切换后的LoopMode。
579            let playBackState: AVSessionManager.AVPlaybackState = {
580              loopMode: AVSessionManager.LoopMode.LOOP_MODE_SINGLE,
581            };
582            session.setAVPlaybackState(playBackState).then(() => {
583              console.info(`set AVPlaybackState successfully`);
584            }).catch((err: BusinessError) => {
585              console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
586            });
587          });
588        })
589    }
590    .width('100%')
591    .height('100%')
592  }
593}
594```
595
596### 进度控制
597
598应用如果支持进度显示,进一步也可以支持进度控制。应用需要响应seek的控制命令,那么当用户在播控中心的界面上进行拖动操作时,应用就会收到对应的回调。参考实现:
599
600```ts
601import { avSession as AVSessionManager } from '@kit.AVSessionKit';
602
603@Entry
604@Component
605struct Index {
606  @State message: string = 'hello world';
607
608  build() {
609    Column() {
610      Text(this.message)
611        .onClick(async () => {
612          let context = this.getUIContext().getHostContext() as Context;
613          // 假设已经创建了一个session,如何创建session可以参考之前的案例。
614          let type: AVSessionManager.AVSessionType = 'audio';
615          let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
616
617          session.on('seek', (position: number) => {
618            console.info(`on seek , the time is ${JSON.stringify(position)}`);
619
620            // 由于应用内seek可能会触发较长的缓冲等待,可以先把状态设置为 Buffering。
621            let playbackState: AVSessionManager.AVPlaybackState = {
622              state: AVSessionManager.PlaybackState.PLAYBACK_STATE_BUFFERING, // 缓冲状态。
623            };
624            session.setAVPlaybackState(playbackState, (err) => {
625              if (err) {
626                console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
627              } else {
628                console.info(`SetAVPlaybackState successfully`);
629              }
630            });
631
632            // 应用响应seek命令,使用应用内播放器完成seek实现。
633
634            // 应用内更新新的位置后,也需要同步更新状态给系统。
635            playbackState.state = AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY; // 播放状态。
636            playbackState.position = {
637              elapsedTime: position, // 已经播放的位置,以ms为单位。
638              updateTime: new Date().getTime(), // 应用更新当前位置的时间戳,以ms为单位。
639            }
640            session.setAVPlaybackState(playbackState, (err) => {
641              if (err) {
642                console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
643              } else {
644                console.info(`SetAVPlaybackState successfully`);
645              }
646            });
647          });
648        })
649    }
650    .width('100%')
651    .height('100%')
652  }
653}
654```
655
656## 适配媒体通知
657
658当前系统不直接向应用提供主动发送媒体控制通知的接口,那么当应用正确接入媒体播控中心并进入播放状态时,系统会自动发送通知,同时在通知和锁屏界面进行展示。
659
660> **说明:**
661>
662> 通知中心、锁屏下的播控卡片的展示,由系统进行发送,并控制相应的生命周期。
663
664## 适配蓝牙按键与有线按键事件
665
666当前系统不直接向应用提供监听多模按键事件的接口,应用如需要监听蓝牙与有线耳机的媒体按键事件,可以通过注册AVSession的控制指令来实现。AVSession提供了如下两种实现方式:
667- 方式一(推荐使用):
668  按照应用业务需求,正确接入媒体播控中心,[注册需要的控制指令](#注册控制命令)并实现对应的功能。AVSession会监听多模按键事件,将其转换为AVSession的控制指令发送回应用。应用无须区分不同的按键事件,按照AVSession的回调处理即可。按照此方式接入播放暂停,也等同于适配了蓝牙耳机的佩戴检测,在双耳佩戴与摘下时也会收到如下播放暂停控制指令。目前支持转换的AVSession控制指令如下:
669  | 控制命令 | 功能说明   |
670  | ------  | -------------------------|
671  | play    | 播放命令。 |
672  | pause    | 暂停命令。 |
673  | stop    | 停止命令。 |
674  | playNext    | 播放下一首命令。 |
675  | playPrevious    | 播放上一首命令。 |
676  | fastForward    | 快进命令。 |
677  | rewind    | 快退命令。 |
678
679```ts
680import { avSession as AVSessionManager } from '@kit.AVSessionKit';
681import { BusinessError } from '@kit.BasicServicesKit';
682
683@Entry
684@Component
685struct Index {
686  @State message: string = 'hello world';
687
688  build() {
689    Column() {
690      Text(this.message)
691        .onClick(async () => {
692          let context = this.getUIContext().getHostContext() as Context;
693          let type: AVSessionManager.AVSessionType = 'audio';
694          let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
695          // 设置必要的媒体信息,务必设置,否则接收不到控制事件。
696          let metadata: AVSessionManager.AVMetadata = {
697            assetId: '0', // 由应用指定,用于标识应用媒体库里的媒体。
698            title: 'TITLE',
699            mediaImage: 'IMAGE',
700            artist: 'ARTIST'
701          };
702          session.setAVMetadata(metadata).then(() => {
703            console.info(`SetAVMetadata successfully`);
704          }).catch((err: BusinessError) => {
705            console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
706          });
707          // 一般在监听器中会对播放器做相应逻辑处理。
708          // 不要忘记处理完后需要通过set接口同步播放相关信息,参考上面的用例。
709          session.on('play', () => {
710            console.info(`on play , do play task`);
711            // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('play')取消监听。
712            // 处理完毕后,请使用SetAVPlayState上报播放状态。
713          });
714          session.on('pause', () => {
715            console.info(`on pause , do pause task`);
716            // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('pause')取消监听。
717            // 处理完毕后,请使用SetAVPlayState上报播放状态。
718          });
719        })
720    }
721    .width('100%')
722    .height('100%')
723  }
724}
725```
726
727- 方式二:
728  通过AVSession注册[HandleMediaKeyEvent](../../reference/apis-avsession-kit/arkts-apis-avsession-AVSession.md#onhandlekeyevent10)指令。该回调接口会直接转发媒体按键事件[KeyEvent](../../reference/apis-input-kit/js-apis-keyevent.md)。应用需要自行识别按键事件的类型,并响应事件实现对应的功能。目前支持转发的按键事件类型如下:
729
730  | 按键类型([KeyCode](../../reference/apis-input-kit/js-apis-keycode.md#keycode)) | 功能说明   |
731  | ------  | -------------------------|
732  | KEYCODE_MEDIA_PLAY_PAUSE    | 多媒体键:播放/暂停 |
733  | KEYCODE_MEDIA_STOP    | 多媒体键:停止 |
734  | KEYCODE_MEDIA_NEXT    | 多媒体键:下一首 |
735  | KEYCODE_MEDIA_PREVIOUS    | 多媒体键:上一首 |
736  | KEYCODE_MEDIA_REWIND    | 多媒体键:快退 |
737  | KEYCODE_MEDIA_FAST_FORWARD    | 	多媒体键:快进 |
738  | KEYCODE_MEDIA_PLAY    | 多媒体键:播放 |
739  | KEYCODE_MEDIA_PAUSE   | 多媒体键:暂停|
740
741```ts
742import { avSession as AVSessionManager } from '@kit.AVSessionKit';
743import { BusinessError } from '@kit.BasicServicesKit';
744
745@Entry
746@Component
747struct Index {
748  @State message: string = 'hello world';
749
750  build() {
751    Column() {
752      Text(this.message)
753        .onClick(async () => {
754          let context = this.getUIContext().getHostContext() as Context;
755          let type: AVSessionManager.AVSessionType = 'audio';
756          let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
757          // 设置必要的媒体信息,务必设置,否则接收不到按键事件。
758          let metadata: AVSessionManager.AVMetadata = {
759            assetId: '0', // 由应用指定,用于标识应用媒体库里的媒体。
760            title: 'TITLE',
761            mediaImage: 'IMAGE',
762            artist: 'ARTIST'
763          };
764          session.setAVMetadata(metadata).then(() => {
765            console.info(`SetAVMetadata successfully`);
766          }).catch((err: BusinessError) => {
767            console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
768          });
769          session.on('handleKeyEvent', (event) => {
770            // 解析keycode,应用需要根据keycode对播放器做相应逻辑处理。
771            console.info(`on handleKeyEvent, keyCode=${event.key.code}`);
772          });
773        })
774    }
775    .width('100%')
776    .height('100%')
777  }
778}
779```
780
781> **说明:**
782>
783> 1. 方式一与方式二均需正确设置媒体信息AVMetadata并注册相应控制接口,否则会无法接收到控制指令与按键事件。
784> 2. 方式一与方式二,选择其一接入即可,无须同时接入,系统推荐按照方式一接入。
785
786<!--RP2-->
787<!--RP2End-->