mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -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
@@ -8,3 +8,4 @@ test/browser/media/dash/*.mpd text eol=lf whitespace=-blank-at-eol
|
||||
test/browser/media/dash/*.m4s binary
|
||||
test/browser/media/*.m4a binary
|
||||
refactor/baselines/core-vendor/*.txt text eol=lf
|
||||
refactor/baselines/thumbnail-vendor/* text eol=lf whitespace=-blank-at-eof
|
||||
@@ -3,5 +3,32 @@
|
||||
* Github: https://github.com/zhw2590582/ArtPlayer
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*
|
||||
* ArtPlayer thumbnail tool bundled third-party notices
|
||||
*
|
||||
* tiny-emitter - reference 2.1.0, locally adapted to a TypeScript class
|
||||
* https://github.com/scottcorgan/tiny-emitter/tree/2.1.0
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2017 Scott Corgan
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this;function o(...i){r.off(e,o),t.apply(n,i)}return o._=t,this.on(e,o,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;r<n.length;r+=1)n[r].fn.apply(n[r].ctx,t);return this}off(e,t){const n=this.e||(this.e={}),r=n[e],o=[];if(r&&t)for(let i=0,s=r.length;i<s;i+=1)r[i].fn!==t&&r[i].fn._!==t&&o.push(r[i]);return o.length?n[e]=o:delete n[e],this}}const t=new WeakMap;function n(e){return t.has(e)||t.set(e,{closed:!1,epoch:0,inputEpoch:0,job:null,video:null,sourceUrl:null,thumbnailUrl:null,sourceUrls:new Set,thumbnailUrls:new Set,sourceListeners:[],loading:!1}),t.get(e)}function r(e){const t=new Error(`Thumbnail task cancelled: ${e}`);return t.name="AbortError",t}function o(e,t){t&&(e.delete(t),URL.revokeObjectURL(t))}function i(e){let t;for(const r of e)try{r()}catch(n){t||(t=n)}if(t)throw t}function s(e,t="destroyed"){const r=n(e);return!r.closed&&(r.closed=!0,r.epoch++,r.job?.cancel(t),!0)}function c(e){const t=n(e),r=new Set([...t.sourceUrls,e.videoUrl]),s=new Set([...t.thumbnailUrls,e.thumbnailUrl]),c=[];for(const n of new Set([t.video,e.video]))n&&c.push(()=>n.pause?.(),()=>n.removeAttribute?.("src"),()=>n.load?.(),()=>n.parentNode?.removeChild(n));i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function a(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function l(e,t){let s=t.epoch;const c=e.video;let l,u,d,h,p=!1,f=!1,m=()=>{};const b=[],y=new Promise((e,t)=>{l=e,u=t});y.catch(()=>{});const g=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function v(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{v()}catch(r){t||(t=r)}if(n)try{e.emit("error",t?.message)}catch(o){t=o}u(t)}}function U(t){if(!g())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!g())return;if(r===t.points.length)return void function(){if(g())try{v(),e.emit("done"),l()}catch(t){let r=t;try{e.emit("error",t?.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let a=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!g()||a||c.seeking||void 0!==c.readyState&&c.readyState<2)){a=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!g())return;t.canvas.toBlob(s=>{if(g()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,g);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(l){w(l)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(g()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,l=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=a(i,0,n.duration),e.option.end=a(s||n.duration,i,n.duration),l(e.option.end>e.option.begin,"End time must be greater than the start time"),l(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,l(e.file&&n,"Please select the video file first"),l(!e.processing,"There is currently a task in progress, please wait a moment..."),l(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),g()||clearTimeout(d)}h={video:c,promise:y,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{g()&&w(new Error(`Unable to load video: media error ${c.error?.code||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.removeEventListener(e,t)),c.addEventListener(e,t),!g())return i([()=>c.removeEventListener(e,t),...b.splice(0)]),y;c.error?w(new Error(`Unable to load video: media error ${c.error.code}`)):E(!0)}catch(j){throw w(j,!1),j}return
Line truncated
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this,o=t;function i(...t){r.off(e,i),o.apply(n,t)}return i._=t,this.on(e,i,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;r<n.length;r+=1)n[r].fn.apply(n[r].ctx,t);return this}off(e,t){const n=this.e||(this.e={}),r=n[e],o=[];if(r&&t)for(let i=0,s=r.length;i<s;i+=1)r[i].fn!==t&&r[i].fn._!==t&&o.push(r[i]);return o.length?n[e]=o:delete n[e],this}}const t=new WeakMap;function n(e){return t.has(e)||t.set(e,{closed:!1,epoch:0,inputEpoch:0,job:null,video:null,sourceUrl:null,thumbnailUrl:null,sourceUrls:new Set,thumbnailUrls:new Set,sourceListeners:[],loading:!1}),t.get(e)}function r(e){const t=new Error(`Thumbnail task cancelled: ${e}`);return t.name="AbortError",t}function o(e,t){t&&(e.delete(t),URL.revokeObjectURL(t))}function i(e){let t;for(const r of e)try{r()}catch(n){t||(t=n)}if(t)throw t}function s(e,t="destroyed"){const r=n(e);return!r.closed&&(r.closed=!0,r.epoch++,r.job?.cancel(t),!0)}function c(e){const t=n(e),r=new Set([...t.sourceUrls,e.videoUrl]),s=new Set([...t.thumbnailUrls,e.thumbnailUrl]),c=[];for(const n of new Set([t.video,e.video]))n&&c.push(()=>n.pause?.(),()=>n.removeAttribute?.("src"),()=>n.load?.(),()=>n.parentNode?.removeChild(n));i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function a(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function l(e,t){let s=t.epoch;const c=e.video;let l,u,d,h,p=!1,f=!1,m=()=>{};const b=[],y=new Promise((e,t)=>{l=e,u=t});y.catch(()=>{});const g=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function v(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{v()}catch(r){t||(t=r)}if(n)try{e.emit("error",t?.message)}catch(o){t=o}u(t)}}function U(t){if(!g())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!g())return;if(r===t.points.length)return void function(){if(g())try{v(),e.emit("done"),l()}catch(t){let r=t;try{e.emit("error",t?.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let a=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!g()||a||c.seeking||void 0!==c.readyState&&c.readyState<2)){a=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!g())return;t.canvas.toBlob(s=>{if(g()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,g);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(l){w(l)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(g()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,l=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=a(i,0,n.duration),e.option.end=a(s||n.duration,i,n.duration),l(e.option.end>e.option.begin,"End time must be greater than the start time"),l(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,l(e.file&&n,"Please select the video file first"),l(!e.processing,"There is currently a task in progress, please wait a moment..."),l(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),g()||clearTimeout(d)}h={video:c,promise:y,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{g()&&w(new Error(`Unable to load video: media error ${c.error?.code||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.removeEventListener(e,t)),c.addEventListener(e,t),!g())return i([()=>c.removeEventListener(e,t),...b.splice(0)]),y;c.error?w(new Error(`Unable to load video: media error ${c.error.code}`)):E(!0)}catch(j){throw w(j,!1),j}retLine truncated
|
||||
@@ -3,5 +3,32 @@
|
||||
* Github: https://github.com/zhw2590582/ArtPlayer
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*
|
||||
* ArtPlayer thumbnail tool bundled third-party notices
|
||||
*
|
||||
* tiny-emitter - reference 2.1.0, locally adapted to a TypeScript class
|
||||
* https://github.com/scottcorgan/tiny-emitter/tree/2.1.0
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2017 Scott Corgan
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this;function o(...i){r.off(e,o),t.apply(n,i)}return o._=t,this.on(e,o,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;r<n.length;r+=1)n[r].fn.apply(n[r].ctx,t);return this}off(e,t){const n=this.e||(this.e={}),r=n[e],o=[];if(r&&t)for(let i=0,s=r.length;i<s;i+=1)r[i].fn!==t&&r[i].fn._!==t&&o.push(r[i]);return o.length?n[e]=o:delete n[e],this}}const t=new WeakMap;function n(e){return t.has(e)||t.set(e,{closed:!1,epoch:0,inputEpoch:0,job:null,video:null,sourceUrl:null,thumbnailUrl:null,sourceUrls:new Set,thumbnailUrls:new Set,sourceListeners:[],loading:!1}),t.get(e)}function r(e){const t=new Error(`Thumbnail task cancelled: ${e}`);return t.name="AbortError",t}function o(e,t){t&&(e.delete(t),URL.revokeObjectURL(t))}function i(e){let t;for(const r of e)try{r()}catch(n){t||(t=n)}if(t)throw t}function s(e,t="destroyed"){var r;const o=n(e);return!o.closed&&(o.closed=!0,o.epoch++,null==(r=o.job)||r.cancel(t),!0)}function c(e){const t=n(e),r=new Set([...t.sourceUrls,e.videoUrl]),s=new Set([...t.thumbnailUrls,e.thumbnailUrl]),c=[];for(const n of new Set([t.video,e.video]))n&&c.push(()=>{var e;return null==(e=n.pause)?void 0:e.call(n)},()=>{var e;return null==(e=n.removeAttribute)?void 0:e.call(n,"src")},()=>{var e;return null==(e=n.load)?void 0:e.call(n)},()=>{var e;return null==(e=n.parentNode)?void 0:e.removeChild(n)});i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function l(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function a(e,t){let s=t.epoch;const c=e.video;let a,u,d,h,p=!1,f=!1,m=()=>{};const b=[],v=new Promise((e,t)=>{a=e,u=t});v.catch(()=>{});const y=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function g(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{g()}catch(r){t||(t=r)}if(n)try{e.emit("error",null==t?void 0:t.message)}catch(o){t=o}u(t)}}function U(t){if(!y())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!y())return;if(r===t.points.length)return void function(){if(y())try{g(),e.emit("done"),a()}catch(t){let r=t;try{e.emit("error",null==t?void 0:t.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let l=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!y()||l||c.seeking||void 0!==c.readyState&&c.readyState<2)){l=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!y())return;t.canvas.toBlob(s=>{if(y()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,y);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(a){w(a)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(y()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,a=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=l(i,0,n.duration),e.option.end=l(s||n.duration,i,n.duration),a(e.option.end>e.option.begin,"End time must be greater than the start time"),a(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,a(e.file&&n,"Please select the video file first"),a(!e.processing,"There is currently a task in progress, please wait a moment..."),a(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),y()||clearTimeout(d)}h={video:c,promise:v,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{var e;y()&&w(new Error(`Unable to load video: media error ${(null==(e=c.error)?void 0:e.code)||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.removeELine truncated
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this,o=t;function i(...t){r.off(e,i),o.apply(n,t)}return i._=t,this.on(e,i,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;r<n.length;r+=1)n[r].fn.apply(n[r].ctx,t);return this}off(e,t){const n=this.e||(this.e={}),r=n[e],o=[];if(r&&t)for(let i=0,s=r.length;i<s;i+=1)r[i].fn!==t&&r[i].fn._!==t&&o.push(r[i]);return o.length?n[e]=o:delete n[e],this}}const t=new WeakMap;function n(e){return t.has(e)||t.set(e,{closed:!1,epoch:0,inputEpoch:0,job:null,video:null,sourceUrl:null,thumbnailUrl:null,sourceUrls:new Set,thumbnailUrls:new Set,sourceListeners:[],loading:!1}),t.get(e)}function r(e){const t=new Error(`Thumbnail task cancelled: ${e}`);return t.name="AbortError",t}function o(e,t){t&&(e.delete(t),URL.revokeObjectURL(t))}function i(e){let t;for(const r of e)try{r()}catch(n){t||(t=n)}if(t)throw t}function s(e,t="destroyed"){var r;const o=n(e);return!o.closed&&(o.closed=!0,o.epoch++,null==(r=o.job)||r.cancel(t),!0)}function c(e){const t=n(e),r=new Set([...t.sourceUrls,e.videoUrl]),s=new Set([...t.thumbnailUrls,e.thumbnailUrl]),c=[];for(const n of new Set([t.video,e.video]))n&&c.push(()=>{var e;return null==(e=n.pause)?void 0:e.call(n)},()=>{var e;return null==(e=n.removeAttribute)?void 0:e.call(n,"src")},()=>{var e;return null==(e=n.load)?void 0:e.call(n)},()=>{var e;return null==(e=n.parentNode)?void 0:e.removeChild(n)});i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function l(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function a(e,t){let s=t.epoch;const c=e.video;let a,u,d,h,p=!1,f=!1,m=()=>{};const b=[],v=new Promise((e,t)=>{a=e,u=t});v.catch(()=>{});const y=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function g(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{g()}catch(r){t||(t=r)}if(n)try{e.emit("error",null==t?void 0:t.message)}catch(o){t=o}u(t)}}function U(t){if(!y())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!y())return;if(r===t.points.length)return void function(){if(y())try{g(),e.emit("done"),a()}catch(t){let r=t;try{e.emit("error",null==t?void 0:t.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let l=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!y()||l||c.seeking||void 0!==c.readyState&&c.readyState<2)){l=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!y())return;t.canvas.toBlob(s=>{if(y()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,y);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(a){w(a)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(y()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,a=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=l(i,0,n.duration),e.option.end=l(s||n.duration,i,n.duration),a(e.option.end>e.option.begin,"End time must be greater than the start time"),a(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,a(e.file&&n,"Please select the video file first"),a(!e.processing,"There is currently a task in progress, please wait a moment..."),a(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),y()||clearTimeout(d)}h={video:c,promise:v,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{var e;y()&&w(new Error(`Unable to load video: media error ${(null==(e=c.error)?void 0:e.code)||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.remLine truncated
|
||||
@@ -3,6 +3,33 @@
|
||||
* Github: https://github.com/zhw2590582/ArtPlayer
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*
|
||||
* ArtPlayer thumbnail tool bundled third-party notices
|
||||
*
|
||||
* tiny-emitter - reference 2.1.0, locally adapted to a TypeScript class
|
||||
* https://github.com/scottcorgan/tiny-emitter/tree/2.1.0
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2017 Scott Corgan
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
class Emitter {
|
||||
on(name, fn, ctx) {
|
||||
@@ -12,15 +39,17 @@ class Emitter {
|
||||
}
|
||||
once(name, fn, ctx) {
|
||||
const self = this;
|
||||
const callback = fn;
|
||||
function listener(...args) {
|
||||
self.off(name, listener);
|
||||
fn.apply(ctx, args);
|
||||
callback.apply(ctx, args);
|
||||
}
|
||||
listener._ = fn;
|
||||
return this.on(name, listener, ctx);
|
||||
}
|
||||
emit(name, ...data) {
|
||||
const evtArr = ((this.e || (this.e = {}))[name] || []).slice();
|
||||
const e = 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);
|
||||
}
|
||||
|
||||
+2
-2
@@ -51,7 +51,7 @@
|
||||
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports",
|
||||
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js",
|
||||
"typecheck": "node scripts/typecheck.mjs",
|
||||
"test:unit": "node --test test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js",
|
||||
"test:unit": "node --test test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js",
|
||||
"test:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs",
|
||||
"test": "yarn test:node && yarn test:baseline",
|
||||
"test:browser": "playwright test",
|
||||
@@ -83,7 +83,7 @@
|
||||
"generate:media:hls": "node scripts/generate-hls-fixture.mjs",
|
||||
"test:iframe": "node --test test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe.test.js test/iframe-lifecycle.test.js refactor/scripts/iframe-contract.test.mjs refactor/scripts/iframe-types.test.mjs",
|
||||
"test:iframe-types-package": "node refactor/scripts/iframe-package-types.mjs",
|
||||
"test:thumbnail": "node --test test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js refactor/scripts/thumbnail-contract.test.mjs"
|
||||
"test:thumbnail": "node --test test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js refactor/scripts/thumbnail-contract.test.mjs"
|
||||
},
|
||||
"browserslist": "last 1 Chrome version",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
# Thumbnail tool maintenance map
|
||||
|
||||
The runtime responsibilities are separated under PKG-TOOL-THUMB-03. Strict TS and
|
||||
published declaration work belongs to PKG-TOOL-THUMB-04. Runtime restructuring
|
||||
does not imply complete migration or release readiness.
|
||||
The runtime responsibilities were separated under PKG-TOOL-THUMB-03 and are now
|
||||
strict TypeScript under PKG-TOOL-THUMB-04. Published declarations and installed
|
||||
entrypoint checks are still in progress; runtime migration is not release readiness.
|
||||
|
||||
| File | Responsibility |
|
||||
| --- | --- |
|
||||
| src/index.js | Public class, historical method names, construction and destruction entry |
|
||||
| src/lifecycle.js | Private state, source/input generations, cancellation and resource release |
|
||||
| src/source.js | File loading, native metadata/error listeners and source/thumbnail Blob URLs |
|
||||
| src/extraction.js | Owned metadata wait and serial frame job, callback/error completion and cancellation |
|
||||
| src/input.js | Option validation/clamps, file-input wrapper creation, listener registration/replacement and release |
|
||||
| src/sheet.js | Midpoint grid, canvas/footer geometry, temporary download anchor |
|
||||
| src/emitter.js | Existing on/once/emit/off semantics; provenance review remains in 04 |
|
||||
| src/utils.js | Existing clamp, filename, sleep and serial-promise helpers |
|
||||
| src/index.ts | Public class, historical method names, construction and destruction entry |
|
||||
| src/lifecycle.ts | Private state, source/input generations, cancellation and resource release |
|
||||
| src/source.ts | File loading, native metadata/error listeners and source/thumbnail Blob URLs |
|
||||
| src/extraction.ts | Owned metadata wait and serial frame job, callback/error completion and cancellation |
|
||||
| src/input.ts | Option validation/clamps, file-input wrapper creation, listener registration/replacement and release |
|
||||
| src/sheet.ts | Midpoint grid, canvas/footer geometry, temporary download anchor |
|
||||
| src/emitter.ts | Typed local adaptation of tiny-emitter; preserves on/once/emit/off behavior |
|
||||
| src/utils.ts | Pure clamp and filename helpers; unused sleep/serial helpers removed |
|
||||
| src/types.ts | Internal option, frame, event tuple, job and lifecycle contracts; no runtime output |
|
||||
|
||||
The entry delegates to input/source/extraction/sheet; none imports the entry.
|
||||
The entry delegates to input/source/extraction/sheet. Helpers import its type only;
|
||||
the imports are erased and do not create a runtime dependency on the entry.
|
||||
Input/source use lifecycle state, extraction uses lifecycle/source, and sheet uses
|
||||
only filename calculation. There is no dependency back from helpers to the class.
|
||||
only filename calculation. types.ts contains no imports or executable state.
|
||||
Input records live in a private WeakMap, so callers replacing `option` cannot
|
||||
lose ownership of generated inputs/listeners. Normal construction preserves the
|
||||
existing instance-field order and bound inputChange/ondrop methods. DEFAULTS,
|
||||
@@ -76,6 +78,7 @@ Use Yarn and the repository scripts:
|
||||
|
||||
```sh
|
||||
yarn test:thumbnail
|
||||
yarn typecheck
|
||||
yarn build artplayer-tool-thumbnail
|
||||
yarn test:browser test/browser/thumbnail-tool.spec.js test/browser/thumbnail-native.spec.js test/browser/thumbnail-input.spec.js
|
||||
```
|
||||
@@ -96,7 +99,42 @@ Safari/WebKit evidence in 05. The independent tool example is
|
||||
`test/thumbnail-lifecycle.test.js` covers jobs, source cancellation, native errors,
|
||||
callback failures, reentrancy and private resources. Browser tests hold real PNG
|
||||
callbacks and replace a selected file during encoding; only the latest job may
|
||||
complete. Continue strict TS and installed declarations in 04; preserve old tests
|
||||
and the default delay/height boundary. The old sleep/serial helpers in utils.js are
|
||||
no longer used by extraction and can be removed during that migration.
|
||||
complete. Continue published declarations and installed consumers in 04; preserve
|
||||
old tests and the default delay/height boundary.
|
||||
See [task plan](../../refactor/plan.md) and [risk ledger](../../refactor/risks.json).
|
||||
|
||||
## Type and provenance boundaries
|
||||
|
||||
All eight executable source modules and the shared type module are checked with
|
||||
strict, noUncheckedIndexedAccess, noImplicitOverride, skipLibCheck=false and no
|
||||
ambient Node/test globals. Public fields use declare so TypeScript does not add
|
||||
early undefined properties or change constructor property order. File/URL/density
|
||||
fields retain their absence until the corresponding operation publishes them.
|
||||
Unknown option fields remain unknown, and custom string/number/symbol events keep
|
||||
an open payload boundary; built-in events have precise tuples. This source typing
|
||||
is not yet the missing public npm declaration entrypoint.
|
||||
|
||||
Assertions are limited to existing runtime boundaries: a temporary empty option
|
||||
object before setup validation; the input/wrapper conversion; event target and
|
||||
dataTransfer/files supplied by native input/drop events; private records inserted
|
||||
before lookup; nonempty/dense frame arrays; a successful 2D canvas context; and
|
||||
the old property read of message on arbitrary thrown values. These assertions
|
||||
preserve existing runtime validation/errors rather than adding new coercions.
|
||||
The constructor and destroy rollback paths also intentionally tolerate fields
|
||||
that have not yet been assigned. DOM listener casts connect known event names to
|
||||
the matching bound callbacks. Keep new assertions equally local and documented.
|
||||
|
||||
The emitter structurally corresponds to tiny-emitter 2.1.0, which the recovered
|
||||
3.5.31 manifest declared as ^2.1.0. The exact original copied revision is unknown.
|
||||
The pinned [reference files](../../refactor/baselines/thumbnail-vendor/sources.json)
|
||||
and [MIT notice](THIRD_PARTY_NOTICES) retain upstream attribution. The repository
|
||||
build includes the complete notice in main, legacy and ESM bundle headers.
|
||||
Final packed contents remain a distribution gate. Do not replace this local
|
||||
emitter with the core emitter as part of a type-only change: dispatch semantics
|
||||
and historical prototype-key handling need their own behavior review.
|
||||
|
||||
`test/thumbnail-runtime.test.js` compares upstream, recovered/historical bundles
|
||||
and the selected candidate for dispatch behavior, and checks candidate public
|
||||
descriptors and validation. `test/thumbnail-vendor.test.js` verifies fixed source
|
||||
bytes, complete notices and dist/docs equality. `test/types/thumbnail-runtime.ts`
|
||||
checks source consumers with positive cases and six rejected invalid uses.
|
||||
@@ -0,0 +1,26 @@
|
||||
ArtPlayer thumbnail tool bundled third-party notices
|
||||
|
||||
tiny-emitter - reference 2.1.0, locally adapted to a TypeScript class
|
||||
https://github.com/scottcorgan/tiny-emitter/tree/2.1.0
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Scott Corgan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -3,5 +3,32 @@
|
||||
* Github: https://github.com/zhw2590582/ArtPlayer
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*
|
||||
* ArtPlayer thumbnail tool bundled third-party notices
|
||||
*
|
||||
* tiny-emitter - reference 2.1.0, locally adapted to a TypeScript class
|
||||
* https://github.com/scottcorgan/tiny-emitter/tree/2.1.0
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2017 Scott Corgan
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this;function o(...i){r.off(e,o),t.apply(n,i)}return o._=t,this.on(e,o,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;r<n.length;r+=1)n[r].fn.apply(n[r].ctx,t);return this}off(e,t){const n=this.e||(this.e={}),r=n[e],o=[];if(r&&t)for(let i=0,s=r.length;i<s;i+=1)r[i].fn!==t&&r[i].fn._!==t&&o.push(r[i]);return o.length?n[e]=o:delete n[e],this}}const t=new WeakMap;function n(e){return t.has(e)||t.set(e,{closed:!1,epoch:0,inputEpoch:0,job:null,video:null,sourceUrl:null,thumbnailUrl:null,sourceUrls:new Set,thumbnailUrls:new Set,sourceListeners:[],loading:!1}),t.get(e)}function r(e){const t=new Error(`Thumbnail task cancelled: ${e}`);return t.name="AbortError",t}function o(e,t){t&&(e.delete(t),URL.revokeObjectURL(t))}function i(e){let t;for(const r of e)try{r()}catch(n){t||(t=n)}if(t)throw t}function s(e,t="destroyed"){const r=n(e);return!r.closed&&(r.closed=!0,r.epoch++,r.job?.cancel(t),!0)}function c(e){const t=n(e),r=new Set([...t.sourceUrls,e.videoUrl]),s=new Set([...t.thumbnailUrls,e.thumbnailUrl]),c=[];for(const n of new Set([t.video,e.video]))n&&c.push(()=>n.pause?.(),()=>n.removeAttribute?.("src"),()=>n.load?.(),()=>n.parentNode?.removeChild(n));i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function a(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function l(e,t){let s=t.epoch;const c=e.video;let l,u,d,h,p=!1,f=!1,m=()=>{};const b=[],y=new Promise((e,t)=>{l=e,u=t});y.catch(()=>{});const g=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function v(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{v()}catch(r){t||(t=r)}if(n)try{e.emit("error",t?.message)}catch(o){t=o}u(t)}}function U(t){if(!g())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!g())return;if(r===t.points.length)return void function(){if(g())try{v(),e.emit("done"),l()}catch(t){let r=t;try{e.emit("error",t?.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let a=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!g()||a||c.seeking||void 0!==c.readyState&&c.readyState<2)){a=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!g())return;t.canvas.toBlob(s=>{if(g()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,g);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(l){w(l)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(g()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,l=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=a(i,0,n.duration),e.option.end=a(s||n.duration,i,n.duration),l(e.option.end>e.option.begin,"End time must be greater than the start time"),l(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,l(e.file&&n,"Please select the video file first"),l(!e.processing,"There is currently a task in progress, please wait a moment..."),l(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),g()||clearTimeout(d)}h={video:c,promise:y,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{g()&&w(new Error(`Unable to load video: media error ${c.error?.code||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.removeEventListener(e,t)),c.addEventListener(e,t),!g())return i([()=>c.removeEventListener(e,t),...b.splice(0)]),y;c.error?w(new Error(`Unable to load video: media error ${c.error.code}`)):E(!0)}catch(j){throw w(j,!1),j}return
Line truncated
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this,o=t;function i(...t){r.off(e,i),o.apply(n,t)}return i._=t,this.on(e,i,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;r<n.length;r+=1)n[r].fn.apply(n[r].ctx,t);return this}off(e,t){const n=this.e||(this.e={}),r=n[e],o=[];if(r&&t)for(let i=0,s=r.length;i<s;i+=1)r[i].fn!==t&&r[i].fn._!==t&&o.push(r[i]);return o.length?n[e]=o:delete n[e],this}}const t=new WeakMap;function n(e){return t.has(e)||t.set(e,{closed:!1,epoch:0,inputEpoch:0,job:null,video:null,sourceUrl:null,thumbnailUrl:null,sourceUrls:new Set,thumbnailUrls:new Set,sourceListeners:[],loading:!1}),t.get(e)}function r(e){const t=new Error(`Thumbnail task cancelled: ${e}`);return t.name="AbortError",t}function o(e,t){t&&(e.delete(t),URL.revokeObjectURL(t))}function i(e){let t;for(const r of e)try{r()}catch(n){t||(t=n)}if(t)throw t}function s(e,t="destroyed"){const r=n(e);return!r.closed&&(r.closed=!0,r.epoch++,r.job?.cancel(t),!0)}function c(e){const t=n(e),r=new Set([...t.sourceUrls,e.videoUrl]),s=new Set([...t.thumbnailUrls,e.thumbnailUrl]),c=[];for(const n of new Set([t.video,e.video]))n&&c.push(()=>n.pause?.(),()=>n.removeAttribute?.("src"),()=>n.load?.(),()=>n.parentNode?.removeChild(n));i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function a(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function l(e,t){let s=t.epoch;const c=e.video;let l,u,d,h,p=!1,f=!1,m=()=>{};const b=[],y=new Promise((e,t)=>{l=e,u=t});y.catch(()=>{});const g=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function v(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{v()}catch(r){t||(t=r)}if(n)try{e.emit("error",t?.message)}catch(o){t=o}u(t)}}function U(t){if(!g())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!g())return;if(r===t.points.length)return void function(){if(g())try{v(),e.emit("done"),l()}catch(t){let r=t;try{e.emit("error",t?.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let a=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!g()||a||c.seeking||void 0!==c.readyState&&c.readyState<2)){a=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!g())return;t.canvas.toBlob(s=>{if(g()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,g);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(l){w(l)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(g()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,l=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=a(i,0,n.duration),e.option.end=a(s||n.duration,i,n.duration),l(e.option.end>e.option.begin,"End time must be greater than the start time"),l(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,l(e.file&&n,"Please select the video file first"),l(!e.processing,"There is currently a task in progress, please wait a moment..."),l(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),g()||clearTimeout(d)}h={video:c,promise:y,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{g()&&w(new Error(`Unable to load video: media error ${c.error?.code||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.removeEventListener(e,t)),c.addEventListener(e,t),!g())return i([()=>c.removeEventListener(e,t),...b.splice(0)]),y;c.error?w(new Error(`Unable to load video: media error ${c.error.code}`)):E(!0)}catch(j){throw w(j,!1),j}retLine truncated
|
||||
@@ -3,5 +3,32 @@
|
||||
* Github: https://github.com/zhw2590582/ArtPlayer
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*
|
||||
* ArtPlayer thumbnail tool bundled third-party notices
|
||||
*
|
||||
* tiny-emitter - reference 2.1.0, locally adapted to a TypeScript class
|
||||
* https://github.com/scottcorgan/tiny-emitter/tree/2.1.0
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2017 Scott Corgan
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this;function o(...i){r.off(e,o),t.apply(n,i)}return o._=t,this.on(e,o,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;r<n.length;r+=1)n[r].fn.apply(n[r].ctx,t);return this}off(e,t){const n=this.e||(this.e={}),r=n[e],o=[];if(r&&t)for(let i=0,s=r.length;i<s;i+=1)r[i].fn!==t&&r[i].fn._!==t&&o.push(r[i]);return o.length?n[e]=o:delete n[e],this}}const t=new WeakMap;function n(e){return t.has(e)||t.set(e,{closed:!1,epoch:0,inputEpoch:0,job:null,video:null,sourceUrl:null,thumbnailUrl:null,sourceUrls:new Set,thumbnailUrls:new Set,sourceListeners:[],loading:!1}),t.get(e)}function r(e){const t=new Error(`Thumbnail task cancelled: ${e}`);return t.name="AbortError",t}function o(e,t){t&&(e.delete(t),URL.revokeObjectURL(t))}function i(e){let t;for(const r of e)try{r()}catch(n){t||(t=n)}if(t)throw t}function s(e,t="destroyed"){var r;const o=n(e);return!o.closed&&(o.closed=!0,o.epoch++,null==(r=o.job)||r.cancel(t),!0)}function c(e){const t=n(e),r=new Set([...t.sourceUrls,e.videoUrl]),s=new Set([...t.thumbnailUrls,e.thumbnailUrl]),c=[];for(const n of new Set([t.video,e.video]))n&&c.push(()=>{var e;return null==(e=n.pause)?void 0:e.call(n)},()=>{var e;return null==(e=n.removeAttribute)?void 0:e.call(n,"src")},()=>{var e;return null==(e=n.load)?void 0:e.call(n)},()=>{var e;return null==(e=n.parentNode)?void 0:e.removeChild(n)});i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function l(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function a(e,t){let s=t.epoch;const c=e.video;let a,u,d,h,p=!1,f=!1,m=()=>{};const b=[],v=new Promise((e,t)=>{a=e,u=t});v.catch(()=>{});const y=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function g(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{g()}catch(r){t||(t=r)}if(n)try{e.emit("error",null==t?void 0:t.message)}catch(o){t=o}u(t)}}function U(t){if(!y())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!y())return;if(r===t.points.length)return void function(){if(y())try{g(),e.emit("done"),a()}catch(t){let r=t;try{e.emit("error",null==t?void 0:t.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let l=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!y()||l||c.seeking||void 0!==c.readyState&&c.readyState<2)){l=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!y())return;t.canvas.toBlob(s=>{if(y()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,y);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(a){w(a)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(y()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,a=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=l(i,0,n.duration),e.option.end=l(s||n.duration,i,n.duration),a(e.option.end>e.option.begin,"End time must be greater than the start time"),a(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,a(e.file&&n,"Please select the video file first"),a(!e.processing,"There is currently a task in progress, please wait a moment..."),a(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),y()||clearTimeout(d)}h={video:c,promise:v,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{var e;y()&&w(new Error(`Unable to load video: media error ${(null==(e=c.error)?void 0:e.code)||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.removeELine truncated
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this,o=t;function i(...t){r.off(e,i),o.apply(n,t)}return i._=t,this.on(e,i,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;r<n.length;r+=1)n[r].fn.apply(n[r].ctx,t);return this}off(e,t){const n=this.e||(this.e={}),r=n[e],o=[];if(r&&t)for(let i=0,s=r.length;i<s;i+=1)r[i].fn!==t&&r[i].fn._!==t&&o.push(r[i]);return o.length?n[e]=o:delete n[e],this}}const t=new WeakMap;function n(e){return t.has(e)||t.set(e,{closed:!1,epoch:0,inputEpoch:0,job:null,video:null,sourceUrl:null,thumbnailUrl:null,sourceUrls:new Set,thumbnailUrls:new Set,sourceListeners:[],loading:!1}),t.get(e)}function r(e){const t=new Error(`Thumbnail task cancelled: ${e}`);return t.name="AbortError",t}function o(e,t){t&&(e.delete(t),URL.revokeObjectURL(t))}function i(e){let t;for(const r of e)try{r()}catch(n){t||(t=n)}if(t)throw t}function s(e,t="destroyed"){var r;const o=n(e);return!o.closed&&(o.closed=!0,o.epoch++,null==(r=o.job)||r.cancel(t),!0)}function c(e){const t=n(e),r=new Set([...t.sourceUrls,e.videoUrl]),s=new Set([...t.thumbnailUrls,e.thumbnailUrl]),c=[];for(const n of new Set([t.video,e.video]))n&&c.push(()=>{var e;return null==(e=n.pause)?void 0:e.call(n)},()=>{var e;return null==(e=n.removeAttribute)?void 0:e.call(n,"src")},()=>{var e;return null==(e=n.load)?void 0:e.call(n)},()=>{var e;return null==(e=n.parentNode)?void 0:e.removeChild(n)});i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function l(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function a(e,t){let s=t.epoch;const c=e.video;let a,u,d,h,p=!1,f=!1,m=()=>{};const b=[],v=new Promise((e,t)=>{a=e,u=t});v.catch(()=>{});const y=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function g(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{g()}catch(r){t||(t=r)}if(n)try{e.emit("error",null==t?void 0:t.message)}catch(o){t=o}u(t)}}function U(t){if(!y())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!y())return;if(r===t.points.length)return void function(){if(y())try{g(),e.emit("done"),a()}catch(t){let r=t;try{e.emit("error",null==t?void 0:t.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let l=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!y()||l||c.seeking||void 0!==c.readyState&&c.readyState<2)){l=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!y())return;t.canvas.toBlob(s=>{if(y()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,y);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(a){w(a)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(y()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,a=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=l(i,0,n.duration),e.option.end=l(s||n.duration,i,n.duration),a(e.option.end>e.option.begin,"End time must be greater than the start time"),a(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,a(e.file&&n,"Please select the video file first"),a(!e.processing,"There is currently a task in progress, please wait a moment..."),a(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),y()||clearTimeout(d)}h={video:c,promise:v,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{var e;y()&&w(new Error(`Unable to load video: media error ${(null==(e=c.error)?void 0:e.code)||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.remLine truncated
|
||||
@@ -3,6 +3,33 @@
|
||||
* Github: https://github.com/zhw2590582/ArtPlayer
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*
|
||||
* ArtPlayer thumbnail tool bundled third-party notices
|
||||
*
|
||||
* tiny-emitter - reference 2.1.0, locally adapted to a TypeScript class
|
||||
* https://github.com/scottcorgan/tiny-emitter/tree/2.1.0
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2017 Scott Corgan
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
class Emitter {
|
||||
on(name, fn, ctx) {
|
||||
@@ -12,15 +39,17 @@ class Emitter {
|
||||
}
|
||||
once(name, fn, ctx) {
|
||||
const self = this;
|
||||
const callback = fn;
|
||||
function listener(...args) {
|
||||
self.off(name, listener);
|
||||
fn.apply(ctx, args);
|
||||
callback.apply(ctx, args);
|
||||
}
|
||||
listener._ = fn;
|
||||
return this.on(name, listener, ctx);
|
||||
}
|
||||
emit(name, ...data) {
|
||||
const evtArr = ((this.e || (this.e = {}))[name] || []).slice();
|
||||
const e = 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);
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": { "allowJs": false },
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"reviewedAt": "2026-09-12T20:36:43.265Z",
|
||||
"referenceVersion": "2.1.0",
|
||||
"sources": [
|
||||
{
|
||||
"file": "refactor/baselines/thumbnail-vendor/tiny-emitter-2.1.0.txt",
|
||||
"url": "https://raw.githubusercontent.com/scottcorgan/tiny-emitter/2.1.0/index.js",
|
||||
"sha256": "3e3679233f20177d63d253e7c2645024fef51731d05df15621c62a48ba98e0d3"
|
||||
},
|
||||
{
|
||||
"file": "refactor/baselines/thumbnail-vendor/tiny-emitter-license.txt",
|
||||
"url": "https://raw.githubusercontent.com/scottcorgan/tiny-emitter/2.1.0/LICENSE",
|
||||
"sha256": "aada4c7da6a5be46beadf9d4f40d7dba9e222e57180d46289b2bb9112e65daae"
|
||||
},
|
||||
{
|
||||
"file": "refactor/baselines/thumbnail-vendor/tiny-emitter-package.json",
|
||||
"url": "https://raw.githubusercontent.com/scottcorgan/tiny-emitter/2.1.0/package.json",
|
||||
"sha256": "f5328afd1e742ba9b107e316e5fc4c74ec81201bafad8eb8e6dc456ef6d628de"
|
||||
}
|
||||
],
|
||||
"limitation": "Pinned upstream comparison reference. Structural correspondence and the recovered 3.5.31 dependency establish attribution, not the exact original copy revision or complete npm archive."
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
function E () {
|
||||
// Keep this empty so it's easier to inherit from
|
||||
// (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
|
||||
}
|
||||
|
||||
E.prototype = {
|
||||
on: function (name, callback, ctx) {
|
||||
var e = this.e || (this.e = {});
|
||||
|
||||
(e[name] || (e[name] = [])).push({
|
||||
fn: callback,
|
||||
ctx: ctx
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
once: function (name, callback, ctx) {
|
||||
var self = this;
|
||||
function listener () {
|
||||
self.off(name, listener);
|
||||
callback.apply(ctx, arguments);
|
||||
};
|
||||
|
||||
listener._ = callback
|
||||
return this.on(name, listener, ctx);
|
||||
},
|
||||
|
||||
emit: function (name) {
|
||||
var data = [].slice.call(arguments, 1);
|
||||
var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
|
||||
var i = 0;
|
||||
var len = evtArr.length;
|
||||
|
||||
for (i; i < len; i++) {
|
||||
evtArr[i].fn.apply(evtArr[i].ctx, data);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
off: function (name, callback) {
|
||||
var e = this.e || (this.e = {});
|
||||
var evts = e[name];
|
||||
var liveEvents = [];
|
||||
|
||||
if (evts && callback) {
|
||||
for (var i = 0, len = evts.length; i < len; i++) {
|
||||
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
|
||||
liveEvents.push(evts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove event from queue to prevent memory leak
|
||||
// Suggested by https://github.com/lazd
|
||||
// Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
|
||||
|
||||
(liveEvents.length)
|
||||
? e[name] = liveEvents
|
||||
: delete e[name];
|
||||
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = E;
|
||||
module.exports.TinyEmitter = E;
|
||||
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Scott Corgan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "tiny-emitter",
|
||||
"version": "2.1.0",
|
||||
"description": "A tiny (less than 1k) event emitter library",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test-node": "tape test/index.js | tap-format-spec",
|
||||
"test": "testling | tap-format-spec",
|
||||
"bundle": "node_modules/.bin/browserify index.js > dist/tinyemitter.js -s TinyEmitter && echo 'Bundled'",
|
||||
"minify": "node_modules/.bin/uglifyjs dist/tinyemitter.js -o dist/tinyemitter.min.js -m && echo 'Minified'",
|
||||
"build": "npm test && npm run bundle && npm run minify",
|
||||
"size": "node_modules/.bin/uglifyjs index.js -o minified.js -m && ls -l && rm minified.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/scottcorgan/tiny-emitter.git"
|
||||
},
|
||||
"keywords": [
|
||||
"event",
|
||||
"emitter",
|
||||
"pubsub",
|
||||
"tiny",
|
||||
"events",
|
||||
"bind"
|
||||
],
|
||||
"author": "Scott Corgan",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/scottcorgan/tiny-emitter/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tap-format/spec": "0.2.0",
|
||||
"browserify": "11.2.0",
|
||||
"tape": "4.2.1",
|
||||
"testling": "1.7.1",
|
||||
"uglify-js": "2.5.0"
|
||||
},
|
||||
"testling": {
|
||||
"files": [
|
||||
"test/index.js"
|
||||
],
|
||||
"browsers": [
|
||||
"iexplore/10.0",
|
||||
"iexplore/9.0",
|
||||
"firefox/16..latest",
|
||||
"chrome/22..latest",
|
||||
"safari/5.1..latest",
|
||||
"ipad/6.0..latest",
|
||||
"iphone/6.0..latest",
|
||||
"android-browser/4.2..latest"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
# PKG-TOOL-THUMB-04 runtime TypeScript checkpoint
|
||||
|
||||
Status: doing. This checkpoint migrates executable source and records emitter
|
||||
attribution. It does not complete public declarations, installed entrypoints or
|
||||
the task's compatibility policy gate.
|
||||
|
||||
## Source and compatibility
|
||||
|
||||
Starts at efd9f42fb984302dd061d5cd9f96bd4de74fcccf. Eight runtime JS modules become
|
||||
strict TS, with a ninth type-only module for configuration, events, frame points,
|
||||
job controls and lifecycle state. The input and source WeakMaps remain private;
|
||||
helpers import the facade only as an erased type. Existing runtime validation is
|
||||
retained. There are no ts-ignore, ts-nocheck or broad any escape hatches. Local
|
||||
assertions and initialization invariants are explained in the package architecture
|
||||
map. Unused sleep and serial-promise helpers are removed; neither was reachable
|
||||
from the public bundle or used by the owned extraction job.
|
||||
|
||||
declare fields preserve the old own-property order and lazy optional fields.
|
||||
Normal class exports, historical creat* spellings, synchronous validation,
|
||||
Promise<void> completion, cancellation and the workspace 4.4.0 defaults remain.
|
||||
Source options accept Element wrappers and unknown extension fields. Known event
|
||||
tuples are checked and unknown string/number/symbol events remain possible.
|
||||
The source types are internal until the package's declaration entry is supplied.
|
||||
|
||||
## Emitter origin
|
||||
|
||||
Pinned upstream comparison: tiny-emitter 2.1.0 index.js, package.json and LICENSE,
|
||||
fetched from its fixed Git tag and recorded with SHA-256. Its four methods and
|
||||
listener registry structure correspond to the local class; the recovered 3.5.31
|
||||
manifest also declared tiny-emitter ^2.1.0. This establishes the appropriate
|
||||
attribution, not an exact original copied revision or a recovered complete npm
|
||||
archive. Changes from that comparison reference are the existing class/ES module
|
||||
conversion, rest arguments, local names and loop/style forms, plus this migration's
|
||||
erased generic types and equivalent typed local aliases.
|
||||
|
||||
The complete upstream MIT notice is retained in package THIRD_PARTY_NOTICES and
|
||||
the existing normal build banner includes it in all three standalone artifacts.
|
||||
No new runtime dependency is introduced. VENDOR-09 records final packed-content
|
||||
verification still outstanding; THUMB-PROVENANCE-01 retains the historical archive
|
||||
limit. Source attribution must not be replaced with the repository author's name.
|
||||
|
||||
## Validation
|
||||
|
||||
The validation JSON for this checkpoint records final commands, file hashes and
|
||||
browser outcomes. Six new runtime tests check public descriptors/defaults/unknown
|
||||
options/validation and compare upstream, recovered 3.5.31, frozen workspace main
|
||||
and legacy, and the selected candidate for emitter snapshot/removal/ctx/symbol/
|
||||
exception behavior. Two provenance tests verify fixed upstream bytes and full
|
||||
notice retention in package and dist/docs copies. Existing lifecycle/input tests
|
||||
remain unchanged. Source type consumers include six negative uses.
|
||||
|
||||
Browser runs retain native media controls. Windows WebKit Blob decoding remains
|
||||
an unavailable-capability control, not successful thumbnail extraction. No new
|
||||
coverage percentage, physical Safari, remote GitHub CI or npm result is claimed.
|
||||
|
||||
## Continue / rollback
|
||||
|
||||
Continue 04 with the missing public declaration, legacy compiler and isolated
|
||||
installed CommonJS/ESM consumers. Resolve or explicitly request a decision for
|
||||
THUMB-COMPAT-01 (recovered 3.5.31 delay/fixed height versus workspace 4.4.0 defaults)
|
||||
before claiming compatibility closure. Review custom emitter typing and dispatch
|
||||
boundaries alongside those consumers. BASE-DIST-01 still covers the missing
|
||||
promised .esm.js and types entries; task 06 owns final distribution acceptance.
|
||||
05/06 retain supported WebKit/Safari file extraction and core integration/demo
|
||||
verification. No manifest entry, version, lockfile or package manager changes in
|
||||
this checkpoint. No push, tag or publication.
|
||||
|
||||
To roll back, revert this checkpoint commit and rebuild the tool using the normal
|
||||
Yarn package build. Preserve upstream provenance/notice records for any retained
|
||||
copy of the emitter. Do not revert the separate 03 resource fixes accidentally.
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 222 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 100 / doing 10 / blocked 0 / done 112 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 99 / doing 11 / blocked 0 / done 112 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -358,7 +358,7 @@
|
||||
| PKG-TOOL-THUMB-01 | artplayer-tool-thumbnail<br>核对包契约与历史用法 | BASE-05 | 构造/defaults/事件/方法/历史拼写、.esm.js 入口和缺失 types | 源码/声明/README/demo/可恢复发布内容差异已登记;公开形状和版本范围冻结;区分旧 npm 的 main/CSS 与当前工作区无 main、.esm.js/类型缺失,不把工作区状态冒称旧 npm 发布事实 | H | done |
|
||||
| PKG-TOOL-THUMB-02 | artplayer-tool-thumbnail<br>建立特有行为与错误测试 | PKG-TOOL-THUMB-01, ENG-03, ENG-05 | 文件输入/拖放、抽帧网格、begin/end、进度/失败/重复任务 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
|
||||
| PKG-TOOL-THUMB-03 | artplayer-tool-thumbnail<br>整理内部职责与资源 | PKG-TOOL-THUMB-02, CORE-01 | 输入、抽帧队列、网格导出、URL/监听清理分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
|
||||
| PKG-TOOL-THUMB-04 | artplayer-tool-thumbnail<br>迁移自有源码和公开类型 | PKG-TOOL-THUMB-03, ENG-04, ENG-06, CORE-07 | 补齐真实 API 声明,自有 emitter/utils TS 化 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容;为 BASE-DIST-01 提供兼容入口/声明及消费者验证 | H | todo |
|
||||
| PKG-TOOL-THUMB-04 | artplayer-tool-thumbnail<br>迁移自有源码和公开类型 | PKG-TOOL-THUMB-03, ENG-04, ENG-06, CORE-07 | 补齐真实 API 声明,自有 emitter/utils TS 化 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容;为 BASE-DIST-01 提供兼容入口/声明及消费者验证 | H | doing |
|
||||
| PKG-TOOL-THUMB-05 | artplayer-tool-thumbnail<br>验证新旧核心和组合 | PKG-TOOL-THUMB-04, CORE-22 | 工具独立浏览器使用,生成缩略图在核心中显示 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
| PKG-TOOL-THUMB-06 | artplayer-tool-thumbnail<br>验证分发并同步文档 | PKG-TOOL-THUMB-05, ENG-07 | tool.thumbnail.js、历史 ESM 兼容文件、types 路径和 tarball | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
|
||||
|
||||
@@ -538,4 +538,5 @@
|
||||
- PKG-TOOL-THUMB-01: [记录](baselines/thumbnail-release.json) [记录](baselines/thumbnail-contract.md) [记录](changes/2026-09-13-PKG-TOOL-THUMB-01-contract.md) [记录](baselines/thumbnail-contract-validation.json)
|
||||
- PKG-TOOL-THUMB-02: [记录](baselines/thumbnail-behavior-validation.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-02-behavior.md)
|
||||
- PKG-TOOL-THUMB-03: [记录](changes/2026-09-13-PKG-TOOL-THUMB-03-input-checkpoint.md) [记录](baselines/thumbnail-input-checkpoint.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-03-lifecycle.md) [记录](baselines/thumbnail-lifecycle-validation.json)
|
||||
- PKG-TOOL-THUMB-04: [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md) [记录](baselines/thumbnail-runtime-types-validation.json)
|
||||
- PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.json) [记录](baselines/factory-compatibility-proposals.json) [记录](factory-compatibility-decision.md) [记录](changes/2026-09-12-PKG-FACTORY-01-decision.md)
|
||||
@@ -1,5 +1,19 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-TOOL-THUMB-04 运行时 TS 与 emitter 来源检查点(doing)
|
||||
|
||||
8个运行时模块已迁为严格TS,新增类型模块明确option/事件/帧/job/资源状态;declare保留
|
||||
旧实例字段顺序和按需出现的字段。移除未使用的sleep/serial助手。生成ESM除许可和两个
|
||||
等价emitter局部变量外与03运行时相同,默认值和同步/异步行为保持。固定tiny-emitter
|
||||
2.1.0上游比较文件及完整MIT通知,登记VENDOR-09;不声称确定原始复制revision。
|
||||
新增6项门面/emitter兼容与2项来源/通知检查,源码/main/legacy各110项通过。
|
||||
三份浏览器各57通过(34真实工具、3输入、17Blob不可用、3原生媒体对照)。
|
||||
完整CI1654通过,339生产TS和3导入通过;保留既有编辑器声明lint警告及Yarn警告记录。
|
||||
公开声明/旧编译器/实际安装入口和默认历史差异仍待04继续,05/06保留真实设备/分发门槛。
|
||||
见[变更](changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md)和
|
||||
[验证](baselines/thumbnail-runtime-types-validation.json)。222项:112 done、11 doing、99 todo。
|
||||
|
||||
|
||||
## PKG-TOOL-THUMB-03 完成:抽帧任务与媒体资源所有权
|
||||
|
||||
增加lifecycle/source/extraction模块,保留公开类形状与工作区默认行为。元数据等待、
|
||||
|
||||
@@ -181,3 +181,4 @@
|
||||
| THUMB-LIFE-01 | open / 已复现 | Thumbnail drop binding and repeated destroy fail; pending work and replacement resource ownership need repair | PKG-TOOL-THUMB-02, PKG-TOOL-THUMB-03, PKG-TOOL-THUMB-05 |
|
||||
| THUMB-PROVENANCE-01 | open / 源码/产物事实 | Thumbnail local emitter attribution and historical distribution completeness remain unverified | PKG-TOOL-THUMB-04, PKG-TOOL-THUMB-06 |
|
||||
| THUMB-MEDIA-01 | open / 已复现 | Windows WebKit native Blob URLs cannot load tested MP4 media despite successful HTTP controls | PKG-TOOL-THUMB-05, PKG-TOOL-THUMB-06 |
|
||||
| VENDOR-09 | open / 源码/产物事实 | Thumbnail tiny-emitter 改写来源与最终分发许可 | PKG-TOOL-THUMB-04, PKG-TOOL-THUMB-06 |
|
||||
@@ -3934,6 +3934,25 @@
|
||||
],
|
||||
"compatibleResolution": "Preserve independent HTTP/Blob capability controls; obtain actual supported WebKit/Safari file extraction evidence before release acceptance. Do not count the 12 unavailable controls as successful thumbnail generation or silently replace local files with HTTP media.",
|
||||
"closureCriteria": "Actual generated PNG/extraction and lifecycle proof in supported Safari/WebKit environments, or explicit supported-environment decision with migration documentation; Windows limitation stays identifiable."
|
||||
},
|
||||
{
|
||||
"id": "VENDOR-09",
|
||||
"title": "Thumbnail tiny-emitter 改写来源与最终分发许可",
|
||||
"confirmation": "source-observed",
|
||||
"status": "open",
|
||||
"owners": [
|
||||
"PKG-TOOL-THUMB-04",
|
||||
"PKG-TOOL-THUMB-06"
|
||||
],
|
||||
"evidence": [
|
||||
"refactor/baselines/thumbnail-vendor/sources.json",
|
||||
"packages/artplayer-tool-thumbnail/src/emitter.ts",
|
||||
"packages/artplayer-tool-thumbnail/THIRD_PARTY_NOTICES",
|
||||
"test/thumbnail-vendor.test.js"
|
||||
],
|
||||
"compatibleResolution": "Preserve original attribution, typed local dispatch compatibility and complete MIT notice in source/package/standalone bundles.",
|
||||
"closureCriteria": "Pinned upstream reference and local adaptation reviewed; all bundle and actual packed distribution notices verified. Do not claim the exact original copied revision or a recovered complete historical archive.",
|
||||
"workspaceState": "TS adaptation and pinned 2.1.0 source/license references recorded; normal build includes package notices. Final installed/packed distribution evidence remains with 04/06."
|
||||
}
|
||||
]
|
||||
}
|
||||
+5
-2
@@ -3809,11 +3809,14 @@
|
||||
"ENG-06",
|
||||
"CORE-07"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "doing",
|
||||
"risk": "H",
|
||||
"deliverable": "补齐真实 API 声明,自有 emitter/utils TS 化",
|
||||
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容;为 BASE-DIST-01 提供兼容入口/声明及消费者验证",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md",
|
||||
"baselines/thumbnail-runtime-types-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-TOOL-THUMB-05",
|
||||
|
||||
@@ -1025,6 +1025,39 @@
|
||||
"sha256": "e00bbf82bf08c452825690372ded39a9b60f4baf438f0e7ce8300e9bc4629c03"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "thumbnail-tiny-emitter",
|
||||
"riskId": "VENDOR-09",
|
||||
"inputs": [
|
||||
"packages/artplayer-tool-thumbnail/src/emitter.ts"
|
||||
],
|
||||
"owners": [
|
||||
"PKG-TOOL-THUMB-04",
|
||||
"PKG-TOOL-THUMB-06"
|
||||
],
|
||||
"sourceStatus": "Local on/once/emit/off structurally correspond to the fixed tiny-emitter 2.1.0 reference; recovered 3.5.31 declared ^2.1.0. Exact original copied revision is unknown. This migration adds types while preserving the local class and dispatch behavior.",
|
||||
"licenseStatus": "Pinned upstream MIT notice copied verbatim into package THIRD_PARTY_NOTICES and included by the normal standalone build banner. Final packed distribution verification remains 06.",
|
||||
"upstreamSources": [
|
||||
"https://raw.githubusercontent.com/scottcorgan/tiny-emitter/2.1.0/index.js",
|
||||
"https://raw.githubusercontent.com/scottcorgan/tiny-emitter/2.1.0/LICENSE",
|
||||
"https://raw.githubusercontent.com/scottcorgan/tiny-emitter/2.1.0/package.json"
|
||||
],
|
||||
"upstreamReviewedAt": "2026-09-13",
|
||||
"updatePolicy": "Keep attribution and pinned upstream comparison. Review observable dispatch changes independently from type migration; run upstream/historical/candidate parity tests and validate all bundle and packed notices.",
|
||||
"fingerprints": [
|
||||
{
|
||||
"path": "packages/artplayer-tool-thumbnail/src/emitter.ts",
|
||||
"algorithm": "sha256-lf",
|
||||
"sha256": "2844fa8d279d35d676f61eef4f8a3e173146548c64b8e49f2dabfc25aec56668"
|
||||
}
|
||||
],
|
||||
"reviewEvidence": [
|
||||
"refactor/baselines/thumbnail-vendor/sources.json",
|
||||
"test/thumbnail-runtime.test.js",
|
||||
"test/thumbnail-vendor.test.js",
|
||||
"refactor/changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md"
|
||||
]
|
||||
}
|
||||
],
|
||||
"integrations": [
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Runtime compatibility tests.
|
||||
import test from 'node:test'
|
||||
import vm from 'node:vm'
|
||||
import { thumbnailCandidate, thumbnailEnvironment, thumbnailHistorical } from './helpers/thumbnail.js'
|
||||
|
||||
const candidate = await thumbnailCandidate()
|
||||
function create(implementation) {
|
||||
const env = thumbnailEnvironment(implementation)
|
||||
return { ...env, tool: new env.Factory({ fileInput: new env.Element('input') }) }
|
||||
}
|
||||
const referenceModule = { exports: {} }
|
||||
vm.runInNewContext(fs.readFileSync(new URL('../refactor/baselines/thumbnail-vendor/tiny-emitter-2.1.0.txt', import.meta.url), 'utf8'), { module: referenceModule })
|
||||
const Upstream = referenceModule.exports
|
||||
const upstream = () => new Upstream()
|
||||
const emitters = [upstream, ...thumbnailHistorical().map(implementation => () => create(implementation).tool), () => create(candidate).tool]
|
||||
|
||||
test('Thumbnail typed facade preserves descriptors, lazy fields, default class and unknown options', () => {
|
||||
const { tool, Factory, exported } = create(candidate)
|
||||
const baseline = create(thumbnailHistorical().find(item => item.name === 'workspace.js'))
|
||||
const describe = object => Object.fromEntries(Object.entries(Object.getOwnPropertyDescriptors(object)).map(([name, value]) => [name, {
|
||||
enumerable: value.enumerable,
|
||||
configurable: value.configurable,
|
||||
writable: value.writable,
|
||||
getter: typeof value.get,
|
||||
setter: typeof value.set,
|
||||
type: typeof value.value,
|
||||
arity: typeof value.value === 'function' ? value.value.length : undefined,
|
||||
}]))
|
||||
assert.deepEqual(describe(Factory.prototype), describe(baseline.Factory.prototype))
|
||||
assert.deepEqual(describe(Factory), describe(baseline.Factory))
|
||||
assert.deepEqual(describe(tool), describe(baseline.tool))
|
||||
assert.deepEqual(Object.keys(tool), ['processing', 'option', 'video', 'duration', 'inputChange', 'ondrop'])
|
||||
assert.equal(exported.default, undefined)
|
||||
assert.equal(typeof thumbnailEnvironment(candidate, true).exported, 'function')
|
||||
const metadata = { id: 123 }
|
||||
assert.equal(tool.setup({ custom: metadata, width: 21.5 }), tool)
|
||||
assert.equal(tool.option.custom, metadata)
|
||||
assert.equal(tool.option.width, 21.5)
|
||||
assert.notEqual(Factory.DEFAULTS, Factory.DEFAULTS)
|
||||
assert.deepEqual(Object.keys(Factory.DEFAULTS), Object.keys(baseline.Factory.DEFAULTS))
|
||||
assert(Number.isNaN(Factory.DEFAULTS.end))
|
||||
tool.destroy()
|
||||
})
|
||||
|
||||
test('Thumbnail emitter keeps upstream snapshot order, duplicate removal and original once callback identity', () => {
|
||||
for (const create of emitters) {
|
||||
const emitter = create()
|
||||
const seen = []
|
||||
const callback = value => seen.push(value)
|
||||
assert.equal(emitter.on('value', () => emitter.off('value', callback)), emitter)
|
||||
emitter.on('value', callback).once('value', callback).on('value', callback)
|
||||
emitter.emit('value', 7).emit('value', 8)
|
||||
assert.deepEqual(seen, [7, 7, 7])
|
||||
assert.equal(emitter.off('value'), emitter)
|
||||
assert.equal(emitter.e.value, undefined)
|
||||
emitter.once('removed', callback).off('removed', callback).emit('removed', 9)
|
||||
assert.deepEqual(seen, [7, 7, 7])
|
||||
}
|
||||
})
|
||||
|
||||
test('Thumbnail emitter preserves ctx, variadic arguments, symbol events and ignored callback returns', () => {
|
||||
for (const create of emitters) {
|
||||
const emitter = create()
|
||||
const ctx = { calls: 0 }
|
||||
const name = Symbol('event')
|
||||
function callback(...args) {
|
||||
assert.equal(this, ctx)
|
||||
assert.deepEqual(args, [1, 'two', null])
|
||||
this.calls++
|
||||
return 123
|
||||
}
|
||||
assert.equal(emitter.on(name, callback, ctx), emitter)
|
||||
assert.equal(emitter.once(name, callback, ctx), emitter)
|
||||
assert.equal(emitter.emit(name, 1, 'two', null), emitter)
|
||||
assert.equal(ctx.calls, 2)
|
||||
emitter.off(name, callback).emit(name)
|
||||
assert.equal(ctx.calls, 2)
|
||||
}
|
||||
})
|
||||
|
||||
test('Thumbnail emitter synchronous listener failures retain identity and stop the current snapshot', () => {
|
||||
for (const create of emitters) {
|
||||
const emitter = create()
|
||||
const failure = { reason: 'listener failure' }
|
||||
let later = 0
|
||||
emitter.once('event', () => {
|
||||
throw failure
|
||||
})
|
||||
emitter.on('event', () => later++)
|
||||
assert.throws(() => emitter.emit('event'), error => error === failure)
|
||||
assert.equal(later, 0)
|
||||
emitter.emit('event')
|
||||
assert.equal(later, 1)
|
||||
}
|
||||
})
|
||||
|
||||
test('Thumbnail emitter additions during dispatch wait until the next snapshot', () => {
|
||||
for (const create of emitters) {
|
||||
const emitter = create()
|
||||
const seen = []
|
||||
emitter.once('event', () => {
|
||||
seen.push('first')
|
||||
emitter.on('event', () => seen.push('new'))
|
||||
})
|
||||
emitter.on('event', () => seen.push('second'))
|
||||
emitter.emit('event')
|
||||
assert.deepEqual(seen, ['first', 'second'])
|
||||
emitter.emit('event')
|
||||
assert.deepEqual(seen, ['first', 'second', 'second', 'new'])
|
||||
}
|
||||
})
|
||||
|
||||
test('Thumbnail typed facade retains synchronous validation, option mutation and grid geometry', () => {
|
||||
const { tool } = create(candidate)
|
||||
tool.setup({ number: 10, width: 20, height: 30, column: 3, begin: 5 })
|
||||
tool.duration = 100
|
||||
assert.deepEqual(JSON.parse(JSON.stringify(tool.creatScreenshotDate())), Array.from({ length: 10 }, (_, i) => ({ time: 10 + i * 10, x: i % 3 * 20, y: Math.floor(i / 3) * 30 })))
|
||||
const canvas = tool.creatCanvas()
|
||||
assert.equal(canvas.width, 60)
|
||||
assert.equal(canvas.height, 150)
|
||||
const errors = []
|
||||
tool.on('error', value => errors.push(value))
|
||||
assert.throws(() => tool.setup({ width: '80' }), /not a number/)
|
||||
assert.equal(tool.option.width, 20)
|
||||
assert.throws(() => tool.download(), /does not seem to be ready/)
|
||||
assert.equal(errors.length, 2)
|
||||
assert.equal(tool.errorHandle(true, 'unused'), undefined)
|
||||
tool.destroy()
|
||||
})
|
||||
@@ -0,0 +1,31 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { createHash } from 'node:crypto'
|
||||
import fs from 'node:fs'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Pinned provenance and distributable notice checks.
|
||||
import test from 'node:test'
|
||||
|
||||
const read = file => fs.readFileSync(new URL(`../${file}`, import.meta.url), 'utf8')
|
||||
const base = 'refactor/baselines/thumbnail-vendor/'
|
||||
const license = read(`${base}tiny-emitter-license.txt`).replaceAll('\r\n', '\n').trim()
|
||||
|
||||
test('Thumbnail emitter reference files retain pinned bytes, version and full upstream attribution', () => {
|
||||
const manifest = JSON.parse(read(`${base}sources.json`))
|
||||
for (const source of manifest.sources)
|
||||
assert.equal(createHash('sha256').update(fs.readFileSync(new URL(`../${source.file}`, import.meta.url))).digest('hex'), source.sha256)
|
||||
const pkg = JSON.parse(read(`${base}tiny-emitter-package.json`))
|
||||
assert.equal(pkg.name, 'tiny-emitter')
|
||||
assert.equal(pkg.version, '2.1.0')
|
||||
assert.equal(pkg.license, 'MIT')
|
||||
assert.match(license, /Copyright \(c\) 2017 Scott Corgan/)
|
||||
assert(read('packages/artplayer-tool-thumbnail/THIRD_PARTY_NOTICES').replaceAll('\r\n', '\n').includes(license))
|
||||
})
|
||||
|
||||
test('Thumbnail standalone distributions retain the complete notice and equal their docs copies', () => {
|
||||
for (const suffix of ['.js', '.legacy.js', '.mjs']) {
|
||||
const filename = `artplayer-tool-thumbnail${suffix}`
|
||||
const bundle = read(`packages/artplayer-tool-thumbnail/dist/${filename}`)
|
||||
const banner = bundle.match(/^\/\*![\s\S]*?\*\//)?.[0].replace(/^ \* ?/gm, '').replaceAll('\r\n', '\n')
|
||||
assert(banner?.includes(license), `${filename} must carry the complete upstream notice`)
|
||||
assert.equal(read(`docs/compiled/${filename}`), bundle)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,45 @@
|
||||
import Thumbnail from '../../packages/artplayer-tool-thumbnail/src'
|
||||
|
||||
const input = document.createElement('input')
|
||||
const tool = new Thumbnail({ fileInput: input, number: 10, tracking: { id: 'example' } })
|
||||
const same: Thumbnail = tool.setup({ width: 80 }).on('update', (url, progress) => {
|
||||
const value: string = url
|
||||
const percent: number = progress
|
||||
void [value, percent]
|
||||
})
|
||||
tool.once('file', function (file) {
|
||||
this.name = file.name
|
||||
}, { name: '' })
|
||||
tool.on('error', (message) => {
|
||||
const value: string | undefined = message
|
||||
void value
|
||||
})
|
||||
tool.on('custom', (...payload) => {
|
||||
const values: unknown[] = payload
|
||||
void values
|
||||
})
|
||||
tool.emit(Symbol('extension'), 'value')
|
||||
const pending: Promise<void> = tool.start()
|
||||
const point: { time: number, x: number, y: number } | undefined = tool.creatScreenshotDate()[0]
|
||||
const canvas: HTMLCanvasElement = tool.creatCanvas()
|
||||
const defaults: number = Thumbnail.DEFAULTS.height
|
||||
const wrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||
tool.setup({ fileInput: wrapper })
|
||||
tool.loadVideo(input.files?.[0])
|
||||
tool.loadVideo(null)
|
||||
const destroyed: void = tool.destroy()
|
||||
void [same, pending, point, canvas, defaults, destroyed]
|
||||
|
||||
// @ts-expect-error Numeric options cannot be strings.
|
||||
tool.setup({ width: '80' })
|
||||
// @ts-expect-error A file-input selector string has never been supported.
|
||||
const invalidInput = new Thumbnail({ fileInput: '#input' })
|
||||
// @ts-expect-error Known update events carry a URL followed by numeric progress.
|
||||
tool.emit('update', 123, '50%')
|
||||
// @ts-expect-error Known callbacks must accept their actual payload.
|
||||
tool.on('video', (file: File) => file.name)
|
||||
// @ts-expect-error The public start result is completion, not the thumbnail URL.
|
||||
const incorrect: Promise<string> = tool.start()
|
||||
// @ts-expect-error Runtime ESM exposes only a default class, no .default self-alias.
|
||||
const InvalidAlias = Thumbnail.default
|
||||
void [incorrect, invalidInput, InvalidAlias]
|
||||
Reference in new issue
Block a user