鸿蒙开发干货——手把手教你玩转多媒体文件操作

大家好,我是青蓝逐码 的云杰。在鸿蒙应用开发中,多媒体文件的交互能力是提升用户体验的关键一环。无论是实现社交 APP 的图片分享,还是视频类应用的离线缓存,掌握网络图片/视频下载、本地图片转像素进行图片处理、截图功能集成、图片/视频/不同格式文件的保存等核心技能都至关重要。本文将带你解锁鸿蒙开发中多媒体文件处理的全流程技巧,附完整代码示例和避坑指南,让你的应用轻松玩转图片、视频与文件存储。

一、网络资源下载:图片 / 视频获取的两种核心方案

以下载视频为例,下载图片也是一样的道理

request.agent.create

相关链接:developer.huawei.com/consumer/cn... 具体步骤如下:

  1. 获取上下文对象和缓存目录路径。
ts 复制代码
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.cacheDir;
  1. 定义保存到沙箱路径的视频文件名,使用当前时间戳作为文件名的一部分。
ts 复制代码
let filePath = `${filesDir}/${Date.now()}.mp4`;
this.filePath = filePath;
  1. 配置下载任务的相关参数,包括操作类型、URL、保存路径、进度通知、覆盖现有文件和网络类型。
ts 复制代码
let config: request.agent.Config = {
  action: request.agent.Action.DOWNLOAD,
  url: 'https://download.blender.org/durian/trailer/sintel_trailer-1080p.mp4',
  saveas: filePath,
  gauge: true,
  overwrite: true,
  network: request.agent.Network.WIFI
};
  1. 使用request.agent.create方法创建下载任务。
ts 复制代码
request.agent.create(context, config)
  .then(async (task: request.agent.Task) => {
    // 创建成功之后,开始下载任务
    task.start((err: BusinessError) => {
      if (err) {
        request.agent.remove(task.tid)
        promptAction.showToast({ message: '下载失败' })
      } else {
        promptAction.showToast({ message: '开始下载任务!!' })
      }
    })
    task.on('completed', async () => {
      // 下载完成--->保存到相册
    })
  })

在这个函数中,首先获取上下文对象和缓存目录路径,然后定义保存到沙箱路径的视频文件名。接着配置下载任务的相关参数,包括操作类型、URL、保存路径、进度通知、覆盖现有文件和网络类型。最后使用request.agent.create方法创建下载任务,并在任务创建成功后开始下载。如果下载过程中出现错误,将删除任务并显示提示信息;如果下载成功,将显示开始下载的提示信息。当下载任务完成时,可以在task.on('completed', ...)回调中进行后续操作,例如将视频保存到相册。

request.downloadFile

相关链接:developer.huawei.com/consumer/cn...

  1. 获取应用上下文和文件目录:
ts 复制代码
let context = getContext(this);
let filesDir: string = context.filesDir;

这里,我们首先获取应用的上下文(context),然后从上下文中获取文件目录(filesDir)。5

  1. 定义下载文件的路径:
ts 复制代码
let filePath = `${filesDir}/${Date.now()}.mp4`;

我们创建一个唯一的文件路径,其中包含当前时间戳(Date.now()),以确保每次下载的文件名都是唯一的。

  1. 下载文件:
ts 复制代码
request.downloadFile(getContext(this), {
  url: 'https://download.blender.org/durian/trailer/sintel_trailer-1080p.mp4',
  filePath: filePath
}).then((downloadTask: request.DownloadTask) => {
  downloadTask.on('complete', () => {
    // 保存至相册
  })
})

我们使用request.downloadFile方法下载一个视频文件。这个方法接受两个参数:应用上下文(getContext(this))和一个配置对象。配置对象包含以下属性:

  • url:要下载的文件的URL。
  • filePath:下载文件的保存路径。

request.downloadFile方法返回一个Promise,当下载任务创建成功时,Promise会解析为一个DownloadTask对象。我们可以使用then方法处理这个对象。

then方法的回调中,我们监听DownloadTask对象的complete事件。当下载任务完成时,这个事件会被触发。

二、Media 文档图片操作

如何将app.media.图片,转换为PixelMap,再对图片做一系列的操作呢;

相关链接:developer.huawei.com/consumer/cn...

  1. 从媒体资源中获取图片内容,将其转换为PixelMap首先,通过resourceManager.getMediaContent方法获取媒体内容的ArrayBuffer,然后创建一个ImageSource实例。接着,使用getImageInfo法获取图片的尺寸信息,并通过createPixelMap方法将ImageSource转换为PixelMap对象。最后,再次调用getImageInfo方法获取PixelMap的宽度和高度,并将其存储在组件的状态变量中。
ts 复制代码
async DoPixelMap() { 
  /**
* 转像素
*/
getContext(this).resourceManager.getMediaContent($r("app.media.img01")).then((data) => {
    let arrayBuffer = data.buffer.slice(data.byteOffset, data.byteLength + data.byteOffset)
    const imagePacker = image.createImagePacker()
    let imageSource: image.ImageSource = image.createImageSource(arrayBuffer);
    imageSource.getImageInfo((err, value) => {
      //获取图片资源的尺寸
console.log(`图片的尺寸为:width:${value.size.width}height:${value.size.height}`)
      if (err) {
        return;
      }
      //转PixelMap
 let opts: image.DecodingOptions =
        { editable: true, desiredSize: { height: value.size.height, width: value.size.width } };
      imageSource.createPixelMap(opts, (err,
        pixelMap) => {
        this.imagePixelMap = pixelMap
        this.imagePixelMap.getImageInfo().then((value) => {
          this.imageWidth = value.size.width
          this.imageHeight = value.size.height
        })
      })
    })
  })
}
  1. 图片操作

注意: 对图片操作之前要确保图片的editable属性为True

  • 图片旋转

图片旋转的角度取值范围:0-360。超出取值范围时,根据圆周360度自动矫正。例如,-100度与260度效果相同。

如果图片旋转的角度不是90的整数倍,旋转后图片的尺寸会发生改变。

ts 复制代码
pixelMap.rotateSync(angle);
  • 图片裁剪
参数名 类型 必填 说明
region Region 裁剪的尺寸。
ts 复制代码
import { BusinessError } from '@kit.BasicServicesKit';

async function CropSync() {
  let region : image.Region = { x: 0, y: 0, size: { height: 100, width: 100 } };
  if (pixelMap != undefined) {
    pixelMap.cropSync(region);
  }
}
  • 图片缩放
  1. 建议宽高的缩放倍数取非负数,否则会产生翻转效果。
  2. 宽高的缩放倍数 = 缩放后的图片宽高 / 缩放前的图片宽高。
参数名 类型 必填 说明
x number 宽度的缩放倍数。
y number 高度的缩放倍数。
level AntiAliasingLevel 采用的缩放算法。
ts 复制代码
import { BusinessError } from '@kit.BasicServicesKit';

async function ScaleSync() {
  let scaleX: number = 2.0;
  let scaleY: number = 1.0;
  if (pixelMap != undefined) {
    pixelMap.scaleSync(scaleX, scaleY, image.AntiAliasingLevel.LOW);
  }
}
  • 图片翻转
参数名 类型 必填 说明
horizontal boolean true表示进行水平翻转,false表示不进行水平翻转。
vertical boolean true表示进行垂直翻转,false表示不进行垂直翻转。
ts 复制代码
import { BusinessError } from '@kit.BasicServicesKit';

async function FlipSync() {
  let horizontal : boolean = true;
  let vertical : boolean = false;
  if (pixelMap != undefined) {
    pixelMap.flipSync(horizontal, vertical);
  }
}

三、截图功能开发

组件截图

componentSnapshot

直接使用componentSnapshot可能导致实例不明确的问题,建议使用getUIContext获取UIContext实例,并使用getComponentSnapshot获取绑定实例的componentSnapshot。

ts 复制代码
this.getUIContext().getComponentSnapshot().get('id')

视频流截图(Xcomponent组件)

参数名 类型 必填 说明
surfaceId string 对应Surface的ID,可通过预览组件获取,如XComponent组件。
region Region 区域信息。Region.size的宽高需和设置的预览流大小保持一致。
ts 复制代码
import { BusinessError } from '@kit.BasicServicesKit';

async function CreatePixelMapFromSurface(surfaceId: string) {
  let region: image.Region = { x: 0, y: 0, size: { height: 100, width: 100 } };
  image.createPixelMapFromSurface(surfaceId, region).then(() => {
    console.info('Succeeded in creating pixelmap from Surface');
  }).catch((error: BusinessError) => {
    console.error(`Failed to create pixelmap. code is ${error.code}, message is ${error.message}`);
  });
} 

屏幕截图

  1. screenshot.pick

获取屏幕截图。此接口仅可在2in1设备上使用。

ts 复制代码
import { BusinessError } from '@kit.BasicServicesKit';

try {
  let promise = screenshot.pick();
  promise.then((pickInfo: screenshot.PickInfo) => {
    pickInfo.pixelMap.release(); // PixelMap使用完后及时释放内存
  }).catch((err: BusinessError) => {
    console.log('Failed to pick. Code: ' + JSON.stringify(err));
  });
} catch (exception) {
  console.error('Failed to pick Code: ' + JSON.stringify(exception));
};
  1. screenshot.capture

获取屏幕全屏截图,此接口仅支持在平板和2in1设备上使用。与pick接口不同之处是可以通过设置不同的displayId截取不同屏幕的截图。

参数名 类型 必填 说明
options CaptureOption 截取图像的相关信息。可包含设备ID,即displayId。 此参数不填时,默认截取displayId为0的屏幕截图。
ts 复制代码
import { BusinessError } from '@kit.BasicServicesKit';
import { image } from '@kit.ImageKit';

let captureOption: screenshot.CaptureOption = {
  "displayId": 0
};
try {
  let promise = screenshot.capture(captureOption);
  promise.then((pixelMap: image.PixelMap) => {
    pixelMap.release(); // PixelMap使用完后及时释放内存
  }).catch((err: BusinessError) => {
    console.log('Failed to save screenshot. Code: ' + JSON.stringify(err));
  });
} catch (exception) {
  console.error('Failed to save screenshot. Code: ' + JSON.stringify(exception));
};

四.文件保存至本地

使用安全控件保存图片/视频

下列以保存图片为案例

  • 设置安全控件按钮属性。
  • 创建安全控件按钮。
  • 调用MediaAssetChangeRequest.createImageAssetRequestPhotoAccessHelper.applyChanges接口创建图片资源。
ts 复制代码
import { photoAccessHelper } from '@kit.MediaLibraryKit';

@Entry
@Component
struct Index {
    saveButtonOptions: SaveButtonOptions = {
    icon: SaveIconStyle.FULL_FILLED,
    text: SaveDescription.SAVE_IMAGE,
    buttonType: ButtonType.Capsule
  } // 设置安全控件按钮属性。

  build() {
    Row() {
      Column() {
        SaveButton(this.saveButtonOptions) // 创建安全控件按钮。
          .onClick(async (event, result: SaveButtonOnClickResult) => {
             if (result == SaveButtonOnClickResult.SUCCESS) {
               try {
                 let context = getContext();
                 let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
                 // 需要确保fileUri对应的资源存在。
                 let fileUri = 'file://com.example.temptest/data/storage/el2/base/haps/entry/files/test.jpg';
                   // 创建一个图片资源变更请求(申请书)  
                 let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(context, fileUri);
                //创建的申请书提交,执行申请书
                 await phAccessHelper.applyChanges(assetChangeRequest);
                 console.info('createAsset successfully, uri: ' + assetChangeRequest.getAsset().uri);
               } catch (err) {
                 console.error(`create asset failed with error: ${err.code}, ${err.message}`);
               }
             } else {
               console.error('SaveButtonOnClickResult create asset failed');
             }
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

弹窗授权保存图片/视频

下列以保存视频为案例

  • 指定待保存到媒体库的位于应用沙箱的应用文件图片uri。
  • 指定待保存照片的创建选项,包括文件后缀和照片类型,标题和照片子类型可选。
  • 调用showAssetsCreationDialog,基于弹窗授权的方式获取的目标媒体文件uri。
  • 将来源于应用沙箱的照片内容写入媒体库的目标uri。
ts 复制代码
async save(srcFileUri: string) {
  let context = getContext(this);
  let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
  try {
    let srcFileUris: Array<string> = [
      srcFileUri
    ];
    // 指定待保存照片的创建选项,包括文件后缀和照片类型,标题和照片子类型可选
 let photoCreationConfigs: Array<photoAccessHelper.PhotoCreationConfig> = [
      {
        title: `${Date.now()}`, // 可选
fileNameExtension: 'mp4',
        photoType: photoAccessHelper.PhotoType.VIDEO,
        subtype: photoAccessHelper.PhotoSubtype.DEFAULT, // 可选
}
    ];
    // 基于弹窗授权的方式获取媒体库的目标uri
 let desFileUris: Array<string> = await phAccessHelper.showAssetsCreationDialog(srcFileUris, photoCreationConfigs);
    // 将来源于应用沙箱的内容写入媒体库的目标uri
 let desFile: fileIo.File = await fileIo.open(desFileUris[0], fileIo.OpenMode.WRITE_ONLY);
    let srcFile: fileIo.File = await fileIo.open(srcFileUri, fileIo.OpenMode.READ_ONLY);
    await fileIo.copyFile(srcFile.fd, desFile.fd);
    fileIo.closeSync(srcFile);
    fileIo.closeSync(desFile);
    promptAction.showToast({ message: '下载成功' })
  } catch (err) {
    console.error(`failed to create asset by dialog successfully errCode is: ${err.code}, ${err.message}`);
  }
}

保存文件

  1. 选择文件保存位置
  2. 将数据导出至我们选择的位置

以保存txt文件为案例

ts 复制代码
import { common } from '@kit.AbilityKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { picker } from '@kit.CoreFileKit'
import { fileIo as fs } from '@kit.CoreFileKit';
@Entry
@Component
struct KeyEventExample {
  @State Text: string = '你好啊'
  @State uris: Array<string> = [];
  build() {
    Column() {
      Text(this.Text)
      Button('创建保存文件')
        .onClick(() => {
          let documentSaveOptions = new picker.DocumentSaveOptions();
          documentSaveOptions.newFileNames = ['DocumentViewPicker01.txt'];
          // 请确保 getContext(this) 返回结果为 UIAbilityContext
          let context = getContext(this) as common.Context;
          // 创建文件选择器实例。
          const documentViewPicker = new picker.DocumentViewPicker(context);
          //用户选择目标文件夹,用户选择与文件类型相对应的文件夹,即可完成文件保存操作。保存成功后,返回保存文档的URI。
          documentViewPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => {
            this.uris = documentSaveResult;
          })
            .catch((err: BusinessError) => {
              AlertDialog.show({
                message: JSON.stringify(`Invoke documentViewPicker.save failed, code is ${err.code}, message is ${err.message}`,
                  null, 2)
              })
            })
        })
      Button('写入保存文件')
        .onClick(() => {
          //这里需要注意接口权限参数是fs.OpenMode.READ_WRITE。
          let file = fs.openSync(this.uris[0], fs.OpenMode.READ_WRITE);
          let writeLen: number = fs.writeSync(file.fd, '吾乃天下第一');
          AlertDialog.show({ message: JSON.stringify('write data to file succeed and size is:' + writeLen, null, 2) })
          fs.closeSync(file);
        })
    }.height(300).width('100%').padding(35)
  }
}

封装

ts 复制代码
 /**
* 保存到本地
*/
import { common } from '@kit.AbilityKit';
import { fileIo as fs, picker } from '@kit.CoreFileKit';
import { CountType } from '../pages/FileTextPage';
import { promptAction } from '@kit.ArkUI';

class SaveFile {
  uris: Array<string> = [];

  async getAdreessTxt() {
    let documentSaveOptions = new picker.DocumentSaveOptions();
    documentSaveOptions.newFileNames = [`数据${Date.now()}.txt`];
    // 请确保 getContext(this) 返回结果为 UIAbilityContext
 let context = getContext(this) as common.Context;
    // 创建文件选择器实例。
 const documentViewPicker = new picker.DocumentViewPicker(context);
    this.uris = await documentViewPicker.save(documentSaveOptions)
  }

  async getAdreessJson() {
    let documentSaveOptions = new picker.DocumentSaveOptions();
    documentSaveOptions.newFileNames = [`数据${Date.now()}.json`];
    // 请确保 getContext(this) 返回结果为 UIAbilityContext
 let context = getContext(this) as common.Context;
    // 创建文件选择器实例。
 const documentViewPicker = new picker.DocumentViewPicker(context);
    this.uris = await documentViewPicker.save(documentSaveOptions)
  }

  async writeFile(数据) {
    //这里需要注意接口权限参数是fs.OpenMode.READ_WRITE。
 let file = fs.openSync(this.uris[0], fs.OpenMode.READ_WRITE);
    let writeLen: number = fs.writeSync(file.fd, JSON.stringify(数据));
    promptAction.showToast({ message: '导出成功' })
    fs.closeSync(file);
  }
}

export  const savefile = new SaveFile()

总结

以上我们详细拆解了鸿蒙开发中多媒体文件处理的全流程核心技术,涵盖网络图片/视频下载、本地像素处理、截图集成及多格式文件存储等关键场景,附可直接复用的代码示例与避坑经验。期待你将这些技术应用于实际项目,提升应用交互体验,若有任何实践疑问或创新想法,欢迎随时留言探讨,后续将带来更多鸿蒙开发实战干货!

相关推荐
心态还需努力呀1 分钟前
【鸿蒙 PC 命令行适配】c-ares 在鸿蒙 PC 上的移植与交叉编译实战(可复现指南)
c语言·开源·harmonyos·鸿蒙·openharmony
摘星编程8 分钟前
React Native鸿蒙版:forwardRef组件引用转发
react native·react.js·harmonyos
俺不理解12 分钟前
鸿蒙 Stage Arkts HSP+HAR 的集成
华为·harmonyos·模块化·har·hsp
小雨青年14 分钟前
鸿蒙 HarmonyOS 6 | AI Kit 集成 CANN Kit 异构计算服务
人工智能·华为·harmonyos
前端不太难36 分钟前
HarmonyOS 游戏卡顿,问题不在渲染
华为·状态模式·harmonyos
讯方洋哥38 分钟前
HarmonyOS App开发——一多图片浏览器应用App开发
华为·harmonyos
Miguo94well9 小时前
Flutter框架跨平台鸿蒙开发——海龟汤APP的开发流程
flutter·华为·harmonyos·鸿蒙
讯方洋哥10 小时前
HarmonyOS App开发——购物商城应用App开发
harmonyos
无穷小亮10 小时前
Flutter框架跨平台鸿蒙开发——Excel函数教程APP的开发流程
flutter·华为·excel·harmonyos·鸿蒙
无穷小亮10 小时前
Flutter框架跨平台鸿蒙开发——打字练习APP开发流程
flutter·华为·harmonyos·鸿蒙