package edu.mit.mitmobile2;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.LinkedList;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import android.content.Context;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.os.Environment;
import android.util.Log;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageButton;
import edu.mit.mitmobile2.about.Config;
import edu.mit.mitmobile2.tour.Tour.TourItem;
public class AudioPlayer implements OnCompletionListener, OnErrorListener {
class DownloadMsg {
String url;
int origPage;
ImageButton ib;
boolean kill_thread = false;
}
interface DownloadListener {
void done();
}
private Context ctx;
private MediaPlayer mp;
private static AudioPlayer instance;
DownloadListener callback;
static String TAG = "AudioPlayer";
private boolean hasSDcard = true;
private ImageButton curAudioButton;
private String curFilename;
private int curPos = 0;
private String root;
private HashMap<String,String> fetching = new HashMap<String,String>();
private HashMap<String,Integer> seekTimes = new HashMap<String,Integer>();
private LinkedList<DownloadMsg> downloadQueue = new LinkedList<DownloadMsg>();
private Thread downloadThread;
private int curPage = -1;
/*****************************************************/
public AudioPlayer() {
mp = new MediaPlayer();
mp.setOnCompletionListener(this);
mp.setOnErrorListener(this);
//mp.setOnBufferingUpdateListener(this);
}
/*****************************************************/
public void init(Context ctx, DownloadListener callback) {
this.ctx = ctx;
this.callback = callback;
if (instance==null) {
instance = new AudioPlayer();
}
setupCache();
}
/*****************************************************/
void downloadFileThread(final String url, ImageButton ib) {
if (fetching.containsKey(url)) {
return;
}
Log.v(TAG, "audio_debug: downloadFileThread");
fetching.put(url, null);
DownloadMsg dm = new DownloadMsg();
dm.origPage = curPage;
dm.url = url;
dm.ib = ib;
if (ib!=null) {
// FIXME
//ib.setImageResource(R.drawable.sm_busybox); // this is xml
ib.setImageResource(R.drawable.sm_busybox_01);
Animation spinner = AnimationUtils.loadAnimation(ctx,R.anim.spin);
ib.startAnimation(spinner);
}
downloadQueue.addLast(dm);
if ((downloadThread==null)|| !downloadThread.isAlive()){
Log.v(TAG, "audio_debug: downloadFileThread - new Thread");
downloadThread = new Thread() {
@Override
public void run() {
while (!downloadQueue.isEmpty()) {
Log.v(TAG, "audio_debug: downloadFileThread - remove");
final DownloadMsg dm = downloadQueue.removeFirst();
if (dm.kill_thread) break;
downloadFile(url);
if (dm.ib!=null) {
// Stop spin...
dm.ib.post(new Runnable() {
@Override
public void run() {
dm.ib.setImageResource(R.drawable.audio_pause);
dm.ib.setAnimation(null);
}
});
}
final String filename = safeFilenameEncode(url);
if (filename.equalsIgnoreCase(curFilename)) {
if (dm.origPage==curPage) play(filename); // TODO auto-play last pressed button
//play(filename); // auto-play last pressed button
}
//if (callback!=null) callback.done();
}
}
};
downloadThread.start();
}
}
void close() {
Log.v(TAG, "audio_debug: close");
mp.release();
// FIXME race?
DownloadMsg dm = new DownloadMsg();
dm.kill_thread = true;
//downloadThread.stop(); // deprecated and considered unsafe?
downloadQueue.add(dm);
downloadThread = null;
}
/*****************************************************/
void downloadFile(final String url) {
// TODO stop spinning arrow on error
Log.v(TAG, "audio_debug: downloadFile");
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet request = new HttpGet(url);
try {
HttpResponse response = httpClient.execute(request);
save(response.getEntity().getContent(),safeFilenameEncode(url));
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
fetching.remove(url);
}
/*****************************************************/
boolean isFileAvailable(String filename) {
if (!fetching.containsKey(filename)) {
String path = root + "/" + filename;
File file = new File(path, "");
if (file.exists()) return true;
}
return false;
}
/*****************************************************/
private void setupCache() {
String subpath = "/Android/data/" + Config.release_project_name + "/cache/"; // TODO for API 7 or lower, this dir will get deleted on uninstall
String state = Environment.getExternalStorageState();
hasSDcard = false;
root = "/data/data/edu.mit.mitmobile2/mitmobile2audio"; // default to Internal file
if (Environment.MEDIA_MOUNTED.equals(state)) {
hasSDcard = true;
root = Environment.getExternalStorageDirectory().toString() + subpath;
//root = Environment.getExternalStorageDirectory().toString() + "/mitmobile2audio";
File file = new File(root, "");
if (!file.exists()) {
boolean success = file.mkdirs(); // makes whole path
if (!success) {
hasSDcard = false;
Log.e("Audio","Audio: mkdir failed");
}
}
}
}
/***********************************************************/
public void togglePlay(String url, ImageButton audioButton) {
Log.v(TAG, "audio_debug: togglePlay");
boolean playNew = false;
final String filename = safeFilenameEncode(url);
if (mp.isPlaying()) {
// Remember current...
curPos = mp.getCurrentPosition();
seekTimes.put(curFilename, curPos);
// Maybe start new...
if (filename.equals(curFilename)) {
// Same so pause
audioButton.setImageResource(R.drawable.audio_speaker);
mp.pause();
} else {
// Different so stop old...
curAudioButton.setImageResource(R.drawable.audio_speaker);
mp.stop();
// ... and start new
playNew = true;
}
} else {
// TODO check if paused?
playNew = true;
}
if (playNew) {
mp.reset();
if (!isFileAvailable(filename)) {
downloadFileThread(url, audioButton);
} else {
audioButton.setImageResource(R.drawable.audio_pause);
play(filename);
}
}
curFilename = filename;
curAudioButton = audioButton;
}
/*****************************************************/
public void play(String fname) {
Log.v(TAG, "audio_debug: play");
curFilename = fname;
try {
String path = root + "/" + curFilename;
mp.reset();
mp.setDataSource(path);
mp.prepare();
mp.start();
// Continue from last paused?
Integer seekTime = seekTimes.get(curFilename);
if (seekTime!=null) {
curPos = seekTime;
mp.seekTo(curPos);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/*****************************************************/
private void save(InputStream in, String fname) {
File file = null;
OutputStream fos = null;
Log.v(TAG, "audio_debug: save");
try {
// Open file...
if (hasSDcard) {
String path = root + "/" + fname;
file = new File(path, "");
fos = new FileOutputStream(file);
}
else {
fos = ctx.openFileOutput(fname, Context.MODE_PRIVATE);
}
// Write file...
byte[] buffer = new byte[1024];
int len1 = 0;
while ( (len1 = in.read(buffer)) > 0 ) {
fos.write(buffer,0, len1);
}
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
// delete bad data
if (file!=null) file.delete();
e.printStackTrace();
}
}
/*****************************************************/
public void stop() {
Log.v(TAG, "audio_debug: stop");
if (mp.isPlaying()) {
mp.stop();
// Reset button...
if (curAudioButton!=null)
curAudioButton.setImageResource(R.drawable.audio_speaker);
}
seekTimes.put(curFilename, null);
}
/*****************************************************/
@Override
public void onCompletion(MediaPlayer mp) {
if (curAudioButton!=null) {
curAudioButton.setImageResource(R.drawable.audio_speaker);
}
//mp.reset();
seekTimes.put(curFilename, null);
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
if (curAudioButton!=null) {
curAudioButton.setImageResource(R.drawable.audio_speaker);
}
seekTimes.put(curFilename, null);
return false;
}
/*****************************************************/
public void downloadTourItemAudio(TourItem tourItem) {
if(tourItem.getAudioUrl() != null) {
downloadFileThread(tourItem.getAudioUrl(), null);
}
}
/*
* this is just the simplest safe filename encoding scheme i could think
* of to avoid having unsafe characters such as '/' in the file name
*
* just replace all characters with hex version of the codepoint
*/
private static String safeFilenameEncode(String url) {
String safeFilename = "";
boolean isFirst = true;
for(int i=0; i < url.length(); i++) {
if(isFirst) {
isFirst = false;
} else {
safeFilename += "-";
}
safeFilename += Integer.toHexString(url.codePointAt(i));
}
return safeFilename;
}
public void setPage(int newPage) {
this.curPage = newPage;
}
}