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

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

参考文档

由于文件权限获取不太方便,现在使用的是 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%')
  }
}
相关推荐
KevinChaos6 小时前
阳台小花园开发手记 01|HarmonyOS 7.0 互动卡片:小花园为何要两套界面
华为·harmonyos
Ranger09296 小时前
下一代鸿蒙打包工具链:arkdown
harmonyos
程序猿追8 小时前
一个工程面向手机与二合一:HarmonyOS 6 部署配置和本地手记
华为·智能手机·harmonyos
行者-全栈开发19 小时前
【鸿蒙心迹】深色模式适配全链路——从配置到自定义(HarmonyOS 7.x)
华为·harmonyos
行者-全栈开发1 天前
【鸿蒙心迹】国际化 i18n 与多语言资源管理实战(HarmonyOS 7.x)
ui·华为·重构·国际化·harmonyos·多语言
OH_TPC1 天前
HarmonyOS APP开发---"云客服"远程视频客服App,需要用到这个库
harmonyos
逆回2 天前
给鸿蒙手机做一只 3D 宠物:五个把我卡住的问题
harmonyos
钉先生贾2 天前
性能、体积与稳定性:怎样写出可信的适配结论
华为·harmonyos
笑 总不会犯错2 天前
摆脱多端数据撕裂,鸿蒙分布式数据库同步与冲突合并
harmonyos
用户117910488332 天前
鸿蒙签名失败不是玄学:5 个证据点把 hapsigntool 一次排通 GitHub:swsgbl/hmharness
开源·ai编程·harmonyos