@gentomiyano/optimized-web-audio-player 0.2.3 → 0.3.0

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.
package/README.md CHANGED
@@ -1,216 +1,237 @@
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
- ## PLAYER-UI-CONTRACT-01 Bound Compact
43
-
44
- Version 0.2.3 makes the occurrence-bound inline Player use the standard
45
- Compact visual implementation. `CompactPlayer` and `BoundCompactPlayerView`
46
- now share one surface, metadata grid, waveform signal, seek overlay, tokens,
47
- and container-query behavior; the former `player-bound-compact` surface and
48
- its separate active panel are removed.
49
-
50
- The React API and playback semantics are unchanged. An inactive occurrence
51
- shows its own metadata at zero and activates its context only on Play. The
52
- active occurrence exposes global Play / Pause, time, and seek while the
53
- existing Global Mini continues to use the same Store and audio element. See
54
- `docs/PLAYER-UI-CONTRACT-01-BOUND-COMPACT.md`.
55
-
56
- ## WP-04.4 SSR-transparent Provider
57
-
58
- Version 0.2.2 renders consumer children inside the same Player Context from
59
- server rendering through hydration and Runtime readiness. `usePlayer()`
60
- exposes `runtimeStatus` as `pending` or `ready`; pending state uses an inert,
61
- SSR-safe Store bridge and never creates browser audio infrastructure.
62
-
63
- `BoundCompactPlayer` renders its occurrence metadata while pending. Runtime
64
- attachment updates the shared external Store without replacing or remounting
65
- the consumer subtree. The built-package smoke now performs server rendering,
66
- hydration, duplicate-ID, stable-sentinel, shared Mini Player, and single-audio
67
- checks. See `docs/WP-04.4-SSR-TRANSPARENT-PROVIDER.md`.
68
-
69
- ## WP-04.3 React package boundary
70
-
71
- Version 0.2.1 keeps React Context consumers in the `/react` entrypoint and
72
- exports props-only views from `/ui`. Import `PlayerProvider`, `usePlayer`,
73
- `useBoundPlayer`, and `BoundCompactPlayer` from `/react`; import
74
- `BoundCompactPlayerView`, `MiniPlayer`, and the other pure views from `/ui`.
75
-
76
- The release check packs the built package, installs that tarball into an empty
77
- consumer, renders a bound inline Player with a shared Mini Player, and verifies
78
- one Store, one active item, and one audio element. See
79
- `docs/WP-04.3-REACT-BOUNDARY.md`.
80
-
81
- ## WP-04.2 playback contexts and bound inline players
82
-
83
- Version 0.2.0 adds consumer-neutral `manual-history` and `ordered-queue`
84
- playback contexts. Contexts change only through an explicit
85
- `activateContext` command; rerendering or navigating while the application
86
- shell retains `PlayerProvider` does not replace the active context.
87
-
88
- `manual-history` records only explicitly played occurrences, never advances
89
- on natural end, and supports Previous / Next only inside the recorded session
90
- history. `ordered-queue` keeps normal queue navigation, natural advancement,
91
- shuffle, and repeat behavior. Queue updates preserve playback when the same
92
- `queueItemId` and source descriptor remain selected.
93
-
94
- Consumers can use `useBoundPlayer` or `BoundCompactPlayer` to bind an inline
95
- control to one queue occurrence without creating another audio element or
96
- store. See `docs/WP-04.2-PLAYBACK-CONTEXT.md` for contracts and integration
97
- examples.
98
-
99
- ## WP-06 debug local media import
100
-
101
- The standalone Showcase has a dev-only Local Media panel for dropping WAV,
102
- MP3, JPG/JPEG, PNG, and WebP files. Multi-file audio drops become a temporary
103
- Queue in drop order. Metadata overrides, artwork switching, track-number
104
- sorting, duplicate policy, re-read, reorder, delete, clear, and cancellable
105
- waveform analysis remain in tab memory only.
106
-
107
- No local file, metadata, filename, or object URL is sent, logged, persisted, or
108
- written back. Reloading clears the import. Audio and artwork object URLs are
109
- reference-counted, reused for duplicate identities, and revoked after the last
110
- item is removed, on clear, or when the Showcase unmounts.
111
-
112
- The parser and UI live only under `showcase/local-media/**`. They are not
113
- package exports or `tsup` entries. `pnpm build` scans every production
114
- JavaScript output for WP-06 markers and fails if the debug importer leaks into
115
- the package. See `docs/WP-06-DEBUG-MEDIA-IMPORT.md` for format coverage,
116
- limitations, and the manual checkpoint.
117
-
118
- ## WP-05 background playback checkpoint
119
-
120
- The standalone Showcase now integrates Media Session metadata, playback and
121
- position state, and OS action handlers. Music exposes play, pause, stop,
122
- Previous, Next, and seek-to. Podcast additionally exposes configurable skip
123
- backward and forward actions. Every handler delegates to the existing
124
- `PlayerCommands`; initial queue load remains non-autoplay.
125
-
126
- `audioMode=auto` keeps Web Audio fades on non-iOS browsers and uses the native
127
- HTMLAudioElement path on iPhone/iPadOS, prioritizing background continuity over
128
- fades. Real-device comparison modes are:
129
-
130
- - `http://127.0.0.1:4274/?audioMode=web-audio`
131
- - `http://127.0.0.1:4274/?audioMode=html-media`
132
-
133
- Double-click `start-player-showcase-device.cmd` for same-LAN iPhone testing,
134
- then replace the host with this computer's private LAN IP. The original
135
- `start-player-showcase.cmd` remains the loopback-only desktop launcher.
136
- `start-player-showcase-mobile.cmd` is an equivalent mobile-review launcher.
137
- The Showcase also has a `Mobile preview` toggle; `?preview=mobile` opens the
138
- same constrained review shell directly on desktop.
139
-
140
- Automated verification and the accepted device observations are recorded in
141
- `docs/WP-05A-BACKGROUND-PLAYBACK-FOUNDATION.md`. Broader device/version,
142
- Bluetooth, and production-consumer claims still require their own acceptance
143
- record.
144
-
145
- ## WP-03 runtime showcase
146
-
147
- Double-click `start-player-showcase.cmd` to open the standalone Showcase at
148
- `http://127.0.0.1:4274/`. The runtime keeps one Core store, one browser audio
149
- element, and one lazily-created Web Audio gain graph above the Compact, Wide,
150
- Detailed, Podcast, and Mini view switcher. Queue Panel and the fixed Global
151
- Mini also consume that same state. Switching views therefore does not stop
152
- playback or create another audio element.
153
-
154
- The Showcase uses generated music and podcast WAV tone fixtures. They are local,
155
- copyright-free integration media only and are not production assets. The
156
- runtime connects UI exclusively through `PlayerState` and `PlayerCommands`;
157
- the waveform remains visual-only and seeking is owned by the Seek Bar.
158
-
159
- Manual UX check:
160
-
161
- 1. Start playback and confirm that Audio Context changes from `uninitialized`
162
- to `running` only after the first user action.
163
- 2. Check Play/Pause, Seek, Volume, Previous, and Next in Wide view. On desktop,
164
- open the compact vertical Volume control from its icon in Compact and Mini;
165
- narrow mobile layouts intentionally omit it.
166
- 3. While playing, switch between Compact, Wide, Detailed, Podcast, and Mini and
167
- confirm that the same track, time, and playback state continue.
168
- 4. Open Queue Panel, select another item, and check the Podcast queue with
169
- skip-back/forward controls.
170
- 5. Toggle Global Mini and confirm that the page bottom inset is reserved only
171
- while it is visible.
172
- 6. In browser developer tools, confirm that
173
- `document.querySelectorAll('audio[data-player-audio="true"]').length` is
174
- always `1`.
175
-
176
- ## WP-01 entry points
177
-
178
- - `@gentomiyano/optimized-web-audio-player/contracts`: immutable state, commands, lifecycle,
179
- host adapter, config, preset, and debug-controller contracts
180
- - `@gentomiyano/optimized-web-audio-player/ports`: small media, gain, and persistence ports
181
- - `@gentomiyano/optimized-web-audio-player/config`: preview defaults, strict validation,
182
- versioned Config JSON schema v2, and initial state
183
- - `@gentomiyano/optimized-web-audio-player/media-delivery`: WP-04 URL-free delivery
184
- descriptors, ephemeral playback-lease and Host Adapter contracts, upload
185
- evidence validation, and waveform validation
186
- - `@gentomiyano/optimized-web-audio-player/consumer`: ordered consumer-record to Core Queue
187
- conversion for already-authorized directly playable media
188
- - `@gentomiyano/optimized-web-audio-player/react`: the single-audio React Runtime and hook
189
- - `@gentomiyano/optimized-web-audio-player/ui`: reusable Player views and primitives
190
- - `@gentomiyano/optimized-web-audio-player/styles.css`: production UI tokens and styles
191
- - `@gentomiyano/optimized-web-audio-player/testing`: privacy-safe fixtures and mock scenarios
192
-
193
- The root entry point exports production contracts, ports, config, and the
194
- provider-neutral media-delivery contract only. Runtime/UI are opt-in subpaths,
195
- and testing helpers are isolated behind `/testing`. No debug Runtime, Showcase,
196
- local importer, consumer-specific adapter, or production media URL is included
197
- in the package root.
198
-
199
- ## Commands
200
-
201
- ```text
202
- pnpm lint
203
- pnpm test
204
- pnpm typecheck
205
- pnpm build
206
- pnpm showcase:build
207
- ```
208
-
209
- ## Parallel ownership after the WP-01 checkpoint
210
-
211
- - Core: `src/core/**`, `tests/core/**`
212
- - WP-02B UI ownership explicitly includes `src/ui/**`, `src/icons/**`,
213
- `src/debug/ui/**`, and `tests/ui/**`.
214
- - UI may also render its mock views inside the pre-created `showcase/**` shell.
215
- - Integration-owned files, including contracts, ports, package configuration,
216
- 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
+ ## PLAYER-UI-CONTRACT-01 Bound Compact
43
+
44
+ Version 0.2.3 makes the occurrence-bound inline Player use the standard
45
+ Compact visual implementation. `CompactPlayer` and `BoundCompactPlayerView`
46
+ now share one surface, metadata grid, waveform signal, seek overlay, tokens,
47
+ and container-query behavior; the former `player-bound-compact` surface and
48
+ its separate active panel are removed.
49
+
50
+ The React API and playback semantics are unchanged. An inactive occurrence
51
+ shows its own metadata at zero and activates its context only on Play. The
52
+ active occurrence exposes global Play / Pause, time, and seek while the
53
+ existing Global Mini continues to use the same Store and audio element. See
54
+ `docs/PLAYER-UI-CONTRACT-01-BOUND-COMPACT.md`.
55
+
56
+ ## WP-04.4 SSR-transparent Provider
57
+
58
+ Version 0.2.2 renders consumer children inside the same Player Context from
59
+ server rendering through hydration and Runtime readiness. `usePlayer()`
60
+ exposes `runtimeStatus` as `pending` or `ready`; pending state uses an inert,
61
+ SSR-safe Store bridge and never creates browser audio infrastructure.
62
+
63
+ `BoundCompactPlayer` renders its occurrence metadata while pending. Runtime
64
+ attachment updates the shared external Store without replacing or remounting
65
+ the consumer subtree. The built-package smoke now performs server rendering,
66
+ hydration, duplicate-ID, stable-sentinel, shared Mini Player, and single-audio
67
+ checks. See `docs/WP-04.4-SSR-TRANSPARENT-PROVIDER.md`.
68
+
69
+ ## WP-04.3 React package boundary
70
+
71
+ Version 0.2.1 keeps React Context consumers in the `/react` entrypoint and
72
+ exports props-only views from `/ui`. Import `PlayerProvider`, `usePlayer`,
73
+ `useBoundPlayer`, and `BoundCompactPlayer` from `/react`; import
74
+ `BoundCompactPlayerView`, `MiniPlayer`, and the other pure views from `/ui`.
75
+
76
+ The release check packs the built package, installs that tarball into an empty
77
+ consumer, renders a bound inline Player with a shared Mini Player, and verifies
78
+ one Store, one active item, and one audio element. See
79
+ `docs/WP-04.3-REACT-BOUNDARY.md`.
80
+
81
+ ## WP-04.2 playback contexts and bound inline players
82
+
83
+ Version 0.2.0 adds consumer-neutral `manual-history` and `ordered-queue`
84
+ playback contexts. Contexts change only through an explicit
85
+ `activateContext` command; rerendering or navigating while the application
86
+ shell retains `PlayerProvider` does not replace the active context.
87
+
88
+ `manual-history` records only explicitly played occurrences, never advances
89
+ on natural end, and supports Previous / Next only inside the recorded session
90
+ history. `ordered-queue` keeps normal queue navigation, natural advancement,
91
+ shuffle, and repeat behavior. Queue updates preserve playback when the same
92
+ `queueItemId` and source descriptor remain selected.
93
+
94
+ Consumers can use `useBoundPlayer` or `BoundCompactPlayer` to bind an inline
95
+ control to one queue occurrence without creating another audio element or
96
+ store. See `docs/WP-04.2-PLAYBACK-CONTEXT.md` for contracts and integration
97
+ examples.
98
+
99
+ ## FORNOW Release and Podcast content contexts
100
+
101
+ Version 0.3.0 adds the Release and Podcast Program content surfaces.
102
+
103
+ The package now exposes Player-side models and props-only views for intrinsic
104
+ Release Tracklists and Podcast Programs. `ReleasePlayer` and
105
+ `PodcastProgramPlayer` use the existing shared `PlayerCommands` and
106
+ `PlayerProvider`; they do not mount another audio element or expose their
107
+ ordered content as a visible generic Queue.
108
+
109
+ Hosts keep access, publication, entitlement, and asset processing policy.
110
+ They may render `processing`, `locked`, `unavailable`, and `failed` entries,
111
+ but only an authorized `ready` entry carries a `QueueItem`. Player media uses
112
+ an explicit `contentContext` with Release/Track or Program/Episode IDs so the
113
+ shared Mini Player and Media Session retain the playing source identity.
114
+
115
+ Podcast Episodes may provide timestamped chapters. Selecting an active
116
+ chapter seeks the shared Player; selecting a chapter from another Episode
117
+ activates that Episode first. See
118
+ `docs/FORNOW-RELEASE-PODCAST-PLAYER.md` for the integration boundary.
119
+
120
+ ## WP-06 debug local media import
121
+
122
+ The standalone Showcase has a dev-only Local Media panel for dropping WAV,
123
+ MP3, JPG/JPEG, PNG, and WebP files. Multi-file audio drops become a temporary
124
+ Queue in drop order. Metadata overrides, artwork switching, track-number
125
+ sorting, duplicate policy, re-read, reorder, delete, clear, and cancellable
126
+ waveform analysis remain in tab memory only.
127
+
128
+ No local file, metadata, filename, or object URL is sent, logged, persisted, or
129
+ written back. Reloading clears the import. Audio and artwork object URLs are
130
+ reference-counted, reused for duplicate identities, and revoked after the last
131
+ item is removed, on clear, or when the Showcase unmounts.
132
+
133
+ The parser and UI live only under `showcase/local-media/**`. They are not
134
+ package exports or `tsup` entries. `pnpm build` scans every production
135
+ JavaScript output for WP-06 markers and fails if the debug importer leaks into
136
+ the package. See `docs/WP-06-DEBUG-MEDIA-IMPORT.md` for format coverage,
137
+ limitations, and the manual checkpoint.
138
+
139
+ ## WP-05 background playback checkpoint
140
+
141
+ The standalone Showcase now integrates Media Session metadata, playback and
142
+ position state, and OS action handlers. Music exposes play, pause, stop,
143
+ Previous, Next, and seek-to. Podcast additionally exposes configurable skip
144
+ backward and forward actions. Every handler delegates to the existing
145
+ `PlayerCommands`; initial queue load remains non-autoplay.
146
+
147
+ `audioMode=auto` keeps Web Audio fades on non-iOS browsers and uses the native
148
+ HTMLAudioElement path on iPhone/iPadOS, prioritizing background continuity over
149
+ fades. Real-device comparison modes are:
150
+
151
+ - `http://127.0.0.1:4274/?audioMode=web-audio`
152
+ - `http://127.0.0.1:4274/?audioMode=html-media`
153
+
154
+ Double-click `start-player-showcase-device.cmd` for same-LAN iPhone testing,
155
+ then replace the host with this computer's private LAN IP. The original
156
+ `start-player-showcase.cmd` remains the loopback-only desktop launcher.
157
+ `start-player-showcase-mobile.cmd` is an equivalent mobile-review launcher.
158
+ The Showcase also has a `Mobile preview` toggle; `?preview=mobile` opens the
159
+ same constrained review shell directly on desktop.
160
+
161
+ Automated verification and the accepted device observations are recorded in
162
+ `docs/WP-05A-BACKGROUND-PLAYBACK-FOUNDATION.md`. Broader device/version,
163
+ Bluetooth, and production-consumer claims still require their own acceptance
164
+ record.
165
+
166
+ ## WP-03 runtime showcase
167
+
168
+ Double-click `start-player-showcase.cmd` to open the standalone Showcase at
169
+ `http://127.0.0.1:4274/`. The runtime keeps one Core store, one browser audio
170
+ element, and one lazily-created Web Audio gain graph above the Compact, Wide,
171
+ Detailed, Podcast, and Mini view switcher. Queue Panel and the fixed Global
172
+ Mini also consume that same state. Switching views therefore does not stop
173
+ playback or create another audio element.
174
+
175
+ The Showcase uses generated music and podcast WAV tone fixtures. They are local,
176
+ copyright-free integration media only and are not production assets. The
177
+ runtime connects UI exclusively through `PlayerState` and `PlayerCommands`;
178
+ the waveform remains visual-only and seeking is owned by the Seek Bar.
179
+
180
+ Manual UX check:
181
+
182
+ 1. Start playback and confirm that Audio Context changes from `uninitialized`
183
+ to `running` only after the first user action.
184
+ 2. Check Play/Pause, Seek, Volume, Previous, and Next in Wide view. On desktop,
185
+ open the compact vertical Volume control from its icon in Compact and Mini;
186
+ narrow mobile layouts intentionally omit it.
187
+ 3. While playing, switch between Compact, Wide, Detailed, Podcast, and Mini and
188
+ confirm that the same track, time, and playback state continue.
189
+ 4. Open Queue Panel, select another item, and check the Podcast queue with
190
+ skip-back/forward controls.
191
+ 5. Toggle Global Mini and confirm that the page bottom inset is reserved only
192
+ while it is visible.
193
+ 6. In browser developer tools, confirm that
194
+ `document.querySelectorAll('audio[data-player-audio="true"]').length` is
195
+ always `1`.
196
+
197
+ ## WP-01 entry points
198
+
199
+ - `@gentomiyano/optimized-web-audio-player/contracts`: immutable state, commands, lifecycle,
200
+ host adapter, config, preset, and debug-controller contracts
201
+ - `@gentomiyano/optimized-web-audio-player/ports`: small media, gain, and persistence ports
202
+ - `@gentomiyano/optimized-web-audio-player/config`: preview defaults, strict validation,
203
+ versioned Config JSON schema v2, and initial state
204
+ - `@gentomiyano/optimized-web-audio-player/media-delivery`: WP-04 URL-free delivery
205
+ descriptors, ephemeral playback-lease and Host Adapter contracts, upload
206
+ evidence validation, and waveform validation
207
+ - `@gentomiyano/optimized-web-audio-player/consumer`: ordered consumer-record to Core Queue
208
+ conversion for already-authorized directly playable media
209
+ - `@gentomiyano/optimized-web-audio-player/react`: the single-audio React Runtime and hook
210
+ - `@gentomiyano/optimized-web-audio-player/ui`: reusable Player views and primitives
211
+ - `@gentomiyano/optimized-web-audio-player/styles.css`: production UI tokens and styles
212
+ - `@gentomiyano/optimized-web-audio-player/testing`: privacy-safe fixtures and mock scenarios
213
+
214
+ The root entry point exports production contracts, ports, config, and the
215
+ provider-neutral media-delivery contract only. Runtime/UI are opt-in subpaths,
216
+ and testing helpers are isolated behind `/testing`. No debug Runtime, Showcase,
217
+ local importer, consumer-specific adapter, or production media URL is included
218
+ in the package root.
219
+
220
+ ## Commands
221
+
222
+ ```text
223
+ pnpm lint
224
+ pnpm test
225
+ pnpm typecheck
226
+ pnpm build
227
+ pnpm showcase:build
228
+ ```
229
+
230
+ ## Parallel ownership after the WP-01 checkpoint
231
+
232
+ - Core: `src/core/**`, `tests/core/**`
233
+ - WP-02B UI ownership explicitly includes `src/ui/**`, `src/icons/**`,
234
+ `src/debug/ui/**`, and `tests/ui/**`.
235
+ - UI may also render its mock views inside the pre-created `showcase/**` shell.
236
+ - Integration-owned files, including contracts, ports, package configuration,
237
+ adapters, and testing fixtures, must not be changed by Core or UI work.
@@ -1,5 +1,5 @@
1
- import { p as PlayerExperienceConfig, z as PlayerState } from '../player-CE714iU_.js';
2
- import { PlayerExperienceConfigValidationResult } from '../contracts/index.js';
1
+ import { q as PlayerExperienceConfig, C as PlayerState } from '../player-DRQO7SLs.js';
2
+ import { P as PlayerExperienceConfigValidationResult } from '../config-Bhyo_y05.js';
3
3
 
4
4
  declare const PLAYER_EXPERIENCE_CONFIG_SCHEMA_VERSION: 2;
5
5
  declare const PLAYER_EXPERIENCE_CONFIG_LIMITS: Readonly<{
@@ -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\";\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
+ {"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 activeContext: null,\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"]}
@@ -0,0 +1,14 @@
1
+ import { q as PlayerExperienceConfig } from './player-DRQO7SLs.js';
2
+
3
+ interface ConfigValidationSuccess {
4
+ readonly ok: true;
5
+ readonly value: Readonly<PlayerExperienceConfig>;
6
+ readonly errors: readonly [];
7
+ }
8
+ interface ConfigValidationFailure {
9
+ readonly ok: false;
10
+ readonly errors: readonly string[];
11
+ }
12
+ type PlayerExperienceConfigValidationResult = ConfigValidationSuccess | ConfigValidationFailure;
13
+
14
+ export type { ConfigValidationFailure as C, PlayerExperienceConfigValidationResult as P, ConfigValidationSuccess as a };
@@ -1,4 +1,21 @@
1
- import { Q as QueueItem, u as PlayerMediaAdapter } from '../player-CE714iU_.js';
1
+ import { c as PlaybackContextInput, Q as QueueItem, v as PlayerMediaAdapter } from '../player-DRQO7SLs.js';
2
+ import { e as PodcastProgramPlaybackModel, R as ReleasePlaybackModel, c as PodcastChapter } from '../content-CVe0cm0y.js';
3
+
4
+ type PlayerContentContextRejectionReason = "duplicate-content-id" | "duplicate-queue-item-id" | "ready-without-queue-item" | "non-ready-with-queue-item" | "content-context-mismatch";
5
+ interface PlayerContentContextRejection {
6
+ readonly entryId: string;
7
+ readonly entryIndex: number;
8
+ readonly reason: PlayerContentContextRejectionReason;
9
+ }
10
+ interface PlayerContentContextBuildResult {
11
+ readonly context: Extract<PlaybackContextInput, {
12
+ readonly mode: "ordered-queue";
13
+ }>;
14
+ readonly rejected: readonly PlayerContentContextRejection[];
15
+ }
16
+ declare function buildReleasePlaybackContext(release: ReleasePlaybackModel): PlayerContentContextBuildResult;
17
+ declare function buildPodcastPlaybackContext(program: PodcastProgramPlaybackModel): PlayerContentContextBuildResult;
18
+ declare function findActivePodcastChapter(chapters: readonly PodcastChapter[] | undefined, currentTimeSec: number): PodcastChapter | null;
2
19
 
3
20
  interface PlayerQueueCandidate<TSource> {
4
21
  /** Host-owned stable identity for this occurrence in a queue context. */
@@ -24,4 +41,4 @@ interface PlayerQueueBuildResult {
24
41
  */
25
42
  declare function buildPlayerQueue<TSource>(candidates: readonly PlayerQueueCandidate<TSource>[], adapter: PlayerMediaAdapter<TSource>): PlayerQueueBuildResult;
26
43
 
27
- export { type PlayerQueueBuildResult, type PlayerQueueCandidate, type PlayerQueueRejection, type PlayerQueueRejectionReason, buildPlayerQueue };
44
+ export { type PlayerContentContextBuildResult, type PlayerContentContextRejection, type PlayerContentContextRejectionReason, type PlayerQueueBuildResult, type PlayerQueueCandidate, type PlayerQueueRejection, type PlayerQueueRejectionReason, buildPlayerQueue, buildPodcastPlaybackContext, buildReleasePlaybackContext, findActivePodcastChapter };