Caching sounds using byte arrays from within jar file
Asked Answered
C

1

4

I can read and play sounds using the "Playing a Clip" solution from the tag wiki page. However, for sounds that are played frequently (e.g., a quick laser gun sound, a footstep, etc.), it's jarring to me to be opening streams and re-reading files every time you want to create a new Clip. So, I'm trying to cache read files into a byte[] and, subsequently, load them from the cache.

The loading part is easy:

// Get a BAIS.
ByteArrayInputStream bais = new ByteArrayInputStream(cache.get(fileName));

// Convert to an audio stream.
AudioInputStream ais = AudioSystem.getAudioInputStream(bais);

However, initially getting the file contents into a byte array is proving to be a challenge. The problem is that I'm trying to read sounds from a file included in the .jar – so using java.io.File isn't an option (as I understand), and the various solutions I've seen (links below) don't apply.

It seems to me that the hardest part would be getting the length of the file to create the byte array without using java.io.File. I can read the bytes with a Scanner, but I need to read them into some array. Should I just use ArrayList<Byte>? (See "Suboptimal Example" below.)

So, my question: what is the best way that I can read an embedded file into a byte[] for repeated access later?

Limitations

  • I have to be able to access files within a jarfile. I believe this limits me to Class.getResource or Class.getResourceAsStream.
  • The file bytes should be stored in a standard byte[] variable.
  • I'd prefer to do this without introducing unnecessary dependencies such as Guava or Apache Commons. My entire project so far is in vanilla Java (JDK6) and I'd like to keep it that way.

What have I tried?

I've tried using RandomAccessFile, like so:

// Get the file.
RandomAccessFile f = new RandomAccessFile(fullPath, "r");

// Create a byte array.
theseBytes = new byte[(int) f.length()];

// Read into the array.
f.read(theseBytes);

// Close the file.
f.close();

// Put in map for later reference.
byteCache.put(fullPath, theseBytes);

However, apparently this only works for disk-referenced file; I get the following error:

java.io.FileNotFoundException: \path\to\sound\in\jar\file.wav (The system cannot find the path specified)

Suboptimal Example

While this example works, I don't think an ArrayList is the best way to do this, due to constant resizing, etc.

// Get a stream.
InputStream s = clazz.getResourceAsStream(fullPath);

// Get a byte array.
ArrayList<Byte> byteArrayList = new ArrayList<Byte>();

// Create a storage variable.
int last = 0;

// Loop.
while ((last = s.read()) != -1) {
    // Get it.
    byteArrayList.add((byte) last);
}

// Create a byte array.
theseBytes = new byte[byteArrayList.size()];

// Loop over each element.
for (int i = 0; i < theseBytes.length; i++) {
    // Set the byte.
    theseBytes[i] = byteArrayList.get(i);
}

Previous Reading

Couscous answered 31/3, 2013 at 2:56 Comment(1)
excellent question format!Emmuela
G
3

Try this:

InputStream is = new BufferedInputStream(getClass().getResourceAsStream(name));
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (int b; (b = is.read()) != -1;) {
    out.write(b);
}
byte[] a = out.toByteArray();

where name is the path to the file within the .jar.

Garber answered 31/3, 2013 at 3:17 Comment(1)
Interesting and clever. Thanks.Couscous

© 2022 - 2024 — McMap. All rights reserved.