refact(capture): rewrite with task

This commit is contained in:
wryk 2019-05-21 15:51:22 +02:00
parent a2017f3f47
commit 1711911d62
4 changed files with 93 additions and 107 deletions

24
package-lock.json generated
View file

@ -3227,11 +3227,6 @@
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
"dev": true "dev": true
}, },
"eventemitter3": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
"integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA=="
},
"events": { "events": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
@ -6076,18 +6071,11 @@
"os-tmpdir": "^1.0.0" "os-tmpdir": "^1.0.0"
} }
}, },
"p-event": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-event/-/p-event-4.1.0.tgz",
"integrity": "sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA==",
"requires": {
"p-timeout": "^2.0.1"
}
},
"p-finally": { "p-finally": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
"dev": true
}, },
"p-locate": { "p-locate": {
"version": "2.0.0", "version": "2.0.0",
@ -6115,14 +6103,6 @@
} }
} }
}, },
"p-timeout": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz",
"integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==",
"requires": {
"p-finally": "^1.0.0"
}
},
"package-json": { "package-json": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz",

View file

@ -12,12 +12,10 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.4.3", "@babel/runtime": "^7.4.3",
"eventemitter3": "^3.1.0",
"folktale": "^2.3.2", "folktale": "^2.3.2",
"generic-pool": "^3.6.1", "generic-pool": "^3.6.1",
"gif-writer": "^0.9.3", "gif-writer": "^0.9.3",
"objectFitPolyfill": "^2.1.1", "objectFitPolyfill": "^2.1.1",
"p-event": "^4.0.0",
"postcss-modules": "^1.4.1", "postcss-modules": "^1.4.1",
"vue": "^2.6.10", "vue": "^2.6.10",
"vue-router": "^3.0.2", "vue-router": "^3.0.2",

View file

@ -1,5 +1,4 @@
import EventEmitter from 'eventemitter3' import { task, of as taskOf } from 'folktale/concurrency/task'
import pEvent from 'p-event'
import { makeRectangle, crop } from '/services/rectangle.js' import { makeRectangle, crop } from '/services/rectangle.js'
import { import {
@ -8,88 +7,92 @@ import {
GIF_FRAME_RATE GIF_FRAME_RATE
} from '/constants.js' } from '/constants.js'
export function capture ({ mediaStream, facingMode }, duration) { export function capture({ mediaStream, facingMode }, duration, progressCallback) {
const emitter = new EventEmitter() const delayTime = 1000 / GIF_FRAME_RATE
const totalFrames = duration / 1000 * GIF_FRAME_RATE
Promise.resolve().then(async () => { if (totalFrames < 1) {
const delayTime = 1000 / GIF_FRAME_RATE return taskOf({
const totalFrames = duration / 1000 * GIF_FRAME_RATE imageWidth: GIF_WIDTH,
imageHeight: GIF_HEIGHT,
// Well, this is a very low frame rate or very short duration clip imageDataList: [],
if (totalFrames < 1) { delayTime
emitter.emit('done', { })
imageWidth: GIF_WIDTH, }
imageHeight: GIF_HEIGHT,
imageDataList: [],
delayTime
})
return
}
const imageDataList = []
const canvas = document.createElement('canvas')
canvas.width = GIF_WIDTH
canvas.height = GIF_HEIGHT
const destinationRectangle = makeRectangle(0, 0, canvas.width, canvas.height)
const canvasContext = canvas.getContext('2d')
if (facingMode === 'user' || facingMode === 'unknow') {
canvasContext.translate(destinationRectangle.width, 0)
canvasContext.scale(-1, 1)
}
return task(resolver => {
const video = document.createElement('video') const video = document.createElement('video')
video.setAttribute('playsinline', '') video.setAttribute('playsinline', '')
video.setAttribute('webkit-playsinline', '') video.setAttribute('webkit-playsinline', '')
video.srcObject = mediaStream video.srcObject = mediaStream
video.play() video.play()
await pEvent(video, 'canplaythrough') video.addEventListener('canplaythrough', afterVideoAvailable, {
const soureRectangle = crop(makeRectangle(0, 0, video.videoWidth, video.videoHeight)) once: true
})
step() resolver.cleanup(() => {
video.removeEventListener('canplaythrough', afterVideoAvailable)
})
function step () { function afterVideoAvailable() {
canvasContext.drawImage( const canvas = document.createElement('canvas')
video, canvas.width = GIF_WIDTH
soureRectangle.x, canvas.height = GIF_HEIGHT
soureRectangle.y,
soureRectangle.width,
soureRectangle.height,
destinationRectangle.x,
destinationRectangle.y,
destinationRectangle.width,
destinationRectangle.height
)
const imageData = canvasContext.getImageData( const canvasContext = canvas.getContext('2d')
destinationRectangle.x,
destinationRectangle.y,
destinationRectangle.width,
destinationRectangle.height
)
imageDataList.push(imageData) if (facingMode === 'user' || facingMode === 'unknow') {
canvasContext.translate(canvas.width, 0)
canvasContext.scale(-1, 1)
}
emitter.emit('progress', imageDataList.length / totalFrames) const imageDataList = []
const destinationRectangle = makeRectangle(0, 0, canvas.width, canvas.height)
const soureRectangle = crop(makeRectangle(0, 0, video.videoWidth, video.videoHeight))
if (imageDataList.length < totalFrames) {
setTimeout(step, delayTime) captureFrame()
} else {
emitter.emit('done', { const intervalId = setInterval(captureFrame, delayTime)
imageDataList,
imageWidth: GIF_WIDTH, resolver.cleanup(() => {
imageHeight: GIF_HEIGHT, clearInterval(intervalId)
delayTime })
})
function captureFrame () {
canvasContext.drawImage(
video,
soureRectangle.x,
soureRectangle.y,
soureRectangle.width,
soureRectangle.height,
destinationRectangle.x,
destinationRectangle.y,
destinationRectangle.width,
destinationRectangle.height
)
const imageData = canvasContext.getImageData(
destinationRectangle.x,
destinationRectangle.y,
destinationRectangle.width,
destinationRectangle.height
)
imageDataList.push(imageData)
progressCallback(imageDataList.length / totalFrames)
if (imageDataList.length >= totalFrames) {
resolver.resolve({
imageDataList,
imageWidth: GIF_WIDTH,
imageHeight: GIF_HEIGHT,
delayTime
})
}
} }
} }
}) })
.catch(error => emitter.emit('error', error))
return emitter
} }

View file

@ -119,23 +119,28 @@ export default {
}, },
runCapture () { runCapture () {
this.capturing = true this.capturing = true
const capturing = capture(this.camera, this.duration.selected * 1000)
capturing.once('error', error => { const captureExecution = capture(
console.error(error) this.camera,
this.duration.selected * 1000,
value => {
this.capturingProgress = value
}
).run()
const cleanup = () => {
this.capturing = false this.capturing = false
this.capturingProgress = 0 this.capturingProgress = 0
}) }
capturing.on('progress', value => { captureExecution.listen({
this.capturingProgress = value onCancelled: cleanup,
}) onRejected: cleanup,
onResolved: (captureData) => {
capturing.once('done', captureData => { cleanup()
this.capturing = false this.$store.commit('updateCapture', captureData)
this.capturingProgress = 0 this.$router.push({ name: 'preview' })
this.$store.commit('updateCapture', captureData) }
this.$router.push({ name: 'preview' })
}) })
}, },
async ensureCamera () { async ensureCamera () {