2010-06-09 07:30:20 +08:00
|
|
|
package com.phonegap;
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.IOException;
|
|
|
|
import android.media.AudioManager;
|
|
|
|
import android.media.MediaPlayer;
|
|
|
|
import android.media.MediaPlayer.OnErrorListener;
|
|
|
|
import android.media.MediaRecorder;
|
|
|
|
import android.media.MediaPlayer.OnCompletionListener;
|
|
|
|
import android.media.MediaPlayer.OnPreparedListener;
|
|
|
|
|
2010-09-01 04:39:37 +08:00
|
|
|
/**
|
|
|
|
* This class implements the audio playback and recording capabilities used by PhoneGap.
|
|
|
|
* It is called by the AudioHandler PhoneGap class.
|
|
|
|
* Only one file can be played or recorded per class instance.
|
|
|
|
*
|
|
|
|
* Local audio files must reside on sdcard
|
|
|
|
* android_asset: file name must start with /android_asset/sound.mp3
|
|
|
|
* sdcard: file name is just sound.mp3
|
|
|
|
*/
|
2010-06-09 07:30:20 +08:00
|
|
|
public class AudioPlayer implements OnCompletionListener, OnPreparedListener, OnErrorListener {
|
2010-09-01 04:39:37 +08:00
|
|
|
|
|
|
|
// AudioPlayer states
|
|
|
|
private static int MEDIA_NONE = 0;
|
|
|
|
private static int MEDIA_STARTING = 1;
|
|
|
|
private static int MEDIA_RUNNING = 2;
|
|
|
|
private static int MEDIA_PAUSED = 3;
|
|
|
|
private static int MEDIA_STOPPED = 4;
|
2010-06-09 07:30:20 +08:00
|
|
|
|
2010-09-01 04:39:37 +08:00
|
|
|
// AudioPlayer message ids
|
|
|
|
private static int MEDIA_STATE = 1;
|
|
|
|
private static int MEDIA_DURATION = 2;
|
|
|
|
private static int MEDIA_ERROR = 3;
|
2010-06-09 07:30:20 +08:00
|
|
|
|
2010-09-01 04:39:37 +08:00
|
|
|
// AudioPlayer error codes
|
|
|
|
private static int MEDIA_ERROR_PLAY_MODE_SET = 1;
|
|
|
|
private static int MEDIA_ERROR_ALREADY_RECORDING = 2;
|
|
|
|
private static int MEDIA_ERROR_STARTING_RECORDING = 3;
|
|
|
|
private static int MEDIA_ERROR_RECORD_MODE_SET = 4;
|
|
|
|
private static int MEDIA_ERROR_STARTING_PLAYBACK = 5;
|
|
|
|
private static int MEDIA_ERROR_RESUME_STATE = 6;
|
|
|
|
private static int MEDIA_ERROR_PAUSE_STATE = 7;
|
|
|
|
private static int MEDIA_ERROR_STOP_STATE = 8;
|
|
|
|
|
|
|
|
private AudioHandler handler; // The AudioHandler object
|
|
|
|
private String id; // The id of this player (used to identify Media object in JavaScript)
|
|
|
|
private int state = MEDIA_NONE; // State of recording or playback
|
|
|
|
private String audioFile = null; // File name to play or record to
|
|
|
|
private long duration = -1; // Duration of audio
|
|
|
|
|
|
|
|
private MediaRecorder recorder = null; // Audio recording object
|
|
|
|
private String tempFile = null; // Temporary recording file name
|
|
|
|
|
|
|
|
private MediaPlayer mPlayer = null; // Audio player object
|
|
|
|
private boolean prepareOnly = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor.
|
|
|
|
*
|
|
|
|
* @param handler The audio handler object
|
|
|
|
* @param id The id of this audio player
|
|
|
|
*/
|
|
|
|
public AudioPlayer(AudioHandler handler, String id) {
|
|
|
|
this.handler = handler;
|
|
|
|
this.id = id;
|
|
|
|
|
|
|
|
// YES, I know this is bad, but I can't do it the right way because Google didn't have the
|
|
|
|
// foresight to add android.os.environment.getExternalDataDirectory until Android 2.2
|
|
|
|
this.tempFile = "/sdcard/tmprecording.mp3";
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destroy player and stop audio playing or recording.
|
|
|
|
*/
|
|
|
|
public void destroy() {
|
|
|
|
|
|
|
|
// Stop any play or record
|
|
|
|
if (this.mPlayer != null) {
|
|
|
|
this.stopPlaying();
|
|
|
|
this.mPlayer.release();
|
|
|
|
this.mPlayer = null;
|
|
|
|
}
|
|
|
|
if (this.recorder != null) {
|
|
|
|
this.stopRecording();
|
|
|
|
this.recorder.release();
|
|
|
|
this.recorder = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start recording the specified file.
|
|
|
|
*
|
|
|
|
* @param file The name of the file
|
|
|
|
*/
|
|
|
|
public void startRecording(String file) {
|
|
|
|
if (this.mPlayer != null) {
|
|
|
|
System.out.println("AudioPlayer Error: Can't record in play mode.");
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_PLAY_MODE_SET+");");
|
2010-09-01 04:39:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure we're not already recording
|
|
|
|
else if (this.recorder == null) {
|
|
|
|
this.audioFile = file;
|
|
|
|
this.recorder = new MediaRecorder();
|
|
|
|
this.recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
|
|
|
this.recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); // THREE_GPP);
|
|
|
|
this.recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); //AMR_NB);
|
|
|
|
this.recorder.setOutputFile(this.tempFile);
|
2010-06-09 07:30:20 +08:00
|
|
|
try {
|
2010-09-01 04:39:37 +08:00
|
|
|
this.recorder.prepare();
|
|
|
|
this.recorder.start();
|
|
|
|
this.state = MEDIA_RUNNING;
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");");
|
2010-09-01 04:39:37 +08:00
|
|
|
return;
|
2010-06-09 07:30:20 +08:00
|
|
|
} catch (IllegalStateException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_STARTING_RECORDING+");");
|
2010-09-01 04:39:37 +08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
System.out.println("AudioPlayer Error: Already recording.");
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_ALREADY_RECORDING+");");
|
2010-06-09 07:30:20 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-01 04:39:37 +08:00
|
|
|
/**
|
|
|
|
* Save temporary recorded file to specified name
|
|
|
|
*
|
|
|
|
* @param file
|
|
|
|
*/
|
|
|
|
public void moveFile(String file) {
|
|
|
|
|
2010-06-09 07:30:20 +08:00
|
|
|
/* this is a hack to save the file as the specified name */
|
2010-09-01 04:39:37 +08:00
|
|
|
File f = new File(this.tempFile);
|
|
|
|
f.renameTo(new File("/sdcard/" + file));
|
2010-06-09 07:30:20 +08:00
|
|
|
}
|
|
|
|
|
2010-09-01 04:39:37 +08:00
|
|
|
/**
|
|
|
|
* Stop recording and save to the file specified when recording started.
|
|
|
|
*/
|
|
|
|
public void stopRecording() {
|
|
|
|
if (this.recorder != null) {
|
|
|
|
try{
|
|
|
|
if (this.state == MEDIA_RUNNING) {
|
|
|
|
this.state = MEDIA_STOPPED;
|
|
|
|
this.recorder.stop();
|
|
|
|
|
|
|
|
// Send status notification to JavaScript
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");");
|
2010-09-01 04:39:37 +08:00
|
|
|
}
|
|
|
|
this.moveFile(this.audioFile);
|
|
|
|
}
|
|
|
|
catch (Exception e) {
|
|
|
|
e.printStackTrace();
|
2010-06-09 07:30:20 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-01 04:39:37 +08:00
|
|
|
/**
|
|
|
|
* Start or resume playing audio file.
|
|
|
|
*
|
|
|
|
* @param file The name of the audio file.
|
|
|
|
*/
|
|
|
|
public void startPlaying(String file) {
|
|
|
|
if (this.recorder != null) {
|
|
|
|
System.out.println("AudioPlayer Error: Can't play in record mode.");
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_RECORD_MODE_SET+");");
|
2010-09-01 04:39:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// If this is a new request to play audio
|
|
|
|
else if ((this.mPlayer == null) || (this.state == MEDIA_STOPPED)) {
|
2010-06-09 07:30:20 +08:00
|
|
|
try {
|
2010-09-01 04:39:37 +08:00
|
|
|
// If stopped, then reset player
|
|
|
|
if (this.mPlayer != null) {
|
|
|
|
this.mPlayer.reset();
|
2010-06-09 07:30:20 +08:00
|
|
|
}
|
2010-09-01 04:39:37 +08:00
|
|
|
// Otherwise, create a new one
|
|
|
|
else {
|
|
|
|
this.mPlayer = new MediaPlayer();
|
|
|
|
}
|
|
|
|
this.audioFile = file;
|
|
|
|
|
|
|
|
// If streaming file
|
|
|
|
if (this.isStreaming(file)) {
|
|
|
|
this.mPlayer.setDataSource(file);
|
|
|
|
this.mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
|
|
|
this.mPlayer.prepareAsync();
|
|
|
|
}
|
|
|
|
|
|
|
|
// If local file
|
|
|
|
else {
|
|
|
|
if (file.startsWith("/android_asset/")) {
|
|
|
|
String f = file.substring(15);
|
2010-09-07 02:13:09 +08:00
|
|
|
android.content.res.AssetFileDescriptor fd = this.handler.ctx.getBaseContext().getAssets().openFd(f);
|
2010-09-01 04:39:37 +08:00
|
|
|
this.mPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.mPlayer.setDataSource("/sdcard/" + file);
|
|
|
|
}
|
|
|
|
this.mPlayer.prepare();
|
|
|
|
|
|
|
|
// Get duration
|
|
|
|
this.duration = this.mPlayer.getDuration();
|
|
|
|
}
|
|
|
|
this.mPlayer.setOnPreparedListener(this);
|
|
|
|
this.state = MEDIA_STARTING;
|
|
|
|
|
|
|
|
// Send status notification to JavaScript
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");");
|
2010-09-01 04:39:37 +08:00
|
|
|
}
|
|
|
|
catch (Exception e) {
|
2010-06-09 07:30:20 +08:00
|
|
|
e.printStackTrace();
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_STARTING_PLAYBACK+");");
|
2010-09-01 04:39:37 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have already have created an audio player
|
|
|
|
else {
|
|
|
|
|
|
|
|
// If player has been paused, then resume playback
|
|
|
|
if ((this.state == MEDIA_PAUSED) || (this.state == MEDIA_STARTING)) {
|
|
|
|
this.mPlayer.start();
|
|
|
|
this.state = MEDIA_RUNNING;
|
|
|
|
|
|
|
|
// Send status notification to JavaScript
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");");
|
2010-09-01 04:39:37 +08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
System.out.println("AudioPlayer Error: startPlaying() called during invalid state: "+this.state);
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_RESUME_STATE+");");
|
2010-06-09 07:30:20 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-01 04:39:37 +08:00
|
|
|
/**
|
|
|
|
* Pause playing.
|
|
|
|
*/
|
|
|
|
public void pausePlaying() {
|
|
|
|
|
|
|
|
// If playing, then pause
|
|
|
|
if (this.state == MEDIA_RUNNING) {
|
|
|
|
this.mPlayer.pause();
|
|
|
|
this.state = MEDIA_PAUSED;
|
|
|
|
|
|
|
|
// Send status notification to JavaScript
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");");
|
2010-09-01 04:39:37 +08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
System.out.println("AudioPlayer Error: pausePlaying() called during invalid state: "+this.state);
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_PAUSE_STATE+");");
|
2010-09-01 04:39:37 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop playing the audio file.
|
|
|
|
*/
|
2010-06-09 07:30:20 +08:00
|
|
|
public void stopPlaying() {
|
2010-09-01 04:39:37 +08:00
|
|
|
if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) {
|
|
|
|
this.state = MEDIA_STOPPED;
|
|
|
|
this.mPlayer.stop();
|
|
|
|
|
|
|
|
// Send status notification to JavaScript
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");");
|
2010-09-01 04:39:37 +08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
System.out.println("AudioPlayer Error: stopPlaying() called during invalid state: "+this.state);
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_STOP_STATE+");");
|
2010-06-09 07:30:20 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-01 04:39:37 +08:00
|
|
|
/**
|
|
|
|
* Callback to be invoked when playback of a media source has completed.
|
|
|
|
*
|
|
|
|
* @param mPlayer The MediaPlayer that reached the end of the file
|
|
|
|
*/
|
2010-06-09 07:30:20 +08:00
|
|
|
public void onCompletion(MediaPlayer mPlayer) {
|
2010-09-01 04:39:37 +08:00
|
|
|
this.state = MEDIA_STOPPED;
|
|
|
|
|
|
|
|
// Send status notification to JavaScript
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");");
|
2010-06-09 07:30:20 +08:00
|
|
|
}
|
|
|
|
|
2010-09-01 04:39:37 +08:00
|
|
|
/**
|
|
|
|
* Get current position of playback.
|
|
|
|
*
|
|
|
|
* @return position in msec or -1 if not playing
|
|
|
|
*/
|
|
|
|
public long getCurrentPosition() {
|
|
|
|
if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) {
|
|
|
|
return this.mPlayer.getCurrentPosition();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return -1;
|
|
|
|
}
|
2010-06-09 07:30:20 +08:00
|
|
|
}
|
|
|
|
|
2010-09-01 04:39:37 +08:00
|
|
|
/**
|
|
|
|
* Determine if playback file is streaming or local.
|
|
|
|
* It is streaming if file name starts with "http://"
|
|
|
|
*
|
|
|
|
* @param file The file name
|
|
|
|
* @return T=streaming, F=local
|
|
|
|
*/
|
|
|
|
public boolean isStreaming(String file) {
|
2010-06-09 07:30:20 +08:00
|
|
|
if (file.contains("http://")) {
|
|
|
|
return true;
|
2010-09-01 04:39:37 +08:00
|
|
|
}
|
|
|
|
else {
|
2010-06-09 07:30:20 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-01 04:39:37 +08:00
|
|
|
/**
|
|
|
|
* Get the duration of the audio file.
|
|
|
|
*
|
|
|
|
* @param file The name of the audio file.
|
|
|
|
* @return The duration in msec.
|
|
|
|
* -1=can't be determined
|
|
|
|
* -2=not allowed
|
|
|
|
*/
|
|
|
|
public long getDuration(String file) {
|
|
|
|
|
|
|
|
// Can't get duration of recording
|
|
|
|
if (this.recorder != null) {
|
|
|
|
return(-2); // not allowed
|
|
|
|
}
|
|
|
|
|
|
|
|
// If audio file already loaded and started, then return duration
|
|
|
|
if (this.mPlayer != null) {
|
|
|
|
return this.duration;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If no player yet, then create one
|
|
|
|
else {
|
|
|
|
this.prepareOnly = true;
|
|
|
|
this.startPlaying(file);
|
|
|
|
|
|
|
|
// This will only return value for local, since streaming
|
|
|
|
// file hasn't been read yet.
|
|
|
|
return this.duration;
|
|
|
|
}
|
2010-06-09 07:30:20 +08:00
|
|
|
}
|
|
|
|
|
2010-09-01 04:39:37 +08:00
|
|
|
/**
|
|
|
|
* Callback to be invoked when the media source is ready for playback.
|
|
|
|
*
|
|
|
|
* @param mPlayer The MediaPlayer that is ready for playback
|
|
|
|
*/
|
2010-06-09 07:30:20 +08:00
|
|
|
public void onPrepared(MediaPlayer mPlayer) {
|
2010-09-01 04:39:37 +08:00
|
|
|
// Listen for playback completion
|
|
|
|
this.mPlayer.setOnCompletionListener(this);
|
|
|
|
|
|
|
|
// If start playing after prepared
|
|
|
|
if (!this.prepareOnly) {
|
|
|
|
|
|
|
|
// Start playing
|
|
|
|
this.mPlayer.start();
|
|
|
|
|
|
|
|
// Set player init flag
|
|
|
|
this.state = MEDIA_RUNNING;
|
|
|
|
|
|
|
|
// Send status notification to JavaScript
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");");
|
2010-06-09 07:30:20 +08:00
|
|
|
}
|
2010-09-01 04:39:37 +08:00
|
|
|
|
|
|
|
// Save off duration
|
|
|
|
this.duration = this.mPlayer.getDuration();
|
|
|
|
this.prepareOnly = false;
|
|
|
|
|
|
|
|
// Send status notification to JavaScript
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_DURATION+","+this.duration+");");
|
2010-09-01 04:39:37 +08:00
|
|
|
|
2010-06-09 07:30:20 +08:00
|
|
|
}
|
|
|
|
|
2010-09-01 04:39:37 +08:00
|
|
|
/**
|
|
|
|
* Callback to be invoked when there has been an error during an asynchronous operation
|
|
|
|
* (other errors will throw exceptions at method call time).
|
|
|
|
*
|
|
|
|
* @param mPlayer the MediaPlayer the error pertains to
|
|
|
|
* @param arg1 the type of error that has occurred: (MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_SERVER_DIED)
|
|
|
|
* @param arg2 an extra code, specific to the error.
|
|
|
|
*/
|
2010-06-09 07:30:20 +08:00
|
|
|
public boolean onError(MediaPlayer mPlayer, int arg1, int arg2) {
|
2010-09-01 04:39:37 +08:00
|
|
|
System.out.println("AudioPlayer.onError(" + arg1 + ", " + arg2+")");
|
|
|
|
|
|
|
|
// TODO: Not sure if this needs to be sent?
|
|
|
|
this.mPlayer.stop();
|
|
|
|
this.mPlayer.release();
|
|
|
|
|
|
|
|
// Send error notification to JavaScript
|
2010-09-07 02:13:09 +08:00
|
|
|
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+arg1+");");
|
2010-06-09 07:30:20 +08:00
|
|
|
return false;
|
2010-09-01 04:39:37 +08:00
|
|
|
}
|
2010-06-09 07:30:20 +08:00
|
|
|
}
|