box-content-preview 3.91.0 → 3.93.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/dist/lib/index.js CHANGED
@@ -620,8 +620,8 @@ const VIEWER_EVENT = {
620
620
  // When gallery view opens
621
621
  load: 'load',
622
622
  // Preview is finished loading.
623
- mediaEndAutoplay: 'mediaendautoplay',
624
- // Media playback has completed, with autoplay enabled.
623
+ mediaEndPlayNext: 'mediaEndPlayNext',
624
+ // Media playback has completed, with play-next enabled.
625
625
  metric: 'viewermetric',
626
626
  // A viewer metric.
627
627
  notificationHide: 'notificationhide',
@@ -1018,7 +1018,7 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
1018
1018
  const CLIENT_NAME = "box-content-preview"; // eslint-disable-line no-undef
1019
1019
  const CLIENT_NAME_KEY = 'box_client_name';
1020
1020
  const CLIENT_VERSION_KEY = 'box_client_version';
1021
- const CLIENT_VERSION = "3.91.0"; // eslint-disable-line no-undef
1021
+ const CLIENT_VERSION = "3.93.0"; // eslint-disable-line no-undef
1022
1022
  const HEADER_CLIENT_NAME = 'X-Box-Client-Name';
1023
1023
  const HEADER_CLIENT_VERSION = 'X-Box-Client-Version';
1024
1024
  const PROMISE_MAP = {};
@@ -2143,7 +2143,7 @@ function DurationLabels({
2143
2143
 
2144
2144
  /***/ },
2145
2145
 
2146
- /***/ 6929
2146
+ /***/ 9229
2147
2147
  (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
2148
2148
 
2149
2149
 
@@ -2250,6 +2250,36 @@ function MediaSettingsMenuAutoplay({
2250
2250
  }
2251
2251
  // EXTERNAL MODULE: ./src/lib/viewers/controls/media/MediaSettingsMenuGuides.tsx
2252
2252
  var MediaSettingsMenuGuides = __webpack_require__(4692);
2253
+ ;// ./src/lib/viewers/controls/media/MediaSettingsMenuPlayNext.tsx
2254
+
2255
+
2256
+ function MediaSettingsMenuPlayNext({
2257
+ playNext,
2258
+ onPlayNextChange
2259
+ }) {
2260
+ const {
2261
+ setActiveMenu
2262
+ } = external_react_["default"].useContext(Settings/* default */.A.Context);
2263
+ const handleChange = value => {
2264
+ setActiveMenu(SettingsContext/* Menu */.W.MAIN);
2265
+ onPlayNextChange(value);
2266
+ };
2267
+ return /*#__PURE__*/external_react_["default"].createElement(Settings/* default */.A.Menu, {
2268
+ name: SettingsContext/* Menu */.W.PLAY_NEXT
2269
+ }, /*#__PURE__*/external_react_["default"].createElement(Settings/* default */.A.MenuBack, {
2270
+ label: "Play next file"
2271
+ }), /*#__PURE__*/external_react_["default"].createElement(Settings/* default */.A.RadioItem, {
2272
+ isSelected: !playNext,
2273
+ label: "Disabled",
2274
+ onChange: handleChange,
2275
+ value: false
2276
+ }), /*#__PURE__*/external_react_["default"].createElement(Settings/* default */.A.RadioItem, {
2277
+ isSelected: playNext,
2278
+ label: "Enabled",
2279
+ onChange: handleChange,
2280
+ value: true
2281
+ }));
2282
+ }
2253
2283
  ;// ./src/lib/viewers/controls/media/MediaSettingsMenuQuality.tsx
2254
2284
 
2255
2285
 
@@ -2402,6 +2432,7 @@ function MediaSettingsMenuSubtitles({
2402
2432
 
2403
2433
 
2404
2434
 
2435
+
2405
2436
  function MediaSettings({
2406
2437
  audioTrack,
2407
2438
  audioTracks = [],
@@ -2416,7 +2447,9 @@ function MediaSettings({
2416
2447
  onGuideChange = (noop_default()),
2417
2448
  onQualityChange,
2418
2449
  onRateChange,
2450
+ onPlayNextChange,
2419
2451
  onSubtitleChange,
2452
+ playNext = false,
2420
2453
  quality,
2421
2454
  rate,
2422
2455
  subtitle,
@@ -2425,7 +2458,9 @@ function MediaSettings({
2425
2458
  }) {
2426
2459
  const subtitleDisplayLanguage = getDisplayLanguage(subtitle, subtitles);
2427
2460
  const autoValue = autoplay ? "Enabled" : "Disabled";
2461
+ const playNextValue = playNext ? "Enabled" : "Disabled";
2428
2462
  const rateValue = rate === '1.0' || !rate ? "Normal" : rate;
2463
+ const showPlayNext = typeof onPlayNextChange === 'function';
2429
2464
  const labelledAudioTracks = external_react_["default"].useMemo(() => addLabels(audioTracks), [audioTracks]);
2430
2465
  const hydratedSelectedAudioTrack = labelledAudioTracks.find(({
2431
2466
  id
@@ -2444,6 +2479,11 @@ function MediaSettings({
2444
2479
  label: "Autoplay",
2445
2480
  target: SettingsContext/* Menu */.W.AUTOPLAY,
2446
2481
  value: autoValue
2482
+ }), showPlayNext && /*#__PURE__*/external_react_["default"].createElement(Settings/* default */.A.MenuItem, {
2483
+ "data-testid": "bp-media-settings-play-next",
2484
+ label: "Play next file",
2485
+ target: SettingsContext/* Menu */.W.PLAY_NEXT,
2486
+ value: playNextValue
2447
2487
  }), /*#__PURE__*/external_react_["default"].createElement(Settings/* default */.A.MenuItem, {
2448
2488
  "data-testid": "bp-media-settings-speed",
2449
2489
  label: "Speed",
@@ -2473,6 +2513,9 @@ function MediaSettings({
2473
2513
  })), /*#__PURE__*/external_react_["default"].createElement(MediaSettingsMenuAutoplay, {
2474
2514
  autoplay: autoplay,
2475
2515
  onAutoplayChange: onAutoplayChange
2516
+ }), showPlayNext && onPlayNextChange && /*#__PURE__*/external_react_["default"].createElement(MediaSettingsMenuPlayNext, {
2517
+ onPlayNextChange: onPlayNextChange,
2518
+ playNext: playNext
2476
2519
  }), /*#__PURE__*/external_react_["default"].createElement(MediaSettingsMenuRate, {
2477
2520
  onRateChange: onRateChange,
2478
2521
  rate: rate
@@ -2916,10 +2959,10 @@ function TimestampControl({
2916
2959
 
2917
2960
  // EXPORTS
2918
2961
  __webpack_require__.d(__webpack_exports__, {
2919
- A: () => (/* binding */ VolumeControls)
2962
+ Ay: () => (/* binding */ VolumeControls)
2920
2963
  });
2921
2964
 
2922
- // UNUSED EXPORTS: getIcon
2965
+ // UNUSED EXPORTS: VOLUME_FLYOUT_DISMISS_MS, getIcon
2923
2966
 
2924
2967
  // EXTERNAL MODULE: external "react"
2925
2968
  var external_react_ = __webpack_require__(1649);
@@ -2981,12 +3024,26 @@ function IconVolumeMuted24(props) {
2981
3024
  var MediaToggle = __webpack_require__(8997);
2982
3025
  ;// ./src/lib/viewers/controls/hooks/useAttention.ts
2983
3026
 
3027
+ /** True when focus/hover is moving to a descendant, not leaving the host. */
3028
+ function isRelatedTargetInside(event) {
3029
+ return event.currentTarget.contains(event.relatedTarget);
3030
+ }
2984
3031
  function useAttention() {
2985
3032
  const [isFocused, setFocused] = external_react_.useState(false);
2986
3033
  const [isHovered, setHovered] = external_react_.useState(false);
2987
- const handleBlur = () => setFocused(false);
3034
+ const handleBlur = event => {
3035
+ if (isRelatedTargetInside(event)) {
3036
+ return;
3037
+ }
3038
+ setFocused(false);
3039
+ };
2988
3040
  const handleFocus = () => setFocused(true);
2989
- const handleMouseOut = () => setHovered(false);
3041
+ const handleMouseOut = event => {
3042
+ if (isRelatedTargetInside(event)) {
3043
+ return;
3044
+ }
3045
+ setHovered(false);
3046
+ };
2990
3047
  const handleMouseOver = () => setHovered(true);
2991
3048
  return [isFocused || isHovered, {
2992
3049
  onBlur: handleBlur,
@@ -3023,7 +3080,7 @@ var util = __webpack_require__(4410);
3023
3080
  // extracted by mini-css-extract-plugin
3024
3081
 
3025
3082
  ;// ./src/lib/viewers/controls/slider/VolumeSliderControl.tsx
3026
- const _excluded = ["className", "max", "min", "onMove", "onUpdate", "step", "title", "value", "onMouseOver"];
3083
+ const _excluded = ["className", "max", "min", "onMove", "onUpdate", "step", "title", "value", "onMouseOver", "tabIndex"];
3027
3084
  function VolumeSliderControl_extends() { return VolumeSliderControl_extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, VolumeSliderControl_extends.apply(null, arguments); }
3028
3085
  function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
3029
3086
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
@@ -3049,11 +3106,19 @@ function VolumeSliderControl(_ref) {
3049
3106
  step = 1,
3050
3107
  title,
3051
3108
  value,
3052
- onMouseOver
3109
+ onMouseOver,
3110
+ tabIndex = 0
3053
3111
  } = _ref,
3054
3112
  rest = _objectWithoutProperties(_ref, _excluded);
3055
3113
  const [isScrubbing, setIsScrubbing] = external_react_["default"].useState(false);
3056
3114
  const sliderElRef = external_react_["default"].useRef(null);
3115
+ const recordScrub = () => {
3116
+ window.Box?.Preview?.resin?.recordAction({
3117
+ action: 'programmatic',
3118
+ component: 'toolbar',
3119
+ target: 'volumeSlider'
3120
+ });
3121
+ };
3057
3122
  const getPositionRelativeToSlider = external_react_["default"].useCallback(clientY => {
3058
3123
  const {
3059
3124
  current: sliderEl
@@ -3085,10 +3150,12 @@ function VolumeSliderControl(_ref) {
3085
3150
  const key = (0,util/* decodeKeydown */.wU)(event);
3086
3151
  if (key === 'ArrowDown') {
3087
3152
  event.stopPropagation(); // Prevents global key handling
3153
+ recordScrub();
3088
3154
  onUpdate(Math.max(min, Math.min(value - step, max)));
3089
3155
  }
3090
3156
  if (key === 'ArrowUp') {
3091
3157
  event.stopPropagation(); // Prevents global key handling
3158
+ recordScrub();
3092
3159
  onUpdate(Math.max(min, Math.min(value + step, max)));
3093
3160
  }
3094
3161
  };
@@ -3101,6 +3168,7 @@ function VolumeSliderControl(_ref) {
3101
3168
  clientY
3102
3169
  } = event;
3103
3170
  if (button > 1 || ctrlKey || metaKey) return;
3171
+ recordScrub();
3104
3172
  onUpdate(getPositionValue(pageY, clientY));
3105
3173
  setIsScrubbing(true);
3106
3174
  // Prevent clicking on the slider from triggering the mouse down event on the parent slider track
@@ -3123,6 +3191,7 @@ function VolumeSliderControl(_ref) {
3123
3191
  const handleTouchStart = ({
3124
3192
  touches
3125
3193
  }) => {
3194
+ recordScrub();
3126
3195
  onUpdate(getPositionValue(touches[0].pageY, touches[0].clientY));
3127
3196
  setIsScrubbing(true);
3128
3197
  };
@@ -3160,33 +3229,29 @@ function VolumeSliderControl(_ref) {
3160
3229
  className: "bp-VolumeVerticalSliderControl-track-container"
3161
3230
  }, /*#__PURE__*/external_react_["default"].createElement("div", {
3162
3231
  ref: sliderElRef,
3163
- className: "bp-VolumeVerticalSliderControl-track-background",
3164
- onMouseDown: handleMouseDown,
3165
- role: "button",
3166
- tabIndex: 0
3167
- }, /*#__PURE__*/external_react_["default"].createElement("div", {
3168
3232
  "aria-label": title,
3169
3233
  "aria-valuemax": max,
3170
3234
  "aria-valuemin": min,
3171
3235
  "aria-valuenow": value,
3172
- className: "bp-VolumeVerticalSliderControl-track",
3236
+ className: "bp-VolumeVerticalSliderControl-track-background",
3173
3237
  "data-resin-target": "volumeSlider",
3174
3238
  "data-testid": "bp-volume-slider-control-track",
3175
- onFocus: onMouseOver,
3239
+ onFocus: (noop_default()),
3176
3240
  onKeyDown: handleKeydown,
3177
3241
  onMouseDown: handleMouseDown,
3178
3242
  onMouseMove: handleMouseMove,
3179
3243
  onMouseOver: onMouseOver,
3180
3244
  onTouchStart: handleTouchStart,
3181
3245
  role: "slider",
3246
+ tabIndex: tabIndex
3247
+ }, /*#__PURE__*/external_react_["default"].createElement("div", {
3248
+ className: "bp-VolumeVerticalSliderControl-track",
3182
3249
  style: {
3183
3250
  height: `${heightValueBasedOnVolume / max * 100}%`
3184
- },
3185
- tabIndex: 0
3251
+ }
3186
3252
  }))));
3187
3253
  }
3188
3254
  ;// ./src/lib/viewers/controls/media/VolumeControls.tsx
3189
- function VolumeControls_extends() { return VolumeControls_extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, VolumeControls_extends.apply(null, arguments); }
3190
3255
 
3191
3256
 
3192
3257
 
@@ -3197,6 +3262,9 @@ function VolumeControls_extends() { return VolumeControls_extends = Object.assig
3197
3262
 
3198
3263
 
3199
3264
 
3265
+
3266
+ /** Keyboard volume keeps the flyout open longer than the 200ms max-width transition. */
3267
+ const VOLUME_FLYOUT_DISMISS_MS = 1000;
3200
3268
  function getIcon(volume) {
3201
3269
  let Icon = icons_IconVolumeMuted24;
3202
3270
  if (volume >= 0.66) {
@@ -3209,16 +3277,32 @@ function getIcon(volume) {
3209
3277
  return Icon;
3210
3278
  }
3211
3279
  function VolumeControls({
3280
+ keyboardVolumeStep = 0,
3212
3281
  onMuteChange,
3213
3282
  onVolumeChange,
3214
3283
  volume = 1
3215
3284
  }) {
3216
3285
  const [isActive, handlers] = useAttention();
3286
+ const [isRevealed, setIsRevealed] = (0,external_react_.useState)(false);
3287
+ const revealTimerRef = (0,external_react_.useRef)(0);
3217
3288
  const isMuted = !volume;
3218
3289
  const Icon = isMuted ? icons_IconVolumeMuted24 : getIcon(volume);
3219
3290
  const title = isMuted ? "Unmute" : "Mute";
3220
3291
  const value = Math.round(volume * 100);
3221
- const handleVolume = external_react_["default"].useCallback(newValue => {
3292
+ const isOpen = isActive || isRevealed;
3293
+ (0,external_react_.useEffect)(() => {
3294
+ if (!keyboardVolumeStep) {
3295
+ return;
3296
+ }
3297
+ setIsRevealed(true);
3298
+ window.clearTimeout(revealTimerRef.current);
3299
+ revealTimerRef.current = window.setTimeout(() => {
3300
+ setIsRevealed(false);
3301
+ revealTimerRef.current = 0;
3302
+ }, VOLUME_FLYOUT_DISMISS_MS);
3303
+ }, [keyboardVolumeStep]);
3304
+ (0,external_react_.useEffect)(() => () => window.clearTimeout(revealTimerRef.current), []);
3305
+ const handleVolume = (0,external_react_.useCallback)(newValue => {
3222
3306
  const newValueToUse = newValue <= 5 ? 0 : newValue;
3223
3307
  // make sure the value is always between 0 and 1 since the value passed in could be
3224
3308
  // greater than 100 if the user drags the mouse above the top of the sider when adjusting the volume
@@ -3228,27 +3312,39 @@ function VolumeControls({
3228
3312
  return /*#__PURE__*/external_react_["default"].createElement("div", {
3229
3313
  className: "bp-VolumeControls",
3230
3314
  "data-testid": "bp-volume-controls",
3231
- onBlur: handlers.onBlur,
3315
+ onBlur: event => {
3316
+ handlers.onBlur(event);
3317
+ if (event.currentTarget.contains(event.relatedTarget)) {
3318
+ return;
3319
+ }
3320
+ window.clearTimeout(revealTimerRef.current);
3321
+ revealTimerRef.current = 0;
3322
+ setIsRevealed(false);
3323
+ },
3232
3324
  onFocus: handlers.onFocus,
3233
3325
  onMouseOut: handlers.onMouseOut,
3234
3326
  onMouseOver: handlers.onMouseOver
3235
- }, /*#__PURE__*/external_react_["default"].createElement(MediaToggle/* default */.A, VolumeControls_extends({
3327
+ }, /*#__PURE__*/external_react_["default"].createElement(MediaToggle/* default */.A, {
3236
3328
  className: "bp-VolumeControls-toggle",
3237
3329
  "data-resin-target": "volumeToggle",
3238
3330
  onClick: () => onMuteChange(!isMuted),
3239
3331
  title: title
3240
- }, handlers), /*#__PURE__*/external_react_["default"].createElement(Icon, null)), /*#__PURE__*/external_react_["default"].createElement("div", {
3332
+ }, /*#__PURE__*/external_react_["default"].createElement(Icon, null)), /*#__PURE__*/external_react_["default"].createElement("div", {
3333
+ "aria-hidden": !isOpen,
3241
3334
  className: classnames_default()('bp-VolumeControls-flyout', {
3242
- 'bp-is-open': isActive
3335
+ 'bp-is-open': isOpen
3243
3336
  })
3244
- }, /*#__PURE__*/external_react_["default"].createElement(VolumeSliderControl, VolumeControls_extends({
3337
+ }, /*#__PURE__*/external_react_["default"].createElement(VolumeSliderControl, {
3338
+ "aria-hidden": !isOpen,
3245
3339
  className: "bp-VolumeControls-slider",
3246
3340
  max: 100,
3341
+ onMouseOver: handlers.onMouseOver,
3247
3342
  onUpdate: handleVolume,
3248
3343
  step: 1,
3344
+ tabIndex: isOpen ? 0 : -1,
3249
3345
  title: "Volume Slider",
3250
3346
  value: value
3251
- }, handlers))));
3347
+ })));
3252
3348
  }
3253
3349
 
3254
3350
  /***/ },
@@ -4137,7 +4233,14 @@ function SettingsRadioItem(props, ref) {
4137
4233
  value
4138
4234
  } = props;
4139
4235
  const displayedValue = label || value.toString();
4236
+ const recordAction = window.Box?.Preview?.resin?.recordAction;
4237
+ const settingsRadioItemAction = {
4238
+ action: 'programmatic',
4239
+ component: 'toolbar',
4240
+ target: 'settingsRadioItem'
4241
+ };
4140
4242
  const handleClick = () => {
4243
+ recordAction?.(settingsRadioItemAction);
4141
4244
  onChange(value);
4142
4245
  };
4143
4246
  const handleKeydown = event => {
@@ -4145,6 +4248,7 @@ function SettingsRadioItem(props, ref) {
4145
4248
  if (key !== 'ArrowLeft' && key !== 'Enter' && key !== 'Space') {
4146
4249
  return;
4147
4250
  }
4251
+ recordAction?.(settingsRadioItemAction);
4148
4252
  onChange(value);
4149
4253
  };
4150
4254
  return /*#__PURE__*/external_react_["default"].createElement("div", {
@@ -4297,6 +4401,7 @@ let Menu = /*#__PURE__*/function (Menu) {
4297
4401
  Menu["AUDIO"] = "audio";
4298
4402
  Menu["AUTOPLAY"] = "autoplay";
4299
4403
  Menu["GUIDES"] = "guides";
4404
+ Menu["PLAY_NEXT"] = "playnext";
4300
4405
  Menu["QUALITY"] = "quality";
4301
4406
  Menu["RATE"] = "rate";
4302
4407
  Menu["SUBTITLES"] = "subtitles";
@@ -4333,7 +4438,7 @@ var util = __webpack_require__(4410);
4333
4438
  // extracted by mini-css-extract-plugin
4334
4439
 
4335
4440
  ;// ./src/lib/viewers/controls/slider/SliderControl.tsx
4336
- const _excluded = ["className", "max", "min", "onMove", "onUpdate", "step", "title", "track", "value"];
4441
+ const _excluded = ["className", "data-resin-target", "max", "min", "onMove", "onUpdate", "step", "title", "track", "value"];
4337
4442
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
4338
4443
  function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
4339
4444
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
@@ -4345,6 +4450,7 @@ function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t =
4345
4450
  function SliderControl(_ref) {
4346
4451
  let {
4347
4452
  className,
4453
+ 'data-resin-target': resinTarget,
4348
4454
  max = 100,
4349
4455
  min = 0,
4350
4456
  onMove = (noop_default()),
@@ -4357,6 +4463,16 @@ function SliderControl(_ref) {
4357
4463
  rest = _objectWithoutProperties(_ref, _excluded);
4358
4464
  const [isScrubbing, setIsScrubbing] = external_react_["default"].useState(false);
4359
4465
  const sliderElRef = external_react_["default"].useRef(null);
4466
+ const recordScrub = () => {
4467
+ if (!resinTarget) {
4468
+ return;
4469
+ }
4470
+ window.Box?.Preview?.resin?.recordAction({
4471
+ action: 'programmatic',
4472
+ component: 'toolbar',
4473
+ target: resinTarget
4474
+ });
4475
+ };
4360
4476
  const getPosition = external_react_["default"].useCallback(pageX => {
4361
4477
  const {
4362
4478
  current: sliderEl
@@ -4383,10 +4499,12 @@ function SliderControl(_ref) {
4383
4499
  const key = (0,util/* decodeKeydown */.wU)(event);
4384
4500
  if (key === 'ArrowLeft') {
4385
4501
  event.stopPropagation(); // Prevents global key handling
4502
+ recordScrub();
4386
4503
  onUpdate(Math.max(min, Math.min(value - step, max)));
4387
4504
  }
4388
4505
  if (key === 'ArrowRight') {
4389
4506
  event.stopPropagation(); // Prevents global key handling
4507
+ recordScrub();
4390
4508
  onUpdate(Math.max(min, Math.min(value + step, max)));
4391
4509
  }
4392
4510
  };
@@ -4397,6 +4515,7 @@ function SliderControl(_ref) {
4397
4515
  pageX
4398
4516
  }) => {
4399
4517
  if (button > 1 || ctrlKey || metaKey) return;
4518
+ recordScrub();
4400
4519
  onUpdate(getPositionValue(pageX));
4401
4520
  setIsScrubbing(true);
4402
4521
  };
@@ -4416,6 +4535,7 @@ function SliderControl(_ref) {
4416
4535
  const handleTouchStart = ({
4417
4536
  touches
4418
4537
  }) => {
4538
+ recordScrub();
4419
4539
  onUpdate(getPositionValue(touches[0].pageX));
4420
4540
  setIsScrubbing(true);
4421
4541
  };
@@ -4453,6 +4573,7 @@ function SliderControl(_ref) {
4453
4573
  className: classnames_default()('bp-SliderControl', className, {
4454
4574
  'bp-is-scrubbing': isScrubbing
4455
4575
  }),
4576
+ "data-resin-target": resinTarget,
4456
4577
  onKeyDown: handleKeydown,
4457
4578
  onMouseDown: handleMouseDown,
4458
4579
  onMouseMove: handleMouseMove,
@@ -4488,7 +4609,7 @@ function getPdfjsWorkerSrc() {
4488
4609
 
4489
4610
  /***/ },
4490
4611
 
4491
- /***/ 2668
4612
+ /***/ 673
4492
4613
  (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
4493
4614
 
4494
4615
 
@@ -4504,8 +4625,8 @@ var external_react_ = __webpack_require__(1649);
4504
4625
  // EXTERNAL MODULE: ./node_modules/lodash/isFinite.js
4505
4626
  var lodash_isFinite = __webpack_require__(2297);
4506
4627
  var isFinite_default = /*#__PURE__*/__webpack_require__.n(lodash_isFinite);
4507
- // EXTERNAL MODULE: ./src/lib/viewers/controls/media/MediaSettings.tsx + 5 modules
4508
- var MediaSettings = __webpack_require__(6929);
4628
+ // EXTERNAL MODULE: ./src/lib/viewers/controls/media/MediaSettings.tsx + 6 modules
4629
+ var MediaSettings = __webpack_require__(9229);
4509
4630
  // EXTERNAL MODULE: ./src/lib/viewers/controls/media/PlayPauseToggle.tsx + 1 modules
4510
4631
  var PlayPauseToggle = __webpack_require__(6321);
4511
4632
  // EXTERNAL MODULE: ./src/lib/viewers/controls/media/TimestampControl.tsx + 1 modules
@@ -4612,316 +4733,8 @@ function morphPeaks(from, to, elapsedMs) {
4612
4733
  }
4613
4734
  return out;
4614
4735
  }
4615
- ;// ./src/lib/viewers/media/waveform/viewport.ts
4616
- function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4617
- function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4618
- function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
4619
- function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
4620
- function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
4621
-
4622
- /** Visible window: start/end times, pixels-per-second, and the current zoom. */
4623
- function createWaveformViewport({
4624
- durationSec,
4625
- gutterPx = 0,
4626
- heightPx,
4627
- maxZoom,
4628
- scrollLeftPx,
4629
- widthPx,
4630
- zoomLevel
4631
- }) {
4632
- const zoom = Number.isFinite(zoomLevel) && zoomLevel > 0 ? zoomLevel : constants/* WAVEFORM_ZOOM_MIN */.LW;
4633
- const viewDurationSec = durationSec > 0 ? durationSec / zoom : 0; // seconds visible at this zoom
4634
- const pixelsPerSecond = viewDurationSec > 0 && widthPx > 0 ? widthPx / viewDurationSec : 0;
4635
- const leadingGutterPx = Number.isFinite(gutterPx) && gutterPx > 0 ? gutterPx : 0;
4636
- const unclampedStartSec = pixelsPerSecond > 0 ? (scrollLeftPx - leadingGutterPx) / pixelsPerSecond : 0;
4637
- const gutterDurationSec = pixelsPerSecond > 0 ? leadingGutterPx / pixelsPerSecond : 0;
4638
- const latestWindowStartSec = leadingGutterPx > 0 ? durationSec + gutterDurationSec - viewDurationSec : Math.max(0, durationSec - viewDurationSec);
4639
- const earliestWindowStartSec = leadingGutterPx > 0 ? -gutterDurationSec : 0;
4640
- const startSec = pixelsPerSecond > 0 ? Math.min(latestWindowStartSec, Math.max(earliestWindowStartSec, unclampedStartSec)) : 0;
4641
- return {
4642
- durationSec,
4643
- endSec: startSec + viewDurationSec,
4644
- gutterPx: leadingGutterPx,
4645
- heightPx,
4646
- maxZoom,
4647
- pixelsPerSecond,
4648
- scrollLeftPx,
4649
- startSec,
4650
- widthPx,
4651
- zoomLevel: zoom
4652
- };
4653
- }
4654
-
4655
- /** Same viewport with a different scroll, so start/end times update. */
4656
- function getViewportAtScroll(viewport, scrollLeftPx) {
4657
- return createWaveformViewport(_objectSpread(_objectSpread({}, viewport), {}, {
4658
- scrollLeftPx
4659
- }));
4660
- }
4661
-
4662
- /** True when the visible window has not moved — skip a React state update. */
4663
- function viewportEquals(prev, next) {
4664
- return !!prev && prev.durationSec === next.durationSec && prev.endSec === next.endSec && prev.gutterPx === next.gutterPx && prev.maxZoom === next.maxZoom && prev.scrollLeftPx === next.scrollLeftPx && prev.startSec === next.startSec && prev.widthPx === next.widthPx && prev.zoomLevel === next.zoomLevel;
4665
- }
4666
-
4667
- /** How many CSS pixels from the left of the visible window this time sits. */
4668
- function positionPxFromTime(timeSec, viewport) {
4669
- return (timeSec - viewport.startSec) * viewport.pixelsPerSecond;
4670
- }
4671
-
4672
- /** Media time under a point this many CSS pixels from the left of the visible window. */
4673
- function timeFromPositionPx(positionPx, viewport) {
4674
- if (!(viewport.pixelsPerSecond > 0)) {
4675
- return viewport.startSec;
4676
- }
4677
- return viewport.startSec + positionPx / viewport.pixelsPerSecond;
4678
- }
4679
-
4680
- /** True when this time sits inside the visible window. */
4681
- function isTimeInView(timeSec, viewport) {
4682
- return timeSec >= viewport.startSec && timeSec <= viewport.endSec;
4683
- }
4684
-
4685
- /** UI max zoom: 24×, ~1 peak per CSS pixel, and a minimum visible window. */
4686
- function getWaveformZoomMax({
4687
- durationSec,
4688
- peakCount,
4689
- viewWidthPx
4690
- }) {
4691
- if (!(peakCount > 0) || !(viewWidthPx > 0)) {
4692
- return constants/* WAVEFORM_ZOOM_MIN */.LW;
4693
- }
4694
- const peakLimitedMax = Math.floor(peakCount / viewWidthPx);
4695
- const durationLimitedMax = durationSec > 0 ? durationSec / constants/* WAVEFORM_MIN_VIEW_WINDOW_SEC */.Kl : constants/* WAVEFORM_ZOOM_MIN */.LW;
4696
- return Math.max(constants/* WAVEFORM_ZOOM_MIN */.LW, Math.min(constants/* WAVEFORM_ZOOM_MAX */.tK, peakLimitedMax, durationLimitedMax));
4697
- }
4698
-
4699
- /** Keep zoom between 1× and this file's max. */
4700
- function clampWaveformZoom(zoomLevel, maxZoom = constants/* WAVEFORM_ZOOM_MIN */.LW) {
4701
- const max = Math.max(constants/* WAVEFORM_ZOOM_MIN */.LW, maxZoom);
4702
- if (!Number.isFinite(zoomLevel)) {
4703
- return constants/* WAVEFORM_ZOOM_MIN */.LW;
4704
- }
4705
- return Math.min(max, Math.max(constants/* WAVEFORM_ZOOM_MIN */.LW, zoomLevel));
4706
- }
4707
-
4708
- /** WaveSurfer zoom density. 0 = fit the whole file in the view. */
4709
- function getZoomedPixelsPerSecond({
4710
- durationSec,
4711
- isTape = false,
4712
- maxZoom = constants/* WAVEFORM_ZOOM_MIN */.LW,
4713
- viewWidthPx,
4714
- zoomLevel
4715
- }) {
4716
- const zoom = clampWaveformZoom(zoomLevel, maxZoom);
4717
- if (!(durationSec > 0) || !(viewWidthPx > 0)) {
4718
- return 0;
4719
- }
4720
- if (!isTape && zoom <= constants/* WAVEFORM_ZOOM_MIN */.LW) {
4721
- return 0;
4722
- }
4723
- return viewWidthPx / durationSec * zoom;
4724
- }
4725
-
4726
- /** Half-view empty space so t=0 and duration can sit under the center pin. */
4727
- function getTapeGutterPx(widthPx) {
4728
- return widthPx > 0 ? widthPx / 2 : 0;
4729
- }
4730
-
4731
- /** Tape first paint: 10s on screen, or 1× when the file is shorter than that. */
4732
- function getTapeDefaultZoom(durationSec, maxZoom = constants/* WAVEFORM_ZOOM_MIN */.LW) {
4733
- if (!(durationSec > 0)) {
4734
- return constants/* WAVEFORM_ZOOM_MIN */.LW;
4735
- }
4736
- return clampWaveformZoom(durationSec / constants/* WAVEFORM_TAPE_DEFAULT_WINDOW_SEC */.Av, maxZoom);
4737
- }
4738
-
4739
- /** Map zoom (1…max) onto the 0–100 slider. */
4740
- function sliderValueFromZoom(zoomLevel, maxZoom = constants/* WAVEFORM_ZOOM_MIN */.LW) {
4741
- const max = Math.max(constants/* WAVEFORM_ZOOM_MIN */.LW, maxZoom);
4742
- const zoom = clampWaveformZoom(zoomLevel, max);
4743
- if (max <= constants/* WAVEFORM_ZOOM_MIN */.LW) {
4744
- return 0;
4745
- }
4746
- return (zoom - constants/* WAVEFORM_ZOOM_MIN */.LW) / (max - constants/* WAVEFORM_ZOOM_MIN */.LW) * constants/* WAVEFORM_ZOOM_SLIDER_MAX */.nh;
4747
- }
4748
-
4749
- /** Inverse of sliderValueFromZoom. */
4750
- function zoomFromSliderValue(value, maxZoom = constants/* WAVEFORM_ZOOM_MIN */.LW) {
4751
- const max = Math.max(constants/* WAVEFORM_ZOOM_MIN */.LW, maxZoom);
4752
- if (max <= constants/* WAVEFORM_ZOOM_MIN */.LW) {
4753
- return constants/* WAVEFORM_ZOOM_MIN */.LW;
4754
- }
4755
- const t = Math.min(constants/* WAVEFORM_ZOOM_SLIDER_MAX */.nh, Math.max(0, value)) / constants/* WAVEFORM_ZOOM_SLIDER_MAX */.nh;
4756
- return clampWaveformZoom(constants/* WAVEFORM_ZOOM_MIN */.LW + t * (max - constants/* WAVEFORM_ZOOM_MIN */.LW), max);
4757
- }
4758
-
4759
- /** Max scroll that still shows a full window (no overscroll). Gutters add empty lead/trail. */
4760
- function maxScrollLeft(viewport) {
4761
- const gutterPx = viewport.gutterPx || 0;
4762
- return Math.max(0, viewport.durationSec * viewport.pixelsPerSecond + 2 * gutterPx - viewport.widthPx);
4763
- }
4764
-
4765
- /** Scroll offset that still shows a full window (no overscroll). */
4766
- function clampScrollLeft(scrollLeftPx, viewport) {
4767
- if (!Number.isFinite(scrollLeftPx)) {
4768
- return 0;
4769
- }
4770
- return Math.min(maxScrollLeft(viewport), Math.max(0, scrollLeftPx));
4771
- }
4772
-
4773
- /** Center this time in the view, clamped so the window still fills the canvas. */
4774
- function getCenteredScrollLeft(timeSec, viewport) {
4775
- return clampScrollLeft(timeSec * viewport.pixelsPerSecond + (viewport.gutterPx || 0) - viewport.widthPx / 2, viewport);
4776
- }
4777
-
4778
- /**
4779
- * Host/media seek while zoomed: jump so the time (and its comment avatar) is on screen.
4780
- * No-op at fit-to-width or when that time is already visible.
4781
- */
4782
- function getSeekCameraAction({
4783
- cameraMode = 'desktop',
4784
- timeSec,
4785
- viewport
4786
- }) {
4787
- if (!(viewport.widthPx > 0) || !(viewport.pixelsPerSecond > 0)) {
4788
- return {
4789
- type: 'none'
4790
- };
4791
- }
4792
- if (cameraMode === 'tape') {
4793
- const scrollLeftPx = getCenteredScrollLeft(timeSec, viewport);
4794
- if (Math.abs(scrollLeftPx - viewport.scrollLeftPx) < 1) {
4795
- return {
4796
- type: 'none'
4797
- };
4798
- }
4799
- return {
4800
- type: 'jump',
4801
- scrollLeftPx
4802
- };
4803
- }
4804
- if (viewport.zoomLevel <= constants/* WAVEFORM_ZOOM_MIN */.LW) {
4805
- return {
4806
- type: 'none'
4807
- };
4808
- }
4809
- if (isTimeInView(timeSec, viewport)) {
4810
- return {
4811
- type: 'none'
4812
- };
4813
- }
4814
- return {
4815
- type: 'jump',
4816
- scrollLeftPx: getCenteredScrollLeft(timeSec, viewport)
4817
- };
4818
- }
4819
-
4820
- /** Always keep this time under the center pin. Reuses followRight so the camera hook can pin. */
4821
- function getTapeCameraAction({
4822
- timeSec,
4823
- viewport
4824
- }) {
4825
- if (!(viewport.widthPx > 0) || !(viewport.pixelsPerSecond > 0)) {
4826
- return {
4827
- type: 'none'
4828
- };
4829
- }
4830
- return {
4831
- isPlayheadPinned: true,
4832
- scrollLeftPx: getCenteredScrollLeft(timeSec, viewport),
4833
- type: 'followRight'
4834
- };
4835
- }
4836
-
4837
- /** Follow inset in CSS px, capped at one third of the view so a narrow player still has room. */
4838
- function getFollowInsetPx(widthPx, insetPx = constants/* WAVEFORM_FOLLOW_INSET_PX */.NM) {
4839
- if (!(widthPx > 0)) {
4840
- return 0;
4841
- }
4842
- return Math.min(Math.max(0, insetPx), widthPx / 3);
4843
- }
4844
-
4845
- /** CSS left for a playhead pinned to the follow inset. */
4846
- function getPinnedPlayheadLeft(widthPx, insetPx = constants/* WAVEFORM_FOLLOW_INSET_PX */.NM) {
4847
- const inset = getFollowInsetPx(widthPx, insetPx);
4848
- if (!(widthPx > 0)) {
4849
- return '0%';
4850
- }
4851
- return `${(widthPx - inset) / widthPx * 100}%`;
4852
- }
4853
-
4854
- /** CSS left for a playhead locked to the center of the view. */
4855
- function getTapePinnedPlayheadLeft() {
4856
- return '50%';
4857
- }
4858
-
4859
- /** CSS left % of the playhead from the left of the visible window. */
4860
- function timeLeftPercent(timeSec, durationSec, viewport) {
4861
- if (viewport.widthPx > 0 && viewport.pixelsPerSecond > 0) {
4862
- return `${positionPxFromTime(timeSec, viewport) / viewport.widthPx * 100}%`;
4863
- }
4864
- const progress = durationSec > 0 ? Math.min(1, Math.max(0, timeSec / durationSec)) : 0;
4865
- return `${progress * 100}%`;
4866
- }
4867
-
4868
- /**
4869
- * Walk across the view until the playhead nears the right edge, then follow.
4870
- * Off-screen at play start jumps in; off-screen right while playing keeps following.
4871
- */
4872
- function getPlayheadCameraAction({
4873
- cameraMode = 'desktop',
4874
- insetPx = constants/* WAVEFORM_FOLLOW_INSET_PX */.NM,
4875
- isPlaying,
4876
- playJustStarted,
4877
- timeSec,
4878
- viewport
4879
- }) {
4880
- if (cameraMode === 'tape') {
4881
- return getTapeCameraAction({
4882
- timeSec,
4883
- viewport
4884
- });
4885
- }
4886
- if (!isPlaying || viewport.zoomLevel <= constants/* WAVEFORM_ZOOM_MIN */.LW || !(viewport.widthPx > 0) || !(viewport.pixelsPerSecond > 0)) {
4887
- return {
4888
- type: 'none'
4889
- };
4890
- }
4891
- const inset = getFollowInsetPx(viewport.widthPx, insetPx);
4892
- const viewX = positionPxFromTime(timeSec, viewport);
4893
- const playheadCanvasX = timeSec * viewport.pixelsPerSecond;
4894
- const followX = viewport.widthPx - inset;
4895
- if (viewX < 0) {
4896
- if (!playJustStarted) {
4897
- return {
4898
- type: 'none'
4899
- };
4900
- }
4901
- return {
4902
- type: 'jump',
4903
- scrollLeftPx: clampScrollLeft(playheadCanvasX - inset, viewport)
4904
- };
4905
- }
4906
- if (viewX >= followX) {
4907
- const unclampedScroll = playheadCanvasX - followX;
4908
- const scrollLeftPx = clampScrollLeft(unclampedScroll, viewport);
4909
- if (playJustStarted && viewX > viewport.widthPx) {
4910
- return {
4911
- type: 'jump',
4912
- scrollLeftPx
4913
- };
4914
- }
4915
- return {
4916
- type: 'followRight',
4917
- isPlayheadPinned: unclampedScroll <= maxScrollLeft(viewport),
4918
- scrollLeftPx
4919
- };
4920
- }
4921
- return {
4922
- type: 'none'
4923
- };
4924
- }
4736
+ // EXTERNAL MODULE: ./src/lib/viewers/media/waveform/viewport.ts
4737
+ var waveform_viewport = __webpack_require__(772);
4925
4738
  ;// ./src/lib/viewers/media/waveform/useTapeWaveform.ts
4926
4739
 
4927
4740
 
@@ -5123,6 +4936,32 @@ function WaveformCommentMarkers({
5123
4936
  }));
5124
4937
  })));
5125
4938
  }
4939
+ ;// ./src/lib/viewers/media/waveform/WaveformGeneratingIndicator.scss
4940
+ // extracted by mini-css-extract-plugin
4941
+
4942
+ ;// ./src/lib/viewers/media/waveform/WaveformGeneratingIndicator.tsx
4943
+
4944
+
4945
+ function WaveformGeneratingIndicator() {
4946
+ const label = "Generating waveform";
4947
+ return /*#__PURE__*/external_react_["default"].createElement("div", {
4948
+ "aria-live": "polite",
4949
+ className: "bp-WaveformGeneratingIndicator",
4950
+ "data-testid": "bp-waveform-generating",
4951
+ role: "status"
4952
+ }, /*#__PURE__*/external_react_["default"].createElement("span", {
4953
+ "aria-hidden": "true",
4954
+ className: "bp-WaveformGeneratingIndicator-bars"
4955
+ }, /*#__PURE__*/external_react_["default"].createElement("span", {
4956
+ className: "bp-WaveformGeneratingIndicator-bar"
4957
+ }), /*#__PURE__*/external_react_["default"].createElement("span", {
4958
+ className: "bp-WaveformGeneratingIndicator-bar"
4959
+ }), /*#__PURE__*/external_react_["default"].createElement("span", {
4960
+ className: "bp-WaveformGeneratingIndicator-bar"
4961
+ })), /*#__PURE__*/external_react_["default"].createElement("span", {
4962
+ className: "bp-WaveformGeneratingIndicator-label"
4963
+ }, label));
4964
+ }
5126
4965
  ;// external "react-dom"
5127
4966
 
5128
4967
  ;// ./node_modules/wavesurfer.js/dist/wavesurfer.esm.js
@@ -5213,15 +5052,62 @@ function getBufferedProgress(bufferedRange, durationSec) {
5213
5052
  function getPlayedWaveformColor(isTape = false) {
5214
5053
  return isTape ? WAVEFORM_COLOR_HOVER_PLAYED : WAVEFORM_COLOR_PLAYED;
5215
5054
  }
5055
+ function rangeFills({
5056
+ buffer,
5057
+ hover,
5058
+ rangeEnd,
5059
+ rangeStart
5060
+ }) {
5061
+ const start = clampTo0And1(rangeStart);
5062
+ const end = clampTo0And1(rangeEnd);
5063
+ const hoverInside = hover != null && hover > start && hover < end;
5064
+ const outsideAfter = [{
5065
+ color: WAVEFORM_COLOR_UNPLAYED,
5066
+ end: Math.max(end, buffer),
5067
+ start: end
5068
+ }, {
5069
+ color: WAVEFORM_COLOR_BUFFER,
5070
+ end: 1,
5071
+ start: Math.max(end, buffer)
5072
+ }];
5073
+ const ranges = hoverInside ? [{
5074
+ color: WAVEFORM_COLOR_UNPLAYED,
5075
+ end: start,
5076
+ start: 0
5077
+ }, {
5078
+ color: WAVEFORM_COLOR_HOVER_PLAYED,
5079
+ end: hover,
5080
+ start
5081
+ }, {
5082
+ color: WAVEFORM_COLOR_HOVER_AREA,
5083
+ end,
5084
+ start: hover
5085
+ }, ...outsideAfter] : [{
5086
+ color: WAVEFORM_COLOR_UNPLAYED,
5087
+ end: start,
5088
+ start: 0
5089
+ }, {
5090
+ color: WAVEFORM_COLOR_PLAYED,
5091
+ end,
5092
+ start
5093
+ }, ...outsideAfter];
5094
+ const fill = gradientStopsFromColorRanges(ranges);
5095
+ return {
5096
+ progressColor: fill,
5097
+ waveColor: fill
5098
+ };
5099
+ }
5216
5100
 
5217
5101
  /**
5218
5102
  * progressColor paints left of the playhead (clipped by wavesurfer).
5219
5103
  * waveColor paints right of the playhead, including buffered vs not-yet-buffered.
5104
+ * An open comment range replaces the played/unplayed split with in-range / out-of-range.
5220
5105
  */
5221
5106
  function getWaveformFills({
5222
5107
  bufferProgress,
5223
5108
  hoverProgress,
5224
- isTape = false
5109
+ isTape = false,
5110
+ rangeProgress
5225
5111
  }) {
5226
5112
  const buffer = clampTo0And1(bufferProgress);
5227
5113
  const playedColor = getPlayedWaveformColor(isTape);
@@ -5231,6 +5117,14 @@ function getWaveformFills({
5231
5117
  waveColor: WAVEFORM_COLOR_UNPLAYED
5232
5118
  };
5233
5119
  }
5120
+ if (rangeProgress && rangeProgress.end > rangeProgress.start) {
5121
+ return rangeFills({
5122
+ buffer,
5123
+ hover: hoverProgress == null ? null : clampTo0And1(hoverProgress),
5124
+ rangeEnd: rangeProgress.end,
5125
+ rangeStart: rangeProgress.start
5126
+ });
5127
+ }
5234
5128
  if (hoverProgress == null) {
5235
5129
  return {
5236
5130
  progressColor: playedColor,
@@ -5394,6 +5288,8 @@ function tintWaveformTiles({
5394
5288
  });
5395
5289
  });
5396
5290
  }
5291
+ // EXTERNAL MODULE: ./src/lib/viewers/media/waveform/range.ts
5292
+ var waveform_range = __webpack_require__(4253);
5397
5293
  ;// ./src/lib/viewers/media/waveform/usePlayheadCamera.ts
5398
5294
 
5399
5295
 
@@ -5487,7 +5383,7 @@ function usePlayheadCamera({
5487
5383
  wavesurfer.setScroll(scrollLeftPx);
5488
5384
  const appliedScrollLeft = getScrollLeft(wavesurfer, scrollLeftPx);
5489
5385
  lastCameraScrollRef.current = appliedScrollLeft;
5490
- viewportRef.current = getViewportAtScroll(viewportRef.current, appliedScrollLeft);
5386
+ viewportRef.current = (0,waveform_viewport/* getViewportAtScroll */.NI)(viewportRef.current, appliedScrollLeft);
5491
5387
  window.requestAnimationFrame(() => {
5492
5388
  // Keep the flag through delayed `scroll` while the camera is driving.
5493
5389
  if (jumpAnimationRef.current || isFollowPinnedRef.current) {
@@ -5504,7 +5400,7 @@ function usePlayheadCamera({
5504
5400
  if (!playhead || !isPinned || !(viewport.widthPx > 0)) {
5505
5401
  return;
5506
5402
  }
5507
- playhead.style.left = isTape() ? getTapePinnedPlayheadLeft() : getPinnedPlayheadLeft(viewport.widthPx);
5403
+ playhead.style.left = isTape() ? (0,waveform_viewport/* getTapePinnedPlayheadLeft */.yW)() : (0,waveform_viewport/* getPinnedPlayheadLeft */.gl)(viewport.widthPx);
5508
5404
  }, [isTape]);
5509
5405
  const apply = (0,external_react_.useCallback)((timeSec, playJustStarted = false) => {
5510
5406
  mediaTimeRef.current = timeSec;
@@ -5523,8 +5419,8 @@ function usePlayheadCamera({
5523
5419
  if (jumpAnimationRef.current && !playJustStarted) {
5524
5420
  return;
5525
5421
  }
5526
- const liveViewport = getViewportAtScroll(viewport, getScrollLeft(wavesurfer, viewport.scrollLeftPx));
5527
- const action = getPlayheadCameraAction({
5422
+ const liveViewport = (0,waveform_viewport/* getViewportAtScroll */.NI)(viewport, getScrollLeft(wavesurfer, viewport.scrollLeftPx));
5423
+ const action = (0,waveform_viewport/* getPlayheadCameraAction */.aJ)({
5528
5424
  cameraMode: cameraModeRef.current,
5529
5425
  isPlaying: true,
5530
5426
  playJustStarted,
@@ -5562,12 +5458,12 @@ function usePlayheadCamera({
5562
5458
  const start = (0,util/* getCurrentTimeMs */.RU)();
5563
5459
  const tick = now => {
5564
5460
  const t = Math.min(1, (now - start) / constants/* WAVEFORM_PLAYHEAD_JUMP_MS */.Bq);
5565
- const liveAction = getPlayheadCameraAction({
5461
+ const liveAction = (0,waveform_viewport/* getPlayheadCameraAction */.aJ)({
5566
5462
  cameraMode: cameraModeRef.current,
5567
5463
  isPlaying: true,
5568
5464
  playJustStarted: true,
5569
5465
  timeSec: mediaTimeRef.current,
5570
- viewport: getViewportAtScroll(viewportRef.current, getScrollLeft(wavesurfer, from))
5466
+ viewport: (0,waveform_viewport/* getViewportAtScroll */.NI)(viewportRef.current, getScrollLeft(wavesurfer, from))
5571
5467
  });
5572
5468
  const to = liveAction.type === 'none' ? action.scrollLeftPx : liveAction.scrollLeftPx;
5573
5469
  applyScrollLeft(from + (to - from) * (1 - (1 - t) * (1 - t)), false);
@@ -5590,7 +5486,7 @@ function usePlayheadCamera({
5590
5486
  const isUserPan = (0,external_react_.useCallback)(scrollLeftPx => {
5591
5487
  const isCameraOwnedScroll = programmaticScrollRef.current || jumpAnimationRef.current !== 0;
5592
5488
  const lastCameraScroll = lastCameraScrollRef.current;
5593
- const maxScroll = maxScrollLeft(viewportRef.current);
5489
+ const maxScroll = (0,waveform_viewport/* maxScrollLeft */.d0)(viewportRef.current);
5594
5490
  const isClampedToMax = lastCameraScroll != null && scrollLeftPx >= maxScroll - 1 && lastCameraScroll >= maxScroll - 1;
5595
5491
  const isFollowCameraScroll = isFollowPinnedRef.current && lastCameraScroll != null && Math.abs(scrollLeftPx - lastCameraScroll) <= 1;
5596
5492
  return !(isCameraOwnedScroll && (jumpAnimationRef.current !== 0 || isFollowCameraScroll || isClampedToMax || !isFollowPinnedRef.current));
@@ -5606,8 +5502,8 @@ function usePlayheadCamera({
5606
5502
  if (!isTape()) {
5607
5503
  isFollowPinnedRef.current = false;
5608
5504
  }
5609
- const liveViewport = getViewportAtScroll(viewportRef.current, getScrollLeft(wavesurfer, viewportRef.current.scrollLeftPx));
5610
- const action = getSeekCameraAction({
5505
+ const liveViewport = (0,waveform_viewport/* getViewportAtScroll */.NI)(viewportRef.current, getScrollLeft(wavesurfer, viewportRef.current.scrollLeftPx));
5506
+ const action = (0,waveform_viewport/* getSeekCameraAction */.wA)({
5611
5507
  cameraMode: cameraModeRef.current,
5612
5508
  timeSec,
5613
5509
  viewport: liveViewport
@@ -5628,7 +5524,7 @@ function usePlayheadCamera({
5628
5524
  applyScrollLeft(from + (to - from) * (1 - (1 - t) * (1 - t)), false);
5629
5525
  const playhead = playheadRef.current;
5630
5526
  if (playhead) {
5631
- playhead.style.left = isTape() ? getTapePinnedPlayheadLeft() : timeLeftPercent(mediaTimeRef.current, viewportRef.current.durationSec, viewportRef.current);
5527
+ playhead.style.left = isTape() ? (0,waveform_viewport/* getTapePinnedPlayheadLeft */.yW)() : (0,waveform_viewport/* timeLeftPercent */.Mu)(mediaTimeRef.current, viewportRef.current.durationSec, viewportRef.current);
5632
5528
  }
5633
5529
  if (t < 1) {
5634
5530
  jumpAnimationRef.current = window.requestAnimationFrame(tick);
@@ -5646,16 +5542,16 @@ function usePlayheadCamera({
5646
5542
  }
5647
5543
  const scrollLeftPx = getScrollLeft(wavesurfer);
5648
5544
  if (!isUserPan(scrollLeftPx)) {
5649
- viewportRef.current = getViewportAtScroll(viewportRef.current, scrollLeftPx);
5545
+ viewportRef.current = (0,waveform_viewport/* getViewportAtScroll */.NI)(viewportRef.current, scrollLeftPx);
5650
5546
  return;
5651
5547
  }
5652
- viewportRef.current = getViewportAtScroll(viewportRef.current, scrollLeftPx);
5548
+ viewportRef.current = (0,waveform_viewport/* getViewportAtScroll */.NI)(viewportRef.current, scrollLeftPx);
5653
5549
  const playhead = playheadRef.current;
5654
5550
  if (isTape()) {
5655
5551
  if (playhead) {
5656
- playhead.style.left = getTapePinnedPlayheadLeft();
5552
+ playhead.style.left = (0,waveform_viewport/* getTapePinnedPlayheadLeft */.yW)();
5657
5553
  }
5658
- const pinTimeSec = Math.min(viewportRef.current.durationSec, Math.max(0, timeFromPositionPx(viewportRef.current.widthPx / 2, viewportRef.current)));
5554
+ const pinTimeSec = Math.min(viewportRef.current.durationSec, Math.max(0, (0,waveform_viewport/* timeFromPositionPx */.lZ)(viewportRef.current.widthPx / 2, viewportRef.current)));
5659
5555
  userIsScrollingRef.current = true;
5660
5556
  window.clearTimeout(scrollSettleTimerRef.current);
5661
5557
  scrollSettleTimerRef.current = window.setTimeout(() => {
@@ -5673,7 +5569,7 @@ function usePlayheadCamera({
5673
5569
  }
5674
5570
  const timeSec = readMediaTime();
5675
5571
  if (playhead) {
5676
- playhead.style.left = timeLeftPercent(timeSec, viewportRef.current.durationSec, viewportRef.current);
5572
+ playhead.style.left = (0,waveform_viewport/* timeLeftPercent */.Mu)(timeSec, viewportRef.current.durationSec, viewportRef.current);
5677
5573
  }
5678
5574
  userIsScrollingRef.current = true;
5679
5575
  holdFollowUntilInsetRef.current = true;
@@ -5709,15 +5605,318 @@ function usePlayheadCamera({
5709
5605
  seekTo
5710
5606
  };
5711
5607
  }
5608
+ ;// ./src/lib/viewers/media/waveform/WaveformRangeSelection.scss
5609
+ // extracted by mini-css-extract-plugin
5610
+
5611
+ ;// ./src/lib/viewers/media/waveform/WaveformRangeSelection.tsx
5612
+
5613
+
5614
+
5615
+
5616
+
5617
+
5618
+ function offsetLeftCss(base, offsetPx) {
5619
+ if (!offsetPx) {
5620
+ return base;
5621
+ }
5622
+ const op = offsetPx < 0 ? '-' : '+';
5623
+ return `calc(${base} ${op} ${Math.abs(offsetPx)}px)`;
5624
+ }
5625
+ function applyHandleLeft(el, timeSec, durationSec, viewport, collapsedOffsetPx = 0) {
5626
+ if (!el) {
5627
+ return;
5628
+ }
5629
+ el.style.left = offsetLeftCss((0,waveform_viewport/* timeLeftPercent */.Mu)(timeSec, durationSec, viewport), collapsedOffsetPx);
5630
+ }
5631
+ function applyRegion(el, startSec, endSec, durationSec, viewport, collapsed) {
5632
+ if (!el) {
5633
+ return;
5634
+ }
5635
+ const start = (0,waveform_viewport/* timeLeftPercent */.Mu)(startSec, durationSec, viewport);
5636
+ const halfLine = constants/* WAVEFORM_RANGE_HANDLE_LINE_PX */.h1 / 2;
5637
+ if (collapsed) {
5638
+ const extentPx = constants/* WAVEFORM_RANGE_COLLAPSED_OFFSET_PX */.BG + halfLine;
5639
+ el.style.left = offsetLeftCss(start, -extentPx);
5640
+ el.style.width = `${extentPx * 2}px`;
5641
+ return;
5642
+ }
5643
+ const end = (0,waveform_viewport/* timeLeftPercent */.Mu)(endSec, durationSec, viewport);
5644
+ el.style.left = offsetLeftCss(start, -halfLine);
5645
+ el.style.width = `calc(${end} - ${start} + ${constants/* WAVEFORM_RANGE_HANDLE_LINE_PX */.h1}px)`;
5646
+ }
5647
+ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(function WaveformRangeSelection({
5648
+ currentTimeSec = 0,
5649
+ durationSec,
5650
+ getPlayheadSec,
5651
+ interactive = true,
5652
+ isHighlighted = false,
5653
+ keepHighlight = true,
5654
+ onDragChange,
5655
+ onPreviewChange,
5656
+ onRangeChange,
5657
+ range,
5658
+ viewport
5659
+ }, ref) {
5660
+ const rootRef = (0,external_react_.useRef)(null);
5661
+ const regionRef = (0,external_react_.useRef)(null);
5662
+ const startHandleRef = (0,external_react_.useRef)(null);
5663
+ const endHandleRef = (0,external_react_.useRef)(null);
5664
+ const tooltipRef = (0,external_react_.useRef)(null);
5665
+ const dragRef = (0,external_react_.useRef)(null);
5666
+ const rangeRef = (0,external_react_.useRef)(range);
5667
+ const displayedRef = (0,external_react_.useRef)((0,waveform_range/* resolveRange */.Ss)(range, (0,waveform_range/* durationMsFromSec */.uH)(durationSec)));
5668
+ const viewportRef = (0,external_react_.useRef)(viewport);
5669
+ const durationSecRef = (0,external_react_.useRef)(durationSec);
5670
+ const onDragChangeRef = (0,external_react_.useRef)(onDragChange);
5671
+ const onPreviewChangeRef = (0,external_react_.useRef)(onPreviewChange);
5672
+ const onRangeChangeRef = (0,external_react_.useRef)(onRangeChange);
5673
+ const getPlayheadSecRef = (0,external_react_.useRef)(getPlayheadSec);
5674
+ const [dragRange, setDragRange] = (0,external_react_.useState)(null);
5675
+ const [activeHandle, setActiveHandle] = (0,external_react_.useState)(null);
5676
+ const activeHandleRef = (0,external_react_.useRef)(null);
5677
+ rangeRef.current = range;
5678
+ durationSecRef.current = durationSec;
5679
+ onDragChangeRef.current = onDragChange;
5680
+ onPreviewChangeRef.current = onPreviewChange;
5681
+ onRangeChangeRef.current = onRangeChange;
5682
+ getPlayheadSecRef.current = getPlayheadSec;
5683
+ activeHandleRef.current = activeHandle;
5684
+ const durationMs = (0,waveform_range/* durationMsFromSec */.uH)(durationSec);
5685
+ const displayed = dragRange ?? (0,waveform_range/* resolveRange */.Ss)(range, durationMs);
5686
+ displayedRef.current = displayed;
5687
+ const syncPositions = (0,external_react_.useCallback)((next, nextViewport, nextDurationSec) => {
5688
+ displayedRef.current = next;
5689
+ const collapsed = next.startMs === next.endMs;
5690
+ applyRegion(regionRef.current, next.startMs / 1000, next.endMs / 1000, nextDurationSec, nextViewport, collapsed);
5691
+ applyHandleLeft(startHandleRef.current, next.startMs / 1000, nextDurationSec, nextViewport, collapsed ? -constants/* WAVEFORM_RANGE_COLLAPSED_OFFSET_PX */.BG : 0);
5692
+ applyHandleLeft(endHandleRef.current, next.endMs / 1000, nextDurationSec, nextViewport, collapsed ? constants/* WAVEFORM_RANGE_COLLAPSED_OFFSET_PX */.BG : 0);
5693
+ const tooltip = tooltipRef.current;
5694
+ if (tooltip) {
5695
+ const tooltipHandle = activeHandleRef.current ?? 'end';
5696
+ const tooltipMs = tooltipHandle === 'start' ? next.startMs : next.endMs;
5697
+ tooltip.style.left = (0,waveform_viewport/* timeLeftPercent */.Mu)(tooltipMs / 1000, nextDurationSec, nextViewport);
5698
+ }
5699
+ }, []);
5700
+ (0,external_react_.useImperativeHandle)(ref, () => ({
5701
+ applyViewport: nextViewport => {
5702
+ viewportRef.current = nextViewport;
5703
+ syncPositions(displayedRef.current, nextViewport, durationSecRef.current);
5704
+ }
5705
+ }), [syncPositions]);
5706
+ (0,external_react_.useLayoutEffect)(() => {
5707
+ syncPositions(displayedRef.current, viewportRef.current, durationSec);
5708
+ }, [activeHandle, displayed.endMs, displayed.startMs, durationSec, syncPositions]);
5709
+ (0,external_react_.useLayoutEffect)(() => {
5710
+ const next = (0,waveform_viewport/* createWaveformViewport */.eq)({
5711
+ durationSec: viewport.durationSec,
5712
+ heightPx: viewport.heightPx,
5713
+ maxZoom: viewport.maxZoom,
5714
+ scrollLeftPx: viewportRef.current.scrollLeftPx,
5715
+ widthPx: viewport.widthPx,
5716
+ zoomLevel: viewport.zoomLevel
5717
+ });
5718
+ viewportRef.current = next;
5719
+ syncPositions(displayedRef.current, next, durationSecRef.current);
5720
+ }, [syncPositions, viewport.durationSec, viewport.heightPx, viewport.maxZoom, viewport.widthPx, viewport.zoomLevel]);
5721
+ const endDrag = (0,external_react_.useCallback)(event => {
5722
+ const drag = dragRef.current;
5723
+ if (!drag || event.pointerId && drag.pointerId !== event.pointerId) {
5724
+ return;
5725
+ }
5726
+ [startHandleRef.current, endHandleRef.current].forEach(handleEl => {
5727
+ if (handleEl && handleEl.hasPointerCapture(event.pointerId)) {
5728
+ handleEl.releasePointerCapture(event.pointerId);
5729
+ }
5730
+ });
5731
+ dragRef.current = null;
5732
+ setActiveHandle(null);
5733
+ setDragRange(null);
5734
+ onDragChangeRef.current?.(false);
5735
+ const didChange = drag.range.startMs !== drag.originRange.startMs || drag.range.endMs !== drag.originRange.endMs;
5736
+ if (!didChange) {
5737
+ return;
5738
+ }
5739
+ const committed = (0,waveform_range/* commitRangeChange */.dG)(drag.range);
5740
+ if (committed) {
5741
+ onRangeChangeRef.current?.(committed);
5742
+ }
5743
+ }, []);
5744
+ const moveDrag = (0,external_react_.useCallback)(event => {
5745
+ const drag = dragRef.current;
5746
+ if (!drag || event.pointerId && drag.pointerId !== event.pointerId) {
5747
+ return;
5748
+ }
5749
+ const root = rootRef.current;
5750
+ if (!root) {
5751
+ return;
5752
+ }
5753
+ const rect = root.getBoundingClientRect();
5754
+ const pointerX = event.clientX - rect.left;
5755
+ const vp = viewportRef.current;
5756
+ const duration = (0,waveform_range/* durationMsFromSec */.uH)(durationSecRef.current);
5757
+ const playheadSec = getPlayheadSecRef.current?.() ?? currentTimeSec;
5758
+ const pointerMs = (0,waveform_range/* snapTimeMs */.Mh)({
5759
+ pixelsPerSecond: vp.pixelsPerSecond,
5760
+ playheadMs: playheadSec * 1000,
5761
+ timeMs: (0,waveform_range/* pointerTimeMs */.v2)(pointerX, vp)
5762
+ });
5763
+ if (drag.originMs === drag.range.startMs && drag.originMs === drag.range.endMs && pointerMs === drag.originMs) {
5764
+ return;
5765
+ }
5766
+ const next = (0,waveform_range/* dragRangeHandle */.Mo)({
5767
+ durationMs: duration,
5768
+ handle: drag.handle,
5769
+ pointerMs,
5770
+ range: {
5771
+ endMs: drag.range.endMs,
5772
+ startMs: drag.range.startMs
5773
+ }
5774
+ });
5775
+ drag.handle = next.handle;
5776
+ drag.range = next.range;
5777
+ setActiveHandle(next.handle);
5778
+ setDragRange(next.range);
5779
+ syncPositions(next.range, vp, durationSecRef.current);
5780
+ onPreviewChangeRef.current?.({
5781
+ endMs: next.range.endMs,
5782
+ startMs: next.range.startMs
5783
+ });
5784
+ }, [currentTimeSec, syncPositions]);
5785
+ const startDrag = (0,external_react_.useCallback)((handle, event) => {
5786
+ if (!interactive || event.button > 1 || event.ctrlKey || event.metaKey) {
5787
+ return;
5788
+ }
5789
+ event.preventDefault();
5790
+ event.stopPropagation();
5791
+ const resolved = (0,waveform_range/* resolveRange */.Ss)(rangeRef.current, (0,waveform_range/* durationMsFromSec */.uH)(durationSecRef.current));
5792
+ dragRef.current = {
5793
+ handle,
5794
+ originMs: handle === 'start' ? resolved.startMs : resolved.endMs,
5795
+ originRange: {
5796
+ endMs: resolved.endMs,
5797
+ startMs: resolved.startMs
5798
+ },
5799
+ pointerId: event.pointerId,
5800
+ range: resolved
5801
+ };
5802
+ event.currentTarget.setPointerCapture(event.pointerId);
5803
+ setActiveHandle(handle);
5804
+ setDragRange(resolved);
5805
+ onDragChangeRef.current?.(true);
5806
+ onPreviewChangeRef.current?.({
5807
+ endMs: (0,waveform_range/* isRangeCollapsed */.BT)(rangeRef.current) ? null : resolved.endMs,
5808
+ startMs: resolved.startMs
5809
+ });
5810
+ }, [interactive]);
5811
+ const onHandlePointerDown = (0,external_react_.useCallback)(event => {
5812
+ const root = rootRef.current;
5813
+ if (!root) {
5814
+ return;
5815
+ }
5816
+ const rect = root.getBoundingClientRect();
5817
+ const pointerX = event.clientX - rect.left;
5818
+ const vp = viewportRef.current;
5819
+ const resolved = (0,waveform_range/* resolveRange */.Ss)(rangeRef.current, (0,waveform_range/* durationMsFromSec */.uH)(durationSecRef.current));
5820
+ const handle = (0,waveform_range/* pickRangeHandle */.HG)({
5821
+ endX: (0,waveform_range/* visualRangeHandlePx */.Eh)({
5822
+ handle: 'end',
5823
+ range: resolved,
5824
+ viewport: vp
5825
+ }),
5826
+ pointerX,
5827
+ startX: (0,waveform_range/* visualRangeHandlePx */.Eh)({
5828
+ handle: 'start',
5829
+ range: resolved,
5830
+ viewport: vp
5831
+ })
5832
+ });
5833
+ startDrag(handle, event);
5834
+ }, [startDrag]);
5835
+ (0,external_react_.useEffect)(() => {
5836
+ const onMove = event => {
5837
+ if (!dragRef.current) {
5838
+ return;
5839
+ }
5840
+ event.preventDefault();
5841
+ moveDrag(event);
5842
+ };
5843
+ const onUp = event => {
5844
+ if (!dragRef.current) {
5845
+ return;
5846
+ }
5847
+ endDrag(event);
5848
+ };
5849
+ window.addEventListener('pointermove', onMove);
5850
+ window.addEventListener('pointerup', onUp);
5851
+ window.addEventListener('pointercancel', onUp);
5852
+ return () => {
5853
+ window.removeEventListener('pointermove', onMove);
5854
+ window.removeEventListener('pointerup', onUp);
5855
+ window.removeEventListener('pointercancel', onUp);
5856
+ };
5857
+ }, [endDrag, moveDrag]);
5858
+ (0,external_react_.useEffect)(() => {
5859
+ return () => {
5860
+ if (!dragRef.current) {
5861
+ return;
5862
+ }
5863
+ dragRef.current = null;
5864
+ onDragChangeRef.current?.(false);
5865
+ };
5866
+ }, []);
5867
+ if (!(durationSec > 0) || !(durationMs >= 0)) {
5868
+ return null;
5869
+ }
5870
+ const tooltipHandle = activeHandle ?? 'end';
5871
+ const tooltipMs = tooltipHandle === 'start' ? displayed.startMs : displayed.endMs;
5872
+ const collapsed = displayed.startMs === displayed.endMs;
5873
+ const highlight = isHighlighted || collapsed;
5874
+ return /*#__PURE__*/external_react_["default"].createElement("div", {
5875
+ ref: rootRef,
5876
+ className: `bp-WaveformRange${keepHighlight ? ' bp-WaveformRange--persist' : ''}${highlight ? ' bp-WaveformRange--highlight' : ''}`,
5877
+ "data-collapsed": collapsed ? 'true' : 'false',
5878
+ "data-highlighted": highlight ? 'true' : 'false',
5879
+ "data-testid": "bp-waveform-range"
5880
+ }, /*#__PURE__*/external_react_["default"].createElement("div", {
5881
+ ref: regionRef,
5882
+ className: "bp-WaveformRange-region",
5883
+ "data-testid": "bp-waveform-range-region"
5884
+ }), /*#__PURE__*/external_react_["default"].createElement("div", {
5885
+ ref: startHandleRef,
5886
+ className: `bp-WaveformRange-handle bp-WaveformRange-handle--start${activeHandle === 'start' ? ' bp-WaveformRange-handle--dragging' : ''}`,
5887
+ "data-testid": "bp-waveform-range-handle-start",
5888
+ onMouseMove: event => event.stopPropagation(),
5889
+ onPointerDown: onHandlePointerDown
5890
+ }, /*#__PURE__*/external_react_["default"].createElement("div", {
5891
+ className: "bp-WaveformRange-handleGrip"
5892
+ })), /*#__PURE__*/external_react_["default"].createElement("div", {
5893
+ ref: endHandleRef,
5894
+ className: `bp-WaveformRange-handle bp-WaveformRange-handle--end${activeHandle === 'end' ? ' bp-WaveformRange-handle--dragging' : ''}`,
5895
+ "data-testid": "bp-waveform-range-handle-end",
5896
+ onMouseMove: event => event.stopPropagation(),
5897
+ onPointerDown: onHandlePointerDown
5898
+ }, /*#__PURE__*/external_react_["default"].createElement("div", {
5899
+ className: "bp-WaveformRange-handleGrip"
5900
+ })), activeHandle && /*#__PURE__*/external_react_["default"].createElement("div", {
5901
+ ref: tooltipRef,
5902
+ className: "bp-WaveformRange-tooltip",
5903
+ "data-testid": "bp-waveform-range-tooltip"
5904
+ }, /*#__PURE__*/external_react_["default"].createElement("div", {
5905
+ className: "bp-WaveformRange-tooltipTime"
5906
+ }, formatTime(tooltipMs / 1000))));
5907
+ });
5908
+ /* harmony default export */ const waveform_WaveformRangeSelection = (/*#__PURE__*/external_react_["default"].memo(WaveformRangeSelection));
5712
5909
  ;// ./src/lib/viewers/media/waveform/WaveformView.scss
5713
5910
  // extracted by mini-css-extract-plugin
5714
5911
 
5715
5912
  ;// ./src/lib/viewers/media/waveform/WaveformView.tsx
5716
- function WaveformView_ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
5717
- function WaveformView_objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? WaveformView_ownKeys(Object(t), !0).forEach(function (r) { WaveformView_defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : WaveformView_ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5718
- function WaveformView_defineProperty(e, r, t) { return (r = WaveformView_toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
5719
- function WaveformView_toPropertyKey(t) { var i = WaveformView_toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
5720
- function WaveformView_toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
5913
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
5914
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5915
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
5916
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
5917
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
5918
+
5919
+
5721
5920
 
5722
5921
 
5723
5922
 
@@ -5838,10 +6037,14 @@ function WaveformView({
5838
6037
  isPlaying = false,
5839
6038
  mediaEl,
5840
6039
  onPlayPause,
6040
+ onRangeChange,
6041
+ onRangeClear,
6042
+ onRangeDragChange,
5841
6043
  onSeek,
5842
6044
  onViewportChange,
5843
6045
  onZoomChange,
5844
6046
  peaks,
6047
+ range = null,
5845
6048
  zoomLevel: zoomLevelProp
5846
6049
  }) {
5847
6050
  // DOM / WaveSurfer
@@ -5850,6 +6053,7 @@ function WaveformView({
5850
6053
  const playheadRef = (0,external_react_.useRef)(null); // playhead element the camera positions
5851
6054
  const trackRef = (0,external_react_.useRef)(null); // hover / tap target around the canvas
5852
6055
  const wavesurferRef = (0,external_react_.useRef)(null); // WaveSurfer instance
6056
+ const rangeLayerRef = (0,external_react_.useRef)(null);
5853
6057
 
5854
6058
  // Latest props for WaveSurfer + media listeners that must not re-subscribe each render.
5855
6059
  const cameraModeRef = (0,external_react_.useRef)(cameraMode); // latest tape/desktop; WaveSurfer create() deps stay empty
@@ -5861,6 +6065,7 @@ function WaveformView({
5861
6065
  const mediaElRef = (0,external_react_.useRef)(mediaEl); // latest <audio>/<video>; camera + playhead tick read this
5862
6066
  const onPlayPauseRef = (0,external_react_.useRef)(onPlayPause); // latest play/pause; tape tap must not re-bind
5863
6067
  const onSeekRef = (0,external_react_.useRef)(onSeek); // latest onSeek; click/scroll handlers must not re-bind
6068
+ const onRangeClearRef = (0,external_react_.useRef)(onRangeClear); // latest click-outside clear; WaveSurfer click must not re-bind
5864
6069
  const onViewportChangeRef = (0,external_react_.useRef)(onViewportChange); // latest viewport callback; camera commits here
5865
6070
  const peaksRef = (0,external_react_.useRef)(peaks); // latest peaks; WaveSurfer create() + morph read this
5866
6071
 
@@ -5877,6 +6082,7 @@ function WaveformView({
5877
6082
  const pointerZoomClearTimerRef = (0,external_react_.useRef)(0); // clears pointerZoomRef after WAVEFORM_ZOOM_DISMISS_MS
5878
6083
  const pointerZoomRef = (0,external_react_.useRef)(false); // pinch/wheel in progress; skip play/pause and tape recenter
5879
6084
  const queuedTapeSeekTimeSecRef = (0,external_react_.useRef)(null); // seek time waiting for the next animation frame
6085
+ const rangeProgressRef = (0,external_react_.useRef)(null); // latest range progress; zoomed tile tint reads this
5880
6086
  const suppressNextTapPlayPauseRef = (0,external_react_.useRef)(false); // swallow play/pause after a swipe or pinch
5881
6087
  const suppressNextTapPlayPauseTimerRef = (0,external_react_.useRef)(0); // clears suppressNextTapPlayPauseRef
5882
6088
  const suppressViewportSyncRef = (0,external_react_.useRef)(false); // ignore WaveSurfer zoom/scroll while we setOptions
@@ -5892,6 +6098,7 @@ function WaveformView({
5892
6098
  mediaElRef.current = mediaEl;
5893
6099
  onPlayPauseRef.current = onPlayPause;
5894
6100
  onSeekRef.current = onSeek;
6101
+ onRangeClearRef.current = onRangeClear;
5895
6102
  onViewportChangeRef.current = onViewportChange;
5896
6103
  peaksRef.current = peaks;
5897
6104
  const [internalZoom, setInternalZoom] = (0,external_react_.useState)(constants/* WAVEFORM_ZOOM_MIN */.LW);
@@ -5900,14 +6107,20 @@ function WaveformView({
5900
6107
  const [overlayPortalHost, setOverlayPortalHost] = (0,external_react_.useState)(null);
5901
6108
  const [canvasWidthPx, setCanvasWidthPx] = (0,external_react_.useState)(0);
5902
6109
  const [scrollLeft, setScrollLeft] = (0,external_react_.useState)(0);
6110
+ const [previewRange, setPreviewRange] = (0,external_react_.useState)(null);
6111
+ const [isRangeDragging, setIsRangeDragging] = (0,external_react_.useState)(false);
6112
+ const isRangeDraggingRef = (0,external_react_.useRef)(false);
6113
+ const skipNextSeekRef = (0,external_react_.useRef)(false);
6114
+ const lastSetTimeSecRef = (0,external_react_.useRef)(null);
6115
+ const [isRangeHovered, setIsRangeHovered] = (0,external_react_.useState)(false);
5903
6116
  const isControlled = typeof zoomLevelProp === 'number';
5904
- const maxZoom = getWaveformZoomMax({
6117
+ const maxZoom = (0,waveform_viewport/* getWaveformZoomMax */.by)({
5905
6118
  durationSec,
5906
6119
  peakCount: peaks.length,
5907
6120
  viewWidthPx: canvasWidthPx
5908
6121
  });
5909
6122
  hasZoomHandlersRef.current = maxZoom > constants/* WAVEFORM_ZOOM_MIN */.LW && (typeof onZoomChange === 'function' || typeof zoomLevelProp !== 'number');
5910
- const zoomLevel = clampWaveformZoom(isControlled ? zoomLevelProp : internalZoom, maxZoom);
6123
+ const zoomLevel = (0,waveform_viewport/* clampWaveformZoom */.ii)(isControlled ? zoomLevelProp : internalZoom, maxZoom);
5911
6124
  const zoomRef = (0,external_react_.useRef)(zoomLevel); // latest zoom for WaveSurfer redraw/zoom handlers
5912
6125
  zoomRef.current = zoomLevel;
5913
6126
  const prevZoomRef = (0,external_react_.useRef)(null); // last applied zoom; tape recenters only when this changes
@@ -5916,10 +6129,26 @@ function WaveformView({
5916
6129
  const isScrollableWindow = isZoomed || isTape;
5917
6130
  const bufferProgress = getBufferedProgress(bufferedRange, durationSec);
5918
6131
  bufferProgressRef.current = bufferProgress;
5919
- hoverProgressRef.current = hoverProgress;
5920
- const viewport = (0,external_react_.useMemo)(() => createWaveformViewport({
6132
+ hoverProgressRef.current = isRangeDragging ? null : hoverProgress;
6133
+ const activeRange = isRangeDragging ? previewRange ?? range : range;
6134
+ const activeRangeRef = (0,external_react_.useRef)(activeRange);
6135
+ activeRangeRef.current = activeRange;
6136
+ const durationMs = (0,waveform_range/* durationMsFromSec */.uH)(durationSec);
6137
+ const rangeStartMs = activeRange?.startMs;
6138
+ const rangeEndMs = activeRange?.endMs;
6139
+ const activeRangeProgress = (0,external_react_.useMemo)(() => {
6140
+ if (rangeStartMs == null || !Number.isFinite(rangeStartMs)) {
6141
+ return null;
6142
+ }
6143
+ return (0,waveform_range/* rangeProgress */.D$)((0,waveform_range/* resolveRange */.Ss)({
6144
+ endMs: rangeEndMs ?? null,
6145
+ startMs: rangeStartMs
6146
+ }, durationMs), durationMs);
6147
+ }, [durationMs, rangeEndMs, rangeStartMs]);
6148
+ rangeProgressRef.current = activeRangeProgress;
6149
+ const viewport = (0,external_react_.useMemo)(() => (0,waveform_viewport/* createWaveformViewport */.eq)({
5921
6150
  durationSec,
5922
- gutterPx: isTape ? getTapeGutterPx(canvasWidthPx) : 0,
6151
+ gutterPx: isTape ? (0,waveform_viewport/* getTapeGutterPx */.iA)(canvasWidthPx) : 0,
5923
6152
  heightPx: height,
5924
6153
  maxZoom,
5925
6154
  scrollLeftPx: scrollLeft,
@@ -5928,6 +6157,7 @@ function WaveformView({
5928
6157
  }), [canvasWidthPx, durationSec, height, isTape, maxZoom, scrollLeft, zoomLevel]);
5929
6158
  const viewportRef = (0,external_react_.useRef)(viewport); // live scroll window; prefer this over render-state while the camera is moving
5930
6159
  const onViewportCommit = (0,external_react_.useCallback)((scrollLeftPx, nextViewport, commitReactState = true) => {
6160
+ rangeLayerRef.current?.applyViewport(nextViewport);
5931
6161
  onViewportChangeRef.current?.(nextViewport);
5932
6162
  if (commitReactState) {
5933
6163
  setScrollLeft(scrollLeftPx);
@@ -5976,7 +6206,7 @@ function WaveformView({
5976
6206
  }, [isUserPanning]);
5977
6207
  toggleTapePlaybackRef.current = toggleTapePlayback;
5978
6208
  (0,external_react_.useLayoutEffect)(() => {
5979
- viewportRef.current = createWaveformViewport({
6209
+ viewportRef.current = (0,waveform_viewport/* createWaveformViewport */.eq)({
5980
6210
  durationSec: viewport.durationSec,
5981
6211
  gutterPx: viewport.gutterPx,
5982
6212
  heightPx: viewport.heightPx,
@@ -5987,7 +6217,7 @@ function WaveformView({
5987
6217
  });
5988
6218
  }, [viewport]);
5989
6219
  const setZoomLevel = (0,external_react_.useCallback)(nextZoom => {
5990
- const zoom = clampWaveformZoom(nextZoom, maxZoom);
6220
+ const zoom = (0,waveform_viewport/* clampWaveformZoom */.ii)(nextZoom, maxZoom);
5991
6221
  if (!isControlled) {
5992
6222
  setInternalZoom(zoom);
5993
6223
  }
@@ -6042,7 +6272,8 @@ function WaveformView({
6042
6272
  return;
6043
6273
  }
6044
6274
  const scrollLeftPx = WaveformView_getScrollLeft(wavesurfer);
6045
- viewportRef.current = getViewportAtScroll(viewportRef.current, scrollLeftPx);
6275
+ viewportRef.current = (0,waveform_viewport/* getViewportAtScroll */.NI)(viewportRef.current, scrollLeftPx);
6276
+ rangeLayerRef.current?.applyViewport(viewportRef.current);
6046
6277
  onViewportChangeRef.current?.(viewportRef.current);
6047
6278
  setScrollLeft(scrollLeftPx);
6048
6279
  }, []);
@@ -6052,12 +6283,14 @@ function WaveformView({
6052
6283
  return;
6053
6284
  }
6054
6285
  if (isTape) {
6055
- playhead.style.left = getTapePinnedPlayheadLeft();
6286
+ playhead.style.left = (0,waveform_viewport/* getTapePinnedPlayheadLeft */.yW)();
6056
6287
  } else if (!isFollowPinned()) {
6057
- playhead.style.left = timeLeftPercent(timeSec, durationSecRef.current, viewportRef.current);
6288
+ playhead.style.left = (0,waveform_viewport/* timeLeftPercent */.Mu)(timeSec, durationSecRef.current, viewportRef.current);
6058
6289
  }
6290
+ rangeLayerRef.current?.applyViewport(viewportRef.current);
6059
6291
  const wavesurfer = wavesurferRef.current;
6060
- if (wavesurfer && wavesurfer.setTime) {
6292
+ if (wavesurfer && wavesurfer.setTime && lastSetTimeSecRef.current !== timeSec) {
6293
+ lastSetTimeSecRef.current = timeSec;
6061
6294
  wavesurfer.setTime(timeSec);
6062
6295
  }
6063
6296
  }, [isFollowPinned, isTape]);
@@ -6070,14 +6303,14 @@ function WaveformView({
6070
6303
  return;
6071
6304
  }
6072
6305
  const viewWidthPx = wavesurfer.getWidth ? wavesurfer.getWidth() : container.clientWidth;
6073
- const minPxPerSec = getZoomedPixelsPerSecond({
6306
+ const minPxPerSec = (0,waveform_viewport/* getZoomedPixelsPerSecond */.J$)({
6074
6307
  durationSec,
6075
6308
  isTape,
6076
6309
  maxZoom,
6077
6310
  viewWidthPx,
6078
6311
  zoomLevel
6079
6312
  });
6080
- const gutterPx = isTape ? getTapeGutterPx(viewWidthPx) : 0;
6313
+ const gutterPx = isTape ? (0,waveform_viewport/* getTapeGutterPx */.iA)(viewWidthPx) : 0;
6081
6314
  const origin = isTape ? null : zoomOriginRef.current;
6082
6315
  zoomOriginRef.current = null;
6083
6316
  const didZoomChange = prevZoomRef.current !== zoomLevel;
@@ -6086,7 +6319,7 @@ function WaveformView({
6086
6319
  }
6087
6320
  suppressViewportSyncRef.current = true;
6088
6321
  try {
6089
- wavesurfer.setOptions(WaveformView_objectSpread({
6322
+ wavesurfer.setOptions(_objectSpread({
6090
6323
  autoScroll: false,
6091
6324
  fillParent: !isTape,
6092
6325
  minPxPerSec
@@ -6096,7 +6329,7 @@ function WaveformView({
6096
6329
  } : {}));
6097
6330
  applyWaveformGutters(wavesurfer, gutterPx);
6098
6331
  if (minPxPerSec > 0) {
6099
- const windowViewport = createWaveformViewport({
6332
+ const windowViewport = (0,waveform_viewport/* createWaveformViewport */.eq)({
6100
6333
  durationSec,
6101
6334
  gutterPx,
6102
6335
  heightPx: liveHeightRef.current || height,
@@ -6108,9 +6341,10 @@ function WaveformView({
6108
6341
  if (origin) {
6109
6342
  applyScrollLeft(origin.timeSec * minPxPerSec + gutterPx - origin.pointerX, true);
6110
6343
  } else if (didZoomChange && (isTape || !pointerZoomRef.current)) {
6111
- applyScrollLeft(getCenteredScrollLeft(currentTimeRef.current, windowViewport), true);
6344
+ applyScrollLeft((0,waveform_viewport/* getCenteredScrollLeft */.b6)(currentTimeRef.current, windowViewport), true);
6112
6345
  }
6113
6346
  }
6347
+ lastSetTimeSecRef.current = currentTimeRef.current;
6114
6348
  wavesurfer.setTime(currentTimeRef.current);
6115
6349
  } finally {
6116
6350
  suppressViewportSyncRef.current = false;
@@ -6149,14 +6383,30 @@ function WaveformView({
6149
6383
  waveColor: WAVEFORM_COLOR_UNPLAYED
6150
6384
  });
6151
6385
  const unsubscribeClick = wavesurfer.on('click', relativeX => {
6152
- if (!interactiveRef.current) {
6386
+ if (skipNextSeekRef.current) {
6387
+ skipNextSeekRef.current = false;
6153
6388
  return;
6154
6389
  }
6390
+ if (!interactiveRef.current || isRangeDraggingRef.current) {
6391
+ return;
6392
+ }
6393
+ const rangeDraft = activeRangeRef.current;
6394
+ const timeSec = relativeX * durationSecRef.current;
6395
+ if (rangeDraft && !(0,waveform_range/* isRangeCollapsed */.BT)(rangeDraft)) {
6396
+ const pointerX = (0,waveform_viewport/* positionPxFromTime */.TO)(timeSec, viewportRef.current);
6397
+ if (!(0,waveform_range/* isPointerOverRange */.H5)({
6398
+ pointerX,
6399
+ range: rangeDraft,
6400
+ viewport: viewportRef.current
6401
+ })) {
6402
+ onRangeClearRef.current?.();
6403
+ }
6404
+ }
6155
6405
  if (cameraModeRef.current === 'tape') {
6156
6406
  toggleTapePlaybackRef.current?.();
6157
6407
  return;
6158
6408
  }
6159
- onSeekRef.current?.(relativeX * durationSecRef.current);
6409
+ onSeekRef.current?.(timeSec);
6160
6410
  });
6161
6411
  const unsubscribeScroll = wavesurfer.on('scroll', () => {
6162
6412
  if (suppressViewportSyncRef.current) {
@@ -6169,6 +6419,7 @@ function WaveformView({
6169
6419
  }
6170
6420
  syncViewport();
6171
6421
  });
6422
+ rangeLayerRef.current?.applyViewport(viewportRef.current);
6172
6423
  });
6173
6424
  const unsubscribeZoom = wavesurfer.on('zoom', () => {
6174
6425
  if (suppressViewportSyncRef.current) {
@@ -6183,11 +6434,13 @@ function WaveformView({
6183
6434
  tintZoomedWaveform(wavesurfer, getWaveformFills({
6184
6435
  bufferProgress: bufferProgressRef.current,
6185
6436
  hoverProgress: cameraModeRef.current === 'tape' ? null : hoverProgressRef.current,
6186
- isTape: cameraModeRef.current === 'tape'
6437
+ isTape: cameraModeRef.current === 'tape',
6438
+ rangeProgress: rangeProgressRef.current
6187
6439
  }), true);
6188
6440
  });
6189
6441
  wavesurferRef.current = wavesurfer;
6190
6442
  displayedPeaksRef.current = peaksRef.current;
6443
+ lastSetTimeSecRef.current = null;
6191
6444
  disableScrollOverscroll(container);
6192
6445
  syncViewport();
6193
6446
  applyZoomWindowRef.current?.();
@@ -6205,6 +6458,7 @@ function WaveformView({
6205
6458
  wavesurfer.destroy();
6206
6459
  wavesurferRef.current = null;
6207
6460
  displayedPeaksRef.current = null;
6461
+ lastSetTimeSecRef.current = null;
6208
6462
  };
6209
6463
  }, [cancelJump, handleCameraScroll, handleTapeSwipe, height, isUserPanning, releaseUserPanHold, syncViewport]);
6210
6464
  (0,external_react_.useLayoutEffect)(() => {
@@ -6245,7 +6499,7 @@ function WaveformView({
6245
6499
  }, [syncViewport]);
6246
6500
  (0,external_react_.useEffect)(() => {
6247
6501
  const rawZoom = isControlled ? zoomLevelProp : internalZoom;
6248
- const clamped = clampWaveformZoom(typeof rawZoom === 'number' ? rawZoom : constants/* WAVEFORM_ZOOM_MIN */.LW, maxZoom);
6502
+ const clamped = (0,waveform_viewport/* clampWaveformZoom */.ii)(typeof rawZoom === 'number' ? rawZoom : constants/* WAVEFORM_ZOOM_MIN */.LW, maxZoom);
6249
6503
  if (clamped === rawZoom) {
6250
6504
  return;
6251
6505
  }
@@ -6359,7 +6613,8 @@ function WaveformView({
6359
6613
  const fills = getWaveformFills({
6360
6614
  bufferProgress,
6361
6615
  hoverProgress: isTape ? null : hoverProgress,
6362
- isTape
6616
+ isTape,
6617
+ rangeProgress: activeRangeProgress
6363
6618
  });
6364
6619
  if (isScrollableWindow) {
6365
6620
  tintZoomedWaveform(wavesurfer, fills);
@@ -6369,8 +6624,9 @@ function WaveformView({
6369
6624
  progressColor: toCanvasFill(fills.progressColor, devicePixelWidth(canvasWidthPx)),
6370
6625
  waveColor: toCanvasFill(fills.waveColor, devicePixelWidth(canvasWidthPx))
6371
6626
  });
6627
+ lastSetTimeSecRef.current = currentTimeRef.current;
6372
6628
  wavesurfer.setTime(currentTimeRef.current);
6373
- }, [bufferProgress, canvasWidthPx, hoverProgress, isScrollableWindow, isTape]);
6629
+ }, [activeRangeProgress, bufferProgress, canvasWidthPx, hoverProgress, isRangeDragging, isScrollableWindow, isTape, isZoomed]);
6374
6630
  (0,external_react_.useEffect)(() => {
6375
6631
  const track = trackRef.current;
6376
6632
  if (!track) {
@@ -6451,11 +6707,11 @@ function WaveformView({
6451
6707
  };
6452
6708
  }, [durationSec, markPointerZoom, setZoomLevel]);
6453
6709
  const onHoverMove = (0,external_react_.useCallback)(event => {
6454
- if (!interactive || isTape || isTouchHoverInput(event)) {
6710
+ if (isTape || isTouchHoverInput(event)) {
6455
6711
  return;
6456
6712
  }
6457
6713
  const rect = event.currentTarget.getBoundingClientRect();
6458
- if (!(rect.width > 0) || !(durationSec > 0)) {
6714
+ if (!(rect.width > 0)) {
6459
6715
  return;
6460
6716
  }
6461
6717
  const pointerX = event.clientX - rect.left;
@@ -6463,20 +6719,48 @@ function WaveformView({
6463
6719
  return;
6464
6720
  }
6465
6721
  const vp = viewportRef.current;
6722
+ const rangeDraft = activeRangeRef.current;
6723
+ setIsRangeHovered(!!rangeDraft && (0,waveform_range/* isPointerOverRange */.H5)({
6724
+ pointerX,
6725
+ range: rangeDraft,
6726
+ viewport: vp
6727
+ }));
6728
+ if (!interactive || isRangeDraggingRef.current || !(durationSec > 0)) {
6729
+ return;
6730
+ }
6466
6731
  if (vp.pixelsPerSecond > 0) {
6467
- setHoverProgress(Math.min(1, Math.max(0, timeFromPositionPx(pointerX, vp) / durationSec)));
6732
+ setHoverProgress(Math.min(1, Math.max(0, (0,waveform_viewport/* timeFromPositionPx */.lZ)(pointerX, vp) / durationSec)));
6468
6733
  return;
6469
6734
  }
6470
6735
  setHoverProgress(Math.min(1, Math.max(0, pointerX / rect.width)));
6471
6736
  }, [durationSec, interactive, isTape]);
6472
6737
  const onHoverLeave = (0,external_react_.useCallback)(() => {
6473
6738
  setHoverProgress(null);
6739
+ setIsRangeHovered(false);
6740
+ }, []);
6741
+ const handleRangeDragChange = (0,external_react_.useCallback)(isDragging => {
6742
+ isRangeDraggingRef.current = isDragging;
6743
+ setIsRangeDragging(isDragging);
6744
+ if (isDragging) {
6745
+ setHoverProgress(null);
6746
+ } else {
6747
+ skipNextSeekRef.current = true;
6748
+ window.setTimeout(() => {
6749
+ skipNextSeekRef.current = false;
6750
+ }, 0);
6751
+ setPreviewRange(null);
6752
+ }
6753
+ onRangeDragChange?.(isDragging);
6754
+ }, [onRangeDragChange]);
6755
+ const getPlayheadSec = (0,external_react_.useCallback)(() => {
6756
+ return mediaElRef.current ? mediaElRef.current.currentTime : currentTimeRef.current;
6474
6757
  }, []);
6475
6758
  const bindOverlayPortalHost = (0,external_react_.useCallback)(node => {
6476
6759
  const host = node?.parentElement ?? null;
6477
6760
  setOverlayPortalHost(prev => prev === host ? prev : host);
6478
6761
  }, []);
6479
- const hoverLeft = hoverProgress == null ? null : timeLeftPercent(hoverProgress * durationSec, durationSec, viewportRef.current);
6762
+ const hoverLeft = isRangeDragging || hoverProgress == null ? null : (0,waveform_viewport/* timeLeftPercent */.Mu)(hoverProgress * durationSec, durationSec, viewportRef.current);
6763
+ const showRangeOverlay = Boolean(range) && (isRangeDragging || !isPlaying || !(0,waveform_range/* isRangeCollapsed */.BT)(range));
6480
6764
  const scrubTimeChip = isTape && scrubPreviewTimeSec != null ? /*#__PURE__*/external_react_["default"].createElement("div", {
6481
6765
  className: "bp-WaveformView-hover bp-WaveformView-hover--tape",
6482
6766
  "data-testid": "bp-waveform-hover"
@@ -6518,7 +6802,20 @@ function WaveformView({
6518
6802
  "aria-hidden": "true",
6519
6803
  className: "bp-WaveformView-playhead",
6520
6804
  "data-testid": "bp-waveform-playhead"
6521
- }), hoverLeft != null && hoverProgress != null && /*#__PURE__*/external_react_["default"].createElement("div", {
6805
+ }), showRangeOverlay && range && /*#__PURE__*/external_react_["default"].createElement(waveform_WaveformRangeSelection, {
6806
+ ref: rangeLayerRef,
6807
+ currentTimeSec: currentTime,
6808
+ durationSec: durationSec,
6809
+ getPlayheadSec: getPlayheadSec,
6810
+ interactive: interactive,
6811
+ isHighlighted: isRangeDragging || isRangeHovered,
6812
+ keepHighlight: true,
6813
+ onDragChange: handleRangeDragChange,
6814
+ onPreviewChange: setPreviewRange,
6815
+ onRangeChange: interactive ? onRangeChange : undefined,
6816
+ range: range,
6817
+ viewport: viewport
6818
+ }), hoverLeft != null && hoverProgress != null && !isRangeDragging && /*#__PURE__*/external_react_["default"].createElement("div", {
6522
6819
  className: "bp-WaveformView-hover",
6523
6820
  "data-testid": "bp-waveform-hover",
6524
6821
  style: {
@@ -6620,8 +6917,9 @@ function WaveformZoomControl({
6620
6917
  const zoomControlElRef = (0,external_react_.useRef)(null); // root; outside pointerdown unpins the flyout
6621
6918
  const dismissTimerRef = (0,external_react_.useRef)(0); // delay before hover-close of the flyout
6622
6919
  const sliderId = `bp-waveform-zoom-slider${(0,external_react_.useId)()}`;
6623
- const zoom = clampWaveformZoom(zoomLevel, maxZoom);
6624
- const zoomValue = Math.round(sliderValueFromZoom(zoom, maxZoom));
6920
+ const zoom = (0,waveform_viewport/* clampWaveformZoom */.ii)(zoomLevel, maxZoom);
6921
+ const zoomValue = Math.round((0,waveform_viewport/* sliderValueFromZoom */.Iw)(zoom, maxZoom));
6922
+ const zoomMultiplier = (0,waveform_viewport/* getWaveformZoomMultiplier */.gd)(zoom);
6625
6923
  const isOpen = isHovered || isFocused || isRevealed || isPinned;
6626
6924
  const isAtMinZoom = zoomValue <= 0;
6627
6925
  const isAtMaxZoom = zoomValue >= constants/* WAVEFORM_ZOOM_SLIDER_MAX */.nh;
@@ -6647,11 +6945,11 @@ function WaveformZoomControl({
6647
6945
  return () => document.removeEventListener('pointerdown', closeIfOutside);
6648
6946
  }, [isPinned]);
6649
6947
  const handleSlider = (0,external_react_.useCallback)(newValue => {
6650
- onZoomChange(zoomFromSliderValue(newValue, maxZoom));
6948
+ onZoomChange((0,waveform_viewport/* zoomFromSliderValue */.eU)(newValue, maxZoom));
6651
6949
  }, [maxZoom, onZoomChange]);
6652
6950
  const handleStep = (0,external_react_.useCallback)(delta => {
6653
- onZoomChange(zoomFromSliderValue(zoomValue + delta, maxZoom));
6654
- }, [maxZoom, onZoomChange, zoomValue]);
6951
+ onZoomChange((0,waveform_viewport/* stepWaveformZoom */.xV)(zoom, maxZoom, delta));
6952
+ }, [maxZoom, onZoomChange, zoom]);
6655
6953
  const handleZoomInPointerDown = (0,external_react_.useCallback)(() => {
6656
6954
  wasFlyoutOpenOnPointerDownRef.current = isHovered || isFocused || isRevealed || isPinned;
6657
6955
  }, [isFocused, isHovered, isPinned, isRevealed]);
@@ -6668,6 +6966,7 @@ function WaveformZoomControl({
6668
6966
  ref: zoomControlElRef,
6669
6967
  "aria-label": "Zoom",
6670
6968
  className: classnames_default()('bp-WaveformZoomControl', {
6969
+ 'bp-has-multiplier': zoomMultiplier,
6671
6970
  'bp-is-open': isOpen
6672
6971
  }),
6673
6972
  "data-testid": "bp-waveform-zoom",
@@ -6693,7 +6992,15 @@ function WaveformZoomControl({
6693
6992
  }, constants/* WAVEFORM_ZOOM_DISMISS_MS */.m6);
6694
6993
  },
6695
6994
  role: "group"
6696
- }, /*#__PURE__*/external_react_["default"].createElement(MediaToggle/* default */.A, {
6995
+ }, zoomMultiplier && /*#__PURE__*/external_react_["default"].createElement(external_react_["default"].Fragment, null, /*#__PURE__*/external_react_["default"].createElement("span", {
6996
+ className: "bp-WaveformZoomControl-multiplier",
6997
+ "data-testid": "bp-waveform-zoom-multiplier",
6998
+ title: "Current zoom level"
6999
+ }, zoomMultiplier), /*#__PURE__*/external_react_["default"].createElement("span", {
7000
+ "aria-hidden": "true",
7001
+ className: "bp-WaveformZoomControl-divider",
7002
+ "data-testid": "bp-waveform-zoom-divider"
7003
+ })), /*#__PURE__*/external_react_["default"].createElement(MediaToggle/* default */.A, {
6697
7004
  "aria-disabled": isOpen && isAtMaxZoom,
6698
7005
  className: "bp-WaveformZoomControl-button",
6699
7006
  "data-resin-target": "waveformZoomIn",
@@ -6754,23 +7061,34 @@ function WaveformZoomControl({
6754
7061
 
6755
7062
 
6756
7063
 
7064
+
6757
7065
  const PLACEHOLDER_PEAKS = placeholderPeaks();
6758
7066
  function MP3ControlsV2({
6759
7067
  autoplay,
6760
7068
  bufferedRange,
6761
7069
  commentMarkers,
7070
+ commentRangeDraft = null,
6762
7071
  currentTime,
6763
7072
  durationTime,
7073
+ hasStartedPlayback = false,
7074
+ isGeneratingWaveform = false,
6764
7075
  isPlaying,
7076
+ keyboardVolumeStep = 0,
7077
+ keyboardZoomStep = 0,
6765
7078
  mediaEl,
6766
7079
  onAutoplayChange,
6767
7080
  onCommentMarkerClick,
7081
+ onCommentRangeChange,
7082
+ onCommentRangeClear,
7083
+ onCommentRangeDragChange,
6768
7084
  onMuteChange,
6769
7085
  onPlayPause,
7086
+ onPlayNextChange,
6770
7087
  onRateChange,
6771
7088
  onTimeChange,
6772
7089
  onVolumeChange,
6773
7090
  peaks,
7091
+ playNext,
6774
7092
  rate,
6775
7093
  volume
6776
7094
  }) {
@@ -6785,14 +7103,15 @@ function MP3ControlsV2({
6785
7103
  const hasRealPeaks = !!(peaks && peaks.length);
6786
7104
  const waveformPeaks = hasRealPeaks ? peaks : PLACEHOLDER_PEAKS;
6787
7105
  const waveformDurationSec = hasWaveformDuration ? durationValue : PLACEHOLDER_DURATION_SEC;
6788
- const [playRequested, setPlayRequested] = (0,external_react_.useState)(false);
7106
+ const [playRequested, setPlayRequested] = (0,external_react_.useState)(hasStartedPlayback);
6789
7107
  const [viewport, setViewport] = (0,external_react_.useState)(null);
6790
7108
  const isTape = useTapeWaveform();
6791
7109
  const hasAppliedTapeDefaultZoomRef = (0,external_react_.useRef)(false); // ~10s window applied; reset to 1× when leaving tape
7110
+ const lastKeyboardZoomStepRef = (0,external_react_.useRef)(0); // last +/− step applied; skip re-step when maxZoom retriggers the effect
6792
7111
  const userChangedTapeZoomRef = (0,external_react_.useRef)(false); // pinch/wheel zoom; skip re-applying the 10s default
6793
7112
  const handleViewportChange = (0,external_react_.useCallback)(next => {
6794
7113
  setMaxZoom(prev => prev === next.maxZoom ? prev : next.maxZoom);
6795
- setViewport(prev => viewportEquals(prev, next) ? prev : next);
7114
+ setViewport(prev => (0,waveform_viewport/* viewportEquals */.KH)(prev, next) ? prev : next);
6796
7115
  }, []);
6797
7116
  const revealZoomControl = (0,external_react_.useCallback)(() => {
6798
7117
  setIsZoomRevealed(true);
@@ -6808,7 +7127,20 @@ function MP3ControlsV2({
6808
7127
  revealZoomControl();
6809
7128
  }, [revealZoomControl]);
6810
7129
  (0,external_react_.useEffect)(() => {
6811
- setZoomLevel(prev => clampWaveformZoom(prev, maxZoom));
7130
+ const steps = keyboardZoomStep - lastKeyboardZoomStepRef.current;
7131
+ if (!steps) {
7132
+ return;
7133
+ }
7134
+ lastKeyboardZoomStepRef.current = keyboardZoomStep;
7135
+ if (!(maxZoom > constants/* WAVEFORM_ZOOM_MIN */.LW)) {
7136
+ return;
7137
+ }
7138
+ userChangedTapeZoomRef.current = true;
7139
+ setZoomLevel(prev => (0,waveform_viewport/* stepWaveformZoom */.xV)(prev, maxZoom, steps * constants/* WAVEFORM_ZOOM_BUTTON_STEP */.QD));
7140
+ revealZoomControl();
7141
+ }, [keyboardZoomStep, maxZoom, revealZoomControl]);
7142
+ (0,external_react_.useEffect)(() => {
7143
+ setZoomLevel(prev => (0,waveform_viewport/* clampWaveformZoom */.ii)(prev, maxZoom));
6812
7144
  }, [maxZoom]);
6813
7145
  (0,external_react_.useEffect)(() => {
6814
7146
  if (!isTape) {
@@ -6826,16 +7158,16 @@ function MP3ControlsV2({
6826
7158
  hasAppliedTapeDefaultZoomRef.current = true;
6827
7159
  return;
6828
7160
  }
6829
- const nextZoom = getTapeDefaultZoom(durationValue, Math.max(maxZoom, viewport.maxZoom));
7161
+ const nextZoom = (0,waveform_viewport/* getTapeDefaultZoom */.lz)(durationValue, Math.max(maxZoom, viewport.maxZoom));
6830
7162
  hasAppliedTapeDefaultZoomRef.current = true;
6831
7163
  setZoomLevel(prev => prev === nextZoom ? prev : nextZoom);
6832
7164
  }, [durationValue, hasRealPeaks, hasWaveformDuration, isTape, maxZoom, viewport]);
6833
7165
  (0,external_react_.useEffect)(() => () => window.clearTimeout(zoomRevealTimerRef.current), []);
6834
7166
  (0,external_react_.useEffect)(() => {
6835
- if (isPlaying) {
7167
+ if (isPlaying || hasStartedPlayback) {
6836
7168
  setPlayRequested(true);
6837
7169
  }
6838
- }, [isPlaying]);
7170
+ }, [hasStartedPlayback, isPlaying]);
6839
7171
  const handlePlayOverlayClick = (0,external_react_.useCallback)(() => {
6840
7172
  setPlayRequested(true);
6841
7173
  onPlayPause(true);
@@ -6853,11 +7185,13 @@ function MP3ControlsV2({
6853
7185
  }
6854
7186
  onTimeChange(marker.time);
6855
7187
  }, [onCommentMarkerClick, onPlayPause, onTimeChange]);
6856
- const isWaveformInteractive = playRequested && hasMediaMetadata;
6857
- const isWaitingToPlay = playRequested && !hasMediaMetadata;
6858
- const showPlayOverlay = !playRequested && !isPlaying;
7188
+ const hasStarted = playRequested || hasStartedPlayback;
7189
+ const isWaveformInteractive = hasStarted && hasMediaMetadata;
7190
+ const isWaitingToPlay = hasStarted && !hasMediaMetadata;
7191
+ const showPlayOverlay = !hasStarted && !isPlaying;
6859
7192
  const hasZoomHandlers = hasRealPeaks && !showPlayOverlay;
6860
7193
  const hasZoomControl = hasZoomHandlers && hasMediaMetadata && maxZoom > constants/* WAVEFORM_ZOOM_MIN */.LW;
7194
+ const showGeneratingWaveformIndicator = isGeneratingWaveform && !hasRealPeaks;
6861
7195
  const waveformZoomLevel = isTape || hasZoomHandlers ? zoomLevel : constants/* WAVEFORM_ZOOM_MIN */.LW;
6862
7196
  return /*#__PURE__*/external_react_["default"].createElement("div", {
6863
7197
  className: "bp-MP3ControlsV2",
@@ -6875,10 +7209,14 @@ function MP3ControlsV2({
6875
7209
  isPlaying: isPlaying,
6876
7210
  mediaEl: mediaEl,
6877
7211
  onPlayPause: isWaveformInteractive ? onPlayPause : undefined,
7212
+ onRangeChange: isWaveformInteractive ? onCommentRangeChange : undefined,
7213
+ onRangeClear: isWaveformInteractive ? onCommentRangeClear : undefined,
7214
+ onRangeDragChange: onCommentRangeDragChange,
6878
7215
  onSeek: isWaveformInteractive ? onTimeChange : undefined,
6879
7216
  onViewportChange: hasRealPeaks ? handleViewportChange : undefined,
6880
7217
  onZoomChange: hasZoomHandlers ? handleWaveformZoom : undefined,
6881
7218
  peaks: waveformPeaks,
7219
+ range: commentRangeDraft,
6882
7220
  zoomLevel: waveformZoomLevel
6883
7221
  }), /*#__PURE__*/external_react_["default"].createElement(WaveformCommentMarkers, {
6884
7222
  commentMarkers: waveformMarkers,
@@ -6887,9 +7225,9 @@ function MP3ControlsV2({
6887
7225
  onCommentMarkerClick: handleCommentMarkerClick,
6888
7226
  selectedId: selectedMarkerId,
6889
7227
  viewport: viewport
6890
- })), hasZoomControl && /*#__PURE__*/external_react_["default"].createElement("div", {
7228
+ })), (showGeneratingWaveformIndicator || hasZoomControl) && /*#__PURE__*/external_react_["default"].createElement("div", {
6891
7229
  className: "bp-MP3ControlsV2-waveformZoom"
6892
- }, /*#__PURE__*/external_react_["default"].createElement(WaveformZoomControl, {
7230
+ }, showGeneratingWaveformIndicator ? /*#__PURE__*/external_react_["default"].createElement(WaveformGeneratingIndicator, null) : /*#__PURE__*/external_react_["default"].createElement(WaveformZoomControl, {
6893
7231
  isRevealed: isZoomRevealed,
6894
7232
  maxZoom: maxZoom,
6895
7233
  onZoomChange: handleWaveformZoom,
@@ -6926,7 +7264,8 @@ function MP3ControlsV2({
6926
7264
  durationTime: durationValue
6927
7265
  })), /*#__PURE__*/external_react_["default"].createElement("div", {
6928
7266
  className: "bp-MP3ControlsV2-group"
6929
- }, /*#__PURE__*/external_react_["default"].createElement(VolumeControls/* default */.A, {
7267
+ }, /*#__PURE__*/external_react_["default"].createElement(VolumeControls/* default */.Ay, {
7268
+ keyboardVolumeStep: keyboardVolumeStep,
6930
7269
  onMuteChange: onMuteChange,
6931
7270
  onVolumeChange: onVolumeChange,
6932
7271
  volume: volume
@@ -6934,7 +7273,9 @@ function MP3ControlsV2({
6934
7273
  autoplay: autoplay,
6935
7274
  className: "bp-MP3Controls-settings",
6936
7275
  onAutoplayChange: onAutoplayChange,
7276
+ onPlayNextChange: onPlayNextChange,
6937
7277
  onRateChange: onRateChange,
7278
+ playNext: playNext,
6938
7279
  rate: rate
6939
7280
  }))));
6940
7281
  }
@@ -7013,11 +7354,13 @@ function isFpsAvailable(player) {
7013
7354
 
7014
7355
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
7015
7356
  /* harmony export */ Av: () => (/* binding */ WAVEFORM_TAPE_DEFAULT_WINDOW_SEC),
7357
+ /* harmony export */ BG: () => (/* binding */ WAVEFORM_RANGE_COLLAPSED_OFFSET_PX),
7016
7358
  /* harmony export */ Bq: () => (/* binding */ WAVEFORM_PLAYHEAD_JUMP_MS),
7017
7359
  /* harmony export */ DY: () => (/* binding */ WAVEFORM_BAR_RADIUS),
7018
7360
  /* harmony export */ EB: () => (/* binding */ CLIENT_DECODE_MAX_COMPRESSED_BYTES),
7019
7361
  /* harmony export */ FY: () => (/* binding */ MAX_PEAK_COUNT),
7020
7362
  /* harmony export */ GQ: () => (/* binding */ DURATION_MISMATCH_TOLERANCE_SEC),
7363
+ /* harmony export */ IQ: () => (/* binding */ WAVEFORM_RANGE_SNAP_PX),
7021
7364
  /* harmony export */ Kl: () => (/* binding */ WAVEFORM_MIN_VIEW_WINDOW_SEC),
7022
7365
  /* harmony export */ LW: () => (/* binding */ WAVEFORM_ZOOM_MIN),
7023
7366
  /* harmony export */ Le: () => (/* binding */ TAPE_POINTER_MEDIA_QUERY),
@@ -7026,8 +7369,10 @@ function isFpsAvailable(player) {
7026
7369
  /* harmony export */ QD: () => (/* binding */ WAVEFORM_ZOOM_BUTTON_STEP),
7027
7370
  /* harmony export */ XS: () => (/* binding */ WAVEFORM_BAR_MIN_HEIGHT),
7028
7371
  /* harmony export */ Zs: () => (/* binding */ WAVEFORM_FOLLOW_SCROLL_SETTLE_MS),
7372
+ /* harmony export */ bX: () => (/* binding */ WAVEFORM_RANGE_MIN_DURATION_MS),
7029
7373
  /* harmony export */ f3: () => (/* binding */ CLIENT_DECODE_PEAK_COUNT),
7030
7374
  /* harmony export */ fI: () => (/* binding */ WAVEFORM_TAPE_CLICK_SUPPRESS_MS),
7375
+ /* harmony export */ h1: () => (/* binding */ WAVEFORM_RANGE_HANDLE_LINE_PX),
7031
7376
  /* harmony export */ i8: () => (/* binding */ PEAK_UNIT_MAX),
7032
7377
  /* harmony export */ m6: () => (/* binding */ WAVEFORM_ZOOM_DISMISS_MS),
7033
7378
  /* harmony export */ mJ: () => (/* binding */ PEAK_UNIT_MIN),
@@ -7035,10 +7380,12 @@ function isFpsAvailable(player) {
7035
7380
  /* harmony export */ nh: () => (/* binding */ WAVEFORM_ZOOM_SLIDER_MAX),
7036
7381
  /* harmony export */ oN: () => (/* binding */ WAVEFORM_HEIGHT),
7037
7382
  /* harmony export */ qY: () => (/* binding */ MAX_PAYLOAD_BYTES),
7383
+ /* harmony export */ r0: () => (/* binding */ WAVEFORM_RANGE_HIT_AREA_PX),
7038
7384
  /* harmony export */ sh: () => (/* binding */ WAVEFORM_PAYLOAD_VERSION),
7039
7385
  /* harmony export */ tK: () => (/* binding */ WAVEFORM_ZOOM_MAX),
7040
7386
  /* harmony export */ zo: () => (/* binding */ WAVEFORM_BAR_WIDTH)
7041
7387
  /* harmony export */ });
7388
+ /* unused harmony exports WAVEFORM_RANGE_PLAYHEAD_GAP_PX, WAVEFORM_RANGE_GRIP_IN_MS, WAVEFORM_RANGE_GRIP_OUT_MS */
7042
7389
  /** Current waveform payload schema version. Bump only with a migration path. */
7043
7390
  const WAVEFORM_PAYLOAD_VERSION = 1;
7044
7391
 
@@ -7088,6 +7435,24 @@ const WAVEFORM_BAR_RADIUS = WAVEFORM_BAR_WIDTH / 2;
7088
7435
  const WAVEFORM_BAR_MIN_HEIGHT = WAVEFORM_BAR_WIDTH;
7089
7436
  const WAVEFORM_HEIGHT = 140;
7090
7437
 
7438
+ /** Hit target centered on each range handle line. */
7439
+ const WAVEFORM_RANGE_HIT_AREA_PX = 24;
7440
+ /** Visible handle line and playhead width. */
7441
+ const WAVEFORM_RANGE_HANDLE_LINE_PX = 2;
7442
+ /** Screen gap between the playhead and each collapsed handle line. */
7443
+ const WAVEFORM_RANGE_PLAYHEAD_GAP_PX = 1;
7444
+ /**
7445
+ * Offset of each collapsed handle from the timestamp/playhead center so the
7446
+ * 2px lines sit 1px left and right of the 2px playhead.
7447
+ */
7448
+ const WAVEFORM_RANGE_COLLAPSED_OFFSET_PX = WAVEFORM_RANGE_HANDLE_LINE_PX / 2 + WAVEFORM_RANGE_PLAYHEAD_GAP_PX + WAVEFORM_RANGE_HANDLE_LINE_PX / 2;
7449
+ /** Snap a dragging handle to the playhead when this close. */
7450
+ const WAVEFORM_RANGE_SNAP_PX = 8;
7451
+ /** Shortest committed range. Collapsed drafts (`endMs: null`) are exempt. */
7452
+ const WAVEFORM_RANGE_MIN_DURATION_MS = 250;
7453
+ const WAVEFORM_RANGE_GRIP_IN_MS = 150;
7454
+ const WAVEFORM_RANGE_GRIP_OUT_MS = 100;
7455
+
7091
7456
  /***/ },
7092
7457
 
7093
7458
  /***/ 4971
@@ -7642,6 +8007,221 @@ function loadPeaks(request) {
7642
8007
 
7643
8008
  /***/ },
7644
8009
 
8010
+ /***/ 4253
8011
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
8012
+
8013
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
8014
+ /* harmony export */ BT: () => (/* binding */ isRangeCollapsed),
8015
+ /* harmony export */ D$: () => (/* binding */ rangeProgress),
8016
+ /* harmony export */ Eh: () => (/* binding */ visualRangeHandlePx),
8017
+ /* harmony export */ H5: () => (/* binding */ isPointerOverRange),
8018
+ /* harmony export */ HG: () => (/* binding */ pickRangeHandle),
8019
+ /* harmony export */ Mh: () => (/* binding */ snapTimeMs),
8020
+ /* harmony export */ Mo: () => (/* binding */ dragRangeHandle),
8021
+ /* harmony export */ Ss: () => (/* binding */ resolveRange),
8022
+ /* harmony export */ dG: () => (/* binding */ commitRangeChange),
8023
+ /* harmony export */ uH: () => (/* binding */ durationMsFromSec),
8024
+ /* harmony export */ v2: () => (/* binding */ pointerTimeMs)
8025
+ /* harmony export */ });
8026
+ /* unused harmony export clampTimeMs */
8027
+ /* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4929);
8028
+ /* harmony import */ var _viewport__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(772);
8029
+
8030
+
8031
+ function clampToRange(value, min, max) {
8032
+ if (!Number.isFinite(value)) {
8033
+ return min;
8034
+ }
8035
+ return Math.min(max, Math.max(min, value));
8036
+ }
8037
+
8038
+ /** Integer ms in `[0, durationMs]`. */
8039
+ function clampTimeMs(timeMs, durationMs) {
8040
+ const duration = Math.max(0, durationMs);
8041
+ return Math.round(clampToRange(timeMs, 0, duration));
8042
+ }
8043
+ function durationMsFromSec(durationSec) {
8044
+ if (!Number.isFinite(durationSec) || durationSec <= 0) {
8045
+ return 0;
8046
+ }
8047
+ return Math.round(durationSec * 1000);
8048
+ }
8049
+ function isRangeCollapsed(range) {
8050
+ return !range || range.endMs == null || range.endMs <= range.startMs;
8051
+ }
8052
+
8053
+ /** Clamp both edges onto the file. Collapsed drafts stay a point. */
8054
+ function resolveRange(range, durationMs) {
8055
+ const startMs = clampTimeMs(range.startMs, durationMs);
8056
+ if (range.endMs == null) {
8057
+ return {
8058
+ endMs: startMs,
8059
+ startMs
8060
+ };
8061
+ }
8062
+ const endMs = clampTimeMs(range.endMs, durationMs);
8063
+ if (endMs <= startMs) {
8064
+ return {
8065
+ endMs: startMs,
8066
+ startMs
8067
+ };
8068
+ }
8069
+ return {
8070
+ endMs,
8071
+ startMs
8072
+ };
8073
+ }
8074
+
8075
+ /**
8076
+ * Which handle should receive this pointer. Overlapping hit areas (collapsed or
8077
+ * a span narrower than the 24px target) split at the midpoint so each edge stays reachable.
8078
+ */
8079
+ function pickRangeHandle({
8080
+ endX,
8081
+ pointerX,
8082
+ startX
8083
+ }) {
8084
+ const mid = (startX + endX) / 2;
8085
+ if (!Number.isFinite(mid)) {
8086
+ return 'end';
8087
+ }
8088
+ return pointerX < mid ? 'start' : 'end';
8089
+ }
8090
+
8091
+ /** Screen x of a handle. Collapsed drafts sit 1px left/right of the playhead, not on it. */
8092
+ function visualRangeHandlePx({
8093
+ handle,
8094
+ range,
8095
+ viewport
8096
+ }) {
8097
+ const timeSec = (handle === 'start' ? range.startMs : range.endMs) / 1000;
8098
+ const x = (0,_viewport__WEBPACK_IMPORTED_MODULE_1__/* .positionPxFromTime */ .TO)(timeSec, viewport);
8099
+ if (range.startMs !== range.endMs) {
8100
+ return x;
8101
+ }
8102
+ return handle === 'start' ? x - _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_COLLAPSED_OFFSET_PX */ .BG : x + _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_COLLAPSED_OFFSET_PX */ .BG;
8103
+ }
8104
+
8105
+ /** True when the pointer is over the range rectangle or its handle hit targets. */
8106
+ function isPointerOverRange({
8107
+ pointerX,
8108
+ range,
8109
+ viewport
8110
+ }) {
8111
+ const resolved = resolveRange(range, durationMsFromSec(viewport.durationSec));
8112
+ const startX = visualRangeHandlePx({
8113
+ handle: 'start',
8114
+ range: resolved,
8115
+ viewport
8116
+ });
8117
+ const endX = visualRangeHandlePx({
8118
+ handle: 'end',
8119
+ range: resolved,
8120
+ viewport
8121
+ });
8122
+ const pad = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_HIT_AREA_PX */ .r0 / 2;
8123
+ return pointerX >= Math.min(startX, endX) - pad && pointerX <= Math.max(startX, endX) + pad;
8124
+ }
8125
+
8126
+ /** Snap to the playhead when the pointer is within `WAVEFORM_RANGE_SNAP_PX`. */
8127
+ function snapTimeMs({
8128
+ pixelsPerSecond,
8129
+ playheadMs,
8130
+ timeMs
8131
+ }) {
8132
+ if (!(pixelsPerSecond > 0) || !Number.isFinite(playheadMs)) {
8133
+ return timeMs;
8134
+ }
8135
+ const deltaPx = Math.abs(timeMs - playheadMs) / 1000 * pixelsPerSecond;
8136
+ if (deltaPx <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_SNAP_PX */ .IQ) {
8137
+ return Math.round(playheadMs);
8138
+ }
8139
+ return timeMs;
8140
+ }
8141
+
8142
+ /**
8143
+ * Move one edge. Crossing the other edge swaps roles so a collapsed pair can
8144
+ * expand in either direction. Enforces `[0, duration]` and the minimum span
8145
+ * once the drag has actually opened a range. An already-open range stays a
8146
+ * span — snapping one edge onto the other clamps to the minimum duration.
8147
+ */
8148
+ function dragRangeHandle({
8149
+ durationMs,
8150
+ handle,
8151
+ pointerMs,
8152
+ range
8153
+ }) {
8154
+ const duration = Math.max(0, durationMs);
8155
+ const pointer = clampTimeMs(pointerMs, duration);
8156
+ let {
8157
+ startMs,
8158
+ endMs
8159
+ } = range;
8160
+ let nextHandle = handle;
8161
+ if (handle === 'start') {
8162
+ if (pointer > endMs) {
8163
+ startMs = endMs;
8164
+ endMs = pointer;
8165
+ nextHandle = 'end';
8166
+ } else {
8167
+ startMs = pointer;
8168
+ }
8169
+ } else if (pointer < startMs) {
8170
+ endMs = startMs;
8171
+ startMs = pointer;
8172
+ nextHandle = 'start';
8173
+ } else {
8174
+ endMs = pointer;
8175
+ }
8176
+ const wasOpen = range.startMs !== range.endMs;
8177
+ const opened = wasOpen || startMs !== endMs;
8178
+ if (opened && endMs - startMs < _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_MIN_DURATION_MS */ .bX) {
8179
+ if (nextHandle === 'end') {
8180
+ endMs = clampTimeMs(startMs + _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_MIN_DURATION_MS */ .bX, duration);
8181
+ if (endMs - startMs < _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_MIN_DURATION_MS */ .bX) {
8182
+ startMs = clampTimeMs(endMs - _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_MIN_DURATION_MS */ .bX, duration);
8183
+ }
8184
+ } else {
8185
+ startMs = clampTimeMs(endMs - _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_MIN_DURATION_MS */ .bX, duration);
8186
+ if (endMs - startMs < _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_MIN_DURATION_MS */ .bX) {
8187
+ endMs = clampTimeMs(startMs + _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_MIN_DURATION_MS */ .bX, duration);
8188
+ }
8189
+ }
8190
+ }
8191
+ return {
8192
+ handle: nextHandle,
8193
+ range: {
8194
+ endMs,
8195
+ startMs
8196
+ }
8197
+ };
8198
+ }
8199
+ function pointerTimeMs(pointerX, viewport) {
8200
+ return Math.round((0,_viewport__WEBPACK_IMPORTED_MODULE_1__/* .timeFromPositionPx */ .lZ)(pointerX, viewport) * 1000);
8201
+ }
8202
+
8203
+ /** Sidebar payload after mouseup. Null when the draft is still a single timestamp. */
8204
+ function commitRangeChange(range) {
8205
+ if (range.endMs <= range.startMs) {
8206
+ return null;
8207
+ }
8208
+ return {
8209
+ endMs: range.endMs,
8210
+ startMs: range.startMs
8211
+ };
8212
+ }
8213
+ function rangeProgress(range, durationMs) {
8214
+ if (!(durationMs > 0) || range.endMs <= range.startMs) {
8215
+ return null;
8216
+ }
8217
+ return {
8218
+ end: range.endMs / durationMs,
8219
+ start: range.startMs / durationMs
8220
+ };
8221
+ }
8222
+
8223
+ /***/ },
8224
+
7645
8225
  /***/ 1053
7646
8226
  (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
7647
8227
 
@@ -7829,6 +8409,369 @@ function isRetryableWaveformError(code) {
7829
8409
 
7830
8410
  /***/ },
7831
8411
 
8412
+ /***/ 772
8413
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
8414
+
8415
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
8416
+ /* harmony export */ Iw: () => (/* binding */ sliderValueFromZoom),
8417
+ /* harmony export */ J$: () => (/* binding */ getZoomedPixelsPerSecond),
8418
+ /* harmony export */ KH: () => (/* binding */ viewportEquals),
8419
+ /* harmony export */ Mu: () => (/* binding */ timeLeftPercent),
8420
+ /* harmony export */ NI: () => (/* binding */ getViewportAtScroll),
8421
+ /* harmony export */ TO: () => (/* binding */ positionPxFromTime),
8422
+ /* harmony export */ aJ: () => (/* binding */ getPlayheadCameraAction),
8423
+ /* harmony export */ b6: () => (/* binding */ getCenteredScrollLeft),
8424
+ /* harmony export */ by: () => (/* binding */ getWaveformZoomMax),
8425
+ /* harmony export */ d0: () => (/* binding */ maxScrollLeft),
8426
+ /* harmony export */ eU: () => (/* binding */ zoomFromSliderValue),
8427
+ /* harmony export */ eq: () => (/* binding */ createWaveformViewport),
8428
+ /* harmony export */ gd: () => (/* binding */ getWaveformZoomMultiplier),
8429
+ /* harmony export */ gl: () => (/* binding */ getPinnedPlayheadLeft),
8430
+ /* harmony export */ iA: () => (/* binding */ getTapeGutterPx),
8431
+ /* harmony export */ ii: () => (/* binding */ clampWaveformZoom),
8432
+ /* harmony export */ lZ: () => (/* binding */ timeFromPositionPx),
8433
+ /* harmony export */ lz: () => (/* binding */ getTapeDefaultZoom),
8434
+ /* harmony export */ wA: () => (/* binding */ getSeekCameraAction),
8435
+ /* harmony export */ xV: () => (/* binding */ stepWaveformZoom),
8436
+ /* harmony export */ yW: () => (/* binding */ getTapePinnedPlayheadLeft)
8437
+ /* harmony export */ });
8438
+ /* unused harmony exports isTimeInView, getTapeCameraAction, getFollowInsetPx */
8439
+ /* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4929);
8440
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
8441
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
8442
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
8443
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
8444
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
8445
+
8446
+ /** Visible window: start/end times, pixels-per-second, and the current zoom. */
8447
+ function createWaveformViewport({
8448
+ durationSec,
8449
+ gutterPx = 0,
8450
+ heightPx,
8451
+ maxZoom,
8452
+ scrollLeftPx,
8453
+ widthPx,
8454
+ zoomLevel
8455
+ }) {
8456
+ const zoom = Number.isFinite(zoomLevel) && zoomLevel > 0 ? zoomLevel : _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW;
8457
+ const viewDurationSec = durationSec > 0 ? durationSec / zoom : 0; // seconds visible at this zoom
8458
+ const pixelsPerSecond = viewDurationSec > 0 && widthPx > 0 ? widthPx / viewDurationSec : 0;
8459
+ const leadingGutterPx = Number.isFinite(gutterPx) && gutterPx > 0 ? gutterPx : 0;
8460
+ const unclampedStartSec = pixelsPerSecond > 0 ? (scrollLeftPx - leadingGutterPx) / pixelsPerSecond : 0;
8461
+ const gutterDurationSec = pixelsPerSecond > 0 ? leadingGutterPx / pixelsPerSecond : 0;
8462
+ const latestWindowStartSec = leadingGutterPx > 0 ? durationSec + gutterDurationSec - viewDurationSec : Math.max(0, durationSec - viewDurationSec);
8463
+ const earliestWindowStartSec = leadingGutterPx > 0 ? -gutterDurationSec : 0;
8464
+ const startSec = pixelsPerSecond > 0 ? Math.min(latestWindowStartSec, Math.max(earliestWindowStartSec, unclampedStartSec)) : 0;
8465
+ return {
8466
+ durationSec,
8467
+ endSec: startSec + viewDurationSec,
8468
+ gutterPx: leadingGutterPx,
8469
+ heightPx,
8470
+ maxZoom,
8471
+ pixelsPerSecond,
8472
+ scrollLeftPx,
8473
+ startSec,
8474
+ widthPx,
8475
+ zoomLevel: zoom
8476
+ };
8477
+ }
8478
+
8479
+ /** Same viewport with a different scroll, so start/end times update. */
8480
+ function getViewportAtScroll(viewport, scrollLeftPx) {
8481
+ return createWaveformViewport(_objectSpread(_objectSpread({}, viewport), {}, {
8482
+ scrollLeftPx
8483
+ }));
8484
+ }
8485
+
8486
+ /** True when the visible window has not moved — skip a React state update. */
8487
+ function viewportEquals(prev, next) {
8488
+ return !!prev && prev.durationSec === next.durationSec && prev.endSec === next.endSec && prev.gutterPx === next.gutterPx && prev.maxZoom === next.maxZoom && prev.scrollLeftPx === next.scrollLeftPx && prev.startSec === next.startSec && prev.widthPx === next.widthPx && prev.zoomLevel === next.zoomLevel;
8489
+ }
8490
+
8491
+ /** How many CSS pixels from the left of the visible window this time sits. */
8492
+ function positionPxFromTime(timeSec, viewport) {
8493
+ return (timeSec - viewport.startSec) * viewport.pixelsPerSecond;
8494
+ }
8495
+
8496
+ /** Media time under a point this many CSS pixels from the left of the visible window. */
8497
+ function timeFromPositionPx(positionPx, viewport) {
8498
+ if (!(viewport.pixelsPerSecond > 0)) {
8499
+ return viewport.startSec;
8500
+ }
8501
+ return viewport.startSec + positionPx / viewport.pixelsPerSecond;
8502
+ }
8503
+
8504
+ /** True when this time sits inside the visible window. */
8505
+ function isTimeInView(timeSec, viewport) {
8506
+ return timeSec >= viewport.startSec && timeSec <= viewport.endSec;
8507
+ }
8508
+
8509
+ /** UI max zoom: 24×, ~1 peak per CSS pixel, and a minimum visible window. */
8510
+ function getWaveformZoomMax({
8511
+ durationSec,
8512
+ peakCount,
8513
+ viewWidthPx
8514
+ }) {
8515
+ if (!(peakCount > 0) || !(viewWidthPx > 0)) {
8516
+ return _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW;
8517
+ }
8518
+ const peakLimitedMax = Math.floor(peakCount / viewWidthPx);
8519
+ const durationLimitedMax = durationSec > 0 ? durationSec / _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_MIN_VIEW_WINDOW_SEC */ .Kl : _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW;
8520
+ return Math.max(_constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW, Math.min(_constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MAX */ .tK, peakLimitedMax, durationLimitedMax));
8521
+ }
8522
+
8523
+ /** Keep zoom between 1× and this file's max. */
8524
+ function clampWaveformZoom(zoomLevel, maxZoom = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8525
+ const max = Math.max(_constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW, maxZoom);
8526
+ if (!Number.isFinite(zoomLevel)) {
8527
+ return _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW;
8528
+ }
8529
+ return Math.min(max, Math.max(_constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW, zoomLevel));
8530
+ }
8531
+
8532
+ /** Zoom badge: 1.1×–1.9× below 2×, then whole numbers. Omit at 1×. */
8533
+ function getWaveformZoomMultiplier(zoomLevel) {
8534
+ if (!Number.isFinite(zoomLevel) || zoomLevel <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8535
+ return null;
8536
+ }
8537
+ if (zoomLevel < 2) {
8538
+ const tenths = Math.round(zoomLevel * 10) / 10;
8539
+ if (tenths <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8540
+ return null;
8541
+ }
8542
+ if (tenths >= 2) {
8543
+ return '2x';
8544
+ }
8545
+ return `${tenths.toFixed(1)}x`;
8546
+ }
8547
+ return `${Math.floor(zoomLevel)}x`;
8548
+ }
8549
+
8550
+ /** WaveSurfer zoom density. 0 = fit the whole file in the view. */
8551
+ function getZoomedPixelsPerSecond({
8552
+ durationSec,
8553
+ isTape = false,
8554
+ maxZoom = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW,
8555
+ viewWidthPx,
8556
+ zoomLevel
8557
+ }) {
8558
+ const zoom = clampWaveformZoom(zoomLevel, maxZoom);
8559
+ if (!(durationSec > 0) || !(viewWidthPx > 0)) {
8560
+ return 0;
8561
+ }
8562
+ if (!isTape && zoom <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8563
+ return 0;
8564
+ }
8565
+ return viewWidthPx / durationSec * zoom;
8566
+ }
8567
+
8568
+ /** Half-view empty space so t=0 and duration can sit under the center pin. */
8569
+ function getTapeGutterPx(widthPx) {
8570
+ return widthPx > 0 ? widthPx / 2 : 0;
8571
+ }
8572
+
8573
+ /** Tape first paint: 10s on screen, or 1× when the file is shorter than that. */
8574
+ function getTapeDefaultZoom(durationSec, maxZoom = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8575
+ if (!(durationSec > 0)) {
8576
+ return _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW;
8577
+ }
8578
+ return clampWaveformZoom(durationSec / _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_TAPE_DEFAULT_WINDOW_SEC */ .Av, maxZoom);
8579
+ }
8580
+
8581
+ /** Map zoom (1…max) onto the 0–100 slider. */
8582
+ function sliderValueFromZoom(zoomLevel, maxZoom = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8583
+ const max = Math.max(_constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW, maxZoom);
8584
+ const zoom = clampWaveformZoom(zoomLevel, max);
8585
+ if (max <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8586
+ return 0;
8587
+ }
8588
+ return (zoom - _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) / (max - _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) * _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_SLIDER_MAX */ .nh;
8589
+ }
8590
+
8591
+ /** Inverse of sliderValueFromZoom. */
8592
+ function zoomFromSliderValue(value, maxZoom = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8593
+ const max = Math.max(_constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW, maxZoom);
8594
+ if (max <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8595
+ return _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW;
8596
+ }
8597
+ const t = Math.min(_constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_SLIDER_MAX */ .nh, Math.max(0, value)) / _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_SLIDER_MAX */ .nh;
8598
+ return clampWaveformZoom(_constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW + t * (max - _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW), max);
8599
+ }
8600
+
8601
+ /** One zoom-in/out button step on the 0–100 slider, mapped back to zoom. */
8602
+ function stepWaveformZoom(zoomLevel, maxZoom, sliderDelta) {
8603
+ return zoomFromSliderValue(Math.round(sliderValueFromZoom(zoomLevel, maxZoom)) + sliderDelta, maxZoom);
8604
+ }
8605
+
8606
+ /** Max scroll that still shows a full window (no overscroll). Gutters add empty lead/trail. */
8607
+ function maxScrollLeft(viewport) {
8608
+ const gutterPx = viewport.gutterPx || 0;
8609
+ return Math.max(0, viewport.durationSec * viewport.pixelsPerSecond + 2 * gutterPx - viewport.widthPx);
8610
+ }
8611
+
8612
+ /** Scroll offset that still shows a full window (no overscroll). */
8613
+ function clampScrollLeft(scrollLeftPx, viewport) {
8614
+ if (!Number.isFinite(scrollLeftPx)) {
8615
+ return 0;
8616
+ }
8617
+ return Math.min(maxScrollLeft(viewport), Math.max(0, scrollLeftPx));
8618
+ }
8619
+
8620
+ /** Center this time in the view, clamped so the window still fills the canvas. */
8621
+ function getCenteredScrollLeft(timeSec, viewport) {
8622
+ return clampScrollLeft(timeSec * viewport.pixelsPerSecond + (viewport.gutterPx || 0) - viewport.widthPx / 2, viewport);
8623
+ }
8624
+
8625
+ /**
8626
+ * Host/media seek while zoomed: jump so the time (and its comment avatar) is on screen.
8627
+ * No-op at fit-to-width or when that time is already visible.
8628
+ */
8629
+ function getSeekCameraAction({
8630
+ cameraMode = 'desktop',
8631
+ timeSec,
8632
+ viewport
8633
+ }) {
8634
+ if (!(viewport.widthPx > 0) || !(viewport.pixelsPerSecond > 0)) {
8635
+ return {
8636
+ type: 'none'
8637
+ };
8638
+ }
8639
+ if (cameraMode === 'tape') {
8640
+ const scrollLeftPx = getCenteredScrollLeft(timeSec, viewport);
8641
+ if (Math.abs(scrollLeftPx - viewport.scrollLeftPx) < 1) {
8642
+ return {
8643
+ type: 'none'
8644
+ };
8645
+ }
8646
+ return {
8647
+ type: 'jump',
8648
+ scrollLeftPx
8649
+ };
8650
+ }
8651
+ if (viewport.zoomLevel <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8652
+ return {
8653
+ type: 'none'
8654
+ };
8655
+ }
8656
+ if (isTimeInView(timeSec, viewport)) {
8657
+ return {
8658
+ type: 'none'
8659
+ };
8660
+ }
8661
+ return {
8662
+ type: 'jump',
8663
+ scrollLeftPx: getCenteredScrollLeft(timeSec, viewport)
8664
+ };
8665
+ }
8666
+
8667
+ /** Always keep this time under the center pin. Reuses followRight so the camera hook can pin. */
8668
+ function getTapeCameraAction({
8669
+ timeSec,
8670
+ viewport
8671
+ }) {
8672
+ if (!(viewport.widthPx > 0) || !(viewport.pixelsPerSecond > 0)) {
8673
+ return {
8674
+ type: 'none'
8675
+ };
8676
+ }
8677
+ return {
8678
+ isPlayheadPinned: true,
8679
+ scrollLeftPx: getCenteredScrollLeft(timeSec, viewport),
8680
+ type: 'followRight'
8681
+ };
8682
+ }
8683
+
8684
+ /** Follow inset in CSS px, capped at one third of the view so a narrow player still has room. */
8685
+ function getFollowInsetPx(widthPx, insetPx = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_FOLLOW_INSET_PX */ .NM) {
8686
+ if (!(widthPx > 0)) {
8687
+ return 0;
8688
+ }
8689
+ return Math.min(Math.max(0, insetPx), widthPx / 3);
8690
+ }
8691
+
8692
+ /** CSS left for a playhead pinned to the follow inset. */
8693
+ function getPinnedPlayheadLeft(widthPx, insetPx = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_FOLLOW_INSET_PX */ .NM) {
8694
+ const inset = getFollowInsetPx(widthPx, insetPx);
8695
+ if (!(widthPx > 0)) {
8696
+ return '0%';
8697
+ }
8698
+ return `${(widthPx - inset) / widthPx * 100}%`;
8699
+ }
8700
+
8701
+ /** CSS left for a playhead locked to the center of the view. */
8702
+ function getTapePinnedPlayheadLeft() {
8703
+ return '50%';
8704
+ }
8705
+
8706
+ /** CSS left % of the playhead from the left of the visible window. */
8707
+ function timeLeftPercent(timeSec, durationSec, viewport) {
8708
+ if (viewport.widthPx > 0 && viewport.pixelsPerSecond > 0) {
8709
+ return `${positionPxFromTime(timeSec, viewport) / viewport.widthPx * 100}%`;
8710
+ }
8711
+ const progress = durationSec > 0 ? Math.min(1, Math.max(0, timeSec / durationSec)) : 0;
8712
+ return `${progress * 100}%`;
8713
+ }
8714
+
8715
+ /**
8716
+ * Walk across the view until the playhead nears the right edge, then follow.
8717
+ * Off-screen at play start jumps in; off-screen right while playing keeps following.
8718
+ */
8719
+ function getPlayheadCameraAction({
8720
+ cameraMode = 'desktop',
8721
+ insetPx = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_FOLLOW_INSET_PX */ .NM,
8722
+ isPlaying,
8723
+ playJustStarted,
8724
+ timeSec,
8725
+ viewport
8726
+ }) {
8727
+ if (cameraMode === 'tape') {
8728
+ return getTapeCameraAction({
8729
+ timeSec,
8730
+ viewport
8731
+ });
8732
+ }
8733
+ if (!isPlaying || viewport.zoomLevel <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW || !(viewport.widthPx > 0) || !(viewport.pixelsPerSecond > 0)) {
8734
+ return {
8735
+ type: 'none'
8736
+ };
8737
+ }
8738
+ const inset = getFollowInsetPx(viewport.widthPx, insetPx);
8739
+ const viewX = positionPxFromTime(timeSec, viewport);
8740
+ const playheadCanvasX = timeSec * viewport.pixelsPerSecond;
8741
+ const followX = viewport.widthPx - inset;
8742
+ if (viewX < 0) {
8743
+ if (!playJustStarted) {
8744
+ return {
8745
+ type: 'none'
8746
+ };
8747
+ }
8748
+ return {
8749
+ type: 'jump',
8750
+ scrollLeftPx: clampScrollLeft(playheadCanvasX - inset, viewport)
8751
+ };
8752
+ }
8753
+ if (viewX >= followX) {
8754
+ const unclampedScroll = playheadCanvasX - followX;
8755
+ const scrollLeftPx = clampScrollLeft(unclampedScroll, viewport);
8756
+ if (playJustStarted && viewX > viewport.widthPx) {
8757
+ return {
8758
+ type: 'jump',
8759
+ scrollLeftPx
8760
+ };
8761
+ }
8762
+ return {
8763
+ type: 'followRight',
8764
+ isPlayheadPinned: unclampedScroll <= maxScrollLeft(viewport),
8765
+ scrollLeftPx
8766
+ };
8767
+ }
8768
+ return {
8769
+ type: 'none'
8770
+ };
8771
+ }
8772
+
8773
+ /***/ },
8774
+
7832
8775
  /***/ 2485
7833
8776
  (module, exports) {
7834
8777
 
@@ -14598,7 +15541,7 @@ var x = (y) => {
14598
15541
  var x = {}; __webpack_require__.d(x, y); return x
14599
15542
  }
14600
15543
  var y = (x) => (() => (x))
14601
- module.exports = x({ ["createElement"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.createElement), ["default"]: () => (__WEBPACK_EXTERNAL_MODULE_react__["default"]), ["useCallback"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useCallback), ["useEffect"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useEffect), ["useId"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useId), ["useLayoutEffect"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useLayoutEffect), ["useMemo"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useMemo), ["useRef"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useRef), ["useState"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useState) });
15544
+ module.exports = x({ ["createElement"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.createElement), ["default"]: () => (__WEBPACK_EXTERNAL_MODULE_react__["default"]), ["forwardRef"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.forwardRef), ["useCallback"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useCallback), ["useEffect"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useEffect), ["useId"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useId), ["useImperativeHandle"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useImperativeHandle), ["useLayoutEffect"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useLayoutEffect), ["useMemo"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useMemo), ["useRef"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useRef), ["useState"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useState) });
14602
15545
 
14603
15546
  /***/ },
14604
15547
 
@@ -23492,7 +24435,7 @@ class Browser {
23492
24435
  ;// ./src/lib/Logger.js
23493
24436
  /* eslint-disable no-undef */
23494
24437
  const CLIENT_NAME = "box-content-preview";
23495
- const CLIENT_VERSION = "3.91.0";
24438
+ const CLIENT_VERSION = "3.93.0";
23496
24439
  /* eslint-enable no-undef */
23497
24440
 
23498
24441
  class Logger {
@@ -32718,6 +33661,44 @@ class TextLoader extends viewers_AssetLoader {
32718
33661
  }
32719
33662
  }
32720
33663
  /* harmony default export */ const text_TextLoader = (new TextLoader());
33664
+ ;// ./src/lib/viewers/controls/media/types.ts
33665
+ /**
33666
+ * Draft range the activity sidebar is holding. Preview draws start/end handles
33667
+ * on the waveform until the sidebar sends `comment_range_draft_clear`.
33668
+ * The viewer emits `comment_range_draft_change` on pointer-up so the host
33669
+ * can persist the new timestamps, and `comment_range_draft_dismiss` when
33670
+ * the user clicks the waveform outside an open range. See media/README.md.
33671
+ * `endMs: null` means a single timestamp — both handles sit on `startMs`.
33672
+ */
33673
+
33674
+ /** Committed range after a handle drag. Always a real span (`endMs > startMs`). */
33675
+
33676
+ /** Sidebar → viewer: show or update the draft handles. */
33677
+ const EVENT_COMMENT_RANGE_DRAFT = 'comment_range_draft';
33678
+
33679
+ /** Sidebar → viewer: hide the handles (toggle-off, post, version switch, unmount). */
33680
+ const EVENT_COMMENT_RANGE_DRAFT_CLEAR = 'comment_range_draft_clear';
33681
+
33682
+ /** Viewer → sidebar: the user clicked the waveform outside an open draft range. */
33683
+ const EVENT_COMMENT_RANGE_DRAFT_DISMISS = 'comment_range_draft_dismiss';
33684
+
33685
+ /** Viewer → sidebar: a handle drag finished. Emitted on pointer-up only. */
33686
+ const EVENT_COMMENT_RANGE_DRAFT_CHANGE = 'comment_range_draft_change';
33687
+
33688
+ /** True when a host draft can be shown. Collapsed drafts use `endMs: null`. */
33689
+ function isValidCommentRangeDraft(value) {
33690
+ if (!value || typeof value !== 'object') {
33691
+ return false;
33692
+ }
33693
+ const draft = value;
33694
+ if (!Number.isFinite(draft.startMs)) {
33695
+ return false;
33696
+ }
33697
+ if (draft.endMs != null && (!Number.isFinite(draft.endMs) || Number(draft.endMs) <= Number(draft.startMs))) {
33698
+ return false;
33699
+ }
33700
+ return true;
33701
+ }
32721
33702
  // EXTERNAL MODULE: ./node_modules/lodash/isEmpty.js
32722
33703
  var isEmpty = __webpack_require__(2193);
32723
33704
  var isEmpty_default = /*#__PURE__*/__webpack_require__.n(isEmpty);
@@ -34687,6 +35668,7 @@ const CSS_CLASS_MEDIA_CONTAINER = 'bp-media-container';
34687
35668
  const DEFAULT_VOLUME = 1;
34688
35669
  const MEDIA_VOLUME_CACHE_KEY = 'media-volume';
34689
35670
  const MEDIA_AUTOPLAY_CACHE_KEY = 'media-autoplay';
35671
+ const MEDIA_PLAY_NEXT_CACHE_KEY = 'media-play-next';
34690
35672
  const MEDIA_SPEED_CACHE_KEY = 'media-speed';
34691
35673
  const MEDIA_VOLUME_INCREMENT = 0.05;
34692
35674
  const EMIT_WAIT_TIME_IN_MILLIS = 100;
@@ -34737,6 +35719,7 @@ class MediaBaseViewer extends viewers_BaseViewer {
34737
35719
  this.containerClickHandler = this.containerClickHandler.bind(this);
34738
35720
  this.errorHandler = this.errorHandler.bind(this);
34739
35721
  this.handleAutoplay = this.handleAutoplay.bind(this);
35722
+ this.handlePlayNext = this.handlePlayNext.bind(this);
34740
35723
  this.handleRate = this.handleRate.bind(this);
34741
35724
  this.handleTimeupdateFromMediaControls = this.handleTimeupdateFromMediaControls.bind(this);
34742
35725
  this.loadeddataHandler = this.loadeddataHandler.bind(this);
@@ -34749,6 +35732,7 @@ class MediaBaseViewer extends viewers_BaseViewer {
34749
35732
  this.resetPlayIcon = this.resetPlayIcon.bind(this);
34750
35733
  this.seekHandler = this.seekHandler.bind(this);
34751
35734
  this.setAutoplay = this.setAutoplay.bind(this);
35735
+ this.setPlayNext = this.setPlayNext.bind(this);
34752
35736
  this.setRate = this.setRate.bind(this);
34753
35737
  this.setTimeCode = this.setTimeCode.bind(this);
34754
35738
  this.setVolume = this.setVolume.bind(this);
@@ -35058,7 +36042,9 @@ class MediaBaseViewer extends viewers_BaseViewer {
35058
36042
  * @return {void}
35059
36043
  */
35060
36044
  handleRate() {
35061
- const speed = this.cache.get(MEDIA_SPEED_CACHE_KEY) - 0;
36045
+ const cachedSpeed = this.cache.get(MEDIA_SPEED_CACHE_KEY) - 0;
36046
+ // Default to 1x when cache is empty (v2 overlay play can run before loadUIReact).
36047
+ const speed = Number.isFinite(cachedSpeed) ? cachedSpeed : 1;
35062
36048
  if (speed && this.mediaEl.playbackRate !== speed && this.mediaEl.playbackRate > 0) {
35063
36049
  this.emit('ratechange', speed);
35064
36050
  }
@@ -35106,6 +36092,20 @@ class MediaBaseViewer extends viewers_BaseViewer {
35106
36092
  this.renderUI();
35107
36093
  }
35108
36094
  }
36095
+
36096
+ /**
36097
+ * Handler for play next
36098
+ *
36099
+ * @private
36100
+ * @emits playnext
36101
+ * @return {void}
36102
+ */
36103
+ handlePlayNext() {
36104
+ this.emit('playnext', this.isPlayNextEnabled());
36105
+ if (this.controls) {
36106
+ this.renderUI();
36107
+ }
36108
+ }
35109
36109
  /**
35110
36110
  * Autoplay the media
35111
36111
  *
@@ -35132,6 +36132,16 @@ class MediaBaseViewer extends viewers_BaseViewer {
35132
36132
  isAutoplayEnabled() {
35133
36133
  return this.cache.get(MEDIA_AUTOPLAY_CACHE_KEY) === 'Enabled';
35134
36134
  }
36135
+
36136
+ /**
36137
+ * Determines if play next is enabled
36138
+ *
36139
+ * @protected
36140
+ * @return {boolean} Indicates if play next is enabled
36141
+ */
36142
+ isPlayNextEnabled() {
36143
+ return this.cache.get(MEDIA_PLAY_NEXT_CACHE_KEY) === 'Enabled';
36144
+ }
35135
36145
  /**
35136
36146
  * Loads the controls
35137
36147
  *
@@ -35154,6 +36164,9 @@ class MediaBaseViewer extends viewers_BaseViewer {
35154
36164
  if (!this.cache.has(MEDIA_AUTOPLAY_CACHE_KEY)) {
35155
36165
  this.cache.set(MEDIA_AUTOPLAY_CACHE_KEY, 'Disabled');
35156
36166
  }
36167
+ if (!this.cache.has(MEDIA_PLAY_NEXT_CACHE_KEY)) {
36168
+ this.cache.set(MEDIA_PLAY_NEXT_CACHE_KEY, 'Disabled');
36169
+ }
35157
36170
  if (!this.cache.has(MEDIA_SPEED_CACHE_KEY)) {
35158
36171
  this.cache.set(MEDIA_SPEED_CACHE_KEY, '1.0');
35159
36172
  }
@@ -35248,6 +36261,18 @@ class MediaBaseViewer extends viewers_BaseViewer {
35248
36261
  this.handleAutoplay();
35249
36262
  }
35250
36263
 
36264
+ /**
36265
+ * Updates play next
36266
+ *
36267
+ * @protected
36268
+ * @param {boolean} playNext - True if enabled
36269
+ * @return {void}
36270
+ */
36271
+ setPlayNext(playNext) {
36272
+ this.cache.set(MEDIA_PLAY_NEXT_CACHE_KEY, playNext ? 'Enabled' : 'Disabled', true);
36273
+ this.handlePlayNext();
36274
+ }
36275
+
35251
36276
  /**
35252
36277
  * Updates play rate
35253
36278
  *
@@ -35349,17 +36374,17 @@ class MediaBaseViewer extends viewers_BaseViewer {
35349
36374
  }
35350
36375
 
35351
36376
  /**
35352
- * Emits the previewnextfile event if autoplay is enabled.
36377
+ * Emits mediaEndPlayNext when play next is enabled.
35353
36378
  *
35354
36379
  * @private
35355
- * @emits previewnextfile
36380
+ * @emits mediaEndPlayNext
35356
36381
  * @return {void}
35357
36382
  */
35358
36383
  mediaendHandler() {
35359
36384
  this.resetPlayIcon();
35360
36385
  this.processMetrics();
35361
- if (this.isAutoplayEnabled()) {
35362
- this.emit(lib_events/* VIEWER_EVENT */.Oi.mediaEndAutoplay);
36386
+ if (this.isPlayNextEnabled()) {
36387
+ this.emit(lib_events/* VIEWER_EVENT */.Oi.mediaEndPlayNext);
35363
36388
  }
35364
36389
  }
35365
36390
 
@@ -35935,8 +36960,8 @@ class MediaBaseViewer extends viewers_BaseViewer {
35935
36960
  }
35936
36961
  }
35937
36962
  /* harmony default export */ const media_MediaBaseViewer = (MediaBaseViewer);
35938
- // EXTERNAL MODULE: ./src/lib/viewers/controls/media/MediaSettings.tsx + 5 modules
35939
- var MediaSettings = __webpack_require__(6929);
36963
+ // EXTERNAL MODULE: ./src/lib/viewers/controls/media/MediaSettings.tsx + 6 modules
36964
+ var MediaSettings = __webpack_require__(9229);
35940
36965
  // EXTERNAL MODULE: ./src/lib/viewers/controls/media/PlayPauseToggle.tsx + 1 modules
35941
36966
  var PlayPauseToggle = __webpack_require__(6321);
35942
36967
  // EXTERNAL MODULE: ./src/lib/viewers/controls/media/DurationLabels.tsx + 1 modules
@@ -36129,7 +37154,7 @@ function MP3Controls({
36129
37154
  isPlaying: isPlaying,
36130
37155
  movePlayback: movePlayback,
36131
37156
  onPlayPause: onPlayPause
36132
- }), /*#__PURE__*/external_react_["default"].createElement(VolumeControls/* default */.A, {
37157
+ }), /*#__PURE__*/external_react_["default"].createElement(VolumeControls/* default */.Ay, {
36133
37158
  onMuteChange: onMuteChange,
36134
37159
  onVolumeChange: onVolumeChange,
36135
37160
  volume: volume
@@ -36180,6 +37205,8 @@ class MP3ControlsRoot {
36180
37205
  var waveform_constants = __webpack_require__(4929);
36181
37206
  // EXTERNAL MODULE: ./src/lib/viewers/media/waveform/createWaveformLoader.ts + 1 modules
36182
37207
  var createWaveformLoader = __webpack_require__(4971);
37208
+ // EXTERNAL MODULE: ./src/lib/viewers/media/waveform/range.ts
37209
+ var range = __webpack_require__(4253);
36183
37210
  // EXTERNAL MODULE: ./src/lib/viewers/media/waveform/validateWaveformPayload.ts
36184
37211
  var validateWaveformPayload = __webpack_require__(1053);
36185
37212
  ;// ./src/lib/viewers/media/MP3.scss
@@ -36201,7 +37228,60 @@ function MP3Viewer_toPrimitive(t, r) { if ("object" != typeof t || !t) return t;
36201
37228
 
36202
37229
 
36203
37230
 
37231
+
37232
+
36204
37233
  const CSS_CLASS_MP3 = 'bp-media-mp3';
37234
+ const AUDIO_V2_SHUTTLE_RATES = [2, 4, 8, 16];
37235
+ const AUDIO_V2_SHUTTLE_TICK_MS = 50;
37236
+ const AUDIO_V2_SKIP_SEC = 1;
37237
+ const AUDIO_V2_SHIFT_SKIP_SEC = 5;
37238
+
37239
+ /**
37240
+ * Next J/L shuttle rate. Opposite direction starts at 2×.
37241
+ *
37242
+ * @param {number} currentRate
37243
+ * @param {boolean} sameDirection
37244
+ * @return {number} 2, 4, 8, or 16
37245
+ */
37246
+ function nextShuttleRate(currentRate, sameDirection) {
37247
+ if (!sameDirection) {
37248
+ return AUDIO_V2_SHUTTLE_RATES[0];
37249
+ }
37250
+ const index = AUDIO_V2_SHUTTLE_RATES.indexOf(currentRate);
37251
+ if (index < 0) {
37252
+ return AUDIO_V2_SHUTTLE_RATES[0];
37253
+ }
37254
+ return AUDIO_V2_SHUTTLE_RATES[Math.min(index + 1, AUDIO_V2_SHUTTLE_RATES.length - 1)];
37255
+ }
37256
+
37257
+ /**
37258
+ * Next/previous comment marker for ↑/↓. Walks by id so same-time stacks each get a stop.
37259
+ *
37260
+ * @param {Array<Object>} markers
37261
+ * @param {Object} options
37262
+ * @param {number} options.direction -1 previous, 1 next
37263
+ * @param {string|null} options.selectedId
37264
+ * @param {number} options.time current playhead
37265
+ * @return {Object|null} marker to select
37266
+ */
37267
+ function nextCommentMarker(markers, {
37268
+ direction,
37269
+ selectedId,
37270
+ time
37271
+ }) {
37272
+ const sorted = (markers || []).filter(marker => Number.isFinite(marker.time)).sort((left, right) => left.time - right.time || String(left.id).localeCompare(String(right.id)));
37273
+ if (!sorted.length) {
37274
+ return null;
37275
+ }
37276
+ const selectedIndex = sorted.findIndex(marker => marker.id === selectedId);
37277
+ if (selectedIndex >= 0) {
37278
+ return sorted[Math.max(0, Math.min(sorted.length - 1, selectedIndex + direction))];
37279
+ }
37280
+ if (direction > 0) {
37281
+ return sorted.find(marker => marker.time >= time) || sorted[sorted.length - 1];
37282
+ }
37283
+ return [...sorted].reverse().find(marker => marker.time <= time) || sorted[0];
37284
+ }
36205
37285
  function createLoadFailedError(message) {
36206
37286
  const error = new Error(message);
36207
37287
  error.name = 'LOAD_FAILED';
@@ -36214,6 +37294,7 @@ class MP3Viewer extends media_MediaBaseViewer {
36214
37294
  * @inheritdoc
36215
37295
  */
36216
37296
  MP3Viewer_defineProperty(this, "handlePlayRequest", () => {
37297
+ this.exitShuttle();
36217
37298
  this.userRequestedPlay = true;
36218
37299
  this.togglePlay();
36219
37300
  if (this.shouldRaceClientWaveformDecode() && this.isWaveformDecodeRetryPending && !this.hasUsedWaveformDecodePlayRetry) {
@@ -36230,6 +37311,7 @@ class MP3Viewer extends media_MediaBaseViewer {
36230
37311
  * @return {void}
36231
37312
  */
36232
37313
  MP3Viewer_defineProperty(this, "handlePlayPause", shouldPlay => {
37314
+ this.exitShuttle();
36233
37315
  if (shouldPlay) {
36234
37316
  this.handlePlayRequest();
36235
37317
  return;
@@ -36239,19 +37321,69 @@ class MP3Viewer extends media_MediaBaseViewer {
36239
37321
  MP3Viewer_defineProperty(this, "handleCommentMarkersUpdated", (markers = []) => {
36240
37322
  this.commentMarkers = markers;
36241
37323
  const selected = markers.find(marker => marker.isSelected);
36242
- const selectedId = selected ? selected.id : null;
36243
- if (selectedId !== this.hostSelectedMarkerId) {
36244
- this.hostSelectedMarkerId = selectedId;
36245
- if (selected && Number.isFinite(selected.time)) {
36246
- this.pendingHostSelectedSeek = selected;
36247
- this.applyPendingHostSelectedSeek();
36248
- } else {
36249
- this.pendingHostSelectedSeek = null;
37324
+
37325
+ // Host echoes often omit isSelected. Keep hostSelectedMarkerId so ↑/↓ can
37326
+ // still walk other comments at the same time. Clear only when that id is gone.
37327
+ if (selected) {
37328
+ if (selected.id !== this.hostSelectedMarkerId) {
37329
+ this.hostSelectedMarkerId = selected.id;
37330
+ if (Number.isFinite(selected.time)) {
37331
+ this.pendingHostSelectedSeek = selected;
37332
+ this.applyPendingHostSelectedSeek();
37333
+ }
36250
37334
  }
37335
+ } else if (this.hostSelectedMarkerId && !markers.some(marker => marker.id === this.hostSelectedMarkerId)) {
37336
+ this.hostSelectedMarkerId = null;
37337
+ this.pendingHostSelectedSeek = null;
37338
+ }
37339
+ this.renderUI();
37340
+ });
37341
+ MP3Viewer_defineProperty(this, "handleCommentRangeDraft", draft => {
37342
+ if (this.isCommentRangeDragging || !isValidCommentRangeDraft(draft)) {
37343
+ return;
37344
+ }
37345
+ this.commentRangeDraft = {
37346
+ endMs: draft.endMs == null ? null : draft.endMs,
37347
+ startMs: draft.startMs
37348
+ };
37349
+ this.syncCommentRangeLoop();
37350
+ this.renderUI();
37351
+ });
37352
+ MP3Viewer_defineProperty(this, "handleCommentRangeDraftClear", () => {
37353
+ this.commentRangeDraft = null;
37354
+ this.isCommentRangeDragging = false;
37355
+ this.syncCommentRangeLoop();
37356
+ this.renderUI();
37357
+ });
37358
+ MP3Viewer_defineProperty(this, "handleCommentRangeChange", range => {
37359
+ if (!range || !Number.isFinite(range.startMs) || !Number.isFinite(range.endMs) || range.endMs <= range.startMs) {
37360
+ return;
37361
+ }
37362
+ this.commentRangeDraft = {
37363
+ endMs: range.endMs,
37364
+ startMs: range.startMs
37365
+ };
37366
+ this.emit(EVENT_COMMENT_RANGE_DRAFT_CHANGE, {
37367
+ endMs: range.endMs,
37368
+ startMs: range.startMs
37369
+ });
37370
+ this.syncCommentRangeLoop();
37371
+ this.renderUI();
37372
+ });
37373
+ MP3Viewer_defineProperty(this, "handleCommentRangeDragChange", isDragging => {
37374
+ this.isCommentRangeDragging = !!isDragging;
37375
+ });
37376
+ MP3Viewer_defineProperty(this, "handleCommentRangeClear", () => {
37377
+ if (!this.commentRangeDraft || this.commentRangeDraft.endMs == null) {
37378
+ return;
36251
37379
  }
37380
+ this.commentRangeDraft = null;
37381
+ this.emit(EVENT_COMMENT_RANGE_DRAFT_DISMISS);
37382
+ this.syncCommentRangeLoop();
36252
37383
  this.renderUI();
36253
37384
  });
36254
37385
  MP3Viewer_defineProperty(this, "handleCommentMarkerClick", marker => {
37386
+ this.exitShuttle();
36255
37387
  this.hostSelectedMarkerId = marker.id;
36256
37388
  this.pendingHostSelectedSeek = marker;
36257
37389
  if (this.mediaEl) {
@@ -36290,6 +37422,7 @@ class MP3Viewer extends media_MediaBaseViewer {
36290
37422
  this.isAudioPlayerV2 = this.getIsAudioPlayerV2();
36291
37423
  this.waveformPeaks = [];
36292
37424
  this.waveformPeaksSource = null;
37425
+ this.isWaveformConversionPolling = false;
36293
37426
  this.waveformDurationSec = 0;
36294
37427
  if (this.isAudioPlayerV2) {
36295
37428
  this.wrapperEl.classList.add('bp-media--v2');
@@ -36299,12 +37432,22 @@ class MP3Viewer extends media_MediaBaseViewer {
36299
37432
  // Listen on the loading shell. The waveform is on
36300
37433
  // screen before the audio blob is playable; getViewer() is still null.
36301
37434
  this.bindCommentMarkersListener();
37435
+ this.bindCommentRangeDraftListeners();
36302
37436
  }
36303
37437
 
36304
37438
  // Audio element
36305
37439
  this.mediaEl = this.mediaContainerEl.appendChild(document.createElement('audio'));
36306
37440
  this.mediaEl.setAttribute('preload', 'auto');
36307
37441
  this.commentMarkers = [];
37442
+ this.commentRangeDraft = null;
37443
+ this.hostSelectedMarkerId = null;
37444
+ this.isCommentRangeDragging = false;
37445
+ this.shuttleDirection = null;
37446
+ this.shuttleRate = 0;
37447
+ this.reverseShuttleTimer = 0;
37448
+ this.commentRangeLoopTimer = 0;
37449
+ this.keyboardVolumeStep = 0;
37450
+ this.keyboardZoomStep = 0;
36308
37451
  }
36309
37452
  bindCommentMarkersListener() {
36310
37453
  if (!this.isAudioPlayerV2) {
@@ -36313,6 +37456,15 @@ class MP3Viewer extends media_MediaBaseViewer {
36313
37456
  this.removeListener('comment_markers', this.handleCommentMarkersUpdated);
36314
37457
  this.addListener('comment_markers', this.handleCommentMarkersUpdated);
36315
37458
  }
37459
+ bindCommentRangeDraftListeners() {
37460
+ if (!this.isAudioPlayerV2) {
37461
+ return;
37462
+ }
37463
+ this.removeListener(EVENT_COMMENT_RANGE_DRAFT, this.handleCommentRangeDraft);
37464
+ this.removeListener(EVENT_COMMENT_RANGE_DRAFT_CLEAR, this.handleCommentRangeDraftClear);
37465
+ this.addListener(EVENT_COMMENT_RANGE_DRAFT, this.handleCommentRangeDraft);
37466
+ this.addListener(EVENT_COMMENT_RANGE_DRAFT_CLEAR, this.handleCommentRangeDraftClear);
37467
+ }
36316
37468
 
36317
37469
  /**
36318
37470
  * Audio player v2 (waveform). Off unless the host enables `audioPlayerV2`
@@ -36324,6 +37476,217 @@ class MP3Viewer extends media_MediaBaseViewer {
36324
37476
  return this.featureEnabled(constants/* AUDIO_PLAYER_V2 */.Ku) && this.useReactControls();
36325
37477
  }
36326
37478
 
37479
+ /**
37480
+ * V2 audio keymap. Flag off and video keep MediaBaseViewer.handleKeydownReact.
37481
+ * Alt is not in Preview's key decoder — read it from the native event.
37482
+ *
37483
+ * @param {string} key
37484
+ * @param {KeyboardEvent} [event]
37485
+ * @return {boolean} consumed
37486
+ */
37487
+ onKeydown(key, event) {
37488
+ if (this.isAudioPlayerV2 && this.handleKeydownAudioV2(key, event)) {
37489
+ return true;
37490
+ }
37491
+ return super.onKeydown(key, event);
37492
+ }
37493
+ handleKeydownAudioV2(key, event) {
37494
+ const decoded = (key || '').toLowerCase();
37495
+ const altKey = !!(event && event.altKey);
37496
+ switch (decoded) {
37497
+ case 'space':
37498
+ this.toggleV2Play();
37499
+ return true;
37500
+ case 'k':
37501
+ this.exitShuttle();
37502
+ this.pause(undefined, true);
37503
+ return true;
37504
+ case 'j':
37505
+ this.shuttle('reverse');
37506
+ return true;
37507
+ case 'l':
37508
+ this.shuttle('forward');
37509
+ return true;
37510
+ case 'arrowleft':
37511
+ this.quickSeek(-AUDIO_V2_SKIP_SEC);
37512
+ return true;
37513
+ case 'arrowright':
37514
+ this.quickSeek(AUDIO_V2_SKIP_SEC);
37515
+ return true;
37516
+ case ',':
37517
+ this.frameStep('back');
37518
+ return true;
37519
+ case '.':
37520
+ this.frameStep('forward');
37521
+ return true;
37522
+ case 'arrowup':
37523
+ if (altKey || !(this.commentMarkers || []).some(marker => Number.isFinite(marker.time))) {
37524
+ this.stepKeyboardVolume(1);
37525
+ return true;
37526
+ }
37527
+ this.jumpToCommentMarker(-1);
37528
+ return true;
37529
+ case 'arrowdown':
37530
+ if (altKey || !(this.commentMarkers || []).some(marker => Number.isFinite(marker.time))) {
37531
+ this.stepKeyboardVolume(-1);
37532
+ return true;
37533
+ }
37534
+ this.jumpToCommentMarker(1);
37535
+ return true;
37536
+ case 'end':
37537
+ if (!this.mediaEl || !Number.isFinite(this.mediaEl.duration)) {
37538
+ return false;
37539
+ }
37540
+ this.exitShuttle();
37541
+ this.setMediaTime(this.mediaEl.duration);
37542
+ return true;
37543
+ case '0':
37544
+ case 'home':
37545
+ this.exitShuttle();
37546
+ return false;
37547
+ case 'shift+arrowleft':
37548
+ this.quickSeek(-AUDIO_V2_SHIFT_SKIP_SEC);
37549
+ return true;
37550
+ case 'shift+arrowright':
37551
+ this.quickSeek(AUDIO_V2_SHIFT_SKIP_SEC);
37552
+ return true;
37553
+ case 'i':
37554
+ case 'o':
37555
+ case '/':
37556
+ case 'shift+l':
37557
+ // Range UI is not on this branch yet. Consume so I/O/loop cannot
37558
+ // fall through to shuttle or Preview navigation.
37559
+ return true;
37560
+ case 'shift++':
37561
+ case '+':
37562
+ case '=':
37563
+ this.stepKeyboardZoom(1);
37564
+ return true;
37565
+ case '-':
37566
+ case 'shift+_':
37567
+ this.stepKeyboardZoom(-1);
37568
+ return true;
37569
+ default:
37570
+ return false;
37571
+ }
37572
+ }
37573
+ toggleV2Play() {
37574
+ const reverseShuttle = this.shuttleDirection === 'reverse';
37575
+ const wasPlaying = !!(this.mediaEl && !this.mediaEl.paused);
37576
+ this.exitShuttle();
37577
+ if (reverseShuttle || wasPlaying) {
37578
+ this.pause(undefined, true);
37579
+ return;
37580
+ }
37581
+ this.handlePlayRequest();
37582
+ }
37583
+ shuttle(direction) {
37584
+ const sameDirection = this.shuttleDirection === direction;
37585
+ const rate = nextShuttleRate(this.shuttleRate, sameDirection);
37586
+ if (direction === 'forward') {
37587
+ this.startForwardShuttle(rate);
37588
+ return;
37589
+ }
37590
+ this.startReverseShuttle(rate);
37591
+ }
37592
+ startForwardShuttle(rate) {
37593
+ this.stopReverseShuttle();
37594
+ this.shuttleDirection = 'forward';
37595
+ this.shuttleRate = rate;
37596
+ this.userRequestedPlay = true;
37597
+ const playPromise = this.play();
37598
+ if (this.mediaEl) {
37599
+ this.mediaEl.playbackRate = this.shuttleRate;
37600
+ }
37601
+ if (playPromise && typeof playPromise.then === 'function') {
37602
+ playPromise.then(() => {
37603
+ if (this.mediaEl && this.shuttleDirection === 'forward') {
37604
+ this.mediaEl.playbackRate = this.shuttleRate;
37605
+ }
37606
+ this.scheduleCommentRangeLoopWrap();
37607
+ }).catch(() => {});
37608
+ }
37609
+ this.scheduleCommentRangeLoopWrap(true);
37610
+ }
37611
+ startReverseShuttle(rate) {
37612
+ this.stopReverseShuttle();
37613
+ this.shuttleDirection = 'reverse';
37614
+ this.shuttleRate = rate;
37615
+ this.userRequestedPlay = true;
37616
+ this.pause(undefined, true);
37617
+ this.handleRate();
37618
+ this.reverseShuttleTimer = window.setInterval(() => {
37619
+ const floor = this.clampTimeToOpenCommentRange(0);
37620
+ if (!this.mediaEl || this.mediaEl.currentTime <= floor) {
37621
+ this.exitShuttle();
37622
+ return;
37623
+ }
37624
+ this.quickSeek(-(this.shuttleRate * AUDIO_V2_SHUTTLE_TICK_MS / 1000));
37625
+ if (this.mediaEl.currentTime <= floor) {
37626
+ this.exitShuttle();
37627
+ }
37628
+ }, AUDIO_V2_SHUTTLE_TICK_MS);
37629
+ }
37630
+ stopReverseShuttle() {
37631
+ if (this.reverseShuttleTimer) {
37632
+ window.clearInterval(this.reverseShuttleTimer);
37633
+ this.reverseShuttleTimer = 0;
37634
+ }
37635
+ }
37636
+ exitShuttle() {
37637
+ const wasShuttling = !!this.shuttleDirection;
37638
+ this.stopReverseShuttle();
37639
+ this.shuttleDirection = null;
37640
+ this.shuttleRate = 0;
37641
+ if (wasShuttling) {
37642
+ this.handleRate();
37643
+ }
37644
+ }
37645
+
37646
+ /**
37647
+ * Keep the settings rate unless forward shuttle owns playbackRate.
37648
+ *
37649
+ * @inheritdoc
37650
+ */
37651
+ handleRate() {
37652
+ if (this.shuttleDirection === 'forward' && this.shuttleRate > 0 && this.mediaEl) {
37653
+ this.mediaEl.playbackRate = this.shuttleRate;
37654
+ if (this.controls) {
37655
+ this.renderUI();
37656
+ }
37657
+ this.scheduleCommentRangeLoopWrap();
37658
+ return;
37659
+ }
37660
+ super.handleRate();
37661
+ this.scheduleCommentRangeLoopWrap();
37662
+ }
37663
+ jumpToCommentMarker(direction) {
37664
+ if (!this.mediaEl) {
37665
+ return;
37666
+ }
37667
+ const target = nextCommentMarker(this.commentMarkers, {
37668
+ direction,
37669
+ selectedId: this.hostSelectedMarkerId,
37670
+ time: this.mediaEl.currentTime
37671
+ });
37672
+ if (!target || target.id === this.hostSelectedMarkerId) {
37673
+ return;
37674
+ }
37675
+ this.handleCommentMarkerClick(target);
37676
+ }
37677
+ stepKeyboardVolume(direction) {
37678
+ this.keyboardVolumeStep += 1;
37679
+ if (direction > 0) {
37680
+ this.increaseVolume();
37681
+ return;
37682
+ }
37683
+ this.decreaseVolume();
37684
+ }
37685
+ stepKeyboardZoom(direction) {
37686
+ this.keyboardZoomStep += direction;
37687
+ this.renderUI();
37688
+ }
37689
+
36327
37690
  /**
36328
37691
  * Load v2 controls (and wavesurfer) only when the audio player v2 gate is on.
36329
37692
  *
@@ -36366,7 +37729,7 @@ class MP3Viewer extends media_MediaBaseViewer {
36366
37729
  * @return {Promise<{ default: Function }>} MP3ControlsV2 module
36367
37730
  */
36368
37731
  importV2Controls() {
36369
- return Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 2668));
37732
+ return Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 673));
36370
37733
  }
36371
37734
 
36372
37735
  /**
@@ -36388,7 +37751,15 @@ class MP3Viewer extends media_MediaBaseViewer {
36388
37751
  * @inheritdoc
36389
37752
  */
36390
37753
  destroy() {
37754
+ this.clearCommentRangeLoopTimer();
36391
37755
  this.removeListener('comment_markers', this.handleCommentMarkersUpdated);
37756
+ this.removeListener(EVENT_COMMENT_RANGE_DRAFT, this.handleCommentRangeDraft);
37757
+ this.removeListener(EVENT_COMMENT_RANGE_DRAFT_CLEAR, this.handleCommentRangeDraftClear);
37758
+ this.commentRangeDraft = null;
37759
+ this.isCommentRangeDragging = false;
37760
+ this.stopReverseShuttle();
37761
+ this.shuttleDirection = null;
37762
+ this.shuttleRate = 0;
36392
37763
  this.abortWaveformLoads();
36393
37764
  super.destroy();
36394
37765
  }
@@ -36398,6 +37769,10 @@ class MP3Viewer extends media_MediaBaseViewer {
36398
37769
  */
36399
37770
  load() {
36400
37771
  if (this.isAudioPlayerV2) {
37772
+ // A file-version switch is a clear, not a resync of the previous draft.
37773
+ this.commentRangeDraft = null;
37774
+ this.isCommentRangeDragging = false;
37775
+ this.syncCommentRangeLoop();
36401
37776
  this.showAudioLoadingShell();
36402
37777
  this.startConversionWaveformLoad();
36403
37778
  }
@@ -36471,6 +37846,48 @@ class MP3Viewer extends media_MediaBaseViewer {
36471
37846
  return (0,validateWaveformPayload/* isPositiveFinite */.Ze)(compressedBytes) && compressedBytes <= waveform_constants/* CLIENT_DECODE_MAX_COMPRESSED_BYTES */.EB && (0,validateWaveformPayload/* isPositiveFinite */.Ze)(durationSec) && durationSec <= waveform_constants/* CLIENT_DECODE_MAX_DURATION_SEC */.nG;
36472
37847
  }
36473
37848
 
37849
+ /**
37850
+ * @return {Object|null} waveform conversion representation
37851
+ */
37852
+ getWaveformRepresentation() {
37853
+ const {
37854
+ file
37855
+ } = this.options;
37856
+ return file?.representations?.entries ? getRepresentation(file, constants/* WAVEFORM_REP_NAME */.u4) : null;
37857
+ }
37858
+
37859
+ /**
37860
+ * @return {string|null} conversion representation state
37861
+ */
37862
+ getWaveformConversionState() {
37863
+ const conversionStatus = this.getWaveformRepresentation()?.status;
37864
+ return conversionStatus && typeof conversionStatus === 'object' ? conversionStatus.state : null;
37865
+ }
37866
+
37867
+ /**
37868
+ * Conversion JSON is already (or about to be) fetchable.
37869
+ *
37870
+ * @return {boolean}
37871
+ */
37872
+ isConversionWaveformAvailable() {
37873
+ const conversionState = this.getWaveformConversionState();
37874
+ return conversionState === constants/* STATUS_SUCCESS */.$g || conversionState === constants/* STATUS_VIEWABLE */.Re;
37875
+ }
37876
+
37877
+ /**
37878
+ * Generating chip while conversion RepStatus is polling. Hidden when the
37879
+ * rep is already success/viewable, when polling finishes, when conversion
37880
+ * errors, and once any source applies peaks.
37881
+ *
37882
+ * @return {boolean}
37883
+ */
37884
+ isGeneratingWaveform() {
37885
+ if (!this.isAudioPlayerV2 || this.destroyed || this.waveformPeaksSource) {
37886
+ return false;
37887
+ }
37888
+ return !!this.isWaveformConversionPolling;
37889
+ }
37890
+
36474
37891
  /**
36475
37892
  * @param {number} durationSec
36476
37893
  * @return {boolean}
@@ -36507,30 +37924,40 @@ class MP3Viewer extends media_MediaBaseViewer {
36507
37924
  if (!this.isAudioPlayerV2 || this.destroyed) {
36508
37925
  return;
36509
37926
  }
36510
- const {
36511
- file
36512
- } = this.options;
36513
- const waveform = file?.representations?.entries ? getRepresentation(file, constants/* WAVEFORM_REP_NAME */.u4) : null;
37927
+ const waveform = this.getWaveformRepresentation();
36514
37928
  const template = waveform?.content?.url_template;
36515
- const conversionStatus = waveform?.status;
36516
- const statusState = conversionStatus && typeof conversionStatus === 'object' ? conversionStatus.state : null;
36517
- if (!waveform || !template || !statusState || statusState === constants/* STATUS_ERROR */.uD) {
37929
+ const conversionState = this.getWaveformConversionState();
37930
+ if (!waveform || !template || !conversionState || conversionState === constants/* STATUS_ERROR */.uD) {
36518
37931
  this.startClientWaveformDecode();
37932
+ this.renderUI();
36519
37933
  return;
36520
37934
  }
36521
37935
  this.abortConversionWaveformLoad();
36522
37936
  this.waveformStatus = this.getRepStatus(waveform);
36523
37937
  this.waveformStatus.removeListener('conversionpending', this.resetLoadTimeout);
36524
37938
  const status = this.waveformStatus;
37939
+ const shouldPollConversion = !this.isConversionWaveformAvailable();
36525
37940
  try {
37941
+ if (shouldPollConversion) {
37942
+ this.isWaveformConversionPolling = true;
37943
+ this.renderUI();
37944
+ }
36526
37945
  await status.getPromise();
37946
+ this.isWaveformConversionPolling = false;
36527
37947
  if (this.destroyed || this.waveformStatus !== status || this.waveformPeaksSource) {
36528
37948
  return;
36529
37949
  }
37950
+ if (shouldPollConversion) {
37951
+ this.renderUI();
37952
+ }
36530
37953
  const result = await this.loadConversionWaveformPayload(template);
36531
37954
  this.handleConversionWaveformResult(result, status);
36532
37955
  } catch {
37956
+ this.isWaveformConversionPolling = false;
36533
37957
  this.startClientWaveformDecode();
37958
+ if (shouldPollConversion) {
37959
+ this.renderUI();
37960
+ }
36534
37961
  }
36535
37962
  }
36536
37963
 
@@ -36581,6 +38008,7 @@ class MP3Viewer extends media_MediaBaseViewer {
36581
38008
  }
36582
38009
  this.waveformPeaks = payload.peaks;
36583
38010
  this.waveformPeaksSource = source;
38011
+ this.isWaveformConversionPolling = false;
36584
38012
  this.isWaveformDecodeRetryPending = false;
36585
38013
  if (source === 'conversion') {
36586
38014
  this.waveformDurationSec = payload.durationSec;
@@ -36749,6 +38177,7 @@ class MP3Viewer extends media_MediaBaseViewer {
36749
38177
  });
36750
38178
  }
36751
38179
  this.bindCommentMarkersListener();
38180
+ this.bindCommentRangeDraftListeners();
36752
38181
  this.renderUI();
36753
38182
  }
36754
38183
  /**
@@ -36764,11 +38193,162 @@ class MP3Viewer extends media_MediaBaseViewer {
36764
38193
  return;
36765
38194
  }
36766
38195
  this.pendingHostSelectedSeek = null;
38196
+ this.exitShuttle();
38197
+ this.handleCommentRangeClear();
36767
38198
  this.mediaEl.pause();
36768
38199
  if (this.mediaEl.currentTime !== marker.time) {
36769
38200
  this.mediaEl.currentTime = marker.time;
36770
38201
  }
36771
38202
  }
38203
+
38204
+ /**
38205
+ * Seconds of the open draft span, or null when collapsed / absent.
38206
+ * Collapsed drafts (`endMs: null`) do not confine playback.
38207
+ *
38208
+ * @return {{endSec: number, startSec: number}|null}
38209
+ */
38210
+ getOpenCommentRangeSeconds() {
38211
+ const draft = this.commentRangeDraft;
38212
+ if ((0,range/* isRangeCollapsed */.BT)(draft) || !Number.isFinite(draft.startMs) || !Number.isFinite(draft.endMs)) {
38213
+ return null;
38214
+ }
38215
+ return {
38216
+ endSec: draft.endMs / 1000,
38217
+ startSec: draft.startMs / 1000
38218
+ };
38219
+ }
38220
+
38221
+ /**
38222
+ * Keep a seek inside the open draft. Waveform click-outside and host
38223
+ * comment seeks dismiss first, then seek; clamp does not apply then.
38224
+ *
38225
+ * @param {number} time
38226
+ * @return {number}
38227
+ */
38228
+ clampTimeToOpenCommentRange(time) {
38229
+ const range = this.getOpenCommentRangeSeconds();
38230
+ if (!range || !Number.isFinite(time)) {
38231
+ return time;
38232
+ }
38233
+ return Math.min(Math.max(time, range.startSec), range.endSec);
38234
+ }
38235
+ clearCommentRangeLoopTimer() {
38236
+ if (this.commentRangeLoopTimer) {
38237
+ window.clearTimeout(this.commentRangeLoopTimer);
38238
+ this.commentRangeLoopTimer = 0;
38239
+ }
38240
+ }
38241
+
38242
+ /**
38243
+ * Wrap when the remaining time to end elapses. Recalculate after play, seek,
38244
+ * rate change, or a draft resize.
38245
+ *
38246
+ * @param {boolean} [force] schedule even if the element still reports paused
38247
+ * @return {void}
38248
+ */
38249
+ scheduleCommentRangeLoopWrap(force = false) {
38250
+ this.clearCommentRangeLoopTimer();
38251
+ const range = this.getOpenCommentRangeSeconds();
38252
+ if (!range || !this.mediaEl || !force && this.mediaEl.paused) {
38253
+ return;
38254
+ }
38255
+ const rate = this.mediaEl.playbackRate > 0 ? this.mediaEl.playbackRate : 1;
38256
+ const remainingSec = range.endSec - this.mediaEl.currentTime;
38257
+ this.commentRangeLoopTimer = window.setTimeout(() => {
38258
+ this.commentRangeLoopTimer = 0;
38259
+ const openRange = this.getOpenCommentRangeSeconds();
38260
+ if (!openRange || !this.mediaEl || this.mediaEl.paused) {
38261
+ return;
38262
+ }
38263
+ if (this.mediaEl.currentTime !== openRange.startSec) {
38264
+ this.mediaEl.currentTime = openRange.startSec;
38265
+ }
38266
+ this.scheduleCommentRangeLoopWrap();
38267
+ }, Math.max(0, remainingSec / rate * 1000));
38268
+ }
38269
+
38270
+ /**
38271
+ * Update the playback loop to match the current range after it arrives, resizes,
38272
+ * or clears.
38273
+ *
38274
+ * @return {void}
38275
+ */
38276
+ syncCommentRangeLoop() {
38277
+ this.clearCommentRangeLoopTimer();
38278
+ const range = this.getOpenCommentRangeSeconds();
38279
+ if (!range || !this.mediaEl || this.mediaEl.paused) {
38280
+ return;
38281
+ }
38282
+ this.enforceCommentRangePlayback(range);
38283
+ }
38284
+
38285
+ /**
38286
+ * Keep playback inside the open span. A playhead outside [start, end) jumps
38287
+ * to start, then the wrap timer is armed.
38288
+ *
38289
+ * @param {{endSec: number, startSec: number}} range Open draft in seconds
38290
+ * @return {void}
38291
+ */
38292
+ enforceCommentRangePlayback(range) {
38293
+ const {
38294
+ endSec,
38295
+ startSec
38296
+ } = range;
38297
+ const currPlayhead = this.mediaEl.currentTime;
38298
+ const nextPlayhead = currPlayhead >= startSec && currPlayhead < endSec ? currPlayhead : startSec;
38299
+ if (nextPlayhead !== currPlayhead) {
38300
+ this.mediaEl.currentTime = nextPlayhead;
38301
+ }
38302
+ this.scheduleCommentRangeLoopWrap();
38303
+ }
38304
+
38305
+ /**
38306
+ * Play. An open draft loops that span: keep the playhead when it is already
38307
+ * inside, otherwise jump to start. Does not use play(start, end) because
38308
+ * that pauses at end.
38309
+ *
38310
+ * @inheritdoc
38311
+ */
38312
+ play(...args) {
38313
+ const range = this.getOpenCommentRangeSeconds();
38314
+ if (!range || !this.mediaEl) {
38315
+ // Spread so an argument-less call stays argument-less; super.play() skips
38316
+ // playback when it is handed an explicit undefined start.
38317
+ return super.play(...args);
38318
+ }
38319
+ const requested = Number.isFinite(args[0]) ? args[0] : this.mediaEl.currentTime;
38320
+ const next = requested >= range.startSec && requested < range.endSec ? requested : range.startSec;
38321
+ if (this.mediaEl.currentTime !== next) {
38322
+ this.mediaEl.currentTime = next;
38323
+ }
38324
+ const playPromise = super.play();
38325
+ this.scheduleCommentRangeLoopWrap(true);
38326
+ return playPromise;
38327
+ }
38328
+
38329
+ /**
38330
+ * Seek. Keyboard and in-range waveform seeks stay inside an open draft.
38331
+ *
38332
+ * @inheritdoc
38333
+ */
38334
+ setMediaTime(time) {
38335
+ super.setMediaTime(this.clampTimeToOpenCommentRange(time));
38336
+ this.scheduleCommentRangeLoopWrap();
38337
+ }
38338
+
38339
+ /**
38340
+ * If the range ends at file end, wrap instead of resetting the play icon.
38341
+ *
38342
+ * @inheritdoc
38343
+ */
38344
+ mediaendHandler() {
38345
+ if (this.getOpenCommentRangeSeconds()) {
38346
+ // `ended` fires after the element is paused; play() wraps and restarts.
38347
+ this.play();
38348
+ return;
38349
+ }
38350
+ super.mediaendHandler();
38351
+ }
36772
38352
  /**
36773
38353
  * @inheritdoc
36774
38354
  */
@@ -36806,9 +38386,19 @@ class MP3Viewer extends media_MediaBaseViewer {
36806
38386
  const Mp3ControlsV2 = this.MP3ControlsV2;
36807
38387
  this.controls.render(/*#__PURE__*/external_react_["default"].createElement(Mp3ControlsV2, MP3Viewer_extends({}, sharedProps, {
36808
38388
  commentMarkers: this.commentMarkers || [],
38389
+ commentRangeDraft: this.commentRangeDraft || null,
38390
+ hasStartedPlayback: !!this.userRequestedPlay,
38391
+ isGeneratingWaveform: this.isGeneratingWaveform(),
38392
+ keyboardVolumeStep: this.keyboardVolumeStep,
38393
+ keyboardZoomStep: this.keyboardZoomStep,
36809
38394
  mediaEl: this.mediaEl,
36810
38395
  onCommentMarkerClick: this.handleCommentMarkerClick,
36811
- peaks: this.waveformPeaks
38396
+ onCommentRangeChange: this.handleCommentRangeChange,
38397
+ onCommentRangeClear: this.handleCommentRangeClear,
38398
+ onCommentRangeDragChange: this.handleCommentRangeDragChange,
38399
+ onPlayNextChange: this.setPlayNext,
38400
+ peaks: this.waveformPeaks,
38401
+ playNext: this.isPlayNextEnabled()
36812
38402
  })));
36813
38403
  return;
36814
38404
  }
@@ -38404,7 +39994,7 @@ function VideoControls({
38404
39994
  onPlayPause: onPlayPause
38405
39995
  })), /*#__PURE__*/external_react_["default"].createElement("div", {
38406
39996
  className: "bp-VideoControls-group bp-VideoControls-group--media-controls"
38407
- }, /*#__PURE__*/external_react_["default"].createElement(VolumeControls/* default */.A, {
39997
+ }, /*#__PURE__*/external_react_["default"].createElement(VolumeControls/* default */.Ay, {
38408
39998
  onMuteChange: onMuteChange,
38409
39999
  onVolumeChange: onVolumeChange,
38410
40000
  volume: volume
@@ -38889,12 +40479,14 @@ function VideoControlsV2({
38889
40479
  onGuideChange,
38890
40480
  onMuteChange,
38891
40481
  onPlayPause,
40482
+ onPlayNextChange,
38892
40483
  onQualityChange,
38893
40484
  onRateChange,
38894
40485
  onSubtitleChange,
38895
40486
  onSubtitlesToggle,
38896
40487
  onTimeChange,
38897
40488
  onVolumeChange,
40489
+ playNext,
38898
40490
  quality,
38899
40491
  rate,
38900
40492
  subtitle,
@@ -38963,7 +40555,7 @@ function VideoControlsV2({
38963
40555
  mediaEl: mediaEl
38964
40556
  })), /*#__PURE__*/external_react_["default"].createElement("div", {
38965
40557
  className: "bp-VideoControlsV2-group bp-VideoControlsV2-group--right"
38966
- }, /*#__PURE__*/external_react_["default"].createElement(VolumeControls/* default */.A, {
40558
+ }, /*#__PURE__*/external_react_["default"].createElement(VolumeControls/* default */.Ay, {
38967
40559
  onMuteChange: onMuteChange,
38968
40560
  onVolumeChange: onVolumeChange,
38969
40561
  volume: volume
@@ -38983,9 +40575,11 @@ function VideoControlsV2({
38983
40575
  onAudioTrackChange: onAudioTrackChange,
38984
40576
  onAutoplayChange: onAutoplayChange,
38985
40577
  onGuideChange: onGuideChange,
40578
+ onPlayNextChange: onPlayNextChange,
38986
40579
  onQualityChange: onQualityChange,
38987
40580
  onRateChange: onRateChange,
38988
40581
  onSubtitleChange: onSubtitleChange,
40582
+ playNext: playNext,
38989
40583
  quality: quality,
38990
40584
  rate: rate,
38991
40585
  subtitle: subtitle,
@@ -39143,7 +40737,10 @@ class MP4Viewer extends media_VideoBaseViewer {
39143
40737
  volume: this.mediaEl.volume
39144
40738
  };
39145
40739
  if (this.isVideoPlayerV2) {
39146
- this.controls.render(/*#__PURE__*/external_react_["default"].createElement(VideoControlsV2, sharedProps));
40740
+ this.controls.render(/*#__PURE__*/external_react_["default"].createElement(VideoControlsV2, MP4Viewer_extends({}, sharedProps, {
40741
+ onPlayNextChange: this.setPlayNext,
40742
+ playNext: this.isPlayNextEnabled()
40743
+ })));
39147
40744
  return;
39148
40745
  }
39149
40746
  this.controls.render(/*#__PURE__*/external_react_["default"].createElement(VideoControls, MP4Viewer_extends({}, sharedProps, {
@@ -40639,7 +42236,10 @@ class DashViewer extends media_VideoBaseViewer {
40639
42236
  this.controls.render(/*#__PURE__*/external_react_["default"].createElement(VideoControlsV2, DashViewer_extends({
40640
42237
  commentMarkers: this.commentMarkers,
40641
42238
  onCommentMarkerClick: this.handleCommentMarkerClick
40642
- }, sharedProps)));
42239
+ }, sharedProps, {
42240
+ onPlayNextChange: this.setPlayNext,
42241
+ playNext: this.isPlayNextEnabled()
42242
+ })));
40643
42243
  return;
40644
42244
  }
40645
42245
  this.controls.render(/*#__PURE__*/external_react_["default"].createElement(VideoControls, DashViewer_extends({}, sharedProps, {
@@ -53307,6 +54907,7 @@ class Preview extends (events_default()) {
53307
54907
  this.viewer.destroy();
53308
54908
  }
53309
54909
  this.viewer = undefined;
54910
+ Preview.resin = null;
53310
54911
  }
53311
54912
 
53312
54913
  /**
@@ -54192,6 +55793,7 @@ class Preview extends (events_default()) {
54192
55793
 
54193
55794
  // Optional resin analytics instance for tracking user interactions
54194
55795
  this.options.resin = options.resin;
55796
+ Preview.resin = options.resin;
54195
55797
 
54196
55798
  // Options that are applicable to certain file ids
54197
55799
  this.options.fileOptions = options.fileOptions || {};
@@ -54567,6 +56169,10 @@ class Preview extends (events_default()) {
54567
56169
  this.emit(data.event, data.data);
54568
56170
  this.emit(lib_events/* VIEWER_EVENT */.Oi.default, data);
54569
56171
  break;
56172
+ case lib_events/* VIEWER_EVENT */.Oi.mediaEndPlayNext:
56173
+ this.emit(data.event, data.data);
56174
+ this.emit(lib_events/* VIEWER_EVENT */.Oi.default, data);
56175
+ break;
54570
56176
  case lib_events/* VIEWER_EVENT */.Oi.error:
54571
56177
  // Do nothing since 'error' event was already caught, and will be emitted
54572
56178
  // as a 'preview_error' event