mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-16 17:46:17 -08:00
refactor(core): CORE-11 split media events and scope reconnect lifetimes
This commit is contained in:
1 parent
5e31274f1d
commit
b8039ad1f1
27 files changed
+4844
-475
No files matched your search
@@ -99,15 +99,15 @@ when changing defaults, schema or resolution; also rerun installed-package valid
|
||||
|
||||
## Current TypeScript boundary: utilities
|
||||
|
||||
| Module | Responsibility and constraints |
|
||||
| --- | --- |
|
||||
| `src/utils/index.ts` | Existing export barrel; no added runtime names or wrapper methods |
|
||||
| `src/utils/format.ts` | Clamp, capitalization, clock formatting and HTML entity conversion; reusable private lookup maps |
|
||||
| `src/utils/property.ts` | Native defineProperty alias, own-property inspection and recursive merge; keys are written as own data properties |
|
||||
| `src/utils/time.ts` | Sleep, trailing debounce and leading throttle; infer argument tuple/receiver, preserve scheduling and synchronous return behavior |
|
||||
| `src/utils/error.ts` | ArtPlayerError, truthiness guard and internal rejection handling; public media promises are handled elsewhere |
|
||||
| `src/utils/file.ts` | Historical extension parsing and transient download anchor lifecycle |
|
||||
| `src/utils/subtitle.ts` | Existing SRT/ASS-to-VTT text conversions and VTT Blob creation |
|
||||
| Module | Responsibility and constraints |
|
||||
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `src/utils/index.ts` | Existing export barrel; no added runtime names or wrapper methods |
|
||||
| `src/utils/format.ts` | Clamp, capitalization, clock formatting and HTML entity conversion; reusable private lookup maps |
|
||||
| `src/utils/property.ts` | Native defineProperty alias, own-property inspection and recursive merge; keys are written as own data properties |
|
||||
| `src/utils/time.ts` | Sleep, trailing debounce and leading throttle; infer argument tuple/receiver, preserve scheduling and synchronous return behavior |
|
||||
| `src/utils/error.ts` | ArtPlayerError, truthiness guard and internal rejection handling; public media promises are handled elsewhere |
|
||||
| `src/utils/file.ts` | Historical extension parsing and transient download anchor lifecycle |
|
||||
| `src/utils/subtitle.ts` | Existing SRT/ASS-to-VTT text conversions and VTT Blob creation |
|
||||
|
||||
These modules do not import the player, UI components or each other except for the
|
||||
barrel. Existing DOM and browser capability utilities remain JS and are re-exported
|
||||
@@ -133,7 +133,7 @@ throw stops that dispatch and propagates unchanged. Off accepts either a once wr
|
||||
or its original callback; duplicate registrations of a callback are removed together.
|
||||
The once wrapper unsubscribes before invoking user code and now records consumption,
|
||||
so a nested dispatch cannot execute a captured copy twice. This fixes a demonstrated
|
||||
old defect without changing ordinary snapshot behavior. Event names such as __proto__
|
||||
old defect without changing ordinary snapshot behavior. Event names such as **proto**
|
||||
and toString use own properties rather than inherited Object.prototype values.
|
||||
|
||||
Shared contracts in test/contracts/emitter.js run against published, workspace and
|
||||
@@ -318,6 +318,42 @@ finding, not a claim that a standalone debounce can know when its owner is destr
|
||||
|
||||
## Verification and maintenance
|
||||
|
||||
### Media events and reconnect (CORE-11)
|
||||
|
||||
`player/eventInit.ts` preserves registration order while assembling
|
||||
`media/events/{forward,readiness,playback,reconnect,listen,types}.ts`.
|
||||
Forwarding keeps the configured names, original Event object and existing proxy.
|
||||
Owned Emitter handlers detach on destruction; public subscriptions remain intact.
|
||||
UI changes stop if a synchronous component callback closes the instance, so a
|
||||
first-ready control callback cannot cause a ghost ready after destroy.
|
||||
|
||||
Source ownership is now layered:
|
||||
|
||||
```text
|
||||
instance scope
|
||||
current source scope
|
||||
source operation scope (switch completion/failure)
|
||||
pending reconnect scope
|
||||
```
|
||||
|
||||
The source scope survives a rejected switch, allowing the current resource to retry.
|
||||
Playback guards capture both source and operation identity, including late play
|
||||
after a failed switch has already removed its operation from the current map.
|
||||
Changing URL releases the old source scope and all its pending work. Reconnect
|
||||
coalesces duplicate errors into one pending attempt, preserves its first Event,
|
||||
uses the existing delay/limit, and resets on canplay or an independent new source.
|
||||
It still reads option.url when the retry starts; reentrant source replacement
|
||||
invalidates that attempt. Attempt numbers are captured before source assignment
|
||||
so synchronous proxy canplay cannot change the reported number. Recovery cancels
|
||||
pending failure notices and clears art-error. Internal asynchronous observer errors
|
||||
are reported through console.warn with the original thrown value.
|
||||
|
||||
`test/media-events.test.js` covers forwarding, sequence, cancellation, retry budget,
|
||||
failed-switch recovery and reentry. Its browser counterpart uses real HTTP 503 and
|
||||
local media, compares old/new stale retry and ghost-ready behavior, and checks the
|
||||
mobile metadata branch with an Android UA (not a physical device). Proxy-owned SDK
|
||||
events still need adapter-level source identity; native Event has no source token.
|
||||
|
||||
### Playback properties (CORE-10)
|
||||
|
||||
The toggle, currentTime, seek/forward/backward, volume/muted, playbackRate,
|
||||
@@ -365,8 +401,8 @@ guards deferred invocation, ignores obsolete returned failures, and cancels its
|
||||
continuations. Direct assignment failures are reported with console.warn; switch
|
||||
callers receive the original rejection. `playMix.ts` captures the current source:
|
||||
its native result/rejection is preserved, while obsolete notice/event/mutex effects
|
||||
are suppressed. Object URL ownership, other playback migration and reconnect
|
||||
generations remain CORE-19/10/11 work.
|
||||
are suppressed. CORE-10 migrates playback properties and CORE-11 owns reconnect
|
||||
generations; object URL ownership remains CORE-19 work.
|
||||
|
||||
`test/source.test.js` checks cancellation, reentry, synchronous events and cleanup;
|
||||
`test/browser/source.spec.js` compares published/candidate real-media switching and
|
||||
|
||||
Vendored
+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.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var o=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),r.exports);const a=t(o),n="5.4.1",i={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","mediaGroup","muted","networkState","paused","playbackRate","played","preload","readyState","seekable","seeking","src","startDate","textTracks","videoTracks","volume"],methods:["addTextTrack","canPlayType","load","play","pause"],events:["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","progress","ratechange","seeked","seeking","stalled","suspend","timeupdate","volumechange","waiting"],prototypes:["width","height","videoWidth","videoHeight","poster","webkitDecodedFrameCount","webkitDroppedFrameCount","playsInline","webLine truncated
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var o=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),r.exports);const a=t(o),n="5.4.1",i={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","mediaGroup","muted","networkState","paused","playbackRate","played","preload","readyState","seekable","seeking","src","startDate","textTracks","videoTracks","volume"],methods:["addTextTrack","canPlayType","load","play","pause"],events:["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","progress","ratechange","seeked","seeking","stalled","suspend","timeupdate","volumechange","waiting"],prototypes:["width","height","videoWidth","videoHeight","poster","webkitDecodedFrameCount","webkitDroppedFrameCount","playsInline","webLine 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.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t,e=Object.defineProperty,r=Object.defineProperties,o=Object.getOwnPropertyDescriptors,n=Object.getOwnPropertySymbols,a=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,s=(t,r,o)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[r]=o,l=(t,e)=>{for(var r in e||(e={}))a.call(e,r)&&s(t,r,e[r]);if(n)for(var r of n(e))i.call(e,r)&&s(t,r,e[r]);return t},c=(t,e)=>r(t,o(e)),d=(t,e,r)=>new Promise((o,n)=>{var a=t=>{try{s(r.next(t))}catch(e){n(e)}},i=t=>{try{s(r.throw(t))}catch(e){n(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(a,i);s((r=r.apply(t,e)).next())});function p(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var h,u={exports:{}};var m=(h||(h=1,u.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var n=t(r);if("boolean"===n)return"boolean";if("string"===n)return"string";if("number"===n)return"number";if("symbol"===n)return"symbol";if("function"===n)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(n=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return n.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function n(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return a(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),a(s,l,c),i(s,l,c),n(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),a(l,c,d),i(l,c,d),n(l,c,d)})}}(t,e,o),t}function a(t,e,o){if("string"===r(e)){var n=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return n===t}):e.toLowerCase().trim()===n))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(n,"'"))}}function i(t,e,o){if("function"===r(e)){var n=e(t,r(t),o);if(!0!==n){var a=r(n);throw"string"===a?new Error(n):"error"===a?n:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(n,"'"))}}}return n.kindOf=r,n}()),u.exports);const f=p(m),g="5.4.1",v={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","meLine truncated
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t,e=Object.defineProperty,r=Object.defineProperties,o=Object.getOwnPropertyDescriptors,n=Object.getOwnPropertySymbols,a=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,s=(t,r,o)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[r]=o,l=(t,e)=>{for(var r in e||(e={}))a.call(e,r)&&s(t,r,e[r]);if(n)for(var r of n(e))i.call(e,r)&&s(t,r,e[r]);return t},c=(t,e)=>r(t,o(e)),d=(t,e,r)=>new Promise((o,n)=>{var a=t=>{try{s(r.next(t))}catch(e){n(e)}},i=t=>{try{s(r.throw(t))}catch(e){n(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(a,i);s((r=r.apply(t,e)).next())});function p(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var h,u={exports:{}};var m=(h||(h=1,u.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var n=t(r);if("boolean"===n)return"boolean";if("string"===n)return"string";if("number"===n)return"number";if("symbol"===n)return"symbol";if("function"===n)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(n=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return n.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function n(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return a(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),a(s,l,c),i(s,l,c),n(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),a(l,c,d),i(l,c,d),n(l,c,d)})}}(t,e,o),t}function a(t,e,o){if("string"===r(e)){var n=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return n===t}):e.toLowerCase().trim()===n))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(n,"'"))}}function i(t,e,o){if("function"===r(e)){var n=e(t,r(t),o);if(!0!==n){var a=r(n);throw"string"===a?new Error(n):"error"===a?n:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(n,"'"))}}}return n.kindOf=r,n}()),u.exports);const f=p(m),g="5.4.1",v={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","meLine truncated
|
||||
+224
-146
@@ -2770,105 +2770,241 @@ function durationMix(art) {
|
||||
}
|
||||
});
|
||||
}
|
||||
function eventInit(art) {
|
||||
const {
|
||||
i18n,
|
||||
notice,
|
||||
option,
|
||||
constructor,
|
||||
proxy,
|
||||
template: { $player, $video, $poster }
|
||||
} = art;
|
||||
let reconnectTime = 0;
|
||||
function forwardMediaEvents(art) {
|
||||
const { proxy, template: { $video } } = art;
|
||||
for (let index = 0; index < config$1.events.length; index++) {
|
||||
proxy($video, config$1.events[index], (event) => {
|
||||
art.emit(`video:${event.type}`, event);
|
||||
if (!isClosing(art))
|
||||
art.emit(`video:${event.type}`, event);
|
||||
});
|
||||
}
|
||||
art.on("video:canplay", () => {
|
||||
reconnectTime = 0;
|
||||
art.loading.show = false;
|
||||
}
|
||||
function listenMedia(art, name, callback, once = false) {
|
||||
const guarded = (event) => {
|
||||
if (!isClosing(art))
|
||||
callback(event);
|
||||
};
|
||||
art[once ? "once" : "on"](name, guarded);
|
||||
getScope(art).add(() => {
|
||||
art.off(name, guarded);
|
||||
});
|
||||
art.once("video:canplay", () => {
|
||||
art.loading.show = false;
|
||||
art.controls.show = true;
|
||||
art.mask.show = true;
|
||||
}
|
||||
function showMediaUI(art, changes, active) {
|
||||
for (const [name, value] of changes) {
|
||||
if (isClosing(art) || active && !active())
|
||||
return;
|
||||
art[name].show = value;
|
||||
}
|
||||
}
|
||||
const current = /* @__PURE__ */ new WeakMap();
|
||||
const assignments = /* @__PURE__ */ new WeakMap();
|
||||
const sources = /* @__PURE__ */ new WeakMap();
|
||||
function getSourceScope(owner) {
|
||||
return sources.get(owner) || getScope(owner);
|
||||
}
|
||||
function captureSource(owner) {
|
||||
const operation = current.get(owner);
|
||||
const source = sources.get(owner);
|
||||
return () => current.get(owner) === operation && (!operation || operation.active()) && sources.get(owner) === source && (!source || !source.closed);
|
||||
}
|
||||
function beginSource(owner) {
|
||||
const previous = sources.get(owner);
|
||||
const source = getScope(owner).child();
|
||||
const scope = source.child();
|
||||
sources.set(owner, source);
|
||||
source.add(() => {
|
||||
if (sources.get(owner) === source)
|
||||
sources.delete(owner);
|
||||
});
|
||||
const operation = {
|
||||
scope,
|
||||
assigned: false,
|
||||
acceptingEvents: false,
|
||||
active: () => !isClosing(owner) && !scope.closed && current.get(owner) === operation
|
||||
};
|
||||
current.set(owner, operation);
|
||||
scope.add(() => {
|
||||
if (current.get(owner) === operation)
|
||||
current.delete(owner);
|
||||
operation.onAssigned = void 0;
|
||||
operation.onError = void 0;
|
||||
});
|
||||
previous?.dispose();
|
||||
return operation;
|
||||
}
|
||||
function takeAssignment(owner) {
|
||||
const operation = assignments.get(owner);
|
||||
assignments.delete(owner);
|
||||
return operation || beginSource(owner);
|
||||
}
|
||||
function finishAssignment(operation) {
|
||||
operation.assigned = true;
|
||||
const callback = operation.onAssigned;
|
||||
operation.onAssigned = void 0;
|
||||
if (operation.active())
|
||||
callback?.();
|
||||
}
|
||||
function assignUrl(owner, operation, url) {
|
||||
assignments.set(owner, operation);
|
||||
operation.acceptingEvents = true;
|
||||
try {
|
||||
owner.url = url;
|
||||
} finally {
|
||||
if (assignments.get(owner) === operation) {
|
||||
assignments.delete(owner);
|
||||
finishAssignment(operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
function failSource(operation, error2) {
|
||||
if (!operation.active())
|
||||
return;
|
||||
if (operation.onError)
|
||||
operation.onError(error2);
|
||||
else
|
||||
console.warn("Failed to initialize ArtPlayer source:", error2);
|
||||
operation.scope.dispose();
|
||||
}
|
||||
function installEnded(art) {
|
||||
const { option } = art;
|
||||
listenMedia(art, "video:ended", () => {
|
||||
if (option.loop) {
|
||||
const active = captureSource(art);
|
||||
art.seek = 0;
|
||||
if (isClosing(art) || !active())
|
||||
return;
|
||||
silencePromise(art.play());
|
||||
if (active())
|
||||
showMediaUI(art, [["controls", false], ["mask", false]], active);
|
||||
} else {
|
||||
showMediaUI(art, [["controls", true], ["mask", true]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
function installPlaybackUI(art, reconnect) {
|
||||
const { template: { $poster } } = art;
|
||||
listenMedia(art, "video:loadedmetadata", () => {
|
||||
art.emit("resize");
|
||||
if (isMobile)
|
||||
showMediaUI(art, [["loading", false], ["controls", true], ["mask", true]]);
|
||||
});
|
||||
listenMedia(art, "video:loadstart", () => {
|
||||
reconnect.loadStart();
|
||||
showMediaUI(art, [["loading", true], ["mask", false], ["controls", true]]);
|
||||
});
|
||||
listenMedia(art, "video:pause", () => {
|
||||
showMediaUI(art, [["controls", true], ["mask", true]]);
|
||||
});
|
||||
listenMedia(art, "video:play", () => {
|
||||
showMediaUI(art, [["mask", false]]);
|
||||
if (!isClosing(art))
|
||||
setStyle($poster, "display", "none");
|
||||
});
|
||||
listenMedia(art, "video:playing", () => showMediaUI(art, [["mask", false]]));
|
||||
listenMedia(art, "video:progress", () => {
|
||||
if (art.playing)
|
||||
showMediaUI(art, [["loading", false]]);
|
||||
});
|
||||
listenMedia(art, "video:seeked", () => showMediaUI(art, [["loading", false], ["mask", true]]));
|
||||
listenMedia(art, "video:seeking", () => showMediaUI(art, [["loading", true], ["mask", false]]));
|
||||
listenMedia(art, "video:timeupdate", () => showMediaUI(art, [["mask", false]]));
|
||||
listenMedia(art, "video:waiting", () => showMediaUI(art, [["loading", true], ["mask", false]]));
|
||||
}
|
||||
function installReadiness(art, reconnect) {
|
||||
listenMedia(art, "video:canplay", () => {
|
||||
reconnect.reset();
|
||||
showMediaUI(art, [["loading", false]]);
|
||||
});
|
||||
listenMedia(art, "video:canplay", () => {
|
||||
showMediaUI(art, [["loading", false], ["controls", true], ["mask", true]]);
|
||||
if (isClosing(art))
|
||||
return;
|
||||
art.isReady = true;
|
||||
art.emit("ready");
|
||||
});
|
||||
art.on("video:ended", () => {
|
||||
if (option.loop) {
|
||||
art.seek = 0;
|
||||
silencePromise(art.play());
|
||||
art.controls.show = false;
|
||||
art.mask.show = false;
|
||||
} else {
|
||||
art.controls.show = true;
|
||||
art.mask.show = true;
|
||||
}, true);
|
||||
}
|
||||
function createReconnect(art) {
|
||||
const { option, constructor, i18n, notice, template: { $player } } = art;
|
||||
let source = getSourceScope(art);
|
||||
let attempts = 0;
|
||||
let pending;
|
||||
const run = async (error2) => {
|
||||
if (isClosing(art))
|
||||
return;
|
||||
const current2 = getSourceScope(art);
|
||||
if (current2 !== source) {
|
||||
source = current2;
|
||||
attempts = 0;
|
||||
}
|
||||
});
|
||||
art.on("video:error", async (error2) => {
|
||||
if (reconnectTime < constructor.RECONNECT_TIME_MAX) {
|
||||
if (!await wait(getScope(art), constructor.RECONNECT_SLEEP_TIME) || getScope(art).closed)
|
||||
if (pending && !pending.closed)
|
||||
return;
|
||||
const attempt = current2.child();
|
||||
pending = attempt;
|
||||
attempt.add(() => {
|
||||
if (pending === attempt)
|
||||
pending = void 0;
|
||||
});
|
||||
const active = () => !isClosing(art) && !attempt.closed && current2 === getSourceScope(art);
|
||||
const retry = attempts < constructor.RECONNECT_TIME_MAX;
|
||||
try {
|
||||
if (!retry) {
|
||||
showMediaUI(art, [["mask", true], ["loading", false], ["controls", true]], active);
|
||||
if (!active())
|
||||
return;
|
||||
addClass($player, "art-error");
|
||||
}
|
||||
if (!await wait(attempt, constructor.RECONNECT_SLEEP_TIME) || !active())
|
||||
return;
|
||||
reconnectTime += 1;
|
||||
art.url = option.url;
|
||||
notice.show = `${i18n.get("Reconnect")}: ${reconnectTime}`;
|
||||
art.emit("error", error2, reconnectTime);
|
||||
} else {
|
||||
art.mask.show = true;
|
||||
art.loading.show = false;
|
||||
art.controls.show = true;
|
||||
addClass($player, "art-error");
|
||||
if (!await wait(getScope(art), constructor.RECONNECT_SLEEP_TIME) || getScope(art).closed)
|
||||
return;
|
||||
notice.show = i18n.get("Video Load Failed");
|
||||
if (retry) {
|
||||
const url = option.url;
|
||||
if (!active())
|
||||
return;
|
||||
attempts += 1;
|
||||
const attemptNumber = attempts;
|
||||
attempt.dispose();
|
||||
const operation = beginSource(art);
|
||||
source = getSourceScope(art);
|
||||
assignUrl(art, operation, url);
|
||||
if (!operation.active())
|
||||
return;
|
||||
const message = `${i18n.get("Reconnect")}: ${attemptNumber}`;
|
||||
if (!operation.active())
|
||||
return;
|
||||
notice.show = message;
|
||||
if (operation.active())
|
||||
art.emit("error", error2, attemptNumber);
|
||||
} else {
|
||||
const message = i18n.get("Video Load Failed");
|
||||
if (active())
|
||||
notice.show = message;
|
||||
}
|
||||
} finally {
|
||||
attempt.dispose();
|
||||
}
|
||||
});
|
||||
art.on("video:loadedmetadata", () => {
|
||||
art.emit("resize");
|
||||
if (isMobile) {
|
||||
art.loading.show = false;
|
||||
art.controls.show = true;
|
||||
art.mask.show = true;
|
||||
};
|
||||
return {
|
||||
reset() {
|
||||
attempts = 0;
|
||||
pending?.dispose();
|
||||
removeClass($player, "art-error");
|
||||
},
|
||||
loadStart() {
|
||||
removeClass($player, "art-error");
|
||||
},
|
||||
schedule(error2) {
|
||||
void run(error2).catch((failure) => {
|
||||
console.warn("ArtPlayer reconnect failed:", failure);
|
||||
});
|
||||
}
|
||||
});
|
||||
art.on("video:loadstart", () => {
|
||||
art.loading.show = true;
|
||||
art.mask.show = false;
|
||||
art.controls.show = true;
|
||||
});
|
||||
art.on("video:pause", () => {
|
||||
art.controls.show = true;
|
||||
art.mask.show = true;
|
||||
});
|
||||
art.on("video:play", () => {
|
||||
art.mask.show = false;
|
||||
setStyle($poster, "display", "none");
|
||||
});
|
||||
art.on("video:playing", () => {
|
||||
art.mask.show = false;
|
||||
});
|
||||
art.on("video:progress", () => {
|
||||
if (art.playing) {
|
||||
art.loading.show = false;
|
||||
}
|
||||
});
|
||||
art.on("video:seeked", () => {
|
||||
art.loading.show = false;
|
||||
art.mask.show = true;
|
||||
});
|
||||
art.on("video:seeking", () => {
|
||||
art.loading.show = true;
|
||||
art.mask.show = false;
|
||||
});
|
||||
art.on("video:timeupdate", () => {
|
||||
art.mask.show = false;
|
||||
});
|
||||
art.on("video:waiting", () => {
|
||||
art.loading.show = true;
|
||||
art.mask.show = false;
|
||||
});
|
||||
};
|
||||
}
|
||||
function eventInit(art) {
|
||||
forwardMediaEvents(art);
|
||||
const reconnect = createReconnect(art);
|
||||
installReadiness(art, reconnect);
|
||||
installEnded(art);
|
||||
listenMedia(art, "video:error", reconnect.schedule);
|
||||
installPlaybackUI(art, reconnect);
|
||||
}
|
||||
function flipMix(art) {
|
||||
const {
|
||||
@@ -3412,64 +3548,6 @@ function playingMix(art) {
|
||||
}
|
||||
});
|
||||
}
|
||||
const current = /* @__PURE__ */ new WeakMap();
|
||||
const assignments = /* @__PURE__ */ new WeakMap();
|
||||
function captureSource(owner) {
|
||||
const operation = current.get(owner);
|
||||
return () => current.get(owner) === operation && (!operation || operation.active());
|
||||
}
|
||||
function beginSource(owner) {
|
||||
const previous = current.get(owner);
|
||||
const scope = getScope(owner).child();
|
||||
const operation = {
|
||||
scope,
|
||||
assigned: false,
|
||||
acceptingEvents: false,
|
||||
active: () => !isClosing(owner) && !scope.closed && current.get(owner) === operation
|
||||
};
|
||||
current.set(owner, operation);
|
||||
scope.add(() => {
|
||||
if (current.get(owner) === operation)
|
||||
current.delete(owner);
|
||||
operation.onAssigned = void 0;
|
||||
operation.onError = void 0;
|
||||
});
|
||||
previous?.scope.dispose();
|
||||
return operation;
|
||||
}
|
||||
function takeAssignment(owner) {
|
||||
const operation = assignments.get(owner);
|
||||
assignments.delete(owner);
|
||||
return operation || beginSource(owner);
|
||||
}
|
||||
function finishAssignment(operation) {
|
||||
operation.assigned = true;
|
||||
const callback = operation.onAssigned;
|
||||
operation.onAssigned = void 0;
|
||||
if (operation.active())
|
||||
callback?.();
|
||||
}
|
||||
function assignUrl(owner, operation, url) {
|
||||
assignments.set(owner, operation);
|
||||
operation.acceptingEvents = true;
|
||||
try {
|
||||
owner.url = url;
|
||||
} finally {
|
||||
if (assignments.get(owner) === operation) {
|
||||
assignments.delete(owner);
|
||||
finishAssignment(operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
function failSource(operation, error2) {
|
||||
if (!operation.active())
|
||||
return;
|
||||
if (operation.onError)
|
||||
operation.onError(error2);
|
||||
else
|
||||
console.warn("Failed to initialize ArtPlayer source:", error2);
|
||||
operation.scope.dispose();
|
||||
}
|
||||
function playMix(art) {
|
||||
const {
|
||||
i18n,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { MediaEventHost } from './types'
|
||||
import config from '../../config'
|
||||
import { isClosing } from '../../lifecycle/instance'
|
||||
|
||||
export function forwardMediaEvents(art: MediaEventHost): void {
|
||||
const { proxy, template: { $video } } = art
|
||||
for (let index = 0; index < config.events.length; index++) {
|
||||
proxy($video, config.events[index]!, (event: Event) => {
|
||||
if (!isClosing(art))
|
||||
art.emit(`video:${event.type}`, event)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { MediaEventName, MediaEvents, MediaListener, MediaUI } from './types'
|
||||
import { getScope, isClosing } from '../../lifecycle/instance'
|
||||
|
||||
export function listenMedia(art: MediaEvents, name: MediaEventName, callback: MediaListener, once = false): void {
|
||||
const guarded: MediaListener = (event) => {
|
||||
if (!isClosing(art))
|
||||
callback(event)
|
||||
}
|
||||
art[once ? 'once' : 'on'](name, guarded)
|
||||
getScope(art).add(() => {
|
||||
art.off(name, guarded)
|
||||
})
|
||||
}
|
||||
|
||||
export function showMediaUI(art: MediaUI, changes: [keyof MediaUI, boolean][], active?: () => boolean): void {
|
||||
for (const [name, value] of changes) {
|
||||
if (isClosing(art) || (active && !active()))
|
||||
return
|
||||
art[name].show = value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { MediaEventHost, Reconnect } from './types'
|
||||
import { isClosing } from '../../lifecycle/instance'
|
||||
import { captureSource } from '../../source/operation'
|
||||
import { isMobile, setStyle, silencePromise } from '../../utils'
|
||||
import { listenMedia, showMediaUI } from './listen'
|
||||
|
||||
export function installEnded(art: MediaEventHost): void {
|
||||
const { option } = art
|
||||
listenMedia(art, 'video:ended', () => {
|
||||
if (option.loop) {
|
||||
const active = captureSource(art)
|
||||
art.seek = 0
|
||||
if (isClosing(art) || !active())
|
||||
return
|
||||
silencePromise(art.play())
|
||||
if (active())
|
||||
showMediaUI(art, [['controls', false], ['mask', false]], active)
|
||||
}
|
||||
else {
|
||||
showMediaUI(art, [['controls', true], ['mask', true]])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function installPlaybackUI(art: MediaEventHost, reconnect: Reconnect): void {
|
||||
const { template: { $poster } } = art
|
||||
listenMedia(art, 'video:loadedmetadata', () => {
|
||||
art.emit('resize')
|
||||
if (isMobile)
|
||||
showMediaUI(art, [['loading', false], ['controls', true], ['mask', true]])
|
||||
})
|
||||
listenMedia(art, 'video:loadstart', () => {
|
||||
reconnect.loadStart()
|
||||
showMediaUI(art, [['loading', true], ['mask', false], ['controls', true]])
|
||||
})
|
||||
listenMedia(art, 'video:pause', () => {
|
||||
showMediaUI(art, [['controls', true], ['mask', true]])
|
||||
})
|
||||
listenMedia(art, 'video:play', () => {
|
||||
showMediaUI(art, [['mask', false]])
|
||||
if (!isClosing(art))
|
||||
setStyle($poster, 'display', 'none')
|
||||
})
|
||||
listenMedia(art, 'video:playing', () => showMediaUI(art, [['mask', false]]))
|
||||
listenMedia(art, 'video:progress', () => {
|
||||
if (art.playing)
|
||||
showMediaUI(art, [['loading', false]])
|
||||
})
|
||||
listenMedia(art, 'video:seeked', () => showMediaUI(art, [['loading', false], ['mask', true]]))
|
||||
listenMedia(art, 'video:seeking', () => showMediaUI(art, [['loading', true], ['mask', false]]))
|
||||
listenMedia(art, 'video:timeupdate', () => showMediaUI(art, [['mask', false]]))
|
||||
listenMedia(art, 'video:waiting', () => showMediaUI(art, [['loading', true], ['mask', false]]))
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { MediaEventHost, Reconnect } from './types'
|
||||
import { isClosing } from '../../lifecycle/instance'
|
||||
import { listenMedia, showMediaUI } from './listen'
|
||||
|
||||
export function installReadiness(art: MediaEventHost, reconnect: Reconnect): void {
|
||||
listenMedia(art, 'video:canplay', () => {
|
||||
reconnect.reset()
|
||||
showMediaUI(art, [['loading', false]])
|
||||
})
|
||||
listenMedia(art, 'video:canplay', () => {
|
||||
showMediaUI(art, [['loading', false], ['controls', true], ['mask', true]])
|
||||
if (isClosing(art))
|
||||
return
|
||||
art.isReady = true
|
||||
art.emit('ready')
|
||||
}, true)
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import type ResourceScope from '../../lifecycle/scope'
|
||||
import type { MediaEventHost, Reconnect } from './types'
|
||||
import { isClosing } from '../../lifecycle/instance'
|
||||
import { wait } from '../../lifecycle/resources'
|
||||
import { assignUrl, beginSource, getSourceScope } from '../../source/operation'
|
||||
import { addClass, removeClass } from '../../utils'
|
||||
import { showMediaUI } from './listen'
|
||||
|
||||
export function createReconnect(art: MediaEventHost): Reconnect {
|
||||
const { option, constructor, i18n, notice, template: { $player } } = art
|
||||
let source = getSourceScope(art)
|
||||
let attempts = 0
|
||||
let pending: ResourceScope | undefined
|
||||
|
||||
const run = async (error: unknown) => {
|
||||
if (isClosing(art))
|
||||
return
|
||||
const current = getSourceScope(art)
|
||||
if (current !== source) {
|
||||
source = current
|
||||
attempts = 0
|
||||
}
|
||||
if (pending && !pending.closed)
|
||||
return
|
||||
const attempt = current.child()
|
||||
pending = attempt
|
||||
attempt.add(() => {
|
||||
if (pending === attempt)
|
||||
pending = undefined
|
||||
})
|
||||
const active = () => !isClosing(art) && !attempt.closed && current === getSourceScope(art)
|
||||
const retry = attempts < constructor.RECONNECT_TIME_MAX
|
||||
try {
|
||||
if (!retry) {
|
||||
showMediaUI(art, [['mask', true], ['loading', false], ['controls', true]], active)
|
||||
if (!active())
|
||||
return
|
||||
addClass($player, 'art-error')
|
||||
}
|
||||
if (!await wait(attempt, constructor.RECONNECT_SLEEP_TIME) || !active())
|
||||
return
|
||||
if (retry) {
|
||||
const url = option.url
|
||||
if (!active())
|
||||
return
|
||||
attempts += 1
|
||||
const attemptNumber = attempts
|
||||
// End the old timer before creating the retry's new source generation.
|
||||
attempt.dispose()
|
||||
const operation = beginSource(art)
|
||||
source = getSourceScope(art)
|
||||
assignUrl(art, operation, url)
|
||||
if (!operation.active())
|
||||
return
|
||||
const message = `${i18n.get('Reconnect')}: ${attemptNumber}`
|
||||
if (!operation.active())
|
||||
return
|
||||
notice.show = message
|
||||
if (operation.active())
|
||||
art.emit('error', error, attemptNumber)
|
||||
}
|
||||
else {
|
||||
const message = i18n.get('Video Load Failed')
|
||||
if (active())
|
||||
notice.show = message
|
||||
}
|
||||
}
|
||||
finally {
|
||||
attempt.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
reset() {
|
||||
attempts = 0
|
||||
pending?.dispose()
|
||||
removeClass($player, 'art-error')
|
||||
},
|
||||
loadStart() {
|
||||
removeClass($player, 'art-error')
|
||||
},
|
||||
schedule(error) {
|
||||
void run(error).catch((failure) => {
|
||||
console.warn('ArtPlayer reconnect failed:', failure)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
export type MediaEventName = `video:${string}`
|
||||
export type MediaListener = (event?: unknown) => void
|
||||
|
||||
export interface MediaEvents {
|
||||
on: (name: MediaEventName, callback: MediaListener) => unknown
|
||||
once: (name: MediaEventName, callback: MediaListener) => unknown
|
||||
off: (name: MediaEventName, callback: MediaListener) => unknown
|
||||
emit: {
|
||||
(name: MediaEventName, event: Event): unknown
|
||||
(name: 'ready' | 'resize'): unknown
|
||||
(name: 'error', error: unknown, attempt: number): unknown
|
||||
}
|
||||
}
|
||||
|
||||
export interface MediaUI {
|
||||
loading: { set show(value: boolean) }
|
||||
controls: { set show(value: boolean) }
|
||||
mask: { set show(value: boolean) }
|
||||
}
|
||||
|
||||
export interface MediaEventHost extends MediaEvents, MediaUI {
|
||||
template: {
|
||||
$video: EventTarget
|
||||
$player: Pick<HTMLElement, 'classList'>
|
||||
$poster: Pick<HTMLElement, 'style'>
|
||||
}
|
||||
proxy: (target: EventTarget, name: string, callback: (event: Event) => void) => unknown
|
||||
isReady: boolean
|
||||
readonly playing: boolean
|
||||
url: string | null
|
||||
set seek(time: number)
|
||||
play: () => unknown
|
||||
option: { url: string, loop: boolean }
|
||||
i18n: { get: (key: string) => string }
|
||||
notice: { set show(message: string) }
|
||||
constructor: { RECONNECT_TIME_MAX: number, RECONNECT_SLEEP_TIME: number }
|
||||
}
|
||||
|
||||
export interface Reconnect {
|
||||
reset: () => void
|
||||
loadStart: () => void
|
||||
schedule: (error: unknown) => void
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
import config from '../config'
|
||||
import { getScope } from '../lifecycle/instance'
|
||||
import { wait } from '../lifecycle/resources'
|
||||
import { addClass, isMobile, setStyle, silencePromise } from '../utils'
|
||||
|
||||
export default function eventInit(art) {
|
||||
const {
|
||||
i18n,
|
||||
notice,
|
||||
option,
|
||||
constructor,
|
||||
proxy,
|
||||
template: { $player, $video, $poster },
|
||||
} = art
|
||||
|
||||
let reconnectTime = 0
|
||||
|
||||
for (let index = 0; index < config.events.length; index++) {
|
||||
proxy($video, config.events[index], (event) => {
|
||||
art.emit(`video:${event.type}`, event)
|
||||
})
|
||||
}
|
||||
|
||||
// art.on('video:abort', () => {
|
||||
|
||||
// });
|
||||
|
||||
art.on('video:canplay', () => {
|
||||
reconnectTime = 0
|
||||
art.loading.show = false
|
||||
})
|
||||
|
||||
art.once('video:canplay', () => {
|
||||
art.loading.show = false
|
||||
art.controls.show = true
|
||||
art.mask.show = true
|
||||
art.isReady = true
|
||||
art.emit('ready')
|
||||
})
|
||||
|
||||
// art.on('video:canplaythrough', () => {
|
||||
|
||||
// });
|
||||
|
||||
// art.on('video:durationchange', () => {
|
||||
|
||||
// });
|
||||
|
||||
// art.on('video:emptied', () => {
|
||||
|
||||
// });
|
||||
|
||||
art.on('video:ended', () => {
|
||||
if (option.loop) {
|
||||
art.seek = 0
|
||||
silencePromise(art.play())
|
||||
art.controls.show = false
|
||||
art.mask.show = false
|
||||
}
|
||||
else {
|
||||
art.controls.show = true
|
||||
art.mask.show = true
|
||||
}
|
||||
})
|
||||
|
||||
art.on('video:error', async (error) => {
|
||||
if (reconnectTime < constructor.RECONNECT_TIME_MAX) {
|
||||
if (!await wait(getScope(art), constructor.RECONNECT_SLEEP_TIME) || getScope(art).closed)
|
||||
return
|
||||
reconnectTime += 1
|
||||
art.url = option.url
|
||||
notice.show = `${i18n.get('Reconnect')}: ${reconnectTime}`
|
||||
art.emit('error', error, reconnectTime)
|
||||
}
|
||||
else {
|
||||
art.mask.show = true
|
||||
art.loading.show = false
|
||||
art.controls.show = true
|
||||
addClass($player, 'art-error')
|
||||
if (!await wait(getScope(art), constructor.RECONNECT_SLEEP_TIME) || getScope(art).closed)
|
||||
return
|
||||
notice.show = i18n.get('Video Load Failed')
|
||||
}
|
||||
})
|
||||
|
||||
// art.on('video:loadeddata', () => {
|
||||
|
||||
// });
|
||||
|
||||
art.on('video:loadedmetadata', () => {
|
||||
art.emit('resize')
|
||||
if (isMobile) {
|
||||
art.loading.show = false
|
||||
art.controls.show = true
|
||||
art.mask.show = true
|
||||
}
|
||||
})
|
||||
|
||||
art.on('video:loadstart', () => {
|
||||
art.loading.show = true
|
||||
art.mask.show = false
|
||||
art.controls.show = true
|
||||
})
|
||||
|
||||
art.on('video:pause', () => {
|
||||
art.controls.show = true
|
||||
art.mask.show = true
|
||||
})
|
||||
|
||||
art.on('video:play', () => {
|
||||
art.mask.show = false
|
||||
setStyle($poster, 'display', 'none')
|
||||
})
|
||||
|
||||
art.on('video:playing', () => {
|
||||
art.mask.show = false
|
||||
})
|
||||
|
||||
art.on('video:progress', () => {
|
||||
if (art.playing) {
|
||||
art.loading.show = false
|
||||
}
|
||||
})
|
||||
|
||||
// art.on('video:ratechange', () => {
|
||||
|
||||
// });
|
||||
|
||||
art.on('video:seeked', () => {
|
||||
art.loading.show = false
|
||||
art.mask.show = true
|
||||
})
|
||||
|
||||
art.on('video:seeking', () => {
|
||||
art.loading.show = true
|
||||
art.mask.show = false
|
||||
})
|
||||
|
||||
// art.on('video:stalled', () => {
|
||||
|
||||
// });
|
||||
|
||||
// art.on('video:suspend', () => {
|
||||
|
||||
// });
|
||||
|
||||
art.on('video:timeupdate', () => {
|
||||
art.mask.show = false
|
||||
})
|
||||
|
||||
// art.on('video:volumechange', () => {
|
||||
|
||||
// });
|
||||
|
||||
art.on('video:waiting', () => {
|
||||
art.loading.show = true
|
||||
art.mask.show = false
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { MediaEventHost } from '../media/events/types'
|
||||
import { forwardMediaEvents } from '../media/events/forward'
|
||||
import { listenMedia } from '../media/events/listen'
|
||||
import { installEnded, installPlaybackUI } from '../media/events/playback'
|
||||
import { installReadiness } from '../media/events/readiness'
|
||||
import { createReconnect } from '../media/events/reconnect'
|
||||
|
||||
export default function eventInit(art: MediaEventHost): void {
|
||||
forwardMediaEvents(art)
|
||||
const reconnect = createReconnect(art)
|
||||
installReadiness(art, reconnect)
|
||||
installEnded(art)
|
||||
listenMedia(art, 'video:error', reconnect.schedule)
|
||||
installPlaybackUI(art, reconnect)
|
||||
}
|
||||
@@ -13,15 +13,28 @@ export interface SourceOperation {
|
||||
|
||||
const current = new WeakMap<object, SourceOperation>()
|
||||
const assignments = new WeakMap<object, SourceOperation>()
|
||||
const sources = new WeakMap<object, ResourceScope>()
|
||||
|
||||
export function getSourceScope(owner: object): ResourceScope {
|
||||
return sources.get(owner) || getScope(owner)
|
||||
}
|
||||
|
||||
export function captureSource(owner: object): () => boolean {
|
||||
const operation = current.get(owner)
|
||||
const source = sources.get(owner)
|
||||
return () => current.get(owner) === operation && (!operation || operation.active())
|
||||
&& sources.get(owner) === source && (!source || !source.closed)
|
||||
}
|
||||
|
||||
export function beginSource(owner: object): SourceOperation {
|
||||
const previous = current.get(owner)
|
||||
const scope = getScope(owner).child()
|
||||
const previous = sources.get(owner)
|
||||
const source = getScope(owner).child()
|
||||
const scope = source.child()
|
||||
sources.set(owner, source)
|
||||
source.add(() => {
|
||||
if (sources.get(owner) === source)
|
||||
sources.delete(owner)
|
||||
})
|
||||
const operation: SourceOperation = {
|
||||
scope,
|
||||
assigned: false,
|
||||
@@ -35,7 +48,7 @@ export function beginSource(owner: object): SourceOperation {
|
||||
operation.onAssigned = undefined
|
||||
operation.onError = undefined
|
||||
})
|
||||
previous?.scope.dispose()
|
||||
previous?.dispose()
|
||||
return operation
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user