| /* |
| 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 |
| { |
| /// <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 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). |
| /// </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 |
| { |
| |
| string args = JSON.JsonHelper.Deserialize<string[]>(options)[0]; |
| this.captureImageOptions = String.IsNullOrEmpty(args) ? CaptureImageOptions.Default : JSON.JsonHelper.Deserialize<CaptureImageOptions>(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)); |
| } |
| } |
| |
| /// <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 |
| { |
| string args = JSON.JsonHelper.Deserialize<string[]>(options)[0]; |
| this.captureAudioOptions = String.IsNullOrEmpty(args) ? CaptureAudioOptions.Default : JSON.JsonHelper.Deserialize<CaptureAudioOptions>(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)); |
| } |
| } |
| |
| /// <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 |
| { |
| string args = JSON.JsonHelper.Deserialize<string[]>(options)[0]; |
| this.captureVideoOptions = String.IsNullOrEmpty(args) ? CaptureVideoOptions.Default : JSON.JsonHelper.Deserialize<CaptureVideoOptions>(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)); |
| } |
| } |
| |
| /// <summary> |
| /// Retrieves the format information of the media file. |
| /// </summary> |
| /// <param name="options"></param> |
| public void getFormatData(string options) |
| { |
| try |
| { |
| MediaFormatOptions mediaFormatOptions; |
| try |
| { |
| mediaFormatOptions = new MediaFormatOptions(); |
| string[] optionStrings = JSON.JsonHelper.Deserialize<string[]>(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)); |
| } |
| } |
| |
| /// <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)[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)); |
| } |
| } |
| |
| |
| /// <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); |
| |
| 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; |
| } |
| } |
| |
| /// <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)); |
| 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; |
| } |
| } |
| |
| /// <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)); |
| 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; |
| } |
| } |
| |
| /// <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 (IsolatedStorageFileStream stream = isoFile.CreateFile(filePath)) |
| { |
| stream.Write(imageBytes, 0, imageBytes.Length); |
| } |
| |
| return filePath; |
| } |
| catch (Exception) |
| { |
| //TODO: log or do something else |
| throw; |
| } |
| } |
| |
| |
| } |
| } |