DEV Community

HarmonyOS
HarmonyOS

Posted on

Synchronized Real-time Text Highlighting with Audio Playback Timeline

Read the original article:Synchronized Real-time Text Highlighting with Audio Playback Timeline

Requirement Description

This guide shows how to synchronize visual text highlighting with audio playback timeline using sample-accurate timing in HarmonyOS applications. The implementation provides real-time word-by-word highlighting that precisely follows audio playback progress, creating a synchronized visual-audio experience for users.

Background Knowledge

Real-time text highlighting synchronized with audio playback requires precise timing coordination between audio sample processing and UI animations. The core concepts include;

Sample-Accurate Timing: Using audio sample counts to determine exact playback position.

Progress Calculation: Converting audio samples to text progression using mathematical interpolation.

Word Tokenization: Splitting text content into individual highlightable units.

State Management: Tracking current highlighting position and animation states.

The synchronization works by;

  • Tracking the number of audio samples played during AudioRenderer callbacks
  • Calculating text progression percentage based on total audio duration
  • Mapping text progression to word indices for highlighting

Common use cases for this feature;

  • Karaoke applications with lyric highlighting
  • Educational apps with reading assistance
  • Accessibility features for text-to-speech
  • Media players with subtitle synchronization
  • Language learning applications with pronunciation guides.

Example usage on a weather app;

TextHighlightingScaled.gif

Implementation Steps

Setup Audio Progress Tracking

  • Initialize sample counters in your audio playback component
  • Configure AudioRenderer callback to track played samples
  • Calculate total expected samples from audio duration and sample rate

Implement Text Tokenization

  • Split your text content into individual words or phrases
  • Store word boundaries and create indexable word arrays

Create Progress Calculation Logic

  • Convert played samples to time-based progress percentage
  • Map progress percentage to word index positions
  • Implement smooth interpolation for natural highlighting transitions

Synchronize Audio and Visual Updates

  • Update highlighting state in audio callback functions
  • Ensure thread-safe communication between audio and UI threads
  • Handle edge cases like audio seeking or playback interruptions

Code Snippet / Configuration

Component Setup

Initialize component with text tokenization and progress tracking;

@Component
export struct SynchronizedTextHighlight {
  @State private words: string[] = [];
  @State private currentWordIndex: number = 0;

  private playedSamples: number = 0;
  private totalSamples: number = 0;

  aboutToAppear(): void {
    const fullText = "Hope you had a good day! Overcast skies today. 22 degrees and comfortable.";
    this.words = fullText.split(' ').filter(word => word.length > 0);
    this.setupAudioProgressTracking();
  }
  //Rest of the code
}
Enter fullscreen mode Exit fullscreen mode

Progress Calculation

Convert audio progress to word highlighting position;

private calculateProgress(): void {
    const audioProgress = Math.min(this.playedSamples / this.totalSamples, 1.0);
    const targetWordIndex = Math.floor(audioProgress * this.words.length);

    if(targetWordIndex !== this.currentWordIndex) {
      animateTo({duration: 150, curve: Curve.EaseOut}, () => {
        this.currentWordIndex = Math.min(targetWordIndex, this.words.length - 1);
      });
    }
  }
Enter fullscreen mode Exit fullscreen mode

Audio Callback Integration

Track audio playback progress and update highlighting;

private setupAudioProgressTracking(): void {
    this.audioRenderer?.on('writeData', (buffer: ArrayBuffer) => {
      const numSamples = buffer.byteLength / 2;
      this.playedSamples += numSamples;
      this.calculateProgress();

      //Process your audio data here
      const int16Samples = new Int16Array(buffer);
      // ... audio processing code ...
    });
  }
Enter fullscreen mode Exit fullscreen mode

Text Highlightling Component

Display text with synchronized word highlighting and progress bar;

build() {
    Column() {
      Text() {
        ForEach(this.words, (word: string, index: number) => {
          Span(`${word} `) //Notice the extra space here
            .fontColor(this.getWordColor(index))
            .animation({duration: 200, curve: Curve.EaseInOut})
        },(word: string, index: number) => `${word}_${index}`)
      }
      .padding(16)

      Progress({
        value: (this.playedSamples / this.totalSamples) * 100,
        total: 100
      })
        .width('90%')
        .height(4)
    }
  }

private getWordColor(index: number): ResourceColor {
    if(index < this.currentWordIndex) {
      return '#ff1e90ff';  //Highlighted already, blue
    } else if(index === this.currentWordIndex) {
      return '#ffffd700';  //Currently highlighting, gold
    } else {
      return '#ff808080';  //Not yet highlighted, gray
    }
  }
Enter fullscreen mode Exit fullscreen mode

Limitations or Considerations

HarmonyOS API Version: Requires HarmonyOS API 11 or higher for AudioRenderer

Text Length: Very long texts may cause performance issues with excessive Span elements

Written by Can Kankaya

Top comments (0)