Table of Contents
Plugin Concepts
The cordova media capture plugin , can be used to capture audios , images , and videos , using a smartphone .
This plugin adds the capture
object , accessible globally , when the deviceready
event is fired , using navigator.device.capture
. This object , has three methods defined , allowing the capture of audios , images , and videos .
The captured media file , is an object which has the following properties :
/*MediaFile object */ { name : "Name of file", //String fullPath : "Path file including its name", //String type : "Mime type", //String size : number_of_Bytes_in_File , //number lastModifiedDate : Date, /*Date , date and time file last modified .*/ getFormatData (successFct , failureFct ){ /*Function , takes on success , on failure functions .*/ successFct({ /*success function , called with an object containing information , about the captured media file .*/ codecs : "" , /*String . Format of audio , video content , unsupported on android , iOS , returns null .*/ bitrate : number_Average_Bitrate , /*number . 0 for images , unsupported android , iOS , returns 0 .*/ height : heightImageVideo , /*number . Height in pixels images , videos . supported , iOS , android . 0 for audio .*/ width : widthImageVideo , /* number . Width in pixels images , videos . supported , iOS , android . 0 for audio .*/ duration : number_Sec_Audio_Video , /*number . duration in seconds , for audios , and videos . 0 for images .*/ }); failureFct({ /*failure function , called with an object containing error details .*/ code : ERROR_CODE , /* ERROR_CODE , can be one of the following predefined error codes . CaptureError.CAPTURE_INVALID_ARGUMENT invalid options passed . numeric value 2 . CaptureError.CAPTURE_INTERNAL_ERR camera , microphone , failed capture image or sound . numeric value 0 . CaptureError.CAPTURE_PERMISSION_DENIED user denied permission . numeric value 4 . CaptureError.CAPTURE_APPLICATION_BUSY camera , audio capture , serving other request . numeric value 1 . CaptureError.CAPTURE_NO_MEDIA_FILES user exit without capturing anything . numeric value 3 . CaptureError.CAPTURE_NOT_SUPPORTED requested capture operation not supported . numeric value 20 .*/}); } }
The options that can be set , to capture audios are :
{ limit : 1 , /*Number . number of recordings to capture , default 1 . unsupported on ios .*/ duration : 60 /*Number . duration recording in seconds , unlimited , if not set . unsupported on android .*/ }
The options that can be set to capture videos are :
{ limit : 1 , /*Number . Default 1 , number of video recordings , to capture . unsupported iOS .*/ duration : 60 , /*Number . duration recordings in sec , unlimited if not set , supported both android and iOS . */ quality : 1 /*Number . quality of video capturing . possible values , 0 for low quality , 1 for high quality . default 1 . unsupported ios .*/ }
The options that can be set to capture images are :
{ limit : 1 , /*Number . number of images to capture . unsupported ios .*/ }
When media capture is done successfully , a function to handle the success event is called . This function receives an array of objects , of type MediaFile
, containing the captured media files .
function captureSuccess(mediaFiles ){ for(let mediaFile of mediaFiles ){ console.log(mediaFile.name ); console.log(mediaFile.fullPath ); console.log(mediaFile.size ); console.log(mediaFile.lastModifiedDate ); mediaFile.getFormatData( function(mediaFileData ){ console.log(mediaFileData.codecs ); console.log(mediaFileData.bitrate ); console.log(mediaFileData.height ); console.log(mediaFileData.width ); console.log(mediaFileData.duration ); } , function(captureError ){ console.log(captureError.code); } ); }}
When capturing medias is done unsuccessfully , a function to handle the failure event is called , It receives an object , containing the error code .
function captureFailure(captureError ){ console.log(captureError.code ) ; } /*ERROR_CODE , as described earlier , can be one of the following predefined error codes . CaptureError.CAPTURE_INVALID_ARGUMENT invalid options passed . numeric value 2 . CaptureError.CAPTURE_INTERNAL_ERR camera , microphone , failed capture image or sound . numeric value 0 . CaptureError.CAPTURE_PERMISSION_DENIED user denied permission . numeric value 4 . CaptureError.CAPTURE_APPLICATION_BUSY camera , audio capture , serving other request . numeric value 1 . CaptureError.CAPTURE_NO_MEDIA_FILES user exit without capturing anything . numeric value 3 . CaptureError.CAPTURE_NOT_SUPPORTED requested capture operation not supported . numeric value 20 .*/
For iOS , usage description must be provided , when accessing , the camera , microphone , and photo library . This can be done , by adding to the config.xml
file , in the root directory of the project , in between the widget
element , the following content .
<edit-config file="*-Info.plist" mode="merge" target="NSCameraUsageDescription"> <string>need camera access to take pictures</string> </edit-config> <edit-config file="*-Info.plist" mode="merge" target="NSMicrophoneUsageDescription"> <string>need microphone access to record sounds</string> </edit-config> <edit-config file="*-Info.plist" mode="merge" target="NSPhotoLibraryUsageDescription"> <string>need to photo library access to get pictures from there</string> </edit-config>
To capture audios , the following function can be used :
navigator.device.capture.captureAudio( callBackSuccessFunction , callBackFailureFunction , options ); /*options , is optional .*/
To capture videos , the following function can be used :
navigator.device.capture.captureVideo( callBackSuccessFunction , callBackFailureFunction , options ); /*options , is optional .*/
To capture images , the following function can be used :
navigator.device.capture.captureImage( callBackSuccessFunction , callBackFailureFunction , options ); /*options , is optional .*/
On android , an application might get destroyed , because of low memory , while performing media capture . In such a case , the result are delivered via the pendingcaptureresult
, and pendingcaptureerror
events , instead of being available via the registered call back , success and error functions .
//onDeviceReady function function onDeviceReady( ) { /*When an application is destroyed on android , instead of receiving the results on the registered success , and failure callback functions , they are available via the pendingcaptureresult and pendingcaptureerror events , to which one must register , to get the results .*/ document.addEventListener('pendingcaptureresult' , function(mediaFiles ) { /* success , do something .*/ } ); document.addEventListener('pendingcaptureerror', function(captureError ) { /* error , do something .*/ } );} document.addEventListener('deviceready' , onDeviceReady );
Demo application
To create a demo application , start by issuing the following commands .
$ cordova create media-capture-plugin-demo com.twiserandom.mobileapps.demo.mediaCapturePluginDemo "Media Capture Plugin Demo" $ cd media-capture-plugin-demo/ $ cordova platform add ios $ cordova platform add android $ cordova plugin add cordova-plugin-media-capture
Edit the config.xml
file , in the root of your application , to look like this :
<?xml version='1.0' encoding='utf-8'?> <widget id="com.twiserandom.mobileapps.demo.mediaCapturePluginDemo" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0"> <name>Media Capture Plugin Demo</name> <description> A sample Apache Cordova application that responds to the deviceready event. </description> <author email="dev@cordova.apache.org" href="http://cordova.io"> Apache Cordova Team </author> <content src="index.html" /> <access origin="*" /> <allow-intent href="http://*/*" /> <allow-intent href="https://*/*" /> <allow-intent href="tel:*" /> <allow-intent href="sms:*" /> <allow-intent href="mailto:*" /> <allow-intent href="geo:*" /> <platform name="android"> <allow-intent href="market:*" /> </platform> <platform name="ios"> <allow-intent href="itms:*" /> <allow-intent href="itms-apps:*" /> </platform> <edit-config file="*-Info.plist" mode="merge" target="NSCameraUsageDescription"> <string>need camera access to take pictures</string> </edit-config> <edit-config file="*-Info.plist" mode="merge" target="NSMicrophoneUsageDescription"> <string>need microphone access to record sounds</string> </edit-config> <edit-config file="*-Info.plist" mode="merge" target="NSPhotoLibraryUsageDescription"> <string>need to photo library access to get pictures from there</string> </edit-config> </widget>
Edit the www/index.html
file , to look like this :
<!DOCTYPE html> <html> <head> <meta name="format-detection" content="telephone=no" > <meta name="msapplication-tap-highlight" content="no" > <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width" > <title>Demo Media capture plugin </title> <style> html { box-sizing: content-box; } html * , html *::before , html *::after{ box-sizing: inherit; } *{ border-radius: 0px; margin : 0px; padding : 0px; } body{ padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); } .app{ display: flex; flex-direction: column; margin : 10px; } .app button{ font-size: 18px; } .app img , .app audio , .app video{ width: 100%; } </style> </head> <body> <div id="mediaCapturePluginDemo" > <div id="imageCaptureApp" class="app" > <!-- image capture --> <img /> <button onclick="captureMedia( this.previousElementSibling , 'img' , {limit : 2 } )" > Capture Image </button > </div > <!-- image capture app--> <div id="audioCaptureApp" class="app" > <!-- audio capture app--> <audio controls autoplay></audio > <button onclick="captureMedia( this.previousElementSibling , 'audio', {limit : 2 , duration: 30 } )" > Capture audio </button > </div > <!-- audio capture app--> <div id="videoCaptureApp" class="app" > <!-- video capture app --> <video controls autoplay></video > <button onclick="captureMedia( this.previousElementSibling , 'video', {limit : 2 , duration : 22 , quality : 1 } )" > Capture video </button > </div > <!-- video capture app--> </div > <script type="text/javascript" src="cordova.js"></script> <script> document.addEventListener('deviceready' , onDeviceReady , false ); document.addEventListener("pause", onPause, false); document.addEventListener("resume", onResume, false); // On Device Ready Function function onDeviceReady( ){ deviceIsReady = true ; idStorageKey = "idStorageKey"; document.addEventListener('pendingcaptureresult' , mediaCapture_Success ); document.addEventListener('pendingcaptureerror', mediaCapture_Failure ); } //Capture Media function captureMedia(displayMedia_Element , mediaSource , captureOptions ){ mediaElement = displayMedia_Element; switch(mediaSource ){ case 'img' : navigator.device.capture.captureImage( mediaCapture_Success, mediaCapture_Failure, captureOptions ); break; case 'audio' : navigator.device.capture.captureAudio( mediaCapture_Success, mediaCapture_Failure, captureOptions ); break; case 'video' : navigator.device.capture.captureVideo( mediaCapture_Success, mediaCapture_Failure, captureOptions ); break; } } //Capture media success function mediaCapture_Success(mediaFiles ){ let currentMediaElement = mediaElement; let imageDurationDisplay = 10 * 1000; let previousMediaFile = null; let duration = 0 ; for(let mediaFile of mediaFiles ){ (function(){ let currentMediaFile = mediaFile; if(previousMediaFile == null ) currentMediaElement.src = currentMediaFile.fullPath; else previousMediaFile.getFormatData( function(mediaFileData ){ duration = duration + mediaFileData.duration == 0 ? imageDurationDisplay : mediaFileData.duration * 1000; setTimeout(function() { currentMediaElement.src = currentMediaFile.fullPath; }, duration ); } , function(captureError ){ console.log(captureError.code ); });})(); previousMediaFile = mediaFile ;} } //Caputre media failure function mediaCapture_Failure(captureError ){ console.log(captureError.code ); } //Applicaton on pause handler function onPause( ){ if( "mediaElement" in window ){ let mediaElementParentId = mediaElement.parentElement.id; window.localStorage.setItem(idStorageKey, mediaElementParentId ); } else window.localStorage.removeItem(idStorageKey ); } //Application on Resume handler function onResume( ){ let mediaElementParentId = window.localStorage.getItem(idStorageKey ); if(mediaElementParentId !== null ) mediaElement = document.getElementById(mediaElementParentId ).firstElementChild; } </script> </body> </html>
Run the application using :
$ cordova emulate ios $ cordova emulate android