← cd ../blog

React Native Audio: De ce am ales expo-audio pentru jocuri

React Native Audio: De ce am ales expo-audio pentru jocuri — imagine de copertă

În dezvoltarea Hangman Hints, m-am lovit rapid de o problemă comună în ecosistemul React Native: latența audio. Când creezi un joc, feedback-ul auditiv trebuie să fie instantaneu. Dacă jucătorul alege o literă corectă, sunetul trebuie să se audă în momentul în care atinge ecranul, nu 50ms mai târziu.

De ce am renunțat la expo-av

expo-av este excelent pentru aplicații media (redare muzică, podcast-uri), dar este „greoi” pentru gaming. Arhitectura sa este bazată pe o instanță de tip Player care trebuie inițializată, ceea ce introduce un overhead semnificativ. În testele mele, pe Android, expo-av a prezentat o latență medie de 70-90ms, cauzată în mare parte de modul în care interacționează cu MediaPlayer la nivel de sistem.

expo-audio, pe de altă parte, folosește AudioTrack direct pe Android, reducând latența la ~40ms. Această diferență de 30ms este sesizabilă pentru un jucător și transformă experiența din „lentoare” în „responsivă”.

Arhitectura AudioPool: Managementul sunetelor

Pentru a evita drop-out-urile audio când jucătorul apasă rapid pe mai multe litere, am implementat un AudioPool. Acesta preîncarcă resursele și gestionează un buffer de maxim 8 sunete simultane.

class AudioPool {
  private pool: Audio.Sound[] = [];
  private maxInstances = 8;

  async preload(source: any) {
    for (let i = 0; i < this.maxInstances; i++) {
      const { sound } = await Audio.Sound.createAsync(source);
      this.pool.push(sound);
    }
  }

  async play(priority: 'high' | 'low') {
    const sound = this.pool.find(s => !s.isPlaying);
    if (sound) {
      await sound.replayAsync();
    }
  }
}

Sincronizarea cu Hapticele

Feedback-ul multisenzorial este cheia succesului în Hangman Hints. Folosesc expo-haptics pentru a oferi o vibrație ușoară la fiecare „click”. Secretul este să declanșezi vibrația cu 5ms înaintea sunetului, compensând latența de percepție a creierului.

const playEffect = async (type: 'correct' | 'wrong') => {
  Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
  await audioPool.play(type === 'correct' ? 'high' : 'low');
};

iOS: AVAudioEngine vs AVAudioPlayer

Pe iOS, expo-audio utilizează AVAudioEngine, care este mult mai performant decât AVAudioPlayer pentru mixare dinamică. Pentru a permite sunetelor de sistem (notificări) să coexiste cu jocul, am configurat sesiunea astfel:

await Audio.setAudioModeAsync({
  playsInSilentModeIOS: true,
  staysActiveInBackground: false,
  interruptionModeIOS: InterruptionModeIOS.MixWithOthers,
});

Hook-ul useGameAudio

Pentru a integra totul în React, am creat un hook care folosește useReducer pentru a ține evidența stării audio și a priorităților:

export const useGameAudio = () => {
  const [state, dispatch] = useReducer(audioReducer, initialState);

  const triggerSound = useCallback((action: string) => {
    // Logică pentru mixare dinamică și priorități
    AudioPool.play(action === 'correct' ? 'high' : 'low');
  }, []);

  return { triggerSound };
};

Concluzii

Pentru un joc care necesită interacțiune rapidă, expo-audio este alegerea corectă. Deși necesită mai mult boilerplate pentru managementul pool-ului de sunete, controlul asupra latenței și stabilitatea pe Android justifică pe deplin efortul. Dacă lucrezi la un proiect similar, nu te baza pe soluțiile „out-of-the-box” dacă acestea nu îți oferă acces la nivel scăzut de buffer audio.