diff options
Diffstat (limited to 'plugins/org.apache.cordova.media/src/android/AudioPlayer.java')
| -rw-r--r-- | plugins/org.apache.cordova.media/src/android/AudioPlayer.java | 587 |
1 files changed, 0 insertions, 587 deletions
diff --git a/plugins/org.apache.cordova.media/src/android/AudioPlayer.java b/plugins/org.apache.cordova.media/src/android/AudioPlayer.java deleted file mode 100644 index 7524c5b6..00000000 --- a/plugins/org.apache.cordova.media/src/android/AudioPlayer.java +++ /dev/null @@ -1,587 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. -*/ -package org.apache.cordova.media; - -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.MediaPlayer.OnCompletionListener; -import android.media.MediaPlayer.OnErrorListener; -import android.media.MediaPlayer.OnPreparedListener; -import android.media.MediaRecorder; -import android.os.Environment; -import android.util.Log; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; - -/** - * This class implements the audio playback and recording capabilities used by Cordova. - * It is called by the AudioHandler Cordova class. - * Only one file can be played or recorded per class instance. - * - * 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 AudioPlayer implements OnCompletionListener, OnPreparedListener, OnErrorListener { - - // AudioPlayer modes - public enum MODE { NONE, PLAY, RECORD }; - - // AudioPlayer states - public enum STATE { MEDIA_NONE, - MEDIA_STARTING, - MEDIA_RUNNING, - MEDIA_PAUSED, - MEDIA_STOPPED, - MEDIA_LOADING - }; - - private static final String LOG_TAG = "AudioPlayer"; - - // AudioPlayer message ids - private static int MEDIA_STATE = 1; - private static int MEDIA_DURATION = 2; - private static int MEDIA_POSITION = 3; - private static int MEDIA_ERROR = 9; - - // Media error codes - private static int MEDIA_ERR_NONE_ACTIVE = 0; - private static int MEDIA_ERR_ABORTED = 1; -// private static int MEDIA_ERR_NETWORK = 2; -// 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 MODE mode = MODE.NONE; // Playback or Recording mode - private STATE state = 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 MediaRecorder recorder = null; // Audio recording object - private String tempFile = null; // Temporary recording file name - - 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. - * - * @param handler The audio handler object - * @param id The id of this audio player - */ - public AudioPlayer(AudioHandler handler, String id, String file) { - this.handler = handler; - this.id = id; - this.audioFile = file; - this.recorder = new MediaRecorder(); - - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - this.tempFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording.3gp"; - } else { - this.tempFile = "/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/tmprecording.3gp"; - } - - } - - /** - * Destroy player and stop audio playing or recording. - */ - public void destroy() { - // Stop any play or record - if (this.player != null) { - if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) { - this.player.stop(); - this.setState(STATE.MEDIA_STOPPED); - } - this.player.release(); - this.player = 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) { - switch (this.mode) { - case PLAY: - Log.d(LOG_TAG, "AudioPlayer Error: Can't record in play mode."); - sendErrorStatus(MEDIA_ERR_ABORTED); - break; - case NONE: - this.audioFile = file; - 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(); - } - - sendErrorStatus(MEDIA_ERR_ABORTED); - break; - case RECORD: - Log.d(LOG_TAG, "AudioPlayer Error: Already recording."); - sendErrorStatus(MEDIA_ERR_ABORTED); - } - } - - /** - * 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.tempFile); - - if (!file.startsWith("/")) { - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - file = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + file; - } else { - file = "/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/" + file; - } - } - - String logMsg = "renaming " + this.tempFile + " to " + file; - Log.d(LOG_TAG, logMsg); - if (!f.renameTo(new File(file))) Log.e(LOG_TAG, "FAILED " + logMsg); - } - - /** - * Stop recording and save to the file specified when recording started. - */ - public void stopRecording() { - if (this.recorder != null) { - try{ - if (this.state == STATE.MEDIA_RUNNING) { - this.recorder.stop(); - this.setState(STATE.MEDIA_STOPPED); - } - this.recorder.reset(); - this.moveFile(this.audioFile); - } - catch (Exception e) { - e.printStackTrace(); - } - } - } - - //========================================================================== - // Playback - //========================================================================== - - /** - * Start or resume playing audio file. - * - * @param file The name of the audio file. - */ - public void startPlaying(String file) { - if (this.readyPlayer(file) && this.player != null) { - this.player.start(); - this.setState(STATE.MEDIA_RUNNING); - this.seekOnPrepared = 0; //insures this is always reset - } else { - this.prepareOnly = false; - } - } - - /** - * Seek or jump to a new time in the track. - */ - public void seekToPlaying(int milliseconds) { - if (this.readyPlayer(this.audioFile)) { - this.player.seekTo(milliseconds); - Log.d(LOG_TAG, "Send a onStatus update for the new seek"); - sendStatusChange(MEDIA_POSITION, null, (milliseconds / 1000.0f)); - } - else { - this.seekOnPrepared = milliseconds; - } - } - - /** - * Pause playing. - */ - public void pausePlaying() { - - // If playing, then pause - if (this.state == STATE.MEDIA_RUNNING && this.player != null) { - this.player.pause(); - this.setState(STATE.MEDIA_PAUSED); - } - else { - Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: " + this.state.ordinal()); - sendErrorStatus(MEDIA_ERR_NONE_ACTIVE); - } - } - - /** - * Stop playing the audio file. - */ - public void stopPlaying() { - if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) { - this.player.pause(); - this.player.seekTo(0); - Log.d(LOG_TAG, "stopPlaying is calling stopped"); - this.setState(STATE.MEDIA_STOPPED); - } - else { - Log.d(LOG_TAG, "AudioPlayer Error: stopPlaying() called during invalid state: " + this.state.ordinal()); - sendErrorStatus(MEDIA_ERR_NONE_ACTIVE); - } - } - - /** - * Callback to be invoked when playback of a media source has completed. - * - * @param player The MediaPlayer that reached the end of the file - */ - public void onCompletion(MediaPlayer player) { - Log.d(LOG_TAG, "on completion is calling stopped"); - this.setState(STATE.MEDIA_STOPPED); - } - - /** - * Get current position of playback. - * - * @return position in msec or -1 if not playing - */ - public long getCurrentPosition() { - if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) { - int curPos = this.player.getCurrentPosition(); - sendStatusChange(MEDIA_POSITION, null, (curPos / 1000.0f)); - return curPos; - } - else { - return -1; - } - } - - /** - * 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://") || file.contains("https://")) { - return true; - } - else { - return false; - } - } - - /** - * 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 float 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.player != 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 player The MediaPlayer that is ready for playback - */ - public void onPrepared(MediaPlayer player) { - // Listen for playback completion - this.player.setOnCompletionListener(this); - // seek to any location received while not prepared - this.seekToPlaying(this.seekOnPrepared); - // If start playing after prepared - if (!this.prepareOnly) { - this.player.start(); - this.setState(STATE.MEDIA_RUNNING); - this.seekOnPrepared = 0; //reset only when played - } else { - this.setState(STATE.MEDIA_STARTING); - } - // Save off duration - this.duration = getDurationInSeconds(); - // reset prepare only flag - this.prepareOnly = true; - - // Send status notification to JavaScript - sendStatusChange(MEDIA_DURATION, null, this.duration); - } - - /** - * By default Android returns the length of audio in mills but we want seconds - * - * @return length of clip in seconds - */ - private float getDurationInSeconds() { - 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 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 player, int arg1, int arg2) { - Log.d(LOG_TAG, "AudioPlayer.onError(" + arg1 + ", " + arg2 + ")"); - - // TODO: Not sure if this needs to be sent? - this.player.stop(); - this.player.release(); - - // Send error notification to JavaScript - sendErrorStatus(arg1); - return false; - } - - /** - * Set the state and send it to JavaScript. - * - * @param state - */ - private void setState(STATE state) { - if (this.state != state) { - sendStatusChange(MEDIA_STATE, null, (float)state.ordinal()); - } - this.state = state; - } - - /** - * Set the mode and send it to JavaScript. - * - * @param mode - */ - private void setMode(MODE mode) { - if (this.mode != mode) { - //mode is not part of the expected behavior, so no notification - //this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + mode + ");"); - } - this.mode = mode; - } - - /** - * Get the audio state. - * - * @return int - */ - public int getState() { - return this.state.ordinal(); - } - - /** - * Set the volume for audio player - * - * @param volume - */ - public void setVolume(float 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."); - sendErrorStatus(MEDIA_ERR_ABORTED); - return false; //player is not ready - } - return true; - } - - /** - * 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) { - if (playMode()) { - switch (this.state) { - case MEDIA_NONE: - if (this.player == null) { - this.player = new MediaPlayer(); - } - try { - this.loadAudioFile(file); - } catch (Exception e) { - sendErrorStatus(MEDIA_ERR_ABORTED); - } - return false; - case MEDIA_LOADING: - //cordova js is not aware of MEDIA_LOADING, so we send MEDIA_STARTING instead - 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) { - sendErrorStatus(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); - sendErrorStatus(MEDIA_ERR_ABORTED); - } - } - return false; - } - - /** - * 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.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.cordova.getActivity().getAssets().openFd(f); - this.player.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength()); - } - else { - File fp = new File(file); - if (fp.exists()) { - FileInputStream fileInputStream = new FileInputStream(file); - this.player.setDataSource(fileInputStream.getFD()); - fileInputStream.close(); - } - else { - this.player.setDataSource(Environment.getExternalStorageDirectory().getPath() + "/" + file); - } - } - this.setState(STATE.MEDIA_STARTING); - this.player.setOnPreparedListener(this); - this.player.prepare(); - - // Get duration - this.duration = getDurationInSeconds(); - } - } - - private void sendErrorStatus(int errorCode) { - sendStatusChange(MEDIA_ERROR, errorCode, null); - } - - private void sendStatusChange(int messageType, Integer additionalCode, Float value) { - - if (additionalCode != null && value != null) { - throw new IllegalArgumentException("Only one of additionalCode or value can be specified, not both"); - } - - JSONObject statusDetails = new JSONObject(); - try { - statusDetails.put("id", this.id); - statusDetails.put("msgType", messageType); - if (additionalCode != null) { - JSONObject code = new JSONObject(); - code.put("code", additionalCode.intValue()); - statusDetails.put("value", code); - } - else if (value != null) { - statusDetails.put("value", value.floatValue()); - } - } catch (JSONException e) { - Log.e(LOG_TAG, "Failed to create status details", e); - } - - this.handler.sendEventMessage("status", statusDetails); - } -} |
