环信鸿蒙IM SDK实现附件消息发送与下载

时间:2024-11-01 07:12:41

环信HarmonyOS IM SDK 正式版已经发布,该版本全面覆盖即时通讯(IM)的核心功能,为用户提供了完整的IM全功能体验,同时支持从Android APK到 NEXT 的数据迁移,更好地满足企业在不同业务场景下的适配需求。

点此下载最新版环信 HarmonyOS IM SDK

环信鸿蒙IM SDK 除文本消息外,还支持发送附件类型消息,包括语音、图片、视频和文件消息。本文为大家介绍从系统获取系统图片,视频,语音文件,使用sdk进行发送与下载。

一、发送附件消息
1、发送图片消息

① 权限添加
在对应模块的module.json5配置中,添加requestPermissions节点和对应权限的配置:

{
    "name": "ohos.permission.READ_MEDIA",
    "reason": "$string:app_name",
    "usedScene": {
        "abilities": [
            "EntryAbility"
        ]
    }
},
{
    "name": "ohos.permission.WRITE_MEDIA",
    "reason": "$string:app_name",
    "usedScene": {
        "abilities": [
            "EntryAbility"
        ]
    }
}

② 权限检测

//检查权限
export  async function checkAppPermission(context:common.UIAbilityContext): Promise<boolean> {
  try {
  const READ_MEDIA_PERMISSION: Permissions = 'ohos.permission.READ_MEDIA' //媒体读取权限
  const WRITE_MEDIA_PERMISSION: Permissions = 'ohos.permission.WRITE_MEDIA' //媒体写入权限
  let permissionList: Permissions[] = []; //需要申请选项列表
  let readPermission = await checkPermissions(READ_MEDIA_PERMISSION)//检查是否有媒体读取权限
  !readPermission && permissionList.push(READ_MEDIA_PERMISSION)
  let writePermission = await checkPermissions(WRITE_MEDIA_PERMISSION)//检查是否有媒体写入权限
  !writePermission && permissionList.push(READ_MEDIA_PERMISSION)

  if (permissionList.length) {
  //申请权限
  let res: boolean = await applyPermission(context, permissionList)
  if (!res) {//用户未同意授权
  AlertDialog.show({
    title: "提示",
    message: "无权限读写用户外部存储中的媒体文件信息,请前往系统设置开启",
    alignment: DialogAlignment.Center,
    secondaryButton: {
      value: '关闭',
      action: () => {

      }
    }
  })
}
return res
}
return true

}

catch (e) {
  return Promise.reject(e)
}
}

检查用户权限:

//检查用户权限
//@params permissions:权限名称数组
export  async function checkPermissions(permissions: Permissions): Promise<boolean> {
  try {
    let grantStatus: abilityAccessCtrl.GrantStatus = await checkAccessToken(permissions);
    return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
  }
  catch (e) {
    return Promise.reject(e)
  }
}

从相册中选择图片:

 //从相册选择
    let PhotoSelectOptions = new picker.PhotoSelectOptions();
    PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
    PhotoSelectOptions.maxSelectNumber = 1;
    let photoPicker = new picker.PhotoViewPicker();
    photoPicker.select(PhotoSelectOptions).then(async (PhotoSelectResult) => {
      if (PhotoSelectResult.photoUris.length) {
        //复制图片到缓存目录(缓存目录才有读写权限)
        let filePath = PhotoSelectResult.photoUris[0]
        if (filePath) {
        //这里获取到的filePath 就是图片的路径

        }
      }

发送方调用 createImageSendMessage 方法传入图片的本地资源标志符 URI、设置是否发送原图以及接收方的用户 ID (群聊或聊天室分别为群组 ID 或聊天室 ID)创建图片消息,然后调用 sendMessage 方法发送该消息。SDK 会将图片上传至环信服务器,服务器自动生成图片缩略图。

这里imageFilePathOrUri 为上面获取到的filePath
// `imageFilePathOrUri` 为图片本地路径或者Uri。
let message = ChatMessage.createImageSendMessage(toChatUsername, imageFilePathOrUri);
// 会话类型,包含 `Chat`、`GroupChat` 和 `ChatRoom`,表示单聊、群聊或聊天室,默认为单聊。
message.setChatType(ChatType.GroupChat);
// 发送消息
ChatClient.getInstance().chatManager()?.sendMessage(message);

2、发送视频消息

① 缩略图获取

import picker from '@ohos.file.picker';
import {checkAppPermission} from '../../utils/permissions/CheckAccess'
import { common } from '@kit.AbilityKit';
import camerapicker from '@ohos.multimedia.cameraPicker';
import camera from '@ohos.multimedia.camera';
import { BusinessError } from '@ohos.base';
import { ChatMessage, } from 'easemob'
import  SendMessages from '../../presenter/SendMessage'
//全局公共样式
@Styles
function fillScreen() {
  .width('100%')
  .backgroundColor(Color.White)
  .borderRadius(20)
  .padding(10)
}

@CustomDialog
@Component
export struct AttachmentMessageDialog {
  @Prop conversationId: ResourceStr
  @Prop imgMsg: ChatMessage|undefined = undefined
  changeMessage = (imgMsg: ChatMessage|undefined)=> {}

  private controller: CustomDialogController
  private context = getContext(this) as common.UIAbilityContext; //UIAbilityContext
  build() {
    Column() {
      Row(){
        Image($r('app.media.ease_chat_takepic_pressed'))
          .width(20)
          .height(20)
        Text("相机")
          .fontColor('#33b1ff')

      }
      .width('100%')
      .padding({top:30,left:20,bottom:20})
      .onClick(async ()=>{this.camera()})
      Row(){}.width('100%').backgroundColor("#e7e9eb").height(1)
      Row(){
        Image($r('app.media.ease_chat_image_pressed'))
          .width(20)
          .height(20)
        Text("相册")
          .fontColor('#33b1ff')

      }
      .width('100%')
      .padding({top:20,left:20,bottom:20})
      .onClick(async () =>{this.photo()})
      Row(){}.width('100%').backgroundColor("#e7e9eb").height(1)
      Row(){
        Image($r('app.media.em_chat_video_pressed'))
          .width(20)
          .height(20)
        Text("视频")
          .fontColor('#33b1ff')
      }
      .width('100%')
      .padding({top:20,left:20,bottom:20})
      .onClick(async ()=>{ this.video()})
      Row(){}.width('100%').backgroundColor("#e7e9eb").height(1)
      Row(){
        Image($r('app.media.em_chat_card_normal'))
          .width(20)
          .height(20)
        Text("名片")
          .fontColor('#33b1ff')
      }
      .width('100%')
      .padding({top:20,left:20,bottom:20})
      .onClick( ()=>{

      })
      Row(){}.width('100%').backgroundColor("#e7e9eb").height(7)
      Row(){
        Text("取消")
          .fontColor('#33b1ff')
      }
      .padding({top:20,left:20,bottom:20})

    }
    .width('100%')

  }

  private async photo(){
    //检查是否有读写外部媒体权限
    let res: boolean = await checkAppPermission(this.context)
    //无权限返回
    if (!res) return
    //从相册选择
    let PhotoSelectOptions = new picker.PhotoSelectOptions();
    PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
    PhotoSelectOptions.maxSelectNumber = 1;
    let photoPicker = new picker.PhotoViewPicker();
    photoPicker.select(PhotoSelectOptions).then(async (PhotoSelectResult) => {
      if (PhotoSelectResult.photoUris.length) {
        //复制图片到缓存目录(缓存目录才有读写权限)
        let filePath = PhotoSelectResult.photoUris[0]
        if (filePath) {
          SendMessages.sendImageMessage(this.conversationId+"", filePath).then((value)=>{
            this.changeMessage(value)
          })


        }
      }
    })
  }
  private async camera(){
    //检查是否有读写外部媒体权限
    let res: boolean = await checkAppPermission(this.context)
    //无权限返回
    if (!res) return
    try {
      let pickerProfile: camerapicker.PickerProfile = {
        cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
      };
      let pickerResult: camerapicker.PickerResult = await camerapicker.pick(this.context,
        [camerapicker.PickerMediaType.PHOTO, camerapicker.PickerMediaType.PHOTO], pickerProfile);
      if(pickerResult?.resultUri){


      }
    } catch (error) {
      let err = error as BusinessError;
      console.error(`the pick call failed. error code: ${err.code}`);
    }
  }
  private async video(){
    console.log(this.conversationId+"------")
    //检查是否有读写外部媒体权限
    let res: boolean = await checkAppPermission(this.context)
    //无权限返回
    if (!res) return
    //从相册选择
    let PhotoSelectOptions = new picker.PhotoSelectOptions();
    PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.VIDEO_TYPE;
    PhotoSelectOptions.maxSelectNumber = 1;
    let photoPicker = new picker.PhotoViewPicker();
    photoPicker.select(PhotoSelectOptions).then(async (PhotoSelectResult) => {
      if (PhotoSelectResult.photoUris.length) {
        //复制图片到缓存目录(缓存目录才有读写权限)
        let filePath = PhotoSelectResult.photoUris[0]
        if (filePath) {


          SendMessages.sendVideoMessage(this.conversationId+"", filePath,this.context).then((value)=>{
             this.changeMessage(value)
          })
          console.log("PhotoSelectOptions",filePath)



        }
      }
    })
  }
}

②发送方调用 createVideoSendMessage 方法传入接收方的用户 ID(群聊或聊天室分别为群组 ID 或聊天室 ID)、视频文件的本地路径、视频时长以及缩略图的本地存储路径,然后调用 sendMessage 方法发送消息。SDK 会将视频文件上传至消息服务器。若需要视频缩略图,你需自行获取视频首帧的路径,将该路径传入 createVideoSendMessage 方法。
上面获取到视频首诊

// 在应用层获取视频首帧
let thumbPath = this.getThumbPath(videoPath);
let message = ChatMessage.createVideoSendMessage(toChatUsername, videoPath, videoLength, thumbPath);
if (!message) {
    return;
}
// 会话类型,包含 `Chat`、`GroupChat` 和 `ChatRoom`,表示单聊、群聊或聊天室,默认为单聊。
message.setChatType(ChatType.GroupChat);
// 发送消息
ChatClient.getInstance().chatManager()?.sendMessage(message);

3、发送语音消息

①音频录制

// 音频录制
import { media } from '@kit.MediaKit';
import { BusinessError, systemDateTime } from '@kit.BasicServicesKit';
import fs from '@ohos.file.fs';
class AudioRecorder {
  private avRecorder: media.AVRecorder | undefined = undefined;
  public maxAmplitude: number = 0;
  public audioPath = ""

  private avProfile: media.AVRecorderProfile = {
    audioBitrate: 100000, // 音频比特率
    audioChannels: 2, // 音频声道数
    audioCodec: media.CodecMimeType.AUDIO_AAC, // 音频编码格式,当前只支持aac
    audioSampleRate: 48000, // 音频采样率
    fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // 封装格式,当前只支持m4a
  };
  private avConfig: media.AVRecorderConfig = {
    audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // 音频输入源,这里设置为麦克风
    profile: this.avProfile,
    url: 'fd://35', // 参考应用文件访问与管理开发示例新建并读写一个文件
  };

  // 注册audioRecorder回调函数
  setAudioRecorderCallback(): void {
    if (this.avRecorder !== undefined) {
      // 状态机变化回调函数
      this.avRecorder.on('stateChange', (state: media.AVRecorderState, _: media.StateChangeReason) => {
        console.log(`AudioRecorder current state is ${state}`);
      })
      // 错误上报回调函数
      this.avRecorder.on('error', (err: BusinessError) => {
        console.error(`AudioRecorder failed, code is ${err.code}, message is ${err.message}`);
      })
    }
  }

  // 开始录制对应的流程
  async startRecordingProcess(): Promise<void> {
    if (this.avRecorder !== undefined) {
      await this.avRecorder.release();
      this.avRecorder = undefined;
    }
    // 1.创建录制实例
    this.avRecorder = await media.createAVRecorder();
    this.setAudioRecorderCallback();
    // 2.获取录制文件fd赋予avConfig里的url;参考FilePicker文档
    const context = getContext(this);
    const path = context.filesDir;

    let currentTimeMillis: number = new Date