box-content-preview 3.92.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.92.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
@@ -3069,6 +3112,13 @@ function VolumeSliderControl(_ref) {
3069
3112
  rest = _objectWithoutProperties(_ref, _excluded);
3070
3113
  const [isScrubbing, setIsScrubbing] = external_react_["default"].useState(false);
3071
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
+ };
3072
3122
  const getPositionRelativeToSlider = external_react_["default"].useCallback(clientY => {
3073
3123
  const {
3074
3124
  current: sliderEl
@@ -3100,10 +3150,12 @@ function VolumeSliderControl(_ref) {
3100
3150
  const key = (0,util/* decodeKeydown */.wU)(event);
3101
3151
  if (key === 'ArrowDown') {
3102
3152
  event.stopPropagation(); // Prevents global key handling
3153
+ recordScrub();
3103
3154
  onUpdate(Math.max(min, Math.min(value - step, max)));
3104
3155
  }
3105
3156
  if (key === 'ArrowUp') {
3106
3157
  event.stopPropagation(); // Prevents global key handling
3158
+ recordScrub();
3107
3159
  onUpdate(Math.max(min, Math.min(value + step, max)));
3108
3160
  }
3109
3161
  };
@@ -3116,6 +3168,7 @@ function VolumeSliderControl(_ref) {
3116
3168
  clientY
3117
3169
  } = event;
3118
3170
  if (button > 1 || ctrlKey || metaKey) return;
3171
+ recordScrub();
3119
3172
  onUpdate(getPositionValue(pageY, clientY));
3120
3173
  setIsScrubbing(true);
3121
3174
  // Prevent clicking on the slider from triggering the mouse down event on the parent slider track
@@ -3138,6 +3191,7 @@ function VolumeSliderControl(_ref) {
3138
3191
  const handleTouchStart = ({
3139
3192
  touches
3140
3193
  }) => {
3194
+ recordScrub();
3141
3195
  onUpdate(getPositionValue(touches[0].pageY, touches[0].clientY));
3142
3196
  setIsScrubbing(true);
3143
3197
  };
@@ -4179,7 +4233,14 @@ function SettingsRadioItem(props, ref) {
4179
4233
  value
4180
4234
  } = props;
4181
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
+ };
4182
4242
  const handleClick = () => {
4243
+ recordAction?.(settingsRadioItemAction);
4183
4244
  onChange(value);
4184
4245
  };
4185
4246
  const handleKeydown = event => {
@@ -4187,6 +4248,7 @@ function SettingsRadioItem(props, ref) {
4187
4248
  if (key !== 'ArrowLeft' && key !== 'Enter' && key !== 'Space') {
4188
4249
  return;
4189
4250
  }
4251
+ recordAction?.(settingsRadioItemAction);
4190
4252
  onChange(value);
4191
4253
  };
4192
4254
  return /*#__PURE__*/external_react_["default"].createElement("div", {
@@ -4339,6 +4401,7 @@ let Menu = /*#__PURE__*/function (Menu) {
4339
4401
  Menu["AUDIO"] = "audio";
4340
4402
  Menu["AUTOPLAY"] = "autoplay";
4341
4403
  Menu["GUIDES"] = "guides";
4404
+ Menu["PLAY_NEXT"] = "playnext";
4342
4405
  Menu["QUALITY"] = "quality";
4343
4406
  Menu["RATE"] = "rate";
4344
4407
  Menu["SUBTITLES"] = "subtitles";
@@ -4375,7 +4438,7 @@ var util = __webpack_require__(4410);
4375
4438
  // extracted by mini-css-extract-plugin
4376
4439
 
4377
4440
  ;// ./src/lib/viewers/controls/slider/SliderControl.tsx
4378
- 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"];
4379
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); }
4380
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; }
4381
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; }
@@ -4387,6 +4450,7 @@ function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t =
4387
4450
  function SliderControl(_ref) {
4388
4451
  let {
4389
4452
  className,
4453
+ 'data-resin-target': resinTarget,
4390
4454
  max = 100,
4391
4455
  min = 0,
4392
4456
  onMove = (noop_default()),
@@ -4399,6 +4463,16 @@ function SliderControl(_ref) {
4399
4463
  rest = _objectWithoutProperties(_ref, _excluded);
4400
4464
  const [isScrubbing, setIsScrubbing] = external_react_["default"].useState(false);
4401
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
+ };
4402
4476
  const getPosition = external_react_["default"].useCallback(pageX => {
4403
4477
  const {
4404
4478
  current: sliderEl
@@ -4425,10 +4499,12 @@ function SliderControl(_ref) {
4425
4499
  const key = (0,util/* decodeKeydown */.wU)(event);
4426
4500
  if (key === 'ArrowLeft') {
4427
4501
  event.stopPropagation(); // Prevents global key handling
4502
+ recordScrub();
4428
4503
  onUpdate(Math.max(min, Math.min(value - step, max)));
4429
4504
  }
4430
4505
  if (key === 'ArrowRight') {
4431
4506
  event.stopPropagation(); // Prevents global key handling
4507
+ recordScrub();
4432
4508
  onUpdate(Math.max(min, Math.min(value + step, max)));
4433
4509
  }
4434
4510
  };
@@ -4439,6 +4515,7 @@ function SliderControl(_ref) {
4439
4515
  pageX
4440
4516
  }) => {
4441
4517
  if (button > 1 || ctrlKey || metaKey) return;
4518
+ recordScrub();
4442
4519
  onUpdate(getPositionValue(pageX));
4443
4520
  setIsScrubbing(true);
4444
4521
  };
@@ -4458,6 +4535,7 @@ function SliderControl(_ref) {
4458
4535
  const handleTouchStart = ({
4459
4536
  touches
4460
4537
  }) => {
4538
+ recordScrub();
4461
4539
  onUpdate(getPositionValue(touches[0].pageX));
4462
4540
  setIsScrubbing(true);
4463
4541
  };
@@ -4495,6 +4573,7 @@ function SliderControl(_ref) {
4495
4573
  className: classnames_default()('bp-SliderControl', className, {
4496
4574
  'bp-is-scrubbing': isScrubbing
4497
4575
  }),
4576
+ "data-resin-target": resinTarget,
4498
4577
  onKeyDown: handleKeydown,
4499
4578
  onMouseDown: handleMouseDown,
4500
4579
  onMouseMove: handleMouseMove,
@@ -4530,7 +4609,7 @@ function getPdfjsWorkerSrc() {
4530
4609
 
4531
4610
  /***/ },
4532
4611
 
4533
- /***/ 7450
4612
+ /***/ 673
4534
4613
  (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
4535
4614
 
4536
4615
 
@@ -4546,8 +4625,8 @@ var external_react_ = __webpack_require__(1649);
4546
4625
  // EXTERNAL MODULE: ./node_modules/lodash/isFinite.js
4547
4626
  var lodash_isFinite = __webpack_require__(2297);
4548
4627
  var isFinite_default = /*#__PURE__*/__webpack_require__.n(lodash_isFinite);
4549
- // EXTERNAL MODULE: ./src/lib/viewers/controls/media/MediaSettings.tsx + 5 modules
4550
- var MediaSettings = __webpack_require__(6929);
4628
+ // EXTERNAL MODULE: ./src/lib/viewers/controls/media/MediaSettings.tsx + 6 modules
4629
+ var MediaSettings = __webpack_require__(9229);
4551
4630
  // EXTERNAL MODULE: ./src/lib/viewers/controls/media/PlayPauseToggle.tsx + 1 modules
4552
4631
  var PlayPauseToggle = __webpack_require__(6321);
4553
4632
  // EXTERNAL MODULE: ./src/lib/viewers/controls/media/TimestampControl.tsx + 1 modules
@@ -4654,321 +4733,8 @@ function morphPeaks(from, to, elapsedMs) {
4654
4733
  }
4655
4734
  return out;
4656
4735
  }
4657
- ;// ./src/lib/viewers/media/waveform/viewport.ts
4658
- 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; }
4659
- 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; }
4660
- 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; }
4661
- function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
4662
- 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); }
4663
-
4664
- /** Visible window: start/end times, pixels-per-second, and the current zoom. */
4665
- function createWaveformViewport({
4666
- durationSec,
4667
- gutterPx = 0,
4668
- heightPx,
4669
- maxZoom,
4670
- scrollLeftPx,
4671
- widthPx,
4672
- zoomLevel
4673
- }) {
4674
- const zoom = Number.isFinite(zoomLevel) && zoomLevel > 0 ? zoomLevel : constants/* WAVEFORM_ZOOM_MIN */.LW;
4675
- const viewDurationSec = durationSec > 0 ? durationSec / zoom : 0; // seconds visible at this zoom
4676
- const pixelsPerSecond = viewDurationSec > 0 && widthPx > 0 ? widthPx / viewDurationSec : 0;
4677
- const leadingGutterPx = Number.isFinite(gutterPx) && gutterPx > 0 ? gutterPx : 0;
4678
- const unclampedStartSec = pixelsPerSecond > 0 ? (scrollLeftPx - leadingGutterPx) / pixelsPerSecond : 0;
4679
- const gutterDurationSec = pixelsPerSecond > 0 ? leadingGutterPx / pixelsPerSecond : 0;
4680
- const latestWindowStartSec = leadingGutterPx > 0 ? durationSec + gutterDurationSec - viewDurationSec : Math.max(0, durationSec - viewDurationSec);
4681
- const earliestWindowStartSec = leadingGutterPx > 0 ? -gutterDurationSec : 0;
4682
- const startSec = pixelsPerSecond > 0 ? Math.min(latestWindowStartSec, Math.max(earliestWindowStartSec, unclampedStartSec)) : 0;
4683
- return {
4684
- durationSec,
4685
- endSec: startSec + viewDurationSec,
4686
- gutterPx: leadingGutterPx,
4687
- heightPx,
4688
- maxZoom,
4689
- pixelsPerSecond,
4690
- scrollLeftPx,
4691
- startSec,
4692
- widthPx,
4693
- zoomLevel: zoom
4694
- };
4695
- }
4696
-
4697
- /** Same viewport with a different scroll, so start/end times update. */
4698
- function getViewportAtScroll(viewport, scrollLeftPx) {
4699
- return createWaveformViewport(_objectSpread(_objectSpread({}, viewport), {}, {
4700
- scrollLeftPx
4701
- }));
4702
- }
4703
-
4704
- /** True when the visible window has not moved — skip a React state update. */
4705
- function viewportEquals(prev, next) {
4706
- 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;
4707
- }
4708
-
4709
- /** How many CSS pixels from the left of the visible window this time sits. */
4710
- function positionPxFromTime(timeSec, viewport) {
4711
- return (timeSec - viewport.startSec) * viewport.pixelsPerSecond;
4712
- }
4713
-
4714
- /** Media time under a point this many CSS pixels from the left of the visible window. */
4715
- function timeFromPositionPx(positionPx, viewport) {
4716
- if (!(viewport.pixelsPerSecond > 0)) {
4717
- return viewport.startSec;
4718
- }
4719
- return viewport.startSec + positionPx / viewport.pixelsPerSecond;
4720
- }
4721
-
4722
- /** True when this time sits inside the visible window. */
4723
- function isTimeInView(timeSec, viewport) {
4724
- return timeSec >= viewport.startSec && timeSec <= viewport.endSec;
4725
- }
4726
-
4727
- /** UI max zoom: 24×, ~1 peak per CSS pixel, and a minimum visible window. */
4728
- function getWaveformZoomMax({
4729
- durationSec,
4730
- peakCount,
4731
- viewWidthPx
4732
- }) {
4733
- if (!(peakCount > 0) || !(viewWidthPx > 0)) {
4734
- return constants/* WAVEFORM_ZOOM_MIN */.LW;
4735
- }
4736
- const peakLimitedMax = Math.floor(peakCount / viewWidthPx);
4737
- const durationLimitedMax = durationSec > 0 ? durationSec / constants/* WAVEFORM_MIN_VIEW_WINDOW_SEC */.Kl : constants/* WAVEFORM_ZOOM_MIN */.LW;
4738
- return Math.max(constants/* WAVEFORM_ZOOM_MIN */.LW, Math.min(constants/* WAVEFORM_ZOOM_MAX */.tK, peakLimitedMax, durationLimitedMax));
4739
- }
4740
-
4741
- /** Keep zoom between 1× and this file's max. */
4742
- function clampWaveformZoom(zoomLevel, maxZoom = constants/* WAVEFORM_ZOOM_MIN */.LW) {
4743
- const max = Math.max(constants/* WAVEFORM_ZOOM_MIN */.LW, maxZoom);
4744
- if (!Number.isFinite(zoomLevel)) {
4745
- return constants/* WAVEFORM_ZOOM_MIN */.LW;
4746
- }
4747
- return Math.min(max, Math.max(constants/* WAVEFORM_ZOOM_MIN */.LW, zoomLevel));
4748
- }
4749
-
4750
- /** WaveSurfer zoom density. 0 = fit the whole file in the view. */
4751
- function getZoomedPixelsPerSecond({
4752
- durationSec,
4753
- isTape = false,
4754
- maxZoom = constants/* WAVEFORM_ZOOM_MIN */.LW,
4755
- viewWidthPx,
4756
- zoomLevel
4757
- }) {
4758
- const zoom = clampWaveformZoom(zoomLevel, maxZoom);
4759
- if (!(durationSec > 0) || !(viewWidthPx > 0)) {
4760
- return 0;
4761
- }
4762
- if (!isTape && zoom <= constants/* WAVEFORM_ZOOM_MIN */.LW) {
4763
- return 0;
4764
- }
4765
- return viewWidthPx / durationSec * zoom;
4766
- }
4767
-
4768
- /** Half-view empty space so t=0 and duration can sit under the center pin. */
4769
- function getTapeGutterPx(widthPx) {
4770
- return widthPx > 0 ? widthPx / 2 : 0;
4771
- }
4772
-
4773
- /** Tape first paint: 10s on screen, or 1× when the file is shorter than that. */
4774
- function getTapeDefaultZoom(durationSec, maxZoom = constants/* WAVEFORM_ZOOM_MIN */.LW) {
4775
- if (!(durationSec > 0)) {
4776
- return constants/* WAVEFORM_ZOOM_MIN */.LW;
4777
- }
4778
- return clampWaveformZoom(durationSec / constants/* WAVEFORM_TAPE_DEFAULT_WINDOW_SEC */.Av, maxZoom);
4779
- }
4780
-
4781
- /** Map zoom (1…max) onto the 0–100 slider. */
4782
- function sliderValueFromZoom(zoomLevel, maxZoom = constants/* WAVEFORM_ZOOM_MIN */.LW) {
4783
- const max = Math.max(constants/* WAVEFORM_ZOOM_MIN */.LW, maxZoom);
4784
- const zoom = clampWaveformZoom(zoomLevel, max);
4785
- if (max <= constants/* WAVEFORM_ZOOM_MIN */.LW) {
4786
- return 0;
4787
- }
4788
- return (zoom - constants/* WAVEFORM_ZOOM_MIN */.LW) / (max - constants/* WAVEFORM_ZOOM_MIN */.LW) * constants/* WAVEFORM_ZOOM_SLIDER_MAX */.nh;
4789
- }
4790
-
4791
- /** Inverse of sliderValueFromZoom. */
4792
- function zoomFromSliderValue(value, maxZoom = constants/* WAVEFORM_ZOOM_MIN */.LW) {
4793
- const max = Math.max(constants/* WAVEFORM_ZOOM_MIN */.LW, maxZoom);
4794
- if (max <= constants/* WAVEFORM_ZOOM_MIN */.LW) {
4795
- return constants/* WAVEFORM_ZOOM_MIN */.LW;
4796
- }
4797
- const t = Math.min(constants/* WAVEFORM_ZOOM_SLIDER_MAX */.nh, Math.max(0, value)) / constants/* WAVEFORM_ZOOM_SLIDER_MAX */.nh;
4798
- return clampWaveformZoom(constants/* WAVEFORM_ZOOM_MIN */.LW + t * (max - constants/* WAVEFORM_ZOOM_MIN */.LW), max);
4799
- }
4800
-
4801
- /** One zoom-in/out button step on the 0–100 slider, mapped back to zoom. */
4802
- function stepWaveformZoom(zoomLevel, maxZoom, sliderDelta) {
4803
- return zoomFromSliderValue(Math.round(sliderValueFromZoom(zoomLevel, maxZoom)) + sliderDelta, maxZoom);
4804
- }
4805
-
4806
- /** Max scroll that still shows a full window (no overscroll). Gutters add empty lead/trail. */
4807
- function maxScrollLeft(viewport) {
4808
- const gutterPx = viewport.gutterPx || 0;
4809
- return Math.max(0, viewport.durationSec * viewport.pixelsPerSecond + 2 * gutterPx - viewport.widthPx);
4810
- }
4811
-
4812
- /** Scroll offset that still shows a full window (no overscroll). */
4813
- function clampScrollLeft(scrollLeftPx, viewport) {
4814
- if (!Number.isFinite(scrollLeftPx)) {
4815
- return 0;
4816
- }
4817
- return Math.min(maxScrollLeft(viewport), Math.max(0, scrollLeftPx));
4818
- }
4819
-
4820
- /** Center this time in the view, clamped so the window still fills the canvas. */
4821
- function getCenteredScrollLeft(timeSec, viewport) {
4822
- return clampScrollLeft(timeSec * viewport.pixelsPerSecond + (viewport.gutterPx || 0) - viewport.widthPx / 2, viewport);
4823
- }
4824
-
4825
- /**
4826
- * Host/media seek while zoomed: jump so the time (and its comment avatar) is on screen.
4827
- * No-op at fit-to-width or when that time is already visible.
4828
- */
4829
- function getSeekCameraAction({
4830
- cameraMode = 'desktop',
4831
- timeSec,
4832
- viewport
4833
- }) {
4834
- if (!(viewport.widthPx > 0) || !(viewport.pixelsPerSecond > 0)) {
4835
- return {
4836
- type: 'none'
4837
- };
4838
- }
4839
- if (cameraMode === 'tape') {
4840
- const scrollLeftPx = getCenteredScrollLeft(timeSec, viewport);
4841
- if (Math.abs(scrollLeftPx - viewport.scrollLeftPx) < 1) {
4842
- return {
4843
- type: 'none'
4844
- };
4845
- }
4846
- return {
4847
- type: 'jump',
4848
- scrollLeftPx
4849
- };
4850
- }
4851
- if (viewport.zoomLevel <= constants/* WAVEFORM_ZOOM_MIN */.LW) {
4852
- return {
4853
- type: 'none'
4854
- };
4855
- }
4856
- if (isTimeInView(timeSec, viewport)) {
4857
- return {
4858
- type: 'none'
4859
- };
4860
- }
4861
- return {
4862
- type: 'jump',
4863
- scrollLeftPx: getCenteredScrollLeft(timeSec, viewport)
4864
- };
4865
- }
4866
-
4867
- /** Always keep this time under the center pin. Reuses followRight so the camera hook can pin. */
4868
- function getTapeCameraAction({
4869
- timeSec,
4870
- viewport
4871
- }) {
4872
- if (!(viewport.widthPx > 0) || !(viewport.pixelsPerSecond > 0)) {
4873
- return {
4874
- type: 'none'
4875
- };
4876
- }
4877
- return {
4878
- isPlayheadPinned: true,
4879
- scrollLeftPx: getCenteredScrollLeft(timeSec, viewport),
4880
- type: 'followRight'
4881
- };
4882
- }
4883
-
4884
- /** Follow inset in CSS px, capped at one third of the view so a narrow player still has room. */
4885
- function getFollowInsetPx(widthPx, insetPx = constants/* WAVEFORM_FOLLOW_INSET_PX */.NM) {
4886
- if (!(widthPx > 0)) {
4887
- return 0;
4888
- }
4889
- return Math.min(Math.max(0, insetPx), widthPx / 3);
4890
- }
4891
-
4892
- /** CSS left for a playhead pinned to the follow inset. */
4893
- function getPinnedPlayheadLeft(widthPx, insetPx = constants/* WAVEFORM_FOLLOW_INSET_PX */.NM) {
4894
- const inset = getFollowInsetPx(widthPx, insetPx);
4895
- if (!(widthPx > 0)) {
4896
- return '0%';
4897
- }
4898
- return `${(widthPx - inset) / widthPx * 100}%`;
4899
- }
4900
-
4901
- /** CSS left for a playhead locked to the center of the view. */
4902
- function getTapePinnedPlayheadLeft() {
4903
- return '50%';
4904
- }
4905
-
4906
- /** CSS left % of the playhead from the left of the visible window. */
4907
- function timeLeftPercent(timeSec, durationSec, viewport) {
4908
- if (viewport.widthPx > 0 && viewport.pixelsPerSecond > 0) {
4909
- return `${positionPxFromTime(timeSec, viewport) / viewport.widthPx * 100}%`;
4910
- }
4911
- const progress = durationSec > 0 ? Math.min(1, Math.max(0, timeSec / durationSec)) : 0;
4912
- return `${progress * 100}%`;
4913
- }
4914
-
4915
- /**
4916
- * Walk across the view until the playhead nears the right edge, then follow.
4917
- * Off-screen at play start jumps in; off-screen right while playing keeps following.
4918
- */
4919
- function getPlayheadCameraAction({
4920
- cameraMode = 'desktop',
4921
- insetPx = constants/* WAVEFORM_FOLLOW_INSET_PX */.NM,
4922
- isPlaying,
4923
- playJustStarted,
4924
- timeSec,
4925
- viewport
4926
- }) {
4927
- if (cameraMode === 'tape') {
4928
- return getTapeCameraAction({
4929
- timeSec,
4930
- viewport
4931
- });
4932
- }
4933
- if (!isPlaying || viewport.zoomLevel <= constants/* WAVEFORM_ZOOM_MIN */.LW || !(viewport.widthPx > 0) || !(viewport.pixelsPerSecond > 0)) {
4934
- return {
4935
- type: 'none'
4936
- };
4937
- }
4938
- const inset = getFollowInsetPx(viewport.widthPx, insetPx);
4939
- const viewX = positionPxFromTime(timeSec, viewport);
4940
- const playheadCanvasX = timeSec * viewport.pixelsPerSecond;
4941
- const followX = viewport.widthPx - inset;
4942
- if (viewX < 0) {
4943
- if (!playJustStarted) {
4944
- return {
4945
- type: 'none'
4946
- };
4947
- }
4948
- return {
4949
- type: 'jump',
4950
- scrollLeftPx: clampScrollLeft(playheadCanvasX - inset, viewport)
4951
- };
4952
- }
4953
- if (viewX >= followX) {
4954
- const unclampedScroll = playheadCanvasX - followX;
4955
- const scrollLeftPx = clampScrollLeft(unclampedScroll, viewport);
4956
- if (playJustStarted && viewX > viewport.widthPx) {
4957
- return {
4958
- type: 'jump',
4959
- scrollLeftPx
4960
- };
4961
- }
4962
- return {
4963
- type: 'followRight',
4964
- isPlayheadPinned: unclampedScroll <= maxScrollLeft(viewport),
4965
- scrollLeftPx
4966
- };
4967
- }
4968
- return {
4969
- type: 'none'
4970
- };
4971
- }
4736
+ // EXTERNAL MODULE: ./src/lib/viewers/media/waveform/viewport.ts
4737
+ var waveform_viewport = __webpack_require__(772);
4972
4738
  ;// ./src/lib/viewers/media/waveform/useTapeWaveform.ts
4973
4739
 
4974
4740
 
@@ -5170,6 +4936,32 @@ function WaveformCommentMarkers({
5170
4936
  }));
5171
4937
  })));
5172
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
+ }
5173
4965
  ;// external "react-dom"
5174
4966
 
5175
4967
  ;// ./node_modules/wavesurfer.js/dist/wavesurfer.esm.js
@@ -5496,200 +5288,8 @@ function tintWaveformTiles({
5496
5288
  });
5497
5289
  });
5498
5290
  }
5499
- ;// ./src/lib/viewers/media/waveform/range.ts
5500
-
5501
-
5502
- function clampToRange(value, min, max) {
5503
- if (!Number.isFinite(value)) {
5504
- return min;
5505
- }
5506
- return Math.min(max, Math.max(min, value));
5507
- }
5508
-
5509
- /** Integer ms in `[0, durationMs]`. */
5510
- function clampTimeMs(timeMs, durationMs) {
5511
- const duration = Math.max(0, durationMs);
5512
- return Math.round(clampToRange(timeMs, 0, duration));
5513
- }
5514
- function durationMsFromSec(durationSec) {
5515
- if (!Number.isFinite(durationSec) || durationSec <= 0) {
5516
- return 0;
5517
- }
5518
- return Math.round(durationSec * 1000);
5519
- }
5520
- function isRangeCollapsed(range) {
5521
- return !range || range.endMs == null || range.endMs <= range.startMs;
5522
- }
5523
-
5524
- /** Clamp both edges onto the file. Collapsed drafts stay a point. */
5525
- function resolveRange(range, durationMs) {
5526
- const startMs = clampTimeMs(range.startMs, durationMs);
5527
- if (range.endMs == null) {
5528
- return {
5529
- endMs: startMs,
5530
- startMs
5531
- };
5532
- }
5533
- const endMs = clampTimeMs(range.endMs, durationMs);
5534
- if (endMs <= startMs) {
5535
- return {
5536
- endMs: startMs,
5537
- startMs
5538
- };
5539
- }
5540
- return {
5541
- endMs,
5542
- startMs
5543
- };
5544
- }
5545
-
5546
- /**
5547
- * Which handle should receive this pointer. Overlapping hit areas (collapsed or
5548
- * a span narrower than the 24px target) split at the midpoint so each edge stays reachable.
5549
- */
5550
- function pickRangeHandle({
5551
- endX,
5552
- pointerX,
5553
- startX
5554
- }) {
5555
- const mid = (startX + endX) / 2;
5556
- if (!Number.isFinite(mid)) {
5557
- return 'end';
5558
- }
5559
- return pointerX < mid ? 'start' : 'end';
5560
- }
5561
-
5562
- /** Screen x of a handle. Collapsed drafts sit 1px left/right of the playhead, not on it. */
5563
- function visualRangeHandlePx({
5564
- handle,
5565
- range,
5566
- viewport
5567
- }) {
5568
- const timeSec = (handle === 'start' ? range.startMs : range.endMs) / 1000;
5569
- const x = positionPxFromTime(timeSec, viewport);
5570
- if (range.startMs !== range.endMs) {
5571
- return x;
5572
- }
5573
- return handle === 'start' ? x - constants/* WAVEFORM_RANGE_COLLAPSED_OFFSET_PX */.BG : x + constants/* WAVEFORM_RANGE_COLLAPSED_OFFSET_PX */.BG;
5574
- }
5575
-
5576
- /** True when the pointer is over the range rectangle or its handle hit targets. */
5577
- function isPointerOverRange({
5578
- pointerX,
5579
- range,
5580
- viewport
5581
- }) {
5582
- const resolved = resolveRange(range, durationMsFromSec(viewport.durationSec));
5583
- const startX = visualRangeHandlePx({
5584
- handle: 'start',
5585
- range: resolved,
5586
- viewport
5587
- });
5588
- const endX = visualRangeHandlePx({
5589
- handle: 'end',
5590
- range: resolved,
5591
- viewport
5592
- });
5593
- const pad = constants/* WAVEFORM_RANGE_HIT_AREA_PX */.r0 / 2;
5594
- return pointerX >= Math.min(startX, endX) - pad && pointerX <= Math.max(startX, endX) + pad;
5595
- }
5596
-
5597
- /** Snap to the playhead when the pointer is within `WAVEFORM_RANGE_SNAP_PX`. */
5598
- function snapTimeMs({
5599
- pixelsPerSecond,
5600
- playheadMs,
5601
- timeMs
5602
- }) {
5603
- if (!(pixelsPerSecond > 0) || !Number.isFinite(playheadMs)) {
5604
- return timeMs;
5605
- }
5606
- const deltaPx = Math.abs(timeMs - playheadMs) / 1000 * pixelsPerSecond;
5607
- if (deltaPx <= constants/* WAVEFORM_RANGE_SNAP_PX */.IQ) {
5608
- return Math.round(playheadMs);
5609
- }
5610
- return timeMs;
5611
- }
5612
-
5613
- /**
5614
- * Move one edge. Crossing the other edge swaps roles so a collapsed pair can
5615
- * expand in either direction. Enforces `[0, duration]` and the minimum span
5616
- * once the drag has actually opened a range. An already-open range stays a
5617
- * span — snapping one edge onto the other clamps to the minimum duration.
5618
- */
5619
- function dragRangeHandle({
5620
- durationMs,
5621
- handle,
5622
- pointerMs,
5623
- range
5624
- }) {
5625
- const duration = Math.max(0, durationMs);
5626
- const pointer = clampTimeMs(pointerMs, duration);
5627
- let {
5628
- startMs,
5629
- endMs
5630
- } = range;
5631
- let nextHandle = handle;
5632
- if (handle === 'start') {
5633
- if (pointer > endMs) {
5634
- startMs = endMs;
5635
- endMs = pointer;
5636
- nextHandle = 'end';
5637
- } else {
5638
- startMs = pointer;
5639
- }
5640
- } else if (pointer < startMs) {
5641
- endMs = startMs;
5642
- startMs = pointer;
5643
- nextHandle = 'start';
5644
- } else {
5645
- endMs = pointer;
5646
- }
5647
- const wasOpen = range.startMs !== range.endMs;
5648
- const opened = wasOpen || startMs !== endMs;
5649
- if (opened && endMs - startMs < constants/* WAVEFORM_RANGE_MIN_DURATION_MS */.bX) {
5650
- if (nextHandle === 'end') {
5651
- endMs = clampTimeMs(startMs + constants/* WAVEFORM_RANGE_MIN_DURATION_MS */.bX, duration);
5652
- if (endMs - startMs < constants/* WAVEFORM_RANGE_MIN_DURATION_MS */.bX) {
5653
- startMs = clampTimeMs(endMs - constants/* WAVEFORM_RANGE_MIN_DURATION_MS */.bX, duration);
5654
- }
5655
- } else {
5656
- startMs = clampTimeMs(endMs - constants/* WAVEFORM_RANGE_MIN_DURATION_MS */.bX, duration);
5657
- if (endMs - startMs < constants/* WAVEFORM_RANGE_MIN_DURATION_MS */.bX) {
5658
- endMs = clampTimeMs(startMs + constants/* WAVEFORM_RANGE_MIN_DURATION_MS */.bX, duration);
5659
- }
5660
- }
5661
- }
5662
- return {
5663
- handle: nextHandle,
5664
- range: {
5665
- endMs,
5666
- startMs
5667
- }
5668
- };
5669
- }
5670
- function pointerTimeMs(pointerX, viewport) {
5671
- return Math.round(timeFromPositionPx(pointerX, viewport) * 1000);
5672
- }
5673
-
5674
- /** Sidebar payload after mouseup. Null when the draft is still a single timestamp. */
5675
- function commitRangeChange(range) {
5676
- if (range.endMs <= range.startMs) {
5677
- return null;
5678
- }
5679
- return {
5680
- endMs: range.endMs,
5681
- startMs: range.startMs
5682
- };
5683
- }
5684
- function rangeProgress(range, durationMs) {
5685
- if (!(durationMs > 0) || range.endMs <= range.startMs) {
5686
- return null;
5687
- }
5688
- return {
5689
- end: range.endMs / durationMs,
5690
- start: range.startMs / durationMs
5691
- };
5692
- }
5291
+ // EXTERNAL MODULE: ./src/lib/viewers/media/waveform/range.ts
5292
+ var waveform_range = __webpack_require__(4253);
5693
5293
  ;// ./src/lib/viewers/media/waveform/usePlayheadCamera.ts
5694
5294
 
5695
5295
 
@@ -5783,7 +5383,7 @@ function usePlayheadCamera({
5783
5383
  wavesurfer.setScroll(scrollLeftPx);
5784
5384
  const appliedScrollLeft = getScrollLeft(wavesurfer, scrollLeftPx);
5785
5385
  lastCameraScrollRef.current = appliedScrollLeft;
5786
- viewportRef.current = getViewportAtScroll(viewportRef.current, appliedScrollLeft);
5386
+ viewportRef.current = (0,waveform_viewport/* getViewportAtScroll */.NI)(viewportRef.current, appliedScrollLeft);
5787
5387
  window.requestAnimationFrame(() => {
5788
5388
  // Keep the flag through delayed `scroll` while the camera is driving.
5789
5389
  if (jumpAnimationRef.current || isFollowPinnedRef.current) {
@@ -5800,7 +5400,7 @@ function usePlayheadCamera({
5800
5400
  if (!playhead || !isPinned || !(viewport.widthPx > 0)) {
5801
5401
  return;
5802
5402
  }
5803
- 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);
5804
5404
  }, [isTape]);
5805
5405
  const apply = (0,external_react_.useCallback)((timeSec, playJustStarted = false) => {
5806
5406
  mediaTimeRef.current = timeSec;
@@ -5819,8 +5419,8 @@ function usePlayheadCamera({
5819
5419
  if (jumpAnimationRef.current && !playJustStarted) {
5820
5420
  return;
5821
5421
  }
5822
- const liveViewport = getViewportAtScroll(viewport, getScrollLeft(wavesurfer, viewport.scrollLeftPx));
5823
- const action = getPlayheadCameraAction({
5422
+ const liveViewport = (0,waveform_viewport/* getViewportAtScroll */.NI)(viewport, getScrollLeft(wavesurfer, viewport.scrollLeftPx));
5423
+ const action = (0,waveform_viewport/* getPlayheadCameraAction */.aJ)({
5824
5424
  cameraMode: cameraModeRef.current,
5825
5425
  isPlaying: true,
5826
5426
  playJustStarted,
@@ -5858,12 +5458,12 @@ function usePlayheadCamera({
5858
5458
  const start = (0,util/* getCurrentTimeMs */.RU)();
5859
5459
  const tick = now => {
5860
5460
  const t = Math.min(1, (now - start) / constants/* WAVEFORM_PLAYHEAD_JUMP_MS */.Bq);
5861
- const liveAction = getPlayheadCameraAction({
5461
+ const liveAction = (0,waveform_viewport/* getPlayheadCameraAction */.aJ)({
5862
5462
  cameraMode: cameraModeRef.current,
5863
5463
  isPlaying: true,
5864
5464
  playJustStarted: true,
5865
5465
  timeSec: mediaTimeRef.current,
5866
- viewport: getViewportAtScroll(viewportRef.current, getScrollLeft(wavesurfer, from))
5466
+ viewport: (0,waveform_viewport/* getViewportAtScroll */.NI)(viewportRef.current, getScrollLeft(wavesurfer, from))
5867
5467
  });
5868
5468
  const to = liveAction.type === 'none' ? action.scrollLeftPx : liveAction.scrollLeftPx;
5869
5469
  applyScrollLeft(from + (to - from) * (1 - (1 - t) * (1 - t)), false);
@@ -5886,7 +5486,7 @@ function usePlayheadCamera({
5886
5486
  const isUserPan = (0,external_react_.useCallback)(scrollLeftPx => {
5887
5487
  const isCameraOwnedScroll = programmaticScrollRef.current || jumpAnimationRef.current !== 0;
5888
5488
  const lastCameraScroll = lastCameraScrollRef.current;
5889
- const maxScroll = maxScrollLeft(viewportRef.current);
5489
+ const maxScroll = (0,waveform_viewport/* maxScrollLeft */.d0)(viewportRef.current);
5890
5490
  const isClampedToMax = lastCameraScroll != null && scrollLeftPx >= maxScroll - 1 && lastCameraScroll >= maxScroll - 1;
5891
5491
  const isFollowCameraScroll = isFollowPinnedRef.current && lastCameraScroll != null && Math.abs(scrollLeftPx - lastCameraScroll) <= 1;
5892
5492
  return !(isCameraOwnedScroll && (jumpAnimationRef.current !== 0 || isFollowCameraScroll || isClampedToMax || !isFollowPinnedRef.current));
@@ -5902,8 +5502,8 @@ function usePlayheadCamera({
5902
5502
  if (!isTape()) {
5903
5503
  isFollowPinnedRef.current = false;
5904
5504
  }
5905
- const liveViewport = getViewportAtScroll(viewportRef.current, getScrollLeft(wavesurfer, viewportRef.current.scrollLeftPx));
5906
- 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)({
5907
5507
  cameraMode: cameraModeRef.current,
5908
5508
  timeSec,
5909
5509
  viewport: liveViewport
@@ -5924,7 +5524,7 @@ function usePlayheadCamera({
5924
5524
  applyScrollLeft(from + (to - from) * (1 - (1 - t) * (1 - t)), false);
5925
5525
  const playhead = playheadRef.current;
5926
5526
  if (playhead) {
5927
- 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);
5928
5528
  }
5929
5529
  if (t < 1) {
5930
5530
  jumpAnimationRef.current = window.requestAnimationFrame(tick);
@@ -5942,16 +5542,16 @@ function usePlayheadCamera({
5942
5542
  }
5943
5543
  const scrollLeftPx = getScrollLeft(wavesurfer);
5944
5544
  if (!isUserPan(scrollLeftPx)) {
5945
- viewportRef.current = getViewportAtScroll(viewportRef.current, scrollLeftPx);
5545
+ viewportRef.current = (0,waveform_viewport/* getViewportAtScroll */.NI)(viewportRef.current, scrollLeftPx);
5946
5546
  return;
5947
5547
  }
5948
- viewportRef.current = getViewportAtScroll(viewportRef.current, scrollLeftPx);
5548
+ viewportRef.current = (0,waveform_viewport/* getViewportAtScroll */.NI)(viewportRef.current, scrollLeftPx);
5949
5549
  const playhead = playheadRef.current;
5950
5550
  if (isTape()) {
5951
5551
  if (playhead) {
5952
- playhead.style.left = getTapePinnedPlayheadLeft();
5552
+ playhead.style.left = (0,waveform_viewport/* getTapePinnedPlayheadLeft */.yW)();
5953
5553
  }
5954
- 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)));
5955
5555
  userIsScrollingRef.current = true;
5956
5556
  window.clearTimeout(scrollSettleTimerRef.current);
5957
5557
  scrollSettleTimerRef.current = window.setTimeout(() => {
@@ -5969,7 +5569,7 @@ function usePlayheadCamera({
5969
5569
  }
5970
5570
  const timeSec = readMediaTime();
5971
5571
  if (playhead) {
5972
- 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);
5973
5573
  }
5974
5574
  userIsScrollingRef.current = true;
5975
5575
  holdFollowUntilInsetRef.current = true;
@@ -6026,13 +5626,13 @@ function applyHandleLeft(el, timeSec, durationSec, viewport, collapsedOffsetPx =
6026
5626
  if (!el) {
6027
5627
  return;
6028
5628
  }
6029
- el.style.left = offsetLeftCss(timeLeftPercent(timeSec, durationSec, viewport), collapsedOffsetPx);
5629
+ el.style.left = offsetLeftCss((0,waveform_viewport/* timeLeftPercent */.Mu)(timeSec, durationSec, viewport), collapsedOffsetPx);
6030
5630
  }
6031
5631
  function applyRegion(el, startSec, endSec, durationSec, viewport, collapsed) {
6032
5632
  if (!el) {
6033
5633
  return;
6034
5634
  }
6035
- const start = timeLeftPercent(startSec, durationSec, viewport);
5635
+ const start = (0,waveform_viewport/* timeLeftPercent */.Mu)(startSec, durationSec, viewport);
6036
5636
  const halfLine = constants/* WAVEFORM_RANGE_HANDLE_LINE_PX */.h1 / 2;
6037
5637
  if (collapsed) {
6038
5638
  const extentPx = constants/* WAVEFORM_RANGE_COLLAPSED_OFFSET_PX */.BG + halfLine;
@@ -6040,7 +5640,7 @@ function applyRegion(el, startSec, endSec, durationSec, viewport, collapsed) {
6040
5640
  el.style.width = `${extentPx * 2}px`;
6041
5641
  return;
6042
5642
  }
6043
- const end = timeLeftPercent(endSec, durationSec, viewport);
5643
+ const end = (0,waveform_viewport/* timeLeftPercent */.Mu)(endSec, durationSec, viewport);
6044
5644
  el.style.left = offsetLeftCss(start, -halfLine);
6045
5645
  el.style.width = `calc(${end} - ${start} + ${constants/* WAVEFORM_RANGE_HANDLE_LINE_PX */.h1}px)`;
6046
5646
  }
@@ -6064,7 +5664,7 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6064
5664
  const tooltipRef = (0,external_react_.useRef)(null);
6065
5665
  const dragRef = (0,external_react_.useRef)(null);
6066
5666
  const rangeRef = (0,external_react_.useRef)(range);
6067
- const displayedRef = (0,external_react_.useRef)(resolveRange(range, durationMsFromSec(durationSec)));
5667
+ const displayedRef = (0,external_react_.useRef)((0,waveform_range/* resolveRange */.Ss)(range, (0,waveform_range/* durationMsFromSec */.uH)(durationSec)));
6068
5668
  const viewportRef = (0,external_react_.useRef)(viewport);
6069
5669
  const durationSecRef = (0,external_react_.useRef)(durationSec);
6070
5670
  const onDragChangeRef = (0,external_react_.useRef)(onDragChange);
@@ -6081,8 +5681,8 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6081
5681
  onRangeChangeRef.current = onRangeChange;
6082
5682
  getPlayheadSecRef.current = getPlayheadSec;
6083
5683
  activeHandleRef.current = activeHandle;
6084
- const durationMs = durationMsFromSec(durationSec);
6085
- const displayed = dragRange ?? resolveRange(range, durationMs);
5684
+ const durationMs = (0,waveform_range/* durationMsFromSec */.uH)(durationSec);
5685
+ const displayed = dragRange ?? (0,waveform_range/* resolveRange */.Ss)(range, durationMs);
6086
5686
  displayedRef.current = displayed;
6087
5687
  const syncPositions = (0,external_react_.useCallback)((next, nextViewport, nextDurationSec) => {
6088
5688
  displayedRef.current = next;
@@ -6094,7 +5694,7 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6094
5694
  if (tooltip) {
6095
5695
  const tooltipHandle = activeHandleRef.current ?? 'end';
6096
5696
  const tooltipMs = tooltipHandle === 'start' ? next.startMs : next.endMs;
6097
- tooltip.style.left = timeLeftPercent(tooltipMs / 1000, nextDurationSec, nextViewport);
5697
+ tooltip.style.left = (0,waveform_viewport/* timeLeftPercent */.Mu)(tooltipMs / 1000, nextDurationSec, nextViewport);
6098
5698
  }
6099
5699
  }, []);
6100
5700
  (0,external_react_.useImperativeHandle)(ref, () => ({
@@ -6107,7 +5707,7 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6107
5707
  syncPositions(displayedRef.current, viewportRef.current, durationSec);
6108
5708
  }, [activeHandle, displayed.endMs, displayed.startMs, durationSec, syncPositions]);
6109
5709
  (0,external_react_.useLayoutEffect)(() => {
6110
- const next = createWaveformViewport({
5710
+ const next = (0,waveform_viewport/* createWaveformViewport */.eq)({
6111
5711
  durationSec: viewport.durationSec,
6112
5712
  heightPx: viewport.heightPx,
6113
5713
  maxZoom: viewport.maxZoom,
@@ -6136,7 +5736,7 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6136
5736
  if (!didChange) {
6137
5737
  return;
6138
5738
  }
6139
- const committed = commitRangeChange(drag.range);
5739
+ const committed = (0,waveform_range/* commitRangeChange */.dG)(drag.range);
6140
5740
  if (committed) {
6141
5741
  onRangeChangeRef.current?.(committed);
6142
5742
  }
@@ -6153,17 +5753,17 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6153
5753
  const rect = root.getBoundingClientRect();
6154
5754
  const pointerX = event.clientX - rect.left;
6155
5755
  const vp = viewportRef.current;
6156
- const duration = durationMsFromSec(durationSecRef.current);
5756
+ const duration = (0,waveform_range/* durationMsFromSec */.uH)(durationSecRef.current);
6157
5757
  const playheadSec = getPlayheadSecRef.current?.() ?? currentTimeSec;
6158
- const pointerMs = snapTimeMs({
5758
+ const pointerMs = (0,waveform_range/* snapTimeMs */.Mh)({
6159
5759
  pixelsPerSecond: vp.pixelsPerSecond,
6160
5760
  playheadMs: playheadSec * 1000,
6161
- timeMs: pointerTimeMs(pointerX, vp)
5761
+ timeMs: (0,waveform_range/* pointerTimeMs */.v2)(pointerX, vp)
6162
5762
  });
6163
5763
  if (drag.originMs === drag.range.startMs && drag.originMs === drag.range.endMs && pointerMs === drag.originMs) {
6164
5764
  return;
6165
5765
  }
6166
- const next = dragRangeHandle({
5766
+ const next = (0,waveform_range/* dragRangeHandle */.Mo)({
6167
5767
  durationMs: duration,
6168
5768
  handle: drag.handle,
6169
5769
  pointerMs,
@@ -6188,7 +5788,7 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6188
5788
  }
6189
5789
  event.preventDefault();
6190
5790
  event.stopPropagation();
6191
- const resolved = resolveRange(rangeRef.current, durationMsFromSec(durationSecRef.current));
5791
+ const resolved = (0,waveform_range/* resolveRange */.Ss)(rangeRef.current, (0,waveform_range/* durationMsFromSec */.uH)(durationSecRef.current));
6192
5792
  dragRef.current = {
6193
5793
  handle,
6194
5794
  originMs: handle === 'start' ? resolved.startMs : resolved.endMs,
@@ -6204,7 +5804,7 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6204
5804
  setDragRange(resolved);
6205
5805
  onDragChangeRef.current?.(true);
6206
5806
  onPreviewChangeRef.current?.({
6207
- endMs: isRangeCollapsed(rangeRef.current) ? null : resolved.endMs,
5807
+ endMs: (0,waveform_range/* isRangeCollapsed */.BT)(rangeRef.current) ? null : resolved.endMs,
6208
5808
  startMs: resolved.startMs
6209
5809
  });
6210
5810
  }, [interactive]);
@@ -6216,15 +5816,15 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6216
5816
  const rect = root.getBoundingClientRect();
6217
5817
  const pointerX = event.clientX - rect.left;
6218
5818
  const vp = viewportRef.current;
6219
- const resolved = resolveRange(rangeRef.current, durationMsFromSec(durationSecRef.current));
6220
- const handle = pickRangeHandle({
6221
- endX: visualRangeHandlePx({
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)({
6222
5822
  handle: 'end',
6223
5823
  range: resolved,
6224
5824
  viewport: vp
6225
5825
  }),
6226
5826
  pointerX,
6227
- startX: visualRangeHandlePx({
5827
+ startX: (0,waveform_range/* visualRangeHandlePx */.Eh)({
6228
5828
  handle: 'start',
6229
5829
  range: resolved,
6230
5830
  viewport: vp
@@ -6310,11 +5910,11 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6310
5910
  // extracted by mini-css-extract-plugin
6311
5911
 
6312
5912
  ;// ./src/lib/viewers/media/waveform/WaveformView.tsx
6313
- 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; }
6314
- 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; }
6315
- 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; }
6316
- function WaveformView_toPropertyKey(t) { var i = WaveformView_toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
6317
- 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); }
6318
5918
 
6319
5919
 
6320
5920
 
@@ -6438,6 +6038,7 @@ function WaveformView({
6438
6038
  mediaEl,
6439
6039
  onPlayPause,
6440
6040
  onRangeChange,
6041
+ onRangeClear,
6441
6042
  onRangeDragChange,
6442
6043
  onSeek,
6443
6044
  onViewportChange,
@@ -6464,6 +6065,7 @@ function WaveformView({
6464
6065
  const mediaElRef = (0,external_react_.useRef)(mediaEl); // latest <audio>/<video>; camera + playhead tick read this
6465
6066
  const onPlayPauseRef = (0,external_react_.useRef)(onPlayPause); // latest play/pause; tape tap must not re-bind
6466
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
6467
6069
  const onViewportChangeRef = (0,external_react_.useRef)(onViewportChange); // latest viewport callback; camera commits here
6468
6070
  const peaksRef = (0,external_react_.useRef)(peaks); // latest peaks; WaveSurfer create() + morph read this
6469
6071
 
@@ -6496,6 +6098,7 @@ function WaveformView({
6496
6098
  mediaElRef.current = mediaEl;
6497
6099
  onPlayPauseRef.current = onPlayPause;
6498
6100
  onSeekRef.current = onSeek;
6101
+ onRangeClearRef.current = onRangeClear;
6499
6102
  onViewportChangeRef.current = onViewportChange;
6500
6103
  peaksRef.current = peaks;
6501
6104
  const [internalZoom, setInternalZoom] = (0,external_react_.useState)(constants/* WAVEFORM_ZOOM_MIN */.LW);
@@ -6511,13 +6114,13 @@ function WaveformView({
6511
6114
  const lastSetTimeSecRef = (0,external_react_.useRef)(null);
6512
6115
  const [isRangeHovered, setIsRangeHovered] = (0,external_react_.useState)(false);
6513
6116
  const isControlled = typeof zoomLevelProp === 'number';
6514
- const maxZoom = getWaveformZoomMax({
6117
+ const maxZoom = (0,waveform_viewport/* getWaveformZoomMax */.by)({
6515
6118
  durationSec,
6516
6119
  peakCount: peaks.length,
6517
6120
  viewWidthPx: canvasWidthPx
6518
6121
  });
6519
6122
  hasZoomHandlersRef.current = maxZoom > constants/* WAVEFORM_ZOOM_MIN */.LW && (typeof onZoomChange === 'function' || typeof zoomLevelProp !== 'number');
6520
- const zoomLevel = clampWaveformZoom(isControlled ? zoomLevelProp : internalZoom, maxZoom);
6123
+ const zoomLevel = (0,waveform_viewport/* clampWaveformZoom */.ii)(isControlled ? zoomLevelProp : internalZoom, maxZoom);
6521
6124
  const zoomRef = (0,external_react_.useRef)(zoomLevel); // latest zoom for WaveSurfer redraw/zoom handlers
6522
6125
  zoomRef.current = zoomLevel;
6523
6126
  const prevZoomRef = (0,external_react_.useRef)(null); // last applied zoom; tape recenters only when this changes
@@ -6530,22 +6133,22 @@ function WaveformView({
6530
6133
  const activeRange = isRangeDragging ? previewRange ?? range : range;
6531
6134
  const activeRangeRef = (0,external_react_.useRef)(activeRange);
6532
6135
  activeRangeRef.current = activeRange;
6533
- const durationMs = durationMsFromSec(durationSec);
6136
+ const durationMs = (0,waveform_range/* durationMsFromSec */.uH)(durationSec);
6534
6137
  const rangeStartMs = activeRange?.startMs;
6535
6138
  const rangeEndMs = activeRange?.endMs;
6536
6139
  const activeRangeProgress = (0,external_react_.useMemo)(() => {
6537
6140
  if (rangeStartMs == null || !Number.isFinite(rangeStartMs)) {
6538
6141
  return null;
6539
6142
  }
6540
- return rangeProgress(resolveRange({
6143
+ return (0,waveform_range/* rangeProgress */.D$)((0,waveform_range/* resolveRange */.Ss)({
6541
6144
  endMs: rangeEndMs ?? null,
6542
6145
  startMs: rangeStartMs
6543
6146
  }, durationMs), durationMs);
6544
6147
  }, [durationMs, rangeEndMs, rangeStartMs]);
6545
6148
  rangeProgressRef.current = activeRangeProgress;
6546
- const viewport = (0,external_react_.useMemo)(() => createWaveformViewport({
6149
+ const viewport = (0,external_react_.useMemo)(() => (0,waveform_viewport/* createWaveformViewport */.eq)({
6547
6150
  durationSec,
6548
- gutterPx: isTape ? getTapeGutterPx(canvasWidthPx) : 0,
6151
+ gutterPx: isTape ? (0,waveform_viewport/* getTapeGutterPx */.iA)(canvasWidthPx) : 0,
6549
6152
  heightPx: height,
6550
6153
  maxZoom,
6551
6154
  scrollLeftPx: scrollLeft,
@@ -6603,7 +6206,7 @@ function WaveformView({
6603
6206
  }, [isUserPanning]);
6604
6207
  toggleTapePlaybackRef.current = toggleTapePlayback;
6605
6208
  (0,external_react_.useLayoutEffect)(() => {
6606
- viewportRef.current = createWaveformViewport({
6209
+ viewportRef.current = (0,waveform_viewport/* createWaveformViewport */.eq)({
6607
6210
  durationSec: viewport.durationSec,
6608
6211
  gutterPx: viewport.gutterPx,
6609
6212
  heightPx: viewport.heightPx,
@@ -6614,7 +6217,7 @@ function WaveformView({
6614
6217
  });
6615
6218
  }, [viewport]);
6616
6219
  const setZoomLevel = (0,external_react_.useCallback)(nextZoom => {
6617
- const zoom = clampWaveformZoom(nextZoom, maxZoom);
6220
+ const zoom = (0,waveform_viewport/* clampWaveformZoom */.ii)(nextZoom, maxZoom);
6618
6221
  if (!isControlled) {
6619
6222
  setInternalZoom(zoom);
6620
6223
  }
@@ -6669,7 +6272,7 @@ function WaveformView({
6669
6272
  return;
6670
6273
  }
6671
6274
  const scrollLeftPx = WaveformView_getScrollLeft(wavesurfer);
6672
- viewportRef.current = getViewportAtScroll(viewportRef.current, scrollLeftPx);
6275
+ viewportRef.current = (0,waveform_viewport/* getViewportAtScroll */.NI)(viewportRef.current, scrollLeftPx);
6673
6276
  rangeLayerRef.current?.applyViewport(viewportRef.current);
6674
6277
  onViewportChangeRef.current?.(viewportRef.current);
6675
6278
  setScrollLeft(scrollLeftPx);
@@ -6680,9 +6283,9 @@ function WaveformView({
6680
6283
  return;
6681
6284
  }
6682
6285
  if (isTape) {
6683
- playhead.style.left = getTapePinnedPlayheadLeft();
6286
+ playhead.style.left = (0,waveform_viewport/* getTapePinnedPlayheadLeft */.yW)();
6684
6287
  } else if (!isFollowPinned()) {
6685
- playhead.style.left = timeLeftPercent(timeSec, durationSecRef.current, viewportRef.current);
6288
+ playhead.style.left = (0,waveform_viewport/* timeLeftPercent */.Mu)(timeSec, durationSecRef.current, viewportRef.current);
6686
6289
  }
6687
6290
  rangeLayerRef.current?.applyViewport(viewportRef.current);
6688
6291
  const wavesurfer = wavesurferRef.current;
@@ -6700,14 +6303,14 @@ function WaveformView({
6700
6303
  return;
6701
6304
  }
6702
6305
  const viewWidthPx = wavesurfer.getWidth ? wavesurfer.getWidth() : container.clientWidth;
6703
- const minPxPerSec = getZoomedPixelsPerSecond({
6306
+ const minPxPerSec = (0,waveform_viewport/* getZoomedPixelsPerSecond */.J$)({
6704
6307
  durationSec,
6705
6308
  isTape,
6706
6309
  maxZoom,
6707
6310
  viewWidthPx,
6708
6311
  zoomLevel
6709
6312
  });
6710
- const gutterPx = isTape ? getTapeGutterPx(viewWidthPx) : 0;
6313
+ const gutterPx = isTape ? (0,waveform_viewport/* getTapeGutterPx */.iA)(viewWidthPx) : 0;
6711
6314
  const origin = isTape ? null : zoomOriginRef.current;
6712
6315
  zoomOriginRef.current = null;
6713
6316
  const didZoomChange = prevZoomRef.current !== zoomLevel;
@@ -6716,7 +6319,7 @@ function WaveformView({
6716
6319
  }
6717
6320
  suppressViewportSyncRef.current = true;
6718
6321
  try {
6719
- wavesurfer.setOptions(WaveformView_objectSpread({
6322
+ wavesurfer.setOptions(_objectSpread({
6720
6323
  autoScroll: false,
6721
6324
  fillParent: !isTape,
6722
6325
  minPxPerSec
@@ -6726,7 +6329,7 @@ function WaveformView({
6726
6329
  } : {}));
6727
6330
  applyWaveformGutters(wavesurfer, gutterPx);
6728
6331
  if (minPxPerSec > 0) {
6729
- const windowViewport = createWaveformViewport({
6332
+ const windowViewport = (0,waveform_viewport/* createWaveformViewport */.eq)({
6730
6333
  durationSec,
6731
6334
  gutterPx,
6732
6335
  heightPx: liveHeightRef.current || height,
@@ -6738,7 +6341,7 @@ function WaveformView({
6738
6341
  if (origin) {
6739
6342
  applyScrollLeft(origin.timeSec * minPxPerSec + gutterPx - origin.pointerX, true);
6740
6343
  } else if (didZoomChange && (isTape || !pointerZoomRef.current)) {
6741
- applyScrollLeft(getCenteredScrollLeft(currentTimeRef.current, windowViewport), true);
6344
+ applyScrollLeft((0,waveform_viewport/* getCenteredScrollLeft */.b6)(currentTimeRef.current, windowViewport), true);
6742
6345
  }
6743
6346
  }
6744
6347
  lastSetTimeSecRef.current = currentTimeRef.current;
@@ -6787,11 +6390,23 @@ function WaveformView({
6787
6390
  if (!interactiveRef.current || isRangeDraggingRef.current) {
6788
6391
  return;
6789
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
+ }
6790
6405
  if (cameraModeRef.current === 'tape') {
6791
6406
  toggleTapePlaybackRef.current?.();
6792
6407
  return;
6793
6408
  }
6794
- onSeekRef.current?.(relativeX * durationSecRef.current);
6409
+ onSeekRef.current?.(timeSec);
6795
6410
  });
6796
6411
  const unsubscribeScroll = wavesurfer.on('scroll', () => {
6797
6412
  if (suppressViewportSyncRef.current) {
@@ -6884,7 +6499,7 @@ function WaveformView({
6884
6499
  }, [syncViewport]);
6885
6500
  (0,external_react_.useEffect)(() => {
6886
6501
  const rawZoom = isControlled ? zoomLevelProp : internalZoom;
6887
- 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);
6888
6503
  if (clamped === rawZoom) {
6889
6504
  return;
6890
6505
  }
@@ -7104,17 +6719,17 @@ function WaveformView({
7104
6719
  return;
7105
6720
  }
7106
6721
  const vp = viewportRef.current;
7107
- const draft = activeRangeRef.current;
7108
- setIsRangeHovered(!!draft && isPointerOverRange({
6722
+ const rangeDraft = activeRangeRef.current;
6723
+ setIsRangeHovered(!!rangeDraft && (0,waveform_range/* isPointerOverRange */.H5)({
7109
6724
  pointerX,
7110
- range: draft,
6725
+ range: rangeDraft,
7111
6726
  viewport: vp
7112
6727
  }));
7113
6728
  if (!interactive || isRangeDraggingRef.current || !(durationSec > 0)) {
7114
6729
  return;
7115
6730
  }
7116
6731
  if (vp.pixelsPerSecond > 0) {
7117
- 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)));
7118
6733
  return;
7119
6734
  }
7120
6735
  setHoverProgress(Math.min(1, Math.max(0, pointerX / rect.width)));
@@ -7144,8 +6759,8 @@ function WaveformView({
7144
6759
  const host = node?.parentElement ?? null;
7145
6760
  setOverlayPortalHost(prev => prev === host ? prev : host);
7146
6761
  }, []);
7147
- const hoverLeft = isRangeDragging || hoverProgress == null ? null : timeLeftPercent(hoverProgress * durationSec, durationSec, viewportRef.current);
7148
- const showRangeOverlay = Boolean(range) && (isRangeDragging || !isPlaying || !isRangeCollapsed(range));
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));
7149
6764
  const scrubTimeChip = isTape && scrubPreviewTimeSec != null ? /*#__PURE__*/external_react_["default"].createElement("div", {
7150
6765
  className: "bp-WaveformView-hover bp-WaveformView-hover--tape",
7151
6766
  "data-testid": "bp-waveform-hover"
@@ -7302,8 +6917,9 @@ function WaveformZoomControl({
7302
6917
  const zoomControlElRef = (0,external_react_.useRef)(null); // root; outside pointerdown unpins the flyout
7303
6918
  const dismissTimerRef = (0,external_react_.useRef)(0); // delay before hover-close of the flyout
7304
6919
  const sliderId = `bp-waveform-zoom-slider${(0,external_react_.useId)()}`;
7305
- const zoom = clampWaveformZoom(zoomLevel, maxZoom);
7306
- 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);
7307
6923
  const isOpen = isHovered || isFocused || isRevealed || isPinned;
7308
6924
  const isAtMinZoom = zoomValue <= 0;
7309
6925
  const isAtMaxZoom = zoomValue >= constants/* WAVEFORM_ZOOM_SLIDER_MAX */.nh;
@@ -7329,10 +6945,10 @@ function WaveformZoomControl({
7329
6945
  return () => document.removeEventListener('pointerdown', closeIfOutside);
7330
6946
  }, [isPinned]);
7331
6947
  const handleSlider = (0,external_react_.useCallback)(newValue => {
7332
- onZoomChange(zoomFromSliderValue(newValue, maxZoom));
6948
+ onZoomChange((0,waveform_viewport/* zoomFromSliderValue */.eU)(newValue, maxZoom));
7333
6949
  }, [maxZoom, onZoomChange]);
7334
6950
  const handleStep = (0,external_react_.useCallback)(delta => {
7335
- onZoomChange(stepWaveformZoom(zoom, maxZoom, delta));
6951
+ onZoomChange((0,waveform_viewport/* stepWaveformZoom */.xV)(zoom, maxZoom, delta));
7336
6952
  }, [maxZoom, onZoomChange, zoom]);
7337
6953
  const handleZoomInPointerDown = (0,external_react_.useCallback)(() => {
7338
6954
  wasFlyoutOpenOnPointerDownRef.current = isHovered || isFocused || isRevealed || isPinned;
@@ -7350,6 +6966,7 @@ function WaveformZoomControl({
7350
6966
  ref: zoomControlElRef,
7351
6967
  "aria-label": "Zoom",
7352
6968
  className: classnames_default()('bp-WaveformZoomControl', {
6969
+ 'bp-has-multiplier': zoomMultiplier,
7353
6970
  'bp-is-open': isOpen
7354
6971
  }),
7355
6972
  "data-testid": "bp-waveform-zoom",
@@ -7375,7 +6992,15 @@ function WaveformZoomControl({
7375
6992
  }, constants/* WAVEFORM_ZOOM_DISMISS_MS */.m6);
7376
6993
  },
7377
6994
  role: "group"
7378
- }, /*#__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, {
7379
7004
  "aria-disabled": isOpen && isAtMaxZoom,
7380
7005
  className: "bp-WaveformZoomControl-button",
7381
7006
  "data-resin-target": "waveformZoomIn",
@@ -7436,26 +7061,34 @@ function WaveformZoomControl({
7436
7061
 
7437
7062
 
7438
7063
 
7064
+
7439
7065
  const PLACEHOLDER_PEAKS = placeholderPeaks();
7440
7066
  function MP3ControlsV2({
7441
7067
  autoplay,
7442
7068
  bufferedRange,
7443
7069
  commentMarkers,
7070
+ commentRangeDraft = null,
7444
7071
  currentTime,
7445
7072
  durationTime,
7446
7073
  hasStartedPlayback = false,
7074
+ isGeneratingWaveform = false,
7447
7075
  isPlaying,
7448
7076
  keyboardVolumeStep = 0,
7449
7077
  keyboardZoomStep = 0,
7450
7078
  mediaEl,
7451
7079
  onAutoplayChange,
7452
7080
  onCommentMarkerClick,
7081
+ onCommentRangeChange,
7082
+ onCommentRangeClear,
7083
+ onCommentRangeDragChange,
7453
7084
  onMuteChange,
7454
7085
  onPlayPause,
7086
+ onPlayNextChange,
7455
7087
  onRateChange,
7456
7088
  onTimeChange,
7457
7089
  onVolumeChange,
7458
7090
  peaks,
7091
+ playNext,
7459
7092
  rate,
7460
7093
  volume
7461
7094
  }) {
@@ -7478,7 +7111,7 @@ function MP3ControlsV2({
7478
7111
  const userChangedTapeZoomRef = (0,external_react_.useRef)(false); // pinch/wheel zoom; skip re-applying the 10s default
7479
7112
  const handleViewportChange = (0,external_react_.useCallback)(next => {
7480
7113
  setMaxZoom(prev => prev === next.maxZoom ? prev : next.maxZoom);
7481
- setViewport(prev => viewportEquals(prev, next) ? prev : next);
7114
+ setViewport(prev => (0,waveform_viewport/* viewportEquals */.KH)(prev, next) ? prev : next);
7482
7115
  }, []);
7483
7116
  const revealZoomControl = (0,external_react_.useCallback)(() => {
7484
7117
  setIsZoomRevealed(true);
@@ -7503,11 +7136,11 @@ function MP3ControlsV2({
7503
7136
  return;
7504
7137
  }
7505
7138
  userChangedTapeZoomRef.current = true;
7506
- setZoomLevel(prev => stepWaveformZoom(prev, maxZoom, steps * constants/* WAVEFORM_ZOOM_BUTTON_STEP */.QD));
7139
+ setZoomLevel(prev => (0,waveform_viewport/* stepWaveformZoom */.xV)(prev, maxZoom, steps * constants/* WAVEFORM_ZOOM_BUTTON_STEP */.QD));
7507
7140
  revealZoomControl();
7508
7141
  }, [keyboardZoomStep, maxZoom, revealZoomControl]);
7509
7142
  (0,external_react_.useEffect)(() => {
7510
- setZoomLevel(prev => clampWaveformZoom(prev, maxZoom));
7143
+ setZoomLevel(prev => (0,waveform_viewport/* clampWaveformZoom */.ii)(prev, maxZoom));
7511
7144
  }, [maxZoom]);
7512
7145
  (0,external_react_.useEffect)(() => {
7513
7146
  if (!isTape) {
@@ -7525,7 +7158,7 @@ function MP3ControlsV2({
7525
7158
  hasAppliedTapeDefaultZoomRef.current = true;
7526
7159
  return;
7527
7160
  }
7528
- const nextZoom = getTapeDefaultZoom(durationValue, Math.max(maxZoom, viewport.maxZoom));
7161
+ const nextZoom = (0,waveform_viewport/* getTapeDefaultZoom */.lz)(durationValue, Math.max(maxZoom, viewport.maxZoom));
7529
7162
  hasAppliedTapeDefaultZoomRef.current = true;
7530
7163
  setZoomLevel(prev => prev === nextZoom ? prev : nextZoom);
7531
7164
  }, [durationValue, hasRealPeaks, hasWaveformDuration, isTape, maxZoom, viewport]);
@@ -7558,6 +7191,7 @@ function MP3ControlsV2({
7558
7191
  const showPlayOverlay = !hasStarted && !isPlaying;
7559
7192
  const hasZoomHandlers = hasRealPeaks && !showPlayOverlay;
7560
7193
  const hasZoomControl = hasZoomHandlers && hasMediaMetadata && maxZoom > constants/* WAVEFORM_ZOOM_MIN */.LW;
7194
+ const showGeneratingWaveformIndicator = isGeneratingWaveform && !hasRealPeaks;
7561
7195
  const waveformZoomLevel = isTape || hasZoomHandlers ? zoomLevel : constants/* WAVEFORM_ZOOM_MIN */.LW;
7562
7196
  return /*#__PURE__*/external_react_["default"].createElement("div", {
7563
7197
  className: "bp-MP3ControlsV2",
@@ -7575,10 +7209,14 @@ function MP3ControlsV2({
7575
7209
  isPlaying: isPlaying,
7576
7210
  mediaEl: mediaEl,
7577
7211
  onPlayPause: isWaveformInteractive ? onPlayPause : undefined,
7212
+ onRangeChange: isWaveformInteractive ? onCommentRangeChange : undefined,
7213
+ onRangeClear: isWaveformInteractive ? onCommentRangeClear : undefined,
7214
+ onRangeDragChange: onCommentRangeDragChange,
7578
7215
  onSeek: isWaveformInteractive ? onTimeChange : undefined,
7579
7216
  onViewportChange: hasRealPeaks ? handleViewportChange : undefined,
7580
7217
  onZoomChange: hasZoomHandlers ? handleWaveformZoom : undefined,
7581
7218
  peaks: waveformPeaks,
7219
+ range: commentRangeDraft,
7582
7220
  zoomLevel: waveformZoomLevel
7583
7221
  }), /*#__PURE__*/external_react_["default"].createElement(WaveformCommentMarkers, {
7584
7222
  commentMarkers: waveformMarkers,
@@ -7587,9 +7225,9 @@ function MP3ControlsV2({
7587
7225
  onCommentMarkerClick: handleCommentMarkerClick,
7588
7226
  selectedId: selectedMarkerId,
7589
7227
  viewport: viewport
7590
- })), hasZoomControl && /*#__PURE__*/external_react_["default"].createElement("div", {
7228
+ })), (showGeneratingWaveformIndicator || hasZoomControl) && /*#__PURE__*/external_react_["default"].createElement("div", {
7591
7229
  className: "bp-MP3ControlsV2-waveformZoom"
7592
- }, /*#__PURE__*/external_react_["default"].createElement(WaveformZoomControl, {
7230
+ }, showGeneratingWaveformIndicator ? /*#__PURE__*/external_react_["default"].createElement(WaveformGeneratingIndicator, null) : /*#__PURE__*/external_react_["default"].createElement(WaveformZoomControl, {
7593
7231
  isRevealed: isZoomRevealed,
7594
7232
  maxZoom: maxZoom,
7595
7233
  onZoomChange: handleWaveformZoom,
@@ -7635,7 +7273,9 @@ function MP3ControlsV2({
7635
7273
  autoplay: autoplay,
7636
7274
  className: "bp-MP3Controls-settings",
7637
7275
  onAutoplayChange: onAutoplayChange,
7276
+ onPlayNextChange: onPlayNextChange,
7638
7277
  onRateChange: onRateChange,
7278
+ playNext: playNext,
7639
7279
  rate: rate
7640
7280
  }))));
7641
7281
  }
@@ -8367,6 +8007,221 @@ function loadPeaks(request) {
8367
8007
 
8368
8008
  /***/ },
8369
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
+
8370
8225
  /***/ 1053
8371
8226
  (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
8372
8227
 
@@ -8554,6 +8409,369 @@ function isRetryableWaveformError(code) {
8554
8409
 
8555
8410
  /***/ },
8556
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
+
8557
8775
  /***/ 2485
8558
8776
  (module, exports) {
8559
8777
 
@@ -24217,7 +24435,7 @@ class Browser {
24217
24435
  ;// ./src/lib/Logger.js
24218
24436
  /* eslint-disable no-undef */
24219
24437
  const CLIENT_NAME = "box-content-preview";
24220
- const CLIENT_VERSION = "3.92.0";
24438
+ const CLIENT_VERSION = "3.93.0";
24221
24439
  /* eslint-enable no-undef */
24222
24440
 
24223
24441
  class Logger {
@@ -33443,6 +33661,44 @@ class TextLoader extends viewers_AssetLoader {
33443
33661
  }
33444
33662
  }
33445
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
+ }
33446
33702
  // EXTERNAL MODULE: ./node_modules/lodash/isEmpty.js
33447
33703
  var isEmpty = __webpack_require__(2193);
33448
33704
  var isEmpty_default = /*#__PURE__*/__webpack_require__.n(isEmpty);
@@ -35412,6 +35668,7 @@ const CSS_CLASS_MEDIA_CONTAINER = 'bp-media-container';
35412
35668
  const DEFAULT_VOLUME = 1;
35413
35669
  const MEDIA_VOLUME_CACHE_KEY = 'media-volume';
35414
35670
  const MEDIA_AUTOPLAY_CACHE_KEY = 'media-autoplay';
35671
+ const MEDIA_PLAY_NEXT_CACHE_KEY = 'media-play-next';
35415
35672
  const MEDIA_SPEED_CACHE_KEY = 'media-speed';
35416
35673
  const MEDIA_VOLUME_INCREMENT = 0.05;
35417
35674
  const EMIT_WAIT_TIME_IN_MILLIS = 100;
@@ -35462,6 +35719,7 @@ class MediaBaseViewer extends viewers_BaseViewer {
35462
35719
  this.containerClickHandler = this.containerClickHandler.bind(this);
35463
35720
  this.errorHandler = this.errorHandler.bind(this);
35464
35721
  this.handleAutoplay = this.handleAutoplay.bind(this);
35722
+ this.handlePlayNext = this.handlePlayNext.bind(this);
35465
35723
  this.handleRate = this.handleRate.bind(this);
35466
35724
  this.handleTimeupdateFromMediaControls = this.handleTimeupdateFromMediaControls.bind(this);
35467
35725
  this.loadeddataHandler = this.loadeddataHandler.bind(this);
@@ -35474,6 +35732,7 @@ class MediaBaseViewer extends viewers_BaseViewer {
35474
35732
  this.resetPlayIcon = this.resetPlayIcon.bind(this);
35475
35733
  this.seekHandler = this.seekHandler.bind(this);
35476
35734
  this.setAutoplay = this.setAutoplay.bind(this);
35735
+ this.setPlayNext = this.setPlayNext.bind(this);
35477
35736
  this.setRate = this.setRate.bind(this);
35478
35737
  this.setTimeCode = this.setTimeCode.bind(this);
35479
35738
  this.setVolume = this.setVolume.bind(this);
@@ -35783,7 +36042,9 @@ class MediaBaseViewer extends viewers_BaseViewer {
35783
36042
  * @return {void}
35784
36043
  */
35785
36044
  handleRate() {
35786
- 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;
35787
36048
  if (speed && this.mediaEl.playbackRate !== speed && this.mediaEl.playbackRate > 0) {
35788
36049
  this.emit('ratechange', speed);
35789
36050
  }
@@ -35831,6 +36092,20 @@ class MediaBaseViewer extends viewers_BaseViewer {
35831
36092
  this.renderUI();
35832
36093
  }
35833
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
+ }
35834
36109
  /**
35835
36110
  * Autoplay the media
35836
36111
  *
@@ -35857,6 +36132,16 @@ class MediaBaseViewer extends viewers_BaseViewer {
35857
36132
  isAutoplayEnabled() {
35858
36133
  return this.cache.get(MEDIA_AUTOPLAY_CACHE_KEY) === 'Enabled';
35859
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
+ }
35860
36145
  /**
35861
36146
  * Loads the controls
35862
36147
  *
@@ -35879,6 +36164,9 @@ class MediaBaseViewer extends viewers_BaseViewer {
35879
36164
  if (!this.cache.has(MEDIA_AUTOPLAY_CACHE_KEY)) {
35880
36165
  this.cache.set(MEDIA_AUTOPLAY_CACHE_KEY, 'Disabled');
35881
36166
  }
36167
+ if (!this.cache.has(MEDIA_PLAY_NEXT_CACHE_KEY)) {
36168
+ this.cache.set(MEDIA_PLAY_NEXT_CACHE_KEY, 'Disabled');
36169
+ }
35882
36170
  if (!this.cache.has(MEDIA_SPEED_CACHE_KEY)) {
35883
36171
  this.cache.set(MEDIA_SPEED_CACHE_KEY, '1.0');
35884
36172
  }
@@ -35973,6 +36261,18 @@ class MediaBaseViewer extends viewers_BaseViewer {
35973
36261
  this.handleAutoplay();
35974
36262
  }
35975
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
+
35976
36276
  /**
35977
36277
  * Updates play rate
35978
36278
  *
@@ -36074,17 +36374,17 @@ class MediaBaseViewer extends viewers_BaseViewer {
36074
36374
  }
36075
36375
 
36076
36376
  /**
36077
- * Emits the previewnextfile event if autoplay is enabled.
36377
+ * Emits mediaEndPlayNext when play next is enabled.
36078
36378
  *
36079
36379
  * @private
36080
- * @emits previewnextfile
36380
+ * @emits mediaEndPlayNext
36081
36381
  * @return {void}
36082
36382
  */
36083
36383
  mediaendHandler() {
36084
36384
  this.resetPlayIcon();
36085
36385
  this.processMetrics();
36086
- if (this.isAutoplayEnabled()) {
36087
- this.emit(lib_events/* VIEWER_EVENT */.Oi.mediaEndAutoplay);
36386
+ if (this.isPlayNextEnabled()) {
36387
+ this.emit(lib_events/* VIEWER_EVENT */.Oi.mediaEndPlayNext);
36088
36388
  }
36089
36389
  }
36090
36390
 
@@ -36660,8 +36960,8 @@ class MediaBaseViewer extends viewers_BaseViewer {
36660
36960
  }
36661
36961
  }
36662
36962
  /* harmony default export */ const media_MediaBaseViewer = (MediaBaseViewer);
36663
- // EXTERNAL MODULE: ./src/lib/viewers/controls/media/MediaSettings.tsx + 5 modules
36664
- var MediaSettings = __webpack_require__(6929);
36963
+ // EXTERNAL MODULE: ./src/lib/viewers/controls/media/MediaSettings.tsx + 6 modules
36964
+ var MediaSettings = __webpack_require__(9229);
36665
36965
  // EXTERNAL MODULE: ./src/lib/viewers/controls/media/PlayPauseToggle.tsx + 1 modules
36666
36966
  var PlayPauseToggle = __webpack_require__(6321);
36667
36967
  // EXTERNAL MODULE: ./src/lib/viewers/controls/media/DurationLabels.tsx + 1 modules
@@ -36905,6 +37205,8 @@ class MP3ControlsRoot {
36905
37205
  var waveform_constants = __webpack_require__(4929);
36906
37206
  // EXTERNAL MODULE: ./src/lib/viewers/media/waveform/createWaveformLoader.ts + 1 modules
36907
37207
  var createWaveformLoader = __webpack_require__(4971);
37208
+ // EXTERNAL MODULE: ./src/lib/viewers/media/waveform/range.ts
37209
+ var range = __webpack_require__(4253);
36908
37210
  // EXTERNAL MODULE: ./src/lib/viewers/media/waveform/validateWaveformPayload.ts
36909
37211
  var validateWaveformPayload = __webpack_require__(1053);
36910
37212
  ;// ./src/lib/viewers/media/MP3.scss
@@ -36926,6 +37228,8 @@ function MP3Viewer_toPrimitive(t, r) { if ("object" != typeof t || !t) return t;
36926
37228
 
36927
37229
 
36928
37230
 
37231
+
37232
+
36929
37233
  const CSS_CLASS_MP3 = 'bp-media-mp3';
36930
37234
  const AUDIO_V2_SHUTTLE_RATES = [2, 4, 8, 16];
36931
37235
  const AUDIO_V2_SHUTTLE_TICK_MS = 50;
@@ -37034,6 +37338,50 @@ class MP3Viewer extends media_MediaBaseViewer {
37034
37338
  }
37035
37339
  this.renderUI();
37036
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;
37379
+ }
37380
+ this.commentRangeDraft = null;
37381
+ this.emit(EVENT_COMMENT_RANGE_DRAFT_DISMISS);
37382
+ this.syncCommentRangeLoop();
37383
+ this.renderUI();
37384
+ });
37037
37385
  MP3Viewer_defineProperty(this, "handleCommentMarkerClick", marker => {
37038
37386
  this.exitShuttle();
37039
37387
  this.hostSelectedMarkerId = marker.id;
@@ -37074,6 +37422,7 @@ class MP3Viewer extends media_MediaBaseViewer {
37074
37422
  this.isAudioPlayerV2 = this.getIsAudioPlayerV2();
37075
37423
  this.waveformPeaks = [];
37076
37424
  this.waveformPeaksSource = null;
37425
+ this.isWaveformConversionPolling = false;
37077
37426
  this.waveformDurationSec = 0;
37078
37427
  if (this.isAudioPlayerV2) {
37079
37428
  this.wrapperEl.classList.add('bp-media--v2');
@@ -37083,16 +37432,20 @@ class MP3Viewer extends media_MediaBaseViewer {
37083
37432
  // Listen on the loading shell. The waveform is on
37084
37433
  // screen before the audio blob is playable; getViewer() is still null.
37085
37434
  this.bindCommentMarkersListener();
37435
+ this.bindCommentRangeDraftListeners();
37086
37436
  }
37087
37437
 
37088
37438
  // Audio element
37089
37439
  this.mediaEl = this.mediaContainerEl.appendChild(document.createElement('audio'));
37090
37440
  this.mediaEl.setAttribute('preload', 'auto');
37091
37441
  this.commentMarkers = [];
37442
+ this.commentRangeDraft = null;
37092
37443
  this.hostSelectedMarkerId = null;
37444
+ this.isCommentRangeDragging = false;
37093
37445
  this.shuttleDirection = null;
37094
37446
  this.shuttleRate = 0;
37095
37447
  this.reverseShuttleTimer = 0;
37448
+ this.commentRangeLoopTimer = 0;
37096
37449
  this.keyboardVolumeStep = 0;
37097
37450
  this.keyboardZoomStep = 0;
37098
37451
  }
@@ -37103,6 +37456,15 @@ class MP3Viewer extends media_MediaBaseViewer {
37103
37456
  this.removeListener('comment_markers', this.handleCommentMarkersUpdated);
37104
37457
  this.addListener('comment_markers', this.handleCommentMarkersUpdated);
37105
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
+ }
37106
37468
 
37107
37469
  /**
37108
37470
  * Audio player v2 (waveform). Off unless the host enables `audioPlayerV2`
@@ -37241,8 +37603,10 @@ class MP3Viewer extends media_MediaBaseViewer {
37241
37603
  if (this.mediaEl && this.shuttleDirection === 'forward') {
37242
37604
  this.mediaEl.playbackRate = this.shuttleRate;
37243
37605
  }
37606
+ this.scheduleCommentRangeLoopWrap();
37244
37607
  }).catch(() => {});
37245
37608
  }
37609
+ this.scheduleCommentRangeLoopWrap(true);
37246
37610
  }
37247
37611
  startReverseShuttle(rate) {
37248
37612
  this.stopReverseShuttle();
@@ -37252,12 +37616,13 @@ class MP3Viewer extends media_MediaBaseViewer {
37252
37616
  this.pause(undefined, true);
37253
37617
  this.handleRate();
37254
37618
  this.reverseShuttleTimer = window.setInterval(() => {
37255
- if (!this.mediaEl || this.mediaEl.currentTime <= 0) {
37619
+ const floor = this.clampTimeToOpenCommentRange(0);
37620
+ if (!this.mediaEl || this.mediaEl.currentTime <= floor) {
37256
37621
  this.exitShuttle();
37257
37622
  return;
37258
37623
  }
37259
37624
  this.quickSeek(-(this.shuttleRate * AUDIO_V2_SHUTTLE_TICK_MS / 1000));
37260
- if (this.mediaEl.currentTime <= 0) {
37625
+ if (this.mediaEl.currentTime <= floor) {
37261
37626
  this.exitShuttle();
37262
37627
  }
37263
37628
  }, AUDIO_V2_SHUTTLE_TICK_MS);
@@ -37289,9 +37654,11 @@ class MP3Viewer extends media_MediaBaseViewer {
37289
37654
  if (this.controls) {
37290
37655
  this.renderUI();
37291
37656
  }
37657
+ this.scheduleCommentRangeLoopWrap();
37292
37658
  return;
37293
37659
  }
37294
37660
  super.handleRate();
37661
+ this.scheduleCommentRangeLoopWrap();
37295
37662
  }
37296
37663
  jumpToCommentMarker(direction) {
37297
37664
  if (!this.mediaEl) {
@@ -37362,7 +37729,7 @@ class MP3Viewer extends media_MediaBaseViewer {
37362
37729
  * @return {Promise<{ default: Function }>} MP3ControlsV2 module
37363
37730
  */
37364
37731
  importV2Controls() {
37365
- return Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 7450));
37732
+ return Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 673));
37366
37733
  }
37367
37734
 
37368
37735
  /**
@@ -37384,7 +37751,12 @@ class MP3Viewer extends media_MediaBaseViewer {
37384
37751
  * @inheritdoc
37385
37752
  */
37386
37753
  destroy() {
37754
+ this.clearCommentRangeLoopTimer();
37387
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;
37388
37760
  this.stopReverseShuttle();
37389
37761
  this.shuttleDirection = null;
37390
37762
  this.shuttleRate = 0;
@@ -37397,6 +37769,10 @@ class MP3Viewer extends media_MediaBaseViewer {
37397
37769
  */
37398
37770
  load() {
37399
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();
37400
37776
  this.showAudioLoadingShell();
37401
37777
  this.startConversionWaveformLoad();
37402
37778
  }
@@ -37470,6 +37846,48 @@ class MP3Viewer extends media_MediaBaseViewer {
37470
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;
37471
37847
  }
37472
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
+
37473
37891
  /**
37474
37892
  * @param {number} durationSec
37475
37893
  * @return {boolean}
@@ -37506,30 +37924,40 @@ class MP3Viewer extends media_MediaBaseViewer {
37506
37924
  if (!this.isAudioPlayerV2 || this.destroyed) {
37507
37925
  return;
37508
37926
  }
37509
- const {
37510
- file
37511
- } = this.options;
37512
- const waveform = file?.representations?.entries ? getRepresentation(file, constants/* WAVEFORM_REP_NAME */.u4) : null;
37927
+ const waveform = this.getWaveformRepresentation();
37513
37928
  const template = waveform?.content?.url_template;
37514
- const conversionStatus = waveform?.status;
37515
- const statusState = conversionStatus && typeof conversionStatus === 'object' ? conversionStatus.state : null;
37516
- if (!waveform || !template || !statusState || statusState === constants/* STATUS_ERROR */.uD) {
37929
+ const conversionState = this.getWaveformConversionState();
37930
+ if (!waveform || !template || !conversionState || conversionState === constants/* STATUS_ERROR */.uD) {
37517
37931
  this.startClientWaveformDecode();
37932
+ this.renderUI();
37518
37933
  return;
37519
37934
  }
37520
37935
  this.abortConversionWaveformLoad();
37521
37936
  this.waveformStatus = this.getRepStatus(waveform);
37522
37937
  this.waveformStatus.removeListener('conversionpending', this.resetLoadTimeout);
37523
37938
  const status = this.waveformStatus;
37939
+ const shouldPollConversion = !this.isConversionWaveformAvailable();
37524
37940
  try {
37941
+ if (shouldPollConversion) {
37942
+ this.isWaveformConversionPolling = true;
37943
+ this.renderUI();
37944
+ }
37525
37945
  await status.getPromise();
37946
+ this.isWaveformConversionPolling = false;
37526
37947
  if (this.destroyed || this.waveformStatus !== status || this.waveformPeaksSource) {
37527
37948
  return;
37528
37949
  }
37950
+ if (shouldPollConversion) {
37951
+ this.renderUI();
37952
+ }
37529
37953
  const result = await this.loadConversionWaveformPayload(template);
37530
37954
  this.handleConversionWaveformResult(result, status);
37531
37955
  } catch {
37956
+ this.isWaveformConversionPolling = false;
37532
37957
  this.startClientWaveformDecode();
37958
+ if (shouldPollConversion) {
37959
+ this.renderUI();
37960
+ }
37533
37961
  }
37534
37962
  }
37535
37963
 
@@ -37580,6 +38008,7 @@ class MP3Viewer extends media_MediaBaseViewer {
37580
38008
  }
37581
38009
  this.waveformPeaks = payload.peaks;
37582
38010
  this.waveformPeaksSource = source;
38011
+ this.isWaveformConversionPolling = false;
37583
38012
  this.isWaveformDecodeRetryPending = false;
37584
38013
  if (source === 'conversion') {
37585
38014
  this.waveformDurationSec = payload.durationSec;
@@ -37748,6 +38177,7 @@ class MP3Viewer extends media_MediaBaseViewer {
37748
38177
  });
37749
38178
  }
37750
38179
  this.bindCommentMarkersListener();
38180
+ this.bindCommentRangeDraftListeners();
37751
38181
  this.renderUI();
37752
38182
  }
37753
38183
  /**
@@ -37764,11 +38194,161 @@ class MP3Viewer extends media_MediaBaseViewer {
37764
38194
  }
37765
38195
  this.pendingHostSelectedSeek = null;
37766
38196
  this.exitShuttle();
38197
+ this.handleCommentRangeClear();
37767
38198
  this.mediaEl.pause();
37768
38199
  if (this.mediaEl.currentTime !== marker.time) {
37769
38200
  this.mediaEl.currentTime = marker.time;
37770
38201
  }
37771
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
+ }
37772
38352
  /**
37773
38353
  * @inheritdoc
37774
38354
  */
@@ -37806,12 +38386,19 @@ class MP3Viewer extends media_MediaBaseViewer {
37806
38386
  const Mp3ControlsV2 = this.MP3ControlsV2;
37807
38387
  this.controls.render(/*#__PURE__*/external_react_["default"].createElement(Mp3ControlsV2, MP3Viewer_extends({}, sharedProps, {
37808
38388
  commentMarkers: this.commentMarkers || [],
38389
+ commentRangeDraft: this.commentRangeDraft || null,
37809
38390
  hasStartedPlayback: !!this.userRequestedPlay,
38391
+ isGeneratingWaveform: this.isGeneratingWaveform(),
37810
38392
  keyboardVolumeStep: this.keyboardVolumeStep,
37811
38393
  keyboardZoomStep: this.keyboardZoomStep,
37812
38394
  mediaEl: this.mediaEl,
37813
38395
  onCommentMarkerClick: this.handleCommentMarkerClick,
37814
- 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()
37815
38402
  })));
37816
38403
  return;
37817
38404
  }
@@ -39892,12 +40479,14 @@ function VideoControlsV2({
39892
40479
  onGuideChange,
39893
40480
  onMuteChange,
39894
40481
  onPlayPause,
40482
+ onPlayNextChange,
39895
40483
  onQualityChange,
39896
40484
  onRateChange,
39897
40485
  onSubtitleChange,
39898
40486
  onSubtitlesToggle,
39899
40487
  onTimeChange,
39900
40488
  onVolumeChange,
40489
+ playNext,
39901
40490
  quality,
39902
40491
  rate,
39903
40492
  subtitle,
@@ -39986,9 +40575,11 @@ function VideoControlsV2({
39986
40575
  onAudioTrackChange: onAudioTrackChange,
39987
40576
  onAutoplayChange: onAutoplayChange,
39988
40577
  onGuideChange: onGuideChange,
40578
+ onPlayNextChange: onPlayNextChange,
39989
40579
  onQualityChange: onQualityChange,
39990
40580
  onRateChange: onRateChange,
39991
40581
  onSubtitleChange: onSubtitleChange,
40582
+ playNext: playNext,
39992
40583
  quality: quality,
39993
40584
  rate: rate,
39994
40585
  subtitle: subtitle,
@@ -40146,7 +40737,10 @@ class MP4Viewer extends media_VideoBaseViewer {
40146
40737
  volume: this.mediaEl.volume
40147
40738
  };
40148
40739
  if (this.isVideoPlayerV2) {
40149
- 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
+ })));
40150
40744
  return;
40151
40745
  }
40152
40746
  this.controls.render(/*#__PURE__*/external_react_["default"].createElement(VideoControls, MP4Viewer_extends({}, sharedProps, {
@@ -41642,7 +42236,10 @@ class DashViewer extends media_VideoBaseViewer {
41642
42236
  this.controls.render(/*#__PURE__*/external_react_["default"].createElement(VideoControlsV2, DashViewer_extends({
41643
42237
  commentMarkers: this.commentMarkers,
41644
42238
  onCommentMarkerClick: this.handleCommentMarkerClick
41645
- }, sharedProps)));
42239
+ }, sharedProps, {
42240
+ onPlayNextChange: this.setPlayNext,
42241
+ playNext: this.isPlayNextEnabled()
42242
+ })));
41646
42243
  return;
41647
42244
  }
41648
42245
  this.controls.render(/*#__PURE__*/external_react_["default"].createElement(VideoControls, DashViewer_extends({}, sharedProps, {
@@ -54310,6 +54907,7 @@ class Preview extends (events_default()) {
54310
54907
  this.viewer.destroy();
54311
54908
  }
54312
54909
  this.viewer = undefined;
54910
+ Preview.resin = null;
54313
54911
  }
54314
54912
 
54315
54913
  /**
@@ -55195,6 +55793,7 @@ class Preview extends (events_default()) {
55195
55793
 
55196
55794
  // Optional resin analytics instance for tracking user interactions
55197
55795
  this.options.resin = options.resin;
55796
+ Preview.resin = options.resin;
55198
55797
 
55199
55798
  // Options that are applicable to certain file ids
55200
55799
  this.options.fileOptions = options.fileOptions || {};
@@ -55570,6 +56169,10 @@ class Preview extends (events_default()) {
55570
56169
  this.emit(data.event, data.data);
55571
56170
  this.emit(lib_events/* VIEWER_EVENT */.Oi.default, data);
55572
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;
55573
56176
  case lib_events/* VIEWER_EVENT */.Oi.error:
55574
56177
  // Do nothing since 'error' event was already caught, and will be emitted
55575
56178
  // as a 'preview_error' event