box-content-preview 3.92.0 → 3.94.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.94.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
+ /***/ 8862
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;
@@ -6005,6 +5605,25 @@ function usePlayheadCamera({
6005
5605
  seekTo
6006
5606
  };
6007
5607
  }
5608
+ ;// ./src/lib/viewers/controls/icons/IconComment24.tsx
5609
+ 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); }
5610
+
5611
+ function IconComment24(props) {
5612
+ return /*#__PURE__*/external_react_.createElement("svg", _extends({
5613
+ "data-testid": "IconComment24",
5614
+ fill: "none",
5615
+ focusable: false,
5616
+ height: 24,
5617
+ viewBox: "0 0 24 24",
5618
+ width: 24
5619
+ }, props), /*#__PURE__*/external_react_.createElement("path", {
5620
+ clipRule: "evenodd",
5621
+ d: "M4 11c0-2.297.683-3.999 1.926-5.138C7.18 4.712 9.155 4 12 4c2.845 0 4.819.711 6.074 1.862C19.317 7.001 20 8.702 20 11c0 2.297-.683 3.999-1.926 5.138C16.82 17.288 14.845 18 12 18h-.459a1.1 1.1 0 0 0-.774.318C9.665 19.41 8.243 20.048 7.16 20.2c-.553.078-.905.013-1.07-.065C6 20.09 6 20.084 6 20V20c0-.22.072-.347.361-.813.285-.458.665-1.104.685-2.104a1.1 1.1 0 0 0-.606-1.005C4.885 15.298 4 13.745 4 11Zm8-9c-3.155 0-5.681.789-7.426 2.388C2.817 5.999 2 8.298 2 11c0 2.938.91 5.227 2.966 6.539a2.79 2.79 0 0 1-.303.592l-.055.088C4.372 18.591 4 19.18 4 20c0 .912.496 1.59 1.23 1.94.657.313 1.453.345 2.205.24 1.428-.2 3.108-.95 4.464-2.18H12c3.155 0 5.681-.789 7.426-2.388C21.183 16.001 22 13.702 22 11c0-2.703-.817-5.001-2.574-6.612C17.68 2.788 15.155 2 12 2ZM8 12a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm5-1a1 1 0 1 1-2 0 1 1 0 0 1 2 0Zm3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z",
5622
+ fill: "currentColor",
5623
+ fillRule: "evenodd"
5624
+ }));
5625
+ }
5626
+ /* harmony default export */ const icons_IconComment24 = (IconComment24);
6008
5627
  ;// ./src/lib/viewers/media/waveform/WaveformRangeSelection.scss
6009
5628
  // extracted by mini-css-extract-plugin
6010
5629
 
@@ -6015,6 +5634,7 @@ function usePlayheadCamera({
6015
5634
 
6016
5635
 
6017
5636
 
5637
+
6018
5638
  function offsetLeftCss(base, offsetPx) {
6019
5639
  if (!offsetPx) {
6020
5640
  return base;
@@ -6026,13 +5646,13 @@ function applyHandleLeft(el, timeSec, durationSec, viewport, collapsedOffsetPx =
6026
5646
  if (!el) {
6027
5647
  return;
6028
5648
  }
6029
- el.style.left = offsetLeftCss(timeLeftPercent(timeSec, durationSec, viewport), collapsedOffsetPx);
5649
+ el.style.left = offsetLeftCss((0,waveform_viewport/* timeLeftPercent */.Mu)(timeSec, durationSec, viewport), collapsedOffsetPx);
6030
5650
  }
6031
5651
  function applyRegion(el, startSec, endSec, durationSec, viewport, collapsed) {
6032
5652
  if (!el) {
6033
5653
  return;
6034
5654
  }
6035
- const start = timeLeftPercent(startSec, durationSec, viewport);
5655
+ const start = (0,waveform_viewport/* timeLeftPercent */.Mu)(startSec, durationSec, viewport);
6036
5656
  const halfLine = constants/* WAVEFORM_RANGE_HANDLE_LINE_PX */.h1 / 2;
6037
5657
  if (collapsed) {
6038
5658
  const extentPx = constants/* WAVEFORM_RANGE_COLLAPSED_OFFSET_PX */.BG + halfLine;
@@ -6040,7 +5660,7 @@ function applyRegion(el, startSec, endSec, durationSec, viewport, collapsed) {
6040
5660
  el.style.width = `${extentPx * 2}px`;
6041
5661
  return;
6042
5662
  }
6043
- const end = timeLeftPercent(endSec, durationSec, viewport);
5663
+ const end = (0,waveform_viewport/* timeLeftPercent */.Mu)(endSec, durationSec, viewport);
6044
5664
  el.style.left = offsetLeftCss(start, -halfLine);
6045
5665
  el.style.width = `calc(${end} - ${start} + ${constants/* WAVEFORM_RANGE_HANDLE_LINE_PX */.h1}px)`;
6046
5666
  }
@@ -6051,6 +5671,7 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6051
5671
  interactive = true,
6052
5672
  isHighlighted = false,
6053
5673
  keepHighlight = true,
5674
+ onDragCreate,
6054
5675
  onDragChange,
6055
5676
  onPreviewChange,
6056
5677
  onRangeChange,
@@ -6062,9 +5683,10 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6062
5683
  const startHandleRef = (0,external_react_.useRef)(null);
6063
5684
  const endHandleRef = (0,external_react_.useRef)(null);
6064
5685
  const tooltipRef = (0,external_react_.useRef)(null);
5686
+ const commentRef = (0,external_react_.useRef)(null);
6065
5687
  const dragRef = (0,external_react_.useRef)(null);
6066
5688
  const rangeRef = (0,external_react_.useRef)(range);
6067
- const displayedRef = (0,external_react_.useRef)(resolveRange(range, durationMsFromSec(durationSec)));
5689
+ const displayedRef = (0,external_react_.useRef)((0,waveform_range/* resolveRange */.Ss)(range, (0,waveform_range/* durationMsFromSec */.uH)(durationSec)));
6068
5690
  const viewportRef = (0,external_react_.useRef)(viewport);
6069
5691
  const durationSecRef = (0,external_react_.useRef)(durationSec);
6070
5692
  const onDragChangeRef = (0,external_react_.useRef)(onDragChange);
@@ -6081,9 +5703,10 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6081
5703
  onRangeChangeRef.current = onRangeChange;
6082
5704
  getPlayheadSecRef.current = getPlayheadSec;
6083
5705
  activeHandleRef.current = activeHandle;
6084
- const durationMs = durationMsFromSec(durationSec);
6085
- const displayed = dragRange ?? resolveRange(range, durationMs);
5706
+ const durationMs = (0,waveform_range/* durationMsFromSec */.uH)(durationSec);
5707
+ const displayed = dragRange ?? (0,waveform_range/* resolveRange */.Ss)(range, durationMs);
6086
5708
  displayedRef.current = displayed;
5709
+ const showCommentButton = Boolean(onDragCreate) && displayed.startMs !== displayed.endMs && activeHandle == null;
6087
5710
  const syncPositions = (0,external_react_.useCallback)((next, nextViewport, nextDurationSec) => {
6088
5711
  displayedRef.current = next;
6089
5712
  const collapsed = next.startMs === next.endMs;
@@ -6094,7 +5717,11 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6094
5717
  if (tooltip) {
6095
5718
  const tooltipHandle = activeHandleRef.current ?? 'end';
6096
5719
  const tooltipMs = tooltipHandle === 'start' ? next.startMs : next.endMs;
6097
- tooltip.style.left = timeLeftPercent(tooltipMs / 1000, nextDurationSec, nextViewport);
5720
+ tooltip.style.left = (0,waveform_viewport/* timeLeftPercent */.Mu)(tooltipMs / 1000, nextDurationSec, nextViewport);
5721
+ }
5722
+ const comment = commentRef.current;
5723
+ if (comment && next.startMs !== next.endMs) {
5724
+ comment.style.left = (0,waveform_viewport/* timeLeftPercent */.Mu)((next.startMs + next.endMs) / 2000, nextDurationSec, nextViewport);
6098
5725
  }
6099
5726
  }, []);
6100
5727
  (0,external_react_.useImperativeHandle)(ref, () => ({
@@ -6105,9 +5732,9 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6105
5732
  }), [syncPositions]);
6106
5733
  (0,external_react_.useLayoutEffect)(() => {
6107
5734
  syncPositions(displayedRef.current, viewportRef.current, durationSec);
6108
- }, [activeHandle, displayed.endMs, displayed.startMs, durationSec, syncPositions]);
5735
+ }, [activeHandle, displayed.endMs, displayed.startMs, durationSec, showCommentButton, syncPositions]);
6109
5736
  (0,external_react_.useLayoutEffect)(() => {
6110
- const next = createWaveformViewport({
5737
+ const next = (0,waveform_viewport/* createWaveformViewport */.eq)({
6111
5738
  durationSec: viewport.durationSec,
6112
5739
  heightPx: viewport.heightPx,
6113
5740
  maxZoom: viewport.maxZoom,
@@ -6136,7 +5763,7 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6136
5763
  if (!didChange) {
6137
5764
  return;
6138
5765
  }
6139
- const committed = commitRangeChange(drag.range);
5766
+ const committed = (0,waveform_range/* commitRangeChange */.dG)(drag.range);
6140
5767
  if (committed) {
6141
5768
  onRangeChangeRef.current?.(committed);
6142
5769
  }
@@ -6153,17 +5780,17 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6153
5780
  const rect = root.getBoundingClientRect();
6154
5781
  const pointerX = event.clientX - rect.left;
6155
5782
  const vp = viewportRef.current;
6156
- const duration = durationMsFromSec(durationSecRef.current);
5783
+ const duration = (0,waveform_range/* durationMsFromSec */.uH)(durationSecRef.current);
6157
5784
  const playheadSec = getPlayheadSecRef.current?.() ?? currentTimeSec;
6158
- const pointerMs = snapTimeMs({
5785
+ const pointerMs = (0,waveform_range/* snapTimeMs */.Mh)({
6159
5786
  pixelsPerSecond: vp.pixelsPerSecond,
6160
5787
  playheadMs: playheadSec * 1000,
6161
- timeMs: pointerTimeMs(pointerX, vp)
5788
+ timeMs: (0,waveform_range/* pointerTimeMs */.v2)(pointerX, vp)
6162
5789
  });
6163
5790
  if (drag.originMs === drag.range.startMs && drag.originMs === drag.range.endMs && pointerMs === drag.originMs) {
6164
5791
  return;
6165
5792
  }
6166
- const next = dragRangeHandle({
5793
+ const next = (0,waveform_range/* dragRangeHandle */.Mo)({
6167
5794
  durationMs: duration,
6168
5795
  handle: drag.handle,
6169
5796
  pointerMs,
@@ -6188,7 +5815,7 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6188
5815
  }
6189
5816
  event.preventDefault();
6190
5817
  event.stopPropagation();
6191
- const resolved = resolveRange(rangeRef.current, durationMsFromSec(durationSecRef.current));
5818
+ const resolved = (0,waveform_range/* resolveRange */.Ss)(rangeRef.current, (0,waveform_range/* durationMsFromSec */.uH)(durationSecRef.current));
6192
5819
  dragRef.current = {
6193
5820
  handle,
6194
5821
  originMs: handle === 'start' ? resolved.startMs : resolved.endMs,
@@ -6204,7 +5831,7 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6204
5831
  setDragRange(resolved);
6205
5832
  onDragChangeRef.current?.(true);
6206
5833
  onPreviewChangeRef.current?.({
6207
- endMs: isRangeCollapsed(rangeRef.current) ? null : resolved.endMs,
5834
+ endMs: (0,waveform_range/* isRangeCollapsed */.BT)(rangeRef.current) ? null : resolved.endMs,
6208
5835
  startMs: resolved.startMs
6209
5836
  });
6210
5837
  }, [interactive]);
@@ -6216,15 +5843,15 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6216
5843
  const rect = root.getBoundingClientRect();
6217
5844
  const pointerX = event.clientX - rect.left;
6218
5845
  const vp = viewportRef.current;
6219
- const resolved = resolveRange(rangeRef.current, durationMsFromSec(durationSecRef.current));
6220
- const handle = pickRangeHandle({
6221
- endX: visualRangeHandlePx({
5846
+ const resolved = (0,waveform_range/* resolveRange */.Ss)(rangeRef.current, (0,waveform_range/* durationMsFromSec */.uH)(durationSecRef.current));
5847
+ const handle = (0,waveform_range/* pickRangeHandle */.HG)({
5848
+ endX: (0,waveform_range/* visualRangeHandlePx */.Eh)({
6222
5849
  handle: 'end',
6223
5850
  range: resolved,
6224
5851
  viewport: vp
6225
5852
  }),
6226
5853
  pointerX,
6227
- startX: visualRangeHandlePx({
5854
+ startX: (0,waveform_range/* visualRangeHandlePx */.Eh)({
6228
5855
  handle: 'start',
6229
5856
  range: resolved,
6230
5857
  viewport: vp
@@ -6271,6 +5898,22 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6271
5898
  const tooltipMs = tooltipHandle === 'start' ? displayed.startMs : displayed.endMs;
6272
5899
  const collapsed = displayed.startMs === displayed.endMs;
6273
5900
  const highlight = isHighlighted || collapsed;
5901
+ const commentButton = showCommentButton && onDragCreate ? /*#__PURE__*/external_react_["default"].createElement("button", {
5902
+ ref: commentRef,
5903
+ className: "bp-WaveformRange-comment",
5904
+ "data-testid": "bp-waveform-range-comment",
5905
+ onClick: event => {
5906
+ event.stopPropagation();
5907
+ onDragCreate();
5908
+ },
5909
+ onPointerDown: event => {
5910
+ event.stopPropagation();
5911
+ },
5912
+ type: "button"
5913
+ }, /*#__PURE__*/external_react_["default"].createElement(icons_IconComment24, {
5914
+ "aria-hidden": "true",
5915
+ className: "bp-WaveformRange-commentIcon"
5916
+ }), "Comment") : null;
6274
5917
  return /*#__PURE__*/external_react_["default"].createElement("div", {
6275
5918
  ref: rootRef,
6276
5919
  className: `bp-WaveformRange${keepHighlight ? ' bp-WaveformRange--persist' : ''}${highlight ? ' bp-WaveformRange--highlight' : ''}`,
@@ -6303,18 +5946,18 @@ const WaveformRangeSelection = /*#__PURE__*/(0,external_react_.forwardRef)(funct
6303
5946
  "data-testid": "bp-waveform-range-tooltip"
6304
5947
  }, /*#__PURE__*/external_react_["default"].createElement("div", {
6305
5948
  className: "bp-WaveformRange-tooltipTime"
6306
- }, formatTime(tooltipMs / 1000))));
5949
+ }, formatTime(tooltipMs / 1000))), commentButton);
6307
5950
  });
6308
5951
  /* harmony default export */ const waveform_WaveformRangeSelection = (/*#__PURE__*/external_react_["default"].memo(WaveformRangeSelection));
6309
5952
  ;// ./src/lib/viewers/media/waveform/WaveformView.scss
6310
5953
  // extracted by mini-css-extract-plugin
6311
5954
 
6312
5955
  ;// ./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); }
5956
+ 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; }
5957
+ 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; }
5958
+ 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; }
5959
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
5960
+ 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
5961
 
6319
5962
 
6320
5963
 
@@ -6423,6 +6066,46 @@ function zoomOriginAtPointer(pointerX, wavesurfer, durationSec, fallbackWidth) {
6423
6066
  timeSec: (WaveformView_getScrollLeft(wavesurfer) + pointerX) / fullWidth * durationSec
6424
6067
  };
6425
6068
  }
6069
+ /** Null when the press should stay a click-to-seek or is not on an empty span of the track. */
6070
+ function rangeCreateDragFromEvent(event, source) {
6071
+ const {
6072
+ track
6073
+ } = source;
6074
+ if (!track || !(source.durationSec > 0) || !Number.isFinite(event.clientX)) {
6075
+ return null;
6076
+ }
6077
+ const rect = track.getBoundingClientRect();
6078
+ if (!(rect.width > 0)) {
6079
+ return null;
6080
+ }
6081
+ const pointerX = event.clientX - rect.left;
6082
+ if (source.range && !(0,waveform_range/* isRangeCollapsed */.BT)(source.range) && (0,waveform_range/* isPointerOverRange */.H5)({
6083
+ pointerX,
6084
+ range: source.range,
6085
+ viewport: source.viewport
6086
+ })) {
6087
+ return null;
6088
+ }
6089
+ return {
6090
+ active: false,
6091
+ originMs: (0,waveform_range/* clampTimeMs */.DL)((0,waveform_range/* pointerTimeMs */.v2)(pointerX, source.viewport), (0,waveform_range/* durationMsFromSec */.uH)(source.durationSec)),
6092
+ originX: event.clientX,
6093
+ pointerId: event.pointerId,
6094
+ range: null
6095
+ };
6096
+ }
6097
+ function drawnRangeFromMove(drag, clientX, track, viewport, playheadSec, durationSec) {
6098
+ const pointerMs = (0,waveform_range/* snapTimeMs */.Mh)({
6099
+ pixelsPerSecond: viewport.pixelsPerSecond,
6100
+ playheadMs: playheadSec * 1000,
6101
+ timeMs: (0,waveform_range/* pointerTimeMs */.v2)(clientX - track.getBoundingClientRect().left, viewport)
6102
+ });
6103
+ return (0,waveform_range/* rangeFromCreateDrag */.Qp)({
6104
+ durationMs: (0,waveform_range/* durationMsFromSec */.uH)(durationSec),
6105
+ originMs: drag.originMs,
6106
+ pointerMs
6107
+ });
6108
+ }
6426
6109
 
6427
6110
  /**
6428
6111
  * Renders V1 peaks with wavesurfer. Does not fetch audio or attach a media element.
@@ -6438,6 +6121,8 @@ function WaveformView({
6438
6121
  mediaEl,
6439
6122
  onPlayPause,
6440
6123
  onRangeChange,
6124
+ onRangeClear,
6125
+ onRangeDragCreate,
6441
6126
  onRangeDragChange,
6442
6127
  onSeek,
6443
6128
  onViewportChange,
@@ -6464,6 +6149,9 @@ function WaveformView({
6464
6149
  const mediaElRef = (0,external_react_.useRef)(mediaEl); // latest <audio>/<video>; camera + playhead tick read this
6465
6150
  const onPlayPauseRef = (0,external_react_.useRef)(onPlayPause); // latest play/pause; tape tap must not re-bind
6466
6151
  const onSeekRef = (0,external_react_.useRef)(onSeek); // latest onSeek; click/scroll handlers must not re-bind
6152
+ const onRangeChangeRef = (0,external_react_.useRef)(onRangeChange); // latest range commit; drag-create must not re-bind
6153
+ const onRangeClearRef = (0,external_react_.useRef)(onRangeClear); // latest click-outside clear; WaveSurfer click must not re-bind
6154
+ const onRangeDragChangeRef = (0,external_react_.useRef)(onRangeDragChange); // latest drag flag; unmount must not re-bind
6467
6155
  const onViewportChangeRef = (0,external_react_.useRef)(onViewportChange); // latest viewport callback; camera commits here
6468
6156
  const peaksRef = (0,external_react_.useRef)(peaks); // latest peaks; WaveSurfer create() + morph read this
6469
6157
 
@@ -6496,6 +6184,9 @@ function WaveformView({
6496
6184
  mediaElRef.current = mediaEl;
6497
6185
  onPlayPauseRef.current = onPlayPause;
6498
6186
  onSeekRef.current = onSeek;
6187
+ onRangeChangeRef.current = onRangeChange;
6188
+ onRangeClearRef.current = onRangeClear;
6189
+ onRangeDragChangeRef.current = onRangeDragChange;
6499
6190
  onViewportChangeRef.current = onViewportChange;
6500
6191
  peaksRef.current = peaks;
6501
6192
  const [internalZoom, setInternalZoom] = (0,external_react_.useState)(constants/* WAVEFORM_ZOOM_MIN */.LW);
@@ -6505,19 +6196,23 @@ function WaveformView({
6505
6196
  const [canvasWidthPx, setCanvasWidthPx] = (0,external_react_.useState)(0);
6506
6197
  const [scrollLeft, setScrollLeft] = (0,external_react_.useState)(0);
6507
6198
  const [previewRange, setPreviewRange] = (0,external_react_.useState)(null);
6199
+ const [createRange, setCreateRange] = (0,external_react_.useState)(null);
6508
6200
  const [isRangeDragging, setIsRangeDragging] = (0,external_react_.useState)(false);
6509
6201
  const isRangeDraggingRef = (0,external_react_.useRef)(false);
6202
+ const rangeRef = (0,external_react_.useRef)(range); // latest committed draft; drag-create must not start on top of an open span
6203
+ const createDragRef = (0,external_react_.useRef)(null);
6204
+ const removeCreateDragListenersRef = (0,external_react_.useRef)(null);
6510
6205
  const skipNextSeekRef = (0,external_react_.useRef)(false);
6511
6206
  const lastSetTimeSecRef = (0,external_react_.useRef)(null);
6512
6207
  const [isRangeHovered, setIsRangeHovered] = (0,external_react_.useState)(false);
6513
6208
  const isControlled = typeof zoomLevelProp === 'number';
6514
- const maxZoom = getWaveformZoomMax({
6209
+ const maxZoom = (0,waveform_viewport/* getWaveformZoomMax */.by)({
6515
6210
  durationSec,
6516
6211
  peakCount: peaks.length,
6517
6212
  viewWidthPx: canvasWidthPx
6518
6213
  });
6519
6214
  hasZoomHandlersRef.current = maxZoom > constants/* WAVEFORM_ZOOM_MIN */.LW && (typeof onZoomChange === 'function' || typeof zoomLevelProp !== 'number');
6520
- const zoomLevel = clampWaveformZoom(isControlled ? zoomLevelProp : internalZoom, maxZoom);
6215
+ const zoomLevel = (0,waveform_viewport/* clampWaveformZoom */.ii)(isControlled ? zoomLevelProp : internalZoom, maxZoom);
6521
6216
  const zoomRef = (0,external_react_.useRef)(zoomLevel); // latest zoom for WaveSurfer redraw/zoom handlers
6522
6217
  zoomRef.current = zoomLevel;
6523
6218
  const prevZoomRef = (0,external_react_.useRef)(null); // last applied zoom; tape recenters only when this changes
@@ -6527,25 +6222,26 @@ function WaveformView({
6527
6222
  const bufferProgress = getBufferedProgress(bufferedRange, durationSec);
6528
6223
  bufferProgressRef.current = bufferProgress;
6529
6224
  hoverProgressRef.current = isRangeDragging ? null : hoverProgress;
6530
- const activeRange = isRangeDragging ? previewRange ?? range : range;
6225
+ rangeRef.current = range;
6226
+ const activeRange = (isRangeDragging ? previewRange ?? createRange : createRange) ?? range;
6531
6227
  const activeRangeRef = (0,external_react_.useRef)(activeRange);
6532
6228
  activeRangeRef.current = activeRange;
6533
- const durationMs = durationMsFromSec(durationSec);
6229
+ const durationMs = (0,waveform_range/* durationMsFromSec */.uH)(durationSec);
6534
6230
  const rangeStartMs = activeRange?.startMs;
6535
6231
  const rangeEndMs = activeRange?.endMs;
6536
6232
  const activeRangeProgress = (0,external_react_.useMemo)(() => {
6537
6233
  if (rangeStartMs == null || !Number.isFinite(rangeStartMs)) {
6538
6234
  return null;
6539
6235
  }
6540
- return rangeProgress(resolveRange({
6236
+ return (0,waveform_range/* rangeProgress */.D$)((0,waveform_range/* resolveRange */.Ss)({
6541
6237
  endMs: rangeEndMs ?? null,
6542
6238
  startMs: rangeStartMs
6543
6239
  }, durationMs), durationMs);
6544
6240
  }, [durationMs, rangeEndMs, rangeStartMs]);
6545
6241
  rangeProgressRef.current = activeRangeProgress;
6546
- const viewport = (0,external_react_.useMemo)(() => createWaveformViewport({
6242
+ const viewport = (0,external_react_.useMemo)(() => (0,waveform_viewport/* createWaveformViewport */.eq)({
6547
6243
  durationSec,
6548
- gutterPx: isTape ? getTapeGutterPx(canvasWidthPx) : 0,
6244
+ gutterPx: isTape ? (0,waveform_viewport/* getTapeGutterPx */.iA)(canvasWidthPx) : 0,
6549
6245
  heightPx: height,
6550
6246
  maxZoom,
6551
6247
  scrollLeftPx: scrollLeft,
@@ -6603,7 +6299,7 @@ function WaveformView({
6603
6299
  }, [isUserPanning]);
6604
6300
  toggleTapePlaybackRef.current = toggleTapePlayback;
6605
6301
  (0,external_react_.useLayoutEffect)(() => {
6606
- viewportRef.current = createWaveformViewport({
6302
+ viewportRef.current = (0,waveform_viewport/* createWaveformViewport */.eq)({
6607
6303
  durationSec: viewport.durationSec,
6608
6304
  gutterPx: viewport.gutterPx,
6609
6305
  heightPx: viewport.heightPx,
@@ -6614,7 +6310,7 @@ function WaveformView({
6614
6310
  });
6615
6311
  }, [viewport]);
6616
6312
  const setZoomLevel = (0,external_react_.useCallback)(nextZoom => {
6617
- const zoom = clampWaveformZoom(nextZoom, maxZoom);
6313
+ const zoom = (0,waveform_viewport/* clampWaveformZoom */.ii)(nextZoom, maxZoom);
6618
6314
  if (!isControlled) {
6619
6315
  setInternalZoom(zoom);
6620
6316
  }
@@ -6669,7 +6365,7 @@ function WaveformView({
6669
6365
  return;
6670
6366
  }
6671
6367
  const scrollLeftPx = WaveformView_getScrollLeft(wavesurfer);
6672
- viewportRef.current = getViewportAtScroll(viewportRef.current, scrollLeftPx);
6368
+ viewportRef.current = (0,waveform_viewport/* getViewportAtScroll */.NI)(viewportRef.current, scrollLeftPx);
6673
6369
  rangeLayerRef.current?.applyViewport(viewportRef.current);
6674
6370
  onViewportChangeRef.current?.(viewportRef.current);
6675
6371
  setScrollLeft(scrollLeftPx);
@@ -6680,9 +6376,9 @@ function WaveformView({
6680
6376
  return;
6681
6377
  }
6682
6378
  if (isTape) {
6683
- playhead.style.left = getTapePinnedPlayheadLeft();
6379
+ playhead.style.left = (0,waveform_viewport/* getTapePinnedPlayheadLeft */.yW)();
6684
6380
  } else if (!isFollowPinned()) {
6685
- playhead.style.left = timeLeftPercent(timeSec, durationSecRef.current, viewportRef.current);
6381
+ playhead.style.left = (0,waveform_viewport/* timeLeftPercent */.Mu)(timeSec, durationSecRef.current, viewportRef.current);
6686
6382
  }
6687
6383
  rangeLayerRef.current?.applyViewport(viewportRef.current);
6688
6384
  const wavesurfer = wavesurferRef.current;
@@ -6700,14 +6396,14 @@ function WaveformView({
6700
6396
  return;
6701
6397
  }
6702
6398
  const viewWidthPx = wavesurfer.getWidth ? wavesurfer.getWidth() : container.clientWidth;
6703
- const minPxPerSec = getZoomedPixelsPerSecond({
6399
+ const minPxPerSec = (0,waveform_viewport/* getZoomedPixelsPerSecond */.J$)({
6704
6400
  durationSec,
6705
6401
  isTape,
6706
6402
  maxZoom,
6707
6403
  viewWidthPx,
6708
6404
  zoomLevel
6709
6405
  });
6710
- const gutterPx = isTape ? getTapeGutterPx(viewWidthPx) : 0;
6406
+ const gutterPx = isTape ? (0,waveform_viewport/* getTapeGutterPx */.iA)(viewWidthPx) : 0;
6711
6407
  const origin = isTape ? null : zoomOriginRef.current;
6712
6408
  zoomOriginRef.current = null;
6713
6409
  const didZoomChange = prevZoomRef.current !== zoomLevel;
@@ -6716,7 +6412,7 @@ function WaveformView({
6716
6412
  }
6717
6413
  suppressViewportSyncRef.current = true;
6718
6414
  try {
6719
- wavesurfer.setOptions(WaveformView_objectSpread({
6415
+ wavesurfer.setOptions(_objectSpread({
6720
6416
  autoScroll: false,
6721
6417
  fillParent: !isTape,
6722
6418
  minPxPerSec
@@ -6726,7 +6422,7 @@ function WaveformView({
6726
6422
  } : {}));
6727
6423
  applyWaveformGutters(wavesurfer, gutterPx);
6728
6424
  if (minPxPerSec > 0) {
6729
- const windowViewport = createWaveformViewport({
6425
+ const windowViewport = (0,waveform_viewport/* createWaveformViewport */.eq)({
6730
6426
  durationSec,
6731
6427
  gutterPx,
6732
6428
  heightPx: liveHeightRef.current || height,
@@ -6738,7 +6434,7 @@ function WaveformView({
6738
6434
  if (origin) {
6739
6435
  applyScrollLeft(origin.timeSec * minPxPerSec + gutterPx - origin.pointerX, true);
6740
6436
  } else if (didZoomChange && (isTape || !pointerZoomRef.current)) {
6741
- applyScrollLeft(getCenteredScrollLeft(currentTimeRef.current, windowViewport), true);
6437
+ applyScrollLeft((0,waveform_viewport/* getCenteredScrollLeft */.b6)(currentTimeRef.current, windowViewport), true);
6742
6438
  }
6743
6439
  }
6744
6440
  lastSetTimeSecRef.current = currentTimeRef.current;
@@ -6787,11 +6483,23 @@ function WaveformView({
6787
6483
  if (!interactiveRef.current || isRangeDraggingRef.current) {
6788
6484
  return;
6789
6485
  }
6486
+ const rangeDraft = activeRangeRef.current;
6487
+ const timeSec = relativeX * durationSecRef.current;
6488
+ if (rangeDraft && !(0,waveform_range/* isRangeCollapsed */.BT)(rangeDraft)) {
6489
+ const pointerX = (0,waveform_viewport/* positionPxFromTime */.TO)(timeSec, viewportRef.current);
6490
+ if (!(0,waveform_range/* isPointerOverRange */.H5)({
6491
+ pointerX,
6492
+ range: rangeDraft,
6493
+ viewport: viewportRef.current
6494
+ })) {
6495
+ onRangeClearRef.current?.();
6496
+ }
6497
+ }
6790
6498
  if (cameraModeRef.current === 'tape') {
6791
6499
  toggleTapePlaybackRef.current?.();
6792
6500
  return;
6793
6501
  }
6794
- onSeekRef.current?.(relativeX * durationSecRef.current);
6502
+ onSeekRef.current?.(timeSec);
6795
6503
  });
6796
6504
  const unsubscribeScroll = wavesurfer.on('scroll', () => {
6797
6505
  if (suppressViewportSyncRef.current) {
@@ -6884,7 +6592,7 @@ function WaveformView({
6884
6592
  }, [syncViewport]);
6885
6593
  (0,external_react_.useEffect)(() => {
6886
6594
  const rawZoom = isControlled ? zoomLevelProp : internalZoom;
6887
- const clamped = clampWaveformZoom(typeof rawZoom === 'number' ? rawZoom : constants/* WAVEFORM_ZOOM_MIN */.LW, maxZoom);
6595
+ const clamped = (0,waveform_viewport/* clampWaveformZoom */.ii)(typeof rawZoom === 'number' ? rawZoom : constants/* WAVEFORM_ZOOM_MIN */.LW, maxZoom);
6888
6596
  if (clamped === rawZoom) {
6889
6597
  return;
6890
6598
  }
@@ -7104,17 +6812,17 @@ function WaveformView({
7104
6812
  return;
7105
6813
  }
7106
6814
  const vp = viewportRef.current;
7107
- const draft = activeRangeRef.current;
7108
- setIsRangeHovered(!!draft && isPointerOverRange({
6815
+ const rangeDraft = activeRangeRef.current;
6816
+ setIsRangeHovered(!!rangeDraft && (0,waveform_range/* isPointerOverRange */.H5)({
7109
6817
  pointerX,
7110
- range: draft,
6818
+ range: rangeDraft,
7111
6819
  viewport: vp
7112
6820
  }));
7113
6821
  if (!interactive || isRangeDraggingRef.current || !(durationSec > 0)) {
7114
6822
  return;
7115
6823
  }
7116
6824
  if (vp.pixelsPerSecond > 0) {
7117
- setHoverProgress(Math.min(1, Math.max(0, timeFromPositionPx(pointerX, vp) / durationSec)));
6825
+ setHoverProgress(Math.min(1, Math.max(0, (0,waveform_viewport/* timeFromPositionPx */.lZ)(pointerX, vp) / durationSec)));
7118
6826
  return;
7119
6827
  }
7120
6828
  setHoverProgress(Math.min(1, Math.max(0, pointerX / rect.width)));
@@ -7140,12 +6848,104 @@ function WaveformView({
7140
6848
  const getPlayheadSec = (0,external_react_.useCallback)(() => {
7141
6849
  return mediaElRef.current ? mediaElRef.current.currentTime : currentTimeRef.current;
7142
6850
  }, []);
6851
+ (0,external_react_.useEffect)(() => {
6852
+ return () => {
6853
+ removeCreateDragListenersRef.current?.();
6854
+ if (createDragRef.current?.active) {
6855
+ createDragRef.current = null;
6856
+ onRangeDragChangeRef.current?.(false);
6857
+ }
6858
+ };
6859
+ }, []);
6860
+ (0,external_react_.useLayoutEffect)(() => {
6861
+ if (isRangeDraggingRef.current) {
6862
+ return;
6863
+ }
6864
+ setCreateRange(current => current == null ? current : null);
6865
+ }, [range]);
6866
+ const onTrackPointerDown = (0,external_react_.useCallback)(event => {
6867
+ if (cameraModeRef.current === 'tape' || !interactiveRef.current || event.button > 0 || event.ctrlKey || event.metaKey || isRangeDraggingRef.current || createDragRef.current) {
6868
+ return;
6869
+ }
6870
+ const {
6871
+ target
6872
+ } = event;
6873
+ if (target instanceof Element && target.closest('[data-testid="bp-waveform-range-handle-start"], [data-testid="bp-waveform-range-handle-end"], [data-testid="bp-waveform-range-comment"]')) {
6874
+ return;
6875
+ }
6876
+ const drag = rangeCreateDragFromEvent(event, {
6877
+ durationSec: durationSecRef.current,
6878
+ range: rangeRef.current,
6879
+ track: trackRef.current,
6880
+ viewport: viewportRef.current
6881
+ });
6882
+ if (!drag) {
6883
+ return;
6884
+ }
6885
+ createDragRef.current = drag;
6886
+ const onMove = moveEvent => {
6887
+ if (createDragRef.current !== drag || moveEvent.pointerId !== drag.pointerId) {
6888
+ return;
6889
+ }
6890
+ if (!Number.isFinite(moveEvent.clientX)) {
6891
+ return;
6892
+ }
6893
+ if (!drag.active && Math.abs(moveEvent.clientX - drag.originX) < constants/* WAVEFORM_RANGE_CREATE_DRAG_PX */.B4) {
6894
+ return;
6895
+ }
6896
+ const track = trackRef.current;
6897
+ if (!track) {
6898
+ return;
6899
+ }
6900
+ moveEvent.preventDefault();
6901
+ const playheadSec = mediaElRef.current ? mediaElRef.current.currentTime : currentTimeRef.current;
6902
+ drag.range = drawnRangeFromMove(drag, moveEvent.clientX, track, viewportRef.current, playheadSec, durationSecRef.current);
6903
+ if (!drag.active) {
6904
+ drag.active = true;
6905
+ handleRangeDragChange(true);
6906
+ }
6907
+ setCreateRange(drag.range);
6908
+ setPreviewRange(drag.range);
6909
+ };
6910
+ let onUp = () => undefined;
6911
+ const removeListeners = () => {
6912
+ window.removeEventListener('pointermove', onMove);
6913
+ window.removeEventListener('pointerup', onUp);
6914
+ window.removeEventListener('pointercancel', onUp);
6915
+ if (removeCreateDragListenersRef.current === removeListeners) {
6916
+ removeCreateDragListenersRef.current = null;
6917
+ }
6918
+ };
6919
+ onUp = upEvent => {
6920
+ if (createDragRef.current !== drag || upEvent.pointerId && upEvent.pointerId !== drag.pointerId) {
6921
+ return;
6922
+ }
6923
+ const wasActive = drag.active;
6924
+ const drawn = drag.range;
6925
+ createDragRef.current = null;
6926
+ removeListeners();
6927
+ if (!wasActive || !drawn) {
6928
+ return;
6929
+ }
6930
+ const committed = (0,waveform_range/* commitRangeChange */.dG)(drawn);
6931
+ if (committed) {
6932
+ setCreateRange(committed);
6933
+ onRangeChangeRef.current?.(committed);
6934
+ }
6935
+ handleRangeDragChange(false);
6936
+ };
6937
+ removeCreateDragListenersRef.current = removeListeners;
6938
+ window.addEventListener('pointermove', onMove);
6939
+ window.addEventListener('pointerup', onUp);
6940
+ window.addEventListener('pointercancel', onUp);
6941
+ }, [handleRangeDragChange]);
7143
6942
  const bindOverlayPortalHost = (0,external_react_.useCallback)(node => {
7144
6943
  const host = node?.parentElement ?? null;
7145
6944
  setOverlayPortalHost(prev => prev === host ? prev : host);
7146
6945
  }, []);
7147
- const hoverLeft = isRangeDragging || hoverProgress == null ? null : timeLeftPercent(hoverProgress * durationSec, durationSec, viewportRef.current);
7148
- const showRangeOverlay = Boolean(range) && (isRangeDragging || !isPlaying || !isRangeCollapsed(range));
6946
+ const hoverLeft = isRangeDragging || hoverProgress == null ? null : (0,waveform_viewport/* timeLeftPercent */.Mu)(hoverProgress * durationSec, durationSec, viewportRef.current);
6947
+ const overlayRange = createRange ?? range;
6948
+ const showRangeOverlay = Boolean(overlayRange) && (isRangeDragging || !isPlaying || !(0,waveform_range/* isRangeCollapsed */.BT)(overlayRange));
7149
6949
  const scrubTimeChip = isTape && scrubPreviewTimeSec != null ? /*#__PURE__*/external_react_["default"].createElement("div", {
7150
6950
  className: "bp-WaveformView-hover bp-WaveformView-hover--tape",
7151
6951
  "data-testid": "bp-waveform-hover"
@@ -7166,6 +6966,7 @@ function WaveformView({
7166
6966
  className: "bp-WaveformView-track",
7167
6967
  onMouseLeave: interactive && !isTape ? onHoverLeave : undefined,
7168
6968
  onMouseMove: interactive && !isTape ? onHoverMove : undefined,
6969
+ onPointerDown: onTrackPointerDown,
7169
6970
  onPointerMove: interactive && !isTape ? event => {
7170
6971
  if (event.pointerType === 'touch') {
7171
6972
  onHoverLeave();
@@ -7187,7 +6988,7 @@ function WaveformView({
7187
6988
  "aria-hidden": "true",
7188
6989
  className: "bp-WaveformView-playhead",
7189
6990
  "data-testid": "bp-waveform-playhead"
7190
- }), showRangeOverlay && range && /*#__PURE__*/external_react_["default"].createElement(waveform_WaveformRangeSelection, {
6991
+ }), showRangeOverlay && overlayRange && /*#__PURE__*/external_react_["default"].createElement(waveform_WaveformRangeSelection, {
7191
6992
  ref: rangeLayerRef,
7192
6993
  currentTimeSec: currentTime,
7193
6994
  durationSec: durationSec,
@@ -7196,9 +6997,10 @@ function WaveformView({
7196
6997
  isHighlighted: isRangeDragging || isRangeHovered,
7197
6998
  keepHighlight: true,
7198
6999
  onDragChange: handleRangeDragChange,
7000
+ onDragCreate: interactive && !isRangeDragging ? onRangeDragCreate : undefined,
7199
7001
  onPreviewChange: setPreviewRange,
7200
7002
  onRangeChange: interactive ? onRangeChange : undefined,
7201
- range: range,
7003
+ range: overlayRange,
7202
7004
  viewport: viewport
7203
7005
  }), hoverLeft != null && hoverProgress != null && !isRangeDragging && /*#__PURE__*/external_react_["default"].createElement("div", {
7204
7006
  className: "bp-WaveformView-hover",
@@ -7302,8 +7104,9 @@ function WaveformZoomControl({
7302
7104
  const zoomControlElRef = (0,external_react_.useRef)(null); // root; outside pointerdown unpins the flyout
7303
7105
  const dismissTimerRef = (0,external_react_.useRef)(0); // delay before hover-close of the flyout
7304
7106
  const sliderId = `bp-waveform-zoom-slider${(0,external_react_.useId)()}`;
7305
- const zoom = clampWaveformZoom(zoomLevel, maxZoom);
7306
- const zoomValue = Math.round(sliderValueFromZoom(zoom, maxZoom));
7107
+ const zoom = (0,waveform_viewport/* clampWaveformZoom */.ii)(zoomLevel, maxZoom);
7108
+ const zoomValue = Math.round((0,waveform_viewport/* sliderValueFromZoom */.Iw)(zoom, maxZoom));
7109
+ const zoomMultiplier = (0,waveform_viewport/* getWaveformZoomMultiplier */.gd)(zoom);
7307
7110
  const isOpen = isHovered || isFocused || isRevealed || isPinned;
7308
7111
  const isAtMinZoom = zoomValue <= 0;
7309
7112
  const isAtMaxZoom = zoomValue >= constants/* WAVEFORM_ZOOM_SLIDER_MAX */.nh;
@@ -7329,10 +7132,10 @@ function WaveformZoomControl({
7329
7132
  return () => document.removeEventListener('pointerdown', closeIfOutside);
7330
7133
  }, [isPinned]);
7331
7134
  const handleSlider = (0,external_react_.useCallback)(newValue => {
7332
- onZoomChange(zoomFromSliderValue(newValue, maxZoom));
7135
+ onZoomChange((0,waveform_viewport/* zoomFromSliderValue */.eU)(newValue, maxZoom));
7333
7136
  }, [maxZoom, onZoomChange]);
7334
7137
  const handleStep = (0,external_react_.useCallback)(delta => {
7335
- onZoomChange(stepWaveformZoom(zoom, maxZoom, delta));
7138
+ onZoomChange((0,waveform_viewport/* stepWaveformZoom */.xV)(zoom, maxZoom, delta));
7336
7139
  }, [maxZoom, onZoomChange, zoom]);
7337
7140
  const handleZoomInPointerDown = (0,external_react_.useCallback)(() => {
7338
7141
  wasFlyoutOpenOnPointerDownRef.current = isHovered || isFocused || isRevealed || isPinned;
@@ -7350,6 +7153,7 @@ function WaveformZoomControl({
7350
7153
  ref: zoomControlElRef,
7351
7154
  "aria-label": "Zoom",
7352
7155
  className: classnames_default()('bp-WaveformZoomControl', {
7156
+ 'bp-has-multiplier': zoomMultiplier,
7353
7157
  'bp-is-open': isOpen
7354
7158
  }),
7355
7159
  "data-testid": "bp-waveform-zoom",
@@ -7375,7 +7179,15 @@ function WaveformZoomControl({
7375
7179
  }, constants/* WAVEFORM_ZOOM_DISMISS_MS */.m6);
7376
7180
  },
7377
7181
  role: "group"
7378
- }, /*#__PURE__*/external_react_["default"].createElement(MediaToggle/* default */.A, {
7182
+ }, zoomMultiplier && /*#__PURE__*/external_react_["default"].createElement(external_react_["default"].Fragment, null, /*#__PURE__*/external_react_["default"].createElement("span", {
7183
+ className: "bp-WaveformZoomControl-multiplier",
7184
+ "data-testid": "bp-waveform-zoom-multiplier",
7185
+ title: "Current zoom level"
7186
+ }, zoomMultiplier), /*#__PURE__*/external_react_["default"].createElement("span", {
7187
+ "aria-hidden": "true",
7188
+ className: "bp-WaveformZoomControl-divider",
7189
+ "data-testid": "bp-waveform-zoom-divider"
7190
+ })), /*#__PURE__*/external_react_["default"].createElement(MediaToggle/* default */.A, {
7379
7191
  "aria-disabled": isOpen && isAtMaxZoom,
7380
7192
  className: "bp-WaveformZoomControl-button",
7381
7193
  "data-resin-target": "waveformZoomIn",
@@ -7436,26 +7248,35 @@ function WaveformZoomControl({
7436
7248
 
7437
7249
 
7438
7250
 
7251
+
7439
7252
  const PLACEHOLDER_PEAKS = placeholderPeaks();
7440
7253
  function MP3ControlsV2({
7441
7254
  autoplay,
7442
7255
  bufferedRange,
7443
7256
  commentMarkers,
7257
+ commentRangeDraft = null,
7444
7258
  currentTime,
7445
7259
  durationTime,
7446
7260
  hasStartedPlayback = false,
7261
+ isGeneratingWaveform = false,
7447
7262
  isPlaying,
7448
7263
  keyboardVolumeStep = 0,
7449
7264
  keyboardZoomStep = 0,
7450
7265
  mediaEl,
7451
7266
  onAutoplayChange,
7452
7267
  onCommentMarkerClick,
7268
+ onCommentRangeChange,
7269
+ onCommentRangeClear,
7270
+ onCommentRangeDragCreate,
7271
+ onCommentRangeDragChange,
7453
7272
  onMuteChange,
7454
7273
  onPlayPause,
7274
+ onPlayNextChange,
7455
7275
  onRateChange,
7456
7276
  onTimeChange,
7457
7277
  onVolumeChange,
7458
7278
  peaks,
7279
+ playNext,
7459
7280
  rate,
7460
7281
  volume
7461
7282
  }) {
@@ -7478,7 +7299,7 @@ function MP3ControlsV2({
7478
7299
  const userChangedTapeZoomRef = (0,external_react_.useRef)(false); // pinch/wheel zoom; skip re-applying the 10s default
7479
7300
  const handleViewportChange = (0,external_react_.useCallback)(next => {
7480
7301
  setMaxZoom(prev => prev === next.maxZoom ? prev : next.maxZoom);
7481
- setViewport(prev => viewportEquals(prev, next) ? prev : next);
7302
+ setViewport(prev => (0,waveform_viewport/* viewportEquals */.KH)(prev, next) ? prev : next);
7482
7303
  }, []);
7483
7304
  const revealZoomControl = (0,external_react_.useCallback)(() => {
7484
7305
  setIsZoomRevealed(true);
@@ -7503,11 +7324,11 @@ function MP3ControlsV2({
7503
7324
  return;
7504
7325
  }
7505
7326
  userChangedTapeZoomRef.current = true;
7506
- setZoomLevel(prev => stepWaveformZoom(prev, maxZoom, steps * constants/* WAVEFORM_ZOOM_BUTTON_STEP */.QD));
7327
+ setZoomLevel(prev => (0,waveform_viewport/* stepWaveformZoom */.xV)(prev, maxZoom, steps * constants/* WAVEFORM_ZOOM_BUTTON_STEP */.QD));
7507
7328
  revealZoomControl();
7508
7329
  }, [keyboardZoomStep, maxZoom, revealZoomControl]);
7509
7330
  (0,external_react_.useEffect)(() => {
7510
- setZoomLevel(prev => clampWaveformZoom(prev, maxZoom));
7331
+ setZoomLevel(prev => (0,waveform_viewport/* clampWaveformZoom */.ii)(prev, maxZoom));
7511
7332
  }, [maxZoom]);
7512
7333
  (0,external_react_.useEffect)(() => {
7513
7334
  if (!isTape) {
@@ -7525,7 +7346,7 @@ function MP3ControlsV2({
7525
7346
  hasAppliedTapeDefaultZoomRef.current = true;
7526
7347
  return;
7527
7348
  }
7528
- const nextZoom = getTapeDefaultZoom(durationValue, Math.max(maxZoom, viewport.maxZoom));
7349
+ const nextZoom = (0,waveform_viewport/* getTapeDefaultZoom */.lz)(durationValue, Math.max(maxZoom, viewport.maxZoom));
7529
7350
  hasAppliedTapeDefaultZoomRef.current = true;
7530
7351
  setZoomLevel(prev => prev === nextZoom ? prev : nextZoom);
7531
7352
  }, [durationValue, hasRealPeaks, hasWaveformDuration, isTape, maxZoom, viewport]);
@@ -7558,6 +7379,7 @@ function MP3ControlsV2({
7558
7379
  const showPlayOverlay = !hasStarted && !isPlaying;
7559
7380
  const hasZoomHandlers = hasRealPeaks && !showPlayOverlay;
7560
7381
  const hasZoomControl = hasZoomHandlers && hasMediaMetadata && maxZoom > constants/* WAVEFORM_ZOOM_MIN */.LW;
7382
+ const showGeneratingWaveformIndicator = isGeneratingWaveform && !hasRealPeaks;
7561
7383
  const waveformZoomLevel = isTape || hasZoomHandlers ? zoomLevel : constants/* WAVEFORM_ZOOM_MIN */.LW;
7562
7384
  return /*#__PURE__*/external_react_["default"].createElement("div", {
7563
7385
  className: "bp-MP3ControlsV2",
@@ -7575,10 +7397,15 @@ function MP3ControlsV2({
7575
7397
  isPlaying: isPlaying,
7576
7398
  mediaEl: mediaEl,
7577
7399
  onPlayPause: isWaveformInteractive ? onPlayPause : undefined,
7400
+ onRangeChange: isWaveformInteractive ? onCommentRangeChange : undefined,
7401
+ onRangeClear: isWaveformInteractive ? onCommentRangeClear : undefined,
7402
+ onRangeDragChange: onCommentRangeDragChange,
7403
+ onRangeDragCreate: isWaveformInteractive ? onCommentRangeDragCreate : undefined,
7578
7404
  onSeek: isWaveformInteractive ? onTimeChange : undefined,
7579
7405
  onViewportChange: hasRealPeaks ? handleViewportChange : undefined,
7580
7406
  onZoomChange: hasZoomHandlers ? handleWaveformZoom : undefined,
7581
7407
  peaks: waveformPeaks,
7408
+ range: commentRangeDraft,
7582
7409
  zoomLevel: waveformZoomLevel
7583
7410
  }), /*#__PURE__*/external_react_["default"].createElement(WaveformCommentMarkers, {
7584
7411
  commentMarkers: waveformMarkers,
@@ -7587,9 +7414,9 @@ function MP3ControlsV2({
7587
7414
  onCommentMarkerClick: handleCommentMarkerClick,
7588
7415
  selectedId: selectedMarkerId,
7589
7416
  viewport: viewport
7590
- })), hasZoomControl && /*#__PURE__*/external_react_["default"].createElement("div", {
7417
+ })), (showGeneratingWaveformIndicator || hasZoomControl) && /*#__PURE__*/external_react_["default"].createElement("div", {
7591
7418
  className: "bp-MP3ControlsV2-waveformZoom"
7592
- }, /*#__PURE__*/external_react_["default"].createElement(WaveformZoomControl, {
7419
+ }, showGeneratingWaveformIndicator ? /*#__PURE__*/external_react_["default"].createElement(WaveformGeneratingIndicator, null) : /*#__PURE__*/external_react_["default"].createElement(WaveformZoomControl, {
7593
7420
  isRevealed: isZoomRevealed,
7594
7421
  maxZoom: maxZoom,
7595
7422
  onZoomChange: handleWaveformZoom,
@@ -7635,7 +7462,9 @@ function MP3ControlsV2({
7635
7462
  autoplay: autoplay,
7636
7463
  className: "bp-MP3Controls-settings",
7637
7464
  onAutoplayChange: onAutoplayChange,
7465
+ onPlayNextChange: onPlayNextChange,
7638
7466
  onRateChange: onRateChange,
7467
+ playNext: playNext,
7639
7468
  rate: rate
7640
7469
  }))));
7641
7470
  }
@@ -7714,6 +7543,7 @@ function isFpsAvailable(player) {
7714
7543
 
7715
7544
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
7716
7545
  /* harmony export */ Av: () => (/* binding */ WAVEFORM_TAPE_DEFAULT_WINDOW_SEC),
7546
+ /* harmony export */ B4: () => (/* binding */ WAVEFORM_RANGE_CREATE_DRAG_PX),
7717
7547
  /* harmony export */ BG: () => (/* binding */ WAVEFORM_RANGE_COLLAPSED_OFFSET_PX),
7718
7548
  /* harmony export */ Bq: () => (/* binding */ WAVEFORM_PLAYHEAD_JUMP_MS),
7719
7549
  /* harmony export */ DY: () => (/* binding */ WAVEFORM_BAR_RADIUS),
@@ -7810,6 +7640,8 @@ const WAVEFORM_RANGE_COLLAPSED_OFFSET_PX = WAVEFORM_RANGE_HANDLE_LINE_PX / 2 + W
7810
7640
  const WAVEFORM_RANGE_SNAP_PX = 8;
7811
7641
  /** Shortest committed range. Collapsed drafts (`endMs: null`) are exempt. */
7812
7642
  const WAVEFORM_RANGE_MIN_DURATION_MS = 250;
7643
+ /** Pointer travel before a waveform press becomes a new draft range. Shorter presses stay click-to-seek. */
7644
+ const WAVEFORM_RANGE_CREATE_DRAG_PX = 4;
7813
7645
  const WAVEFORM_RANGE_GRIP_IN_MS = 150;
7814
7646
  const WAVEFORM_RANGE_GRIP_OUT_MS = 100;
7815
7647
 
@@ -8367,6 +8199,244 @@ function loadPeaks(request) {
8367
8199
 
8368
8200
  /***/ },
8369
8201
 
8202
+ /***/ 4253
8203
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
8204
+
8205
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
8206
+ /* harmony export */ BT: () => (/* binding */ isRangeCollapsed),
8207
+ /* harmony export */ D$: () => (/* binding */ rangeProgress),
8208
+ /* harmony export */ DL: () => (/* binding */ clampTimeMs),
8209
+ /* harmony export */ Eh: () => (/* binding */ visualRangeHandlePx),
8210
+ /* harmony export */ H5: () => (/* binding */ isPointerOverRange),
8211
+ /* harmony export */ HG: () => (/* binding */ pickRangeHandle),
8212
+ /* harmony export */ Mh: () => (/* binding */ snapTimeMs),
8213
+ /* harmony export */ Mo: () => (/* binding */ dragRangeHandle),
8214
+ /* harmony export */ Qp: () => (/* binding */ rangeFromCreateDrag),
8215
+ /* harmony export */ Ss: () => (/* binding */ resolveRange),
8216
+ /* harmony export */ dG: () => (/* binding */ commitRangeChange),
8217
+ /* harmony export */ uH: () => (/* binding */ durationMsFromSec),
8218
+ /* harmony export */ v2: () => (/* binding */ pointerTimeMs)
8219
+ /* harmony export */ });
8220
+ /* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4929);
8221
+ /* harmony import */ var _viewport__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(772);
8222
+
8223
+
8224
+ function clampToRange(value, min, max) {
8225
+ if (!Number.isFinite(value)) {
8226
+ return min;
8227
+ }
8228
+ return Math.min(max, Math.max(min, value));
8229
+ }
8230
+
8231
+ /** Integer ms in `[0, durationMs]`. */
8232
+ function clampTimeMs(timeMs, durationMs) {
8233
+ const duration = Math.max(0, durationMs);
8234
+ return Math.round(clampToRange(timeMs, 0, duration));
8235
+ }
8236
+ function durationMsFromSec(durationSec) {
8237
+ if (!Number.isFinite(durationSec) || durationSec <= 0) {
8238
+ return 0;
8239
+ }
8240
+ return Math.round(durationSec * 1000);
8241
+ }
8242
+ function isRangeCollapsed(range) {
8243
+ return !range || range.endMs == null || range.endMs <= range.startMs;
8244
+ }
8245
+
8246
+ /** Clamp both edges onto the file. Collapsed drafts stay a point. */
8247
+ function resolveRange(range, durationMs) {
8248
+ const startMs = clampTimeMs(range.startMs, durationMs);
8249
+ if (range.endMs == null) {
8250
+ return {
8251
+ endMs: startMs,
8252
+ startMs
8253
+ };
8254
+ }
8255
+ const endMs = clampTimeMs(range.endMs, durationMs);
8256
+ if (endMs <= startMs) {
8257
+ return {
8258
+ endMs: startMs,
8259
+ startMs
8260
+ };
8261
+ }
8262
+ return {
8263
+ endMs,
8264
+ startMs
8265
+ };
8266
+ }
8267
+
8268
+ /**
8269
+ * Which handle should receive this pointer. Overlapping hit areas (collapsed or
8270
+ * a span narrower than the 24px target) split at the midpoint so each edge stays reachable.
8271
+ */
8272
+ function pickRangeHandle({
8273
+ endX,
8274
+ pointerX,
8275
+ startX
8276
+ }) {
8277
+ const mid = (startX + endX) / 2;
8278
+ if (!Number.isFinite(mid)) {
8279
+ return 'end';
8280
+ }
8281
+ return pointerX < mid ? 'start' : 'end';
8282
+ }
8283
+
8284
+ /** Screen x of a handle. Collapsed drafts sit 1px left/right of the playhead, not on it. */
8285
+ function visualRangeHandlePx({
8286
+ handle,
8287
+ range,
8288
+ viewport
8289
+ }) {
8290
+ const timeSec = (handle === 'start' ? range.startMs : range.endMs) / 1000;
8291
+ const x = (0,_viewport__WEBPACK_IMPORTED_MODULE_1__/* .positionPxFromTime */ .TO)(timeSec, viewport);
8292
+ if (range.startMs !== range.endMs) {
8293
+ return x;
8294
+ }
8295
+ 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;
8296
+ }
8297
+
8298
+ /** True when the pointer is over the range rectangle or its handle hit targets. */
8299
+ function isPointerOverRange({
8300
+ pointerX,
8301
+ range,
8302
+ viewport
8303
+ }) {
8304
+ const resolved = resolveRange(range, durationMsFromSec(viewport.durationSec));
8305
+ const startX = visualRangeHandlePx({
8306
+ handle: 'start',
8307
+ range: resolved,
8308
+ viewport
8309
+ });
8310
+ const endX = visualRangeHandlePx({
8311
+ handle: 'end',
8312
+ range: resolved,
8313
+ viewport
8314
+ });
8315
+ const pad = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_HIT_AREA_PX */ .r0 / 2;
8316
+ return pointerX >= Math.min(startX, endX) - pad && pointerX <= Math.max(startX, endX) + pad;
8317
+ }
8318
+
8319
+ /** Snap to the playhead when the pointer is within `WAVEFORM_RANGE_SNAP_PX`. */
8320
+ function snapTimeMs({
8321
+ pixelsPerSecond,
8322
+ playheadMs,
8323
+ timeMs
8324
+ }) {
8325
+ if (!(pixelsPerSecond > 0) || !Number.isFinite(playheadMs)) {
8326
+ return timeMs;
8327
+ }
8328
+ const deltaPx = Math.abs(timeMs - playheadMs) / 1000 * pixelsPerSecond;
8329
+ if (deltaPx <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_SNAP_PX */ .IQ) {
8330
+ return Math.round(playheadMs);
8331
+ }
8332
+ return timeMs;
8333
+ }
8334
+
8335
+ /**
8336
+ * Move one edge. Crossing the other edge swaps roles so a collapsed pair can
8337
+ * expand in either direction. Enforces `[0, duration]` and the minimum span
8338
+ * once the drag has actually opened a range. An already-open range stays a
8339
+ * span — snapping one edge onto the other clamps to the minimum duration.
8340
+ */
8341
+ function dragRangeHandle({
8342
+ durationMs,
8343
+ handle,
8344
+ pointerMs,
8345
+ range
8346
+ }) {
8347
+ const duration = Math.max(0, durationMs);
8348
+ const pointer = clampTimeMs(pointerMs, duration);
8349
+ let {
8350
+ startMs,
8351
+ endMs
8352
+ } = range;
8353
+ let nextHandle = handle;
8354
+ if (handle === 'start') {
8355
+ if (pointer > endMs) {
8356
+ startMs = endMs;
8357
+ endMs = pointer;
8358
+ nextHandle = 'end';
8359
+ } else {
8360
+ startMs = pointer;
8361
+ }
8362
+ } else if (pointer < startMs) {
8363
+ endMs = startMs;
8364
+ startMs = pointer;
8365
+ nextHandle = 'start';
8366
+ } else {
8367
+ endMs = pointer;
8368
+ }
8369
+ const wasOpen = range.startMs !== range.endMs;
8370
+ const opened = wasOpen || startMs !== endMs;
8371
+ if (opened && endMs - startMs < _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_MIN_DURATION_MS */ .bX) {
8372
+ if (nextHandle === 'end') {
8373
+ endMs = clampTimeMs(startMs + _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_MIN_DURATION_MS */ .bX, duration);
8374
+ if (endMs - startMs < _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_MIN_DURATION_MS */ .bX) {
8375
+ startMs = clampTimeMs(endMs - _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_MIN_DURATION_MS */ .bX, duration);
8376
+ }
8377
+ } else {
8378
+ startMs = clampTimeMs(endMs - _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_MIN_DURATION_MS */ .bX, duration);
8379
+ if (endMs - startMs < _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_MIN_DURATION_MS */ .bX) {
8380
+ endMs = clampTimeMs(startMs + _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_RANGE_MIN_DURATION_MS */ .bX, duration);
8381
+ }
8382
+ }
8383
+ }
8384
+ return {
8385
+ handle: nextHandle,
8386
+ range: {
8387
+ endMs,
8388
+ startMs
8389
+ }
8390
+ };
8391
+ }
8392
+ function pointerTimeMs(pointerX, viewport) {
8393
+ return Math.round((0,_viewport__WEBPACK_IMPORTED_MODULE_1__/* .timeFromPositionPx */ .lZ)(pointerX, viewport) * 1000);
8394
+ }
8395
+
8396
+ /** Sidebar payload after mouseup. Null when the draft is still a single timestamp. */
8397
+ function commitRangeChange(range) {
8398
+ if (range.endMs <= range.startMs) {
8399
+ return null;
8400
+ }
8401
+ return {
8402
+ endMs: range.endMs,
8403
+ startMs: range.startMs
8404
+ };
8405
+ }
8406
+
8407
+ /**
8408
+ * Open a draft from a waveform drag. The press time stays anchored; the pointer
8409
+ * chooses which edge moves. Enforces the minimum span once the drag leaves the point.
8410
+ */
8411
+ function rangeFromCreateDrag({
8412
+ durationMs,
8413
+ originMs,
8414
+ pointerMs
8415
+ }) {
8416
+ const origin = clampTimeMs(originMs, durationMs);
8417
+ const handle = pointerMs >= origin ? 'end' : 'start';
8418
+ return dragRangeHandle({
8419
+ durationMs,
8420
+ handle,
8421
+ pointerMs,
8422
+ range: {
8423
+ endMs: origin,
8424
+ startMs: origin
8425
+ }
8426
+ }).range;
8427
+ }
8428
+ function rangeProgress(range, durationMs) {
8429
+ if (!(durationMs > 0) || range.endMs <= range.startMs) {
8430
+ return null;
8431
+ }
8432
+ return {
8433
+ end: range.endMs / durationMs,
8434
+ start: range.startMs / durationMs
8435
+ };
8436
+ }
8437
+
8438
+ /***/ },
8439
+
8370
8440
  /***/ 1053
8371
8441
  (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
8372
8442
 
@@ -8554,6 +8624,369 @@ function isRetryableWaveformError(code) {
8554
8624
 
8555
8625
  /***/ },
8556
8626
 
8627
+ /***/ 772
8628
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
8629
+
8630
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
8631
+ /* harmony export */ Iw: () => (/* binding */ sliderValueFromZoom),
8632
+ /* harmony export */ J$: () => (/* binding */ getZoomedPixelsPerSecond),
8633
+ /* harmony export */ KH: () => (/* binding */ viewportEquals),
8634
+ /* harmony export */ Mu: () => (/* binding */ timeLeftPercent),
8635
+ /* harmony export */ NI: () => (/* binding */ getViewportAtScroll),
8636
+ /* harmony export */ TO: () => (/* binding */ positionPxFromTime),
8637
+ /* harmony export */ aJ: () => (/* binding */ getPlayheadCameraAction),
8638
+ /* harmony export */ b6: () => (/* binding */ getCenteredScrollLeft),
8639
+ /* harmony export */ by: () => (/* binding */ getWaveformZoomMax),
8640
+ /* harmony export */ d0: () => (/* binding */ maxScrollLeft),
8641
+ /* harmony export */ eU: () => (/* binding */ zoomFromSliderValue),
8642
+ /* harmony export */ eq: () => (/* binding */ createWaveformViewport),
8643
+ /* harmony export */ gd: () => (/* binding */ getWaveformZoomMultiplier),
8644
+ /* harmony export */ gl: () => (/* binding */ getPinnedPlayheadLeft),
8645
+ /* harmony export */ iA: () => (/* binding */ getTapeGutterPx),
8646
+ /* harmony export */ ii: () => (/* binding */ clampWaveformZoom),
8647
+ /* harmony export */ lZ: () => (/* binding */ timeFromPositionPx),
8648
+ /* harmony export */ lz: () => (/* binding */ getTapeDefaultZoom),
8649
+ /* harmony export */ wA: () => (/* binding */ getSeekCameraAction),
8650
+ /* harmony export */ xV: () => (/* binding */ stepWaveformZoom),
8651
+ /* harmony export */ yW: () => (/* binding */ getTapePinnedPlayheadLeft)
8652
+ /* harmony export */ });
8653
+ /* unused harmony exports isTimeInView, getTapeCameraAction, getFollowInsetPx */
8654
+ /* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4929);
8655
+ 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; }
8656
+ 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; }
8657
+ 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; }
8658
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
8659
+ 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); }
8660
+
8661
+ /** Visible window: start/end times, pixels-per-second, and the current zoom. */
8662
+ function createWaveformViewport({
8663
+ durationSec,
8664
+ gutterPx = 0,
8665
+ heightPx,
8666
+ maxZoom,
8667
+ scrollLeftPx,
8668
+ widthPx,
8669
+ zoomLevel
8670
+ }) {
8671
+ const zoom = Number.isFinite(zoomLevel) && zoomLevel > 0 ? zoomLevel : _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW;
8672
+ const viewDurationSec = durationSec > 0 ? durationSec / zoom : 0; // seconds visible at this zoom
8673
+ const pixelsPerSecond = viewDurationSec > 0 && widthPx > 0 ? widthPx / viewDurationSec : 0;
8674
+ const leadingGutterPx = Number.isFinite(gutterPx) && gutterPx > 0 ? gutterPx : 0;
8675
+ const unclampedStartSec = pixelsPerSecond > 0 ? (scrollLeftPx - leadingGutterPx) / pixelsPerSecond : 0;
8676
+ const gutterDurationSec = pixelsPerSecond > 0 ? leadingGutterPx / pixelsPerSecond : 0;
8677
+ const latestWindowStartSec = leadingGutterPx > 0 ? durationSec + gutterDurationSec - viewDurationSec : Math.max(0, durationSec - viewDurationSec);
8678
+ const earliestWindowStartSec = leadingGutterPx > 0 ? -gutterDurationSec : 0;
8679
+ const startSec = pixelsPerSecond > 0 ? Math.min(latestWindowStartSec, Math.max(earliestWindowStartSec, unclampedStartSec)) : 0;
8680
+ return {
8681
+ durationSec,
8682
+ endSec: startSec + viewDurationSec,
8683
+ gutterPx: leadingGutterPx,
8684
+ heightPx,
8685
+ maxZoom,
8686
+ pixelsPerSecond,
8687
+ scrollLeftPx,
8688
+ startSec,
8689
+ widthPx,
8690
+ zoomLevel: zoom
8691
+ };
8692
+ }
8693
+
8694
+ /** Same viewport with a different scroll, so start/end times update. */
8695
+ function getViewportAtScroll(viewport, scrollLeftPx) {
8696
+ return createWaveformViewport(_objectSpread(_objectSpread({}, viewport), {}, {
8697
+ scrollLeftPx
8698
+ }));
8699
+ }
8700
+
8701
+ /** True when the visible window has not moved — skip a React state update. */
8702
+ function viewportEquals(prev, next) {
8703
+ 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;
8704
+ }
8705
+
8706
+ /** How many CSS pixels from the left of the visible window this time sits. */
8707
+ function positionPxFromTime(timeSec, viewport) {
8708
+ return (timeSec - viewport.startSec) * viewport.pixelsPerSecond;
8709
+ }
8710
+
8711
+ /** Media time under a point this many CSS pixels from the left of the visible window. */
8712
+ function timeFromPositionPx(positionPx, viewport) {
8713
+ if (!(viewport.pixelsPerSecond > 0)) {
8714
+ return viewport.startSec;
8715
+ }
8716
+ return viewport.startSec + positionPx / viewport.pixelsPerSecond;
8717
+ }
8718
+
8719
+ /** True when this time sits inside the visible window. */
8720
+ function isTimeInView(timeSec, viewport) {
8721
+ return timeSec >= viewport.startSec && timeSec <= viewport.endSec;
8722
+ }
8723
+
8724
+ /** UI max zoom: 24×, ~1 peak per CSS pixel, and a minimum visible window. */
8725
+ function getWaveformZoomMax({
8726
+ durationSec,
8727
+ peakCount,
8728
+ viewWidthPx
8729
+ }) {
8730
+ if (!(peakCount > 0) || !(viewWidthPx > 0)) {
8731
+ return _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW;
8732
+ }
8733
+ const peakLimitedMax = Math.floor(peakCount / viewWidthPx);
8734
+ 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;
8735
+ 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));
8736
+ }
8737
+
8738
+ /** Keep zoom between 1× and this file's max. */
8739
+ function clampWaveformZoom(zoomLevel, maxZoom = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8740
+ const max = Math.max(_constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW, maxZoom);
8741
+ if (!Number.isFinite(zoomLevel)) {
8742
+ return _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW;
8743
+ }
8744
+ return Math.min(max, Math.max(_constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW, zoomLevel));
8745
+ }
8746
+
8747
+ /** Zoom badge: 1.1×–1.9× below 2×, then whole numbers. Omit at 1×. */
8748
+ function getWaveformZoomMultiplier(zoomLevel) {
8749
+ if (!Number.isFinite(zoomLevel) || zoomLevel <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8750
+ return null;
8751
+ }
8752
+ if (zoomLevel < 2) {
8753
+ const tenths = Math.round(zoomLevel * 10) / 10;
8754
+ if (tenths <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8755
+ return null;
8756
+ }
8757
+ if (tenths >= 2) {
8758
+ return '2x';
8759
+ }
8760
+ return `${tenths.toFixed(1)}x`;
8761
+ }
8762
+ return `${Math.floor(zoomLevel)}x`;
8763
+ }
8764
+
8765
+ /** WaveSurfer zoom density. 0 = fit the whole file in the view. */
8766
+ function getZoomedPixelsPerSecond({
8767
+ durationSec,
8768
+ isTape = false,
8769
+ maxZoom = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW,
8770
+ viewWidthPx,
8771
+ zoomLevel
8772
+ }) {
8773
+ const zoom = clampWaveformZoom(zoomLevel, maxZoom);
8774
+ if (!(durationSec > 0) || !(viewWidthPx > 0)) {
8775
+ return 0;
8776
+ }
8777
+ if (!isTape && zoom <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8778
+ return 0;
8779
+ }
8780
+ return viewWidthPx / durationSec * zoom;
8781
+ }
8782
+
8783
+ /** Half-view empty space so t=0 and duration can sit under the center pin. */
8784
+ function getTapeGutterPx(widthPx) {
8785
+ return widthPx > 0 ? widthPx / 2 : 0;
8786
+ }
8787
+
8788
+ /** Tape first paint: 10s on screen, or 1× when the file is shorter than that. */
8789
+ function getTapeDefaultZoom(durationSec, maxZoom = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8790
+ if (!(durationSec > 0)) {
8791
+ return _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW;
8792
+ }
8793
+ return clampWaveformZoom(durationSec / _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_TAPE_DEFAULT_WINDOW_SEC */ .Av, maxZoom);
8794
+ }
8795
+
8796
+ /** Map zoom (1…max) onto the 0–100 slider. */
8797
+ function sliderValueFromZoom(zoomLevel, maxZoom = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8798
+ const max = Math.max(_constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW, maxZoom);
8799
+ const zoom = clampWaveformZoom(zoomLevel, max);
8800
+ if (max <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8801
+ return 0;
8802
+ }
8803
+ 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;
8804
+ }
8805
+
8806
+ /** Inverse of sliderValueFromZoom. */
8807
+ function zoomFromSliderValue(value, maxZoom = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8808
+ const max = Math.max(_constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW, maxZoom);
8809
+ if (max <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8810
+ return _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW;
8811
+ }
8812
+ 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;
8813
+ return clampWaveformZoom(_constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW + t * (max - _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW), max);
8814
+ }
8815
+
8816
+ /** One zoom-in/out button step on the 0–100 slider, mapped back to zoom. */
8817
+ function stepWaveformZoom(zoomLevel, maxZoom, sliderDelta) {
8818
+ return zoomFromSliderValue(Math.round(sliderValueFromZoom(zoomLevel, maxZoom)) + sliderDelta, maxZoom);
8819
+ }
8820
+
8821
+ /** Max scroll that still shows a full window (no overscroll). Gutters add empty lead/trail. */
8822
+ function maxScrollLeft(viewport) {
8823
+ const gutterPx = viewport.gutterPx || 0;
8824
+ return Math.max(0, viewport.durationSec * viewport.pixelsPerSecond + 2 * gutterPx - viewport.widthPx);
8825
+ }
8826
+
8827
+ /** Scroll offset that still shows a full window (no overscroll). */
8828
+ function clampScrollLeft(scrollLeftPx, viewport) {
8829
+ if (!Number.isFinite(scrollLeftPx)) {
8830
+ return 0;
8831
+ }
8832
+ return Math.min(maxScrollLeft(viewport), Math.max(0, scrollLeftPx));
8833
+ }
8834
+
8835
+ /** Center this time in the view, clamped so the window still fills the canvas. */
8836
+ function getCenteredScrollLeft(timeSec, viewport) {
8837
+ return clampScrollLeft(timeSec * viewport.pixelsPerSecond + (viewport.gutterPx || 0) - viewport.widthPx / 2, viewport);
8838
+ }
8839
+
8840
+ /**
8841
+ * Host/media seek while zoomed: jump so the time (and its comment avatar) is on screen.
8842
+ * No-op at fit-to-width or when that time is already visible.
8843
+ */
8844
+ function getSeekCameraAction({
8845
+ cameraMode = 'desktop',
8846
+ timeSec,
8847
+ viewport
8848
+ }) {
8849
+ if (!(viewport.widthPx > 0) || !(viewport.pixelsPerSecond > 0)) {
8850
+ return {
8851
+ type: 'none'
8852
+ };
8853
+ }
8854
+ if (cameraMode === 'tape') {
8855
+ const scrollLeftPx = getCenteredScrollLeft(timeSec, viewport);
8856
+ if (Math.abs(scrollLeftPx - viewport.scrollLeftPx) < 1) {
8857
+ return {
8858
+ type: 'none'
8859
+ };
8860
+ }
8861
+ return {
8862
+ type: 'jump',
8863
+ scrollLeftPx
8864
+ };
8865
+ }
8866
+ if (viewport.zoomLevel <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW) {
8867
+ return {
8868
+ type: 'none'
8869
+ };
8870
+ }
8871
+ if (isTimeInView(timeSec, viewport)) {
8872
+ return {
8873
+ type: 'none'
8874
+ };
8875
+ }
8876
+ return {
8877
+ type: 'jump',
8878
+ scrollLeftPx: getCenteredScrollLeft(timeSec, viewport)
8879
+ };
8880
+ }
8881
+
8882
+ /** Always keep this time under the center pin. Reuses followRight so the camera hook can pin. */
8883
+ function getTapeCameraAction({
8884
+ timeSec,
8885
+ viewport
8886
+ }) {
8887
+ if (!(viewport.widthPx > 0) || !(viewport.pixelsPerSecond > 0)) {
8888
+ return {
8889
+ type: 'none'
8890
+ };
8891
+ }
8892
+ return {
8893
+ isPlayheadPinned: true,
8894
+ scrollLeftPx: getCenteredScrollLeft(timeSec, viewport),
8895
+ type: 'followRight'
8896
+ };
8897
+ }
8898
+
8899
+ /** Follow inset in CSS px, capped at one third of the view so a narrow player still has room. */
8900
+ function getFollowInsetPx(widthPx, insetPx = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_FOLLOW_INSET_PX */ .NM) {
8901
+ if (!(widthPx > 0)) {
8902
+ return 0;
8903
+ }
8904
+ return Math.min(Math.max(0, insetPx), widthPx / 3);
8905
+ }
8906
+
8907
+ /** CSS left for a playhead pinned to the follow inset. */
8908
+ function getPinnedPlayheadLeft(widthPx, insetPx = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_FOLLOW_INSET_PX */ .NM) {
8909
+ const inset = getFollowInsetPx(widthPx, insetPx);
8910
+ if (!(widthPx > 0)) {
8911
+ return '0%';
8912
+ }
8913
+ return `${(widthPx - inset) / widthPx * 100}%`;
8914
+ }
8915
+
8916
+ /** CSS left for a playhead locked to the center of the view. */
8917
+ function getTapePinnedPlayheadLeft() {
8918
+ return '50%';
8919
+ }
8920
+
8921
+ /** CSS left % of the playhead from the left of the visible window. */
8922
+ function timeLeftPercent(timeSec, durationSec, viewport) {
8923
+ if (viewport.widthPx > 0 && viewport.pixelsPerSecond > 0) {
8924
+ return `${positionPxFromTime(timeSec, viewport) / viewport.widthPx * 100}%`;
8925
+ }
8926
+ const progress = durationSec > 0 ? Math.min(1, Math.max(0, timeSec / durationSec)) : 0;
8927
+ return `${progress * 100}%`;
8928
+ }
8929
+
8930
+ /**
8931
+ * Walk across the view until the playhead nears the right edge, then follow.
8932
+ * Off-screen at play start jumps in; off-screen right while playing keeps following.
8933
+ */
8934
+ function getPlayheadCameraAction({
8935
+ cameraMode = 'desktop',
8936
+ insetPx = _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_FOLLOW_INSET_PX */ .NM,
8937
+ isPlaying,
8938
+ playJustStarted,
8939
+ timeSec,
8940
+ viewport
8941
+ }) {
8942
+ if (cameraMode === 'tape') {
8943
+ return getTapeCameraAction({
8944
+ timeSec,
8945
+ viewport
8946
+ });
8947
+ }
8948
+ if (!isPlaying || viewport.zoomLevel <= _constants__WEBPACK_IMPORTED_MODULE_0__/* .WAVEFORM_ZOOM_MIN */ .LW || !(viewport.widthPx > 0) || !(viewport.pixelsPerSecond > 0)) {
8949
+ return {
8950
+ type: 'none'
8951
+ };
8952
+ }
8953
+ const inset = getFollowInsetPx(viewport.widthPx, insetPx);
8954
+ const viewX = positionPxFromTime(timeSec, viewport);
8955
+ const playheadCanvasX = timeSec * viewport.pixelsPerSecond;
8956
+ const followX = viewport.widthPx - inset;
8957
+ if (viewX < 0) {
8958
+ if (!playJustStarted) {
8959
+ return {
8960
+ type: 'none'
8961
+ };
8962
+ }
8963
+ return {
8964
+ type: 'jump',
8965
+ scrollLeftPx: clampScrollLeft(playheadCanvasX - inset, viewport)
8966
+ };
8967
+ }
8968
+ if (viewX >= followX) {
8969
+ const unclampedScroll = playheadCanvasX - followX;
8970
+ const scrollLeftPx = clampScrollLeft(unclampedScroll, viewport);
8971
+ if (playJustStarted && viewX > viewport.widthPx) {
8972
+ return {
8973
+ type: 'jump',
8974
+ scrollLeftPx
8975
+ };
8976
+ }
8977
+ return {
8978
+ type: 'followRight',
8979
+ isPlayheadPinned: unclampedScroll <= maxScrollLeft(viewport),
8980
+ scrollLeftPx
8981
+ };
8982
+ }
8983
+ return {
8984
+ type: 'none'
8985
+ };
8986
+ }
8987
+
8988
+ /***/ },
8989
+
8557
8990
  /***/ 2485
8558
8991
  (module, exports) {
8559
8992
 
@@ -24217,7 +24650,7 @@ class Browser {
24217
24650
  ;// ./src/lib/Logger.js
24218
24651
  /* eslint-disable no-undef */
24219
24652
  const CLIENT_NAME = "box-content-preview";
24220
- const CLIENT_VERSION = "3.92.0";
24653
+ const CLIENT_VERSION = "3.94.0";
24221
24654
  /* eslint-enable no-undef */
24222
24655
 
24223
24656
  class Logger {
@@ -33443,6 +33876,53 @@ class TextLoader extends viewers_AssetLoader {
33443
33876
  }
33444
33877
  }
33445
33878
  /* harmony default export */ const text_TextLoader = (new TextLoader());
33879
+ ;// ./src/lib/viewers/controls/media/types.ts
33880
+ /**
33881
+ * Draft range drawn on the waveform. Sidebar sends `comment_range_draft`
33882
+ * when checkbox is toggled on and Preview keeps the handles until
33883
+ * `comment_range_draft_clear`. A waveform drag can also create a local draft
33884
+ * before the timestamp toggle is on. See media/README.md.
33885
+ * `endMs: null` means a single timestamp — both handles sit on `startMs`.
33886
+ */
33887
+
33888
+ /** Committed range after a handle drag. Always a real span (`endMs > startMs`). */
33889
+
33890
+ /** Sidebar → viewer: show or update the draft handles. */
33891
+ const EVENT_COMMENT_RANGE_DRAFT = 'comment_range_draft';
33892
+
33893
+ /** Sidebar → viewer: hide the handles (toggle-off, post, version switch, unmount). */
33894
+ const EVENT_COMMENT_RANGE_DRAFT_CLEAR = 'comment_range_draft_clear';
33895
+
33896
+ /** Viewer → sidebar: click outside an open range while the timestamp toggle is already on. */
33897
+ const EVENT_COMMENT_RANGE_DRAFT_DISMISS = 'comment_range_draft_dismiss';
33898
+
33899
+ /**
33900
+ * Viewer → sidebar: a range edit finished while the timestamp toggle is already on.
33901
+ * Pointer-up only. Not used to start commenting — that is `comment_range_compose`.
33902
+ */
33903
+ const EVENT_COMMENT_RANGE_DRAFT_CHANGE = 'comment_range_draft_change';
33904
+
33905
+ /**
33906
+ * Viewer → sidebar: the user pressed Comment on a draft range.
33907
+ * The host opens Activity if needed, checks the timestamp toggle, and adopts
33908
+ * `{ startMs, endMs }` without echoing `comment_range_draft`.
33909
+ */
33910
+ const EVENT_COMMENT_RANGE_DRAG_CREATE = 'comment_range_compose';
33911
+
33912
+ /** True when a host draft can be shown. Collapsed drafts use `endMs: null`. */
33913
+ function isValidCommentRangeDraft(value) {
33914
+ if (!value || typeof value !== 'object') {
33915
+ return false;
33916
+ }
33917
+ const draft = value;
33918
+ if (!Number.isFinite(draft.startMs)) {
33919
+ return false;
33920
+ }
33921
+ if (draft.endMs != null && (!Number.isFinite(draft.endMs) || Number(draft.endMs) <= Number(draft.startMs))) {
33922
+ return false;
33923
+ }
33924
+ return true;
33925
+ }
33446
33926
  // EXTERNAL MODULE: ./node_modules/lodash/isEmpty.js
33447
33927
  var isEmpty = __webpack_require__(2193);
33448
33928
  var isEmpty_default = /*#__PURE__*/__webpack_require__.n(isEmpty);
@@ -35412,6 +35892,7 @@ const CSS_CLASS_MEDIA_CONTAINER = 'bp-media-container';
35412
35892
  const DEFAULT_VOLUME = 1;
35413
35893
  const MEDIA_VOLUME_CACHE_KEY = 'media-volume';
35414
35894
  const MEDIA_AUTOPLAY_CACHE_KEY = 'media-autoplay';
35895
+ const MEDIA_PLAY_NEXT_CACHE_KEY = 'media-play-next';
35415
35896
  const MEDIA_SPEED_CACHE_KEY = 'media-speed';
35416
35897
  const MEDIA_VOLUME_INCREMENT = 0.05;
35417
35898
  const EMIT_WAIT_TIME_IN_MILLIS = 100;
@@ -35462,6 +35943,7 @@ class MediaBaseViewer extends viewers_BaseViewer {
35462
35943
  this.containerClickHandler = this.containerClickHandler.bind(this);
35463
35944
  this.errorHandler = this.errorHandler.bind(this);
35464
35945
  this.handleAutoplay = this.handleAutoplay.bind(this);
35946
+ this.handlePlayNext = this.handlePlayNext.bind(this);
35465
35947
  this.handleRate = this.handleRate.bind(this);
35466
35948
  this.handleTimeupdateFromMediaControls = this.handleTimeupdateFromMediaControls.bind(this);
35467
35949
  this.loadeddataHandler = this.loadeddataHandler.bind(this);
@@ -35474,6 +35956,7 @@ class MediaBaseViewer extends viewers_BaseViewer {
35474
35956
  this.resetPlayIcon = this.resetPlayIcon.bind(this);
35475
35957
  this.seekHandler = this.seekHandler.bind(this);
35476
35958
  this.setAutoplay = this.setAutoplay.bind(this);
35959
+ this.setPlayNext = this.setPlayNext.bind(this);
35477
35960
  this.setRate = this.setRate.bind(this);
35478
35961
  this.setTimeCode = this.setTimeCode.bind(this);
35479
35962
  this.setVolume = this.setVolume.bind(this);
@@ -35783,7 +36266,9 @@ class MediaBaseViewer extends viewers_BaseViewer {
35783
36266
  * @return {void}
35784
36267
  */
35785
36268
  handleRate() {
35786
- const speed = this.cache.get(MEDIA_SPEED_CACHE_KEY) - 0;
36269
+ const cachedSpeed = this.cache.get(MEDIA_SPEED_CACHE_KEY) - 0;
36270
+ // Default to 1x when cache is empty (v2 overlay play can run before loadUIReact).
36271
+ const speed = Number.isFinite(cachedSpeed) ? cachedSpeed : 1;
35787
36272
  if (speed && this.mediaEl.playbackRate !== speed && this.mediaEl.playbackRate > 0) {
35788
36273
  this.emit('ratechange', speed);
35789
36274
  }
@@ -35831,6 +36316,20 @@ class MediaBaseViewer extends viewers_BaseViewer {
35831
36316
  this.renderUI();
35832
36317
  }
35833
36318
  }
36319
+
36320
+ /**
36321
+ * Handler for play next
36322
+ *
36323
+ * @private
36324
+ * @emits playnext
36325
+ * @return {void}
36326
+ */
36327
+ handlePlayNext() {
36328
+ this.emit('playnext', this.isPlayNextEnabled());
36329
+ if (this.controls) {
36330
+ this.renderUI();
36331
+ }
36332
+ }
35834
36333
  /**
35835
36334
  * Autoplay the media
35836
36335
  *
@@ -35857,6 +36356,16 @@ class MediaBaseViewer extends viewers_BaseViewer {
35857
36356
  isAutoplayEnabled() {
35858
36357
  return this.cache.get(MEDIA_AUTOPLAY_CACHE_KEY) === 'Enabled';
35859
36358
  }
36359
+
36360
+ /**
36361
+ * Determines if play next is enabled
36362
+ *
36363
+ * @protected
36364
+ * @return {boolean} Indicates if play next is enabled
36365
+ */
36366
+ isPlayNextEnabled() {
36367
+ return this.cache.get(MEDIA_PLAY_NEXT_CACHE_KEY) === 'Enabled';
36368
+ }
35860
36369
  /**
35861
36370
  * Loads the controls
35862
36371
  *
@@ -35879,6 +36388,9 @@ class MediaBaseViewer extends viewers_BaseViewer {
35879
36388
  if (!this.cache.has(MEDIA_AUTOPLAY_CACHE_KEY)) {
35880
36389
  this.cache.set(MEDIA_AUTOPLAY_CACHE_KEY, 'Disabled');
35881
36390
  }
36391
+ if (!this.cache.has(MEDIA_PLAY_NEXT_CACHE_KEY)) {
36392
+ this.cache.set(MEDIA_PLAY_NEXT_CACHE_KEY, 'Disabled');
36393
+ }
35882
36394
  if (!this.cache.has(MEDIA_SPEED_CACHE_KEY)) {
35883
36395
  this.cache.set(MEDIA_SPEED_CACHE_KEY, '1.0');
35884
36396
  }
@@ -35973,6 +36485,18 @@ class MediaBaseViewer extends viewers_BaseViewer {
35973
36485
  this.handleAutoplay();
35974
36486
  }
35975
36487
 
36488
+ /**
36489
+ * Updates play next
36490
+ *
36491
+ * @protected
36492
+ * @param {boolean} playNext - True if enabled
36493
+ * @return {void}
36494
+ */
36495
+ setPlayNext(playNext) {
36496
+ this.cache.set(MEDIA_PLAY_NEXT_CACHE_KEY, playNext ? 'Enabled' : 'Disabled', true);
36497
+ this.handlePlayNext();
36498
+ }
36499
+
35976
36500
  /**
35977
36501
  * Updates play rate
35978
36502
  *
@@ -36074,17 +36598,17 @@ class MediaBaseViewer extends viewers_BaseViewer {
36074
36598
  }
36075
36599
 
36076
36600
  /**
36077
- * Emits the previewnextfile event if autoplay is enabled.
36601
+ * Emits mediaEndPlayNext when play next is enabled.
36078
36602
  *
36079
36603
  * @private
36080
- * @emits previewnextfile
36604
+ * @emits mediaEndPlayNext
36081
36605
  * @return {void}
36082
36606
  */
36083
36607
  mediaendHandler() {
36084
36608
  this.resetPlayIcon();
36085
36609
  this.processMetrics();
36086
- if (this.isAutoplayEnabled()) {
36087
- this.emit(lib_events/* VIEWER_EVENT */.Oi.mediaEndAutoplay);
36610
+ if (this.isPlayNextEnabled()) {
36611
+ this.emit(lib_events/* VIEWER_EVENT */.Oi.mediaEndPlayNext);
36088
36612
  }
36089
36613
  }
36090
36614
 
@@ -36660,8 +37184,8 @@ class MediaBaseViewer extends viewers_BaseViewer {
36660
37184
  }
36661
37185
  }
36662
37186
  /* 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);
37187
+ // EXTERNAL MODULE: ./src/lib/viewers/controls/media/MediaSettings.tsx + 6 modules
37188
+ var MediaSettings = __webpack_require__(9229);
36665
37189
  // EXTERNAL MODULE: ./src/lib/viewers/controls/media/PlayPauseToggle.tsx + 1 modules
36666
37190
  var PlayPauseToggle = __webpack_require__(6321);
36667
37191
  // EXTERNAL MODULE: ./src/lib/viewers/controls/media/DurationLabels.tsx + 1 modules
@@ -36905,6 +37429,8 @@ class MP3ControlsRoot {
36905
37429
  var waveform_constants = __webpack_require__(4929);
36906
37430
  // EXTERNAL MODULE: ./src/lib/viewers/media/waveform/createWaveformLoader.ts + 1 modules
36907
37431
  var createWaveformLoader = __webpack_require__(4971);
37432
+ // EXTERNAL MODULE: ./src/lib/viewers/media/waveform/range.ts
37433
+ var range = __webpack_require__(4253);
36908
37434
  // EXTERNAL MODULE: ./src/lib/viewers/media/waveform/validateWaveformPayload.ts
36909
37435
  var validateWaveformPayload = __webpack_require__(1053);
36910
37436
  ;// ./src/lib/viewers/media/MP3.scss
@@ -36926,6 +37452,8 @@ function MP3Viewer_toPrimitive(t, r) { if ("object" != typeof t || !t) return t;
36926
37452
 
36927
37453
 
36928
37454
 
37455
+
37456
+
36929
37457
  const CSS_CLASS_MP3 = 'bp-media-mp3';
36930
37458
  const AUDIO_V2_SHUTTLE_RATES = [2, 4, 8, 16];
36931
37459
  const AUDIO_V2_SHUTTLE_TICK_MS = 50;
@@ -37034,6 +37562,84 @@ class MP3Viewer extends media_MediaBaseViewer {
37034
37562
  }
37035
37563
  this.renderUI();
37036
37564
  });
37565
+ MP3Viewer_defineProperty(this, "handleCommentRangeDraft", draft => {
37566
+ if (this.isCommentRangeDragging || !isValidCommentRangeDraft(draft)) {
37567
+ return;
37568
+ }
37569
+ // Checkbox-on sends a collapsed playhead. A waveform-drawn span should
37570
+ // become that timestamp value instead of collapsing back to the playhead.
37571
+ const local = this.commentRangeDraft;
37572
+ if (local && !(0,range/* isRangeCollapsed */.BT)(local) && draft.endMs == null) {
37573
+ if (!this.isCommentRangeTimestampActive) {
37574
+ this.isCommentRangeTimestampActive = true;
37575
+ this.emit(EVENT_COMMENT_RANGE_DRAFT_CHANGE, {
37576
+ endMs: local.endMs,
37577
+ startMs: local.startMs
37578
+ });
37579
+ }
37580
+ this.syncCommentRangeLoop();
37581
+ this.renderUI();
37582
+ return;
37583
+ }
37584
+ this.isCommentRangeTimestampActive = true;
37585
+ this.commentRangeDraft = {
37586
+ endMs: draft.endMs == null ? null : draft.endMs,
37587
+ startMs: draft.startMs
37588
+ };
37589
+ this.syncCommentRangeLoop();
37590
+ this.renderUI();
37591
+ });
37592
+ MP3Viewer_defineProperty(this, "handleCommentRangeDraftClear", () => {
37593
+ this.commentRangeDraft = null;
37594
+ this.isCommentRangeDragging = false;
37595
+ this.isCommentRangeTimestampActive = false;
37596
+ this.syncCommentRangeLoop();
37597
+ this.renderUI();
37598
+ });
37599
+ MP3Viewer_defineProperty(this, "handleCommentRangeChange", range => {
37600
+ if (!range || !Number.isFinite(range.startMs) || !Number.isFinite(range.endMs) || range.endMs <= range.startMs) {
37601
+ return;
37602
+ }
37603
+ this.commentRangeDraft = {
37604
+ endMs: range.endMs,
37605
+ startMs: range.startMs
37606
+ };
37607
+ if (this.isCommentRangeTimestampActive) {
37608
+ this.emit(EVENT_COMMENT_RANGE_DRAFT_CHANGE, {
37609
+ endMs: range.endMs,
37610
+ startMs: range.startMs
37611
+ });
37612
+ }
37613
+ this.syncCommentRangeLoop();
37614
+ this.renderUI();
37615
+ });
37616
+ MP3Viewer_defineProperty(this, "handleCommentRangeDragCreate", () => {
37617
+ const draft = this.commentRangeDraft;
37618
+ if (!draft || (0,range/* isRangeCollapsed */.BT)(draft)) {
37619
+ return;
37620
+ }
37621
+ this.isCommentRangeTimestampActive = true;
37622
+ this.emit(EVENT_COMMENT_RANGE_DRAG_CREATE, {
37623
+ endMs: draft.endMs,
37624
+ startMs: draft.startMs
37625
+ });
37626
+ });
37627
+ MP3Viewer_defineProperty(this, "handleCommentRangeDragChange", isDragging => {
37628
+ this.isCommentRangeDragging = !!isDragging;
37629
+ });
37630
+ MP3Viewer_defineProperty(this, "handleCommentRangeClear", () => {
37631
+ if (!this.commentRangeDraft || this.commentRangeDraft.endMs == null) {
37632
+ return;
37633
+ }
37634
+ const notifyHost = this.isCommentRangeTimestampActive;
37635
+ this.commentRangeDraft = null;
37636
+ this.isCommentRangeTimestampActive = false;
37637
+ if (notifyHost) {
37638
+ this.emit(EVENT_COMMENT_RANGE_DRAFT_DISMISS);
37639
+ }
37640
+ this.syncCommentRangeLoop();
37641
+ this.renderUI();
37642
+ });
37037
37643
  MP3Viewer_defineProperty(this, "handleCommentMarkerClick", marker => {
37038
37644
  this.exitShuttle();
37039
37645
  this.hostSelectedMarkerId = marker.id;
@@ -37074,6 +37680,7 @@ class MP3Viewer extends media_MediaBaseViewer {
37074
37680
  this.isAudioPlayerV2 = this.getIsAudioPlayerV2();
37075
37681
  this.waveformPeaks = [];
37076
37682
  this.waveformPeaksSource = null;
37683
+ this.isWaveformConversionPolling = false;
37077
37684
  this.waveformDurationSec = 0;
37078
37685
  if (this.isAudioPlayerV2) {
37079
37686
  this.wrapperEl.classList.add('bp-media--v2');
@@ -37083,16 +37690,21 @@ class MP3Viewer extends media_MediaBaseViewer {
37083
37690
  // Listen on the loading shell. The waveform is on
37084
37691
  // screen before the audio blob is playable; getViewer() is still null.
37085
37692
  this.bindCommentMarkersListener();
37693
+ this.bindCommentRangeDraftListeners();
37086
37694
  }
37087
37695
 
37088
37696
  // Audio element
37089
37697
  this.mediaEl = this.mediaContainerEl.appendChild(document.createElement('audio'));
37090
37698
  this.mediaEl.setAttribute('preload', 'auto');
37091
37699
  this.commentMarkers = [];
37700
+ this.commentRangeDraft = null;
37092
37701
  this.hostSelectedMarkerId = null;
37702
+ this.isCommentRangeDragging = false;
37703
+ this.isCommentRangeTimestampActive = false;
37093
37704
  this.shuttleDirection = null;
37094
37705
  this.shuttleRate = 0;
37095
37706
  this.reverseShuttleTimer = 0;
37707
+ this.commentRangeLoopTimer = 0;
37096
37708
  this.keyboardVolumeStep = 0;
37097
37709
  this.keyboardZoomStep = 0;
37098
37710
  }
@@ -37103,6 +37715,15 @@ class MP3Viewer extends media_MediaBaseViewer {
37103
37715
  this.removeListener('comment_markers', this.handleCommentMarkersUpdated);
37104
37716
  this.addListener('comment_markers', this.handleCommentMarkersUpdated);
37105
37717
  }
37718
+ bindCommentRangeDraftListeners() {
37719
+ if (!this.isAudioPlayerV2) {
37720
+ return;
37721
+ }
37722
+ this.removeListener(EVENT_COMMENT_RANGE_DRAFT, this.handleCommentRangeDraft);
37723
+ this.removeListener(EVENT_COMMENT_RANGE_DRAFT_CLEAR, this.handleCommentRangeDraftClear);
37724
+ this.addListener(EVENT_COMMENT_RANGE_DRAFT, this.handleCommentRangeDraft);
37725
+ this.addListener(EVENT_COMMENT_RANGE_DRAFT_CLEAR, this.handleCommentRangeDraftClear);
37726
+ }
37106
37727
 
37107
37728
  /**
37108
37729
  * Audio player v2 (waveform). Off unless the host enables `audioPlayerV2`
@@ -37241,8 +37862,10 @@ class MP3Viewer extends media_MediaBaseViewer {
37241
37862
  if (this.mediaEl && this.shuttleDirection === 'forward') {
37242
37863
  this.mediaEl.playbackRate = this.shuttleRate;
37243
37864
  }
37865
+ this.scheduleCommentRangeLoopWrap();
37244
37866
  }).catch(() => {});
37245
37867
  }
37868
+ this.scheduleCommentRangeLoopWrap(true);
37246
37869
  }
37247
37870
  startReverseShuttle(rate) {
37248
37871
  this.stopReverseShuttle();
@@ -37252,12 +37875,13 @@ class MP3Viewer extends media_MediaBaseViewer {
37252
37875
  this.pause(undefined, true);
37253
37876
  this.handleRate();
37254
37877
  this.reverseShuttleTimer = window.setInterval(() => {
37255
- if (!this.mediaEl || this.mediaEl.currentTime <= 0) {
37878
+ const floor = this.clampTimeToOpenCommentRange(0);
37879
+ if (!this.mediaEl || this.mediaEl.currentTime <= floor) {
37256
37880
  this.exitShuttle();
37257
37881
  return;
37258
37882
  }
37259
37883
  this.quickSeek(-(this.shuttleRate * AUDIO_V2_SHUTTLE_TICK_MS / 1000));
37260
- if (this.mediaEl.currentTime <= 0) {
37884
+ if (this.mediaEl.currentTime <= floor) {
37261
37885
  this.exitShuttle();
37262
37886
  }
37263
37887
  }, AUDIO_V2_SHUTTLE_TICK_MS);
@@ -37289,9 +37913,11 @@ class MP3Viewer extends media_MediaBaseViewer {
37289
37913
  if (this.controls) {
37290
37914
  this.renderUI();
37291
37915
  }
37916
+ this.scheduleCommentRangeLoopWrap();
37292
37917
  return;
37293
37918
  }
37294
37919
  super.handleRate();
37920
+ this.scheduleCommentRangeLoopWrap();
37295
37921
  }
37296
37922
  jumpToCommentMarker(direction) {
37297
37923
  if (!this.mediaEl) {
@@ -37362,7 +37988,7 @@ class MP3Viewer extends media_MediaBaseViewer {
37362
37988
  * @return {Promise<{ default: Function }>} MP3ControlsV2 module
37363
37989
  */
37364
37990
  importV2Controls() {
37365
- return Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 7450));
37991
+ return Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 8862));
37366
37992
  }
37367
37993
 
37368
37994
  /**
@@ -37384,7 +38010,13 @@ class MP3Viewer extends media_MediaBaseViewer {
37384
38010
  * @inheritdoc
37385
38011
  */
37386
38012
  destroy() {
38013
+ this.clearCommentRangeLoopTimer();
37387
38014
  this.removeListener('comment_markers', this.handleCommentMarkersUpdated);
38015
+ this.removeListener(EVENT_COMMENT_RANGE_DRAFT, this.handleCommentRangeDraft);
38016
+ this.removeListener(EVENT_COMMENT_RANGE_DRAFT_CLEAR, this.handleCommentRangeDraftClear);
38017
+ this.commentRangeDraft = null;
38018
+ this.isCommentRangeDragging = false;
38019
+ this.isCommentRangeTimestampActive = false;
37388
38020
  this.stopReverseShuttle();
37389
38021
  this.shuttleDirection = null;
37390
38022
  this.shuttleRate = 0;
@@ -37397,6 +38029,11 @@ class MP3Viewer extends media_MediaBaseViewer {
37397
38029
  */
37398
38030
  load() {
37399
38031
  if (this.isAudioPlayerV2) {
38032
+ // A file-version switch is a clear, not a resync of the previous draft.
38033
+ this.commentRangeDraft = null;
38034
+ this.isCommentRangeDragging = false;
38035
+ this.isCommentRangeTimestampActive = false;
38036
+ this.syncCommentRangeLoop();
37400
38037
  this.showAudioLoadingShell();
37401
38038
  this.startConversionWaveformLoad();
37402
38039
  }
@@ -37470,6 +38107,48 @@ class MP3Viewer extends media_MediaBaseViewer {
37470
38107
  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
38108
  }
37472
38109
 
38110
+ /**
38111
+ * @return {Object|null} waveform conversion representation
38112
+ */
38113
+ getWaveformRepresentation() {
38114
+ const {
38115
+ file
38116
+ } = this.options;
38117
+ return file?.representations?.entries ? getRepresentation(file, constants/* WAVEFORM_REP_NAME */.u4) : null;
38118
+ }
38119
+
38120
+ /**
38121
+ * @return {string|null} conversion representation state
38122
+ */
38123
+ getWaveformConversionState() {
38124
+ const conversionStatus = this.getWaveformRepresentation()?.status;
38125
+ return conversionStatus && typeof conversionStatus === 'object' ? conversionStatus.state : null;
38126
+ }
38127
+
38128
+ /**
38129
+ * Conversion JSON is already (or about to be) fetchable.
38130
+ *
38131
+ * @return {boolean}
38132
+ */
38133
+ isConversionWaveformAvailable() {
38134
+ const conversionState = this.getWaveformConversionState();
38135
+ return conversionState === constants/* STATUS_SUCCESS */.$g || conversionState === constants/* STATUS_VIEWABLE */.Re;
38136
+ }
38137
+
38138
+ /**
38139
+ * Generating chip while conversion RepStatus is polling. Hidden when the
38140
+ * rep is already success/viewable, when polling finishes, when conversion
38141
+ * errors, and once any source applies peaks.
38142
+ *
38143
+ * @return {boolean}
38144
+ */
38145
+ isGeneratingWaveform() {
38146
+ if (!this.isAudioPlayerV2 || this.destroyed || this.waveformPeaksSource) {
38147
+ return false;
38148
+ }
38149
+ return !!this.isWaveformConversionPolling;
38150
+ }
38151
+
37473
38152
  /**
37474
38153
  * @param {number} durationSec
37475
38154
  * @return {boolean}
@@ -37506,30 +38185,40 @@ class MP3Viewer extends media_MediaBaseViewer {
37506
38185
  if (!this.isAudioPlayerV2 || this.destroyed) {
37507
38186
  return;
37508
38187
  }
37509
- const {
37510
- file
37511
- } = this.options;
37512
- const waveform = file?.representations?.entries ? getRepresentation(file, constants/* WAVEFORM_REP_NAME */.u4) : null;
38188
+ const waveform = this.getWaveformRepresentation();
37513
38189
  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) {
38190
+ const conversionState = this.getWaveformConversionState();
38191
+ if (!waveform || !template || !conversionState || conversionState === constants/* STATUS_ERROR */.uD) {
37517
38192
  this.startClientWaveformDecode();
38193
+ this.renderUI();
37518
38194
  return;
37519
38195
  }
37520
38196
  this.abortConversionWaveformLoad();
37521
38197
  this.waveformStatus = this.getRepStatus(waveform);
37522
38198
  this.waveformStatus.removeListener('conversionpending', this.resetLoadTimeout);
37523
38199
  const status = this.waveformStatus;
38200
+ const shouldPollConversion = !this.isConversionWaveformAvailable();
37524
38201
  try {
38202
+ if (shouldPollConversion) {
38203
+ this.isWaveformConversionPolling = true;
38204
+ this.renderUI();
38205
+ }
37525
38206
  await status.getPromise();
38207
+ this.isWaveformConversionPolling = false;
37526
38208
  if (this.destroyed || this.waveformStatus !== status || this.waveformPeaksSource) {
37527
38209
  return;
37528
38210
  }
38211
+ if (shouldPollConversion) {
38212
+ this.renderUI();
38213
+ }
37529
38214
  const result = await this.loadConversionWaveformPayload(template);
37530
38215
  this.handleConversionWaveformResult(result, status);
37531
38216
  } catch {
38217
+ this.isWaveformConversionPolling = false;
37532
38218
  this.startClientWaveformDecode();
38219
+ if (shouldPollConversion) {
38220
+ this.renderUI();
38221
+ }
37533
38222
  }
37534
38223
  }
37535
38224
 
@@ -37580,6 +38269,7 @@ class MP3Viewer extends media_MediaBaseViewer {
37580
38269
  }
37581
38270
  this.waveformPeaks = payload.peaks;
37582
38271
  this.waveformPeaksSource = source;
38272
+ this.isWaveformConversionPolling = false;
37583
38273
  this.isWaveformDecodeRetryPending = false;
37584
38274
  if (source === 'conversion') {
37585
38275
  this.waveformDurationSec = payload.durationSec;
@@ -37748,6 +38438,7 @@ class MP3Viewer extends media_MediaBaseViewer {
37748
38438
  });
37749
38439
  }
37750
38440
  this.bindCommentMarkersListener();
38441
+ this.bindCommentRangeDraftListeners();
37751
38442
  this.renderUI();
37752
38443
  }
37753
38444
  /**
@@ -37764,11 +38455,161 @@ class MP3Viewer extends media_MediaBaseViewer {
37764
38455
  }
37765
38456
  this.pendingHostSelectedSeek = null;
37766
38457
  this.exitShuttle();
38458
+ this.handleCommentRangeClear();
37767
38459
  this.mediaEl.pause();
37768
38460
  if (this.mediaEl.currentTime !== marker.time) {
37769
38461
  this.mediaEl.currentTime = marker.time;
37770
38462
  }
37771
38463
  }
38464
+
38465
+ /**
38466
+ * Seconds of the open draft span, or null when collapsed / absent.
38467
+ * Collapsed drafts (`endMs: null`) do not confine playback.
38468
+ *
38469
+ * @return {{endSec: number, startSec: number}|null}
38470
+ */
38471
+ getOpenCommentRangeSeconds() {
38472
+ const draft = this.commentRangeDraft;
38473
+ if ((0,range/* isRangeCollapsed */.BT)(draft) || !Number.isFinite(draft.startMs) || !Number.isFinite(draft.endMs)) {
38474
+ return null;
38475
+ }
38476
+ return {
38477
+ endSec: draft.endMs / 1000,
38478
+ startSec: draft.startMs / 1000
38479
+ };
38480
+ }
38481
+
38482
+ /**
38483
+ * Keep a seek inside the open draft. Waveform click-outside and host
38484
+ * comment seeks dismiss first, then seek; clamp does not apply then.
38485
+ *
38486
+ * @param {number} time
38487
+ * @return {number}
38488
+ */
38489
+ clampTimeToOpenCommentRange(time) {
38490
+ const range = this.getOpenCommentRangeSeconds();
38491
+ if (!range || !Number.isFinite(time)) {
38492
+ return time;
38493
+ }
38494
+ return Math.min(Math.max(time, range.startSec), range.endSec);
38495
+ }
38496
+ clearCommentRangeLoopTimer() {
38497
+ if (this.commentRangeLoopTimer) {
38498
+ window.clearTimeout(this.commentRangeLoopTimer);
38499
+ this.commentRangeLoopTimer = 0;
38500
+ }
38501
+ }
38502
+
38503
+ /**
38504
+ * Wrap when the remaining time to end elapses. Recalculate after play, seek,
38505
+ * rate change, or a draft resize.
38506
+ *
38507
+ * @param {boolean} [force] schedule even if the element still reports paused
38508
+ * @return {void}
38509
+ */
38510
+ scheduleCommentRangeLoopWrap(force = false) {
38511
+ this.clearCommentRangeLoopTimer();
38512
+ const range = this.getOpenCommentRangeSeconds();
38513
+ if (!range || !this.mediaEl || !force && this.mediaEl.paused) {
38514
+ return;
38515
+ }
38516
+ const rate = this.mediaEl.playbackRate > 0 ? this.mediaEl.playbackRate : 1;
38517
+ const remainingSec = range.endSec - this.mediaEl.currentTime;
38518
+ this.commentRangeLoopTimer = window.setTimeout(() => {
38519
+ this.commentRangeLoopTimer = 0;
38520
+ const openRange = this.getOpenCommentRangeSeconds();
38521
+ if (!openRange || !this.mediaEl || this.mediaEl.paused) {
38522
+ return;
38523
+ }
38524
+ if (this.mediaEl.currentTime !== openRange.startSec) {
38525
+ this.mediaEl.currentTime = openRange.startSec;
38526
+ }
38527
+ this.scheduleCommentRangeLoopWrap();
38528
+ }, Math.max(0, remainingSec / rate * 1000));
38529
+ }
38530
+
38531
+ /**
38532
+ * Update the playback loop to match the current range after it arrives, resizes,
38533
+ * or clears.
38534
+ *
38535
+ * @return {void}
38536
+ */
38537
+ syncCommentRangeLoop() {
38538
+ this.clearCommentRangeLoopTimer();
38539
+ const range = this.getOpenCommentRangeSeconds();
38540
+ if (!range || !this.mediaEl || this.mediaEl.paused) {
38541
+ return;
38542
+ }
38543
+ this.enforceCommentRangePlayback(range);
38544
+ }
38545
+
38546
+ /**
38547
+ * Keep playback inside the open span. A playhead outside [start, end) jumps
38548
+ * to start, then the wrap timer is armed.
38549
+ *
38550
+ * @param {{endSec: number, startSec: number}} range Open draft in seconds
38551
+ * @return {void}
38552
+ */
38553
+ enforceCommentRangePlayback(range) {
38554
+ const {
38555
+ endSec,
38556
+ startSec
38557
+ } = range;
38558
+ const currPlayhead = this.mediaEl.currentTime;
38559
+ const nextPlayhead = currPlayhead >= startSec && currPlayhead < endSec ? currPlayhead : startSec;
38560
+ if (nextPlayhead !== currPlayhead) {
38561
+ this.mediaEl.currentTime = nextPlayhead;
38562
+ }
38563
+ this.scheduleCommentRangeLoopWrap();
38564
+ }
38565
+
38566
+ /**
38567
+ * Play. An open draft loops that span: keep the playhead when it is already
38568
+ * inside, otherwise jump to start. Does not use play(start, end) because
38569
+ * that pauses at end.
38570
+ *
38571
+ * @inheritdoc
38572
+ */
38573
+ play(...args) {
38574
+ const range = this.getOpenCommentRangeSeconds();
38575
+ if (!range || !this.mediaEl) {
38576
+ // Spread so an argument-less call stays argument-less; super.play() skips
38577
+ // playback when it is handed an explicit undefined start.
38578
+ return super.play(...args);
38579
+ }
38580
+ const requested = Number.isFinite(args[0]) ? args[0] : this.mediaEl.currentTime;
38581
+ const next = requested >= range.startSec && requested < range.endSec ? requested : range.startSec;
38582
+ if (this.mediaEl.currentTime !== next) {
38583
+ this.mediaEl.currentTime = next;
38584
+ }
38585
+ const playPromise = super.play();
38586
+ this.scheduleCommentRangeLoopWrap(true);
38587
+ return playPromise;
38588
+ }
38589
+
38590
+ /**
38591
+ * Seek. Keyboard and in-range waveform seeks stay inside an open draft.
38592
+ *
38593
+ * @inheritdoc
38594
+ */
38595
+ setMediaTime(time) {
38596
+ super.setMediaTime(this.clampTimeToOpenCommentRange(time));
38597
+ this.scheduleCommentRangeLoopWrap();
38598
+ }
38599
+
38600
+ /**
38601
+ * If the range ends at file end, wrap instead of resetting the play icon.
38602
+ *
38603
+ * @inheritdoc
38604
+ */
38605
+ mediaendHandler() {
38606
+ if (this.getOpenCommentRangeSeconds()) {
38607
+ // `ended` fires after the element is paused; play() wraps and restarts.
38608
+ this.play();
38609
+ return;
38610
+ }
38611
+ super.mediaendHandler();
38612
+ }
37772
38613
  /**
37773
38614
  * @inheritdoc
37774
38615
  */
@@ -37806,12 +38647,20 @@ class MP3Viewer extends media_MediaBaseViewer {
37806
38647
  const Mp3ControlsV2 = this.MP3ControlsV2;
37807
38648
  this.controls.render(/*#__PURE__*/external_react_["default"].createElement(Mp3ControlsV2, MP3Viewer_extends({}, sharedProps, {
37808
38649
  commentMarkers: this.commentMarkers || [],
38650
+ commentRangeDraft: this.commentRangeDraft || null,
37809
38651
  hasStartedPlayback: !!this.userRequestedPlay,
38652
+ isGeneratingWaveform: this.isGeneratingWaveform(),
37810
38653
  keyboardVolumeStep: this.keyboardVolumeStep,
37811
38654
  keyboardZoomStep: this.keyboardZoomStep,
37812
38655
  mediaEl: this.mediaEl,
37813
38656
  onCommentMarkerClick: this.handleCommentMarkerClick,
37814
- peaks: this.waveformPeaks
38657
+ onCommentRangeChange: this.handleCommentRangeChange,
38658
+ onCommentRangeClear: this.handleCommentRangeClear,
38659
+ onCommentRangeDragChange: this.handleCommentRangeDragChange,
38660
+ onCommentRangeDragCreate: this.handleCommentRangeDragCreate,
38661
+ onPlayNextChange: this.setPlayNext,
38662
+ peaks: this.waveformPeaks,
38663
+ playNext: this.isPlayNextEnabled()
37815
38664
  })));
37816
38665
  return;
37817
38666
  }
@@ -39892,12 +40741,14 @@ function VideoControlsV2({
39892
40741
  onGuideChange,
39893
40742
  onMuteChange,
39894
40743
  onPlayPause,
40744
+ onPlayNextChange,
39895
40745
  onQualityChange,
39896
40746
  onRateChange,
39897
40747
  onSubtitleChange,
39898
40748
  onSubtitlesToggle,
39899
40749
  onTimeChange,
39900
40750
  onVolumeChange,
40751
+ playNext,
39901
40752
  quality,
39902
40753
  rate,
39903
40754
  subtitle,
@@ -39986,9 +40837,11 @@ function VideoControlsV2({
39986
40837
  onAudioTrackChange: onAudioTrackChange,
39987
40838
  onAutoplayChange: onAutoplayChange,
39988
40839
  onGuideChange: onGuideChange,
40840
+ onPlayNextChange: onPlayNextChange,
39989
40841
  onQualityChange: onQualityChange,
39990
40842
  onRateChange: onRateChange,
39991
40843
  onSubtitleChange: onSubtitleChange,
40844
+ playNext: playNext,
39992
40845
  quality: quality,
39993
40846
  rate: rate,
39994
40847
  subtitle: subtitle,
@@ -40146,7 +40999,10 @@ class MP4Viewer extends media_VideoBaseViewer {
40146
40999
  volume: this.mediaEl.volume
40147
41000
  };
40148
41001
  if (this.isVideoPlayerV2) {
40149
- this.controls.render(/*#__PURE__*/external_react_["default"].createElement(VideoControlsV2, sharedProps));
41002
+ this.controls.render(/*#__PURE__*/external_react_["default"].createElement(VideoControlsV2, MP4Viewer_extends({}, sharedProps, {
41003
+ onPlayNextChange: this.setPlayNext,
41004
+ playNext: this.isPlayNextEnabled()
41005
+ })));
40150
41006
  return;
40151
41007
  }
40152
41008
  this.controls.render(/*#__PURE__*/external_react_["default"].createElement(VideoControls, MP4Viewer_extends({}, sharedProps, {
@@ -41642,7 +42498,10 @@ class DashViewer extends media_VideoBaseViewer {
41642
42498
  this.controls.render(/*#__PURE__*/external_react_["default"].createElement(VideoControlsV2, DashViewer_extends({
41643
42499
  commentMarkers: this.commentMarkers,
41644
42500
  onCommentMarkerClick: this.handleCommentMarkerClick
41645
- }, sharedProps)));
42501
+ }, sharedProps, {
42502
+ onPlayNextChange: this.setPlayNext,
42503
+ playNext: this.isPlayNextEnabled()
42504
+ })));
41646
42505
  return;
41647
42506
  }
41648
42507
  this.controls.render(/*#__PURE__*/external_react_["default"].createElement(VideoControls, DashViewer_extends({}, sharedProps, {
@@ -54310,6 +55169,7 @@ class Preview extends (events_default()) {
54310
55169
  this.viewer.destroy();
54311
55170
  }
54312
55171
  this.viewer = undefined;
55172
+ Preview.resin = null;
54313
55173
  }
54314
55174
 
54315
55175
  /**
@@ -55195,6 +56055,7 @@ class Preview extends (events_default()) {
55195
56055
 
55196
56056
  // Optional resin analytics instance for tracking user interactions
55197
56057
  this.options.resin = options.resin;
56058
+ Preview.resin = options.resin;
55198
56059
 
55199
56060
  // Options that are applicable to certain file ids
55200
56061
  this.options.fileOptions = options.fileOptions || {};
@@ -55570,6 +56431,10 @@ class Preview extends (events_default()) {
55570
56431
  this.emit(data.event, data.data);
55571
56432
  this.emit(lib_events/* VIEWER_EVENT */.Oi.default, data);
55572
56433
  break;
56434
+ case lib_events/* VIEWER_EVENT */.Oi.mediaEndPlayNext:
56435
+ this.emit(data.event, data.data);
56436
+ this.emit(lib_events/* VIEWER_EVENT */.Oi.default, data);
56437
+ break;
55573
56438
  case lib_events/* VIEWER_EVENT */.Oi.error:
55574
56439
  // Do nothing since 'error' event was already caught, and will be emitted
55575
56440
  // as a 'preview_error' event