DEV Community

zhonghua
zhonghua

Posted on • Edited on

HarmonyOS Sports Development: Creating Your Personal Sports MetronomeCore HarmonyOS Technology

## Sports Development ## Media Kit (Media Services)

Foreword

Maintaining a steady rhythm during exercise is crucial for enhancing the effectiveness of your workout. Whether you're running, cycling, or engaging in high-intensity interval training (HIIT), a precise metronome can help you better control your exercise rhythm, thereby achieving better training results. This article will delve into how to develop a sports metronome, combining practical HarmonyOS development experience, allowing you to easily master the rhythm during exercise.

Image description

I. Why Choose SoundPool Over AVPlayer

When developing the sports metronome, we opted for SoundPool instead of AVPlayer. This is because SoundPool excels at playing short, sharp提示 sounds, capable of responding quickly and ensuring the timely playback of sound effects, which is vital for a sports metronome that requires precise rhythm control. In contrast, AVPlayer is more suitable for playing long audio files, such as music or videos, and its responsiveness and immediacy in sound effect playback are not as good as SoundPool.

II. Core Logic of the Sports Metronome

  1. Initializing Sound Effects

Before the metronome starts, we need to load the sound effect files and initialize the SoundPool. Below is the core code for initializing sound effects:

async initSound() {
  this.soundPool = await media.createSoundPool(1, {
    usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // Audio stream usage type: music
    rendererFlags: 1
  });

  const context = getContext(this) as common.UIAbilityContext;
  const fd = context.resourceManager.getRawFdSync('medias/metronome2.mp3'); // It is recommended to use a short "tick" sound
  this.soundId = await this.soundPool.load(
    fd.fd,
    fd.offset,
    fd.length
  );
}
Enter fullscreen mode Exit fullscreen mode
  1. Metronome Playback Logic

The core logic of the metronome is to play sound effects at regular intervals based on the set BPM (beats per minute) and trigger visual cues (such as flashing) each time it plays. Below is the core code for the metronome playback logic:

startMetronome() {
  if (this.isPlaying) return;

  this.isPlaying = true;
  const interval = 60000 / this.bpm; // Beat interval

  const playWithFlash = () => {
    this.flashActive = true;
    setTimeout(() => this.flashActive = false, 100); // Set flashActive to false after 100 milliseconds to restore the visual state.

    if (this.soundPool && this.soundId !== -1) {
      this.soundPool.play(this.soundId, {
        loop: 0,
        rate: 1.0,
        leftVolume: 1.0, // Maximum volume to ensure it can be heard during exercise
        rightVolume: 1.0,
        priority: 0
      });
    }

    this.timerId = setTimeout(playWithFlash, interval);
  };

  playWithFlash();
}
Enter fullscreen mode Exit fullscreen mode
  1. Stopping the Metronome

When stopping the metronome, we need to clear the timer and release the SoundPool resources. Below is the core code for stopping the metronome:

stopMetronome() {
  this.isPlaying = false;
  clearTimeout(this.timerId);
}
Enter fullscreen mode Exit fullscreen mode

III. User Interface Design

To allow users to conveniently control the metronome, we need to design a simple and intuitive user interface. Below is the core code for the user interface:

build() {
  Column() {
    // Large visual cue area
    Circle()
      .width(200)
      .height(200)
      .fill(this.flashActive ? Color.Red : Color.White)
      .margin({ bottom: 40 })
      .animation({ duration: 50, curve: Curve.EaseIn })

    // Extra-large BPM display
    Text(`${this.bpm}`)
      .fontSize(60)
      .fontWeight(FontWeight.Bold)
      .margin({ bottom: 30 })

    // Sports-specific BPM range slider
    Slider({
      value: this.bpm,
      min: 40,  // Lowest effective sports rhythm
      max: 200, // High-intensity interval training upper limit
      step: 1
    })
      .width('90%')
      .height(60)
      .onChange((value: number) => {
        this.bpm = value;
        if (this.isPlaying) {
          this.stopMetronome();
          this.startMetronome();
        }
      })

    // Sports-specific control button
    Button(this.isPlaying ? 'Stop Exercise' : 'Start Exercise')
      .width(200)
      .height(80)
      .fontSize(24)
      .backgroundColor(this.isPlaying ? Color.Red : Color.Green)
      .onClick(() => {
        this.isPlaying ? this.stopMetronome() : this.startMetronome();
      })
  }
  .width('100%')
  .height('100%')
  .justifyContent(FlexAlign.Center)
}
Enter fullscreen mode Exit fullscreen mode

Key Points Analysis

  • Visual Cue: Implement visual beat cues using the Circle component and the flashActive state.

  • BPM Display: Display the current BPM value using the Text component, allowing users to intuitively see the beat frequency.

  • BPM Adjustment: Allow users to adjust the BPM value using the Slider component, and automatically update the metronome frequency after adjustment.

  • Control Button: Implement the start and stop functions of the metronome using the Button component.

IV. Optimization and Improvement

  1. Setting the Metronome Frequency Based on Exercise Step Frequency

To better adapt to the user's exercise rhythm, we can dynamically adjust the metronome frequency based on the user's step frequency. I previously wrote an article on how to obtain step frequency and stride length.

private async updateBpmFromStepFrequency() {
  // Get the current step frequency
  const currentStepFrequency = await this.getStepFrequency();
  this.bpm = Math.round(currentStepFrequency * 60); // Convert step frequency to BPM
  if (this.isPlaying) {
    this.stopMetronome();
    this.startMetronome();
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Adding Sound Effect Selection Functionality

To meet the needs of different users, we can add sound effect selection functionality, allowing users to choose different提示 sounds. For example, provide multiple sound effect files for users to choose from.

private soundOptions: string[] = ['metronome1.mp3', 'metronome2.mp3', 'metronome3.mp3'];
private selectedSoundIndex: number = 0;

private async loadSound() {
  const context = getContext(this) as common.UIAbilityContext;
  const fd = context.resourceManager.getRawFdSync(`medias/${this.soundOptions[this.selectedSoundIndex]}`);
  this.soundId = await this.soundPool.load(
    fd.fd,
    fd.offset,
    fd.length
  );
}
Enter fullscreen mode Exit fullscreen mode
  1. Optimizing Visual Cue Effects

To enhance the user experience, we can further optimize the visual cue effects, such as adding animation effects or changing the cue color.

Circle()
  .width(200)
  .height(200)
  .fill(this.flashActive ? Color.Red : Color.White)
  .margin({ bottom: 40 })
  .animation({ duration: 100, curve: Curve.EaseInOut }) // Add animation effects
Enter fullscreen mode Exit fullscreen mode

V. Summary

Using HarmonyOS's SoundPool and related APIs, we can easily develop a powerful sports metronome.

Top comments (0)