Î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.

