CodenameOne
CodenameOne copied to clipboard
sound clips play unreliably on Android
The attached test program plays a series of short sound clips, each clip plays 3 times, and should therefore be identical each time. They are not. Sometimes a clip plays correctly, sometimes not at all, sometimes in an audibly damaged way. This problem manifests differently on different hardware, and short clips seem to be particularly problematic. For comparison, you can build this for IOS.
The times reported while the clips are playing are elapsed time from start to when the doneplaying callback is executed. Its' suspicious that the play times are irregular and sometimes shorter than the sound clips actual duration.
package com.boardspace.dtest;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;
import com.codename1.media.MediaManager;
import com.codename1.ui.Component;
// issue #2567
// this version shows that g.rotate interacts badly with clipping
//
import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Label;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.Resources;
import com.codename1.ui.Graphics;
import com.codename1.ui.layouts.BorderLayout;
class Clip
{ InputStream stream;
String name;
long endtime = 0;
long starttime = 0;
Dtest parent;
String error = "";
Runnable notifier = new Runnable()
{ public void run()
{ endtime = System.currentTimeMillis();
try { stream.close(); }
catch (IOException e) {}
synchronized(parent) { parent.notify(); }
}
};
Clip()
{
}
public String toString()
{ long et = endtime;
if(et<=starttime) { et = System.currentTimeMillis(); }
return("<clip "+name+" "+(et-starttime)+(error==null?"":error)+">");
}
public void play(Dtest p,String n,InputStream s)
{
parent = p;
stream = s;
name = n;
starttime = System.currentTimeMillis();
endtime = starttime-1;
try {
MediaManager.createMedia(stream, "audio/wav", notifier ).play();
}
catch(IOException e)
{
error = e.toString();
}
}
}
public class Dtest
{
public String clips[] = {
"badhint.wav",
"click4.wav",
"pick-3.wav",
"cannon.wav",
"chat.wav",
"Doorbl.wav",
"chatchimes.wav",
"gamechat.wav",
"goodhint.wav",
"droptile.wav",
"dkbell.wav",
"drop-1.wav",
"drop-3.wav",
"knock.wav",
"cowbell.wav",
"scrape.wav",
"ticktock.wav",
};
private Form current;
@SuppressWarnings("unused")
private Resources theme;
public void init(Object context) {
theme = UIManager.initFirstTheme("/theme");
}
public int loops = 0;
public Clip finishedClip;
public String errorMessage;
public void playClip(Component hi,Resources res,String clip) throws IOException
{ int aa[]=null;
InputStream stream = res.getData(clip);
if(stream==null)
{ errorMessage = "clip "+clip+" not found";
}
else {
hi.repaint();
Clip cl = new Clip();
finishedClip = cl;
cl.play(this,clip,stream);
while(cl.endtime<cl.starttime)
{
try {
Thread.sleep(10);
hi.repaint();
} catch (InterruptedException e) {
errorMessage = e.toString();
}
}
hi.repaint();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void start() {
if(current != null){
current.show();
return;
}
Form hi = new Form("Hi >>0 World");
current = hi;
hi.setLayout(new BorderLayout());
hi.addComponent(BorderLayout.CENTER, new Label("Hi Overlay World 2") {
@Override
public void paint(Graphics g) {
//g.resetAffine();
int w = getWidth();
int h = getHeight();
g.setColor(0x8f8f9f7f);
g.fillRect(0,0,w,h);
g.setColor(0xff);
g.drawString("clip #"+loops+" "+finishedClip,w/4,h/2);
if(errorMessage!=null) { g.drawString(errorMessage,w/4,h/2+30); }
}
});
hi.show();
Runnable rr = new Runnable (){
public void run() {
System.out.println("running");
Resources res;
try {
res = Resources.open("/theme.res");
while(true)
{
try {
String clip = clips[loops];
for(int i=0;i<3;i++) { playClip(hi,res,clip); }
try {
Thread.sleep(2000);
} catch(InterruptedException e) {};
}
catch (Throwable e) { errorMessage = "error: "+e.toString(); }
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {};
loops = (loops+1)%clips.length;
}}
catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
};
new Thread(rr).start();
}
public void stop() {
current = Display.getInstance().getCurrent();
}
public void destroy() {
}
}
Interesting. Do you get the same issue if you load the clip from FileSystemStorage rather than the resource file?
Yes, the source of the clip doesn't matter. In another variation, I made a binary in-memory file and read it using a ByteArrayInputStream; no difference.
I'm curious specifically about an input stream created from filesystemstorage. That will follow a different code path.
The version that I actually deploy (rather than the test program I provided) uses this: ``` final InputStream instream = data==null ? new FileInputStream(clipFile) : new ByteArrayInputStream(data);
and arranges for the FileInputStream to be a copy of the resource file.