roy-image-repository 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1,2 @@
1
1
  var royImageRepository=(function(n){"use strict";async function u(r){return new Promise((e,t)=>{const i=document.createElement("video");i.muted=!0,i.crossOrigin="anonymous",i.preload="metadata",i.onloadedmetadata=()=>{i.width=i.videoWidth,i.height=i.videoHeight,e(i)},i.onerror=s=>{console.log("video load error",s),t(s)},i.src=r})}function l(r){return{all:r=r||new Map,on:function(e,t){var i=r.get(e);i?i.push(t):r.set(e,[t])},off:function(e,t){var i=r.get(e);i&&(t?i.splice(i.indexOf(t)>>>0,1):r.set(e,[]))},emit:function(e,t){var i=r.get(e);i&&i.slice().map(function(s){s(t)}),(i=r.get("*"))&&i.slice().map(function(s){s(e,t)})}}}function a(r){return r/1e6}function h(r){return r*1e6}class d{queue=[];running=!1;taskId=0;stopped=!1;enqueue(e){return this.stopped?Promise.reject(new Error("Queue has been stopped")):new Promise((t,i)=>{const s={id:++this.taskId,task:e,resolve:t,reject:i};this.queue.push(s),this.process()})}async process(){if(!this.running&&this.queue.length!==0){for(this.running=!0;this.queue.length>0;){const e=this.queue.shift();if(!e)break;try{const t=await e.task();e.resolve(t)}catch(t){e.reject(t)}}this.running=!1}}clear(e="Task cleared"){for(const t of this.queue)t.reject(new Error(e));this.queue=[]}stop(){this.stopped=!0,this.clear("Queue stopped")}isRunning(){return this.running}size(){return this.queue.length}}class o{constructor(e){this.videoUrl=e}static _repositories={};_isInited=!1;_isPlaying=!1;_videoElement;get videoElement(){if(!this._videoElement)throw new Error("image repository is not inited!");return this._videoElement}_requestVideoFrameCallbackId;_emitter=l();static getRepository(e){const t=e.toLowerCase().trim();return this._repositories[t]||(this._repositories[t]=(async()=>{const i=new o(e);return await i.init(),i})()),this._repositories[t]}async init(){this._isInited||(this._isInited=!0,this._videoElement=await u(this.videoUrl))}uninit(){this._isInited&&(this._isInited=!1,this._isPlaying&&this.stop(),this._taskQueue.stop(),this._videoElement=void 0,this._emitter.all.clear())}_taskQueue=new d;async getImage(e){return await this._taskQueue.enqueue(()=>new Promise((t,i)=>{if(!this._videoElement){i(new Error("image repository is not inited!"));return}if(this._isPlaying){i(new Error("image repository is playing!"));return}this._videoElement.requestVideoFrameCallback(()=>{t(this.videoElement)}),this._videoElement.currentTime=a(e)}))}async play(){if(!this._videoElement)throw new Error("image repository is not inited!");if(this._isPlaying)throw new Error("image repository is playing!");try{this._isPlaying=!0;const e=(t,i)=>{this._emitter.emit("requestFrame",{frameTimestampInMicroseconds:h(i.mediaTime),originalEvent:{now:t,metadata:i}}),this._requestVideoFrameCallbackId=this._videoElement.requestVideoFrameCallback(e)};return this._requestVideoFrameCallbackId=this._videoElement.requestVideoFrameCallback(e),await this._videoElement.play()}catch(e){throw this._isPlaying=!1,e}}stop(){if(!this._videoElement)throw new Error("image repository is not inited!");if(!this._isPlaying)throw new Error("image repository is not playing");this.pause(),this._videoElement.currentTime=a(0)}pause(){if(!this._videoElement)throw new Error("image repository is not inited!");if(!this._isPlaying)throw new Error("image repository is not playing");try{this._isPlaying=!1,this._requestVideoFrameCallbackId&&(this._videoElement.cancelVideoFrameCallback(this._requestVideoFrameCallbackId),this._requestVideoFrameCallbackId=void 0),this._videoElement.pause()}catch(e){throw this._isPlaying=!0,e}}on(e,t){return this._emitter.on(e,t),{off:()=>{this._emitter.off(e,t)}}}}return n.ImageRepository=o,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),n})({});
2
+ //# sourceMappingURL=roy-image-repository.iife.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roy-image-repository.iife.js","sources":["../../src/utils/loadVideo.ts","../../node_modules/.pnpm/mitt@3.0.1/node_modules/mitt/dist/mitt.mjs","../../src/utils/date.ts","../../src/utils/taskQueue.ts","../../src/index.ts"],"sourcesContent":["export async function loadVideo(url: string) {\n return new Promise<HTMLVideoElement>((resolve, reject) => {\n const videoEl = document.createElement(\"video\") as HTMLVideoElement;\n videoEl.muted = true;\n videoEl.crossOrigin = \"anonymous\";\n videoEl.preload = \"metadata\";\n videoEl.onloadedmetadata = () => {\n // 👇 关键一步 fabric v5.5.2里面读取的是video标签的宽高属性,因此这里强制设置一下\n videoEl.width = videoEl.videoWidth;\n videoEl.height = videoEl.videoHeight;\n\n resolve(videoEl);\n };\n videoEl.onerror = evt => {\n console.log(\"video load error\", evt);\n reject(evt);\n };\n\n videoEl.src = url;\n });\n}\n","export default function(n){return{all:n=n||new Map,on:function(t,e){var i=n.get(t);i?i.push(e):n.set(t,[e])},off:function(t,e){var i=n.get(t);i&&(e?i.splice(i.indexOf(e)>>>0,1):n.set(t,[]))},emit:function(t,e){var i=n.get(t);i&&i.slice().map(function(n){n(e)}),(i=n.get(\"*\"))&&i.slice().map(function(n){n(t,e)})}}}\n//# sourceMappingURL=mitt.mjs.map\n","/**\n * 微秒=>秒\n */\nexport function us2s(ts: number) {\n return ts / 1000000;\n}\n\n/**\n * 秒=>微秒\n */\nexport function s2us(ts: number) {\n return ts * 1000000;\n}\n","export type AsyncTask<R = void> = () => Promise<R>;\r\n\r\ninterface QueueTask<R> {\r\n id: number;\r\n task: AsyncTask<R>;\r\n resolve: (value: R) => void;\r\n reject: (reason?: unknown) => void;\r\n}\r\n\r\nexport class SerialTaskQueue {\r\n private queue: QueueTask<unknown>[] = [];\r\n private running = false;\r\n private taskId = 0;\r\n private stopped = false;\r\n\r\n /**\r\n * 添加任务(泛型自动推导)\r\n */\r\n enqueue<R>(task: AsyncTask<R>): Promise<R> {\r\n if (this.stopped) {\r\n return Promise.reject(new Error(\"Queue has been stopped\"));\r\n }\r\n\r\n return new Promise<R>((resolve, reject) => {\r\n const queueTask: QueueTask<R> = {\r\n id: ++this.taskId,\r\n task,\r\n resolve,\r\n reject,\r\n };\r\n\r\n // 用 unknown 存储,运行时不影响\r\n this.queue.push(queueTask as QueueTask<unknown>);\r\n this.process();\r\n });\r\n }\r\n\r\n /**\r\n * 串行执行核心逻辑\r\n */\r\n private async process() {\r\n if (this.running) return;\r\n if (this.queue.length === 0) return;\r\n\r\n this.running = true;\r\n\r\n while (this.queue.length > 0) {\r\n const current = this.queue.shift();\r\n if (!current) break;\r\n\r\n try {\r\n const result = await current.task();\r\n current.resolve(result);\r\n } catch (err) {\r\n current.reject(err);\r\n }\r\n }\r\n\r\n this.running = false;\r\n }\r\n\r\n /**\r\n * 清空未执行任务\r\n */\r\n clear(reason = \"Task cleared\") {\r\n for (const task of this.queue) {\r\n task.reject(new Error(reason));\r\n }\r\n this.queue = [];\r\n }\r\n\r\n /**\r\n * 停止队列(不可恢复)\r\n */\r\n stop() {\r\n this.stopped = true;\r\n this.clear(\"Queue stopped\");\r\n }\r\n\r\n /**\r\n * 当前是否正在执行\r\n */\r\n isRunning() {\r\n return this.running;\r\n }\r\n\r\n /**\r\n * 当前等待数量\r\n */\r\n size() {\r\n return this.queue.length;\r\n }\r\n}\r\n","/**\r\n * 连续帧图片仓库\r\n * 内部利用video视频格式存储\r\n */\r\nimport { loadVideo } from \"@/utils/loadVideo\";\r\nimport mitt, { type Handler } from \"mitt\";\r\nimport { s2us, us2s } from \"./utils/date\";\r\nimport { SerialTaskQueue } from \"./utils/taskQueue\";\r\n\r\ntype ImageRepositoryEvents = {\r\n /**\r\n * 请求帧数据回调\r\n */\r\n [\"requestFrame\"]: {\r\n /**\r\n * 当前请求帧的时间戳,单位微秒\r\n */\r\n frameTimestampInMicroseconds: number;\r\n /**\r\n * 原生的事件对象\r\n */\r\n originalEvent: {\r\n now: DOMHighResTimeStamp;\r\n metadata: VideoFrameCallbackMetadata;\r\n };\r\n };\r\n};\r\n\r\nexport class ImageRepository {\r\n private static _repositories: Record<string, Promise<ImageRepository>> = {};\r\n\r\n private _isInited = false;\r\n private _isPlaying = false;\r\n\r\n private _videoElement?: HTMLVideoElement;\r\n /**\r\n * 视频DOM元素,使用它来渲染画布\r\n */\r\n get videoElement() {\r\n if (!this._videoElement) {\r\n throw new Error(`image repository is not inited!`);\r\n }\r\n return this._videoElement;\r\n }\r\n\r\n private _requestVideoFrameCallbackId?: number;\r\n\r\n private _emitter = mitt<ImageRepositoryEvents>();\r\n\r\n private constructor(private readonly videoUrl: string) {}\r\n\r\n static getRepository(videoUrl: string) {\r\n const key = videoUrl.toLowerCase().trim();\r\n if (!this._repositories[key]) {\r\n this._repositories[key] = (async () => {\r\n const repo = new ImageRepository(videoUrl);\r\n await repo.init();\r\n return repo;\r\n })();\r\n }\r\n return this._repositories[key];\r\n }\r\n\r\n async init() {\r\n if (this._isInited) {\r\n return;\r\n }\r\n this._isInited = true;\r\n this._videoElement = await loadVideo(this.videoUrl);\r\n }\r\n\r\n uninit() {\r\n if (!this._isInited) {\r\n return;\r\n }\r\n this._isInited = false;\r\n if (this._isPlaying) {\r\n this.stop();\r\n }\r\n this._taskQueue.stop();\r\n\r\n this._videoElement = undefined;\r\n this._emitter.all.clear();\r\n }\r\n\r\n private _taskQueue = new SerialTaskQueue();\r\n /**\r\n * 根据传入的微秒时间戳,获取指定位置的帧图片,直接返回切换到对应帧的视频对象,在并发调用的情况下,内部通过统一的串行队列,将调用的结果按照顺序返回\r\n * @param timstampInMicroseconds 微秒时间戳\r\n */\r\n async getImage(timstampInMicroseconds: number) {\r\n return await this._taskQueue.enqueue(() => {\r\n return new Promise<HTMLVideoElement>((resolve, reject) => {\r\n if (!this._videoElement) {\r\n reject(new Error(`image repository is not inited!`));\r\n return;\r\n }\r\n // 判断是否已经处于播放中了\r\n if (this._isPlaying) {\r\n reject(new Error(`image repository is playing!`));\r\n return;\r\n }\r\n this._videoElement.requestVideoFrameCallback(() => {\r\n resolve(this.videoElement!);\r\n });\r\n // currentTime单位是秒,因此这里要将传入的微秒时间戳转换为秒\r\n this._videoElement.currentTime = us2s(timstampInMicroseconds);\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * 开始播放连续帧图片\r\n */\r\n async play() {\r\n if (!this._videoElement) {\r\n throw new Error(`image repository is not inited!`);\r\n }\r\n // 判断是否已经处于播放中了\r\n if (this._isPlaying) {\r\n throw new Error(`image repository is playing!`);\r\n }\r\n\r\n try {\r\n this._isPlaying = true;\r\n\r\n const callback: VideoFrameRequestCallback = (now, metadata) => {\r\n // mediaTime单位是秒,因此要转换为微秒\r\n this._emitter.emit(\"requestFrame\", {\r\n frameTimestampInMicroseconds: s2us(metadata.mediaTime),\r\n originalEvent: { now, metadata },\r\n });\r\n // 触发下一次\r\n this._requestVideoFrameCallbackId =\r\n this._videoElement!.requestVideoFrameCallback(callback);\r\n };\r\n this._requestVideoFrameCallbackId =\r\n this._videoElement.requestVideoFrameCallback(callback);\r\n\r\n return await this._videoElement.play();\r\n } catch (err) {\r\n this._isPlaying = false;\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * 停止播放\r\n */\r\n stop() {\r\n if (!this._videoElement) {\r\n throw new Error(`image repository is not inited!`);\r\n }\r\n if (!this._isPlaying) {\r\n throw new Error(`image repository is not playing`);\r\n }\r\n this.pause();\r\n this._videoElement.currentTime = us2s(0);\r\n }\r\n\r\n /**\r\n * 暂停播放\r\n */\r\n pause() {\r\n if (!this._videoElement) {\r\n throw new Error(`image repository is not inited!`);\r\n }\r\n if (!this._isPlaying) {\r\n throw new Error(`image repository is not playing`);\r\n }\r\n try {\r\n this._isPlaying = false;\r\n\r\n if (!!this._requestVideoFrameCallbackId) {\r\n this._videoElement!.cancelVideoFrameCallback(\r\n this._requestVideoFrameCallbackId\r\n );\r\n this._requestVideoFrameCallbackId = undefined;\r\n }\r\n\r\n this._videoElement.pause();\r\n } catch (err) {\r\n this._isPlaying = true;\r\n throw err;\r\n }\r\n }\r\n\r\n on<T extends keyof ImageRepositoryEvents>(\r\n eventName: T,\r\n handler: Handler<ImageRepositoryEvents[T]>\r\n ) {\r\n this._emitter.on(eventName, handler);\r\n\r\n return {\r\n off: () => {\r\n this._emitter.off(eventName, handler);\r\n },\r\n };\r\n }\r\n}\r\n"],"names":["loadVideo","url","resolve","reject","videoEl","evt","mitt","n","t","e","us2s","ts","s2us","SerialTaskQueue","task","queueTask","current","result","err","reason","ImageRepository","videoUrl","key","repo","timstampInMicroseconds","callback","now","metadata","eventName","handler"],"mappings":"iDAAA,eAAsBA,EAAUC,EAAa,CAC3C,OAAO,IAAI,QAA0B,CAACC,EAASC,IAAW,CACxD,MAAMC,EAAU,SAAS,cAAc,OAAO,EAC9CA,EAAQ,MAAQ,GAChBA,EAAQ,YAAc,YACtBA,EAAQ,QAAU,WAClBA,EAAQ,iBAAmB,IAAM,CAE/BA,EAAQ,MAAQA,EAAQ,WACxBA,EAAQ,OAASA,EAAQ,YAEzBF,EAAQE,CAAO,CACjB,EACAA,EAAQ,QAAUC,GAAO,CACvB,QAAQ,IAAI,mBAAoBA,CAAG,EACnCF,EAAOE,CAAG,CACZ,EAEAD,EAAQ,IAAMH,CAChB,CAAC,CACH,CCpBe,SAAAK,EAASC,EAAE,CAAC,MAAM,CAAC,IAAIA,EAAEA,GAAG,IAAI,IAAI,GAAG,SAASC,EAAEC,EAAE,CAAC,IAAI,EAAEF,EAAE,IAAIC,CAAC,EAAE,EAAE,EAAE,KAAKC,CAAC,EAAEF,EAAE,IAAIC,EAAE,CAACC,CAAC,CAAC,CAAC,EAAE,IAAI,SAASD,EAAEC,EAAE,CAAC,IAAI,EAAEF,EAAE,IAAIC,CAAC,EAAE,IAAIC,EAAE,EAAE,OAAO,EAAE,QAAQA,CAAC,IAAI,EAAE,CAAC,EAAEF,EAAE,IAAIC,EAAE,EAAE,EAAE,EAAE,KAAK,SAASA,EAAEC,EAAE,CAAC,IAAI,EAAEF,EAAE,IAAIC,CAAC,EAAE,GAAG,EAAE,QAAQ,IAAI,SAASD,EAAE,CAACA,EAAEE,CAAC,CAAC,CAAC,GAAG,EAAEF,EAAE,IAAI,GAAG,IAAI,EAAE,MAAK,EAAG,IAAI,SAASA,EAAE,CAACA,EAAEC,EAAEC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CCGlT,SAASC,EAAKC,EAAY,CAC/B,OAAOA,EAAK,GACd,CAKO,SAASC,EAAKD,EAAY,CAC/B,OAAOA,EAAK,GACd,CCHO,MAAME,CAAgB,CACnB,MAA8B,CAAA,EAC9B,QAAU,GACV,OAAS,EACT,QAAU,GAKlB,QAAWC,EAAgC,CACzC,OAAI,KAAK,QACA,QAAQ,OAAO,IAAI,MAAM,wBAAwB,CAAC,EAGpD,IAAI,QAAW,CAACZ,EAASC,IAAW,CACzC,MAAMY,EAA0B,CAC9B,GAAI,EAAE,KAAK,OACX,KAAAD,EACA,QAAAZ,EACA,OAAAC,CAAA,EAIF,KAAK,MAAM,KAAKY,CAA+B,EAC/C,KAAK,QAAA,CACP,CAAC,CACH,CAKA,MAAc,SAAU,CACtB,GAAI,MAAK,SACL,KAAK,MAAM,SAAW,EAI1B,KAFA,KAAK,QAAU,GAER,KAAK,MAAM,OAAS,GAAG,CAC5B,MAAMC,EAAU,KAAK,MAAM,MAAA,EAC3B,GAAI,CAACA,EAAS,MAEd,GAAI,CACF,MAAMC,EAAS,MAAMD,EAAQ,KAAA,EAC7BA,EAAQ,QAAQC,CAAM,CACxB,OAASC,EAAK,CACZF,EAAQ,OAAOE,CAAG,CACpB,CACF,CAEA,KAAK,QAAU,GACjB,CAKA,MAAMC,EAAS,eAAgB,CAC7B,UAAWL,KAAQ,KAAK,MACtBA,EAAK,OAAO,IAAI,MAAMK,CAAM,CAAC,EAE/B,KAAK,MAAQ,CAAA,CACf,CAKA,MAAO,CACL,KAAK,QAAU,GACf,KAAK,MAAM,eAAe,CAC5B,CAKA,WAAY,CACV,OAAO,KAAK,OACd,CAKA,MAAO,CACL,OAAO,KAAK,MAAM,MACpB,CACF,CChEO,MAAMC,CAAgB,CAqBnB,YAA6BC,EAAkB,CAAlB,KAAA,SAAAA,CAAmB,CApBxD,OAAe,cAA0D,CAAA,EAEjE,UAAY,GACZ,WAAa,GAEb,cAIR,IAAI,cAAe,CACjB,GAAI,CAAC,KAAK,cACR,MAAM,IAAI,MAAM,iCAAiC,EAEnD,OAAO,KAAK,aACd,CAEQ,6BAEA,SAAWf,EAAA,EAInB,OAAO,cAAce,EAAkB,CACrC,MAAMC,EAAMD,EAAS,YAAA,EAAc,KAAA,EACnC,OAAK,KAAK,cAAcC,CAAG,IACzB,KAAK,cAAcA,CAAG,GAAK,SAAY,CACrC,MAAMC,EAAO,IAAIH,EAAgBC,CAAQ,EACzC,aAAME,EAAK,KAAA,EACJA,CACT,GAAA,GAEK,KAAK,cAAcD,CAAG,CAC/B,CAEA,MAAM,MAAO,CACP,KAAK,YAGT,KAAK,UAAY,GACjB,KAAK,cAAgB,MAAMtB,EAAU,KAAK,QAAQ,EACpD,CAEA,QAAS,CACF,KAAK,YAGV,KAAK,UAAY,GACb,KAAK,YACP,KAAK,KAAA,EAEP,KAAK,WAAW,KAAA,EAEhB,KAAK,cAAgB,OACrB,KAAK,SAAS,IAAI,MAAA,EACpB,CAEQ,WAAa,IAAIa,EAKzB,MAAM,SAASW,EAAgC,CAC7C,OAAO,MAAM,KAAK,WAAW,QAAQ,IAC5B,IAAI,QAA0B,CAACtB,EAASC,IAAW,CACxD,GAAI,CAAC,KAAK,cAAe,CACvBA,EAAO,IAAI,MAAM,iCAAiC,CAAC,EACnD,MACF,CAEA,GAAI,KAAK,WAAY,CACnBA,EAAO,IAAI,MAAM,8BAA8B,CAAC,EAChD,MACF,CACA,KAAK,cAAc,0BAA0B,IAAM,CACjDD,EAAQ,KAAK,YAAa,CAC5B,CAAC,EAED,KAAK,cAAc,YAAcQ,EAAKc,CAAsB,CAC9D,CAAC,CACF,CACH,CAKA,MAAM,MAAO,CACX,GAAI,CAAC,KAAK,cACR,MAAM,IAAI,MAAM,iCAAiC,EAGnD,GAAI,KAAK,WACP,MAAM,IAAI,MAAM,8BAA8B,EAGhD,GAAI,CACF,KAAK,WAAa,GAElB,MAAMC,EAAsC,CAACC,EAAKC,IAAa,CAE7D,KAAK,SAAS,KAAK,eAAgB,CACjC,6BAA8Bf,EAAKe,EAAS,SAAS,EACrD,cAAe,CAAE,IAAAD,EAAK,SAAAC,CAAA,CAAS,CAChC,EAED,KAAK,6BACH,KAAK,cAAe,0BAA0BF,CAAQ,CAC1D,EACA,YAAK,6BACH,KAAK,cAAc,0BAA0BA,CAAQ,EAEhD,MAAM,KAAK,cAAc,KAAA,CAClC,OAASP,EAAK,CACZ,WAAK,WAAa,GACZA,CACR,CACF,CAKA,MAAO,CACL,GAAI,CAAC,KAAK,cACR,MAAM,IAAI,MAAM,iCAAiC,EAEnD,GAAI,CAAC,KAAK,WACR,MAAM,IAAI,MAAM,iCAAiC,EAEnD,KAAK,MAAA,EACL,KAAK,cAAc,YAAcR,EAAK,CAAC,CACzC,CAKA,OAAQ,CACN,GAAI,CAAC,KAAK,cACR,MAAM,IAAI,MAAM,iCAAiC,EAEnD,GAAI,CAAC,KAAK,WACR,MAAM,IAAI,MAAM,iCAAiC,EAEnD,GAAI,CACF,KAAK,WAAa,GAEZ,KAAK,+BACT,KAAK,cAAe,yBAClB,KAAK,4BAAA,EAEP,KAAK,6BAA+B,QAGtC,KAAK,cAAc,MAAA,CACrB,OAASQ,EAAK,CACZ,WAAK,WAAa,GACZA,CACR,CACF,CAEA,GACEU,EACAC,EACA,CACA,YAAK,SAAS,GAAGD,EAAWC,CAAO,EAE5B,CACL,IAAK,IAAM,CACT,KAAK,SAAS,IAAID,EAAWC,CAAO,CACtC,CAAA,CAEJ,CACF","x_google_ignoreList":[1]}
@@ -203,3 +203,4 @@ class o {
203
203
  export {
204
204
  o as ImageRepository
205
205
  };
206
+ //# sourceMappingURL=roy-image-repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roy-image-repository.js","sources":["../../src/utils/loadVideo.ts","../../node_modules/.pnpm/mitt@3.0.1/node_modules/mitt/dist/mitt.mjs","../../src/utils/date.ts","../../src/utils/taskQueue.ts","../../src/index.ts"],"sourcesContent":["export async function loadVideo(url: string) {\n return new Promise<HTMLVideoElement>((resolve, reject) => {\n const videoEl = document.createElement(\"video\") as HTMLVideoElement;\n videoEl.muted = true;\n videoEl.crossOrigin = \"anonymous\";\n videoEl.preload = \"metadata\";\n videoEl.onloadedmetadata = () => {\n // 👇 关键一步 fabric v5.5.2里面读取的是video标签的宽高属性,因此这里强制设置一下\n videoEl.width = videoEl.videoWidth;\n videoEl.height = videoEl.videoHeight;\n\n resolve(videoEl);\n };\n videoEl.onerror = evt => {\n console.log(\"video load error\", evt);\n reject(evt);\n };\n\n videoEl.src = url;\n });\n}\n","export default function(n){return{all:n=n||new Map,on:function(t,e){var i=n.get(t);i?i.push(e):n.set(t,[e])},off:function(t,e){var i=n.get(t);i&&(e?i.splice(i.indexOf(e)>>>0,1):n.set(t,[]))},emit:function(t,e){var i=n.get(t);i&&i.slice().map(function(n){n(e)}),(i=n.get(\"*\"))&&i.slice().map(function(n){n(t,e)})}}}\n//# sourceMappingURL=mitt.mjs.map\n","/**\n * 微秒=>秒\n */\nexport function us2s(ts: number) {\n return ts / 1000000;\n}\n\n/**\n * 秒=>微秒\n */\nexport function s2us(ts: number) {\n return ts * 1000000;\n}\n","export type AsyncTask<R = void> = () => Promise<R>;\r\n\r\ninterface QueueTask<R> {\r\n id: number;\r\n task: AsyncTask<R>;\r\n resolve: (value: R) => void;\r\n reject: (reason?: unknown) => void;\r\n}\r\n\r\nexport class SerialTaskQueue {\r\n private queue: QueueTask<unknown>[] = [];\r\n private running = false;\r\n private taskId = 0;\r\n private stopped = false;\r\n\r\n /**\r\n * 添加任务(泛型自动推导)\r\n */\r\n enqueue<R>(task: AsyncTask<R>): Promise<R> {\r\n if (this.stopped) {\r\n return Promise.reject(new Error(\"Queue has been stopped\"));\r\n }\r\n\r\n return new Promise<R>((resolve, reject) => {\r\n const queueTask: QueueTask<R> = {\r\n id: ++this.taskId,\r\n task,\r\n resolve,\r\n reject,\r\n };\r\n\r\n // 用 unknown 存储,运行时不影响\r\n this.queue.push(queueTask as QueueTask<unknown>);\r\n this.process();\r\n });\r\n }\r\n\r\n /**\r\n * 串行执行核心逻辑\r\n */\r\n private async process() {\r\n if (this.running) return;\r\n if (this.queue.length === 0) return;\r\n\r\n this.running = true;\r\n\r\n while (this.queue.length > 0) {\r\n const current = this.queue.shift();\r\n if (!current) break;\r\n\r\n try {\r\n const result = await current.task();\r\n current.resolve(result);\r\n } catch (err) {\r\n current.reject(err);\r\n }\r\n }\r\n\r\n this.running = false;\r\n }\r\n\r\n /**\r\n * 清空未执行任务\r\n */\r\n clear(reason = \"Task cleared\") {\r\n for (const task of this.queue) {\r\n task.reject(new Error(reason));\r\n }\r\n this.queue = [];\r\n }\r\n\r\n /**\r\n * 停止队列(不可恢复)\r\n */\r\n stop() {\r\n this.stopped = true;\r\n this.clear(\"Queue stopped\");\r\n }\r\n\r\n /**\r\n * 当前是否正在执行\r\n */\r\n isRunning() {\r\n return this.running;\r\n }\r\n\r\n /**\r\n * 当前等待数量\r\n */\r\n size() {\r\n return this.queue.length;\r\n }\r\n}\r\n","/**\r\n * 连续帧图片仓库\r\n * 内部利用video视频格式存储\r\n */\r\nimport { loadVideo } from \"@/utils/loadVideo\";\r\nimport mitt, { type Handler } from \"mitt\";\r\nimport { s2us, us2s } from \"./utils/date\";\r\nimport { SerialTaskQueue } from \"./utils/taskQueue\";\r\n\r\ntype ImageRepositoryEvents = {\r\n /**\r\n * 请求帧数据回调\r\n */\r\n [\"requestFrame\"]: {\r\n /**\r\n * 当前请求帧的时间戳,单位微秒\r\n */\r\n frameTimestampInMicroseconds: number;\r\n /**\r\n * 原生的事件对象\r\n */\r\n originalEvent: {\r\n now: DOMHighResTimeStamp;\r\n metadata: VideoFrameCallbackMetadata;\r\n };\r\n };\r\n};\r\n\r\nexport class ImageRepository {\r\n private static _repositories: Record<string, Promise<ImageRepository>> = {};\r\n\r\n private _isInited = false;\r\n private _isPlaying = false;\r\n\r\n private _videoElement?: HTMLVideoElement;\r\n /**\r\n * 视频DOM元素,使用它来渲染画布\r\n */\r\n get videoElement() {\r\n if (!this._videoElement) {\r\n throw new Error(`image repository is not inited!`);\r\n }\r\n return this._videoElement;\r\n }\r\n\r\n private _requestVideoFrameCallbackId?: number;\r\n\r\n private _emitter = mitt<ImageRepositoryEvents>();\r\n\r\n private constructor(private readonly videoUrl: string) {}\r\n\r\n static getRepository(videoUrl: string) {\r\n const key = videoUrl.toLowerCase().trim();\r\n if (!this._repositories[key]) {\r\n this._repositories[key] = (async () => {\r\n const repo = new ImageRepository(videoUrl);\r\n await repo.init();\r\n return repo;\r\n })();\r\n }\r\n return this._repositories[key];\r\n }\r\n\r\n async init() {\r\n if (this._isInited) {\r\n return;\r\n }\r\n this._isInited = true;\r\n this._videoElement = await loadVideo(this.videoUrl);\r\n }\r\n\r\n uninit() {\r\n if (!this._isInited) {\r\n return;\r\n }\r\n this._isInited = false;\r\n if (this._isPlaying) {\r\n this.stop();\r\n }\r\n this._taskQueue.stop();\r\n\r\n this._videoElement = undefined;\r\n this._emitter.all.clear();\r\n }\r\n\r\n private _taskQueue = new SerialTaskQueue();\r\n /**\r\n * 根据传入的微秒时间戳,获取指定位置的帧图片,直接返回切换到对应帧的视频对象,在并发调用的情况下,内部通过统一的串行队列,将调用的结果按照顺序返回\r\n * @param timstampInMicroseconds 微秒时间戳\r\n */\r\n async getImage(timstampInMicroseconds: number) {\r\n return await this._taskQueue.enqueue(() => {\r\n return new Promise<HTMLVideoElement>((resolve, reject) => {\r\n if (!this._videoElement) {\r\n reject(new Error(`image repository is not inited!`));\r\n return;\r\n }\r\n // 判断是否已经处于播放中了\r\n if (this._isPlaying) {\r\n reject(new Error(`image repository is playing!`));\r\n return;\r\n }\r\n this._videoElement.requestVideoFrameCallback(() => {\r\n resolve(this.videoElement!);\r\n });\r\n // currentTime单位是秒,因此这里要将传入的微秒时间戳转换为秒\r\n this._videoElement.currentTime = us2s(timstampInMicroseconds);\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * 开始播放连续帧图片\r\n */\r\n async play() {\r\n if (!this._videoElement) {\r\n throw new Error(`image repository is not inited!`);\r\n }\r\n // 判断是否已经处于播放中了\r\n if (this._isPlaying) {\r\n throw new Error(`image repository is playing!`);\r\n }\r\n\r\n try {\r\n this._isPlaying = true;\r\n\r\n const callback: VideoFrameRequestCallback = (now, metadata) => {\r\n // mediaTime单位是秒,因此要转换为微秒\r\n this._emitter.emit(\"requestFrame\", {\r\n frameTimestampInMicroseconds: s2us(metadata.mediaTime),\r\n originalEvent: { now, metadata },\r\n });\r\n // 触发下一次\r\n this._requestVideoFrameCallbackId =\r\n this._videoElement!.requestVideoFrameCallback(callback);\r\n };\r\n this._requestVideoFrameCallbackId =\r\n this._videoElement.requestVideoFrameCallback(callback);\r\n\r\n return await this._videoElement.play();\r\n } catch (err) {\r\n this._isPlaying = false;\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * 停止播放\r\n */\r\n stop() {\r\n if (!this._videoElement) {\r\n throw new Error(`image repository is not inited!`);\r\n }\r\n if (!this._isPlaying) {\r\n throw new Error(`image repository is not playing`);\r\n }\r\n this.pause();\r\n this._videoElement.currentTime = us2s(0);\r\n }\r\n\r\n /**\r\n * 暂停播放\r\n */\r\n pause() {\r\n if (!this._videoElement) {\r\n throw new Error(`image repository is not inited!`);\r\n }\r\n if (!this._isPlaying) {\r\n throw new Error(`image repository is not playing`);\r\n }\r\n try {\r\n this._isPlaying = false;\r\n\r\n if (!!this._requestVideoFrameCallbackId) {\r\n this._videoElement!.cancelVideoFrameCallback(\r\n this._requestVideoFrameCallbackId\r\n );\r\n this._requestVideoFrameCallbackId = undefined;\r\n }\r\n\r\n this._videoElement.pause();\r\n } catch (err) {\r\n this._isPlaying = true;\r\n throw err;\r\n }\r\n }\r\n\r\n on<T extends keyof ImageRepositoryEvents>(\r\n eventName: T,\r\n handler: Handler<ImageRepositoryEvents[T]>\r\n ) {\r\n this._emitter.on(eventName, handler);\r\n\r\n return {\r\n off: () => {\r\n this._emitter.off(eventName, handler);\r\n },\r\n };\r\n }\r\n}\r\n"],"names":["loadVideo","url","resolve","reject","videoEl","evt","mitt","n","t","e","us2s","ts","s2us","SerialTaskQueue","task","queueTask","current","result","err","reason","ImageRepository","videoUrl","key","repo","timstampInMicroseconds","callback","now","metadata","eventName","handler"],"mappings":"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","x_google_ignoreList":[1]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "roy-image-repository",
3
3
  "author": "roy",
4
- "version": "0.1.3",
4
+ "version": "0.1.4",
5
5
  "module": "./dist/roy-image-repository.js",
6
6
  "type": "module",
7
7
  "exports": {