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

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
@@ -39,6 +39,20 @@ The public npm registry, scoped package name, and release workflow are fixed.
39
39
  Initial npm publication still requires the scope owner's authentication.
40
40
  Release and consumer pinning rules are documented in `docs/RELEASING.md`.
41
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
+
42
56
  ## WP-04.4 SSR-transparent Provider
43
57
 
44
58
  Version 0.2.2 renders consumer children inside the same Player Context from
@@ -2951,100 +2951,6 @@ function IconButton({
2951
2951
  }
2952
2952
  );
2953
2953
  }
2954
- function SeekBar({
2955
- canSeek,
2956
- className,
2957
- commitMode,
2958
- currentTimeSec,
2959
- durationSec,
2960
- label = "Seek",
2961
- onSeek,
2962
- status
2963
- }) {
2964
- const duration = durationSec !== null && Number.isFinite(durationSec) && durationSec > 0 ? durationSec : 0;
2965
- const disabled = !canSeek || duration === 0 || status === "loading";
2966
- const safeCurrentTime = clamp(currentTimeSec, 0, duration);
2967
- const [previewTime, setPreviewTime] = useState(safeCurrentTime);
2968
- const previewProgress = duration === 0 ? 0 : clamp(previewTime / duration, 0, 1) * 100;
2969
- const isAdjusting = useRef(false);
2970
- const lastCommitted = useRef(null);
2971
- useEffect(() => {
2972
- if (!isAdjusting.current) {
2973
- setPreviewTime(safeCurrentTime);
2974
- }
2975
- }, [safeCurrentTime]);
2976
- function updatePreview(value) {
2977
- const nextValue = clamp(value, 0, duration);
2978
- isAdjusting.current = true;
2979
- setPreviewTime(nextValue);
2980
- if (commitMode === "live") {
2981
- onSeek(nextValue);
2982
- lastCommitted.current = nextValue;
2983
- }
2984
- }
2985
- function commitPreview(value = previewTime) {
2986
- const nextValue = clamp(value, 0, duration);
2987
- if (disabled || !isAdjusting.current || commitMode === "live" && lastCommitted.current === nextValue) {
2988
- isAdjusting.current = false;
2989
- return;
2990
- }
2991
- onSeek(nextValue);
2992
- lastCommitted.current = nextValue;
2993
- isAdjusting.current = false;
2994
- }
2995
- function cancelPreview() {
2996
- isAdjusting.current = false;
2997
- lastCommitted.current = null;
2998
- setPreviewTime(safeCurrentTime);
2999
- }
3000
- return /* @__PURE__ */ jsxs(
3001
- "label",
3002
- {
3003
- className: ["player-seek", className].filter(Boolean).join(" "),
3004
- "data-disabled": disabled ? "true" : "false",
3005
- children: [
3006
- /* @__PURE__ */ jsx("span", { className: "player-visually-hidden", children: label }),
3007
- /* @__PURE__ */ jsx(
3008
- "span",
3009
- {
3010
- "aria-hidden": "true",
3011
- className: "player-seek__progress",
3012
- style: { width: `${String(disabled ? 0 : previewProgress)}%` }
3013
- }
3014
- ),
3015
- /* @__PURE__ */ jsx(
3016
- "input",
3017
- {
3018
- "aria-label": label,
3019
- "aria-valuetext": formatTimeAria(previewTime),
3020
- disabled,
3021
- max: duration || 1,
3022
- min: 0,
3023
- onBlur: (event) => {
3024
- commitPreview(Number(event.currentTarget.value));
3025
- },
3026
- onChange: (event) => {
3027
- updatePreview(Number(event.currentTarget.value));
3028
- },
3029
- onKeyUp: (event) => {
3030
- commitPreview(Number(event.currentTarget.value));
3031
- },
3032
- onPointerDown: () => {
3033
- isAdjusting.current = true;
3034
- },
3035
- onPointerCancel: cancelPreview,
3036
- onPointerUp: (event) => {
3037
- commitPreview(Number(event.currentTarget.value));
3038
- },
3039
- step: 0.1,
3040
- type: "range",
3041
- value: disabled ? 0 : previewTime
3042
- }
3043
- )
3044
- ]
3045
- }
3046
- );
3047
- }
3048
2954
  var FALLBACK_PEAKS = [
3049
2955
  0.16,
3050
2956
  0.24,
@@ -3141,6 +3047,182 @@ function Waveform({
3141
3047
  }
3142
3048
  );
3143
3049
  }
3050
+ function CompactPlayerLayout({
3051
+ appearance,
3052
+ className,
3053
+ contextId,
3054
+ contextMode,
3055
+ currentTimeSec,
3056
+ durationSec,
3057
+ errorNotice,
3058
+ isActive,
3059
+ isPlaying,
3060
+ item,
3061
+ label,
3062
+ primaryControl,
3063
+ queueItemId,
3064
+ runtimeStatus,
3065
+ seekControl,
3066
+ status,
3067
+ trailingControl,
3068
+ waveformOpacity,
3069
+ waveformPlayedStateEnabled
3070
+ }) {
3071
+ const progress = durationSec === null || durationSec <= 0 ? 0 : currentTimeSec / durationSec;
3072
+ return /* @__PURE__ */ jsxs(
3073
+ PlayerSurface,
3074
+ {
3075
+ appearance,
3076
+ className: ["player-compact", className].filter(Boolean).join(" "),
3077
+ label,
3078
+ children: [
3079
+ /* @__PURE__ */ jsxs(
3080
+ "div",
3081
+ {
3082
+ className: "player-compact__main",
3083
+ ...isActive === void 0 ? {} : { "data-active": isActive ? "true" : "false" },
3084
+ "data-has-status": status === void 0 ? "false" : "true",
3085
+ "data-has-trailing-control": trailingControl === void 0 ? "false" : "true",
3086
+ "data-playback-context-id": contextId,
3087
+ "data-playback-context-mode": contextMode,
3088
+ "data-player-runtime-status": runtimeStatus,
3089
+ "data-queue-item-id": queueItemId,
3090
+ children: [
3091
+ primaryControl,
3092
+ /* @__PURE__ */ jsx(Artwork, { className: "player-compact__artwork", item }),
3093
+ /* @__PURE__ */ jsx(TrackMetadata, { item, scrollTitle: isPlaying }),
3094
+ /* @__PURE__ */ jsxs("div", { className: "player-compact__signal", children: [
3095
+ status,
3096
+ /* @__PURE__ */ jsxs(
3097
+ "div",
3098
+ {
3099
+ className: "player-compact__scrubber",
3100
+ "data-seek-enabled": seekControl === void 0 ? "false" : "true",
3101
+ children: [
3102
+ /* @__PURE__ */ jsx(
3103
+ Waveform,
3104
+ {
3105
+ opacity: waveformOpacity,
3106
+ playedStateEnabled: waveformPlayedStateEnabled,
3107
+ progress,
3108
+ waveform: item?.waveform
3109
+ }
3110
+ ),
3111
+ seekControl
3112
+ ]
3113
+ }
3114
+ ),
3115
+ /* @__PURE__ */ jsx(
3116
+ TimeReadout,
3117
+ {
3118
+ currentTimeSec,
3119
+ durationSec
3120
+ }
3121
+ )
3122
+ ] }),
3123
+ trailingControl
3124
+ ]
3125
+ }
3126
+ ),
3127
+ errorNotice
3128
+ ]
3129
+ }
3130
+ );
3131
+ }
3132
+ function SeekBar({
3133
+ canSeek,
3134
+ className,
3135
+ commitMode,
3136
+ currentTimeSec,
3137
+ durationSec,
3138
+ label = "Seek",
3139
+ onSeek,
3140
+ status
3141
+ }) {
3142
+ const duration = durationSec !== null && Number.isFinite(durationSec) && durationSec > 0 ? durationSec : 0;
3143
+ const disabled = !canSeek || duration === 0 || status === "loading";
3144
+ const safeCurrentTime = clamp(currentTimeSec, 0, duration);
3145
+ const [previewTime, setPreviewTime] = useState(safeCurrentTime);
3146
+ const previewProgress = duration === 0 ? 0 : clamp(previewTime / duration, 0, 1) * 100;
3147
+ const isAdjusting = useRef(false);
3148
+ const lastCommitted = useRef(null);
3149
+ useEffect(() => {
3150
+ if (!isAdjusting.current) {
3151
+ setPreviewTime(safeCurrentTime);
3152
+ }
3153
+ }, [safeCurrentTime]);
3154
+ function updatePreview(value) {
3155
+ const nextValue = clamp(value, 0, duration);
3156
+ isAdjusting.current = true;
3157
+ setPreviewTime(nextValue);
3158
+ if (commitMode === "live") {
3159
+ onSeek(nextValue);
3160
+ lastCommitted.current = nextValue;
3161
+ }
3162
+ }
3163
+ function commitPreview(value = previewTime) {
3164
+ const nextValue = clamp(value, 0, duration);
3165
+ if (disabled || !isAdjusting.current || commitMode === "live" && lastCommitted.current === nextValue) {
3166
+ isAdjusting.current = false;
3167
+ return;
3168
+ }
3169
+ onSeek(nextValue);
3170
+ lastCommitted.current = nextValue;
3171
+ isAdjusting.current = false;
3172
+ }
3173
+ function cancelPreview() {
3174
+ isAdjusting.current = false;
3175
+ lastCommitted.current = null;
3176
+ setPreviewTime(safeCurrentTime);
3177
+ }
3178
+ return /* @__PURE__ */ jsxs(
3179
+ "label",
3180
+ {
3181
+ className: ["player-seek", className].filter(Boolean).join(" "),
3182
+ "data-disabled": disabled ? "true" : "false",
3183
+ children: [
3184
+ /* @__PURE__ */ jsx("span", { className: "player-visually-hidden", children: label }),
3185
+ /* @__PURE__ */ jsx(
3186
+ "span",
3187
+ {
3188
+ "aria-hidden": "true",
3189
+ className: "player-seek__progress",
3190
+ style: { width: `${String(disabled ? 0 : previewProgress)}%` }
3191
+ }
3192
+ ),
3193
+ /* @__PURE__ */ jsx(
3194
+ "input",
3195
+ {
3196
+ "aria-label": label,
3197
+ "aria-valuetext": formatTimeAria(previewTime),
3198
+ disabled,
3199
+ max: duration || 1,
3200
+ min: 0,
3201
+ onBlur: (event) => {
3202
+ commitPreview(Number(event.currentTarget.value));
3203
+ },
3204
+ onChange: (event) => {
3205
+ updatePreview(Number(event.currentTarget.value));
3206
+ },
3207
+ onKeyUp: (event) => {
3208
+ commitPreview(Number(event.currentTarget.value));
3209
+ },
3210
+ onPointerDown: () => {
3211
+ isAdjusting.current = true;
3212
+ },
3213
+ onPointerCancel: cancelPreview,
3214
+ onPointerUp: (event) => {
3215
+ commitPreview(Number(event.currentTarget.value));
3216
+ },
3217
+ step: 0.1,
3218
+ type: "range",
3219
+ value: disabled ? 0 : previewTime
3220
+ }
3221
+ )
3222
+ ]
3223
+ }
3224
+ );
3225
+ }
3144
3226
  function BoundCompactPlayerView({
3145
3227
  appearance,
3146
3228
  className,
@@ -3151,77 +3233,48 @@ function BoundCompactPlayerView({
3151
3233
  runtimeStatus,
3152
3234
  state
3153
3235
  }) {
3154
- const progress = state.durationSec === null || state.durationSec <= 0 ? 0 : state.currentTimeSec / state.durationSec;
3155
3236
  return /* @__PURE__ */ jsx(
3156
- PlayerSurface,
3237
+ CompactPlayerLayout,
3157
3238
  {
3158
3239
  appearance,
3159
- className: ["player-bound-compact", className].filter(Boolean).join(" "),
3240
+ ...className === void 0 ? {} : { className },
3241
+ ...contextId === void 0 ? {} : { contextId },
3242
+ ...contextMode === void 0 ? {} : { contextMode },
3243
+ currentTimeSec: state.currentTimeSec,
3244
+ durationSec: state.durationSec,
3245
+ isActive: state.isActive,
3246
+ isPlaying: state.isPlaying,
3247
+ item: state.item,
3160
3248
  label: `Inline player: ${state.item.title}`,
3161
- children: /* @__PURE__ */ jsxs(
3162
- "div",
3249
+ primaryControl: /* @__PURE__ */ jsx(
3250
+ IconButton,
3163
3251
  {
3164
- className: "player-bound-compact__main",
3165
- "data-active": state.isActive ? "true" : "false",
3166
- "data-playback-context-id": contextId,
3167
- "data-playback-context-mode": contextMode,
3168
- "data-player-runtime-status": runtimeStatus,
3169
- "data-queue-item-id": state.queueItemId,
3170
- children: [
3171
- /* @__PURE__ */ jsx(
3172
- IconButton,
3173
- {
3174
- className: "player-play-button",
3175
- label: state.isPlaying ? "Pause" : "Play",
3176
- onClick: () => {
3177
- void commands.togglePlayback();
3178
- },
3179
- children: state.isPlaying ? /* @__PURE__ */ jsx(PauseIcon, { size: 22 }) : /* @__PURE__ */ jsx(PlayIcon, { size: 22 })
3180
- }
3181
- ),
3182
- /* @__PURE__ */ jsx(
3183
- Artwork,
3184
- {
3185
- className: "player-bound-compact__artwork",
3186
- item: state.item
3187
- }
3188
- ),
3189
- /* @__PURE__ */ jsx(TrackMetadata, { item: state.item, scrollTitle: state.isPlaying }),
3190
- /* @__PURE__ */ jsxs("div", { className: "player-bound-compact__signal", children: [
3191
- /* @__PURE__ */ jsx(
3192
- Waveform,
3193
- {
3194
- opacity: options.waveformOpacity,
3195
- playedStateEnabled: options.waveformPlayedStateEnabled,
3196
- progress,
3197
- waveform: state.item.waveform
3198
- }
3199
- ),
3200
- /* @__PURE__ */ jsx(
3201
- TimeReadout,
3202
- {
3203
- currentTimeSec: state.currentTimeSec,
3204
- durationSec: state.durationSec
3205
- }
3206
- )
3207
- ] }),
3208
- /* @__PURE__ */ jsx(
3209
- SeekBar,
3210
- {
3211
- canSeek: state.canSeek,
3212
- commitMode: options.seekCommitMode,
3213
- currentTimeSec: state.currentTimeSec,
3214
- durationSec: state.durationSec,
3215
- label: `Seek in ${state.item.title}`,
3216
- onSeek: (seconds) => {
3217
- commands.seekTo(seconds);
3218
- },
3219
- status: state.status
3220
- }
3221
- )
3222
- ]
3252
+ className: "player-play-button",
3253
+ label: state.isPlaying ? "Pause" : "Play",
3254
+ onClick: () => {
3255
+ void commands.togglePlayback();
3256
+ },
3257
+ children: state.isPlaying ? /* @__PURE__ */ jsx(PauseIcon, { size: 22 }) : /* @__PURE__ */ jsx(PlayIcon, { size: 22 })
3258
+ }
3259
+ ),
3260
+ queueItemId: state.queueItemId,
3261
+ ...runtimeStatus === void 0 ? {} : { runtimeStatus },
3262
+ seekControl: /* @__PURE__ */ jsx(
3263
+ SeekBar,
3264
+ {
3265
+ canSeek: state.canSeek,
3266
+ commitMode: options.seekCommitMode,
3267
+ currentTimeSec: state.currentTimeSec,
3268
+ durationSec: state.durationSec,
3269
+ label: `Seek in ${state.item.title}`,
3270
+ onSeek: (seconds) => {
3271
+ commands.seekTo(seconds);
3272
+ },
3273
+ status: state.status
3223
3274
  }
3224
- )
3275
+ ),
3276
+ waveformOpacity: options.waveformOpacity,
3277
+ waveformPlayedStateEnabled: options.waveformPlayedStateEnabled
3225
3278
  }
3226
3279
  );
3227
3280
  }