mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-25 08:01:20 -08:00
refactor(thumbnail): [PKG-TOOL-THUMB-04] type runtime modules and retain emitter attribution
This commit is contained in:
1 parent
efd9f42fb9
commit
15485b6420
37 files changed
+8446
-152
No files matched your search
@@ -1,44 +0,0 @@
|
||||
export default class Emitter {
|
||||
on(name, fn, ctx) {
|
||||
const e = this.e || (this.e = {});
|
||||
(e[name] || (e[name] = [])).push({ fn, ctx })
|
||||
return this
|
||||
}
|
||||
|
||||
once(name, fn, ctx) {
|
||||
const self = this
|
||||
function listener(...args) {
|
||||
self.off(name, listener)
|
||||
fn.apply(ctx, args)
|
||||
}
|
||||
listener._ = fn
|
||||
return this.on(name, listener, ctx)
|
||||
}
|
||||
|
||||
emit(name, ...data) {
|
||||
const evtArr = ((this.e || (this.e = {}))[name] || []).slice()
|
||||
for (let i = 0; i < evtArr.length; i += 1) {
|
||||
evtArr[i].fn.apply(evtArr[i].ctx, data)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
off(name, callback) {
|
||||
const e = this.e || (this.e = {})
|
||||
const evts = e[name]
|
||||
const liveEvents = []
|
||||
if (evts && callback) {
|
||||
for (let i = 0, len = evts.length; i < len; i += 1) {
|
||||
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
|
||||
liveEvents.push(evts[i])
|
||||
}
|
||||
}
|
||||
if (liveEvents.length) {
|
||||
e[name] = liveEvents
|
||||
}
|
||||
else {
|
||||
delete e[name]
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Locally adapted from tiny-emitter; upstream reference 2.1.0. See THIRD_PARTY_NOTICES.
|
||||
type EventMap<Events> = { [Name in keyof Events]: readonly unknown[] }
|
||||
type Listener<Args extends unknown[]> = ((...args: Args) => unknown) & { _?: (...args: Args) => unknown }
|
||||
interface Registration<Args extends unknown[]> {
|
||||
fn: Listener<Args>
|
||||
ctx: unknown
|
||||
}
|
||||
type Registry<Events extends EventMap<Events>> = { [Name in keyof Events]?: Registration<[...Events[Name]]>[] }
|
||||
|
||||
export default class Emitter<Events extends EventMap<Events> = Record<PropertyKey, unknown[]>> {
|
||||
declare e?: Registry<Events>
|
||||
|
||||
on<Name extends keyof Events, Context>(name: Name, fn: (this: Context, ...args: [...Events[Name]]) => unknown, ctx?: Context): this {
|
||||
const e: Registry<Events> = this.e || (this.e = {});
|
||||
(e[name] || (e[name] = [])).push({ fn, ctx })
|
||||
return this
|
||||
}
|
||||
|
||||
once<Name extends keyof Events, Context>(name: Name, fn: (this: Context, ...args: [...Events[Name]]) => unknown, ctx?: Context): this {
|
||||
// eslint-disable-next-line ts/no-this-alias -- Preserve the owning emitter independently of callback ctx.
|
||||
const self = this
|
||||
const callback: Listener<[...Events[Name]]> = fn
|
||||
function listener(...args: [...Events[Name]]) {
|
||||
self.off(name, listener)
|
||||
callback.apply(ctx, args)
|
||||
}
|
||||
listener._ = fn
|
||||
return this.on(name, listener, ctx)
|
||||
}
|
||||
|
||||
emit<Name extends keyof Events>(name: Name, ...data: [...Events[Name]]): this {
|
||||
const e: Registry<Events> = this.e || (this.e = {})
|
||||
const evtArr = (e[name] || []).slice()
|
||||
for (let i = 0; i < evtArr.length; i += 1) {
|
||||
evtArr[i]!.fn.apply(evtArr[i]!.ctx, data)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
off<Name extends keyof Events>(name: Name, callback?: Listener<[...Events[Name]]>): this {
|
||||
const e: Registry<Events> = this.e || (this.e = {})
|
||||
const evts = e[name]
|
||||
const liveEvents: Registration<[...Events[Name]]>[] = []
|
||||
if (evts && callback) {
|
||||
for (let i = 0, len = evts.length; i < len; i += 1) {
|
||||
if (evts[i]!.fn !== callback && evts[i]!.fn._ !== callback)
|
||||
liveEvents.push(evts[i]!)
|
||||
}
|
||||
}
|
||||
if (liveEvents.length) {
|
||||
e[name] = liveEvents
|
||||
}
|
||||
else {
|
||||
delete e[name]
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
+19
-17
@@ -1,12 +1,14 @@
|
||||
import type ArtplayerToolThumbnail from './index'
|
||||
import type { Cleanup, ExtractionJob, LifecycleState } from './types'
|
||||
import { cancellation, cleanupAll, stateFor } from './lifecycle'
|
||||
import { replaceThumbnail } from './source'
|
||||
import { clamp } from './utils'
|
||||
|
||||
function prepare(tool, job) {
|
||||
function prepare(tool: ArtplayerToolThumbnail, job: ExtractionJob) {
|
||||
const { video } = job
|
||||
const { width, number, begin, end } = tool.option
|
||||
const height = (video.videoHeight / video.videoWidth) * width
|
||||
const guard = (condition, message) => {
|
||||
const guard = (condition: unknown, message: string) => {
|
||||
try {
|
||||
tool.errorHandle(condition, message)
|
||||
}
|
||||
@@ -28,23 +30,23 @@ function prepare(tool, job) {
|
||||
guard(tool.density <= 1, `The preview density cannot be greater than 1, but got ${tool.density}`)
|
||||
const points = tool.creatScreenshotDate()
|
||||
const canvas = tool.creatCanvas()
|
||||
const context = canvas.getContext('2d')
|
||||
const context = canvas.getContext('2d')!
|
||||
tool.emit('canvas', canvas)
|
||||
return { width, height, number, points, canvas, context }
|
||||
}
|
||||
|
||||
function createJob(tool, state) {
|
||||
function createJob(tool: ArtplayerToolThumbnail, state: LifecycleState) {
|
||||
let epoch = state.epoch
|
||||
const video = tool.video
|
||||
let resolve
|
||||
let reject
|
||||
let timer
|
||||
let resolve!: () => void
|
||||
let reject!: (error: unknown) => void
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
let settled = false
|
||||
let running = false
|
||||
let job
|
||||
let job: ExtractionJob
|
||||
let frameCleanup = () => {}
|
||||
const listeners = []
|
||||
const promise = new Promise((yes, no) => {
|
||||
const listeners: Cleanup[] = []
|
||||
const promise = new Promise<void>((yes, no) => {
|
||||
resolve = yes
|
||||
reject = no
|
||||
})
|
||||
@@ -62,7 +64,7 @@ function createJob(tool, state) {
|
||||
cleanupAll([() => clearTimeout(timer), frameCleanup, ...listeners.splice(0)])
|
||||
return true
|
||||
}
|
||||
function fail(error, report = true) {
|
||||
function fail(error: unknown, report = true) {
|
||||
if (settled)
|
||||
return
|
||||
try {
|
||||
@@ -73,7 +75,7 @@ function createJob(tool, state) {
|
||||
}
|
||||
if (report) {
|
||||
try {
|
||||
tool.emit('error', error?.message)
|
||||
tool.emit('error', (error as { message?: string } | null | undefined)?.message)
|
||||
}
|
||||
catch (listenerError) {
|
||||
error = listenerError
|
||||
@@ -92,7 +94,7 @@ function createJob(tool, state) {
|
||||
catch (error) {
|
||||
let failure = error
|
||||
try {
|
||||
tool.emit('error', error?.message)
|
||||
tool.emit('error', (error as { message?: string } | null | undefined)?.message)
|
||||
}
|
||||
catch (listenerError) {
|
||||
failure = listenerError
|
||||
@@ -100,7 +102,7 @@ function createJob(tool, state) {
|
||||
reject(failure)
|
||||
}
|
||||
}
|
||||
function extract(plan) {
|
||||
function extract(plan: ReturnType<typeof prepare>) {
|
||||
if (!live())
|
||||
return
|
||||
running = true
|
||||
@@ -113,7 +115,7 @@ function createJob(tool, state) {
|
||||
complete()
|
||||
return
|
||||
}
|
||||
const point = plan.points[index]
|
||||
const point = plan.points[index]!
|
||||
let drawing = false
|
||||
let encoded = false
|
||||
const previous = video.oncanplay
|
||||
@@ -211,7 +213,7 @@ function createJob(tool, state) {
|
||||
}],
|
||||
['loadedmetadata', () => ready()],
|
||||
['durationchange', () => ready()],
|
||||
]) {
|
||||
] as const) {
|
||||
listeners.push(() => video.removeEventListener(name, callback))
|
||||
video.addEventListener(name, callback)
|
||||
if (!live()) {
|
||||
@@ -231,7 +233,7 @@ function createJob(tool, state) {
|
||||
return promise
|
||||
}
|
||||
|
||||
export function startExtraction(tool) {
|
||||
export function startExtraction(tool: ArtplayerToolThumbnail): Promise<void> {
|
||||
const state = stateFor(tool)
|
||||
if (state.closed) {
|
||||
const promise = Promise.reject(cancellation('destroyed'))
|
||||
+24
-13
@@ -1,3 +1,4 @@
|
||||
import type { InputOptions, RuntimeOptions, ThumbnailEvents } from './types'
|
||||
import Emitter from './emitter'
|
||||
import { startExtraction } from './extraction'
|
||||
import { connectInput, releaseInput, setupInput } from './input'
|
||||
@@ -5,11 +6,21 @@ import { cleanupAll, closeState, releaseMedia, stateFor } from './lifecycle'
|
||||
import { createSheet, downloadSheet, screenshotPoints } from './sheet'
|
||||
import { loadSource } from './source'
|
||||
|
||||
export default class ArtplayerToolThumbnail extends Emitter {
|
||||
constructor(option = {}) {
|
||||
export default class ArtplayerToolThumbnail extends Emitter<ThumbnailEvents> {
|
||||
declare processing: boolean
|
||||
declare option: RuntimeOptions
|
||||
declare video: HTMLVideoElement
|
||||
declare duration: number
|
||||
declare density: number | undefined
|
||||
declare file: File | undefined
|
||||
declare videoUrl: string | undefined
|
||||
declare thumbnailUrl: string | undefined
|
||||
|
||||
constructor(option: InputOptions = {}) {
|
||||
super()
|
||||
this.processing = false
|
||||
this.option = {}
|
||||
// setup validates the temporary empty options before construction can succeed.
|
||||
this.option = {} as RuntimeOptions
|
||||
try {
|
||||
this.setup(Object.assign({}, ArtplayerToolThumbnail.DEFAULTS, option))
|
||||
this.video = ArtplayerToolThumbnail.creatVideo()
|
||||
@@ -44,19 +55,19 @@ export default class ArtplayerToolThumbnail extends Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
static ondragover(event) {
|
||||
static ondragover(event: DragEvent) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
ondrop(event) {
|
||||
ondrop(event: DragEvent) {
|
||||
if (stateFor(this).closed)
|
||||
return
|
||||
event.preventDefault()
|
||||
const file = event.dataTransfer.files[0]
|
||||
const file = event.dataTransfer!.files[0]
|
||||
this.loadVideo(file)
|
||||
}
|
||||
|
||||
setup(option = {}) {
|
||||
setup(option: InputOptions = {}) {
|
||||
if (!stateFor(this).closed)
|
||||
setupInput(this, option)
|
||||
return this
|
||||
@@ -80,15 +91,15 @@ export default class ArtplayerToolThumbnail extends Emitter {
|
||||
return video
|
||||
}
|
||||
|
||||
inputChange(event) {
|
||||
inputChange(event: Event) {
|
||||
if (stateFor(this).closed)
|
||||
return
|
||||
const file = this.option.fileInput.files[0]
|
||||
const file = this.option.fileInput.files![0]
|
||||
this.loadVideo(file)
|
||||
event.target.value = ''
|
||||
;(event.target as HTMLInputElement).value = ''
|
||||
}
|
||||
|
||||
loadVideo(file) {
|
||||
loadVideo(file?: File | null) {
|
||||
loadSource(this, file)
|
||||
}
|
||||
|
||||
@@ -112,12 +123,12 @@ export default class ArtplayerToolThumbnail extends Emitter {
|
||||
'Download does not seem to be ready, please create preview first',
|
||||
)
|
||||
this.errorHandle(!this.processing, 'There is currently a task in progress, please wait a moment...')
|
||||
const name = downloadSheet(this.file, this.thumbnailUrl)
|
||||
const name = downloadSheet(this.file!, this.thumbnailUrl!)
|
||||
this.emit('download', name)
|
||||
return this
|
||||
}
|
||||
|
||||
errorHandle(condition, msg) {
|
||||
errorHandle(condition: unknown, msg: string) {
|
||||
if (!condition) {
|
||||
this.emit('error', msg)
|
||||
throw new Error(msg)
|
||||
+25
-12
@@ -1,10 +1,21 @@
|
||||
import type ArtplayerToolThumbnail from './index'
|
||||
import type { InputOptions } from './types'
|
||||
import { cancellation, stateFor } from './lifecycle'
|
||||
import { clamp } from './utils'
|
||||
|
||||
const inputs = new WeakMap()
|
||||
type Registration = readonly [name: string, callback: EventListener]
|
||||
interface InputRecord {
|
||||
input: HTMLInputElement
|
||||
wrapper: HTMLElement | null
|
||||
position: string
|
||||
listeners: Registration[]
|
||||
callbacks: Registration[] | null
|
||||
}
|
||||
|
||||
function release(record) {
|
||||
let failure
|
||||
const inputs = new WeakMap<ArtplayerToolThumbnail, InputRecord>()
|
||||
|
||||
function release(record: InputRecord) {
|
||||
let failure: unknown
|
||||
for (const [name, callback] of record.listeners.splice(0)) {
|
||||
try {
|
||||
record.input.removeEventListener(name, callback)
|
||||
@@ -28,7 +39,7 @@ function release(record) {
|
||||
throw failure
|
||||
}
|
||||
|
||||
function connect(record, callbacks) {
|
||||
function connect(record: InputRecord, callbacks: Registration[]) {
|
||||
try {
|
||||
for (const [name, callback] of callbacks) {
|
||||
record.listeners.push([name, callback])
|
||||
@@ -44,12 +55,12 @@ function connect(record, callbacks) {
|
||||
}
|
||||
}
|
||||
|
||||
export function setupInput(tool, patch) {
|
||||
export function setupInput(tool: ArtplayerToolThumbnail, patch: InputOptions) {
|
||||
const state = stateFor(tool)
|
||||
const epoch = ++state.inputEpoch
|
||||
const current = () => !state.closed && state.inputEpoch === epoch
|
||||
const option = Object.assign({}, tool.option, patch)
|
||||
const target = option.fileInput
|
||||
const target = option.fileInput!
|
||||
tool.errorHandle(target instanceof Element, 'The \'fileInput\' is not a Element')
|
||||
for (const name of ['number', 'width', 'column', 'begin', 'end'])
|
||||
tool.errorHandle(typeof option[name] === 'number', `The '${name}' is not a number`)
|
||||
@@ -65,9 +76,10 @@ export function setupInput(tool, patch) {
|
||||
tool.option = option
|
||||
return option
|
||||
}
|
||||
const record = { input: target, wrapper: null, position: '', listeners: [], callbacks: previous?.callbacks || null }
|
||||
// A wrapper is replaced by its owned file input before the record is committed.
|
||||
const record: InputRecord = { input: target as HTMLInputElement, wrapper: null, position: '', listeners: [], callbacks: previous?.callbacks || null }
|
||||
try {
|
||||
if (!(target.tagName === 'INPUT' && target.type === 'file')) {
|
||||
if (!(target.tagName === 'INPUT' && (target as HTMLInputElement).type === 'file')) {
|
||||
record.wrapper = target
|
||||
record.position = target.style.position
|
||||
record.input = document.createElement('input')
|
||||
@@ -98,13 +110,14 @@ export function setupInput(tool, patch) {
|
||||
return option
|
||||
}
|
||||
|
||||
export function connectInput(tool, dragover) {
|
||||
const record = inputs.get(tool)
|
||||
record.callbacks = [['change', tool.inputChange], ['dragover', dragover], ['drop', tool.ondrop]]
|
||||
export function connectInput(tool: ArtplayerToolThumbnail, dragover: (event: DragEvent) => void) {
|
||||
const record = inputs.get(tool)!
|
||||
// These callbacks are registered only for their matching DOM event names.
|
||||
record.callbacks = [['change', tool.inputChange], ['dragover', dragover as EventListener], ['drop', tool.ondrop as EventListener]]
|
||||
connect(record, record.callbacks)
|
||||
}
|
||||
|
||||
export function releaseInput(tool) {
|
||||
export function releaseInput(tool: ArtplayerToolThumbnail) {
|
||||
const record = inputs.get(tool)
|
||||
if (!record)
|
||||
return
|
||||
+13
-10
@@ -1,6 +1,9 @@
|
||||
const states = new WeakMap()
|
||||
import type ArtplayerToolThumbnail from './index'
|
||||
import type { Cleanup, LifecycleState } from './types'
|
||||
|
||||
export function stateFor(tool) {
|
||||
const states = new WeakMap<ArtplayerToolThumbnail, LifecycleState>()
|
||||
|
||||
export function stateFor(tool: ArtplayerToolThumbnail): LifecycleState {
|
||||
if (!states.has(tool)) {
|
||||
states.set(tool, {
|
||||
closed: false,
|
||||
@@ -16,24 +19,24 @@ export function stateFor(tool) {
|
||||
loading: false,
|
||||
})
|
||||
}
|
||||
return states.get(tool)
|
||||
return states.get(tool)!
|
||||
}
|
||||
|
||||
export function cancellation(reason) {
|
||||
export function cancellation(reason: string) {
|
||||
const error = new Error(`Thumbnail task cancelled: ${reason}`)
|
||||
error.name = 'AbortError'
|
||||
return error
|
||||
}
|
||||
|
||||
export function revoke(urls, url) {
|
||||
export function revoke(urls: Set<string>, url: string | undefined) {
|
||||
if (!url)
|
||||
return
|
||||
urls.delete(url)
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
export function cleanupAll(callbacks) {
|
||||
let failure
|
||||
export function cleanupAll(callbacks: Cleanup[]) {
|
||||
let failure: unknown
|
||||
for (const callback of callbacks) {
|
||||
try {
|
||||
callback()
|
||||
@@ -46,7 +49,7 @@ export function cleanupAll(callbacks) {
|
||||
throw failure
|
||||
}
|
||||
|
||||
export function closeState(tool, reason = 'destroyed') {
|
||||
export function closeState(tool: ArtplayerToolThumbnail, reason = 'destroyed') {
|
||||
const state = stateFor(tool)
|
||||
if (state.closed)
|
||||
return false
|
||||
@@ -56,11 +59,11 @@ export function closeState(tool, reason = 'destroyed') {
|
||||
return true
|
||||
}
|
||||
|
||||
export function releaseMedia(tool) {
|
||||
export function releaseMedia(tool: ArtplayerToolThumbnail) {
|
||||
const state = stateFor(tool)
|
||||
const sources = new Set([...state.sourceUrls, tool.videoUrl])
|
||||
const thumbnails = new Set([...state.thumbnailUrls, tool.thumbnailUrl])
|
||||
const media = []
|
||||
const media: Cleanup[] = []
|
||||
for (const video of new Set([state.video, tool.video])) {
|
||||
if (video) {
|
||||
media.push(
|
||||
+6
-5
@@ -1,11 +1,12 @@
|
||||
import type { ScreenshotPoint, SheetOptions } from './types'
|
||||
import { getFileName } from './utils'
|
||||
|
||||
export function screenshotPoints(option, duration) {
|
||||
export function screenshotPoints(option: SheetOptions, duration: number): ScreenshotPoint[] {
|
||||
const { number, width, height, column, begin } = option
|
||||
const timeGap = duration / number
|
||||
const timePoints = [begin + timeGap]
|
||||
while (timePoints.length < number) {
|
||||
const last = timePoints[timePoints.length - 1]
|
||||
const last = timePoints[timePoints.length - 1]!
|
||||
timePoints.push(last + timeGap)
|
||||
}
|
||||
return timePoints.map((item, index) => ({
|
||||
@@ -15,10 +16,10 @@ export function screenshotPoints(option, duration) {
|
||||
}))
|
||||
}
|
||||
|
||||
export function createSheet(option) {
|
||||
export function createSheet(option: SheetOptions) {
|
||||
const { number, width, height, column } = option
|
||||
const canvas = document.createElement('canvas')
|
||||
const context = canvas.getContext('2d')
|
||||
const context = canvas.getContext('2d')!
|
||||
canvas.width = width * column
|
||||
canvas.height = Math.ceil(number / column) * height + 30
|
||||
context.fillStyle = 'black'
|
||||
@@ -33,7 +34,7 @@ export function createSheet(option) {
|
||||
return canvas
|
||||
}
|
||||
|
||||
export function downloadSheet(file, url) {
|
||||
export function downloadSheet(file: File, url: string) {
|
||||
const link = document.createElement('a')
|
||||
const name = `${getFileName(file.name)}.png`
|
||||
link.download = name
|
||||
+6
-4
@@ -1,6 +1,8 @@
|
||||
import type ArtplayerToolThumbnail from './index'
|
||||
import type { Cleanup } from './types'
|
||||
import { cleanupAll, revoke, stateFor } from './lifecycle'
|
||||
|
||||
export function loadSource(tool, file) {
|
||||
export function loadSource(tool: ArtplayerToolThumbnail, file?: File | null) {
|
||||
const state = stateFor(tool)
|
||||
if (!file || state.closed)
|
||||
return
|
||||
@@ -23,7 +25,7 @@ export function loadSource(tool, file) {
|
||||
state.loading = true
|
||||
const current = () => !state.closed && state.epoch === epoch
|
||||
const previousListeners = state.sourceListeners
|
||||
const listeners = []
|
||||
const listeners: Cleanup[] = []
|
||||
state.sourceListeners = listeners
|
||||
let pendingCleanup = () => {}
|
||||
let reported = false
|
||||
@@ -50,7 +52,7 @@ export function loadSource(tool, file) {
|
||||
revoke(state.sourceUrls, url)
|
||||
return
|
||||
}
|
||||
for (const [name, callback] of [['error', error], ['loadedmetadata', ready]]) {
|
||||
for (const [name, callback] of [['error', error], ['loadedmetadata', ready]] as const) {
|
||||
pendingCleanup = () => video.removeEventListener(name, callback)
|
||||
listeners.push(pendingCleanup)
|
||||
video.addEventListener(name, callback)
|
||||
@@ -94,7 +96,7 @@ export function loadSource(tool, file) {
|
||||
}
|
||||
}
|
||||
|
||||
export function replaceThumbnail(tool, blob, live) {
|
||||
export function replaceThumbnail(tool: ArtplayerToolThumbnail, blob: Blob, live: () => boolean) {
|
||||
const state = stateFor(tool)
|
||||
const url = URL.createObjectURL(blob)
|
||||
state.thumbnailUrls.add(url)
|
||||
@@ -0,0 +1,62 @@
|
||||
export interface SheetOptions {
|
||||
number: number
|
||||
width: number
|
||||
height: number
|
||||
column: number
|
||||
begin: number
|
||||
end: number
|
||||
}
|
||||
|
||||
export interface InputOptions extends Partial<SheetOptions> {
|
||||
fileInput?: Element
|
||||
[name: string]: unknown
|
||||
}
|
||||
|
||||
export interface RuntimeOptions extends SheetOptions {
|
||||
fileInput: HTMLInputElement
|
||||
[name: string]: unknown
|
||||
}
|
||||
|
||||
export interface ScreenshotPoint {
|
||||
time: number
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export interface ThumbnailEvents extends Record<PropertyKey, unknown[]> {
|
||||
file: [file: File]
|
||||
video: [video: HTMLVideoElement]
|
||||
canvas: [canvas: HTMLCanvasElement]
|
||||
update: [url: string, progress: number]
|
||||
done: []
|
||||
download: [name: string]
|
||||
error: [message: string | undefined]
|
||||
destroy: []
|
||||
}
|
||||
|
||||
export type Cleanup = () => void
|
||||
|
||||
export interface ExtractionJob {
|
||||
video: HTMLVideoElement
|
||||
promise: Promise<void>
|
||||
ready: (synchronous?: boolean) => void
|
||||
fail: (error: unknown, report?: boolean) => void
|
||||
cancel: (reason: string) => void
|
||||
adopt: (epoch: number) => boolean
|
||||
reported?: unknown
|
||||
wasReported?: boolean
|
||||
}
|
||||
|
||||
export interface LifecycleState {
|
||||
closed: boolean
|
||||
epoch: number
|
||||
inputEpoch: number
|
||||
job: ExtractionJob | null
|
||||
video: HTMLVideoElement | null
|
||||
sourceUrl: string | null
|
||||
thumbnailUrl: string | null
|
||||
sourceUrls: Set<string>
|
||||
thumbnailUrls: Set<string>
|
||||
sourceListeners: Cleanup[]
|
||||
loading: boolean
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
export function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
export function runPromisesInSeries(ps) {
|
||||
return ps.reduce((p, next) => p.then(next), Promise.resolve())
|
||||
}
|
||||
|
||||
export function getFileName(name) {
|
||||
const nameArray = name.split('.')
|
||||
nameArray.pop()
|
||||
return nameArray.join('.')
|
||||
}
|
||||
|
||||
export function clamp(num, a, b) {
|
||||
return Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b))
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export function getFileName(name: string) {
|
||||
const nameArray = name.split('.')
|
||||
nameArray.pop()
|
||||
return nameArray.join('.')
|
||||
}
|
||||
|
||||
export function clamp(num: number, a: number, b: number) {
|
||||
return Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b))
|
||||
}
|
||||
Reference in new issue
Block a user