CodenameOne icon indicating copy to clipboard operation
CodenameOne copied to clipboard

sound clips play unreliably on Android

Open ddyer0 opened this issue 4 years ago • 4 comments

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() {
	}
	}

theme.zip

ddyer0 avatar Aug 08 '21 00:08 ddyer0

Interesting. Do you get the same issue if you load the clip from FileSystemStorage rather than the resource file?

shannah avatar Sep 09 '21 11:09 shannah

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.

ddyer0 avatar Sep 09 '21 16:09 ddyer0

I'm curious specifically about an input stream created from filesystemstorage. That will follow a different code path.

shannah avatar Sep 09 '21 16:09 shannah

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.

ddyer0 avatar Sep 09 '21 17:09 ddyer0