refact(encode): rewrite with tasks

This commit is contained in:
wryk 2019-05-20 20:22:27 +02:00
parent b5e48f7566
commit 6e83c88c04
2 changed files with 115 additions and 77 deletions

View file

@ -1,76 +1,103 @@
import EventEmitter from 'eventemitter3' import { calcProgress } from '/services/util.js'
import genericPool from 'generic-pool' import { task, do as taskDo, waitAll } from 'folktale/concurrency/task'
import { promisesProgress, calcProgress } from '/services/util.js'
import { GIF_PALETTE_SIZE } from '/constants.js' import { GIF_PALETTE_SIZE } from '/constants.js'
export function encode ({ imageDataList, imageWidth, imageHeight, delayTime }, { boomerangEffect }) { export function encode ({ imageDataList, imageWidth, imageHeight, delayTime }, { boomerangEffect }, progressCallback) {
const emitter = new EventEmitter() return taskDo(function * () {
const indexedColorImageList = yield quantizeColorImageDataList(
imageDataList,
GIF_PALETTE_SIZE,
(value) => progressCallback(calcProgress(0, 0.9, value))
)
const quantizeColorWorkerPool = genericPool.createPool({ return writeBlob(
create: () => new Worker('/services/quantize-color.worker.js'), imageWidth,
destroy: worker => worker.terminate() imageHeight,
}, { indexedColorImageList,
min: 0, delayTime,
max: 2 boomerangEffect,
(value) => progressCallback(calcProgress(0.9, 1, value))
)
})
}
function quantizeColorImageDataList (imageDataList, paletteSize, progressCallback) {
let complete = 0
const tasks = imageDataList
.map(imageData => quantizeColorImageData(imageData, paletteSize))
.map(task => {
return task.map(x => {
progressCallback(++complete / imageDataList.length)
return x
})
}) })
const indexedColorImagesP = imageDataList return waitAll(tasks)
.map(async imageData => { }
const worker = await quantizeColorWorkerPool.acquire()
function quantizeColorImageData (imageData, paletteSize) {
return task((resolver) => {
const worker = new Worker('/services/quantize-color.worker.js')
resolver.cleanup(() => {
worker.terminate()
})
worker.onerror = resolver.reject
worker.onmessageerror = resolver.reject
const indexedColorImage = await new Promise((resolve, reject) => {
worker.onerror = reject
worker.onmessageerror = reject
worker.onmessage = event => { worker.onmessage = event => {
resolve(event.data) resolver.resolve(event.data)
} }
worker.postMessage({ worker.postMessage({
imageData, imageData,
paletteSize: GIF_PALETTE_SIZE paletteSize
}) })
}) })
}
await quantizeColorWorkerPool.release(worker) function writeBlob (
return indexedColorImage imageWidth,
imageHeight,
indexedColorImages,
delayTime,
boomerangEffect,
onProgress
) {
return task((resolver) => {
const worker = new Worker('/services/write-gif.worker.js')
resolver.cleanup(() => {
worker.terminate()
}) })
const progressPromises = promisesProgress(indexedColorImagesP, function (value) { worker.onerror = resolver.reject
emitter.emit('progress', calcProgress(0, 0.9, value)) worker.onmessageerror = resolver.reject
})
Promise.all(progressPromises).then(indexedColorImages => { worker.onmessage = event => {
const writeWorker = new Worker('/services/write-gif.worker.js')
writeWorker.onerror = error => emitter.emit('error', error)
writeWorker.onmessageerror = error => emitter.emit('error', error)
writeWorker.onmessage = event => {
const { type, payload } = event.data const { type, payload } = event.data
switch (type) { switch (type) {
default:
emitter.emit('error', new Error(`Unexpected worker message with type ${type}`))
break
case 'progress': case 'progress':
emitter.emit('progress', calcProgress(0.9, 1, payload.value)) onProgress(payload.value)
break return
case 'done': case 'done':
const byteArray = new Uint8Array(payload.buffer) resolver.resolve({
const blob = new Blob([byteArray], { type: 'image/gif' }) blob: new Blob([new Uint8Array(payload.buffer)], { type: 'image/gif' }),
emitter.emit('done', {
blob,
createdAt: new Date() createdAt: new Date()
}) })
break return
default:
resolver.reject(new Error(`Unexpected worker message with type ${type}`))
} }
} }
writeWorker.postMessage({ worker.postMessage({
imageWidth, imageWidth,
imageHeight, imageHeight,
indexedColorImages, indexedColorImages,
@ -78,7 +105,4 @@ export function encode ({ imageDataList, imageWidth, imageHeight, delayTime }, {
boomerangEffect boomerangEffect
}) })
}) })
.catch(error => emitter.emit('error', error))
return emitter
} }

View file

@ -35,6 +35,7 @@ export default {
}, },
data: () => ({ data: () => ({
encoding: false, encoding: false,
encodingExecution: null,
encodingProgress: 0, encodingProgress: 0,
objectUrl: null, objectUrl: null,
downloadReady: false downloadReady: false
@ -54,6 +55,7 @@ export default {
}, },
methods: { methods: {
back () { back () {
this.cancelEncode()
this.$router.push({ name: 'preview' }) this.$router.push({ name: 'preview' })
}, },
backHome () { backHome () {
@ -66,23 +68,29 @@ export default {
URL.revokeObjectURL(this.objectUrl) URL.revokeObjectURL(this.objectUrl)
this.$store.commit('updateGif', null) this.$store.commit('updateGif', null)
}, },
startEncoding () { startEncode () {
this.encoding = true this.encoding = true
const encoding = encode(this.capture, { boomerangEffect: this.boomerang })
encoding.once('error', error => {
console.error(error)
this.encoding = false
this.encodingProgress = 0 this.encodingProgress = 0
})
encoding.on('progress', value => { this.encodingExecution = encode(
this.capture,
{ boomerangEffect: this.boomerang },
(value) => {
this.encodingProgress = value this.encodingProgress = value
}) }
).run()
encoding.once('done', gif => { const cleanup = () => {
this.encoding = false this.encoding = false
this.encodingProgress = 0 this.encodingProgress = 0
this.encodingExecution = null
}
this.encodingExecution.listen({
onCancelled: cleanup,
onRejected: cleanup,
onResolved: (gif) => {
cleanup()
this.$store.commit('updateGif', gif) this.$store.commit('updateGif', gif)
this.fillGIF() this.fillGIF()
this.downloadReady = true this.downloadReady = true
@ -99,14 +107,20 @@ export default {
once: true once: true
}) })
} }
}
}) })
},
cancelEncode () {
if (this.encodingExecution) {
this.encodingExecution.cancel()
}
} }
}, },
created () { created () {
if (!this.capture) { if (!this.capture) {
this.$router.push({ name: 'home' }) this.$router.push({ name: 'home' })
} else if (!this.gif) { } else if (!this.gif) {
this.startEncoding() this.startEncode()
} else { } else {
this.fillGIF() this.fillGIF()
} }