跳到主要内容

发送媒体消息

媒体消息

媒体消息指图片、文件等需要处理媒体文件上传的消息。媒体消息的消息内容类型为 MeidaMessageContent 的子类,例如高清语音消息内容(HQVoiceMessage)。

SDK 不支持自定义媒体消息

获取媒体消息本地路径

鸿蒙系统内部获取媒体路径可以分为两类

  1. 从沙箱中获取:类似于 iOS 的沙盒机制,鸿蒙的应用程序只能访问自己的沙箱目录,无法访问其他应用程序的沙箱目录。
    • 沙箱路径 SDK 可以直接访问
  2. 从系统路径获取:例如系统相册,系统文件管理等
    • 系统路径 SDK 无法直接访问,需要将系统路径媒体文件拷贝到沙箱路径
    • 可以使用下面的代码将系统路径文件拷贝到沙箱路径
import fs from '@ohos.file.fs';
import { ArrayChecker, StringChecker } from '@rongcloud/imlib';

/**
* 将系统路径文件复制到沙盒工具类
*/
class SandboxUtil {
/**
* 将相册图片或者文件管理中文件批量复制到沙盒
* @param context 上下文
* @param uris 系统路径数组
* @returns 沙盒路径数组
*/
public static async copyToSandbox(context: Context, uris: Array<string>): Promise<Array<string>> {
let targetList = new Array<string>();
if (!ArrayChecker.isValid(uris)) {
return targetList;
}
for (let u of uris) {
let file = await fs.open(u, fs.OpenMode.READ_ONLY);
let targetPath = context.cacheDir + "/" + file.name;
fs.closeSync(file);
targetPath = "file://" + targetPath;
await fs.copy(u, targetPath);
targetList.push(targetPath);
}
return targetList;
}

/**
* 将单个系统媒体路径的媒体资源复制到沙盒
* @param context 上下文
* @param uri 系统路径
* @returns 沙盒路径
*/
public static async copyOneToSandbox(context: Context, uri: string) : Promise<string> {
let retUri = "";
if (StringChecker.isEmpty(uri)) {
return retUri;
}
let file = await fs.open(uri, fs.OpenMode.READ_ONLY);
let targetPath = context.cacheDir + "/" + file.name;
fs.closeSync(file);
targetPath = "file://" + targetPath;
await fs.copy(uri, targetPath);
return targetPath;
}
}

export { SandboxUtil }

选择相册文件管理中的文件资源参考鸿蒙文档

发送图片消息

在发送前,图片会被压缩质量,以及生成缩略图,在聊天界面中展示。GIF 无缩略图,也不会被压缩。

  • 图片消息的缩略图:SDK 会以原图 30% 质量生成符合标准大小要求的大图后再上传和发送。压缩后最长边不超过 240 px。缩略图用于在聊天界面中展示。
  • 图片:发送消息时如未选择发送原图,SDK 会以原图 85% 质量生成符合标准大小要求的大图后再上传和发送。压缩后最长边不超过 1080 px。

从系统相册获取图片地址

  private chooseImage() {
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
photoSelectOptions.maxSelectNumber = 20; // 选择媒体文件的最大数目
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then(async (photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
let systemUris = photoSelectResult.photoUris;
// 过滤里面的 gif 图
for (let uri of systemUris) {
let name = uri.slice(uri.lastIndexOf('.') + 1);
if (name.toLowerCase() === "gif") {
promptAction.showToast({ message: "所选图片包含 GIF 图片,请重新选择", duration: 1000 })
return
}
}

// 将系统相册中的图片移动到沙盒
// 然后将图片路径传给消息发送接口
let uris = await SandboxUtil.copyToSandbox(getContext(this), systemUris);

}).catch((err: BusinessError) => {
console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
})
}

发送图片消息


let conId = new ConversationIdentifier();
conId.conversationType = ConversationType.Private;
conId.targetId = "会话 id";

let imgMsg = new ImageMessage();
// 使用上一步的沙盒路径创建图片消息
imgMsg.localPath = localPath;

let msg = new Message(conId, imgMsg);

let option: ISendMsgOption = {};

IMEngine.getInstance().sendMediaMessage(msg, option, (msg: Message) => {
// 消息保存到数据库
}, (msg: Message, progress: number) => {
// 媒体上传进度 [1 ~ 100]
}).
then(result => {
if (EngineError.Success !== result.code) {
//发送消息失败
return;
}
if (!result.data) {
// 消息为空
return;
}
let msg = result.data as Message;
})

发送 Gif 消息(1.2.0 版本支持)

从文件管理获取 Gif 路径

  private chooseImage() {
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
photoSelectOptions.maxSelectNumber = 20; // 选择媒体文件的最大数目
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then(async (photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
let systemUris = photoSelectResult.photoUris;
// 通过后缀名检查是不是 Gif 图
for (let uri of systemUris) {
let name = uri.slice(uri.lastIndexOf('.') + 1);
if (name.toLowerCase() !== "gif") {
promptAction.showToast({ message: "所选图片包含非 GIF 图片,请重新选择", duration: 1000 })
return
}
}

// 将系统相册中的 gif 移动到沙盒
// 然后将 gif 路径传给消息发送接口
let uris = await SandboxUtil.copyToSandbox(getContext(this), systemUris);
}).catch((err: BusinessError) => {
console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
})
}

发送 gif 消息


let conId = new ConversationIdentifier();
conId.conversationType = ConversationType.Private;
conId.targetId = "会话 id";

let gifMsg = new GIFMessage();
// 使用上一步的沙盒路径创建 gif 消息
gifMsg.localPath = localPath;

let msg = new Message(conId, gifMsg);

let option: ISendMsgOption = {};

IMEngine.getInstance().sendMediaMessage(msg, option, (msg: Message) => {
// 消息保存到数据库
}, (msg: Message, progress: number) => {
// 媒体上传进度 [1 ~ 100]
}).
then(result => {
if (EngineError.Success !== result.code) {
//发送消息失败
return;
}
if (!result.data) {
// 消息为空
return;
}
let msg = result.data as Message;
})

发送文件消息

从文件管理获取文件路径

    const documentSelectOptions = new picker.DocumentSelectOptions();
documentSelectOptions.maxSelectNumber = 5; // 选择文档的最大数目(可选)
documentSelectOptions.defaultFilePathUri = "file://docs/storage/Users/currentUser"; // 指定选择的文件或者目录路径(可选)
documentSelectOptions.fileSuffixFilters =
['.png', '.txt', '.mp3', '.mp4', '.apk', '.zip', '.docx', '.log', '.xlsx', '.jpg', '.pdf', '.jpeg', '.csv',
'.png', '.chls', '.ipa', '.doc', '.yaml', '.json',
'.html']; // 选择文件的后缀类型,若选择项存在多个后缀名,则每一个后缀名之间用英文逗号进行分隔,而且后缀类型名不能超过100个(可选)。

let uris: Array<string> = [];
const documentViewPicker = new picker.DocumentViewPicker(); // 创建文件选择器实例
documentViewPicker.select(documentSelectOptions).then(async (documentSelectResult: Array<string>) => {
// 将系统文件路径改为沙盒路径
// 使用沙盒路径发送消息
let fileUris: Array<string> = await SandboxUtil.copyToSandbox(getContext(this), documentSelectResult);

console.info('documentViewPicker.select to file succeed and uris are:' + uris);
}).catch((err: BusinessError) => {
console.error(**Invoke documentViewPicker.select failed, code is ${err.code}, message is ${err.message}**);
})

发送文件消息

    let conId = new ConversationIdentifier();
conId.conversationType = ConversationType.Private;
conId.targetId = "会话 id";

// 创建文件消息
let fileMsg = new FileMessage();
// 文件的沙箱路径
fileMsg.localPath = localPath;

let msg = new Message(conId, fileMsg);

let option: ISendMsgOption = {};

IMEngine.getInstance().sendMediaMessage(msg, option, (msg: Message) => {
// 消息保存到数据库
}, (msg: Message, progress: number) => {
// 媒体上传进度 [1 ~ 100]
}).
then(result => {
if (EngineError.Success !== result.code) {
//发送消息失败
return;
}
if (!result.data) {
// 消息为空
return;
}
let msg = result.data as Message;
})

遇到报错 InvalidArgumentMediaLocalPath 有如下原因

  1. 传入的路径不是文件路径
  2. 传入的不是沙箱路径而是系统路径

发送高清语音

高清语音的录制

鸿蒙语音录制文档: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/using-avrecorder-for-recording-V5

语音录制示例代码

语音录制的调用示例代码

提示

注:鸿蒙 Next beta1 遇到语音消息互通的问题,鸿蒙和安卓使用相同的录制参数

鸿蒙录制的语音消息,鸿蒙播放正常,安卓播放正常

安卓录制的语音消息,安卓播放正常,鸿蒙播放异常

鸿蒙 Next beta2 已修复,具体修复版本信息如下

手机版本 5.0 beta2,手机显示版本号 NEXT.0.0.31,对应 DevEco-Studio 版本 5.0.3.500

构造高清语音消息

    let conId = new ConversationIdentifier();
conId.conversationType = ConversationType.Private;
conId.targetId = "会话 id";

// 创建高清语音消息
let hqMsg = new HQVoiceMessage();
// 高清语音的沙箱路径
hqMsg.localPath = localPath;
// 高清语音消息时长,单位秒
hqMsg.duration = duration;

let msg = new Message(conId, hqMsg);

let option: ISendMsgOption = {};
IMEngine.getInstance().
sendMediaMessage(msg, option, (msg: Message) => {
// 消息保存到数据库
}, (msg: Message, progress: number) => {
// 媒体上传进度 [1 ~ 100]
}).
then(result => {
if (EngineError.Success !== result.code) {
//发送消息失败
return;
}
if (!result.data) {
// 消息为空
return;
}
let msg = result.data as Message;
})

发送小视频消息(1.2.0 版本支持)

鸿蒙相机服务:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/camera-overview-V5

录制视频示例代码

  private async recordSight() {
// 视频配置
let pickerProfile: cameraPicker.PickerProfile = {
// 后置摄像头
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
// 设置录制总时长 10 秒,到点自动停止录制
videoDuration: 10,
}

let pickerResult: cameraPicker.PickerResult =
await cameraPicker.pick(getContext(this), [cameraPicker.PickerMediaType.VIDEO], pickerProfile);
if (pickerResult.resultCode === 0) {
let uri = pickerResult.resultUri;
// 将系统路径的视频数据复制到沙盒路径,SDK 只能访问沙盒路径的视频数据
this.sightUri = await SandboxUtil.copyOneToSandbox(getContext(this), uri);
}
}

视频工具类

视频工具类:用于生成缩略图和视频时长

创建小视频消息示例代码

    let conId = new ConversationIdentifier();
conId.conversationType = ConversationType.Private;
conId.targetId = "会话 id";

let sightMsg = new SightMessage();

// 设置沙盒路径为本地路径
sightMsg.localPath = this.sightUri;

// 设置缩略图和视频时长
let result = await MySightUtil.getSightInfo(this.sightUri);
// 不设置 base64 ,小视频将没有缩略图
sightMsg.base64 = result[0];
// 不设置时长,小视频的时长将为 0
sightMsg.duration = result[1];

发送媒体消息

sendMediaMessage 方法使用消息推送属性配置 PushConfig,其中包含了 pushContentpushData

关于是否需要配置输入参数或 MessagepushConfig 属性中 pushContent,请参考以下内容:

  • 如果消息类型为即时通讯服务预定义消息类型中的用户内容类消息格式,例如 HQVoiceMessagepushContent 可设置为 null。一旦消息触发离线推送通知时,远程推送通知默认使用服务端预置的推送通知内容。关于各类型消息的默认推送通知内容,详见用户内容类消息格式
  • 如果消息类型为即时通讯服务预定义消息类型中通知类、信令类("撤回命令消息" 除外),且需要支持远程推送通知,则必须填写 pushContent,否则收件人不在线时无法收到远程推送通知。如无需触发远程推送,可不填该字段。
  • 请注意,聊天室会话不支持离线消息机制,因此也不支持离线消息转推送。
参数类型说明
messageMessage要发送的消息体。必填属性包括会话类型(conversationType),会话 ID(targetId),消息内容(content)。详见消息介绍 中对 Message 的结构说明。
optionISendMsgOption发送消息配置,目前为空

通过 IMEnginesendMediaMessage 方法的回调,融云服务器始终会通知媒体文件上传进度,以及您的消息是否已发送成功。当因任何问题导致发送失败时,可通过回调方法返回异常。

发送媒体消息的方法默认将媒体文件上传到融云的文件服务器,您可以在客户端应用程序 下载媒体消息文件

发送媒体消息并且上传到自己的服务器

您可以直接发送您服务器上托管的文件。将媒体文件的 URL(表示其位置)作为参数,在构建媒体消息内容时传入。在这种情况下,您的文件不会托管在融云服务器上。当您发送带有远程 URL 的文件消息时,文件大小没有限制,您可以直接使用 sendMessage 方法发送消息。