The MIDlet uses regular uncompressed audio samples in 8-bit mono WAVE format with a sample rate of 44100 Hz. On a Series 40 devices, the JAR file size limit enforces some constraints, but compressed audio files, such as MP3, are not ideal for this kind of application, since they require a short silence in the beginning of the audio file. Also, with WAVE files, it is possible to have better sounding samples.
The audio files are played back using Players
. A Player
can be
created using the Manager
object with the method createPlayer
. The createPlayer
method takes
an InputStream
object and the media format as parameters.
During the process, an IOException
or MediaException
can occur, which need to be caught. Here is an example how the snare.wav
sample could be loaded:
try { InputStream is = this.getClass().getResourceAsStream("/snare.wav"); Player snare = Manager.createPlayer(is, "audio/wav"); snare.prefetch(); } catch (IOException ioe) { // ... } catch (MediaException me) { // ... }
The sample can then be played with the following code:
try { snare.setMediaTime(0); snare.start(); } catch (MediaException me) { // ... }
In Java ME, the number of prefetched
audio samples and the number of currently playing samples is limited.
The prefetchedPlayers
and startedPlayers
vectors keep track of which sounds are loaded and playing, so that
when a new sound needs to be loaded or played, the least recently
used sound is discarded:
private static int maxPrefetchedPlayers = 8; private static int maxStartedPlayers = supportsMixing() ? 3 : 1; private static final Vector prefetchedPlayers = new Vector(); private static final Vector startedPlayers = new Vector();
The supportsMixing
method determines whether
the device supports mixing:
public static boolean supportsMixing() { String s = System.getProperty("supports.mixing"); return s != null && s.equalsIgnoreCase("true") && !Main.isS60Phone(); }
When a sound needs to be played, and the sound
has already been prefetched, it is simply restarted. If the sound
has not been prefetched, first the limitPrefetchedPlayers
method is called to make room for a new sound in the prefetchedPlayers
vector, and then a new Player
is created and started.
On some lower-end Nokia Asha software platform devices, for example,
the maximum number of prefetched Players
is less
than 8, which is considered the default. If the maximum is reached,
the value of maxPrefetchedPlayers
needs to be reduced
accordingly; otherwise, some samples would not be played.
public static void playSound(int sound) { if (restart(sound)) { return; } Player player = null; InputStream stream = SoundManager.class.getResourceAsStream(resources[sound]); try { limitPrefetchedPlayers(); player = Manager.createPlayer(stream, "audio/wav"); player.realize(); player.prefetch(); start(sound, player); } catch (Exception e) { // Either the device is in silent mode... if (prefetchedPlayers.isEmpty()) { Main.getInstance().sampleAlert(); } // ...or does not support having all 8 players prefetched else if (maxPrefetchedPlayers > 1) { // Reduce amount of players and try again maxPrefetchedPlayers--; playSound(sound); } } }
The restart
method tries to find
the sound from the prefetchedPlayers
vector. If the
sound is found, the prefetchedPlayers
vector is updated,
so that the Players
are in playing order in the vector
and the found Player
is restarted:
private static boolean restart(int sound) { synchronized (prefetchedPlayers) { for (int i = 0; i < prefetchedPlayers.size(); i++) { SoundPlayer sp = (SoundPlayer) prefetchedPlayers.elementAt(i); if (sp.sound == sound) { prefetchedPlayers.removeElement(sp); prefetchedPlayers.addElement(sp); stop(sp.player); try { sp.player.setMediaTime(0); } catch (Exception e) { } start(sp.player); return true; } } return false; } }
The limitPrefetchedPlayers
method
cleans up Players
, so that, after the method has
run, there is room for a new Player
:
private static void limitPrefetchedPlayers() { synchronized (prefetchedPlayers) { try { while (prefetchedPlayers.size() >= maxPrefetchedPlayers) { SoundPlayer sp = (SoundPlayer) prefetchedPlayers.firstElement(); clean(sp); } } catch (Exception e) { } } } // ... private static void clean(SoundPlayer sp) { synchronized (prefetchedPlayers) { prefetchedPlayers.removeElement(sp); stop(sp.player); try { sp.player.deallocate(); sp.player.close(); } catch (Exception e) { } } }
The first start
method, which
takes the sound and a new Player
as parameters, adds
the Player
to the loadedPlayers
vector
and starts playback:
private static void start(int sound, Player player) { synchronized (prefetchedPlayers) { prefetchedPlayers.addElement(new SoundPlayer(sound, player)); start(player); } }
The second start
method and the stop
method control how many Players
are
concurrently playing:
private static void start(Player player) { synchronized (startedPlayers) { try { while (startedPlayers.size() >= maxStartedPlayers) { Player p = (Player) startedPlayers.firstElement(); startedPlayers.removeElementAt(0); stop(p); } } catch (Exception e) { } startedPlayers.addElement(player); try { player.start(); } catch (Exception e) { } } } // ... private static void stop(Player player) { synchronized (startedPlayers) { startedPlayers.removeElement(player); try { if (player.getState() == Player.STARTED) { try { player.stop(); } catch (Exception e) { } } } catch (Exception e) { } } }