From 90b708fe832a3c465d2d894898dffbe638b192ac Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Tue, 31 Aug 2010 15:39:37 -0500 Subject: [PATCH] Update audio playback and recording. --- framework/assets/js/media.js | 170 +++++-- framework/src/com/phonegap/AudioHandler.java | 209 ++++++-- framework/src/com/phonegap/AudioPlayer.java | 473 +++++++++++++------ framework/src/com/phonegap/DroidGap.java | 2 +- 4 files changed, 637 insertions(+), 217 deletions(-) mode change 100644 => 100755 framework/assets/js/media.js mode change 100644 => 100755 framework/src/com/phonegap/AudioHandler.java mode change 100644 => 100755 framework/src/com/phonegap/AudioPlayer.java diff --git a/framework/assets/js/media.js b/framework/assets/js/media.js old mode 100644 new mode 100755 index 17e0db73..2a1fc2fe --- a/framework/assets/js/media.js +++ b/framework/assets/js/media.js @@ -1,62 +1,152 @@ +/** + * List of media objects. + */ +PhoneGap.mediaObjects = {}; + +PhoneGap.Media = function() {}; + +/** + * Get the media object. + * PRIVATE + * + * @param id The media object id (string) + */ +PhoneGap.Media.getMediaObject = function(id) { + return PhoneGap.mediaObjects[id]; +}; + +/** + * Audio has status update. + * PRIVATE + * + * @param id The media object id (string) + * @param status The status code (int) + * @param msg The status message (string) + */ +PhoneGap.Media.onStatus = function(id, msg, value) { + var media = PhoneGap.mediaObjects[id]; + + // If state update + if (msg == Media.MEDIA_STATE) { + if (value == Media.MEDIA_STOPPED) { + if (media.successCallback) { + media.successCallback(); + } + } + if (media.statusCallback) { + media.statusCallback(value); + } + } + else if (msg == Media.MEDIA_DURATION) { + media._duration = value; + } + else if (msg == Media.MEDIA_ERROR) { + if (media.errorCallback) { + media.errorCallback(value); + } + } +}; + /** * This class provides access to the device media, interfaces to both sound and video - * @constructor + * + * @param src The file name or url to play + * @param successCallback The callback to be called when the file is done playing or recording. + * successCallback() + * @param errorCallback The callback to be called if there is an error. + * errorCallback(int errorCode) + * @param statusCallback The callback to be called when media status has changed. + * statusCallback(int statusCode) */ -function Media(src, successCallback, errorCallback) { - this.src = src; - this.successCallback = successCallback; - this.errorCallback = errorCallback; -} +Media = function(src, successCallback, errorCallback, statusCallback) { + this.id = PhoneGap.createUUID(); + PhoneGap.mediaObjects[this.id] = this; + this.src = src; + this.successCallback = successCallback; + this.errorCallback = errorCallback; + this.statusCallback = statusCallback; + this._duration = -1; +}; -Media.prototype.record = function() { -} - -Media.prototype.play = function() { -} - -Media.prototype.pause = function() { -} - -Media.prototype.stop = function() { -} +// Media messages +Media.MEDIA_STATE = 1; +Media.MEDIA_DURATION = 2; +Media.MEDIA_ERROR = 3; +// Media states +Media.MEDIA_NONE = 0; +Media.MEDIA_STARTING = 1; +Media.MEDIA_RUNNING = 2; +Media.MEDIA_PAUSED = 3; +Media.MEDIA_STOPPED = 4; +Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"]; +// TODO: Will MediaError be used? /** * This class contains information about any Media errors. * @constructor */ function MediaError() { - this.code = null, - this.message = ""; -} + this.code = null, + this.message = ""; +}; -MediaError.MEDIA_ERR_ABORTED = 1; -MediaError.MEDIA_ERR_NETWORK = 2; -MediaError.MEDIA_ERR_DECODE = 3; +MediaError.MEDIA_ERR_ABORTED = 1; +MediaError.MEDIA_ERR_NETWORK = 2; +MediaError.MEDIA_ERR_DECODE = 3; MediaError.MEDIA_ERR_NONE_SUPPORTED = 4; - -//if (typeof navigator.audio == "undefined") navigator.audio = new Media(src); +/** + * Start or resume playing audio file. + */ +Media.prototype.play = function() { + GapAudio.startPlayingAudio(this.id, this.src); +}; /** - * This class provides access to the device media, interfaces to both sound and video - * @constructor + * Stop playing audio file. */ - -Media.prototype.play = function() { - GapAudio.startPlayingAudio(this.src); -} - Media.prototype.stop = function() { - GapAudio.stopPlayingAudio(); -} + GapAudio.stopPlayingAudio(this.id); +}; +/** + * Pause playing audio file. + */ +Media.prototype.pause = function() { + GapAudio.pausePlayingAudio(this.id); +}; + +/** + * Get duration of an audio file. + * The duration is only set for audio that is playing, paused or stopped. + * + * @return duration or -1 if not known. + */ +Media.prototype.getDuration = function() { + return this._duration; +}; + +/** + * Get position of audio. + * + * @return + */ +Media.prototype.getCurrentPosition = function() { + return GapAudio.getCurrentPositionAudio(this.id); +}; + +/** + * Start recording audio file. + */ Media.prototype.startRecord = function() { - GapAudio.startRecordingAudio(this.src); -} - -Media.prototype.stopRecordingAudio = function() { - GapAudio.stopRecordingAudio(); -} + GapAudio.startRecordingAudio(this.id, this.src); +}; +/** + * Stop recording audio file. + */ +Media.prototype.stopRecord = function() { + GapAudio.stopRecordingAudio(this.id); +}; diff --git a/framework/src/com/phonegap/AudioHandler.java b/framework/src/com/phonegap/AudioHandler.java old mode 100644 new mode 100755 index 349dea2b..55e29bf5 --- a/framework/src/com/phonegap/AudioHandler.java +++ b/framework/src/com/phonegap/AudioHandler.java @@ -1,68 +1,195 @@ package com.phonegap; +import java.util.HashMap; +import java.util.Map.Entry; + import android.content.Context; +import android.media.AudioManager; import android.webkit.WebView; +/** + * This class called by DroidGap to play and record audio. + * The file can be local or over a network using http. + * + * Audio formats supported (tested): + * .mp3, .wav + * + * Local audio files must reside in one of two places: + * android_asset: file name must start with /android_asset/sound.mp3 + * sdcard: file name is just sound.mp3 + */ public class AudioHandler { - AudioPlayer audio; - WebView mAppView; - Context mCtx; + HashMap players; // Audio player object + WebView mAppView; // Webview object + DroidGap mCtx; // DroidGap object - AudioHandler(WebView view, Context ctx) - { - mAppView = view; - mCtx = ctx; - // 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 - audio = new AudioPlayer("/sdcard/tmprecording.mp3", mCtx); + /** + * Constructor. + * + * @param view + * @param ctx + */ + AudioHandler(WebView view, DroidGap ctx) { + this.mAppView = view; + this.mCtx = ctx; + this.players = new HashMap(); } /** - * AUDIO - * TODO: Basic functions done but needs more work on error handling and call backs, remove record hack - */ - - public void startRecordingAudio(String file) - { - /* for this to work the recording needs to be specified in the constructor, - * a hack to get around this, I'm moving the recording after it's complete - */ + * Stop all audio players and recorders. + */ + public void destroy() { + java.util.Set> s = this.players.entrySet(); + java.util.Iterator> it = s.iterator(); + while(it.hasNext()) { + Entry entry = it.next(); + AudioPlayer audio = entry.getValue(); + audio.destroy(); + } + this.players.clear(); + } + + /** + * Start recording and save the specified file. + * + * @param id The id of the audio player + * @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); + this.players.put(id, audio); audio.startRecording(file); } - - public void stopRecordingAudio() - { - audio.stopRecording(); + + /** + * Stop recording and save to the file specified when recording started. + * + * @param id The id of the audio player + */ + public void stopRecordingAudio(String id) { + AudioPlayer audio = this.players.get(id); + if (audio != null) { + audio.stopRecording(); + this.players.remove(id); + } } - public void startPlayingAudio(String file) - { + /** + * Start or resume playing audio file. + * + * @param id The id of the audio player + * @param file The name of the audio file. + */ + public void startPlayingAudio(String id, String file) { + AudioPlayer audio = this.players.get(id); + if (audio == null) { + audio = new AudioPlayer(this, id); + this.players.put(id, audio); + } audio.startPlaying(file); } - - public void stopPlayingAudio() - { - audio.stopPlaying(); + + /** + * Pause playing. + * + * @param id The id of the audio player + */ + public void pausePlayingAudio(String id) { + AudioPlayer audio = this.players.get(id); + if (audio != null) { + audio.pausePlaying(); + } + } + + /** + * Stop playing the audio file. + * + * @param id The id of the audio player + */ + public void stopPlayingAudio(String id) { + AudioPlayer audio = this.players.get(id); + if (audio != null) { + audio.stopPlaying(); + //audio.destroy(); + //this.players.remove(id); + } } - public long getCurrentPositionAudio() - { - System.out.println(audio.getCurrentPosition()); - return(audio.getCurrentPosition()); + /** + * Get current position of playback. + * + * @param id The id of the audio player + * @return position in msec + */ + public long getCurrentPositionAudio(String id) { + AudioPlayer audio = this.players.get(id); + if (audio != null) { + return(audio.getCurrentPosition()); + } + return -1; } - public long getDurationAudio(String file) - { - System.out.println(audio.getDuration(file)); - return(audio.getDuration(file)); + /** + * Get the duration of the audio file. + * + * @param id The id of the audio player + * @param file The name of the audio file. + * @return The duration in msec. + */ + public long getDurationAudio(String id, String file) { + + // Get audio file + AudioPlayer audio = this.players.get(id); + if (audio != null) { + return(audio.getDuration(file)); + } + + // If not already open, then open the file + else { + audio = new AudioPlayer(this, id); + this.players.put(id, audio); + return(audio.getDuration(file)); + } } - public void setAudioOutputDevice(int output){ - audio.setAudioOutputDevice(output); + /** + * Set the audio device to be used for playback. + * + * @param output 1=earpiece, 2=speaker + */ + public void setAudioOutputDevice(int output) { + AudioManager audiMgr = (AudioManager) mCtx.getSystemService(Context.AUDIO_SERVICE); + if (output == 2) { + audiMgr.setRouting(AudioManager.MODE_NORMAL, AudioManager.ROUTE_SPEAKER, AudioManager.ROUTE_ALL); + } + else if (output == 1) { + audiMgr.setRouting(AudioManager.MODE_NORMAL, AudioManager.ROUTE_EARPIECE, AudioManager.ROUTE_ALL); + } + else { + System.out.println("AudioHandler.setAudioOutputDevice() Error: Unknown output device."); + } } - public int getAudioOutputDevice(){ - return audio.getAudioOutputDevice(); + /** + * Get the audio device to be used for playback. + * + * @return 1=earpiece, 2=speaker + */ + public int getAudioOutputDevice() { + AudioManager audiMgr = (AudioManager) mCtx.getSystemService(Context.AUDIO_SERVICE); + if (audiMgr.getRouting(AudioManager.MODE_NORMAL) == AudioManager.ROUTE_EARPIECE) { + return 1; + } + else if (audiMgr.getRouting(AudioManager.MODE_NORMAL) == AudioManager.ROUTE_SPEAKER) { + return 2; + } + else { + return -1; + } } } diff --git a/framework/src/com/phonegap/AudioPlayer.java b/framework/src/com/phonegap/AudioPlayer.java old mode 100644 new mode 100755 index a52262b7..238b4994 --- a/framework/src/com/phonegap/AudioPlayer.java +++ b/framework/src/com/phonegap/AudioPlayer.java @@ -2,192 +2,395 @@ package com.phonegap; import java.io.File; import java.io.IOException; - -import android.content.Context; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnErrorListener; import android.media.MediaRecorder; -import android.media.MediaPlayer.OnBufferingUpdateListener; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnPreparedListener; -import android.util.Log; +/** + * 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 { - private MediaRecorder recorder; - private boolean isRecording = false; - MediaPlayer mPlayer; - private boolean isPlaying = false; - private String recording; - private String saveFile; - private Context mCtx; + + // 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; - public AudioPlayer(String file, Context ctx) { - this.recording = file; - this.mCtx = ctx; + // AudioPlayer message ids + private static int MEDIA_STATE = 1; + private static int MEDIA_DURATION = 2; + private static int MEDIA_ERROR = 3; + + // 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"; } - - protected void startRecording(String file){ - if (!isRecording){ - saveFile=file; - recorder = new MediaRecorder(); - recorder.setAudioSource(MediaRecorder.AudioSource.MIC); - recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); - recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); - recorder.setOutputFile(this.recording); + + /** + * 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.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_PLAY_MODE_SET+");"); + } + + // 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 { - recorder.prepare(); + this.recorder.prepare(); + this.recorder.start(); + this.state = MEDIA_RUNNING; + this.handler.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + return; } catch (IllegalStateException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { - // TODO Auto-generated catch block e.printStackTrace(); } - isRecording = true; - recorder.start(); + this.handler.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_STARTING_RECORDING+");"); + } + else { + System.out.println("AudioPlayer Error: Already recording."); + this.handler.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_ALREADY_RECORDING+");"); } } - private void moveFile(String file) { + /** + * 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 */ - File f = new File (this.recording); - f.renameTo(new File("/sdcard" + file)); + File f = new File(this.tempFile); + f.renameTo(new File("/sdcard/" + file)); } - protected void stopRecording(){ - try{ - if((recorder != null)&&(isRecording)) - { - isRecording = false; - recorder.stop(); - recorder.release(); + /** + * 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.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + } + this.moveFile(this.audioFile); + } + catch (Exception e) { + e.printStackTrace(); } - moveFile(saveFile); - } - catch (Exception e) - { - e.printStackTrace(); } } - protected void startPlaying(String file) { - if (isPlaying==false) { + /** + * 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.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_RECORD_MODE_SET+");"); + } + + // If this is a new request to play audio + else if ((this.mPlayer == null) || (this.state == MEDIA_STOPPED)) { try { - mPlayer = new MediaPlayer(); - isPlaying=true; - Log.d("Audio startPlaying", "audio: " + file); - if (isStreaming(file)) - { - Log.d("AudioStartPlaying", "Streaming"); - // Streaming prepare async - mPlayer.setDataSource(file); - mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); - mPlayer.prepareAsync(); - } else { - Log.d("AudioStartPlaying", "File"); - // Not streaming prepare synchronous, abstract base directory - mPlayer.setDataSource("/sdcard/" + file); - mPlayer.prepare(); + // If stopped, then reset player + if (this.mPlayer != null) { + this.mPlayer.reset(); } - mPlayer.setOnPreparedListener(this); - } catch (Exception e) - { + // 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.mCtx.getBaseContext().getAssets().openFd(f); + 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.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + } + catch (Exception e) { e.printStackTrace(); + this.handler.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_STARTING_PLAYBACK+");"); + } + } + + // 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.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + } + else { + System.out.println("AudioPlayer Error: startPlaying() called during invalid state: "+this.state); + this.handler.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_RESUME_STATE+");"); } } } + /** + * 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.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + } + else { + System.out.println("AudioPlayer Error: pausePlaying() called during invalid state: "+this.state); + this.handler.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_PAUSE_STATE+");"); + } + } + + /** + * Stop playing the audio file. + */ public void stopPlaying() { - if (isPlaying) { - mPlayer.stop(); - mPlayer.release(); - isPlaying=false; + if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) { + this.state = MEDIA_STOPPED; + this.mPlayer.stop(); + + // Send status notification to JavaScript + this.handler.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + } + else { + System.out.println("AudioPlayer Error: stopPlaying() called during invalid state: "+this.state); + this.handler.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERROR_STOP_STATE+");"); } } + /** + * 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) { - mPlayer.stop(); - mPlayer.release(); - isPlaying=false; + this.state = MEDIA_STOPPED; + + // Send status notification to JavaScript + this.handler.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); } - protected long getCurrentPosition() { - if (isPlaying) - { - return(mPlayer.getCurrentPosition()); - } else { return(-1); } + /** + * 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; + } } - private boolean isStreaming(String file) - { + /** + * 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; - } else { + } + else { return false; } } - protected long getDuration(String file) { - long duration = -2; - if (!isPlaying & !isStreaming(file)) { - try { - mPlayer = new MediaPlayer(); - mPlayer.setDataSource("/sdcard/" + file); - mPlayer.prepare(); - duration = mPlayer.getDuration(); - mPlayer.release(); - } catch (Exception e) { e.printStackTrace(); return(-3); } - } else - if (isPlaying & !isStreaming(file)) { - duration = mPlayer.getDuration(); - } else - if (isPlaying & isStreaming(file)) { - try { - duration = mPlayer.getDuration(); - } catch (Exception e) { e.printStackTrace(); return(-4); } - }else { return -1; } - return duration; - } - - public void onPrepared(MediaPlayer mPlayer) { - if (isPlaying) { - mPlayer.setOnCompletionListener(this); - mPlayer.setOnBufferingUpdateListener(new OnBufferingUpdateListener() - { - public void onBufferingUpdate(MediaPlayer mPlayer, int percent) - { - /* TODO: call back, e.g. update outer progress bar */ - Log.d("AudioOnBufferingUpdate", "percent: " + percent); - } - }); - mPlayer.start(); + /** + * 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; } } + /** + * 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) { + // 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.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + } + + // Save off duration + this.duration = this.mPlayer.getDuration(); + this.prepareOnly = false; + + // Send status notification to JavaScript + this.handler.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_DURATION+","+this.duration+");"); + + } + + /** + * 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) { - Log.e("AUDIO onError", "error " + arg1 + " " + arg2); + 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.mCtx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+arg1+");"); return false; - } - - protected void setAudioOutputDevice(int output){ - // Changes the default audio output device to speaker or earpiece - AudioManager audiMgr = (AudioManager) mCtx.getSystemService(Context.AUDIO_SERVICE); - if (output == (2)) - audiMgr.setRouting(AudioManager.MODE_NORMAL, AudioManager.ROUTE_SPEAKER, AudioManager.ROUTE_ALL); - else if (output == (1)){ - audiMgr.setRouting(AudioManager.MODE_NORMAL, AudioManager.ROUTE_EARPIECE, AudioManager.ROUTE_ALL); - }else - Log.e("AudioHandler setAudioOutputDevice", " unknown output device"); - } - - protected int getAudioOutputDevice(){ - AudioManager audiMgr = (AudioManager) mCtx.getSystemService(Context.AUDIO_SERVICE); - if (audiMgr.getRouting(AudioManager.MODE_NORMAL) == AudioManager.ROUTE_EARPIECE) - return 1; - else if (audiMgr.getRouting(AudioManager.MODE_NORMAL) == AudioManager.ROUTE_SPEAKER) - return 2; - else - return -1; - } + } } diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 234889a6..18b249d7 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -261,7 +261,7 @@ public class DroidGap extends Activity { } if (audio != null) { - + audio.destroy(); } if (callbackServer != null) { callbackServer.destroy();