cordova-android/framework/src/com/phonegap/AudioPlayer.java

397 lines
12 KiB
Java
Raw Normal View History

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
*/
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-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-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.");
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);
try {
2010-09-01 04:39:37 +08:00
this.recorder.prepare();
this.recorder.start();
this.state = MEDIA_RUNNING;
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");");
2010-09-01 04:39:37 +08:00
return;
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
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.");
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_ALREADY_RECORDING+");");
}
}
2010-09-01 04:39:37 +08:00
/**
* Save temporary recorded file to specified name
*
* @param file
*/
public void moveFile(String file) {
/* 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-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
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-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.");
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)) {
try {
2010-09-01 04:39:37 +08:00
// If stopped, then reset player
if (this.mPlayer != null) {
this.mPlayer.reset();
}
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);
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
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");");
2010-09-01 04:39:37 +08:00
}
catch (Exception e) {
e.printStackTrace();
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
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);
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_RESUME_STATE+");");
}
}
}
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
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);
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.
*/
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
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);
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_STOP_STATE+");");
}
}
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
*/
public void onCompletion(MediaPlayer mPlayer) {
2010-09-01 04:39:37 +08:00
this.state = MEDIA_STOPPED;
// Send status notification to JavaScript
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");");
}
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-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) {
if (file.contains("http://")) {
return true;
2010-09-01 04:39:37 +08:00
}
else {
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-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
*/
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
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");");
}
2010-09-01 04:39:37 +08:00
// Save off duration
this.duration = this.mPlayer.getDuration();
this.prepareOnly = false;
// Send status notification to JavaScript
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_DURATION+","+this.duration+");");
2010-09-01 04:39:37 +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.
*/
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
this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+arg1+");");
return false;
2010-09-01 04:39:37 +08:00
}
}