From 3c9415b1c2d0056cad8c2c49a0a22ceef577b11a Mon Sep 17 00:00:00 2001 From: Lorin Beer Date: Sun, 17 Jun 2012 22:18:09 -0700 Subject: [PATCH 01/11] added create message handler, updated AudioPlayer constructor usage --- framework/src/org/apache/cordova/AudioHandler.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/framework/src/org/apache/cordova/AudioHandler.java b/framework/src/org/apache/cordova/AudioHandler.java index a866d4f4..9da04f09 100755 --- a/framework/src/org/apache/cordova/AudioHandler.java +++ b/framework/src/org/apache/cordova/AudioHandler.java @@ -96,6 +96,12 @@ public class AudioHandler extends Plugin { float f = this.getDurationAudio(args.getString(0), args.getString(1)); return new PluginResult(status, f); } + else if (action.equals("create")) { + String id = args.getString(0); + String src = args.getString(1); + AudioPlayer audio = new AudioPlayer(this, id, src); + this.players.put(id, audio); + } else if (action.equals("release")) { boolean b = this.release(args.getString(0)); return new PluginResult(status, b); @@ -196,7 +202,7 @@ public class AudioHandler extends Plugin { if (this.players.containsKey(id)) { return; } - AudioPlayer audio = new AudioPlayer(this, id); + AudioPlayer audio = new AudioPlayer(this, id, file); this.players.put(id, audio); audio.startRecording(file); } @@ -221,7 +227,7 @@ public class AudioHandler extends Plugin { public void startPlayingAudio(String id, String file) { AudioPlayer audio = this.players.get(id); if (audio == null) { - audio = new AudioPlayer(this, id); + audio = new AudioPlayer(this, id, file); this.players.put(id, audio); } audio.startPlaying(file); @@ -292,7 +298,7 @@ public class AudioHandler extends Plugin { // If not already open, then open the file else { - audio = new AudioPlayer(this, id); + audio = new AudioPlayer(this, id, file); this.players.put(id, audio); return (audio.getDuration(file)); } From d16555ec4b0ae5bcd8e114c9cead313224d5b662 Mon Sep 17 00:00:00 2001 From: Lorin Beer Date: Sun, 17 Jun 2012 22:19:33 -0700 Subject: [PATCH 02/11] added file requirement to constructor, all references to AudioPlayer constructor had direct access to file, so this caused no other changes --- framework/src/org/apache/cordova/AudioPlayer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/framework/src/org/apache/cordova/AudioPlayer.java b/framework/src/org/apache/cordova/AudioPlayer.java index e975afaf..6437e2d0 100755 --- a/framework/src/org/apache/cordova/AudioPlayer.java +++ b/framework/src/org/apache/cordova/AudioPlayer.java @@ -74,7 +74,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On private String tempFile = null; // Temporary recording file name private MediaPlayer mPlayer = null; // Audio player object - private boolean prepareOnly = false; + private boolean prepareOnly = false; /** * Constructor. @@ -82,9 +82,10 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * @param handler The audio handler object * @param id The id of this audio player */ - public AudioPlayer(AudioHandler handler, String id) { + public AudioPlayer(AudioHandler handler, String id, String src) { this.handler = handler; this.id = id; + this.audioFile = src; if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { this.tempFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording.mp3"; } else { From c8bf2f4cb13adc9abeb15b97f41aa3f2137fe059 Mon Sep 17 00:00:00 2001 From: Lorin Beer Date: Sun, 17 Jun 2012 22:37:12 -0700 Subject: [PATCH 03/11] removed audio load code from startPlaying to a private function --- .../src/org/apache/cordova/AudioPlayer.java | 75 ++++++++++--------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/framework/src/org/apache/cordova/AudioPlayer.java b/framework/src/org/apache/cordova/AudioPlayer.java index 6437e2d0..7fc1b938 100755 --- a/framework/src/org/apache/cordova/AudioPlayer.java +++ b/framework/src/org/apache/cordova/AudioPlayer.java @@ -207,40 +207,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On 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.setState(MEDIA_STARTING); - this.mPlayer.setOnPreparedListener(this); - 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.getActivity().getAssets().openFd(f); - this.mPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength()); - } - else { - File fp = new File(file); - if (fp.exists()) { - FileInputStream fileInputStream = new FileInputStream(file); - this.mPlayer.setDataSource(fileInputStream.getFD()); - } - else { - this.mPlayer.setDataSource("/sdcard/" + file); - } - } - this.setState(MEDIA_STARTING); - this.mPlayer.setOnPreparedListener(this); - this.mPlayer.prepare(); - - // Get duration - this.duration = getDurationInSeconds(); - } + this.loadAudioFile(file); } catch (Exception e) { e.printStackTrace(); this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); @@ -462,4 +429,44 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On public void setVolume(float volume) { this.mPlayer.setVolume(volume, volume); } + + /** + * load audio file + * @throws IOException + * @throws IllegalStateException + * @throws SecurityException + * @throws IllegalArgumentException + */ + private void loadAudioFile(String file) throws IllegalArgumentException, SecurityException, IllegalStateException, IOException { + if (this.isStreaming(file)) { + this.mPlayer.setDataSource(file); + this.mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + this.setState(MEDIA_STARTING); + this.mPlayer.setOnPreparedListener(this); + this.mPlayer.prepareAsync(); + } + else { + if (file.startsWith("/android_asset/")) { + String f = file.substring(15); + android.content.res.AssetFileDescriptor fd = this.handler.ctx.getActivity().getAssets().openFd(f); + this.mPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength()); + } + else { + File fp = new File(file); + if (fp.exists()) { + FileInputStream fileInputStream = new FileInputStream(file); + this.mPlayer.setDataSource(fileInputStream.getFD()); + } + else { + this.mPlayer.setDataSource("/sdcard/" + file); + } + } + this.setState(MEDIA_STARTING); + this.mPlayer.setOnPreparedListener(this); + this.mPlayer.prepare(); + + // Get duration + this.duration = getDurationInSeconds(); + } + } } From fc3f1431b22e0b718fff3d2221a31f9404206602 Mon Sep 17 00:00:00 2001 From: Lorin Beer Date: Sun, 17 Jun 2012 22:56:22 -0700 Subject: [PATCH 04/11] made internal status static variables final as well, specifically so that they can be used in switch statements --- framework/src/org/apache/cordova/AudioPlayer.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/framework/src/org/apache/cordova/AudioPlayer.java b/framework/src/org/apache/cordova/AudioPlayer.java index 7fc1b938..b1701d74 100755 --- a/framework/src/org/apache/cordova/AudioPlayer.java +++ b/framework/src/org/apache/cordova/AudioPlayer.java @@ -45,11 +45,11 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On private static final String LOG_TAG = "AudioPlayer"; // AudioPlayer states - public static int MEDIA_NONE = 0; - public static int MEDIA_STARTING = 1; - public static int MEDIA_RUNNING = 2; - public static int MEDIA_PAUSED = 3; - public static int MEDIA_STOPPED = 4; + public static final int MEDIA_NONE = 0; + public static final int MEDIA_STARTING = 1; + public static final int MEDIA_RUNNING = 2; + public static final int MEDIA_PAUSED = 3; + public static final int MEDIA_STOPPED = 4; // AudioPlayer message ids private static int MEDIA_STATE = 1; From e5b9900d3b87d7b2fd9a9411234bcba8a52d1876 Mon Sep 17 00:00:00 2001 From: Lorin Beer Date: Sun, 17 Jun 2012 23:59:13 -0700 Subject: [PATCH 05/11] halfway through refactor --- .../src/org/apache/cordova/AudioPlayer.java | 90 +++++++++++-------- 1 file changed, 52 insertions(+), 38 deletions(-) diff --git a/framework/src/org/apache/cordova/AudioPlayer.java b/framework/src/org/apache/cordova/AudioPlayer.java index b1701d74..e09a3da6 100755 --- a/framework/src/org/apache/cordova/AudioPlayer.java +++ b/framework/src/org/apache/cordova/AudioPlayer.java @@ -45,6 +45,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On private static final String LOG_TAG = "AudioPlayer"; // AudioPlayer states +// public static final int public static final int MEDIA_NONE = 0; public static final int MEDIA_STARTING = 1; public static final int MEDIA_RUNNING = 2; @@ -190,50 +191,17 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * @param file The name of the audio file. */ public void startPlaying(String file) { - if (this.recorder != null) { - Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode."); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); - } - - // If this is a new request to play audio, or stopped - else if ((this.mPlayer == null) || (this.state == MEDIA_STOPPED)) { - try { - // If stopped, then reset player - if (this.mPlayer != null) { - this.mPlayer.reset(); - } - // Otherwise, create a new one - else { - this.mPlayer = new MediaPlayer(); - } - this.audioFile = file; - this.loadAudioFile(file); - } catch (Exception e) { - e.printStackTrace(); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); - } - } - - // 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.setState(MEDIA_RUNNING); - } - else { - Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: " + this.state); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); - } - } + if (this.readyPlayer(file)) { + this.mPlayer.start(); + this.setState(MEDIA_RUNNING); + } } /** * Seek or jump to a new time in the track. */ public void seekToPlaying(int milliseconds) { - if (this.mPlayer != null) { + if (this.mPlayer != null) { this.mPlayer.seekTo(milliseconds); Log.d(LOG_TAG, "Send a onStatus update for the new seek"); this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + milliseconds / 1000.0f + ");"); @@ -430,6 +398,52 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On this.mPlayer.setVolume(volume, volume); } + /** + * attempts to initialize the media player for playback + * @param file the file to play + * @return false if player not ready, reports if in wrong mode or state + */ + private boolean readyPlayer(String file) { + //make sure we are in the right mode + if (this.recorder != null) { + Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode."); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); + return false; //player is not ready + } + + if (this.mPlayer == null) { + this.mPlayer = new MediaPlayer(); + try { + this.prepareOnly = false; + this.loadAudioFile(file); + } catch (Exception e) { + e.printStackTrace(); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); + } + return false; + } + + try { + switch (this.state) { + case MEDIA_STOPPED: + this.mPlayer.reset(); + this.loadAudioFile(file); + return true; + case MEDIA_PAUSED: + return true; + case MEDIA_STARTING: + return true; + default: + Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: " + this.state); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); + } + } catch (Exception e) { + e.printStackTrace(); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); + } + return false; + } + /** * load audio file * @throws IOException From d9e79842790ca1c5fa75bbfff534d2bf079cf815 Mon Sep 17 00:00:00 2001 From: Lorin Beer Date: Mon, 18 Jun 2012 10:29:56 -0700 Subject: [PATCH 06/11] fixed seek behaviour, but introduces a bunch of new problems --- .../src/org/apache/cordova/AudioPlayer.java | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/framework/src/org/apache/cordova/AudioPlayer.java b/framework/src/org/apache/cordova/AudioPlayer.java index e09a3da6..7208f1e1 100755 --- a/framework/src/org/apache/cordova/AudioPlayer.java +++ b/framework/src/org/apache/cordova/AudioPlayer.java @@ -45,7 +45,6 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On private static final String LOG_TAG = "AudioPlayer"; // AudioPlayer states -// public static final int public static final int MEDIA_NONE = 0; public static final int MEDIA_STARTING = 1; public static final int MEDIA_RUNNING = 2; @@ -75,7 +74,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On private String tempFile = null; // Temporary recording file name private MediaPlayer mPlayer = null; // Audio player object - private boolean prepareOnly = false; + private boolean prepareOnly = true; /** * Constructor. @@ -83,10 +82,17 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * @param handler The audio handler object * @param id The id of this audio player */ - public AudioPlayer(AudioHandler handler, String id, String src) { + public AudioPlayer(AudioHandler handler, String id, String file) { this.handler = handler; this.id = id; - this.audioFile = src; + this.audioFile = file; + this.mPlayer = new MediaPlayer(); + try { + this.loadAudioFile(file); + } catch (Exception e) { + e.printStackTrace(); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); + } if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { this.tempFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording.mp3"; } else { @@ -191,9 +197,11 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * @param file The name of the audio file. */ public void startPlaying(String file) { - if (this.readyPlayer(file)) { + if (this.playerReady(file)) { this.mPlayer.start(); this.setState(MEDIA_RUNNING); + } else { + this.prepareOnly = false; } } @@ -201,7 +209,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * Seek or jump to a new time in the track. */ public void seekToPlaying(int milliseconds) { - if (this.mPlayer != null) { + if (this.playerReady(this.audioFile)) { this.mPlayer.seekTo(milliseconds); Log.d(LOG_TAG, "Send a onStatus update for the new seek"); this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + milliseconds / 1000.0f + ");"); @@ -327,11 +335,13 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On // Set player init flag this.setState(MEDIA_RUNNING); + } else { + this.setState(MEDIA_STARTING); } // Save off duration this.duration = getDurationInSeconds(); - this.prepareOnly = false; + this.prepareOnly = true; // Send status notification to JavaScript this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_DURATION + "," + this.duration + ");"); @@ -403,7 +413,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * @param file the file to play * @return false if player not ready, reports if in wrong mode or state */ - private boolean readyPlayer(String file) { + private boolean playerReady(String file) { //make sure we are in the right mode if (this.recorder != null) { Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode."); @@ -433,6 +443,8 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On return true; case MEDIA_STARTING: return true; + case MEDIA_RUNNING: + return true; default: Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: " + this.state); this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); @@ -455,7 +467,9 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On if (this.isStreaming(file)) { this.mPlayer.setDataSource(file); this.mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); - this.setState(MEDIA_STARTING); + //altered meaning of states here + //this.setState(MEDIA_STARTING); + this.setState(MEDIA_NONE); this.mPlayer.setOnPreparedListener(this); this.mPlayer.prepareAsync(); } @@ -475,7 +489,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On this.mPlayer.setDataSource("/sdcard/" + file); } } - this.setState(MEDIA_STARTING); + //this.setState(MEDIA_STARTING); this.mPlayer.setOnPreparedListener(this); this.mPlayer.prepare(); From 3d5e2340ca36c28899f3cf74d360500cb64016a0 Mon Sep 17 00:00:00 2001 From: Lorin Beer Date: Thu, 28 Jun 2012 15:29:23 -0700 Subject: [PATCH 07/11] update to use ordinal instead of enum value --- framework/src/org/apache/cordova/AudioHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 framework/src/org/apache/cordova/AudioHandler.java diff --git a/framework/src/org/apache/cordova/AudioHandler.java b/framework/src/org/apache/cordova/AudioHandler.java old mode 100755 new mode 100644 index 9da04f09..7b42959d --- a/framework/src/org/apache/cordova/AudioHandler.java +++ b/framework/src/org/apache/cordova/AudioHandler.java @@ -155,7 +155,7 @@ public class AudioHandler extends Plugin { // Get all audio players and pause them for (AudioPlayer audio : this.players.values()) { - if (audio.getState() == AudioPlayer.MEDIA_RUNNING) { + if (audio.getState() == AudioPlayer.STATE.MEDIA_RUNNING.ordinal()) { this.pausedForPhone.add(audio); audio.pausePlaying(); } From 0cf9f51816634de38ed5656b3344986bda666524 Mon Sep 17 00:00:00 2001 From: Lorin Beer Date: Thu, 28 Jun 2012 15:36:28 -0700 Subject: [PATCH 08/11] use enums to track internal states instead of int. Fixed 'unknown state' bug with the addition of loading state. Mega commit, lost some history. --- .../src/org/apache/cordova/AudioPlayer.java | 363 ++++++++++-------- 1 file changed, 203 insertions(+), 160 deletions(-) mode change 100755 => 100644 framework/src/org/apache/cordova/AudioPlayer.java diff --git a/framework/src/org/apache/cordova/AudioPlayer.java b/framework/src/org/apache/cordova/AudioPlayer.java old mode 100755 new mode 100644 index 7208f1e1..3d520a2d --- a/framework/src/org/apache/cordova/AudioPlayer.java +++ b/framework/src/org/apache/cordova/AudioPlayer.java @@ -31,6 +31,8 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import org.apache.cordova.AudioPlayer.MODE; + /** * This class implements the audio playback and recording capabilities used by Cordova. * It is called by the AudioHandler Cordova class. @@ -41,16 +43,21 @@ import java.io.IOException; * sdcard: file name is just sound.mp3 */ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, OnErrorListener { - + + // AudioPlayer modes + public enum MODE { NONE, PLAY, RECORD }; + + // AudioPlayer states + public enum STATE { MEDIA_NONE, + MEDIA_LOADING, + MEDIA_STARTING, + MEDIA_RUNNING, + MEDIA_PAUSED, + MEDIA_STOPPED + }; + private static final String LOG_TAG = "AudioPlayer"; - // AudioPlayer states - public static final int MEDIA_NONE = 0; - public static final int MEDIA_STARTING = 1; - public static final int MEDIA_RUNNING = 2; - public static final int MEDIA_PAUSED = 3; - public static final int MEDIA_STOPPED = 4; - // AudioPlayer message ids private static int MEDIA_STATE = 1; private static int MEDIA_DURATION = 2; @@ -64,17 +71,20 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On private static int MEDIA_ERR_DECODE = 3; private static int MEDIA_ERR_NONE_SUPPORTED = 4; - 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 float duration = -1; // Duration of audio + private AudioHandler handler; // The AudioHandler object + private String id; // The id of this player (used to identify Media object in JavaScript) + private MODE mode = MODE.NONE; // Playback or Recording mode + private STATE state = STATE.MEDIA_NONE; // State of recording or playback - private MediaRecorder recorder = null; // Audio recording object - private String tempFile = null; // Temporary recording file name + private String audioFile = null; // File name to play or record to + private float 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 = true; + private MediaPlayer player = null; // Audio player object + private boolean prepareOnly = true; // playback after file prepare flag + private int seekOnPrepared = 0; // seek to this location once media is prepared /** * Constructor. @@ -85,19 +95,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On public AudioPlayer(AudioHandler handler, String id, String file) { this.handler = handler; this.id = id; - this.audioFile = file; - this.mPlayer = new MediaPlayer(); - try { - this.loadAudioFile(file); - } catch (Exception e) { - e.printStackTrace(); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); - } - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - this.tempFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording.mp3"; - } else { - this.tempFile = "/data/data/" + handler.ctx.getActivity().getPackageName() + "/cache/tmprecording.mp3"; - } + this.audioFile = file; } /** @@ -105,13 +103,13 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On */ public void destroy() { // Stop any play or record - if (this.mPlayer != null) { - if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) { - this.mPlayer.stop(); - this.setState(MEDIA_STOPPED); + if (this.player != null) { + if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) { + this.player.stop(); + this.setState(STATE.MEDIA_STOPPED); } - this.mPlayer.release(); - this.mPlayer = null; + this.player.release(); + this.player = null; } if (this.recorder != null) { this.stopRecording(); @@ -126,34 +124,37 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * @param file The name of the file */ public void startRecording(String file) { - if (this.mPlayer != null) { - Log.d(LOG_TAG, "AudioPlayer Error: Can't record in play mode."); + switch (this.mode) { + case PLAY: + Log.d(LOG_TAG, "AudioPlayer Error: Can't record in play mode."); this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});"); - } - // 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 { - this.recorder.prepare(); - this.recorder.start(); - this.setState(MEDIA_RUNNING); - return; - } catch (IllegalStateException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});"); - } - else { - Log.d(LOG_TAG, "AudioPlayer Error: Already recording."); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});"); - } + break; + case NONE: + // Make sure we're not already recording + 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 { + this.recorder.prepare(); + this.recorder.start(); + this.setState(STATE.MEDIA_RUNNING); + return; + } catch (IllegalStateException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});"); + } + break; + case RECORD: + Log.d(LOG_TAG, "AudioPlayer Error: Already recording."); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});"); + } } /** @@ -179,9 +180,9 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On public void stopRecording() { if (this.recorder != null) { try{ - if (this.state == MEDIA_RUNNING) { + if (this.state == STATE.MEDIA_RUNNING) { this.recorder.stop(); - this.setState(MEDIA_STOPPED); + this.setState(STATE.MEDIA_STOPPED); } this.moveFile(this.audioFile); } @@ -191,16 +192,21 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On } } + //========================================================================== + // Playback + //========================================================================== + /** * Start or resume playing audio file. * * @param file The name of the audio file. */ public void startPlaying(String file) { - if (this.playerReady(file)) { - this.mPlayer.start(); - this.setState(MEDIA_RUNNING); + if (this.readyPlayer(file)) { + this.player.start(); + this.setState(STATE.MEDIA_RUNNING); } else { + // this.prepareOnly = false; } } @@ -209,11 +215,14 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * Seek or jump to a new time in the track. */ public void seekToPlaying(int milliseconds) { - if (this.playerReady(this.audioFile)) { - this.mPlayer.seekTo(milliseconds); + if (this.readyPlayer(this.audioFile)) { + this.player.seekTo(milliseconds); Log.d(LOG_TAG, "Send a onStatus update for the new seek"); this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + milliseconds / 1000.0f + ");"); } + else { + this.seekOnPrepared = milliseconds; + } } /** @@ -222,12 +231,12 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On public void pausePlaying() { // If playing, then pause - if (this.state == MEDIA_RUNNING) { - this.mPlayer.pause(); - this.setState(MEDIA_PAUSED); + if (this.state == STATE.MEDIA_RUNNING) { + this.player.pause(); + this.setState(STATE.MEDIA_PAUSED); } else { - Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: " + this.state); + Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: " + this.state.ordinal()); this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});"); } } @@ -236,12 +245,12 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * Stop playing the audio file. */ public void stopPlaying() { - if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) { - this.mPlayer.stop(); - this.setState(MEDIA_STOPPED); + if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) { + this.player.stop(); + this.setState(STATE.MEDIA_STOPPED); } else { - Log.d(LOG_TAG, "AudioPlayer Error: stopPlaying() called during invalid state: " + this.state); + Log.d(LOG_TAG, "AudioPlayer Error: stopPlaying() called during invalid state: " + this.state.ordinal()); this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});"); } } @@ -249,10 +258,10 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On /** * Callback to be invoked when playback of a media source has completed. * - * @param mPlayer The MediaPlayer that reached the end of the file + * @param player The MediaPlayer that reached the end of the file */ - public void onCompletion(MediaPlayer mPlayer) { - this.setState(MEDIA_STOPPED); + public void onCompletion(MediaPlayer player) { + this.setState(STATE.MEDIA_STOPPED); } /** @@ -261,8 +270,8 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * @return position in msec or -1 if not playing */ public long getCurrentPosition() { - if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) { - int curPos = this.mPlayer.getCurrentPosition(); + if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) { + int curPos = this.player.getCurrentPosition(); this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + curPos / 1000.0f + ");"); return curPos; } @@ -303,7 +312,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On } // If audio file already loaded and started, then return duration - if (this.mPlayer != null) { + if (this.player != null) { return this.duration; } @@ -321,31 +330,28 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On /** * Callback to be invoked when the media source is ready for playback. * - * @param mPlayer The MediaPlayer that is ready for playback + * @param player The MediaPlayer that is ready for playback */ - public void onPrepared(MediaPlayer mPlayer) { + public void onPrepared(MediaPlayer player) { // Listen for playback completion - this.mPlayer.setOnCompletionListener(this); - + this.player.setOnCompletionListener(this); // If start playing after prepared if (!this.prepareOnly) { - - // Start playing - this.mPlayer.start(); - - // Set player init flag - this.setState(MEDIA_RUNNING); + this.player.start(); + this.setState(STATE.MEDIA_RUNNING); } else { - this.setState(MEDIA_STARTING); + this.setState(STATE.MEDIA_STARTING); } - // Save off duration this.duration = getDurationInSeconds(); + // reset prepare only flag this.prepareOnly = true; - + // seek to any location received while not prepared + this.seekToPlaying(this.seekOnPrepared); + // reset seek location + this.seekOnPrepared = 0; // Send status notification to JavaScript this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_DURATION + "," + this.duration + ");"); - } /** @@ -354,23 +360,23 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * @return length of clip in seconds */ private float getDurationInSeconds() { - return (this.mPlayer.getDuration() / 1000.0f); + return (this.player.getDuration() / 1000.0f); } /** * 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 player 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) { + public boolean onError(MediaPlayer player, int arg1, int arg2) { Log.d(LOG_TAG, "AudioPlayer.onError(" + arg1 + ", " + arg2 + ")"); // TODO: Not sure if this needs to be sent? - this.mPlayer.stop(); - this.mPlayer.release(); + this.player.stop(); + this.player.release(); // Send error notification to JavaScript this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', { \"code\":" + arg1 + "});"); @@ -382,21 +388,33 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * * @param state */ - private void setState(int state) { + private void setState(STATE state) { if (this.state != state) { - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + state + ");"); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + this.state.ordinal() + ");"); } - this.state = state; } + /** + * Set the mode and send it to JavaScript. + * + * @param state + */ + private void setMode(MODE mode) { + if (this.mode != mode) { + //mode is not part of the expected behaviour, so no notification + //this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + mode + ");"); + } + this.mode = mode; + } + /** * Get the audio state. * * @return int */ public int getState() { - return this.state; + return this.state.ordinal(); } /** @@ -405,7 +423,26 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * @param volume */ public void setVolume(float volume) { - this.mPlayer.setVolume(volume, volume); + this.player.setVolume(volume, volume); + } + + /** + * attempts to put the player in play mode + * @return true if in playmode, false otherwise + */ + private boolean playMode() { + switch(this.mode) { + case NONE: + this.setMode(MODE.PLAY); + break; + case PLAY: + break; + case RECORD: + Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode."); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); + return false; //player is not ready + } + return true; } /** @@ -413,46 +450,52 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * @param file the file to play * @return false if player not ready, reports if in wrong mode or state */ - private boolean playerReady(String file) { - //make sure we are in the right mode - if (this.recorder != null) { - Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode."); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); - return false; //player is not ready - } - - if (this.mPlayer == null) { - this.mPlayer = new MediaPlayer(); - try { - this.prepareOnly = false; - this.loadAudioFile(file); - } catch (Exception e) { - e.printStackTrace(); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); - } - return false; + private boolean readyPlayer(String file) { + if (playMode()) { + switch (this.state) { + case MEDIA_NONE: + if (this.player == null) { + this.player = new MediaPlayer(); + } + try { + this.loadAudioFile(file); + } catch (Exception e) { + e.printStackTrace(); + } + return false; + case MEDIA_LOADING: + //cordova js is not aware of MEDIA_LOADING, so we send MEDIA_STARTING insntead + Log.d(LOG_TAG, "AudioPlayer Loading: startPlaying() called during media preparation: " + STATE.MEDIA_STARTING.ordinal()); + this.prepareOnly = false; + return false; + case MEDIA_STARTING: + case MEDIA_RUNNING: + case MEDIA_PAUSED: + return true; + case MEDIA_STOPPED: + //if we are readying the same file + if (this.audioFile.compareTo(file) == 0) { + //reset the audio file + player.seekTo(0); + player.pause(); + return true; + } else { + //reset the player + this.player.reset(); + try { + this.loadAudioFile(file); + } catch (Exception e) { + e.printStackTrace(); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); + } + //if we had to prepare= the file, we won't be in the correct state for playback + return false; + } + default: + Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: " + this.state); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); + } } - - try { - switch (this.state) { - case MEDIA_STOPPED: - this.mPlayer.reset(); - this.loadAudioFile(file); - return true; - case MEDIA_PAUSED: - return true; - case MEDIA_STARTING: - return true; - case MEDIA_RUNNING: - return true; - default: - Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: " + this.state); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); - } - } catch (Exception e) { - e.printStackTrace(); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); - } return false; } @@ -465,33 +508,33 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On */ private void loadAudioFile(String file) throws IllegalArgumentException, SecurityException, IllegalStateException, IOException { if (this.isStreaming(file)) { - this.mPlayer.setDataSource(file); - this.mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); - //altered meaning of states here - //this.setState(MEDIA_STARTING); - this.setState(MEDIA_NONE); - this.mPlayer.setOnPreparedListener(this); - this.mPlayer.prepareAsync(); + this.player.setDataSource(file); + this.player.setAudioStreamType(AudioManager.STREAM_MUSIC); + //if it's a streaming file, play mode is implied + this.setMode(MODE.PLAY); + this.setState(STATE.MEDIA_STARTING); + this.player.setOnPreparedListener(this); + this.player.prepareAsync(); } else { if (file.startsWith("/android_asset/")) { String f = file.substring(15); android.content.res.AssetFileDescriptor fd = this.handler.ctx.getActivity().getAssets().openFd(f); - this.mPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength()); + this.player.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength()); } else { File fp = new File(file); if (fp.exists()) { FileInputStream fileInputStream = new FileInputStream(file); - this.mPlayer.setDataSource(fileInputStream.getFD()); + this.player.setDataSource(fileInputStream.getFD()); } else { - this.mPlayer.setDataSource("/sdcard/" + file); + this.player.setDataSource("/sdcard/" + file); } } - //this.setState(MEDIA_STARTING); - this.mPlayer.setOnPreparedListener(this); - this.mPlayer.prepare(); + this.setState(STATE.MEDIA_STARTING); + this.player.setOnPreparedListener(this); + this.player.prepare(); // Get duration this.duration = getDurationInSeconds(); From 10465066ee32bcd94f7d31f72924fb09ebfdd215 Mon Sep 17 00:00:00 2001 From: Lorin Beer Date: Thu, 28 Jun 2012 15:37:11 -0700 Subject: [PATCH 09/11] Merge branches 'master' and 'dev' From 762854ad7adb7fe53d1fc63282f2742862a6ab4a Mon Sep 17 00:00:00 2001 From: Lorin Beer Date: Thu, 28 Jun 2012 15:53:47 -0700 Subject: [PATCH 10/11] changed handling of stopRecording to reflect handling of create message --- framework/src/org/apache/cordova/AudioHandler.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/framework/src/org/apache/cordova/AudioHandler.java b/framework/src/org/apache/cordova/AudioHandler.java index 7b42959d..c4e3063f 100644 --- a/framework/src/org/apache/cordova/AudioHandler.java +++ b/framework/src/org/apache/cordova/AudioHandler.java @@ -198,13 +198,12 @@ public class AudioHandler extends Plugin { * @param file The name of the file */ public void startRecordingAudio(String id, String file) { - // If already recording, then just return; - if (this.players.containsKey(id)) { - return; - } - AudioPlayer audio = new AudioPlayer(this, id, file); - this.players.put(id, audio); - audio.startRecording(file); + AudioPlayer audio = this.players.get(id); + if ( audio == null) { + audio = new AudioPlayer(this, id, file); + this.players.put(id, audio); + } + audio.startRecording(file); } /** From 3ea72e5d212cf6911cd0465d65f1edab3617e650 Mon Sep 17 00:00:00 2001 From: Lorin Beer Date: Thu, 28 Jun 2012 16:17:00 -0700 Subject: [PATCH 11/11] added deleted tempfile setup --- framework/src/org/apache/cordova/AudioPlayer.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/framework/src/org/apache/cordova/AudioPlayer.java b/framework/src/org/apache/cordova/AudioPlayer.java index 3d520a2d..14645b24 100644 --- a/framework/src/org/apache/cordova/AudioPlayer.java +++ b/framework/src/org/apache/cordova/AudioPlayer.java @@ -96,6 +96,13 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On this.handler = handler; this.id = id; this.audioFile = file; + + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + this.tempFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording.mp3"; + } else { + this.tempFile = "/data/data/" + handler.ctx.getActivity().getPackageName() + "/cache/tmprecording.mp3"; + } + } /**