2022-04-29 10:04:41 +05:30
|
|
|
module.exports = async options => {
|
2022-04-29 11:15:53 +05:30
|
|
|
let index = 0
|
|
|
|
|
let error = false
|
|
|
|
|
let seekResolve
|
|
|
|
|
let extractOffsets = false
|
|
|
|
|
|
2022-05-09 11:06:32 +05:30
|
|
|
// For minification
|
|
|
|
|
|
|
|
|
|
// Options
|
|
|
|
|
const _call = 'call'
|
|
|
|
|
const _count = 'count'
|
|
|
|
|
const _width = 'width'
|
|
|
|
|
const _height = 'height'
|
|
|
|
|
const _length = 'length'
|
|
|
|
|
const _format = 'format'
|
|
|
|
|
const _onLoad = 'onLoad'
|
|
|
|
|
const _offsets = 'offsets'
|
|
|
|
|
const _endTime = 'endTime'
|
|
|
|
|
const _duration = 'duration'
|
|
|
|
|
const _startTime = 'startTime'
|
|
|
|
|
const _onProgress = 'onProgress'
|
|
|
|
|
const _currentTime = 'currentTime'
|
|
|
|
|
const _createElement = 'createElement'
|
|
|
|
|
|
2022-04-29 11:15:53 +05:30
|
|
|
const frames = []
|
|
|
|
|
|
|
|
|
|
const isNumber = n => {
|
|
|
|
|
return +n + '' === n + ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isTimestamp = timestamp => {
|
2022-05-09 11:06:32 +05:30
|
|
|
return isNumber(timestamp) && +timestamp >= 0 && +timestamp <= video[_duration]
|
2022-04-29 11:15:53 +05:30
|
|
|
}
|
|
|
|
|
|
2022-04-30 12:38:59 +05:30
|
|
|
const isPrototypeOf = (constructor, value) => {
|
|
|
|
|
return (value instanceof constructor)
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-29 11:15:53 +05:30
|
|
|
const fallbackToDefault = (property, defaultValue) => {
|
2022-05-09 11:06:32 +05:30
|
|
|
options[property] = hasOwnProperty[_call](options, property) ? options[property] : defaultValue
|
2022-04-29 11:15:53 +05:30
|
|
|
}
|
|
|
|
|
|
2022-05-09 11:06:32 +05:30
|
|
|
const hasOwnProperty = {}.hasOwnProperty
|
2022-04-29 16:50:08 +05:30
|
|
|
|
2022-04-29 11:15:53 +05:30
|
|
|
// Buffer Video Element
|
2022-05-09 11:06:32 +05:30
|
|
|
const video = document[_createElement]('video')
|
2022-04-29 11:15:53 +05:30
|
|
|
video.src = options.url
|
|
|
|
|
video.crossOrigin = 'anonymous'
|
|
|
|
|
video.onseeked = async () => {
|
|
|
|
|
if (seekResolve) { seekResolve() }
|
|
|
|
|
}
|
|
|
|
|
video.onerror = () => {
|
|
|
|
|
error = true
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-09 11:06:32 +05:30
|
|
|
while ((video[_duration] === Infinity || isNaN(video[_duration])) && video.readyState < 2) {
|
2022-04-29 11:15:53 +05:30
|
|
|
await new Promise(resolve => setTimeout(resolve, 100))
|
2022-05-09 11:06:32 +05:30
|
|
|
video[_currentTime] = 10000000 * Math.random()
|
2022-04-30 12:38:59 +05:30
|
|
|
if (error) { break }
|
2022-04-29 11:15:53 +05:30
|
|
|
}
|
|
|
|
|
|
2022-05-06 16:54:26 +05:30
|
|
|
// Set currentTime to duration / 2
|
|
|
|
|
// (fix for correctly invoking onseeked event for currentTime = 0 if the first frame is at 0 sec.)
|
2022-05-09 11:06:32 +05:30
|
|
|
video[_currentTime] = video[_duration] / 2
|
2022-05-06 16:54:49 +05:30
|
|
|
await new Promise(resolve => { seekResolve = resolve })
|
2022-05-06 16:54:26 +05:30
|
|
|
|
2022-04-29 11:15:53 +05:30
|
|
|
// Set options to default values if not set
|
2022-05-09 11:06:32 +05:30
|
|
|
fallbackToDefault(_format, 'image/png')
|
|
|
|
|
fallbackToDefault(_offsets, [])
|
|
|
|
|
fallbackToDefault(_startTime, 0)
|
|
|
|
|
fallbackToDefault(_endTime, video[_duration])
|
|
|
|
|
fallbackToDefault(_count, 1)
|
|
|
|
|
fallbackToDefault(_onLoad, false)
|
|
|
|
|
fallbackToDefault(_onProgress, false)
|
2022-04-29 11:15:53 +05:30
|
|
|
|
|
|
|
|
// Filter out invalid offsets
|
2022-05-09 11:06:32 +05:30
|
|
|
if (!isPrototypeOf(Array, options[_offsets])) { options[_offsets] = [] } else {
|
|
|
|
|
options[_offsets] = options[_offsets].filter(offset => {
|
2022-04-29 11:15:53 +05:30
|
|
|
return isTimestamp(offset)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-09 11:06:32 +05:30
|
|
|
if (options[_offsets][_length] !== 0) { extractOffsets = true }
|
2022-04-29 11:15:53 +05:30
|
|
|
|
|
|
|
|
// Check if start and end times are valid
|
2022-05-09 11:06:32 +05:30
|
|
|
if (!isTimestamp(options[_startTime])) { options[_startTime] = 0 }
|
2022-04-29 11:15:53 +05:30
|
|
|
|
2022-05-09 11:06:32 +05:30
|
|
|
if (!isTimestamp(options[_endTime])) { options[_endTime] = video[_duration] }
|
2022-04-29 11:15:53 +05:30
|
|
|
|
2022-05-06 07:55:30 +05:30
|
|
|
// Float values
|
2022-05-09 11:06:32 +05:30
|
|
|
options[_startTime] = +options[_startTime]
|
|
|
|
|
options[_endTime] = +options[_endTime]
|
2022-05-06 07:55:30 +05:30
|
|
|
|
2022-05-09 11:06:32 +05:30
|
|
|
if (options[_startTime] >= options[_endTime]) {
|
|
|
|
|
options[_startTime] = options[_endTime]
|
|
|
|
|
options[_count] = 1
|
2022-04-29 11:15:53 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert count value to a positive integer (floor() or 0 if string)
|
2022-05-09 11:06:32 +05:30
|
|
|
options[_count] = Math.abs(~~options[_count])
|
|
|
|
|
if (options[_count] === 0) { options[_count] = 1 }
|
|
|
|
|
if (extractOffsets) { options[_count] = options[_offsets][_length] }
|
2022-04-29 11:15:53 +05:30
|
|
|
|
2022-04-30 12:38:59 +05:30
|
|
|
// Starting at startTime and ending at endTime - interval
|
2022-05-09 11:06:32 +05:30
|
|
|
const interval = (options[_endTime] - options[_startTime]) / options[_count]
|
2022-04-29 11:15:53 +05:30
|
|
|
|
|
|
|
|
// Set Width and Height
|
2022-05-09 11:06:32 +05:30
|
|
|
let isWidthSet = hasOwnProperty[_call](options, 'width')
|
|
|
|
|
let isHeightSet = hasOwnProperty[_call](options, 'height')
|
2022-04-29 11:15:53 +05:30
|
|
|
const videoDimensionRatio = video.videoWidth / video.videoHeight
|
|
|
|
|
|
|
|
|
|
// Reset Width and Height if not valid
|
2022-05-09 11:06:32 +05:30
|
|
|
if (isWidthSet && !isNumber(options[_width])) { isWidthSet = false }
|
2022-04-29 11:15:53 +05:30
|
|
|
|
2022-05-09 11:06:32 +05:30
|
|
|
if (isHeightSet && !isNumber(options[_height])) { isHeightSet = false }
|
2022-04-29 11:15:53 +05:30
|
|
|
|
|
|
|
|
if (!isWidthSet && !isHeightSet) {
|
|
|
|
|
// Both Width and Height not set
|
2022-05-09 11:06:32 +05:30
|
|
|
options[_width] = 128 // Default Value (randomly set)
|
|
|
|
|
options[_height] = options[_width] / videoDimensionRatio
|
2022-04-29 11:15:53 +05:30
|
|
|
} else if (isWidthSet && !isHeightSet) {
|
|
|
|
|
// Width set but Height not set
|
2022-05-09 11:06:32 +05:30
|
|
|
options[_height] = options[_width] / videoDimensionRatio
|
2022-04-29 11:15:53 +05:30
|
|
|
} else if (!isWidthSet && isHeightSet) {
|
|
|
|
|
// Height set but Width not set
|
2022-05-09 11:06:32 +05:30
|
|
|
options[_width] = options[_height] * videoDimensionRatio
|
2022-04-29 11:15:53 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Float values
|
2022-05-09 11:06:32 +05:30
|
|
|
options[_width] = +options[_width]
|
|
|
|
|
options[_height] = +options[_height]
|
2022-04-29 11:15:53 +05:30
|
|
|
|
2022-04-30 12:38:59 +05:30
|
|
|
// Reset onLoad and onProgress functions if not valid
|
2022-05-09 11:06:32 +05:30
|
|
|
if (!isPrototypeOf(Function, options[_onLoad])) { options[_onLoad] = false }
|
2022-04-30 12:38:59 +05:30
|
|
|
|
2022-05-09 11:06:32 +05:30
|
|
|
if (!isPrototypeOf(Function, options[_onProgress])) { options[_onProgress] = false }
|
2022-04-30 12:38:59 +05:30
|
|
|
|
2022-05-09 11:06:32 +05:30
|
|
|
if (options[_onLoad]) { options[_onLoad]() }
|
2022-04-30 12:38:59 +05:30
|
|
|
|
2022-04-29 11:15:53 +05:30
|
|
|
// Buffer Canvas Element
|
2022-05-09 11:06:32 +05:30
|
|
|
const canvas = document[_createElement]('canvas')
|
2022-04-29 11:15:53 +05:30
|
|
|
const context = canvas.getContext('2d')
|
2022-05-09 11:06:32 +05:30
|
|
|
canvas[_width] = options[_width]
|
|
|
|
|
canvas[_height] = options[_height]
|
2022-04-29 11:15:53 +05:30
|
|
|
|
2022-04-29 11:39:56 +05:30
|
|
|
const extract = async resolve => {
|
2022-05-09 11:06:32 +05:30
|
|
|
while (index < options[_count]) {
|
|
|
|
|
video[_currentTime] = extractOffsets ? options[_offsets][index] : options[_startTime] + index * interval
|
2022-04-29 11:15:53 +05:30
|
|
|
await new Promise(resolve => { seekResolve = resolve })
|
2022-05-09 11:06:32 +05:30
|
|
|
context.clearRect(0, 0, canvas[_width], canvas[_height])
|
|
|
|
|
context.drawImage(video, 0, 0, canvas[_width], canvas[_height])
|
2022-04-30 12:38:59 +05:30
|
|
|
frames.push({
|
2022-05-09 11:06:32 +05:30
|
|
|
offset: video[_currentTime],
|
|
|
|
|
image: canvas.toDataURL(options[_format])
|
2022-04-30 12:38:59 +05:30
|
|
|
})
|
2022-04-29 11:15:53 +05:30
|
|
|
index++
|
2022-05-09 11:06:32 +05:30
|
|
|
if (options[_onProgress]) { options[_onProgress](index, options[_count]) }
|
2022-04-29 11:15:53 +05:30
|
|
|
}
|
|
|
|
|
resolve(frames)
|
2022-04-29 11:39:56 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Promise(resolve => {
|
|
|
|
|
if (error) { resolve([]) }
|
2022-04-30 12:38:59 +05:30
|
|
|
|
2022-04-29 11:39:56 +05:30
|
|
|
extract(resolve)
|
2022-04-29 11:15:53 +05:30
|
|
|
})
|
|
|
|
|
}
|