@gentomiyano/optimized-web-audio-player 0.1.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.
Files changed (53) hide show
  1. package/README.md +159 -0
  2. package/dist/config/index.d.ts +37 -0
  3. package/dist/config/index.js +280 -0
  4. package/dist/config/index.js.map +1 -0
  5. package/dist/consumer/index.d.ts +27 -0
  6. package/dist/consumer/index.js +38 -0
  7. package/dist/consumer/index.js.map +1 -0
  8. package/dist/consumer-smoke/node_modules/.modules.yaml +30 -0
  9. package/dist/consumer-smoke/node_modules/.package-map.json +1 -0
  10. package/dist/consumer-smoke/node_modules/.pnpm/lock.yaml +57 -0
  11. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/LICENSE +21 -0
  12. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/README.md +9 -0
  13. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler-unstable_mock.development.js +414 -0
  14. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler-unstable_mock.production.js +406 -0
  15. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler-unstable_post_task.development.js +150 -0
  16. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler-unstable_post_task.production.js +140 -0
  17. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler.development.js +364 -0
  18. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler.native.development.js +350 -0
  19. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler.native.production.js +330 -0
  20. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/cjs/scheduler.production.js +340 -0
  21. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/index.js +7 -0
  22. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/index.native.js +7 -0
  23. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/package.json +27 -0
  24. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/unstable_mock.js +7 -0
  25. package/dist/consumer-smoke/node_modules/.pnpm/scheduler@0.27.0/node_modules/scheduler/unstable_post_task.js +7 -0
  26. package/dist/consumer-smoke/node_modules/.pnpm-workspace-state-v1.json +29 -0
  27. package/dist/contracts/index.d.ts +15 -0
  28. package/dist/contracts/index.js +3 -0
  29. package/dist/contracts/index.js.map +1 -0
  30. package/dist/index.d.ts +6 -0
  31. package/dist/index.js +368 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/media-delivery/index.d.ts +185 -0
  34. package/dist/media-delivery/index.js +91 -0
  35. package/dist/media-delivery/index.js.map +1 -0
  36. package/dist/media-element-C9dis4aG.d.ts +40 -0
  37. package/dist/player-DE9Vqztu.d.ts +212 -0
  38. package/dist/ports/index.d.ts +32 -0
  39. package/dist/ports/index.js +3 -0
  40. package/dist/ports/index.js.map +1 -0
  41. package/dist/react/index.d.ts +86 -0
  42. package/dist/react/index.js +2393 -0
  43. package/dist/react/index.js.map +1 -0
  44. package/dist/testing/index.d.ts +36 -0
  45. package/dist/testing/index.js +365 -0
  46. package/dist/testing/index.js.map +1 -0
  47. package/dist/ui/index.d.ts +143 -0
  48. package/dist/ui/index.js +1344 -0
  49. package/dist/ui/index.js.map +1 -0
  50. package/dist/ui/player-tokens.css +572 -0
  51. package/dist/ui/player-views.css +654 -0
  52. package/dist/ui/styles.css +2 -0
  53. package/package.json +97 -0
@@ -0,0 +1,365 @@
1
+ // src/adapters/mock/mock-player-adapter.ts
2
+ var MOCK_RECORDS = Object.freeze([
3
+ Object.freeze({
4
+ id: "sample-track-alpha",
5
+ playable: true,
6
+ title: "Sample Track Alpha",
7
+ creatorName: "Sample Creator",
8
+ collectionTitle: "Sample Collection",
9
+ sourceUrl: "/mock-audio/sample-track-alpha.mp3",
10
+ durationHintSec: 185,
11
+ waveformPeaks: Object.freeze([0.18, 0.42, 0.7, 0.54, 0.82, 0.33])
12
+ }),
13
+ Object.freeze({
14
+ id: "sample-track-beta",
15
+ playable: true,
16
+ title: "Sample Track Beta",
17
+ creatorName: "Sample Creator",
18
+ collectionTitle: "Sample Collection",
19
+ sourceUrl: "/mock-audio/sample-track-beta.mp3",
20
+ durationHintSec: 212,
21
+ waveformPeaks: Object.freeze([0.28, 0.5, 0.36, 0.76, 0.61, 0.24])
22
+ })
23
+ ]);
24
+ var RECOVERABLE_ERROR = Object.freeze({
25
+ code: "network",
26
+ recoverable: true,
27
+ messageKey: "player.error.network"
28
+ });
29
+ var FATAL_ERROR = Object.freeze({
30
+ code: "unsupported",
31
+ recoverable: false,
32
+ messageKey: "player.error.unsupported"
33
+ });
34
+ var MOCK_PLAYER_SCENARIOS = /* @__PURE__ */ new Set([
35
+ "success",
36
+ "loading",
37
+ "duration-unknown",
38
+ "missing-waveform",
39
+ "recoverable-error",
40
+ "fatal-error"
41
+ ]);
42
+ function wait(delayMs) {
43
+ if (delayMs <= 0) {
44
+ return Promise.resolve();
45
+ }
46
+ return new Promise((resolve) => {
47
+ setTimeout(resolve, delayMs);
48
+ });
49
+ }
50
+ var MockPlayerAdapter = class {
51
+ #scenario;
52
+ #delayMs;
53
+ constructor(options = {}) {
54
+ this.#scenario = options.scenario ?? "success";
55
+ this.#delayMs = options.delayMs ?? 0;
56
+ if (!MOCK_PLAYER_SCENARIOS.has(this.#scenario)) {
57
+ throw new TypeError("Mock scenario is not supported.");
58
+ }
59
+ if (!Number.isFinite(this.#delayMs) || this.#delayMs < 0 || this.#delayMs > 3e4) {
60
+ throw new RangeError("Mock delayMs must be between 0 and 30000.");
61
+ }
62
+ }
63
+ toPlayerMediaItem(source) {
64
+ if (!source.playable || typeof source.sourceUrl !== "string" || source.sourceUrl.length === 0) {
65
+ return null;
66
+ }
67
+ const base = {
68
+ id: source.id,
69
+ kind: "track",
70
+ title: source.title,
71
+ source: {
72
+ url: source.sourceUrl,
73
+ crossOrigin: "anonymous",
74
+ mimeType: "audio/mpeg"
75
+ },
76
+ ...source.creatorName === void 0 ? {} : { creatorName: source.creatorName },
77
+ ...source.collectionTitle === void 0 ? {} : { collectionTitle: source.collectionTitle },
78
+ ...this.#scenario === "duration-unknown" || source.durationHintSec === void 0 ? {} : { durationHintSec: source.durationHintSec }
79
+ };
80
+ return {
81
+ ...base,
82
+ ...this.#scenario === "missing-waveform" || source.waveformPeaks === void 0 ? {} : {
83
+ waveform: {
84
+ version: 1,
85
+ peaks: source.waveformPeaks
86
+ }
87
+ }
88
+ };
89
+ }
90
+ async loadScenario() {
91
+ await wait(this.#delayMs);
92
+ const queue = MOCK_RECORDS.flatMap((record, index) => {
93
+ const item = this.toPlayerMediaItem(record);
94
+ return item === null ? [] : [{ queueItemId: `sample-queue-${String(index + 1)}`, item }];
95
+ });
96
+ return Object.freeze({
97
+ scenario: this.#scenario,
98
+ queue: Object.freeze(queue),
99
+ initialError: this.#scenario === "recoverable-error" ? RECOVERABLE_ERROR : this.#scenario === "fatal-error" ? FATAL_ERROR : null,
100
+ remainsLoading: this.#scenario === "loading"
101
+ });
102
+ }
103
+ };
104
+ var mockMediaRecords = MOCK_RECORDS;
105
+
106
+ // src/config/defaults.ts
107
+ var PLAYER_EXPERIENCE_CONFIG_LIMITS = Object.freeze({
108
+ fadeMs: Object.freeze({ min: 0, max: 1e4 }),
109
+ previousRestartThresholdSec: Object.freeze({ min: 0, max: 60 }),
110
+ waveformOpacity: Object.freeze({ min: 0, max: 1 }),
111
+ initialVolume: Object.freeze({ min: 0, max: 1 }),
112
+ podcastSkipSec: Object.freeze({ minExclusive: 0, max: 3600 })
113
+ });
114
+ var PREVIEW_PLAYER_EXPERIENCE_CONFIG = Object.freeze({
115
+ playFadeInMs: 300,
116
+ pauseFadeOutMs: 600,
117
+ trackChangeFadeOutMs: 300,
118
+ trackChangeFadeInMs: 300,
119
+ seekFadeMs: 120,
120
+ fadeCurve: "ease-out",
121
+ previousRestartThresholdSec: 3,
122
+ seekCommitMode: "on-release",
123
+ waveformPlayedStateEnabled: true,
124
+ waveformOpacity: 0.72,
125
+ initialShuffleEnabled: false,
126
+ initialRepeatMode: "off",
127
+ initialVolume: 0.8,
128
+ persistMode: "none",
129
+ podcastBackSec: 15,
130
+ podcastForwardSec: 30,
131
+ podcastPreviousMode: "previous-episode"
132
+ });
133
+
134
+ // src/config/validation.ts
135
+ var CONFIG_KEYS = [
136
+ "playFadeInMs",
137
+ "pauseFadeOutMs",
138
+ "trackChangeFadeOutMs",
139
+ "trackChangeFadeInMs",
140
+ "seekFadeMs",
141
+ "fadeCurve",
142
+ "previousRestartThresholdSec",
143
+ "seekCommitMode",
144
+ "waveformPlayedStateEnabled",
145
+ "waveformOpacity",
146
+ "initialShuffleEnabled",
147
+ "initialRepeatMode",
148
+ "initialVolume",
149
+ "persistMode",
150
+ "podcastBackSec",
151
+ "podcastForwardSec",
152
+ "podcastPreviousMode"
153
+ ];
154
+ var FADE_CURVES = /* @__PURE__ */ new Set([
155
+ "linear",
156
+ "ease-in",
157
+ "ease-out",
158
+ "equal-power"
159
+ ]);
160
+ var SEEK_COMMIT_MODES = /* @__PURE__ */ new Set(["live", "on-release"]);
161
+ var REPEAT_MODES = /* @__PURE__ */ new Set(["off", "all", "one"]);
162
+ var PERSIST_MODES = /* @__PURE__ */ new Set(["none", "session", "local"]);
163
+ var PODCAST_PREVIOUS_MODES = /* @__PURE__ */ new Set([
164
+ "previous-episode",
165
+ "restart-episode"
166
+ ]);
167
+ function isRecord(value) {
168
+ return typeof value === "object" && value !== null && !Array.isArray(value);
169
+ }
170
+ function validateFiniteRange(value, path, min, max, errors) {
171
+ if (typeof value !== "number" || !Number.isFinite(value) || value < min || value > max) {
172
+ errors.push(
173
+ `${path} must be a finite number between ${String(min)} and ${String(max)}.`
174
+ );
175
+ return false;
176
+ }
177
+ return true;
178
+ }
179
+ function validateBoolean(value, path, errors) {
180
+ if (typeof value !== "boolean") {
181
+ errors.push(`${path} must be a boolean.`);
182
+ return false;
183
+ }
184
+ return true;
185
+ }
186
+ function validateConfigRecord(value, options) {
187
+ if (!isRecord(value)) {
188
+ return { ok: false, errors: ["config must be an object."] };
189
+ }
190
+ const errors = [];
191
+ const allowedKeys = new Set(CONFIG_KEYS);
192
+ for (const key of Object.keys(value)) {
193
+ if (!allowedKeys.has(key)) {
194
+ errors.push(`config.${key} is not supported.`);
195
+ }
196
+ }
197
+ const has = (key) => Object.hasOwn(value, key);
198
+ const fadeLimits = PLAYER_EXPERIENCE_CONFIG_LIMITS.fadeMs;
199
+ for (const key of [
200
+ "playFadeInMs",
201
+ "pauseFadeOutMs",
202
+ "trackChangeFadeOutMs",
203
+ "trackChangeFadeInMs",
204
+ "seekFadeMs"
205
+ ]) {
206
+ if (has(key)) {
207
+ validateFiniteRange(value[key], `config.${key}`, fadeLimits.min, fadeLimits.max, errors);
208
+ }
209
+ }
210
+ const thresholdLimits = PLAYER_EXPERIENCE_CONFIG_LIMITS.previousRestartThresholdSec;
211
+ if (has("previousRestartThresholdSec")) {
212
+ validateFiniteRange(
213
+ value.previousRestartThresholdSec,
214
+ "config.previousRestartThresholdSec",
215
+ thresholdLimits.min,
216
+ thresholdLimits.max,
217
+ errors
218
+ );
219
+ }
220
+ const volumeLimits = PLAYER_EXPERIENCE_CONFIG_LIMITS.initialVolume;
221
+ if (has("initialVolume")) {
222
+ validateFiniteRange(
223
+ value.initialVolume,
224
+ "config.initialVolume",
225
+ volumeLimits.min,
226
+ volumeLimits.max,
227
+ errors
228
+ );
229
+ }
230
+ const waveformOpacityLimits = PLAYER_EXPERIENCE_CONFIG_LIMITS.waveformOpacity;
231
+ if (has("waveformOpacity")) {
232
+ validateFiniteRange(
233
+ value.waveformOpacity,
234
+ "config.waveformOpacity",
235
+ waveformOpacityLimits.min,
236
+ waveformOpacityLimits.max,
237
+ errors
238
+ );
239
+ }
240
+ for (const key of [
241
+ "waveformPlayedStateEnabled",
242
+ "initialShuffleEnabled"
243
+ ]) {
244
+ if (has(key)) {
245
+ validateBoolean(value[key], `config.${key}`, errors);
246
+ }
247
+ }
248
+ const skipLimits = PLAYER_EXPERIENCE_CONFIG_LIMITS.podcastSkipSec;
249
+ for (const key of ["podcastBackSec", "podcastForwardSec"]) {
250
+ if (has(key)) {
251
+ const candidate = value[key];
252
+ const valid = validateFiniteRange(
253
+ candidate,
254
+ `config.${key}`,
255
+ skipLimits.minExclusive,
256
+ skipLimits.max,
257
+ errors
258
+ );
259
+ if (valid && candidate <= skipLimits.minExclusive) {
260
+ errors.push(`config.${key} must be greater than 0.`);
261
+ }
262
+ }
263
+ }
264
+ if (has("fadeCurve") && !FADE_CURVES.has(value.fadeCurve)) {
265
+ errors.push("config.fadeCurve is not supported.");
266
+ }
267
+ if (has("seekCommitMode") && !SEEK_COMMIT_MODES.has(value.seekCommitMode)) {
268
+ errors.push("config.seekCommitMode is not supported.");
269
+ }
270
+ if (has("initialRepeatMode") && !REPEAT_MODES.has(value.initialRepeatMode)) {
271
+ errors.push("config.initialRepeatMode is not supported.");
272
+ }
273
+ if (has("persistMode") && !PERSIST_MODES.has(value.persistMode)) {
274
+ errors.push("config.persistMode is not supported.");
275
+ }
276
+ if (has("podcastPreviousMode") && !PODCAST_PREVIOUS_MODES.has(
277
+ value.podcastPreviousMode
278
+ )) {
279
+ errors.push("config.podcastPreviousMode is not supported.");
280
+ }
281
+ if (errors.length > 0) {
282
+ return { ok: false, errors: Object.freeze(errors) };
283
+ }
284
+ const merged = { ...PREVIEW_PLAYER_EXPERIENCE_CONFIG, ...value } ;
285
+ return {
286
+ ok: true,
287
+ errors: [],
288
+ value: Object.freeze({ ...merged })
289
+ };
290
+ }
291
+ function validatePlayerExperienceConfigOverride(value) {
292
+ return validateConfigRecord(value);
293
+ }
294
+ function resolvePlayerExperienceConfig(override) {
295
+ return validatePlayerExperienceConfigOverride(override ?? {});
296
+ }
297
+
298
+ // src/config/initial-state.ts
299
+ function createInitialPlayerState(experienceConfig) {
300
+ const resolved = resolvePlayerExperienceConfig(experienceConfig);
301
+ if (!resolved.ok) {
302
+ throw new TypeError(resolved.errors.join(" "));
303
+ }
304
+ return Object.freeze({
305
+ currentItem: null,
306
+ currentItemId: null,
307
+ currentQueueItemId: null,
308
+ mediaKind: null,
309
+ status: "idle",
310
+ currentTimeSec: 0,
311
+ durationSec: null,
312
+ volume: resolved.value.initialVolume,
313
+ muted: false,
314
+ shuffleEnabled: resolved.value.initialShuffleEnabled,
315
+ repeatMode: resolved.value.initialRepeatMode,
316
+ queue: Object.freeze([]),
317
+ currentQueueIndex: null,
318
+ canSeek: false,
319
+ canGoPrevious: false,
320
+ canGoNext: false,
321
+ needsUserGesture: false,
322
+ lastTransitionCause: null,
323
+ error: null,
324
+ experienceConfig: resolved.value
325
+ });
326
+ }
327
+
328
+ // src/testing/fixtures.ts
329
+ function createPlayerMediaItemFixture(overrides = {}) {
330
+ return {
331
+ id: "fixture-track",
332
+ kind: "track",
333
+ title: "Fixture Track",
334
+ creatorName: "Fixture Creator",
335
+ source: {
336
+ url: "/mock-audio/fixture-track.mp3",
337
+ mimeType: "audio/mpeg",
338
+ crossOrigin: "anonymous"
339
+ },
340
+ durationHintSec: 180,
341
+ waveform: {
342
+ version: 1,
343
+ peaks: [0.2, 0.6, 0.4, 0.8]
344
+ },
345
+ ...overrides
346
+ };
347
+ }
348
+ function createQueueItemFixture(overrides = {}) {
349
+ return {
350
+ queueItemId: "fixture-queue-item",
351
+ item: createPlayerMediaItemFixture(),
352
+ ...overrides
353
+ };
354
+ }
355
+ function createPlayerStateFixture(overrides = {}, experienceConfig) {
356
+ const initial = createInitialPlayerState(experienceConfig);
357
+ return Object.freeze({
358
+ ...initial,
359
+ ...overrides
360
+ });
361
+ }
362
+
363
+ export { MockPlayerAdapter, createPlayerMediaItemFixture, createPlayerStateFixture, createQueueItemFixture, mockMediaRecords };
364
+ //# sourceMappingURL=index.js.map
365
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/mock/mock-player-adapter.ts","../../src/config/defaults.ts","../../src/config/validation.ts","../../src/config/initial-state.ts","../../src/testing/fixtures.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type {\n PlayerError,\n PlayerMediaAdapter,\n PlayerMediaItem,\n QueueItem,\n} from \"../../contracts/player\";\n\nexport type MockPlayerScenario =\n | \"success\"\n | \"loading\"\n | \"duration-unknown\"\n | \"missing-waveform\"\n | \"recoverable-error\"\n | \"fatal-error\";\n\nexport interface MockMediaRecord {\n readonly id: string;\n readonly playable: boolean;\n readonly title: string;\n readonly creatorName?: string;\n readonly collectionTitle?: string;\n readonly sourceUrl?: string;\n readonly durationHintSec?: number;\n readonly waveformPeaks?: readonly number[];\n}\n\nexport interface MockPlayerAdapterOptions {\n readonly scenario?: MockPlayerScenario;\n readonly delayMs?: number;\n}\n\nexport interface MockPlayerScenarioResult {\n readonly scenario: MockPlayerScenario;\n readonly queue: readonly QueueItem[];\n readonly initialError: PlayerError | null;\n readonly remainsLoading: boolean;\n}\n\nconst MOCK_RECORDS: readonly MockMediaRecord[] = Object.freeze([\n Object.freeze({\n id: \"sample-track-alpha\",\n playable: true,\n title: \"Sample Track Alpha\",\n creatorName: \"Sample Creator\",\n collectionTitle: \"Sample Collection\",\n sourceUrl: \"/mock-audio/sample-track-alpha.mp3\",\n durationHintSec: 185,\n waveformPeaks: Object.freeze([0.18, 0.42, 0.7, 0.54, 0.82, 0.33]),\n }),\n Object.freeze({\n id: \"sample-track-beta\",\n playable: true,\n title: \"Sample Track Beta\",\n creatorName: \"Sample Creator\",\n collectionTitle: \"Sample Collection\",\n sourceUrl: \"/mock-audio/sample-track-beta.mp3\",\n durationHintSec: 212,\n waveformPeaks: Object.freeze([0.28, 0.5, 0.36, 0.76, 0.61, 0.24]),\n }),\n]);\n\nconst RECOVERABLE_ERROR: PlayerError = Object.freeze({\n code: \"network\",\n recoverable: true,\n messageKey: \"player.error.network\",\n});\n\nconst FATAL_ERROR: PlayerError = Object.freeze({\n code: \"unsupported\",\n recoverable: false,\n messageKey: \"player.error.unsupported\",\n});\n\nconst MOCK_PLAYER_SCENARIOS = new Set<MockPlayerScenario>([\n \"success\",\n \"loading\",\n \"duration-unknown\",\n \"missing-waveform\",\n \"recoverable-error\",\n \"fatal-error\",\n]);\n\nfunction wait(delayMs: number): Promise<void> {\n if (delayMs <= 0) {\n return Promise.resolve();\n }\n\n return new Promise((resolve) => {\n setTimeout(resolve, delayMs);\n });\n}\n\nexport class MockPlayerAdapter\n implements PlayerMediaAdapter<MockMediaRecord>\n{\n readonly #scenario: MockPlayerScenario;\n readonly #delayMs: number;\n\n constructor(options: MockPlayerAdapterOptions = {}) {\n this.#scenario = options.scenario ?? \"success\";\n this.#delayMs = options.delayMs ?? 0;\n\n if (!MOCK_PLAYER_SCENARIOS.has(this.#scenario)) {\n throw new TypeError(\"Mock scenario is not supported.\");\n }\n\n if (\n !Number.isFinite(this.#delayMs) ||\n this.#delayMs < 0 ||\n this.#delayMs > 30_000\n ) {\n throw new RangeError(\"Mock delayMs must be between 0 and 30000.\");\n }\n }\n\n toPlayerMediaItem(source: MockMediaRecord): PlayerMediaItem | null {\n if (\n !source.playable ||\n typeof source.sourceUrl !== \"string\" ||\n source.sourceUrl.length === 0\n ) {\n return null;\n }\n\n const base: PlayerMediaItem = {\n id: source.id,\n kind: \"track\",\n title: source.title,\n source: {\n url: source.sourceUrl,\n crossOrigin: \"anonymous\",\n mimeType: \"audio/mpeg\",\n },\n ...(source.creatorName === undefined\n ? {}\n : { creatorName: source.creatorName }),\n ...(source.collectionTitle === undefined\n ? {}\n : { collectionTitle: source.collectionTitle }),\n ...(this.#scenario === \"duration-unknown\" ||\n source.durationHintSec === undefined\n ? {}\n : { durationHintSec: source.durationHintSec }),\n };\n\n return {\n ...base,\n ...(this.#scenario === \"missing-waveform\" ||\n source.waveformPeaks === undefined\n ? {}\n : {\n waveform: {\n version: 1,\n peaks: source.waveformPeaks,\n },\n }),\n };\n }\n\n async loadScenario(): Promise<MockPlayerScenarioResult> {\n await wait(this.#delayMs);\n\n const queue = MOCK_RECORDS.flatMap((record, index) => {\n const item = this.toPlayerMediaItem(record);\n return item === null\n ? []\n : [{ queueItemId: `sample-queue-${String(index + 1)}`, item }];\n });\n\n return Object.freeze({\n scenario: this.#scenario,\n queue: Object.freeze(queue),\n initialError:\n this.#scenario === \"recoverable-error\"\n ? RECOVERABLE_ERROR\n : this.#scenario === \"fatal-error\"\n ? FATAL_ERROR\n : null,\n remainsLoading: this.#scenario === \"loading\",\n });\n }\n}\n\nexport const mockMediaRecords = MOCK_RECORDS;\n","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","import type {\n PlayerExperienceConfig,\n PlayerMediaItem,\n PlayerState,\n QueueItem,\n} from \"../contracts/player\";\nimport { createInitialPlayerState } from \"../config/initial-state\";\n\nexport function createPlayerMediaItemFixture(\n overrides: Partial<PlayerMediaItem> = {},\n): PlayerMediaItem {\n return {\n id: \"fixture-track\",\n kind: \"track\",\n title: \"Fixture Track\",\n creatorName: \"Fixture Creator\",\n source: {\n url: \"/mock-audio/fixture-track.mp3\",\n mimeType: \"audio/mpeg\",\n crossOrigin: \"anonymous\",\n },\n durationHintSec: 180,\n waveform: {\n version: 1,\n peaks: [0.2, 0.6, 0.4, 0.8],\n },\n ...overrides,\n };\n}\n\nexport function createQueueItemFixture(\n overrides: Partial<QueueItem> = {},\n): QueueItem {\n return {\n queueItemId: \"fixture-queue-item\",\n item: createPlayerMediaItemFixture(),\n ...overrides,\n };\n}\n\nexport function createPlayerStateFixture(\n overrides: Partial<PlayerState> = {},\n experienceConfig?: Partial<PlayerExperienceConfig>,\n): Readonly<PlayerState> {\n const initial = createInitialPlayerState(experienceConfig);\n return Object.freeze({\n ...initial,\n ...overrides,\n });\n}\n"]}
@@ -0,0 +1,143 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { w as PlayerViewProps, r as PlayerMediaItem, u as PlayerState, f as PlayerCommands, d as PlayerAppearance, j as PlayerError, R as RepeatMode, b as PlaybackStatus, S as SeekCommitMode, x as PlayerWaveform } from '../player-DE9Vqztu.js';
4
+
5
+ interface CompactPlayerProps extends PlayerViewProps {
6
+ readonly className?: string;
7
+ }
8
+ declare function CompactPlayer({ appearance, className, commands, state, }: CompactPlayerProps): react.JSX.Element;
9
+
10
+ interface DetailedPlayerProps extends PlayerViewProps {
11
+ readonly className?: string;
12
+ readonly queueLimit?: number;
13
+ }
14
+ declare function DetailedPlayer({ appearance, className, commands, queueLimit, state, }: DetailedPlayerProps): react.JSX.Element;
15
+
16
+ interface MiniPlayerProps extends PlayerViewProps {
17
+ readonly className?: string;
18
+ }
19
+ declare function MiniPlayer({ appearance, className, commands, state, }: MiniPlayerProps): react.JSX.Element | null;
20
+
21
+ interface PlayerSurfaceProps {
22
+ readonly appearance: PlayerAppearance;
23
+ readonly children: ReactNode;
24
+ readonly className?: string;
25
+ readonly label: string;
26
+ }
27
+ declare function PlayerSurface({ appearance, children, className, label, }: PlayerSurfaceProps): react.JSX.Element;
28
+ interface ArtworkProps {
29
+ readonly item: PlayerMediaItem | null;
30
+ readonly className?: string;
31
+ }
32
+ declare function Artwork({ className, item }: ArtworkProps): react.JSX.Element;
33
+ interface TrackMetadataProps {
34
+ readonly item: PlayerMediaItem | null;
35
+ readonly status?: string;
36
+ readonly className?: string;
37
+ readonly scrollTitle?: boolean;
38
+ }
39
+ declare function TrackMetadata({ className, item, scrollTitle, status, }: TrackMetadataProps): react.JSX.Element;
40
+ interface TimeReadoutProps {
41
+ readonly currentTimeSec: number;
42
+ readonly durationSec: number | null;
43
+ readonly className?: string;
44
+ }
45
+ declare function TimeReadout({ className, currentTimeSec, durationSec, }: TimeReadoutProps): react.JSX.Element;
46
+ interface IconButtonProps {
47
+ readonly label: string;
48
+ readonly children: ReactNode;
49
+ readonly onClick: () => void;
50
+ readonly disabled?: boolean;
51
+ readonly pressed?: boolean;
52
+ readonly className?: string;
53
+ }
54
+ declare function IconButton({ children, className, disabled, label, onClick, pressed, }: IconButtonProps): react.JSX.Element;
55
+ interface PrimaryPlaybackButtonProps {
56
+ readonly state: Readonly<PlayerState>;
57
+ readonly commands: PlayerCommands;
58
+ readonly className?: string;
59
+ }
60
+ declare function PrimaryPlaybackButton({ className, commands, state, }: PrimaryPlaybackButtonProps): react.JSX.Element;
61
+ interface TransportControlsProps {
62
+ readonly state: Readonly<PlayerState>;
63
+ readonly commands: PlayerCommands;
64
+ readonly compact?: boolean;
65
+ readonly showModes?: boolean;
66
+ readonly className?: string;
67
+ }
68
+ declare function TransportControls({ className, commands, compact, showModes, state, }: TransportControlsProps): react.JSX.Element;
69
+ interface VolumeControlProps {
70
+ readonly state: Readonly<PlayerState>;
71
+ readonly commands: PlayerCommands;
72
+ readonly className?: string;
73
+ }
74
+ declare function VolumeControl({ className, commands, state, }: VolumeControlProps): react.JSX.Element;
75
+ interface PopoverVolumeControlProps {
76
+ readonly state: Readonly<PlayerState>;
77
+ readonly commands: PlayerCommands;
78
+ readonly label: string;
79
+ readonly className?: string;
80
+ }
81
+ declare function PopoverVolumeControl({ className, commands, label, state, }: PopoverVolumeControlProps): react.JSX.Element;
82
+ interface QueueToggleProps {
83
+ readonly onClick: () => void;
84
+ readonly expanded?: boolean;
85
+ }
86
+ declare function QueueToggle({ expanded, onClick, }: QueueToggleProps): react.JSX.Element;
87
+ interface PlayerStatusProps {
88
+ readonly state: Readonly<PlayerState>;
89
+ }
90
+ declare function PlayerStatus({ state }: PlayerStatusProps): react.JSX.Element;
91
+ interface PlayerErrorNoticeProps {
92
+ readonly state: Readonly<PlayerState>;
93
+ readonly commands: PlayerCommands;
94
+ }
95
+ declare function PlayerErrorNotice({ commands, state, }: PlayerErrorNoticeProps): react.JSX.Element | null;
96
+
97
+ declare function clamp(value: number, min: number, max: number): number;
98
+ declare function formatTime(seconds: number | null): string;
99
+ declare function formatTimeAria(seconds: number): string;
100
+ declare function getRepeatLabel(mode: RepeatMode): string;
101
+ declare function getNextRepeatMode(mode: RepeatMode): RepeatMode;
102
+ declare function getStatusLabel(state: Readonly<PlayerState>): string;
103
+ declare function getErrorMessage(error: PlayerError): string;
104
+
105
+ interface PodcastPlayerProps extends PlayerViewProps {
106
+ readonly className?: string;
107
+ }
108
+ declare function PodcastPlayer({ appearance, className, commands, state, }: PodcastPlayerProps): react.JSX.Element;
109
+
110
+ interface QueuePanelProps extends PlayerViewProps {
111
+ readonly className?: string;
112
+ readonly label?: string;
113
+ readonly onClose?: () => void;
114
+ }
115
+ declare function QueuePanel({ appearance, className, commands, label, onClose, state, }: QueuePanelProps): react.JSX.Element;
116
+
117
+ interface SeekBarProps {
118
+ readonly currentTimeSec: number;
119
+ readonly durationSec: number | null;
120
+ readonly canSeek: boolean;
121
+ readonly status: PlaybackStatus;
122
+ readonly commitMode: SeekCommitMode;
123
+ readonly onSeek: (seconds: number) => void;
124
+ readonly label?: string;
125
+ readonly className?: string;
126
+ }
127
+ declare function SeekBar({ canSeek, className, commitMode, currentTimeSec, durationSec, label, onSeek, status, }: SeekBarProps): react.JSX.Element;
128
+
129
+ interface WaveformProps {
130
+ readonly waveform?: PlayerWaveform | undefined;
131
+ readonly progress?: number;
132
+ readonly playedStateEnabled?: boolean;
133
+ readonly opacity?: number;
134
+ readonly className?: string;
135
+ }
136
+ declare function Waveform({ className, opacity, playedStateEnabled, progress, waveform, }: WaveformProps): react.JSX.Element;
137
+
138
+ interface WidePlayerProps extends PlayerViewProps {
139
+ readonly className?: string;
140
+ }
141
+ declare function WidePlayer({ appearance, className, commands, state, }: WidePlayerProps): react.JSX.Element;
142
+
143
+ export { Artwork, type ArtworkProps, CompactPlayer, type CompactPlayerProps, DetailedPlayer, type DetailedPlayerProps, IconButton, type IconButtonProps, MiniPlayer, type MiniPlayerProps, PlayerErrorNotice, type PlayerErrorNoticeProps, PlayerStatus, type PlayerStatusProps, PlayerSurface, type PlayerSurfaceProps, PodcastPlayer, type PodcastPlayerProps, PopoverVolumeControl, type PopoverVolumeControlProps, PrimaryPlaybackButton, type PrimaryPlaybackButtonProps, QueuePanel, type QueuePanelProps, QueueToggle, type QueueToggleProps, SeekBar, type SeekBarProps, TimeReadout, type TimeReadoutProps, TrackMetadata, type TrackMetadataProps, TransportControls, type TransportControlsProps, VolumeControl, type VolumeControlProps, Waveform, type WaveformProps, WidePlayer, type WidePlayerProps, clamp, formatTime, formatTimeAria, getErrorMessage, getNextRepeatMode, getRepeatLabel, getStatusLabel };