Cordova plugin media capture a tutorial

mohamad wael
7 min readFeb 3, 2021

--

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 pendingcaptureerrorevents , 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 ;} }
//Capture 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

Originally published at https://twiserandom.com on February 3, 2021.

--

--

No responses yet