@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,185 @@
1
+ import { s as PlayerMediaSource, r as PlayerMediaItem } from '../player-DE9Vqztu.js';
2
+
3
+ declare const MEDIA_DELIVERY_CONTRACT_VERSION: 1;
4
+ type SupportedUploadFormat = "wav" | "mp3";
5
+ type FutureUploadFormat = "flac" | "aiff" | "aac" | "m4a";
6
+ /**
7
+ * This classification belongs to the consumer/backend boundary. It MUST NOT be
8
+ * used by Player Core to make authorization decisions.
9
+ */
10
+ type MediaAccessClass = "public" | "free-member" | "support-member" | "purchased" | "admin" | "unpublished";
11
+ type MediaSourceRole = "public" | "protected" | "preview";
12
+ type UploadHeaderFormat = SupportedUploadFormat | "unknown";
13
+ /**
14
+ * Evidence produced by the upload service. `detectedHeaderFormat` must come
15
+ * from a trusted parser/decoder, not from the file name or declared MIME type.
16
+ */
17
+ interface MediaUploadEvidence {
18
+ readonly extension: string;
19
+ readonly declaredMimeType: string;
20
+ readonly detectedHeaderFormat: UploadHeaderFormat;
21
+ }
22
+ type UploadValidationErrorCode = "unsupported-extension" | "unsupported-mime-type" | "unrecognized-header" | "extension-header-mismatch" | "mime-header-mismatch";
23
+ interface UploadValidationError {
24
+ readonly code: UploadValidationErrorCode;
25
+ readonly field: "extension" | "declaredMimeType" | "detectedHeaderFormat";
26
+ }
27
+ interface NormalizedUploadIdentity {
28
+ readonly format: SupportedUploadFormat;
29
+ readonly extension: ".wav" | ".mp3";
30
+ /** Canonical delivery-independent MIME identity. */
31
+ readonly mimeType: "audio/wave" | "audio/mpeg";
32
+ }
33
+ type MediaUploadValidationResult = {
34
+ readonly ok: true;
35
+ readonly value: NormalizedUploadIdentity;
36
+ readonly errors: readonly [];
37
+ } | {
38
+ readonly ok: false;
39
+ readonly errors: readonly UploadValidationError[];
40
+ };
41
+ type AudioSampleEncoding = "pcm-integer" | "ieee-float" | "lossy" | "other";
42
+ interface MediaLoudnessMetadata {
43
+ /** Integrated loudness in LUFS when the processor supports it. */
44
+ readonly integratedLufs: number | null;
45
+ /** Maximum decoded sample peak in dBFS. */
46
+ readonly samplePeakDbfs: number | null;
47
+ /** Optional oversampled true peak in dBTP. */
48
+ readonly truePeakDbtp: number | null;
49
+ }
50
+ /**
51
+ * Normalized mono envelope. Each value is the maximum absolute amplitude in
52
+ * one equal-duration bucket after taking the maximum across source channels.
53
+ */
54
+ interface WaveformPeaksV1 {
55
+ readonly version: 1;
56
+ readonly aggregation: "max-absolute";
57
+ readonly channelMode: "merged";
58
+ readonly peaks: readonly number[];
59
+ }
60
+ interface ProcessedMediaMetadata {
61
+ readonly contractVersion: typeof MEDIA_DELIVERY_CONTRACT_VERSION;
62
+ readonly mediaAssetId: string;
63
+ readonly processingRevision: string;
64
+ readonly durationSec: number;
65
+ /**
66
+ * Intentionally numeric rather than an enum. Upload policy does not fix a
67
+ * single sample rate.
68
+ */
69
+ readonly sampleRateHz: number;
70
+ /**
71
+ * Null when bit depth is not meaningful for the encoded format (for example
72
+ * MP3).
73
+ */
74
+ readonly bitDepth: number | null;
75
+ readonly sampleEncoding: AudioSampleEncoding;
76
+ /** Processor-reported codec identifier; not interpreted by Player Core. */
77
+ readonly codec: string;
78
+ readonly channelCount: number;
79
+ readonly bitrateBps: number | null;
80
+ readonly loudness: MediaLoudnessMetadata;
81
+ }
82
+ /**
83
+ * Stable, URL-free identity for one generated delivery representation.
84
+ * Container and codec are strings so the delivery format can evolve without a
85
+ * Player contract revision.
86
+ */
87
+ interface MediaDeliveryDescriptor {
88
+ readonly contractVersion: typeof MEDIA_DELIVERY_CONTRACT_VERSION;
89
+ readonly deliveryId: string;
90
+ readonly mediaAssetId: string;
91
+ readonly deliveryRevision: string;
92
+ readonly container: string;
93
+ readonly codec: string;
94
+ readonly mimeType: string;
95
+ readonly byteLength: number;
96
+ readonly durationSec: number;
97
+ readonly rangeMode: "bytes";
98
+ readonly metadataId: string;
99
+ readonly waveformId: string;
100
+ }
101
+ /**
102
+ * Opaque backend storage references. These are never Player input and must not
103
+ * be interpreted as public URLs.
104
+ */
105
+ interface MediaStorageReferences {
106
+ readonly sourceMasterRef: string;
107
+ readonly deliveryAudioRef: string;
108
+ readonly waveformRef: string;
109
+ readonly artworkRef: string | null;
110
+ readonly metadataRef: string;
111
+ }
112
+ /**
113
+ * A Host-owned candidate. Host may retain multiple candidates, but only
114
+ * candidates currently playable by the user may be returned to Player Runtime.
115
+ */
116
+ interface MediaDeliverySourceReference {
117
+ readonly sourceId: string;
118
+ readonly deliveryId: string;
119
+ readonly role: MediaSourceRole;
120
+ readonly priority: number;
121
+ }
122
+ type PlayerMediaDescriptor = Omit<PlayerMediaItem, "source"> & {
123
+ /**
124
+ * Ordered Host-approved candidates. This object contains no playback URL,
125
+ * token, cookie, Authorization value, or access-policy decision.
126
+ */
127
+ readonly sourceSet: readonly MediaDeliverySourceReference[];
128
+ };
129
+ type PlaybackSourceResolveReason = "initial" | "expires-soon" | "expired" | "recoverable-network-error" | "manual-retry";
130
+ interface PlaybackSourceResolveRequest {
131
+ readonly mediaItemId: string;
132
+ readonly sourceSet: readonly MediaDeliverySourceReference[];
133
+ readonly reason: PlaybackSourceResolveReason;
134
+ /** Opaque non-secret correlation id from the previous successful response. */
135
+ readonly currentLeaseId?: string;
136
+ readonly resumeAtSec?: number;
137
+ }
138
+ /**
139
+ * The only contract that carries a playback URL. It is an ephemeral in-memory
140
+ * lease and must not be serialized, persisted, logged, or placed in analytics.
141
+ */
142
+ interface ResolvedPlaybackSource extends PlayerMediaSource {
143
+ readonly sourceId: string;
144
+ readonly deliveryId: string;
145
+ readonly leaseId: string;
146
+ /**
147
+ * Optional absolute epoch time. When present, Runtime refreshes before use
148
+ * after this time; the refresh margin remains Host configuration.
149
+ */
150
+ readonly expiresAtEpochMs?: number;
151
+ readonly rangeMode: "bytes";
152
+ }
153
+ type PlaybackSourceResolveErrorCode = "not-playable" | "source-unavailable" | "temporarily-unavailable";
154
+ type PlaybackSourceResolveResult = {
155
+ readonly ok: true;
156
+ readonly source: ResolvedPlaybackSource;
157
+ } | {
158
+ readonly ok: false;
159
+ readonly error: {
160
+ readonly code: PlaybackSourceResolveErrorCode;
161
+ readonly recoverable: boolean;
162
+ readonly messageKey: string;
163
+ };
164
+ };
165
+ /**
166
+ * Consumer-owned boundary. Authentication, membership, purchase, publication,
167
+ * rights, and URL signing stay behind this interface.
168
+ */
169
+ interface PlayerDeliveryHostAdapter<TConsumerMedia> {
170
+ /**
171
+ * Returns null when the consumer declines to expose playable media. Returned
172
+ * source references have already passed consumer access policy.
173
+ */
174
+ toPlayerMediaDescriptor(source: TConsumerMedia): PlayerMediaDescriptor | null;
175
+ /**
176
+ * Resolves or refreshes one short-lived playback lease. The request never
177
+ * echoes the previous URL or any credential.
178
+ */
179
+ resolvePlaybackSource(request: PlaybackSourceResolveRequest): Promise<PlaybackSourceResolveResult>;
180
+ }
181
+
182
+ declare function validateMediaUploadEvidence(evidence: MediaUploadEvidence): MediaUploadValidationResult;
183
+ declare function validateWaveformPeaks(waveform: WaveformPeaksV1): readonly string[];
184
+
185
+ export { type AudioSampleEncoding, type FutureUploadFormat, MEDIA_DELIVERY_CONTRACT_VERSION, type MediaAccessClass, type MediaDeliveryDescriptor, type MediaDeliverySourceReference, type MediaLoudnessMetadata, type MediaSourceRole, type MediaStorageReferences, type MediaUploadEvidence, type MediaUploadValidationResult, type NormalizedUploadIdentity, type PlaybackSourceResolveErrorCode, type PlaybackSourceResolveReason, type PlaybackSourceResolveRequest, type PlaybackSourceResolveResult, type PlayerDeliveryHostAdapter, type PlayerMediaDescriptor, type ProcessedMediaMetadata, type ResolvedPlaybackSource, type SupportedUploadFormat, type UploadHeaderFormat, type UploadValidationError, type UploadValidationErrorCode, type WaveformPeaksV1, validateMediaUploadEvidence, validateWaveformPeaks };
@@ -0,0 +1,91 @@
1
+ // src/media-delivery/validation.ts
2
+ var EXTENSION_FORMATS = /* @__PURE__ */ new Map([
3
+ [".wav", "wav"],
4
+ [".mp3", "mp3"]
5
+ ]);
6
+ var MIME_FORMATS = /* @__PURE__ */ new Map([
7
+ ["audio/wave", "wav"],
8
+ ["audio/wav", "wav"],
9
+ ["audio/x-wav", "wav"],
10
+ ["audio/vnd.wave", "wav"],
11
+ ["audio/mpeg", "mp3"],
12
+ ["audio/mp3", "mp3"]
13
+ ]);
14
+ function normalizeExtension(extension) {
15
+ const normalized = extension.trim().toLowerCase();
16
+ return normalized.startsWith(".") ? normalized : `.${normalized}`;
17
+ }
18
+ function normalizeMimeType(mimeType) {
19
+ return mimeType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
20
+ }
21
+ function validateMediaUploadEvidence(evidence) {
22
+ const extension = normalizeExtension(evidence.extension);
23
+ const mimeType = normalizeMimeType(evidence.declaredMimeType);
24
+ const extensionFormat = EXTENSION_FORMATS.get(extension);
25
+ const mimeFormat = MIME_FORMATS.get(mimeType);
26
+ const headerFormat = evidence.detectedHeaderFormat === "unknown" ? void 0 : evidence.detectedHeaderFormat;
27
+ const errors = [];
28
+ if (extensionFormat === void 0) {
29
+ errors.push({
30
+ code: "unsupported-extension",
31
+ field: "extension"
32
+ });
33
+ }
34
+ if (mimeFormat === void 0) {
35
+ errors.push({
36
+ code: "unsupported-mime-type",
37
+ field: "declaredMimeType"
38
+ });
39
+ }
40
+ if (headerFormat === void 0) {
41
+ errors.push({
42
+ code: "unrecognized-header",
43
+ field: "detectedHeaderFormat"
44
+ });
45
+ }
46
+ if (extensionFormat !== void 0 && headerFormat !== void 0 && extensionFormat !== headerFormat) {
47
+ errors.push({
48
+ code: "extension-header-mismatch",
49
+ field: "extension"
50
+ });
51
+ }
52
+ if (mimeFormat !== void 0 && headerFormat !== void 0 && mimeFormat !== headerFormat) {
53
+ errors.push({
54
+ code: "mime-header-mismatch",
55
+ field: "declaredMimeType"
56
+ });
57
+ }
58
+ if (errors.length > 0 || extensionFormat === void 0 || mimeFormat === void 0 || headerFormat === void 0) {
59
+ return Object.freeze({
60
+ ok: false,
61
+ errors: Object.freeze(errors)
62
+ });
63
+ }
64
+ return Object.freeze({
65
+ ok: true,
66
+ value: Object.freeze({
67
+ format: headerFormat,
68
+ extension: headerFormat === "wav" ? ".wav" : ".mp3",
69
+ mimeType: headerFormat === "wav" ? "audio/wave" : "audio/mpeg"
70
+ }),
71
+ errors: []
72
+ });
73
+ }
74
+ function validateWaveformPeaks(waveform) {
75
+ const errors = [];
76
+ if (waveform.peaks.length === 0) {
77
+ errors.push("waveform.peaks must contain at least one value.");
78
+ }
79
+ waveform.peaks.forEach((peak, index) => {
80
+ if (!Number.isFinite(peak) || peak < 0 || peak > 1) {
81
+ errors.push(
82
+ `waveform.peaks[${String(index)}] must be a finite number between 0 and 1.`
83
+ );
84
+ }
85
+ });
86
+ return Object.freeze(errors);
87
+ }
88
+
89
+ export { validateMediaUploadEvidence, validateWaveformPeaks };
90
+ //# sourceMappingURL=index.js.map
91
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/media-delivery/validation.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type {\n MediaUploadEvidence,\n MediaUploadValidationResult,\n SupportedUploadFormat,\n UploadValidationError,\n WaveformPeaksV1,\n} from \"./contracts\";\n\nconst EXTENSION_FORMATS = new Map<string, SupportedUploadFormat>([\n [\".wav\", \"wav\"],\n [\".mp3\", \"mp3\"],\n]);\n\nconst MIME_FORMATS = new Map<string, SupportedUploadFormat>([\n [\"audio/wave\", \"wav\"],\n [\"audio/wav\", \"wav\"],\n [\"audio/x-wav\", \"wav\"],\n [\"audio/vnd.wave\", \"wav\"],\n [\"audio/mpeg\", \"mp3\"],\n [\"audio/mp3\", \"mp3\"],\n]);\n\nfunction normalizeExtension(extension: string): string {\n const normalized = extension.trim().toLowerCase();\n return normalized.startsWith(\".\") ? normalized : `.${normalized}`;\n}\n\nfunction normalizeMimeType(mimeType: string): string {\n return mimeType.split(\";\", 1)[0]?.trim().toLowerCase() ?? \"\";\n}\n\nexport function validateMediaUploadEvidence(\n evidence: MediaUploadEvidence,\n): MediaUploadValidationResult {\n const extension = normalizeExtension(evidence.extension);\n const mimeType = normalizeMimeType(evidence.declaredMimeType);\n const extensionFormat = EXTENSION_FORMATS.get(extension);\n const mimeFormat = MIME_FORMATS.get(mimeType);\n const headerFormat =\n evidence.detectedHeaderFormat === \"unknown\"\n ? undefined\n : evidence.detectedHeaderFormat;\n const errors: UploadValidationError[] = [];\n\n if (extensionFormat === undefined) {\n errors.push({\n code: \"unsupported-extension\",\n field: \"extension\",\n });\n }\n if (mimeFormat === undefined) {\n errors.push({\n code: \"unsupported-mime-type\",\n field: \"declaredMimeType\",\n });\n }\n if (headerFormat === undefined) {\n errors.push({\n code: \"unrecognized-header\",\n field: \"detectedHeaderFormat\",\n });\n }\n if (\n extensionFormat !== undefined &&\n headerFormat !== undefined &&\n extensionFormat !== headerFormat\n ) {\n errors.push({\n code: \"extension-header-mismatch\",\n field: \"extension\",\n });\n }\n if (\n mimeFormat !== undefined &&\n headerFormat !== undefined &&\n mimeFormat !== headerFormat\n ) {\n errors.push({\n code: \"mime-header-mismatch\",\n field: \"declaredMimeType\",\n });\n }\n\n if (\n errors.length > 0 ||\n extensionFormat === undefined ||\n mimeFormat === undefined ||\n headerFormat === undefined\n ) {\n return Object.freeze({\n ok: false,\n errors: Object.freeze(errors),\n });\n }\n\n return Object.freeze({\n ok: true,\n value: Object.freeze({\n format: headerFormat,\n extension: headerFormat === \"wav\" ? \".wav\" : \".mp3\",\n mimeType: headerFormat === \"wav\" ? \"audio/wave\" : \"audio/mpeg\",\n }),\n errors: [] as const,\n });\n}\n\nexport function validateWaveformPeaks(\n waveform: WaveformPeaksV1,\n): readonly string[] {\n const errors: string[] = [];\n\n if (waveform.peaks.length === 0) {\n errors.push(\"waveform.peaks must contain at least one value.\");\n }\n\n waveform.peaks.forEach((peak, index) => {\n if (!Number.isFinite(peak) || peak < 0 || peak > 1) {\n errors.push(\n `waveform.peaks[${String(index)}] must be a finite number between 0 and 1.`,\n );\n }\n });\n\n return Object.freeze(errors);\n}\n"]}
@@ -0,0 +1,40 @@
1
+ import { j as PlayerError, s as PlayerMediaSource } from './player-DE9Vqztu.js';
2
+
3
+ type PlayerMediaEventType = "loadstart" | "loadedmetadata" | "canplay" | "play" | "playing" | "pause" | "waiting" | "stalled" | "seeking" | "seeked" | "timeupdate" | "ended" | "error" | "durationchange" | "volumechange";
4
+ interface PlayerMediaEvent {
5
+ readonly type: PlayerMediaEventType;
6
+ }
7
+ type PlayerMediaEventListener = (event: PlayerMediaEvent) => void;
8
+ interface MediaElementStateSnapshot {
9
+ readonly currentTimeSec: number;
10
+ readonly durationSec: number | null;
11
+ readonly volume: number;
12
+ readonly muted: boolean;
13
+ readonly paused: boolean;
14
+ readonly ended: boolean;
15
+ /** Confirms that the element still has an assigned/current source. */
16
+ readonly hasCurrentSource: boolean;
17
+ /** Compares the current source with the last source assigned by this port. */
18
+ readonly currentSourceMatchesAssigned: boolean;
19
+ }
20
+ interface MediaElementPort {
21
+ getCurrentTimeSec(): number;
22
+ getDurationSec(): number | null;
23
+ getVolume(): number;
24
+ isMuted(): boolean;
25
+ isPaused(): boolean;
26
+ getStateSnapshot(): MediaElementStateSnapshot;
27
+ getError(): PlayerError | null;
28
+ setSource(source: PlayerMediaSource): void;
29
+ clearSource(): void;
30
+ load(): void;
31
+ play(): Promise<void>;
32
+ pause(): void;
33
+ seekTo(seconds: number): void;
34
+ setVolume(volume: number): void;
35
+ setMuted(muted: boolean): void;
36
+ addEventListener(type: PlayerMediaEventType, listener: PlayerMediaEventListener): void;
37
+ removeEventListener(type: PlayerMediaEventType, listener: PlayerMediaEventListener): void;
38
+ }
39
+
40
+ export type { MediaElementPort as M, PlayerMediaEvent as P, MediaElementStateSnapshot as a, PlayerMediaEventListener as b, PlayerMediaEventType as c };
@@ -0,0 +1,212 @@
1
+ type PlaybackStatus = "idle" | "loading" | "playing" | "paused" | "seeking" | "ended" | "error";
2
+ type PlaybackTransitionCause = "user-play" | "user-pause" | "user-stop" | "natural-end" | "os-interruption" | "background-suspension" | "autoplay-blocked" | "source-error";
3
+ type PlayerMediaSyncReason = "visibilitychange" | "pageshow" | "focus" | "audio-context-statechange" | "manual";
4
+ type RepeatMode = "off" | "all" | "one";
5
+ type MediaKind = "track" | "podcastEpisode";
6
+ type PlayerAppearance = "dark" | "light";
7
+ type FadeCurve = "linear" | "ease-in" | "ease-out" | "equal-power";
8
+ type SeekCommitMode = "live" | "on-release";
9
+ type PersistMode = "none" | "session" | "local";
10
+ type PodcastPreviousMode = "previous-episode" | "restart-episode";
11
+ interface PlayerExperienceConfig {
12
+ readonly playFadeInMs: number;
13
+ readonly pauseFadeOutMs: number;
14
+ readonly trackChangeFadeOutMs: number;
15
+ readonly trackChangeFadeInMs: number;
16
+ readonly seekFadeMs: number;
17
+ readonly fadeCurve: FadeCurve;
18
+ readonly previousRestartThresholdSec: number;
19
+ readonly seekCommitMode: SeekCommitMode;
20
+ /** Whether the played waveform region is visually distinguished. */
21
+ readonly waveformPlayedStateEnabled: boolean;
22
+ /** Overall waveform opacity in the inclusive range 0..1. */
23
+ readonly waveformOpacity: number;
24
+ readonly initialShuffleEnabled: boolean;
25
+ readonly initialRepeatMode: RepeatMode;
26
+ readonly initialVolume: number;
27
+ readonly persistMode: PersistMode;
28
+ readonly podcastBackSec: number;
29
+ readonly podcastForwardSec: number;
30
+ readonly podcastPreviousMode: PodcastPreviousMode;
31
+ }
32
+ interface PlayerExperienceConfigEnvelope {
33
+ readonly schemaVersion: 2;
34
+ readonly config: PlayerExperienceConfig;
35
+ }
36
+ interface PlayerMediaSource {
37
+ readonly url: string;
38
+ readonly mimeType?: string;
39
+ readonly crossOrigin: "anonymous";
40
+ }
41
+ interface PlayerWaveform {
42
+ readonly version: 1;
43
+ readonly peaks: readonly number[];
44
+ }
45
+ interface PlayerMediaItem {
46
+ readonly id: string;
47
+ readonly kind: MediaKind;
48
+ readonly title: string;
49
+ readonly creatorName?: string;
50
+ readonly collectionTitle?: string;
51
+ readonly artwork?: {
52
+ readonly url: string;
53
+ readonly alt: string;
54
+ };
55
+ readonly source: PlayerMediaSource;
56
+ readonly durationHintSec?: number;
57
+ readonly waveform?: PlayerWaveform;
58
+ }
59
+ interface QueueItem {
60
+ readonly queueItemId: string;
61
+ readonly item: PlayerMediaItem;
62
+ }
63
+ type PlayerErrorCode = "autoplay-blocked" | "network" | "cors" | "decode" | "unsupported" | "audio-context" | "unknown";
64
+ interface PlayerError {
65
+ readonly code: PlayerErrorCode;
66
+ readonly recoverable: boolean;
67
+ readonly messageKey: string;
68
+ }
69
+ interface PlayerState {
70
+ readonly currentItem: PlayerMediaItem | null;
71
+ readonly currentItemId: string | null;
72
+ readonly currentQueueItemId: string | null;
73
+ readonly mediaKind: MediaKind | null;
74
+ readonly status: PlaybackStatus;
75
+ readonly currentTimeSec: number;
76
+ readonly durationSec: number | null;
77
+ readonly volume: number;
78
+ readonly muted: boolean;
79
+ readonly shuffleEnabled: boolean;
80
+ readonly repeatMode: RepeatMode;
81
+ readonly queue: readonly QueueItem[];
82
+ readonly currentQueueIndex: number | null;
83
+ readonly canSeek: boolean;
84
+ readonly canGoPrevious: boolean;
85
+ readonly canGoNext: boolean;
86
+ readonly needsUserGesture: boolean;
87
+ /**
88
+ * Last meaningful playback transition. External media pauses are never
89
+ * persisted as a user pause.
90
+ */
91
+ readonly lastTransitionCause: PlaybackTransitionCause | null;
92
+ readonly error: PlayerError | null;
93
+ readonly experienceConfig: Readonly<PlayerExperienceConfig>;
94
+ }
95
+ interface SetQueueOptions {
96
+ readonly startQueueItemId?: string;
97
+ readonly autoplay?: boolean;
98
+ }
99
+ interface PauseOptions {
100
+ /** When omitted, Core applies the configured pause fade. */
101
+ readonly fade?: boolean;
102
+ }
103
+ interface StopOptions {
104
+ /** When omitted, Core applies the configured stop/pause fade. */
105
+ readonly fade?: boolean;
106
+ /** Defaults to true. Stop keeps the item and queue even when reset. */
107
+ readonly resetPosition?: boolean;
108
+ }
109
+ interface PlayerCommandResult {
110
+ readonly ok: boolean;
111
+ readonly error?: PlayerError;
112
+ }
113
+ interface PlayerCommands {
114
+ load(item: PlayerMediaItem): Promise<PlayerCommandResult>;
115
+ setQueue(queue: readonly QueueItem[], options?: SetQueueOptions): Promise<PlayerCommandResult>;
116
+ play(): Promise<PlayerCommandResult>;
117
+ pause(options?: PauseOptions): Promise<void>;
118
+ stop(options?: StopOptions): Promise<void>;
119
+ togglePlayback(): Promise<PlayerCommandResult>;
120
+ seekTo(seconds: number): void;
121
+ seekBy(deltaSeconds: number): void;
122
+ setVolume(volume: number): void;
123
+ toggleMute(): void;
124
+ setShuffle(enabled: boolean): void;
125
+ setRepeatMode(mode: RepeatMode): void;
126
+ next(): Promise<PlayerCommandResult>;
127
+ previous(): Promise<PlayerCommandResult>;
128
+ retry(): Promise<PlayerCommandResult>;
129
+ /** Stops at zero, then removes the current item, queue, and error. */
130
+ clear(): Promise<void>;
131
+ }
132
+ interface PlayerExperienceConfigStore {
133
+ getConfig(): Readonly<PlayerExperienceConfig>;
134
+ updateConfig(patch: Partial<PlayerExperienceConfig>): void;
135
+ resetConfig(): void;
136
+ subscribe(listener: () => void): () => void;
137
+ }
138
+ interface PlayerStore {
139
+ getState(): PlayerState;
140
+ subscribe(listener: () => void): () => void;
141
+ readonly commands: PlayerCommands;
142
+ readonly experience: PlayerExperienceConfigStore;
143
+ /**
144
+ * Re-reads the media element after page/background lifecycle transitions.
145
+ * This is Runtime infrastructure, not a UI command.
146
+ */
147
+ synchronizeFromMedia(reason: PlayerMediaSyncReason): Promise<void>;
148
+ /** Runtime-unmount cleanup; not a normal UI command. */
149
+ destroy(): Promise<void>;
150
+ }
151
+ interface PlayerMediaAdapter<TSource> {
152
+ /**
153
+ * Maps already-authorized media with a directly playable Delivery Audio URL.
154
+ * This covers public immutable delivery media as well as local/fixture media.
155
+ * Protected expiring delivery uses PlayerDeliveryHostAdapter from
156
+ * /media-delivery instead.
157
+ */
158
+ toPlayerMediaItem(source: TSource): PlayerMediaItem | null;
159
+ }
160
+ interface PlayerHostConfig {
161
+ readonly appearance: PlayerAppearance;
162
+ readonly experienceConfig?: Partial<PlayerExperienceConfig>;
163
+ readonly miniPlayer: {
164
+ readonly enabled: boolean;
165
+ readonly onInsetChange?: (bottomInsetPx: number) => void;
166
+ };
167
+ readonly persistenceNamespace: string;
168
+ }
169
+ interface PlayerViewProps {
170
+ readonly state: Readonly<PlayerState>;
171
+ readonly commands: PlayerCommands;
172
+ readonly appearance: PlayerAppearance;
173
+ }
174
+ interface PlayerExperiencePreset {
175
+ readonly id: string;
176
+ readonly name: string;
177
+ readonly config: PlayerExperienceConfig;
178
+ readonly updatedAt: string;
179
+ }
180
+ interface PlayerConfigImportResult {
181
+ readonly ok: boolean;
182
+ readonly errors: readonly string[];
183
+ }
184
+ interface PlayerDebugSnapshot {
185
+ readonly config: Readonly<PlayerExperienceConfig>;
186
+ readonly presets: readonly PlayerExperiencePreset[];
187
+ readonly activePresetId: string | null;
188
+ readonly lastConfigImportResult: PlayerConfigImportResult | null;
189
+ }
190
+ interface PlayerDebugController {
191
+ getConfig(): Readonly<PlayerExperienceConfig>;
192
+ /** Immutable external-store snapshot for useSyncExternalStore-style consumers. */
193
+ getSnapshot(): Readonly<PlayerDebugSnapshot>;
194
+ subscribe(listener: () => void): () => void;
195
+ /** A manual patch clears activePresetId because the config has diverged. */
196
+ applyConfig(patch: Partial<PlayerExperienceConfig>): void;
197
+ /** Restores preview defaults and clears activePresetId. */
198
+ resetConfig(): void;
199
+ copyConfigJson(): Promise<void>;
200
+ /** Atomic import; a successful import clears activePresetId. */
201
+ loadConfigJson(json: string): PlayerConfigImportResult;
202
+ listPresets(): readonly PlayerExperiencePreset[];
203
+ /** Saves the current config and makes the new preset active. */
204
+ savePreset(name: string): PlayerExperiencePreset;
205
+ /** Applies the preset atomically and makes its id active. */
206
+ applyPreset(id: string): PlayerConfigImportResult;
207
+ /** Deleting the active preset clears its id but keeps the current config. */
208
+ deletePreset(id: string): void;
209
+ exportProductionConfigJson(): string;
210
+ }
211
+
212
+ export type { StopOptions as A, FadeCurve as F, MediaKind as M, PauseOptions as P, QueueItem as Q, RepeatMode as R, SeekCommitMode as S, PersistMode as a, PlaybackStatus as b, PlaybackTransitionCause as c, PlayerAppearance as d, PlayerCommandResult as e, PlayerCommands as f, PlayerConfigImportResult as g, PlayerDebugController as h, PlayerDebugSnapshot as i, PlayerError as j, PlayerErrorCode as k, PlayerExperienceConfig as l, PlayerExperienceConfigEnvelope as m, PlayerExperienceConfigStore as n, PlayerExperiencePreset as o, PlayerHostConfig as p, PlayerMediaAdapter as q, PlayerMediaItem as r, PlayerMediaSource as s, PlayerMediaSyncReason as t, PlayerState as u, PlayerStore as v, PlayerViewProps as w, PlayerWaveform as x, PodcastPreviousMode as y, SetQueueOptions as z };
@@ -0,0 +1,32 @@
1
+ import { F as FadeCurve } from '../player-DE9Vqztu.js';
2
+ export { M as MediaElementPort, a as MediaElementStateSnapshot, P as PlayerMediaEvent, b as PlayerMediaEventListener, c as PlayerMediaEventType } from '../media-element-C9dis4aG.js';
3
+
4
+ interface GainTransition {
5
+ readonly durationMs: number;
6
+ readonly curve: FadeCurve;
7
+ }
8
+ interface GainPort {
9
+ getValue(): number;
10
+ setValue(value: number): void;
11
+ cancelTransition(): void;
12
+ transitionTo(value: number, transition: GainTransition): Promise<void>;
13
+ destroy(): Promise<void>;
14
+ }
15
+
16
+ interface PlayerPersistencePort {
17
+ read(namespace: string, key: string): string | null;
18
+ write(namespace: string, key: string, value: string): void;
19
+ remove(namespace: string, key: string): void;
20
+ }
21
+ interface PodcastProgress {
22
+ readonly itemId: string;
23
+ readonly positionSec: number;
24
+ readonly updatedAt: string;
25
+ }
26
+ interface PodcastProgressPort {
27
+ load(itemId: string): Promise<PodcastProgress | null>;
28
+ save(progress: PodcastProgress): Promise<void>;
29
+ remove(itemId: string): Promise<void>;
30
+ }
31
+
32
+ export type { GainPort, GainTransition, PlayerPersistencePort, PodcastProgress, PodcastProgressPort };
@@ -0,0 +1,3 @@
1
+
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,86 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { f as PlayerCommands, t as PlayerMediaSyncReason, Q as QueueItem, l as PlayerExperienceConfig, v as PlayerStore, n as PlayerExperienceConfigStore, u as PlayerState } from '../player-DE9Vqztu.js';
4
+ import { c as PlayerMediaEventType, a as MediaElementStateSnapshot } from '../media-element-C9dis4aG.js';
5
+
6
+ type PlayerAudioMode = "auto" | "web-audio" | "html-media";
7
+ type ResolvedPlayerAudioMode = "web-audio" | "html-media";
8
+
9
+ type PlayerRuntimeCommandName = keyof PlayerCommands;
10
+
11
+ type PlayerRuntimeDiagnosticPayload = {
12
+ readonly type: "command";
13
+ readonly command: PlayerRuntimeCommandName;
14
+ } | {
15
+ readonly type: "media-event";
16
+ readonly event: PlayerMediaEventType;
17
+ readonly media: MediaElementStateSnapshot;
18
+ } | {
19
+ readonly type: "lifecycle-sync";
20
+ readonly reason: PlayerMediaSyncReason;
21
+ readonly visibility: DocumentVisibilityState;
22
+ readonly media: MediaElementStateSnapshot;
23
+ } | {
24
+ readonly type: "audio-context-statechange";
25
+ readonly state: AudioContextState | "uninitialized";
26
+ } | {
27
+ readonly type: "audio-context-resume";
28
+ readonly outcome: "resumed" | "failed";
29
+ readonly state: AudioContextState | "uninitialized";
30
+ readonly errorCode: "audio-context" | null;
31
+ } | {
32
+ readonly type: "media-session-action";
33
+ readonly action: MediaSessionAction;
34
+ readonly failed: boolean;
35
+ };
36
+ type PlayerRuntimeDiagnosticEvent = PlayerRuntimeDiagnosticPayload & {
37
+ readonly occurredAt: string;
38
+ };
39
+ type PlayerRuntimeDiagnosticListener = (event: PlayerRuntimeDiagnosticEvent) => void;
40
+
41
+ interface PlayerProviderProps {
42
+ readonly children: ReactNode;
43
+ /** Read once when the Runtime is created. Later queue changes use commands. */
44
+ readonly initialQueue?: readonly QueueItem[];
45
+ readonly initialQueueItemId?: string;
46
+ readonly experienceConfig?: Partial<PlayerExperienceConfig>;
47
+ readonly loadingFallback?: ReactNode;
48
+ /**
49
+ * auto uses native HTML audio on iOS/iPadOS and Web Audio elsewhere.
50
+ * Explicit modes exist for real-device comparison and host policy.
51
+ */
52
+ readonly audioMode?: PlayerAudioMode;
53
+ /** Optional host observer. The runtime does not retain diagnostic history. */
54
+ readonly onDiagnosticEvent?: PlayerRuntimeDiagnosticListener;
55
+ }
56
+ interface PlayerRuntimeContextValue {
57
+ readonly store: PlayerStore;
58
+ readonly commands: PlayerCommands;
59
+ readonly experience: PlayerExperienceConfigStore;
60
+ readonly audioContextState: AudioContextState | "uninitialized";
61
+ readonly audioMode: ResolvedPlayerAudioMode;
62
+ readonly mediaSessionSupported: boolean;
63
+ readonly mediaSessionActionsEnabled: boolean;
64
+ }
65
+ declare function PlayerProvider({ audioMode, children, experienceConfig, initialQueue, initialQueueItemId, loadingFallback, onDiagnosticEvent, }: PlayerProviderProps): react.JSX.Element;
66
+
67
+ interface MiniPlayerHostProps {
68
+ readonly children: ReactNode;
69
+ readonly visible: boolean;
70
+ readonly onInsetChange?: (bottomInsetPx: number) => void;
71
+ readonly className?: string;
72
+ }
73
+ declare function MiniPlayerHost({ children, className, onInsetChange, visible, }: MiniPlayerHostProps): react.JSX.Element | null;
74
+
75
+ interface UsePlayerResult {
76
+ readonly state: Readonly<PlayerState>;
77
+ readonly commands: PlayerCommands;
78
+ readonly experience: PlayerExperienceConfigStore;
79
+ readonly audioContextState: AudioContextState | "uninitialized";
80
+ readonly audioMode: ResolvedPlayerAudioMode;
81
+ readonly mediaSessionSupported: boolean;
82
+ readonly mediaSessionActionsEnabled: boolean;
83
+ }
84
+ declare function usePlayer(): UsePlayerResult;
85
+
86
+ export { MiniPlayerHost, type MiniPlayerHostProps, PlayerProvider, type PlayerProviderProps, type PlayerRuntimeContextValue, type PlayerRuntimeDiagnosticEvent, type PlayerRuntimeDiagnosticListener, type PlayerRuntimeDiagnosticPayload, type UsePlayerResult, usePlayer };