1# 使用AVPlayer播放流媒体(ArkTS)
2
3本开发指导将介绍如何使用[AVPlayer](media-kit-intro.md#avplayer)开发流媒体播放功能,以完整地播放一个流媒体视频作为示例,实现端到端播放流媒体资源。
4当前指导仅介绍如何实现流媒体播放功能,本地音视频播放等其他场景,请参考[视频播放](using-avplayer-for-playback.md)。
5
6## 流媒体支持的格式
7
8| 流媒体协议类型 | 典型链接 | 网络点播 | 网络直播 |内容保护 |
9| -------- | -------- | -------- | -------- | -------- |
10| HLS | `https://xxxx/index.m3u8` | 支持 | 支持 | 支持,详见[DRM Kit](../drm/drm-overview.md)。 |
11| DASH | `https://xxxx.mpd` | 支持 | - | 支持,详见[DRM Kit](../drm/drm-overview.md)。 |
12| HTTP/HTTPS | `https://xxxx.mp4` | 支持 | - | - |
13| HTTP-FLV | `https://xxxx.flv` | 支持 | 支持 | - |
14
15## 开发步骤
16
17创建AVPlayer,设置播放资源和窗口,设置播放参数(音量/倍速/缩放模式),播放控制(播放/暂停/跳转/停止),重置,销毁资源。在进行应用开发的过程中,开发者可以通过AVPlayer的state属性主动获取当前状态或使用on('stateChange')方法监听状态变化。如果应用在视频播放器处于错误状态时执行操作,系统可能会抛出异常或生成其他未定义的行为。状态的详细说明请参考[AVPlayerState](../../reference/apis-media-kit/js-apis-media.md#avplayerstate9)。具体的开发步骤如下:
18
191. 创建实例createAVPlayer(),AVPlayer初始化idle状态。
20
212. 设置业务需要的监听事件,搭配全流程场景使用。支持的监听事件包括:
22
23   | 事件类型 | 说明 |
24   | -------- | -------- |
25   | stateChange | 必要事件,监听播放器的state属性改变。 |
26   | error | 必要事件,监听播放器的错误信息。 |
27   | durationUpdate | 用于进度条,监听进度条长度,刷新资源时长。 |
28   | timeUpdate | 用于进度条,监听进度条当前位置,刷新当前时间。 |
29   | seekDone | 响应API调用,监听seek()请求完成情况。<br/>当使用seek()跳转到指定播放位置后,如果seek操作成功,将上报该事件。 |
30   | speedDone | 响应API调用,监听setSpeed()请求完成情况。<br/>当使用setSpeed()设置播放倍速后,如果setSpeed操作成功,将上报该事件。 |
31   | volumeChange | 响应API调用,监听setVolume()请求完成情况。<br/>当使用setVolume()调节播放音量后,如果setVolume操作成功,将上报该事件。 |
32   | bufferingUpdate | 用于网络播放,监听网络播放缓冲信息,用于上报缓冲百分比以及缓存播放进度。 |
33   | audioInterrupt | 监听音频焦点切换信息,搭配属性audioInterruptMode使用。<br/>如果当前设备存在多个音频正在播放,音频焦点被切换(即播放其他媒体如通话等)时将上报该事件,应用可以及时处理。 |
34
353. 设置资源:[使用AVPlayer设置播放URL](playback-url-setting-method.md),AVPlayer进入initialized状态。
36   > **说明:**
37   >
38   > 下面代码示例中的url仅作示意使用,开发者需根据实际情况,确认资源有效性并设置:
39   >
40   > - 使用网络播放路径,需[声明权限](../../security/AccessToken/declare-permissions.md):ohos.permission.INTERNET41   >
42   > - 需要使用支持的播放格式与协议。
43   >
44
454. 设置窗口:获取并设置属性SurfaceID,用于设置显示画面。
46
47   应用需要从XComponent组件获取surfaceID,获取方式请参考[XComponent](../../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md)。
48
495. 准备播放:调用prepare(),AVPlayer进入prepared状态,此时可以获取duration,设置缩放模式、音量等。
50
516. 视频播控:播放play(),暂停pause(),跳转seek(),停止stop() 等操作。
52
537. (可选)更换资源:调用reset()重置资源,AVPlayer重新进入idle状态,允许更换资源url。
54
558. 退出播放:调用release()销毁实例,AVPlayer进入released状态,退出播放。
56
57## 注意事项
58
59播放流媒体的标准流程如上述开发步骤所示,但使用不同的流媒体格式在实际开发的过程中还是会存在一定差异,本节将详细描述不同流媒体格式业务的差异,包括设置视频起播策略、切换音视频轨道等。
60
61### 流媒体缓冲状态
62
63当下载速率低于片源的码率时,可能会出现卡顿,此时播放器检测到缓冲区数据不足,会先缓冲一些数据再播放,避免连续卡顿。一次卡顿对应的缓冲事件上报过程为:BUFFERING_START-> BUFFERING_PERCENT 0 -> ... -> BUFFERING_PERCENT 100 -> BUFFERING_END。而CACHED_DURATION无论是卡顿过程中还是播放过程中,都会持续上报,直至下载至资源末尾。详见[BufferingInfoType缓冲事件类型枚举](../../reference/apis-media-kit/js-apis-media.md#bufferinginfotype8)。
64
65监听当前bufferingUpdate缓冲状态示例代码:
66
67```ts
68avPlayer.on('bufferingUpdate', (infoType : media.BufferingInfoType, value : number) => {
69  console.info(`AVPlayer bufferingUpdate, infoType is ${infoType}, value is ${value}.`);
70})
71```
72
73### HLS切换码率
74
75当前流媒体HLS协议流支持多码率播放,默认情况下,播放器会根据网络下载速度选择合适的码率。
76
771. 通过[on('availableBitrates')](../../reference/apis-media-kit/js-apis-media.md#onavailablebitrates9)监听当前HLS协议流可用的码率,若监听的码率列表长度为0,则不支持设置指定码率。
78
79    ```ts
80    // 创建avPlayer实例对象
81    let avPlayer: media.AVPlayer = await media.createAVPlayer();
82    // 监听当前HLS协议流可用的码率
83    avPlayer.on('availableBitrates', (bitrates: Array<number>) => {
84      consle.info('availableBitrates called, and availableBitrates length is: ' + bitrates.length);
85    })
86    ```
87
882. 通过[setBitrate](../../reference/apis-media-kit/js-apis-media.md#setbitrate9)接口设置播放码率,若用户设置的码率不在可用码率中,播放器将从可用码率中选择最小且最接近的码率。该接口只能在prepared/playing/paused/completed状态下调用,可通过监听[bitrateDone](../../reference/apis-media-kit/js-apis-media.md#onbitratedone9)事件确认是否生效。
89
90    ```ts
91    // 创建avPlayer实例对象
92    let avPlayer: media.AVPlayer = await media.createAVPlayer();
93    // 监听码率设置是否生效
94    avPlayer.on('bitrateDone', (bitrate: number) => {
95      consle.info('bitrateDone called, and bitrate value is: ' + bitrate);
96    })
97    // 设置播放码率
98    let bitrate: number = 96000;
99    avPlayer.setBitrate(bitrate);
100    ```
101
102### DASH设置视频起播策略
103
104为了保证在弱网环境下的播放体验,AVPlayer会默认选择最低的视频分辨率开始播放,随后依据网络状况自动调整。开发者可根据实际需求,自定义DASH视频的起播策略,包括设定视频的宽度、高度以及色彩格式等参数。
105
106以调节视频起播分辨率为例,下述示例代码描述了设置视频宽度1920px、高度1080px起播。此时,AVPlayer会选择MPD资源中一路分辨率为1920x1080的视频资源进行播放。
107
108```ts
109let mediaSource : media.MediaSource = media.createMediaSourceWithUrl("http://test.cn/dash/aaa.mpd",  {"User-Agent" : "User-Agent-Value"});
110let playbackStrategy : media.PlaybackStrategy = {preferredWidth: 1920, preferredHeight: 1080};
111avPlayer.setMediaSource(mediaSource, playbackStrategy);
112```
113
114### DASH切换音视频轨道
115
116DASH流媒体资源一般包含多路分辨率、码率、采样率、编码格式等参数各不相同的音频、视频和字幕资源。默认情况下,AVPlayer会依据网络状况自动切换不同码率的视频轨道。开发者可根据实际需求,自主选择指定的音视频轨道进行播放,此时自适应码率切换策略会失效。
117
1181. 设置selectTrack生效的监听事件[trackChange](../../reference/apis-media-kit/js-apis-media.md)。
119
120    ```ts
121    avPlayer.on('trackChange', (index: number, isSelect: boolean) => {
122      console.info(`trackChange info, index: ${error}, isSelect: ${isSelect}`);
123    })
124    ```
125
1262. 调用[getTrackDescription](../../reference/apis-media-kit/js-apis-media.md#gettrackdescription9)获取所有音视频轨道列表。开发者可根据实际需求,基于[MediaDescription](../../reference/apis-media-kit/js-apis-media.md#mediadescription8)各字段信息,确定目标轨道索引。
127
128    ```ts
129    // 以获取1080p视频轨道索引为例
130    public videoTrackIndex: number;
131    avPlayer.getTrackDescription((error: BusinessError, arrList: Array<media.MediaDescription>) => {
132      if (arrList != null) {
133        for (let i = 0; i < arrList.length; i++) {
134          let propertyIndex: Object = arrList[i][media.MediaDescriptionKey.MD_KEY_TRACK_INDEX];
135          let propertyType: Object = arrList[i][media.MediaDescriptionKey.MD_KEY_TRACK_TYPE];
136          let propertyWidth: Object = arrList[i][media.MediaDescriptionKey.MD_KEY_WIDTH];
137          let propertyHeight: Object = arrList[i][media.MediaDescriptionKey.MD_KEY_HEIGHT];
138          if (propertyType == media.MediaType.MEDIA_TYPE_VID && propertyWidth == 1920 && propertyHeight == 1080) {
139            videoTrackIndex = parseInt(propertyIndex.toString()); // 获取1080p视频轨道索引
140          }
141        }
142      } else {
143        console.error(`getTrackDescription fail, error:${error}`);
144      }
145    });
146    ```
147
1483. 在音视频播放过程中调用[selectTrack](../../reference/apis-media-kit/js-apis-media.md#selecttrack12)选择对应的音视频轨道,或者调用[deselectTrack](../../reference/apis-media-kit/js-apis-media.md#deselecttrack12)取消选择的音视频轨道。
149
150    ```ts
151    // 切换至目标视频轨道
152    avPlayer.selectTrack(videoTrackIndex);
153    // 取消选择目标视频轨道
154    // avPlayer.deselectTrack(videoTrackIndex);
155    ```
156
157## 异常场景说明
158
159使用avPlayer播放流媒体过程中断网:流媒体模块会根据返回的错误码、服务器请求失败的响应时间、请求次数等因素综合处理。若错误码类型属于不进行请求重试的类型,会向应用上报对应的错误码。若错误码类型需要进行请求重试,会在30s内进行至多10次的请求重试。若请求重试次数超过10次,或重试总时长超过30秒,会上向应用上报对应的错误码。若请求重试成功,则继续播放。
160
161## 完整示例
162
163参考以下示例,完整地播放一个流媒体视频。
164
165```ts
166import { media } from '@kit.MediaKit';
167import { fileIo as fs } from '@kit.CoreFileKit';
168import { common } from '@kit.AbilityKit';
169import { BusinessError } from '@kit.BasicServicesKit';
170
171export class AVPlayerDemo {
172  private count: number = 0;
173  private surfaceID: string = ''; // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法
174  private isSeek: boolean = true; // 用于区分模式是否支持seek操作
175  public audioTrackList: number[] = [];
176  public videoTrackList: number[] = [];
177
178  constructor(surfaceID: string) {
179    this.surfaceID = surfaceID;
180  }
181
182  // 注册avplayer回调函数
183  setAVPlayerCallback(avPlayer: media.AVPlayer) {
184    // startRenderFrame首帧渲染回调函数
185    avPlayer.on('startRenderFrame', () => {
186      console.info(`AVPlayer start render frame`);
187    });
188    // seek操作结果回调函数
189    avPlayer.on('seekDone', (seekDoneTime: number) => {
190      console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
191    })
192    // avPlayer.on('trackChange', (index: number, isSelect: boolean) => {
193    //   console.info(`AVPlayer track changed, track index: ${index}, isSelect: ${isSelect}`);
194    // })
195    // error回调监听函数,当avPlayer在操作过程中出现错误时调用 reset接口触发重置流程
196    avPlayer.on('error', (err: BusinessError) => {
197      console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
198      avPlayer.reset(); // 调用reset重置资源,触发idle状态
199    })
200    // 状态机变化回调函数
201    avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
202      switch (state) {
203        case 'idle': // 成功调用reset接口后触发该状态机上报
204          console.info('AVPlayer state idle called.');
205          avPlayer.release(); // 调用release接口销毁实例对象
206          break;
207        case 'initialized': // avplayer 设置播放源后触发该状态上报
208          console.info('AVPlayer state initialized called.');
209          avPlayer.surfaceId = this.surfaceID; // 设置显示画面,当播放的资源为纯音频时无需设置
210          avPlayer.prepare();
211          break;
212        case 'prepared': // prepare调用成功后上报该状态机
213          console.info('AVPlayer state prepared called.');
214          avPlayer.play(); // 调用播放接口开始播放
215          break;
216        case 'playing': // play成功调用后触发该状态机上报
217          console.info('AVPlayer state playing called.');
218          break;
219        case 'paused': // pause成功调用后触发该状态机上报
220          console.info('AVPlayer state paused called.');
221          break;
222        case 'completed': // 播放结束后触发该状态机上报
223          console.info('AVPlayer state completed called.');
224          avPlayer.stop(); //调用播放结束接口
225          break;
226        case 'stopped': // stop接口成功调用后触发该状态机上报
227          console.info('AVPlayer state stopped called.');
228          avPlayer.reset(); // 调用reset接口初始化avplayer状态
229          break;
230        case 'released':
231          console.info('AVPlayer state released called.');
232          break;
233        default:
234          console.info('AVPlayer state unknown called.');
235          break;
236      }
237    })
238    // 监听流媒体缓冲状态、缓冲百分比、已缓冲数据预估可播放时长
239    avPlayer.on('bufferingUpdate', (infoType : media.BufferingInfoType, value : number) => {
240      console.info(`AVPlayer bufferingUpdate, infoType is ${infoType}, value is ${value}.`);
241    })
242  }
243
244  // 以下demo为通过url设置网络地址来实现播放流媒体HLS点播视频
245  async avPlayerVodDemo() {
246    // 创建avPlayer实例对象
247    let avPlayer: media.AVPlayer = await media.createAVPlayer();
248    // 创建状态机变化回调函数
249    this.setAVPlayerCallback(avPlayer);
250    this.isSeek = true; // 点播支持seek操作
251    avPlayer.url = 'http://xxx.xxx.xxx.xxx:xx/xx/index.m3u8';
252  }
253
254  // 以下demo为通过url设置网络地址来实现播放流媒体HLS直播视频
255  async avPlayerLiveDemo() {
256    // 创建avPlayer实例对象
257    let avPlayer: media.AVPlayer = await media.createAVPlayer();
258    // 创建状态机变化回调函数
259    this.setAVPlayerCallback(avPlayer);
260    this.isSeek = false; // 直播不支持seek操作
261    avPlayer.url = 'http://xxx.xxx.xxx.xxx:xx/xx/index.m3u8';
262  }
263
264  // 以下demo为通过url设置网络地址来实现播放Dash流媒体视频
265  async avPlayerDashDemo() {
266    // 创建avPlayer实例对象
267    let avPlayer: media.AVPlayer = await media.createAVPlayer();
268    // 创建状态机变化回调函数
269    this.setAVPlayerCallback(avPlayer);
270    // 设置播放偏好策略
271    // let mediaSource : media.MediaSource = media.createMediaSourceWithUrl("http://test.cn/dash/aaa.mpd",  {"User-Agent" : "User-Agent-Value"});
272    // let playbackStrategy : media.PlaybackStrategy = {preferredWidth: 1, preferredHeight: 2, preferredBufferDuration: 3, preferredHdr: false};
273    // avPlayer.setMediaSource(mediaSource, playbackStrategy);
274    this.isSeek = true; // 表示支持seek操作
275    avPlayer.url = 'http://test.cn/dash/aaa.mpd'; //须替换为DASH资源实际地址
276
277    // 通过selectTrack设置音频/视频轨道,通过deselectTrack取消上次设置的音频/视频轨道并恢复到默认音频/视频轨道
278    avPlayer.getTrackDescription((error: BusinessError, arrList: Array<media.MediaDescription>) => {
279      if (arrList != null) {
280        for (let i = 0; i < arrList.length; i++) {
281          let propertyIndex: Object = arrList[i][media.MediaDescriptionKey.MD_KEY_TRACK_INDEX];
282          let propertyType: Object = arrList[i][media.MediaDescriptionKey.MD_KEY_TRACK_TYPE];
283          if (propertyType == 0) {
284            this.audioTrackList.push(parseInt(propertyIndex.toString())); // 获取音频轨道列表
285          } else if (propertyType == 1) {
286            this.videoTrackList.push(parseInt(propertyIndex.toString())); // 获取视频轨道列表
287          }
288        }
289      } else {
290        console.error(`getTrackDescription fail, error:${error}`);
291      }
292    });
293    // 选择其中一个视频轨道
294    // avPlayer.selectTrack(this.videoTrackList[0]);
295    // 取消选择的视频轨道
296    // avPlayer.deselectTrack(this.videoTrackList[0]);
297  }
298
299  // 以下demo为通过setMediaSource设置自定义头域及媒体播放优选参数实现初始播放参数设置,以流媒体Https点播为例
300  async preDownloadDemo() {
301    // 创建avPlayer实例对象
302    let avPlayer: media.AVPlayer = await media.createAVPlayer();
303    // 创建状态机变化回调函数
304    this.setAVPlayerCallback(avPlayer);
305    this.isSeek = true; // 点播支持seek操作
306    // 创建mediaSource实例对象,设置媒体来源,定制HTTP请求,如需要,可以键值对的形式设置User-Agent、Cookie、Referer等字段
307    let mediaSource : media.MediaSource = media.createMediaSourceWithUrl("https://xxx.xxx",  {"User-Agent" : "User-Agent-Value", "Cookie" : "Cookie-Value", "Referer" : "Referer-Value"});
308    // 设置播放策略,设置缓冲区数据量为20s
309    let playbackStrategy : media.PlaybackStrategy = {preferredBufferDuration: 20};
310    // 为avPlayer设置媒体来源和播放策略
311    avPlayer.setMediaSource(mediaSource, playbackStrategy);
312  }
313}
314```
315
316
317
318