summaryrefslogtreecommitdiff
path: root/plugins/org.apache.cordova.media/src
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/org.apache.cordova.media/src')
-rw-r--r--plugins/org.apache.cordova.media/src/android/AudioHandler.java410
-rw-r--r--plugins/org.apache.cordova.media/src/android/AudioPlayer.java587
-rw-r--r--plugins/org.apache.cordova.media/src/android/FileHelper.java38
-rw-r--r--plugins/org.apache.cordova.media/src/blackberry10/index.js237
-rw-r--r--plugins/org.apache.cordova.media/src/ios/CDVSound.h113
-rw-r--r--plugins/org.apache.cordova.media/src/ios/CDVSound.m703
-rw-r--r--plugins/org.apache.cordova.media/src/tizen/MediaProxy.js223
-rw-r--r--plugins/org.apache.cordova.media/src/ubuntu/media.cpp128
-rw-r--r--plugins/org.apache.cordova.media/src/ubuntu/media.h267
-rw-r--r--plugins/org.apache.cordova.media/src/windows8/MediaProxy.js217
-rw-r--r--plugins/org.apache.cordova.media/src/wp/AudioPlayer.cs647
-rw-r--r--plugins/org.apache.cordova.media/src/wp/Media.cs590
12 files changed, 4160 insertions, 0 deletions
diff --git a/plugins/org.apache.cordova.media/src/android/AudioHandler.java b/plugins/org.apache.cordova.media/src/android/AudioHandler.java
new file mode 100644
index 00000000..f06b75a4
--- /dev/null
+++ b/plugins/org.apache.cordova.media/src/android/AudioHandler.java
@@ -0,0 +1,410 @@
+/*
+ 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 org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaResourceApi;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+import org.apache.cordova.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+
+/**
+ * This class called by CordovaActivity 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 extends CordovaPlugin {
+
+ public static String TAG = "AudioHandler";
+ HashMap<String, AudioPlayer> players; // Audio player object
+ ArrayList<AudioPlayer> pausedForPhone; // Audio players that were paused when phone call came in
+ private int origVolumeStream = -1;
+ private CallbackContext messageChannel;
+
+ /**
+ * Constructor.
+ */
+ public AudioHandler() {
+ this.players = new HashMap<String, AudioPlayer>();
+ this.pausedForPhone = new ArrayList<AudioPlayer>();
+ }
+
+ /**
+ * Executes the request and returns PluginResult.
+ * @param action The action to execute.
+ * @param args JSONArry of arguments for the plugin.
+ * @param callbackContext The callback context used when calling back into JavaScript.
+ * @return A PluginResult object with a status and message.
+ */
+ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+ CordovaResourceApi resourceApi = webView.getResourceApi();
+ PluginResult.Status status = PluginResult.Status.OK;
+ String result = "";
+
+ if (action.equals("startRecordingAudio")) {
+ String target = args.getString(1);
+ String fileUriStr;
+ try {
+ Uri targetUri = resourceApi.remapUri(Uri.parse(target));
+ fileUriStr = targetUri.toString();
+ } catch (IllegalArgumentException e) {
+ fileUriStr = target;
+ }
+ this.startRecordingAudio(args.getString(0), FileHelper.stripFileProtocol(fileUriStr));
+ }
+ else if (action.equals("stopRecordingAudio")) {
+ this.stopRecordingAudio(args.getString(0));
+ }
+ else if (action.equals("startPlayingAudio")) {
+ String target = args.getString(1);
+ String fileUriStr;
+ try {
+ Uri targetUri = resourceApi.remapUri(Uri.parse(target));
+ fileUriStr = targetUri.toString();
+ } catch (IllegalArgumentException e) {
+ fileUriStr = target;
+ }
+ this.startPlayingAudio(args.getString(0), FileHelper.stripFileProtocol(fileUriStr));
+ }
+ else if (action.equals("seekToAudio")) {
+ this.seekToAudio(args.getString(0), args.getInt(1));
+ }
+ else if (action.equals("pausePlayingAudio")) {
+ this.pausePlayingAudio(args.getString(0));
+ }
+ else if (action.equals("stopPlayingAudio")) {
+ this.stopPlayingAudio(args.getString(0));
+ } else if (action.equals("setVolume")) {
+ try {
+ this.setVolume(args.getString(0), Float.parseFloat(args.getString(1)));
+ } catch (NumberFormatException nfe) {
+ //no-op
+ }
+ } else if (action.equals("getCurrentPositionAudio")) {
+ float f = this.getCurrentPositionAudio(args.getString(0));
+ callbackContext.sendPluginResult(new PluginResult(status, f));
+ return true;
+ }
+ else if (action.equals("getDurationAudio")) {
+ float f = this.getDurationAudio(args.getString(0), args.getString(1));
+ callbackContext.sendPluginResult(new PluginResult(status, f));
+ return true;
+ }
+ else if (action.equals("create")) {
+ String id = args.getString(0);
+ String src = FileHelper.stripFileProtocol(args.getString(1));
+ getOrCreatePlayer(id, src);
+ }
+ else if (action.equals("release")) {
+ boolean b = this.release(args.getString(0));
+ callbackContext.sendPluginResult(new PluginResult(status, b));
+ return true;
+ }
+ else if (action.equals("messageChannel")) {
+ messageChannel = callbackContext;
+ return true;
+ }
+ else { // Unrecognized action.
+ return false;
+ }
+
+ callbackContext.sendPluginResult(new PluginResult(status, result));
+
+ return true;
+ }
+
+ /**
+ * Stop all audio players and recorders.
+ */
+ public void onDestroy() {
+ if (!players.isEmpty()) {
+ onLastPlayerReleased();
+ }
+ for (AudioPlayer audio : this.players.values()) {
+ audio.destroy();
+ }
+ this.players.clear();
+ }
+
+ /**
+ * Stop all audio players and recorders on navigate.
+ */
+ @Override
+ public void onReset() {
+ onDestroy();
+ }
+
+ /**
+ * Called when a message is sent to plugin.
+ *
+ * @param id The message id
+ * @param data The message data
+ * @return Object to stop propagation or null
+ */
+ public Object onMessage(String id, Object data) {
+
+ // If phone message
+ if (id.equals("telephone")) {
+
+ // If phone ringing, then pause playing
+ if ("ringing".equals(data) || "offhook".equals(data)) {
+
+ // Get all audio players and pause them
+ for (AudioPlayer audio : this.players.values()) {
+ if (audio.getState() == AudioPlayer.STATE.MEDIA_RUNNING.ordinal()) {
+ this.pausedForPhone.add(audio);
+ audio.pausePlaying();
+ }
+ }
+
+ }
+
+ // If phone idle, then resume playing those players we paused
+ else if ("idle".equals(data)) {
+ for (AudioPlayer audio : this.pausedForPhone) {
+ audio.startPlaying(null);
+ }
+ this.pausedForPhone.clear();
+ }
+ }
+ return null;
+ }
+
+ //--------------------------------------------------------------------------
+ // LOCAL METHODS
+ //--------------------------------------------------------------------------
+
+ private AudioPlayer getOrCreatePlayer(String id, String file) {
+ AudioPlayer ret = players.get(id);
+ if (ret == null) {
+ if (players.isEmpty()) {
+ onFirstPlayerCreated();
+ }
+ ret = new AudioPlayer(this, id, file);
+ players.put(id, ret);
+ }
+ return ret;
+ }
+
+ /**
+ * Release the audio player instance to save memory.
+ * @param id The id of the audio player
+ */
+ private boolean release(String id) {
+ AudioPlayer audio = players.remove(id);
+ if (audio == null) {
+ return false;
+ }
+ if (players.isEmpty()) {
+ onLastPlayerReleased();
+ }
+ audio.destroy();
+ return true;
+ }
+
+ /**
+ * 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) {
+ AudioPlayer audio = getOrCreatePlayer(id, file);
+ audio.startRecording(file);
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ /**
+ * 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 = getOrCreatePlayer(id, file);
+ audio.startPlaying(file);
+ }
+
+ /**
+ * Seek to a location.
+ * @param id The id of the audio player
+ * @param milliseconds int: number of milliseconds to skip 1000 = 1 second
+ */
+ public void seekToAudio(String id, int milliseconds) {
+ AudioPlayer audio = this.players.get(id);
+ if (audio != null) {
+ audio.seekToPlaying(milliseconds);
+ }
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ /**
+ * Get current position of playback.
+ * @param id The id of the audio player
+ * @return position in msec
+ */
+ public float getCurrentPositionAudio(String id) {
+ AudioPlayer audio = this.players.get(id);
+ if (audio != null) {
+ return (audio.getCurrentPosition() / 1000.0f);
+ }
+ return -1;
+ }
+
+ /**
+ * 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 float getDurationAudio(String id, String file) {
+ AudioPlayer audio = getOrCreatePlayer(id, file);
+ return audio.getDuration(file);
+ }
+
+ /**
+ * Set the audio device to be used for playback.
+ *
+ * @param output 1=earpiece, 2=speaker
+ */
+ @SuppressWarnings("deprecation")
+ public void setAudioOutputDevice(int output) {
+ AudioManager audiMgr = (AudioManager) this.cordova.getActivity().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.");
+ }
+ }
+
+ /**
+ * Get the audio device to be used for playback.
+ *
+ * @return 1=earpiece, 2=speaker
+ */
+ @SuppressWarnings("deprecation")
+ public int getAudioOutputDevice() {
+ AudioManager audiMgr = (AudioManager) this.cordova.getActivity().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;
+ }
+ }
+
+ /**
+ * Set the volume for an audio device
+ *
+ * @param id The id of the audio player
+ * @param volume Volume to adjust to 0.0f - 1.0f
+ */
+ public void setVolume(String id, float volume) {
+ AudioPlayer audio = this.players.get(id);
+ if (audio != null) {
+ audio.setVolume(volume);
+ } else {
+ System.out.println("AudioHandler.setVolume() Error: Unknown Audio Player " + id);
+ }
+ }
+
+ private void onFirstPlayerCreated() {
+ origVolumeStream = cordova.getActivity().getVolumeControlStream();
+ cordova.getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ }
+
+ private void onLastPlayerReleased() {
+ if (origVolumeStream != -1) {
+ cordova.getActivity().setVolumeControlStream(origVolumeStream);
+ origVolumeStream = -1;
+ }
+ }
+
+ void sendEventMessage(String action, JSONObject actionData) {
+ JSONObject message = new JSONObject();
+ try {
+ message.put("action", action);
+ if (actionData != null) {
+ message.put(action, actionData);
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, "Failed to create event message", e);
+ }
+
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, message);
+ pluginResult.setKeepCallback(true);
+ if (messageChannel != null) {
+ messageChannel.sendPluginResult(pluginResult);
+ }
+ }
+}
diff --git a/plugins/org.apache.cordova.media/src/android/AudioPlayer.java b/plugins/org.apache.cordova.media/src/android/AudioPlayer.java
new file mode 100644
index 00000000..7524c5b6
--- /dev/null
+++ b/plugins/org.apache.cordova.media/src/android/AudioPlayer.java
@@ -0,0 +1,587 @@
+/*
+ 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);
+ }
+}
diff --git a/plugins/org.apache.cordova.media/src/android/FileHelper.java b/plugins/org.apache.cordova.media/src/android/FileHelper.java
new file mode 100644
index 00000000..e20752c6
--- /dev/null
+++ b/plugins/org.apache.cordova.media/src/android/FileHelper.java
@@ -0,0 +1,38 @@
+/*
+ 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.net.Uri;
+
+public class FileHelper {
+
+ /**
+ * Removes the "file://" prefix from the given URI string, if applicable.
+ * If the given URI string doesn't have a "file://" prefix, it is returned unchanged.
+ *
+ * @param uriString the URI string to operate on
+ * @return a path without the "file://" prefix
+ */
+ public static String stripFileProtocol(String uriString) {
+ if (uriString.startsWith("file://")) {
+ return Uri.parse(uriString).getPath();
+ }
+ return uriString;
+ }
+}
diff --git a/plugins/org.apache.cordova.media/src/blackberry10/index.js b/plugins/org.apache.cordova.media/src/blackberry10/index.js
new file mode 100644
index 00000000..1b9b7860
--- /dev/null
+++ b/plugins/org.apache.cordova.media/src/blackberry10/index.js
@@ -0,0 +1,237 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var audioObjects = {},
+ mediaErrorsHandled = false;
+
+// There is a bug in the webplatform handling of media error
+// dialogs prior to 10.2. This function needs to be run once
+// on the webview which plays audio to prevent freezing.
+function handleMediaErrors() {
+ var webview = qnx.webplatform.getWebViews()[0],
+ handler = webview.onDialogRequested;
+ if (!mediaErrorsHandled) {
+ webview.allowWebEvent("DialogRequested");
+ webview.onDialogRequested = undefined;
+ webview.onDialogRequested = function (eventArgs) {
+ var parsedArgs = JSON.parse(eventArgs);
+ if (parsedArgs.dialogType === 'MediaError') {
+ return '{"setPreventDefault": true}';
+ }
+ handler(eventArgs);
+ };
+ mediaErrorsHandled = true;
+ }
+}
+
+module.exports = {
+
+ create: function (success, fail, args, env) {
+ var result = new PluginResult(args, env),
+ id;
+
+ if (!args[0]) {
+ result.error("Media Object id was not sent in arguments");
+ return;
+ }
+
+ id = JSON.parse(decodeURIComponent(args[0]));
+
+ if (!args[1]){
+ audioObjects[id] = new Audio();
+ } else {
+ audioObjects[id] = new Audio(JSON.parse(decodeURIComponent(args[1])));
+ }
+
+ handleMediaErrors();
+
+ result.ok();
+ },
+
+ startPlayingAudio: function (success, fail, args, env) {
+
+ var audio,
+ id,
+ result = new PluginResult(args, env);
+
+ if (!args[0]) {
+ result.error("Media Object id was not sent in arguments");
+ return;
+ }
+
+ id = JSON.parse(decodeURIComponent(args[0]));
+
+ audio = audioObjects[id];
+
+ if (!audio) {
+ result.error("Audio object has not been initialized");
+ } else {
+ audio.play();
+ result.ok();
+ }
+ },
+
+ stopPlayingAudio: function (success, fail, args, env) {
+
+ var audio,
+ id,
+ result = new PluginResult(args, env);
+
+ if (!args[0]) {
+ result.error("Media Object id was not sent in arguments");
+ return;
+ }
+
+ id = JSON.parse(decodeURIComponent(args[0]));
+
+ audio = audioObjects[id];
+
+ if (!audio) {
+ result.error("Audio Object has not been initialized");
+ return;
+ }
+
+ audio.pause();
+ audio.currentTime = 0;
+
+ result.ok();
+ },
+
+ seekToAudio: function (success, fail, args, env) {
+
+ var audio,
+ result = new PluginResult(args, env);
+
+ if (!args[0]) {
+ result.error("Media Object id was not sent in arguments");
+ return;
+ }
+
+ audio = audioObjects[JSON.parse(decodeURIComponent(args[0]))];
+
+ if (!audio) {
+ result.error("Audio Object has not been initialized");
+ } else if (!args[1]) {
+ result.error("Media seek time argument not found");
+ } else {
+ try {
+ audio.currentTime = JSON.parse(decodeURIComponent(args[1])) / 1000;
+ result.ok();
+ } catch (e) {
+ result.error("Error seeking audio: " + e);
+ }
+ }
+ },
+
+ pausePlayingAudio: function (success, fail, args, env) {
+
+ var audio,
+ result = new PluginResult(args, env);
+
+ if (!args[0]) {
+ result.error("Media Object id was not sent in arguments");
+ return;
+ }
+
+ audio = audioObjects[JSON.parse(decodeURIComponent(args[0]))];
+
+ if (!audio) {
+ result.error("Audio Object has not been initialized");
+ return;
+ }
+
+ audio.pause();
+ },
+
+ getCurrentPositionAudio: function (success, fail, args, env) {
+
+ var audio,
+ result = new PluginResult(args, env);
+
+ if (!args[0]) {
+ result.error("Media Object id was not sent in arguments");
+ return;
+ }
+
+ audio = audioObjects[JSON.parse(decodeURIComponent(args[0]))];
+
+ if (!audio) {
+ result.error("Audio Object has not been initialized");
+ return;
+ }
+
+ result.ok(audio.currentTime);
+ },
+
+ getDuration: function (success, fail, args, env) {
+
+ var audio,
+ result = new PluginResult(args, env);
+
+ if (!args[0]) {
+ result.error("Media Object id was not sent in arguments");
+ return;
+ }
+
+ audio = audioObjects[JSON.parse(decodeURIComponent(args[0]))];
+
+ if (!audio) {
+ result.error("Audio Object has not been initialized");
+ return;
+ }
+
+ result.ok(audio.duration);
+ },
+
+ startRecordingAudio: function (success, fail, args, env) {
+ var result = new PluginResult(args, env);
+ result.error("Not supported");
+ },
+
+ stopRecordingAudio: function (success, fail, args, env) {
+ var result = new PluginResult(args, env);
+ result.error("Not supported");
+ },
+
+ release: function (success, fail, args, env) {
+ var audio,
+ id,
+ result = new PluginResult(args, env);
+
+ if (!args[0]) {
+ result.error("Media Object id was not sent in arguments");
+ return;
+ }
+
+ id = JSON.parse(decodeURIComponent(args[0]));
+
+ audio = audioObjects[id];
+
+ if (audio) {
+ if(audio.src !== ""){
+ audio.src = undefined;
+ }
+ audioObjects[id] = undefined;
+ }
+
+ result.ok();
+ }
+};
diff --git a/plugins/org.apache.cordova.media/src/ios/CDVSound.h b/plugins/org.apache.cordova.media/src/ios/CDVSound.h
new file mode 100644
index 00000000..984924de
--- /dev/null
+++ b/plugins/org.apache.cordova.media/src/ios/CDVSound.h
@@ -0,0 +1,113 @@
+/*
+ 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.
+ */
+
+#import <Foundation/Foundation.h>
+#import <AudioToolbox/AudioServices.h>
+#import <AVFoundation/AVFoundation.h>
+
+#import <Cordova/CDVPlugin.h>
+
+enum CDVMediaError {
+ MEDIA_ERR_ABORTED = 1,
+ MEDIA_ERR_NETWORK = 2,
+ MEDIA_ERR_DECODE = 3,
+ MEDIA_ERR_NONE_SUPPORTED = 4
+};
+typedef NSUInteger CDVMediaError;
+
+enum CDVMediaStates {
+ MEDIA_NONE = 0,
+ MEDIA_STARTING = 1,
+ MEDIA_RUNNING = 2,
+ MEDIA_PAUSED = 3,
+ MEDIA_STOPPED = 4
+};
+typedef NSUInteger CDVMediaStates;
+
+enum CDVMediaMsg {
+ MEDIA_STATE = 1,
+ MEDIA_DURATION = 2,
+ MEDIA_POSITION = 3,
+ MEDIA_ERROR = 9
+};
+typedef NSUInteger CDVMediaMsg;
+
+@interface CDVAudioPlayer : AVAudioPlayer
+{
+ NSString* mediaId;
+}
+@property (nonatomic, copy) NSString* mediaId;
+@end
+
+@interface CDVAudioRecorder : AVAudioRecorder
+{
+ NSString* mediaId;
+}
+@property (nonatomic, copy) NSString* mediaId;
+@end
+
+@interface CDVAudioFile : NSObject
+{
+ NSString* resourcePath;
+ NSURL* resourceURL;
+ CDVAudioPlayer* player;
+ CDVAudioRecorder* recorder;
+ NSNumber* volume;
+}
+
+@property (nonatomic, strong) NSString* resourcePath;
+@property (nonatomic, strong) NSURL* resourceURL;
+@property (nonatomic, strong) CDVAudioPlayer* player;
+@property (nonatomic, strong) NSNumber* volume;
+
+@property (nonatomic, strong) CDVAudioRecorder* recorder;
+
+@end
+
+@interface CDVSound : CDVPlugin <AVAudioPlayerDelegate, AVAudioRecorderDelegate>
+{
+ NSMutableDictionary* soundCache;
+ AVAudioSession* avSession;
+}
+@property (nonatomic, strong) NSMutableDictionary* soundCache;
+@property (nonatomic, strong) AVAudioSession* avSession;
+
+- (void)startPlayingAudio:(CDVInvokedUrlCommand*)command;
+- (void)pausePlayingAudio:(CDVInvokedUrlCommand*)command;
+- (void)stopPlayingAudio:(CDVInvokedUrlCommand*)command;
+- (void)seekToAudio:(CDVInvokedUrlCommand*)command;
+- (void)release:(CDVInvokedUrlCommand*)command;
+- (void)getCurrentPositionAudio:(CDVInvokedUrlCommand*)command;
+
+- (BOOL)hasAudioSession;
+
+// helper methods
+- (NSURL*)urlForRecording:(NSString*)resourcePath;
+- (NSURL*)urlForPlaying:(NSString*)resourcePath;
+
+- (CDVAudioFile*)audioFileForResource:(NSString*)resourcePath withId:(NSString*)mediaId doValidation:(BOOL)bValidate forRecording:(BOOL)bRecord;
+- (BOOL)prepareToPlay:(CDVAudioFile*)audioFile withId:(NSString*)mediaId;
+- (NSString*)createMediaErrorWithCode:(CDVMediaError)code message:(NSString*)message;
+
+- (void)startRecordingAudio:(CDVInvokedUrlCommand*)command;
+- (void)stopRecordingAudio:(CDVInvokedUrlCommand*)command;
+
+- (void)setVolume:(CDVInvokedUrlCommand*)command;
+
+@end
diff --git a/plugins/org.apache.cordova.media/src/ios/CDVSound.m b/plugins/org.apache.cordova.media/src/ios/CDVSound.m
new file mode 100644
index 00000000..309b2e29
--- /dev/null
+++ b/plugins/org.apache.cordova.media/src/ios/CDVSound.m
@@ -0,0 +1,703 @@
+/*
+ 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.
+ */
+
+#import "CDVSound.h"
+#import "CDVFile.h"
+#import <Cordova/NSArray+Comparisons.h>
+
+#define DOCUMENTS_SCHEME_PREFIX @"documents://"
+#define HTTP_SCHEME_PREFIX @"http://"
+#define HTTPS_SCHEME_PREFIX @"https://"
+#define CDVFILE_PREFIX @"cdvfile://"
+#define RECORDING_WAV @"wav"
+
+@implementation CDVSound
+
+@synthesize soundCache, avSession;
+
+// Maps a url for a resource path for recording
+- (NSURL*)urlForRecording:(NSString*)resourcePath
+{
+ NSURL* resourceURL = nil;
+ NSString* filePath = nil;
+ NSString* docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
+
+ // first check for correct extension
+ if ([[resourcePath pathExtension] caseInsensitiveCompare:RECORDING_WAV] != NSOrderedSame) {
+ resourceURL = nil;
+ NSLog(@"Resource for recording must have %@ extension", RECORDING_WAV);
+ } else if ([resourcePath hasPrefix:DOCUMENTS_SCHEME_PREFIX]) {
+ // try to find Documents:// resources
+ filePath = [resourcePath stringByReplacingOccurrencesOfString:DOCUMENTS_SCHEME_PREFIX withString:[NSString stringWithFormat:@"%@/", docsPath]];
+ NSLog(@"Will use resource '%@' from the documents folder with path = %@", resourcePath, filePath);
+ } else if ([resourcePath hasPrefix:CDVFILE_PREFIX]) {
+ CDVFile *filePlugin = [self.commandDelegate getCommandInstance:@"File"];
+ CDVFilesystemURL *url = [CDVFilesystemURL fileSystemURLWithString:resourcePath];
+ filePath = [filePlugin filesystemPathForURL:url];
+ if (filePath == nil) {
+ resourceURL = [NSURL URLWithString:resourcePath];
+ }
+ } else {
+ // if resourcePath is not from FileSystem put in tmp dir, else attempt to use provided resource path
+ NSString* tmpPath = [NSTemporaryDirectory()stringByStandardizingPath];
+ BOOL isTmp = [resourcePath rangeOfString:tmpPath].location != NSNotFound;
+ BOOL isDoc = [resourcePath rangeOfString:docsPath].location != NSNotFound;
+ if (!isTmp && !isDoc) {
+ // put in temp dir
+ filePath = [NSString stringWithFormat:@"%@/%@", tmpPath, resourcePath];
+ } else {
+ filePath = resourcePath;
+ }
+ }
+
+ if (filePath != nil) {
+ // create resourceURL
+ resourceURL = [NSURL fileURLWithPath:filePath];
+ }
+ return resourceURL;
+}
+
+// Maps a url for a resource path for playing
+// "Naked" resource paths are assumed to be from the www folder as its base
+- (NSURL*)urlForPlaying:(NSString*)resourcePath
+{
+ NSURL* resourceURL = nil;
+ NSString* filePath = nil;
+
+ // first try to find HTTP:// or Documents:// resources
+
+ if ([resourcePath hasPrefix:HTTP_SCHEME_PREFIX] || [resourcePath hasPrefix:HTTPS_SCHEME_PREFIX]) {
+ // if it is a http url, use it
+ NSLog(@"Will use resource '%@' from the Internet.", resourcePath);
+ resourceURL = [NSURL URLWithString:resourcePath];
+ } else if ([resourcePath hasPrefix:DOCUMENTS_SCHEME_PREFIX]) {
+ NSString* docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
+ filePath = [resourcePath stringByReplacingOccurrencesOfString:DOCUMENTS_SCHEME_PREFIX withString:[NSString stringWithFormat:@"%@/", docsPath]];
+ NSLog(@"Will use resource '%@' from the documents folder with path = %@", resourcePath, filePath);
+ } else if ([resourcePath hasPrefix:CDVFILE_PREFIX]) {
+ CDVFile *filePlugin = [self.commandDelegate getCommandInstance:@"File"];
+ CDVFilesystemURL *url = [CDVFilesystemURL fileSystemURLWithString:resourcePath];
+ filePath = [filePlugin filesystemPathForURL:url];
+ if (filePath == nil) {
+ resourceURL = [NSURL URLWithString:resourcePath];
+ }
+ } else {
+ // attempt to find file path in www directory or LocalFileSystem.TEMPORARY directory
+ filePath = [self.commandDelegate pathForResource:resourcePath];
+ if (filePath == nil) {
+ // see if this exists in the documents/temp directory from a previous recording
+ NSString* testPath = [NSString stringWithFormat:@"%@/%@", [NSTemporaryDirectory()stringByStandardizingPath], resourcePath];
+ if ([[NSFileManager defaultManager] fileExistsAtPath:testPath]) {
+ // inefficient as existence will be checked again below but only way to determine if file exists from previous recording
+ filePath = testPath;
+ NSLog(@"Will attempt to use file resource from LocalFileSystem.TEMPORARY directory");
+ } else {
+ // attempt to use path provided
+ filePath = resourcePath;
+ NSLog(@"Will attempt to use file resource '%@'", filePath);
+ }
+ } else {
+ NSLog(@"Found resource '%@' in the web folder.", filePath);
+ }
+ }
+ // if the resourcePath resolved to a file path, check that file exists
+ if (filePath != nil) {
+ // create resourceURL
+ resourceURL = [NSURL fileURLWithPath:filePath];
+ // try to access file
+ NSFileManager* fMgr = [NSFileManager defaultManager];
+ if (![fMgr fileExistsAtPath:filePath]) {
+ resourceURL = nil;
+ NSLog(@"Unknown resource '%@'", resourcePath);
+ }
+ }
+
+ return resourceURL;
+}
+
+// Creates or gets the cached audio file resource object
+- (CDVAudioFile*)audioFileForResource:(NSString*)resourcePath withId:(NSString*)mediaId doValidation:(BOOL)bValidate forRecording:(BOOL)bRecord
+{
+ BOOL bError = NO;
+ CDVMediaError errcode = MEDIA_ERR_NONE_SUPPORTED;
+ NSString* errMsg = @"";
+ NSString* jsString = nil;
+ CDVAudioFile* audioFile = nil;
+ NSURL* resourceURL = nil;
+
+ if ([self soundCache] == nil) {
+ [self setSoundCache:[NSMutableDictionary dictionaryWithCapacity:1]];
+ } else {
+ audioFile = [[self soundCache] objectForKey:mediaId];
+ }
+ if (audioFile == nil) {
+ // validate resourcePath and create
+ if ((resourcePath == nil) || ![resourcePath isKindOfClass:[NSString class]] || [resourcePath isEqualToString:@""]) {
+ bError = YES;
+ errcode = MEDIA_ERR_ABORTED;
+ errMsg = @"invalid media src argument";
+ } else {
+ audioFile = [[CDVAudioFile alloc] init];
+ audioFile.resourcePath = resourcePath;
+ audioFile.resourceURL = nil; // validate resourceURL when actually play or record
+ [[self soundCache] setObject:audioFile forKey:mediaId];
+ }
+ }
+ if (bValidate && (audioFile.resourceURL == nil)) {
+ if (bRecord) {
+ resourceURL = [self urlForRecording:resourcePath];
+ } else {
+ resourceURL = [self urlForPlaying:resourcePath];
+ }
+ if (resourceURL == nil) {
+ bError = YES;
+ errcode = MEDIA_ERR_ABORTED;
+ errMsg = [NSString stringWithFormat:@"Cannot use audio file from resource '%@'", resourcePath];
+ } else {
+ audioFile.resourceURL = resourceURL;
+ }
+ }
+
+ if (bError) {
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:errcode message:errMsg]];
+ [self.commandDelegate evalJs:jsString];
+ }
+
+ return audioFile;
+}
+
+// returns whether or not audioSession is available - creates it if necessary
+- (BOOL)hasAudioSession
+{
+ BOOL bSession = YES;
+
+ if (!self.avSession) {
+ NSError* error = nil;
+
+ self.avSession = [AVAudioSession sharedInstance];
+ if (error) {
+ // is not fatal if can't get AVAudioSession , just log the error
+ NSLog(@"error creating audio session: %@", [[error userInfo] description]);
+ self.avSession = nil;
+ bSession = NO;
+ }
+ }
+ return bSession;
+}
+
+// helper function to create a error object string
+- (NSString*)createMediaErrorWithCode:(CDVMediaError)code message:(NSString*)message
+{
+ NSMutableDictionary* errorDict = [NSMutableDictionary dictionaryWithCapacity:2];
+
+ [errorDict setObject:[NSNumber numberWithUnsignedInteger:code] forKey:@"code"];
+ [errorDict setObject:message ? message:@"" forKey:@"message"];
+
+ NSData* jsonData = [NSJSONSerialization dataWithJSONObject:errorDict options:0 error:nil];
+ return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
+}
+
+- (void)create:(CDVInvokedUrlCommand*)command
+{
+ NSString* mediaId = [command argumentAtIndex:0];
+ NSString* resourcePath = [command argumentAtIndex:1];
+
+ CDVAudioFile* audioFile = [self audioFileForResource:resourcePath withId:mediaId doValidation:NO forRecording:NO];
+
+ if (audioFile == nil) {
+ NSString* errorMessage = [NSString stringWithFormat:@"Failed to initialize Media file with path %@", resourcePath];
+ NSString* jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:errorMessage]];
+ [self.commandDelegate evalJs:jsString];
+ } else {
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }
+}
+
+- (void)setVolume:(CDVInvokedUrlCommand*)command
+{
+ NSString* callbackId = command.callbackId;
+
+#pragma unused(callbackId)
+ NSString* mediaId = [command argumentAtIndex:0];
+ NSNumber* volume = [command argumentAtIndex:1 withDefault:[NSNumber numberWithFloat:1.0]];
+
+ if ([self soundCache] != nil) {
+ CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId];
+ if (audioFile != nil) {
+ audioFile.volume = volume;
+ if (audioFile.player) {
+ audioFile.player.volume = [volume floatValue];
+ }
+ [[self soundCache] setObject:audioFile forKey:mediaId];
+ }
+ }
+
+ // don't care for any callbacks
+}
+
+- (void)startPlayingAudio:(CDVInvokedUrlCommand*)command
+{
+ NSString* callbackId = command.callbackId;
+
+#pragma unused(callbackId)
+ NSString* mediaId = [command argumentAtIndex:0];
+ NSString* resourcePath = [command argumentAtIndex:1];
+ NSDictionary* options = [command argumentAtIndex:2 withDefault:nil];
+
+ BOOL bError = NO;
+ NSString* jsString = nil;
+
+ CDVAudioFile* audioFile = [self audioFileForResource:resourcePath withId:mediaId doValidation:YES forRecording:NO];
+ if ((audioFile != nil) && (audioFile.resourceURL != nil)) {
+ if (audioFile.player == nil) {
+ bError = [self prepareToPlay:audioFile withId:mediaId];
+ }
+ if (!bError) {
+ // audioFile.player != nil or player was successfully created
+ // get the audioSession and set the category to allow Playing when device is locked or ring/silent switch engaged
+ if ([self hasAudioSession]) {
+ NSError* __autoreleasing err = nil;
+ NSNumber* playAudioWhenScreenIsLocked = [options objectForKey:@"playAudioWhenScreenIsLocked"];
+ BOOL bPlayAudioWhenScreenIsLocked = YES;
+ if (playAudioWhenScreenIsLocked != nil) {
+ bPlayAudioWhenScreenIsLocked = [playAudioWhenScreenIsLocked boolValue];
+ }
+
+ NSString* sessionCategory = bPlayAudioWhenScreenIsLocked ? AVAudioSessionCategoryPlayback : AVAudioSessionCategorySoloAmbient;
+ [self.avSession setCategory:sessionCategory error:&err];
+ if (![self.avSession setActive:YES error:&err]) {
+ // other audio with higher priority that does not allow mixing could cause this to fail
+ NSLog(@"Unable to play audio: %@", [err localizedFailureReason]);
+ bError = YES;
+ }
+ }
+ if (!bError) {
+ NSLog(@"Playing audio sample '%@'", audioFile.resourcePath);
+ NSNumber* loopOption = [options objectForKey:@"numberOfLoops"];
+ NSInteger numberOfLoops = 0;
+ if (loopOption != nil) {
+ numberOfLoops = [loopOption intValue] - 1;
+ }
+ audioFile.player.numberOfLoops = numberOfLoops;
+ if (audioFile.player.isPlaying) {
+ [audioFile.player stop];
+ audioFile.player.currentTime = 0;
+ }
+ if (audioFile.volume != nil) {
+ audioFile.player.volume = [audioFile.volume floatValue];
+ }
+
+ [audioFile.player play];
+ double position = round(audioFile.player.duration * 1000) / 1000;
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%.3f);\n%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_DURATION, position, @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_RUNNING];
+ [self.commandDelegate evalJs:jsString];
+ }
+ }
+ if (bError) {
+ /* I don't see a problem playing previously recorded audio so removing this section - BG
+ NSError* error;
+ // try loading it one more time, in case the file was recorded previously
+ audioFile.player = [[ AVAudioPlayer alloc ] initWithContentsOfURL:audioFile.resourceURL error:&error];
+ if (error != nil) {
+ NSLog(@"Failed to initialize AVAudioPlayer: %@\n", error);
+ audioFile.player = nil;
+ } else {
+ NSLog(@"Playing audio sample '%@'", audioFile.resourcePath);
+ audioFile.player.numberOfLoops = numberOfLoops;
+ [audioFile.player play];
+ } */
+ // error creating the session or player
+ // jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, MEDIA_ERR_NONE_SUPPORTED];
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_NONE_SUPPORTED message:nil]];
+ [self.commandDelegate evalJs:jsString];
+ }
+ }
+ // else audioFile was nil - error already returned from audioFile for resource
+ return;
+}
+
+- (BOOL)prepareToPlay:(CDVAudioFile*)audioFile withId:(NSString*)mediaId
+{
+ BOOL bError = NO;
+ NSError* __autoreleasing playerError = nil;
+
+ // create the player
+ NSURL* resourceURL = audioFile.resourceURL;
+
+ if ([resourceURL isFileURL]) {
+ audioFile.player = [[CDVAudioPlayer alloc] initWithContentsOfURL:resourceURL error:&playerError];
+ } else {
+ NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:resourceURL];
+ NSString* userAgent = [self.commandDelegate userAgent];
+ if (userAgent) {
+ [request setValue:userAgent forHTTPHeaderField:@"User-Agent"];
+ }
+
+ NSURLResponse* __autoreleasing response = nil;
+ NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&playerError];
+ if (playerError) {
+ NSLog(@"Unable to download audio from: %@", [resourceURL absoluteString]);
+ } else {
+ // bug in AVAudioPlayer when playing downloaded data in NSData - we have to download the file and play from disk
+ CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
+ CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuidRef);
+ NSString* filePath = [NSString stringWithFormat:@"%@/%@", [NSTemporaryDirectory()stringByStandardizingPath], uuidString];
+ CFRelease(uuidString);
+ CFRelease(uuidRef);
+
+ [data writeToFile:filePath atomically:YES];
+ NSURL* fileURL = [NSURL fileURLWithPath:filePath];
+ audioFile.player = [[CDVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&playerError];
+ }
+ }
+
+ if (playerError != nil) {
+ NSLog(@"Failed to initialize AVAudioPlayer: %@\n", [playerError localizedDescription]);
+ audioFile.player = nil;
+ if (self.avSession) {
+ [self.avSession setActive:NO error:nil];
+ }
+ bError = YES;
+ } else {
+ audioFile.player.mediaId = mediaId;
+ audioFile.player.delegate = self;
+ bError = ![audioFile.player prepareToPlay];
+ }
+ return bError;
+}
+
+- (void)stopPlayingAudio:(CDVInvokedUrlCommand*)command
+{
+ NSString* mediaId = [command argumentAtIndex:0];
+ CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId];
+ NSString* jsString = nil;
+
+ if ((audioFile != nil) && (audioFile.player != nil)) {
+ NSLog(@"Stopped playing audio sample '%@'", audioFile.resourcePath);
+ [audioFile.player stop];
+ audioFile.player.currentTime = 0;
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED];
+ } // ignore if no media playing
+ if (jsString) {
+ [self.commandDelegate evalJs:jsString];
+ }
+}
+
+- (void)pausePlayingAudio:(CDVInvokedUrlCommand*)command
+{
+ NSString* mediaId = [command argumentAtIndex:0];
+ NSString* jsString = nil;
+ CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId];
+
+ if ((audioFile != nil) && (audioFile.player != nil)) {
+ NSLog(@"Paused playing audio sample '%@'", audioFile.resourcePath);
+ [audioFile.player pause];
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_PAUSED];
+ }
+ // ignore if no media playing
+
+ if (jsString) {
+ [self.commandDelegate evalJs:jsString];
+ }
+}
+
+- (void)seekToAudio:(CDVInvokedUrlCommand*)command
+{
+ // args:
+ // 0 = Media id
+ // 1 = path to resource
+ // 2 = seek to location in milliseconds
+
+ NSString* mediaId = [command argumentAtIndex:0];
+
+ CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId];
+ double position = [[command argumentAtIndex:1] doubleValue];
+
+ if ((audioFile != nil) && (audioFile.player != nil)) {
+ NSString* jsString;
+ double posInSeconds = position / 1000;
+ if (posInSeconds >= audioFile.player.duration) {
+ // The seek is past the end of file. Stop media and reset to beginning instead of seeking past the end.
+ [audioFile.player stop];
+ audioFile.player.currentTime = 0;
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%.3f);\n%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_POSITION, 0.0, @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED];
+ // NSLog(@"seekToEndJsString=%@",jsString);
+ } else {
+ audioFile.player.currentTime = posInSeconds;
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%f);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_POSITION, posInSeconds];
+ // NSLog(@"seekJsString=%@",jsString);
+ }
+
+ [self.commandDelegate evalJs:jsString];
+ }
+}
+
+- (void)release:(CDVInvokedUrlCommand*)command
+{
+ NSString* mediaId = [command argumentAtIndex:0];
+
+ if (mediaId != nil) {
+ CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId];
+ if (audioFile != nil) {
+ if (audioFile.player && [audioFile.player isPlaying]) {
+ [audioFile.player stop];
+ }
+ if (audioFile.recorder && [audioFile.recorder isRecording]) {
+ [audioFile.recorder stop];
+ }
+ if (self.avSession) {
+ [self.avSession setActive:NO error:nil];
+ self.avSession = nil;
+ }
+ [[self soundCache] removeObjectForKey:mediaId];
+ NSLog(@"Media with id %@ released", mediaId);
+ }
+ }
+}
+
+- (void)getCurrentPositionAudio:(CDVInvokedUrlCommand*)command
+{
+ NSString* callbackId = command.callbackId;
+ NSString* mediaId = [command argumentAtIndex:0];
+
+#pragma unused(mediaId)
+ CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId];
+ double position = -1;
+
+ if ((audioFile != nil) && (audioFile.player != nil) && [audioFile.player isPlaying]) {
+ position = round(audioFile.player.currentTime * 1000) / 1000;
+ }
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:position];
+
+ NSString* jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%.3f);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_POSITION, position];
+ [self.commandDelegate evalJs:jsString];
+ [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+}
+
+- (void)startRecordingAudio:(CDVInvokedUrlCommand*)command
+{
+ NSString* callbackId = command.callbackId;
+
+#pragma unused(callbackId)
+
+ NSString* mediaId = [command argumentAtIndex:0];
+ CDVAudioFile* audioFile = [self audioFileForResource:[command argumentAtIndex:1] withId:mediaId doValidation:YES forRecording:YES];
+ __block NSString* jsString = nil;
+ __block NSString* errorMsg = @"";
+
+ if ((audioFile != nil) && (audioFile.resourceURL != nil)) {
+ void (^startRecording)(void) = ^{
+ NSError* __autoreleasing error = nil;
+
+ if (audioFile.recorder != nil) {
+ [audioFile.recorder stop];
+ audioFile.recorder = nil;
+ }
+ // get the audioSession and set the category to allow recording when device is locked or ring/silent switch engaged
+ if ([self hasAudioSession]) {
+ if (![self.avSession.category isEqualToString:AVAudioSessionCategoryPlayAndRecord]) {
+ [self.avSession setCategory:AVAudioSessionCategoryRecord error:nil];
+ }
+
+ if (![self.avSession setActive:YES error:&error]) {
+ // other audio with higher priority that does not allow mixing could cause this to fail
+ errorMsg = [NSString stringWithFormat:@"Unable to record audio: %@", [error localizedFailureReason]];
+ // jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, MEDIA_ERR_ABORTED];
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:errorMsg]];
+ [self.commandDelegate evalJs:jsString];
+ return;
+ }
+ }
+
+ // create a new recorder for each start record
+ audioFile.recorder = [[CDVAudioRecorder alloc] initWithURL:audioFile.resourceURL settings:nil error:&error];
+
+ bool recordingSuccess = NO;
+ if (error == nil) {
+ audioFile.recorder.delegate = self;
+ audioFile.recorder.mediaId = mediaId;
+ recordingSuccess = [audioFile.recorder record];
+ if (recordingSuccess) {
+ NSLog(@"Started recording audio sample '%@'", audioFile.resourcePath);
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_RUNNING];
+ [self.commandDelegate evalJs:jsString];
+ }
+ }
+
+ if ((error != nil) || (recordingSuccess == NO)) {
+ if (error != nil) {
+ errorMsg = [NSString stringWithFormat:@"Failed to initialize AVAudioRecorder: %@\n", [error localizedFailureReason]];
+ } else {
+ errorMsg = @"Failed to start recording using AVAudioRecorder";
+ }
+ audioFile.recorder = nil;
+ if (self.avSession) {
+ [self.avSession setActive:NO error:nil];
+ }
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:errorMsg]];
+ [self.commandDelegate evalJs:jsString];
+ }
+ };
+
+ SEL rrpSel = NSSelectorFromString(@"requestRecordPermission:");
+ if ([self hasAudioSession] && [self.avSession respondsToSelector:rrpSel])
+ {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
+ [self.avSession performSelector:rrpSel withObject:^(BOOL granted){
+ if (granted) {
+ startRecording();
+ } else {
+ NSString* msg = @"Error creating audio session, microphone permission denied.";
+ NSLog(@"%@", msg);
+ audioFile.recorder = nil;
+ if (self.avSession) {
+ [self.avSession setActive:NO error:nil];
+ }
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:msg]];
+ [self.commandDelegate evalJs:jsString];
+ }
+ }];
+#pragma clang diagnostic pop
+ } else {
+ startRecording();
+ }
+
+ } else {
+ // file did not validate
+ NSString* errorMsg = [NSString stringWithFormat:@"Could not record audio at '%@'", audioFile.resourcePath];
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:errorMsg]];
+ [self.commandDelegate evalJs:jsString];
+ }
+}
+
+- (void)stopRecordingAudio:(CDVInvokedUrlCommand*)command
+{
+ NSString* mediaId = [command argumentAtIndex:0];
+
+ CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId];
+ NSString* jsString = nil;
+
+ if ((audioFile != nil) && (audioFile.recorder != nil)) {
+ NSLog(@"Stopped recording audio sample '%@'", audioFile.resourcePath);
+ [audioFile.recorder stop];
+ // no callback - that will happen in audioRecorderDidFinishRecording
+ }
+ // ignore if no media recording
+ if (jsString) {
+ [self.commandDelegate evalJs:jsString];
+ }
+}
+
+- (void)audioRecorderDidFinishRecording:(AVAudioRecorder*)recorder successfully:(BOOL)flag
+{
+ CDVAudioRecorder* aRecorder = (CDVAudioRecorder*)recorder;
+ NSString* mediaId = aRecorder.mediaId;
+ CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId];
+ NSString* jsString = nil;
+
+ if (audioFile != nil) {
+ NSLog(@"Finished recording audio sample '%@'", audioFile.resourcePath);
+ }
+ if (flag) {
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED];
+ } else {
+ // jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, MEDIA_ERR_DECODE];
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_DECODE message:nil]];
+ }
+ if (self.avSession) {
+ [self.avSession setActive:NO error:nil];
+ }
+ [self.commandDelegate evalJs:jsString];
+}
+
+- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer*)player successfully:(BOOL)flag
+{
+ CDVAudioPlayer* aPlayer = (CDVAudioPlayer*)player;
+ NSString* mediaId = aPlayer.mediaId;
+ CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId];
+ NSString* jsString = nil;
+
+ if (audioFile != nil) {
+ NSLog(@"Finished playing audio sample '%@'", audioFile.resourcePath);
+ }
+ if (flag) {
+ audioFile.player.currentTime = 0;
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED];
+ } else {
+ // jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, MEDIA_ERR_DECODE];
+ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_DECODE message:nil]];
+ }
+ if (self.avSession) {
+ [self.avSession setActive:NO error:nil];
+ }
+ [self.commandDelegate evalJs:jsString];
+}
+
+- (void)onMemoryWarning
+{
+ [[self soundCache] removeAllObjects];
+ [self setSoundCache:nil];
+ [self setAvSession:nil];
+
+ [super onMemoryWarning];
+}
+
+- (void)dealloc
+{
+ [[self soundCache] removeAllObjects];
+}
+
+- (void)onReset
+{
+ for (CDVAudioFile* audioFile in [[self soundCache] allValues]) {
+ if (audioFile != nil) {
+ if (audioFile.player != nil) {
+ [audioFile.player stop];
+ audioFile.player.currentTime = 0;
+ }
+ if (audioFile.recorder != nil) {
+ [audioFile.recorder stop];
+ }
+ }
+ }
+
+ [[self soundCache] removeAllObjects];
+}
+
+@end
+
+@implementation CDVAudioFile
+
+@synthesize resourcePath;
+@synthesize resourceURL;
+@synthesize player, volume;
+@synthesize recorder;
+
+@end
+@implementation CDVAudioPlayer
+@synthesize mediaId;
+
+@end
+
+@implementation CDVAudioRecorder
+@synthesize mediaId;
+
+@end
diff --git a/plugins/org.apache.cordova.media/src/tizen/MediaProxy.js b/plugins/org.apache.cordova.media/src/tizen/MediaProxy.js
new file mode 100644
index 00000000..c2ee4b07
--- /dev/null
+++ b/plugins/org.apache.cordova.media/src/tizen/MediaProxy.js
@@ -0,0 +1,223 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var cordova = require('cordova'),
+ Media = require('org.apache.cordova.media.Media');
+
+var MediaError = require('org.apache.cordova.media.MediaError'),
+ audioObjects = {};
+
+module.exports = {
+ // Initiates the audio file
+ create:function(successCallback, errorCallback, args) {
+ var id = args[0], src = args[1];
+
+ console.log("media::create() - id =" + id + ", src =" + src);
+
+ audioObjects[id] = new Audio(src);
+
+ audioObjects[id].onStalledCB = function () {
+ console.log("media::onStalled()");
+
+ audioObjects[id].timer = window.setTimeout(
+ function () {
+ audioObjects[id].pause();
+
+ if (audioObjects[id].currentTime !== 0)
+ audioObjects[id].currentTime = 0;
+
+ console.log("media::onStalled() - MEDIA_ERROR -> " + MediaError.MEDIA_ERR_ABORTED);
+
+ var err = new MediaError(MediaError.MEDIA_ERR_ABORTED, "Stalled");
+
+ Media.onStatus(id, Media.MEDIA_ERROR, err);
+ },
+ 2000);
+ };
+
+ audioObjects[id].onEndedCB = function () {
+ console.log("media::onEndedCB() - MEDIA_STATE -> MEDIA_STOPPED");
+
+ Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_STOPPED);
+ };
+
+ audioObjects[id].onErrorCB = function () {
+ console.log("media::onErrorCB() - MEDIA_ERROR -> " + event.srcElement.error);
+
+ Media.onStatus(id, Media.MEDIA_ERROR, event.srcElement.error);
+ };
+
+ audioObjects[id].onPlayCB = function () {
+ console.log("media::onPlayCB() - MEDIA_STATE -> MEDIA_STARTING");
+
+ Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_STARTING);
+ };
+
+ audioObjects[id].onPlayingCB = function () {
+ console.log("media::onPlayingCB() - MEDIA_STATE -> MEDIA_RUNNING");
+
+ Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_RUNNING);
+ };
+
+ audioObjects[id].onDurationChangeCB = function () {
+ console.log("media::onDurationChangeCB() - MEDIA_DURATION -> " + audioObjects[id].duration);
+
+ Media.onStatus(id, Media.MEDIA_DURATION, audioObjects[id].duration);
+ };
+
+ audioObjects[id].onTimeUpdateCB = function () {
+ console.log("media::onTimeUpdateCB() - MEDIA_POSITION -> " + audioObjects[id].currentTime);
+
+ Media.onStatus(id, Media.MEDIA_POSITION, audioObjects[id].currentTime);
+ };
+
+ audioObjects[id].onCanPlayCB = function () {
+ console.log("media::onCanPlayCB()");
+
+ window.clearTimeout(audioObjects[id].timer);
+
+ audioObjects[id].play();
+ };
+
+ },
+
+ // Start playing the audio
+ startPlayingAudio:function(successCallback, errorCallback, args) {
+ var id = args[0], src = args[1], options = args[2];
+
+ console.log("media::startPlayingAudio() - id =" + id + ", src =" + src + ", options =" + options);
+
+ audioObjects[id].addEventListener('canplay', audioObjects[id].onCanPlayCB);
+ audioObjects[id].addEventListener('ended', audioObjects[id].onEndedCB);
+ audioObjects[id].addEventListener('timeupdate', audioObjects[id].onTimeUpdateCB);
+ audioObjects[id].addEventListener('durationchange', audioObjects[id].onDurationChangeCB);
+ audioObjects[id].addEventListener('playing', audioObjects[id].onPlayingCB);
+ audioObjects[id].addEventListener('play', audioObjects[id].onPlayCB);
+ audioObjects[id].addEventListener('error', audioObjects[id].onErrorCB);
+ audioObjects[id].addEventListener('stalled', audioObjects[id].onStalledCB);
+
+ audioObjects[id].play();
+ },
+
+ // Stops the playing audio
+ stopPlayingAudio:function(successCallback, errorCallback, args) {
+ var id = args[0];
+
+ window.clearTimeout(audioObjects[id].timer);
+
+ audioObjects[id].pause();
+
+ if (audioObjects[id].currentTime !== 0)
+ audioObjects[id].currentTime = 0;
+
+ console.log("media::stopPlayingAudio() - MEDIA_STATE -> MEDIA_STOPPED");
+
+ Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_STOPPED);
+
+ audioObjects[id].removeEventListener('canplay', audioObjects[id].onCanPlayCB);
+ audioObjects[id].removeEventListener('ended', audioObjects[id].onEndedCB);
+ audioObjects[id].removeEventListener('timeupdate', audioObjects[id].onTimeUpdateCB);
+ audioObjects[id].removeEventListener('durationchange', audioObjects[id].onDurationChangeCB);
+ audioObjects[id].removeEventListener('playing', audioObjects[id].onPlayingCB);
+ audioObjects[id].removeEventListener('play', audioObjects[id].onPlayCB);
+ audioObjects[id].removeEventListener('error', audioObjects[id].onErrorCB);
+ audioObjects[id].removeEventListener('error', audioObjects[id].onStalledCB);
+ },
+
+ // Seeks to the position in the audio
+ seekToAudio:function(successCallback, errorCallback, args) {
+ var id = args[0], milliseconds = args[1];
+
+ console.log("media::seekToAudio()");
+
+ audioObjects[id].currentTime = milliseconds;
+ successCallback( audioObjects[id].currentTime);
+ },
+
+ // Pauses the playing audio
+ pausePlayingAudio:function(successCallback, errorCallback, args) {
+ var id = args[0];
+
+ console.log("media::pausePlayingAudio() - MEDIA_STATE -> MEDIA_PAUSED");
+
+ audioObjects[id].pause();
+
+ Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_PAUSED);
+ },
+
+ // Gets current position in the audio
+ getCurrentPositionAudio:function(successCallback, errorCallback, args) {
+ var id = args[0];
+ console.log("media::getCurrentPositionAudio()");
+ successCallback(audioObjects[id].currentTime);
+ },
+
+ // Start recording audio
+ startRecordingAudio:function(successCallback, errorCallback, args) {
+ var id = args[0], src = args[1];
+
+ console.log("media::startRecordingAudio() - id =" + id + ", src =" + src);
+
+ function gotStreamCB(stream) {
+ audioObjects[id].src = webkitURL.createObjectURL(stream);
+ console.log("media::startRecordingAudio() - stream CB");
+ }
+
+ function gotStreamFailedCB(error) {
+ console.log("media::startRecordingAudio() - error CB:" + error.toString());
+ }
+
+ if (navigator.webkitGetUserMedia) {
+ audioObjects[id] = new Audio();
+ navigator.webkitGetUserMedia('audio', gotStreamCB, gotStreamFailedCB);
+ } else {
+ console.log("webkitGetUserMedia not supported");
+ }
+ successCallback();
+ },
+
+ // Stop recording audio
+ stopRecordingAudio:function(successCallback, errorCallback, args) {
+ var id = args[0];
+
+ console.log("media::stopRecordingAudio() - id =" + id);
+
+ audioObjects[id].pause();
+ successCallback();
+ },
+
+ // Release the media object
+ release:function(successCallback, errorCallback, args) {
+ var id = args[0];
+ window.clearTimeout(audioObjects[id].timer);
+ console.log("media::release()");
+ },
+
+ setVolume:function(successCallback, errorCallback, args) {
+ var id = args[0], volume = args[1];
+
+ console.log("media::setVolume()");
+
+ audioObjects[id].volume = volume;
+ }
+};
+
+require("cordova/tizen/commandProxy").add("Media", module.exports);
diff --git a/plugins/org.apache.cordova.media/src/ubuntu/media.cpp b/plugins/org.apache.cordova.media/src/ubuntu/media.cpp
new file mode 100644
index 00000000..2814b5b3
--- /dev/null
+++ b/plugins/org.apache.cordova.media/src/ubuntu/media.cpp
@@ -0,0 +1,128 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+#include "media.h"
+
+void Media::create(int scId, int ecId, const QString &id, const QString &src) {
+ Q_UNUSED(scId);
+ Q_UNUSED(ecId);
+
+ if (_id2Player.find(id) != _id2Player.end()) {
+ _id2Player[id]->stop();
+ _id2Player.remove(id);
+ }
+
+ _id2Player[id] = QSharedPointer<Player>(new Player(id, src, this));
+}
+
+void Media::relase(int scId, int ecId, const QString &id) {
+ Q_UNUSED(scId);
+ Q_UNUSED(ecId);
+
+ if (_id2Player.find(id) == _id2Player.end())
+ return;
+ _id2Player.remove(id);
+}
+
+void Media::startPlayingAudio(int scId, int ecId, const QString &id, const QString &src, QVariantMap options) {
+ Q_UNUSED(scId);
+ Q_UNUSED(ecId);
+ Q_UNUSED(src);
+ Q_UNUSED(options);
+
+ if (_id2Player.find(id) == _id2Player.end())
+ return;
+ QSharedPointer<Player> player = _id2Player[id];
+ player->play();
+}
+
+void Media::pausePlayingAudio(int scId, int ecId, const QString &id) {
+ Q_UNUSED(scId);
+ Q_UNUSED(ecId);
+
+ if (_id2Player.find(id) == _id2Player.end())
+ return;
+ QSharedPointer<Player> player = _id2Player[id];
+ player->pause();
+}
+
+void Media::stopPlayingAudio(int scId, int ecId, const QString &id) {
+ Q_UNUSED(scId);
+ Q_UNUSED(ecId);
+
+ if (_id2Player.find(id) == _id2Player.end())
+ return;
+ QSharedPointer<Player> player = _id2Player[id];
+ player->stop();
+}
+
+void Media::startRecordingAudio(int scId, int ecId, const QString &id, const QString &src) {
+ Q_UNUSED(scId);
+ Q_UNUSED(ecId);
+ Q_UNUSED(src);
+
+ if (_id2Player.find(id) == _id2Player.end())
+ return;
+ QSharedPointer<Player> player = _id2Player[id];
+ player->startRecording();
+}
+
+void Media::stopRecordingAudio(int scId, int ecId, const QString &id) {
+ Q_UNUSED(scId);
+ Q_UNUSED(ecId);
+
+ if (_id2Player.find(id) == _id2Player.end())
+ return;
+ QSharedPointer<Player> player = _id2Player[id];
+ player->stopRecording();
+}
+
+void Media::getCurrentPositionAudio(int scId, int ecId, const QString &id) {
+ Q_UNUSED(ecId);
+
+ if (_id2Player.find(id) == _id2Player.end())
+ return;
+
+ QSharedPointer<Player> player = _id2Player[id];
+ double position = player->getPosition();
+ this->cb(scId, position);
+}
+
+void Media::seekToAudio(int scId, int ecId, const QString &id, qint64 position) {
+ Q_UNUSED(scId);
+ Q_UNUSED(ecId);
+
+ if (_id2Player.find(id) == _id2Player.end())
+ return;
+
+ QSharedPointer<Player> player = _id2Player[id];
+ player->seekTo(position);
+}
+
+void Media::setVolume(int scId, int ecId, const QString &id, int volume) {
+ Q_UNUSED(scId);
+ Q_UNUSED(ecId);
+
+ if (_id2Player.find(id) == _id2Player.end())
+ return;
+ QSharedPointer<Player> player = _id2Player[id];
+ player->setVolume(volume);
+}
diff --git a/plugins/org.apache.cordova.media/src/ubuntu/media.h b/plugins/org.apache.cordova.media/src/ubuntu/media.h
new file mode 100644
index 00000000..c1f37122
--- /dev/null
+++ b/plugins/org.apache.cordova.media/src/ubuntu/media.h
@@ -0,0 +1,267 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+#ifndef MEDIA_H_789768978
+#define MEDIA_H_789768978
+
+#include <QtMultimedia/QMediaPlayer>
+#include <QtCore>
+#include <QAudioRecorder>
+#include <QtMultimedia/QAudioEncoderSettings>
+
+#include <cplugin.h>
+#include <cordova.h>
+
+class Player;
+
+class Media: public CPlugin {
+ Q_OBJECT
+public:
+ explicit Media(Cordova *cordova): CPlugin(cordova) {
+ }
+
+ virtual const QString fullName() override {
+ return Media::fullID();
+ }
+
+ virtual const QString shortName() override {
+ return "Media";
+ }
+
+ static const QString fullID() {
+ return "Media";
+ }
+
+ enum State {
+ MEDIA_NONE = 0,
+ MEDIA_STARTING = 1,
+ MEDIA_RUNNING = 2,
+ MEDIA_PAUSED = 3,
+ MEDIA_STOPPED = 4
+ };
+ enum ErrorCode {
+ MEDIA_ERR_NONE_ACTIVE = 0,
+ MEDIA_ERR_ABORTED = 1,
+ MEDIA_ERR_NETWORK = 2,
+ MEDIA_ERR_DECODE = 3,
+ MEDIA_ERR_NONE_SUPPORTED = 4
+ };
+
+ void execJS(const QString &js) {
+ m_cordova->execJS(js);
+ }
+public slots:
+ void create(int scId, int ecId, const QString &id, const QString &src);
+ void relase(int scId, int ecId, const QString &id);
+
+ void startRecordingAudio(int scId, int ecId, const QString &id, const QString &src);
+ void stopRecordingAudio(int scId, int ecId, const QString &id);
+
+ void startPlayingAudio(int scId, int ecId, const QString &id, const QString &src, QVariantMap options);
+ void pausePlayingAudio(int scId, int ecId, const QString &id);
+ void stopPlayingAudio(int scId, int ecId, const QString &id);
+ void getCurrentPositionAudio(int scId, int ecId, const QString &id);
+ void seekToAudio(int scId, int ecId, const QString &id, qint64 position);
+ void setVolume(int scId, int ecId, const QString &id, int volume);
+
+private:
+ QMap<QString, QSharedPointer<Player> > _id2Player;
+};
+
+class Player: public QObject {
+ Q_OBJECT
+public:
+ Player(const QString &id, QString src, Media *plugin):
+ _state(Media::MEDIA_NONE),
+ _src(src),
+ _mode(MODE_NONE),
+ _plugin(plugin),
+ _id(id),
+ _stateChanged(false) {
+ QUrl url(src, QUrl::TolerantMode);
+
+ if (url.scheme().isEmpty()) {
+ QAudioEncoderSettings audioSettings;
+
+ _recorder.setEncodingSettings(audioSettings);
+ _recorder.setOutputLocation(QFileInfo(src).absoluteFilePath());
+
+ _player.setMedia(QUrl::fromLocalFile(QFileInfo(src).absoluteFilePath()));
+ } else {
+ _player.setMedia(url);
+ }
+ QObject::connect(&_player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, SLOT(onMediaStatusChanged(QMediaPlayer::MediaStatus)));
+ QObject::connect(&_recorder, SIGNAL(error(QMediaRecorder::Error)), this, SLOT(onError(QMediaRecorder::Error)));
+
+ connect(&_timer, SIGNAL(timeout()), this, SLOT(reportPosition()));
+ }
+
+ void startRecording() {
+ if (recordMode() && _state != Media::MEDIA_RUNNING) {
+ _recorder.record();
+ setState(Media::MEDIA_RUNNING);
+ }
+ }
+ void stopRecording() {
+ if (recordMode() && _state == Media::MEDIA_RUNNING) {
+ _recorder.stop();
+ setState(Media::MEDIA_STOPPED);
+ }
+ }
+
+ void setVolume(int volume) {
+ _player.setVolume(volume);
+ }
+
+ void play() {
+ if (playMode() && _state != Media::MEDIA_RUNNING) {
+ _player.play();
+ setState(Media::MEDIA_RUNNING);
+ }
+ }
+ void pause() {
+ if (playMode() && _state == Media::MEDIA_RUNNING) {
+ _player.pause();
+ setState(Media::MEDIA_PAUSED);
+ }
+ }
+ void stop() {
+ if (playMode() && (_state == Media::MEDIA_RUNNING || _state == Media::MEDIA_PAUSED)) {
+ _player.stop();
+ setState(Media::MEDIA_STOPPED);
+ }
+ }
+ double getDuration() {
+ if (_mode == MODE_NONE || _player.duration() == -1)
+ return -1;
+ if (_mode != MODE_PLAY)
+ return -2;
+ return static_cast<double>(_player.duration()) / 1000.0;
+ }
+ double getPosition() {
+ if (_mode != MODE_PLAY)
+ return -1;
+ return static_cast<double>(_player.position()) / 1000.0;
+ }
+ bool seekTo(qint64 position) {
+ if (!_player.isSeekable())
+ return false;
+ _player.setPosition(position * 1000);
+ return true;
+ }
+private slots:
+ void reportPosition() {
+ double position = getPosition();
+ _plugin->execJS(QString("Media.onStatus('%1', Media.MEDIA_POSITION, %2)")
+ .arg(_id).arg(position));
+ double duration = getDuration();
+ _plugin->execJS(QString("Media.onStatus('%1', Media.MEDIA_DURATION, %2)")
+ .arg(_id).arg(duration));
+
+ if (_stateChanged && !(_state == Media::MEDIA_RUNNING && (duration == -1 || position == 0))) {
+ qCritical() << _id << "POSITION" << position << ":" << duration;
+ _stateChanged = false;
+ _plugin->execJS(QString("Media.onStatus('%1', Media.MEDIA_STATE, %2)").arg(_id).arg(_state));
+ }
+ }
+
+ void onMediaStatusChanged(QMediaPlayer::MediaStatus status) {
+ if (status == QMediaPlayer::InvalidMedia) {
+ reportError(Media::MEDIA_ERR_ABORTED, "AudioPlayer Error: The current media cannot be played.");
+ setState(Media::MEDIA_STOPPED);
+ }
+ if (status == QMediaPlayer::EndOfMedia) {
+ setState(Media::MEDIA_STOPPED);
+ seekTo(0);
+ }
+ }
+ void onError(QMediaRecorder::Error) {
+ reportError(Media::MEDIA_ERR_NONE_SUPPORTED, "AudioPlayer Error: Device is not ready or not available.");
+ setState(Media::MEDIA_STOPPED);
+ }
+
+private:
+ void reportError(int code, const QString &descr) {
+ Q_UNUSED(descr);
+ _plugin->execJS(QString("Media.onStatus('%1', Media.MEDIA_ERROR, {code: %2})")
+ .arg(_id).arg(code));
+ }
+
+ bool playMode() {
+ switch (_mode) {
+ case Player::MODE_NONE:
+ _mode = MODE_PLAY;
+ break;
+ case Player::MODE_PLAY:
+ break;
+ case Player::MODE_RECORD:
+ reportError(Media::MEDIA_ERR_NONE_SUPPORTED, "AudioPlayer Error: Can't play in record mode.");
+ return false;
+ break;
+ }
+ return true;
+ }
+
+ bool recordMode() {
+ switch (_mode) {
+ case Player::MODE_NONE:
+ if (_recorder.outputLocation().isEmpty()) {
+ reportError(Media::MEDIA_ERR_NONE_SUPPORTED, "AudioPlayer Error: unsupported output location.");
+ return false;
+ }
+ _mode = MODE_RECORD;
+ break;
+ case Player::MODE_PLAY:
+ reportError(Media::MEDIA_ERR_NONE_SUPPORTED, "AudioPlayer Error: Can't play in play mode.");
+ return false;
+ break;
+ case Player::MODE_RECORD:
+ break;
+ }
+ return true;
+ }
+
+ void setState(Media::State state) {
+ _state = state;
+ _stateChanged = true;
+ _timer.start(250);
+ }
+
+ QMediaPlayer _player;
+
+ QAudioRecorder _recorder;
+ QTimer _timer;
+
+ Media::State _state;
+ QString _src;
+ enum Mode {
+ MODE_NONE,
+ MODE_PLAY,
+ MODE_RECORD
+ };
+ Mode _mode;
+ Media *_plugin;
+ QString _id;
+
+ bool _stateChanged;
+};
+
+#endif
diff --git a/plugins/org.apache.cordova.media/src/windows8/MediaProxy.js b/plugins/org.apache.cordova.media/src/windows8/MediaProxy.js
new file mode 100644
index 00000000..e7ec51b7
--- /dev/null
+++ b/plugins/org.apache.cordova.media/src/windows8/MediaProxy.js
@@ -0,0 +1,217 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+/*global Windows:true */
+
+var cordova = require('cordova'),
+ Media = require('org.apache.cordova.media.Media');
+
+var MediaError = require('org.apache.cordova.media.MediaError');
+
+var recordedFile;
+
+module.exports = {
+ mediaCaptureMrg:null,
+
+ // Initiates the audio file
+ create:function(win, lose, args) {
+ var id = args[0];
+ var src = args[1];
+ var thisM = Media.get(id);
+ Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_STARTING);
+
+ Media.prototype.node = null;
+
+ var fn = src.split('.').pop(); // gets the file extension
+ if (thisM.node === null) {
+ if (fn === 'mp3' || fn === 'wma' || fn === 'wav' ||
+ fn === 'cda' || fn === 'adx' || fn === 'wm' ||
+ fn === 'm3u' || fn === 'wmx' || fn === 'm4a') {
+ thisM.node = new Audio(src);
+ thisM.node.load();
+
+ var getDuration = function () {
+ var dur = thisM.node.duration;
+ if (isNaN(dur)) {
+ dur = -1;
+ }
+ Media.onStatus(id, Media.MEDIA_DURATION, dur);
+ };
+
+ thisM.node.onloadedmetadata = getDuration;
+ getDuration();
+ }
+ else {
+ lose && lose({code:MediaError.MEDIA_ERR_ABORTED});
+ }
+ }
+ },
+
+ // Start playing the audio
+ startPlayingAudio:function(win, lose, args) {
+ var id = args[0];
+ //var src = args[1];
+ //var options = args[2];
+
+ var thisM = Media.get(id);
+ // if Media was released, then node will be null and we need to create it again
+ if (!thisM.node) {
+ module.exports.create(win, lose, args);
+ }
+
+ Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_RUNNING);
+
+ thisM.node.play();
+ },
+
+ // Stops the playing audio
+ stopPlayingAudio:function(win, lose, args) {
+ var id = args[0];
+ try {
+ (Media.get(id)).node.pause();
+ (Media.get(id)).node.currentTime = 0;
+ Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_STOPPED);
+ win();
+ } catch (err) {
+ lose("Failed to stop: "+err);
+ }
+ },
+
+ // Seeks to the position in the audio
+ seekToAudio:function(win, lose, args) {
+ var id = args[0];
+ var milliseconds = args[1];
+ try {
+ (Media.get(id)).node.currentTime = milliseconds / 1000;
+ win();
+ } catch (err) {
+ lose("Failed to seek: "+err);
+ }
+ },
+
+ // Pauses the playing audio
+ pausePlayingAudio:function(win, lose, args) {
+ var id = args[0];
+ var thisM = Media.get(id);
+ try {
+ thisM.node.pause();
+ Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_PAUSED);
+ } catch (err) {
+ lose("Failed to pause: "+err);
+ }
+ },
+
+ // Gets current position in the audio
+ getCurrentPositionAudio:function(win, lose, args) {
+ var id = args[0];
+ try {
+ var p = (Media.get(id)).node.currentTime;
+ Media.onStatus(id, Media.MEDIA_POSITION, p);
+ win(p);
+ } catch (err) {
+ lose(err);
+ }
+ },
+
+ // Start recording audio
+ startRecordingAudio:function(win, lose, args) {
+ var id = args[0];
+ var src = args[1];
+
+ var normalizedSrc = src.replace(/\//g, '\\');
+ var destPath = normalizedSrc.substr(0, normalizedSrc.lastIndexOf('\\'));
+ var destFileName = normalizedSrc.replace(destPath + '\\', '');
+
+ // Initialize device
+ Media.prototype.mediaCaptureMgr = null;
+ var thisM = (Media.get(id));
+ var captureInitSettings = new Windows.Media.Capture.MediaCaptureInitializationSettings();
+ captureInitSettings.streamingCaptureMode = Windows.Media.Capture.StreamingCaptureMode.audio;
+ thisM.mediaCaptureMgr = new Windows.Media.Capture.MediaCapture();
+ thisM.mediaCaptureMgr.addEventListener("failed", lose);
+
+ thisM.mediaCaptureMgr.initializeAsync(captureInitSettings).done(function (result) {
+ thisM.mediaCaptureMgr.addEventListener("recordlimitationexceeded", lose);
+ thisM.mediaCaptureMgr.addEventListener("failed", lose);
+
+ // Start recording
+ Windows.Storage.ApplicationData.current.temporaryFolder.createFileAsync(destFileName, Windows.Storage.CreationCollisionOption.replaceExisting).done(function (newFile) {
+ recordedFile = newFile;
+ var encodingProfile = null;
+ switch (newFile.fileType) {
+ case '.m4a':
+ encodingProfile = Windows.Media.MediaProperties.MediaEncodingProfile.createM4a(Windows.Media.MediaProperties.AudioEncodingQuality.auto);
+ break;
+ case '.mp3':
+ encodingProfile = Windows.Media.MediaProperties.MediaEncodingProfile.createMp3(Windows.Media.MediaProperties.AudioEncodingQuality.auto);
+ break;
+ case '.wma':
+ encodingProfile = Windows.Media.MediaProperties.MediaEncodingProfile.createWma(Windows.Media.MediaProperties.AudioEncodingQuality.auto);
+ break;
+ default:
+ lose("Invalid file type for record");
+ break;
+ }
+ thisM.mediaCaptureMgr.startRecordToStorageFileAsync(encodingProfile, newFile).done(win, lose);
+ }, lose);
+ }, lose);
+ },
+
+ // Stop recording audio
+ stopRecordingAudio:function(win, lose, args) {
+ var id = args[0];
+ var thisM = Media.get(id);
+
+ var normalizedSrc = thisM.src.replace(/\//g, '\\');
+ var destPath = normalizedSrc.substr(0, normalizedSrc.lastIndexOf('\\'));
+ var destFileName = normalizedSrc.replace(destPath + '\\', '');
+
+ thisM.mediaCaptureMgr.stopRecordAsync().done(function () {
+ if (destPath) {
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(destPath).done(function(destFolder) {
+ recordedFile.copyAsync(destFolder, destFileName, Windows.Storage.CreationCollisionOption.replaceExisting).done(win, lose);
+ }, lose);
+ } else {
+ // if path is not defined, we leave recorded file in temporary folder (similar to iOS)
+ win();
+ }
+ }, lose);
+ },
+
+ // Release the media object
+ release:function(win, lose, args) {
+ var id = args[0];
+ var thisM = Media.get(id);
+ try {
+ delete thisM.node;
+ } catch (err) {
+ lose("Failed to release: "+err);
+ }
+ },
+ setVolume:function(win, lose, args) {
+ var id = args[0];
+ var volume = args[1];
+ var thisM = Media.get(id);
+ thisM.volume = volume;
+ }
+};
+
+require("cordova/exec/proxy").add("Media",module.exports);
diff --git a/plugins/org.apache.cordova.media/src/wp/AudioPlayer.cs b/plugins/org.apache.cordova.media/src/wp/AudioPlayer.cs
new file mode 100644
index 00000000..882eb96e
--- /dev/null
+++ b/plugins/org.apache.cordova.media/src/wp/AudioPlayer.cs
@@ -0,0 +1,647 @@
+/*
+ Licensed 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.
+*/
+
+using System;
+using System.IO;
+using System.IO.IsolatedStorage;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Threading;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Media;
+using Microsoft.Phone.Controls;
+using System.Diagnostics;
+using System.Windows.Resources;
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+
+ /// <summary>
+ /// Implements audio record and play back functionality.
+ /// </summary>
+ internal class AudioPlayer : IDisposable
+ {
+ #region Constants
+
+ // AudioPlayer states
+ private const int PlayerState_None = 0;
+ private const int PlayerState_Starting = 1;
+ private const int PlayerState_Running = 2;
+ private const int PlayerState_Paused = 3;
+ private const int PlayerState_Stopped = 4;
+
+ // AudioPlayer messages
+ private const int MediaState = 1;
+ private const int MediaDuration = 2;
+ private const int MediaPosition = 3;
+ private const int MediaError = 9;
+
+ // AudioPlayer errors
+ private const int MediaErrorPlayModeSet = 1;
+ private const int MediaErrorAlreadyRecording = 2;
+ private const int MediaErrorStartingRecording = 3;
+ private const int MediaErrorRecordModeSet = 4;
+ private const int MediaErrorStartingPlayback = 5;
+ private const int MediaErrorResumeState = 6;
+ private const int MediaErrorPauseState = 7;
+ private const int MediaErrorStopState = 8;
+
+ //TODO: get rid of this callback, it should be universal
+ //private const string CallbackFunction = "CordovaMediaonStatus";
+
+ #endregion
+
+
+ /// <summary>
+ /// The AudioHandler object
+ /// </summary>
+ private Media handler;
+
+ /// <summary>
+ /// Temporary buffer to store audio chunk
+ /// </summary>
+ private byte[] buffer;
+
+ /// <summary>
+ /// Xna game loop dispatcher
+ /// </summary>
+ DispatcherTimer dtXna;
+
+
+ /// <summary>
+ /// Output buffer
+ /// </summary>
+ private MemoryStream memoryStream;
+
+ /// <summary>
+ /// The id of this player (used to identify Media object in JavaScript)
+ /// </summary>
+ private String id;
+
+ /// <summary>
+ /// State of recording or playback
+ /// </summary>
+ private int state = PlayerState_None;
+
+ /// <summary>
+ /// File name to play or record to
+ /// </summary>
+ private String audioFile = null;
+
+ /// <summary>
+ /// Duration of audio
+ /// </summary>
+ private double duration = -1;
+
+ /// <summary>
+ /// Audio player object
+ /// </summary>
+ private MediaElement player = null;
+
+ /// <summary>
+ /// Audio source
+ /// </summary>
+ private Microphone recorder;
+
+ /// <summary>
+ /// Internal flag specified that we should only open audio w/o playing it
+ /// </summary>
+ private bool prepareOnly = false;
+
+ /// <summary>
+ /// Creates AudioPlayer instance
+ /// </summary>
+ /// <param name="handler">Media object</param>
+ /// <param name="id">player id</param>
+ public AudioPlayer(Media handler, String id)
+ {
+ this.handler = handler;
+ this.id = id;
+ }
+
+
+ /// <summary>
+ /// Destroys player and stop audio playing or recording
+ /// </summary>
+ public void Dispose()
+ {
+ if (this.player != null)
+ {
+ this.stopPlaying();
+ this.player = null;
+ }
+ if (this.recorder != null)
+ {
+ this.stopRecording();
+ this.recorder = null;
+ }
+
+ this.FinalizeXnaGameLoop();
+ }
+
+ private void InvokeCallback(int message, string value, bool removeHandler)
+ {
+ string args = string.Format("('{0}',{1},{2});", this.id, message, value);
+ string callback = @"(function(id,msg,value){
+ try {
+ if (msg == Media.MEDIA_ERROR) {
+ value = {'code':value};
+ }
+ Media.onStatus(id,msg,value);
+ }
+ catch(e) {
+ console.log('Error calling Media.onStatus :: ' + e);
+ }
+ })" + args;
+ this.handler.InvokeCustomScript(new ScriptCallback("eval", new string[] { callback }), false);
+ }
+
+ private void InvokeCallback(int message, int value, bool removeHandler)
+ {
+ InvokeCallback(message, value.ToString(), removeHandler);
+ }
+
+ private void InvokeCallback(int message, double value, bool removeHandler)
+ {
+ InvokeCallback(message, value.ToString(), removeHandler);
+ }
+
+ /// <summary>
+ /// Starts recording, data is stored in memory
+ /// </summary>
+ /// <param name="filePath"></param>
+ public void startRecording(string filePath)
+ {
+ if (this.player != null)
+ {
+ InvokeCallback(MediaError, MediaErrorPlayModeSet, false);
+ }
+ else if (this.recorder == null)
+ {
+ try
+ {
+ this.audioFile = filePath;
+ this.InitializeXnaGameLoop();
+ this.recorder = Microphone.Default;
+ this.recorder.BufferDuration = TimeSpan.FromMilliseconds(500);
+ this.buffer = new byte[recorder.GetSampleSizeInBytes(this.recorder.BufferDuration)];
+ this.recorder.BufferReady += new EventHandler<EventArgs>(recorderBufferReady);
+ MemoryStream stream = new MemoryStream();
+ this.memoryStream = stream;
+ int numBits = 16;
+ int numBytes = numBits / 8;
+
+ // inline version from AudioFormatsHelper
+ stream.Write(System.Text.Encoding.UTF8.GetBytes("RIFF"), 0, 4);
+ stream.Write(BitConverter.GetBytes(0), 0, 4);
+ stream.Write(System.Text.Encoding.UTF8.GetBytes("WAVE"), 0, 4);
+ stream.Write(System.Text.Encoding.UTF8.GetBytes("fmt "), 0, 4);
+ stream.Write(BitConverter.GetBytes(16), 0, 4);
+ stream.Write(BitConverter.GetBytes((short)1), 0, 2);
+ stream.Write(BitConverter.GetBytes((short)1), 0, 2);
+ stream.Write(BitConverter.GetBytes(this.recorder.SampleRate), 0, 4);
+ stream.Write(BitConverter.GetBytes(this.recorder.SampleRate * numBytes), 0, 4);
+ stream.Write(BitConverter.GetBytes((short)(numBytes)), 0, 2);
+ stream.Write(BitConverter.GetBytes((short)(numBits)), 0, 2);
+ stream.Write(System.Text.Encoding.UTF8.GetBytes("data"), 0, 4);
+ stream.Write(BitConverter.GetBytes(0), 0, 4);
+
+ this.recorder.Start();
+ FrameworkDispatcher.Update();
+ this.SetState(PlayerState_Running);
+ }
+ catch (Exception)
+ {
+ InvokeCallback(MediaError, MediaErrorStartingRecording, false);
+ //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorStartingRecording),false);
+ }
+ }
+ else
+ {
+ InvokeCallback(MediaError, MediaErrorAlreadyRecording, false);
+ //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorAlreadyRecording),false);
+ }
+ }
+
+ /// <summary>
+ /// Stops recording
+ /// </summary>
+ public void stopRecording()
+ {
+ if (this.recorder != null)
+ {
+ if (this.state == PlayerState_Running)
+ {
+ try
+ {
+ this.recorder.Stop();
+ this.recorder.BufferReady -= recorderBufferReady;
+ this.recorder = null;
+ SaveAudioClipToLocalStorage();
+ this.FinalizeXnaGameLoop();
+ this.SetState(PlayerState_Stopped);
+ }
+ catch (Exception)
+ {
+ //TODO
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Starts or resume playing audio file
+ /// </summary>
+ /// <param name="filePath">The name of the audio file</param>
+ /// <summary>
+ /// Starts or resume playing audio file
+ /// </summary>
+ /// <param name="filePath">The name of the audio file</param>
+ public void startPlaying(string filePath)
+ {
+ if (this.recorder != null)
+ {
+ InvokeCallback(MediaError, MediaErrorRecordModeSet, false);
+ //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorRecordModeSet),false);
+ return;
+ }
+
+
+ if (this.player == null || this.player.Source.AbsolutePath.LastIndexOf(filePath) < 0)
+ {
+ try
+ {
+ // this.player is a MediaElement, it must be added to the visual tree in order to play
+ PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame;
+ if (frame != null)
+ {
+ PhoneApplicationPage page = frame.Content as PhoneApplicationPage;
+ if (page != null)
+ {
+ Grid grid = page.FindName("LayoutRoot") as Grid;
+ if (grid != null)
+ {
+
+ this.player = grid.FindName("playerMediaElement") as MediaElement;
+ if (this.player == null) // still null ?
+ {
+ this.player = new MediaElement();
+ this.player.Name = "playerMediaElement";
+ grid.Children.Add(this.player);
+ this.player.Visibility = Visibility.Visible;
+ }
+ if (this.player.CurrentState == System.Windows.Media.MediaElementState.Playing)
+ {
+ this.player.Stop(); // stop it!
+ }
+
+ this.player.Source = null; // Garbage collect it.
+ this.player.MediaOpened += MediaOpened;
+ this.player.MediaEnded += MediaEnded;
+ this.player.MediaFailed += MediaFailed;
+ }
+ }
+ }
+
+ this.audioFile = filePath;
+
+ Uri uri = new Uri(filePath, UriKind.RelativeOrAbsolute);
+ if (uri.IsAbsoluteUri)
+ {
+ this.player.Source = uri;
+ }
+ else
+ {
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (!isoFile.FileExists(filePath))
+ {
+ // try to unpack it from the dll into isolated storage
+ StreamResourceInfo fileResourceStreamInfo = Application.GetResourceStream(new Uri(filePath, UriKind.Relative));
+ if (fileResourceStreamInfo != null)
+ {
+ using (BinaryReader br = new BinaryReader(fileResourceStreamInfo.Stream))
+ {
+ byte[] data = br.ReadBytes((int)fileResourceStreamInfo.Stream.Length);
+
+ string[] dirParts = filePath.Split('/');
+ string dirName = "";
+ for (int n = 0; n < dirParts.Length - 1; n++)
+ {
+ dirName += dirParts[n] + "/";
+ }
+ if (!isoFile.DirectoryExists(dirName))
+ {
+ isoFile.CreateDirectory(dirName);
+ }
+
+ using (IsolatedStorageFileStream outFile = isoFile.OpenFile(filePath, FileMode.Create))
+ {
+ using (BinaryWriter writer = new BinaryWriter(outFile))
+ {
+ writer.Write(data);
+ }
+ }
+ }
+ }
+ }
+ if (isoFile.FileExists(filePath))
+ {
+ using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Open, isoFile))
+ {
+ this.player.SetSource(stream);
+ }
+ }
+ else
+ {
+ InvokeCallback(MediaError, MediaErrorPlayModeSet, false);
+ //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, 1), false);
+ return;
+ }
+ }
+ }
+ this.SetState(PlayerState_Starting);
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine("Error in AudioPlayer::startPlaying : " + e.Message);
+ InvokeCallback(MediaError, MediaErrorStartingPlayback, false);
+ //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorStartingPlayback),false);
+ }
+ }
+ else
+ {
+ if (this.state != PlayerState_Running)
+ {
+ this.player.Play();
+ this.SetState(PlayerState_Running);
+ }
+ else
+ {
+ InvokeCallback(MediaError, MediaErrorResumeState, false);
+ //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorResumeState),false);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Callback to be invoked when the media source is ready for playback
+ /// </summary>
+ private void MediaOpened(object sender, RoutedEventArgs arg)
+ {
+ if (this.player != null)
+ {
+ this.duration = this.player.NaturalDuration.TimeSpan.TotalSeconds;
+ InvokeCallback(MediaDuration, this.duration, false);
+ //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaDuration, this.duration),false);
+ if (!this.prepareOnly)
+ {
+ this.player.Play();
+ this.SetState(PlayerState_Running);
+ }
+ this.prepareOnly = false;
+ }
+ else
+ {
+ // TODO: occasionally MediaOpened is signalled, but player is null
+ }
+ }
+
+ /// <summary>
+ /// Callback to be invoked when playback of a media source has completed
+ /// </summary>
+ private void MediaEnded(object sender, RoutedEventArgs arg)
+ {
+ this.SetState(PlayerState_Stopped);
+ }
+
+ /// <summary>
+ /// Callback to be invoked when playback of a media source has failed
+ /// </summary>
+ private void MediaFailed(object sender, RoutedEventArgs arg)
+ {
+ player.Stop();
+ InvokeCallback(MediaError, MediaErrorStartingPlayback, false);
+ //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError.ToString(), "Media failed"),false);
+ }
+
+ /// <summary>
+ /// Seek or jump to a new time in the track
+ /// </summary>
+ /// <param name="milliseconds">The new track position</param>
+ public void seekToPlaying(int milliseconds)
+ {
+ if (this.player != null)
+ {
+ TimeSpan tsPos = new TimeSpan(0, 0, 0, 0, milliseconds);
+ this.player.Position = tsPos;
+ InvokeCallback(MediaPosition, milliseconds / 1000.0f, false);
+ //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaPosition, milliseconds / 1000.0f),false);
+ }
+ }
+
+ /// <summary>
+ /// Set the volume of the player
+ /// </summary>
+ /// <param name="vol">volume 0.0-1.0, default value is 0.5</param>
+ public void setVolume(double vol)
+ {
+ if (this.player != null)
+ {
+ this.player.Volume = vol;
+ }
+ }
+
+ /// <summary>
+ /// Pauses playing
+ /// </summary>
+ public void pausePlaying()
+ {
+ if (this.state == PlayerState_Running)
+ {
+ this.player.Pause();
+ this.SetState(PlayerState_Paused);
+ }
+ else
+ {
+ InvokeCallback(MediaError, MediaErrorPauseState, false);
+ //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorPauseState),false);
+ }
+ }
+
+
+ /// <summary>
+ /// Stops playing the audio file
+ /// </summary>
+ public void stopPlaying()
+ {
+ if ((this.state == PlayerState_Running) || (this.state == PlayerState_Paused))
+ {
+ this.player.Stop();
+
+ this.player.Position = new TimeSpan(0L);
+ this.SetState(PlayerState_Stopped);
+ }
+ //else // Why is it an error to call stop on a stopped media?
+ //{
+ // this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorStopState), false);
+ //}
+ }
+
+ /// <summary>
+ /// Gets current position of playback
+ /// </summary>
+ /// <returns>current position</returns>
+ public double getCurrentPosition()
+ {
+ if ((this.state == PlayerState_Running) || (this.state == PlayerState_Paused))
+ {
+ double currentPosition = this.player.Position.TotalSeconds;
+ //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaPosition, currentPosition),false);
+ return currentPosition;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ /// <summary>
+ /// Gets the duration of the audio file
+ /// </summary>
+ /// <param name="filePath">The name of the audio file</param>
+ /// <returns>track duration</returns>
+ public double getDuration(string filePath)
+ {
+ if (this.recorder != null)
+ {
+ return (-2);
+ }
+
+ if (this.player != null)
+ {
+ return this.duration;
+
+ }
+ else
+ {
+ this.prepareOnly = true;
+ this.startPlaying(filePath);
+ return this.duration;
+ }
+ }
+
+ /// <summary>
+ /// Sets the state and send it to JavaScript
+ /// </summary>
+ /// <param name="state">state</param>
+ private void SetState(int state)
+ {
+ if (this.state != state)
+ {
+ InvokeCallback(MediaState, state, false);
+ //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaState, state),false);
+ }
+
+ this.state = state;
+ }
+
+ #region record methods
+
+ /// <summary>
+ /// Copies data from recorder to memory storages and updates recording state
+ /// </summary>
+ /// <param name="sender"></param>
+ /// <param name="e"></param>
+ private void recorderBufferReady(object sender, EventArgs e)
+ {
+ this.recorder.GetData(this.buffer);
+ this.memoryStream.Write(this.buffer, 0, this.buffer.Length);
+ }
+
+ /// <summary>
+ /// Writes audio data from memory to isolated storage
+ /// </summary>
+ /// <returns></returns>
+ private void SaveAudioClipToLocalStorage()
+ {
+ if (memoryStream == null || memoryStream.Length <= 0)
+ {
+ return;
+ }
+
+ long position = memoryStream.Position;
+ memoryStream.Seek(4, SeekOrigin.Begin);
+ memoryStream.Write(BitConverter.GetBytes((int)memoryStream.Length - 8), 0, 4);
+ memoryStream.Seek(40, SeekOrigin.Begin);
+ memoryStream.Write(BitConverter.GetBytes((int)memoryStream.Length - 44), 0, 4);
+ memoryStream.Seek(position, SeekOrigin.Begin);
+
+ try
+ {
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ string directory = Path.GetDirectoryName(audioFile);
+
+ if (!isoFile.DirectoryExists(directory))
+ {
+ isoFile.CreateDirectory(directory);
+ }
+
+ this.memoryStream.Seek(0, SeekOrigin.Begin);
+
+ using (IsolatedStorageFileStream fileStream = isoFile.CreateFile(audioFile))
+ {
+ this.memoryStream.CopyTo(fileStream);
+ }
+ }
+ }
+ catch (Exception)
+ {
+ //TODO: log or do something else
+ throw;
+ }
+ }
+
+ #region Xna loop
+ /// <summary>
+ /// Special initialization required for the microphone: XNA game loop
+ /// </summary>
+ private void InitializeXnaGameLoop()
+ {
+ // Timer to simulate the XNA game loop (Microphone is from XNA)
+ this.dtXna = new DispatcherTimer();
+ this.dtXna.Interval = TimeSpan.FromMilliseconds(33);
+ this.dtXna.Tick += delegate { try { FrameworkDispatcher.Update(); } catch { } };
+ this.dtXna.Start();
+ }
+ /// <summary>
+ /// Finalizes XNA game loop for microphone
+ /// </summary>
+ private void FinalizeXnaGameLoop()
+ {
+ // Timer to simulate the XNA game loop (Microphone is from XNA)
+ if (this.dtXna != null)
+ {
+ this.dtXna.Stop();
+ this.dtXna = null;
+ }
+ }
+
+ #endregion
+
+ #endregion
+ }
+}
diff --git a/plugins/org.apache.cordova.media/src/wp/Media.cs b/plugins/org.apache.cordova.media/src/wp/Media.cs
new file mode 100644
index 00000000..aedd2bb6
--- /dev/null
+++ b/plugins/org.apache.cordova.media/src/wp/Media.cs
@@ -0,0 +1,590 @@
+/*
+ Licensed 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.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Windows;
+using System.Diagnostics;
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+ /// <summary>
+ /// Provides the ability to record and play back audio files on a device.
+ /// </summary>
+ public class Media : BaseCommand
+ {
+ /// <summary>
+ /// Audio player objects
+ /// </summary>
+ private static Dictionary<string, AudioPlayer> players = new Dictionary<string, AudioPlayer>();
+
+ /// <summary>
+ /// Represents Media action options.
+ /// </summary>
+ [DataContract]
+ public class MediaOptions
+ {
+ /// <summary>
+ /// Audio id
+ /// </summary>
+ [DataMember(Name = "id", IsRequired = true)]
+ public string Id { get; set; }
+
+ /// <summary>
+ /// Path to audio file
+ /// </summary>
+ [DataMember(Name = "src")]
+ public string Src { get; set; }
+
+ /// <summary>
+ /// New track position
+ /// </summary>
+ [DataMember(Name = "milliseconds")]
+ public int Milliseconds { get; set; }
+
+ public string CallbackId { get; set; }
+ }
+
+ /// <summary>
+ /// Releases the audio player instance to save memory.
+ /// </summary>
+ public void release(string options)
+ {
+ string callbackId = this.CurrentCommandCallbackId;
+ try
+ {
+ MediaOptions mediaOptions = new MediaOptions();
+
+ try
+ {
+ string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options);
+ mediaOptions.Id = optionsString[0];
+ callbackId = mediaOptions.CallbackId = optionsString[1];
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId);
+ return;
+ }
+
+ if (!Media.players.ContainsKey(mediaOptions.Id))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, false), callbackId);
+ return;
+ }
+
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ try
+ {
+ AudioPlayer audio = Media.players[mediaOptions.Id];
+ Media.players.Remove(mediaOptions.Id);
+ audio.Dispose();
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, true), mediaOptions.CallbackId);
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), mediaOptions.CallbackId);
+ }
+ });
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId);
+ }
+ }
+
+ private AudioPlayer GetOrCreatePlayerById(string id)
+ {
+ AudioPlayer audio = null;
+
+ lock (Media.players)
+ {
+ if (!Media.players.TryGetValue(id, out audio))
+ {
+ audio = new AudioPlayer(this, id);
+ Media.players.Add(id, audio);
+ Debug.WriteLine("Media Created in GetOrCreatePlayerById");
+ }
+ }
+
+
+
+ return audio;
+ }
+
+ /// <summary>
+ /// Starts recording and save the specified file
+ /// </summary>
+ public void startRecordingAudio(string options)
+ {
+ string callbackId = this.CurrentCommandCallbackId;
+ try
+ {
+ MediaOptions mediaOptions = new MediaOptions();
+
+ try
+ {
+ string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options);
+ mediaOptions.Id = optionsString[0];
+ mediaOptions.Src = optionsString[1];
+ callbackId = mediaOptions.CallbackId = optionsString[2];
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), mediaOptions.CallbackId);
+ return;
+ }
+
+ if (mediaOptions != null)
+ {
+
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ try
+ {
+ AudioPlayer audio;
+ if (!Media.players.ContainsKey(mediaOptions.Id))
+ {
+ audio = new AudioPlayer(this, mediaOptions.Id);
+ Media.players.Add(mediaOptions.Id, audio);
+ }
+ else
+ {
+ audio = Media.players[mediaOptions.Id];
+ }
+
+ if (audio != null)
+ {
+ audio.startRecording(mediaOptions.Src);
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK), mediaOptions.CallbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR,
+ "Error accessing AudioPlayer for key " + mediaOptions.Id), mediaOptions.CallbackId);
+ }
+
+
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), mediaOptions.CallbackId);
+ }
+
+ });
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), mediaOptions.CallbackId);
+ }
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId);
+ }
+ }
+
+ /// <summary>
+ /// Stops recording and save to the file specified when recording started
+ /// </summary>
+ public void stopRecordingAudio(string options)
+ {
+ string callbackId = this.CurrentCommandCallbackId;
+
+ try
+ {
+ string[] optStrings = JSON.JsonHelper.Deserialize<string[]>(options);
+ string mediaId = optStrings[0];
+ callbackId = optStrings[1];
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ try
+ {
+ if (Media.players.ContainsKey(mediaId))
+ {
+ AudioPlayer audio = Media.players[mediaId];
+ audio.stopRecording();
+ Media.players.Remove(mediaId);
+ }
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId);
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId);
+ }
+ });
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId);
+ }
+ }
+
+ public void setVolume(string options) // id,volume
+ {
+ string callbackId = this.CurrentCommandCallbackId;
+ try
+ {
+ string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options);
+ string id = optionsString[0];
+ double volume = 0.0d;
+ double.TryParse(optionsString[1], out volume);
+
+ callbackId = optionsString[2];
+
+ if (Media.players.ContainsKey(id))
+ {
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ try
+ {
+ AudioPlayer player = Media.players[id];
+ player.setVolume(volume);
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId);
+ }
+ });
+ }
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ "Error parsing options into setVolume method"), callbackId);
+ }
+ }
+
+ // Some Audio Notes:
+ // In the Windows Phone Emulator, playback of video or audio content using the MediaElement control is not supported.
+ // While playing, a MediaElement stops all other media playback on the phone.
+ // Multiple MediaElement controls are NOT supported
+
+ // Called when you create a new Media('blah.wav') object in JS.
+ public void create(string options)
+ {
+ string callbackId = this.CurrentCommandCallbackId;
+ try
+ {
+ MediaOptions mediaOptions;
+ try
+ {
+ string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options);
+ mediaOptions = new MediaOptions();
+ mediaOptions.Id = optionsString[0];
+ mediaOptions.Src = optionsString[1];
+ callbackId = mediaOptions.CallbackId = optionsString[2];
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ "Error parsing options into create method"), callbackId);
+ return;
+ }
+
+ GetOrCreatePlayerById(mediaOptions.Id);
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId);
+
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId);
+ }
+ }
+
+ /// <summary>
+ /// Starts or resume playing audio file
+ /// </summary>
+ public void startPlayingAudio(string options)
+ {
+ string callbackId = this.CurrentCommandCallbackId;
+ try
+ {
+ MediaOptions mediaOptions;
+ try
+ {
+ string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options);
+ mediaOptions = new MediaOptions();
+ mediaOptions.Id = optionsString[0];
+ mediaOptions.Src = optionsString[1];
+ int msec = 0;
+ if (int.TryParse(optionsString[2], out msec))
+ {
+ mediaOptions.Milliseconds = msec;
+ }
+ callbackId = mediaOptions.CallbackId = optionsString[3];
+
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId);
+ return;
+ }
+
+ AudioPlayer audio = GetOrCreatePlayerById(mediaOptions.Id);
+
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ try
+ {
+ audio.startPlaying(mediaOptions.Src);
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId);
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId);
+ }
+ });
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId);
+ }
+ }
+
+
+ /// <summary>
+ /// Seeks to a location
+ /// </summary>
+ public void seekToAudio(string options)
+ {
+ string callbackId = this.CurrentCommandCallbackId;
+ try
+ {
+ MediaOptions mediaOptions;
+
+ try
+ {
+ string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options);
+ mediaOptions = new MediaOptions();
+ mediaOptions.Id = optionsString[0];
+ int msec = 0;
+ if (int.TryParse(optionsString[2], out msec))
+ {
+ mediaOptions.Milliseconds = msec;
+ }
+ callbackId = mediaOptions.CallbackId = optionsString[3];
+
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId);
+ return;
+ }
+
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ try
+ {
+ if (Media.players.ContainsKey(mediaOptions.Id))
+ {
+ AudioPlayer audio = Media.players[mediaOptions.Id];
+ audio.seekToPlaying(mediaOptions.Milliseconds);
+ }
+ else
+ {
+ Debug.WriteLine("ERROR: seekToAudio could not find mediaPlayer for " + mediaOptions.Id);
+ }
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId);
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId);
+ }
+ });
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId);
+ }
+ }
+
+ /// <summary>
+ /// Pauses playing
+ /// </summary>
+ public void pausePlayingAudio(string options)
+ {
+ string callbackId = this.CurrentCommandCallbackId;
+ try
+ {
+ string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options);
+ string mediaId = optionsString[0];
+ callbackId = optionsString[1];
+
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ try
+ {
+ if (Media.players.ContainsKey(mediaId))
+ {
+ AudioPlayer audio = Media.players[mediaId];
+ audio.pausePlaying();
+ }
+ else
+ {
+ Debug.WriteLine("ERROR: pausePlayingAudio could not find mediaPlayer for " + mediaId);
+ }
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId);
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message),callbackId);
+ }
+ });
+
+
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION),callbackId);
+ }
+
+
+ }
+
+
+ /// <summary>
+ /// Stops playing the audio file
+ /// </summary>
+ public void stopPlayingAudio(String options)
+ {
+ string callbackId = this.CurrentCommandCallbackId;
+ try
+ {
+ string[] optionsStrings = JSON.JsonHelper.Deserialize<string[]>(options);
+ string mediaId = optionsStrings[0];
+ callbackId = optionsStrings[1];
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ try
+ {
+ if (Media.players.ContainsKey(mediaId))
+ {
+ AudioPlayer audio = Media.players[mediaId];
+ audio.stopPlaying();
+ }
+ else
+ {
+ Debug.WriteLine("stopPlaying could not find mediaPlayer for " + mediaId);
+ }
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId);
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId);
+ }
+ });
+
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId);
+ }
+ }
+
+ /// <summary>
+ /// Gets current position of playback
+ /// </summary>
+ public void getCurrentPositionAudio(string options)
+ {
+ string callbackId = this.CurrentCommandCallbackId;
+ try
+ {
+ string[] optionsStrings = JSON.JsonHelper.Deserialize<string[]>(options);
+ string mediaId = optionsStrings[0];
+ callbackId = optionsStrings[1];
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ try
+ {
+ if (Media.players.ContainsKey(mediaId))
+ {
+ AudioPlayer audio = Media.players[mediaId];
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, audio.getCurrentPosition()), callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, -1), callbackId);
+ }
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId);
+ }
+ });
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId);
+ return;
+ }
+ }
+
+
+ /// <summary>
+ /// Gets the duration of the audio file
+ /// </summary>
+
+ [Obsolete("This method will be removed shortly")]
+ public void getDurationAudio(string options)
+ {
+ string callbackId = this.CurrentCommandCallbackId;
+ try
+ {
+ MediaOptions mediaOptions;
+
+ try
+ {
+ string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options);
+
+ mediaOptions = new MediaOptions();
+ mediaOptions.Id = optionsString[0];
+ mediaOptions.Src = optionsString[1];
+ callbackId = mediaOptions.CallbackId = optionsString[2];
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId);
+ return;
+ }
+
+ AudioPlayer audio;
+ if (Media.players.ContainsKey(mediaOptions.Id))
+ {
+ audio = Media.players[mediaOptions.Id];
+ }
+ else
+ {
+ Debug.WriteLine("ERROR: getDurationAudio could not find mediaPlayer for " + mediaOptions.Id);
+ audio = new AudioPlayer(this, mediaOptions.Id);
+ Media.players.Add(mediaOptions.Id, audio);
+ }
+
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, audio.getDuration(mediaOptions.Src)), callbackId);
+ });
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId);
+ }
+ }
+ }
+}