@videojs/spf 10.0.0-beta.23 → 10.0.0-beta.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (461) hide show
  1. package/dist/{dev/core → default/core/actors}/create-machine-actor.js +3 -3
  2. package/dist/default/core/actors/create-machine-actor.js.map +1 -0
  3. package/dist/default/core/{create-transition-actor.js → actors/create-transition-actor.js} +3 -3
  4. package/dist/default/core/actors/create-transition-actor.js.map +1 -0
  5. package/dist/default/core/composition/create-composition.js +85 -0
  6. package/dist/default/core/composition/create-composition.js.map +1 -0
  7. package/dist/default/core/composition/share-signals.js +37 -0
  8. package/dist/default/core/composition/share-signals.js.map +1 -0
  9. package/dist/{dev/core → default/core/reactors}/create-machine-reactor.js +4 -4
  10. package/dist/default/core/reactors/create-machine-reactor.js.map +1 -0
  11. package/dist/default/core/signals/primitives.js +33 -11
  12. package/dist/default/core/signals/primitives.js.map +1 -1
  13. package/dist/default/core/{task.js → tasks/task.js} +2 -2
  14. package/dist/default/core/tasks/task.js.map +1 -0
  15. package/dist/default/dom.js +9 -10
  16. package/dist/default/hls.js +3 -0
  17. package/dist/default/index.js +12 -3
  18. package/dist/default/index.js.map +1 -1
  19. package/dist/default/media/abr/quality-selection.js +83 -0
  20. package/dist/default/media/abr/quality-selection.js.map +1 -0
  21. package/dist/default/{core → media}/buffer/back-buffer.js +2 -2
  22. package/dist/default/media/buffer/back-buffer.js.map +1 -0
  23. package/dist/{dev/core → default/media}/buffer/forward-buffer.js +14 -2
  24. package/dist/default/media/buffer/forward-buffer.js.map +1 -0
  25. package/dist/{dev/dom/media → default/media/dom/mse}/append-segment.js +1 -1
  26. package/dist/default/media/dom/mse/append-segment.js.map +1 -0
  27. package/dist/default/{dom/media → media/dom/mse}/buffer-flusher.js +1 -1
  28. package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -0
  29. package/dist/default/media/dom/mse/duration.js +78 -0
  30. package/dist/default/media/dom/mse/duration.js.map +1 -0
  31. package/dist/default/media/dom/mse/end-of-stream.js +20 -0
  32. package/dist/default/media/dom/mse/end-of-stream.js.map +1 -0
  33. package/dist/default/{dom/media → media/dom/mse}/mediasource-setup.js +63 -18
  34. package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -0
  35. package/dist/default/{dom/text/parse-vtt-segment.js → media/dom/text/resolve-vtt-segment.js} +5 -5
  36. package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -0
  37. package/dist/default/media/dom/text/text-track-slots.js +69 -0
  38. package/dist/default/media/dom/text/text-track-slots.js.map +1 -0
  39. package/dist/{dev/core → default/media}/hls/parse-attributes.js +1 -1
  40. package/dist/default/media/hls/parse-attributes.js.map +1 -0
  41. package/dist/{dev/core → default/media}/hls/parse-media-playlist.js +1 -1
  42. package/dist/default/media/hls/parse-media-playlist.js.map +1 -0
  43. package/dist/{dev/core → default/media}/hls/parse-multivariant.js +2 -2
  44. package/dist/default/media/hls/parse-multivariant.js.map +1 -0
  45. package/dist/default/{core → media}/hls/resolve-url.js +1 -1
  46. package/dist/default/media/hls/resolve-url.js.map +1 -0
  47. package/dist/default/media/primitives/select-tracks.js +45 -0
  48. package/dist/default/media/primitives/select-tracks.js.map +1 -0
  49. package/dist/default/media/types/index.js +36 -0
  50. package/dist/default/media/types/index.js.map +1 -0
  51. package/dist/default/media/utils/preload.js +22 -0
  52. package/dist/default/media/utils/preload.js.map +1 -0
  53. package/dist/default/media/utils/track-selection.js +47 -0
  54. package/dist/default/media/utils/track-selection.js.map +1 -0
  55. package/dist/default/media/utils/tracks.js +103 -0
  56. package/dist/default/media/utils/tracks.js.map +1 -0
  57. package/dist/default/{core/abr → network}/bandwidth-estimator.js +6 -5
  58. package/dist/default/network/bandwidth-estimator.js.map +1 -0
  59. package/dist/{dev/dom → default}/network/chunked-stream-iterable.js +1 -1
  60. package/dist/default/network/chunked-stream-iterable.js.map +1 -0
  61. package/dist/{dev/core/abr → default/network}/ewma.js +1 -1
  62. package/dist/default/network/ewma.js.map +1 -0
  63. package/dist/default/network/fetch.js +109 -0
  64. package/dist/default/network/fetch.js.map +1 -0
  65. package/dist/{dev/dom/features/segment-loader-actor.js → default/playback/actors/dom/segment-loader.js} +24 -15
  66. package/dist/default/playback/actors/dom/segment-loader.js.map +1 -0
  67. package/dist/{dev/dom/media/source-buffer-actor.js → default/playback/actors/dom/source-buffer.js} +8 -8
  68. package/dist/default/playback/actors/dom/source-buffer.js.map +1 -0
  69. package/dist/{dev/dom/features/text-tracks-actor.js → default/playback/actors/dom/text-tracks.js} +7 -3
  70. package/dist/default/playback/actors/dom/text-tracks.js.map +1 -0
  71. package/dist/default/playback/actors/text-track-segment-loader.js +135 -0
  72. package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -0
  73. package/dist/default/playback/behaviors/calculate-presentation-duration.js +58 -0
  74. package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -0
  75. package/dist/default/playback/behaviors/dom/end-of-stream.js +178 -0
  76. package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -0
  77. package/dist/default/playback/behaviors/dom/load-segments.js +177 -0
  78. package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -0
  79. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +173 -0
  80. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
  81. package/dist/default/playback/behaviors/dom/setup-mediasource.js +89 -0
  82. package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -0
  83. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +54 -0
  84. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
  85. package/dist/default/playback/behaviors/dom/sync-text-tracks.js +99 -0
  86. package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
  87. package/dist/default/playback/behaviors/dom/track-current-time.js +46 -0
  88. package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -0
  89. package/dist/default/playback/behaviors/dom/track-load-triggers.js +94 -0
  90. package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -0
  91. package/dist/default/playback/behaviors/dom/track-playback-rate.js +37 -0
  92. package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -0
  93. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +90 -0
  94. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
  95. package/dist/default/playback/behaviors/quality-switching.js +96 -0
  96. package/dist/default/playback/behaviors/quality-switching.js.map +1 -0
  97. package/dist/default/playback/behaviors/resolve-presentation.js +85 -0
  98. package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -0
  99. package/dist/default/playback/behaviors/resolve-track.js +97 -0
  100. package/dist/default/playback/behaviors/resolve-track.js.map +1 -0
  101. package/dist/default/playback/behaviors/select-tracks.js +132 -0
  102. package/dist/default/playback/behaviors/select-tracks.js.map +1 -0
  103. package/dist/default/playback/behaviors/sync-preload.js +59 -0
  104. package/dist/default/playback/behaviors/sync-preload.js.map +1 -0
  105. package/dist/default/playback/behaviors/track-types.js +61 -0
  106. package/dist/default/playback/behaviors/track-types.js.map +1 -0
  107. package/dist/default/{dom/playback-engine → playback/engines/hls}/adapter.js +33 -26
  108. package/dist/default/playback/engines/hls/adapter.js.map +1 -0
  109. package/dist/default/playback/engines/hls/engine.js +102 -0
  110. package/dist/default/playback/engines/hls/engine.js.map +1 -0
  111. package/dist/dev/core/{actor.d.ts → actors/actor.d.ts} +2 -2
  112. package/dist/dev/core/actors/actor.d.ts.map +1 -0
  113. package/dist/dev/core/actors/create-machine-actor.d.ts +125 -0
  114. package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -0
  115. package/dist/{default/core → dev/core/actors}/create-machine-actor.js +3 -3
  116. package/dist/dev/core/actors/create-machine-actor.js.map +1 -0
  117. package/dist/dev/core/actors/create-transition-actor.d.ts +42 -0
  118. package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -0
  119. package/dist/dev/core/{create-transition-actor.js → actors/create-transition-actor.js} +3 -3
  120. package/dist/dev/core/actors/create-transition-actor.js.map +1 -0
  121. package/dist/dev/core/composition/create-composition.d.ts +274 -0
  122. package/dist/dev/core/composition/create-composition.d.ts.map +1 -0
  123. package/dist/dev/core/composition/create-composition.js +85 -0
  124. package/dist/dev/core/composition/create-composition.js.map +1 -0
  125. package/dist/dev/core/composition/share-signals.d.ts +45 -0
  126. package/dist/dev/core/composition/share-signals.d.ts.map +1 -0
  127. package/dist/dev/core/composition/share-signals.js +37 -0
  128. package/dist/dev/core/composition/share-signals.js.map +1 -0
  129. package/dist/dev/core/machine.d.ts +0 -1
  130. package/dist/dev/core/machine.d.ts.map +1 -1
  131. package/dist/dev/core/reactors/create-machine-reactor.d.ts +93 -0
  132. package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -0
  133. package/dist/{default/core → dev/core/reactors}/create-machine-reactor.js +4 -4
  134. package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -0
  135. package/dist/dev/core/signals/primitives.d.ts +38 -1
  136. package/dist/dev/core/signals/primitives.d.ts.map +1 -1
  137. package/dist/dev/core/signals/primitives.js +33 -11
  138. package/dist/dev/core/signals/primitives.js.map +1 -1
  139. package/dist/dev/core/tasks/task.d.ts +115 -0
  140. package/dist/dev/core/tasks/task.d.ts.map +1 -0
  141. package/dist/dev/core/{task.js → tasks/task.js} +2 -2
  142. package/dist/dev/core/tasks/task.js.map +1 -0
  143. package/dist/dev/dom.d.ts +9 -10
  144. package/dist/dev/dom.js +9 -10
  145. package/dist/dev/hls.d.ts +3 -0
  146. package/dist/dev/hls.js +3 -0
  147. package/dist/dev/index.d.ts +15 -3
  148. package/dist/dev/index.d.ts.map +1 -1
  149. package/dist/dev/index.js +12 -3
  150. package/dist/dev/index.js.map +1 -1
  151. package/dist/dev/media/abr/quality-selection.d.ts +22 -0
  152. package/dist/dev/media/abr/quality-selection.d.ts.map +1 -0
  153. package/dist/dev/media/abr/quality-selection.js +83 -0
  154. package/dist/dev/media/abr/quality-selection.js.map +1 -0
  155. package/dist/dev/media/buffer/back-buffer.d.ts +14 -0
  156. package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -0
  157. package/dist/dev/{core → media}/buffer/back-buffer.js +2 -2
  158. package/dist/dev/media/buffer/back-buffer.js.map +1 -0
  159. package/dist/dev/media/buffer/forward-buffer.d.ts +14 -0
  160. package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -0
  161. package/dist/{default/core → dev/media}/buffer/forward-buffer.js +14 -2
  162. package/dist/dev/media/buffer/forward-buffer.js.map +1 -0
  163. package/dist/dev/{dom/media → media/dom/mse}/append-segment.d.ts +1 -1
  164. package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -0
  165. package/dist/{default/dom/media → dev/media/dom/mse}/append-segment.js +1 -1
  166. package/dist/dev/media/dom/mse/append-segment.js.map +1 -0
  167. package/dist/dev/{dom/media → media/dom/mse}/buffer-flusher.d.ts +1 -1
  168. package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -0
  169. package/dist/dev/{dom/media → media/dom/mse}/buffer-flusher.js +1 -1
  170. package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -0
  171. package/dist/dev/media/dom/mse/duration.js +78 -0
  172. package/dist/dev/media/dom/mse/duration.js.map +1 -0
  173. package/dist/dev/media/dom/mse/end-of-stream.js +20 -0
  174. package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -0
  175. package/dist/dev/{dom/media → media/dom/mse}/mediasource-setup.js +63 -18
  176. package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -0
  177. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +12 -0
  178. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -0
  179. package/dist/dev/{dom/text/parse-vtt-segment.js → media/dom/text/resolve-vtt-segment.js} +5 -5
  180. package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -0
  181. package/dist/dev/media/dom/text/text-track-slots.d.ts +31 -0
  182. package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -0
  183. package/dist/dev/media/dom/text/text-track-slots.js +69 -0
  184. package/dist/dev/media/dom/text/text-track-slots.js.map +1 -0
  185. package/dist/{default/core → dev/media}/hls/parse-attributes.js +1 -1
  186. package/dist/dev/media/hls/parse-attributes.js.map +1 -0
  187. package/dist/{default/core → dev/media}/hls/parse-media-playlist.js +1 -1
  188. package/dist/dev/media/hls/parse-media-playlist.js.map +1 -0
  189. package/dist/{default/core → dev/media}/hls/parse-multivariant.js +2 -2
  190. package/dist/dev/media/hls/parse-multivariant.js.map +1 -0
  191. package/dist/dev/{core → media}/hls/resolve-url.js +1 -1
  192. package/dist/dev/media/hls/resolve-url.js.map +1 -0
  193. package/dist/dev/media/primitives/select-tracks.js +45 -0
  194. package/dist/dev/media/primitives/select-tracks.js.map +1 -0
  195. package/dist/dev/{core → media}/types/index.d.ts +30 -2
  196. package/dist/dev/media/types/index.d.ts.map +1 -0
  197. package/dist/dev/media/types/index.js +36 -0
  198. package/dist/dev/media/types/index.js.map +1 -0
  199. package/dist/dev/media/utils/preload.js +22 -0
  200. package/dist/dev/media/utils/preload.js.map +1 -0
  201. package/dist/dev/media/utils/track-selection.js +47 -0
  202. package/dist/dev/media/utils/track-selection.js.map +1 -0
  203. package/dist/dev/media/utils/tracks.js +103 -0
  204. package/dist/dev/media/utils/tracks.js.map +1 -0
  205. package/dist/dev/{core/abr → network}/bandwidth-estimator.d.ts +17 -2
  206. package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -0
  207. package/dist/dev/{core/abr → network}/bandwidth-estimator.js +6 -5
  208. package/dist/dev/network/bandwidth-estimator.js.map +1 -0
  209. package/dist/{default/dom → dev}/network/chunked-stream-iterable.js +1 -1
  210. package/dist/dev/network/chunked-stream-iterable.js.map +1 -0
  211. package/dist/{default/core/abr → dev/network}/ewma.js +1 -1
  212. package/dist/dev/network/ewma.js.map +1 -0
  213. package/dist/dev/network/fetch.js +109 -0
  214. package/dist/dev/network/fetch.js.map +1 -0
  215. package/dist/dev/playback/actors/dom/segment-loader.d.ts +36 -0
  216. package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -0
  217. package/dist/{default/dom/features/segment-loader-actor.js → dev/playback/actors/dom/segment-loader.js} +24 -15
  218. package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -0
  219. package/dist/dev/{dom/media/source-buffer-actor.d.ts → playback/actors/dom/source-buffer.d.ts} +5 -5
  220. package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -0
  221. package/dist/{default/dom/media/source-buffer-actor.js → dev/playback/actors/dom/source-buffer.js} +8 -8
  222. package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -0
  223. package/dist/dev/playback/actors/dom/text-tracks.d.ts +1 -0
  224. package/dist/{default/dom/features/text-tracks-actor.js → dev/playback/actors/dom/text-tracks.js} +7 -3
  225. package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -0
  226. package/dist/dev/playback/actors/text-track-segment-loader.d.ts +59 -0
  227. package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -0
  228. package/dist/dev/playback/actors/text-track-segment-loader.js +135 -0
  229. package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -0
  230. package/dist/dev/playback/actors/text-tracks.d.ts +42 -0
  231. package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -0
  232. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +17 -0
  233. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -0
  234. package/dist/dev/playback/behaviors/calculate-presentation-duration.js +58 -0
  235. package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -0
  236. package/dist/dev/playback/behaviors/dom/end-of-stream.js +178 -0
  237. package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -0
  238. package/dist/dev/playback/behaviors/dom/load-segments.d.ts +62 -0
  239. package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -0
  240. package/dist/dev/playback/behaviors/dom/load-segments.js +177 -0
  241. package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -0
  242. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +173 -0
  243. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
  244. package/dist/dev/playback/behaviors/dom/setup-mediasource.js +89 -0
  245. package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -0
  246. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +31 -0
  247. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -0
  248. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +54 -0
  249. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
  250. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js +99 -0
  251. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
  252. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +34 -0
  253. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -0
  254. package/dist/dev/playback/behaviors/dom/track-current-time.js +46 -0
  255. package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -0
  256. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +48 -0
  257. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -0
  258. package/dist/dev/playback/behaviors/dom/track-load-triggers.js +94 -0
  259. package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -0
  260. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +34 -0
  261. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -0
  262. package/dist/dev/playback/behaviors/dom/track-playback-rate.js +37 -0
  263. package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -0
  264. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +90 -0
  265. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
  266. package/dist/dev/playback/behaviors/quality-switching.js +96 -0
  267. package/dist/dev/playback/behaviors/quality-switching.js.map +1 -0
  268. package/dist/dev/playback/behaviors/resolve-presentation.d.ts +7 -0
  269. package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -0
  270. package/dist/dev/playback/behaviors/resolve-presentation.js +85 -0
  271. package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -0
  272. package/dist/dev/playback/behaviors/resolve-track.js +97 -0
  273. package/dist/dev/playback/behaviors/resolve-track.js.map +1 -0
  274. package/dist/dev/playback/behaviors/select-tracks.js +132 -0
  275. package/dist/dev/playback/behaviors/select-tracks.js.map +1 -0
  276. package/dist/dev/playback/behaviors/sync-preload.js +59 -0
  277. package/dist/dev/playback/behaviors/sync-preload.js.map +1 -0
  278. package/dist/dev/playback/behaviors/track-types.js +61 -0
  279. package/dist/dev/playback/behaviors/track-types.js.map +1 -0
  280. package/dist/dev/{dom/playback-engine → playback/engines/hls}/adapter.d.ts +13 -12
  281. package/dist/dev/playback/engines/hls/adapter.d.ts.map +1 -0
  282. package/dist/dev/{dom/playback-engine → playback/engines/hls}/adapter.js +33 -26
  283. package/dist/dev/playback/engines/hls/adapter.js.map +1 -0
  284. package/dist/dev/playback/engines/hls/engine.d.ts +177 -0
  285. package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -0
  286. package/dist/dev/playback/engines/hls/engine.js +102 -0
  287. package/dist/dev/playback/engines/hls/engine.js.map +1 -0
  288. package/package.json +7 -7
  289. package/dist/default/core/abr/bandwidth-estimator.js.map +0 -1
  290. package/dist/default/core/abr/ewma.js.map +0 -1
  291. package/dist/default/core/abr/quality-selection.js +0 -53
  292. package/dist/default/core/abr/quality-selection.js.map +0 -1
  293. package/dist/default/core/buffer/back-buffer.js.map +0 -1
  294. package/dist/default/core/buffer/forward-buffer.js.map +0 -1
  295. package/dist/default/core/create-machine-actor.js.map +0 -1
  296. package/dist/default/core/create-machine-reactor.js.map +0 -1
  297. package/dist/default/core/create-transition-actor.js.map +0 -1
  298. package/dist/default/core/features/calculate-presentation-duration.js +0 -53
  299. package/dist/default/core/features/calculate-presentation-duration.js.map +0 -1
  300. package/dist/default/core/features/quality-switching.js +0 -69
  301. package/dist/default/core/features/quality-switching.js.map +0 -1
  302. package/dist/default/core/features/resolve-presentation.js +0 -73
  303. package/dist/default/core/features/resolve-presentation.js.map +0 -1
  304. package/dist/default/core/features/resolve-track.js +0 -66
  305. package/dist/default/core/features/resolve-track.js.map +0 -1
  306. package/dist/default/core/features/select-tracks.js +0 -130
  307. package/dist/default/core/features/select-tracks.js.map +0 -1
  308. package/dist/default/core/features/sync-preload-attribute.js +0 -26
  309. package/dist/default/core/features/sync-preload-attribute.js.map +0 -1
  310. package/dist/default/core/hls/parse-attributes.js.map +0 -1
  311. package/dist/default/core/hls/parse-media-playlist.js.map +0 -1
  312. package/dist/default/core/hls/parse-multivariant.js.map +0 -1
  313. package/dist/default/core/hls/resolve-url.js.map +0 -1
  314. package/dist/default/core/task.js.map +0 -1
  315. package/dist/default/core/types/index.js +0 -15
  316. package/dist/default/core/types/index.js.map +0 -1
  317. package/dist/default/core/utils/generate-id.js +0 -21
  318. package/dist/default/core/utils/generate-id.js.map +0 -1
  319. package/dist/default/core/utils/track-selection.js +0 -39
  320. package/dist/default/core/utils/track-selection.js.map +0 -1
  321. package/dist/default/dom/features/end-of-stream.js +0 -145
  322. package/dist/default/dom/features/end-of-stream.js.map +0 -1
  323. package/dist/default/dom/features/load-segments.js +0 -192
  324. package/dist/default/dom/features/load-segments.js.map +0 -1
  325. package/dist/default/dom/features/load-text-track-cues.js +0 -104
  326. package/dist/default/dom/features/load-text-track-cues.js.map +0 -1
  327. package/dist/default/dom/features/segment-loader-actor.js.map +0 -1
  328. package/dist/default/dom/features/setup-mediasource.js +0 -48
  329. package/dist/default/dom/features/setup-mediasource.js.map +0 -1
  330. package/dist/default/dom/features/setup-sourcebuffer.js +0 -78
  331. package/dist/default/dom/features/setup-sourcebuffer.js.map +0 -1
  332. package/dist/default/dom/features/sync-text-tracks.js +0 -95
  333. package/dist/default/dom/features/sync-text-tracks.js.map +0 -1
  334. package/dist/default/dom/features/text-track-segment-loader-actor.js +0 -55
  335. package/dist/default/dom/features/text-track-segment-loader-actor.js.map +0 -1
  336. package/dist/default/dom/features/text-tracks-actor.js.map +0 -1
  337. package/dist/default/dom/features/track-current-time.js +0 -49
  338. package/dist/default/dom/features/track-current-time.js.map +0 -1
  339. package/dist/default/dom/features/track-playback-initiated.js +0 -66
  340. package/dist/default/dom/features/track-playback-initiated.js.map +0 -1
  341. package/dist/default/dom/features/track-playback-rate.js +0 -32
  342. package/dist/default/dom/features/track-playback-rate.js.map +0 -1
  343. package/dist/default/dom/features/update-duration.js +0 -78
  344. package/dist/default/dom/features/update-duration.js.map +0 -1
  345. package/dist/default/dom/media/append-segment.js.map +0 -1
  346. package/dist/default/dom/media/buffer-flusher.js.map +0 -1
  347. package/dist/default/dom/media/mediasource-setup.js.map +0 -1
  348. package/dist/default/dom/media/source-buffer-actor.js.map +0 -1
  349. package/dist/default/dom/network/chunked-stream-iterable.js.map +0 -1
  350. package/dist/default/dom/network/fetch.js +0 -54
  351. package/dist/default/dom/network/fetch.js.map +0 -1
  352. package/dist/default/dom/playback-engine/adapter.js.map +0 -1
  353. package/dist/default/dom/playback-engine/engine.js +0 -142
  354. package/dist/default/dom/playback-engine/engine.js.map +0 -1
  355. package/dist/default/dom/playback-engine.js +0 -4
  356. package/dist/default/dom/text/parse-vtt-segment.js.map +0 -1
  357. package/dist/dev/core/abr/bandwidth-estimator.d.ts.map +0 -1
  358. package/dist/dev/core/abr/bandwidth-estimator.js.map +0 -1
  359. package/dist/dev/core/abr/ewma.js.map +0 -1
  360. package/dist/dev/core/abr/quality-selection.js +0 -53
  361. package/dist/dev/core/abr/quality-selection.js.map +0 -1
  362. package/dist/dev/core/actor.d.ts.map +0 -1
  363. package/dist/dev/core/buffer/back-buffer.js.map +0 -1
  364. package/dist/dev/core/buffer/forward-buffer.js.map +0 -1
  365. package/dist/dev/core/create-machine-actor.d.ts +0 -12
  366. package/dist/dev/core/create-machine-actor.d.ts.map +0 -1
  367. package/dist/dev/core/create-machine-actor.js.map +0 -1
  368. package/dist/dev/core/create-machine-reactor.d.ts +0 -8
  369. package/dist/dev/core/create-machine-reactor.d.ts.map +0 -1
  370. package/dist/dev/core/create-machine-reactor.js.map +0 -1
  371. package/dist/dev/core/create-transition-actor.d.ts +0 -23
  372. package/dist/dev/core/create-transition-actor.d.ts.map +0 -1
  373. package/dist/dev/core/create-transition-actor.js.map +0 -1
  374. package/dist/dev/core/features/calculate-presentation-duration.js +0 -53
  375. package/dist/dev/core/features/calculate-presentation-duration.js.map +0 -1
  376. package/dist/dev/core/features/quality-switching.js +0 -69
  377. package/dist/dev/core/features/quality-switching.js.map +0 -1
  378. package/dist/dev/core/features/resolve-presentation.js +0 -73
  379. package/dist/dev/core/features/resolve-presentation.js.map +0 -1
  380. package/dist/dev/core/features/resolve-track.js +0 -66
  381. package/dist/dev/core/features/resolve-track.js.map +0 -1
  382. package/dist/dev/core/features/select-tracks.js +0 -130
  383. package/dist/dev/core/features/select-tracks.js.map +0 -1
  384. package/dist/dev/core/features/sync-preload-attribute.js +0 -26
  385. package/dist/dev/core/features/sync-preload-attribute.js.map +0 -1
  386. package/dist/dev/core/hls/parse-attributes.js.map +0 -1
  387. package/dist/dev/core/hls/parse-media-playlist.js.map +0 -1
  388. package/dist/dev/core/hls/parse-multivariant.js.map +0 -1
  389. package/dist/dev/core/hls/resolve-url.js.map +0 -1
  390. package/dist/dev/core/task.js.map +0 -1
  391. package/dist/dev/core/types/index.d.ts.map +0 -1
  392. package/dist/dev/core/types/index.js +0 -15
  393. package/dist/dev/core/types/index.js.map +0 -1
  394. package/dist/dev/core/utils/generate-id.js +0 -21
  395. package/dist/dev/core/utils/generate-id.js.map +0 -1
  396. package/dist/dev/core/utils/track-selection.d.ts +0 -16
  397. package/dist/dev/core/utils/track-selection.d.ts.map +0 -1
  398. package/dist/dev/core/utils/track-selection.js +0 -39
  399. package/dist/dev/core/utils/track-selection.js.map +0 -1
  400. package/dist/dev/dom/features/end-of-stream.js +0 -145
  401. package/dist/dev/dom/features/end-of-stream.js.map +0 -1
  402. package/dist/dev/dom/features/load-segments.d.ts +0 -72
  403. package/dist/dev/dom/features/load-segments.d.ts.map +0 -1
  404. package/dist/dev/dom/features/load-segments.js +0 -192
  405. package/dist/dev/dom/features/load-segments.js.map +0 -1
  406. package/dist/dev/dom/features/load-text-track-cues.d.ts +0 -85
  407. package/dist/dev/dom/features/load-text-track-cues.d.ts.map +0 -1
  408. package/dist/dev/dom/features/load-text-track-cues.js +0 -104
  409. package/dist/dev/dom/features/load-text-track-cues.js.map +0 -1
  410. package/dist/dev/dom/features/segment-loader-actor.d.ts +0 -1
  411. package/dist/dev/dom/features/segment-loader-actor.js.map +0 -1
  412. package/dist/dev/dom/features/setup-mediasource.js +0 -48
  413. package/dist/dev/dom/features/setup-mediasource.js.map +0 -1
  414. package/dist/dev/dom/features/setup-sourcebuffer.d.ts +0 -9
  415. package/dist/dev/dom/features/setup-sourcebuffer.d.ts.map +0 -1
  416. package/dist/dev/dom/features/setup-sourcebuffer.js +0 -78
  417. package/dist/dev/dom/features/setup-sourcebuffer.js.map +0 -1
  418. package/dist/dev/dom/features/sync-text-tracks.js +0 -95
  419. package/dist/dev/dom/features/sync-text-tracks.js.map +0 -1
  420. package/dist/dev/dom/features/text-track-segment-loader-actor.d.ts +0 -12
  421. package/dist/dev/dom/features/text-track-segment-loader-actor.d.ts.map +0 -1
  422. package/dist/dev/dom/features/text-track-segment-loader-actor.js +0 -55
  423. package/dist/dev/dom/features/text-track-segment-loader-actor.js.map +0 -1
  424. package/dist/dev/dom/features/text-tracks-actor.d.ts +0 -31
  425. package/dist/dev/dom/features/text-tracks-actor.d.ts.map +0 -1
  426. package/dist/dev/dom/features/text-tracks-actor.js.map +0 -1
  427. package/dist/dev/dom/features/track-current-time.d.ts +0 -40
  428. package/dist/dev/dom/features/track-current-time.d.ts.map +0 -1
  429. package/dist/dev/dom/features/track-current-time.js +0 -49
  430. package/dist/dev/dom/features/track-current-time.js.map +0 -1
  431. package/dist/dev/dom/features/track-playback-initiated.d.ts +0 -46
  432. package/dist/dev/dom/features/track-playback-initiated.d.ts.map +0 -1
  433. package/dist/dev/dom/features/track-playback-initiated.js +0 -66
  434. package/dist/dev/dom/features/track-playback-initiated.js.map +0 -1
  435. package/dist/dev/dom/features/track-playback-rate.d.ts +0 -34
  436. package/dist/dev/dom/features/track-playback-rate.d.ts.map +0 -1
  437. package/dist/dev/dom/features/track-playback-rate.js +0 -32
  438. package/dist/dev/dom/features/track-playback-rate.js.map +0 -1
  439. package/dist/dev/dom/features/update-duration.js +0 -78
  440. package/dist/dev/dom/features/update-duration.js.map +0 -1
  441. package/dist/dev/dom/media/append-segment.d.ts.map +0 -1
  442. package/dist/dev/dom/media/append-segment.js.map +0 -1
  443. package/dist/dev/dom/media/buffer-flusher.d.ts.map +0 -1
  444. package/dist/dev/dom/media/buffer-flusher.js.map +0 -1
  445. package/dist/dev/dom/media/mediasource-setup.js.map +0 -1
  446. package/dist/dev/dom/media/source-buffer-actor.d.ts.map +0 -1
  447. package/dist/dev/dom/media/source-buffer-actor.js.map +0 -1
  448. package/dist/dev/dom/network/chunked-stream-iterable.js.map +0 -1
  449. package/dist/dev/dom/network/fetch.js +0 -54
  450. package/dist/dev/dom/network/fetch.js.map +0 -1
  451. package/dist/dev/dom/playback-engine/adapter.d.ts.map +0 -1
  452. package/dist/dev/dom/playback-engine/adapter.js.map +0 -1
  453. package/dist/dev/dom/playback-engine/engine.d.ts +0 -124
  454. package/dist/dev/dom/playback-engine/engine.d.ts.map +0 -1
  455. package/dist/dev/dom/playback-engine/engine.js +0 -142
  456. package/dist/dev/dom/playback-engine/engine.js.map +0 -1
  457. package/dist/dev/dom/playback-engine.d.ts +0 -4
  458. package/dist/dev/dom/playback-engine.js +0 -4
  459. package/dist/dev/dom/text/parse-vtt-segment.d.ts +0 -12
  460. package/dist/dev/dom/text/parse-vtt-segment.d.ts.map +0 -1
  461. package/dist/dev/dom/text/parse-vtt-segment.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import { untrack, update } from "./signals/primitives.js";
2
- import { createMachineCore } from "./machine.js";
3
- //#region src/core/create-machine-actor.ts
1
+ import { untrack, update } from "../signals/primitives.js";
2
+ import { createMachineCore } from "../machine.js";
3
+ //#region src/core/actors/create-machine-actor.ts
4
4
  /**
5
5
  * Creates a message-driven actor from a declarative definition.
6
6
  *
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-machine-actor.js","names":[],"sources":["../../../../src/core/actors/create-machine-actor.ts"],"sourcesContent":["import { createMachineCore } from '../machine';\nimport { untrack, update } from '../signals/primitives';\nimport type { TaskLike } from '../tasks/task';\nimport type { ActorSnapshot, SignalActor } from './actor';\n\n// =============================================================================\n// Runner interfaces\n// =============================================================================\n\n/**\n * Minimal interface for any runner that can be used with createMachineActor.\n */\nexport interface RunnerLike {\n schedule<Value = void, Err = unknown>(task: TaskLike<Value, Err>): Promise<Value>;\n abortAll(): void;\n destroy(): void;\n whenSettled(callback: () => void): void;\n}\n\n// =============================================================================\n// Definition types\n// =============================================================================\n\n/**\n * Context passed to message handlers.\n * `runner` is present and typed as the exact runner instance only when the\n * definition includes a runner factory.\n */\nexport type HandlerContext<\n UserState extends string,\n Context extends object,\n RunnerFactory extends (() => RunnerLike) | undefined,\n> = {\n transition: (to: UserState) => void;\n /** Context snapshot captured at dispatch time. Stale after any `setContext` call. */\n context: Context;\n /**\n * Live untracked read of the current context. Use in async task closures that\n * execute after the handler returns — e.g. `getCtx: getContext` passed to tasks\n * scheduled on the runner, so each task reads the context committed by the\n * previous task rather than the stale snapshot from dispatch time.\n */\n getContext: () => Context;\n setContext: (next: Context) => void;\n} & (RunnerFactory extends () => infer R ? { runner: R } : object);\n\n/**\n * Definition for a single user-defined state.\n */\nexport type ActorStateDefinition<\n UserState extends string,\n Context extends object,\n Message extends { type: string },\n RunnerFactory extends (() => RunnerLike) | undefined,\n> = {\n /**\n * When the actor's runner settles while in this state, automatically\n * transition to this state. The framework owns the generation-token logic —\n * re-registering after each `runner.schedule()` call so that\n * `abortAll()` + reschedule correctly supersedes stale callbacks.\n */\n onSettled?: UserState;\n /** Message handlers active in this state. Messages with no handler are silently dropped. */\n on?: {\n [M in Message as M['type']]?: (\n message: Extract<Message, { type: M['type'] }>,\n ctx: HandlerContext<UserState, Context, RunnerFactory>\n ) => void;\n };\n};\n\n/**\n * Full actor definition passed to `createMachineActor`.\n *\n * `UserState` is the set of domain-meaningful states. `'destroyed'` is always\n * added by the framework as the implicit terminal state — do not include it here.\n */\nexport type ActorDefinition<\n UserState extends string,\n Context extends object,\n Message extends { type: string },\n RunnerFactory extends (() => RunnerLike) | undefined = undefined,\n> = {\n /**\n * Runner factory — called once at `createMachineActor()` time.\n * The runner lives for the full actor lifetime and is destroyed with it.\n *\n * @example\n * runner: () => new SerialRunner()\n */\n runner?: RunnerFactory;\n /** Initial state. */\n initial: UserState;\n /** Initial context. */\n context: Context;\n /**\n * Per-state definitions. States with no definition silently drop all messages.\n * All user-defined states must appear as keys in the `UserState` union.\n */\n states: Partial<Record<UserState, ActorStateDefinition<UserState, Context, Message, RunnerFactory>>>;\n};\n\n// =============================================================================\n// Live actor interface\n// =============================================================================\n\n/** Live actor instance returned by `createMachineActor`. */\nexport interface MessageActor<State extends string, Context extends object, Message extends { type: string }>\n extends SignalActor<State, Context> {\n send(message: Message): void;\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/**\n * Creates a message-driven actor from a declarative definition.\n *\n * The actor owns a reactive snapshot signal (state + context), an optional\n * runner, and dispatches incoming messages to per-state handlers. `'destroyed'`\n * is always the implicit terminal state — `destroy()` transitions there\n * unconditionally and all subsequent `send()` calls are no-ops.\n *\n * When a state declares `onSettled`, the framework calls `runner.whenSettled()`\n * after the handler returns. The runner owns the generation-token logic — if\n * new tasks are scheduled before the current batch settles, the callback is\n * automatically superseded.\n *\n * @example\n * const actor = createMachineActor({\n * runner: () => new SerialRunner(),\n * initial: 'idle',\n * context: {},\n * states: {\n * idle: {\n * on: {\n * load: (msg, { transition, runner }) => {\n * segments.forEach(s => runner.schedule(new Task(...)));\n * transition('loading');\n * }\n * }\n * },\n * loading: {\n * onSettled: 'idle',\n * on: {\n * load: (msg, { runner }) => {\n * runner.abortAll();\n * segments.forEach(s => runner.schedule(new Task(...)));\n * }\n * }\n * }\n * }\n * });\n */\nexport function createMachineActor<\n UserState extends string,\n Context extends object,\n Message extends { type: string },\n RunnerFactory extends (() => RunnerLike) | undefined = undefined,\n>(\n def: ActorDefinition<UserState, Context, Message, RunnerFactory>\n): MessageActor<UserState | 'destroyed', Context, Message> {\n type FullState = UserState | 'destroyed';\n\n const runner = def.runner?.() as RunnerLike | undefined;\n const { snapshotSignal, getState, transition } = createMachineCore<FullState, ActorSnapshot<FullState, Context>>({\n value: def.initial as FullState,\n context: def.context,\n });\n\n const getContext = (): Context => untrack(() => snapshotSignal.get().context);\n\n const setContext = (context: Context): void => {\n update(snapshotSignal, { context });\n };\n\n return {\n get snapshot() {\n return snapshotSignal;\n },\n\n send(message: Message): void {\n const state = getState();\n if (state === 'destroyed') return;\n const stateDef = def.states[state as UserState];\n const handler = stateDef?.on?.[message.type as keyof typeof stateDef.on] as\n | ((msg: Message, ctx: HandlerContext<UserState, Context, RunnerFactory>) => void)\n | undefined;\n if (!handler) return;\n handler(message, {\n context: getContext(),\n getContext,\n transition: (to: UserState) => transition(to as FullState),\n setContext,\n ...(runner ? { runner } : {}),\n } as HandlerContext<UserState, Context, RunnerFactory>);\n // Register onSettled after the handler so we read the post-transition state.\n const newState = getState();\n if (newState !== 'destroyed') {\n const newStateDef = def.states[newState as UserState];\n if (newStateDef?.onSettled && runner) {\n const targetState = newStateDef.onSettled as FullState;\n runner.whenSettled(() => {\n if (getState() !== newState) return;\n transition(targetState);\n });\n }\n }\n },\n\n destroy(): void {\n if (getState() === 'destroyed') return;\n runner?.destroy();\n transition('destroyed');\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2JA,SAAgB,mBAMd,KACyD;CAGzD,MAAM,SAAS,IAAI,UAAU;CAC7B,MAAM,EAAE,gBAAgB,UAAU,eAAe,kBAAgE;EAC/G,OAAO,IAAI;EACX,SAAS,IAAI;EACd,CAAC;CAEF,MAAM,mBAA4B,cAAc,eAAe,KAAK,CAAC,QAAQ;CAE7E,MAAM,cAAc,YAA2B;AAC7C,SAAO,gBAAgB,EAAE,SAAS,CAAC;;AAGrC,QAAO;EACL,IAAI,WAAW;AACb,UAAO;;EAGT,KAAK,SAAwB;GAC3B,MAAM,QAAQ,UAAU;AACxB,OAAI,UAAU,YAAa;GAE3B,MAAM,UADW,IAAI,OAAO,QACF,KAAK,QAAQ;AAGvC,OAAI,CAAC,QAAS;AACd,WAAQ,SAAS;IACf,SAAS,YAAY;IACrB;IACA,aAAa,OAAkB,WAAW,GAAgB;IAC1D;IACA,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;IAC7B,CAAsD;GAEvD,MAAM,WAAW,UAAU;AAC3B,OAAI,aAAa,aAAa;IAC5B,MAAM,cAAc,IAAI,OAAO;AAC/B,QAAI,aAAa,aAAa,QAAQ;KACpC,MAAM,cAAc,YAAY;AAChC,YAAO,kBAAkB;AACvB,UAAI,UAAU,KAAK,SAAU;AAC7B,iBAAW,YAAY;OACvB;;;;EAKR,UAAgB;AACd,OAAI,UAAU,KAAK,YAAa;AAChC,WAAQ,SAAS;AACjB,cAAW,YAAY;;EAE1B"}
@@ -1,6 +1,6 @@
1
- import { untrack, update } from "./signals/primitives.js";
2
- import { createMachineCore } from "./machine.js";
3
- //#region src/core/create-transition-actor.ts
1
+ import { untrack, update } from "../signals/primitives.js";
2
+ import { createMachineCore } from "../machine.js";
3
+ //#region src/core/actors/create-transition-actor.ts
4
4
  /**
5
5
  * Creates a reducer-shaped actor from an initial context and a reducer function.
6
6
  *
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-transition-actor.js","names":[],"sources":["../../../../src/core/actors/create-transition-actor.ts"],"sourcesContent":["import type { Machine } from '../machine';\nimport { createMachineCore } from '../machine';\nimport { untrack, update } from '../signals/primitives';\nimport type { ActorSnapshot } from './actor';\n\n// =============================================================================\n// Definition types\n// =============================================================================\n\n/**\n * A reducer-shaped actor: `(context, message) => context`.\n *\n * No finite states — the snapshot carries `value: 'active' | 'destroyed'`\n * as a universal lifecycle marker rather than domain state. The interesting\n * state is entirely in the context, which is reactive via `snapshot`.\n *\n * Use this when the actor has context that needs to be reactive but no\n * meaningful state machine (e.g., a message-driven model with DOM side\n * effects). For actors that need per-state behavior, use `createMachineActor`.\n */\nexport interface TransitionActor<Context extends object, Message extends { type: string }>\n extends Machine<ActorSnapshot<'active' | 'destroyed', Context>> {\n send(message: Message): void;\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/**\n * Creates a reducer-shaped actor from an initial context and a reducer function.\n *\n * The reducer receives the current context and a message and returns the next\n * context. Returning the same reference (by identity) skips the signal update —\n * so early-returning `context` unchanged is both the no-op and the optimization.\n *\n * Side effects (e.g. DOM mutations) may be performed inside the reducer.\n * They run synchronously before the signal is updated.\n *\n * @example\n * const actor = createTransitionActor(\n * { count: 0 },\n * (context, message: { type: 'increment' }) => ({ count: context.count + 1 })\n * );\n */\nexport function createTransitionActor<Context extends object, Message extends { type: string }>(\n initialContext: Context,\n reducer: (context: Context, message: Message) => Context\n): TransitionActor<Context, Message> {\n const { snapshotSignal, getState, transition } = createMachineCore<\n 'active' | 'destroyed',\n ActorSnapshot<'active' | 'destroyed', Context>\n >({ value: 'active', context: initialContext });\n\n const getContext = (): Context => untrack(() => snapshotSignal.get().context);\n const setContext = (context: Context): void => update(snapshotSignal, { context });\n\n return {\n get snapshot() {\n return snapshotSignal;\n },\n\n send(message: Message): void {\n if (getState() === 'destroyed') return;\n const context = getContext();\n const newContext = reducer(context, message);\n if (newContext !== context) setContext(newContext);\n },\n\n destroy(): void {\n if (getState() === 'destroyed') return;\n transition('destroyed');\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6CA,SAAgB,sBACd,gBACA,SACmC;CACnC,MAAM,EAAE,gBAAgB,UAAU,eAAe,kBAG/C;EAAE,OAAO;EAAU,SAAS;EAAgB,CAAC;CAE/C,MAAM,mBAA4B,cAAc,eAAe,KAAK,CAAC,QAAQ;CAC7E,MAAM,cAAc,YAA2B,OAAO,gBAAgB,EAAE,SAAS,CAAC;AAElF,QAAO;EACL,IAAI,WAAW;AACb,UAAO;;EAGT,KAAK,SAAwB;AAC3B,OAAI,UAAU,KAAK,YAAa;GAChC,MAAM,UAAU,YAAY;GAC5B,MAAM,aAAa,QAAQ,SAAS,QAAQ;AAC5C,OAAI,eAAe,QAAS,YAAW,WAAW;;EAGpD,UAAgB;AACd,OAAI,UAAU,KAAK,YAAa;AAChC,cAAW,YAAY;;EAE1B"}
@@ -0,0 +1,85 @@
1
+ import { signal } from "../signals/primitives.js";
2
+ //#region src/core/composition/create-composition.ts
3
+ /**
4
+ * Create a composition from a set of behaviors.
5
+ *
6
+ * Composition unions the behaviors' declared `stateKeys` / `contextKeys`
7
+ * to know which signals to create. Each signal is seeded from
8
+ * `initialState` / `initialContext` when supplied, defaulting to
9
+ * `undefined`. Behaviors are responsible for writing their own slots
10
+ * once their preconditions are met.
11
+ *
12
+ * Cross-behavior type conflicts (e.g. two behaviors disagreeing on a
13
+ * field's type) surface as a compose-time type error via
14
+ * `ValidateComposition`.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const composition = createComposition([resolvePresentation, switchVideoQuality], {
19
+ * config: { parsePresentation: parseMultivariantPlaylist, initialBandwidth: 2_000_000 },
20
+ * initialState: { bandwidthState: { fastEstimate: 0, ... } },
21
+ * });
22
+ * ```
23
+ */
24
+ /**
25
+ * Create a typed signal map for a given set of keys, seeded from an
26
+ * optional partial initial value.
27
+ *
28
+ * Pipeline: `Set` dedupes the iterable (insertion order preserved, so
29
+ * first occurrence wins) → `Object.fromEntries` materializes one
30
+ * `signal()` per unique key, seeded from `initial[key]` or `undefined`.
31
+ *
32
+ * Per-key value types live in TypeScript only — at runtime every signal
33
+ * is `Signal<unknown>`. The boundary cast at the return narrows the wide
34
+ * `Record<PropertyKey, Signal<unknown>>` shape to the caller's expected
35
+ * per-key types from `S`.
36
+ *
37
+ * Used by `createComposition` to derive engine state/context maps from
38
+ * the union of behaviors' declared `stateKeys` / `contextKeys`.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * interface State { count?: number; label?: string }
43
+ * const state = buildSignalMap<State>(['count', 'label'], { count: 5 });
44
+ * state.count.get(); // 5
45
+ * state.label.get(); // undefined
46
+ * ```
47
+ */
48
+ function buildSignalMap(keys, initial) {
49
+ const init = initial;
50
+ const uniqueKeys = new Set(keys);
51
+ return Object.fromEntries([...uniqueKeys].map((key) => [key, signal(init[key])]));
52
+ }
53
+ function createComposition(behaviors, options) {
54
+ const validBehaviors = behaviors;
55
+ const state = buildSignalMap(validBehaviors.flatMap((b) => b.stateKeys), options?.initialState ?? {});
56
+ const context = buildSignalMap(validBehaviors.flatMap((b) => b.contextKeys), options?.initialContext ?? {});
57
+ const deps = {
58
+ state,
59
+ context,
60
+ config: options?.config ?? {}
61
+ };
62
+ const cleanups = validBehaviors.map((behavior) => behavior.setup(deps));
63
+ return {
64
+ state,
65
+ context,
66
+ async destroy() {
67
+ const results = [];
68
+ for (const cleanup of cleanups) {
69
+ if (cleanup == null) continue;
70
+ if (typeof cleanup === "function") results.push(cleanup());
71
+ else if ("destroy" in cleanup) results.push(cleanup.destroy());
72
+ }
73
+ await Promise.all(results);
74
+ for (const sig of Object.values(state)) sig.set(void 0);
75
+ for (const sig of Object.values(context)) sig.set(void 0);
76
+ }
77
+ };
78
+ }
79
+ function defineBehavior(behavior) {
80
+ return behavior;
81
+ }
82
+ //#endregion
83
+ export { createComposition, defineBehavior };
84
+
85
+ //# sourceMappingURL=create-composition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-composition.js","names":[],"sources":["../../../../src/core/composition/create-composition.ts"],"sourcesContent":["import { type ReadonlySignal, type Signal, signal } from '../signals/primitives';\n\n/**\n * Cleanup returned by a behavior. Behaviors may return:\n * - `void` / `undefined` — no cleanup needed\n * - A function — called on destroy (may return a Promise)\n * - An object with `destroy()` — called on destroy (may return a Promise)\n */\nexport type BehaviorCleanup = void | (() => void | Promise<void>) | { destroy(): void | Promise<void> };\n\n/**\n * A signal map keyed by the fields of `S`. Each field is a writable signal.\n *\n * Optional fields on `S` map to required signal slots whose value type\n * includes `undefined`, ensuring every key has a signal even when the\n * underlying value is absent.\n *\n * Used in two roles:\n * - Engine-side **construction**: `Composition<S, C>` exposes its public\n * surface as `StateSignals<S>` (everything writable) so external code\n * can read or write any slot.\n * - Behavior **input convenience**: a behavior that writes to every slot\n * can type its setup state param as `StateSignals<{ ... }>` rather than\n * spelling out per-slot `Signal<T>` types.\n *\n * Behaviors that mix read-only and writable slots type the setup param\n * directly as a slot map (`{ x: Signal<T>; y: ReadonlySignal<U> }`)\n * instead of going through `StateSignals<>`.\n */\nexport type StateSignals<S extends object> = { [K in keyof S]-?: Signal<S[K]> };\n\n/**\n * A signal map keyed by the fields of `C`. Each field is a writable signal\n * for a platform object or actor reference. Same dual role as\n * `StateSignals<S>` — see its docblock.\n */\nexport type ContextSignals<C extends object> = { [K in keyof C]-?: Signal<C[K]> };\n\n/**\n * Slot-map shape — a record where each value is at least a `ReadonlySignal`.\n * `Signal<T>` is structurally a subtype of `ReadonlySignal<T>` (it adds\n * `.set()`), so a writable slot satisfies this bound too.\n *\n * This is the bound used for behavior `state` / `context` slot maps. It\n * lets a single behavior declare a *heterogeneous* slot map where some\n * slots are `Signal<T>` (writable) and others are `ReadonlySignal<T>`\n * (read-only) — making read/write intent explicit at the call site and\n * giving body-level enforcement (TS rejects `.set()` on a read-only slot).\n */\nexport type AnySlotMap = Record<PropertyKey, ReadonlySignal<unknown>>;\n\n/**\n * The deps object passed to each behavior by the composition.\n *\n * - `state` — slot map for state fields (reactive data). Per-slot read/\n * write intent expressed via `Signal<T>` vs `ReadonlySignal<T>`.\n * - `context` — slot map for platform objects and actor references.\n * - `config` — static configuration, passed once at composition creation.\n */\nexport interface BehaviorDeps<StateMap extends AnySlotMap, ContextMap extends AnySlotMap, Cfg extends object> {\n state: StateMap;\n context: ContextMap;\n config: Cfg;\n}\n\n/**\n * A behavior announces the state and context keys it needs alongside a\n * `setup` function that receives deps (state, context, config) and\n * returns an optional cleanup handle.\n *\n * The `stateKeys` / `contextKeys` declarations are the runtime expression\n * of the behavior's contract — the caller (e.g. `createComposition`) uses\n * them to know which signals to provide. The setup parameter type\n * declares the *slot map* (per-slot `Signal<T>` vs `ReadonlySignal<T>`);\n * together they form a complete contract.\n *\n * Manual `Behavior<>` literals (e.g. engine wrappers that forward keys\n * from a wrapped behavior, or pass-through behaviors like `shareSignals`)\n * opt out of exhaustiveness — the type alias is permissive (subset).\n * Source behaviors should use `defineBehavior` to get exhaustiveness\n * enforcement at the call site.\n */\nexport interface Behavior<\n StateMap extends AnySlotMap = Empty,\n ContextMap extends AnySlotMap = Empty,\n Cfg extends object = Empty,\n> {\n /** State keys this behavior reads/writes. Subset of `keyof StateMap`. */\n stateKeys: readonly (keyof StateMap)[];\n /** Context keys this behavior reads/writes. Subset of `keyof ContextMap`. */\n contextKeys: readonly (keyof ContextMap)[];\n setup: (deps: BehaviorDeps<StateMap, ContextMap, Cfg>) => BehaviorCleanup;\n}\n\n// =============================================================================\n// Behavior type inference\n// =============================================================================\n\n/** A behavior with an unconstrained setup — used as a generic bound. */\ntype AnyBehavior = {\n stateKeys: readonly PropertyKey[];\n contextKeys: readonly PropertyKey[];\n setup: (deps: any) => BehaviorCleanup;\n};\n\n/** Extract the deps type from a behavior's setup function. */\ntype DepsOf<B> = B extends { setup: (deps: infer D, ...args: any[]) => any } ? D : never;\n\n/**\n * Empty-object fallback used when a behavior omits state, context, or config.\n *\n * Using `{}` rather than `object` is deliberate — `object & {x: T}` collapses\n * to `{x: never}` under TS's union-to-intersection conversion in some inference\n * contexts (likely a TS quirk around the `object` upper bound), whereas\n * `{} & {x: T}` simplifies cleanly to `{x: T}`.\n */\n// biome-ignore lint/complexity/noBannedTypes: see comment above\ntype Empty = {};\n\n/**\n * Unwrap a signal map back to its state/context shape.\n *\n * Inferring through `{ get(): infer V }` rather than `Signal<infer V>`\n * sidesteps `Signal`'s nominal/invariance behaviour — the conditional\n * matches structurally on the read side, and `V` is inferred covariantly.\n */\ntype UnwrapSignals<M> = M extends object ? { [K in keyof M]: M[K] extends { get(): infer V } ? V : never } : Empty;\n\n/** Infer the state shape a behavior requires from its deps parameter. */\nexport type InferBehaviorState<F> = DepsOf<F> extends { state: infer M } ? UnwrapSignals<M> : Empty;\n\n/** Infer the context shape a behavior requires from its deps parameter. */\nexport type InferBehaviorContext<F> = DepsOf<F> extends { context: infer M } ? UnwrapSignals<M> : Empty;\n\n/** Infer the config shape a behavior requires from its deps parameter. */\nexport type InferBehaviorConfig<F> = DepsOf<F> extends { config: infer C extends object } ? C : Empty;\n\n/**\n * Recursively intersect a per-behavior projection across the tuple.\n *\n * Iterating over the tuple directly avoids `UnionToIntersection`'s\n * function-contravariance trick, which produces unstable intersections\n * (collapsing concrete fields to `never` or unrelated types) when one of the\n * union members is the empty `{}` fallback.\n */\ntype IntersectBehaviors<Behaviors extends readonly AnyBehavior[], Project extends object> = Behaviors extends readonly [\n infer First extends AnyBehavior,\n ...infer Rest extends readonly AnyBehavior[],\n]\n ? Apply<Project, First> & IntersectBehaviors<Rest, Project>\n : Empty;\n\n/**\n * Apply a projection (one of the marker types below) to a single behavior.\n * Encoded as a discriminated dispatch so the recursion above can stay generic\n * and we don't have to write three near-identical recursive types.\n */\ntype Apply<Project extends object, F> = Project extends { kind: 'state' }\n ? InferBehaviorState<F>\n : Project extends { kind: 'context' }\n ? InferBehaviorContext<F>\n : Project extends { kind: 'config' }\n ? InferBehaviorConfig<F>\n : never;\n\ntype StateProjection = { kind: 'state' };\ntype ContextProjection = { kind: 'context' };\ntype ConfigProjection = { kind: 'config' };\n\n/** Resolve the combined state shape from an array of behaviors (intersection of all requirements). */\nexport type ResolveBehaviorState<Behaviors extends readonly AnyBehavior[]> =\n IntersectBehaviors<Behaviors, StateProjection> extends infer R extends object ? R : Empty;\n\n/** Resolve the combined context shape from an array of behaviors (intersection of all requirements). */\nexport type ResolveBehaviorContext<Behaviors extends readonly AnyBehavior[]> =\n IntersectBehaviors<Behaviors, ContextProjection> extends infer R extends object ? R : Empty;\n\n/** Resolve the combined config shape from an array of behaviors (intersection of all requirements). */\nexport type ResolveBehaviorConfig<Behaviors extends readonly AnyBehavior[]> =\n IntersectBehaviors<Behaviors, ConfigProjection> extends infer R extends object ? R : Empty;\n\n/**\n * True if any property in `T` collapsed to `undefined` or `never` — indicating\n * a type conflict from intersecting incompatible behavior requirements.\n *\n * - Required conflicts: `{ v: number } & { v: string }` → `{ v: never }` — caught via `[never] extends [undefined]`\n * - Optional conflicts: `{ v?: number } & { v?: string }` → `{ v?: undefined }` — caught directly\n */\ntype HasConflict<T extends object> = true extends {\n [K in keyof T]: [T[K]] extends [undefined] ? true : never;\n}[keyof T]\n ? true\n : false;\n\n// =============================================================================\n// Composition validation\n// =============================================================================\n\n/**\n * Validate that a behavior composition has no type conflicts.\n * Returns the behaviors tuple if valid, or an error message type if conflicts are detected.\n *\n * State, context, and config are all checked the same way — by intersecting\n * each behavior's requirement and looking for collapsed fields. The\n * intersection-based check applies the same rule to context as to state, so\n * two behaviors that disagree on a context field's type (e.g. `Surface` vs\n * `VideoSurface`) surface a conflict at compose time. The prior subtype-based\n * approach for owners is gone — the unified rule is simpler and catches the\n * cases where two behaviors silently agreed on a wider supertype.\n */\ntype ValidateComposition<Behaviors extends readonly AnyBehavior[]> =\n HasConflict<ResolveBehaviorState<Behaviors>> extends true\n ? 'Error: behaviors have conflicting state types'\n : HasConflict<ResolveBehaviorContext<Behaviors>> extends true\n ? 'Error: behaviors have conflicting context types'\n : HasConflict<ResolveBehaviorConfig<Behaviors>> extends true\n ? 'Error: behaviors have conflicting config types'\n : [...Behaviors];\n\n// =============================================================================\n// Composition\n// =============================================================================\n\n/**\n * A composition of behaviors with shared state and context signal maps.\n */\nexport interface Composition<S extends object, C extends object> {\n state: StateSignals<S>;\n context: ContextSignals<C>;\n destroy(): Promise<void>;\n}\n\n/**\n * Options for `createComposition`.\n *\n * Composition derives the state and context signal maps from each\n * behavior's declared `stateKeys` / `contextKeys`; `initialState` and\n * `initialContext` seed those signals at creation time. Any unseeded\n * signal starts as `undefined`.\n */\nexport interface CompositionOptions<S extends object, C extends object, Cfg extends object> {\n /** Static configuration passed to every behavior. */\n config?: Cfg;\n /** Initial values for state signals — any subset of `keyof S`. */\n initialState?: Partial<S>;\n /** Initial values for context signals — any subset of `keyof C`. */\n initialContext?: Partial<C>;\n}\n\n/**\n * Create a composition from a set of behaviors.\n *\n * Composition unions the behaviors' declared `stateKeys` / `contextKeys`\n * to know which signals to create. Each signal is seeded from\n * `initialState` / `initialContext` when supplied, defaulting to\n * `undefined`. Behaviors are responsible for writing their own slots\n * once their preconditions are met.\n *\n * Cross-behavior type conflicts (e.g. two behaviors disagreeing on a\n * field's type) surface as a compose-time type error via\n * `ValidateComposition`.\n *\n * @example\n * ```ts\n * const composition = createComposition([resolvePresentation, switchVideoQuality], {\n * config: { parsePresentation: parseMultivariantPlaylist, initialBandwidth: 2_000_000 },\n * initialState: { bandwidthState: { fastEstimate: 0, ... } },\n * });\n * ```\n */\n/**\n * Create a typed signal map for a given set of keys, seeded from an\n * optional partial initial value.\n *\n * Pipeline: `Set` dedupes the iterable (insertion order preserved, so\n * first occurrence wins) → `Object.fromEntries` materializes one\n * `signal()` per unique key, seeded from `initial[key]` or `undefined`.\n *\n * Per-key value types live in TypeScript only — at runtime every signal\n * is `Signal<unknown>`. The boundary cast at the return narrows the wide\n * `Record<PropertyKey, Signal<unknown>>` shape to the caller's expected\n * per-key types from `S`.\n *\n * Used by `createComposition` to derive engine state/context maps from\n * the union of behaviors' declared `stateKeys` / `contextKeys`.\n *\n * @example\n * ```ts\n * interface State { count?: number; label?: string }\n * const state = buildSignalMap<State>(['count', 'label'], { count: 5 });\n * state.count.get(); // 5\n * state.label.get(); // undefined\n * ```\n */\nexport function buildSignalMap<S extends object>(\n keys: Iterable<PropertyKey>,\n initial: Partial<S>\n): { [K in keyof S]-?: Signal<S[K]> } {\n const init = initial as Record<PropertyKey, unknown>;\n const uniqueKeys = new Set(keys);\n return Object.fromEntries([...uniqueKeys].map((key) => [key, signal(init[key])])) as {\n [K in keyof S]-?: Signal<S[K]>;\n };\n}\n\nexport function createComposition<const Behaviors extends readonly AnyBehavior[]>(\n behaviors: ValidateComposition<Behaviors>,\n options?: CompositionOptions<\n ResolveBehaviorState<Behaviors>,\n ResolveBehaviorContext<Behaviors>,\n ResolveBehaviorConfig<Behaviors>\n >\n): Composition<ResolveBehaviorState<Behaviors>, ResolveBehaviorContext<Behaviors>> {\n type S = ResolveBehaviorState<Behaviors>;\n type C = ResolveBehaviorContext<Behaviors>;\n type Cfg = ResolveBehaviorConfig<Behaviors>;\n\n // ValidateComposition<Behaviors> is `[...Behaviors]` on success, an error\n // string on conflict. The function body only runs when the call typechecks\n // (i.e. the success case), so iterating as the behavior tuple is sound.\n const validBehaviors = behaviors as unknown as readonly AnyBehavior[];\n\n const state = buildSignalMap<S>(\n validBehaviors.flatMap((b) => b.stateKeys),\n options?.initialState ?? {}\n );\n const context = buildSignalMap<C>(\n validBehaviors.flatMap((b) => b.contextKeys),\n options?.initialContext ?? {}\n );\n\n const deps: BehaviorDeps<StateSignals<S>, ContextSignals<C>, Cfg> = {\n state,\n context,\n config: (options?.config ?? {}) as Cfg,\n };\n const cleanups = validBehaviors.map((behavior) => behavior.setup(deps));\n\n return {\n state,\n context,\n async destroy() {\n const results: (void | Promise<void>)[] = [];\n for (const cleanup of cleanups) {\n if (cleanup == null) continue;\n if (typeof cleanup === 'function') {\n results.push(cleanup());\n } else if ('destroy' in cleanup) {\n results.push(cleanup.destroy());\n }\n }\n await Promise.all(results);\n // Reset every signal to undefined as a final cleanup, matching the\n // prior post-destroy `owners.set({})` semantics. A later stage will\n // move per-signal cleanup into the behaviors that own the writes.\n for (const sig of Object.values(state) as Signal<unknown>[]) sig.set(undefined);\n for (const sig of Object.values(context) as Signal<unknown>[]) sig.set(undefined);\n },\n };\n}\n\n// =============================================================================\n// defineBehavior — typed factory with key/param consistency enforcement\n// =============================================================================\n\n/**\n * Compose-time exhaustiveness check.\n *\n * Adds a phantom error tag to the parameter shape when `Keys` does not\n * cover every key in `Slot`. The user's value won't satisfy the phantom\n * field requirement, so TS surfaces the failure at the call site with a\n * descriptive message. When exhaustive, the tag is `Empty` and adds no\n * constraint.\n */\ntype ExhaustiveKeys<Keys extends readonly PropertyKey[], Slot extends object, Name extends string> = [\n keyof Slot,\n] extends [Keys[number]]\n ? Empty\n : { [K in `Error: ${Name}Keys must list every key in the typed slice`]: Exclude<keyof Slot, Keys[number]> };\n\n/**\n * Typed factory for behaviors that enforces single-behavior key/param\n * consistency: declared `stateKeys` must equal `keyof S` (where `S` is\n * inferred from the setup's `state` parameter type), and same for\n * `contextKeys` / `C`.\n *\n * The `const` modifier on `SK` / `CK` captures literal tuples so e.g.\n * `stateKeys: ['preload']` infers as `readonly ['preload']`, no `as\n * const` needed at the call site.\n *\n * Cross-behavior consistency at `createComposition` is unchanged — the\n * existing `IntersectBehaviors` machinery still runs over each\n * behavior's setup param type.\n *\n * @example\n * ```ts\n * export const syncPreload = defineBehavior({\n * stateKeys: ['preload'],\n * contextKeys: ['mediaElement'],\n * setup: ({ state, context }: {\n * state: StateSignals<{ preload?: 'auto' | 'metadata' | 'none' }>;\n * context: ContextSignals<{ mediaElement?: HTMLMediaElement | undefined }>;\n * }) => { ... },\n * });\n * ```\n */\n/**\n * Deps shape for a behavior whose deps slot is empty (no keys). When a\n * slot is empty, the corresponding deps field is optional — callers\n * (typically tests) can omit it, and it defaults to `{}` at runtime via\n * `createComposition`.\n *\n * When a slot has at least one key, the behavior reads `state.foo` /\n * `context.bar` / `config.baz` and we need the field to be required so\n * the access is type-safe.\n */\ntype RequireIfNonEmpty<Key extends string, T extends object> = keyof T extends never\n ? { [K in Key]?: T }\n : { [K in Key]: T };\n\ntype DepsForCfg<StateMap extends AnySlotMap, ContextMap extends AnySlotMap, Cfg extends object> = RequireIfNonEmpty<\n 'state',\n StateMap\n> &\n RequireIfNonEmpty<'context', ContextMap> &\n RequireIfNonEmpty<'config', Cfg>;\n\nexport function defineBehavior<\n StateMap extends AnySlotMap = Empty,\n ContextMap extends AnySlotMap = Empty,\n Cfg extends object = Empty,\n const SK extends readonly (keyof StateMap)[] = readonly [],\n const CK extends readonly (keyof ContextMap)[] = readonly [],\n R extends BehaviorCleanup = BehaviorCleanup,\n>(\n behavior: {\n stateKeys: SK;\n contextKeys: CK;\n setup: (deps: { state: StateMap; context: ContextMap; config: Cfg }) => R;\n } & ExhaustiveKeys<SK, StateMap, 'state'> &\n ExhaustiveKeys<CK, ContextMap, 'context'>\n): {\n stateKeys: SK;\n contextKeys: CK;\n setup: (deps: DepsForCfg<StateMap, ContextMap, Cfg>) => R;\n} {\n // The runtime shape is identical; the cast bridges TS's view of the\n // parameter (config required) to the return view (config optional when\n // Cfg has no keys).\n return behavior as unknown as {\n stateKeys: SK;\n contextKeys: CK;\n setup: (deps: DepsForCfg<StateMap, ContextMap, Cfg>) => R;\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsSA,SAAgB,eACd,MACA,SACoC;CACpC,MAAM,OAAO;CACb,MAAM,aAAa,IAAI,IAAI,KAAK;AAChC,QAAO,OAAO,YAAY,CAAC,GAAG,WAAW,CAAC,KAAK,QAAQ,CAAC,KAAK,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC;;AAKnF,SAAgB,kBACd,WACA,SAKiF;CAQjF,MAAM,iBAAiB;CAEvB,MAAM,QAAQ,eACZ,eAAe,SAAS,MAAM,EAAE,UAAU,EAC1C,SAAS,gBAAgB,EAAE,CAC5B;CACD,MAAM,UAAU,eACd,eAAe,SAAS,MAAM,EAAE,YAAY,EAC5C,SAAS,kBAAkB,EAAE,CAC9B;CAED,MAAM,OAA8D;EAClE;EACA;EACA,QAAS,SAAS,UAAU,EAAE;EAC/B;CACD,MAAM,WAAW,eAAe,KAAK,aAAa,SAAS,MAAM,KAAK,CAAC;AAEvE,QAAO;EACL;EACA;EACA,MAAM,UAAU;GACd,MAAM,UAAoC,EAAE;AAC5C,QAAK,MAAM,WAAW,UAAU;AAC9B,QAAI,WAAW,KAAM;AACrB,QAAI,OAAO,YAAY,WACrB,SAAQ,KAAK,SAAS,CAAC;aACd,aAAa,QACtB,SAAQ,KAAK,QAAQ,SAAS,CAAC;;AAGnC,SAAM,QAAQ,IAAI,QAAQ;AAI1B,QAAK,MAAM,OAAO,OAAO,OAAO,MAAM,CAAuB,KAAI,IAAI,KAAA,EAAU;AAC/E,QAAK,MAAM,OAAO,OAAO,OAAO,QAAQ,CAAuB,KAAI,IAAI,KAAA,EAAU;;EAEpF;;AAqEH,SAAgB,eAQd,UAUA;AAIA,QAAO"}
@@ -0,0 +1,37 @@
1
+ //#region src/core/composition/share-signals.ts
2
+ /**
3
+ * Behavior factory that hands the composition's signal refs to a
4
+ * consumer-supplied callback (`config.onSignalsReady`) at setup time.
5
+ *
6
+ * Generic over `S` and `C` — the caller instantiates with their own
7
+ * state/context types, and the callback's parameter shape is fully
8
+ * type-driven from those. Suitable for both reads and writes (per-slot
9
+ * intent can be expressed by typing captured refs as `Signal<T>` or
10
+ * `ReadonlySignal<T>` at the call site).
11
+ *
12
+ * Declares no keys of its own (`stateKeys: []`, `contextKeys: []`); the
13
+ * composition's state/context maps come from other behaviors' key
14
+ * declarations. This behavior just observes/forwards whatever signals
15
+ * the composition built.
16
+ *
17
+ * Uses a `Behavior<>` literal (not `defineBehavior`) so the empty key
18
+ * arrays don't trip the exhaustiveness check — the setup-param state/
19
+ * context shapes here describe what the consumer's callback receives,
20
+ * not keys this behavior needs created.
21
+ */
22
+ function makeShareSignals() {
23
+ return {
24
+ stateKeys: [],
25
+ contextKeys: [],
26
+ setup: ({ state, context, config }) => {
27
+ config.onSignalsReady?.({
28
+ state,
29
+ context
30
+ });
31
+ }
32
+ };
33
+ }
34
+ //#endregion
35
+ export { makeShareSignals };
36
+
37
+ //# sourceMappingURL=share-signals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share-signals.js","names":[],"sources":["../../../../src/core/composition/share-signals.ts"],"sourcesContent":["import type { Behavior, ContextSignals, StateSignals } from './create-composition';\n\n/**\n * Config consumed by the `shareSignals` behavior.\n *\n * The callback fires once during composition setup with the composition's\n * state and context signal refs. Capture them to drive the composition\n * externally (writes) or observe its state (reads).\n *\n * The callback runs while other behaviors are still in their setup phase —\n * for the typical \"capture refs, use later\" pattern this is fine (signal\n * refs are stable identities), but reading inside the callback may yield\n * only initial-seed values rather than what later behaviors write.\n */\nexport interface ShareSignalsConfig<S extends object, C extends object> {\n onSignalsReady?: (signals: { state: StateSignals<S>; context: ContextSignals<C> }) => void;\n}\n\n/**\n * Behavior factory that hands the composition's signal refs to a\n * consumer-supplied callback (`config.onSignalsReady`) at setup time.\n *\n * Generic over `S` and `C` — the caller instantiates with their own\n * state/context types, and the callback's parameter shape is fully\n * type-driven from those. Suitable for both reads and writes (per-slot\n * intent can be expressed by typing captured refs as `Signal<T>` or\n * `ReadonlySignal<T>` at the call site).\n *\n * Declares no keys of its own (`stateKeys: []`, `contextKeys: []`); the\n * composition's state/context maps come from other behaviors' key\n * declarations. This behavior just observes/forwards whatever signals\n * the composition built.\n *\n * Uses a `Behavior<>` literal (not `defineBehavior`) so the empty key\n * arrays don't trip the exhaustiveness check — the setup-param state/\n * context shapes here describe what the consumer's callback receives,\n * not keys this behavior needs created.\n */\nexport function makeShareSignals<S extends object, C extends object>(): Behavior<\n StateSignals<S>,\n ContextSignals<C>,\n ShareSignalsConfig<S, C>\n> {\n return {\n stateKeys: [],\n contextKeys: [],\n setup: ({ state, context, config }) => {\n config.onSignalsReady?.({ state, context });\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,mBAId;AACA,QAAO;EACL,WAAW,EAAE;EACb,aAAa,EAAE;EACf,QAAQ,EAAE,OAAO,SAAS,aAAa;AACrC,UAAO,iBAAiB;IAAE;IAAO;IAAS,CAAC;;EAE9C"}
@@ -1,7 +1,7 @@
1
- import { effect } from "./signals/effect.js";
2
- import { untrack } from "./signals/primitives.js";
3
- import { createMachineCore } from "./machine.js";
4
- //#region src/core/create-machine-reactor.ts
1
+ import { untrack } from "../signals/primitives.js";
2
+ import { effect } from "../signals/effect.js";
3
+ import { createMachineCore } from "../machine.js";
4
+ //#region src/core/reactors/create-machine-reactor.ts
5
5
  const toArray = (x) => x === void 0 ? [] : Array.isArray(x) ? x : [x];
6
6
  /**
7
7
  * Creates a reactive Reactor from a declarative definition.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-machine-reactor.js","names":[],"sources":["../../../../src/core/reactors/create-machine-reactor.ts"],"sourcesContent":["import type { Machine, MachineSnapshot } from '../machine';\nimport { createMachineCore } from '../machine';\nimport { effect } from '../signals/effect';\nimport { untrack } from '../signals/primitives';\n\n// =============================================================================\n// Definition types\n// =============================================================================\n\n/**\n * A reactive state-deriving function used in the `monitor` field.\n *\n * Returns the target state the reactor should be in. Any signals read inside\n * the fn body create reactive dependencies — the framework re-evaluates it when\n * those signals change and automatically calls `transition()` when the returned\n * state differs from the current one.\n */\nexport type ReactorDeriveFn<State extends string> = () => State;\n\n/**\n * An effect function used in reactor `entry` and `effects` blocks.\n *\n * May return a cleanup function that runs before each re-evaluation and on\n * state exit (including destroy).\n */\nexport type ReactorEffectFn = () => (() => void) | { abort(): void } | void;\n\n/**\n * Per-state effect grouping for a single reactor state.\n *\n * - `entry` effects run once on state entry. The fn body is automatically\n * untracked — no `untrack()` calls are needed inside. Use this for\n * one-time setup: reading current values, attaching event listeners, etc.\n * - `effects` run on state entry and re-run whenever a signal read inside\n * the fn body changes. Use `untrack()` for reads you do not want to track.\n * Use this for work that must stay in sync with reactive state.\n *\n * Both are optional; pass `{}` for states with no effects.\n */\nexport type ReactorStateDefinition = {\n entry?: ReactorEffectFn | ReactorEffectFn[];\n effects?: ReactorEffectFn | ReactorEffectFn[];\n};\n\n/**\n * Full reactor definition passed to `createMachineReactor`.\n *\n * `State` is the set of domain-meaningful states. `'destroying'` and\n * `'destroyed'` are always added by the framework as implicit terminal states —\n * do not include them here.\n */\nexport type ReactorDefinition<State extends string> = {\n /** Initial state. */\n initial: State;\n /**\n * Reactive state derivation. Registered before per-state effects — the\n * ordering guarantee ensures transitions fired here take effect before\n * per-state effects re-evaluate in the same flush.\n */\n monitor?: ReactorDeriveFn<State> | ReactorDeriveFn<State>[];\n /**\n * Per-state effect groupings. Every valid state must be declared — pass `{}`\n * for states with no effects. `entry` and `effects` each become independent\n * `effect()` calls gated on that state, with their own cleanup lifecycles.\n */\n states: Record<State, ReactorStateDefinition>;\n};\n\n// =============================================================================\n// Live reactor interface\n// =============================================================================\n\n/** Live reactor instance returned by `createMachineReactor`. */\nexport type Reactor<State extends string> = Machine<MachineSnapshot<State>>;\n\n// =============================================================================\n// Implementation helpers\n// =============================================================================\n\nconst toArray = <T>(x: T | T[] | undefined): T[] => (x === undefined ? [] : Array.isArray(x) ? x : [x]);\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/**\n * Creates a reactive Reactor from a declarative definition.\n *\n * A Reactor is driven by subscriptions to external signals rather than\n * imperative messages. Each state holds an array of effect functions —\n * every element becomes one independent `effect()` call gated on that state,\n * with its own dependency tracking and cleanup lifecycle.\n *\n * `'destroying'` and `'destroyed'` are always implicit terminal states.\n * `destroy()` transitions through both in sequence: `'destroying'` first (for\n * potential async teardown in a future extension), then immediately `'destroyed'`\n * for the synchronous base case. Active effect cleanups fire via disposal.\n *\n * @example\n * const reactor = createMachineReactor({\n * initial: 'waiting',\n * monitor: () => srcSignal.get() ? 'active' : 'waiting',\n * states: {\n * active: {\n * // entry: runs once on state entry; fn body is automatically untracked.\n * entry: () => listen(el, 'play', handler),\n * // effects: re-runs whenever tracked signals change.\n * effects: () => { currentTimeSignal.get(); return cleanup; },\n * },\n * waiting: {},\n * }\n * });\n */\nexport function createMachineReactor<State extends string>(\n def: ReactorDefinition<State>\n): Reactor<State | 'destroying' | 'destroyed'> {\n type FullState = State | 'destroying' | 'destroyed';\n\n const { snapshotSignal, getState, transition } = createMachineCore<FullState, MachineSnapshot<FullState>>({\n value: def.initial as FullState,\n });\n\n const effectDisposals: Array<() => void> = [];\n\n const wrapResult = (result: ReturnType<ReactorEffectFn>) => {\n if (!result) return undefined;\n if (typeof result === 'function') return result;\n return () => result.abort();\n };\n\n type EffectCall = () => ReturnType<ReactorEffectFn>;\n\n type EffectDescriptor = {\n fn: ReactorEffectFn;\n shouldSkip: (snapshot: { value: FullState }) => boolean;\n toFnCall?: (baseCall: EffectCall) => EffectCall;\n };\n\n const untracked: EffectDescriptor['toFnCall'] = (baseCall) => () => untrack(baseCall);\n\n const isTerminal = (snapshot: { value: FullState }) =>\n snapshot.value === 'destroying' || snapshot.value === 'destroyed';\n\n // `monitor` descriptors are built first — the ordering guarantee ensures\n // transitions they trigger take effect before per-state effects re-evaluate\n // in the same flush. See the comment on effect registration order in the\n // previous implementation for full details.\n const descriptors: EffectDescriptor[] = [\n ...toArray(def.monitor).map((fn) => ({\n fn: () => {\n const target = fn();\n if (target !== (getState() as State)) transition(target as FullState);\n },\n shouldSkip: isTerminal,\n })),\n ...(Object.entries(def.states) as Array<[State, ReactorStateDefinition]>).flatMap(([state, stateDef]) => {\n const isNotState = (snapshot: { value: FullState }) => snapshot.value !== state;\n return [\n ...toArray(stateDef.entry).map((fn) => ({ fn, shouldSkip: isNotState, toFnCall: untracked })),\n ...toArray(stateDef.effects).map((fn) => ({ fn, shouldSkip: isNotState })),\n ];\n }),\n ];\n\n const toEffect = ({ fn, shouldSkip, toFnCall = (baseCall) => baseCall }: EffectDescriptor) =>\n effect(() => {\n const snapshot = snapshotSignal.get();\n if (shouldSkip(snapshot)) return;\n const baseCall = () => fn();\n return wrapResult(toFnCall(baseCall)());\n });\n\n effectDisposals.push(...descriptors.map(toEffect));\n\n return {\n get snapshot() {\n return snapshotSignal;\n },\n\n destroy(): void {\n const state = getState();\n if (state === 'destroying' || state === 'destroyed') return;\n // Two-step teardown: transition through 'destroying' first to leave room\n // for async teardown in a future extension, then immediately 'destroyed'\n // for the synchronous base case. Active effect cleanups fire via disposal.\n transition('destroying');\n transition('destroyed');\n for (const dispose of effectDisposals) dispose();\n },\n };\n}\n"],"mappings":";;;;AA+EA,MAAM,WAAc,MAAiC,MAAM,KAAA,IAAY,EAAE,GAAG,MAAM,QAAQ,EAAE,GAAG,IAAI,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCtG,SAAgB,qBACd,KAC6C;CAG7C,MAAM,EAAE,gBAAgB,UAAU,eAAe,kBAAyD,EACxG,OAAO,IAAI,SACZ,CAAC;CAEF,MAAM,kBAAqC,EAAE;CAE7C,MAAM,cAAc,WAAwC;AAC1D,MAAI,CAAC,OAAQ,QAAO,KAAA;AACpB,MAAI,OAAO,WAAW,WAAY,QAAO;AACzC,eAAa,OAAO,OAAO;;CAW7B,MAAM,aAA2C,mBAAmB,QAAQ,SAAS;CAErF,MAAM,cAAc,aAClB,SAAS,UAAU,gBAAgB,SAAS,UAAU;CAMxD,MAAM,cAAkC,CACtC,GAAG,QAAQ,IAAI,QAAQ,CAAC,KAAK,QAAQ;EACnC,UAAU;GACR,MAAM,SAAS,IAAI;AACnB,OAAI,WAAY,UAAU,CAAY,YAAW,OAAoB;;EAEvE,YAAY;EACb,EAAE,EACH,GAAI,OAAO,QAAQ,IAAI,OAAO,CAA4C,SAAS,CAAC,OAAO,cAAc;EACvG,MAAM,cAAc,aAAmC,SAAS,UAAU;AAC1E,SAAO,CACL,GAAG,QAAQ,SAAS,MAAM,CAAC,KAAK,QAAQ;GAAE;GAAI,YAAY;GAAY,UAAU;GAAW,EAAE,EAC7F,GAAG,QAAQ,SAAS,QAAQ,CAAC,KAAK,QAAQ;GAAE;GAAI,YAAY;GAAY,EAAE,CAC3E;GACD,CACH;CAED,MAAM,YAAY,EAAE,IAAI,YAAY,YAAY,aAAa,eAC3D,aAAa;AAEX,MAAI,WADa,eAAe,KAAK,CACb,CAAE;EAC1B,MAAM,iBAAiB,IAAI;AAC3B,SAAO,WAAW,SAAS,SAAS,EAAE,CAAC;GACvC;AAEJ,iBAAgB,KAAK,GAAG,YAAY,IAAI,SAAS,CAAC;AAElD,QAAO;EACL,IAAI,WAAW;AACb,UAAO;;EAGT,UAAgB;GACd,MAAM,QAAQ,UAAU;AACxB,OAAI,UAAU,gBAAgB,UAAU,YAAa;AAIrD,cAAW,aAAa;AACxB,cAAW,YAAY;AACvB,QAAK,MAAM,WAAW,gBAAiB,UAAS;;EAEnD"}
@@ -2,6 +2,23 @@ import { Signal } from "signal-polyfill";
2
2
  //#region src/core/signals/primitives.ts
3
3
  /** Read a signal value without tracking it as a dependency. */
4
4
  const untrack = Signal.subtle.untrack;
5
+ /**
6
+ * Read a signal's current value without tracking it as a dependency. Sugar
7
+ * for `untrack(() => signal.get())` to reduce boilerplate at single-read
8
+ * sites. Structurally typed to accept any signal-like (Signal, Computed,
9
+ * ReadonlySignal).
10
+ *
11
+ * Accepts an optional `transform` to project the value in the same call;
12
+ * the default is the identity function so the single-arg form returns `T`
13
+ * unchanged.
14
+ *
15
+ * @example
16
+ * const value = peek(someSignal);
17
+ * const id = peek(presentationSignal, (p) => p?.id);
18
+ */
19
+ function peek(source, transform = (v) => v) {
20
+ return untrack(() => transform(source.get()));
21
+ }
5
22
  /** Create a writable reactive value. */
6
23
  function signal(initialValue, options) {
7
24
  return new Signal.State(initialValue, options);
@@ -10,23 +27,28 @@ function signal(initialValue, options) {
10
27
  function computed(fn, options) {
11
28
  return new Signal.Computed(fn, options);
12
29
  }
13
- /**
14
- * Update a writable signal. Accepts either a partial object to merge into the
15
- * current state, or an updater function that receives the current state and
16
- * returns the next state.
17
- *
18
- * @example
19
- * update(state, { playbackRate: 2 });
20
- * update(state, (s) => ({ ...s, count: s.count + 1 }));
21
- */
22
30
  function update(signal, updater) {
23
31
  const current = untrack(() => signal.get());
24
- signal.set(typeof updater === "function" ? updater(current) : {
32
+ if (typeof updater === "function") signal.set(updater(current));
33
+ else signal.set({
25
34
  ...current,
26
35
  ...updater
27
36
  });
28
37
  }
38
+ /**
39
+ * Read every signal in a map and return a plain object snapshot. Each read
40
+ * tracks in the surrounding Computed/Effect — equivalent to calling `.get()`
41
+ * on a single `Signal<S>` over the merged shape.
42
+ *
43
+ * Convenience for behaviors that pass whole state/context snapshots to pure
44
+ * helpers; prefer per-field reads when only a few fields are needed.
45
+ */
46
+ function snapshot(map) {
47
+ const out = {};
48
+ for (const key in map) out[key] = map[key].get();
49
+ return out;
50
+ }
29
51
  //#endregion
30
- export { computed, signal, untrack, update };
52
+ export { computed, peek, signal, snapshot, untrack, update };
31
53
 
32
54
  //# sourceMappingURL=primitives.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"primitives.js","names":["SignalNS"],"sources":["../../../../src/core/signals/primitives.ts"],"sourcesContent":["import { Signal as SignalNS } from 'signal-polyfill';\n\n/** Read a signal value without tracking it as a dependency. */\nexport const untrack: <T>(fn: () => T) => T = SignalNS.subtle.untrack;\n\n/** A writable reactive value (read + write). */\nexport type Signal<T> = SignalNS.State<T>;\n\n/** A derived reactive value that re-evaluates when its dependencies change (read-only). */\nexport type Computed<T> = SignalNS.Computed<T>;\n\n/** A read-only view of a reactive value. */\nexport type ReadonlySignal<T> = Omit<SignalNS.State<T>, 'set'>;\n\nexport interface SignalOptions<T> {\n equals?: (t: T, t2: T) => boolean;\n}\n\n/** Create a writable reactive value. */\nexport function signal<T>(initialValue: T, options?: SignalOptions<T>): Signal<T> {\n return new SignalNS.State(initialValue, options as SignalNS.Options<T>);\n}\n\n/** Create a computed reactive value. */\nexport function computed<T>(fn: () => T, options?: SignalOptions<T>): Computed<T> {\n return new SignalNS.Computed(fn, options as SignalNS.Options<T>);\n}\n\n/**\n * Update a writable signal. Accepts either a partial object to merge into the\n * current state, or an updater function that receives the current state and\n * returns the next state.\n *\n * @example\n * update(state, { playbackRate: 2 });\n * update(state, (s) => ({ ...s, count: s.count + 1 }));\n */\nexport function update<T extends object>(signal: Signal<T>, updater: Partial<T> | ((current: T) => T)): void {\n const current = untrack(() => signal.get());\n signal.set(typeof updater === 'function' ? updater(current) : { ...current, ...updater });\n}\n"],"mappings":";;;AAGA,MAAa,UAAiCA,OAAS,OAAO;;AAgB9D,SAAgB,OAAU,cAAiB,SAAuC;AAChF,QAAO,IAAIA,OAAS,MAAM,cAAc,QAA+B;;;AAIzE,SAAgB,SAAY,IAAa,SAAyC;AAChF,QAAO,IAAIA,OAAS,SAAS,IAAI,QAA+B;;;;;;;;;;;AAYlE,SAAgB,OAAyB,QAAmB,SAAiD;CAC3G,MAAM,UAAU,cAAc,OAAO,KAAK,CAAC;AAC3C,QAAO,IAAI,OAAO,YAAY,aAAa,QAAQ,QAAQ,GAAG;EAAE,GAAG;EAAS,GAAG;EAAS,CAAC"}
1
+ {"version":3,"file":"primitives.js","names":["SignalNS"],"sources":["../../../../src/core/signals/primitives.ts"],"sourcesContent":["import { Signal as SignalNS } from 'signal-polyfill';\n\n/** Read a signal value without tracking it as a dependency. */\nexport const untrack: <T>(fn: () => T) => T = SignalNS.subtle.untrack;\n\n/**\n * Read a signal's current value without tracking it as a dependency. Sugar\n * for `untrack(() => signal.get())` to reduce boilerplate at single-read\n * sites. Structurally typed to accept any signal-like (Signal, Computed,\n * ReadonlySignal).\n *\n * Accepts an optional `transform` to project the value in the same call;\n * the default is the identity function so the single-arg form returns `T`\n * unchanged.\n *\n * @example\n * const value = peek(someSignal);\n * const id = peek(presentationSignal, (p) => p?.id);\n */\nexport function peek<T, R = T>(source: { get(): T }, transform: (value: T) => R = (v: T) => v as unknown as R): R {\n return untrack(() => transform(source.get()));\n}\n\n/** A writable reactive value (read + write). */\nexport type Signal<T> = SignalNS.State<T>;\n\n/** A derived reactive value that re-evaluates when its dependencies change (read-only). */\nexport type Computed<T> = SignalNS.Computed<T>;\n\n/** A read-only view of a reactive value. */\nexport type ReadonlySignal<T> = Omit<SignalNS.State<T>, 'set'>;\n\nexport interface SignalOptions<T> {\n equals?: (t: T, t2: T) => boolean;\n}\n\n/** Create a writable reactive value. */\nexport function signal<T>(initialValue: T, options?: SignalOptions<T>): Signal<T> {\n return new SignalNS.State(initialValue, options as SignalNS.Options<T>);\n}\n\n/** Create a computed reactive value. */\nexport function computed<T>(fn: () => T, options?: SignalOptions<T>): Computed<T> {\n return new SignalNS.Computed(fn, options as SignalNS.Options<T>);\n}\n\n/**\n * Update a writable signal. Two forms:\n *\n * - **Updater function** `(current) => next`. Works for any signal type,\n * including `Signal<T | undefined>` handle undefined in the updater.\n * - **Partial object** to merge into the current state. Requires\n * `T extends object`.\n *\n * @example\n * update(state, { playbackRate: 2 });\n * update(state, (s) => ({ ...s, count: s.count + 1 }));\n * update(maybeUndefinedSignal, (current) => current ?? defaultValue);\n */\nexport function update<T>(signal: Signal<T>, updater: (current: T) => T): void;\nexport function update<T extends object>(signal: Signal<T>, updater: Partial<T>): void;\nexport function update<T>(signal: Signal<T>, updater: ((current: T) => T) | object): void {\n const current = untrack(() => signal.get());\n if (typeof updater === 'function') {\n signal.set((updater as (current: T) => T)(current));\n } else {\n // Partial<T> form — `T extends object` enforced by the public overload.\n signal.set({ ...(current as object), ...updater } as T);\n }\n}\n\n/**\n * Equality comparator for objects with an optional `id` field. Designed for\n * use as a `computed` `equals` option when reacting to identity changes\n * (Ham-shaped objects, JSON-API-shaped resources) while filtering internal\n * updates that preserve the id.\n *\n * Handles undefined inputs symmetrically: both undefined → equal; one\n * undefined → different.\n *\n * @example\n * const presentationById = computed(() => state.presentation.get(), {\n * equals: equalsById,\n * });\n */\nexport function equalsById<T extends { id?: string }>(a: T | undefined, b: T | undefined): boolean {\n return a?.id === b?.id;\n}\n\n/**\n * Read every signal in a map and return a plain object snapshot. Each read\n * tracks in the surrounding Computed/Effect — equivalent to calling `.get()`\n * on a single `Signal<S>` over the merged shape.\n *\n * Convenience for behaviors that pass whole state/context snapshots to pure\n * helpers; prefer per-field reads when only a few fields are needed.\n */\nexport function snapshot<M extends Record<string, ReadonlySignal<unknown>>>(\n map: M\n): { [K in keyof M]: M[K] extends { get(): infer V } ? V : never } {\n const out = {} as { [K in keyof M]: M[K] extends { get(): infer V } ? V : never };\n for (const key in map) {\n out[key] = map[key]!.get() as never;\n }\n return out;\n}\n"],"mappings":";;;AAGA,MAAa,UAAiCA,OAAS,OAAO;;;;;;;;;;;;;;;AAgB9D,SAAgB,KAAe,QAAsB,aAA8B,MAAS,GAAsB;AAChH,QAAO,cAAc,UAAU,OAAO,KAAK,CAAC,CAAC;;;AAiB/C,SAAgB,OAAU,cAAiB,SAAuC;AAChF,QAAO,IAAIA,OAAS,MAAM,cAAc,QAA+B;;;AAIzE,SAAgB,SAAY,IAAa,SAAyC;AAChF,QAAO,IAAIA,OAAS,SAAS,IAAI,QAA+B;;AAkBlE,SAAgB,OAAU,QAAmB,SAA6C;CACxF,MAAM,UAAU,cAAc,OAAO,KAAK,CAAC;AAC3C,KAAI,OAAO,YAAY,WACrB,QAAO,IAAK,QAA8B,QAAQ,CAAC;KAGnD,QAAO,IAAI;EAAE,GAAI;EAAoB,GAAG;EAAS,CAAM;;;;;;;;;;AA8B3D,SAAgB,SACd,KACiE;CACjE,MAAM,MAAM,EAAE;AACd,MAAK,MAAM,OAAO,IAChB,KAAI,OAAO,IAAI,KAAM,KAAK;AAE5B,QAAO"}
@@ -1,6 +1,6 @@
1
- import { generateId } from "./utils/generate-id.js";
2
1
  import { anyAbortSignal } from "@videojs/utils/events";
3
- //#region src/core/task.ts
2
+ import { generateId } from "@videojs/utils/string";
3
+ //#region src/core/tasks/task.ts
4
4
  /**
5
5
  * Generic reusable task that wraps an async run function.
6
6
  *
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task.js","names":["#runFn","#abortController","#signal","#status","#value","#error","#pending","#destroyed","#settled","#resolveSettled","#chain","#current"],"sources":["../../../../src/core/tasks/task.ts"],"sourcesContent":["import { anyAbortSignal } from '@videojs/utils/events';\nimport { generateId } from '@videojs/utils/string';\n\n// =============================================================================\n// DeepReadonly\n// =============================================================================\n\n/** Recursively marks all properties as readonly. */\nexport type DeepReadonly<T> = T extends (infer U)[]\n ? ReadonlyArray<DeepReadonly<U>>\n : T extends object\n ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n : T;\n\n// =============================================================================\n// Task\n// =============================================================================\n\nexport type TaskStatus = 'pending' | 'running' | 'done' | 'error';\n\n/**\n * Configuration for a Task.\n */\nexport interface TaskConfig {\n /**\n * Identifier for this task.\n * - string: used as-is\n * - () => string: called once at construction time\n * - undefined: a unique ID is generated via generateId()\n */\n id?: string | (() => string);\n\n /**\n * Optional external AbortSignal to compose with the task's internal one.\n * The task's work is aborted when either the internal controller (via abort())\n * or this external signal fires — whichever comes first.\n */\n signal?: AbortSignal;\n}\n\n/**\n * Minimal contract for a schedulable unit of async work.\n */\nexport interface TaskLike<TValue = void, TError = unknown> {\n readonly id: string;\n readonly status: TaskStatus;\n readonly value: DeepReadonly<TValue> | undefined;\n readonly error: DeepReadonly<TError> | undefined;\n run(): Promise<TValue>;\n abort(): void;\n}\n\n/**\n * Generic reusable task that wraps an async run function.\n *\n * Owns its own AbortController so it can always be aborted independently.\n * Optionally composes an external AbortSignal so that a parent's cancellation\n * propagates into the task's work without requiring the caller to track the\n * task separately.\n *\n * Ordering guarantee: `value` is written before `status` transitions to `'done'`;\n * `error` is written before `status` transitions to `'error'`. Any reader\n * observing `status === 'done'` is guaranteed `value` is already present.\n */\nexport class Task<TValue = void, TError = unknown> implements TaskLike<TValue, TError> {\n readonly id: string;\n readonly #runFn: (signal: AbortSignal) => Promise<TValue>;\n readonly #abortController = new AbortController();\n readonly #signal: AbortSignal;\n\n #status: TaskStatus = 'pending';\n #value: TValue | undefined = undefined;\n #error: TError | undefined = undefined;\n\n constructor(runFn: (signal: AbortSignal) => Promise<TValue>, config?: TaskConfig) {\n this.#runFn = runFn;\n const rawId = config?.id;\n this.id = typeof rawId === 'function' ? rawId() : (rawId ?? generateId());\n this.#signal = config?.signal\n ? anyAbortSignal([this.#abortController.signal, config.signal])\n : this.#abortController.signal;\n }\n\n get status(): TaskStatus {\n return this.#status;\n }\n\n get value(): DeepReadonly<TValue> | undefined {\n return this.#value as DeepReadonly<TValue> | undefined;\n }\n\n get error(): DeepReadonly<TError> | undefined {\n return this.#error as DeepReadonly<TError> | undefined;\n }\n\n async run(): Promise<TValue> {\n this.#status = 'running';\n try {\n const result = await this.#runFn(this.#signal);\n this.#value = result; // value before status — ordering guarantee\n this.#status = 'done';\n return result;\n } catch (e) {\n this.#error = e as TError; // error before status — ordering guarantee\n this.#status = 'error';\n throw e;\n }\n }\n\n abort(): void {\n this.#abortController.abort();\n }\n}\n\n// =============================================================================\n// ConcurrentRunner\n// =============================================================================\n\n/**\n * Runs tasks concurrently, deduplicated by task id.\n *\n * If a task with a given id is already in flight, subsequent schedule() calls\n * for that id are silently ignored until the first completes. Tasks are stored\n * so abortAll() can cancel any in-flight work (e.g. on engine cleanup).\n */\nexport class ConcurrentRunner {\n readonly #pending = new Map<string, { task: TaskLike<unknown, unknown>; promise: Promise<unknown> }>();\n #settled: Promise<void> = Promise.resolve();\n #resolveSettled: (() => void) | null = null;\n #destroyed = false;\n\n schedule<TValue = void, TError = unknown>(task: TaskLike<TValue, TError>): Promise<TValue> {\n if (this.#destroyed) return Promise.resolve() as Promise<TValue>;\n const existing = this.#pending.get(task.id);\n if (existing) return existing.promise as Promise<TValue>;\n\n if (this.#pending.size === 0) {\n this.#settled = new Promise((resolve) => {\n this.#resolveSettled = resolve;\n });\n }\n\n const promise = task.run();\n // Suppress unhandled rejection for callers that ignore the return value.\n promise.catch(() => {});\n // Cleanup: update pending and resolve settled regardless of outcome.\n const cleanup = () => {\n this.#pending.delete(task.id);\n if (this.#pending.size === 0) {\n this.#resolveSettled?.();\n this.#resolveSettled = null;\n }\n };\n promise.then(cleanup, cleanup);\n\n this.#pending.set(task.id, { task: task as TaskLike<unknown, unknown>, promise: promise as Promise<unknown> });\n return promise;\n }\n\n /**\n * Registers a callback to fire when all currently in-flight tasks settle.\n * If the runner is already idle, the callback is never called. If abortAll()\n * is called before the batch settles, the callback is superseded and silently\n * dropped — no stale callbacks, no generation token required by the caller.\n */\n whenSettled(callback: () => void): void {\n if (this.#pending.size === 0) return;\n const captured = this.#settled;\n captured.then(\n () => {\n if (this.#settled !== captured) return;\n callback();\n },\n () => {}\n );\n }\n\n abortAll(): void {\n for (const { task } of this.#pending.values()) task.abort();\n this.#pending.clear();\n // Resolve the current settled promise so any .then() handlers are queued,\n // then replace the reference — whenSettled callbacks that captured the old\n // reference will see the identity mismatch and be dropped.\n this.#resolveSettled?.();\n this.#resolveSettled = null;\n this.#settled = Promise.resolve();\n }\n\n destroy(): void {\n this.#destroyed = true;\n this.abortAll();\n }\n}\n\n// =============================================================================\n// SerialRunner\n// =============================================================================\n\n/**\n * Runs tasks one at a time in submission order.\n *\n * Each schedule() call returns a Promise that resolves or rejects with the\n * task's result when it is eventually executed. Tasks wait in queue until the\n * prior task completes.\n *\n * Serialization is achieved by chaining each task's run() onto the tail of a\n * shared promise chain — no explicit queue or drain loop needed.\n *\n * abortAll() aborts all pending (not yet started) tasks and the currently\n * in-flight task. Pending tasks still run briefly but receive an aborted\n * signal and are expected to exit early.\n */\nexport class SerialRunner {\n #chain: Promise<unknown> = Promise.resolve();\n readonly #pending = new Set<TaskLike<unknown, unknown>>();\n #current: TaskLike<unknown, unknown> | null = null;\n #destroyed = false;\n\n schedule<TValue = void, TError = unknown>(task: TaskLike<TValue, TError>): Promise<TValue> {\n if (this.#destroyed) return Promise.resolve() as Promise<TValue>;\n const t = task as TaskLike<unknown, unknown>;\n this.#pending.add(t);\n\n const result = this.#chain\n .then(() => {\n this.#pending.delete(t);\n this.#current = t;\n return task.run();\n })\n .finally(() => {\n this.#current = null;\n });\n\n // Advance the chain regardless of whether this task succeeds or fails.\n this.#chain = result.then(\n () => {},\n () => {}\n );\n\n return result as Promise<TValue>;\n }\n\n /**\n * A promise that resolves when all currently-scheduled tasks have settled.\n * Use the reference as a generation token: capture it after scheduling a\n * batch, then check identity in the resolution callback to detect whether\n * a subsequent abortAll() + new batch has superseded this one.\n */\n get settled(): Promise<void> {\n return this.#chain as Promise<void>;\n }\n\n /**\n * Registers a callback to fire when all currently-pending tasks settle.\n * If the runner is already idle (no pending or running tasks), the callback\n * is never called. If new tasks are scheduled before the current batch\n * settles, the callback is superseded and silently dropped — no stale\n * callbacks, no generation token required by the caller.\n */\n whenSettled(callback: () => void): void {\n if (this.#pending.size === 0 && this.#current === null) return;\n const currentChain = this.#chain;\n currentChain.then(\n () => {\n if (this.#chain !== currentChain) return;\n callback();\n },\n () => {}\n );\n }\n\n /** Aborts and clears queued tasks without touching the in-flight task. */\n abortPending(): void {\n for (const task of this.#pending) task.abort();\n this.#pending.clear();\n }\n\n abortAll(): void {\n this.abortPending();\n this.#current?.abort();\n }\n\n destroy(): void {\n this.#destroyed = true;\n this.abortAll();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgEA,IAAa,OAAb,MAAuF;CACrF;CACA;CACA,mBAA4B,IAAI,iBAAiB;CACjD;CAEA,UAAsB;CACtB,SAA6B,KAAA;CAC7B,SAA6B,KAAA;CAE7B,YAAY,OAAiD,QAAqB;AAChF,QAAA,QAAc;EACd,MAAM,QAAQ,QAAQ;AACtB,OAAK,KAAK,OAAO,UAAU,aAAa,OAAO,GAAI,SAAS,YAAY;AACxE,QAAA,SAAe,QAAQ,SACnB,eAAe,CAAC,MAAA,gBAAsB,QAAQ,OAAO,OAAO,CAAC,GAC7D,MAAA,gBAAsB;;CAG5B,IAAI,SAAqB;AACvB,SAAO,MAAA;;CAGT,IAAI,QAA0C;AAC5C,SAAO,MAAA;;CAGT,IAAI,QAA0C;AAC5C,SAAO,MAAA;;CAGT,MAAM,MAAuB;AAC3B,QAAA,SAAe;AACf,MAAI;GACF,MAAM,SAAS,MAAM,MAAA,MAAY,MAAA,OAAa;AAC9C,SAAA,QAAc;AACd,SAAA,SAAe;AACf,UAAO;WACA,GAAG;AACV,SAAA,QAAc;AACd,SAAA,SAAe;AACf,SAAM;;;CAIV,QAAc;AACZ,QAAA,gBAAsB,OAAO;;;;;;;;;;AAejC,IAAa,mBAAb,MAA8B;CAC5B,2BAAoB,IAAI,KAA8E;CACtG,WAA0B,QAAQ,SAAS;CAC3C,kBAAuC;CACvC,aAAa;CAEb,SAA0C,MAAiD;AACzF,MAAI,MAAA,UAAiB,QAAO,QAAQ,SAAS;EAC7C,MAAM,WAAW,MAAA,QAAc,IAAI,KAAK,GAAG;AAC3C,MAAI,SAAU,QAAO,SAAS;AAE9B,MAAI,MAAA,QAAc,SAAS,EACzB,OAAA,UAAgB,IAAI,SAAS,YAAY;AACvC,SAAA,iBAAuB;IACvB;EAGJ,MAAM,UAAU,KAAK,KAAK;AAE1B,UAAQ,YAAY,GAAG;EAEvB,MAAM,gBAAgB;AACpB,SAAA,QAAc,OAAO,KAAK,GAAG;AAC7B,OAAI,MAAA,QAAc,SAAS,GAAG;AAC5B,UAAA,kBAAwB;AACxB,UAAA,iBAAuB;;;AAG3B,UAAQ,KAAK,SAAS,QAAQ;AAE9B,QAAA,QAAc,IAAI,KAAK,IAAI;GAAQ;GAA6C;GAA6B,CAAC;AAC9G,SAAO;;;;;;;;CAST,YAAY,UAA4B;AACtC,MAAI,MAAA,QAAc,SAAS,EAAG;EAC9B,MAAM,WAAW,MAAA;AACjB,WAAS,WACD;AACJ,OAAI,MAAA,YAAkB,SAAU;AAChC,aAAU;WAEN,GACP;;CAGH,WAAiB;AACf,OAAK,MAAM,EAAE,UAAU,MAAA,QAAc,QAAQ,CAAE,MAAK,OAAO;AAC3D,QAAA,QAAc,OAAO;AAIrB,QAAA,kBAAwB;AACxB,QAAA,iBAAuB;AACvB,QAAA,UAAgB,QAAQ,SAAS;;CAGnC,UAAgB;AACd,QAAA,YAAkB;AAClB,OAAK,UAAU;;;;;;;;;;;;;;;;;AAsBnB,IAAa,eAAb,MAA0B;CACxB,SAA2B,QAAQ,SAAS;CAC5C,2BAAoB,IAAI,KAAiC;CACzD,WAA8C;CAC9C,aAAa;CAEb,SAA0C,MAAiD;AACzF,MAAI,MAAA,UAAiB,QAAO,QAAQ,SAAS;EAC7C,MAAM,IAAI;AACV,QAAA,QAAc,IAAI,EAAE;EAEpB,MAAM,SAAS,MAAA,MACZ,WAAW;AACV,SAAA,QAAc,OAAO,EAAE;AACvB,SAAA,UAAgB;AAChB,UAAO,KAAK,KAAK;IACjB,CACD,cAAc;AACb,SAAA,UAAgB;IAChB;AAGJ,QAAA,QAAc,OAAO,WACb,UACA,GACP;AAED,SAAO;;;;;;;;CAST,IAAI,UAAyB;AAC3B,SAAO,MAAA;;;;;;;;;CAUT,YAAY,UAA4B;AACtC,MAAI,MAAA,QAAc,SAAS,KAAK,MAAA,YAAkB,KAAM;EACxD,MAAM,eAAe,MAAA;AACrB,eAAa,WACL;AACJ,OAAI,MAAA,UAAgB,aAAc;AAClC,aAAU;WAEN,GACP;;;CAIH,eAAqB;AACnB,OAAK,MAAM,QAAQ,MAAA,QAAe,MAAK,OAAO;AAC9C,QAAA,QAAc,OAAO;;CAGvB,WAAiB;AACf,OAAK,cAAc;AACnB,QAAA,SAAe,OAAO;;CAGxB,UAAgB;AACd,QAAA,YAAkB;AAClB,OAAK,UAAU"}
@@ -1,10 +1,9 @@
1
- import { loadSegments } from "./dom/features/load-segments.js";
2
- import { destroyVttParser, parseVttSegment } from "./dom/text/parse-vtt-segment.js";
3
- import { loadTextTrackCues } from "./dom/features/load-text-track-cues.js";
4
- import { trackCurrentTime } from "./dom/features/track-current-time.js";
5
- import { trackPlaybackInitiated } from "./dom/features/track-playback-initiated.js";
6
- import { trackPlaybackRate } from "./dom/features/track-playback-rate.js";
7
- import { appendSegment } from "./dom/media/append-segment.js";
8
- import { flushBuffer } from "./dom/media/buffer-flusher.js";
9
- import { SpfMedia, SpfMediaMixin, spfMediaDefaultProps } from "./dom/playback-engine/adapter.js";
10
- export { SpfMedia, SpfMediaMixin, appendSegment, destroyVttParser, flushBuffer, loadSegments, loadTextTrackCues, parseVttSegment, spfMediaDefaultProps, trackCurrentTime, trackPlaybackInitiated, trackPlaybackRate };
1
+ import { appendSegment } from "./media/dom/mse/append-segment.js";
2
+ import { flushBuffer } from "./media/dom/mse/buffer-flusher.js";
3
+ import { destroyVttResolver, resolveVttSegment } from "./media/dom/text/resolve-vtt-segment.js";
4
+ import { loadAudioSegments, loadVideoSegments } from "./playback/behaviors/dom/load-segments.js";
5
+ import { setupTextTrackActors } from "./playback/behaviors/dom/setup-text-track-actors.js";
6
+ import { trackCurrentTime } from "./playback/behaviors/dom/track-current-time.js";
7
+ import { trackLoadTriggers } from "./playback/behaviors/dom/track-load-triggers.js";
8
+ import { trackPlaybackRate } from "./playback/behaviors/dom/track-playback-rate.js";
9
+ export { appendSegment, destroyVttResolver, flushBuffer, loadAudioSegments, loadVideoSegments, resolveVttSegment, setupTextTrackActors, trackCurrentTime, trackLoadTriggers, trackPlaybackRate };
@@ -0,0 +1,3 @@
1
+ import { createSimpleHlsEngine } from "./playback/engines/hls/engine.js";
2
+ import { SimpleHlsMediaElement, SimpleHlsMediaMixin, simpleHlsMediaDefaultProps } from "./playback/engines/hls/adapter.js";
3
+ export { SimpleHlsMediaElement, SimpleHlsMediaMixin, createSimpleHlsEngine, simpleHlsMediaDefaultProps };
@@ -1,14 +1,23 @@
1
+ import { computed, signal, snapshot, untrack, update } from "./core/signals/primitives.js";
2
+ import { createComposition, defineBehavior } from "./core/composition/create-composition.js";
3
+ import { makeShareSignals } from "./core/composition/share-signals.js";
4
+ import { effect } from "./core/signals/effect.js";
5
+ import { ConcurrentRunner, SerialRunner, Task } from "./core/tasks/task.js";
6
+ import { createMachineActor } from "./core/actors/create-machine-actor.js";
7
+ import { createTransitionActor } from "./core/actors/create-transition-actor.js";
8
+ import { createMachineReactor } from "./core/reactors/create-machine-reactor.js";
1
9
  //#region src/index.ts
2
10
  /**
3
11
  * Stream Processing Framework (SPF) for Video.js 10
4
12
  *
5
- * Bundle size target: < 20KB (minified + gzipped)
6
- * Primary use case: CMAF HLS playback with Video.js v10
13
+ * The compositional primitives: createComposition, signals, tasks, actors,
14
+ * reactors. Media-domain helpers and the HLS playback engine live behind
15
+ * the `./dom` and `./hls` subpaths.
7
16
  *
8
17
  * @packageDocumentation
9
18
  */
10
19
  const VERSION = "0.1.0";
11
20
  //#endregion
12
- export { VERSION };
21
+ export { ConcurrentRunner, SerialRunner, Task, VERSION, computed, createComposition, createMachineActor, createMachineReactor, createTransitionActor, defineBehavior, effect, makeShareSignals, signal, snapshot, untrack, update };
13
22
 
14
23
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["/**\n * Stream Processing Framework (SPF) for Video.js 10\n *\n * Bundle size target: < 20KB (minified + gzipped)\n * Primary use case: CMAF HLS playback with Video.js v10\n *\n * @packageDocumentation\n */\n\n// Public API exports will be added here as features are implemented\n\nexport const VERSION = '0.1.0';\n"],"mappings":";;;;;;;;;AAWA,MAAa,UAAU"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["/**\n * Stream Processing Framework (SPF) for Video.js 10\n *\n * The compositional primitives: createComposition, signals, tasks, actors,\n * reactors. Media-domain helpers and the HLS playback engine live behind\n * the `./dom` and `./hls` subpaths.\n *\n * @packageDocumentation\n */\n\nexport const VERSION = '0.1.0';\n\n// =============================================================================\n// Composition\n// =============================================================================\n\nexport type {\n Behavior,\n BehaviorCleanup,\n BehaviorDeps,\n Composition,\n CompositionOptions,\n ContextSignals,\n InferBehaviorConfig,\n InferBehaviorContext,\n InferBehaviorState,\n ResolveBehaviorConfig,\n ResolveBehaviorContext,\n ResolveBehaviorState,\n StateSignals,\n} from './core/composition/create-composition';\nexport { createComposition, defineBehavior } from './core/composition/create-composition';\nexport type { ShareSignalsConfig } from './core/composition/share-signals';\nexport { makeShareSignals } from './core/composition/share-signals';\n\n// =============================================================================\n// Signals\n// =============================================================================\n\nexport { effect } from './core/signals/effect';\nexport type { Computed, ReadonlySignal, Signal, SignalOptions } from './core/signals/primitives';\nexport { computed, signal, snapshot, untrack, update } from './core/signals/primitives';\n\n// =============================================================================\n// Tasks\n// =============================================================================\n\nexport type { TaskConfig, TaskLike, TaskStatus } from './core/tasks/task';\nexport { ConcurrentRunner, SerialRunner, Task } from './core/tasks/task';\n\n// =============================================================================\n// Machine (shared by actors and reactors)\n// =============================================================================\n\nexport type { Machine, MachineSnapshot } from './core/machine';\n\n// =============================================================================\n// Actors\n// =============================================================================\n\nexport type { ActorSnapshot, CallbackActor, SignalActor } from './core/actors/actor';\nexport type {\n ActorDefinition,\n ActorStateDefinition,\n HandlerContext,\n MessageActor,\n RunnerLike,\n} from './core/actors/create-machine-actor';\nexport { createMachineActor } from './core/actors/create-machine-actor';\nexport type { TransitionActor } from './core/actors/create-transition-actor';\nexport { createTransitionActor } from './core/actors/create-transition-actor';\n\n// =============================================================================\n// Reactors\n// =============================================================================\n\nexport type {\n Reactor,\n ReactorDefinition,\n ReactorDeriveFn,\n ReactorEffectFn,\n ReactorStateDefinition,\n} from './core/reactors/create-machine-reactor';\nexport { createMachineReactor } from './core/reactors/create-machine-reactor';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAUA,MAAa,UAAU"}
@@ -0,0 +1,83 @@
1
+ //#region src/media/abr/quality-selection.ts
2
+ /**
3
+ * Default quality selection configuration.
4
+ * Values match Shaka Player upgrade threshold (0.85 = 15% headroom).
5
+ */
6
+ const DEFAULT_QUALITY_CONFIG = {
7
+ safetyMargin: .85,
8
+ upgradeMargin: 1.15
9
+ };
10
+ /**
11
+ * Select the track to apply now, given current bandwidth, a current
12
+ * selection (optional), and tuning. Returns:
13
+ *
14
+ * - The bandwidth-fitting optimal when no `currentTrack` is supplied.
15
+ * - The optimal when it's a downgrade vs. `currentTrack` (downgrades
16
+ * apply immediately — no hysteresis).
17
+ * - The optimal when it clears `currentTrack.bandwidth * upgradeMargin`
18
+ * (upgrade clears hysteresis).
19
+ * - `currentTrack` itself when an upgrade doesn't clear the margin
20
+ * (stay put — caller checks identity to no-op).
21
+ *
22
+ * "Optimal" is the highest-bandwidth track where the available bandwidth
23
+ * meets the safety requirement (`currentBandwidth >= track.bandwidth / safetyMargin`).
24
+ * Falls back to the lowest-bandwidth track when nothing fits the safety
25
+ * margin (preserves a definitive pick under under-bandwidth conditions).
26
+ *
27
+ * @example
28
+ * const tracks = [low, mid, high];
29
+ * selectQuality(tracks, 5_000_000, { currentTrack: low });
30
+ * // Returns `high` if 5 Mbps clears safety AND high.bandwidth >= low.bandwidth * upgradeMargin.
31
+ * // Returns `low` (no-op signal) otherwise.
32
+ */
33
+ function selectQuality(tracks, currentBandwidth, opts = {}) {
34
+ if (tracks.length === 0) return;
35
+ const safetyMargin = opts.safetyMargin ?? DEFAULT_QUALITY_CONFIG.safetyMargin;
36
+ const upgradeMargin = opts.upgradeMargin ?? DEFAULT_QUALITY_CONFIG.upgradeMargin;
37
+ const { currentTrack } = opts;
38
+ const sortedTracks = tracks.slice().sort((a, b) => a.bandwidth - b.bandwidth);
39
+ let chosen;
40
+ for (const track of sortedTracks) if (currentBandwidth >= track.bandwidth / safetyMargin) {
41
+ if (!chosen || track.bandwidth > chosen.bandwidth || track.bandwidth === chosen.bandwidth && hasHigherResolution(track, chosen)) chosen = track;
42
+ }
43
+ const optimal = chosen ?? sortedTracks[0];
44
+ if (!optimal) return void 0;
45
+ if (!currentTrack) return optimal;
46
+ if (optimal.bandwidth < currentTrack.bandwidth) return optimal;
47
+ if (optimal.bandwidth >= currentTrack.bandwidth * upgradeMargin) return optimal;
48
+ return currentTrack;
49
+ }
50
+ /**
51
+ * Select the lowest-bandwidth track from the candidate set.
52
+ *
53
+ * Used as a safety-net fallback by callers that need a definitive pick when
54
+ * a primary selection algorithm declines to choose. Pairs naturally with
55
+ * `selectQuality` — when ABR has no good answer (e.g., all candidates
56
+ * exceed available bandwidth and the algorithm doesn't fall back
57
+ * internally), the lowest bitrate is the safest default.
58
+ *
59
+ * @param tracks - Candidate tracks (can be unsorted)
60
+ * @returns The lowest-bandwidth track, or `undefined` if `tracks` is empty
61
+ *
62
+ * @example
63
+ * const fallback = selectLowestQuality(tracks);
64
+ */
65
+ function selectLowestQuality(tracks) {
66
+ if (tracks.length === 0) return void 0;
67
+ return tracks.reduce((min, t) => t.bandwidth < min.bandwidth ? t : min);
68
+ }
69
+ /**
70
+ * Check if track A has higher resolution than track B.
71
+ * Compares by total pixel count (width × height).
72
+ *
73
+ * @param trackA - First track to compare
74
+ * @param trackB - Second track to compare
75
+ * @returns True if trackA has more pixels than trackB
76
+ */
77
+ function hasHigherResolution(trackA, trackB) {
78
+ return (trackA.width ?? 0) * (trackA.height ?? 0) > (trackB.width ?? 0) * (trackB.height ?? 0);
79
+ }
80
+ //#endregion
81
+ export { DEFAULT_QUALITY_CONFIG, selectLowestQuality, selectQuality };
82
+
83
+ //# sourceMappingURL=quality-selection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality-selection.js","names":[],"sources":["../../../../src/media/abr/quality-selection.ts"],"sourcesContent":["/**\n * Quality Selection Algorithm\n *\n * Selects optimal video track based on bandwidth estimate with safety margin.\n * Stateless selection - picks highest quality that fits bandwidth.\n *\n * Key concepts:\n * - **Safety margin** (0.85): Pick track where bandwidth >= track.bandwidth / 0.85\n * - This ensures 15% headroom to avoid buffering\n * - At same bandwidth, prefer higher resolution\n */\n\nimport type { PartiallyResolvedVideoTrack, VideoTrack } from '../types';\n\n/**\n * Quality selection configuration.\n */\nexport interface QualityConfig {\n /**\n * Safety margin (0-1).\n * To select a track, need: currentBandwidth >= track.bandwidth / safetyMargin.\n * Default 0.85 means track must use ≤85% of available bandwidth (15% headroom).\n */\n safetyMargin: number;\n /**\n * Upgrade hysteresis ratio (>= 1). When `currentTrack` is supplied, an\n * upgrade is applied only if `optimal.bandwidth >= currentTrack.bandwidth * upgradeMargin`.\n * Downgrades are always applied. Default 1.15 means optimal must clear\n * the current bandwidth by at least 15% to trigger an upgrade.\n */\n upgradeMargin: number;\n}\n\n/**\n * Default quality selection configuration.\n * Values match Shaka Player upgrade threshold (0.85 = 15% headroom).\n */\nexport const DEFAULT_QUALITY_CONFIG: QualityConfig = {\n safetyMargin: 0.85,\n upgradeMargin: 1.15,\n};\n\n/**\n * Options for `selectQuality`. Spread of `Partial<QualityConfig>` plus a\n * runtime-supplied `currentTrack` for upgrade-vs-downgrade decisions.\n */\nexport interface SelectQualityOpts extends Partial<QualityConfig> {\n /**\n * Track currently selected. When supplied, `selectQuality` returns\n * `currentTrack` (no change) for upgrades that don't clear the\n * `upgradeMargin`. When omitted, no hysteresis is applied — the\n * computed optimal is returned regardless.\n */\n currentTrack?: PartiallyResolvedVideoTrack | VideoTrack;\n}\n\n/**\n * Select the track to apply now, given current bandwidth, a current\n * selection (optional), and tuning. Returns:\n *\n * - The bandwidth-fitting optimal when no `currentTrack` is supplied.\n * - The optimal when it's a downgrade vs. `currentTrack` (downgrades\n * apply immediately — no hysteresis).\n * - The optimal when it clears `currentTrack.bandwidth * upgradeMargin`\n * (upgrade clears hysteresis).\n * - `currentTrack` itself when an upgrade doesn't clear the margin\n * (stay put — caller checks identity to no-op).\n *\n * \"Optimal\" is the highest-bandwidth track where the available bandwidth\n * meets the safety requirement (`currentBandwidth >= track.bandwidth / safetyMargin`).\n * Falls back to the lowest-bandwidth track when nothing fits the safety\n * margin (preserves a definitive pick under under-bandwidth conditions).\n *\n * @example\n * const tracks = [low, mid, high];\n * selectQuality(tracks, 5_000_000, { currentTrack: low });\n * // Returns `high` if 5 Mbps clears safety AND high.bandwidth >= low.bandwidth * upgradeMargin.\n * // Returns `low` (no-op signal) otherwise.\n */\nexport function selectQuality(\n tracks: readonly (PartiallyResolvedVideoTrack | VideoTrack)[],\n currentBandwidth: number,\n opts: SelectQualityOpts = {}\n): PartiallyResolvedVideoTrack | VideoTrack | undefined {\n if (tracks.length === 0) {\n return undefined;\n }\n\n const safetyMargin = opts.safetyMargin ?? DEFAULT_QUALITY_CONFIG.safetyMargin;\n const upgradeMargin = opts.upgradeMargin ?? DEFAULT_QUALITY_CONFIG.upgradeMargin;\n const { currentTrack } = opts;\n\n // Sort tracks by bandwidth (lowest first)\n const sortedTracks = tracks.slice().sort((a, b) => a.bandwidth - b.bandwidth);\n\n // Start with no selection\n let chosen: PartiallyResolvedVideoTrack | VideoTrack | undefined;\n\n for (const track of sortedTracks) {\n // Check if we have enough bandwidth for this track with safety margin\n // Required bandwidth = track.bandwidth / safetyMargin\n const requiredBandwidth = track.bandwidth / safetyMargin;\n\n if (currentBandwidth >= requiredBandwidth) {\n // We can support this track - prefer it if better than current choice\n if (\n !chosen ||\n track.bandwidth > chosen.bandwidth ||\n (track.bandwidth === chosen.bandwidth && hasHigherResolution(track, chosen))\n ) {\n chosen = track;\n }\n }\n }\n\n // If no track fits with safety margin, fall back to lowest quality\n const optimal = chosen ?? sortedTracks[0];\n if (!optimal) return undefined;\n\n // Apply upgrade hysteresis. No currentTrack → no hysteresis, return\n // optimal. Downgrade (optimal.bandwidth < current) → always apply.\n // Upgrade → only when `optimal.bandwidth >= current * upgradeMargin`,\n // else stay put (return currentTrack so caller's id-compare no-ops).\n if (!currentTrack) return optimal;\n if (optimal.bandwidth < currentTrack.bandwidth) return optimal;\n if (optimal.bandwidth >= currentTrack.bandwidth * upgradeMargin) return optimal;\n return currentTrack;\n}\n\n/**\n * Select the lowest-bandwidth track from the candidate set.\n *\n * Used as a safety-net fallback by callers that need a definitive pick when\n * a primary selection algorithm declines to choose. Pairs naturally with\n * `selectQuality` — when ABR has no good answer (e.g., all candidates\n * exceed available bandwidth and the algorithm doesn't fall back\n * internally), the lowest bitrate is the safest default.\n *\n * @param tracks - Candidate tracks (can be unsorted)\n * @returns The lowest-bandwidth track, or `undefined` if `tracks` is empty\n *\n * @example\n * const fallback = selectLowestQuality(tracks);\n */\nexport function selectLowestQuality<T extends { bandwidth: number }>(tracks: readonly T[]): T | undefined {\n if (tracks.length === 0) return undefined;\n return tracks.reduce((min, t) => (t.bandwidth < min.bandwidth ? t : min));\n}\n\n/**\n * Check if track A has higher resolution than track B.\n * Compares by total pixel count (width × height).\n *\n * @param trackA - First track to compare\n * @param trackB - Second track to compare\n * @returns True if trackA has more pixels than trackB\n */\nfunction hasHigherResolution(\n trackA: PartiallyResolvedVideoTrack | VideoTrack,\n trackB: PartiallyResolvedVideoTrack | VideoTrack\n): boolean {\n const pixelsA = (trackA.width ?? 0) * (trackA.height ?? 0);\n const pixelsB = (trackB.width ?? 0) * (trackB.height ?? 0);\n return pixelsA > pixelsB;\n}\n"],"mappings":";;;;;AAqCA,MAAa,yBAAwC;CACnD,cAAc;CACd,eAAe;CAChB;;;;;;;;;;;;;;;;;;;;;;;;AAuCD,SAAgB,cACd,QACA,kBACA,OAA0B,EAAE,EAC0B;AACtD,KAAI,OAAO,WAAW,EACpB;CAGF,MAAM,eAAe,KAAK,gBAAgB,uBAAuB;CACjE,MAAM,gBAAgB,KAAK,iBAAiB,uBAAuB;CACnE,MAAM,EAAE,iBAAiB;CAGzB,MAAM,eAAe,OAAO,OAAO,CAAC,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,UAAU;CAG7E,IAAI;AAEJ,MAAK,MAAM,SAAS,aAKlB,KAAI,oBAFsB,MAAM,YAAY;MAKxC,CAAC,UACD,MAAM,YAAY,OAAO,aACxB,MAAM,cAAc,OAAO,aAAa,oBAAoB,OAAO,OAAO,CAE3E,UAAS;;CAMf,MAAM,UAAU,UAAU,aAAa;AACvC,KAAI,CAAC,QAAS,QAAO,KAAA;AAMrB,KAAI,CAAC,aAAc,QAAO;AAC1B,KAAI,QAAQ,YAAY,aAAa,UAAW,QAAO;AACvD,KAAI,QAAQ,aAAa,aAAa,YAAY,cAAe,QAAO;AACxE,QAAO;;;;;;;;;;;;;;;;;AAkBT,SAAgB,oBAAqD,QAAqC;AACxG,KAAI,OAAO,WAAW,EAAG,QAAO,KAAA;AAChC,QAAO,OAAO,QAAQ,KAAK,MAAO,EAAE,YAAY,IAAI,YAAY,IAAI,IAAK;;;;;;;;;;AAW3E,SAAS,oBACP,QACA,QACS;AAGT,SAFiB,OAAO,SAAS,MAAM,OAAO,UAAU,MACvC,OAAO,SAAS,MAAM,OAAO,UAAU"}
@@ -1,4 +1,4 @@
1
- //#region src/core/buffer/back-buffer.ts
1
+ //#region src/media/buffer/back-buffer.ts
2
2
  /**
3
3
  * Default back buffer configuration.
4
4
  */
@@ -41,6 +41,6 @@ function calculateBackBufferFlushPoint(segments, currentTime, config = DEFAULT_B
41
41
  return segmentsBefore[segmentsToFlush].startTime;
42
42
  }
43
43
  //#endregion
44
- export { calculateBackBufferFlushPoint };
44
+ export { DEFAULT_BACK_BUFFER_CONFIG, calculateBackBufferFlushPoint };
45
45
 
46
46
  //# sourceMappingURL=back-buffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"back-buffer.js","names":[],"sources":["../../../../src/media/buffer/back-buffer.ts"],"sourcesContent":["/**\n * Back Buffer Strategy (Simple)\n *\n * Calculates flush points for back buffer management.\n * V1 uses simple \"keep N segments\" strategy.\n */\n\nimport type { Segment } from '../types';\n\n/**\n * Back buffer configuration.\n */\nexport interface BackBufferConfig {\n /**\n * Number of segments to keep behind current playback position.\n * Default: 2 segments.\n */\n keepSegments: number;\n}\n\n/**\n * Default back buffer configuration.\n */\nexport const DEFAULT_BACK_BUFFER_CONFIG: BackBufferConfig = {\n keepSegments: 2,\n};\n\n/**\n * Calculate back buffer flush point.\n *\n * Determines where to flush old segments from the back buffer.\n * Keeps a fixed number of segments behind the current playback position.\n *\n * Algorithm:\n * 1. Find segments before currentTime\n * 2. Count back N segments (keepSegments)\n * 3. Return startTime of segment N+1 back (flush everything before this)\n *\n * @param segments - Available segments (should be sorted by startTime)\n * @param currentTime - Current playback position in seconds\n * @param config - Optional back buffer configuration\n * @returns Time in seconds to flush up to (flush range: [0, flushEnd))\n *\n * @example\n * const segments = [\n * { startTime: 0, duration: 6, ... },\n * { startTime: 6, duration: 6, ... },\n * { startTime: 12, duration: 6, ... },\n * { startTime: 18, duration: 6, ... },\n * ];\n *\n * // Playing at 18s, keep 2 segments\n * const flushEnd = calculateBackBufferFlushPoint(segments, 18);\n * // Returns 6 (flush [0, 6), keep [6-18))\n */\nexport function calculateBackBufferFlushPoint(\n segments: Segment[],\n currentTime: number,\n config: BackBufferConfig = DEFAULT_BACK_BUFFER_CONFIG\n): number {\n if (segments.length === 0) {\n return 0;\n }\n\n // Find all segments before current time (not including current segment)\n const segmentsBefore = segments.filter((seg) => seg.startTime < currentTime);\n\n // If no segments before current time, nothing to flush\n if (segmentsBefore.length === 0) {\n return 0;\n }\n\n // Calculate how many segments to flush\n // Keep last N segments, flush the rest\n const segmentsToFlush = segmentsBefore.length - config.keepSegments;\n\n // If we don't have enough segments to flush, keep everything\n if (segmentsToFlush <= 0) {\n return 0;\n }\n\n // If we want to flush all segments, return currentTime\n // (flush everything before current playback position)\n if (segmentsToFlush >= segmentsBefore.length) {\n return currentTime;\n }\n\n // Return the startTime of the first segment we want to keep\n // Everything before this will be flushed\n return segmentsBefore[segmentsToFlush]!.startTime;\n}\n"],"mappings":";;;;AAuBA,MAAa,6BAA+C,EAC1D,cAAc,GACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BD,SAAgB,8BACd,UACA,aACA,SAA2B,4BACnB;AACR,KAAI,SAAS,WAAW,EACtB,QAAO;CAIT,MAAM,iBAAiB,SAAS,QAAQ,QAAQ,IAAI,YAAY,YAAY;AAG5E,KAAI,eAAe,WAAW,EAC5B,QAAO;CAKT,MAAM,kBAAkB,eAAe,SAAS,OAAO;AAGvD,KAAI,mBAAmB,EACrB,QAAO;AAKT,KAAI,mBAAmB,eAAe,OACpC,QAAO;AAKT,QAAO,eAAe,iBAAkB"}