Return-Path: X-Original-To: apmail-cordova-commits-archive@www.apache.org Delivered-To: apmail-cordova-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 478389861 for ; Wed, 15 May 2013 20:35:45 +0000 (UTC) Received: (qmail 70931 invoked by uid 500); 15 May 2013 20:35:44 -0000 Delivered-To: apmail-cordova-commits-archive@cordova.apache.org Received: (qmail 70668 invoked by uid 500); 15 May 2013 20:35:43 -0000 Mailing-List: contact commits-help@cordova.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cordova.apache.org Delivered-To: mailing list commits@cordova.apache.org Received: (qmail 69565 invoked by uid 99); 15 May 2013 20:35:42 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 15 May 2013 20:35:41 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id C096BB699; Wed, 15 May 2013 20:35:41 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: mwbrooks@apache.org To: commits@cordova.apache.org Date: Wed, 15 May 2013 20:36:08 -0000 Message-Id: In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [29/37] Add WP7 and WP8 platform files. http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/f59ddbbd/lib/cordova-wp7/templates/standalone/Plugins/Accelerometer.cs ---------------------------------------------------------------------- diff --git a/lib/cordova-wp7/templates/standalone/Plugins/Accelerometer.cs b/lib/cordova-wp7/templates/standalone/Plugins/Accelerometer.cs new file mode 100644 index 0000000..cba911c --- /dev/null +++ b/lib/cordova-wp7/templates/standalone/Plugins/Accelerometer.cs @@ -0,0 +1,196 @@ +/* + 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; +using System.Diagnostics; + +namespace WPCordovaClassLib.Cordova.Commands +{ + /// + /// Captures device motion in the x, y, and z direction. + /// + public class Accelerometer : BaseCommand + { + #region AccelerometerOptions class + /// + /// Represents Accelerometer options. + /// + [DataContract] + public class AccelerometerOptions + { + /// + /// How often to retrieve the Acceleration in milliseconds + /// + [DataMember(IsRequired = false, Name = "frequency")] + public int Frequency { get; set; } + + /// + /// Watcher id + /// + [DataMember(IsRequired = false, Name = "id")] + public string Id { get; set; } + + /// + /// Creates options object with default parameters + /// + public AccelerometerOptions() + { + this.SetDefaultValues(new StreamingContext()); + } + + /// + /// Initializes default values for class fields. + /// Implemented in separate method because default constructor is not invoked during deserialization. + /// + /// + [OnDeserializing()] + public void SetDefaultValues(StreamingContext context) + { + this.Frequency = 10000; + } + } + + #endregion + + #region Status codes and Constants + + public const int Stopped = 0; + public const int Starting = 1; + public const int Running = 2; + public const int ErrorFailedToStart = 3; + + public const double gConstant = -9.81; + + #endregion + + #region Static members + + /// + /// Status of listener + /// + private static int currentStatus; + + /// + /// Accelerometer + /// + private static Microsoft.Devices.Sensors.Accelerometer accelerometer = new Microsoft.Devices.Sensors.Accelerometer(); + + private static DateTime StartOfEpoch = new DateTime(1970, 1, 1, 0, 0, 0); + + #endregion + + /// + /// Sensor listener event + /// + private void accelerometer_CurrentValueChanged(object sender, SensorReadingEventArgs e) + { + this.SetStatus(Running); + + PluginResult result = new PluginResult(PluginResult.Status.OK, GetCurrentAccelerationFormatted()); + result.KeepCallback = true; + DispatchCommandResult(result); + } + + /// + /// Starts listening for acceleration sensor + /// + /// status of listener + public void start(string options) + { + if ((currentStatus == Running) || (currentStatus == Starting)) + { + return; + } + try + { + lock (accelerometer) + { + accelerometer.CurrentValueChanged += accelerometer_CurrentValueChanged; + accelerometer.Start(); + this.SetStatus(Starting); + } + + long timeout = 2000; + while ((currentStatus == Starting) && (timeout > 0)) + { + timeout = timeout - 100; + Thread.Sleep(100); + } + + if (currentStatus != Running) + { + this.SetStatus(ErrorFailedToStart); + DispatchCommandResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, ErrorFailedToStart)); + return; + } + } + catch (Exception) + { + this.SetStatus(ErrorFailedToStart); + DispatchCommandResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, ErrorFailedToStart)); + return; + } + PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT); + result.KeepCallback = true; + DispatchCommandResult(result); + } + + public void stop(string options) + { + if (currentStatus == Running) + { + lock (accelerometer) + { + accelerometer.CurrentValueChanged -= accelerometer_CurrentValueChanged; + accelerometer.Stop(); + this.SetStatus(Stopped); + } + } + DispatchCommandResult(new PluginResult(PluginResult.Status.OK)); + } + + /// + /// Formats current coordinates into JSON format + /// + /// Coordinates in JSON format + private string GetCurrentAccelerationFormatted() + { + // convert to unix timestamp + // long timestamp = ((accelerometer.CurrentValue.Timestamp.DateTime - StartOfEpoch).Ticks) / 10000; + // Note: Removed timestamp, to let the JS side create it using (new Date().getTime()) -jm + // this resolves an issue with inconsistencies between JS dates and Native DateTime + string resultCoordinates = String.Format("\"x\":{0},\"y\":{1},\"z\":{2}", + (accelerometer.CurrentValue.Acceleration.X * gConstant).ToString("0.00000", CultureInfo.InvariantCulture), + (accelerometer.CurrentValue.Acceleration.Y * gConstant).ToString("0.00000", CultureInfo.InvariantCulture), + (accelerometer.CurrentValue.Acceleration.Z * gConstant).ToString("0.00000", CultureInfo.InvariantCulture)); + return "{" + resultCoordinates + "}"; + } + + /// + /// Sets current status + /// + /// current status + private void SetStatus(int status) + { + currentStatus = status; + } + } +} + http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/f59ddbbd/lib/cordova-wp7/templates/standalone/Plugins/AudioFormatsHelper.cs ---------------------------------------------------------------------- diff --git a/lib/cordova-wp7/templates/standalone/Plugins/AudioFormatsHelper.cs b/lib/cordova-wp7/templates/standalone/Plugins/AudioFormatsHelper.cs new file mode 100644 index 0000000..dca7ee6 --- /dev/null +++ b/lib/cordova-wp7/templates/standalone/Plugins/AudioFormatsHelper.cs @@ -0,0 +1,89 @@ +/* + 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; + +namespace WPCordovaClassLib.Cordova.Commands +{ + /// + /// Provides extra functionality to support different audio formats. + /// + public static class AudioFormatsHelper + { + #region Wav + /// + /// Adds wav file format header to the stream + /// https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ + /// + /// The stream + /// Sample Rate + public static void InitializeWavStream(this Stream stream, int sampleRate) + { + #region args checking + + if (stream == null) + { + throw new ArgumentNullException("stream can't be null or empty"); + } + + #endregion + + int numBits = 16; + int numBytes = numBits / 8; + + 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(sampleRate), 0, 4); + stream.Write(BitConverter.GetBytes(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); + } + + /// + /// Updates wav file format header + /// https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ + /// + /// Wav stream + public static void UpdateWavStream(this Stream stream) + { + #region args checking + + if (stream == null) + { + throw new ArgumentNullException("stream can't be null or empty"); + } + + #endregion + + var position = stream.Position; + + stream.Seek(4, SeekOrigin.Begin); + stream.Write(BitConverter.GetBytes((int)stream.Length - 8), 0, 4); + stream.Seek(40, SeekOrigin.Begin); + stream.Write(BitConverter.GetBytes((int)stream.Length - 44), 0, 4); + stream.Seek(position, SeekOrigin.Begin); + } + + #endregion + } +} http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/f59ddbbd/lib/cordova-wp7/templates/standalone/Plugins/AudioPlayer.cs ---------------------------------------------------------------------- diff --git a/lib/cordova-wp7/templates/standalone/Plugins/AudioPlayer.cs b/lib/cordova-wp7/templates/standalone/Plugins/AudioPlayer.cs new file mode 100644 index 0000000..a83be5b --- /dev/null +++ b/lib/cordova-wp7/templates/standalone/Plugins/AudioPlayer.cs @@ -0,0 +1,620 @@ +/* + 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 +{ + /// + /// Implements audio record and play back functionality. + /// + 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 + + /// + /// The AudioHandler object + /// + private Media handler; + + /// + /// Temporary buffer to store audio chunk + /// + private byte[] buffer; + + /// + /// Xna game loop dispatcher + /// + DispatcherTimer dtXna; + + /// + /// Output buffer + /// + private MemoryStream memoryStream; + + /// + /// The id of this player (used to identify Media object in JavaScript) + /// + private String id; + + /// + /// State of recording or playback + /// + private int state = PlayerState_None; + + /// + /// File name to play or record to + /// + private String audioFile = null; + + /// + /// Duration of audio + /// + private double duration = -1; + + /// + /// Audio player object + /// + private MediaElement player = null; + + /// + /// Audio source + /// + private Microphone recorder; + + /// + /// Internal flag specified that we should only open audio w/o playing it + /// + private bool prepareOnly = false; + + /// + /// Creates AudioPlayer instance + /// + /// Media object + /// player id + public AudioPlayer(Media handler, String id) + { + this.handler = handler; + this.id = id; + } + + /// + /// Destroys player and stop audio playing or recording + /// + 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); + } + + /// + /// Starts recording, data is stored in memory + /// + /// + 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(recorderBufferReady); + this.memoryStream = new MemoryStream(); + this.memoryStream.InitializeWavStream(this.recorder.SampleRate); + 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); + } + } + + /// + /// Stops recording + /// + 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 + } + } + } + } + + /// + /// Starts or resume playing audio file + /// + /// The name of the audio file + /// + /// Starts or resume playing audio file + /// + /// The name of the audio file + 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); + } + } + } + + /// + /// Callback to be invoked when the media source is ready for playback + /// + 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 + } + } + + /// + /// Callback to be invoked when playback of a media source has completed + /// + private void MediaEnded(object sender, RoutedEventArgs arg) + { + this.SetState(PlayerState_Stopped); + } + + /// + /// Callback to be invoked when playback of a media source has failed + /// + 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); + } + + /// + /// Seek or jump to a new time in the track + /// + /// The new track position + 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); + } + } + + /// + /// Set the volume of the player + /// + /// volume 0.0-1.0, default value is 0.5 + public void setVolume(double vol) + { + if (this.player != null) + { + this.player.Volume = vol; + } + } + + /// + /// Pauses playing + /// + 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); + } + } + + + /// + /// Stops playing the audio file + /// + 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); + //} + } + + /// + /// Gets current position of playback + /// + /// current position + 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; + } + } + + /// + /// Gets the duration of the audio file + /// + /// The name of the audio file + /// track duration + 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; + } + } + + /// + /// Sets the state and send it to JavaScript + /// + /// state + 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 + + /// + /// Copies data from recorder to memory storages and updates recording state + /// + /// + /// + private void recorderBufferReady(object sender, EventArgs e) + { + this.recorder.GetData(this.buffer); + this.memoryStream.Write(this.buffer, 0, this.buffer.Length); + } + + /// + /// Writes audio data from memory to isolated storage + /// + /// + private void SaveAudioClipToLocalStorage() + { + if (this.memoryStream == null || this.memoryStream.Length <= 0) + { + return; + } + + this.memoryStream.UpdateWavStream(); + + 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 + /// + /// Special initialization required for the microphone: XNA game loop + /// + 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(); + } + /// + /// Finalizes XNA game loop for microphone + /// + 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 + } +} http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/f59ddbbd/lib/cordova-wp7/templates/standalone/Plugins/Battery.cs ---------------------------------------------------------------------- diff --git a/lib/cordova-wp7/templates/standalone/Plugins/Battery.cs b/lib/cordova-wp7/templates/standalone/Plugins/Battery.cs new file mode 100644 index 0000000..962959e --- /dev/null +++ b/lib/cordova-wp7/templates/standalone/Plugins/Battery.cs @@ -0,0 +1,79 @@ +/* + 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 Microsoft.Phone.Info; + +namespace WPCordovaClassLib.Cordova.Commands +{ + /// + /// Listens for changes to the state of the battery on the device. + /// Currently only the "isPlugged" parameter available via native APIs. + /// + public class Battery : BaseCommand + { + private bool isPlugged = false; + private EventHandler powerChanged; + + public Battery() + { + powerChanged = new EventHandler(DeviceStatus_PowerSourceChanged); + isPlugged = DeviceStatus.PowerSource.ToString().CompareTo("External") == 0; + } + + public void start(string options) + { + // Register power changed event handler + DeviceStatus.PowerSourceChanged += powerChanged; + + PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT); + result.KeepCallback = true; + DispatchCommandResult(result); + } + public void stop(string options) + { + // Unregister power changed event handler + DeviceStatus.PowerSourceChanged -= powerChanged; + } + + private void DeviceStatus_PowerSourceChanged(object sender, EventArgs e) + { + isPlugged = DeviceStatus.PowerSource.ToString().CompareTo("External") == 0; + PluginResult result = new PluginResult(PluginResult.Status.OK, GetCurrentBatteryStateFormatted()); + result.KeepCallback = true; + DispatchCommandResult(result); + } + + private string GetCurrentBatteryStateFormatted() + { + string batteryState = String.Format("\"level\":{0},\"isPlugged\":{1}", + "null", + isPlugged ? "true" : "false" + ); + batteryState = "{" + batteryState + "}"; + return batteryState; + } + + } +} http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/f59ddbbd/lib/cordova-wp7/templates/standalone/Plugins/Camera.cs ---------------------------------------------------------------------- diff --git a/lib/cordova-wp7/templates/standalone/Plugins/Camera.cs b/lib/cordova-wp7/templates/standalone/Plugins/Camera.cs new file mode 100644 index 0000000..5ff8045 --- /dev/null +++ b/lib/cordova-wp7/templates/standalone/Plugins/Camera.cs @@ -0,0 +1,490 @@ +/* + 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; +using Microsoft.Xna.Framework.Media; +using System.Diagnostics; + +namespace WPCordovaClassLib.Cordova.Commands +{ + public class Camera : BaseCommand + { + + /// + /// Return base64 encoded string + /// + private const int DATA_URL = 0; + + /// + /// Return file uri + /// + private const int FILE_URI = 1; + + /// + /// Choose image from picture library + /// + private const int PHOTOLIBRARY = 0; + + /// + /// Take picture from camera + /// + + private const int CAMERA = 1; + + /// + /// Choose image from picture library + /// + private const int SAVEDPHOTOALBUM = 2; + + /// + /// Take a picture of type JPEG + /// + private const int JPEG = 0; + + /// + /// Take a picture of type PNG + /// + private const int PNG = 1; + + /// + /// Folder to store captured images + /// + private const string isoFolder = "CapturedImagesCache"; + + /// + /// Represents captureImage action options. + /// + [DataContract] + public class CameraOptions + { + /// + /// Source to getPicture from. + /// + [DataMember(IsRequired = false, Name = "sourceType")] + public int PictureSourceType { get; set; } + + /// + /// Format of image that returned from getPicture. + /// + [DataMember(IsRequired = false, Name = "destinationType")] + public int DestinationType { get; set; } + + /// + /// Quality of saved image + /// + [DataMember(IsRequired = false, Name = "quality")] + public int Quality { get; set; } + + /// + /// Controls whether or not the image is also added to the device photo album. + /// + [DataMember(IsRequired = false, Name = "saveToPhotoAlbum")] + public bool SaveToPhotoAlbum { get; set; } + + /// + /// Ignored + /// + [DataMember(IsRequired = false, Name = "correctOrientation")] + public bool CorrectOrientation { get; set; } + + + + /// + /// Ignored + /// + [DataMember(IsRequired = false, Name = "allowEdit")] + public bool AllowEdit { get; set; } + + /// + /// Height in pixels to scale image + /// + [DataMember(IsRequired = false, Name = "encodingType")] + public int EncodingType { get; set; } + + /// + /// Height in pixels to scale image + /// + [DataMember(IsRequired = false, Name = "mediaType")] + public int MediaType { get; set; } + + + /// + /// Height in pixels to scale image + /// + [DataMember(IsRequired = false, Name = "targetHeight")] + public int TargetHeight { get; set; } + + + /// + /// Width in pixels to scale image + /// + [DataMember(IsRequired = false, Name = "targetWidth")] + public int TargetWidth { get; set; } + + /// + /// Creates options object with default parameters + /// + public CameraOptions() + { + this.SetDefaultValues(new StreamingContext()); + } + + /// + /// Initializes default values for class fields. + /// Implemented in separate method because default constructor is not invoked during deserialization. + /// + /// + [OnDeserializing()] + public void SetDefaultValues(StreamingContext context) + { + PictureSourceType = CAMERA; + DestinationType = FILE_URI; + Quality = 80; + TargetHeight = -1; + TargetWidth = -1; + SaveToPhotoAlbum = false; + CorrectOrientation = true; + AllowEdit = false; + MediaType = -1; + EncodingType = -1; + } + } + + /// + /// Used to open photo library + /// + PhotoChooserTask photoChooserTask; + + /// + /// Used to open camera application + /// + CameraCaptureTask cameraTask; + + /// + /// Camera options + /// + CameraOptions cameraOptions; + + public void takePicture(string options) + { + try + { + string[] args = JSON.JsonHelper.Deserialize(options); + // ["quality", "destinationType", "sourceType", "targetWidth", "targetHeight", "encodingType", + // "mediaType", "allowEdit", "correctOrientation", "saveToPhotoAlbum" ] + this.cameraOptions = new CameraOptions(); + this.cameraOptions.Quality = int.Parse(args[0]); + this.cameraOptions.DestinationType = int.Parse(args[1]); + this.cameraOptions.PictureSourceType = int.Parse(args[2]); + this.cameraOptions.TargetWidth = int.Parse(args[3]); + this.cameraOptions.TargetHeight = int.Parse(args[4]); + this.cameraOptions.EncodingType = int.Parse(args[5]); + this.cameraOptions.MediaType = int.Parse(args[6]); + this.cameraOptions.AllowEdit = bool.Parse(args[7]); + this.cameraOptions.CorrectOrientation = bool.Parse(args[8]); + this.cameraOptions.SaveToPhotoAlbum = bool.Parse(args[9]); + + //this.cameraOptions = String.IsNullOrEmpty(options) ? + // new CameraOptions() : JSON.JsonHelper.Deserialize(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 += onCameraTaskCompleted; + cameraTask.Show(); + } + else + { + if ((cameraOptions.PictureSourceType == PHOTOLIBRARY) || (cameraOptions.PictureSourceType == SAVEDPHOTOALBUM)) + { + photoChooserTask = new PhotoChooserTask(); + photoChooserTask.Completed += onPickerTaskCompleted; + photoChooserTask.Show(); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.NO_RESULT)); + } + } + + } + + public void onCameraTaskCompleted(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) + { + // Save image in media library + if (cameraOptions.SaveToPhotoAlbum) + { + MediaLibrary library = new MediaLibrary(); + Picture pict = library.SavePicture(e.OriginalFileName, e.ChosenPhoto); // to save to photo-roll ... + } + + int orient = ImageExifHelper.getImageOrientationFromStream(e.ChosenPhoto); + int newAngle = 0; + switch (orient) + { + case ImageExifOrientation.LandscapeLeft: + newAngle = 90; + break; + case ImageExifOrientation.PortraitUpsideDown: + newAngle = 180; + break; + case ImageExifOrientation.LandscapeRight: + newAngle = 270; + break; + case ImageExifOrientation.Portrait: + default: break; // 0 default already set + } + + Stream rotImageStream = ImageExifHelper.RotateStream(e.ChosenPhoto, newAngle); + + // we should return stream position back after saving stream to media library + rotImageStream.Seek(0, SeekOrigin.Begin); + + WriteableBitmap image = PictureDecoder.DecodeJpeg(rotImageStream); + + imagePathOrContent = this.SaveImageToLocalStorage(image, Path.GetFileName(e.OriginalFileName)); + + + } + else if (cameraOptions.DestinationType == DATA_URL) + { + imagePathOrContent = this.GetImageContent(e.ChosenPhoto); + } + else + { + // TODO: shouldn't this happen before we launch the camera-picker? + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrect 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; + } + + } + + public void onPickerTaskCompleted(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 + { + // TODO: shouldn't this happen before we launch the camera-picker? + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrect 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; + } + } + + /// + /// Returns image content in a form of base64 string + /// + /// Image stream + /// Base64 representation of the image + private string GetImageContent(Stream stream) + { + int streamLength = (int)stream.Length; + byte[] fileData = new byte[streamLength + 1]; + stream.Read(fileData, 0, streamLength); + + //use photo's actual width & height if user doesn't provide width & height + if (cameraOptions.TargetWidth < 0 && cameraOptions.TargetHeight < 0) + { + stream.Close(); + return Convert.ToBase64String(fileData); + } + else + { + // resize photo + byte[] resizedFile = ResizePhoto(stream, fileData); + stream.Close(); + return Convert.ToBase64String(resizedFile); + } + } + + /// + /// Resize image + /// + /// Image stream + /// File data + /// resized image + private byte[] ResizePhoto(Stream stream, byte[] fileData) + { + int streamLength = (int)stream.Length; + int intResult = 0; + + byte[] resizedFile; + + stream.Read(fileData, 0, streamLength); + + BitmapImage objBitmap = new BitmapImage(); + MemoryStream objBitmapStream = new MemoryStream(fileData); + MemoryStream objBitmapStreamResized = new MemoryStream(); + WriteableBitmap objWB; + objBitmap.SetSource(stream); + objWB = new WriteableBitmap(objBitmap); + + // resize the photo with user defined TargetWidth & TargetHeight + Extensions.SaveJpeg(objWB, objBitmapStreamResized, cameraOptions.TargetWidth, cameraOptions.TargetHeight, 0, cameraOptions.Quality); + + //Convert the resized stream to a byte array. + streamLength = (int)objBitmapStreamResized.Length; + resizedFile = new Byte[streamLength]; //-1 + objBitmapStreamResized.Position = 0; + //for some reason we have to set Position to zero, but we don't have to earlier when we get the bytes from the chosen photo... + intResult = objBitmapStreamResized.Read(resizedFile, 0, streamLength); + + return resizedFile; + } + + /// + /// Saves captured image in isolated storage + /// + /// image file name + /// Image path + 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/cordova-cli/blob/f59ddbbd/lib/cordova-wp7/templates/standalone/Plugins/Capture.cs ---------------------------------------------------------------------- diff --git a/lib/cordova-wp7/templates/standalone/Plugins/Capture.cs b/lib/cordova-wp7/templates/standalone/Plugins/Capture.cs new file mode 100644 index 0000000..5e14a16 --- /dev/null +++ b/lib/cordova-wp7/templates/standalone/Plugins/Capture.cs @@ -0,0 +1,736 @@ +/* + 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 WPCordovaClassLib.Cordova.UI; +using AudioResult = WPCordovaClassLib.Cordova.UI.AudioCaptureTask.AudioResult; +using VideoResult = WPCordovaClassLib.Cordova.UI.VideoCaptureTask.VideoResult; +using System.Windows; +using System.Diagnostics; +using Microsoft.Phone.Controls; + +namespace WPCordovaClassLib.Cordova.Commands +{ + /// + /// Provides access to the audio, image, and video capture capabilities of the device + /// + public class Capture : BaseCommand + { + #region Internal classes (options and resultant objects) + + /// + /// Represents captureImage action options. + /// + [DataContract] + public class CaptureImageOptions + { + /// + /// 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). + /// + [DataMember(IsRequired = false, Name = "limit")] + public int Limit { get; set; } + + public static CaptureImageOptions Default + { + get { return new CaptureImageOptions() { Limit = 1 }; } + } + } + + /// + /// Represents captureAudio action options. + /// + [DataContract] + public class CaptureAudioOptions + { + /// + /// The maximum number of audio files the device user can capture in a single capture operation. The value must be greater than or equal to 1 (defaults to 1). + /// + [DataMember(IsRequired = false, Name = "limit")] + public int Limit { get; set; } + + public static CaptureAudioOptions Default + { + get { return new CaptureAudioOptions() { Limit = 1 }; } + } + } + + /// + /// Represents captureVideo action options. + /// + [DataContract] + public class CaptureVideoOptions + { + /// + /// 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). + /// + [DataMember(IsRequired = false, Name = "limit")] + public int Limit { get; set; } + + public static CaptureVideoOptions Default + { + get { return new CaptureVideoOptions() { Limit = 1 }; } + } + } + + /// + /// Represents getFormatData action options. + /// + [DataContract] + public class MediaFormatOptions + { + /// + /// File path + /// + [DataMember(IsRequired = true, Name = "fullPath")] + public string FullPath { get; set; } + + /// + /// File mime type + /// + [DataMember(Name = "type")] + public string Type { get; set; } + + } + + /// + /// Stores image info + /// + [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(); + } + } + } + + /// + /// Stores additional media file data + /// + [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 + + /// + /// Folder to store captured images + /// + private string isoFolder = "CapturedImagesCache"; + + /// + /// Capture Image options + /// + protected CaptureImageOptions captureImageOptions; + + /// + /// Capture Audio options + /// + protected CaptureAudioOptions captureAudioOptions; + + /// + /// Capture Video options + /// + protected CaptureVideoOptions captureVideoOptions; + + /// + /// Used to open camera application + /// + private CameraCaptureTask cameraTask; + + /// + /// Used for audio recording + /// + private AudioCaptureTask audioCaptureTask; + + /// + /// Used for video recording + /// + private VideoCaptureTask videoCaptureTask; + + /// + /// Stores information about captured files + /// + List files = new List(); + + /// + /// Launches default camera application to capture image + /// + /// may contains limit or mode parameters + public void captureImage(string options) + { + try + { + try + { + + string args = JSON.JsonHelper.Deserialize(options)[0]; + this.captureImageOptions = String.IsNullOrEmpty(args) ? CaptureImageOptions.Default : JSON.JsonHelper.Deserialize(args); + + } + 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)); + } + } + + /// + /// Launches our own audio recording control to capture audio + /// + /// may contains additional parameters + public void captureAudio(string options) + { + try + { + try + { + string args = JSON.JsonHelper.Deserialize(options)[0]; + this.captureAudioOptions = String.IsNullOrEmpty(args) ? CaptureAudioOptions.Default : JSON.JsonHelper.Deserialize(args); + + } + 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)); + } + } + + /// + /// Launches our own video recording control to capture video + /// + /// may contains additional parameters + public void captureVideo(string options) + { + try + { + try + { + string args = JSON.JsonHelper.Deserialize(options)[0]; + this.captureVideoOptions = String.IsNullOrEmpty(args) ? CaptureVideoOptions.Default : JSON.JsonHelper.Deserialize(args); + + } + 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)); + } + } + + /// + /// Retrieves the format information of the media file. + /// + /// + public void getFormatData(string options) + { + try + { + MediaFormatOptions mediaFormatOptions; + try + { + mediaFormatOptions = new MediaFormatOptions(); + string[] optionStrings = JSON.JsonHelper.Deserialize(options); + mediaFormatOptions.FullPath = optionStrings[0]; + mediaFormatOptions.Type = optionStrings[1]; + } + 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")) + { + Deployment.Current.Dispatcher.BeginInvoke(() => + { + 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)); + } + } + + /// + /// Opens specified file in media player + /// + /// MediaFile to play + public void play(string options) + { + try + { + MediaFile file; + + try + { + file = String.IsNullOrEmpty(options) ? null : JSON.JsonHelper.Deserialize(options)[0]; + + } + 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)); + } + } + + + /// + /// Handles result of capture to save image information + /// + /// + /// stores information about current captured image + 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); + + int orient = ImageExifHelper.getImageOrientationFromStream(e.ChosenPhoto); + int newAngle = 0; + switch (orient) + { + case ImageExifOrientation.LandscapeLeft: + newAngle = 90; + break; + case ImageExifOrientation.PortraitUpsideDown: + newAngle = 180; + break; + case ImageExifOrientation.LandscapeRight: + newAngle = 270; + break; + case ImageExifOrientation.Portrait: + default: break; // 0 default already set + } + + Stream rotImageStream = ImageExifHelper.RotateStream(e.ChosenPhoto, newAngle); + + // Save image in isolated storage + + // we should return stream position back after saving stream to media library + rotImageStream.Seek(0, SeekOrigin.Begin); + + byte[] imageBytes = new byte[rotImageStream.Length]; + rotImageStream.Read(imageBytes, 0, imageBytes.Length); + rotImageStream.Dispose(); + string pathLocalStorage = this.SaveImageToLocalStorage(fileName, isoFolder, imageBytes); + imageBytes = null; + // 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)); + 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)); + files.Clear(); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Canceled.")); + } + break; + + default: + if (files.Count > 0) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files)); + files.Clear(); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Did not complete!")); + } + break; + } + } + + /// + /// Handles result of audio recording tasks + /// + /// + /// stores information about current captured audio + 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)); + 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)); + files.Clear(); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Canceled.")); + } + break; + + default: + if (files.Count > 0) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files)); + files.Clear(); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Did not complete!")); + } + break; + } + } + + /// + /// Handles result of video recording tasks + /// + /// + /// stores information about current captured video + 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)); + 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)); + files.Clear(); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Did not complete!")); + } + break; + } + } + + /// + /// Extract file from Isolated Storage as WriteableBitmap object + /// + /// + /// + 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; + } + } + + + /// + /// Saves captured image in isolated storage + /// + /// image file name + /// folder to store images + /// Image path + 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 (IsolatedStorageFileStream 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