一文搞懂HarmonyOS Next图片视频选择与图片视频拍摄

233 篇文章 0 订阅
195 篇文章 0 订阅

往期知识点整理

背景

在聊天软件中,发送相册中视频和照片、用相机拍摄视频和图片发送是很常用的功能。在Android和iOS端,大部分应用都通过API方式定义UI来实现相册选择照片、视频,相机拍摄照片、视频,它们一般都支持以下功能:

  1. 相册选择:
    1. 支持单选或多选;
    2. 对图片支持是否原图选择;
    3. 对于视频支持选择视频的文件大小、视频时长等过滤;
    4. 支持点击图像放大预览
  2. 对于相机拍摄
    1. 支持点击拍照,长按录制视频;
    2. 视频录制支持最大最小录制时长限制;
    3. 拍摄或录制结束后支持预览。

对于鸿蒙应用要实现上述功能,系统也提供了对应API,要实现上述功能需要几个系统权限:

  • 读取系统相册权限
  • 麦克风权限
  • 摄像头权限

HarmonyOS 权限系统介绍

与Android系统相比,HarmonyOS提供了更严谨的权限控制,这里不得不提HarmonyOS的应用权限管控策略。HarmonyOS 提供了一种允许应用访问系统资源(如:通讯录等)和系统能力(如:访问摄像头、麦克风等)的通用权限访问方式,来保护系统数据(包括用户个人数据)或功能,避免它们被不当或恶意使用,应用权限保护的对象可以分为数据和功能:

  • 数据包括个人数据(如照片、通讯录、日历、位置等)、设备数据(如设备标识、相机、麦克风等)。
  • 功能包括设备功能(如访问摄像头/麦克风、打电话、联网等)、应用功能(如弹出悬浮窗、创建快捷方式等)。

同时HarmonyOS 根据授权方式的不同,权限类型可分为system_grant(系统授权)和user_grant(用户授权):

  • system_grant(系统授权)指的是系统授权类型,在该类型的权限许可下,应用被允许访问的数据不会涉及到用户或设备的敏感信息,应用被允许执行的操作对系统或者其他应用产生的影响可控。如果在应用中申请了system_grant权限,那么系统会在用户安装应用时,自动把相应权限授予给应用。
  • user_grant(用户授权)指的是用户授权类型,在该类型的权限许可下,应用被允许访问的数据将会涉及到用户或设备的敏感信息,应用被允许执行的操作可能对系统或者其他应用产生严重的影响。该类型权限不仅需要在安装包中申请权限,还需要在应用动态运行时,通过发送弹窗的方式请求用户授权。在用户手动允许授权后,应用才会真正获取相应权限,从而成功访问操作目标对象。

例如,在 应用权限列表 中,麦克风和摄像头对应的权限都是属于用户授权权限,列表中给出了详细的权限使用理由。应用需要在应用商店的详情页面,向用户展示所申请的user_grant权限列表。

除了授权方式外还要了解另一个概念APL(Ability Privilege Level,元能力权限等级)等级。应用的等级可以分为以下三个等级,等级依次提高。

APL级别说明
normal默认情况下,应用的APL等级都为normal等级。
system_basic该等级的应用服务提供系统基础服务。
system_core该等级的应用服务提供操作系统核心能力。应用APL等级不允许配置为system_core。

根据权限对于不同等级应用有不同的开放范围,权限类型对应分为以下三个等级,等级依次提高。

APL级别说明开放范围
normal允许应用访问超出默认规则外的普通系统资源,如配置Wi-Fi信息、调用相机拍摄等。这些系统资源的开放(包括数据和功能)对用户隐私以及其他应用带来的风险低。APL等级为normal及以上的应用。
system_basic允许应用访问操作系统基础服务(系统提供或者预置的基础功能)相关的资源,如系统设置、身份认证等。这些系统资源的开放对用户隐私以及其他应用带来的风险较高。1、APL等级为system_basic及以上的应用。
2、部分权限对normal级别的应用受限开放,这部分权限在本指导中描述为“ 受限开放权限 ”。
system_core涉及开放操作系统核心资源的访问操作。这部分系统资源是系统最核心的底层服务,如果遭受破坏,操作系统将无法正常运行。1、 APL等级为system_core的应用。
2、 仅对系统应用开放。

了解了授权方式和权限等级后,我们再来聊聊这个设计背后的原因。

首先聊聊授权方式,其中系统授权类似于Android中的普通权限申请,在清单文件声明即可,对于用户授权,类似于Android中的动态权限,需要触发弹窗让用户感知后决定是否授权。这个涉及背后的逻辑很清晰,对于数据不会涉及到用户或设备的敏感信息(比如使用网络、使用蓝牙等),默认声明后授权不会对系统产生什么破坏,如果也强制用户感知授权后才可以使用,对于开发者来说增加工作量,对用户体验也不是很友好;

其次,对于权限等级,HarmonyOS分成了三类,normal 级别的,即普通应用应用可以使用的权限,比如相机,麦克风等,只要说明使用的场景即可向用户申请;system_core级别的是只有系统应用可以使用,普通应用无法申请,如果普通应用使用可能会对系统造成不可修复的错误,比如类似Android的root权限,如果普通应用申请到后,删除了系统核心文件等,系统会遭到破坏;第三种是system_basic,主要对系统应用开发,对普通应用受限开放,什么是受限开放呢?比如说读取相册权限,如果授权给普通应用,用户赋予它这个权限后,它偷偷的将相册数据传送到应用服务端用户完全无法感知,但是对于一些应用,比如网盘、相册备份类应用,没有这个权限还无法工作,所以系统设计时考虑到这一点,对于特殊的应用向平台申请后,平台根据应用类型给特殊应用放开该权限。可能有人会疑惑,为什么读取相册的是受限,而相机、麦克风是normal权限,因为麦克风、相机无法在用户无感知时使用,而读取文件可以。

回到正题,选择图片视频需要system_basic 受限权限,麦克风相机也需要用户授权,为了减少授权导致的操作流程终端,HarmonyOS 提供了系统Picker,可以使用系统组件再不需要权限的情况下完成功能。 应用拉起系统Picker组件(文件选择器、照片选择器、联系人选择器等),由用户在Picker上选择对应的文件、照片、联系人等资源,应用即可获取到Picker的返回结果,系统Picker由系统独立进程实现,从系统Picker获取资源是一个跨进程调度过程。

为什么使用系统Picker获取资源不需要权限?因为从系统Picker获取资源需要用户操作,是用户可以感知的,所以不需要再申请权限。注意,此时应用获取到的读取资源的权限是临时的,受限的,只能临时受限访问对应的资源。我们从Picker里选择了1.jpeg,在我们应用进程可以操作这个文件,我们从系统相册中获取到另一个2.jpeg文件,在进程里直接访问是不可以的,而且对1.jpeg的访问也是有时效性的。

接下来介绍如何基于系统API,从相册选择照片和视频,以及从相机拍摄视频和照片。

从相册选择

HarmonyOS 提供了 photoAccessHelper.PhotoViewPicker() 获取系统相册中的图片,下面是使用PhotoViewPicker的示例:

import { BusinessError } from '@kit.BasicServicesKit';
async function example01() {
  try {
    let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
    PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
    PhotoSelectOptions.maxSelectNumber = 5;

    let photoPicker = new photoAccessHelper.PhotoViewPicker();
    photoPicker.select(PhotoSelectOptions).then((PhotoSelectResult: photoAccessHelper.PhotoSelectResult) => {
      console.info('PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + JSON.stringify(PhotoSelectResult));
    }).catch((err: BusinessError) => {
      console.error(`PhotoViewPicker.select failed with err: ${err.code}, ${err.message}`);
    });
  } catch (error) {
    let err: BusinessError = error as BusinessError;
    console.error(`PhotoViewPicker failed with err: ${err.code}, ${err.message}`);
  }
}

这里面涉及到两个对象PhotoSelectOptions和PhotoSelectResult,分别表示跳转到图片选择器的参数,和选择完返回的结果。 PhotoSelectOptions有一下三个参数:

名称类型必填说明
isEditSupported11+boolean是否支持编辑照片,true表示支持,false表示不支持,默认为true。
isOriginalSupported12+boolean是否显示选择原图按钮,true表示显示,false表示不显示,默认为true。
元服务API: 从API version 12开始,该接口支持在元服务中使用。
subWindowName12+string子窗窗口名称。
元服务API: 从API version 12开始,该接口支持在元服务中使用。

PhotoSelectOptions 继承自BaseSelectOptions,提供了以下配置:

名称类型必填说明
MIMEType10+PhotoViewMIMETypes可选择的媒体文件类型,若无此参数,则默认为图片和视频类型。
元服务API: 从API version 11开始,该接口支持在元服务中使用。
maxSelectNumber10+number选择媒体文件数量的最大值(最大可设置的值为500,若不设置则默认为50)。
元服务API: 从API version 11开始,该接口支持在元服务中使用。
isPhotoTakingSupported11+boolean是否支持拍照,true表示支持,false表示不支持,默认为true。
元服务API: 从API version 11开始,该接口支持在元服务中使用。
isSearchSupported11+boolean是否支持搜索,true表示支持,false表示不支持,默认为true。
元服务API: 从API version 11开始,该接口支持在元服务中使用。
recommendationOptions11+RecommendationOptions图片推荐相关配置参数。
元服务API: 从API version 11开始,该接口支持在元服务中使用。
preselectedUris11+Array预选择图片的uri数据。
元服务API: 从API version 11开始,该接口支持在元服务中使用。
isPreviewForSingleSelectionSupported12+boolean单选模式下是否需要进大图预览,true表示需要,false表示不需要,默认为true。
元服务API: 从API version 12开始,该接口支持在元服务中使用。

综合上述配置说明,通过PhotoViewPicker可以实现最开始我们提到的:

  1. 支持选择图片和视频
  2. 支持设置最多选择媒体数量
  3. 支持是否选择原图 不支持选择视频的最大最小长度配置。

PhotoSelectResult是返回图库选择后的结果集,结构如下:

名称类型可读可写说明
photoUrisArray返回图库选择后的媒体文件的uri数组,此uri数组只能通过临时授权的方式调用 photoAccessHelper.getAssets接口 去使用,具体使用方式参见用户文件uri介绍中的 媒体文件uri的使用方式。
isOriginalPhotoboolean返回图库选择后的媒体文件是否为原图。

返回用户选中的媒体列表和是否选中原图。这里产生一个问题,如果既有选择视频又有选择图片,如何根据一个uri判断是视频还是图片? 这里有用到photoAccessHelper 来解析媒体信息,下面是一个方法封装:

public async uriGetAssets(uri:string): Promise<photoAccessHelper.PhotoAsset|undefined> {  
  try {  
    let context = getContext(this) as common.UIAbilityContext;  
    let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);  
    let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();  
    // 配置查询条件,使用PhotoViewPicker选择图片返回的uri进行查询  
    predicates.equalTo('uri', uri);  
    let fetchOption: photoAccessHelper.FetchOptions = {  
      fetchColumns: [photoAccessHelper.PhotoKeys.WIDTH, photoAccessHelper.PhotoKeys.HEIGHT, photoAccessHelper.PhotoKeys.TITLE, photoAccessHelper.PhotoKeys.DURATION],  
      predicates: predicates  
    };  
    let fetchResult: photoAccessHelper.FetchResult<photoAccessHelper.PhotoAsset> = await phAccessHelper.getAssets(fetchOption);  
    // 得到uri对应的PhotoAsset对象,读取文件的部分信息  
    const asset: photoAccessHelper.PhotoAsset = await fetchResult.getFirstObject();  
    Logg.i(TAG, 'asset displayName: ' + asset.displayName);  
    Logg.i(TAG, 'asset uri: '+asset.uri);  
    Logg.i(TAG, 'asset photoType: ' + asset.photoType);  
    Logg.i(TAG, 'asset width: ' +  asset.get(photoAccessHelper.PhotoKeys.WIDTH));  
    Logg.i(TAG, 'asset height: ' +  asset.get(photoAccessHelper.PhotoKeys.HEIGHT));  
    Logg.i(TAG, 'asset title: '  +  asset.get(photoAccessHelper.PhotoKeys.TITLE));  
    return asset;  
  } catch (error){  
    Logg.e(TAG, 'uriGetAssets failed with err: ' + JSON.stringify(error));  
  }  
  return undefined;  
}

FetchOptions用来配置要查询的媒体信息内容,比如长、宽等信息,其中photoAccessHelper.PhotoAsset的photoType表示媒体类型,是音频还是视频。

接下里我们再介绍另一个能力,如果是视频的话有时候想要获取视频封面,即Thumbnail,photoAccessHelper.PhotoAsset提供了查询封面的接口:getThumbnail(),返回的是一个pixelMap,如何将pixelMap转换成图像呢?在pixelMap文档里看到从pixelMap读取buffer的方法:

import { BusinessError } from '@kit.BasicServicesKit';
async function Demo() {
  const readBuffer: ArrayBuffer = new ArrayBuffer(96); // 96为需要创建的像素buffer大小,取值为:height * width *4
  if (pixelMap != undefined) {
    pixelMap.readPixelsToBuffer(readBuffer, (error: BusinessError, res: void) => {
      if(error) {
        console.error(`Failed to read image pixel data. code is ${error.code}, message is ${error.message}`);// 不符合条件则进入
        return;
      } else {
        console.info('Succeeded in reading image pixel data.');  //符合条件则进入
      }
    })
  }
}

上面ArrayBuffer需要指定大小,将前面获取到的宽高相乘再乘以4即可。将获取到的readBuffer写入文件发现图片不是正常的图片,这里又需要用到image.createImagePacker():

let imagePath:string|undefined = undefined;  
let pixelMap = await matedata.getThumbnail();  
Logg.i(this.TAG, 'getThumbnail successful ' + JSON.stringify(pixelMap));  
let cacheDir = getContext().cacheDir;  
imagePath = `${cacheDir}/thumbnail${Date.now()}.jpg`;  
let dstFile = fs.openSync(imagePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);  
let packOpts: image.PackingOption = { format: "image/jpeg", quality: 98 };  
await image.createImagePacker().packToFile(pixelMap, dstFile.fd, packOpts)  
fs.close(dstFile);  
Logg.i(this.TAG, "copy file success");

需用用createImagePacker将pixelMap写入到目标文件,并且指定图片格式和质量。

从相机拍摄

上面实现了从相册获取照片,接下来实现使用摄像机拍照和拍视频功能。 有两种方式可以实现打开系统相机进行拍摄。

Want

下面是示例代码:

invokeCamera(callback: (uri: string) => void) {  
  const context = getContext(this) as common.UIAbilityContext;  
  const want: Want = {  
    action: 'ohos.want.action.imageCapture',  
    parameters: {  
      "callBundleName": context.abilityInfo.bundleName,  
    }  
  };  
  const result: (error: BusinessError, data: common.AbilityResult) => void =  
    (error: BusinessError, data: common.AbilityResult) => {  
      if (error && error.code !== 0) {  
        console.log(`${TAG_CAMERA_ERROR} ${JSON.stringify(error.message)}`)  
        return;  
      }  
      // 获取相机拍照后返回的图片地址  
      const resultUri: string = data.want?.parameters?.resourceUri as string;  
      if (callback && resultUri) {  
        callback(resultUri);  
      }  
    }  
  context.startAbilityForResult(want, result);  
}

通过want意图对象打开相机界面,参考 如何调用系统拍照并获取图片 ,这种方式无法设置录制视频最大长度等。

cameraPicker

相机选择器使用示例:

import { cameraPicker as picker } from '@kit.CameraKit';
import { camera } from '@kit.CameraKit';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
let mContext = getContext(this) as common.Context;

async function demo() {
  try {
    let pickerProfile: picker.PickerProfile = {
      cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
    };
    let pickerResult: picker.PickerResult = await picker.pick(mContext,
      [picker.PickerMediaType.PHOTO, picker.PickerMediaType.VIDEO], pickerProfile);
    console.log("the pick pickerResult is:" + JSON.stringify(pickerResult));
  } catch (error) {
    let err = error as BusinessError;
    console.error(`the pick call failed. error code: ${err.code}`);
  }
}

示例中mediaTypes数组指定了媒体格式,可以包含视频和图片;PickerProfile指定了摄像头是前置还是后置,主要有一下属性:

名称类型必填说明
cameraPositioncamera.CameraPosition相机的位置。
saveUristring保存配置信息的uri。
videoDurationnumber录制的最大时长。

PickerResult是相机选择器的处理结果,有一下属性:

名称类型必填说明
resultCodenumber处理的结果,成功返回0,失败返回-1。
resultUristring返回的uri地址。若saveUri为空,resultUri为公共媒体路径。若saveUri不为空且具备写权限,resultUri与saveUri相同。若saveUri不为空且不具备写权限,则无法获取到resultUri。
mediaTypePickerMediaType返回的媒体类型。

根据返回结果中mediaType来表示是图片还是视频,按照不同媒体类型处理即可。

总结

本文介绍了HarmonyOS Next中图片视频选择、图片视频拍摄的能力,重点分析了无需权限即可实现的photoAccessHelper和cameraPicker组件。

最后

总是有很多小伙伴反馈说:鸿蒙开发不知道学习哪些技术?不知道需要重点掌握哪些鸿蒙开发知识点? 为了解决大家这些学习烦恼。在这准备了一份很实用的鸿蒙全栈开发学习路线与学习文档给大家用来跟着学习。

针对一些列因素,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植……等)技术知识点。

《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN/733GH/overview

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

鸿蒙开发面试真题(含参考答案):

在这里插入图片描述

《OpenHarmony源码解析》:

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……
  • 系统架构分析
  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

图片

OpenHarmony 设备开发学习手册:https://gitcode.com/HarmonyOS_MN/733GH/overview

图片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值