DEV Community

HarmonyOS
HarmonyOS

Posted on

Video Audio Playback

Read the original article:Video Audio Playback

Video Audio Playback

Problem Description

Developers may encounter the following issues with the Video component in HarmonyOS:

  • Video playback stuttering on real devices
  • Seeking (jumping) to a specific time via dragging fails
  • The Video component fails to load online videos
  • Unable to retrieve thumbnails from network videos

This article aims to provide solutions to these problems.

Background Knowledge

HarmonyOS Documentation:

  • setCurrentTime: Specifies the playback position of a video.
  • request: Asynchronous HTTP request method that returns a result using Promise.
  • requestInStream: Initiates an HTTP request based on a URL and returns a streamed response. Asynchronous, also using Promise.

Common Scenarios:

  • Video stuttering during playback on a physical device
  • Seeking fails: calling this.controller.setCurrentTime(value) does not jump to the specified time, and may reset to the beginning
  • Video component fails to load online videos or retrieve thumbnails: videos work fine when loaded locally (e.g., from the file system), but fail when streamed online — result is a black screen.

Troubleshooting Process

Q: When downloading the video to the sandbox and getting the thumbnail, an error message appears: http request failed, code: 2300023, message: Failed to write the received data to disk/application.
A: The default value of the maximum byte limit of the response message for the http request is 510241024. If the video is too large, an error message will be reported. Set the maximum byte limit of the response data to 100M.

http.createHttp().request(url,
      {
        method: http.RequestMethod.GET,
        connectTimeout: 60000,
        readTimeout: 60000,
        maxLimit: 100 * 1024 * 1024,
        expectDataType: 2,
      },
Enter fullscreen mode Exit fullscreen mode

When the maximum limit of 100M is exceeded, use requestInStream to return in stream mode. There is no size limit, but you should also pay attention to the phone's memory. Or use request to complete the download function.

Implementation Steps

"requestPermissions": [
{
  "name": "ohos.permission.INTERNET"
}
]
Enter fullscreen mode Exit fullscreen mode

Code Snippet/ Configuration

  • Failed to drag the video to jump to the specified time point: Set the precision mode to this.controller.setCurrentTime(10, SeekMode.Accurate), and if the current cached playback time of the Video component is lower than the jump position, there will be no response when jumping. The sample code is as follows:
@Component
@Entry
struct Index {
  private controller: VideoController = new VideoController()
  build() {
    Column() {
      Video({
        src: 'https://cdn.xxxxxxx.com//h5_images/files/videos/xxxxxxxx.mp4',
        // previewUri: this.previewUris,
        controller: this.controller,
        currentProgressRate: 1
      })
        .width('100%')
        .height(300)
        .autoPlay(true)
        .objectFit(ImageFit.Contain)
        .controls(true)
      Button('xxx').onClick(() => {
        this.controller.setCurrentTime(10, SeekMode.Accurate)
      })
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The Video component cannot load online videos or get thumbnails: You need to download online videos to your local computer through a request, and then play and get thumbnails.
The sample code is as follows:

import { http } from '@kit.NetworkKit'
 import { BusinessError } from '@kit.BasicServicesKit';
 import fs from '@ohos.file.fs';
 import { promptAction } from '@kit.ArkUI';
 import { media } from '@kit.MediaKit';
 import { image } from '@kit.ImageKit';
 @Entry
 @Component
 struct VideoTest {
   @State videoPath: string = ''
   @State pixelMap: image.PixelMap | undefined = undefined;
   controller: VideoController = new VideoController();
   build() {
     Row() {
       Column() {
         Button('Download')
           .width('50%')
           .height(44)
           .onClick(() => {
             this.saveHttpVideo('https://file.xxxxxxx.net/ChildPlat/Album/20200617/Video/xxxxx.mp4')
           })
         Video({
           src: this.videoPath,
           previewUri: this.pixelMap,
           controller: this.controller
         })
           .width('100%')
           .height(600)
           .autoPlay(true)
           .controls(true)
           .loop(false)
           .objectFit(ImageFit.Contain)
       }
       .width('100%')
     }
     .height('100%')
   }
   // download
   async saveHttpVideo(url: string) {
     http.createHttp().request(url,
       {
         method: http.RequestMethod.GET,
         connectTimeout: 60000,
         readTimeout: 60000,
         expectDataType: 2,
       },
       async (error: BusinessError, data: http.HttpResponse) => {
         if (error) {
           console.error(`http reqeust 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 = getContext().filesDir + '/' + dateStr + '.mp4'
               let file = await fs.open(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
               // write to file
               await fs.write(file.fd, buffer);
               // close file
               await fs.close(file.fd);
               promptAction.showToast({message:'Download completed'})
               this.testFetchFrameByTime(path)
               this.videoPath = 'file://' + path;
               console.log("videoPath = " + path)
             } catch (error) {
               console.error("error is " + JSON.stringify(error))
             }
           } else {
             console.error("error occurred when image downloaded!")
           }
         }
       })
   }
   // Get the thumbnail
   async testFetchFrameByTime(filePath: string) {
     // Create an AVImageGenerator object
     let avImageGenerator: media.AVImageGenerator = await media.createAVImageGenerator()
     let file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
     let avFileDescriptor: media.AVFileDescriptor = { fd: file.fd };
     avImageGenerator.fdSrc = avFileDescriptor;
     // Initialize the input parameters
     let timeUs = 0
     let queryOption = media.AVImageQueryOptions.AV_IMAGE_QUERY_NEXT_SYNC
     let param: media.PixelMapParams = { width : 300, height : 400, }
     // Get thumbnail (promise mode)
     this.pixelMap = await avImageGenerator.fetchFrameByTime(timeUs, queryOption, param)
     // Release resources (promise mode)
     avImageGenerator.release()
     fs.closeSync(file);
   }
 }
Enter fullscreen mode Exit fullscreen mode
  • When the video component plays online videos and autoplay is not set to true, the video will not be played by default and the video display will be black. How to make the video component display the online video thumbnail when it does not automatically play by default: you need to download the video to the local computer in aboutToAppear, get the thumbnail, and display the thumbnail through the previewUri parameter of the video component.
  • The sample code is as follows:
import { http } from '@kit.NetworkKit'
import { BusinessError } from '@kit.BasicServicesKit';
import fs from '@ohos.file.fs';
import { media } from '@kit.MediaKit';
import { image } from '@kit.ImageKit';

@Entry
@Component
struct VideoTest {
  @State videoPath: string = ''
  @State pixelMap: image.PixelMap | undefined = undefined;
  controller: VideoController = new VideoController();
  aboutToAppear(): void {
    this.saveHttpVideo('https://xxx.mp4')
  }
  build() {
    Row() {
      Column() {
        Video({
          src: this.videoPath,
          previewUri: this.pixelMap,
          controller: this.controller
        })
          .width('100%')
          .height(600)
          .controls(true)
          .loop(false)
          .objectFit(ImageFit.Contain)
      }
      .width('100%')
    }
    .height('100%')
  }

  // Download
  async saveHttpVideo(url: string) {
    http.createHttp().request(url,
      {
        method: http.RequestMethod.GET,
        connectTimeout: 60000,
        readTimeout: 60000,
        expectDataType: 2,
      },
      async (error: BusinessError, data: http.HttpResponse) => {
        if (error) {
          console.error(`http reqeust 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 = getContext().filesDir + '/' + dateStr + '.mp4'
              let file = await fs.open(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
              // Write to file
              await fs.write(file.fd, buffer);
              // Close file
              await fs.close(file.fd);
              //promptAction.showToast({message:'Download completed'})
              this.testFetchFrameByTime(path)
              this.videoPath = 'file://' + path;
              console.log("videoPath = " + path)
            } catch (error) {
              console.error("error is " + JSON.stringify(error))
            }
          } else {
            console.error("error occurred when image downloaded!")
          }
        }
      })
  }

  // Get thumbnail
  async testFetchFrameByTime(filePath: string) {
    // Create AVImageGenerator object
    let avImageGenerator: media.AVImageGenerator = await media.createAVImageGenerator()
    let file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
    let avFileDescriptor: media.AVFileDescriptor = { fd: file.fd };
    avImageGenerator.fdSrc = avFileDescriptor;
    // Initialize input parameters
    let timeUs = 0
    let queryOption = media.AVImageQueryOptions.AV_IMAGE_QUERY_NEXT_SYNC
    let param: media.PixelMapParams = { width : 300, height : 400, }
    // Get thumbnail (promise mode)
    this.pixelMap = await avImageGenerator.fetchFrameByTime(timeUs, queryOption, param)
    // Release resources (promise mode)
    avImageGenerator.release()
    fs.closeSync(file);
  }
}
Enter fullscreen mode Exit fullscreen mode

Related Documents or Links

https://developer.huawei.com/consumer/en/doc/harmonyos-references/ts-media-components-video#events

Written by Hatice Akyel

Top comments (0)