mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
fix(canvas): [PKG-CANVAS-SUBTITLE-01] attach native subtitle tracks
This commit is contained in:
1 parent
d89728c0a6
commit
c3967bcbf4
23 files changed
+1459
-11
No files matched your search
@@ -20,6 +20,9 @@ const art = new Artplayer({
|
||||
miniProgressBar: true,
|
||||
autoPlayback: true,
|
||||
autoOrientation: true,
|
||||
subtitle: {
|
||||
url: '/assets/sample/subtitle.srt',
|
||||
},
|
||||
thumbnails: {
|
||||
url: '/assets/sample/thumbnails.png',
|
||||
number: 60,
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyCanvas=t(),define(function(){return e.artplayerProxyCanvas})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyCanvas=t()}(this,function(){"use strict";function e(e){return Number.isFinite(e.videoWidth)&&Number.isFinite(e.videoHeight)&&e.videoWidth>0&&e.videoHeight>0}function t(e,t){let n=!1,i=!1,o=!1,r=!1,a=0,s=null;function l(){null!==s&&cancelAnimationFrame(s),s=null}function c(){if(!o&&!i&&t())if(r){r=!1,o=!0;const n=a,s=()=>{o=!1,c()};e(()=>!i&&t()&&a===n).then(s,s)}else n&&null===s&&(s=requestAnimationFrame(()=>{s=null,r=!0,c()}))}function u(){!i&&t()&&(a++,r=!0,l(),c())}function f(){n=!1,r=!1,a++,l()}return{request:u,start(){n||i||!t()||(n=!0,u())},stop:f,destroy(){i=!0,f()}}}function n(n){return i=>{const o=i.constructor,r=o.utils.createElement("canvas"),a=o.utils.createElement("video");let s=i,l=i.isDestroy,c=null;const u=[],f=()=>!l&&!s?.isDestroy,d=function(e,t,n){let i=!1;return{mount(){if(i||!n())return;const o=t();o&&(e.setAttribute("aria-hidden","true"),e.tabIndex=-1,e.playsInline=!0,Object.assign(e.style,{position:"absolute",width:"auto",height:"auto",opacity:"0",pointerEvents:"none",left:"0",top:"0"}),o.appendChild(e),i=!0,n()||e.remove())},destroy(){const t=[],n=()=>{e.srcObject=null};for(const i of[()=>e.pause(),()=>e.removeAttribute("src"),n,()=>e.load(),()=>e.remove()])try{i()}catch(o){t.push(o)}if(i=!1,t.length)throw t[0]}}}(a,()=>s?.template?.$player,f),p=function(t,n,i,o,r){const a=t.getContext("2d");let s=!1;return{available:()=>!!a||(s||(s=!0,r(new Error("Canvas 2D context is unavailable"))),!1),async draw(s){const l=()=>s()&&n.readyState>=2&&!n.seeking;if(!a||!l()||!e(n)||!t.width||!t.height)return;let c=!1;try{if("undefined"!==typeof createImageBitmap){c=!0;const e=await createImageBitmap(n);c=!1;try{l()&&a.drawImage(e,0,0,t.width,t.height)}finally{e.close()}}else a.drawImage(n,0,0,t.width,t.height);if(!l())return;i&&i(a,n),l()&&o(a,n)}catch(u){const e=c&&"object"===typeof u&&null!==u&&"name"in u&&"InvalidStateError"===u.name&&0===n.currentTime&&"function"===typeof n.getVideoPlaybackQuality&&0===n.getVideoPlaybackQuality().totalVideoFrames;l()&&!e&&r(u)}}}}(r,a,n,(e,t)=>s?.emit("artplayerProxyCanvas:draw",e,t),e=>s?.emit("artplayerProxyCanvas:error",e)),h=t(p.draw,f);if(function(e,t,n,i,o){const r=new Map;for(const a in e){const t=Reflect.get(e,a);"function"===typeof t&&r.set(a,(...n)=>Reflect.apply(t,e,n))}for(const a in t)a in e||Object.defineProperty(e,a,{get(){const e=Reflect.get(t,a);return"function"!==typeof e?e:(...r)=>n()?("load"===a&&i(),"play"===a&&o(),Reflect.apply(e,t,r)):"play"===a?Promise.resolve():void 0},set(e){n()&&("src"!==a&&"srcObject"!==a||i(),Reflect.set(t,a,e))},configurable:!0,enumerable:!0});for(const[a,s]of r)Reflect.set(e,a,s)}(r,a,f,h.stop,d.mount),l)return s=null,r;function y(){if(l)return;l=!0,h.destroy(),null!==c&&clearTimeout(c),c=null;const e=[];for(const n of[...u.splice(0).reverse(),d.destroy])try{n()}catch(t){e.push(t)}if(s=null,r.width=0,r.height=0,e.length)throw e[0]}function m(e,t){if(!f())return;const n=()=>{("destroy"===e||f())&&t()},o=()=>i.off(e,n);u.push(o),i.on(e,n),l&&o()}function v(){p.available()&&h.request()}try{m("destroy",y),m("video:loadedmetadata",()=>{e(a)&&(r.width=a.videoWidth,r.height=a.videoHeight)}),m("video:play",()=>{d.mount(),p.available()&&h.start()}),m("video:pause",h.stop),m("video:emptied",h.stop),m("video:seeked",v),m("resize",()=>{!function(t,n,i,o){if(!i||o||!e(n))return;const r=i.clientWidth,a=i.clientHeight;if(!Number.isFinite(r)||!Number.isFinite(a)||r<=0||a<=0)return;const s=n.videoWidth/n.videoHeight,l=r/a>s?a*s:r,c=r/a>s?a:r/s;t.width=l,t.height=c,t.style.padding=`${(a-c)/2}px ${(r-l)/2}px`}(r,a,s?.template?.$player,s?.option.autoSize),v()}),f()&&(c=setTimeout(()=>{if(c=null,f())try{d.mount();for(const e of o.config.events){if(!f())break;const t=e=>{f()&&s?.emit(`video:${e.type}`,e)},n=()=>a.removeEventListener(e,t);u.push(n);const o=i.proxy(a,e,t);f()?u.push(o):o()}}catch(e){try{y()}catch{}i.emit("artplayerProxyCanvas:error",e)}}))}catch(g){try{y()}catch{}throw g}return r}}return Object.assign(n,{default:n})});
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyCanvas=t(),define(function(){return e.artplayerProxyCanvas})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyCanvas=t()}(this,function(){"use strict";function e(e){return Number.isFinite(e.videoWidth)&&Number.isFinite(e.videoHeight)&&e.videoWidth>0&&e.videoHeight>0}function t(e,t){let n=!1,i=!1,o=!1,r=!1,a=0,l=null;function s(){null!==l&&cancelAnimationFrame(l),l=null}function c(){if(!o&&!i&&t())if(r){r=!1,o=!0;const n=a,l=()=>{o=!1,c()};e(()=>!i&&t()&&a===n).then(l,l)}else n&&null===l&&(l=requestAnimationFrame(()=>{l=null,r=!0,c()}))}function u(){!i&&t()&&(a++,r=!0,s(),c())}function d(){n=!1,r=!1,a++,s()}return{request:u,start(){n||i||!t()||(n=!0,u())},stop:d,destroy(){i=!0,d()}}}function n(n){return i=>{const o=i.constructor,r=o.utils.createElement("canvas"),a=o.utils.createElement("video");let l=i,s=i.isDestroy,c=null;const u=[],d=()=>!s&&!l?.isDestroy,f=function(e,t,n){let i=!1;return{mount(){if(i||!n())return;const o=t();o&&(e.setAttribute("aria-hidden","true"),e.tabIndex=-1,e.playsInline=!0,Object.assign(e.style,{position:"absolute",width:"auto",height:"auto",opacity:"0",pointerEvents:"none",left:"0",top:"0"}),o.appendChild(e),i=!0,n()||e.remove())},destroy(){const t=[],n=()=>{e.srcObject=null};for(const i of[()=>e.pause(),()=>e.removeAttribute("src"),n,()=>e.load(),()=>e.remove()])try{i()}catch(o){t.push(o)}if(i=!1,t.length)throw t[0]}}}(a,()=>l?.template?.$player,d),p=function(t,n,i,o,r){const a=t.getContext("2d");let l=!1;return{available:()=>!!a||(l||(l=!0,r(new Error("Canvas 2D context is unavailable"))),!1),async draw(l){const s=()=>l()&&n.readyState>=2&&!n.seeking;if(!a||!s()||!e(n)||!t.width||!t.height)return;let c=!1;try{if("undefined"!==typeof createImageBitmap){c=!0;const e=await createImageBitmap(n);c=!1;try{s()&&a.drawImage(e,0,0,t.width,t.height)}finally{e.close()}}else a.drawImage(n,0,0,t.width,t.height);if(!s())return;i&&i(a,n),s()&&o(a,n)}catch(u){const e=c&&"object"===typeof u&&null!==u&&"name"in u&&"InvalidStateError"===u.name&&0===n.currentTime&&"function"===typeof n.getVideoPlaybackQuality&&0===n.getVideoPlaybackQuality().totalVideoFrames;s()&&!e&&r(u)}}}}(r,a,n,(e,t)=>l?.emit("artplayerProxyCanvas:draw",e,t),e=>l?.emit("artplayerProxyCanvas:error",e)),h=t(p.draw,d);if(function(e,t,n,i,o){const r=new Map;for(const a in e){const t=Reflect.get(e,a);"function"===typeof t&&r.set(a,(...n)=>Reflect.apply(t,e,n))}for(const a in t)a in e||Object.defineProperty(e,a,{get(){const e=Reflect.get(t,a);return"function"!==typeof e?e:(...r)=>n()?("load"===a&&i(),"play"===a&&o(),Reflect.apply(e,t,r)):"play"===a?Promise.resolve():void 0},set(e){n()&&("src"!==a&&"srcObject"!==a||i(),Reflect.set(t,a,e))},configurable:!0,enumerable:!0});for(const[a,l]of r)Reflect.set(e,a,l)}(r,a,d,h.stop,f.mount),s)return l=null,r;function y(){if(s)return;s=!0,h.destroy(),null!==c&&clearTimeout(c),c=null;const e=[];for(const n of[...u.splice(0).reverse(),f.destroy])try{n()}catch(t){e.push(t)}if(l=null,r.width=0,r.height=0,e.length)throw e[0]}function m(e,t){if(!d())return;const n=()=>{("destroy"===e||d())&&t()},o=()=>i.off(e,n);u.push(o),i.on(e,n),s&&o()}function v(){p.available()&&h.request()}try{!function(e,t,n,i){if(!i())return;const o=e.appendChild;n.kind="metadata",n.default=!0,t.appendChild(n),e.appendChild=r=>i()&&1===r.nodeType&&"TRACK"===r.nodeName&&"namespaceURI"in r&&"http://www.w3.org/1999/xhtml"===r.namespaceURI?(n.remove(),t.appendChild(r)):o.call(e,r)}(r,a,o.utils.createElement("track"),d),m("destroy",y),m("video:loadedmetadata",()=>{e(a)&&(r.width=a.videoWidth,r.height=a.videoHeight)}),m("video:play",()=>{f.mount(),p.available()&&h.start()}),m("video:pause",h.stop),m("video:emptied",h.stop),m("video:seeked",v),m("resize",()=>{!function(t,n,i,o){if(!i||o||!e(n))return;const r=i.clientWidth,a=i.clientHeight;if(!Number.isFinite(r)||!Number.isFinite(a)||r<=0||a<=0)return;const l=n.videoWidth/n.videoHeight,s=r/a>l?a*l:r,c=r/a>l?a:r/l;t.width=s,t.height=c,t.style.padding=`${(a-c)/2}px ${(r-s)/2}px`}(r,a,l?.template?.$player,l?.option.autoSize),v()}),d()&&(c=setTimeout(()=>{if(c=null,d())try{f.mount();for(const e of o.config.events){if(!d())break;const t=e=>{d()&&l?.emit(`video:${e.type}`,e)},n=()=>a.removeEventListener(e,t);u.push(n);const o=i.proxy(a,e,t);d()?u.push(o):o()}}catch(e){try{y()}catch{}i.emit("artplayerProxyCanvas:error",e)}}))}catch(g){try{y()}catch{}throw g}return r}}return Object.assign(n,{default:n})});
|
||||
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyCanvas=t(),define(function(){return e.artplayerProxyCanvas})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyCanvas=t()}(this,function(){"use strict";function e(e){return Number.isFinite(e.videoWidth)&&Number.isFinite(e.videoHeight)&&e.videoWidth>0&&e.videoHeight>0}function t(t,n,i,o,r){const l=t.getContext("2d");let a=!1;return{available:()=>!!l||(a||(a=!0,r(new Error("Canvas 2D context is unavailable"))),!1),draw(a){return s=this,u=null,c=function*(){const s=()=>a()&&n.readyState>=2&&!n.seeking;if(!l||!s()||!e(n)||!t.width||!t.height)return;let u=!1;try{if("undefined"!==typeof createImageBitmap){u=!0;const e=yield createImageBitmap(n);u=!1;try{s()&&l.drawImage(e,0,0,t.width,t.height)}finally{e.close()}}else l.drawImage(n,0,0,t.width,t.height);if(!s())return;i&&i(l,n),s()&&o(l,n)}catch(c){const e=u&&"object"===typeof c&&null!==c&&"name"in c&&"InvalidStateError"===c.name&&0===n.currentTime&&"function"===typeof n.getVideoPlaybackQuality&&0===n.getVideoPlaybackQuality().totalVideoFrames;s()&&!e&&r(c)}},new Promise((e,t)=>{var n=e=>{try{o(c.next(e))}catch(n){t(n)}},i=e=>{try{o(c.throw(e))}catch(n){t(n)}},o=t=>t.done?e(t.value):Promise.resolve(t.value).then(n,i);o((c=c.apply(s,u)).next())});var s,u,c}}}function n(e,t){let n=!1,i=!1,o=!1,r=!1,l=0,a=null;function s(){null!==a&&cancelAnimationFrame(a),a=null}function u(){if(!o&&!i&&t())if(r){r=!1,o=!0;const n=l,a=()=>{o=!1,u()};e(()=>!i&&t()&&l===n).then(a,a)}else n&&null===a&&(a=requestAnimationFrame(()=>{a=null,r=!0,u()}))}function c(){!i&&t()&&(l++,r=!0,s(),u())}function d(){n=!1,r=!1,l++,s()}return{request:c,start(){n||i||!t()||(n=!0,c())},stop:d,destroy(){i=!0,d()}}}function i(i){return o=>{const r=o.constructor,l=r.utils.createElement("canvas"),a=r.utils.createElement("video");let s=o,u=o.isDestroy,c=null;const d=[],f=()=>!u&&!(null==s?void 0:s.isDestroy),h=function(e,t,n){let i=!1;return{mount(){if(i||!n())return;const o=t();o&&(e.setAttribute("aria-hidden","true"),e.tabIndex=-1,e.playsInline=!0,Object.assign(e.style,{position:"absolute",width:"auto",height:"auto",opacity:"0",pointerEvents:"none",left:"0",top:"0"}),o.appendChild(e),i=!0,n()||e.remove())},destroy(){const t=[],n=()=>{e.srcObject=null};for(const i of[()=>e.pause(),()=>e.removeAttribute("src"),n,()=>e.load(),()=>e.remove()])try{i()}catch(o){t.push(o)}if(i=!1,t.length)throw t[0]}}}(a,()=>{var e;return null==(e=null==s?void 0:s.template)?void 0:e.$player},f),p=t(l,a,i,(e,t)=>null==s?void 0:s.emit("artplayerProxyCanvas:draw",e,t),e=>null==s?void 0:s.emit("artplayerProxyCanvas:error",e)),y=n(p.draw,f);if(function(e,t,n,i,o){const r=new Map;for(const l in e){const t=Reflect.get(e,l);"function"===typeof t&&r.set(l,(...n)=>Reflect.apply(t,e,n))}for(const l in t)l in e||Object.defineProperty(e,l,{get(){const e=Reflect.get(t,l);return"function"!==typeof e?e:(...r)=>n()?("load"===l&&i(),"play"===l&&o(),Reflect.apply(e,t,r)):"play"===l?Promise.resolve():void 0},set(e){n()&&("src"!==l&&"srcObject"!==l||i(),Reflect.set(t,l,e))},configurable:!0,enumerable:!0});for(const[l,a]of r)Reflect.set(e,l,a)}(l,a,f,y.stop,h.mount),u)return s=null,l;function v(){if(u)return;u=!0,y.destroy(),null!==c&&clearTimeout(c),c=null;const e=[];for(const n of[...d.splice(0).reverse(),h.destroy])try{n()}catch(t){e.push(t)}if(s=null,l.width=0,l.height=0,e.length)throw e[0]}function m(e,t){if(!f())return;const n=()=>{("destroy"===e||f())&&t()},i=()=>o.off(e,n);d.push(i),o.on(e,n),u&&i()}function g(){p.available()&&y.request()}try{m("destroy",v),m("video:loadedmetadata",()=>{e(a)&&(l.width=a.videoWidth,l.height=a.videoHeight)}),m("video:play",()=>{h.mount(),p.available()&&y.start()}),m("video:pause",y.stop),m("video:emptied",y.stop),m("video:seeked",g),m("resize",()=>{var t;!function(t,n,i,o){if(!i||o||!e(n))return;const r=i.clientWidth,l=i.clientHeight;if(!Number.isFinite(r)||!Number.isFinite(l)||r<=0||l<=0)return;const a=n.videoWidth/n.videoHeight,s=r/l>a?l*a:r,u=r/l>a?l:r/a;t.width=s,t.height=u,t.style.padding=`${(l-u)/2}px ${(r-s)/2}px`}(l,a,null==(t=null==s?void 0:s.template)?void 0:t.$player,null==s?void 0:s.option.autoSize),g()}),f()&&(c=setTimeout(()=>{if(c=null,f())try{h.mount();for(const e of r.config.events){if(!f())break;const t=e=>{f()&&(null==s||s.emit(`video:${e.type}`,e))},n=()=>a.removeEventListener(e,t);d.push(n);const i=o.proxy(a,e,t);f()?d.push(i):i()}}catch(e){try{v()}catch(t){}o.emit("artplayerProxyCanvas:error",e)}}))}catch(b){try{v()}catch(w){}throw b}return l}}return Object.assign(i,{default:i})});
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyCanvas=t(),define(function(){return e.artplayerProxyCanvas})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyCanvas=t()}(this,function(){"use strict";function e(e){return Number.isFinite(e.videoWidth)&&Number.isFinite(e.videoHeight)&&e.videoWidth>0&&e.videoHeight>0}function t(t,n,i,o,r){const a=t.getContext("2d");let l=!1;return{available:()=>!!a||(l||(l=!0,r(new Error("Canvas 2D context is unavailable"))),!1),draw(l){return s=this,u=null,c=function*(){const s=()=>l()&&n.readyState>=2&&!n.seeking;if(!a||!s()||!e(n)||!t.width||!t.height)return;let u=!1;try{if("undefined"!==typeof createImageBitmap){u=!0;const e=yield createImageBitmap(n);u=!1;try{s()&&a.drawImage(e,0,0,t.width,t.height)}finally{e.close()}}else a.drawImage(n,0,0,t.width,t.height);if(!s())return;i&&i(a,n),s()&&o(a,n)}catch(c){const e=u&&"object"===typeof c&&null!==c&&"name"in c&&"InvalidStateError"===c.name&&0===n.currentTime&&"function"===typeof n.getVideoPlaybackQuality&&0===n.getVideoPlaybackQuality().totalVideoFrames;s()&&!e&&r(c)}},new Promise((e,t)=>{var n=e=>{try{o(c.next(e))}catch(n){t(n)}},i=e=>{try{o(c.throw(e))}catch(n){t(n)}},o=t=>t.done?e(t.value):Promise.resolve(t.value).then(n,i);o((c=c.apply(s,u)).next())});var s,u,c}}}function n(e,t){let n=!1,i=!1,o=!1,r=!1,a=0,l=null;function s(){null!==l&&cancelAnimationFrame(l),l=null}function u(){if(!o&&!i&&t())if(r){r=!1,o=!0;const n=a,l=()=>{o=!1,u()};e(()=>!i&&t()&&a===n).then(l,l)}else n&&null===l&&(l=requestAnimationFrame(()=>{l=null,r=!0,u()}))}function c(){!i&&t()&&(a++,r=!0,s(),u())}function d(){n=!1,r=!1,a++,s()}return{request:c,start(){n||i||!t()||(n=!0,c())},stop:d,destroy(){i=!0,d()}}}function i(i){return o=>{const r=o.constructor,a=r.utils.createElement("canvas"),l=r.utils.createElement("video");let s=o,u=o.isDestroy,c=null;const d=[],f=()=>!u&&!(null==s?void 0:s.isDestroy),p=function(e,t,n){let i=!1;return{mount(){if(i||!n())return;const o=t();o&&(e.setAttribute("aria-hidden","true"),e.tabIndex=-1,e.playsInline=!0,Object.assign(e.style,{position:"absolute",width:"auto",height:"auto",opacity:"0",pointerEvents:"none",left:"0",top:"0"}),o.appendChild(e),i=!0,n()||e.remove())},destroy(){const t=[],n=()=>{e.srcObject=null};for(const i of[()=>e.pause(),()=>e.removeAttribute("src"),n,()=>e.load(),()=>e.remove()])try{i()}catch(o){t.push(o)}if(i=!1,t.length)throw t[0]}}}(l,()=>{var e;return null==(e=null==s?void 0:s.template)?void 0:e.$player},f),h=t(a,l,i,(e,t)=>null==s?void 0:s.emit("artplayerProxyCanvas:draw",e,t),e=>null==s?void 0:s.emit("artplayerProxyCanvas:error",e)),y=n(h.draw,f);if(function(e,t,n,i,o){const r=new Map;for(const a in e){const t=Reflect.get(e,a);"function"===typeof t&&r.set(a,(...n)=>Reflect.apply(t,e,n))}for(const a in t)a in e||Object.defineProperty(e,a,{get(){const e=Reflect.get(t,a);return"function"!==typeof e?e:(...r)=>n()?("load"===a&&i(),"play"===a&&o(),Reflect.apply(e,t,r)):"play"===a?Promise.resolve():void 0},set(e){n()&&("src"!==a&&"srcObject"!==a||i(),Reflect.set(t,a,e))},configurable:!0,enumerable:!0});for(const[a,l]of r)Reflect.set(e,a,l)}(a,l,f,y.stop,p.mount),u)return s=null,a;function m(){if(u)return;u=!0,y.destroy(),null!==c&&clearTimeout(c),c=null;const e=[];for(const n of[...d.splice(0).reverse(),p.destroy])try{n()}catch(t){e.push(t)}if(s=null,a.width=0,a.height=0,e.length)throw e[0]}function v(e,t){if(!f())return;const n=()=>{("destroy"===e||f())&&t()},i=()=>o.off(e,n);d.push(i),o.on(e,n),u&&i()}function g(){h.available()&&y.request()}try{!function(e,t,n,i){if(!i())return;const o=e.appendChild;n.kind="metadata",n.default=!0,t.appendChild(n),e.appendChild=r=>i()&&1===r.nodeType&&"TRACK"===r.nodeName&&"namespaceURI"in r&&"http://www.w3.org/1999/xhtml"===r.namespaceURI?(n.remove(),t.appendChild(r)):o.call(e,r)}(a,l,r.utils.createElement("track"),f),v("destroy",m),v("video:loadedmetadata",()=>{e(l)&&(a.width=l.videoWidth,a.height=l.videoHeight)}),v("video:play",()=>{p.mount(),h.available()&&y.start()}),v("video:pause",y.stop),v("video:emptied",y.stop),v("video:seeked",g),v("resize",()=>{var t;!function(t,n,i,o){if(!i||o||!e(n))return;const r=i.clientWidth,a=i.clientHeight;if(!Number.isFinite(r)||!Number.isFinite(a)||r<=0||a<=0)return;const l=n.videoWidth/n.videoHeight,s=r/a>l?a*l:r,u=r/a>l?a:r/l;t.width=s,t.height=u,t.style.padding=`${(a-u)/2}px ${(r-s)/2}px`}(a,l,null==(t=null==s?void 0:s.template)?void 0:t.$player,null==s?void 0:s.option.autoSize),g()}),f()&&(c=setTimeout(()=>{if(c=null,f())try{p.mount();for(const e of r.config.events){if(!f())break;const t=e=>{f()&&(null==s||s.emit(`video:${e.type}`,e))},n=()=>l.removeEventListener(e,t);d.push(n);const i=o.proxy(l,e,t);f()?d.push(i):i()}}catch(e){try{m()}catch(t){}o.emit("artplayerProxyCanvas:error",e)}}))}catch(b){try{m()}catch(w){}throw b}return a}}return Object.assign(i,{default:i})});
|
||||
@@ -204,6 +204,21 @@ function createFrameScheduler(draw, active) {
|
||||
}
|
||||
};
|
||||
}
|
||||
function forwardSubtitleTrack(canvas, video, initialTrack, active) {
|
||||
if (!active())
|
||||
return;
|
||||
const append = canvas.appendChild;
|
||||
initialTrack.kind = "metadata";
|
||||
initialTrack.default = true;
|
||||
video.appendChild(initialTrack);
|
||||
canvas.appendChild = (node) => {
|
||||
if (active() && node.nodeType === 1 && node.nodeName === "TRACK" && "namespaceURI" in node && node.namespaceURI === "http://www.w3.org/1999/xhtml") {
|
||||
initialTrack.remove();
|
||||
return video.appendChild(node);
|
||||
}
|
||||
return append.call(canvas, node);
|
||||
};
|
||||
}
|
||||
function artplayerProxyCanvas(callback) {
|
||||
return (art) => {
|
||||
const constructor = art.constructor;
|
||||
@@ -262,6 +277,7 @@ function artplayerProxyCanvas(callback) {
|
||||
scheduler.request();
|
||||
}
|
||||
try {
|
||||
forwardSubtitleTrack(canvas, video, constructor.utils.createElement("track"), active);
|
||||
listen("destroy", destroy);
|
||||
listen("video:loadedmetadata", () => {
|
||||
if (hasDimensions(video)) {
|
||||
|
||||
@@ -22,6 +22,7 @@ callback remains optional and function-valued.
|
||||
| --- | --- |
|
||||
| index.ts | Host event subscriptions, deferred media forwarding, initialization rollback and terminal disposal |
|
||||
| adapter.ts | Preserve native canvas methods; forward only video properties absent from canvas; guard escaped media actions after disposal |
|
||||
| subtitles.ts | Supply the initial native metadata track and route the core's actual HTML track insertion into the backing video |
|
||||
| media.ts | Connect the backing video before playback and release its source, stream reference and DOM node |
|
||||
| geometry.ts | Finite intrinsic dimensions and aspect-fit canvas/padding calculations |
|
||||
| renderer.ts | Decoded-frame eligibility, bitmap acquisition/close, callback and draw/error notification order |
|
||||
@@ -33,7 +34,18 @@ private core implementation modules.
|
||||
|
||||
## State and event order
|
||||
|
||||
Canvas methods keep their receiver and win over same-named video methods. Media properties
|
||||
Canvas methods keep their receiver and win over same-named video methods for
|
||||
ordinary Canvas content. `appendChild` has one intentional
|
||||
subtitle-specific adaptation: active HTML track elements attach to the backing video,
|
||||
where native loading and cuechange work. Other nodes still attach to the Canvas.
|
||||
The initial empty metadata track makes the old core's synchronous textTracks[0]
|
||||
capability check succeed; it is removed before the first actual track is appended.
|
||||
The core owns actual track replacement/removal and subtitle URLs. After destruction,
|
||||
escaped appendChild cannot attach more tracks to the unloaded private video.
|
||||
This fixes the formerly empty subtitle path; a working track's parent is now VIDEO.
|
||||
The initialization is inside the same rollback boundary as host registration.
|
||||
|
||||
Media properties
|
||||
remain live enumerable/configurable descriptors. Assigning src/srcObject or invoking load
|
||||
invalidates pending frames; the returned canvas and callback arguments keep their identities.
|
||||
The video is connected before play with absolute positioning, transparent opacity, no focus
|
||||
@@ -89,4 +101,8 @@ through the semantic generator; the editor root stays plain, with an explicit
|
||||
artplayerProxyCanvas.RuntimeFactory assertion for code needing the runtime self alias.
|
||||
Broader installed artifact contents/deep paths and 8082 demos remain 06.
|
||||
Real Safari/mobile/device scope remains 05; Windows Playwright WebKit is not a device claim.
|
||||
`canvas-subtitles.spec.js` exercises native VTT load, paused seeking, replacement,
|
||||
single-track ownership, ordinary Canvas child insertion/removal and escaped insertion
|
||||
after destroy on old/new cores. It also belongs to the installed browser scope;
|
||||
keep its bytes and native engine provenance with the lifecycle report.
|
||||
See `refactor/canvas-validation.md` and the task evidence for actual outcomes and limitations.
|
||||
@@ -53,6 +53,12 @@ Runtime falsy callback arguments retain historical behavior; declarations accept
|
||||
optional function, matching the published types. Implementation and test guidance is in
|
||||
[ARCHITECTURE.md](ARCHITECTURE.md).
|
||||
|
||||
The standard ArtPlayer `subtitle` option uses native subtitle tracks on the backing
|
||||
video. HTML track elements passed to the active proxy's `appendChild` are attached
|
||||
there so captions can load and follow playback; ordinary Canvas content keeps its
|
||||
normal parent. The initial metadata track is replaced by the first actual subtitle.
|
||||
This repairs the previously empty subtitle path without changing subtitle options.
|
||||
|
||||
### Older TypeScript consumers
|
||||
|
||||
The 1.0.0 declaration used `export =` and required a callback; 1.1.0 changed to
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyCanvas=t(),define(function(){return e.artplayerProxyCanvas})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyCanvas=t()}(this,function(){"use strict";function e(e){return Number.isFinite(e.videoWidth)&&Number.isFinite(e.videoHeight)&&e.videoWidth>0&&e.videoHeight>0}function t(e,t){let n=!1,i=!1,o=!1,r=!1,a=0,s=null;function l(){null!==s&&cancelAnimationFrame(s),s=null}function c(){if(!o&&!i&&t())if(r){r=!1,o=!0;const n=a,s=()=>{o=!1,c()};e(()=>!i&&t()&&a===n).then(s,s)}else n&&null===s&&(s=requestAnimationFrame(()=>{s=null,r=!0,c()}))}function u(){!i&&t()&&(a++,r=!0,l(),c())}function f(){n=!1,r=!1,a++,l()}return{request:u,start(){n||i||!t()||(n=!0,u())},stop:f,destroy(){i=!0,f()}}}function n(n){return i=>{const o=i.constructor,r=o.utils.createElement("canvas"),a=o.utils.createElement("video");let s=i,l=i.isDestroy,c=null;const u=[],f=()=>!l&&!s?.isDestroy,d=function(e,t,n){let i=!1;return{mount(){if(i||!n())return;const o=t();o&&(e.setAttribute("aria-hidden","true"),e.tabIndex=-1,e.playsInline=!0,Object.assign(e.style,{position:"absolute",width:"auto",height:"auto",opacity:"0",pointerEvents:"none",left:"0",top:"0"}),o.appendChild(e),i=!0,n()||e.remove())},destroy(){const t=[],n=()=>{e.srcObject=null};for(const i of[()=>e.pause(),()=>e.removeAttribute("src"),n,()=>e.load(),()=>e.remove()])try{i()}catch(o){t.push(o)}if(i=!1,t.length)throw t[0]}}}(a,()=>s?.template?.$player,f),p=function(t,n,i,o,r){const a=t.getContext("2d");let s=!1;return{available:()=>!!a||(s||(s=!0,r(new Error("Canvas 2D context is unavailable"))),!1),async draw(s){const l=()=>s()&&n.readyState>=2&&!n.seeking;if(!a||!l()||!e(n)||!t.width||!t.height)return;let c=!1;try{if("undefined"!==typeof createImageBitmap){c=!0;const e=await createImageBitmap(n);c=!1;try{l()&&a.drawImage(e,0,0,t.width,t.height)}finally{e.close()}}else a.drawImage(n,0,0,t.width,t.height);if(!l())return;i&&i(a,n),l()&&o(a,n)}catch(u){const e=c&&"object"===typeof u&&null!==u&&"name"in u&&"InvalidStateError"===u.name&&0===n.currentTime&&"function"===typeof n.getVideoPlaybackQuality&&0===n.getVideoPlaybackQuality().totalVideoFrames;l()&&!e&&r(u)}}}}(r,a,n,(e,t)=>s?.emit("artplayerProxyCanvas:draw",e,t),e=>s?.emit("artplayerProxyCanvas:error",e)),h=t(p.draw,f);if(function(e,t,n,i,o){const r=new Map;for(const a in e){const t=Reflect.get(e,a);"function"===typeof t&&r.set(a,(...n)=>Reflect.apply(t,e,n))}for(const a in t)a in e||Object.defineProperty(e,a,{get(){const e=Reflect.get(t,a);return"function"!==typeof e?e:(...r)=>n()?("load"===a&&i(),"play"===a&&o(),Reflect.apply(e,t,r)):"play"===a?Promise.resolve():void 0},set(e){n()&&("src"!==a&&"srcObject"!==a||i(),Reflect.set(t,a,e))},configurable:!0,enumerable:!0});for(const[a,s]of r)Reflect.set(e,a,s)}(r,a,f,h.stop,d.mount),l)return s=null,r;function y(){if(l)return;l=!0,h.destroy(),null!==c&&clearTimeout(c),c=null;const e=[];for(const n of[...u.splice(0).reverse(),d.destroy])try{n()}catch(t){e.push(t)}if(s=null,r.width=0,r.height=0,e.length)throw e[0]}function m(e,t){if(!f())return;const n=()=>{("destroy"===e||f())&&t()},o=()=>i.off(e,n);u.push(o),i.on(e,n),l&&o()}function v(){p.available()&&h.request()}try{m("destroy",y),m("video:loadedmetadata",()=>{e(a)&&(r.width=a.videoWidth,r.height=a.videoHeight)}),m("video:play",()=>{d.mount(),p.available()&&h.start()}),m("video:pause",h.stop),m("video:emptied",h.stop),m("video:seeked",v),m("resize",()=>{!function(t,n,i,o){if(!i||o||!e(n))return;const r=i.clientWidth,a=i.clientHeight;if(!Number.isFinite(r)||!Number.isFinite(a)||r<=0||a<=0)return;const s=n.videoWidth/n.videoHeight,l=r/a>s?a*s:r,c=r/a>s?a:r/s;t.width=l,t.height=c,t.style.padding=`${(a-c)/2}px ${(r-l)/2}px`}(r,a,s?.template?.$player,s?.option.autoSize),v()}),f()&&(c=setTimeout(()=>{if(c=null,f())try{d.mount();for(const e of o.config.events){if(!f())break;const t=e=>{f()&&s?.emit(`video:${e.type}`,e)},n=()=>a.removeEventListener(e,t);u.push(n);const o=i.proxy(a,e,t);f()?u.push(o):o()}}catch(e){try{y()}catch{}i.emit("artplayerProxyCanvas:error",e)}}))}catch(g){try{y()}catch{}throw g}return r}}return Object.assign(n,{default:n})});
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyCanvas=t(),define(function(){return e.artplayerProxyCanvas})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyCanvas=t()}(this,function(){"use strict";function e(e){return Number.isFinite(e.videoWidth)&&Number.isFinite(e.videoHeight)&&e.videoWidth>0&&e.videoHeight>0}function t(e,t){let n=!1,i=!1,o=!1,r=!1,a=0,l=null;function s(){null!==l&&cancelAnimationFrame(l),l=null}function c(){if(!o&&!i&&t())if(r){r=!1,o=!0;const n=a,l=()=>{o=!1,c()};e(()=>!i&&t()&&a===n).then(l,l)}else n&&null===l&&(l=requestAnimationFrame(()=>{l=null,r=!0,c()}))}function u(){!i&&t()&&(a++,r=!0,s(),c())}function d(){n=!1,r=!1,a++,s()}return{request:u,start(){n||i||!t()||(n=!0,u())},stop:d,destroy(){i=!0,d()}}}function n(n){return i=>{const o=i.constructor,r=o.utils.createElement("canvas"),a=o.utils.createElement("video");let l=i,s=i.isDestroy,c=null;const u=[],d=()=>!s&&!l?.isDestroy,f=function(e,t,n){let i=!1;return{mount(){if(i||!n())return;const o=t();o&&(e.setAttribute("aria-hidden","true"),e.tabIndex=-1,e.playsInline=!0,Object.assign(e.style,{position:"absolute",width:"auto",height:"auto",opacity:"0",pointerEvents:"none",left:"0",top:"0"}),o.appendChild(e),i=!0,n()||e.remove())},destroy(){const t=[],n=()=>{e.srcObject=null};for(const i of[()=>e.pause(),()=>e.removeAttribute("src"),n,()=>e.load(),()=>e.remove()])try{i()}catch(o){t.push(o)}if(i=!1,t.length)throw t[0]}}}(a,()=>l?.template?.$player,d),p=function(t,n,i,o,r){const a=t.getContext("2d");let l=!1;return{available:()=>!!a||(l||(l=!0,r(new Error("Canvas 2D context is unavailable"))),!1),async draw(l){const s=()=>l()&&n.readyState>=2&&!n.seeking;if(!a||!s()||!e(n)||!t.width||!t.height)return;let c=!1;try{if("undefined"!==typeof createImageBitmap){c=!0;const e=await createImageBitmap(n);c=!1;try{s()&&a.drawImage(e,0,0,t.width,t.height)}finally{e.close()}}else a.drawImage(n,0,0,t.width,t.height);if(!s())return;i&&i(a,n),s()&&o(a,n)}catch(u){const e=c&&"object"===typeof u&&null!==u&&"name"in u&&"InvalidStateError"===u.name&&0===n.currentTime&&"function"===typeof n.getVideoPlaybackQuality&&0===n.getVideoPlaybackQuality().totalVideoFrames;s()&&!e&&r(u)}}}}(r,a,n,(e,t)=>l?.emit("artplayerProxyCanvas:draw",e,t),e=>l?.emit("artplayerProxyCanvas:error",e)),h=t(p.draw,d);if(function(e,t,n,i,o){const r=new Map;for(const a in e){const t=Reflect.get(e,a);"function"===typeof t&&r.set(a,(...n)=>Reflect.apply(t,e,n))}for(const a in t)a in e||Object.defineProperty(e,a,{get(){const e=Reflect.get(t,a);return"function"!==typeof e?e:(...r)=>n()?("load"===a&&i(),"play"===a&&o(),Reflect.apply(e,t,r)):"play"===a?Promise.resolve():void 0},set(e){n()&&("src"!==a&&"srcObject"!==a||i(),Reflect.set(t,a,e))},configurable:!0,enumerable:!0});for(const[a,l]of r)Reflect.set(e,a,l)}(r,a,d,h.stop,f.mount),s)return l=null,r;function y(){if(s)return;s=!0,h.destroy(),null!==c&&clearTimeout(c),c=null;const e=[];for(const n of[...u.splice(0).reverse(),f.destroy])try{n()}catch(t){e.push(t)}if(l=null,r.width=0,r.height=0,e.length)throw e[0]}function m(e,t){if(!d())return;const n=()=>{("destroy"===e||d())&&t()},o=()=>i.off(e,n);u.push(o),i.on(e,n),s&&o()}function v(){p.available()&&h.request()}try{!function(e,t,n,i){if(!i())return;const o=e.appendChild;n.kind="metadata",n.default=!0,t.appendChild(n),e.appendChild=r=>i()&&1===r.nodeType&&"TRACK"===r.nodeName&&"namespaceURI"in r&&"http://www.w3.org/1999/xhtml"===r.namespaceURI?(n.remove(),t.appendChild(r)):o.call(e,r)}(r,a,o.utils.createElement("track"),d),m("destroy",y),m("video:loadedmetadata",()=>{e(a)&&(r.width=a.videoWidth,r.height=a.videoHeight)}),m("video:play",()=>{f.mount(),p.available()&&h.start()}),m("video:pause",h.stop),m("video:emptied",h.stop),m("video:seeked",v),m("resize",()=>{!function(t,n,i,o){if(!i||o||!e(n))return;const r=i.clientWidth,a=i.clientHeight;if(!Number.isFinite(r)||!Number.isFinite(a)||r<=0||a<=0)return;const l=n.videoWidth/n.videoHeight,s=r/a>l?a*l:r,c=r/a>l?a:r/l;t.width=s,t.height=c,t.style.padding=`${(a-c)/2}px ${(r-s)/2}px`}(r,a,l?.template?.$player,l?.option.autoSize),v()}),d()&&(c=setTimeout(()=>{if(c=null,d())try{f.mount();for(const e of o.config.events){if(!d())break;const t=e=>{d()&&l?.emit(`video:${e.type}`,e)},n=()=>a.removeEventListener(e,t);u.push(n);const o=i.proxy(a,e,t);d()?u.push(o):o()}}catch(e){try{y()}catch{}i.emit("artplayerProxyCanvas:error",e)}}))}catch(g){try{y()}catch{}throw g}return r}}return Object.assign(n,{default:n})});
|
||||
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyCanvas=t(),define(function(){return e.artplayerProxyCanvas})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyCanvas=t()}(this,function(){"use strict";function e(e){return Number.isFinite(e.videoWidth)&&Number.isFinite(e.videoHeight)&&e.videoWidth>0&&e.videoHeight>0}function t(t,n,i,o,r){const l=t.getContext("2d");let a=!1;return{available:()=>!!l||(a||(a=!0,r(new Error("Canvas 2D context is unavailable"))),!1),draw(a){return s=this,u=null,c=function*(){const s=()=>a()&&n.readyState>=2&&!n.seeking;if(!l||!s()||!e(n)||!t.width||!t.height)return;let u=!1;try{if("undefined"!==typeof createImageBitmap){u=!0;const e=yield createImageBitmap(n);u=!1;try{s()&&l.drawImage(e,0,0,t.width,t.height)}finally{e.close()}}else l.drawImage(n,0,0,t.width,t.height);if(!s())return;i&&i(l,n),s()&&o(l,n)}catch(c){const e=u&&"object"===typeof c&&null!==c&&"name"in c&&"InvalidStateError"===c.name&&0===n.currentTime&&"function"===typeof n.getVideoPlaybackQuality&&0===n.getVideoPlaybackQuality().totalVideoFrames;s()&&!e&&r(c)}},new Promise((e,t)=>{var n=e=>{try{o(c.next(e))}catch(n){t(n)}},i=e=>{try{o(c.throw(e))}catch(n){t(n)}},o=t=>t.done?e(t.value):Promise.resolve(t.value).then(n,i);o((c=c.apply(s,u)).next())});var s,u,c}}}function n(e,t){let n=!1,i=!1,o=!1,r=!1,l=0,a=null;function s(){null!==a&&cancelAnimationFrame(a),a=null}function u(){if(!o&&!i&&t())if(r){r=!1,o=!0;const n=l,a=()=>{o=!1,u()};e(()=>!i&&t()&&l===n).then(a,a)}else n&&null===a&&(a=requestAnimationFrame(()=>{a=null,r=!0,u()}))}function c(){!i&&t()&&(l++,r=!0,s(),u())}function d(){n=!1,r=!1,l++,s()}return{request:c,start(){n||i||!t()||(n=!0,c())},stop:d,destroy(){i=!0,d()}}}function i(i){return o=>{const r=o.constructor,l=r.utils.createElement("canvas"),a=r.utils.createElement("video");let s=o,u=o.isDestroy,c=null;const d=[],f=()=>!u&&!(null==s?void 0:s.isDestroy),h=function(e,t,n){let i=!1;return{mount(){if(i||!n())return;const o=t();o&&(e.setAttribute("aria-hidden","true"),e.tabIndex=-1,e.playsInline=!0,Object.assign(e.style,{position:"absolute",width:"auto",height:"auto",opacity:"0",pointerEvents:"none",left:"0",top:"0"}),o.appendChild(e),i=!0,n()||e.remove())},destroy(){const t=[],n=()=>{e.srcObject=null};for(const i of[()=>e.pause(),()=>e.removeAttribute("src"),n,()=>e.load(),()=>e.remove()])try{i()}catch(o){t.push(o)}if(i=!1,t.length)throw t[0]}}}(a,()=>{var e;return null==(e=null==s?void 0:s.template)?void 0:e.$player},f),p=t(l,a,i,(e,t)=>null==s?void 0:s.emit("artplayerProxyCanvas:draw",e,t),e=>null==s?void 0:s.emit("artplayerProxyCanvas:error",e)),y=n(p.draw,f);if(function(e,t,n,i,o){const r=new Map;for(const l in e){const t=Reflect.get(e,l);"function"===typeof t&&r.set(l,(...n)=>Reflect.apply(t,e,n))}for(const l in t)l in e||Object.defineProperty(e,l,{get(){const e=Reflect.get(t,l);return"function"!==typeof e?e:(...r)=>n()?("load"===l&&i(),"play"===l&&o(),Reflect.apply(e,t,r)):"play"===l?Promise.resolve():void 0},set(e){n()&&("src"!==l&&"srcObject"!==l||i(),Reflect.set(t,l,e))},configurable:!0,enumerable:!0});for(const[l,a]of r)Reflect.set(e,l,a)}(l,a,f,y.stop,h.mount),u)return s=null,l;function v(){if(u)return;u=!0,y.destroy(),null!==c&&clearTimeout(c),c=null;const e=[];for(const n of[...d.splice(0).reverse(),h.destroy])try{n()}catch(t){e.push(t)}if(s=null,l.width=0,l.height=0,e.length)throw e[0]}function m(e,t){if(!f())return;const n=()=>{("destroy"===e||f())&&t()},i=()=>o.off(e,n);d.push(i),o.on(e,n),u&&i()}function g(){p.available()&&y.request()}try{m("destroy",v),m("video:loadedmetadata",()=>{e(a)&&(l.width=a.videoWidth,l.height=a.videoHeight)}),m("video:play",()=>{h.mount(),p.available()&&y.start()}),m("video:pause",y.stop),m("video:emptied",y.stop),m("video:seeked",g),m("resize",()=>{var t;!function(t,n,i,o){if(!i||o||!e(n))return;const r=i.clientWidth,l=i.clientHeight;if(!Number.isFinite(r)||!Number.isFinite(l)||r<=0||l<=0)return;const a=n.videoWidth/n.videoHeight,s=r/l>a?l*a:r,u=r/l>a?l:r/a;t.width=s,t.height=u,t.style.padding=`${(l-u)/2}px ${(r-s)/2}px`}(l,a,null==(t=null==s?void 0:s.template)?void 0:t.$player,null==s?void 0:s.option.autoSize),g()}),f()&&(c=setTimeout(()=>{if(c=null,f())try{h.mount();for(const e of r.config.events){if(!f())break;const t=e=>{f()&&(null==s||s.emit(`video:${e.type}`,e))},n=()=>a.removeEventListener(e,t);d.push(n);const i=o.proxy(a,e,t);f()?d.push(i):i()}}catch(e){try{v()}catch(t){}o.emit("artplayerProxyCanvas:error",e)}}))}catch(b){try{v()}catch(w){}throw b}return l}}return Object.assign(i,{default:i})});
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyCanvas=t(),define(function(){return e.artplayerProxyCanvas})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyCanvas=t()}(this,function(){"use strict";function e(e){return Number.isFinite(e.videoWidth)&&Number.isFinite(e.videoHeight)&&e.videoWidth>0&&e.videoHeight>0}function t(t,n,i,o,r){const a=t.getContext("2d");let l=!1;return{available:()=>!!a||(l||(l=!0,r(new Error("Canvas 2D context is unavailable"))),!1),draw(l){return s=this,u=null,c=function*(){const s=()=>l()&&n.readyState>=2&&!n.seeking;if(!a||!s()||!e(n)||!t.width||!t.height)return;let u=!1;try{if("undefined"!==typeof createImageBitmap){u=!0;const e=yield createImageBitmap(n);u=!1;try{s()&&a.drawImage(e,0,0,t.width,t.height)}finally{e.close()}}else a.drawImage(n,0,0,t.width,t.height);if(!s())return;i&&i(a,n),s()&&o(a,n)}catch(c){const e=u&&"object"===typeof c&&null!==c&&"name"in c&&"InvalidStateError"===c.name&&0===n.currentTime&&"function"===typeof n.getVideoPlaybackQuality&&0===n.getVideoPlaybackQuality().totalVideoFrames;s()&&!e&&r(c)}},new Promise((e,t)=>{var n=e=>{try{o(c.next(e))}catch(n){t(n)}},i=e=>{try{o(c.throw(e))}catch(n){t(n)}},o=t=>t.done?e(t.value):Promise.resolve(t.value).then(n,i);o((c=c.apply(s,u)).next())});var s,u,c}}}function n(e,t){let n=!1,i=!1,o=!1,r=!1,a=0,l=null;function s(){null!==l&&cancelAnimationFrame(l),l=null}function u(){if(!o&&!i&&t())if(r){r=!1,o=!0;const n=a,l=()=>{o=!1,u()};e(()=>!i&&t()&&a===n).then(l,l)}else n&&null===l&&(l=requestAnimationFrame(()=>{l=null,r=!0,u()}))}function c(){!i&&t()&&(a++,r=!0,s(),u())}function d(){n=!1,r=!1,a++,s()}return{request:c,start(){n||i||!t()||(n=!0,c())},stop:d,destroy(){i=!0,d()}}}function i(i){return o=>{const r=o.constructor,a=r.utils.createElement("canvas"),l=r.utils.createElement("video");let s=o,u=o.isDestroy,c=null;const d=[],f=()=>!u&&!(null==s?void 0:s.isDestroy),p=function(e,t,n){let i=!1;return{mount(){if(i||!n())return;const o=t();o&&(e.setAttribute("aria-hidden","true"),e.tabIndex=-1,e.playsInline=!0,Object.assign(e.style,{position:"absolute",width:"auto",height:"auto",opacity:"0",pointerEvents:"none",left:"0",top:"0"}),o.appendChild(e),i=!0,n()||e.remove())},destroy(){const t=[],n=()=>{e.srcObject=null};for(const i of[()=>e.pause(),()=>e.removeAttribute("src"),n,()=>e.load(),()=>e.remove()])try{i()}catch(o){t.push(o)}if(i=!1,t.length)throw t[0]}}}(l,()=>{var e;return null==(e=null==s?void 0:s.template)?void 0:e.$player},f),h=t(a,l,i,(e,t)=>null==s?void 0:s.emit("artplayerProxyCanvas:draw",e,t),e=>null==s?void 0:s.emit("artplayerProxyCanvas:error",e)),y=n(h.draw,f);if(function(e,t,n,i,o){const r=new Map;for(const a in e){const t=Reflect.get(e,a);"function"===typeof t&&r.set(a,(...n)=>Reflect.apply(t,e,n))}for(const a in t)a in e||Object.defineProperty(e,a,{get(){const e=Reflect.get(t,a);return"function"!==typeof e?e:(...r)=>n()?("load"===a&&i(),"play"===a&&o(),Reflect.apply(e,t,r)):"play"===a?Promise.resolve():void 0},set(e){n()&&("src"!==a&&"srcObject"!==a||i(),Reflect.set(t,a,e))},configurable:!0,enumerable:!0});for(const[a,l]of r)Reflect.set(e,a,l)}(a,l,f,y.stop,p.mount),u)return s=null,a;function m(){if(u)return;u=!0,y.destroy(),null!==c&&clearTimeout(c),c=null;const e=[];for(const n of[...d.splice(0).reverse(),p.destroy])try{n()}catch(t){e.push(t)}if(s=null,a.width=0,a.height=0,e.length)throw e[0]}function v(e,t){if(!f())return;const n=()=>{("destroy"===e||f())&&t()},i=()=>o.off(e,n);d.push(i),o.on(e,n),u&&i()}function g(){h.available()&&y.request()}try{!function(e,t,n,i){if(!i())return;const o=e.appendChild;n.kind="metadata",n.default=!0,t.appendChild(n),e.appendChild=r=>i()&&1===r.nodeType&&"TRACK"===r.nodeName&&"namespaceURI"in r&&"http://www.w3.org/1999/xhtml"===r.namespaceURI?(n.remove(),t.appendChild(r)):o.call(e,r)}(a,l,r.utils.createElement("track"),f),v("destroy",m),v("video:loadedmetadata",()=>{e(l)&&(a.width=l.videoWidth,a.height=l.videoHeight)}),v("video:play",()=>{p.mount(),h.available()&&y.start()}),v("video:pause",y.stop),v("video:emptied",y.stop),v("video:seeked",g),v("resize",()=>{var t;!function(t,n,i,o){if(!i||o||!e(n))return;const r=i.clientWidth,a=i.clientHeight;if(!Number.isFinite(r)||!Number.isFinite(a)||r<=0||a<=0)return;const l=n.videoWidth/n.videoHeight,s=r/a>l?a*l:r,u=r/a>l?a:r/l;t.width=s,t.height=u,t.style.padding=`${(a-u)/2}px ${(r-s)/2}px`}(a,l,null==(t=null==s?void 0:s.template)?void 0:t.$player,null==s?void 0:s.option.autoSize),g()}),f()&&(c=setTimeout(()=>{if(c=null,f())try{p.mount();for(const e of r.config.events){if(!f())break;const t=e=>{f()&&(null==s||s.emit(`video:${e.type}`,e))},n=()=>l.removeEventListener(e,t);d.push(n);const i=o.proxy(l,e,t);f()?d.push(i):i()}}catch(e){try{m()}catch(t){}o.emit("artplayerProxyCanvas:error",e)}}))}catch(b){try{m()}catch(w){}throw b}return a}}return Object.assign(i,{default:i})});
|
||||
@@ -204,6 +204,21 @@ function createFrameScheduler(draw, active) {
|
||||
}
|
||||
};
|
||||
}
|
||||
function forwardSubtitleTrack(canvas, video, initialTrack, active) {
|
||||
if (!active())
|
||||
return;
|
||||
const append = canvas.appendChild;
|
||||
initialTrack.kind = "metadata";
|
||||
initialTrack.default = true;
|
||||
video.appendChild(initialTrack);
|
||||
canvas.appendChild = (node) => {
|
||||
if (active() && node.nodeType === 1 && node.nodeName === "TRACK" && "namespaceURI" in node && node.namespaceURI === "http://www.w3.org/1999/xhtml") {
|
||||
initialTrack.remove();
|
||||
return video.appendChild(node);
|
||||
}
|
||||
return append.call(canvas, node);
|
||||
};
|
||||
}
|
||||
function artplayerProxyCanvas(callback) {
|
||||
return (art) => {
|
||||
const constructor = art.constructor;
|
||||
@@ -262,6 +277,7 @@ function artplayerProxyCanvas(callback) {
|
||||
scheduler.request();
|
||||
}
|
||||
try {
|
||||
forwardSubtitleTrack(canvas, video, constructor.utils.createElement("track"), active);
|
||||
listen("destroy", destroy);
|
||||
listen("video:loadedmetadata", () => {
|
||||
if (hasDimensions(video)) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { hasDimensions, resizeCanvas } from './geometry'
|
||||
import { ownMedia } from './media'
|
||||
import { createRenderer } from './renderer'
|
||||
import { createFrameScheduler } from './scheduler'
|
||||
import { forwardSubtitleTrack } from './subtitles'
|
||||
|
||||
function artplayerProxyCanvas(callback?: DrawCallback) {
|
||||
return (art: Artplayer): HTMLCanvasElement => {
|
||||
@@ -67,6 +68,7 @@ function artplayerProxyCanvas(callback?: DrawCallback) {
|
||||
}
|
||||
|
||||
try {
|
||||
forwardSubtitleTrack(canvas, video, constructor.utils.createElement('track'), active)
|
||||
listen('destroy', destroy)
|
||||
listen('video:loadedmetadata', () => {
|
||||
if (hasDimensions(video)) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export function forwardSubtitleTrack(canvas: HTMLCanvasElement, video: HTMLVideoElement, initialTrack: HTMLTrackElement, active: () => boolean): void {
|
||||
if (!active())
|
||||
return
|
||||
const append = canvas.appendChild
|
||||
initialTrack.kind = 'metadata'
|
||||
initialTrack.default = true
|
||||
video.appendChild(initialTrack)
|
||||
canvas.appendChild = <T extends Node>(node: T): T => {
|
||||
if (active() && node.nodeType === 1 && node.nodeName === 'TRACK' && 'namespaceURI' in node && node.namespaceURI === 'http://www.w3.org/1999/xhtml') {
|
||||
initialTrack.remove()
|
||||
return video.appendChild(node)
|
||||
}
|
||||
return append.call(canvas, node) as T
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,60 @@
|
||||
# PKG-CANVAS-SUBTITLE-01 Canvas 原生字幕轨道
|
||||
|
||||
Canvas-05 的组合检查在三引擎、新旧核心均复现字幕 cues 为零。
|
||||
核心初始化会检查 textTracks[0];Canvas 底层 video 没有模板的初始 track,所以
|
||||
字幕初始化直接返回。仅将后续 appendChild(track) 转给 video 仍是六项失败,
|
||||
已保留两轮报告,不能把插入路由单独视为修复。
|
||||
|
||||
最终方案:底层 video 先创建无 URL 的 metadata 占位轨道以提供原有能力探测;
|
||||
核心实际 appendChild(HTML track) 时移除占位轨道,把真实字幕交给原生 video。
|
||||
普通 Canvas 节点操作保持原路径;销毁后不再向底层 video 插入轨道。
|
||||
新 subtitles.ts 只负责此适配,入口负责初始化失败回滚和整体资源释放。
|
||||
|
||||
这是修复原本不可工作的字幕路径。原生 Canvas 返回值、绘制方法、公开类型和
|
||||
媒体事件不变;实际字幕 track 的 parent 变成 video,已在 README/ARCHITECTURE
|
||||
明确。仅 active 的 HTML track 插入进入媒体路径,普通节点继续使用绑定 Canvas
|
||||
接收者的原方法。并未把所有 DOM 方法改为 video 方法,也没有修改消费者配置。
|
||||
核心负责真实轨道替换、cuechange 订阅和 Blob URL;代理负责底层 video 的释放。
|
||||
初始轨道创建/插入位于现有初始化 try/catch 内,失败回滚所有私有资源。
|
||||
|
||||
## 验证与历史对照
|
||||
|
||||
详情、浏览器版本、报告 SHA、加载包入口及摘要在
|
||||
[canvas-subtitles-validation.json](../baselines/canvas-subtitles-validation.json)。
|
||||
|
||||
- 最初源码和仅插入路由两轮各 6 失败;初始轨道修正后 6 通过。
|
||||
- 扩展原生字幕/Canvas 生命周期 24 通过,覆盖真实 VTT 加载、暂停寻址、字幕
|
||||
替换、旧节点断开且只留一个原生轨道、普通 span 插入/移除、Canvas 方法和销毁。
|
||||
- 仓库外构建/打包/离线安装/冻结重装后,main/legacy 各 33 通过,另含 Ambilight
|
||||
组合播放/取色/双 RAF 清理。最后加入历史模式后再次定向 6 通过。
|
||||
- 真正 npm 1.1.0 同一字幕用例三引擎共 6 失败。设置
|
||||
`ARTPLAYER_CANVAS_SUBTITLE_BASELINE=1.1.0` 可重跑;它不允许与 installed map
|
||||
混用,且失败不被改写为通过。首次失败的 $track 仍在被替换掉的模板 video 内;
|
||||
不能误称它已经被 append 到 Canvas,根因首先是同步能力检查返回空。
|
||||
- 58 项 Canvas Node 回归,包括初始轨道插入异常后的回滚;17 项实际安装旧/新
|
||||
TypeScript 消费者;严格分包 TS、专项 ESLint、根 lint(0 错误/1 既有 warning)、
|
||||
6 项风险/runner 测试及固定工具链通过。所有公开声明未变。
|
||||
- `yarn build artplayer-proxy-canvas` 生成三种 dist 并同步 docs/compiled。四包
|
||||
`test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas` 的
|
||||
36 个运行时/5+8 类型模式仍只针对 core/chapter;不将它们虚报为 Canvas 验收。
|
||||
|
||||
本地 Canvas 示例增加现有 subtitle.srt 配置。内置浏览器显示一个 Canvas、底层
|
||||
video 的 34 条原生 cues,实际播放并显示“星星降落的日子 / 星が降った日”。
|
||||
UA 报 Chrome/152.0.0.0;该后端不支持完整版本查询或内容导出,截图在会话中。
|
||||
一次直接诊断 seek 返回 0,未计为 demo 寻址成功;正式寻址证据来自安装测试。
|
||||
临时标签页已关闭。对示例单独使用普通 Node ESLint 会报页面提供的 Artplayer/
|
||||
插件全局与编辑器消费的 art 未使用;没有为此改动全局 lint 规则。示例语法和
|
||||
实际页面验证通过,测试文件自身的 if 样式诊断已修复。
|
||||
|
||||
## 兼容、接续和回退
|
||||
|
||||
API-07/08 的修复限于原先无法工作的字幕能力与轨道父节点;原接口、样式类、
|
||||
Canvas 绘制、工厂同步返回和 main/module/legacy/types 入口均保留。没有新增依赖。
|
||||
后续字幕工作从 subtitles.ts 开始,调度仍归 scheduler/renderer,避免依赖核心私有
|
||||
实现。运行 `yarn test:browser:installed test/browser/canvas-subtitles.spec.js
|
||||
test/browser/canvas-lifecycle.spec.js test/browser/ambilight-proxy.spec.js --workers=2`
|
||||
前提供已验证的四包 map;普通源码模式用 test:browser:source。
|
||||
|
||||
Canvas-05/06、Document PiP、真机及完整发布门槛仍未完成;Auto Thumbnail 首帧
|
||||
问题也未改变。没有远端 CI、推送、部署或 npm 发布。本任务独立本地提交。
|
||||
回退需一起还原 source、测试、架构/示例、生成产物和任务/风险状态,保留历史报告。
|
||||
+6
-4
@@ -2,9 +2,9 @@
|
||||
|
||||
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 258 项,范围 22 个包及工作区/示例。
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 259 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 50 / doing 19 / blocked 0 / done 189 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 50 / doing 19 / blocked 0 / done 190 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
| artplayer-plugin-multiple-subtitles | 1.2.0 | PKG-MULTI-SUB-01, PKG-MULTI-SUB-02, PKG-MULTI-SUB-03, PKG-MULTI-SUB-04, PKG-MULTI-SUB-07, PKG-MULTI-SUB-08, PKG-MULTI-SUB-05, PKG-MULTI-SUB-06, PKG-MULTI-SUB-09, PKG-MULTI-SUB-10, PKG-MULTI-SUB-11 |
|
||||
| artplayer-plugin-vast | 1.2.0 | PKG-VAST-01, PKG-VAST-02, PKG-VAST-03, PKG-VAST-04, PKG-VAST-05, PKG-VAST-06, PKG-VAST-07 |
|
||||
| artplayer-plugin-vtt-thumbnail | 1.1.0 | PKG-VTT-THUMB-01, PKG-VTT-THUMB-02, PKG-VTT-THUMB-03, PKG-VTT-THUMB-04, PKG-VTT-THUMB-05, PKG-VTT-THUMB-06 |
|
||||
| artplayer-proxy-canvas | 1.1.0 | PKG-AMBILIGHT-PROXY-01, PKG-CANVAS-01, PKG-CANVAS-02, PKG-CANVAS-03, PKG-CANVAS-04, PKG-CANVAS-05, PKG-CANVAS-06, PKG-FACTORY-01 |
|
||||
| artplayer-proxy-canvas | 1.1.0 | PKG-AMBILIGHT-PROXY-01, PKG-CANVAS-01, PKG-CANVAS-02, PKG-CANVAS-03, PKG-CANVAS-04, PKG-CANVAS-05, PKG-CANVAS-06, PKG-FACTORY-01, PKG-CANVAS-SUBTITLE-01 |
|
||||
| artplayer-proxy-mediabunny | 1.2.0 | PKG-MB-01, PKG-MB-02, PKG-MB-03, PKG-MB-04, PKG-MB-05, PKG-MB-06, PKG-MB-07, PKG-MB-08, PKG-MB-09, PKG-MB-10 |
|
||||
| artplayer-tool-iframe | 1.1.0 | PKG-IFRAME-01, PKG-IFRAME-02, PKG-IFRAME-03, PKG-IFRAME-04, PKG-IFRAME-05, PKG-IFRAME-06 |
|
||||
| artplayer-tool-thumbnail | 4.4.0 | PKG-TOOL-THUMB-01, PKG-TOOL-THUMB-02, PKG-TOOL-THUMB-03, PKG-TOOL-THUMB-04, PKG-TOOL-THUMB-05, PKG-TOOL-THUMB-06 |
|
||||
@@ -347,8 +347,9 @@
|
||||
| PKG-CANVAS-02 | artplayer-proxy-canvas<br>建立特有行为与错误测试 | PKG-CANVAS-01, ENG-03, ENG-05 | ready/loadeddata/canplay、play/seek、回调、resize 与销毁 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
|
||||
| PKG-CANVAS-03 | artplayer-proxy-canvas<br>整理内部职责与资源 | PKG-CANVAS-02, CORE-06, CORE-11, CORE-16 | video adapter/原 canvas 方法/RAF 绘制/事件订阅分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
|
||||
| PKG-CANVAS-04 | artplayer-proxy-canvas<br>迁移自有源码和公开类型 | PKG-CANVAS-03, ENG-04, ENG-06, CORE-07 | canvas 与媒体能力的精确组合类型,保持 Result | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
|
||||
| PKG-CANVAS-05 | artplayer-proxy-canvas<br>验证新旧核心和组合 | PKG-CANVAS-04, CORE-22, PKG-DPIP-04, PKG-FACTORY-01 | 真实 video/canvas 绘制、字幕和 document PiP 恢复 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
| PKG-CANVAS-05 | artplayer-proxy-canvas<br>验证新旧核心和组合 | PKG-CANVAS-04, CORE-22, PKG-DPIP-04, PKG-FACTORY-01, PKG-CANVAS-SUBTITLE-01 | 真实 video/canvas 绘制、字幕和 document PiP 恢复 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
| PKG-CANVAS-06 | artplayer-proxy-canvas<br>验证分发并同步文档 | PKG-CANVAS-05, ENG-07 | canvas.js、三种产物、调用兼容与资源证据 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
|
||||
| PKG-CANVAS-SUBTITLE-01 | artplayer-proxy-canvas<br>修复 Canvas 代理原生字幕轨道挂载 | PKG-CANVAS-04 | 初始字幕能力、原生轨道挂载与资源生命周期适配及新旧核心回归 | 复现旧字幕 cues 为空;新旧核心加载、寻址、切换、销毁通过;普通 Canvas 节点行为和分发入口保持兼容 | M | done |
|
||||
|
||||
## 5 包迁移:artplayer-proxy-mediabunny
|
||||
|
||||
@@ -670,3 +671,4 @@
|
||||
- SITE-VCONSOLE-01: [记录](changes/2026-09-14-SITE-VCONSOLE-01-lifecycle.md) [记录](baselines/vconsole-lifecycle-validation.json)
|
||||
- CI-BROWSER-01: [记录](changes/2026-09-14-CI-BROWSER-01-scopes.md) [记录](baselines/browser-scope-validation.json)
|
||||
- PKG-CHAPTER-HOVER-01: [记录](changes/2026-09-14-PKG-CHAPTER-HOVER-01-geometry.md) [记录](baselines/chapter-hover-validation.json)
|
||||
- PKG-CANVAS-SUBTITLE-01: [记录](changes/2026-09-14-PKG-CANVAS-SUBTITLE-01-track.md) [记录](baselines/canvas-subtitles-validation.json)
|
||||
@@ -1,5 +1,20 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-CANVAS-SUBTITLE-01 原生字幕轨道修复完成
|
||||
|
||||
新旧核心三引擎复现 Canvas 字幕为空。先前说明需修正:初始失败中的 $track
|
||||
仍在旧模板的 detached video;底层 video 没有初始轨道,核心能力检查直接返回。
|
||||
仅路由 appendChild 的尝试仍六项失败;增加无 URL metadata 占位轨道并在首次
|
||||
真实轨道插入时替换后,原生字幕正常加载。模块、普通 Canvas 路径、失败回滚、
|
||||
销毁边界与包内文档一起更新。实际 npm 1.1.0 对照六项失败,保留旧红新绿。
|
||||
源码扩展24通过,安装main/legacy各33通过,最终定向6通过;Node58、安装类型17、
|
||||
风险/runner6、严格TS/lint/工具链通过。示例更新且内置浏览器实际显示双语字幕。
|
||||
见[变更](changes/2026-09-14-PKG-CANVAS-SUBTITLE-01-track.md)与
|
||||
[证据](baselines/canvas-subtitles-validation.json)。259项:190 done、19 doing、50 todo。
|
||||
Canvas-05/06及Document PiP/真机未关闭;Auto Thumbnail原生首帧仍待另一后端或
|
||||
准确解码路径证据。本机WSL无已安装发行版,未安装系统环境或更改原像素门槛。
|
||||
没有push/部署/发布;本任务独立提交后必须通过提交审计再开始下一任务。
|
||||
|
||||
## PKG-CHAPTER-HOVER-01 全屏悬停测试同步完成
|
||||
|
||||
原始 installed 224/1 失败保持归档。受控原生进度条移动实验确认立即读取坐标
|
||||
|
||||
@@ -272,3 +272,4 @@
|
||||
| ENG-PERF-OBS-01 | resolved / 已复现 | A nominal 350 ms resource observation can record only 349 ms and fail the required minimum | ENG-13 |
|
||||
| VCONSOLE-LIFECYCLE-01 | resolved / 已复现 | vConsole delayed log, panel and scroller work outlives its owner | SITE-07, SITE-05, SITE-VCONSOLE-01 |
|
||||
| CHAPTER-FULLSCREEN-GEOMETRY-01 | resolved / 已复现 | Chromium 原生全屏后章节 hover 标题曾出现后又隐藏 | PKG-CHAPTER-05, PKG-CHAPTER-HOVER-01 |
|
||||
| CANVAS-SUBTITLE-01 | resolved / 已复现 | Canvas底层video缺初始字幕轨道且普通Canvas挂载不能加载原生字幕 | PKG-CANVAS-SUBTITLE-01, PKG-CANVAS-05 |
|
||||
@@ -6218,6 +6218,29 @@
|
||||
"test/browser/chapter-hover.spec.js"
|
||||
],
|
||||
"resolutionRationale": "The immediate bounding-box read permits stale pointer coordinates while progress transforms move. Initial hit-test control gives 4 failures/2 passes before and 6 passes after waiting for locator stability. WebKit hit tests disagree with the final rectangle, so additional explicit bounds assertions were added: baseline 6 failures, stable main/legacy 6 passes each. Complete main/legacy chapter combination suites retain title, opacity, thumbnails and fullscreen assertions (66 passes each). This closes the identified test synchronization race, not all possible production fullscreen defects or CHAPTER-TIMING-01. Original 224/1 failure remains archived; its exact scheduler cannot be replayed."
|
||||
},
|
||||
{
|
||||
"id": "CANVAS-SUBTITLE-01",
|
||||
"title": "Canvas底层video缺初始字幕轨道且普通Canvas挂载不能加载原生字幕",
|
||||
"confirmation": "reproduced",
|
||||
"status": "resolved",
|
||||
"owners": [
|
||||
"PKG-CANVAS-SUBTITLE-01",
|
||||
"PKG-CANVAS-05"
|
||||
],
|
||||
"evidence": [
|
||||
"refactor/changes/2026-09-14-PKG-CANVAS-SUBTITLE-01-track.md",
|
||||
"test/browser/canvas-subtitles.spec.js"
|
||||
],
|
||||
"compatibleResolution": "适配原生track能力和插入,同时保留普通Canvas操作;不得要求旧核心消费者更改调用。",
|
||||
"closureCriteria": "新旧核心三引擎字幕加载/寻址/切换/清理、普通节点与分发回归,有旧红新绿证据。",
|
||||
"workspaceState": "Native subtitle bootstrap and track insertion are isolated in subtitles.ts. Old/new core source and installed main/legacy pass real subtitle load/seek/replacement/cleanup and normal Canvas-node checks. Public declaration shape is unchanged.",
|
||||
"resolutionEvidence": [
|
||||
"refactor/changes/2026-09-14-PKG-CANVAS-SUBTITLE-01-track.md",
|
||||
"refactor/baselines/canvas-subtitles-validation.json",
|
||||
"test/browser/canvas-subtitles.spec.js"
|
||||
],
|
||||
"resolutionRationale": "Initial candidate and routing-only controls each fail all six subtitle cases. Bootstrap + insertion adaptation passes; npm 1.1.0 independently fails all six. Expanded native source tests pass 24; installed main and legacy pass 33 each; final six pass after adding baseline selection. Captions also visibly render in the local demo. This fixes subtitle capability/insertion only; broader Canvas integration remains open."
|
||||
}
|
||||
]
|
||||
}
|
||||
+21
-1
@@ -3635,7 +3635,8 @@
|
||||
"PKG-CANVAS-04",
|
||||
"CORE-22",
|
||||
"PKG-DPIP-04",
|
||||
"PKG-FACTORY-01"
|
||||
"PKG-FACTORY-01",
|
||||
"PKG-CANVAS-SUBTITLE-01"
|
||||
],
|
||||
"status": "todo",
|
||||
"risk": "H",
|
||||
@@ -5389,6 +5390,25 @@
|
||||
"changes/2026-09-14-PKG-CHAPTER-HOVER-01-geometry.md",
|
||||
"baselines/chapter-hover-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-CANVAS-SUBTITLE-01",
|
||||
"phase": "5 包迁移:artplayer-proxy-canvas",
|
||||
"title": "修复 Canvas 代理原生字幕轨道挂载",
|
||||
"scope": [
|
||||
"artplayer-proxy-canvas"
|
||||
],
|
||||
"dependsOn": [
|
||||
"PKG-CANVAS-04"
|
||||
],
|
||||
"status": "done",
|
||||
"risk": "M",
|
||||
"deliverable": "初始字幕能力、原生轨道挂载与资源生命周期适配及新旧核心回归",
|
||||
"acceptance": "复现旧字幕 cues 为空;新旧核心加载、寻址、切换、销毁通过;普通 Canvas 节点行为和分发入口保持兼容",
|
||||
"evidence": [
|
||||
"changes/2026-09-14-PKG-CANVAS-SUBTITLE-01-track.md",
|
||||
"baselines/canvas-subtitles-validation.json"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export const installedTests = [
|
||||
'ambilight-lifecycle.spec.js',
|
||||
'ambilight-proxy.spec.js',
|
||||
'canvas-lifecycle.spec.js',
|
||||
'canvas-subtitles.spec.js',
|
||||
]
|
||||
|
||||
export function browserInvocation(scope: string, args: string[], environment: NodeJS.ProcessEnv) {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Browser regression entry
|
||||
|
||||
`canvas-subtitles.spec.js` covers native track bootstrap/load/seek/replacement and
|
||||
cleanup on old/new cores, including ordinary Canvas child operations. It is part
|
||||
of both browser scopes. `ARTPLAYER_CANVAS_SUBTITLE_BASELINE=1.1.0` runs the exact
|
||||
published proxy as a failing historical control; it cannot be combined with an
|
||||
installed candidate map. This is a real media/track test, not a simulated cue list.
|
||||
|
||||
Chapter pointer geometry is shared by `chapter-hover.ts` and the combination
|
||||
suite. It awaits locator actionability with `hover({ trial: true })` before
|
||||
reading coordinates; `art.fullscreen` can become true while controls still move.
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import process from 'node:process'
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { canvasCandidate, canvasHistorical } from '../helpers/canvas.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const baseline = process.env.ARTPLAYER_CANVAS_SUBTITLE_BASELINE
|
||||
assert(!baseline || baseline === '1.1.0', 'Only the frozen published 1.1.0 subtitle baseline is supported')
|
||||
assert(!baseline || !process.env.ARTPLAYER_BROWSER_ARTIFACTS, 'Historical subtitles cannot claim installed candidate provenance')
|
||||
const candidate = baseline ? (await canvasHistorical()).find(item => item.name === 'published-1.1.0') : await canvasCandidate()
|
||||
assert(candidate)
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
test(`${core}: canvas proxy loads and seeks native subtitle cues`, async ({ page }, testInfo) => {
|
||||
await page.route('**/canvas-captions.vtt', route => route.fulfill({ contentType: 'text/vtt', body: 'WEBVTT\n\n00:00:00.000 --> 00:00:02.000\nOpening caption\n\n00:00:02.000 --> 00:00:07.000\nLater caption\n' }))
|
||||
await page.route('**/canvas-replacement.vtt', route => route.fulfill({ contentType: 'text/vtt', body: 'WEBVTT\n\n00:00:00.000 --> 00:00:07.000\nReplacement caption\n' }))
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
await page.addScriptTag({ content: `(() => { const module = { exports: {} }; const exports = module.exports; ${candidate.source}; window.canvasFactory = module.exports.default || module.exports; })();` })
|
||||
await page.evaluate(() => {
|
||||
window.art = new window.Artplayer({ container: '.player', url: '/test/pattern.mp4', muted: true, proxy: window.canvasFactory(), subtitle: { url: '/canvas-captions.vtt' } })
|
||||
document.querySelector('#play').onclick = () => window.art.play()
|
||||
document.querySelector('#pause').onclick = () => window.art.pause()
|
||||
})
|
||||
let completed = false
|
||||
try {
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
await page.click('#play')
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.1)
|
||||
await page.click('#pause')
|
||||
await expect.poll(() => page.evaluate(() => window.art.subtitle.cues.length)).toBe(2)
|
||||
await expect(page.locator('.art-subtitle')).toHaveText('Opening caption')
|
||||
await page.evaluate(() => window.art.currentTime = 3)
|
||||
await expect(page.locator('.art-subtitle')).toHaveText('Later caption')
|
||||
await page.evaluate(async () => {
|
||||
window.previousCanvasTrack = window.art.template.$track
|
||||
await window.art.subtitle.switch('/canvas-replacement.vtt')
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.art.subtitle.cues.length)).toBe(1)
|
||||
await page.evaluate(() => window.art.currentTime = 4)
|
||||
await expect(page.locator('.art-subtitle')).toHaveText('Replacement caption')
|
||||
expect(await page.evaluate(() => ({ previousConnected: window.previousCanvasTrack.isConnected, count: window.art.video.textTracks.length, parent: window.art.template.$track.parentNode === window.art.template.$player.querySelector('video') }))).toEqual({ previousConnected: false, count: 1, parent: true })
|
||||
expect(await page.evaluate(() => {
|
||||
const canvas = window.art.video
|
||||
const marker = document.createElement('span')
|
||||
const append = canvas.appendChild
|
||||
const returned = append(marker)
|
||||
const same = returned === marker && marker.parentNode === canvas
|
||||
canvas.removeChild(marker)
|
||||
window.canvasAppend = append
|
||||
window.backingCanvasVideo = window.art.template.$player.querySelector('video')
|
||||
return same && !marker.parentNode && typeof canvas.toDataURL() === 'string'
|
||||
})).toBe(true)
|
||||
completed = true
|
||||
}
|
||||
finally {
|
||||
await testInfo.attach('canvas-subtitle-state', { contentType: 'application/json', body: JSON.stringify({ core, implementation: candidate.name, sha256: hash(candidate.source), ...await page.evaluate(() => ({ renderer: window.art.video.nodeName, trackParent: window.art.template.$track.parentNode?.nodeName, trackState: window.art.template.$track.readyState, cues: window.art.subtitle.cues.length, backingTracks: window.art.template.$player.querySelector('video')?.textTracks.length, text: window.art.template.$subtitle.textContent, time: window.art.currentTime })) }) })
|
||||
await page.evaluate(() => window.art.destroy())
|
||||
if (completed) {
|
||||
expect(await page.evaluate(() => {
|
||||
const track = document.createElement('track')
|
||||
window.canvasAppend?.(track)
|
||||
const video = window.backingCanvasVideo
|
||||
return video ? { connected: video.isConnected, paused: video.paused, source: video.getAttribute('src'), restarted: track.parentNode === video } : null
|
||||
})).toEqual({ connected: false, paused: true, source: null, restarted: false })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -5,6 +5,22 @@ import { canvasCandidate, canvasEnvironment, canvasHistorical } from './helpers/
|
||||
|
||||
const implementation = await canvasCandidate()
|
||||
const listenerCount = env => [...env.listeners.values()].reduce((count, set) => count + set.size, 0)
|
||||
|
||||
test('Canvas rolls back private media when initial subtitle track insertion fails', async () => {
|
||||
const env = await canvasEnvironment(implementation)
|
||||
const failure = new Error('Initial track insertion failed')
|
||||
env.video.appendChild = () => {
|
||||
throw failure
|
||||
}
|
||||
assert.throws(() => env.factory()(env.art), error => error === failure)
|
||||
assert.equal(listenerCount(env), 0)
|
||||
assert.equal(env.timers.size, 0)
|
||||
assert.equal(env.frames.size, 0)
|
||||
assert.equal(env.video.parentNode, undefined)
|
||||
assert.equal(env.video.src, undefined)
|
||||
assert.equal(env.video.paused, true)
|
||||
assert.deepEqual([env.canvas.width, env.canvas.height], [0, 0])
|
||||
})
|
||||
function deferred() {
|
||||
let resolve, reject
|
||||
const promise = new Promise((yes, no) => {
|
||||
|
||||
+23
-1
@@ -75,6 +75,24 @@ export async function canvasEnvironment(implementation, settings = {}) {
|
||||
paused: true,
|
||||
muted: false,
|
||||
style: {},
|
||||
children: [],
|
||||
textTracks: [],
|
||||
appendChild(node) {
|
||||
node.parentNode?.removeChild(node)
|
||||
this.children.push(node)
|
||||
node.parentNode = this
|
||||
if (node.nodeName === 'TRACK')
|
||||
this.textTracks.push(node.track)
|
||||
return node
|
||||
},
|
||||
removeChild(node) {
|
||||
this.children.splice(this.children.indexOf(node), 1)
|
||||
const index = this.textTracks.indexOf(node.track)
|
||||
if (index >= 0)
|
||||
this.textTracks.splice(index, 1)
|
||||
node.parentNode = null
|
||||
return node
|
||||
},
|
||||
play(...args) {
|
||||
calls.push({ name: 'play', receiver: this, args })
|
||||
this.paused = false
|
||||
@@ -111,7 +129,11 @@ export async function canvasEnvironment(implementation, settings = {}) {
|
||||
},
|
||||
}
|
||||
const art = {
|
||||
constructor: { config: { events: ['loadedmetadata', 'play', 'pause', 'seeked', 'error'] }, utils: { createElement: tag => tag === 'canvas' ? canvas : video } },
|
||||
constructor: { config: { events: ['loadedmetadata', 'play', 'pause', 'seeked', 'error'] }, utils: { createElement: (tag) => {
|
||||
if (tag === 'track')
|
||||
return { nodeName: 'TRACK', nodeType: 1, namespaceURI: 'http://www.w3.org/1999/xhtml', track: { mode: 'disabled', cues: [], activeCues: [] }, remove() { this.parentNode?.removeChild(this) } }
|
||||
return tag === 'canvas' ? canvas : video
|
||||
} } },
|
||||
option: { autoSize: false },
|
||||
template: { $player: player },
|
||||
isDestroy: false,
|
||||
|
||||
Reference in new issue
Block a user