diff --git a/package-lock.json b/package-lock.json index 3d42581..b087f72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3227,11 +3227,6 @@ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", "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": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", @@ -6076,18 +6071,11 @@ "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": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true }, "p-locate": { "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": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", diff --git a/package.json b/package.json index 7b71bb2..9b4c5cf 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,10 @@ }, "dependencies": { "@babel/runtime": "^7.4.3", - "eventemitter3": "^3.1.0", "folktale": "^2.3.2", "generic-pool": "^3.6.1", "gif-writer": "^0.9.3", "objectFitPolyfill": "^2.1.1", - "p-event": "^4.0.0", "postcss-modules": "^1.4.1", "vue": "^2.6.10", "vue-router": "^3.0.2", diff --git a/src/services/capture.js b/src/services/capture.js index 83562de..139d8e2 100644 --- a/src/services/capture.js +++ b/src/services/capture.js @@ -1,5 +1,4 @@ -import EventEmitter from 'eventemitter3' -import pEvent from 'p-event' +import { task, of as taskOf } from 'folktale/concurrency/task' import { makeRectangle, crop } from '/services/rectangle.js' import { @@ -8,88 +7,92 @@ import { GIF_FRAME_RATE } from '/constants.js' -export function capture ({ mediaStream, facingMode }, duration) { - const emitter = new EventEmitter() +export function capture({ mediaStream, facingMode }, duration, progressCallback) { + const delayTime = 1000 / GIF_FRAME_RATE + const totalFrames = duration / 1000 * GIF_FRAME_RATE - Promise.resolve().then(async () => { - const delayTime = 1000 / GIF_FRAME_RATE - const totalFrames = duration / 1000 * GIF_FRAME_RATE - - // Well, this is a very low frame rate or very short duration clip - if (totalFrames < 1) { - 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) - } + if (totalFrames < 1) { + return taskOf({ + imageWidth: GIF_WIDTH, + imageHeight: GIF_HEIGHT, + imageDataList: [], + delayTime + }) + } + return task(resolver => { const video = document.createElement('video') video.setAttribute('playsinline', '') video.setAttribute('webkit-playsinline', '') video.srcObject = mediaStream video.play() - await pEvent(video, 'canplaythrough') - const soureRectangle = crop(makeRectangle(0, 0, video.videoWidth, video.videoHeight)) + video.addEventListener('canplaythrough', afterVideoAvailable, { + once: true + }) - step() + resolver.cleanup(() => { + video.removeEventListener('canplaythrough', afterVideoAvailable) + }) - function step () { - canvasContext.drawImage( - video, - soureRectangle.x, - soureRectangle.y, - soureRectangle.width, - soureRectangle.height, - destinationRectangle.x, - destinationRectangle.y, - destinationRectangle.width, - destinationRectangle.height - ) + function afterVideoAvailable() { + const canvas = document.createElement('canvas') + canvas.width = GIF_WIDTH + canvas.height = GIF_HEIGHT - const imageData = canvasContext.getImageData( - destinationRectangle.x, - destinationRectangle.y, - destinationRectangle.width, - destinationRectangle.height - ) + const canvasContext = canvas.getContext('2d') - 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) - } else { - emitter.emit('done', { - imageDataList, - imageWidth: GIF_WIDTH, - imageHeight: GIF_HEIGHT, - delayTime - }) + + captureFrame() + + const intervalId = setInterval(captureFrame, delayTime) + + resolver.cleanup(() => { + clearInterval(intervalId) + }) + + 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 -} +} \ No newline at end of file diff --git a/src/views/screens/capture.vue b/src/views/screens/capture.vue index 426015e..dc5e729 100644 --- a/src/views/screens/capture.vue +++ b/src/views/screens/capture.vue @@ -119,23 +119,28 @@ export default { }, runCapture () { this.capturing = true - const capturing = capture(this.camera, this.duration.selected * 1000) - capturing.once('error', error => { - console.error(error) + const captureExecution = capture( + this.camera, + this.duration.selected * 1000, + value => { + this.capturingProgress = value + } + ).run() + + const cleanup = () => { this.capturing = false this.capturingProgress = 0 - }) + } - capturing.on('progress', value => { - this.capturingProgress = value - }) - - capturing.once('done', captureData => { - this.capturing = false - this.capturingProgress = 0 - this.$store.commit('updateCapture', captureData) - this.$router.push({ name: 'preview' }) + captureExecution.listen({ + onCancelled: cleanup, + onRejected: cleanup, + onResolved: (captureData) => { + cleanup() + this.$store.commit('updateCapture', captureData) + this.$router.push({ name: 'preview' }) + } }) }, async ensureCamera () {