Cordova plugin media capture a tutorial

 

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