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

大家好,我是青蓝逐码 的云杰。在鸿蒙应用开发中,多媒体文件的交互能力是提升用户体验的关键一环。无论是实现社交 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()

总结

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

相关推荐
别说我什么都不会4 小时前
【仓颉三方库】对象存储——OBS Cangjie SDK
harmonyos
星释5 小时前
鸿蒙Flutter仓库停止更新?
flutter·华为·harmonyos
王老汉6 小时前
鸿蒙生态新利器:华为ArkUI-X混合开发框架深度解析
华为·harmonyos·arkui-x
NapleC7 小时前
HarmonyOS:网络HTTP数据请求
网络·http·harmonyos
新小梦11 小时前
OpenHarmony声明为系统应用和系统签名文件
harmonyos
别说我什么都不会11 小时前
【仓颉三方库】 数据解析——ini4cj
harmonyos
PMEcho11 小时前
墨刀上线高级交互功能,能否超越Axure?
产品经理·axure·交互设计·墨刀·原型设计·高级交互
悬空八只脚12 小时前
React-Native开发鸿蒙NEXT-svg绘制睡眠质量图part1
harmonyos
搞瓶可乐1 天前
鸿蒙ArkUI实战之组件;Text组件,Image组件,Button组件,Span组件和TextInput组件的使用场景及使用方法
华为·harmonyos·鸿蒙系统·arkui·组件化开发·基础组件使用