鸿蒙选择本地视频文件,并获取首帧预览图

选择本地视频文件,并获取首帧预览图

参考文档

由于文件权限获取不太方便,现在使用的是 picker 的方式获取本地视频文件。文件位于我的手机/下载目录下。

操作分为几步:

  1. 获取文件地址;
  2. 获取视频信息;
  3. 首帧截图;
  4. 显示截图;

获取文件地址

typescript 复制代码
try {
  let context = CCAppContext.context.getHostContext()!
  let documentSelectOptions = new picker.DocumentSelectOptions();
  documentSelectOptions.fileSuffixFilters = ['视频|.mp4', '视频|.avi']
  let documentPicker = new picker.DocumentViewPicker(context);
  documentPicker.select(documentSelectOptions).then((documentSelectResult: Array<string>) => {
    console.info('DocumentViewPicker.select successfully, documentSelectResult uri: ' + JSON.stringify(documentSelectResult));
    if (documentSelectResult.length > 0) {
      let uri = documentSelectResult[0] // 这个是获取到的文件地址
  }).catch((err: BusinessError) => {
    console.error(`DocumentViewPicker.select failed with err, code is: ${err.code}, message is: ${err.message}`);
  });
} catch (error) {
  let err: BusinessError = error as BusinessError;
  console.error(`DocumentViewPicker failed with err, code is: ${err.code}, message is: ${err.message}`);
}

获取视频信息 & 获取首帧截图 & 显示图片

typescript 复制代码
try {
  // 打开视频文件获取文件描述符
  let fd = await fileIo.open(videoPath, fileIo.OpenMode.READ_ONLY)

  const extractor = await media.createAVMetadataExtractor()
  extractor.fdSrc = { fd: fd.fd }

  let metaData = await extractor.fetchMetadata()
  this.imageWidth = parseInt(metaData.videoWidth || '1') // 视频宽度
  this.imageHeight = parseInt(metaData.videoHeight || '1') // 视频高度
  let orientation = metaData.videoOrientation // 视频旋转,截图有可能有旋转角度

  const avImageGenerator = await media.createAVImageGenerator()
  avImageGenerator.fdSrc = { fd: fd.fd }

  // 配置缩略图参数
  const param: media.PixelMapParams = {
    width: this.imageWidth,
    height: this.imageHeight,
  }
  this.pixelMap = await avImageGenerator.fetchFrameByTime(
    0, // 0表示首帧(单位微秒)
    media.AVImageQueryOptions.AV_IMAGE_QUERY_NEXT_SYNC,
    param
  )
  await avImageGenerator.release() // 释放资源
  fileIo.close(fd) // 关闭文件
} catch (error) {
  console.error(`Get thumbnail failed: ${error.code}, ${error.message}`)
}

完整代码

typescript 复制代码
import { fileIo, fileUri, picker } from '@kit.CoreFileKit';
import { BusinessError } from '@ohos.base'
import { media } from '@kit.MediaKit';

@Entry
struct test {
  @State pixelMap: PixelMap | undefined = undefined
  @State imageWidth: number = 1
  @State imageHeight: number = 1
  @State orientation: number = 0

  chooseFile() {
    try {
      let context = getContext()
      let documentSelectOptions = new picker.DocumentSelectOptions();
      documentSelectOptions.fileSuffixFilters = ['视频|.mp4', '视频|.avi']
      let documentPicker = new picker.DocumentViewPicker(context);
      documentPicker.select(documentSelectOptions).then((documentSelectResult: Array<string>) => {
        console.info('DocumentViewPicker.select successfully, documentSelectResult uri: ' + JSON.stringify(documentSelectResult));
        if (documentSelectResult.length > 0) {
          let uri = documentSelectResult[0]
          this.getFirstFrame(uri)
        }
      }).catch((err: BusinessError) => {
        console.error(`DocumentViewPicker.select failed with err, code is: ${err.code}, message is: ${err.message}`);
      });
    } catch (error) {
      let err: BusinessError = error as BusinessError;
      console.error(`DocumentViewPicker failed with err, code is: ${err.code}, message is: ${err.message}`);
    }
  }

  async getFirstFrame(videoPath: string) {
    try {
      // 打开视频文件获取文件描述符
      let fd = await fileIo.open(videoPath, fileIo.OpenMode.READ_ONLY);

      const extractor = await media.createAVMetadataExtractor()
      extractor.fdSrc = { fd: fd.fd };

      let metaData = await extractor.fetchMetadata()
      this.imageWidth = parseInt(metaData.videoWidth || '1')
      this.imageHeight = parseInt(metaData.videoHeight || '1')
      this.orientation = parseInt(metaData.videoOrientation || '0')

      const avImageGenerator = await media.createAVImageGenerator();
      avImageGenerator.fdSrc = { fd: fd.fd };

      // 配置缩略图参数
      const param: media.PixelMapParams = {
        width: this.imageWidth,
        height: this.imageHeight
      };
      this.pixelMap = await avImageGenerator.fetchFrameByTime(
        0, // 0表示首帧(单位微秒)
        media.AVImageQueryOptions.AV_IMAGE_QUERY_NEXT_SYNC,
        param
      );
      await avImageGenerator.release(); // 释放资源
      fileIo.close(fd); // 关闭文件
    } catch (error) {
      console.error(`Get thumbnail failed: ${error.code}, ${error.message}`);
    }
  }

  build() {
    Column() {
      Text('截图')
        .fontSize('22fp')
        .fontColor(Color.Black)
        .onClick(() => {
          this.chooseFile()
        })

      Image(this.pixelMap)
        .objectFit(ImageFit.Cover)
        .width('30%')
        .aspectRatio(1)
        .orientation(this.orientation)
    }
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .width('100%')
    .height('100%')
  }
}
相关推荐
李李李勃谦4 小时前
鸿蒙PC密码管理器实战:本地加密存储与自动填充完整实现
华为·harmonyos
Swift社区5 小时前
鸿蒙 App 架构中的“领域拆分”
华为·架构·harmonyos
maaath8 小时前
【maaath】Flutter for OpenHarmony 手表配饰应用实战开发
flutter·华为·harmonyos
maaath9 小时前
【maaath】Flutter for OpenHarmony 跨平台计算器应用开发实践
flutter·华为·harmonyos
前端不太难14 小时前
鸿蒙PC和App:都在走向 System
华为·状态模式·harmonyos
maaath14 小时前
【maaath】Flutter for OpenHarmony 闹钟时钟应用开发实战
flutter·华为·harmonyos
maaath14 小时前
【maaath】Flutter for OpenHarmony 短信管理应用实战
flutter·华为·harmonyos
程序猿追14 小时前
从零打造一个“跳一跳”:在HarmonyOS模拟器上用Canvas复刻经典
华为·harmonyos
@不误正业14 小时前
第13章-开源鸿蒙是否适合做端侧AI操作系统
人工智能·开源·harmonyos
UnicornDev15 小时前
【HarmonyOS 6】底部悬浮导航的迷你栏适配(API23)
华为·harmonyos·arkts·鸿蒙