video-playback.md 17.5 KB
Newer Older
W
wusongqing 已提交
1
# 视频播放开发指导
2

W
wusongqing 已提交
3
## 场景介绍
4

W
wusongqing 已提交
5
视频播放的主要工作是将视频数据转码并输出到设备进行播放,同时管理播放任务。本文将对视频播放全流程、视频切换、视频循环播放等场景开发进行介绍说明。
6

W
wusongqing 已提交
7
**图1** 视频播放状态机
8

W
wusongqing 已提交
9
![zh-ch_image_video_state_machine](figures/zh-ch_image_video_state_machine.png)
10

Z
zengyawen 已提交
11

12

W
wusongqing 已提交
13
**图2** 视频播放零层图
14

W
wusongqing 已提交
15
![zh-ch_image_video_player](figures/zh-ch_image_video_player.png)
16

W
wusongqing 已提交
17
*注意:视频播放需要显示、音频、编解码等硬件能力。
18

W
wusongqing 已提交
19 20 21
1. 三方应用从Xcomponent组件获取surfaceID。
2. 三方应用把surfaceID传递给VideoPlayer JS。
3. 媒体服务把帧数据flush给surface buffer。
22

23 24 25 26 27 28 29 30 31 32 33 34 35
## 兼容性说明

推荐使用视频软件主流的播放格式和主流分辨率,不建议开发者自制非常或者异常码流,以免产生无法播放、卡住、花屏等兼容性问题。若发生此类问题不会影响系统,退出码流播放即可。

主流的播放格式和主流分辨率如下:

| 视频容器规格 |                     规格描述                      |               分辨率               |
| :----------: | :-----------------------------------------------: | :--------------------------------: |
|     mp4      | 视频格式:H264/MPEG2/MPEG4/H263 音频格式:AAC/MP3 | 主流分辨率,如1080P/720P/480P/270P |
|     mkv      | 视频格式:H264/MPEG2/MPEG4/H263 音频格式:AAC/MP3 | 主流分辨率,如1080P/720P/480P/270P |
|      ts      |   视频格式:H264/MPEG2/MPEG4 音频格式:AAC/MP3    | 主流分辨率,如1080P/720P/480P/270P |
|     webm     |          视频格式:VP8 音频格式:VORBIS           | 主流分辨率,如1080P/720P/480P/270P |

W
wusongqing 已提交
36
## 开发步骤
37

Z
zengyawen 已提交
38
详细API含义可参考:[媒体服务API文档VideoPlayer](../reference/apis/js-apis-media.md)
39

W
wusongqing 已提交
40
### 全流程场景
41

W
wusongqing 已提交
42
包含流程:创建实例,设置url,设置SurfaceId,准备播放视频,播放视频,暂停播放,获取轨道信息,跳转播放位置,设置音量,设置倍速,结束播放,重置,释放资源等流程。
43

W
wusongqing 已提交
44
VideoPlayer支持的url媒体源输入类型可参考:[url属性说明](../reference/apis/js-apis-media.md#videoplayer_属性)
45

Z
zengyawen 已提交
46
Xcomponent创建方法可参考:[Xcomponent创建方法](#xcomponent创建方法)
47

48 49
*注意:SetSurface需要在设置url和Prepare之间

50
```js
51 52 53
import media from '@ohos.multimedia.media'
import fileIO from '@ohos.fileio'

W
wusongqing 已提交
54 55
let videoPlayer = undefined; // 用于保存createVideoPlayer创建的对象
let surfaceID = undefined; // 用于保存Xcomponent接口返回的surfaceID
56

57 58 59 60 61 62
// 调用Xcomponent的接口用于获取surfaceID,并保存在surfaceID变量中,该接口由XComponent组件默认加载,非主动调用
LoadXcomponent() {
	surfaceID = this.$element('Xcomponent').getXComponentSurfaceId();
    console.info('LoadXcomponent surfaceID is' + surfaceID);
}

W
wusongqing 已提交
63
// 函数调用发生错误时用于上报错误信息
64 65 66 67 68 69
function failureCallback(error) {
    console.info(`error happened,error Name is ${error.name}`);
    console.info(`error happened,error Code is ${error.code}`);
    console.info(`error happened,error Message is ${error.message}`);
}

W
wusongqing 已提交
70
// 当函数调用发生异常时用于上报错误信息
71 72 73 74 75 76
function catchCallback(error) {
    console.info(`catch error happened,error Name is ${error.name}`);
    console.info(`catch error happened,error Code is ${error.code}`);
    console.info(`catch error happened,error Message is ${error.message}`);
}

W
wusongqing 已提交
77
// 用于打印视频轨道信息
78 79 80 81 82 83 84 85
function printfDescription(obj) {
    for (let item in obj) {
        let property = obj[item];
        console.info('key is ' + item);
        console.info('value is ' + property);
    }
}

W
wusongqing 已提交
86
// 调用createVideoPlayer接口返回videoPlayer实例对象
87 88 89 90 91 92 93 94 95
await media.createVideoPlayer().then((video) => {
    if (typeof (video) != 'undefined') {
        console.info('createVideoPlayer success!');
        videoPlayer = video;
    } else {
        console.info('createVideoPlayer fail!');
    }
}, failureCallback).catch(catchCallback);

96 97
// 用户选择视频设置fd(本地播放)
let fdPath = 'fd://'
98 99
// path路径的码流可通过"hdc file send D:\xxx\01.mp3 /data/accounts/account_0/appdata" 命令,将其推送到设备上
let path = '/data/accounts/account_0/appdata/ohos.xxx.xxx.xxx/01.mp4';
100 101 102 103 104 105 106 107
await fileIO.open(path).then(fdNumber) => {
   fdPath = fdPath + '' + fdNumber;
   console.info('open fd sucess fd is' + fdPath);
}, (err) => {
   console.info('open fd failed err is' + err);
}),catch((err) => {
   console.info('open fd failed err is' + err);
});
W
wusongqing 已提交
108

109
videoPlayer.url = fdPath;
W
wusongqing 已提交
110 111

// 设置surfaceID用于显示视频画面
112 113 114 115
await videoPlayer.setDisplaySurface(surfaceID).then(() => {
    console.info('setDisplaySurface success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
116
// 调用prepare完成播放前准备工作
117 118 119 120
await videoPlayer.prepare().then(() => {
    console.info('prepare success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
121
// 调用play接口正式开始播放
122 123 124 125
await videoPlayer.play().then(() => {
    console.info('play success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
126
// 暂停播放
127 128 129 130
await videoPlayer.pause().then(() => {
    console.info('pause success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
131
// 通过promise回调方式获取视频轨道信息
132 133 134 135 136 137 138 139 140 141 142 143 144
let arrayDescription;
await videoPlayer.getTrackDescription().then((arrlist) => {
    if (typeof (arrlist) != 'undefined') {
        arrayDescription = arrlist;
    } else {
        console.log('video getTrackDescription fail');
    }
}, failureCallback).catch(catchCallback);

for (let i = 0; i < arrayDescription.length; i++) {
    printfDescription(arrayDescription[i]);
}

W
wusongqing 已提交
145
// 跳转播放时间到50s位置,具体入参意义请参考接口文档
146
let seekTime = 50000;
147
await videoPlayer.seek(seekTime, media.SeekMode._NEXT_SYNC).then((seekDoneTime) => {
148 149 150
    console.info('seek success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
151
// 音量设置接口,具体入参意义请参考接口文档
152 153 154 155 156
let volume = 0.5;
await videoPlayer.setVolume(volume).then(() => {
    console.info('setVolume success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
157
// 倍速设置接口,具体入参意义请参考接口文档
158
let speed = media.PlaybackRateMode.SPEED_FORWARD_2_00_X;
159 160 161 162
await videoPlayer.setSpeed(speed).then(() => {
    console.info('setSpeed success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
163
// 结束播放
164 165 166 167
await videoPlayer.stop().then(() => {
    console.info('stop success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
168
// 重置播放配置
169 170 171 172
await videoPlayer.reset().then(() => {
    console.info('reset success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
173
// 释放播放资源
174 175 176 177
await videoPlayer.release().then(() => {
    console.info('release success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
178
// 相关对象置undefined
179 180 181 182
videoPlayer = undefined;
surfaceID = undefined;
```

W
wusongqing 已提交
183
### 正常播放场景
184 185

```js
186 187 188
import media from '@ohos.multimedia.media'
import fileIO from '@ohos.fileio'

W
wusongqing 已提交
189 190
let videoPlayer = undefined; // 用于保存createVideoPlayer创建的对象
let surfaceID = undefined; // 用于保存Xcomponent接口返回的surfaceID
191

192 193 194 195 196 197
// 调用Xcomponent的接口用于获取surfaceID,并保存在surfaceID变量中,该接口由XComponent组件默认加载,非主动调用
LoadXcomponent() {
	surfaceID = this.$element('Xcomponent').getXComponentSurfaceId();
    console.info('LoadXcomponent surfaceID is' + surfaceID);
}

W
wusongqing 已提交
198
// 函数调用发生错误时用于上报错误信息
199 200 201 202 203 204
function failureCallback(error) {
    console.info(`error happened,error Name is ${error.name}`);
    console.info(`error happened,error Code is ${error.code}`);
    console.info(`error happened,error Message is ${error.message}`);
}

W
wusongqing 已提交
205
// 当函数调用发生异常时用于上报错误信息
206 207 208 209 210 211
function catchCallback(error) {
    console.info(`catch error happened,error Name is ${error.name}`);
    console.info(`catch error happened,error Code is ${error.code}`);
    console.info(`catch error happened,error Message is ${error.message}`);
}

W
wusongqing 已提交
212
// 设置'playbackCompleted'事件回调,播放完成触发
213 214 215 216 217 218 219 220 221 222 223 224 225
function SetCallBack(videoPlayer) {
	videoPlayer.on('playbackCompleted', () => {
        console.info('video play finish');
        
        await videoPlayer.release().then(() => {
    		console.info('release success');
		}, failureCallback).catch(catchCallback);

		videoPlayer = undefined;
        surfaceID = undefined;
    });
}

W
wusongqing 已提交
226
// 调用createVideoPlayer接口返回videoPlayer实例对象
227 228 229 230 231 232 233 234 235
await media.createVideoPlayer().then((video) => {
    if (typeof (video) != 'undefined') {
        console.info('createVideoPlayer success!');
        videoPlayer = video;
    } else {
        console.info('createVideoPlayer fail!');
    }
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
236
// 设置事件回调
237 238
SetCallBack(videoPlayer);

239 240
// 用户选择视频设置fd(本地播放)
let fdPath = 'fd://'
241 242
// path路径的码流可通过"hdc file send D:\xxx\01.mp3 /data/accounts/account_0/appdata" 命令,将其推送到设备上
let path = '/data/accounts/account_0/appdata/ohos.xxx.xxx.xxx/01.mp4';
243 244 245 246 247 248 249 250
await fileIO.open(path).then(fdNumber) => {
   fdPath = fdPath + '' + fdNumber;
   console.info('open fd sucess fd is' + fdPath);
}, (err) => {
   console.info('open fd failed err is' + err);
}),catch((err) => {
   console.info('open fd failed err is' + err);
});
W
wusongqing 已提交
251

252
videoPlayer.url = fdPath;
W
wusongqing 已提交
253 254

// 设置surfaceID用于显示视频画面
255 256 257 258
await videoPlayer.setDisplaySurface(surfaceID).then(() => {
    console.info('setDisplaySurface success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
259
// 调用prepare完成播放前准备工作
260 261 262 263
await videoPlayer.prepare().then(() => {
    console.info('prepare success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
264
// 调用play接口正式开始播放
265 266 267 268 269
await videoPlayer.play().then(() => {
    console.info('play success');
}, failureCallback).catch(catchCallback);
```

W
wusongqing 已提交
270
### 切视频场景
271 272

```js
273 274 275
import media from '@ohos.multimedia.media'
import fileIO from '@ohos.fileio'

W
wusongqing 已提交
276 277
let videoPlayer = undefined; // 用于保存createVideoPlayer创建的对象
let surfaceID = undefined; // 用于保存Xcomponent接口返回的surfaceID
278

279 280 281 282 283 284
// 调用Xcomponent的接口用于获取surfaceID,并保存在surfaceID变量中,该接口由XComponent组件默认加载,非主动调用
LoadXcomponent() {
	surfaceID = this.$element('Xcomponent').getXComponentSurfaceId();
    console.info('LoadXcomponent surfaceID is' + surfaceID);
}

W
wusongqing 已提交
285
// 函数调用发生错误时用于上报错误信息
286 287 288 289 290 291
function failureCallback(error) {
    console.info(`error happened,error Name is ${error.name}`);
    console.info(`error happened,error Code is ${error.code}`);
    console.info(`error happened,error Message is ${error.message}`);
}

W
wusongqing 已提交
292
// 当函数调用发生异常时用于上报错误信息
293 294 295 296 297 298
function catchCallback(error) {
    console.info(`catch error happened,error Name is ${error.name}`);
    console.info(`catch error happened,error Code is ${error.code}`);
    console.info(`catch error happened,error Message is ${error.message}`);
}

W
wusongqing 已提交
299
// 设置'playbackCompleted'事件回调,播放完成触发
300 301 302 303 304 305 306 307 308 309 310 311 312
function SetCallBack(videoPlayer) {
	videoPlayer.on('playbackCompleted', () => {
        console.info('video play finish');
        
        await videoPlayer.release().then(() => {
    		console.info('release success');
		}, failureCallback).catch(catchCallback);

		videoPlayer = undefined;
        surfaceID = undefined;
    });
}

W
wusongqing 已提交
313
// 调用createVideoPlayer接口返回videoPlayer实例对象
314 315 316 317 318 319 320 321 322
await media.createVideoPlayer().then((video) => {
    if (typeof (video) != 'undefined') {
        console.info('createVideoPlayer success!');
        videoPlayer = video;
    } else {
        console.info('createVideoPlayer fail!');
    }
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
323
// 设置事件回调
324 325
SetCallBack(videoPlayer);

326 327
// 用户选择视频设置fd(本地播放)
let fdPath = 'fd://'
328 329
// path路径的码流可通过"hdc file send D:\xxx\01.mp3 /data/accounts/account_0/appdata" 命令,将其推送到设备上
let path = '/data/accounts/account_0/appdata/ohos.xxx.xxx.xxx/01.mp4';
330 331 332 333 334 335 336 337
await fileIO.open(path).then(fdNumber) => {
   fdPath = fdPath + '' + fdNumber;
   console.info('open fd sucess fd is' + fdPath);
}, (err) => {
   console.info('open fd failed err is' + err);
}),catch((err) => {
   console.info('open fd failed err is' + err);
});
W
wusongqing 已提交
338

339
videoPlayer.url = fdPath;
W
wusongqing 已提交
340 341

// 设置surfaceID用于显示视频画面
342 343 344 345
await videoPlayer.setDisplaySurface(surfaceID).then(() => {
    console.info('setDisplaySurface success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
346
// 调用prepare完成播放前准备工作
347 348 349 350
await videoPlayer.prepare().then(() => {
    console.info('prepare success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
351
// 调用play接口正式开始播放
352 353 354 355
await videoPlayer.play().then(() => {
    console.info('play success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
356 357
// 播放一段时间后,下发切视频指令
// 重置播放配置
358 359 360 361
await videoPlayer.reset().then(() => {
    console.info('reset success');
}, failureCallback).catch(catchCallback);

362 363
// 用户选择视频设置fd(本地播放)
let fdNextPath = 'fd://'
364 365
// path路径的码流可通过"hdc file send D:\xxx\02.mp3 /data/accounts/account_0/appdata" 命令,将其推送到设备上
let nextPath = '/data/accounts/account_0/appdata/ohos.xxx.xxx.xxx/02.mp4';
366 367 368 369 370 371 372 373 374 375
await fileIO.open(nextPath).then(fdNumber) => {
   fdNextPath = fdNextPath + '' + fdNumber;
   console.info('open fd sucess fd is' + fdNextPath);
}, (err) => {
   console.info('open fd failed err is' + err);
}),catch((err) => {
   console.info('open fd failed err is' + err);
});

videoPlayer.url = fdNextPath;
W
wusongqing 已提交
376 377

// 设置surfaceID用于显示视频画面
378 379 380 381
await videoPlayer.setDisplaySurface(surfaceID).then(() => {
    console.info('setDisplaySurface success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
382
// 调用prepare完成播放前准备工作
383 384 385 386
await videoPlayer.prepare().then(() => {
    console.info('prepare success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
387
// 调用play接口正式开始播放
388 389 390 391 392
await videoPlayer.play().then(() => {
    console.info('play success');
}, failureCallback).catch(catchCallback);
```

W
wusongqing 已提交
393
### 单个视频循环场景
394 395

```js
396 397 398
import media from '@ohos.multimedia.media'
import fileIO from '@ohos.fileio'

W
wusongqing 已提交
399 400
let videoPlayer = undefined; // 用于保存createVideoPlayer创建的对象
let surfaceID = undefined; // 用于保存Xcomponent接口返回的surfaceID
401

402 403 404 405 406 407
// 调用Xcomponent的接口用于获取surfaceID,并保存在surfaceID变量中,该接口由XComponent组件默认加载,非主动调用
LoadXcomponent() {
	surfaceID = this.$element('Xcomponent').getXComponentSurfaceId();
    console.info('LoadXcomponent surfaceID is' + surfaceID);
}

W
wusongqing 已提交
408
// 函数调用发生错误时用于上报错误信息
409 410 411 412 413 414
function failureCallback(error) {
    console.info(`error happened,error Name is ${error.name}`);
    console.info(`error happened,error Code is ${error.code}`);
    console.info(`error happened,error Message is ${error.message}`);
}

W
wusongqing 已提交
415
// 当函数调用发生异常时用于上报错误信息
416 417 418 419 420 421
function catchCallback(error) {
    console.info(`catch error happened,error Name is ${error.name}`);
    console.info(`catch error happened,error Code is ${error.code}`);
    console.info(`catch error happened,error Message is ${error.message}`);
}

W
wusongqing 已提交
422
// 设置'playbackCompleted'事件回调,播放完成触发
423 424 425 426 427 428 429 430 431 432 433 434 435
function SetCallBack(videoPlayer) {
	videoPlayer.on('playbackCompleted', () => {
        console.info('video play finish');
        
        await videoPlayer.release().then(() => {
    		console.info('release success');
		}, failureCallback).catch(catchCallback);

		videoPlayer = undefined;
        surfaceID = undefined;
    });
}

W
wusongqing 已提交
436
// 调用createVideoPlayer接口返回videoPlayer实例对象
437 438 439 440 441 442 443 444 445
await media.createVideoPlayer().then((video) => {
    if (typeof (video) != 'undefined') {
        console.info('createVideoPlayer success!');
        videoPlayer = video;
    } else {
        console.info('createVideoPlayer fail!');
    }
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
446
// 设置事件回调
447 448
SetCallBack(videoPlayer);

449 450
// 用户选择视频设置fd(本地播放)
let fdPath = 'fd://'
451 452
// path路径的码流可通过"hdc file send D:\xxx\01.mp3 /data/accounts/account_0/appdata" 命令,将其推送到设备上
let path = '/data/accounts/account_0/appdata/ohos.xxx.xxx.xxx/01.mp4';
453 454 455 456 457 458 459 460
await fileIO.open(path).then(fdNumber) => {
   fdPath = fdPath + '' + fdNumber;
   console.info('open fd sucess fd is' + fdPath);
}, (err) => {
   console.info('open fd failed err is' + err);
}),catch((err) => {
   console.info('open fd failed err is' + err);
});
W
wusongqing 已提交
461

462
videoPlayer.url = fdPath;
W
wusongqing 已提交
463 464

// 设置surfaceID用于显示视频画面
465 466 467 468
await videoPlayer.setDisplaySurface(surfaceID).then(() => {
    console.info('setDisplaySurface success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
469
// 调用prepare完成播放前准备工作
470 471 472 473
await videoPlayer.prepare().then(() => {
    console.info('prepare success');
}, failureCallback).catch(catchCallback);

W
wusongqing 已提交
474
// 设置循环播放属性
475 476
videoPlayer.loop = true;

W
wusongqing 已提交
477
// 调用play接口正式开始播放
478 479 480
await videoPlayer.play().then(() => {
    console.info('play success');
}, failureCallback).catch(catchCallback);
481 482 483 484
```

### Xcomponent创建方法

Z
zengyawen 已提交
485
播放视频中获取surfaceID依赖了Xcomponent,需要创建一个和xxx.js同名的xxx.hml文件,xxx.hml里面需要添加如下代码:
486 487 488

```js
<xcomponent id = 'Xcomponent'
Z
zengyawen 已提交
489
      if = "{{isFlush}}" // 刷新surfaceID,isFlush赋值false再赋值true为一次刷新,会主动再次加载LoadXcomponent获取新的surfaceID
490
      type = 'surface'
Z
zengyawen 已提交
491 492
      onload = 'LoadXcomponent' // 默认加载接口
      style = "width:720px;height:480px;border-color:red;border-width:5px;"> // 设置窗口宽高等属性
493 494
</xcomponent>
```