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 fdSrcto trigger theinitializedstate:avPlayer.fdSrc = fileDescriptor;
- In the state callback, call avPlayer.prepare()after receivinginitializedto trigger resource loading.
- In the state callback, call avPlayer.play()after receivingprepared; setavPlayer.loop = truebefore playback for looping.
- Call stopto 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.timerfor timer adjustments). SeeVibratePreset. | 
| 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)