GWT backend for libGDX enabling Java game development for web browsers through JavaScript compilation
—
The GWT backend provides advanced audio capabilities through Web Audio API integration, offering superior audio performance and features compared to basic HTML5 Audio. This system provides low-latency audio, advanced audio processing, and better mobile device support.
public class WebAudioAPIManager implements LifecycleListener {
// Web Audio API availability and setup
public static boolean isSupported();
public static WebAudioAPIManager getInstance();
// Audio context management
public void createContext();
public void resumeContext();
public void suspendContext();
public boolean isContextRunning();
// Lifecycle management
public void pause();
public void resume();
public void dispose();
// Audio graph management
public AudioControlGraph createAudioGraph();
public void releaseAudioGraph(AudioControlGraph graph);
// Volume and settings
public void setMasterVolume(float volume);
public float getMasterVolume();
}public class WebAudioAPISound implements Sound {
// Sound playback control
public long play();
public long play(float volume);
public long play(float volume, float pitch, float pan);
// Instance control
public void stop();
public void stop(long soundId);
public void pause();
public void pause(long soundId);
public void resume();
public void resume(long soundId);
// Sound properties
public void setVolume(long soundId, float volume);
public void setPitch(long soundId, float pitch);
public void setPan(long soundId, float pan, float volume);
// Looping control
public void setLooping(long soundId, boolean looping);
// Resource management
public void dispose();
}public class WebAudioAPIMusic implements Music {
// Playback control
public void play();
public void pause();
public void stop();
// State queries
public boolean isPlaying();
public boolean isLooping();
// Music properties
public void setLooping(boolean isLooping);
public void setVolume(float volume);
public float getVolume();
public void setPan(float pan, float volume);
// Position control
public void setPosition(float position);
public float getPosition();
// Event handling
public void setOnCompletionListener(OnCompletionListener listener);
// Resource management
public void dispose();
}public class AudioControlGraph {
// Audio node management
public void connect(AudioNode source, AudioNode destination);
public void disconnect(AudioNode source);
public void disconnect(AudioNode source, AudioNode destination);
// Volume and gain control
public void setVolume(float volume);
public float getVolume();
public void setMasterGain(float gain);
// Audio effects
public void applyLowPassFilter(float frequency);
public void applyHighPassFilter(float frequency);
public void applyReverb(float roomSize, float damping);
public void applyDelay(float delayTime, float feedback);
// Spatial audio
public void setListenerPosition(float x, float y, float z);
public void setListenerOrientation(float forwardX, float forwardY, float forwardZ,
float upX, float upY, float upZ);
public void setSourcePosition(float x, float y, float z);
// Audio analysis
public float[] getFrequencyData();
public float[] getTimeData();
public float getAverageVolume();
// Resource management
public void dispose();
}public class AudioControlGraphPool extends Pool<AudioControlGraph> {
// Object pooling for audio graphs
public AudioControlGraphPool(int initialCapacity, int max);
// Pool management
protected AudioControlGraph newObject();
protected void reset(AudioControlGraph object);
// Pool operations
public AudioControlGraph obtain();
public void free(AudioControlGraph object);
public void clear();
}public class WebAudioGame implements ApplicationListener {
private WebAudioAPIManager audioManager;
private WebAudioAPISound jumpSound;
private WebAudioAPIMusic backgroundMusic;
@Override
public void create() {
// Check Web Audio API support
if (WebAudioAPIManager.isSupported()) {
audioManager = WebAudioAPIManager.getInstance();
audioManager.createContext();
// Load audio assets
jumpSound = (WebAudioAPISound) Gdx.audio.newSound(Gdx.files.internal("audio/jump.ogg"));
backgroundMusic = (WebAudioAPIMusic) Gdx.audio.newMusic(Gdx.files.internal("music/background.ogg"));
// Configure background music
backgroundMusic.setLooping(true);
backgroundMusic.setVolume(0.7f);
System.out.println("Web Audio API initialized successfully");
} else {
System.out.println("Web Audio API not supported, using fallback");
// Fall back to regular HTML5 Audio
}
}
@Override
public void render() {
// Resume audio context on user interaction (required by browsers)
if (Gdx.input.justTouched() && audioManager != null) {
audioManager.resumeContext();
if (!backgroundMusic.isPlaying()) {
backgroundMusic.play();
}
}
}
public void playJumpSound() {
if (jumpSound != null) {
// Play with random pitch variation
float pitch = 0.8f + (float)Math.random() * 0.4f; // 0.8 to 1.2
jumpSound.play(1.0f, pitch, 0.0f);
}
}
@Override
public void dispose() {
if (jumpSound != null) jumpSound.dispose();
if (backgroundMusic != null) backgroundMusic.dispose();
if (audioManager != null) audioManager.dispose();
}
}public class AudioEffectsDemo implements ApplicationListener {
private AudioControlGraph audioGraph;
private WebAudioAPISound laserSound;
private WebAudioAPIMusic ambienceMusic;
@Override
public void create() {
if (WebAudioAPIManager.isSupported()) {
WebAudioAPIManager manager = WebAudioAPIManager.getInstance();
manager.createContext();
// Create audio processing graph
audioGraph = manager.createAudioGraph();
// Load sounds
laserSound = (WebAudioAPISound) Gdx.audio.newSound(Gdx.files.internal("sfx/laser.ogg"));
ambienceMusic = (WebAudioAPIMusic) Gdx.audio.newMusic(Gdx.files.internal("music/ambience.ogg"));
// Apply audio effects
setupAudioEffects();
}
}
private void setupAudioEffects() {
// Apply low-pass filter for underwater effect
audioGraph.applyLowPassFilter(800.0f);
// Add reverb for spacious environment
audioGraph.applyReverb(0.8f, 0.2f);
// Set up spatial audio
audioGraph.setListenerPosition(0, 0, 0);
audioGraph.setListenerOrientation(0, 0, -1, 0, 1, 0); // Looking forward, up is up
}
public void playLaserAt(float x, float y) {
if (laserSound != null) {
// Calculate pan based on position
float screenWidth = Gdx.graphics.getWidth();
float pan = (x - screenWidth / 2) / (screenWidth / 2); // -1 to 1
pan = Math.max(-1, Math.min(1, pan)); // Clamp to valid range
// Play with spatial positioning
audioGraph.setSourcePosition(x, y, 0);
laserSound.play(1.0f, 1.0f, pan);
}
}
public void startUnderwaterMode() {
// Apply underwater audio effect
audioGraph.applyLowPassFilter(400.0f);
audioGraph.setMasterGain(0.6f);
// Reduce music volume
if (ambienceMusic != null) {
ambienceMusic.setVolume(0.3f);
}
}
public void endUnderwaterMode() {
// Remove underwater effect
audioGraph.applyLowPassFilter(20000.0f); // Remove filter
audioGraph.setMasterGain(1.0f);
// Restore music volume
if (ambienceMusic != null) {
ambienceMusic.setVolume(0.8f);
}
}
@Override
public void dispose() {
if (audioGraph != null) audioGraph.dispose();
if (laserSound != null) laserSound.dispose();
if (ambienceMusic != null) ambienceMusic.dispose();
}
}public class AudioVisualizerDemo implements ApplicationListener {
private AudioControlGraph audioGraph;
private WebAudioAPIMusic music;
private float[] frequencyData;
private float[] timeData;
private ShapeRenderer shapeRenderer;
@Override
public void create() {
if (WebAudioAPIManager.isSupported()) {
WebAudioAPIManager manager = WebAudioAPIManager.getInstance();
manager.createContext();
audioGraph = manager.createAudioGraph();
music = (WebAudioAPIMusic) Gdx.audio.newMusic(Gdx.files.internal("music/electronic.ogg"));
music.setLooping(true);
// Initialize visualization data arrays
frequencyData = new float[128];
timeData = new float[128];
shapeRenderer = new ShapeRenderer();
}
}
@Override
public void render() {
// Update audio analysis data
if (audioGraph != null && music.isPlaying()) {
frequencyData = audioGraph.getFrequencyData();
timeData = audioGraph.getTimeData();
}
// Clear screen
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// Draw frequency spectrum
drawFrequencySpectrum();
// Draw waveform
drawWaveform();
// Start music on user input
if (Gdx.input.justTouched() && music != null && !music.isPlaying()) {
music.play();
}
}
private void drawFrequencySpectrum() {
if (frequencyData == null) return;
shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
shapeRenderer.setColor(0, 1, 0, 0.8f); // Green bars
float barWidth = Gdx.graphics.getWidth() / (float)frequencyData.length;
for (int i = 0; i < frequencyData.length; i++) {
float height = frequencyData[i] * Gdx.graphics.getHeight() * 0.4f;
float x = i * barWidth;
float y = Gdx.graphics.getHeight() * 0.6f;
shapeRenderer.rect(x, y, barWidth - 1, height);
}
shapeRenderer.end();
}
private void drawWaveform() {
if (timeData == null) return;
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
shapeRenderer.setColor(1, 1, 0, 1); // Yellow waveform
float centerY = Gdx.graphics.getHeight() * 0.3f;
float amplitude = 100;
float stepX = Gdx.graphics.getWidth() / (float)(timeData.length - 1);
for (int i = 0; i < timeData.length - 1; i++) {
float x1 = i * stepX;
float y1 = centerY + timeData[i] * amplitude;
float x2 = (i + 1) * stepX;
float y2 = centerY + timeData[i + 1] * amplitude;
shapeRenderer.line(x1, y1, x2, y2);
}
shapeRenderer.end();
}
@Override
public void dispose() {
if (audioGraph != null) audioGraph.dispose();
if (music != null) music.dispose();
if (shapeRenderer != null) shapeRenderer.dispose();
}
}public class DynamicAudioLoader {
private WebAudioAPIManager audioManager;
private Map<String, WebAudioAPISound> loadedSounds;
private Map<String, WebAudioAPIMusic> loadedMusic;
public DynamicAudioLoader() {
loadedSounds = new HashMap<>();
loadedMusic = new HashMap<>();
if (WebAudioAPIManager.isSupported()) {
audioManager = WebAudioAPIManager.getInstance();
audioManager.createContext();
}
}
public void loadSound(String name, String path, Runnable onComplete) {
if (loadedSounds.containsKey(name)) {
if (onComplete != null) onComplete.run();
return;
}
// Load sound asynchronously
Timer.schedule(new Timer.Task() {
@Override
public void run() {
try {
WebAudioAPISound sound = (WebAudioAPISound) Gdx.audio.newSound(Gdx.files.internal(path));
loadedSounds.put(name, sound);
System.out.println("Loaded sound: " + name);
if (onComplete != null) onComplete.run();
} catch (Exception e) {
System.err.println("Failed to load sound: " + name + " - " + e.getMessage());
}
}
}, 0.1f); // Small delay to simulate async loading
}
public void loadMusic(String name, String path, Runnable onComplete) {
if (loadedMusic.containsKey(name)) {
if (onComplete != null) onComplete.run();
return;
}
Timer.schedule(new Timer.Task() {
@Override
public void run() {
try {
WebAudioAPIMusic music = (WebAudioAPIMusic) Gdx.audio.newMusic(Gdx.files.internal(path));
loadedMusic.put(name, music);
System.out.println("Loaded music: " + name);
if (onComplete != null) onComplete.run();
} catch (Exception e) {
System.err.println("Failed to load music: " + name + " - " + e.getMessage());
}
}
}, 0.1f);
}
public WebAudioAPISound getSound(String name) {
return loadedSounds.get(name);
}
public WebAudioAPIMusic getMusic(String name) {
return loadedMusic.get(name);
}
public void playSound(String name) {
WebAudioAPISound sound = getSound(name);
if (sound != null) {
sound.play();
} else {
System.err.println("Sound not loaded: " + name);
}
}
public void playSound(String name, float volume, float pitch) {
WebAudioAPISound sound = getSound(name);
if (sound != null) {
sound.play(volume, pitch, 0);
} else {
System.err.println("Sound not loaded: " + name);
}
}
public void playMusic(String name) {
WebAudioAPIMusic music = getMusic(name);
if (music != null) {
music.play();
} else {
System.err.println("Music not loaded: " + name);
}
}
public void unloadSound(String name) {
WebAudioAPISound sound = loadedSounds.remove(name);
if (sound != null) {
sound.dispose();
System.out.println("Unloaded sound: " + name);
}
}
public void unloadMusic(String name) {
WebAudioAPIMusic music = loadedMusic.remove(name);
if (music != null) {
music.dispose();
System.out.println("Unloaded music: " + name);
}
}
public void dispose() {
// Dispose all loaded audio
for (WebAudioAPISound sound : loadedSounds.values()) {
sound.dispose();
}
loadedSounds.clear();
for (WebAudioAPIMusic music : loadedMusic.values()) {
music.dispose();
}
loadedMusic.clear();
if (audioManager != null) {
audioManager.dispose();
}
}
}// Web Audio API provides several performance benefits over HTML5 Audio:
public class AudioPerformanceComparison {
public void demonstrateAdvantages() {
if (WebAudioAPIManager.isSupported()) {
// ✓ Lower latency audio playback
// ✓ Multiple simultaneous audio instances
// ✓ Precise timing control
// ✓ Audio processing and effects
// ✓ Better mobile device support
// ✓ No browser audio instance limits
System.out.println("Using Web Audio API for optimal performance");
} else {
// Fall back to HTML5 Audio
// ✗ Higher latency
// ✗ Limited simultaneous sounds
// ✗ No advanced audio processing
// ✗ Browser-dependent limitations
System.out.println("Falling back to HTML5 Audio");
}
}
public void testAudioLatency() {
WebAudioAPISound testSound = (WebAudioAPISound) Gdx.audio.newSound(Gdx.files.internal("test_click.wav"));
long startTime = System.currentTimeMillis();
testSound.play();
long endTime = System.currentTimeMillis();
System.out.println("Audio playback latency: " + (endTime - startTime) + "ms");
// Web Audio API typically shows <10ms latency
// HTML5 Audio often shows 50-200ms latency
}
}// Web Audio API support across browsers
public class BrowserCompatibility {
public static void checkAudioSupport() {
if (WebAudioAPIManager.isSupported()) {
System.out.println("Web Audio API supported - using advanced audio features");
// Available in:
// - Chrome 14+
// - Firefox 25+
// - Safari 6+
// - Edge 12+
// - Mobile Chrome/Safari
} else {
System.out.println("Web Audio API not supported - using HTML5 Audio fallback");
// Fallback for:
// - Internet Explorer
// - Very old browser versions
// - Browsers with Web Audio disabled
}
}
public static void handleAudioContext() {
// Modern browsers require user interaction before audio context can start
WebAudioAPIManager manager = WebAudioAPIManager.getInstance();
if (Gdx.input.justTouched()) {
// Resume context after user interaction
manager.resumeContext();
System.out.println("Audio context resumed");
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-badlogicgames-gdx--gdx-backend-gwt