mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-13 15:56:15 -08:00
refactor(hls): [PKG-HLS-04] migrate modules and compatible types
This commit is contained in:
1 parent
ba93b98be5
commit
2f9bee4ee0
34 files changed
+4860
-88
No files matched your search
+39
-13
@@ -1,17 +1,43 @@
|
||||
interface Config {
|
||||
control?: boolean
|
||||
setting?: boolean
|
||||
title?: string
|
||||
auto?: string
|
||||
getName?: (level: object) => string
|
||||
// Generated from the package public declaration by yarn build:ts. Do not edit.
|
||||
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
|
||||
declare namespace artplayerPluginHlsControlDefinitions {
|
||||
export interface QualityLevel {
|
||||
height: number
|
||||
name?: string
|
||||
}
|
||||
export interface AudioTrack {
|
||||
id: number
|
||||
name: string
|
||||
lang?: string
|
||||
language?: string
|
||||
}
|
||||
export interface Config<Item extends object = object> {
|
||||
control?: boolean
|
||||
setting?: boolean
|
||||
title?: string
|
||||
auto?: string
|
||||
/** Plain callback; current-label calls omit index. SDK objects retain their identity. */
|
||||
getName?: (item: Item, index?: number) => string
|
||||
}
|
||||
export interface Option<Level extends object = QualityLevel, Track extends object = AudioTrack> {
|
||||
quality?: Config<Level>
|
||||
audio?: Config<Track>
|
||||
}
|
||||
export interface Result {
|
||||
name: 'artplayerPluginHlsControl'
|
||||
update: () => void
|
||||
}
|
||||
export function artplayerPluginHlsControl<Level extends object = QualityLevel, Track extends object = AudioTrack>(option?: Option<Level, Track>): (art: Artplayer) => Result
|
||||
// Keep the required last signature for historical Parameters<typeof factory>[0] consumers.
|
||||
export function artplayerPluginHlsControl<Level extends object = QualityLevel, Track extends object = AudioTrack>(option: Option<Level, Track>): (art: Artplayer) => Result
|
||||
}
|
||||
|
||||
declare const artplayerPluginHlsControl: (option: { quality?: Config, audio?: Config }) => (art: Artplayer) => {
|
||||
name: 'artplayerPluginHlsControl'
|
||||
update: () => void
|
||||
declare const artplayerPluginHlsControl: typeof artplayerPluginHlsControlDefinitions.artplayerPluginHlsControl
|
||||
declare namespace artplayerPluginHlsControl {
|
||||
export type QualityLevel = artplayerPluginHlsControlDefinitions.QualityLevel
|
||||
export type AudioTrack = artplayerPluginHlsControlDefinitions.AudioTrack
|
||||
export type Config<Item extends object = object> = artplayerPluginHlsControlDefinitions.Config<Item>
|
||||
export type Option<Level extends object = QualityLevel, Track extends object = AudioTrack> = artplayerPluginHlsControlDefinitions.Option<Level, Track>
|
||||
export type Result = artplayerPluginHlsControlDefinitions.Result
|
||||
}
|
||||
|
||||
export default artplayerPluginHlsControl
|
||||
|
||||
export = artplayerPluginHlsControl
|
||||
export as namespace artplayerPluginHlsControl;
|
||||
@@ -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.artplayerPluginHlsControl=e(),define(function(){return t.artplayerPluginHlsControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginHlsControl=e()}(this,function(){"use strict";function t(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const o=e.get(t.html);return o?(t.default&&(o.default=!0,o.value=t.value),!1):(e.set(t.html,t),!0)})}function e(t,e,o,n,l,i){let r;const c={control:!1,setting:!1};function s(o){c[o]&&(c[o]=!1,t["control"===o?"controls":"setting"].remove(e))}function u(){r=void 0,s("control"),s("setting")}function a(e,o){for(const t of e.model.selector)t.default=t===o;e.config.control&&t.controls.check(o),e===r&&l(e.hls)&&e.config.setting&&t.setting.check(o)}return{update:function(f,d,h,m){if(!h)return void u();const g=r,v=h.selector.findIndex(t=>t.default);if(!m&&g?.hls===f&&g.model.title===h.title&&(-1!==v||!g.model.selector.some(t=>t.default)&&g.model.html===h.html)&&c.control===Boolean(d.control)&&c.setting===Boolean(d.setting)&&g.model.selector.length===h.selector.length&&g.model.selector.every((t,e)=>t.html===h.selector[e].html&&t.value===h.selector[e].value)){g.config=d;const t=g.model.selector[v];return void(!t||t.default&&g.model.html===h.html||(g.model.html=h.html,a(g,t)))}const p={hls:f,config:d,model:h};r=p;const y=()=>r===p&&l(f),T=e=>i(()=>y()?(f[o]=e.value,y()?(h.html=e.html,t.notice.show=`${h.title}: ${e.html}`,y()&&a(p,e),e.html):e.html):e.html);d.control?(c.control=!0,t.controls.update({name:e,position:"right",html:h.html,style:{padding:"0 10px"},selector:h.selector,onSelect:T})):s("control"),y()&&(d.setting?(c.setting=!0,t.setting.update({name:e,tooltip:h.html,html:h.title,icon:n,width:200,selector:h.selector,onSelect:T})):s("setting"))},clear:u,invalidate:function(){r=void 0}}}return function(o={}){return n=>{const{$video:l}=n.template,{errorHandle:i}=n.constructor.utils;let r,c,s=!1,u=0,a=()=>{},f=0;const d=new WeakSet,h=t=>!s&&!d.has(t)&&n.hls===t&&t.media===l,m=e(n,"hls-quality","currentLevel",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>',h,v),g=e(n,"hls-audio","audioTrack",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>',h,v);function v(t){f++;let e=!1;try{const o=t();return e=!0,o}finally{if(f--,!f){const t=c;c=void 0,e&&t&&p(t,!1)}}}function p(e,n){if(!h(e))return;const l=++u,i=o.quality||{},r=function(e,o){if(!e.levels.length)return null;const n=o.auto||"Auto",l=o.getName||(t=>t.name||`${t.height}P`),i=function(t){return!0===t.autoLevelEnabled?-1:t.currentLevel}(e),r=e.levels[i],c=r?l(r):n,s=t(e.levels.map((t,e)=>({html:l(t,e),value:e,default:i===e}))).sort((t,e)=>e.value-t.value);return s.push({html:n,value:-1,default:-1===i}),{html:c,title:o.title||"Quality",selector:s}}(e,i);if(!h(e)||l!==u)return;if(m.update(e,i,r,n),!h(e)||l!==u)return;const c=o.audio||{},s=function(e,o){if(!e.audioTracks.length)return null;const n=o.auto||"Auto",l=o.getName||(t=>t.name||t.lang||t.language),i=e.audioTracks[e.audioTrack],r=i?l(i):n,c=t(e.audioTracks.map((t,o)=>({html:l(t,o),value:t.id,default:e.audioTrack===t.id})));return{html:r,title:o.title||"Audio",selector:c}}(e,c);h(e)&&l===u&&g.update(e,c,s,n)}function y(){if(s)return;const t=n.hls;if(i(t?.media===l,'Cannot find instance of HLS from "art.hls"'),!d.has(t)){if(r!==t){a(),r=t;try{a=function(t,e,o){const n=t.constructor?.Events;if(!n||"function"!==typeof t.on||"function"!==typeof t.off)return()=>{};const l=[];let i=!0;function r(){if(i){i=!1;for(const[e,o]of l)t.off(e,o);l.length=0}}try{for(const r of["MANIFEST_PARSED","LEVELS_UPDATED","LEVEL_SWITCHED","AUDIO_TRACKS_UPDATED","AUDIO_TRACK_SWITCHED","DESTROYING"]){const c=n[r];if("string"!==typeof c||l.some(([t])=>t===c))continue;const s=()=>{i&&("DESTROYING"===r?o():e())};l.push([c,s]),t.on(c,s)}}catch(c){throw r(),c}return r}(t,()=>{f?c=t:p(t,!1)},()=>{d.add(t),a(),r===t&&(r=void 0,s||(m.clear(),g.clear()))})}catch(e){throw r=void 0,e}}p(t,!0)}}return n.on("ready",y),n.on("restart",y),n.on("destroy",function t(){s||(s=!0,u++,c=void 0,m.invalidate(),g.invalidate(),a(),n.off("ready",Line truncated
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginHlsControl=e(),define(function(){return t.artplayerPluginHlsControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginHlsControl=e()}(this,function(){"use strict";function t(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const o=e.get(t.html);return o?(t.default&&(o.default=!0,o.value=t.value),!1):(e.set(t.html,t),!0)})}function e(t,e,o,n,l,i){let r;const c={control:!1,setting:!1};function s(o){c[o]&&(c[o]=!1,t["control"===o?"controls":"setting"].remove(e))}function u(){r=void 0,s("control"),s("setting")}function a(e,o){for(const t of e.model.selector)t.default=t===o;e.config.control&&t.controls.check(o),e===r&&l(e.hls)&&e.config.setting&&t.setting.check(o)}return{update:function(f,d,h,m){if(!h)return void u();const g=r,v=h.selector.findIndex(t=>t.default);if(!m&&g?.hls===f&&g.model.title===h.title&&(-1!==v||!g.model.selector.some(t=>t.default)&&g.model.html===h.html)&&c.control===Boolean(d.control)&&c.setting===Boolean(d.setting)&&g.model.selector.length===h.selector.length&&g.model.selector.every((t,e)=>t.html===h.selector[e].html&&t.value===h.selector[e].value)){g.config=d;const t=g.model.selector[v];return void(!t||t.default&&g.model.html===h.html||(g.model.html=h.html,a(g,t)))}const p={hls:f,config:d,model:h};r=p;const y=()=>r===p&&l(f),T=e=>i(()=>y()?(f[o]=e.value,y()?(h.html=e.html,t.notice.show=`${h.title}: ${e.html}`,y()&&a(p,e),e.html):e.html):e.html);d.control?(c.control=!0,t.controls.update({name:e,position:"right",html:h.html,style:{padding:"0 10px"},selector:h.selector,onSelect:T})):s("control"),y()&&(d.setting?(c.setting=!0,t.setting.update({name:e,tooltip:h.html,html:h.title,icon:n,width:200,selector:h.selector,onSelect:T})):s("setting"))},clear:u,invalidate:function(){r=void 0}}}return function(o={}){return n=>{const l=n,{$video:i}=l.template,{errorHandle:r}=l.constructor.utils;let c,s,u=!1,a=0,f=()=>{},d=0;const h=new WeakSet,m=t=>!u&&!h.has(t)&&l.hls===t&&t.media===i,g=e(l,"hls-quality","currentLevel",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>',m,p),v=e(l,"hls-audio","audioTrack",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>',m,p);function p(t){d++;let e=!1;try{const o=t();return e=!0,o}finally{if(d--,!d){const t=s;s=void 0,e&&t&&y(t,!1)}}}function y(e,n){if(!m(e))return;const l=++a,i=o.quality||{},r=function(e,o){if(!e.levels.length)return null;const n=o.auto||"Auto",l=o.getName||(t=>t.name||`${t.height}P`),i=function(t){return!0===t.autoLevelEnabled?-1:t.currentLevel}(e),r=e.levels[i],c=r?l(r):n,s=t(e.levels.map((t,e)=>({html:l(t,e),value:e,default:i===e}))).sort((t,e)=>e.value-t.value);return s.push({html:n,value:-1,default:-1===i}),{html:c,title:o.title||"Quality",selector:s}}(e,i);if(!m(e)||l!==a)return;if(g.update(e,i,r,n),!m(e)||l!==a)return;const c=o.audio||{},s=function(e,o){if(!e.audioTracks.length)return null;const n=o.auto||"Auto",l=o.getName||(t=>t.name||t.lang||t.language),i=e.audioTracks[e.audioTrack],r=i?l(i):n,c=t(e.audioTracks.map((t,o)=>({html:l(t,o),value:t.id,default:e.audioTrack===t.id})));return{html:r,title:o.title||"Audio",selector:c}}(e,c);m(e)&&l===a&&v.update(e,c,s,n)}function T(){if(u)return;const t=l.hls;if(r(t?.media===i,'Cannot find instance of HLS from "art.hls"'),!h.has(t)){if(c!==t){f(),c=t;try{f=function(t,e,o){const n=t.constructor?.Events;if(!n||"function"!==typeof t.on||"function"!==typeof t.off)return()=>{};const l=t,i=[];let r=!0;function c(){if(r){r=!1;for(const[t,e]of i)l.off(t,e);i.length=0}}try{for(const t of["MANIFEST_PARSED","LEVELS_UPDATED","LEVEL_SWITCHED","AUDIO_TRACKS_UPDATED","AUDIO_TRACK_SWITCHED","DESTROYING"]){const c=n[t];if("string"!==typeof c||i.some(([t])=>t===c))continue;const s=()=>{r&&("DESTROYING"===t?o():e())};i.push([c,s]),l.on(c,s)}}catch(s){throw c(),s}return c}(t,()=>{d?s=t:y(t,!1)},()=>{h.add(t),f(),c===t&&(c=void 0,u||(g.clear(),v.clear()))})}catch(e){throw c=void 0,e}}y(t,!0)}}return l.on("ready",T),l.on("restart",T),l.on("destroy",function t(){u||(u=!0,a++,s=void 0,g.invalidate(),v.invalidate(),f(),l.offLine 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.artplayerPluginHlsControl=e(),define(function(){return t.artplayerPluginHlsControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginHlsControl=e()}(this,function(){"use strict";function t(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const o=e.get(t.html);return o?(t.default&&(o.default=!0,o.value=t.value),!1):(e.set(t.html,t),!0)})}function e(t,e,o,l,n,i){let r;const c={control:!1,setting:!1};function s(o){c[o]&&(c[o]=!1,t["control"===o?"controls":"setting"].remove(e))}function u(){r=void 0,s("control"),s("setting")}function a(e,o){for(const t of e.model.selector)t.default=t===o;e.config.control&&t.controls.check(o),e===r&&n(e.hls)&&e.config.setting&&t.setting.check(o)}return{update:function(f,d,h,m){if(!h)return void u();const g=r,v=h.selector.findIndex(t=>t.default);if(!m&&(null==g?void 0:g.hls)===f&&g.model.title===h.title&&(-1!==v||!g.model.selector.some(t=>t.default)&&g.model.html===h.html)&&c.control===Boolean(d.control)&&c.setting===Boolean(d.setting)&&g.model.selector.length===h.selector.length&&g.model.selector.every((t,e)=>t.html===h.selector[e].html&&t.value===h.selector[e].value)){g.config=d;const t=g.model.selector[v];return void(!t||t.default&&g.model.html===h.html||(g.model.html=h.html,a(g,t)))}const p={hls:f,config:d,model:h};r=p;const y=()=>r===p&&n(f),T=e=>i(()=>y()?(f[o]=e.value,y()?(h.html=e.html,t.notice.show=`${h.title}: ${e.html}`,y()&&a(p,e),e.html):e.html):e.html);d.control?(c.control=!0,t.controls.update({name:e,position:"right",html:h.html,style:{padding:"0 10px"},selector:h.selector,onSelect:T})):s("control"),y()&&(d.setting?(c.setting=!0,t.setting.update({name:e,tooltip:h.html,html:h.title,icon:l,width:200,selector:h.selector,onSelect:T})):s("setting"))},clear:u,invalidate:function(){r=void 0}}}return function(o={}){return l=>{const{$video:n}=l.template,{errorHandle:i}=l.constructor.utils;let r,c,s=!1,u=0,a=()=>{},f=0;const d=new WeakSet,h=t=>!s&&!d.has(t)&&l.hls===t&&t.media===n,m=e(l,"hls-quality","currentLevel",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>',h,v),g=e(l,"hls-audio","audioTrack",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>',h,v);function v(t){f++;let e=!1;try{const o=t();return e=!0,o}finally{if(f--,!f){const t=c;c=void 0,e&&t&&p(t,!1)}}}function p(e,l){if(!h(e))return;const n=++u,i=o.quality||{},r=function(e,o){if(!e.levels.length)return null;const l=o.auto||"Auto",n=o.getName||(t=>t.name||`${t.height}P`),i=function(t){return!0===t.autoLevelEnabled?-1:t.currentLevel}(e),r=e.levels[i],c=r?n(r):l,s=t(e.levels.map((t,e)=>({html:n(t,e),value:e,default:i===e}))).sort((t,e)=>e.value-t.value);return s.push({html:l,value:-1,default:-1===i}),{html:c,title:o.title||"Quality",selector:s}}(e,i);if(!h(e)||n!==u)return;if(m.update(e,i,r,l),!h(e)||n!==u)return;const c=o.audio||{},s=function(e,o){if(!e.audioTracks.length)return null;const l=o.auto||"Auto",n=o.getName||(t=>t.name||t.lang||t.language),i=e.audioTracks[e.audioTrack],r=i?n(i):l,c=t(e.audioTracks.map((t,o)=>({html:n(t,o),value:t.id,default:e.audioTrack===t.id})));return{html:r,title:o.title||"Audio",selector:c}}(e,c);h(e)&&n===u&&g.update(e,c,s,l)}function y(){if(s)return;const t=l.hls;if(i((null==t?void 0:t.media)===n,'Cannot find instance of HLS from "art.hls"'),!d.has(t)){if(r!==t){a(),r=t;try{a=function(t,e,o){var l;const n=null==(l=t.constructor)?void 0:l.Events;if(!n||"function"!==typeof t.on||"function"!==typeof t.off)return()=>{};const i=[];let r=!0;function c(){if(r){r=!1;for(const[e,o]of i)t.off(e,o);i.length=0}}try{for(const l of["MANIFEST_PARSED","LEVELS_UPDATED","LEVEL_SWITCHED","AUDIO_TRACKS_UPDATED","AUDIO_TRACK_SWITCHED","DESTROYING"]){const c=n[l];if("string"!==typeof c||i.some(([t])=>t===c))continue;const s=()=>{r&&("DESTROYING"===l?o():e())};i.push([c,s]),t.on(c,s)}}catch(s){throw c(),s}return c}(t,()=>{f?c=t:p(t,!1)},()=>{d.add(t),a(),r===t&&(r=void 0,s||(m.clear(),g.clear()))})}catch(e){throw r=void 0,e}}p(t,!0)}}return l.on("ready",y),l.on("restart",y),l.on("destroy",function t(){s||(s=!0,u++,cLine truncated
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginHlsControl=e(),define(function(){return t.artplayerPluginHlsControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginHlsControl=e()}(this,function(){"use strict";function t(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const o=e.get(t.html);return o?(t.default&&(o.default=!0,o.value=t.value),!1):(e.set(t.html,t),!0)})}function e(t,e,o,l,n,i){let r;const c={control:!1,setting:!1};function s(o){c[o]&&(c[o]=!1,t["control"===o?"controls":"setting"].remove(e))}function u(){r=void 0,s("control"),s("setting")}function a(e,o){for(const t of e.model.selector)t.default=t===o;e.config.control&&t.controls.check(o),e===r&&n(e.hls)&&e.config.setting&&t.setting.check(o)}return{update:function(f,d,h,m){if(!h)return void u();const g=r,v=h.selector.findIndex(t=>t.default);if(!m&&(null==g?void 0:g.hls)===f&&g.model.title===h.title&&(-1!==v||!g.model.selector.some(t=>t.default)&&g.model.html===h.html)&&c.control===Boolean(d.control)&&c.setting===Boolean(d.setting)&&g.model.selector.length===h.selector.length&&g.model.selector.every((t,e)=>t.html===h.selector[e].html&&t.value===h.selector[e].value)){g.config=d;const t=g.model.selector[v];return void(!t||t.default&&g.model.html===h.html||(g.model.html=h.html,a(g,t)))}const p={hls:f,config:d,model:h};r=p;const y=()=>r===p&&n(f),T=e=>i(()=>y()?(f[o]=e.value,y()?(h.html=e.html,t.notice.show=`${h.title}: ${e.html}`,y()&&a(p,e),e.html):e.html):e.html);d.control?(c.control=!0,t.controls.update({name:e,position:"right",html:h.html,style:{padding:"0 10px"},selector:h.selector,onSelect:T})):s("control"),y()&&(d.setting?(c.setting=!0,t.setting.update({name:e,tooltip:h.html,html:h.title,icon:l,width:200,selector:h.selector,onSelect:T})):s("setting"))},clear:u,invalidate:function(){r=void 0}}}return function(o={}){return l=>{const n=l,{$video:i}=n.template,{errorHandle:r}=n.constructor.utils;let c,s,u=!1,a=0,f=()=>{},d=0;const h=new WeakSet,m=t=>!u&&!h.has(t)&&n.hls===t&&t.media===i,g=e(n,"hls-quality","currentLevel",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>',m,p),v=e(n,"hls-audio","audioTrack",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>',m,p);function p(t){d++;let e=!1;try{const o=t();return e=!0,o}finally{if(d--,!d){const t=s;s=void 0,e&&t&&y(t,!1)}}}function y(e,l){if(!m(e))return;const n=++a,i=o.quality||{},r=function(e,o){if(!e.levels.length)return null;const l=o.auto||"Auto",n=o.getName||(t=>t.name||`${t.height}P`),i=function(t){return!0===t.autoLevelEnabled?-1:t.currentLevel}(e),r=e.levels[i],c=r?n(r):l,s=t(e.levels.map((t,e)=>({html:n(t,e),value:e,default:i===e}))).sort((t,e)=>e.value-t.value);return s.push({html:l,value:-1,default:-1===i}),{html:c,title:o.title||"Quality",selector:s}}(e,i);if(!m(e)||n!==a)return;if(g.update(e,i,r,l),!m(e)||n!==a)return;const c=o.audio||{},s=function(e,o){if(!e.audioTracks.length)return null;const l=o.auto||"Auto",n=o.getName||(t=>t.name||t.lang||t.language),i=e.audioTracks[e.audioTrack],r=i?n(i):l,c=t(e.audioTracks.map((t,o)=>({html:n(t,o),value:t.id,default:e.audioTrack===t.id})));return{html:r,title:o.title||"Audio",selector:c}}(e,c);m(e)&&n===a&&v.update(e,c,s,l)}function T(){if(u)return;const t=n.hls;if(r((null==t?void 0:t.media)===i,'Cannot find instance of HLS from "art.hls"'),!h.has(t)){if(c!==t){f(),c=t;try{f=function(t,e,o){var l;const n=null==(l=t.constructor)?void 0:l.Events;if(!n||"function"!==typeof t.on||"function"!==typeof t.off)return()=>{};const i=t,r=[];let c=!0;function s(){if(c){c=!1;for(const[t,e]of r)i.off(t,e);r.length=0}}try{for(const t of["MANIFEST_PARSED","LEVELS_UPDATED","LEVEL_SWITCHED","AUDIO_TRACKS_UPDATED","AUDIO_TRACK_SWITCHED","DESTROYING"]){const l=n[t];if("string"!==typeof l||r.some(([t])=>t===l))continue;const s=()=>{c&&("DESTROYING"===t?o():e())};r.push([l,s]),i.on(l,s)}}catch(u){throw s(),u}return s}(t,()=>{d?s=t:y(t,!1)},()=>{h.add(t),f(),c===t&&(c=void 0,u||(g.clear(),v.clear()))})}catch(e){throw c=void 0,e}}y(t,!0)}}return n.on("ready",T),n.on("restart",T),n.on("destroy",function t(){u||(uLine truncated
|
||||
@@ -129,6 +129,7 @@ function subscribeHls(hls, refresh, destroyed) {
|
||||
if (!events || typeof hls.on !== "function" || typeof hls.off !== "function")
|
||||
return () => {
|
||||
};
|
||||
const source = hls;
|
||||
const subscriptions = [];
|
||||
let active = true;
|
||||
function release() {
|
||||
@@ -136,7 +137,7 @@ function subscribeHls(hls, refresh, destroyed) {
|
||||
return;
|
||||
active = false;
|
||||
for (const [event, callback] of subscriptions)
|
||||
hls.off(event, callback);
|
||||
source.off(event, callback);
|
||||
subscriptions.length = 0;
|
||||
}
|
||||
try {
|
||||
@@ -153,7 +154,7 @@ function subscribeHls(hls, refresh, destroyed) {
|
||||
refresh();
|
||||
};
|
||||
subscriptions.push([event, callback]);
|
||||
hls.on(event, callback);
|
||||
source.on(event, callback);
|
||||
}
|
||||
} catch (error) {
|
||||
release();
|
||||
@@ -162,7 +163,8 @@ function subscribeHls(hls, refresh, destroyed) {
|
||||
return release;
|
||||
}
|
||||
function artplayerPluginHlsControl(option = {}) {
|
||||
return (art) => {
|
||||
return (player) => {
|
||||
const art = player;
|
||||
const { $video } = art.template;
|
||||
const { errorHandle } = art.constructor.utils;
|
||||
let closed = false;
|
||||
|
||||
@@ -1,21 +1,48 @@
|
||||
# HLS control maintenance
|
||||
|
||||
The public entry remains `src/index.js`: `artplayerPluginHlsControl(option = {})` returns a
|
||||
The source entry is `src/index.ts`: `artplayerPluginHlsControl(option = {})` returns a
|
||||
synchronous ArtPlayer plugin with `{ name: 'artplayerPluginHlsControl', update }`. `update()`
|
||||
returns undefined. The consumer creates, attaches and destroys `art.hls`; this plugin does not
|
||||
import Hls.js or own its media engine. Public declarations are still in `types/` pending the
|
||||
separate TypeScript migration (PKG-HLS-04).
|
||||
import Hls.js or own its media engine. Public declarations are authored in `types/`; implementation
|
||||
imports their configuration/result types so there is one public contract. All five self-owned
|
||||
modules are strict TypeScript, with unchecked indexed access enabled and no emitted compiler files.
|
||||
|
||||
| Module | Responsibility |
|
||||
| --- | --- |
|
||||
| `src/index.js` | Core ready/restart/destroy hooks, current engine identity, refresh generation, selection event barrier |
|
||||
| `src/mapping.js` | Quality/audio labels, duplicate groups, selected item and Auto policy; no ArtPlayer/DOM dependencies |
|
||||
| `src/menu.js` | Existing controls/setting update/remove/check APIs, current callback ownership and stable menu reuse |
|
||||
| `src/sdk-events.js` | Optional SDK event capabilities, six subscriptions, setup rollback and listener release |
|
||||
| `src/index.ts` | Core ready/restart/destroy hooks, current engine identity, refresh generation, selection event barrier |
|
||||
| `src/mapping.ts` | Quality/audio labels, duplicate groups, selected item and Auto policy; no ArtPlayer/DOM dependencies |
|
||||
| `src/menu.ts` | Existing controls/setting update/remove/check APIs, current callback ownership and stable menu reuse |
|
||||
| `src/sdk-events.ts` | Optional SDK event capabilities, six subscriptions, setup rollback and listener release |
|
||||
| `src/types.ts` | Internal SDK capabilities, menu models and the narrow host surface; no runtime code |
|
||||
|
||||
The entry depends on these three modules; they do not import the entry or each other.
|
||||
SVG icons remain local build inputs. There is no cross-instance cache, timer, worker or fetch.
|
||||
|
||||
## Public types and editor generation
|
||||
|
||||
`types/artplayer-plugin-hls-control.d.ts` retains the historical path and TS 4.3.5 syntax.
|
||||
Conditional `.d.cts`/`.d.mts` bridges preserve callable CommonJS/default ESM imports and named types;
|
||||
legacy keeps the same JavaScript file and adds a `typesVersions` fallback for older resolution.
|
||||
Do not change these runtime paths or add a runtime dependency on the Hls SDK to obtain its types.
|
||||
|
||||
Default `QualityLevel`/`AudioTrack` fields provide useful formatter inference. `Option<Level, Track>`
|
||||
also accepts caller-supplied SDK types, including inference from an annotated callback. A formatter
|
||||
still accepts the original object and optional index, returns a string and runs as a plain callback.
|
||||
The optional factory overload permits omitted/undefined configuration. The required last overload
|
||||
deliberately preserves old `Parameters<typeof artplayerPluginHlsControl>[0]['quality']` consumers;
|
||||
do not remove it as redundant. `update()` stays synchronous and returns void.
|
||||
|
||||
The single ArtPlayer-to-Host assertion represents the externally attached SDK surface. `update`
|
||||
still uses the historical errorHandle/media identity guard. The SDK subscription assertion follows
|
||||
actual on/off function checks; guarded selector indices follow an equal-length check. These limited
|
||||
boundaries are not proof that arbitrary external objects implement Hls. Keep runtime contract tests.
|
||||
|
||||
`scripts/plugin-editor-types.mjs` generates the HLS Monaco declaration from the authored public file.
|
||||
It creates private definitions plus a callable global/named-type bridge, rejecting unsupported
|
||||
imports/exports. `yarn build:ts` semantically checks that output with both compilers before writing it;
|
||||
never hand-edit `docs/assets/ts/artplayer-plugin-hls-control.d.ts`. The generic defaults and both
|
||||
overloads must survive generation. Other unconverted plugins still use their existing generator path.
|
||||
|
||||
## Refresh and selection
|
||||
|
||||
Registration does not require an Hls instance yet. The first ready/restart/manual update checks
|
||||
@@ -65,8 +92,11 @@ Use the repository's pinned Node and Yarn:
|
||||
|
||||
```sh
|
||||
node --test test/hls-control.test.js
|
||||
node --test refactor/scripts/hls-types.test.mjs
|
||||
yarn test:browser hls-control.spec.js
|
||||
yarn test:browser hls-editor-types.spec.js
|
||||
yarn build artplayer-plugin-hls-control
|
||||
yarn build:ts
|
||||
yarn ci:check
|
||||
```
|
||||
|
||||
@@ -79,3 +109,8 @@ Windows Playwright WebKit lacks MSE; explicit capability/error cleanup runs ther
|
||||
cases are marked skipped. Safari/native HLS, SDK workers, grouped track changes, SDK version range,
|
||||
updated example and isolated package verification remain PKG-HLS-05/06 work. See
|
||||
[refactor validation](../../refactor/hls-validation.md). Keep these gaps visible in release reviews.
|
||||
|
||||
Actual Hls.js 1.5.17 declaration consumers pass TS 5.9.3. TS 4.3.5 reports two existing SDK DOM type
|
||||
errors (MediaDecodingConfiguration and MediaCapabilitiesDecodingInfo), reproduced with the SDK alone.
|
||||
The plugin introduces no additional errors; do not hide those SDK diagnostics with skipLibCheck or
|
||||
claim every SDK/compiler combination passes. Standalone plugin consumers pass both compilers.
|
||||
@@ -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.artplayerPluginHlsControl=e(),define(function(){return t.artplayerPluginHlsControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginHlsControl=e()}(this,function(){"use strict";function t(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const o=e.get(t.html);return o?(t.default&&(o.default=!0,o.value=t.value),!1):(e.set(t.html,t),!0)})}function e(t,e,o,n,l,i){let r;const c={control:!1,setting:!1};function s(o){c[o]&&(c[o]=!1,t["control"===o?"controls":"setting"].remove(e))}function u(){r=void 0,s("control"),s("setting")}function a(e,o){for(const t of e.model.selector)t.default=t===o;e.config.control&&t.controls.check(o),e===r&&l(e.hls)&&e.config.setting&&t.setting.check(o)}return{update:function(f,d,h,m){if(!h)return void u();const g=r,v=h.selector.findIndex(t=>t.default);if(!m&&g?.hls===f&&g.model.title===h.title&&(-1!==v||!g.model.selector.some(t=>t.default)&&g.model.html===h.html)&&c.control===Boolean(d.control)&&c.setting===Boolean(d.setting)&&g.model.selector.length===h.selector.length&&g.model.selector.every((t,e)=>t.html===h.selector[e].html&&t.value===h.selector[e].value)){g.config=d;const t=g.model.selector[v];return void(!t||t.default&&g.model.html===h.html||(g.model.html=h.html,a(g,t)))}const p={hls:f,config:d,model:h};r=p;const y=()=>r===p&&l(f),T=e=>i(()=>y()?(f[o]=e.value,y()?(h.html=e.html,t.notice.show=`${h.title}: ${e.html}`,y()&&a(p,e),e.html):e.html):e.html);d.control?(c.control=!0,t.controls.update({name:e,position:"right",html:h.html,style:{padding:"0 10px"},selector:h.selector,onSelect:T})):s("control"),y()&&(d.setting?(c.setting=!0,t.setting.update({name:e,tooltip:h.html,html:h.title,icon:n,width:200,selector:h.selector,onSelect:T})):s("setting"))},clear:u,invalidate:function(){r=void 0}}}return function(o={}){return n=>{const{$video:l}=n.template,{errorHandle:i}=n.constructor.utils;let r,c,s=!1,u=0,a=()=>{},f=0;const d=new WeakSet,h=t=>!s&&!d.has(t)&&n.hls===t&&t.media===l,m=e(n,"hls-quality","currentLevel",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>',h,v),g=e(n,"hls-audio","audioTrack",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>',h,v);function v(t){f++;let e=!1;try{const o=t();return e=!0,o}finally{if(f--,!f){const t=c;c=void 0,e&&t&&p(t,!1)}}}function p(e,n){if(!h(e))return;const l=++u,i=o.quality||{},r=function(e,o){if(!e.levels.length)return null;const n=o.auto||"Auto",l=o.getName||(t=>t.name||`${t.height}P`),i=function(t){return!0===t.autoLevelEnabled?-1:t.currentLevel}(e),r=e.levels[i],c=r?l(r):n,s=t(e.levels.map((t,e)=>({html:l(t,e),value:e,default:i===e}))).sort((t,e)=>e.value-t.value);return s.push({html:n,value:-1,default:-1===i}),{html:c,title:o.title||"Quality",selector:s}}(e,i);if(!h(e)||l!==u)return;if(m.update(e,i,r,n),!h(e)||l!==u)return;const c=o.audio||{},s=function(e,o){if(!e.audioTracks.length)return null;const n=o.auto||"Auto",l=o.getName||(t=>t.name||t.lang||t.language),i=e.audioTracks[e.audioTrack],r=i?l(i):n,c=t(e.audioTracks.map((t,o)=>({html:l(t,o),value:t.id,default:e.audioTrack===t.id})));return{html:r,title:o.title||"Audio",selector:c}}(e,c);h(e)&&l===u&&g.update(e,c,s,n)}function y(){if(s)return;const t=n.hls;if(i(t?.media===l,'Cannot find instance of HLS from "art.hls"'),!d.has(t)){if(r!==t){a(),r=t;try{a=function(t,e,o){const n=t.constructor?.Events;if(!n||"function"!==typeof t.on||"function"!==typeof t.off)return()=>{};const l=[];let i=!0;function r(){if(i){i=!1;for(const[e,o]of l)t.off(e,o);l.length=0}}try{for(const r of["MANIFEST_PARSED","LEVELS_UPDATED","LEVEL_SWITCHED","AUDIO_TRACKS_UPDATED","AUDIO_TRACK_SWITCHED","DESTROYING"]){const c=n[r];if("string"!==typeof c||l.some(([t])=>t===c))continue;const s=()=>{i&&("DESTROYING"===r?o():e())};l.push([c,s]),t.on(c,s)}}catch(c){throw r(),c}return r}(t,()=>{f?c=t:p(t,!1)},()=>{d.add(t),a(),r===t&&(r=void 0,s||(m.clear(),g.clear()))})}catch(e){throw r=void 0,e}}p(t,!0)}}return n.on("ready",y),n.on("restart",y),n.on("destroy",function t(){s||(s=!0,u++,c=void 0,m.invalidate(),g.invalidate(),a(),n.off("ready",Line truncated
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginHlsControl=e(),define(function(){return t.artplayerPluginHlsControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginHlsControl=e()}(this,function(){"use strict";function t(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const o=e.get(t.html);return o?(t.default&&(o.default=!0,o.value=t.value),!1):(e.set(t.html,t),!0)})}function e(t,e,o,n,l,i){let r;const c={control:!1,setting:!1};function s(o){c[o]&&(c[o]=!1,t["control"===o?"controls":"setting"].remove(e))}function u(){r=void 0,s("control"),s("setting")}function a(e,o){for(const t of e.model.selector)t.default=t===o;e.config.control&&t.controls.check(o),e===r&&l(e.hls)&&e.config.setting&&t.setting.check(o)}return{update:function(f,d,h,m){if(!h)return void u();const g=r,v=h.selector.findIndex(t=>t.default);if(!m&&g?.hls===f&&g.model.title===h.title&&(-1!==v||!g.model.selector.some(t=>t.default)&&g.model.html===h.html)&&c.control===Boolean(d.control)&&c.setting===Boolean(d.setting)&&g.model.selector.length===h.selector.length&&g.model.selector.every((t,e)=>t.html===h.selector[e].html&&t.value===h.selector[e].value)){g.config=d;const t=g.model.selector[v];return void(!t||t.default&&g.model.html===h.html||(g.model.html=h.html,a(g,t)))}const p={hls:f,config:d,model:h};r=p;const y=()=>r===p&&l(f),T=e=>i(()=>y()?(f[o]=e.value,y()?(h.html=e.html,t.notice.show=`${h.title}: ${e.html}`,y()&&a(p,e),e.html):e.html):e.html);d.control?(c.control=!0,t.controls.update({name:e,position:"right",html:h.html,style:{padding:"0 10px"},selector:h.selector,onSelect:T})):s("control"),y()&&(d.setting?(c.setting=!0,t.setting.update({name:e,tooltip:h.html,html:h.title,icon:n,width:200,selector:h.selector,onSelect:T})):s("setting"))},clear:u,invalidate:function(){r=void 0}}}return function(o={}){return n=>{const l=n,{$video:i}=l.template,{errorHandle:r}=l.constructor.utils;let c,s,u=!1,a=0,f=()=>{},d=0;const h=new WeakSet,m=t=>!u&&!h.has(t)&&l.hls===t&&t.media===i,g=e(l,"hls-quality","currentLevel",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>',m,p),v=e(l,"hls-audio","audioTrack",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>',m,p);function p(t){d++;let e=!1;try{const o=t();return e=!0,o}finally{if(d--,!d){const t=s;s=void 0,e&&t&&y(t,!1)}}}function y(e,n){if(!m(e))return;const l=++a,i=o.quality||{},r=function(e,o){if(!e.levels.length)return null;const n=o.auto||"Auto",l=o.getName||(t=>t.name||`${t.height}P`),i=function(t){return!0===t.autoLevelEnabled?-1:t.currentLevel}(e),r=e.levels[i],c=r?l(r):n,s=t(e.levels.map((t,e)=>({html:l(t,e),value:e,default:i===e}))).sort((t,e)=>e.value-t.value);return s.push({html:n,value:-1,default:-1===i}),{html:c,title:o.title||"Quality",selector:s}}(e,i);if(!m(e)||l!==a)return;if(g.update(e,i,r,n),!m(e)||l!==a)return;const c=o.audio||{},s=function(e,o){if(!e.audioTracks.length)return null;const n=o.auto||"Auto",l=o.getName||(t=>t.name||t.lang||t.language),i=e.audioTracks[e.audioTrack],r=i?l(i):n,c=t(e.audioTracks.map((t,o)=>({html:l(t,o),value:t.id,default:e.audioTrack===t.id})));return{html:r,title:o.title||"Audio",selector:c}}(e,c);m(e)&&l===a&&v.update(e,c,s,n)}function T(){if(u)return;const t=l.hls;if(r(t?.media===i,'Cannot find instance of HLS from "art.hls"'),!h.has(t)){if(c!==t){f(),c=t;try{f=function(t,e,o){const n=t.constructor?.Events;if(!n||"function"!==typeof t.on||"function"!==typeof t.off)return()=>{};const l=t,i=[];let r=!0;function c(){if(r){r=!1;for(const[t,e]of i)l.off(t,e);i.length=0}}try{for(const t of["MANIFEST_PARSED","LEVELS_UPDATED","LEVEL_SWITCHED","AUDIO_TRACKS_UPDATED","AUDIO_TRACK_SWITCHED","DESTROYING"]){const c=n[t];if("string"!==typeof c||i.some(([t])=>t===c))continue;const s=()=>{r&&("DESTROYING"===t?o():e())};i.push([c,s]),l.on(c,s)}}catch(s){throw c(),s}return c}(t,()=>{d?s=t:y(t,!1)},()=>{h.add(t),f(),c===t&&(c=void 0,u||(g.clear(),v.clear()))})}catch(e){throw c=void 0,e}}y(t,!0)}}return l.on("ready",T),l.on("restart",T),l.on("destroy",function t(){u||(u=!0,a++,s=void 0,g.invalidate(),v.invalidate(),f(),l.offLine truncated
|
||||
+1
-1
@@ -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.artplayerPluginHlsControl=e(),define(function(){return t.artplayerPluginHlsControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginHlsControl=e()}(this,function(){"use strict";function t(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const o=e.get(t.html);return o?(t.default&&(o.default=!0,o.value=t.value),!1):(e.set(t.html,t),!0)})}function e(t,e,o,l,n,i){let r;const c={control:!1,setting:!1};function s(o){c[o]&&(c[o]=!1,t["control"===o?"controls":"setting"].remove(e))}function u(){r=void 0,s("control"),s("setting")}function a(e,o){for(const t of e.model.selector)t.default=t===o;e.config.control&&t.controls.check(o),e===r&&n(e.hls)&&e.config.setting&&t.setting.check(o)}return{update:function(f,d,h,m){if(!h)return void u();const g=r,v=h.selector.findIndex(t=>t.default);if(!m&&(null==g?void 0:g.hls)===f&&g.model.title===h.title&&(-1!==v||!g.model.selector.some(t=>t.default)&&g.model.html===h.html)&&c.control===Boolean(d.control)&&c.setting===Boolean(d.setting)&&g.model.selector.length===h.selector.length&&g.model.selector.every((t,e)=>t.html===h.selector[e].html&&t.value===h.selector[e].value)){g.config=d;const t=g.model.selector[v];return void(!t||t.default&&g.model.html===h.html||(g.model.html=h.html,a(g,t)))}const p={hls:f,config:d,model:h};r=p;const y=()=>r===p&&n(f),T=e=>i(()=>y()?(f[o]=e.value,y()?(h.html=e.html,t.notice.show=`${h.title}: ${e.html}`,y()&&a(p,e),e.html):e.html):e.html);d.control?(c.control=!0,t.controls.update({name:e,position:"right",html:h.html,style:{padding:"0 10px"},selector:h.selector,onSelect:T})):s("control"),y()&&(d.setting?(c.setting=!0,t.setting.update({name:e,tooltip:h.html,html:h.title,icon:l,width:200,selector:h.selector,onSelect:T})):s("setting"))},clear:u,invalidate:function(){r=void 0}}}return function(o={}){return l=>{const{$video:n}=l.template,{errorHandle:i}=l.constructor.utils;let r,c,s=!1,u=0,a=()=>{},f=0;const d=new WeakSet,h=t=>!s&&!d.has(t)&&l.hls===t&&t.media===n,m=e(l,"hls-quality","currentLevel",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>',h,v),g=e(l,"hls-audio","audioTrack",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>',h,v);function v(t){f++;let e=!1;try{const o=t();return e=!0,o}finally{if(f--,!f){const t=c;c=void 0,e&&t&&p(t,!1)}}}function p(e,l){if(!h(e))return;const n=++u,i=o.quality||{},r=function(e,o){if(!e.levels.length)return null;const l=o.auto||"Auto",n=o.getName||(t=>t.name||`${t.height}P`),i=function(t){return!0===t.autoLevelEnabled?-1:t.currentLevel}(e),r=e.levels[i],c=r?n(r):l,s=t(e.levels.map((t,e)=>({html:n(t,e),value:e,default:i===e}))).sort((t,e)=>e.value-t.value);return s.push({html:l,value:-1,default:-1===i}),{html:c,title:o.title||"Quality",selector:s}}(e,i);if(!h(e)||n!==u)return;if(m.update(e,i,r,l),!h(e)||n!==u)return;const c=o.audio||{},s=function(e,o){if(!e.audioTracks.length)return null;const l=o.auto||"Auto",n=o.getName||(t=>t.name||t.lang||t.language),i=e.audioTracks[e.audioTrack],r=i?n(i):l,c=t(e.audioTracks.map((t,o)=>({html:n(t,o),value:t.id,default:e.audioTrack===t.id})));return{html:r,title:o.title||"Audio",selector:c}}(e,c);h(e)&&n===u&&g.update(e,c,s,l)}function y(){if(s)return;const t=l.hls;if(i((null==t?void 0:t.media)===n,'Cannot find instance of HLS from "art.hls"'),!d.has(t)){if(r!==t){a(),r=t;try{a=function(t,e,o){var l;const n=null==(l=t.constructor)?void 0:l.Events;if(!n||"function"!==typeof t.on||"function"!==typeof t.off)return()=>{};const i=[];let r=!0;function c(){if(r){r=!1;for(const[e,o]of i)t.off(e,o);i.length=0}}try{for(const l of["MANIFEST_PARSED","LEVELS_UPDATED","LEVEL_SWITCHED","AUDIO_TRACKS_UPDATED","AUDIO_TRACK_SWITCHED","DESTROYING"]){const c=n[l];if("string"!==typeof c||i.some(([t])=>t===c))continue;const s=()=>{r&&("DESTROYING"===l?o():e())};i.push([c,s]),t.on(c,s)}}catch(s){throw c(),s}return c}(t,()=>{f?c=t:p(t,!1)},()=>{d.add(t),a(),r===t&&(r=void 0,s||(m.clear(),g.clear()))})}catch(e){throw r=void 0,e}}p(t,!0)}}return l.on("ready",y),l.on("restart",y),l.on("destroy",function t(){s||(s=!0,u++,cLine truncated
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginHlsControl=e(),define(function(){return t.artplayerPluginHlsControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginHlsControl=e()}(this,function(){"use strict";function t(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const o=e.get(t.html);return o?(t.default&&(o.default=!0,o.value=t.value),!1):(e.set(t.html,t),!0)})}function e(t,e,o,l,n,i){let r;const c={control:!1,setting:!1};function s(o){c[o]&&(c[o]=!1,t["control"===o?"controls":"setting"].remove(e))}function u(){r=void 0,s("control"),s("setting")}function a(e,o){for(const t of e.model.selector)t.default=t===o;e.config.control&&t.controls.check(o),e===r&&n(e.hls)&&e.config.setting&&t.setting.check(o)}return{update:function(f,d,h,m){if(!h)return void u();const g=r,v=h.selector.findIndex(t=>t.default);if(!m&&(null==g?void 0:g.hls)===f&&g.model.title===h.title&&(-1!==v||!g.model.selector.some(t=>t.default)&&g.model.html===h.html)&&c.control===Boolean(d.control)&&c.setting===Boolean(d.setting)&&g.model.selector.length===h.selector.length&&g.model.selector.every((t,e)=>t.html===h.selector[e].html&&t.value===h.selector[e].value)){g.config=d;const t=g.model.selector[v];return void(!t||t.default&&g.model.html===h.html||(g.model.html=h.html,a(g,t)))}const p={hls:f,config:d,model:h};r=p;const y=()=>r===p&&n(f),T=e=>i(()=>y()?(f[o]=e.value,y()?(h.html=e.html,t.notice.show=`${h.title}: ${e.html}`,y()&&a(p,e),e.html):e.html):e.html);d.control?(c.control=!0,t.controls.update({name:e,position:"right",html:h.html,style:{padding:"0 10px"},selector:h.selector,onSelect:T})):s("control"),y()&&(d.setting?(c.setting=!0,t.setting.update({name:e,tooltip:h.html,html:h.title,icon:l,width:200,selector:h.selector,onSelect:T})):s("setting"))},clear:u,invalidate:function(){r=void 0}}}return function(o={}){return l=>{const n=l,{$video:i}=n.template,{errorHandle:r}=n.constructor.utils;let c,s,u=!1,a=0,f=()=>{},d=0;const h=new WeakSet,m=t=>!u&&!h.has(t)&&n.hls===t&&t.media===i,g=e(n,"hls-quality","currentLevel",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>',m,p),v=e(n,"hls-audio","audioTrack",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>',m,p);function p(t){d++;let e=!1;try{const o=t();return e=!0,o}finally{if(d--,!d){const t=s;s=void 0,e&&t&&y(t,!1)}}}function y(e,l){if(!m(e))return;const n=++a,i=o.quality||{},r=function(e,o){if(!e.levels.length)return null;const l=o.auto||"Auto",n=o.getName||(t=>t.name||`${t.height}P`),i=function(t){return!0===t.autoLevelEnabled?-1:t.currentLevel}(e),r=e.levels[i],c=r?n(r):l,s=t(e.levels.map((t,e)=>({html:n(t,e),value:e,default:i===e}))).sort((t,e)=>e.value-t.value);return s.push({html:l,value:-1,default:-1===i}),{html:c,title:o.title||"Quality",selector:s}}(e,i);if(!m(e)||n!==a)return;if(g.update(e,i,r,l),!m(e)||n!==a)return;const c=o.audio||{},s=function(e,o){if(!e.audioTracks.length)return null;const l=o.auto||"Auto",n=o.getName||(t=>t.name||t.lang||t.language),i=e.audioTracks[e.audioTrack],r=i?n(i):l,c=t(e.audioTracks.map((t,o)=>({html:n(t,o),value:t.id,default:e.audioTrack===t.id})));return{html:r,title:o.title||"Audio",selector:c}}(e,c);m(e)&&n===a&&v.update(e,c,s,l)}function T(){if(u)return;const t=n.hls;if(r((null==t?void 0:t.media)===i,'Cannot find instance of HLS from "art.hls"'),!h.has(t)){if(c!==t){f(),c=t;try{f=function(t,e,o){var l;const n=null==(l=t.constructor)?void 0:l.Events;if(!n||"function"!==typeof t.on||"function"!==typeof t.off)return()=>{};const i=t,r=[];let c=!0;function s(){if(c){c=!1;for(const[t,e]of r)i.off(t,e);r.length=0}}try{for(const t of["MANIFEST_PARSED","LEVELS_UPDATED","LEVEL_SWITCHED","AUDIO_TRACKS_UPDATED","AUDIO_TRACK_SWITCHED","DESTROYING"]){const l=n[t];if("string"!==typeof l||r.some(([t])=>t===l))continue;const s=()=>{c&&("DESTROYING"===t?o():e())};r.push([l,s]),i.on(l,s)}}catch(u){throw s(),u}return s}(t,()=>{d?s=t:y(t,!1)},()=>{h.add(t),f(),c===t&&(c=void 0,u||(g.clear(),v.clear()))})}catch(e){throw c=void 0,e}}y(t,!0)}}return n.on("ready",T),n.on("restart",T),n.on("destroy",function t(){u||(uLine truncated
|
||||
+5
-3
@@ -129,6 +129,7 @@ function subscribeHls(hls, refresh, destroyed) {
|
||||
if (!events || typeof hls.on !== "function" || typeof hls.off !== "function")
|
||||
return () => {
|
||||
};
|
||||
const source = hls;
|
||||
const subscriptions = [];
|
||||
let active = true;
|
||||
function release() {
|
||||
@@ -136,7 +137,7 @@ function subscribeHls(hls, refresh, destroyed) {
|
||||
return;
|
||||
active = false;
|
||||
for (const [event, callback] of subscriptions)
|
||||
hls.off(event, callback);
|
||||
source.off(event, callback);
|
||||
subscriptions.length = 0;
|
||||
}
|
||||
try {
|
||||
@@ -153,7 +154,7 @@ function subscribeHls(hls, refresh, destroyed) {
|
||||
refresh();
|
||||
};
|
||||
subscriptions.push([event, callback]);
|
||||
hls.on(event, callback);
|
||||
source.on(event, callback);
|
||||
}
|
||||
} catch (error) {
|
||||
release();
|
||||
@@ -162,7 +163,8 @@ function subscribeHls(hls, refresh, destroyed) {
|
||||
return release;
|
||||
}
|
||||
function artplayerPluginHlsControl(option = {}) {
|
||||
return (art) => {
|
||||
return (player) => {
|
||||
const art = player;
|
||||
const { $video } = art.template;
|
||||
const { errorHandle } = art.constructor.utils;
|
||||
let closed = false;
|
||||
|
||||
@@ -19,19 +19,36 @@
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/artplayer-plugin-hls-control.d.ts",
|
||||
"import": "./dist/artplayer-plugin-hls-control.mjs",
|
||||
"require": "./dist/artplayer-plugin-hls-control.js"
|
||||
"import": {
|
||||
"types": "./types/artplayer-plugin-hls-control.d.mts",
|
||||
"default": "./dist/artplayer-plugin-hls-control.mjs"
|
||||
},
|
||||
"require": {
|
||||
"types": "./types/artplayer-plugin-hls-control.d.cts",
|
||||
"default": "./dist/artplayer-plugin-hls-control.js"
|
||||
}
|
||||
},
|
||||
"./legacy": {
|
||||
"types": "./types/artplayer-plugin-hls-control.d.ts",
|
||||
"import": "./dist/artplayer-plugin-hls-control.legacy.js",
|
||||
"require": "./dist/artplayer-plugin-hls-control.legacy.js"
|
||||
"import": {
|
||||
"types": "./types/artplayer-plugin-hls-control.d.cts",
|
||||
"default": "./dist/artplayer-plugin-hls-control.legacy.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./types/artplayer-plugin-hls-control.d.cts",
|
||||
"default": "./dist/artplayer-plugin-hls-control.legacy.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"main": "./dist/artplayer-plugin-hls-control.js",
|
||||
"module": "./dist/artplayer-plugin-hls-control.mjs",
|
||||
"types": "./types/artplayer-plugin-hls-control.d.ts",
|
||||
"legacy": "./dist/artplayer-plugin-hls-control.legacy.js",
|
||||
"browserslist": "last 1 Chrome version"
|
||||
"browserslist": "last 1 Chrome version",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"legacy": [
|
||||
"types/artplayer-plugin-hls-control.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-11
@@ -1,25 +1,30 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { AudioTrack, Option, QualityLevel, Result } from '../types/artplayer-plugin-hls-control'
|
||||
import type { Cleanup, Hls, Host } from './types'
|
||||
import $audio from './audio.svg?raw'
|
||||
import { audioModel, qualityModel } from './mapping'
|
||||
import { createMenu } from './menu'
|
||||
import $quality from './quality.svg?raw'
|
||||
import { subscribeHls } from './sdk-events'
|
||||
|
||||
export default function artplayerPluginHlsControl(option = {}) {
|
||||
return (art) => {
|
||||
export default function artplayerPluginHlsControl<Level extends object = QualityLevel, Track extends object = AudioTrack>(option: Option<Level, Track> = {}) {
|
||||
return (player: Artplayer): Result => {
|
||||
// The consumer owns the optional SDK; update validates its media identity before use.
|
||||
const art = player as unknown as Host<Level, Track>
|
||||
const { $video } = art.template
|
||||
const { errorHandle } = art.constructor.utils
|
||||
let closed = false
|
||||
let revision = 0
|
||||
let engine
|
||||
let unsubscribe = () => {}
|
||||
let engine: Hls<Level, Track> | undefined
|
||||
let unsubscribe: Cleanup = () => {}
|
||||
let selecting = 0
|
||||
let pending
|
||||
let pending: Hls<Level, Track> | undefined
|
||||
const retired = new WeakSet()
|
||||
const active = hls => !closed && !retired.has(hls) && art.hls === hls && hls.media === $video
|
||||
const active = (hls: Hls<Level, Track>) => !closed && !retired.has(hls) && art.hls === hls && hls.media === $video
|
||||
const quality = createMenu(art, 'hls-quality', 'currentLevel', $quality, active, select)
|
||||
const audio = createMenu(art, 'hls-audio', 'audioTrack', $audio, active, select)
|
||||
|
||||
function select(callback) {
|
||||
function select<T>(callback: () => T): T {
|
||||
selecting++
|
||||
let succeeded = false
|
||||
try {
|
||||
@@ -38,7 +43,7 @@ export default function artplayerPluginHlsControl(option = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function refresh(hls, force) {
|
||||
function refresh(hls: Hls<Level, Track>, force: boolean): void {
|
||||
if (!active(hls))
|
||||
return
|
||||
const version = ++revision
|
||||
@@ -55,10 +60,10 @@ export default function artplayerPluginHlsControl(option = {}) {
|
||||
audio.update(hls, audioConfig, audioView, force)
|
||||
}
|
||||
|
||||
function update() {
|
||||
function update(): void {
|
||||
if (closed)
|
||||
return
|
||||
const hls = art.hls
|
||||
const hls = art.hls!
|
||||
errorHandle(hls?.media === $video, 'Cannot find instance of HLS from "art.hls"')
|
||||
if (retired.has(hls))
|
||||
return
|
||||
@@ -91,7 +96,7 @@ export default function artplayerPluginHlsControl(option = {}) {
|
||||
refresh(hls, true)
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
function destroy(): void {
|
||||
if (closed)
|
||||
return
|
||||
closed = true
|
||||
+10
-7
@@ -1,5 +1,8 @@
|
||||
function uniqueLabels(items) {
|
||||
const seen = new Map()
|
||||
import type { Config } from '../types/artplayer-plugin-hls-control'
|
||||
import type { AudioFields, Hls, LevelFields, MenuModel, SelectorItem } from './types'
|
||||
|
||||
function uniqueLabels(items: SelectorItem[]): SelectorItem[] {
|
||||
const seen = new Map<string, SelectorItem>()
|
||||
return items.filter((item) => {
|
||||
if (item.html === undefined)
|
||||
return true
|
||||
@@ -16,15 +19,15 @@ function uniqueLabels(items) {
|
||||
})
|
||||
}
|
||||
|
||||
export function qualitySelection(hls) {
|
||||
export function qualitySelection(hls: Pick<Hls, 'currentLevel' | 'autoLevelEnabled'>): number {
|
||||
return hls.autoLevelEnabled === true ? -1 : hls.currentLevel
|
||||
}
|
||||
|
||||
export function qualityModel(hls, config) {
|
||||
export function qualityModel<Level extends object>(hls: Pick<Hls<Level>, 'levels' | 'currentLevel' | 'autoLevelEnabled'>, config: Config<Level>): MenuModel | null {
|
||||
if (!hls.levels.length)
|
||||
return null
|
||||
const auto = config.auto || 'Auto'
|
||||
const getName = config.getName || (level => level.name || `${level.height}P`)
|
||||
const getName = config.getName || ((level: LevelFields) => level.name || `${level.height}P`)
|
||||
const selected = qualitySelection(hls)
|
||||
const level = hls.levels[selected]
|
||||
const html = level ? getName(level) : auto
|
||||
@@ -34,11 +37,11 @@ export function qualityModel(hls, config) {
|
||||
return { html, title: config.title || 'Quality', selector }
|
||||
}
|
||||
|
||||
export function audioModel(hls, config) {
|
||||
export function audioModel<Track extends object>(hls: Pick<Hls<object, Track>, 'audioTracks' | 'audioTrack'>, config: Config<Track>): MenuModel | null {
|
||||
if (!hls.audioTracks.length)
|
||||
return null
|
||||
const auto = config.auto || 'Auto'
|
||||
const getName = config.getName || (track => track.name || track.lang || track.language)
|
||||
const getName = config.getName || ((track: AudioFields) => track.name || track.lang || track.language)
|
||||
const track = hls.audioTracks[hls.audioTrack]
|
||||
const html = track ? getName(track) : auto
|
||||
const selector = uniqueLabels(hls.audioTracks.map((item, index) => ({ html: getName(item, index), value: item.id, default: hls.audioTrack === item.id })))
|
||||
+12
-9
@@ -1,21 +1,23 @@
|
||||
export function createMenu(art, name, field, icon, active, select) {
|
||||
let current
|
||||
import type { DisplayConfig, MenuHost, MenuModel, MenuState, SelectorItem } from './types'
|
||||
|
||||
export function createMenu<Engine extends { currentLevel: number, audioTrack: number }>(art: MenuHost, name: string, field: 'currentLevel' | 'audioTrack', icon: string, active: (hls: Engine) => boolean, select: <T>(callback: () => T) => T) {
|
||||
let current: MenuState<Engine> | undefined
|
||||
const owned = { control: false, setting: false }
|
||||
|
||||
function remove(surface) {
|
||||
function remove(surface: 'control' | 'setting'): void {
|
||||
if (!owned[surface])
|
||||
return
|
||||
owned[surface] = false
|
||||
art[surface === 'control' ? 'controls' : 'setting'].remove(name)
|
||||
}
|
||||
|
||||
function clear() {
|
||||
function clear(): void {
|
||||
current = undefined
|
||||
remove('control')
|
||||
remove('setting')
|
||||
}
|
||||
|
||||
function check(state, item) {
|
||||
function check(state: MenuState<Engine>, item: SelectorItem): void {
|
||||
for (const entry of state.model.selector)
|
||||
entry.default = entry === item
|
||||
if (state.config.control)
|
||||
@@ -26,18 +28,19 @@ export function createMenu(art, name, field, icon, active, select) {
|
||||
art.setting.check(item)
|
||||
}
|
||||
|
||||
function update(hls, config, model, force) {
|
||||
function update(hls: Engine, config: DisplayConfig, model: MenuModel | null, force: boolean): void {
|
||||
if (!model) {
|
||||
clear()
|
||||
return
|
||||
}
|
||||
const previous = current
|
||||
const selectedIndex = model.selector.findIndex(item => item.default)
|
||||
// Equal lengths below protect the same-index reads in the comparison.
|
||||
const reusable = !force && previous?.hls === hls && previous.model.title === model.title
|
||||
&& (selectedIndex !== -1 || (!previous.model.selector.some(item => item.default) && previous.model.html === model.html))
|
||||
&& owned.control === Boolean(config.control) && owned.setting === Boolean(config.setting)
|
||||
&& previous.model.selector.length === model.selector.length
|
||||
&& previous.model.selector.every((item, index) => item.html === model.selector[index].html && item.value === model.selector[index].value)
|
||||
&& previous.model.selector.every((item, index) => item.html === model.selector[index]!.html && item.value === model.selector[index]!.value)
|
||||
if (reusable) {
|
||||
previous.config = config
|
||||
const target = previous.model.selector[selectedIndex]
|
||||
@@ -50,7 +53,7 @@ export function createMenu(art, name, field, icon, active, select) {
|
||||
const state = { hls, config, model }
|
||||
current = state
|
||||
const valid = () => current === state && active(hls)
|
||||
const onSelect = item => select(() => {
|
||||
const onSelect = (item: SelectorItem) => select(() => {
|
||||
if (!valid())
|
||||
return item.html
|
||||
hls[field] = item.value
|
||||
@@ -80,7 +83,7 @@ export function createMenu(art, name, field, icon, active, select) {
|
||||
}
|
||||
}
|
||||
|
||||
function invalidate() {
|
||||
function invalidate(): void {
|
||||
current = undefined
|
||||
}
|
||||
return { update, clear, invalidate }
|
||||
+10
-6
@@ -1,19 +1,23 @@
|
||||
export function subscribeHls(hls, refresh, destroyed) {
|
||||
import type { Cleanup, Hls, SdkEvent } from './types'
|
||||
|
||||
export function subscribeHls(hls: Hls, refresh: Cleanup, destroyed: Cleanup): Cleanup {
|
||||
const events = hls.constructor?.Events
|
||||
if (!events || typeof hls.on !== 'function' || typeof hls.off !== 'function')
|
||||
return () => {}
|
||||
const subscriptions = []
|
||||
// Keep the checked SDK object so method calls retain their original receiver.
|
||||
const source = hls as Hls & Required<Pick<Hls, 'on' | 'off'>>
|
||||
const subscriptions: [string, Cleanup][] = []
|
||||
let active = true
|
||||
function release() {
|
||||
function release(): void {
|
||||
if (!active)
|
||||
return
|
||||
active = false
|
||||
for (const [event, callback] of subscriptions)
|
||||
hls.off(event, callback)
|
||||
source.off(event, callback)
|
||||
subscriptions.length = 0
|
||||
}
|
||||
try {
|
||||
for (const key of ['MANIFEST_PARSED', 'LEVELS_UPDATED', 'LEVEL_SWITCHED', 'AUDIO_TRACKS_UPDATED', 'AUDIO_TRACK_SWITCHED', 'DESTROYING']) {
|
||||
for (const key of ['MANIFEST_PARSED', 'LEVELS_UPDATED', 'LEVEL_SWITCHED', 'AUDIO_TRACKS_UPDATED', 'AUDIO_TRACK_SWITCHED', 'DESTROYING'] satisfies SdkEvent[]) {
|
||||
const event = events[key]
|
||||
if (typeof event !== 'string' || subscriptions.some(([name]) => name === event))
|
||||
continue
|
||||
@@ -26,7 +30,7 @@ export function subscribeHls(hls, refresh, destroyed) {
|
||||
refresh()
|
||||
}
|
||||
subscriptions.push([event, callback])
|
||||
hls.on(event, callback)
|
||||
source.on(event, callback)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
@@ -0,0 +1,79 @@
|
||||
import type { Config } from '../types/artplayer-plugin-hls-control'
|
||||
|
||||
export interface LevelFields {
|
||||
height?: number
|
||||
name?: string
|
||||
}
|
||||
|
||||
export interface AudioFields {
|
||||
id: number
|
||||
name?: string
|
||||
lang?: string
|
||||
language?: string
|
||||
}
|
||||
|
||||
export type SdkEvent = 'MANIFEST_PARSED' | 'LEVELS_UPDATED' | 'LEVEL_SWITCHED' | 'AUDIO_TRACKS_UPDATED' | 'AUDIO_TRACK_SWITCHED' | 'DESTROYING'
|
||||
export type Cleanup = () => void
|
||||
|
||||
export interface Hls<Level extends object = object, Track extends object = object> {
|
||||
media: object | null
|
||||
levels: (Level & LevelFields)[]
|
||||
audioTracks: (Track & AudioFields)[]
|
||||
currentLevel: number
|
||||
audioTrack: number
|
||||
autoLevelEnabled?: boolean
|
||||
constructor?: { Events?: Partial<Record<SdkEvent, unknown>> }
|
||||
on?: (event: string, callback: Cleanup) => unknown
|
||||
off?: (event: string, callback: Cleanup) => unknown
|
||||
}
|
||||
|
||||
export interface SelectorItem {
|
||||
html: string | undefined
|
||||
value: number
|
||||
default: boolean
|
||||
}
|
||||
|
||||
export interface MenuModel {
|
||||
html: string | undefined
|
||||
title: string
|
||||
selector: SelectorItem[]
|
||||
}
|
||||
|
||||
export interface MenuOption {
|
||||
name: string
|
||||
html: string | undefined
|
||||
selector: SelectorItem[]
|
||||
onSelect: (item: SelectorItem) => string | undefined
|
||||
position?: 'right'
|
||||
style?: { padding: string }
|
||||
tooltip?: string
|
||||
icon?: string
|
||||
width?: number
|
||||
}
|
||||
|
||||
export interface Registry {
|
||||
update: (option: MenuOption) => unknown
|
||||
remove: (name: string) => unknown
|
||||
check: (item: SelectorItem) => unknown
|
||||
}
|
||||
|
||||
export interface MenuHost {
|
||||
controls: Registry
|
||||
setting: Registry
|
||||
notice: { show: string }
|
||||
}
|
||||
|
||||
export interface Host<Level extends object, Track extends object> extends MenuHost {
|
||||
template: { $video: object }
|
||||
constructor: { utils: { errorHandle: (condition: unknown, message: string) => void } }
|
||||
hls?: Hls<Level, Track>
|
||||
on: (event: 'ready' | 'restart' | 'destroy', callback: Cleanup) => unknown
|
||||
off: (event: 'ready' | 'restart' | 'destroy', callback: Cleanup) => unknown
|
||||
}
|
||||
|
||||
export type DisplayConfig = Pick<Config, 'control' | 'setting'>
|
||||
export interface MenuState<Engine> {
|
||||
hls: Engine
|
||||
config: DisplayConfig
|
||||
model: MenuModel
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": { "allowJs": false },
|
||||
"include": ["src/**/*.ts", "types/**/*.d.ts", "types/**/*.d.mts", "types/**/*.d.cts", "../../types/assets.d.ts"]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* eslint-disable ts/no-redeclare -- CommonJS callable export and named-type namespace intentionally merge. */
|
||||
import type * as Definition from './artplayer-plugin-hls-control.js'
|
||||
|
||||
declare const artplayerPluginHlsControl: typeof Definition.default
|
||||
|
||||
declare namespace artplayerPluginHlsControl {
|
||||
type QualityLevel = Definition.QualityLevel
|
||||
type AudioTrack = Definition.AudioTrack
|
||||
type Config<Item extends object = object> = Definition.Config<Item>
|
||||
type Option<Level extends object = QualityLevel, Track extends object = AudioTrack> = Definition.Option<Level, Track>
|
||||
type Result = Definition.Result
|
||||
}
|
||||
|
||||
export = artplayerPluginHlsControl
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from './artplayer-plugin-hls-control.cjs'
|
||||
export type { AudioTrack, Config, Option, QualityLevel, Result } from './artplayer-plugin-hls-control.js'
|
||||
+25
-3
@@ -1,16 +1,38 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
|
||||
interface Config {
|
||||
export interface QualityLevel {
|
||||
height: number
|
||||
name?: string
|
||||
}
|
||||
|
||||
export interface AudioTrack {
|
||||
id: number
|
||||
name: string
|
||||
lang?: string
|
||||
language?: string
|
||||
}
|
||||
|
||||
export interface Config<Item extends object = object> {
|
||||
control?: boolean
|
||||
setting?: boolean
|
||||
title?: string
|
||||
auto?: string
|
||||
getName?: (level: object) => string
|
||||
/** Plain callback; current-label calls omit index. SDK objects retain their identity. */
|
||||
getName?: (item: Item, index?: number) => string
|
||||
}
|
||||
|
||||
declare const artplayerPluginHlsControl: (option: { quality?: Config, audio?: Config }) => (art: Artplayer) => {
|
||||
export interface Option<Level extends object = QualityLevel, Track extends object = AudioTrack> {
|
||||
quality?: Config<Level>
|
||||
audio?: Config<Track>
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
name: 'artplayerPluginHlsControl'
|
||||
update: () => void
|
||||
}
|
||||
|
||||
declare function artplayerPluginHlsControl<Level extends object = QualityLevel, Track extends object = AudioTrack>(option?: Option<Level, Track>): (art: Artplayer) => Result
|
||||
// Keep the required last signature for historical Parameters<typeof factory>[0] consumers.
|
||||
declare function artplayerPluginHlsControl<Level extends object = QualityLevel, Track extends object = AudioTrack>(option: Option<Level, Track>): (art: Artplayer) => Result
|
||||
|
||||
export default artplayerPluginHlsControl
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,71 @@
|
||||
# PKG-HLS-04:HLS 严格 TypeScript 与兼容声明
|
||||
|
||||
## 实现与兼容边界
|
||||
|
||||
上一任务建立的入口、映射、菜单、SDK 订阅模块迁移到 .ts,新增 types.ts 描述窄宿主、
|
||||
SDK 可选能力和选择模型。五个自有模块均纳入 strict/noUncheckedIndexedAccess,
|
||||
不引入 any、通配模块声明或 Hls 生产依赖。源码直接复用公开 Option/Result 类型,
|
||||
独立消费夹具校验源码工厂与公开工厂相互可赋值。实际模块地图见包内 ARCHITECTURE.md。
|
||||
|
||||
公开工厂、字面量 name、同步 update、控件名/图标、DOM/CSS 和运行时根/legacy 路径不变。
|
||||
getName 保留原 SDK 对象身份和普通函数调用,索引准确标为可选;默认质量/音轨字段可以推断,
|
||||
调用方可用泛型或回调注解选择真实 SDK 类型。旧 object 参数 formatter 仍可赋值。
|
||||
新增可选配置重载支持原来运行时就允许的省略/undefined;保留最后的必填重载,
|
||||
以兼容旧 Parameters<typeof factory>[0]['quality'] 类型提取,不将其改成可能 undefined。
|
||||
旧声明的 name 已是字面量,本次没有新增 name 写入限制。
|
||||
|
||||
.d.ts 继续为历史类型入口,.d.cts/.d.mts 提供 callable CommonJS/ESM 默认和命名类型桥,
|
||||
legacy 的 typesVersions 支持 TS 4.3 Node10 查找。没有改变三个 JavaScript 文件的路径,
|
||||
没有提前升级版本;逐包 next-major 与最终安装包检查继续由发布任务及 HLS-06 执行。
|
||||
|
||||
有限断言均有边界说明:入口承接外部附加的 art.hls,仍通过历史 errorHandle/media 检查;
|
||||
SDK on/off 断言位于函数能力检查之后;选择项索引位于相等长度检查之后。它们不验证全部
|
||||
外部 SDK 结构,不能替代运行时契约测试。新增的局部对象别名保持原方法接收者。
|
||||
|
||||
## 在线编辑器修复
|
||||
|
||||
旧生成方式保留 default/named exports 又附加 export=,严格编译生成结果会报告 TS2309。
|
||||
新增 scripts/plugin-editor-types.mjs 使用 AST 为 HLS 生成私有定义和 callable/global 类型桥,
|
||||
保留泛型默认和重载,拒绝未知 import/export,不静默删除依赖。build:ts 在写入该声明前
|
||||
以 TS 4.3.5/5.9.3 对核心+插件独立语义检查,skipLibCheck=false。
|
||||
生成器暂仅应用已验证的 HLS,其他插件按各自迁移任务接续;未手改 docs/assets/ts。
|
||||
|
||||
新测试检查输出可再生成、三条错误用法确实拒绝、损坏声明/未解析依赖不会被隐藏。
|
||||
真实 Monaco worker 用例在三个浏览器中加载实际生成文件,验证默认字段、旧类型提取、
|
||||
错误 formatter 诊断,并执行编译后的插件工厂。原核心 Monaco 示例也重跑并播放成功。
|
||||
HLS 编辑器用例自身只执行工厂,不宣称播放;实际播放由独立 HLS SDK 矩阵验证。
|
||||
首轮编辑器测试把重载错误预期写成 2322;实际 worker 正确返回 2769,无其他正例诊断。
|
||||
按真实重载语义纠正断言后,最终六项全部通过,没有修改声明来容忍非法返回。
|
||||
|
||||
## 验收证据
|
||||
|
||||
冻结文件为 [hls-types-validation.json](../baselines/hls-types-validation.json),保留源码/类型/
|
||||
生成脚本及产物指纹、浏览器输入与各用例结果、CI 日志摘要;旧任务冻结证据不覆盖。
|
||||
|
||||
| 项目 | 本次结果 |
|
||||
| --- | --- |
|
||||
| 结构与类型 | HLS 五个 TS 模块;全仓实际已迁移生产 TS 254 个严格检查通过 |
|
||||
| 旧/新类型消费者 | TS 5.9 Node10 CJS、NodeNext CJS/ESM、Bundler ESM 和 TS 4.3 Node10 CJS 正例零诊断,每组八条非法用法拒绝;另检查 CJS 命名空间与源码工厂 |
|
||||
| 实际 SDK 类型 | 真实 Hls.js 1.5.17 的 Level/MediaPlaylist 泛型消费;TS 5.9 零错误,TS 4.3 与 SDK-only 对照均仅两个既有 DOM 类型错误,插件无新增 |
|
||||
| Node 与产物 | 源码/发布专项 56 通过;加 main/legacy/ESM 构建共享契约累计 83 通过 |
|
||||
| 实际播放 | modern/legacy 各 35 passed、16 skipped、0 failed/retry,输入 SHA 与最终构建一致;覆盖新旧核心和插件四组合、选择/切源/失败/销毁/SDK 事件同步 |
|
||||
| 编辑器 | 核心+HLS 在 Chromium/Firefox/WebKit 共六项通过,无失败/重试/跳过 |
|
||||
| 完整本地 CI | yarn ci:check:536 项通过,496 单元、11 工程、29 基线;现有生成核心 editor unused eslint-disable 警告未手改 |
|
||||
| 构建 | yarn build artplayer-plugin-hls-control、yarn build:ts 通过;三份 dist 与 docs/compiled 字节一致 |
|
||||
|
||||
main 为 5266 raw / 2539 gzip9 字节,legacy 5322 / 2564,ESM 9649 / 3117。
|
||||
与上一任务相比只有局部别名/类型擦除后的微小变化,类型依赖未进入运行包,不声称体积优化。
|
||||
|
||||
## 未覆盖范围与交接
|
||||
|
||||
TS 4.3 的 MediaDecodingConfiguration/MediaCapabilitiesDecodingInfo 缺失来自真实 SDK
|
||||
声明与旧 lib.dom 组合;保留明确对照,不加虚假 DOM 定义,不以 skipLibCheck 隐藏。
|
||||
独立插件的旧编译器验收仍严格零诊断。SDK-01 和 HLS-ENV-01 保持 open。
|
||||
Windows WebKit 的 16 个播放跳过源于已确认缺少 MSE;能力/失败清理及 Monaco 测试照常运行。
|
||||
Safari/native、SDK worker/版本/分组轨道、官方示例及隔离 tarball 为 HLS-05/06 和发布门槛。
|
||||
|
||||
本步没有新增依赖;改进现有 build:ts、typecheck,新增生成 helper 和专项测试,已纳入
|
||||
现有 test:baseline/ci:check。包内文档、类型入口和生成产物随源码一起更新。
|
||||
本任务独立提交主题:refactor(hls): [PKG-HLS-04] migrate modules and compatible types。
|
||||
回退该 commit 可恢复上一任务四模块 JS 和旧类型/生成行为;保留 HLS-01~03 的基线与修复。
|
||||
提交并核实 Git 状态后进入 PKG-HLS-05。全项目目标继续,没有 push/tag/publish/merge。
|
||||
@@ -59,8 +59,20 @@ EX-03、REL-09/REVIEW-02 接续,SDK-01/HLS-ENV-01 保持 open。
|
||||
Safari/native fallback、更多 SDK 版本、安装包及示例页面全部组合不是本轮完成内容。
|
||||
显式安装产物映射暂不接受 HLS 源码回退;HLS-06 必须加入 HLS 包对应映射和隔离 tarball 消费。
|
||||
|
||||
## 后续实施
|
||||
## PKG-HLS-03/04 后续验证
|
||||
|
||||
PKG-HLS-03 拆出映射、UI 提交/删除、Hls 订阅与生命周期;把上述缺陷变成候选修复断言,
|
||||
保留发布版观察。PKG-HLS-04 迁移自有模块和公开类型,修正可选 option、getName 对象与可选 index,
|
||||
保持旧声明接受范围及分发路径。PKG-HLS-05/06 完成实际 SDK、浏览器、示例及安装包验收。
|
||||
PKG-HLS-03 已拆出映射、UI 提交/删除、Hls 订阅与生命周期;旧缺陷成为候选修复断言,
|
||||
保留发布版观察。该步最终 Node 专项 56 通过,三格式共享契约累计 83 通过,modern/legacy
|
||||
浏览器各 35 通过、16 跳过;以上早期 02 结果保持其原始范围,不冒充当前源码结果。
|
||||
|
||||
PKG-HLS-04 把五个模块迁移为严格 TS,公开类型保留旧调用和类型提取习惯,补齐默认字段、
|
||||
SDK 泛型、可选 index 和 CJS/ESM/legacy 声明路径。在线编辑器声明从公开源生成,构建时用
|
||||
TS 4.3/5.9 严格校验;真实 Monaco 验证推断、错误提示及编译后的工厂执行。
|
||||
这些编辑器用例不声称执行 HLS 播放,实际播放仍由 hls-control.spec.js 负责。
|
||||
本步结果见 [类型迁移记录](changes/2026-09-12-PKG-HLS-04-types.md) 和
|
||||
[冻结证据](baselines/hls-types-validation.json)。
|
||||
|
||||
实际 Hls.js 1.5.17 声明在 TS 4.3 缺少 MediaDecodingConfiguration 和
|
||||
MediaCapabilitiesDecodingInfo;仅 SDK 的消费者也出现相同两个诊断。插件无新增诊断,
|
||||
不注入虚假 DOM 定义或打开 skipLibCheck。独立插件五组类型模式均要求零诊断。
|
||||
PKG-HLS-05/06 继续完整 SDK、浏览器、示例及安装包验收,SDK-01/HLS-ENV-01 仍开放。
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 215 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 151 / doing 0 / blocked 0 / done 64 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 150 / doing 0 / blocked 0 / done 65 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
| PKG-HLS-01 | artplayer-plugin-hls-control<br>核对包契约与历史用法 | BASE-05 | quality/audio 配置、getName、update、控件名及 Hls 实例绑定 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done |
|
||||
| PKG-HLS-02 | artplayer-plugin-hls-control<br>建立特有行为与错误测试 | PKG-HLS-01, ENG-03, ENG-05 | Auto/手动实际 level、音轨、去重/过滤和拓扑变化 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
|
||||
| PKG-HLS-03 | artplayer-plugin-hls-control<br>整理内部职责与资源 | PKG-HLS-02, CORE-11, CORE-14 | 映射计算与 selector 更新/删除、Hls 事件订阅分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
|
||||
| PKG-HLS-04 | artplayer-plugin-hls-control<br>迁移自有源码和公开类型 | PKG-HLS-03, ENG-04, ENG-06, CORE-07 | HLS 能力适配类型、回调上下文和旧 getName 参数 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
|
||||
| PKG-HLS-04 | artplayer-plugin-hls-control<br>迁移自有源码和公开类型 | PKG-HLS-03, ENG-04, ENG-06, CORE-07 | HLS 能力适配类型、回调上下文和旧 getName 参数 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
|
||||
| PKG-HLS-05 | artplayer-plugin-hls-control<br>验证新旧核心和组合 | PKG-HLS-04, CORE-22 | 本地多码率 HLS、换成无轨道来源、旧核心和最终核心 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
| PKG-HLS-06 | artplayer-plugin-hls-control<br>验证分发并同步文档 | PKG-HLS-05, ENG-07 | hls.control.js、依赖范围与回退记录 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
|
||||
|
||||
@@ -468,3 +468,4 @@
|
||||
- PKG-HLS-01: [记录](baselines/hls-control-contract.md) [记录](baselines/hls-control-release.json) [记录](changes/2026-09-11-PKG-HLS-01-contract.md)
|
||||
- PKG-HLS-02: [记录](hls-validation.md) [记录](baselines/hls-sdk.json) [记录](baselines/hls-validation.json) [记录](changes/2026-09-11-PKG-HLS-02-tests.md)
|
||||
- PKG-HLS-03: [记录](changes/2026-09-12-PKG-HLS-03-modules.md) [记录](baselines/hls-modules-validation.json) [记录](hls-validation.md)
|
||||
- PKG-HLS-04: [记录](changes/2026-09-12-PKG-HLS-04-types.md) [记录](baselines/hls-types-validation.json) [记录](hls-validation.md)
|
||||
@@ -1,5 +1,20 @@
|
||||
# 进度与证据
|
||||
|
||||
## 最新完成:PKG-HLS-04 严格 TS 与公开类型
|
||||
|
||||
HLS 五个自有模块已迁移严格 TS;公开声明保留旧 object formatter、Parameters 配置提取、
|
||||
同步 update 及运行时入口,补齐默认字段、SDK 泛型、可选索引和 CJS/ESM/legacy 类型桥。
|
||||
在线编辑器由公开声明生成全局桥,并在 build:ts 内做双编译器严格语义检查。
|
||||
|
||||
完整本地 CI 536 项、254 个生产 TS 文件通过;HLS Node 专项 56 项,三格式累计 83 项通过。
|
||||
modern/legacy 浏览器各 35 通过、16 跳过,核心+HLS Monaco 六项通过;无失败/重试。
|
||||
最终产物输入指纹一致。实际 SDK 在 TS 4.3 的两个 DOM 类型缺失诊断有 SDK-only 对照,
|
||||
不隐藏或称为全 SDK 支持;Windows WebKit 的 MSE/Safari 缺口依然开放。
|
||||
|
||||
见 [交付记录](changes/2026-09-12-PKG-HLS-04-types.md)、[冻结证据](baselines/hls-types-validation.json)
|
||||
和包内 ARCHITECTURE.md。65 done、150 todo;本任务独立本地 commit,核实后进入 PKG-HLS-05。
|
||||
完整 SDK/组合/安装包、其余生态包、性能/CI/CD/版本/复盘继续执行原计划,没有公开发布。
|
||||
|
||||
## 最新完成:PKG-HLS-03 源码分层与生命周期修复
|
||||
|
||||
HLS control 已拆为入口、选择映射、菜单管理、SDK 订阅四个 JS 模块,保持公开入口和同步调用;修复空轨道旧菜单、过期回调、Auto 标签、重复组丢失选中态,并补事件同步、销毁/订阅失败恢复及重入保护。公开类型和严格 TS 迁移属于下一步 04。
|
||||
|
||||
+1
-1
@@ -610,7 +610,7 @@
|
||||
"EX-03"
|
||||
],
|
||||
"evidence": [
|
||||
"packages/artplayer-plugin-hls-control/src/index.js",
|
||||
"packages/artplayer-plugin-hls-control/src/index.ts",
|
||||
"docs/assets/example/hls.control.js",
|
||||
"refactor/hls-validation.md"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Use the repository's built-in baseline runner.
|
||||
import test from 'node:test'
|
||||
import { ESLint } from 'eslint'
|
||||
import ts from 'typescript'
|
||||
import compat from 'typescript-compat'
|
||||
import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from '../../scripts/plugin-editor-types.mjs'
|
||||
import { checkConsumer } from '../../scripts/typecheck.mjs'
|
||||
import { ensureArchive, readMember, refactorDir } from './releases.mjs'
|
||||
|
||||
test('HLS generated editor types are reproducible, standalone and reject invalid formatter uses', async () => {
|
||||
const source = fs.readFileSync(new URL('../../packages/artplayer-plugin-hls-control/types/artplayer-plugin-hls-control.d.ts', import.meta.url), 'utf8')
|
||||
const generated = generatePluginEditorDeclaration(source, 'artplayerPluginHlsControl')
|
||||
const file = 'docs/assets/ts/artplayer-plugin-hls-control.d.ts'
|
||||
const [formatted] = await new ESLint({ fix: true, fixTypes: ['layout'] }).lintText(generated, { filePath: file })
|
||||
assert.equal(formatted.errorCount, 0)
|
||||
assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), formatted.output || generated, 'Run yarn build:ts')
|
||||
const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')
|
||||
const consumer = `
|
||||
const option: artplayerPluginHlsControl.Option = { quality: { getName: (level, index) => level.height + ':' + index }, audio: { getName: track => track.name } }
|
||||
const legacyOption: NonNullable<Parameters<typeof artplayerPluginHlsControl>[0]['quality']> = { getName: (value: object) => String(value) }
|
||||
const plugin: (art: Artplayer) => artplayerPluginHlsControl.Result = artplayerPluginHlsControl(option)
|
||||
artplayerPluginHlsControl()
|
||||
artplayerPluginHlsControl(undefined)
|
||||
artplayerPluginHlsControl({ quality: { getName: (value: { bitrate: number }) => String(value.bitrate) } })
|
||||
// @ts-expect-error Formatter must return a string.
|
||||
artplayerPluginHlsControl({ quality: { getName: level => level.height } })
|
||||
// @ts-expect-error Default quality type cannot silently become any.
|
||||
artplayerPluginHlsControl({ quality: { getName: level => level.missing } })
|
||||
// @ts-expect-error Private definitions must not leak into globals.
|
||||
const hidden: artplayerPluginHlsControlDefinitions.Option = {}
|
||||
void [legacyOption, plugin, hidden]
|
||||
`
|
||||
for (const compiler of [ts, compat]) {
|
||||
assert.deepEqual(checkPluginEditorDeclaration(generated, core, consumer, compiler), [], compiler.version)
|
||||
assert.equal(checkPluginEditorDeclaration(generated, core, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''), compiler).length, 3)
|
||||
assert(checkPluginEditorDeclaration(`${generated}\nexport interface Broken { value: MissingType }`, core, '', compiler).some(item => item.code === 2309), 'Do not hide incompatible export assignment or unresolved declarations')
|
||||
}
|
||||
assert.throws(() => generatePluginEditorDeclaration(source.replace('from \'artplayer\'', 'from \'missing-sdk\''), 'artplayerPluginHlsControl'), /Unsupported plugin editor import/)
|
||||
assert.throws(() => generatePluginEditorDeclaration(`${source}\nexport { Missing } from './missing'`, 'artplayerPluginHlsControl'), /Unsupported plugin editor declaration/)
|
||||
assert.throws(() => generatePluginEditorDeclaration('export interface {', 'plugin'), /Invalid plugin declaration syntax/)
|
||||
})
|
||||
|
||||
test('HLS root, legacy, named types and callback inference work across old and modern compilers', () => {
|
||||
const source = fs.readFileSync(path.join(refactorDir, '../test/types/hls-control.ts'), 'utf8')
|
||||
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]) {
|
||||
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
|
||||
const invalid = source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')
|
||||
assert.equal(checkConsumer(compiler, mode, invalid).length, 8, `${compiler.version} ${mode} must reject all invalid uses`)
|
||||
}
|
||||
const cjs = 'import hls = require(\'artplayer-plugin-hls-control\'); import legacy = require(\'artplayer-plugin-hls-control/legacy\'); const option: hls.Option = { audio: { getName: track => track.name } }; hls(option); legacy();'
|
||||
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', cjs), [])
|
||||
})
|
||||
|
||||
test('HLS callback generics accept actual pinned SDK declarations without a runtime dependency', async () => {
|
||||
const { release } = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/hls-sdk.json')))
|
||||
const archive = await ensureArchive(release)
|
||||
const directory = fs.mkdtempSync(path.join(refactorDir, '.cache/hls-types-'))
|
||||
try {
|
||||
fs.writeFileSync(path.join(directory, 'sdk.d.ts'), readMember(archive, 'package/dist/hls.js.d.ts'))
|
||||
const filename = path.join(directory, 'consumer.ts')
|
||||
fs.writeFileSync(filename, `import type { Level, MediaPlaylist } from './sdk';
|
||||
import hls from 'artplayer-plugin-hls-control';
|
||||
hls<Level, MediaPlaylist>({ quality: { getName: level => String(level.bitrate) }, audio: { getName: track => track.name } });
|
||||
hls({ quality: { getName: (level: Level, index?: number) => level.height + ':' + index }, audio: { getName: (track: MediaPlaylist) => track.groupId } });
|
||||
`)
|
||||
for (const compiler of [ts, compat]) {
|
||||
const program = compiler.createProgram([filename], { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: true, module: compiler.ModuleKind.CommonJS, moduleResolution: compiler.ModuleResolutionKind.NodeJs, target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'] })
|
||||
const describe = program => compiler.getPreEmitDiagnostics(program).map(item => ({ file: item.file && path.basename(item.file.fileName), code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') }))
|
||||
const expected = compiler === compat
|
||||
? [
|
||||
{ file: 'sdk.d.ts', code: 2304, message: 'Cannot find name \'MediaDecodingConfiguration\'.' },
|
||||
{ file: 'sdk.d.ts', code: 2304, message: 'Cannot find name \'MediaCapabilitiesDecodingInfo\'.' },
|
||||
]
|
||||
: []
|
||||
assert.deepEqual(describe(program), expected, `Actual Hls.js types / TS ${compiler.version}`)
|
||||
const sdkOnly = path.join(directory, 'sdk-only.ts')
|
||||
fs.writeFileSync(sdkOnly, 'import type { Level, MediaPlaylist } from \'./sdk\'; export type Inputs = [Level, MediaPlaylist];\n')
|
||||
const control = compiler.createProgram([sdkOnly], program.getCompilerOptions())
|
||||
assert.deepEqual(describe(control), expected, 'Old DOM library errors must originate in the SDK alone, not the plugin')
|
||||
assert(program.getSourceFiles().some(file => path.resolve(file.fileName) === path.join(directory, 'sdk.d.ts')))
|
||||
}
|
||||
}
|
||||
finally {
|
||||
const resolved = fs.realpathSync(directory)
|
||||
assert.equal(path.dirname(resolved), fs.realpathSync(path.join(refactorDir, '.cache')))
|
||||
assert(path.basename(resolved).startsWith('hls-types-'))
|
||||
fs.rmSync(resolved, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
+6
-2
@@ -1850,11 +1850,15 @@
|
||||
"ENG-06",
|
||||
"CORE-07"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"risk": "H",
|
||||
"deliverable": "HLS 能力适配类型、回调上下文和旧 getName 参数",
|
||||
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-12-PKG-HLS-04-types.md",
|
||||
"baselines/hls-types-validation.json",
|
||||
"hls-validation.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-HLS-05",
|
||||
|
||||
@@ -1027,10 +1027,10 @@
|
||||
"EX-03"
|
||||
],
|
||||
"sourceFiles": [
|
||||
"packages/artplayer-plugin-hls-control/src/index.js",
|
||||
"packages/artplayer-plugin-hls-control/src/mapping.js",
|
||||
"packages/artplayer-plugin-hls-control/src/menu.js",
|
||||
"packages/artplayer-plugin-hls-control/src/sdk-events.js",
|
||||
"packages/artplayer-plugin-hls-control/src/index.ts",
|
||||
"packages/artplayer-plugin-hls-control/src/mapping.ts",
|
||||
"packages/artplayer-plugin-hls-control/src/menu.ts",
|
||||
"packages/artplayer-plugin-hls-control/src/sdk-events.ts",
|
||||
"docs/assets/example/hls.control.js"
|
||||
],
|
||||
"contract": "Caller supplies art.hls; homepage demo loads hls.js 1.5.17. Preserve quality/audio selectors and old core integration.",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# 已实现的类型检查与迁移入口
|
||||
|
||||
PKG-HLS-04 增加 HLS 的 5 个严格 TS 源模块,公开声明保留历史 .d.ts 并增加 CJS/ESM 条件桥。
|
||||
test/types/hls-control.ts 覆盖配置省略、旧 Parameters 提取、默认字段和 SDK 泛型、同步返回,
|
||||
hls-source.ts 检查源码工厂与公开工厂可相互赋值。hls-types.test.mjs 对五组编译模式检查正反例,
|
||||
并消费实际 Hls.js 1.5.17 声明。TS 4.3 的两个 SDK 自有 DOM 缺失诊断有 SDK-only 对照,
|
||||
不作插件通过的豁免;独立插件消费者仍要求零错误。
|
||||
scripts/plugin-editor-types.mjs 与 build:ts 负责 HLS 全局声明生成和双编译器语义校验,
|
||||
真实 Monaco 用例为 test/browser/hls-editor-types.spec.js;它验证类型和编译后工厂执行,
|
||||
实际 HLS 播放由 hls-control.spec.js 验证。详见 changes/2026-09-12-PKG-HLS-04-types.md。
|
||||
下方按原任务保留历史计数;当前执行统计以本次冻结证据和 typecheck 输出为准。
|
||||
|
||||
CORE-21 已完成声明与消费者验收。核心公开声明源已迁至 packages/artplayer/public/,
|
||||
使用 yarn build:types 生成历史 types/ 路径;yarn check:types 只读检查漂移、
|
||||
源错误和内部依赖,已接入 ci:check。public/ 与 src/ 分别纳入严格检查,
|
||||
|
||||
+12
-1
@@ -2,7 +2,9 @@ import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { ESLint } from 'eslint'
|
||||
import { glob } from 'glob'
|
||||
import compat from 'typescript-compat'
|
||||
import { generateCoreEditorDeclaration } from './editor-types.mjs'
|
||||
import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from './plugin-editor-types.mjs'
|
||||
|
||||
function ensureDirExists(filePath) {
|
||||
const dir = path.dirname(filePath)
|
||||
@@ -40,7 +42,16 @@ console.log(`✨ Built ${artplayerTSoutput}`);
|
||||
for (let index = 0; index < pluginsTS.length; index++) {
|
||||
const type = pluginsTS[index]
|
||||
const { name, file } = parsePluginInfo(type)
|
||||
const code = `${String(fs.readFileSync(type)).replace(reg, '')}\nexport = ${name};\nexport as namespace ${name};\n`
|
||||
const source = String(fs.readFileSync(type))
|
||||
const code = name === 'artplayerPluginHlsControl'
|
||||
? generatePluginEditorDeclaration(source, name)
|
||||
: `${source.replace(reg, '')}\nexport = ${name};\nexport as namespace ${name};\n`
|
||||
if (name === 'artplayerPluginHlsControl') {
|
||||
const core = fs.readFileSync(artplayerTSoutput, 'utf8')
|
||||
const diagnostics = [...checkPluginEditorDeclaration(code, core), ...checkPluginEditorDeclaration(code, core, '', compat)]
|
||||
if (diagnostics.length)
|
||||
throw new Error(`Invalid ${name} editor declaration: ${JSON.stringify(diagnostics)}`)
|
||||
}
|
||||
const output = path.join('docs/assets/ts', file)
|
||||
ensureDirExists(output)
|
||||
fs.writeFileSync(output, code.trim())
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import ts from 'typescript'
|
||||
|
||||
// Migrate each package explicitly; unknown imports/exports must not disappear silently.
|
||||
export function generatePluginEditorDeclaration(code, name) {
|
||||
assert(/^[a-z_$][\w$]*$/i.test(name), 'Invalid plugin global')
|
||||
const source = ts.createSourceFile('plugin.d.ts', code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
|
||||
assert.equal(source.parseDiagnostics.length, 0, 'Invalid plugin declaration syntax')
|
||||
const factory = ts.factory
|
||||
const definitions = []
|
||||
const aliases = []
|
||||
const internal = `${name}Definitions`
|
||||
let exported = false
|
||||
let callable = false
|
||||
for (const node of source.statements) {
|
||||
if (ts.isImportDeclaration(node)) {
|
||||
assert(node.importClause?.isTypeOnly && !node.importClause.namedBindings && node.importClause.name?.text === 'Artplayer' && node.moduleSpecifier.text === 'artplayer', 'Unsupported plugin editor import')
|
||||
continue
|
||||
}
|
||||
if (ts.isExportAssignment(node)) {
|
||||
assert(!node.isExportEquals && ts.isIdentifier(node.expression) && node.expression.text === name, 'Unexpected plugin default export')
|
||||
exported = true
|
||||
continue
|
||||
}
|
||||
assert(ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isFunctionDeclaration(node), 'Unsupported plugin editor declaration')
|
||||
assert(node.name && node.name.text !== internal, 'Invalid plugin declaration name')
|
||||
definitions.push(factory.replaceModifiers(node, [factory.createModifier(ts.SyntaxKind.ExportKeyword)]))
|
||||
if (ts.isFunctionDeclaration(node)) {
|
||||
assert.equal(node.name.text, name, 'Unexpected plugin callable')
|
||||
callable = true
|
||||
continue
|
||||
}
|
||||
if (node.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
|
||||
aliases.push(factory.createTypeAliasDeclaration(
|
||||
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
||||
node.name,
|
||||
node.typeParameters,
|
||||
factory.createTypeReferenceNode(factory.createQualifiedName(factory.createIdentifier(internal), node.name), node.typeParameters?.map(parameter => factory.createTypeReferenceNode(parameter.name))),
|
||||
))
|
||||
}
|
||||
}
|
||||
assert(exported && callable, 'Plugin editor requires a default function')
|
||||
const namespace = (identifier, statements) => factory.createModuleDeclaration([factory.createModifier(ts.SyntaxKind.DeclareKeyword)], factory.createIdentifier(identifier), factory.createModuleBlock(statements), ts.NodeFlags.Namespace)
|
||||
const statements = [
|
||||
namespace(internal, definitions),
|
||||
factory.createVariableStatement([factory.createModifier(ts.SyntaxKind.DeclareKeyword)], factory.createVariableDeclarationList([
|
||||
factory.createVariableDeclaration(name, undefined, factory.createTypeQueryNode(factory.createQualifiedName(factory.createIdentifier(internal), factory.createIdentifier(name)))),
|
||||
], ts.NodeFlags.Const)),
|
||||
namespace(name, aliases),
|
||||
factory.createExportAssignment(undefined, true, factory.createIdentifier(name)),
|
||||
factory.createNamespaceExportDeclaration(factory.createIdentifier(name)),
|
||||
]
|
||||
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })
|
||||
return `// Generated from the package public declaration by yarn build:ts. Do not edit.\n/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */\n${statements.map(node => printer.printNode(ts.EmitHint.Unspecified, node, source)).join('\n')}\n`
|
||||
}
|
||||
|
||||
export function checkPluginEditorDeclaration(code, core, consumer = '', compiler = ts) {
|
||||
const folder = fileURLToPath(new URL('../refactor/.cache/plugin-editor-semantic/', import.meta.url))
|
||||
const sources = new Map([
|
||||
[path.join(folder, 'artplayer.d.ts'), core],
|
||||
[path.join(folder, 'plugin.d.ts'), code],
|
||||
[path.join(folder, 'consumer.ts'), consumer],
|
||||
])
|
||||
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: compiler.ModuleKind.CommonJS, moduleResolution: compiler.ModuleResolutionKind.NodeJs }
|
||||
const host = compiler.createCompilerHost(options)
|
||||
const getSourceFile = host.getSourceFile.bind(host)
|
||||
host.getSourceFile = (file, version, onError, create) => sources.has(path.resolve(file))
|
||||
? compiler.createSourceFile(file, sources.get(path.resolve(file)), version, true)
|
||||
: getSourceFile(file, version, onError, create)
|
||||
const program = compiler.createProgram([...sources.keys()], options, host)
|
||||
for (const file of program.getSourceFiles())
|
||||
assert(sources.has(path.resolve(file.fileName)) || program.isSourceFileDefaultLibrary(file), `Plugin editor types escaped the standalone bundle: ${file.fileName}`)
|
||||
return compiler.getPreEmitDiagnostics(program).map(diagnostic => ({ code: diagnostic.code, message: compiler.flattenDiagnosticMessageText(diagnostic.messageText, '\n') }))
|
||||
}
|
||||
@@ -73,7 +73,7 @@ export function runTypechecks() {
|
||||
console.log(`Strict project passed: ${relative(config)} (${result.files.length} root files)`)
|
||||
}
|
||||
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'bundler-esm'], [ts, 'nodenext-esm'], [compat, 'node10-commonjs']]) {
|
||||
for (const fixture of ['test/types/public.ts', 'test/types/declaration-inputs.ts', 'test/types/declaration-legacy.ts', 'test/types/plugins-public.ts', 'test/types/playback-public.ts', 'test/types/chapter-options.ts', 'test/types/chapter-exports.ts', 'test/types/language-value.ts', 'refactor/fixtures/consumers/language.ts', 'refactor/fixtures/consumers/legacy-plugin.ts']) {
|
||||
for (const fixture of ['test/types/public.ts', 'test/types/declaration-inputs.ts', 'test/types/declaration-legacy.ts', 'test/types/plugins-public.ts', 'test/types/playback-public.ts', 'test/types/chapter-options.ts', 'test/types/chapter-exports.ts', 'test/types/hls-control.ts', 'test/types/language-value.ts', 'refactor/fixtures/consumers/language.ts', 'refactor/fixtures/consumers/legacy-plugin.ts']) {
|
||||
assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, fixture), 'utf8')), [], `Consumer failed: TS ${compiler.version} ${mode} ${fixture}`)
|
||||
}
|
||||
if (mode === 'nodenext-cjs')
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import fs from 'node:fs'
|
||||
import process from 'node:process'
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { compilePackage } from '../helpers/load.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
test('Monaco consumes HLS global types and executes the emitted plugin factory', async ({ page }, testInfo) => {
|
||||
const code = process.env.ARTPLAYER_HLS_ARTIFACT
|
||||
? fs.readFileSync(process.env.ARTPLAYER_HLS_ARTIFACT, 'utf8')
|
||||
: await compilePackage('artplayer-plugin-hls-control', 'umd')
|
||||
const declarations = Object.fromEntries(['artplayer', 'artplayer-plugin-hls-control'].map(name => [name, hash(fs.readFileSync(new URL(`../../docs/assets/ts/${name}.d.ts`, import.meta.url)))]))
|
||||
const inputs = { candidate: process.env.ARTPLAYER_HLS_ARTIFACT || 'workspace source build', sha256: hash(code), declarations }
|
||||
await testInfo.attach('hls-editor-inputs', { contentType: 'application/json', body: JSON.stringify(inputs) })
|
||||
await page.goto('/test/player.html?core=candidate&chapter=published')
|
||||
await page.addScriptTag({ content: code })
|
||||
await page.addScriptTag({ url: '/assets/js/vs/loader.js' })
|
||||
const result = await page.evaluate(async () => {
|
||||
window.require.config({ paths: { vs: '/assets/js/vs' } })
|
||||
await new Promise((resolve, reject) => window.require(['vs/editor/editor.main'], resolve, reject))
|
||||
const api = window.monaco
|
||||
const types = api.languages.typescript
|
||||
types.typescriptDefaults.setCompilerOptions({ target: types.ScriptTarget.ES2020, strict: true, skipLibCheck: false, noEmit: false, types: [], allowNonTsExtensions: true })
|
||||
const names = ['artplayer', 'artplayer-plugin-hls-control']
|
||||
const libraries = await Promise.all(names.map(async (name) => {
|
||||
const uri = `file:///${name}.d.ts`
|
||||
const source = await (await fetch(`/assets/ts/${name}.d.ts`)).text()
|
||||
return { uri, handle: types.typescriptDefaults.addExtraLib(source, uri) }
|
||||
}))
|
||||
const model = api.editor.createModel(`
|
||||
const hlsOption: artplayerPluginHlsControl.Option = {
|
||||
quality: { getName: (level, index) => level.height + ':' + index },
|
||||
audio: { getName: track => track.name },
|
||||
}
|
||||
const oldQuality: NonNullable<Parameters<typeof artplayerPluginHlsControl>[0]['quality']> = { getName: (value: object) => String(value) }
|
||||
const typedFactory: (art: Artplayer) => artplayerPluginHlsControl.Result = artplayerPluginHlsControl(hlsOption)
|
||||
const defaultFactory = artplayerPluginHlsControl()
|
||||
`, 'typescript', api.Uri.parse('file:///hls-consumer.ts'))
|
||||
const invalid = api.editor.createModel('artplayerPluginHlsControl({ quality: { getName: level => level.height } })', 'typescript', api.Uri.parse('file:///hls-invalid.ts'))
|
||||
try {
|
||||
const worker = await (await types.getTypeScriptWorker())(model.uri, invalid.uri)
|
||||
const syntax = await worker.getSyntacticDiagnostics(model.uri.toString())
|
||||
const semantic = await worker.getSemanticDiagnostics(model.uri.toString())
|
||||
const declarations = (await Promise.all(libraries.map(lib => worker.getSemanticDiagnostics(lib.uri)))).flat()
|
||||
const bad = await worker.getSemanticDiagnostics(invalid.uri.toString())
|
||||
if (syntax.length || semantic.length || declarations.length)
|
||||
return { syntax, semantic, declarations, invalid: bad.map(item => item.code), executed: false }
|
||||
const output = await worker.getEmitOutput(model.uri.toString())
|
||||
const script = output.outputFiles.find(file => file.name.endsWith('.js'))
|
||||
if (!script)
|
||||
throw new Error('Monaco did not emit the HLS fixture')
|
||||
// eslint-disable-next-line no-new-func -- Exercise the local editor Run action with its real emitted code.
|
||||
const executed = new Function(`${script.text}\nreturn typeof typedFactory === 'function' && typeof defaultFactory === 'function'`)()
|
||||
return { syntax, semantic, declarations, invalid: bad.map(item => item.code), executed }
|
||||
}
|
||||
finally {
|
||||
model.dispose()
|
||||
invalid.dispose()
|
||||
for (const library of libraries)
|
||||
library.handle.dispose()
|
||||
}
|
||||
})
|
||||
expect(result.syntax).toEqual([])
|
||||
expect(result.semantic).toEqual([])
|
||||
expect(result.declarations).toEqual([])
|
||||
expect(result.invalid).toEqual([2769])
|
||||
expect(result.executed).toBe(true)
|
||||
})
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { AudioTrack, Config, Option, QualityLevel, Result } from 'artplayer-plugin-hls-control'
|
||||
import Artplayer from 'artplayer'
|
||||
import hlsControl from 'artplayer-plugin-hls-control'
|
||||
import legacy from 'artplayer-plugin-hls-control/legacy'
|
||||
|
||||
const option: Option = {
|
||||
quality: { control: true, getName: (level, index) => `${level.height}P ${index ?? ''}` },
|
||||
audio: { setting: true, getName: track => track.name },
|
||||
}
|
||||
const art = new Artplayer({ container: '#player', url: 'video.m3u8', plugins: [hlsControl(), hlsControl(option), legacy()] })
|
||||
const result: Result = hlsControl(option)(art)
|
||||
type HistoricalOption = Parameters<typeof hlsControl>[0]
|
||||
const historicalQuality: HistoricalOption['quality'] = { getName: (item: object) => String(item) }
|
||||
hlsControl({ quality: historicalQuality })
|
||||
hlsControl(undefined)
|
||||
const done: void = result.update()
|
||||
const generic: Config = { getName: (item: object) => String(item) }
|
||||
hlsControl({ quality: generic, audio: generic })
|
||||
const level: QualityLevel = { height: 720 }
|
||||
const track: AudioTrack = { id: 0, name: 'English' }
|
||||
|
||||
interface CustomLevel { height: number, bitrate: number, label: string }
|
||||
interface CustomTrack { id: number, name: string, groupId: string }
|
||||
hlsControl({ quality: { getName: (value: CustomLevel) => value.label }, audio: { getName: (value: CustomTrack) => value.groupId } })
|
||||
hlsControl<CustomLevel, CustomTrack>({ quality: { getName: value => String(value.bitrate) } })
|
||||
|
||||
// @ts-expect-error update stays synchronous and returns void.
|
||||
const promise: Promise<void> = result.update()
|
||||
// @ts-expect-error update takes no configuration argument.
|
||||
result.update({})
|
||||
// @ts-expect-error The result name remains a literal.
|
||||
const invalidName: Result['name'] = 'hls'
|
||||
// @ts-expect-error Selected-label calls do not supply the index.
|
||||
hlsControl({ quality: { getName: (value: QualityLevel, index: number) => String(index) } })
|
||||
// @ts-expect-error A formatter must return a string.
|
||||
hlsControl({ audio: { getName: () => 123 } })
|
||||
// @ts-expect-error Control visibility is boolean.
|
||||
legacy({ quality: { control: 'yes' } })
|
||||
// @ts-expect-error Null was never a valid options object.
|
||||
hlsControl(null)
|
||||
// @ts-expect-error Default quality fields are typed, not an any escape.
|
||||
hlsControl({ quality: { getName: value => value.unknownProperty } })
|
||||
void [done, level, track, promise, invalidName]
|
||||
@@ -0,0 +1,9 @@
|
||||
import type PublicFactory from 'artplayer-plugin-hls-control'
|
||||
import factory from '../../packages/artplayer-plugin-hls-control/src/index'
|
||||
|
||||
const compatible: typeof PublicFactory = factory
|
||||
const implementation: typeof factory = compatible
|
||||
implementation({ quality: { getName: level => `${level.height}P` }, audio: { getName: track => track.name } })
|
||||
compatible()
|
||||
// @ts-expect-error Source and public declaration both reject non-string formatter results.
|
||||
factory({ quality: { getName: () => 1 } })
|
||||
Reference in new issue
Block a user