jquery-mobilegetusermediawebcam-capture

getusermedia freezes in mobile browsers


The JavaScript below captures still images from a webcam on a jQuery-mobile website. The code works perfectly on desktops, but on mobile phones (Android and iOS) the video plays then stops on the first frame!

Code (sorry about the length):

    window.shutter = document.createElement('audio');
    window.shutter.volume = 1;

    // var v = new uploadZone($('<img data-src="http://link-to-upload/" data-multiple="true"/>"));
    // v.load();
    uploadZone = function(element){
        var object = this,
            mobileInput = $('<input type="file" accept="image/*" multiple="multiple" />'),
            errBack = function(e){console.log('error',e);},
            localstream,
            canvas = document.createElement('canvas'),
            thumb = document.createElement('canvas'),
            ctx = canvas.getContext("2d"),
            ctxsmall = thumb.getContext("2d"),
            videoObj = {"video": true},
            video = document.createElement('video'),
            uz = $('<div id="uploadzone-container">'),
            snap = $('<a href="#" class="uz-snap"><i class="fa fa-camera"></i></a>'),
            confirm = $('<a href="#" class="uz-confirm"><i class="fa fa-check"></i></a>'),
            clear = $('<a href="#" class="uz-clear"><i class="fa fa-close"></i></a>'),
            collection = $('<form id="uploadzone-collection" class="scrollY"></form>'),
            backdrop = $('<div class="modal-backdrop fade in uploadZone"></div>'),
            choice = $('<div class="uz-choice"><h2>Upload picture</h2></div>'),
            webcam = $('<a class="btn btn-success">Webcam</a>'),
            files = $('<a class="btn btn-primary">Gallery</a>'),
            uzVid = $('<div id="uz-video-container"></div>'),
            upload = $('<a class="btn btn-warning" href="#"><i class="fa fa-upload"></i> upload </a>'),
            li = '<div><img width="80px" height="80px" src=""/><input name="title" type="text" /><input type="hidden"/> </div>';

        this.uploadObject = {
                img:[],//array of [title]=data
                cover: element.attr('data-cover'),//BOOL is this an album cover or not
                gallery: element.attr('data-snap-picture'), //album name
                date: element.attr('data-date') //when was this picture taken ?
            };
        canvas.width  = 600;
        canvas.height = 500;
        thumb.width  = 80;
        thumb.height = 80;
        if(element.is('[data-multiple]')){
            collection.append(upload.hide());
        }else{
            mobileInput.removeAttr('multiple');
        }
        //$.post(url, $('#uploadzone-collection').serialize()).done(function(o) {
        backdrop.click(function(){$(this).remove();uz.remove();object.stop()});
        webcam.click(function(e){object.webcamStart();});
        files.click(function(e){
            mobileInput.trigger('click');
            uz.append(collection);
        });
        mobileInput.change(function(evt){
            console.log('changed');
          //  console.log(new FormData( this ));
            var files = evt.target.files; // FileList object

            // Loop through the FileList and render image files as thumbnails.
            for (var i = 0, f; f = files[i]; i++) {
                // Only process image files.
                if (!f.type.match('image.*')) {continue;}

                var reader = new FileReader();
                // Closure to capture the file information.
                reader.onload = (function(theFile) {
                    return function(e) {
                        if(element.is('[data-multiple]')){
                            var newLi = $(li);
                            upload.show();
                            collection.append(newLi);
                            var title = escape(theFile.name);
                            newLi.find('img')[0].src = e.target.result;
                            object.uploadObject['img'].push({'title':title,'data':e.target.result});
                        }else{
                            element[0].src = e.target.result;
                            backdrop.trigger('click');
                            object.uploadObject['img'].push({'title':element.attr('data-snap-picture'),'data':e.target.result});
                            object.uploadAll();
                        }
                    };
                })(f);

                // Read in the image file as a data URL.
                reader.readAsDataURL(f);
            }
            if(collection.find('img').length > 0)upload.show(); else upload.hide();
        });


        clear.click(function(){
            snap.show();
            confirm.hide();
            clear.hide();
            video.play();
        });
        snap.click(function(){
            window.shutter.play();video.pause();
            snap.hide();
            confirm.show();
            clear.show();
        });
        confirm.click(function(){
            if(element.is('[data-multiple]')){
                var newLi = $(li);
                upload.show();
                collection.append(newLi);
                var title = prompt("Picture title", "Paper "+newLi.index());
                if (title != null) {
                    ctxsmall.drawImage(video, 0, 0, 80, 80);
                    ctx.drawImage(video, 0, 0, 600, 500);
                    newLi.find('img')[0].src = thumb.toDataURL();
                    newLi.find('input').val(title);
                    object.uploadObject['img'].push({'title':title,'data':canvas.toDataURL()});
                    clear.trigger('click');
                }else{
                    newLi.remove();
                }
            }else{
                ctx.drawImage(video, 0, 0, 600, 500);
                var dataURL = canvas.toDataURL();
                $(element)[0].src = dataURL;
                object.uploadObject['img'].push({'title':element.attr('data-snap-picture'),'data':dataURL});
                object.uploadAll();
                backdrop.trigger('click');
            }
        });
        upload.click(function(){
            object.uploadAll();
            backdrop.trigger('click');
        });

        this.uploadAll =function(){
            var url = element.attr('data-src')+'/'+element.attr('data-snap-picture');
            if(element.is('[data-cover]'))url= url+'/1';

            if(object.uploadObject['img'].length < 1)return console.log('nothing to upload');

            return $.post(url,object.uploadObject,function(){
                alert('success');
            }).fail(function(){alert('falied')}).then(function(){object.uploadObject['img']=[]});
        }
        this.load = function(){//with choice
            if(!$(element).is('[data-src]'))return alert('bad attempt');
            $('body').append(backdrop).append(uz.append(choice.append(webcam).append(files)));
        }
        this.stop = function(){
            if (video.mozSrcObject) {
                console.log('mox');
                video.mozSrcObject.stop();
                video.src = null;
            }else{
                video.src = "";
                if(localstream)localstream.stop();
            }
        };
        this.webcamStart = function(){
            choice.slideUp()
            object.start();
            uz.append(uzVid.append(video)).append(collection);
            uzVid.append(snap).append(confirm.hide());
        }
        this.start = function(){
            if (navigator.webkitGetUserMedia) {// WebKit-prefixed
                navigator.webkitGetUserMedia(videoObj, function(stream) {
                    video.src = window.webkitURL.createObjectURL(stream);
                    video.play();
                    localstream = stream;
                }, errBack);
            } else if (navigator.mozGetUserMedia) {// Firefox-prefixed
                navigator.mozGetUserMedia(videoObj, function(stream) {
                    video.src = window.URL.createObjectURL(stream);
                    video.play();
                    localstream = stream;
                }, errBack);
            }else if (navigator.getUserMedia) {// Standard
                navigator.getUserMedia(videoObj, function(stream) {
                    video.src = stream;
                    video.play();
                    localstream = stream;
                }, errBack);
            }
        };
    };

I'm not sure if the problem is in my this.start() function? Or is there something else I'm not aware of when handling a webcam on mobile devices?


Solution

  • video.setAttribute('autoplay', '');
    video.setAttribute('muted', '');
    video.setAttribute('playsinline', '');
    

    I have to do these to make it work in Safari. Also, Chrome iOS and Firefox iOS just don't have permission to use camera somehow. The solution comes from https://medium.com/@leemartin/hello-webrtc-on-safari-11-e8bcb5335295