fix(chapter): [PKG-CHAPTER-05] checkpoint composition tests and bounded title layout

This commit is contained in:
Harvey Zhao committed 2026-09-12 04:16:32 +08:00
1 parent d18c545675
commit e0d47ea74b
16 files changed
+7942 -10

No files matched your search

+1 -1
View File
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginChapter=e(),define(function(){return t.artplayerPluginChapter})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginChapter=e()}(this,function(){"use strict";return function(){if("undefined"===typeof document)return;const t="artplayer-plugin-chapter",e=()=>{let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,(document.head||document.documentElement).appendChild(e)),e.textContent=".artplayer-plugin-chapter .art-control-progress-inner{height:100%!important;background-color:transparent!important}.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-hover,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-loaded,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-played{display:none!important}.artplayer-plugin-chapter .art-control-thumbnails{bottom:calc(var(--art-bottom-gap) + 64px)!important}.artplayer-plugin-chapter .art-chapters{position:absolute;z-index:0;inset:0;display:flex;align-items:center;gap:4px;height:100%;transform:scaleY(1.25)}.artplayer-plugin-chapter .art-chapters .art-chapter{display:flex;align-items:center;height:100%}.artplayer-plugin-chapter .art-chapters .art-chapter .art-chapter-inner{position:relative;cursor:pointer;width:100%;height:50%;border-radius:10px;overflow:hidden;transition:height var(--art-transition-duration) ease;background-color:var(--art-progress-color)}.artplayer-plugin-chapter .art-chapters .art-chapter:hover .art-chapter-inner{height:100%}.artplayer-plugin-chapter .art-chapter-title{transform-origin:bottom center;transform:scale(.5);opacity:0;position:absolute;z-index:70;top:-50px;left:0;padding:3px 5px;line-height:1;font-size:14px;border-radius:var(--art-border-radius);white-space:nowrap;background-color:var(--art-tip-background);transition:transform var(--art-transition-duration) ease,opacity var(--art-transition-duration) ease}.artplayer-plugin-chapter.art-progress-hover .art-chapter-title{transform:scale(1);opacity:1}"};document.getElementById(t)||"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,{once:!0})}(),function(t={}){return e=>{const r=e.template.$player,a=e.query(".art-control-progress-inner");if(!a)throw new Error("Missing ArtPlayer progress container");const n="artplayer-plugin-chapter";let o=function(t){const e=(e,r)=>{const a=t.ownerDocument.createElement("div");return a.className=r,e.appendChild(a),a},r=e(t,"art-chapters"),a=e(t,"art-chapter-title");let n=[];function o(){a.textContent="",a.style.left="0px",a.style.visibility="hidden"}function i(){n=[],r.textContent="",o()}return o(),{clear:i,render:function(t,a){n=t.map(t=>{const n=e(r,"art-chapter"),o=e(n,"art-chapter-inner"),i=t.end-t.start;return n.dataset.start=String(t.start),n.dataset.end=String(t.end),n.dataset.duration=String(i),n.dataset.title=t.title.trim(),n.style.width=i/a*100+"%",{start:t.start,end:t.end,title:t.title.trim(),bars:{hover:e(o,"art-progress-hover"),loaded:e(o,"art-progress-loaded"),played:e(o,"art-progress-played")}}})},setBar:function(e,i,s){if("hover"!==e&&"loaded"!==e&&"played"!==e)return;if(!Number.isFinite(i))return;const l=s*i;let p;for(const t of n)t.bars[e].style.width=l<t.start?"0px":l>t.end?"100%":(l-t.start)/(t.end-t.start)*100+"%",l>=t.start&&l<=t.end&&(p=t);if("hover"!==e)return;o();const d=r.clientWidth*i;p?.title&&d>0&&(a.textContent=p.title,a.style.visibility="visible",a.style.left=`${Math.max(0,Math.min(d-a.clientWidth/2,t.clientWidth-a.clientWidth))}px`)},destroy:function(){i(),r.remove(),a.remove()}}}(a);function i(t){if(!o||e.isDestroy)return;o.clear(),r.classList.remove(n);const a=function(t=[],e){if(!Array.isArray(t)||!t.length||!Number.isFinite(e)||e<=0)return[];t.sort((t,e)=>t.start-e.start);for(const[n,o]of t.entries()){if(!o||"number"!==typeof o.start||"number"!==typeof o.end||"string"!==typeof o.title)throw new TypeError("Illegal chapter data type");o.end===1/0&&(o.end=e);const r=t[n+1];if(!Number.isFinite(o.start)||!Number.isFinite(o.end)||o.start<0||o.end>e||o.start>=o.end||r&&o.end>r.start)throw new Error("Illegal chapter time point")}const r=t[0],a=t[t.length-1];r&&r.start>0&&t.unshift({start:0,end:r.start,title:""}),a&&a.end<e&&t.push({start:a.end,end:e,title:""});for(let n=0;n<t.length-1;n++){const e=t[n],r=t[n+1];e&&r&&e.end!==r.start&&t.splice(n+1,0,{start:e.end,end:r.start,title:""})}return t}(t,e.duration);a.length&&(o.render(a,e.duration),r.classList.add(n),e.emit("setBar","loaded",e.loaded||0))}function s(t,r){e.isDestroy||o?.setBar(t,r,e.duration)}function l(){i(t.chapters)}return e.on("setBar",s),e.once("video:loadedmetadata",l),e.on("destroy",function t(){e.off("setBar",s),e.off("video:loadedmetadata",l),e.off("destroy",t),o?.destroy(),o=void 0,r.classList.remove(n)}),{name:"artplayerPluginChapter",update:({chapters:t})=>i(t)}}}});
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginChapter=e(),define(function(){return t.artplayerPluginChapter})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginChapter=e()}(this,function(){"use strict";return function(){if("undefined"===typeof document)return;const t="artplayer-plugin-chapter",e=()=>{let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,(document.head||document.documentElement).appendChild(e)),e.textContent=".artplayer-plugin-chapter .art-control-progress-inner{height:100%!important;background-color:transparent!important}.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-hover,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-loaded,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-played{display:none!important}.artplayer-plugin-chapter .art-control-thumbnails{bottom:calc(var(--art-bottom-gap) + 64px)!important}.artplayer-plugin-chapter .art-chapters{position:absolute;z-index:0;inset:0;display:flex;align-items:center;gap:4px;height:100%;transform:scaleY(1.25)}.artplayer-plugin-chapter .art-chapters .art-chapter{display:flex;align-items:center;height:100%}.artplayer-plugin-chapter .art-chapters .art-chapter .art-chapter-inner{position:relative;cursor:pointer;width:100%;height:50%;border-radius:10px;overflow:hidden;transition:height var(--art-transition-duration) ease;background-color:var(--art-progress-color)}.artplayer-plugin-chapter .art-chapters .art-chapter:hover .art-chapter-inner{height:100%}.artplayer-plugin-chapter .art-chapter-title{transform-origin:bottom center;transform:scale(.5);opacity:0;position:absolute;z-index:70;top:-50px;left:0;padding:3px 5px;line-height:1;font-size:14px;border-radius:var(--art-border-radius);white-space:nowrap;box-sizing:border-box;max-width:100%;overflow:hidden;text-overflow:ellipsis;background-color:var(--art-tip-background);transition:transform var(--art-transition-duration) ease,opacity var(--art-transition-duration) ease}.artplayer-plugin-chapter.art-progress-hover .art-chapter-title{transform:scale(1);opacity:1}"};document.getElementById(t)||"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,{once:!0})}(),function(t={}){return e=>{const r=e.template.$player,a=e.query(".art-control-progress-inner");if(!a)throw new Error("Missing ArtPlayer progress container");const n="artplayer-plugin-chapter";let o=function(t){const e=(e,r)=>{const a=t.ownerDocument.createElement("div");return a.className=r,e.appendChild(a),a},r=e(t,"art-chapters"),a=e(t,"art-chapter-title");let n=[];function o(){a.textContent="",a.style.left="0px",a.style.visibility="hidden"}function i(){n=[],r.textContent="",o()}return o(),{clear:i,render:function(t,a){n=t.map(t=>{const n=e(r,"art-chapter"),o=e(n,"art-chapter-inner"),i=t.end-t.start;return n.dataset.start=String(t.start),n.dataset.end=String(t.end),n.dataset.duration=String(i),n.dataset.title=t.title.trim(),n.style.width=i/a*100+"%",{start:t.start,end:t.end,title:t.title.trim(),bars:{hover:e(o,"art-progress-hover"),loaded:e(o,"art-progress-loaded"),played:e(o,"art-progress-played")}}})},setBar:function(e,i,s){if("hover"!==e&&"loaded"!==e&&"played"!==e)return;if(!Number.isFinite(i))return;const l=s*i;let d;for(const t of n)t.bars[e].style.width=l<t.start?"0px":l>t.end?"100%":(l-t.start)/(t.end-t.start)*100+"%",l>=t.start&&l<=t.end&&(d=t);if("hover"!==e)return;o();const p=r.clientWidth*i;d?.title&&p>0&&(a.textContent=d.title,a.style.visibility="visible",a.style.left=`${Math.max(0,Math.min(p-a.clientWidth/2,t.clientWidth-a.clientWidth))}px`)},destroy:function(){i(),r.remove(),a.remove()}}}(a);function i(t){if(!o||e.isDestroy)return;o.clear(),r.classList.remove(n);const a=function(t=[],e){if(!Array.isArray(t)||!t.length||!Number.isFinite(e)||e<=0)return[];t.sort((t,e)=>t.start-e.start);for(const[n,o]of t.entries()){if(!o||"number"!==typeof o.start||"number"!==typeof o.end||"string"!==typeof o.title)throw new TypeError("Illegal chapter data type");o.end===1/0&&(o.end=e);const r=t[n+1];if(!Number.isFinite(o.start)||!Number.isFinite(o.end)||o.start<0||o.end>e||o.start>=o.end||r&&o.end>r.start)throw new Error("Illegal chapter time point")}const r=t[0],a=t[t.length-1];r&&r.start>0&&t.unshift({start:0,end:r.start,title:""}),a&&a.end<e&&t.push({start:a.end,end:e,title:""});for(let n=0;n<t.length-1;n++){const e=t[n],r=t[n+1];e&&r&&e.end!==r.start&&t.splice(n+1,0,{start:e.end,end:r.start,title:""})}return t}(t,e.duration);a.length&&(o.render(a,e.duration),r.classList.add(n),e.emit("setBar","loaded",e.loaded||0))}function s(t,r){e.isDestroy||o?.setBar(t,r,e.duration)}function l(){i(t.chapters)}return e.on("setBar",s),e.once("video:loadedmetadata",l),e.on("destroy",function t(){e.off("setBar",s),e.off("video:loadedmetadata",l),e.off("destroy",t),o?.destroy(),o=void 0,r.classList.remove(n)}),{name:"artplayerPluginChapteLine truncated
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginChapter=e(),define(function(){return t.artplayerPluginChapter})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginChapter=e()}(this,function(){"use strict";return function(){if("undefined"===typeof document)return;const t="artplayer-plugin-chapter",e=()=>{let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,(document.head||document.documentElement).appendChild(e)),e.textContent=".artplayer-plugin-chapter .art-control-progress-inner{height:100%!important;background-color:transparent!important}.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-hover,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-loaded,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-played{display:none!important}.artplayer-plugin-chapter .art-control-thumbnails{bottom:calc(var(--art-bottom-gap) + 64px)!important}.artplayer-plugin-chapter .art-chapters{position:absolute;z-index:0;inset:0;display:flex;align-items:center;gap:4px;height:100%;transform:scaleY(1.25)}.artplayer-plugin-chapter .art-chapters .art-chapter{display:flex;align-items:center;height:100%}.artplayer-plugin-chapter .art-chapters .art-chapter .art-chapter-inner{position:relative;cursor:pointer;width:100%;height:50%;border-radius:10px;overflow:hidden;transition:height var(--art-transition-duration) ease;background-color:var(--art-progress-color)}.artplayer-plugin-chapter .art-chapters .art-chapter:hover .art-chapter-inner{height:100%}.artplayer-plugin-chapter .art-chapter-title{transform-origin:bottom center;transform:scale(.5);opacity:0;position:absolute;z-index:70;top:-50px;left:0;padding:3px 5px;line-height:1;font-size:14px;border-radius:var(--art-border-radius);white-space:nowrap;background-color:var(--art-tip-background);transition:transform var(--art-transition-duration) ease,opacity var(--art-transition-duration) ease}.artplayer-plugin-chapter.art-progress-hover .art-chapter-title{transform:scale(1);opacity:1}"};document.getElementById(t)||"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,{once:!0})}(),function(t={}){return e=>{const r=e.template.$player,a=e.query(".art-control-progress-inner");if(!a)throw new Error("Missing ArtPlayer progress container");const n="artplayer-plugin-chapter";let o=function(t){const e=(e,r)=>{const a=t.ownerDocument.createElement("div");return a.className=r,e.appendChild(a),a},r=e(t,"art-chapters"),a=e(t,"art-chapter-title");let n=[];function o(){a.textContent="",a.style.left="0px",a.style.visibility="hidden"}function i(){n=[],r.textContent="",o()}return o(),{clear:i,render:function(t,a){n=t.map(t=>{const n=e(r,"art-chapter"),o=e(n,"art-chapter-inner"),i=t.end-t.start;return n.dataset.start=String(t.start),n.dataset.end=String(t.end),n.dataset.duration=String(i),n.dataset.title=t.title.trim(),n.style.width=i/a*100+"%",{start:t.start,end:t.end,title:t.title.trim(),bars:{hover:e(o,"art-progress-hover"),loaded:e(o,"art-progress-loaded"),played:e(o,"art-progress-played")}}})},setBar:function(e,i,s){if("hover"!==e&&"loaded"!==e&&"played"!==e)return;if(!Number.isFinite(i))return;const l=s*i;let p;for(const t of n)t.bars[e].style.width=l<t.start?"0px":l>t.end?"100%":(l-t.start)/(t.end-t.start)*100+"%",l>=t.start&&l<=t.end&&(p=t);if("hover"!==e)return;o();const d=r.clientWidth*i;(null==p?void 0:p.title)&&d>0&&(a.textContent=p.title,a.style.visibility="visible",a.style.left=`${Math.max(0,Math.min(d-a.clientWidth/2,t.clientWidth-a.clientWidth))}px`)},destroy:function(){i(),r.remove(),a.remove()}}}(a);function i(t){if(!o||e.isDestroy)return;o.clear(),r.classList.remove(n);const a=function(t=[],e){if(!Array.isArray(t)||!t.length||!Number.isFinite(e)||e<=0)return[];t.sort((t,e)=>t.start-e.start);for(const[n,o]of t.entries()){if(!o||"number"!==typeof o.start||"number"!==typeof o.end||"string"!==typeof o.title)throw new TypeError("Illegal chapter data type");o.end===1/0&&(o.end=e);const r=t[n+1];if(!Number.isFinite(o.start)||!Number.isFinite(o.end)||o.start<0||o.end>e||o.start>=o.end||r&&o.end>r.start)throw new Error("Illegal chapter time point")}const r=t[0],a=t[t.length-1];r&&r.start>0&&t.unshift({start:0,end:r.start,title:""}),a&&a.end<e&&t.push({start:a.end,end:e,title:""});for(let n=0;n<t.length-1;n++){const e=t[n],r=t[n+1];e&&r&&e.end!==r.start&&t.splice(n+1,0,{start:e.end,end:r.start,title:""})}return t}(t,e.duration);a.length&&(o.render(a,e.duration),r.classList.add(n),e.emit("setBar","loaded",e.loaded||0))}function s(t,r){e.isDestroy||null==o||o.setBar(t,r,e.duration)}function l(){i(t.chapters)}return e.on("setBar",s),e.once("video:loadedmetadata",l),e.on("destroy",function t(){e.off("setBar",s),e.off("video:loadedmetadata",l),e.off("destroy",t),null==o||o.destroy(),o=void 0,r.classList.remove(n)}),{name:"artplayerPluginChapter",update:({chapters:t})=>i(t)}}}});
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginChapter=e(),define(function(){return t.artplayerPluginChapter})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginChapter=e()}(this,function(){"use strict";return function(){if("undefined"===typeof document)return;const t="artplayer-plugin-chapter",e=()=>{let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,(document.head||document.documentElement).appendChild(e)),e.textContent=".artplayer-plugin-chapter .art-control-progress-inner{height:100%!important;background-color:transparent!important}.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-hover,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-loaded,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-played{display:none!important}.artplayer-plugin-chapter .art-control-thumbnails{bottom:calc(var(--art-bottom-gap) + 64px)!important}.artplayer-plugin-chapter .art-chapters{position:absolute;z-index:0;inset:0;display:flex;align-items:center;gap:4px;height:100%;transform:scaleY(1.25)}.artplayer-plugin-chapter .art-chapters .art-chapter{display:flex;align-items:center;height:100%}.artplayer-plugin-chapter .art-chapters .art-chapter .art-chapter-inner{position:relative;cursor:pointer;width:100%;height:50%;border-radius:10px;overflow:hidden;transition:height var(--art-transition-duration) ease;background-color:var(--art-progress-color)}.artplayer-plugin-chapter .art-chapters .art-chapter:hover .art-chapter-inner{height:100%}.artplayer-plugin-chapter .art-chapter-title{transform-origin:bottom center;transform:scale(.5);opacity:0;position:absolute;z-index:70;top:-50px;left:0;padding:3px 5px;line-height:1;font-size:14px;border-radius:var(--art-border-radius);white-space:nowrap;box-sizing:border-box;max-width:100%;overflow:hidden;text-overflow:ellipsis;background-color:var(--art-tip-background);transition:transform var(--art-transition-duration) ease,opacity var(--art-transition-duration) ease}.artplayer-plugin-chapter.art-progress-hover .art-chapter-title{transform:scale(1);opacity:1}"};document.getElementById(t)||"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,{once:!0})}(),function(t={}){return e=>{const r=e.template.$player,a=e.query(".art-control-progress-inner");if(!a)throw new Error("Missing ArtPlayer progress container");const n="artplayer-plugin-chapter";let o=function(t){const e=(e,r)=>{const a=t.ownerDocument.createElement("div");return a.className=r,e.appendChild(a),a},r=e(t,"art-chapters"),a=e(t,"art-chapter-title");let n=[];function o(){a.textContent="",a.style.left="0px",a.style.visibility="hidden"}function i(){n=[],r.textContent="",o()}return o(),{clear:i,render:function(t,a){n=t.map(t=>{const n=e(r,"art-chapter"),o=e(n,"art-chapter-inner"),i=t.end-t.start;return n.dataset.start=String(t.start),n.dataset.end=String(t.end),n.dataset.duration=String(i),n.dataset.title=t.title.trim(),n.style.width=i/a*100+"%",{start:t.start,end:t.end,title:t.title.trim(),bars:{hover:e(o,"art-progress-hover"),loaded:e(o,"art-progress-loaded"),played:e(o,"art-progress-played")}}})},setBar:function(e,i,s){if("hover"!==e&&"loaded"!==e&&"played"!==e)return;if(!Number.isFinite(i))return;const l=s*i;let d;for(const t of n)t.bars[e].style.width=l<t.start?"0px":l>t.end?"100%":(l-t.start)/(t.end-t.start)*100+"%",l>=t.start&&l<=t.end&&(d=t);if("hover"!==e)return;o();const p=r.clientWidth*i;(null==d?void 0:d.title)&&p>0&&(a.textContent=d.title,a.style.visibility="visible",a.style.left=`${Math.max(0,Math.min(p-a.clientWidth/2,t.clientWidth-a.clientWidth))}px`)},destroy:function(){i(),r.remove(),a.remove()}}}(a);function i(t){if(!o||e.isDestroy)return;o.clear(),r.classList.remove(n);const a=function(t=[],e){if(!Array.isArray(t)||!t.length||!Number.isFinite(e)||e<=0)return[];t.sort((t,e)=>t.start-e.start);for(const[n,o]of t.entries()){if(!o||"number"!==typeof o.start||"number"!==typeof o.end||"string"!==typeof o.title)throw new TypeError("Illegal chapter data type");o.end===1/0&&(o.end=e);const r=t[n+1];if(!Number.isFinite(o.start)||!Number.isFinite(o.end)||o.start<0||o.end>e||o.start>=o.end||r&&o.end>r.start)throw new Error("Illegal chapter time point")}const r=t[0],a=t[t.length-1];r&&r.start>0&&t.unshift({start:0,end:r.start,title:""}),a&&a.end<e&&t.push({start:a.end,end:e,title:""});for(let n=0;n<t.length-1;n++){const e=t[n],r=t[n+1];e&&r&&e.end!==r.start&&t.splice(n+1,0,{start:e.end,end:r.start,title:""})}return t}(t,e.duration);a.length&&(o.render(a,e.duration),r.classList.add(n),e.emit("setBar","loaded",e.loaded||0))}function s(t,r){e.isDestroy||null==o||o.setBar(t,r,e.duration)}function l(){i(t.chapters)}return e.on("setBar",s),e.once("video:loadedmetadata",l),e.on("destroy",function t(){e.off("setBar",s),e.off("video:loadedmetadata",l),e.off("destroy",t),null==o||o.destroy(),o=void 0,r.classList.remove(nLine truncated
+1 -1
View File
@@ -105,7 +105,7 @@ function createProgress(inner) {
clearTitle();
return { clear, render, setBar, destroy };
}
const style = ".artplayer-plugin-chapter .art-control-progress-inner {\n height: 100% !important;\n background-color: transparent !important;\n}\n.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-hover,\n.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-loaded,\n.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-played {\n display: none !important;\n}\n.artplayer-plugin-chapter .art-control-thumbnails {\n bottom: calc(var(--art-bottom-gap) + 64px) !important;\n}\n.artplayer-plugin-chapter .art-chapters {\n position: absolute;\n z-index: 0;\n inset: 0;\n display: flex;\n align-items: center;\n gap: 4px;\n height: 100%;\n transform: scaleY(1.25);\n}\n.artplayer-plugin-chapter .art-chapters .art-chapter {\n display: flex;\n align-items: center;\n height: 100%;\n}\n.artplayer-plugin-chapter .art-chapters .art-chapter .art-chapter-inner {\n position: relative;\n cursor: pointer;\n width: 100%;\n height: 50%;\n border-radius: 10px;\n overflow: hidden;\n transition: height var(--art-transition-duration) ease;\n background-color: var(--art-progress-color);\n}\n.artplayer-plugin-chapter .art-chapters .art-chapter:hover .art-chapter-inner {\n height: 100%;\n}\n.artplayer-plugin-chapter .art-chapter-title {\n transform-origin: bottom center;\n transform: scale(0.5);\n opacity: 0;\n position: absolute;\n z-index: 70;\n top: -50px;\n left: 0;\n padding: 3px 5px;\n line-height: 1;\n font-size: 14px;\n border-radius: var(--art-border-radius);\n white-space: nowrap;\n background-color: var(--art-tip-background);\n transition: transform var(--art-transition-duration) ease, opacity var(--art-transition-duration) ease;\n}\n.artplayer-plugin-chapter.art-progress-hover .art-chapter-title {\n transform: scale(1);\n opacity: 1;\n}\n";
const style = ".artplayer-plugin-chapter .art-control-progress-inner {\n height: 100% !important;\n background-color: transparent !important;\n}\n.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-hover,\n.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-loaded,\n.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-played {\n display: none !important;\n}\n.artplayer-plugin-chapter .art-control-thumbnails {\n bottom: calc(var(--art-bottom-gap) + 64px) !important;\n}\n.artplayer-plugin-chapter .art-chapters {\n position: absolute;\n z-index: 0;\n inset: 0;\n display: flex;\n align-items: center;\n gap: 4px;\n height: 100%;\n transform: scaleY(1.25);\n}\n.artplayer-plugin-chapter .art-chapters .art-chapter {\n display: flex;\n align-items: center;\n height: 100%;\n}\n.artplayer-plugin-chapter .art-chapters .art-chapter .art-chapter-inner {\n position: relative;\n cursor: pointer;\n width: 100%;\n height: 50%;\n border-radius: 10px;\n overflow: hidden;\n transition: height var(--art-transition-duration) ease;\n background-color: var(--art-progress-color);\n}\n.artplayer-plugin-chapter .art-chapters .art-chapter:hover .art-chapter-inner {\n height: 100%;\n}\n.artplayer-plugin-chapter .art-chapter-title {\n transform-origin: bottom center;\n transform: scale(0.5);\n opacity: 0;\n position: absolute;\n z-index: 70;\n top: -50px;\n left: 0;\n padding: 3px 5px;\n line-height: 1;\n font-size: 14px;\n border-radius: var(--art-border-radius);\n white-space: nowrap;\n box-sizing: border-box;\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n background-color: var(--art-tip-background);\n transition: transform var(--art-transition-duration) ease, opacity var(--art-transition-duration) ease;\n}\n.artplayer-plugin-chapter.art-progress-hover .art-chapter-title {\n transform: scale(1);\n opacity: 1;\n}\n";
function installStyle() {
if (typeof document === "undefined")
return;
@@ -36,6 +36,9 @@ attributes. A successful update adds the historical player class, then emits
`setBar('loaded', art.loaded || 0)` synchronously. Progress events update the three
bars; a shared boundary belongs to the later chapter for hover title selection,
matching the old traversal order. Titles use textContent, never caller HTML.
Title boxes are limited to the progress container width, including their padding,
and display an ellipsis for long text. Full textContent and data-title remain intact.
The renderer still clamps horizontal position; CSS owns text clipping, not normalization.
`update({})` and non-array chapter input clear the view. Invalid types throw
TypeError and invalid time ranges throw Error with the existing messages. NaN and
@@ -83,6 +86,17 @@ For UI, start at progress.ts/style.less and verify real mouse interaction. For e
or cleanup changes, start at index.ts and rerun early destroy/multiple-instance tests.
Any public declaration change also needs new/old compiler and tarball consumers.
`test/browser/chapter-combinations.spec.js` adds old/new core and plugin combinations
with quality selection, actual thumbnail hover, narrow titles, element fullscreen,
web fullscreen reparenting and trusted taps. Quality menus must finish opening before
selection and lose pointer/focus before progress hover. The published core hides its
menu with opacity/pointer-events; it does not use the candidate visibility rule.
The published core also has a recorded Windows WebKit quality-position reset; keep
that historical observation separate from the candidate's position-restoration assertion.
The touch cases use a 390px viewport, hasTouch and an iPhone user agent on the desktop
engines. They validate the mobile branch and trusted input, not an actual iPhone.
See refactor/changes/2026-09-12-PKG-CHAPTER-05-combinations.md for current evidence and gaps.
The public declaration remains available to old compilers and exports Chapters,
Option and Result as types. Modern import/require conditions select format-specific
bridges; typesVersions provides the legacy subpath to old resolvers. All bridges
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginChapter=e(),define(function(){return t.artplayerPluginChapter})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginChapter=e()}(this,function(){"use strict";return function(){if("undefined"===typeof document)return;const t="artplayer-plugin-chapter",e=()=>{let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,(document.head||document.documentElement).appendChild(e)),e.textContent=".artplayer-plugin-chapter .art-control-progress-inner{height:100%!important;background-color:transparent!important}.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-hover,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-loaded,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-played{display:none!important}.artplayer-plugin-chapter .art-control-thumbnails{bottom:calc(var(--art-bottom-gap) + 64px)!important}.artplayer-plugin-chapter .art-chapters{position:absolute;z-index:0;inset:0;display:flex;align-items:center;gap:4px;height:100%;transform:scaleY(1.25)}.artplayer-plugin-chapter .art-chapters .art-chapter{display:flex;align-items:center;height:100%}.artplayer-plugin-chapter .art-chapters .art-chapter .art-chapter-inner{position:relative;cursor:pointer;width:100%;height:50%;border-radius:10px;overflow:hidden;transition:height var(--art-transition-duration) ease;background-color:var(--art-progress-color)}.artplayer-plugin-chapter .art-chapters .art-chapter:hover .art-chapter-inner{height:100%}.artplayer-plugin-chapter .art-chapter-title{transform-origin:bottom center;transform:scale(.5);opacity:0;position:absolute;z-index:70;top:-50px;left:0;padding:3px 5px;line-height:1;font-size:14px;border-radius:var(--art-border-radius);white-space:nowrap;background-color:var(--art-tip-background);transition:transform var(--art-transition-duration) ease,opacity var(--art-transition-duration) ease}.artplayer-plugin-chapter.art-progress-hover .art-chapter-title{transform:scale(1);opacity:1}"};document.getElementById(t)||"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,{once:!0})}(),function(t={}){return e=>{const r=e.template.$player,a=e.query(".art-control-progress-inner");if(!a)throw new Error("Missing ArtPlayer progress container");const n="artplayer-plugin-chapter";let o=function(t){const e=(e,r)=>{const a=t.ownerDocument.createElement("div");return a.className=r,e.appendChild(a),a},r=e(t,"art-chapters"),a=e(t,"art-chapter-title");let n=[];function o(){a.textContent="",a.style.left="0px",a.style.visibility="hidden"}function i(){n=[],r.textContent="",o()}return o(),{clear:i,render:function(t,a){n=t.map(t=>{const n=e(r,"art-chapter"),o=e(n,"art-chapter-inner"),i=t.end-t.start;return n.dataset.start=String(t.start),n.dataset.end=String(t.end),n.dataset.duration=String(i),n.dataset.title=t.title.trim(),n.style.width=i/a*100+"%",{start:t.start,end:t.end,title:t.title.trim(),bars:{hover:e(o,"art-progress-hover"),loaded:e(o,"art-progress-loaded"),played:e(o,"art-progress-played")}}})},setBar:function(e,i,s){if("hover"!==e&&"loaded"!==e&&"played"!==e)return;if(!Number.isFinite(i))return;const l=s*i;let p;for(const t of n)t.bars[e].style.width=l<t.start?"0px":l>t.end?"100%":(l-t.start)/(t.end-t.start)*100+"%",l>=t.start&&l<=t.end&&(p=t);if("hover"!==e)return;o();const d=r.clientWidth*i;p?.title&&d>0&&(a.textContent=p.title,a.style.visibility="visible",a.style.left=`${Math.max(0,Math.min(d-a.clientWidth/2,t.clientWidth-a.clientWidth))}px`)},destroy:function(){i(),r.remove(),a.remove()}}}(a);function i(t){if(!o||e.isDestroy)return;o.clear(),r.classList.remove(n);const a=function(t=[],e){if(!Array.isArray(t)||!t.length||!Number.isFinite(e)||e<=0)return[];t.sort((t,e)=>t.start-e.start);for(const[n,o]of t.entries()){if(!o||"number"!==typeof o.start||"number"!==typeof o.end||"string"!==typeof o.title)throw new TypeError("Illegal chapter data type");o.end===1/0&&(o.end=e);const r=t[n+1];if(!Number.isFinite(o.start)||!Number.isFinite(o.end)||o.start<0||o.end>e||o.start>=o.end||r&&o.end>r.start)throw new Error("Illegal chapter time point")}const r=t[0],a=t[t.length-1];r&&r.start>0&&t.unshift({start:0,end:r.start,title:""}),a&&a.end<e&&t.push({start:a.end,end:e,title:""});for(let n=0;n<t.length-1;n++){const e=t[n],r=t[n+1];e&&r&&e.end!==r.start&&t.splice(n+1,0,{start:e.end,end:r.start,title:""})}return t}(t,e.duration);a.length&&(o.render(a,e.duration),r.classList.add(n),e.emit("setBar","loaded",e.loaded||0))}function s(t,r){e.isDestroy||o?.setBar(t,r,e.duration)}function l(){i(t.chapters)}return e.on("setBar",s),e.once("video:loadedmetadata",l),e.on("destroy",function t(){e.off("setBar",s),e.off("video:loadedmetadata",l),e.off("destroy",t),o?.destroy(),o=void 0,r.classList.remove(n)}),{name:"artplayerPluginChapter",update:({chapters:t})=>i(t)}}}});
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginChapter=e(),define(function(){return t.artplayerPluginChapter})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginChapter=e()}(this,function(){"use strict";return function(){if("undefined"===typeof document)return;const t="artplayer-plugin-chapter",e=()=>{let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,(document.head||document.documentElement).appendChild(e)),e.textContent=".artplayer-plugin-chapter .art-control-progress-inner{height:100%!important;background-color:transparent!important}.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-hover,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-loaded,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-played{display:none!important}.artplayer-plugin-chapter .art-control-thumbnails{bottom:calc(var(--art-bottom-gap) + 64px)!important}.artplayer-plugin-chapter .art-chapters{position:absolute;z-index:0;inset:0;display:flex;align-items:center;gap:4px;height:100%;transform:scaleY(1.25)}.artplayer-plugin-chapter .art-chapters .art-chapter{display:flex;align-items:center;height:100%}.artplayer-plugin-chapter .art-chapters .art-chapter .art-chapter-inner{position:relative;cursor:pointer;width:100%;height:50%;border-radius:10px;overflow:hidden;transition:height var(--art-transition-duration) ease;background-color:var(--art-progress-color)}.artplayer-plugin-chapter .art-chapters .art-chapter:hover .art-chapter-inner{height:100%}.artplayer-plugin-chapter .art-chapter-title{transform-origin:bottom center;transform:scale(.5);opacity:0;position:absolute;z-index:70;top:-50px;left:0;padding:3px 5px;line-height:1;font-size:14px;border-radius:var(--art-border-radius);white-space:nowrap;box-sizing:border-box;max-width:100%;overflow:hidden;text-overflow:ellipsis;background-color:var(--art-tip-background);transition:transform var(--art-transition-duration) ease,opacity var(--art-transition-duration) ease}.artplayer-plugin-chapter.art-progress-hover .art-chapter-title{transform:scale(1);opacity:1}"};document.getElementById(t)||"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,{once:!0})}(),function(t={}){return e=>{const r=e.template.$player,a=e.query(".art-control-progress-inner");if(!a)throw new Error("Missing ArtPlayer progress container");const n="artplayer-plugin-chapter";let o=function(t){const e=(e,r)=>{const a=t.ownerDocument.createElement("div");return a.className=r,e.appendChild(a),a},r=e(t,"art-chapters"),a=e(t,"art-chapter-title");let n=[];function o(){a.textContent="",a.style.left="0px",a.style.visibility="hidden"}function i(){n=[],r.textContent="",o()}return o(),{clear:i,render:function(t,a){n=t.map(t=>{const n=e(r,"art-chapter"),o=e(n,"art-chapter-inner"),i=t.end-t.start;return n.dataset.start=String(t.start),n.dataset.end=String(t.end),n.dataset.duration=String(i),n.dataset.title=t.title.trim(),n.style.width=i/a*100+"%",{start:t.start,end:t.end,title:t.title.trim(),bars:{hover:e(o,"art-progress-hover"),loaded:e(o,"art-progress-loaded"),played:e(o,"art-progress-played")}}})},setBar:function(e,i,s){if("hover"!==e&&"loaded"!==e&&"played"!==e)return;if(!Number.isFinite(i))return;const l=s*i;let d;for(const t of n)t.bars[e].style.width=l<t.start?"0px":l>t.end?"100%":(l-t.start)/(t.end-t.start)*100+"%",l>=t.start&&l<=t.end&&(d=t);if("hover"!==e)return;o();const p=r.clientWidth*i;d?.title&&p>0&&(a.textContent=d.title,a.style.visibility="visible",a.style.left=`${Math.max(0,Math.min(p-a.clientWidth/2,t.clientWidth-a.clientWidth))}px`)},destroy:function(){i(),r.remove(),a.remove()}}}(a);function i(t){if(!o||e.isDestroy)return;o.clear(),r.classList.remove(n);const a=function(t=[],e){if(!Array.isArray(t)||!t.length||!Number.isFinite(e)||e<=0)return[];t.sort((t,e)=>t.start-e.start);for(const[n,o]of t.entries()){if(!o||"number"!==typeof o.start||"number"!==typeof o.end||"string"!==typeof o.title)throw new TypeError("Illegal chapter data type");o.end===1/0&&(o.end=e);const r=t[n+1];if(!Number.isFinite(o.start)||!Number.isFinite(o.end)||o.start<0||o.end>e||o.start>=o.end||r&&o.end>r.start)throw new Error("Illegal chapter time point")}const r=t[0],a=t[t.length-1];r&&r.start>0&&t.unshift({start:0,end:r.start,title:""}),a&&a.end<e&&t.push({start:a.end,end:e,title:""});for(let n=0;n<t.length-1;n++){const e=t[n],r=t[n+1];e&&r&&e.end!==r.start&&t.splice(n+1,0,{start:e.end,end:r.start,title:""})}return t}(t,e.duration);a.length&&(o.render(a,e.duration),r.classList.add(n),e.emit("setBar","loaded",e.loaded||0))}function s(t,r){e.isDestroy||o?.setBar(t,r,e.duration)}function l(){i(t.chapters)}return e.on("setBar",s),e.once("video:loadedmetadata",l),e.on("destroy",function t(){e.off("setBar",s),e.off("video:loadedmetadata",l),e.off("destroy",t),o?.destroy(),o=void 0,r.classList.remove(n)}),{name:"artplayerPluginChapteLine truncated
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginChapter=e(),define(function(){return t.artplayerPluginChapter})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginChapter=e()}(this,function(){"use strict";return function(){if("undefined"===typeof document)return;const t="artplayer-plugin-chapter",e=()=>{let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,(document.head||document.documentElement).appendChild(e)),e.textContent=".artplayer-plugin-chapter .art-control-progress-inner{height:100%!important;background-color:transparent!important}.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-hover,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-loaded,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-played{display:none!important}.artplayer-plugin-chapter .art-control-thumbnails{bottom:calc(var(--art-bottom-gap) + 64px)!important}.artplayer-plugin-chapter .art-chapters{position:absolute;z-index:0;inset:0;display:flex;align-items:center;gap:4px;height:100%;transform:scaleY(1.25)}.artplayer-plugin-chapter .art-chapters .art-chapter{display:flex;align-items:center;height:100%}.artplayer-plugin-chapter .art-chapters .art-chapter .art-chapter-inner{position:relative;cursor:pointer;width:100%;height:50%;border-radius:10px;overflow:hidden;transition:height var(--art-transition-duration) ease;background-color:var(--art-progress-color)}.artplayer-plugin-chapter .art-chapters .art-chapter:hover .art-chapter-inner{height:100%}.artplayer-plugin-chapter .art-chapter-title{transform-origin:bottom center;transform:scale(.5);opacity:0;position:absolute;z-index:70;top:-50px;left:0;padding:3px 5px;line-height:1;font-size:14px;border-radius:var(--art-border-radius);white-space:nowrap;background-color:var(--art-tip-background);transition:transform var(--art-transition-duration) ease,opacity var(--art-transition-duration) ease}.artplayer-plugin-chapter.art-progress-hover .art-chapter-title{transform:scale(1);opacity:1}"};document.getElementById(t)||"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,{once:!0})}(),function(t={}){return e=>{const r=e.template.$player,a=e.query(".art-control-progress-inner");if(!a)throw new Error("Missing ArtPlayer progress container");const n="artplayer-plugin-chapter";let o=function(t){const e=(e,r)=>{const a=t.ownerDocument.createElement("div");return a.className=r,e.appendChild(a),a},r=e(t,"art-chapters"),a=e(t,"art-chapter-title");let n=[];function o(){a.textContent="",a.style.left="0px",a.style.visibility="hidden"}function i(){n=[],r.textContent="",o()}return o(),{clear:i,render:function(t,a){n=t.map(t=>{const n=e(r,"art-chapter"),o=e(n,"art-chapter-inner"),i=t.end-t.start;return n.dataset.start=String(t.start),n.dataset.end=String(t.end),n.dataset.duration=String(i),n.dataset.title=t.title.trim(),n.style.width=i/a*100+"%",{start:t.start,end:t.end,title:t.title.trim(),bars:{hover:e(o,"art-progress-hover"),loaded:e(o,"art-progress-loaded"),played:e(o,"art-progress-played")}}})},setBar:function(e,i,s){if("hover"!==e&&"loaded"!==e&&"played"!==e)return;if(!Number.isFinite(i))return;const l=s*i;let p;for(const t of n)t.bars[e].style.width=l<t.start?"0px":l>t.end?"100%":(l-t.start)/(t.end-t.start)*100+"%",l>=t.start&&l<=t.end&&(p=t);if("hover"!==e)return;o();const d=r.clientWidth*i;(null==p?void 0:p.title)&&d>0&&(a.textContent=p.title,a.style.visibility="visible",a.style.left=`${Math.max(0,Math.min(d-a.clientWidth/2,t.clientWidth-a.clientWidth))}px`)},destroy:function(){i(),r.remove(),a.remove()}}}(a);function i(t){if(!o||e.isDestroy)return;o.clear(),r.classList.remove(n);const a=function(t=[],e){if(!Array.isArray(t)||!t.length||!Number.isFinite(e)||e<=0)return[];t.sort((t,e)=>t.start-e.start);for(const[n,o]of t.entries()){if(!o||"number"!==typeof o.start||"number"!==typeof o.end||"string"!==typeof o.title)throw new TypeError("Illegal chapter data type");o.end===1/0&&(o.end=e);const r=t[n+1];if(!Number.isFinite(o.start)||!Number.isFinite(o.end)||o.start<0||o.end>e||o.start>=o.end||r&&o.end>r.start)throw new Error("Illegal chapter time point")}const r=t[0],a=t[t.length-1];r&&r.start>0&&t.unshift({start:0,end:r.start,title:""}),a&&a.end<e&&t.push({start:a.end,end:e,title:""});for(let n=0;n<t.length-1;n++){const e=t[n],r=t[n+1];e&&r&&e.end!==r.start&&t.splice(n+1,0,{start:e.end,end:r.start,title:""})}return t}(t,e.duration);a.length&&(o.render(a,e.duration),r.classList.add(n),e.emit("setBar","loaded",e.loaded||0))}function s(t,r){e.isDestroy||null==o||o.setBar(t,r,e.duration)}function l(){i(t.chapters)}return e.on("setBar",s),e.once("video:loadedmetadata",l),e.on("destroy",function t(){e.off("setBar",s),e.off("video:loadedmetadata",l),e.off("destroy",t),null==o||o.destroy(),o=void 0,r.classList.remove(n)}),{name:"artplayerPluginChapter",update:({chapters:t})=>i(t)}}}});
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginChapter=e(),define(function(){return t.artplayerPluginChapter})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginChapter=e()}(this,function(){"use strict";return function(){if("undefined"===typeof document)return;const t="artplayer-plugin-chapter",e=()=>{let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,(document.head||document.documentElement).appendChild(e)),e.textContent=".artplayer-plugin-chapter .art-control-progress-inner{height:100%!important;background-color:transparent!important}.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-hover,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-loaded,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-played{display:none!important}.artplayer-plugin-chapter .art-control-thumbnails{bottom:calc(var(--art-bottom-gap) + 64px)!important}.artplayer-plugin-chapter .art-chapters{position:absolute;z-index:0;inset:0;display:flex;align-items:center;gap:4px;height:100%;transform:scaleY(1.25)}.artplayer-plugin-chapter .art-chapters .art-chapter{display:flex;align-items:center;height:100%}.artplayer-plugin-chapter .art-chapters .art-chapter .art-chapter-inner{position:relative;cursor:pointer;width:100%;height:50%;border-radius:10px;overflow:hidden;transition:height var(--art-transition-duration) ease;background-color:var(--art-progress-color)}.artplayer-plugin-chapter .art-chapters .art-chapter:hover .art-chapter-inner{height:100%}.artplayer-plugin-chapter .art-chapter-title{transform-origin:bottom center;transform:scale(.5);opacity:0;position:absolute;z-index:70;top:-50px;left:0;padding:3px 5px;line-height:1;font-size:14px;border-radius:var(--art-border-radius);white-space:nowrap;box-sizing:border-box;max-width:100%;overflow:hidden;text-overflow:ellipsis;background-color:var(--art-tip-background);transition:transform var(--art-transition-duration) ease,opacity var(--art-transition-duration) ease}.artplayer-plugin-chapter.art-progress-hover .art-chapter-title{transform:scale(1);opacity:1}"};document.getElementById(t)||"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,{once:!0})}(),function(t={}){return e=>{const r=e.template.$player,a=e.query(".art-control-progress-inner");if(!a)throw new Error("Missing ArtPlayer progress container");const n="artplayer-plugin-chapter";let o=function(t){const e=(e,r)=>{const a=t.ownerDocument.createElement("div");return a.className=r,e.appendChild(a),a},r=e(t,"art-chapters"),a=e(t,"art-chapter-title");let n=[];function o(){a.textContent="",a.style.left="0px",a.style.visibility="hidden"}function i(){n=[],r.textContent="",o()}return o(),{clear:i,render:function(t,a){n=t.map(t=>{const n=e(r,"art-chapter"),o=e(n,"art-chapter-inner"),i=t.end-t.start;return n.dataset.start=String(t.start),n.dataset.end=String(t.end),n.dataset.duration=String(i),n.dataset.title=t.title.trim(),n.style.width=i/a*100+"%",{start:t.start,end:t.end,title:t.title.trim(),bars:{hover:e(o,"art-progress-hover"),loaded:e(o,"art-progress-loaded"),played:e(o,"art-progress-played")}}})},setBar:function(e,i,s){if("hover"!==e&&"loaded"!==e&&"played"!==e)return;if(!Number.isFinite(i))return;const l=s*i;let d;for(const t of n)t.bars[e].style.width=l<t.start?"0px":l>t.end?"100%":(l-t.start)/(t.end-t.start)*100+"%",l>=t.start&&l<=t.end&&(d=t);if("hover"!==e)return;o();const p=r.clientWidth*i;(null==d?void 0:d.title)&&p>0&&(a.textContent=d.title,a.style.visibility="visible",a.style.left=`${Math.max(0,Math.min(p-a.clientWidth/2,t.clientWidth-a.clientWidth))}px`)},destroy:function(){i(),r.remove(),a.remove()}}}(a);function i(t){if(!o||e.isDestroy)return;o.clear(),r.classList.remove(n);const a=function(t=[],e){if(!Array.isArray(t)||!t.length||!Number.isFinite(e)||e<=0)return[];t.sort((t,e)=>t.start-e.start);for(const[n,o]of t.entries()){if(!o||"number"!==typeof o.start||"number"!==typeof o.end||"string"!==typeof o.title)throw new TypeError("Illegal chapter data type");o.end===1/0&&(o.end=e);const r=t[n+1];if(!Number.isFinite(o.start)||!Number.isFinite(o.end)||o.start<0||o.end>e||o.start>=o.end||r&&o.end>r.start)throw new Error("Illegal chapter time point")}const r=t[0],a=t[t.length-1];r&&r.start>0&&t.unshift({start:0,end:r.start,title:""}),a&&a.end<e&&t.push({start:a.end,end:e,title:""});for(let n=0;n<t.length-1;n++){const e=t[n],r=t[n+1];e&&r&&e.end!==r.start&&t.splice(n+1,0,{start:e.end,end:r.start,title:""})}return t}(t,e.duration);a.length&&(o.render(a,e.duration),r.classList.add(n),e.emit("setBar","loaded",e.loaded||0))}function s(t,r){e.isDestroy||null==o||o.setBar(t,r,e.duration)}function l(){i(t.chapters)}return e.on("setBar",s),e.once("video:loadedmetadata",l),e.on("destroy",function t(){e.off("setBar",s),e.off("video:loadedmetadata",l),e.off("destroy",t),null==o||o.destroy(),o=void 0,r.classList.remove(nLine truncated
@@ -105,7 +105,7 @@ function createProgress(inner) {
clearTitle();
return { clear, render, setBar, destroy };
}
const style = ".artplayer-plugin-chapter .art-control-progress-inner {\n height: 100% !important;\n background-color: transparent !important;\n}\n.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-hover,\n.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-loaded,\n.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-played {\n display: none !important;\n}\n.artplayer-plugin-chapter .art-control-thumbnails {\n bottom: calc(var(--art-bottom-gap) + 64px) !important;\n}\n.artplayer-plugin-chapter .art-chapters {\n position: absolute;\n z-index: 0;\n inset: 0;\n display: flex;\n align-items: center;\n gap: 4px;\n height: 100%;\n transform: scaleY(1.25);\n}\n.artplayer-plugin-chapter .art-chapters .art-chapter {\n display: flex;\n align-items: center;\n height: 100%;\n}\n.artplayer-plugin-chapter .art-chapters .art-chapter .art-chapter-inner {\n position: relative;\n cursor: pointer;\n width: 100%;\n height: 50%;\n border-radius: 10px;\n overflow: hidden;\n transition: height var(--art-transition-duration) ease;\n background-color: var(--art-progress-color);\n}\n.artplayer-plugin-chapter .art-chapters .art-chapter:hover .art-chapter-inner {\n height: 100%;\n}\n.artplayer-plugin-chapter .art-chapter-title {\n transform-origin: bottom center;\n transform: scale(0.5);\n opacity: 0;\n position: absolute;\n z-index: 70;\n top: -50px;\n left: 0;\n padding: 3px 5px;\n line-height: 1;\n font-size: 14px;\n border-radius: var(--art-border-radius);\n white-space: nowrap;\n background-color: var(--art-tip-background);\n transition: transform var(--art-transition-duration) ease, opacity var(--art-transition-duration) ease;\n}\n.artplayer-plugin-chapter.art-progress-hover .art-chapter-title {\n transform: scale(1);\n opacity: 1;\n}\n";
const style = ".artplayer-plugin-chapter .art-control-progress-inner {\n height: 100% !important;\n background-color: transparent !important;\n}\n.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-hover,\n.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-loaded,\n.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-played {\n display: none !important;\n}\n.artplayer-plugin-chapter .art-control-thumbnails {\n bottom: calc(var(--art-bottom-gap) + 64px) !important;\n}\n.artplayer-plugin-chapter .art-chapters {\n position: absolute;\n z-index: 0;\n inset: 0;\n display: flex;\n align-items: center;\n gap: 4px;\n height: 100%;\n transform: scaleY(1.25);\n}\n.artplayer-plugin-chapter .art-chapters .art-chapter {\n display: flex;\n align-items: center;\n height: 100%;\n}\n.artplayer-plugin-chapter .art-chapters .art-chapter .art-chapter-inner {\n position: relative;\n cursor: pointer;\n width: 100%;\n height: 50%;\n border-radius: 10px;\n overflow: hidden;\n transition: height var(--art-transition-duration) ease;\n background-color: var(--art-progress-color);\n}\n.artplayer-plugin-chapter .art-chapters .art-chapter:hover .art-chapter-inner {\n height: 100%;\n}\n.artplayer-plugin-chapter .art-chapter-title {\n transform-origin: bottom center;\n transform: scale(0.5);\n opacity: 0;\n position: absolute;\n z-index: 70;\n top: -50px;\n left: 0;\n padding: 3px 5px;\n line-height: 1;\n font-size: 14px;\n border-radius: var(--art-border-radius);\n white-space: nowrap;\n box-sizing: border-box;\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n background-color: var(--art-tip-background);\n transition: transform var(--art-transition-duration) ease, opacity var(--art-transition-duration) ease;\n}\n.artplayer-plugin-chapter.art-progress-hover .art-chapter-title {\n transform: scale(1);\n opacity: 1;\n}\n";
function installStyle() {
if (typeof document === "undefined")
return;
@@ -59,6 +59,10 @@
font-size: 14px;
border-radius: var(--art-border-radius);
white-space: nowrap;
box-sizing: border-box;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
background-color: var(--art-tip-background);
transition:
transform var(--art-transition-duration) ease,
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,60 @@
# PKG-CHAPTER-05:进度、清晰度、缩略图与全屏组合
状态:进行中。起点 d18c5456,工作区已核实干净;Chapter-04 和 CORE-22 前置已完成。
现有 chapter.spec.js 的全面章节用例固定旧核心。新增组合覆盖旧/新核心、旧/新 chapter,
使用本地固定 MP4 和缩略图网格验证真实 hover、seek、清晰度切换、显式章节更新、原生/
网页全屏进出与缩略图/标题位置。触摸部分在 390px、hasTouch 与 iPhone UA 模拟环境中
要求可信 touchstart,并检查进入/退出网页全屏后的章节 seek;不声称已在 iPhone 真机运行。
本阶段先保留实际测试结果;任何失败先核对原生能力、旧版行为、测试夹具与候选实现,
不改断言使候选假通过。物理设备和未覆盖场景继续作为未验收范围。
## 已确认的显示缺陷与源码修复
390px 的长标题在新旧插件都超过进度条右边界;真实页面显示文字被播放器外边缘直接裁切。
先加入候选不越界的失败断言以及旧版溢出观察,三引擎分别两种核心:6 个候选失败、
6 个历史观察通过。style.less 现使用 max-width:100%、border-box、overflow:hidden 和
text-overflow:ellipsis,把显示宽度交给样式层;progress.ts/chapters.ts、同步 update、
数组原地变更、事件以及声明不变。完整 textContent 与 data-title 仍按原样保留。
正式 main/legacy/module 与 docs/compiled 已由 yarn build artplayer-plugin-chapter 生成。
## 夹具修正与尚未关闭的时序疑点
第一轮 48 项:36 个全屏/触摸组合通过,12 个 quality 场景失败。10 个卡在 hover 结束
章节:鼠标点在展开的 quality 菜单覆盖区域,尚未进入进度条;两个旧核心 WebKit 的
位置回到 0,已有 progress-quality.spec.js 复现该历史缺陷。该旧版观察保持显式分支,
候选核心仍必须恢复切换前位置,没有用旧缺陷放宽候选断言。
第二轮先移动鼠标,仍 12 项失败:候选菜单有焦点保持;旧 core CSS 原本只有 opacity=0
和 pointer-events:none,并无 visibility:hidden,toBeHidden 不能表示它的视觉关闭。
第三轮外部 Pause 按钮点击后,候选菜单退出,旧核心的 toBeHidden 仍失败。对照实际
发布包样式后改为检查 opacity=0,选择前等待 opacity=1,随后仍要求真实标题与缩略图
可见、位置不重叠。所有首轮报告均保留;这是菜单交互前提修正,不是删除章节断言。
第三轮 24 项中长标题全部通过;另有候选核心 WebKit 的 quality restart 等待超时。
修正菜单后的一轮 12 quality 为 11 通过/1 失败,最后候选核心+候选章节仍在 7 秒
轮询内未看到 restart,而 afterEach 已记录 restart、新源、恢复位置、readyState=4,
没有媒体错误。来源可能涉及原生 seek/事件耗时,尚未证明;不据此修改核心或提高超时,
也不因后续某次通过关闭这个疑点。原始事件序列和失败 trace 继续保留。
本次未安装依赖、未更改发布版本。触摸模拟不代替物理 Safari/iOS/Android,完整分发与
8082 demo 属于 Chapter-06。任务仍 doing,源码修复与证据以独立 checkpoint 提交。
回退本检查点会移除长标题显示限制,恢复旧显示;已发布的 npm 包没有发生变化。
## 本检查点验证结果
- 正式 main 和 legacy 分别 102 项通过,无重试/跳过;使用明确产物映射,核心与章节包
的浏览器服务指纹均与实际 dist 字节一致,不是冒用源码运行结果。
- 每组包含原有 42 项章节用例与新增 60 项组合。新增部分为 12 个长标题(含 6 个历史
溢出观察)、12 个 quality/缩略图(含两项旧核心 WebKit 位置归零观察)、24 个真实元素/
网页全屏、12 个触摸模拟。不能把所有通过项都称为没有历史缺陷的候选验收。
- 完整 CI 599 项(550 单元、14 工程、35 基线)通过;严格源码/类型消费、声明一致性、
lint 通过,保留一条已知生成声明 warning。最终新增测试定向 lint 通过。
- 长标题修复后截图已实际查看:文字省略且标题框在播放器内,完整内容保持在 DOM 数据。
- 全部阶段包括 48 项首轮、12 项菜单修正失败、12 项长标题先红后绿、24 项第三轮
17 通过/7 失败及最后 quality 11/1 均保留,见 [冻结证据](../baselines/chapter-combinations-checkpoint.json)。
CHAPTER-LAYOUT-01 已按具体显示范围关闭;CHAPTER-TIMING-01 仍 open。最终产物两轮
通过不解释早期时序超时,不能以它们消除待查证据。当前 217 项,70 done、3 doing、144 todo。
下一步可推进独立包;Chapter-05 的物理设备和时序缺口继续随风险台账接续,不阻塞全局目标。
+3 -2
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 217 项,范围 22 个包及工作区/示例。
状态:todo 145 / doing 2 / blocked 0 / done 70 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 144 / doing 3 / blocked 0 / done 70 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -138,7 +138,7 @@
| PKG-CHAPTER-02 | artplayer-plugin-chapter<br>建立特有行为与错误测试 | PKG-CHAPTER-01, ENG-03, ENG-05 | 区间重叠/空列表/边界 seek、hover 标题和 update 用例 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言;修正 BASE-TYPE-02 的可选参数声明并补正反例 | M | done |
| PKG-CHAPTER-03 | artplayer-plugin-chapter<br>以 TypeScript 拆分内部职责与资源 | PKG-CHAPTER-02, ENG-07 | 以严格 TS 拆分区间计算、进度 DOM、事件生命周期与样式注入;公开声明消费在 04 闭环 | 不依赖新核心方法或大范围设置重构;旧 API/事件和资源通过,必要适配限包内 | M | done |
| PKG-CHAPTER-04 | artplayer-plugin-chapter<br>迁移自有源码和公开类型 | PKG-CHAPTER-03, ENG-04, ENG-06 | 承接 03 的自有源码 TS 化,完善公开 Chapters/Option/Result 与 update 推导,关闭声明模块解析和旧消费差异 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容;处理 BASE-TYPE-01/03,保持运行时和新旧 TS 模块解析消费 | M | done |
| PKG-CHAPTER-05 | artplayer-plugin-chapter<br>验证新旧核心和组合 | PKG-CHAPTER-04, CORE-22 | chapter + quality/thumbnail、移动和全屏进度场景 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | M | todo |
| PKG-CHAPTER-05 | artplayer-plugin-chapter<br>验证新旧核心和组合 | PKG-CHAPTER-04, CORE-22 | chapter + quality/thumbnail、移动和全屏进度场景 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | M | doing |
| PKG-CHAPTER-06 | artplayer-plugin-chapter<br>验证分发并同步文档 | PKG-CHAPTER-05, ENG-07 | chapter.js 示例、产物和变更记录 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | M | todo |
## 5 包迁移:artplayer-plugin-ambilight
@@ -468,6 +468,7 @@
- PKG-CHAPTER-02: [记录](changes/2026-09-10-PKG-CHAPTER-02-tests.md) [记录](baselines/chapter-validation.json)
- PKG-CHAPTER-03: [记录](changes/2026-09-10-PKG-CHAPTER-03-typescript-modules.md) [记录](baselines/chapter-migration-validation.json)
- PKG-CHAPTER-04: [记录](changes/2026-09-10-PKG-CHAPTER-04-public-types.md) [记录](baselines/chapter-types-validation.json)
- PKG-CHAPTER-05: [记录](changes/2026-09-12-PKG-CHAPTER-05-combinations.md) [记录](baselines/chapter-combinations-checkpoint.json)
- PKG-AUDIO-01: [记录](changes/2026-09-12-PKG-AUDIO-01-contract.md) [记录](baselines/audio-track-contract.md) [记录](baselines/audio-track-release.json)
- PKG-AUDIO-02: [记录](changes/2026-09-12-PKG-AUDIO-02-tests.md) [记录](audio-validation.md) [记录](baselines/audio-validation.json)
- PKG-AUDIO-03: [记录](changes/2026-09-12-PKG-AUDIO-03-lifecycle.md) [记录](baselines/audio-lifecycle-validation.json)
+15
View File
@@ -1,5 +1,20 @@
# 进度与证据
## 当前实施:Chapter-05 组合验证与长标题修复检查点
正式 main/legacy 的章节基础及新旧核心/插件组合各 102 项通过,完整 CI 599 项通过。
新增覆盖清晰度/缩略图、窄屏长标题、真实元素与网页全屏,以及 390px 可信触摸模拟。
长标题超出进度条的显示缺陷已通过 CSS 修复,完整文本与公开接口不变;截图已查看。
保留菜单交互前提修正的失败记录、旧核心 WebKit 位置归零观察。一个早期 WebKit
quality restart 等待超时的确切原因尚未证明,CHAPTER-TIMING-01 保持 open;后续
两种产物通过不能关闭它。触摸模拟没有替代手机真机。Chapter-05 仍 doing,尚未执行 06。
当前 217 项:70 done、3 doing、144 todo。Audio-05、HLS-SDK-01 的风险仍保留。
本批为本地 checkpoint,未推送或发布。后续优先推进独立包的源码/契约迁移,并在有效
环境中接续开放的媒体/设备验证。详见 [本次变更](changes/2026-09-12-PKG-CHAPTER-05-combinations.md)
和 [冻结证据](baselines/chapter-combinations-checkpoint.json)。
## 当前实施:Audio-05 恢复修复与真实缓冲检查点
已修复旧核心时间零 seek 后候选音频漏恢复,严格 TS 与公开接口不变;三格式 Node 97 项、
+2
View File
@@ -133,3 +133,5 @@
| CORE-SOURCE-01 | resolved / 已复现 | 候选核心连续切源丢失原播放意图 | CORE-24 |
| AUDIO-RESUME-01 | resolved / 已复现 | WebKit 旧核心切源后候选音频保持暂停 | PKG-AUDIO-05 |
| AUDIO-BUFFER-01 | open / 已复现 | Windows WebKit 原生媒体无法在受限响应下推进至真实缓冲 | PKG-AUDIO-05 |
| CHAPTER-LAYOUT-01 | resolved / 已复现 | 长章节标题超出窄播放器进度条宽度 | PKG-CHAPTER-05 |
| CHAPTER-TIMING-01 | open / 已复现 | Windows WebKit 清晰度切换曾在等待窗口内未见 restart,稍后状态恢复 | PKG-CHAPTER-05 |
+37
View File
@@ -2791,6 +2791,43 @@
],
"compatibleResolution": "保留真实 waiting 断言;使用独立原生对照和其他实际支持环境/输入继续验证,不将没有播放推进的诊断当成缓冲通过,也不凭此改变插件 API 或否定真实 Safari 支持。",
"closureCriteria": "受影响的缓冲/恢复得到真实媒体证据;与原生能力和测试夹具差异分开,发布环境的剩余范围经显式处理。"
},
{
"id": "CHAPTER-LAYOUT-01",
"title": "长章节标题超出窄播放器进度条宽度",
"confirmation": "reproduced",
"status": "resolved",
"owners": [
"PKG-CHAPTER-05"
],
"evidence": [
"refactor/baselines/chapter-combinations-checkpoint.json",
"test/browser/chapter-combinations.spec.js"
],
"compatibleResolution": "样式限制最大宽度并省略溢出显示,保留完整 textContent/data-title、DOM hooks 和原有章节数据语义。",
"closureCriteria": "两种核心三引擎窄屏候选标题不越界,历史溢出观察和完整文本保留断言通过。",
"resolutionEvidence": [
"refactor/baselines/chapter-combinations-checkpoint.json",
"packages/artplayer-plugin-chapter/src/style.less",
"packages/artplayer-plugin-chapter/ARCHITECTURE.md"
],
"resolutionRationale": "先复现 6 个候选失败并保留截图,CSS 修复后正式 main/legacy 分别 12 个长标题用例通过(各含 6 历史溢出观察)。实际浏览器输入与产物指纹已冻结,API/类型未改变。"
},
{
"id": "CHAPTER-TIMING-01",
"title": "Windows WebKit 清晰度切换曾在等待窗口内未见 restart,稍后状态恢复",
"confirmation": "reproduced",
"status": "open",
"owners": [
"PKG-CHAPTER-05"
],
"evidence": [
"refactor/baselines/chapter-combinations-checkpoint.json",
"test/browser/chapter-combinations.spec.js",
"refactor/changes/2026-09-12-PKG-CHAPTER-05-combinations.md"
],
"compatibleResolution": "记录原生事件和绝对/相对时序,进一步区分媒体驱动耗时、夹具和核心源操作;未证明原因前不改核心、不扩大超时或重复到绿灯。",
"closureCriteria": "确认先前超时的来源并有对应可靠回归;后续单次通过不足以关闭。"
}
]
}
+5 -2
View File
@@ -1351,11 +1351,14 @@
"PKG-CHAPTER-04",
"CORE-22"
],
"status": "todo",
"status": "doing",
"risk": "M",
"deliverable": "chapter + quality/thumbnail、移动和全屏进度场景",
"acceptance": "最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成",
"evidence": []
"evidence": [
"changes/2026-09-12-PKG-CHAPTER-05-combinations.md",
"baselines/chapter-combinations-checkpoint.json"
]
},
{
"id": "PKG-CHAPTER-06",
+194
View File
@@ -0,0 +1,194 @@
import { devices } from '@playwright/test'
import { expect, test } from './fixtures.js'
async function openChapter(page, core, chapter) {
await page.goto(`/test/player.html?core=${core}&chapter=${chapter}`)
await page.evaluate(() => {
document.querySelector('.player').style.cssText = 'width:min(640px,100%);height:300px'
window.chapterEvents = []
window.chapterMediaEvents = []
window.art = new window.Artplayer({
container: '.player',
url: '/test/pattern.mp4?quality=B',
muted: true,
fullscreen: true,
fullscreenWeb: true,
miniProgressBar: true,
quality: [
{ html: 'Quality A', url: '/test/pattern.mp4?quality=A' },
{ html: 'Quality B', url: '/test/pattern.mp4?quality=B', default: true },
],
thumbnails: { url: '/test/thumbnail-grid.svg', number: 100, column: 10, width: 100, height: 60 },
plugins: [window.artplayerPluginChapter({ chapters: [
{ start: 0, end: 2, title: 'Opening' },
{ start: 2, end: 6, title: 'Middle' },
{ start: 6, end: 8, title: 'Ending' },
] })],
})
for (const name of ['restart', 'seek', 'fullscreen', 'fullscreenWeb'])
window.art.on(name, value => window.chapterEvents.push({ name, value }))
for (const name of ['loadedmetadata', 'loadeddata', 'canplay', 'seeking', 'seeked', 'playing', 'pause', 'error'])
window.art.video.addEventListener(name, () => window.chapterMediaEvents.push({ name, time: window.art.currentTime, source: window.art.video.currentSrc, paused: window.art.video.paused, readyState: window.art.video.readyState }))
document.querySelector('#play').onclick = () => window.art.play()
document.querySelector('#pause').onclick = () => window.art.pause()
})
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
await expect(page.locator('.art-chapter')).toHaveCount(3)
}
async function hoverChapter(page, percentage, text) {
const inner = page.locator('.art-control-progress-inner')
const box = await inner.boundingBox()
const x = Math.round(box.x + box.width * percentage)
await page.mouse.move(x, box.y + box.height / 2)
await expect(page.locator('.art-chapter-title')).toHaveText(text)
await expect(page.locator('.art-chapter-title')).toHaveCSS('opacity', '1')
await expect(page.locator('.art-control-thumbnails')).toBeVisible()
await expect.poll(() => page.evaluate(() => window.art.controls.thumbnails.style.backgroundImage)).not.toBe('')
const layout = await page.evaluate(() => {
const rect = (element) => {
const box = element.getBoundingClientRect()
return { left: box.left, right: box.right, top: box.top, bottom: box.bottom }
}
return {
title: rect(document.querySelector('.art-chapter-title')),
thumbnail: rect(window.art.controls.thumbnails),
progress: rect(document.querySelector('.art-control-progress-inner')),
}
})
expect(layout.title.left).toBeGreaterThanOrEqual(layout.progress.left - 1)
expect(layout.title.right).toBeLessThanOrEqual(layout.progress.right + 1)
expect(layout.thumbnail.bottom).toBeLessThanOrEqual(layout.title.top)
return { box, x, layout }
}
test.afterEach(async ({ page }, testInfo) => {
const state = await page.evaluate(() => ({
events: window.chapterEvents,
nativeEvents: window.chapterMediaEvents,
time: window.art?.currentTime,
paused: window.art?.video.paused,
mediaError: window.art?.video.error?.code || null,
source: window.art?.video.currentSrc,
readyState: window.art?.video.readyState,
mobileBranch: window.Artplayer?.utils.isMobile,
maxTouchPoints: navigator.maxTouchPoints,
chapters: [...document.querySelectorAll('.art-chapter')].map(node => ({ ...node.dataset })),
})).catch(error => ({ error: error.message }))
await testInfo.attach('chapter-combination-state', { contentType: 'application/json', body: JSON.stringify(state) })
if (!page.isClosed())
await page.evaluate(() => window.art?.destroy())
})
for (const core of ['published', 'candidate']) {
for (const chapter of ['published', 'candidate']) {
test(`${core} core + ${chapter} chapter: long titles retain text within a narrow player`, async ({ page }, testInfo) => {
await page.setViewportSize({ width: 390, height: 844 })
await openChapter(page, core, chapter)
const fullTitle = 'A long chapter title with complete metadata retained for consumers. '.repeat(4)
await page.evaluate(fullTitle => window.art.plugins.artplayerPluginChapter.update({ chapters: [{ start: 0, end: Infinity, title: fullTitle }] }), fullTitle)
const box = await page.locator('.art-control-progress-inner').boundingBox()
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2)
await expect(page.locator('.art-chapter-title')).toHaveText(fullTitle.trim())
await expect(page.locator('.art-chapter-title')).toHaveCSS('opacity', '1')
const layout = await page.locator('.art-chapter-title').evaluate((element) => {
const title = element.getBoundingClientRect()
const progress = element.parentElement.getBoundingClientRect()
return { left: title.left, right: title.right, width: title.width, progressLeft: progress.left, progressRight: progress.right, text: element.textContent, dataset: element.parentElement.querySelector('.art-chapter').dataset.title }
})
await testInfo.attach('long-chapter-layout', { contentType: 'application/json', body: JSON.stringify(layout) })
await testInfo.attach('long-chapter-title', { contentType: 'image/png', body: await page.screenshot() })
expect(layout.text).toBe(fullTitle.trim())
expect(layout.dataset).toBe(fullTitle.trim())
expect(layout.left).toBeGreaterThanOrEqual(layout.progressLeft - 1)
if (chapter === 'published')
expect(layout.right).toBeGreaterThan(layout.progressRight)
else
expect(layout.right).toBeLessThanOrEqual(layout.progressRight + 1)
})
test(`${core} core + ${chapter} chapter: quality switch preserves chapter/thumbnail interactions`, async ({ page, browserName }, testInfo) => {
await openChapter(page, core, chapter)
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.2)
await page.locator('#pause').click()
const { box, x } = await hoverChapter(page, 0.375, 'Middle')
await page.mouse.click(x, box.y + box.height / 2)
await expect.poll(() => page.evaluate(() => !window.art.video.seeking)).toBe(true)
const time = await page.evaluate(() => window.art.currentTime)
expect(time).toBeCloseTo(3, 1)
await expect.poll(() => page.locator('.art-chapter .art-progress-played').nth(1).evaluate(node => Number.parseFloat(node.style.width))).toBeCloseTo(25, 0)
expect(await page.locator('.art-chapter .art-progress-played').evaluateAll(nodes => [nodes[0].style.width, nodes[2].style.width])).toEqual(['100%', '0px'])
await page.locator('.art-control-quality').hover()
await expect(page.locator('.art-control-quality .art-selector-list')).toHaveCSS('opacity', '1')
await page.locator('.art-control-quality .art-selector-item').filter({ hasText: 'Quality A' }).click()
await expect.poll(() => page.evaluate(() => window.art.url.includes('quality=A'))).toBe(true)
await expect.poll(() => page.evaluate(() => window.chapterEvents.filter(event => event.name === 'restart').map(event => event.value))).toEqual(['/test/pattern.mp4?quality=A'])
await expect.poll(() => page.evaluate(() => !window.art.video.seeking && window.art.video.readyState >= 2)).toBe(true)
const restoredTime = await page.evaluate(() => window.art.currentTime)
const historicalReset = core === 'published' && browserName === 'webkit'
if (historicalReset)
expect(restoredTime).toBeLessThan(0.05)
else
expect(restoredTime).toBeCloseTo(time, 1)
await testInfo.attach('quality-position', { contentType: 'application/json', body: JSON.stringify({ before: time, restoredTime, historicalReset }) })
expect(await page.evaluate(() => window.art.video.paused)).toBe(true)
await expect(page.locator('.art-control-quality .art-selector-value')).toHaveText('Quality A')
await page.locator('#pause').click()
await expect(page.locator('.art-control-quality .art-selector-list')).toHaveCSS('opacity', '0')
await hoverChapter(page, 0.85, 'Ending')
await page.evaluate(() => window.art.plugins.artplayerPluginChapter.update({ chapters: [{ start: 0, end: Infinity, title: 'Updated after quality' }] }))
await hoverChapter(page, 0.5, 'Updated after quality')
await testInfo.attach('chapter-quality-thumbnail', { contentType: 'image/png', body: await page.locator('.art-video-player').screenshot() })
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(restoredTime + 0.2)
expect(await page.evaluate(() => window.art.video.error)).toBeNull()
})
for (const mode of ['fullscreenWeb', 'fullscreen']) {
test(`${core} core + ${chapter} chapter: ${mode} keeps hover geometry and exits cleanly`, async ({ page }, testInfo) => {
await openChapter(page, core, chapter)
if (mode === 'fullscreenWeb')
await page.evaluate(() => { window.Artplayer.FULLSCREEN_WEB_IN_BODY = true })
const control = page.locator(`.art-control-${mode}`)
const before = await page.locator('.art-control-progress-inner').boundingBox()
await control.click()
await expect.poll(() => page.evaluate(mode => window.art[mode], mode)).toBe(true)
const { box, layout } = await hoverChapter(page, 0.5, 'Middle')
expect(box.width).toBeGreaterThanOrEqual(before.width)
await testInfo.attach(`chapter-${mode}`, { contentType: 'image/png', body: await page.screenshot() })
await testInfo.attach('chapter-fullscreen-layout', { contentType: 'application/json', body: JSON.stringify(layout) })
await control.click()
await expect.poll(() => page.evaluate(mode => window.art[mode], mode)).toBe(false)
expect(await page.evaluate(() => window.art.template.$container.contains(window.art.template.$player))).toBe(true)
await hoverChapter(page, 0.99, 'Ending')
expect(await page.evaluate(mode => window.chapterEvents.filter(event => event.name === mode).map(event => event.value), mode)).toEqual([true, false])
})
}
test.describe(`${core} core + ${chapter} chapter touch emulation`, () => {
test.use({ viewport: { width: 390, height: 844 }, hasTouch: true, userAgent: devices['iPhone 13'].userAgent })
test('trusted taps seek through chapters before and after web fullscreen', async ({ page }, testInfo) => {
await openChapter(page, core, chapter)
expect(await page.evaluate(() => window.Artplayer.utils.isMobile)).toBe(true)
await page.evaluate(() => {
window.chapterTouches = []
window.art.template.$progress.addEventListener('touchstart', event => window.chapterTouches.push({ trusted: event.isTrusted, touches: event.touches.length }))
})
for (const fullscreen of [false, true, false]) {
if (await page.evaluate(() => window.art.fullscreenWeb) !== fullscreen)
await page.locator('.art-control-fullscreenWeb').tap()
await expect.poll(() => page.evaluate(() => window.art.fullscreenWeb)).toBe(fullscreen)
const progress = await page.locator('.art-control-progress-inner').boundingBox()
await page.touchscreen.tap(progress.x + progress.width * 0.375, progress.y + progress.height / 2)
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeCloseTo(3, 1)
await expect.poll(() => page.evaluate(() => !window.art.video.seeking)).toBe(true)
await expect(page.locator('.art-chapter')).toHaveCount(3)
}
expect(await page.evaluate(() => window.chapterTouches)).toEqual(Array.from({ length: 3 }, () => ({ trusted: true, touches: 1 })))
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
await testInfo.attach('chapter-touch-emulation', { contentType: 'image/png', body: await page.screenshot() })
})
})
}
}