应用内通话消息,支持应用实现网络音视频通话的能力。当终端处于锁屏或解锁两种不同状态时,Push Kit将分别进行以下处理:
终端处于锁屏状态时,可在锁屏上点击接听或拒绝按钮。锁屏状态下只支持接听语音。
终端处于解锁状态时,网络音视频通话呼叫消息显性展示于横幅,支持用户接听视频或语音。
接听视频时会拉起应用内的接听界面。接通后,可以正常挂断(主动挂断/被动挂断)应用内通话消息。
应用内通话消息样式可参考如下示例,真实样式请以实际效果为准:
| 锁屏 | 来电横幅 |
|---|---|
![]() | ![]() |
推送应用内通话消息能力支持Phone、Tablet设备,并且从6.1.0(23)版本开始,新增支持儿童智能表。
推送应用内通话消息需要申请场景化消息权益,请参见申请推送应用内通话消息权益。
调测阶段,每个项目每日全网最多可推送1000条测试消息。发送测试消息需设置testMessage为true。
正式发布阶段,单设备单应用下每日推送消息总条数受设备消息频控限制,系统会根据使用场景和流量进行管控,不合理的使用场景系统会进行频控。
参见指导获取Push Token。
在您的工程内创建一个UIAbility类型的组件,如VoIPUIAbility.ets(在项目工程的src/main/ets/entryability目录下),负责处理应用内通话消息的主流程,并完成onCreate()、onWindowStageCreate()、onDestroy()方法的覆写,代码示例如下:
- import { UIAbility } from '@kit.AbilityKit';
- import { pushService } from '@kit.PushKit';
- import { window } from '@kit.ArkUI';
- import { hilog } from '@kit.PerformanceAnalysisKit';
- import { VoipCallService } from '../service/VoipCallService';
- import { BusinessError } from '@kit.BasicServicesKit';
-
- const DOMAIN = 0x0000;
-
- export default class VoIPUIAbility extends UIAbility {
- onCreate(): void {
- hilog.info(DOMAIN, 'testTag', 'VoIPUIAbility onCreate');
-
- try {
- pushService.receiveMessage('VoIP', this, async (data) => {
- // 处理应用内通话消息数据
- try {
- await VoipCallService.processVoIPMainMsg(data.data, this.context);
- } catch (error) {
- hilog.error(DOMAIN, 'testTag', 'Failed to process VoIP message: %{public}d %{public}s', error.code,
- error.message);
- }
- });
- } catch (e) {
- hilog.error(DOMAIN, 'testTag', `Failed to register VoIP, error: ${e.code}, ${e.message}.`);
- }
- }
-
- onWindowStageCreate(windowStage: window.WindowStage): void {
- hilog.info(DOMAIN, 'testTag', 'VoIPUIAbility onWindowStageCreate.');
-
- windowStage.loadContent('pages/CalleePage').catch((err: BusinessError) => {
- hilog.error(DOMAIN, 'testTag', `Failed to load content, error: ${err.code}, ${err.message}.`);
- });
- }
-
- onDestroy(): void {
- hilog.info(DOMAIN, 'testTag', 'VoIPUIAbility onDestroy');
- }
- }
VoipCallService.ets(在项目工程的src/main/ets/service目录下),处理应用内通话消息,代码示例如下:
- import { voipCall } from '@kit.CallServiceKit';
- import { hilog } from '@kit.PerformanceAnalysisKit';
- import { common } from '@kit.AbilityKit';
- import { image } from '@kit.ImageKit';
- import { resourceManager } from '@kit.LocalizationKit';
- import { BusinessError } from '@kit.BasicServicesKit';
-
- export interface VoipScene {
- scene: string;
- }
-
- export interface Content {
- data: string;
- header: string;
- callId: string;
- }
-
- const DOMAIN = 0x0000;
-
- export class VoipCallService {
- private static callId: string | undefined;
-
- public static async processVoIPMainMsg(data: string,
- context: common.UIAbilityContext): Promise<void> {
- hilog.info(DOMAIN, 'testTag', `Process VoIP message: ${data}`);
-
- let content: Content;
- let scene: VoipScene;
- let callId: string;
-
- try {
- content = JSON.parse(data);
- scene = JSON.parse(content.data);
- callId = content.callId;
- if (!callId) {
- hilog.error(DOMAIN, 'testTag', 'CallId is null');
- }
- VoipCallService.callId = callId;
- } catch (e) {
- let err: BusinessError = e as BusinessError;
- hilog.error(DOMAIN, 'testTag', 'Failed to parse VoIP message data: %{public}d %{public}s', err.code, err.message);
- return;
- }
-
- try {
- // 注册voipCallUiEvent事件
- voipCall.on('voipCallUiEvent', async (event) => {
- hilog.info(DOMAIN, 'testTag', `Process voip call ui event: ${JSON.stringify(event)}.`);
-
- await VoipCallService.processVoipCallEvent(event.voipCallUiEvent);
- });
- } catch (err) {
- let e: BusinessError = err as BusinessError;
- hilog.error(DOMAIN, 'testTag', 'Failed to register event: %{public}d %{public}s', e.code, e.message);
- }
-
- const resourceMgr: resourceManager.ResourceManager = context.resourceManager;
- let fileData: Uint8Array = new Uint8Array(0);
- try {
- // example.png表示用户头像,取值为“/resources/rawfile”路径下的文件名
- fileData = await resourceMgr.getRawFileContent('example.png');
- } catch (e) {
- hilog.error(DOMAIN, 'testTag', 'Failed to get raw file: %{public}d %{public}s', e.code, e.message);
- }
- const buffer = fileData.buffer;
- const imageSource: image.ImageSource = image.createImageSource(buffer);
- const pixelMap: image.PixelMap = await imageSource.createPixelMap();
- if (pixelMap) {
- pixelMap.getImageInfo((err, imageInfo) => {
- if (imageInfo) {
- hilog.info(DOMAIN, 'testTag',
- `User profile imageInfo: ${imageInfo.size.width} * ${imageInfo.size.height}.`);
- } else {
- hilog.error(DOMAIN, 'testTag', `Failed to obtain the image information.code is ${err.code}, message is ${err.message}`);
- }
- });
- }
-
- // 构造上报来电的参数。注意,voipCallType.scene为您自定义的场景类型字段,从云侧推送消息时,请注意与端侧取值保持一致
- let call: voipCall.VoipCallAttribute = {
- callId: callId,
- voipCallType: scene?.scene === 'video' ? voipCall.VoipCallType.VOIP_CALL_VIDEO :
- voipCall.VoipCallType.VOIP_CALL_VOICE,
- userName: 'push',
- userProfile: pixelMap,
- abilityName: 'VoIPUIAbility',
- voipCallState: voipCall.VoipCallState.VOIP_CALL_STATE_RINGING
- };
-
- try {
- // 上报来电
- let error = await voipCall.reportIncomingCall(call);
- hilog.info(DOMAIN, 'testTag', `ReportIncomingCall result: ${error}.`);
- } catch (err) {
- let e: BusinessError = err as BusinessError;
- hilog.error(DOMAIN, 'testTag', 'Failed to report incoming call: %{public}d %{public}s', e.code, e.message);
- }
-
- // ...应用播放振动和铃声
- }
-
- public static async processVoipCallEvent(event: voipCall.VoipCallUiEvent) {
- try {
- switch (event) {
- case voipCall.VoipCallUiEvent.VOIP_CALL_EVENT_VOICE_ANSWER:
- case voipCall.VoipCallUiEvent.VOIP_CALL_EVENT_VIDEO_ANSWER:
- // 立即向Call Service Kit上报answered状态
- await voipCall.reportCallStateChange(VoipCallService.callId,
- voipCall.VoipCallState.VOIP_CALL_STATE_ANSWERED);
-
- // ...在应用内完成接听
-
- // 应用内接听后,向Call Service Kit上报active状态
- await voipCall.reportCallStateChange(VoipCallService.callId,
- voipCall.VoipCallState.VOIP_CALL_STATE_ACTIVE);
- break;
- case voipCall.VoipCallUiEvent.VOIP_CALL_EVENT_REJECT:
- case voipCall.VoipCallUiEvent.VOIP_CALL_EVENT_HANGUP:
- // ...应用内完成挂断
-
- // 向Call Service Kit上报通话状态
- await voipCall.reportCallStateChange(VoipCallService.callId,
- voipCall.VoipCallState.VOIP_CALL_STATE_DISCONNECTED);
- break;
- default: {
- break;
- }
- }
- } catch (err) {
- let e: BusinessError = err as BusinessError;
- hilog.error(DOMAIN, 'testTag', 'Failed to report call state change: %{public}d %{public}s', e.code, e.message);
- }
- }
-
- public static close(): void {
- hilog.info(DOMAIN, 'testTag', 'Close VoIP');
-
- VoipCallService.processVoipCallEvent(voipCall.VoipCallUiEvent.VOIP_CALL_EVENT_HANGUP);
- try {
- voipCall.off('voipCallUiEvent');
- } catch (err) {
- let e: BusinessError = err as BusinessError;
- hilog.error(DOMAIN, 'testTag', 'Failed to unregister event: %{public}d %{public}s', e.code, e.message);
- }
- }
- }
需要在项目工程的src/main/resources/rawfile目录下添加example.png,表示来电时的用户头像。
在项目工程的 src/main/ets/pages目录添加:视频接听页面CalleePage.ets,代码示例如下:
- import CallComponent from '../component/CallComponent';
- import { hilog } from '@kit.PerformanceAnalysisKit';
-
- const DOMAIN = 0x0000;
-
- @Entry
- @Component
- struct CalleePage {
- @StorageLink('close') @Watch('close') end: boolean | undefined = undefined;
-
- aboutToAppear() {
- hilog.info(DOMAIN, 'testTag', 'CalleePage aboutToAppear');
-
- this.end = false;
- }
-
- private close() {
- if (this.end) {
- hilog.info(DOMAIN, 'testTag', 'CalleePage close');
-
- this.getUIContext().getRouter().back(); // 此处仅为示例(跳转返回),请根据实际情况设定路由
- }
- }
-
- aboutToDisappear() {
- hilog.info(DOMAIN, 'testTag', 'CalleePage aboutToDisappear');
- }
-
- build() {
- Column() {
- CallComponent({})
- }
- }
- }
CallComponent.ets(在项目工程的src/main/ets/component目录下),代码示例如下:
- import { VoipCallService } from '../service/VoipCallService';
- import { voipCall } from '@kit.CallServiceKit';
-
- @Component
- export default struct CallComponent {
- @StorageLink('close') end: boolean | undefined = undefined;
-
- build() {
- Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween }) {
- Row() {
- }
- .width('100%')
- .justifyContent(FlexAlign.Center)
-
- Row({ space: 30 }) {
-
- Column() {
- Button()
- .width(80)
- .height(80)
- .backgroundColor(Color.Green)
- .onClick(() => {
- VoipCallService.processVoipCallEvent(voipCall.VoipCallUiEvent.VOIP_CALL_EVENT_VIDEO_ANSWER);
- })
-
- Text('Answer').fontColor(Color.White).padding({ top: 5 })
- }
-
- Column() {
- Button()
- .width(80)
- .height(80)
- .backgroundColor(Color.Red)
- .onClick(() => {
- this.end = true;
- VoipCallService.close();
- })
-
- Text('Hang Up').fontColor(Color.White).padding({ top: 5 })
- }
-
- }
- .width('100%')
- .justifyContent(FlexAlign.Center)
- }
- .padding('30 10')
- .backgroundColor(Color.Black)
- }
- }
在项目工程的 src/main/resources/base/profile/main_pages.json添加page目录,示例如下:
- {
- "src": [
- "pages/Index",
- "pages/CalleePage"
- ]
- }
示例代码提供的页面效果仅供开发参考,不代表最终效果。
在项目工程的 src/main/module.json5 文件的abilities模块中配置VoIPUIAbility的 actions 信息。
- "abilities": [
- // ...
- {
- "name": "VoIPUIAbility",
- "srcEntry": "./ets/entryability/VoIPUIAbility.ets",
- "launchType": "singleton",
- "description": "$string:module_desc",
- "startWindowIcon": "$media:startIcon",
- "startWindowBackground": "$color:start_window_background",
- "exported": false,
- "skills": [
- // 保持现有skill对象不变
- // 新增一个独立的skill对象,配置actions参数为action.ohos.push.listener,有且只能有一个ability定义该action
- {
- "actions": [
- "action.ohos.push.listener"
- ]
- }
- ]
- }
- // ...
- ]
应用服务端调用REST API推送消息,消息详情可参见场景化消息API接口功能介绍。
如果您需要呼叫,应用服务器可以调用REST API推送应用内通话消息,请求示例如下:
- // Request URL
- POST "https://push-api.cloud.huawei.com/v3/[projectId]/messages:send"
-
- // Request Header
- Content-Type: application/json
- Authorization: Bearer eyJr*****OiIx---****.eyJh*****iJodHR--***.QRod*****4Gp---****
- push-type: 10
-
- // Request Body
- {
- "pushOptions": {
- "ttl": 30
- },
- "payload": {
- "extraData": "{\"scene\": \"voice\"}"
- },
- "target": {
- "token": ["MAMzLg**********aZW"]
- }
- }
应用内通话消息只能用于音视频通话场景唤醒应用,完成呼叫,不要通过此种类型消息来挂断来电或者和应用通信,应用应该使用自己建立的网络连接和应用通信。相比应用服务器推送Push消息,使用现有的网络连接和应用通信通常会更快,在网络不佳的情况下,推送的Push消息可能无法到达应用。
应用无论是否在前台,自己的网络连接存在时,建议您通过Push推送应用内通话消息,再通过自己的网络连接发送通话消息,保证该呼叫能够到达应用。
如果您需要给被叫方发送未接来电通知,应用服务器可以调用REST API推送通知消息。以通知消息为例,请求示例如下:
- // Request URL
- POST "https://push-api.cloud.huawei.com/v3/[projectId]/messages:send"
-
- // Request Header
- Content-Type: application/json
- Authorization: Bearer eyJr*****OiIx---****.eyJh*****iJodHR--***.QRod*****4Gp---****
- push-type: 0
-
- // Request Body
- {
- "pushOptions": {
- "ttl":86400
- },
- "payload": {
- "notification": {
- "category": "MISS_CALL",
- "title": "通知标题",
- "body": "通知内容",
- "clickAction": {
- "actionType": 0
- },
- "appMessageId": "12345"
- }
- },
- "target": {
- "token": ["MAMzLg**********aZW"]
- }
- }