HarmonyOS Next Best Practices for Ringing and Vibration in Call Pages
1. Background Introduction
In developing audio-video calling modules, call ringing is typically implemented on both outgoing and incoming call pages, similar to WeChat's video call interface. The called party often experiences vibration alongside ringing. On Android, MediaPlayer
loops MP3 ringtones from the raw
directory, and Vibrator
achieves vibration effects. This article introduces how to implement ringing and vibration in HarmonyOS Next.
2. Ringing Implementation
For package size considerations, we usually place a short ringtone in the app and repeat it for continuous ringing. Call pages often limit the maximum duration to one minute, so the ringtone can play for up to one minute.
We place the ringtone file in rawfile
and read it via resourceManager
. HarmonyOS provides two ways to play audio:
- SoundPool: Enables low-latency short sound playback, suitable for急促 brief sound effects (e.g., camera shutter, system notifications). It supports audio resources under 1MB; files over 1MB are truncated to 1MB.
- AVPlayer: Achieves end-to-end playback of original media resources.
Since SoundPool
's load
method does not support rawfile
directory resources, we focus on AVPlayer
for audio playback.
AVPlayer Playback Workflow
The full process includes: creating an AVPlayer
, setting playback resources, configuring parameters (volume/speed/focus mode), playback control (play/pause/seek/stop), resetting, and destroying resources.
AVPlayer
is similar to Android's MediaPlayer
, maintaining a state machine. You can actively get the current state via the state
property or listen for changes using on('stateChange')
. Operating on an AVPlayer
in an error state may throw exceptions or cause undefined behavior.
Ringing Tool: RingAVPlayer
- Create the player:
let avPlayer: media.AVPlayer = await media.createAVPlayer();
- Set state change callbacks:
avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {});
- Get the local audio resource FD:
let fileDescriptor = await context.resourceManager.getRawFd('ring.mp3');
- Assign
fdSrc
to trigger theinitialized
state:avPlayer.fdSrc = fileDescriptor;
- In the state callback, call
avPlayer.prepare()
after receivinginitialized
to trigger resource loading. - In the state callback, call
avPlayer.play()
after receivingprepared
; setavPlayer.loop = true
before playback for looping. - Call
stop
to end playback.
import { media } from '@kit.MediaKit';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { audio } from '@kit.AudioKit';
import { Logg } from '../../../../../../../Index';
const TAG = 'RingAVPlayer';
export class RingAVPlayer {
private mAVPlayer: media.AVPlayer|undefined = undefined;
private count: number = 0;
// Register AVPlayer callback functions
setAVPlayerCallback(avPlayer: media.AVPlayer) {
// Seek operation result callback
avPlayer.on('seekDone', (seekDoneTime: number) => {
Logg.i(TAG, `AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
})
// Error callback: call reset when an error occurs
avPlayer.on('error', (err: BusinessError) => {
Logg.e(TAG, `Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
avPlayer.reset(); // Reset resources, trigger idle state
})
// State machine change callback
avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
switch (state) {
case 'idle':
console.info('AVPlayer state idle called.');
avPlayer.release();
break;
case 'initialized':
Logg.i(TAG, 'AVPlayer state initialized called.');
avPlayer.audioRendererInfo = {
usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
rendererFlags: 0
}
avPlayer.prepare();
break;
case 'prepared':
Logg.i(TAG, 'AVPlayer state prepared called.');
avPlayer.loop = true;
avPlayer.play();
break;
case 'playing':
Logg.i(TAG, 'AVPlayer state playing called.');
break;
case 'paused':
Logg.i(TAG, 'AVPlayer state paused called.');
break;
case 'completed':
Logg.i(TAG, 'AVPlayer state completed called.');
break;
case 'stopped':
Logg.i(TAG, 'AVPlayer state stopped called.');
break;
case 'released':
Logg.i(TAG, 'AVPlayer state released called.');
break;
default:
Logg.i(TAG, 'AVPlayer state unknown called.');
break;
}
})
}
// Demo: Use resource manager to get media files in HAP and play via fdSrc
async startAVPlayer() {
this.mAVPlayer = await media.createAVPlayer();
this.setAVPlayerCallback(this.mAVPlayer);
let context = getContext(this) as common.UIAbilityContext;
let fileDescriptor = await context.resourceManager.getRawFd('call_music.mp3');
let avFileDescriptor: media.AVFileDescriptor =
{ fd: fileDescriptor.fd, offset: fileDescriptor.offset, length: fileDescriptor.length };
this.mAVPlayer.fdSrc = avFileDescriptor;
}
stopAVPlayer() {
if (this.mAVPlayer) {
this.mAVPlayer.stop();
this.mAVPlayer.release();
}
}
}
3. Vibration Implementation
On Android, Vibrator
is used as follows:
protected long[] pattern = { 1000, 1000, 1000, 1000 };
mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
mVibrator.vibrate(pattern, 0);
The array elements represent vibration durations (ms). The first element is the wait time before vibration, and subsequent elements alternate between vibration and pause. repeat
determines repetition: -1
for no repeat, 0
for continuous vibration. { 1000, 1000, 1000, 1000 }
means wait 1s, vibrate 1s, wait 1s, vibrate 1s, repeating. Call cancel
to stop.
HarmonyOS Next provides corresponding vibration capabilities via the Vibrator
module, supporting:
- Key presses with different intensities and durations
- Alarms and calls with single or periodic vibrations of varying intensities and durations
Three Vibration Types
Name | Description |
---|---|
Fixed-duration vibration | Specify a duration; the motor vibrates at default intensity and frequency. See VibrateTime . |
Preset vibration | System-defined vibration effects for fixed scenarios (e.g., haptic.clock.timer for timer adjustments). See VibratePreset . |
Custom vibration | Allows users to design vibration effects via configuration files. See VibrateFromFile . |
Example Code
Fixed-Duration Vibration
import { vibrator } from '@kit.SensorServiceKit';
import { BusinessError } from '@kit.BasicServicesKit';
try {
vibrator.startVibration({
type: 'time',
duration: 1000,
}, {
id: 0,
usage: 'alarm'
}, (error: BusinessError) => {
if (error) {
console.error(`Failed to start vibration. Code: ${error.code}, message: ${error.message}`);
return;
}
console.info('Succeed in starting vibration');
});
} catch (err) {
let e: BusinessError = err as BusinessError;
console.error(`An unexpected error occurred. Code: ${e.code}, message: ${e.message}`);
}
// Stop fixed-duration vibration
import { vibrator } from '@kit.SensorServiceKit';
import { BusinessError } from '@kit.BasicServicesKit';
try {
vibrator.stopVibration(vibrator.VibratorStopMode.VIBRATOR_STOP_MODE_TIME, (error: BusinessError) => {
if (error) {
console.error(`Failed to stop vibration. Code: ${error.code}, message: ${error.message}`);
return;
}
console.info('Succeed in stopping vibration');
})
} catch (err) {
let e: BusinessError = err as BusinessError;
console.error(`An unexpected error occurred. Code: ${e.code}, message: ${e.message}`);
}
Preset Vibration
import { vibrator } from '@kit.SensorServiceKit';
import { BusinessError } from '@kit.BasicServicesKit';
try {
vibrator.isSupportEffect('haptic.effect.soft', (err: BusinessError, state: boolean) => {
if (err) {
console.error(`Failed to query effect. Code: ${err.code}, message: ${err.message}`);
return;
}
console.info('Succeed in querying effect');
if (state) {
vibrator.startVibration({
type: 'preset',
effectId: 'haptic.effect.soft',
count: 1,
intensity: 50,
}, {
usage: 'unknown'
}, (error: BusinessError) => {
if (error) {
console.error(`Failed to start vibration. Code: ${error.code}, message: ${error.message}`);
} else {
console.info('Succeed in starting vibration');
}
});
}
})
} catch (error) {
let e: BusinessError = error as BusinessError;
console.error(`An unexpected error occurred. Code: ${e.code}, message: ${e.message}`);
}
// Stop preset vibration
import { vibrator } from '@kit.SensorServiceKit';
import { BusinessError } from '@kit.BasicServicesKit';
try {
vibrator.stopVibration(vibrator.VibratorStopMode.VIBRATOR_STOP_MODE_PRESET, (error: BusinessError) => {
if (error) {
console.error(`Failed to stop vibration. Code: ${error.code}, message: ${error.message}`);
return;
}
console.info('Succeed in stopping vibration');
})
} catch (err) {
let e: BusinessError = err as BusinessError;
console.error(`An unexpected error occurred. Code: ${e.code}, message: ${e.message}`);
}
Custom Vibration from File
import { vibrator } from '@kit.SensorServiceKit';
import { resourceManager } from '@kit.LocalizationKit';
import { BusinessError } from '@kit.BasicServicesKit';
const fileName: string = 'xxx.json';
let rawFd: resourceManager.RawFileDescriptor = getContext().resourceManager.getRawFdSync(fileName);
try {
vibrator.startVibration({
type: "file",
hapticFd: { fd: rawFd.fd, offset: rawFd.offset, length: rawFd.length }
}, {
id: 0,
usage: 'alarm'
}, (error: BusinessError) => {
if (error) {
console.error(`Failed to start vibration. Code: ${error.code}, message: ${error.message}`);
return;
}
console.info('Succeed in starting vibration');
});
} catch (err) {
let e: BusinessError = err as BusinessError;
console.error(`An unexpected error occurred. Code: ${e.code}, message: ${e.message}`);
}
getContext().resourceManager.closeRawFdSync(fileName);
// Stop all vibrations
import { vibrator } from '@kit.SensorServiceKit';
import { BusinessError } from '@kit.BasicServicesKit';
try {
vibrator.stopVibration((error: BusinessError) => {
if (error) {
console.error(`Failed to stop vibration. Code: ${error.code}, message: ${error.message}`);
return;
}
console.info('Succeed in stopping vibration');
})
} catch (error) {
let e: BusinessError = error as BusinessError;
console.error(`An unexpected error occurred. Code: ${e.code}, message: ${e.message}`);
}
4. Reference Documents
- @ohos.vibrator (Vibration)
- Vibration Development Guide (ArkTS)
- SoundPool (Audio Pool) API
- @ohos.multimedia.media (Media Service)
5. Summary
This article introduces implementing custom ringing and vibration effects on call pages in HarmonyOS Next audio-video calling scenarios, focusing on AVPlayer
and vibrator
APIs.
Top comments (0)