HarmonyOS 鸿蒙Next中uniapp开发如何通过uts插件实现选择动态照片

HarmonyOS 鸿蒙Next中uniapp开发如何通过uts插件实现选择动态照片 通过uts插件实现选择动态照片,支持批量获取,并解析图片中包含的地址、视频等信息

8 回复

您好,可以参考下uts插件文档

  1. 在uni-modules目录上点击右键,选择新建uni-modules插件,继续选择UTS插件-API插件,键入合适的插件名后点击创建。utssdk目录下添加app-harmony目录和相关文件。

  2. 在app-harmony/index.uts中实现选择动态照片的逻辑。

  3. 在uni-app页面中调用插件。

【背景知识】

uts插件是指利用uts语法,调用OS的API或三方SDK,封装成一个供前端调用的uni_modules插件。

更多关于HarmonyOS 鸿蒙Next中uniapp开发如何通过uts插件实现选择动态照片的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


先配置权限

module.json5 或插件 package.json 中添加权限:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.READ_MEDIA",
        "reason": "读取相册动态照片",
        "usedScene": {
          "abilities": ["MainAbility"],
          "when": "always"
        }
      }
    ]
  }
}

然后自己实现插件,比如

import picker from '@ohos.file.picker';
import photoAccessHelper from '@ohos.file.photoAccessHelper';
import { BusinessError } from '@ohos.base';
import image from '@ohos.multimedia.image';
import common from '@ohos.app.ability.common';
import geoLocationManager from '@ohos.geoLocationManager';

// 对外返回结构
export interface LivePhotoItem {
  imageUri: string;
  videoUri?: string;
  latitude: number;
  longitude: number;
  address?: string;
  isLivePhoto: boolean;
}

/**
 * 选择动态照片(支持批量)
 */
export async function selectLivePhotos(maxCount: number = 9): Promise<LivePhotoItem[]> {
  const photoPicker = new picker.PhotoViewPicker();
  const options = new picker.PhotoSelectOptions();
  options.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
  options.maxSelectNumber = maxCount;

  try {
    // 1. 打开相册选择
    const selectResult = await photoPicker.select(options);
    if (!selectResult?.photoUris?.length) return [];

    // 2. 解析每个选中资源
    const context = getContext() as common.UIAbilityContext;
    const photoView = photoAccessHelper.getPhotoView(context);
    const results: LivePhotoItem[] = [];

    for (const uri of selectResult.photoUris) {
      try {
        const asset = await photoView.getAssetFromUri(uri);
        const item: LivePhotoItem = {
          imageUri: uri,
          latitude: 0,
          longitude: 0,
          isLivePhoto: asset.type === photoAccessHelper.PhotoType.LIVE_PHOTO
        };

        // 3. 提取动态照片的视频
        if (item.isLivePhoto) {
          const subRes = await asset.getSubResources();
          for (const res of subRes) {
            if (res.type === photoAccessHelper.PhotoType.VIDEO) {
              item.videoUri = res.uri;
              break;
            }
          }
        }

        // 4. 解析 EXIF GPS 信息
        const exif = await readExifGps(uri);
        item.latitude = exif.latitude;
        item.longitude = exif.longitude;

        // 5. (可选)经纬度反解析地址
        if (item.latitude && item.longitude) {
          item.address = await reverseGeocode(item.latitude, item.longitude);
        }

        results.push(item);
      } catch (e) {
        console.error('解析单张失败', e);
      }
    }
    return results;
  } catch (err) {
    console.error('选择失败', err);
    throw err;
  }
}

/**
 * 读取图片 EXIF GPS 信息
 */
async function readExifGps(uri: string): Promise<{ latitude: number; longitude: number }> {
  try {
    const source = image.createImageSource(uri);
    const props = await source.getImageProperty('Exif', {});
    // 解析 GPSLatitude/GPSLongitude 等标签(格式需适配鸿蒙返回)
    return {
      latitude: parseFloat(props.GPSLatitude || '0'),
      longitude: parseFloat(props.GPSLongitude || '0')
    };
  } catch (e) {
    return { latitude: 0, longitude: 0 };
  }
}

/**
 * (可选)经纬度反解析地址(需联网/位置权限)
 */
async function reverseGeocode(lat: number, lng: number): Promise<string> {
  try {
    const res = await geoLocationManager.getAddressFromLocation({ latitude: lat, longitude: lng });
    return res?.[0]?.placeName || '';
  } catch (e) {
    return '';
  }
}

最后在你的UniApp页面调用即可:

<template>
  <button @click="chooseLivePhotos">选择动态照片</button>
  <view v-for="(item, idx) in list" :key="idx">
    <text>图片:{{ item.imageUri }}</text>
    <text v-if="item.isLivePhoto">视频:{{ item.videoUri }}</text>
    <text>地址:{{ item.address || '无' }}</text>
  </view>
</template>

<script setup>
import { ref } from 'vue';
import { selectLivePhotos, LivePhotoItem } from '@/uni_modules/ha-live-photo-picker';

const list = ref<LivePhotoItem[]>([]);

async function chooseLivePhotos() {
  try {
    const res = await selectLivePhotos(9); // 最多选9张
    list.value = res;
    console.log('选中动态照片', res);
  } catch (err) {
    uni.showToast({ title: '选择失败', icon: 'none' });
  }
}
</script>

这是一个非常有技术深度的需求。在 UniApp 开发鸿蒙(HarmonyOS NEXT)应用时,标准的 uni.chooseImageuni.chooseMedia 可能无法直接满足**“选择动态照片(Live Photo)”“解析其中包含的地址、视频”**这种定制化需求。

要实现这个功能,你需要编写一个 UTS(Uni Type Script)插件,直接调用鸿蒙原生的 PhotoViewPickerPHAsset 相关 API。

以下是实现这一功能的完整技术方案和代码逻辑:

核心思路

  1. 选择器(Picker): 使用鸿蒙原生的 @ohos.file.picker 模块中的 PhotoViewPicker
  2. 权限: 需要在 module.json5 中申请 ohos.permission.READ_MEDIA
  3. 数据获取: 获取到 PhotoAsset 对象后,判断其是否为 Live Photo(通常包含一张图片和一段视频)。
  4. 解析信息:
    • 视频: 从 Live Photo 中提取视频流并保存到临时路径。
    • 地址: 读取图片的 EXIF 信息(GPS 标签)获取经纬度,再反解为地址。

第一步:配置权限

uni_modules/你的插件目录/package.json 或者主项目的 module.json5 中添加权限:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.READ_MEDIA",
        "reason": "用于选择动态照片",
        "usedScene": {
          "abilities": ["MainAbility"],
          "when": "always"
        }
      }
    ]
  }
}

第二步:编写 UTS 插件代码

创建一个 UTS 类文件(例如 LivePhotoPicker.uts)。

// LivePhotoPicker.uts
import picker from '@ohos.file.picker';
import photoAccessHelper from '@ohos.file.photoAccessHelper';
import { BusinessError } from '@ohos.base';
import media from '@ohos.multimedia.media'; // 用于处理媒体资源
// 注意:读取EXIF可能需要 @ohos.multimedia.image 模块

export class LivePhotoPicker {
  private photoPicker: picker.PhotoViewPicker;

  constructor() {
    this.photoPicker = new picker.PhotoViewPicker();
  }

  /**
   * 选择动态照片
   */
  async selectLivePhotos(): Promise<Array<LivePhotoResult>> {
    return new Promise((resolve, reject) => {
      // 1. 配置选择器,允许选择图片和视频(LivePhoto本质是图+视频)
      let options = new picker.PhotoSelectOptions();
      options.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE; // 主要选图片,LivePhoto以图片形式展示
      options.maxSelectNumber = 10; // 批量获取

      this.photoPicker.select(options, (err, result) => {
        if (err) {
          reject(err);
          return;
        }

        if (result && result.photoUris && result.photoUris.length > 0) {
          // 2. 遍历选中的资源,解析详细信息
          this.parseAssets(result.photoUris).then(res => {
            resolve(res);
          }).catch(e => {
            reject(e);
          });
        } else {
          resolve([]);
        }
      });
    });
  }

  /**
   * 解析资源(核心逻辑)
   */
  private async parseAssets(uris: string[]): Promise<Array<LivePhotoResult>> {
    let results: Array<LivePhotoResult> = [];

    // 获取 PhotoAsset 实例(需要 context,这里假设你能获取到 application 的 context)
    // 注意:在 uts 插件中获取 context 方式可能因版本而异,通常是 getContext()
    let context = getContext() as common.UIAbilityContext;
    let photoView = photoAccessHelper.getPhotoView(context);

    for (let uri of uris) {
      try {
        // 通过 URI 获取 PhotoAsset
        let asset = await photoView.getAssetFromUri(uri);

        let resultItem = new LivePhotoResult();
        resultItem.imagePath = uri; // 原图路径

        // 3. 判断并提取 Live Photo 的视频部分
        // Live Photo 在鸿蒙中通常是一个主资源,包含子资源(视频)
        if (asset.type === photoAccessHelper.PhotoType.LIVE_PHOTO) {
           // 获取子资源(即视频)
           let subResources = await asset.getSubResources();
           for (let subRes of subResources) {
             if (subRes.type === photoAccessHelper.PhotoType.VIDEO) {
               // 获取视频 URI 或文件流
               resultItem.videoPath = subRes.uri;
               break;
             }
           }
        }

        // 4. 解析 EXIF 信息(地址)
        // 这里需要调用 image 模块读取图片的 ExifInterface
        // 伪代码逻辑:
        // let exif = await image.createExif(uri);
        // resultItem.location = exif.getGPSInfo();

        results.push(resultItem);

      } catch (e) {
        console.error('解析资源失败', e);
      }
    }
    return results;
  }
}

// 定义返回数据结构
export class LivePhotoResult {
  imagePath: string = ''; // 图片路径
  videoPath: string = ''; // 关联的视频路径(如果是LivePhoto)
  latitude: number = 0;   // 纬度
  longitude: number = 0;  // 经度
  address: string = '';   // 解析后的地址
}

第三步:在 UniApp 页面中调用

// 在 .vue 页面中
import { LivePhotoPicker, LivePhotoResult } from '@/uni_modules/你的插件路径/LivePhotoPicker';

const picker = new LivePhotoPicker();

async function choosePhoto() {
  try {
    const photos: LivePhotoResult[] = await picker.selectLivePhotos();
    console.log('选择的照片:', photos);

    photos.forEach(p => {
      console.log('图片:', p.imagePath);
      if (p.videoPath) {
        console.log('这是动态照片,包含视频:', p.videoPath);
      }
      if (p.address) {
        console.log('拍摄地点:', p.address);
      }
    });
  } catch (error) {
    console.error('选择失败', error);
  }
}

注意配置权限, 剩下的看下这个指南吧 https://doc.dcloud.net.cn/uni-app-x/plugin/uts-for-harmony.html

在您的uni-app项目中,通常在 uni_modules 目录下新建一个文件夹(例如 ha-photo-picker)作为插件根目录。

定义接口 :

在插件的 utssdk/interface.uts 文件中,定义将要暴露给前端的方法签名。

可以参考uts插件文档试试。

在HarmonyOS Next中,通过uts插件实现选择动态照片,需使用@ohos.multimedia.mediaLibrary API的getActiveAlbum方法获取动态照片相册,结合FileAsseturi属性选取资源。在uniapp中封装uts插件,定义接口调用系统选择器,返回动态照片的本地路径或文件对象。注意需在module.json5中配置ohos.permission.READ_MEDIA权限。

在HarmonyOS NEXT中,通过uts插件实现动态照片选择与信息解析,核心在于利用系统Picker的PhotoViewPicker能力,结合媒体库接口获取Dynamic Photos元数据。

实现步骤:

  1. 集成Picker能力:在uts插件中,调用ohos.file.pickerPhotoViewPicker。通过select方法设置MIME_TYPE_IMAGE过滤,并开启isPhotoDynamic属性支持动态照片。

  2. 批量获取URI:调用select(option)返回用户选择的图片URI列表。需设置maxSelectNumber控制批量上限。

  3. 解析动态照片:对每个URI,使用@ohos.multimedia.mediaLibrary@ohos.multimedia.image接口。通过MediaDataSource读取原数据,检查文件是否包含动态元数据(如com.android.camera.dynamic标签或HDR增益图)。动态照片通常为HEIC文件内嵌MOV/MP4视频流,或存为独立视频帧。

  4. 提取地址与视频:获取动态照片关联的视频URI(通常为同文件名不同后缀或元数据引用),使用@ohos.multimedia.mediaVideoInfo接口提取时长、尺寸。地理位置通过ImageInfoexif字段读取GPS坐标。

关键代码片段(uts):

import { PhotoViewPicker } from '@kit.MediaKit';
import { mediaLibrary } from '@kit.MediaKit';

async function pickDynamicPhotos(maxCount: number): Promise<Array<DynamicPhotoInfo>> {
  const picker = new PhotoViewPicker();
  const result = await picker.select({
    MIME_TYPE: ['image/*'],
    isPhotoDynamic: true,
    maxSelectNumber: maxCount
  });
  return result.photoUris.map(uri => extractDynamicInfo(uri));
}

function extractDynamicInfo(uri: string): DynamicPhotoInfo {
  // 使用mediaLibrary获取动态数据
  const info = getDynamicMetaData(uri);
  return {
    uri: uri,
    videoUri: info.videoUri,
    latitude: info.gpsLatitude,
    longitude: info.gpsLongitude,
    duration: info.videoDuration
  };
}

注意事项

  • Picker需在UIAbility上下文调用。
  • 动态照片功能依赖设备硬件与系统版本(HarmonyOS NEXT 5.0+)。
  • 批量选择时注意性能,避免阻塞主线程。
回到顶部