Flutter调用HarmonyOS NEXT原生相机拍摄&相册选择照片视频 ...

农民  金牌会员 | 2025-1-22 07:18:18 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 871|帖子 871|积分 2613

目次
 
1.项目配景
2.遇到的问题
3.开辟准备
4.开辟过程
首先创建注册调用鸿蒙原生的渠道
创建并初始化插件
绑定通道完成插件中的功能
5.具体步骤
根据传值判定是相册选取照旧打开相机
相册选取照片或视频
相机拍摄照片或视频
调用picker拍摄接口获取拍摄的结果
视频封面缩略图处理
打包缩略图
路径处理
数据返回
6.Flutter调用HarmonyOS原生通过路径上传到服务器
完整代码:

 
1.项目配景

我们的移动端项目是使用Flutter开辟,考虑到开辟周期和成本,使用了HarmonyOSNEXT(后续简称:鸿蒙)的Flutter兼容库,再将部分三方库更新为鸿蒙的Flutter兼容库,本项目选择相册的图片视频,使用相机照相拍视频我们使用的是调用Android和iOS的原生方法使用
2.遇到的问题

因为我们使用的是原生方法,所以鸿蒙也得开辟一套原生的配合使用,固然我们也发现鸿蒙的Flutter兼容库中有image_picker这个库,但是在实际线上运行中,部分机型是无法正常工作的,主要是国内厂商深度定制引起的,那根据设备类型判定在纯血鸿蒙手机上用image_picker也是可行的方案,考虑到如许不方便后期维护,所以照旧计划使用Flutter通过通道的形式去调用鸿蒙原生方式来实现
3.开辟准备

首先得将鸿蒙适配Flutter的SDK下载,具体步骤可以参考:Flutter SDK 仓库,也可以参考我的上一篇文章:Flutter适配HarmonyOS实践_flutter支持鸿蒙系统
4.开辟过程


  • 首先创建注册调用鸿蒙原生的渠道
  • 创建并初始化插件
  • 绑定通道完成插件中的功能
首先创建注册调用鸿蒙原生的渠道

使用了兼容库后,ohos项目中在entry/src/main/ets/plugins目次下会主动生成一个GeneratedPluginRegistrant.ets文件,里面会注册全部你使用的兼容鸿蒙的插件,但是我们不能在这里注册,因为每次build,他会根据Flutter项目中的pubspec.yaml文件中最新的插件引用去重新注册。
我们找到GeneratedPluginRegistrant的注册地:EntryAbility.ets,我们在plugins中创建一个FlutterCallNativeRegistrant.ets,将他也注册一下:
  1. import { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos';
  2. import FlutterCallNativeRegistrant from '../plugins/FlutterCallNativeRegistrant';
  3. import { GeneratedPluginRegistrant } from '../plugins/GeneratedPluginRegistrant';
  4. export default class EntryAbility extends FlutterAbility {
  5.   configureFlutterEngine(flutterEngine: FlutterEngine) {
  6.     super.configureFlutterEngine(flutterEngine)
  7.     GeneratedPluginRegistrant.registerWith(flutterEngine)
  8.     ///GeneratedPluginRegistrant是自动根据引入的插件库生成的,所以调用原生的插件必须新起文件进行单独注册
  9.     FlutterCallNativeRegistrant.registerWith(flutterEngine,this)
  10.   }
  11. }
复制代码
创建并初始化插件

创建FlutterCallNativePlugin插件在FlutterCallNativeRegistrant中初始化
  1. export default class FlutterCallNativeRegistrant {
  2.   private channel: MethodChannel | null = null;
  3.   private photoPlugin?:PhotoPlugin;
  4.   static registerWith(flutterEngine: FlutterEngine) {
  5.     try {
  6.       flutterEngine.getPlugins()?.add(new FlutterCallNativePlugin());
  7.     } catch (e) {
  8.     }
  9.   }
  10. }
复制代码
绑定通道完成插件中的功能

绑定MethodChannel界说2个执行方法来调用原生的相册选取照片视频,相机拍摄照片视频:selectPhoto和selectVideo
  1. import { FlutterPlugin, FlutterPluginBinding, MethodCall,
  2.   MethodCallHandler,
  3.   MethodChannel, MethodResult } from "@ohos/flutter_ohos";
  4. import router from '@ohos.router';
  5. import PhotoPlugin from "./PhotoPlugin";
  6. import { UIAbility } from "@kit.AbilityKit";
  7. export default class FlutterCallNativePlugin implements FlutterPlugin,MethodCallHandler{
  8.   private channel: MethodChannel | null = null;
  9.   private photoPlugin?:PhotoPlugin;
  10.   getUniqueClassName(): string {
  11.     return "FlutterCallNativePlugin"
  12.   }
  13.   onMethodCall(call: MethodCall, result: MethodResult): void {
  14.     switch (call.method) {
  15.       case "selectPhoto":
  16.           this.photoPlugin = PhotoPlugin.getInstance();
  17.           this.photoPlugin.setDataInfo(call, result ,1)
  18.           this.photoPlugin.openImagePicker();
  19.           break;
  20.       case "selectVideo":
  21.           this.photoPlugin = PhotoPlugin.getInstance();
  22.           this.photoPlugin.setDataInfo(call, result ,2)
  23.           this.photoPlugin.openImagePicker();
  24.         break;
  25.    
  26.       default:
  27.         result.notImplemented();
  28.         break;
  29.     }
  30.   }
  31.   onAttachedToEngine(binding: FlutterPluginBinding): void {
  32.     this.channel = new MethodChannel(binding.getBinaryMessenger(), "flutter_callNative");
  33.     this.channel.setMethodCallHandler(this)
  34.   }
  35.   onDetachedFromEngine(binding: FlutterPluginBinding): void {
  36.     if (this.channel != null) {
  37.       this.channel.setMethodCallHandler(null)
  38.     }
  39.   }
  40. }
复制代码
5.具体步骤



  • 根据传值判定是相册选取照旧打开相机
  • 相册选取照片或视频
  • 相机拍摄照片或视频
  • 视频封面处理
  • 路径处理
  • 数据返回
根据传值判定是相册选取照旧打开相机

  1.   openImagePicker() {
  2.     if (this.type === 1) {
  3.       this.openCameraTakePhoto()
  4.     } else if (this.type === 2) {
  5.       this.selectMedia()
  6.     } else {
  7.       this.selectMedia()
  8.     }
  9.   }
复制代码
相册选取照片或视频

用户偶然须要分享图片、视频等用户文件,开辟者可以通过特定接口拉起系统图库,用户自行选择待分享的资源,然后最终完成分享。此接口本身无需申请权限,目前适用于界面UIAbility,使用窗口组件触发。
这个方式的好处显而易见,不像Android大概iOS还须要向用户申请隐私权限,在鸿蒙中,以下操作完全是系统级的,不须要额外申请权限
1.创建图片媒体文件类型文件选择选项实例
  1. const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
复制代码
2.根据类型配置可选的媒体文件类型和媒体文件的最大数目等参数
  1. if (this.mediaType === 1) {
  2.       photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE // 过滤选择媒体文件类型为IMAGE
  3.     } else if (this.mediaType === 2) {
  4.       photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE // 过滤选择媒体文件类型为VIDEO
  5.     }
  6. photoSelectOptions.maxSelectNumber = this.mediaType === 2?1:this.maxCount; // 选择媒体文件的最大数目
  7.     photoSelectOptions.isPhotoTakingSupported=false;//是否支持拍照
  8.     photoSelectOptions.isSearchSupported=false;//是否支持搜索
复制代码
还有其他可配置项请参考API文档
3创建图库选择器实例,调用PhotoViewPicker.select接口拉起图库界面举行文件选择。文件选择成功后,返回PhotoSelectResult结果集。
  1. let uris: Array<string> = [];
  2. const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
  3. photoViewPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
  4.   uris = photoSelectResult.photoUris;
  5.   console.info('photoViewPicker.select to file succeed and uris are:' + uris);
  6. }).catch((err: BusinessError) => {
  7.   console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
  8. })
复制代码
打印相册选择图片和视频的结果:
  1. photoViewPicker.select to file succeed and uris
  2. are:file://media/Photo/172/IMG_1736574824_157/IMG_20250111_135204.jpg,file://media/Photo/164/IMG_1736514105_152/image_1736514005016.jpg
复制代码
  1. photoViewPicker.select to file succeed and uris
  2. are:file://media/Photo/136/VID_1735732161_009/VID_20250101_194749.mp4
复制代码
相机拍摄照片或视频

1.配置PickerProfile
   阐明
  PickerProfile的saveUri为可选参数,如果未配置该项,拍摄的照片和视频默认存入媒体库中。
  如果不想将照片和视频存入媒体库,请自行配置应用沙箱内的文件路径。
  应用沙箱内的这个文件必须是一个存在的、可写的文件。这个文件的uri传入picker接口之后,相当于应用给系统相机授权该文件的读写权限。系统相机在拍摄竣事之后,会对此文件举行覆盖写入
  1. let pathDir = getContext().filesDir;
  2.     let fileName = `${new Date().getTime()}`
  3.     let filePath = pathDir + `/${fileName}.tmp`
  4.     let result: picker.PickerResult
  5.     fileIo.createRandomAccessFileSync(filePath, fileIo.OpenMode.CREATE);
  6.     let uri = fileUri.getUriFromPath(filePath);
  7.     let pickerProfile: picker.PickerProfile = {
  8.       cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
  9.       saveUri: uri
  10.     };
复制代码
调用picker拍摄接口获取拍摄的结果

  1. if (this.mediaType === 1) {
  2.       result =
  3.         await picker.pick(getContext(), [picker.PickerMediaType.PHOTO],
  4.           pickerProfile);
  5.     } else if (this.mediaType === 2) {
  6.       result =
  7.         await picker.pick(getContext(), [picker.PickerMediaType.VIDEO],
  8.           pickerProfile);
  9.     }
  10. console.info(`picker resultCode: ${result.resultCode},resultUri: ${result.resultUri},mediaType: ${result.mediaType}`);
复制代码
打印结果:
  1. picker resultCode: 0,resultUri:
  2. file://com.example.demo/data/storage/el2/base/haps/entry/files/1737443816605.tmp,mediaType: photo
复制代码
  1. picker resultCode: 0,resultUri:
  2. file://com.example.demo/data/storage/el2/base/haps/entry/files/1737443929031.tmp,mediaType: video
复制代码
  1. 因为我们配置了saveUri,所以拍摄的图片视频是存在我们应用沙盒中。
复制代码
视频封面缩略图处理

视频拿到一样寻常都是直接上传,但是有的场景须要将适配封面也拿到,那么路径在沙盒中,就直接一次性处理好
1.创建AVImageGenerator对象
  1. // 创建AVImageGenerator对象
  2.       let avImageGenerator: media.AVImageGenerator = await media.createAVImageGenerator()
复制代码
2.根据传入的视频uri打开视频文件
  1. let file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
复制代码
3.将打开后的文件配置给avImageGenerator
  1. let avFileDescriptor: media.AVFileDescriptor = { fd: file.fd };
  2.       avImageGenerator.fdSrc = avFileDescriptor;
复制代码
4.初始化参数
  1.   let timeUs = 0
  2.       let queryOption = media.AVImageQueryOptions.AV_IMAGE_QUERY_NEXT_SYNC
  3.       let param: media.PixelMapParams = {
  4.         width : 300,
  5.         height : 400,
  6.       }
复制代码
5.异步获取缩略图
  1. // 获取缩略图(promise模式)
  2.       let pixelMap = await avImageGenerator.fetchFrameByTime(timeUs, queryOption, param)
复制代码
6.缩放资源,并返回缩略图
  1. avImageGenerator.release()
  2.       console.info(`release success.`)
  3.       fs.closeSync(file)
  4.       return pixelMap
复制代码
打包缩略图

1.创建imagePicker实例,该类是图片打包器类,用于图片压缩和打包
  1. const imagePackerApi: image.ImagePacker = image.createImagePacker();
复制代码
2.创建配置image.PackingOption
  1.         let packOpts: image.PackingOption = { format: "image/jpeg", quality: 98 }
复制代码
3.将缩略图打包保存并返回文件路径
  1. imagePackerApi.packing(pixelMap, packOpts).then(async (buffer: ArrayBuffer) => {
  2.             let fileName = `${new Date().getTime()}.tmp`
  3.             // //文件操作
  4.             let filePath = getContext().cacheDir + fileName
  5.             let file = fileIo.openSync(filePath,fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE)
  6.             fileIo.writeSync(file.fd,buffer)
  7.             //获取uri
  8.             let urlStr = fileUri.getUriFromPath(filePath)
  9.             resolve(urlStr)
  10.           })
复制代码
路径处理

因为以上全部的路径都是在鸿蒙设备上的路径,Flutter的MultipartFile.fromFile(ipath)是无法读取纯血鸿蒙设备的路径
  1. 01-16 16:23:46.805   17556-17654   A00000/com.gqs...erOHOS_Native  
  2. flutter settings log message: 错误信息:PathNotFoundException: Cannot retrieve length of file, path = 'file://com.example.demo/data/storage/el2/base/haps/entry/files/1737015822716.tmp' (OS Error: No such file or directory, errno = 2)
复制代码
所以我们须要把路径转换一下:
  1. /*
  2. * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. *     http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. import common from '@ohos.app.ability.common';
  16. import fs from '@ohos.file.fs';
  17. import util from '@ohos.util';
  18. import Log from '@ohos/flutter_ohos/src/main/ets/util/Log';
  19. const TAG = "FileUtils";
  20. export default class FileUtils {
  21.   static getPathFromUri(context: common.Context | null, uri: string, defExtension?: string) {
  22.     Log.i(TAG, "getPathFromUri : " + uri);
  23.     let inputFile: fs.File;
  24.     try {
  25.       inputFile = fs.openSync(uri);
  26.     } catch (err) {
  27.       Log.e(TAG, "open uri file failed err:" + err)
  28.       return null;
  29.     }
  30.     if (inputFile == null) {
  31.       return null;
  32.     }
  33.     const uuid = util.generateRandomUUID();
  34.     if (!context) {
  35.       return
  36.     }
  37.     {
  38.       const targetDirectoryPath = context.cacheDir + "/" + uuid;
  39.       try {
  40.         fs.mkdirSync(targetDirectoryPath);
  41.         let targetDir = fs.openSync(targetDirectoryPath);
  42.         Log.i(TAG, "mkdirSync success targetDirectoryPath:" + targetDirectoryPath + " fd: " + targetDir.fd);
  43.         fs.closeSync(targetDir);
  44.       } catch (err) {
  45.         Log.e(TAG, "mkdirSync failed err:" + err);
  46.         return null;
  47.       }
  48.       const inputFilePath = uri.substring(uri.lastIndexOf("/") + 1);
  49.       const inputFilePathSplits = inputFilePath.split(".");
  50.       Log.i(TAG, "getPathFromUri inputFilePath: " + inputFilePath);
  51.       const outputFileName = inputFilePathSplits[0];
  52.       let extension: string;
  53.       if (inputFilePathSplits.length == 2) {
  54.         extension = "." + inputFilePathSplits[1];
  55.       } else {
  56.         if (defExtension) {
  57.           extension = defExtension;
  58.         } else {
  59.           extension = ".jpg";
  60.         }
  61.       }
  62.       const outputFilePath = targetDirectoryPath + "/" + outputFileName + extension;
  63.       const outputFile = fs.openSync(outputFilePath, fs.OpenMode.CREATE);
  64.       try {
  65.         Log.i(TAG, "copyFileSync inputFile fd:" + inputFile.fd + " outputFile fd:" + outputFile.fd);
  66.         fs.copyFileSync(inputFile.fd, outputFilePath);
  67.       } catch (err) {
  68.         Log.e(TAG, "copyFileSync failed err:" + err);
  69.         return null;
  70.       } finally {
  71.         fs.closeSync(inputFile);
  72.         fs.closeSync(outputFile);
  73.       }
  74.       return outputFilePath;
  75.     }
  76.   }
  77. }
复制代码
通过调用FileUtils的静态方法getPathFromUri,传入上下文和路径,就能获取到真正的SD卡的文件地址:
  1. /data/storage/el2/base/haps/entry/cache/53ee7666-7ba4-4f72-9d37-3c09111a2293/1737446424534.tmp
复制代码
数据返回

  1.    let videoUrl =  this.retrieveCurrentDirectoryUri(uris[0])
  2.    let coverImageUrl =  this.retrieveCurrentDirectoryUri(videoThumb)
  3.    map.set("videoUrl", this.retrieveCurrentDirectoryUri(uris[0]));
  4.    map.set("coverImageUrl", this.retrieveCurrentDirectoryUri(videoThumb));
  5.    this.result?.success(map);
复制代码
6.Flutter调用HarmonyOS原生通过路径上传到服务器

  1. 上文中我们提到建立通道Channel
  2. MethodChannel communicateChannel = MethodChannel("flutter_callNative");
  3. final result = await communicateChannel.invokeMethod("selectVideo", vars);
  4. if (result["videoUrl"] != null && result["coverImageUrl"] != null) {
  5.     String? video = await FileUploader.uploadFile(result["videoUrl"].toString());
  6.     String? coverImageUrl =await FileUploader.uploadFile(result["coverImageUrl"].toString());
  7. }
复制代码
完整代码:

  1. import { camera, cameraPicker as picker } from '@kit.CameraKit'import { fileIo, fileUri } from '@kit.CoreFileKit'import { MethodCall, MethodResult } from '@ohos/flutter_ohos';import { photoAccessHelper } from '@kit.MediaLibraryKit';import { BusinessError } from '@kit.BasicServicesKit';import json from '@ohos.util.json';import FileUtils from '../utils/FileUtils';import HashMap from '@ohos.util.HashMap';import media from '@ohos.multimedia.media';import { image } from '@kit.ImageKit';import { fileIo as fs } from '@kit.CoreFileKit';/** * @FileName : PhotoPlugin * @Author : kirk.wang * @Time : 2025/1/16 11:30 * @Description :  flutter调用鸿蒙原生组件的选择相片、选择视频、照相、录制视频 */export default class  PhotoPlugin {  private imgSrcList: Array<string> = [];  private call?: MethodCall;  private result?: MethodResult;  ///打开方式:1-拍摄,2-相册  private type: number=0;  ///最大数量  private maxCount: number=0;  ///资源类型:1-图片,2-视频,else 全部文件类型  private mediaType: number=0;  // 静态属性存储单例实例  private static instance: PhotoPlugin;  // 静态方法获取单例实例  public static getInstance(): PhotoPlugin {    if (!PhotoPlugin.instance) {      PhotoPlugin.instance = new PhotoPlugin();    }    return PhotoPlugin.instance;  }  // 提供设置和获取数据的方法  public setDataInfo(call: MethodCall, result: MethodResult, mediaType: number) {    this.call = call;    this.result = result;    this.mediaType = mediaType;    this.type = this.call.argument("type") as number;    this.maxCount = call.argument("maxCount") as number;  }  openImagePicker() {
  2.     if (this.type === 1) {
  3.       this.openCameraTakePhoto()
  4.     } else if (this.type === 2) {
  5.       this.selectMedia()
  6.     } else {
  7.       this.selectMedia()
  8.     }
  9.   }  selectMedia() {    const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();    if (this.mediaType === 1) {      photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE // 过滤选择媒体文件类型为IMAGE    } else if (this.mediaType === 2) {      photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE // 过滤选择媒体文件类型为VIDEO    }    photoSelectOptions.maxSelectNumber = this.mediaType === 2?1:this.maxCount; // 选择媒体文件的最大数目    photoSelectOptions.isPhotoTakingSupported=false;//是否支持照相    photoSelectOptions.isSearchSupported=false;//是否支持搜索    let uris: Array<string> = [];    const photoViewPicker = new photoAccessHelper.PhotoViewPicker();    photoViewPicker.select(photoSelectOptions).then(async (photoSelectResult: photoAccessHelper.PhotoSelectResult) => {      uris = photoSelectResult.photoUris;      console.info('photoViewPicker.select to file succeed and uris are:' + uris);      let jsonResult = "";      if (this.mediaType === 1) {        uris.forEach((uri => {          this.imgSrcList.push(this.retrieveCurrentDirectoryUri(uri))        }))        jsonResult = json.stringify(this.imgSrcList)        this.result?.success(jsonResult);      } else if (this.mediaType === 2) {          let map = new HashMap<string, string>;          await this.getVideoThumbPath(uris[0]).then((videoThumb)=>{            let videoUrl =  this.retrieveCurrentDirectoryUri(uris[0])            let coverImageUrl =  this.retrieveCurrentDirectoryUri(videoThumb)                map.set("videoUrl", videoUrl);                map.set("coverImageUrl", coverImageUrl);                this.result?.success(map);          });      }      console.assert('result  success:'+jsonResult);    }).catch((err: BusinessError) => {      console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);    })  }  async openCameraTakePhoto() {    let pathDir = getContext().filesDir;
  10.     let fileName = `${new Date().getTime()}`
  11.     let filePath = pathDir + `/${fileName}.tmp`
  12.     let result: picker.PickerResult
  13.     fileIo.createRandomAccessFileSync(filePath, fileIo.OpenMode.CREATE);
  14.     let uri = fileUri.getUriFromPath(filePath);
  15.     let pickerProfile: picker.PickerProfile = {
  16.       cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
  17.       saveUri: uri
  18.     };    if (this.mediaType === 1) {      result =        await picker.pick(getContext(), [picker.PickerMediaType.PHOTO],          pickerProfile);    } else if (this.mediaType === 2) {      result =        await picker.pick(getContext(), [picker.PickerMediaType.VIDEO],          pickerProfile);    } else if (this.mediaType === 3) {      result =        await picker.pick(getContext(), [picker.PickerMediaType.PHOTO, picker.PickerMediaType.VIDEO],          pickerProfile);    } else {      result =        await picker.pick(getContext(), [picker.PickerMediaType.PHOTO, picker.PickerMediaType.VIDEO],          pickerProfile);    }    console.info(`picker resultCode: ${result.resultCode},resultUri: ${result.resultUri},mediaType: ${result.mediaType}`);    if (result.resultCode == 0) {      if (result.mediaType === picker.PickerMediaType.PHOTO) {        let imgSrc = this.retrieveCurrentDirectoryUri(result.resultUri);        this.imgSrcList.push(imgSrc);        this.result?.success(json.stringify(this.imgSrcList));      } else {           let map = new HashMap<string, string>;          await this.getVideoThumbPath(result.resultUri).then((videoThumb)=>{            if(videoThumb!==''){              let videoUrl =  this.retrieveCurrentDirectoryUri(result.resultUri)              let coverImageUrl =  this.retrieveCurrentDirectoryUri(videoThumb)                map.set("videoUrl",videoUrl);                map.set("coverImageUrl", coverImageUrl);                this.result?.success(map);            }          });      }    }  }  retrieveCurrentDirectoryUri(uri: string): string {    let realPath = FileUtils.getPathFromUri(getContext(), uri);    return realPath ?? '';  }  async getVideoThumbPath(filePath:string) {    return new Promise<string>((resolve, reject) => {      setTimeout(() => {        let packOpts: image.PackingOption = { format: "image/jpeg", quality: 98 }
  19.         const imagePackerApi = image.createImagePacker();         this.getVideoThumb(filePath).then((pixelMap)=>{          imagePackerApi.packing(pixelMap, packOpts).then(async (buffer: ArrayBuffer) => {
  20.             let fileName = `${new Date().getTime()}.tmp`
  21.             // //文件操作
  22.             let filePath = getContext().cacheDir + fileName
  23.             let file = fileIo.openSync(filePath,fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE)
  24.             fileIo.writeSync(file.fd,buffer)
  25.             //获取uri
  26.             let urlStr = fileUri.getUriFromPath(filePath)
  27.             resolve(urlStr)
  28.           })        })      }, 0);    });  }  ///获取视频缩略图     getVideoThumb = async (filePath: string) => {      // 创建AVImageGenerator对象
  29.       let avImageGenerator: media.AVImageGenerator = await media.createAVImageGenerator()      let file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);      let avFileDescriptor: media.AVFileDescriptor = { fd: file.fd };
  30.       avImageGenerator.fdSrc = avFileDescriptor;      // 初始化入参      let timeUs = 0
  31.       let queryOption = media.AVImageQueryOptions.AV_IMAGE_QUERY_NEXT_SYNC
  32.       let param: media.PixelMapParams = {
  33.         width : 300,
  34.         height : 400,
  35.       }      // 获取缩略图(promise模式)
  36.       let pixelMap = await avImageGenerator.fetchFrameByTime(timeUs, queryOption, param)      // 开释资源(promise模式)      avImageGenerator.release()
  37.       console.info(`release success.`)
  38.       fs.closeSync(file)
  39.       return pixelMap    };}
复制代码
创作不易,如果我的内容帮助到了你,烦请小同伴点个关注,留个言,分享给须要的人,不胜感激。
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

农民

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表