'cordova-plugin-camera' with Quasar



  • Hello,
    I am looking to use the Cordova plugin “cordova-plugin-camera”.
    If I create a project directly under cordova, I have no problem.
    If I use the example of the documentation of quasar framework for this plugin, the photo does not appear after using the camera and I have a message in the console of the ‘remote devices’ of Chrome "Failed to load resource: net :: ERR_INVALID_URL "with the url of my picture.
    Has anyone ever had this problem?
    Thank you for your help.

    0_1548009315832_5c5517e6-b695-436f-b755-9f56808e1b1c-image.png



  • you can access the camera with the html5 API, did you know that? I could share with you some (still dirty) code I’m currently working on:

    <template>
        <section>
            <q-modal v-model="opened">
                <section class="relative-position overflow-hidden fit">
                    <figure class="no-margin" v-if="!imageUrl">
                        <video ref="video" :src="source" class="fixed-center" :height="camHeight"
                        />
                    </figure>
    
                    <div>
                        <img v-if="bin.images.length > 0" class="rounded-borders border-primary avatar-lg absolute-bottom-right last-pic-avatar"
                             :src="lastImg"
                        />
    
                        <loading v-if="loadingCamera">
                            Loading...
                        </loading>
    
                        <img v-if="imageUrl" class="window-width" :src="imageUrl" alt="Image taken">
    
                        <!--<div class="fullscreen" v-if="notSupported !== false">-->
                        <!--<h2>This browser does not support the camera.</h2>-->
                        <!--<p>-->
                        <!--Please download <a href="https://www.mozilla.org">Firefox</a> <a href="https://www.google.com/chrome">Chrome</a>-->
                        <!--</p>-->
                        <!--</div>-->
    
                        <cam-btn-group class="no-shadow bg-black flat">
                            <div v-if="imageUrl">
                                <q-btn @click="onConfirmImage" class="absolute-center" flat color="white" icon="far fa-check-circle" size="xl"
                                />
                                <q-page-sticky position="bottom-right" :offset="[10, -54]">
                                    <q-btn @click="onCancelImage" class="q-ml-xl" flat color="white" icon="far fa-times-circle" size="xl"
                                           wait-for-ripple
                                    />
                                </q-page-sticky>
                            </div>
                            <div v-else>
                                <q-btn @click="onCapture" class="absolute-center" flat color="white" icon="far fa-dot-circle" size="xl"/>
    
                                <q-page-sticky position="bottom-right" :offset="[10, -60]">
                                    <q-btn @click="exitCamera" label="Next" wait-for-ripple flat color="white"
                                           icon-right="fas fa-long-arrow-alt-right" size="xl"
                                    />
                                </q-page-sticky>
                            </div>
                        </cam-btn-group>
                    </div>
                </section>
            </q-modal>
        </section>
    </template>
    
    <script>
    import { get }     from 'vuex-pathify'
    import messages    from '../../common/messages.json'
    import Loading     from '../VLoading'
    import CamBtnGroup from '../VFooterBtns.vue'
    
    export default {
        name: 'Camera',
        components: {
            Loading,
            CamBtnGroup
        },
        data () {
            return {
                opened: true,
                mediaStream: null,
                imageUrl: null,
                isDefaultBackCamera: true,
                camera: null,
                cameras: [],
                source: null,
                loadingCamera: true
            }
        },
        computed: {
            ...get('DeployModule/*'),
            hasBackCamera () {
                const backCameraDevices = this.cameras.filter(device => {
                    return device.label.indexOf('back') !== -1
                })
                return backCameraDevices.length > 1 ? backCameraDevices[0] : false
            },
            camHeight () {
                return window.innerHeight
            },
            lastImg () {
                return this.bin.images.slice(-1)
            }
        },
        mounted () {
            if (navigator.mediaDevices === undefined) {
                navigator.mediaDevices = {}
            }
    
            if (navigator.mediaDevices.getUserMedia === undefined) {
                navigator.mediaDevices.getUserMedia = this.legacyGetUserMediaSupport()
            }
    
            let constraints = this.isDefaultBackCamera ? 'environment' : 'user'
            constraints = { video: { facingMode: { ideal: constraints } } }
    
            this.testMediaAccess(constraints)
        },
        methods: {
            legacyGetUserMediaSupport () {
                return constraints => {
                    // First get ahold of the legacy getUserMedia, if present
                    let getUserMedia =
                                navigator.getUserMedia ||
                                navigator.webkitGetUserMedia ||
                                navigator.mozGetUserMedia ||
                                navigator.msGetUserMedia ||
                                navigator.oGetUserMedia
    
                    // Some browsers just don't implement it - return a rejected promise with an error
                    // to keep a consistent interface
                    if (!getUserMedia) {
                        return Promise.reject(
                            new Error('getUserMedia is not implemented in this browser')
                        )
                    }
    
                    // Otherwise, wrap the call to the old navigator.getUserMedia with a Promise
                    return new Promise(function (resolve, reject) {
                        getUserMedia.call(navigator, constraints, resolve, reject)
                    })
                }
            },
            testMediaAccess (constraints) {
                // Check if there's the camera set by isDefaultBackCamera
                navigator.mediaDevices.getUserMedia(constraints).then(stream => {
                    this.getCameras()
                }).catch(() => {
                    this.$q.notify(messages.camera.error.backCamera)
                    this.testMediaAccess({ video: true })
                })
            },
            getCameras () {
                // GET The cameras && and get the Back Camera
                navigator.mediaDevices.enumerateDevices().then(deviceInfos => {
                    this.loadingCamera = false
                    deviceInfos.forEach(device => {
                        const constraints = this.hasBackCamera ? 'back' : 'amera' // Camera string could be Capital
    
                        if (device.label.indexOf(constraints) !== -1) {
                            this.camera = device
                        }
    
                        if (device.kind.indexOf('videoinput') !== -1) {
                            this.cameras.push(device)
                        }
                    })
                    this.setCamera(this.camera.deviceId)
                })
            },
            setCamera (deviceId) {
                navigator.mediaDevices.getUserMedia({ video: { deviceId: { exact: deviceId } } }).then(mediaStream => {
                    this.mediaStream = mediaStream
                    /** Load the stream to the video tag**/
                    if ('srcObject' in this.$refs.video) {
                        // new browsers api
                        this.$refs.video.srcObject = mediaStream
                        this.$refs.video.onloadedmetadata = () => {
                            this.$refs.video.play()
                        }
                    } else {
                        // old broswers
                        this.mediaStream = window.HTMLMediaElement.srcObject(mediaStream)
                    }
                }).catch(() => this.$q.notify(messages.camera.error.camera))
            },
            onCapture () {
                const mediaStreamTrack = this.mediaStream.getVideoTracks()[0]
                const imageCapture = new window.ImageCapture(mediaStreamTrack)
    
                return imageCapture.takePhoto().then(blob => {
                    this.imageUrl = URL.createObjectURL(blob)
                })
            },
            onConfirmImage () {
                this.bin.images.push(this.imageUrl)
                this.$store.set('DeployModule/bin@images', this.bin.images)
                this.imageUrl = null
                this.setCamera(this.camera.deviceId)
            },
            onCancelImage () {
                this.imageUrl = null
                this.setCamera(this.camera.deviceId)
            },
            exitCamera () {
                let stream = this.$refs.video.srcObject
                let tracks = stream.getTracks()
    
                tracks.forEach(track => {
                    // stops the video track
                    track.stop()
                })
                this.$refs.video.srcObject = null
                this.mediaStream = null
    
                this.$emit('stopped')
    
                this.opened = false
            }
        }
    }
    </script>
    
    <style lang="stylus" scoped>
        .icon-group .wrapper-icon
            font-size 36px
    
        .inner-icon
            position relative
            font-size 20px
            top 4px
            left -54px
            z-index 99
    
        .last-pic-avatar
            margin 0 60px 70px 0
            z-index 9999999999
    
        .arrow-exit
            position absolute
            left 0
            top 5px
    </style>
    
    
    


  • Thank you, but I would really like to use this plugin.

    • Here is the cordova code that I use to do my tests which is not a problem:
    <!DOCTYPE html>
    <html>
      <head>
        <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; script-src 'self' 'unsafe-inline'; img-src 'self' data: content: ;"/>
        <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"
        />
        <link rel="stylesheet" type="text/css" href="css/index.css" />
        <title>Hello World</title>
      </head>
      <body>
        <div class="page">
          <p><img src="img/logo.png" alt="image" id="photo" /></p>
          <p><button id="btn">Take picture</button></p>
          <p id="msg"></p>
        </div>
        <script type="text/javascript" src="cordova.js"></script>
        <script>
          let app = {
            init: function() {
              document
                .getElementById('btn')
                .addEventListener('click', app.takephoto);
            },
            takephoto: function() {
              let opts = {
                quality: 80,
                destinationType: Camera.DestinationType.FILE_URI,
                sourceType: Camera.PictureSourceType.CAMERA,
                mediaType: Camera.MediaType.PICTURE,
                encodingType: Camera.EncodingType.JPEG,
                cameraDirection: Camera.Direction.BACK,
                targetWidth: 300,
                targetHeight: 400
              };
              navigator.camera.getPicture(app.ftw, app.wtf, opts);
            },
            ftw: function(imgURI) {
              console.log(imgURI)
              document.getElementById('msg').textContent = imgURI;
              document.getElementById('photo').src = imgURI;
            },
            wtf: function(msg) {
              document.getElementById('msg').textContent = msg;
            }
          };
          document.addEventListener('deviceready', app.init);
        </script>
      </body>
    </html>
    
    

    0_1548930824363_Screenshot_1548930572.png

    • And now the code under Quasar where the image does not appear.
    <template>
      <div class="page">
        <img
            :src="photo"
            alt="photo"
          />
        <button @click="takephoto">Take picture</button>
        <p>{{msg}}</p>
      </div>
    </template>
    
    <script>
    
    export default {
      name: 'hello',
      data () {
        return {
          msg: '',
          photo: 'statics/quasar-logo.png'
        }
      },
      methods: {
        takephoto: function () {
          let opts = {
            quality: 80,
            destinationType: Camera.DestinationType.FILE_URI,
            sourceType: Camera.PictureSourceType.CAMERA,
            mediaType: Camera.MediaType.PICTURE,
            encodingType: Camera.EncodingType.JPEG,
            cameraDirection: Camera.Direction.BACK,
            targetWidth: 300,
            targetHeight: 400
          }
          navigator.camera.getPicture(this.ftw, this.wtf, opts)
        },
        ftw: function (imgURI) {
          this.msg = imgURI
          this.photo = imgURI
        },
        wtf: function (msg) {
          this.msg = msg
        }
      }
    }
    </script>
    
    <style>
    </style>
    
    

    0_1548931777015_Screenshot_1548931749.png



  • I use the same plugin for a project and it works perfectly.
    Your code does look very similar to mine. The only difference I’ve spotted is that I’ve declared navigator before camera in the options like so:
    encodingType: navigator.camera.EncodingType.JPEG,
    mediaType: navigator.camera.MediaType.PICTURE,

    Hope this helps



  • @Lou-Rectoret
    Good Morning. The example that you use is very useful to me, only that at this moment it is generating problems to me “… get (‘DeployModule / *’)”, you could help, showing the code of that module. Thank you so much.



  • Hey @desman !

    “… get (‘DeployModule / *’)is the way vuex-pathify works for mapping the State less verbose.

    So I’m only storing the image path in the Central State, see this:
    bcd66c51-8652-41ce-b413-6c78da78ffba-imagen.png

    That’s all, I’m using vuex-pathify on top of Vuex.
    Try setting the path on the data property of the component, you should be fine.

    GL


Log in to reply