Add better camera access for non-cordova deployments (#116)
This adds direct javascript access to the camera (with permission requests). Capturing an image with the camera now happens right on the detect page and not a separate window. This also opens the possibility of live detection on the web app. Reviewed-on: Georgi_Lab/ALVINN_f7#116
This commit is contained in:
@@ -7,7 +7,11 @@
|
|||||||
<f7-block class="detect-grid">
|
<f7-block class="detect-grid">
|
||||||
<div class="image-container">
|
<div class="image-container">
|
||||||
<canvas id="im-draw" ref="image_cvs" @click="structureClick" :style="`display: ${imageLoaded ? 'block' : 'none'}; flex: 1 1 0%; max-width: 100%; max-height: 100%; min-width: 0; min-height: 0; background-size: contain; background-position: center; background-repeat: no-repeat`" />
|
<canvas id="im-draw" ref="image_cvs" @click="structureClick" :style="`display: ${imageLoaded ? 'block' : 'none'}; flex: 1 1 0%; max-width: 100%; max-height: 100%; min-width: 0; min-height: 0; background-size: contain; background-position: center; background-repeat: no-repeat`" />
|
||||||
<SvgIcon v-if="!imageView" :icon="f7route.params.region" fill-color="var(--avn-theme-color)" @click="selectImage" />
|
<SvgIcon v-if="!imageView && !videoAvailable" :icon="f7route.params.region" fill-color="var(--avn-theme-color)" @click="selectImage" />
|
||||||
|
<div class="vid-container" v-if="videoAvailable" style="width: 100%; height: 100%">
|
||||||
|
<video id="vid-view" ref="vid_viewer" :srcObject="cameraStream" :autoPlay="true" style="width: 100%; height: 100%"></video>
|
||||||
|
<f7-button @click="captureVidFrame()" style="position: absolute; bottom: 32px; left: 50%; transform: translateX(-50%);" fill>Capture</f7-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="(resultData && resultData.detections) || detecting" class="chip-results" style="grid-area: result-view; flex: 0 0 auto; align-self: center;">
|
<div v-if="(resultData && resultData.detections) || detecting" class="chip-results" style="grid-area: result-view; flex: 0 0 auto; align-self: center;">
|
||||||
<f7-chip v-for="result in showResults.filter( r => { return r.aboveThreshold && r.isSearched && !r.isDeleted })"
|
<f7-chip v-for="result in showResults.filter( r => { return r.aboveThreshold && r.isSearched && !r.isDeleted })"
|
||||||
@@ -47,7 +51,7 @@
|
|||||||
<SvgIcon :icon="(uploadUid) ? 'cloud_done' : 'cloud_upload'"/>
|
<SvgIcon :icon="(uploadUid) ? 'cloud_done' : 'cloud_upload'"/>
|
||||||
</f7-button>
|
</f7-button>
|
||||||
</f7-segmented>
|
</f7-segmented>
|
||||||
<input type="file" ref="image_chooser" @change="getImage()" accept="image/*" capture="environment" style="display: none;"/>
|
<input type="file" ref="image_chooser" @change="getImage()" accept="image/*" style="display: none;"/>
|
||||||
</f7-block>
|
</f7-block>
|
||||||
|
|
||||||
<f7-panel :id="detectorName + '-settings'" right cover :backdrop="false" :container-el="`#${detectorName}-detect-page`">
|
<f7-panel :id="detectorName + '-settings'" right cover :backdrop="false" :container-el="`#${detectorName}-detect-page`">
|
||||||
@@ -86,7 +90,7 @@
|
|||||||
<f7-button style="height: auto; width: auto;" popover-close="#capture-popover" @click="selectImage('file')">
|
<f7-button style="height: auto; width: auto;" popover-close="#capture-popover" @click="selectImage('file')">
|
||||||
<SvgIcon icon="photo_library" />
|
<SvgIcon icon="photo_library" />
|
||||||
</f7-button>
|
</f7-button>
|
||||||
<f7-button style="height: auto; width: auto;" popover-close="#capture-popover" class="disabled" @click="videoStream">
|
<f7-button style="height: auto; width: auto;" popover-close="#capture-popover" @click="videoStream">
|
||||||
<SvgIcon icon="videocam"/>
|
<SvgIcon icon="videocam"/>
|
||||||
</f7-button>
|
</f7-button>
|
||||||
</f7-segmented>
|
</f7-segmented>
|
||||||
@@ -321,7 +325,10 @@
|
|||||||
uploadUid: null,
|
uploadUid: null,
|
||||||
uploadDirty: false,
|
uploadDirty: false,
|
||||||
modelLocation: '',
|
modelLocation: '',
|
||||||
modelLoading: false
|
modelLoading: false,
|
||||||
|
videoDeviceAvailable: false,
|
||||||
|
videoAvailable: false,
|
||||||
|
cameraStream: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
@@ -480,18 +487,36 @@
|
|||||||
this.detectorLabels.forEach( s => s.detect = false )
|
this.detectorLabels.forEach( s => s.detect = false )
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectImage (mode) {
|
async selectImage (mode) {
|
||||||
this.imageLoadMode = mode
|
this.imageLoadMode = mode
|
||||||
if (mode == "camera") {
|
|
||||||
this.$refs.image_chooser.setAttribute("capture","environment")
|
|
||||||
} else {
|
|
||||||
this.$refs.image_chooser.removeAttribute("capture")
|
|
||||||
}
|
|
||||||
if (this.isCordova && mode == "camera") {
|
if (this.isCordova && mode == "camera") {
|
||||||
navigator.camera.getPicture(this.getImage, this.onFail, { quality: 50, destinationType: Camera.DestinationType.DATA_URL, correctOrientation: true });
|
navigator.camera.getPicture(this.getImage, this.onFail, { quality: 50, destinationType: Camera.DestinationType.DATA_URL, correctOrientation: true });
|
||||||
} else {
|
return
|
||||||
var loadResult = this.$refs.image_chooser.click()
|
|
||||||
}
|
}
|
||||||
|
if (mode == "camera") {
|
||||||
|
const devicesList = await navigator.mediaDevices.enumerateDevices()
|
||||||
|
this.videoDeviceAvailable = devicesList.some( d => d.kind == "videoinput")
|
||||||
|
if (this.videoDeviceAvailable) {
|
||||||
|
navigator.mediaDevices.getUserMedia({video: true})
|
||||||
|
var vidConstraint = {
|
||||||
|
video: {
|
||||||
|
width: {
|
||||||
|
ideal: 1920
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
ideal: 1080
|
||||||
|
},
|
||||||
|
facingMode: 'environment'
|
||||||
|
},
|
||||||
|
audio: false
|
||||||
|
}
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia(vidConstraint);
|
||||||
|
this.videoAvailable = true
|
||||||
|
this.cameraStream = stream
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var loadResult = this.$refs.image_chooser.click()
|
||||||
},
|
},
|
||||||
onFail (message) {
|
onFail (message) {
|
||||||
alert(`Camera fail: ${message}`)
|
alert(`Camera fail: ${message}`)
|
||||||
@@ -539,7 +564,11 @@
|
|||||||
},
|
},
|
||||||
getImage (searchImage) {
|
getImage (searchImage) {
|
||||||
let loadImage = new Promise(resolve => {
|
let loadImage = new Promise(resolve => {
|
||||||
if (this.isCordova && this.imageLoadMode == "camera") {
|
if (this.videoAvailable) {
|
||||||
|
this.cameraStream.getTracks().forEach( t => t.stop())
|
||||||
|
this.videoAvailable = false
|
||||||
|
resolve(searchImage)
|
||||||
|
} else if (this.isCordova && this.imageLoadMode == "camera") {
|
||||||
resolve('data:image/jpg;base64,' + searchImage)
|
resolve('data:image/jpg;base64,' + searchImage)
|
||||||
} else {
|
} else {
|
||||||
const searchImage = this.$refs.image_chooser.files[0]
|
const searchImage = this.$refs.image_chooser.files[0]
|
||||||
@@ -573,9 +602,18 @@
|
|||||||
f7.dialog.alert(`Error loading image: ${e.message}`)
|
f7.dialog.alert(`Error loading image: ${e.message}`)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
videoStream() {
|
async videoStream() {
|
||||||
//TODO
|
//TODO
|
||||||
return null
|
},
|
||||||
|
captureVidFrame() {
|
||||||
|
const vidViewer = this.$refs.vid_viewer
|
||||||
|
vidViewer.pause()
|
||||||
|
let tempCVS = document.createElement('canvas')
|
||||||
|
tempCVS.height = vidViewer.videoHeight || parseInt(vidViewer.style.height)
|
||||||
|
tempCVS.width = vidViewer.videoWidth || parseInt(vidViewer.style.width)
|
||||||
|
const tempCtx = tempCVS.getContext('2d')
|
||||||
|
tempCtx.drawImage(vidViewer, 0, 0)
|
||||||
|
this.getImage(tempCVS.toDataURL())
|
||||||
},
|
},
|
||||||
async submitData () {
|
async submitData () {
|
||||||
var uploadData = this.showResults
|
var uploadData = this.showResults
|
||||||
|
|||||||
Reference in New Issue
Block a user