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

View file

@ -35,6 +35,7 @@ export default {
},
data: () => ({
encoding: false,
encodingExecution: null,
encodingProgress: 0,
objectUrl: null,
downloadReady: false
@ -54,6 +55,7 @@ export default {
},
methods: {
back () {
this.cancelEncode()
this.$router.push({ name: 'preview' })
},
backHome () {
@ -66,47 +68,59 @@ export default {
URL.revokeObjectURL(this.objectUrl)
this.$store.commit('updateGif', null)
},
startEncoding () {
startEncode () {
this.encoding = true
const encoding = encode(this.capture, { boomerangEffect: this.boomerang })
this.encodingProgress = 0
encoding.once('error', error => {
console.error(error)
this.encodingExecution = encode(
this.capture,
{ boomerangEffect: this.boomerang },
(value) => {
this.encodingProgress = value
}
).run()
const cleanup = () => {
this.encoding = false
this.encodingProgress = 0
})
this.encodingExecution = null
}
encoding.on('progress', value => {
this.encodingProgress = value
})
this.encodingExecution.listen({
onCancelled: cleanup,
onRejected: cleanup,
onResolved: (gif) => {
cleanup()
this.$store.commit('updateGif', gif)
this.fillGIF()
this.downloadReady = true
encoding.once('done', gif => {
this.encoding = false
this.encodingProgress = 0
this.$store.commit('updateGif', gif)
this.fillGIF()
this.downloadReady = true
if (document.hidden && ('Notification' in window) && Notification.permission === 'granted') {
const notification = new Notification('You can now download your souvenir', {
body: 'Thank you for your patience.',
icon: appLogo
})
if (document.hidden && ('Notification' in window) && Notification.permission === 'granted') {
const notification = new Notification('You can now download your souvenir', {
body: 'Thank you for your patience.',
icon: appLogo
})
notification.addEventListener('click', () => {
parent.focus()
}, {
once: true
})
notification.addEventListener('click', () => {
parent.focus()
}, {
once: true
})
}
}
})
},
cancelEncode () {
if (this.encodingExecution) {
this.encodingExecution.cancel()
}
}
},
created () {
if (!this.capture) {
this.$router.push({ name: 'home' })
} else if (!this.gif) {
this.startEncoding()
this.startEncode()
} else {
this.fillGIF()
}