0900086000300134184.20201216095126.86523331460016843504112994983392.png

在开发《硬要写》app过程中,处理按键、提醒音效和振动时,我没有再使用soundpool+vibrator的组合,改用了音振协同。代码量减少,管理更清晰。协同会同时开始音乐和振动。

我将代码整理出来,供大家使用。

@ohos.multimedia.audioHaptic 

- 使用rawfile文件夹中的mp3和震动马达json;

- 使用单例类实现全局音振;

- 调用简单,快捷;

1. module.json5中增加权限:

"name": "ohos.permission.VIBRATE"

2. 新建单例类AudioHapticService.ets:

import { audioHaptic } from '@kit.AudioKit';
import { BusinessError, emitter } from '@kit.BasicServicesKit';
// 音频文件名称,需要在rawfile目录下
const audioName:string = 'music_end_default.mp3';
// 振动文件名称,需要在rawfile目录下
const vibratorName:string = 'vibrator.json';
// 音频振动服务事件
export const AudioHapticServiceEvt:string = 'endOfStream';
/**
 * 音频和触觉服务
 */
export class AudioHapticService {
  //单例部分
  private static instance: AudioHapticService | undefined = undefined;
  static getInstance(): AudioHapticService {
    if (!AudioHapticService.instance) {
      AudioHapticService.instance = new AudioHapticService();
    }
    return AudioHapticService.instance;
  }

  private constructor() {}
  // 音频ID,用于注销
  private ahId :number | undefined = undefined;
  // 音频振动播放器
  private audioHapticPlayer: audioHaptic.AudioHapticPlayer | undefined = undefined;
  // 播放状态
  private playing: boolean = false
  // 初始化音频和振动
  async initAudioHaptic(context:Context){
    // 获取音频振动管理器
    let ahManager = audioHaptic.getAudioHapticManager();
    try {
      // 注册音频文件
      let audioFile = context.resourceManager.getRawFdSync(audioName);
      let audioFd : audioHaptic.AudioHapticFileDescriptor = {
        fd: audioFile.fd,
        offset: audioFile.offset,
        length: audioFile.length,
      };
      // 注册振动文件
      let hapticFile = context.resourceManager.getRawFdSync(vibratorName);
      let hapticFd: audioHaptic.AudioHapticFileDescriptor = {
        fd: hapticFile.fd,
        offset: hapticFile.offset,
        length: hapticFile.length,
      };
      // 注册音频和振动文件
      this.ahId = await ahManager.registerSourceFromFd(audioFd,hapticFd);
      // 创建音频振动播放器
      this.audioHapticPlayer = await ahManager.createPlayer(this.ahId,{muteAudio: false, muteHaptics: false});
      // 关闭音频和振动文件描述符,避免内存泄漏
      context.resourceManager.closeRawFdSync(audioName);
      context.resourceManager.closeRawFdSync(vibratorName);
      // 注册音频振动播放器事件,当音频播放结束时,触发事件
      this.audioHapticPlayer.on('endOfStream', () => {
        console.info(`Receive the callback of endOfStream.`);
        // 广播音振结束事件
        emitter.emit(AudioHapticServiceEvt);
      });
    } catch (error) {
      throw error as Error;
    }
  }

  /**
   * 开始播放音频和振动
   */
  async startEndNotify(){
    if(this.audioHapticPlayer){
      try {
        await this.audioHapticPlayer.start()
        this.playing = true;
      } catch (err) {
        const be: BusinessError = err as BusinessError
        console.error(`AudioHaptic start failed: ${be.code}, ${be.message}`)
      }
    }
  }

  /**
   * 停止播放音频和振动
   */
  async stopEndNotify(){
    if (!this.audioHapticPlayer) {
      return;
    }
    try {
      await this.audioHapticPlayer.stop()
      this.playing = false;
    } catch (err) {
      const be: BusinessError = err as BusinessError
      console.error(`AudioHaptic stop failed: ${be.code}, ${be.message}`)
    }
  }

  /**
   * 释放音频和振动播放器
   */
  release(){
    if(this.audioHapticPlayer){
      this.audioHapticPlayer.release().then(() => {
        this.playing = false;
        console.info(`Promise returned to indicate that start playing successfully.`);
      }).catch((err: BusinessError) => {
        console.error(`Failed to start playing. ${err}`);
      });
    }
  }
  
  /**
   * 查询是否正在播放
   */
  isPlaying(): boolean {
    return this.playing
  }
}

3. 打开EntryAbility.ets 在onCreate中插入单例初始化代码:

async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
  // 初始化单例
  AudioHapticService.getInstance().initAudioHaptic(this.context);
}

4. 新建vibrator.json,保存至resources -> rawFile文件夹

{
  "MetaData": {
    "Create": "2025-12-11",
    "Description": "音振协同,震动2秒且短促有力",
    "Version": 1.0,
    "ChannelNumber": 1
  },
  "Channels": [
    {
      "Parameters": {
        "Index": 0
      },
      "Pattern": [
        {
          "Event": {
            "Type": "continuous",
            "StartTime": 0,
            "Duration": 2000,
            "Parameters": {
              "Frequency": 30,
              "Intensity": 80,
              "Curve": [
                {
                  "Time": 0,
                  "Frequency": 0,
                  "Intensity": 0
                },
                {
                  "Time": 250,
                  "Frequency": 12,
                  "Intensity": 0.5
                },
                {
                  "Time": 500,
                  "Frequency": -8,
                  "Intensity": 1.0
                },
                {
                  "Time": 750,
                  "Frequency": 0,
                  "Intensity": 0
                },
                {
                  "Time": 1000,
                  "Frequency": 0,
                  "Intensity": 0
                },
                {
                  "Time": 1250,
                  "Frequency": 20,
                  "Intensity": 0.5
                },
                {
                  "Time": 1500,
                  "Frequency": 25,
                  "Intensity": 1.0
                },
                {
                  "Time": 1750,
                  "Frequency": 20,
                  "Intensity": 1
                }
              ]
            }
          }
        }
      ]
    }
  ]
}

5. 在任意代码位置,使用音振协同:

Button('play AudioHaptic').onClick(()=>{
  // 播放音乐和震动
  AudioHapticService.getInstance().startEndNotify();
})

Button('stop AudioHaptic').onClick(()=>{
  // 停止音乐和震动
  AudioHapticService.getInstance().stopEndNotify();
})

注意:

  • 自备音乐.mp3文件,音乐可以和震动频率不一样长,比如音乐10分钟,震动2秒。
  • 我使用的是registerSourceFromFd需要api 20+,低api可以改用registerSource。
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐