@cssints/apg-specs 0.0.0-stage → 0.5.1

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 (260) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -2
  3. package/dist/apg/accordion.bridge.d.mts +35 -0
  4. package/dist/apg/accordion.bridge.mjs +326 -0
  5. package/dist/apg/accordion.model.json +8397 -0
  6. package/dist/apg/alert.bridge.d.mts +22 -0
  7. package/dist/apg/alert.bridge.mjs +86 -0
  8. package/dist/apg/alert.model.json +871 -0
  9. package/dist/apg/alertdialog.bridge.d.mts +21 -0
  10. package/dist/apg/alertdialog.bridge.mjs +221 -0
  11. package/dist/apg/alertdialog.model.json +5232 -0
  12. package/dist/apg/breadcrumb.bridge.d.mts +26 -0
  13. package/dist/apg/breadcrumb.bridge.mjs +120 -0
  14. package/dist/apg/breadcrumb.model.json +574 -0
  15. package/dist/apg/button.bridge.d.mts +35 -0
  16. package/dist/apg/button.bridge.mjs +208 -0
  17. package/dist/apg/button.model.json +5814 -0
  18. package/dist/apg/carousel.bridge.d.mts +44 -0
  19. package/dist/apg/carousel.bridge.mjs +262 -0
  20. package/dist/apg/carousel.model.json +8963 -0
  21. package/dist/apg/checkbox-group.bridge.d.mts +25 -0
  22. package/dist/apg/checkbox-group.bridge.mjs +276 -0
  23. package/dist/apg/checkbox-group.model.json +3863 -0
  24. package/dist/apg/checkbox.bridge.d.mts +34 -0
  25. package/dist/apg/checkbox.bridge.mjs +253 -0
  26. package/dist/apg/checkbox.model.json +3158 -0
  27. package/dist/apg/combobox-autocomplete.bridge.d.mts +14 -0
  28. package/dist/apg/combobox-autocomplete.bridge.mjs +326 -0
  29. package/dist/apg/combobox-autocomplete.model.json +11586 -0
  30. package/dist/apg/combobox-dialog.bridge.d.mts +17 -0
  31. package/dist/apg/combobox-dialog.bridge.mjs +250 -0
  32. package/dist/apg/combobox-dialog.model.json +8981 -0
  33. package/dist/apg/combobox-grid.bridge.d.mts +17 -0
  34. package/dist/apg/combobox-grid.bridge.mjs +227 -0
  35. package/dist/apg/combobox-grid.model.json +8554 -0
  36. package/dist/apg/combobox-select-only.bridge.d.mts +24 -0
  37. package/dist/apg/combobox-select-only.bridge.mjs +304 -0
  38. package/dist/apg/combobox-select-only.model.json +7846 -0
  39. package/dist/apg/combobox-tree.bridge.d.mts +17 -0
  40. package/dist/apg/combobox-tree.bridge.mjs +211 -0
  41. package/dist/apg/combobox-tree.model.json +9345 -0
  42. package/dist/apg/dialog-modal.bridge.d.mts +35 -0
  43. package/dist/apg/dialog-modal.bridge.mjs +186 -0
  44. package/dist/apg/dialog-modal.model.json +7580 -0
  45. package/dist/apg/disclosure.bridge.d.mts +25 -0
  46. package/dist/apg/disclosure.bridge.mjs +202 -0
  47. package/dist/apg/disclosure.model.json +4703 -0
  48. package/dist/apg/feed.bridge.d.mts +31 -0
  49. package/dist/apg/feed.bridge.mjs +246 -0
  50. package/dist/apg/feed.model.json +4523 -0
  51. package/dist/apg/grid-selection.bridge.d.mts +13 -0
  52. package/dist/apg/grid-selection.bridge.mjs +160 -0
  53. package/dist/apg/grid-selection.model.json +6974 -0
  54. package/dist/apg/grid-sort.bridge.d.mts +12 -0
  55. package/dist/apg/grid-sort.bridge.mjs +138 -0
  56. package/dist/apg/grid-sort.model.json +3799 -0
  57. package/dist/apg/grid.bridge.d.mts +54 -0
  58. package/dist/apg/grid.bridge.mjs +441 -0
  59. package/dist/apg/grid.model.json +3802 -0
  60. package/dist/apg/link.bridge.d.mts +24 -0
  61. package/dist/apg/link.bridge.mjs +126 -0
  62. package/dist/apg/link.model.json +694 -0
  63. package/dist/apg/listbox-multi.bridge.d.mts +10 -0
  64. package/dist/apg/listbox-multi.bridge.mjs +261 -0
  65. package/dist/apg/listbox-multi.model.json +10893 -0
  66. package/dist/apg/listbox-rearrangeable.bridge.d.mts +20 -0
  67. package/dist/apg/listbox-rearrangeable.bridge.mjs +237 -0
  68. package/dist/apg/listbox-rearrangeable.model.json +17592 -0
  69. package/dist/apg/listbox-shared.d.mts +58 -0
  70. package/dist/apg/listbox-shared.mjs +171 -0
  71. package/dist/apg/listbox-single.bridge.d.mts +10 -0
  72. package/dist/apg/listbox-single.bridge.mjs +196 -0
  73. package/dist/apg/listbox-single.model.json +8024 -0
  74. package/dist/apg/listbox-typeahead.bridge.d.mts +10 -0
  75. package/dist/apg/listbox-typeahead.bridge.mjs +153 -0
  76. package/dist/apg/listbox-typeahead.model.json +5615 -0
  77. package/dist/apg/menu-button.bridge.d.mts +29 -0
  78. package/dist/apg/menu-button.bridge.mjs +259 -0
  79. package/dist/apg/menu-button.model.json +5706 -0
  80. package/dist/apg/menu-checkable.bridge.d.mts +25 -0
  81. package/dist/apg/menu-checkable.bridge.mjs +226 -0
  82. package/dist/apg/menu-checkable.model.json +4277 -0
  83. package/dist/apg/menu-menubar.bridge.d.mts +27 -0
  84. package/dist/apg/menu-menubar.bridge.mjs +273 -0
  85. package/dist/apg/menu-menubar.model.json +9540 -0
  86. package/dist/apg/meter.bridge.d.mts +28 -0
  87. package/dist/apg/meter.bridge.mjs +159 -0
  88. package/dist/apg/meter.model.json +1185 -0
  89. package/dist/apg/player.bridge.d.mts +24 -0
  90. package/dist/apg/player.bridge.mjs +342 -0
  91. package/dist/apg/player.model.json +11013 -0
  92. package/dist/apg/radio.bridge.d.mts +35 -0
  93. package/dist/apg/radio.bridge.mjs +371 -0
  94. package/dist/apg/radio.model.json +5011 -0
  95. package/dist/apg/slider-multithumb.bridge.d.mts +34 -0
  96. package/dist/apg/slider-multithumb.bridge.mjs +365 -0
  97. package/dist/apg/slider-multithumb.model.json +5774 -0
  98. package/dist/apg/slider.bridge.d.mts +54 -0
  99. package/dist/apg/slider.bridge.mjs +359 -0
  100. package/dist/apg/slider.model.json +4881 -0
  101. package/dist/apg/spinbutton.bridge.d.mts +41 -0
  102. package/dist/apg/spinbutton.bridge.mjs +327 -0
  103. package/dist/apg/spinbutton.model.json +7571 -0
  104. package/dist/apg/switch.bridge.d.mts +30 -0
  105. package/dist/apg/switch.bridge.mjs +243 -0
  106. package/dist/apg/switch.model.json +3159 -0
  107. package/dist/apg/tabs.bridge.d.mts +41 -0
  108. package/dist/apg/tabs.bridge.mjs +294 -0
  109. package/dist/apg/tabs.model.json +7659 -0
  110. package/dist/apg/timeline.bridge.d.mts +36 -0
  111. package/dist/apg/timeline.bridge.mjs +265 -0
  112. package/dist/apg/timeline.model.json +6189 -0
  113. package/dist/apg/toolbar.bridge.d.mts +25 -0
  114. package/dist/apg/toolbar.bridge.mjs +307 -0
  115. package/dist/apg/toolbar.model.json +6616 -0
  116. package/dist/apg/tooltip.bridge.d.mts +36 -0
  117. package/dist/apg/tooltip.bridge.mjs +208 -0
  118. package/dist/apg/tooltip.model.json +8295 -0
  119. package/dist/apg/treegrid.bridge.d.mts +28 -0
  120. package/dist/apg/treegrid.bridge.mjs +311 -0
  121. package/dist/apg/treegrid.model.json +17358 -0
  122. package/dist/apg/treeview.bridge.d.mts +26 -0
  123. package/dist/apg/treeview.bridge.mjs +396 -0
  124. package/dist/apg/treeview.model.json +13745 -0
  125. package/dist/apg/windowsplitter.bridge.d.mts +36 -0
  126. package/dist/apg/windowsplitter.bridge.mjs +242 -0
  127. package/dist/apg/windowsplitter.model.json +6253 -0
  128. package/dist/canvas/camera.model.json +6568 -0
  129. package/dist/canvas/canvas.bridge.d.mts +190 -0
  130. package/dist/canvas/canvas.bridge.mjs +729 -0
  131. package/dist/canvas/pointer.model.json +17095 -0
  132. package/dist/scenario.d.mts +233 -0
  133. package/dist/scenario.mjs +313 -0
  134. package/package.json +57 -4
  135. package/scenarios/apg/README.md +221 -0
  136. package/scenarios/apg/accordion.base-ui.json +1005 -0
  137. package/scenarios/apg/accordion.react-aria.json +929 -0
  138. package/scenarios/apg/alert.base-ui.json +81 -0
  139. package/scenarios/apg/alert.react-aria.json +98 -0
  140. package/scenarios/apg/alertdialog.base-ui.json +331 -0
  141. package/scenarios/apg/alertdialog.react-aria.json +225 -0
  142. package/scenarios/apg/breadcrumb.react-aria.json +312 -0
  143. package/scenarios/apg/button.ariakit.json +136 -0
  144. package/scenarios/apg/button.base-ui.json +747 -0
  145. package/scenarios/apg/button.react-aria.json +443 -0
  146. package/scenarios/apg/carousel.apg.json +909 -0
  147. package/scenarios/apg/checkbox.ariakit.json +299 -0
  148. package/scenarios/apg/checkbox.base-ui.json +1126 -0
  149. package/scenarios/apg/checkbox.react-aria.json +518 -0
  150. package/scenarios/apg/combobox.ariakit.json +945 -0
  151. package/scenarios/apg/combobox.base-ui.json +2444 -0
  152. package/scenarios/apg/combobox.react-aria.json +845 -0
  153. package/scenarios/apg/dialog-modal.ariakit.json +1102 -0
  154. package/scenarios/apg/dialog-modal.base-ui.json +775 -0
  155. package/scenarios/apg/dialog-modal.react-aria.json +638 -0
  156. package/scenarios/apg/disclosure.apg.json +349 -0
  157. package/scenarios/apg/disclosure.ariakit.json +163 -0
  158. package/scenarios/apg/disclosure.base-ui.json +451 -0
  159. package/scenarios/apg/disclosure.react-aria.json +643 -0
  160. package/scenarios/apg/feed.apg.json +442 -0
  161. package/scenarios/apg/grid.ariakit.json +197 -0
  162. package/scenarios/apg/grid.base-ui.json +720 -0
  163. package/scenarios/apg/grid.react-aria.json +969 -0
  164. package/scenarios/apg/landmarks.ariakit.json +93 -0
  165. package/scenarios/apg/landmarks.react-aria.json +330 -0
  166. package/scenarios/apg/link.ariakit.json +83 -0
  167. package/scenarios/apg/link.base-ui.json +88 -0
  168. package/scenarios/apg/link.react-aria.json +186 -0
  169. package/scenarios/apg/listbox.ariakit.json +78 -0
  170. package/scenarios/apg/listbox.base-ui.json +163 -0
  171. package/scenarios/apg/listbox.react-aria.json +1118 -0
  172. package/scenarios/apg/menu-button.ariakit.json +924 -0
  173. package/scenarios/apg/menu-button.base-ui.json +1155 -0
  174. package/scenarios/apg/menu-button.react-aria.json +1080 -0
  175. package/scenarios/apg/menubar.ariakit.json +758 -0
  176. package/scenarios/apg/menubar.base-ui.json +673 -0
  177. package/scenarios/apg/meter.base-ui.json +243 -0
  178. package/scenarios/apg/meter.react-aria.json +201 -0
  179. package/scenarios/apg/radio.ariakit.json +459 -0
  180. package/scenarios/apg/radio.base-ui.json +581 -0
  181. package/scenarios/apg/radio.react-aria.json +576 -0
  182. package/scenarios/apg/slider-multithumb.base-ui.json +316 -0
  183. package/scenarios/apg/slider-multithumb.react-aria.json +363 -0
  184. package/scenarios/apg/slider.base-ui.json +544 -0
  185. package/scenarios/apg/slider.react-aria.json +456 -0
  186. package/scenarios/apg/spinbutton.base-ui.json +1688 -0
  187. package/scenarios/apg/spinbutton.react-aria.json +426 -0
  188. package/scenarios/apg/switch.base-ui.json +456 -0
  189. package/scenarios/apg/switch.react-aria.json +303 -0
  190. package/scenarios/apg/table.ariakit.json +65 -0
  191. package/scenarios/apg/table.react-aria.json +221 -0
  192. package/scenarios/apg/tabs.ariakit.json +329 -0
  193. package/scenarios/apg/tabs.base-ui.json +875 -0
  194. package/scenarios/apg/tabs.react-aria.json +644 -0
  195. package/scenarios/apg/toolbar.ariakit.json +595 -0
  196. package/scenarios/apg/toolbar.base-ui.json +1189 -0
  197. package/scenarios/apg/toolbar.react-aria.json +383 -0
  198. package/scenarios/apg/tooltip.ariakit.json +441 -0
  199. package/scenarios/apg/tooltip.base-ui.json +411 -0
  200. package/scenarios/apg/tooltip.react-aria.json +454 -0
  201. package/scenarios/apg/treegrid.apg.json +2558 -0
  202. package/scenarios/apg/treegrid.react-aria.json +5094 -0
  203. package/scenarios/apg/treeview.apg.json +928 -0
  204. package/scenarios/apg/windowsplitter.apg.json +557 -0
  205. package/scenarios/canvas/README.md +69 -0
  206. package/scenarios/canvas/camera.contract.json +237 -0
  207. package/scenarios/canvas/camera.excalidraw.json +57 -0
  208. package/scenarios/canvas/marquee.excalidraw.json +122 -0
  209. package/scenarios/canvas/pan.excalidraw.json +46 -0
  210. package/scenarios/canvas/pointer.contract.json +528 -0
  211. package/scenarios/parts.json +291 -0
  212. package/shared/aria.qnt +186 -0
  213. package/shared/details-group.qnt +50 -0
  214. package/shared/focusgroup.qnt +152 -0
  215. package/shared/invokers.qnt +81 -0
  216. package/shared/popover.qnt +71 -0
  217. package/specs/apg/STATIC.md +103 -0
  218. package/specs/apg/accordion.qnt +578 -0
  219. package/specs/apg/alert.qnt +131 -0
  220. package/specs/apg/alertdialog.qnt +455 -0
  221. package/specs/apg/breadcrumb.qnt +88 -0
  222. package/specs/apg/button.qnt +445 -0
  223. package/specs/apg/carousel.qnt +732 -0
  224. package/specs/apg/checkbox-group.qnt +388 -0
  225. package/specs/apg/checkbox.qnt +394 -0
  226. package/specs/apg/combobox-autocomplete.qnt +764 -0
  227. package/specs/apg/combobox-dialog.qnt +627 -0
  228. package/specs/apg/combobox-grid.qnt +632 -0
  229. package/specs/apg/combobox-select-only.qnt +626 -0
  230. package/specs/apg/combobox-tree.qnt +633 -0
  231. package/specs/apg/dialog-modal.qnt +499 -0
  232. package/specs/apg/disclosure.qnt +309 -0
  233. package/specs/apg/feed.qnt +485 -0
  234. package/specs/apg/grid-selection.qnt +512 -0
  235. package/specs/apg/grid-sort.qnt +330 -0
  236. package/specs/apg/grid.qnt +360 -0
  237. package/specs/apg/link.qnt +119 -0
  238. package/specs/apg/listbox-multi.qnt +799 -0
  239. package/specs/apg/listbox-rearrangeable.qnt +801 -0
  240. package/specs/apg/listbox-single.qnt +511 -0
  241. package/specs/apg/listbox-typeahead.qnt +396 -0
  242. package/specs/apg/menu-button.qnt +363 -0
  243. package/specs/apg/menu-checkable.qnt +304 -0
  244. package/specs/apg/menu-menubar.qnt +531 -0
  245. package/specs/apg/meter.qnt +129 -0
  246. package/specs/apg/player.qnt +1081 -0
  247. package/specs/apg/radio.qnt +394 -0
  248. package/specs/apg/slider-multithumb.qnt +441 -0
  249. package/specs/apg/slider.qnt +396 -0
  250. package/specs/apg/spinbutton.qnt +494 -0
  251. package/specs/apg/switch.qnt +389 -0
  252. package/specs/apg/tabs.qnt +412 -0
  253. package/specs/apg/timeline.qnt +612 -0
  254. package/specs/apg/toolbar.qnt +364 -0
  255. package/specs/apg/tooltip.qnt +553 -0
  256. package/specs/apg/treegrid.qnt +983 -0
  257. package/specs/apg/treeview.qnt +1004 -0
  258. package/specs/apg/windowsplitter.qnt +487 -0
  259. package/specs/canvas/camera.qnt +407 -0
  260. package/specs/canvas/pointer.qnt +841 -0
@@ -0,0 +1,1081 @@
1
+ // APG Media Player — a composition, because W3C ships no media-player pattern.
2
+ // https://www.w3.org/WAI/ARIA/apg/patterns/ (no player pattern exists)
3
+ //
4
+ // The control surface is three APG patterns, re-stated here as contracts (the
5
+ // generator's import closure accepts pure-def `shared/*` modules only, so
6
+ // slider/toolbar/button cannot be imported):
7
+ // - Slider + the "Media Seek Slider" example
8
+ // (https://www.w3.org/WAI/ARIA/apg/patterns/slider/examples/slider-seek/):
9
+ // role=slider, aria-valuemin/now/max, aria-valuetext as m:ss; the thumb keys
10
+ // are ArrowLeft/Down -1s, ArrowRight/Up +1s, PageDown/Up -15/+15s, Home/End.
11
+ // Deliberate divergence from slider.qnt: the timeline does NOT mirror in RTL.
12
+ // - Toolbar: the controls row is one tab stop with roving focus; modelled only
13
+ // as a `focus` scope so the seek thumb and the surface keys do not fight.
14
+ // - Button / Menu Button: play-pause, mute, fullscreen toggles, and the v1
15
+ // speed menu; modelled as `plays`/`pauses` callbacks and the `rate` set.
16
+ //
17
+ // The playback machine is not ARIA: it comes from the HTML `HTMLMediaElement`
18
+ // state/event contract (https://html.spec.whatwg.org/multipage/media.html),
19
+ // reduced to the states with an observable UI consequence. bsky HLS is VOD, so
20
+ // live is out of v1 scope.
21
+ //
22
+ // Request vs commit (map ticket rt1p) covers the four preferences — volume,
23
+ // muted, rate, fullscreen — which a host may own. `setVolume`, `toggleMute`,
24
+ // `setRate`, `toggleFullscreen` are requests; `propVolume`, `propMuted`,
25
+ // `propRate`, `propFullscreen` are the host writing the controlled props;
26
+ // `control` / `acceptRequests` configure the host (init is literal-only).
27
+ // Invariants: ControlledFollowsProps (no event commits while controlled),
28
+ // RejectedRequestUntouched, AcceptedRequestCommits.
29
+ //
30
+ // The generator has no action guards, so each action assigns every variable and
31
+ // out-of-phase events are no-ops via `if (<phase>) new else current`.
32
+ //
33
+ // ponytail: transport (play/pause/seek) and the playback stage stay adapter-driven
34
+ // and uncontrolled: they are device state, and a host steers them through the
35
+ // `play()` / `pause()` / `seek()` requests the adapter answers, not a value prop.
36
+ // Add `paused` / `position` props when a host must own the transport.
37
+ // ponytail: `live` (duration === Infinity) is not carried; add a flag + a live
38
+ // edge when a PDS serves live video. Drag cadence (preview vs commit) is modelled
39
+ // as two events; the consumer chooses how often to fire the commit.
40
+ module apg_player {
41
+ var stage: str // "loading" | "ready" | "playing" | "paused" | "buffering" | "ended" | "error"
42
+ var seeking: bool // a seek is in flight (HTMLMediaElement.seeking)
43
+ var pos: int // currentTime, whole seconds (seek aria-valuenow)
44
+ var dur: int // duration, whole seconds (seek aria-valuemax); 0 until known
45
+ var known: bool // loadedmetadata fired → duration known, seeking allowed
46
+ var paused: bool // exposed .paused mirror
47
+ var ended: bool // exposed .ended mirror
48
+ var vol: int // 0..100 (volume thumb aria-valuenow)
49
+ var applied: int // gain actually handed to the adapter (0 while muted)
50
+ var muted: bool
51
+ var rate: int // percent: 50 | 100 | 150 | 200 (100 = 1x)
52
+ var full: bool // fullscreen is on (Fullscreen API on the container)
53
+ var textMin: int // minutes shown in aria-valuetext
54
+ var textSec: int // seconds shown in aria-valuetext (0..59)
55
+ var focus: str // "none" | "surface" | "seek" | "control"
56
+ var plays: int // onPlay callbacks
57
+ var pauses: int // onPause callbacks
58
+ var seeks: int // committed seeks (onSeek)
59
+ var controlled: bool // the host owns the preferences (volume/muted/rate/fullscreen props)
60
+ var accepting: bool // controlled host that answers every request with its intent
61
+ var prefs: int // the controlled props, packed with `snap` (meaningful while controlled)
62
+ var reqs: int // preference requests that fired a change callback
63
+ var reqKind: str // "none" | "volume" | "muted" | "rate" | "fullscreen": the last request
64
+ var askNum: int // intent of the last request (volume / rate, or 1 / 0 for a toggle)
65
+ var outcome: str // "none" | "accepted" | "rejected": fate of the last request
66
+ var base: int // `snap` of the preferences when the last request was received
67
+
68
+ // --- pure behaviour ----------------------------------------------------
69
+ def effVol(v: int, m: bool): int = if (m) 0 else v
70
+ def isLoading(s: str): bool = s == "loading"
71
+ def isActive(s: str): bool = or(s == "playing", s == "buffering")
72
+ def canPlay(s: str): bool =
73
+ or(or(s == "ready", s == "paused"), or(s == "ended", s == "buffering"))
74
+ def clampTo(p: int, k: bool, d: int): int =
75
+ if (k) (if (p < 0) 0 else if (p > d) d else p) else p
76
+ def tm(p: int): int = p / 60
77
+ def ts(p: int): int = p % 60
78
+ def keyDelta(k: str): int =
79
+ if (or(k == "ArrowLeft", k == "ArrowDown")) -1
80
+ else if (or(k == "ArrowRight", k == "ArrowUp")) 1
81
+ else if (k == "PageUp") 15
82
+ else if (k == "PageDown") -15
83
+ else 0
84
+ def seekTarget(k: str, p: int, d: int): int =
85
+ if (k == "Home") 0
86
+ else if (k == "End") d
87
+ else clampTo(p + keyDelta(k), true, d)
88
+ def surfaceDelta(k: str): int = if (k == "ArrowRight") 5 else -5
89
+
90
+ // --- request / commit (map ticket rt1p) ----------------------------------
91
+ // The four preferences (volume, muted, rate, fullscreen) are user-changeable
92
+ // and host-ownable. A request fires its callback when the intent differs from
93
+ // the current value; then, uncontrolled the request is accepted (it commits),
94
+ // controlled and `holds` the host ignores it (rejected, state untouched), and
95
+ // controlled and `follows` the host writes the intent back through the prop.
96
+ def b2i(b: bool): int = if (b) 1 else 0
97
+ // injective for vol 0..100, rate 0..200: ((vol*2 + muted) * 201 + rate) * 2 + full
98
+ def snap(v: int, m: bool, r: int, f: bool): int = ((v * 2 + b2i(m)) * 201 + r) * 2 + b2i(f)
99
+ def holds: bool = and(controlled, not(accepting))
100
+ def follows: bool = and(controlled, accepting)
101
+ def verdict: str = if (holds) "rejected" else "accepted"
102
+
103
+ // --- ARIA contract (derived from the state) -----------------------------
104
+ def ariaValueNow: int = pos
105
+ def ariaValueMin: int = 0
106
+ def ariaValueMax: int = dur
107
+
108
+ // --- machine -----------------------------------------------------------
109
+ action init = all {
110
+ stage' = "loading",
111
+ seeking' = false,
112
+ pos' = 0,
113
+ dur' = 0,
114
+ known' = false,
115
+ paused' = true,
116
+ ended' = false,
117
+ vol' = 100,
118
+ applied' = 100,
119
+ muted' = false,
120
+ rate' = 100,
121
+ full' = false,
122
+ textMin' = 0,
123
+ textSec' = 0,
124
+ focus' = "none",
125
+ plays' = 0,
126
+ pauses' = 0,
127
+ seeks' = 0,
128
+ controlled' = false,
129
+ accepting' = false,
130
+ prefs' = 0,
131
+ reqs' = 0,
132
+ reqKind' = "none",
133
+ askNum' = 0,
134
+ outcome' = "none",
135
+ base' = 0
136
+ }
137
+
138
+ action onLoadedMetadata = {
139
+ nondet d = Set(0, 1, 5, 60, 90).oneOf()
140
+ all {
141
+ stage' = if (isLoading(stage)) "ready" else stage,
142
+ seeking' = if (isLoading(stage)) false else seeking,
143
+ pos' = if (isLoading(stage)) 0 else pos,
144
+ dur' = if (isLoading(stage)) d else dur,
145
+ known' = if (isLoading(stage)) true else known,
146
+ paused' = paused,
147
+ ended' = ended,
148
+ vol' = vol,
149
+ applied' = applied,
150
+ muted' = muted,
151
+ rate' = rate,
152
+ full' = full,
153
+ textMin' = if (isLoading(stage)) 0 else textMin,
154
+ textSec' = if (isLoading(stage)) 0 else textSec,
155
+ focus' = focus,
156
+ plays' = plays,
157
+ pauses' = pauses,
158
+ seeks' = seeks,
159
+ controlled' = controlled,
160
+ accepting' = accepting,
161
+ prefs' = prefs,
162
+ reqs' = reqs,
163
+ reqKind' = reqKind,
164
+ askNum' = askNum,
165
+ outcome' = outcome,
166
+ base' = base
167
+ }
168
+ }
169
+
170
+ action onCanPlay = all {
171
+ stage' = if (isLoading(stage)) "ready" else stage,
172
+ seeking' = seeking,
173
+ pos' = pos,
174
+ dur' = dur,
175
+ known' = known,
176
+ paused' = paused,
177
+ ended' = ended,
178
+ vol' = vol,
179
+ applied' = applied,
180
+ muted' = muted,
181
+ rate' = rate,
182
+ full' = full,
183
+ textMin' = textMin,
184
+ textSec' = textSec,
185
+ focus' = focus,
186
+ plays' = plays,
187
+ pauses' = pauses,
188
+ seeks' = seeks,
189
+ controlled' = controlled,
190
+ accepting' = accepting,
191
+ prefs' = prefs,
192
+ reqs' = reqs,
193
+ reqKind' = reqKind,
194
+ askNum' = askNum,
195
+ outcome' = outcome,
196
+ base' = base
197
+ }
198
+
199
+ action onWaiting = all {
200
+ stage' = if (stage == "playing") "buffering" else stage,
201
+ seeking' = seeking,
202
+ pos' = pos,
203
+ dur' = dur,
204
+ known' = known,
205
+ paused' = paused,
206
+ ended' = ended,
207
+ vol' = vol,
208
+ applied' = applied,
209
+ muted' = muted,
210
+ rate' = rate,
211
+ full' = full,
212
+ textMin' = textMin,
213
+ textSec' = textSec,
214
+ focus' = focus,
215
+ plays' = plays,
216
+ pauses' = pauses,
217
+ seeks' = seeks,
218
+ controlled' = controlled,
219
+ accepting' = accepting,
220
+ prefs' = prefs,
221
+ reqs' = reqs,
222
+ reqKind' = reqKind,
223
+ askNum' = askNum,
224
+ outcome' = outcome,
225
+ base' = base
226
+ }
227
+
228
+ action onPlaying = all {
229
+ stage' = if (stage == "buffering") "playing" else stage,
230
+ seeking' = seeking,
231
+ pos' = pos,
232
+ dur' = dur,
233
+ known' = known,
234
+ paused' = if (stage == "buffering") false else paused,
235
+ ended' = ended,
236
+ vol' = vol,
237
+ applied' = applied,
238
+ muted' = muted,
239
+ rate' = rate,
240
+ full' = full,
241
+ textMin' = textMin,
242
+ textSec' = textSec,
243
+ focus' = focus,
244
+ plays' = plays,
245
+ pauses' = pauses,
246
+ seeks' = seeks,
247
+ controlled' = controlled,
248
+ accepting' = accepting,
249
+ prefs' = prefs,
250
+ reqs' = reqs,
251
+ reqKind' = reqKind,
252
+ askNum' = askNum,
253
+ outcome' = outcome,
254
+ base' = base
255
+ }
256
+
257
+ action onTimeUpdate = {
258
+ nondet t = Set(0, 1, 2, 30, 59, 60, 90).oneOf()
259
+ all {
260
+ stage' = stage,
261
+ seeking' = seeking,
262
+ pos' = if (stage == "playing") clampTo(t, known, dur) else pos,
263
+ dur' = dur,
264
+ known' = known,
265
+ paused' = paused,
266
+ ended' = ended,
267
+ vol' = vol,
268
+ applied' = applied,
269
+ muted' = muted,
270
+ rate' = rate,
271
+ full' = full,
272
+ textMin' = if (stage == "playing") tm(clampTo(t, known, dur)) else textMin,
273
+ textSec' = if (stage == "playing") ts(clampTo(t, known, dur)) else textSec,
274
+ focus' = focus,
275
+ plays' = plays,
276
+ pauses' = pauses,
277
+ seeks' = seeks,
278
+ controlled' = controlled,
279
+ accepting' = accepting,
280
+ prefs' = prefs,
281
+ reqs' = reqs,
282
+ reqKind' = reqKind,
283
+ askNum' = askNum,
284
+ outcome' = outcome,
285
+ base' = base
286
+ }
287
+ }
288
+
289
+ action onPause = all {
290
+ stage' = if (isActive(stage)) "paused" else stage,
291
+ seeking' = seeking,
292
+ pos' = pos,
293
+ dur' = dur,
294
+ known' = known,
295
+ paused' = if (isActive(stage)) true else paused,
296
+ ended' = ended,
297
+ vol' = vol,
298
+ applied' = applied,
299
+ muted' = muted,
300
+ rate' = rate,
301
+ full' = full,
302
+ textMin' = textMin,
303
+ textSec' = textSec,
304
+ focus' = focus,
305
+ plays' = plays,
306
+ pauses' = pauses,
307
+ seeks' = seeks,
308
+ controlled' = controlled,
309
+ accepting' = accepting,
310
+ prefs' = prefs,
311
+ reqs' = reqs,
312
+ reqKind' = reqKind,
313
+ askNum' = askNum,
314
+ outcome' = outcome,
315
+ base' = base
316
+ }
317
+
318
+ action onEnded = all {
319
+ stage' = if (stage == "playing") "ended" else stage,
320
+ seeking' = seeking,
321
+ pos' = if (and(stage == "playing", known)) dur else pos,
322
+ dur' = dur,
323
+ known' = known,
324
+ paused' = if (stage == "playing") true else paused,
325
+ ended' = if (stage == "playing") true else ended,
326
+ vol' = vol,
327
+ applied' = applied,
328
+ muted' = muted,
329
+ rate' = rate,
330
+ full' = full,
331
+ textMin' = if (and(stage == "playing", known)) tm(dur) else textMin,
332
+ textSec' = if (and(stage == "playing", known)) ts(dur) else textSec,
333
+ focus' = focus,
334
+ plays' = plays,
335
+ pauses' = pauses,
336
+ seeks' = seeks,
337
+ controlled' = controlled,
338
+ accepting' = accepting,
339
+ prefs' = prefs,
340
+ reqs' = reqs,
341
+ reqKind' = reqKind,
342
+ askNum' = askNum,
343
+ outcome' = outcome,
344
+ base' = base
345
+ }
346
+
347
+ action onError = all {
348
+ stage' = "error",
349
+ seeking' = false,
350
+ pos' = pos,
351
+ dur' = dur,
352
+ known' = known,
353
+ paused' = true,
354
+ ended' = false,
355
+ vol' = vol,
356
+ applied' = applied,
357
+ muted' = muted,
358
+ rate' = rate,
359
+ full' = full,
360
+ textMin' = textMin,
361
+ textSec' = textSec,
362
+ focus' = focus,
363
+ plays' = plays,
364
+ pauses' = pauses,
365
+ seeks' = seeks,
366
+ controlled' = controlled,
367
+ accepting' = accepting,
368
+ prefs' = prefs,
369
+ reqs' = reqs,
370
+ reqKind' = reqKind,
371
+ askNum' = askNum,
372
+ outcome' = outcome,
373
+ base' = base
374
+ }
375
+
376
+ action play = all {
377
+ stage' = if (canPlay(stage)) "playing" else stage,
378
+ seeking' = seeking,
379
+ pos' = if (and(canPlay(stage), stage == "ended")) 0 else pos,
380
+ dur' = dur,
381
+ known' = known,
382
+ paused' = if (canPlay(stage)) false else paused,
383
+ ended' = if (canPlay(stage)) false else ended,
384
+ vol' = vol,
385
+ applied' = applied,
386
+ muted' = muted,
387
+ rate' = rate,
388
+ full' = full,
389
+ textMin' = if (and(canPlay(stage), stage == "ended")) 0 else textMin,
390
+ textSec' = if (and(canPlay(stage), stage == "ended")) 0 else textSec,
391
+ focus' = focus,
392
+ plays' = if (canPlay(stage)) plays + 1 else plays,
393
+ pauses' = pauses,
394
+ seeks' = seeks,
395
+ controlled' = controlled,
396
+ accepting' = accepting,
397
+ prefs' = prefs,
398
+ reqs' = reqs,
399
+ reqKind' = reqKind,
400
+ askNum' = askNum,
401
+ outcome' = outcome,
402
+ base' = base
403
+ }
404
+
405
+ action pause = all {
406
+ stage' = if (isActive(stage)) "paused" else stage,
407
+ seeking' = seeking,
408
+ pos' = pos,
409
+ dur' = dur,
410
+ known' = known,
411
+ paused' = if (isActive(stage)) true else paused,
412
+ ended' = ended,
413
+ vol' = vol,
414
+ applied' = applied,
415
+ muted' = muted,
416
+ rate' = rate,
417
+ full' = full,
418
+ textMin' = textMin,
419
+ textSec' = textSec,
420
+ focus' = focus,
421
+ plays' = plays,
422
+ pauses' = if (isActive(stage)) pauses + 1 else pauses,
423
+ seeks' = seeks,
424
+ controlled' = controlled,
425
+ accepting' = accepting,
426
+ prefs' = prefs,
427
+ reqs' = reqs,
428
+ reqKind' = reqKind,
429
+ askNum' = askNum,
430
+ outcome' = outcome,
431
+ base' = base
432
+ }
433
+
434
+ action togglePlay = all {
435
+ stage' = if (isActive(stage)) "paused" else if (canPlay(stage)) "playing" else stage,
436
+ seeking' = seeking,
437
+ pos' = if (and(not(isActive(stage)), and(canPlay(stage), stage == "ended"))) 0 else pos,
438
+ dur' = dur,
439
+ known' = known,
440
+ paused' = if (isActive(stage)) true else if (canPlay(stage)) false else paused,
441
+ ended' = if (isActive(stage)) ended else if (canPlay(stage)) false else ended,
442
+ vol' = vol,
443
+ applied' = applied,
444
+ muted' = muted,
445
+ rate' = rate,
446
+ full' = full,
447
+ textMin' = if (and(not(isActive(stage)), and(canPlay(stage), stage == "ended"))) 0 else textMin,
448
+ textSec' = if (and(not(isActive(stage)), and(canPlay(stage), stage == "ended"))) 0 else textSec,
449
+ focus' = focus,
450
+ plays' = if (and(not(isActive(stage)), canPlay(stage))) plays + 1 else plays,
451
+ pauses' = if (isActive(stage)) pauses + 1 else pauses,
452
+ seeks' = seeks,
453
+ controlled' = controlled,
454
+ accepting' = accepting,
455
+ prefs' = prefs,
456
+ reqs' = reqs,
457
+ reqKind' = reqKind,
458
+ askNum' = askNum,
459
+ outcome' = outcome,
460
+ base' = base
461
+ }
462
+
463
+ action seekPreview = {
464
+ nondet t = Set(0, 1, 2, 30, 59, 60, 90).oneOf()
465
+ all {
466
+ stage' = if (and(known, stage == "ended")) "paused" else stage,
467
+ seeking' = if (known) true else seeking,
468
+ pos' = if (known) clampTo(t, true, dur) else pos,
469
+ dur' = dur,
470
+ known' = known,
471
+ paused' = paused,
472
+ ended' = if (known) false else ended,
473
+ vol' = vol,
474
+ applied' = applied,
475
+ muted' = muted,
476
+ rate' = rate,
477
+ full' = full,
478
+ textMin' = if (known) tm(clampTo(t, true, dur)) else textMin,
479
+ textSec' = if (known) ts(clampTo(t, true, dur)) else textSec,
480
+ focus' = focus,
481
+ plays' = plays,
482
+ pauses' = pauses,
483
+ seeks' = seeks,
484
+ controlled' = controlled,
485
+ accepting' = accepting,
486
+ prefs' = prefs,
487
+ reqs' = reqs,
488
+ reqKind' = reqKind,
489
+ askNum' = askNum,
490
+ outcome' = outcome,
491
+ base' = base
492
+ }
493
+ }
494
+
495
+ action seekCommit = {
496
+ nondet t = Set(0, 1, 2, 30, 59, 60, 90).oneOf()
497
+ all {
498
+ stage' = if (and(known, stage == "ended")) "paused" else stage,
499
+ seeking' = if (known) true else seeking,
500
+ pos' = if (known) clampTo(t, true, dur) else pos,
501
+ dur' = dur,
502
+ known' = known,
503
+ paused' = paused,
504
+ ended' = if (known) false else ended,
505
+ vol' = vol,
506
+ applied' = applied,
507
+ muted' = muted,
508
+ rate' = rate,
509
+ full' = full,
510
+ textMin' = if (known) tm(clampTo(t, true, dur)) else textMin,
511
+ textSec' = if (known) ts(clampTo(t, true, dur)) else textSec,
512
+ focus' = focus,
513
+ plays' = plays,
514
+ pauses' = pauses,
515
+ seeks' = if (known) seeks + 1 else seeks,
516
+ controlled' = controlled,
517
+ accepting' = accepting,
518
+ prefs' = prefs,
519
+ reqs' = reqs,
520
+ reqKind' = reqKind,
521
+ askNum' = askNum,
522
+ outcome' = outcome,
523
+ base' = base
524
+ }
525
+ }
526
+
527
+ action seekKey = {
528
+ nondet key = Set(
529
+ "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown",
530
+ "PageUp", "PageDown", "Home", "End"
531
+ ).oneOf()
532
+ all {
533
+ stage' = if (and(and(focus == "seek", known), stage == "ended")) "paused" else stage,
534
+ seeking' = if (and(focus == "seek", known)) true else seeking,
535
+ pos' = if (and(focus == "seek", known)) seekTarget(key, pos, dur) else pos,
536
+ dur' = dur,
537
+ known' = known,
538
+ paused' = paused,
539
+ ended' = if (and(focus == "seek", known)) false else ended,
540
+ vol' = vol,
541
+ applied' = applied,
542
+ muted' = muted,
543
+ rate' = rate,
544
+ full' = full,
545
+ textMin' = if (and(focus == "seek", known)) tm(seekTarget(key, pos, dur)) else textMin,
546
+ textSec' = if (and(focus == "seek", known)) ts(seekTarget(key, pos, dur)) else textSec,
547
+ focus' = focus,
548
+ plays' = plays,
549
+ pauses' = pauses,
550
+ seeks' = if (and(focus == "seek", known)) seeks + 1 else seeks,
551
+ controlled' = controlled,
552
+ accepting' = accepting,
553
+ prefs' = prefs,
554
+ reqs' = reqs,
555
+ reqKind' = reqKind,
556
+ askNum' = askNum,
557
+ outcome' = outcome,
558
+ base' = base
559
+ }
560
+ }
561
+
562
+ action surfaceSeekKey = {
563
+ nondet key = Set("ArrowLeft", "ArrowRight").oneOf()
564
+ all {
565
+ stage' = if (and(and(focus == "surface", known), stage == "ended")) "paused" else stage,
566
+ seeking' = if (and(focus == "surface", known)) true else seeking,
567
+ pos' = if (and(focus == "surface", known)) clampTo(pos + surfaceDelta(key), true, dur) else pos,
568
+ dur' = dur,
569
+ known' = known,
570
+ paused' = paused,
571
+ ended' = if (and(focus == "surface", known)) false else ended,
572
+ vol' = vol,
573
+ applied' = applied,
574
+ muted' = muted,
575
+ rate' = rate,
576
+ full' = full,
577
+ textMin' = if (and(focus == "surface", known)) tm(clampTo(pos + surfaceDelta(key), true, dur)) else textMin,
578
+ textSec' = if (and(focus == "surface", known)) ts(clampTo(pos + surfaceDelta(key), true, dur)) else textSec,
579
+ focus' = focus,
580
+ plays' = plays,
581
+ pauses' = pauses,
582
+ seeks' = if (and(focus == "surface", known)) seeks + 1 else seeks,
583
+ controlled' = controlled,
584
+ accepting' = accepting,
585
+ prefs' = prefs,
586
+ reqs' = reqs,
587
+ reqKind' = reqKind,
588
+ askNum' = askNum,
589
+ outcome' = outcome,
590
+ base' = base
591
+ }
592
+ }
593
+
594
+ action onSeeked = all {
595
+ stage' = stage,
596
+ seeking' = false,
597
+ pos' = pos,
598
+ dur' = dur,
599
+ known' = known,
600
+ paused' = paused,
601
+ ended' = ended,
602
+ vol' = vol,
603
+ applied' = applied,
604
+ muted' = muted,
605
+ rate' = rate,
606
+ full' = full,
607
+ textMin' = textMin,
608
+ textSec' = textSec,
609
+ focus' = focus,
610
+ plays' = plays,
611
+ pauses' = pauses,
612
+ seeks' = seeks,
613
+ controlled' = controlled,
614
+ accepting' = accepting,
615
+ prefs' = prefs,
616
+ reqs' = reqs,
617
+ reqKind' = reqKind,
618
+ askNum' = askNum,
619
+ outcome' = outcome,
620
+ base' = base
621
+ }
622
+
623
+ action setVolume = {
624
+ nondet v = Set(0, 25, 50, 75, 100).oneOf()
625
+ all {
626
+ stage' = stage,
627
+ seeking' = seeking,
628
+ pos' = pos,
629
+ dur' = dur,
630
+ known' = known,
631
+ paused' = paused,
632
+ ended' = ended,
633
+ vol' = if (holds) vol else v,
634
+ applied' = effVol(if (holds) vol else v, muted),
635
+ muted' = muted,
636
+ rate' = rate,
637
+ full' = full,
638
+ textMin' = textMin,
639
+ textSec' = textSec,
640
+ focus' = focus,
641
+ plays' = plays,
642
+ pauses' = pauses,
643
+ seeks' = seeks,
644
+ controlled' = controlled,
645
+ accepting' = accepting,
646
+ prefs' = if (follows) snap(v, muted, rate, full) else prefs,
647
+ reqs' = if (v == vol) reqs else reqs + 1,
648
+ reqKind' = if (v == vol) reqKind else "volume",
649
+ askNum' = if (v == vol) askNum else v,
650
+ outcome' = if (v == vol) outcome else verdict,
651
+ base' = if (v == vol) base else snap(vol, muted, rate, full)
652
+ }
653
+ }
654
+
655
+ action toggleMute = all {
656
+ stage' = stage,
657
+ seeking' = seeking,
658
+ pos' = pos,
659
+ dur' = dur,
660
+ known' = known,
661
+ paused' = paused,
662
+ ended' = ended,
663
+ vol' = vol,
664
+ applied' = effVol(vol, if (holds) muted else not(muted)),
665
+ muted' = if (holds) muted else not(muted),
666
+ rate' = rate,
667
+ full' = full,
668
+ textMin' = textMin,
669
+ textSec' = textSec,
670
+ focus' = focus,
671
+ plays' = plays,
672
+ pauses' = pauses,
673
+ seeks' = seeks,
674
+ controlled' = controlled,
675
+ accepting' = accepting,
676
+ prefs' = if (follows) snap(vol, not(muted), rate, full) else prefs,
677
+ reqs' = reqs + 1,
678
+ reqKind' = "muted",
679
+ askNum' = if (muted) 0 else 1,
680
+ outcome' = verdict,
681
+ base' = snap(vol, muted, rate, full)
682
+ }
683
+
684
+ action setRate = {
685
+ nondet r = Set(50, 100, 150, 200).oneOf()
686
+ all {
687
+ stage' = stage,
688
+ seeking' = seeking,
689
+ pos' = pos,
690
+ dur' = dur,
691
+ known' = known,
692
+ paused' = paused,
693
+ ended' = ended,
694
+ vol' = vol,
695
+ applied' = applied,
696
+ muted' = muted,
697
+ rate' = if (holds) rate else r,
698
+ full' = full,
699
+ textMin' = textMin,
700
+ textSec' = textSec,
701
+ focus' = focus,
702
+ plays' = plays,
703
+ pauses' = pauses,
704
+ seeks' = seeks,
705
+ controlled' = controlled,
706
+ accepting' = accepting,
707
+ prefs' = if (follows) snap(vol, muted, r, full) else prefs,
708
+ reqs' = if (r == rate) reqs else reqs + 1,
709
+ reqKind' = if (r == rate) reqKind else "rate",
710
+ askNum' = if (r == rate) askNum else r,
711
+ outcome' = if (r == rate) outcome else verdict,
712
+ base' = if (r == rate) base else snap(vol, muted, rate, full)
713
+ }
714
+ }
715
+
716
+ action toggleFullscreen = all {
717
+ stage' = stage,
718
+ seeking' = seeking,
719
+ pos' = pos,
720
+ dur' = dur,
721
+ known' = known,
722
+ paused' = paused,
723
+ ended' = ended,
724
+ vol' = vol,
725
+ applied' = applied,
726
+ muted' = muted,
727
+ rate' = rate,
728
+ full' = if (holds) full else not(full),
729
+ textMin' = textMin,
730
+ textSec' = textSec,
731
+ focus' = focus,
732
+ plays' = plays,
733
+ pauses' = pauses,
734
+ seeks' = seeks,
735
+ controlled' = controlled,
736
+ accepting' = accepting,
737
+ prefs' = if (follows) snap(vol, muted, rate, not(full)) else prefs,
738
+ reqs' = reqs + 1,
739
+ reqKind' = "fullscreen",
740
+ askNum' = if (full) 0 else 1,
741
+ outcome' = verdict,
742
+ base' = snap(vol, muted, rate, full)
743
+ }
744
+
745
+ action focusOn = {
746
+ nondet target = Set("none", "surface", "seek", "control").oneOf()
747
+ all {
748
+ stage' = stage,
749
+ seeking' = seeking,
750
+ pos' = pos,
751
+ dur' = dur,
752
+ known' = known,
753
+ paused' = paused,
754
+ ended' = ended,
755
+ vol' = vol,
756
+ applied' = applied,
757
+ muted' = muted,
758
+ rate' = rate,
759
+ full' = full,
760
+ textMin' = textMin,
761
+ textSec' = textSec,
762
+ focus' = target,
763
+ plays' = plays,
764
+ pauses' = pauses,
765
+ seeks' = seeks,
766
+ controlled' = controlled,
767
+ accepting' = accepting,
768
+ prefs' = prefs,
769
+ reqs' = reqs,
770
+ reqKind' = reqKind,
771
+ askNum' = askNum,
772
+ outcome' = outcome,
773
+ base' = base
774
+ }
775
+ }
776
+
777
+ action blur = all {
778
+ stage' = stage,
779
+ seeking' = seeking,
780
+ pos' = pos,
781
+ dur' = dur,
782
+ known' = known,
783
+ paused' = paused,
784
+ ended' = ended,
785
+ vol' = vol,
786
+ applied' = applied,
787
+ muted' = muted,
788
+ rate' = rate,
789
+ full' = full,
790
+ textMin' = textMin,
791
+ textSec' = textSec,
792
+ focus' = "none",
793
+ plays' = plays,
794
+ pauses' = pauses,
795
+ seeks' = seeks,
796
+ controlled' = controlled,
797
+ accepting' = accepting,
798
+ prefs' = prefs,
799
+ reqs' = reqs,
800
+ reqKind' = reqKind,
801
+ askNum' = askNum,
802
+ outcome' = outcome,
803
+ base' = base
804
+ }
805
+
806
+ action loadStart = all {
807
+ stage' = "loading",
808
+ seeking' = false,
809
+ pos' = 0,
810
+ dur' = 0,
811
+ known' = false,
812
+ paused' = true,
813
+ ended' = false,
814
+ vol' = vol,
815
+ applied' = applied,
816
+ muted' = muted,
817
+ rate' = rate,
818
+ full' = full,
819
+ textMin' = 0,
820
+ textSec' = 0,
821
+ focus' = focus,
822
+ plays' = plays,
823
+ pauses' = pauses,
824
+ seeks' = seeks,
825
+ controlled' = controlled,
826
+ accepting' = accepting,
827
+ prefs' = prefs,
828
+ reqs' = reqs,
829
+ reqKind' = reqKind,
830
+ askNum' = askNum,
831
+ outcome' = outcome,
832
+ base' = base
833
+ }
834
+
835
+ // commit: the host writes a controlled preference (a no-op while uncontrolled)
836
+ action propVolume = {
837
+ nondet v = Set(0, 25, 50, 75, 100).oneOf()
838
+ all {
839
+ stage' = stage,
840
+ seeking' = seeking,
841
+ pos' = pos,
842
+ dur' = dur,
843
+ known' = known,
844
+ paused' = paused,
845
+ ended' = ended,
846
+ vol' = if (controlled) v else vol,
847
+ applied' = if (controlled) effVol(v, muted) else applied,
848
+ muted' = muted,
849
+ rate' = rate,
850
+ full' = full,
851
+ textMin' = textMin,
852
+ textSec' = textSec,
853
+ focus' = focus,
854
+ plays' = plays,
855
+ pauses' = pauses,
856
+ seeks' = seeks,
857
+ controlled' = controlled,
858
+ accepting' = accepting,
859
+ prefs' = if (controlled) snap(v, muted, rate, full) else prefs,
860
+ reqs' = reqs,
861
+ reqKind' = reqKind,
862
+ askNum' = askNum,
863
+ outcome' = if (controlled) "none" else outcome,
864
+ base' = base
865
+ }
866
+ }
867
+
868
+ action propMuted = {
869
+ nondet m = Set(true, false).oneOf()
870
+ all {
871
+ stage' = stage,
872
+ seeking' = seeking,
873
+ pos' = pos,
874
+ dur' = dur,
875
+ known' = known,
876
+ paused' = paused,
877
+ ended' = ended,
878
+ vol' = vol,
879
+ applied' = if (controlled) effVol(vol, m) else applied,
880
+ muted' = if (controlled) m else muted,
881
+ rate' = rate,
882
+ full' = full,
883
+ textMin' = textMin,
884
+ textSec' = textSec,
885
+ focus' = focus,
886
+ plays' = plays,
887
+ pauses' = pauses,
888
+ seeks' = seeks,
889
+ controlled' = controlled,
890
+ accepting' = accepting,
891
+ prefs' = if (controlled) snap(vol, m, rate, full) else prefs,
892
+ reqs' = reqs,
893
+ reqKind' = reqKind,
894
+ askNum' = askNum,
895
+ outcome' = if (controlled) "none" else outcome,
896
+ base' = base
897
+ }
898
+ }
899
+
900
+ action propRate = {
901
+ nondet r = Set(50, 100, 150, 200).oneOf()
902
+ all {
903
+ stage' = stage,
904
+ seeking' = seeking,
905
+ pos' = pos,
906
+ dur' = dur,
907
+ known' = known,
908
+ paused' = paused,
909
+ ended' = ended,
910
+ vol' = vol,
911
+ applied' = applied,
912
+ muted' = muted,
913
+ rate' = if (controlled) r else rate,
914
+ full' = full,
915
+ textMin' = textMin,
916
+ textSec' = textSec,
917
+ focus' = focus,
918
+ plays' = plays,
919
+ pauses' = pauses,
920
+ seeks' = seeks,
921
+ controlled' = controlled,
922
+ accepting' = accepting,
923
+ prefs' = if (controlled) snap(vol, muted, r, full) else prefs,
924
+ reqs' = reqs,
925
+ reqKind' = reqKind,
926
+ askNum' = askNum,
927
+ outcome' = if (controlled) "none" else outcome,
928
+ base' = base
929
+ }
930
+ }
931
+
932
+ action propFullscreen = {
933
+ nondet f = Set(true, false).oneOf()
934
+ all {
935
+ stage' = stage,
936
+ seeking' = seeking,
937
+ pos' = pos,
938
+ dur' = dur,
939
+ known' = known,
940
+ paused' = paused,
941
+ ended' = ended,
942
+ vol' = vol,
943
+ applied' = applied,
944
+ muted' = muted,
945
+ rate' = rate,
946
+ full' = if (controlled) f else full,
947
+ textMin' = textMin,
948
+ textSec' = textSec,
949
+ focus' = focus,
950
+ plays' = plays,
951
+ pauses' = pauses,
952
+ seeks' = seeks,
953
+ controlled' = controlled,
954
+ accepting' = accepting,
955
+ prefs' = if (controlled) snap(vol, muted, rate, f) else prefs,
956
+ reqs' = reqs,
957
+ reqKind' = reqKind,
958
+ askNum' = askNum,
959
+ outcome' = if (controlled) "none" else outcome,
960
+ base' = base
961
+ }
962
+ }
963
+
964
+ // configuration: the host starts owning the preferences, adopting the current ones
965
+ action control = all {
966
+ stage' = stage,
967
+ seeking' = seeking,
968
+ pos' = pos,
969
+ dur' = dur,
970
+ known' = known,
971
+ paused' = paused,
972
+ ended' = ended,
973
+ vol' = vol,
974
+ applied' = applied,
975
+ muted' = muted,
976
+ rate' = rate,
977
+ full' = full,
978
+ textMin' = textMin,
979
+ textSec' = textSec,
980
+ focus' = focus,
981
+ plays' = plays,
982
+ pauses' = pauses,
983
+ seeks' = seeks,
984
+ controlled' = true,
985
+ accepting' = accepting,
986
+ prefs' = snap(vol, muted, rate, full),
987
+ reqs' = reqs,
988
+ reqKind' = reqKind,
989
+ askNum' = askNum,
990
+ outcome' = outcome,
991
+ base' = base
992
+ }
993
+
994
+ // configuration: the controlled host answers every request with its intent
995
+ action acceptRequests = all {
996
+ stage' = stage,
997
+ seeking' = seeking,
998
+ pos' = pos,
999
+ dur' = dur,
1000
+ known' = known,
1001
+ paused' = paused,
1002
+ ended' = ended,
1003
+ vol' = vol,
1004
+ applied' = applied,
1005
+ muted' = muted,
1006
+ rate' = rate,
1007
+ full' = full,
1008
+ textMin' = textMin,
1009
+ textSec' = textSec,
1010
+ focus' = focus,
1011
+ plays' = plays,
1012
+ pauses' = pauses,
1013
+ seeks' = seeks,
1014
+ controlled' = controlled,
1015
+ accepting' = true,
1016
+ prefs' = prefs,
1017
+ reqs' = reqs,
1018
+ reqKind' = reqKind,
1019
+ askNum' = askNum,
1020
+ outcome' = outcome,
1021
+ base' = base
1022
+ }
1023
+
1024
+ action step = any {
1025
+ init,
1026
+ onLoadedMetadata, onCanPlay, onWaiting, onPlaying, onTimeUpdate,
1027
+ onPause, onEnded, onError,
1028
+ play, pause, togglePlay,
1029
+ seekPreview, seekCommit, seekKey, surfaceSeekKey, onSeeked,
1030
+ setVolume, toggleMute, setRate, toggleFullscreen,
1031
+ propVolume, propMuted, propRate, propFullscreen, control, acceptRequests,
1032
+ focusOn, blur, loadStart
1033
+ }
1034
+
1035
+ // --- invariants --------------------------------------------------------
1036
+ // aria-valuenow stays inside [aria-valuemin, aria-valuemax]
1037
+ val SeekInRange: bool = and(pos >= 0, or(not(known), pos <= dur))
1038
+ val AriaNowWithinBounds: bool =
1039
+ and(ariaValueMin <= ariaValueNow, or(not(known), ariaValueNow <= ariaValueMax))
1040
+ // playing implies neither paused nor ended; the exposed mirrors track the stage
1041
+ val PlayingNotPaused: bool = or(stage != "playing", not(paused))
1042
+ val PlayingNotEnded: bool = or(stage != "playing", not(ended))
1043
+ val PausedMirrorsStage: bool = paused == not(isActive(stage))
1044
+ val EndedMirrorsStage: bool = ended == (stage == "ended")
1045
+ // the m:ss value text corresponds to the numeric value
1046
+ val ValueTextMirrors: bool = and(textMin == pos / 60, textSec == pos % 60)
1047
+ val ValueTextWellFormed: bool = and(textMin >= 0, and(textSec >= 0, textSec < 60))
1048
+ // muted silences; unmuted applies the slider value verbatim
1049
+ val MutedSilences: bool = and(
1050
+ applied == effVol(vol, muted),
1051
+ or(muted, applied == vol),
1052
+ or(not(muted), applied == 0)
1053
+ )
1054
+ val VolumeInRange: bool = and(vol >= 0, and(vol <= 100, and(applied >= 0, applied <= 100)))
1055
+ // seeking only happens once the duration is known
1056
+ val SeekingOnlyWhenKnown: bool = or(not(seeking), known)
1057
+ // the ended position is the timeline end
1058
+ val EndedAtDuration: bool = or(not(ended), or(not(known), pos == dur))
1059
+ // the speed menu offers only the modelled rates
1060
+ val RateAllowed: bool = or(rate == 50, or(rate == 100, or(rate == 150, rate == 200)))
1061
+ // focus is one of the modelled scopes
1062
+ val FocusKnown: bool = or(
1063
+ or(focus == "none", focus == "surface"),
1064
+ or(focus == "seek", focus == "control")
1065
+ )
1066
+ // callbacks only ever count up from zero
1067
+ val CallbacksNonNegative: bool = and(plays >= 0, and(pauses >= 0, and(seeks >= 0, reqs >= 0)))
1068
+ // controlled: only the props set the preferences, so no event commits
1069
+ val ControlledFollowsProps: bool = or(not(controlled), snap(vol, muted, rate, full) == prefs)
1070
+ // a rejected request leaves the preferences as it found them
1071
+ val RejectedRequestUntouched: bool = or(not(outcome == "rejected"), snap(vol, muted, rate, full) == base)
1072
+ // an accepted request committed exactly its intent
1073
+ val AcceptedRequestCommits: bool = or(
1074
+ not(outcome == "accepted"),
1075
+ if (reqKind == "volume") vol == askNum
1076
+ else if (reqKind == "muted") b2i(muted) == askNum
1077
+ else if (reqKind == "rate") rate == askNum
1078
+ else if (reqKind == "fullscreen") b2i(full) == askNum
1079
+ else false
1080
+ )
1081
+ }