You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by pu...@apache.org on 2012/02/22 09:15:23 UTC
[17/29] wp7 commit: stage 2 - fix errors
stage 2 - fix errors
Project: http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/commit/4789ce6d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/tree/4789ce6d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/diff/4789ce6d
Branch: refs/heads/master
Commit: 4789ce6d30e49e085c4d792ede0555e1bdc11386
Parents: 552bc14
Author: Jesse MacFadyen <pu...@gmail.com>
Authored: Thu Feb 9 15:40:07 2012 -0800
Committer: Jesse MacFadyen <pu...@gmail.com>
Committed: Thu Feb 9 15:40:07 2012 -0800
----------------------------------------------------------------------
framework/Cordova/CommandFactory.cs | 104 ++
framework/Cordova/Commands/Accelerometer.cs | 314 +++++
framework/Cordova/Commands/AudioPlayer.cs | 593 ++++++++
framework/Cordova/Commands/BaseCommand.cs | 127 ++
framework/Cordova/Commands/Camera.cs | 308 +++++
framework/Cordova/Commands/Capture.cs | 710 ++++++++++
framework/Cordova/Commands/Compass.cs | 343 +++++
framework/Cordova/Commands/Connection.cs | 89 ++
framework/Cordova/Commands/Contacts.cs | 527 +++++++
framework/Cordova/Commands/DebugConsole.cs | 49 +
framework/Cordova/Commands/Device.cs | 123 ++
framework/Cordova/Commands/File.cs | 1430 ++++++++++++++++++++
framework/Cordova/Commands/FileTransfer.cs | 317 +++++
framework/Cordova/Commands/GeoLocation.cs | 34 +
framework/Cordova/Commands/Media.cs | 450 ++++++
framework/Cordova/Commands/MimeTypeMapper.cs | 99 ++
framework/Cordova/Commands/Notification.cs | 203 +++
framework/Cordova/DOMStorageHelper.cs | 145 ++
framework/Cordova/JSON/JsonHelper.cs | 86 ++
framework/Cordova/NativeExecution.cs | 204 +++
framework/Cordova/OrientationHelper.cs | 128 ++
framework/Cordova/PhoneGapCommandCall.cs | 86 ++
framework/Cordova/PluginResult.cs | 161 +++
framework/Cordova/ScriptCallback.cs | 68 +
framework/Cordova/UI/AudioCaptureTask.cs | 105 ++
framework/Cordova/UI/AudioRecorder.xaml | 48 +
framework/Cordova/UI/AudioRecorder.xaml.cs | 385 ++++++
framework/Cordova/UI/VideoCaptureTask.cs | 104 ++
framework/Cordova/UI/VideoRecorder.xaml | 34 +
framework/Cordova/UI/VideoRecorder.xaml.cs | 405 ++++++
framework/PhoneGap/CommandFactory.cs | 104 --
framework/PhoneGap/Commands/Accelerometer.cs | 314 -----
framework/PhoneGap/Commands/AudioPlayer.cs | 593 --------
framework/PhoneGap/Commands/BaseCommand.cs | 127 --
framework/PhoneGap/Commands/Camera.cs | 308 -----
framework/PhoneGap/Commands/Capture.cs | 710 ----------
framework/PhoneGap/Commands/Compass.cs | 343 -----
framework/PhoneGap/Commands/Connection.cs | 89 --
framework/PhoneGap/Commands/Contacts.cs | 527 -------
framework/PhoneGap/Commands/DebugConsole.cs | 49 -
framework/PhoneGap/Commands/Device.cs | 123 --
framework/PhoneGap/Commands/File.cs | 1430 --------------------
framework/PhoneGap/Commands/FileTransfer.cs | 317 -----
framework/PhoneGap/Commands/GeoLocation.cs | 34 -
framework/PhoneGap/Commands/Media.cs | 450 ------
framework/PhoneGap/Commands/MimeTypeMapper.cs | 99 --
framework/PhoneGap/Commands/Notification.cs | 203 ---
framework/PhoneGap/DOMStorageHelper.cs | 145 --
framework/PhoneGap/JSON/JsonHelper.cs | 86 --
framework/PhoneGap/NativeExecution.cs | 204 ---
framework/PhoneGap/OrientationHelper.cs | 128 --
framework/PhoneGap/PhoneGapCommandCall.cs | 86 --
framework/PhoneGap/PluginResult.cs | 161 ---
framework/PhoneGap/ScriptCallback.cs | 68 -
framework/PhoneGap/UI/AudioCaptureTask.cs | 105 --
framework/PhoneGap/UI/AudioRecorder.xaml | 48 -
framework/PhoneGap/UI/AudioRecorder.xaml.cs | 385 ------
framework/PhoneGap/UI/VideoCaptureTask.cs | 104 --
framework/PhoneGap/UI/VideoRecorder.xaml | 34 -
framework/PhoneGap/UI/VideoRecorder.xaml.cs | 405 ------
framework/WP7GapClassLib.csproj | 60 +-
61 files changed, 7809 insertions(+), 7809 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/blob/4789ce6d/framework/Cordova/CommandFactory.cs
----------------------------------------------------------------------
diff --git a/framework/Cordova/CommandFactory.cs b/framework/Cordova/CommandFactory.cs
new file mode 100644
index 0000000..3518981
--- /dev/null
+++ b/framework/Cordova/CommandFactory.cs
@@ -0,0 +1,104 @@
+/*
+ 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.Net;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Ink;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Shapes;
+using System.Collections.Generic;
+using WP7GapClassLib.Cordova.Commands;
+using System.Reflection;
+using System.Diagnostics;
+
+namespace WP7GapClassLib.Cordova
+{
+ /// <summary>
+ /// Provides functionality to create phone gap command by name.
+ /// </summary>
+ public static class CommandFactory
+ {
+ /// <summary>
+ /// Represents predefined namespace name for custom plugins
+ /// </summary>
+ private static readonly string CustomPluginNamespacePrefix = "Cordova.Extension.Commands.";
+
+ private static readonly string BaseCommandNamespacePrefix = "WP7GapClassLib.Cordova.Commands.";
+
+ /// <summary>
+ /// Performance optimization allowing more faster create already known commands.
+ /// </summary>
+
+ private static Dictionary<string, Type> commandMap = new Dictionary<string, Type>();
+
+ /// <summary>
+ /// Creates command using command class name. Returns null for unknown commands.
+ /// </summary>
+ /// <param name="service">Command class name, for example Device or Notification</param>
+ /// <returns>Command class instance or null</returns>
+ public static BaseCommand CreateByServiceName(string service)
+ {
+
+ if (string.IsNullOrEmpty(service))
+ {
+ throw new ArgumentNullException("service", "service to create can't be null");
+ }
+
+ if (!commandMap.ContainsKey(service))
+ {
+
+ Type t = Type.GetType(BaseCommandNamespacePrefix + service);
+
+ // custom plugin could be defined in own namespace and assembly
+ if (t == null)
+ {
+ string serviceFullName = service.Contains(".") ? service : CustomPluginNamespacePrefix + service;
+
+ foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
+ {
+ // in this case service name represents full type name including namespace
+ t = a.GetType(serviceFullName);
+
+ if (t == null) // try the Commands Namespace
+ {
+ t = a.GetType(BaseCommandNamespacePrefix + service);
+ }
+
+ if (t != null)
+ {
+ break;
+ }
+ }
+
+ }
+
+ // unknown command, still didn't find it
+ if (t == null)
+ {
+ Debug.WriteLine("Unable to locate command :: " + service);
+ return null;
+ }
+
+ commandMap[service] = t;
+ }
+
+ return Activator.CreateInstance(commandMap[service]) as BaseCommand;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/blob/4789ce6d/framework/Cordova/Commands/Accelerometer.cs
----------------------------------------------------------------------
diff --git a/framework/Cordova/Commands/Accelerometer.cs b/framework/Cordova/Commands/Accelerometer.cs
new file mode 100644
index 0000000..8c39780
--- /dev/null
+++ b/framework/Cordova/Commands/Accelerometer.cs
@@ -0,0 +1,314 @@
+/*
+ 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.Threading;
+using Microsoft.Devices.Sensors;
+using System.Globalization;
+
+namespace WP7GapClassLib.Cordova.Commands
+{
+ /// <summary>
+ /// Captures device motion in the x, y, and z direction.
+ /// </summary>
+ public class Accelerometer : BaseCommand
+ {
+#region AccelerometerOptions class
+ /// <summary>
+ /// Represents Accelerometer options.
+ /// </summary>
+ [DataContract]
+ public class AccelerometerOptions
+ {
+ /// <summary>
+ /// How often to retrieve the Acceleration in milliseconds
+ /// </summary>
+ [DataMember(IsRequired = false, Name = "frequency")]
+ public int Frequency { get; set; }
+
+ /// <summary>
+ /// Watcher id
+ /// </summary>
+ [DataMember(IsRequired = false, Name = "id")]
+ public string Id { get; set; }
+
+ /// <summary>
+ /// Creates options object with default parameters
+ /// </summary>
+ public AccelerometerOptions()
+ {
+ this.SetDefaultValues(new StreamingContext());
+ }
+
+ /// <summary>
+ /// Initializes default values for class fields.
+ /// Implemented in separate method because default constructor is not invoked during deserialization.
+ /// </summary>
+ /// <param name="context"></param>
+ [OnDeserializing()]
+ public void SetDefaultValues(StreamingContext context)
+ {
+ this.Frequency = 10000;
+ }
+ }
+
+ #endregion
+
+#region Status codes
+
+ public const int Stopped = 0;
+ public const int Starting = 1;
+ public const int Running = 2;
+ public const int ErrorFailedToStart = 3;
+
+ #endregion
+
+#region Static members
+
+ /// <summary>
+ /// Status of listener
+ /// </summary>
+ private static int currentStatus;
+
+ /// <summary>
+ /// Id for get getAcceleration method
+ /// </summary>
+ private static string getAccelId = "getAccelId";
+
+ /// <summary>
+ /// Accelerometer
+ /// </summary>
+ private static Microsoft.Devices.Sensors.Accelerometer accelerometer = new Microsoft.Devices.Sensors.Accelerometer();
+
+ /// <summary>
+ /// Listeners for callbacks
+ /// </summary>
+ private static Dictionary<string, Accelerometer> watchers = new Dictionary<string, Accelerometer>();
+
+ #endregion
+
+ /// <summary>
+ /// Time the value was last changed
+ /// </summary>
+ private DateTime lastValueChangedTime;
+
+ /// <summary>
+ /// Accelerometer options
+ /// </summary>
+ private AccelerometerOptions accelOptions;
+
+ /// <summary>
+ /// Start listening for acceleration sensor
+ /// </summary>
+ public void startWatch(string options)
+ {
+ try
+ {
+ accelOptions = JSON.JsonHelper.Deserialize<AccelerometerOptions>(options);
+ }
+ catch (Exception ex)
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+ return;
+ }
+
+ if (string.IsNullOrEmpty(accelOptions.Id))
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+ return;
+ }
+
+ try
+ {
+ lock (accelerometer)
+ {
+ watchers.Add(accelOptions.Id, this);
+ accelerometer.CurrentValueChanged += watchers[accelOptions.Id].accelerometer_CurrentValueChanged;
+ accelerometer.Start();
+ this.SetStatus(Starting);
+ }
+ }
+ catch (Exception)
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ErrorFailedToStart));
+ return;
+ }
+ }
+
+ /// <summary>
+ /// Stops listening to acceleration sensor
+ /// </summary>
+ public void stopWatch(string options)
+ {
+ try
+ {
+ accelOptions = JSON.JsonHelper.Deserialize<AccelerometerOptions>(options);
+ }
+ catch (Exception ex)
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+ return;
+ }
+
+ if (string.IsNullOrEmpty(accelOptions.Id))
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+ return;
+ }
+
+ if (currentStatus != Stopped)
+ {
+ lock (accelerometer)
+ {
+ Accelerometer watcher = watchers[accelOptions.Id];
+
+ watcher.Dispose();
+ accelerometer.CurrentValueChanged -= watcher.accelerometer_CurrentValueChanged;
+ watchers.Remove(accelOptions.Id);
+ }
+ }
+ this.SetStatus(Stopped);
+
+ this.DispatchCommandResult();
+ }
+
+ /// <summary>
+ /// Gets current accelerometer coordinates
+ /// </summary>
+ public void getAcceleration(string options)
+ {
+ try
+ {
+ if (currentStatus != Running)
+ {
+ int status = this.start();
+ if (status == ErrorFailedToStart)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, ErrorFailedToStart));
+ return;
+ }
+
+ long timeout = 2000;
+ while ((currentStatus == Starting) && (timeout > 0))
+ {
+ timeout = timeout - 100;
+ Thread.Sleep(100);
+ }
+
+ if (currentStatus != Running)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, ErrorFailedToStart));
+ return;
+ }
+ }
+ lock (accelerometer)
+ {
+ if (watchers.ContainsKey(getAccelId))
+ {
+ accelerometer.CurrentValueChanged -= watchers[getAccelId].accelerometer_CurrentValueChanged;
+ watchers.Remove(getAccelId);
+ }
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, GetCurrentAccelerationFormatted()));
+ }
+ }
+ catch (UnauthorizedAccessException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ILLEGAL_ACCESS_EXCEPTION, ErrorFailedToStart));
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ErrorFailedToStart));
+ }
+ }
+
+ /// <summary>
+ /// Sensor listener event
+ /// </summary>
+ private void accelerometer_CurrentValueChanged(object sender, SensorReadingEventArgs<AccelerometerReading> e)
+ {
+ this.SetStatus(Running);
+
+ if (accelOptions != null)
+ {
+ if (((DateTime.Now - lastValueChangedTime).TotalMilliseconds) > accelOptions.Frequency)
+ {
+ lastValueChangedTime = DateTime.Now;
+ PluginResult result = new PluginResult(PluginResult.Status.OK, GetCurrentAccelerationFormatted());
+ result.KeepCallback = true;
+ DispatchCommandResult(result);
+ }
+ }
+
+ if (watchers.Count == 0)
+ {
+ accelerometer.Stop();
+ this.SetStatus(Stopped);
+ }
+ }
+
+ /// <summary>
+ /// Starts listening for acceleration sensor
+ /// </summary>
+ /// <returns>status of listener</returns>
+ private int start()
+ {
+ if ((currentStatus == Running) || (currentStatus == Starting))
+ {
+ return currentStatus;
+ }
+ try
+ {
+ lock (accelerometer)
+ {
+ watchers.Add(getAccelId, this);
+ accelerometer.CurrentValueChanged += watchers[getAccelId].accelerometer_CurrentValueChanged;
+ accelerometer.Start();
+ this.SetStatus(Starting);
+ }
+ }
+ catch (Exception)
+ {
+ this.SetStatus(ErrorFailedToStart);
+ }
+ return currentStatus;
+ }
+
+ /// <summary>
+ /// Formats current coordinates into JSON format
+ /// </summary>
+ /// <returns>Coordinates in JSON format</returns>
+ private string GetCurrentAccelerationFormatted()
+ {
+ string resultCoordinates = String.Format("\"x\":{0},\"y\":{1},\"z\":{2}",
+ accelerometer.CurrentValue.Acceleration.X.ToString("0.00000",CultureInfo.InvariantCulture),
+ accelerometer.CurrentValue.Acceleration.Y.ToString("0.00000", CultureInfo.InvariantCulture),
+ accelerometer.CurrentValue.Acceleration.Z.ToString("0.00000", CultureInfo.InvariantCulture));
+ resultCoordinates = "{" + resultCoordinates + "}";
+ return resultCoordinates;
+ }
+
+ /// <summary>
+ /// Sets current status
+ /// </summary>
+ /// <param name="status">current status</param>
+ private void SetStatus(int status)
+ {
+ currentStatus = status;
+ }
+ }
+}
+
http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/blob/4789ce6d/framework/Cordova/Commands/AudioPlayer.cs
----------------------------------------------------------------------
diff --git a/framework/Cordova/Commands/AudioPlayer.cs b/framework/Cordova/Commands/AudioPlayer.cs
new file mode 100644
index 0000000..dcdc5ec
--- /dev/null
+++ b/framework/Cordova/Commands/AudioPlayer.cs
@@ -0,0 +1,593 @@
+/*
+ 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;
+
+namespace WP7GapClassLib.Cordova.Commands
+{
+ /// <summary>
+ /// Implements audio record and play back functionality.
+ /// </summary>
+ internal class AudioPlayer: IDisposable
+ {
+ #region Constants
+
+ // AudioPlayer states
+ private const int MediaNone = 0;
+ private const int MediaStarting = 1;
+ private const int MediaRunning = 2;
+ private const int MediaPaused = 3;
+ private const int MediaStopped = 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;
+
+ 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 = MediaNone;
+
+ /// <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();
+ }
+
+ /// <summary>
+ /// Starts recording, data is stored in memory
+ /// </summary>
+ /// <param name="filePath"></param>
+ public void startRecording(string filePath)
+ {
+ if (this.player != null)
+ {
+ this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorPlayModeSet));
+ }
+ 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);
+ this.memoryStream = new MemoryStream();
+ this.WriteWavHeader(this.memoryStream, this.recorder.SampleRate);
+ this.recorder.Start();
+ FrameworkDispatcher.Update();
+ this.SetState(MediaRunning);
+ }
+ catch (Exception)
+ {
+ this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorStartingRecording));
+ }
+ } else
+ {
+
+ this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorAlreadyRecording));
+ }
+ }
+
+ /// <summary>
+ /// Stops recording
+ /// </summary>
+ public void stopRecording()
+ {
+ if (this.recorder != null)
+ {
+ if (this.state == MediaRunning)
+ {
+ try
+ {
+ this.recorder.Stop();
+ this.recorder.BufferReady -= recorderBufferReady;
+ this.recorder = null;
+ SaveAudioClipToLocalStorage();
+ this.FinalizeXnaGameLoop();
+ this.SetState(MediaStopped);
+ }
+ 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)
+ {
+ this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorRecordModeSet));
+ return;
+ }
+
+
+ if ((this.player == null) || (this.state == MediaStopped))
+ {
+ try
+ {
+ if (this.player == null)
+ {
+ // 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 = new MediaElement();
+ grid.Children.Add(this.player);
+ this.player.Visibility = Visibility.Collapsed;
+ 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))
+ {
+ using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Open, isoFile))
+ {
+ this.player.SetSource(stream);
+ }
+ }
+ else
+ {
+ throw new ArgumentException("Source doesn't exist");
+ }
+ }
+ }
+ this.SetState(MediaStarting);
+ }
+ catch (Exception e)
+ {
+ string s = e.Message;
+ this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorStartingPlayback));
+ }
+ }
+ else
+ {
+ if ((this.state == MediaPaused) || (this.state == MediaStarting))
+ {
+ this.player.Play();
+ this.SetState(MediaRunning);
+ }
+ else
+ {
+ this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorResumeState));
+ }
+ }
+ }
+
+ /// <summary>
+ /// Callback to be invoked when the media source is ready for playback
+ /// </summary>
+ private void MediaOpened(object sender, RoutedEventArgs arg)
+ {
+ if (!this.prepareOnly)
+ {
+ this.player.Play();
+ this.SetState(MediaRunning);
+ }
+
+ this.duration = this.player.NaturalDuration.TimeSpan.TotalSeconds;
+ this.prepareOnly = false;
+ this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaDuration, this.duration));
+ }
+
+ /// <summary>
+ /// Callback to be invoked when playback of a media source has completed
+ /// </summary>
+ private void MediaEnded(object sender, RoutedEventArgs arg)
+ {
+ this.SetState(MediaStopped);
+ }
+
+ /// <summary>
+ /// Callback to be invoked when playback of a media source has failed
+ /// </summary>
+ private void MediaFailed(object sender, RoutedEventArgs arg)
+ {
+ player.Stop();
+ this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError.ToString(), "Media failed"));
+ }
+
+ /// <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 timeSpen = new TimeSpan(0, 0, 0, 0, milliseconds);
+ this.player.Position = timeSpen;
+ this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaPosition, milliseconds / 1000.0f));
+ }
+ }
+
+ /// <summary>
+ /// Pauses playing
+ /// </summary>
+ public void pausePlaying()
+ {
+ if (this.state == MediaRunning)
+ {
+ this.player.Pause();
+ this.SetState(MediaPaused);
+ } else
+ {
+ this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorPauseState));
+ }
+ }
+
+
+ /// <summary>
+ /// Stops playing the audio file
+ /// </summary>
+ public void stopPlaying()
+ {
+ if ((this.state == MediaRunning) || (this.state == MediaPaused))
+ {
+ this.player.Stop();
+ this.SetState(MediaStopped);
+ } else
+ {
+ this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorStopState));
+ }
+ }
+
+ /// <summary>
+ /// Gets current position of playback
+ /// </summary>
+ /// <returns>current position</returns>
+ public double getCurrentPosition()
+ {
+ if ((this.state == MediaRunning) || (this.state == MediaPaused))
+ {
+ double currentPosition = this.player.Position.TotalSeconds;
+ this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaPosition, currentPosition));
+ return currentPosition;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ /// <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)
+ {
+ this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaState, state));
+ }
+
+ 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 (this.memoryStream == null || this.memoryStream.Length <= 0)
+ {
+ return;
+ }
+
+ this.UpdateWavHeader(this.memoryStream);
+
+ 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 Wav format
+ // Original source http://damianblog.com/2011/02/07/storing-wp7-recorded-audio-as-wav-format-streams/
+
+ /// <summary>
+ /// Adds wav file format header to the stream
+ /// https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
+ /// </summary>
+ /// <param name="stream">Wav stream</param>
+ /// <param name="sampleRate">Sample Rate</param>
+ private void WriteWavHeader(Stream stream, int sampleRate)
+ {
+ const int bitsPerSample = 16;
+ const int bytesPerSample = bitsPerSample / 8;
+ var encoding = System.Text.Encoding.UTF8;
+
+ // ChunkID Contains the letters "RIFF" in ASCII form (0x52494646 big-endian form).
+ stream.Write(encoding.GetBytes("RIFF"), 0, 4);
+
+ // NOTE this will be filled in later
+ stream.Write(BitConverter.GetBytes(0), 0, 4);
+
+ // Format Contains the letters "WAVE"(0x57415645 big-endian form).
+ stream.Write(encoding.GetBytes("WAVE"), 0, 4);
+
+ // Subchunk1ID Contains the letters "fmt " (0x666d7420 big-endian form).
+ stream.Write(encoding.GetBytes("fmt "), 0, 4);
+
+ // Subchunk1Size 16 for PCM. This is the size of therest of the Subchunk which follows this number.
+ stream.Write(BitConverter.GetBytes(16), 0, 4);
+
+ // AudioFormat PCM = 1 (i.e. Linear quantization) Values other than 1 indicate some form of compression.
+ stream.Write(BitConverter.GetBytes((short)1), 0, 2);
+
+ // NumChannels Mono = 1, Stereo = 2, etc.
+ stream.Write(BitConverter.GetBytes((short)1), 0, 2);
+
+ // SampleRate 8000, 44100, etc.
+ stream.Write(BitConverter.GetBytes(sampleRate), 0, 4);
+
+ // ByteRate = SampleRate * NumChannels * BitsPerSample/8
+ stream.Write(BitConverter.GetBytes(sampleRate * bytesPerSample), 0, 4);
+
+ // BlockAlign NumChannels * BitsPerSample/8 The number of bytes for one sample including all channels.
+ stream.Write(BitConverter.GetBytes((short)(bytesPerSample)), 0, 2);
+
+ // BitsPerSample 8 bits = 8, 16 bits = 16, etc.
+ stream.Write(BitConverter.GetBytes((short)(bitsPerSample)), 0, 2);
+
+ // Subchunk2ID Contains the letters "data" (0x64617461 big-endian form).
+ stream.Write(encoding.GetBytes("data"), 0, 4);
+
+ // NOTE to be filled in later
+ stream.Write(BitConverter.GetBytes(0), 0, 4);
+ }
+
+ /// <summary>
+ /// Updates wav file format header
+ /// https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
+ /// </summary>
+ /// <param name="stream">Wav stream</param>
+ private void UpdateWavHeader(Stream stream)
+ {
+ if (!stream.CanSeek) throw new Exception("Can't seek stream to update wav header");
+
+ var oldPos = stream.Position;
+
+ // ChunkSize 36 + SubChunk2Size
+ stream.Seek(4, SeekOrigin.Begin);
+ stream.Write(BitConverter.GetBytes((int)stream.Length - 8), 0, 4);
+
+ // Subchunk2Size == NumSamples * NumChannels * BitsPerSample/8 This is the number of bytes in the data.
+ stream.Seek(40, SeekOrigin.Begin);
+ stream.Write(BitConverter.GetBytes((int)stream.Length - 44), 0, 4);
+
+ stream.Seek(oldPos, SeekOrigin.Begin);
+ }
+
+ #endregion
+
+ #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)
+ this.dtXna.Stop();
+ this.dtXna = null;
+ }
+
+ #endregion
+
+ #endregion
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/blob/4789ce6d/framework/Cordova/Commands/BaseCommand.cs
----------------------------------------------------------------------
diff --git a/framework/Cordova/Commands/BaseCommand.cs b/framework/Cordova/Commands/BaseCommand.cs
new file mode 100644
index 0000000..6358ac0
--- /dev/null
+++ b/framework/Cordova/Commands/BaseCommand.cs
@@ -0,0 +1,127 @@
+/*
+ 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.Reflection;
+using Microsoft.Phone.Shell;
+
+namespace WP7GapClassLib.Cordova.Commands
+{
+ public abstract class BaseCommand : IDisposable
+ {
+ /*
+ * All commands + plugins must extend BaseCommand, because they are dealt with as BaseCommands in PGView.xaml.cs
+ *
+ **/
+
+ public event EventHandler<PluginResult> OnCommandResult;
+
+ public event EventHandler<ScriptCallback> OnCustomScript;
+
+ public BaseCommand()
+ {
+ PhoneApplicationService service = PhoneApplicationService.Current;
+ service.Activated += this.OnResume;
+ service.Deactivated += this.OnPause;
+ }
+
+ /*
+ * InvokeMethodNamed will call the named method of a BaseCommand subclass if it exists and pass the variable arguments list along.
+ **/
+
+ public object InvokeMethodNamed(string methodName, params object[] args)
+ {
+ MethodInfo mInfo = this.GetType().GetMethod(methodName);
+
+ if (mInfo != null)
+ {
+ // every function handles DispatchCommandResult by itself
+ return mInfo.Invoke(this, args);
+ }
+
+ // actually methodName could refer to a property
+ if (args == null || args.Length == 0 ||
+ (args.Length == 1 && "undefined".Equals(args[0])))
+ {
+ PropertyInfo pInfo = this.GetType().GetProperty(methodName);
+ if (pInfo != null)
+ {
+
+ object res = pInfo.GetValue(this , null);
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, res));
+
+ return res;
+ }
+ }
+
+ throw new MissingMethodException(methodName);
+
+ }
+
+
+ public void InvokeCustomScript(ScriptCallback script)
+ {
+ if (this.OnCustomScript != null)
+ {
+ this.OnCustomScript(this, script);
+ }
+ }
+
+ public void DispatchCommandResult()
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.NO_RESULT));
+ }
+
+ public void DispatchCommandResult(PluginResult result)
+ {
+ if (this.OnCommandResult != null)
+ {
+ this.OnCommandResult(this, result);
+
+ if (!result.KeepCallback)
+ {
+ this.Dispose();
+ }
+
+ }
+ }
+
+ /// <summary>
+ /// Occurs when the application is being deactivated.
+ /// </summary>
+ public virtual void OnPause(object sender, DeactivatedEventArgs e)
+ {
+ }
+
+ /// <summary>
+ /// Occurs when the application is being made active after previously being put
+ /// into a dormant state or tombstoned.
+ /// </summary>
+ public virtual void OnResume(object sender, Microsoft.Phone.Shell.ActivatedEventArgs e)
+ {
+ }
+
+ public void Dispose()
+ {
+ PhoneApplicationService service = PhoneApplicationService.Current;
+ service.Activated -= this.OnResume;
+ service.Deactivated -= this.OnPause;
+ this.OnCommandResult = null;
+ }
+ }
+
+
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/blob/4789ce6d/framework/Cordova/Commands/Camera.cs
----------------------------------------------------------------------
diff --git a/framework/Cordova/Commands/Camera.cs b/framework/Cordova/Commands/Camera.cs
new file mode 100644
index 0000000..94663b0
--- /dev/null
+++ b/framework/Cordova/Commands/Camera.cs
@@ -0,0 +1,308 @@
+/*
+ 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.Net;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Ink;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Collections.Generic;
+using Microsoft.Phone.Tasks;
+using System.Runtime.Serialization;
+using System.IO;
+using System.IO.IsolatedStorage;
+using System.Windows.Media.Imaging;
+using Microsoft.Phone;
+
+namespace WP7GapClassLib.Cordova.Commands
+{
+ public class Camera : BaseCommand
+ {
+
+ /// <summary>
+ /// Return base64 encoded string
+ /// </summary>
+ private const int DATA_URL = 0;
+
+ /// <summary>
+ /// Return file uri
+ /// </summary>
+ private const int FILE_URI = 1;
+
+ /// <summary>
+ /// Choose image from picture library
+ /// </summary>
+ private const int PHOTOLIBRARY = 0;
+
+ /// <summary>
+ /// Take picture from camera
+ /// </summary>
+
+ private const int CAMERA = 1;
+
+ /// <summary>
+ /// Choose image from picture library
+ /// </summary>
+ private const int SAVEDPHOTOALBUM = 2;
+
+ /// <summary>
+ /// Take a picture of type JPEG
+ /// </summary>
+ private const int JPEG = 0;
+
+ /// <summary>
+ /// Take a picture of type PNG
+ /// </summary>
+ private const int PNG = 1;
+
+ /// <summary>
+ /// Folder to store captured images
+ /// </summary>
+ private const string isoFolder = "CapturedImagesCache";
+
+ /// <summary>
+ /// Represents captureImage action options.
+ /// </summary>
+ [DataContract]
+ public class CameraOptions
+ {
+ /// <summary>
+ /// Source to getPicture from.
+ /// </summary>
+ [DataMember(IsRequired = false, Name = "sourceType")]
+ public int PictureSourceType { get; set; }
+
+ /// <summary>
+ /// Format of image that returned from getPicture.
+ /// </summary>
+ [DataMember(IsRequired = false, Name = "destinationType")]
+ public int DestinationType { get; set; }
+
+ /// <summary>
+ /// Quality of saved image
+ /// </summary>
+ [DataMember(IsRequired = false, Name = "quality")]
+ public int Quality { get; set; }
+
+
+ /// <summary>
+ /// Height in pixels to scale image
+ /// </summary>
+ [DataMember(IsRequired = false, Name = "targetHeight")]
+ public int TargetHeight { get; set; }
+
+ /// <summary>
+ /// Width in pixels to scale image
+ /// </summary>
+ [DataMember(IsRequired = false, Name = "targetWidth")]
+ public int TargetWidth { get; set; }
+
+ /// <summary>
+ /// Creates options object with default parameters
+ /// </summary>
+ public CameraOptions()
+ {
+ this.SetDefaultValues(new StreamingContext());
+ }
+
+ /// <summary>
+ /// Initializes default values for class fields.
+ /// Implemented in separate method because default constructor is not invoked during deserialization.
+ /// </summary>
+ /// <param name="context"></param>
+ [OnDeserializing()]
+ public void SetDefaultValues(StreamingContext context)
+ {
+ PictureSourceType = CAMERA;
+ DestinationType = FILE_URI;
+ Quality = 80;
+ TargetHeight = -1;
+ TargetWidth = -1;
+ }
+
+ }
+
+ /// <summary>
+ /// Used to open photo library
+ /// </summary>
+ PhotoChooserTask photoChooserTask;
+
+ /// <summary>
+ /// Used to open camera application
+ /// </summary>
+ CameraCaptureTask cameraTask;
+
+ /// <summary>
+ /// Camera options
+ /// </summary>
+ CameraOptions cameraOptions;
+
+ public void getPicture(string options)
+ {
+ try
+ {
+ this.cameraOptions = String.IsNullOrEmpty(options) ?
+ new CameraOptions() : JSON.JsonHelper.Deserialize<CameraOptions>(options);
+ }
+ catch (Exception ex)
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+ return;
+ }
+
+ //TODO Check if all the options are acceptable
+
+
+ if (cameraOptions.PictureSourceType == CAMERA)
+ {
+ cameraTask = new CameraCaptureTask();
+ cameraTask.Completed += onTaskCompleted;
+ cameraTask.Show();
+ }
+ else
+ {
+ if ((cameraOptions.PictureSourceType == PHOTOLIBRARY) || (cameraOptions.PictureSourceType == SAVEDPHOTOALBUM))
+ {
+ photoChooserTask = new PhotoChooserTask();
+ photoChooserTask.Completed += onTaskCompleted;
+ photoChooserTask.Show();
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.NO_RESULT));
+ }
+ }
+
+ }
+
+ public void onTaskCompleted(object sender, PhotoResult e)
+ {
+ if (e.Error != null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
+ return;
+ }
+
+ switch (e.TaskResult)
+ {
+ case TaskResult.OK:
+ try
+ {
+ string imagePathOrContent = string.Empty;
+
+ if (cameraOptions.DestinationType == FILE_URI)
+ {
+ WriteableBitmap image = PictureDecoder.DecodeJpeg(e.ChosenPhoto);
+ imagePathOrContent = this.SaveImageToLocalStorage(image, Path.GetFileName(e.OriginalFileName));
+ }
+ else if (cameraOptions.DestinationType == DATA_URL)
+ {
+ imagePathOrContent = this.GetImageContent(e.ChosenPhoto);
+
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrec option: destinationType"));
+ return;
+ }
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, imagePathOrContent));
+
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error retrieving image."));
+ }
+ break;
+
+ case TaskResult.Cancel:
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection cancelled."));
+ break;
+
+ default:
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection did not complete!"));
+ break;
+ }
+ }
+
+
+ /// <summary>
+ /// Returns image content in a form of base64 string
+ /// </summary>
+ /// <param name="stream">Image stream</param>
+ /// <returns>Base64 representation of the image</returns>
+ private string GetImageContent(Stream stream)
+ {
+ int streamLength = (int)stream.Length;
+ byte[] fileData = new byte[streamLength + 1];
+ stream.Read(fileData, 0, streamLength);
+ stream.Close();
+
+ return Convert.ToBase64String(fileData);
+ }
+
+
+ /// <summary>
+ /// Saves captured image in isolated storage
+ /// </summary>
+ /// <param name="imageFileName">image file name</param>
+ /// <returns>Image path</returns>
+ private string SaveImageToLocalStorage(WriteableBitmap image, string imageFileName)
+ {
+
+ if (image == null)
+ {
+ throw new ArgumentNullException("imageBytes");
+ }
+ try
+ {
+
+
+ var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
+
+ if (!isoFile.DirectoryExists(isoFolder))
+ {
+ isoFile.CreateDirectory(isoFolder);
+ }
+
+ string filePath = System.IO.Path.Combine("/" + isoFolder + "/", imageFileName);
+
+ using (var stream = isoFile.CreateFile(filePath))
+ {
+ // resize image if Height and Width defined via options
+ if (cameraOptions.TargetHeight > 0 && cameraOptions.TargetWidth > 0)
+ {
+ image.SaveJpeg(stream, cameraOptions.TargetWidth, cameraOptions.TargetHeight, 0, cameraOptions.Quality);
+ }
+ else
+ {
+ image.SaveJpeg(stream, image.PixelWidth, image.PixelHeight, 0, cameraOptions.Quality);
+ }
+ }
+
+ return new Uri(filePath, UriKind.Relative).ToString();
+ }
+ catch (Exception)
+ {
+ //TODO: log or do something else
+ throw;
+ }
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/blob/4789ce6d/framework/Cordova/Commands/Capture.cs
----------------------------------------------------------------------
diff --git a/framework/Cordova/Commands/Capture.cs b/framework/Cordova/Commands/Capture.cs
new file mode 100644
index 0000000..c600747
--- /dev/null
+++ b/framework/Cordova/Commands/Capture.cs
@@ -0,0 +1,710 @@
+/*
+ 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.IO;
+using System.IO.IsolatedStorage;
+using System.Runtime.Serialization;
+using System.Windows.Media.Imaging;
+using Microsoft.Phone;
+using Microsoft.Phone.Tasks;
+using Microsoft.Xna.Framework.Media;
+using WP7GapClassLib.Cordova.UI;
+using AudioResult = WP7GapClassLib.Cordova.UI.AudioCaptureTask.AudioResult;
+using VideoResult = WP7GapClassLib.Cordova.UI.VideoCaptureTask.VideoResult;
+
+namespace WP7GapClassLib.Cordova.Commands
+{
+ /// <summary>
+ /// Provides access to the audio, image, and video capture capabilities of the device
+ /// </summary>
+ public class Capture : BaseCommand
+ {
+ #region Internal classes (options and resultant objects)
+
+ /// <summary>
+ /// Represents captureImage action options.
+ /// </summary>
+ [DataContract]
+ public class CaptureImageOptions
+ {
+ /// <summary>
+ /// The maximum number of images the device user can capture in a single capture operation. The value must be greater than or equal to 1 (defaults to 1).
+ /// </summary>
+ [DataMember(IsRequired = false, Name = "limit")]
+ public int Limit { get; set; }
+
+ public static CaptureImageOptions Default
+ {
+ get { return new CaptureImageOptions() { Limit = 1 }; }
+ }
+ }
+
+ /// <summary>
+ /// Represents captureAudio action options.
+ /// </summary>
+ [DataContract]
+ public class CaptureAudioOptions
+ {
+ /// <summary>
+ /// The maximum number of images the device user can capture in a single capture operation. The value must be greater than or equal to 1 (defaults to 1).
+ /// </summary>
+ [DataMember(IsRequired = false, Name = "limit")]
+ public int Limit { get; set; }
+
+ public static CaptureAudioOptions Default
+ {
+ get { return new CaptureAudioOptions() { Limit = 1 }; }
+ }
+ }
+
+ /// <summary>
+ /// Represents captureVideo action options.
+ /// </summary>
+ [DataContract]
+ public class CaptureVideoOptions
+ {
+ /// <summary>
+ /// The maximum number of video files the device user can capture in a single capture operation. The value must be greater than or equal to 1 (defaults to 1).
+ /// </summary>
+ [DataMember(IsRequired = false, Name = "limit")]
+ public int Limit { get; set; }
+
+ public static CaptureVideoOptions Default
+ {
+ get { return new CaptureVideoOptions() { Limit = 1 }; }
+ }
+ }
+
+ /// <summary>
+ /// Represents getFormatData action options.
+ /// </summary>
+ [DataContract]
+ public class MediaFormatOptions
+ {
+ /// <summary>
+ /// File path
+ /// </summary>
+ [DataMember(IsRequired = true, Name = "fullPath")]
+ public string FullPath { get; set; }
+
+ /// <summary>
+ /// File mime type
+ /// </summary>
+ [DataMember(Name = "type")]
+ public string Type { get; set; }
+
+ }
+
+ /// <summary>
+ /// Stores image info
+ /// </summary>
+ [DataContract]
+ public class MediaFile
+ {
+
+ [DataMember(Name = "name")]
+ public string FileName { get; set; }
+
+ [DataMember(Name = "fullPath")]
+ public string FilePath { get; set; }
+
+ [DataMember(Name = "type")]
+ public string Type { get; set; }
+
+ [DataMember(Name = "lastModifiedDate")]
+ public string LastModifiedDate { get; set; }
+
+ [DataMember(Name = "size")]
+ public long Size { get; set; }
+
+ public MediaFile(string filePath, Picture image)
+ {
+ this.FilePath = filePath;
+ this.FileName = System.IO.Path.GetFileName(this.FilePath);
+ this.Type = MimeTypeMapper.GetMimeType(FileName);
+ this.Size = image.GetImage().Length;
+
+ using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ this.LastModifiedDate = storage.GetLastWriteTime(filePath).DateTime.ToString();
+ }
+
+ }
+
+ public MediaFile(string filePath, Stream stream)
+ {
+ this.FilePath = filePath;
+ this.FileName = System.IO.Path.GetFileName(this.FilePath);
+ this.Type = MimeTypeMapper.GetMimeType(FileName);
+ this.Size = stream.Length;
+
+ using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ this.LastModifiedDate = storage.GetLastWriteTime(filePath).DateTime.ToString();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Stores additional media file data
+ /// </summary>
+ [DataContract]
+ public class MediaFileData
+ {
+ [DataMember(Name = "height")]
+ public int Height { get; set; }
+
+ [DataMember(Name = "width")]
+ public int Width { get; set; }
+
+ [DataMember(Name = "bitrate")]
+ public int Bitrate { get; set; }
+
+ [DataMember(Name = "duration")]
+ public int Duration { get; set; }
+
+ [DataMember(Name = "codecs")]
+ public string Codecs { get; set; }
+
+ public MediaFileData(WriteableBitmap image)
+ {
+ this.Height = image.PixelHeight;
+ this.Width = image.PixelWidth;
+ this.Bitrate = 0;
+ this.Duration = 0;
+ this.Codecs = "";
+ }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Folder to store captured images
+ /// </summary>
+ private string isoFolder = "CapturedImagesCache";
+
+ /// <summary>
+ /// Capture Image options
+ /// </summary>
+ protected CaptureImageOptions captureImageOptions;
+
+ /// <summary>
+ /// Capture Audio options
+ /// </summary>
+ protected CaptureAudioOptions captureAudioOptions;
+
+ /// <summary>
+ /// Capture Video options
+ /// </summary>
+ protected CaptureVideoOptions captureVideoOptions;
+
+ /// <summary>
+ /// Used to open camera application
+ /// </summary>
+ private CameraCaptureTask cameraTask;
+
+ /// <summary>
+ /// Used for audio recording
+ /// </summary>
+ private AudioCaptureTask audioCaptureTask;
+
+ /// <summary>
+ /// Used for video recording
+ /// </summary>
+ private VideoCaptureTask videoCaptureTask;
+
+ /// <summary>
+ /// Stores information about captured files
+ /// </summary>
+ List<MediaFile> files = new List<MediaFile>();
+
+ /// <summary>
+ /// Launches default camera application to capture image
+ /// </summary>
+ /// <param name="options">may contains limit or mode parameters</param>
+ public void captureImage(string options)
+ {
+ try
+ {
+ try
+ {
+ this.captureImageOptions = String.IsNullOrEmpty(options) ?
+ CaptureImageOptions.Default : JSON.JsonHelper.Deserialize<CaptureImageOptions>(options);
+
+ }
+ catch (Exception ex)
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+ return;
+ }
+
+
+ cameraTask = new CameraCaptureTask();
+ cameraTask.Completed += this.cameraTask_Completed;
+ cameraTask.Show();
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message));
+ }
+ }
+
+ /// <summary>
+ /// Launches our own audio recording control to capture audio
+ /// </summary>
+ /// <param name="options">may contains additional parameters</param>
+ public void captureAudio(string options)
+ {
+ try
+ {
+ try
+ {
+ this.captureAudioOptions = String.IsNullOrEmpty(options) ?
+ CaptureAudioOptions.Default : JSON.JsonHelper.Deserialize<CaptureAudioOptions>(options);
+
+ }
+ catch (Exception ex)
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+ return;
+ }
+
+ audioCaptureTask = new AudioCaptureTask();
+ audioCaptureTask.Completed += audioRecordingTask_Completed;
+ audioCaptureTask.Show();
+
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message));
+ }
+ }
+
+ /// <summary>
+ /// Launches our own video recording control to capture video
+ /// </summary>
+ /// <param name="options">may contains additional parameters</param>
+ public void captureVideo(string options)
+ {
+ try
+ {
+ try
+ {
+ this.captureVideoOptions = String.IsNullOrEmpty(options) ?
+ CaptureVideoOptions.Default : JSON.JsonHelper.Deserialize<CaptureVideoOptions>(options);
+
+ }
+ catch (Exception ex)
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+ return;
+ }
+
+ videoCaptureTask = new VideoCaptureTask();
+ videoCaptureTask.Completed += videoRecordingTask_Completed;
+ videoCaptureTask.Show();
+
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message));
+ }
+ }
+
+ /// <summary>
+ /// Retrieves the format information of the media file.
+ /// </summary>
+ /// <param name="options"></param>
+ public void getFormatData(string options)
+ {
+ if (String.IsNullOrEmpty(options))
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+ return;
+ }
+
+ try
+ {
+ MediaFormatOptions mediaFormatOptions;
+ try
+ {
+ mediaFormatOptions = JSON.JsonHelper.Deserialize<MediaFormatOptions>(options);
+ }
+ catch (Exception ex)
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+ return;
+ }
+
+ if (string.IsNullOrEmpty(mediaFormatOptions.FullPath))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+ }
+
+ string mimeType = mediaFormatOptions.Type;
+
+ if (string.IsNullOrEmpty(mimeType))
+ {
+ mimeType = MimeTypeMapper.GetMimeType(mediaFormatOptions.FullPath);
+ }
+
+ if (mimeType.Equals("image/jpeg"))
+ {
+ WriteableBitmap image = ExtractImageFromLocalStorage(mediaFormatOptions.FullPath);
+
+ if (image == null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "File not found"));
+ return;
+ }
+
+ MediaFileData mediaData = new MediaFileData(image);
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, mediaData));
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
+ }
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
+ }
+ }
+
+ /// <summary>
+ /// Opens specified file in media player
+ /// </summary>
+ /// <param name="options">MediaFile to play</param>
+ public void play(string options)
+ {
+ try
+ {
+ MediaFile file;
+
+ try
+ {
+ file = String.IsNullOrEmpty(options) ? null : JSON.JsonHelper.Deserialize<MediaFile>(options);
+
+ }
+ catch (Exception ex)
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+ return;
+ }
+
+ if (file == null || String.IsNullOrEmpty(file.FilePath))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "File path is missing"));
+ return;
+ }
+
+ // if url starts with '/' media player throws FileNotFound exception
+ Uri fileUri = new Uri(file.FilePath.TrimStart(new char[] { '/', '\\' }), UriKind.Relative);
+
+ MediaPlayerLauncher player = new MediaPlayerLauncher();
+ player.Media = fileUri;
+ player.Location = MediaLocationType.Data;
+ player.Show();
+
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.OK));
+
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message));
+ }
+ }
+
+ /// <summary>
+ /// Handles result of capture to save image information
+ /// </summary>
+ /// <param name="sender"></param>
+ /// <param name="e">stores information about current captured image</param>
+ private void cameraTask_Completed(object sender, PhotoResult e)
+ {
+
+ if (e.Error != null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
+ return;
+ }
+
+ switch (e.TaskResult)
+ {
+ case TaskResult.OK:
+ try
+ {
+ string fileName = System.IO.Path.GetFileName(e.OriginalFileName);
+
+ // Save image in media library
+ MediaLibrary library = new MediaLibrary();
+ Picture image = library.SavePicture(fileName, e.ChosenPhoto);
+
+ // Save image in isolated storage
+
+ // we should return stream position back after saving stream to media library
+ e.ChosenPhoto.Seek(0, SeekOrigin.Begin);
+ byte[] imageBytes = new byte[e.ChosenPhoto.Length];
+ e.ChosenPhoto.Read(imageBytes, 0, imageBytes.Length);
+ string pathLocalStorage = this.SaveImageToLocalStorage(fileName, isoFolder, imageBytes);
+
+ // Get image data
+ MediaFile data = new MediaFile(pathLocalStorage, image);
+
+ this.files.Add(data);
+
+ if (files.Count < this.captureImageOptions.Limit)
+ {
+ cameraTask.Show();
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files, "navigator.device.capture._castMediaFile"));
+ files.Clear();
+ }
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error capturing image."));
+ }
+ break;
+
+ case TaskResult.Cancel:
+ if (files.Count > 0)
+ {
+ // User canceled operation, but some images were made
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files, "navigator.device.capture._castMediaFile"));
+ files.Clear();
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Canceled."));
+ }
+ break;
+
+ default:
+ if (files.Count > 0)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files, "navigator.device.capture._castMediaFile"));
+ files.Clear();
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Did not complete!"));
+ }
+ break;
+ }
+ }
+
+ /// <summary>
+ /// Handles result of audio recording tasks
+ /// </summary>
+ /// <param name="sender"></param>
+ /// <param name="e">stores information about current captured audio</param>
+ private void audioRecordingTask_Completed(object sender, AudioResult e)
+ {
+
+ if (e.Error != null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
+ return;
+ }
+
+ switch (e.TaskResult)
+ {
+ case TaskResult.OK:
+ try
+ {
+ // Get image data
+ MediaFile data = new MediaFile(e.AudioFileName, e.AudioFile);
+
+ this.files.Add(data);
+
+ if (files.Count < this.captureAudioOptions.Limit)
+ {
+ audioCaptureTask.Show();
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files, "navigator.device.capture._castMediaFile"));
+ files.Clear();
+ }
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error capturing audio."));
+ }
+ break;
+
+ case TaskResult.Cancel:
+ if (files.Count > 0)
+ {
+ // User canceled operation, but some audio clips were made
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files, "navigator.device.capture._castMediaFile"));
+ files.Clear();
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Canceled."));
+ }
+ break;
+
+ default:
+ if (files.Count > 0)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files, "navigator.device.capture._castMediaFile"));
+ files.Clear();
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Did not complete!"));
+ }
+ break;
+ }
+ }
+
+ /// <summary>
+ /// Handles result of video recording tasks
+ /// </summary>
+ /// <param name="sender"></param>
+ /// <param name="e">stores information about current captured video</param>
+ private void videoRecordingTask_Completed(object sender, VideoResult e)
+ {
+
+ if (e.Error != null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
+ return;
+ }
+
+ switch (e.TaskResult)
+ {
+ case TaskResult.OK:
+ try
+ {
+ // Get image data
+ MediaFile data = new MediaFile(e.VideoFileName, e.VideoFile);
+
+ this.files.Add(data);
+
+ if (files.Count < this.captureVideoOptions.Limit)
+ {
+ videoCaptureTask.Show();
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files, "navigator.device.capture._castMediaFile"));
+ files.Clear();
+ }
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error capturing video."));
+ }
+ break;
+
+ case TaskResult.Cancel:
+ if (files.Count > 0)
+ {
+ // User canceled operation, but some video clips were made
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files));
+ files.Clear();
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Canceled."));
+ }
+ break;
+
+ default:
+ if (files.Count > 0)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files, "navigator.device.capture._castMediaFile"));
+ files.Clear();
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Did not complete!"));
+ }
+ break;
+ }
+ }
+
+ /// <summary>
+ /// Extract file from Isolated Storage as WriteableBitmap object
+ /// </summary>
+ /// <param name="filePath"></param>
+ /// <returns></returns>
+ private WriteableBitmap ExtractImageFromLocalStorage(string filePath)
+ {
+ try
+ {
+
+ var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
+
+ using (var imageStream = isoFile.OpenFile(filePath, FileMode.Open, FileAccess.Read))
+ {
+ var imageSource = PictureDecoder.DecodeJpeg(imageStream);
+ return imageSource;
+ }
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ }
+
+
+ /// <summary>
+ /// Saves captured image in isolated storage
+ /// </summary>
+ /// <param name="imageFileName">image file name</param>
+ /// <param name="imageFolder">folder to store images</param>
+ /// <returns>Image path</returns>
+ private string SaveImageToLocalStorage(string imageFileName, string imageFolder, byte[] imageBytes)
+ {
+ if (imageBytes == null)
+ {
+ throw new ArgumentNullException("imageBytes");
+ }
+ try
+ {
+ var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
+
+ if (!isoFile.DirectoryExists(imageFolder))
+ {
+ isoFile.CreateDirectory(imageFolder);
+ }
+ string filePath = System.IO.Path.Combine("/" + imageFolder + "/", imageFileName);
+
+ using (var stream = isoFile.CreateFile(filePath))
+ {
+ stream.Write(imageBytes, 0, imageBytes.Length);
+ }
+
+ return filePath;
+ }
+ catch (Exception)
+ {
+ //TODO: log or do something else
+ throw;
+ }
+ }
+
+
+ }
+}
\ No newline at end of file