Skip to content

Commit e4d7573

Browse files
authored
Fixed some issues and improved performance of AndroidAudioService (#348)
* Fixed Android volume buttons not working * Moved native calls to a separate thread to prevent slowing games (same as iOS) * Added pendingPlay flag to alleviate the Audio flow in extreme situations (same as iOS) * Increased the global SoundPool maxStreams setting from 5 to 20 * Added skipPause & skipStop flags to prevent an Android issue (pausing or stopping a music before playing it makes it unplayable) * Updated Gluon copyright and implemented volume keys management * Updated Gluon copyright * Removed volume keys management code (now directly managed by Gluon Substrate)
1 parent addcdc9 commit e4d7573

File tree

3 files changed

+44
-20
lines changed

3 files changed

+44
-20
lines changed

modules/audio/src/main/java/com/gluonhq/attach/audio/impl/AndroidAudioService.java

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020 Gluon
2+
* Copyright (c) 2020, 2023, Gluon
33
*
44
* This program is free software: you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License as published by
@@ -37,6 +37,8 @@
3737
import java.nio.file.Files;
3838
import java.nio.file.Path;
3939
import java.util.Optional;
40+
import java.util.concurrent.Executors;
41+
import java.util.concurrent.ScheduledExecutorService;
4042
import java.util.function.Function;
4143
import java.util.logging.Logger;
4244

@@ -121,10 +123,17 @@ private String copyToPrivateStorageIfNeeded(URL url) throws Exception {
121123
return file.toAbsolutePath().toString();
122124
}
123125

126+
// All native calls are executed in a background thread to not hold the caller thread (which is probably the UI
127+
// thread). This prevents games using many sounds to be slowed down by audio calls.
128+
private final static ScheduledExecutorService nativeExecutor = Executors.newSingleThreadScheduledExecutor();
129+
124130
private static class AndroidAudio implements Audio {
125131

126-
private boolean isDisposed = false;
127132
private final int id;
133+
private boolean isDisposed = false;
134+
private boolean pendingPlay = false; // flag used by play() to alleviate the Audio flow in extreme situations
135+
private boolean skipPause = true; // flag used to skip unnecessary calls to pause(), because calling pause() before play() prevents the music to be played (Android issue)
136+
private boolean skipStop = true; // flag used to skip unnecessary calls to stop(), because calling stop() before play() prevents the music to be played (Android issue)
128137

129138
AndroidAudio(int id) {
130139
this.id = id;
@@ -135,39 +144,53 @@ public void setLooping(boolean looping) {
135144
if (isDisposed)
136145
return;
137146

138-
AndroidAudioService.setLooping(id, looping);
147+
nativeExecutor.execute(() -> AndroidAudioService.setLooping(id, looping));
139148
}
140149

141150
@Override
142151
public void setVolume(double volume) {
143152
if (isDisposed)
144153
return;
145154

146-
AndroidAudioService.setVolume(id, volume);
155+
nativeExecutor.execute(() -> AndroidAudioService.setVolume(id, volume));
147156
}
148157

149158
@Override
150159
public void play() {
151-
if (isDisposed)
160+
// We set pendingPlay to true before the native play() call, and then back to false after that call.
161+
// In extreme situations (like observed with SpaceFX with many simultaneous explosions sounds), it can
162+
// happen that the game calls play() again even before the previous call has been executed. In that case,
163+
// we just drop that second call, as it doesn't make sense to start the same sound twice so closely. And
164+
// most important, this improves the performance (the game was noticeably slower when the native iOS sound
165+
// system was not alleviate in this way).
166+
167+
if (isDisposed || pendingPlay)
152168
return;
153169

154-
AndroidAudioService.play(id);
170+
pendingPlay = true;
171+
nativeExecutor.execute(() -> {
172+
AndroidAudioService.play(id);
173+
pendingPlay = false;
174+
});
175+
skipPause = skipStop = false;
155176
}
156177

157178
@Override
158179
public void pause() {
159-
if (isDisposed)
180+
if (isDisposed || skipPause)
160181
return;
161182

162-
AndroidAudioService.pause(id);
183+
nativeExecutor.execute(() -> AndroidAudioService.pause(id));
184+
skipPause = true;
163185
}
164186

165187
@Override
166188
public void stop() {
167-
if (isDisposed)
189+
if (isDisposed || skipStop)
168190
return;
169191

170-
AndroidAudioService.stop(id);
192+
nativeExecutor.execute(() -> AndroidAudioService.stop(id));
193+
skipPause = skipStop = true;
171194
}
172195

173196
@Override
@@ -176,7 +199,7 @@ public void dispose() {
176199
return;
177200

178201
isDisposed = true;
179-
AndroidAudioService.dispose(id);
202+
nativeExecutor.execute(() -> AndroidAudioService.dispose(id));
180203
}
181204

182205
@Override
@@ -185,7 +208,6 @@ public boolean isDisposed() {
185208
}
186209
}
187210

188-
// native
189211
private native static int loadSoundImpl(String fullName);
190212
private native static int loadMusicImpl(String fullName);
191213

modules/audio/src/main/native/android/c/audio.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2021, Gluon
2+
* Copyright (c) 2020, 2023, Gluon
33
*
44
* This program is free software: you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License as published by
@@ -41,7 +41,7 @@ static jmethodID jAudioServiceDisposeMethod;
4141
static void initializeAudioDalvikHandles() {
4242
jAudioServiceClass = GET_REGISTER_DALVIK_CLASS(jAudioServiceClass, "com/gluonhq/helloandroid/DalvikAudioService");
4343
ATTACH_DALVIK();
44-
jmethodID jAudioServiceInitMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jAudioServiceClass, "<init>", "()V");
44+
jmethodID jAudioServiceInitMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jAudioServiceClass, "<init>", "(Landroid/app/Activity;)V");
4545

4646
jAudioServiceLoadSoundMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jAudioServiceClass, "loadSoundImpl", "(Ljava/lang/String;)I");
4747
jAudioServiceLoadMusicMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jAudioServiceClass, "loadMusicImpl", "(Ljava/lang/String;)I");
@@ -53,7 +53,8 @@ static void initializeAudioDalvikHandles() {
5353
jAudioServiceStopMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jAudioServiceClass, "stop", "(I)V");
5454
jAudioServiceDisposeMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jAudioServiceClass, "dispose", "(I)V");
5555

56-
jobject jObj = (*dalvikEnv)->NewObject(dalvikEnv, jAudioServiceClass, jAudioServiceInitMethod);
56+
jobject jActivity = substrateGetActivity();
57+
jobject jObj = (*dalvikEnv)->NewObject(dalvikEnv, jAudioServiceClass, jAudioServiceInitMethod, jActivity);
5758
jDalvikAudioService = (*dalvikEnv)->NewGlobalRef(dalvikEnv, jObj);
5859

5960
DETACH_DALVIK();

modules/audio/src/main/native/android/dalvik/DalvikAudioService.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, Gluon
2+
* Copyright (c) 2020, 2023, Gluon
33
*
44
* This program is free software: you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License as published by
@@ -27,6 +27,8 @@
2727
*/
2828
package com.gluonhq.helloandroid;
2929

30+
import android.app.Activity;
31+
import android.content.Context;
3032
import android.media.AudioAttributes;
3133
import android.media.AudioManager;
3234
import android.media.MediaPlayer;
@@ -39,11 +41,10 @@
3941
public class DalvikAudioService {
4042

4143
private DalvikAudio[] cache = new DalvikAudio[10];
42-
4344
private SoundPool pool = null;
4445

45-
public DalvikAudioService() {
46-
46+
public DalvikAudioService(Activity activity) {
47+
activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
4748
}
4849

4950
/**
@@ -108,7 +109,7 @@ private SoundPool createPool() {
108109
return new SoundPool.Builder()
109110
.setAudioAttributes(audioAttributes)
110111
// this is arbitrary, but it should be a reasonable amount
111-
.setMaxStreams(5)
112+
.setMaxStreams(20)
112113
.build();
113114
}
114115

0 commit comments

Comments
 (0)