@gentomiyano/optimized-web-audio-player 0.1.0 → 0.2.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 (47) hide show
  1. package/README.md +189 -159
  2. package/dist/config/index.d.ts +1 -1
  3. package/dist/config/index.js +1 -0
  4. package/dist/config/index.js.map +1 -1
  5. package/dist/consumer/index.d.ts +1 -1
  6. package/dist/consumer/index.js.map +1 -1
  7. package/dist/contracts/index.d.ts +2 -2
  8. package/dist/index.d.ts +2 -2
  9. package/dist/index.js +1 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/media-delivery/index.d.ts +1 -1
  12. package/dist/media-delivery/index.js.map +1 -1
  13. package/dist/{media-element-C9dis4aG.d.ts → media-element-DyMO-PhK.d.ts} +1 -1
  14. package/dist/{player-DE9Vqztu.d.ts → player-BxNFzRq4.d.ts} +53 -1
  15. package/dist/ports/index.d.ts +2 -2
  16. package/dist/react/index.d.ts +22 -3
  17. package/dist/react/index.js +721 -12
  18. package/dist/react/index.js.map +1 -1
  19. package/dist/testing/index.d.ts +1 -1
  20. package/dist/testing/index.js +1 -0
  21. package/dist/testing/index.js.map +1 -1
  22. package/dist/ui/index.d.ts +18 -2
  23. package/dist/ui/index.js +148 -55
  24. package/dist/ui/index.js.map +1 -1
  25. package/dist/ui/player-tokens.css +572 -572
  26. package/dist/ui/player-views.css +713 -654
  27. package/dist/ui/styles.css +2 -2
  28. package/package.json +14 -3
  29. package/dist/consumer-smoke/node_modules/.modules.yaml +0 -30
  30. package/dist/consumer-smoke/node_modules/.package-map.json +0 -1
  31. package/dist/consumer-smoke/node_modules/.pnpm/lock.yaml +0 -57
  32. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/LICENSE +0 -21
  33. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/README.md +0 -9
  34. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler-unstable_mock.development.js +0 -414
  35. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler-unstable_mock.production.js +0 -406
  36. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler-unstable_post_task.development.js +0 -150
  37. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler-unstable_post_task.production.js +0 -140
  38. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler.development.js +0 -364
  39. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler.native.development.js +0 -350
  40. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler.native.production.js +0 -330
  41. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler.production.js +0 -340
  42. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/index.js +0 -7
  43. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/index.native.js +0 -7
  44. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/package.json +0 -27
  45. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/unstable_mock.js +0 -7
  46. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/unstable_post_task.js +0 -7
  47. package/dist/consumer-smoke/node_modules/.pnpm-workspace-state-v1.json +0 -29
package/README.md CHANGED
@@ -1,159 +1,189 @@
1
- # Optimized Web Audio Player
2
-
3
- Consumer-neutral Music Player package published as
4
- `@gentomiyano/optimized-web-audio-player`.
5
-
6
- ## MVP status
7
-
8
- The standalone Player MVP was accepted on 2026-08-01. The accepted scope
9
- includes the framework-independent Core, reusable React Runtime and UI,
10
- Compact / Wide / Detailed / Podcast / Mini views, Queue, Debug customization,
11
- local-media Showcase tooling, Media Session integration, responsive mobile
12
- review, and the media-delivery boundary contract.
13
-
14
- The tested iOS device continued playback in the background, during sleep, and
15
- on the Lock Screen. Lock Screen Previous / Next and Seek worked, and starting
16
- audio in another application interrupted the Player as expected. iOS uses the
17
- continuity-first native HTML media path by default; fade timing is not
18
- perceptible there and is an accepted MVP platform limitation. Narrow Compact
19
- and Mini layouts, including Global Mini, omit their in-player volume control
20
- because iOS owns device output volume.
21
-
22
- This acceptance does not connect a consumer application or production media
23
- origin, and it is not a claim of identical behavior across every iOS version,
24
- browser, Bluetooth route, or device.
25
-
26
- ## WP-07 consumer integration foundation
27
-
28
- The production React Runtime, UI, CSS, and consumer queue builder now have
29
- explicit package entry points. A consumer can mount one empty
30
- `PlayerProvider` in its application shell, map authorized public media through
31
- its own Host Adapter, and build collection, selection, or page-order queues
32
- without copying Player source.
33
-
34
- Official Website and Timeline are not changed in this phase. Protected-URL
35
- refresh and the first consumer connection remain explicit checkpoints. See
36
- `docs/WP-07-CONSUMER-INTEGRATION-FOUNDATION.md`.
37
-
38
- The public npm registry, scoped package name, and release workflow are fixed.
39
- Initial npm publication still requires the scope owner's authentication.
40
- Release and consumer pinning rules are documented in `docs/RELEASING.md`.
41
-
42
- ## WP-06 debug local media import
43
-
44
- The standalone Showcase has a dev-only Local Media panel for dropping WAV,
45
- MP3, JPG/JPEG, PNG, and WebP files. Multi-file audio drops become a temporary
46
- Queue in drop order. Metadata overrides, artwork switching, track-number
47
- sorting, duplicate policy, re-read, reorder, delete, clear, and cancellable
48
- waveform analysis remain in tab memory only.
49
-
50
- No local file, metadata, filename, or object URL is sent, logged, persisted, or
51
- written back. Reloading clears the import. Audio and artwork object URLs are
52
- reference-counted, reused for duplicate identities, and revoked after the last
53
- item is removed, on clear, or when the Showcase unmounts.
54
-
55
- The parser and UI live only under `showcase/local-media/**`. They are not
56
- package exports or `tsup` entries. `pnpm build` scans every production
57
- JavaScript output for WP-06 markers and fails if the debug importer leaks into
58
- the package. See `docs/WP-06-DEBUG-MEDIA-IMPORT.md` for format coverage,
59
- limitations, and the manual checkpoint.
60
-
61
- ## WP-05 background playback checkpoint
62
-
63
- The standalone Showcase now integrates Media Session metadata, playback and
64
- position state, and OS action handlers. Music exposes play, pause, stop,
65
- Previous, Next, and seek-to. Podcast additionally exposes configurable skip
66
- backward and forward actions. Every handler delegates to the existing
67
- `PlayerCommands`; initial queue load remains non-autoplay.
68
-
69
- `audioMode=auto` keeps Web Audio fades on non-iOS browsers and uses the native
70
- HTMLAudioElement path on iPhone/iPadOS, prioritizing background continuity over
71
- fades. Real-device comparison modes are:
72
-
73
- - `http://127.0.0.1:4274/?audioMode=web-audio`
74
- - `http://127.0.0.1:4274/?audioMode=html-media`
75
-
76
- Double-click `start-player-showcase-device.cmd` for same-LAN iPhone testing,
77
- then replace the host with this computer's private LAN IP. The original
78
- `start-player-showcase.cmd` remains the loopback-only desktop launcher.
79
- `start-player-showcase-mobile.cmd` is an equivalent mobile-review launcher.
80
- The Showcase also has a `Mobile preview` toggle; `?preview=mobile` opens the
81
- same constrained review shell directly on desktop.
82
-
83
- Automated verification and the accepted device observations are recorded in
84
- `docs/WP-05A-BACKGROUND-PLAYBACK-FOUNDATION.md`. Broader device/version,
85
- Bluetooth, and production-consumer claims still require their own acceptance
86
- record.
87
-
88
- ## WP-03 runtime showcase
89
-
90
- Double-click `start-player-showcase.cmd` to open the standalone Showcase at
91
- `http://127.0.0.1:4274/`. The runtime keeps one Core store, one browser audio
92
- element, and one lazily-created Web Audio gain graph above the Compact, Wide,
93
- Detailed, Podcast, and Mini view switcher. Queue Panel and the fixed Global
94
- Mini also consume that same state. Switching views therefore does not stop
95
- playback or create another audio element.
96
-
97
- The Showcase uses generated music and podcast WAV tone fixtures. They are local,
98
- copyright-free integration media only and are not production assets. The
99
- runtime connects UI exclusively through `PlayerState` and `PlayerCommands`;
100
- the waveform remains visual-only and seeking is owned by the Seek Bar.
101
-
102
- Manual UX check:
103
-
104
- 1. Start playback and confirm that Audio Context changes from `uninitialized`
105
- to `running` only after the first user action.
106
- 2. Check Play/Pause, Seek, Volume, Previous, and Next in Wide view. On desktop,
107
- open the compact vertical Volume control from its icon in Compact and Mini;
108
- narrow mobile layouts intentionally omit it.
109
- 3. While playing, switch between Compact, Wide, Detailed, Podcast, and Mini and
110
- confirm that the same track, time, and playback state continue.
111
- 4. Open Queue Panel, select another item, and check the Podcast queue with
112
- skip-back/forward controls.
113
- 5. Toggle Global Mini and confirm that the page bottom inset is reserved only
114
- while it is visible.
115
- 6. In browser developer tools, confirm that
116
- `document.querySelectorAll('audio[data-player-audio="true"]').length` is
117
- always `1`.
118
-
119
- ## WP-01 entry points
120
-
121
- - `@gentomiyano/optimized-web-audio-player/contracts`: immutable state, commands, lifecycle,
122
- host adapter, config, preset, and debug-controller contracts
123
- - `@gentomiyano/optimized-web-audio-player/ports`: small media, gain, and persistence ports
124
- - `@gentomiyano/optimized-web-audio-player/config`: preview defaults, strict validation,
125
- versioned Config JSON schema v2, and initial state
126
- - `@gentomiyano/optimized-web-audio-player/media-delivery`: WP-04 URL-free delivery
127
- descriptors, ephemeral playback-lease and Host Adapter contracts, upload
128
- evidence validation, and waveform validation
129
- - `@gentomiyano/optimized-web-audio-player/consumer`: ordered consumer-record to Core Queue
130
- conversion for already-authorized directly playable media
131
- - `@gentomiyano/optimized-web-audio-player/react`: the single-audio React Runtime and hook
132
- - `@gentomiyano/optimized-web-audio-player/ui`: reusable Player views and primitives
133
- - `@gentomiyano/optimized-web-audio-player/styles.css`: production UI tokens and styles
134
- - `@gentomiyano/optimized-web-audio-player/testing`: privacy-safe fixtures and mock scenarios
135
-
136
- The root entry point exports production contracts, ports, config, and the
137
- provider-neutral media-delivery contract only. Runtime/UI are opt-in subpaths,
138
- and testing helpers are isolated behind `/testing`. No debug Runtime, Showcase,
139
- local importer, consumer-specific adapter, or production media URL is included
140
- in the package root.
141
-
142
- ## Commands
143
-
144
- ```text
145
- pnpm lint
146
- pnpm test
147
- pnpm typecheck
148
- pnpm build
149
- pnpm showcase:build
150
- ```
151
-
152
- ## Parallel ownership after the WP-01 checkpoint
153
-
154
- - Core: `src/core/**`, `tests/core/**`
155
- - WP-02B UI ownership explicitly includes `src/ui/**`, `src/icons/**`,
156
- `src/debug/ui/**`, and `tests/ui/**`.
157
- - UI may also render its mock views inside the pre-created `showcase/**` shell.
158
- - Integration-owned files, including contracts, ports, package configuration,
159
- adapters, and testing fixtures, must not be changed by Core or UI work.
1
+ # Optimized Web Audio Player
2
+
3
+ Consumer-neutral Music Player package published as
4
+ `@gentomiyano/optimized-web-audio-player`.
5
+
6
+ ## MVP status
7
+
8
+ The standalone Player MVP was accepted on 2026-08-01. The accepted scope
9
+ includes the framework-independent Core, reusable React Runtime and UI,
10
+ Compact / Wide / Detailed / Podcast / Mini views, Queue, Debug customization,
11
+ local-media Showcase tooling, Media Session integration, responsive mobile
12
+ review, and the media-delivery boundary contract.
13
+
14
+ The tested iOS device continued playback in the background, during sleep, and
15
+ on the Lock Screen. Lock Screen Previous / Next and Seek worked, and starting
16
+ audio in another application interrupted the Player as expected. iOS uses the
17
+ continuity-first native HTML media path by default; fade timing is not
18
+ perceptible there and is an accepted MVP platform limitation. Narrow Compact
19
+ and Mini layouts, including Global Mini, omit their in-player volume control
20
+ because iOS owns device output volume.
21
+
22
+ This acceptance does not connect a consumer application or production media
23
+ origin, and it is not a claim of identical behavior across every iOS version,
24
+ browser, Bluetooth route, or device.
25
+
26
+ ## WP-07 consumer integration foundation
27
+
28
+ The production React Runtime, UI, CSS, and consumer queue builder now have
29
+ explicit package entry points. A consumer can mount one empty
30
+ `PlayerProvider` in its application shell, map authorized public media through
31
+ its own Host Adapter, and build collection, selection, or page-order queues
32
+ without copying Player source.
33
+
34
+ Official Website and Timeline are not changed in this phase. Protected-URL
35
+ refresh and the first consumer connection remain explicit checkpoints. See
36
+ `docs/WP-07-CONSUMER-INTEGRATION-FOUNDATION.md`.
37
+
38
+ The public npm registry, scoped package name, and release workflow are fixed.
39
+ Initial npm publication still requires the scope owner's authentication.
40
+ Release and consumer pinning rules are documented in `docs/RELEASING.md`.
41
+
42
+ ## WP-04.3 React package boundary
43
+
44
+ Version 0.2.1 keeps React Context consumers in the `/react` entrypoint and
45
+ exports props-only views from `/ui`. Import `PlayerProvider`, `usePlayer`,
46
+ `useBoundPlayer`, and `BoundCompactPlayer` from `/react`; import
47
+ `BoundCompactPlayerView`, `MiniPlayer`, and the other pure views from `/ui`.
48
+
49
+ The release check packs the built package, installs that tarball into an empty
50
+ consumer, renders a bound inline Player with a shared Mini Player, and verifies
51
+ one Store, one active item, and one audio element. See
52
+ `docs/WP-04.3-REACT-BOUNDARY.md`.
53
+
54
+ ## WP-04.2 playback contexts and bound inline players
55
+
56
+ Version 0.2.0 adds consumer-neutral `manual-history` and `ordered-queue`
57
+ playback contexts. Contexts change only through an explicit
58
+ `activateContext` command; rerendering or navigating while the application
59
+ shell retains `PlayerProvider` does not replace the active context.
60
+
61
+ `manual-history` records only explicitly played occurrences, never advances
62
+ on natural end, and supports Previous / Next only inside the recorded session
63
+ history. `ordered-queue` keeps normal queue navigation, natural advancement,
64
+ shuffle, and repeat behavior. Queue updates preserve playback when the same
65
+ `queueItemId` and source descriptor remain selected.
66
+
67
+ Consumers can use `useBoundPlayer` or `BoundCompactPlayer` to bind an inline
68
+ control to one queue occurrence without creating another audio element or
69
+ store. See `docs/WP-04.2-PLAYBACK-CONTEXT.md` for contracts and integration
70
+ examples.
71
+
72
+ ## WP-06 debug local media import
73
+
74
+ The standalone Showcase has a dev-only Local Media panel for dropping WAV,
75
+ MP3, JPG/JPEG, PNG, and WebP files. Multi-file audio drops become a temporary
76
+ Queue in drop order. Metadata overrides, artwork switching, track-number
77
+ sorting, duplicate policy, re-read, reorder, delete, clear, and cancellable
78
+ waveform analysis remain in tab memory only.
79
+
80
+ No local file, metadata, filename, or object URL is sent, logged, persisted, or
81
+ written back. Reloading clears the import. Audio and artwork object URLs are
82
+ reference-counted, reused for duplicate identities, and revoked after the last
83
+ item is removed, on clear, or when the Showcase unmounts.
84
+
85
+ The parser and UI live only under `showcase/local-media/**`. They are not
86
+ package exports or `tsup` entries. `pnpm build` scans every production
87
+ JavaScript output for WP-06 markers and fails if the debug importer leaks into
88
+ the package. See `docs/WP-06-DEBUG-MEDIA-IMPORT.md` for format coverage,
89
+ limitations, and the manual checkpoint.
90
+
91
+ ## WP-05 background playback checkpoint
92
+
93
+ The standalone Showcase now integrates Media Session metadata, playback and
94
+ position state, and OS action handlers. Music exposes play, pause, stop,
95
+ Previous, Next, and seek-to. Podcast additionally exposes configurable skip
96
+ backward and forward actions. Every handler delegates to the existing
97
+ `PlayerCommands`; initial queue load remains non-autoplay.
98
+
99
+ `audioMode=auto` keeps Web Audio fades on non-iOS browsers and uses the native
100
+ HTMLAudioElement path on iPhone/iPadOS, prioritizing background continuity over
101
+ fades. Real-device comparison modes are:
102
+
103
+ - `http://127.0.0.1:4274/?audioMode=web-audio`
104
+ - `http://127.0.0.1:4274/?audioMode=html-media`
105
+
106
+ Double-click `start-player-showcase-device.cmd` for same-LAN iPhone testing,
107
+ then replace the host with this computer's private LAN IP. The original
108
+ `start-player-showcase.cmd` remains the loopback-only desktop launcher.
109
+ `start-player-showcase-mobile.cmd` is an equivalent mobile-review launcher.
110
+ The Showcase also has a `Mobile preview` toggle; `?preview=mobile` opens the
111
+ same constrained review shell directly on desktop.
112
+
113
+ Automated verification and the accepted device observations are recorded in
114
+ `docs/WP-05A-BACKGROUND-PLAYBACK-FOUNDATION.md`. Broader device/version,
115
+ Bluetooth, and production-consumer claims still require their own acceptance
116
+ record.
117
+
118
+ ## WP-03 runtime showcase
119
+
120
+ Double-click `start-player-showcase.cmd` to open the standalone Showcase at
121
+ `http://127.0.0.1:4274/`. The runtime keeps one Core store, one browser audio
122
+ element, and one lazily-created Web Audio gain graph above the Compact, Wide,
123
+ Detailed, Podcast, and Mini view switcher. Queue Panel and the fixed Global
124
+ Mini also consume that same state. Switching views therefore does not stop
125
+ playback or create another audio element.
126
+
127
+ The Showcase uses generated music and podcast WAV tone fixtures. They are local,
128
+ copyright-free integration media only and are not production assets. The
129
+ runtime connects UI exclusively through `PlayerState` and `PlayerCommands`;
130
+ the waveform remains visual-only and seeking is owned by the Seek Bar.
131
+
132
+ Manual UX check:
133
+
134
+ 1. Start playback and confirm that Audio Context changes from `uninitialized`
135
+ to `running` only after the first user action.
136
+ 2. Check Play/Pause, Seek, Volume, Previous, and Next in Wide view. On desktop,
137
+ open the compact vertical Volume control from its icon in Compact and Mini;
138
+ narrow mobile layouts intentionally omit it.
139
+ 3. While playing, switch between Compact, Wide, Detailed, Podcast, and Mini and
140
+ confirm that the same track, time, and playback state continue.
141
+ 4. Open Queue Panel, select another item, and check the Podcast queue with
142
+ skip-back/forward controls.
143
+ 5. Toggle Global Mini and confirm that the page bottom inset is reserved only
144
+ while it is visible.
145
+ 6. In browser developer tools, confirm that
146
+ `document.querySelectorAll('audio[data-player-audio="true"]').length` is
147
+ always `1`.
148
+
149
+ ## WP-01 entry points
150
+
151
+ - `@gentomiyano/optimized-web-audio-player/contracts`: immutable state, commands, lifecycle,
152
+ host adapter, config, preset, and debug-controller contracts
153
+ - `@gentomiyano/optimized-web-audio-player/ports`: small media, gain, and persistence ports
154
+ - `@gentomiyano/optimized-web-audio-player/config`: preview defaults, strict validation,
155
+ versioned Config JSON schema v2, and initial state
156
+ - `@gentomiyano/optimized-web-audio-player/media-delivery`: WP-04 URL-free delivery
157
+ descriptors, ephemeral playback-lease and Host Adapter contracts, upload
158
+ evidence validation, and waveform validation
159
+ - `@gentomiyano/optimized-web-audio-player/consumer`: ordered consumer-record to Core Queue
160
+ conversion for already-authorized directly playable media
161
+ - `@gentomiyano/optimized-web-audio-player/react`: the single-audio React Runtime and hook
162
+ - `@gentomiyano/optimized-web-audio-player/ui`: reusable Player views and primitives
163
+ - `@gentomiyano/optimized-web-audio-player/styles.css`: production UI tokens and styles
164
+ - `@gentomiyano/optimized-web-audio-player/testing`: privacy-safe fixtures and mock scenarios
165
+
166
+ The root entry point exports production contracts, ports, config, and the
167
+ provider-neutral media-delivery contract only. Runtime/UI are opt-in subpaths,
168
+ and testing helpers are isolated behind `/testing`. No debug Runtime, Showcase,
169
+ local importer, consumer-specific adapter, or production media URL is included
170
+ in the package root.
171
+
172
+ ## Commands
173
+
174
+ ```text
175
+ pnpm lint
176
+ pnpm test
177
+ pnpm typecheck
178
+ pnpm build
179
+ pnpm showcase:build
180
+ ```
181
+
182
+ ## Parallel ownership after the WP-01 checkpoint
183
+
184
+ - Core: `src/core/**`, `tests/core/**`
185
+ - WP-02B UI ownership explicitly includes `src/ui/**`, `src/icons/**`,
186
+ `src/debug/ui/**`, and `tests/ui/**`.
187
+ - UI may also render its mock views inside the pre-created `showcase/**` shell.
188
+ - Integration-owned files, including contracts, ports, package configuration,
189
+ adapters, and testing fixtures, must not be changed by Core or UI work.
@@ -1,4 +1,4 @@
1
- import { l as PlayerExperienceConfig, u as PlayerState } from '../player-DE9Vqztu.js';
1
+ import { p as PlayerExperienceConfig, y as PlayerState } from '../player-BxNFzRq4.js';
2
2
  import { PlayerExperienceConfigValidationResult } from '../contracts/index.js';
3
3
 
4
4
  declare const PLAYER_EXPERIENCE_CONFIG_SCHEMA_VERSION: 2;
@@ -264,6 +264,7 @@ function createInitialPlayerState(experienceConfig) {
264
264
  shuffleEnabled: resolved.value.initialShuffleEnabled,
265
265
  repeatMode: resolved.value.initialRepeatMode,
266
266
  queue: Object.freeze([]),
267
+ activeContext: null,
267
268
  currentQueueIndex: null,
268
269
  canSeek: false,
269
270
  canGoPrevious: false,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/config/defaults.ts","../../src/config/validation.ts","../../src/config/initial-state.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { PlayerExperienceConfig } from \"../contracts/player\";\n\nexport const PLAYER_EXPERIENCE_CONFIG_SCHEMA_VERSION = 2 as const;\n\nexport const PLAYER_EXPERIENCE_CONFIG_LIMITS = Object.freeze({\n fadeMs: Object.freeze({ min: 0, max: 10_000 }),\n previousRestartThresholdSec: Object.freeze({ min: 0, max: 60 }),\n waveformOpacity: Object.freeze({ min: 0, max: 1 }),\n initialVolume: Object.freeze({ min: 0, max: 1 }),\n podcastSkipSec: Object.freeze({ minExclusive: 0, max: 3_600 }),\n});\n\nexport const PREVIEW_PLAYER_EXPERIENCE_CONFIG: Readonly<PlayerExperienceConfig> =\n Object.freeze({\n playFadeInMs: 300,\n pauseFadeOutMs: 600,\n trackChangeFadeOutMs: 300,\n trackChangeFadeInMs: 300,\n seekFadeMs: 120,\n fadeCurve: \"ease-out\",\n previousRestartThresholdSec: 3,\n seekCommitMode: \"on-release\",\n waveformPlayedStateEnabled: true,\n waveformOpacity: 0.72,\n initialShuffleEnabled: false,\n initialRepeatMode: \"off\",\n initialVolume: 0.8,\n persistMode: \"none\",\n podcastBackSec: 15,\n podcastForwardSec: 30,\n podcastPreviousMode: \"previous-episode\",\n });\n","import type {\n FadeCurve,\n PersistMode,\n PlayerExperienceConfig,\n PodcastPreviousMode,\n RepeatMode,\n SeekCommitMode,\n} from \"../contracts/player\";\nimport type { PlayerExperienceConfigValidationResult } from \"../contracts/config\";\nimport {\n PLAYER_EXPERIENCE_CONFIG_LIMITS,\n PLAYER_EXPERIENCE_CONFIG_SCHEMA_VERSION,\n PREVIEW_PLAYER_EXPERIENCE_CONFIG,\n} from \"./defaults\";\n\nconst CONFIG_KEYS = [\n \"playFadeInMs\",\n \"pauseFadeOutMs\",\n \"trackChangeFadeOutMs\",\n \"trackChangeFadeInMs\",\n \"seekFadeMs\",\n \"fadeCurve\",\n \"previousRestartThresholdSec\",\n \"seekCommitMode\",\n \"waveformPlayedStateEnabled\",\n \"waveformOpacity\",\n \"initialShuffleEnabled\",\n \"initialRepeatMode\",\n \"initialVolume\",\n \"persistMode\",\n \"podcastBackSec\",\n \"podcastForwardSec\",\n \"podcastPreviousMode\",\n] as const satisfies readonly (keyof PlayerExperienceConfig)[];\n\nconst FADE_CURVES = new Set<FadeCurve>([\n \"linear\",\n \"ease-in\",\n \"ease-out\",\n \"equal-power\",\n]);\nconst SEEK_COMMIT_MODES = new Set<SeekCommitMode>([\"live\", \"on-release\"]);\nconst REPEAT_MODES = new Set<RepeatMode>([\"off\", \"all\", \"one\"]);\nconst PERSIST_MODES = new Set<PersistMode>([\"none\", \"session\", \"local\"]);\nconst PODCAST_PREVIOUS_MODES = new Set<PodcastPreviousMode>([\n \"previous-episode\",\n \"restart-episode\",\n]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction validateFiniteRange(\n value: unknown,\n path: string,\n min: number,\n max: number,\n errors: string[],\n): value is number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value) ||\n value < min ||\n value > max\n ) {\n errors.push(\n `${path} must be a finite number between ${String(min)} and ${String(max)}.`,\n );\n return false;\n }\n return true;\n}\n\nfunction validateBoolean(\n value: unknown,\n path: string,\n errors: string[],\n): value is boolean {\n if (typeof value !== \"boolean\") {\n errors.push(`${path} must be a boolean.`);\n return false;\n }\n return true;\n}\n\nfunction validateConfigRecord(\n value: unknown,\n options: { readonly partial: boolean },\n): PlayerExperienceConfigValidationResult {\n if (!isRecord(value)) {\n return { ok: false, errors: [\"config must be an object.\"] };\n }\n\n const errors: string[] = [];\n const allowedKeys = new Set<string>(CONFIG_KEYS);\n\n for (const key of Object.keys(value)) {\n if (!allowedKeys.has(key)) {\n errors.push(`config.${key} is not supported.`);\n }\n }\n\n if (!options.partial) {\n for (const key of CONFIG_KEYS) {\n if (!Object.hasOwn(value, key)) {\n errors.push(`config.${key} is required.`);\n }\n }\n }\n\n const has = (key: keyof PlayerExperienceConfig): boolean =>\n Object.hasOwn(value, key);\n const fadeLimits = PLAYER_EXPERIENCE_CONFIG_LIMITS.fadeMs;\n\n for (const key of [\n \"playFadeInMs\",\n \"pauseFadeOutMs\",\n \"trackChangeFadeOutMs\",\n \"trackChangeFadeInMs\",\n \"seekFadeMs\",\n ] as const) {\n if (has(key)) {\n validateFiniteRange(value[key], `config.${key}`, fadeLimits.min, fadeLimits.max, errors);\n }\n }\n\n const thresholdLimits =\n PLAYER_EXPERIENCE_CONFIG_LIMITS.previousRestartThresholdSec;\n if (has(\"previousRestartThresholdSec\")) {\n validateFiniteRange(\n value.previousRestartThresholdSec,\n \"config.previousRestartThresholdSec\",\n thresholdLimits.min,\n thresholdLimits.max,\n errors,\n );\n }\n\n const volumeLimits = PLAYER_EXPERIENCE_CONFIG_LIMITS.initialVolume;\n if (has(\"initialVolume\")) {\n validateFiniteRange(\n value.initialVolume,\n \"config.initialVolume\",\n volumeLimits.min,\n volumeLimits.max,\n errors,\n );\n }\n\n const waveformOpacityLimits =\n PLAYER_EXPERIENCE_CONFIG_LIMITS.waveformOpacity;\n if (has(\"waveformOpacity\")) {\n validateFiniteRange(\n value.waveformOpacity,\n \"config.waveformOpacity\",\n waveformOpacityLimits.min,\n waveformOpacityLimits.max,\n errors,\n );\n }\n\n for (const key of [\n \"waveformPlayedStateEnabled\",\n \"initialShuffleEnabled\",\n ] as const) {\n if (has(key)) {\n validateBoolean(value[key], `config.${key}`, errors);\n }\n }\n\n const skipLimits = PLAYER_EXPERIENCE_CONFIG_LIMITS.podcastSkipSec;\n for (const key of [\"podcastBackSec\", \"podcastForwardSec\"] as const) {\n if (has(key)) {\n const candidate = value[key];\n const valid = validateFiniteRange(\n candidate,\n `config.${key}`,\n skipLimits.minExclusive,\n skipLimits.max,\n errors,\n );\n if (valid && candidate <= skipLimits.minExclusive) {\n errors.push(`config.${key} must be greater than 0.`);\n }\n }\n }\n\n if (has(\"fadeCurve\") && !FADE_CURVES.has(value.fadeCurve as FadeCurve)) {\n errors.push(\"config.fadeCurve is not supported.\");\n }\n if (\n has(\"seekCommitMode\") &&\n !SEEK_COMMIT_MODES.has(value.seekCommitMode as SeekCommitMode)\n ) {\n errors.push(\"config.seekCommitMode is not supported.\");\n }\n if (\n has(\"initialRepeatMode\") &&\n !REPEAT_MODES.has(value.initialRepeatMode as RepeatMode)\n ) {\n errors.push(\"config.initialRepeatMode is not supported.\");\n }\n if (has(\"persistMode\") && !PERSIST_MODES.has(value.persistMode as PersistMode)) {\n errors.push(\"config.persistMode is not supported.\");\n }\n if (\n has(\"podcastPreviousMode\") &&\n !PODCAST_PREVIOUS_MODES.has(\n value.podcastPreviousMode as PodcastPreviousMode,\n )\n ) {\n errors.push(\"config.podcastPreviousMode is not supported.\");\n }\n\n if (errors.length > 0) {\n return { ok: false, errors: Object.freeze(errors) };\n }\n\n const merged = options.partial\n ? { ...PREVIEW_PLAYER_EXPERIENCE_CONFIG, ...value }\n : value;\n\n return {\n ok: true,\n errors: [],\n value: Object.freeze({ ...merged }) as Readonly<PlayerExperienceConfig>,\n };\n}\n\nexport function validatePlayerExperienceConfig(\n value: unknown,\n): PlayerExperienceConfigValidationResult {\n return validateConfigRecord(value, { partial: false });\n}\n\nexport function validatePlayerExperienceConfigOverride(\n value: unknown,\n): PlayerExperienceConfigValidationResult {\n return validateConfigRecord(value, { partial: true });\n}\n\nexport function resolvePlayerExperienceConfig(\n override?: Partial<PlayerExperienceConfig>,\n): PlayerExperienceConfigValidationResult {\n return validatePlayerExperienceConfigOverride(override ?? {});\n}\n\nexport function parsePlayerExperienceConfigJson(\n json: string,\n): PlayerExperienceConfigValidationResult {\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(json) as unknown;\n } catch {\n return { ok: false, errors: [\"Config JSON is not valid JSON.\"] };\n }\n\n if (!isRecord(parsed)) {\n return { ok: false, errors: [\"Config JSON envelope must be an object.\"] };\n }\n\n const envelopeKeys = Object.keys(parsed);\n const errors: string[] = [];\n\n for (const key of envelopeKeys) {\n if (key !== \"schemaVersion\" && key !== \"config\") {\n errors.push(`envelope.${key} is not supported.`);\n }\n }\n if (!Object.hasOwn(parsed, \"schemaVersion\")) {\n errors.push(\"envelope.schemaVersion is required.\");\n } else if (\n parsed.schemaVersion !== PLAYER_EXPERIENCE_CONFIG_SCHEMA_VERSION\n ) {\n errors.push(\"envelope.schemaVersion is not supported.\");\n }\n if (!Object.hasOwn(parsed, \"config\")) {\n errors.push(\"envelope.config is required.\");\n }\n\n if (errors.length > 0) {\n return { ok: false, errors: Object.freeze(errors) };\n }\n\n return validatePlayerExperienceConfig(parsed.config);\n}\n\nexport function serializePlayerExperienceConfig(\n config: PlayerExperienceConfig,\n): string {\n const result = validatePlayerExperienceConfig(config);\n\n if (!result.ok) {\n throw new TypeError(result.errors.join(\" \"));\n }\n\n return JSON.stringify(\n {\n schemaVersion: PLAYER_EXPERIENCE_CONFIG_SCHEMA_VERSION,\n config: result.value,\n },\n null,\n 2,\n );\n}\n","import type {\n PlayerExperienceConfig,\n PlayerState,\n} from \"../contracts/player\";\nimport { resolvePlayerExperienceConfig } from \"./validation\";\n\nexport function createInitialPlayerState(\n experienceConfig?: Partial<PlayerExperienceConfig>,\n): Readonly<PlayerState> {\n const resolved = resolvePlayerExperienceConfig(experienceConfig);\n\n if (!resolved.ok) {\n throw new TypeError(resolved.errors.join(\" \"));\n }\n\n return Object.freeze({\n currentItem: null,\n currentItemId: null,\n currentQueueItemId: null,\n mediaKind: null,\n status: \"idle\",\n currentTimeSec: 0,\n durationSec: null,\n volume: resolved.value.initialVolume,\n muted: false,\n shuffleEnabled: resolved.value.initialShuffleEnabled,\n repeatMode: resolved.value.initialRepeatMode,\n queue: Object.freeze([]),\n currentQueueIndex: null,\n canSeek: false,\n canGoPrevious: false,\n canGoNext: false,\n needsUserGesture: false,\n lastTransitionCause: null,\n error: null,\n experienceConfig: resolved.value,\n });\n}\n"]}
1
+ {"version":3,"sources":["../../src/config/defaults.ts","../../src/config/validation.ts","../../src/config/initial-state.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { PlayerExperienceConfig } from \"../contracts/player\";\r\n\r\nexport const PLAYER_EXPERIENCE_CONFIG_SCHEMA_VERSION = 2 as const;\r\n\r\nexport const PLAYER_EXPERIENCE_CONFIG_LIMITS = Object.freeze({\r\n fadeMs: Object.freeze({ min: 0, max: 10_000 }),\r\n previousRestartThresholdSec: Object.freeze({ min: 0, max: 60 }),\r\n waveformOpacity: Object.freeze({ min: 0, max: 1 }),\r\n initialVolume: Object.freeze({ min: 0, max: 1 }),\r\n podcastSkipSec: Object.freeze({ minExclusive: 0, max: 3_600 }),\r\n});\r\n\r\nexport const PREVIEW_PLAYER_EXPERIENCE_CONFIG: Readonly<PlayerExperienceConfig> =\r\n Object.freeze({\r\n playFadeInMs: 300,\r\n pauseFadeOutMs: 600,\r\n trackChangeFadeOutMs: 300,\r\n trackChangeFadeInMs: 300,\r\n seekFadeMs: 120,\r\n fadeCurve: \"ease-out\",\r\n previousRestartThresholdSec: 3,\r\n seekCommitMode: \"on-release\",\r\n waveformPlayedStateEnabled: true,\r\n waveformOpacity: 0.72,\r\n initialShuffleEnabled: false,\r\n initialRepeatMode: \"off\",\r\n initialVolume: 0.8,\r\n persistMode: \"none\",\r\n podcastBackSec: 15,\r\n podcastForwardSec: 30,\r\n podcastPreviousMode: \"previous-episode\",\r\n });\r\n","import type {\r\n FadeCurve,\r\n PersistMode,\r\n PlayerExperienceConfig,\r\n PodcastPreviousMode,\r\n RepeatMode,\r\n SeekCommitMode,\r\n} from \"../contracts/player\";\r\nimport type { PlayerExperienceConfigValidationResult } from \"../contracts/config\";\r\nimport {\r\n PLAYER_EXPERIENCE_CONFIG_LIMITS,\r\n PLAYER_EXPERIENCE_CONFIG_SCHEMA_VERSION,\r\n PREVIEW_PLAYER_EXPERIENCE_CONFIG,\r\n} from \"./defaults\";\r\n\r\nconst CONFIG_KEYS = [\r\n \"playFadeInMs\",\r\n \"pauseFadeOutMs\",\r\n \"trackChangeFadeOutMs\",\r\n \"trackChangeFadeInMs\",\r\n \"seekFadeMs\",\r\n \"fadeCurve\",\r\n \"previousRestartThresholdSec\",\r\n \"seekCommitMode\",\r\n \"waveformPlayedStateEnabled\",\r\n \"waveformOpacity\",\r\n \"initialShuffleEnabled\",\r\n \"initialRepeatMode\",\r\n \"initialVolume\",\r\n \"persistMode\",\r\n \"podcastBackSec\",\r\n \"podcastForwardSec\",\r\n \"podcastPreviousMode\",\r\n] as const satisfies readonly (keyof PlayerExperienceConfig)[];\r\n\r\nconst FADE_CURVES = new Set<FadeCurve>([\r\n \"linear\",\r\n \"ease-in\",\r\n \"ease-out\",\r\n \"equal-power\",\r\n]);\r\nconst SEEK_COMMIT_MODES = new Set<SeekCommitMode>([\"live\", \"on-release\"]);\r\nconst REPEAT_MODES = new Set<RepeatMode>([\"off\", \"all\", \"one\"]);\r\nconst PERSIST_MODES = new Set<PersistMode>([\"none\", \"session\", \"local\"]);\r\nconst PODCAST_PREVIOUS_MODES = new Set<PodcastPreviousMode>([\r\n \"previous-episode\",\r\n \"restart-episode\",\r\n]);\r\n\r\nfunction isRecord(value: unknown): value is Record<string, unknown> {\r\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\r\n}\r\n\r\nfunction validateFiniteRange(\r\n value: unknown,\r\n path: string,\r\n min: number,\r\n max: number,\r\n errors: string[],\r\n): value is number {\r\n if (\r\n typeof value !== \"number\" ||\r\n !Number.isFinite(value) ||\r\n value < min ||\r\n value > max\r\n ) {\r\n errors.push(\r\n `${path} must be a finite number between ${String(min)} and ${String(max)}.`,\r\n );\r\n return false;\r\n }\r\n return true;\r\n}\r\n\r\nfunction validateBoolean(\r\n value: unknown,\r\n path: string,\r\n errors: string[],\r\n): value is boolean {\r\n if (typeof value !== \"boolean\") {\r\n errors.push(`${path} must be a boolean.`);\r\n return false;\r\n }\r\n return true;\r\n}\r\n\r\nfunction validateConfigRecord(\r\n value: unknown,\r\n options: { readonly partial: boolean },\r\n): PlayerExperienceConfigValidationResult {\r\n if (!isRecord(value)) {\r\n return { ok: false, errors: [\"config must be an object.\"] };\r\n }\r\n\r\n const errors: string[] = [];\r\n const allowedKeys = new Set<string>(CONFIG_KEYS);\r\n\r\n for (const key of Object.keys(value)) {\r\n if (!allowedKeys.has(key)) {\r\n errors.push(`config.${key} is not supported.`);\r\n }\r\n }\r\n\r\n if (!options.partial) {\r\n for (const key of CONFIG_KEYS) {\r\n if (!Object.hasOwn(value, key)) {\r\n errors.push(`config.${key} is required.`);\r\n }\r\n }\r\n }\r\n\r\n const has = (key: keyof PlayerExperienceConfig): boolean =>\r\n Object.hasOwn(value, key);\r\n const fadeLimits = PLAYER_EXPERIENCE_CONFIG_LIMITS.fadeMs;\r\n\r\n for (const key of [\r\n \"playFadeInMs\",\r\n \"pauseFadeOutMs\",\r\n \"trackChangeFadeOutMs\",\r\n \"trackChangeFadeInMs\",\r\n \"seekFadeMs\",\r\n ] as const) {\r\n if (has(key)) {\r\n validateFiniteRange(value[key], `config.${key}`, fadeLimits.min, fadeLimits.max, errors);\r\n }\r\n }\r\n\r\n const thresholdLimits =\r\n PLAYER_EXPERIENCE_CONFIG_LIMITS.previousRestartThresholdSec;\r\n if (has(\"previousRestartThresholdSec\")) {\r\n validateFiniteRange(\r\n value.previousRestartThresholdSec,\r\n \"config.previousRestartThresholdSec\",\r\n thresholdLimits.min,\r\n thresholdLimits.max,\r\n errors,\r\n );\r\n }\r\n\r\n const volumeLimits = PLAYER_EXPERIENCE_CONFIG_LIMITS.initialVolume;\r\n if (has(\"initialVolume\")) {\r\n validateFiniteRange(\r\n value.initialVolume,\r\n \"config.initialVolume\",\r\n volumeLimits.min,\r\n volumeLimits.max,\r\n errors,\r\n );\r\n }\r\n\r\n const waveformOpacityLimits =\r\n PLAYER_EXPERIENCE_CONFIG_LIMITS.waveformOpacity;\r\n if (has(\"waveformOpacity\")) {\r\n validateFiniteRange(\r\n value.waveformOpacity,\r\n \"config.waveformOpacity\",\r\n waveformOpacityLimits.min,\r\n waveformOpacityLimits.max,\r\n errors,\r\n );\r\n }\r\n\r\n for (const key of [\r\n \"waveformPlayedStateEnabled\",\r\n \"initialShuffleEnabled\",\r\n ] as const) {\r\n if (has(key)) {\r\n validateBoolean(value[key], `config.${key}`, errors);\r\n }\r\n }\r\n\r\n const skipLimits = PLAYER_EXPERIENCE_CONFIG_LIMITS.podcastSkipSec;\r\n for (const key of [\"podcastBackSec\", \"podcastForwardSec\"] as const) {\r\n if (has(key)) {\r\n const candidate = value[key];\r\n const valid = validateFiniteRange(\r\n candidate,\r\n `config.${key}`,\r\n skipLimits.minExclusive,\r\n skipLimits.max,\r\n errors,\r\n );\r\n if (valid && candidate <= skipLimits.minExclusive) {\r\n errors.push(`config.${key} must be greater than 0.`);\r\n }\r\n }\r\n }\r\n\r\n if (has(\"fadeCurve\") && !FADE_CURVES.has(value.fadeCurve as FadeCurve)) {\r\n errors.push(\"config.fadeCurve is not supported.\");\r\n }\r\n if (\r\n has(\"seekCommitMode\") &&\r\n !SEEK_COMMIT_MODES.has(value.seekCommitMode as SeekCommitMode)\r\n ) {\r\n errors.push(\"config.seekCommitMode is not supported.\");\r\n }\r\n if (\r\n has(\"initialRepeatMode\") &&\r\n !REPEAT_MODES.has(value.initialRepeatMode as RepeatMode)\r\n ) {\r\n errors.push(\"config.initialRepeatMode is not supported.\");\r\n }\r\n if (has(\"persistMode\") && !PERSIST_MODES.has(value.persistMode as PersistMode)) {\r\n errors.push(\"config.persistMode is not supported.\");\r\n }\r\n if (\r\n has(\"podcastPreviousMode\") &&\r\n !PODCAST_PREVIOUS_MODES.has(\r\n value.podcastPreviousMode as PodcastPreviousMode,\r\n )\r\n ) {\r\n errors.push(\"config.podcastPreviousMode is not supported.\");\r\n }\r\n\r\n if (errors.length > 0) {\r\n return { ok: false, errors: Object.freeze(errors) };\r\n }\r\n\r\n const merged = options.partial\r\n ? { ...PREVIEW_PLAYER_EXPERIENCE_CONFIG, ...value }\r\n : value;\r\n\r\n return {\r\n ok: true,\r\n errors: [],\r\n value: Object.freeze({ ...merged }) as Readonly<PlayerExperienceConfig>,\r\n };\r\n}\r\n\r\nexport function validatePlayerExperienceConfig(\r\n value: unknown,\r\n): PlayerExperienceConfigValidationResult {\r\n return validateConfigRecord(value, { partial: false });\r\n}\r\n\r\nexport function validatePlayerExperienceConfigOverride(\r\n value: unknown,\r\n): PlayerExperienceConfigValidationResult {\r\n return validateConfigRecord(value, { partial: true });\r\n}\r\n\r\nexport function resolvePlayerExperienceConfig(\r\n override?: Partial<PlayerExperienceConfig>,\r\n): PlayerExperienceConfigValidationResult {\r\n return validatePlayerExperienceConfigOverride(override ?? {});\r\n}\r\n\r\nexport function parsePlayerExperienceConfigJson(\r\n json: string,\r\n): PlayerExperienceConfigValidationResult {\r\n let parsed: unknown;\r\n\r\n try {\r\n parsed = JSON.parse(json) as unknown;\r\n } catch {\r\n return { ok: false, errors: [\"Config JSON is not valid JSON.\"] };\r\n }\r\n\r\n if (!isRecord(parsed)) {\r\n return { ok: false, errors: [\"Config JSON envelope must be an object.\"] };\r\n }\r\n\r\n const envelopeKeys = Object.keys(parsed);\r\n const errors: string[] = [];\r\n\r\n for (const key of envelopeKeys) {\r\n if (key !== \"schemaVersion\" && key !== \"config\") {\r\n errors.push(`envelope.${key} is not supported.`);\r\n }\r\n }\r\n if (!Object.hasOwn(parsed, \"schemaVersion\")) {\r\n errors.push(\"envelope.schemaVersion is required.\");\r\n } else if (\r\n parsed.schemaVersion !== PLAYER_EXPERIENCE_CONFIG_SCHEMA_VERSION\r\n ) {\r\n errors.push(\"envelope.schemaVersion is not supported.\");\r\n }\r\n if (!Object.hasOwn(parsed, \"config\")) {\r\n errors.push(\"envelope.config is required.\");\r\n }\r\n\r\n if (errors.length > 0) {\r\n return { ok: false, errors: Object.freeze(errors) };\r\n }\r\n\r\n return validatePlayerExperienceConfig(parsed.config);\r\n}\r\n\r\nexport function serializePlayerExperienceConfig(\r\n config: PlayerExperienceConfig,\r\n): string {\r\n const result = validatePlayerExperienceConfig(config);\r\n\r\n if (!result.ok) {\r\n throw new TypeError(result.errors.join(\" \"));\r\n }\r\n\r\n return JSON.stringify(\r\n {\r\n schemaVersion: PLAYER_EXPERIENCE_CONFIG_SCHEMA_VERSION,\r\n config: result.value,\r\n },\r\n null,\r\n 2,\r\n );\r\n}\r\n","import type {\r\n PlayerExperienceConfig,\r\n PlayerState,\r\n} from \"../contracts/player\";\r\nimport { resolvePlayerExperienceConfig } from \"./validation\";\r\n\r\nexport function createInitialPlayerState(\r\n experienceConfig?: Partial<PlayerExperienceConfig>,\r\n): Readonly<PlayerState> {\r\n const resolved = resolvePlayerExperienceConfig(experienceConfig);\r\n\r\n if (!resolved.ok) {\r\n throw new TypeError(resolved.errors.join(\" \"));\r\n }\r\n\r\n return Object.freeze({\r\n currentItem: null,\r\n currentItemId: null,\r\n currentQueueItemId: null,\r\n mediaKind: null,\r\n status: \"idle\",\r\n currentTimeSec: 0,\r\n durationSec: null,\r\n volume: resolved.value.initialVolume,\r\n muted: false,\r\n shuffleEnabled: resolved.value.initialShuffleEnabled,\r\n repeatMode: resolved.value.initialRepeatMode,\r\n queue: Object.freeze([]),\r\n activeContext: null,\r\n currentQueueIndex: null,\r\n canSeek: false,\r\n canGoPrevious: false,\r\n canGoNext: false,\r\n needsUserGesture: false,\r\n lastTransitionCause: null,\r\n error: null,\r\n experienceConfig: resolved.value,\r\n });\r\n}\r\n"]}
@@ -1,4 +1,4 @@
1
- import { Q as QueueItem, q as PlayerMediaAdapter } from '../player-DE9Vqztu.js';
1
+ import { Q as QueueItem, u as PlayerMediaAdapter } from '../player-BxNFzRq4.js';
2
2
 
3
3
  interface PlayerQueueCandidate<TSource> {
4
4
  /** Host-owned stable identity for this occurrence in a queue context. */
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/consumer/queue.ts"],"names":[],"mappings":";AAiCO,SAAS,gBAAA,CACd,YACA,OAAA,EACwB;AACxB,EAAA,MAAM,QAAqB,EAAC;AAC5B,EAAA,MAAM,WAAmC,EAAC;AAC1C,EAAA,MAAM,gBAAA,uBAAuB,GAAA,EAAY;AAEzC,EAAA,UAAA,CAAW,OAAA,CAAQ,CAAC,SAAA,EAAW,cAAA,KAAmB;AAChD,IAAA,IAAI,gBAAA,CAAiB,GAAA,CAAI,SAAA,CAAU,WAAW,CAAA,EAAG;AAC/C,MAAA,QAAA,CAAS,IAAA,CAAK,OAAO,MAAA,CAAO;AAAA,QAC1B,cAAA;AAAA,QACA,aAAa,SAAA,CAAU,WAAA;AAAA,QACvB,MAAA,EAAQ;AAAA,OACT,CAAC,CAAA;AACF,MAAA;AAAA,IACF;AACA,IAAA,gBAAA,CAAiB,GAAA,CAAI,UAAU,WAAW,CAAA;AAE1C,IAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,iBAAA,CAAkB,SAAA,CAAU,MAAM,CAAA;AACvD,IAAA,IAAI,SAAS,IAAA,EAAM;AACjB,MAAA,QAAA,CAAS,IAAA,CAAK,OAAO,MAAA,CAAO;AAAA,QAC1B,cAAA;AAAA,QACA,aAAa,SAAA,CAAU,WAAA;AAAA,QACvB,MAAA,EAAQ;AAAA,OACT,CAAC,CAAA;AACF,MAAA;AAAA,IACF;AAEA,IAAA,KAAA,CAAM,IAAA,CAAK,OAAO,MAAA,CAAO;AAAA,MACvB,aAAa,SAAA,CAAU,WAAA;AAAA,MACvB;AAAA,KACD,CAAC,CAAA;AAAA,EACJ,CAAC,CAAA;AAED,EAAA,OAAO,OAAO,MAAA,CAAO;AAAA,IACnB,KAAA,EAAO,MAAA,CAAO,MAAA,CAAO,KAAK,CAAA;AAAA,IAC1B,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,QAAQ;AAAA,GACjC,CAAA;AACH","file":"index.js","sourcesContent":["import type {\n PlayerMediaAdapter,\n QueueItem,\n} from \"../contracts/player\";\n\nexport interface PlayerQueueCandidate<TSource> {\n /** Host-owned stable identity for this occurrence in a queue context. */\n readonly queueItemId: string;\n readonly source: TSource;\n}\n\nexport type PlayerQueueRejectionReason =\n | \"not-playable\"\n | \"duplicate-queue-item-id\";\n\nexport interface PlayerQueueRejection {\n readonly candidateIndex: number;\n readonly queueItemId: string;\n readonly reason: PlayerQueueRejectionReason;\n}\n\nexport interface PlayerQueueBuildResult {\n readonly queue: readonly QueueItem[];\n readonly rejected: readonly PlayerQueueRejection[];\n}\n\n/**\n * Builds a Core queue from consumer records that already passed Host access\n * policy. Candidate order is preserved. The same media item may occur more\n * than once when each occurrence has a unique queueItemId.\n *\n * The result retains only Player items, never the consumer record itself.\n */\nexport function buildPlayerQueue<TSource>(\n candidates: readonly PlayerQueueCandidate<TSource>[],\n adapter: PlayerMediaAdapter<TSource>,\n): PlayerQueueBuildResult {\n const queue: QueueItem[] = [];\n const rejected: PlayerQueueRejection[] = [];\n const seenQueueItemIds = new Set<string>();\n\n candidates.forEach((candidate, candidateIndex) => {\n if (seenQueueItemIds.has(candidate.queueItemId)) {\n rejected.push(Object.freeze({\n candidateIndex,\n queueItemId: candidate.queueItemId,\n reason: \"duplicate-queue-item-id\",\n }));\n return;\n }\n seenQueueItemIds.add(candidate.queueItemId);\n\n const item = adapter.toPlayerMediaItem(candidate.source);\n if (item === null) {\n rejected.push(Object.freeze({\n candidateIndex,\n queueItemId: candidate.queueItemId,\n reason: \"not-playable\",\n }));\n return;\n }\n\n queue.push(Object.freeze({\n queueItemId: candidate.queueItemId,\n item,\n }));\n });\n\n return Object.freeze({\n queue: Object.freeze(queue),\n rejected: Object.freeze(rejected),\n });\n}\n"]}
1
+ {"version":3,"sources":["../../src/consumer/queue.ts"],"names":[],"mappings":";AAiCO,SAAS,gBAAA,CACd,YACA,OAAA,EACwB;AACxB,EAAA,MAAM,QAAqB,EAAC;AAC5B,EAAA,MAAM,WAAmC,EAAC;AAC1C,EAAA,MAAM,gBAAA,uBAAuB,GAAA,EAAY;AAEzC,EAAA,UAAA,CAAW,OAAA,CAAQ,CAAC,SAAA,EAAW,cAAA,KAAmB;AAChD,IAAA,IAAI,gBAAA,CAAiB,GAAA,CAAI,SAAA,CAAU,WAAW,CAAA,EAAG;AAC/C,MAAA,QAAA,CAAS,IAAA,CAAK,OAAO,MAAA,CAAO;AAAA,QAC1B,cAAA;AAAA,QACA,aAAa,SAAA,CAAU,WAAA;AAAA,QACvB,MAAA,EAAQ;AAAA,OACT,CAAC,CAAA;AACF,MAAA;AAAA,IACF;AACA,IAAA,gBAAA,CAAiB,GAAA,CAAI,UAAU,WAAW,CAAA;AAE1C,IAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,iBAAA,CAAkB,SAAA,CAAU,MAAM,CAAA;AACvD,IAAA,IAAI,SAAS,IAAA,EAAM;AACjB,MAAA,QAAA,CAAS,IAAA,CAAK,OAAO,MAAA,CAAO;AAAA,QAC1B,cAAA;AAAA,QACA,aAAa,SAAA,CAAU,WAAA;AAAA,QACvB,MAAA,EAAQ;AAAA,OACT,CAAC,CAAA;AACF,MAAA;AAAA,IACF;AAEA,IAAA,KAAA,CAAM,IAAA,CAAK,OAAO,MAAA,CAAO;AAAA,MACvB,aAAa,SAAA,CAAU,WAAA;AAAA,MACvB;AAAA,KACD,CAAC,CAAA;AAAA,EACJ,CAAC,CAAA;AAED,EAAA,OAAO,OAAO,MAAA,CAAO;AAAA,IACnB,KAAA,EAAO,MAAA,CAAO,MAAA,CAAO,KAAK,CAAA;AAAA,IAC1B,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,QAAQ;AAAA,GACjC,CAAA;AACH","file":"index.js","sourcesContent":["import type {\r\n PlayerMediaAdapter,\r\n QueueItem,\r\n} from \"../contracts/player\";\r\n\r\nexport interface PlayerQueueCandidate<TSource> {\r\n /** Host-owned stable identity for this occurrence in a queue context. */\r\n readonly queueItemId: string;\r\n readonly source: TSource;\r\n}\r\n\r\nexport type PlayerQueueRejectionReason =\r\n | \"not-playable\"\r\n | \"duplicate-queue-item-id\";\r\n\r\nexport interface PlayerQueueRejection {\r\n readonly candidateIndex: number;\r\n readonly queueItemId: string;\r\n readonly reason: PlayerQueueRejectionReason;\r\n}\r\n\r\nexport interface PlayerQueueBuildResult {\r\n readonly queue: readonly QueueItem[];\r\n readonly rejected: readonly PlayerQueueRejection[];\r\n}\r\n\r\n/**\r\n * Builds a Core queue from consumer records that already passed Host access\r\n * policy. Candidate order is preserved. The same media item may occur more\r\n * than once when each occurrence has a unique queueItemId.\r\n *\r\n * The result retains only Player items, never the consumer record itself.\r\n */\r\nexport function buildPlayerQueue<TSource>(\r\n candidates: readonly PlayerQueueCandidate<TSource>[],\r\n adapter: PlayerMediaAdapter<TSource>,\r\n): PlayerQueueBuildResult {\r\n const queue: QueueItem[] = [];\r\n const rejected: PlayerQueueRejection[] = [];\r\n const seenQueueItemIds = new Set<string>();\r\n\r\n candidates.forEach((candidate, candidateIndex) => {\r\n if (seenQueueItemIds.has(candidate.queueItemId)) {\r\n rejected.push(Object.freeze({\r\n candidateIndex,\r\n queueItemId: candidate.queueItemId,\r\n reason: \"duplicate-queue-item-id\",\r\n }));\r\n return;\r\n }\r\n seenQueueItemIds.add(candidate.queueItemId);\r\n\r\n const item = adapter.toPlayerMediaItem(candidate.source);\r\n if (item === null) {\r\n rejected.push(Object.freeze({\r\n candidateIndex,\r\n queueItemId: candidate.queueItemId,\r\n reason: \"not-playable\",\r\n }));\r\n return;\r\n }\r\n\r\n queue.push(Object.freeze({\r\n queueItemId: candidate.queueItemId,\r\n item,\r\n }));\r\n });\r\n\r\n return Object.freeze({\r\n queue: Object.freeze(queue),\r\n rejected: Object.freeze(rejected),\r\n });\r\n}\r\n"]}
@@ -1,5 +1,5 @@
1
- import { l as PlayerExperienceConfig } from '../player-DE9Vqztu.js';
2
- export { F as FadeCurve, M as MediaKind, P as PauseOptions, a as PersistMode, b as PlaybackStatus, c as PlaybackTransitionCause, d as PlayerAppearance, e as PlayerCommandResult, f as PlayerCommands, g as PlayerConfigImportResult, h as PlayerDebugController, i as PlayerDebugSnapshot, j as PlayerError, k as PlayerErrorCode, m as PlayerExperienceConfigEnvelope, n as PlayerExperienceConfigStore, o as PlayerExperiencePreset, p as PlayerHostConfig, q as PlayerMediaAdapter, r as PlayerMediaItem, s as PlayerMediaSource, t as PlayerMediaSyncReason, u as PlayerState, v as PlayerStore, w as PlayerViewProps, x as PlayerWaveform, y as PodcastPreviousMode, Q as QueueItem, R as RepeatMode, S as SeekCommitMode, z as SetQueueOptions, A as StopOptions } from '../player-DE9Vqztu.js';
1
+ import { p as PlayerExperienceConfig } from '../player-BxNFzRq4.js';
2
+ export { A as ActivatePlaybackContextOptions, B as BoundPlayerCommands, a as BoundPlayerViewState, F as FadeCurve, M as MediaKind, P as PauseOptions, b as PersistMode, c as PlaybackContextInput, d as PlaybackContextMode, e as PlaybackContextState, f as PlaybackStatus, g as PlaybackTransitionCause, h as PlayerAppearance, i as PlayerCommandResult, j as PlayerCommands, k as PlayerConfigImportResult, l as PlayerDebugController, m as PlayerDebugSnapshot, n as PlayerError, o as PlayerErrorCode, q as PlayerExperienceConfigEnvelope, r as PlayerExperienceConfigStore, s as PlayerExperiencePreset, t as PlayerHostConfig, u as PlayerMediaAdapter, v as PlayerMediaItem, w as PlayerMediaSource, x as PlayerMediaSyncReason, y as PlayerState, z as PlayerStore, C as PlayerViewProps, D as PlayerWaveform, E as PodcastPreviousMode, Q as QueueItem, R as RepeatMode, S as SeekCommitMode, G as SetQueueOptions, H as StopOptions } from '../player-BxNFzRq4.js';
3
3
 
4
4
  interface ConfigValidationSuccess {
5
5
  readonly ok: true;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { ConfigValidationFailure, ConfigValidationSuccess, PlayerExperienceConfigValidationResult } from './contracts/index.js';
2
- export { F as FadeCurve, M as MediaKind, P as PauseOptions, a as PersistMode, b as PlaybackStatus, c as PlaybackTransitionCause, d as PlayerAppearance, e as PlayerCommandResult, f as PlayerCommands, g as PlayerConfigImportResult, h as PlayerDebugController, i as PlayerDebugSnapshot, j as PlayerError, k as PlayerErrorCode, l as PlayerExperienceConfig, m as PlayerExperienceConfigEnvelope, n as PlayerExperienceConfigStore, o as PlayerExperiencePreset, p as PlayerHostConfig, q as PlayerMediaAdapter, r as PlayerMediaItem, s as PlayerMediaSource, t as PlayerMediaSyncReason, u as PlayerState, v as PlayerStore, w as PlayerViewProps, x as PlayerWaveform, y as PodcastPreviousMode, Q as QueueItem, R as RepeatMode, S as SeekCommitMode, z as SetQueueOptions, A as StopOptions } from './player-DE9Vqztu.js';
2
+ export { A as ActivatePlaybackContextOptions, B as BoundPlayerCommands, a as BoundPlayerViewState, F as FadeCurve, M as MediaKind, P as PauseOptions, b as PersistMode, c as PlaybackContextInput, d as PlaybackContextMode, e as PlaybackContextState, f as PlaybackStatus, g as PlaybackTransitionCause, h as PlayerAppearance, i as PlayerCommandResult, j as PlayerCommands, k as PlayerConfigImportResult, l as PlayerDebugController, m as PlayerDebugSnapshot, n as PlayerError, o as PlayerErrorCode, p as PlayerExperienceConfig, q as PlayerExperienceConfigEnvelope, r as PlayerExperienceConfigStore, s as PlayerExperiencePreset, t as PlayerHostConfig, u as PlayerMediaAdapter, v as PlayerMediaItem, w as PlayerMediaSource, x as PlayerMediaSyncReason, y as PlayerState, z as PlayerStore, C as PlayerViewProps, D as PlayerWaveform, E as PodcastPreviousMode, Q as QueueItem, R as RepeatMode, S as SeekCommitMode, G as SetQueueOptions, H as StopOptions } from './player-BxNFzRq4.js';
3
3
  export { PLAYER_EXPERIENCE_CONFIG_LIMITS, PLAYER_EXPERIENCE_CONFIG_SCHEMA_VERSION, PREVIEW_PLAYER_EXPERIENCE_CONFIG, createInitialPlayerState, parsePlayerExperienceConfigJson, resolvePlayerExperienceConfig, serializePlayerExperienceConfig, validatePlayerExperienceConfig, validatePlayerExperienceConfigOverride } from './config/index.js';
4
4
  export { AudioSampleEncoding, FutureUploadFormat, MEDIA_DELIVERY_CONTRACT_VERSION, MediaAccessClass, MediaDeliveryDescriptor, MediaDeliverySourceReference, MediaLoudnessMetadata, MediaSourceRole, MediaStorageReferences, MediaUploadEvidence, MediaUploadValidationResult, NormalizedUploadIdentity, PlaybackSourceResolveErrorCode, PlaybackSourceResolveReason, PlaybackSourceResolveRequest, PlaybackSourceResolveResult, PlayerDeliveryHostAdapter, PlayerMediaDescriptor, ProcessedMediaMetadata, ResolvedPlaybackSource, SupportedUploadFormat, UploadHeaderFormat, UploadValidationError, UploadValidationErrorCode, WaveformPeaksV1, validateMediaUploadEvidence, validateWaveformPeaks } from './media-delivery/index.js';
5
5
  export { GainPort, GainTransition, PlayerPersistencePort, PodcastProgress, PodcastProgressPort } from './ports/index.js';
6
- export { M as MediaElementPort, a as MediaElementStateSnapshot, P as PlayerMediaEvent, b as PlayerMediaEventListener, c as PlayerMediaEventType } from './media-element-C9dis4aG.js';
6
+ export { M as MediaElementPort, a as MediaElementStateSnapshot, P as PlayerMediaEvent, b as PlayerMediaEventListener, c as PlayerMediaEventType } from './media-element-DyMO-PhK.js';
package/dist/index.js CHANGED
@@ -264,6 +264,7 @@ function createInitialPlayerState(experienceConfig) {
264
264
  shuffleEnabled: resolved.value.initialShuffleEnabled,
265
265
  repeatMode: resolved.value.initialRepeatMode,
266
266
  queue: Object.freeze([]),
267
+ activeContext: null,
267
268
  currentQueueIndex: null,
268
269
  canSeek: false,
269
270
  canGoPrevious: false,