Video播放网络视频异常的常见问题
==================

#### 问题现象

Video组件常用于视频播放中，本文总结了四种使用Video播放网络视频时的常见问题及解决方案：

1.  网络视频无法加载显示。
2.  播放进度跳转失败。
3.  下载网络视频后立即播放失败。
4.  视频缩略图展示失败。

#### 背景知识

HarmonyOS开发文档：

*   [setCurrentTime](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-media-components-video#setcurrenttime8)：指定视频播放的进度位置，并指定跳转模式。
*   [HttpRequest.request](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-http#request-1)：根据URL地址和相关配置项，发起HTTP网络请求，使用callback方式作为异步方法。
*   [HttpRequest.requestInStream](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-http#requestinstream10-1)：根据URL地址和相关配置项，发起HTTP网络请求并返回流式响应，使用callback方式作为异步方法。

#### 解决方案

**场景一：在真机上使用Video播放网络视频失败。**

*   **分析原因**：网络资源访问需申请网络权限。
*   **解决方案**：在工程的module.json5文件中添加[网络权限ohos.permission.INTERNET](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/graphics-accelerate-assetdownload-prepare)后，可正常播放。

**场景二：使用setCurrentTime(value)或拖动视频跳转指定时间点失败，会回到最开始的位置。**

*   **分析原因**：
    1.  [setCurrentTime](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-media-components-video#setcurrenttime)没有设置跳转模式，很大概率跳转至关键帧造成问题。
    2.  Video组件当前缓存的播放时间低于跳转的位置，就会出现跳转时没反应。
*   **解决方案**：使用[setCurrentTime](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-media-components-video#setcurrenttime8)方法跳转且确保value值小于缓存的播放时间。

**场景三：下载网络视频文件到沙箱，使用VideoController.start()播放失败。**

*   **分析原因**：设置视频数据源后，视频播放地址更新到组件上有延迟。
*   **解决方案**：使用setTimeout(()=>{this.videoController.start()},100)方法延迟播放。

**场景四：Video组件获取网络视频的缩略图失败**。

*   **分析原因**：无法直接获取网络视频的缩略图。
*   **解决方案**：
    *   **方案一：**通过配置[VideoOptions](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-media-components-video#videooptions对象说明)参数提供的posterOptions对象中的showFirstFrame为true，可以显示视频起播首帧，而需要获取任意一帧图像可参考方案二。
    *   **方案二：**在aboutToAppear中通过request将视频下载到本地，通过testFetchFrameByTime函数获取缩略图后，通过Video组件的previewUri参数显示缩略图。
        
        下载网络视频到本地完整代码如下：
        
        import { BusinessError } from '@kit.BasicServicesKit';
        import { http } from '@kit.NetworkKit';
        import { fileIo as fs, fileUri } from '@kit.CoreFileKit';
        import { media } from '@kit.MediaKit';
        import image from '@ohos.multimedia.image';
        
        @Component
        @Entry
        struct Index {
          private controller: VideoController = new VideoController();
          @State videoSrc: string = '';
          @State pixelMap: image.PixelMap | undefined = undefined;
          private uiContext: Context | undefined = this.getUIContext().getHostContext();
        
          saveHttpVideo(url: string) {
            http.createHttp().request(url,
              {
                method: http.RequestMethod.GET,
                connectTimeout: 60000,
                readTimeout: 60000,
                maxLimit: 1000 \* 1024 \* 1024,
                expectDataType: 2,
              },
              async (error: BusinessError, data: http.HttpResponse) => {
                if (error) {
                  console.error(\`http request failed with. Code: ${error.code}, message: ${error.message}\`);
                } else {
                  if (http.ResponseCode.OK === data.responseCode) {
                    let buffer: ArrayBuffer = data.result as ArrayBuffer;
                    try {
                      const dateStr = (new Date().getTime()).toString();
                      let path = this.uiContext?.filesDir + '/' + dateStr + '.mp4';
                      this.videoSrc = fileUri.getUriFromPath(path);
                      let file = await fs.open(path, fs.OpenMode.READ\_WRITE | fs.OpenMode.CREATE);
                      try {
                       _ // 写入文件_
                        await fs.write(file.fd, buffer);
                        this.getUIContext().getPromptAction().showToast({ message: '下载完成' });
                        this.testFetchFrameByTime(path);
                        console.log(\`videoPath = ${path}\`);
                      } finally {
                      _  // 关闭文件_
                        await fs.close(file.fd);
                      }
                    } catch (error) {
                      console.error(\`error is ${JSON.stringify(error)}\`);
                    }
                  } else {
                    console.error(\`error occurred when image downloaded!\`);
                  }
                }
              });
          }
        
          async testFetchFrameByTime(filePath: string) {
          _  // 创建AVImageGenerator对象_
            let avImageGenerator: media.AVImageGenerator = await media.createAVImageGenerator();
            let file = fs.openSync(filePath, fs.OpenMode.READ\_ONLY);
            try {
              let avFileDescriptor: media.AVFileDescriptor = { fd: file.fd };
              avImageGenerator.fdSrc = avFileDescriptor;
            _  // 初始化入参_
              let timeUs = 6000000;
              let queryOption = media.AVImageQueryOptions.AV\_IMAGE\_QUERY\_NEXT\_SYNC;
              let param: media.PixelMapParams = { width: 300, height: 400, };
             _ // 获取缩略图（promise模式）_
              this.pixelMap = await avImageGenerator.fetchFrameByTime(timeUs, queryOption, param);
           _   // 释放资源（promise模式）_
              avImageGenerator.release();
            } finally {
              fs.closeSync(file);
            }
          }
        
          aboutToAppear(): void {
            this.saveHttpVideo('xxx.mp4');
          }
        
          build() {
            Column({ space: 20 }) {
              Video({
                src: this.videoSrc,
                previewUri: this.pixelMap,
                controller: this.controller,
              })
                .width('100%')
                .height(300)
                .autoPlay(false)
                .objectFit(ImageFit.Contain)
                .controls(true)
                .onError((err) => {
                _  // 通过onError事件获取错误码，code为错误码，message为错误信息。_
                  console.error(\`code is ${err.code}, message is ${err.message}\`);
                });
            };
          }
        }
        

#### 常见FAQ

Q：在视频下载到沙盒并获取缩略图时报错：http请求失败，代码：2300023，消息：将接收到的数据写入磁盘/应用程序失败。

A：http发起请求的响应消息的最大字节限制默认值是510241024（5MB），视频过大会导致报错，设置响应数据最大字节限制为100M即可。超过100M最大限制后，用[requestInStream](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-http#requestinstream10-2)，流式返回，没有大小限制，但是也要关注手机内存。或者使用[request.downloadFile](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-request#requestdownloadfile9-1)完成下载功能。

Q：如何获取网络视频的缓冲进度？

A：推荐[使用AVPlayer播放器](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/video-playback)播放视频，订阅音视频缓存更新事件[on('bufferingUpdate')](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-media-avplayer#onbufferingupdate9)。