refactor(core): CORE-11 split media events and scope reconnect lifetimes

This commit is contained in:
Harvey Zhao committed 2026-09-11 03:19:52 +08:00
1 parent 5e31274f1d
commit b8039ad1f1
27 files changed
+4844 -475

No files matched your search

+48 -12
View File
@@ -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
+1 -1
View File
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.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
View File
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.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
View File
@@ -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
}
-159
View File
@@ -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)
}
+16 -3
View File
@@ -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
}